Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package mako for openSUSE:Factory checked in at 2021-07-12 21:40:20 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/mako (Old) and /work/SRC/openSUSE:Factory/.mako.new.2625 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "mako" Mon Jul 12 21:40:20 2021 rev:6 rq:905773 version:1.6 Changes: -------- --- /work/SRC/openSUSE:Factory/mako/mako.changes 2021-05-04 22:01:00.116597794 +0200 +++ /work/SRC/openSUSE:Factory/.mako.new.2625/mako.changes 2021-07-12 21:40:49.715828912 +0200 @@ -1,0 +2,13 @@ +Mon Jul 12 07:10:54 UTC 2021 - Michael Vetter <[email protected]> + +- Update to 1.6: + * Modes allow to conditonally change options at runtime, and allow to + setup things like a "do not disturb" mode. + * Add support for synchronous hints, to easily replace an existing + notification from a script. + * Add an "exec" binding to execute a command + * Add "on-notify" bindings to trigger an action when a notification + is opened. + * Several small improvments and bugfixes: + +------------------------------------------------------------------- Old: ---- v1.5.tar.gz New: ---- v1.6.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ mako.spec ++++++ --- /var/tmp/diff_new_pack.KAOAiG/_old 2021-07-12 21:40:50.179825255 +0200 +++ /var/tmp/diff_new_pack.KAOAiG/_new 2021-07-12 21:40:50.183825224 +0200 @@ -17,7 +17,7 @@ Name: mako -Version: 1.5 +Version: 1.6 Release: 0 Summary: A Wayland notification daemon License: MIT ++++++ v1.5.tar.gz -> v1.6.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mako-1.5/.builds/alpine.yml new/mako-1.6/.builds/alpine.yml --- old/mako-1.5/.builds/alpine.yml 2021-05-03 19:32:05.000000000 +0200 +++ new/mako-1.6/.builds/alpine.yml 2021-07-11 10:52:44.000000000 +0200 @@ -1,4 +1,4 @@ -image: alpine/latest +image: alpine/edge packages: - elogind-dev - meson diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mako-1.5/README.md new/mako-1.6/README.md --- old/mako-1.5/README.md 2021-05-03 19:32:05.000000000 +0200 +++ new/mako-1.6/README.md 2021-07-11 10:52:44.000000000 +0200 @@ -6,9 +6,9 @@ <img src="https://sr.ht/meoc.png" alt="mako screenshot"> </p> -mako implements the [GNOME Desktop Notifications Specification][gnome-draft]. +mako implements the [FreeDesktop Notifications Specification][spec]. -Feel free to join the IRC channel: ##emersion on irc.freenode.net. +Feel free to join the IRC channel: #emersion on irc.libera.chat. ## Running @@ -67,5 +67,5 @@ MIT -[gnome-draft]: https://developer.gnome.org/notification-spec/ +[spec]: https://specifications.freedesktop.org/notification-spec/notification-spec-latest.html [basu]: https://github.com/emersion/basu diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mako-1.5/config.c new/mako-1.6/config.c --- old/mako-1.5/config.c 2021-05-03 19:32:05.000000000 +0200 +++ new/mako-1.6/config.c 2021-07-11 10:52:44.000000000 +0200 @@ -58,11 +58,6 @@ config->max_history = 5; config->sort_criteria = MAKO_SORT_CRITERIA_TIME; config->sort_asc = 0; - - config->button_bindings.left = MAKO_BINDING_INVOKE_DEFAULT_ACTION; - config->button_bindings.right = MAKO_BINDING_DISMISS; - config->button_bindings.middle = MAKO_BINDING_NONE; - config->touch = MAKO_BINDING_DISMISS; } void finish_config(struct mako_config *config) { @@ -126,6 +121,11 @@ style->anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; + style->button_bindings.left.action = MAKO_BINDING_INVOKE_DEFAULT_ACTION; + style->button_bindings.right.action = MAKO_BINDING_DISMISS; + style->button_bindings.middle.action = MAKO_BINDING_NONE; + style->touch_binding.action = MAKO_BINDING_DISMISS; + // Everything in the default config is explicitly specified. memset(&style->spec, true, sizeof(struct mako_style_spec)); } @@ -134,13 +134,32 @@ memset(style, 0, sizeof(struct mako_style)); } +static void finish_binding(struct mako_binding *binding) { + free(binding->command); +} + void finish_style(struct mako_style *style) { + finish_binding(&style->button_bindings.left); + finish_binding(&style->button_bindings.middle); + finish_binding(&style->button_bindings.right); + finish_binding(&style->touch_binding); + finish_binding(&style->notify_binding); free(style->icon_path); free(style->font); free(style->format); free(style->output); } +static void copy_binding(struct mako_binding *dst, + const struct mako_binding *src) { + finish_binding(dst); + + *dst = *src; + if (src->command != NULL) { + dst->command = strdup(src->command); + } +} + // Update `target` with the values specified in `style`. If a failure occurs, // `target` will remain unchanged. bool apply_style(struct mako_style *target, const struct mako_style *style) { @@ -335,6 +354,29 @@ target->spec.max_visible = true; } + if (style->spec.button_bindings.left) { + copy_binding(&target->button_bindings.left, &style->button_bindings.left); + target->spec.button_bindings.left = true; + } + if (style->spec.button_bindings.middle) { + copy_binding(&target->button_bindings.middle, &style->button_bindings.middle); + target->spec.button_bindings.middle = true; + } + if (style->spec.button_bindings.right) { + copy_binding(&target->button_bindings.right, &style->button_bindings.right); + target->spec.button_bindings.right = true; + } + + if (style->spec.touch_binding) { + copy_binding(&target->touch_binding, &style->touch_binding); + target->spec.touch_binding = true; + } + + if (style->spec.notify_binding) { + copy_binding(&target->notify_binding, &style->notify_binding); + target->spec.notify_binding = true; + } + return true; } @@ -414,10 +456,10 @@ memcpy(¤t_specifier, format_pos, 2); if (!strstr(target->format, current_specifier)) { memcpy(target_format_pos, format_pos, 2); + target_format_pos += 2; // This needs to go to the next slot. } ++format_pos; // Enough to move to the next match. - target_format_pos += 2; // This needs to go to the next slot. } } } @@ -444,37 +486,6 @@ return false; } return true; - } else if (strncmp(name, "on-button-", 10) == 0 - || strcmp(name, "on-touch") == 0) { - - enum mako_binding action; - - if (strcmp(value, "none") == 0) { - action = MAKO_BINDING_NONE; - } else if (strcmp(value, "dismiss") == 0) { - action = MAKO_BINDING_DISMISS; - } else if (strcmp(value, "dismiss-all") == 0) { - action = MAKO_BINDING_DISMISS_ALL; - } else if (strcmp(value, "dismiss-group") == 0) { - action = MAKO_BINDING_DISMISS_GROUP; - } else if (strcmp(value, "invoke-default-action") == 0) { - action = MAKO_BINDING_INVOKE_DEFAULT_ACTION; - } else { - return false; - } - - if (strcmp(name, "on-button-left") == 0) { - config->button_bindings.left = action; - } else if (strcmp(name, "on-button-right") == 0) { - config->button_bindings.right = action; - } else if (strcmp(name, "on-button-middle") == 0) { - config->button_bindings.middle = action; - } else if (strcmp(name, "on-touch") == 0) { - config->touch = action; - } else { - return false; - } - return true; } else if (strcmp(name, "max-history") == 0) { return parse_int(value, &config->max_history); } @@ -482,6 +493,10 @@ return false; } +static bool has_prefix(const char *str, const char *prefix) { + return strncmp(str, prefix, strlen(prefix)) == 0; +} + static bool apply_style_option(struct mako_style *style, const char *name, const char *value) { struct mako_style_spec *spec = &style->spec; @@ -602,6 +617,45 @@ return true; } else if (strcmp(name, "anchor") == 0) { return spec->anchor = parse_anchor(value, &style->anchor); + } else if (has_prefix(name, "on-")) { + struct mako_binding binding = {0}; + if (strcmp(value, "none") == 0) { + binding.action = MAKO_BINDING_NONE; + } else if (strcmp(value, "dismiss") == 0) { + binding.action = MAKO_BINDING_DISMISS; + } else if (strcmp(value, "dismiss-all") == 0) { + binding.action = MAKO_BINDING_DISMISS_ALL; + } else if (strcmp(value, "dismiss-group") == 0) { + binding.action = MAKO_BINDING_DISMISS_GROUP; + } else if (strcmp(value, "invoke-default-action") == 0) { + binding.action = MAKO_BINDING_INVOKE_DEFAULT_ACTION; + } else if (has_prefix(value, "exec ")) { + binding.action = MAKO_BINDING_EXEC; + binding.command = strdup(value + strlen("exec ")); + } else { + return false; + } + + if (strcmp(name, "on-button-left") == 0) { + copy_binding(&style->button_bindings.left, &binding); + style->spec.button_bindings.left = true; + } else if (strcmp(name, "on-button-right") == 0) { + copy_binding(&style->button_bindings.right, &binding); + style->spec.button_bindings.right = true; + } else if (strcmp(name, "on-button-middle") == 0) { + copy_binding(&style->button_bindings.middle, &binding); + style->spec.button_bindings.middle = true; + } else if (strcmp(name, "on-touch") == 0) { + copy_binding(&style->touch_binding, &binding); + style->spec.touch_binding = true; + } else if (strcmp(name, "on-notify") == 0) { + copy_binding(&style->notify_binding, &binding); + style->spec.notify_binding = true; + } else { + return false; + } + + return true; } return false; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mako-1.5/contrib/completions/fish/mako.fish new/mako-1.6/contrib/completions/fish/mako.fish --- old/mako-1.5/contrib/completions/fish/mako.fish 2021-05-03 19:32:05.000000000 +0200 +++ new/mako-1.6/contrib/completions/fish/mako.fish 2021-07-11 10:52:44.000000000 +0200 @@ -27,6 +27,8 @@ complete -c mako -l format -d 'Format string' -x complete -c mako -l hidden-format -d 'Hidden format string' -x complete -c mako -l max-visible -d 'Max visible notifications' -x +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 ignore-timeout -d 'Enable notification timeout or not' -xa "1 0" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mako-1.5/contrib/completions/fish/makoctl.fish new/mako-1.6/contrib/completions/fish/makoctl.fish --- old/mako-1.5/contrib/completions/fish/makoctl.fish 1970-01-01 01:00:00.000000000 +0100 +++ new/mako-1.6/contrib/completions/fish/makoctl.fish 2021-07-11 10:52:44.000000000 +0200 @@ -0,0 +1,24 @@ +function __fish_makoctl_complete_no_subcommand + for i in (commandline -opc) + if contains -- $i dismiss restore invoke menu list reload help + return 1 + end + end + return 0 +end + +complete -c makoctl -n '__fish_makoctl_complete_no_subcommand' -a dismiss -d 'Dismiss notification (the last one if none is given)' -x +complete -c makoctl -n '__fish_makoctl_complete_no_subcommand' -a restore -d 'Restore the most recently expired notification from the history buffer' -x +complete -c makoctl -n '__fish_makoctl_complete_no_subcommand' -a invoke -d 'Invoke an action on the notification (the last one if none is given)' -x +complete -c makoctl -n '__fish_makoctl_complete_no_subcommand' -a menu -d 'Use a program to select one action to be invoked on the notification (the last one if none is given)' -x +complete -c makoctl -n '__fish_makoctl_complete_no_subcommand' -a list -d 'List notifications' -x +complete -c makoctl -n '__fish_makoctl_complete_no_subcommand' -a reload -d 'Reload the configuration file' -x +complete -c makoctl -n '__fish_makoctl_complete_no_subcommand' -a help -d 'Show help message and quit' -x + +complete -c makoctl -n '__fish_seen_subcommand_from dismiss' -s a -l all -d "Dismiss all notifications" -x +complete -c makoctl -n '__fish_seen_subcommand_from dismiss' -s g -l group -d "Dismiss all the notifications in the last notification's group" -x +complete -c makoctl -n '__fish_seen_subcommand_from dismiss' -s n -d "Dismiss the notification with the given id" -x +complete -c makoctl -n '__fish_seen_subcommand_from invoke' -s n -d "Invoke an action on the notification with the given id" -x +complete -c makoctl -n '__fish_seen_subcommand_from menu' -s n -d "Use a program to select one action on the notification with the given id" -x +complete -c makoctl -n '__fish_seen_subcommand_from menu' -a "(__fish_complete_command)" -x + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mako-1.5/contrib/completions/meson.build new/mako-1.6/contrib/completions/meson.build --- old/mako-1.5/contrib/completions/meson.build 2021-05-03 19:32:05.000000000 +0200 +++ new/mako-1.6/contrib/completions/meson.build 2021-07-11 10:52:44.000000000 +0200 @@ -10,7 +10,8 @@ endif if get_option('fish-completions') - fish_files = files('fish/mako.fish') + fish_files = files('fish/mako.fish', 'fish/makoctl.fish') + fish_comp = dependency('fish', required: false) if fish_comp.found() fish_install_dir = fish_comp.get_pkgconfig_variable('completionsdir') else diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mako-1.5/contrib/completions/zsh/_mako new/mako-1.6/contrib/completions/zsh/_mako --- old/mako-1.5/contrib/completions/zsh/_mako 2021-05-03 19:32:05.000000000 +0200 +++ new/mako-1.6/contrib/completions/zsh/_mako 2021-07-11 10:52:44.000000000 +0200 @@ -27,5 +27,5 @@ '--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)' \ - '--anchor[Position on output to put notifications.]:position:(top-right bottom-right bottom-center bottom-left top-left top-center)' \ - '--sort[Sorts incoming notifications by time and/or priority in ascending(+) or descending(-) order.]:sort pattern:' + '--anchor[Position on output to put notifications.]:position:(top-right bottom-right bottom-center bottom-left top-left top-center center-right center-left center)' \ + '--sort[Sort incoming notifications by time and/or priority in ascending(+) or descending(-) order.]:sort pattern:' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mako-1.5/contrib/completions/zsh/_makoctl new/mako-1.6/contrib/completions/zsh/_makoctl --- old/mako-1.5/contrib/completions/zsh/_makoctl 2021-05-03 19:32:05.000000000 +0200 +++ new/mako-1.6/contrib/completions/zsh/_makoctl 2021-07-11 10:52:44.000000000 +0200 @@ -3,11 +3,11 @@ local -a makoctl_cmds makoctl_cmds=( - 'dismiss:Dismisses notification (first by default)' + 'dismiss:Dismiss notification (first by default)' 'restore:Restore the most recently expired notification from the history buffer' - 'invoke:Invokes an action on the first notification. If action is not specified, invokes the default action' + 'invoke:Invoke an action on the first notification. If action is not specified, invoke the default action' 'list:Retrieve a list of current notifications' - 'reload:Reloads the configuration file' + 'reload:Reload the configuration file' 'help:Show help message and quit' ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mako-1.5/criteria.c new/mako-1.6/criteria.c --- old/mako-1.5/criteria.c 2021-05-03 19:32:05.000000000 +0200 +++ new/mako-1.6/criteria.c 2021-07-11 10:52:44.000000000 +0200 @@ -40,6 +40,7 @@ regfree(&criteria->body_pattern); free(criteria->raw_string); free(criteria->output); + free(criteria->mode); free(criteria); } @@ -151,6 +152,10 @@ return false; } + if (spec.mode && strcmp(criteria->mode, notif->state->current_mode) != 0) { + return false; + } + return true; } @@ -351,6 +356,10 @@ criteria->output = strdup(value); criteria->spec.output = true; return true; + } else if (strcmp(key, "mode") == 0) { + criteria->mode = strdup(value); + criteria->spec.mode = true; + return true; } else { // Anything left must be one of the boolean fields, defined using // standard syntax. Continue on. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mako-1.5/dbus/mako.c new/mako-1.6/dbus/mako.c --- old/mako-1.5/dbus/mako.c 2021-05-03 19:32:05.000000000 +0200 +++ new/mako-1.6/dbus/mako.c 2021-07-11 10:52:44.000000000 +0200 @@ -109,7 +109,7 @@ struct mako_action *action; wl_list_for_each(action, ¬if->actions, link) { if (strcmp(action->key, action_key) == 0) { - notify_action_invoked(action); + notify_action_invoked(action, NULL); break; } } @@ -173,6 +173,12 @@ return ret; } + ret = sd_bus_message_append(reply, "{sv}", "category", + "s", notif->category); + if (ret < 0) { + return ret; + } + ret = sd_bus_message_append(reply, "{sv}", "summary", "s", notif->summary); if (ret < 0) { @@ -296,6 +302,24 @@ } } +static int handle_set_mode(sd_bus_message *msg, void *data, + sd_bus_error *ret_error) { + struct mako_state *state = data; + + const char *mode; + int ret = sd_bus_message_read(msg, "s", &mode); + if (ret < 0) { + return ret; + } + + free(state->current_mode); + state->current_mode = strdup(mode); + + reapply_config(state); + + return sd_bus_reply_method_return(msg, ""); +} + static int handle_reload(sd_bus_message *msg, void *data, sd_bus_error *ret_error) { struct mako_state *state = data; @@ -322,6 +346,7 @@ SD_BUS_METHOD("RestoreNotification", "", "", handle_restore_action, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("ListNotifications", "", "aa{sv}", handle_list_notifications, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("Reload", "", "", handle_reload, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetMode", "s", "", handle_set_mode, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_VTABLE_END }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mako-1.5/dbus/xdg.c new/mako-1.6/dbus/xdg.c --- old/mako-1.5/dbus/xdg.c 2021-05-03 19:32:05.000000000 +0200 +++ new/mako-1.6/dbus/xdg.c 2021-07-11 10:52:44.000000000 +0200 @@ -59,6 +59,16 @@ } } + ret = sd_bus_message_append(reply, "s", "x-canonical-private-synchronous"); + if (ret < 0) { + return ret; + } + + ret = sd_bus_message_append(reply, "s", "x-dunst-stack-tag"); + if (ret < 0) { + return ret; + } + ret = sd_bus_message_close_container(reply); if (ret < 0) { return ret; @@ -239,6 +249,14 @@ // will win over app_icon if provided. free(notif->app_icon); notif->app_icon = strdup(image_path); + } else if (strcmp(hint, "x-canonical-private-synchronous") == 0 || + strcmp(hint, "x-dunst-stack-tag") == 0) { + const char *tag = NULL; + ret = sd_bus_message_read(msg, "v", "s", &tag); + if (ret < 0) { + return ret; + } + notif->tag = strdup(tag); } else if (strcmp(hint, "image-data") == 0 || strcmp(hint, "image_data") == 0 || // Deprecated. strcmp(hint, "icon_data") == 0) { // Even more deprecated. @@ -343,6 +361,17 @@ } notif->requested_timeout = requested_timeout; + if (notif->tag) { + // Find and replace the existing notfication with a matching tag + struct mako_notification *replace_notif = get_tagged_notification(state, notif->tag, app_name); + if (replace_notif) { + notif->id = replace_notif->id; + wl_list_insert(&replace_notif->link, ¬if->link); + destroy_notification(replace_notif); + replaces_id = notif->id; + } + } + // We can insert a notification prior to matching criteria, because sort is // global. We also know that inserting a notification into the global list // regardless of the configured sort criteria places it in the correct @@ -400,6 +429,8 @@ group_notifications(state, notif_criteria); destroy_criteria(notif_criteria); + notification_execute_binding(notif, ¬if->style.notify_binding, NULL); + set_dirty(notif->surface); return sd_bus_reply_method_return(msg, "u", notif->id); @@ -460,7 +491,8 @@ "NotificationClosed", "uu", notif->id, reason); } -void notify_action_invoked(struct mako_action *action) { +void notify_action_invoked(struct mako_action *action, + const char *activation_token) { if (!action->notification->style.actions) { // Actions are disabled for this notification, bail. return; @@ -468,6 +500,11 @@ struct mako_state *state = action->notification->state; + if (activation_token != NULL) { + sd_bus_emit_signal(state->bus, service_path, service_interface, + "ActivationToken", "us", action->notification->id, activation_token); + } + sd_bus_emit_signal(state->bus, service_path, service_interface, "ActionInvoked", "us", action->notification->id, action->key); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mako-1.5/icon.c new/mako-1.6/icon.c --- old/mako-1.5/icon.c 2021-05-03 19:32:05.000000000 +0200 +++ new/mako-1.6/icon.c 2021-07-11 10:52:44.000000000 +0200 @@ -254,13 +254,12 @@ icon->height = image_height * icon->scale; icon->image = create_cairo_surface_from_gdk_pixbuf(image); + g_object_unref(image); if (icon->image == NULL) { free(icon); return NULL; } - g_object_unref(image); - return icon; } #else diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mako-1.5/include/config.h new/mako-1.6/include/config.h --- old/mako-1.5/include/config.h 2021-05-03 19:32:05.000000000 +0200 +++ new/mako-1.6/include/config.h 2021-07-11 10:52:44.000000000 +0200 @@ -7,12 +7,18 @@ #include <pango/pango.h> #include "types.h" -enum mako_binding { +enum mako_binding_action { MAKO_BINDING_NONE, MAKO_BINDING_DISMISS, MAKO_BINDING_DISMISS_GROUP, MAKO_BINDING_DISMISS_ALL, MAKO_BINDING_INVOKE_DEFAULT_ACTION, + MAKO_BINDING_EXEC, +}; + +struct mako_binding { + enum mako_binding_action action; + char *command; // for MAKO_BINDING_EXEC }; enum mako_sort_criteria { @@ -38,6 +44,10 @@ struct { bool background, text, border, progress; } colors; + struct { + bool left, right, middle; + } button_bindings; + bool touch_binding, notify_binding; }; @@ -81,6 +91,11 @@ char *output; enum zwlr_layer_shell_v1_layer layer; uint32_t anchor; + + struct { + struct mako_binding left, right, middle; + } button_bindings; + struct mako_binding touch_binding, notify_binding; }; struct mako_config { @@ -91,12 +106,6 @@ int32_t max_history; struct mako_style superstyle; - - struct { - enum mako_binding left, right, middle; - } button_bindings; - - enum mako_binding touch; }; void init_default_config(struct mako_config *config); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mako-1.5/include/criteria.h new/mako-1.6/include/criteria.h --- old/mako-1.5/include/criteria.h 2021-05-03 19:32:05.000000000 +0200 +++ new/mako-1.6/include/criteria.h 2021-07-11 10:52:44.000000000 +0200 @@ -33,6 +33,8 @@ char *body; regex_t body_pattern; + char *mode; + // Second-pass matches: int group_index; bool grouped; // Whether group_index is non-zero diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mako-1.5/include/dbus.h new/mako-1.6/include/dbus.h --- old/mako-1.5/include/dbus.h 2021-05-03 19:32:05.000000000 +0200 +++ new/mako-1.6/include/dbus.h 2021-07-11 10:52:44.000000000 +0200 @@ -20,7 +20,8 @@ void notify_notification_closed(struct mako_notification *notif, enum mako_notification_close_reason reason); -void notify_action_invoked(struct mako_action *action); +void notify_action_invoked(struct mako_action *action, + const char *activation_token); int init_dbus_xdg(struct mako_state *state); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mako-1.5/include/mako.h new/mako-1.6/include/mako.h --- old/mako-1.5/include/mako.h 2021-05-03 19:32:05.000000000 +0200 +++ new/mako-1.6/include/mako.h 2021-07-11 10:52:44.000000000 +0200 @@ -3,6 +3,7 @@ #include <stdbool.h> #include <wayland-client.h> +#include <wayland-cursor.h> #ifdef HAVE_LIBSYSTEMD #include <systemd/sd-bus.h> #elif HAVE_LIBELOGIND @@ -16,6 +17,7 @@ #include "pool-buffer.h" #include "wlr-layer-shell-unstable-v1-client-protocol.h" #include "xdg-output-unstable-v1-client-protocol.h" +#include "xdg-activation-v1-client-protocol.h" struct mako_state; @@ -55,14 +57,19 @@ struct wl_shm *shm; struct zwlr_layer_shell_v1 *layer_shell; struct zxdg_output_manager_v1 *xdg_output_manager; + struct xdg_activation_v1 *xdg_activation; struct wl_list outputs; // mako_output::link struct wl_list seats; // mako_seat::link + struct wl_cursor_theme *cursor_theme; + const struct wl_cursor_image *cursor_image; + struct wl_surface *cursor_surface; struct wl_list surfaces; // mako_surface::link uint32_t last_id; struct wl_list notifications; // mako_notification::link struct wl_list history; // mako_notification::link + char *current_mode; int argc; char **argv; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mako-1.5/include/notification.h new/mako-1.6/include/notification.h --- old/mako-1.5/include/notification.h 2021-05-03 19:32:05.000000000 +0200 +++ new/mako-1.6/include/notification.h 2021-07-11 10:52:44.000000000 +0200 @@ -42,6 +42,7 @@ enum mako_notification_urgency urgency; char *category; char *desktop_entry; + char *tag; int32_t progress; struct mako_image_data *image_data; @@ -69,6 +70,12 @@ size_t count; }; +struct mako_binding_context { + struct mako_surface *surface; + struct mako_seat *seat; + uint32_t serial; +}; + #define DEFAULT_ACTION_KEY "default" typedef char *(*mako_format_func_t)(char variable, bool *markup, void *data); @@ -89,13 +96,15 @@ char *format_notif_text(char variable, bool *markup, void *data); size_t format_text(const char *format, char *buf, mako_format_func_t func, void *data); struct mako_notification *get_notification(struct mako_state *state, uint32_t id); +struct mako_notification *get_tagged_notification(struct mako_state *state, const char *tag, const char *app_name); size_t format_notification(struct mako_notification *notif, const char *format, char *buf); void notification_handle_button(struct mako_notification *notif, uint32_t button, - enum wl_pointer_button_state state); -void notification_handle_touch(struct mako_notification *notif); + enum wl_pointer_button_state state, const struct mako_binding_context *ctx); +void notification_handle_touch(struct mako_notification *notif, + const struct mako_binding_context *ctx); void notification_execute_binding(struct mako_notification *notif, - enum mako_binding binding); + const struct mako_binding *binding, const struct mako_binding_context *ctx); void insert_notification(struct mako_state *state, struct mako_notification *notif); int group_notifications(struct mako_state *state, struct mako_criteria *criteria); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mako-1.5/include/types.h new/mako-1.6/include/types.h --- old/mako-1.5/include/types.h 2021-05-03 19:32:05.000000000 +0200 +++ new/mako-1.6/include/types.h 2021-07-11 10:52:44.000000000 +0200 @@ -55,6 +55,8 @@ bool body; bool body_pattern; + bool mode; + bool none; // Special criteria that never matches, used for grouping // Fields that can only be matched after grouping, and thus can't be diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mako-1.5/include/wayland.h new/mako-1.6/include/wayland.h --- old/mako-1.5/include/wayland.h 2021-05-03 19:32:05.000000000 +0200 +++ new/mako-1.6/include/wayland.h 2021-07-11 10:52:44.000000000 +0200 @@ -29,12 +29,14 @@ struct { struct wl_pointer *wl_pointer; int32_t x, y; + struct mako_surface *surface; } pointer; struct { struct wl_touch *wl_touch; struct { int32_t x, y; + struct mako_surface *surface; } pts[MAX_TOUCHPOINTS]; } touch; }; @@ -42,5 +44,7 @@ bool init_wayland(struct mako_state *state); void finish_wayland(struct mako_state *state); void set_dirty(struct mako_surface *surface); +char *create_xdg_activation_token(struct mako_surface *surface, + struct mako_seat *seat, uint32_t serial); #endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mako-1.5/main.c new/mako-1.6/main.c --- old/mako-1.5/main.c 2021-05-03 19:32:05.000000000 +0200 +++ new/mako-1.6/main.c 2021-07-11 10:52:44.000000000 +0200 @@ -42,7 +42,7 @@ " --hidden-format <format> Format string.\n" " --max-visible <n> Max number of visible notifications.\n" " --max-history <n> Max size of history buffer.\n" - " --history <0|1> Add expired notificatinos to history.\n" + " --history <0|1> Add expired notifications to history.\n" " --sort <sort_criteria> Sorts incoming notifications by time\n" " and/or priority in ascending(+) or\n" " descending(-) order.\n" @@ -69,10 +69,13 @@ } wl_list_init(&state->notifications); wl_list_init(&state->history); + state->current_mode = strdup("default"); return true; } static void finish(struct mako_state *state) { + free(state->current_mode); + struct mako_notification *notif, *tmp; wl_list_for_each_safe(notif, tmp, &state->notifications, link) { destroy_notification(notif); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mako-1.5/mako.5.scd new/mako-1.6/mako.5.scd --- old/mako-1.5/mako.5.scd 2021-05-03 19:32:05.000000000 +0200 +++ new/mako-1.6/mako.5.scd 2021-07-11 10:52:44.000000000 +0200 @@ -29,11 +29,11 @@ Default: -time -# BUTTON AND TOUCH BINDINGS - -Specify the action to take per button or touch event. Supported values are -_none_, _dismiss_, _dismiss-all_, _dismiss-group_, _invoke-default-action_, +# BINDING OPTIONS +Bindings allow to perform an action when an event is triggered. Supported +values are _none_, _dismiss_, _dismiss-all_, _dismiss-group_, +_invoke-default-action_ and _exec <command>_. *on-button-left*=_action_ Default: invoke-default-action @@ -47,6 +47,22 @@ *on-touch*=_action_ Default: dismiss +*on-notify* = _action_ + Default: none + +When _exec_ is used, the command will be executed in a POSIX shell. The +shell variable _id_ will be set to the notification ID. For example, the +following option will display an interactive action menu on middle click: + +``` +on-button-middle=exec makoctl menu -n "$id" dmenu -p 'Select action: ' +``` + +The following option will play a sound when a new notification is opened: + +``` +on-notify=exec mpv /usr/share/sounds/freedesktop/stereo/message.oga +``` # STYLE OPTIONS @@ -224,7 +240,7 @@ *anchor*=_position_ Show notifications at the specified position on the output. Supported values are _top-right_, _top-center_, _top-left_, _bottom-right_, _bottom-center_, - _bottom-left_, and _center_. + _bottom-left_, _center-right_, _center-left_ and _center_. Default: top-right @@ -232,8 +248,9 @@ In addition to the set of options at the top of the file, the config file may contain zero or more sections, each containing any combination of the -*STYLE OPTIONS*. The sections, called criteria, are defined with an INI-like -square bracket syntax. The brackets may contain any number of fields, like so: +*BINDING OPTIONS* and *STYLE OPTIONS*. The sections, called criteria, are +defined with an INI-like square bracket syntax. The brackets may contain any +number of fields, like so: \[field=value field2=value2 ...\] @@ -268,6 +285,10 @@ - _desktop-entry_ (string) - _actionable_ (boolean) - _expiring_ (boolean) +- _mode_ (string) + - Only apply style options in this section if the provided mode is + currently enabled. For more information about modes, see the _MODES_ + section. The following fields are also available to match on a second pass based on where previous style options decided to place each notification: @@ -388,6 +409,25 @@ *%t* Total number of notifications +# MODES + +mako supports applying style options conditionally via modes. A configuration +section with a _mode_ criteria will only be applied if the current mode +matches. **makoctl**(1) can be used to change the current mode. + +The default mode is named "default". + +For example, to hide all notifications if the mode "do-not-disturb" is +enabled: + +``` +[mode=do-not-disturb] +invisible=1 +``` + +_makoctl set-mode do-not-disturb_ will hide all notifications, +_makoctl set-mode default_ will show them again. + # AUTHORS Maintained by Simon Ser <[email protected]>, who is assisted by other diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mako-1.5/makoctl new/mako-1.6/makoctl --- old/mako-1.5/makoctl 2021-05-03 19:32:05.000000000 +0200 +++ new/mako-1.6/makoctl 2021-07-11 10:52:44.000000000 +0200 @@ -15,12 +15,13 @@ echo " invoke [-n id] [action] Invoke an action on the notification" echo " with the given id, or the last" echo " notification if none is given" - echo " menu [-n id] [prog] [arg ...] Use [prog] [args ...] to select one" + echo " menu [-n id] <prog> [arg ...] Use <prog> [args ...] to select one" echo " action to be invoked on the notification" echo " with the given id, or the last" echo " notification if none is given" echo " list List notifications" echo " reload Reload the configuration file" + echo " set-mode <name> Switch the current mode" echo " help Show this help" } @@ -129,6 +130,9 @@ "reload") call Reload ;; +"set-mode") + call SetMode "s" "$2" + ;; "help"|"--help"|"-h") usage ;; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mako-1.5/makoctl.1.scd new/mako-1.6/makoctl.1.scd --- old/mako-1.5/makoctl.1.scd 2021-05-03 19:32:05.000000000 +0200 +++ new/mako-1.6/makoctl.1.scd 2021-07-11 10:52:44.000000000 +0200 @@ -42,7 +42,7 @@ Invoke the action on the notification with the given id. Defaults to the first notification. -*menu* [-n <id>] [program] [argument...] +*menu* [-n <id>] <program> [argument...] Use a program to select an action on a notification. The list of actions are joined on newlines and passed to _program_. The program should write the selected action to stdout. If an action is given, this action @@ -70,6 +70,11 @@ *reload* Reloads the configuration file. +*set-mode* <name> + Switches the current mode to _name_. This replaces the previous mode. + + See the _MODES_ section in **mako**(5) for more information about modes. + *help, -h, --help* Show help message and quit. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mako-1.5/meson.build new/mako-1.6/meson.build --- old/mako-1.5/meson.build 2021-05-03 19:32:05.000000000 +0200 +++ new/mako-1.6/meson.build 2021-07-11 10:52:44.000000000 +0200 @@ -1,7 +1,7 @@ project( 'mako', 'c', - version: '1.4.1', + version: '1.6', license: 'MIT', meson_version: '>=0.50.0', default_options: [ @@ -11,8 +11,10 @@ ], ) -add_project_arguments('-Wno-unused-parameter', language: 'c') -add_project_arguments('-Wno-missing-braces', language: 'c') +add_project_arguments([ + '-Wno-unused-parameter', + '-Wno-missing-braces', +], language: 'c') datadir = get_option('datadir') @@ -27,7 +29,8 @@ gobject = dependency('gobject-2.0') realtime = cc.find_library('rt') wayland_client = dependency('wayland-client') -wayland_protos = dependency('wayland-protocols', version: '>=1.14') +wayland_protos = dependency('wayland-protocols', version: '>=1.21') +wayland_cursor = dependency('wayland-cursor') epoll = dependency('', required: false) if (not cc.has_function('timerfd_create', prefix: '#include <sys/timerfd.h>') or @@ -105,6 +108,7 @@ gobject, realtime, wayland_client, + wayland_cursor, ], include_directories: [mako_inc], install: true, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mako-1.5/notification.c new/mako-1.6/notification.c --- old/mako-1.5/notification.c 2021-05-03 19:32:05.000000000 +0200 +++ new/mako-1.6/notification.c 2021-07-11 10:52:44.000000000 +0200 @@ -1,8 +1,12 @@ #define _POSIX_C_SOURCE 200809L +#include <assert.h> #include <ctype.h> #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <sys/wait.h> +#include <unistd.h> + #include <pango/pangocairo.h> #include <wayland-client.h> #include <linux/input-event-codes.h> @@ -15,6 +19,7 @@ #include "notification.h" #include "icon.h" #include "string-util.h" +#include "wayland.h" bool hotspot_at(struct mako_hotspot *hotspot, int32_t x, int32_t y) { return x >= hotspot->x && @@ -44,6 +49,7 @@ free(notif->body); free(notif->category); free(notif->desktop_entry); + free(notif->tag); if (notif->image_data != NULL) { free(notif->image_data->data); free(notif->image_data); @@ -55,6 +61,7 @@ notif->body = strdup(""); notif->category = strdup(""); notif->desktop_entry = strdup(""); + notif->tag = strdup(""); notif->image_data = NULL; @@ -133,6 +140,19 @@ return NULL; } +struct mako_notification *get_tagged_notification(struct mako_state *state, + const char *tag, const char *app_name) { + struct mako_notification *notif; + wl_list_for_each(notif, &state->notifications, link) { + if (notif->tag && strlen(notif->tag) != 0 && + strcmp(notif->tag, tag) == 0 && + strcmp(notif->app_name, app_name) == 0) { + return notif; + } + } + return NULL; +} + void close_group_notifications(struct mako_notification *top_notif, enum mako_notification_close_reason reason) { struct mako_state *state = top_notif->state; @@ -305,22 +325,23 @@ return len; } -static enum mako_binding get_binding(struct mako_config *config, +static const struct mako_binding *get_button_binding(struct mako_style *style, uint32_t button) { switch (button) { case BTN_LEFT: - return config->button_bindings.left; + return &style->button_bindings.left; case BTN_RIGHT: - return config->button_bindings.right; + return &style->button_bindings.right; case BTN_MIDDLE: - return config->button_bindings.middle; + return &style->button_bindings.middle; } - return MAKO_BINDING_NONE; + return NULL; } void notification_execute_binding(struct mako_notification *notif, - enum mako_binding binding) { - switch (binding) { + const struct mako_binding *binding, + const struct mako_binding_context *ctx) { + switch (binding->action) { case MAKO_BINDING_NONE: break; case MAKO_BINDING_DISMISS: @@ -336,28 +357,74 @@ struct mako_action *action; wl_list_for_each(action, ¬if->actions, link) { if (strcmp(action->key, DEFAULT_ACTION_KEY) == 0) { - notify_action_invoked(action); + char *activation_token = NULL; + if (ctx != NULL) { + activation_token = create_xdg_activation_token(ctx->surface, + ctx->seat, ctx->serial); + } + notify_action_invoked(action, activation_token); + free(activation_token); break; } } close_notification(notif, MAKO_NOTIFICATION_CLOSE_DISMISSED); break; + case MAKO_BINDING_EXEC: + assert(binding->command != NULL); + pid_t pid = fork(); + if (pid < 0) { + perror("fork failed"); + break; + } else if (pid == 0) { + // Double-fork to avoid SIGCHLD issues + pid = fork(); + if (pid < 0) { + perror("fork failed"); + _exit(1); + } else if (pid == 0) { + // We pass variables using additional sh arguments. To convert + // back the arguments to variables, insert a short script + // preamble before the user's command. + const char setup_vars[] = "id=\"$1\"\n"; + + size_t cmd_size = strlen(setup_vars) + strlen(binding->command) + 1; + char *cmd = malloc(cmd_size); + snprintf(cmd, cmd_size, "%s%s", setup_vars, binding->command); + + char id_str[32]; + snprintf(id_str, sizeof(id_str), "%" PRIu32, notif->id); + + char *const argv[] = { "sh", "-c", cmd, "sh", id_str, NULL }; + execvp("sh", argv); + perror("exec failed"); + _exit(1); + } + _exit(0); + } + if (waitpid(pid, NULL, 0) < 0) { + perror("waitpid failed"); + } + break; } } void notification_handle_button(struct mako_notification *notif, uint32_t button, - enum wl_pointer_button_state state) { + enum wl_pointer_button_state state, + const struct mako_binding_context *ctx) { if (state != WL_POINTER_BUTTON_STATE_PRESSED) { return; } - notification_execute_binding(notif, - get_binding(¬if->state->config, button)); + const struct mako_binding *binding = + get_button_binding(¬if->style, button); + if (binding != NULL) { + notification_execute_binding(notif, binding, ctx); + } } -void notification_handle_touch(struct mako_notification *notif) { - notification_execute_binding(notif, - notif->state->config.touch); +void notification_handle_touch(struct mako_notification *notif, + const struct mako_binding_context *ctx) { + notification_execute_binding(notif, ¬if->style.touch_binding, ctx); } /* diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mako-1.5/protocol/meson.build new/mako-1.6/protocol/meson.build --- old/mako-1.5/protocol/meson.build 2021-05-03 19:32:05.000000000 +0200 +++ new/mako-1.6/protocol/meson.build 2021-07-11 10:52:44.000000000 +0200 @@ -23,6 +23,7 @@ client_protocols = [ [wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'], + [wl_protocol_dir, 'staging/xdg-activation/xdg-activation-v1.xml'], [wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'], ['wlr-layer-shell-unstable-v1.xml'], ] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mako-1.5/types.c new/mako-1.6/types.c --- old/mako-1.5/types.c 2021-05-03 19:32:05.000000000 +0200 +++ new/mako-1.6/types.c 2021-07-11 10:52:44.000000000 +0200 @@ -348,6 +348,10 @@ } else if (strcmp(string, "bottom-left") == 0) { *out = ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT; + } else if (strcmp(string, "center-right") == 0) { + *out = ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; + } else if (strcmp(string, "center-left") == 0) { + *out = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT; } else if (strcmp(string, "center") == 0) { *out = 0; } else { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mako-1.5/wayland.c new/mako-1.6/wayland.c --- old/mako-1.5/wayland.c 2021-05-03 19:32:05.000000000 +0200 +++ new/mako-1.6/wayland.c 2021-07-11 10:52:44.000000000 +0200 @@ -100,6 +100,17 @@ free(output); } +static struct mako_surface *get_surface(struct mako_state *state, + struct wl_surface *wl_surface) { + struct mako_surface *surface; + wl_list_for_each(surface, &state->surfaces, link) { + if (surface->surface == wl_surface) { + return surface; + } + } + return NULL; +} + static void touch_handle_motion(void *data, struct wl_touch *wl_touch, uint32_t time, int32_t id, wl_fixed_t surface_x, wl_fixed_t surface_y) { @@ -112,14 +123,15 @@ } static void touch_handle_down(void *data, struct wl_touch *wl_touch, - uint32_t serial, uint32_t time, struct wl_surface *sfc, int32_t id, - wl_fixed_t surface_x, wl_fixed_t surface_y) { + uint32_t serial, uint32_t time, struct wl_surface *wl_surface, + int32_t id, wl_fixed_t surface_x, wl_fixed_t surface_y) { struct mako_seat *seat = data; if (id >= MAX_TOUCHPOINTS) { return; } seat->touch.pts[id].x = wl_fixed_to_int(surface_x); seat->touch.pts[id].y = wl_fixed_to_int(surface_y); + seat->touch.pts[id].surface = get_surface(seat->state, wl_surface); } static void touch_handle_up(void *data, struct wl_touch *wl_touch, @@ -127,19 +139,50 @@ struct mako_seat *seat = data; struct mako_state *state = seat->state; - struct mako_notification *notif; if (id >= MAX_TOUCHPOINTS) { return; } + + const struct mako_binding_context ctx = { + .surface = seat->touch.pts[id].surface, + .seat = seat, + .serial = serial, + }; + + struct mako_notification *notif; wl_list_for_each(notif, &state->notifications, link) { if (hotspot_at(¬if->hotspot, seat->touch.pts[id].x, seat->touch.pts[id].y)) { struct mako_surface *surface = notif->surface; - notification_handle_touch(notif); + notification_handle_touch(notif, &ctx); set_dirty(surface); break; } } + seat->touch.pts[id].surface = NULL; +} + +static void pointer_handle_enter(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, struct wl_surface *wl_surface, + wl_fixed_t surface_x, wl_fixed_t surface_y) { + struct mako_seat *seat = data; + struct mako_state *state = seat->state; + + 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); + + // Change the mouse cursor to "left_ptr" + if (state->cursor_theme != NULL) { + wl_pointer_set_cursor(wl_pointer, serial, state->cursor_surface, + state->cursor_image->hotspot_x, state->cursor_image->hotspot_y); + } +} + +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; + seat->pointer.surface = NULL; } static void pointer_handle_motion(void *data, struct wl_pointer *wl_pointer, @@ -155,21 +198,26 @@ struct mako_seat *seat = data; struct mako_state *state = seat->state; + const struct mako_binding_context ctx = { + .surface = seat->pointer.surface, + .seat = seat, + .serial = serial, + }; + struct mako_notification *notif; wl_list_for_each(notif, &state->notifications, link) { if (hotspot_at(¬if->hotspot, seat->pointer.x, seat->pointer.y)) { struct mako_surface *surface = notif->surface; - notification_handle_button(notif, button, button_state); + notification_handle_button(notif, button, button_state, &ctx); set_dirty(surface); break; } } - } static const struct wl_pointer_listener pointer_listener = { - .enter = noop, - .leave = noop, + .enter = pointer_handle_enter, + .leave = pointer_handle_leave, .motion = pointer_handle_motion, .button = pointer_handle_button, .axis = noop, @@ -331,6 +379,9 @@ state->xdg_output_manager = wl_registry_bind(registry, name, &zxdg_output_manager_v1_interface, ZXDG_OUTPUT_V1_NAME_SINCE_VERSION); + } else if (strcmp(interface, xdg_activation_v1_interface.name) == 0) { + state->xdg_activation = wl_registry_bind(registry, name, + &xdg_activation_v1_interface, 1); } } @@ -400,6 +451,39 @@ } } + // Set up the cursor. It needs a wl_surface with the cursor loaded into it. + // If one of these fail, mako will work fine without the cursor being able to change. + const char *cursor_size_env = getenv("XCURSOR_SIZE"); + int cursor_size = 24; + if (cursor_size_env != NULL) { + errno = 0; + char *end; + int temp_size = (int)strtol(cursor_size_env, &end, 10); + if (errno == 0 && cursor_size_env[0] != 0 && end[0] == 0 && temp_size > 0) { + cursor_size = temp_size; + } else { + fprintf(stderr, "Error: XCURSOR_SIZE is invalid\n"); + } + } + state->cursor_theme = wl_cursor_theme_load(NULL, cursor_size, state->shm); + if (state->cursor_theme == NULL) { + fprintf(stderr, "couldn't find a cursor theme\n"); + return true; + } + struct wl_cursor *cursor = wl_cursor_theme_get_cursor(state->cursor_theme, "left_ptr"); + if (cursor == NULL) { + fprintf(stderr, "couldn't find cursor icon \"left_ptr\"\n"); + wl_cursor_theme_destroy(state->cursor_theme); + // Set to NULL so it doesn't get free'd again + state->cursor_theme = NULL; + return true; + } + state->cursor_image = cursor->images[0]; + struct wl_buffer *cursor_buffer = wl_cursor_image_get_buffer(cursor->images[0]); + state->cursor_surface = wl_compositor_create_surface(state->compositor); + wl_surface_attach(state->cursor_surface, cursor_buffer, 0, 0); + wl_surface_commit(state->cursor_surface); + return true; } @@ -419,9 +503,18 @@ destroy_seat(seat); } + if (state->xdg_activation != NULL) { + xdg_activation_v1_destroy(state->xdg_activation); + } if (state->xdg_output_manager != NULL) { zxdg_output_manager_v1_destroy(state->xdg_output_manager); } + + if (state->cursor_theme != NULL) { + wl_cursor_theme_destroy(state->cursor_theme); + wl_surface_destroy(state->cursor_surface); + } + zwlr_layer_shell_v1_destroy(state->layer_shell); wl_compositor_destroy(state->compositor); wl_shm_destroy(state->shm); @@ -622,3 +715,38 @@ surface->dirty = true; schedule_frame_and_commit(surface); } + +static void activation_token_handle_done(void *data, + struct xdg_activation_token_v1 *token, const char *token_str) { + char **out = data; + *out = strdup(token_str); +} + +static const struct xdg_activation_token_v1_listener activation_token_listener = { + .done = activation_token_handle_done, +}; + +char *create_xdg_activation_token(struct mako_surface *surface, + struct mako_seat *seat, uint32_t serial) { + struct mako_state *state = seat->state; + if (state->xdg_activation == NULL) { + return NULL; + } + + char *token_str = NULL; + struct xdg_activation_token_v1 *token = + xdg_activation_v1_get_activation_token(state->xdg_activation); + xdg_activation_token_v1_add_listener(token, &activation_token_listener, + &token_str); + xdg_activation_token_v1_set_serial(token, serial, seat->wl_seat); + xdg_activation_token_v1_set_surface(token, surface->surface); + xdg_activation_token_v1_commit(token); + + while (wl_display_dispatch(state->display) >= 0 && token_str == NULL) { + // This space is intentionally left blank + } + + xdg_activation_token_v1_destroy(token); + + return token_str; +}
