diff --git a/docs/changelog.rst b/docs/changelog.rst index 955829bd2..19d98de39 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -93,6 +93,8 @@ Detailed list of changes - Graphics: Fix deleted but not freed images without any references being incorrectly freed on a subsequent delete command (:disc:`8129`) +- Add support for `escape code protocol `__ for notifying applications on dark/light color scheme change + 0.38.0 [2024-12-15] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/kitty/boss.py b/kitty/boss.py index 217d394bb..a617089e5 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -1550,6 +1550,7 @@ class Boss: def default_bg_changed_for(self, window_id: int) -> None: w = self.window_id_map.get(window_id) if w is not None: + w.on_color_scheme_preference_change() tm = self.os_window_map.get(w.os_window_id) if tm is not None: tm.update_tab_bar_data() diff --git a/kitty/colors.c b/kitty/colors.c index 24986d574..6fbcabafd 100644 --- a/kitty/colors.c +++ b/kitty/colors.c @@ -673,7 +673,13 @@ rgb_get(Color *self, void *closure UNUSED) { static PyObject* luminance_get(Color *self, void *closure UNUSED) { - return PyFloat_FromDouble(rgb_luminance(self->color)); + return PyFloat_FromDouble(rgb_luminance(self->color) / 255.0); +} + +static PyObject* +is_dark_get(Color *self, void *closure UNUSED) { + if (rgb_luminance(self->color) / 255.0 < 0.5) Py_RETURN_TRUE; + Py_RETURN_FALSE; } static PyObject* @@ -721,6 +727,7 @@ static PyGetSetDef color_getsetters[] = { {"luminance", (getter) luminance_get, NULL, "luminance", NULL}, {"as_sgr", (getter) sgr_get, NULL, "as_sgr", NULL}, {"as_sharp", (getter) sharp_get, NULL, "as_sharp", NULL}, + {"is_dark", (getter) is_dark_get, NULL, "is_dark", NULL}, {NULL} /* Sentinel */ }; diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index a530a0a49..9b5cc1398 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -746,6 +746,10 @@ class Color: def luminance(self) -> float: pass + @property + def is_dark(self) -> bool: + pass + @property def as_sgr(self) -> str: pass @@ -1191,6 +1195,7 @@ class Screen: linebuf: LineBuf in_bracketed_paste_mode: bool in_band_resize_notification: bool + color_preference_notification: bool cursor_visible: bool scrolled_by: int cursor: Cursor diff --git a/kitty/modes.h b/kitty/modes.h index 615dc02fd..c1bacf537 100644 --- a/kitty/modes.h +++ b/kitty/modes.h @@ -85,6 +85,9 @@ // Pending updates mode #define PENDING_UPDATE (2026 << 5) +// Notification of color preference change +#define COLOR_PREFERENCE_NOTIFICATION (2031 << 5) + // In-band resize notification mode #define INBAND_RESIZE_NOTIFICATION (2048 << 5) diff --git a/kitty/screen.c b/kitty/screen.c index 4637f884a..d93e8427d 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -1116,6 +1116,7 @@ set_mode_from_const(Screen *self, unsigned int mode, bool val) { SIMPLE_MODE(DECARM) SIMPLE_MODE(BRACKETED_PASTE) SIMPLE_MODE(FOCUS_TRACKING) + SIMPLE_MODE(COLOR_PREFERENCE_NOTIFICATION) SIMPLE_MODE(HANDLE_TERMIOS_SIGNALS) MOUSE_MODE(MOUSE_BUTTON_TRACKING, mouse_tracking_mode, BUTTON_MODE) MOUSE_MODE(MOUSE_MOTION_TRACKING, mouse_tracking_mode, MOTION_MODE) @@ -1691,6 +1692,7 @@ copy_specific_mode(Screen *self, unsigned int mode, const ScreenModes *src, Scre SIMPLE_MODE(DECARM) SIMPLE_MODE(BRACKETED_PASTE) SIMPLE_MODE(FOCUS_TRACKING) + SIMPLE_MODE(COLOR_PREFERENCE_NOTIFICATION) SIMPLE_MODE(INBAND_RESIZE_NOTIFICATION) SIMPLE_MODE(DECCKM) SIMPLE_MODE(DECTCEM) @@ -1732,6 +1734,7 @@ copy_specific_modes(Screen *self, const ScreenModes *src, ScreenModes *dest) { copy_specific_mode(self, DECARM, src, dest); copy_specific_mode(self, BRACKETED_PASTE, src, 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, DECCKM, src, dest); copy_specific_mode(self, DECTCEM, src, dest); @@ -2191,8 +2194,6 @@ screen_manipulate_title_stack(Screen *self, unsigned int op, unsigned int which) void report_device_status(Screen *self, unsigned int which, bool private) { - // We don't implement the private device status codes, since I haven't come - // across any programs that use them unsigned int x, y; static char buf[64]; switch(which) { @@ -2210,6 +2211,10 @@ report_device_status(Screen *self, unsigned int which, bool private) { int sz = snprintf(buf, sizeof(buf) - 1, "%s%u;%uR", (private ? "?": ""), y + 1, x + 1); if (sz > 0) write_escape_code_to_child(self, ESC_CSI, buf); break; + case 996: // https://github.com/contour-terminal/contour/blob/master/docs/vt-extensions/color-palette-update-notifications.md + if (private) { + CALLBACK("report_color_scheme_preference", NULL); + } break; } } @@ -2233,6 +2238,7 @@ report_mode_status(Screen *self, unsigned int which, bool private) { KNOWN_MODE(DECCKM); KNOWN_MODE(BRACKETED_PASTE); KNOWN_MODE(FOCUS_TRACKING); + KNOWN_MODE(COLOR_PREFERENCE_NOTIFICATION); KNOWN_MODE(INBAND_RESIZE_NOTIFICATION); #undef KNOWN_MODE case ALTERNATE_SCREEN: @@ -3872,6 +3878,7 @@ WRAP0(clear_scrollback) MODE_GETSET(in_bracketed_paste_mode, BRACKETED_PASTE) MODE_GETSET(focus_tracking_enabled, FOCUS_TRACKING) +MODE_GETSET(color_preference_notification, COLOR_PREFERENCE_NOTIFICATION) MODE_GETSET(in_band_resize_notification, INBAND_RESIZE_NOTIFICATION) MODE_GETSET(auto_repeat_enabled, DECARM) MODE_GETSET(cursor_visible, DECTCEM) @@ -4898,6 +4905,7 @@ static PyMethodDef methods[] = { static PyGetSetDef getsetters[] = { GETSET(in_bracketed_paste_mode) + GETSET(color_preference_notification) GETSET(auto_repeat_enabled) GETSET(focus_tracking_enabled) GETSET(in_band_resize_notification) diff --git a/kitty/screen.h b/kitty/screen.h index f9032c2b2..06e5fa56f 100644 --- a/kitty/screen.h +++ b/kitty/screen.h @@ -15,7 +15,7 @@ typedef enum ScrollTypes { SCROLL_LINE = -999999, SCROLL_PAGE, SCROLL_FULL } ScrollType; typedef struct { - bool mLNM, mIRM, mDECTCEM, mDECSCNM, mDECOM, mDECAWM, mDECCOLM, mDECARM, mDECCKM, + bool mLNM, mIRM, mDECTCEM, mDECSCNM, mDECOM, mDECAWM, mDECCOLM, mDECARM, mDECCKM, mCOLOR_PREFERENCE_NOTIFICATION, mBRACKETED_PASTE, mFOCUS_TRACKING, mDECSACE, mHANDLE_TERMIOS_SIGNALS, mINBAND_RESIZE_NOTIFICATION; MouseTrackingMode mouse_tracking_mode; MouseTrackingProtocol mouse_tracking_protocol; diff --git a/kitty/window.py b/kitty/window.py index bdf6ef93c..f8da6527e 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -1325,6 +1325,15 @@ class Window: if default_bg_changed: get_boss().default_bg_changed_for(self.id) + def on_color_scheme_preference_change(self) -> None: + if self.screen.color_preference_notification: + self.report_color_scheme_preference() + + def report_color_scheme_preference(self) -> None: + cp = self.screen.color_profile + n = 1 if cp.default_bg.is_dark else 2 + self.screen.send_escape_code_to_child(ESC_CSI, f'?997;{n}n') + def set_color_table_color(self, code: int, bvalue: Optional[memoryview] = None) -> None: value = str(bvalue or b'', 'utf-8', 'replace') cp = self.screen.color_profile