(in-package :stumpwm) (set-prefix-key (kbd "C-t")) ;; prompt the user for an interactive command. The first arg is an ;; optional initial contents. (defcommand colon1 (&optional (initial "")) (:rest) (let ((cmd (read-one-line (current-screen) ": " :initial-input initial))) (when cmd (eval-command cmd t)))) ;; Read some doc (define-key *root-map* (kbd "d") "exec gv") ;; Browse somewhere (define-key *root-map* (kbd "b") "colon1 exec firefox http://www.") ;; Ssh somewhere (define-key *root-map* (kbd "C-s") "colon1 exec xterm -e ssh ") ;; Lock screen (define-key *root-map* (kbd "C-l") "exec xlock") ;; Web jump (works for DuckDuckGo and Imdb) (defmacro make-web-jump (name prefix) `(defcommand ,(intern name) (search) ((:rest ,(concatenate 'string name " search: "))) (nsubstitute #\+ #\Space search) (run-shell-command (concatenate 'string ,prefix search)))) ;;; Appearance (set-fg-color "#d8dee9") (set-bg-color "#1b2b34") (set-border-color "#6699cc") (setf *normal-border-width* 2) (setf *window-border-style* :thin) (setf *ignore-wm-inc-hints* t) (set-font "-b&h-luxi mono-medium-r-normal--20-*-*-*-m-0-iso10646-1") (setf *message-window-padding* 16) (setf *message-window-gravity* :center) (setf *input-window-gravity* :center) ;;; Modules (set-module-dir "~/.stumpwm.d/modules/util") (load-module "swm-gaps") (setf swm-gaps:*inner-gaps-size* 8) (setf swm-gaps:*outer-gaps-size* 16) ;;; Frame behaviour ;; Prevent new frames from sucking windows from other frames on creation (setf *new-frame-action* :empty) (setf *suppress-frame-indicator* t) ;;; Window placement policy (define-frame-preference "Default" (0 t nil :class "Konqueror" :role "...konqueror-mainwindow") (1 t nil :class "XTerm") (:float t t :class "alsamixer-scratch") (:float t t :class "ncmpcpp-scratch")) (define-frame-preference "Emacs" (1 t t :restore "emacs-editing-dump" :title "...xdvi") (0 t t :create "emacs-dump" :class "Emacs")) ;;; Commands (defcommand kill-window-or-frame () () "Kill the current window, or remove the frame if empty." (if (current-window) (delete-window) (remove-split))) (defcommand toggle-fullscreen () () "Toggle fullscreen for the current window." (fullscreen)) (defcommand focus-left () () "Focus the frame to the left." (move-focus :left)) (defcommand focus-right () () "Focus the frame to the right." (move-focus :right)) (defcommand focus-up () () "Focus the frame above." (move-focus :up)) (defcommand focus-down () () "Focus the frame below." (move-focus :down)) (defcommand split-horizontal () () "Split the current frame horizontally." (split-frame-in-dir :column)) (defcommand split-vertical () () "Split the current frame vertically." (split-frame-in-dir :row)) (defcommand cycle-next () () "Cycle to the next window in the current frame." (next-in-frame)) (defcommand cycle-prev () () "Cycle to the previous window in the current frame." (prev-in-frame)) (defcommand rofi-run () () "Open rofi run launcher." (run-shell-command "rofi -show run")) (defcommand rofi-pull-window () () "Pull a window from the group into the current frame using rofi." (let* ((windows (remove (current-window) (group-windows (current-group)))) (names (format nil "~{~a~%~}" (mapcar #'window-name windows))) (selection (run-prog-collect-output "/bin/sh" "-c" (format nil "echo '~a' | rofi -i -dmenu -p 'window'" names)))) (when (and selection (not (string= selection ""))) (let ((win (find (string-trim '(#\Newline) selection) windows :key #'window-name :test #'string=))) (when win (pull-window win)))))) (defcommand gaps-off () () "Disable gaps." (swm-gaps:toggle-gaps-off)) (defcommand gaps-small () () "Set small gaps." (setf swm-gaps:*inner-gaps-size* 8 swm-gaps:*outer-gaps-size* 8) (swm-gaps:toggle-gaps-on)) (defcommand gaps-medium () () "Set medium gaps." (setf swm-gaps:*inner-gaps-size* 16 swm-gaps:*outer-gaps-size* 16) (swm-gaps:toggle-gaps-on)) (defcommand gaps-large () () "Set large gaps." (setf swm-gaps:*inner-gaps-size* 32 swm-gaps:*outer-gaps-size* 32) (swm-gaps:toggle-gaps-on)) (defcommand mpv-pick () () "Pick a video from ~/Videos and play it in mpv." (run-shell-command "mpv-pick.sh")) (defcommand mpv-url () () "Play the URL in the clipboard with mpv." (run-shell-command "mpv-url.sh")) (defcommand yt-download () () "Download the URL in the clipboard to a chosen subdirectory of ~/Videos with yt-dlp." (let* ((url (run-prog-collect-output "/usr/bin/xclip" "-o" "-selection" "clipboard")) (url (string-trim '(#\Newline) url))) (if (string= url "") (message "Clipboard is empty!") (let* ((dir (run-prog-collect-output "/bin/sh" "-c" "{ echo '.'; find $HOME/Videos -maxdepth 1 -mindepth 1 -type d -exec basename {} \\; | sort; } | rofi -i -dmenu -p 'download to'" )) (dir (string-trim '(#\Newline) dir))) (when (not (string= dir "")) (let ((dest (format nil "~a/Videos/~a" (getenv "HOME") dir))) (run-shell-command (format nil "cd ~s && yt-dlp ~s" dest url)) (message "Downloading to ~a..." dest))))))) ;;; Brightness commands (defcommand brightness-10 () () "Set brightness to 10%." (run-shell-command "brightnessctl set 10%")) (defcommand brightness-20 () () "Set brightness to 20%." (run-shell-command "brightnessctl set 20%")) (defcommand brightness-30 () () "Set brightness to 30%." (run-shell-command "brightnessctl set 30%")) (defcommand brightness-40 () () "Set brightness to 40%." (run-shell-command "brightnessctl set 40%")) (defcommand brightness-50 () () "Set brightness to 50%." (run-shell-command "brightnessctl set 50%")) (defcommand brightness-60 () () "Set brightness to 60%." (run-shell-command "brightnessctl set 60%")) (defcommand brightness-70 () () "Set brightness to 70%." (run-shell-command "brightnessctl set 70%")) (defcommand brightness-80 () () "Set brightness to 80%." (run-shell-command "brightnessctl set 80%")) (defcommand brightness-90 () () "Set brightness to 90%." (run-shell-command "brightnessctl set 90%")) (defcommand brightness-100 () () "Set brightness to 100%." (run-shell-command "brightnessctl set 100%")) ;;; Float terminal scratchpad system ;; ;; StumpWM's define-frame-preference supports :float as the frame number, which ;; causes the window to be floated *during* group-add-window, before tiling ;; logic runs. This is the only reliable way to float a window from config — ;; attempting to call float-window from *new-window-hook* is too late, as ;; StumpWM has already tiled it. The hook IS still useful for positioning ;; after the float is established. ;; ;; float-window and float-window-move-resize are internal (stumpwm::), not ;; exported from the stumpwm package. ;; ;; To add a new float terminal: ;; 1. Add (:float t t :class "my-class") to the "Default" define-frame-preference ;; 2. Call (register-float-term "my-class" "command" x y width height) ;; Pass :persistent t if the window should hide on s-q instead of closing ;; 3. Define a command that calls (spawn-or-focus "my-class" "command") (defvar *float-term-rules* '()) (defun register-float-term (class command x y width height &key persistent) "Register a floating terminal. Add a matching define-frame-preference rule too. If PERSISTENT is t, smart-kill will hide the window instead of killing it." (setf *float-term-rules* (cons (list class command x y width height :persistent persistent) (remove class *float-term-rules* :key #'car :test #'string=)))) (add-hook *new-window-hook* (lambda (win) (let ((rule (assoc (window-class win) *float-term-rules* :test #'string=))) (when rule (let ((x (nth 2 rule)) (y (nth 3 rule)) (width (nth 4 rule)) (height (nth 5 rule))) (stumpwm::float-window-move-resize win :x x :y y :width width :height height)))))) (defun spawn-or-focus (class command) "Unhide or focus existing window with CLASS, or spawn COMMAND if none exists." (let ((win (find-if (lambda (w) (string= (window-class w) class)) (screen-windows (current-screen))))) (cond ((null win) (run-shell-command command)) ((eq win (current-window)) nil) (t (focus-window win))))) (defcommand smart-kill () () "Kill window normally, unless it's a persistent float — then hide it instead." (let* ((win (current-window)) (class (when win (window-class win))) (rule (when class (assoc class *float-term-rules* :test #'string=)))) (if (and rule (getf (nthcdr 6 rule) :persistent)) (hide-window win) (kill-window-or-frame)))) ;; Registrations — add new float terminals here (register-float-term "alsamixer-scratch" "wezterm start --class alsamixer-scratch -- alsamixer" 628 450 800 400) (defcommand alsamixer-float () () "Open alsamixer in a floating wezterm window." (spawn-or-focus "alsamixer-scratch" "wezterm start --class alsamixer-scratch -- alsamixer")) (register-float-term "ncmpcpp-scratch" "wezterm start --class ncmpcpp-scratch -- ncmpcpp" 628 300 900 500 :persistent t) (defcommand ncmpcpp-float () () "Open ncmpcpp in a persistent floating wezterm window." (spawn-or-focus "ncmpcpp-scratch" "wezterm start --class ncmpcpp-scratch -- ncmpcpp")) ;;; Config keymap (defvar *config-map* (make-sparse-keymap)) (define-key *config-map* (kbd "a") "alsamixer-float") (defvar *brightness-map* (make-sparse-keymap)) (define-key *brightness-map* (kbd "1") "brightness-10") (define-key *brightness-map* (kbd "2") "brightness-20") (define-key *brightness-map* (kbd "3") "brightness-30") (define-key *brightness-map* (kbd "4") "brightness-40") (define-key *brightness-map* (kbd "5") "brightness-50") (define-key *brightness-map* (kbd "6") "brightness-60") (define-key *brightness-map* (kbd "7") "brightness-70") (define-key *brightness-map* (kbd "8") "brightness-80") (define-key *brightness-map* (kbd "9") "brightness-90") (define-key *brightness-map* (kbd "0") "brightness-100") (define-key *config-map* (kbd "b") *brightness-map*) ;;; Keymaps (defvar *gaps-map* (make-sparse-keymap)) (define-key *gaps-map* (kbd "0") "gaps-off") (define-key *gaps-map* (kbd "1") "gaps-small") (define-key *gaps-map* (kbd "2") "gaps-medium") (define-key *gaps-map* (kbd "3") "gaps-large") (defvar *window-map* (make-sparse-keymap)) (define-key *window-map* (kbd "r") "iresize") (define-key *window-map* (kbd "g") *gaps-map*) (defvar *media-map* (make-sparse-keymap)) (define-key *media-map* (kbd "v") "mpv-pick") (define-key *media-map* (kbd "m") "mpv-url") (define-key *media-map* (kbd "d") "yt-download") (define-key *media-map* (kbd "p") "ncmpcpp-float") ;;; Top-level bindings (define-key *top-map* (kbd "s-d") "rofi-run") (define-key *top-map* (kbd "s-w") "rofi-pull-window") (define-key *top-map* (kbd "s-h") "move-focus left") (define-key *top-map* (kbd "s-j") "move-focus down") (define-key *top-map* (kbd "s-k") "move-focus up") (define-key *top-map* (kbd "s-l") "move-focus right") (define-key *top-map* (kbd "s-p") "next-in-frame") (define-key *top-map* (kbd "s-n") "prev-in-frame") (define-key *top-map* (kbd "s-q") "smart-kill") (define-key *top-map* (kbd "s-F") "select-floating-window") (define-key *top-map* (kbd "s-f") "toggle-fullscreen") (define-key *top-map* (kbd "s-c") *config-map*) (define-key *top-map* (kbd "s--") "vsplit") (define-key *top-map* (kbd "s-\\") "hsplit") (define-key *top-map* (kbd "s-W") *window-map*) (define-key *top-map* (kbd "s-m") *media-map*) ;;; Groups (grename "1") (run-commands "gnewbg 2" "gnewbg 3" "gnewbg 4") ;;; Group switching (define-key *top-map* (kbd "s-1") "gselect 1") (define-key *top-map* (kbd "s-2") "gselect 2") (define-key *top-map* (kbd "s-3") "gselect 3") (define-key *top-map* (kbd "s-4") "gselect 4")