about summary refs log tree commit diff
path: root/users/tazjin/emacs/config/desktop.el
diff options
context:
space:
mode:
Diffstat (limited to 'users/tazjin/emacs/config/desktop.el')
-rw-r--r--users/tazjin/emacs/config/desktop.el347
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.