;;; -*- mode: lisp; -*- (in-package :stumpwm) ;;; Setup Modules and Quicklisp ;; path to modules ;; git clone git@github.com:stumpwm/stumpwm-contrib.git ~/.config/stumpwm/modules (init-load-path #p"~/.config/stumpwm/modules/") (let ((quicklisp-init (merge-pathnames ".cache/quicklisp/setup.lisp" (user-homedir-pathname)))) (when (probe-file quicklisp-init) (load quicklisp-init))) ;; (setq *debug-level* 5) ;; (redirect-all-output (data-dir-file "debug-output" "txt")) (defun battery-stuff () (string-trim '(#\Space #\Newline) (run-shell-command "/home/ladot/.local/bin/battery" t))) (defun memory-stuff () (string-trim '(#\Space #\Newline) (run-shell-command "/home/ladot/.local/bin/memory" t))) ;;; Helpers (defun tr-define-key (key command) (define-key *top-map* (kbd (concat "s-" key )) command) (define-key *root-map* (kbd key) command)) (defun file-readable-p (file) "Return t, if FILE is available for reading." (handler-case (with-open-file (f file) (read-line f)) (stream-error () nil))) (defun executable-p (name) "Tell if given executable is present in PATH." (let ((which-out (string-trim '(#\ #\linefeed) (run-shell-command (concat "which " name) t)))) (unless (string-equal "" which-out) which-out))) (defun window-menu-format (w) (list (format-expand *window-formatters* *window-format* w) w)) (defun window-from-menu (windows) (when windows (second (select-from-menu (group-screen (window-group (car windows))) (mapcar 'window-menu-format windows) "Select Window: ")))) (defun windows-in-group (group) (group-windows (find group (the list (screen-groups (current-screen))) :key 'group-name :test 'equal))) (defun floatingp (window) "Return T if WINDOW is floating and NIL otherwise" (typep window 'stumpwm::float-window)) (defun always-on-top-off (window) () "Stop the given WINDOW from always being on top of other windows" (let ((ontop-wins (group-on-top-windows (current-group)))) (setf (group-on-top-windows (current-group)) (remove window ontop-wins)))) (defun always-on-top-on (window) () "Set the given WINDOW to always be on top of other windows" (let ((w window) (windows (the list (group-on-top-windows (current-group))))) (when w (unless (find w windows) (push window (group-on-top-windows (current-group))))))) (defmacro with-on-top (win &body body) "Make sure WIN is on the top level while the body is running and restore it's always-on-top state afterwords" (let ((cw (gensym)) (ontop (gensym))) `(let* ((,cw ,win) (,ontop (find ,cw (group-on-top-windows (current-group))))) (unwind-protect (progn (unless ,ontop (always-on-top-on ,cw)) ,@body)) (unless ,ontop (always-on-top-off ,cw))))) (defun slop-get-pos () (mapcar #'parse-integer (ppcre:split "[^0-9]" (run-shell-command "slop -f \"%x %y %w %h\"" t)))) (defun slop () "Slop the current window or just float if slop cli not present." (when (executable-p "slop") (let ((win (current-window)) (group (current-group)) (pos (slop-get-pos))) (stumpwm::float-window win group) (stumpwm::float-window-move-resize win :x (nth 0 pos) :y (nth 1 pos) :width (nth 2 pos) :height (nth 3 pos)) (always-on-top-on win)))) ;;; Moving the mouse for me ;; Used for warping the cursor (load-module "beckon") (defmacro with-focus-lost (&body body) "Make sure WIN is on the top level while the body is running and restore it's always-on-top state afterwords" `(progn (banish) ,@body (when (current-window) (beckon:beckon)))) (defcommand remove-lose-focus () () "Remove the window without feaking out because of :sloppy *mouse-focus-policy*" (with-focus-lost (remove-split))) (defcommand fullscreen-and-raise () () "Fullscreen window and make sure it's on top of all other windows" (with-on-top (stumpwm:current-window) (fullscreen))) ;;; Theme (setf *colors* '("#282828" ;black "#cc241d" ;red "#b8bb26" ;green "#d79921" ;yellow "#458588" ;blue "#d3869b" ;magenta "#8ec07c" ;cyan "#ebdbb2")) ;white (update-color-map (current-screen)) ;;; Font ;;(ql:quickload :clx-truetype) ;;STYLE style Style ;; Make sure my local fonts are avaliable ;(pushnew (concat (getenv "HOME") ; "/.local/share/fonts/") ; xft:*font-dirs* :test #'string=) ;(xft:cache-fonts) (set-font "spleen-8x16") ;(parent-font "-*-spleen-*-*-*-*-17-*") ; (when (find parent-font (the list (clx-truetype:get-font-families)) ; :test #'string=))) ;;; Basic Settings (setf *window-format* "%m%s%50t") (setf *mode-line-background-color* (car *colors*) *mode-line-foreground-color* (car (last *colors*)) *time-modeline-string* "%b %d (%a) %T" *mode-line-timeout* 1) (setf *message-window-gravity* :center *window-border-style* :thin *message-window-padding* 3 *timeout-frame-indicator-wait* 0 *maxsize-border-width* 2 *normal-border-width* 2 *transient-border-width* 2 stumpwm::*float-window-border* 1 stumpwm::*float-window-title-height* 1) ;; Focus Follow Mouse (setf *mouse-focus-policy* :sloppy) ;;; Completion ;; ;; Show all completions from start ;; (setf *input-completion-show-empty* nil) ;; ;; keep completions open even when one is selected ;; (setf *input-completion-style* (make-input-completion-style-unambiguous)) (setf *input-window-gravity* :center ;; TODO determin why this appears above *message-window-input-gravity* :left) (setf *input-completion-show-empty* t) ;; Remember commands and offers orderless completion ;;(ql:quickload :stumpwm-prescient) ;;(setf *input-refine-candidates-fn* 'stumpwm-prescient:refine-input) ;;; Startup Commands (run-shell-command "feh --no-fehbg --bg-fill $HOME/.local/share/bg3.png") (run-shell-command "xsetroot -cursor_name left_ptr") ;;; Bindings (set-prefix-key (kbd "M-c")) ;; General Top Level Bindings (define-key *top-map* (kbd "s-n") "pull-hidden-next") (define-key *top-map* (kbd "s-p") "pull-hidden-previous") ;; Tab like cycling (define-key *top-map* (kbd "s-j") "next-in-frame") (define-key *top-map* (kbd "s-k") "prev-in-frame") ;; Frame cycling (define-key *top-map* (kbd "s-TAB") "fnext") (define-key *top-map* (kbd "s-ISO_Left_Tab") "fprev") (setf *resize-increment* 25) (tr-define-key "t" "fullscreen") (tr-define-key "o" "only") (tr-define-key "P" "exec clipmenu") (tr-define-key "r" "exec dmenu_run") ;; Window Movement (dyn-blacklist-command "move-window") (dyn-blacklist-command "remove-lose-focus") (define-key *top-map* (kbd "s-H") "move-window left") (define-key *top-map* (kbd "s-J") "move-window down") (define-key *top-map* (kbd "s-K") "move-window up") (define-key *top-map* (kbd "s-L") "move-window right") ;;; Volume Stuff (let ((vdown "exec cm down 5") (vup "exec cm up 5") (m *top-map*)) (define-key m (kbd "s-C-a") vdown) (define-key m (kbd "XF86AudioLowerVolume") vdown) (define-key m (kbd "s-C-f") vup) (define-key m (kbd "XF86AudioRaiseVolume") vup)) ;;; Brightness (when *initializing* (defconstant backlightfile "/sys/class/backlight/*/brightness")) (let ((bdown "exec xbacklight -dec 10") (bup "exec xbacklight -inc 10") (m *top-map*)) (define-key m (kbd "s-C-s") bdown) (define-key m (kbd "XF86MonBrightnessDown") bdown) (define-key m (kbd "s-C-d") bup) (define-key m (kbd "XF86MonBrightnessUp") bup)) ;;; General Root Level Bindings (defcommand term (&optional prg) () (run-shell-command (if prg (format nil "xterm ") "xterm"))) (define-key *root-map* (kbd "q") "exec xterm") (define-key *root-map* (kbd "y") "eval (term \"cm\")") (define-key *root-map* (kbd "b") "pull-from-windowlist") (define-key *root-map* (kbd "R") "iresize") (define-key *root-map* (kbd "B") "beckon") (define-key *root-map* (kbd "f") "fullscreen-and-raise") (define-key *root-map* (kbd "Q") "quit-confirm") (define-key *top-map* (kbd "s-x") "remove-lose-focus") (define-key *top-map* (kbd "s-q") "exec xterm") (define-key *top-map* (kbd "s-c") "exec xkill") (define-key *top-map* (kbd "s-f") "exec tabbed surf -e") (define-key *top-map* (kbd "s-b") "exec drawterm") ;;(define-key *root-map* (kbd "SPC") "exec cabl -c") ;; more usful alternatives to i and I (define-key *root-map* (kbd "i") "show-window-properties") (define-key *root-map* (kbd "I") "list-window-properties") ;;; Groups (grename "main") (gnewbg "media") (gnewbg "other") ;(gnewbg ".trash") ; hidden group ;; Don't jump between groups when switching apps (setf *run-or-raise-all-groups* nil) (define-key *groups-map* (kbd "l") "change-default-layout") (define-key *groups-map* (kbd "d") "gnew-dynamic") (define-key *groups-map* (kbd "s") "gselect") (load-module "globalwindows") (define-key *groups-map* (kbd "b") "global-pull-windowlist") ;;;; Hide and Show Windows (defcommand pull-from-trash () () (let* ((windows (windows-in-group ".trash")) (window (window-from-menu windows))) (when window (move-window-to-group window (current-group)) (stumpwm::pull-window window)))) (defcommand move-to-trash () () (stumpwm:run-commands "gmove .trash")) (tr-define-key "]" "move-to-trash") (tr-define-key "[" "pull-from-trash") ;;; Floating Windows (defcommand toggle-slop-this () () (let ((win (current-window)) (group (current-group))) (cond ((floatingp win) (always-on-top-off win) (stumpwm::unfloat-window win group)) (t (slop))))) (tr-define-key "z" "toggle-slop-this") ;;; Splits (defcommand hsplit-and-focus () () "create a new frame on the right and focus it." (with-focus-lost (hsplit) (move-focus :right))) (defcommand vsplit-and-focus () () "create a new frame below and focus it." (with-focus-lost (vsplit) (move-focus :down))) (define-key *top-map* (kbd "s-w") "hsplit-and-focus") (define-key *top-map* (kbd "s-s") "vsplit-and-focus") ;; Extra mappings for dynamic windows (define-minor-mode my/tile-mode () () (:interactive t) (:scope :dynamic-group) (:top-map '(("s-v" . "exchange-with-master") ("s-=" . "change-default-split-ratio 1/2"))) (:lighter-make-clickable nil) (:lighter "MY/TILE")) ;; (my/tile-mode) (loop :for i :in '("hsplit-and-focus" "vsplit-and-focus") :do (dyn-blacklist-command i)) (load-module "stumpwm-sndioctl") (load "~/.stumpwm.d/modeline.lisp") ;;; Gaps (load-module "swm-gaps") ;(setf swm-gaps:*inner-gaps-size* 13 ;swm-gaps:*outer-gaps-size* 7 ;swm-gaps:*head-gaps-size* 0) (setf *message-window-padding* 3 *message-window-y-padding* 3 *normal-border-width* 2 *mode-line-border-width* 2 swm-gaps:*head-gaps-size* 12 swm-gaps:*inner-gaps-size* 6 swm-gaps:*outer-gaps-size* 6) (when *initializing* (swm-gaps:toggle-gaps)) (define-key *root-map* (kbd "*") "toggle-gaps") (setf *window-format* "%c%m" *window-border-style* :tight *hidden-window-color* "^**" *group-format* "%n") ;(setq *startup-message* (format nil "Welcome! M-t C-h for help.")) ;; Orange (setq *startup-message* (format nil "")) (setq *ignore-wm-inc-hints* t) ;; Set focus and unfocus colors (set-focus-color "#665c54") (set-unfocus-color "#3c3836") (set-float-focus-color "#8EC07C") (set-float-unfocus-color "#689D6A") (set-fg-color "#EBDBB2") (set-bg-color "#282828") (set-border-color "#665c54") (set-msg-border-width 2) ;;; Remaps (define-remapped-keys '(("(acme)" ("C-b" . "Left") ("C-n" . "Down") ("C-p" . "Up") ("C-d" . ("Right" "C-h"))) ("(discord|Element|Google-chrome)" ("C-a" . "Home") ("C-e" . "End") ("C-E" . "C-e") ("C-n" . "Down") ("C-p" . "Up") ("C-f" . "Right") ("C-b" . "Left") ("C-N" . "S-Down") ("C-P" . "S-Up") ("C-F" . "S-Right") ("C-B" . "S-Left") ("C-v" . "Next") ("M-v" . "Prior") ("C-w" . ("C-S-Left" "C-x")) ("C-y" . "C-v") ("M-<" . "Home") ("M->" . "End") ("C-M-b" . "M-Left") ("C-M-f" . "M-Right") ("M-f" . "C-Right") ("M-b" . "C-Left") ("C-s" . "C-f") ("C-j" . "C-k") ("C-/" . "C-z") ("C-k" . ("C-S-End" "C-x")) ("C-d" . "Delete") ("M-d" . "C-Delete")))) ;;; Undo And Redo Functionality ;;(load-module "winner-mode") ;;(define-key *root-map* (kbd "u") "winner-undo") ;;(define-key *root-map* (kbd "C-r") "winner-redo") ;;(add-hook *post-command-hook* (lambda (command) ;;(when (member command winner-mode:*default-commands*) ;;(winner-mode:dump-group-to-file)))) ;;; Emacs integration (defcommand emacs () () ; override default emacs command "Start emacs if emacsclient is not running and focus emacs if it is running in the current group" (run-or-raise "oemacsclient -c -a 'emacs'" '(:class "Emacs"))) ;; Treat emacs splits like Xorg windows (defun is-emacs-p (win) "nil if the WIN" (when win (string-equal (window-class win) "Emacs"))) (defmacro exec-el (expression) "execute emacs lisp do not collect it's output" `(eval-string-as-el (write-to-string ',expression))) (defun eval-string-as-el (elisp &optional collect-output-p) "evaluate a string as emacs lisp" (let ((result (run-shell-command (format nil "timeout --signal=9 1m emacsclient --eval \"~a\"" elisp) collect-output-p))) (handler-case (read-from-string result) ;; Pass back a string when we can't read from the string (error () result)))) (defmacro eval-el (expression) "evaluate emacs lisp and collect it's output" `(eval-string-as-el ,(write-to-string expression :case :downcase) t)) (declaim (ftype (function (string) (values string &optional)) emacs-winmove)) (defun emacs-winmove (direction) "executes the emacs function winmove-DIRECTION where DIRECTION is a string" (eval-string-as-el (concat "(windmove-" direction ")") t)) ;;; Set up for recording (defun setup-recording-environment (group-name) "Sets up a recording environment and returns a function to start the necessary programs for recording a new YouTube video" (let* ((obs-window (find-matching-windows (list :class "obs") t t)) (clip-browser-name "Recording-Clips") (clip-directory "~/media/") (clip-browser-window (find-matching-windows (list :title clip-browser-name) t t))) ;; Create the recording group (define-frame-preference group-name (0 t t :class "obs") (0 t t :title "Recording-Clips")) (gnew group-name) ;; Setup obs (unless obs-window (run-shell-command "obs")) ;; Create a window for previewing and managing clips (unless clip-browser-window (run-shell-command (format nil "emacsclient -c -F '((name . \"~a\"))' -e '(dired \"~a\")'" clip-browser-name clip-directory)) ;; Set recording font (exec-el (fontaine-set-preset 'large))))) (defcommand recording () () (setup-recording-environment "recording")) ;;; Window focusing (defun switched-emacs-window (dir) (declare (type Keyword dir) (optimize (speed 3) (safety 1))) (if (is-emacs-p (current-window)) ;; There is not emacs window in that direction (not (length= (emacs-winmove (string-downcase (string dir))) 1)) nil)) (defun maybe-beckon () (if (current-window) (beckon:beckon) nil)) (defun better-move-focus (ogdir) "Similar to move-focus but also treats emacs windows as Xorg windows" (declare (type (member :up :down :left :right) ogdir) (optimize (speed 3) (safety 3))) (let ((cw (current-window))) (cond ((not cw) (progn (move-focus ogdir) (maybe-beckon))) ((switched-emacs-window ogdir)) ;; If fullscreen don't change focus ((stumpwm:window-fullscreen cw)) (t (progn (move-focus ogdir) (maybe-beckon)))))) (defcommand my-mv (dir) ((:direction "Enter direction: ")) (when dir (better-move-focus dir))) (define-key *top-map* (kbd "s-h") "my-mv left") (define-key *top-map* (kbd "s-j") "my-mv down") (define-key *top-map* (kbd "s-k") "my-mv up") (define-key *top-map* (kbd "s-l") "my-mv right") (define-stumpwm-type :dunstctl (input prompt) (completing-read (current-screen) prompt '("context" "action" "close" "history"))) (defcommand dunst () () (run-shell-command "dunstctl context"))