dotfiles/config/stumpwm/config.lisp

318 lines
12 KiB
Common Lisp

(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*)