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