mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-04-05 07:15:30 -04:00
Merge branch 'configure-select-override-mods'
This commit is contained in:
commit
ecfff42300
9 changed files with 213 additions and 41 deletions
|
|
@ -37,6 +37,12 @@
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
* `[mouse-bindings].selection-override-modifiers` option, specifying
|
||||||
|
which modifiers to hold to override mouse grabs by client
|
||||||
|
applications and force selection instead.
|
||||||
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
### Deprecated
|
### Deprecated
|
||||||
### Removed
|
### Removed
|
||||||
|
|
|
||||||
196
config.c
196
config.c
|
|
@ -1333,6 +1333,10 @@ parse_section_cursor(struct context *ctx)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
parse_modifiers(struct context *ctx, const char *text, size_t len,
|
||||||
|
struct config_key_modifiers *modifiers);
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
parse_section_mouse(struct context *ctx)
|
parse_section_mouse(struct context *ctx)
|
||||||
{
|
{
|
||||||
|
|
@ -1474,6 +1478,11 @@ parse_modifiers(struct context *ctx, const char *text, size_t len,
|
||||||
bool ret = false;
|
bool ret = false;
|
||||||
|
|
||||||
*modifiers = (struct config_key_modifiers){0};
|
*modifiers = (struct config_key_modifiers){0};
|
||||||
|
|
||||||
|
/* Handle "none" separately because e.g. none+shift is nonsense */
|
||||||
|
if (strncmp(text, "none", len) == 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
char *copy = xstrndup(text, len);
|
char *copy = xstrndup(text, len);
|
||||||
|
|
||||||
for (char *tok_ctx = NULL, *key = strtok_r(copy, "+", &tok_ctx);
|
for (char *tok_ctx = NULL, *key = strtok_r(copy, "+", &tok_ctx);
|
||||||
|
|
@ -1947,6 +1956,31 @@ parse_section_url_bindings(struct context *ctx)
|
||||||
&ctx->conf->bindings.url);
|
&ctx->conf->bindings.url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const struct {
|
||||||
|
const char *name;
|
||||||
|
int code;
|
||||||
|
} button_map[] = {
|
||||||
|
{"BTN_LEFT", BTN_LEFT},
|
||||||
|
{"BTN_RIGHT", BTN_RIGHT},
|
||||||
|
{"BTN_MIDDLE", BTN_MIDDLE},
|
||||||
|
{"BTN_SIDE", BTN_SIDE},
|
||||||
|
{"BTN_EXTRA", BTN_EXTRA},
|
||||||
|
{"BTN_FORWARD", BTN_FORWARD},
|
||||||
|
{"BTN_BACK", BTN_BACK},
|
||||||
|
{"BTN_TASK", BTN_TASK},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char*
|
||||||
|
mouse_event_code_get_name(int code)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < ALEN(button_map); i++) {
|
||||||
|
if (code == button_map[i].code)
|
||||||
|
return button_map[i].name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
value_to_mouse_combos(struct context *ctx, struct key_combo_list *key_combos)
|
value_to_mouse_combos(struct context *ctx, struct key_combo_list *key_combos)
|
||||||
{
|
{
|
||||||
|
|
@ -1971,10 +2005,6 @@ value_to_mouse_combos(struct context *ctx, struct key_combo_list *key_combos)
|
||||||
*key = '\0';
|
*key = '\0';
|
||||||
if (!parse_modifiers(ctx, combo, key - combo, &modifiers))
|
if (!parse_modifiers(ctx, combo, key - combo, &modifiers))
|
||||||
goto err;
|
goto err;
|
||||||
if (modifiers.shift) {
|
|
||||||
LOG_CONTEXTUAL_ERR("Shift cannot be used in mouse bindings");
|
|
||||||
goto err;
|
|
||||||
}
|
|
||||||
key++; /* Skip past the '+' */
|
key++; /* Skip past the '+' */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1999,24 +2029,10 @@ value_to_mouse_combos(struct context *ctx, struct key_combo_list *key_combos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct {
|
|
||||||
const char *name;
|
|
||||||
int code;
|
|
||||||
} map[] = {
|
|
||||||
{"BTN_LEFT", BTN_LEFT},
|
|
||||||
{"BTN_RIGHT", BTN_RIGHT},
|
|
||||||
{"BTN_MIDDLE", BTN_MIDDLE},
|
|
||||||
{"BTN_SIDE", BTN_SIDE},
|
|
||||||
{"BTN_EXTRA", BTN_EXTRA},
|
|
||||||
{"BTN_FORWARD", BTN_FORWARD},
|
|
||||||
{"BTN_BACK", BTN_BACK},
|
|
||||||
{"BTN_TASK", BTN_TASK},
|
|
||||||
};
|
|
||||||
|
|
||||||
int button = 0;
|
int button = 0;
|
||||||
for (size_t i = 0; i < ALEN(map); i++) {
|
for (size_t i = 0; i < ALEN(button_map); i++) {
|
||||||
if (strcmp(key, map[i].name) == 0) {
|
if (strcmp(key, button_map[i].name) == 0) {
|
||||||
button = map[i].code;
|
button = button_map[i].code;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2054,6 +2070,98 @@ err:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
modifiers_equal(const struct config_key_modifiers *mods1,
|
||||||
|
const struct config_key_modifiers *mods2)
|
||||||
|
{
|
||||||
|
bool shift = mods1->shift == mods2->shift;
|
||||||
|
bool alt = mods1->alt == mods2->alt;
|
||||||
|
bool ctrl = mods1->ctrl == mods2->ctrl;
|
||||||
|
bool meta = mods1->meta == mods2->meta;
|
||||||
|
return shift && alt && ctrl && meta;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
modifiers_disjoint(const struct config_key_modifiers *mods1,
|
||||||
|
const struct config_key_modifiers *mods2)
|
||||||
|
{
|
||||||
|
bool shift = mods1->shift && mods2->shift;
|
||||||
|
bool alt = mods1->alt && mods2->alt;
|
||||||
|
bool ctrl = mods1->ctrl && mods2->ctrl;
|
||||||
|
bool meta = mods1->meta && mods2->meta;
|
||||||
|
return !(shift || alt || ctrl || meta);
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *
|
||||||
|
modifiers_to_str(const struct config_key_modifiers *mods)
|
||||||
|
{
|
||||||
|
char *ret = xasprintf("%s%s%s%s",
|
||||||
|
mods->ctrl ? "Control+" : "",
|
||||||
|
mods->alt ? "Alt+": "",
|
||||||
|
mods->meta ? "Meta+": "",
|
||||||
|
mods->shift ? "Shift+": "");
|
||||||
|
ret[strlen(ret) - 1] = '\0';
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *
|
||||||
|
mouse_combo_to_str(const struct key_combo *combo)
|
||||||
|
{
|
||||||
|
char *combo_modifiers_str = modifiers_to_str(&combo->modifiers);
|
||||||
|
const char *combo_button_str = mouse_event_code_get_name(combo->m.button);
|
||||||
|
xassert(combo_button_str != NULL);
|
||||||
|
|
||||||
|
char *ret;
|
||||||
|
if (combo->m.count == 1)
|
||||||
|
ret = xasprintf("%s+%s", combo_modifiers_str, combo_button_str);
|
||||||
|
else
|
||||||
|
ret = xasprintf("%s+%s-%d",
|
||||||
|
combo_modifiers_str,
|
||||||
|
combo_button_str,
|
||||||
|
combo->m.count);
|
||||||
|
|
||||||
|
free (combo_modifiers_str);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
selection_override_interferes_with_mouse_binding(struct context *ctx,
|
||||||
|
int action,
|
||||||
|
const struct key_combo_list *key_combos,
|
||||||
|
bool blame_modifiers)
|
||||||
|
{
|
||||||
|
struct config *conf = ctx->conf;
|
||||||
|
|
||||||
|
if (action == BIND_ACTION_NONE)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const struct config_key_modifiers *override_mods =
|
||||||
|
&conf->mouse.selection_override_modifiers;
|
||||||
|
for (size_t i = 0; i < key_combos->count; i++) {
|
||||||
|
const struct key_combo *combo = &key_combos->combos[i];
|
||||||
|
|
||||||
|
if (!modifiers_disjoint(&combo->modifiers, override_mods)) {
|
||||||
|
char *modifiers_str = modifiers_to_str(override_mods);
|
||||||
|
char *combo_str = mouse_combo_to_str(combo);
|
||||||
|
if (blame_modifiers) {
|
||||||
|
LOG_CONTEXTUAL_ERR(
|
||||||
|
"modifiers conflict with existing binding %s=%s",
|
||||||
|
binding_action_map[action],
|
||||||
|
combo_str);
|
||||||
|
} else {
|
||||||
|
LOG_CONTEXTUAL_ERR(
|
||||||
|
"binding conflicts with selection override modifiers (%s)",
|
||||||
|
modifiers_str);
|
||||||
|
}
|
||||||
|
free (modifiers_str);
|
||||||
|
free (combo_str);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
has_mouse_binding_collisions(struct context *ctx,
|
has_mouse_binding_collisions(struct context *ctx,
|
||||||
const struct key_combo_list *key_combos)
|
const struct key_combo_list *key_combos)
|
||||||
|
|
@ -2071,14 +2179,10 @@ has_mouse_binding_collisions(struct context *ctx,
|
||||||
const struct config_key_modifiers *mods1 = &combo1->modifiers;
|
const struct config_key_modifiers *mods1 = &combo1->modifiers;
|
||||||
const struct config_key_modifiers *mods2 = &combo2->modifiers;
|
const struct config_key_modifiers *mods2 = &combo2->modifiers;
|
||||||
|
|
||||||
bool shift = mods1->shift == mods2->shift;
|
|
||||||
bool alt = mods1->alt == mods2->alt;
|
|
||||||
bool ctrl = mods1->ctrl == mods2->ctrl;
|
|
||||||
bool meta = mods1->meta == mods2->meta;
|
|
||||||
bool button = combo1->button == combo2->m.button;
|
bool button = combo1->button == combo2->m.button;
|
||||||
bool count = combo1->count == combo2->m.count;
|
bool count = combo1->count == combo2->m.count;
|
||||||
|
|
||||||
if (shift && alt && ctrl && meta && button && count) {
|
if (modifiers_equal(mods1, mods2) && button && count) {
|
||||||
bool has_pipe = combo1->pipe.argv.args != NULL;
|
bool has_pipe = combo1->pipe.argv.args != NULL;
|
||||||
LOG_CONTEXTUAL_ERR("%s already mapped to '%s%s%s%s'",
|
LOG_CONTEXTUAL_ERR("%s already mapped to '%s%s%s%s'",
|
||||||
combo2->text,
|
combo2->text,
|
||||||
|
|
@ -2102,6 +2206,37 @@ parse_section_mouse_bindings(struct context *ctx)
|
||||||
const char *key = ctx->key;
|
const char *key = ctx->key;
|
||||||
const char *value = ctx->value;
|
const char *value = ctx->value;
|
||||||
|
|
||||||
|
if (strcmp(ctx->key, "selection-override-modifiers") == 0) {
|
||||||
|
if (!parse_modifiers(ctx, ctx->value, strlen(ctx->value),
|
||||||
|
&conf->mouse.selection_override_modifiers)) {
|
||||||
|
LOG_CONTEXTUAL_ERR("%s: invalid modifiers '%s'", key, ctx->value);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure no existing bindings use these modifiers */
|
||||||
|
for (size_t i = 0; i < conf->bindings.mouse.count; i++) {
|
||||||
|
const struct config_mouse_binding *binding = &conf->bindings.mouse.arr[i];
|
||||||
|
struct key_combo combo = {
|
||||||
|
.modifiers = binding->modifiers,
|
||||||
|
.m = {
|
||||||
|
.button = binding->button,
|
||||||
|
.count = binding->count,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
struct key_combo_list key_combos = {
|
||||||
|
.count = 1,
|
||||||
|
.combos = &combo,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (selection_override_interferes_with_mouse_binding(ctx, binding->action, &key_combos, true)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
struct argv pipe_argv;
|
struct argv pipe_argv;
|
||||||
|
|
||||||
ssize_t pipe_remove_len = pipe_argv_from_value(ctx, &pipe_argv);
|
ssize_t pipe_remove_len = pipe_argv_from_value(ctx, &pipe_argv);
|
||||||
|
|
@ -2136,7 +2271,8 @@ parse_section_mouse_bindings(struct context *ctx)
|
||||||
|
|
||||||
struct key_combo_list key_combos = {0};
|
struct key_combo_list key_combos = {0};
|
||||||
if (!value_to_mouse_combos(ctx, &key_combos) ||
|
if (!value_to_mouse_combos(ctx, &key_combos) ||
|
||||||
has_mouse_binding_collisions(ctx, &key_combos))
|
has_mouse_binding_collisions(ctx, &key_combos) ||
|
||||||
|
selection_override_interferes_with_mouse_binding(ctx, action, &key_combos, false))
|
||||||
{
|
{
|
||||||
free_argv(&pipe_argv);
|
free_argv(&pipe_argv);
|
||||||
free_key_combo_list(&key_combos);
|
free_key_combo_list(&key_combos);
|
||||||
|
|
@ -2780,6 +2916,12 @@ config_load(struct config *conf, const char *conf_path,
|
||||||
.mouse = {
|
.mouse = {
|
||||||
.hide_when_typing = false,
|
.hide_when_typing = false,
|
||||||
.alternate_scroll_mode = true,
|
.alternate_scroll_mode = true,
|
||||||
|
.selection_override_modifiers = {
|
||||||
|
.shift = true,
|
||||||
|
.alt = false,
|
||||||
|
.ctrl = false,
|
||||||
|
.meta = false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
.csd = {
|
.csd = {
|
||||||
.preferred = CONF_CSD_PREFER_SERVER,
|
.preferred = CONF_CSD_PREFER_SERVER,
|
||||||
|
|
|
||||||
1
config.h
1
config.h
|
|
@ -193,6 +193,7 @@ struct config {
|
||||||
struct {
|
struct {
|
||||||
bool hide_when_typing;
|
bool hide_when_typing;
|
||||||
bool alternate_scroll_mode;
|
bool alternate_scroll_mode;
|
||||||
|
struct config_key_modifiers selection_override_modifiers;
|
||||||
} mouse;
|
} mouse;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
|
|
|
||||||
|
|
@ -865,10 +865,6 @@ of the modifiers *must* be valid XKB key names, and the button name
|
||||||
*must* be a valid libinput name. You can find the button names using
|
*must* be a valid libinput name. You can find the button names using
|
||||||
*libinput debug-events*.
|
*libinput debug-events*.
|
||||||
|
|
||||||
Note that *Shift* cannot be used as a modifier in mouse bindings since
|
|
||||||
it is used to enable selection when the client application is grabbing
|
|
||||||
the mouse.
|
|
||||||
|
|
||||||
The trailing *COUNT* is optional and specifies the click count
|
The trailing *COUNT* is optional and specifies the click count
|
||||||
required to trigger the binding. The default if *COUNT* is omitted is
|
required to trigger the binding. The default if *COUNT* is omitted is
|
||||||
_1_.
|
_1_.
|
||||||
|
|
@ -879,7 +875,19 @@ say you want to bind *BTN\_MIDDLE* to *fullscreen*. Since
|
||||||
need to unmap the default binding. This can be done by setting
|
need to unmap the default binding. This can be done by setting
|
||||||
_action=none_; e.g. *primary-paste=none*.
|
_action=none_; e.g. *primary-paste=none*.
|
||||||
|
|
||||||
All actions listed under *key-bindings* can be used here as well.
|
*selection-override-modifiers*
|
||||||
|
The modifiers set in this set (which may be set to any combination
|
||||||
|
of modifiers, e.g. _mod1+mod2+mod3_, as well as _none_) are used
|
||||||
|
to enable selecting text with the mouse irrespective of whether a
|
||||||
|
client application currently has the mouse grabbed.
|
||||||
|
These modifiers cannot be used as modifiers in mouse bindings.
|
||||||
|
Because the order of bindings is significant, it is best to set
|
||||||
|
this prior to any other mouse bindings that might use modifiers in
|
||||||
|
the default set.
|
||||||
|
Default: _Shift_
|
||||||
|
|
||||||
|
The actions to which mouse combos can be bound are listed below. All
|
||||||
|
actions listed under *key-bindings* can be used here as well.
|
||||||
|
|
||||||
*select-begin*
|
*select-begin*
|
||||||
Begin an interactive selection. The selection is finalized, and
|
Begin an interactive selection. The selection is finalized, and
|
||||||
|
|
|
||||||
1
foot.ini
1
foot.ini
|
|
@ -169,6 +169,7 @@
|
||||||
# toggle-url-visible=t
|
# toggle-url-visible=t
|
||||||
|
|
||||||
[mouse-bindings]
|
[mouse-bindings]
|
||||||
|
# selection-override-modifiers=Shift
|
||||||
# primary-paste=BTN_MIDDLE
|
# primary-paste=BTN_MIDDLE
|
||||||
# select-begin=BTN_LEFT
|
# select-begin=BTN_LEFT
|
||||||
# select-begin-block=Control+BTN_LEFT
|
# select-begin-block=Control+BTN_LEFT
|
||||||
|
|
|
||||||
14
input.c
14
input.c
|
|
@ -701,6 +701,11 @@ keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard,
|
||||||
seat->kbd.key_arrow_down = xkb_keymap_key_by_name(seat->kbd.xkb_keymap, "DOWN");
|
seat->kbd.key_arrow_down = xkb_keymap_key_by_name(seat->kbd.xkb_keymap, "DOWN");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Set selection-override modmask from configured mods and seat's mod indices */
|
||||||
|
const struct config_key_modifiers* override_mods =
|
||||||
|
&wayl->conf->mouse.selection_override_modifiers;
|
||||||
|
seat->kbd.selection_override_modmask = conf_modifiers_to_mask(seat, override_mods);
|
||||||
|
|
||||||
munmap(map_str, size);
|
munmap(map_str, size);
|
||||||
close(fd);
|
close(fd);
|
||||||
|
|
||||||
|
|
@ -983,7 +988,7 @@ UNITTEST
|
||||||
xassert(strcmp(info->seq, "\033[27;3;13~") == 0);
|
xassert(strcmp(info->seq, "\033[27;3;13~") == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
void
|
||||||
get_current_modifiers(const struct seat *seat,
|
get_current_modifiers(const struct seat *seat,
|
||||||
xkb_mod_mask_t *effective,
|
xkb_mod_mask_t *effective,
|
||||||
xkb_mod_mask_t *consumed, uint32_t key)
|
xkb_mod_mask_t *consumed, uint32_t key)
|
||||||
|
|
@ -2372,11 +2377,8 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
|
||||||
get_current_modifiers(seat, &mods, NULL, 0);
|
get_current_modifiers(seat, &mods, NULL, 0);
|
||||||
mods &= seat->kbd.bind_significant;
|
mods &= seat->kbd.bind_significant;
|
||||||
|
|
||||||
/* Ignore Shift when matching modifiers, since it is
|
/* Ignore selection override modifiers when matching modifiers */
|
||||||
* used to enable selection in mouse grabbing client
|
mods &= ~seat->kbd.selection_override_modmask;
|
||||||
* applications */
|
|
||||||
if (seat->kbd.mod_shift != XKB_MOD_INVALID)
|
|
||||||
mods &= ~(1 << seat->kbd.mod_shift);
|
|
||||||
|
|
||||||
const struct mouse_binding *match = NULL;
|
const struct mouse_binding *match = NULL;
|
||||||
|
|
||||||
|
|
|
||||||
5
input.h
5
input.h
|
|
@ -27,4 +27,9 @@ extern const struct wl_pointer_listener pointer_listener;
|
||||||
|
|
||||||
void input_repeat(struct seat *seat, uint32_t key);
|
void input_repeat(struct seat *seat, uint32_t key);
|
||||||
|
|
||||||
|
void get_current_modifiers(const struct seat *seat,
|
||||||
|
xkb_mod_mask_t *effective,
|
||||||
|
xkb_mod_mask_t *consumed,
|
||||||
|
uint32_t key);
|
||||||
|
|
||||||
const char *xcursor_for_csd_border(struct terminal *term, int x, int y);
|
const char *xcursor_for_csd_border(struct terminal *term, int x, int y);
|
||||||
|
|
|
||||||
11
terminal.c
11
terminal.c
|
|
@ -2826,10 +2826,15 @@ term_mouse_grabbed(const struct terminal *term, struct seat *seat)
|
||||||
/*
|
/*
|
||||||
* Mouse is grabbed by us, regardless of whether mouse tracking has been enabled or not.
|
* Mouse is grabbed by us, regardless of whether mouse tracking has been enabled or not.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
xkb_mod_mask_t mods;
|
||||||
|
get_current_modifiers(seat, &mods, NULL, 0);
|
||||||
|
|
||||||
|
const xkb_mod_mask_t override_modmask = seat->kbd.selection_override_modmask;
|
||||||
|
bool override_mods_pressed = (mods & override_modmask) == override_modmask;
|
||||||
|
|
||||||
return term->mouse_tracking == MOUSE_NONE ||
|
return term->mouse_tracking == MOUSE_NONE ||
|
||||||
(seat->kbd_focus == term &&
|
(seat->kbd_focus == term && override_mods_pressed);
|
||||||
seat->kbd.shift &&
|
|
||||||
!seat->kbd.alt && /*!seat->kbd.ctrl &&*/ !seat->kbd.super);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
|
||||||
|
|
@ -201,6 +201,8 @@ struct seat {
|
||||||
xkb_mod_mask_t bind_significant;
|
xkb_mod_mask_t bind_significant;
|
||||||
xkb_mod_mask_t kitty_significant;
|
xkb_mod_mask_t kitty_significant;
|
||||||
|
|
||||||
|
xkb_mod_mask_t selection_override_modmask;
|
||||||
|
|
||||||
xkb_keycode_t key_arrow_up;
|
xkb_keycode_t key_arrow_up;
|
||||||
xkb_keycode_t key_arrow_down;
|
xkb_keycode_t key_arrow_down;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue