2
* rb-notification-plugin.c
4
* Copyright (C) 2010 Jonathan Matthew <jonathan@d14n.org>
6
* This program is free software; you can redistribute it and/or modify
7
* it under the terms of the GNU General Public License as published by
8
* the Free Software Foundation; either version 2, or (at your option)
11
* The Rhythmbox authors hereby grant permission for non-GPL compatible
12
* GStreamer plugins to be used and distributed together with GStreamer
13
* and Rhythmbox. This permission is above and beyond the permissions granted
14
* by the GPL license by which Rhythmbox is covered. If you modify this code
15
* you may extend this exception to your version of the code, but you are not
16
* obligated to do so. If you do not wish to do so, delete this exception
17
* statement from your version.
19
* This program is distributed in the hope that it will be useful,
20
* but WITHOUT ANY WARRANTY; without even the implied warranty of
21
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22
* GNU General Public License for more details.
24
* You should have received a copy of the GNU General Public License
25
* along with this program; if not, write to the Free Software
26
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
32
#include <glib/gi18n-lib.h>
35
#include <glib-object.h>
36
#include <pango/pango-bidi-type.h>
37
#include <libnotify/notify.h>
40
#include "rb-plugin-macros.h"
43
#include "rb-shell-player.h"
44
#include "rb-stock-icons.h"
46
#define PLAYING_ENTRY_NOTIFY_TIME 4
48
#define RB_TYPE_NOTIFICATION_PLUGIN (rb_notification_plugin_get_type ())
49
#define RB_NOTIFICATION_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_NOTIFICATION_PLUGIN, RBNotificationPlugin))
50
#define RB_NOTIFICATION_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), RB_TYPE_NOTIFICATION_PLUGIN, RBNotificationPluginClass))
51
#define RB_IS_NOTIFICATION_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_NOTIFICATION_PLUGIN))
52
#define RB_IS_NOTIFICATION_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_NOTIFICATION_PLUGIN))
53
#define RB_NOTIFICATION_PLUGIN_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_NOTIFICATION_PLUGIN, RBNotificationPluginClass))
57
PeasExtensionBase parent;
59
/* current playing data */
61
char *current_album_and_artist; /* from _album_ by _artist_ */
63
gchar *notify_art_path;
64
NotifyNotification *notification;
65
gboolean notify_supports_actions;
66
gboolean notify_supports_icon_buttons;
67
gboolean notify_supports_persistence;
69
RBShellPlayer *shell_player;
71
} RBNotificationPlugin;
75
PeasExtensionBaseClass parent_class;
76
} RBNotificationPluginClass;
78
G_MODULE_EXPORT void peas_register_types (PeasObjectModule *module);
80
RB_DEFINE_PLUGIN(RB_TYPE_NOTIFICATION_PLUGIN, RBNotificationPlugin, rb_notification_plugin,)
83
markup_escape (const char *text)
85
return (text == NULL) ? NULL : g_markup_escape_text (text, -1);
89
notification_closed_cb (NotifyNotification *notification,
90
RBNotificationPlugin *plugin)
92
rb_debug ("notification closed");
96
notification_next_cb (NotifyNotification *notification,
98
RBNotificationPlugin *plugin)
100
rb_debug ("notification action: %s", action);
101
rb_shell_player_do_next (plugin->shell_player, NULL);
105
notification_playpause_cb (NotifyNotification *notification,
107
RBNotificationPlugin *plugin)
109
rb_debug ("notification action: %s", action);
110
rb_shell_player_playpause (plugin->shell_player, FALSE, NULL);
114
notification_previous_cb (NotifyNotification *notification,
116
RBNotificationPlugin *plugin)
118
rb_debug ("notification action: %s", action);
119
rb_shell_player_do_previous (plugin->shell_player, NULL);
123
do_notify (RBNotificationPlugin *plugin,
126
const char *secondary,
127
const char *image_uri,
130
GError *error = NULL;
131
NotifyNotification *notification;
133
if (notify_is_initted () == FALSE) {
136
if (notify_init ("Rhythmbox") == FALSE) {
137
g_warning ("libnotify initialization failed");
141
/* ask the notification server if it supports actions */
142
caps = notify_get_server_caps ();
143
if (g_list_find_custom (caps, "actions", (GCompareFunc)g_strcmp0) != NULL) {
144
rb_debug ("notification server supports actions");
145
plugin->notify_supports_actions = TRUE;
147
if (g_list_find_custom (caps, "action-icons", (GCompareFunc)g_strcmp0) != NULL) {
148
rb_debug ("notifiction server supports icon buttons");
149
plugin->notify_supports_icon_buttons = TRUE;
152
rb_debug ("notification server does not support actions");
154
if (g_list_find_custom (caps, "persistence", (GCompareFunc)g_strcmp0) != NULL) {
155
rb_debug ("notification server supports persistence");
156
plugin->notify_supports_persistence = TRUE;
158
rb_debug ("notification server does not support persistence");
161
rb_list_deep_free (caps);
167
if (secondary == NULL)
171
notification = plugin->notification;
176
if (notification == NULL) {
177
notification = notify_notification_new (primary, secondary, RB_APP_ICON);
179
g_signal_connect_object (notification,
181
G_CALLBACK (notification_closed_cb),
184
plugin->notification = notification;
187
notify_notification_clear_hints (notification);
188
notify_notification_update (notification, primary, secondary, RB_APP_ICON);
191
notify_notification_set_timeout (notification, timeout);
193
if (image_uri != NULL) {
194
notify_notification_clear_hints (notification);
195
notify_notification_set_hint_string (notification,
200
notify_notification_clear_actions (notification);
201
if (playback && plugin->notify_supports_actions) {
202
if (plugin->notify_supports_icon_buttons) {
203
gboolean playing = FALSE;
204
rb_shell_player_get_playing (plugin->shell_player, &playing, NULL);
206
notify_notification_add_action (notification,
207
"media-skip-backward",
209
(NotifyActionCallback) notification_previous_cb,
212
notify_notification_add_action (notification,
213
playing ? "media-playback-pause" : "media-playback-start",
214
playing ? _("Pause") : _("Play"),
215
(NotifyActionCallback) notification_playpause_cb,
218
notify_notification_set_hint_byte (notification,
223
notify_notification_add_action (notification,
224
"media-skip-forward",
226
(NotifyActionCallback) notification_next_cb,
231
if (plugin->notify_supports_persistence) {
239
notify_notification_set_hint_byte (notification,
244
if (notify_notification_show (notification, &error) == FALSE) {
245
g_warning ("Failed to send notification (%s): %s", primary, error->message);
246
g_error_free (error);
251
notify_playing_entry (RBNotificationPlugin *plugin, gboolean requested)
254
PLAYING_ENTRY_NOTIFY_TIME * 1000,
255
plugin->current_title,
256
plugin->current_album_and_artist,
257
plugin->notify_art_path,
262
notify_custom (RBNotificationPlugin *plugin,
265
const char *secondary,
266
const char *image_uri,
269
do_notify (plugin, timeout, primary, secondary, image_uri, FALSE);
273
cleanup_notification (RBNotificationPlugin *plugin)
275
if (plugin->notification != NULL) {
276
g_signal_handlers_disconnect_by_func (plugin->notification,
277
G_CALLBACK (notification_closed_cb),
279
notify_notification_close (plugin->notification, NULL);
280
plugin->notification = NULL;
285
shell_notify_playing_cb (RBShell *shell, gboolean requested, RBNotificationPlugin *plugin)
287
notify_playing_entry (plugin, requested);
291
shell_notify_custom_cb (RBShell *shell,
294
const char *secondary,
295
const char *image_uri,
297
RBNotificationPlugin *plugin)
299
notify_custom (plugin, timeout, primary, secondary, image_uri, requested);
303
get_artist_album_templates (const char *artist,
305
const char **artist_template,
306
const char **album_template)
308
PangoDirection tag_dir;
309
PangoDirection template_dir;
311
/* Translators: by Artist */
312
*artist_template = _("by <i>%s</i>");
313
/* Translators: from Album */
314
*album_template = _("from <i>%s</i>");
316
/* find the direction (left-to-right or right-to-left) of the
317
* track's tags and the localized templates
319
if (artist != NULL && artist[0] != '\0') {
320
tag_dir = pango_find_base_dir (artist, -1);
321
template_dir = pango_find_base_dir (*artist_template, -1);
322
} else if (album != NULL && album[0] != '\0') {
323
tag_dir = pango_find_base_dir (album, -1);
324
template_dir = pango_find_base_dir (*album_template, -1);
329
/* if the track's tags and the localized templates have a different
330
* direction, switch to direction-neutral templates in order to improve
332
* text can have a neutral direction, this condition only applies when
333
* both directions are defined and they are conflicting.
334
* https://bugzilla.gnome.org/show_bug.cgi?id=609767
336
if (((tag_dir == PANGO_DIRECTION_LTR) && (template_dir == PANGO_DIRECTION_RTL)) ||
337
((tag_dir == PANGO_DIRECTION_RTL) && (template_dir == PANGO_DIRECTION_LTR))) {
338
/* these strings should not be localized, they must be
339
* locale-neutral and direction-neutral
341
*artist_template = "<i>%s</i>";
342
*album_template = "/ <i>%s</i>";
347
update_current_playing_data (RBNotificationPlugin *plugin, RhythmDBEntry *entry)
350
const char *stream_title = NULL;
356
const char *artist_template = NULL;
357
const char *album_template = NULL;
359
g_free (plugin->current_title);
360
g_free (plugin->current_album_and_artist);
361
g_free (plugin->notify_art_path);
362
plugin->current_title = NULL;
363
plugin->current_album_and_artist = NULL;
364
plugin->notify_art_path = NULL;
367
plugin->current_title = g_strdup (_("Not Playing"));
368
plugin->current_album_and_artist = g_strdup ("");
372
secondary = g_string_sized_new (100);
374
/* get artist, preferring streaming song details */
375
value = rhythmdb_entry_request_extra_metadata (plugin->db,
377
RHYTHMDB_PROP_STREAM_SONG_ARTIST);
379
artist = markup_escape (g_value_get_string (value));
380
g_value_unset (value);
383
artist = markup_escape (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST));
386
/* get album, preferring streaming song details */
387
value = rhythmdb_entry_request_extra_metadata (plugin->db,
389
RHYTHMDB_PROP_STREAM_SONG_ALBUM);
391
album = markup_escape (g_value_get_string (value));
392
g_value_unset (value);
395
album = markup_escape (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM));
398
get_artist_album_templates (artist, album, &artist_template, &album_template);
400
if (artist != NULL && artist[0] != '\0') {
401
g_string_append_printf (secondary, artist_template, artist);
405
if (album != NULL && album[0] != '\0') {
406
if (secondary->len != 0)
407
g_string_append_c (secondary, ' ');
409
g_string_append_printf (secondary, album_template, album);
413
/* get title and possibly stream name.
414
* if we have a streaming song title, the entry's title
415
* property is the stream name.
417
value = rhythmdb_entry_request_extra_metadata (plugin->db,
419
RHYTHMDB_PROP_STREAM_SONG_TITLE);
421
title = g_value_dup_string (value);
422
g_value_unset (value);
425
stream_title = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE);
427
title = g_strdup (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE));
430
if (stream_title != NULL && stream_title[0] != '\0') {
433
escaped = markup_escape (stream_title);
434
if (secondary->len == 0)
435
g_string_append (secondary, escaped);
437
g_string_append_printf (secondary, " (%s)", escaped);
442
/* Translators: unknown track title */
443
title = g_strdup (_("Unknown"));
446
plugin->current_title = title;
447
plugin->current_album_and_artist = g_string_free (secondary, FALSE);
451
playing_entry_changed_cb (RBShellPlayer *player,
452
RhythmDBEntry *entry,
453
RBNotificationPlugin *plugin)
455
update_current_playing_data (plugin, entry);
458
notify_playing_entry (plugin, FALSE);
463
is_playing_entry (RBNotificationPlugin *plugin, RhythmDBEntry *entry)
465
RhythmDBEntry *playing;
467
playing = rb_shell_player_get_playing_entry (plugin->shell_player);
468
if (playing == NULL) {
472
rhythmdb_entry_unref (playing);
473
return (entry == playing);
477
db_art_uri_metadata_cb (RhythmDB *db,
478
RhythmDBEntry *entry,
481
RBNotificationPlugin *plugin)
485
if (is_playing_entry (plugin, entry) == FALSE)
488
if (G_VALUE_HOLDS (metadata, G_TYPE_STRING)) {
489
const char *uri = g_value_get_string (metadata);
490
if (g_str_has_prefix (uri, "file://")) {
491
char *path = g_filename_from_uri (uri, NULL, NULL);
492
if (g_strcmp0 (path, plugin->notify_art_path) != 0) {
493
g_free (plugin->notify_art_path);
494
plugin->notify_art_path = path;
496
/* same art URI, ignore it */
501
/* unsupported art URI, ignore it */
505
g_free (plugin->notify_art_path);
506
plugin->notify_art_path = NULL;
509
if (rb_shell_player_get_playing_time (plugin->shell_player, &time, NULL)) {
510
if (time < PLAYING_ENTRY_NOTIFY_TIME) {
511
notify_playing_entry (plugin, FALSE);
514
notify_playing_entry (plugin, FALSE);
519
db_stream_metadata_cb (RhythmDB *db,
520
RhythmDBEntry *entry,
523
RBNotificationPlugin *plugin)
525
if (is_playing_entry (plugin, entry) == FALSE)
528
update_current_playing_data (plugin, entry);
531
/* plugin infrastructure */
534
impl_activate (PeasActivatable *bplugin)
536
RBNotificationPlugin *plugin;
539
rb_debug ("activating notification plugin");
541
plugin = RB_NOTIFICATION_PLUGIN (bplugin);
542
g_object_get (plugin, "object", &shell, NULL);
544
"shell-player", &plugin->shell_player,
548
/* connect various things */
549
g_signal_connect_object (shell, "notify-playing-entry", G_CALLBACK (shell_notify_playing_cb), plugin, 0);
550
g_signal_connect_object (shell, "notify-custom", G_CALLBACK (shell_notify_custom_cb), plugin, 0);
552
g_signal_connect_object (plugin->shell_player, "playing-song-changed", G_CALLBACK (playing_entry_changed_cb), plugin, 0);
554
g_signal_connect_object (plugin->db, "entry_extra_metadata_notify::" RHYTHMDB_PROP_COVER_ART_URI,
555
G_CALLBACK (db_art_uri_metadata_cb), plugin, 0);
556
g_signal_connect_object (plugin->db, "entry_extra_metadata_notify::" RHYTHMDB_PROP_STREAM_SONG_TITLE,
557
G_CALLBACK (db_stream_metadata_cb), plugin, 0);
558
g_signal_connect_object (plugin->db, "entry_extra_metadata_notify::" RHYTHMDB_PROP_STREAM_SONG_ARTIST,
559
G_CALLBACK (db_stream_metadata_cb), plugin, 0);
560
g_signal_connect_object (plugin->db, "entry_extra_metadata_notify::" RHYTHMDB_PROP_STREAM_SONG_ALBUM,
561
G_CALLBACK (db_stream_metadata_cb), plugin, 0);
563
/* hook into shell preferences so we can poke stuff into the general prefs page? */
565
g_object_unref (shell);
569
impl_deactivate (PeasActivatable *bplugin)
571
RBNotificationPlugin *plugin;
574
plugin = RB_NOTIFICATION_PLUGIN (bplugin);
576
g_object_get (plugin, "object", &shell, NULL);
578
cleanup_notification (plugin);
580
/* disconnect signal handlers used to update the icon */
581
if (plugin->shell_player != NULL) {
582
g_signal_handlers_disconnect_by_func (plugin->shell_player, playing_entry_changed_cb, plugin);
584
g_object_unref (plugin->shell_player);
585
plugin->shell_player = NULL;
588
if (plugin->db != NULL) {
589
g_signal_handlers_disconnect_by_func (plugin->db, db_art_uri_metadata_cb, plugin);
590
g_signal_handlers_disconnect_by_func (plugin->db, db_stream_metadata_cb, plugin);
592
g_object_unref (plugin->db);
596
g_signal_handlers_disconnect_by_func (shell, shell_notify_playing_cb, plugin);
597
g_signal_handlers_disconnect_by_func (shell, shell_notify_custom_cb, plugin);
599
/* forget what's playing */
600
g_free (plugin->current_title);
601
g_free (plugin->current_album_and_artist);
602
g_free (plugin->notify_art_path);
603
plugin->current_title = NULL;
604
plugin->current_album_and_artist = NULL;
605
plugin->notify_art_path = NULL;
607
g_object_unref (shell);
611
rb_notification_plugin_init (RBNotificationPlugin *plugin)
616
peas_register_types (PeasObjectModule *module)
618
rb_notification_plugin_register_type (G_TYPE_MODULE (module));
619
peas_object_module_register_extension_type (module,
620
PEAS_TYPE_ACTIVATABLE,
621
RB_TYPE_NOTIFICATION_PLUGIN);