
* Bumped version requirements for libhandy and gtk-layer-shell * Bumped Ubuntu Action version to 23.10
740 lines
31 KiB
Vala
740 lines
31 KiB
Vala
namespace SwayNotificationCenter {
|
|
[GtkTemplate (ui = "/org/erikreider/sway-notification-center/controlCenter/controlCenter.ui")]
|
|
public class ControlCenter : Gtk.ApplicationWindow {
|
|
[GtkChild]
|
|
unowned Gtk.Box notifications_box;
|
|
[GtkChild]
|
|
unowned Gtk.Stack stack;
|
|
[GtkChild]
|
|
unowned Gtk.ScrolledWindow scrolled_window;
|
|
FadedViewport viewport = new FadedViewport (20);
|
|
Gtk.ListBox list_box = new Gtk.ListBox ();
|
|
|
|
[GtkChild]
|
|
unowned Gtk.Box box;
|
|
|
|
unowned NotificationGroup ? expanded_group = null;
|
|
private double fade_animation_progress = 1.0;
|
|
private Animation ? notification_fade_animation;
|
|
private double scroll_animation_progress = 1.0;
|
|
private Animation ? scroll_animation;
|
|
|
|
HashTable<uint32, unowned NotificationGroup> noti_groups_id =
|
|
new HashTable<uint32, unowned NotificationGroup> (direct_hash, direct_equal);
|
|
/** NOTE: Only includes groups with ids with length of > 0 */
|
|
HashTable<string, unowned NotificationGroup> noti_groups_name =
|
|
new HashTable<string, unowned NotificationGroup> (str_hash, str_equal);
|
|
|
|
const string STACK_NOTIFICATIONS_PAGE = "notifications-list";
|
|
const string STACK_PLACEHOLDER_PAGE = "notifications-placeholder";
|
|
|
|
private Gtk.GestureMultiPress blank_window_gesture;
|
|
private bool blank_window_down = false;
|
|
private bool blank_window_in = false;
|
|
|
|
private SwayncDaemon swaync_daemon;
|
|
private NotiDaemon noti_daemon;
|
|
|
|
private int list_position = 0;
|
|
|
|
private bool list_reverse = false;
|
|
private Gtk.Align list_align = Gtk.Align.START;
|
|
|
|
private Array<Widgets.BaseWidget> widgets = new Array<Widgets.BaseWidget> ();
|
|
private const string[] DEFAULT_WIDGETS = { "title", "dnd", "notifications" };
|
|
|
|
public ControlCenter (SwayncDaemon swaync_daemon, NotiDaemon noti_daemon) {
|
|
this.swaync_daemon = swaync_daemon;
|
|
this.noti_daemon = noti_daemon;
|
|
|
|
this.swaync_daemon.reloading_css.connect (reload_notifications_style);
|
|
|
|
viewport.set_visible (true);
|
|
viewport.set_vexpand (true);
|
|
viewport.set_shadow_type (Gtk.ShadowType.NONE);
|
|
scrolled_window.add (viewport);
|
|
|
|
list_box.set_visible (true);
|
|
list_box.set_valign (Gtk.Align.END);
|
|
list_box.set_selection_mode (Gtk.SelectionMode.NONE);
|
|
list_box.set_activate_on_single_click (false);
|
|
list_box.get_style_context ().add_class ("control-center-list");
|
|
viewport.add (list_box);
|
|
|
|
if (swaync_daemon.use_layer_shell) {
|
|
if (!GtkLayerShell.is_supported ()) {
|
|
stderr.printf ("GTKLAYERSHELL IS NOT SUPPORTED!\n");
|
|
stderr.printf ("Swaync only works on Wayland!\n");
|
|
stderr.printf ("If running waylans session, try running:\n");
|
|
stderr.printf ("\tGDK_BACKEND=wayland swaync\n");
|
|
Process.exit (1);
|
|
}
|
|
GtkLayerShell.init_for_window (this);
|
|
GtkLayerShell.set_namespace (this, "swaync-control-center");
|
|
GtkLayerShell.set_anchor (this, GtkLayerShell.Edge.TOP, true);
|
|
GtkLayerShell.set_anchor (this, GtkLayerShell.Edge.LEFT, true);
|
|
GtkLayerShell.set_anchor (this, GtkLayerShell.Edge.RIGHT, true);
|
|
GtkLayerShell.set_anchor (this, GtkLayerShell.Edge.BOTTOM, true);
|
|
}
|
|
|
|
this.map.connect (() => {
|
|
set_anchor ();
|
|
// Wait until the layer has attached
|
|
ulong id = 0;
|
|
id = notify["has-toplevel-focus"].connect (() => {
|
|
disconnect (id);
|
|
unowned Gdk.Monitor monitor = null;
|
|
unowned Gdk.Window ? win = get_window ();
|
|
if (win != null) {
|
|
monitor = get_display ().get_monitor_at_window (win);
|
|
}
|
|
swaync_daemon.show_blank_windows (monitor);
|
|
});
|
|
});
|
|
this.unmap.connect (swaync_daemon.hide_blank_windows);
|
|
|
|
/*
|
|
* Handling of bank window presses (pressing outside of ControlCenter)
|
|
*/
|
|
blank_window_gesture = new Gtk.GestureMultiPress (this);
|
|
blank_window_gesture.set_touch_only (false);
|
|
blank_window_gesture.set_exclusive (true);
|
|
blank_window_gesture.set_button (Gdk.BUTTON_PRIMARY);
|
|
blank_window_gesture.set_propagation_phase (Gtk.PropagationPhase.BUBBLE);
|
|
blank_window_gesture.pressed.connect ((_gesture, _n_press, x, y) => {
|
|
// Calculate if the clicked coords intersect the ControlCenter
|
|
Gdk.Rectangle click_rectangle = Gdk.Rectangle () {
|
|
width = 1,
|
|
height = 1,
|
|
x = (int) x,
|
|
y = (int) y,
|
|
};
|
|
blank_window_in = !box.intersect (click_rectangle, null);
|
|
blank_window_down = true;
|
|
});
|
|
blank_window_gesture.released.connect ((gesture, _n_press, _x, _y) => {
|
|
// Emit released
|
|
if (!blank_window_down) return;
|
|
blank_window_down = false;
|
|
if (blank_window_in) {
|
|
try {
|
|
swaync_daemon.set_visibility (false);
|
|
} catch (Error e) {
|
|
stderr.printf ("ControlCenter BlankWindow Click Error: %s\n",
|
|
e.message);
|
|
}
|
|
}
|
|
|
|
Gdk.EventSequence ? sequence = gesture.get_current_sequence ();
|
|
if (sequence == null) {
|
|
blank_window_in = false;
|
|
}
|
|
});
|
|
blank_window_gesture.update.connect ((gesture, sequence) => {
|
|
Gtk.GestureSingle gesture_single = (Gtk.GestureSingle) gesture;
|
|
if (sequence != gesture_single.get_current_sequence ()) return;
|
|
// Calculate if the clicked coords intersect the ControlCenter
|
|
double x, y;
|
|
gesture.get_point (sequence, out x, out y);
|
|
Gdk.Rectangle click_rectangle = Gdk.Rectangle () {
|
|
width = 1,
|
|
height = 1,
|
|
x = (int) x,
|
|
y = (int) y,
|
|
};
|
|
if (box.intersect (click_rectangle, null)) {
|
|
blank_window_in = false;
|
|
}
|
|
});
|
|
blank_window_gesture.cancel.connect ((gesture, sequence) => {
|
|
blank_window_down = false;
|
|
});
|
|
|
|
// Only use release for closing notifications due to Escape key
|
|
// sometimes being passed through to unfucused application
|
|
// Ex: Firefox in a fullscreen YouTube video
|
|
this.key_release_event.connect ((w, event_key) => {
|
|
if (this.get_focus () is Gtk.Entry) {
|
|
switch (Gdk.keyval_name (event_key.keyval)) {
|
|
case "Escape":
|
|
this.set_focus (null);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
if (event_key.type == Gdk.EventType.KEY_RELEASE) {
|
|
switch (Gdk.keyval_name (event_key.keyval)) {
|
|
case "Escape":
|
|
case "Caps_Lock":
|
|
this.set_visibility (false);
|
|
return true;
|
|
}
|
|
}
|
|
return true;
|
|
});
|
|
|
|
key_press_event.connect (key_press_event_cb);
|
|
|
|
stack.set_visible_child_name (STACK_PLACEHOLDER_PAGE);
|
|
// Switches the stack page depending on the amount of notifications
|
|
list_box.add.connect (() => {
|
|
stack.set_visible_child_name (STACK_NOTIFICATIONS_PAGE);
|
|
});
|
|
|
|
list_box.remove.connect ((container, _widget) => {
|
|
if (container.get_children ().length () > 0) return;
|
|
stack.set_visible_child_name (STACK_PLACEHOLDER_PAGE);
|
|
});
|
|
|
|
add_widgets ();
|
|
|
|
notification_fade_animation = new Animation (this, Constants.ANIMATION_DURATION,
|
|
Animation.ease_in_out_cubic,
|
|
fade_animation_value_cb,
|
|
fade_animation_done_cb);
|
|
scroll_animation = new Animation (this, Constants.ANIMATION_DURATION,
|
|
Animation.ease_in_out_cubic,
|
|
scroll_animation_value_cb,
|
|
scroll_animation_done_cb);
|
|
list_box.draw.connect (list_box_draw_cb);
|
|
}
|
|
|
|
void fade_animation_value_cb (double progress) {
|
|
this.fade_animation_progress = progress;
|
|
|
|
this.queue_draw ();
|
|
}
|
|
|
|
void fade_animation_done_cb () {}
|
|
|
|
void fade_animate (double to) {
|
|
notification_fade_animation.stop ();
|
|
notification_fade_animation.start (fade_animation_progress, to);
|
|
}
|
|
|
|
void scroll_animation_value_cb (double progress) {
|
|
this.scroll_animation_progress = progress;
|
|
|
|
// Scroll to the top of the group
|
|
if (scroll_animation_progress > 0) {
|
|
scrolled_window.vadjustment.set_value (scroll_animation_progress);
|
|
}
|
|
}
|
|
|
|
void scroll_animation_done_cb () {
|
|
int y = expanded_group.get_relative_y (list_box);
|
|
if (y > 0) {
|
|
scrolled_window.vadjustment.set_value (y);
|
|
}
|
|
}
|
|
|
|
void scroll_animate (double to) {
|
|
scroll_animation.stop ();
|
|
scroll_animation.start (scroll_animation_progress, to);
|
|
}
|
|
|
|
/// Fade non-expanded groups when one group is expanded
|
|
private bool list_box_draw_cb (Cairo.Context cr) {
|
|
Cairo.Pattern fade_gradient = new Cairo.Pattern.linear (0, 0, 0, 1);
|
|
fade_gradient.add_color_stop_rgba (0, 1, 1, 1, 1 - fade_animation_progress - 0.5);
|
|
|
|
foreach (unowned Gtk.Widget widget in list_box.get_children ()) {
|
|
Gtk.Allocation alloc;
|
|
widget.get_allocated_size (out alloc, null);
|
|
|
|
cr.save ();
|
|
cr.translate (0, alloc.y);
|
|
|
|
cr.push_group ();
|
|
widget.draw (cr);
|
|
|
|
cr.scale (alloc.width, alloc.height);
|
|
if (widget != expanded_group) {
|
|
cr.set_source (fade_gradient);
|
|
cr.rectangle (0, 0, alloc.width, alloc.height);
|
|
cr.set_operator (Cairo.Operator.DEST_OUT);
|
|
cr.fill ();
|
|
}
|
|
|
|
cr.pop_group_to_source ();
|
|
cr.paint ();
|
|
|
|
cr.restore ();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private bool key_press_event_cb (Gdk.EventKey event_key) {
|
|
if (this.get_focus () is Gtk.Entry) return false;
|
|
if (event_key.type == Gdk.EventType.KEY_PRESS) {
|
|
var children = list_box.get_children ();
|
|
var group = (NotificationGroup) list_box.get_focus_child ();
|
|
switch (Gdk.keyval_name (event_key.keyval)) {
|
|
case "Return":
|
|
if (group != null) {
|
|
var noti = group.get_latest_notification ();
|
|
if (group.only_single_notification () && noti != null) {
|
|
noti.click_default_action ();
|
|
break;
|
|
}
|
|
group.on_expand_change (group.toggle_expanded ());
|
|
}
|
|
break;
|
|
case "Delete":
|
|
case "BackSpace":
|
|
if (group != null) {
|
|
int len = (int) children.length ();
|
|
if (len == 0) break;
|
|
// Add a delta so that we select the next notification
|
|
// due to it not being gone from the list yet due to
|
|
// the fade transition
|
|
int delta = 2;
|
|
if (list_reverse) {
|
|
if (children.first ().data != group) {
|
|
delta = 0;
|
|
}
|
|
list_position--;
|
|
} else {
|
|
if (list_position > 0) list_position--;
|
|
if (children.last ().data == group) {
|
|
delta = 0;
|
|
}
|
|
}
|
|
var noti = group.get_latest_notification ();
|
|
if (group.only_single_notification () && noti != null) {
|
|
close_notification (noti.param.applied_id, true);
|
|
break;
|
|
}
|
|
group.close_all_notifications ();
|
|
navigate_list (list_position + delta);
|
|
return true;
|
|
}
|
|
break;
|
|
case "C":
|
|
close_all_notifications ();
|
|
break;
|
|
case "D":
|
|
try {
|
|
swaync_daemon.toggle_dnd ();
|
|
} catch (Error e) {
|
|
error ("Error: %s\n", e.message);
|
|
}
|
|
break;
|
|
case "Down":
|
|
if (list_position + 1 < children.length ()) {
|
|
++list_position;
|
|
}
|
|
break;
|
|
case "Up":
|
|
if (list_position > 0) --list_position;
|
|
break;
|
|
case "Home":
|
|
list_position = 0;
|
|
break;
|
|
case "End":
|
|
list_position = ((int) children.length ()) - 1;
|
|
if (list_position == uint.MAX) list_position = 0;
|
|
break;
|
|
default:
|
|
// Pressing 1-9 to activate a notification action
|
|
for (int i = 0; i < 9; i++) {
|
|
uint keyval = Gdk.keyval_from_name (
|
|
(i + 1).to_string ());
|
|
if (event_key.keyval == keyval && group != null) {
|
|
var noti = group.get_latest_notification ();
|
|
noti.click_alt_action (i);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
navigate_list (list_position);
|
|
}
|
|
// Override the builtin list navigation
|
|
return true;
|
|
}
|
|
|
|
/** Adds all custom widgets. Removes previous widgets */
|
|
public void add_widgets () {
|
|
// Remove all widgets
|
|
foreach (var widget in widgets.data) {
|
|
box.remove (widget);
|
|
}
|
|
widgets.remove_range (0, widgets.length);
|
|
|
|
string[] w = ConfigModel.instance.widgets.data;
|
|
if (w.length == 0) w = DEFAULT_WIDGETS;
|
|
bool has_notification = false;
|
|
foreach (string key in w) {
|
|
// Reposition the notifications_box
|
|
if (key == "notifications") {
|
|
has_notification = true;
|
|
uint pos = box.get_children ().length ();
|
|
box.reorder_child (notifications_box, (int) (pos > 0 ? --pos : 0));
|
|
continue;
|
|
}
|
|
// Add the widget if it is valid
|
|
Widgets.BaseWidget ? widget = Widgets.get_widget_from_key (
|
|
key, swaync_daemon, noti_daemon);
|
|
if (widget == null) continue;
|
|
widgets.append_val (widget);
|
|
box.pack_start (widgets.index (widgets.length - 1),
|
|
false, true, 0);
|
|
}
|
|
if (!has_notification) {
|
|
warning ("Notification widget not included in \"widgets\" config. Using default bottom position");
|
|
uint pos = box.get_children ().length ();
|
|
box.reorder_child (notifications_box, (int) (pos > 0 ? --pos : 0));
|
|
}
|
|
}
|
|
|
|
/** Resets the UI positions */
|
|
private void set_anchor () {
|
|
if (swaync_daemon.use_layer_shell) {
|
|
// Set the exlusive zone
|
|
int exclusive_zone = ConfigModel.instance.control_center_exclusive_zone ? 0 : 100;
|
|
GtkLayerShell.set_exclusive_zone (this, exclusive_zone);
|
|
// Grabs the keyboard input until closed
|
|
bool keyboard_shortcuts = ConfigModel.instance.keyboard_shortcuts;
|
|
var mode = keyboard_shortcuts ?
|
|
GtkLayerShell.KeyboardMode.EXCLUSIVE :
|
|
GtkLayerShell.KeyboardMode.NONE;
|
|
GtkLayerShell.set_keyboard_mode (this, mode);
|
|
|
|
// Set layer
|
|
GtkLayerShell.Layer layer;
|
|
switch (ConfigModel.instance.control_center_layer) {
|
|
case Layer.BACKGROUND:
|
|
layer = GtkLayerShell.Layer.BACKGROUND;
|
|
break;
|
|
case Layer.BOTTOM:
|
|
layer = GtkLayerShell.Layer.BOTTOM;
|
|
break;
|
|
case Layer.TOP:
|
|
layer = GtkLayerShell.Layer.TOP;
|
|
break;
|
|
default:
|
|
case Layer.OVERLAY:
|
|
layer = GtkLayerShell.Layer.OVERLAY;
|
|
break;
|
|
}
|
|
GtkLayerShell.set_layer (this, layer);
|
|
}
|
|
|
|
// Set the box margins
|
|
box.set_margin_top (ConfigModel.instance.control_center_margin_top);
|
|
box.set_margin_start (ConfigModel.instance.control_center_margin_left);
|
|
box.set_margin_end (ConfigModel.instance.control_center_margin_right);
|
|
box.set_margin_bottom (ConfigModel.instance.control_center_margin_bottom);
|
|
|
|
// Anchor box to north/south edges as needed
|
|
Gtk.Align align_x = Gtk.Align.END;
|
|
PositionX pos_x = ConfigModel.instance.control_center_positionX;
|
|
if (pos_x == PositionX.NONE) pos_x = ConfigModel.instance.positionX;
|
|
switch (pos_x) {
|
|
case PositionX.LEFT:
|
|
align_x = Gtk.Align.START;
|
|
break;
|
|
case PositionX.CENTER:
|
|
align_x = Gtk.Align.CENTER;
|
|
break;
|
|
default:
|
|
case PositionX.RIGHT:
|
|
align_x = Gtk.Align.END;
|
|
break;
|
|
}
|
|
Gtk.Align align_y = Gtk.Align.START;
|
|
PositionY pos_y = ConfigModel.instance.control_center_positionY;
|
|
if (pos_y == PositionY.NONE) pos_y = ConfigModel.instance.positionY;
|
|
switch (pos_y) {
|
|
default:
|
|
case PositionY.TOP:
|
|
align_y = Gtk.Align.START;
|
|
// Set cc widget position
|
|
list_reverse = false;
|
|
list_align = Gtk.Align.START;
|
|
break;
|
|
case PositionY.CENTER:
|
|
align_y = Gtk.Align.CENTER;
|
|
// Set cc widget position
|
|
list_reverse = false;
|
|
list_align = Gtk.Align.START;
|
|
break;
|
|
case PositionY.BOTTOM:
|
|
align_y = Gtk.Align.END;
|
|
// Set cc widget position
|
|
list_reverse = true;
|
|
list_align = Gtk.Align.END;
|
|
break;
|
|
}
|
|
// Fit the ControlCenter to the monitor height
|
|
if (ConfigModel.instance.fit_to_screen) align_y = Gtk.Align.FILL;
|
|
// Set the ControlCenter alignment
|
|
box.set_halign (align_x);
|
|
box.set_valign (align_y);
|
|
|
|
list_box.set_valign (list_align);
|
|
list_box.set_sort_func (list_box_sort_func);
|
|
|
|
// Always set the size request in all events.
|
|
box.set_size_request (ConfigModel.instance.control_center_width,
|
|
ConfigModel.instance.control_center_height);
|
|
}
|
|
|
|
/**
|
|
* Returns < 0 if row1 should be before row2, 0 if they are equal
|
|
* and > 0 otherwise
|
|
*/
|
|
private int list_box_sort_func (Gtk.ListBoxRow row1, Gtk.ListBoxRow row2) {
|
|
int val = list_reverse ? 1 : -1;
|
|
|
|
var a_group = (NotificationGroup) row1;
|
|
var b_group = (NotificationGroup) row2;
|
|
|
|
// Check urgency before time
|
|
var a_urgency = a_group.get_is_urgent ();
|
|
var b_urgency = b_group.get_is_urgent ();
|
|
if (a_urgency != b_urgency) {
|
|
return a_urgency ? val : val * -1;
|
|
}
|
|
|
|
// Check time
|
|
var a_time = a_group.get_time ();
|
|
var b_time = b_group.get_time ();
|
|
if (a_time < 0 || b_time < 0) return 0;
|
|
// Sort the list in reverse if needed
|
|
if (a_time == b_time) return 0;
|
|
return a_time > b_time ? val : val * -1;
|
|
}
|
|
|
|
private void scroll_to_start (bool reverse) {
|
|
Gtk.ScrollType scroll_type = Gtk.ScrollType.START;
|
|
if (reverse) {
|
|
scroll_type = Gtk.ScrollType.END;
|
|
}
|
|
scrolled_window.scroll_child (scroll_type, false);
|
|
}
|
|
|
|
public uint notification_count () {
|
|
uint count = 0;
|
|
foreach (unowned Gtk.Widget widget in list_box.get_children ()) {
|
|
if (widget is NotificationGroup) {
|
|
count += ((NotificationGroup) widget).get_num_notifications ();
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
public void close_all_notifications () {
|
|
foreach (var w in list_box.get_children ()) {
|
|
NotificationGroup group = (NotificationGroup) w;
|
|
if (group != null) group.close_all_notifications ();
|
|
}
|
|
|
|
try {
|
|
swaync_daemon.subscribe_v2 (notification_count (),
|
|
swaync_daemon.get_dnd (),
|
|
get_visibility (),
|
|
swaync_daemon.inhibited);
|
|
} catch (Error e) {
|
|
stderr.printf (e.message + "\n");
|
|
}
|
|
|
|
if (ConfigModel.instance.hide_on_clear) {
|
|
this.set_visibility (false);
|
|
}
|
|
}
|
|
|
|
private void navigate_list (int i) {
|
|
unowned Gtk.Widget ? widget = list_box.get_children ().nth_data (i);
|
|
if (widget == null) {
|
|
// Try getting the last widget
|
|
if (list_reverse) {
|
|
widget = list_box.get_children ().nth_data (0);
|
|
} else {
|
|
int len = ((int) list_box.get_children ().length ()) - 1;
|
|
widget = list_box.get_children ().nth_data (len);
|
|
}
|
|
}
|
|
if (widget != null) {
|
|
widget.grab_focus ();
|
|
list_box.set_focus_child (widget);
|
|
}
|
|
}
|
|
|
|
private void on_visibility_change () {
|
|
// Updates all widgets on visibility change
|
|
foreach (var widget in widgets.data) {
|
|
widget.on_cc_visibility_change (visible);
|
|
}
|
|
|
|
if (this.visible) {
|
|
// Focus the first notification
|
|
list_position = list_reverse ?
|
|
(((int) list_box.get_children ().length ()) - 1) : 0;
|
|
if (list_position == uint.MAX) list_position = 0;
|
|
|
|
list_box.grab_focus ();
|
|
navigate_list (list_position);
|
|
foreach (var w in list_box.get_children ()) {
|
|
var group = (NotificationGroup) w;
|
|
if (group != null) group.update ();
|
|
}
|
|
}
|
|
swaync_daemon.subscribe_v2 (notification_count (),
|
|
noti_daemon.dnd,
|
|
this.visible,
|
|
swaync_daemon.inhibited);
|
|
}
|
|
|
|
public bool toggle_visibility () {
|
|
var cc_visibility = !this.visible;
|
|
if (this.visible != cc_visibility) {
|
|
this.set_visible (cc_visibility);
|
|
on_visibility_change ();
|
|
}
|
|
return cc_visibility;
|
|
}
|
|
|
|
public void set_visibility (bool visibility) {
|
|
if (this.visible == visibility) return;
|
|
this.set_visible (visibility);
|
|
on_visibility_change ();
|
|
}
|
|
|
|
public void close_notification (uint32 id, bool dismiss) {
|
|
unowned NotificationGroup group = null;
|
|
if (!noti_groups_id.lookup_extended (id, null, out group))return;
|
|
foreach (var w in group.get_notifications ()) {
|
|
var noti = (Notification) w;
|
|
if (noti != null && noti.param.applied_id == id) {
|
|
if (!dismiss) {
|
|
noti.remove_noti_timeout ();
|
|
noti.destroy ();
|
|
} else {
|
|
noti.close_notification (false);
|
|
group.remove_notification (noti);
|
|
}
|
|
noti_groups_id.remove (id);
|
|
break;
|
|
}
|
|
}
|
|
if (group.is_empty ()) {
|
|
if (group.name_id.length > 0) {
|
|
noti_groups_name.remove (group.name_id);
|
|
}
|
|
if (expanded_group == group) {
|
|
expanded_group = null;
|
|
fade_animate (1);
|
|
}
|
|
group.destroy ();
|
|
}
|
|
}
|
|
|
|
public void replace_notification (uint32 id, NotifyParams new_params) {
|
|
unowned NotificationGroup group = null;
|
|
if (noti_groups_id.lookup_extended (id, null, out group)) {
|
|
foreach (var w in group.get_notifications ()) {
|
|
var noti = (Notification) w;
|
|
if (noti != null && noti.param.applied_id == id) {
|
|
noti_groups_id.remove (id);
|
|
noti_groups_id.set (new_params.applied_id, group);
|
|
noti.replace_notification (new_params);
|
|
// Position the notification in the beginning of the list
|
|
list_box.invalidate_sort ();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add a new notification if the old one isn't visible
|
|
add_notification (new_params);
|
|
}
|
|
|
|
public void add_notification (NotifyParams param) {
|
|
var noti = new Notification.regular (param,
|
|
noti_daemon,
|
|
NotificationType.CONTROL_CENTER);
|
|
noti.grab_focus.connect ((w) => {
|
|
int i = list_box.get_children ().index (w);
|
|
if (list_position != uint.MAX && list_position != i) {
|
|
list_position = i;
|
|
}
|
|
});
|
|
noti.set_time ();
|
|
|
|
NotificationGroup ? group = null;
|
|
if (param.name_id.length > 0) {
|
|
noti_groups_name.lookup_extended (param.name_id, null, out group);
|
|
}
|
|
if (group == null) {
|
|
group = new NotificationGroup (param.name_id, param.display_name);
|
|
// Collapse other groups on expand
|
|
group.on_expand_change.connect ((expanded) => {
|
|
if (!expanded) {
|
|
fade_animate (1);
|
|
foreach (unowned Gtk.Widget child in list_box.get_children ()) {
|
|
child.set_sensitive (true);
|
|
}
|
|
return;
|
|
}
|
|
expanded_group = group;
|
|
expanded_group.set_sensitive (true);
|
|
fade_animate (0);
|
|
int y = expanded_group.get_relative_y (list_box);
|
|
if (y > 0) {
|
|
scroll_animate (y);
|
|
}
|
|
foreach (unowned Gtk.Widget child in list_box.get_children ()) {
|
|
NotificationGroup g = (NotificationGroup) child;
|
|
if (g != null && g != group) {
|
|
g.set_expanded (false);
|
|
if (g.only_single_notification ()) {
|
|
g.set_sensitive (false);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
if (param.name_id.length > 0) {
|
|
noti_groups_name.set (param.name_id, group);
|
|
}
|
|
list_box.add (group);
|
|
}
|
|
noti_groups_id.set (param.applied_id, group);
|
|
|
|
group.add_notification (noti);
|
|
list_box.invalidate_sort ();
|
|
scroll_to_start (list_reverse);
|
|
try {
|
|
swaync_daemon.subscribe_v2 (notification_count (),
|
|
swaync_daemon.get_dnd (),
|
|
get_visibility (),
|
|
swaync_daemon.inhibited);
|
|
} catch (Error e) {
|
|
stderr.printf (e.message + "\n");
|
|
}
|
|
|
|
// Keep focus on currently focused notification
|
|
list_box.grab_focus ();
|
|
navigate_list (++list_position);
|
|
}
|
|
|
|
public bool get_visibility () {
|
|
return this.visible;
|
|
}
|
|
|
|
/** Forces each notification EventBox to reload its style_context #27 */
|
|
private void reload_notifications_style () {
|
|
foreach (var c in list_box.get_children ()) {
|
|
NotificationGroup group = (NotificationGroup) c;
|
|
if (group != null) {
|
|
foreach (unowned var widget in group.get_notifications ()) {
|
|
Notification noti = (Notification) widget;
|
|
noti.reload_style_context ();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|