Skip to content

Commit 2c7d1c6

Browse files
committed
added docs for theme detection
1 parent bed66ec commit 2c7d1c6

File tree

3 files changed

+275
-20
lines changed

3 files changed

+275
-20
lines changed

content/docs/readline/color-detection.md

Lines changed: 126 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,8 @@ if (cap.hasPaletteColors()) {
8888
}
8989
```
9090

91-
The `detect()` method queries:
91+
The `detect()` method queries (in priority order):
92+
- Theme mode (CSI ? 996 n) - if supported by the terminal
9293
- Foreground color (OSC 10)
9394
- Background color (OSC 11)
9495
- Cursor color (OSC 12) - if supported
@@ -233,9 +234,55 @@ if (theme.isDark()) {
233234

234235
## Detection Methods
235236

236-
The detector uses multiple strategies to determine the terminal theme:
237+
The detector uses multiple strategies to determine the terminal theme, in priority order:
237238

238-
### 1. OSC Color Queries (Most Accurate)
239+
### 1. Theme Mode Query (CSI ? 996 n) — Fastest & Simplest
240+
241+
The preferred method on supporting terminals. Sends a single escape sequence and gets back a direct dark/light answer — no RGB parsing or luminance calculation needed.
242+
243+
```
244+
CSI ? 996 n → CSI ? 997 ; 1 n (dark)
245+
CSI ? 997 ; 2 n (light)
246+
```
247+
248+
This is based on the [Contour VT extension](https://contour-terminal.org/vt-extensions/color-palette-update-notifications/) for color palette update notifications.
249+
250+
#### Using via Connection
251+
252+
```java
253+
// One-shot query: returns DARK, LIGHT, or null (unsupported/timeout)
254+
TerminalTheme theme = connection.queryThemeMode(500);
255+
256+
if (theme != null) {
257+
System.out.println("Theme: " + theme);
258+
} else {
259+
// Fall back to OSC 10/11 or environment detection
260+
}
261+
```
262+
263+
#### Checking Support
264+
265+
```java
266+
// Check before querying (avoids timeout on unsupported terminals)
267+
if (connection.supportsThemeQuery()) {
268+
TerminalTheme theme = connection.queryThemeMode(500);
269+
}
270+
```
271+
272+
#### Supported Terminals
273+
274+
| Terminal | Version | Notes |
275+
|----------|---------|-------|
276+
| Contour | 0.4.0+ | Origin of the protocol |
277+
| Ghostty | 1.0.0+ | Full support |
278+
| Kitty | 0.38.1+ | Full support |
279+
| tmux || Passes through to underlying terminal |
280+
| VTE / GNOME Terminal | 0.82.0+ | Full support |
281+
| Foot || Full support |
282+
283+
The `TerminalColorDetector` tries this method first when the terminal supports it, then falls back to OSC queries and environment detection.
284+
285+
### 2. OSC Color Queries (Most Accurate Fallback)
239286

240287
Queries the terminal directly for its background color using OSC 10/11 escape sequences:
241288

@@ -373,7 +420,7 @@ if (bgColor != null) {
373420

374421
See [Terminal Colors](terminal-colors#ansi-color-utilities) for complete documentation of RGB/ANSI conversion utilities.
375422

376-
### 2. Environment Variables
423+
### 3. Environment Variables
377424

378425
Checks standard environment variables via [`TerminalEnvironment`](terminal-environment):
379426

@@ -390,7 +437,7 @@ Checks standard environment variables via [`TerminalEnvironment`](terminal-envir
390437

391438
The `TerminalEnvironment` class parses these once and caches the results. See [Terminal Environment](terminal-environment) for details on all supported environment variables and terminal types.
392439

393-
### 3. Terminal-Specific Detection
440+
### 4. Terminal-Specific Detection
394441

395442
For terminals that don't support OSC queries, the detector reads configuration files:
396443

@@ -468,6 +515,63 @@ set -g allow-passthrough on
468515
tmux set -g allow-passthrough on
469516
```
470517

