Merge lp:~miwaxe+launchpad/synapse-project/clementine-support into lp:synapse-project

Proposed by Andrea Brancaleoni on 2013-11-18
Status: Needs review
Proposed branch: lp:~miwaxe+launchpad/synapse-project/clementine-support
Merge into: lp:synapse-project
Diff against target: 497 lines (+471/-0)
3 files modified
src/plugins/Makefile.am (+1/-0)
src/plugins/clementine-plugin.vala (+469/-0)
src/ui/synapse-main.vala (+1/-0)
To merge this branch: bzr merge lp:~miwaxe+launchpad/synapse-project/clementine-support
Reviewer Review Type Date Requested Status
Jeremy Munsch (community) Resubmit on 2015-07-18
Michal Hruby 2013-11-18 Needs Information on 2014-01-19
Review via email: mp+195676@code.launchpad.net

Description of the change

I've ported the XNoise plugin to Clementine, my favourite music player. Please review it!

:-)

To post a comment you must log in.
Michal Hruby (mhr3) wrote :

Code-wise this looks perfectly fine, but could use testing from someone who uses clementine... Any takers?

review: Needs Information
Jeremy Munsch (jeremy-munsch) wrote :

I just wanted to try this on 15.04 but a dependency is unique-3.0 which is not available anymore.
looks like gir1.2-unique-3.0 was last available in Saucy (13.10).

Since last LTS is Trusty (14.04), this should be update with the last version of synapse in order to be tested.

Jeremy Munsch (jeremy-munsch) wrote :

> I just wanted to try this on 15.04 but a dependency is unique-3.0 which is not
> available anymore.
> looks like gir1.2-unique-3.0 was last available in Saucy (13.10).
>
> Since last LTS is Trusty (14.04), this should be update with the last version
> of synapse in order to be tested.

I also tested the code in the last version of synapse, but the code is outdated right now. So it needs a great update to be used.

review: Resubmit
Jeremy Munsch (jeremy-munsch) wrote :

Oups sorry i miss clicked for the resubmit.

Unmerged revisions

514. By Andrea Brancaleoni <email address hidden> on 2013-11-18

Clementine Plugin

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/plugins/Makefile.am'
2--- src/plugins/Makefile.am 2013-07-02 23:06:40 +0000
3+++ src/plugins/Makefile.am 2013-11-18 21:23:46 +0000
4@@ -52,6 +52,7 @@
5 selection-plugin.vala \
6 test-slow-plugin.vala \
7 xnoise-media-player-plugin.vala \
8+ clementine-plugin.vala \
9 system-management.vala \
10 $(NULL)
11
12
13=== added file 'src/plugins/clementine-plugin.vala'
14--- src/plugins/clementine-plugin.vala 1970-01-01 00:00:00 +0000
15+++ src/plugins/clementine-plugin.vala 2013-11-18 21:23:46 +0000
16@@ -0,0 +1,469 @@
17+/*
18+ * Copyright (C) 2012 Jörn Magens <shuerhaaken@googlemail.com>
19+ *
20+ * This program is free software; you can redistribute it and/or modify
21+ * it under the terms of the GNU General Public License as published by
22+ * the Free Software Foundation; either version 2 of the License, or
23+ * (at your option) any later version.
24+ *
25+ * This program is distributed in the hope that it will be useful,
26+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
27+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28+ * GNU General Public License for more details.
29+ *
30+ * You should have received a copy of the GNU General Public License
31+ * along with this program; if not, write to the Free Software
32+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
33+ *
34+ * Authored by Jörn Magens <shuerhaaken@googlemail.com>
35+ * Authored by Andrea Brancaleoni <miwaxe@gmail.com>
36+ *
37+ */
38+
39+namespace Synapse
40+{
41+ [DBus (name = "org.mpris.MediaPlayer2.Player")]
42+ private interface ClementinePlayer : Object
43+ {
44+ public const string UNIQUE_NAME = "org.mpris.MediaPlayer2.clementine";
45+ public const string OBJECT_PATH = "/org/mpris/MediaPlayer2";
46+
47+ public abstract void next () throws IOError;
48+ public abstract void previous () throws IOError;
49+ public abstract void pause () throws IOError;
50+ public abstract void play_pause () throws IOError;
51+ public abstract void stop () throws IOError;
52+ public abstract void play () throws IOError;
53+ public abstract void open_uri (string uri) throws IOError;
54+ }
55+
56+ [DBus (name = "org.mpris.MediaPlayer2")]
57+ private interface ClementineEngine : Object
58+ {
59+ public const string UNIQUE_NAME = "org.mpris.MediaPlayer2.clementine";
60+ public const string OBJECT_PATH = "/org/mpris/MediaPlayer2";
61+
62+ public abstract void quit () throws IOError;
63+ public abstract void raise () throws IOError;
64+ }
65+
66+ public class ClementineActions: Object, Activatable, ItemProvider, ActionProvider
67+ {
68+ public bool enabled { get; set; default = true; }
69+
70+ public void activate ()
71+ {
72+ }
73+
74+ public void deactivate ()
75+ {
76+ }
77+
78+ static void register_plugin ()
79+ {
80+ DataSink.PluginRegistry.get_default ().register_plugin (
81+ typeof (ClementineActions),
82+ "Clementine",
83+ _ ("Control Clementine media player."),
84+ "clementine",
85+ register_plugin,
86+ Environment.find_program_in_path ("clementine") != null,
87+ _ ("Clementine is not installed!")
88+ );
89+ }
90+
91+ static construct
92+ {
93+ register_plugin ();
94+ }
95+
96+ private abstract class ClementineAction: Object, Match
97+ {
98+ // from Match interface
99+ public string title { get; construct set; }
100+ public string description { get; set; }
101+ public string icon_name { get; construct set; }
102+ public bool has_thumbnail { get; construct set; }
103+ public string thumbnail_path { get; construct set; }
104+ public MatchType match_type { get; construct set; }
105+ public int default_relevancy { get; set; }
106+
107+ public abstract bool valid_for_match (Match match);
108+ public abstract void execute_internal (Match? match);
109+
110+ public void execute (Match? match)
111+ {
112+ execute_internal (match);
113+ }
114+
115+ public virtual int get_relevancy ()
116+ {
117+ bool clementine_running = DBusService.get_default ().name_has_owner (
118+ ClementinePlayer.UNIQUE_NAME);
119+ return clementine_running ? default_relevancy + Match.Score.INCREMENT_LARGE : default_relevancy;
120+ }
121+ }
122+
123+ private abstract class ClementineControlMatch: Object, Match
124+ {
125+ // for Match interface
126+ public string title { get; construct set; }
127+ public string description { get; set; default = ""; }
128+ public string icon_name { get; construct set; default = ""; }
129+ public bool has_thumbnail { get; construct set; default = false; }
130+ public string thumbnail_path { get; construct set; }
131+ public MatchType match_type { get; construct set; }
132+
133+ public void execute (Match? match)
134+ {
135+ this.do_action ();
136+ }
137+
138+ public abstract void do_action ();
139+
140+ public virtual bool action_available ()
141+ {
142+ return DBusService.get_default ().name_has_owner (
143+ ClementinePlayer.UNIQUE_NAME
144+ );
145+ }
146+ }
147+
148+ /* MATCHES of Type.ACTION */
149+ private class Quit : ClementineControlMatch
150+ {
151+ public Quit ()
152+ {
153+ Object (title: _ ("Quit"),
154+ description: _ ("Quit Clementine"),
155+ icon_name: "gtk-close",
156+ has_thumbnail: false,
157+ match_type: MatchType.ACTION
158+ );
159+ }
160+
161+ public override void do_action ()
162+ {
163+ try {
164+ ClementineEngine player = Bus.get_proxy_sync (BusType.SESSION,
165+ ClementineEngine.UNIQUE_NAME,
166+ ClementineEngine.OBJECT_PATH);
167+ player.quit ();
168+ } catch (IOError e) {
169+ Utils.Logger.warning (this, "Clementine is not available.\n%s", e.message);
170+ }
171+ }
172+ }
173+
174+ private class Raise : ClementineControlMatch
175+ {
176+ public Raise ()
177+ {
178+ Object (title: _ ("Raise"),
179+ description: _ ("Show Clementine"),
180+ icon_name: "clementine",
181+ has_thumbnail: false,
182+ match_type: MatchType.ACTION
183+ );
184+ }
185+
186+ public override void do_action ()
187+ {
188+ try {
189+ ClementineEngine player = Bus.get_proxy_sync (BusType.SESSION,
190+ ClementineEngine.UNIQUE_NAME,
191+ ClementineEngine.OBJECT_PATH);
192+ player.raise ();
193+ } catch (IOError e) {
194+ Utils.Logger.warning (this, "Clementine is not available.\n%s", e.message);
195+ }
196+ }
197+ }
198+
199+ private class Play : ClementineControlMatch
200+ {
201+ public Play ()
202+ {
203+ Object (title: _ ("Play"),
204+ description: _ ("Start playback in Clementine"),
205+ icon_name: "media-playback-start",
206+ has_thumbnail: false,
207+ match_type: MatchType.ACTION
208+ );
209+ }
210+
211+ public override void do_action ()
212+ {
213+ try {
214+ ClementinePlayer player = Bus.get_proxy_sync (BusType.SESSION,
215+ ClementinePlayer.UNIQUE_NAME,
216+ ClementinePlayer.OBJECT_PATH);
217+ player.play ();
218+ } catch (IOError e) {
219+ Utils.Logger.warning (this, "Clementine is not available.\n%s", e.message);
220+ }
221+ }
222+ }
223+
224+ private class TogglePlaying : ClementineControlMatch
225+ {
226+ public TogglePlaying ()
227+ {
228+ Object (title: _ ("TogglePlaying"),
229+ description: _ ("Start/Pause playback in Clementine"),
230+ icon_name: "media-playback-pause",
231+ has_thumbnail: false,
232+ match_type: MatchType.ACTION
233+ );
234+ }
235+
236+ public override void do_action ()
237+ {
238+ try {
239+ ClementinePlayer player = Bus.get_proxy_sync (BusType.SESSION,
240+ ClementinePlayer.UNIQUE_NAME,
241+ ClementinePlayer.OBJECT_PATH);
242+ player.play_pause ();
243+ } catch (IOError e) {
244+ Utils.Logger.warning (this, "Clementine is not available.\n%s", e.message);
245+ }
246+ }
247+
248+ public override bool action_available ()
249+ {
250+ return true;
251+ }
252+ }
253+
254+ private class Pause : ClementineControlMatch
255+ {
256+ public Pause ()
257+ {
258+ Object (title: _ ("Pause"),
259+ description: _ ("Pause playback in Clementine"),
260+ icon_name: "media-playback-pause",
261+ has_thumbnail: false,
262+ match_type: MatchType.ACTION
263+ );
264+ }
265+
266+ public override void do_action ()
267+ {
268+ try {
269+ ClementinePlayer player = Bus.get_proxy_sync (BusType.SESSION,
270+ ClementinePlayer.UNIQUE_NAME,
271+ ClementinePlayer.OBJECT_PATH);
272+ player.pause ();
273+ } catch (IOError e) {
274+ Utils.Logger.warning (this, "Clementine is not available.\n%s", e.message);
275+ }
276+ }
277+ }
278+
279+ private class Next : ClementineControlMatch
280+ {
281+ public Next ()
282+ {
283+ Object (title: _ ("Next"),
284+ description: _ ("Plays the next song in Clementine's playlist"),
285+ icon_name: "media-skip-forward",
286+ has_thumbnail: false,
287+ match_type: MatchType.ACTION
288+ );
289+ }
290+
291+ public override void do_action ()
292+ {
293+ try {
294+ ClementinePlayer player = Bus.get_proxy_sync (BusType.SESSION,
295+ ClementinePlayer.UNIQUE_NAME,
296+ ClementinePlayer.OBJECT_PATH);
297+
298+ player.next ();
299+ } catch (IOError e) {
300+ Utils.Logger.warning (this, "Clementine is not available.\n%s", e.message);
301+ }
302+ }
303+ }
304+
305+ private class Previous : ClementineControlMatch
306+ {
307+ public Previous ()
308+ {
309+ Object (title: _ ("Previous"),
310+ description: _ ("Plays the previous song in Clementine's playlist"),
311+ icon_name: "media-skip-backward",
312+ has_thumbnail: false,
313+ match_type: MatchType.ACTION
314+ );
315+ }
316+
317+ public override void do_action ()
318+ {
319+ try {
320+ ClementinePlayer player = Bus.get_proxy_sync (BusType.SESSION,
321+ ClementinePlayer.UNIQUE_NAME,
322+ ClementinePlayer.OBJECT_PATH);
323+ player.previous ();
324+ } catch (IOError e) {
325+ Utils.Logger.warning (this, "Clementine is not available.\n%s", e.message);
326+ }
327+ }
328+ }
329+
330+ private class Stop : ClementineControlMatch
331+ {
332+ public Stop ()
333+ {
334+ Object (title: _ ("Stop"),
335+ description: _ ("Stops the playback of Clementine"),
336+ icon_name: "media-playback-stop",
337+ has_thumbnail: false,
338+ match_type: MatchType.ACTION
339+ );
340+ }
341+
342+ public override void do_action ()
343+ {
344+ try {
345+ ClementinePlayer player = Bus.get_proxy_sync (BusType.SESSION,
346+ ClementinePlayer.UNIQUE_NAME,
347+ ClementinePlayer.OBJECT_PATH);
348+ player.stop ();
349+ } catch (IOError e) {
350+ Utils.Logger.warning (this, "Clementine is not available.\n%s", e.message);
351+ }
352+ }
353+ }
354+
355+ /* ACTIONS FOR MP3s */
356+ private class OpenUri: ClementineAction
357+ {
358+ public OpenUri ()
359+ {
360+ Object (title: _ ("Play in Clementine"),
361+ description: _ ("Queues and plays the song"),
362+ icon_name: "media-playback-start",
363+ has_thumbnail: false,
364+ match_type: MatchType.ACTION,
365+ default_relevancy: Match.Score.ABOVE_AVERAGE
366+ );
367+ }
368+
369+ public override void execute_internal (Match? match)
370+ {
371+ return_if_fail (match.match_type == MatchType.GENERIC_URI);
372+ UriMatch uri = match as UriMatch;
373+ return_if_fail ((uri.file_type & QueryFlags.AUDIO) != 0 ||
374+ (uri.file_type & QueryFlags.VIDEO) != 0);
375+ try {
376+ ClementinePlayer player = Bus.get_proxy_sync (BusType.SESSION,
377+ ClementinePlayer.UNIQUE_NAME,
378+ ClementinePlayer.OBJECT_PATH);
379+ player.open_uri (uri.uri);
380+ player.play ();
381+ } catch (IOError e) {
382+ Utils.Logger.warning (this, "Clementine is not available.\n%s", e.message);
383+ }
384+ }
385+
386+ public override bool valid_for_match (Match match)
387+ {
388+ switch (match.match_type)
389+ {
390+ case MatchType.GENERIC_URI:
391+ UriMatch uri = match as UriMatch;
392+ if ((uri.file_type & QueryFlags.AUDIO) != 0 ||
393+ (uri.file_type & QueryFlags.VIDEO) != 0)
394+ return true;
395+ else
396+ return false;
397+ default:
398+ return false;
399+ }
400+ }
401+ }
402+
403+ private Gee.List<ClementineAction> actions;
404+ private Gee.List<ClementineControlMatch> matches;
405+
406+ construct
407+ {
408+ actions = new Gee.ArrayList<ClementineAction> ();
409+ matches = new Gee.ArrayList<ClementineControlMatch> ();
410+
411+ actions.add (new OpenUri());
412+
413+ matches.add (new Raise ());
414+ matches.add (new Quit ());
415+
416+ matches.add (new Play ());
417+ matches.add (new TogglePlaying ());
418+ matches.add (new Pause ());
419+ matches.add (new Stop ());
420+ matches.add (new Previous ());
421+ matches.add (new Next ());
422+ }
423+
424+ public async ResultSet? search (Query q) throws SearchError
425+ {
426+ // we only search for actions
427+ if (!(QueryFlags.ACTIONS in q.query_type)) return null;
428+
429+ var result = new ResultSet ();
430+
431+ var matchers = Query.get_matchers_for_query (q.query_string, 0,
432+ RegexCompileFlags.OPTIMIZE | RegexCompileFlags.CASELESS);
433+
434+ foreach (var action in matches)
435+ {
436+ if (!action.action_available ()) continue;
437+ foreach (var matcher in matchers)
438+ {
439+ if (matcher.key.match (action.title))
440+ {
441+ result.add (action, matcher.value - Match.Score.INCREMENT_SMALL);
442+ break;
443+ }
444+ }
445+ }
446+ q.check_cancellable ();
447+ return result;
448+ }
449+
450+ public ResultSet? find_for_match (ref Query query, Match match)
451+ {
452+ bool query_empty = query.query_string == "";
453+ var results = new ResultSet ();
454+
455+ if (query_empty)
456+ {
457+ foreach (var action in actions)
458+ {
459+ if (action.valid_for_match (match))
460+ {
461+ results.add (action, action.get_relevancy ());
462+ }
463+ }
464+ }
465+ else
466+ {
467+ var matchers = Query.get_matchers_for_query (query.query_string, 0,
468+ RegexCompileFlags.OPTIMIZE | RegexCompileFlags.CASELESS);
469+ foreach (var action in actions)
470+ {
471+ if (!action.valid_for_match (match)) continue;
472+ foreach (var matcher in matchers)
473+ {
474+ if (matcher.key.match (action.title))
475+ {
476+ results.add (action, matcher.value);
477+ break;
478+ }
479+ }
480+ }
481+ }
482+ return results;
483+ }
484+ }
485+}
486
487=== modified file 'src/ui/synapse-main.vala'
488--- src/ui/synapse-main.vala 2013-07-20 18:34:34 +0000
489+++ src/ui/synapse-main.vala 2013-11-18 21:23:46 +0000
490@@ -177,6 +177,7 @@
491 typeof (SelectionPlugin),
492 typeof (SshPlugin),
493 typeof (XnoiseActions),
494+ typeof (ClementineActions),
495 // typeof (FileOpPlugin),
496 // typeof (PidginPlugin),
497 // typeof (ChatActions),