diff --git a/src/textual/_xterm_parser.py b/src/textual/_xterm_parser.py index 5b810273fa..f4379ff73a 100644 --- a/src/textual/_xterm_parser.py +++ b/src/textual/_xterm_parser.py @@ -14,7 +14,7 @@ # When trying to determine whether the current sequence is a supported/valid # escape sequence, at which length should we give up and consider our search # to be unsuccessful? -_MAX_SEQUENCE_SEARCH_THRESHOLD = 20 +_MAX_SEQUENCE_SEARCH_THRESHOLD = 24 _re_mouse_event = re.compile("^" + re.escape("\x1b[") + r"( None: bracketed_paste = False break + if sequence.startswith(BG_COLOR) and len(sequence) == BG_COLOR_LEN: + rgb_str = sequence[len(BG_COLOR) :] + rgb = rgb_str.split("/") + if len(rgb) == 3: + r = int(rgb[0], 16) + g = int(rgb[1], 16) + b = int(rgb[2], 16) + self.debug_log( + f"Detected BG_COLOR response {r:02x}/{g:02x}/{b:02x}" + ) + on_token(events.BackgroundColor(r, g, b)) + break + if not bracketed_paste: # Check cursor position report if ( diff --git a/src/textual/app.py b/src/textual/app.py index f34ca20370..354d400f9f 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -3275,6 +3275,15 @@ async def _on_app_blur(self, event: events.AppBlur) -> None: self.app_focus = False self.screen.refresh_bindings() + def _is_dark_color(self, r: int, g: int, b: int) -> bool: + # perceived brightness formula + perceived_brightness = 0.2126 * r + 0.7152 * g + 0.0722 * b + return perceived_brightness < 128 + + async def _on_background_color(self, event: events.BackgroundColor) -> None: + """Background color detected""" + self.dark = self._is_dark_color(event.r & 0xFF, event.g & 0xFF, event.b & 0xFF) + def _detach_from_dom(self, widgets: list[Widget]) -> list[Widget]: """Detach a list of widgets from the DOM. diff --git a/src/textual/drivers/linux_driver.py b/src/textual/drivers/linux_driver.py index cd6a4c0130..835ba63675 100644 --- a/src/textual/drivers/linux_driver.py +++ b/src/textual/drivers/linux_driver.py @@ -248,6 +248,7 @@ def on_terminal_resize(signum, stack) -> None: self.flush() self._key_thread = Thread(target=self._run_input_thread) send_size_event() + self.write("\x1b]11;?\x07") # Detect background color self._key_thread.start() self._request_terminal_sync_mode_support() self._enable_bracketed_paste() diff --git a/src/textual/events.py b/src/textual/events.py index ce5ecac4e3..9a2bece489 100644 --- a/src/textual/events.py +++ b/src/textual/events.py @@ -707,3 +707,14 @@ def __init__(self, text: str, stderr: bool = False) -> None: def __rich_repr__(self) -> rich.repr.Result: yield self.text yield self.stderr + + +@dataclass +class BackgroundColor(Event, bubble=False): + """Internal event used when background color of the terminal is + detected + """ + + r: int + g: int + b: int diff --git a/tests/test_xterm_parser.py b/tests/test_xterm_parser.py index 2995738897..92ad77be14 100644 --- a/tests/test_xterm_parser.py +++ b/tests/test_xterm_parser.py @@ -91,7 +91,7 @@ def test_cant_match_escape_sequence_too_long(parser): """The sequence did not match, and we hit the maximum sequence search length threshold, so each character should be issued as a key-press instead. """ - sequence = "\x1b[123456789123456789123" + sequence = "\x1b[123456789123456789123456" events = list(parser.feed(sequence)) # Every character in the sequence is converted to a key press