diff --git a/config.c b/config.c index 70be717..4cb1cd6 100644 --- a/config.c +++ b/config.c @@ -105,6 +105,7 @@ void init_default_style(struct mako_style *style) { style->actions = true; style->default_timeout = 0; style->ignore_timeout = false; + style->freeze = false; style->colors.background = 0x285577FF; style->colors.text = 0xFFFFFFFF; @@ -301,6 +302,11 @@ bool apply_style(struct mako_style *target, const struct mako_style *style) { target->spec.ignore_timeout = true; } + if (style->spec.freeze) { + target->freeze = style->freeze; + target->spec.freeze = true; + } + if (style->spec.colors.background) { target->colors.background = style->colors.background; target->spec.colors.background = true; @@ -623,6 +629,8 @@ static bool apply_style_option(struct mako_style *style, const char *name, } else if (strcmp(name, "ignore-timeout") == 0) { return spec->ignore_timeout = parse_boolean(value, &style->ignore_timeout); + } else if (strcmp(name, "freeze") == 0) { + return spec->freeze = parse_boolean(value, &style->freeze); } else if (strcmp(name, "group-by") == 0) { return spec->group_criteria_spec = parse_criteria_spec(value, &style->group_criteria_spec); diff --git a/criteria.c b/criteria.c index 9db3966..523a467 100644 --- a/criteria.c +++ b/criteria.c @@ -418,15 +418,56 @@ struct mako_criteria *global_criteria(struct mako_config *config) { return criteria; } -// Iterate through `criteria_list`, applying the style from each matching +static void timespec_from_ms(struct timespec *t, long time_ms) { + static const long ms = 1000000; + + t->tv_sec = time_ms / 1000; + t->tv_nsec = (time_ms % 1000) * ms; +} + +static void timespec_add(struct timespec *t, struct timespec *u) { + static const long s = 1000000000; + + t->tv_sec += u->tv_sec; + t->tv_nsec += u->tv_nsec; + + if (t->tv_nsec >= s) { + t->tv_nsec -= s; + ++t->tv_sec; + } +} + +static void timespec_sub(struct timespec *t, struct timespec *u) { + static const long s = 1000000000; + + t->tv_sec -= u->tv_sec; + t->tv_nsec += s; + t->tv_nsec -= u->tv_nsec; + + if (t->tv_nsec >= s) { + t->tv_nsec -= s; + } else { + --t->tv_sec; + } +} + +static void handle_notification_timer(void *data) { + struct mako_notification *notif = data; + struct mako_surface *surface = notif->surface; + notif->timer = NULL; + + close_notification(notif, MAKO_NOTIFICATION_CLOSE_EXPIRED, true); + set_dirty(surface); +} + +// Iterate through the criteria list, applying the style from each matching // criteria to `notif`. Returns the number of criteria that matched, or -1 if // a failure occurs. -ssize_t apply_each_criteria(struct wl_list *criteria_list, - struct mako_notification *notif) { +ssize_t apply_each_criteria(struct mako_state *state, struct mako_notification *notif) { ssize_t match_count = 0; struct mako_criteria *criteria; - wl_list_for_each(criteria, criteria_list, link) { + wl_list_for_each(criteria, &state->config.criteria, link) { if (!match_criteria(criteria, notif)) { continue; } @@ -447,6 +488,40 @@ ssize_t apply_each_criteria(struct wl_list *criteria_list, } } + int32_t expire_timeout = notif->requested_timeout; + if (expire_timeout < 0 || notif->style.ignore_timeout) { + expire_timeout = notif->style.default_timeout; + } + if (notif->frozen != notif->style.freeze) { + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + if (notif->style.freeze) { + notif->froze_at = now; + } else { + timespec_sub(&now, ¬if->froze_at); + timespec_add(¬if->at, &now); + } + notif->frozen = notif->style.freeze; + } + if (notif->frozen) { + expire_timeout = 0; + } + + if (expire_timeout > 0) { + struct timespec at = notif->at, delta; + timespec_from_ms(&delta, expire_timeout); + timespec_add(&at, &delta); + if (notif->timer) { + notif->timer->at = at; + } else { + notif->timer = add_event_loop_timer(&state->event_loop, &at, + handle_notification_timer, notif); + } + } else if (notif->timer) { + destroy_timer(notif->timer); + notif->timer = NULL; + } + if (!notif->surface) { notif->surface = create_surface(notif->state, notif->style.output, notif->style.layer, notif->style.anchor); diff --git a/dbus/mako.c b/dbus/mako.c index 4c95881..8f3c874 100644 --- a/dbus/mako.c +++ b/dbus/mako.c @@ -305,7 +305,7 @@ static void reapply_config(struct mako_state *state) { finish_style(¬if->style); init_empty_style(¬if->style); - apply_each_criteria(&state->config.criteria, notif); + apply_each_criteria(state, notif); // Having to do this for every single notification really hurts... but // it does do The Right Thing (tm). diff --git a/dbus/xdg.c b/dbus/xdg.c index 1f91431..650dff7 100644 --- a/dbus/xdg.c +++ b/dbus/xdg.c @@ -81,15 +81,6 @@ static int handle_get_capabilities(sd_bus_message *msg, void *data, return 0; } -static void handle_notification_timer(void *data) { - struct mako_notification *notif = data; - struct mako_surface *surface = notif->surface; - notif->timer = NULL; - - close_notification(notif, MAKO_NOTIFICATION_CLOSE_EXPIRED, true); - set_dirty(surface); -} - static int handle_notify(sd_bus_message *msg, void *data, sd_bus_error *ret_error) { struct mako_state *state = data; @@ -382,7 +373,7 @@ static int handle_notify(sd_bus_message *msg, void *data, insert_notification(state, notif); } - int match_count = apply_each_criteria(&state->config.criteria, notif); + int match_count = apply_each_criteria(state, notif); if (match_count == -1) { // We encountered an allocation failure or similar while applying // criteria. The notification may be partially matched, but the worst @@ -398,16 +389,6 @@ static int handle_notify(sd_bus_message *msg, void *data, return -1; } - int32_t expire_timeout = notif->requested_timeout; - if (expire_timeout < 0 || notif->style.ignore_timeout) { - expire_timeout = notif->style.default_timeout; - } - - if (expire_timeout > 0) { - notif->timer = add_event_loop_timer(&state->event_loop, expire_timeout, - handle_notification_timer, notif); - } - if (notif->style.icons) { notif->icon = create_icon(notif); } diff --git a/doc/mako.5.scd b/doc/mako.5.scd index 91378ba..f931a69 100644 --- a/doc/mako.5.scd +++ b/doc/mako.5.scd @@ -408,6 +408,12 @@ associated command-line option. Default: 0 +*freeze*=0|1 + Whether to freeze this notification's active timeout, stopping it from + progressing. This can be used for pausing notifications while you're away. + + Default: 0 + # COLORS Colors can be specified as _#RRGGBB_ or _#RRGGBBAA_. diff --git a/event-loop.c b/event-loop.c index 99c1f4a..92c627a 100644 --- a/event-loop.c +++ b/event-loop.c @@ -74,21 +74,6 @@ void finish_event_loop(struct mako_event_loop *loop) { } } -static void timespec_add(struct timespec *t, int delta_ms) { - static const long ms = 1000000, s = 1000000000; - - int delta_ms_low = delta_ms % 1000; - int delta_s_high = delta_ms / 1000; - - t->tv_sec += delta_s_high; - - t->tv_nsec += (long)delta_ms_low * ms; - if (t->tv_nsec >= s) { - t->tv_nsec -= s; - ++t->tv_sec; - } -} - static bool timespec_less(struct timespec *t1, struct timespec *t2) { if (t1->tv_sec != t2->tv_sec) { return t1->tv_sec < t2->tv_sec; @@ -124,7 +109,7 @@ static void update_event_loop_timer(struct mako_event_loop *loop) { } struct mako_timer *add_event_loop_timer(struct mako_event_loop *loop, - int delay_ms, mako_event_loop_timer_func_t func, void *data) { + struct timespec *at, mako_event_loop_timer_func_t func, void *data) { struct mako_timer *timer = calloc(1, sizeof(struct mako_timer)); if (timer == NULL) { fprintf(stderr, "allocation failed\n"); @@ -133,10 +118,9 @@ struct mako_timer *add_event_loop_timer(struct mako_event_loop *loop, timer->event_loop = loop; timer->func = func; timer->user_data = data; - wl_list_insert(&loop->timers, &timer->link); + timer->at = *at; - clock_gettime(CLOCK_MONOTONIC, &timer->at); - timespec_add(&timer->at, delay_ms); + wl_list_insert(&loop->timers, &timer->link); update_event_loop_timer(loop); return timer; diff --git a/include/config.h b/include/config.h index 013923a..1295b83 100644 --- a/include/config.h +++ b/include/config.h @@ -42,7 +42,7 @@ 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, icons, max_icon_size, icon_path, group_criteria_spec, invisible, history, - icon_location, max_visible, layer, output, anchor; + icon_location, max_visible, layer, output, anchor, freeze; struct { bool background, text, border, progress; } colors; @@ -76,6 +76,7 @@ struct mako_style { bool actions; int default_timeout; // in ms bool ignore_timeout; + bool freeze; struct { uint32_t background; diff --git a/include/criteria.h b/include/criteria.h index 2f03514..58c36ef 100644 --- a/include/criteria.h +++ b/include/criteria.h @@ -7,6 +7,7 @@ #include #include "config.h" #include "types.h" +#include "mako.h" struct mako_config; struct mako_notification; @@ -53,8 +54,7 @@ bool parse_criteria(const char *string, struct mako_criteria *criteria); bool apply_criteria_field(struct mako_criteria *criteria, char *token); struct mako_criteria *global_criteria(struct mako_config *config); -ssize_t apply_each_criteria(struct wl_list *criteria_list, - struct mako_notification *notif); +ssize_t apply_each_criteria(struct mako_state *state, struct mako_notification *notif); struct mako_criteria *create_criteria_from_notification( struct mako_notification *notif, struct mako_criteria_spec *spec); diff --git a/include/event-loop.h b/include/event-loop.h index 95c8d36..cd63e98 100644 --- a/include/event-loop.h +++ b/include/event-loop.h @@ -47,7 +47,7 @@ bool init_event_loop(struct mako_event_loop *loop, sd_bus *bus, void finish_event_loop(struct mako_event_loop *loop); int run_event_loop(struct mako_event_loop *loop); struct mako_timer *add_event_loop_timer(struct mako_event_loop *loop, - int delay_ms, mako_event_loop_timer_func_t func, void *data); + struct timespec *at, mako_event_loop_timer_func_t func, void *data); void destroy_timer(struct mako_timer *timer); diff --git a/include/notification.h b/include/notification.h index 9a395ba..5130898 100644 --- a/include/notification.h +++ b/include/notification.h @@ -48,6 +48,9 @@ struct mako_notification { struct mako_hotspot hotspot; struct mako_timer *timer; + struct timespec at; + struct timespec froze_at; + bool frozen; }; struct mako_action { diff --git a/notification.c b/notification.c index 8c0c8a7..5fc2e6c 100644 --- a/notification.c +++ b/notification.c @@ -66,6 +66,9 @@ void reset_notification(struct mako_notification *notif) { destroy_icon(notif->icon); notif->icon = NULL; + + clock_gettime(CLOCK_MONOTONIC, ¬if->at); + notif->frozen = false; } struct mako_notification *create_notification(struct mako_state *state) { diff --git a/render.c b/render.c index 5b68632..44b78a0 100644 --- a/render.c +++ b/render.c @@ -355,7 +355,7 @@ void render(struct mako_surface *surface, struct pool_buffer *buffer, int scale, // Immediately before rendering we need to re-match all of the criteria // so that matches against the anchor and output work even if the // output was automatically assigned by the compositor. - int rematch_count = apply_each_criteria(&state->config.criteria, notif); + int rematch_count = apply_each_criteria(state, notif); if (rematch_count == -1) { // We encountered an allocation failure or similar while applying // criteria. The notification may be partially matched, but the @@ -427,7 +427,7 @@ void render(struct mako_surface *surface, struct pool_buffer *buffer, int scale, struct mako_notification *hidden_notif = create_notification(state); hidden_notif->surface = surface; hidden_notif->hidden = true; - apply_each_criteria(&state->config.criteria, hidden_notif); + apply_each_criteria(state, hidden_notif); struct mako_style *style = &hidden_notif->style;