hints/unicode_input kittens: Do not lose keypresses that are sent very rapidly via an automation tool immediately after the kitten is launched
We now buffer the key events until the kitten tells us it is ready. Without this the key presses are delivered to the underlying window as the kitten's overlay window was not being focused until the kitten set the ready message. Fixes #7089
This commit is contained in:
parent
9c1324e9d0
commit
9d48fa9126
10 changed files with 131 additions and 38 deletions
|
|
@ -111,6 +111,8 @@ Detailed list of changes
|
|||
|
||||
- When re-attaching a detached tab preserve internal layout state such as biases and orientations (:iss:`8106`)
|
||||
|
||||
- hints/unicode_input kittens: Do not lose keypresses that are sent very rapidly via an automation tool immediately after the kitten is launched (:iss:`7089`)
|
||||
|
||||
0.37.0 [2024-10-30]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
|||
|
|
@ -217,11 +217,11 @@ func main(_ *cli.Command, o *Options, args []string) (rc int, err error) {
|
|||
}
|
||||
|
||||
lp.OnInitialize = func() (string, error) {
|
||||
lp.SendOverlayReady()
|
||||
lp.SetCursorVisible(false)
|
||||
lp.SetWindowTitle(window_title)
|
||||
lp.AllowLineWrapping(false)
|
||||
draw_screen()
|
||||
lp.SendOverlayReady()
|
||||
return "", nil
|
||||
}
|
||||
lp.OnFinalize = func() string {
|
||||
|
|
|
|||
|
|
@ -321,7 +321,7 @@ void enter_event(void);
|
|||
void mouse_event(const int, int, int);
|
||||
void focus_in_event(void);
|
||||
void scroll_event(double, double, int, int);
|
||||
void on_key_input(GLFWkeyevent *ev);
|
||||
void on_key_input(const GLFWkeyevent *ev);
|
||||
void request_window_attention(id_type, bool);
|
||||
#ifndef __APPLE__
|
||||
void play_canberra_sound(const char *which_sound, const char *event_id, bool is_path, const char *role, const char *theme_name);
|
||||
|
|
|
|||
|
|
@ -1715,6 +1715,8 @@ def systemd_move_pid_into_new_scope(pid: int, scope_name: str, description: str)
|
|||
def play_desktop_sound_async(name: str, event_id: str = 'test sound', is_path: bool = False, theme_name: str = '') -> str: ...
|
||||
def cocoa_play_system_sound_by_id_async(sound_id: int) -> None: ...
|
||||
def glfw_get_system_color_theme(query_if_unintialized: bool = True) -> Literal['light', 'dark', 'no_preference']: ...
|
||||
def set_redirect_keys_to_overlay(os_window_id: int, tab_id: int, window_id: int, overlay_window_id: int) -> None: ...
|
||||
def buffer_keys_in_window(os_window_id: int, tab_id: int, window_id: int, enabled: bool = True) -> bool: ...
|
||||
|
||||
class MousePosition(TypedDict):
|
||||
cell_x: int
|
||||
|
|
|
|||
103
kitty/keys.c
103
kitty/keys.c
|
|
@ -106,8 +106,13 @@ static Window*
|
|||
active_window(void) {
|
||||
Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab;
|
||||
Window *w = t->windows + t->active_window;
|
||||
if (w->render_data.screen) return w;
|
||||
return NULL;
|
||||
if (!w->render_data.screen) return NULL;
|
||||
if (w->redirect_keys_to_overlay) {
|
||||
for (unsigned i = 0; i < t->num_windows; i++) {
|
||||
if (t->windows[i].id == w->redirect_keys_to_overlay && w->render_data.screen) return t->windows + i;
|
||||
}
|
||||
}
|
||||
return w;
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -162,8 +167,57 @@ format_mods(unsigned mods) {
|
|||
return buf;
|
||||
}
|
||||
|
||||
static void
|
||||
send_key_to_child(id_type window_id, Screen *screen, const GLFWkeyevent *ev) {
|
||||
const int action = ev->action;
|
||||
const uint32_t key = ev->key, native_key = ev->native_key;
|
||||
const char *text = ev->text ? ev->text : "";
|
||||
|
||||
if (action == GLFW_REPEAT && !screen->modes.mDECARM) {
|
||||
debug("discarding repeat key event as DECARM is off\n");
|
||||
return;
|
||||
}
|
||||
if (screen->scrolled_by && action == GLFW_PRESS && !is_no_action_key(key, native_key)) {
|
||||
screen_history_scroll(screen, SCROLL_FULL, false); // scroll back to bottom
|
||||
}
|
||||
char encoded_key[KEY_BUFFER_SIZE] = {0};
|
||||
int size = encode_glfw_key_event(ev, screen->modes.mDECCKM, screen_current_key_encoding_flags(screen), encoded_key);
|
||||
if (size == SEND_TEXT_TO_CHILD) {
|
||||
schedule_write_to_child(window_id, 1, text, strlen(text));
|
||||
debug("sent key as text to child (window_id: %llu): %s\n", window_id, text);
|
||||
} else if (size > 0) {
|
||||
if (size == 1 && screen->modes.mHANDLE_TERMIOS_SIGNALS) {
|
||||
if (screen_send_signal_for_key(screen, *encoded_key)) return;
|
||||
}
|
||||
schedule_write_to_child(window_id, 1, encoded_key, size);
|
||||
if (OPT(debug_keyboard)) {
|
||||
debug("sent encoded key to child (window_id: %llu): ", window_id);
|
||||
for (int ki = 0; ki < size; ki++) {
|
||||
if (encoded_key[ki] == 27) { debug("^[ "); }
|
||||
else if (encoded_key[ki] == ' ') { debug("SPC "); }
|
||||
else if (isprint(encoded_key[ki])) { debug("%c ", encoded_key[ki]); }
|
||||
else { debug("0x%x ", encoded_key[ki]); }
|
||||
}
|
||||
debug("\n");
|
||||
}
|
||||
} else {
|
||||
debug("ignoring as keyboard mode does not support encoding this event\n");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
on_key_input(GLFWkeyevent *ev) {
|
||||
dispatch_buffered_keys(Window *w) {
|
||||
if (!w->render_data.screen || !w->buffered_keys.count) return;
|
||||
GLFWkeyevent *keys = w->buffered_keys.key_data;
|
||||
for (size_t i = 0; i < w->buffered_keys.count; i++) {
|
||||
debug("Sending previously buffered key ");
|
||||
send_key_to_child(w->id, w->render_data.screen, keys + i);
|
||||
}
|
||||
free(w->buffered_keys.key_data); zero_at_ptr(&w->buffered_keys);
|
||||
}
|
||||
|
||||
void
|
||||
on_key_input(const GLFWkeyevent *ev) {
|
||||
Window *w = active_window();
|
||||
const int action = ev->action, mods = ev->mods;
|
||||
const uint32_t key = ev->key, native_key = ev->native_key;
|
||||
|
|
@ -235,42 +289,25 @@ on_key_input(GLFWkeyevent *ev) {
|
|||
}
|
||||
}
|
||||
if (!w) return;
|
||||
screen = w->render_data.screen;
|
||||
} else if (w->last_special_key_pressed == key) {
|
||||
w->last_special_key_pressed = 0;
|
||||
debug("ignoring release event for previous press that was handled as shortcut\n");
|
||||
return;
|
||||
}
|
||||
if (w->buffered_keys.enabled) {
|
||||
if (w->buffered_keys.capacity < w->buffered_keys.count + 1) {
|
||||
w->buffered_keys.capacity = MAX(16, w->buffered_keys.capacity + 8);
|
||||
GLFWkeyevent *new = malloc(w->buffered_keys.capacity * sizeof(GLFWkeyevent));
|
||||
if (!new) fatal("Out of memory");
|
||||
memcpy(new, w->buffered_keys.key_data, w->buffered_keys.count * sizeof(new[0]));
|
||||
w->buffered_keys.key_data = new;
|
||||
}
|
||||
GLFWkeyevent *k = w->buffered_keys.key_data;
|
||||
k[w->buffered_keys.count++] = *ev;
|
||||
debug("bufferring key until child is ready\n");
|
||||
} else send_key_to_child(w->id, screen, ev);
|
||||
#undef dispatch_key_event
|
||||
if (action == GLFW_REPEAT && !screen->modes.mDECARM) {
|
||||
debug("discarding repeat key event as DECARM is off\n");
|
||||
return;
|
||||
}
|
||||
if (screen->scrolled_by && action == GLFW_PRESS && !is_no_action_key(key, native_key)) {
|
||||
screen_history_scroll(screen, SCROLL_FULL, false); // scroll back to bottom
|
||||
}
|
||||
char encoded_key[KEY_BUFFER_SIZE] = {0};
|
||||
int size = encode_glfw_key_event(ev, screen->modes.mDECCKM, screen_current_key_encoding_flags(screen), encoded_key);
|
||||
if (size == SEND_TEXT_TO_CHILD) {
|
||||
schedule_write_to_child(w->id, 1, text, strlen(text));
|
||||
debug("sent key as text to child: %s\n", text);
|
||||
} else if (size > 0) {
|
||||
if (size == 1 && screen->modes.mHANDLE_TERMIOS_SIGNALS) {
|
||||
if (screen_send_signal_for_key(screen, *encoded_key)) return;
|
||||
}
|
||||
schedule_write_to_child(w->id, 1, encoded_key, size);
|
||||
if (OPT(debug_keyboard)) {
|
||||
debug("sent encoded key to child: ");
|
||||
for (int ki = 0; ki < size; ki++) {
|
||||
if (encoded_key[ki] == 27) { debug("^[ "); }
|
||||
else if (encoded_key[ki] == ' ') { debug("SPC "); }
|
||||
else if (isprint(encoded_key[ki])) { debug("%c ", encoded_key[ki]); }
|
||||
else { debug("0x%x ", encoded_key[ki]); }
|
||||
}
|
||||
debug("\n");
|
||||
}
|
||||
} else {
|
||||
debug("ignoring as keyboard mode does not support encoding this event\n");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
|||
|
|
@ -294,16 +294,17 @@ def move_window_to_group(self, all_windows: WindowList, group: int) -> bool:
|
|||
def add_window(
|
||||
self, all_windows: WindowList, window: WindowType, location: Optional[str] = None,
|
||||
overlay_for: Optional[int] = None, put_overlay_behind: bool = False, bias: Optional[float] = None,
|
||||
) -> None:
|
||||
) -> Optional[WindowType]:
|
||||
if overlay_for is not None:
|
||||
underlay = all_windows.id_map.get(overlay_for)
|
||||
if underlay is not None:
|
||||
window.margin, window.padding = underlay.margin.copy(), underlay.padding.copy()
|
||||
all_windows.add_window(window, group_of=overlay_for, head_of_group=put_overlay_behind)
|
||||
return
|
||||
return underlay
|
||||
if location == 'neighbor':
|
||||
location = 'after'
|
||||
self.add_non_overlay_window(all_windows, window, location, bias)
|
||||
return None
|
||||
|
||||
def add_non_overlay_window(self, all_windows: WindowList, window: WindowType, location: Optional[str], bias: Optional[float] = None) -> None:
|
||||
next_to: Optional[WindowType] = None
|
||||
|
|
|
|||
|
|
@ -348,7 +348,8 @@ update_os_window_title(OSWindow *os_window) {
|
|||
|
||||
static void
|
||||
destroy_window(Window *w) {
|
||||
free(w->pending_clicks.clicks); w->pending_clicks.clicks = NULL; w->pending_clicks.num = 0; w->pending_clicks.capacity = 0;
|
||||
free(w->pending_clicks.clicks); zero_at_ptr(&w->pending_clicks);
|
||||
free(w->buffered_keys.key_data); zero_at_ptr(&w->buffered_keys);
|
||||
Py_CLEAR(w->render_data.screen); Py_CLEAR(w->title);
|
||||
Py_CLEAR(w->title_bar_data.last_drawn_title_object_id);
|
||||
free(w->title_bar_data.buf); w->title_bar_data.buf = NULL;
|
||||
|
|
@ -532,6 +533,26 @@ set_active_window(id_type os_window_id, id_type tab_id, id_type window_id) {
|
|||
END_WITH_WINDOW;
|
||||
}
|
||||
|
||||
static bool
|
||||
buffer_keys_in_window(id_type os_window_id, id_type tab_id, id_type window_id, bool enable) {
|
||||
WITH_WINDOW(os_window_id, tab_id, window_id)
|
||||
window->buffered_keys.enabled = enable;
|
||||
if (!enable) dispatch_buffered_keys(window);
|
||||
return true;
|
||||
END_WITH_WINDOW;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
set_redirect_keys_to_overlay(id_type os_window_id, id_type tab_id, id_type window_id, id_type overlay_id) {
|
||||
WITH_WINDOW(os_window_id, tab_id, window_id)
|
||||
window->redirect_keys_to_overlay = overlay_id;
|
||||
return true;
|
||||
END_WITH_WINDOW;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
swap_tabs(id_type os_window_id, unsigned int a, unsigned int b) {
|
||||
WITH_OS_WINDOW(os_window_id)
|
||||
|
|
@ -1359,6 +1380,13 @@ PYWRAP1(redirect_mouse_handling) {
|
|||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
PYWRAP1(buffer_keys_in_window) {
|
||||
int enabled = 1;
|
||||
id_type a, b, c; PA("KKK|p", &a, &b, &c, &enabled);
|
||||
if (buffer_keys_in_window(a, b, c, enabled)) Py_RETURN_TRUE;
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
|
||||
THREE_ID_OBJ(update_window_title)
|
||||
THREE_ID(remove_window)
|
||||
THREE_ID(detach_window)
|
||||
|
|
@ -1372,6 +1400,7 @@ K(mark_os_window_dirty)
|
|||
KKK(set_active_window)
|
||||
KII(swap_tabs)
|
||||
KK5I(add_borders_rect)
|
||||
KKKK(set_redirect_keys_to_overlay)
|
||||
|
||||
static PyObject*
|
||||
os_window_focus_counters(PyObject *self UNUSED, PyObject *args UNUSED) {
|
||||
|
|
@ -1430,6 +1459,8 @@ static PyMethodDef module_methods[] = {
|
|||
MW(attach_window, METH_VARARGS),
|
||||
MW(set_active_tab, METH_VARARGS),
|
||||
MW(mark_os_window_dirty, METH_VARARGS),
|
||||
MW(set_redirect_keys_to_overlay, METH_VARARGS),
|
||||
MW(buffer_keys_in_window, METH_VARARGS),
|
||||
MW(set_active_window, METH_VARARGS),
|
||||
MW(swap_tabs, METH_VARARGS),
|
||||
MW(add_borders_rect, METH_VARARGS),
|
||||
|
|
|
|||
|
|
@ -198,6 +198,12 @@ typedef struct {
|
|||
monotonic_t last_drag_scroll_at;
|
||||
uint32_t last_special_key_pressed;
|
||||
WindowBarData title_bar_data, url_target_bar_data;
|
||||
id_type redirect_keys_to_overlay;
|
||||
struct {
|
||||
bool enabled;
|
||||
void *key_data;
|
||||
size_t count, capacity;
|
||||
} buffered_keys;
|
||||
struct {
|
||||
PendingClick *clicks;
|
||||
size_t num, capacity;
|
||||
|
|
@ -417,3 +423,4 @@ void change_live_resize_state(OSWindow*, bool);
|
|||
bool render_os_window(OSWindow *w, monotonic_t now, bool ignore_render_frames, bool scan_for_animated_images);
|
||||
void update_mouse_pointer_shape(void);
|
||||
void adjust_window_size_for_csd(OSWindow *w, int width, int height, int *adjusted_width, int *adjusted_height);
|
||||
void dispatch_buffered_keys(Window *w);
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
GLFW_RELEASE,
|
||||
add_tab,
|
||||
attach_window,
|
||||
buffer_keys_in_window,
|
||||
current_focused_os_window_id,
|
||||
detach_window,
|
||||
focus_os_window,
|
||||
|
|
@ -45,6 +46,7 @@
|
|||
ring_bell,
|
||||
set_active_tab,
|
||||
set_active_window,
|
||||
set_redirect_keys_to_overlay,
|
||||
swap_tabs,
|
||||
sync_os_window_title,
|
||||
)
|
||||
|
|
@ -518,6 +520,10 @@ def _add_window(
|
|||
overlay_behind: bool = False, bias: Optional[float] = None
|
||||
) -> None:
|
||||
self.current_layout.add_window(self.windows, window, location, overlay_for, put_overlay_behind=overlay_behind, bias=bias)
|
||||
if overlay_behind and (w := self.active_window):
|
||||
set_redirect_keys_to_overlay(self.os_window_id, self.id, w.id, window.id)
|
||||
buffer_keys_in_window(self.os_window_id, self.id, window.id, True)
|
||||
window.keys_redirected_till_ready_from = w.id
|
||||
self.mark_tab_bar_dirty()
|
||||
self.relayout()
|
||||
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@
|
|||
add_timer,
|
||||
add_window,
|
||||
base64_decode,
|
||||
buffer_keys_in_window,
|
||||
cell_size_for_window,
|
||||
click_mouse_cmd_output,
|
||||
click_mouse_url,
|
||||
|
|
@ -77,6 +78,7 @@
|
|||
pointer_name_to_css_name,
|
||||
pt_to_px,
|
||||
replace_c0_codes_except_nl_space_tab,
|
||||
set_redirect_keys_to_overlay,
|
||||
set_window_logo,
|
||||
set_window_padding,
|
||||
set_window_render_data,
|
||||
|
|
@ -622,6 +624,7 @@ def __init__(
|
|||
self.watchers.add(global_watchers())
|
||||
else:
|
||||
self.watchers = global_watchers().copy()
|
||||
self.keys_redirected_till_ready_from: int = 0
|
||||
self.last_focused_at = 0.
|
||||
self.is_focused: bool = False
|
||||
self.last_resized_at = 0.
|
||||
|
|
@ -1407,6 +1410,10 @@ def handle_overlay_ready(self, msg: memoryview) -> None:
|
|||
tab = boss.tab_for_window(self)
|
||||
if tab is not None:
|
||||
tab.move_window_to_top_of_group(self)
|
||||
if self.keys_redirected_till_ready_from:
|
||||
set_redirect_keys_to_overlay(self.os_window_id, self.tab_id, self.keys_redirected_till_ready_from, 0)
|
||||
buffer_keys_in_window(self.os_window_id, self.tab_id, self.id, False)
|
||||
self.keys_redirected_till_ready_from = 0
|
||||
|
||||
def append_remote_data(self, msgb: memoryview) -> str:
|
||||
if not msgb:
|
||||
|
|
|
|||
Loading…
Reference in a new issue