mirror of
https://github.com/kovidgoyal/kitty.git
synced 2026-08-04 14:46:03 +00:00
Merge branch 'visibility-reports' of https://github.com/rockorager/kitty
This commit is contained in:
commit
7e647774d1
9 changed files with 125 additions and 3 deletions
|
|
@ -193,6 +193,8 @@ Detailed list of changes
|
|||
|
||||
- Desktop file chooser integration: when the specified directory does not exist, open file chooser at home directory (:iss:`10292`)
|
||||
|
||||
- Add support for `terminal visibility reports <https://rockorager.dev/misc/visibility-reports/>`__
|
||||
|
||||
|
||||
0.48.1 [2026-07-24]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
|
|
|||
12
kitty/glfw.c
12
kitty/glfw.c
|
|
@ -334,6 +334,7 @@ window_occlusion_callback(GLFWwindow *window, bool occluded) {
|
|||
if (!set_callback_window(window)) return;
|
||||
debug("OSWindow %llu occlusion state changed, occluded: %d\n", global_state.callback_os_window->id, occluded);
|
||||
if (!occluded) global_state.check_for_active_animated_images = true;
|
||||
update_os_window_visibility_reports(global_state.callback_os_window);
|
||||
request_tick_callback();
|
||||
global_state.callback_os_window = NULL;
|
||||
}
|
||||
|
|
@ -342,6 +343,7 @@ static void
|
|||
window_iconify_callback(GLFWwindow *window, int iconified) {
|
||||
if (!set_callback_window(window)) return;
|
||||
if (!iconified) global_state.check_for_active_animated_images = true;
|
||||
update_os_window_visibility_reports(global_state.callback_os_window);
|
||||
request_tick_callback();
|
||||
global_state.callback_os_window = NULL;
|
||||
}
|
||||
|
|
@ -633,6 +635,7 @@ set_os_window_visibility(OSWindow *w, int set_visible, bool move_to_active_scree
|
|||
w->keep_rendering_till_swap = 256; // try this many times
|
||||
request_tick_callback();
|
||||
} else glfwHideWindow(w->handle);
|
||||
update_os_window_visibility_reports(w);
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -1456,6 +1459,7 @@ change_state_for_os_window(OSWindow *w, int state) {
|
|||
case WINDOW_HIDDEN:
|
||||
glfwHideWindow(w->handle); break;
|
||||
}
|
||||
update_os_window_visibility_reports(w);
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
|
@ -2562,15 +2566,19 @@ wakeup_main_loop(void) {
|
|||
}
|
||||
|
||||
bool
|
||||
should_os_window_be_rendered(OSWindow* w) {
|
||||
is_os_window_potentially_visible(OSWindow* w) {
|
||||
return (
|
||||
glfwGetWindowAttrib(w->handle, GLFW_ICONIFIED)
|
||||
|| !glfwGetWindowAttrib(w->handle, GLFW_VISIBLE)
|
||||
|| glfwGetWindowAttrib(w->handle, GLFW_OCCLUDED)
|
||||
|| !glfwAreSwapsAllowed(w->handle)
|
||||
) ? false : true;
|
||||
}
|
||||
|
||||
bool
|
||||
should_os_window_be_rendered(OSWindow* w) {
|
||||
return is_os_window_potentially_visible(w) && glfwAreSwapsAllowed(w->handle);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
primary_monitor_size(PYNOARG) {
|
||||
GLFWmonitor* monitor = glfwGetPrimaryMonitor();
|
||||
|
|
|
|||
|
|
@ -88,6 +88,9 @@
|
|||
// Notification of color preference change
|
||||
#define COLOR_PREFERENCE_NOTIFICATION (2031 << 5)
|
||||
|
||||
// Terminal visibility reports
|
||||
#define VISIBILITY_REPORTS (2033 << 5)
|
||||
|
||||
// In-band resize notification mode
|
||||
#define INBAND_RESIZE_NOTIFICATION (2048 << 5)
|
||||
|
||||
|
|
|
|||
|
|
@ -1524,6 +1524,27 @@ write_escape_code_to_child(Screen *self, unsigned char which, const char *data)
|
|||
return written;
|
||||
}
|
||||
|
||||
static void
|
||||
send_visibility_report(Screen *self, bool potentially_visible) {
|
||||
write_escape_code_to_child(self, ESC_CSI, potentially_visible ? "?999;1n" : "?999;2n");
|
||||
}
|
||||
|
||||
void
|
||||
screen_visibility_changed(Screen *self, bool potentially_visible) {
|
||||
const uint8_t state = potentially_visible ? 1 : 2;
|
||||
if (self->visibility_state != state) {
|
||||
self->visibility_state = state;
|
||||
if (self->modes.mVISIBILITY_REPORTS) send_visibility_report(self, potentially_visible);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
report_current_visibility(Screen *self) {
|
||||
const bool potentially_visible = is_kitty_window_visible(self->window_id);
|
||||
self->visibility_state = potentially_visible ? 1 : 2;
|
||||
send_visibility_report(self, potentially_visible);
|
||||
}
|
||||
|
||||
static bool
|
||||
write_escape_code_to_child_python(Screen *self, unsigned char which, PyObject *data) {
|
||||
bool written = false;
|
||||
|
|
@ -1799,6 +1820,10 @@ set_mode_from_const(Screen *self, unsigned int mode, bool val) {
|
|||
self->modes.mINBAND_RESIZE_NOTIFICATION = val;
|
||||
if (val) CALLBACK("notify_child_of_resize", NULL);
|
||||
break;
|
||||
case VISIBILITY_REPORTS:
|
||||
self->modes.mVISIBILITY_REPORTS = val;
|
||||
if (val) report_current_visibility(self);
|
||||
break;
|
||||
default:
|
||||
private = mode >= 1 << 5;
|
||||
if (private) mode >>= 5;
|
||||
|
|
@ -2341,6 +2366,7 @@ copy_specific_mode(Screen *self, unsigned int mode, const ScreenModes *src, Scre
|
|||
SIMPLE_MODE(COLOR_PREFERENCE_NOTIFICATION)
|
||||
SIMPLE_MODE(PASTE_EVENTS)
|
||||
SIMPLE_MODE(INBAND_RESIZE_NOTIFICATION)
|
||||
SIMPLE_MODE(VISIBILITY_REPORTS)
|
||||
SIMPLE_MODE(DECCKM)
|
||||
SIMPLE_MODE(DECTCEM)
|
||||
SIMPLE_MODE(DECAWM)
|
||||
|
|
@ -2383,6 +2409,7 @@ copy_specific_modes(Screen *self, const ScreenModes *src, ScreenModes *dest) {
|
|||
copy_specific_mode(self, FOCUS_TRACKING, src, dest);
|
||||
copy_specific_mode(self, COLOR_PREFERENCE_NOTIFICATION, src, dest);
|
||||
copy_specific_mode(self, INBAND_RESIZE_NOTIFICATION, src, dest);
|
||||
copy_specific_mode(self, VISIBILITY_REPORTS, src, dest);
|
||||
copy_specific_mode(self, PASTE_EVENTS, src, dest);
|
||||
copy_specific_mode(self, DECCKM, src, dest);
|
||||
copy_specific_mode(self, DECTCEM, src, dest);
|
||||
|
|
@ -2937,6 +2964,9 @@ report_device_status(Screen *self, unsigned int which, bool private) {
|
|||
if (private) {
|
||||
CALLBACK("report_color_scheme_preference", NULL);
|
||||
} break;
|
||||
case 998: // https://rockorager.dev/misc/visibility-reports/
|
||||
if (private) report_current_visibility(self);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2961,6 +2991,7 @@ report_mode_status(Screen *self, unsigned int which, bool private) {
|
|||
KNOWN_MODE(BRACKETED_PASTE);
|
||||
KNOWN_MODE(FOCUS_TRACKING);
|
||||
KNOWN_MODE(COLOR_PREFERENCE_NOTIFICATION);
|
||||
KNOWN_MODE(VISIBILITY_REPORTS);
|
||||
KNOWN_MODE(INBAND_RESIZE_NOTIFICATION);
|
||||
KNOWN_MODE(PASTE_EVENTS);
|
||||
#undef KNOWN_MODE
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ typedef struct DnDCommand {
|
|||
typedef struct {
|
||||
bool mLNM, mIRM, mDECTCEM, mDECSCNM, mDECOM, mDECAWM, mDECCOLM, mDECARM, mDECCKM, mCOLOR_PREFERENCE_NOTIFICATION,
|
||||
mBRACKETED_PASTE, mFOCUS_TRACKING, mDECSACE, mHANDLE_TERMIOS_SIGNALS, mINBAND_RESIZE_NOTIFICATION,
|
||||
mPASTE_EVENTS;
|
||||
mPASTE_EVENTS, mVISIBILITY_REPORTS;
|
||||
MouseTrackingMode mouse_tracking_mode;
|
||||
MouseTrackingProtocol mouse_tracking_protocol;
|
||||
} ScreenModes;
|
||||
|
|
@ -150,6 +150,7 @@ typedef struct {
|
|||
DisableLigature disable_ligatures;
|
||||
PyObject *marker;
|
||||
bool has_focus;
|
||||
uint8_t visibility_state; // 0 = unknown, 1 = potentially visible, 2 = not visible
|
||||
bool has_activity_since_last_focus;
|
||||
hyperlink_id_type active_hyperlink_id;
|
||||
HYPERLINK_POOL_HANDLE hyperlink_pool;
|
||||
|
|
@ -225,6 +226,7 @@ void screen_restore_mode(Screen *, unsigned int);
|
|||
void screen_save_modes(Screen *);
|
||||
void screen_save_mode(Screen *, unsigned int);
|
||||
bool write_escape_code_to_child(Screen *self, unsigned char which, const char *data);
|
||||
void screen_visibility_changed(Screen *self, bool potentially_visible);
|
||||
void screen_cursor_position(Screen*, unsigned int, unsigned int);
|
||||
void screen_cursor_move(Screen *self, unsigned int count/*=1*/, int move_direction/*=-1*/, bool allow_move_to_previous_line);
|
||||
void screen_erase_in_line(Screen *, unsigned int, bool);
|
||||
|
|
|
|||
|
|
@ -165,6 +165,39 @@ window_for_window_id(id_type kitty_window_id) {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
bool
|
||||
is_kitty_window_visible(id_type kitty_window_id) {
|
||||
for (size_t i = 0; i < global_state.num_os_windows; i++) {
|
||||
OSWindow *os_window = global_state.os_windows + i;
|
||||
for (size_t t = 0; t < os_window->num_tabs; t++) {
|
||||
Tab *tab = os_window->tabs + t;
|
||||
for (size_t w = 0; w < tab->num_windows; w++) {
|
||||
Window *window = tab->windows + w;
|
||||
if (window->id == kitty_window_id) {
|
||||
return is_os_window_potentially_visible(os_window) && t == os_window->active_tab && window->visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// If the view cannot be found, its visibility is unknown and must be
|
||||
// treated conservatively as potentially visible.
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
update_os_window_visibility_reports(OSWindow *os_window) {
|
||||
const bool os_window_is_visible = is_os_window_potentially_visible(os_window);
|
||||
for (size_t t = 0; t < os_window->num_tabs; t++) {
|
||||
Tab *tab = os_window->tabs + t;
|
||||
for (size_t w = 0; w < tab->num_windows; w++) {
|
||||
Window *window = tab->windows + w;
|
||||
if (window->render_data.screen) {
|
||||
screen_visibility_changed(window->render_data.screen, os_window_is_visible && t == os_window->active_tab && window->visible);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
free_bgimage_bitmap(BackgroundImage *bgimage) {
|
||||
if (!bgimage->bitmap) return;
|
||||
|
|
@ -502,6 +535,10 @@ attach_window(id_type os_window_id, id_type tab_id, id_type id) {
|
|||
) resize_screen(osw, w->render_data.screen, true);
|
||||
else screen_dirty_sprite_positions(w->render_data.screen);
|
||||
w->render_data.screen->reload_all_gpu_data = true;
|
||||
screen_visibility_changed(
|
||||
w->render_data.screen,
|
||||
is_os_window_potentially_visible(osw) && t == osw->active_tab && w->visible
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -585,6 +622,7 @@ set_active_tab(id_type os_window_id, unsigned int idx) {
|
|||
WITH_OS_WINDOW(os_window_id)
|
||||
os_window->active_tab = idx;
|
||||
os_window->needs_render = true;
|
||||
update_os_window_visibility_reports(os_window);
|
||||
END_WITH_OS_WINDOW
|
||||
}
|
||||
|
||||
|
|
@ -1203,6 +1241,12 @@ PYWRAP1(update_window_visibility) {
|
|||
bool was_visible = window->visible & 1;
|
||||
window->visible = visible & 1;
|
||||
if (!was_visible && window->visible) global_state.check_for_active_animated_images = true;
|
||||
if (window->render_data.screen) {
|
||||
screen_visibility_changed(
|
||||
window->render_data.screen,
|
||||
is_os_window_potentially_visible(osw) && t == osw->active_tab && window->visible
|
||||
);
|
||||
}
|
||||
END_WITH_WINDOW;
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -566,6 +566,9 @@ void update_os_window_references(void);
|
|||
void mark_os_window_for_close(OSWindow* w, CloseRequest cr);
|
||||
void update_os_window_viewport(OSWindow *window, bool notify_boss);
|
||||
bool should_os_window_be_rendered(OSWindow* w);
|
||||
bool is_os_window_potentially_visible(OSWindow* w);
|
||||
bool is_kitty_window_visible(id_type kitty_window_id);
|
||||
void update_os_window_visibility_reports(OSWindow *w);
|
||||
void wakeup_main_loop(void);
|
||||
bool make_window_context_current(id_type);
|
||||
void hide_mouse(OSWindow *w);
|
||||
|
|
|
|||
|
|
@ -799,6 +799,17 @@ class TestParser(BaseTest):
|
|||
pb('\033[?2026$p', ('report_mode_status', 2026, 1))
|
||||
self.ae(c.wtcbuf, b'\x1b[?2026;2$y')
|
||||
|
||||
c.clear()
|
||||
pb('\033[?998n', ('report_device_status', 998, 1))
|
||||
self.ae(c.wtcbuf, b'\x1b[?999;1n')
|
||||
c.clear()
|
||||
pb('\033[?2033h', ('screen_set_mode', 2033, 1))
|
||||
self.ae(c.wtcbuf, b'\x1b[?999;1n')
|
||||
c.clear()
|
||||
pb('\033[?2033$p', ('report_mode_status', 2033, 1))
|
||||
self.ae(c.wtcbuf, b'\x1b[?2033;1$y')
|
||||
pb('\033[?2033l', ('screen_reset_mode', 2033, 1))
|
||||
|
||||
def test_csi_code_rep(self):
|
||||
s = self.create_screen(8)
|
||||
pb = partial(self.parse_bytes_dump, s)
|
||||
|
|
|
|||
|
|
@ -404,6 +404,24 @@ class TestScreen(BaseTest):
|
|||
parse_bytes(s, b'\x1b[?2048h') # ]
|
||||
self.ae(c.num_of_resize_events, 2)
|
||||
|
||||
def test_visibility_reports(self):
|
||||
s = self.create_screen()
|
||||
c = s.callbacks
|
||||
parse_bytes(s, b'\x1b[?2033$p') # ]
|
||||
self.ae(c.wtcbuf, b'\x1b[?2033;2$y') # ]
|
||||
c.clear()
|
||||
parse_bytes(s, b'\x1b[?998n') # ]
|
||||
self.ae(c.wtcbuf, b'\x1b[?999;1n') # ]
|
||||
c.clear()
|
||||
parse_bytes(s, b'\x1b[?2033h\x1b[?2033$p') # ]]
|
||||
self.ae(c.wtcbuf, b'\x1b[?999;1n\x1b[?2033;1$y') # ]]
|
||||
c.clear()
|
||||
parse_bytes(s, b'\x1b[?2033h') # ]
|
||||
self.ae(c.wtcbuf, b'\x1b[?999;1n') # ]
|
||||
c.clear()
|
||||
parse_bytes(s, b'\x1b[?2033l\x1b[?2033$p') # ]]
|
||||
self.ae(c.wtcbuf, b'\x1b[?2033;2$y') # ]
|
||||
|
||||
def test_da1(self):
|
||||
s = self.create_screen()
|
||||
parse_bytes(s, b'\x1b[c\x1b[0c') # ]]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue