Linux: Allow using XKB key names to bind shortcuts to keys not supported by GLFW
Useful to bind keys such as the play/pause or volume buttons. Also can be used to bind non-ascii keys on international keyboards. Fixes #665
This commit is contained in:
parent
5dd3243674
commit
c8fc21d336
@ -40,9 +40,11 @@ def parse_shortcut(sc):
|
||||
return None, None
|
||||
key = parts[-1].upper()
|
||||
key = getattr(defines, 'GLFW_KEY_' + named_keys.get(key, key), None)
|
||||
if key is not None:
|
||||
return mods, key
|
||||
return mods, None
|
||||
is_native = False
|
||||
if key is None:
|
||||
key = defines.key_for_native_key_name(parts[-1])
|
||||
is_native = key is not None
|
||||
return mods, is_native, key
|
||||
|
||||
|
||||
KeyAction = namedtuple('KeyAction', 'func args')
|
||||
@ -140,15 +142,15 @@ sequence_sep = '>'
|
||||
|
||||
class KeyDefinition:
|
||||
|
||||
def __init__(self, is_sequence, action, mods, key, rest=()):
|
||||
def __init__(self, is_sequence, action, mods, is_native, key, rest=()):
|
||||
self.is_sequence = is_sequence
|
||||
self.action = action
|
||||
self.trigger = mods, key
|
||||
self.trigger = mods, is_native, key
|
||||
self.rest = rest
|
||||
|
||||
def resolve(self, kitty_mod):
|
||||
self.trigger = defines.resolve_key_mods(kitty_mod, self.trigger[0]), self.trigger[1]
|
||||
self.rest = tuple((defines.resolve_key_mods(kitty_mod, mods), key) for mods, key in self.rest)
|
||||
self.trigger = defines.resolve_key_mods(kitty_mod, self.trigger[0]), self.trigger[1], self.trigger[2]
|
||||
self.rest = tuple((defines.resolve_key_mods(kitty_mod, mods), is_native, key) for mods, is_native, key in self.rest)
|
||||
|
||||
|
||||
def parse_key(val, key_definitions):
|
||||
@ -161,18 +163,18 @@ def parse_key(val, key_definitions):
|
||||
trigger = None
|
||||
rest = []
|
||||
for part in sc.split(sequence_sep):
|
||||
mods, key = parse_shortcut(part)
|
||||
mods, is_native, key = parse_shortcut(part)
|
||||
if key is None:
|
||||
if mods is not None:
|
||||
log_error('Shortcut: {} has unknown key, ignoring'.format(sc))
|
||||
return
|
||||
if trigger is None:
|
||||
trigger = mods, key
|
||||
trigger = mods, is_native, key
|
||||
else:
|
||||
rest.append((mods, key))
|
||||
rest.append((mods, is_native, key))
|
||||
rest = tuple(rest)
|
||||
else:
|
||||
mods, key = parse_shortcut(sc)
|
||||
mods, is_native, key = parse_shortcut(sc)
|
||||
if key is None:
|
||||
if mods is not None:
|
||||
log_error('Shortcut: {} has unknown key, ignoring'.format(sc))
|
||||
@ -186,9 +188,9 @@ def parse_key(val, key_definitions):
|
||||
if paction is not None:
|
||||
all_key_actions.add(paction.func)
|
||||
if is_sequence:
|
||||
key_definitions.append(KeyDefinition(True, paction, trigger[0], trigger[1], rest))
|
||||
key_definitions.append(KeyDefinition(True, paction, trigger[0], trigger[1], trigger[2], rest))
|
||||
else:
|
||||
key_definitions.append(KeyDefinition(False, paction, mods, key))
|
||||
key_definitions.append(KeyDefinition(False, paction, mods, is_native, key))
|
||||
|
||||
|
||||
def parse_symbol_map(val):
|
||||
|
||||
@ -83,8 +83,16 @@ as color16 to color255.''')
|
||||
'shortcuts': [
|
||||
_('Keyboard shortcuts'),
|
||||
_('''\
|
||||
For a list of key names, see: :link:`GLFW keys <http://www.glfw.org/docs/latest/group__keys.html>`
|
||||
For a list of modifier names, see: :link:`GLFW mods <http://www.glfw.org/docs/latest/group__mods.html>`
|
||||
For a list of key names, see: :link:`GLFW keys
|
||||
<http://www.glfw.org/docs/latest/group__keys.html>`. The name to use is the part
|
||||
after the :code:`GLFW_KEY_` prefix. For a list of modifier names, see:
|
||||
:link:`GLFW mods <http://www.glfw.org/docs/latest/group__mods.html>`
|
||||
|
||||
On Linux you can also use XKB key names to bind keys that are not supported by
|
||||
GLFW. See :link:`XKB keys
|
||||
<https://github.com/xkbcommon/libxkbcommon/blob/master/xkbcommon/xkbcommon-keysyms.h>`
|
||||
for a list of key names. The name to use is the part after the :code:`XKB_KEY_`
|
||||
prefix.
|
||||
|
||||
You can use the special action :code:`no_op` to unmap a keyboard shortcut that is
|
||||
assigned in the default configuration.
|
||||
|
||||
@ -286,7 +286,7 @@ void focus_in_event();
|
||||
void wakeup_io_loop(bool);
|
||||
void scroll_event(double, double);
|
||||
void fake_scroll(int, bool);
|
||||
void set_special_key_combo(int glfw_key, int mods);
|
||||
void set_special_key_combo(int glfw_key, int mods, bool is_native);
|
||||
void on_key_input(int key, int scancode, int action, int mods, const char*, int);
|
||||
void request_window_attention(id_type, bool);
|
||||
SPRITE_MAP_HANDLE alloc_sprite_map(unsigned int, unsigned int);
|
||||
|
||||
76
kitty/keys.c
76
kitty/keys.c
@ -26,12 +26,26 @@ key_to_bytes(int glfw_key, bool smkx, bool extended, int mods, int action) {
|
||||
#define SPECIAL_INDEX(key) ((key & 0x7f) | ( (mods & 0xF) << 7))
|
||||
#define IS_ALT_MODS(mods) (mods == GLFW_MOD_ALT || mods == (GLFW_MOD_ALT | GLFW_MOD_SHIFT))
|
||||
|
||||
typedef struct { int mods, scancode; } NativeKey;
|
||||
static NativeKey *native_special_keys = NULL;
|
||||
static size_t native_special_keys_capacity = 0, native_special_keys_count = 0;
|
||||
|
||||
void
|
||||
set_special_key_combo(int glfw_key, int mods) {
|
||||
uint16_t key = key_map[glfw_key];
|
||||
if (key != UINT8_MAX) {
|
||||
key = SPECIAL_INDEX(key);
|
||||
needs_special_handling[key] = true;
|
||||
set_special_key_combo(int glfw_key, int mods, bool is_native) {
|
||||
if (is_native) {
|
||||
if (native_special_keys_count >= native_special_keys_capacity) {
|
||||
native_special_keys_capacity = MAX(128, 2 * native_special_keys_capacity);
|
||||
native_special_keys = realloc(native_special_keys, sizeof(native_special_keys[0]) * native_special_keys_capacity);
|
||||
if (native_special_keys == NULL) fatal("Out of memory");
|
||||
}
|
||||
native_special_keys[native_special_keys_count].mods = mods;
|
||||
native_special_keys[native_special_keys_count++].scancode = glfw_key;
|
||||
} else {
|
||||
uint16_t key = key_map[glfw_key];
|
||||
if (key != UINT8_MAX) {
|
||||
key = SPECIAL_INDEX(key);
|
||||
needs_special_handling[key] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -81,6 +95,24 @@ is_ascii_control_char(char c) {
|
||||
return c == 0 || (1 <= c && c <= 31) || c == 127;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
check_if_special(int key, int mods, int scancode) {
|
||||
uint16_t qkey = (0 <= key && key < (ssize_t)arraysz(key_map)) ? key_map[key] : UINT8_MAX;
|
||||
bool special = false;
|
||||
if (qkey != UINT8_MAX) {
|
||||
qkey = SPECIAL_INDEX(qkey);
|
||||
special = needs_special_handling[qkey];
|
||||
}
|
||||
#ifdef __APPLE__
|
||||
(void)scancode;
|
||||
#else
|
||||
for (size_t i = 0; !special && i < native_special_keys_count; i++) {
|
||||
if (scancode == native_special_keys[i].scancode && mods == native_special_keys[i].mods) special = true;
|
||||
}
|
||||
#endif
|
||||
return special;
|
||||
}
|
||||
|
||||
void
|
||||
on_key_input(int key, int scancode, int action, int mods, const char* text, int state UNUSED) {
|
||||
Window *w = active_window();
|
||||
@ -95,14 +127,7 @@ on_key_input(int key, int scancode, int action, int mods, const char* text, int
|
||||
Screen *screen = w->render_data.screen;
|
||||
bool has_text = text && !is_ascii_control_char(text[0]);
|
||||
if (action == GLFW_PRESS || action == GLFW_REPEAT) {
|
||||
uint16_t qkey = (0 <= key && key < (ssize_t)arraysz(key_map)) ? key_map[key] : UINT8_MAX;
|
||||
bool special = false;
|
||||
if (qkey != UINT8_MAX) {
|
||||
qkey = SPECIAL_INDEX(qkey);
|
||||
special = needs_special_handling[qkey];
|
||||
}
|
||||
/* printf("key: %s mods: %d special: %d\n", key_name(lkey), mods, special); */
|
||||
if (special) {
|
||||
if (check_if_special(key, mods, scancode)) {
|
||||
PyObject *ret = PyObject_CallMethod(global_state.boss, "dispatch_special_key", "iiii", key, scancode, action, mods);
|
||||
if (ret == NULL) { PyErr_Print(); }
|
||||
else {
|
||||
@ -149,13 +174,38 @@ PYWRAP1(key_to_bytes) {
|
||||
return Py_BuildValue("y#", ans + 1, *ans);
|
||||
}
|
||||
|
||||
PYWRAP1(key_for_native_key_name) {
|
||||
const char *name;
|
||||
int case_sensitive = 0;
|
||||
PA("s|p", &name, case_sensitive);
|
||||
#ifdef __APPLE__
|
||||
Py_RETURN_NONE;
|
||||
#else
|
||||
if (glfwGetXKBScancode) { // if this function is called before GLFW is initialized glfwGetXKBScancode will be NULL
|
||||
int scancode = glfwGetXKBScancode(name, case_sensitive);
|
||||
if (scancode) return Py_BuildValue("i", scancode);
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
#endif
|
||||
}
|
||||
|
||||
static PyMethodDef module_methods[] = {
|
||||
M(key_to_bytes, METH_VARARGS),
|
||||
M(key_for_native_key_name, METH_VARARGS),
|
||||
{0}
|
||||
};
|
||||
|
||||
void
|
||||
finalize(void) {
|
||||
free(native_special_keys);
|
||||
}
|
||||
|
||||
bool
|
||||
init_keys(PyObject *module) {
|
||||
if (PyModule_AddFunctions(module, module_methods) != 0) return false;
|
||||
if (Py_AtExit(finalize) != 0) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Failed to register the keys at exit handler");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ from . import fast_data_types as defines
|
||||
from .key_encoding import KEY_MAP
|
||||
from .terminfo import key_as_bytes
|
||||
from .utils import base64_encode
|
||||
from .constants import is_macos
|
||||
|
||||
|
||||
def modify_key_bytes(keybytes, amt):
|
||||
@ -271,11 +272,17 @@ def interpret_key_event(key, scancode, mods, window, action):
|
||||
|
||||
|
||||
def get_shortcut(keymap, mods, key, scancode):
|
||||
return keymap.get((mods & 0b1111, key))
|
||||
mods &= 0b1111
|
||||
ans = keymap.get((mods, False, key))
|
||||
if ans is None and not is_macos:
|
||||
ans = keymap.get((mods, True, scancode))
|
||||
return ans
|
||||
|
||||
|
||||
def shortcut_matches(s, mods, key, scancode):
|
||||
return s[0] & 0b1111 == mods & 0b1111 and s[1] == key
|
||||
mods &= 0b1111
|
||||
q = scancode if s[1] else key
|
||||
return s[0] & 0b1111 == mods & 0b1111 and s[2] == q
|
||||
|
||||
|
||||
def generate_key_table():
|
||||
|
||||
@ -69,7 +69,7 @@ def load_all_shaders(semi_transparent=0):
|
||||
load_borders_program()
|
||||
|
||||
|
||||
def init_graphics(debug_keyboard=False):
|
||||
def init_glfw(debug_keyboard=False):
|
||||
glfw_module = 'cocoa' if is_macos else ('wayland' if is_wayland else 'x11')
|
||||
if debug_keyboard:
|
||||
os.environ['GLFW_DEBUG_KEYBOARD'] = '1'
|
||||
@ -95,7 +95,7 @@ def get_new_os_window_trigger(opts):
|
||||
from .fast_data_types import cocoa_set_new_window_trigger
|
||||
new_os_window_shortcuts.sort(key=prefer_cmd_shortcuts, reverse=True)
|
||||
for candidate in new_os_window_shortcuts:
|
||||
if cocoa_set_new_window_trigger(*candidate):
|
||||
if cocoa_set_new_window_trigger(candidate[0], candidate[2]):
|
||||
new_os_window_trigger = candidate
|
||||
break
|
||||
return new_os_window_trigger
|
||||
@ -240,10 +240,10 @@ def _main():
|
||||
if not is_first:
|
||||
talk_to_instance(args)
|
||||
return
|
||||
init_glfw(args.debug_keyboard) # needed for parsing native keysyms
|
||||
opts = create_opts(args)
|
||||
if opts.editor != '.':
|
||||
os.environ['EDITOR'] = opts.editor
|
||||
init_graphics(args.debug_keyboard)
|
||||
try:
|
||||
with setup_profiling(args):
|
||||
# Avoid needing to launch threads to reap zombies
|
||||
|
||||
@ -325,8 +325,9 @@ set_special_keys(PyObject *dict) {
|
||||
dict_iter(dict) {
|
||||
if (!PyTuple_Check(key)) { PyErr_SetString(PyExc_TypeError, "dict keys for special keys must be tuples"); return; }
|
||||
int mods = PyLong_AsLong(PyTuple_GET_ITEM(key, 0));
|
||||
int glfw_key = PyLong_AsLong(PyTuple_GET_ITEM(key, 1));
|
||||
set_special_key_combo(glfw_key, mods);
|
||||
bool is_native = PyTuple_GET_ITEM(key, 1) == Py_True;
|
||||
int glfw_key = PyLong_AsLong(PyTuple_GET_ITEM(key, 2));
|
||||
set_special_key_combo(glfw_key, mods, is_native);
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user