diff --git a/config.c b/config.c index 70be717..87db932 100644 --- a/config.c +++ b/config.c @@ -129,6 +129,9 @@ void init_default_style(struct mako_style *style) { style->button_bindings.right.action = MAKO_BINDING_DISMISS; style->button_bindings.middle.action = MAKO_BINDING_NONE; style->touch_binding.action = MAKO_BINDING_DISMISS; + style->long_touch_binding.action = MAKO_BINDING_INVOKE_ACTION; + style->long_touch_binding.action_name = strdup(DEFAULT_ACTION_KEY); + style->long_press_duration = 500; // Everything in the default config is explicitly specified. memset(&style->spec, true, sizeof(struct mako_style_spec)); @@ -148,6 +151,7 @@ void finish_style(struct mako_style *style) { finish_binding(&style->button_bindings.middle); finish_binding(&style->button_bindings.right); finish_binding(&style->touch_binding); + finish_binding(&style->long_touch_binding); finish_binding(&style->notify_binding); free(style->icon_path); free(style->font); @@ -385,6 +389,16 @@ bool apply_style(struct mako_style *target, const struct mako_style *style) { target->spec.touch_binding = true; } + if (style->spec.long_touch_binding) { + copy_binding(&target->long_touch_binding, &style->long_touch_binding); + target->spec.long_touch_binding = true; + } + + if (style->spec.long_press_duration) { + target->long_press_duration = style->long_press_duration; + target->spec.long_press_duration = true; + } + if (style->spec.notify_binding) { copy_binding(&target->notify_binding, &style->notify_binding); target->spec.notify_binding = true; @@ -660,6 +674,8 @@ static bool apply_style_option(struct mako_style *style, const char *name, return true; } else if (strcmp(name, "anchor") == 0) { return spec->anchor = parse_anchor(value, &style->anchor); + } else if (strcmp(name, "long-press-duration") == 0) { + return spec->long_press_duration = parse_int_ge(value, &style->long_press_duration, 0); } else if (has_prefix(name, "on-")) { struct mako_binding binding = {0}; if (strcmp(value, "none") == 0) { @@ -697,6 +713,9 @@ static bool apply_style_option(struct mako_style *style, const char *name, } else if (strcmp(name, "on-touch") == 0) { copy_binding(&style->touch_binding, &binding); style->spec.touch_binding = true; + } else if (strcmp(name, "on-long-touch") == 0) { + copy_binding(&style->long_touch_binding, &binding); + style->spec.long_touch_binding = true; } else if (strcmp(name, "on-notify") == 0) { copy_binding(&style->notify_binding, &binding); style->spec.notify_binding = true; @@ -886,6 +905,8 @@ int parse_config_arguments(struct mako_config *config, int argc, char **argv) { {"on-button-right", required_argument, 0, 0}, {"on-button-middle", required_argument, 0, 0}, {"on-touch", required_argument, 0, 0}, + {"on-long-touch", required_argument, 0, 0}, + {"long-press-duration", required_argument, 0, 0}, {0}, }; diff --git a/doc/mako.5.scd b/doc/mako.5.scd index 91378ba..fff19ed 100644 --- a/doc/mako.5.scd +++ b/doc/mako.5.scd @@ -57,10 +57,23 @@ Supported options: Default: dismiss *on-touch*=_action_ - Performs the action when tapped via a touch device. + Performs the action when tapped via a touch device if the tap + duration is less than *long-press-duration*. Default: dismiss +*on-long-touch*=_action_ + Performs the action when tapped via a touch device if the press + duration is greater or equal to *long-press-duration*. + + Default: invoke-default-action + +*long-press-duration*=_time_ + Specifies the cutoff time (in milliseconds) for a press to be + considered a long press. + + Default: 500 + *on-notify*=_action_ Performs the action when the notification is opened. diff --git a/include/config.h b/include/config.h index 013923a..5ff541c 100644 --- a/include/config.h +++ b/include/config.h @@ -42,14 +42,14 @@ 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, long_press_duration; struct { bool background, text, border, progress; } colors; struct { bool left, right, middle; } button_bindings; - bool touch_binding, notify_binding; + bool touch_binding, long_touch_binding, notify_binding; }; @@ -98,7 +98,8 @@ struct mako_style { struct { struct mako_binding left, right, middle; } button_bindings; - struct mako_binding touch_binding, notify_binding; + struct mako_binding touch_binding, long_touch_binding, notify_binding; + int32_t long_press_duration; }; struct mako_config { diff --git a/include/notification.h b/include/notification.h index 9a395ba..b1482ec 100644 --- a/include/notification.h +++ b/include/notification.h @@ -19,6 +19,12 @@ struct mako_hotspot { int32_t width, height; }; +struct mako_binding_context { + struct mako_surface *surface; + struct mako_seat *seat; + uint32_t serial; +}; + struct mako_notification { struct mako_state *state; struct mako_surface *surface; @@ -38,6 +44,8 @@ struct mako_notification { char *body; int32_t requested_timeout; struct wl_list actions; // mako_action::link + struct mako_timer *long_press_timer; + struct mako_binding_context long_press_ctx; enum mako_notification_urgency urgency; char *category; @@ -70,12 +78,6 @@ struct mako_hidden_format_data { size_t count; }; -struct mako_binding_context { - struct mako_surface *surface; - struct mako_seat *seat; - uint32_t serial; -}; - 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); @@ -100,8 +102,10 @@ 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, const struct mako_binding_context *ctx); -void notification_handle_touch(struct mako_notification *notif, +void notification_handle_touch_start(struct mako_notification *notif, const struct mako_binding_context *ctx); +void notification_handle_touch(struct mako_notification *notif, + const struct mako_binding_context *ctx, int32_t duration_ms); void notification_execute_binding(struct mako_notification *notif, const struct mako_binding *binding, const struct mako_binding_context *ctx); void insert_notification(struct mako_state *state, struct mako_notification *notif); diff --git a/include/wayland.h b/include/wayland.h index cc00500..d4d39c4 100644 --- a/include/wayland.h +++ b/include/wayland.h @@ -3,6 +3,7 @@ #include #include +#include "mako.h" #define MAX_TOUCHPOINTS 10 @@ -35,6 +36,7 @@ struct mako_seat { struct wl_touch *wl_touch; struct { int32_t x, y; + uint32_t time; struct mako_surface *surface; } pts[MAX_TOUCHPOINTS]; } touch; diff --git a/notification.c b/notification.c index 8c0c8a7..32e7f7b 100644 --- a/notification.c +++ b/notification.c @@ -41,6 +41,8 @@ void reset_notification(struct mako_notification *notif) { destroy_timer(notif->timer); notif->timer = NULL; + destroy_timer(notif->long_press_timer); + notif->long_press_timer = NULL; free(notif->app_name); free(notif->app_icon); @@ -446,8 +448,32 @@ void notification_handle_button(struct mako_notification *notif, uint32_t button } void notification_handle_touch(struct mako_notification *notif, + const struct mako_binding_context *ctx, int32_t duration_ms) { + destroy_timer(notif->long_press_timer); + notif->long_press_timer = NULL; + if (duration_ms >= notif->style.long_press_duration) { + notification_execute_binding(notif, ¬if->style.long_touch_binding, ctx); + } else { + notification_execute_binding(notif, ¬if->style.touch_binding, ctx); + } +} + +void handle_notification_touch_timer(void *data) { + struct mako_notification *notif = data; + notif->long_press_timer = NULL; + struct mako_binding_context ctx = notif->long_press_ctx; + notification_execute_binding(notif, ¬if->style.long_touch_binding, &ctx); + set_dirty(ctx.surface); +} + +void notification_handle_touch_start(struct mako_notification *notif, const struct mako_binding_context *ctx) { - notification_execute_binding(notif, ¬if->style.touch_binding, ctx); + if (notif->long_press_timer) { + return; + } + notif->long_press_ctx = *ctx; + notif->long_press_timer = add_event_loop_timer(¬if->state->event_loop, 500, + handle_notification_touch_timer, notif); } /* diff --git a/wayland.c b/wayland.c index eeefb30..6d8b6ff 100644 --- a/wayland.c +++ b/wayland.c @@ -120,9 +120,23 @@ static void touch_handle_down(void *data, struct wl_touch *wl_touch, if (id >= MAX_TOUCHPOINTS) { return; } + struct mako_state *state = seat->state; 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); + seat->touch.pts[id].time = time; + seat->touch.pts[id].surface = get_surface(state, wl_surface); + + struct mako_notification *notif; + const struct mako_binding_context ctx = { + .surface = seat->touch.pts[id].surface, + .seat = seat, + .serial = serial, + }; + wl_list_for_each(notif, &state->notifications, link) { + if (hotspot_at(¬if->hotspot, seat->touch.pts[id].x, seat->touch.pts[id].y)) { + notification_handle_touch_start(notif, &ctx); + } + } } static void touch_handle_up(void *data, struct wl_touch *wl_touch, @@ -144,7 +158,7 @@ static void touch_handle_up(void *data, struct wl_touch *wl_touch, 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, &ctx); + notification_handle_touch(notif, &ctx, time - seat->touch.pts[id].time); set_dirty(surface); break; }