Merge lp:~fabiozaramella/wingpanel-indicator-sound/implement-bluetooth-controls into lp:~wingpanel-devs/wingpanel-indicator-sound/trunk

Proposed by Fabio Zaramella
Status: Merged
Approved by: Danielle Foré
Approved revision: 155
Merged at revision: 155
Proposed branch: lp:~fabiozaramella/wingpanel-indicator-sound/implement-bluetooth-controls
Merge into: lp:~wingpanel-devs/wingpanel-indicator-sound/trunk
Diff against target: 740 lines (+536/-48)
7 files modified
src/CMakeLists.txt (+4/-0)
src/Services/Adapter.vala (+38/-0)
src/Services/Device.vala (+43/-0)
src/Services/Manager.vala (+257/-0)
src/Services/MediaPlayer.vala (+33/-0)
src/Widgets/MprisGui.vala (+125/-48)
src/Widgets/MprisWidget.vala (+36/-0)
To merge this branch: bzr merge lp:~fabiozaramella/wingpanel-indicator-sound/implement-bluetooth-controls
Reviewer Review Type Date Requested Status
WingPanel Devs Pending
Review via email: mp+322258@code.launchpad.net

Commit message

Implements controls for paired bluetooth devices

Description of the change

This branch implements controls for paired bluetooth devices.

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/CMakeLists.txt'
2--- src/CMakeLists.txt 2016-12-22 19:28:43 +0000
3+++ src/CMakeLists.txt 2017-04-09 16:26:45 +0000
4@@ -22,6 +22,10 @@
5 Services/MprisClient.vala
6 Services/Settings.vala
7 Services/Volume-control.vala
8+ Services/Manager.vala
9+ Services/Adapter.vala
10+ Services/Device.vala
11+ Services/MediaPlayer.vala
12 ${CMAKE_CURRENT_BINARY_DIR}/config.vala
13 PACKAGES
14 wingpanel-2.0
15
16=== added file 'src/Services/Adapter.vala'
17--- src/Services/Adapter.vala 1970-01-01 00:00:00 +0000
18+++ src/Services/Adapter.vala 2017-04-09 16:26:45 +0000
19@@ -0,0 +1,38 @@
20+/*
21+ * Copyright (c) 2015-2017 elementary LLC. (http://launchpad.net/wingpanel-indicator-sound)
22+ *
23+ * This program is free software; you can redistribute it and/or
24+ * modify it under the terms of the GNU General Public
25+ * License as published by the Free Software Foundation; either
26+ * version 2 of the License, or (at your option) any later version.
27+ *
28+ * This program is distributed in the hope that it will be useful,
29+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
30+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
31+ * General Public License for more details.
32+ *
33+ * You should have received a copy of the GNU General Public
34+ * License along with this program; If not, see <http://www.gnu.org/licenses/>.
35+ *
36+ */
37+
38+[DBus (name = "org.bluez.Adapter1")]
39+public interface Sound.Services.Adapter : Object {
40+ public abstract void remove_device (ObjectPath device) throws IOError;
41+ public abstract void set_discovery_filter (HashTable<string, Variant> properties) throws IOError;
42+ public abstract void start_discovery () throws IOError;
43+ public abstract void stop_discovery () throws IOError;
44+
45+ public abstract string[] UUIDs { public owned get; private set; }
46+ public abstract bool discoverable { public get; public set; }
47+ public abstract bool discovering { public get; private set; }
48+ public abstract bool pairable { public get; public set; }
49+ public abstract bool powered { public get; public set; }
50+ public abstract string address { public owned get; private set; }
51+ public abstract string alias { public owned get; public set; }
52+ public abstract string modalias { public owned get; private set; }
53+ public abstract string name { public owned get; private set; }
54+ public abstract uint @class { public get; private set; }
55+ public abstract uint discoverable_timeout { public get; private set; }
56+ public abstract uint pairable_timeout { public get; private set; }
57+}
58
59=== added file 'src/Services/Device.vala'
60--- src/Services/Device.vala 1970-01-01 00:00:00 +0000
61+++ src/Services/Device.vala 2017-04-09 16:26:45 +0000
62@@ -0,0 +1,43 @@
63+/*
64+ * Copyright (c) 2015-2017 elementary LLC. (http://launchpad.net/wingpanel-indicator-sound)
65+ *
66+ * This program is free software; you can redistribute it and/or
67+ * modify it under the terms of the GNU General Public
68+ * License as published by the Free Software Foundation; either
69+ * version 2 of the License, or (at your option) any later version.
70+ *
71+ * This program is distributed in the hope that it will be useful,
72+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
73+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
74+ * General Public License for more details.
75+ *
76+ * You should have received a copy of the GNU General Public
77+ * License along with this program; If not, see <http://www.gnu.org/licenses/>.
78+ *
79+ */
80+
81+[DBus (name = "org.bluez.Device1")]
82+public interface Sound.Services.Device : Object {
83+ public abstract void cancel_pairing () throws IOError;
84+ public abstract void connect () throws IOError;
85+ public abstract void connect_profile (string UUID) throws IOError;
86+ public abstract void disconnect () throws IOError;
87+ public abstract void disconnect_profile (string UUID) throws IOError;
88+ public abstract void pair () throws IOError;
89+
90+ public abstract string[] UUIDs { public owned get; private set; }
91+ public abstract bool blocked { public owned get; public set; }
92+ public abstract bool connected { public owned get; private set; }
93+ public abstract bool legacy_pairing { public owned get; private set; }
94+ public abstract bool paired { public owned get; private set; }
95+ public abstract bool trusted { public owned get; public set; }
96+ public abstract int16 RSSI { public owned get; private set; }
97+ public abstract ObjectPath adapter { public owned get; private set; }
98+ public abstract string address { public owned get; private set; }
99+ public abstract string alias { public owned get; public set; }
100+ public abstract string icon { public owned get; private set; }
101+ public abstract string modalias { public owned get; private set; }
102+ public abstract string name { public owned get; private set; }
103+ public abstract uint16 appearance { public owned get; private set; }
104+ public abstract uint32 @class { public owned get; private set; }
105+}
106
107=== added file 'src/Services/Manager.vala'
108--- src/Services/Manager.vala 1970-01-01 00:00:00 +0000
109+++ src/Services/Manager.vala 2017-04-09 16:26:45 +0000
110@@ -0,0 +1,257 @@
111+/*
112+ * Copyright (c) 2015-2017 elementary LLC. (http://launchpad.net/wingpanel-indicator-sound)
113+ *
114+ * This program is free software; you can redistribute it and/or
115+ * modify it under the terms of the GNU General Public
116+ * License as published by the Free Software Foundation; either
117+ * version 2 of the License, or (at your option) any later version.
118+ *
119+ * This program is distributed in the hope that it will be useful,
120+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
121+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
122+ * General Public License for more details.
123+ *
124+ * You should have received a copy of the GNU General Public
125+ * License along with this program; If not, see <http://www.gnu.org/licenses/>.
126+ *
127+ */
128+
129+[DBus (name = "org.freedesktop.DBus.ObjectManager")]
130+public interface Sound.Services.DBusInterface : Object {
131+ public signal void interfaces_added (ObjectPath object_path, HashTable<string, HashTable<string, Variant>> param);
132+ public signal void interfaces_removed (ObjectPath object_path, string[] string_array);
133+
134+ public abstract HashTable<ObjectPath, HashTable<string, HashTable<string, Variant>>> get_managed_objects () throws IOError;
135+}
136+
137+public class Sound.Services.ObjectManager : Object {
138+ public signal void global_state_changed (bool enabled, bool connected);
139+ public signal void adapter_added (Services.Adapter adapter);
140+ public signal void adapter_removed (Services.Adapter adapter);
141+ public signal void device_added (Services.Device adapter);
142+ public signal void device_removed (Services.Device adapter);
143+ public signal void media_player_added (Services.MediaPlayer media_player, string name, string icon);
144+ public signal void media_player_removed (Services.MediaPlayer media_player);
145+ public signal void media_player_status_changed (string status, string title, string album);
146+
147+ public bool has_object { get; private set; default = false; }
148+ public string media_player_status { get; private set; default = "stopped";}
149+ public string current_track_title { get; private set; default = "Not playing";}
150+ public string current_track_artist { get; private set;}
151+
152+ private Services.DBusInterface object_interface;
153+ private Gee.HashMap<string, Services.Adapter> adapters;
154+ private Gee.HashMap<string, Services.Device> devices;
155+ private Gee.HashMap<string, Services.MediaPlayer> media_players;
156+
157+ public ObjectManager () { }
158+
159+ construct {
160+ adapters = new Gee.HashMap<string, Services.Adapter> (null, null);
161+ devices = new Gee.HashMap<string, Services.Device> (null, null);
162+ media_players = new Gee.HashMap<string, Services.MediaPlayer> (null, null);
163+
164+ Bus.get_proxy.begin<Services.DBusInterface> (BusType.SYSTEM, "org.bluez", "/", DBusProxyFlags.NONE, null, (obj, res) => {
165+ try {
166+ object_interface = Bus.get_proxy.end (res);
167+ object_interface.get_managed_objects ().foreach (add_path);
168+ object_interface.interfaces_added.connect (add_path);
169+ object_interface.interfaces_removed.connect (remove_path);
170+ } catch (Error e) {
171+ critical (e.message);
172+ }
173+ });
174+ }
175+
176+ [CCode (instance_pos = -1)]
177+ private void add_path (ObjectPath path, HashTable<string, HashTable<string, Variant>> param) {
178+ if ("org.bluez.Adapter1" in param) {
179+ try {
180+ Services.Adapter adapter = Bus.get_proxy_sync (BusType.SYSTEM, "org.bluez", path, DBusProxyFlags.GET_INVALIDATED_PROPERTIES);
181+ lock (adapters) {
182+ adapters.set (path, adapter);
183+ }
184+ has_object = true;
185+
186+ adapter_added (adapter);
187+ (adapter as DBusProxy).g_properties_changed.connect ((changed, invalid) => {
188+ var powered = changed.lookup_value ("Powered", new VariantType ("b"));
189+ if (powered != null) {
190+ check_global_state ();
191+ }
192+ });
193+ } catch (Error e) {
194+ warning ("Connecting to bluetooth adapter failed: %s", e.message);
195+ }
196+ } else if ("org.bluez.Device1" in param) {
197+ try {
198+ Services.Device device = Bus.get_proxy_sync (BusType.SYSTEM, "org.bluez", path, DBusProxyFlags.GET_INVALIDATED_PROPERTIES);
199+ if (device.paired) {
200+ lock (devices) {
201+ devices.set (path, device);
202+ }
203+
204+ device_added (device);
205+ }
206+
207+ (device as DBusProxy).g_properties_changed.connect ((changed, invalid) => {
208+ var connected = changed.lookup_value ("Connected", new VariantType ("b"));
209+ if (connected != null) {
210+ check_global_state ();
211+ }
212+
213+ var paired = changed.lookup_value ("Paired", new VariantType ("b"));
214+ if (paired != null) {
215+ if (device.paired) {
216+ lock (devices) {
217+ devices.set (path, device);
218+ }
219+
220+ device_added (device);
221+ } else {
222+ lock (devices) {
223+ devices.unset (path);
224+ }
225+
226+ device_removed (device);
227+ }
228+ }
229+ });
230+ } catch (Error e) {
231+ warning ("Connecting to bluetooth device failed: %s", e.message);
232+ }
233+ } else if ("org.bluez.MediaPlayer1" in param) {
234+ try {
235+ Services.MediaPlayer media_player = Bus.get_proxy_sync (BusType.SYSTEM, "org.bluez", path, DBusProxyFlags.GET_INVALIDATED_PROPERTIES);
236+ lock (media_players) {
237+ media_players.set (path, media_player);
238+ }
239+ string device_name = path.substring (0, path.last_index_of("/"));
240+ Services.Device cur_device = Bus.get_proxy_sync (BusType.SYSTEM, "org.bluez", device_name, DBusProxyFlags.GET_INVALIDATED_PROPERTIES);
241+ media_player_status = media_player.track.lookup ("Title").get_string (null);
242+ media_player_added (media_player, cur_device.name, cur_device.icon);
243+
244+ (media_player as DBusProxy).g_properties_changed.connect ((changed, invalid) => {
245+ if (changed.print (true).contains ("Track")) {
246+ Variant tmp = changed.lookup_value ("Track", VariantType.DICTIONARY);
247+ string title = tmp.lookup_value ("Title", VariantType.STRING).get_string (null);
248+ string artist = tmp.lookup_value ("Artist", VariantType.STRING).get_string (null);
249+ current_track_title = title;
250+ current_track_artist = artist;
251+ media_player_status_changed ("", title, artist);
252+ } else if (changed.lookup("Status", "s")) {
253+ string status = changed.lookup_value ("Status", VariantType.STRING).get_string (null);
254+ media_player_status = status;
255+ media_player_status_changed (status, "", "");
256+ }
257+ });
258+ } catch (Error e) {
259+ warning ("Connecting to bluetooth media player failed: %s", e.message);
260+ }
261+ }
262+ }
263+
264+ [CCode (instance_pos = -1)]
265+ public void remove_path (ObjectPath path) {
266+ lock (adapters) {
267+ var adapter = adapters.get (path);
268+ if (adapter != null) {
269+ adapters.unset (path);
270+ has_object = !adapters.is_empty;
271+
272+ adapter_removed (adapter);
273+ return;
274+ }
275+ }
276+
277+ lock (devices) {
278+ var device = devices.get (path);
279+ if (device != null) {
280+ devices.unset (path);
281+ device_removed (device);
282+ }
283+ }
284+
285+ lock (media_players) {
286+ var media_player = media_players.get (path);
287+ if (media_player != null) {
288+ media_players.unset (path);
289+ media_player_removed (media_player);
290+ }
291+ }
292+ }
293+
294+ public Gee.Collection<Services.Adapter> get_adapters () {
295+ lock (adapters) {
296+ return adapters.values;
297+ }
298+ }
299+
300+ public Gee.Collection<Services.Device> get_devices () {
301+ lock (devices) {
302+ return devices.values;
303+ }
304+ }
305+
306+ public Services.Adapter? get_adapter_from_path (string path) {
307+ lock (adapters) {
308+ return adapters.get (path);
309+ }
310+ }
311+
312+ private void check_global_state () {
313+ global_state_changed (get_global_state (), get_connected ());
314+ }
315+
316+ public bool get_connected () {
317+ lock (devices) {
318+ foreach (var device in devices.values) {
319+ if (device.connected) {
320+ return true;
321+ }
322+ }
323+ }
324+
325+ return false;
326+ }
327+
328+ public bool get_global_state () {
329+ lock (adapters) {
330+ foreach (var adapter in adapters.values) {
331+ if (adapter.powered) {
332+ return true;
333+ }
334+ }
335+ }
336+
337+ return false;
338+ }
339+
340+ public void set_global_state (bool state) {
341+ new Thread<void*> (null, () => {
342+ lock (devices) {
343+ foreach (var device in devices.values) {
344+ if (device.connected) {
345+ try {
346+ device.disconnect ();
347+ } catch (Error e) {
348+ critical (e.message);
349+ }
350+ }
351+ }
352+ }
353+
354+ lock (adapters) {
355+ foreach (var adapter in adapters.values) {
356+ adapter.powered = state;
357+ }
358+ }
359+
360+ return null;
361+ });
362+ }
363+
364+ public void set_last_state () {
365+ check_global_state ();
366+ }
367+}
368
369=== added file 'src/Services/MediaPlayer.vala'
370--- src/Services/MediaPlayer.vala 1970-01-01 00:00:00 +0000
371+++ src/Services/MediaPlayer.vala 2017-04-09 16:26:45 +0000
372@@ -0,0 +1,33 @@
373+/*
374+ * Copyright (c) 2015-2017 elementary LLC. (http://launchpad.net/wingpanel-indicator-sound)
375+ *
376+ * This program is free software; you can redistribute it and/or
377+ * modify it under the terms of the GNU General Public
378+ * License as published by the Free Software Foundation; either
379+ * version 2 of the License, or (at your option) any later version.
380+ *
381+ * This program is distributed in the hope that it will be useful,
382+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
383+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
384+ * General Public License for more details.
385+ *
386+ * You should have received a copy of the GNU General Public
387+ * License along with this program; If not, see <http://www.gnu.org/licenses/>.
388+ *
389+ * Authored by: Fiorotto Giuliano <mr.fiorotto@gmail.com>
390+ * Fabio Zaramella <ffabio.96.x@gmail.com>
391+ */
392+
393+[DBus (name = "org.bluez.MediaPlayer1")]
394+public interface Sound.Services.MediaPlayer : Object {
395+ public abstract void play () throws IOError;
396+ public abstract void pause () throws IOError;
397+ public abstract void stop () throws IOError;
398+ public abstract void next () throws IOError;
399+ public abstract void previous () throws IOError;
400+ public abstract void fast_forward () throws IOError;
401+ public abstract void rewind () throws IOError;
402+ public abstract string name { public owned get; }
403+ public abstract string status { public owned get; }
404+ public abstract HashTable<string,Variant> track { public owned get; }
405+}
406
407=== modified file 'src/Widgets/MprisGui.vala'
408--- src/Widgets/MprisGui.vala 2017-02-03 20:19:16 +0000
409+++ src/Widgets/MprisGui.vala 2017-04-09 16:26:45 +0000
410@@ -71,6 +71,7 @@
411 }
412
413 private Services.MprisClient? client_ = null;
414+ private Services.MediaPlayer? mp_client = null;
415
416 public Services.MprisClient? client {
417 get {
418@@ -120,6 +121,22 @@
419 }
420
421 /**
422+ * Create a new ClientWidget for bluetooth controls
423+ *
424+ * @param client The underlying MediaPlayer instance to use
425+ */
426+ public ClientWidget.bluetooth (Services.MediaPlayer media_player_client, string name, string icon){
427+ mp_client = media_player_client;
428+
429+ app_icon = new ThemedIcon (icon);
430+ background.set_from_gicon (app_icon, Gtk.IconSize.DIALOG);
431+ title_label.set_markup ("<b>%s</b>".printf (Markup.escape_text (name)));
432+ artist_label.set_text (NOT_PLAYING);
433+
434+ update_controls ();
435+ }
436+
437+ /**
438 * Create a new ClientWidget for the default player
439 *
440 * @param info The AppInfo of the default music player
441@@ -194,25 +211,33 @@
442 prev_btn = btn;
443 btn.clicked.connect (()=> {
444 Idle.add (()=> {
445- if (client.player.can_go_previous) {
446- if(!Thread.supported ()) {
447- warning ("Threading is not supported. DBus timeout could be blocking UI");
448- try {
449- client.player.previous();
450- } catch (Error e) {
451- warning ("Going to previous track probably failed (faulty MPRIS interface): %s", e.message);
452- }
453- } else {
454- new Thread <void*> ("wingpanel_indicator_sound_dbus_backward_thread", () => {
455- try {
456- client.player.previous();
457- } catch (Error e) {
458- warning ("Going to previous track probably failed (faulty MPRIS interface): %s", e.message);
459- }
460- return null;
461- });
462- }
463+ if (!Thread.supported ()) {
464+ warning ("Threading is not supported. DBus timeout could be blocking UI");
465+ try {
466+ if (mp_client == null && client.player.can_go_previous) {
467+ client.player.previous ();
468+ } else if (mp_client != null) {
469+ mp_client.previous ();
470+ }
471+ } catch (Error e) {
472+ warning ("Going to previous track probably failed (faulty MPRIS interface): %s", e.message);
473+ }
474+ } else {
475+ new Thread <void*> ("wingpanel_indicator_sound_dbus_backward_thread", () => {
476+ try {
477+ if (mp_client == null) {
478+ client.player.previous ();
479+ } else if(mp_client != null) {
480+ mp_client.previous ();
481+ }
482+ } catch (Error e) {
483+ warning ("Going to previous track probably failed (faulty MPRIS interface): %s", e.message);
484+ }
485+
486+ return null;
487+ });
488 }
489+
490 return false;
491 });
492 });
493@@ -224,23 +249,43 @@
494 play_btn = btn;
495 btn.clicked.connect (()=> {
496 Idle.add (()=> {
497- if(!Thread.supported ()) {
498+ if (!Thread.supported ()) {
499 warning ("Threading is not supported. DBus timeout could be blocking UI");
500 try {
501- client.player.play_pause();
502- } catch (Error e) {
503+ if (mp_client == null) {
504+ client.player.play_pause ();
505+ } else if (mp_client != null) {
506+ if (mp_client.status == "playing") {
507+ mp_client.pause ();
508+ } else {
509+ mp_client.play ();
510+ }
511+ update_play_status ();
512+ }
513+ } catch (Error e) {
514 warning ("Playing/Pausing probably failed (faulty MPRIS interface): %s", e.message);
515 }
516 } else {
517 new Thread <void*> ("wingpanel_indicator_sound_dbus_backward_thread", () => {
518 try {
519- client.player.play_pause();
520+ if (mp_client == null) {
521+ client.player.play_pause ();
522+ } else if (mp_client != null) {
523+ if (mp_client.status == "playing") {
524+ mp_client.pause ();
525+ } else {
526+ mp_client.play ();
527+ }
528+ update_play_status ();
529+ }
530 } catch (Error e) {
531- warning ("Playing/Pausing probably failed (faulty MPRIS interface): %s", e.message);
532+ warning ("Playing/Pausing probably failed (faulty MPRIS interface): %s", e.message);
533 }
534+
535 return null;
536- });
537+ });
538 }
539+
540 return false;
541 });
542 });
543@@ -251,25 +296,33 @@
544 next_btn = btn;
545 btn.clicked.connect (()=> {
546 Idle.add (()=> {
547- if (client.player.can_go_next) {
548- if(!Thread.supported ()) {
549- warning ("Threading is not supported. DBus timeout could be blocking UI");
550+ if(!Thread.supported ()) {
551+ warning ("Threading is not supported. DBus timeout could be blocking UI");
552+ try {
553+ if (mp_client == null && client.player.can_go_next) {
554+ client.player.next ();
555+ } else if (mp_client != null) {
556+ mp_client.next ();
557+ }
558+ } catch (Error e) {
559+ warning ("Going to next track probably failed (faulty MPRIS interface): %s", e.message);
560+ }
561+ } else {
562+ new Thread <void*> ("wingpanel_indicator_sound_dbus_forward_thread", () => {
563 try {
564- client.player.next();
565- } catch (Error e) {
566+ if (mp_client == null) {
567+ client.player.next ();
568+ } else if (mp_client != null) {
569+ mp_client.next ();
570+ }
571+ } catch (Error e) {
572 warning ("Going to next track probably failed (faulty MPRIS interface): %s", e.message);
573 }
574- } else {
575- new Thread <void*> ("wingpanel_indicator_sound_dbus_forward_thread", () => {
576- try {
577- client.player.next();
578- } catch (Error e) {
579- warning ("Going to next track probably failed (faulty MPRIS interface): %s", e.message);
580- }
581- return null;
582- });
583- }
584+
585+ return null;
586+ });
587 }
588+
589 return false;
590 });
591 });
592@@ -295,7 +348,7 @@
593
594 private void connect_to_client () {
595 client.prop.properties_changed.connect ((i,p,inv)=> {
596- if (i == "org.mpris.MediaPlayer2.Player") {
597+ if (i == "org.mpris.MediaPlayer2.Player") {
598 /* Handle mediaplayer2 iface */
599 p.foreach ((k,v)=> {
600 if (k == "Metadata") {
601@@ -323,17 +376,17 @@
602 try {
603 close ();
604 if (client != null && client.player.can_raise) {
605- if(!Thread.supported ()) {
606+ if (!Thread.supported ()) {
607 warning ("Threading is not supported. DBus timeout could be blocking UI");
608 try {
609- client.player.raise();
610+ client.player.raise ();
611 } catch (Error e) {
612 warning ("Raising the player probably failed (faulty MPRIS interface): %s", e.message);
613 }
614 } else {
615 new Thread <void*> ("wingpanel_indicator_sound_dbus_backward_thread", () => {
616 try {
617- client.player.raise();
618+ client.player.raise ();
619 } catch (Error e) {
620 warning ("Raising the player probably failed (faulty MPRIS interface): %s", e.message);
621 }
622@@ -343,7 +396,7 @@
623 } else if (app_info != null) {
624 app_info.launch (null, null);
625 }
626- } catch (Error e) {
627+ } catch (Error e) {
628 warning ("Could not launch player");
629 }
630
631@@ -376,11 +429,11 @@
632 private void update_play_status () {
633 switch (client.player.playback_status) {
634 case "Playing":
635- (play_btn.get_image () as Gtk.Image).set_from_icon_name ("media-playback-pause-symbolic", Gtk.IconSize.LARGE_TOOLBAR);
636+ (play_btn.get_image () as Gtk.Image).set_from_icon_name ("media-playback-pause-symbolic", Gtk.IconSize.LARGE_TOOLBAR);
637 break;
638 default:
639 /* Stopped, Paused */
640- (play_btn.get_image () as Gtk.Image).set_from_icon_name ("media-playback-start-symbolic", Gtk.IconSize.LARGE_TOOLBAR);
641+ (play_btn.get_image () as Gtk.Image).set_from_icon_name ("media-playback-start-symbolic", Gtk.IconSize.LARGE_TOOLBAR);
642 break;
643 }
644 }
645@@ -389,8 +442,13 @@
646 * Update prev/next sensitivity based on player requirements
647 */
648 private void update_controls () {
649- prev_btn.set_sensitive (client.player.can_go_previous);
650- next_btn.set_sensitive (client.player.can_go_next);
651+ if (mp_client == null) {
652+ prev_btn.set_sensitive (client.player.can_go_previous);
653+ next_btn.set_sensitive (client.player.can_go_next);
654+ } else {
655+ prev_btn.set_sensitive (true);
656+ next_btn.set_sensitive (true);
657+ }
658 }
659
660 /**
661@@ -498,4 +556,23 @@
662
663 return Gdk.pixbuf_get_from_surface (surface, 0, 0, mask_size, mask_size);
664 }
665+
666+ public void update_play (string playing, string title, string artist) {
667+ if (playing != "") {
668+ switch (playing) {
669+ case "playing":
670+ (play_btn.get_image () as Gtk.Image).set_from_icon_name ("media-playback-pause-symbolic", Gtk.IconSize.LARGE_TOOLBAR);
671+ break;
672+ default:
673+ /* Stopped, Paused */
674+ (play_btn.get_image () as Gtk.Image).set_from_icon_name ("media-playback-start-symbolic", Gtk.IconSize.LARGE_TOOLBAR);
675+ break;
676+ }
677+ }
678+
679+ if (title != "" && artist != "") {
680+ title_label.set_markup ("<b>%s</b>".printf (Markup.escape_text (title)));
681+ artist_label.set_text (artist);
682+ }
683+ }
684 }
685
686=== modified file 'src/Widgets/MprisWidget.vala'
687--- src/Widgets/MprisWidget.vala 2017-02-03 20:19:16 +0000
688+++ src/Widgets/MprisWidget.vala 2017-04-09 16:26:45 +0000
689@@ -21,9 +21,12 @@
690
691 AppInfo? default_music;
692 ClientWidget default_widget;
693+ ClientWidget bluetooth_widget;
694 HashTable<string,ClientWidget> ifaces;
695 public signal void close ();
696
697+ public Sound.Services.ObjectManager object_manager;
698+
699 public MprisWidget() {
700 Object (orientation: Gtk.Orientation.VERTICAL, spacing: 1);
701
702@@ -46,6 +49,39 @@
703 pack_start(default_widget, false, false, 0);
704 }
705
706+ object_manager = new Services.ObjectManager ();
707+ object_manager.bind_property ("has-object", this, "visible", GLib.BindingFlags.SYNC_CREATE);
708+
709+ if (object_manager.has_object) {
710+ object_manager.set_last_state ();
711+ }
712+
713+ object_manager.media_player_added.connect ((media_player, name, icon) => {
714+ try {
715+ bluetooth_widget = new ClientWidget.bluetooth (media_player, name, icon);
716+ bluetooth_widget.close.connect (() => {
717+ close ();
718+ });
719+
720+ bluetooth_widget.show_all ();
721+ pack_start (bluetooth_widget, false, false, 0);
722+ } catch (Error e) {
723+ warning ("Connecting to bluetooth device failed: %s", e.message);
724+ }
725+ });
726+
727+ object_manager.media_player_removed.connect ((media_player) => {
728+ debug ("Media player %s removed", media_player.name);
729+ bluetooth_widget.destroy ();
730+ });
731+
732+ object_manager.media_player_status_changed.connect ((status, title, artist) => {
733+ bluetooth_widget.update_play (status, title, artist);
734+ if (status == "playing" && default_widget.client.player.playback_status == "Playing") {
735+ default_widget.client.player.play_pause ();
736+ }
737+ });
738+
739 show_all();
740 }
741

Subscribers

People subscribed via source and target branches

to all changes: