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

Proposed by Andrea Brancaleoni
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) Needs Resubmitting
Michal Hruby Needs Information
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.
Revision history for this message
Michal Hruby (mhr3) wrote :

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

review: Needs Information
Revision history for this message
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.

Revision history for this message
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: Needs Resubmitting
Revision history for this message
Jeremy Munsch (jeremy-munsch) wrote :

Oups sorry i miss clicked for the resubmit.

Unmerged revisions

514. By Andrea Brancaleoni <email address hidden>

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),