Optionally support native notification windows (#552)

This commit is contained in:
Cassidy James Blaede 2020-01-14 14:59:01 -07:00 committed by Daniel Foré
parent cdd3bdcfb0
commit a127d09a22
6 changed files with 240 additions and 19 deletions

View File

@ -57,6 +57,11 @@
<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>

View File

@ -22,13 +22,18 @@ 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 ();
@ -41,26 +46,40 @@ namespace Gala.Plugins.Notify
#else
stack = new NotificationStack (screen);
#endif
wm.ui_group.add_child (stack);
track_actor (stack);
stack.animations_changed.connect ((running) => {
freeze_track = running;
});
server = new NotifyServer (stack);
update_position ();
#if HAS_MUTTER330
unowned Meta.MonitorManager monitor_manager = Meta.MonitorManager.@get ();
monitor_manager.monitors_changed.connect (update_position);
display.workareas_changed.connect (update_position);
#else
screen.monitors_changed.connect (update_position);
screen.workareas_changed.connect (update_position);
#endif
Bus.own_name (BusType.SESSION, "org.freedesktop.Notifications", BusNameOwnerFlags.NONE,
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);
@ -76,6 +95,20 @@ namespace Gala.Plugins.Notify
});
}
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

157
src/NotificationStack.vala Normal file
View File

@ -0,0 +1,157 @@
/*
* Copyright 2020 elementary, Inc (https://elementary.io)
* 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/>.
*/
public class Gala.NotificationStack : Object {
// 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
private const int TOP_OFFSET = 2;
private const int ADDITIONAL_MARGIN = 12;
private const int MARGIN = 12;
private const int WIDTH = 300;
private int stack_x;
private int stack_y;
private int stack_width;
public Meta.Screen screen { get; construct; }
private Gee.ArrayList<unowned Meta.WindowActor> notifications;
public NotificationStack (Meta.Screen screen) {
Object (screen: screen);
}
construct {
notifications = new Gee.ArrayList<Meta.WindowActor> ();
screen.monitors_changed.connect (update_stack_allocation);
screen.workareas_changed.connect (update_stack_allocation);
update_stack_allocation ();
}
public void show_notification (Meta.WindowActor notification) {
notification.set_pivot_point (0.5f, 0.5f);
unowned Meta.Window window = notification.get_meta_window ();
window.stick ();
var scale = Utils.get_ui_scaling_factor ();
var entry = new Clutter.TransitionGroup ();
entry.remove_on_complete = true;
entry.duration = 400;
var opacity_transition = new Clutter.PropertyTransition ("opacity");
opacity_transition.set_from_value (0);
opacity_transition.set_to_value (255);
var flip_transition = new Clutter.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);
notification.add_transition ("entry", entry);
/**
* We will make space for the incomming notification
* by shifting all current notifications by height
* and then add it to the notifications list.
*/
update_positions (notification.height);
move_window (notification, stack_x, stack_y + TOP_OFFSET + ADDITIONAL_MARGIN * scale);
notifications.insert (0, notification);
}
private void update_stack_allocation () {
var primary = screen.get_primary_monitor ();
var area = screen.get_active_workspace ().get_work_area_for_monitor (primary);
var scale = Utils.get_ui_scaling_factor ();
stack_width = (WIDTH + MARGIN) * scale;
stack_x = area.x + area.width - stack_width;
stack_y = area.y;
}
private void update_positions (float add_y = 0.0f) {
var scale = Utils.get_ui_scaling_factor ();
var y = stack_y + TOP_OFFSET + add_y + ADDITIONAL_MARGIN * scale;
var i = notifications.size;
var delay_step = i > 0 ? 150 / i : 0;
foreach (var actor in notifications) {
actor.save_easing_state ();
actor.set_easing_mode (Clutter.AnimationMode.EASE_OUT_BACK);
actor.set_easing_duration (200);
actor.set_easing_delay ((i--) * delay_step);
move_window (actor, -1, (int)y);
actor.restore_easing_state ();
y += actor.height;
}
}
public void destroy_notification (Meta.WindowActor notification) {
notification.save_easing_state ();
notification.set_easing_duration (100);
notification.set_easing_mode (Clutter.AnimationMode.EASE_IN_QUAD);
notification.opacity = 0;
notification.x += stack_width;
notification.restore_easing_state ();
notifications.remove (notification);
update_positions ();
}
/**
* This function takes care of properly updating both the actor
* position and the actual window position.
*
* To enable animations for a window we first need to move it's frame
* in the compositor and then calculate & apply the coordinates for the window
* actor.
*/
private static void move_window (Meta.WindowActor actor, int x, int y) {
if (actor.is_destroyed ()) {
return;
}
unowned Meta.Window window = actor.get_meta_window ();
if (window == null) {
return;
}
var rect = window.get_frame_rect ();
window.move_frame (false, x != -1 ? x : rect.x, y != -1 ? y : rect.y);
/**
* move_frame does not guarantee that the frame rectangle
* will be updated instantly, get the buffer rectangle.
*/
rect = window.get_buffer_rect ();
actor.x = rect.x - ((actor.width - rect.width) / 2);
actor.y = rect.y - ((actor.height - rect.height) / 2);
}
}

