/* * keys.c * Copyright (C) 2017 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "state.h" #include "keys.h" #include "screen.h" #include "glfw-wrapper.h" #include #ifndef __APPLE__ #include #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->geometry.left, top = w->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("bufferring 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; }