From e0aa9a8762e8c89fd993252518d805bd3eeb4c0d Mon Sep 17 00:00:00 2001 From: Rio Sanjaya Date: Wed, 17 Dec 2025 09:05:09 +0700 Subject: [PATCH 1/7] Define hotspot_at_offset() --- include/notification.h | 1 + notification.c | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/include/notification.h b/include/notification.h index 48c96f75..089bd9a9 100644 --- a/include/notification.h +++ b/include/notification.h @@ -79,6 +79,7 @@ struct mako_binding_context { typedef char *(*mako_format_func_t)(char variable, bool *markup, void *data); bool hotspot_at(struct mako_hotspot *hotspot, int32_t x, int32_t y); +bool hotspot_at_offset(struct mako_hotspot *hotspot, int32_t x, int32_t y, int32_t offset); void reset_notification(struct mako_notification *notif); struct mako_notification *create_notification(struct mako_state *state); diff --git a/notification.c b/notification.c index ceaddb18..3307e573 100644 --- a/notification.c +++ b/notification.c @@ -27,6 +27,13 @@ bool hotspot_at(struct mako_hotspot *hotspot, int32_t x, int32_t y) { y < hotspot->y + hotspot->height; } +bool hotspot_at_offset(struct mako_hotspot *hotspot, int32_t x, int32_t y, int32_t offset) { + return x >= hotspot->x - offset && + y >= hotspot->y - offset && + x < hotspot->x + hotspot->width + offset && + y < hotspot->y + hotspot->height + offset; +} + void reset_notification(struct mako_notification *notif) { struct mako_action *action, *tmp; wl_list_for_each_safe(action, tmp, ¬if->actions, link) { From 952cf26f4be7f6c523083b3b1b997e193225420b Mon Sep 17 00:00:00 2001 From: Rio Sanjaya Date: Wed, 17 Dec 2025 09:06:19 +0700 Subject: [PATCH 2/7] Handle pointer leaves surface: if surface is notification and within hotspot, then close the notification --- wayland.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/wayland.c b/wayland.c index eeefb30a..725588c9 100644 --- a/wayland.c +++ b/wayland.c @@ -234,6 +234,18 @@ static void pointer_handle_enter(void *data, struct wl_pointer *wl_pointer, static void pointer_handle_leave(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *wl_surface) { struct mako_seat *seat = data; + struct mako_state *state = seat->state; + + struct mako_notification *notif; + wl_list_for_each(notif, &state->notifications, link) { + if (hotspot_at_offset(¬if->hotspot, seat->pointer.x, seat->pointer.y, 10)) { + struct mako_surface *surface = notif->surface; + close_notification(notif, MAKO_NOTIFICATION_CLOSE_DISMISSED, true); + set_dirty(surface); + break; + } + } + seat->pointer.surface = NULL; } From 09fcd174050ffdc97c5cdc6e77d3d2b103e22340 Mon Sep 17 00:00:00 2001 From: Rio Sanjaya Date: Wed, 17 Dec 2025 09:10:30 +0700 Subject: [PATCH 3/7] Define hover_dismiss_timer --- include/mako.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/mako.h b/include/mako.h index dc2c372a..f1b5274d 100644 --- a/include/mako.h +++ b/include/mako.h @@ -47,6 +47,7 @@ struct mako_surface { struct mako_state { struct mako_config config; struct mako_event_loop event_loop; + struct mako_timer *hover_dismiss_timer; sd_bus *bus; sd_bus_slot *xdg_slot, *mako_slot; From e89b94cff55eb930d8800f209f9fa39dfd8ed685 Mon Sep 17 00:00:00 2001 From: Rio Sanjaya Date: Wed, 17 Dec 2025 09:13:25 +0700 Subject: [PATCH 4/7] Handle pointer enter surface: cancel hover_dismiss_timer if active --- .gitignore | 2 ++ wayland.c | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/.gitignore b/.gitignore index d73983bf..afdd8a95 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ +.cache/ +.vscode/ /subprojects/ diff --git a/wayland.c b/wayland.c index 725588c9..322ab549 100644 --- a/wayland.c +++ b/wayland.c @@ -198,6 +198,11 @@ static void pointer_handle_enter(void *data, struct wl_pointer *wl_pointer, struct mako_seat *seat = data; struct mako_state *state = seat->state; + if (state->hover_dismiss_timer != NULL) { + destroy_timer(state->hover_dismiss_timer); + state->hover_dismiss_timer = NULL; + } + seat->pointer.x = wl_fixed_to_int(surface_x); seat->pointer.y = wl_fixed_to_int(surface_y); seat->pointer.surface = get_surface(state, wl_surface); From fbec5dce717da4c7f4546ac076cf03af5d994e26 Mon Sep 17 00:00:00 2001 From: Rio Sanjaya Date: Wed, 17 Dec 2025 09:15:48 +0700 Subject: [PATCH 5/7] Add hover_dismiss_timer to pointer_handle_leave() --- wayland.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/wayland.c b/wayland.c index 322ab549..915bf20d 100644 --- a/wayland.c +++ b/wayland.c @@ -202,7 +202,7 @@ static void pointer_handle_enter(void *data, struct wl_pointer *wl_pointer, destroy_timer(state->hover_dismiss_timer); state->hover_dismiss_timer = NULL; } - + seat->pointer.x = wl_fixed_to_int(surface_x); seat->pointer.y = wl_fixed_to_int(surface_y); seat->pointer.surface = get_surface(state, wl_surface); @@ -236,6 +236,18 @@ static void pointer_handle_enter(void *data, struct wl_pointer *wl_pointer, } } +static void handle_notification_hover_dismiss_timer(void *data) { + struct mako_notification *notif = data; + struct mako_surface *surface = notif->surface; + + if (notif->state->hover_dismiss_timer != NULL) { + notif->state->hover_dismiss_timer = NULL; + } + + close_notification(notif, MAKO_NOTIFICATION_CLOSE_EXPIRED, true); + set_dirty(surface); +} + static void pointer_handle_leave(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *wl_surface) { struct mako_seat *seat = data; @@ -244,9 +256,8 @@ static void pointer_handle_leave(void *data, struct wl_pointer *wl_pointer, struct mako_notification *notif; wl_list_for_each(notif, &state->notifications, link) { if (hotspot_at_offset(¬if->hotspot, seat->pointer.x, seat->pointer.y, 10)) { - struct mako_surface *surface = notif->surface; - close_notification(notif, MAKO_NOTIFICATION_CLOSE_DISMISSED, true); - set_dirty(surface); + notif->state->hover_dismiss_timer = add_event_loop_timer(¬if->state->event_loop, 500, + handle_notification_hover_dismiss_timer, notif); break; } } From 95108fd743c16bda943157fe3268c6120cf9305d Mon Sep 17 00:00:00 2001 From: Rio Sanjaya Date: Wed, 17 Dec 2025 09:26:34 +0700 Subject: [PATCH 6/7] Add hover-to-dismiss config: - config.h: Add hover_to_dismiss_timeout to mako_style_spec and mako_style - config.c: Initialize hover_to_dismiss_timeout - wayland.c: Test if hover_to_dismiss_timeout > 0 before check pointer leaving notification hotspot surface - Various completion scripts and doc string --- config.c | 13 +++++++++++++ contrib/completions/bash/mako | 1 + contrib/completions/fish/mako.fish | 1 + contrib/completions/zsh/_mako | 1 + doc/mako.5.scd | 6 ++++++ include/config.h | 3 ++- main.c | 1 + wayland.c | 12 ++++++++---- 8 files changed, 33 insertions(+), 5 deletions(-) diff --git a/config.c b/config.c index 817f7e97..332aded4 100644 --- a/config.c +++ b/config.c @@ -109,6 +109,7 @@ void init_default_style(struct mako_style *style) { style->actions = true; style->default_timeout = 0; + style->hover_to_dismiss_timeout = 0; style->ignore_timeout = false; style->colors.background = 0x285577FF; @@ -306,6 +307,11 @@ bool apply_style(struct mako_style *target, const struct mako_style *style) { target->spec.default_timeout = true; } + if (style->spec.hover_to_dismiss_timeout) { + target->hover_to_dismiss_timeout = style->hover_to_dismiss_timeout; + target->spec.hover_to_dismiss_timeout = true; + } + if (style->spec.ignore_timeout) { target->ignore_timeout = style->ignore_timeout; target->spec.ignore_timeout = true; @@ -421,6 +427,7 @@ bool apply_superset_style( target->spec.icons = true; target->spec.max_icon_size = true; target->spec.default_timeout = true; + target->spec.hover_to_dismiss_timeout = true; target->spec.markup = true; target->spec.actions = true; target->spec.history = true; @@ -468,6 +475,8 @@ bool apply_superset_style( target->max_icon_size = max(style->max_icon_size, target->max_icon_size); target->default_timeout = max(style->default_timeout, target->default_timeout); + target->hover_to_dismiss_timeout = + max(style->hover_to_dismiss_timeout, target->hover_to_dismiss_timeout); target->markup |= style->markup; target->actions |= style->actions; @@ -639,6 +648,9 @@ static bool apply_style_option(struct mako_style *style, const char *name, } else if (strcmp(name, "default-timeout") == 0) { return spec->default_timeout = parse_int_ge(value, &style->default_timeout, 0); + } else if (strcmp(name, "hover-to-dismiss-timeout") == 0) { + return spec->hover_to_dismiss_timeout = + parse_int_ge(value, &style->hover_to_dismiss_timeout, 0); } else if (strcmp(name, "ignore-timeout") == 0) { return spec->ignore_timeout = parse_boolean(value, &style->ignore_timeout); @@ -920,6 +932,7 @@ int parse_config_arguments(struct mako_config *config, int argc, char **argv) { {"max-history", required_argument, 0, 0}, {"history", required_argument, 0, 0}, {"default-timeout", required_argument, 0, 0}, + {"hover-to-dismiss-timeout", required_argument, 0, 0}, {"ignore-timeout", required_argument, 0, 0}, {"output", required_argument, 0, 0}, {"layer", required_argument, 0, 0}, diff --git a/contrib/completions/bash/mako b/contrib/completions/bash/mako index bdb5bbb6..e96919d0 100644 --- a/contrib/completions/bash/mako +++ b/contrib/completions/bash/mako @@ -33,6 +33,7 @@ _mako() '--history' '--sort' '--default-timeout' + '--hover-to-dismiss-timeout' '--ignore-timeout' '--output' '--layer' diff --git a/contrib/completions/fish/mako.fish b/contrib/completions/fish/mako.fish index c64d203d..cf24b861 100644 --- a/contrib/completions/fish/mako.fish +++ b/contrib/completions/fish/mako.fish @@ -32,6 +32,7 @@ complete -c mako -l max-history -d 'Max size of history buffer' -x complete -c mako -l history -d 'Add expired notifications to history' -xa "1 0" complete -c mako -l sort -d 'Set notification sorting method' -x complete -c mako -l default-timeout -d 'Notification timeout in ms' -x +complete -c mako -l hover-to-dismiss-timeout -d 'Default hover-to-dismiss timeout in milliseconds.' -x complete -c mako -l ignore-timeout -d 'Enable notification timeout or not' -xa "1 0" complete -c mako -l output -d 'Show notifications on this output' -xa '(complete_outputs)' complete -c mako -l layer -d 'Show notifications on this layer' -x diff --git a/contrib/completions/zsh/_mako b/contrib/completions/zsh/_mako index 4a1f01c2..a2dad6c1 100644 --- a/contrib/completions/zsh/_mako +++ b/contrib/completions/zsh/_mako @@ -26,6 +26,7 @@ _arguments \ '--max-history[Max size of history buffer.]:historical notifications:' \ '--history[Add expired notification to history.]:history:' \ '--default-timeout[Default timeout in milliseconds.]:timeout (ms):' \ + '--hover-to-dismiss-timeout[Default hover-to-dismiss timeout in milliseconds.]:timeout (ms):' \ '--ignore-timeout[If set, mako will ignore the expire timeout sent by notifications and use the one provided by default-timeout instead.]:Use default timeout:(0 1)' \ '--output[Show notifications on this output.]:name:' \ '--layer[Arrange notifications at this layer.]:layer:(background bottom top overlay)' \ diff --git a/doc/mako.5.scd b/doc/mako.5.scd index 18eb3fba..6e929b06 100644 --- a/doc/mako.5.scd +++ b/doc/mako.5.scd @@ -268,6 +268,12 @@ Default when grouped: (%g) %s\\n%b Default: 0 +*hover-to-dismiss-timeout*=_timeout_ + Set the default hover-to-dismiss timeout to _timeout_ in milliseconds. To disable the + timeout, set it to zero. + + Default: 0 + *ignore-timeout*=0|1 If set, mako will ignore the expire timeout sent by notifications and use the one provided by _default-timeout_ instead. diff --git a/include/config.h b/include/config.h index 48769898..8eab2f1c 100644 --- a/include/config.h +++ b/include/config.h @@ -40,7 +40,7 @@ enum mako_icon_location { // structs are also mirrored. struct mako_style_spec { bool width, height, outer_margin, margin, padding, border_size, border_radius, font, - markup, format, text_alignment, actions, default_timeout, ignore_timeout, + markup, format, text_alignment, actions, default_timeout, hover_to_dismiss_timeout, ignore_timeout, icons, max_icon_size, icon_path, icon_border_radius, group_criteria_spec, invisible, history, icon_location, max_visible, layer, output, anchor; struct { @@ -76,6 +76,7 @@ struct mako_style { bool actions; int default_timeout; // in ms + int hover_to_dismiss_timeout; // in ms bool ignore_timeout; struct { diff --git a/main.c b/main.c index c273a4b8..1df02239 100644 --- a/main.c +++ b/main.c @@ -52,6 +52,7 @@ static const char usage[] = " and/or priority in ascending(+) or\n" " descending(-) order.\n" " --default-timeout Default timeout in milliseconds.\n" + " --hover-to-dismiss-timeout Default hover-to-dismiss timeout in milliseconds.\n" " --ignore-timeout <0|1> Enable/disable notification timeout.\n" " --output Show notifications on this output.\n" " --layer Arrange notifications at this layer.\n" diff --git a/wayland.c b/wayland.c index 915bf20d..f9549baf 100644 --- a/wayland.c +++ b/wayland.c @@ -252,13 +252,17 @@ static void pointer_handle_leave(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *wl_surface) { struct mako_seat *seat = data; struct mako_state *state = seat->state; + struct mako_style *style = &state->config.superstyle; struct mako_notification *notif; wl_list_for_each(notif, &state->notifications, link) { - if (hotspot_at_offset(¬if->hotspot, seat->pointer.x, seat->pointer.y, 10)) { - notif->state->hover_dismiss_timer = add_event_loop_timer(¬if->state->event_loop, 500, - handle_notification_hover_dismiss_timer, notif); - break; + if (style->hover_to_dismiss_timeout > 0) { + if (hotspot_at_offset(¬if->hotspot, seat->pointer.x, seat->pointer.y, 10)) { + notif->state->hover_dismiss_timer = add_event_loop_timer( + ¬if->state->event_loop, style->hover_to_dismiss_timeout, + handle_notification_hover_dismiss_timer, notif); + break; + } } } From 87f824bbe9425f21356a974af9d9993a31be6517 Mon Sep 17 00:00:00 2001 From: Rio Sanjaya Date: Wed, 17 Dec 2025 09:28:23 +0700 Subject: [PATCH 7/7] Clean up .gitignore --- .gitignore | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitignore b/.gitignore index afdd8a95..d73983bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1 @@ -.cache/ -.vscode/ /subprojects/