Last active
April 30, 2025 16:09
-
-
Save wroyca/94a92672e32802b884a6934615b0a822 to your computer and use it in GitHub Desktop.
Synchronizes Emacs' background color with the terminal background by using VT100 control sequences
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;;; appearance-control-sequences.el --- Terminal control sequence integration -*- lexical-binding: t -*- | |
;;; Commentary: | |
;; | |
;; This module synchronizes Emacs' background color with the terminal background | |
;; by using VT100 control sequences. It supports both setting the background | |
;; color (using the "\e]11;" sequence) and resetting it on exit (using "\e]111;" | |
;; if available, or restoring the original color otherwise). | |
;; | |
;;; Code: | |
(defgroup appearance-control-sequences nil | |
"Terminal control sequence integration for Emacs." | |
:group 'terminals | |
:prefix "appearance-control-sequences-") | |
(defcustom appearance-control-sequences-sync-background t | |
"Whether to synchronize terminal background with Emacs background." | |
:type 'boolean | |
:group 'appearance-control-sequences) | |
(defcustom appearance-control-sequences-query-timeout 0.5 | |
"Timeout in seconds when querying terminal capabilities." | |
:type 'number | |
:group 'appearance-control-sequences) | |
(defvar appearance-control-sequences--original-background nil | |
"Original terminal background color before modification.") | |
(defvar appearance-control-sequences--supports-reset nil | |
"Whether terminal supports the background reset control sequence (111).") | |
(defvar appearance-control-sequences--initialized nil | |
"Whether terminal capability detection has been performed.") | |
;;; Internal functions | |
(defun appearance-control-sequences--query-terminal-color () | |
"Query the terminal for its current background color. | |
Returns the color string if successful, nil otherwise." | |
(when (and (not (display-graphic-p)) | |
(>= emacs-major-version 25)) ; read-event with timeout needs Emacs 25+ | |
(let (chr response) | |
;; Send ANSI query sequence to request background color | |
(send-string-to-terminal "\e]11;?\e\\") | |
;; Validation: properly formatted response begins with escape-bracket | |
(when (and (eq (read-event nil nil appearance-control-sequences-query-timeout) ?\e) | |
(eq (read-event nil nil appearance-control-sequences-query-timeout) ?\])) | |
;; Collect response characters until terminating backslash | |
(setq response "") | |
(while (and (not (eq (setq chr (read-event nil nil appearance-control-sequences-query-timeout)) ?\\)) | |
chr) | |
(setq response (concat response (string chr)))) | |
;; Extract RGB components from response (format: 11;rgb:RRRR/GGGG/BBBB) | |
(when (string-match "11;rgb:\\([a-f0-9]+\\)/\\([a-f0-9]+\\)/\\([a-f0-9]+\\)" response) | |
response))))) | |
(defun appearance-control-sequences--detect-terminal-capabilities () | |
"Detect terminal capabilities for background color control. | |
Returns non-nil if terminal supports background color setting." | |
(when (and (not (display-graphic-p)) | |
(not appearance-control-sequences--initialized)) | |
;; First establish baseline by capturing current background | |
(setq appearance-control-sequences--original-background | |
(appearance-control-sequences--query-terminal-color)) | |
;; Probe for reset capability - modern terminals support this sequence | |
(when appearance-control-sequences--original-background | |
(send-string-to-terminal "\e]111;?\e\\") | |
(setq appearance-control-sequences--supports-reset | |
(and (eq (read-event nil nil appearance-control-sequences-query-timeout) ?\e) | |
(eq (read-event nil nil appearance-control-sequences-query-timeout) ?\])))) | |
(setq appearance-control-sequences--initialized t) | |
appearance-control-sequences--original-background)) | |
(defun appearance-control-sequences--update-terminal-background (&optional frame) | |
"Update terminal background to match FRAME's background color. | |
If FRAME is nil, use the current frame." | |
(when (and appearance-control-sequences-sync-background | |
(not (display-graphic-p)) | |
appearance-control-sequences--initialized) | |
(let ((bg (frame-parameter frame 'background-color))) | |
(when bg | |
(send-string-to-terminal | |
(format "\e]11;%s\a" bg)))))) | |
(defun appearance-control-sequences--reset-terminal-background (&rest _args) | |
"Reset terminal background to its original state. | |
This can be called with any number of arguments, allowing it to be | |
used with various hooks." | |
(when (and appearance-control-sequences-sync-background | |
(not (display-graphic-p)) | |
appearance-control-sequences--initialized) | |
(if appearance-control-sequences--supports-reset | |
;; Use the dedicated reset sequence if supported | |
(send-string-to-terminal "\e]111;\a") | |
;; Otherwise, restore the original background color if we saved it | |
(when appearance-control-sequences--original-background | |
(send-string-to-terminal | |
(format "\e]11;%s\a" | |
(progn | |
(string-match "11;\\(.*\\)" appearance-control-sequences--original-background) | |
(match-string 1 appearance-control-sequences--original-background)))))))) | |
(defun appearance-control-sequences--after-theme-change (&rest _) | |
"Update terminal background after theme changes. | |
This ignores all arguments and just updates the terminal background. | |
It is designed to be used as advice for theme-related functions." | |
(appearance-control-sequences--update-terminal-background)) | |
;;;###autoload | |
(define-minor-mode appearance-control-sequences-mode | |
"Toggle terminal control sequence integration mode. | |
When enabled, synchronizes terminal background with Emacs background | |
and ensures proper restoration on exit." | |
:global t | |
:lighter " CS" | |
(if appearance-control-sequences-mode | |
(progn | |
;; Perform one-time terminal capability discovery | |
(unless appearance-control-sequences--initialized | |
(appearance-control-sequences--detect-terminal-capabilities)) | |
;; Register lifecycle events requiring background updates | |
(add-hook 'after-make-frame-functions #'appearance-control-sequences--update-terminal-background) | |
(add-hook 'window-configuration-change-hook #'appearance-control-sequences--update-terminal-background) | |
(add-hook 'kill-emacs-hook #'appearance-control-sequences--reset-terminal-background) | |
(add-hook 'suspend-tty-functions #'appearance-control-sequences--reset-terminal-background) | |
(add-hook 'resume-tty-functions #'appearance-control-sequences--update-terminal-background) | |
;; Theme changes require special handling via advice | |
(advice-add 'load-theme :after #'appearance-control-sequences--after-theme-change) | |
;; Apply immediately to sync current state | |
(appearance-control-sequences--update-terminal-background)) | |
;; Deregistration to ensure clean state | |
(remove-hook 'after-make-frame-functions #'appearance-control-sequences--update-terminal-background) | |
(remove-hook 'window-configuration-change-hook #'appearance-control-sequences--update-terminal-background) | |
(remove-hook 'kill-emacs-hook #'appearance-control-sequences--reset-terminal-background) | |
(remove-hook 'suspend-tty-functions #'appearance-control-sequences--reset-terminal-background) | |
(remove-hook 'resume-tty-functions #'appearance-control-sequences--update-terminal-background) | |
(advice-remove 'load-theme #'appearance-control-sequences--after-theme-change) | |
;; Explicit cleanup for deterministic behavior | |
(appearance-control-sequences--reset-terminal-background))) | |
;;;###autoload | |
(defun appearance-control-sequences-update-now () | |
"Force an immediate update of the terminal background color." | |
(interactive) | |
(appearance-control-sequences--update-terminal-background)) | |
;;;###autoload | |
(defun appearance-control-sequences-reset-now () | |
"Force an immediate reset of the terminal background color." | |
(interactive) | |
(appearance-control-sequences--reset-terminal-background)) | |
(appearance-control-sequences-mode 1) | |
(provide 'appearance-control-sequences) | |
;;; appearance-control-sequences.el ends here |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment