From ca394230218c2d75386e7c0e0b1b4bc0c16e0196 Mon Sep 17 00:00:00 2001 From: Gustavo Marques Date: Mon, 10 Jul 2023 17:44:04 -0300 Subject: [PATCH 1/3] handle urgency and desktop-entry hints in the server we already need to check thoses to block the GSD notfications instead of re-checking they in the notification class, only get they value one time in notify(). Signed-off-by: Gustavo Marques --- src/DBus.vala | 39 ++++++++++++++---- src/Notification.vala | 95 ++++++++++++++++--------------------------- 2 files changed, 67 insertions(+), 67 deletions(-) diff --git a/src/DBus.vala b/src/DBus.vala index 3ebcf2ec..7dcd22ec 100644 --- a/src/DBus.vala +++ b/src/DBus.vala @@ -74,15 +74,22 @@ public class Notifications.Server : Object { int32 expire_timeout, BusName sender ) throws DBusError, IOError { + NotificationPriority urgency = NORMAL; + string? app_id = null; + + if ("desktop-entry" in hints && hints["desktop-entry"].is_of_type (VariantType.STRING)) { + app_id = hints["desktop-entry"].get_string (); + } + + if ("urgency" in hints && hints["urgency"].is_of_type (VariantType.BYTE)) { + urgency = priority_from_urgency (hints["urgency"].get_byte ()); + } + // Silence "Automatic suspend. Suspending soon because of inactivity." notifications // These values and hints are taken from gnome-settings-daemon source code // See: https://gitlab.gnome.org/GNOME/gnome-settings-daemon/-/blob/master/plugins/power/gsd-power-manager.c#L356 // We must check for app_icon == "" to not block low power notifications - if ("desktop-entry" in hints && hints["desktop-entry"].get_string () == "gnome-power-panel" - && "urgency" in hints && hints["urgency"].get_byte () == 2 - && app_icon == "" - && expire_timeout == 0 - ) { + if (app_id == "gnome-power-panel" && urgency == URGENT && app_icon == "" && expire_timeout == 0) { debug ("Blocked GSD notification"); throw new DBusError.FAILED ("Notification Blocked"); } @@ -92,8 +99,11 @@ public class Notifications.Server : Object { if (hints.contains (X_CANONICAL_PRIVATE_SYNCHRONOUS)) { send_confirmation (app_icon, hints); } else { - var notification = new Notifications.Notification (app_name, app_icon, summary, body, actions, hints); - if (!settings.get_boolean ("do-not-disturb") || notification.priority == GLib.NotificationPriority.URGENT) { + var notification = new Notification (app_id, app_name, app_icon, summary, body, actions, hints) { + priority = urgency + }; + + if (!settings.get_boolean ("do-not-disturb") || notification.priority == URGENT) { var app_settings = new Settings.with_path ( "io.elementary.notifications.applications", settings.path.concat ("applications", "/", notification.app_id, "/") @@ -182,7 +192,20 @@ public class Notifications.Server : Object { CanberraGtk.context_get ().play_full (0, props); } - static unowned string category_to_sound_name (string category) { + // convert between freedesktop urgency levels and GLib.NotificationPriority levels + // See: https://specifications.freedesktop.org/notification-spec/notification-spec-latest.html#urgency-levels + private static NotificationPriority priority_from_urgency (uint8 urgency) { + switch (urgency) { + case 0: return LOW; + case 1: return NORMAL; + case 2: return URGENT; + default: + warning ("unknown urgency value: %u, ignoring", urgency); + return NORMAL; + } + } + + private static unowned string category_to_sound_name (string category) { unowned string sound; switch (category) { diff --git a/src/Notification.vala b/src/Notification.vala index 99267741..66a3e044 100644 --- a/src/Notification.vala +++ b/src/Notification.vala @@ -1,32 +1,33 @@ /* -* Copyright 2020 elementary, Inc. (https://elementary.io) -* -* This program is free software; you can redistribute it and/or -* modify it under the terms of the GNU General Public -* License as published by the Free Software Foundation; either -* version 3 of the License, or (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* General Public License for more details. -* -* You should have received a copy of the GNU General Public -* License along with this program; if not, write to the -* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, -* Boston, MA 02110-1301 USA -* -*/ + * Copyright 2020-2023 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: GPL-3.0-or-later + */ public class Notifications.Notification : GLib.Object { private const string OTHER_APP_ID = "gala-other"; - public GLib.DesktopAppInfo? app_info { get; private set; default = null; } - public GLib.NotificationPriority priority { get; private set; default = GLib.NotificationPriority.NORMAL; } + public DesktopAppInfo? app_info { get; construct; } + + public string app_id { + get { + if (_app_id == null) { + if (app_info != null && app_info.get_boolean ("X-GNOME-UsesNotifications")) { + _app_id = app_info.get_id (); + // GLib.DesktopAppInfo.get_id() always include the .desktop suffix. + _app_id = _app_id.substring (0, _app_id.last_index_of (".desktop")); + } else { + _app_id = "gala-other"; + } + } + + return _app_id; + } + } + + public NotificationPriority priority { get; set; default = NORMAL; } public HashTable hints { get; construct; } public string[] actions { get; construct; } public string app_icon { get; construct; } - public string app_id { get; private set; default = OTHER_APP_ID; } public string app_name { get; construct; } public string body { get; construct set; } public string summary { get; construct set; } @@ -35,11 +36,22 @@ public class Notifications.Notification : GLib.Object { public GLib.Icon? badge_icon { get; set; default = null; } public MaskedImage? image { get; set; default = null; } + private string? _app_id = null; + private static Regex entity_regex; private static Regex tag_regex; - public Notification (string app_name, string app_icon, string summary, string body, string[] actions, HashTable hints) { + public Notification ( + string? app_id, + string app_name, + string app_icon, + string summary, + string body, + string[] actions, + HashTable hints + ) { Object ( + app_info: app_id != null ? new DesktopAppInfo (app_id + ".desktop") : null, app_name: app_name, app_icon: app_icon, summary: summary, @@ -59,43 +71,6 @@ public class Notifications.Notification : GLib.Object { } construct { - unowned Variant? variant = null; - - // GLib.Notification.set_priority () - // convert between freedesktop urgency levels and GLib.NotificationPriority levels - // See: https://specifications.freedesktop.org/notification-spec/notification-spec-latest.html#urgency-levels - if ("urgency" in hints && hints["urgency"].is_of_type (VariantType.BYTE)) { - switch (hints["urgency"].get_byte ()) { - case 0: - priority = LOW; - break; - case 1: - priority = NORMAL; - break; - case 2: - priority = URGENT; - break; - default: - warning ("unknown urgency value: %i, ignoring", hints["urgency"].get_byte ()); - break; - } - } - - if ("desktop-entry" in hints && hints["desktop-entry"].is_of_type (VariantType.STRING)) { - app_info = new DesktopAppInfo ("%s.desktop".printf (hints["desktop-entry"].get_string ())); - - if (app_info != null && app_info.get_boolean ("X-GNOME-UsesNotifications")) { - var app_info_id = app_info.get_id (); - if (app_info_id != null) { - if (app_info_id.has_suffix (".desktop")) { - app_id = app_info_id.substring (0, app_info_id.length - ".desktop".length); - } else { - app_id = app_info_id; - } - } - } - } - // Always "" if sent by GLib.Notification if (app_icon == "" && app_info != null) { primary_icon = app_info.get_icon (); @@ -109,6 +84,8 @@ public class Notifications.Notification : GLib.Object { primary_icon = new ThemedIcon (app_icon); } + unowned Variant? variant = null; + // GLib.Notification.set_icon () if ((variant = hints.lookup ("image-path")) != null || (variant = hints.lookup ("image_path")) != null) { var image_path = variant.get_string (); From 337277d59dc3dd560c86cfd255fc676e0ab28021 Mon Sep 17 00:00:00 2001 From: Gustavo Marques Date: Mon, 10 Jul 2023 19:46:07 -0300 Subject: [PATCH 2/3] select the right title in the server the server is now in the responsability in defining the title and body, if it end up without one of those, we error out. body sanitization was moved from the bubble to the notification and extented to deal with carriage returns and force feed characters. Signed-off-by: Gustavo Marques --- src/Bubble.vala | 15 +------ src/DBus.vala | 20 ++++++++- src/Notification.vala | 100 +++++++++++++++++++++++------------------- 3 files changed, 76 insertions(+), 59 deletions(-) diff --git a/src/Bubble.vala b/src/Bubble.vala index 5fd8c994..0429a86e 100644 --- a/src/Bubble.vala +++ b/src/Bubble.vala @@ -108,7 +108,7 @@ public class Notifications.Bubble : AbstractBubble { } } - var title_label = new Gtk.Label (notification.summary) { + var title_label = new Gtk.Label (notification.title) { ellipsize = Pango.EllipsizeMode.END, max_width_chars = 33, valign = Gtk.Align.END, @@ -119,7 +119,7 @@ public class Notifications.Bubble : AbstractBubble { var body_label = new Gtk.Label (notification.body) { ellipsize = Pango.EllipsizeMode.END, - lines = 2, + lines = "\n" in notification.body ? 1 : 2, max_width_chars = 33, use_markup = true, valign = Gtk.Align.START, @@ -129,17 +129,6 @@ public class Notifications.Bubble : AbstractBubble { xalign = 0 }; - if ("\n" in notification.body) { - string[] lines = notification.body.split ("\n"); - string stripped_body = lines[0] + "\n"; - for (int i = 1; i < lines.length; i++) { - stripped_body += lines[i].strip () + ""; - } - - body_label.label = stripped_body.strip (); - body_label.lines = 1; - } - column_spacing = 6; attach (image_overlay, 0, 0, 1, 2); attach (title_label, 1, 0); diff --git a/src/DBus.vala b/src/DBus.vala index 7dcd22ec..7b2c8c2d 100644 --- a/src/DBus.vala +++ b/src/DBus.vala @@ -94,15 +94,31 @@ public class Notifications.Server : Object { throw new DBusError.FAILED ("Notification Blocked"); } - var id = (replaces_id != 0 ? replaces_id : ++id_counter); + var id = replaces_id; if (hints.contains (X_CANONICAL_PRIVATE_SYNCHRONOUS)) { send_confirmation (app_icon, hints); } else { - var notification = new Notification (app_id, app_name, app_icon, summary, body, actions, hints) { + // Only summary is required, so try to set a title when body is empty + if (body._strip () == "") { + body = summary._strip (); + summary = app_name._strip (); + } else if (summary._strip () == "") { + summary = app_name._strip (); + } + + if (body == "" || summary == "" && app_id == null) { + throw new DBusError.INVALID_ARGS ("summary must not be empty"); + } + + var notification = new Notification (app_id, app_icon, summary, body, actions, hints) { priority = urgency }; + if (id == 0) { + id = ++id_counter; + } + if (!settings.get_boolean ("do-not-disturb") || notification.priority == URGENT) { var app_settings = new Settings.with_path ( "io.elementary.notifications.applications", diff --git a/src/Notification.vala b/src/Notification.vala index 66a3e044..7899e787 100644 --- a/src/Notification.vala +++ b/src/Notification.vala @@ -4,9 +4,8 @@ */ public class Notifications.Notification : GLib.Object { - private const string OTHER_APP_ID = "gala-other"; - public DesktopAppInfo? app_info { get; construct; } + public NotificationPriority priority { get; set; default = NORMAL; } public string app_id { get { @@ -24,26 +23,46 @@ public class Notifications.Notification : GLib.Object { } } - public NotificationPriority priority { get; set; default = NORMAL; } - public HashTable hints { get; construct; } - public string[] actions { get; construct; } - public string app_icon { get; construct; } - public string app_name { get; construct; } - public string body { get; construct set; } - public string summary { get; construct set; } + public string title { + get { + // GLib.Notifications only requires the title, when that's the case, we use it as the body. + // So, use the applications's display name as title if we have one. + if (_title == null && app_info != null) { + return app_info.get_display_name (); + } + + return _title ?? ""; + } + + construct set { + _title = (value == null || value == "") ? null : fix_markup (value); + } + } + + public string body { + get { + return _body ?? ""; + } + + construct set { + _body = (value == null || value == "") ? null : fix_markup (sanitize_body (value)); + } + } public GLib.Icon? primary_icon { get; set; default = null; } public GLib.Icon? badge_icon { get; set; default = null; } public MaskedImage? image { get; set; default = null; } - private string? _app_id = null; + public HashTable hints { get; construct; } + public string[] actions { get; construct; } + public string app_icon { get; construct; } - private static Regex entity_regex; - private static Regex tag_regex; + private string? _app_id; + private string? _title; + private string? _body; public Notification ( string? app_id, - string app_name, string app_icon, string summary, string body, @@ -52,24 +71,14 @@ public class Notifications.Notification : GLib.Object { ) { Object ( app_info: app_id != null ? new DesktopAppInfo (app_id + ".desktop") : null, - app_name: app_name, app_icon: app_icon, - summary: summary, + title: summary, body: body, actions: actions, hints: hints ); } - static construct { - try { - entity_regex = new Regex ("&(?!amp;|quot;|apos;|lt;|gt;|nbsp;|#39)"); - tag_regex = new Regex ("<(?!\\/?[biu]>)"); - } catch (Error e) { - warning ("Invalid regex: %s", e.message); - } - } - construct { // Always "" if sent by GLib.Notification if (app_icon == "" && app_info != null) { @@ -115,31 +124,15 @@ public class Notifications.Notification : GLib.Object { if (image == null && primary_icon == null) { primary_icon = new ThemedIcon ("dialog-information"); } - - // Always "" if sent by GLib.Notification - if (app_name == "" && app_info != null) { - app_name = app_info.get_display_name (); - } - - /*Only summary is required by GLib.Notification, so try to set a title when body is empty*/ - if (body == "") { - body = fix_markup (summary); - summary = app_name; - } else { - body = fix_markup (body); - summary = fix_markup (summary); - } } - /** - * Copied from gnome-shell, fixes the mess of markup that is sent to us - */ - private string fix_markup (string markup) { + // Copied from gnome-shell, fixes the mess of markup that is sent to us + private static string fix_markup (string markup) { var text = markup; try { - text = entity_regex.replace (markup, markup.length, 0, "&"); - text = tag_regex.replace (text, text.length, 0, "<"); + text = /&(?!amp;|quot;|apos;|lt;|gt;|nbsp;|#39)/.replace (markup, markup.length, 0, "&"); //vala-lint=space-before-paren + text = /<(?!\/?[biu]>)/.replace (text, text.length, 0, "<"); //vala-lint=space-before-paren } catch (Error e) { warning ("Invalid regex: %s", e.message); } @@ -147,6 +140,25 @@ public class Notifications.Notification : GLib.Object { return text; } + // remove sequences of whitespaces and newlines. + private static string sanitize_body (string body) { + var lines = body.delimit ("\f\r\n", '\n')._delimit ("\t\v", ' ').split ("\n"); + foreach (unowned var line in lines) { + line._strip (); + } + + var sanitized = string.joinv ("\n", lines); + while (" " in sanitized) { + sanitized = sanitized.replace (" ", " "); + } + + while ("\n\n" in sanitized) { + sanitized = sanitized.replace ("\n\n", "\n"); + } + + return sanitized; + } + private Gdk.Pixbuf? image_data_variant_to_pixbuf (Variant img) { if (img.get_type_string () != "(iiibiiay)") { warning ("Invalid type string: %s", img.get_type_string ()); From d8e6352a5a885e39a4666e4807f37bbc90f05295 Mon Sep 17 00:00:00 2001 From: Gustavo Marques Date: Tue, 11 Jul 2023 16:07:24 -0300 Subject: [PATCH 3/3] handle image hints in the server the notification have now explicit image and badge properties the server set them to the right icons, and masking happens in the bubble. MaskedImage was updated to handle any GLib.LoadableIcon. Signed-off-by: Gustavo Marques --- src/Bubble.vala | 37 +++++------- src/DBus.vala | 74 ++++++++++++++++++++++- src/Notification.vala | 113 ++++++++--------------------------- src/Widgets/MaskedImage.vala | 62 +++++++++---------- 4 files changed, 142 insertions(+), 144 deletions(-) diff --git a/src/Bubble.vala b/src/Bubble.vala index 0429a86e..2ed47eac 100644 --- a/src/Bubble.vala +++ b/src/Bubble.vala @@ -81,31 +81,26 @@ public class Notifications.Bubble : AbstractBubble { } construct { - var app_image = new Gtk.Image () { - gicon = notification.primary_icon + var image_overlay = new Gtk.Overlay () { + valign = START }; - var image_overlay = new Gtk.Overlay (); - image_overlay.valign = Gtk.Align.START; + if (notification.image is LoadableIcon) { + image_overlay.child = new MaskedImage ((LoadableIcon) notification.image); + } else { + image_overlay.child = new Gtk.Image.from_gicon (notification.image, DIALOG) { + pixel_size = 48 + }; + } - if (notification.image != null) { - app_image.pixel_size = 24; - app_image.halign = app_image.valign = Gtk.Align.END; + if (notification.badge != null) { + var badge_image = new Gtk.Image.from_gicon (notification.badge, LARGE_TOOLBAR) { + pixel_size = 24, + halign = END, + valign = END + }; - image_overlay.add (notification.image); - image_overlay.add_overlay (app_image); - } else { - app_image.pixel_size = 48; - image_overlay.add (app_image); - - if (notification.badge_icon != null) { - var badge_image = new Gtk.Image.from_gicon (notification.badge_icon, Gtk.IconSize.LARGE_TOOLBAR) { - halign = Gtk.Align.END, - valign = Gtk.Align.END, - pixel_size = 24 - }; - image_overlay.add_overlay (badge_image); - } + image_overlay.add_overlay (badge_image); } var title_label = new Gtk.Label (notification.title) { diff --git a/src/DBus.vala b/src/DBus.vala index 7b2c8c2d..999c7bc3 100644 --- a/src/DBus.vala +++ b/src/DBus.vala @@ -24,6 +24,8 @@ public class Notifications.Server : Object { private Gee.HashMap bubbles; + private static VariantType variant_type_pixbuf = new VariantType ("(iiibiiay)"); + construct { settings = new GLib.Settings ("io.elementary.notifications"); bubbles = new Gee.HashMap (); @@ -111,7 +113,8 @@ public class Notifications.Server : Object { throw new DBusError.INVALID_ARGS ("summary must not be empty"); } - var notification = new Notification (app_id, app_icon, summary, body, actions, hints) { + var notification = new Notification (app_id, summary, body, actions) { + image = parse_image_string (app_icon), priority = urgency }; @@ -119,6 +122,16 @@ public class Notifications.Server : Object { id = ++id_counter; } + var image = search_image (hints); + if (image != null) { + if (image is LoadableIcon) { + notification.badge = notification.image; + notification.image = image; + } else { + notification.badge = image; + } + } + if (!settings.get_boolean ("do-not-disturb") || notification.priority == URGENT) { var app_settings = new Settings.with_path ( "io.elementary.notifications.applications", @@ -221,6 +234,65 @@ public class Notifications.Server : Object { } } + // search for a image hint, in priority order. + // See: https://specifications.freedesktop.org/notification-spec/notification-spec-latest.html#icons-and-images + private static Icon? search_image (HashTable hints) { + const string[] IMAGE_HINTS = { "image-data", "image_data", "image-path", "image_path", "icon_data" }; + Icon? image = null; + + foreach (unowned var hint in IMAGE_HINTS) { + if (!hints.contains (hint)) { + continue; + } + + if (hints[hint].is_of_type (VariantType.STRING)) { + image = parse_image_string (hints[hint].get_string ()); + } else if (hints[hint].is_of_type (variant_type_pixbuf)) { + image = parse_image_pixbuf (hints[hint]); + } + + if (image != null) { + break; + } + + warning ("wrong type for hint '%s': %s. ignoring", hint, hints[hint].get_type_string ()); + } + + return image; + } + + private static Gdk.Pixbuf? parse_image_pixbuf (Variant variant) + requires (variant.is_of_type (variant_type_pixbuf)) { + int width, height, rowstride, bps; + bool has_alpha; + Bytes data; + + variant.get ("(iiibiiay)", out width, out height, out rowstride, out has_alpha, out bps, null, null); + data = variant.get_child_value (6).get_data_as_bytes (); + + return new Gdk.Pixbuf.from_bytes (data, RGB, has_alpha, bps, width, height, rowstride); + } + + private static Icon? parse_image_string (string image) { + if (Gtk.IconTheme.get_default ().has_icon (image)) { + return new ThemedIcon (image); + } + + File? file = null; + + if (image.has_prefix ("file:")) { + file = File.new_for_uri (image); + } else if (image.has_prefix ("/")) { + file = File.new_for_path (image); + } + + if (file != null && file.query_exists ()) { + return new FileIcon (file); + } + + return null; + } + private static unowned string category_to_sound_name (string category) { unowned string sound; diff --git a/src/Notification.vala b/src/Notification.vala index 7899e787..4ce0b67f 100644 --- a/src/Notification.vala +++ b/src/Notification.vala @@ -49,83 +49,41 @@ public class Notifications.Notification : GLib.Object { } } - public GLib.Icon? primary_icon { get; set; default = null; } - public GLib.Icon? badge_icon { get; set; default = null; } - public MaskedImage? image { get; set; default = null; } + public Icon image { + get { + if (_image == null) { + return app_info != null ? app_info.get_icon () : fallback_icon; + } + + return _image; + } + + set { + _image = value; + } + } + + public Icon? badge { get; set; } - public HashTable hints { get; construct; } public string[] actions { get; construct; } - public string app_icon { get; construct; } - - private string? _app_id; - private string? _title; - private string? _body; - - public Notification ( - string? app_id, - string app_icon, - string summary, - string body, - string[] actions, - HashTable hints - ) { + + private Icon _image; + private string _app_id; + private string _title; + private string _body; + + // used when no icon was provided + private static Icon fallback_icon = new ThemedIcon ("dialog-information"); + + public Notification (string? app_id, string summary, string body, string[] actions) { Object ( app_info: app_id != null ? new DesktopAppInfo (app_id + ".desktop") : null, - app_icon: app_icon, title: summary, body: body, - actions: actions, - hints: hints + actions: actions ); } - construct { - // Always "" if sent by GLib.Notification - if (app_icon == "" && app_info != null) { - primary_icon = app_info.get_icon (); - } else if (app_icon.contains ("/")) { - var file = File.new_for_uri (app_icon); - if (file.query_exists ()) { - primary_icon = new FileIcon (file); - } - } else { - // Icon name set directly, such as by Notify.Notification - primary_icon = new ThemedIcon (app_icon); - } - - unowned Variant? variant = null; - - // GLib.Notification.set_icon () - if ((variant = hints.lookup ("image-path")) != null || (variant = hints.lookup ("image_path")) != null) { - var image_path = variant.get_string (); - - // GLib.Notification also sends icon names via this hint - if (Gtk.IconTheme.get_default ().has_icon (image_path) && image_path != app_icon) { - badge_icon = new ThemedIcon (image_path); - } else if (image_path.has_prefix ("/") || image_path.has_prefix ("file://")) { - try { - var pixbuf = new Gdk.Pixbuf.from_file (image_path); - image = new Notifications.MaskedImage (pixbuf); - } catch (Error e) { - critical ("Unable to mask image: %s", e.message); - } - } - } - - // Raw image data sent within a variant - if ((variant = hints.lookup ("image-data")) != null || (variant = hints.lookup ("image_data")) != null || (variant = hints.lookup ("icon_data")) != null) { - var pixbuf = image_data_variant_to_pixbuf (variant); - if (pixbuf != null) { - image = new Notifications.MaskedImage (pixbuf); - } - } - - // Display a generic notification icon if there is no notification image - if (image == null && primary_icon == null) { - primary_icon = new ThemedIcon ("dialog-information"); - } - } - // Copied from gnome-shell, fixes the mess of markup that is sent to us private static string fix_markup (string markup) { var text = markup; @@ -158,23 +116,4 @@ public class Notifications.Notification : GLib.Object { return sanitized; } - - private Gdk.Pixbuf? image_data_variant_to_pixbuf (Variant img) { - if (img.get_type_string () != "(iiibiiay)") { - warning ("Invalid type string: %s", img.get_type_string ()); - return null; - } - int width = img.get_child_value (0).get_int32 (); - int height = img.get_child_value (1).get_int32 (); - int rowstride = img.get_child_value (2).get_int32 (); - bool has_alpha = img.get_child_value (3).get_boolean (); - int bits_per_sample = img.get_child_value (4).get_int32 (); - unowned uint8[] raw = (uint8[]) img.get_child_value (6).get_data (); - - // Build the pixbuf from the unowned buffer, and copy it to maintain our own instance. - Gdk.Pixbuf pixbuf = new Gdk.Pixbuf.with_unowned_data (raw, Gdk.Colorspace.RGB, - has_alpha, bits_per_sample, width, height, rowstride, null); - return pixbuf.copy (); - } - } diff --git a/src/Widgets/MaskedImage.vala b/src/Widgets/MaskedImage.vala index 336e4119..6d6b6221 100644 --- a/src/Widgets/MaskedImage.vala +++ b/src/Widgets/MaskedImage.vala @@ -1,56 +1,48 @@ /* -* Copyright 2019 elementary, Inc. (https://elementary.io) -* -* This program is free software; you can redistribute it and/or -* modify it under the terms of the GNU General Public -* License as published by the Free Software Foundation; either -* version 3 of the License, or (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* General Public License for more details. -* -* You should have received a copy of the GNU General Public -* License along with this program; if not, write to the -* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, -* Boston, MA 02110-1301 USA -* -*/ + * Copyright 2019 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ public class Notifications.MaskedImage : Gtk.Overlay { - private const int ICON_SIZE = 48; + public LoadableIcon gicon { get; construct; } - public Gdk.Pixbuf pixbuf { get; construct; } + private const int ICON_SIZE = 48; - public MaskedImage (Gdk.Pixbuf pixbuf) { - Object (pixbuf: pixbuf); + public MaskedImage (LoadableIcon gicon) { + Object (gicon: gicon); } construct { - var mask = new Gtk.Image.from_resource ("/io/elementary/notifications/image-mask.svg"); - mask.pixel_size = ICON_SIZE; - - var scale = get_style_context ().get_scale (); + child = new Gtk.Image.from_gicon (mask_icon (gicon, get_style_context ().get_scale ()), DIALOG) { + pixel_size = ICON_SIZE + }; - var image = new Gtk.Image (); - image.gicon = mask_pixbuf (pixbuf, scale); - image.pixel_size = ICON_SIZE; - - add (image); + var mask = new Gtk.Image.from_resource ("/io/elementary/notifications/image-mask.svg") { + pixel_size = ICON_SIZE + }; add_overlay (mask); } - private static Gdk.Pixbuf? mask_pixbuf (Gdk.Pixbuf pixbuf, int scale) { - var size = ICON_SIZE * scale; + private static Icon? mask_icon (LoadableIcon icon, int scale) { var mask_offset = 4 * scale; var mask_size_offset = mask_offset * 2; + var size = ICON_SIZE * scale - mask_size_offset; + Gdk.Pixbuf input; + + if (icon is Gdk.Pixbuf) { + input = ((Gdk.Pixbuf) icon).scale_simple (size, size, BILINEAR); + } else try { + input = new Gdk.Pixbuf.from_stream_at_scale (icon.load (ICON_SIZE, null), size, size, false); + } catch (Error e) { + warning ("failed to scale icon: %s", e.message); + return new ThemedIcon ("image-missing"); + } + var mask_size = ICON_SIZE * scale; var offset_x = mask_offset; var offset_y = mask_offset + scale; - size = size - mask_size_offset; - var input = pixbuf.scale_simple (size, size, Gdk.InterpType.BILINEAR); var surface = new Cairo.ImageSurface (Cairo.Format.ARGB32, mask_size, mask_size); var cr = new Cairo.Context (surface);