Remove notification replace flicker (#320)

This commit is contained in:
Erik Reider 2023-09-26 23:09:51 +02:00 committed by GitHub
parent 7de2c71b98
commit 0d368c2f07
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 181 additions and 120 deletions

View File

@ -174,7 +174,7 @@ namespace SwayNotificationCenter {
} else if (children.last ().data == noti) {
if (list_position > 0) list_position--;
}
close_notification (noti.param.applied_id);
close_notification (noti.param.applied_id, true);
}
break;
case "C":
@ -458,11 +458,11 @@ namespace SwayNotificationCenter {
on_visibility_change ();
}
public void close_notification (uint32 id, bool replaces = false) {
public void close_notification (uint32 id, bool dismiss) {
foreach (var w in list_box.get_children ()) {
var noti = (Notification) w;
if (noti != null && noti.param.applied_id == id) {
if (replaces) {
if (!dismiss) {
noti.remove_noti_timeout ();
noti.destroy ();
} else {
@ -474,8 +474,22 @@ namespace SwayNotificationCenter {
}
}
public void add_notification (NotifyParams param,
NotiDaemon noti_daemon) {
public void replace_notification (uint32 id, NotifyParams new_params) {
foreach (var w in list_box.get_children ()) {
var noti = (Notification) w;
if (noti != null && noti.param.applied_id == id) {
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);

View File

@ -62,9 +62,9 @@ namespace SwayNotificationCenter {
/** Method to close notification and send DISMISSED signal */
public void manually_close_notification (uint32 id, bool timeout)
throws DBusError, IOError {
NotificationWindow.instance.close_notification (id, false);
NotificationWindow.instance.close_notification (id, true);
if (!timeout) {
control_center.close_notification (id);
control_center.close_notification (id, true);
NotificationClosed (id, ClosedReasons.DISMISSED);
swaync_daemon.subscribe_v2 (control_center.notification_count (),
@ -170,11 +170,10 @@ namespace SwayNotificationCenter {
debug ("Notification: %s\n", param.to_string ());
// Replace notification logic
// Get the notification id to replace
uint32 replace_notification = 0;
if (id == replaces_id) {
param.replaces = true;
NotificationWindow.instance.close_notification (id, true);
control_center.close_notification (id, true);
replace_notification = id;
} else if (param.synchronous != null
&& param.synchronous.length > 0) {
// Tries replacing without replaces_id instead
@ -182,29 +181,38 @@ namespace SwayNotificationCenter {
// if there is any notification to replace
if (synchronous_ids.lookup_extended (
param.synchronous, null, out r_id)) {
param.replaces = true;
// Close the notification
NotificationWindow.instance.close_notification (r_id, true);
control_center.close_notification (r_id, true);
replace_notification = r_id;
}
synchronous_ids.set (param.synchronous, id);
}
// Only show popup notification if it is ENABLED or TRANSIENT
if ((state == NotificationStatusEnum.ENABLED || state == NotificationStatusEnum.TRANSIENT)
&& !control_center.get_visibility ()) {
&& !control_center.get_visibility ()
// Also check if urgency is Critical and not inhibited and dnd
if (param.urgency == UrgencyLevels.CRITICAL ||
(!dnd && !swaync_daemon.inhibited
&& param.urgency != UrgencyLevels.CRITICAL)) {
NotificationWindow.instance.add_notification (param, this);
&& (param.urgency == UrgencyLevels.CRITICAL
|| (!dnd && !swaync_daemon.inhibited && param.urgency != UrgencyLevels.CRITICAL))) {
if (replace_notification > 0) {
NotificationWindow.instance.replace_notification (replace_notification, param);
} else {
NotificationWindow.instance.add_notification (param);
}
} else if (replace_notification > 0) {
// Remove the old notification due to it not being replaced
NotificationWindow.instance.close_notification (replace_notification, false);
}
// Only add notification to CC if it isn't IGNORED and not transient/TRANSIENT
if (state != NotificationStatusEnum.IGNORED
&& state != NotificationStatusEnum.TRANSIENT
&& !param.transient) {
control_center.add_notification (param, this);
if (replace_notification > 0) {
control_center.replace_notification (replace_notification, param);
} else {
control_center.add_notification (param);
}
} else if (replace_notification > 0) {
// Remove the old notification due to it not being replaced
control_center.close_notification (replace_notification, false);
}
#if WANT_SCRIPTING
@ -292,8 +300,8 @@ namespace SwayNotificationCenter {
*/
[DBus (name = "CloseNotification")]
public void close_notification (uint32 id) throws DBusError, IOError {
NotificationWindow.instance.close_notification (id, false);
control_center.close_notification (id);
NotificationWindow.instance.close_notification (id, true);
control_center.close_notification (id, true);
NotificationClosed (id, ClosedReasons.CLOSED_BY_CLOSENOTIFICATION);
}

View File

@ -112,9 +112,6 @@ namespace SwayNotificationCenter {
public Array<Action> actions { get; set; }
/** If the notification replaces another */
public bool replaces { get; set; }
public NotifyParams (uint32 applied_id,
string app_name,
uint32 replaces_id,
@ -134,7 +131,6 @@ namespace SwayNotificationCenter {
this.expire_timeout = expire_timeout;
this.time = (int64) (get_real_time () * 0.000001);
this.replaces = false;
this.has_synch = false;
parse_hints ();

View File

@ -30,7 +30,7 @@ namespace SwayNotificationCenter {
[GtkChild]
unowned Gtk.Button close_button;
private Gtk.ButtonBox alt_actions_box = new Gtk.ButtonBox (Gtk.Orientation.HORIZONTAL);
private Gtk.ButtonBox ? alt_actions_box = null;
[GtkChild]
unowned Gtk.Label summary;
@ -70,8 +70,8 @@ namespace SwayNotificationCenter {
public bool is_timed { get; construct; default = false; }
public NotifyParams param { get; construct; }
public NotiDaemon noti_daemon { get; construct; }
public NotifyParams param { get; private set; }
public unowned NotiDaemon noti_daemon { get; construct; }
public NotificationType notification_type {
get;
@ -112,8 +112,9 @@ namespace SwayNotificationCenter {
NotiDaemon noti_daemon,
NotificationType notification_type) {
Object (noti_daemon: noti_daemon,
param: param,
notification_type: notification_type);
this.param = param;
build_noti ();
}
/** Show a timed notification */
@ -124,7 +125,6 @@ namespace SwayNotificationCenter {
uint timeout_low,
uint timeout_critical) {
Object (noti_daemon: noti_daemon,
param: param,
notification_type: notification_type,
is_timed: true,
timeout_delay: timeout,
@ -132,6 +132,8 @@ namespace SwayNotificationCenter {
timeout_critical_delay: timeout_critical,
number_of_body_lines: 5
);
this.param = param;
build_noti ();
}
construct {
@ -196,12 +198,80 @@ namespace SwayNotificationCenter {
});
this.transition_time = ConfigModel.instance.transition_time;
build_noti ();
if (is_timed) {
///
/// Signals
///
this.button_press_event.connect ((event) => {
if (event.button != Gdk.BUTTON_SECONDARY) return false;
// Right click
this.close_notification ();
return true;
});
// Adds CSS :hover selector to EventBox
default_action.enter_notify_event.connect ((event) => {
if (event.detail != Gdk.NotifyType.INFERIOR
&& event.window == default_action.get_window ()) {
default_action_in = true;
default_action_update_state ();
}
return true;
});
default_action.leave_notify_event.connect ((event) => {
if (event.detail != Gdk.NotifyType.INFERIOR
&& event.window == default_action.get_window ()) {
default_action_in = false;
default_action_update_state ();
}
return true;
});
default_action.unmap.connect (() => default_action_in = false);
close_button.clicked.connect (() => close_notification ());
this.event_box.enter_notify_event.connect ((event) => {
close_revealer.set_reveal_child (true);
remove_noti_timeout ();
return false;
});
this.event_box.leave_notify_event.connect ((event) => {
if (event.detail == Gdk.NotifyType.INFERIOR) return true;
close_revealer.set_reveal_child (false);
add_notification_timeout ();
this.size_allocate.connect (on_size_allocation);
}
return false;
});
this.carousel.page_changed.connect ((_, i) => {
if (i != this.carousel_empty_widget_index) return;
remove_noti_timeout ();
try {
noti_daemon.manually_close_notification (
param.applied_id, false);
} catch (Error e) {
print ("Error: %s\n", e.message);
this.destroy ();
}
});
inline_reply_entry.key_release_event.connect ((w, event_key) => {
switch (Gdk.keyval_name (event_key.keyval)) {
case "Return":
inline_reply_button.clicked ();
return true;
default:
return false;
}
});
inline_reply_button.clicked.connect (() => {
string text = inline_reply_entry.get_text ().strip ();
if (text.length == 0) return;
noti_daemon.NotificationReplied (param.applied_id, text);
// Dismiss notification without activating Action
action_clicked (null);
});
}
private void default_action_update_state () {
@ -235,50 +305,8 @@ namespace SwayNotificationCenter {
this.summary.set_text (param.summary ?? param.app_name);
this.summary.set_ellipsize (Pango.EllipsizeMode.END);
this.button_press_event.connect ((event) => {
if (event.button != Gdk.BUTTON_SECONDARY) return false;
// Right click
this.close_notification ();
return true;
});
// Adds CSS :hover selector to EventBox
default_action.enter_notify_event.connect ((event) => {
if (event.detail != Gdk.NotifyType.INFERIOR
&& event.window == default_action.get_window ()) {
default_action_in = true;
default_action_update_state ();
}
return true;
});
default_action.leave_notify_event.connect ((event) => {
if (event.detail != Gdk.NotifyType.INFERIOR
&& event.window == default_action.get_window ()) {
default_action_in = false;
default_action_update_state ();
}
return true;
});
default_action.unmap.connect (() => default_action_in = false);
close_revealer.set_transition_duration (this.transition_time);
close_button.clicked.connect (() => close_notification ());
this.event_box.enter_notify_event.connect ((event) => {
close_revealer.set_reveal_child (true);
remove_noti_timeout ();
return false;
});
this.event_box.leave_notify_event.connect ((event) => {
if (event.detail == Gdk.NotifyType.INFERIOR) return true;
close_revealer.set_reveal_child (false);
add_notification_timeout ();
return false;
});
this.revealer.set_transition_duration (this.transition_time);
this.carousel.set_animation_duration (this.transition_time);
@ -295,17 +323,8 @@ namespace SwayNotificationCenter {
this.carousel_empty_widget_index = 0;
break;
}
this.carousel.page_changed.connect ((_, i) => {
if (i != this.carousel_empty_widget_index) return;
remove_noti_timeout ();
try {
noti_daemon.manually_close_notification (
param.applied_id, false);
} catch (Error e) {
print ("Error: %s\n", e.message);
this.destroy ();
}
});
// Reset state
this.carousel.scroll_to (event_box);
#if HAVE_LATEST_LIBHANDY
this.carousel.allow_scroll_wheel = false;
#endif
@ -322,13 +341,16 @@ namespace SwayNotificationCenter {
this.show ();
if (param.replaces) {
Timeout.add (0, () => {
this.revealer.set_reveal_child (true);
} else {
Timeout.add (0, () => {
this.revealer.set_reveal_child (true);
return Source.REMOVE;
});
return Source.REMOVE;
});
remove_noti_timeout ();
this.size_allocate.disconnect (on_size_allocation);
if (is_timed) {
add_notification_timeout ();
this.size_allocate.connect (on_size_allocation);
}
}
@ -337,6 +359,9 @@ namespace SwayNotificationCenter {
this.body.set_lines (this.number_of_body_lines);
// Reset state
this.body_image.hide ();
// Removes all image tags and adds them to an array
if (text.length > 0) {
try {
@ -428,6 +453,7 @@ namespace SwayNotificationCenter {
}
public void click_alt_action (uint index) {
if (alt_actions_box == null) return;
List<weak Gtk.Widget> ? children = alt_actions_box.get_children ();
uint length = children.length ();
if (length == 0 || index >= length) return;
@ -459,6 +485,11 @@ namespace SwayNotificationCenter {
}
private void set_style_urgency () {
// Reset state
base_box.get_style_context ().remove_class ("low");
base_box.get_style_context ().remove_class ("normal");
base_box.get_style_context ().remove_class ("critical");
switch (param.urgency) {
case UrgencyLevels.LOW:
base_box.get_style_context ().add_class ("low");
@ -474,6 +505,8 @@ namespace SwayNotificationCenter {
}
private void set_inline_reply () {
// Reset state
inline_reply_box.hide ();
// Only show inline replies in popup notifications if the compositor
// supports ON_DEMAND layer shell keyboard interactivity
if (!ConfigModel.instance.notification_inline_replies
@ -501,32 +534,23 @@ namespace SwayNotificationCenter {
},
null);
inline_reply_entry.key_release_event.connect ((w, event_key) => {
switch (Gdk.keyval_name (event_key.keyval)) {
case "Return":
inline_reply_button.clicked ();
return true;
default:
return false;
}
});
inline_reply_button.set_label (param.inline_reply.name ?? "Reply");
inline_reply_button.clicked.connect (() => {
string text = inline_reply_entry.get_text ().strip ();
if (text.length == 0) return;
noti_daemon.NotificationReplied (param.applied_id, text);
// Dismiss notification without activating Action
action_clicked (null);
});
}
private void set_actions () {
// Reset state
foreach (Gtk.Widget child in base_box.get_children ()) {
if (child is Gtk.ScrolledWindow) {
child.destroy ();
}
}
// Check for security codes
string ? code = parse_body_codes ();
if (param.actions.length > 0 || code != null) {
var viewport = new Gtk.Viewport (null, null);
var scroll = new Gtk.ScrolledWindow (null, null);
alt_actions_box = new Gtk.ButtonBox (Gtk.Orientation.HORIZONTAL);
alt_actions_box.set_homogeneous (true);
alt_actions_box.set_layout (Gtk.ButtonBoxStyle.EXPAND);
@ -621,6 +645,11 @@ namespace SwayNotificationCenter {
});
}
public void replace_notification (NotifyParams new_params) {
this.param = new_params;
build_noti ();
}
private void set_icon () {
var image_visibility = ConfigModel.instance.image_visibility;
if (image_visibility == ImageVisibility.NEVER) {

View File

@ -170,7 +170,7 @@ namespace SwayNotificationCenter {
}
}
private void remove_notification (Notification ? noti, bool replaces) {
private void remove_notification (Notification ? noti, bool dismiss) {
// Remove notification and its destruction timeout
if (noti != null) {
#if HAVE_LATEST_GTK_LAYER_SHELL
@ -188,7 +188,7 @@ namespace SwayNotificationCenter {
noti.destroy ();
}
if (!replaces
if (dismiss
&& (!get_realized ()
|| !get_mapped ()
|| !(get_child () is Gtk.Widget)
@ -198,10 +198,9 @@ namespace SwayNotificationCenter {
}
}
public void add_notification (NotifyParams param,
NotiDaemon noti_daemon) {
public void add_notification (NotifyParams param) {
var noti = new Notification.timed (param,
noti_daemon,
swaync_daemon.noti_daemon,
NotificationType.POPUP,
ConfigModel.instance.timeout,
ConfigModel.instance.timeout_low,
@ -234,16 +233,31 @@ namespace SwayNotificationCenter {
scroll_to_start (list_reverse);
}
public void close_notification (uint32 id, bool replaces) {
public void close_notification (uint32 id, bool dismiss) {
foreach (var w in box.get_children ()) {
var noti = (Notification) w;
if (noti != null && noti.param.applied_id == id) {
remove_notification (noti, replaces);
remove_notification (noti, dismiss);
break;
}
}
}
public void replace_notification (uint32 id, NotifyParams new_params) {
foreach (var w in box.get_children ()) {
var noti = (Notification) w;
if (noti != null && noti.param.applied_id == id) {
noti.replace_notification (new_params);
// Position the notification in the beginning of the list
box.reorder_child (noti, (int) box.get_children ().length ());
return;
}
}
// Display a new notification if the old one isn't visible
add_notification (new_params);
}
public uint32 ? get_latest_notification () {
List<weak Gtk.Widget> children = box.get_children ();
if (children.is_empty ()) return null;

View File

@ -251,7 +251,7 @@ namespace SwayNotificationCenter {
/** Closes a specific notification with the `id` */
public void close_notification (uint32 id) throws DBusError, IOError {
noti_daemon.control_center.close_notification (id);
noti_daemon.control_center.close_notification (id, true);
}
/**