Merge branch 'master' of https://github.com/kovidgoyal/kitty into fix-rectircle-subpixel-rendering

This commit is contained in:
Jesse Leite
2021-03-25 00:17:47 -04:00
34 changed files with 353 additions and 107 deletions

View File

@@ -102,6 +102,12 @@ To update |kitty|, :doc:`follow the instructions <binary>`.
- 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 <binary>`.
- 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`)

View File

@@ -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**

View File

@@ -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')

View File

@@ -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

36
glfw/glfw3.h vendored
View File

@@ -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.

6
glfw/ibus_glfw.c vendored
View File

@@ -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:

4
glfw/input.c vendored
View File

@@ -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

2
glfw/internal.h vendored
View File

@@ -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);

View File

@@ -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",

31
glfw/wl_init.c vendored
View File

@@ -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)

3
glfw/wl_platform.h vendored
View File

@@ -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];

129
glfw/wl_text_input.c vendored Normal file
View File

@@ -0,0 +1,129 @@
/*
* wl_text_input.c
* Copyright (C) 2021 Kovid Goyal <kovid at kovidgoyal.net>
*
* 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;
}
}

14
glfw/wl_text_input.h vendored Normal file
View File

@@ -0,0 +1,14 @@
/*
* Copyright (C) 2021 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#pragma once
#include <wayland-client.h>
#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);

5
glfw/wl_window.c vendored
View File

@@ -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;

4
glfw/x11_window.c vendored
View File

@@ -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);
}
//////////////////////////////////////////////////////////////////////////

20
glfw/xkb_glfw.c vendored
View File

@@ -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)

2
glfw/xkb_glfw.h vendored
View File

@@ -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);

View File

@@ -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);

View File

@@ -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)

30
kitty/glfw-wrapper.h generated
View File

@@ -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

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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__)

View File

@@ -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);
}

View File

@@ -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,

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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()

View File

@@ -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)

View File

@@ -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')

View File

@@ -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()