mirror of
https://github.com/elementary/gala.git
synced 2024-12-23 17:23:01 +03:00
Remove old Notify plugin (#814)
* Remove old Notify plugin * missed a spot
This commit is contained in:
parent
eb3611221a
commit
41d7ab1383
@ -70,11 +70,6 @@
|
||||
<summary>Enable edge tiling when dropping windows on screen edges</summary>
|
||||
<description>If enabled, dropping windows on vertical screen edges maximizes them vertically and resizes them horizontally to cover half of the available area. Dropping windows on the top screen edge maximizes them completely.</description>
|
||||
</key>
|
||||
<key type="b" name="use-new-notifications">
|
||||
<default>false</default>
|
||||
<summary>If new notifications should be used</summary>
|
||||
<description>If new notifications UI should be used, requires io.elemenetary.notifications to be present and running</description>
|
||||
</key>
|
||||
<key type="s" name="panel-main-menu-action">
|
||||
<default>''</default>
|
||||
<summary>Panel main menu action</summary>
|
||||
@ -264,30 +259,7 @@
|
||||
<description>See normal-focused.</description>
|
||||
</key>
|
||||
</schema>
|
||||
|
||||
<schema path="/org/pantheon/desktop/gala/notifications/" id="org.pantheon.desktop.gala.notifications">
|
||||
<key type="b" name="do-not-disturb">
|
||||
<default>false</default>
|
||||
<summary>Disable all notifications</summary>
|
||||
</key>
|
||||
<child name="applications" schema="org.pantheon.desktop.gala.notifications.application" />
|
||||
</schema>
|
||||
|
||||
<schema id="org.pantheon.desktop.gala.notifications.application">
|
||||
<key type="b" name="bubbles">
|
||||
<default>true</default>
|
||||
<summary>Enable bubbles</summary>
|
||||
</key>
|
||||
<key type="b" name="sounds">
|
||||
<default>true</default>
|
||||
<summary>Enable sounds</summary>
|
||||
</key>
|
||||
<key type="b" name="remember">
|
||||
<default>true</default>
|
||||
<summary>Show missed notifications in notification center</summary>
|
||||
</key>
|
||||
</schema>
|
||||
|
||||
<schema path="/org/pantheon/desktop/gala/mask-corners/" id="org.pantheon.desktop.gala.mask-corners">
|
||||
<key type="b" name="enable">
|
||||
<default>true</default>
|
||||
|
@ -179,7 +179,6 @@ subdir('lib')
|
||||
subdir('src')
|
||||
subdir('daemon')
|
||||
subdir('plugins/maskcorners')
|
||||
subdir('plugins/notify')
|
||||
subdir('plugins/pip')
|
||||
subdir('plugins/template')
|
||||
subdir('plugins/zoom')
|
||||
|
@ -1,126 +0,0 @@
|
||||
//
|
||||
// Copyright (C) 2014 Tom Beckmann
|
||||
//
|
||||
// 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, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using Clutter;
|
||||
using Meta;
|
||||
|
||||
namespace Gala.Plugins.Notify {
|
||||
public class ConfirmationNotification : Notification {
|
||||
const int DURATION = 2000;
|
||||
const int PROGRESS_HEIGHT = 6;
|
||||
|
||||
public bool has_progress { get; private set; }
|
||||
|
||||
int _progress;
|
||||
public int progress {
|
||||
get {
|
||||
return _progress;
|
||||
}
|
||||
private set {
|
||||
_progress = value;
|
||||
content.invalidate ();
|
||||
}
|
||||
}
|
||||
|
||||
public string confirmation_type { get; private set; }
|
||||
|
||||
int old_progress;
|
||||
|
||||
public ConfirmationNotification (uint32 id, Gdk.Pixbuf? icon, bool icon_only,
|
||||
int progress, string confirmation_type) {
|
||||
Object (id: id, icon: icon, urgency: NotificationUrgency.LOW, expire_timeout: DURATION);
|
||||
|
||||
this.icon_only = icon_only;
|
||||
this.has_progress = progress > -1;
|
||||
this.progress = progress;
|
||||
this.confirmation_type = confirmation_type;
|
||||
}
|
||||
|
||||
public override void update_allocation (out float content_height, AllocationFlags flags) {
|
||||
content_height = ICON_SIZE * style_context.get_scale ();
|
||||
}
|
||||
|
||||
public override void draw_content (Cairo.Context cr) {
|
||||
if (!has_progress)
|
||||
return;
|
||||
|
||||
var scale = style_context.get_scale ();
|
||||
var scaled_margin = MARGIN * scale;
|
||||
var scaled_width = WIDTH * scale;
|
||||
var x = (MARGIN + PADDING + ICON_SIZE + SPACING) * scale;
|
||||
var y = (MARGIN + PADDING + (ICON_SIZE - PROGRESS_HEIGHT) / 2) * scale;
|
||||
var width = scaled_width - x - scaled_margin;
|
||||
|
||||
if (!transitioning)
|
||||
draw_progress_bar (cr, x, y, width, progress);
|
||||
else {
|
||||
Granite.Drawing.Utilities.cairo_rounded_rectangle (cr, scaled_margin, scaled_margin, scaled_width - scaled_margin * 2, ICON_SIZE * scale + PADDING * 2 * scale, 4 * scale);
|
||||
cr.clip ();
|
||||
|
||||
draw_progress_bar (cr, x, y + animation_slide_y_offset, width, old_progress);
|
||||
draw_progress_bar (cr, x, y + animation_slide_y_offset - animation_slide_height, width, progress);
|
||||
|
||||
cr.reset_clip ();
|
||||
}
|
||||
}
|
||||
|
||||
void draw_progress_bar (Cairo.Context cr, int x, float y, int width, int progress) {
|
||||
var fraction = (int) Math.floor (progress.clamp (0, 100) / 100.0 * width);
|
||||
|
||||
var scale = style_context.get_scale ();
|
||||
var scaled_progress_height = PROGRESS_HEIGHT * scale;
|
||||
Granite.Drawing.Utilities.cairo_rounded_rectangle (cr, x, y, width,
|
||||
scaled_progress_height, scaled_progress_height / 2);
|
||||
cr.set_source_rgb (0.8, 0.8, 0.8);
|
||||
cr.fill ();
|
||||
|
||||
if (progress > 0) {
|
||||
Granite.Drawing.Utilities.cairo_rounded_rectangle (cr, x, y, fraction,
|
||||
scaled_progress_height, scaled_progress_height / 2);
|
||||
cr.set_source_rgb (0.3, 0.3, 0.3);
|
||||
cr.fill ();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void update_slide_animation () {
|
||||
// just trigger the draw function, which will move our progress bar down
|
||||
content.invalidate ();
|
||||
}
|
||||
|
||||
public void update (Gdk.Pixbuf? icon, int progress, string confirmation_type,
|
||||
bool icon_only) {
|
||||
if (this.confirmation_type != confirmation_type) {
|
||||
this.confirmation_type = confirmation_type;
|
||||
|
||||
old_progress = this.progress;
|
||||
|
||||
var scale = style_context.get_scale ();
|
||||
play_update_transition ((ICON_SIZE + PADDING * 2) * scale);
|
||||
}
|
||||
|
||||
if (this.icon_only != icon_only) {
|
||||
this.icon_only = icon_only;
|
||||
queue_relayout ();
|
||||
}
|
||||
|
||||
this.has_progress = progress > -1;
|
||||
this.progress = progress;
|
||||
|
||||
update_base (icon, DURATION);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,146 +0,0 @@
|
||||
//
|
||||
// Copyright (C) 2014 Tom Beckmann
|
||||
//
|
||||
// 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, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using Clutter;
|
||||
using Meta;
|
||||
|
||||
namespace Gala.Plugins.Notify {
|
||||
public class Main : Gala.Plugin {
|
||||
private GLib.Settings behavior_settings;
|
||||
Gala.WindowManager? wm = null;
|
||||
|
||||
NotifyServer server;
|
||||
NotificationStack stack;
|
||||
|
||||
uint owner_id = 0U;
|
||||
|
||||
public override void initialize (Gala.WindowManager wm) {
|
||||
behavior_settings = new GLib.Settings ("org.pantheon.desktop.gala.behavior");
|
||||
|
||||
this.wm = wm;
|
||||
#if HAS_MUTTER330
|
||||
unowned Meta.Display display = wm.get_display ();
|
||||
#else
|
||||
var screen = wm.get_screen ();
|
||||
#endif
|
||||
|
||||
#if HAS_MUTTER330
|
||||
stack = new NotificationStack (display);
|
||||
#else
|
||||
stack = new NotificationStack (screen);
|
||||
#endif
|
||||
stack.animations_changed.connect ((running) => {
|
||||
freeze_track = running;
|
||||
});
|
||||
|
||||
#if HAS_MUTTER330
|
||||
Meta.MonitorManager.@get ().monitors_changed_internal.connect (update_position);
|
||||
display.workareas_changed.connect (update_position);
|
||||
#else
|
||||
screen.monitors_changed.connect (update_position);
|
||||
screen.workareas_changed.connect (update_position);
|
||||
#endif
|
||||
|
||||
server = new NotifyServer (stack);
|
||||
|
||||
if (!behavior_settings.get_boolean ("use-new-notifications")) {
|
||||
enable ();
|
||||
}
|
||||
|
||||
behavior_settings.changed["use-new-notifications"].connect (() => {
|
||||
if (!behavior_settings.get_boolean ("use-new-notifications")) {
|
||||
enable ();
|
||||
} else {
|
||||
disable ();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void enable ()
|
||||
{
|
||||
if (owner_id != 0U) {
|
||||
return;
|
||||
}
|
||||
|
||||
wm.ui_group.add_child (stack);
|
||||
track_actor (stack);
|
||||
|
||||
update_position ();
|
||||
|
||||
owner_id = Bus.own_name (BusType.SESSION, "org.freedesktop.Notifications", BusNameOwnerFlags.REPLACE,
|
||||
(connection) => {
|
||||
try {
|
||||
connection.register_object ("/org/freedesktop/Notifications", server);
|
||||
} catch (Error e) {
|
||||
warning ("Registring notification server failed: %s", e.message);
|
||||
destroy ();
|
||||
}
|
||||
},
|
||||
() => {},
|
||||
(con, name) => {
|
||||
warning ("Could not aquire bus %s", name);
|
||||
destroy ();
|
||||
});
|
||||
}
|
||||
|
||||
void disable () {
|
||||
if (owner_id == 0U) {
|
||||
return;
|
||||
}
|
||||
|
||||
Bus.unown_name (owner_id);
|
||||
|
||||
untrack_actor (stack);
|
||||
wm.ui_group.remove_child (stack);
|
||||
|
||||
owner_id = 0U;
|
||||
}
|
||||
|
||||
void update_position () {
|
||||
#if HAS_MUTTER330
|
||||
unowned Meta.Display display = wm.get_display ();
|
||||
var primary = display.get_primary_monitor ();
|
||||
var area = display.get_workspace_manager ().get_active_workspace ().get_work_area_for_monitor (primary);
|
||||
#else
|
||||
var screen = wm.get_screen ();
|
||||
var primary = screen.get_primary_monitor ();
|
||||
var area = screen.get_active_workspace ().get_work_area_for_monitor (primary);
|
||||
#endif
|
||||
|
||||
stack.x = area.x + area.width - stack.width;
|
||||
stack.y = area.y;
|
||||
}
|
||||
|
||||
public override void destroy () {
|
||||
if (wm == null)
|
||||
return;
|
||||
|
||||
untrack_actor (stack);
|
||||
stack.destroy ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Gala.PluginInfo register_plugin () {
|
||||
return Gala.PluginInfo () {
|
||||
name = "Notify",
|
||||
author = "Gala Developers",
|
||||
plugin_type = typeof (Gala.Plugins.Notify.Main),
|
||||
provides = Gala.PluginFunction.ADDITION,
|
||||
load_priority = Gala.LoadPriority.IMMEDIATE
|
||||
};
|
||||
}
|
@ -1,352 +0,0 @@
|
||||
//
|
||||
// Copyright (C) 2014 Tom Beckmann
|
||||
//
|
||||
// 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, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using Clutter;
|
||||
using Meta;
|
||||
|
||||
namespace Gala.Plugins.Notify {
|
||||
/**
|
||||
* Wrapper class only containing the summary and body label. Allows us to
|
||||
* instantiate the content very easily for when we need to slide the old
|
||||
* and new content down.
|
||||
*/
|
||||
class NormalNotificationContent : Actor {
|
||||
static Regex entity_regex;
|
||||
static Regex tag_regex;
|
||||
|
||||
static construct {
|
||||
try {
|
||||
entity_regex = new Regex ("&(?!amp;|quot;|apos;|lt;|gt;)");
|
||||
tag_regex = new Regex ("<(?!\\/?[biu]>)");
|
||||
} catch (Error e) {}
|
||||
}
|
||||
|
||||
const int LABEL_SPACING = 2;
|
||||
|
||||
Text summary_label;
|
||||
Text body_label;
|
||||
|
||||
construct {
|
||||
summary_label = new Text.with_text (null, "");
|
||||
summary_label.line_wrap = true;
|
||||
summary_label.use_markup = true;
|
||||
summary_label.line_wrap_mode = Pango.WrapMode.WORD_CHAR;
|
||||
|
||||
body_label = new Text.with_text (null, "");
|
||||
body_label.line_wrap = true;
|
||||
body_label.use_markup = true;
|
||||
body_label.line_wrap_mode = Pango.WrapMode.WORD_CHAR;
|
||||
|
||||
var style_path = new Gtk.WidgetPath ();
|
||||
style_path.append_type (typeof (Gtk.Window));
|
||||
style_path.append_type (typeof (Gtk.EventBox));
|
||||
style_path.iter_add_class (1, "gala-notification");
|
||||
style_path.append_type (typeof (Gtk.Label));
|
||||
|
||||
var label_style_context = new Gtk.StyleContext ();
|
||||
label_style_context.add_provider (Gala.Utils.get_gala_css (), Gtk.STYLE_PROVIDER_PRIORITY_FALLBACK);
|
||||
label_style_context.set_path (style_path);
|
||||
|
||||
Gdk.RGBA color;
|
||||
|
||||
label_style_context.save ();
|
||||
label_style_context.add_class ("title");
|
||||
color = label_style_context.get_color (Gtk.StateFlags.NORMAL);
|
||||
summary_label.color = {
|
||||
(uint8) (color.red * 255),
|
||||
(uint8) (color.green * 255),
|
||||
(uint8) (color.blue * 255),
|
||||
(uint8) (color.alpha * 255)
|
||||
};
|
||||
label_style_context.restore ();
|
||||
|
||||
label_style_context.save ();
|
||||
label_style_context.add_class ("label");
|
||||
color = label_style_context.get_color (Gtk.StateFlags.NORMAL);
|
||||
body_label.color = {
|
||||
(uint8) (color.red * 255),
|
||||
(uint8) (color.green * 255),
|
||||
(uint8) (color.blue * 255),
|
||||
(uint8) (color.alpha * 255)
|
||||
};
|
||||
label_style_context.restore ();
|
||||
|
||||
add_child (summary_label);
|
||||
add_child (body_label);
|
||||
}
|
||||
|
||||
public void set_values (string summary, string body) {
|
||||
summary_label.set_markup ("<b>%s</b>".printf (fix_markup (summary)));
|
||||
body_label.set_markup (fix_markup (body));
|
||||
}
|
||||
|
||||
public override void get_preferred_height (float for_width, out float min_height, out float nat_height) {
|
||||
var scale = Utils.get_ui_scaling_factor ();
|
||||
float label_height;
|
||||
get_allocation_values (null, null, null, null, out label_height, null, scale);
|
||||
|
||||
min_height = nat_height = label_height;
|
||||
}
|
||||
|
||||
public override void allocate (ActorBox box, AllocationFlags flags) {
|
||||
var scale = Utils.get_ui_scaling_factor ();
|
||||
float label_x, label_width, summary_height, body_height, label_height, label_y;
|
||||
get_allocation_values (out label_x, out label_width, out summary_height,
|
||||
out body_height, out label_height, out label_y, scale);
|
||||
|
||||
var summary_alloc = ActorBox ();
|
||||
summary_alloc.set_origin (label_x, label_y);
|
||||
summary_alloc.set_size (label_width, summary_height);
|
||||
summary_label.allocate (summary_alloc, flags);
|
||||
|
||||
var body_alloc = ActorBox ();
|
||||
body_alloc.set_origin (label_x, label_y + summary_height + LABEL_SPACING * scale);
|
||||
body_alloc.set_size (label_width, body_height);
|
||||
body_label.allocate (body_alloc, flags);
|
||||
|
||||
base.allocate (box, flags);
|
||||
}
|
||||
|
||||
void get_allocation_values (out float label_x, out float label_width, out float summary_height,
|
||||
out float body_height, out float label_height, out float label_y, int scale) {
|
||||
var height = Notification.ICON_SIZE * scale;
|
||||
var margin = Notification.MARGIN * scale;
|
||||
var padding = Notification.PADDING * scale;
|
||||
var spacing = Notification.SPACING * scale;
|
||||
|
||||
label_x = margin + padding + height + spacing;
|
||||
label_width = Notification.WIDTH * scale - label_x - margin - spacing;
|
||||
|
||||
summary_label.get_preferred_height (label_width, null, out summary_height);
|
||||
body_label.get_preferred_height (label_width, null, out body_height);
|
||||
|
||||
label_height = summary_height + LABEL_SPACING * scale + body_height;
|
||||
label_y = margin + padding;
|
||||
// center
|
||||
if (label_height < height) {
|
||||
label_y += (height - (int) label_height) / 2;
|
||||
label_height = height;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copied from gnome-shell, fixes the mess of markup that is sent to us
|
||||
*/
|
||||
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, "<");
|
||||
} catch (Error e) {}
|
||||
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
public class NormalNotification : Notification {
|
||||
public string summary { get; construct set; }
|
||||
public string body { get; construct set; }
|
||||
public uint32 sender_pid { get; construct; }
|
||||
public string[] notification_actions { get; construct set; }
|
||||
#if HAS_MUTTER330
|
||||
public Meta.Display display { get; construct; }
|
||||
#else
|
||||
public Screen screen { get; construct; }
|
||||
#endif
|
||||
|
||||
Actor content_container;
|
||||
NormalNotificationContent notification_content;
|
||||
NormalNotificationContent? old_notification_content = null;
|
||||
|
||||
#if HAS_MUTTER330
|
||||
public NormalNotification (Meta.Display display, uint32 id, string summary, string body, Gdk.Pixbuf? icon,
|
||||
NotificationUrgency urgency, int32 expire_timeout, uint32 pid, string[] actions) {
|
||||
Object (
|
||||
id: id,
|
||||
icon: icon,
|
||||
urgency: urgency,
|
||||
expire_timeout: expire_timeout,
|
||||
display: display,
|
||||
summary: summary,
|
||||
body: body,
|
||||
sender_pid: pid,
|
||||
notification_actions: actions
|
||||
);
|
||||
}
|
||||
#else
|
||||
public NormalNotification (Screen screen, uint32 id, string summary, string body, Gdk.Pixbuf? icon,
|
||||
NotificationUrgency urgency, int32 expire_timeout, uint32 pid, string[] actions) {
|
||||
Object (
|
||||
id: id,
|
||||
icon: icon,
|
||||
urgency: urgency,
|
||||
expire_timeout: expire_timeout,
|
||||
screen: screen,
|
||||
summary: summary,
|
||||
body: body,
|
||||
sender_pid: pid,
|
||||
notification_actions: actions
|
||||
);
|
||||
}
|
||||
#endif
|
||||
|
||||
construct {
|
||||
content_container = new Actor ();
|
||||
|
||||
notification_content = new NormalNotificationContent ();
|
||||
notification_content.set_values (summary, body);
|
||||
|
||||
content_container.add_child (notification_content);
|
||||
insert_child_below (content_container, null);
|
||||
}
|
||||
|
||||
public void update (string summary, string body, Gdk.Pixbuf? icon, int32 expire_timeout,
|
||||
string[] actions) {
|
||||
var visible_change = this.summary != summary || this.body != body;
|
||||
|
||||
if (visible_change) {
|
||||
if (old_notification_content != null)
|
||||
old_notification_content.destroy ();
|
||||
|
||||
old_notification_content = new NormalNotificationContent ();
|
||||
old_notification_content.set_values (this.summary, this.body);
|
||||
|
||||
content_container.add_child (old_notification_content);
|
||||
|
||||
this.summary = summary;
|
||||
this.body = body;
|
||||
notification_content.set_values (summary, body);
|
||||
|
||||
float content_height, old_content_height;
|
||||
notification_content.get_preferred_height (0, null, out content_height);
|
||||
old_notification_content.get_preferred_height (0, null, out old_content_height);
|
||||
|
||||
content_height = float.max (content_height, old_content_height);
|
||||
|
||||
play_update_transition (content_height + PADDING * 2 * style_context.get_scale ());
|
||||
|
||||
get_transition ("switch").completed.connect (() => {
|
||||
if (old_notification_content != null)
|
||||
old_notification_content.destroy ();
|
||||
old_notification_content = null;
|
||||
});
|
||||
}
|
||||
|
||||
notification_actions = actions;
|
||||
update_base (icon, expire_timeout);
|
||||
}
|
||||
|
||||
protected override void update_slide_animation () {
|
||||
if (old_notification_content != null)
|
||||
old_notification_content.y = animation_slide_y_offset;
|
||||
|
||||
notification_content.y = animation_slide_y_offset - animation_slide_height;
|
||||
}
|
||||
|
||||
public override void update_allocation (out float content_height, AllocationFlags flags) {
|
||||
var box = ActorBox ();
|
||||
box.set_origin (0, 0);
|
||||
box.set_size (width, height);
|
||||
|
||||
content_container.allocate (box, flags);
|
||||
|
||||
// the for_width is not needed in our implementation of get_preferred_height as we
|
||||
// assume a constant width
|
||||
notification_content.get_preferred_height (0, null, out content_height);
|
||||
|
||||
var scale = style_context.get_scale ();
|
||||
var scaled_margin = MARGIN * scale;
|
||||
|
||||
content_container.set_clip (scaled_margin, scaled_margin, scaled_margin * 2 + WIDTH * scale, content_height + PADDING * 2 * scale);
|
||||
}
|
||||
|
||||
public override void get_preferred_height (float for_width, out float min_height, out float nat_height) {
|
||||
float content_height;
|
||||
notification_content.get_preferred_height (for_width, null, out content_height);
|
||||
|
||||
min_height = nat_height = content_height + (MARGIN + PADDING) * 2 * style_context.get_scale ();
|
||||
}
|
||||
|
||||
public override void activate () {
|
||||
// we currently only support the default action, which can be triggered by clicking
|
||||
// on the notification according to spec
|
||||
for (var i = 0; i < notification_actions.length; i += 2) {
|
||||
if (notification_actions[i] == "default") {
|
||||
action_invoked (id, "default");
|
||||
dismiss ();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// if no default action has been set, we fallback to trying to find a window for the
|
||||
// notification's sender process
|
||||
unowned Meta.Window? window = get_window ();
|
||||
if (window != null) {
|
||||
unowned Meta.Workspace workspace = window.get_workspace ();
|
||||
#if HAS_MUTTER330
|
||||
var time = display.get_current_time ();
|
||||
|
||||
if (workspace != display.get_workspace_manager ().get_active_workspace ())
|
||||
workspace.activate_with_focus (window, time);
|
||||
else
|
||||
window.activate (time);
|
||||
#else
|
||||
var time = screen.get_display ().get_current_time ();
|
||||
|
||||
if (workspace != screen.get_active_workspace ())
|
||||
workspace.activate_with_focus (window, time);
|
||||
else
|
||||
window.activate (time);
|
||||
#endif
|
||||
|
||||
dismiss ();
|
||||
}
|
||||
}
|
||||
|
||||
unowned Meta.Window? get_window () {
|
||||
if (sender_pid == 0)
|
||||
return null;
|
||||
|
||||
#if HAS_MUTTER330
|
||||
unowned GLib.List<Meta.WindowActor> actors = display.get_window_actors ();
|
||||
#else
|
||||
unowned GLib.List<Meta.WindowActor> actors = screen.get_window_actors ();
|
||||
#endif
|
||||
foreach (unowned Meta.WindowActor actor in actors) {
|
||||
if (actor.is_destroyed ())
|
||||
continue;
|
||||
|
||||
unowned Meta.Window window = actor.get_meta_window ();
|
||||
|
||||
// the windows are sorted by stacking order when returned
|
||||
// from meta_get_window_actors, so we can just pick the first
|
||||
// one we find and have a pretty good match
|
||||
if (window.get_pid () == sender_pid)
|
||||
return window;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
void dismiss () {
|
||||
closed (id, NotificationClosedReason.DISMISSED);
|
||||
close ();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,412 +0,0 @@
|
||||
//
|
||||
// Copyright (C) 2014 Tom Beckmann
|
||||
//
|
||||
// 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, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using Clutter;
|
||||
using Meta;
|
||||
|
||||
namespace Gala.Plugins.Notify {
|
||||
public abstract class Notification : Actor {
|
||||
public const int WIDTH = 300;
|
||||
public const int ICON_SIZE = 48;
|
||||
public const int MARGIN = 12;
|
||||
|
||||
public const int SPACING = 6;
|
||||
public const int PADDING = 4;
|
||||
|
||||
public signal void action_invoked (uint32 id, string action);
|
||||
public signal void closed (uint32 id, uint32 reason);
|
||||
|
||||
public uint32 id { get; construct; }
|
||||
public Gdk.Pixbuf? icon { get; construct set; }
|
||||
public NotificationUrgency urgency { get; construct; }
|
||||
public int32 expire_timeout { get; construct set; }
|
||||
|
||||
public uint64 relevancy_time { get; private set; }
|
||||
public bool being_destroyed { get; private set; default = false; }
|
||||
|
||||
protected bool icon_only { get; protected set; default = false; }
|
||||
#if HAS_MUTTER336
|
||||
protected Clutter.Actor icon_texture { get; private set; }
|
||||
#else
|
||||
protected Clutter.Texture icon_texture { get; private set; }
|
||||
#endif
|
||||
protected Actor icon_container { get; private set; }
|
||||
|
||||
/**
|
||||
* Whether we're currently sliding content for an update animation
|
||||
*/
|
||||
protected bool transitioning { get; private set; default = false; }
|
||||
|
||||
Clutter.Actor close_button;
|
||||
|
||||
protected Gtk.StyleContext style_context { get; private set; }
|
||||
|
||||
uint remove_timeout = 0;
|
||||
|
||||
// temporary things needed for the slide transition
|
||||
protected float animation_slide_height { get; private set; }
|
||||
#if HAS_MUTTER336
|
||||
Clutter.Actor old_texture;
|
||||
#else
|
||||
Clutter.Texture old_texture;
|
||||
#endif
|
||||
float _animation_slide_y_offset = 0.0f;
|
||||
public float animation_slide_y_offset {
|
||||
get {
|
||||
return _animation_slide_y_offset;
|
||||
}
|
||||
set {
|
||||
_animation_slide_y_offset = value;
|
||||
|
||||
icon_texture.y = -animation_slide_height + _animation_slide_y_offset;
|
||||
old_texture.y = _animation_slide_y_offset;
|
||||
|
||||
update_slide_animation ();
|
||||
}
|
||||
}
|
||||
|
||||
protected Notification (uint32 id, Gdk.Pixbuf? icon, NotificationUrgency urgency,
|
||||
int32 expire_timeout) {
|
||||
Object (
|
||||
id: id,
|
||||
icon: icon,
|
||||
urgency: urgency,
|
||||
expire_timeout: expire_timeout
|
||||
);
|
||||
}
|
||||
|
||||
construct {
|
||||
var scale = Utils.get_ui_scaling_factor ();
|
||||
relevancy_time = new DateTime.now_local ().to_unix ();
|
||||
width = (WIDTH + MARGIN * 2) * scale;
|
||||
reactive = true;
|
||||
set_pivot_point (0.5f, 0.5f);
|
||||
|
||||
#if HAS_MUTTER336
|
||||
icon_texture = new Clutter.Actor ();
|
||||
#else
|
||||
icon_texture = new Clutter.Texture ();
|
||||
#endif
|
||||
icon_texture.set_pivot_point (0.5f, 0.5f);
|
||||
|
||||
icon_container = new Actor ();
|
||||
icon_container.add_child (icon_texture);
|
||||
|
||||
close_button = Utils.create_close_button ();
|
||||
close_button.opacity = 0;
|
||||
close_button.reactive = true;
|
||||
close_button.set_easing_duration (300);
|
||||
|
||||
var close_click = new ClickAction ();
|
||||
close_click.clicked.connect (() => {
|
||||
closed (id, NotificationClosedReason.DISMISSED);
|
||||
close ();
|
||||
});
|
||||
close_button.add_action (close_click);
|
||||
|
||||
add_child (icon_container);
|
||||
add_child (close_button);
|
||||
|
||||
var style_path = new Gtk.WidgetPath ();
|
||||
style_path.append_type (typeof (Gtk.Window));
|
||||
style_path.append_type (typeof (Gtk.EventBox));
|
||||
|
||||
style_context = new Gtk.StyleContext ();
|
||||
style_context.add_provider (Gala.Utils.get_gala_css (), Gtk.STYLE_PROVIDER_PRIORITY_FALLBACK);
|
||||
style_context.add_class ("gala-notification");
|
||||
style_context.set_path (style_path);
|
||||
style_context.set_scale (scale);
|
||||
|
||||
var label_style_path = style_path.copy ();
|
||||
label_style_path.iter_add_class (1, "gala-notification");
|
||||
label_style_path.append_type (typeof (Gtk.Label));
|
||||
|
||||
var canvas = new Canvas ();
|
||||
canvas.draw.connect (draw);
|
||||
content = canvas;
|
||||
|
||||
set_values ();
|
||||
|
||||
var click = new ClickAction ();
|
||||
click.clicked.connect (() => {
|
||||
activate ();
|
||||
});
|
||||
add_action (click);
|
||||
|
||||
open ();
|
||||
}
|
||||
|
||||
public void open () {
|
||||
var entry = new TransitionGroup ();
|
||||
entry.remove_on_complete = true;
|
||||
entry.duration = 400;
|
||||
|
||||
var opacity_transition = new PropertyTransition ("opacity");
|
||||
opacity_transition.set_from_value (0);
|
||||
opacity_transition.set_to_value (255);
|
||||
|
||||
var flip_transition = new KeyframeTransition ("rotation-angle-x");
|
||||
flip_transition.set_from_value (90.0);
|
||||
flip_transition.set_to_value (0.0);
|
||||
flip_transition.set_key_frames ({ 0.6 });
|
||||
flip_transition.set_values ({ -10.0 });
|
||||
|
||||
entry.add_transition (opacity_transition);
|
||||
entry.add_transition (flip_transition);
|
||||
add_transition ("entry", entry);
|
||||
|
||||
switch (urgency) {
|
||||
case NotificationUrgency.LOW:
|
||||
case NotificationUrgency.NORMAL:
|
||||
return;
|
||||
case NotificationUrgency.CRITICAL:
|
||||
var icon_entry = new TransitionGroup ();
|
||||
icon_entry.duration = 1000;
|
||||
icon_entry.remove_on_complete = true;
|
||||
icon_entry.progress_mode = AnimationMode.EASE_IN_OUT_CUBIC;
|
||||
|
||||
double[] keyframes = { 0.2, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0 };
|
||||
GLib.Value[] scale = { 0.0, 1.2, 1.6, 1.6, 1.6, 1.6, 1.2, 1.0 };
|
||||
|
||||
var rotate_transition = new KeyframeTransition ("rotation-angle-z");
|
||||
rotate_transition.set_from_value (30.0);
|
||||
rotate_transition.set_to_value (0.0);
|
||||
rotate_transition.set_key_frames (keyframes);
|
||||
rotate_transition.set_values ({ 30.0, -30.0, 30.0, -20.0, 10.0, -5.0, 2.0, 0.0 });
|
||||
|
||||
var scale_x_transition = new KeyframeTransition ("scale-x");
|
||||
scale_x_transition.set_from_value (0.0);
|
||||
scale_x_transition.set_to_value (1.0);
|
||||
scale_x_transition.set_key_frames (keyframes);
|
||||
scale_x_transition.set_values (scale);
|
||||
|
||||
var scale_y_transition = new KeyframeTransition ("scale-y");
|
||||
scale_y_transition.set_from_value (0.0);
|
||||
scale_y_transition.set_to_value (1.0);
|
||||
scale_y_transition.set_key_frames (keyframes);
|
||||
scale_y_transition.set_values (scale);
|
||||
|
||||
icon_entry.add_transition (rotate_transition);
|
||||
icon_entry.add_transition (scale_x_transition);
|
||||
icon_entry.add_transition (scale_y_transition);
|
||||
|
||||
icon_texture.add_transition ("entry", icon_entry);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public void close () {
|
||||
set_easing_duration (100);
|
||||
|
||||
set_easing_mode (AnimationMode.EASE_IN_QUAD);
|
||||
opacity = 0;
|
||||
|
||||
x = (WIDTH + MARGIN * 2) * style_context.get_scale ();
|
||||
|
||||
being_destroyed = true;
|
||||
var transition = get_transition ("x");
|
||||
if (transition != null)
|
||||
transition.completed.connect (() => destroy ());
|
||||
else
|
||||
destroy ();
|
||||
}
|
||||
|
||||
protected void update_base (Gdk.Pixbuf? icon, int32 expire_timeout) {
|
||||
this.icon = icon;
|
||||
this.expire_timeout = expire_timeout;
|
||||
this.relevancy_time = new DateTime.now_local ().to_unix ();
|
||||
|
||||
set_values ();
|
||||
}
|
||||
|
||||
void set_values () {
|
||||
if (icon != null) {
|
||||
try {
|
||||
#if HAS_MUTTER336
|
||||
var image = new Clutter.Image ();
|
||||
Cogl.PixelFormat pixel_format = (icon.get_has_alpha () ? Cogl.PixelFormat.RGBA_8888 : Cogl.PixelFormat.RGB_888);
|
||||
image.set_data (icon.get_pixels (), pixel_format, icon.width, icon.height, icon.rowstride);
|
||||
icon_texture.set_content (image);
|
||||
#else
|
||||
icon_texture.set_from_rgb_data (icon.get_pixels (), icon.get_has_alpha (),
|
||||
icon.get_width (), icon.get_height (),
|
||||
icon.get_rowstride (), (icon.get_has_alpha () ? 4 : 3), 0);
|
||||
#endif
|
||||
} catch (Error e) {}
|
||||
}
|
||||
|
||||
set_timeout ();
|
||||
}
|
||||
|
||||
void set_timeout () {
|
||||
// crtitical notifications have to be dismissed manually
|
||||
if (expire_timeout <= 0 || urgency == NotificationUrgency.CRITICAL)
|
||||
return;
|
||||
|
||||
clear_timeout ();
|
||||
|
||||
remove_timeout = Timeout.add (expire_timeout, () => {
|
||||
closed (id, NotificationClosedReason.EXPIRED);
|
||||
close ();
|
||||
remove_timeout = 0;
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
void clear_timeout () {
|
||||
if (remove_timeout != 0) {
|
||||
Source.remove (remove_timeout);
|
||||
remove_timeout = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool enter_event (CrossingEvent event) {
|
||||
close_button.opacity = 255;
|
||||
|
||||
clear_timeout ();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool leave_event (CrossingEvent event) {
|
||||
close_button.opacity = 0;
|
||||
|
||||
// TODO consider decreasing the timeout now or calculating the remaining
|
||||
set_timeout ();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual void activate () {}
|
||||
|
||||
public virtual void draw_content (Cairo.Context cr) {}
|
||||
|
||||
public abstract void update_allocation (out float content_height, AllocationFlags flags);
|
||||
|
||||
public override void allocate (ActorBox box, AllocationFlags flags) {
|
||||
var icon_alloc = ActorBox ();
|
||||
|
||||
var scale = style_context.get_scale ();
|
||||
var scaled_width = WIDTH * scale;
|
||||
var scaled_icon_size = ICON_SIZE * scale;
|
||||
var scaled_margin_padding = (MARGIN + PADDING) * scale;
|
||||
icon_alloc.set_origin (icon_only ? (scaled_width - scaled_icon_size) / 2 : scaled_margin_padding, scaled_margin_padding);
|
||||
icon_alloc.set_size (scaled_icon_size, scaled_icon_size);
|
||||
icon_container.allocate (icon_alloc, flags);
|
||||
|
||||
var close_alloc = ActorBox ();
|
||||
close_alloc.set_origin (scaled_margin_padding - close_button.width / 2,
|
||||
scaled_margin_padding - close_button.height / 2);
|
||||
close_alloc.set_size (close_button.width, close_button.height);
|
||||
close_button.allocate (close_alloc, flags);
|
||||
|
||||
float content_height;
|
||||
update_allocation (out content_height, flags);
|
||||
box.set_size (MARGIN * 2 * scale + scaled_width, scaled_margin_padding * 2 + content_height);
|
||||
|
||||
base.allocate (box, flags);
|
||||
|
||||
var canvas = (Canvas) content;
|
||||
var canvas_width = (int) box.get_width ();
|
||||
var canvas_height = (int) box.get_height ();
|
||||
if (canvas.width != canvas_width || canvas.height != canvas_height)
|
||||
canvas.set_size (canvas_width, canvas_height);
|
||||
}
|
||||
|
||||
public override void get_preferred_height (float for_width, out float min_height, out float nat_height) {
|
||||
min_height = nat_height = (ICON_SIZE + (MARGIN + PADDING) * 2) * style_context.get_scale ();
|
||||
}
|
||||
|
||||
protected void play_update_transition (float slide_height) {
|
||||
Transition transition;
|
||||
if ((transition = get_transition ("switch")) != null) {
|
||||
transition.completed ();
|
||||
remove_transition ("switch");
|
||||
}
|
||||
|
||||
animation_slide_height = slide_height;
|
||||
|
||||
var scale = style_context.get_scale ();
|
||||
var scaled_padding = PADDING * scale;
|
||||
var scaled_icon_size = ICON_SIZE * scale;
|
||||
#if HAS_MUTTER336
|
||||
old_texture = new Clutter.Actor ();
|
||||
#else
|
||||
old_texture = new Clutter.Texture ();
|
||||
#endif
|
||||
icon_container.add_child (old_texture);
|
||||
icon_container.set_clip (0, -scaled_padding, scaled_icon_size, scaled_icon_size + scaled_padding * 2);
|
||||
|
||||
if (icon != null) {
|
||||
try {
|
||||
#if HAS_MUTTER336
|
||||
var image = new Clutter.Image ();
|
||||
Cogl.PixelFormat pixel_format = (icon.get_has_alpha () ? Cogl.PixelFormat.ARGB_8888 : Cogl.PixelFormat.RGB_888);
|
||||
image.set_data (icon.get_pixels (), pixel_format, icon.width, icon.height, icon.rowstride);
|
||||
old_texture.set_content (image);
|
||||
#else
|
||||
old_texture.set_from_rgb_data (icon.get_pixels (), icon.get_has_alpha (),
|
||||
icon.get_width (), icon.get_height (),
|
||||
icon.get_rowstride (), (icon.get_has_alpha () ? 4 : 3), 0);
|
||||
#endif
|
||||
} catch (Error e) {}
|
||||
}
|
||||
|
||||
transition = new PropertyTransition ("animation-slide-y-offset");
|
||||
transition.duration = 200;
|
||||
transition.progress_mode = AnimationMode.EASE_IN_OUT_QUAD;
|
||||
transition.set_from_value (0.0f);
|
||||
transition.set_to_value (animation_slide_height);
|
||||
transition.remove_on_complete = true;
|
||||
|
||||
transition.completed.connect (() => {
|
||||
old_texture.destroy ();
|
||||
icon_container.remove_clip ();
|
||||
_animation_slide_y_offset = 0;
|
||||
transitioning = false;
|
||||
});
|
||||
|
||||
add_transition ("switch", transition);
|
||||
transitioning = true;
|
||||
}
|
||||
|
||||
protected virtual void update_slide_animation () {}
|
||||
|
||||
bool draw (Cairo.Context cr) {
|
||||
var canvas = (Canvas) content;
|
||||
|
||||
var scale = style_context.get_scale ();
|
||||
var x = MARGIN;
|
||||
var y = MARGIN;
|
||||
var width = canvas.width / scale - MARGIN * 2;
|
||||
var height = canvas.height / scale - MARGIN * 2;
|
||||
cr.set_operator (Cairo.Operator.CLEAR);
|
||||
cr.paint ();
|
||||
cr.set_operator (Cairo.Operator.OVER);
|
||||
|
||||
cr.save ();
|
||||
cr.scale (scale, scale);
|
||||
style_context.render_background (cr, x, y, width, height);
|
||||
style_context.render_frame (cr, x, y, width, height);
|
||||
cr.restore ();
|
||||
|
||||
draw_content (cr);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,108 +0,0 @@
|
||||
//
|
||||
// Copyright (C) 2014 Tom Beckmann
|
||||
//
|
||||
// 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, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using Clutter;
|
||||
using Meta;
|
||||
|
||||
namespace Gala.Plugins.Notify {
|
||||
public class NotificationStack : Actor {
|
||||
// we need to keep a small offset to the top, because we clip the container to
|
||||
// its allocations and the close button would be off for the first notification
|
||||
const int TOP_OFFSET = 2;
|
||||
const int ADDITIONAL_MARGIN = 12;
|
||||
|
||||
public signal void animations_changed (bool running);
|
||||
|
||||
#if HAS_MUTTER330
|
||||
public Meta.Display display { get; construct; }
|
||||
|
||||
public NotificationStack (Meta.Display display) {
|
||||
Object (display: display);
|
||||
}
|
||||
#else
|
||||
public Screen screen { get; construct; }
|
||||
|
||||
public new float width {
|
||||
get {
|
||||
var scale = Utils.get_ui_scaling_factor ();
|
||||
return (Notification.WIDTH + 2 * Notification.MARGIN + ADDITIONAL_MARGIN) * scale;
|
||||
}
|
||||
}
|
||||
|
||||
public NotificationStack (Screen screen) {
|
||||
Object (screen: screen);
|
||||
}
|
||||
#endif
|
||||
|
||||
construct {
|
||||
clip_to_allocation = true;
|
||||
}
|
||||
|
||||
public void show_notification (Notification notification) {
|
||||
animations_changed (true);
|
||||
var scale = Utils.get_ui_scaling_factor ();
|
||||
|
||||
// raise ourselves when we got something to show
|
||||
get_parent ().set_child_above_sibling (this, null);
|
||||
|
||||
// we have a shoot-over on the start of the close animation, which gets clipped
|
||||
// unless we make our container a bit wider and move the notifications over
|
||||
notification.margin_left = ADDITIONAL_MARGIN * scale;
|
||||
|
||||
notification.notify["being-destroyed"].connect (() => {
|
||||
animations_changed (true);
|
||||
});
|
||||
|
||||
notification.destroy.connect (() => {
|
||||
animations_changed (false);
|
||||
update_positions ();
|
||||
});
|
||||
|
||||
notification.get_transition ("entry").completed.connect (() => {
|
||||
animations_changed (false);
|
||||
});
|
||||
|
||||
float height;
|
||||
notification.get_preferred_height (Notification.WIDTH * scale, out height, null);
|
||||
update_positions (height);
|
||||
|
||||
notification.y = TOP_OFFSET * scale;
|
||||
insert_child_at_index (notification, 0);
|
||||
}
|
||||
|
||||
void update_positions (float add_y = 0.0f) {
|
||||
var scale = Utils.get_ui_scaling_factor ();
|
||||
var y = add_y + TOP_OFFSET * scale;
|
||||
var i = get_n_children ();
|
||||
var delay_step = i > 0 ? 150 / i : 0;
|
||||
foreach (var child in get_children ()) {
|
||||
if (((Notification) child).being_destroyed)
|
||||
continue;
|
||||
|
||||
child.save_easing_state ();
|
||||
child.set_easing_mode (AnimationMode.EASE_OUT_BACK);
|
||||
child.set_easing_duration (200);
|
||||
child.set_easing_delay ((i--) * delay_step);
|
||||
|
||||
child.y = y;
|
||||
child.restore_easing_state ();
|
||||
|
||||
y += child.height;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,623 +0,0 @@
|
||||
//
|
||||
// Copyright (C) 2014 Tom Beckmann
|
||||
//
|
||||
// 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, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using Meta;
|
||||
|
||||
namespace Gala.Plugins.Notify {
|
||||
const string X_CANONICAL_PRIVATE_SYNCHRONOUS = "x-canonical-private-synchronous";
|
||||
const string X_CANONICAL_PRIVATE_ICON_ONLY = "x-canonical-private-icon-only";
|
||||
|
||||
public enum NotificationUrgency {
|
||||
LOW = 0,
|
||||
NORMAL = 1,
|
||||
CRITICAL = 2
|
||||
}
|
||||
|
||||
public enum NotificationClosedReason {
|
||||
EXPIRED = 1,
|
||||
DISMISSED = 2,
|
||||
CLOSE_NOTIFICATION_CALL = 3,
|
||||
UNDEFINED = 4
|
||||
}
|
||||
|
||||
[DBus (name = "org.freedesktop.DBus")]
|
||||
private interface DBus : Object {
|
||||
[DBus (name = "GetConnectionUnixProcessID")]
|
||||
public abstract uint32 get_connection_unix_process_id (string name) throws Error;
|
||||
}
|
||||
|
||||
[DBus (name = "org.freedesktop.Notifications")]
|
||||
public class NotifyServer : Object {
|
||||
const int DEFAULT_TMEOUT = 4000;
|
||||
const string FALLBACK_ICON = "dialog-information";
|
||||
const string FALLBACK_APP_ID = "gala-other";
|
||||
|
||||
static Gdk.RGBA? icon_fg_color = null;
|
||||
static Gdk.Pixbuf? image_mask_pixbuf = null;
|
||||
|
||||
[DBus (visible = false)]
|
||||
public signal void show_notification (Notification notification);
|
||||
|
||||
public signal void notification_closed (uint32 id, uint32 reason);
|
||||
public signal void action_invoked (uint32 id, string action_key);
|
||||
|
||||
[DBus (visible = false)]
|
||||
public NotificationStack stack { get; construct; }
|
||||
|
||||
uint32 id_counter = 0;
|
||||
|
||||
DBus? bus_proxy = null;
|
||||
unowned Canberra.Context? ca_context = null;
|
||||
Gee.HashMap<string, GLib.Settings> app_settings_cache;
|
||||
Gee.HashMap<string, AppInfo> app_info_cache;
|
||||
|
||||
public NotifyServer (NotificationStack stack) {
|
||||
Object (stack: stack);
|
||||
}
|
||||
|
||||
construct {
|
||||
try {
|
||||
bus_proxy = Bus.get_proxy_sync (BusType.SESSION, "org.freedesktop.DBus", "/");
|
||||
} catch (Error e) {
|
||||
warning (e.message);
|
||||
bus_proxy = null;
|
||||
}
|
||||
|
||||
var locale = Intl.setlocale (LocaleCategory.MESSAGES, null);
|
||||
ca_context = CanberraGtk.context_get ();
|
||||
ca_context.change_props (Canberra.PROP_APPLICATION_NAME, "Gala",
|
||||
Canberra.PROP_APPLICATION_ID, "org.pantheon.gala",
|
||||
Canberra.PROP_APPLICATION_NAME, "start-here",
|
||||
Canberra.PROP_APPLICATION_LANGUAGE, locale,
|
||||
null);
|
||||
ca_context.open ();
|
||||
|
||||
app_settings_cache = new Gee.HashMap<string, GLib.Settings> ();
|
||||
app_info_cache = new Gee.HashMap<string, AppInfo> ();
|
||||
}
|
||||
|
||||
public string [] get_capabilities () throws DBusError, IOError {
|
||||
return {
|
||||
"body",
|
||||
"body-markup",
|
||||
"sound",
|
||||
// even though we don't fully support actions, we still want to receive the default
|
||||
// action. Well written applications will check if the actions capability is available
|
||||
// before settings a default action, so we have to specify it here. Also, not displaying
|
||||
// certain actions even though requested is allowed according to spec, so we should be fine
|
||||
"actions",
|
||||
X_CANONICAL_PRIVATE_SYNCHRONOUS,
|
||||
X_CANONICAL_PRIVATE_ICON_ONLY
|
||||
};
|
||||
}
|
||||
|
||||
public void get_server_information (out string name, out string vendor,
|
||||
out string version, out string spec_version) throws DBusError, IOError {
|
||||
name = "pantheon-notify";
|
||||
vendor = "elementaryOS";
|
||||
version = "0.1";
|
||||
spec_version = "1.2";
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of the CloseNotification DBus method
|
||||
*
|
||||
* @param id The id of the notification to be closed.
|
||||
*/
|
||||
public void close_notification (uint32 id) throws DBusError, IOError {
|
||||
foreach (var child in stack.get_children ()) {
|
||||
unowned Notification notification = (Notification) child;
|
||||
if (notification.id != id)
|
||||
continue;
|
||||
|
||||
notification_closed_callback (notification, id,
|
||||
NotificationClosedReason.CLOSE_NOTIFICATION_CALL);
|
||||
notification.close ();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// according to spec, an empty dbus error should be sent if the notification
|
||||
// doesn't exist (anymore)
|
||||
throw new DBusError.FAILED ("");
|
||||
}
|
||||
|
||||
public new uint32 notify (string app_name, uint32 replaces_id, string app_icon, string summary,
|
||||
string body, string[] actions, HashTable<string, Variant> hints, int32 expire_timeout, BusName sender)
|
||||
throws DBusError, IOError {
|
||||
unowned Variant? variant = null;
|
||||
|
||||
AppInfo? app_info = null;
|
||||
|
||||
if ((variant = hints.lookup ("desktop-entry")) != null
|
||||
&& variant.is_of_type (VariantType.STRING)) {
|
||||
string desktop_id = variant.get_string ();
|
||||
if (!desktop_id.has_suffix (".desktop"))
|
||||
desktop_id += ".desktop";
|
||||
|
||||
app_info = new DesktopAppInfo (desktop_id);
|
||||
} else {
|
||||
app_info = get_appinfo_from_app_name (app_name);
|
||||
}
|
||||
|
||||
// Get app icon from .desktop as fallback
|
||||
string icon = app_icon;
|
||||
if (app_icon == "" && app_info != null)
|
||||
icon = app_info.get_icon ().to_string ();
|
||||
|
||||
var id = (replaces_id != 0 ? replaces_id : ++id_counter);
|
||||
var pixbuf = get_pixbuf (app_name, icon, hints);
|
||||
var timeout = (expire_timeout == uint32.MAX ? DEFAULT_TMEOUT : expire_timeout);
|
||||
|
||||
var urgency = NotificationUrgency.NORMAL;
|
||||
if ((variant = hints.lookup ("urgency")) != null)
|
||||
urgency = (NotificationUrgency) variant.get_byte ();
|
||||
|
||||
var icon_only = hints.contains (X_CANONICAL_PRIVATE_ICON_ONLY);
|
||||
var confirmation = hints.contains (X_CANONICAL_PRIVATE_SYNCHRONOUS);
|
||||
var progress = confirmation && hints.contains ("value");
|
||||
|
||||
unowned NotifySettings notify_settings = NotifySettings.get_default ();
|
||||
|
||||
// Default values for confirmations
|
||||
var allow_bubble = true;
|
||||
var allow_sound = true;
|
||||
|
||||
if (!confirmation) {
|
||||
if (notify_settings.do_not_disturb) {
|
||||
allow_bubble = allow_sound = false;
|
||||
} else {
|
||||
string app_id = "";
|
||||
bool has_notifications_key = false;
|
||||
|
||||
if (app_info != null) {
|
||||
app_id = app_info.get_id ().replace (".desktop", "");
|
||||
if (app_info is DesktopAppInfo)
|
||||
has_notifications_key = ((DesktopAppInfo) app_info).get_boolean ("X-GNOME-UsesNotifications");
|
||||
}
|
||||
|
||||
if (!has_notifications_key)
|
||||
app_id = FALLBACK_APP_ID;
|
||||
|
||||
GLib.Settings? app_settings = app_settings_cache.get (app_id);
|
||||
if (app_settings == null) {
|
||||
var schema = SettingsSchemaSource.get_default ().lookup ("org.pantheon.desktop.gala.notifications.application", true);
|
||||
if (schema != null) {
|
||||
app_settings = new GLib.Settings.full (schema, null, "/org/pantheon/desktop/gala/notifications/applications/%s/".printf (app_id));
|
||||
app_settings_cache.set (app_id, app_settings);
|
||||
}
|
||||
}
|
||||
|
||||
if (app_settings != null) {
|
||||
allow_bubble = app_settings.get_boolean ("bubbles");
|
||||
allow_sound = app_settings.get_boolean ("sounds");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if 0 // enable to debug notifications
|
||||
print ("Notification from '%s', replaces: %u\n" +
|
||||
"\tapp icon: '%s'\n\tsummary: '%s'\n\tbody: '%s'\n\tn actions: %u\n\texpire: %i\n\tHints:\n",
|
||||
app_name, replaces_id, app_icon, summary, body, actions.length);
|
||||
hints.@foreach ((key, val) => {
|
||||
print ("\t\t%s => %s\n", key, val.is_of_type (VariantType.STRING) ?
|
||||
val.get_string () : "<" + val.get_type ().dup_string () + ">");
|
||||
});
|
||||
print ("\tActions: ");
|
||||
foreach (var action in actions) {
|
||||
print ("%s, ", action);
|
||||
}
|
||||
print ("\n");
|
||||
#endif
|
||||
|
||||
uint32 pid = 0;
|
||||
try {
|
||||
pid = bus_proxy.get_connection_unix_process_id (sender);
|
||||
} catch (Error e) { warning (e.message); }
|
||||
|
||||
if (allow_sound)
|
||||
handle_sounds (hints);
|
||||
|
||||
foreach (var child in stack.get_children ()) {
|
||||
unowned Notification notification = (Notification) child;
|
||||
|
||||
if (notification.being_destroyed)
|
||||
continue;
|
||||
|
||||
// we only want a single confirmation notification, so we just take the
|
||||
// first one that can be found, no need to check ids or anything
|
||||
unowned ConfirmationNotification? confirmation_notification = notification as ConfirmationNotification;
|
||||
if (confirmation
|
||||
&& confirmation_notification != null) {
|
||||
|
||||
// value may be -1 for a muted state, but -1 is interpreted as no progress set by the
|
||||
// ConfirmationNotification class, so if we do have a progress, we make sure it's set
|
||||
// to 0 for a muted state.
|
||||
var progress_value = progress ? int.max (hints.@get ("value").get_int32 (), 0) : -1;
|
||||
|
||||
confirmation_notification.update (pixbuf,
|
||||
progress_value,
|
||||
hints.@get (X_CANONICAL_PRIVATE_SYNCHRONOUS).get_string (),
|
||||
icon_only);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
unowned NormalNotification? normal_notification = notification as NormalNotification;
|
||||
if (!confirmation
|
||||
&& notification.id == id
|
||||
&& normal_notification != null) {
|
||||
|
||||
normal_notification.update (summary, body, pixbuf, timeout, actions);
|
||||
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
if (allow_bubble) {
|
||||
Notification notification;
|
||||
if (confirmation)
|
||||
notification = new ConfirmationNotification (id, pixbuf, icon_only,
|
||||
progress ? hints.@get ("value").get_int32 () : -1,
|
||||
hints.@get (X_CANONICAL_PRIVATE_SYNCHRONOUS).get_string ());
|
||||
else
|
||||
#if HAS_MUTTER330
|
||||
notification = new NormalNotification (stack.display, id, summary, body, pixbuf,
|
||||
urgency, timeout, pid, actions);
|
||||
#else
|
||||
notification = new NormalNotification (stack.screen, id, summary, body, pixbuf,
|
||||
urgency, timeout, pid, actions);
|
||||
#endif
|
||||
|
||||
notification.action_invoked.connect (notification_action_invoked_callback);
|
||||
notification.closed.connect (notification_closed_callback);
|
||||
stack.show_notification (notification);
|
||||
} else {
|
||||
notification_closed (id, NotificationClosedReason.EXPIRED);
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
static Gdk.RGBA? get_icon_fg_color () {
|
||||
if (icon_fg_color != null)
|
||||
return icon_fg_color;
|
||||
|
||||
var style_path = new Gtk.WidgetPath ();
|
||||
style_path.append_type (typeof (Gtk.Window));
|
||||
style_path.append_type (typeof (Gtk.EventBox));
|
||||
style_path.iter_add_class (1, "gala-notification");
|
||||
style_path.append_type (typeof (Gtk.Label));
|
||||
|
||||
var label_style_context = new Gtk.StyleContext ();
|
||||
label_style_context.add_provider (Gala.Utils.get_gala_css (), Gtk.STYLE_PROVIDER_PRIORITY_FALLBACK);
|
||||
label_style_context.set_path (style_path);
|
||||
label_style_context.add_class ("label");
|
||||
label_style_context.set_state (Gtk.StateFlags.NORMAL);
|
||||
icon_fg_color = label_style_context.get_color (Gtk.StateFlags.NORMAL);
|
||||
|
||||
return icon_fg_color;
|
||||
}
|
||||
|
||||
static Gdk.Pixbuf? get_pixbuf (string app_name, string app_icon, HashTable<string, Variant> hints) {
|
||||
// decide on the icon, order:
|
||||
// - image-data
|
||||
// - image-path
|
||||
// - app_icon
|
||||
// - icon_data
|
||||
// - from app name?
|
||||
// - fallback to dialog-information
|
||||
|
||||
Gdk.Pixbuf? pixbuf = null;
|
||||
Variant? variant = null;
|
||||
var scale = Utils.get_ui_scaling_factor ();
|
||||
var size = Notification.ICON_SIZE * scale;
|
||||
var mask_offset = 4 * scale;
|
||||
var mask_size_offset = mask_offset * 2;
|
||||
var has_mask = false;
|
||||
|
||||
if ((variant = hints.lookup ("image-data")) != null
|
||||
|| (variant = hints.lookup ("image_data")) != null
|
||||
|| (variant = hints.lookup ("icon_data")) != null) {
|
||||
|
||||
has_mask = true;
|
||||
size = size - mask_size_offset;
|
||||
|
||||
pixbuf = load_from_variant_at_size (variant, size);
|
||||
|
||||
} else if ((variant = hints.lookup ("image-path")) != null
|
||||
|| (variant = hints.lookup ("image_path")) != null) {
|
||||
|
||||
var image_path = variant.get_string ();
|
||||
|
||||
try {
|
||||
if (image_path.has_prefix ("file://") || image_path.has_prefix ("/")) {
|
||||
has_mask = true;
|
||||
size = size - mask_size_offset;
|
||||
|
||||
var file_path = File.new_for_commandline_arg (image_path).get_path ();
|
||||
pixbuf = new Gdk.Pixbuf.from_file_at_scale (file_path, size, size, true);
|
||||
} else {
|
||||
pixbuf = Gtk.IconTheme.get_default ().load_icon_for_scale (image_path, Notification.ICON_SIZE, scale, 0);
|
||||
}
|
||||
} catch (Error e) { warning (e.message); }
|
||||
|
||||
} else if (app_icon != "") {
|
||||
|
||||
try {
|
||||
var themed = new ThemedIcon.with_default_fallbacks (app_icon);
|
||||
var info = Gtk.IconTheme.get_default ().lookup_by_gicon_for_scale (themed, Notification.ICON_SIZE, scale, 0);
|
||||
if (info != null) {
|
||||
pixbuf = info.load_symbolic (get_icon_fg_color ());
|
||||
|
||||
if (pixbuf.height != size)
|
||||
pixbuf = pixbuf.scale_simple (size, size, Gdk.InterpType.HYPER);
|
||||
|
||||
}
|
||||
} catch (Error e) { warning (e.message); }
|
||||
|
||||
}
|
||||
|
||||
if (pixbuf == null) {
|
||||
|
||||
try {
|
||||
var themed = new ThemedIcon (app_name.down ());
|
||||
var info = Gtk.IconTheme.get_default ().lookup_by_gicon_for_scale (themed, Notification.ICON_SIZE, scale, 0);
|
||||
if (info != null) {
|
||||
pixbuf = info.load_symbolic (get_icon_fg_color ());
|
||||
|
||||
if (pixbuf.height != size)
|
||||
pixbuf = pixbuf.scale_simple (size, size, Gdk.InterpType.HYPER);
|
||||
}
|
||||
} catch (Error e) {
|
||||
|
||||
try {
|
||||
pixbuf = Gtk.IconTheme.get_default ().load_icon_for_scale (FALLBACK_ICON, Notification.ICON_SIZE, scale, 0);
|
||||
} catch (Error e) { warning (e.message); }
|
||||
}
|
||||
} else if (has_mask) {
|
||||
var offset_x = mask_offset;
|
||||
var offset_y = mask_offset + scale;
|
||||
var mask_size = Notification.ICON_SIZE * scale;
|
||||
|
||||
var surface = new Cairo.ImageSurface (Cairo.Format.ARGB32, mask_size, mask_size);
|
||||
var cr = new Cairo.Context (surface);
|
||||
|
||||
Granite.Drawing.Utilities.cairo_rounded_rectangle (cr, offset_x, offset_y, size, size, mask_offset);
|
||||
cr.clip ();
|
||||
|
||||
Gdk.cairo_set_source_pixbuf (cr, pixbuf, offset_x, offset_y);
|
||||
cr.paint ();
|
||||
|
||||
cr.reset_clip ();
|
||||
|
||||
if (image_mask_pixbuf == null) {
|
||||
try {
|
||||
image_mask_pixbuf = new Gdk.Pixbuf.from_file_at_scale (Config.PKGDATADIR + "/image-mask.svg", -1, mask_size, true);
|
||||
} catch (Error e) {
|
||||
warning (e.message);
|
||||
}
|
||||
}
|
||||
|
||||
Gdk.cairo_set_source_pixbuf (cr, image_mask_pixbuf, 0, 0);
|
||||
cr.paint ();
|
||||
|
||||
pixbuf = Gdk.pixbuf_get_from_surface (surface, 0, 0, mask_size, mask_size);
|
||||
}
|
||||
|
||||
return pixbuf;
|
||||
}
|
||||
|
||||
static Gdk.Pixbuf? load_from_variant_at_size (Variant variant, int size) {
|
||||
if (!variant.is_of_type (new VariantType ("(iiibiiay)"))) {
|
||||
critical ("notify icon/image-data format invalid");
|
||||
return null;
|
||||
}
|
||||
|
||||
int width, height, rowstride, bits_per_sample, n_channels;
|
||||
bool has_alpha;
|
||||
|
||||
variant.get ("(iiibiiay)", out width, out height, out rowstride,
|
||||
out has_alpha, out bits_per_sample, out n_channels, null);
|
||||
|
||||
var data = variant.get_child_value (6);
|
||||
unowned uint8[] pixel_data = (uint8[]) data.get_data ();
|
||||
|
||||
var pixbuf = new Gdk.Pixbuf.with_unowned_data (pixel_data, Gdk.Colorspace.RGB, has_alpha,
|
||||
bits_per_sample, width, height, rowstride, null);
|
||||
|
||||
return pixbuf.scale_simple (size, size, Gdk.InterpType.BILINEAR);
|
||||
}
|
||||
|
||||
void handle_sounds (HashTable<string,Variant> hints) {
|
||||
if (ca_context == null)
|
||||
return;
|
||||
|
||||
Variant? variant = null;
|
||||
|
||||
// Are we suppose to play a sound at all?
|
||||
if ((variant = hints.lookup ("suppress-sound")) != null
|
||||
&& variant.get_boolean ())
|
||||
return;
|
||||
|
||||
Canberra.Proplist props;
|
||||
Canberra.Proplist.create (out props);
|
||||
props.sets (Canberra.PROP_CANBERRA_CACHE_CONTROL, "volatile");
|
||||
|
||||
bool play_sound = false;
|
||||
|
||||
// no sounds for confirmation bubbles
|
||||
if ((variant = hints.lookup (X_CANONICAL_PRIVATE_SYNCHRONOUS)) != null) {
|
||||
var confirmation_type = variant.get_string ();
|
||||
|
||||
// the sound indicator is an exception here, it won't emit a sound at all, even though for
|
||||
// consistency it should. So we make it emit the default one.
|
||||
if (confirmation_type != "indicator-sound")
|
||||
return;
|
||||
|
||||
props.sets (Canberra.PROP_EVENT_ID, "audio-volume-change");
|
||||
play_sound = true;
|
||||
}
|
||||
|
||||
if ((variant = hints.lookup ("sound-name")) != null) {
|
||||
props.sets (Canberra.PROP_EVENT_ID, variant.get_string ());
|
||||
play_sound = true;
|
||||
}
|
||||
|
||||
if ((variant = hints.lookup ("sound-file")) != null) {
|
||||
props.sets (Canberra.PROP_MEDIA_FILENAME, variant.get_string ());
|
||||
play_sound = true;
|
||||
}
|
||||
|
||||
// pick a sound according to the category
|
||||
if (!play_sound) {
|
||||
variant = hints.lookup ("category");
|
||||
unowned string? sound_name = null;
|
||||
|
||||
if (variant != null)
|
||||
sound_name = category_to_sound (variant.get_string ());
|
||||
else
|
||||
sound_name = "dialog-information";
|
||||
|
||||
if (sound_name != null) {
|
||||
props.sets (Canberra.PROP_EVENT_ID, sound_name);
|
||||
play_sound = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (play_sound)
|
||||
ca_context.play_full (0, props);
|
||||
}
|
||||
|
||||
static unowned string? category_to_sound (string category) {
|
||||
unowned string? sound = null;
|
||||
|
||||
switch (category) {
|
||||
case "device.added":
|
||||
sound = "device-added";
|
||||
break;
|
||||
case "device.removed":
|
||||
sound = "device-removed";
|
||||
break;
|
||||
case "im":
|
||||
sound = "message";
|
||||
break;
|
||||
case "im.received":
|
||||
sound = "message-new-instant";
|
||||
break;
|
||||
case "network.connected":
|
||||
sound = "network-connectivity-established";
|
||||
break;
|
||||
case "network.disconnected":
|
||||
sound = "network-connectivity-lost";
|
||||
break;
|
||||
case "presence.online":
|
||||
sound = "service-login";
|
||||
break;
|
||||
case "presence.offline":
|
||||
sound = "service-logout";
|
||||
break;
|
||||
// no sound at all
|
||||
case "x-gnome.music":
|
||||
sound = null;
|
||||
break;
|
||||
// generic errors
|
||||
case "device.error":
|
||||
case "email.bounced":
|
||||
case "im.error":
|
||||
case "network.error":
|
||||
case "transfer.error":
|
||||
sound = "dialog-error";
|
||||
break;
|
||||
// use generic default
|
||||
case "network":
|
||||
case "email":
|
||||
case "email.arrived":
|
||||
case "presence":
|
||||
case "transfer":
|
||||
case "transfer.complete":
|
||||
default:
|
||||
sound = "dialog-information";
|
||||
break;
|
||||
}
|
||||
|
||||
return sound;
|
||||
}
|
||||
|
||||
void notification_closed_callback (Notification notification, uint32 id, uint32 reason) {
|
||||
notification.action_invoked.disconnect (notification_action_invoked_callback);
|
||||
notification.closed.disconnect (notification_closed_callback);
|
||||
|
||||
notification_closed (id, reason);
|
||||
}
|
||||
|
||||
void notification_action_invoked_callback (Notification notification, uint32 id, string action) {
|
||||
action_invoked (id, action);
|
||||
}
|
||||
|
||||
AppInfo? get_appinfo_from_app_name (string app_name) {
|
||||
if (app_name.strip () == "")
|
||||
return null;
|
||||
|
||||
AppInfo? app_info = app_info_cache.get (app_name);
|
||||
if (app_info != null)
|
||||
return app_info;
|
||||
|
||||
foreach (unowned AppInfo info in AppInfo.get_all ()) {
|
||||
if (info == null || !validate (info, app_name))
|
||||
continue;
|
||||
|
||||
app_info = info;
|
||||
break;
|
||||
}
|
||||
|
||||
app_info_cache.set (app_name, app_info);
|
||||
|
||||
return app_info;
|
||||
}
|
||||
|
||||
static bool validate (AppInfo appinfo, string name) {
|
||||
string? app_executable = appinfo.get_executable ();
|
||||
string? app_name = appinfo.get_name ();
|
||||
string? app_display_name = appinfo.get_display_name ();
|
||||
|
||||
if (app_name == null || app_executable == null || app_display_name == null)
|
||||
return false;
|
||||
|
||||
string token = name.down ().strip ();
|
||||
string? token_executable = token;
|
||||
if (!token_executable.has_prefix (Path.DIR_SEPARATOR_S))
|
||||
token_executable = Environment.find_program_in_path (token_executable);
|
||||
|
||||
if (!app_executable.has_prefix (Path.DIR_SEPARATOR_S))
|
||||
app_executable = Environment.find_program_in_path (app_executable);
|
||||
|
||||
string[] args;
|
||||
|
||||
try {
|
||||
Shell.parse_argv (appinfo.get_commandline (), out args);
|
||||
} catch (ShellError e) {
|
||||
warning ("%s", e.message);
|
||||
}
|
||||
|
||||
return (app_name.down () == token
|
||||
|| token_executable == app_executable
|
||||
|| (args.length > 0 && args[0] == token)
|
||||
|| app_display_name.down ().contains (token));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
//
|
||||
// Copyright (C) 2014 Gala Developers
|
||||
//
|
||||
// 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, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
//
|
||||
// Authored by: Marcus Wichelmann <admin@marcusw.de>
|
||||
//
|
||||
|
||||
namespace Gala.Plugins.Notify {
|
||||
public class NotifySettings : Granite.Services.Settings {
|
||||
public bool do_not_disturb { get; set; }
|
||||
|
||||
static NotifySettings? instance = null;
|
||||
|
||||
private NotifySettings () {
|
||||
base (Config.SCHEMA + ".notifications");
|
||||
}
|
||||
|
||||
public static unowned NotifySettings get_default () {
|
||||
if (instance == null)
|
||||
instance = new NotifySettings ();
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,235 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="48"
|
||||
height="48"
|
||||
id="svg2"
|
||||
version="1.1">
|
||||
<defs
|
||||
id="defs4">
|
||||
<linearGradient
|
||||
x1="45.447727"
|
||||
y1="92.539597"
|
||||
x2="45.447727"
|
||||
y2="7.0165396"
|
||||
id="ButtonShadow-9"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="scale(1.0058652,0.994169)">
|
||||
<stop
|
||||
id="stop3750-3"
|
||||
style="stop-color:#000000;stop-opacity:1"
|
||||
offset="0" />
|
||||
<stop
|
||||
id="stop3752-1"
|
||||
style="stop-color:#000000;stop-opacity:0.58823532"
|
||||
offset="1" />
|
||||
</linearGradient>
|
||||
<filter
|
||||
id="filter3174-4"
|
||||
style="color-interpolation-filters:sRGB">
|
||||
<feGaussianBlur
|
||||
id="feGaussianBlur3176-5"
|
||||
stdDeviation="1.71" />
|
||||
</filter>
|
||||
<linearGradient
|
||||
x1="45.447727"
|
||||
y1="92.539597"
|
||||
x2="45.447727"
|
||||
y2="7.0165396"
|
||||
id="ButtonShadow"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="scale(1.0058652,0.994169)">
|
||||
<stop
|
||||
id="stop3750-9"
|
||||
style="stop-color:#000000;stop-opacity:1"
|
||||
offset="0" />
|
||||
<stop
|
||||
id="stop3752-7"
|
||||
style="stop-color:#000000;stop-opacity:0.58823532"
|
||||
offset="1" />
|
||||
</linearGradient>
|
||||
<filter
|
||||
id="filter3174"
|
||||
style="color-interpolation-filters:sRGB">
|
||||
<feGaussianBlur
|
||||
id="feGaussianBlur3176"
|
||||
stdDeviation="1.71" />
|
||||
</filter>
|
||||
<linearGradient
|
||||
xlink:href="#ButtonShadow-9"
|
||||
id="linearGradient6176"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="scale(1.0058652,0.994169)"
|
||||
x1="45.447727"
|
||||
y1="92.539597"
|
||||
x2="45.447727"
|
||||
y2="7.0165396" />
|
||||
<linearGradient
|
||||
xlink:href="#ButtonShadow"
|
||||
id="linearGradient6178"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="scale(1.0058652,0.994169)"
|
||||
x1="45.447727"
|
||||
y1="92.539597"
|
||||
x2="45.447727"
|
||||
y2="7.0165396" />
|
||||
<linearGradient
|
||||
id="linearGradient3688-464-309-9-2-4-2">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#181818;stop-opacity:1"
|
||||
id="stop2889-7-9-6-9" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#181818;stop-opacity:0"
|
||||
id="stop2891-6-6-1-7" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
gradientTransform="matrix(1.1578952,0,0,0.6428571,-3.78948,16.2857)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#linearGradient3702-501-757-8-4-1-1-9"
|
||||
id="linearGradient6084"
|
||||
y2="39.999443"
|
||||
x2="25.058096"
|
||||
y1="47.027729"
|
||||
x1="25.058096" />
|
||||
<linearGradient
|
||||
id="linearGradient3702-501-757-8-4-1-1-9">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#181818;stop-opacity:0"
|
||||
id="stop2895-8-9-9-1-4" />
|
||||
<stop
|
||||
offset="0.5"
|
||||
style="stop-color:#181818;stop-opacity:1"
|
||||
id="stop2897-7-8-7-7-3" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#181818;stop-opacity:0"
|
||||
id="stop2899-4-5-1-5-6" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
gradientTransform="translate(0,1)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#linearGradient3924-1-7-54"
|
||||
id="linearGradient3028-82"
|
||||
y2="43"
|
||||
x2="23.99999"
|
||||
y1="4.999989"
|
||||
x1="23.99999" />
|
||||
<linearGradient
|
||||
id="linearGradient3924-1-7-54">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#ffffff;stop-opacity:1"
|
||||
id="stop3926-3-4-4" />
|
||||
<stop
|
||||
offset="0.06316455"
|
||||
style="stop-color:#ffffff;stop-opacity:0.23529412"
|
||||
id="stop3928-91-41-8" />
|
||||
<stop
|
||||
offset="0.95056331"
|
||||
style="stop-color:#ffffff;stop-opacity:0.15686275"
|
||||
id="stop3930-6-6-6" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#ffffff;stop-opacity:0.39215687"
|
||||
id="stop3932-6-6-7" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
gradientTransform="matrix(2.3201719,0,0,0.89999994,28.617841,5.0999865)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#linearGradient3688-464-309-9-2-4-2"
|
||||
id="radialGradient3013-3"
|
||||
fy="43.5"
|
||||
fx="4.9929786"
|
||||
r="2.5"
|
||||
cy="43.5"
|
||||
cx="4.9929786" />
|
||||
<radialGradient
|
||||
gradientTransform="matrix(-2.3201719,0,0,-0.89999994,19.382168,83.399981)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#linearGradient3688-464-309-9-2-4-2"
|
||||
id="radialGradient3015-3"
|
||||
fy="43.5"
|
||||
fx="4.9929786"
|
||||
r="2.5"
|
||||
cy="43.5"
|
||||
cx="4.9929786" />
|
||||
</defs>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
transform="translate(906.27272,-451.79544)"
|
||||
style="display:none"
|
||||
id="layer2">
|
||||
<rect
|
||||
style="opacity:0.9;fill:url(#linearGradient6178);fill-opacity:1;fill-rule:nonzero;stroke:none;filter:url(#filter3174)"
|
||||
id="rect3745"
|
||||
y="7"
|
||||
x="5"
|
||||
ry="6"
|
||||
rx="6"
|
||||
height="85"
|
||||
width="86" />
|
||||
</g>
|
||||
<g
|
||||
transform="translate(903,-452.15908)"
|
||||
style="display:none"
|
||||
id="layer2-4">
|
||||
<rect
|
||||
style="opacity:0.9;fill:url(#linearGradient6176);fill-opacity:1;fill-rule:nonzero;stroke:none;filter:url(#filter3174-4)"
|
||||
id="rect3745-5"
|
||||
y="7"
|
||||
x="5"
|
||||
ry="6"
|
||||
rx="6"
|
||||
height="85"
|
||||
width="86" />
|
||||
</g>
|
||||
<rect
|
||||
width="37"
|
||||
height="37"
|
||||
rx="1"
|
||||
ry="1"
|
||||
x="5.5"
|
||||
y="6.4999828"
|
||||
id="rect6741-11-6"
|
||||
style="opacity:0.4;fill:none;stroke:url(#linearGradient3028-82);stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
<rect
|
||||
width="39"
|
||||
height="39"
|
||||
rx="2"
|
||||
ry="2"
|
||||
x="4.5"
|
||||
y="5.4999828"
|
||||
id="rect5505-21-3-2-7-4"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.3;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" />
|
||||
<path
|
||||
id="rect4188"
|
||||
d="M 7.7890625 45 L 7.7890625 46.5 L 40.210938 46.5 L 40.210938 45 L 7.7890625 45 z "
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.3;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:url(#linearGradient6084);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;filter-blend-mode:normal;filter-gaussianBlur-deviation:0" />
|
||||
<path
|
||||
id="rect2801-3"
|
||||
d="M 44 42 L 44 42.5 C 44 43.885 42.885 45 41.5 45 L 40.210938 45 L 40.210938 46.5 L 46 46.5 L 46 42 L 44 42 z "
|
||||
style="fill:url(#radialGradient3013-3);fill-opacity:1;stroke:none;opacity:0.3" />
|
||||
<path
|
||||
id="rect3696-6"
|
||||
d="M 2 42 L 2 46.5 L 7.7890625 46.5 L 7.7890625 45 L 6.5 45 C 5.115 45 4 43.885 4 42.5 L 4 42 L 2 42 z "
|
||||
style="fill:url(#radialGradient3015-3);fill-opacity:1;stroke:none;opacity:0.3" />
|
||||
</svg>
|
Before Width: | Height: | Size: 7.7 KiB |
@ -1,26 +0,0 @@
|
||||
gala_notify_sources = [
|
||||
'Main.vala',
|
||||
'ConfirmationNotification.vala',
|
||||
'NormalNotification.vala',
|
||||
'Notification.vala',
|
||||
'NotificationStack.vala',
|
||||
'NotifyServer.vala',
|
||||
'NotifySettings.vala',
|
||||
]
|
||||
|
||||
canberra_dep = [dependency('libcanberra'), dependency('libcanberra-gtk3')]
|
||||
|
||||
gala_notify_lib = shared_library(
|
||||
'gala-notify',
|
||||
gala_notify_sources,
|
||||
dependencies: [gala_dep, gala_base_dep, canberra_dep],
|
||||
include_directories: config_inc_dir,
|
||||
install: true,
|
||||
install_dir: plugins_dir,
|
||||
install_rpath: mutter_typelib_dir,
|
||||
)
|
||||
|
||||
install_data(
|
||||
'data/image-mask.svg',
|
||||
install_dir: pkgdata_dir,
|
||||
)
|
@ -19,7 +19,6 @@ namespace Gala {
|
||||
public class BehaviorSettings : Granite.Services.Settings {
|
||||
public bool dynamic_workspaces { get; set; }
|
||||
public bool edge_tiling { get; set; }
|
||||
public bool use_new_notifications { get; set; }
|
||||
public string panel_main_menu_action { get; set; }
|
||||
public string toggle_recording_action { get; set; }
|
||||
public string overlay_action { get; set; }
|
||||
|
@ -1542,10 +1542,8 @@ namespace Gala {
|
||||
|
||||
break;
|
||||
case Meta.WindowType.NOTIFICATION:
|
||||
if (BehaviorSettings.get_default ().use_new_notifications) {
|
||||
notification_stack.show_notification (actor);
|
||||
map_completed (actor);
|
||||
}
|
||||
notification_stack.show_notification (actor);
|
||||
map_completed (actor);
|
||||
|
||||
break;
|
||||
default:
|
||||
|
Loading…
Reference in New Issue
Block a user