From c5a0a73077f3a9b11bf36c5fb8ae9d4e34d44ef7 Mon Sep 17 00:00:00 2001 From: Rasmus Larsen Date: Tue, 7 May 2019 23:37:27 +0200 Subject: [PATCH 01/12] Ignore files created by IDEA-based IDEs --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ac72d99..aaaf735 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Generated by Cargo # will have compiled files and executables /target/ +/.idea/ # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html From de1dea5c6893e60c2fb5788665c826422c4ca50b Mon Sep 17 00:00:00 2001 From: Rasmus Larsen Date: Tue, 7 May 2019 23:39:15 +0200 Subject: [PATCH 02/12] Add a keybind and shortcut to share the currently playing song --- Cargo.toml | 1 + src/commands.rs | 25 +++++++++++++++++++++++-- src/main.rs | 1 + 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6750d87..b5d53b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ unicode-width = "0.1.5" dbus = { version = "0.6.4", optional = true } rand = "0.6.5" webbrowser = "0.5" +clipboard = "0.5.0" [dependencies.rspotify] git = "https://github.com/samrayleung/rspotify" diff --git a/src/commands.rs b/src/commands.rs index 819fc4f..5265d41 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -6,6 +6,7 @@ use cursive::event::{Event, Key}; use cursive::views::ViewRef; use cursive::Cursive; +use clipboard::{ClipboardContext, ClipboardProvider}; use library::Library; use queue::{Queue, RepeatSetting}; use spotify::Spotify; @@ -197,6 +198,25 @@ impl CommandManager { ); } + { + let queue = queue.clone(); + self.register_command( + "share", + Some(Box::new(move |_, _| { + if let Some(url) = queue + .get_current() + .and_then(|t| t.id) + .map(|id| format!("https://open.spotify.com/track/{}", id)) + { + let mut ctx: ClipboardContext = ClipboardProvider::new().unwrap(); + ctx.set_contents(url).unwrap(); + } + + Ok(None) + })), + ) + } + { let spotify = spotify.clone(); self.register_command( @@ -284,9 +304,9 @@ impl CommandManager { }); } - pub fn register_keybindings<'a>( + pub fn register_keybindings( this: Arc, - cursive: &'a mut Cursive, + cursive: &mut Cursive, keybindings: Option>, ) { let mut kb = Self::default_keybindings(); @@ -321,6 +341,7 @@ impl CommandManager { kb.insert(",".into(), "seek -500".into()); kb.insert("r".into(), "repeat".into()); kb.insert("z".into(), "shuffle".into()); + kb.insert("m".into(), "share".into()); kb.insert("F1".into(), "focus queue".into()); kb.insert("F2".into(), "focus search".into()); diff --git a/src/main.rs b/src/main.rs index 78c35f4..e9ff401 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ extern crate clap; extern crate crossbeam_channel; #[macro_use] extern crate cursive; +extern crate clipboard; extern crate directories; extern crate failure; extern crate futures; From 6ffaa8e8a01863267a90786cd3f98b4d55a67b3e Mon Sep 17 00:00:00 2001 From: Rasmus Larsen Date: Wed, 8 May 2019 01:29:03 +0200 Subject: [PATCH 03/12] Update README with information about new keybind --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index aa617f7..aabf145 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,7 @@ have them configurable. * `r` to toggle repeat mode * `z` to toggle shuffle playback * `q` quits ncspot +* `m` copies a sharable URL to the song to the system clipboard You can also open a Vim style commandprompt using `:`, the following commands are supported: @@ -65,6 +66,7 @@ are supported: * `stop`: Stop playback * `previous`/`next`: Play previous/next track * `clear`: Clear playlist +* `share`: Copies a sharable URL to the song to the system clipboard The screens can be opened with `queue`, `search`, `playlists` and `log`, whereas `search` can be supplied with a search term that will be entered after opening From a145d73072e2afb75fe877085e154d3f23e53889 Mon Sep 17 00:00:00 2001 From: Rasmus Larsen Date: Wed, 8 May 2019 18:45:10 +0200 Subject: [PATCH 04/12] Move keybind to --- README.md | 2 +- src/commands.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index aabf145..25ffc16 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ have them configurable. * `r` to toggle repeat mode * `z` to toggle shuffle playback * `q` quits ncspot -* `m` copies a sharable URL to the song to the system clipboard +* `x` copies a sharable URL to the song to the system clipboard You can also open a Vim style commandprompt using `:`, the following commands are supported: diff --git a/src/commands.rs b/src/commands.rs index 5265d41..84d50dc 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -341,7 +341,7 @@ impl CommandManager { kb.insert(",".into(), "seek -500".into()); kb.insert("r".into(), "repeat".into()); kb.insert("z".into(), "shuffle".into()); - kb.insert("m".into(), "share".into()); + kb.insert("x".into(), "share".into()); kb.insert("F1".into(), "focus queue".into()); kb.insert("F2".into(), "focus search".into()); From 4897d979313cc0ad7c8a8acd1a459b4f5aa3cbd2 Mon Sep 17 00:00:00 2001 From: Rasmus Larsen Date: Thu, 9 May 2019 16:04:07 +0200 Subject: [PATCH 05/12] Move URL handling to relevant structs and share selected song --- src/album.rs | 6 ++++++ src/artist.rs | 6 ++++++ src/commands.rs | 21 +++++++++++---------- src/playlist.rs | 7 +++++++ src/track.rs | 6 ++++++ src/traits.rs | 1 + src/ui/listview.rs | 21 +++++++++++++++++++++ 7 files changed, 58 insertions(+), 10 deletions(-) diff --git a/src/album.rs b/src/album.rs index 9d5699b..93016d4 100644 --- a/src/album.rs +++ b/src/album.rs @@ -199,6 +199,12 @@ impl ListItem for Album { Some(AlbumView::new(queue, library, self).as_boxed_view_ext()) } + fn share_url(&self) -> Option { + self.id + .clone() + .map(|id| format!("https://open.spotify.com/album/{}", id)) + } + fn artist(&self) -> Option { Some(Artist::new( self.artist_ids[0].clone(), diff --git a/src/artist.rs b/src/artist.rs index 698bf2d..603ab24 100644 --- a/src/artist.rs +++ b/src/artist.rs @@ -193,4 +193,10 @@ impl ListItem for Artist { fn open(&self, queue: Arc, library: Arc) -> Option> { Some(ArtistView::new(queue, library, self).as_boxed_view_ext()) } + + fn share_url(&self) -> Option { + self.id + .clone() + .map(|id| format!("https://open.spotify.com/artist/{}", id)) + } } diff --git a/src/commands.rs b/src/commands.rs index 84d50dc..94af581 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -10,7 +10,7 @@ use clipboard::{ClipboardContext, ClipboardProvider}; use library::Library; use queue::{Queue, RepeatSetting}; use spotify::Spotify; -use traits::ViewExt; +use traits::{ListItem, ViewExt}; use ui::layout::Layout; type CommandCb = dyn Fn(&mut Cursive, &[String]) -> Result, String>; @@ -202,14 +202,14 @@ impl CommandManager { let queue = queue.clone(); self.register_command( "share", - Some(Box::new(move |_, _| { - if let Some(url) = queue - .get_current() - .and_then(|t| t.id) - .map(|id| format!("https://open.spotify.com/track/{}", id)) - { - let mut ctx: ClipboardContext = ClipboardProvider::new().unwrap(); - ctx.set_contents(url).unwrap(); + Some(Box::new(move |_, args| { + if let Some(url) = args.get(0).and_then(|source| match source.as_str() { + "current" => queue.get_current().and_then(|t| t.share_url()), + _ => None, + }) { + ClipboardProvider::new() + .and_then(|mut ctx: ClipboardContext| ctx.set_contents(url)) + .unwrap(); } Ok(None) @@ -341,7 +341,8 @@ impl CommandManager { kb.insert(",".into(), "seek -500".into()); kb.insert("r".into(), "repeat".into()); kb.insert("z".into(), "shuffle".into()); - kb.insert("x".into(), "share".into()); + kb.insert("x".into(), "share current".into()); + kb.insert("Shift+x".into(), "share selected".into()); kb.insert("F1".into(), "focus queue".into()); kb.insert("F2".into(), "focus search".into()); diff --git a/src/playlist.rs b/src/playlist.rs index bd32498..401f3df 100644 --- a/src/playlist.rs +++ b/src/playlist.rs @@ -166,4 +166,11 @@ impl ListItem for Playlist { fn open(&self, queue: Arc, library: Arc) -> Option> { Some(PlaylistView::new(queue, library, self).as_boxed_view_ext()) } + + fn share_url(&self) -> Option { + Some(format!( + "https://open.spotify.com/user/{}/{}", + self.owner_id, self.id + )) + } } diff --git a/src/track.rs b/src/track.rs index 1267e3b..ecff57b 100644 --- a/src/track.rs +++ b/src/track.rs @@ -189,6 +189,12 @@ impl ListItem for Track { None } + fn share_url(&self) -> Option { + self.id + .clone() + .map(|id| format!("https://open.spotify.com/track/{}", id)) + } + fn album(&self, queue: Arc) -> Option { let spotify = queue.get_spotify(); diff --git a/src/traits.rs b/src/traits.rs index 62bd0a4..aae2c70 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -18,6 +18,7 @@ pub trait ListItem: Sync + Send + 'static { fn queue(&mut self, queue: Arc); fn toggle_saved(&mut self, library: Arc); fn open(&self, queue: Arc, library: Arc) -> Option>; + fn share_url(&self) -> Option; fn album(&self, _queue: Arc) -> Option { None diff --git a/src/ui/listview.rs b/src/ui/listview.rs index 50da0f3..00f4b69 100644 --- a/src/ui/listview.rs +++ b/src/ui/listview.rs @@ -9,6 +9,7 @@ use cursive::view::ScrollBase; use cursive::{Cursive, Printer, Rect, Vec2}; use unicode_width::UnicodeWidthStr; +use clipboard::{ClipboardContext, ClipboardProvider}; use commands::CommandResult; use library::Library; use queue::Queue; @@ -314,6 +315,26 @@ impl ViewExt for ListView { } } + if cmd == "share" { + return args.get(0).map_or_else( + || Err("wrong number of parameters".to_string()), + |source| match source.as_str() { + "selected" => { + if let Some(url) = self.content.read().ok().and_then(|content| { + content.get(self.selected).and_then(ListItem::share_url) + }) { + ClipboardProvider::new() + .and_then(|mut ctx: ClipboardContext| ctx.set_contents(url)) + .unwrap(); + } + + Ok(CommandResult::Consumed(None)) + } + _ => Ok(CommandResult::Ignored), + }, + ); + } + if cmd == "move" { if let Some(dir) = args.get(0) { let amount: usize = args From 90c1930c39319e18807974bc6be972ccee9a557e Mon Sep 17 00:00:00 2001 From: Rasmus Larsen Date: Fri, 10 May 2019 11:06:46 +0200 Subject: [PATCH 06/12] Fix playlist share url generation --- src/playlist.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/playlist.rs b/src/playlist.rs index 401f3df..5682763 100644 --- a/src/playlist.rs +++ b/src/playlist.rs @@ -169,7 +169,7 @@ impl ListItem for Playlist { fn share_url(&self) -> Option { Some(format!( - "https://open.spotify.com/user/{}/{}", + "https://open.spotify.com/user/{}/playlist/{}", self.owner_id, self.id )) } From 224597ae9248a83ec0559e9ce427e5111095dca0 Mon Sep 17 00:00:00 2001 From: Rasmus Larsen Date: Fri, 10 May 2019 12:55:22 +0200 Subject: [PATCH 07/12] Move share command handling completely to ListView --- src/commands.rs | 19 ------------------- src/ui/listview.rs | 33 ++++++++++++++++----------------- 2 files changed, 16 insertions(+), 36 deletions(-) diff --git a/src/commands.rs b/src/commands.rs index 94af581..0e447f1 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -198,25 +198,6 @@ impl CommandManager { ); } - { - let queue = queue.clone(); - self.register_command( - "share", - Some(Box::new(move |_, args| { - if let Some(url) = args.get(0).and_then(|source| match source.as_str() { - "current" => queue.get_current().and_then(|t| t.share_url()), - _ => None, - }) { - ClipboardProvider::new() - .and_then(|mut ctx: ClipboardContext| ctx.set_contents(url)) - .unwrap(); - } - - Ok(None) - })), - ) - } - { let spotify = spotify.clone(); self.register_command( diff --git a/src/ui/listview.rs b/src/ui/listview.rs index 00f4b69..eafcd7b 100644 --- a/src/ui/listview.rs +++ b/src/ui/listview.rs @@ -316,23 +316,22 @@ impl ViewExt for ListView { } if cmd == "share" { - return args.get(0).map_or_else( - || Err("wrong number of parameters".to_string()), - |source| match source.as_str() { - "selected" => { - if let Some(url) = self.content.read().ok().and_then(|content| { - content.get(self.selected).and_then(ListItem::share_url) - }) { - ClipboardProvider::new() - .and_then(|mut ctx: ClipboardContext| ctx.set_contents(url)) - .unwrap(); - } - - Ok(CommandResult::Consumed(None)) - } - _ => Ok(CommandResult::Ignored), - }, - ); + return if let Some(url) = args.get(0).and_then(|source| match source.as_str() { + "selected" => self + .content + .read() + .ok() + .and_then(|content| content.get(self.selected).and_then(|i| i.share_url())), , + "current" => self.queue.get_current().and_then(|t| t.share_url()), + _ => None, + }) { + ClipboardProvider::new() + .and_then(|mut ctx: ClipboardContext| ctx.set_contents(url)) + .ok(); + Ok(CommandResult::Consumed(None)) + } else { + Ok(CommandResult::Ignored) + }; } if cmd == "move" { From 641ddf01139a7712079f8496388e66f697cda936 Mon Sep 17 00:00:00 2001 From: Rasmus Larsen Date: Fri, 10 May 2019 12:57:31 +0200 Subject: [PATCH 08/12] Remove extra trailing comma and reformat --- src/ui/listview.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/ui/listview.rs b/src/ui/listview.rs index eafcd7b..f66427d 100644 --- a/src/ui/listview.rs +++ b/src/ui/listview.rs @@ -316,15 +316,14 @@ impl ViewExt for ListView { } if cmd == "share" { - return if let Some(url) = args.get(0).and_then(|source| match source.as_str() { - "selected" => self - .content - .read() - .ok() - .and_then(|content| content.get(self.selected).and_then(|i| i.share_url())), , - "current" => self.queue.get_current().and_then(|t| t.share_url()), - _ => None, - }) { + return if let Some(url) = + args.get(0).and_then(|source| match source.as_str() { + "selected" => self.content.read().ok().and_then(|content| { + content.get(self.selected).and_then(ListItem::share_url) + }), + "current" => self.queue.get_current().and_then(|t| t.share_url()), + _ => None, + }) { ClipboardProvider::new() .and_then(|mut ctx: ClipboardContext| ctx.set_contents(url)) .ok(); From 1a075694affe44112d465d0390c90dc731110369 Mon Sep 17 00:00:00 2001 From: Rasmus Larsen Date: Fri, 10 May 2019 13:06:36 +0200 Subject: [PATCH 09/12] Break share command into smaller chunks --- src/ui/listview.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/ui/listview.rs b/src/ui/listview.rs index f66427d..6576d3a 100644 --- a/src/ui/listview.rs +++ b/src/ui/listview.rs @@ -316,14 +316,17 @@ impl ViewExt for ListView { } if cmd == "share" { - return if let Some(url) = - args.get(0).and_then(|source| match source.as_str() { + let source = args.get(0); + let url = + source.and_then(|source| match source.as_str() { "selected" => self.content.read().ok().and_then(|content| { content.get(self.selected).and_then(ListItem::share_url) }), "current" => self.queue.get_current().and_then(|t| t.share_url()), _ => None, - }) { + }); + + return if let Some(url) = url { ClipboardProvider::new() .and_then(|mut ctx: ClipboardContext| ctx.set_contents(url)) .ok(); From 9ec80ac6684a1fb931edd0e2a5fb17441d50cb56 Mon Sep 17 00:00:00 2001 From: Rasmus Larsen Date: Fri, 10 May 2019 13:07:12 +0200 Subject: [PATCH 10/12] Remove unused imports --- src/commands.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/commands.rs b/src/commands.rs index 0e447f1..b0662b4 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -6,11 +6,10 @@ use cursive::event::{Event, Key}; use cursive::views::ViewRef; use cursive::Cursive; -use clipboard::{ClipboardContext, ClipboardProvider}; use library::Library; use queue::{Queue, RepeatSetting}; use spotify::Spotify; -use traits::{ListItem, ViewExt}; +use traits::ViewExt; use ui::layout::Layout; type CommandCb = dyn Fn(&mut Cursive, &[String]) -> Result, String>; From cd4bd627dab16d1337eb87b88ea674fe6b4828fe Mon Sep 17 00:00:00 2001 From: Rasmus Larsen Date: Fri, 10 May 2019 20:20:55 +0200 Subject: [PATCH 11/12] Ensure no 'unknown command' error is thrown when no track is played --- src/ui/listview.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/ui/listview.rs b/src/ui/listview.rs index 6576d3a..f60c221 100644 --- a/src/ui/listview.rs +++ b/src/ui/listview.rs @@ -216,6 +216,10 @@ impl View for ListView { self.content.read().unwrap().len() != self.last_content_len } + fn required_size(&mut self, constraint: Vec2) -> Vec2 { + Vec2::new(constraint.x, self.content.read().unwrap().len()) + } + fn on_event(&mut self, e: Event) -> EventResult { match e { Event::Mouse { @@ -263,10 +267,6 @@ impl View for ListView { EventResult::Consumed(None) } - fn required_size(&mut self, constraint: Vec2) -> Vec2 { - Vec2::new(constraint.x, self.content.read().unwrap().len()) - } - fn important_area(&self, view_size: Vec2) -> Rect { if self.content.read().unwrap().len() > 0 { Rect::from((view_size.x, self.selected)) @@ -326,14 +326,13 @@ impl ViewExt for ListView { _ => None, }); - return if let Some(url) = url { + if let Some(url) = url { ClipboardProvider::new() .and_then(|mut ctx: ClipboardContext| ctx.set_contents(url)) .ok(); - Ok(CommandResult::Consumed(None)) - } else { - Ok(CommandResult::Ignored) }; + + return Ok(CommandResult::Consumed(None)); } if cmd == "move" { From 6daabb0b0d3165c273b432cac87add8700b64e0c Mon Sep 17 00:00:00 2001 From: Rasmus Larsen Date: Fri, 10 May 2019 21:36:45 +0200 Subject: [PATCH 12/12] Update share functionality in README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 25ffc16..e969fae 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ have them configurable. * `z` to toggle shuffle playback * `q` quits ncspot * `x` copies a sharable URL to the song to the system clipboard +* `Shift-x` copies a sharable URL to the currently selected item to the system clipboard You can also open a Vim style commandprompt using `:`, the following commands are supported: @@ -66,7 +67,7 @@ are supported: * `stop`: Stop playback * `previous`/`next`: Play previous/next track * `clear`: Clear playlist -* `share`: Copies a sharable URL to the song to the system clipboard +* `share [current | selected]`: Copies a sharable URL of either the selected item or the currernt song to the system clipboard The screens can be opened with `queue`, `search`, `playlists` and `log`, whereas `search` can be supplied with a search term that will be entered after opening