diff --git a/kitty/crypto.c b/kitty/crypto.c index fa77dcfe1..c24499515 100644 --- a/kitty/crypto.c +++ b/kitty/crypto.c @@ -22,11 +22,6 @@ typedef enum HASH_ALGORITHM { SHA1_HASH, SHA224_HASH, SHA256_HASH, SHA384_HASH, SHA512_HASH } HASH_ALGORITHM; static PyObject* Crypto_Exception = NULL; -#define ADD_TYPE(which) \ - if (PyType_Ready(&which##_Type) < 0) return false; \ - if (PyModule_AddObject(module, #which, (PyObject *)&which##_Type) != 0) return false; \ - Py_INCREF(&which##_Type); - static PyObject* set_error_from_openssl(const char *prefix) { BIO *bio = BIO_new(BIO_s_mem()); diff --git a/kitty/data-types.h b/kitty/data-types.h index 654766b7b..218835116 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -133,6 +133,14 @@ typedef struct ImageAnchorPosition { #endif #define START_ALLOW_CASE_RANGE IGNORE_PEDANTIC_WARNINGS #define END_ALLOW_CASE_RANGE END_IGNORE_PEDANTIC_WARNINGS +#define BIT_MASK(__TYPE__, __ONE_COUNT__) \ + ((__TYPE__) (-((__ONE_COUNT__) != 0))) \ + & (((__TYPE__) -1) >> ((sizeof(__TYPE__) * CHAR_BIT) - (__ONE_COUNT__))) +#define ADD_TYPE(which) \ + if (PyType_Ready(&which##_Type) < 0) return false; \ + if (PyModule_AddObject(module, #which, (PyObject *)&which##_Type) != 0) return false; \ + Py_INCREF(&which##_Type); + typedef enum UTF8State { UTF8_ACCEPT = 0, UTF8_REJECT = 1} UTF8State; diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index 45c9d560a..d67849998 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -1466,3 +1466,17 @@ class AES256GCMDecrypt: def add_data_to_be_authenticated_but_not_decrypted(self, data: bytes) -> None: ... def add_data_to_be_decrypted(self, data: bytes, finished: bool = False) -> bytes: ... + + +class SingleKey: + + def __init__(self, mods: int = 0, is_native: bool = False, key: int = -1): ... + def __hash__(self) -> int: ... + def __len__(self) -> int: ... + def __getitem__(self, x: int) -> int: ... + @property + def mods(self) -> int: ... + @property + def is_native(self) -> bool: ... + @property + def key(self) -> int: ... diff --git a/kitty/keys.c b/kitty/keys.c index 246076e03..19deb223d 100644 --- a/kitty/keys.c +++ b/kitty/keys.c @@ -283,11 +283,155 @@ static PyMethodDef module_methods[] = { {0} }; +// SingleKey {{{ +#define KEY_BITS 21 +#define MOD_BITS 10 + +typedef union Key { + struct { + uint32_t key : KEY_BITS; + uint32_t mods : MOD_BITS; + uint32_t is_native: 1; + }; + uint32_t val; +} Key; + +static PyTypeObject SingleKey_Type; + +typedef struct { + PyObject_HEAD + + Key key; +} SingleKey; + +static PyObject * +SingleKey_new(PyTypeObject *type, PyObject *args, PyObject *kw) { + static char *kwds[] = {"mods", "is_native", "key", NULL}; + long key = -1; unsigned short mods = 0; int is_native = 0; + if (!PyArg_ParseTupleAndKeywords(args, kw, "|Hpl", kwds, &mods, &is_native, &key)) return NULL; + SingleKey *self = (SingleKey *)type->tp_alloc(type, 0); + if (self) { + if (key > 0 && key <= 0x10FFFF) { + uint32_t k = (uint32_t)key; + self->key.key = k & BIT_MASK(uint32_t, KEY_BITS); + } + self->key.mods = mods; + if (is_native) self->key.is_native = 1u; + } + 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 key = self->key.key; + if (key) pos += PyOS_snprintf(buf + pos, sizeof(buf) - pos, "key=%lu, ", 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 val = self->key.key; + if (val) return PyLong_FromUnsignedLong(val); + return PyLong_FromLong(-1); +} + +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 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}, + {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 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 = 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; } diff --git a/kitty_tests/datatypes.py b/kitty_tests/datatypes.py index fd3d022c8..69129c652 100644 --- a/kitty_tests/datatypes.py +++ b/kitty_tests/datatypes.py @@ -532,3 +532,15 @@ class TestDataTypes(BaseTest): q('a\x1bbc', 'ac') q('a\x1b[bc', 'ac') q('a\x1b[12;34:43mbc', 'abc') + + def test_SingleKey(self): + from kitty.fast_data_types import SingleKey, GLFW_MOD_NUM_LOCK, GLFW_MOD_SHIFT + for m in (GLFW_MOD_NUM_LOCK, GLFW_MOD_SHIFT): + s = SingleKey(mods=m) + self.ae(s.mods, m) + self.ae(tuple(SingleKey()), (0, False, -1)) + self.ae(tuple(SingleKey(key=0x10ffff, mods=GLFW_MOD_SHIFT, is_native=True)), (GLFW_MOD_SHIFT, True, 0x10ffff)) + self.ae(repr(SingleKey()), 'SingleKey()') + self.ae(repr(SingleKey(key=23, mods=2, is_native=True)), 'SingleKey(mods=2, is_native=True, key=23)') + self.ae(repr(SingleKey(key=23, mods=2)), 'SingleKey(mods=2, key=23)') + self.ae(repr(SingleKey(key=23)), 'SingleKey(key=23)')