1/*
2 * Copyright (C) 2012 Jörn Magens <shuerhaaken@googlemail.com>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
17 *
18 * Authored by Jörn Magens <shuerhaaken@googlemail.com>
19 *
20 */
21
22namespace Synapse
23{
24  [DBus (name = "org.gtk.xnoise.PlayerEngine")]
25  private interface XnoisePlayerEngine : Object
26  {
27    public const string UNIQUE_NAME = "org.gtk.xnoise.PlayerEngine";
28    public const string OBJECT_PATH = "/PlayerEngine";
29
30    public abstract void quit () throws Error;
31    public abstract void raise () throws Error;
32
33    public abstract void next () throws Error;
34    public abstract void previous () throws Error;
35    public abstract void pause () throws Error;
36    public abstract void toggle_playing () throws Error;
37    public abstract void stop () throws Error;
38    public abstract void play () throws Error;
39    public abstract void open_uri (string uri) throws Error;
40  }
41
42  public class XnoiseActions : Object, Activatable, ItemProvider, ActionProvider
43  {
44    public bool enabled { get; set; default = true; }
45
46    public void activate ()
47    {
48    }
49
50    public void deactivate ()
51    {
52    }
53
54    static void register_plugin ()
55    {
56      PluginRegistry.get_default ().register_plugin (
57        typeof (XnoiseActions),
58        "Xnoise",
59        _("Control Xnoise media player."),
60        "xnoise",
61        register_plugin,
62        Environment.find_program_in_path ("xnoise") != null,
63        _("Xnoise is not installed!")
64      );
65    }
66
67    static construct
68    {
69      register_plugin ();
70    }
71
72    private abstract class XnoiseAction : Action
73    {
74      public virtual int get_relevancy ()
75      {
76        bool xnoise_running = DBusService.get_default ().name_has_owner (XnoisePlayerEngine.UNIQUE_NAME);
77        return xnoise_running ? default_relevancy + MatchScore.INCREMENT_LARGE : default_relevancy;
78      }
79    }
80
81    private abstract class XnoiseControlMatch : ActionMatch
82    {
83      public virtual bool action_available ()
84      {
85        return DBusService.get_default ().name_has_owner (XnoisePlayerEngine.UNIQUE_NAME);
86      }
87    }
88
89    /* MATCHES of Type.ACTION */
90    private class Quit : XnoiseControlMatch
91    {
92      public Quit ()
93      {
94        Object (title:         _("Quit"),
95                description:   _("Quit Xnoise"),
96                icon_name:     "gtk-close",
97                has_thumbnail: false);
98      }
99
100      public override void do_action ()
101      {
102        try {
103          XnoisePlayerEngine player = Bus.get_proxy_sync (BusType.SESSION,
104                                           XnoisePlayerEngine.UNIQUE_NAME,
105                                           XnoisePlayerEngine.OBJECT_PATH);
106          player.quit ();
107        } catch (Error e) {
108          warning ("Xnoise is not available.\n%s", e.message);
109        }
110      }
111    }
112
113    private class Raise : XnoiseControlMatch
114    {
115      public Raise ()
116      {
117        Object (title:         _("Raise"),
118                description:   _("Show Xnoise"),
119                icon_name:     "xnoise",
120                has_thumbnail: false);
121      }
122
123      public override void do_action ()
124      {
125        try {
126          XnoisePlayerEngine player = Bus.get_proxy_sync (BusType.SESSION,
127                                           XnoisePlayerEngine.UNIQUE_NAME,
128                                           XnoisePlayerEngine.OBJECT_PATH);
129          player.raise ();
130        } catch (Error e) {
131          warning ("Xnoise is not available.\n%s", e.message);
132        }
133      }
134    }
135
136    private class Play : XnoiseControlMatch
137    {
138      public Play ()
139      {
140        Object (title:         _("Play"),
141                description:   _("Start playback in Xnoise"),
142                icon_name:     "media-playback-start",
143                has_thumbnail: false);
144      }
145
146      public override void do_action ()
147      {
148        try {
149          XnoisePlayerEngine player = Bus.get_proxy_sync (BusType.SESSION,
150                                           XnoisePlayerEngine.UNIQUE_NAME,
151                                           XnoisePlayerEngine.OBJECT_PATH);
152          player.play ();
153        } catch (Error e) {
154          warning ("Xnoise is not available.\n%s", e.message);
155        }
156      }
157    }
158
159    private class TogglePlaying : XnoiseControlMatch
160    {
161      public TogglePlaying ()
162      {
163        Object (title:         _("TogglePlaying"),
164                description:   _("Start/Pause playback in Xnoise"),
165                icon_name:     "media-playback-pause",
166                has_thumbnail: false);
167      }
168
169      public override void do_action ()
170      {
171        try {
172          XnoisePlayerEngine player = Bus.get_proxy_sync (BusType.SESSION,
173                                           XnoisePlayerEngine.UNIQUE_NAME,
174                                           XnoisePlayerEngine.OBJECT_PATH);
175          player.toggle_playing ();
176        } catch (Error e) {
177          warning ("Xnoise is not available.\n%s", e.message);
178        }
179      }
180
181      public override bool action_available ()
182      {
183        return true;
184      }
185    }
186
187    private class Pause : XnoiseControlMatch
188    {
189      public Pause ()
190      {
191        Object (title:         _("Pause"),
192                description:   _("Pause playback in Xnoise"),
193                icon_name:     "media-playback-pause",
194                has_thumbnail: false);
195      }
196
197      public override void do_action ()
198      {
199        try {
200          XnoisePlayerEngine player = Bus.get_proxy_sync (BusType.SESSION,
201                                           XnoisePlayerEngine.UNIQUE_NAME,
202                                           XnoisePlayerEngine.OBJECT_PATH);
203          player.pause ();
204        } catch (Error e) {
205          warning ("Xnoise is not available.\n%s", e.message);
206        }
207      }
208    }
209
210    private class Next : XnoiseControlMatch
211    {
212      public Next ()
213      {
214        Object (title:         _("Next"),
215                description:   _("Plays the next song in Xnoise's playlist"),
216                icon_name:     "media-skip-forward",
217                has_thumbnail: false);
218      }
219
220      public override void do_action ()
221      {
222        try {
223          XnoisePlayerEngine player = Bus.get_proxy_sync (BusType.SESSION,
224                                           XnoisePlayerEngine.UNIQUE_NAME,
225                                           XnoisePlayerEngine.OBJECT_PATH);
226
227          player.next ();
228        } catch (Error e) {
229          warning ("Xnoise is not available.\n%s", e.message);
230        }
231      }
232    }
233
234    private class Previous : XnoiseControlMatch
235    {
236      public Previous ()
237      {
238        Object (title:         _("Previous"),
239                description:   _("Plays the previous song in Xnoise's playlist"),
240                icon_name:     "media-skip-backward",
241                has_thumbnail: false);
242      }
243
244      public override void do_action ()
245      {
246        try {
247          XnoisePlayerEngine player = Bus.get_proxy_sync (BusType.SESSION,
248                                           XnoisePlayerEngine.UNIQUE_NAME,
249                                           XnoisePlayerEngine.OBJECT_PATH);
250          player.previous ();
251        } catch (Error e) {
252          warning ("Xnoise is not available.\n%s", e.message);
253        }
254      }
255    }
256
257    private class Stop : XnoiseControlMatch
258    {
259      public Stop ()
260      {
261        Object (title:         _("Stop"),
262                description:   _("Stops the playback of Xnoise"),
263                icon_name:     "media-playback-stop",
264                has_thumbnail: false);
265      }
266
267      public override void do_action ()
268      {
269        try {
270          XnoisePlayerEngine player = Bus.get_proxy_sync (BusType.SESSION,
271                                           XnoisePlayerEngine.UNIQUE_NAME,
272                                           XnoisePlayerEngine.OBJECT_PATH);
273          player.stop ();
274        } catch (Error e) {
275          warning ("Xnoise is not available.\n%s", e.message);
276        }
277      }
278    }
279
280    /* ACTIONS FOR MP3s */
281    private class OpenUri : XnoiseAction
282    {
283      public OpenUri ()
284      {
285        Object (title: _("Play in Xnoise"),
286                description: _("Queues and plays the song"),
287                icon_name: "media-playback-start",
288                has_thumbnail: false,
289                default_relevancy: MatchScore.ABOVE_AVERAGE
290                );
291      }
292
293      public override void do_execute (Match match, Match? target = null)
294      {
295        unowned UriMatch? uri = match as UriMatch;
296        return_if_fail (uri != null);
297        return_if_fail ((uri.file_type & QueryFlags.AUDIO) != 0 ||
298                        (uri.file_type & QueryFlags.VIDEO) != 0);
299
300        try {
301          XnoisePlayerEngine player = Bus.get_proxy_sync (BusType.SESSION,
302                                           XnoisePlayerEngine.UNIQUE_NAME,
303                                           XnoisePlayerEngine.OBJECT_PATH);
304          player.open_uri (uri.uri);
305          player.play ();
306        } catch (Error e) {
307          warning ("Xnoise is not available.\n%s", e.message);
308        }
309      }
310
311      public override bool valid_for_match (Match match)
312      {
313        unowned UriMatch? uri_match = match as UriMatch;
314        if (uri_match == null)
315          return false;
316
317        return ((uri_match.file_type & QueryFlags.AUDIO) != 0 ||
318                (uri_match.file_type & QueryFlags.VIDEO) != 0);
319      }
320    }
321
322    private Gee.List<XnoiseAction> actions;
323    private Gee.List<XnoiseControlMatch> matches;
324
325    construct
326    {
327      actions = new Gee.ArrayList<XnoiseAction> ();
328      matches = new Gee.ArrayList<XnoiseControlMatch> ();
329
330      actions.add (new OpenUri());
331
332      matches.add (new Raise ());
333      matches.add (new Quit ());
334
335      matches.add (new Play ());
336      matches.add (new TogglePlaying ());
337      matches.add (new Pause ());
338      matches.add (new Stop ());
339      matches.add (new Previous ());
340      matches.add (new Next ());
341    }
342
343    public async ResultSet? search (Query q) throws SearchError
344    {
345      // we only search for actions
346      if (!(QueryFlags.ACTIONS in q.query_type)) return null;
347
348      var result = new ResultSet ();
349
350      var matchers = Query.get_matchers_for_query (q.query_string, 0,
351        RegexCompileFlags.OPTIMIZE | RegexCompileFlags.CASELESS);
352
353      foreach (var action in matches)
354      {
355        if (!action.action_available ()) continue;
356        foreach (var matcher in matchers)
357        {
358          if (matcher.key.match (action.title))
359          {
360            result.add (action, matcher.value - MatchScore.INCREMENT_SMALL);
361            break;
362          }
363        }
364      }
365      q.check_cancellable ();
366      return result;
367    }
368
369    public ResultSet? find_for_match (ref Query query, Match match)
370    {
371      bool query_empty = query.query_string == "";
372      var results = new ResultSet ();
373
374      if (query_empty)
375      {
376        foreach (var action in actions)
377        {
378          if (action.valid_for_match (match))
379          {
380            results.add (action, action.get_relevancy ());
381          }
382        }
383      }
384      else
385      {
386        var matchers = Query.get_matchers_for_query (query.query_string, 0,
387          RegexCompileFlags.OPTIMIZE | RegexCompileFlags.CASELESS);
388        foreach (var action in actions)
389        {
390          if (!action.valid_for_match (match)) continue;
391          foreach (var matcher in matchers)
392          {
393            if (matcher.key.match (action.title))
394            {
395              results.add (action, matcher.value);
396              break;
397            }
398          }
399        }
400      }
401      return results;
402    }
403  }
404}
405