Remove old Notify plugin (#814)

* Remove old Notify plugin

* missed a spot
This commit is contained in:
Daniel Foré 2020-05-25 14:23:07 -07:00 committed by GitHub
parent eb3611221a
commit 41d7ab1383
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 2 additions and 2100 deletions

View File

@ -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>

View File

@ -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')

View File

@ -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);
}
}
}

View File

@ -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
};
}

View File

@ -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, "&amp;");
text = tag_regex.replace (text, text.length, 0, "&lt;");
} 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 ();
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -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));
}
}
}

View File

@ -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;
}
}
}

View File

@ -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

View File

@ -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,
)

View File

@ -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; }

View File

@ -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: