2019-06-19 10:04:47 +02:00
|
|
|
#include "input.h"
|
|
|
|
|
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
#include <signal.h>
|
|
|
|
|
#include <threads.h>
|
2019-07-09 10:00:54 +02:00
|
|
|
#include <locale.h>
|
2020-07-15 13:34:32 +02:00
|
|
|
#include <errno.h>
|
2021-11-20 19:24:00 +01:00
|
|
|
#include <wctype.h>
|
2019-06-19 10:04:47 +02:00
|
|
|
#include <sys/mman.h>
|
2019-08-05 19:33:01 +02:00
|
|
|
#include <sys/timerfd.h>
|
2020-02-29 12:52:55 +01:00
|
|
|
#include <sys/epoll.h>
|
2020-07-15 13:34:32 +02:00
|
|
|
#include <fcntl.h>
|
2019-06-19 10:04:47 +02:00
|
|
|
|
2019-07-10 14:17:44 +02:00
|
|
|
#include <linux/input-event-codes.h>
|
|
|
|
|
|
2019-06-19 10:04:47 +02:00
|
|
|
#include <xkbcommon/xkbcommon.h>
|
|
|
|
|
#include <xkbcommon/xkbcommon-keysyms.h>
|
2019-07-09 10:00:54 +02:00
|
|
|
#include <xkbcommon/xkbcommon-compose.h>
|
2019-06-19 10:04:47 +02:00
|
|
|
|
2020-02-25 20:31:13 +01:00
|
|
|
#include <xdg-shell.h>
|
|
|
|
|
|
2019-06-19 10:04:47 +02:00
|
|
|
#define LOG_MODULE "input"
|
2020-12-02 18:52:50 +01:00
|
|
|
#define LOG_ENABLE_DBG 0
|
2019-06-19 10:04:47 +02:00
|
|
|
#include "log.h"
|
2019-07-09 16:26:36 +02:00
|
|
|
#include "commands.h"
|
osc: add support for OSC 133;A (prompt markers)
This patch adds support for the OSC-133;A sequence, introduced by
FinalTerm and implemented by iTerm2, Kitty and more. See
https://iterm2.com/documentation-one-page.html#documentation-escape-codes.html.
The shell emits the OSC just before printing the prompt. This lets the
terminal know where, in the scrollback, there are prompts.
We implement this using a simple boolean in the row struct ("this row
has a prompt"). The prompt marker must be reflowed along with the text
on window resizes.
In an ideal world, erasing, or overwriting the cell where the OSC was
emitted, would remove the prompt mark. Since we don't store this
information in the cell struct, we can't do that. The best we can do
is reset it in erase_line(). This works well enough in the "normal"
screen, when used with a "normal" shell. It doesn't really work in
fullscreen apps, on the alt screen. But that doesn't matter since we
don't support jumping between prompts on the alt screen anyway.
To be able to jump between prompts, two new key bindings have been
added: prompt-prev and prompt-next, bound to ctrl+shift+z and
ctrl+shift+x respectively.
prompt-prev will jump to the previous, not currently visible, prompt,
by moving the viewport, ensuring the prompt is at the top of the
screen.
prompt-next jumps to the next prompt, visible or not. Again, by moving
the viewport to ensure the prompt is at the top of the screen. If
we're at the bottom of the scrollback, the viewport is instead moved
as far down as possible.
Closes #30
2022-06-15 18:44:23 +02:00
|
|
|
#include "config.h"
|
|
|
|
|
#include "grid.h"
|
2019-08-27 17:23:28 +02:00
|
|
|
#include "keymap.h"
|
2021-12-11 14:51:56 +01:00
|
|
|
#include "kitty-keymap.h"
|
2020-08-07 20:42:34 +01:00
|
|
|
#include "macros.h"
|
2020-03-02 20:29:28 +01:00
|
|
|
#include "quirks.h"
|
2019-08-27 17:23:28 +02:00
|
|
|
#include "render.h"
|
|
|
|
|
#include "search.h"
|
2019-07-11 09:51:51 +02:00
|
|
|
#include "selection.h"
|
2020-07-15 13:34:32 +02:00
|
|
|
#include "spawn.h"
|
2019-08-27 17:23:28 +02:00
|
|
|
#include "terminal.h"
|
2020-07-15 13:34:32 +02:00
|
|
|
#include "tokenize.h"
|
2022-07-28 18:09:16 +02:00
|
|
|
#include "unicode-mode.h"
|
2021-01-31 11:12:07 +01:00
|
|
|
#include "url-mode.h"
|
2020-05-01 11:46:24 +02:00
|
|
|
#include "util.h"
|
2019-07-15 15:42:21 +02:00
|
|
|
#include "vt.h"
|
2025-05-18 11:35:27 +02:00
|
|
|
#include "xkbcommon-vmod.h"
|
2020-08-08 20:34:30 +01:00
|
|
|
#include "xmalloc.h"
|
2021-01-14 21:30:06 +00:00
|
|
|
#include "xsnprintf.h"
|
2019-06-19 10:04:47 +02:00
|
|
|
|
2020-07-15 13:34:32 +02:00
|
|
|
struct pipe_context {
|
|
|
|
|
char *text;
|
|
|
|
|
size_t idx;
|
|
|
|
|
size_t left;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
fdm_write_pipe(struct fdm *fdm, int fd, int events, void *data)
|
|
|
|
|
{
|
|
|
|
|
struct pipe_context *ctx = data;
|
|
|
|
|
|
|
|
|
|
if (events & EPOLLHUP)
|
|
|
|
|
goto pipe_closed;
|
|
|
|
|
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(events & EPOLLOUT);
|
2020-07-15 13:34:32 +02:00
|
|
|
ssize_t written = write(fd, &ctx->text[ctx->idx], ctx->left);
|
|
|
|
|
|
|
|
|
|
if (written < 0) {
|
|
|
|
|
LOG_WARN("failed to write to pipe: %s", strerror(errno));
|
|
|
|
|
goto pipe_closed;
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(written <= ctx->left);
|
2020-07-15 13:34:32 +02:00
|
|
|
ctx->idx += written;
|
|
|
|
|
ctx->left -= written;
|
|
|
|
|
|
|
|
|
|
if (ctx->left == 0)
|
|
|
|
|
goto pipe_closed;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
pipe_closed:
|
|
|
|
|
free(ctx->text);
|
|
|
|
|
free(ctx);
|
|
|
|
|
fdm_del(fdm, fd);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-18 16:36:39 +02:00
|
|
|
static void alternate_scroll(struct seat *seat, int amount, int button);
|
|
|
|
|
|
2020-08-22 13:35:36 +02:00
|
|
|
static bool
|
2020-07-08 18:41:09 +02:00
|
|
|
execute_binding(struct seat *seat, struct terminal *term,
|
2023-09-18 16:36:39 +02:00
|
|
|
const struct key_binding *binding, uint32_t serial, int amount)
|
2020-03-08 12:08:46 +01:00
|
|
|
{
|
2022-02-08 19:43:00 +01:00
|
|
|
const enum bind_action_normal action = binding->action;
|
|
|
|
|
|
2020-03-08 12:08:46 +01:00
|
|
|
switch (action) {
|
2020-03-12 10:46:27 +01:00
|
|
|
case BIND_ACTION_NONE:
|
2020-08-22 13:35:36 +02:00
|
|
|
return true;
|
2020-03-12 10:46:27 +01:00
|
|
|
|
2021-10-24 11:49:37 +02:00
|
|
|
case BIND_ACTION_NOOP:
|
|
|
|
|
return true;
|
|
|
|
|
|
2020-09-10 18:17:47 +02:00
|
|
|
case BIND_ACTION_SCROLLBACK_UP_PAGE:
|
2021-06-04 23:25:45 +02:00
|
|
|
if (term->grid == &term->normal) {
|
|
|
|
|
cmd_scrollback_up(term, term->rows);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
break;
|
2020-03-08 15:17:29 +01:00
|
|
|
|
2020-09-10 18:17:47 +02:00
|
|
|
case BIND_ACTION_SCROLLBACK_UP_HALF_PAGE:
|
2021-06-04 23:25:45 +02:00
|
|
|
if (term->grid == &term->normal) {
|
|
|
|
|
cmd_scrollback_up(term, max(term->rows / 2, 1));
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
break;
|
2020-09-09 19:40:48 +02:00
|
|
|
|
2020-09-10 18:17:47 +02:00
|
|
|
case BIND_ACTION_SCROLLBACK_UP_LINE:
|
2021-06-04 23:25:45 +02:00
|
|
|
if (term->grid == &term->normal) {
|
|
|
|
|
cmd_scrollback_up(term, 1);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
break;
|
2020-09-10 18:17:47 +02:00
|
|
|
|
2023-09-18 16:36:39 +02:00
|
|
|
case BIND_ACTION_SCROLLBACK_UP_MOUSE:
|
|
|
|
|
if (term->grid == &term->alt) {
|
|
|
|
|
if (term->alt_scrolling)
|
|
|
|
|
alternate_scroll(seat, amount, BTN_BACK);
|
|
|
|
|
} else
|
|
|
|
|
cmd_scrollback_up(term, amount);
|
|
|
|
|
break;
|
|
|
|
|
|
2020-09-10 18:17:47 +02:00
|
|
|
case BIND_ACTION_SCROLLBACK_DOWN_PAGE:
|
2021-06-04 23:25:45 +02:00
|
|
|
if (term->grid == &term->normal) {
|
|
|
|
|
cmd_scrollback_down(term, term->rows);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
break;
|
2020-03-08 15:17:29 +01:00
|
|
|
|
2020-09-10 18:17:47 +02:00
|
|
|
case BIND_ACTION_SCROLLBACK_DOWN_HALF_PAGE:
|
2021-06-04 23:25:45 +02:00
|
|
|
if (term->grid == &term->normal) {
|
|
|
|
|
cmd_scrollback_down(term, max(term->rows / 2, 1));
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
break;
|
2020-09-09 19:40:48 +02:00
|
|
|
|
2020-09-10 18:17:47 +02:00
|
|
|
case BIND_ACTION_SCROLLBACK_DOWN_LINE:
|
2021-06-04 23:25:45 +02:00
|
|
|
if (term->grid == &term->normal) {
|
|
|
|
|
cmd_scrollback_down(term, 1);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
break;
|
2020-09-10 18:17:47 +02:00
|
|
|
|
2023-09-18 16:36:39 +02:00
|
|
|
case BIND_ACTION_SCROLLBACK_DOWN_MOUSE:
|
|
|
|
|
if (term->grid == &term->alt) {
|
|
|
|
|
if (term->alt_scrolling)
|
|
|
|
|
alternate_scroll(seat, amount, BTN_FORWARD);
|
|
|
|
|
} else
|
|
|
|
|
cmd_scrollback_down(term, amount);
|
|
|
|
|
break;
|
|
|
|
|
|
2022-02-23 19:03:54 +01:00
|
|
|
case BIND_ACTION_SCROLLBACK_HOME:
|
|
|
|
|
if (term->grid == &term->normal) {
|
|
|
|
|
cmd_scrollback_up(term, term->grid->num_rows);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case BIND_ACTION_SCROLLBACK_END:
|
|
|
|
|
if (term->grid == &term->normal) {
|
|
|
|
|
cmd_scrollback_down(term, term->grid->num_rows);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
2020-03-08 12:08:46 +01:00
|
|
|
case BIND_ACTION_CLIPBOARD_COPY:
|
2020-07-08 18:41:09 +02:00
|
|
|
selection_to_clipboard(seat, term, serial);
|
2020-08-22 13:35:36 +02:00
|
|
|
return true;
|
2020-03-08 12:08:46 +01:00
|
|
|
|
|
|
|
|
case BIND_ACTION_CLIPBOARD_PASTE:
|
2020-07-08 18:41:09 +02:00
|
|
|
selection_from_clipboard(seat, term, serial);
|
2020-03-08 12:08:46 +01:00
|
|
|
term_reset_view(term);
|
2020-08-22 13:35:36 +02:00
|
|
|
return true;
|
2020-03-08 12:08:46 +01:00
|
|
|
|
|
|
|
|
case BIND_ACTION_PRIMARY_PASTE:
|
2020-07-08 18:41:09 +02:00
|
|
|
selection_from_primary(seat, term);
|
2022-01-11 20:26:49 +01:00
|
|
|
term_reset_view(term);
|
2020-08-22 13:35:36 +02:00
|
|
|
return true;
|
2020-03-08 12:08:46 +01:00
|
|
|
|
|
|
|
|
case BIND_ACTION_SEARCH_START:
|
|
|
|
|
search_begin(term);
|
2020-08-22 13:35:36 +02:00
|
|
|
return true;
|
2020-03-08 12:08:46 +01:00
|
|
|
|
2020-03-08 15:17:29 +01:00
|
|
|
case BIND_ACTION_FONT_SIZE_UP:
|
|
|
|
|
term_font_size_increase(term);
|
2020-08-22 13:35:36 +02:00
|
|
|
return true;
|
2020-03-08 15:17:29 +01:00
|
|
|
|
|
|
|
|
case BIND_ACTION_FONT_SIZE_DOWN:
|
|
|
|
|
term_font_size_decrease(term);
|
2020-08-22 13:35:36 +02:00
|
|
|
return true;
|
2020-03-08 15:17:29 +01:00
|
|
|
|
|
|
|
|
case BIND_ACTION_FONT_SIZE_RESET:
|
|
|
|
|
term_font_size_reset(term);
|
2020-08-22 13:35:36 +02:00
|
|
|
return true;
|
2020-03-08 15:17:29 +01:00
|
|
|
|
|
|
|
|
case BIND_ACTION_SPAWN_TERMINAL:
|
|
|
|
|
term_spawn_new(term);
|
2020-08-22 13:35:36 +02:00
|
|
|
return true;
|
2020-03-08 15:17:29 +01:00
|
|
|
|
2020-03-12 09:34:09 +01:00
|
|
|
case BIND_ACTION_MINIMIZE:
|
|
|
|
|
xdg_toplevel_set_minimized(term->window->xdg_toplevel);
|
2020-08-22 13:35:36 +02:00
|
|
|
return true;
|
2020-03-12 09:34:09 +01:00
|
|
|
|
|
|
|
|
case BIND_ACTION_MAXIMIZE:
|
2020-03-12 17:47:53 +01:00
|
|
|
if (term->window->is_fullscreen)
|
|
|
|
|
xdg_toplevel_unset_fullscreen(term->window->xdg_toplevel);
|
2020-03-12 09:34:09 +01:00
|
|
|
if (term->window->is_maximized)
|
|
|
|
|
xdg_toplevel_unset_maximized(term->window->xdg_toplevel);
|
|
|
|
|
else
|
|
|
|
|
xdg_toplevel_set_maximized(term->window->xdg_toplevel);
|
2020-08-22 13:35:36 +02:00
|
|
|
return true;
|
2020-03-12 09:34:09 +01:00
|
|
|
|
|
|
|
|
case BIND_ACTION_FULLSCREEN:
|
|
|
|
|
if (term->window->is_fullscreen)
|
|
|
|
|
xdg_toplevel_unset_fullscreen(term->window->xdg_toplevel);
|
|
|
|
|
else
|
|
|
|
|
xdg_toplevel_set_fullscreen(term->window->xdg_toplevel, NULL);
|
2020-08-22 13:35:36 +02:00
|
|
|
return true;
|
2020-03-12 09:34:09 +01:00
|
|
|
|
2020-07-15 09:46:13 +02:00
|
|
|
case BIND_ACTION_PIPE_SCROLLBACK:
|
2021-06-04 23:25:45 +02:00
|
|
|
if (term->grid == &term->alt)
|
|
|
|
|
break;
|
|
|
|
|
/* FALLTHROUGH */
|
2020-07-31 17:02:53 +02:00
|
|
|
case BIND_ACTION_PIPE_VIEW:
|
2022-12-08 11:45:23 +01:00
|
|
|
case BIND_ACTION_PIPE_SELECTED:
|
|
|
|
|
case BIND_ACTION_PIPE_COMMAND_OUTPUT: {
|
2022-02-08 19:43:00 +01:00
|
|
|
if (binding->aux->type != BINDING_AUX_PIPE)
|
2020-08-22 13:35:36 +02:00
|
|
|
return true;
|
2020-07-15 13:34:32 +02:00
|
|
|
|
|
|
|
|
struct pipe_context *ctx = NULL;
|
|
|
|
|
|
2020-07-22 19:34:07 +02:00
|
|
|
int pipe_fd[2] = {-1, -1};
|
|
|
|
|
int stdout_fd = -1;
|
|
|
|
|
int stderr_fd = -1;
|
|
|
|
|
|
|
|
|
|
char *text = NULL;
|
|
|
|
|
size_t len = 0;
|
|
|
|
|
|
2020-07-15 13:34:32 +02:00
|
|
|
if (pipe(pipe_fd) < 0) {
|
|
|
|
|
LOG_ERRNO("failed to create pipe");
|
|
|
|
|
goto pipe_err;
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-22 19:34:07 +02:00
|
|
|
stdout_fd = open("/dev/null", O_WRONLY);
|
|
|
|
|
stderr_fd = open("/dev/null", O_WRONLY);
|
2020-07-15 18:17:28 +02:00
|
|
|
|
|
|
|
|
if (stdout_fd < 0 || stderr_fd < 0) {
|
|
|
|
|
LOG_ERRNO("failed to open /dev/null");
|
|
|
|
|
goto pipe_err;
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-31 17:02:53 +02:00
|
|
|
bool success;
|
|
|
|
|
switch (action) {
|
|
|
|
|
case BIND_ACTION_PIPE_SCROLLBACK:
|
|
|
|
|
success = term_scrollback_to_text(term, &text, &len);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case BIND_ACTION_PIPE_VIEW:
|
|
|
|
|
success = term_view_to_text(term, &text, &len);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case BIND_ACTION_PIPE_SELECTED:
|
|
|
|
|
text = selection_to_text(term);
|
|
|
|
|
success = text != NULL;
|
|
|
|
|
len = text != NULL ? strlen(text) : 0;
|
|
|
|
|
break;
|
|
|
|
|
|
2022-12-08 11:45:23 +01:00
|
|
|
case BIND_ACTION_PIPE_COMMAND_OUTPUT:
|
|
|
|
|
success = term_command_output_to_text(term, &text, &len);
|
|
|
|
|
break;
|
|
|
|
|
|
2020-07-31 17:02:53 +02:00
|
|
|
default:
|
2021-02-09 13:52:33 +00:00
|
|
|
BUG("Unhandled action type");
|
2020-07-31 17:02:53 +02:00
|
|
|
success = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2020-07-15 13:34:32 +02:00
|
|
|
|
|
|
|
|
if (!success)
|
|
|
|
|
goto pipe_err;
|
|
|
|
|
|
|
|
|
|
/* Make write-end non-blocking; required by the FDM */
|
|
|
|
|
{
|
|
|
|
|
int flags = fcntl(pipe_fd[1], F_GETFL);
|
|
|
|
|
if (flags < 0 ||
|
|
|
|
|
fcntl(pipe_fd[1], F_SETFL, flags | O_NONBLOCK) < 0)
|
|
|
|
|
{
|
|
|
|
|
LOG_ERRNO("failed to make write-end of pipe non-blocking");
|
|
|
|
|
goto pipe_err;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-15 16:47:01 +02:00
|
|
|
/* Make sure write-end is closed on exec() - or the spawned
|
|
|
|
|
* program may not terminate*/
|
2020-07-15 13:34:32 +02:00
|
|
|
{
|
|
|
|
|
int flags = fcntl(pipe_fd[1], F_GETFD);
|
|
|
|
|
if (flags < 0 ||
|
|
|
|
|
fcntl(pipe_fd[1], F_SETFD, flags | FD_CLOEXEC) < 0)
|
|
|
|
|
{
|
|
|
|
|
LOG_ERRNO("failed to set FD_CLOEXEC on writeend of pipe");
|
|
|
|
|
goto pipe_err;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-23 06:57:30 +02:00
|
|
|
if (spawn(term->reaper, term->cwd, binding->aux->pipe.args,
|
|
|
|
|
pipe_fd[0], stdout_fd, stderr_fd, NULL, NULL, NULL) < 0)
|
2020-07-15 13:34:32 +02:00
|
|
|
goto pipe_err;
|
|
|
|
|
|
|
|
|
|
/* Close read end */
|
|
|
|
|
close(pipe_fd[0]);
|
|
|
|
|
|
2020-08-08 20:34:30 +01:00
|
|
|
ctx = xmalloc(sizeof(*ctx));
|
2020-07-15 13:34:32 +02:00
|
|
|
*ctx = (struct pipe_context){
|
|
|
|
|
.text = text,
|
|
|
|
|
.left = len,
|
|
|
|
|
};
|
|
|
|
|
|
2020-07-15 16:47:01 +02:00
|
|
|
/* Asynchronously write the output to the pipe */
|
2020-07-15 13:34:32 +02:00
|
|
|
if (!fdm_add(term->fdm, pipe_fd[1], EPOLLOUT, &fdm_write_pipe, ctx))
|
|
|
|
|
goto pipe_err;
|
|
|
|
|
|
2020-08-22 13:35:36 +02:00
|
|
|
return true;
|
2020-07-15 09:46:13 +02:00
|
|
|
|
2020-08-22 13:35:36 +02:00
|
|
|
pipe_err:
|
2020-07-15 18:17:28 +02:00
|
|
|
if (stdout_fd >= 0)
|
|
|
|
|
close(stdout_fd);
|
|
|
|
|
if (stderr_fd >= 0)
|
|
|
|
|
close(stderr_fd);
|
2020-07-15 13:34:32 +02:00
|
|
|
if (pipe_fd[0] >= 0)
|
|
|
|
|
close(pipe_fd[0]);
|
|
|
|
|
if (pipe_fd[1] >= 0)
|
|
|
|
|
close(pipe_fd[1]);
|
|
|
|
|
free(text);
|
|
|
|
|
free(ctx);
|
2020-08-22 13:35:36 +02:00
|
|
|
return true;
|
2020-07-15 13:34:32 +02:00
|
|
|
}
|
|
|
|
|
|
2021-02-04 20:55:08 +01:00
|
|
|
case BIND_ACTION_SHOW_URLS_COPY:
|
2022-03-22 19:07:06 +01:00
|
|
|
case BIND_ACTION_SHOW_URLS_LAUNCH:
|
|
|
|
|
case BIND_ACTION_SHOW_URLS_PERSISTENT: {
|
2021-01-31 11:12:07 +01:00
|
|
|
xassert(!urls_mode_is_active(term));
|
|
|
|
|
|
2022-03-22 19:07:06 +01:00
|
|
|
enum url_action url_action =
|
|
|
|
|
action == BIND_ACTION_SHOW_URLS_COPY ? URL_ACTION_COPY :
|
|
|
|
|
action == BIND_ACTION_SHOW_URLS_LAUNCH ? URL_ACTION_LAUNCH :
|
|
|
|
|
URL_ACTION_PERSISTENT;
|
2021-02-04 20:55:08 +01:00
|
|
|
|
2025-02-03 08:55:47 +01:00
|
|
|
urls_collect(term, url_action, &term->conf->url.preg, true, &term->urls);
|
2021-02-13 11:43:28 +01:00
|
|
|
urls_assign_key_combos(term->conf, &term->urls);
|
2025-02-03 08:55:47 +01:00
|
|
|
urls_render(term, &term->conf->url.launch);
|
2021-01-31 11:12:07 +01:00
|
|
|
return true;
|
2021-02-04 20:55:08 +01:00
|
|
|
}
|
2021-01-30 12:43:59 +01:00
|
|
|
|
2022-02-06 19:36:44 +01:00
|
|
|
case BIND_ACTION_TEXT_BINDING:
|
|
|
|
|
xassert(binding->aux->type == BINDING_AUX_TEXT);
|
|
|
|
|
term_to_slave(term, binding->aux->text.data, binding->aux->text.len);
|
|
|
|
|
return true;
|
|
|
|
|
|
osc: add support for OSC 133;A (prompt markers)
This patch adds support for the OSC-133;A sequence, introduced by
FinalTerm and implemented by iTerm2, Kitty and more. See
https://iterm2.com/documentation-one-page.html#documentation-escape-codes.html.
The shell emits the OSC just before printing the prompt. This lets the
terminal know where, in the scrollback, there are prompts.
We implement this using a simple boolean in the row struct ("this row
has a prompt"). The prompt marker must be reflowed along with the text
on window resizes.
In an ideal world, erasing, or overwriting the cell where the OSC was
emitted, would remove the prompt mark. Since we don't store this
information in the cell struct, we can't do that. The best we can do
is reset it in erase_line(). This works well enough in the "normal"
screen, when used with a "normal" shell. It doesn't really work in
fullscreen apps, on the alt screen. But that doesn't matter since we
don't support jumping between prompts on the alt screen anyway.
To be able to jump between prompts, two new key bindings have been
added: prompt-prev and prompt-next, bound to ctrl+shift+z and
ctrl+shift+x respectively.
prompt-prev will jump to the previous, not currently visible, prompt,
by moving the viewport, ensuring the prompt is at the top of the
screen.
prompt-next jumps to the next prompt, visible or not. Again, by moving
the viewport to ensure the prompt is at the top of the screen. If
we're at the bottom of the scrollback, the viewport is instead moved
as far down as possible.
Closes #30
2022-06-15 18:44:23 +02:00
|
|
|
case BIND_ACTION_PROMPT_PREV: {
|
|
|
|
|
if (term->grid != &term->normal)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
struct grid *grid = term->grid;
|
|
|
|
|
const int sb_start =
|
|
|
|
|
grid_sb_start_ignore_uninitialized(grid, term->rows);
|
|
|
|
|
|
|
|
|
|
/* Check each row from current view-1 (that is, the first
|
|
|
|
|
* currently not visible row), up to, and including, the
|
|
|
|
|
* scrollback start */
|
|
|
|
|
for (int r_sb_rel =
|
|
|
|
|
grid_row_abs_to_sb_precalc_sb_start(
|
|
|
|
|
grid, sb_start, grid->view) - 1;
|
|
|
|
|
r_sb_rel >= 0; r_sb_rel--)
|
|
|
|
|
{
|
|
|
|
|
const int r_abs =
|
|
|
|
|
grid_row_sb_to_abs_precalc_sb_start(grid, sb_start, r_sb_rel);
|
|
|
|
|
|
|
|
|
|
const struct row *row = grid->rows[r_abs];
|
|
|
|
|
xassert(row != NULL);
|
|
|
|
|
|
2022-12-08 10:35:30 +01:00
|
|
|
if (!row->shell_integration.prompt_marker)
|
osc: add support for OSC 133;A (prompt markers)
This patch adds support for the OSC-133;A sequence, introduced by
FinalTerm and implemented by iTerm2, Kitty and more. See
https://iterm2.com/documentation-one-page.html#documentation-escape-codes.html.
The shell emits the OSC just before printing the prompt. This lets the
terminal know where, in the scrollback, there are prompts.
We implement this using a simple boolean in the row struct ("this row
has a prompt"). The prompt marker must be reflowed along with the text
on window resizes.
In an ideal world, erasing, or overwriting the cell where the OSC was
emitted, would remove the prompt mark. Since we don't store this
information in the cell struct, we can't do that. The best we can do
is reset it in erase_line(). This works well enough in the "normal"
screen, when used with a "normal" shell. It doesn't really work in
fullscreen apps, on the alt screen. But that doesn't matter since we
don't support jumping between prompts on the alt screen anyway.
To be able to jump between prompts, two new key bindings have been
added: prompt-prev and prompt-next, bound to ctrl+shift+z and
ctrl+shift+x respectively.
prompt-prev will jump to the previous, not currently visible, prompt,
by moving the viewport, ensuring the prompt is at the top of the
screen.
prompt-next jumps to the next prompt, visible or not. Again, by moving
the viewport to ensure the prompt is at the top of the screen. If
we're at the bottom of the scrollback, the viewport is instead moved
as far down as possible.
Closes #30
2022-06-15 18:44:23 +02:00
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
grid->view = r_abs;
|
|
|
|
|
term_damage_view(term);
|
|
|
|
|
render_refresh(term);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case BIND_ACTION_PROMPT_NEXT: {
|
|
|
|
|
if (term->grid != &term->normal)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
struct grid *grid = term->grid;
|
|
|
|
|
const int num_rows = grid->num_rows;
|
|
|
|
|
|
|
|
|
|
if (grid->view == grid->offset) {
|
|
|
|
|
/* Already at the bottom */
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Check each row from view+1, to the bottom of the scrollback */
|
|
|
|
|
for (int r_abs = (grid->view + 1) & (num_rows - 1);
|
|
|
|
|
;
|
|
|
|
|
r_abs = (r_abs + 1) & (num_rows - 1))
|
|
|
|
|
{
|
|
|
|
|
const struct row *row = grid->rows[r_abs];
|
|
|
|
|
xassert(row != NULL);
|
|
|
|
|
|
2022-12-08 10:35:30 +01:00
|
|
|
if (!row->shell_integration.prompt_marker) {
|
osc: add support for OSC 133;A (prompt markers)
This patch adds support for the OSC-133;A sequence, introduced by
FinalTerm and implemented by iTerm2, Kitty and more. See
https://iterm2.com/documentation-one-page.html#documentation-escape-codes.html.
The shell emits the OSC just before printing the prompt. This lets the
terminal know where, in the scrollback, there are prompts.
We implement this using a simple boolean in the row struct ("this row
has a prompt"). The prompt marker must be reflowed along with the text
on window resizes.
In an ideal world, erasing, or overwriting the cell where the OSC was
emitted, would remove the prompt mark. Since we don't store this
information in the cell struct, we can't do that. The best we can do
is reset it in erase_line(). This works well enough in the "normal"
screen, when used with a "normal" shell. It doesn't really work in
fullscreen apps, on the alt screen. But that doesn't matter since we
don't support jumping between prompts on the alt screen anyway.
To be able to jump between prompts, two new key bindings have been
added: prompt-prev and prompt-next, bound to ctrl+shift+z and
ctrl+shift+x respectively.
prompt-prev will jump to the previous, not currently visible, prompt,
by moving the viewport, ensuring the prompt is at the top of the
screen.
prompt-next jumps to the next prompt, visible or not. Again, by moving
the viewport to ensure the prompt is at the top of the screen. If
we're at the bottom of the scrollback, the viewport is instead moved
as far down as possible.
Closes #30
2022-06-15 18:44:23 +02:00
|
|
|
if (r_abs == grid->offset + term->rows - 1) {
|
2024-02-06 12:36:45 +01:00
|
|
|
/* We've reached the bottom of the scrollback */
|
osc: add support for OSC 133;A (prompt markers)
This patch adds support for the OSC-133;A sequence, introduced by
FinalTerm and implemented by iTerm2, Kitty and more. See
https://iterm2.com/documentation-one-page.html#documentation-escape-codes.html.
The shell emits the OSC just before printing the prompt. This lets the
terminal know where, in the scrollback, there are prompts.
We implement this using a simple boolean in the row struct ("this row
has a prompt"). The prompt marker must be reflowed along with the text
on window resizes.
In an ideal world, erasing, or overwriting the cell where the OSC was
emitted, would remove the prompt mark. Since we don't store this
information in the cell struct, we can't do that. The best we can do
is reset it in erase_line(). This works well enough in the "normal"
screen, when used with a "normal" shell. It doesn't really work in
fullscreen apps, on the alt screen. But that doesn't matter since we
don't support jumping between prompts on the alt screen anyway.
To be able to jump between prompts, two new key bindings have been
added: prompt-prev and prompt-next, bound to ctrl+shift+z and
ctrl+shift+x respectively.
prompt-prev will jump to the previous, not currently visible, prompt,
by moving the viewport, ensuring the prompt is at the top of the
screen.
prompt-next jumps to the next prompt, visible or not. Again, by moving
the viewport to ensure the prompt is at the top of the screen. If
we're at the bottom of the scrollback, the viewport is instead moved
as far down as possible.
Closes #30
2022-06-15 18:44:23 +02:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int sb_start = grid_sb_start_ignore_uninitialized(grid, term->rows);
|
|
|
|
|
int ofs_sb_rel =
|
|
|
|
|
grid_row_abs_to_sb_precalc_sb_start(grid, sb_start, grid->offset);
|
|
|
|
|
int new_view_sb_rel =
|
|
|
|
|
grid_row_abs_to_sb_precalc_sb_start(grid, sb_start, r_abs);
|
|
|
|
|
|
|
|
|
|
new_view_sb_rel = min(ofs_sb_rel, new_view_sb_rel);
|
|
|
|
|
grid->view = grid_row_sb_to_abs_precalc_sb_start(
|
|
|
|
|
grid, sb_start, new_view_sb_rel);
|
|
|
|
|
|
|
|
|
|
term_damage_view(term);
|
|
|
|
|
render_refresh(term);
|
2024-02-22 15:08:13 -06:00
|
|
|
break;
|
osc: add support for OSC 133;A (prompt markers)
This patch adds support for the OSC-133;A sequence, introduced by
FinalTerm and implemented by iTerm2, Kitty and more. See
https://iterm2.com/documentation-one-page.html#documentation-escape-codes.html.
The shell emits the OSC just before printing the prompt. This lets the
terminal know where, in the scrollback, there are prompts.
We implement this using a simple boolean in the row struct ("this row
has a prompt"). The prompt marker must be reflowed along with the text
on window resizes.
In an ideal world, erasing, or overwriting the cell where the OSC was
emitted, would remove the prompt mark. Since we don't store this
information in the cell struct, we can't do that. The best we can do
is reset it in erase_line(). This works well enough in the "normal"
screen, when used with a "normal" shell. It doesn't really work in
fullscreen apps, on the alt screen. But that doesn't matter since we
don't support jumping between prompts on the alt screen anyway.
To be able to jump between prompts, two new key bindings have been
added: prompt-prev and prompt-next, bound to ctrl+shift+z and
ctrl+shift+x respectively.
prompt-prev will jump to the previous, not currently visible, prompt,
by moving the viewport, ensuring the prompt is at the top of the
screen.
prompt-next jumps to the next prompt, visible or not. Again, by moving
the viewport to ensure the prompt is at the top of the screen. If
we're at the bottom of the scrollback, the viewport is instead moved
as far down as possible.
Closes #30
2022-06-15 18:44:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-28 18:09:16 +02:00
|
|
|
case BIND_ACTION_UNICODE_INPUT:
|
unicode-mode: move state from seat to term
This fixes an issue where entering unicode-mode in one foot client,
also enabled unicode-mode on other foot clients. Both
visually (although glitchy), and in effect.
The reason the state was originally in the seat objects, was to fully
support multi-seat. That is, one seat/keyboard entering unicode-mode
should not affect other seats/keyboards.
The issue with this is that seat objects are Wayland global. Thus, in
server mode, all seat objects are shared between the foot clients.
There is a similarity with IME, which also keeps state in the
seat. There's one big difference, however, and that is IME has Wayland
native enter/leave events, that the compositor emits when windows are
focused/unfocused. These events allow us to reset IME state. For our
own Unicode mode, there is nothing similar.
This patch moves the Unicode state from seats, to the terminal
struct. This does mean that if one seat/keyboard enters Unicode mode,
then *all* seats/keyboards will affect the unicode state. This
potential downside is outweighed by the fact that different foot
clients no longer affect each other.
Closes #1717
2024-05-21 07:06:45 +02:00
|
|
|
unicode_mode_activate(term);
|
2022-07-28 18:09:16 +02:00
|
|
|
return true;
|
|
|
|
|
|
2024-04-10 19:26:23 +02:00
|
|
|
case BIND_ACTION_QUIT:
|
|
|
|
|
term_shutdown(term);
|
|
|
|
|
return true;
|
|
|
|
|
|
2025-02-03 08:55:47 +01:00
|
|
|
case BIND_ACTION_REGEX_LAUNCH:
|
|
|
|
|
case BIND_ACTION_REGEX_COPY:
|
|
|
|
|
if (binding->aux->type != BINDING_AUX_REGEX)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
tll_foreach(term->conf->custom_regexes, it) {
|
|
|
|
|
const struct custom_regex *regex = &it->item;
|
|
|
|
|
|
|
|
|
|
if (streq(regex->name, binding->aux->regex_name)) {
|
|
|
|
|
xassert(!urls_mode_is_active(term));
|
|
|
|
|
|
|
|
|
|
enum url_action url_action = action == BIND_ACTION_REGEX_LAUNCH
|
|
|
|
|
? URL_ACTION_LAUNCH : URL_ACTION_COPY;
|
|
|
|
|
|
|
|
|
|
if (regex->regex == NULL) {
|
|
|
|
|
LOG_ERR("regex:%s has no regex defined", regex->name);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (url_action == URL_ACTION_LAUNCH && regex->launch.argv.args == NULL) {
|
|
|
|
|
LOG_ERR("regex:%s has no launch command defined", regex->name);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
urls_collect(term, url_action, ®ex->preg, false, &term->urls);
|
|
|
|
|
urls_assign_key_combos(term->conf, &term->urls);
|
|
|
|
|
urls_render(term, ®ex->launch);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOG_ERR(
|
|
|
|
|
"no regex section named '%s' defined in the configuration",
|
|
|
|
|
binding->aux->regex_name);
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
2025-04-20 07:58:02 +02:00
|
|
|
case BIND_ACTION_THEME_SWITCH_1:
|
config: add [colors-dark] and [colors-light], replacing [colors] and [colors2]
The main reason for having two color sections is to be able to switch
between dark and light. Thus, it's better if the section names reflect
this, rather than the more generic 'colors' and 'colors2' (which was
the dark one and which was the light one, now again?)
When the second color section was added, we kept the original name,
colors, to make sure we didn't break existing configurations, and
third-party themes.
However, in the long run, it's probably better to be specific in the
section naming, to avoid confusion.
So, add 'colors-dark', and 'colors-light'. Keep 'colors' and 'colors2'
as aliases for now, but mark them as deprecated. They WILL be removed
in a future release.
Also rename the option values for initial-color-theme, from 1/2, to
dark/light. Keep the old ones for now, marked as deprecated.
Update all bundled themes to use the new names. In the light-only
themes (i.e. themes that define a single, light, theme), use
colors-light, and set initial-color-theme=light.
Possible improvements: disable color switching if only one color
section has been explicitly configured (todo: figure out how to handle
the default color theme values...)
2025-12-19 09:29:06 +01:00
|
|
|
case BIND_ACTION_THEME_SWITCH_DARK:
|
|
|
|
|
term_theme_switch_to_dark(term);
|
2025-04-20 07:58:02 +02:00
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
case BIND_ACTION_THEME_SWITCH_2:
|
config: add [colors-dark] and [colors-light], replacing [colors] and [colors2]
The main reason for having two color sections is to be able to switch
between dark and light. Thus, it's better if the section names reflect
this, rather than the more generic 'colors' and 'colors2' (which was
the dark one and which was the light one, now again?)
When the second color section was added, we kept the original name,
colors, to make sure we didn't break existing configurations, and
third-party themes.
However, in the long run, it's probably better to be specific in the
section naming, to avoid confusion.
So, add 'colors-dark', and 'colors-light'. Keep 'colors' and 'colors2'
as aliases for now, but mark them as deprecated. They WILL be removed
in a future release.
Also rename the option values for initial-color-theme, from 1/2, to
dark/light. Keep the old ones for now, marked as deprecated.
Update all bundled themes to use the new names. In the light-only
themes (i.e. themes that define a single, light, theme), use
colors-light, and set initial-color-theme=light.
Possible improvements: disable color switching if only one color
section has been explicitly configured (todo: figure out how to handle
the default color theme values...)
2025-12-19 09:29:06 +01:00
|
|
|
case BIND_ACTION_THEME_SWITCH_LIGHT:
|
|
|
|
|
term_theme_switch_to_light(term);
|
2025-04-20 07:58:02 +02:00
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
case BIND_ACTION_THEME_TOGGLE:
|
2025-06-09 07:37:29 +02:00
|
|
|
term_theme_toggle(term);
|
2025-04-20 07:58:02 +02:00
|
|
|
return true;
|
|
|
|
|
|
2020-08-11 10:53:21 +02:00
|
|
|
case BIND_ACTION_SELECT_BEGIN:
|
2021-02-02 09:52:05 +01:00
|
|
|
selection_start(
|
|
|
|
|
term, seat->mouse.col, seat->mouse.row, SELECTION_CHAR_WISE, false);
|
|
|
|
|
return true;
|
2020-08-11 09:55:33 +02:00
|
|
|
|
2020-08-11 10:53:21 +02:00
|
|
|
case BIND_ACTION_SELECT_BEGIN_BLOCK:
|
2021-02-02 09:52:05 +01:00
|
|
|
selection_start(
|
|
|
|
|
term, seat->mouse.col, seat->mouse.row, SELECTION_BLOCK, false);
|
|
|
|
|
return true;
|
2020-08-11 10:17:19 +02:00
|
|
|
|
2020-08-11 10:53:21 +02:00
|
|
|
case BIND_ACTION_SELECT_EXTEND:
|
2021-02-02 09:52:05 +01:00
|
|
|
selection_extend(
|
|
|
|
|
seat, term, seat->mouse.col, seat->mouse.row, term->selection.kind);
|
|
|
|
|
return true;
|
2021-01-06 11:11:46 +01:00
|
|
|
|
|
|
|
|
case BIND_ACTION_SELECT_EXTEND_CHAR_WISE:
|
2021-02-02 09:52:05 +01:00
|
|
|
if (term->selection.kind != SELECTION_BLOCK) {
|
2021-01-06 11:11:46 +01:00
|
|
|
selection_extend(
|
|
|
|
|
seat, term, seat->mouse.col, seat->mouse.row, SELECTION_CHAR_WISE);
|
2020-08-22 13:35:36 +02:00
|
|
|
return true;
|
2020-08-11 10:17:19 +02:00
|
|
|
}
|
2020-08-22 13:35:36 +02:00
|
|
|
return false;
|
2020-08-11 10:17:19 +02:00
|
|
|
|
2020-08-11 10:53:21 +02:00
|
|
|
case BIND_ACTION_SELECT_WORD:
|
2021-02-02 09:52:05 +01:00
|
|
|
selection_start(
|
|
|
|
|
term, seat->mouse.col, seat->mouse.row, SELECTION_WORD_WISE, false);
|
|
|
|
|
return true;
|
2020-08-11 09:55:33 +02:00
|
|
|
|
2020-08-11 10:53:21 +02:00
|
|
|
case BIND_ACTION_SELECT_WORD_WS:
|
2021-02-02 09:52:05 +01:00
|
|
|
selection_start(
|
|
|
|
|
term, seat->mouse.col, seat->mouse.row, SELECTION_WORD_WISE, true);
|
|
|
|
|
return true;
|
2020-08-11 09:55:33 +02:00
|
|
|
|
selection: add support for selecting the contents of a quote
This patch changes the default of triple clicking, from selecting the
current logical row, to first trying to select the contents of the
quote under the cursor, and if failing to find a quote, selecting the
current row (like before).
This is implemented by adding a new key binding, 'select-quote'.
It will search for surrounding quote characters, and if one is found
on each side of the cursor, the quote is selected. If not, the entire
row is selected instead.
Subsequent selection operations will behave as if the selection is
either a word selection (a quote was found), or a row selection (no
quote found).
Escaped quote characters are not supported: "foo \" bar" will match
'foo \', and not 'foo " bar'.
Mismatched quotes are not custom handled. They will simply not match.
Nested quotes ("123 'abc def' 456") are supported.
Closes #1364
2023-09-19 16:23:34 +02:00
|
|
|
case BIND_ACTION_SELECT_QUOTE:
|
|
|
|
|
selection_start(
|
|
|
|
|
term, seat->mouse.col, seat->mouse.row, SELECTION_QUOTE_WISE, false);
|
|
|
|
|
break;
|
|
|
|
|
|
2020-08-11 10:53:21 +02:00
|
|
|
case BIND_ACTION_SELECT_ROW:
|
2021-02-02 09:52:05 +01:00
|
|
|
selection_start(
|
|
|
|
|
term, seat->mouse.col, seat->mouse.row, SELECTION_LINE_WISE, false);
|
|
|
|
|
return true;
|
2020-08-11 09:55:33 +02:00
|
|
|
|
2020-03-08 12:08:46 +01:00
|
|
|
case BIND_ACTION_COUNT:
|
2021-02-09 15:16:19 +00:00
|
|
|
BUG("Invalid action type");
|
2020-08-22 13:35:36 +02:00
|
|
|
return false;
|
2020-03-08 12:08:46 +01:00
|
|
|
}
|
2020-08-22 13:35:36 +02:00
|
|
|
|
|
|
|
|
return false;
|
2020-03-08 12:08:46 +01:00
|
|
|
}
|
|
|
|
|
|
2019-06-19 10:04:47 +02:00
|
|
|
static void
|
|
|
|
|
keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard,
|
|
|
|
|
uint32_t format, int32_t fd, uint32_t size)
|
|
|
|
|
{
|
2020-03-14 11:51:47 +01:00
|
|
|
LOG_DBG("keyboard_keymap: keyboard=%p (format=%u, size=%u)",
|
2020-08-23 07:50:27 +02:00
|
|
|
(void *)wl_keyboard, format, size);
|
2020-03-14 11:51:47 +01:00
|
|
|
|
2020-07-08 16:45:26 +02:00
|
|
|
struct seat *seat = data;
|
|
|
|
|
struct wayland *wayl = seat->wayl;
|
2019-06-19 10:04:47 +02:00
|
|
|
|
2020-11-09 19:59:12 +01:00
|
|
|
/*
|
|
|
|
|
* Free old keymap state
|
|
|
|
|
*/
|
2019-06-19 10:04:47 +02:00
|
|
|
|
2020-07-08 16:45:26 +02:00
|
|
|
if (seat->kbd.xkb_keymap != NULL) {
|
|
|
|
|
xkb_keymap_unref(seat->kbd.xkb_keymap);
|
|
|
|
|
seat->kbd.xkb_keymap = NULL;
|
2019-08-12 21:32:53 +02:00
|
|
|
}
|
2020-07-08 16:45:26 +02:00
|
|
|
if (seat->kbd.xkb_state != NULL) {
|
|
|
|
|
xkb_state_unref(seat->kbd.xkb_state);
|
|
|
|
|
seat->kbd.xkb_state = NULL;
|
2019-08-12 21:32:53 +02:00
|
|
|
}
|
2020-03-18 14:29:34 +01:00
|
|
|
|
key-binding: new API, for handling sets of key bindings
Up until now, our Wayland seats have been tracking key bindings. This
makes sense, since the seat’s keymap determines how the key bindings
are resolved.
However, tying bindings to the seat/keymap alone isn’t enough, since
we also depend on the current configuration (i.e. user settings) when
resolving a key binding.
This means configurations that doesn’t match the wayland object’s
configuration, currently don’t resolve key bindings correctly. This
applies to footclients where the user has overridden key bindings on
the command line (e.g. --override key-bindings.foo=bar).
Thus, to correctly resolve key bindings, each set of key bindings must
be tied *both* to a seat/keymap, *and* a configuration.
This patch introduces a key-binding manager, with an API to
add/remove/lookup, and load/unload keymaps from sets of key bindings.
In the API, sets are tied to a seat and terminal instance, since this
makes the most sense (we need to instantiate, or incref a set whenever
a new terminal instance is created). Internally, the set is tied to a
seat and the terminal’s configuration.
Sets are *added* when a new seat is added, and when a new terminal
instance is created. Since there can only be one instance of each
seat, sets are always removed when a seat is removed.
Terminals on the other hand can re-use the same configuration (and
typically do). Thus, sets ref-count the configuration. In other words,
when instantiating a new terminal, we may not have to instantiate a
new set of key bindings, but can often be incref:ed instead.
Whenever the keymap changes on a seat, all key bindings sets
associated with that seat reloads (re-resolves) their key bindings.
Closes #931
2022-04-17 15:39:51 +02:00
|
|
|
key_binding_unload_keymap(wayl->key_binding_manager, seat);
|
2020-08-09 22:40:53 +02:00
|
|
|
|
2020-11-09 19:59:12 +01:00
|
|
|
/* Verify keymap is in a format we understand */
|
|
|
|
|
switch ((enum wl_keyboard_keymap_format)format) {
|
|
|
|
|
case WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP:
|
2025-10-18 08:23:53 +02:00
|
|
|
goto err;
|
2020-11-09 19:59:12 +01:00
|
|
|
|
|
|
|
|
case WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1:
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
LOG_WARN("unrecognized keymap format: %u", format);
|
2025-10-18 08:23:53 +02:00
|
|
|
goto err;
|
2020-11-09 19:59:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
char *map_str = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
|
|
|
|
|
if (map_str == MAP_FAILED) {
|
|
|
|
|
LOG_ERRNO("failed to mmap keyboard keymap");
|
2025-10-18 08:23:53 +02:00
|
|
|
goto err;
|
2020-11-09 19:59:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while (map_str[size - 1] == '\0')
|
|
|
|
|
size--;
|
|
|
|
|
|
2020-10-20 21:01:33 +02:00
|
|
|
if (seat->kbd.xkb != NULL) {
|
|
|
|
|
seat->kbd.xkb_keymap = xkb_keymap_new_from_buffer(
|
|
|
|
|
seat->kbd.xkb, map_str, size, XKB_KEYMAP_FORMAT_TEXT_V1,
|
|
|
|
|
XKB_KEYMAP_COMPILE_NO_FLAGS);
|
|
|
|
|
|
|
|
|
|
}
|
2019-07-05 15:13:06 +02:00
|
|
|
|
2025-10-18 08:23:53 +02:00
|
|
|
munmap(map_str, size);
|
|
|
|
|
|
2020-10-20 21:01:33 +02:00
|
|
|
if (seat->kbd.xkb_keymap != NULL) {
|
|
|
|
|
seat->kbd.xkb_state = xkb_state_new(seat->kbd.xkb_keymap);
|
2020-09-15 18:44:54 +02:00
|
|
|
|
2020-11-11 18:25:54 +01:00
|
|
|
seat->kbd.mod_shift = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_MOD_NAME_SHIFT);
|
|
|
|
|
seat->kbd.mod_alt = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_MOD_NAME_ALT) ;
|
|
|
|
|
seat->kbd.mod_ctrl = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_MOD_NAME_CTRL);
|
2021-11-21 12:01:16 +01:00
|
|
|
seat->kbd.mod_super = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_MOD_NAME_LOGO);
|
2021-11-20 20:31:59 +01:00
|
|
|
seat->kbd.mod_caps = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_MOD_NAME_CAPS);
|
|
|
|
|
seat->kbd.mod_num = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_MOD_NAME_NUM);
|
2020-10-19 18:30:19 +02:00
|
|
|
|
input/config: support *all* modifier names
That is, allow custom modifiers (i.e. other than ctrl/shift/alt etc)
in key bindings.
This is done by no longer validating/translating modifier names to
booleans for a pre-configured set of modifiers (ctrl, shift, alt,
super).
Instead, we keep the modifier *names* in a list, in the key binding
struct.
When a keymap is loaded, and we "convert" the key binding, _then_ we
do modifier translation. For invalid modifier names, we print an
error, and then ignore it. I.e. we no longer fail to load a config due
to invalid modifier names.
We also need to update how we determine the set of significant
modifiers. Any modifier not in this list will be ignored when matching
key bindings.
Before this patch, we hardcoded this to shift/alt/ctrl/super. Now, to
handle custom modifiers as well, we simply treat *all* modifiers
defined by the current layout as significant.
Typically, the only unwanted modifiers are "locked" modifiers. We are
already filtering these out.
2023-05-06 11:39:38 +02:00
|
|
|
/* Significant modifiers in the legacy keyboard protocol */
|
|
|
|
|
seat->kbd.legacy_significant = 0;
|
2021-11-21 11:53:22 +01:00
|
|
|
if (seat->kbd.mod_shift != XKB_MOD_INVALID)
|
input/config: support *all* modifier names
That is, allow custom modifiers (i.e. other than ctrl/shift/alt etc)
in key bindings.
This is done by no longer validating/translating modifier names to
booleans for a pre-configured set of modifiers (ctrl, shift, alt,
super).
Instead, we keep the modifier *names* in a list, in the key binding
struct.
When a keymap is loaded, and we "convert" the key binding, _then_ we
do modifier translation. For invalid modifier names, we print an
error, and then ignore it. I.e. we no longer fail to load a config due
to invalid modifier names.
We also need to update how we determine the set of significant
modifiers. Any modifier not in this list will be ignored when matching
key bindings.
Before this patch, we hardcoded this to shift/alt/ctrl/super. Now, to
handle custom modifiers as well, we simply treat *all* modifiers
defined by the current layout as significant.
Typically, the only unwanted modifiers are "locked" modifiers. We are
already filtering these out.
2023-05-06 11:39:38 +02:00
|
|
|
seat->kbd.legacy_significant |= 1 << seat->kbd.mod_shift;
|
2021-11-21 11:53:22 +01:00
|
|
|
if (seat->kbd.mod_alt != XKB_MOD_INVALID)
|
input/config: support *all* modifier names
That is, allow custom modifiers (i.e. other than ctrl/shift/alt etc)
in key bindings.
This is done by no longer validating/translating modifier names to
booleans for a pre-configured set of modifiers (ctrl, shift, alt,
super).
Instead, we keep the modifier *names* in a list, in the key binding
struct.
When a keymap is loaded, and we "convert" the key binding, _then_ we
do modifier translation. For invalid modifier names, we print an
error, and then ignore it. I.e. we no longer fail to load a config due
to invalid modifier names.
We also need to update how we determine the set of significant
modifiers. Any modifier not in this list will be ignored when matching
key bindings.
Before this patch, we hardcoded this to shift/alt/ctrl/super. Now, to
handle custom modifiers as well, we simply treat *all* modifiers
defined by the current layout as significant.
Typically, the only unwanted modifiers are "locked" modifiers. We are
already filtering these out.
2023-05-06 11:39:38 +02:00
|
|
|
seat->kbd.legacy_significant |= 1 << seat->kbd.mod_alt;
|
2021-11-21 11:53:22 +01:00
|
|
|
if (seat->kbd.mod_ctrl != XKB_MOD_INVALID)
|
input/config: support *all* modifier names
That is, allow custom modifiers (i.e. other than ctrl/shift/alt etc)
in key bindings.
This is done by no longer validating/translating modifier names to
booleans for a pre-configured set of modifiers (ctrl, shift, alt,
super).
Instead, we keep the modifier *names* in a list, in the key binding
struct.
When a keymap is loaded, and we "convert" the key binding, _then_ we
do modifier translation. For invalid modifier names, we print an
error, and then ignore it. I.e. we no longer fail to load a config due
to invalid modifier names.
We also need to update how we determine the set of significant
modifiers. Any modifier not in this list will be ignored when matching
key bindings.
Before this patch, we hardcoded this to shift/alt/ctrl/super. Now, to
handle custom modifiers as well, we simply treat *all* modifiers
defined by the current layout as significant.
Typically, the only unwanted modifiers are "locked" modifiers. We are
already filtering these out.
2023-05-06 11:39:38 +02:00
|
|
|
seat->kbd.legacy_significant |= 1 << seat->kbd.mod_ctrl;
|
2021-11-21 12:01:16 +01:00
|
|
|
if (seat->kbd.mod_super != XKB_MOD_INVALID)
|
input/config: support *all* modifier names
That is, allow custom modifiers (i.e. other than ctrl/shift/alt etc)
in key bindings.
This is done by no longer validating/translating modifier names to
booleans for a pre-configured set of modifiers (ctrl, shift, alt,
super).
Instead, we keep the modifier *names* in a list, in the key binding
struct.
When a keymap is loaded, and we "convert" the key binding, _then_ we
do modifier translation. For invalid modifier names, we print an
error, and then ignore it. I.e. we no longer fail to load a config due
to invalid modifier names.
We also need to update how we determine the set of significant
modifiers. Any modifier not in this list will be ignored when matching
key bindings.
Before this patch, we hardcoded this to shift/alt/ctrl/super. Now, to
handle custom modifiers as well, we simply treat *all* modifiers
defined by the current layout as significant.
Typically, the only unwanted modifiers are "locked" modifiers. We are
already filtering these out.
2023-05-06 11:39:38 +02:00
|
|
|
seat->kbd.legacy_significant |= 1 << seat->kbd.mod_super;
|
2021-11-21 11:53:22 +01:00
|
|
|
|
input/config: support *all* modifier names
That is, allow custom modifiers (i.e. other than ctrl/shift/alt etc)
in key bindings.
This is done by no longer validating/translating modifier names to
booleans for a pre-configured set of modifiers (ctrl, shift, alt,
super).
Instead, we keep the modifier *names* in a list, in the key binding
struct.
When a keymap is loaded, and we "convert" the key binding, _then_ we
do modifier translation. For invalid modifier names, we print an
error, and then ignore it. I.e. we no longer fail to load a config due
to invalid modifier names.
We also need to update how we determine the set of significant
modifiers. Any modifier not in this list will be ignored when matching
key bindings.
Before this patch, we hardcoded this to shift/alt/ctrl/super. Now, to
handle custom modifiers as well, we simply treat *all* modifiers
defined by the current layout as significant.
Typically, the only unwanted modifiers are "locked" modifiers. We are
already filtering these out.
2023-05-06 11:39:38 +02:00
|
|
|
/* Significant modifiers in the kitty keyboard protocol */
|
|
|
|
|
seat->kbd.kitty_significant = seat->kbd.legacy_significant;
|
2021-11-21 11:53:22 +01:00
|
|
|
if (seat->kbd.mod_caps != XKB_MOD_INVALID)
|
|
|
|
|
seat->kbd.kitty_significant |= 1 << seat->kbd.mod_caps;
|
|
|
|
|
if (seat->kbd.mod_num != XKB_MOD_INVALID)
|
|
|
|
|
seat->kbd.kitty_significant |= 1 << seat->kbd.mod_num;
|
2021-11-21 11:27:31 +01:00
|
|
|
|
2025-03-29 10:34:40 +01:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Create a mask of all "virtual" modifiers. Some compositors
|
|
|
|
|
* add these *in addition* to the "real" modifiers (Mod1,
|
|
|
|
|
* Mod2, etc).
|
|
|
|
|
*
|
|
|
|
|
* Since our modifier logic (both for internal shortcut
|
|
|
|
|
* processing, and e.g. the kitty keyboard protocol) makes
|
|
|
|
|
* very few assumptions on available modifiers, which keys map
|
|
|
|
|
* to which modifier etc, the presence of virtual modifiers
|
|
|
|
|
* causes various things to break.
|
|
|
|
|
*
|
|
|
|
|
* For example, if a foot shortcut is Mod1+b (i.e. Alt+b), it
|
|
|
|
|
* won't match if the compositor _also_ sets the Alt modifier
|
|
|
|
|
* (the corresponding shortcut in foot would be Alt+Mod1+b).
|
|
|
|
|
*
|
|
|
|
|
* See https://codeberg.org/dnkl/foot/issues/2009
|
|
|
|
|
*
|
|
|
|
|
* Mutter (GNOME) is known to set the virtual modifiers in
|
|
|
|
|
* addtiion to the real modifiers.
|
|
|
|
|
*
|
|
|
|
|
* As far as I know, there's no compositor that _only_ sets
|
|
|
|
|
* virtual modifiers (don't think that's even legal...?)
|
|
|
|
|
*/
|
|
|
|
|
{
|
|
|
|
|
xkb_mod_index_t alt = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_VMOD_NAME_ALT);
|
|
|
|
|
xkb_mod_index_t meta = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_VMOD_NAME_META);
|
|
|
|
|
xkb_mod_index_t super = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_VMOD_NAME_SUPER);
|
|
|
|
|
xkb_mod_index_t hyper = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_VMOD_NAME_HYPER);
|
|
|
|
|
xkb_mod_index_t num_lock = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_VMOD_NAME_NUM);
|
|
|
|
|
xkb_mod_index_t scroll_lock = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_VMOD_NAME_SCROLL);
|
|
|
|
|
xkb_mod_index_t level_three = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_VMOD_NAME_LEVEL3);
|
|
|
|
|
xkb_mod_index_t level_five = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_VMOD_NAME_LEVEL5);
|
|
|
|
|
|
|
|
|
|
xkb_mod_index_t ignore = 0;
|
|
|
|
|
|
|
|
|
|
if (alt != XKB_MOD_INVALID) ignore |= 1 << alt;
|
|
|
|
|
if (meta != XKB_MOD_INVALID) ignore |= 1 << meta;
|
|
|
|
|
if (super != XKB_MOD_INVALID) ignore |= 1 << super;
|
|
|
|
|
if (hyper != XKB_MOD_INVALID) ignore |= 1 << hyper;
|
|
|
|
|
if (num_lock != XKB_MOD_INVALID) ignore |= 1 << num_lock;
|
|
|
|
|
if (scroll_lock != XKB_MOD_INVALID) ignore |= 1 << scroll_lock;
|
|
|
|
|
if (level_three != XKB_MOD_INVALID) ignore |= 1 << level_three;
|
|
|
|
|
if (level_five != XKB_MOD_INVALID) ignore |= 1 << level_five;
|
|
|
|
|
|
|
|
|
|
seat->kbd.virtual_modifiers = ignore;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-20 21:01:33 +02:00
|
|
|
seat->kbd.key_arrow_up = xkb_keymap_key_by_name(seat->kbd.xkb_keymap, "UP");
|
|
|
|
|
seat->kbd.key_arrow_down = xkb_keymap_key_by_name(seat->kbd.xkb_keymap, "DOWN");
|
2020-10-19 18:30:19 +02:00
|
|
|
}
|
2019-07-09 10:00:54 +02:00
|
|
|
|
key-binding: new API, for handling sets of key bindings
Up until now, our Wayland seats have been tracking key bindings. This
makes sense, since the seat’s keymap determines how the key bindings
are resolved.
However, tying bindings to the seat/keymap alone isn’t enough, since
we also depend on the current configuration (i.e. user settings) when
resolving a key binding.
This means configurations that doesn’t match the wayland object’s
configuration, currently don’t resolve key bindings correctly. This
applies to footclients where the user has overridden key bindings on
the command line (e.g. --override key-bindings.foo=bar).
Thus, to correctly resolve key bindings, each set of key bindings must
be tied *both* to a seat/keymap, *and* a configuration.
This patch introduces a key-binding manager, with an API to
add/remove/lookup, and load/unload keymaps from sets of key bindings.
In the API, sets are tied to a seat and terminal instance, since this
makes the most sense (we need to instantiate, or incref a set whenever
a new terminal instance is created). Internally, the set is tied to a
seat and the terminal’s configuration.
Sets are *added* when a new seat is added, and when a new terminal
instance is created. Since there can only be one instance of each
seat, sets are always removed when a seat is removed.
Terminals on the other hand can re-use the same configuration (and
typically do). Thus, sets ref-count the configuration. In other words,
when instantiating a new terminal, we may not have to instantiate a
new set of key bindings, but can often be incref:ed instead.
Whenever the keymap changes on a seat, all key bindings sets
associated with that seat reloads (re-resolves) their key bindings.
Closes #931
2022-04-17 15:39:51 +02:00
|
|
|
key_binding_load_keymap(wayl->key_binding_manager, seat);
|
2025-10-18 08:23:53 +02:00
|
|
|
|
|
|
|
|
err:
|
|
|
|
|
close(fd);
|
2019-06-19 10:04:47 +02:00
|
|
|
}
|
|
|
|
|
|
2020-01-14 19:28:37 +01:00
|
|
|
static void
|
2019-06-19 10:04:47 +02:00
|
|
|
keyboard_enter(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial,
|
|
|
|
|
struct wl_surface *surface, struct wl_array *keys)
|
|
|
|
|
{
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(surface != NULL);
|
2022-04-23 15:54:37 +02:00
|
|
|
xassert(serial != 0);
|
2019-11-28 19:46:27 +01:00
|
|
|
|
2020-07-08 16:45:26 +02:00
|
|
|
struct seat *seat = data;
|
2020-02-29 12:13:58 +01:00
|
|
|
struct wl_window *win = wl_surface_get_user_data(surface);
|
|
|
|
|
struct terminal *term = win->term;
|
|
|
|
|
|
2020-07-11 11:13:45 +02:00
|
|
|
LOG_DBG("%s: keyboard_enter: keyboard=%p, serial=%u, surface=%p",
|
2020-08-23 07:50:27 +02:00
|
|
|
seat->name, (void *)wl_keyboard, serial, (void *)surface);
|
2020-03-14 11:51:47 +01:00
|
|
|
|
2020-07-09 09:16:54 +02:00
|
|
|
term_kbd_focus_in(term);
|
2020-07-08 16:45:26 +02:00
|
|
|
seat->kbd_focus = term;
|
2020-07-09 11:20:46 +02:00
|
|
|
seat->kbd.serial = serial;
|
2019-06-19 10:04:47 +02:00
|
|
|
}
|
|
|
|
|
|
2019-08-05 19:33:01 +02:00
|
|
|
static bool
|
2020-07-08 16:45:26 +02:00
|
|
|
start_repeater(struct seat *seat, uint32_t key)
|
2019-06-19 10:04:47 +02:00
|
|
|
{
|
2020-07-08 16:45:26 +02:00
|
|
|
if (seat->kbd.repeat.dont_re_repeat)
|
2019-08-05 19:33:01 +02:00
|
|
|
return true;
|
|
|
|
|
|
2021-01-16 12:27:58 +01:00
|
|
|
if (seat->kbd.repeat.rate == 0)
|
|
|
|
|
return true;
|
|
|
|
|
|
2019-08-05 19:33:01 +02:00
|
|
|
struct itimerspec t = {
|
2020-07-08 16:45:26 +02:00
|
|
|
.it_value = {.tv_sec = 0, .tv_nsec = seat->kbd.repeat.delay * 1000000},
|
|
|
|
|
.it_interval = {.tv_sec = 0, .tv_nsec = 1000000000 / seat->kbd.repeat.rate},
|
2019-08-05 19:33:01 +02:00
|
|
|
};
|
2019-06-21 18:40:53 +02:00
|
|
|
|
2019-08-05 19:33:01 +02:00
|
|
|
if (t.it_value.tv_nsec >= 1000000000) {
|
|
|
|
|
t.it_value.tv_sec += t.it_value.tv_nsec / 1000000000;
|
|
|
|
|
t.it_value.tv_nsec %= 1000000000;
|
|
|
|
|
}
|
|
|
|
|
if (t.it_interval.tv_nsec >= 1000000000) {
|
|
|
|
|
t.it_interval.tv_sec += t.it_interval.tv_nsec / 1000000000;
|
|
|
|
|
t.it_interval.tv_nsec %= 1000000000;
|
|
|
|
|
}
|
2020-07-08 16:45:26 +02:00
|
|
|
if (timerfd_settime(seat->kbd.repeat.fd, 0, &t, NULL) < 0) {
|
|
|
|
|
LOG_ERRNO("%s: failed to arm keyboard repeat timer", seat->name);
|
2019-08-05 19:33:01 +02:00
|
|
|
return false;
|
2019-06-21 18:40:53 +02:00
|
|
|
}
|
2019-08-02 18:19:07 +02:00
|
|
|
|
2020-07-08 16:45:26 +02:00
|
|
|
seat->kbd.repeat.key = key;
|
2019-08-05 19:33:01 +02:00
|
|
|
return true;
|
2019-08-02 18:19:07 +02:00
|
|
|
}
|
|
|
|
|
|
2019-08-05 19:33:01 +02:00
|
|
|
static bool
|
2020-07-08 16:45:26 +02:00
|
|
|
stop_repeater(struct seat *seat, uint32_t key)
|
2019-08-02 18:19:07 +02:00
|
|
|
{
|
2020-07-08 16:45:26 +02:00
|
|
|
if (key != -1 && key != seat->kbd.repeat.key)
|
2019-08-05 19:33:01 +02:00
|
|
|
return true;
|
|
|
|
|
|
2020-08-23 07:42:20 +02:00
|
|
|
if (timerfd_settime(seat->kbd.repeat.fd, 0, &(struct itimerspec){{0}}, NULL) < 0) {
|
2020-07-08 16:45:26 +02:00
|
|
|
LOG_ERRNO("%s: failed to disarm keyboard repeat timer", seat->name);
|
2019-08-05 19:33:01 +02:00
|
|
|
return false;
|
2019-08-02 18:19:07 +02:00
|
|
|
}
|
2019-08-05 19:33:01 +02:00
|
|
|
|
|
|
|
|
return true;
|
2019-08-02 18:19:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2019-08-05 19:33:01 +02:00
|
|
|
keyboard_leave(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial,
|
|
|
|
|
struct wl_surface *surface)
|
2019-08-02 18:19:07 +02:00
|
|
|
{
|
2020-07-08 16:45:26 +02:00
|
|
|
struct seat *seat = data;
|
2019-08-05 19:33:01 +02:00
|
|
|
|
2020-03-14 11:51:47 +01:00
|
|
|
LOG_DBG("keyboard_leave: keyboard=%p, serial=%u, surface=%p",
|
2020-08-23 07:50:27 +02:00
|
|
|
(void *)wl_keyboard, serial, (void *)surface);
|
2020-03-14 11:51:47 +01:00
|
|
|
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(
|
2020-07-08 16:45:26 +02:00
|
|
|
seat->kbd_focus == NULL ||
|
2019-11-28 19:24:16 +01:00
|
|
|
surface == NULL || /* Seen on Sway 1.2 */
|
2020-07-08 16:45:26 +02:00
|
|
|
((const struct wl_window *)wl_surface_get_user_data(surface))->term == seat->kbd_focus
|
2020-02-29 12:13:58 +01:00
|
|
|
);
|
2019-11-21 18:17:02 +01:00
|
|
|
|
2020-07-08 16:45:26 +02:00
|
|
|
struct terminal *old_focused = seat->kbd_focus;
|
|
|
|
|
seat->kbd_focus = NULL;
|
2019-11-29 22:11:41 +01:00
|
|
|
|
2020-07-08 16:45:26 +02:00
|
|
|
stop_repeater(seat, -1);
|
2020-07-09 08:46:25 +02:00
|
|
|
seat->kbd.shift = false;
|
|
|
|
|
seat->kbd.alt = false;
|
|
|
|
|
seat->kbd.ctrl = false;
|
2021-11-21 12:01:16 +01:00
|
|
|
seat->kbd.super = false;
|
2025-04-19 08:05:15 +02:00
|
|
|
|
2020-10-19 18:30:19 +02:00
|
|
|
if (seat->kbd.xkb_compose_state != NULL)
|
|
|
|
|
xkb_compose_state_reset(seat->kbd.xkb_compose_state);
|
2020-01-13 19:58:57 +01:00
|
|
|
|
2025-04-19 08:05:15 +02:00
|
|
|
if (seat->kbd.xkb_state != NULL && seat->kbd.xkb_keymap != NULL) {
|
|
|
|
|
const xkb_layout_index_t layout_count = xkb_keymap_num_layouts(seat->kbd.xkb_keymap);
|
|
|
|
|
|
|
|
|
|
for (xkb_layout_index_t i = 0; i < layout_count; i++)
|
|
|
|
|
xkb_state_update_mask(seat->kbd.xkb_state, 0, 0, 0, i, i, i);
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-31 17:09:06 +02:00
|
|
|
if (old_focused != NULL) {
|
|
|
|
|
seat->pointer.hidden = false;
|
|
|
|
|
term_xcursor_update_for_seat(old_focused, seat);
|
2020-01-02 19:30:34 +01:00
|
|
|
term_kbd_focus_out(old_focused);
|
2020-07-31 17:09:06 +02:00
|
|
|
} else {
|
2019-11-24 00:06:50 +01:00
|
|
|
/*
|
|
|
|
|
* Sway bug - under certain conditions we get a
|
|
|
|
|
* keyboard_leave() (and keyboard_key()) without first having
|
|
|
|
|
* received a keyboard_enter()
|
|
|
|
|
*/
|
2019-11-28 19:47:00 +01:00
|
|
|
LOG_WARN(
|
|
|
|
|
"compositor sent keyboard_leave event without a keyboard_enter "
|
2020-08-23 07:50:27 +02:00
|
|
|
"event: surface=%p", (void *)surface);
|
2019-11-24 00:06:50 +01:00
|
|
|
}
|
2019-06-19 10:04:47 +02:00
|
|
|
}
|
|
|
|
|
|
2020-01-11 18:18:31 +01:00
|
|
|
static const struct key_data *
|
2020-01-11 18:40:27 +01:00
|
|
|
keymap_data_for_sym(xkb_keysym_t sym, size_t *count)
|
2020-01-11 18:18:31 +01:00
|
|
|
{
|
|
|
|
|
switch (sym) {
|
|
|
|
|
case XKB_KEY_Escape: *count = ALEN(key_escape); return key_escape;
|
|
|
|
|
case XKB_KEY_Return: *count = ALEN(key_return); return key_return;
|
keymap: handle shift+tab combos correctly, after consuming modifiers
Shift+Tab is an odd bird in XKB; it produces a shifted variant of tab,
ISO_Left_Tab, with Shift being a consumed modifier.
From a terminal perspective, Tab and ISO_Left_Tab are the same key,
with Shift+Tab generating ‘CSI Z’, and all other combos generating a
‘CSI 27;mods;9~’ escape.
Before, when we didn’t filter out consumed modifiers, we could simply
re-use the keymap for Tab when translating ISO_Left_Tab.
This is no longer possible, since shift has been filtered out. As a
result, Shift+Tab no longer generated ‘CSI Z’, but ‘\t’. I.e as if
shift wasn’t being pressed.
This patch adds a separate keymap table for ISO_Left_Tab. It’s MOD_ANY
entry corresponds to Shift+Tab, and emits ‘CSI Z’. All its other
entries *exclude* shift (since it has been consumed), *but*, the
encoded modifier (in the escape sequences) *include* shift.
2021-02-28 11:50:05 +01:00
|
|
|
case XKB_KEY_ISO_Left_Tab: *count = ALEN(key_iso_left_tab); return key_iso_left_tab;
|
|
|
|
|
case XKB_KEY_Tab: *count = ALEN(key_tab); return key_tab;
|
2020-01-11 18:18:31 +01:00
|
|
|
case XKB_KEY_BackSpace: *count = ALEN(key_backspace); return key_backspace;
|
|
|
|
|
case XKB_KEY_Up: *count = ALEN(key_up); return key_up;
|
|
|
|
|
case XKB_KEY_Down: *count = ALEN(key_down); return key_down;
|
|
|
|
|
case XKB_KEY_Right: *count = ALEN(key_right); return key_right;
|
|
|
|
|
case XKB_KEY_Left: *count = ALEN(key_left); return key_left;
|
|
|
|
|
case XKB_KEY_Home: *count = ALEN(key_home); return key_home;
|
|
|
|
|
case XKB_KEY_End: *count = ALEN(key_end); return key_end;
|
|
|
|
|
case XKB_KEY_Insert: *count = ALEN(key_insert); return key_insert;
|
|
|
|
|
case XKB_KEY_Delete: *count = ALEN(key_delete); return key_delete;
|
|
|
|
|
case XKB_KEY_Page_Up: *count = ALEN(key_pageup); return key_pageup;
|
|
|
|
|
case XKB_KEY_Page_Down: *count = ALEN(key_pagedown); return key_pagedown;
|
|
|
|
|
case XKB_KEY_F1: *count = ALEN(key_f1); return key_f1;
|
|
|
|
|
case XKB_KEY_F2: *count = ALEN(key_f2); return key_f2;
|
|
|
|
|
case XKB_KEY_F3: *count = ALEN(key_f3); return key_f3;
|
|
|
|
|
case XKB_KEY_F4: *count = ALEN(key_f4); return key_f4;
|
|
|
|
|
case XKB_KEY_F5: *count = ALEN(key_f5); return key_f5;
|
|
|
|
|
case XKB_KEY_F6: *count = ALEN(key_f6); return key_f6;
|
|
|
|
|
case XKB_KEY_F7: *count = ALEN(key_f7); return key_f7;
|
|
|
|
|
case XKB_KEY_F8: *count = ALEN(key_f8); return key_f8;
|
|
|
|
|
case XKB_KEY_F9: *count = ALEN(key_f9); return key_f9;
|
|
|
|
|
case XKB_KEY_F10: *count = ALEN(key_f10); return key_f10;
|
|
|
|
|
case XKB_KEY_F11: *count = ALEN(key_f11); return key_f11;
|
|
|
|
|
case XKB_KEY_F12: *count = ALEN(key_f12); return key_f12;
|
|
|
|
|
case XKB_KEY_F13: *count = ALEN(key_f13); return key_f13;
|
|
|
|
|
case XKB_KEY_F14: *count = ALEN(key_f14); return key_f14;
|
|
|
|
|
case XKB_KEY_F15: *count = ALEN(key_f15); return key_f15;
|
|
|
|
|
case XKB_KEY_F16: *count = ALEN(key_f16); return key_f16;
|
|
|
|
|
case XKB_KEY_F17: *count = ALEN(key_f17); return key_f17;
|
|
|
|
|
case XKB_KEY_F18: *count = ALEN(key_f18); return key_f18;
|
|
|
|
|
case XKB_KEY_F19: *count = ALEN(key_f19); return key_f19;
|
|
|
|
|
case XKB_KEY_F20: *count = ALEN(key_f20); return key_f20;
|
|
|
|
|
case XKB_KEY_F21: *count = ALEN(key_f21); return key_f21;
|
|
|
|
|
case XKB_KEY_F22: *count = ALEN(key_f22); return key_f22;
|
|
|
|
|
case XKB_KEY_F23: *count = ALEN(key_f23); return key_f23;
|
|
|
|
|
case XKB_KEY_F24: *count = ALEN(key_f24); return key_f24;
|
|
|
|
|
case XKB_KEY_F25: *count = ALEN(key_f25); return key_f25;
|
|
|
|
|
case XKB_KEY_F26: *count = ALEN(key_f26); return key_f26;
|
|
|
|
|
case XKB_KEY_F27: *count = ALEN(key_f27); return key_f27;
|
|
|
|
|
case XKB_KEY_F28: *count = ALEN(key_f28); return key_f28;
|
|
|
|
|
case XKB_KEY_F29: *count = ALEN(key_f29); return key_f29;
|
|
|
|
|
case XKB_KEY_F30: *count = ALEN(key_f30); return key_f30;
|
|
|
|
|
case XKB_KEY_F31: *count = ALEN(key_f31); return key_f31;
|
|
|
|
|
case XKB_KEY_F32: *count = ALEN(key_f32); return key_f32;
|
|
|
|
|
case XKB_KEY_F33: *count = ALEN(key_f33); return key_f33;
|
|
|
|
|
case XKB_KEY_F34: *count = ALEN(key_f34); return key_f34;
|
|
|
|
|
case XKB_KEY_F35: *count = ALEN(key_f35); return key_f35;
|
|
|
|
|
case XKB_KEY_KP_Up: *count = ALEN(key_kp_up); return key_kp_up;
|
|
|
|
|
case XKB_KEY_KP_Down: *count = ALEN(key_kp_down); return key_kp_down;
|
|
|
|
|
case XKB_KEY_KP_Right: *count = ALEN(key_kp_right); return key_kp_right;
|
|
|
|
|
case XKB_KEY_KP_Left: *count = ALEN(key_kp_left); return key_kp_left;
|
|
|
|
|
case XKB_KEY_KP_Begin: *count = ALEN(key_kp_begin); return key_kp_begin;
|
|
|
|
|
case XKB_KEY_KP_Home: *count = ALEN(key_kp_home); return key_kp_home;
|
|
|
|
|
case XKB_KEY_KP_End: *count = ALEN(key_kp_end); return key_kp_end;
|
|
|
|
|
case XKB_KEY_KP_Insert: *count = ALEN(key_kp_insert); return key_kp_insert;
|
|
|
|
|
case XKB_KEY_KP_Delete: *count = ALEN(key_kp_delete); return key_kp_delete;
|
|
|
|
|
case XKB_KEY_KP_Page_Up: *count = ALEN(key_kp_pageup); return key_kp_pageup;
|
|
|
|
|
case XKB_KEY_KP_Page_Down: *count = ALEN(key_kp_pagedown); return key_kp_pagedown;
|
|
|
|
|
case XKB_KEY_KP_Enter: *count = ALEN(key_kp_enter); return key_kp_enter;
|
|
|
|
|
case XKB_KEY_KP_Divide: *count = ALEN(key_kp_divide); return key_kp_divide;
|
|
|
|
|
case XKB_KEY_KP_Multiply: *count = ALEN(key_kp_multiply); return key_kp_multiply;
|
|
|
|
|
case XKB_KEY_KP_Subtract: *count = ALEN(key_kp_subtract); return key_kp_subtract;
|
|
|
|
|
case XKB_KEY_KP_Add: *count = ALEN(key_kp_add); return key_kp_add;
|
|
|
|
|
case XKB_KEY_KP_Separator: *count = ALEN(key_kp_separator); return key_kp_separator;
|
2020-06-27 20:03:41 +02:00
|
|
|
case XKB_KEY_KP_Decimal: *count = ALEN(key_kp_decimal); return key_kp_decimal;
|
2020-01-11 18:18:31 +01:00
|
|
|
case XKB_KEY_KP_0: *count = ALEN(key_kp_0); return key_kp_0;
|
|
|
|
|
case XKB_KEY_KP_1: *count = ALEN(key_kp_1); return key_kp_1;
|
|
|
|
|
case XKB_KEY_KP_2: *count = ALEN(key_kp_2); return key_kp_2;
|
|
|
|
|
case XKB_KEY_KP_3: *count = ALEN(key_kp_3); return key_kp_3;
|
|
|
|
|
case XKB_KEY_KP_4: *count = ALEN(key_kp_4); return key_kp_4;
|
|
|
|
|
case XKB_KEY_KP_5: *count = ALEN(key_kp_5); return key_kp_5;
|
|
|
|
|
case XKB_KEY_KP_6: *count = ALEN(key_kp_6); return key_kp_6;
|
|
|
|
|
case XKB_KEY_KP_7: *count = ALEN(key_kp_7); return key_kp_7;
|
|
|
|
|
case XKB_KEY_KP_8: *count = ALEN(key_kp_8); return key_kp_8;
|
|
|
|
|
case XKB_KEY_KP_9: *count = ALEN(key_kp_9); return key_kp_9;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
2020-01-11 17:55:45 +01:00
|
|
|
|
2020-01-11 18:40:27 +01:00
|
|
|
static const struct key_data *
|
2021-05-25 17:57:39 +02:00
|
|
|
keymap_lookup(struct terminal *term, xkb_keysym_t sym, enum modifier mods)
|
2020-01-11 17:55:45 +01:00
|
|
|
{
|
2020-01-11 18:18:31 +01:00
|
|
|
size_t count;
|
2020-01-11 18:40:27 +01:00
|
|
|
const struct key_data *info = keymap_data_for_sym(sym, &count);
|
2020-01-11 17:55:45 +01:00
|
|
|
|
2020-01-11 18:18:31 +01:00
|
|
|
if (info == NULL)
|
2020-01-11 18:40:27 +01:00
|
|
|
return NULL;
|
2020-01-11 17:55:45 +01:00
|
|
|
|
2020-11-11 18:28:37 +01:00
|
|
|
const enum cursor_keys cursor_keys_mode = term->cursor_keys_mode;
|
|
|
|
|
const enum keypad_keys keypad_keys_mode
|
2020-12-01 18:27:56 +01:00
|
|
|
= term->num_lock_modifier ? KEYPAD_NUMERICAL : term->keypad_keys_mode;
|
2020-11-11 18:28:37 +01:00
|
|
|
|
2020-12-23 18:13:40 +01:00
|
|
|
LOG_DBG("keypad mode: %d", keypad_keys_mode);
|
2020-12-01 19:31:49 +01:00
|
|
|
|
2020-01-11 18:18:31 +01:00
|
|
|
for (size_t j = 0; j < count; j++) {
|
2021-11-11 17:43:39 +01:00
|
|
|
enum modifier modifiers = info[j].modifiers;
|
|
|
|
|
|
|
|
|
|
if (modifiers & MOD_MODIFY_OTHER_KEYS_STATE1) {
|
|
|
|
|
if (term->modify_other_keys_2)
|
|
|
|
|
continue;
|
|
|
|
|
modifiers &= ~MOD_MODIFY_OTHER_KEYS_STATE1;
|
|
|
|
|
}
|
|
|
|
|
if (modifiers & MOD_MODIFY_OTHER_KEYS_STATE2) {
|
|
|
|
|
if (!term->modify_other_keys_2)
|
|
|
|
|
continue;
|
|
|
|
|
modifiers &= ~MOD_MODIFY_OTHER_KEYS_STATE2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (modifiers != MOD_ANY && modifiers != mods)
|
2020-01-11 18:18:31 +01:00
|
|
|
continue;
|
2020-01-11 17:55:45 +01:00
|
|
|
|
2020-01-11 18:18:31 +01:00
|
|
|
if (info[j].cursor_keys_mode != CURSOR_KEYS_DONTCARE &&
|
2020-11-11 18:28:37 +01:00
|
|
|
info[j].cursor_keys_mode != cursor_keys_mode)
|
2020-01-11 18:18:31 +01:00
|
|
|
continue;
|
2020-01-11 17:55:45 +01:00
|
|
|
|
2020-01-11 18:18:31 +01:00
|
|
|
if (info[j].keypad_keys_mode != KEYPAD_DONTCARE &&
|
2020-11-11 18:28:37 +01:00
|
|
|
info[j].keypad_keys_mode != keypad_keys_mode)
|
2020-01-11 18:18:31 +01:00
|
|
|
continue;
|
2020-01-11 17:55:45 +01:00
|
|
|
|
2020-01-11 18:40:27 +01:00
|
|
|
return &info[j];
|
2020-01-11 17:55:45 +01:00
|
|
|
}
|
|
|
|
|
|
2020-01-11 18:40:27 +01:00
|
|
|
return NULL;
|
2020-01-11 17:55:45 +01:00
|
|
|
}
|
|
|
|
|
|
2021-05-25 18:38:16 +01:00
|
|
|
UNITTEST
|
2021-05-25 17:57:53 +02:00
|
|
|
{
|
|
|
|
|
struct terminal term = {
|
|
|
|
|
.num_lock_modifier = false,
|
|
|
|
|
.keypad_keys_mode = KEYPAD_NUMERICAL,
|
|
|
|
|
.cursor_keys_mode = CURSOR_KEYS_NORMAL,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const struct key_data *info = keymap_lookup(&term, XKB_KEY_ISO_Left_Tab, MOD_SHIFT | MOD_CTRL);
|
2021-11-11 17:43:39 +01:00
|
|
|
xassert(info != NULL);
|
2024-01-24 23:17:28 +00:00
|
|
|
xassert(streq(info->seq, "\033[27;6;9~"));
|
2021-05-25 17:57:53 +02:00
|
|
|
}
|
|
|
|
|
|
2021-11-11 17:43:39 +01:00
|
|
|
UNITTEST
|
|
|
|
|
{
|
|
|
|
|
struct terminal term = {
|
|
|
|
|
.modify_other_keys_2 = false,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const struct key_data *info = keymap_lookup(&term, XKB_KEY_Return, MOD_ALT);
|
|
|
|
|
xassert(info != NULL);
|
2024-01-24 23:17:28 +00:00
|
|
|
xassert(streq(info->seq, "\033\r"));
|
2021-11-11 17:43:39 +01:00
|
|
|
|
|
|
|
|
term.modify_other_keys_2 = true;
|
|
|
|
|
info = keymap_lookup(&term, XKB_KEY_Return, MOD_ALT);
|
|
|
|
|
xassert(info != NULL);
|
2024-01-24 23:17:28 +00:00
|
|
|
xassert(streq(info->seq, "\033[27;3;13~"));
|
2021-11-11 17:43:39 +01:00
|
|
|
}
|
|
|
|
|
|
2021-09-27 19:09:07 +00:00
|
|
|
void
|
2021-11-20 20:31:59 +01:00
|
|
|
get_current_modifiers(const struct seat *seat,
|
|
|
|
|
xkb_mod_mask_t *effective,
|
2024-02-06 10:41:01 +01:00
|
|
|
xkb_mod_mask_t *consumed, uint32_t key,
|
|
|
|
|
bool filter_locked)
|
2021-11-20 20:31:59 +01:00
|
|
|
{
|
2022-03-04 17:54:19 +01:00
|
|
|
if (unlikely(seat->kbd.xkb_state == NULL)) {
|
|
|
|
|
if (effective != NULL)
|
|
|
|
|
*effective = 0;
|
|
|
|
|
if (consumed != NULL)
|
|
|
|
|
*consumed = 0;
|
2021-10-06 20:12:17 +02:00
|
|
|
}
|
|
|
|
|
|
2022-03-04 17:54:19 +01:00
|
|
|
else {
|
2024-02-06 10:41:01 +01:00
|
|
|
const xkb_mod_mask_t locked =
|
|
|
|
|
xkb_state_serialize_mods(seat->kbd.xkb_state, XKB_STATE_MODS_LOCKED);
|
|
|
|
|
|
2022-03-04 17:54:19 +01:00
|
|
|
if (effective != NULL) {
|
|
|
|
|
*effective = xkb_state_serialize_mods(
|
|
|
|
|
seat->kbd.xkb_state, XKB_STATE_MODS_EFFECTIVE);
|
2024-02-06 10:41:01 +01:00
|
|
|
|
|
|
|
|
if (filter_locked)
|
|
|
|
|
*effective &= ~locked;
|
2022-03-04 17:54:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (consumed != NULL) {
|
|
|
|
|
*consumed = xkb_state_key_get_consumed_mods2(
|
|
|
|
|
seat->kbd.xkb_state, key, XKB_CONSUMED_MODE_XKB);
|
2024-02-06 10:41:01 +01:00
|
|
|
|
|
|
|
|
if (filter_locked)
|
|
|
|
|
*consumed &= ~locked;
|
2022-03-04 17:54:19 +01:00
|
|
|
}
|
2021-11-27 18:44:29 +01:00
|
|
|
}
|
2021-10-06 20:12:17 +02:00
|
|
|
}
|
|
|
|
|
|
2021-11-20 20:31:59 +01:00
|
|
|
struct kbd_ctx {
|
|
|
|
|
xkb_layout_index_t layout;
|
|
|
|
|
xkb_keycode_t key;
|
|
|
|
|
xkb_keysym_t sym;
|
|
|
|
|
|
|
|
|
|
struct {
|
|
|
|
|
const xkb_keysym_t *syms;
|
|
|
|
|
size_t count;
|
|
|
|
|
} level0_syms;
|
|
|
|
|
|
|
|
|
|
xkb_mod_mask_t mods;
|
|
|
|
|
xkb_mod_mask_t consumed;
|
|
|
|
|
|
|
|
|
|
struct {
|
|
|
|
|
const uint8_t *buf;
|
|
|
|
|
size_t count;
|
|
|
|
|
} utf8;
|
2023-03-27 16:56:10 +02:00
|
|
|
uint32_t *utf32;
|
2021-11-20 20:31:59 +01:00
|
|
|
|
|
|
|
|
enum xkb_compose_status compose_status;
|
|
|
|
|
enum wl_keyboard_key_state key_state;
|
|
|
|
|
};
|
|
|
|
|
|
2021-11-29 19:31:48 +01:00
|
|
|
static bool
|
2021-11-20 20:31:59 +01:00
|
|
|
legacy_kbd_protocol(struct seat *seat, struct terminal *term,
|
|
|
|
|
const struct kbd_ctx *ctx)
|
2021-11-17 17:51:41 +01:00
|
|
|
{
|
2021-11-28 16:36:14 +01:00
|
|
|
if (ctx->key_state != WL_KEYBOARD_KEY_STATE_PRESSED)
|
2021-11-28 18:45:28 +01:00
|
|
|
return false;
|
2021-11-29 21:03:33 +01:00
|
|
|
if (ctx->compose_status == XKB_COMPOSE_COMPOSING)
|
|
|
|
|
return false;
|
2021-11-28 16:36:14 +01:00
|
|
|
|
2021-11-20 19:24:00 +01:00
|
|
|
enum modifier keymap_mods = MOD_NONE;
|
|
|
|
|
keymap_mods |= seat->kbd.shift ? MOD_SHIFT : MOD_NONE;
|
|
|
|
|
keymap_mods |= seat->kbd.alt ? MOD_ALT : MOD_NONE;
|
|
|
|
|
keymap_mods |= seat->kbd.ctrl ? MOD_CTRL : MOD_NONE;
|
2021-11-21 12:01:16 +01:00
|
|
|
keymap_mods |= seat->kbd.super ? MOD_META : MOD_NONE;
|
2021-11-20 19:24:00 +01:00
|
|
|
|
2021-11-20 20:31:59 +01:00
|
|
|
const xkb_keysym_t sym = ctx->sym;
|
|
|
|
|
const size_t count = ctx->utf8.count;
|
|
|
|
|
const uint8_t *const utf8 = ctx->utf8.buf;
|
|
|
|
|
|
2022-02-09 20:50:20 +00:00
|
|
|
const struct key_data *keymap = keymap_lookup(term, sym, keymap_mods);
|
2021-11-17 17:51:41 +01:00
|
|
|
if (keymap != NULL) {
|
|
|
|
|
term_to_slave(term, keymap->seq, strlen(keymap->seq));
|
2021-11-29 19:31:48 +01:00
|
|
|
return true;
|
2021-11-17 17:51:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (count == 0)
|
2021-11-29 19:31:48 +01:00
|
|
|
return false;
|
2021-11-17 17:51:41 +01:00
|
|
|
|
|
|
|
|
#define is_control_key(x) ((x) >= 0x40 && (x) <= 0x7f)
|
|
|
|
|
#define IS_CTRL(x) ((x) < 0x20 || ((x) >= 0x7f && (x) <= 0x9f))
|
|
|
|
|
|
2024-02-20 16:18:55 +01:00
|
|
|
//LOG_DBG("term->modify_other_keys=%d, count=%zu, is_ctrl=%d (utf8=0x%02x), sym=%d",
|
|
|
|
|
//term->modify_other_keys_2, count, IS_CTRL(utf8[0]), utf8[0], sym);
|
2021-11-17 17:51:41 +01:00
|
|
|
|
2021-11-20 19:24:00 +01:00
|
|
|
bool ctrl_is_in_effect = (keymap_mods & MOD_CTRL) != 0;
|
2021-11-17 17:51:41 +01:00
|
|
|
bool ctrl_seq = is_control_key(sym) || (count == 1 && IS_CTRL(utf8[0]));
|
|
|
|
|
|
2022-04-07 12:42:44 +02:00
|
|
|
bool modify_other_keys2_in_effect = false;
|
|
|
|
|
|
|
|
|
|
if (term->modify_other_keys_2) {
|
|
|
|
|
/*
|
2024-02-06 12:36:45 +01:00
|
|
|
* Try to mimic XTerm's behavior, when holding shift:
|
2022-04-07 12:42:44 +02:00
|
|
|
*
|
|
|
|
|
* - if other modifiers are pressed (e.g. Alt), emit a CSI escape
|
|
|
|
|
* - upper-case symbols A-Z are encoded as an CSI escape
|
2024-02-06 12:36:45 +01:00
|
|
|
* - other upper-case symbols (e.g 'Ö') or emitted as is
|
2022-04-07 12:42:44 +02:00
|
|
|
* - non-upper cased symbols are _mostly_ emitted as is (foot
|
|
|
|
|
* always emits as is)
|
|
|
|
|
*
|
|
|
|
|
* Examples (assuming Swedish layout):
|
2024-02-06 12:36:45 +01:00
|
|
|
* - Shift-a ('A') emits a CSI
|
|
|
|
|
* - Shift-, (';') emits ';'
|
2022-04-07 12:42:44 +02:00
|
|
|
* - Shift-Alt-, (Alt-;) emits a CSI
|
2024-02-06 12:36:45 +01:00
|
|
|
* - Shift-ö ('Ö') emits 'Ö'
|
2022-04-07 12:42:44 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/* Any modifiers, besides shift active? */
|
|
|
|
|
const xkb_mod_mask_t shift_mask = 1 << seat->kbd.mod_shift;
|
input/config: support *all* modifier names
That is, allow custom modifiers (i.e. other than ctrl/shift/alt etc)
in key bindings.
This is done by no longer validating/translating modifier names to
booleans for a pre-configured set of modifiers (ctrl, shift, alt,
super).
Instead, we keep the modifier *names* in a list, in the key binding
struct.
When a keymap is loaded, and we "convert" the key binding, _then_ we
do modifier translation. For invalid modifier names, we print an
error, and then ignore it. I.e. we no longer fail to load a config due
to invalid modifier names.
We also need to update how we determine the set of significant
modifiers. Any modifier not in this list will be ignored when matching
key bindings.
Before this patch, we hardcoded this to shift/alt/ctrl/super. Now, to
handle custom modifiers as well, we simply treat *all* modifiers
defined by the current layout as significant.
Typically, the only unwanted modifiers are "locked" modifiers. We are
already filtering these out.
2023-05-06 11:39:38 +02:00
|
|
|
if ((ctx->mods & ~shift_mask & seat->kbd.legacy_significant) != 0)
|
2022-04-07 12:42:44 +02:00
|
|
|
modify_other_keys2_in_effect = true;
|
|
|
|
|
|
|
|
|
|
else {
|
|
|
|
|
const xkb_layout_index_t layout_idx = xkb_state_key_get_layout(
|
|
|
|
|
seat->kbd.xkb_state, ctx->key);
|
|
|
|
|
|
|
|
|
|
/*
|
2024-02-06 12:36:45 +01:00
|
|
|
* Get pressed key's base symbol.
|
|
|
|
|
* - for 'A' (shift-a), that's 'a'
|
|
|
|
|
* - for ';' (shift-,), that's ','
|
2022-04-07 12:42:44 +02:00
|
|
|
*/
|
|
|
|
|
const xkb_keysym_t *base_syms = NULL;
|
|
|
|
|
size_t base_count = xkb_keymap_key_get_syms_by_level(
|
|
|
|
|
seat->kbd.xkb_keymap, ctx->key, layout_idx, 0, &base_syms);
|
|
|
|
|
|
|
|
|
|
/* Check if base symbol(s) is a-z. If so, emit CSI */
|
|
|
|
|
const xkb_keysym_t lower_cased_sym = xkb_keysym_to_lower(ctx->sym);
|
|
|
|
|
for (size_t i = 0; i < base_count; i++) {
|
|
|
|
|
const xkb_keysym_t s = base_syms[i];
|
|
|
|
|
if (lower_cased_sym == s && s >= XKB_KEY_a && s <= XKB_KEY_z) {
|
|
|
|
|
modify_other_keys2_in_effect = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (keymap_mods != MOD_NONE && (modify_other_keys2_in_effect ||
|
2021-11-20 19:24:00 +01:00
|
|
|
(ctrl_is_in_effect && !ctrl_seq)))
|
2021-11-17 17:51:41 +01:00
|
|
|
{
|
|
|
|
|
static const int mod_param_map[32] = {
|
|
|
|
|
[MOD_SHIFT] = 2,
|
|
|
|
|
[MOD_ALT] = 3,
|
|
|
|
|
[MOD_SHIFT | MOD_ALT] = 4,
|
|
|
|
|
[MOD_CTRL] = 5,
|
|
|
|
|
[MOD_SHIFT | MOD_CTRL] = 6,
|
|
|
|
|
[MOD_ALT | MOD_CTRL] = 7,
|
|
|
|
|
[MOD_SHIFT | MOD_ALT | MOD_CTRL] = 8,
|
|
|
|
|
[MOD_META] = 9,
|
|
|
|
|
[MOD_META | MOD_SHIFT] = 10,
|
|
|
|
|
[MOD_META | MOD_ALT] = 11,
|
|
|
|
|
[MOD_META | MOD_SHIFT | MOD_ALT] = 12,
|
|
|
|
|
[MOD_META | MOD_CTRL] = 13,
|
|
|
|
|
[MOD_META | MOD_SHIFT | MOD_CTRL] = 14,
|
|
|
|
|
[MOD_META | MOD_ALT | MOD_CTRL] = 15,
|
|
|
|
|
[MOD_META | MOD_SHIFT | MOD_ALT | MOD_CTRL] = 16,
|
|
|
|
|
};
|
|
|
|
|
|
2021-11-21 15:07:38 +01:00
|
|
|
xassert(keymap_mods < ALEN(mod_param_map));
|
2021-11-20 19:24:00 +01:00
|
|
|
int modify_param = mod_param_map[keymap_mods];
|
2021-11-17 17:51:41 +01:00
|
|
|
xassert(modify_param != 0);
|
|
|
|
|
|
2021-11-21 15:05:38 +01:00
|
|
|
char reply[32];
|
2021-11-17 17:51:41 +01:00
|
|
|
size_t n = xsnprintf(reply, sizeof(reply), "\x1b[27;%d;%d~", modify_param, sym);
|
|
|
|
|
term_to_slave(term, reply, n);
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-20 19:24:00 +01:00
|
|
|
else if (keymap_mods & MOD_ALT) {
|
2021-11-17 17:51:41 +01:00
|
|
|
/*
|
|
|
|
|
* When the alt modifier is pressed, we do one out of three things:
|
|
|
|
|
*
|
|
|
|
|
* 1. we prefix the output bytes with ESC
|
|
|
|
|
* 2. we set the 8:th bit in the output byte
|
|
|
|
|
* 3. we ignore the alt modifier
|
|
|
|
|
*
|
|
|
|
|
* #1 is configured with \E[?1036, and is on by default
|
|
|
|
|
*
|
2021-11-21 15:06:41 +01:00
|
|
|
* If #1 has been disabled, we use #2, *if* it's a single byte
|
|
|
|
|
* we're emitting. Since this is a UTF-8 terminal, we then
|
|
|
|
|
* UTF8-encode the 8-bit character. #2 is configured with
|
|
|
|
|
* \E[?1034, and is on by default.
|
2021-11-17 17:51:41 +01:00
|
|
|
*
|
|
|
|
|
* Lastly, if both #1 and #2 have been disabled, the alt
|
|
|
|
|
* modifier is ignored.
|
|
|
|
|
*/
|
|
|
|
|
if (term->meta.esc_prefix) {
|
|
|
|
|
term_to_slave(term, "\x1b", 1);
|
|
|
|
|
term_to_slave(term, utf8, count);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else if (term->meta.eight_bit && count == 1) {
|
fcft: adapt to API changes in fcft-3.x
Fcft no longer uses wchar_t, but plain uint32_t to represent
codepoints.
Since we do a fair amount of string operations in foot, it still makes
sense to use something that actually _is_ a string (or character),
rather than an array of uint32_t.
For this reason, we switch out all wchar_t usage in foot to
char32_t. We also verify, at compile-time, that char32_t used
UTF-32 (which is what fcft expects).
Unfortunately, there are no string functions for char32_t. To avoid
having to re-implement all wcs*() functions, we add a small wrapper
layer of c32*() functions.
These wrapper functions take char32_t arguments, but then simply call
the corresponding wcs*() function.
For this to work, wcs*() must _also_ be UTF-32 compatible. We can
check for the presence of the __STDC_ISO_10646__ macro. If set,
wchar_t is at least 4 bytes and its internal representation is UTF-32.
FreeBSD does *not* define this macro, because its internal wchar_t
representation depends on the current locale. It _does_ use UTF-32
_if_ the current locale is UTF-8.
Since foot enforces UTF-8, we simply need to check if __FreeBSD__ is
defined.
Other fcft API changes:
* fcft_glyph_rasterize() -> fcft_codepoint_rasterize()
* font.space_advance has been removed
* ‘tags’ have been removed from fcft_grapheme_rasterize()
* ‘fcft_log_init()’ removed
* ‘fcft_init()’ and ‘fcft_fini()’ must be explicitly called
2021-08-21 14:50:42 +02:00
|
|
|
const char32_t wc = 0x80 | utf8[0];
|
2021-11-17 17:51:41 +01:00
|
|
|
|
fcft: adapt to API changes in fcft-3.x
Fcft no longer uses wchar_t, but plain uint32_t to represent
codepoints.
Since we do a fair amount of string operations in foot, it still makes
sense to use something that actually _is_ a string (or character),
rather than an array of uint32_t.
For this reason, we switch out all wchar_t usage in foot to
char32_t. We also verify, at compile-time, that char32_t used
UTF-32 (which is what fcft expects).
Unfortunately, there are no string functions for char32_t. To avoid
having to re-implement all wcs*() functions, we add a small wrapper
layer of c32*() functions.
These wrapper functions take char32_t arguments, but then simply call
the corresponding wcs*() function.
For this to work, wcs*() must _also_ be UTF-32 compatible. We can
check for the presence of the __STDC_ISO_10646__ macro. If set,
wchar_t is at least 4 bytes and its internal representation is UTF-32.
FreeBSD does *not* define this macro, because its internal wchar_t
representation depends on the current locale. It _does_ use UTF-32
_if_ the current locale is UTF-8.
Since foot enforces UTF-8, we simply need to check if __FreeBSD__ is
defined.
Other fcft API changes:
* fcft_glyph_rasterize() -> fcft_codepoint_rasterize()
* font.space_advance has been removed
* ‘tags’ have been removed from fcft_grapheme_rasterize()
* ‘fcft_log_init()’ removed
* ‘fcft_init()’ and ‘fcft_fini()’ must be explicitly called
2021-08-21 14:50:42 +02:00
|
|
|
char utf8_meta[MB_CUR_MAX];
|
|
|
|
|
size_t chars = c32rtomb(utf8_meta, wc, &(mbstate_t){0});
|
2021-11-17 17:51:41 +01:00
|
|
|
|
|
|
|
|
if (chars != (size_t)-1)
|
fcft: adapt to API changes in fcft-3.x
Fcft no longer uses wchar_t, but plain uint32_t to represent
codepoints.
Since we do a fair amount of string operations in foot, it still makes
sense to use something that actually _is_ a string (or character),
rather than an array of uint32_t.
For this reason, we switch out all wchar_t usage in foot to
char32_t. We also verify, at compile-time, that char32_t used
UTF-32 (which is what fcft expects).
Unfortunately, there are no string functions for char32_t. To avoid
having to re-implement all wcs*() functions, we add a small wrapper
layer of c32*() functions.
These wrapper functions take char32_t arguments, but then simply call
the corresponding wcs*() function.
For this to work, wcs*() must _also_ be UTF-32 compatible. We can
check for the presence of the __STDC_ISO_10646__ macro. If set,
wchar_t is at least 4 bytes and its internal representation is UTF-32.
FreeBSD does *not* define this macro, because its internal wchar_t
representation depends on the current locale. It _does_ use UTF-32
_if_ the current locale is UTF-8.
Since foot enforces UTF-8, we simply need to check if __FreeBSD__ is
defined.
Other fcft API changes:
* fcft_glyph_rasterize() -> fcft_codepoint_rasterize()
* font.space_advance has been removed
* ‘tags’ have been removed from fcft_grapheme_rasterize()
* ‘fcft_log_init()’ removed
* ‘fcft_init()’ and ‘fcft_fini()’ must be explicitly called
2021-08-21 14:50:42 +02:00
|
|
|
term_to_slave(term, utf8_meta, chars);
|
2021-11-17 17:51:41 +01:00
|
|
|
else
|
fcft: adapt to API changes in fcft-3.x
Fcft no longer uses wchar_t, but plain uint32_t to represent
codepoints.
Since we do a fair amount of string operations in foot, it still makes
sense to use something that actually _is_ a string (or character),
rather than an array of uint32_t.
For this reason, we switch out all wchar_t usage in foot to
char32_t. We also verify, at compile-time, that char32_t used
UTF-32 (which is what fcft expects).
Unfortunately, there are no string functions for char32_t. To avoid
having to re-implement all wcs*() functions, we add a small wrapper
layer of c32*() functions.
These wrapper functions take char32_t arguments, but then simply call
the corresponding wcs*() function.
For this to work, wcs*() must _also_ be UTF-32 compatible. We can
check for the presence of the __STDC_ISO_10646__ macro. If set,
wchar_t is at least 4 bytes and its internal representation is UTF-32.
FreeBSD does *not* define this macro, because its internal wchar_t
representation depends on the current locale. It _does_ use UTF-32
_if_ the current locale is UTF-8.
Since foot enforces UTF-8, we simply need to check if __FreeBSD__ is
defined.
Other fcft API changes:
* fcft_glyph_rasterize() -> fcft_codepoint_rasterize()
* font.space_advance has been removed
* ‘tags’ have been removed from fcft_grapheme_rasterize()
* ‘fcft_log_init()’ removed
* ‘fcft_init()’ and ‘fcft_fini()’ must be explicitly called
2021-08-21 14:50:42 +02:00
|
|
|
term_to_slave(term, utf8, count);
|
2021-11-17 17:51:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else {
|
|
|
|
|
/* Alt ignored */
|
|
|
|
|
term_to_slave(term, utf8, count);
|
|
|
|
|
}
|
|
|
|
|
} else
|
|
|
|
|
term_to_slave(term, utf8, count);
|
2021-11-29 19:31:48 +01:00
|
|
|
|
|
|
|
|
return true;
|
2021-11-17 17:51:41 +01:00
|
|
|
}
|
|
|
|
|
|
2021-12-11 14:51:56 +01:00
|
|
|
UNITTEST
|
|
|
|
|
{
|
|
|
|
|
/* Verify the kitty keymap is sorted */
|
|
|
|
|
xkb_keysym_t last = 0;
|
|
|
|
|
for (size_t i = 0; i < ALEN(kitty_keymap); i++) {
|
|
|
|
|
const struct kitty_key_data *e = &kitty_keymap[i];
|
|
|
|
|
xassert(e->sym > last);
|
|
|
|
|
last = e->sym;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
kitty_search(const void *_key, const void *_e)
|
|
|
|
|
{
|
|
|
|
|
const xkb_keysym_t *key = _key;
|
|
|
|
|
const struct kitty_key_data *e = _e;
|
|
|
|
|
return *key - e->sym;
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-29 19:31:48 +01:00
|
|
|
static bool
|
2021-11-20 20:31:59 +01:00
|
|
|
kitty_kbd_protocol(struct seat *seat, struct terminal *term,
|
|
|
|
|
const struct kbd_ctx *ctx)
|
2021-11-20 19:24:00 +01:00
|
|
|
{
|
2021-11-28 18:45:28 +01:00
|
|
|
const bool repeating = seat->kbd.repeat.dont_re_repeat;
|
|
|
|
|
const bool pressed = ctx->key_state == WL_KEYBOARD_KEY_STATE_PRESSED && !repeating;
|
|
|
|
|
const bool released = ctx->key_state == WL_KEYBOARD_KEY_STATE_RELEASED;
|
2021-11-29 21:03:33 +01:00
|
|
|
const bool composing = ctx->compose_status == XKB_COMPOSE_COMPOSING;
|
input: kitty: merge handling of plain-text and composed characters
All plain-text and composed characters are now printed as-is, in a
single place.
Also fix handling of “generic” keys when emitted as escapes; don’t use
the raw XKB symbol as key in the escape, convert it to a unicode code
point first. For many symbols, these are the same. But not
all.
For now, we fallback to using the symbol as is if XKB fails to convert
it to a codepoint. Not sure if we should simply drop the key press
instead.
Composed characters also need special treatment; we can’t use the
symbol as is, since it typically refers to the last key
pressed (i.e. not the composed character). And, that key is
also (usually) a special “dead” key, which cannot be converted to a
unicode codepoint.
So, what we do is convert the generated utf8 string, and (try to)
convert it to a wchar. If it succeeds, use that. If not, fallback to
using the XKB symbol (as above).
2021-11-29 20:13:23 +01:00
|
|
|
const bool composed = ctx->compose_status == XKB_COMPOSE_COMPOSED;
|
2021-11-28 18:45:28 +01:00
|
|
|
|
2021-12-07 07:31:34 +01:00
|
|
|
const enum kitty_kbd_flags flags =
|
|
|
|
|
term->grid->kitty_kbd.flags[term->grid->kitty_kbd.idx];
|
kitty: implement “report associated text”
In this mode, key events that generate text now add a third CSI
parameter, indicating the actual codepoint.
Remember that we always use the *unshifted* key in the CSI
escapes. With this mode, those CSI escapes now also included the text
codepoint. I.e. what would have been emitted, had we not generated a
CSI escape.
As far as I can tell, this mode has no effect unless “report all keys
as escape sequences” is enabled (reason being, without that, there
aren’t any text events that generate CSIs - they’re always emitted
as-is).
Note that Kitty itself seems to be somewhat buggy in this mode. At
least on Wayland, with my Swedish layout. For example ‘a’ and ‘A’ does
generate the expected CSIs, but ‘å’ and ‘Å’ appears to be treated as
non-text input.
Furthermore, Kitty optimizes away the modifier parameter, if no
modifiers are pressed (e.g. CSI 97;;97u), while we always emit the
modifier (CSI 97;1;97u).
Related to #319
2021-12-06 23:01:41 +01:00
|
|
|
|
2021-11-28 18:45:28 +01:00
|
|
|
const bool disambiguate = flags & KITTY_KBD_DISAMBIGUATE;
|
|
|
|
|
const bool report_events = flags & KITTY_KBD_REPORT_EVENT;
|
2021-12-07 19:55:52 +01:00
|
|
|
const bool report_alternate = flags & KITTY_KBD_REPORT_ALTERNATE;
|
2021-11-29 21:03:33 +01:00
|
|
|
const bool report_all_as_escapes = flags & KITTY_KBD_REPORT_ALL;
|
2021-11-28 18:45:28 +01:00
|
|
|
|
2021-11-28 19:20:37 +01:00
|
|
|
if (!report_events && released)
|
2021-11-28 18:45:28 +01:00
|
|
|
return false;
|
|
|
|
|
|
2024-02-06 12:36:45 +01:00
|
|
|
/* TODO: should we even bother with this, or just say it's not supported? */
|
2021-11-29 21:03:33 +01:00
|
|
|
if (!disambiguate && !report_all_as_escapes && pressed)
|
2021-11-28 18:45:28 +01:00
|
|
|
return legacy_kbd_protocol(seat, term, ctx);
|
|
|
|
|
|
2021-12-07 07:31:34 +01:00
|
|
|
const xkb_keysym_t sym = ctx->sym;
|
2023-03-27 16:56:10 +02:00
|
|
|
const uint32_t *utf32 = ctx->utf32;
|
2021-12-07 07:31:34 +01:00
|
|
|
const uint8_t *const utf8 = ctx->utf8.buf;
|
|
|
|
|
const size_t count = ctx->utf8.count;
|
|
|
|
|
|
input: kitty: update to latest version of the spec
Starting with kitty 0.32.0, the modifier bits during modifier key
events behave differently, compared to before. Or, rather, they have
now been spec:ed; before, behavior was different on e.g. MacOS, and
Linux.
The new behavior is this:
On key press, the modifier bits in the kitty key event *includes* the
pressed modifier key.
On key release, the modifier bits in the kitty key event does *not*
include the released modifier key.
In other words, The modifier bits reflects the state *after* the key
event.
This is the exact opposite of what foot did before this patch.
The patch is really pretty small: in order to include the key in the
modifier set, we simulate a key press to update the XKB state, using
xkb_state_uppate_key(). For key pressed, we simulate an XKB_KEY_DOWN
event, and for key releases we simulate an XKB_KEY_UP event.
Then we re-retrieve the modifers, both the full set, and the consumed
set.
Closes #1561
2024-01-22 16:39:26 +01:00
|
|
|
/* Lookup sym in the pre-defined keysym table */
|
|
|
|
|
const struct kitty_key_data *info = bsearch(
|
|
|
|
|
&sym, kitty_keymap, ALEN(kitty_keymap), sizeof(kitty_keymap[0]),
|
|
|
|
|
&kitty_search);
|
|
|
|
|
xassert(info == NULL || info->sym == sym);
|
|
|
|
|
|
|
|
|
|
xkb_mod_mask_t mods = 0;
|
2024-03-02 08:16:17 +01:00
|
|
|
xkb_mod_mask_t locked = 0;
|
2024-02-29 09:32:38 +01:00
|
|
|
xkb_mod_mask_t consumed = ctx->consumed;
|
input: kitty: update to latest version of the spec
Starting with kitty 0.32.0, the modifier bits during modifier key
events behave differently, compared to before. Or, rather, they have
now been spec:ed; before, behavior was different on e.g. MacOS, and
Linux.
The new behavior is this:
On key press, the modifier bits in the kitty key event *includes* the
pressed modifier key.
On key release, the modifier bits in the kitty key event does *not*
include the released modifier key.
In other words, The modifier bits reflects the state *after* the key
event.
This is the exact opposite of what foot did before this patch.
The patch is really pretty small: in order to include the key in the
modifier set, we simulate a key press to update the XKB state, using
xkb_state_uppate_key(). For key pressed, we simulate an XKB_KEY_DOWN
event, and for key releases we simulate an XKB_KEY_UP event.
Then we re-retrieve the modifers, both the full set, and the consumed
set.
Closes #1561
2024-01-22 16:39:26 +01:00
|
|
|
|
|
|
|
|
if (info != NULL && info->is_modifier) {
|
|
|
|
|
/*
|
|
|
|
|
* Special-case modifier keys.
|
|
|
|
|
*
|
|
|
|
|
* Normally, the "current" XKB state reflects the state
|
|
|
|
|
* *before* the current key event. In other words, the
|
|
|
|
|
* modifiers for key events that affect the modifier state
|
|
|
|
|
* (e.g. one of the control keys, or shift keys etc) does
|
|
|
|
|
* *not* include the key itself.
|
|
|
|
|
*
|
|
|
|
|
* Put another way, if you press "control", the modifier set
|
|
|
|
|
* is empty in the key press event, but contains "ctrl" in the
|
|
|
|
|
* release event.
|
|
|
|
|
*
|
|
|
|
|
* The kitty protocol mandates the modifier list contain the
|
|
|
|
|
* key itself, in *both* the press and release event.
|
|
|
|
|
*
|
|
|
|
|
* We handle this by updating the XKB state to *include* the
|
|
|
|
|
* current key, retrieve the set of modifiers (including the
|
|
|
|
|
* set of consumed modifiers), and then revert the XKB update.
|
|
|
|
|
*/
|
|
|
|
|
xkb_state_update_key(
|
|
|
|
|
seat->kbd.xkb_state, ctx->key, pressed ? XKB_KEY_DOWN : XKB_KEY_UP);
|
|
|
|
|
|
2024-03-02 08:16:17 +01:00
|
|
|
get_current_modifiers(seat, &mods, NULL, 0, false);
|
|
|
|
|
|
|
|
|
|
locked = xkb_state_serialize_mods(
|
|
|
|
|
seat->kbd.xkb_state, XKB_STATE_MODS_LOCKED);
|
input: kitty: update to latest version of the spec
Starting with kitty 0.32.0, the modifier bits during modifier key
events behave differently, compared to before. Or, rather, they have
now been spec:ed; before, behavior was different on e.g. MacOS, and
Linux.
The new behavior is this:
On key press, the modifier bits in the kitty key event *includes* the
pressed modifier key.
On key release, the modifier bits in the kitty key event does *not*
include the released modifier key.
In other words, The modifier bits reflects the state *after* the key
event.
This is the exact opposite of what foot did before this patch.
The patch is really pretty small: in order to include the key in the
modifier set, we simulate a key press to update the XKB state, using
xkb_state_uppate_key(). For key pressed, we simulate an XKB_KEY_DOWN
event, and for key releases we simulate an XKB_KEY_UP event.
Then we re-retrieve the modifers, both the full set, and the consumed
set.
Closes #1561
2024-01-22 16:39:26 +01:00
|
|
|
consumed = xkb_state_key_get_consumed_mods2(
|
2024-02-29 09:32:38 +01:00
|
|
|
seat->kbd.xkb_state, ctx->key, XKB_CONSUMED_MODE_XKB);
|
input: kitty: update to latest version of the spec
Starting with kitty 0.32.0, the modifier bits during modifier key
events behave differently, compared to before. Or, rather, they have
now been spec:ed; before, behavior was different on e.g. MacOS, and
Linux.
The new behavior is this:
On key press, the modifier bits in the kitty key event *includes* the
pressed modifier key.
On key release, the modifier bits in the kitty key event does *not*
include the released modifier key.
In other words, The modifier bits reflects the state *after* the key
event.
This is the exact opposite of what foot did before this patch.
The patch is really pretty small: in order to include the key in the
modifier set, we simulate a key press to update the XKB state, using
xkb_state_uppate_key(). For key pressed, we simulate an XKB_KEY_DOWN
event, and for key releases we simulate an XKB_KEY_UP event.
Then we re-retrieve the modifers, both the full set, and the consumed
set.
Closes #1561
2024-01-22 16:39:26 +01:00
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
|
/*
|
|
|
|
|
* TODO: according to the XKB docs, state updates should
|
|
|
|
|
* always be in pairs: each press should be followed by a
|
|
|
|
|
* release. However, doing this just breaks the xkb state.
|
|
|
|
|
*
|
|
|
|
|
* *Not* pairing the above press/release with a corresponding
|
|
|
|
|
* release/press appears to do exactly what we want.
|
|
|
|
|
*/
|
|
|
|
|
xkb_state_update_key(
|
|
|
|
|
seat->kbd.xkb_state, ctx->key, pressed ? XKB_KEY_UP : XKB_KEY_DOWN);
|
|
|
|
|
#endif
|
|
|
|
|
} else {
|
2024-03-02 08:16:17 +01:00
|
|
|
/* Same as ctx->mods, but *without* filtering locked modifiers */
|
|
|
|
|
get_current_modifiers(seat, &mods, NULL, 0, false);
|
|
|
|
|
locked = xkb_state_serialize_mods(
|
|
|
|
|
seat->kbd.xkb_state, XKB_STATE_MODS_LOCKED);
|
input: kitty: update to latest version of the spec
Starting with kitty 0.32.0, the modifier bits during modifier key
events behave differently, compared to before. Or, rather, they have
now been spec:ed; before, behavior was different on e.g. MacOS, and
Linux.
The new behavior is this:
On key press, the modifier bits in the kitty key event *includes* the
pressed modifier key.
On key release, the modifier bits in the kitty key event does *not*
include the released modifier key.
In other words, The modifier bits reflects the state *after* the key
event.
This is the exact opposite of what foot did before this patch.
The patch is really pretty small: in order to include the key in the
modifier set, we simulate a key press to update the XKB state, using
xkb_state_uppate_key(). For key pressed, we simulate an XKB_KEY_DOWN
event, and for key releases we simulate an XKB_KEY_UP event.
Then we re-retrieve the modifers, both the full set, and the consumed
set.
Closes #1561
2024-01-22 16:39:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mods &= seat->kbd.kitty_significant;
|
|
|
|
|
consumed &= seat->kbd.kitty_significant;
|
|
|
|
|
|
2024-03-02 08:16:17 +01:00
|
|
|
/*
|
|
|
|
|
* A note on locked modifiers; they *are* a part of the protocol,
|
|
|
|
|
* and *should* be included in the modifier set reported in the
|
|
|
|
|
* key event.
|
|
|
|
|
*
|
|
|
|
|
* However, *only* if the key would result in a CSIu *without* the
|
|
|
|
|
* locked modifier being enabled
|
|
|
|
|
*
|
|
|
|
|
* Translated: if *another* modifier is active, or if
|
|
|
|
|
* report-all-keys-as-escapes is enabled, then we include the
|
|
|
|
|
* locked modifier in the key event.
|
|
|
|
|
*
|
|
|
|
|
* But, if the key event would result in plain text output without
|
|
|
|
|
* the locked modifier, then we "ignore" the locked modifier and
|
|
|
|
|
* emit plain text anyway.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
bool is_text = count > 0 && utf32 != NULL && (mods & ~locked & ~consumed) == 0;
|
2023-03-27 16:56:10 +02:00
|
|
|
for (size_t i = 0; utf32[i] != U'\0'; i++) {
|
2025-01-22 10:26:44 +01:00
|
|
|
if (!isc32print(utf32[i])) {
|
2023-03-27 16:56:10 +02:00
|
|
|
is_text = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-07 07:31:34 +01:00
|
|
|
const bool report_associated_text =
|
|
|
|
|
(flags & KITTY_KBD_REPORT_ASSOCIATED) && is_text && !released;
|
|
|
|
|
|
2021-11-29 21:03:33 +01:00
|
|
|
if (composing) {
|
|
|
|
|
/* We never emit anything while composing, *except* modifiers
|
|
|
|
|
* (and only in report-all-keys-as-escape-codes mode) */
|
2021-12-11 14:51:56 +01:00
|
|
|
if (info != NULL && info->is_modifier)
|
2021-11-29 21:03:33 +01:00
|
|
|
goto emit_escapes;
|
|
|
|
|
|
2021-12-11 14:51:56 +01:00
|
|
|
return false;
|
2021-11-29 21:03:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (report_all_as_escapes)
|
|
|
|
|
goto emit_escapes;
|
|
|
|
|
|
2024-03-02 08:16:17 +01:00
|
|
|
if ((mods & ~locked & ~consumed) == 0) {
|
2021-11-20 19:24:00 +01:00
|
|
|
switch (sym) {
|
2025-01-01 08:06:52 +01:00
|
|
|
case XKB_KEY_Return:
|
|
|
|
|
if (!released)
|
|
|
|
|
term_to_slave(term, "\r", 1);
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
case XKB_KEY_BackSpace:
|
|
|
|
|
if (!released)
|
|
|
|
|
term_to_slave(term, "\x7f", 1);
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
case XKB_KEY_Tab:
|
|
|
|
|
if (!released)
|
|
|
|
|
term_to_slave(term, "\t", 1);
|
|
|
|
|
return true;
|
2021-11-20 19:24:00 +01:00
|
|
|
}
|
2021-11-21 11:38:36 +01:00
|
|
|
}
|
2021-11-20 19:24:00 +01:00
|
|
|
|
input: kitty: merge handling of plain-text and composed characters
All plain-text and composed characters are now printed as-is, in a
single place.
Also fix handling of “generic” keys when emitted as escapes; don’t use
the raw XKB symbol as key in the escape, convert it to a unicode code
point first. For many symbols, these are the same. But not
all.
For now, we fallback to using the symbol as is if XKB fails to convert
it to a codepoint. Not sure if we should simply drop the key press
instead.
Composed characters also need special treatment; we can’t use the
symbol as is, since it typically refers to the last key
pressed (i.e. not the composed character). And, that key is
also (usually) a special “dead” key, which cannot be converted to a
unicode codepoint.
So, what we do is convert the generated utf8 string, and (try to)
convert it to a wchar. If it succeeds, use that. If not, fallback to
using the XKB symbol (as above).
2021-11-29 20:13:23 +01:00
|
|
|
/* Plain-text without modifiers, or commposed text, is emitted as-is */
|
2021-12-07 20:26:12 +01:00
|
|
|
if (is_text && !released) {
|
2021-11-21 11:38:36 +01:00
|
|
|
term_to_slave(term, utf8, count);
|
2021-11-29 19:31:48 +01:00
|
|
|
return true;
|
2021-11-20 19:24:00 +01:00
|
|
|
}
|
|
|
|
|
|
2021-11-29 21:03:33 +01:00
|
|
|
emit_escapes:
|
|
|
|
|
;
|
2021-11-20 19:24:00 +01:00
|
|
|
unsigned int encoded_mods = 0;
|
2021-11-21 11:53:22 +01:00
|
|
|
if (seat->kbd.mod_shift != XKB_MOD_INVALID)
|
|
|
|
|
encoded_mods |= mods & (1 << seat->kbd.mod_shift) ? (1 << 0) : 0;
|
|
|
|
|
if (seat->kbd.mod_alt != XKB_MOD_INVALID)
|
|
|
|
|
encoded_mods |= mods & (1 << seat->kbd.mod_alt) ? (1 << 1) : 0;
|
|
|
|
|
if (seat->kbd.mod_ctrl != XKB_MOD_INVALID)
|
|
|
|
|
encoded_mods |= mods & (1 << seat->kbd.mod_ctrl) ? (1 << 2) : 0;
|
2021-11-21 12:01:16 +01:00
|
|
|
if (seat->kbd.mod_super != XKB_MOD_INVALID)
|
|
|
|
|
encoded_mods |= mods & (1 << seat->kbd.mod_super) ? (1 << 3) : 0;
|
2021-11-21 11:53:22 +01:00
|
|
|
if (seat->kbd.mod_caps != XKB_MOD_INVALID)
|
|
|
|
|
encoded_mods |= mods & (1 << seat->kbd.mod_caps) ? (1 << 6) : 0;
|
|
|
|
|
if (seat->kbd.mod_num != XKB_MOD_INVALID)
|
|
|
|
|
encoded_mods |= mods & (1 << seat->kbd.mod_num) ? (1 << 7) : 0;
|
2021-11-20 19:24:00 +01:00
|
|
|
encoded_mods++;
|
|
|
|
|
|
2025-01-22 12:37:36 +01:00
|
|
|
/*
|
|
|
|
|
* Figure out the main, alternate and base key codes.
|
|
|
|
|
*
|
|
|
|
|
* The main key is the unshifted version of the generated symbol,
|
|
|
|
|
* the alternate key is the shifted version, and base is the
|
|
|
|
|
* (unshifted) key assuming the default layout.
|
|
|
|
|
*
|
|
|
|
|
* For example, the user presses shift+a, then:
|
|
|
|
|
* - unshifted = 'a'
|
|
|
|
|
* - shifted = 'A'
|
|
|
|
|
* - base = 'a'
|
|
|
|
|
*
|
|
|
|
|
* Base will in many cases be the same as the unshifted key, but
|
|
|
|
|
* may differ if the active keyboard layout is non-ASCII (examples
|
|
|
|
|
* would be russian, or alternative layouts like neo etc).
|
|
|
|
|
*
|
|
|
|
|
* The shifted key is what we get from XKB, i.e. the resulting key
|
|
|
|
|
* from all active modifiers, plus the pressed key.
|
|
|
|
|
*/
|
|
|
|
|
int unshifted = -1, shifted = -1, base = -1;
|
2021-11-20 19:24:00 +01:00
|
|
|
char final;
|
|
|
|
|
|
2021-12-11 14:51:56 +01:00
|
|
|
if (info != NULL) {
|
2025-01-22 12:37:36 +01:00
|
|
|
/* Use code from lookup table (cursor keys, enter, tab etc)*/
|
2021-12-11 14:51:56 +01:00
|
|
|
if (!info->is_modifier || report_all_as_escapes) {
|
2025-01-22 12:37:36 +01:00
|
|
|
shifted = info->key;
|
2021-12-11 14:51:56 +01:00
|
|
|
final = info->final;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2025-01-22 12:37:36 +01:00
|
|
|
/* Use keysym (typically its Unicode codepoint value) */
|
2021-11-21 12:40:04 +01:00
|
|
|
|
2022-07-26 18:44:29 +02:00
|
|
|
if (composed)
|
2025-01-22 12:37:36 +01:00
|
|
|
shifted = utf32[0]; /* TODO: what if there are multiple codepoints? */
|
|
|
|
|
else
|
|
|
|
|
shifted = xkb_keysym_to_utf32(sym);
|
2021-12-07 21:48:42 +01:00
|
|
|
|
input: kitty: merge handling of plain-text and composed characters
All plain-text and composed characters are now printed as-is, in a
single place.
Also fix handling of “generic” keys when emitted as escapes; don’t use
the raw XKB symbol as key in the escape, convert it to a unicode code
point first. For many symbols, these are the same. But not
all.
For now, we fallback to using the symbol as is if XKB fails to convert
it to a codepoint. Not sure if we should simply drop the key press
instead.
Composed characters also need special treatment; we can’t use the
symbol as is, since it typically refers to the last key
pressed (i.e. not the composed character). And, that key is
also (usually) a special “dead” key, which cannot be converted to a
unicode codepoint.
So, what we do is convert the generated utf8 string, and (try to)
convert it to a wchar. If it succeeds, use that. If not, fallback to
using the XKB symbol (as above).
2021-11-29 20:13:23 +01:00
|
|
|
final = 'u';
|
|
|
|
|
}
|
2021-11-20 19:24:00 +01:00
|
|
|
|
2025-01-22 12:37:36 +01:00
|
|
|
if (shifted <= 0)
|
2021-12-16 12:37:58 +01:00
|
|
|
return false;
|
|
|
|
|
|
2025-01-20 10:34:45 +01:00
|
|
|
/* Base layout key. I.e the symbol the pressed key produces in
|
|
|
|
|
* the base/default layout (layout idx 0) */
|
|
|
|
|
const xkb_keysym_t *base_syms;
|
|
|
|
|
int base_sym_count = xkb_keymap_key_get_syms_by_level(
|
|
|
|
|
seat->kbd.xkb_keymap, ctx->key, 0, 0, &base_syms);
|
|
|
|
|
|
|
|
|
|
if (base_sym_count > 0)
|
|
|
|
|
base = xkb_keysym_to_utf32(base_syms[0]);
|
|
|
|
|
|
2025-01-22 12:37:36 +01:00
|
|
|
/*
|
|
|
|
|
* If the keysym is shifted, use its unshifted codepoint
|
|
|
|
|
* instead. In other words, ctrl+a and ctrl+shift+a should both
|
|
|
|
|
* use the same value for 'key' (97 - i.a. 'a').
|
|
|
|
|
*
|
|
|
|
|
* However, don't do this if a non-significant modifier was used
|
|
|
|
|
* to generate the symbol. This is needed since we cannot encode
|
|
|
|
|
* non-significant modifiers, and thus the "extra" modifier(s)
|
|
|
|
|
* would get lost.
|
|
|
|
|
*
|
|
|
|
|
* Example:
|
|
|
|
|
*
|
|
|
|
|
* the Swedish layout has '2', QUOTATION MARK ("double quote"),
|
|
|
|
|
* '@', and '²' on the same key. '2' is the base symbol.
|
|
|
|
|
*
|
|
|
|
|
* Shift+2 results in QUOTATION MARK
|
|
|
|
|
* AltGr+2 results in '@'
|
|
|
|
|
* AltGr+Shift+2 results in '²'
|
|
|
|
|
*
|
|
|
|
|
* The kitty kbd protocol can't encode AltGr. So, if we always
|
|
|
|
|
* used the base symbol ('2'), Alt+Shift+2 would result in the
|
|
|
|
|
* same escape sequence as AltGr+Alt+Shift+2.
|
|
|
|
|
*
|
|
|
|
|
* (yes, this matches what kitty does, as of 0.23.1)
|
|
|
|
|
*/
|
|
|
|
|
const bool use_level0_sym =
|
|
|
|
|
(ctx->mods & ~seat->kbd.kitty_significant) == 0 && ctx->level0_syms.count > 0;
|
|
|
|
|
|
|
|
|
|
unshifted = use_level0_sym ? xkb_keysym_to_utf32(ctx->level0_syms.syms[0]) : 0;
|
|
|
|
|
|
2021-11-20 19:24:00 +01:00
|
|
|
xassert(encoded_mods >= 1);
|
|
|
|
|
|
2021-11-28 18:45:28 +01:00
|
|
|
char event[4];
|
2021-12-07 21:52:43 +01:00
|
|
|
if (report_events /*&& !pressed*/) {
|
2021-11-28 18:45:28 +01:00
|
|
|
/* Note: this deviates slightly from Kitty, which omits the
|
2024-02-06 12:36:45 +01:00
|
|
|
* ":1" subparameter for key press events */
|
2021-11-28 18:45:28 +01:00
|
|
|
event[0] = ':';
|
|
|
|
|
event[1] = '0' + (pressed ? 1 : repeating ? 2 : 3);
|
|
|
|
|
event[2] = '\0';
|
|
|
|
|
} else
|
|
|
|
|
event[0] = '\0';
|
|
|
|
|
|
2023-03-27 16:56:10 +02:00
|
|
|
char buf[128], *p = buf;
|
kitty: implement “report associated text”
In this mode, key events that generate text now add a third CSI
parameter, indicating the actual codepoint.
Remember that we always use the *unshifted* key in the CSI
escapes. With this mode, those CSI escapes now also included the text
codepoint. I.e. what would have been emitted, had we not generated a
CSI escape.
As far as I can tell, this mode has no effect unless “report all keys
as escape sequences” is enabled (reason being, without that, there
aren’t any text events that generate CSIs - they’re always emitted
as-is).
Note that Kitty itself seems to be somewhat buggy in this mode. At
least on Wayland, with my Swedish layout. For example ‘a’ and ‘A’ does
generate the expected CSIs, but ‘å’ and ‘Å’ appears to be treated as
non-text input.
Furthermore, Kitty optimizes away the modifier parameter, if no
modifiers are pressed (e.g. CSI 97;;97u), while we always emit the
modifier (CSI 97;1;97u).
Related to #319
2021-12-06 23:01:41 +01:00
|
|
|
size_t left = sizeof(buf);
|
|
|
|
|
size_t bytes;
|
2021-11-20 19:24:00 +01:00
|
|
|
|
2025-03-10 15:47:20 +01:00
|
|
|
const int key = unshifted > 0 && isc32print(unshifted) && !composed ? unshifted : shifted;
|
2025-01-22 12:37:36 +01:00
|
|
|
const int alternate = shifted;
|
|
|
|
|
|
2021-11-20 19:24:00 +01:00
|
|
|
if (final == 'u' || final == '~') {
|
kitty: implement “report associated text”
In this mode, key events that generate text now add a third CSI
parameter, indicating the actual codepoint.
Remember that we always use the *unshifted* key in the CSI
escapes. With this mode, those CSI escapes now also included the text
codepoint. I.e. what would have been emitted, had we not generated a
CSI escape.
As far as I can tell, this mode has no effect unless “report all keys
as escape sequences” is enabled (reason being, without that, there
aren’t any text events that generate CSIs - they’re always emitted
as-is).
Note that Kitty itself seems to be somewhat buggy in this mode. At
least on Wayland, with my Swedish layout. For example ‘a’ and ‘A’ does
generate the expected CSIs, but ‘å’ and ‘Å’ appears to be treated as
non-text input.
Furthermore, Kitty optimizes away the modifier parameter, if no
modifiers are pressed (e.g. CSI 97;;97u), while we always emit the
modifier (CSI 97;1;97u).
Related to #319
2021-12-06 23:01:41 +01:00
|
|
|
bytes = snprintf(p, left, "\x1b[%u", key);
|
|
|
|
|
p += bytes; left -= bytes;
|
|
|
|
|
|
2021-12-07 20:25:14 +01:00
|
|
|
if (report_alternate) {
|
|
|
|
|
bool emit_alternate = alternate > 0 && alternate != key;
|
2025-01-22 10:26:44 +01:00
|
|
|
bool emit_base = base > 0 && base != key && base != alternate && isc32print(base);
|
2021-12-07 20:25:14 +01:00
|
|
|
|
|
|
|
|
if (emit_alternate) {
|
|
|
|
|
bytes = snprintf(p, left, ":%u", alternate);
|
|
|
|
|
p += bytes; left -= bytes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (emit_base) {
|
|
|
|
|
bytes = snprintf(
|
|
|
|
|
p, left, "%s:%u", !emit_alternate ? ":" : "", base);
|
|
|
|
|
p += bytes; left -= bytes;
|
|
|
|
|
}
|
2021-12-07 19:55:52 +01:00
|
|
|
}
|
|
|
|
|
|
2021-12-07 21:52:43 +01:00
|
|
|
bool emit_mods = encoded_mods > 1 || event[0] != '\0';
|
|
|
|
|
|
|
|
|
|
if (emit_mods) {
|
kitty: implement “report associated text”
In this mode, key events that generate text now add a third CSI
parameter, indicating the actual codepoint.
Remember that we always use the *unshifted* key in the CSI
escapes. With this mode, those CSI escapes now also included the text
codepoint. I.e. what would have been emitted, had we not generated a
CSI escape.
As far as I can tell, this mode has no effect unless “report all keys
as escape sequences” is enabled (reason being, without that, there
aren’t any text events that generate CSIs - they’re always emitted
as-is).
Note that Kitty itself seems to be somewhat buggy in this mode. At
least on Wayland, with my Swedish layout. For example ‘a’ and ‘A’ does
generate the expected CSIs, but ‘å’ and ‘Å’ appears to be treated as
non-text input.
Furthermore, Kitty optimizes away the modifier parameter, if no
modifiers are pressed (e.g. CSI 97;;97u), while we always emit the
modifier (CSI 97;1;97u).
Related to #319
2021-12-06 23:01:41 +01:00
|
|
|
bytes = snprintf(p, left, ";%u%s", encoded_mods, event);
|
|
|
|
|
p += bytes; left -= bytes;
|
2021-12-07 21:52:43 +01:00
|
|
|
}
|
kitty: implement “report associated text”
In this mode, key events that generate text now add a third CSI
parameter, indicating the actual codepoint.
Remember that we always use the *unshifted* key in the CSI
escapes. With this mode, those CSI escapes now also included the text
codepoint. I.e. what would have been emitted, had we not generated a
CSI escape.
As far as I can tell, this mode has no effect unless “report all keys
as escape sequences” is enabled (reason being, without that, there
aren’t any text events that generate CSIs - they’re always emitted
as-is).
Note that Kitty itself seems to be somewhat buggy in this mode. At
least on Wayland, with my Swedish layout. For example ‘a’ and ‘A’ does
generate the expected CSIs, but ‘å’ and ‘Å’ appears to be treated as
non-text input.
Furthermore, Kitty optimizes away the modifier parameter, if no
modifiers are pressed (e.g. CSI 97;;97u), while we always emit the
modifier (CSI 97;1;97u).
Related to #319
2021-12-06 23:01:41 +01:00
|
|
|
|
2021-12-07 21:52:43 +01:00
|
|
|
if (report_associated_text) {
|
2023-03-27 16:56:10 +02:00
|
|
|
bytes = snprintf(p, left, "%s;%u", !emit_mods ? ";" : "", utf32[0]);
|
2021-12-07 21:52:43 +01:00
|
|
|
p += bytes; left -= bytes;
|
2023-03-27 16:56:10 +02:00
|
|
|
|
|
|
|
|
/* Additional text codepoints */
|
|
|
|
|
if (utf32[0] != U'\0') {
|
|
|
|
|
for (size_t i = 1; utf32[i] != U'\0'; i++) {
|
|
|
|
|
bytes = snprintf(p, left, ":%u", utf32[i]);
|
|
|
|
|
p += bytes; left -= bytes;
|
|
|
|
|
}
|
|
|
|
|
}
|
kitty: implement “report associated text”
In this mode, key events that generate text now add a third CSI
parameter, indicating the actual codepoint.
Remember that we always use the *unshifted* key in the CSI
escapes. With this mode, those CSI escapes now also included the text
codepoint. I.e. what would have been emitted, had we not generated a
CSI escape.
As far as I can tell, this mode has no effect unless “report all keys
as escape sequences” is enabled (reason being, without that, there
aren’t any text events that generate CSIs - they’re always emitted
as-is).
Note that Kitty itself seems to be somewhat buggy in this mode. At
least on Wayland, with my Swedish layout. For example ‘a’ and ‘A’ does
generate the expected CSIs, but ‘å’ and ‘Å’ appears to be treated as
non-text input.
Furthermore, Kitty optimizes away the modifier parameter, if no
modifiers are pressed (e.g. CSI 97;;97u), while we always emit the
modifier (CSI 97;1;97u).
Related to #319
2021-12-06 23:01:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bytes = snprintf(p, left, "%c", final);
|
|
|
|
|
p += bytes; left -= bytes;
|
2021-11-20 19:24:00 +01:00
|
|
|
} else {
|
kitty: implement “report associated text”
In this mode, key events that generate text now add a third CSI
parameter, indicating the actual codepoint.
Remember that we always use the *unshifted* key in the CSI
escapes. With this mode, those CSI escapes now also included the text
codepoint. I.e. what would have been emitted, had we not generated a
CSI escape.
As far as I can tell, this mode has no effect unless “report all keys
as escape sequences” is enabled (reason being, without that, there
aren’t any text events that generate CSIs - they’re always emitted
as-is).
Note that Kitty itself seems to be somewhat buggy in this mode. At
least on Wayland, with my Swedish layout. For example ‘a’ and ‘A’ does
generate the expected CSIs, but ‘å’ and ‘Å’ appears to be treated as
non-text input.
Furthermore, Kitty optimizes away the modifier parameter, if no
modifiers are pressed (e.g. CSI 97;;97u), while we always emit the
modifier (CSI 97;1;97u).
Related to #319
2021-12-06 23:01:41 +01:00
|
|
|
if (encoded_mods > 1 || event[0] != '\0') {
|
|
|
|
|
bytes = snprintf(p, left, "\x1b[1;%u%s%c", encoded_mods, event, final);
|
|
|
|
|
p += bytes; left -= bytes;
|
|
|
|
|
} else {
|
|
|
|
|
bytes = snprintf(p, left, "\x1b[%c", final);
|
|
|
|
|
p += bytes; left -= bytes;
|
|
|
|
|
}
|
2021-11-20 19:24:00 +01:00
|
|
|
}
|
|
|
|
|
|
2021-12-12 15:08:46 +01:00
|
|
|
return term_to_slave(term, buf, sizeof(buf) - left);
|
2021-11-20 19:24:00 +01:00
|
|
|
}
|
|
|
|
|
|
2022-04-20 18:23:20 +02:00
|
|
|
/* Copied from libxkbcommon (internal function) */
|
|
|
|
|
static bool
|
|
|
|
|
keysym_is_modifier(xkb_keysym_t keysym)
|
|
|
|
|
{
|
|
|
|
|
return
|
|
|
|
|
(keysym >= XKB_KEY_Shift_L && keysym <= XKB_KEY_Hyper_R) ||
|
2022-04-20 19:03:48 +02:00
|
|
|
/* libX11 only goes up to XKB_KEY_ISO_Level5_Lock. */
|
2022-04-20 18:23:20 +02:00
|
|
|
(keysym >= XKB_KEY_ISO_Lock && keysym <= XKB_KEY_ISO_Last_Group_Lock) ||
|
|
|
|
|
keysym == XKB_KEY_Mode_switch ||
|
|
|
|
|
keysym == XKB_KEY_Num_Lock;
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-20 16:18:55 +01:00
|
|
|
#if defined(_DEBUG)
|
|
|
|
|
static void
|
|
|
|
|
modifier_string(xkb_mod_mask_t mods, size_t sz, char mod_str[static sz], const struct seat *seat)
|
|
|
|
|
{
|
|
|
|
|
if (sz == 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
mod_str[0] = '\0';
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < sizeof(xkb_mod_mask_t) * 8; i++) {
|
|
|
|
|
if (!(mods & (1u << i)))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
strcat(mod_str, xkb_keymap_mod_get_name(seat->kbd.xkb_keymap, i));
|
|
|
|
|
strcat(mod_str, "+");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (mod_str[0] != '\0') {
|
|
|
|
|
/* Strip the last '+' */
|
|
|
|
|
mod_str[strlen(mod_str) - 1] = '\0';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (mod_str[0] == '\0') {
|
|
|
|
|
strcpy(mod_str, "<none>");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2019-06-19 10:04:47 +02:00
|
|
|
static void
|
2020-11-03 19:44:51 +01:00
|
|
|
key_press_release(struct seat *seat, struct terminal *term, uint32_t serial,
|
|
|
|
|
uint32_t key, uint32_t state)
|
2019-06-19 10:04:47 +02:00
|
|
|
{
|
2022-04-23 15:54:37 +02:00
|
|
|
xassert(serial != 0);
|
|
|
|
|
|
2021-10-20 18:47:36 +02:00
|
|
|
seat->kbd.serial = serial;
|
2020-10-20 21:01:33 +02:00
|
|
|
if (seat->kbd.xkb == NULL ||
|
|
|
|
|
seat->kbd.xkb_keymap == NULL ||
|
|
|
|
|
seat->kbd.xkb_state == NULL)
|
|
|
|
|
{
|
2020-07-14 08:52:11 +02:00
|
|
|
return;
|
2020-10-20 21:01:33 +02:00
|
|
|
}
|
2020-07-14 08:52:11 +02:00
|
|
|
|
2021-11-28 16:36:14 +01:00
|
|
|
const bool pressed = state == WL_KEYBOARD_KEY_STATE_PRESSED;
|
|
|
|
|
//const bool repeated = pressed && seat->kbd.repeat.dont_re_repeat;
|
|
|
|
|
const bool released = state == WL_KEYBOARD_KEY_STATE_RELEASED;
|
|
|
|
|
|
|
|
|
|
if (released)
|
2020-07-08 16:45:26 +02:00
|
|
|
stop_repeater(seat, key);
|
2019-06-19 10:04:47 +02:00
|
|
|
|
2021-11-28 16:36:14 +01:00
|
|
|
bool should_repeat =
|
|
|
|
|
pressed && xkb_keymap_key_repeats(seat->kbd.xkb_keymap, key);
|
|
|
|
|
|
2020-07-08 16:45:26 +02:00
|
|
|
xkb_keysym_t sym = xkb_state_key_get_one_sym(seat->kbd.xkb_state, key);
|
2019-06-19 10:04:47 +02:00
|
|
|
|
2022-04-20 18:23:20 +02:00
|
|
|
if (pressed && term->conf->mouse.hide_when_typing && !keysym_is_modifier(sym)) {
|
2020-08-01 15:39:25 +02:00
|
|
|
seat->pointer.hidden = true;
|
|
|
|
|
term_xcursor_update_for_seat(term, seat);
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-19 18:30:19 +02:00
|
|
|
enum xkb_compose_status compose_status = XKB_COMPOSE_NOTHING;
|
|
|
|
|
|
|
|
|
|
if (seat->kbd.xkb_compose_state != NULL) {
|
2021-11-28 16:36:14 +01:00
|
|
|
if (pressed)
|
|
|
|
|
xkb_compose_state_feed(seat->kbd.xkb_compose_state, sym);
|
2020-10-19 18:30:19 +02:00
|
|
|
compose_status = xkb_compose_state_get_status(
|
|
|
|
|
seat->kbd.xkb_compose_state);
|
|
|
|
|
}
|
2019-07-09 10:00:54 +02:00
|
|
|
|
2021-11-29 21:03:33 +01:00
|
|
|
const bool composed = compose_status == XKB_COMPOSE_COMPOSED;
|
2019-07-09 10:00:54 +02:00
|
|
|
|
2021-10-06 20:12:17 +02:00
|
|
|
xkb_mod_mask_t mods, consumed;
|
2024-02-06 10:41:01 +01:00
|
|
|
get_current_modifiers(seat, &mods, &consumed, key, true);
|
2021-11-21 11:27:31 +01:00
|
|
|
|
input: rewrite of how we match foot’s own key bindings
Bindings are matched in one out of three ways:
* By translated (by XKB) symbols
* By untranslated symbols
* By raw key codes
A translated symbol is affected by pressed modifiers, some of which
can be “consumed”. Consumed modifiers to not partake in the comparison
with the binding’s modifiers. In this mode, ctrl+shift+2 maps to
ctrl+@ on a US layout.
Untranslated symbols, or un-shifted symbols refer to the “base” symbol
of the pressed key, i.e. it’s unaffected by modifiers. In this mode,
consumed modifiers *do* partake in the comparison with the binding’s
modifiers, and ctrl+shift+2 maps to ctrl+shift+2 on a US layout.
More examples: ctrl+shift+u maps to ctrl+U in the translated lookup,
while ctrl+shift+u maps to ctrl+shift+u in the untranslated lookup.
Finally, we also match raw key codes. This allows our bindings to work
using the same physical keys when the user switches between latin and
non-latin layouts.
This means key bindings in foot.ini *must* not include both +shift+
and a *shifted* key. I.e. ctrl+shift+U is not a valid combo as it
cannot be triggered. Unfortunately, this was how you were supposed to
write bindings up until now... so, we try to detect such bindings, log
a deprecation warning and then “fix” the binding for the user.
When specifying bindings in foot.ini, both ctrl+U and ctrl+shift+u are
valid, and will work. The latter is preferred though, since we cannot
detect the raw key code for the former variant. Personally, I also
prefer the latter one because it is more explicit; it’s more obvious
which keys are involved.
However, in some cases it makes more sense to use the other
variant. Typically for non-letter combos.
2021-02-27 20:42:31 +01:00
|
|
|
xkb_layout_index_t layout_idx =
|
|
|
|
|
xkb_state_key_get_layout(seat->kbd.xkb_state, key);
|
|
|
|
|
|
|
|
|
|
const xkb_keysym_t *raw_syms = NULL;
|
|
|
|
|
size_t raw_count = xkb_keymap_key_get_syms_by_level(
|
|
|
|
|
seat->kbd.xkb_keymap, key, layout_idx, 0, &raw_syms);
|
|
|
|
|
|
key-binding: new API, for handling sets of key bindings
Up until now, our Wayland seats have been tracking key bindings. This
makes sense, since the seat’s keymap determines how the key bindings
are resolved.
However, tying bindings to the seat/keymap alone isn’t enough, since
we also depend on the current configuration (i.e. user settings) when
resolving a key binding.
This means configurations that doesn’t match the wayland object’s
configuration, currently don’t resolve key bindings correctly. This
applies to footclients where the user has overridden key bindings on
the command line (e.g. --override key-bindings.foo=bar).
Thus, to correctly resolve key bindings, each set of key bindings must
be tied *both* to a seat/keymap, *and* a configuration.
This patch introduces a key-binding manager, with an API to
add/remove/lookup, and load/unload keymaps from sets of key bindings.
In the API, sets are tied to a seat and terminal instance, since this
makes the most sense (we need to instantiate, or incref a set whenever
a new terminal instance is created). Internally, the set is tied to a
seat and the terminal’s configuration.
Sets are *added* when a new seat is added, and when a new terminal
instance is created. Since there can only be one instance of each
seat, sets are always removed when a seat is removed.
Terminals on the other hand can re-use the same configuration (and
typically do). Thus, sets ref-count the configuration. In other words,
when instantiating a new terminal, we may not have to instantiate a
new set of key bindings, but can often be incref:ed instead.
Whenever the keymap changes on a seat, all key bindings sets
associated with that seat reloads (re-resolves) their key bindings.
Closes #931
2022-04-17 15:39:51 +02:00
|
|
|
const struct key_binding_set *bindings = key_binding_for(
|
terminal: don’t unref a not-yet-referenced key-binding set
Key-binding sets are bound to a seat/configuration pair. The conf
reference is done when a new terminal instance is created.
When that same terminal instance is destroyed, the key binding set is
unref:ed.
If the terminal instance is destroyed *before* the key binding set has
been referenced, we’ll still unref it. This creates an imbalance.
In particular, when the there is exactly one other terminal instance
referencing that same key binding set, that terminal instance will
trigger a foot server crash as soon as it receives a key press/release
event. This happens because the next-to-last terminal instance brought
the reference count of the binding set down to 0, causing it to be
free:d.
Thus, we *must* reference the binding set *before* we can error
out (when instantiating a new terminal instance).
At this point, we don’t yet have a valid terminal instance. But,
that’s ok, because all the key_binding_new_for_term() did with the
terminal instance was get the "struct wayland" and "struct config"
pointers. So, rename the function and simply pass these pointers
explicitly.
Similarly, change key_binding_for() to take a "struct config" pointer,
rather than a "struct terminal" pointer.
Also rename key_binding_unref_term() -> key_binding_unref().
2022-09-05 19:23:40 +02:00
|
|
|
seat->wayl->key_binding_manager, term->conf, seat);
|
key-binding: new API, for handling sets of key bindings
Up until now, our Wayland seats have been tracking key bindings. This
makes sense, since the seat’s keymap determines how the key bindings
are resolved.
However, tying bindings to the seat/keymap alone isn’t enough, since
we also depend on the current configuration (i.e. user settings) when
resolving a key binding.
This means configurations that doesn’t match the wayland object’s
configuration, currently don’t resolve key bindings correctly. This
applies to footclients where the user has overridden key bindings on
the command line (e.g. --override key-bindings.foo=bar).
Thus, to correctly resolve key bindings, each set of key bindings must
be tied *both* to a seat/keymap, *and* a configuration.
This patch introduces a key-binding manager, with an API to
add/remove/lookup, and load/unload keymaps from sets of key bindings.
In the API, sets are tied to a seat and terminal instance, since this
makes the most sense (we need to instantiate, or incref a set whenever
a new terminal instance is created). Internally, the set is tied to a
seat and the terminal’s configuration.
Sets are *added* when a new seat is added, and when a new terminal
instance is created. Since there can only be one instance of each
seat, sets are always removed when a seat is removed.
Terminals on the other hand can re-use the same configuration (and
typically do). Thus, sets ref-count the configuration. In other words,
when instantiating a new terminal, we may not have to instantiate a
new set of key bindings, but can often be incref:ed instead.
Whenever the keymap changes on a seat, all key bindings sets
associated with that seat reloads (re-resolves) their key bindings.
Closes #931
2022-04-17 15:39:51 +02:00
|
|
|
xassert(bindings != NULL);
|
|
|
|
|
|
2021-11-28 16:36:14 +01:00
|
|
|
if (pressed) {
|
unicode-mode: move state from seat to term
This fixes an issue where entering unicode-mode in one foot client,
also enabled unicode-mode on other foot clients. Both
visually (although glitchy), and in effect.
The reason the state was originally in the seat objects, was to fully
support multi-seat. That is, one seat/keyboard entering unicode-mode
should not affect other seats/keyboards.
The issue with this is that seat objects are Wayland global. Thus, in
server mode, all seat objects are shared between the foot clients.
There is a similarity with IME, which also keeps state in the
seat. There's one big difference, however, and that is IME has Wayland
native enter/leave events, that the compositor emits when windows are
focused/unfocused. These events allow us to reset IME state. For our
own Unicode mode, there is nothing similar.
This patch moves the Unicode state from seats, to the terminal
struct. This does mean that if one seat/keyboard enters Unicode mode,
then *all* seats/keyboards will affect the unicode state. This
potential downside is outweighed by the fact that different foot
clients no longer affect each other.
Closes #1717
2024-05-21 07:06:45 +02:00
|
|
|
if (term->unicode_mode.active) {
|
2022-07-28 19:34:13 +02:00
|
|
|
unicode_mode_input(seat, term, sym);
|
2022-07-28 18:09:16 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else if (term->is_searching) {
|
2021-11-28 16:36:14 +01:00
|
|
|
if (should_repeat)
|
|
|
|
|
start_repeater(seat, key);
|
|
|
|
|
|
|
|
|
|
search_input(
|
2024-02-06 10:41:01 +01:00
|
|
|
seat, term, bindings, key, sym, mods, consumed,
|
2021-11-28 16:36:14 +01:00
|
|
|
raw_syms, raw_count, serial);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else if (urls_mode_is_active(term)) {
|
|
|
|
|
if (should_repeat)
|
|
|
|
|
start_repeater(seat, key);
|
|
|
|
|
|
|
|
|
|
urls_input(
|
2024-02-06 10:41:01 +01:00
|
|
|
seat, term, bindings, key, sym, mods, consumed,
|
2021-11-28 16:36:14 +01:00
|
|
|
raw_syms, raw_count, serial);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2019-08-27 17:23:28 +02:00
|
|
|
}
|
|
|
|
|
|
2020-11-30 20:01:46 +01:00
|
|
|
#if defined(_DEBUG) && defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG
|
|
|
|
|
char sym_name[100];
|
|
|
|
|
xkb_keysym_get_name(sym, sym_name, sizeof(sym_name));
|
|
|
|
|
|
2024-02-20 16:18:55 +01:00
|
|
|
char active_mods_str[256] = {0};
|
|
|
|
|
char consumed_mods_str[256] = {0};
|
|
|
|
|
char locked_mods_str[256] = {0};
|
|
|
|
|
|
|
|
|
|
const xkb_mod_mask_t locked =
|
|
|
|
|
xkb_state_serialize_mods(seat->kbd.xkb_state, XKB_STATE_MODS_LOCKED);
|
|
|
|
|
|
|
|
|
|
modifier_string(mods, sizeof(active_mods_str), active_mods_str, seat);
|
|
|
|
|
modifier_string(consumed, sizeof(consumed_mods_str), consumed_mods_str, seat);
|
|
|
|
|
modifier_string(locked, sizeof(locked_mods_str), locked_mods_str, seat);
|
|
|
|
|
|
|
|
|
|
LOG_DBG("%s: %s (%u/0x%x), seat=%s, term=%p, serial=%u, "
|
|
|
|
|
"mods=%s (0x%08x), consumed=%s (0x%08x), locked=%s (0x%08x), "
|
|
|
|
|
"repeats=%d",
|
|
|
|
|
pressed ? "pressed" : "released", sym_name, sym, sym,
|
|
|
|
|
seat->name, (void *)term, serial,
|
|
|
|
|
active_mods_str, mods, consumed_mods_str, consumed,
|
|
|
|
|
locked_mods_str, locked, should_repeat);
|
2021-08-04 17:27:34 +02:00
|
|
|
#endif
|
2019-07-09 14:27:26 +02:00
|
|
|
|
2020-01-11 18:59:46 +01:00
|
|
|
/*
|
2020-03-08 15:17:29 +01:00
|
|
|
* User configurable bindings
|
2020-01-11 18:59:46 +01:00
|
|
|
*/
|
2021-11-28 16:36:14 +01:00
|
|
|
if (pressed) {
|
2025-01-27 10:51:03 +01:00
|
|
|
/* Match untranslated symbols */
|
|
|
|
|
tll_foreach(bindings->key, it) {
|
|
|
|
|
const struct key_binding *bind = &it->item;
|
2020-03-18 14:29:34 +01:00
|
|
|
|
2025-01-31 07:35:54 +01:00
|
|
|
if (bind->mods != mods || bind->mods == 0)
|
2021-11-28 16:36:14 +01:00
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < raw_count; i++) {
|
2022-02-08 19:43:00 +01:00
|
|
|
if (bind->k.sym == raw_syms[i] &&
|
2023-09-18 16:36:39 +02:00
|
|
|
execute_binding(seat, term, bind, serial, 1))
|
2021-11-28 16:36:14 +01:00
|
|
|
{
|
|
|
|
|
goto maybe_repeat;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-01-27 10:51:03 +01:00
|
|
|
}
|
|
|
|
|
|
input: match unshifted key-bindings before shifted
That is, try to match e.g. Control+shift+a, before trying to match
Control+A.
In most cases, order doesn't matter. There are however a couple of
symbols where the layout consumes the shift-modifier, and the
generated symbol is the same in both the shifted and unshifted
form. One such example is backspace.
Before this patch, key-bindings with shift-backspace would be ignored,
if there were another key-binding with backspace.
So, for example, if we had one key-binding with Control+Backspace, and
another with Control+Shift+Backspace, the latter would never trigger,
as we would always match the first one.
By checking for unshifted matches first, we ensure
Control+Shift+Backspace does match.
2025-01-31 09:07:42 +01:00
|
|
|
/* Match translated symbol */
|
|
|
|
|
tll_foreach(bindings->key, it) {
|
|
|
|
|
const struct key_binding *bind = &it->item;
|
|
|
|
|
|
|
|
|
|
if (bind->k.sym == sym &&
|
|
|
|
|
bind->mods == (mods & ~consumed) &&
|
|
|
|
|
execute_binding(seat, term, bind, serial, 1))
|
|
|
|
|
{
|
|
|
|
|
goto maybe_repeat;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-27 10:51:03 +01:00
|
|
|
/* Match raw key code */
|
|
|
|
|
tll_foreach(bindings->key, it) {
|
|
|
|
|
const struct key_binding *bind = &it->item;
|
|
|
|
|
|
2025-01-31 07:35:54 +01:00
|
|
|
if (bind->mods != mods || bind->mods == 0)
|
2025-01-27 10:51:03 +01:00
|
|
|
continue;
|
2021-11-28 16:36:14 +01:00
|
|
|
|
2022-02-07 19:41:33 +01:00
|
|
|
tll_foreach(bind->k.key_codes, code) {
|
2022-02-08 19:43:00 +01:00
|
|
|
if (code->item == key &&
|
2023-09-18 16:36:39 +02:00
|
|
|
execute_binding(seat, term, bind, serial, 1))
|
2021-11-28 16:36:14 +01:00
|
|
|
{
|
|
|
|
|
goto maybe_repeat;
|
|
|
|
|
}
|
2020-03-18 14:29:34 +01:00
|
|
|
}
|
|
|
|
|
}
|
2019-07-11 12:16:50 +02:00
|
|
|
}
|
|
|
|
|
|
2020-01-11 18:59:46 +01:00
|
|
|
/*
|
|
|
|
|
* Keys generating escape sequences
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Compose, and maybe emit "normal" character
|
|
|
|
|
*/
|
|
|
|
|
|
2021-11-29 21:03:33 +01:00
|
|
|
xassert(seat->kbd.xkb_compose_state != NULL || !composed);
|
2019-11-22 22:02:50 +01:00
|
|
|
|
2021-11-17 17:51:41 +01:00
|
|
|
if (compose_status == XKB_COMPOSE_CANCELLED)
|
|
|
|
|
goto maybe_repeat;
|
|
|
|
|
|
2021-11-29 21:03:33 +01:00
|
|
|
int count = composed
|
2020-10-20 21:01:33 +02:00
|
|
|
? xkb_compose_state_get_utf8(seat->kbd.xkb_compose_state, NULL, 0)
|
|
|
|
|
: xkb_state_key_get_utf8(seat->kbd.xkb_state, key, NULL, 0);
|
2020-10-19 18:30:19 +02:00
|
|
|
|
2020-10-20 21:01:33 +02:00
|
|
|
/* Buffer for translated key. Use a static buffer in most cases,
|
|
|
|
|
* and use a malloc:ed buffer when necessary */
|
|
|
|
|
uint8_t buf[32];
|
|
|
|
|
uint8_t *utf8 = count < sizeof(buf) ? buf : xmalloc(count + 1);
|
2023-03-27 16:56:10 +02:00
|
|
|
uint32_t *utf32 = NULL;
|
2019-06-19 10:04:47 +02:00
|
|
|
|
2021-11-29 21:03:33 +01:00
|
|
|
if (composed) {
|
2021-11-20 19:24:00 +01:00
|
|
|
xkb_compose_state_get_utf8(
|
|
|
|
|
seat->kbd.xkb_compose_state, (char *)utf8, count + 1);
|
2021-12-07 07:31:34 +01:00
|
|
|
|
2023-03-27 16:56:10 +02:00
|
|
|
if (count > 0)
|
|
|
|
|
utf32 = ambstoc32((const char *)utf8);
|
2021-11-20 19:24:00 +01:00
|
|
|
} else {
|
|
|
|
|
xkb_state_key_get_utf8(
|
2020-10-20 21:01:33 +02:00
|
|
|
seat->kbd.xkb_state, key, (char *)utf8, count + 1);
|
2023-03-27 16:56:10 +02:00
|
|
|
|
|
|
|
|
utf32 = xcalloc(2, sizeof(utf32[0]));
|
|
|
|
|
utf32[0] = xkb_state_key_get_utf32(seat->kbd.xkb_state, key);
|
2021-11-20 19:24:00 +01:00
|
|
|
}
|
|
|
|
|
|
2021-11-20 20:31:59 +01:00
|
|
|
struct kbd_ctx ctx = {
|
|
|
|
|
.layout = layout_idx,
|
|
|
|
|
.key = key,
|
|
|
|
|
.sym = sym,
|
|
|
|
|
.level0_syms = {
|
|
|
|
|
.syms = raw_syms,
|
|
|
|
|
.count = raw_count,
|
|
|
|
|
},
|
|
|
|
|
.mods = mods,
|
|
|
|
|
.consumed = consumed,
|
|
|
|
|
.utf8 = {
|
|
|
|
|
.buf = utf8,
|
|
|
|
|
.count = count,
|
|
|
|
|
},
|
|
|
|
|
.utf32 = utf32,
|
|
|
|
|
.compose_status = compose_status,
|
|
|
|
|
.key_state = state,
|
|
|
|
|
};
|
|
|
|
|
|
2021-11-29 19:31:48 +01:00
|
|
|
bool handled = term->grid->kitty_kbd.flags[term->grid->kitty_kbd.idx] != 0
|
|
|
|
|
? kitty_kbd_protocol(seat, term, &ctx)
|
|
|
|
|
: legacy_kbd_protocol(seat, term, &ctx);
|
2020-10-20 21:01:33 +02:00
|
|
|
|
2021-11-29 21:03:33 +01:00
|
|
|
if (composed && released)
|
2020-10-20 21:01:33 +02:00
|
|
|
xkb_compose_state_reset(seat->kbd.xkb_compose_state);
|
2019-06-19 10:04:47 +02:00
|
|
|
|
2020-10-20 21:01:33 +02:00
|
|
|
if (utf8 != buf)
|
|
|
|
|
free(utf8);
|
|
|
|
|
|
2024-02-22 15:08:13 -06:00
|
|
|
if (handled && !keysym_is_modifier(sym)) {
|
2021-11-28 16:48:30 +01:00
|
|
|
term_reset_view(term);
|
|
|
|
|
selection_cancel(term);
|
|
|
|
|
}
|
2019-11-22 22:19:00 +01:00
|
|
|
|
2023-03-27 16:56:10 +02:00
|
|
|
free(utf32);
|
|
|
|
|
|
2019-11-22 22:19:00 +01:00
|
|
|
maybe_repeat:
|
2020-01-21 18:51:04 +01:00
|
|
|
clock_gettime(
|
|
|
|
|
term->wl->presentation_clock_id, &term->render.input_time);
|
|
|
|
|
|
2019-11-22 22:19:00 +01:00
|
|
|
if (should_repeat)
|
2020-11-03 19:44:51 +01:00
|
|
|
start_repeater(seat, key);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial,
|
|
|
|
|
uint32_t time, uint32_t key, uint32_t state)
|
|
|
|
|
{
|
|
|
|
|
struct seat *seat = data;
|
|
|
|
|
key_press_release(seat, seat->kbd_focus, serial, key + 8, state);
|
2019-06-19 10:04:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
keyboard_modifiers(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial,
|
|
|
|
|
uint32_t mods_depressed, uint32_t mods_latched,
|
|
|
|
|
uint32_t mods_locked, uint32_t group)
|
|
|
|
|
{
|
2020-07-08 16:45:26 +02:00
|
|
|
struct seat *seat = data;
|
2019-06-19 10:04:47 +02:00
|
|
|
|
2025-03-29 10:34:40 +01:00
|
|
|
mods_depressed &= ~seat->kbd.virtual_modifiers;
|
|
|
|
|
mods_latched &= ~seat->kbd.virtual_modifiers;
|
|
|
|
|
mods_locked &= ~seat->kbd.virtual_modifiers;
|
|
|
|
|
|
2024-02-20 16:18:55 +01:00
|
|
|
#if defined(_DEBUG)
|
|
|
|
|
char depressed[256];
|
|
|
|
|
char latched[256];
|
|
|
|
|
char locked[256];
|
|
|
|
|
|
|
|
|
|
modifier_string(mods_depressed, sizeof(depressed), depressed, seat);
|
|
|
|
|
modifier_string(mods_latched, sizeof(latched), latched, seat);
|
|
|
|
|
modifier_string(mods_locked, sizeof(locked), locked, seat);
|
|
|
|
|
|
|
|
|
|
LOG_DBG(
|
|
|
|
|
"modifiers: depressed=%s (0x%x), latched=%s (0x%x), locked=%s (0x%x), "
|
|
|
|
|
"group=%u",
|
|
|
|
|
depressed, mods_depressed, latched, mods_latched, locked, mods_locked,
|
|
|
|
|
group);
|
|
|
|
|
#endif
|
2019-06-19 10:04:47 +02:00
|
|
|
|
2020-10-20 21:01:33 +02:00
|
|
|
if (seat->kbd.xkb_state != NULL) {
|
|
|
|
|
xkb_state_update_mask(
|
|
|
|
|
seat->kbd.xkb_state, mods_depressed, mods_latched, mods_locked, 0, 0, group);
|
|
|
|
|
|
|
|
|
|
/* Update state of modifiers we're interested in for e.g mouse events */
|
2021-11-21 11:53:22 +01:00
|
|
|
seat->kbd.shift = seat->kbd.mod_shift != XKB_MOD_INVALID
|
|
|
|
|
? xkb_state_mod_index_is_active(
|
|
|
|
|
seat->kbd.xkb_state, seat->kbd.mod_shift, XKB_STATE_MODS_EFFECTIVE)
|
|
|
|
|
: false;
|
|
|
|
|
seat->kbd.alt = seat->kbd.mod_alt != XKB_MOD_INVALID
|
|
|
|
|
? xkb_state_mod_index_is_active(
|
|
|
|
|
seat->kbd.xkb_state, seat->kbd.mod_alt, XKB_STATE_MODS_EFFECTIVE)
|
|
|
|
|
: false;
|
|
|
|
|
seat->kbd.ctrl = seat->kbd.mod_ctrl != XKB_MOD_INVALID
|
|
|
|
|
? xkb_state_mod_index_is_active(
|
|
|
|
|
seat->kbd.xkb_state, seat->kbd.mod_ctrl, XKB_STATE_MODS_EFFECTIVE)
|
|
|
|
|
: false;
|
2021-11-21 12:01:16 +01:00
|
|
|
seat->kbd.super = seat->kbd.mod_super != XKB_MOD_INVALID
|
2021-11-21 11:53:22 +01:00
|
|
|
? xkb_state_mod_index_is_active(
|
2021-11-21 12:01:16 +01:00
|
|
|
seat->kbd.xkb_state, seat->kbd.mod_super, XKB_STATE_MODS_EFFECTIVE)
|
2021-11-21 11:53:22 +01:00
|
|
|
: false;
|
2020-10-20 21:01:33 +02:00
|
|
|
}
|
2020-07-08 16:45:26 +02:00
|
|
|
|
|
|
|
|
if (seat->kbd_focus && seat->kbd_focus->active_surface == TERM_SURF_GRID)
|
2020-07-31 17:09:06 +02:00
|
|
|
term_xcursor_update_for_seat(seat->kbd_focus, seat);
|
2019-06-19 10:04:47 +02:00
|
|
|
}
|
|
|
|
|
|
2025-01-21 08:37:30 +01:00
|
|
|
UNITTEST
|
|
|
|
|
{
|
|
|
|
|
int chan[2];
|
2025-08-30 08:18:31 +02:00
|
|
|
xassert(pipe2(chan, O_CLOEXEC) == 0);
|
2025-01-21 08:37:30 +01:00
|
|
|
|
|
|
|
|
xassert(chan[0] >= 0);
|
|
|
|
|
xassert(chan[1] >= 0);
|
|
|
|
|
|
|
|
|
|
struct config conf = {0};
|
|
|
|
|
struct grid grid = {0};
|
|
|
|
|
|
|
|
|
|
struct terminal term = {
|
|
|
|
|
.conf = &conf,
|
|
|
|
|
.grid = &grid,
|
|
|
|
|
.ptmx = chan[1],
|
|
|
|
|
.selection = {
|
|
|
|
|
.coords = {
|
|
|
|
|
.start = {-1, -1},
|
|
|
|
|
.end = {-1, -1},
|
|
|
|
|
},
|
|
|
|
|
.auto_scroll = {
|
|
|
|
|
.fd = -1,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct key_binding_manager *key_binding_manager = key_binding_manager_new();
|
|
|
|
|
|
|
|
|
|
struct wayland wayl = {
|
|
|
|
|
.key_binding_manager = key_binding_manager,
|
|
|
|
|
.terms = tll_init(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct seat seat = {
|
|
|
|
|
.wayl = &wayl,
|
|
|
|
|
.name = "unittest",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
tll_push_back(wayl.terms, &term);
|
|
|
|
|
term.wl = &wayl;
|
|
|
|
|
|
|
|
|
|
seat.kbd.xkb = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
|
|
|
|
|
xassert(seat.kbd.xkb != NULL);
|
|
|
|
|
|
|
|
|
|
grid.kitty_kbd.flags[0] = KITTY_KBD_DISAMBIGUATE | KITTY_KBD_REPORT_ALTERNATE;
|
|
|
|
|
|
|
|
|
|
/* Swedish keymap */
|
|
|
|
|
{
|
|
|
|
|
seat.kbd.xkb_keymap = xkb_keymap_new_from_names(
|
|
|
|
|
seat.kbd.xkb, &(struct xkb_rule_names){.layout = "se"}, XKB_KEYMAP_COMPILE_NO_FLAGS);
|
|
|
|
|
if (seat.kbd.xkb_keymap == NULL) {
|
|
|
|
|
/* Skip test */
|
|
|
|
|
goto no_keymap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
seat.kbd.xkb_state = xkb_state_new(seat.kbd.xkb_keymap);
|
|
|
|
|
xassert(seat.kbd.xkb_state != NULL);
|
|
|
|
|
|
|
|
|
|
seat.kbd.mod_shift = xkb_keymap_mod_get_index(seat.kbd.xkb_keymap, XKB_MOD_NAME_SHIFT);
|
|
|
|
|
seat.kbd.mod_alt = xkb_keymap_mod_get_index(seat.kbd.xkb_keymap, XKB_MOD_NAME_ALT) ;
|
|
|
|
|
seat.kbd.mod_ctrl = xkb_keymap_mod_get_index(seat.kbd.xkb_keymap, XKB_MOD_NAME_CTRL);
|
|
|
|
|
seat.kbd.mod_super = xkb_keymap_mod_get_index(seat.kbd.xkb_keymap, XKB_MOD_NAME_LOGO);
|
|
|
|
|
seat.kbd.mod_caps = xkb_keymap_mod_get_index(seat.kbd.xkb_keymap, XKB_MOD_NAME_CAPS);
|
|
|
|
|
seat.kbd.mod_num = xkb_keymap_mod_get_index(seat.kbd.xkb_keymap, XKB_MOD_NAME_NUM);
|
|
|
|
|
|
|
|
|
|
/* Significant modifiers in the legacy keyboard protocol */
|
|
|
|
|
seat.kbd.legacy_significant = 0;
|
|
|
|
|
if (seat.kbd.mod_shift != XKB_MOD_INVALID)
|
|
|
|
|
seat.kbd.legacy_significant |= 1 << seat.kbd.mod_shift;
|
|
|
|
|
if (seat.kbd.mod_alt != XKB_MOD_INVALID)
|
|
|
|
|
seat.kbd.legacy_significant |= 1 << seat.kbd.mod_alt;
|
|
|
|
|
if (seat.kbd.mod_ctrl != XKB_MOD_INVALID)
|
|
|
|
|
seat.kbd.legacy_significant |= 1 << seat.kbd.mod_ctrl;
|
|
|
|
|
if (seat.kbd.mod_super != XKB_MOD_INVALID)
|
|
|
|
|
seat.kbd.legacy_significant |= 1 << seat.kbd.mod_super;
|
|
|
|
|
|
|
|
|
|
/* Significant modifiers in the kitty keyboard protocol */
|
|
|
|
|
seat.kbd.kitty_significant = seat.kbd.legacy_significant;
|
|
|
|
|
if (seat.kbd.mod_caps != XKB_MOD_INVALID)
|
|
|
|
|
seat.kbd.kitty_significant |= 1 << seat.kbd.mod_caps;
|
|
|
|
|
if (seat.kbd.mod_num != XKB_MOD_INVALID)
|
|
|
|
|
seat.kbd.kitty_significant |= 1 << seat.kbd.mod_num;
|
|
|
|
|
|
|
|
|
|
key_binding_new_for_seat(key_binding_manager, &seat);
|
|
|
|
|
key_binding_load_keymap(key_binding_manager, &seat);
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
xkb_mod_mask_t mods = 1u << seat.kbd.mod_shift | 1u << seat.kbd.mod_ctrl;
|
|
|
|
|
keyboard_modifiers(&seat, NULL, 1337, mods, 0, 0, 0);
|
|
|
|
|
key_press_release(&seat, &term, 1337, KEY_A + 8, WL_KEYBOARD_KEY_STATE_PRESSED);
|
|
|
|
|
|
|
|
|
|
char escape[64] = {0};
|
|
|
|
|
ssize_t count = read(chan[0], escape, sizeof(escape));
|
|
|
|
|
|
|
|
|
|
/* key: 97 = 'a', alternate: 65 = 'A', base: N/A, mods: 6 = ctrl+shift */
|
|
|
|
|
const char expected_ctrl_shift_a[] = "\033[97:65;6u";
|
|
|
|
|
xassert(count == strlen(expected_ctrl_shift_a));
|
|
|
|
|
xassert(streq(escape, expected_ctrl_shift_a));
|
|
|
|
|
|
|
|
|
|
key_press_release(&seat, &term, 1337, KEY_A + 8, WL_KEYBOARD_KEY_STATE_RELEASED);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
xkb_mod_mask_t mods = 1u << seat.kbd.mod_shift | 1u << seat.kbd.mod_alt;
|
|
|
|
|
keyboard_modifiers(&seat, NULL, 1337, mods, 0, 0, 0);
|
|
|
|
|
key_press_release(&seat, &term, 1337, KEY_2 + 8, WL_KEYBOARD_KEY_STATE_PRESSED);
|
|
|
|
|
|
|
|
|
|
char escape[64] = {0};
|
|
|
|
|
ssize_t count = read(chan[0], escape, sizeof(escape));
|
|
|
|
|
|
|
|
|
|
/* key;. 50 = '2', alternate: 34 = '"', base: N/A, 4 = alt+shift */
|
|
|
|
|
const char expected_alt_shift_2[] = "\033[50:34;4u";
|
|
|
|
|
xassert(count == strlen(expected_alt_shift_2));
|
|
|
|
|
xassert(streq(escape, expected_alt_shift_2));
|
|
|
|
|
|
|
|
|
|
key_press_release(&seat, &term, 1337, KEY_2 + 8, WL_KEYBOARD_KEY_STATE_RELEASED);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
xkb_mod_index_t alt_gr = xkb_keymap_mod_get_index(seat.kbd.xkb_keymap, "Mod5");
|
|
|
|
|
xassert(alt_gr != XKB_MOD_INVALID);
|
|
|
|
|
|
|
|
|
|
xkb_mod_mask_t mods = 1u << seat.kbd.mod_shift | 1u << seat.kbd.mod_alt | 1u << alt_gr;
|
|
|
|
|
keyboard_modifiers(&seat, NULL, 1337, mods, 0, 0, 0);
|
|
|
|
|
key_press_release(&seat, &term, 1337, KEY_2 + 8, WL_KEYBOARD_KEY_STATE_PRESSED);
|
|
|
|
|
|
|
|
|
|
char escape[64] = {0};
|
|
|
|
|
ssize_t count = read(chan[0], escape, sizeof(escape));
|
|
|
|
|
|
|
|
|
|
/* key; 178 = '²', alternate: N/A, base: 50 = '2', 4 = alt+shift (AltGr not part of the protocol) */
|
|
|
|
|
const char expected_altgr_alt_shift_2[] = "\033[178::50;4u";
|
|
|
|
|
xassert(count == strlen(expected_altgr_alt_shift_2));
|
|
|
|
|
xassert(streq(escape, expected_altgr_alt_shift_2));
|
|
|
|
|
|
|
|
|
|
key_press_release(&seat, &term, 1337, KEY_2 + 8, WL_KEYBOARD_KEY_STATE_RELEASED);
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-22 10:26:44 +01:00
|
|
|
{
|
|
|
|
|
xkb_mod_mask_t mods = 1u << seat.kbd.mod_alt;
|
|
|
|
|
keyboard_modifiers(&seat, NULL, 1337, mods, 0, 0, 0);
|
|
|
|
|
key_press_release(&seat, &term, 1337, KEY_BACKSPACE + 8, WL_KEYBOARD_KEY_STATE_PRESSED);
|
|
|
|
|
|
|
|
|
|
char escape[64] = {0};
|
|
|
|
|
ssize_t count = read(chan[0], escape, sizeof(escape));
|
|
|
|
|
|
|
|
|
|
/* key; 127 = <backspace>, alternate: N/A, base: N/A, 3 = alt */
|
|
|
|
|
const char expected_alt_backspace[] = "\033[127;3u";
|
|
|
|
|
xassert(count == strlen(expected_alt_backspace));
|
|
|
|
|
xassert(streq(escape, expected_alt_backspace));
|
|
|
|
|
|
|
|
|
|
key_press_release(&seat, &term, 1337, KEY_BACKSPACE + 8, WL_KEYBOARD_KEY_STATE_RELEASED);
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-22 12:24:06 +01:00
|
|
|
{
|
|
|
|
|
xkb_mod_mask_t mods = 1u << seat.kbd.mod_ctrl;
|
|
|
|
|
keyboard_modifiers(&seat, NULL, 1337, mods, 0, 0, 0);
|
|
|
|
|
key_press_release(&seat, &term, 1337, KEY_ENTER + 8, WL_KEYBOARD_KEY_STATE_PRESSED);
|
|
|
|
|
|
|
|
|
|
char escape[64] = {0};
|
|
|
|
|
ssize_t count = read(chan[0], escape, sizeof(escape));
|
|
|
|
|
|
|
|
|
|
/* key; 13 = <enter>, alternate: N/A, base: N/A, 5 = ctrl */
|
|
|
|
|
const char expected_ctrl_enter[] = "\033[13;5u";
|
|
|
|
|
xassert(count == strlen(expected_ctrl_enter));
|
|
|
|
|
xassert(streq(escape, expected_ctrl_enter));
|
|
|
|
|
|
|
|
|
|
key_press_release(&seat, &term, 1337, KEY_ENTER + 8, WL_KEYBOARD_KEY_STATE_RELEASED);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
xkb_mod_mask_t mods = 1u << seat.kbd.mod_ctrl;
|
|
|
|
|
keyboard_modifiers(&seat, NULL, 1337, mods, 0, 0, 0);
|
|
|
|
|
key_press_release(&seat, &term, 1337, KEY_TAB + 8, WL_KEYBOARD_KEY_STATE_PRESSED);
|
|
|
|
|
|
|
|
|
|
char escape[64] = {0};
|
|
|
|
|
ssize_t count = read(chan[0], escape, sizeof(escape));
|
|
|
|
|
|
|
|
|
|
/* key; 9 = <tab>, alternate: N/A, base: N/A, 5 = ctrl */
|
|
|
|
|
const char expected_ctrl_tab[] = "\033[9;5u";
|
|
|
|
|
xassert(count == strlen(expected_ctrl_tab));
|
|
|
|
|
xassert(streq(escape, expected_ctrl_tab));
|
|
|
|
|
|
|
|
|
|
key_press_release(&seat, &term, 1337, KEY_TAB + 8, WL_KEYBOARD_KEY_STATE_RELEASED);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
xkb_mod_mask_t mods = 1u << seat.kbd.mod_ctrl | 1u << seat.kbd.mod_shift;
|
|
|
|
|
keyboard_modifiers(&seat, NULL, 1337, mods, 0, 0, 0);
|
|
|
|
|
key_press_release(&seat, &term, 1337, KEY_LEFT + 8, WL_KEYBOARD_KEY_STATE_PRESSED);
|
|
|
|
|
|
|
|
|
|
char escape[64] = {0};
|
|
|
|
|
ssize_t count = read(chan[0], escape, sizeof(escape));
|
|
|
|
|
|
|
|
|
|
const char expected_ctrl_shift_left[] = "\033[1;6D";
|
|
|
|
|
xassert(count == strlen(expected_ctrl_shift_left));
|
|
|
|
|
xassert(streq(escape, expected_ctrl_shift_left));
|
|
|
|
|
|
|
|
|
|
key_press_release(&seat, &term, 1337, KEY_LEFT + 8, WL_KEYBOARD_KEY_STATE_RELEASED);
|
|
|
|
|
}
|
2025-01-21 08:37:30 +01:00
|
|
|
key_binding_unload_keymap(key_binding_manager, &seat);
|
|
|
|
|
key_binding_remove_seat(key_binding_manager, &seat);
|
|
|
|
|
|
|
|
|
|
xkb_state_unref(seat.kbd.xkb_state);
|
|
|
|
|
xkb_keymap_unref(seat.kbd.xkb_keymap);
|
|
|
|
|
|
|
|
|
|
seat.kbd.xkb_state = NULL;
|
|
|
|
|
seat.kbd.xkb_keymap = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* de(neo) keymap */
|
|
|
|
|
{
|
|
|
|
|
seat.kbd.xkb_keymap = xkb_keymap_new_from_names(
|
|
|
|
|
seat.kbd.xkb, &(struct xkb_rule_names){.layout = "us,de(neo)"},
|
|
|
|
|
XKB_KEYMAP_COMPILE_NO_FLAGS);
|
|
|
|
|
|
|
|
|
|
if (seat.kbd.xkb_keymap == NULL) {
|
|
|
|
|
/* Skip test */
|
|
|
|
|
goto no_keymap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
seat.kbd.xkb_state = xkb_state_new(seat.kbd.xkb_keymap);
|
|
|
|
|
xassert(seat.kbd.xkb_state != NULL);
|
|
|
|
|
|
|
|
|
|
seat.kbd.mod_shift = xkb_keymap_mod_get_index(seat.kbd.xkb_keymap, XKB_MOD_NAME_SHIFT);
|
|
|
|
|
seat.kbd.mod_alt = xkb_keymap_mod_get_index(seat.kbd.xkb_keymap, XKB_MOD_NAME_ALT) ;
|
|
|
|
|
seat.kbd.mod_ctrl = xkb_keymap_mod_get_index(seat.kbd.xkb_keymap, XKB_MOD_NAME_CTRL);
|
|
|
|
|
seat.kbd.mod_super = xkb_keymap_mod_get_index(seat.kbd.xkb_keymap, XKB_MOD_NAME_LOGO);
|
|
|
|
|
seat.kbd.mod_caps = xkb_keymap_mod_get_index(seat.kbd.xkb_keymap, XKB_MOD_NAME_CAPS);
|
|
|
|
|
seat.kbd.mod_num = xkb_keymap_mod_get_index(seat.kbd.xkb_keymap, XKB_MOD_NAME_NUM);
|
|
|
|
|
|
|
|
|
|
/* Significant modifiers in the legacy keyboard protocol */
|
|
|
|
|
seat.kbd.legacy_significant = 0;
|
|
|
|
|
if (seat.kbd.mod_shift != XKB_MOD_INVALID)
|
|
|
|
|
seat.kbd.legacy_significant |= 1 << seat.kbd.mod_shift;
|
|
|
|
|
if (seat.kbd.mod_alt != XKB_MOD_INVALID)
|
|
|
|
|
seat.kbd.legacy_significant |= 1 << seat.kbd.mod_alt;
|
|
|
|
|
if (seat.kbd.mod_ctrl != XKB_MOD_INVALID)
|
|
|
|
|
seat.kbd.legacy_significant |= 1 << seat.kbd.mod_ctrl;
|
|
|
|
|
if (seat.kbd.mod_super != XKB_MOD_INVALID)
|
|
|
|
|
seat.kbd.legacy_significant |= 1 << seat.kbd.mod_super;
|
|
|
|
|
|
|
|
|
|
/* Significant modifiers in the kitty keyboard protocol */
|
|
|
|
|
seat.kbd.kitty_significant = seat.kbd.legacy_significant;
|
|
|
|
|
if (seat.kbd.mod_caps != XKB_MOD_INVALID)
|
|
|
|
|
seat.kbd.kitty_significant |= 1 << seat.kbd.mod_caps;
|
|
|
|
|
if (seat.kbd.mod_num != XKB_MOD_INVALID)
|
|
|
|
|
seat.kbd.kitty_significant |= 1 << seat.kbd.mod_num;
|
|
|
|
|
|
|
|
|
|
key_binding_new_for_seat(key_binding_manager, &seat);
|
|
|
|
|
key_binding_load_keymap(key_binding_manager, &seat);
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* In the de(neo) layout, the Y key generates 'k'. This
|
2025-01-22 07:51:00 +01:00
|
|
|
* means we should get a key+alternate that indicates 'k',
|
|
|
|
|
* but a base key that is 'y'.
|
2025-01-21 08:37:30 +01:00
|
|
|
*/
|
|
|
|
|
xkb_mod_mask_t mods = 1u << seat.kbd.mod_shift | 1u << seat.kbd.mod_alt;
|
|
|
|
|
keyboard_modifiers(&seat, NULL, 1337, mods, 0, 0, 1);
|
|
|
|
|
key_press_release(&seat, &term, 1337, KEY_Y + 8, WL_KEYBOARD_KEY_STATE_PRESSED);
|
|
|
|
|
|
|
|
|
|
char escape[64] = {0};
|
|
|
|
|
ssize_t count = read(chan[0], escape, sizeof(escape));
|
|
|
|
|
|
|
|
|
|
/* key: 107 = 'k', alternate: 75 = 'K', base: 121 = 'y', mods: 4 = alt+shift */
|
|
|
|
|
const char expected_alt_shift_y[] = "\033[107:75:121;4u";
|
|
|
|
|
xassert(count == strlen(expected_alt_shift_y));
|
|
|
|
|
xassert(streq(escape, expected_alt_shift_y));
|
|
|
|
|
|
|
|
|
|
key_press_release(&seat, &term, 1337, KEY_Y + 8, WL_KEYBOARD_KEY_STATE_RELEASED);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
key_binding_unload_keymap(key_binding_manager, &seat);
|
|
|
|
|
key_binding_remove_seat(key_binding_manager, &seat);
|
|
|
|
|
|
|
|
|
|
xkb_state_unref(seat.kbd.xkb_state);
|
|
|
|
|
xkb_keymap_unref(seat.kbd.xkb_keymap);
|
|
|
|
|
|
|
|
|
|
seat.kbd.xkb_state = NULL;
|
|
|
|
|
seat.kbd.xkb_keymap = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-11 08:23:23 +01:00
|
|
|
/* us(intl) keymap */
|
|
|
|
|
{
|
|
|
|
|
seat.kbd.xkb_keymap = xkb_keymap_new_from_names(
|
|
|
|
|
seat.kbd.xkb, &(struct xkb_rule_names){.layout = "us", .variant = "intl"},
|
|
|
|
|
XKB_KEYMAP_COMPILE_NO_FLAGS);
|
|
|
|
|
|
|
|
|
|
if (seat.kbd.xkb_keymap == NULL) {
|
|
|
|
|
/* Skip test */
|
|
|
|
|
goto no_keymap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
seat.kbd.xkb_state = xkb_state_new(seat.kbd.xkb_keymap);
|
|
|
|
|
xassert(seat.kbd.xkb_state != NULL);
|
|
|
|
|
|
|
|
|
|
seat.kbd.xkb_compose_table = xkb_compose_table_new_from_locale(
|
|
|
|
|
seat.kbd.xkb, setlocale(LC_CTYPE, NULL), XKB_COMPOSE_COMPILE_NO_FLAGS);
|
2025-03-11 08:42:03 +01:00
|
|
|
if (seat.kbd.xkb_compose_table == NULL)
|
|
|
|
|
goto no_keymap;
|
2025-03-11 08:23:23 +01:00
|
|
|
|
|
|
|
|
seat.kbd.xkb_compose_state = xkb_compose_state_new(
|
|
|
|
|
seat.kbd.xkb_compose_table, XKB_COMPOSE_STATE_NO_FLAGS);
|
2025-03-11 08:42:03 +01:00
|
|
|
if (seat.kbd.xkb_compose_state == NULL) {
|
|
|
|
|
xkb_compose_table_unref(seat.kbd.xkb_compose_table);
|
|
|
|
|
goto no_keymap;
|
|
|
|
|
}
|
2025-03-11 08:23:23 +01:00
|
|
|
|
|
|
|
|
seat.kbd.mod_shift = xkb_keymap_mod_get_index(seat.kbd.xkb_keymap, XKB_MOD_NAME_SHIFT);
|
|
|
|
|
seat.kbd.mod_alt = xkb_keymap_mod_get_index(seat.kbd.xkb_keymap, XKB_MOD_NAME_ALT) ;
|
|
|
|
|
seat.kbd.mod_ctrl = xkb_keymap_mod_get_index(seat.kbd.xkb_keymap, XKB_MOD_NAME_CTRL);
|
|
|
|
|
seat.kbd.mod_super = xkb_keymap_mod_get_index(seat.kbd.xkb_keymap, XKB_MOD_NAME_LOGO);
|
|
|
|
|
seat.kbd.mod_caps = xkb_keymap_mod_get_index(seat.kbd.xkb_keymap, XKB_MOD_NAME_CAPS);
|
|
|
|
|
seat.kbd.mod_num = xkb_keymap_mod_get_index(seat.kbd.xkb_keymap, XKB_MOD_NAME_NUM);
|
|
|
|
|
|
|
|
|
|
/* Significant modifiers in the legacy keyboard protocol */
|
|
|
|
|
seat.kbd.legacy_significant = 0;
|
|
|
|
|
if (seat.kbd.mod_shift != XKB_MOD_INVALID)
|
|
|
|
|
seat.kbd.legacy_significant |= 1 << seat.kbd.mod_shift;
|
|
|
|
|
if (seat.kbd.mod_alt != XKB_MOD_INVALID)
|
|
|
|
|
seat.kbd.legacy_significant |= 1 << seat.kbd.mod_alt;
|
|
|
|
|
if (seat.kbd.mod_ctrl != XKB_MOD_INVALID)
|
|
|
|
|
seat.kbd.legacy_significant |= 1 << seat.kbd.mod_ctrl;
|
|
|
|
|
if (seat.kbd.mod_super != XKB_MOD_INVALID)
|
|
|
|
|
seat.kbd.legacy_significant |= 1 << seat.kbd.mod_super;
|
|
|
|
|
|
|
|
|
|
/* Significant modifiers in the kitty keyboard protocol */
|
|
|
|
|
seat.kbd.kitty_significant = seat.kbd.legacy_significant;
|
|
|
|
|
if (seat.kbd.mod_caps != XKB_MOD_INVALID)
|
|
|
|
|
seat.kbd.kitty_significant |= 1 << seat.kbd.mod_caps;
|
|
|
|
|
if (seat.kbd.mod_num != XKB_MOD_INVALID)
|
|
|
|
|
seat.kbd.kitty_significant |= 1 << seat.kbd.mod_num;
|
|
|
|
|
|
|
|
|
|
key_binding_new_for_seat(key_binding_manager, &seat);
|
|
|
|
|
key_binding_load_keymap(key_binding_manager, &seat);
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* Test the compose sequence "shift+', shift+space"
|
|
|
|
|
*
|
|
|
|
|
* Should result in a double quote, but a regression
|
|
|
|
|
* caused it to instead emit a space. See #1987
|
|
|
|
|
*
|
|
|
|
|
* Note: "shift+', space" also results in a double quote,
|
|
|
|
|
* but never regressed to a space.
|
|
|
|
|
*/
|
|
|
|
|
grid.kitty_kbd.flags[0] = KITTY_KBD_DISAMBIGUATE;
|
|
|
|
|
xkb_compose_state_reset(seat.kbd.xkb_compose_state);
|
|
|
|
|
|
|
|
|
|
xkb_mod_mask_t mods = 1u << seat.kbd.mod_shift;
|
|
|
|
|
keyboard_modifiers(&seat, NULL, 1337, mods, 0, 0, 1);
|
|
|
|
|
|
|
|
|
|
key_press_release(&seat, &term, 1337, KEY_APOSTROPHE + 8, WL_KEYBOARD_KEY_STATE_PRESSED);
|
|
|
|
|
key_press_release(&seat, &term, 1337, KEY_APOSTROPHE + 8, WL_KEYBOARD_KEY_STATE_RELEASED);
|
|
|
|
|
|
|
|
|
|
key_press_release(&seat, &term, 1337, KEY_SPACE + 8, WL_KEYBOARD_KEY_STATE_PRESSED);
|
|
|
|
|
|
|
|
|
|
char escape[64] = {0};
|
|
|
|
|
ssize_t count = read(chan[0], escape, sizeof(escape));
|
|
|
|
|
|
|
|
|
|
/* key: 34 = '"', alternate: N/A, base: N/A, mods: 2 = shift */
|
|
|
|
|
const char expected_shift_apostrophe[] = "\033[34;2u";
|
|
|
|
|
xassert(count == strlen(expected_shift_apostrophe));
|
|
|
|
|
xassert(streq(escape, expected_shift_apostrophe));
|
|
|
|
|
|
|
|
|
|
key_press_release(&seat, &term, 1337, KEY_SPACE + 8, WL_KEYBOARD_KEY_STATE_RELEASED);
|
|
|
|
|
|
|
|
|
|
grid.kitty_kbd.flags[0] = KITTY_KBD_DISAMBIGUATE | KITTY_KBD_REPORT_ALTERNATE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
key_binding_unload_keymap(key_binding_manager, &seat);
|
|
|
|
|
key_binding_remove_seat(key_binding_manager, &seat);
|
|
|
|
|
|
|
|
|
|
xkb_compose_state_unref(seat.kbd.xkb_compose_state);
|
|
|
|
|
xkb_compose_table_unref(seat.kbd.xkb_compose_table);
|
|
|
|
|
|
|
|
|
|
xkb_state_unref(seat.kbd.xkb_state);
|
|
|
|
|
xkb_keymap_unref(seat.kbd.xkb_keymap);
|
|
|
|
|
|
|
|
|
|
seat.kbd.xkb_state = NULL;
|
|
|
|
|
seat.kbd.xkb_keymap = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-21 08:37:30 +01:00
|
|
|
no_keymap:
|
|
|
|
|
xkb_context_unref(seat.kbd.xkb);
|
|
|
|
|
key_binding_manager_destroy(key_binding_manager);
|
|
|
|
|
|
|
|
|
|
tll_free(wayl.terms);
|
|
|
|
|
close(chan[0]);
|
|
|
|
|
close(chan[1]);
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-19 10:04:47 +02:00
|
|
|
static void
|
|
|
|
|
keyboard_repeat_info(void *data, struct wl_keyboard *wl_keyboard,
|
|
|
|
|
int32_t rate, int32_t delay)
|
|
|
|
|
{
|
2020-07-08 16:45:26 +02:00
|
|
|
struct seat *seat = data;
|
2019-06-19 10:04:47 +02:00
|
|
|
LOG_DBG("keyboard repeat: rate=%d, delay=%d", rate, delay);
|
2020-07-08 16:45:26 +02:00
|
|
|
seat->kbd.repeat.rate = rate;
|
|
|
|
|
seat->kbd.repeat.delay = delay;
|
2019-06-19 10:04:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const struct wl_keyboard_listener keyboard_listener = {
|
|
|
|
|
.keymap = &keyboard_keymap,
|
|
|
|
|
.enter = &keyboard_enter,
|
|
|
|
|
.leave = &keyboard_leave,
|
|
|
|
|
.key = &keyboard_key,
|
|
|
|
|
.modifiers = &keyboard_modifiers,
|
|
|
|
|
.repeat_info = &keyboard_repeat_info,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
void
|
2020-07-08 16:45:26 +02:00
|
|
|
input_repeat(struct seat *seat, uint32_t key)
|
2019-06-19 10:04:47 +02:00
|
|
|
{
|
2020-11-03 19:44:51 +01:00
|
|
|
/* Should be cleared as soon as we loose focus */
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(seat->kbd_focus != NULL);
|
2020-11-03 19:44:51 +01:00
|
|
|
struct terminal *term = seat->kbd_focus;
|
|
|
|
|
|
|
|
|
|
key_press_release(seat, term, seat->kbd.serial, key, XKB_KEY_DOWN);
|
2019-06-19 10:04:47 +02:00
|
|
|
}
|
2019-07-05 10:44:57 +02:00
|
|
|
|
2020-02-29 09:32:22 +01:00
|
|
|
static bool
|
|
|
|
|
is_top_left(const struct terminal *term, int x, int y)
|
|
|
|
|
{
|
2020-03-02 18:42:49 +01:00
|
|
|
int csd_border_size = term->conf->csd.border_width;
|
2020-02-29 09:32:22 +01:00
|
|
|
return (
|
wayland+input: add support for toplevel edge constraints
Edge constraints are new (not yet available in a wayland-protocols
release) toplevel states, acting as a complement to the existing tiled
states.
Tiled tells us we shouldn't draw shadows etc *outside our window
geometry*.
Constrained tells us the window cannot be resized in the constrained
direction.
This patch does a couple of things:
* Recognize the new states when debug logging
* Change is_top_left() etc to look at the new constrained state
instead of the tiled state. These functions are used when both
choosing cursor shape, and when determining if/how to resize a
window on a CSD edge click-and-drag.
* Update cursor shape selection to use the default (left_ptr) shape
when on a constrained edge (or corner).
* Update CSD resize triggering, to not trigger a resize when attempted
on a constrained edge (or corner).
See
https://gitlab.freedesktop.org/wayland/wayland-protocols/-/commit/86750c99ed062c306e837f11bb9492df572ad677:
An edge constraint is an complementery state to the tiled state,
meaning that it's not only tiled, but constrained in a way that it
can't resize in that direction.
This typically means that the constrained edge is tiled against a
monitor edge. An example configuration is two windows tiled next
to each other on a single monitor. Together they cover the whole
work area.
The left window would have the following tiled and edge constraint
state:
[ tiled_top, tiled_right, tiled_bottom, tiled_left,
constrained_top, constrained_bottom, constrained_left ]
while the right window would have the following:
[ tiled_top, tiled_right, tiled_bottom, tiled_left,
constrained_top, constrained_bottom, constrained_right ]
This aims to replace and deprecate the
`gtk_surface1.configure_edges` event and the
`gtk_surface1.edge_constraint` enum.
2025-04-07 13:32:30 +02:00
|
|
|
(!term->window->is_constrained_top && !term->window->is_constrained_left) &&
|
2020-10-20 21:00:19 +02:00
|
|
|
((term->active_surface == TERM_SURF_BORDER_LEFT && y < 10 * term->scale) ||
|
|
|
|
|
(term->active_surface == TERM_SURF_BORDER_TOP && x < (10 + csd_border_size) * term->scale)));
|
2020-02-29 09:32:22 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
is_top_right(const struct terminal *term, int x, int y)
|
|
|
|
|
{
|
2020-03-02 18:42:49 +01:00
|
|
|
int csd_border_size = term->conf->csd.border_width;
|
2020-02-29 09:32:22 +01:00
|
|
|
return (
|
wayland+input: add support for toplevel edge constraints
Edge constraints are new (not yet available in a wayland-protocols
release) toplevel states, acting as a complement to the existing tiled
states.
Tiled tells us we shouldn't draw shadows etc *outside our window
geometry*.
Constrained tells us the window cannot be resized in the constrained
direction.
This patch does a couple of things:
* Recognize the new states when debug logging
* Change is_top_left() etc to look at the new constrained state
instead of the tiled state. These functions are used when both
choosing cursor shape, and when determining if/how to resize a
window on a CSD edge click-and-drag.
* Update cursor shape selection to use the default (left_ptr) shape
when on a constrained edge (or corner).
* Update CSD resize triggering, to not trigger a resize when attempted
on a constrained edge (or corner).
See
https://gitlab.freedesktop.org/wayland/wayland-protocols/-/commit/86750c99ed062c306e837f11bb9492df572ad677:
An edge constraint is an complementery state to the tiled state,
meaning that it's not only tiled, but constrained in a way that it
can't resize in that direction.
This typically means that the constrained edge is tiled against a
monitor edge. An example configuration is two windows tiled next
to each other on a single monitor. Together they cover the whole
work area.
The left window would have the following tiled and edge constraint
state:
[ tiled_top, tiled_right, tiled_bottom, tiled_left,
constrained_top, constrained_bottom, constrained_left ]
while the right window would have the following:
[ tiled_top, tiled_right, tiled_bottom, tiled_left,
constrained_top, constrained_bottom, constrained_right ]
This aims to replace and deprecate the
`gtk_surface1.configure_edges` event and the
`gtk_surface1.edge_constraint` enum.
2025-04-07 13:32:30 +02:00
|
|
|
(!term->window->is_constrained_top && !term->window->is_constrained_right) &&
|
2020-10-20 21:00:19 +02:00
|
|
|
((term->active_surface == TERM_SURF_BORDER_RIGHT && y < 10 * term->scale) ||
|
|
|
|
|
(term->active_surface == TERM_SURF_BORDER_TOP && x > term->width + 1 * csd_border_size * term->scale - 10 * term->scale)));
|
2020-02-29 09:32:22 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
is_bottom_left(const struct terminal *term, int x, int y)
|
|
|
|
|
{
|
2020-03-02 18:42:49 +01:00
|
|
|
int csd_title_size = term->conf->csd.title_height;
|
|
|
|
|
int csd_border_size = term->conf->csd.border_width;
|
2020-02-29 09:32:22 +01:00
|
|
|
return (
|
wayland+input: add support for toplevel edge constraints
Edge constraints are new (not yet available in a wayland-protocols
release) toplevel states, acting as a complement to the existing tiled
states.
Tiled tells us we shouldn't draw shadows etc *outside our window
geometry*.
Constrained tells us the window cannot be resized in the constrained
direction.
This patch does a couple of things:
* Recognize the new states when debug logging
* Change is_top_left() etc to look at the new constrained state
instead of the tiled state. These functions are used when both
choosing cursor shape, and when determining if/how to resize a
window on a CSD edge click-and-drag.
* Update cursor shape selection to use the default (left_ptr) shape
when on a constrained edge (or corner).
* Update CSD resize triggering, to not trigger a resize when attempted
on a constrained edge (or corner).
See
https://gitlab.freedesktop.org/wayland/wayland-protocols/-/commit/86750c99ed062c306e837f11bb9492df572ad677:
An edge constraint is an complementery state to the tiled state,
meaning that it's not only tiled, but constrained in a way that it
can't resize in that direction.
This typically means that the constrained edge is tiled against a
monitor edge. An example configuration is two windows tiled next
to each other on a single monitor. Together they cover the whole
work area.
The left window would have the following tiled and edge constraint
state:
[ tiled_top, tiled_right, tiled_bottom, tiled_left,
constrained_top, constrained_bottom, constrained_left ]
while the right window would have the following:
[ tiled_top, tiled_right, tiled_bottom, tiled_left,
constrained_top, constrained_bottom, constrained_right ]
This aims to replace and deprecate the
`gtk_surface1.configure_edges` event and the
`gtk_surface1.edge_constraint` enum.
2025-04-07 13:32:30 +02:00
|
|
|
(!term->window->is_constrained_bottom && !term->window->is_constrained_left) &&
|
2020-10-20 21:00:19 +02:00
|
|
|
((term->active_surface == TERM_SURF_BORDER_LEFT && y > csd_title_size * term->scale + term->height) ||
|
|
|
|
|
(term->active_surface == TERM_SURF_BORDER_BOTTOM && x < (10 + csd_border_size) * term->scale)));
|
2020-02-29 09:32:22 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
is_bottom_right(const struct terminal *term, int x, int y)
|
|
|
|
|
{
|
2020-03-02 18:42:49 +01:00
|
|
|
int csd_title_size = term->conf->csd.title_height;
|
|
|
|
|
int csd_border_size = term->conf->csd.border_width;
|
2020-02-29 09:32:22 +01:00
|
|
|
return (
|
wayland+input: add support for toplevel edge constraints
Edge constraints are new (not yet available in a wayland-protocols
release) toplevel states, acting as a complement to the existing tiled
states.
Tiled tells us we shouldn't draw shadows etc *outside our window
geometry*.
Constrained tells us the window cannot be resized in the constrained
direction.
This patch does a couple of things:
* Recognize the new states when debug logging
* Change is_top_left() etc to look at the new constrained state
instead of the tiled state. These functions are used when both
choosing cursor shape, and when determining if/how to resize a
window on a CSD edge click-and-drag.
* Update cursor shape selection to use the default (left_ptr) shape
when on a constrained edge (or corner).
* Update CSD resize triggering, to not trigger a resize when attempted
on a constrained edge (or corner).
See
https://gitlab.freedesktop.org/wayland/wayland-protocols/-/commit/86750c99ed062c306e837f11bb9492df572ad677:
An edge constraint is an complementery state to the tiled state,
meaning that it's not only tiled, but constrained in a way that it
can't resize in that direction.
This typically means that the constrained edge is tiled against a
monitor edge. An example configuration is two windows tiled next
to each other on a single monitor. Together they cover the whole
work area.
The left window would have the following tiled and edge constraint
state:
[ tiled_top, tiled_right, tiled_bottom, tiled_left,
constrained_top, constrained_bottom, constrained_left ]
while the right window would have the following:
[ tiled_top, tiled_right, tiled_bottom, tiled_left,
constrained_top, constrained_bottom, constrained_right ]
This aims to replace and deprecate the
`gtk_surface1.configure_edges` event and the
`gtk_surface1.edge_constraint` enum.
2025-04-07 13:32:30 +02:00
|
|
|
(!term->window->is_constrained_bottom && !term->window->is_constrained_right) &&
|
2020-10-20 21:00:19 +02:00
|
|
|
((term->active_surface == TERM_SURF_BORDER_RIGHT && y > csd_title_size * term->scale + term->height) ||
|
|
|
|
|
(term->active_surface == TERM_SURF_BORDER_BOTTOM && x > term->width + 1 * csd_border_size * term->scale - 10 * term->scale)));
|
2020-02-29 09:32:22 +01:00
|
|
|
}
|
|
|
|
|
|
2023-06-27 16:57:33 +02:00
|
|
|
enum cursor_shape
|
2020-02-29 09:32:22 +01:00
|
|
|
xcursor_for_csd_border(struct terminal *term, int x, int y)
|
|
|
|
|
{
|
2023-06-27 16:57:33 +02:00
|
|
|
if (is_top_left(term, x, y)) return CURSOR_SHAPE_TOP_LEFT_CORNER;
|
|
|
|
|
else if (is_top_right(term, x, y)) return CURSOR_SHAPE_TOP_RIGHT_CORNER;
|
|
|
|
|
else if (is_bottom_left(term, x, y)) return CURSOR_SHAPE_BOTTOM_LEFT_CORNER;
|
|
|
|
|
else if (is_bottom_right(term, x, y)) return CURSOR_SHAPE_BOTTOM_RIGHT_CORNER;
|
wayland+input: add support for toplevel edge constraints
Edge constraints are new (not yet available in a wayland-protocols
release) toplevel states, acting as a complement to the existing tiled
states.
Tiled tells us we shouldn't draw shadows etc *outside our window
geometry*.
Constrained tells us the window cannot be resized in the constrained
direction.
This patch does a couple of things:
* Recognize the new states when debug logging
* Change is_top_left() etc to look at the new constrained state
instead of the tiled state. These functions are used when both
choosing cursor shape, and when determining if/how to resize a
window on a CSD edge click-and-drag.
* Update cursor shape selection to use the default (left_ptr) shape
when on a constrained edge (or corner).
* Update CSD resize triggering, to not trigger a resize when attempted
on a constrained edge (or corner).
See
https://gitlab.freedesktop.org/wayland/wayland-protocols/-/commit/86750c99ed062c306e837f11bb9492df572ad677:
An edge constraint is an complementery state to the tiled state,
meaning that it's not only tiled, but constrained in a way that it
can't resize in that direction.
This typically means that the constrained edge is tiled against a
monitor edge. An example configuration is two windows tiled next
to each other on a single monitor. Together they cover the whole
work area.
The left window would have the following tiled and edge constraint
state:
[ tiled_top, tiled_right, tiled_bottom, tiled_left,
constrained_top, constrained_bottom, constrained_left ]
while the right window would have the following:
[ tiled_top, tiled_right, tiled_bottom, tiled_left,
constrained_top, constrained_bottom, constrained_right ]
This aims to replace and deprecate the
`gtk_surface1.configure_edges` event and the
`gtk_surface1.edge_constraint` enum.
2025-04-07 13:32:30 +02:00
|
|
|
|
|
|
|
|
else if (term->active_surface == TERM_SURF_BORDER_LEFT)
|
|
|
|
|
return !term->window->is_constrained_left
|
|
|
|
|
? CURSOR_SHAPE_LEFT_SIDE : CURSOR_SHAPE_LEFT_PTR;
|
|
|
|
|
|
|
|
|
|
else if (term->active_surface == TERM_SURF_BORDER_RIGHT)
|
|
|
|
|
return !term->window->is_constrained_right
|
|
|
|
|
? CURSOR_SHAPE_RIGHT_SIDE : CURSOR_SHAPE_LEFT_PTR;
|
|
|
|
|
|
|
|
|
|
else if (term->active_surface == TERM_SURF_BORDER_TOP)
|
|
|
|
|
return !term->window->is_constrained_top
|
|
|
|
|
? CURSOR_SHAPE_TOP_SIDE : CURSOR_SHAPE_LEFT_PTR;
|
|
|
|
|
|
|
|
|
|
else if (term->active_surface == TERM_SURF_BORDER_BOTTOM)
|
|
|
|
|
return !term->window->is_constrained_bottom
|
|
|
|
|
? CURSOR_SHAPE_BOTTOM_SIDE : CURSOR_SHAPE_LEFT_PTR;
|
|
|
|
|
|
2020-02-29 09:32:22 +01:00
|
|
|
else {
|
2021-02-09 15:16:19 +00:00
|
|
|
BUG("Unreachable");
|
2023-06-27 16:57:33 +02:00
|
|
|
return CURSOR_SHAPE_NONE;
|
2020-02-29 09:32:22 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-05 00:19:21 +08:00
|
|
|
static void
|
|
|
|
|
mouse_button_state_reset(struct seat *seat)
|
|
|
|
|
{
|
|
|
|
|
tll_free(seat->mouse.buttons);
|
|
|
|
|
seat->mouse.count = 0;
|
|
|
|
|
seat->mouse.last_released_button = 0;
|
|
|
|
|
memset(&seat->mouse.last_time, 0, sizeof(seat->mouse.last_time));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
mouse_coord_pixel_to_cell(struct seat *seat, const struct terminal *term,
|
|
|
|
|
int x, int y)
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* Translate x,y pixel coordinate to a cell coordinate, or -1
|
|
|
|
|
* if the cursor is outside the grid. I.e. if it is inside the
|
|
|
|
|
* margins.
|
|
|
|
|
*/
|
input: allow mouse selections to start inside the margins
Before this, margins were special cased:
* The mouse cursor was always a pointer, and never an I-beam (thus
signaling selections cannot be made).
* The internal mouse coords where set to -1 when the cursor was inside
the margins, causing:
- text selections from being made
- mouse events being passed to mouse grabbing applications
In particular, even with a one-pixel margin, making selections was
unnecessarily hard in e.g. fullscreen mode, where you'd expect to be
able to throw the cursor into the corner of the screen and then start
a selection.
With this patch, the cursor is treated as if it was in the first/last
column/row, when inside the margin(s).
An unintended side-effect of this, initially, was that auto-scrolling
selections where way too easy to trigger, since part of its logic is
checking if the cursor is inside the margins.
That problem has been reduced by two things:
* auto-scrolling does not occur unless a selection has been
started. That is, just holding down the mouse in the margins and
moving up/down doesn't cause scrolling. You have to first select at
least one cell in the visible viewport.
* A selection isn't fully started (i.e. a cell is actually selected)
unless the cursor is inside the actual grid, and *not* in the
margins.
What does the last point mean? We now allow a selection to be
_started_ when clicking in the margin. What this means internally is
we set a start coordinate for a selection, but *not* and end
coordinate. At this point, we don't have an actual selection. Nothing
is selected, and no cells are highlighted, graphically.
This happens when we set an end coordinate. Without the last bullet
point, that would happen as soon as the cursor was _moved_, even if
still inside the margins. Now, we require the cursor to leave the
margins and touch an actual cell before we set an end coordinate.
Closes #1702
2024-07-18 08:08:44 +02:00
|
|
|
if (x < term->margins.left)
|
|
|
|
|
seat->mouse.col = 0;
|
|
|
|
|
else if (x >= term->width - term->margins.right)
|
|
|
|
|
seat->mouse.col = term->cols - 1;
|
2023-07-05 00:19:21 +08:00
|
|
|
else
|
|
|
|
|
seat->mouse.col = (x - term->margins.left) / term->cell_width;
|
|
|
|
|
|
input: allow mouse selections to start inside the margins
Before this, margins were special cased:
* The mouse cursor was always a pointer, and never an I-beam (thus
signaling selections cannot be made).
* The internal mouse coords where set to -1 when the cursor was inside
the margins, causing:
- text selections from being made
- mouse events being passed to mouse grabbing applications
In particular, even with a one-pixel margin, making selections was
unnecessarily hard in e.g. fullscreen mode, where you'd expect to be
able to throw the cursor into the corner of the screen and then start
a selection.
With this patch, the cursor is treated as if it was in the first/last
column/row, when inside the margin(s).
An unintended side-effect of this, initially, was that auto-scrolling
selections where way too easy to trigger, since part of its logic is
checking if the cursor is inside the margins.
That problem has been reduced by two things:
* auto-scrolling does not occur unless a selection has been
started. That is, just holding down the mouse in the margins and
moving up/down doesn't cause scrolling. You have to first select at
least one cell in the visible viewport.
* A selection isn't fully started (i.e. a cell is actually selected)
unless the cursor is inside the actual grid, and *not* in the
margins.
What does the last point mean? We now allow a selection to be
_started_ when clicking in the margin. What this means internally is
we set a start coordinate for a selection, but *not* and end
coordinate. At this point, we don't have an actual selection. Nothing
is selected, and no cells are highlighted, graphically.
This happens when we set an end coordinate. Without the last bullet
point, that would happen as soon as the cursor was _moved_, even if
still inside the margins. Now, we require the cursor to leave the
margins and touch an actual cell before we set an end coordinate.
Closes #1702
2024-07-18 08:08:44 +02:00
|
|
|
if (y < term->margins.top)
|
|
|
|
|
seat->mouse.row = 0;
|
|
|
|
|
else if (y >= term->height - term->margins.bottom)
|
|
|
|
|
seat->mouse.row = term->rows - 1;
|
2023-07-05 00:19:21 +08:00
|
|
|
else
|
|
|
|
|
seat->mouse.row = (y - term->margins.top) / term->cell_height;
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-19 23:01:53 +08:00
|
|
|
static bool
|
|
|
|
|
touch_is_active(const struct seat *seat)
|
|
|
|
|
{
|
|
|
|
|
if (seat->wl_touch == NULL) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (seat->touch.state) {
|
|
|
|
|
case TOUCH_STATE_IDLE:
|
|
|
|
|
case TOUCH_STATE_INHIBITED:
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
case TOUCH_STATE_HELD:
|
|
|
|
|
case TOUCH_STATE_DRAGGING:
|
|
|
|
|
case TOUCH_STATE_SCROLLING:
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BUG("Bad touch state: %d", seat->touch.state);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-05 10:44:57 +02:00
|
|
|
static void
|
|
|
|
|
wl_pointer_enter(void *data, struct wl_pointer *wl_pointer,
|
|
|
|
|
uint32_t serial, struct wl_surface *surface,
|
|
|
|
|
wl_fixed_t surface_x, wl_fixed_t surface_y)
|
|
|
|
|
{
|
2022-08-12 16:13:25 +02:00
|
|
|
if (unlikely(surface == NULL)) {
|
2021-03-04 08:57:31 +01:00
|
|
|
/* Seen on mutter-3.38 */
|
2022-08-12 16:13:25 +02:00
|
|
|
LOG_WARN("compositor sent pointer_enter event with a NULL surface");
|
2021-03-04 08:57:31 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-08 16:45:26 +02:00
|
|
|
struct seat *seat = data;
|
2023-07-05 00:19:21 +08:00
|
|
|
|
2020-02-29 12:09:28 +01:00
|
|
|
struct wl_window *win = wl_surface_get_user_data(surface);
|
|
|
|
|
struct terminal *term = win->term;
|
2019-10-27 18:43:07 +01:00
|
|
|
|
2023-08-19 23:01:53 +08:00
|
|
|
seat->mouse_focus = term;
|
|
|
|
|
term->active_surface = term_surface_kind(term, surface);
|
|
|
|
|
|
|
|
|
|
if (touch_is_active(seat))
|
|
|
|
|
return;
|
|
|
|
|
|
2021-11-19 15:04:51 +01:00
|
|
|
int x = wl_fixed_to_int(surface_x) * term->scale;
|
|
|
|
|
int y = wl_fixed_to_int(surface_y) * term->scale;
|
|
|
|
|
|
2020-07-09 11:20:46 +02:00
|
|
|
seat->pointer.serial = serial;
|
2020-07-31 17:09:06 +02:00
|
|
|
seat->pointer.hidden = false;
|
2021-11-19 15:04:51 +01:00
|
|
|
seat->mouse.x = x;
|
|
|
|
|
seat->mouse.y = y;
|
2020-07-09 11:20:46 +02:00
|
|
|
|
2021-11-19 15:04:51 +01:00
|
|
|
LOG_DBG("pointer-enter: pointer=%p, serial=%u, surface = %p, new-moused = %p, "
|
|
|
|
|
"x=%d, y=%d",
|
|
|
|
|
(void *)wl_pointer, serial, (void *)surface, (void *)term,
|
|
|
|
|
x, y);
|
2019-11-29 22:12:54 +01:00
|
|
|
|
2021-08-04 17:27:34 +02:00
|
|
|
xassert(tll_length(seat->mouse.buttons) == 0);
|
|
|
|
|
|
2021-12-01 20:04:01 +01:00
|
|
|
wayl_reload_xcursor_theme(seat, term->scale); /* Scale may have changed */
|
|
|
|
|
term_xcursor_update_for_seat(term, seat);
|
2019-07-05 15:13:06 +02:00
|
|
|
|
2021-12-01 20:04:01 +01:00
|
|
|
switch (term->active_surface) {
|
2020-07-26 12:31:13 +02:00
|
|
|
case TERM_SURF_GRID: {
|
2023-07-05 00:19:21 +08:00
|
|
|
mouse_coord_pixel_to_cell(seat, term, x, y);
|
2020-02-24 22:40:24 +01:00
|
|
|
break;
|
2020-07-26 12:31:13 +02:00
|
|
|
}
|
2020-02-24 22:40:24 +01:00
|
|
|
|
2020-02-29 15:29:00 +01:00
|
|
|
case TERM_SURF_TITLE:
|
2020-02-24 22:40:24 +01:00
|
|
|
case TERM_SURF_BORDER_LEFT:
|
|
|
|
|
case TERM_SURF_BORDER_RIGHT:
|
|
|
|
|
case TERM_SURF_BORDER_TOP:
|
2020-02-29 09:32:22 +01:00
|
|
|
case TERM_SURF_BORDER_BOTTOM:
|
2020-02-25 20:31:13 +01:00
|
|
|
break;
|
|
|
|
|
|
2020-03-02 20:29:28 +01:00
|
|
|
case TERM_SURF_BUTTON_MINIMIZE:
|
|
|
|
|
case TERM_SURF_BUTTON_MAXIMIZE:
|
2020-03-03 18:22:32 +01:00
|
|
|
case TERM_SURF_BUTTON_CLOSE:
|
2020-03-06 19:16:54 +01:00
|
|
|
render_refresh_csd(term);
|
2020-03-02 20:29:28 +01:00
|
|
|
break;
|
|
|
|
|
|
2020-02-29 15:29:00 +01:00
|
|
|
case TERM_SURF_NONE:
|
2021-02-10 09:01:51 +00:00
|
|
|
BUG("Invalid surface type");
|
2020-02-24 22:40:24 +01:00
|
|
|
break;
|
|
|
|
|
}
|
2019-07-05 10:44:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
wl_pointer_leave(void *data, struct wl_pointer *wl_pointer,
|
|
|
|
|
uint32_t serial, struct wl_surface *surface)
|
|
|
|
|
{
|
2020-07-08 16:45:26 +02:00
|
|
|
struct seat *seat = data;
|
2023-07-05 00:19:21 +08:00
|
|
|
|
|
|
|
|
if (seat->wl_touch != NULL) {
|
2023-08-19 23:01:53 +08:00
|
|
|
switch (seat->touch.state) {
|
|
|
|
|
case TOUCH_STATE_IDLE:
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case TOUCH_STATE_INHIBITED:
|
|
|
|
|
seat->touch.state = TOUCH_STATE_IDLE;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case TOUCH_STATE_HELD:
|
|
|
|
|
case TOUCH_STATE_DRAGGING:
|
|
|
|
|
case TOUCH_STATE_SCROLLING:
|
2023-07-05 00:19:21 +08:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-08 16:45:26 +02:00
|
|
|
struct terminal *old_moused = seat->mouse_focus;
|
2019-11-29 22:13:19 +01:00
|
|
|
|
2020-03-14 11:51:47 +01:00
|
|
|
LOG_DBG(
|
2020-07-11 11:13:45 +02:00
|
|
|
"%s: pointer-leave: pointer=%p, serial=%u, surface = %p, old-moused = %p",
|
2020-08-23 07:50:27 +02:00
|
|
|
seat->name, (void *)wl_pointer, serial, (void *)surface,
|
|
|
|
|
(void *)old_moused);
|
2019-11-29 22:13:19 +01:00
|
|
|
|
2020-07-31 17:09:06 +02:00
|
|
|
seat->pointer.hidden = false;
|
|
|
|
|
|
2020-07-08 16:45:26 +02:00
|
|
|
if (seat->pointer.xcursor_callback != NULL) {
|
2020-02-15 19:02:43 +01:00
|
|
|
/* A cursor frame callback may never be called if the pointer leaves our surface */
|
2020-07-08 16:45:26 +02:00
|
|
|
wl_callback_destroy(seat->pointer.xcursor_callback);
|
|
|
|
|
seat->pointer.xcursor_callback = NULL;
|
2020-07-09 09:52:11 +02:00
|
|
|
seat->pointer.xcursor_pending = false;
|
2020-02-15 19:02:43 +01:00
|
|
|
}
|
|
|
|
|
|
2021-12-01 20:04:01 +01:00
|
|
|
/* Reset last-set-xcursor, to ensure we update it on a pointer-enter event */
|
2023-06-27 16:57:33 +02:00
|
|
|
seat->pointer.shape = CURSOR_SHAPE_NONE;
|
2021-12-01 20:04:01 +01:00
|
|
|
|
2020-02-29 12:08:59 +01:00
|
|
|
/* Reset mouse state */
|
2020-08-10 18:59:03 +02:00
|
|
|
seat->mouse.x = seat->mouse.y = 0;
|
|
|
|
|
seat->mouse.col = seat->mouse.row = 0;
|
2023-07-05 00:19:21 +08:00
|
|
|
mouse_button_state_reset(seat);
|
2021-08-05 18:34:09 +02:00
|
|
|
for (size_t i = 0; i < ALEN(seat->mouse.aggregated); i++)
|
|
|
|
|
seat->mouse.aggregated[i] = 0.0;
|
2020-08-10 18:59:03 +02:00
|
|
|
seat->mouse.have_discrete = false;
|
2020-02-29 12:08:59 +01:00
|
|
|
|
2020-07-08 16:45:26 +02:00
|
|
|
seat->mouse_focus = NULL;
|
2019-11-29 22:13:19 +01:00
|
|
|
if (old_moused == NULL) {
|
2019-11-28 19:47:00 +01:00
|
|
|
LOG_WARN(
|
|
|
|
|
"compositor sent pointer_leave event without a pointer_enter "
|
2020-08-23 07:50:27 +02:00
|
|
|
"event: surface=%p", (void *)surface);
|
2020-02-24 22:41:46 +01:00
|
|
|
} else {
|
2020-02-29 12:09:28 +01:00
|
|
|
if (surface != NULL) {
|
|
|
|
|
/* Sway 1.4 sends this event with a NULL surface when we destroy the window */
|
2020-08-07 20:42:34 +01:00
|
|
|
const struct wl_window UNUSED *win = wl_surface_get_user_data(surface);
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(old_moused == win->term);
|
2020-02-29 12:09:28 +01:00
|
|
|
}
|
2020-03-02 20:29:28 +01:00
|
|
|
|
|
|
|
|
enum term_surface active_surface = old_moused->active_surface;
|
|
|
|
|
|
2020-02-24 22:41:46 +01:00
|
|
|
old_moused->active_surface = TERM_SURF_NONE;
|
2020-03-02 20:29:28 +01:00
|
|
|
|
|
|
|
|
switch (active_surface) {
|
|
|
|
|
case TERM_SURF_BUTTON_MINIMIZE:
|
|
|
|
|
case TERM_SURF_BUTTON_MAXIMIZE:
|
2020-03-03 18:22:32 +01:00
|
|
|
case TERM_SURF_BUTTON_CLOSE:
|
2021-07-31 19:08:51 +02:00
|
|
|
if (old_moused->shutdown.in_progress)
|
2020-03-03 18:22:32 +01:00
|
|
|
break;
|
|
|
|
|
|
2020-03-06 19:16:54 +01:00
|
|
|
render_refresh_csd(old_moused);
|
2020-03-02 20:29:28 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case TERM_SURF_GRID:
|
2021-11-25 15:21:53 +01:00
|
|
|
selection_finalize(seat, old_moused, seat->pointer.serial);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case TERM_SURF_NONE:
|
2020-03-02 20:29:28 +01:00
|
|
|
case TERM_SURF_TITLE:
|
|
|
|
|
case TERM_SURF_BORDER_LEFT:
|
|
|
|
|
case TERM_SURF_BORDER_RIGHT:
|
|
|
|
|
case TERM_SURF_BORDER_TOP:
|
|
|
|
|
case TERM_SURF_BORDER_BOTTOM:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-24 22:41:46 +01:00
|
|
|
}
|
2019-07-05 10:44:57 +02:00
|
|
|
}
|
|
|
|
|
|
2024-08-09 08:20:59 +02:00
|
|
|
static bool
|
|
|
|
|
pointer_is_on_button(const struct terminal *term, const struct seat *seat,
|
|
|
|
|
enum csd_surface csd_surface)
|
|
|
|
|
{
|
|
|
|
|
if (seat->mouse.x < 0)
|
|
|
|
|
return false;
|
|
|
|
|
if (seat->mouse.y < 0)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
struct csd_data info = get_csd_data(term, csd_surface);
|
|
|
|
|
if (seat->mouse.x > info.width)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (seat->mouse.y > info.height)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-05 10:44:57 +02:00
|
|
|
static void
|
|
|
|
|
wl_pointer_motion(void *data, struct wl_pointer *wl_pointer,
|
|
|
|
|
uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y)
|
|
|
|
|
{
|
2020-07-08 16:45:26 +02:00
|
|
|
struct seat *seat = data;
|
2023-07-05 00:19:21 +08:00
|
|
|
|
|
|
|
|
/* Touch-emulated pointer events have wl_pointer == NULL. */
|
2023-08-19 23:01:53 +08:00
|
|
|
if (wl_pointer != NULL && touch_is_active(seat))
|
2023-07-05 00:19:21 +08:00
|
|
|
return;
|
|
|
|
|
|
2020-07-08 16:45:26 +02:00
|
|
|
struct wayland *wayl = seat->wayl;
|
|
|
|
|
struct terminal *term = seat->mouse_focus;
|
2022-08-12 16:13:25 +02:00
|
|
|
|
|
|
|
|
if (unlikely(term == NULL)) {
|
|
|
|
|
/* Typically happens when the compositor sent a pointer enter
|
|
|
|
|
* event with a NULL surface - see wl_pointer_enter().
|
|
|
|
|
*
|
|
|
|
|
* In this case, we never set seat->mouse_focus (since we
|
2024-02-06 12:36:45 +01:00
|
|
|
* can't map the enter event to a specific window). */
|
2022-08-12 16:13:25 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-29 15:38:04 +01:00
|
|
|
struct wl_window *win = term->window;
|
2019-11-22 21:49:50 +01:00
|
|
|
|
2020-08-23 07:50:27 +02:00
|
|
|
LOG_DBG("pointer_motion: pointer=%p, x=%d, y=%d", (void *)wl_pointer,
|
2020-03-14 11:51:47 +01:00
|
|
|
wl_fixed_to_int(surface_x), wl_fixed_to_int(surface_y));
|
|
|
|
|
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(term != NULL);
|
2019-07-05 15:13:06 +02:00
|
|
|
|
2019-09-26 18:39:49 +02:00
|
|
|
int x = wl_fixed_to_int(surface_x) * term->scale;
|
|
|
|
|
int y = wl_fixed_to_int(surface_y) * term->scale;
|
2019-07-05 15:13:06 +02:00
|
|
|
|
2024-08-09 08:20:59 +02:00
|
|
|
enum term_surface surf_kind = term->active_surface;
|
|
|
|
|
int button = 0;
|
|
|
|
|
bool send_to_client = false;
|
|
|
|
|
bool is_on_button = false;
|
|
|
|
|
|
|
|
|
|
/* If current surface is a button, check if pointer was on it
|
|
|
|
|
*before* the motion event */
|
|
|
|
|
switch (surf_kind) {
|
|
|
|
|
case TERM_SURF_BUTTON_MINIMIZE:
|
|
|
|
|
is_on_button = pointer_is_on_button(term, seat, CSD_SURF_MINIMIZE);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case TERM_SURF_BUTTON_MAXIMIZE:
|
|
|
|
|
is_on_button = pointer_is_on_button(term, seat, CSD_SURF_MAXIMIZE);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case TERM_SURF_BUTTON_CLOSE:
|
|
|
|
|
is_on_button = pointer_is_on_button(term, seat, CSD_SURF_CLOSE);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case TERM_SURF_NONE:
|
|
|
|
|
case TERM_SURF_GRID:
|
|
|
|
|
case TERM_SURF_TITLE:
|
|
|
|
|
case TERM_SURF_BORDER_LEFT:
|
|
|
|
|
case TERM_SURF_BORDER_RIGHT:
|
|
|
|
|
case TERM_SURF_BORDER_TOP:
|
|
|
|
|
case TERM_SURF_BORDER_BOTTOM:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-31 17:09:06 +02:00
|
|
|
seat->pointer.hidden = false;
|
2020-07-08 16:45:26 +02:00
|
|
|
seat->mouse.x = x;
|
|
|
|
|
seat->mouse.y = y;
|
2020-02-29 09:32:22 +01:00
|
|
|
|
2021-12-01 20:04:01 +01:00
|
|
|
term_xcursor_update_for_seat(term, seat);
|
|
|
|
|
|
2020-12-12 19:05:24 +01:00
|
|
|
if (tll_length(seat->mouse.buttons) > 0) {
|
|
|
|
|
const struct button_tracker *tracker = &tll_front(seat->mouse.buttons);
|
|
|
|
|
surf_kind = tracker->surf_kind;
|
|
|
|
|
button = tracker->button;
|
|
|
|
|
send_to_client = tracker->send_to_client;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (surf_kind) {
|
2020-02-25 20:31:13 +01:00
|
|
|
case TERM_SURF_NONE:
|
2024-08-09 08:20:59 +02:00
|
|
|
break;
|
|
|
|
|
|
2020-03-02 20:29:28 +01:00
|
|
|
case TERM_SURF_BUTTON_MINIMIZE:
|
2024-08-09 08:20:59 +02:00
|
|
|
if (pointer_is_on_button(term, seat, CSD_SURF_MINIMIZE) != is_on_button)
|
|
|
|
|
render_refresh_csd(term);
|
|
|
|
|
break;
|
|
|
|
|
|
2020-03-02 20:29:28 +01:00
|
|
|
case TERM_SURF_BUTTON_MAXIMIZE:
|
2024-08-09 08:20:59 +02:00
|
|
|
if (pointer_is_on_button(term, seat, CSD_SURF_MAXIMIZE) != is_on_button)
|
|
|
|
|
render_refresh_csd(term);
|
|
|
|
|
break;
|
|
|
|
|
|
2020-03-02 20:29:28 +01:00
|
|
|
case TERM_SURF_BUTTON_CLOSE:
|
2024-08-09 08:20:59 +02:00
|
|
|
if (pointer_is_on_button(term, seat, CSD_SURF_CLOSE) != is_on_button)
|
|
|
|
|
render_refresh_csd(term);
|
2020-02-29 15:38:04 +01:00
|
|
|
break;
|
|
|
|
|
|
2020-02-25 20:31:13 +01:00
|
|
|
case TERM_SURF_TITLE:
|
2020-02-29 15:38:04 +01:00
|
|
|
/* We've started a 'move' timer, but user started dragging
|
|
|
|
|
* right away - abort the timer and initiate the actual move
|
|
|
|
|
* right away */
|
2020-12-12 19:05:24 +01:00
|
|
|
if (button == BTN_LEFT && win->csd.move_timeout_fd != -1) {
|
2020-02-29 15:38:04 +01:00
|
|
|
fdm_del(wayl->fdm, win->csd.move_timeout_fd);
|
|
|
|
|
win->csd.move_timeout_fd = -1;
|
2020-07-08 16:45:26 +02:00
|
|
|
xdg_toplevel_move(win->xdg_toplevel, seat->wl_seat, win->csd.serial);
|
2020-02-29 15:38:04 +01:00
|
|
|
}
|
2020-02-25 20:31:13 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case TERM_SURF_BORDER_LEFT:
|
|
|
|
|
case TERM_SURF_BORDER_RIGHT:
|
|
|
|
|
case TERM_SURF_BORDER_TOP:
|
|
|
|
|
case TERM_SURF_BORDER_BOTTOM:
|
|
|
|
|
break;
|
|
|
|
|
|
2020-02-29 15:29:00 +01:00
|
|
|
case TERM_SURF_GRID: {
|
2020-07-26 12:31:13 +02:00
|
|
|
int old_col = seat->mouse.col;
|
|
|
|
|
int old_row = seat->mouse.row;
|
|
|
|
|
|
input: allow mouse selections to start inside the margins
Before this, margins were special cased:
* The mouse cursor was always a pointer, and never an I-beam (thus
signaling selections cannot be made).
* The internal mouse coords where set to -1 when the cursor was inside
the margins, causing:
- text selections from being made
- mouse events being passed to mouse grabbing applications
In particular, even with a one-pixel margin, making selections was
unnecessarily hard in e.g. fullscreen mode, where you'd expect to be
able to throw the cursor into the corner of the screen and then start
a selection.
With this patch, the cursor is treated as if it was in the first/last
column/row, when inside the margin(s).
An unintended side-effect of this, initially, was that auto-scrolling
selections where way too easy to trigger, since part of its logic is
checking if the cursor is inside the margins.
That problem has been reduced by two things:
* auto-scrolling does not occur unless a selection has been
started. That is, just holding down the mouse in the margins and
moving up/down doesn't cause scrolling. You have to first select at
least one cell in the visible viewport.
* A selection isn't fully started (i.e. a cell is actually selected)
unless the cursor is inside the actual grid, and *not* in the
margins.
What does the last point mean? We now allow a selection to be
_started_ when clicking in the margin. What this means internally is
we set a start coordinate for a selection, but *not* and end
coordinate. At this point, we don't have an actual selection. Nothing
is selected, and no cells are highlighted, graphically.
This happens when we set an end coordinate. Without the last bullet
point, that would happen as soon as the cursor was _moved_, even if
still inside the margins. Now, we require the cursor to leave the
margins and touch an actual cell before we set an end coordinate.
Closes #1702
2024-07-18 08:08:44 +02:00
|
|
|
mouse_coord_pixel_to_cell(seat, term, seat->mouse.x, seat->mouse.y);
|
input: report mouse drag events also when the pointer is outside the grid
As long as the mouse button was *pressed* while the pointer was inside
the grid, we want to keep reporting motion events until the button is
released.
Even when the pointer moves outside the grid (but in this case, the
reported coordinates are bounded by the grid size).
This patch also tries to improve multi-button handling (i.e. multiple
buttons pressed at the same time), and the events we report to the
client for these, in the following ways:
* Motion events now report the *initial* button. That is, if you start
a drag operation with the LEFT button, then press RIGHT (before
releasing LEFT), keep reporting LEFT in the motion events.
* Mouse release events are reported for *any* button, as long as the
pointer is *inside* the grid, *or*, the button released was the
button used to start a drag operation.
The last point is important; if we have reported a button press
followed by motion events (i.e. a drag operation), we need to report
the button release, *even* if the pointer is outside the grid.
Note that the client may receive unbalanced button press/release
events in the following ways if the user pressed one, and then a
second button *inside* the grid, then releases the *first*
button (possibly outside the grid), and finally releases the *second*
button *outside* the grid.
In this case, both buttons will report press events. The first button
will report a release event since it is the initial button in the drag
operation.
However, we don’t track the fact that the second button is being
pressed, and thus if it is released outside the grid, it wont generate
a release event.
2020-12-11 20:48:43 +01:00
|
|
|
|
input: allow mouse selections to start inside the margins
Before this, margins were special cased:
* The mouse cursor was always a pointer, and never an I-beam (thus
signaling selections cannot be made).
* The internal mouse coords where set to -1 when the cursor was inside
the margins, causing:
- text selections from being made
- mouse events being passed to mouse grabbing applications
In particular, even with a one-pixel margin, making selections was
unnecessarily hard in e.g. fullscreen mode, where you'd expect to be
able to throw the cursor into the corner of the screen and then start
a selection.
With this patch, the cursor is treated as if it was in the first/last
column/row, when inside the margin(s).
An unintended side-effect of this, initially, was that auto-scrolling
selections where way too easy to trigger, since part of its logic is
checking if the cursor is inside the margins.
That problem has been reduced by two things:
* auto-scrolling does not occur unless a selection has been
started. That is, just holding down the mouse in the margins and
moving up/down doesn't cause scrolling. You have to first select at
least one cell in the visible viewport.
* A selection isn't fully started (i.e. a cell is actually selected)
unless the cursor is inside the actual grid, and *not* in the
margins.
What does the last point mean? We now allow a selection to be
_started_ when clicking in the margin. What this means internally is
we set a start coordinate for a selection, but *not* and end
coordinate. At this point, we don't have an actual selection. Nothing
is selected, and no cells are highlighted, graphically.
This happens when we set an end coordinate. Without the last bullet
point, that would happen as soon as the cursor was _moved_, even if
still inside the margins. Now, we require the cursor to leave the
margins and touch an actual cell before we set an end coordinate.
Closes #1702
2024-07-18 08:08:44 +02:00
|
|
|
xassert(seat->mouse.col >= 0 && seat->mouse.col < term->cols);
|
|
|
|
|
xassert(seat->mouse.row >= 0 && seat->mouse.row < term->rows);
|
2019-07-11 11:09:34 +02:00
|
|
|
|
2020-08-07 22:04:37 +02:00
|
|
|
/* Cursor has moved to a different cell since last time */
|
|
|
|
|
bool cursor_is_on_new_cell
|
|
|
|
|
= old_col != seat->mouse.col || old_row != seat->mouse.row;
|
2019-07-11 09:51:51 +02:00
|
|
|
|
input: reset mouse button click counter when mouse moves
As described in #883, creating a block selection (with ctrl+BTN_LEFT),
then *quickly* (within 300ms from) creating a new one, will, in fact,
_not_ create a new block selection, but a ‘select-word-whitespace’
selection (ctrl+BTN_LEFT-2).
A similar effect can be seen with plain selections (BTN_LEFT). Click
and drag to make a selection, but *release*, and make a new selection
within 300ms from the initial button press, and the new selection is
suddenly a word-based selection.
This happens because triggering a binding does *not* reset the button
click counter.
So, shouldn’t we just do that?
No, because we rely on this behavior to handle single-, double- and
triple-click actions. If we were to reset the button click handler,
then we’d have to instead delay triggering the first action with
300ms, which would make things appear laggy. If we don’t do this, it
would be impossible to double- and triple-click, since the
single-click action would keep resetting the click counter.
This patch takes a slightly different approach; we reset the click
counter when the mouse has moved “too much”. For now, “too much” is
when the cursor has moved to a different cell.
This way, single-, double- and triple-clicks continue to work as
before. But, creating actual selections, and then quickly releasing
and starting a new selection produces the expected results.
Closes #883
2022-01-12 15:38:32 +01:00
|
|
|
if (cursor_is_on_new_cell) {
|
|
|
|
|
/* Prevent multiple/different mouse bindings from
|
2024-02-06 12:36:45 +01:00
|
|
|
* triggering if the mouse has moved "too much" (to
|
input: reset mouse button click counter when mouse moves
As described in #883, creating a block selection (with ctrl+BTN_LEFT),
then *quickly* (within 300ms from) creating a new one, will, in fact,
_not_ create a new block selection, but a ‘select-word-whitespace’
selection (ctrl+BTN_LEFT-2).
A similar effect can be seen with plain selections (BTN_LEFT). Click
and drag to make a selection, but *release*, and make a new selection
within 300ms from the initial button press, and the new selection is
suddenly a word-based selection.
This happens because triggering a binding does *not* reset the button
click counter.
So, shouldn’t we just do that?
No, because we rely on this behavior to handle single-, double- and
triple-click actions. If we were to reset the button click handler,
then we’d have to instead delay triggering the first action with
300ms, which would make things appear laggy. If we don’t do this, it
would be impossible to double- and triple-click, since the
single-click action would keep resetting the click counter.
This patch takes a slightly different approach; we reset the click
counter when the mouse has moved “too much”. For now, “too much” is
when the cursor has moved to a different cell.
This way, single-, double- and triple-clicks continue to work as
before. But, creating actual selections, and then quickly releasing
and starting a new selection produces the expected results.
Closes #883
2022-01-12 15:38:32 +01:00
|
|
|
* another cell) */
|
|
|
|
|
seat->mouse.count = 0;
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-08 10:08:47 +02:00
|
|
|
/* Cursor is inside the grid, i.e. *not* in the margins */
|
2020-10-11 12:03:20 +02:00
|
|
|
const bool cursor_is_on_grid = seat->mouse.col >= 0 && seat->mouse.row >= 0;
|
|
|
|
|
|
2020-10-11 18:18:18 +02:00
|
|
|
enum selection_scroll_direction auto_scroll_direction
|
input: allow mouse selections to start inside the margins
Before this, margins were special cased:
* The mouse cursor was always a pointer, and never an I-beam (thus
signaling selections cannot be made).
* The internal mouse coords where set to -1 when the cursor was inside
the margins, causing:
- text selections from being made
- mouse events being passed to mouse grabbing applications
In particular, even with a one-pixel margin, making selections was
unnecessarily hard in e.g. fullscreen mode, where you'd expect to be
able to throw the cursor into the corner of the screen and then start
a selection.
With this patch, the cursor is treated as if it was in the first/last
column/row, when inside the margin(s).
An unintended side-effect of this, initially, was that auto-scrolling
selections where way too easy to trigger, since part of its logic is
checking if the cursor is inside the margins.
That problem has been reduced by two things:
* auto-scrolling does not occur unless a selection has been
started. That is, just holding down the mouse in the margins and
moving up/down doesn't cause scrolling. You have to first select at
least one cell in the visible viewport.
* A selection isn't fully started (i.e. a cell is actually selected)
unless the cursor is inside the actual grid, and *not* in the
margins.
What does the last point mean? We now allow a selection to be
_started_ when clicking in the margin. What this means internally is
we set a start coordinate for a selection, but *not* and end
coordinate. At this point, we don't have an actual selection. Nothing
is selected, and no cells are highlighted, graphically.
This happens when we set an end coordinate. Without the last bullet
point, that would happen as soon as the cursor was _moved_, even if
still inside the margins. Now, we require the cursor to leave the
margins and touch an actual cell before we set an end coordinate.
Closes #1702
2024-07-18 08:08:44 +02:00
|
|
|
= term->selection.coords.end.row < 0
|
|
|
|
|
? SELECTION_SCROLL_NOT
|
|
|
|
|
: y < term->margins.top
|
|
|
|
|
? SELECTION_SCROLL_UP
|
|
|
|
|
: y > term->height - term->margins.bottom
|
|
|
|
|
? SELECTION_SCROLL_DOWN
|
|
|
|
|
: SELECTION_SCROLL_NOT;
|
selection: auto-scroll: selection keeps scrolling while mouse is outside grid
Moving the mouse outside the grid while we have an on-going selection
now starts a timer. The interval of this timer depends on the mouse’s
distance from the grid - the further away the mouse is, the shorter
interval.
On each timer timeout, we scroll one line, and update the
selection. Thus, the shorter the interval, the faster we scroll.
The timer is canceled as soon as the mouse enters the grid again, or
the selection is either canceled or finalized.
The timer FD is created and destroyed on-demand.
Most of the logic is now in selection.c. The exception is the
calculation of the timer interval, which depends on the mouse’s
position. Thus, this is done in input.c.
The scroll+selection update logic needs to know a) which direction
we’re scrolling in, and b) which *column* the selection should be
updated with.
If the mouse is outside the grid’s left or right margins, the stored
mouse column will be -1. I.e. we don’t know whether the mouse is on
the left or right side of the grid. This is why the caller, that
starts the timer, must provide this value.
The same applies to top and bottom margins, but since we already have
the scroll *direction*, which row value to use can be derived from this.
2020-10-11 15:44:20 +02:00
|
|
|
|
2020-10-11 18:18:18 +02:00
|
|
|
if (auto_scroll_direction == SELECTION_SCROLL_NOT)
|
selection: auto-scroll: selection keeps scrolling while mouse is outside grid
Moving the mouse outside the grid while we have an on-going selection
now starts a timer. The interval of this timer depends on the mouse’s
distance from the grid - the further away the mouse is, the shorter
interval.
On each timer timeout, we scroll one line, and update the
selection. Thus, the shorter the interval, the faster we scroll.
The timer is canceled as soon as the mouse enters the grid again, or
the selection is either canceled or finalized.
The timer FD is created and destroyed on-demand.
Most of the logic is now in selection.c. The exception is the
calculation of the timer interval, which depends on the mouse’s
position. Thus, this is done in input.c.
The scroll+selection update logic needs to know a) which direction
we’re scrolling in, and b) which *column* the selection should be
updated with.
If the mouse is outside the grid’s left or right margins, the stored
mouse column will be -1. I.e. we don’t know whether the mouse is on
the left or right side of the grid. This is why the caller, that
starts the timer, must provide this value.
The same applies to top and bottom margins, but since we already have
the scroll *direction*, which row value to use can be derived from this.
2020-10-11 15:44:20 +02:00
|
|
|
selection_stop_scroll_timer(term);
|
2020-08-08 10:00:18 +02:00
|
|
|
|
2020-08-07 22:04:37 +02:00
|
|
|
/* Update selection */
|
2020-08-11 16:36:42 +02:00
|
|
|
if (!term->is_searching) {
|
2020-10-11 18:18:18 +02:00
|
|
|
if (auto_scroll_direction != SELECTION_SCROLL_NOT) {
|
|
|
|
|
/*
|
2024-02-06 12:36:45 +01:00
|
|
|
* Start 'selection auto-scrolling'
|
2020-10-11 18:18:18 +02:00
|
|
|
*
|
|
|
|
|
* The speed of the scrolling is proportional to the
|
|
|
|
|
* distance between the mouse and the grid; the
|
2020-10-11 18:22:29 +02:00
|
|
|
* further away the mouse is, the faster we scroll.
|
2020-10-11 18:18:18 +02:00
|
|
|
*
|
2024-02-06 12:36:45 +01:00
|
|
|
* Note that the speed is measured in 'intervals (in
|
|
|
|
|
* ns) between each timed scroll of a single line'.
|
2020-10-11 18:18:18 +02:00
|
|
|
*
|
|
|
|
|
* Thus, the further away the mouse is, the smaller
|
|
|
|
|
* interval value we use.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
int distance = auto_scroll_direction == SELECTION_SCROLL_UP
|
selection: auto-scroll: selection keeps scrolling while mouse is outside grid
Moving the mouse outside the grid while we have an on-going selection
now starts a timer. The interval of this timer depends on the mouse’s
distance from the grid - the further away the mouse is, the shorter
interval.
On each timer timeout, we scroll one line, and update the
selection. Thus, the shorter the interval, the faster we scroll.
The timer is canceled as soon as the mouse enters the grid again, or
the selection is either canceled or finalized.
The timer FD is created and destroyed on-demand.
Most of the logic is now in selection.c. The exception is the
calculation of the timer interval, which depends on the mouse’s
position. Thus, this is done in input.c.
The scroll+selection update logic needs to know a) which direction
we’re scrolling in, and b) which *column* the selection should be
updated with.
If the mouse is outside the grid’s left or right margins, the stored
mouse column will be -1. I.e. we don’t know whether the mouse is on
the left or right side of the grid. This is why the caller, that
starts the timer, must provide this value.
The same applies to top and bottom margins, but since we already have
the scroll *direction*, which row value to use can be derived from this.
2020-10-11 15:44:20 +02:00
|
|
|
? term->margins.top - y
|
|
|
|
|
: y - (term->height - term->margins.bottom);
|
|
|
|
|
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(distance > 0);
|
2020-10-11 18:09:30 +02:00
|
|
|
int divisor
|
|
|
|
|
= distance * term->conf->scrollback.multiplier / term->scale;
|
selection: auto-scroll: selection keeps scrolling while mouse is outside grid
Moving the mouse outside the grid while we have an on-going selection
now starts a timer. The interval of this timer depends on the mouse’s
distance from the grid - the further away the mouse is, the shorter
interval.
On each timer timeout, we scroll one line, and update the
selection. Thus, the shorter the interval, the faster we scroll.
The timer is canceled as soon as the mouse enters the grid again, or
the selection is either canceled or finalized.
The timer FD is created and destroyed on-demand.
Most of the logic is now in selection.c. The exception is the
calculation of the timer interval, which depends on the mouse’s
position. Thus, this is done in input.c.
The scroll+selection update logic needs to know a) which direction
we’re scrolling in, and b) which *column* the selection should be
updated with.
If the mouse is outside the grid’s left or right margins, the stored
mouse column will be -1. I.e. we don’t know whether the mouse is on
the left or right side of the grid. This is why the caller, that
starts the timer, must provide this value.
The same applies to top and bottom margins, but since we already have
the scroll *direction*, which row value to use can be derived from this.
2020-10-11 15:44:20 +02:00
|
|
|
|
|
|
|
|
selection_start_scroll_timer(
|
2020-10-11 18:09:30 +02:00
|
|
|
term, 400000000 / (divisor > 0 ? divisor : 1),
|
input: allow mouse selections to start inside the margins
Before this, margins were special cased:
* The mouse cursor was always a pointer, and never an I-beam (thus
signaling selections cannot be made).
* The internal mouse coords where set to -1 when the cursor was inside
the margins, causing:
- text selections from being made
- mouse events being passed to mouse grabbing applications
In particular, even with a one-pixel margin, making selections was
unnecessarily hard in e.g. fullscreen mode, where you'd expect to be
able to throw the cursor into the corner of the screen and then start
a selection.
With this patch, the cursor is treated as if it was in the first/last
column/row, when inside the margin(s).
An unintended side-effect of this, initially, was that auto-scrolling
selections where way too easy to trigger, since part of its logic is
checking if the cursor is inside the margins.
That problem has been reduced by two things:
* auto-scrolling does not occur unless a selection has been
started. That is, just holding down the mouse in the margins and
moving up/down doesn't cause scrolling. You have to first select at
least one cell in the visible viewport.
* A selection isn't fully started (i.e. a cell is actually selected)
unless the cursor is inside the actual grid, and *not* in the
margins.
What does the last point mean? We now allow a selection to be
_started_ when clicking in the margin. What this means internally is
we set a start coordinate for a selection, but *not* and end
coordinate. At this point, we don't have an actual selection. Nothing
is selected, and no cells are highlighted, graphically.
This happens when we set an end coordinate. Without the last bullet
point, that would happen as soon as the cursor was _moved_, even if
still inside the margins. Now, we require the cursor to leave the
margins and touch an actual cell before we set an end coordinate.
Closes #1702
2024-07-18 08:08:44 +02:00
|
|
|
auto_scroll_direction, seat->mouse.col);
|
2020-10-11 12:03:20 +02:00
|
|
|
}
|
|
|
|
|
|
input: allow mouse selections to start inside the margins
Before this, margins were special cased:
* The mouse cursor was always a pointer, and never an I-beam (thus
signaling selections cannot be made).
* The internal mouse coords where set to -1 when the cursor was inside
the margins, causing:
- text selections from being made
- mouse events being passed to mouse grabbing applications
In particular, even with a one-pixel margin, making selections was
unnecessarily hard in e.g. fullscreen mode, where you'd expect to be
able to throw the cursor into the corner of the screen and then start
a selection.
With this patch, the cursor is treated as if it was in the first/last
column/row, when inside the margin(s).
An unintended side-effect of this, initially, was that auto-scrolling
selections where way too easy to trigger, since part of its logic is
checking if the cursor is inside the margins.
That problem has been reduced by two things:
* auto-scrolling does not occur unless a selection has been
started. That is, just holding down the mouse in the margins and
moving up/down doesn't cause scrolling. You have to first select at
least one cell in the visible viewport.
* A selection isn't fully started (i.e. a cell is actually selected)
unless the cursor is inside the actual grid, and *not* in the
margins.
What does the last point mean? We now allow a selection to be
_started_ when clicking in the margin. What this means internally is
we set a start coordinate for a selection, but *not* and end
coordinate. At this point, we don't have an actual selection. Nothing
is selected, and no cells are highlighted, graphically.
This happens when we set an end coordinate. Without the last bullet
point, that would happen as soon as the cursor was _moved_, even if
still inside the margins. Now, we require the cursor to leave the
margins and touch an actual cell before we set an end coordinate.
Closes #1702
2024-07-18 08:08:44 +02:00
|
|
|
if (term->selection.ongoing &&
|
|
|
|
|
(cursor_is_on_new_cell ||
|
|
|
|
|
(term->selection.coords.end.row < 0 &&
|
|
|
|
|
seat->mouse.x >= term->margins.left &&
|
|
|
|
|
seat->mouse.x < term->width - term->margins.right &&
|
|
|
|
|
seat->mouse.y >= term->margins.top &&
|
|
|
|
|
seat->mouse.y < term->height - term->margins.bottom)))
|
2020-12-12 19:05:24 +01:00
|
|
|
{
|
input: allow mouse selections to start inside the margins
Before this, margins were special cased:
* The mouse cursor was always a pointer, and never an I-beam (thus
signaling selections cannot be made).
* The internal mouse coords where set to -1 when the cursor was inside
the margins, causing:
- text selections from being made
- mouse events being passed to mouse grabbing applications
In particular, even with a one-pixel margin, making selections was
unnecessarily hard in e.g. fullscreen mode, where you'd expect to be
able to throw the cursor into the corner of the screen and then start
a selection.
With this patch, the cursor is treated as if it was in the first/last
column/row, when inside the margin(s).
An unintended side-effect of this, initially, was that auto-scrolling
selections where way too easy to trigger, since part of its logic is
checking if the cursor is inside the margins.
That problem has been reduced by two things:
* auto-scrolling does not occur unless a selection has been
started. That is, just holding down the mouse in the margins and
moving up/down doesn't cause scrolling. You have to first select at
least one cell in the visible viewport.
* A selection isn't fully started (i.e. a cell is actually selected)
unless the cursor is inside the actual grid, and *not* in the
margins.
What does the last point mean? We now allow a selection to be
_started_ when clicking in the margin. What this means internally is
we set a start coordinate for a selection, but *not* and end
coordinate. At this point, we don't have an actual selection. Nothing
is selected, and no cells are highlighted, graphically.
This happens when we set an end coordinate. Without the last bullet
point, that would happen as soon as the cursor was _moved_, even if
still inside the margins. Now, we require the cursor to leave the
margins and touch an actual cell before we set an end coordinate.
Closes #1702
2024-07-18 08:08:44 +02:00
|
|
|
selection_update(term, seat->mouse.col, seat->mouse.row);
|
2020-12-12 19:05:24 +01:00
|
|
|
}
|
2020-08-11 16:36:42 +02:00
|
|
|
}
|
2019-07-11 09:51:51 +02:00
|
|
|
|
2020-08-07 22:04:37 +02:00
|
|
|
/* Send mouse event to client application */
|
2020-12-12 19:05:24 +01:00
|
|
|
if (!term_mouse_grabbed(term, seat) &&
|
2021-12-30 05:13:45 -06:00
|
|
|
(cursor_is_on_new_cell ||
|
|
|
|
|
term->mouse_reporting == MOUSE_SGR_PIXELS) &&
|
2020-12-12 19:05:24 +01:00
|
|
|
((button == 0 && cursor_is_on_grid) ||
|
|
|
|
|
(button != 0 && send_to_client)))
|
2020-08-07 19:55:02 +02:00
|
|
|
{
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(seat->mouse.col < term->cols);
|
|
|
|
|
xassert(seat->mouse.row < term->rows);
|
2019-07-05 15:13:06 +02:00
|
|
|
|
2020-07-09 09:52:11 +02:00
|
|
|
term_mouse_motion(
|
2020-12-12 19:05:24 +01:00
|
|
|
term, button,
|
input: report mouse drag events also when the pointer is outside the grid
As long as the mouse button was *pressed* while the pointer was inside
the grid, we want to keep reporting motion events until the button is
released.
Even when the pointer moves outside the grid (but in this case, the
reported coordinates are bounded by the grid size).
This patch also tries to improve multi-button handling (i.e. multiple
buttons pressed at the same time), and the events we report to the
client for these, in the following ways:
* Motion events now report the *initial* button. That is, if you start
a drag operation with the LEFT button, then press RIGHT (before
releasing LEFT), keep reporting LEFT in the motion events.
* Mouse release events are reported for *any* button, as long as the
pointer is *inside* the grid, *or*, the button released was the
button used to start a drag operation.
The last point is important; if we have reported a button press
followed by motion events (i.e. a drag operation), we need to report
the button release, *even* if the pointer is outside the grid.
Note that the client may receive unbalanced button press/release
events in the following ways if the user pressed one, and then a
second button *inside* the grid, then releases the *first*
button (possibly outside the grid), and finally releases the *second*
button *outside* the grid.
In this case, both buttons will report press events. The first button
will report a release event since it is the initial button in the drag
operation.
However, we don’t track the fact that the second button is being
pressed, and thus if it is released outside the grid, it wont generate
a release event.
2020-12-11 20:48:43 +01:00
|
|
|
seat->mouse.row, seat->mouse.col,
|
2021-12-30 05:13:45 -06:00
|
|
|
seat->mouse.y - term->margins.top,
|
|
|
|
|
seat->mouse.x - term->margins.left,
|
2020-07-09 09:52:11 +02:00
|
|
|
seat->kbd.shift, seat->kbd.alt, seat->kbd.ctrl);
|
|
|
|
|
}
|
2020-02-29 15:29:00 +01:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-07-05 10:44:57 +02:00
|
|
|
}
|
|
|
|
|
|
2020-02-29 12:52:55 +01:00
|
|
|
static bool
|
|
|
|
|
fdm_csd_move(struct fdm *fdm, int fd, int events, void *data)
|
|
|
|
|
{
|
2020-07-08 16:45:26 +02:00
|
|
|
struct seat *seat = data;
|
2020-02-29 12:52:55 +01:00
|
|
|
fdm_del(fdm, fd);
|
|
|
|
|
|
2020-07-08 16:45:26 +02:00
|
|
|
if (seat->mouse_focus == NULL) {
|
|
|
|
|
LOG_WARN(
|
|
|
|
|
"%s: CSD move timeout triggered, but seat's has no mouse focused terminal",
|
|
|
|
|
seat->name);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct wl_window *win = seat->mouse_focus->window;
|
|
|
|
|
|
|
|
|
|
win->csd.move_timeout_fd = -1;
|
|
|
|
|
xdg_toplevel_move(win->xdg_toplevel, seat->wl_seat, win->csd.serial);
|
2020-02-29 12:52:55 +01:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-18 16:36:39 +02:00
|
|
|
static const struct key_binding *
|
2024-08-09 08:20:59 +02:00
|
|
|
match_mouse_binding(const struct seat *seat, const struct terminal *term,
|
|
|
|
|
int button)
|
2023-09-18 16:36:39 +02:00
|
|
|
{
|
|
|
|
|
if (seat->wl_keyboard != NULL && seat->kbd.xkb_state != NULL) {
|
|
|
|
|
/* Seat has keyboard - use mouse bindings *with* modifiers */
|
|
|
|
|
|
|
|
|
|
const struct key_binding_set *bindings =
|
|
|
|
|
key_binding_for(term->wl->key_binding_manager, term->conf, seat);
|
|
|
|
|
xassert(bindings != NULL);
|
|
|
|
|
|
|
|
|
|
xkb_mod_mask_t mods;
|
2024-02-06 10:41:01 +01:00
|
|
|
get_current_modifiers(seat, &mods, NULL, 0, true);
|
2023-09-18 16:36:39 +02:00
|
|
|
|
|
|
|
|
/* Ignore selection override modifiers when
|
|
|
|
|
* matching modifiers */
|
|
|
|
|
mods &= ~bindings->selection_overrides;
|
|
|
|
|
|
|
|
|
|
const struct key_binding *match = NULL;
|
|
|
|
|
|
|
|
|
|
tll_foreach(bindings->mouse, it) {
|
|
|
|
|
const struct key_binding *binding = &it->item;
|
|
|
|
|
|
|
|
|
|
if (binding->m.button != button) {
|
|
|
|
|
/* Wrong button */
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (binding->mods != mods) {
|
|
|
|
|
/* Modifier mismatch */
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (binding->m.count > seat->mouse.count) {
|
|
|
|
|
/* Not correct click count */
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (match == NULL || binding->m.count > match->m.count)
|
|
|
|
|
match = binding;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return match;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else {
|
|
|
|
|
/* Seat does NOT have a keyboard - use mouse bindings *without*
|
|
|
|
|
* modifiers */
|
|
|
|
|
const struct config_key_binding *match = NULL;
|
|
|
|
|
const struct config *conf = term->conf;
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < conf->bindings.mouse.count; i++) {
|
|
|
|
|
const struct config_key_binding *binding =
|
|
|
|
|
&conf->bindings.mouse.arr[i];
|
|
|
|
|
|
|
|
|
|
if (binding->m.button != button) {
|
|
|
|
|
/* Wrong button */
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (binding->m.count > seat->mouse.count) {
|
|
|
|
|
/* Incorrect click count */
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
input/config: support *all* modifier names
That is, allow custom modifiers (i.e. other than ctrl/shift/alt etc)
in key bindings.
This is done by no longer validating/translating modifier names to
booleans for a pre-configured set of modifiers (ctrl, shift, alt,
super).
Instead, we keep the modifier *names* in a list, in the key binding
struct.
When a keymap is loaded, and we "convert" the key binding, _then_ we
do modifier translation. For invalid modifier names, we print an
error, and then ignore it. I.e. we no longer fail to load a config due
to invalid modifier names.
We also need to update how we determine the set of significant
modifiers. Any modifier not in this list will be ignored when matching
key bindings.
Before this patch, we hardcoded this to shift/alt/ctrl/super. Now, to
handle custom modifiers as well, we simply treat *all* modifiers
defined by the current layout as significant.
Typically, the only unwanted modifiers are "locked" modifiers. We are
already filtering these out.
2023-05-06 11:39:38 +02:00
|
|
|
if (tll_length(binding->modifiers) > 0) {
|
2023-09-18 16:36:39 +02:00
|
|
|
/* Binding has modifiers */
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (match == NULL || binding->m.count > match->m.count)
|
|
|
|
|
match = binding;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (match != NULL) {
|
|
|
|
|
static struct key_binding bind;
|
|
|
|
|
bind.action = match->action;
|
|
|
|
|
bind.aux = &match->aux;
|
|
|
|
|
return &bind;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BUG("should not get here");
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-05 10:44:57 +02:00
|
|
|
static void
|
|
|
|
|
wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
|
|
|
|
|
uint32_t serial, uint32_t time, uint32_t button, uint32_t state)
|
|
|
|
|
{
|
2020-03-14 11:51:47 +01:00
|
|
|
LOG_DBG("BUTTON: pointer=%p, serial=%u, button=%x, state=%u",
|
2020-08-23 07:50:27 +02:00
|
|
|
(void *)wl_pointer, serial, button, state);
|
2019-07-05 14:24:51 +02:00
|
|
|
|
2022-04-23 15:54:37 +02:00
|
|
|
xassert(serial != 0);
|
|
|
|
|
|
2020-07-08 16:45:26 +02:00
|
|
|
struct seat *seat = data;
|
2023-07-05 00:19:21 +08:00
|
|
|
|
|
|
|
|
/* Touch-emulated pointer events have wl_pointer == NULL. */
|
2023-08-19 23:01:53 +08:00
|
|
|
if (wl_pointer != NULL && touch_is_active(seat))
|
2023-07-05 00:19:21 +08:00
|
|
|
return;
|
|
|
|
|
|
2020-07-08 16:45:26 +02:00
|
|
|
struct wayland *wayl = seat->wayl;
|
|
|
|
|
struct terminal *term = seat->mouse_focus;
|
2019-07-05 14:24:51 +02:00
|
|
|
|
2021-10-20 18:47:36 +02:00
|
|
|
seat->pointer.serial = serial;
|
2020-07-31 17:09:06 +02:00
|
|
|
seat->pointer.hidden = false;
|
|
|
|
|
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(term != NULL);
|
2020-02-24 22:42:04 +01:00
|
|
|
|
2020-12-12 21:10:28 +01:00
|
|
|
enum term_surface surf_kind = TERM_SURF_NONE;
|
2020-12-12 19:05:24 +01:00
|
|
|
bool send_to_client = false;
|
|
|
|
|
|
2020-02-29 11:55:43 +01:00
|
|
|
if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
|
2023-08-19 23:01:53 +08:00
|
|
|
if (seat->wl_touch != NULL && seat->touch.state == TOUCH_STATE_IDLE) {
|
|
|
|
|
seat->touch.state = TOUCH_STATE_INHIBITED;
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-29 11:55:43 +01:00
|
|
|
/* Time since last click */
|
2022-01-15 14:56:13 +05:30
|
|
|
struct timespec now, since_last;
|
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
|
|
|
timespec_sub(&now, &seat->mouse.last_time, &since_last);
|
2020-02-29 11:55:43 +01:00
|
|
|
|
2020-12-12 19:05:24 +01:00
|
|
|
if (seat->mouse.last_released_button == button &&
|
2022-01-15 14:56:13 +05:30
|
|
|
since_last.tv_sec == 0 && since_last.tv_nsec <= 300 * 1000 * 1000)
|
2020-02-29 11:55:43 +01:00
|
|
|
{
|
2020-07-08 16:45:26 +02:00
|
|
|
seat->mouse.count++;
|
2020-02-29 11:55:43 +01:00
|
|
|
} else
|
2020-07-08 16:45:26 +02:00
|
|
|
seat->mouse.count = 1;
|
2020-02-29 11:55:43 +01:00
|
|
|
|
2021-09-06 18:12:45 +02:00
|
|
|
/*
|
|
|
|
|
* Workaround GNOME bug
|
|
|
|
|
*
|
|
|
|
|
* Dragging the window, then stopping the drag (releasing the
|
|
|
|
|
* mouse button), *without* moving the mouse, and then
|
|
|
|
|
* clicking twice, waiting for the CSD timer, and finally
|
|
|
|
|
* clicking once more, results in the following sequence
|
|
|
|
|
* (keyboard and other irrelevant events filtered out, unless
|
2024-02-06 12:36:45 +01:00
|
|
|
* they're needed to prove a point):
|
2021-09-06 18:12:45 +02:00
|
|
|
*
|
|
|
|
|
* dbg: input.c:1551: cancelling drag timer, moving window
|
|
|
|
|
* dbg: input.c:759: keyboard_leave: keyboard=0x607000003580, serial=873, surface=0x6070000036d0
|
|
|
|
|
* dbg: input.c:1432: seat0: pointer-leave: pointer=0x607000003660, serial=874, surface = 0x6070000396e0, old-moused = 0x622000006100
|
|
|
|
|
*
|
|
|
|
|
* --> drag stopped here
|
|
|
|
|
*
|
|
|
|
|
* --> LMB clicked first time after the drag (generates the
|
|
|
|
|
* enter event on *release*, but no button events)
|
|
|
|
|
* dbg: input.c:1360: pointer-enter: pointer=0x607000003660, serial=876, surface = 0x6070000396e0, new-moused = 0x622000006100
|
|
|
|
|
*
|
|
|
|
|
* --> LMB clicked, and held until the timer times out, second
|
|
|
|
|
* time after the drag
|
|
|
|
|
* dbg: input.c:1712: BUTTON: pointer=0x607000003660, serial=877, button=110, state=1
|
|
|
|
|
* dbg: input.c:1806: starting move timer
|
|
|
|
|
* dbg: input.c:1692: move timer timed out
|
|
|
|
|
* dbg: input.c:759: keyboard_leave: keyboard=0x607000003580, serial=878, surface=0x6070000036d0
|
|
|
|
|
*
|
|
|
|
|
* --> NOTE: ^^ no pointer leave event this time, only the
|
|
|
|
|
* keyboard leave
|
|
|
|
|
*
|
|
|
|
|
* --> LMB clicked one last time
|
|
|
|
|
* dbg: input.c:697: seat0: keyboard_enter: keyboard=0x607000003580, serial=879, surface=0x6070000036d0
|
|
|
|
|
* dbg: input.c:1712: BUTTON: pointer=0x607000003660, serial=880, button=110, state=1
|
|
|
|
|
* err: input.c:1741: BUG in wl_pointer_button(): assertion failed: 'it->item.button != button'
|
|
|
|
|
*
|
|
|
|
|
* What are we seeing?
|
|
|
|
|
*
|
|
|
|
|
* - GNOME does *not* send a pointer *enter* event after the drag
|
|
|
|
|
* has stopped
|
|
|
|
|
* - The second drag does *not* generate a pointer *leave* event
|
2024-02-06 12:36:45 +01:00
|
|
|
* - The missing leave event means we're still tracking LMB as
|
2021-09-06 18:12:45 +02:00
|
|
|
* being held down in our seat struct.
|
|
|
|
|
* - This leads to an assert (debug builds) when LMB is clicked
|
2024-02-06 12:36:45 +01:00
|
|
|
* again (seat's button list already contains LMB).
|
2021-09-06 18:12:45 +02:00
|
|
|
*
|
2024-02-06 12:36:45 +01:00
|
|
|
* Note: I've also observed variants of the above
|
2021-09-06 18:12:45 +02:00
|
|
|
*/
|
|
|
|
|
tll_foreach(seat->mouse.buttons, it) {
|
|
|
|
|
if (it->item.button == button) {
|
|
|
|
|
LOG_WARN("multiple button press events for button %d "
|
|
|
|
|
"(compositor bug?)", button);
|
|
|
|
|
tll_remove(seat->mouse.buttons, it);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-12 19:05:24 +01:00
|
|
|
#if defined(_DEBUG)
|
|
|
|
|
tll_foreach(seat->mouse.buttons, it)
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(it->item.button != button);
|
2020-12-12 19:05:24 +01:00
|
|
|
#endif
|
|
|
|
|
|
2021-01-04 18:32:00 +01:00
|
|
|
/*
|
|
|
|
|
* Remember which surface "owns" this button, so that we can
|
|
|
|
|
* send motion and button release events to that surface, even
|
|
|
|
|
* if the pointer is no longer over it.
|
|
|
|
|
*/
|
2020-12-12 19:05:24 +01:00
|
|
|
tll_push_back(
|
|
|
|
|
seat->mouse.buttons,
|
|
|
|
|
((struct button_tracker){
|
|
|
|
|
.button = button,
|
|
|
|
|
.surf_kind = term->active_surface,
|
|
|
|
|
.send_to_client = false}));
|
|
|
|
|
|
2020-07-08 16:45:26 +02:00
|
|
|
seat->mouse.last_time = now;
|
2020-02-29 11:55:43 +01:00
|
|
|
|
2020-12-12 19:05:24 +01:00
|
|
|
surf_kind = term->active_surface;
|
|
|
|
|
send_to_client = false; /* For now, may be set to true if a binding consumes the button */
|
|
|
|
|
} else {
|
|
|
|
|
bool UNUSED have_button = false;
|
|
|
|
|
tll_foreach(seat->mouse.buttons, it) {
|
|
|
|
|
if (it->item.button == button) {
|
|
|
|
|
have_button = true;
|
|
|
|
|
surf_kind = it->item.surf_kind;
|
|
|
|
|
send_to_client = it->item.send_to_client;
|
|
|
|
|
tll_remove(seat->mouse.buttons, it);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-01-04 18:32:00 +01:00
|
|
|
|
2023-08-19 23:01:53 +08:00
|
|
|
if (seat->wl_touch != NULL && seat->touch.state == TOUCH_STATE_INHIBITED) {
|
|
|
|
|
if (tll_length(seat->mouse.buttons) == 0) {
|
|
|
|
|
seat->touch.state = TOUCH_STATE_IDLE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-04 18:32:00 +01:00
|
|
|
if (!have_button) {
|
|
|
|
|
/*
|
|
|
|
|
* Seen on Sway with slurp
|
|
|
|
|
*
|
|
|
|
|
* 1. Run slurp
|
|
|
|
|
* 2. Press, and hold left mouse button
|
|
|
|
|
* 3. Press escape, to cancel slurp
|
|
|
|
|
* 4. Release mouse button
|
|
|
|
|
* 5. BAM!
|
|
|
|
|
*/
|
2021-09-06 18:12:45 +02:00
|
|
|
LOG_WARN("stray button release event (compositor bug?)");
|
2021-01-04 18:32:00 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-12 19:05:24 +01:00
|
|
|
seat->mouse.last_released_button = button;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (surf_kind) {
|
2020-02-25 20:31:13 +01:00
|
|
|
case TERM_SURF_TITLE:
|
2020-02-29 11:56:16 +01:00
|
|
|
if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
|
|
|
|
|
|
2020-02-29 12:52:55 +01:00
|
|
|
struct wl_window *win = term->window;
|
|
|
|
|
|
2020-02-29 11:56:16 +01:00
|
|
|
/* Toggle maximized state on double-click */
|
2023-07-14 12:03:35 +02:00
|
|
|
if (term->conf->csd.double_click_to_maximize &&
|
|
|
|
|
button == BTN_LEFT &&
|
|
|
|
|
seat->mouse.count == 2)
|
|
|
|
|
{
|
2020-02-29 12:52:55 +01:00
|
|
|
if (win->is_maximized)
|
|
|
|
|
xdg_toplevel_unset_maximized(win->xdg_toplevel);
|
2020-02-29 11:56:16 +01:00
|
|
|
else
|
2020-02-29 12:52:55 +01:00
|
|
|
xdg_toplevel_set_maximized(win->xdg_toplevel);
|
2020-02-26 13:23:11 +01:00
|
|
|
}
|
2020-02-29 11:56:16 +01:00
|
|
|
|
2021-01-04 18:32:22 +01:00
|
|
|
else if (button == BTN_LEFT && win->csd.move_timeout_fd < 0) {
|
2020-02-29 12:52:55 +01:00
|
|
|
const struct itimerspec timeout = {
|
2020-03-01 13:20:07 +01:00
|
|
|
.it_value = {.tv_nsec = 200000000},
|
2020-02-29 12:52:55 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
int fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK);
|
|
|
|
|
if (fd >= 0 &&
|
|
|
|
|
timerfd_settime(fd, 0, &timeout, NULL) == 0 &&
|
2020-07-08 16:45:26 +02:00
|
|
|
fdm_add(wayl->fdm, fd, EPOLLIN, &fdm_csd_move, seat))
|
2020-02-29 12:52:55 +01:00
|
|
|
{
|
|
|
|
|
win->csd.move_timeout_fd = fd;
|
|
|
|
|
win->csd.serial = serial;
|
|
|
|
|
} else {
|
|
|
|
|
LOG_ERRNO("failed to configure XDG toplevel move timer FD");
|
2021-01-04 18:32:22 +01:00
|
|
|
if (fd >= 0)
|
|
|
|
|
close(fd);
|
2020-02-29 12:52:55 +01:00
|
|
|
}
|
|
|
|
|
}
|
2021-11-19 15:08:46 +01:00
|
|
|
|
|
|
|
|
if (button == BTN_RIGHT && tll_length(seat->mouse.buttons) == 1) {
|
2021-11-30 22:15:13 +01:00
|
|
|
const struct csd_data info = get_csd_data(term, CSD_SURF_TITLE);
|
2021-11-19 15:08:46 +01:00
|
|
|
xdg_toplevel_show_window_menu(
|
|
|
|
|
win->xdg_toplevel,
|
|
|
|
|
seat->wl_seat,
|
|
|
|
|
seat->pointer.serial,
|
|
|
|
|
seat->mouse.x + info.x, seat->mouse.y + info.y);
|
|
|
|
|
}
|
2020-02-26 13:23:11 +01:00
|
|
|
}
|
2020-02-29 15:38:04 +01:00
|
|
|
|
|
|
|
|
else if (state == WL_POINTER_BUTTON_STATE_RELEASED) {
|
|
|
|
|
struct wl_window *win = term->window;
|
2021-01-04 18:32:22 +01:00
|
|
|
if (win->csd.move_timeout_fd >= 0) {
|
2020-02-29 15:38:04 +01:00
|
|
|
fdm_del(wayl->fdm, win->csd.move_timeout_fd);
|
|
|
|
|
win->csd.move_timeout_fd = -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-02-25 20:31:13 +01:00
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
case TERM_SURF_BORDER_LEFT:
|
|
|
|
|
case TERM_SURF_BORDER_RIGHT:
|
|
|
|
|
case TERM_SURF_BORDER_TOP:
|
|
|
|
|
case TERM_SURF_BORDER_BOTTOM: {
|
2020-02-29 09:32:22 +01:00
|
|
|
if (button == BTN_LEFT && state == WL_POINTER_BUTTON_STATE_PRESSED) {
|
wayland+input: add support for toplevel edge constraints
Edge constraints are new (not yet available in a wayland-protocols
release) toplevel states, acting as a complement to the existing tiled
states.
Tiled tells us we shouldn't draw shadows etc *outside our window
geometry*.
Constrained tells us the window cannot be resized in the constrained
direction.
This patch does a couple of things:
* Recognize the new states when debug logging
* Change is_top_left() etc to look at the new constrained state
instead of the tiled state. These functions are used when both
choosing cursor shape, and when determining if/how to resize a
window on a CSD edge click-and-drag.
* Update cursor shape selection to use the default (left_ptr) shape
when on a constrained edge (or corner).
* Update CSD resize triggering, to not trigger a resize when attempted
on a constrained edge (or corner).
See
https://gitlab.freedesktop.org/wayland/wayland-protocols/-/commit/86750c99ed062c306e837f11bb9492df572ad677:
An edge constraint is an complementery state to the tiled state,
meaning that it's not only tiled, but constrained in a way that it
can't resize in that direction.
This typically means that the constrained edge is tiled against a
monitor edge. An example configuration is two windows tiled next
to each other on a single monitor. Together they cover the whole
work area.
The left window would have the following tiled and edge constraint
state:
[ tiled_top, tiled_right, tiled_bottom, tiled_left,
constrained_top, constrained_bottom, constrained_left ]
while the right window would have the following:
[ tiled_top, tiled_right, tiled_bottom, tiled_left,
constrained_top, constrained_bottom, constrained_right ]
This aims to replace and deprecate the
`gtk_surface1.configure_edges` event and the
`gtk_surface1.edge_constraint` enum.
2025-04-07 13:32:30 +02:00
|
|
|
enum xdg_toplevel_resize_edge resize_type = XDG_TOPLEVEL_RESIZE_EDGE_NONE;
|
2020-02-29 09:32:22 +01:00
|
|
|
|
2020-07-08 16:45:26 +02:00
|
|
|
int x = seat->mouse.x;
|
|
|
|
|
int y = seat->mouse.y;
|
2020-02-29 09:32:22 +01:00
|
|
|
|
|
|
|
|
if (is_top_left(term, x, y))
|
|
|
|
|
resize_type = XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT;
|
|
|
|
|
else if (is_top_right(term, x, y))
|
|
|
|
|
resize_type = XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT;
|
|
|
|
|
else if (is_bottom_left(term, x, y))
|
|
|
|
|
resize_type = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT;
|
|
|
|
|
else if (is_bottom_right(term, x, y))
|
|
|
|
|
resize_type = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT;
|
wayland+input: add support for toplevel edge constraints
Edge constraints are new (not yet available in a wayland-protocols
release) toplevel states, acting as a complement to the existing tiled
states.
Tiled tells us we shouldn't draw shadows etc *outside our window
geometry*.
Constrained tells us the window cannot be resized in the constrained
direction.
This patch does a couple of things:
* Recognize the new states when debug logging
* Change is_top_left() etc to look at the new constrained state
instead of the tiled state. These functions are used when both
choosing cursor shape, and when determining if/how to resize a
window on a CSD edge click-and-drag.
* Update cursor shape selection to use the default (left_ptr) shape
when on a constrained edge (or corner).
* Update CSD resize triggering, to not trigger a resize when attempted
on a constrained edge (or corner).
See
https://gitlab.freedesktop.org/wayland/wayland-protocols/-/commit/86750c99ed062c306e837f11bb9492df572ad677:
An edge constraint is an complementery state to the tiled state,
meaning that it's not only tiled, but constrained in a way that it
can't resize in that direction.
This typically means that the constrained edge is tiled against a
monitor edge. An example configuration is two windows tiled next
to each other on a single monitor. Together they cover the whole
work area.
The left window would have the following tiled and edge constraint
state:
[ tiled_top, tiled_right, tiled_bottom, tiled_left,
constrained_top, constrained_bottom, constrained_left ]
while the right window would have the following:
[ tiled_top, tiled_right, tiled_bottom, tiled_left,
constrained_top, constrained_bottom, constrained_right ]
This aims to replace and deprecate the
`gtk_surface1.configure_edges` event and the
`gtk_surface1.edge_constraint` enum.
2025-04-07 13:32:30 +02:00
|
|
|
else {
|
|
|
|
|
if (term->active_surface == TERM_SURF_BORDER_LEFT &&
|
|
|
|
|
!term->window->is_constrained_left)
|
|
|
|
|
{
|
|
|
|
|
resize_type = XDG_TOPLEVEL_RESIZE_EDGE_LEFT;
|
|
|
|
|
}
|
2020-02-29 09:32:22 +01:00
|
|
|
|
wayland+input: add support for toplevel edge constraints
Edge constraints are new (not yet available in a wayland-protocols
release) toplevel states, acting as a complement to the existing tiled
states.
Tiled tells us we shouldn't draw shadows etc *outside our window
geometry*.
Constrained tells us the window cannot be resized in the constrained
direction.
This patch does a couple of things:
* Recognize the new states when debug logging
* Change is_top_left() etc to look at the new constrained state
instead of the tiled state. These functions are used when both
choosing cursor shape, and when determining if/how to resize a
window on a CSD edge click-and-drag.
* Update cursor shape selection to use the default (left_ptr) shape
when on a constrained edge (or corner).
* Update CSD resize triggering, to not trigger a resize when attempted
on a constrained edge (or corner).
See
https://gitlab.freedesktop.org/wayland/wayland-protocols/-/commit/86750c99ed062c306e837f11bb9492df572ad677:
An edge constraint is an complementery state to the tiled state,
meaning that it's not only tiled, but constrained in a way that it
can't resize in that direction.
This typically means that the constrained edge is tiled against a
monitor edge. An example configuration is two windows tiled next
to each other on a single monitor. Together they cover the whole
work area.
The left window would have the following tiled and edge constraint
state:
[ tiled_top, tiled_right, tiled_bottom, tiled_left,
constrained_top, constrained_bottom, constrained_left ]
while the right window would have the following:
[ tiled_top, tiled_right, tiled_bottom, tiled_left,
constrained_top, constrained_bottom, constrained_right ]
This aims to replace and deprecate the
`gtk_surface1.configure_edges` event and the
`gtk_surface1.edge_constraint` enum.
2025-04-07 13:32:30 +02:00
|
|
|
else if (term->active_surface == TERM_SURF_BORDER_RIGHT &&
|
|
|
|
|
!term->window->is_constrained_right)
|
|
|
|
|
{
|
|
|
|
|
resize_type = XDG_TOPLEVEL_RESIZE_EDGE_RIGHT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else if (term->active_surface == TERM_SURF_BORDER_TOP &&
|
|
|
|
|
!term->window->is_constrained_top)
|
|
|
|
|
{
|
|
|
|
|
resize_type = XDG_TOPLEVEL_RESIZE_EDGE_TOP;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else if (term->active_surface == TERM_SURF_BORDER_BOTTOM &&
|
|
|
|
|
!term->window->is_constrained_bottom)
|
|
|
|
|
{
|
|
|
|
|
resize_type = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (resize_type != XDG_TOPLEVEL_RESIZE_EDGE_NONE) {
|
|
|
|
|
xdg_toplevel_resize(
|
|
|
|
|
term->window->xdg_toplevel, seat->wl_seat, serial, resize_type);
|
|
|
|
|
}
|
2020-02-29 09:32:22 +01:00
|
|
|
}
|
2020-02-25 20:31:13 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-02 20:29:28 +01:00
|
|
|
case TERM_SURF_BUTTON_MINIMIZE:
|
2024-08-02 17:12:12 +02:00
|
|
|
if (button == BTN_LEFT &&
|
2024-08-09 08:20:59 +02:00
|
|
|
pointer_is_on_button(term, seat, CSD_SURF_MINIMIZE) &&
|
2024-08-02 17:12:12 +02:00
|
|
|
state == WL_POINTER_BUTTON_STATE_RELEASED)
|
|
|
|
|
{
|
2020-03-02 20:29:28 +01:00
|
|
|
xdg_toplevel_set_minimized(term->window->xdg_toplevel);
|
2024-08-02 17:12:12 +02:00
|
|
|
}
|
2020-03-02 20:29:28 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case TERM_SURF_BUTTON_MAXIMIZE:
|
2024-08-02 17:12:12 +02:00
|
|
|
if (button == BTN_LEFT &&
|
2024-08-09 08:20:59 +02:00
|
|
|
pointer_is_on_button(term, seat, CSD_SURF_MAXIMIZE) &&
|
2024-08-02 17:12:12 +02:00
|
|
|
state == WL_POINTER_BUTTON_STATE_RELEASED)
|
|
|
|
|
{
|
2020-03-02 20:29:28 +01:00
|
|
|
if (term->window->is_maximized)
|
|
|
|
|
xdg_toplevel_unset_maximized(term->window->xdg_toplevel);
|
|
|
|
|
else
|
|
|
|
|
xdg_toplevel_set_maximized(term->window->xdg_toplevel);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case TERM_SURF_BUTTON_CLOSE:
|
2024-08-02 17:12:12 +02:00
|
|
|
if (button == BTN_LEFT &&
|
2024-08-09 08:20:59 +02:00
|
|
|
pointer_is_on_button(term, seat, CSD_SURF_CLOSE) &&
|
2024-08-02 17:12:12 +02:00
|
|
|
state == WL_POINTER_BUTTON_STATE_RELEASED)
|
|
|
|
|
{
|
2020-03-02 20:29:28 +01:00
|
|
|
term_shutdown(term);
|
2024-08-02 17:12:12 +02:00
|
|
|
}
|
2020-03-02 20:29:28 +01:00
|
|
|
break;
|
|
|
|
|
|
2020-02-29 15:29:00 +01:00
|
|
|
case TERM_SURF_GRID: {
|
|
|
|
|
search_cancel(term);
|
2021-03-04 08:57:03 +01:00
|
|
|
urls_reset(term);
|
2020-02-29 15:29:00 +01:00
|
|
|
|
2020-08-07 19:59:03 +02:00
|
|
|
bool cursor_is_on_grid = seat->mouse.col >= 0 && seat->mouse.row >= 0;
|
|
|
|
|
|
2020-02-29 15:29:00 +01:00
|
|
|
switch (state) {
|
|
|
|
|
case WL_POINTER_BUTTON_STATE_PRESSED: {
|
2020-12-12 19:05:24 +01:00
|
|
|
bool consumed = false;
|
2020-09-07 19:35:25 +02:00
|
|
|
|
2021-02-02 09:52:05 +01:00
|
|
|
if (cursor_is_on_grid && term_mouse_grabbed(term, seat)) {
|
2023-09-18 16:36:39 +02:00
|
|
|
const struct key_binding *match =
|
|
|
|
|
match_mouse_binding(seat, term, button);
|
2020-09-07 19:35:25 +02:00
|
|
|
|
2023-09-18 16:36:39 +02:00
|
|
|
if (match != NULL)
|
|
|
|
|
consumed = execute_binding(seat, term, match, serial, 1);
|
2020-10-13 08:02:09 +02:00
|
|
|
}
|
|
|
|
|
|
2020-12-12 19:05:24 +01:00
|
|
|
send_to_client = !consumed && cursor_is_on_grid;
|
|
|
|
|
|
|
|
|
|
if (send_to_client)
|
|
|
|
|
tll_back(seat->mouse.buttons).send_to_client = true;
|
|
|
|
|
|
|
|
|
|
if (send_to_client &&
|
2020-10-13 08:02:09 +02:00
|
|
|
!term_mouse_grabbed(term, seat) &&
|
|
|
|
|
cursor_is_on_grid)
|
|
|
|
|
{
|
|
|
|
|
term_mouse_down(
|
|
|
|
|
term, button, seat->mouse.row, seat->mouse.col,
|
2021-12-30 05:13:45 -06:00
|
|
|
seat->mouse.y - term->margins.top,
|
|
|
|
|
seat->mouse.x - term->margins.left,
|
2020-10-13 08:02:09 +02:00
|
|
|
seat->kbd.shift, seat->kbd.alt, seat->kbd.ctrl);
|
2020-07-09 09:52:11 +02:00
|
|
|
}
|
2020-02-29 15:29:00 +01:00
|
|
|
break;
|
|
|
|
|
}
|
2019-08-06 19:32:06 +02:00
|
|
|
|
2020-02-29 15:29:00 +01:00
|
|
|
case WL_POINTER_BUTTON_STATE_RELEASED:
|
2020-08-11 09:55:33 +02:00
|
|
|
selection_finalize(seat, term, serial);
|
2019-08-06 19:32:06 +02:00
|
|
|
|
2020-12-12 19:05:24 +01:00
|
|
|
if (send_to_client && !term_mouse_grabbed(term, seat)) {
|
2020-07-09 09:52:11 +02:00
|
|
|
term_mouse_up(
|
|
|
|
|
term, button, seat->mouse.row, seat->mouse.col,
|
2021-12-30 05:13:45 -06:00
|
|
|
seat->mouse.y - term->margins.top,
|
|
|
|
|
seat->mouse.x - term->margins.left,
|
2020-07-09 09:52:11 +02:00
|
|
|
seat->kbd.shift, seat->kbd.alt, seat->kbd.ctrl);
|
|
|
|
|
}
|
2020-02-29 15:29:00 +01:00
|
|
|
break;
|
2019-07-11 16:42:59 +02:00
|
|
|
}
|
2019-07-05 14:24:51 +02:00
|
|
|
break;
|
2019-07-17 21:30:57 +02:00
|
|
|
}
|
2019-07-05 14:24:51 +02:00
|
|
|
|
2020-02-29 15:29:00 +01:00
|
|
|
case TERM_SURF_NONE:
|
2021-02-10 09:01:51 +00:00
|
|
|
BUG("Invalid surface type");
|
2019-07-05 14:24:51 +02:00
|
|
|
break;
|
2020-02-29 15:29:00 +01:00
|
|
|
|
2019-07-05 14:24:51 +02:00
|
|
|
}
|
2019-07-05 10:44:57 +02:00
|
|
|
}
|
|
|
|
|
|
2020-09-15 18:56:22 +02:00
|
|
|
static void
|
|
|
|
|
alternate_scroll(struct seat *seat, int amount, int button)
|
|
|
|
|
{
|
|
|
|
|
if (seat->wl_keyboard == NULL)
|
|
|
|
|
return;
|
|
|
|
|
|
2020-11-03 19:44:51 +01:00
|
|
|
/* Should be cleared in leave event */
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(seat->mouse_focus != NULL);
|
2020-11-03 19:44:51 +01:00
|
|
|
struct terminal *term = seat->mouse_focus;
|
|
|
|
|
|
2021-10-25 19:39:46 +02:00
|
|
|
assert(button == BTN_BACK || button == BTN_FORWARD);
|
|
|
|
|
|
2020-09-15 18:56:22 +02:00
|
|
|
xkb_keycode_t key = button == BTN_BACK
|
|
|
|
|
? seat->kbd.key_arrow_up : seat->kbd.key_arrow_down;
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < amount; i++)
|
2020-11-03 19:44:51 +01:00
|
|
|
key_press_release(seat, term, seat->kbd.serial, key, XKB_KEY_DOWN);
|
|
|
|
|
key_press_release(seat, term, seat->kbd.serial, key, XKB_KEY_UP);
|
2020-09-15 18:56:22 +02:00
|
|
|
}
|
|
|
|
|
|
2019-07-26 18:47:56 +02:00
|
|
|
static void
|
2021-08-05 18:34:09 +02:00
|
|
|
mouse_scroll(struct seat *seat, int amount, enum wl_pointer_axis axis)
|
2019-07-26 18:47:56 +02:00
|
|
|
{
|
2020-07-08 16:45:26 +02:00
|
|
|
struct terminal *term = seat->mouse_focus;
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(term != NULL);
|
2019-10-27 18:43:07 +01:00
|
|
|
|
2021-08-05 18:34:09 +02:00
|
|
|
int button = axis == WL_POINTER_AXIS_VERTICAL_SCROLL
|
input: don't map wheel events to BTN_{BACK,FORWARD}
BTN_BACK and BTN_FORWARD are separate buttons. The scroll wheel don't
have any button mappings in libinput/wayland, so make up our own
defines.
This allows us to map them in mouse bindings.
Also expose BTN_WHEEL_{LEFT,RIGHT}. These were already defined, and
used, internally, to handle wheel tilt events. With this, they can
also be used in mouse bindings.
Finally, fix encoding used for BTN_{BACK,FORWARD} when sending mouse
button events to the client application. Before this, they were mapped
to buttons 4/5. But, button 4/5 are for the scroll wheel, and as
mentioned above, BTN_{BACK,FORWARD} are not the same as scroll wheel
"buttons".
Closes #1763
2024-07-13 10:24:11 +02:00
|
|
|
? amount < 0 ? BTN_WHEEL_BACK : BTN_WHEEL_FORWARD
|
2021-08-05 18:34:09 +02:00
|
|
|
: amount < 0 ? BTN_WHEEL_LEFT : BTN_WHEEL_RIGHT;
|
2019-07-26 18:47:56 +02:00
|
|
|
amount = abs(amount);
|
|
|
|
|
|
2021-10-25 19:39:46 +02:00
|
|
|
if (term_mouse_grabbed(term, seat)) {
|
2023-09-18 16:36:39 +02:00
|
|
|
seat->mouse.count = 1;
|
2021-10-25 19:39:46 +02:00
|
|
|
|
2023-09-18 16:36:39 +02:00
|
|
|
const struct key_binding *match =
|
|
|
|
|
match_mouse_binding(seat, term, button);
|
|
|
|
|
|
|
|
|
|
if (match != NULL)
|
|
|
|
|
execute_binding(seat, term, match, seat->pointer.serial, amount);
|
|
|
|
|
|
|
|
|
|
seat->mouse.last_released_button = button;
|
2021-10-25 19:39:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else if (seat->mouse.col >= 0 && seat->mouse.row >= 0) {
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(seat->mouse.col < term->cols);
|
|
|
|
|
xassert(seat->mouse.row < term->rows);
|
2020-08-07 22:07:16 +02:00
|
|
|
|
2020-09-15 18:56:22 +02:00
|
|
|
for (int i = 0; i < amount; i++) {
|
|
|
|
|
term_mouse_down(
|
2020-07-08 18:16:43 +02:00
|
|
|
term, button, seat->mouse.row, seat->mouse.col,
|
2021-12-30 05:13:45 -06:00
|
|
|
seat->mouse.y - term->margins.top,
|
|
|
|
|
seat->mouse.x - term->margins.left,
|
2020-07-08 18:16:43 +02:00
|
|
|
seat->kbd.shift, seat->kbd.alt, seat->kbd.ctrl);
|
2020-09-15 18:56:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
term_mouse_up(
|
|
|
|
|
term, button, seat->mouse.row, seat->mouse.col,
|
2021-12-30 05:13:45 -06:00
|
|
|
seat->mouse.y - term->margins.top,
|
|
|
|
|
seat->mouse.x - term->margins.left,
|
2020-09-15 18:56:22 +02:00
|
|
|
seat->kbd.shift, seat->kbd.alt, seat->kbd.ctrl);
|
2019-08-19 21:16:47 +02:00
|
|
|
}
|
2019-07-26 18:47:56 +02:00
|
|
|
}
|
|
|
|
|
|
2024-06-15 10:17:01 +02:00
|
|
|
static double
|
input: apply scrollback.multplier in alterate scroll mode
Before this patch, foot only applied [scrollback].multiplier on the
normal screen, never the alt screen.
However, scrolling can be done in two ways on the alt screen:
If the application has enabled mouse support, we simply pass on the
mouse scroll events to the application. Here, it makes sense to not
apply the multiplier, and instead let the application choose how much
to scroll for each scroll event.
But, if the application has not enabled mouse support, we can still
scroll by simulating the arrow keys being pressed - alternate
scrolling (private mode 1007).
This is enabled by default in foot (but can be disabled in foot.ini
with the [mouse].alternate-scroll-mode setting).
In this mode, it makes more sense to apply the multiplier. And that’s
what this patch changes - the multiplier is now applied, on the alt
screen, when the application has not enabled mouse support, and
alternate scrolling has been enabled in foot.
Closes #859
2021-12-24 15:22:14 +01:00
|
|
|
mouse_scroll_multiplier(const struct terminal *term, const struct seat *seat)
|
2021-11-08 18:41:42 +01:00
|
|
|
{
|
input: apply scrollback.multplier in alterate scroll mode
Before this patch, foot only applied [scrollback].multiplier on the
normal screen, never the alt screen.
However, scrolling can be done in two ways on the alt screen:
If the application has enabled mouse support, we simply pass on the
mouse scroll events to the application. Here, it makes sense to not
apply the multiplier, and instead let the application choose how much
to scroll for each scroll event.
But, if the application has not enabled mouse support, we can still
scroll by simulating the arrow keys being pressed - alternate
scrolling (private mode 1007).
This is enabled by default in foot (but can be disabled in foot.ini
with the [mouse].alternate-scroll-mode setting).
In this mode, it makes more sense to apply the multiplier. And that’s
what this patch changes - the multiplier is now applied, on the alt
screen, when the application has not enabled mouse support, and
alternate scrolling has been enabled in foot.
Closes #859
2021-12-24 15:22:14 +01:00
|
|
|
return (term->grid == &term->normal ||
|
|
|
|
|
(term_mouse_grabbed(term, seat) && term->alt_scrolling))
|
2021-11-08 18:41:42 +01:00
|
|
|
? term->conf->scrollback.multiplier
|
|
|
|
|
: 1.0;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-05 10:44:57 +02:00
|
|
|
static void
|
|
|
|
|
wl_pointer_axis(void *data, struct wl_pointer *wl_pointer,
|
|
|
|
|
uint32_t time, uint32_t axis, wl_fixed_t value)
|
|
|
|
|
{
|
2020-07-08 16:45:26 +02:00
|
|
|
struct seat *seat = data;
|
2019-10-27 18:43:07 +01:00
|
|
|
|
2023-08-19 23:01:53 +08:00
|
|
|
if (touch_is_active(seat))
|
2023-07-05 00:19:21 +08:00
|
|
|
return;
|
|
|
|
|
|
2020-07-08 16:45:26 +02:00
|
|
|
if (seat->mouse.have_discrete)
|
2019-07-26 18:47:56 +02:00
|
|
|
return;
|
2019-07-10 09:15:37 +02:00
|
|
|
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(seat->mouse_focus != NULL);
|
2021-08-05 18:34:09 +02:00
|
|
|
xassert(axis < ALEN(seat->mouse.aggregated));
|
2020-09-27 11:11:45 +02:00
|
|
|
|
input: apply scrollback.multplier in alterate scroll mode
Before this patch, foot only applied [scrollback].multiplier on the
normal screen, never the alt screen.
However, scrolling can be done in two ways on the alt screen:
If the application has enabled mouse support, we simply pass on the
mouse scroll events to the application. Here, it makes sense to not
apply the multiplier, and instead let the application choose how much
to scroll for each scroll event.
But, if the application has not enabled mouse support, we can still
scroll by simulating the arrow keys being pressed - alternate
scrolling (private mode 1007).
This is enabled by default in foot (but can be disabled in foot.ini
with the [mouse].alternate-scroll-mode setting).
In this mode, it makes more sense to apply the multiplier. And that’s
what this patch changes - the multiplier is now applied, on the alt
screen, when the application has not enabled mouse support, and
alternate scrolling has been enabled in foot.
Closes #859
2021-12-24 15:22:14 +01:00
|
|
|
const struct terminal *term = seat->mouse_focus;
|
|
|
|
|
|
2020-04-10 18:43:29 +02:00
|
|
|
/*
|
|
|
|
|
* Aggregate scrolled amount until we get at least 1.0
|
|
|
|
|
*
|
|
|
|
|
* Without this, very slow scrolling will never actually scroll
|
|
|
|
|
* anything.
|
|
|
|
|
*/
|
2021-11-08 18:41:42 +01:00
|
|
|
seat->mouse.aggregated[axis] +=
|
input: apply scrollback.multplier in alterate scroll mode
Before this patch, foot only applied [scrollback].multiplier on the
normal screen, never the alt screen.
However, scrolling can be done in two ways on the alt screen:
If the application has enabled mouse support, we simply pass on the
mouse scroll events to the application. Here, it makes sense to not
apply the multiplier, and instead let the application choose how much
to scroll for each scroll event.
But, if the application has not enabled mouse support, we can still
scroll by simulating the arrow keys being pressed - alternate
scrolling (private mode 1007).
This is enabled by default in foot (but can be disabled in foot.ini
with the [mouse].alternate-scroll-mode setting).
In this mode, it makes more sense to apply the multiplier. And that’s
what this patch changes - the multiplier is now applied, on the alt
screen, when the application has not enabled mouse support, and
alternate scrolling has been enabled in foot.
Closes #859
2021-12-24 15:22:14 +01:00
|
|
|
mouse_scroll_multiplier(term, seat) * wl_fixed_to_double(value);
|
2021-08-05 18:34:09 +02:00
|
|
|
if (fabs(seat->mouse.aggregated[axis]) < seat->mouse_focus->cell_height)
|
2020-09-27 11:11:45 +02:00
|
|
|
return;
|
|
|
|
|
|
2021-08-05 18:34:09 +02:00
|
|
|
int lines = seat->mouse.aggregated[axis] / seat->mouse_focus->cell_height;
|
|
|
|
|
mouse_scroll(seat, lines, axis);
|
|
|
|
|
seat->mouse.aggregated[axis] -= (double)lines * seat->mouse_focus->cell_height;
|
2019-07-26 18:47:56 +02:00
|
|
|
}
|
2019-07-10 09:15:37 +02:00
|
|
|
|
2019-07-26 18:47:56 +02:00
|
|
|
static void
|
|
|
|
|
wl_pointer_axis_discrete(void *data, struct wl_pointer *wl_pointer,
|
2024-06-15 10:17:01 +02:00
|
|
|
enum wl_pointer_axis axis, int32_t discrete)
|
2019-07-26 18:47:56 +02:00
|
|
|
{
|
2024-06-15 10:17:01 +02:00
|
|
|
LOG_DBG("axis_discrete: %d", discrete);
|
2020-07-08 16:45:26 +02:00
|
|
|
struct seat *seat = data;
|
2023-07-05 00:19:21 +08:00
|
|
|
|
2023-08-19 23:01:53 +08:00
|
|
|
if (touch_is_active(seat))
|
2023-07-05 00:19:21 +08:00
|
|
|
return;
|
|
|
|
|
|
2020-07-08 16:45:26 +02:00
|
|
|
seat->mouse.have_discrete = true;
|
2021-08-05 18:34:09 +02:00
|
|
|
int amount = discrete;
|
|
|
|
|
|
|
|
|
|
if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) {
|
|
|
|
|
/* Treat mouse wheel left/right as regular buttons */
|
|
|
|
|
} else
|
input: apply scrollback.multplier in alterate scroll mode
Before this patch, foot only applied [scrollback].multiplier on the
normal screen, never the alt screen.
However, scrolling can be done in two ways on the alt screen:
If the application has enabled mouse support, we simply pass on the
mouse scroll events to the application. Here, it makes sense to not
apply the multiplier, and instead let the application choose how much
to scroll for each scroll event.
But, if the application has not enabled mouse support, we can still
scroll by simulating the arrow keys being pressed - alternate
scrolling (private mode 1007).
This is enabled by default in foot (but can be disabled in foot.ini
with the [mouse].alternate-scroll-mode setting).
In this mode, it makes more sense to apply the multiplier. And that’s
what this patch changes - the multiplier is now applied, on the alt
screen, when the application has not enabled mouse support, and
alternate scrolling has been enabled in foot.
Closes #859
2021-12-24 15:22:14 +01:00
|
|
|
amount *= mouse_scroll_multiplier(seat->mouse_focus, seat);
|
2021-08-05 18:34:09 +02:00
|
|
|
|
|
|
|
|
mouse_scroll(seat, amount, axis);
|
2019-07-05 10:44:57 +02:00
|
|
|
}
|
|
|
|
|
|
2024-06-15 10:17:01 +02:00
|
|
|
#if defined(WL_POINTER_AXIS_VALUE120_SINCE_VERSION)
|
|
|
|
|
static void
|
|
|
|
|
wl_pointer_axis_value120(void *data, struct wl_pointer *wl_pointer,
|
|
|
|
|
enum wl_pointer_axis axis, int32_t value120)
|
|
|
|
|
{
|
|
|
|
|
LOG_DBG("axis_value120: %d -> %.2f", value120, (float)value120 / 120.);
|
|
|
|
|
|
|
|
|
|
struct seat *seat = data;
|
|
|
|
|
|
|
|
|
|
if (touch_is_active(seat))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
seat->mouse.have_discrete = true;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* 120 corresponds to a single "low-res" scroll step.
|
|
|
|
|
*
|
|
|
|
|
* When doing high-res scrolling, take the scrollback.multiplier,
|
|
|
|
|
* and calculate how many degrees there are per line.
|
|
|
|
|
*
|
|
|
|
|
* For example, with scrollback.multiplier = 3, we have 120 / 3 == 40.
|
|
|
|
|
*
|
|
|
|
|
* Then, accumulate high-res scroll events, until we have *at
|
|
|
|
|
* least* that much. Translate the accumulated value to number of
|
|
|
|
|
* lines, and scroll.
|
|
|
|
|
*
|
|
|
|
|
* Subtract the "used" degrees from the accumulated value, and
|
|
|
|
|
* keep what's left (this value will always be less than the
|
|
|
|
|
* per-line value).
|
|
|
|
|
*/
|
|
|
|
|
const double multiplier = mouse_scroll_multiplier(seat->mouse_focus, seat);
|
|
|
|
|
const double per_line = 120. / multiplier;
|
|
|
|
|
|
|
|
|
|
seat->mouse.aggregated_120[axis] += (double)value120;
|
|
|
|
|
|
|
|
|
|
if (fabs(seat->mouse.aggregated_120[axis]) < per_line)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
int lines = (int)(seat->mouse.aggregated_120[axis] / per_line);
|
|
|
|
|
mouse_scroll(seat, lines, axis);
|
|
|
|
|
seat->mouse.aggregated_120[axis] -= (double)lines * per_line;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2019-07-05 10:44:57 +02:00
|
|
|
static void
|
|
|
|
|
wl_pointer_frame(void *data, struct wl_pointer *wl_pointer)
|
|
|
|
|
{
|
2020-07-08 16:45:26 +02:00
|
|
|
struct seat *seat = data;
|
2023-07-05 00:19:21 +08:00
|
|
|
|
2023-08-19 23:01:53 +08:00
|
|
|
if (touch_is_active(seat))
|
2023-07-05 00:19:21 +08:00
|
|
|
return;
|
|
|
|
|
|
2020-07-08 16:45:26 +02:00
|
|
|
seat->mouse.have_discrete = false;
|
2019-07-05 10:44:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
wl_pointer_axis_source(void *data, struct wl_pointer *wl_pointer,
|
|
|
|
|
uint32_t axis_source)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
wl_pointer_axis_stop(void *data, struct wl_pointer *wl_pointer,
|
|
|
|
|
uint32_t time, uint32_t axis)
|
|
|
|
|
{
|
2020-07-08 16:45:26 +02:00
|
|
|
struct seat *seat = data;
|
2021-08-05 18:34:09 +02:00
|
|
|
|
2023-08-19 23:01:53 +08:00
|
|
|
if (touch_is_active(seat))
|
2023-07-05 00:19:21 +08:00
|
|
|
return;
|
|
|
|
|
|
2021-08-05 18:34:09 +02:00
|
|
|
xassert(axis < ALEN(seat->mouse.aggregated));
|
|
|
|
|
seat->mouse.aggregated[axis] = 0.;
|
2019-07-05 10:44:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const struct wl_pointer_listener pointer_listener = {
|
2024-06-15 10:17:01 +02:00
|
|
|
.enter = &wl_pointer_enter,
|
|
|
|
|
.leave = &wl_pointer_leave,
|
|
|
|
|
.motion = &wl_pointer_motion,
|
|
|
|
|
.button = &wl_pointer_button,
|
|
|
|
|
.axis = &wl_pointer_axis,
|
|
|
|
|
.frame = &wl_pointer_frame,
|
|
|
|
|
.axis_source = &wl_pointer_axis_source,
|
|
|
|
|
.axis_stop = &wl_pointer_axis_stop,
|
|
|
|
|
.axis_discrete = &wl_pointer_axis_discrete,
|
|
|
|
|
#if defined(WL_POINTER_AXIS_VALUE120_SINCE_VERSION)
|
|
|
|
|
.axis_value120 = &wl_pointer_axis_value120,
|
|
|
|
|
#endif
|
2019-07-05 10:44:57 +02:00
|
|
|
};
|
2023-07-05 00:19:21 +08:00
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
touch_to_scroll(struct seat *seat, struct terminal *term,
|
|
|
|
|
wl_fixed_t surface_x, wl_fixed_t surface_y)
|
|
|
|
|
{
|
|
|
|
|
bool coord_updated = false;
|
|
|
|
|
|
|
|
|
|
int y = wl_fixed_to_int(surface_y) * term->scale;
|
|
|
|
|
int rows = (y - seat->mouse.y) / term->cell_height;
|
|
|
|
|
if (rows != 0) {
|
|
|
|
|
mouse_scroll(seat, -rows, WL_POINTER_AXIS_VERTICAL_SCROLL);
|
|
|
|
|
seat->mouse.y += rows * term->cell_height;
|
|
|
|
|
coord_updated = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int x = wl_fixed_to_int(surface_x) * term->scale;
|
|
|
|
|
int cols = (x - seat->mouse.x) / term->cell_width;
|
|
|
|
|
if (cols != 0) {
|
|
|
|
|
mouse_scroll(seat, -cols, WL_POINTER_AXIS_HORIZONTAL_SCROLL);
|
|
|
|
|
seat->mouse.x += cols * term->cell_width;
|
|
|
|
|
coord_updated = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return coord_updated;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
wl_touch_down(void *data, struct wl_touch *wl_touch, uint32_t serial,
|
|
|
|
|
uint32_t time, struct wl_surface *surface, int32_t id,
|
|
|
|
|
wl_fixed_t surface_x, wl_fixed_t surface_y)
|
|
|
|
|
{
|
|
|
|
|
struct seat *seat = data;
|
|
|
|
|
|
|
|
|
|
if (seat->touch.state != TOUCH_STATE_IDLE)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
struct wl_window *win = wl_surface_get_user_data(surface);
|
|
|
|
|
struct terminal *term = win->term;
|
|
|
|
|
|
|
|
|
|
LOG_DBG("touch_down: touch=%p, x=%d, y=%d", (void *)wl_touch,
|
|
|
|
|
wl_fixed_to_int(surface_x), wl_fixed_to_int(surface_y));
|
|
|
|
|
|
|
|
|
|
int x = wl_fixed_to_int(surface_x) * term->scale;
|
|
|
|
|
int y = wl_fixed_to_int(surface_y) * term->scale;
|
|
|
|
|
|
|
|
|
|
seat->mouse.x = x;
|
|
|
|
|
seat->mouse.y = y;
|
|
|
|
|
mouse_coord_pixel_to_cell(seat, term, x, y);
|
|
|
|
|
|
|
|
|
|
seat->touch.state = TOUCH_STATE_HELD;
|
|
|
|
|
seat->touch.serial = serial;
|
|
|
|
|
seat->touch.time = time + term->conf->touch.long_press_delay;
|
|
|
|
|
seat->touch.surface = surface;
|
2023-08-19 23:01:53 +08:00
|
|
|
seat->touch.surface_kind = term_surface_kind(term, surface);
|
2023-07-05 00:19:21 +08:00
|
|
|
seat->touch.id = id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
wl_touch_up(void *data, struct wl_touch *wl_touch, uint32_t serial,
|
|
|
|
|
uint32_t time, int32_t id)
|
|
|
|
|
{
|
|
|
|
|
struct seat *seat = data;
|
|
|
|
|
|
|
|
|
|
if (seat->touch.state <= TOUCH_STATE_IDLE || id != seat->touch.id)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
LOG_DBG("touch_up: touch=%p", (void *)wl_touch);
|
|
|
|
|
|
|
|
|
|
struct wl_window *win = wl_surface_get_user_data(seat->touch.surface);
|
|
|
|
|
struct terminal *term = win->term;
|
|
|
|
|
|
2023-08-19 23:01:53 +08:00
|
|
|
struct terminal *old_term = seat->mouse_focus;
|
|
|
|
|
enum term_surface old_active_surface = term->active_surface;
|
2023-07-05 00:19:21 +08:00
|
|
|
seat->mouse_focus = term;
|
2023-08-19 23:01:53 +08:00
|
|
|
term->active_surface = seat->touch.surface_kind;
|
2023-07-05 00:19:21 +08:00
|
|
|
|
|
|
|
|
switch (seat->touch.state) {
|
|
|
|
|
case TOUCH_STATE_HELD:
|
|
|
|
|
wl_pointer_button(seat, NULL, seat->touch.serial, time, BTN_LEFT,
|
|
|
|
|
WL_POINTER_BUTTON_STATE_PRESSED);
|
|
|
|
|
/* fallthrough */
|
|
|
|
|
case TOUCH_STATE_DRAGGING:
|
|
|
|
|
wl_pointer_button(seat, NULL, serial, time, BTN_LEFT,
|
|
|
|
|
WL_POINTER_BUTTON_STATE_RELEASED);
|
|
|
|
|
/* fallthrough */
|
|
|
|
|
case TOUCH_STATE_SCROLLING:
|
2023-07-16 17:24:55 +08:00
|
|
|
term->active_surface = TERM_SURF_NONE;
|
2023-07-05 00:19:21 +08:00
|
|
|
seat->touch.state = TOUCH_STATE_IDLE;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case TOUCH_STATE_INHIBITED:
|
|
|
|
|
case TOUCH_STATE_IDLE:
|
|
|
|
|
BUG("Bad touch state: %d", seat->touch.state);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-19 23:01:53 +08:00
|
|
|
seat->mouse_focus = old_term;
|
|
|
|
|
term->active_surface = old_active_surface;
|
2023-07-05 00:19:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
wl_touch_motion(void *data, struct wl_touch *wl_touch, uint32_t time,
|
|
|
|
|
int32_t id, wl_fixed_t surface_x, wl_fixed_t surface_y)
|
|
|
|
|
{
|
|
|
|
|
struct seat *seat = data;
|
|
|
|
|
if (seat->touch.state <= TOUCH_STATE_IDLE || id != seat->touch.id)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
LOG_DBG("touch_motion: touch=%p, x=%d, y=%d", (void *)wl_touch,
|
|
|
|
|
wl_fixed_to_int(surface_x), wl_fixed_to_int(surface_y));
|
|
|
|
|
|
|
|
|
|
struct wl_window *win = wl_surface_get_user_data(seat->touch.surface);
|
|
|
|
|
struct terminal *term = win->term;
|
|
|
|
|
|
2023-08-19 23:01:53 +08:00
|
|
|
struct terminal *old_term = seat->mouse_focus;
|
|
|
|
|
enum term_surface old_active_surface = term->active_surface;
|
2023-07-05 00:19:21 +08:00
|
|
|
seat->mouse_focus = term;
|
2023-08-19 23:01:53 +08:00
|
|
|
term->active_surface = seat->touch.surface_kind;
|
2023-07-05 00:19:21 +08:00
|
|
|
|
|
|
|
|
switch (seat->touch.state) {
|
|
|
|
|
case TOUCH_STATE_HELD:
|
2023-07-16 17:24:55 +08:00
|
|
|
if (time <= seat->touch.time && term->active_surface == TERM_SURF_GRID) {
|
2023-07-05 00:19:21 +08:00
|
|
|
if (touch_to_scroll(seat, term, surface_x, surface_y))
|
|
|
|
|
seat->touch.state = TOUCH_STATE_SCROLLING;
|
|
|
|
|
break;
|
|
|
|
|
} else {
|
|
|
|
|
wl_pointer_button(seat, NULL, seat->touch.serial, time, BTN_LEFT,
|
|
|
|
|
WL_POINTER_BUTTON_STATE_PRESSED);
|
|
|
|
|
seat->touch.state = TOUCH_STATE_DRAGGING;
|
|
|
|
|
/* fallthrough */
|
|
|
|
|
}
|
|
|
|
|
case TOUCH_STATE_DRAGGING:
|
|
|
|
|
wl_pointer_motion(seat, NULL, time, surface_x, surface_y);
|
|
|
|
|
break;
|
|
|
|
|
case TOUCH_STATE_SCROLLING:
|
|
|
|
|
touch_to_scroll(seat, term, surface_x, surface_y);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case TOUCH_STATE_INHIBITED:
|
|
|
|
|
case TOUCH_STATE_IDLE:
|
|
|
|
|
BUG("Bad touch state: %d", seat->touch.state);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-19 23:01:53 +08:00
|
|
|
seat->mouse_focus = old_term;
|
|
|
|
|
term->active_surface = old_active_surface;
|
2023-07-05 00:19:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
wl_touch_frame(void *data, struct wl_touch *wl_touch)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
wl_touch_cancel(void *data, struct wl_touch *wl_touch)
|
|
|
|
|
{
|
|
|
|
|
struct seat *seat = data;
|
|
|
|
|
if (seat->touch.state == TOUCH_STATE_INHIBITED)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
seat->touch.state = TOUCH_STATE_IDLE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const struct wl_touch_listener touch_listener = {
|
|
|
|
|
.down = wl_touch_down,
|
|
|
|
|
.up = wl_touch_up,
|
|
|
|
|
.motion = wl_touch_motion,
|
|
|
|
|
.frame = wl_touch_frame,
|
|
|
|
|
.cancel = wl_touch_cancel,
|
|
|
|
|
};
|