diff --git a/js/ui/messageTray.js b/js/ui/messageTray.js index d4bd7b51e..7376456b5 100644 --- a/js/ui/messageTray.js +++ b/js/ui/messageTray.js @@ -331,6 +331,10 @@ Signals.addSignalMethods(NotificationPolicy.prototype); // the content and the action area of the notification will be cleared. // The content area is also always cleared if 'customContent' is false // because it might contain the @banner that didn't fit in the banner mode. +// +// If @params contains 'soundName' or 'soundFile', the corresponding +// event sound is played when the notification is shown (if the policy for +// @source allows playing sounds). const Notification = new Lang.Class({ Name: 'Notification', @@ -360,6 +364,9 @@ const Notification = new Lang.Class({ this._spacing = 0; this._scrollPolicy = Gtk.PolicyType.AUTOMATIC; this._imageBin = null; + this._soundName = null; + this._soundFile = null; + this._soundPlayed = false; source.connect('destroy', Lang.bind(this, function (source, reason) { @@ -426,7 +433,9 @@ const Notification = new Lang.Class({ titleMarkup: false, bannerMarkup: false, bodyMarkup: false, - clear: false }); + clear: false, + soundName: null, + soundFile: null }); this._customContent = params.customContent; @@ -524,6 +533,14 @@ const Notification = new Lang.Class({ if (params.body) this.addBody(params.body, params.bodyMarkup); + + if (this._soundName != params.soundName || + this._soundFile != params.soundFile) { + this._soundName = params.soundName; + this._soundFile = params.soundFile; + this._soundPlayed = false; + } + this.updated(); }, @@ -884,6 +901,38 @@ const Notification = new Lang.Class({ (!this._titleFitsInBannerMode && !this._table.has_style_class_name('multi-line-notification')); }, + playSound: function() { + if (this._soundPlayed) + return; + + if (!this.source.policy.enableSound) { + this._soundPlayed = true; + return; + } + + if (this._soundName) { + if (this.source.app) { + let app = this.source.app; + + global.play_theme_sound_full(0, this._soundName, + this.title, null, + app.get_id(), app.get_name()); + } else { + global.play_theme_sound(0, this._soundName, this.title, null); + } + } else if (this._soundFile) { + if (this.source.app) { + let app = this.source.app; + + global.play_sound_file_full(0, this._soundFile, + this.title, null, + app.get_id(), app.get_name()); + } else { + global.play_sound_file(0, this._soundFile, this.title, null); + } + } + }, + updated: function() { if (this.expanded) this.expand(false); @@ -1233,8 +1282,16 @@ const Source = new Lang.Class({ notification.acknowledged = false; this.pushNotification(notification); - if (!this.isMuted && this.policy.showBanners) - this.emit('notify', notification); + if (!this.isMuted) { + // Play the sound now, if banners are disabled. + // Otherwise, it will be played when the notification + // is next shown. + if (this.policy.showBanners) { + this.emit('notify', notification); + } else { + notification.playSound(); + } + } }, destroy: function(reason) { @@ -1998,9 +2055,10 @@ const MessageTray = new Lang.Class({ } else { // The summary box pointer is showing or shown (otherwise, // this._summaryBoxPointerItem would be null) - // Immediately mark the notification as acknowledged, as it's - // not going into the queue + // Immediately mark the notification as acknowledged and play its + // sound, as it's not going into the queue notification.acknowledged = true; + notification.playSound(); } return; @@ -2434,6 +2492,7 @@ const MessageTray = new Lang.Class({ _updateShowingNotification: function() { this._notification.acknowledged = true; + this._notification.playSound(); // We auto-expand notifications with CRITICAL urgency, or for which the relevant setting // is on in the control center. diff --git a/js/ui/notificationDaemon.js b/js/ui/notificationDaemon.js index e02bf22c3..28d2cfbda 100644 --- a/js/ui/notificationDaemon.js +++ b/js/ui/notificationDaemon.js @@ -527,7 +527,9 @@ const NotificationDaemon = new Lang.Class({ notification.update(summary, body, { gicon: gicon, bannerMarkup: true, - clear: true }); + clear: true, + soundFile: hints['sound-file'], + soundName: hints['sound-name'] }); notification.setImage(image); if (actions.length) { @@ -582,7 +584,7 @@ const NotificationDaemon = new Lang.Class({ // 'icon-multi', 'icon-static', 'persistence', - // 'sound', + 'sound', ]; }, diff --git a/src/shell-global.c b/src/shell-global.c index 734433471..e8559ebca 100644 --- a/src/shell-global.c +++ b/src/shell-global.c @@ -1537,11 +1537,12 @@ shell_global_run_at_leisure (ShellGlobal *global, static void build_ca_proplist_for_event (ca_proplist *props, + const char *event_property, const char *event_id, const char *event_description, ClutterEvent *for_event) { - ca_proplist_sets (props, CA_PROP_EVENT_ID, event_id); + ca_proplist_sets (props, event_property, event_id); ca_proplist_sets (props, CA_PROP_EVENT_DESCRIPTION, event_description); ca_proplist_sets (props, CA_PROP_CANBERRA_CACHE_CONTROL, "volatile"); @@ -1589,7 +1590,7 @@ shell_global_play_theme_sound (ShellGlobal *global, ca_proplist *props; ca_proplist_create (&props); - build_ca_proplist_for_event (props, name, description, for_event); + build_ca_proplist_for_event (props, CA_PROP_EVENT_ID, name, description, for_event); ca_context_play_full (global->sound_context, id, props, NULL, NULL); @@ -1621,7 +1622,7 @@ shell_global_play_theme_sound_full (ShellGlobal *global, ca_proplist *props; ca_proplist_create (&props); - build_ca_proplist_for_event (props, name, description, for_event); + build_ca_proplist_for_event (props, CA_PROP_EVENT_ID, name, description, for_event); ca_proplist_sets (props, CA_PROP_APPLICATION_ID, application_id); ca_proplist_sets (props, CA_PROP_APPLICATION_NAME, application_name); @@ -1630,6 +1631,68 @@ shell_global_play_theme_sound_full (ShellGlobal *global, ca_proplist_destroy (props); } +/** + * shell_global_play_sound_file_full: + * @global: the #ShellGlobal + * @id: an id, used to cancel later (0 if not needed) + * @file_name: the file name to play + * @description: the localized description of the event that triggered this alert + * @for_event: (allow-none): a #ClutterEvent in response to which the sound is played + * @application_id: application on behalf of which the sound is played + * @application_name: + * + * Like shell_global_play_theme_sound_full(), but with an explicit path + * instead of a themed sound. + */ +void +shell_global_play_sound_file_full (ShellGlobal *global, + guint id, + const char *file_name, + const char *description, + ClutterEvent *for_event, + const char *application_id, + const char *application_name) +{ + ca_proplist *props; + + ca_proplist_create (&props); + build_ca_proplist_for_event (props, CA_PROP_MEDIA_FILENAME, file_name, description, for_event); + ca_proplist_sets (props, CA_PROP_APPLICATION_ID, application_id); + ca_proplist_sets (props, CA_PROP_APPLICATION_NAME, application_name); + + ca_context_play_full (global->sound_context, id, props, NULL, NULL); + + ca_proplist_destroy (props); +} + +/** + * shell_global_play_sound_file: + * @global: the #ShellGlobal + * @id: an id, used to cancel later (0 if not needed) + * @file_name: the file name to play + * @description: the localized description of the event that triggered this alert + * @for_event: (allow-none): a #ClutterEvent in response to which the sound is played + * + * Like shell_global_play_theme_sound(), but with an explicit path + * instead of a themed sound. + */ +void +shell_global_play_sound_file (ShellGlobal *global, + guint id, + const char *file_name, + const char *description, + ClutterEvent *for_event) +{ + ca_proplist *props; + + ca_proplist_create (&props); + build_ca_proplist_for_event (props, CA_PROP_MEDIA_FILENAME, file_name, description, for_event); + + ca_context_play_full (global->sound_context, id, props, NULL, NULL); + + ca_proplist_destroy (props); +} + /** * shell_global_cancel_theme_sound: * @global: the #ShellGlobal diff --git a/src/shell-global.h b/src/shell-global.h index 88b40bed5..a8df0f5bc 100644 --- a/src/shell-global.h +++ b/src/shell-global.h @@ -124,6 +124,19 @@ void shell_global_play_theme_sound_full (ShellGlobal *global, ClutterEvent *for_event, const char *application_id, const char *application_name); +void shell_global_play_sound_file (ShellGlobal *global, + guint id, + const char *file_name, + const char *description, + ClutterEvent *for_event); +void shell_global_play_sound_file_full (ShellGlobal *global, + guint id, + const char *file_name, + const char *description, + ClutterEvent *for_event, + const char *application_id, + const char *application_name); + void shell_global_cancel_theme_sound (ShellGlobal *global, guint id);