#+TITLE: Desktop #+TODO: TODO(t) CHECK(s) | OFF(o) #+PROPERTY: header-args :mkdirp yes #+PROPERTY: header-args:emacs-lisp :eval never-export #+PROPERTY: header-args:conf-space :comments link #+PROPERTY: header-args:conf-unix :comments link #+PROPERTY: header-args:conf-windows :comments link #+PROPERTY: header-args:conf-xdefaults :comments link #+PROPERTY: header-args:sh :tangle-mode (identity #o755) :comments link :shebang "#!/usr/bin/env bash" #+PROPERTY: header-args:bash :tangle-mode (identity #o755) :comments link :shebang "#!/usr/bin/env bash" #+OPTIONS: broken-links:auto h:6 toc:nil My general desktop environment configuration. Parts prefixed with (OFF) are not used, but kept for historic purposes. For some reason GitHub's org renderer ignores TODO status, hence such a prefix. Round brackets instead of square ones to prevent GitHub's org renderer from screwing up. #+TOC: headlines 6 * Contents :noexport: :PROPERTIES: :TOC: :include all :depth 3 :END: :CONTENTS: - [[#global-customization][Global customization]] - [[#colors][Colors]] - [[#xresources][Xresources]] - [[#colors-in-xresources][Colors in Xresources]] - [[#fonts][Fonts]] - [[#themes][Themes]] - [[#device-specific-settings][Device-specific settings]] - [[#exwm][EXWM]] - [[#xsession][Xsession]] - [[#startup-apps][Startup apps]] - [[#moving-windows][Moving windows]] - [[#resizing-windows][Resizing windows]] - [[#app-shortcuts][App shortcuts]] - [[#move-workspace-to-another-monitor][Move workspace to another monitor]] - [[#switch-to-the-opposite-monitor][Switch to the opposite monitor]] - [[#switching-buffers][Switching buffers]] - [[#keybindings][Keybindings]] - [[#pinentry][Pinentry]] - [[#modeline][Modeline]] - [[#exwm-config][EXWM config]] - [[#i3wm][i3wm]] - [[#general-settings][General settings]] - [[#managing-windows][Managing windows]] - [[#workspaces][Workspaces]] - [[#rules][Rules]] - [[#scratchpad][Scratchpad]] - [[#launch-script][Launch script]] - [[#i3-config][i3 config]] - [[#gaps--borders][Gaps & borders]] - [[#keybindings][Keybindings]] - [[#move--resize-windows][Move & resize windows]] - [[#off-intergration-with-dmenu][(OFF) Intergration with dmenu]] - [[#integration-with-rofi][Integration with rofi]] - [[#launching-apps--misc-keybindings][Launching apps & misc keybindings]] - [[#apps][Apps]] - [[#media-controls--brightness][Media controls & brightness]] - [[#screenshots][Screenshots]] - [[#colors][Colors]] - [[#off-i3blocks][(OFF) i3blocks]] - [[#keyboard-layout][Keyboard Layout]] - [[#autostart][Autostart]] - [[#polybar][Polybar]] - [[#launching][Launching]] - [[#general-settings][General settings]] - [[#colors][Colors]] - [[#bar-config][Bar config]] - [[#modules][Modules]] - [[#ipstack-vpn][ipstack-vpn]] - [[#weather][weather]] - [[#aw-afk][aw-afk]] - [[#pomm][pomm]] - [[#sun][sun]] - [[#sep][SEP]] - [[#tsep][TSEP]] - [[#i3][i3]] - [[#xkeyboard][xkeyboard]] - [[#mpd][mpd]] - [[#pulseaudio][pulseaudio]] - [[#cpu][cpu]] - [[#ram-memory][ram-memory]] - [[#swap-memory][swap-memory]] - [[#network][network]] - [[#date][date]] - [[#battery][battery]] - [[#rofi][Rofi]] - [[#theme][Theme]] - [[#scripts][Scripts]] - [[#buku-bookmarks][Buku bookmarks]] - [[#man-pages][Man pages]] - [[#emojis][Emojis]] - [[#pass][pass]] - [[#flameshot][Flameshot]] - [[#dunst][dunst]] - [[#keynav][keynav]] - [[#config][Config]] - [[#using-with-picom][Using with picom]] - [[#picom][Picom]] - [[#shadows][Shadows]] - [[#fading][Fading]] - [[#opacity][Opacity]] - [[#general-settings][General settings]] - [[#zathura][Zathura]] - [[#various-software][Various software]] - [[#browsers][Browsers]] - [[#office--multimedia][Office & Multimedia]] - [[#latex][LaTeX]] - [[#dev][Dev]] - [[#manifests][Manifests]] - [[#flatpak][Flatpak]] - [[#nix][Nix]] - [[#services][Services]] - [[#music][Music]] - [[#gnu-mcron][GNU Mcron]] - [[#activitywatch][ActivityWatch]] - [[#pulseeffects][PulseEffects]] - [[#xsettingsd][xsettingsd]] - [[#nm-applet][nm-applet]] - [[#discord-rich-presence][Discord rich presence]] - [[#polkit-authentication-agent][Polkit Authentication agent]] - [[#xmodmap][Xmodmap]] - [[#vpn][VPN]] - [[#davmail][Davmail]] - [[#shepherd-config][Shepherd config]] - [[#sync][Sync]] - [[#guix-settings][Guix settings]] :END: * Global customization ** Colors Most of the colors are from the Palenight theme. Colorcodes are taken from [[https://github.com/JonathanSpeek/palenight-iterm2][this repo]]: #+tblname: colors | color | key | value | |---------------+---------+---------| | black | color0 | #292d3e | | red | color1 | #f07178 | | green | color2 | #c3e88d | | yellow | color3 | #ffcb6b | | blue | color4 | #82aaff | | magenta | color5 | #c792ea | | cyan | color6 | #89ddff | | white | color7 | #d0d0d0 | | light-black | color8 | #434758 | | light-red | color9 | #ff8b92 | | light-green | color10 | #ddffa7 | | light-yellow | color11 | #ffe585 | | light-blue | color12 | #9cc4ff | | light-magenta | color13 | #e1acff | | light-cyan | color14 | #a3f7ff | | light-white | color15 | #ffffff | The table above is the only source of truth for colors in this config. The first way to get colors of it is to use the following noweb: #+NAME: get-color #+begin_src emacs-lisp :var table=colors name="black" quote=0 (let ((color (seq-some (lambda (e) (and (string= name (car e)) (nth 2 e))) table))) (if (> quote 0) (concat "\"" color "\"") color)) #+end_src Also, run the following to disable configuration for noweb evaluations: #+begin_src emacs-lisp (setq-local org-confirm-babel-evaluate nil) #+end_src Test: #+begin_src emacs-lisp :noweb yes <> #+end_src #+RESULTS: : #f07178 ** Xresources *** Colors in Xresources However, I'd rather use the =Xresources= file wherever possible. Here is the code to generate an Xresources file from this table: #+NAME: get-xresources #+begin_src emacs-lisp :var table=colors (apply #'concat (mapcar (lambda (elem) (concat "*" (nth 1 elem) ": " (nth 2 elem) "\n")) (seq-filter (lambda (elem) (nth 1 elem)) table))) #+end_src #+begin_src conf-xdefaults :noweb yes :tangle ~/.Xresources <> *background: <> *foreground: <> #+end_src *** Fonts Also, Xresources are used to set =Xft= settings. Unfortunately, the DPI setting has to be unique for each machine, which means I cannot commit =Xresources= to the repo. #+NAME: get-dpi #+begin_src emacs-lisp (let ((hostname (system-name))) (cond ((string-equal hostname "azure") 120) ((string-equal hostname "eminence") 120) ((string-equal hostname "indigo") 120) (t 96))) #+end_src #+begin_src conf-xdefaults :noweb yes :tangle ~/.Xresources Xft.dpi: <> #+end_src ** Themes A few programs I use to customize the apperance are listed below. | Guix dependency | Description | |-----------------------+-------------------------| | matcha-theme | My preferred GTK theme | | papirus-icon-theme | My preferred Icon theme | | gnome-themes-standard | | | xsettingsd | X11 settings daemon | | gnome-themes-extra | | [[https://github.com/derat/xsettingsd][xsettingsd]] is a lightweight daemon which configures X11 applications. It is launched with shepherd in the [[*Services][Services]] section. #+begin_src conf-space :tangle ~/.config/xsettingsd/xsettingsd.conf Net/ThemeName "Matcha-dark-azul" Net/IconThemeName "Papirus-Dark" Gtk/DecorationLayout "menu:minimize,maximize,close" Gtk/FontName "Sans 10" Gtk/MonospaceFontName "JetBrainsMono Nerd Mono 12" Gtk/CursorThemeName "Adwaita" Xft/Antialias 1 Xft/Hinting 0 Xft/HintStyle "hintnone" #+end_src ** Device-specific settings | Guix dependency | Description | |-----------------+--------------------------------------------| | xrandr | X11 CLI to RandR | | xgamma | A tool to alter monitor's gamma correction | | xinput | Configure input devices | Set screen layout & other params depending on hostname #+begin_src sh :tangle ~/bin/scripts/screen-layout hostname=$(hostname) if [ "$hostname" = "indigo" ]; then xrandr --output DisplayPort-0 --off --output HDMI-A-0 --mode 1920x1080 --pos 0x0 --rotate normal --output DVI-D-0 --mode 1920x1080 --pos 1920x0 --rotate normal elif [ "$hostname" = "eminence" ]; then xgamma -gamma 1.25 fi #+end_src * EXWM :PROPERTIES: :header-args+: :tangle ~/.emacs.d/desktop.el :END: Settings for [[https://github.com/ch11ng/exwm][Emacs X Window Manager]], a tiling WM implemented in Emacs Lisp. References: - [[https://github.com/ch11ng/exwm/wiki][EXWM Wiki]] - [[https://github.com/daviwil/emacs-from-scratch/blob/master/Desktop.org][Emacs From Scratch config]] ** Startup & UI *** Xsession First things first, Emacs has to be launched as a window manager. On a more conventional system I'd create a .desktop file in some system folder that can be seen by a login manager, but in the case of Guix it's a bit more complicated, because all such folders are not meant to be changed manually. However, GDM, the login manager that seems to be the default on Guix, launches =~/.xsession= on the startup if it's present, which is just fine for my purposes. #+begin_src sh :tangle ~/.xsession # Source .profile . ~/.profile # Disable access control for the current user xhost +SI:localuser:$USER # Fix for Java applications export _JAVA_AWT_WM_NONREPARENTING=1 # Apply XResourses xrdb -merge ~/.Xresources # Turn off the system bell xset -b # Use i3lock as a screen locker xss-lock -- i3lock & # Some apps that have to be launched only once. picom & # nm-applet & dunst & copyq & # Run the Emacs startup script as a session. # exec dbus-launch --exit-with-session ~/.emacs.d/run-exwm.sh exec dbus-launch --exit-with-session emacs -mm --debug-init -l ~/.emacs.d/desktop.el #+end_src *** Startup apps Now that Emacs is launched, it is necessary to set up the EXWM-specific parts of config. I want to launch some apps from EXWM instead of the Xsession file for two purposes: - the app may need to have the entire desktop environment set up - or it may need to be restarted if Emacs is killed. As of now, these are polybar, feh and, shepherd: #+begin_src emacs-lisp (defun my/exwm-run-polybar () (call-process "~/bin/polybar.sh")) (defun my/exwm-set-wallpaper () (call-process-shell-command "feh --bg-fill ~/Pictures/wallpaper.jpg")) (defun my/exwm-run-shepherd () (when (string-empty-p (shell-command-to-string "pgrep -u pavel shepherd")) (call-process "shepherd"))) #+end_src *** Pinentry The GUI pinentry doesn't work too well with EXWM because of issues with popup windows, so we will use the Emacs one. #+begin_src emacs-lisp (use-package pinentry :straight t :after (exwm) :config (setenv "GPG_AGENT_INFO" nil) ;; use emacs pinentry (setq auth-source-debug t) (setq epg-gpg-program "gpg2") ;; not necessary (require 'epa-file) (epa-file-enable) (setq epa-pinentry-mode 'loopback) (setq epg-pinentry-mode 'loopback) (pinentry-start) (my/run-in-background "gpgconf --reload gpg-agent")) #+end_src #+begin_src conf-space :tangle ~/.gnupg/gpg-agent.conf default-cache-ttl 3600 max-cache-ttl 3600 allow-emacs-pinentry allow-loopback-pinentry #+end_src *** Modeline Show the current workspace in the modeline. #+begin_src emacs-lisp :noweb-ref exwm-mode-line-config :tangle no (defvar my/exwm-mode-line-info "") (add-to-list 'mode-line-misc-info '(:eval my/exwm-mode-line-info)) (defun my/exwm-mode-line-info-update () (setq my/exwm-mode-line-info (concat "[" (propertize (funcall exwm-workspace-index-map exwm-workspace-current-index) 'face `(foreground-color . ,(doom-color 'yellow))) "]")) (setq my/exwm-mode-line-info-no-props (funcall exwm-workspace-index-map exwm-workspace-current-index)) (force-mode-line-update)) (add-hook 'exwm-workspace-switch-hook #'my/exwm-mode-line-info-update) #+end_src ** Windows A bunch of functions related to managing windows in EXWM. *** Moving windows My functions for managing windows. I initially wrote these to mimic the i3 behavior for my Emacs + i3 integration, but I want to try to keep them for the EXWM config as well to make the transition less painful. A predicate which checks whether there is space in the given direction: #+begin_src emacs-lisp (defun my/exwm-direction-exists-p (dir) (cl-some (lambda (dir) (let ((win (windmove-find-other-window dir))) (and win (not (window-minibuffer-p win))))) (pcase dir ('width '(left right)) ('height '(up down))))) #+end_src And a function to move windows with the following behavior: - if there is space in the required direction, move the Emacs window there; - if there is no space in the required direction, but space in two orthogonal directions, move the Emacs window so that there is no more space in the orthogonal directions; #+begin_src emacs-lisp (defun my/exwm-move-window (dir) (let ((other-window (windmove-find-other-window dir)) (other-direction (my/exwm-direction-exists-p (pcase dir ('up 'width) ('down 'width) ('left 'height) ('right 'height))))) (cond ((and other-window (not (window-minibuffer-p other-window))) (window-swap-states (selected-window) other-window)) (other-direction (evil-move-window dir))))) #+end_src *** Resizing windows A hydra so resize windows. It also mimics i3's behavior somewhat. #+begin_src emacs-lisp (setq my/exwm-resize-value 5) (defun my/exwm-resize-window (dir kind &optional value) (unless value (setq value my/exwm-resize-value)) (let* ((is-exwm-floating (and (derived-mode-p 'exwm-mode) exwm--floating-frame)) (func (if is-exwm-floating (intern (concat "exwm-layout-" (pcase kind ('shrink "shrink") ('grow "enlarge")) "-window" (pcase dir ('height "") ('width "-horizontally")))) (intern (concat "evil-window" (pcase kind ('shrink "-decrease-") ('grow "-increase-")) (symbol-name dir)))))) (when is-exwm-floating (setq value (* 5 value))) (funcall func value))) (defhydra my/exwm-resize-hydra (:color pink :hint nil :foreign-keys run) " ^Resize^ _l_: Increase width _h_: Decrease width _j_: Increase height _k_: Decrease height _=_: Balance " ("h" (lambda () (interactive) (my/exwm-resize-window 'width 'shrink))) ("j" (lambda () (interactive) (my/exwm-resize-window 'height 'grow))) ("k" (lambda () (interactive) (my/exwm-resize-window 'height 'shrink))) ("l" (lambda () (interactive) (my/exwm-resize-window 'width 'grow))) ("=" balance-windows) ("q" nil "quit" :color blue)) #+end_src ** Perspectives My package that integrates perspective.el with EXWM. References: - [[https://github.com/SqrtMinusOne/perspective-exwm.el][perspective-exwm.el repo]] #+begin_src emacs-lisp (use-package perspective-exwm :straight (:host github :repo "SqrtMinusOne/perspective-exwm.el") :config (setq perspective-exwm-override-initial-name '((0 . "misc") (1 . "core") (2 . "browser") (3 . "comms") (4 . "dev"))) (general-define-key :keymaps 'perspective-map "e" #'perspective-exwm-move-to-workspace "E" #'perspective-exwm-copy-to-workspace)) #+end_src ** Workspaces *** Move workspace to another monitor A function to move the current workspace to another monitor. #+begin_src emacs-lisp (defun my/exwm-workspace-switch-monitor () (interactive) (if (plist-get exwm-randr-workspace-monitor-plist exwm-workspace-current-index) (setq exwm-randr-workspace-monitor-plist (map-delete exwm-randr-workspace-monitor-plist exwm-workspace-current-index)) (setq exwm-randr-workspace-monitor-plist (plist-put exwm-randr-workspace-monitor-plist exwm-workspace-current-index my/exwm-another-monitor))) (exwm-randr-refresh)) #+end_src *** Switch to the opposite monitor Store the information about which workspace is available on which monitor. #+begin_src emacs-lisp :noweb-ref exwm-monitor-config :tangle no (setq my/exwm-monitor-workspace '()) (defun my/exwm-get-current-monitor () (if (plist-get exwm-randr-workspace-monitor-plist exwm-workspace-current-index) 1 0)) (defun my/exwm-update-current-monitor () (setf (alist-get (my/exwm-get-current-monitor) my/exwm-monitor-workspace) exwm-workspace-current-index)) (add-hook 'exwm-workspace-switch-hook #'my/exwm-update-current-monitor) #+end_src Switch to the opposite monitor. For now, this works only for two monitors because I don't have more. #+begin_src emacs-lisp :noweb-ref exwm-monitor-config :tangle no (defun my/exwm-switch-to-other-monitor () (interactive) (let* ((current (my/exwm-get-current-monitor)) (other (seq-some (lambda (m) (and (not (= (car m) current)) (cdr m))) my/exwm-monitor-workspace)) (focus-follows-mouse nil) (mouse-autoselect-window nil)) (exwm-workspace-switch other))) #+end_src ** Apps *** App shortcuts A +transient+ hydra for shortcuts for the most frequent apps. #+begin_src emacs-lisp (use-package transient :straight t) (defun my/run-in-background (command) (let ((command-parts (split-string command "[ ]+"))) (apply #'call-process `(,(car command-parts) nil 0 nil ,@(cdr command-parts))))) (defhydra my/exwm-apps-hydra (:color blue :hint nil) " ^Apps^ _t_: Terminal (Alacritty) _b_: Browser (Firefox) _v_: VK _s_: Slack _d_: Discord " ("t" (lambda () (interactive) (my/run-in-background "alacritty"))) ("b" (lambda () (interactive) (my/run-in-background "firefox"))) ("v" (lambda () (interactive) (my/run-in-background "vk"))) ("s" (lambda () (interactive) (my/run-in-background "slack-wrapper"))) ("d" (lambda () (interactive) (my/run-in-background "flatpak run com.discordapp.Discord")))) #+end_src *** Locking up Run i3lock. #+begin_src emacs-lisp (defun my/exwm-lock () (interactive) (my/run-in-background "i3lock -f -i /home/pavel/Pictures/lock-wallpaper.png")) #+end_src *** Auto-assign apps A function to automatially assign an app to its designated workspace and perspective. #+begin_src emacs-lisp (defun my/exwm-configure-window () (interactive) (pcase exwm-class-name ((or "Firefox" "Nightly") (perspective-exwm-assign-window :workspace-index 2 :persp-name "browser")) ("Alacritty" (perspective-exwm-assign-window :persp-name "term")) ((or "VK" "Slack" "Discord" "TelegramDesktop") (perspective-exwm-assign-window :workspace-index 3 :persp-name "comms")))) (add-hook 'exwm-manage-finish-hook #'my/exwm-configure-window) #+end_src ** Keybindings Setting keybindings for EXWM. This actually has to be in the =:config= block of the =use-package= form, that is it has to be run after EXWM is loaded, so I use noweb to put this block in the correct place. First, some prefixes for keybindings that are always passed to EXWM instead of the X application in =line-mode=: #+begin_src emacs-lisp :tangle no :noweb-ref exwm-keybindings (setq exwm-input-prefix-keys `(?\C-x ?\C-w ?\M-x ?\M-u)) #+end_src Also other local keybindings, that are also available only in =line-mode=: #+begin_src emacs-lisp :tangle no :noweb-ref exwm-keybindings (defmacro my/app-command (command) `(lambda () (interactive) (my/run-in-background ,command))) (general-define-key :keymaps '(exwm-mode-map) "C-q" #'exwm-input-send-next-key "" (my/app-command "flameshot gui") " s-" #'perspective-exwm-cycle-exwm-buffers-backward " s-" #'perspective-exwm-cycle-exwm-buffers-forward "M-x" #'counsel-M-x "M-SPC" (general-key "SPC")) #+end_src Simulation keys. #+begin_src emacs-lisp :tangle no :noweb-ref exwm-keybindings (setq exwm-input-simulation-keys `((,(kbd "M-w") . ,(kbd "C-w")) (,(kbd "M-c") . ,(kbd "C-c")))) #+end_src And keybindings that are available in both =char-mode= and =line-mode=: #+begin_src emacs-lisp :tangle no :noweb-ref exwm-keybindings (setq exwm-input-global-keys `( ;; Reset to line-mode (,(kbd "s-R") . exwm-reset) ;; Switch windows (,(kbd "s-"). windmove-left) (,(kbd "s-") . windmove-right) (,(kbd "s-") . windmove-up) (,(kbd "s-") . windmove-down) (,(kbd "s-h"). windmove-left) (,(kbd "s-l") . windmove-right) (,(kbd "s-k") . windmove-up) (,(kbd "s-j") . windmove-down) ;; Moving windows (,(kbd "s-H") . (lambda () (interactive) (my/exwm-move-window 'left))) (,(kbd "s-L") . (lambda () (interactive) (my/exwm-move-window 'right))) (,(kbd "s-K") . (lambda () (interactive) (my/exwm-move-window 'up))) (,(kbd "s-J") . (lambda () (interactive) (my/exwm-move-window 'down))) ;; Fullscreen (,(kbd "s-f") . exwm-layout-toggle-fullscreen) (,(kbd "s-F") . exwm-floating-toggle-floating) ;; Quit (,(kbd "s-Q") . evil-quit) ;; Split windows (,(kbd "s-s") . evil-window-vsplit) (,(kbd "s-v") . evil-window-hsplit) ;; Switch perspectives (,(kbd "s-,") . persp-prev) (,(kbd "s-.") . persp-next) ;; Switch buffers (,(kbd "s-e") . persp-ivy-switch-buffer) (,(kbd "s-E") . my/persp-ivy-switch-buffer-other-window) ;; Resize windows (,(kbd "s-r") . my/exwm-resize-hydra/body) ;; Apps & stuff (,(kbd "s-p") . ,(my/app-command "rofi -modi drun,run -show drun")) (,(kbd "s-;") . my/exwm-apps-hydra/body) (,(kbd "s--") . ,(my/app-command "rofi-pass")) (,(kbd "s-=") . ,(my/app-command "rofimoji")) (,(kbd "s-i") . ,(my/app-command "copyq menu")) ;; Basic controls (,(kbd "") . ,(my/app-command "ponymix increase 5 --max-volume 150")) (,(kbd "") . ,(my/app-command "ponymix decrease 5 --max-volume 150")) (,(kbd "") . ,(my/app-command "light -A 5")) (,(kbd "") . ,(my/app-command "light -U 5")) (,(kbd "") . ,(my/app-command "ponymix toggle")) (,(kbd "") . ,(my/app-command "mpc toggle")) (,(kbd "") . ,(my/app-command "mpc pause")) (,(kbd "") . ,(my/app-command "flameshot gui")) ;; Switch workspace (,(kbd "s-q") . my/exwm-switch-to-other-monitor) (,(kbd "s-w") . exwm-workspace-switch) (,(kbd "s-W") . exwm-workspace-move-window) (,(kbd "s-") . my/exwm-workspace-switch-monitor) ;; Perspectives (,(kbd "s-[") . perspective-exwm-cycle-exwm-buffers-backward) (,(kbd "s-]") . perspective-exwm-cycle-exwm-buffers-forward) (,(kbd "s-") . perspective-exwm-cycle-exwm-buffers-backward) (,(kbd "s-") . perspective-exwm-cycle-exwm-buffers-forward) (,(kbd "s-`") . perspective-exwm-switch-perspective) (,(kbd "s-o") . ,(my/app-command "rofi -show window")) ;; 's-N': Switch to certain workspace with Super (Win) plus a number key (0 - 9) ,@(mapcar (lambda (i) `(,(kbd (format "s-%d" i)) . (lambda () (interactive) (exwm-workspace-switch-create ,i)))) (number-sequence 0 9)))) #+end_src A function to apply changes to =exwm-input-global-keys=. #+begin_src emacs-lisp (defun my/exwm-update-global-keys () (interactive) (setq exwm-input--global-keys nil) (dolist (i exwm-input-global-keys) (exwm-input--set-key (car i) (cdr i))) (when exwm--connection (exwm-input--update-global-prefix-keys))) #+end_src ** Fixes *** Catch and report all errors raised when invoking command hooks - *CREDIT*: Thanks David! https://github.com/daviwil/exwm/commit/7b1be884124711af0a02eac740bdb69446bc54cc #+begin_src emacs-lisp :noweb-ref exwm-fixes :tangle no (defun exwm-input--fake-last-command () "Fool some packages into thinking there is a change in the buffer." (setq last-command #'exwm-input--noop) (condition-case hook-error (progn (run-hooks 'pre-command-hook) (run-hooks 'post-command-hook)) ((error) (exwm--log "Error occurred while running command hooks: %s\n\nBacktrace:\n\n%s" hook-error (with-temp-buffer (setq-local standard-output (current-buffer)) (backtrace) (buffer-string)))))) #+end_src *** Improve floating windows behavior #+begin_src emacs-lisp (defun my/fix-exwm-floating-windows () (setq-local exwm-workspace-warp-cursor nil) (setq-local mouse-autoselect-window nil) (setq-local focus-follows-mouse nil)) (add-hook 'exwm-floating-setup-hook #'my/fix-exwm-floating-windows) #+end_src ** EXWM config And the EXWM config itself. #+begin_src emacs-lisp :noweb yes (defun my/exwm-init () (exwm-workspace-switch 1) (my/exwm-run-polybar) (my/exwm-set-wallpaper) (my/exwm-run-shepherd) ;; (with-eval-after-load 'perspective ;; (my/exwm-setup-perspectives)) ) (defun my/exwm-update-class () (exwm-workspace-rename-buffer (format "EXWM :: %s" exwm-class-name))) (use-package exwm :straight t :config (setq exwm-workspace-number 5) (add-hook 'exwm-init-hook #'my/exwm-init) (add-hook 'exwm-update-class-hook #'my/exwm-update-class) (require 'exwm-randr) (exwm-randr-enable) (start-process-shell-command "xrandr" nil "~/bin/scripts/screen-layout") (when (string= (system-name) "indigo") (setq my/exwm-another-monitor "DVI-D-0") (setq exwm-randr-workspace-monitor-plist `(2 ,my/exwm-another-monitor 3 ,my/exwm-another-monitor))) (setq exwm-workspace-warp-cursor t) (setq mouse-autoselect-window t) (setq focus-follows-mouse t) <> <> <> <> (set-frame-parameter (selected-frame) 'alpha '(90 . 90)) (add-to-list 'default-frame-alist '(alpha . (90 . 90))) (perspective-exwm-mode) (exwm-enable)) #+end_src * i3wm :PROPERTIES: :header-args+: :tangle ./.config/i3/config :END: | Guix dependency | Disabled | |-----------------+----------| | i3-gaps | | | i3lock | true | =i3lock= is disabled because the global one has to be used. [[https://i3wm.org/][i3wm]] is a manual tiling window manager, which is currently my window manager of choice. I've tried several alternatives, including [[https://xmonad.org/][xmonad]] & [[https://github.com/ch11ng/exwm][EXWM]], but i3 seems to fit my workflow best. [[https://github.com/Airblader/i3][i3-gaps]] is an i3 fork with a few features like window gaps. I like to enable inner gaps when there is at least one container in a workspace. References: - [[https://i3wm.org/docs/][i3wm docs]] - [[https://github.com/Airblader/i3/wiki][i3-gaps wiki]] ** General settings #+begin_src conf-space set $mod Mod4 font pango:monospace 10 # Use Mouse+$mod to drag floating windows to their wanted position floating_modifier $mod # Move cursor between monitors mouse_warping output # Apply XFCE Settings # exec xfsettingsd # exec xiccd # Set screen layout exec ~/bin/scripts/screen-layout # Most needed keybindigs # reload the configuration file bindsym $mod+Shift+c reload # restart i3 inplace (preserves your layout/session, can be used to upgrade i3) bindsym $mod+Shift+r restart # exit i3 (logs you out of your X session) bindsym $mod+Shift+e exec "i3-nagbar -t warning -m 'You pressed the exit shortcut. Do you really want to exit i3? This will end your X session.' -b 'Yes, exit i3' 'i3-msg exit'" #+end_src ** Managing windows | Guix dependency | |---------------------| | rust-i3-switch-tabs | Some keybindings for managing windows. =emacs-i3-integration= is a script to pass some command to Emacs to get a consistent set of keybindings in both i3 and Emacs. Check out [[file:Emacs.org::i3 integration][the section in Emacs.org]] for details. Kill focused windows #+begin_src conf-space bindsym $mod+Shift+q exec emacs-i3-integration kill #+end_src Change focus #+begin_src conf-space bindsym $mod+h exec emacs-i3-integration focus left bindsym $mod+j exec emacs-i3-integration focus down bindsym $mod+k exec emacs-i3-integration focus up bindsym $mod+l exec emacs-i3-integration focus right bindsym $mod+Left exec emacs-i3-integration focus left bindsym $mod+Down exec emacs-i3-integration focus down bindsym $mod+Up exec emacs-i3-integration focus up bindsym $mod+Right exec emacs-i3-integration focus right #+end_src Move windows around #+begin_src conf-space bindsym $mod+Shift+h exec emacs-i3-integration move left bindsym $mod+Shift+j exec emacs-i3-integration move down bindsym $mod+Shift+k exec emacs-i3-integration move up bindsym $mod+Shift+l exec emacs-i3-integration move right bindsym $mod+Shift+Left exec emacs-i3-integration move left bindsym $mod+Shift+Down exec emacs-i3-integration move down bindsym $mod+Shift+Up exec emacs-i3-integration move up bindsym $mod+Shift+Right exec emacs-i3-integration move right #+end_src Split windows #+begin_src conf-space bindsym $mod+s exec emacs-i3-integration split h bindsym $mod+v exec emacs-i3-integration split v #+end_src Switch tabs #+begin_src conf-space bindsym $mod+period exec i3-switch-tabs right bindsym $mod+comma exec i3-switch-tabs left #+end_src Enter fullscreen mode #+begin_src conf-space # enter fullscreen mode for the focused container bindsym $mod+f fullscreen toggle bindsym $mod+c fullscreen toggle global #+end_src Changing layout #+begin_src conf-space bindsym $mod+w layout stacking bindsym $mod+t layout tabbed bindsym $mod+e exec emacs-i3-integration layout toggle split #+end_src Toggle tiling/floating, switch between tiled and floating windows #+begin_src conf-space bindsym $mod+Shift+f floating toggle bindsym $mod+z focus mode_toggle #+end_src Switching outputs #+begin_src conf-space bindsym $mod+Tab move workspace to output right bindsym $mod+q focus output right #+end_src Focus parent and child container #+begin_src conf-space bindsym $mod+a focus parent bindsym $mod+Shift+A focus child #+end_src Toggle sticky #+begin_src conf-space bindsym $mod+i sticky toggle #+end_src Set windows as floating and sticky, move to the top right. #+begin_src conf-space bindsym $mod+x floating enable; sticky enable; move position 1220 0; resize set width 700 px #+end_src ** Workspaces #+begin_src conf-space set $w1 "1 🚀" set $w2 "2 🌍" set $w3 "3 💬" set $w4 "4 🛠️️" set $w7 "7 🛰️" set $w8 "8 📝" set $w9 "9 🎵" set $w10 "10 📦" bindsym $mod+1 workspace $w1 bindsym $mod+2 workspace $w2 bindsym $mod+3 workspace $w3 bindsym $mod+4 workspace $w4 bindsym $mod+5 workspace 5 bindsym $mod+6 workspace 6 bindsym $mod+7 workspace $w7 bindsym $mod+8 workspace $w8 bindsym $mod+9 workspace $w9 bindsym $mod+0 workspace $w10 # move focused container to workspace bindsym $mod+Shift+1 move container to workspace $w1 bindsym $mod+Shift+2 move container to workspace $w2 bindsym $mod+Shift+3 move container to workspace $w3 bindsym $mod+Shift+4 move container to workspace $w4 bindsym $mod+Shift+5 move container to workspace 5 bindsym $mod+Shift+6 move container to workspace 6 bindsym $mod+Shift+7 move container to workspace $w7 bindsym $mod+Shift+8 move container to workspace $w8 bindsym $mod+Shift+9 move container to workspace $w9 bindsym $mod+Shift+0 move container to workspace $w10 #+end_src ** Rules Rules to automatically assign applications to workspaces and do other stuff, like enable floating. Most apps can be distinguished by a WM class (you can get one with [[https://www.x.org/releases/X11R7.5/doc/man/man1/xprop.1.html][xprop]]), but in some cases it doesn't work, e.g. for terminal applications. In that case rules can be based on a window title, for instance. However, watch out for the following: rule such as ~for_window [title="ncmpcpp.*"] move to workspace $w9~ will move *any* window with a title starting with =ncmpcpp= to workspace =$w9=. For instance, it moves your browser when you google "ncmpcpp". #+begin_src conf-space assign [class="Emacs"] $w1 assign [class="qutebrowser"] $w2 assign [class="firefox"] $w2 assign [class="VK"] $w3 assign [class="Slack"] $w3 assign [class="discord"] $w3 assign [class="TelegramDesktop"] $w3 assign [class="Postman"] $w4 assign [class="Chromium-browse"] $w4 assign [class="chromium"] $w4 assign [class="google-chrome"] $w4 assign [title="Vue Developer Tools"] $w4 assign [class="Google Play Music Desktop Player"] $w9 assign [class="jetbrains-datagrip"] $w4 assign [class="zoom"] $w7 assign [class="skype"] $w7 assign [class="Mailspring"] $w8 assign [class="Thunderbird"] $w8 assign [class="Joplin"] $w8 assign [class="keepassxc"] $w10 for_window [title="VirtScreen"] floating enable for_window [title="ncmpcpp.*"] move to workspace $w9 for_window [title="newsboat.*"] move to workspace $w9 for_window [title=".*run_wego"] move to workspace $w9 for_window [class="cinnamon-settings*"] floating enable for_window [title="Picture-in-Picture"] sticky enable for_window [window_role="GtkFileChooserDialog"] resize set width 1000 px height 800 px for_window [window_role="GtkFileChooserDialog"] move position center #+end_src ** Scratchpad Scratch terminal, inspired by [[https://www.youtube.com/watch?v=q-l7DnDbiiU][this Luke Smith's video]]. *** Launch script First of all, we have to distinguish a scratchpad terminal from a normal one. To do that, one can create st with a required classname. Then, it would be cool not to duplicate scratchpads, so the following script first looks for a window with a created classname. If it exists, the script just toggles the scratchpad visibility. Otherwise, a new instance of a window is created. #+begin_src bash :tangle ./bin/scripts/dropdown CLASSNAME="dropdown_tmux" COMMAND="alacritty --class $CLASSNAME -e tmux new-session -s $CLASSNAME" pid=$(xdotool search --classname "dropdown_tmux") if [[ ! -z $pid ]]; then i3-msg scratchpad show else setsid -f ${COMMAND} fi #+end_src *** i3 config #+begin_src conf-space # Scratchpad for_window [instance="dropdown_*"] floating enable for_window [instance="dropdown_*"] move scratchpad for_window [instance="dropdown_*"] sticky enable for_window [instance="dropdown_*"] scratchpad show for_window [instance="dropdown_*"] move position center bindsym $mod+u exec ~/bin/scripts/dropdown #+end_src ** Gaps & borders The main reason to use i3-gaps #+begin_src conf-space # Borders # for_window [class=".*"] border pixel 0 default_border pixel 3 hide_edge_borders both # Gaps set $default_inner 10 set $default_outer 0 gaps inner $default_inner gaps outer $default_outer smart_gaps on #+end_src *** Keybindings #+begin_src conf-space mode "inner gaps" { bindsym plus gaps inner current plus 5 bindsym minus gaps inner current minus 5 bindsym Shift+plus gaps inner all plus 5 bindsym Shift+minus gaps inner all minus 5 bindsym 0 gaps inner current set 0 bindsym Shift+0 gaps inner all set 0 bindsym r gaps inner current set $default_inner bindsym Shift+r gaps inner all set $default_inner bindsym Return mode "default" bindsym Escape mode "default" } mode "outer gaps" { bindsym plus gaps outer current plus 5 bindsym minus gaps outer current minus 5 bindsym Shift+plus gaps outer all plus 5 bindsym Shift+minus gaps outer all minus 5 bindsym 0 gaps outer current set 0 bindsym Shift+0 gaps outer all set 0 bindsym r gaps outer current set $default_outer bindsym Shift+r gaps outer all set $default_outer bindsym Return mode "default" bindsym Escape mode "default" } bindsym $mod+g mode "inner gaps" bindsym $mod+Shift+g mode "outer gaps" #+end_src ** Move & resize windows | Guix dependency | |-----------------------------| | python-i3-balance-workspace | A more or less standard set of keybindings to move & resize floating windows. Just be careful to always make a way to return from these new modes, otherwise you'd end up in a rather precarious situation. [[https://github.com/atreyasha/i3-balance-workspace][i3-balance-workspace]] is a small Python package to balance the i3 windows, but for the Emacs integration I also want this button to balance the Emacs windows, so here is a small script to do just that. #+begin_src bash :tangle ~/bin/scripts/i3-emacs-balance-windows if [[ $(xdotool getactivewindow getwindowname) =~ ^emacs(:.*)?@.* ]]; then emacsclient -e "(balance-windows)" & fi i3_balance_workspace #+end_src #+begin_src conf-space mode "resize" { bindsym h exec emacs-i3-integration resize shrink width 10 px or 10 ppt bindsym j exec emacs-i3-integration resize grow height 10 px or 10 ppt bindsym k exec emacs-i3-integration resize shrink height 10 px or 10 ppt bindsym l exec emacs-i3-integration resize grow width 10 px or 10 ppt bindsym Shift+h exec emacs-i3-integration resize shrink width 100 px or 100 ppt bindsym Shift+j exec emacs-i3-integration resize grow height 100 px or 100 ppt bindsym Shift+k exec emacs-i3-integration resize shrink height 100 px or 100 ppt bindsym Shift+l exec emacs-i3-integration resize grow width 100 px or 100 ppt # same bindings, but for the arrow keys bindsym Left exec emacs-i3-integration resize shrink width 10 px or 10 ppt bindsym Down exec emacs-i3-integration resize grow height 10 px or 10 ppt bindsym Up exec emacs-i3-integration resize shrink height 10 px or 10 ppt bindsym Right exec emacs-i3-integration resize grow width 10 px or 10 ppt bindsym Shift+Left exec emacs-i3-integration resize shrink width 100 px or 100 ppt bindsym Shift+Down exec emacs-i3-integration resize grow height 100 px or 100 ppt bindsym Shift+Up exec emacs-i3-integration resize shrink height 100 px or 100 ppt bindsym Shift+Right exec emacs-i3-integration resize grow width 100 px or 100 ppt bindsym equal exec i3-emacs-balance-windows # back to normal: Enter or Escape bindsym Return mode "default" bindsym Escape mode "default" } bindsym $mod+r mode "resize" mode "move" { bindsym $mod+Tab focus right bindsym Left move left bindsym Down move down bindsym Up move up bindsym Right move right bindsym h move left bindsym j move down bindsym k move up bindsym l move right # back to normal: Enter or Escape bindsym Return mode "default" bindsym Escape mode "default" } bindsym $mod+m mode "move" focus floating #+end_src ** OFF (OFF) Intergration with dmenu [[https://tools.suckless.org/dmenu/][dmenu]] is a dynamic menu program for X. I've opted out of using it in favour of rofi, but here is a relevant bit of config. Scripts are located in the =bin/scripts= folder. #+begin_src conf-space :tangle no # dmenu bindsym $mod+d exec i3-dmenu-desktop --dmenu="dmenu -l 10" bindsym $mod+apostrophe mode "dmenu" mode "dmenu" { bindsym d exec i3-dmenu-desktop --dmenu="dmenu -l 10"; mode default bindsym p exec dmenu_run -l 10; mode default bindsym m exec dmenu-man; mode default bindsym b exec dmenu-buku; mode default bindsym f exec dmenu-explore; mode default bindsym t exec dmenu-tmuxp; mode default bindsym Escape mode "default" } bindsym $mod+b exec --no-startup-id dmenu-buku #+end_src ** Integration with rofi Keybindings to launch [[https://github.com/davatorium/rofi][rofi]]. For more detail, look the [[*Rofi]] section. #+begin_src conf-space bindsym $mod+d exec "rofi -modi 'drun,run' -show drun" bindsym $mod+b exec --no-startup-id rofi-buku-mine bindsym $mod+minus exec rofi-pass bindsym $mod+equal exec rofimoji bindsym $mod+apostrophe mode "rofi" mode "rofi" { bindsym d exec "rofi -modi 'drun,run' -show drun" bindsym m exec rofi-man; mode default bindsym b exec rofi-buku-mine; mode default bindsym k exec rofi-pass; mode default bindsym Escape mode "default" } #+end_src ** Launching apps & misc keybindings I prefer to use a separate mode to launch most of my apps, with some exceptions. *** Apps #+begin_src conf-space # Launch apps # start a terminal at workspace 1 bindsym $mod+Return exec "i3-msg 'workspace 1 🚀; exec alacritty'" bindsym $mod+p exec "copyq menu" bindsym $mod+Shift+x exec "i3lock -f -i /home/pavel/Pictures/lock-wallpaper.png" bindsym $mod+semicolon mode "apps" mode "apps" { bindsym Escape mode "default" bindsym b exec firefox; mode default bindsym v exec vk; mode default bindsym s exec slack-wrapper; mode default; bindsym d exec "flatpak run com.discordapp.Discord"; mode default; bindsym m exec "alacritty -e ncmpcpp"; mode default bindsym c exec "copyq toggle"; mode default bindsym k exec "keepassxc"; mode default # bindsym e exec mailspring; mode default bindsym a exec emacs; mode default bindsym n exec "alacritty -e newsboat"; mode default bindsym w exec "alacritty /home/pavel/bin/scripts/run_wego"; mode default # bindsym a exec emacsclient -c; mode default # bindsym Shift+a exec emacs; mode default } #+end_src *** Media controls & brightness #+begin_src conf-space # Pulse Audio controls bindsym XF86AudioRaiseVolume exec --no-startup-id "ponymix increase 5 --max-volume 150" bindsym XF86AudioLowerVolume exec --no-startup-id "ponymix decrease 5 --max-volume 150" bindsym XF86AudioMute exec --no-startup-id "ponymix toggle" exec --no-startup-id xmodmap -e 'keycode 135 = Super_R' && xset -r 135 bindsym $mod+F2 exec --no-startup-id "ponymix increase 5" bindsym $mod+F3 exec --no-startup-id "ponymix decrease 5" # Media player controls bindsym XF86AudioPlay exec mpc toggle bindsym XF86AudioPause exec mpc pause bindsym XF86AudioNext exec mpc next bindsym XF86AudioPrev exec mpc prev # Screen brightness bindsym XF86MonBrightnessUp exec light -A 5 bindsym XF86MonBrightnessDown exec light -U 5 #+end_src *** Screenshots #+begin_src conf-space # Screenshots bindsym --release Print exec "flameshot gui" bindsym --release Shift+Print exec "xfce4-screenshooter" #+end_src ** Colors Application of the XResources theme to the WM. #+begin_src conf-space exec xrdb -merge $HOME/.Xresources # Colors set_from_resource $bg-color background set_from_resource $active-color color4 set_from_resource $inactive-bg-color color8 set_from_resource $text-color foreground set_from_resource $inactive-text-color color7 set_from_resource $urgent-bg-color color1 set_from_resource $urgent-text-color color0 # window colors # border background text indicator child border client.focused $active-color $bg-color $text-color $bg-color $active-color client.unfocused $bg-color $inactive-bg-color $inactive-text-color $bg-color $bg-color client.focused_inactive $active-color $inactive-bg-color $inactive-text-color $bg-color $bg-color client.urgent $urgent-bg-color $urgent-bg-color $urgent-text-color $bg-color $urgent-bg-color #+end_src ** OFF (OFF) i3blocks I've opted out of i3bar & [[https://github.com/vivien/i3blocks][i3blocks]] for [[https://github.com/polybar/polybar][polybar]] #+begin_src conf-space :tangle no bar { status_command i3blocks -c ~/.config/i3/i3blocks.conf i3bar_command i3bar font pango:monospace 12 output HDMI-A-0 tray_output none colors { background $bg-color separator #757575 # border background text focused_workspace $bg-color $bg-color $text-color inactive_workspace $inactive-bg-color $inactive-bg-color $inactive-text-color urgent_workspace $urgent-bg-color $urgent-bg-color $urgent-text-color } } bar { status_command i3blocks -c ~/.config/i3/i3blocks.conf i3bar_command i3bar font pango:monospace 10 output DVI-D-0 colors { background $bg-color separator #757575 # border background text focused_workspace $bg-color $bg-color $text-color inactive_workspace $inactive-bg-color $inactive-bg-color $inactive-text-color urgent_workspace $urgent-bg-color $urgent-bg-color $urgent-text-color } } #+end_src ** Keyboard Layout A script to set Russian-English keyboard layout: #+begin_src bash :tangle ./bin/scripts/set_layout setxkbmap -layout us,ru setxkbmap -model pc105 -option 'grp:win_space_toggle' -option 'grp:alt_shift_toggle' #+end_src A script to toggle the layout #+begin_src bash :tangle ./bin/scripts/toggle_layout if setxkbmap -query | grep -q us,ru; then setxkbmap -layout us setxkbmap -option else setxkbmap -layout us,ru setxkbmap -model pc105 -option 'grp:win_space_toggle' -option 'grp:alt_shift_toggle' fi #+end_src And the relevant i3 settings: #+begin_src conf-space # Layout exec_always --no-startup-id set_layout bindsym $mod+slash exec toggle_layout #+end_src ** Autostart #+begin_src conf-space # Polybar exec_always --no-startup-id "bash /home/pavel/bin/polybar.sh" # Wallpaper exec_always "feh --bg-fill ~/Pictures/wallpaper.jpg" # Picom exec picom # Keynav exec keynav # Applets exec --no-startup-id nm-applet # exec --no-startup-id /usr/bin/blueman-applet exec shepherd exec dunst exec copyq exec "xmodmap ~/.Xmodmap" # exec "xrdb -merge ~/.Xresources" # exec "bash ~/bin/autostart.sh" #+end_src * Polybar :PROPERTIES: :header-args:conf-windows: :tangle ./.config/polybar/config :comments link :END: | Category | Guix dependency | Description | |-----------------+-----------------+-------------| | desktop-polybar | polybar | statusbar | [[https://github.com/polybar/polybar][Polybar]] is a nice-looking, WM-agnostic statusbar program. I switched to polybar because I wanted to try out some WMs other than i3, but decided to stick with i3 for now. Don't forget to install the Google Noto Color Emoji font. Guix package with all Noto fonts is way too large. References: - [[https://github.com/polybar/polybar/wiki][polybar docs]] ** General settings *** Colors First, let's use xrdb colors in polybar. To avoid code duplication, I generate them via noweb. #+NAME: get-polybar-colors #+begin_src emacs-lisp :var table=colors :tangle no (mapconcat (lambda (elem) (format "%s = ${xrdb:%s}" (nth 0 elem) (nth 1 elem))) (seq-filter (lambda (elem) (nth 1 elem)) table) "\n") #+end_src #+begin_src conf-windows :noweb yes [colors] <> background = ${xrdb:background} ; foreground = ${xrdb:foreground} #+end_src *** Glyphs Also, let's try to use some glyphs. The [[https://github.com/adi1090x/polybar-themes][polybar-themes]] repository can give some inspiration on what is possible, here I am replicating a powerline-ish look. Although polybar makes it a bit more awkward than it could've been. The approach is to put a glyph between two blocks like this: #+begin_example block1  block2 #+end_example And set the colors like that: | | block1 | glyph | block 2 | |------------+--------+-------+---------| | foreground | F1 | B2 | F2 | | background | B1 | B1 | B2 | So, let's define the glyph symbols: #+begin_src conf-windows [glyph] gleft =  gright =  #+end_src *** Modules To make life a bit easier, I'll define a single source of truth for modules and their colors here. So, here is a table with all modules. #+NAME: polybar_modules | Index | Module | Color | Glyph | |-------+-------------+---------------+-------| | 1 | pulseaudio | light-magenta | + | | 2 | mpd | magenta | + | | 9 | battery | light-cyan | + | | 3 | cpu | cyan | + | | 4 | ram-memory | light-green | + | | 5 | swap-memory | green | + | | 6 | network | light-red | + | | 7 | openvpn | light-red | | | 8 | xkeyboard | red | + | | 10 | weather | light-yellow | + | | 12 | sun | yellow | + | | 13 | aw-afk | light-blue | + | | 14 | date | blue | + | Some functions to use colors in the individual modules: #+NAME: get-polybar-bg #+begin_src emacs-lisp :var table=polybar_modules module="pulseaudio" (format "${colors.%s}" (nth 2 (seq-find (lambda (el) (string-equal (nth 1 el) module)) table))) #+end_src #+NAME: get-polybar-fg #+begin_src emacs-lisp module="pulseaudio" "${colors.black}" #+end_src Also, I want to exclude some modules from certain monitors and machines. For now this concerns just the battery module, so I exclude it from the monitors of my desktop PC. In future I may need to rework this to include hostname, but as long as all my machines have separate monitor names, it works fine. #+NAME: polybar_modules_exclude | Monitor | Exclude | |----------+---------| | DVI-D-0 | battery | | HDMI-A-0 | battery | Now, we need to generate a set of glyphs. The code below generates all the required glyhps so that every combination of neighoring colors in the bar had one. #+NAME: polybar_extra_colors | Color 1 | Color 2 | |------------+---------------| | background | white | | background | light-magenta | | blue | background | #+NAME: polybar-generate-glyphs #+begin_src emacs-lisp :var table=polybar_modules exclude-table=polybar_modules_exclude extra=polybar_extra_colors (let* ((monitors (thread-last exclude-table (seq-map (lambda (el) (nth 0 el))) (seq-uniq))) (exclude-combinations (seq-map (lambda (monitor) (seq-map (lambda (el) (nth 1 el)) (seq-filter (lambda (el) (and (string-equal (nth 0 el) monitor) (nth 1 el))) exclude-table))) `(,@monitors ""))) (module-glyph-combinations (thread-last exclude-combinations (seq-map (lambda (exclude) (thread-last table (seq-filter (lambda (elt) (not (or (member (nth 1 elt) exclude) (not (string-equal (nth 3 elt) "+")))))) ;; (seq-map (lambda (elt) (nth 1 elt))) ))) (seq-uniq))) (color-changes nil)) (dolist (e extra) (add-to-list 'color-changes (concat (nth 0 e) "--" (nth 1 e)))) (dolist (comb module-glyph-combinations) (dotimes (i (1- (length comb))) (add-to-list 'color-changes (concat (nth 2 (nth i comb)) "--" (nth 2 (nth (1+ i) comb)))))) (mapconcat (lambda (el) (let ((colors (split-string el "--"))) (format " [module/glyph-%s--%s] type = custom/text content-background = ${colors.%s} content-foreground = ${colors.%s} content = ${glyph.gright} content-font = 5" (nth 0 colors) (nth 1 colors) (nth 0 colors) (nth 1 colors)))) color-changes "\n")) #+end_src #+begin_src conf-windows :noweb yes <> #+end_src And a set of modules interweaved with corresponding glyphs for each monitor: #+NAME: polybar-generate-modules #+begin_src emacs-lisp :var table=polybar_modules exclude-table=polybar_modules_exclude monitor="DVI-D-0" first-color="background" last-color="background" (let* ((exclude-modules (thread-last exclude-table (seq-filter (lambda (el) (string-equal (nth 0 el) monitor))) (seq-map (lambda (el) (nth 1 el))))) (modules (thread-last table (seq-filter (lambda (el) (not (member (nth 1 el) exclude-modules)))))) (prev-color first-color) (ret nil)) (concat (mapconcat (lambda (el) (apply #'concat (list (when (string-equal (nth 3 el) "+") (setq ret (format "glyph-%s--%s " prev-color (nth 2 el))) (setq prev-color (nth 2 el)) ret) (nth 1 el)))) modules " ") (unless (string-empty-p last-color) (format " glyph-%s--%s " prev-color last-color)))) #+end_src *** Global bar config Global bar configuration. Monitor config and base colors. #+begin_src conf-windows [bar/mybar] monitor = ${env:MONITOR:} width = 100% height = ${env:HEIGHT:27} fixed-center = false bottom=true background = ${colors.background} foreground = ${colors.black} #+end_src Some geometry settings. These are set this way to make glyphs look the way they should #+begin_src conf-windows ; line-size = 3 line-color = #f00 padding = 0 module-margin-left = 0 module-margin-right = 0 margin-bottom = 0 margin-top = 0 ; underline-size = 0 border-size = 0 offset-x = 0 offset-y = 0 radius = 0.0 #+end_src Fonts #+begin_src conf-windows ; font-0 = ${env:FONT0:pango:monospace:size=10;1} ; font-1 = ${env:FONT1:NotoEmoji:scale=10:antialias=false;0} ; font-2 = ${env:FONT2:fontawesome:pixelsize=10;1} ; font-3 = ${env:FONT3:JetBrains Mono Nerd Font:monospace:size=10;1} font-0 = pango:monospace:size=13;2 font-1 = NotoEmoji:scale=10:antialias=false;1 font-2 = fontawesome:pixelsize=13;3 font-3 = JetBrains Mono Nerd Font:monospace:size=13;4 font-4 = JetBrains Mono Nerd Font:monospace:size=17;4 #+end_src Modules. Because I sometimes set up different blocks on different monitors, they are set via environment variables. #+begin_src conf-windows ; modules-left = i3 ; modules-center = test modules-right = ${env:RIGHT_BLOCKS} tray-position = ${env:TRAY:right} tray-padding = 0 tray-maxsize = 16 tray-background = ${colors.background} wm-restack = i3 ; override-redirect = true scroll-up = i3wm-wsnext scroll-down = i3wm-wsprev ; cursor-click = pointer ; cursor-scroll = ns-resize #+end_src Misc settings. #+begin_src conf-windows [settings] screenchange-reload = true compositing-background = source compositing-foreground = over compositing-overline = over compositing-underline = over compositing-border = over [global/wm] margin-top = 0 margin-bottom = 0 #+end_src *** Launch script The script below allows me to: - have different blocks on my two different-sized monitors and my laptop; - have different settings on my desktop PC and laptop; #+begin_src bash :tangle ./bin/polybar.sh :noweb yes hostname=$(hostname) # Settings varying on the hostname export WLAN_INTERFACE=$(nmcli -f DEVICE con show | grep -Ev "(.*docker.*|DEVICE|br.*|tun.*|veth.*|--)" | xargs) if [ "$hostname" = "azure" ]; then TRAY_MONITOR="eDP-1" # export WLAN_INTERFACE="wlp3s0" elif [ "$hostname" = "eminence" ]; then TRAY_MONITOR="eDP" # export WLAN_INTERFACE="wlo1" else TRAY_MONITOR="HDMI-A-0" # export WLAN_INTERFACE="wlp35s0f3u2" fi # Setting varying on the monitor declare -A FONT_SIZES=( ["eDP"]="13" ["eDP-1"]="13" ["DVI-D-0"]="13" ["HDMI-A-0"]="13" ) declare -A EMOJI_SCALE=( ["eDP"]="9" ["eDP-1"]="9" ["DVI-D-0"]="10" ["HDMI-A-0"]="10" ) declare -A BAR_HEIGHT=( ["eDP"]="29" ["eDP-1"]="29" ["DVI-D-0"]="29" ["HDMI-A-0"]="29" ) declare -A BLOCKS=( ["eDP"]="<>" ["eDP-1"]="<>" ["DVI-D-0"]="<>" ["HDMI-A-0"]="<>" ) # Geolocation for some modules export LOC="SPB" # export IPSTACK_API_KEY=$(pass show My_Online/APIs/ipstack | head -n 1) pkill polybar for m in $(xrandr --query | grep " connected" | cut -d" " -f1); do export MONITOR=$m if [ "$MONITOR" = "$TRAY_MONITOR" ]; then export TRAY="right" else export TRAY="none" fi SIZE=${FONT_SIZES[$MONITOR]} SCALE=${EMOJI_SCALE[$MONITOR]} if [[ -z "$SCALE" ]]; then continue fi # export FONT0="pango:monospace:size=$SIZE;1" # export FONT1="NotoEmoji:scale=$SCALE:antialias=false;1" # export FONT2="fontawesome:pixelsize=$SIZE;1" # export FONT3="JetBrains Mono Nerd Font:monospace:size=15;1" export HEIGHT=${BAR_HEIGHT[$MONITOR]} export RIGHT_BLOCKS=${BLOCKS[$MONITOR]} polybar mybar & done #+end_src ** Individual modules Some of the custom modules below use Org mode noweb to evaluate colors, because it's faster than querying =xrdb= at runtime. I wish I could reference polybar values there, but [[https://github.com/polybar/polybar/issues/615][it looks like this is impossible]]. If you want to copy something, you can go to the [[file:bin/polybar/][bin/polybar]] folder. *** pulseaudio PulseAudio status #+begin_src conf-windows :noweb yes [module/pulseaudio] type = internal/pulseaudio use-ui-max = true bar-volume-width = 7 ; bar-volume-foreground-0 = ${colors.white} ; bar-volume-foreground-1 = ${colors.yellow} ; bar-volume-foreground-2 = ${colors.yellow} ; bar-volume-foreground-3 = ${colors.blue} ; bar-volume-foreground-4 = ${colors.blue} ; bar-volume-foreground-5 = ${colors.green} ; bar-volume-foreground-6 = ${colors.green} bar-volume-gradient = false bar-volume-indicator = | bar-volume-indicator-font = 2 bar-volume-fill = ─ bar-volume-fill-font = 2 bar-volume-empty = ─ bar-volume-empty-font = 2 ; bar-volume-empty-foreground = ${colors.light-white} format-volume = ♪ label-volume = %percentage%% ramp-volume-0 = ▁ ramp-volume-1 = ▂ ramp-volume-2 = ▃ ramp-volume-3 = ▄ ramp-volume-4 = ▅ ramp-volume-5 = ▆ ramp-volume-6 = ▇ ramp-volume-7 = █ format-muted = ♪ label-muted = MUTE format-volume-background = <> format-muted-background = <> ; format-volume-underline = ${colors.white} ; format-muted-underline = ${colors.light-black} #+end_src *** mpd [[https://www.musicpd.org/][Music Player Daemon]] status #+begin_src conf-windows :noweb yes [module/mpd] type = internal/mpd format-playing = format-paused = format-stopped = " " label-song = [%album-artist%] %title% label-time = %elapsed%/%total% label-song-maxlen = 30 label-song-ellipsis = true ; format-playing-underline = ${colors.yellow} ; format-paused-underline = ${colors.yellow} ; format-stopped-underline = ${colors.yellow} format-playing-background = <> format-paused-background = <> format-stopped-background = <> label-separator = 0 separator-foreground = ${colors.red} icon-pause =  icon-play =  icon-stop =  icon-prev = 1 icon-next = 2 #+end_src *** cpu CPU usage #+begin_src conf-windows :noweb yes [module/cpu] type = internal/cpu format = "