518+
## Real-Time Theme Change Notifications
519+
520+
Terminals that support the CSI ? 996 n protocol can also send **unsolicited** notifications when the user switches between dark and light mode. This allows your application to adapt its colors in real time without polling.
521+
522+
### How It Works
523+
524+
1. Your application sends `CSI ? 2031 h` to **subscribe** to theme change notifications
525+
2. The terminal sends `CSI ? 997 ; 1 n` (dark) or `CSI ? 997 ; 2 n` (light) whenever the theme changes
526+
3. Your application sends `CSI ? 2031 l` to **unsubscribe**
527+
528+
The `EventDecoder` in the input pipeline automatically intercepts these unsolicited DSR sequences and routes them to a registered handler, preventing them from appearing as garbage in the readline buffer.
529+
530+
### Subscribing to Theme Changes
531+
532+
```java
533+
import org.aesh.terminal.utils.TerminalTheme;
534+
535+
// One-call setup: register handler and enable notifications
536+
connection.enableThemeChangeNotification(theme -> {
537+
System.out.println("Theme changed to: " + theme);
538+
// Update your cached color capability
539+
capability = new TerminalColorCapability(capability.getColorDepth(), theme);
540+
});
541+
```
542+
543+
Or separately:
544+
545+
```java
546+
// Register handler first
547+
connection.setThemeChangeHandler(theme -> {
548+
System.out.println("Theme changed to: " + theme);
549+
});
550+
551+
// Then enable notifications
552+
connection.enableThemeChangeNotification();
553+
```
554+
555+
### Unsubscribing
556+
557+
```java
558+
// Stop the terminal from sending notifications
559+
connection.disableThemeChangeNotification();
560+
561+
// Optionally remove the handler
562+
connection.setThemeChangeHandler(null);
563+
```
564+
565+
### Performance
566+
567+
When no theme change handler is registered, the DSR filtering in `EventDecoder` has **zero overhead** — a single null check on the fast path. When a handler is registered but the input contains no ESC bytes, the overhead is a single linear scan with no allocation. The state machine only activates when an ESC byte is found in the input.
568+
569+
### Supported Terminals
570+
571+
Theme change notifications use the same terminal support as the CSI ? 996 n query. See the [Theme Mode Query](#1-theme-mode-query-csi--996-n--fastest--simplest) section above for the full compatibility table.
572+
573+
See the [Connection](connection#theme-mode-queries) documentation for the complete API reference.
574+
471575
## Example Application
472576

473577
Here's a complete example that adapts colors based on detection:
@@ -543,21 +647,23 @@ public class MyApp {
543647

544648
The following terminals have been tested with full detection support:
545649

546-
| Terminal | OSC Query | Config File | Notes |
547-
|----------|-----------|-------------|-------|
548-
| iTerm2 | Yes | - | Full support |
549-
| Kitty | Yes | - | Full support |
550-
| WezTerm | Yes | - | Full support |
551-
| Alacritty | Yes | Yes | Both methods work |
552-
| Ghostty | Yes | - | Full support |
553-
| GNOME Terminal | Yes | - | Full support |
554-
| Konsole | Yes | - | Full support |
555-
| Windows Terminal | Yes | Yes | Both methods work |
556-
| VS Code Terminal | Limited | Yes | Config file preferred |
557-
| JetBrains IDEs | No | Yes | Config file only |
558-
| tmux | Yes* | - | *Requires 3.3+ or passthrough |
559-
| ConEmu/Cmder | No | Yes | Config file only |
560-
| Apple Terminal | Limited | - | Basic support |
650+
| Terminal | OSC Query | Theme DSR | Config File | Notes |
651+
|----------|-----------|-----------|-------------|-------|
652+
| iTerm2 | Yes | No | - | Full OSC support |
653+
| Kitty | Yes | Yes (0.38.1+) | - | Full support |
654+
| WezTerm | Yes | No | - | Full OSC support |
655+
| Alacritty | Yes | No | Yes | Both methods work |
656+
| Ghostty | Yes | Yes (1.0.0+) | - | Full support |
657+
| GNOME Terminal | Yes | Yes (VTE 0.82.0+) | - | Full support |
658+
| Konsole | Yes | No | - | Full OSC support |
659+
| Windows Terminal | Yes | No | Yes | Both methods work |
660+
| Foot | Limited | Yes | - | Theme DSR preferred |
661+
| Contour | Yes | Yes (0.4.0+) | - | Origin of Theme DSR protocol |
662+
| VS Code Terminal | Limited | No | Yes | Config file preferred |
663+
| JetBrains IDEs | No | No | Yes | Config file only |
664+
| tmux | Yes* | Yes* | - | *Requires 3.3+ or passthrough |
665+
| ConEmu/Cmder | No | No | Yes | Config file only |
666+
| Apple Terminal | Limited | No | - | Basic support |
561667

562668
## Troubleshooting
563669

content/docs/readline/connection.md

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,24 @@ connection.setCloseHandler(ignored -> {
113113
Consumer<Void> closeHandler = connection.getCloseHandler();
114114
```
115115

116+
### Theme Change Handler
117+
118+
Called when the terminal's theme changes (dark/light mode switch). Requires subscribing to theme change notifications with `enableThemeChangeNotification()`:
119+
120+
```java
121+
import org.aesh.terminal.utils.TerminalTheme;
122+
123+
// Set handler for theme changes
124+
connection.setThemeChangeHandler(theme -> {
125+
System.out.println("Theme changed to: " + theme);
126+
// Re-detect colors or update cached capability
127+
});
128+
129+
Consumer<TerminalTheme> themeHandler = connection.getThemeChangeHandler();
130+
```
131+
132+
When a handler is registered, the `EventDecoder` in the input pipeline intercepts unsolicited `CSI ? 997 ; Ps n` responses and routes them to this handler, preventing them from appearing as input. See [Theme Mode Queries](#theme-mode-queries) for the full API.
133+
116134
## Terminal Properties
117135

118136
```java
@@ -331,6 +349,109 @@ Map<Integer, int[]> colors = TerminalColorDetector.queryColorsWithFallback(conne
331349
// Always returns colors - actual if OSC works, estimated if not
332350
```
333351

352+
## Theme Mode Queries
353+
354+
The `Connection` interface provides methods for querying and subscribing to terminal theme changes using the `CSI ? 996 n` protocol. This is simpler and faster than OSC 10/11 RGB queries — it returns a direct `DARK` or `LIGHT` answer.
355+
356+
See [Color Detection](color-detection#1-theme-mode-query-csi--996-n--fastest--simplest) for background on the protocol.
357+
358+
### One-Shot Query
359+
360+
Query the current theme mode:
361+
362+
```java
363+
// Returns DARK, LIGHT, or null (unsupported/timeout)
364+
TerminalTheme theme = connection.queryThemeMode(500);
365+
366+
if (theme != null) {
367+
System.out.println("Terminal theme: " + theme);
368+
}
369+
```
370+
371+
### Checking Support
372+
373+
```java
374+
// Check if the terminal supports theme queries (avoids timeout)
375+
if (connection.supportsThemeQuery()) {
376+
TerminalTheme theme = connection.queryThemeMode(500);
377+
}
378+
```
379+
380+
Support is determined by the `Device.TerminalType` enum — terminals like Kitty, Ghostty, Foot, Contour, GNOME Terminal, and tmux report `supportsThemeDsr() == true`.
381+
382+
### Subscribing to Live Theme Changes
383+
384+
Enable real-time notifications when the user switches dark/light mode:
385+
386+
```java
387+
// One-call setup: register handler and enable notifications
388+
connection.enableThemeChangeNotification(theme -> {
389+
System.out.println("Theme changed to: " + theme);
390+
// Update cached colors
391+
capability = new TerminalColorCapability(capability.getColorDepth(), theme);
392+
});
393+
```
394+
395+
Or step by step:
396+
397+
```java
398+
// 1. Register the handler
399+
connection.setThemeChangeHandler(theme -> {
400+
System.out.println("Theme changed to: " + theme);
401+
});
402+
403+
// 2. Tell the terminal to send notifications (CSI ? 2031 h)
404+
connection.enableThemeChangeNotification();
405+
```
406+
407+
### Unsubscribing
408+
409+
```java
410+
// Tell the terminal to stop sending notifications (CSI ? 2031 l)
411+
connection.disableThemeChangeNotification();
412+
413+
// Optionally remove the handler
414+
connection.setThemeChangeHandler(null);
415+
```
416+
417+
### Supported Terminals
418+
419+
| Terminal | Version | Theme DSR |
420+
|----------|---------|-----------|
421+
| Contour | 0.4.0+ | Yes |
422+
| Ghostty | 1.0.0+ | Yes |
423+
| Kitty | 0.38.1+ | Yes |
424+
| Foot || Yes |
425+
| VTE / GNOME Terminal | 0.82.0+ | Yes |
426+
| tmux || Yes (passthrough) |
427+
428+
### Complete Example
429+
430+
```java
431+
import org.aesh.readline.tty.terminal.TerminalConnection;
432+
import org.aesh.terminal.utils.TerminalTheme;
433+
434+
TerminalConnection connection = new TerminalConnection();
435+
436+
// Query current theme
437+
if (connection.supportsThemeQuery()) {
438+
TerminalTheme theme = connection.queryThemeMode(500);
439+
System.out.println("Current theme: " + theme);
440+
441+
// Subscribe to changes
442+
connection.enableThemeChangeNotification(newTheme -> {
443+
System.out.println("Theme switched to: " + newTheme);
444+
});
445+
}
446+
447+
// ... application runs ...
448+
449+
// Clean up before exit
450+
connection.disableThemeChangeNotification();
451+
connection.setThemeChangeHandler(null);
452+
connection.close();
453+
```
454+
334455
## Device Attributes (DA1/DA2)
335456

336457
Device Attributes queries allow you to detect terminal capabilities that cannot be determined from terminfo alone.

content/docs/readline/terminal.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ connection.setSizeHandler(size -> { /* handle resize */ });
4747
Consumer<Signal> signalHandler = connection.getSignalHandler();
4848
connection.setSignalHandler(signal -> { /* handle signals */ });
4949

50+
// Theme change handler (CSI ? 997 DSR notifications)
51+
Consumer<TerminalTheme> themeHandler = connection.getThemeChangeHandler();
52+
connection.setThemeChangeHandler(theme -> { /* handle dark/light switch */ });
53+
5054
// Close
5155
connection.close();
5256
connection.close(exitCode);
@@ -316,3 +320,27 @@ if (connection.supportsOscQueries(da)) {
316320
// DA1 indicates modern terminal features
317321
}
318322
```
323+
324+
## Theme Mode Detection
325+
326+
Modern terminals can report whether they are using a dark or light color scheme using the `CSI ? 996 n` protocol. This is faster and simpler than parsing OSC 10/11 RGB responses.
327+
328+
```java
329+
// One-shot query
330+
if (connection.supportsThemeQuery()) {
331+
TerminalTheme theme = connection.queryThemeMode(500);
332+
// DARK, LIGHT, or null
333+
}
334+
335+
// Subscribe to real-time dark/light mode changes
336+
connection.enableThemeChangeNotification(theme -> {
337+
System.out.println("Theme changed to: " + theme);
338+
});
339+
340+
// Unsubscribe
341+
connection.disableThemeChangeNotification();
342+
```
343+
344+
Supported terminals include Kitty (0.38.1+), Ghostty (1.0.0+), Contour (0.4.0+), Foot, VTE/GNOME Terminal (0.82.0+), and tmux.
345+
346+
See [Color Detection](color-detection#1-theme-mode-query-csi--996-n--fastest--simplest) for protocol details and [Connection](connection#theme-mode-queries) for the full API reference.

0 commit comments

Comments
 (0)