From 83c6febaab9893b0308bbf4796655a3a0efebfc7 Mon Sep 17 00:00:00 2001 From: Matthew Mahnke Date: Wed, 30 Mar 2016 00:26:53 -0500 Subject: [PATCH 1/4] Add Bundler gem install path to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 381affa..969e9e9 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ [#]*[#] .\#* .swp +vendor/bundle/ From fd384e57b98b51b2dec89f0b5367a1ab049d8b69 Mon Sep 17 00:00:00 2001 From: Matthew Mahnke Date: Wed, 30 Mar 2016 04:17:18 -0500 Subject: [PATCH 2/4] Add Spotify block --- barr.gemspec | 1 + lib/barr.rb | 1 + lib/barr/blocks/spotify.rb | 76 ++++++++++++++++++++++++++++++++++++ spec/blocks/spotify_spec.rb | 77 +++++++++++++++++++++++++++++++++++++ 4 files changed, 155 insertions(+) create mode 100644 lib/barr/blocks/spotify.rb create mode 100644 spec/blocks/spotify_spec.rb diff --git a/barr.gemspec b/barr.gemspec index ddec6fd..6dba0a0 100644 --- a/barr.gemspec +++ b/barr.gemspec @@ -32,6 +32,7 @@ Gem::Specification.new do |spec| spec.add_runtime_dependency "i3ipc", "0.2.0" spec.add_runtime_dependency "weather-api", "1.2.0" + spec.add_runtime_dependency "ruby-dbus", "~> 0.11.0" spec.requirements << "Lemonbar with XFT support (https://github.com/krypt-n/bar)" spec.requirements << "(Optional) I3 for Workspace support" diff --git a/lib/barr.rb b/lib/barr.rb index 41e2454..8631533 100644 --- a/lib/barr.rb +++ b/lib/barr.rb @@ -11,6 +11,7 @@ require 'barr/blocks/mem' require 'barr/blocks/processes' require 'barr/blocks/rhythmbox' +require 'barr/blocks/spotify' require 'barr/blocks/temperature' require 'barr/blocks/whoami' diff --git a/lib/barr/blocks/spotify.rb b/lib/barr/blocks/spotify.rb new file mode 100644 index 0000000..a6257fd --- /dev/null +++ b/lib/barr/blocks/spotify.rb @@ -0,0 +1,76 @@ +# coding: utf-8 +require 'barr/block' +require 'dbus' + +module Barr + module Blocks + class Spotify < Block + DBUS_PLAY = 'dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.PlayPause'.freeze + DBUS_NEXT = 'dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.Next'.freeze + DBUS_PREV = 'dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.Previous'.freeze + + attr_reader :view_opts + + def initialize(opts = {}) + super + + @view_opts = { + artist: opts[:artist].nil? || opts[:artist], + buttons: opts[:buttons].nil? || opts[:buttons], + title: opts[:title].nil? || opts[:title] + } + + spotify_service = DBus.session_bus['org.mpris.MediaPlayer2.spotify'] + @spotify_player = spotify_service.object '/org/mpris/MediaPlayer2' + @spotify_player.introspect + @spotify_iface = @spotify_player['org.mpris.MediaPlayer2.Player'] + end + + def update! + op = [] + + if @view_opts[:artist] || @view_opts[:title] + if(running?) + info = sys_cmd[:foo] + + if @view_opts[:artist] && @view_opts[:title] + op << "#{info[:artist]} - #{info[:title]}" + elsif @view_opts[:artist] + op << info[:artist] + elsif @view_opts[:title] + op << info[:title] + end + else + op << 'None' + end + end + + op << buttons if @view_opts[:buttons] + + @output = op.join(' ') + + end + + def running? + `pgrep spotify`.chomp.length != 0 + end + + def buttons + [ + "%{A:#{DBUS_PREV}:}\uf048%{A}", + "%{A:#{DBUS_PLAY}:}\uf04b%{A}", + "%{A:#{DBUS_NEXT}:}\uf051%{A}" + ].join(' ').freeze + end + + private + + def sys_cmd + dbus_meta = @spotify_iface['Metadata'] + Hash.new title: dbus_meta['xesam:title'], + artist: dbus_meta['xesam:artist'].join(', '), + album: dbus_meta['xesam:album'] + end + end + end +end diff --git a/spec/blocks/spotify_spec.rb b/spec/blocks/spotify_spec.rb new file mode 100644 index 0000000..5dd6c1b --- /dev/null +++ b/spec/blocks/spotify_spec.rb @@ -0,0 +1,77 @@ +# coding: utf-8 +require 'barr/blocks/spotify' + +RSpec.describe Barr::Blocks::Spotify do + let(:sys_cmd) { Hash.new(title: 'Tear In My Heart', + artist: ['Twenty One Pilots'].join(', '), + album: 'Blurryface') } + let(:dbus_play) { 'dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.PlayPause' } + let(:dbus_next) { 'dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.Next' } + let(:dbus_prev) { 'dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.Previous' } + + before do + allow(subject).to receive(:running?).and_return(true) + allow(subject).to receive(:sys_cmd).and_return(sys_cmd) + end + + describe '#initialize' do + it 'sets the default options' do + expect(subject.view_opts[:artist]).to eq true + expect(subject.view_opts[:buttons]).to eq true + expect(subject.view_opts[:title]).to eq true + end + end + + describe '#update' do + context 'with everything enabled' do + before { subject.update! } + + it 'sets the output correctly' do + expect(subject.output).to eq("Twenty One Pilots - Tear In My Heart %{A:#{dbus_prev}:}%{A} %{A:#{dbus_play}:}%{A} %{A:#{dbus_next}:}%{A}") + end + end + + context 'with only artist enabled' do + subject { described_class.new title: false, buttons: false } + + before { subject.update! } + + it 'sets the output correctly' do + expect(subject.output).to eq('Twenty One Pilots') + end + end + + context 'with only title enabled' do + subject { described_class.new artist: false, buttons: false } + + before { subject.update! } + + it 'sets the output correctly' do + expect(subject.output).to eq('Tear In My Heart') + end + end + + context 'with only buttons enabled' do + subject { described_class.new title: false, artist: false } + + before { subject.update! } + + it 'sets the output correctly' do + expect(subject.output).to eq("%{A:#{dbus_prev}:}%{A} %{A:#{dbus_play}:}%{A} %{A:#{dbus_next}:}%{A}") + end + end + + context 'when nothing is playing' do + subject { described_class.new buttons: false } + + before do + allow(subject).to receive(:running?).and_return(false) + subject.update! + end + + it 'sets the output correctly' do + expect(subject.output).to eq('None') + end + end + end +end From 788682777f4f1dddb0f915c798ab96cc13e457e9 Mon Sep 17 00:00:00 2001 From: Matthew Mahnke Date: Wed, 30 Mar 2016 04:31:08 -0500 Subject: [PATCH 3/4] Update README.md & barr.gemspec about usage and dependencies --- README.md | 14 +++++++++++++- barr.gemspec | 1 + 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e9029b1..2a5c521 100644 --- a/README.md +++ b/README.md @@ -146,7 +146,7 @@ seperate_block = Barr::Blocks::Whoami.new ### Block Specific Configuration -#### Battery +#### Battery Show battery status. @@ -235,6 +235,18 @@ There are no `Processes` block specific configurable options. | `buttons` | bool | As above, but for the player control buttons | `true` | | `title` | bool | As above, but for the track title | `true` | +#### Spotify + +**Requires Spotify**. Shows currently playing artist and/or track, as well as control buttons. Control buttons use FontAwesome. + +`rb = Barr::Blocks::Spotify.new buttons: false` + +| Option | Value | Description | Default | +| --- | --- | --- | --- | +| `artist` | bool | Set to `true` or `false` to set whether or not the currently playing artist should be shown. | `true` | +| `buttons` | bool | As above, but for the player control buttons | `true` | +| `title` | bool | As above, but for the track title | `true` | + #### Temperature Shows the current temperature and summary of a given location ID. Clicking it will open the full report in your browser. diff --git a/barr.gemspec b/barr.gemspec index 6dba0a0..1faae14 100644 --- a/barr.gemspec +++ b/barr.gemspec @@ -37,5 +37,6 @@ Gem::Specification.new do |spec| spec.requirements << "Lemonbar with XFT support (https://github.com/krypt-n/bar)" spec.requirements << "(Optional) I3 for Workspace support" spec.requirements << "(Optional) RhythmBox & rhythmbox-client" + spec.requirements << "(Optional) Spotify" spec.requirements << "(Optional) FontAwesome font" end From b08da96e7b9fa8c6c4eddfd634a46be6203e4746 Mon Sep 17 00:00:00 2001 From: Matthew Mahnke Date: Mon, 11 Apr 2016 13:05:14 -0500 Subject: [PATCH 4/4] Add mock for testing Spotify's DBus connection --- lib/barr/blocks/spotify.rb | 25 +++++++++++++++++++------ spec/blocks/spotify_spec.rb | 7 +++++++ spec/mocks/spotify.rb | 19 +++++++++++++++++++ 3 files changed, 45 insertions(+), 6 deletions(-) create mode 100644 spec/mocks/spotify.rb diff --git a/lib/barr/blocks/spotify.rb b/lib/barr/blocks/spotify.rb index a6257fd..ab416e6 100644 --- a/lib/barr/blocks/spotify.rb +++ b/lib/barr/blocks/spotify.rb @@ -9,7 +9,7 @@ class Spotify < Block DBUS_NEXT = 'dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.Next'.freeze DBUS_PREV = 'dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.Previous'.freeze - attr_reader :view_opts + attr_reader :view_opts, :spotify_iface def initialize(opts = {}) super @@ -19,11 +19,7 @@ def initialize(opts = {}) buttons: opts[:buttons].nil? || opts[:buttons], title: opts[:title].nil? || opts[:title] } - - spotify_service = DBus.session_bus['org.mpris.MediaPlayer2.spotify'] - @spotify_player = spotify_service.object '/org/mpris/MediaPlayer2' - @spotify_player.introspect - @spotify_iface = @spotify_player['org.mpris.MediaPlayer2.Player'] + @spotify_iface = dbus_connection end def update! @@ -63,6 +59,23 @@ def buttons ].join(' ').freeze end + def dbus_connection + begin + spotify_service = DBus.session_bus['org.mpris.MediaPlayer2.spotify'] + spotify_player = spotify_service.object '/org/mpris/MediaPlayer2' + spotify_player.introspect + rescue DBus::Error + # This should only happen when testing and Spotify is not running, + # because DBus cannot find a service file that provides + # org.mpris.MediaPlayer2.spotify. This will not be an issue with + # typical usage as long as #running? is checked before using this + # method or #sys_cmd. + return nil + else + return spotify_player['org.mpris.MediaPlayer2.Player'] + end + end + private def sys_cmd diff --git a/spec/blocks/spotify_spec.rb b/spec/blocks/spotify_spec.rb index 5dd6c1b..80faf6b 100644 --- a/spec/blocks/spotify_spec.rb +++ b/spec/blocks/spotify_spec.rb @@ -1,5 +1,6 @@ # coding: utf-8 require 'barr/blocks/spotify' +require './spec/mocks/spotify' RSpec.describe Barr::Blocks::Spotify do let(:sys_cmd) { Hash.new(title: 'Tear In My Heart', @@ -12,6 +13,7 @@ before do allow(subject).to receive(:running?).and_return(true) allow(subject).to receive(:sys_cmd).and_return(sys_cmd) + allow(subject).to receive(:spotify_iface).and_return(SpotifyDbusMock.new) end describe '#initialize' do @@ -20,6 +22,11 @@ expect(subject.view_opts[:buttons]).to eq true expect(subject.view_opts[:title]).to eq true end + + it 'sets a DBus connection' do + subject { described_class.new } + expect(subject.spotify_iface).to be_a(SpotifyDbusMock) + end end describe '#update' do diff --git a/spec/mocks/spotify.rb b/spec/mocks/spotify.rb new file mode 100644 index 0000000..5ccc74e --- /dev/null +++ b/spec/mocks/spotify.rb @@ -0,0 +1,19 @@ +class SpotifyDbusMock + def [](_key) + MetadataMock.new + end +end + +class MetadataMock + def [](key) + if key == 'xesam:title' + return 'Tear In My Heart' + elsif key == 'xesam:artist' + return ['Twenty One Pilots'] + elsif key == 'xesam:album' + return 'Blurryface' + else + raise "I don't know what to do with that key" + end + end +end