View File

@ -19,6 +19,7 @@ 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

@ -70,6 +70,8 @@ namespace Gala {
private Meta.Window? moving; //place for the window that is being moved over
Daemon? daemon_proxy = null;
NotificationStack notification_stack;
Gee.LinkedList<ModalProxy> modal_stack = new Gee.LinkedList<ModalProxy> ();
@ -159,6 +161,8 @@ namespace Gala {
#endif
KeyboardManager.init (display);
notification_stack = new NotificationStack (screen);
// Due to a bug which enables access to the stage when using multiple monitors
// in the screensaver, we have to listen for changes and make sure the input area
// is set to NONE when we are in locked mode
@ -986,7 +990,7 @@ namespace Gala {
public override void show_window_menu (Meta.Window window, Meta.WindowMenuType menu, int x, int y) {
switch (menu) {
case Meta.WindowMenuType.WM:
if (daemon_proxy == null) {
if (daemon_proxy == null || window.get_window_type () == Meta.WindowType.NOTIFICATION) {
return;
}
@ -1527,6 +1531,13 @@ namespace Gala {
window.is_attached_dialog ())
dim_window (window.find_root_ancestor (), true);
break;
case Meta.WindowType.NOTIFICATION:
if (BehaviorSettings.get_default ().use_new_notifications) {
notification_stack.show_notification (actor);
map_completed (actor);
}
break;
default:
map_completed (actor);
@ -1618,6 +1629,17 @@ namespace Gala {
actor.opacity = 0U;
actor.restore_easing_state ();
ulong destroy_handler_id = 0UL;
destroy_handler_id = actor.transitions_completed.connect (() => {
actor.disconnect (destroy_handler_id);
destroying.remove (actor);
destroy_completed (actor);
});
break;
case Meta.WindowType.NOTIFICATION:
destroying.add (actor);
notification_stack.destroy_notification (actor);
ulong destroy_handler_id = 0UL;
destroy_handler_id = actor.transitions_completed.connect (() => {
actor.disconnect (destroy_handler_id);
@ -1930,11 +1952,13 @@ namespace Gala {
parents.prepend (actor.get_parent ());
clutter_actor_reparent (actor, static_windows);
actor.set_translation (-clone_offset_x, -clone_offset_y, 0);
actor.save_easing_state ();
actor.set_easing_duration (300);
actor.opacity = 0;
actor.restore_easing_state ();
if (window.window_type != Meta.WindowType.NOTIFICATION) {
actor.set_translation (-clone_offset_x, -clone_offset_y, 0);
actor.save_easing_state ();
actor.set_easing_duration (300);
actor.opacity = 0;
actor.restore_easing_state ();
}
}
continue;

View File

@ -5,6 +5,7 @@ gala_bin_sources = files(
'DragDropAction.vala',
'InternalUtils.vala',
'KeyboardManager.vala',
'NotificationStack.vala',
'Main.vala',
'MediaFeedback.vala',
'PluginManager.vala',