diff --git a/docs/kittens/custom.rst b/docs/kittens/custom.rst index 76315968e..b34b641f3 100644 --- a/docs/kittens/custom.rst +++ b/docs/kittens/custom.rst @@ -132,6 +132,32 @@ layout, by simply adding the line:: To the ``handle_result()`` function, above. +Sending mouse events +-------------------- + +If the program running in a window is receiving mouse events you can simulate +those using:: + + from kitty.fast_data_types import send_mouse_event + send_mouse_event(screen, x, y, button, action, mods) + +``screen`` is the ``screen`` attribute of the window you want to send the event +to. ``x`` and ``y`` are the 0-indexed coordinates. ``button`` is +``GLFW_MOUSE_BUTTON_{button}`` where ``{button}`` is one of ``LEFT``, +``RIGHT``, ``MIDDLE`` or a digit from ``1`` to ``8``. ``action`` is one of +``PRESS``, ``RELEASE``, ``DRAG`` or ``MOVE``. ``mods`` is a bitmask of +``GLFW_MOD_{mod}`` where ``{mod}`` is one of ``SHIFT``, ``CONTROL`` or ``ALT``. +All the mentioned constants are imported from ``kitty.fast_data_types``. + +For example, to send a left click at position x: 2, y: 3 to the active window:: + + from kitty.fast_data_types import send_mouse_event, GLFW_MOUSE_BUTTON_LEFT, PRESS + send_mouse_event(boss.active_window.screen, 2, 3, GLFW_MOUSE_BUTTON_LEFT, PRESS, 0) + +The function will only send the event if the program is receiving events of +that type, and will return ``True`` if it sent the event, and ``False`` if not. + + Debugging kittens -------------------- diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index 814c4179e..8bcb00fdb 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -327,6 +327,10 @@ FC_WIDTH_NORMAL: int FC_SLANT_ROMAN: int FC_SLANT_ITALIC: int BORDERS_PROGRAM: int +PRESS: int +RELEASE: int +DRAG: int +MOVE: int # }}} diff --git a/kitty/mouse.c b/kitty/mouse.c index 403eb0ba6..19b7cbd06 100644 --- a/kitty/mouse.c +++ b/kitty/mouse.c @@ -15,6 +15,8 @@ #include "control-codes.h" #include "monotonic.h" +extern PyTypeObject Screen_Type; + static MouseShape mouse_cursor_shape = BEAM; typedef enum MouseActions { PRESS, RELEASE, DRAG, MOVE } MouseAction; @@ -698,6 +700,25 @@ scroll_event(double UNUSED xoffset, double yoffset, int flags) { } } +static PyObject* +send_mouse_event(PyObject *self UNUSED, PyObject *args) { + Screen *screen; + unsigned int x, y; + int button, action, mods; + if (!PyArg_ParseTuple(args, "O!IIiii", &Screen_Type, &screen, &x, &y, &button, &action, &mods)) return NULL; + + MouseTrackingMode mode = screen->modes.mouse_tracking_mode; + if (mode == ANY_MODE || (mode == MOTION_MODE && action != MOVE) || (mode == BUTTON_MODE && (action == PRESS || action == RELEASE))) { + int sz = encode_mouse_event_impl(x + 1, y + 1, screen->modes.mouse_tracking_protocol, button, action, mods); + if (sz > 0) { + mouse_event_buf[sz] = 0; + write_escape_code_to_child(screen, CSI, mouse_event_buf); + Py_RETURN_TRUE; + } + } + Py_RETURN_FALSE; +} + static PyObject* test_encode_mouse(PyObject *self UNUSED, PyObject *args) { unsigned int x, y; @@ -732,6 +753,7 @@ send_mock_mouse_event_to_window(PyObject *self UNUSED, PyObject *args) { } static PyMethodDef module_methods[] = { + METHODB(send_mouse_event, METH_VARARGS), METHODB(test_encode_mouse, METH_VARARGS), METHODB(send_mock_mouse_event_to_window, METH_VARARGS), {NULL, NULL, 0, NULL} /* Sentinel */ @@ -739,6 +761,10 @@ static PyMethodDef module_methods[] = { bool init_mouse(PyObject *module) { + PyModule_AddIntMacro(module, PRESS); + PyModule_AddIntMacro(module, RELEASE); + PyModule_AddIntMacro(module, DRAG); + PyModule_AddIntMacro(module, MOVE); if (PyModule_AddFunctions(module, module_methods) != 0) return false; return true; } diff --git a/kitty_tests/keys.py b/kitty_tests/keys.py index b55c34552..a3d6ff34c 100644 --- a/kitty_tests/keys.py +++ b/kitty_tests/keys.py @@ -80,26 +80,25 @@ class TestParser(BaseTest): km(modify_key_bytes(base_key, num).decode('ascii')[1:], key) def test_encode_mouse_event(self): - PRESS, RELEASE, DRAG, MOVE = range(4) NORMAL_PROTOCOL, UTF8_PROTOCOL, SGR_PROTOCOL, URXVT_PROTOCOL = range(4) L, M, R = defines.GLFW_MOUSE_BUTTON_LEFT, defines.GLFW_MOUSE_BUTTON_MIDDLE, defines.GLFW_MOUSE_BUTTON_RIGHT protocol = SGR_PROTOCOL - def enc(button=L, action=PRESS, mods=0, x=1, y=1): + def enc(button=L, action=defines.PRESS, mods=0, x=1, y=1): return defines.test_encode_mouse(x, y, protocol, button, action, mods) self.ae(enc(), '<0;1;1M') - self.ae(enc(action=RELEASE), '<0;1;1m') - self.ae(enc(action=MOVE), '<35;1;1M') - self.ae(enc(action=DRAG), '<32;1;1M') + self.ae(enc(action=defines.RELEASE), '<0;1;1m') + self.ae(enc(action=defines.MOVE), '<35;1;1M') + self.ae(enc(action=defines.DRAG), '<32;1;1M') self.ae(enc(R), '<2;1;1M') - self.ae(enc(R, action=RELEASE), '<2;1;1m') - self.ae(enc(R, action=DRAG), '<34;1;1M') + self.ae(enc(R, action=defines.RELEASE), '<2;1;1m') + self.ae(enc(R, action=defines.DRAG), '<34;1;1M') self.ae(enc(M), '<1;1;1M') - self.ae(enc(M, action=RELEASE), '<1;1;1m') - self.ae(enc(M, action=DRAG), '<33;1;1M') + self.ae(enc(M, action=defines.RELEASE), '<1;1;1m') + self.ae(enc(M, action=defines.DRAG), '<33;1;1M') self.ae(enc(x=1234, y=5678), '<0;1234;5678M') self.ae(enc(mods=defines.GLFW_MOD_SHIFT), '<4;1;1M')