Merge lp:~fabiozaramella/wingpanel-indicator-sound/implement-bluetooth-controls into lp:~wingpanel-devs/wingpanel-indicator-sound/trunk
- implement-bluetooth-controls
- Merge into 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 | ||||
Related bugs: |
|
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 |