summary history branches tags files
commit:70de12764470e50513b1d3d2129a3bf7ffbfb38f
author:Trevor Bentley
committer:Trevor Bentley
date:Wed Jul 26 00:10:30 2017 +0200
parents:af33aefb2a6db1a9740dd199bdcf14dc89a20232
Tap support for touchbar "now playing".  Fix crash on unknown play state.
diff --git a/Cargo.toml b/Cargo.toml
line changes: +3/-3
index 56f98c5..d24af5a
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -54,7 +54,7 @@ systray = {path = "deps/systray-rs", version="0.1.1-connectr"}
 [target."cfg(windows)".dependencies.rubrail]
 #version = "0.3"
 path = "deps/rubrail-rs"
-version="0.3.1-rc"
+version="0.4.1-rc"
 default-features=false
 
 [target."cfg(all(unix, not(target_os = \"macos\")))".dependencies]
@@ -62,7 +62,7 @@ default-features=false
 [target."cfg(all(unix, not(target_os = \"macos\")))".dependencies.rubrail]
 #version = "0.3"
 path = "deps/rubrail-rs"
-version = "0.3.1-rc"
+version = "0.4.1-rc"
 default-features = false
 
 [target."cfg(target_os = \"macos\")".dependencies]
@@ -70,7 +70,7 @@ cocoa = "0.8.1"
 objc-foundation = "0.1.1"
 objc_id = "0.1"
 #rubrail = "0.3"
-rubrail = {path = "deps/rubrail-rs", version="0.3.1-rc"}
+rubrail = {path = "deps/rubrail-rs", version="0.4.1-rc"}
 
 [target."cfg(target_os = \"macos\")".dependencies.objc]
 version = "0.2.2"

diff --git a/deps/rubrail-rs b/deps/rubrail-rs
line changes: +1/-1
index c349ef6..83045c6
--- a/deps/rubrail-rs
+++ b/deps/rubrail-rs
@@ -1 +1 @@
-Subproject commit c349ef6da04d5aa8a22c962f85d8154fcdb93d4a
+Subproject commit 83045c6c3df8a0a1390a592159327ab99307a75e

diff --git a/src/http/mod.rs b/src/http/mod.rs
line changes: +2/-2
index f3df4b6..4b522c1
--- a/src/http/mod.rs
+++ b/src/http/mod.rs
@@ -128,7 +128,7 @@ pub fn http(url: &str, query: &str, body: &str,
                 Err(x) => {
                     let result: Result<String,String> = Err(x.description().to_string());
                     #[cfg(feature = "verbose_http")]
-                    println!("HTTP response: err: {}", x.description().to_string());
+                    warn!("HTTP response: err: {}", x.description().to_string());
                     return HttpResponse {code: response, data: result }
                 }
                 _ => {}
@@ -144,7 +144,7 @@ pub fn http(url: &str, query: &str, body: &str,
         Err(x) => { Err(x.utf8_error().description().to_string()) }
     };
     #[cfg(feature = "verbose_http")]
-    println!("HTTP response: {}", result.clone().unwrap());
+    info!("HTTP response: {}", result.clone().unwrap());
     HttpResponse {code: response, data: result }
 }
 

diff --git a/src/main.rs b/src/main.rs
line changes: +73/-19
index 259eeb1..097d0b9
--- a/src/main.rs
+++ b/src/main.rs
@@ -42,10 +42,12 @@ use std::process;
 pub const REFRESH_PERIOD: i64 = 30;
 
 #[allow(dead_code)]
+#[derive(PartialEq, Debug)]
 enum RefreshTime {
     Now,   // immediately
     Soon,  // after ~1 sec
     Later, // don't change whatever the current schedule is
+    Redraw, // instantly, with stale data
 }
 
 #[derive(Serialize, Deserialize, Debug, Clone, Copy)]
@@ -56,6 +58,7 @@ enum CallbackAction {
     SkipPrev,
     Volume,
     Preset,
+    Redraw,
 }
 
 #[derive(Serialize, Deserialize, Debug)]
@@ -128,12 +131,19 @@ impl TScrubberData for TouchbarScrubberData {
     }
 }
 
