2019-12-14 14:23:20 +01:00
|
|
|
|
;; -*- lexical-binding: t; -*-
|
|
|
|
|
;;
|
|
|
|
|
;; Configure desktop environment settings, including both
|
|
|
|
|
;; window-management (EXWM) as well as additional system-wide
|
|
|
|
|
;; commands.
|
|
|
|
|
|
|
|
|
|
(require 'exwm)
|
|
|
|
|
(require 'exwm-config)
|
|
|
|
|
(require 'exwm-randr)
|
|
|
|
|
(require 'exwm-systemtray)
|
2021-12-24 20:28:22 +01:00
|
|
|
|
(require 'exwm-xim )
|
|
|
|
|
(require 'f)
|
2023-08-28 22:25:59 +02:00
|
|
|
|
(require 'ring)
|
2021-12-24 20:28:22 +01:00
|
|
|
|
(require 's)
|
2023-11-25 14:01:31 +01:00
|
|
|
|
(require 'seq)
|
2019-12-14 14:23:20 +01:00
|
|
|
|
|
2021-03-23 21:30:29 +01:00
|
|
|
|
(defcustom tazjin--screen-lock-command "tazjin-screen-lock"
|
|
|
|
|
"Command to execute for locking the screen."
|
|
|
|
|
:group 'tazjin)
|
|
|
|
|
|
|
|
|
|
(defcustom tazjin--backlight-increase-command "light -A 4"
|
|
|
|
|
"Command to increase screen brightness."
|
|
|
|
|
:group 'tazjin)
|
|
|
|
|
|
|
|
|
|
(defcustom tazjin--backlight-decrease-command "light -U 4"
|
|
|
|
|
"Command to decrease screen brightness."
|
|
|
|
|
:group 'tazjin)
|
|
|
|
|
|
2019-12-14 14:23:20 +01:00
|
|
|
|
(defun pactl (cmd)
|
|
|
|
|
(shell-command (concat "pactl " cmd))
|
|
|
|
|
(message "Volume command: %s" cmd))
|
|
|
|
|
|
|
|
|
|
(defun volume-mute () (interactive) (pactl "set-sink-mute @DEFAULT_SINK@ toggle"))
|
|
|
|
|
(defun volume-up () (interactive) (pactl "set-sink-volume @DEFAULT_SINK@ +5%"))
|
|
|
|
|
(defun volume-down () (interactive) (pactl "set-sink-volume @DEFAULT_SINK@ -5%"))
|
|
|
|
|
|
|
|
|
|
(defun brightness-up ()
|
|
|
|
|
(interactive)
|
2021-03-23 21:30:29 +01:00
|
|
|
|
(shell-command tazjin--backlight-increase-command)
|
2019-12-14 14:23:20 +01:00
|
|
|
|
(message "Brightness increased"))
|
|
|
|
|
|
|
|
|
|
(defun brightness-down ()
|
|
|
|
|
(interactive)
|
2021-03-23 21:30:29 +01:00
|
|
|
|
(shell-command tazjin--backlight-decrease-command)
|
2019-12-14 14:23:20 +01:00
|
|
|
|
(message "Brightness decreased"))
|
|
|
|
|
|
2021-01-19 15:49:16 +01:00
|
|
|
|
(defun set-xkb-layout (layout)
|
|
|
|
|
"Set the current X keyboard layout."
|
|
|
|
|
|
|
|
|
|
(shell-command (format "setxkbmap %s" layout))
|
|
|
|
|
(shell-command "setxkbmap -option caps:super")
|
|
|
|
|
(message "Set X11 keyboard layout to '%s'" layout))
|
|
|
|
|
|
2019-12-14 14:23:20 +01:00
|
|
|
|
(defun lock-screen ()
|
|
|
|
|
(interactive)
|
2021-01-19 15:49:16 +01:00
|
|
|
|
(set-xkb-layout "us")
|
2021-12-24 20:28:22 +01:00
|
|
|
|
(deactivate-input-method)
|
2021-03-23 21:30:29 +01:00
|
|
|
|
(shell-command tazjin--screen-lock-command))
|
2019-12-14 14:23:20 +01:00
|
|
|
|
|
|
|
|
|
(defun create-window-name ()
|
|
|
|
|
"Construct window names to be used for EXWM buffers by
|
|
|
|
|
inspecting the window's X11 class and title.
|
|
|
|
|
|
|
|
|
|
A lot of commonly used applications either create titles that
|
|
|
|
|
are too long by default, or in the case of web
|
|
|
|
|
applications (such as Cider) end up being constructed in
|
|
|
|
|
awkward ways.
|
|
|
|
|
|
|
|
|
|
To avoid this issue, some rewrite rules are applied for more
|
|
|
|
|
human-accessible titles."
|
|
|
|
|
|
|
|
|
|
(pcase (list (or exwm-class-name "unknown") (or exwm-title "unknown"))
|
2023-07-05 13:53:38 +02:00
|
|
|
|
;; 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))))
|
|
|
|
|
|
2024-01-13 00:21:10 +01:00
|
|
|
|
;; similarly for Firefox
|
|
|
|
|
(`("firefox" ,title)
|
|
|
|
|
(format "FF<%s>" title))
|
|
|
|
|
|
2020-09-09 12:26:21 +02:00
|
|
|
|
;; Quassel buffers
|
|
|
|
|
;;
|
|
|
|
|
;; These have a title format that looks like:
|
2021-05-23 21:27:17 +02:00
|
|
|
|
;; "Quassel IRC - #tvl (hackint) — Quassel IRC"
|
2020-09-09 12:26:21 +02:00
|
|
|
|
(`("quassel" ,title)
|
|
|
|
|
(progn
|
2020-09-14 14:12:33 +02:00
|
|
|
|
(if (string-match
|
|
|
|
|
(rx "Quassel IRC - "
|
|
|
|
|
(group (one-or-more (any alnum "[" "]" "&" "-" "#"))) ;; <-- channel name
|
|
|
|
|
" (" (group (one-or-more (any ascii space))) ")" ;; <-- network name
|
|
|
|
|
" — Quassel IRC")
|
|
|
|
|
title)
|
|
|
|
|
(format "Quassel<%s>" (match-string 2 title))
|
|
|
|
|
title)))
|
2020-09-09 12:26:21 +02:00
|
|
|
|
|
2019-12-14 14:23:20 +01:00
|
|
|
|
;; For any other application, a name is constructed from the
|
|
|
|
|
;; window's class and name.
|
|
|
|
|
(`(,class ,title) (format "%s<%s>" class (s-truncate 12 title)))))
|
|
|
|
|
|
|
|
|
|
;; EXWM launch configuration
|
|
|
|
|
|
|
|
|
|
(let ((titlef (lambda ()
|
|
|
|
|
(exwm-workspace-rename-buffer (create-window-name)))))
|
|
|
|
|
(add-hook 'exwm-update-class-hook titlef)
|
|
|
|
|
(add-hook 'exwm-update-title-hook titlef))
|
|
|
|
|
|
|
|
|
|
(fringe-mode 3)
|
2023-11-25 00:29:24 +01:00
|
|
|
|
|
|
|
|
|
;; tab-bar related config
|
|
|
|
|
(setq tab-bar-show 1)
|
|
|
|
|
(setq tab-bar-tab-hints t)
|
2023-11-28 13:30:18 +01:00
|
|
|
|
|
|
|
|
|
(setq tab-bar-format
|
|
|
|
|
'(tab-bar-format-history
|
|
|
|
|
tab-bar-format-tabs tab-bar-separator
|
|
|
|
|
tab-bar-format-align-right tab-bar-format-global))
|
|
|
|
|
|
2023-11-25 00:29:24 +01:00
|
|
|
|
(setq tab-bar-new-tab-choice
|
|
|
|
|
(lambda () (get-buffer-create "*scratch*")))
|
|
|
|
|
|
|
|
|
|
(tab-bar-mode 1)
|
|
|
|
|
|
2023-12-29 23:45:48 +01:00
|
|
|
|
(setq x-no-window-manager t) ;; TODO(tazjin): figure out when to remove this
|
2019-12-14 14:23:20 +01:00
|
|
|
|
(exwm-enable)
|
2023-11-25 14:01:31 +01:00
|
|
|
|
(exwm-randr-enable)
|
2019-12-14 14:23:20 +01:00
|
|
|
|
|
2023-11-25 00:29:24 +01:00
|
|
|
|
;; Tab-management shortcuts
|
|
|
|
|
|
2023-11-26 19:38:07 +01:00
|
|
|
|
(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)
|
|
|
|
|
(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))))
|
|
|
|
|
|
2023-11-25 00:29:24 +01:00
|
|
|
|
(dotimes (i 8)
|
2023-11-26 19:38:07 +01:00
|
|
|
|
(exwm-input-set-key (kbd (format "s-%d" (+ 1 i))) #'tab-bar-select-or-return))
|
2023-11-25 00:29:24 +01:00
|
|
|
|
|
|
|
|
|
(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)
|
|
|
|
|
|
2020-12-09 14:57:25 +01:00
|
|
|
|
;; Launch applications / any command with completion (dmenu style!)
|
2023-08-27 01:22:20 +02:00
|
|
|
|
(exwm-input-set-key (kbd "s-d") #'run-xdg-app)
|
2020-12-09 14:57:25 +01:00
|
|
|
|
(exwm-input-set-key (kbd "s-x") #'run-external-command)
|
|
|
|
|
(exwm-input-set-key (kbd "s-p") #'password-store-lookup)
|
2019-12-14 14:23:20 +01:00
|
|
|
|
|
2023-11-25 00:29:24 +01:00
|
|
|
|
;; Add vterm selector to a key
|
|
|
|
|
(exwm-input-set-key (kbd "s-v") #'ts/switch-to-terminal)
|
2019-12-14 14:23:20 +01:00
|
|
|
|
|
|
|
|
|
;; Toggle between line-mode / char-mode
|
|
|
|
|
(exwm-input-set-key (kbd "C-c C-t C-t") #'exwm-input-toggle-keyboard)
|
|
|
|
|
|
|
|
|
|
;; Volume keys
|
|
|
|
|
(exwm-input-set-key (kbd "<XF86AudioMute>") #'volume-mute)
|
|
|
|
|
(exwm-input-set-key (kbd "<XF86AudioRaiseVolume>") #'volume-up)
|
|
|
|
|
(exwm-input-set-key (kbd "<XF86AudioLowerVolume>") #'volume-down)
|
|
|
|
|
|
|
|
|
|
;; Brightness keys
|
|
|
|
|
(exwm-input-set-key (kbd "<XF86MonBrightnessDown>") #'brightness-down)
|
|
|
|
|
(exwm-input-set-key (kbd "<XF86MonBrightnessUp>") #'brightness-up)
|
|
|
|
|
(exwm-input-set-key (kbd "<XF86Display>") #'lock-screen)
|
|
|
|
|
|
2020-05-16 04:54:44 +02:00
|
|
|
|
;; Shortcuts for switching between keyboard layouts
|
|
|
|
|
(defmacro bind-xkb (lang key)
|
|
|
|
|
`(exwm-input-set-key (kbd (format "s-%s" ,key))
|
|
|
|
|
(lambda ()
|
|
|
|
|
(interactive)
|
|
|
|
|
(set-xkb-layout ,lang))))
|
|
|
|
|
|
|
|
|
|
(bind-xkb "us" "k u")
|
|
|
|
|
(bind-xkb "de" "k d")
|
|
|
|
|
(bind-xkb "no" "k n")
|
|
|
|
|
(bind-xkb "ru" "k r")
|
2020-09-24 13:15:11 +02:00
|
|
|
|
(bind-xkb "se" "k s")
|
2021-06-23 20:14:35 +02:00
|
|
|
|
(bind-xkb "us" "л г")
|
|
|
|
|
(bind-xkb "de" "л в")
|
|
|
|
|
(bind-xkb "no" "л т")
|
|
|
|
|
(bind-xkb "ru" "л к")
|
2019-12-14 14:23:20 +01:00
|
|
|
|
|
2021-12-24 20:28:22 +01:00
|
|
|
|
;; Configuration of EXWM input method handling for X applications
|
|
|
|
|
(exwm-xim-enable)
|
|
|
|
|
(setq default-input-method "russian-computer")
|
|
|
|
|
(push ?\C-\\ exwm-input-prefix-keys)
|
|
|
|
|
|
2019-12-14 14:23:20 +01:00
|
|
|
|
;; Line-editing shortcuts
|
2024-02-18 08:06:01 +01:00
|
|
|
|
(exwm-input-set-simulation-key (kbd "C-d") (kbd "DEL"))
|
|
|
|
|
(exwm-input-set-simulation-key (kbd "C-w") (kbd "C-c"))
|
2019-12-14 14:23:20 +01:00
|
|
|
|
|
|
|
|
|
;; Show time & battery status in the mode line
|
|
|
|
|
(display-time-mode)
|
|
|
|
|
(display-battery-mode)
|
|
|
|
|
|
|
|
|
|
;; enable display of X11 system tray within Emacs
|
|
|
|
|
(exwm-systemtray-enable)
|
|
|
|
|
|
2023-11-25 14:01:31 +01:00
|
|
|
|
;; 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.
|
|
|
|
|
|
|
|
|
|
(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)
|
|
|
|
|
(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)))
|
2020-04-17 14:12:26 +02:00
|
|
|
|
|
2023-11-25 15:39:16 +01:00
|
|
|
|
(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)
|
|
|
|
|
|
|
|
|
|
(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)
|
|
|
|
|
|
2023-11-25 15:47:04 +01:00
|
|
|
|
(let* ((all (exwm-randr--get-monitors))
|
|
|
|
|
(active (seq-map #'car (cadr all)))
|
2023-11-25 15:39:16 +01:00
|
|
|
|
(monitor (if (> (seq-length active) 1)
|
|
|
|
|
(completing-read "Disable which monitor? " active nil t)
|
|
|
|
|
(error "Only one monitor is active!")))
|
2023-11-25 15:47:04 +01:00
|
|
|
|
|
|
|
|
|
;; 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))))))
|
|
|
|
|
|
|
|
|
|
(when new-primary
|
|
|
|
|
(shell-command (format "xrandr --output %s --primary" new-primary)))
|
|
|
|
|
|
|
|
|
|
(shell-command (format "xrandr --output %s --off" monitor))
|
2023-11-25 15:39:16 +01:00
|
|
|
|
(exwm-assign-workspaces)))
|
|
|
|
|
|
2023-11-25 16:15:11 +01:00
|
|
|
|
(defun exwm-switch-monitor ()
|
|
|
|
|
"Switch focus to another monitor by name."
|
|
|
|
|
(interactive)
|
|
|
|
|
|
|
|
|
|
;; 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)))
|
|
|
|
|
|
2023-11-25 15:39:16 +01:00
|
|
|
|
(exwm-input-set-key (kbd "s-m e") #'exwm-enable-monitor)
|
|
|
|
|
(exwm-input-set-key (kbd "s-m d") #'exwm-disable-monitor)
|
2023-11-25 16:15:11 +01:00
|
|
|
|
(exwm-input-set-key (kbd "s-m o") #'exwm-switch-monitor)
|
2023-11-25 15:39:16 +01:00
|
|
|
|
|
2020-12-20 17:26:10 +01:00
|
|
|
|
;; Notmuch shortcuts as EXWM globals
|
|
|
|
|
;; (g m => gmail)
|
|
|
|
|
(exwm-input-set-key (kbd "s-g m") #'notmuch)
|
|
|
|
|
|
2019-12-14 14:23:20 +01:00
|
|
|
|
;; Let buffers move seamlessly between workspaces by making them
|
|
|
|
|
;; accessible in selectors on all frames.
|
|
|
|
|
(setq exwm-workspace-show-all-buffers t)
|
|
|
|
|
(setq exwm-layout-show-all-buffers t)
|
|
|
|
|
|
|
|
|
|
(provide 'desktop)
|