diff --git a/docs/changelog.rst b/docs/changelog.rst index 13b1a3f6d..fc9be69d1 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -102,6 +102,12 @@ To update |kitty|, :doc:`follow the instructions `. - Improve rendering of rounded corners by using a rectircle equation rather than a cubic bezier (:iss:`3409`) +- Graphics protocol: Add a control to allow clients to specify that the cursor + should not move when displaying an image (:iss:`3411`) + +- Fix marking of text not working on lines that contain zero cells + (:iss:`3403`) + 0.19.3 [2020-12-19] ------------------- @@ -138,6 +144,10 @@ To update |kitty|, :doc:`follow the instructions `. - Wayland: Fix key repeat being stopped by the release of an unrelated key (:iss:`2191`) +- Wayland: Add support for the text input protocol (:iss:`3410`) + +- Wayland: Fix mouse handling when using client side decorations + - Add an option, :opt:`detect_urls` to control whether kitty will detect URLs when the mouse moves over them (:pull:`3118`) diff --git a/docs/graphics-protocol.rst b/docs/graphics-protocol.rst index f8bb826e8..0bef2396a 100644 --- a/docs/graphics-protocol.rst +++ b/docs/graphics-protocol.rst @@ -401,6 +401,12 @@ colors. number of rows in the image placement rectangle. If either of these cause the cursor to leave either the screen or the scroll area, the exact positioning of the cursor is undefined, and up to implementations. + The client can ask the terminal emulator to not move the cursor at all + by specifying ``C=1`` in the command, which sets the cursor movement policy + to no movement for placing the current image. + +.. versionadded:: 0.20.0 + Support for the C=1 cursor movement policy Deleting images @@ -680,6 +686,8 @@ Key Value Default Description ``Y`` Positive integer ``0`` The y-offset within the first cell at which to start displaying the image ``c`` Positive integer ``0`` The number of columns to display the image over ``r`` Positive integer ``0`` The number of rows to display the image over +``C`` Positive integer ``0`` Cursor movement policy. ``0`` is the default, to move the cursor to after the image. + ``1`` is to not move the cursor at all when placing the image. ``z`` 32-bit integer ``0`` The *z-index* vertical stacking order of the image **Keys for animation frame loading** diff --git a/gen-apc-parsers.py b/gen-apc-parsers.py index 6e5d6bccb..83b0b6cd5 100755 --- a/gen-apc-parsers.py +++ b/gen-apc-parsers.py @@ -273,6 +273,7 @@ def graphics_parser() -> None: 'X': ('cell_x_offset', 'uint'), 'Y': ('cell_y_offset', 'uint'), 'z': ('z_index', 'int'), + 'C': ('cursor_movement', 'uint'), } text = generate('parse_graphics_code', 'screen_handle_graphics_command', 'graphics_command', keymap, 'GraphicsCommand') write_header(text, 'kitty/parse-graphics-command.h') diff --git a/glfw/cocoa_window.m b/glfw/cocoa_window.m index b319e5060..0b69f103d 100644 --- a/glfw/cocoa_window.m +++ b/glfw/cocoa_window.m @@ -1073,7 +1073,7 @@ is_ascii_control_char(char x) { if (input_source_changed) { debug_key(@"Input source changed, clearing pre-edit text and resetting deadkey state\n"); glfw_keyevent.text = NULL; - glfw_keyevent.ime_state = 1; + glfw_keyevent.ime_state = GLFW_IME_PREEDIT_CHANGED; window->ns.deadKeyState = 0; _glfwInputKeyboard(window, &glfw_keyevent); // clear pre-edit text } @@ -1110,14 +1110,14 @@ is_ascii_control_char(char x) { // 0x75 is the delete key which needs to be ignored during a compose sequence debug_key(@"Sending pre-edit text for dead key (text: %@ markedText: %@).\n", @(format_text(_glfw.ns.text)), markedText); glfw_keyevent.text = [[markedText string] UTF8String]; - glfw_keyevent.ime_state = 1; + glfw_keyevent.ime_state = GLFW_IME_PREEDIT_CHANGED; _glfwInputKeyboard(window, &glfw_keyevent); // update pre-edit text return; } if (in_compose_sequence) { debug_key(@"Clearing pre-edit text at end of compose sequence\n"); glfw_keyevent.text = NULL; - glfw_keyevent.ime_state = 1; + glfw_keyevent.ime_state = GLFW_IME_PREEDIT_CHANGED; _glfwInputKeyboard(window, &glfw_keyevent); // clear pre-edit text } } @@ -1127,11 +1127,11 @@ is_ascii_control_char(char x) { if (!window->ns.deadKeyState) { if ([self hasMarkedText]) { glfw_keyevent.text = [[markedText string] UTF8String]; - glfw_keyevent.ime_state = 1; + glfw_keyevent.ime_state = GLFW_IME_PREEDIT_CHANGED; _glfwInputKeyboard(window, &glfw_keyevent); // update pre-edit text } else if (previous_has_marked_text) { glfw_keyevent.text = NULL; - glfw_keyevent.ime_state = 1; + glfw_keyevent.ime_state = GLFW_IME_PREEDIT_CHANGED; _glfwInputKeyboard(window, &glfw_keyevent); // clear pre-edit text } if (([self hasMarkedText] || previous_has_marked_text) && !_glfw.ns.text[0]) { @@ -1140,7 +1140,7 @@ is_ascii_control_char(char x) { } } glfw_keyevent.text = _glfw.ns.text; - glfw_keyevent.ime_state = 0; + glfw_keyevent.ime_state = GLFW_IME_NONE; add_alternate_keys(&glfw_keyevent, event); _glfwInputKeyboard(window, &glfw_keyevent); } @@ -1295,11 +1295,11 @@ is_ascii_control_char(char x) { [[markedText mutableString] setString:@""]; } -void _glfwPlatformUpdateIMEState(_GLFWwindow *w, int which, int a, int b, int c, int d) { - [w->ns.view updateIMEStateFor: which left:(CGFloat)a top:(CGFloat)b cellWidth:(CGFloat)c cellHeight:(CGFloat)d]; +void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) { + [w->ns.view updateIMEStateFor: ev->type left:(CGFloat)ev->cursor.left top:(CGFloat)ev->cursor.top cellWidth:(CGFloat)ev->cursor.width cellHeight:(CGFloat)ev->cursor.height]; } -- (void)updateIMEStateFor:(int)which +- (void)updateIMEStateFor:(GLFWIMEUpdateType)which left:(CGFloat)left top:(CGFloat)top cellWidth:(CGFloat)cellWidth diff --git a/glfw/glfw3.h b/glfw/glfw3.h index e7ee09fda..14fce1227 100644 --- a/glfw/glfw3.h +++ b/glfw/glfw3.h @@ -1179,12 +1179,34 @@ typedef struct GLFWwindow GLFWwindow; * @ingroup input */ typedef struct GLFWcursor GLFWcursor; + typedef enum { GLFW_RELEASE = 0, GLFW_PRESS = 1, GLFW_REPEAT = 2 } GLFWKeyAction; +typedef enum { + GLFW_IME_NONE, + GLFW_IME_PREEDIT_CHANGED, + GLFW_IME_COMMIT_TEXT +} GLFWIMEState; + +typedef enum { + GLFW_IME_UPDATE_FOCUS = 1, + GLFW_IME_UPDATE_CURSOR_POSITION = 2 +} GLFWIMEUpdateType; + +typedef struct GLFWIMEUpdateEvent { + GLFWIMEUpdateType type; + const char *before_text, *at_text, *after_text; + bool focused; + struct { + int left, top, width, height; + } cursor; +} GLFWIMEUpdateEvent; + + typedef struct GLFWkeyevent { // The [keyboard key](@ref keys) that was pressed or released. @@ -1203,9 +1225,9 @@ typedef struct GLFWkeyevent const char *text; // Used for Input Method events. Zero for normal key events. - // A value of 1 means the pre-edit text for the input event has been changed. - // A value of 2 means the text should be committed. - int ime_state; + // A value of GLFW_IME_PREEDIT_CHANGED means the pre-edit text for the input event has been changed. + // A value of GLFW_IME_COMMIT_TEXT means the text should be committed. + GLFWIMEState ime_state; } GLFWkeyevent; /*! @brief The function pointer type for error callbacks. @@ -4533,16 +4555,12 @@ GLFWAPI GLFWkeyboardfun glfwSetKeyboardCallback(GLFWwindow* window, GLFWkeyboard * Used to notify the IME system of changes in state such as focus gained/lost * and text cursor position. * - * @param which: What data to notify. 1 means focus and 2 means cursor position. - * @param a, b, c, d: Interpreted based on the value of which. When which is 1 - * a is interpreted as a boolean indicating focus gained/lost. When which is 2 - * a, b, c, d are the cursor x, y, width and height values (in the window co-ordinate - * system). + * @param ev: What data to notify. * * @ingroup input * @since Added in version 4.0 */ -GLFWAPI void glfwUpdateIMEState(GLFWwindow* window, int which, int a, int b, int c, int d); +GLFWAPI void glfwUpdateIMEState(GLFWwindow* window, const GLFWIMEUpdateEvent *ev); /*! @brief Sets the mouse button callback. diff --git a/glfw/ibus_glfw.c b/glfw/ibus_glfw.c index 83b0f4ac8..0e36fcd5d 100644 --- a/glfw/ibus_glfw.c +++ b/glfw/ibus_glfw.c @@ -107,7 +107,7 @@ get_ibus_text_from_message(DBusMessage *msg) { } static inline void -send_text(const char *text, int ime_state) { +send_text(const char *text, GLFWIMEState ime_state) { _GLFWwindow *w = _glfwFocusedWindow(); if (w && w->callbacks.keyboard) { GLFWkeyevent fake_ev = {.action = GLFW_PRESS}; @@ -130,11 +130,11 @@ message_handler(DBusConnection *conn UNUSED, DBusMessage *msg, void *user_data) case 0: text = get_ibus_text_from_message(msg); debug("IBUS: CommitText: '%s'\n", text ? text : "(nil)"); - send_text(text, 2); + send_text(text, GLFW_IME_COMMIT_TEXT); break; case 1: text = get_ibus_text_from_message(msg); - send_text(text, 1); + send_text(text, GLFW_IME_PREEDIT_CHANGED); debug("IBUS: UpdatePreeditText: '%s'\n", text ? text : "(nil)"); break; case 2: diff --git a/glfw/input.c b/glfw/input.c index c6a7569e6..f07bccfed 100644 --- a/glfw/input.c +++ b/glfw/input.c @@ -1010,13 +1010,13 @@ GLFWAPI GLFWkeyboardfun glfwSetKeyboardCallback(GLFWwindow* handle, GLFWkeyboard return cbfun; } -GLFWAPI void glfwUpdateIMEState(GLFWwindow* handle, int which, int a, int b, int c, int d) { +GLFWAPI void glfwUpdateIMEState(GLFWwindow* handle, const GLFWIMEUpdateEvent *ev) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT(); #if defined(_GLFW_X11) || defined(_GLFW_WAYLAND) || defined(_GLFW_COCOA) - _glfwPlatformUpdateIMEState(window, which, a, b, c, d); + _glfwPlatformUpdateIMEState(window, ev); #else (void)window; (void)which; (void)a; (void)b; (void)c; (void)d; #endif diff --git a/glfw/internal.h b/glfw/internal.h index 58edb4ce9..85e6da567 100644 --- a/glfw/internal.h +++ b/glfw/internal.h @@ -728,7 +728,7 @@ void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, bool enabled); void _glfwPlatformSetWindowFloating(_GLFWwindow* window, bool enabled); void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, bool enabled); void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity); -void _glfwPlatformUpdateIMEState(_GLFWwindow *w, int which, int a, int b, int c, int d); +void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev); void _glfwPlatformPollEvents(void); void _glfwPlatformWaitEvents(void); diff --git a/glfw/source-info.json b/glfw/source-info.json index ee7c666f3..c333e8508 100644 --- a/glfw/source-info.json +++ b/glfw/source-info.json @@ -55,6 +55,7 @@ "wl_platform.h", "posix_thread.h", "wl_cursors.h", + "wl_text_input.h", "xkb_glfw.h", "dbus_glfw.h", "ibus_glfw.h", @@ -73,13 +74,15 @@ "unstable/pointer-constraints/pointer-constraints-unstable-v1.xml", "unstable/idle-inhibit/idle-inhibit-unstable-v1.xml", "unstable/xdg-decoration/xdg-decoration-unstable-v1.xml", - "unstable/primary-selection/primary-selection-unstable-v1.xml" + "unstable/primary-selection/primary-selection-unstable-v1.xml", + "unstable/text-input/text-input-unstable-v3.xml" ], "sources": [ "wl_init.c", "wl_monitor.c", "wl_window.c", "wl_cursors.c", + "wl_text_input.c", "posix_thread.c", "xkb_glfw.c", "dbus_glfw.c", diff --git a/glfw/wl_init.c b/glfw/wl_init.c index ff9a35cd6..c978dd58a 100644 --- a/glfw/wl_init.c +++ b/glfw/wl_init.c @@ -173,6 +173,8 @@ static void setCursor(GLFWCursorShape shape, _GLFWwindow* window) _glfw.wl.cursorPreviousShape = shape; } +#define x window->wl.allCursorPosX +#define y window->wl.allCursorPosY static void pointerHandleMotion(void* data UNUSED, struct wl_pointer* pointer UNUSED, uint32_t time UNUSED, @@ -181,15 +183,14 @@ static void pointerHandleMotion(void* data UNUSED, { _GLFWwindow* window = _glfw.wl.pointerFocus; GLFWCursorShape cursorShape = GLFW_ARROW_CURSOR; - double x, y; if (!window) return; if (window->cursorMode == GLFW_CURSOR_DISABLED) return; - x = wl_fixed_to_double(sx); - y = wl_fixed_to_double(sy); + window->wl.allCursorPosX = wl_fixed_to_double(sx); + window->wl.allCursorPosY = wl_fixed_to_double(sy); switch (window->wl.decorations.focus) { @@ -252,7 +253,7 @@ static void pointerHandleButton(void* data UNUSED, case mainWindow: break; case topDecoration: - if (window->wl.cursorPosY < _GLFW_DECORATION_WIDTH) + if (y < _GLFW_DECORATION_WIDTH) edges = XDG_TOPLEVEL_RESIZE_EDGE_TOP; else { @@ -261,21 +262,21 @@ static void pointerHandleButton(void* data UNUSED, } break; case leftDecoration: - if (window->wl.cursorPosY < _GLFW_DECORATION_WIDTH) + if (y < _GLFW_DECORATION_WIDTH) edges = XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT; else edges = XDG_TOPLEVEL_RESIZE_EDGE_LEFT; break; case rightDecoration: - if (window->wl.cursorPosY < _GLFW_DECORATION_WIDTH) + if (y < _GLFW_DECORATION_WIDTH) edges = XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT; else edges = XDG_TOPLEVEL_RESIZE_EDGE_RIGHT; break; case bottomDecoration: - if (window->wl.cursorPosX < _GLFW_DECORATION_WIDTH) + if (x < _GLFW_DECORATION_WIDTH) edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT; - else if (window->wl.cursorPosX > window->wl.width + _GLFW_DECORATION_WIDTH) + else if (x > window->wl.width + _GLFW_DECORATION_WIDTH) edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT; else edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM; @@ -293,10 +294,7 @@ static void pointerHandleButton(void* data UNUSED, { if (window->wl.decorations.focus != mainWindow && window->wl.xdg.toplevel) { - xdg_toplevel_show_window_menu(window->wl.xdg.toplevel, - _glfw.wl.seat, serial, - (int32_t)window->wl.cursorPosX, - (int32_t)window->wl.cursorPosY); + xdg_toplevel_show_window_menu(window->wl.xdg.toplevel, _glfw.wl.seat, serial, (int32_t)x, (int32_t)y - _GLFW_DECORATION_TOP); return; } } @@ -318,6 +316,8 @@ static void pointerHandleButton(void* data UNUSED, : GLFW_RELEASE, _glfw.wl.xkb.states.modifiers); } +#undef x +#undef y static void pointerHandleAxis(void* data UNUSED, struct wl_pointer* pointer UNUSED, @@ -584,6 +584,7 @@ static void registryHandleGlobal(void* data UNUSED, if (_glfw.wl.primarySelectionDeviceManager && !_glfw.wl.primarySelectionDevice) { _glfwSetupWaylandPrimarySelectionDevice(); } + _glfwWaylandInitTextInput(); } } else if (strcmp(interface, "xdg_wm_base") == 0) @@ -617,6 +618,11 @@ static void registryHandleGlobal(void* data UNUSED, &zwp_pointer_constraints_v1_interface, 1); } + else if (strcmp(interface, GLFW_WAYLAND_TEXT_INPUT_INTERFACE_NAME) == 0) + { + _glfwWaylandBindTextInput(registry, name); + _glfwWaylandInitTextInput(); + } else if (strcmp(interface, "zwp_idle_inhibit_manager_v1") == 0) { _glfw.wl.idleInhibitManager = @@ -847,6 +853,7 @@ void _glfwPlatformTerminate(void) zwp_relative_pointer_manager_v1_destroy(_glfw.wl.relativePointerManager); if (_glfw.wl.pointerConstraints) zwp_pointer_constraints_v1_destroy(_glfw.wl.pointerConstraints); + _glfwWaylandDestroyTextInput(); if (_glfw.wl.idleInhibitManager) zwp_idle_inhibit_manager_v1_destroy(_glfw.wl.idleInhibitManager); if (_glfw.wl.dataSourceForClipboard) diff --git a/glfw/wl_platform.h b/glfw/wl_platform.h index 7aaed8f1e..f29da2564 100644 --- a/glfw/wl_platform.h +++ b/glfw/wl_platform.h @@ -59,6 +59,7 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR #include "wayland-pointer-constraints-unstable-v1-client-protocol.h" #include "wayland-idle-inhibit-unstable-v1-client-protocol.h" #include "wayland-primary-selection-unstable-v1-client-protocol.h" +#include "wl_text_input.h" #define _glfw_dlopen(name) dlopen(name, RTLD_LAZY | RTLD_LOCAL) #define _glfw_dlclose(handle) dlclose(handle) @@ -131,7 +132,7 @@ typedef struct _GLFWwindowWayland } xdg; _GLFWcursor* currentCursor; - double cursorPosX, cursorPosY; + double cursorPosX, cursorPosY, allCursorPosX, allCursorPosY; char* title; char appId[256]; diff --git a/glfw/wl_text_input.c b/glfw/wl_text_input.c new file mode 100644 index 000000000..e8a6c1673 --- /dev/null +++ b/glfw/wl_text_input.c @@ -0,0 +1,129 @@ +/* + * wl_text_input.c + * Copyright (C) 2021 Kovid Goyal + * + * Distributed under terms of the GPL3 license. + */ + +#include "wl_text_input.h" +#include "internal.h" +#include "wayland-text-input-unstable-v3-client-protocol.h" +#define debug(...) if (_glfw.hints.init.debugKeyboard) printf(__VA_ARGS__); + +static struct zwp_text_input_v3* text_input; +static struct zwp_text_input_manager_v3* text_input_manager; + +static void commit(void) { if (text_input) zwp_text_input_v3_commit (text_input); } + +static void +text_input_enter(void *data UNUSED, struct zwp_text_input_v3 *text_input UNUSED, struct wl_surface *surface UNUSED) { + debug("text-input: enter event\n"); + if (text_input) { + zwp_text_input_v3_enable(text_input); + zwp_text_input_v3_set_content_type(text_input, ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE, ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TERMINAL); + commit(); + } +} + +static void +text_input_leave(void *data UNUSED, struct zwp_text_input_v3 *text_input UNUSED, struct wl_surface *surface UNUSED) { + debug("text-input: leave event\n"); + if (text_input) { + zwp_text_input_v3_disable(text_input); + commit(); + } +} + +static inline void +send_text(const char *text, GLFWIMEState ime_state) { + _GLFWwindow *w = _glfwFocusedWindow(); + if (w && w->callbacks.keyboard) { + GLFWkeyevent fake_ev = {.action = GLFW_PRESS}; + fake_ev.text = text; + fake_ev.ime_state = ime_state; + w->callbacks.keyboard((GLFWwindow*) w, &fake_ev); + } +} + +static void +text_input_preedit_string( + void *data UNUSED, + struct zwp_text_input_v3 *text_input UNUSED, + const char *text, + int32_t cursor_begin, + int32_t cursor_end +) { + debug("text-input: preedit_string event: text: %s cursor_begin: %d cursor_end: %d\n", text, cursor_begin, cursor_end); + send_text(text, GLFW_IME_PREEDIT_CHANGED); +} + +static void +text_input_commit_string(void *data UNUSED, struct zwp_text_input_v3 *text_input UNUSED, const char *text) { + debug("text-input: commit_string event: text: %s\n", text); + send_text(text, GLFW_IME_COMMIT_TEXT); +} + +static void +text_input_delete_surrounding_text( + void *data UNUSED, + struct zwp_text_input_v3 *zwp_text_input_v3 UNUSED, + uint32_t before_length, + uint32_t after_length) { + debug("text-input: delete_surrounding_text event: before_length: %u after_length: %u\n", before_length, after_length); +} + +static void +text_input_done(void *data UNUSED, struct zwp_text_input_v3 *zwp_text_input_v3 UNUSED, uint32_t serial UNUSED) { + debug("text-input: done event: serial: %u\n", serial); +} + +void +_glfwWaylandBindTextInput(struct wl_registry* registry, uint32_t name) { + if (!text_input_manager) text_input_manager = wl_registry_bind(registry, name, &zwp_text_input_manager_v3_interface, 1); +} + +void +_glfwWaylandInitTextInput(void) { + static const struct zwp_text_input_v3_listener text_input_listener = { + .enter = text_input_enter, + .leave = text_input_leave, + .preedit_string = text_input_preedit_string, + .commit_string = text_input_commit_string, + .delete_surrounding_text = text_input_delete_surrounding_text, + .done = text_input_done, + }; + if (!text_input) { + if (text_input_manager && _glfw.wl.seat) { + text_input = zwp_text_input_manager_v3_get_text_input( + text_input_manager, _glfw.wl.seat); + if (text_input) zwp_text_input_v3_add_listener(text_input, &text_input_listener, NULL); + } + } +} + +void +_glfwWaylandDestroyTextInput(void) { + if (text_input) zwp_text_input_v3_destroy(text_input); + if (text_input_manager) zwp_text_input_manager_v3_destroy(text_input_manager); + text_input = NULL; text_input_manager = NULL; +} + +void +_glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) { + if (!text_input) return; + switch(ev->type) { + case GLFW_IME_UPDATE_FOCUS: + debug("\ntext-input: updating IME focus state, focused: %d\n", ev->focused); + if (ev->focused) zwp_text_input_v3_enable(text_input); else zwp_text_input_v3_disable(text_input); + commit(); + break; + case GLFW_IME_UPDATE_CURSOR_POSITION: { + const int scale = w->wl.scale; + const int left = ev->cursor.left / scale, top = ev->cursor.top / scale, width = ev->cursor.width / scale, height = ev->cursor.height / scale; + debug("\ntext-input: updating cursor position: left=%d top=%d width=%d height=%d\n", left, top, width, height); + zwp_text_input_v3_set_cursor_rectangle(text_input, left, top, width, height); + commit(); + } + break; + } +} diff --git a/glfw/wl_text_input.h b/glfw/wl_text_input.h new file mode 100644 index 000000000..a01934758 --- /dev/null +++ b/glfw/wl_text_input.h @@ -0,0 +1,14 @@ +/* + * Copyright (C) 2021 Kovid Goyal + * + * Distributed under terms of the GPL3 license. + */ + +#pragma once +#include + +#define GLFW_WAYLAND_TEXT_INPUT_INTERFACE_NAME "zwp_text_input_manager_v3" + +void _glfwWaylandBindTextInput(struct wl_registry* registry, uint32_t name); +void _glfwWaylandInitTextInput(void); +void _glfwWaylandDestroyTextInput(void); diff --git a/glfw/wl_window.c b/glfw/wl_window.c index c3b897602..dffcb4beb 100644 --- a/glfw/wl_window.c +++ b/glfw/wl_window.c @@ -2104,11 +2104,6 @@ VkResult _glfwPlatformCreateWindowSurface(VkInstance instance, return err; } -void -_glfwPlatformUpdateIMEState(_GLFWwindow *w, int which, int a, int b, int c, int d) { - glfw_xkb_update_ime_state(w, &_glfw.wl.xkb, which, a, b, c, d); -} - static void frame_handle_redraw(void *data, struct wl_callback *callback, uint32_t time UNUSED) { _GLFWwindow* window = (_GLFWwindow*) data; diff --git a/glfw/x11_window.c b/glfw/x11_window.c index 1389e171a..e00545c6c 100644 --- a/glfw/x11_window.c +++ b/glfw/x11_window.c @@ -3085,8 +3085,8 @@ VkResult _glfwPlatformCreateWindowSurface(VkInstance instance, } void -_glfwPlatformUpdateIMEState(_GLFWwindow *w, int which, int a, int b, int c, int d) { - glfw_xkb_update_ime_state(w, &_glfw.x11.xkb, which, a, b, c, d); +_glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) { + glfw_xkb_update_ime_state(w, &_glfw.x11.xkb, ev); } ////////////////////////////////////////////////////////////////////////// diff --git a/glfw/xkb_glfw.c b/glfw/xkb_glfw.c index 21a2018a5..ca3d148b1 100644 --- a/glfw/xkb_glfw.c +++ b/glfw/xkb_glfw.c @@ -349,7 +349,9 @@ glfw_xkb_create_context(_GLFWXKBData *xkb) { "Failed to initialize XKB context"); return false; } +#ifndef _GLFW_WAYLAND glfw_connect_to_ibus(&xkb->ibus); +#endif return true; } @@ -555,16 +557,16 @@ format_xkb_mods(_GLFWXKBData *xkb, const char* name, xkb_mod_mask_t mods) { } void -glfw_xkb_update_ime_state(_GLFWwindow *w, _GLFWXKBData *xkb, int which, int a, int b, int c, int d) { +glfw_xkb_update_ime_state(_GLFWwindow *w, _GLFWXKBData *xkb, const GLFWIMEUpdateEvent *ev) { int x = 0, y = 0; - switch(which) { - case 1: - glfw_ibus_set_focused(&xkb->ibus, a ? true : false); + switch(ev->type) { + case GLFW_IME_UPDATE_FOCUS: + glfw_ibus_set_focused(&xkb->ibus, ev->focused); break; - case 2: + case GLFW_IME_UPDATE_CURSOR_POSITION: _glfwPlatformGetWindowPos(w, &x, &y); - x += a; y += b; - glfw_ibus_set_cursor_geometry(&xkb->ibus, x, y, c, d); + x += ev->cursor.left; y += ev->cursor.top; + glfw_ibus_set_cursor_geometry(&xkb->ibus, x, y, ev->cursor.width, ev->cursor.height); break; } } @@ -575,7 +577,7 @@ glfw_xkb_key_from_ime(_GLFWIBUSKeyEvent *ev, bool handled_by_ime, bool failed) { if (failed && window && window->callbacks.keyboard) { // notify application to remove any existing pre-edit text GLFWkeyevent fake_ev = {.action = GLFW_PRESS}; - fake_ev.ime_state = 1; + fake_ev.ime_state = GLFW_IME_PREEDIT_CHANGED; window->callbacks.keyboard((GLFWwindow*) window, &fake_ev); } static xkb_keycode_t last_handled_press_keycode = 0; @@ -594,7 +596,7 @@ glfw_xkb_key_from_ime(_GLFWIBUSKeyEvent *ev, bool handled_by_ime, bool failed) { format_mods(ev->glfw_ev.mods), ev->glfw_ev.text ); - ev->glfw_ev.ime_state = 0; + ev->glfw_ev.ime_state = GLFW_IME_NONE; _glfwInputKeyboard(window, &ev->glfw_ev); } else debug("↳ discarded\n"); if (!is_release && handled_by_ime) diff --git a/glfw/xkb_glfw.h b/glfw/xkb_glfw.h index 39b74c851..d2c086a3f 100644 --- a/glfw/xkb_glfw.h +++ b/glfw/xkb_glfw.h @@ -92,5 +92,5 @@ const char* glfw_xkb_keysym_name(xkb_keysym_t sym); xkb_keysym_t glfw_xkb_sym_for_key(uint32_t key); void glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t keycode, int action); int glfw_xkb_keysym_from_name(const char *name, bool case_sensitive); -void glfw_xkb_update_ime_state(_GLFWwindow *w, _GLFWXKBData *xkb, int which, int a, int b, int c, int d); +void glfw_xkb_update_ime_state(_GLFWwindow *w, _GLFWXKBData *xkb, const GLFWIMEUpdateEvent *ev); void glfw_xkb_key_from_ime(_GLFWIBUSKeyEvent *ev, bool handled_by_ime, bool failed); diff --git a/kitty/fonts.c b/kitty/fonts.c index 1d9a3a236..b6f508282 100644 --- a/kitty/fonts.c +++ b/kitty/fonts.c @@ -961,7 +961,7 @@ shape_run(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells bool is_special = is_special_glyph(glyph_id, font, &G(current_cell_data)); bool is_empty = is_special && is_empty_glyph(glyph_id, font); uint32_t num_codepoints_used_by_glyph = 0; - bool is_last_glyph = G(glyph_idx) == G(num_glyphs) - 1; + bool is_last_glyph = G(glyph_idx) == G(num_glyphs) - 1, is_first_glyph = G(glyph_idx) == 0; Group *current_group = G(groups) + G(group_idx); if (is_last_glyph) { num_codepoints_used_by_glyph = UINT32_MAX; @@ -977,7 +977,8 @@ shape_run(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells else add_to_current_group = ligature_type == INFINITE_LIGATURE_MIDDLE || ligature_type == INFINITE_LIGATURE_END || is_empty; } else { if (is_special) { - if (font->spacer_strategy == SPACERS_BEFORE) add_to_current_group = G(prev_was_empty); + if (!is_first_glyph && !current_group->num_cells) add_to_current_group = true; + else if (font->spacer_strategy == SPACERS_BEFORE) add_to_current_group = G(prev_was_empty); else add_to_current_group = is_empty; } else { add_to_current_group = !G(prev_was_special); diff --git a/kitty/fonts/box_drawing.py b/kitty/fonts/box_drawing.py index 0d3f85ad2..2bd3bce88 100644 --- a/kitty/fonts/box_drawing.py +++ b/kitty/fonts/box_drawing.py @@ -464,7 +464,8 @@ def rectircle_equations( xexp = radius / cell_width pow = math.pow left_quadrants, lower_quadrants = {'╭': (True, False), '╮': (False, False), '╰': (True, True), '╯': (False, True)}[which] - adjust_left_quadrant = (cell_width // supersample_factor % 2) * supersample_factor + cell_width_is_odd = (cell_width // supersample_factor) % 2 + adjust_x = cell_width_is_odd * supersample_factor if lower_quadrants: def y(t: float) -> float: # 0 -> top of cell, 1 -> middle of cell @@ -478,7 +479,7 @@ def rectircle_equations( if left_quadrants: def x(t: float) -> float: xterm = 1 - pow(t, yexp) - return math.floor(cell_width - abs(a * pow(xterm, xexp)) - adjust_left_quadrant) + return math.floor(cell_width - abs(a * pow(xterm, xexp)) - adjust_x) else: def x(t: float) -> float: xterm = 1 - pow(t, yexp) diff --git a/kitty/glfw-wrapper.h b/kitty/glfw-wrapper.h index 797085bc2..101cde393 100644 --- a/kitty/glfw-wrapper.h +++ b/kitty/glfw-wrapper.h @@ -917,12 +917,34 @@ typedef struct GLFWwindow GLFWwindow; * @ingroup input */ typedef struct GLFWcursor GLFWcursor; + typedef enum { GLFW_RELEASE = 0, GLFW_PRESS = 1, GLFW_REPEAT = 2 } GLFWKeyAction; +typedef enum { + GLFW_IME_NONE, + GLFW_IME_PREEDIT_CHANGED, + GLFW_IME_COMMIT_TEXT +} GLFWIMEState; + +typedef enum { + GLFW_IME_UPDATE_FOCUS = 1, + GLFW_IME_UPDATE_CURSOR_POSITION = 2 +} GLFWIMEUpdateType; + +typedef struct GLFWIMEUpdateEvent { + GLFWIMEUpdateType type; + const char *before_text, *at_text, *after_text; + bool focused; + struct { + int left, top, width, height; + } cursor; +} GLFWIMEUpdateEvent; + + typedef struct GLFWkeyevent { // The [keyboard key](@ref keys) that was pressed or released. @@ -941,9 +963,9 @@ typedef struct GLFWkeyevent const char *text; // Used for Input Method events. Zero for normal key events. - // A value of 1 means the pre-edit text for the input event has been changed. - // A value of 2 means the text should be committed. - int ime_state; + // A value of GLFW_IME_PREEDIT_CHANGED means the pre-edit text for the input event has been changed. + // A value of GLFW_IME_COMMIT_TEXT means the text should be committed. + GLFWIMEState ime_state; } GLFWkeyevent; /*! @brief The function pointer type for error callbacks. @@ -1920,7 +1942,7 @@ typedef GLFWkeyboardfun (*glfwSetKeyboardCallback_func)(GLFWwindow*, GLFWkeyboar GFW_EXTERN glfwSetKeyboardCallback_func glfwSetKeyboardCallback_impl; #define glfwSetKeyboardCallback glfwSetKeyboardCallback_impl -typedef void (*glfwUpdateIMEState_func)(GLFWwindow*, int, int, int, int, int); +typedef void (*glfwUpdateIMEState_func)(GLFWwindow*, const GLFWIMEUpdateEvent*); GFW_EXTERN glfwUpdateIMEState_func glfwUpdateIMEState_impl; #define glfwUpdateIMEState glfwUpdateIMEState_impl diff --git a/kitty/glfw.c b/kitty/glfw.c index b0ff141b3..6e8b87a1f 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -343,7 +343,8 @@ window_focus_callback(GLFWwindow *w, int focused) { global_state.callback_os_window->cursor_blink_zero_time = now; if (is_window_ready_for_callbacks()) { WINDOW_CALLBACK(on_focus, "O", focused ? Py_True : Py_False); - glfwUpdateIMEState(global_state.callback_os_window->handle, 1, focused, 0, 0, 0); + GLFWIMEUpdateEvent ev = { .type = GLFW_IME_UPDATE_FOCUS, .focused = focused }; + glfwUpdateIMEState(global_state.callback_os_window->handle, &ev); } request_tick_callback(); global_state.callback_os_window = NULL; diff --git a/kitty/graphics.c b/kitty/graphics.c index b3a9d687a..46694e0a5 100644 --- a/kitty/graphics.c +++ b/kitty/graphics.c @@ -699,7 +699,9 @@ handle_put_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, b update_src_rect(ref, img); update_dest_rect(ref, g->num_cells, g->num_lines, cell); // Move the cursor, the screen will take care of ensuring it is in bounds - c->x += ref->effective_num_cols; c->y += ref->effective_num_rows - 1; + if (g->cursor_movement != 1) { + c->x += ref->effective_num_cols; c->y += ref->effective_num_rows - 1; + } return img->client_id; } diff --git a/kitty/graphics.h b/kitty/graphics.h index 852b439a1..7343a80d6 100644 --- a/kitty/graphics.h +++ b/kitty/graphics.h @@ -10,7 +10,7 @@ typedef struct { unsigned char action, transmission_type, compressed, delete_action; - uint32_t format, more, id, image_number, data_sz, data_offset, placement_id, quiet; + uint32_t format, more, id, image_number, data_sz, data_offset, placement_id, quiet, cursor_movement; uint32_t width, height, x_offset, y_offset, data_height, data_width, num_cells, num_lines, cell_x_offset, cell_y_offset; int32_t z_index; size_t payload_sz; diff --git a/kitty/history.c b/kitty/history.c index 3a4ae5baf..bba7bb863 100644 --- a/kitty/history.c +++ b/kitty/history.c @@ -278,7 +278,7 @@ __str__(HistoryBuf *self) { if (lines == NULL) return PyErr_NoMemory(); for (index_type i = 0; i < self->count; i++) { init_line(self, index_of(self, i), self->line); - PyObject *t = line_as_unicode(self->line); + PyObject *t = line_as_unicode(self->line, false); if (t == NULL) { Py_CLEAR(lines); return NULL; } PyTuple_SET_ITEM(lines, i, t); } diff --git a/kitty/keys.c b/kitty/keys.c index 11be2dcb6..cde7bf4e3 100644 --- a/kitty/keys.c +++ b/kitty/keys.c @@ -85,7 +85,9 @@ update_ime_position(OSWindow *os_window, Window* w, Screen *screen) { unsigned int left = w->geometry.left, top = w->geometry.top; left += screen->cursor->x * cell_width; top += screen->cursor->y * cell_height; - glfwUpdateIMEState(global_state.callback_os_window->handle, 2, left, top, cell_width, cell_height); + GLFWIMEUpdateEvent ev = { .type = GLFW_IME_UPDATE_CURSOR_POSITION }; + ev.cursor.left = left; ev.cursor.top = top; ev.cursor.width = cell_width; ev.cursor.height = cell_height; + glfwUpdateIMEState(global_state.callback_os_window->handle, &ev); } void @@ -105,19 +107,19 @@ on_key_input(GLFWkeyevent *ev) { id_type active_window_id = w->id; switch(ev->ime_state) { - case 1: // update pre-edit text + case GLFW_IME_PREEDIT_CHANGED: update_ime_position(global_state.callback_os_window, w, screen); screen_draw_overlay_text(screen, text); debug("updated pre-edit text: '%s'\n", text); return; - case 2: // commit text + case GLFW_IME_COMMIT_TEXT: if (*text) { schedule_write_to_child(w->id, 1, text, strlen(text)); debug("committed pre-edit text: %s\n", text); } else debug("committed pre-edit text: (null)\n"); screen_draw_overlay_text(screen, NULL); return; - case 0: + case GLFW_IME_NONE: // for macOS, update ime position on every key input // because the position is required before next input #if defined(__APPLE__) diff --git a/kitty/line-buf.c b/kitty/line-buf.c index e31d2d111..beaf4ceda 100644 --- a/kitty/line-buf.c +++ b/kitty/line-buf.c @@ -470,7 +470,7 @@ __str__(LineBuf *self) { if (lines == NULL) return PyErr_NoMemory(); for (index_type i = 0; i < self->ynum; i++) { init_line(self, self->line, self->line_map[i]); - PyObject *t = line_as_unicode(self->line); + PyObject *t = line_as_unicode(self->line, false); if (t == NULL) { Py_CLEAR(lines); return NULL; } PyTuple_SET_ITEM(lines, i, t); } diff --git a/kitty/line.c b/kitty/line.c index 86858c98b..5b84a8e14 100644 --- a/kitty/line.c +++ b/kitty/line.c @@ -229,7 +229,7 @@ cell_as_utf8_for_fallback(CPUCell *cell, char *buf) { PyObject* -unicode_in_range(Line *self, index_type start, index_type limit, bool include_cc, char leading_char) { +unicode_in_range(const Line *self, const index_type start, const index_type limit, const bool include_cc, const char leading_char, const bool skip_zero_cells) { size_t n = 0; static Py_UCS4 buf[4096]; if (leading_char) buf[n++] = leading_char; @@ -238,6 +238,7 @@ unicode_in_range(Line *self, index_type start, index_type limit, bool include_cc char_type ch = self->cpu_cells[i].ch; if (ch == 0) { if (previous_width == 2) { previous_width = 0; continue; }; + if (skip_zero_cells) continue; } if (ch == '\t') { buf[n++] = '\t'; @@ -255,8 +256,8 @@ unicode_in_range(Line *self, index_type start, index_type limit, bool include_cc } PyObject * -line_as_unicode(Line* self) { - return unicode_in_range(self, 0, xlimit_for_line(self), true, 0); +line_as_unicode(Line* self, bool skip_zero_cells) { + return unicode_in_range(self, 0, xlimit_for_line(self), true, 0, skip_zero_cells); } static PyObject* @@ -380,13 +381,19 @@ is_continued(Line* self, PyObject *a UNUSED) { static PyObject* __repr__(Line* self) { - PyObject *s = line_as_unicode(self); + PyObject *s = line_as_unicode(self, false); if (s == NULL) return NULL; PyObject *ans = PyObject_Repr(s); Py_CLEAR(s); return ans; } +static PyObject* +__str__(Line* self) { + return line_as_unicode(self, false); +} + + static PyObject* width(Line *self, PyObject *val) { #define width_doc "width(x) -> the width of the character at x" @@ -729,8 +736,8 @@ apply_mark(Line *line, const attrs_type mark, index_type *cell_pos, unsigned int #define MARK { line->gpu_cells[x].attrs &= ATTRS_MASK_WITHOUT_MARK; line->gpu_cells[x].attrs |= mark; } index_type x = *cell_pos; MARK; + (*match_pos)++; if (line->cpu_cells[x].ch) { - (*match_pos)++; if (line->cpu_cells[x].ch == '\t') { unsigned num_cells_to_skip_for_tab = line->cpu_cells[x].cc_idx[0]; while (num_cells_to_skip_for_tab && x + 1 < line->xnum && line->cpu_cells[x+1].ch == ' ') { @@ -784,7 +791,7 @@ mark_text_in_line(PyObject *marker, Line *line) { for (index_type i = 0; i < line->xnum; i++) line->gpu_cells[i].attrs &= ATTRS_MASK_WITHOUT_MARK; return; } - PyObject *text = line_as_unicode(line); + PyObject *text = line_as_unicode(line, false); if (PyUnicode_GET_LENGTH(text) > 0) { apply_marker(marker, line, text); } else { @@ -827,7 +834,7 @@ as_text_generic(PyObject *args, void *container, get_line_func get_line, index_t Py_CLEAR(ret); } } else { - t = line_as_unicode(line); + t = line_as_unicode(line, false); } if (t == NULL) goto end; ret = PyObject_CallFunctionObjArgs(callback, t, NULL); @@ -906,7 +913,7 @@ PyTypeObject Line_Type = { .tp_basicsize = sizeof(Line), .tp_dealloc = (destructor)dealloc, .tp_repr = (reprfunc)__repr__, - .tp_str = (reprfunc)line_as_unicode, + .tp_str = (reprfunc)__str__, .tp_as_sequence = &sequence_methods, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_richcompare = richcmp, diff --git a/kitty/lineops.h b/kitty/lineops.h index 2481f1cb4..77bde43eb 100644 --- a/kitty/lineops.h +++ b/kitty/lineops.h @@ -83,8 +83,8 @@ size_t cell_as_unicode(CPUCell *cell, bool include_cc, Py_UCS4 *buf, char_type); size_t cell_as_unicode_for_fallback(CPUCell *cell, Py_UCS4 *buf); size_t cell_as_utf8(CPUCell *cell, bool include_cc, char *buf, char_type); size_t cell_as_utf8_for_fallback(CPUCell *cell, char *buf); -PyObject* unicode_in_range(Line *self, index_type start, index_type limit, bool include_cc, char leading_char); -PyObject* line_as_unicode(Line *); +PyObject* unicode_in_range(const Line *self, const index_type start, const index_type limit, const bool include_cc, const char leading_char, const bool skip_zero_cells); +PyObject* line_as_unicode(Line *, bool); void linebuf_init_line(LineBuf *, index_type); void linebuf_clear(LineBuf *, char_type ch); diff --git a/kitty/parse-graphics-command.h b/kitty/parse-graphics-command.h index 484bb9231..ca64b5f6e 100644 --- a/kitty/parse-graphics-command.h +++ b/kitty/parse-graphics-command.h @@ -39,7 +39,8 @@ static inline void parse_graphics_code(Screen *screen, num_lines = 'r', cell_x_offset = 'X', cell_y_offset = 'Y', - z_index = 'z' + z_index = 'z', + cursor_movement = 'C' }; enum KEYS key = 'a'; @@ -121,6 +122,9 @@ static inline void parse_graphics_code(Screen *screen, case z_index: value_state = INT; break; + case cursor_movement: + value_state = UINT; + break; default: REPORT_ERROR("Malformed GraphicsCommand control block, invalid key " "character: 0x%x", @@ -144,9 +148,9 @@ static inline void parse_graphics_code(Screen *screen, case action: { g.action = screen->parser_buf[pos++] & 0xff; - if (g.action != 'q' && g.action != 'd' && g.action != 'p' && - g.action != 't' && g.action != 'T' && g.action != 'f' && - g.action != 'a') { + if (g.action != 't' && g.action != 'a' && g.action != 'T' && + g.action != 'f' && g.action != 'd' && g.action != 'p' && + g.action != 'q') { REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag " "value for action: 0x%x", g.action); @@ -156,16 +160,16 @@ static inline void parse_graphics_code(Screen *screen, case delete_action: { g.delete_action = screen->parser_buf[pos++] & 0xff; - if (g.delete_action != 'F' && g.delete_action != 'n' && - g.delete_action != 'x' && g.delete_action != 'I' && - g.delete_action != 'p' && g.delete_action != 'i' && - g.delete_action != 'C' && g.delete_action != 'Q' && - g.delete_action != 'A' && g.delete_action != 'z' && - g.delete_action != 'y' && g.delete_action != 'c' && - g.delete_action != 'Z' && g.delete_action != 'N' && - g.delete_action != 'X' && g.delete_action != 'q' && - g.delete_action != 'P' && g.delete_action != 'f' && - g.delete_action != 'Y' && g.delete_action != 'a') { + if (g.delete_action != 'Q' && g.delete_action != 'N' && + g.delete_action != 'z' && g.delete_action != 'p' && + g.delete_action != 'X' && g.delete_action != 'a' && + g.delete_action != 'y' && g.delete_action != 'f' && + g.delete_action != 'Z' && g.delete_action != 'F' && + g.delete_action != 'Y' && g.delete_action != 'n' && + g.delete_action != 'C' && g.delete_action != 'q' && + g.delete_action != 'c' && g.delete_action != 'i' && + g.delete_action != 'A' && g.delete_action != 'P' && + g.delete_action != 'I' && g.delete_action != 'x') { REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag " "value for delete_action: 0x%x", g.delete_action); @@ -175,8 +179,8 @@ static inline void parse_graphics_code(Screen *screen, case transmission_type: { g.transmission_type = screen->parser_buf[pos++] & 0xff; - if (g.transmission_type != 's' && g.transmission_type != 'f' && - g.transmission_type != 'd' && g.transmission_type != 't') { + if (g.transmission_type != 's' && g.transmission_type != 't' && + g.transmission_type != 'd' && g.transmission_type != 'f') { REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag " "value for transmission_type: 0x%x", g.transmission_type); @@ -264,6 +268,7 @@ static inline void parse_graphics_code(Screen *screen, U(num_lines); U(cell_x_offset); U(cell_y_offset); + U(cursor_movement); default: break; } @@ -322,8 +327,8 @@ static inline void parse_graphics_code(Screen *screen, } REPORT_VA_COMMAND( - "s {sc sc sc sc sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI si " - "sI} y#", + "s {sc sc sc sc sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI " + "si sI} y#", "graphics_command", "action", g.action, "delete_action", g.delete_action, "transmission_type", g.transmission_type, "compressed", g.compressed, "format", (unsigned int)g.format, "more", (unsigned int)g.more, "id", @@ -336,8 +341,9 @@ static inline void parse_graphics_code(Screen *screen, (unsigned int)g.data_sz, "data_offset", (unsigned int)g.data_offset, "num_cells", (unsigned int)g.num_cells, "num_lines", (unsigned int)g.num_lines, "cell_x_offset", (unsigned int)g.cell_x_offset, - "cell_y_offset", (unsigned int)g.cell_y_offset, "z_index", (int)g.z_index, - "payload_sz", g.payload_sz, payload, g.payload_sz); + "cell_y_offset", (unsigned int)g.cell_y_offset, "cursor_movement", + (unsigned int)g.cursor_movement, "z_index", (int)g.z_index, "payload_sz", + g.payload_sz, payload, g.payload_sz); screen_handle_graphics_command(screen, &g, payload); } diff --git a/kitty/screen.c b/kitty/screen.c index 094cdbcfe..5ffedf9ac 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -1978,7 +1978,7 @@ text_for_range(Screen *self, const Selection *sel, bool insert_newlines) { Line *line = range_line_(self, y); XRange xr = xrange_for_iteration(&idata, y, line); char leading_char = (i > 0 && insert_newlines && !line->continued) ? '\n' : 0; - PyObject *text = unicode_in_range(line, xr.x, xr.x_limit, true, leading_char); + PyObject *text = unicode_in_range(line, xr.x, xr.x_limit, true, leading_char, false); if (text == NULL) { Py_DECREF(ans); return PyErr_NoMemory(); } PyTuple_SET_ITEM(ans, i, text); } diff --git a/kitty_tests/fonts.py b/kitty_tests/fonts.py index b565e53e2..c8461ac93 100644 --- a/kitty_tests/fonts.py +++ b/kitty_tests/fonts.py @@ -116,6 +116,7 @@ class Rendering(BaseTest): self.ae(groups('|\U0001F601|\U0001F64f|\U0001F63a|'), [(1, 1), (2, 1), (1, 1), (2, 1), (1, 1), (2, 1), (1, 1)]) self.ae(groups('He\u0347\u0305llo\u0337,', font='LiberationMono-Regular.ttf'), [(1, 1), (1, 3), (1, 1), (1, 1), (1, 2), (1, 1)]) + self.ae(groups('i\u0332\u0308', font='LiberationMono-Regular.ttf'), [(1, 2)]) def test_emoji_presentation(self): s = self.create_screen() diff --git a/kitty_tests/graphics.py b/kitty_tests/graphics.py index 5b258a18c..e39c655a0 100644 --- a/kitty_tests/graphics.py +++ b/kitty_tests/graphics.py @@ -124,9 +124,15 @@ def put_helpers(self, cw, ch): s = self.create_screen(10, 5, cell_width=cw, cell_height=ch) return s, 2 / s.columns, 2 / s.lines - def put_cmd(z=0, num_cols=0, num_lines=0, x_off=0, y_off=0, width=0, height=0, cell_x_off=0, cell_y_off=0, placement_id=0): - return 'z=%d,c=%d,r=%d,x=%d,y=%d,w=%d,h=%d,X=%d,Y=%d,p=%d' % ( - z, num_cols, num_lines, x_off, y_off, width, height, cell_x_off, cell_y_off, placement_id) + def put_cmd( + z=0, num_cols=0, num_lines=0, x_off=0, y_off=0, width=0, + height=0, cell_x_off=0, cell_y_off=0, placement_id=0, + cursor_movement=0 + ): + return 'z=%d,c=%d,r=%d,x=%d,y=%d,w=%d,h=%d,X=%d,Y=%d,p=%d,C=%d' % ( + z, num_cols, num_lines, x_off, y_off, width, height, cell_x_off, + cell_y_off, placement_id, cursor_movement + ) def put_image(screen, w, h, **kw): nonlocal iid @@ -510,6 +516,8 @@ class TestGraphics(BaseTest): rect_eq(l2[1]['dest_rect'], -1, 1, -1 + dx, 1 - dy) self.ae(l2[0]['group_count'], 1), self.ae(l2[1]['group_count'], 1) self.ae(s.cursor.x, 0), self.ae(s.cursor.y, 1) + self.ae(put_image(s, 10, 20, cursor_movement=1)[1], 'OK') + self.ae(s.cursor.x, 0), self.ae(s.cursor.y, 1) s.reset() self.assertEqual(s.grman.disk_cache.total_size, 0) diff --git a/kitty_tests/parser.py b/kitty_tests/parser.py index 96bc5d2b5..70474844d 100644 --- a/kitty_tests/parser.py +++ b/kitty_tests/parser.py @@ -381,7 +381,7 @@ class TestParser(BaseTest): k[p] = v.encode('ascii') for f in 'action delete_action transmission_type compressed'.split(): k.setdefault(f, b'\0') - for f in ('format more id data_sz data_offset width height x_offset y_offset data_height data_width' + for f in ('format more id data_sz data_offset width height x_offset y_offset data_height data_width cursor_movement' ' num_cells num_lines cell_x_offset cell_y_offset z_index placement_id image_number quiet').split(): k.setdefault(f, 0) p = k.pop('payload', '').encode('utf-8') diff --git a/kitty_tests/screen.py b/kitty_tests/screen.py index 9784dad83..aba670cc9 100644 --- a/kitty_tests/screen.py +++ b/kitty_tests/screen.py @@ -647,6 +647,13 @@ class TestScreen(BaseTest): self.ae(s.marked_cells(), cells(8)) s.set_marker(marker_from_regex('\t', 3)) self.ae(s.marked_cells(), cells(*range(8))) + s = self.create_screen() + s.cursor.x = 2 + s.draw('x') + s.cursor.x += 1 + s.draw('x') + s.set_marker(marker_from_function(mark_x)) + self.ae(s.marked_cells(), [(2, 0, 1), (4, 0, 2)]) def test_hyperlinks(self): s = self.create_screen()