+enum TouchbarLabelState {
+    TrackArtist,
+    Track,
+    Artist,
+}
+
 #[allow(dead_code)]
 struct TouchbarUI {
     touchbar: Touchbar,
 
     root_bar: rubrail::BarId,
     playing_label: rubrail::ItemId,
+    label_state: Arc<RwLock<TouchbarLabelState>>,
     prev_button: rubrail::ItemId,
     play_pause_button: rubrail::ItemId,
     next_button: rubrail::ItemId,
@@ -165,6 +175,23 @@ impl TouchbarUI {
         }
 
         let playing_label = touchbar.create_label("");
+        let label_state = Arc::new(RwLock::new(TouchbarLabelState::TrackArtist));
+        let cb_label_state = label_state.clone();
+        let tx_clone = tx.clone();
+        touchbar.add_item_tap_gesture(&playing_label, 2, 1, Box::new(move |s| {
+            let mut state = cb_label_state.write().unwrap();
+            *state = match *state {
+                TouchbarLabelState::TrackArtist => TouchbarLabelState::Track,
+                TouchbarLabelState::Track => TouchbarLabelState::Artist,
+                TouchbarLabelState::Artist => TouchbarLabelState::TrackArtist,
+            };
+            let cmd = MenuCallbackCommand {
+                action: CallbackAction::Redraw,
+                sender: *s,
+                data: String::new(),
+            };
+            let _ = tx_clone.send(serde_json::to_string(&cmd).unwrap());
+        }));
 
         // Fade text color to green as finger swipes right across the label, and
         // add a solid white border indicating the label is 'selected' after a
@@ -304,6 +331,7 @@ impl TouchbarUI {
 
             root_bar: root_bar,
             playing_label: playing_label,
+            label_state: label_state,
             prev_button: prev_button,
             play_pause_button: play_pause_button,
             next_button: next_button,
@@ -327,7 +355,11 @@ impl TouchbarUI {
         }
     }
     fn update_now_playing(&mut self, track: &str, artist: &str) {
-        let text = format!("{}\n{}", track, artist);
+        let text = match *self.label_state.read().unwrap() {
+            TouchbarLabelState::TrackArtist =>  format!("{}\n{}", track, artist),
+            TouchbarLabelState::Track =>  format!("{}", track),
+            TouchbarLabelState::Artist =>  format!("{}", artist),
+        };
         self.touchbar.update_label(&self.playing_label, &text);
         self.touchbar.update_label_width(&self.playing_label, 250)
     }
@@ -372,25 +404,37 @@ fn fill_menu<T: TStatusBar>(app: &mut ConnectrApp,
     let device_list = device_list.as_ref().unwrap();
     let player_state = player_state.as_ref().unwrap();
 
+    let track = match player_state.item {
+        Some(ref item) => item.name.clone(),
+        _ => "unknown".to_string()
+    };
+    let artist = match player_state.item {
+        Some(ref item) => item.artists[0].name.clone(),
+        _ => "unknown".to_string()
+    };
+    let album = match player_state.item {
+        Some(ref item) => item.album.name.clone(),
+        _ => "unknown".to_string()
+    };
+
     println!("Playback State:\n{}", player_state);
-    let play_str = format!("{}\n{}\n{}",
-                           &player_state.item.name,
-                           &player_state.item.artists[0].name,
-                           &player_state.item.album.name);
+    let play_str = format!("{}\n{}\n{}", track, artist, album);
     status.set_tooltip(&play_str);
 
     status.add_label("Now Playing:");
     status.add_separator();
     //status.add_label(&player_state.item.name);
-    status.add_label(&format!("{:<50}", &player_state.item.name));
-    status.add_label(&format!("{:<50}", &player_state.item.artists[0].name));
-    status.add_label(&format!("{:<50}", &player_state.item.album.name));
-    touchbar.update_now_playing(&player_state.item.name,
-                                &player_state.item.artists[0].name);
-
-    let ms = player_state.item.duration_ms;
-    let min = ms / 1000 / 60;
-    let sec = (ms - (min * 60 * 1000)) / 1000;
+    status.add_label(&format!("{:<50}", track));
+    status.add_label(&format!("{:<50}", artist));
+    status.add_label(&format!("{:<50}", album));
+    touchbar.update_now_playing(&track, &artist);
+
+    let duration_ms = match player_state.item {
+        Some(ref item) => item.duration_ms,
+        _ => 0,
+    };
+    let min = duration_ms / 1000 / 60;
+    let sec = (duration_ms - (min * 60 * 1000)) / 1000;
     status.add_label(&format!("{:<50}", format!("{}:{:02}", min, sec)));
 
     status.add_label("");
@@ -571,7 +615,7 @@ fn handle_callback(player_state: Option<&connectr::PlayerState>,
                    spotify: &mut connectr::SpotifyConnectr,
                    cmd: &MenuCallbackCommand) -> RefreshTime {
     info!("Executed action: {:?}", cmd.action);
-    let refresh = RefreshTime::Soon;
+    let mut refresh = RefreshTime::Soon;
     match cmd.action {
         CallbackAction::SelectDevice => {
             require(spotify.transfer(cmd.data.clone(), true));
@@ -597,6 +641,9 @@ fn handle_callback(player_state: Option<&connectr::PlayerState>,
             let vol = cmd.data.parse::<u32>().unwrap();
             require(spotify.volume(vol));
         }
+        CallbackAction::Redraw => {
+            refresh = RefreshTime::Redraw;
+        }
     }
     refresh
 }
@@ -606,16 +653,20 @@ fn refresh_time(player_state: Option<&connectr::PlayerState>, now: i64) -> i64 {
         Some(ref state) => {
             match state.is_playing {
                 true => {
+                    let duration_ms = match state.item {
+                        Some(ref item) => item.duration_ms,
+                        _ => 0,
+                    };
                     let track_end = match state.progress_ms {
                         Some(prog) => {
-                            if prog < state.item.duration_ms {
-                                state.item.duration_ms - prog
+                            if prog < duration_ms {
+                                duration_ms - prog
                             }
                             else {
                                 0
                             }
                         },
-                        None => state.item.duration_ms,
+                        None => duration_ms,
                     } as i64;
                     // Refresh 1 second after track ends
                     track_end/1000 + 1
@@ -698,7 +749,10 @@ fn create_spotify_thread(rx_cmd: Receiver<String>) -> SpotifyThread {
                 refresh_time_utc = match refresh_strategy {
                     RefreshTime::Now => now - 1,
                     RefreshTime::Soon => now + 1,
-                    RefreshTime::Later => refresh_time_utc,
+                    _ => refresh_time_utc,
+                };
+                if refresh_strategy == RefreshTime::Redraw {
+                    let _ = tx.send(String::new());
                 }
             }
 

diff --git a/src/webapi/mod.rs b/src/webapi/mod.rs
line changes: +20/-9
index 328a923..04319ff
--- a/src/webapi/mod.rs
+++ b/src/webapi/mod.rs
@@ -115,11 +115,11 @@ pub struct ConnectContext {
 
 #[derive(Deserialize, Debug)]
 pub struct PlayerState {
-    pub timestamp: u64,
+    pub timestamp: i64,
     pub device: ConnectDevice,
     pub progress_ms: Option<u32>,
     pub is_playing: bool,
-    pub item: ConnectPlaybackItem,
+    pub item: Option<ConnectPlaybackItem>,
     pub shuffle_state: bool,
     pub repeat_state: String,
     pub context: Option<ConnectContext>,
@@ -139,12 +139,20 @@ impl fmt::Display for PlayerState {
             Some(x) => (x as f64)/1000.0,
             None => 0.0,
         };
-        let duration: f64 = (self.item.duration_ms as f64) / 1000.0;
-        let progress: f64 = position/duration*100.0;
-        write!(f, "{} on {} [Volume {}%]\n{} <{}>\n{}s / {}s ({:.1}%)\n",
-               play_state, self.device.name, volume,
-               &self.item.name, &self.item.uri,
-               position, duration, progress)
+        if let Some(ref item) = self.item {
+            let duration: f64 = (item.duration_ms as f64) / 1000.0;
+            let progress: f64 = position/duration*100.0;
+            write!(f, "{} on {} [Volume {}%]\n{} <{}>\n{}s / {}s ({:.1}%)\n",
+                   play_state, self.device.name, volume,
+                   &item.name, &item.uri,
+                   position, duration, progress)
+        }
+        else {
+            write!(f, "{} on {} [Volume {}%]\n{} <{}>\n{}s / {}s ({:.1}%)\n",
+                   play_state, self.device.name, volume,
+                   "unknown", "unknown",
+                   position, 0, 0)
+        }
     }
 }
 
@@ -464,7 +472,10 @@ impl<'a> SpotifyConnectr<'a> {
         let json_response = http::http(self.api.get().player_state, "", "",
                                        http::HttpMethod::GET, self.bearer_token());
         match json_response.code {
-            Some(200) => serde_json::from_str(&json_response.data.unwrap()).unwrap(),
+            Some(200) => match serde_json::from_str(&json_response.data.unwrap()) {
+                Ok(json) => json,
+                Err(err) => { info!("json error: {}", err); None },
+            },
             Some(401) => {
                 warn!("Access token invalid.  Attempting to reauthenticate.");
                 self.refresh_access_token();