mirror of
https://github.com/kovidgoyal/kitty.git
synced 2026-02-01 11:34:59 +01:00
This was needed to fix various corner cases when doing blending of colors in linear space. The new architecture has the same performance as the old in the common case of opaque rendering with no UI layers or images. In the case of only positive z-index images there is a performance decrease as the OS Window is now rendered to a offscreen texture and then blitted to screen. However, in the future when we move to Vulkan or I can figure out how to get Wayland to accept buffers with colors in linear space, this performance penalty can be removed. The performance penalty was not significant on my system but this is highly GPU dependent. Modern GPUs are supposedly optimised for rendering to offscreen buffers, so we will see. The awrit project might be a good test case. Now either we have 1-shot rendering for the case of opaque with only ext or all the various pieces are rendered in successive draw calls into an offscreen buffer that is blitted to the output buffer after all drawing is done. Fixes #8869
627 lines
24 KiB
C
627 lines
24 KiB
C
/*
|
|
* keys.c
|
|
* Copyright (C) 2017 Kovid Goyal <kovid at kovidgoyal.net>
|
|
*
|
|
* Distributed under terms of the GPL3 license.
|
|
*/
|
|
|
|
#include "state.h"
|
|
#include "keys.h"
|
|
#include "screen.h"
|
|
#include "glfw-wrapper.h"
|
|
#include <structmember.h>
|
|
|
|
#ifndef __APPLE__
|
|
#include <xkbcommon/xkbcommon.h>
|
|
#endif
|
|
|
|
// python KeyEvent object {{{
|
|
typedef struct {
|
|
PyObject_HEAD
|
|
PyObject *key, *shifted_key, *alternate_key;
|
|
PyObject *mods, *action, *native_key, *ime_state;
|
|
PyObject *text;
|
|
} PyKeyEvent;
|
|
|
|
static PyObject* convert_glfw_key_event_to_python(const GLFWkeyevent *ev);
|
|
|
|
static PyObject*
|
|
new_keyevent_object(PyTypeObject *type UNUSED, PyObject *args, PyObject *kw) {
|
|
static char *kwds[] = {"key", "shifted_key", "alternate_key", "mods", "action", "native_key", "ime_state", "text", NULL};
|
|
GLFWkeyevent ev = {.action=GLFW_PRESS};
|
|
if (!PyArg_ParseTupleAndKeywords(args, kw, "I|IIiiiiz", kwds, &ev.key, &ev.shifted_key, &ev.alternate_key, &ev.mods, &ev.action, &ev.native_key, &ev.ime_state, &ev.text)) return NULL;
|
|
return convert_glfw_key_event_to_python(&ev);
|
|
}
|
|
|
|
bool
|
|
is_modifier_key(const uint32_t key) {
|
|
START_ALLOW_CASE_RANGE
|
|
switch (key) {
|
|
case GLFW_FKEY_LEFT_SHIFT ... GLFW_FKEY_ISO_LEVEL5_SHIFT:
|
|
case GLFW_FKEY_CAPS_LOCK:
|
|
case GLFW_FKEY_SCROLL_LOCK:
|
|
case GLFW_FKEY_NUM_LOCK:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
END_ALLOW_CASE_RANGE
|
|
}
|
|
|
|
static bool
|
|
is_no_action_key(const uint32_t key, const uint32_t native_key) {
|
|
switch (native_key) {
|
|
#ifndef __APPLE__
|
|
case XKB_KEY_XF86Fn:
|
|
case XKB_KEY_XF86WakeUp:
|
|
return true;
|
|
#endif
|
|
default:
|
|
return is_modifier_key(key);
|
|
}
|
|
}
|
|
|
|
static void
|
|
dealloc(PyKeyEvent* self) {
|
|
Py_CLEAR(self->key); Py_CLEAR(self->shifted_key); Py_CLEAR(self->alternate_key);
|
|
Py_CLEAR(self->mods); Py_CLEAR(self->action); Py_CLEAR(self->native_key); Py_CLEAR(self->ime_state);
|
|
Py_CLEAR(self->text);
|
|
Py_TYPE(self)->tp_free((PyObject*)self);
|
|
}
|
|
|
|
static PyMemberDef members[] = {
|
|
#define M(x) {#x, T_OBJECT, offsetof(PyKeyEvent, x), READONLY, #x}
|
|
M(key), M(shifted_key), M(alternate_key),
|
|
M(mods), M(action), M(native_key), M(ime_state),
|
|
M(text),
|
|
{NULL},
|
|
#undef M
|
|
};
|
|
|
|
PyTypeObject PyKeyEvent_Type = {
|
|
PyVarObject_HEAD_INIT(NULL, 0)
|
|
.tp_name = "fast_data_types.KeyEvent",
|
|
.tp_basicsize = sizeof(PyKeyEvent),
|
|
.tp_dealloc = (destructor)dealloc,
|
|
.tp_flags = Py_TPFLAGS_DEFAULT,
|
|
.tp_doc = "A key event",
|
|
.tp_members = members,
|
|
.tp_new = new_keyevent_object,
|
|
};
|
|
|
|
static PyObject*
|
|
convert_glfw_key_event_to_python(const GLFWkeyevent *ev) {
|
|
PyKeyEvent *self = (PyKeyEvent*)PyKeyEvent_Type.tp_alloc(&PyKeyEvent_Type, 0);
|
|
if (!self) return NULL;
|
|
#define C(x) { unsigned long t = ev->x; self->x = PyLong_FromUnsignedLong(t); if (self->x == NULL) { Py_CLEAR(self); return NULL; } }
|
|
C(key); C(shifted_key); C(alternate_key); C(mods); C(action); C(native_key); C(ime_state);
|
|
#undef C
|
|
self->text = PyUnicode_FromString(ev->text ? ev->text : "");
|
|
if (!self->text) { Py_CLEAR(self); return NULL; }
|
|
return (PyObject*)self;
|
|
}
|
|
// }}}
|
|
|
|
static Window*
|
|
active_window(void) {
|
|
Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab;
|
|
Window *w = t->windows + t->active_window;
|
|
if (!w->render_data.screen) return NULL;
|
|
if (w->redirect_keys_to_overlay) {
|
|
for (unsigned i = 0; i < t->num_windows; i++) {
|
|
if (t->windows[i].id == w->redirect_keys_to_overlay && w->render_data.screen) return t->windows + i;
|
|
}
|
|
}
|
|
return w;
|
|
}
|
|
|
|
void
|
|
update_ime_focus(OSWindow *osw, bool focused) {
|
|
if (!osw || !osw->handle) return;
|
|
GLFWIMEUpdateEvent ev = { .focused = focused, .type = GLFW_IME_UPDATE_FOCUS };
|
|
glfwUpdateIMEState(osw->handle, &ev);
|
|
}
|
|
|
|
void
|
|
prepare_ime_position_update_event(OSWindow *osw, Window *w, Screen *screen, GLFWIMEUpdateEvent *ev) {
|
|
unsigned int cell_width = osw->fonts_data->fcm.cell_width, cell_height = osw->fonts_data->fcm.cell_height;
|
|
unsigned int left = w->render_data.geometry.left, top = w->render_data.geometry.top;
|
|
if (screen_is_overlay_active(screen)) {
|
|
left += screen->overlay_line.cursor_x * cell_width;
|
|
top += MIN(screen->overlay_line.ynum + screen->scrolled_by, screen->lines - 1) * cell_height;
|
|
} else {
|
|
left += screen->cursor->x * cell_width;
|
|
top += screen->cursor->y * cell_height;
|
|
}
|
|
ev->cursor.left = left; ev->cursor.top = top; ev->cursor.width = cell_width; ev->cursor.height = cell_height;
|
|
}
|
|
|
|
void
|
|
update_ime_position(Window* w UNUSED, Screen *screen UNUSED) {
|
|
GLFWIMEUpdateEvent ev = { .type = GLFW_IME_UPDATE_CURSOR_POSITION };
|
|
#ifndef __APPLE__
|
|
prepare_ime_position_update_event(global_state.callback_os_window, w, screen, &ev);
|
|
#endif
|
|
glfwUpdateIMEState(global_state.callback_os_window->handle, &ev);
|
|
}
|
|
|
|
const char*
|
|
format_mods(unsigned mods) {
|
|
static char buf[128];
|
|
char *p = buf, *s;
|
|
#define pr(x) p += snprintf(p, sizeof(buf) - (p - buf) - 1, x)
|
|
pr("mods: ");
|
|
s = p;
|
|
if (mods & GLFW_MOD_CONTROL) pr("ctrl+");
|
|
if (mods & GLFW_MOD_ALT) pr("alt+");
|
|
if (mods & GLFW_MOD_SHIFT) pr("shift+");
|
|
if (mods & GLFW_MOD_SUPER) pr("super+");
|
|
if (mods & GLFW_MOD_HYPER) pr("hyper+");
|
|
if (mods & GLFW_MOD_META) pr("meta+");
|
|
if (mods & GLFW_MOD_CAPS_LOCK) pr("capslock+");
|
|
if (mods & GLFW_MOD_NUM_LOCK) pr("numlock+");
|
|
if (p == s) pr("none");
|
|
else p--;
|
|
pr(" ");
|
|
#undef pr
|
|
return buf;
|
|
}
|
|
|
|
static void
|
|
send_key_to_child(id_type window_id, Screen *screen, const GLFWkeyevent *ev) {
|
|
const int action = ev->action;
|
|
const uint32_t key = ev->key, native_key = ev->native_key;
|
|
const char *text = ev->text ? ev->text : "";
|
|
|
|
if (action == GLFW_REPEAT && !screen->modes.mDECARM) {
|
|
debug("discarding repeat key event as DECARM is off\n");
|
|
return;
|
|
}
|
|
if (screen->scrolled_by && action == GLFW_PRESS && !is_no_action_key(key, native_key)) {
|
|
screen_history_scroll(screen, SCROLL_FULL, false); // scroll back to bottom
|
|
}
|
|
char encoded_key[KEY_BUFFER_SIZE] = {0};
|
|
int size = encode_glfw_key_event(ev, screen->modes.mDECCKM, screen_current_key_encoding_flags(screen), encoded_key);
|
|
if (size == SEND_TEXT_TO_CHILD) {
|
|
schedule_write_to_child(window_id, 1, text, strlen(text));
|
|
debug("sent key as text to child (window_id: %llu): %s\n", window_id, text);
|
|
} else if (size > 0) {
|
|
if (size == 1 && screen->modes.mHANDLE_TERMIOS_SIGNALS) {
|
|
if (screen_send_signal_for_key(screen, *encoded_key)) return;
|
|
}
|
|
schedule_write_to_child(window_id, 1, encoded_key, size);
|
|
if (OPT(debug_keyboard)) {
|
|
debug("sent encoded key to child (window_id: %llu): ", window_id);
|
|
for (int ki = 0; ki < size; ki++) {
|
|
if (encoded_key[ki] == 27) { debug("^[ "); }
|
|
else if (encoded_key[ki] == ' ') { debug("SPC "); }
|
|
else if (isprint(encoded_key[ki])) { debug("%c ", encoded_key[ki]); }
|
|
else { debug("0x%x ", encoded_key[ki]); }
|
|
}
|
|
debug("\n");
|
|
}
|
|
} else {
|
|
debug("ignoring as keyboard mode does not support encoding this event\n");
|
|
}
|
|
}
|
|
|
|
void
|
|
dispatch_buffered_keys(Window *w) {
|
|
if (!w->render_data.screen || !w->buffered_keys.count) return;
|
|
GLFWkeyevent *keys = w->buffered_keys.key_data;
|
|
for (size_t i = 0; i < w->buffered_keys.count; i++) {
|
|
debug("Sending previously buffered key ");
|
|
send_key_to_child(w->id, w->render_data.screen, keys + i);
|
|
}
|
|
free(w->buffered_keys.key_data); zero_at_ptr(&w->buffered_keys);
|
|
}
|
|
|
|
void
|
|
on_key_input(const GLFWkeyevent *ev) {
|
|
Window *w = active_window();
|
|
const int action = ev->action, mods = ev->mods;
|
|
const uint32_t key = ev->key, native_key = ev->native_key;
|
|
const char *text = ev->text ? ev->text : "";
|
|
|
|
if (OPT(debug_keyboard)) {
|
|
if (!key && !native_key && text[0]) {
|
|
debug("\x1b[33mon_IME_input\x1b[m: text: %s ", text);
|
|
} else {
|
|
debug("\x1b[33mon_key_input\x1b[m: glfw key: 0x%x native_code: 0x%x action: %s %stext: '%s' state: %d ",
|
|
key, native_key,
|
|
(action == GLFW_RELEASE ? "RELEASE" : (action == GLFW_PRESS ? "PRESS" : "REPEAT")),
|
|
format_mods(mods), text, ev->ime_state);
|
|
}
|
|
}
|
|
if (!w) { debug("no active window, ignoring\n"); return; }
|
|
send_pending_click_to_window(w, -1);
|
|
if (OPT(mouse_hide.hide_wait) < 0 && !is_no_action_key(key, native_key)) hide_mouse(global_state.callback_os_window);
|
|
Screen *screen = w->render_data.screen;
|
|
id_type active_window_id = w->id;
|
|
|
|
switch(ev->ime_state) {
|
|
case GLFW_IME_WAYLAND_DONE_EVENT:
|
|
// If we update IME position here it sends GNOME's text input system into
|
|
// an infinite loop. See https://github.com/kovidgoyal/kitty/issues/5105
|
|
// and also: https://github.com/kovidgoyal/kitty/pull/7283
|
|
screen_update_overlay_text(screen, text);
|
|
debug("handled wayland IME done event\n");
|
|
return;
|
|
case GLFW_IME_PREEDIT_CHANGED:
|
|
screen_update_overlay_text(screen, text);
|
|
update_ime_position(w, screen);
|
|
debug("updated pre-edit text: '%s'\n", text);
|
|
return;
|
|
case GLFW_IME_COMMIT_TEXT:
|
|
if (*text) {
|
|
schedule_write_to_child(w->id, 1, text, strlen(text));
|
|
debug("committed pre-edit text: %s sent to child as text.\n", text);
|
|
} else debug("committed pre-edit text: (null)\n");
|
|
screen_update_overlay_text(screen, NULL);
|
|
return;
|
|
case GLFW_IME_NONE:
|
|
// for macOS, update ime position on every key input
|
|
// because the position is required before next input
|
|
// On Linux this is needed by Fig integration: https://github.com/kovidgoyal/kitty/issues/5241
|
|
update_ime_position(w, screen);
|
|
break;
|
|
default:
|
|
debug("invalid state, ignoring\n");
|
|
return;
|
|
}
|
|
bool dispatch_ok = true, consumed = false;
|
|
#define dispatch_key_event(name) { \
|
|
PyObject *ke = NULL, *ret = NULL; \
|
|
ke = convert_glfw_key_event_to_python(ev); if (!ke) { PyErr_Print(); return; }; \
|
|
ret = PyObject_CallMethod(global_state.boss, #name, "O", ke); Py_CLEAR(ke); \
|
|
if (ret == NULL) { PyErr_Print(); dispatch_ok = false; } \
|
|
else { consumed = ret == Py_True; Py_CLEAR(ret); } \
|
|
w = window_for_window_id(active_window_id); \
|
|
}
|
|
if (action == GLFW_PRESS || action == GLFW_REPEAT) {
|
|
w->last_special_key_pressed = 0;
|
|
dispatch_key_event(dispatch_possible_special_key);
|
|
if (dispatch_ok) {
|
|
if (consumed) {
|
|
debug("handled as shortcut\n");
|
|
if (w) w->last_special_key_pressed = key;
|
|
return;
|
|
}
|
|
}
|
|
if (!w) return;
|
|
screen = w->render_data.screen;
|
|
} else if (w->last_special_key_pressed == key) {
|
|
w->last_special_key_pressed = 0;
|
|
debug("ignoring release event for previous press that was handled as shortcut\n");
|
|
return;
|
|
}
|
|
if (w->buffered_keys.enabled) {
|
|
if (w->buffered_keys.capacity < w->buffered_keys.count + 1) {
|
|
w->buffered_keys.capacity = MAX(16u, w->buffered_keys.capacity + 8);
|
|
GLFWkeyevent *new = malloc(w->buffered_keys.capacity * sizeof(GLFWkeyevent));
|
|
if (!new) fatal("Out of memory");
|
|
memcpy(new, w->buffered_keys.key_data, w->buffered_keys.count * sizeof(new[0]));
|
|
w->buffered_keys.key_data = new;
|
|
}
|
|
GLFWkeyevent *k = w->buffered_keys.key_data;
|
|
k[w->buffered_keys.count++] = *ev;
|
|
debug("buffering key until child is ready\n");
|
|
} else send_key_to_child(w->id, screen, ev);
|
|
#undef dispatch_key_event
|
|
}
|
|
|
|
void
|
|
fake_scroll(Window *w, int amount, bool upwards) {
|
|
if (!w) return;
|
|
int key = upwards ? GLFW_FKEY_UP : GLFW_FKEY_DOWN;
|
|
GLFWkeyevent ev = {.key = key };
|
|
char encoded_key[KEY_BUFFER_SIZE] = {0};
|
|
Screen *screen = w->render_data.screen;
|
|
uint8_t flags = screen_current_key_encoding_flags(screen);
|
|
while (amount-- > 0) {
|
|
ev.action = GLFW_PRESS;
|
|
int size = encode_glfw_key_event(&ev, screen->modes.mDECCKM, flags, encoded_key);
|
|
if (size > 0) schedule_write_to_child(w->id, 1, encoded_key, size);
|
|
ev.action = GLFW_RELEASE;
|
|
size = encode_glfw_key_event(&ev, screen->modes.mDECCKM, flags, encoded_key);
|
|
if (size > 0) schedule_write_to_child(w->id, 1, encoded_key, size);
|
|
}
|
|
}
|
|
|
|
#define PYWRAP1(name) static PyObject* py##name(PyObject UNUSED *self, PyObject *args)
|
|
#define PA(fmt, ...) if(!PyArg_ParseTuple(args, fmt, __VA_ARGS__)) return NULL;
|
|
#define M(name, arg_type) {#name, (PyCFunction)(void (*) (void))(py##name), arg_type, NULL}
|
|
|
|
PYWRAP1(key_for_native_key_name) {
|
|
const char *name;
|
|
int case_sensitive = 0;
|
|
PA("s|p", &name, &case_sensitive);
|
|
#ifndef __APPLE__
|
|
if (glfwGetNativeKeyForName) { // if this function is called before GLFW is initialized glfwGetNativeKeyForName will be NULL
|
|
int native_key = glfwGetNativeKeyForName(name, case_sensitive);
|
|
if (native_key) return Py_BuildValue("i", native_key);
|
|
}
|
|
#endif
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
static PyObject*
|
|
pyencode_key_for_tty(PyObject *self UNUSED, PyObject *args, PyObject *kw) {
|
|
static char *kwds[] = {"key", "shifted_key", "alternate_key", "mods", "action", "key_encoding_flags", "text", "cursor_key_mode", NULL};
|
|
unsigned int key = 0, shifted_key = 0, alternate_key = 0, mods = 0, action = GLFW_PRESS, key_encoding_flags = 0;
|
|
const char *text = NULL;
|
|
int cursor_key_mode = 0;
|
|
if (!PyArg_ParseTupleAndKeywords(args, kw, "I|IIIIIzp", kwds, &key, &shifted_key, &alternate_key, &mods, &action, &key_encoding_flags, &text, &cursor_key_mode)) return NULL;
|
|
GLFWkeyevent ev = { .key = key, .shifted_key = shifted_key, .alternate_key = alternate_key, .text = text, .action = action, .mods = mods };
|
|
char output[KEY_BUFFER_SIZE+1] = {0};
|
|
int num = encode_glfw_key_event(&ev, cursor_key_mode, key_encoding_flags, output);
|
|
if (num == SEND_TEXT_TO_CHILD) return PyUnicode_FromString(text);
|
|
return PyUnicode_FromStringAndSize(output, MAX(0, num));
|
|
}
|
|
|
|
static PyObject*
|
|
pyinject_key(PyObject *self UNUSED, PyObject *args, PyObject *kw) {
|
|
static char *kwds[] = {"key", "shifted_key", "alternate_key", "mods", "action", "text", "os_window_id", NULL};
|
|
unsigned int key = 0, shifted_key = 0, alternate_key = 0, mods = 0, action = GLFW_PRESS;
|
|
unsigned long long os_window_id = 0;
|
|
const char *text = NULL;
|
|
if (!PyArg_ParseTupleAndKeywords(args, kw, "I|IIIIzK", kwds, &key, &shifted_key, &alternate_key, &mods, &action, &text, &os_window_id)) return NULL;
|
|
id_type orig = global_state.callback_os_window ? global_state.callback_os_window->id : 0;
|
|
bool found = false;
|
|
if (os_window_id) {
|
|
for (size_t i = 0; i < global_state.num_os_windows && !found; i++) {
|
|
if (global_state.os_windows[i].id == os_window_id) {
|
|
global_state.callback_os_window = global_state.os_windows + i;
|
|
found = true;
|
|
}
|
|
}
|
|
if (!found) { PyErr_Format(PyExc_IndexError, "Could not find OS Window with id: %llu", os_window_id); return NULL; }
|
|
} else {
|
|
if (!global_state.callback_os_window) {
|
|
for (size_t i = 0; i < global_state.num_os_windows && !found; i++) {
|
|
if (global_state.os_windows[i].is_focused) {
|
|
global_state.callback_os_window = global_state.os_windows + i;
|
|
found = true;
|
|
}
|
|
}
|
|
if (!found && ! global_state.num_os_windows) { PyErr_SetString(PyExc_Exception, "No OS Windows available to inject key presses into"); return NULL; }
|
|
global_state.callback_os_window = global_state.os_windows;
|
|
found = true;
|
|
}
|
|
}
|
|
GLFWkeyevent ev = { .key = key, .shifted_key = shifted_key, .alternate_key = alternate_key, .text = text, .action = action, .mods = mods };
|
|
on_key_input(&ev);
|
|
if (orig) {
|
|
found = false;
|
|
for (size_t i = 0; i < global_state.num_os_windows && !found; i++) {
|
|
if (global_state.os_windows[i].id == orig) {
|
|
global_state.callback_os_window = global_state.os_windows + i; found = true;
|
|
}
|
|
}
|
|
if (!found) global_state.callback_os_window = NULL;
|
|
} else global_state.callback_os_window = NULL;
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
static PyObject*
|
|
pyis_modifier_key(PyObject *self UNUSED, PyObject *a) {
|
|
unsigned long key = PyLong_AsUnsignedLong(a);
|
|
if (PyErr_Occurred()) return NULL;
|
|
if (is_modifier_key(key)) { Py_RETURN_TRUE; }
|
|
Py_RETURN_FALSE;
|
|
}
|
|
|
|
static PyMethodDef module_methods[] = {
|
|
M(key_for_native_key_name, METH_VARARGS),
|
|
M(encode_key_for_tty, METH_VARARGS | METH_KEYWORDS),
|
|
M(inject_key, METH_VARARGS | METH_KEYWORDS),
|
|
M(is_modifier_key, METH_O),
|
|
{0}
|
|
};
|
|
|
|
// SingleKey {{{
|
|
typedef uint64_t keybitfield;
|
|
#define KEY_BITS 51
|
|
#define MOD_BITS 12
|
|
#if 1 << (MOD_BITS-1) < GLFW_MOD_KITTY
|
|
#error "Not enough mod bits"
|
|
#endif
|
|
typedef union Key {
|
|
struct {
|
|
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
|
keybitfield mods : MOD_BITS;
|
|
keybitfield is_native: 1;
|
|
keybitfield key : KEY_BITS;
|
|
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
|
keybitfield key : KEY_BITS;
|
|
keybitfield is_native: 1;
|
|
keybitfield mods : MOD_BITS;
|
|
#else
|
|
#error "Unsupported endianness"
|
|
#endif
|
|
};
|
|
keybitfield val;
|
|
} Key;
|
|
|
|
static PyTypeObject SingleKey_Type;
|
|
static char *SingleKey_kwds[] = {"mods", "is_native", "key", NULL};
|
|
typedef struct {
|
|
PyObject_HEAD
|
|
|
|
Key key;
|
|
bool defined_with_kitty_mod;
|
|
} SingleKey;
|
|
|
|
static inline void
|
|
SingleKey_set_vals(SingleKey *self, long long key, unsigned short mods, int is_native) {
|
|
if (key >= 0 && (unsigned long long)key <= BIT_MASK(keybitfield, KEY_BITS)) {
|
|
keybitfield k = (keybitfield)(unsigned long long)key;
|
|
self->key.key = k & BIT_MASK(keybitfield, KEY_BITS);
|
|
}
|
|
if (!(mods & 1 << (MOD_BITS + 1))) self->key.mods = mods & BIT_MASK(uint32_t, MOD_BITS);
|
|
if (is_native > -1) self->key.is_native = is_native ? 1 : 0;
|
|
}
|
|
|
|
static PyObject *
|
|
SingleKey_new(PyTypeObject *type, PyObject *args, PyObject *kw) {
|
|
long long key = -1; unsigned short mods = 1 << (MOD_BITS + 1); int is_native = -1;
|
|
if (!PyArg_ParseTupleAndKeywords(args, kw, "|HpL", SingleKey_kwds, &mods, &is_native, &key)) return NULL;
|
|
SingleKey *self = (SingleKey *)type->tp_alloc(type, 0);
|
|
if (self) SingleKey_set_vals(self, key, mods, is_native);
|
|
return (PyObject*)self;
|
|
}
|
|
|
|
static void
|
|
SingleKey_dealloc(SingleKey* self) {
|
|
Py_TYPE(self)->tp_free((PyObject*)self);
|
|
}
|
|
|
|
static PyObject*
|
|
SingleKey_repr(PyObject *s) {
|
|
SingleKey *self = (SingleKey*)s;
|
|
char buf[128];
|
|
int pos = 0;
|
|
pos += PyOS_snprintf(buf + pos, sizeof(buf) - pos, "SingleKey(");
|
|
unsigned int mods = self->key.mods;
|
|
if (mods) pos += PyOS_snprintf(buf + pos, sizeof(buf) - pos, "mods=%u, ", mods);
|
|
if (self->key.is_native) pos += PyOS_snprintf(buf + pos, sizeof(buf) - pos, "is_native=True, ");
|
|
unsigned long long key = self->key.key;
|
|
if (key) pos += PyOS_snprintf(buf + pos, sizeof(buf) - pos, "key=%llu, ", key);
|
|
if (buf[pos-1] == ' ') pos -= 2;
|
|
pos += PyOS_snprintf(buf + pos, sizeof(buf) - pos, ")");
|
|
return PyUnicode_FromString(buf);
|
|
}
|
|
|
|
static PyObject*
|
|
SingleKey_get_key(SingleKey *self, void UNUSED *closure) {
|
|
const unsigned long long val = self->key.key;
|
|
return PyLong_FromUnsignedLongLong(val);
|
|
}
|
|
|
|
static PyObject*
|
|
SingleKey_get_mods(SingleKey *self, void UNUSED *closure) {
|
|
const unsigned long mods = self->key.mods;
|
|
return PyLong_FromUnsignedLong(mods);
|
|
|
|
}
|
|
|
|
static PyObject*
|
|
SingleKey_get_is_native(SingleKey *self, void UNUSED *closure) {
|
|
if (self->key.is_native) Py_RETURN_TRUE;
|
|
Py_RETURN_FALSE;
|
|
}
|
|
|
|
static PyObject*
|
|
SingleKey_defined_with_kitty_mod(SingleKey *self, void UNUSED *closure) {
|
|
if (self->defined_with_kitty_mod || (self->key.mods & GLFW_MOD_KITTY)) Py_RETURN_TRUE;
|
|
Py_RETURN_FALSE;
|
|
}
|
|
|
|
|
|
static PyGetSetDef SingleKey_getsetters[] = {
|
|
{"key", (getter)SingleKey_get_key, NULL, "The key as an integer", NULL},
|
|
{"mods", (getter)SingleKey_get_mods, NULL, "The modifiers as an integer", NULL},
|
|
{"is_native", (getter)SingleKey_get_is_native, NULL, "A bool", NULL},
|
|
{"defined_with_kitty_mod", (getter)SingleKey_defined_with_kitty_mod, NULL, "A bool", NULL},
|
|
{NULL} /* Sentinel */
|
|
};
|
|
|
|
static Py_hash_t
|
|
SingleKey_hash(PyObject *self) {
|
|
Py_hash_t ans = ((SingleKey*)self)->key.val;
|
|
if (ans == -1) ans = -2;
|
|
return ans;
|
|
}
|
|
|
|
static PyObject*
|
|
SingleKey_richcompare(PyObject *self, PyObject *other, int op) {
|
|
if (!PyObject_TypeCheck(other, &SingleKey_Type)) { PyErr_SetString(PyExc_TypeError, "Cannot compare SingleKey to other objects"); return NULL; }
|
|
SingleKey *a = (SingleKey*)self, *b = (SingleKey*)other;
|
|
Py_RETURN_RICHCOMPARE(a->key.val, b->key.val, op);
|
|
}
|
|
|
|
static Py_ssize_t
|
|
SingleKey___len__(PyObject *self UNUSED) {
|
|
return 3;
|
|
}
|
|
|
|
static PyObject *
|
|
SingleKey_item(PyObject *o, Py_ssize_t i) {
|
|
SingleKey *self = (SingleKey*)o;
|
|
switch(i) {
|
|
case 0:
|
|
return SingleKey_get_mods(self, NULL);
|
|
case 1:
|
|
return SingleKey_get_is_native(self, NULL);
|
|
case 2:
|
|
return SingleKey_get_key(self, NULL);
|
|
}
|
|
PyErr_SetString(PyExc_IndexError, "tuple index out of range");
|
|
return NULL;
|
|
}
|
|
|
|
static PySequenceMethods SingleKey_sequence_methods = {
|
|
.sq_length = SingleKey___len__,
|
|
.sq_item = SingleKey_item,
|
|
};
|
|
|
|
static PyObject*
|
|
SingleKey_resolve_kitty_mod(SingleKey *self, PyObject *km) {
|
|
if (!(self->key.mods & GLFW_MOD_KITTY)) { Py_INCREF(self); return (PyObject*)self; }
|
|
unsigned long kitty_mod = PyLong_AsUnsignedLong(km);
|
|
if (PyErr_Occurred()) return NULL;
|
|
SingleKey *ans = (SingleKey*)SingleKey_Type.tp_alloc(&SingleKey_Type, 0);
|
|
if (!ans) return NULL;
|
|
ans->key.val = self->key.val;
|
|
ans->key.mods = (ans->key.mods & ~GLFW_MOD_KITTY) | kitty_mod;
|
|
ans->defined_with_kitty_mod = true;
|
|
return (PyObject*)ans;
|
|
}
|
|
|
|
static PyObject*
|
|
SingleKey_replace(SingleKey *self, PyObject *args, PyObject *kw) {
|
|
long long key = -2; unsigned short mods = 1 << (MOD_BITS + 1); int is_native = -1;
|
|
if (!PyArg_ParseTupleAndKeywords(args, kw, "|HpL", SingleKey_kwds, &mods, &is_native, &key)) return NULL;
|
|
SingleKey *ans = (SingleKey*)SingleKey_Type.tp_alloc(&SingleKey_Type, 0);
|
|
if (ans) {
|
|
if (key == -1) key = 0;
|
|
ans->key.val = self->key.val;
|
|
SingleKey_set_vals(ans, key, mods, is_native);
|
|
}
|
|
return (PyObject*)ans;
|
|
}
|
|
|
|
static PyMethodDef SingleKey_methods[] = {
|
|
{"_replace", (PyCFunction)(void (*) (void))SingleKey_replace, METH_VARARGS | METH_KEYWORDS, ""},
|
|
{"resolve_kitty_mod", (PyCFunction)SingleKey_resolve_kitty_mod, METH_O, ""},
|
|
{NULL} /* Sentinel */
|
|
};
|
|
|
|
|
|
static PyTypeObject SingleKey_Type = {
|
|
PyVarObject_HEAD_INIT(NULL, 0)
|
|
.tp_name = "fast_data_types.SingleKey",
|
|
.tp_basicsize = sizeof(SingleKey),
|
|
.tp_dealloc = (destructor)SingleKey_dealloc,
|
|
.tp_flags = Py_TPFLAGS_DEFAULT,
|
|
.tp_doc = "Compact and fast representation of a single key as defined in the config",
|
|
.tp_new = SingleKey_new,
|
|
.tp_hash = SingleKey_hash,
|
|
.tp_richcompare = SingleKey_richcompare,
|
|
.tp_as_sequence = &SingleKey_sequence_methods,
|
|
.tp_repr = SingleKey_repr,
|
|
.tp_methods = SingleKey_methods,
|
|
.tp_getset = SingleKey_getsetters,
|
|
}; // }}}
|
|
|
|
|
|
bool
|
|
init_keys(PyObject *module) {
|
|
if (PyModule_AddFunctions(module, module_methods) != 0) return false;
|
|
if (PyType_Ready(&PyKeyEvent_Type) < 0) return false;
|
|
if (PyModule_AddObject(module, "KeyEvent", (PyObject *)&PyKeyEvent_Type) != 0) return 0;
|
|
Py_INCREF(&PyKeyEvent_Type);
|
|
ADD_TYPE(SingleKey);
|
|
return true;
|
|
}
|