aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-25 09:25:32 -0500
committerCraig Jennings <c@cjennings.net>2026-05-25 09:25:32 -0500
commit56da3d940b26a51102bce39b3b82dfbbc2b391fd (patch)
tree54ce4e57ef954835ccc0fb7ebdb51a5c7c68f59b
parenta522e5537ab9c94a45656b28e94a73b98f47d4b8 (diff)
downloaddotemacs-56da3d940b26a51102bce39b3b82dfbbc2b391fd.tar.gz
dotemacs-56da3d940b26a51102bce39b3b82dfbbc2b391fd.zip
feat(auto-dim): dim non-selected windows via auto-dim-other-buffers
I added auto-dim-config, a module that loads my local auto-dim-other-buffers fork and dims windows that don't have focus so the selected window stands out. A non-selected window drops to a pure-black background with faded gray text. The dimmed faces live in the dupre theme (themes/dupre-faces.el) so they track theme switches, and the module remaps default, the font-lock faces, and org-block onto them so syntax-highlighted code fades too rather than staying lit. Fringe is left out because dimming it forces a full-frame refresh that flickers on this non-pgtk build. dim-on-focus-out is nil, so tabbing to a browser or terminal on Hyprland doesn't dim the whole frame. vterm and agent windows don't dim either, because the terminal paints its own per-cell colors past the face remap. I'm keeping that, since the agent's output stays readable while I work in code on the other side. The module loads after the theme, carries a load-graph header, joins the header-contract allowlist, and the inventory moves to 103 of 103 classified.
-rw-r--r--docs/design/module-inventory.org9
-rw-r--r--init.el1
-rw-r--r--modules/auto-dim-config.el61
-rw-r--r--tests/test-auto-dim-config.el33
-rw-r--r--tests/test-init-module-headers.el1
-rw-r--r--themes/dupre-faces.el10
6 files changed, 111 insertions, 4 deletions
diff --git a/docs/design/module-inventory.org b/docs/design/module-inventory.org
index a6a065da..ffef323a 100644
--- a/docs/design/module-inventory.org
+++ b/docs/design/module-inventory.org
@@ -10,7 +10,7 @@ each module is inspected and classified. A module moves from [[*Pending
classification][Pending classification]] into [[*Classified modules][Classified
modules]] once its source has been read and its load-graph header written.
-Phase 1 exit criterion: every module required by =init.el= (102 total) is
+Phase 1 exit criterion: every module required by =init.el= (103 total) is
represented here with a category and target load shape, every eager survivor
has a documented reason, and top-level timer/process/network side effects are
identified. Classification proceeds in batches; the header-validation test
@@ -26,13 +26,13 @@ This inventory is independent from the helper inventory owned by
- Batch 1 (Foundation, Layer 1): classified. 7 modules.
- Batch 2 (Text/editing command modules, Layer 2): classified. 9 modules.
- Batch 3 (Core libraries and command modules): classified. 7 modules.
-- Batch 4 (UI / core-UX modules, Layer 2): classified. 10 modules.
+- Batch 4 (UI / core-UX modules, Layer 2): classified. 11 modules.
- Batch 5 (Dev entry-points, diff, help, lint, VC, Layer 2): classified. 9 modules.
- Batch 6 (Programming modules, Layer 2-4): classified. 10 modules.
- Batch 7 (Org modules, Layer 3-4): classified. 13 modules.
- Batch 8 (Domain / integration / optional modules, Layer 2-4): classified. 18 modules.
- Batch 9 (Remaining domain / integration / optional modules, Layer 2-4): classified. 19 modules.
-- 102 of 102 modules classified. Phase 1 classification complete.
+- 103 of 103 modules classified. Phase 1 classification complete.
- No load-order changes have been made; =init.el= keeps its current eager order.
* Legend
@@ -112,6 +112,7 @@ eager reason and stay eager.
| Module | Layer | Cat | Current | Target | Runtime requires | Top-level side effects | Direct load |
|--------+-------+-----+---------+--------+------------------+------------------------+-------------|
+| =auto-dim-config= | 2 | C/O | eager | eager | none (loads ~/code auto-dim fork) | enables global minor mode, edits affected-faces | conditional |
| =ui-config= | 2 | C/S | eager | eager | user-constants | UI defaults, post-command hook, display-buffer-alist | yes |
| =ui-theme= | 2 | C | eager | eager | none | theme load path, theme key | yes |
| =ui-navigation= | 2 | C/P | eager | eager | none | nav keymap, 5 global keys, package config | yes |
@@ -285,4 +286,4 @@ found and fixed; the =Phase 2 fix:= notes below describe the change applied.
* Pending classification
-- None. Every module required by =init.el= is classified (102 of 102).
+- None. Every module required by =init.el= is classified (103 of 103).
diff --git a/init.el b/init.el
index 57dd735a..f65e6085 100644
--- a/init.el
+++ b/init.el
@@ -57,6 +57,7 @@
(require 'ui-config) ;; transparency, cursor color, icons, &c.
(require 'ui-theme) ;; themes and theme persistency
(cj/load-theme-from-file)
+(require 'auto-dim-config) ;; dim non-selected windows (faces live in the theme)
(require 'ui-navigation) ;; the movement and navigation of windows
(require 'font-config) ;; font and emoji configuration
(require 'nerd-icons-config) ;; nerd-icons + completion/ibuffer integration + tint
diff --git a/modules/auto-dim-config.el b/modules/auto-dim-config.el
new file mode 100644
index 00000000..83c5b17c
--- /dev/null
+++ b/modules/auto-dim-config.el
@@ -0,0 +1,61 @@
+;;; auto-dim-config.el --- Dim non-selected windows -*- lexical-binding: t; coding: utf-8; -*-
+;; author Craig Jennings <c@cjennings.net>
+;;
+;;; Commentary:
+;;
+;; Layer: 2 (Core UX).
+;; Category: C/O.
+;; Load shape: eager.
+;; Eager reason: global UI minor mode; the dimming should be visible in the
+;; first frame.
+;; Top-level side effects: enables auto-dim-other-buffers-mode and edits
+;; auto-dim-other-buffers-affected-faces.
+;; Runtime requires: none (loads the auto-dim-other-buffers fork via use-package).
+;; Direct test load: conditional (needs the ~/code fork on the load-path).
+;;
+;; Dims windows that do not have focus so the selected window stands out,
+;; using a local fork of auto-dim-other-buffers (the fork adds a focus-change
+;; debounce). The dimmed faces (auto-dim-other-buffers and
+;; auto-dim-other-buffers-hide) live in the active theme
+;; (themes/dupre-faces.el) so they track theme switches.
+
+;;; Code:
+
+(use-package auto-dim-other-buffers
+ :load-path "~/code/auto-dim-other-buffers.el"
+ :ensure nil
+ ;; :vc (:url "git@cjennings.net:auto-dim-other-buffers.git" :rev :newest)
+ :custom
+ ;; Dim only non-selected windows within Emacs, not the whole frame when
+ ;; Emacs loses focus -- on Hyprland focus moves to other apps constantly,
+ ;; and the ai-vterm agents live in their own windows.
+ (auto-dim-other-buffers-dim-on-focus-out nil)
+ (auto-dim-other-buffers-dim-on-switch-to-minibuffer t)
+ :config
+ ;; Remap these faces to auto-dim-other-buffers (pure-black background +
+ ;; faded gray foreground, defined in the theme) in non-selected windows.
+ ;; The font-lock faces are included so code text fades to "disabled"
+ ;; rather than staying lit -- remapping default alone would leave
+ ;; syntax-highlighted text at full colour. Fringe is left out because
+ ;; dimming it forces a full-frame refresh that flickers on this non-pgtk
+ ;; build; org-hide uses the -hide face so hidden text stays hidden.
+ (setq auto-dim-other-buffers-affected-faces
+ '((default . (auto-dim-other-buffers . nil))
+ (org-block . (auto-dim-other-buffers . nil))
+ (org-hide . (auto-dim-other-buffers-hide . nil))
+ (font-lock-keyword-face . (auto-dim-other-buffers . nil))
+ (font-lock-string-face . (auto-dim-other-buffers . nil))
+ (font-lock-comment-face . (auto-dim-other-buffers . nil))
+ (font-lock-comment-delimiter-face . (auto-dim-other-buffers . nil))
+ (font-lock-doc-face . (auto-dim-other-buffers . nil))
+ (font-lock-function-name-face . (auto-dim-other-buffers . nil))
+ (font-lock-variable-name-face . (auto-dim-other-buffers . nil))
+ (font-lock-type-face . (auto-dim-other-buffers . nil))
+ (font-lock-constant-face . (auto-dim-other-buffers . nil))
+ (font-lock-builtin-face . (auto-dim-other-buffers . nil))
+ (font-lock-preprocessor-face . (auto-dim-other-buffers . nil))
+ (font-lock-warning-face . (auto-dim-other-buffers . nil))))
+ (auto-dim-other-buffers-mode 1))
+
+(provide 'auto-dim-config)
+;;; auto-dim-config.el ends here
diff --git a/tests/test-auto-dim-config.el b/tests/test-auto-dim-config.el
new file mode 100644
index 00000000..45e1db5f
--- /dev/null
+++ b/tests/test-auto-dim-config.el
@@ -0,0 +1,33 @@
+;;; test-auto-dim-config.el --- Tests for the auto-dim-other-buffers config -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; auto-dim-config configures the local auto-dim-other-buffers fork: dim only
+;; non-selected windows within Emacs (not the whole frame on focus-out), drop
+;; fringe from the dimmed faces to avoid flicker on this non-pgtk build, and
+;; enable the global mode. Guarded with `skip-unless' because the fork lives
+;; in ~/code and may be absent on a clean checkout.
+
+;;; Code:
+
+(require 'ert)
+(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
+
+(defconst test-auto-dim--fork
+ (expand-file-name "~/code/auto-dim-other-buffers.el")
+ "Local fork directory the module loads via `:load-path'.")
+
+(ert-deftest test-auto-dim-config-applies-settings ()
+ "Normal: loading the module enables the mode with the chosen settings."
+ (skip-unless (file-directory-p test-auto-dim--fork))
+ (require 'auto-dim-config)
+ (unwind-protect
+ (progn
+ (should (bound-and-true-p auto-dim-other-buffers-mode))
+ (should (null auto-dim-other-buffers-dim-on-focus-out))
+ (should (eq t auto-dim-other-buffers-dim-on-switch-to-minibuffer))
+ (should-not (assq 'fringe auto-dim-other-buffers-affected-faces)))
+ (when (fboundp 'auto-dim-other-buffers-mode)
+ (auto-dim-other-buffers-mode -1))))
+
+(provide 'test-auto-dim-config)
+;;; test-auto-dim-config.el ends here
diff --git a/tests/test-init-module-headers.el b/tests/test-init-module-headers.el
index af4f9ec9..ef5a7132 100644
--- a/tests/test-init-module-headers.el
+++ b/tests/test-init-module-headers.el
@@ -48,6 +48,7 @@
"text-config"
"undead-buffers"
;; Batch 4 — UI / core-UX modules (Layer 2)
+ "auto-dim-config"
"ui-config"
"ui-theme"
"ui-navigation"
diff --git a/themes/dupre-faces.el b/themes/dupre-faces.el
index fdd61a55..648fded3 100644
--- a/themes/dupre-faces.el
+++ b/themes/dupre-faces.el
@@ -832,6 +832,16 @@
`(shr-code ((t (:foreground ,green :background ,bg+1))))
`(shr-mark ((t (:background ,yellow-2))))
+;;;;; auto-dim-other-buffers
+ ;; Non-selected windows recede to a pure-black background with faded
+ ;; gray text, so an inactive window reads as "disabled". This face is
+ ;; remapped onto default, the font-lock faces, and org-block (see
+ ;; auto-dim-config.el), so code text fades too rather than staying lit.
+ ;; The -hide face keeps org hidden text invisible in dimmed windows (its
+ ;; foreground must match the dimmed background).
+ `(auto-dim-other-buffers ((t (:foreground ,gray-1 :background "#000000"))))
+ `(auto-dim-other-buffers-hide ((t (:foreground "#000000" :background "#000000"))))
+
)))
(provide 'dupre-faces)