diff --git a/include/sway/commands.h b/include/sway/commands.h index 9bd0f1cb5..493cca55e 100644 --- a/include/sway/commands.h +++ b/include/sway/commands.h @@ -269,6 +269,7 @@ sway_cmd seat_cmd_attach; sway_cmd seat_cmd_cursor; sway_cmd seat_cmd_fallback; sway_cmd seat_cmd_hide_cursor; +sway_cmd seat_cmd_hide_cursor_while_typing; sway_cmd seat_cmd_pointer_constraint; sway_cmd cmd_ipc_cmd; diff --git a/include/sway/config.h b/include/sway/config.h index d49120a06..7441cd144 100644 --- a/include/sway/config.h +++ b/include/sway/config.h @@ -161,7 +161,8 @@ struct seat_config { char *name; int fallback; // -1 means not set list_t *attachments; // list of seat_attachment configs - int hide_cursor_timeout; + int hide_cursor_timeout; // idle timeout + int hide_cursor_typing_timeout; enum seat_config_allow_constrain allow_constrain; }; diff --git a/include/sway/input/cursor.h b/include/sway/input/cursor.h index 516718c97..cd4e04a36 100644 --- a/include/sway/input/cursor.h +++ b/include/sway/input/cursor.h @@ -13,6 +13,12 @@ #define SWAY_SCROLL_LEFT KEY_MAX + 3 #define SWAY_SCROLL_RIGHT KEY_MAX + 4 +enum sway_cursor_hidden_reason { + CURSOR_VISIBLE = 0, + CURSOR_HIDDEN_IDLE = 1, + CURSOR_HIDDEN_TYPING = 2, +}; + struct sway_cursor { struct sway_seat *seat; struct wlr_cursor *cursor; @@ -49,8 +55,9 @@ struct sway_cursor { struct wl_listener constraint_commit; - struct wl_event_source *hide_source; - bool hidden; + struct wl_event_source *hide_source; // idle + struct wl_event_source *hide_source_typing; + uint32_t hidden; // bitfield of enum sway_cursor_hidden_reason size_t pressed_button_count; }; @@ -73,8 +80,18 @@ void cursor_rebase(struct sway_cursor *cursor); void cursor_rebase_all(void); void cursor_handle_activity(struct sway_cursor *cursor); -void cursor_unhide(struct sway_cursor *cursor); -int cursor_get_timeout(struct sway_cursor *cursor); + +void cursor_hide(struct sway_cursor *cursor, + enum sway_cursor_hidden_reason reason); + +// Removes the cursor hidden reason and if all reasons are removed, unhides the +// cursor. If CURSOR_VISIBLE is given as the reason, all reasons will be +// removed and the cursor will be unhidden. +void cursor_unhide(struct sway_cursor *cursor, + enum sway_cursor_hidden_reason reason); + +int cursor_get_timeout(struct sway_cursor *cursor, + enum sway_cursor_hidden_reason reason); void dispatch_cursor_button(struct sway_cursor *cursor, struct wlr_input_device *device, uint32_t time_msec, uint32_t button, diff --git a/sway/commands/seat.c b/sway/commands/seat.c index aa36ba955..dd6e7fd2f 100644 --- a/sway/commands/seat.c +++ b/sway/commands/seat.c @@ -12,6 +12,7 @@ static struct cmd_handler seat_handlers[] = { { "cursor", seat_cmd_cursor }, { "fallback", seat_cmd_fallback }, { "hide_cursor", seat_cmd_hide_cursor }, + { "hide_cursor_while_typing", seat_cmd_hide_cursor_while_typing }, { "pointer_constraint", seat_cmd_pointer_constraint }, }; diff --git a/sway/commands/seat/hide_cursor_while_typing.c b/sway/commands/seat/hide_cursor_while_typing.c new file mode 100644 index 000000000..4e8eecdbf --- /dev/null +++ b/sway/commands/seat/hide_cursor_while_typing.c @@ -0,0 +1,29 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include "sway/commands.h" +#include "sway/config.h" +#include "sway/input/seat.h" +#include "stringop.h" + +struct cmd_results *seat_cmd_hide_cursor_while_typing(int argc, char **argv) { + struct cmd_results *error = + checkarg(argc, "hide_cursor_while_typing", EXPECTED_EQUAL_TO, 1); + if (error) { + return error; + } + if (!config->handler_context.seat_config) { + return cmd_results_new(CMD_FAILURE, "No seat defined"); + } + + char *end; + int timeout = strtol(argv[0], &end, 10); + if (*end) { + return cmd_results_new(CMD_INVALID, "Expected an integer timeout"); + } + if (timeout < 100 && timeout != 0) { + timeout = 100; + } + config->handler_context.seat_config->hide_cursor_typing_timeout = timeout; + + return cmd_results_new(CMD_SUCCESS, NULL); +} diff --git a/sway/config/seat.c b/sway/config/seat.c index 04a44e3a8..a0e0cd12b 100644 --- a/sway/config/seat.c +++ b/sway/config/seat.c @@ -26,6 +26,7 @@ struct seat_config *new_seat_config(const char* name) { return NULL; } seat->hide_cursor_timeout = -1; + seat->hide_cursor_typing_timeout = -1; seat->allow_constrain = CONSTRAIN_DEFAULT; return seat; @@ -144,6 +145,10 @@ void merge_seat_config(struct seat_config *dest, struct seat_config *source) { dest->hide_cursor_timeout = source->hide_cursor_timeout; } + if (source->hide_cursor_typing_timeout != -1) { + dest->hide_cursor_typing_timeout = source->hide_cursor_typing_timeout; + } + if (source->allow_constrain != CONSTRAIN_DEFAULT) { dest->allow_constrain = source->allow_constrain; } diff --git a/sway/input/cursor.c b/sway/input/cursor.c index 610844479..4f427a3ef 100644 --- a/sway/input/cursor.c +++ b/sway/input/cursor.c @@ -176,18 +176,48 @@ void cursor_rebase_all(void) { static int hide_notify(void *data) { struct sway_cursor *cursor = data; - wlr_cursor_set_image(cursor->cursor, NULL, 0, 0, 0, 0, 0, 0); - cursor->hidden = true; - wlr_seat_pointer_clear_focus(cursor->seat->wlr_seat); + cursor_hide(cursor, CURSOR_HIDDEN_IDLE); return 1; } -int cursor_get_timeout(struct sway_cursor *cursor){ +void cursor_hide(struct sway_cursor *cursor, + enum sway_cursor_hidden_reason reason) { + if (!sway_assert(reason != CURSOR_VISIBLE, + "Cannot hide cursor for reason CURSOR_VISIBLE")) { + return; + } + if (cursor->hidden == CURSOR_VISIBLE) { + wlr_cursor_set_image(cursor->cursor, NULL, 0, 0, 0, 0, 0, 0); + cursor->hidden |= reason; + wlr_seat_pointer_clear_focus(cursor->seat->wlr_seat); + } else { + cursor->hidden |= reason; + } +} + +int cursor_get_timeout(struct sway_cursor *cursor, + enum sway_cursor_hidden_reason reason) { struct seat_config *sc = seat_get_config(cursor->seat); if (!sc) { sc = seat_get_config_by_name("*"); } - int timeout = sc ? sc->hide_cursor_timeout : 0; + int timeout = 0; + switch (reason) { + case CURSOR_VISIBLE: + sway_assert(false, "There should not be attempt to retrieve the " + "timeout for CURSOR_VISIBLE"); + break; + case CURSOR_HIDDEN_IDLE: + if (sc) { + timeout = sc->hide_cursor_timeout; + } + break; + case CURSOR_HIDDEN_TYPING: + if (sc) { + timeout = sc->hide_cursor_typing_timeout; + } + break; + } if (timeout < 0) { timeout = 0; } @@ -195,29 +225,44 @@ int cursor_get_timeout(struct sway_cursor *cursor){ } void cursor_handle_activity(struct sway_cursor *cursor) { - wl_event_source_timer_update( - cursor->hide_source, cursor_get_timeout(cursor)); + wl_event_source_timer_update(cursor->hide_source, + cursor_get_timeout(cursor, CURSOR_HIDDEN_IDLE)); wlr_idle_notify_activity(server.idle, cursor->seat->wlr_seat); - if (cursor->hidden) { - cursor_unhide(cursor); + if ((cursor->hidden & CURSOR_HIDDEN_IDLE) != 0) { + cursor_unhide(cursor, CURSOR_HIDDEN_IDLE); } } -void cursor_unhide(struct sway_cursor *cursor) { - cursor->hidden = false; - if (cursor->image_surface) { - cursor_set_image_surface(cursor, - cursor->image_surface, - cursor->hotspot_x, - cursor->hotspot_y, - cursor->image_client); - } else { - const char *image = cursor->image; - cursor->image = NULL; - cursor_set_image(cursor, image, cursor->image_client); +static int handle_typing_unhide(void *data) { + struct sway_cursor *cursor = data; + if ((cursor->hidden & CURSOR_HIDDEN_TYPING) != 0) { + cursor_unhide(cursor, CURSOR_HIDDEN_TYPING); + } + return 1; +} + +void cursor_unhide(struct sway_cursor *cursor, + enum sway_cursor_hidden_reason reason) { + if (reason == CURSOR_VISIBLE) { + cursor->hidden = CURSOR_VISIBLE; + } else { + cursor->hidden &= ~reason; + } + if (cursor->hidden == CURSOR_VISIBLE) { + if (cursor->image_surface) { + cursor_set_image_surface(cursor, + cursor->image_surface, + cursor->hotspot_x, + cursor->hotspot_y, + cursor->image_client); + } else { + const char *image = cursor->image; + cursor->image = NULL; + cursor_set_image(cursor, image, cursor->image_client); + } + cursor_rebase(cursor); } - cursor_rebase(cursor); } static void cursor_motion(struct sway_cursor *cursor, uint32_t time_msec, @@ -587,7 +632,7 @@ void cursor_set_image(struct sway_cursor *cursor, const char *image, cursor->hotspot_x = cursor->hotspot_y = 0; cursor->image_client = client; - if (cursor->hidden) { + if (cursor->hidden != CURSOR_VISIBLE) { return; } @@ -612,7 +657,7 @@ void cursor_set_image_surface(struct sway_cursor *cursor, cursor->hotspot_y = hotspot_y; cursor->image_client = client; - if (cursor->hidden) { + if (cursor->hidden != CURSOR_VISIBLE) { return; } @@ -625,6 +670,7 @@ void sway_cursor_destroy(struct sway_cursor *cursor) { } wl_event_source_remove(cursor->hide_source); + wl_event_source_remove(cursor->hide_source_typing); wl_list_remove(&cursor->motion.link); wl_list_remove(&cursor->motion_absolute.link); @@ -664,6 +710,8 @@ struct sway_cursor *sway_cursor_create(struct sway_seat *seat) { cursor->hide_source = wl_event_loop_add_timer(server.wl_event_loop, hide_notify, cursor); + cursor->hide_source_typing = wl_event_loop_add_timer(server.wl_event_loop, + handle_typing_unhide, cursor); // input events wl_signal_add(&wlr_cursor->events.motion, &cursor->motion); diff --git a/sway/input/keyboard.c b/sway/input/keyboard.c index 06ae99c49..72204b0f8 100644 --- a/sway/input/keyboard.c +++ b/sway/input/keyboard.c @@ -9,6 +9,7 @@ #include "sway/commands.h" #include "sway/desktop/transaction.h" #include "sway/input/input-manager.h" +#include "sway/input/cursor.h" #include "sway/input/keyboard.h" #include "sway/input/seat.h" #include "sway/ipc-server.h" @@ -396,6 +397,19 @@ static void handle_keyboard_key(struct wl_listener *listener, void *data) { event->keycode, event->state); } + // Handle hiding the cursor while typing + int timeout = cursor_get_timeout(seat->cursor, CURSOR_HIDDEN_TYPING); + if (timeout > 0) { + if (event->state == WLR_KEY_PRESSED) { + cursor_hide(seat->cursor, CURSOR_HIDDEN_TYPING); + wl_event_source_timer_update(seat->cursor->hide_source_typing, 0); + } else if (event->state == WLR_KEY_RELEASED + && keyboard->state_keycodes.npressed == 0) { + wl_event_source_timer_update(seat->cursor->hide_source_typing, + timeout); + } + } + transaction_commit_dirty(); free(device_identifier); diff --git a/sway/input/seat.c b/sway/input/seat.c index d58ff9e6f..e1275705e 100644 --- a/sway/input/seat.c +++ b/sway/input/seat.c @@ -507,6 +507,7 @@ static void seat_reset_input_config(struct sway_seat *seat, sway_device->input_device->identifier); wlr_cursor_map_input_to_output(seat->cursor->cursor, sway_device->input_device->wlr_device, NULL); + cursor_unhide(seat->cursor, CURSOR_VISIBLE); } static void seat_apply_input_config(struct sway_seat *seat, @@ -1207,9 +1208,10 @@ void seat_consider_warp_to_focus(struct sway_seat *seat) { } else { cursor_warp_to_workspace(seat->cursor, focus->sway_workspace); } - if (seat->cursor->hidden){ - cursor_unhide(seat->cursor); - wl_event_source_timer_update(seat->cursor->hide_source, cursor_get_timeout(seat->cursor)); + if (seat->cursor->hidden != CURSOR_VISIBLE) { + cursor_unhide(seat->cursor, CURSOR_VISIBLE); + wl_event_source_timer_update(seat->cursor->hide_source, + cursor_get_timeout(seat->cursor, CURSOR_HIDDEN_IDLE)); } } diff --git a/sway/meson.build b/sway/meson.build index 12b86efb8..21c778661 100644 --- a/sway/meson.build +++ b/sway/meson.build @@ -86,6 +86,7 @@ sway_sources = files( 'commands/seat/cursor.c', 'commands/seat/fallback.c', 'commands/seat/hide_cursor.c', + 'commands/seat/hide_cursor_while_typing.c', 'commands/seat/pointer_constraint.c', 'commands/set.c', 'commands/show_marks.c', diff --git a/sway/sway-input.5.scd b/sway/sway-input.5.scd index 8c3a8225f..8dcb4fa29 100644 --- a/sway/sway-input.5.scd +++ b/sway/sway-input.5.scd @@ -184,7 +184,16 @@ correct seat. Hides the cursor image after the specified _timeout_ (in milliseconds) has elapsed with no activity on that cursor. A timeout of 0 (default) disables hiding the cursor. The minimal timeout is 100 and any value less - than that (aside from 0), will be increased to 100. + than that (aside from 0), will be increased to 100. This may be used in + conjunction with _hide_cursor_while_typing_ to have complex cursor hiding + rules. + +*seat* hide_cursor_while_typing + Hides the cursor image while typing. The cursor image will be hidden until + the timeout expires after the last key is released. A timeout of 0 + (default) disables hiding the cursor. The minimal timeout is 100 and any + value less than that (aside from 0), will be increased to 100. This may be + used in conjunction with _hide_cursor_ to have complex cursor hiding rules *seat* pointer_constraint enable|disable|escape Enables or disables the ability for clients to capture the cursor (enabled