diff options
Diffstat (limited to 'users/tazjin/emacs/config/desktop.el')
-rw-r--r-- | users/tazjin/emacs/config/desktop.el | 347 |
1 files changed, 154 insertions, 193 deletions
diff --git a/users/tazjin/emacs/config/desktop.el b/users/tazjin/emacs/config/desktop.el index 0ee53276a1..aa232fec2f 100644 --- a/users/tazjin/emacs/config/desktop.el +++ b/users/tazjin/emacs/config/desktop.el @@ -4,14 +4,15 @@ ;; window-management (EXWM) as well as additional system-wide ;; commands. -(require 'dash) (require 'exwm) (require 'exwm-config) (require 'exwm-randr) (require 'exwm-systemtray) (require 'exwm-xim ) (require 'f) +(require 'ring) (require 's) +(require 'seq) (defcustom tazjin--screen-lock-command "tazjin-screen-lock" "Command to execute for locking the screen." @@ -69,36 +70,17 @@ human-accessible titles." (pcase (list (or exwm-class-name "unknown") (or exwm-title "unknown")) - ;; In Cider windows, rename the class and keep the workspace/file - ;; as the title. - (`("Google-chrome" ,(and (pred (lambda (title) (s-ends-with? " - Cider" title))) title)) - (format "Cider<%s>" (s-chop-suffix " - Cider" title))) - (`("Google-chrome" ,(and (pred (lambda (title) (s-ends-with? " - Cider V" title))) title)) - (format "Cider V<%s>" (s-chop-suffix " - Cider V" title))) - - ;; Attempt to detect IRCCloud windows via their title, which is a - ;; combination of the channel name and network. - ;; - ;; This is what would often be referred to as a "hack". The regexp - ;; will not work if a network connection buffer is selected in - ;; IRCCloud, but since the title contains no other indication that - ;; we're dealing with an IRCCloud window - (`("Google-chrome" - ,(and (pred (lambda (title) - (s-matches? "^[\*\+]\s#[a-zA-Z0-9/\-]+\s\|\s[a-zA-Z\.]+$" title))) - title)) - (format "IRCCloud<%s>" title)) - - ;; For other Chrome windows, make the title shorter. - (`("Google-chrome" ,title) - (format "Chrome<%s>" (s-truncate 42 (s-chop-suffix " - Google Chrome" title)))) - - ;; Gnome-terminal -> Term - (`("Gnome-terminal" ,title) - ;; fish-shell buffers contain some unnecessary whitespace and - ;; such before the current working directory. This can be - ;; stripped since most of my terminals are fish shells anyways. - (format "Term<%s>" (s-trim-left (s-chop-prefix "fish" title)))) + ;; Yandex.Music -> `Я.Music<... stuff ...>' + (`("Chromium-browser" ,(and (pred (lambda (title) (s-starts-with? "Yandex.Music - " title))) title)) + (format "Я.Music<%s>" (s-chop-prefix "Yandex.Music - " title))) + + ;; For other Chromium windows, make the title shorter. + (`("Chromium-browser" ,title) + (format "Chromium<%s>" (s-truncate 42 (s-chop-suffix " - Chromium" title)))) + + ;; similarly for Firefox + (`("firefox" ,title) + (format "FF<%s>" title)) ;; Quassel buffers ;; @@ -120,9 +102,6 @@ (`(,class ,title) (format "%s<%s>" class (s-truncate 12 title))))) ;; EXWM launch configuration -;; -;; This used to use use-package, but when something breaks use-package -;; it doesn't exactly make debugging any easier. (let ((titlef (lambda () (exwm-workspace-rename-buffer (create-window-name))))) @@ -130,117 +109,57 @@ (add-hook 'exwm-update-title-hook titlef)) (fringe-mode 3) -(exwm-enable) - -;; Create 10 EXWM workspaces -(setq exwm-workspace-number 10) - -;; 's-N': Switch to certain workspace, but switch back to the previous -;; one when tapping twice (emulates i3's `back_and_forth' feature) -(defvar *exwm-workspace-from-to* '(-1 . -1)) -(defun exwm-workspace-switch-back-and-forth (target-idx) - ;; If the current workspace is the one we last jumped to, and we are - ;; asked to jump to it again, set the target back to the previous - ;; one. - (when (and (eq exwm-workspace-current-index (cdr *exwm-workspace-from-to*)) - (eq target-idx exwm-workspace-current-index)) - (setq target-idx (car *exwm-workspace-from-to*))) - - (setq *exwm-workspace-from-to* - (cons exwm-workspace-current-index target-idx)) - - (exwm-workspace-switch-create target-idx)) -(dotimes (i 10) - (exwm-input-set-key (kbd (format "s-%d" i)) - `(lambda () - (interactive) - (exwm-workspace-switch-back-and-forth ,i)))) - -;; Implement MRU functionality for EXWM workspaces, making it possible -;; to jump to the previous/next workspace very easily. -(defvar *recent-workspaces* nil - "List of the most recently used EXWM workspaces.") - -(defvar *workspace-jumping-to* nil - "What offset in the workspace history are we jumping to?") - -(defvar *workspace-history-position* 0 - "Where in the workspace history are we right now?") - -(defun update-recent-workspaces () - "Hook to run on every workspace switch which will prepend the new -workspace to the MRU list, unless we are already on that -workspace. Does not affect the MRU list if a jump is -in-progress." +;; tab-bar related config +(setq tab-bar-show 1) +(setq tab-bar-tab-hints t) - (if *workspace-jumping-to* - (setq *workspace-history-position* *workspace-jumping-to* - *workspace-jumping-to* nil) +(setq tab-bar-format + '(tab-bar-format-history + tab-bar-format-tabs tab-bar-separator + tab-bar-format-align-right tab-bar-format-global)) - ;; reset the history position to the front on a normal jump - (setq *workspace-history-position* 0) - - (unless (eq exwm-workspace-current-index (car *recent-workspaces*)) - (setq *recent-workspaces* (cons exwm-workspace-current-index - (-take 9 *recent-workspaces*)))))) - -(add-to-list 'exwm-workspace-switch-hook #'update-recent-workspaces) - -(defun switch-to-previous-workspace () - "Switch to the previous workspace in the MRU workspace list." - (interactive) +(setq tab-bar-new-tab-choice + (lambda () (get-buffer-create "*scratch*"))) - (let* (;; the previous workspace is one position further down in the - ;; workspace history - (position (+ *workspace-history-position* 1)) - (target-idx (elt *recent-workspaces* position))) - (if (not target-idx) - (message "No previous workspace in history!") +(tab-bar-mode 1) - (setq *workspace-jumping-to* position) - (exwm-workspace-switch target-idx)))) +(setq x-no-window-manager t) ;; TODO(tazjin): figure out when to remove this +(exwm-enable) +(exwm-randr-enable) -(exwm-input-set-key (kbd "s-b") #'switch-to-previous-workspace) +;; Tab-management shortcuts -(defun switch-to-next-workspace () - "Switch to the next workspace in the MRU workspace list." +(defun tab-bar-select-or-return () + "This function behaves like `tab-bar-select-tab', except it calls +`tab-recent' if asked to jump to the current tab. This simulates +the back&forth behaviour of i3." (interactive) - - (if (= *workspace-history-position* 0) - (message "No next workspace in history!") - (let* (;; The next workspace is one position further up in the - ;; history. This always exists unless someone messed with - ;; it. - (position (- *workspace-history-position* 1)) - (target-idx (elt *recent-workspaces* position))) - (setq *workspace-jumping-to* position) - (exwm-workspace-switch target-idx)))) - -(exwm-input-set-key (kbd "s-f") #'switch-to-next-workspace) - -;; Provide a binding for jumping to a buffer on a workspace. -(defun exwm-jump-to-buffer () - "Jump to a workspace on which the target buffer is displayed." - (interactive) - (let ((exwm-layout-show-all-buffers nil) - (initial exwm-workspace-current-index)) - (call-interactively #'exwm-workspace-switch-to-buffer) - ;; After jumping, update the back-and-forth list like on a direct - ;; index jump. - (when (not (eq initial exwm-workspace-current-index)) - (setq *exwm-workspace-from-to* - (cons initial exwm-workspace-current-index))))) - -(exwm-input-set-key (kbd "C-c j") #'exwm-jump-to-buffer) + (let* ((key (event-basic-type last-command-event)) + (tab (if (and (characterp key) (>= key ?1) (<= key ?9)) + (- key ?0) + 0)) + (current (1+ (tab-bar--current-tab-index)))) + (if (eq tab current) + (tab-recent) + (tab-bar-select-tab tab)))) + +(dotimes (i 8) + (exwm-input-set-key (kbd (format "s-%d" (+ 1 i))) #'tab-bar-select-or-return)) + +(exwm-input-set-key (kbd "s-9") #'tab-last) +(exwm-input-set-key (kbd "s-f") #'tab-next) +(exwm-input-set-key (kbd "s-b") #'tab-recent) +(exwm-input-set-key (kbd "s-w") #'tab-close) +(exwm-input-set-key (kbd "s-n") #'tab-new) ;; Launch applications / any command with completion (dmenu style!) -(exwm-input-set-key (kbd "s-d") #'counsel-linux-app) +(exwm-input-set-key (kbd "s-d") #'run-xdg-app) (exwm-input-set-key (kbd "s-x") #'run-external-command) (exwm-input-set-key (kbd "s-p") #'password-store-lookup) -;; Add X11 terminal selector to a key -(exwm-input-set-key (kbd "C-x t") #'ts/switch-to-terminal) +;; Add vterm selector to a key +(exwm-input-set-key (kbd "s-v") #'ts/switch-to-terminal) ;; Toggle between line-mode / char-mode (exwm-input-set-key (kbd "C-c C-t C-t") #'exwm-input-toggle-keyboard) @@ -267,10 +186,6 @@ in-progress." (bind-xkb "no" "k n") (bind-xkb "ru" "k r") (bind-xkb "se" "k s") - -;; These are commented out because Emacs no longer starts (??) if -;; they're set at launch. -;; (bind-xkb "us" "л г") (bind-xkb "de" "л в") (bind-xkb "no" "л т") @@ -282,9 +197,8 @@ in-progress." (push ?\C-\\ exwm-input-prefix-keys) ;; Line-editing shortcuts -(exwm-input-set-simulation-keys - '(([?\C-d] . delete) - ([?\C-w] . ?\C-c))) +(exwm-input-set-simulation-key (kbd "C-d") (kbd "DEL")) +(exwm-input-set-simulation-key (kbd "C-w") (kbd "C-c")) ;; Show time & battery status in the mode line (display-time-mode) @@ -293,76 +207,123 @@ in-progress." ;; enable display of X11 system tray within Emacs (exwm-systemtray-enable) -;; Configure xrandr (multi-monitor setup). - -(defun set-randr-config (screens) - (setq exwm-randr-workspace-monitor-plist - (-flatten (-map (lambda (screen) - (-map (lambda (screen-id) (list screen-id (car screen))) (cdr screen))) - screens)))) +;; Multi-monitor configuration. +;; +;; With tab-bar-mode, each monitor only displays at most one +;; workspace. Workspaces are only created, never deleted, meaning that +;; the number of workspaces will be equivalent to the maximum number +;; of displays that were connected during a session. +;; +;; The first workspace is special: It is kept on the primary monitor. -;; Layouts for Tverskoy (X13 AMD laptop) -(defun randr-tverskoy-layout-single () - "Laptop screen only!" +(defun exwm-assign-workspaces () + "Assigns workspaces to the currently existing monitors, putting +the first one on the primary display and allocating the others +dynamically if needed in no particular order." (interactive) - (set-randr-config '(("eDP" (number-sequence 0 9)))) - (shell-command "xrandr --output eDP --auto --primary") - (shell-command "xrandr --output HDMI-A-0 --off") - (exwm-randr-refresh)) - -(defun randr-tverskoy-split-workspace () - "Split the workspace across two screens, assuming external to the left." + (let* ((randr-monitors (exwm-randr--get-monitors)) + (primary (car randr-monitors)) + (all-monitors (seq-map #'car (cadr randr-monitors))) + (sorted-primary-first (seq-sort (lambda (a b) + (or (equal a primary) + (< a b))) + all-monitors)) + ;; assign workspace numbers to each monitor ... + (workspace-assignments + (flatten-list (seq-map-indexed (lambda (monitor idx) + (list idx monitor)) + sorted-primary-first)))) + ;; ensure that the required workspaces exist + (exwm-workspace-switch-create (- (seq-length all-monitors) 1)) + + ;; update randr config + (setq exwm-randr-workspace-monitor-plist workspace-assignments) + (exwm-randr-refresh) + + ;; leave focus on primary workspace + (exwm-workspace-switch 0))) + +(defun list-available-monitors () + "List connected, but unused monitors." + (let* ((all-connected + (seq-map (lambda (line) (car (s-split " " line))) + (s-lines (s-trim (shell-command-to-string "xrandr | grep connected | grep -v disconnected"))))) + (all-active (seq-map #'car (cadr (exwm-randr--get-monitors))))) + (seq-filter (lambda (s) (not (seq-contains-p all-active s))) + all-connected))) + +(defun exwm-enable-monitor () + "Interactively construct an EXWM invocation that enable the +given monitor and assigns a workspace to it." (interactive) - (set-randr-config - '(("HDMI-A-0" 1 2 3 4 5 6 7 8 9) - ("eDP" 0))) - (shell-command "xrandr --output HDMI-A-0 --left-of eDP --auto") - (exwm-randr-refresh)) - -(defun randr-tverskoy-tv () - "Split off a workspace to the TV over HDMI." + (let* ((monitors (list-available-monitors)) + (primary (car (exwm-randr--get-monitors))) + (monitor (pcase (seq-length monitors) + (0 (error "No available monitors.")) + (1 (car monitors)) + (_ + (completing-read "Which monitor? " (list-available-monitors) nil t)))) + + (configurations `(("secondary (left)" . ,(format "--left-of %s" primary)) + ("secondary (right)" . ,(format "--right-of %s" primary)) + ("primary (left)" . ,(format "--left-of %s --primary" primary)) + ("primary (right)" . ,(format "--right-of %s --primary" primary)) + ("mirror" . ,(format "--same-as %s" primary)))) + + (where (completing-read (format "%s should be " monitor) + (seq-map #'car configurations) + nil t)) + (xrandr-pos (cdr (assoc where configurations))) + (xrandr-cmd (format "xrandr --output %s --auto %s" monitor xrandr-pos))) + (message "Invoking '%s'" xrandr-cmd) + (shell-command xrandr-cmd) + (exwm-assign-workspaces))) + +(defun exwm-disable-monitor () + "Interactively choose a monitor to disable." (interactive) - (set-randr-config - '(("eDP" 1 2 3 4 5 6 7 8 9) - ("HDMI-A-0" 0))) - (shell-command "xrandr --output HDMI-A-0 --left-of eDP --mode 1920x1080") - (exwm-randr-refresh)) + (let* ((all (exwm-randr--get-monitors)) + (active (seq-map #'car (cadr all))) + (monitor (if (> (seq-length active) 1) + (completing-read "Disable which monitor? " active nil t) + (error "Only one monitor is active!"))) -;; Layouts for frog (desktop) + ;; If this monitor was primary, pick another active one instead. + (remaining (seq-filter (lambda (s) (not (equal s monitor))) active)) + (new-primary + (when (equal monitor (car all)) + (pcase (seq-length remaining) + (1 (car remaining)) + (_ (completing-read "New primary? " remaining nil t)))))) -(defun randr-frog-layout-right-only () - "Use only the right screen on frog." - (interactive) - (set-randr-config `(("DisplayPort-0" ,(number-sequence 0 9)))) - (shell-command "xrandr --output DisplayPort-0 --off") - (shell-command "xrandr --output DisplayPort-1 --auto --primary")) + (when new-primary + (shell-command (format "xrandr --output %s --primary" new-primary))) -(defun randr-frog-layout-both () - "Use the left and right screen on frog." - (interactive) - (set-randr-config `(("DisplayPort-0" 1 2 3 4 5) - ("DisplayPort-1" 6 7 8 9 0))) + (shell-command (format "xrandr --output %s --off" monitor)) + (exwm-assign-workspaces))) - (shell-command "xrandr --output DisplayPort-0 --auto --primary --left-of DisplayPort-1") - (shell-command "xrandr --output DisplayPort-1 --auto --right-of DisplayPort-0 --rotate left")) +(defun exwm-switch-monitor () + "Switch focus to another monitor by name." + (interactive) -(pcase (s-trim (shell-command-to-string "hostname")) - ("tverskoy" - (exwm-input-set-key (kbd "s-m s") #'randr-tverskoy-layout-single) - (exwm-input-set-key (kbd "s-m 2") #'randr-tverskoy-split-workspace)) + ;; TODO: Filter out currently active? How to determine it? + (let* ((target (completing-read "Switch to monitor: " + (seq-map #'car (cadr (exwm-randr--get-monitors))) + nil t)) + (target-workspace + (cl-loop for (workspace screen) on exwm-randr-workspace-monitor-plist by #'cddr + when (equal screen target) return workspace))) + (exwm-workspace-switch target-workspace))) - ("frog" - (exwm-input-set-key (kbd "s-m b") #'randr-frog-layout-both) - (exwm-input-set-key (kbd "s-m r") #'randr-frog-layout-right-only))) +(exwm-input-set-key (kbd "s-m e") #'exwm-enable-monitor) +(exwm-input-set-key (kbd "s-m d") #'exwm-disable-monitor) +(exwm-input-set-key (kbd "s-m o") #'exwm-switch-monitor) ;; Notmuch shortcuts as EXWM globals ;; (g m => gmail) (exwm-input-set-key (kbd "s-g m") #'notmuch) -(exwm-input-set-key (kbd "s-g M") #'counsel-notmuch) - -(exwm-randr-enable) ;; Let buffers move seamlessly between workspaces by making them ;; accessible in selectors on all frames. |