Color Detection
Æsh Readline provides automatic terminal color detection to help your application adapt its color scheme to the user’s terminal environment.
Overview
The TerminalColorDetector API detects:
- Color Depth - How many colors the terminal supports (8, 16, 256, or true color)
- Theme - Whether the terminal has a light or dark background
- RGB Colors - The actual foreground and background colors (when available)
This allows your application to automatically choose appropriate colors that will be readable on any terminal background.
Quick Start
Fast Detection (Environment Only)
For immediate, non-blocking detection based on environment variables:
import org.aesh.terminal.utils.TerminalColorCapability;
TerminalColorCapability cap = TerminalColorCapability.detectFromEnvironment();
if (cap.getTheme().isDark()) {
// Use light text colors
} else {
// Use dark text colors
}Full Detection (With Terminal Query)
For more accurate detection that queries the terminal:
import org.aesh.readline.terminal.TerminalColorDetector;
import org.aesh.readline.tty.terminal.TerminalConnection;
TerminalConnection connection = new TerminalConnection();
TerminalColorCapability cap = TerminalColorDetector.detect(connection);
System.out.println("Theme: " + cap.getTheme());
System.out.println("Color depth: " + cap.getColorDepth());Cached Detection
For repeated access without re-detection overhead:
// Detects once and caches for 5 minutes
TerminalColorCapability cap = TerminalColorDetector.detectCached(connection);TerminalColorCapability
The TerminalColorCapability class encapsulates all detected information:
TerminalColorCapability cap = TerminalColorDetector.detect(connection);
// Theme detection
TerminalTheme theme = cap.getTheme(); // DARK, LIGHT, or UNKNOWN
boolean isDark = cap.getTheme().isDark(); // true for DARK or UNKNOWN
// Color depth
ColorDepth depth = cap.getColorDepth();
boolean has256 = depth.supports256Colors();
boolean hasTrueColor = depth.supportsTrueColor();
// Actual RGB colors (may be null if not detectable)
int[] bgRGB = cap.getBackgroundRGB(); // [r, g, b] or null
int[] fgRGB = cap.getForegroundRGB(); // [r, g, b] or nullSuggested Color Codes
Get ANSI color codes that work well with the detected theme:
TerminalColorCapability cap = TerminalColorDetector.detect(connection);
// These return appropriate codes for the detected background
int normalText = cap.getSuggestedForegroundCode(); // 30 (black) or 37 (white)
int errorText = cap.getSuggestedErrorCode(); // 31 or 91 (red variants)
int successText = cap.getSuggestedSuccessCode(); // 32 or 92 (green variants)
int warningText = cap.getSuggestedWarningCode(); // 33 or 93 (yellow variants)
int infoText = cap.getSuggestedInfoCode(); // 34 or 94 (blue variants)
// Use in ANSI escape sequences
connection.write("\u001B[" + errorText + "mError: Something went wrong\u001B[0m\n");ColorDepth
The ColorDepth enum represents terminal color capabilities:
| Value | Colors | Description |
|---|---|---|
NO_COLOR | 0 | No color support |
COLORS_8 | 8 | Basic ANSI colors (30-37) |
COLORS_16 | 16 | Extended ANSI colors (30-37, 90-97) |
COLORS_256 | 256 | 256-color palette (38;5;N) |
TRUE_COLOR | 16M | 24-bit RGB (38;2;R;G;B) |
ColorDepth depth = cap.getColorDepth();
if (depth.supportsTrueColor()) {
// Use full RGB colors
connection.write("\u001B[38;2;255;128;0mOrange text\u001B[0m");
} else if (depth.supports256Colors()) {
// Use 256-color palette
connection.write("\u001B[38;5;208mOrange text\u001B[0m");
} else {
// Fall back to basic colors
connection.write("\u001B[33mYellow text\u001B[0m");
}TerminalTheme
The TerminalTheme enum represents the terminal’s background brightness:
| Value | Description |
|---|---|
DARK | Dark background (use light text) |
LIGHT | Light background (use dark text) |
UNKNOWN | Could not detect (assumes dark) |
TerminalTheme theme = cap.getTheme();
switch (theme) {
case DARK:
// Use bright/light colors for text
break;
case LIGHT:
// Use dark colors for text
break;
case UNKNOWN:
// Default to dark theme assumption
break;
}
// Helper method - returns true for DARK and UNKNOWN
if (theme.isDark()) {
// Use light colors
}Detection Methods
The detector uses multiple strategies to determine the terminal theme:
1. OSC Color Queries (Most Accurate)
Queries the terminal directly for its background color using OSC 10/11 escape sequences:
ESC ] 11 ; ? BEL → ESC ] 11 ; rgb:RRRR/GGGG/BBBB BELThis works with most modern terminal emulators including:
- iTerm2, Kitty, WezTerm, Alacritty, Ghostty
- GNOME Terminal, Konsole, xterm
- Windows Terminal
2. Environment Variables
Checks standard environment variables:
| Variable | Example | Meaning |
|---|---|---|
COLORFGBG | 15;0 | Foreground;Background color indices |
COLORTERM | truecolor | Color depth hint |
APPLE_INTERFACE_STYLE | Dark | macOS dark mode |
3. Terminal-Specific Detection
For terminals that don’t support OSC queries, the detector reads configuration files:
Visual Studio Code
Reads settings.json for workbench.colorTheme:
- Linux:
~/.config/Code/User/settings.json - macOS:
~/Library/Application Support/Code/User/settings.json - Windows:
%APPDATA%\Code\User\settings.json
JetBrains IDEs (IntelliJ, PyCharm, etc.)
Reads colors.scheme.xml for the color scheme:
- Linux:
~/.config/JetBrains/<Product>/options/colors.scheme.xml - macOS:
~/Library/Application Support/JetBrains/<Product>/options/colors.scheme.xml - Windows:
%APPDATA%\JetBrains\<Product>\options\colors.scheme.xml
Alacritty
Reads alacritty.toml or alacritty.yml:
- Linux/macOS:
~/.config/alacritty/alacritty.toml - Windows:
%APPDATA%\alacritty\alacritty.toml
Windows Terminal
Reads settings.json for colorScheme:
%LOCALAPPDATA%\Packages\Microsoft.WindowsTerminal_*/LocalState/settings.json
ConEmu/Cmder
Reads ConEmu.xml for palette settings.
Windows System Dark Mode
Queries the Windows registry for AppsUseLightTheme.
Multiplexer Support (tmux/screen)
When running inside tmux or GNU Screen, color detection requires special handling:
// Check if running in a multiplexer
if (TerminalColorDetector.isRunningInTmux()) {
System.out.println("Running inside tmux");
}
if (TerminalColorDetector.isRunningInMultiplexer()) {
System.out.println("Running inside tmux or screen");
}tmux Passthrough
For tmux versions before 3.3, OSC queries need to be wrapped in DCS passthrough sequences. The detector handles this automatically when:
- tmux 3.3+ is detected (native OSC support)
allow-passthroughis enabled in tmux configTMUX_PASSTHROUGH=1environment variable is set
To enable passthrough in tmux:
# In tmux.conf
set -g allow-passthrough on
# Or at runtime
tmux set -g allow-passthrough onExample Application
Here’s a complete example that adapts colors based on detection:
import org.aesh.readline.terminal.TerminalColorDetector;
import org.aesh.readline.tty.terminal.TerminalConnection;
import org.aesh.terminal.utils.TerminalColorCapability;
public class AdaptiveColorApp {
private static final String RESET = "\u001B[0m";
public static void main(String[] args) throws Exception {
TerminalConnection connection = new TerminalConnection();
TerminalColorCapability cap = TerminalColorDetector.detect(connection);
// Get theme-appropriate colors
int error = cap.getSuggestedErrorCode();
int success = cap.getSuggestedSuccessCode();
int info = cap.getSuggestedInfoCode();
// Display messages with appropriate colors
connection.write("\u001B[" + info + "m[INFO]\u001B[0m Application started\n");
connection.write("\u001B[" + success + "m[OK]\u001B[0m Configuration loaded\n");
connection.write("\u001B[" + error + "m[ERROR]\u001B[0m Unable to connect\n");
// Show detection results
connection.write("\nDetected: " + cap.getTheme() + " theme, " +
cap.getColorDepth() + "\n");
if (cap.hasBackgroundColor()) {
int[] bg = cap.getBackgroundRGB();
connection.write("Background: RGB(" + bg[0] + "," + bg[1] + "," + bg[2] + ")\n");
}
connection.close();
}
}Performance Considerations
Detection Timing
| Method | Time | Blocking |
|---|---|---|
detectFromEnvironment() | < 1ms | No |
detectFast() | < 1ms | No |
detect() | 50-200ms | Yes (waits for terminal response) |
detectCached() | < 1ms (after first call) | Only on first call |
Recommendations
- Use
detectCached()for most applications - it caches results for 5 minutes - Use
detectFast()if you can’t wait for terminal queries - Call detection once at startup, not on every output
- Respect user overrides - allow users to specify theme preference
// Good: Detect once at startup
public class MyApp {
private static TerminalColorCapability colors;
public static void main(String[] args) {
TerminalConnection conn = new TerminalConnection();
colors = TerminalColorDetector.detectCached(conn);
// Use 'colors' throughout the application
}
}Supported Terminals
The following terminals have been tested with full detection support:
| Terminal | OSC Query | Config File | Notes |
|---|---|---|---|
| iTerm2 | Yes | - | Full support |
| Kitty | Yes | - | Full support |
| WezTerm | Yes | - | Full support |
| Alacritty | Yes | Yes | Both methods work |
| Ghostty | Yes | - | Full support |
| GNOME Terminal | Yes | - | Full support |
| Konsole | Yes | - | Full support |
| Windows Terminal | Yes | Yes | Both methods work |
| VS Code Terminal | Limited | Yes | Config file preferred |
| JetBrains IDEs | No | Yes | Config file only |
| tmux | Yes* | - | *Requires 3.3+ or passthrough |
| ConEmu/Cmder | No | Yes | Config file only |
| Apple Terminal | Limited | - | Basic support |
Troubleshooting
Theme Not Detected
If theme detection returns UNKNOWN:
- Check environment variables:
echo $TERM $COLORTERM - Verify terminal support: Not all terminals support OSC queries
- tmux users: Enable passthrough with
set -g allow-passthrough on - IDE users: Ensure the IDE config files are in standard locations
Wrong Theme Detected
If the detected theme doesn’t match your terminal:
- Custom color schemes: Some schemes may not be in the known list
- Override manually: Allow users to specify their preference
- Report the issue: Help us improve detection for your terminal
OSC Query Hangs
If detection seems to hang:
- Reduce timeout: Use
detect(connection, 100)for shorter timeout - Use fast detection:
detectFast(connection)skips OSC queries - Check terminal: Some terminals don’t respond to OSC queries
// With custom timeout (milliseconds)
TerminalColorCapability cap = TerminalColorDetector.detect(connection, 100);Using TerminalColor with Detection
The TerminalColor class integrates seamlessly with color detection, providing theme-aware factory methods and automatic color depth adaptation.
Theme-Aware Semantic Colors
Use semantic factory methods that automatically adjust for the detected terminal theme:
import org.aesh.readline.terminal.formatting.TerminalColor;
import org.aesh.terminal.utils.TerminalColorCapability;
TerminalColorCapability cap = TerminalColorDetector.detect(connection);
// These methods automatically choose appropriate colors for the theme
TerminalColor error = TerminalColor.forError(cap); // Red, bright on dark
TerminalColor success = TerminalColor.forSuccess(cap); // Green, bright on dark
TerminalColor warning = TerminalColor.forWarning(cap); // Yellow, bright on dark
TerminalColor info = TerminalColor.forInfo(cap); // Cyan/Blue, bright on dark
TerminalColor highlight = TerminalColor.forHighlight(cap); // Emphasized text
TerminalColor muted = TerminalColor.forMuted(cap); // Secondary/dim textOn dark themes, these return bright variants for readability. On light themes, they use normal intensity to avoid glaring colors.
Example: Adaptive Status Messages
TerminalColorCapability cap = TerminalColorDetector.detectCached(connection);
// Create semantic colors once
TerminalColor errorColor = TerminalColor.forError(cap);
TerminalColor successColor = TerminalColor.forSuccess(cap);
TerminalColor infoColor = TerminalColor.forInfo(cap);
// Use with TerminalString
TerminalString errorMsg = new TerminalString("[ERROR] Connection failed", errorColor);
TerminalString okMsg = new TerminalString("[OK] Connected", successColor);
TerminalString infoMsg = new TerminalString("[INFO] Processing...", infoColor);
connection.write(errorMsg.toString() + "\n");
connection.write(okMsg.toString() + "\n");
connection.write(infoMsg.toString() + "\n");Color Depth Adaptation
When using RGB colors on terminals with limited color support, use forCapability() to automatically downgrade:
// Create an RGB color
TerminalColor brandColor = TerminalColor.fromHex("#FF5733");
// Adapt to terminal capabilities
TerminalColor adapted = brandColor.forCapability(cap);
// 'adapted' will be:
// - The original RGB if terminal supports true color
// - Nearest 256-color palette index if terminal supports 256 colors
// - Nearest basic 16 color if terminal only supports basic colorsThis ensures your colors always work, even on limited terminals. See Terminal Colors for more details on TerminalColor RGB and formatting features.