Stumpwm
Published on Mar 19, 2022 by Kristian Alexander P.
Configuration file
header-args: :tangle ~/.config/stumpwm/config :mkdirp t
Header
Needed so Emacs will use lisp major-mode for this file since it’s not using .lisp extension.
;;;; Stumpwm configuration file ;;; ;;; This file is autogenerated, do not edit. ;;; see `https://java281.dynv6.net/~alexforsale/posts/Stumpwm.html' for details
Load quicklisp
Load my installation of quicklisp. The special operator let
creates new variable quicklisp-init
and executes a series of form (here just runs the macro when
) that uses these bindings.
(let ((quicklisp-init (merge-pathnames ".local/share/quicklisp/setup.lisp" (user-homedir-pathname)))) (when (probe-file quicklisp-init) (load quicklisp-init)))
load stumpwm from quicklisp
(ql:quickload :stumpwm)
CANCELLED Swank
- State “CANCELLED” from
use sly instead
Requires swank
(from the package slime
) in order to run the function swank-loader
. Again I use the special operator let
to dynamically creates the stumpwm command swank
, this command can be run inside stumpwm for toggling swank.
(require :swank) (swank-loader:init) (let ((server-running nil)) (defcommand swank () () "Toggle the swank server on/off" (if server-running (progn (swank:stop-server 4004) (echo-string (current-screen) "Stopping swank.") (setf server-running nil)) (progn (swank:create-server :port 4004 :style swank:*communication-style* :dont-close t) (echo-string (current-screen) "Starting swank. M-x slime-connect RET RET, then (in-package stumpwm).") (setf server-running t)))))
Sly
It has more features enabled without too much customization.
(ql:quickload :slynk) (let ((server-running nil)) (defcommand slynk () () "Toggle the slynk server on/off" (if server-running (progn (slynk:stop-server 4003) (echo "Stopping slynk on port 4003.") (setf server-running nil)) (progn (slynk:create-server :port 4003 :dont-close t) (echo "Starting slynk on port 4003.") (setf server-running t)))))
Stumpwm configuration
The macro in-package
causes the package named to be the current packages; that is, the value of *package*
. In short it’ll let us to call run-shell-command
instead of stuwmpm:run-shell-command
, this also works for variables.
(in-package :stumpwm)
*mouse-focus-policy*
This variable decide how the mouse affects input focus. Possible values are
:ignore
,:sloppy
and:click
.(setf *mouse-focus-policy* :click)
*run-or-raise-all-groups*
Defines how the
run-or-raise
function searches, in this case all groups.(setf *run-or-raise-all-groups* t)
*run-or-raise-all-screens*
Same as above, but to screens.
(setf *run-or-raise-all-screens* t)
*fload-window-modifier*
- The keyboard modifier to use for resize and move floating windows without clicking on the top border. Valid values are
:META
:ALT
:HYPER
:SUPER
,:ALTGR
and:NUMLOCK
.
Functions
empty-directory-p
(defun empty-directory-p (path) (and (null (directory (concatenate 'string path "/*"))) (null (directory (concatenate 'string path "/*/")))))
check is process is active
(defun +config/is-active (process) "Returns PROCESS pid if it's active." (let ((+process (parse-integer (run-shell-command (format nil "pgrep ~A" process) t) :junk-allowed t))) (when +process) (print +process)))
Determine running OS
A simple method is by querying the output of uname -s
:
(defparameter *os* (remove #\Newline (string-downcase (run-shell-command "uname -s" t))) "The currently running operating system in lowercase.")
pass-to-emacsclient
(defun pass-to-emacsclient (arg) (let ((emacs-cmd '("emacsclient"))) (when *chemacs-profile* (push (format nil "-s ~A" *chemacs-profile*) (cdr (last emacs-cmd)))) (push (format nil "--eval ~A" arg) (cdr (last emacs-cmd))) (run-shell-command (format nil "~A" emacs-cmd) t)))
emacs-emms
(defun emacs-emms (cmd) "Runs CMD for `Emacs's `EMMS'." (let ((is-running (pass-to-emacsclient "'emms-player-paused-p'"))) (when is-running (cond ((string= (string-downcase cmd) (or "play" "pause")) (pass-to-emacsclient "'(emms-pause)'")) ((string= (string-downcase cmd) "next") (pass-to-emacsclient "'(emms-next)'")) ((string= (string-downcase cmd) (or "prev" "previous")) (pass-to-emacsclient "'(emms-previous)'"))))))
restart computer
(defun restart-computer () "Restart the computer." (run-shell-command "sudo -A reboot"))
shutdown computer
(defun shutdown-computer () "Restart the computer." (run-shell-command "sudo -A poweroff"))
Global Variables
chemacs
(defparameter *chemacs-profile* nil "The current Chemacs profile") (let ((chemacs-profile (cond ((probe-file (merge-pathnames ".config/chemacs/profile" (user-homedir-pathname))) (merge-pathnames ".config/chemacs/profile" (user-homedir-pathname))) ((probe-file (merge-pathnames ".emacs-profile" (user-homedir-pathname))) (merge-pathnames ".emacs-profile" (user-homedir-pathname)))))) (when chemacs-profile (setf *chemacs-profile* (uiop:read-file-line chemacs-profile))))
emacs daemon name
(if *chemacs-profile* (defvar *emacs-daemon-name* *chemacs-profile*) (defvar *emacs-daemon-name* "default"))
set-transient-gravity
(setf set-transient-gravity :center)
Load contrib modules
Check several directories for modules, and add it to the stumpwm module-dir using the function set-module-dir
.
(let ((module-dir (or (probe-file "/usr/share/stumpwm/contrib") (probe-file "/usr/local/share/stumpwm/contrib") (probe-file (merge-pathnames ".local/share/stumpwm/contrib" (user-homedir-pathname))) (probe-file (merge-pathnames ".config/stumpwm/modules" (user-homedir-pathname)))))) (set-module-dir module-dir))
The modules directory itself is a git repository of the stumpwm-contrib. Currently the only dependency from the contrib modules is stumpish, an interactive shell for stumpwm.
if ! test -d ~/.config/stumpwm/modules; then git clone https://github.com/stumpwm/stumpwm-contrib.git ~/.config/stumpwm/modules fi
Also create a symlink for stumpish to ~/.local/bin
[ ! -d ~/.local/bin ] && mkdir -p ~/.local/bin pushd ~/.local/bin [ ! -L stumpish ] && ln -sv ../../.config/stumpwm/modules/util/stumpish/stumpish stumpish popd
additional module directory
This is for personal module
- Set the variable
Use this variable to set the directory, if there’s more than one directory, consider keep them inside one single directory and add the parent directory instead, since the
set-module-dir
function is recursive.(defvar *additional-module-dir* "~/Projects/lisp/playerctl-mode-line")
- add when not empty
(when (not (empty-directory-p *additional-module-dir*)) (set-module-dir *additional-module-dir*))
Colors
Set the default color to base16-materia
, palette from wal
as a fallback if there’s no wal
cache file yet.
(defvar default-colors '("#263238" "#EC5F67" "#8BD649" "#FFCC00" "#89DDFF" "#82AAFF" "#80CBC4" "#CDD3DE" "#707880" "#EC5F67" "#8BD649" "#FFCC00" "#89DDFF" "#82AAFF" "#80CBC4" "#FFFFFF"))
set the wal file
(defvar wal-colors-path "~/.cache/wal/colors")
Function to get the colors palette
(defun get-color-palette () "Read colors from wal cache file or from `default-colors'." (let ((wal-colors (when (probe-file wal-colors-path) (uiop:read-file-lines wal-colors-path)))) (or wal-colors default-colors)))
Function to associate names into the colors palette
(defun get-color (name) "Map the colors from `get-color-palette'." (let ((colors (mapcar 'cons '("color0" "color1" "color2" "color3" "color4" "color5" "color6" "color7" "color8" "color9" "color10" "color11" "color12" "color13" "color14" "color15") (get-color-palette)))) (cdr (assoc name colors :test 'equal))))
Color Variable
(setf *background* (get-color "color0")) (setf *foreground* (get-color "color7")) (setf *color0* (get-color "color0")) (setf *color1* (get-color "color1")) (setf *color2* (get-color "color2")) (setf *color3* (get-color "color3")) (setf *color4* (get-color "color4")) (setf *color5* (get-color "color5")) (setf *color6* (get-color "color6")) (setf *color7* (get-color "color7")) (setf *color8* (get-color "color8")) (setf *color9* (get-color "color9")) (setf *color10* (get-color "color10")) (setf *color11* (get-color "color11")) (setf *color12* (get-color "color12")) (setf *color13* (get-color "color13")) (setf *color14* (get-color "color14")) (setf *color15* (get-color "color15"))
Map the colors into *colors
variable
(setq *colors* `(,*color0* ,*color1* ,*color2* ,*color3* ,*color4* ,*color5* ,*color6* ,*color7* ,*color8* ,*color9*))
Update the Color Map
(update-color-map (current-screen))
Set the foreground color for the message bar and input
(set-fg-color *color7*)
Set the background color for the message bar and input
(set-bg-color *color0*)
Set the border color for the message bar and input
(set-border-color *color2*)
Set the border color for focused windows.
This is only used when there is more than one frame.
(set-focus-color *color3*)
Set the border color for windows without focus.
This is only used when there is more than one frame.
(set-unfocus-color *color3*)
Set the border color for focused windows in a float group
(set-float-focus-color *color3*)
Set the border color for windows without focus in a float group.
(set-float-unfocus-color *color3*)
Autostarts
The variable *initializing*
is only set to T
when stumpwm is starting up. Which means once per session. Useful for starting up applications that only needed at startup.
(when *initializing* (progn (run-shell-command "xsetroot -cursor_name left_ptr") (run-shell-command "nm-applet") (run-shell-command "dunst") (run-shell-command "picom") (run-shell-command "unclutter") (run-shell-command "blueman-applet") (run-shell-command "xsettingsd") (run-shell-command "udiskie -t") (run-shell-command "~/.fehbg") (grename "Term") ; rename from Default (gnewbg "Dev") (gnewbg "Web") (gnewbg-float "Float") ; floating window (which-key-mode) (run-shell-command "emacs --daemon --debug-init")))
This also rename the Default frame to Term. And create additional frame named Dev, and Web. Usually these are all I needed in a session and my muscle memory set them to 1, 2, and 3 respectively.
mode-line helper functions
show-battery-charge
(defun show-battery-charge () (cond ((string= "freebsd" *os*) (let ((raw-battery (remove #\Newline (run-shell-command "apm -l" t)))) (format nil "~D%" raw-battery))) ((string= "linux" *os*) (let ((raw-battery (run-shell-command "acpi | cut -d, -f2" t))) (substitute #\Space #\Newline raw-battery)))))
show-battery-state
(defun show-battery-state () (cond ((string= "freebsd" *os*) (let ((ac-status (remove #\Newline (run-shell-command "apm -a" t)))) (cond ((equal "0" ac-status) (format nil "Discharging")) ((equal "1" ac-status) (format nil "Charging")) ((equal "2" ac-status) (format nil "Backup Power"))))) ((string= "linux" *os*) (let ((raw-battery (run-shell-command "acpi | cut -d: -f2 | cut -d, -f1" t))) (substitute #\Space #\Newline raw-battery)))))
show-brightness-value
(defun show-brightness-value () (cond ((string= "freebsd" *os*) (let ((current-brightness (remove #\Newline (run-shell-command "backlight |sed 's/.*: //'" t)))) (format nil "~A%" current-brightness))) ((string= "linux" *os*) (let ((max-brightness (parse-integer (run-shell-command "brightnessctl max" t))) (current-brightness (parse-integer (run-shell-command "brightnessctl get" t)))) (floor (* (/ current-brightness max-brightness) 100))))))
show-current-volume
(defun show-current-volume () (let ((current-volume (run-shell-command "pamixer --get-volume-human" t))) (substitute #\Space #\Newline current-volume)))
maildir contrib package
The maildir:*maildir-alist*
already set “Mail” to “~/Mail“, which I don’t use, so I clear it first before adding my mail directories. Also the path listed must contains the mail directory cur
, new
, and tmp
.
(load-module "maildir") (setq maildir:*maildir-alist* nil) ;; most of my linux devices (when (not (empty-directory-p "~/.mail/gmail/inbox/")) (push (cons "gmail" (merge-pathnames ".mail/gmail/inbox/" (user-homedir-pathname))) maildir:*maildir-alist*)) (when (not (empty-directory-p "~/.mail/ymail/inbox/")) (push (cons "ymail" (merge-pathnames ".mail/ymail/inbox/" (user-homedir-pathname))) maildir:*maildir-alist*)) (when (not (empty-directory-p "~/.mail/hotmail/inbox/")) (push (cons "hotmail" (merge-pathnames ".mail/hotmail/inbox/" (user-homedir-pathname))) maildir:*maildir-alist*)) (when (not (empty-directory-p "~/.mail/yahoo/inbox/")) (push (cons "yahoo" (merge-pathnames ".mail/yahoo/inbox/" (user-homedir-pathname))) maildir:*maildir-alist*)) ;; freebsd (let ((user-data-root (concat "/data/freebsd/"(getenv "USER")))) (when (string= "freebsd" *os*) (progn (when (not (empty-directory-p (concat user-data-root "/.mail/gmail/Inbox/"))) (push (cons "gmail" (concat user-data-root "/.mail/gmail/Inbox/")) maildir:*maildir-alist*)) (when (not (empty-directory-p (concat user-data-root "/.mail/ymail/Inbox/"))) (push (cons "ymail" (concat user-data-root "/.mail/ymail/Inbox/")) maildir:*maildir-alist*)) (when (not (empty-directory-p (concat user-data-root "/.mail/hotmail/Inbox/"))) (push (cons "hotmail" (concat user-data-root "/.mail/hotmail/Inbox/")) maildir:*maildir-alist*)) (when (not (empty-directory-p (concat user-data-root "/.mail/yahoo/Inbox/"))) (push (cons "yahoo" (concat user-data-root "/.mail/yahoo/Inbox/")) maildir:*maildir-alist*)))))
MPD contrib package
This package also provides mode-line format.
(load-module "mpd")
(mpd:mpd-connect)
The mode-line format is by default %S [%s;%r;%F]: %a - %A - %t (%n/%p)
:
%S
Playing
if playing,Paused
if paused, elseStopped
%s
S
if shuffle is on,_
otherwise%r
R
if repeat is on,_
otherwise%F
F=#
if crossfade is set,_
otherwise%a
- Artist
%A
- Album
%t
- Title
The mode-line variable
The mode line is a bar that runs across either the top or bottom of a head and is used to display information. By default the mode line displays the list of windows, similar to the output C-t w
produces.
(setf *window-format* "%m%t") (setf *screen-mode-line-format* (list "[^B^3%n^b] ^4%W" "^>" ;;"%m" '(:eval (format nil "^5|Volume: ~D" (show-current-volume))) '(:eval (when (or (not (empty-directory-p "/sys/class/backlight")) (not (empty-directory-p "/dev/backlight"))) (format nil "^6|Backlight: ~D%" (show-brightness-value)))) '(:eval (when (or (not (empty-directory-p "/sys/class/power_supply")) (not (eq 255 (parse-integer (remove #\Newline (run-shell-command "apm -l" t)))))) (format nil "^5|Battery:~D" (show-battery-charge)))) '(:eval (when (or (not (empty-directory-p "/sys/class/power_supply")) (not (eq 255 (parse-integer (remove #\Newline (run-shell-command "apm -l" t)))))) (format nil " ~D" (show-battery-state)))) "^6|%D" ;maildir "^5|%d" )) (setf *time-modeline-string* "%a %b %e %k:%M") (setf *mode-line-timeout* 2) (setf *mode-line-background-color* *background*) (setf *mode-line-foreground-color* *foreground*) (setf *mode-line-border-color* *color2*) (setf *mode-line-border-width* 0) (enable-mode-line (current-screen) (current-head) t)
*window-format*
- This variable decides how the window list is formatted. It is a string with the following formatting options:
%m
- Draw a
#
if the window is marked. %n
- Substitutes the window’s number translated via
*window-number-map*
, if there are more windows than*window-number-map*
then will use the window-number. %s
- Substitute the window’s status.
*
means current window,+
means last window, and - means any other window. %c
- Substitute the window’s class.
%t
- Substitute the window’s name.
%i
- Substitute the window’s resource ID.
*screen-mode-line-format*
This variable describes what will be displayed on the modeline for each screen. Turn it on with the function
TOGGLE-MODE-LINE
or themode-line
command. It is a list where each element may be a string, a symbol, or a list. For a symbol its value is used. For a list of the form(:eval FORM)
FORM
is evaluated and the result is used as a mode line element. If it is a string the string is printed with the following formatting options:%n
- The current group’s name.
%W
- List all windows on the current head of the current group using
*WINDOW-FORMAT*
%d
- Using
*TIME-MODELINE-STRING*
, print the time.
This variable also uses with the color commands (see
info (stumpwm)Colors
):^B
- Turn on bright colors.
^b
- Turn off bright colors.
- ^<digit>
- Set the color to
<digit>
in the*colors
variable. - %D
- This is from the
maildir
contrib package. %m
- From the
mpd
contrib package.
*time-modeline-string*
- The default time value to pass to the modeline. This is using the GNU coreutils format, see
info "(coreutils)Date conversion specifiers"
for details. *mode-line-timeouts*
- The modeline updates after each command, when a new window appears or an existing one disappears, and on a timer. This variable controls how many seconds elapse between each update. If this variable is changed while the modeline is visible, you must toggle the modeline to update timer.
*mode-line-background-color*
- The background color for the modeline.
*mode-line-foreground-color*
- The foreground color for the modeline.
*mode-line-border-color*
- The color of the modeline border.
*mode-line-border-width*
- The border width.
The last line (enable-mode-line (current-screen) (current-head) t)
runs the mode-line.
Input and message.
*input-window-gravity*
This variable controls where the input window appears. Valid values are:
:top-left
,:top-right
,:bottom-left
,:bottom-right
,:center
,:top
,:left
,:right
, and:bottom
.(setf *input-window-gravity* :top-right)
*message-window-padding*
The number of pixels that pad the text in the message window.
(setf *message-window-padding* 10)
*message-window-y-padding*
The number of pixels that pad the text in the message window vertically.
(setf *message-window-y-padding* 10)
- message-window-gravity
This variable controls where the message window appears. Valid values are:
:top-left
,:top-right
,:bottom-left
,:bottom-right
,:center
,:top
,:left
,:right
, and:bottom
.(setf *message-window-gravity* :top-right)
*message-window-input-gravity*
This variable controls where the message window appears when the input window is being displayed. Valid values are:
:top-left
,:top-right
,:bottom-left
,:bottom-right
,:center
,:top
,:left
,:right
, and:bottom
.(setf *message-window-input-gravity* :top-left)
- set-msg-border-width
Set the border width for the message bar, input bar and frame indicator.
(set-msg-border-width 0)
Window Appearance
*maxsize-border-width*
The width in pixels given to the borders of windows with maxsize or ratio hints.
(setf *maxsize-border-width* 0)
*transient-border-width*
The width in pixels given to the borders of transient or pop-up windows.
(setf *transient-border-width* 0)
*window-border-style*
This controls the appearance of the border around windows. Valid values are:
:thick
,:thin
,:tight
, and:none
.(setf *window-border-style* :none)
Font
In order to use TTF fonts, we need an additional package ttf-fonts
, which is available in the contrib
repository, or from quicklisp.
(ql:quickload :ttf-fonts)
This package also depends on clx-truetype which sadly is not in quicklisp repository, add it manually to the local-projects
directory inside your quicklisp installation directory (when not set usually in ~/quicklisp
)
*font-dir*
This is empty by default, so populate it first
(setf xft:*font-dirs* `("/usr/share/fonts/" "/usr/local/share/fonts" ,(merge-pathnames ".local/share/fonts" (user-homedir-pathname))))
The Keybindings
There are several variables that defines when a keybinding is used:
*root-map*
- Known as the “prefix-map”.
*top-map*
- This is where you’ll find the bindings for the “prefix-map”.
*group-map*
- The keymap that group related key bindings sit on.
*group-top-map*
- An alist of the top level maps for each group type.
*exchange-window-map*
- The keymap that
exchange-window
key bindings sit on.
Sets the prefix
Here I’m using s-SPC
(Super Key and the spacebar).
;; change the prefix key to something else (set-prefix-key (kbd "s-SPC"))
The kbd
itself is a function specific to stumpwm.
Sparse Keymaps
The function make-sparse-keymap
used to create a new list of bindings in the key binding tree.
- TODO screenshot
- State “TODO” from
For now I’ll use flameshot. But eventually I’ll use either the contrib module or using my own script.
(defvar *my-screenshot-keymap* (let ((m (make-sparse-keymap))) (define-key m (kbd "d") "exec flameshot gui -d 3000") (define-key m (kbd "s") "exec flameshot full") (define-key m (kbd "S") "exec flameshot gui") m))
- End-session
This uses the
end-session
contrib module.;;(load-module "end-session") (defvar *my-end-session-keymap* (let ((m (make-sparse-keymap))) (define-key m (kbd "q") "quit") ;;(define-key m (kbd "l") "logout") ;;(define-key m (kbd "s") "suspend-computer") (define-key m (kbd "S") "shutdown-computer") (define-key m (kbd "r") "loadrc") (define-key m (kbd "R") "reload") (define-key m (kbd "s-r") "restart-computer") m))
And bind it to
C-s-q
(thatControl
,Shift
, andq
).(define-key *top-map* (kbd "C-s-q") '*my-end-session-keymap*)
- Describe function
(defvar *my-describe-bindings* (let ((m (make-sparse-keymap))) (define-key m (kbd "v") "describe-variable") (define-key m (kbd "f") "describe-function") (define-key m (kbd "c") "describe-command") (define-key m (kbd "k") "describe-key") m))
- float keymap
(defvar *my-frames-float-keymap* (let ((m (make-sparse-keymap))) (define-key m (kbd "f") "float-this") (define-key m (kbd "F") "unfloat-this") (define-key m (kbd "u") "unfloat-this") (define-key m (kbd "s-f") "flatten-floats") m))
- frame management
(defvar *my-frames-management-keymap* (let ((m (make-sparse-keymap))) (define-key m (kbd "C-b") "move-focus left") (define-key m (kbd "C-n") "move-focus down") (define-key m (kbd "C-p") "move-focus up") (define-key m (kbd "C-f") "move-focus right") (define-key m (kbd "C-B") "move-window left") (define-key m (kbd "C-N") "move-window down") (define-key m (kbd "C-P") "move-window up") (define-key m (kbd "C-F") "move-window right") (define-key m (kbd "M-b") "exchange-direction left") (define-key m (kbd "M-n") "exchange-direction down") (define-key m (kbd "M-p") "exchange-direction up") (define-key m (kbd "M-f") "exchange-direction right") (define-key m (kbd "/") "hsplit-and-focus") (define-key m (kbd "-") "vsplit-and-focus") (define-key m (kbd "h") "hsplit") (define-key m (kbd "v") "vsplit") (define-key m (kbd "H") "hsplit-equally") (define-key m (kbd "V") "vsplit-equally") (define-key m (kbd ".") "iresize") (define-key m (kbd "+") "balance-frames") (define-key m (kbd "d") "remove-split") (define-key m (kbd "D") "only") (define-key m (kbd "e") "expose") (define-key m (kbd "f") "fullscreen") (define-key m (kbd "s-w") '*my-frames-float-keymap*) (define-key m (kbd "i") "info") (define-key m (kbd "I") "show-window-properties") (define-key m (kbd "m") "meta") (define-key m (kbd "s") "sibling") (define-key m (kbd "u") "next-urgent") (define-key m (kbd "U") "unmaximize") (define-key m (kbd "k") "delete-window") (define-key m (kbd "b") "windowlist-by-class") (define-key m (kbd "DEL") "repack-window-numbers") (define-key m (kbd "RET") "expose") m))
- apps
(defvar *my-app-bindings* (let ((m (make-sparse-keymap))) (define-key m (kbd "e") "emacs") (define-key m (kbd "w") "web") (define-key m (kbd "s-w") "exec $TERMINAL -e nmtui") (define-key m (kbd "g") "exec gimp") (define-key m (kbd "f") "exec thunar") (define-key m (kbd "s-p") "exec rofi-pass") (define-key m (kbd "p") "exec picard") (define-key m (kbd "h") "exec hakuneko-desktop") (define-key m (kbd "v") "exec vlc") (define-key m (kbd "S") "exec *my-screenshot-keymap*") (define-key m (kbd "t") "exec emacsclient -c -e \"(telega)\"") (define-key m (kbd "T") "exec telegram-desktop") m))
TODO Undefine default keybindings
- State “TODO” from
Custom functions, commands and macros
- colon1
This is a custom command from the default configuration that 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))))
Note that in stumpwm there’s also the command
colon
by default. - make-web-jump
Also from the default configuration, currently sets 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)))) (make-web-jump "duckduckgo" "firefox https://duckduckgo.com/?q=") (make-web-jump "imdb" "firefox http://www.imdb.com/find?q=")
- Split window
(defcommand hsplit-and-focus () () "Create a new frame on the right and focus it." (hsplit) (move-focus :right)) (defcommand vsplit-and-focus () () "Create a new frame below and move focus to it." (vsplit) (move-focus :down))
- emacs
Will start Emacs when there’s no server with the name
*emacs-daemon-name*
, else will start daemonized Emacs.(defcommand emacs () () "Run or raise Emacs." (let ((cmd (if *chemacs-profile* (format nil "emacsclient -s ~A -c -a \"emacs --daemon\"" *emacs-daemon-name*) (format nil "emacsclient -c -a \"emacs --daemon\"")))) (run-shell-command cmd)))
- Browser
Use the environment variable
${BROWSER}
if set.(let ((browser (or (getenv "BROWSER") "firefox"))) (setf *web-browser* browser)) (defcommand web () () "Run or raise browser." (run-or-raise *web-browser* `(:class ,*web-browser*) t nil))
- Terminal Emulator
Although XTerm is my default terminal-emulator, I sometimes try tinkering with other programs, so I set the
$TERMINAL
environment variable to set my current terminal-emulator.(let ((terminal (or (getenv "TERMINAL") "xterm"))) (setf *terminal-emulator* terminal)) (defcommand terminal () () (run-or-raise *terminal-emulator* `(:class ,*terminal-emulator*)))
And my default keybinding is
s-RET
.(define-key *top-map* (kbd "s-RET") "terminal")
Keybinding Tree
This is what in Emacs called “keychord” (I think). So, s-h v
is for the function describe-variable
and so on. Also note that since I’m already use s-d h
this means I’m not using the usual window-manager vim keybindings (h
, j
, k
, l
). Also since “h
” is actually already bound in *top-map*
I need to unbind it in order to set it for *my-describe-bindings*
.
- Top Map
This is the keybinding that resides on the
*top-map*
*my-describe-bindings*
(define-key *top-map* (kbd "s-h") '*my-describe-bindings*)
- Media Keys
- Volume
Using PulseAudio.
(define-key *top-map* (kbd "XF86AudioLowerVolume") "exec pactl -- set-sink-volume @DEFAULT_SINK@ -2%") (define-key *top-map* (kbd "XF86AudioRaiseVolume") "exec pactl -- set-sink-volume @DEFAULT_SINK@ +2%") (define-key *top-map* (kbd "XF86AudioMute") "exec pactl -- set-sink-mute @DEFAULT_SINK@ toggle")
- Audio Player
(define-key *top-map* (kbd "XF86AudioPlay") "exec playerctl play-pause") (define-key *top-map* (kbd "XF86AudioNext") "exec playerctl next") (define-key *top-map* (kbd "XF86AudioPrev") "exec playerctl previous")
- Backlight
(define-key *top-map* (kbd "XF86MonBrightnessDown") "exec brightnessctl set 1%-") (define-key *top-map* (kbd "XF86MonBrightnessUp") "exec brightnessctl set +1%")
- Volume
- Picom transparency
(define-key *top-map* (kbd "s-F2") "exec picom-trans -c -5") (define-key *top-map* (kbd "s-F3") "exec picom-trans -c +5")
- rofi
(define-key *top-map* (kbd "s-d") "exec rofi -show drun")
colon
I use this often enough it deserve it’s own key.
(define-key *top-map* (kbd "s-M-x") "colon")
- Mail
Bind it to
XF86Mail
(define-key *top-map* (kbd "XF86Mail") "exec emacsclient -c -e \"(notmuch)\"")
- Web
Bound to
XF86MyComputer
(define-key *top-map* (kbd "XF86MyComputer") "web")
- Group selections
(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") (define-key *top-map* (kbd "s-5") "gselect 5") (define-key *top-map* (kbd "s-6") "gselect 6") (define-key *top-map* (kbd "s-7") "gselect 7") (define-key *top-map* (kbd "s-8") "gselect 8") (define-key *top-map* (kbd "s-9") "gselect 9") (define-key *top-map* (kbd "s-0") "gselect 10") (define-key *top-map* (kbd "s-TAB") "gother")
- Group move
(define-key *top-map* (kbd "s-!") "gmove-and-follow 1") (define-key *top-map* (kbd "s-@") "gmove-and-follow 2") (define-key *top-map* (kbd "s-#") "gmove-and-follow 3") (define-key *top-map* (kbd "s-$") "gmove-and-follow 4") (define-key *top-map* (kbd "s-%") "gmove-and-follow 5") (define-key *top-map* (kbd "s-^") "gmove-and-follow 6") (define-key *top-map* (kbd "s-&") "gmove-and-follow 7") (define-key *top-map* (kbd "s-*") "gmove-and-follow 8") (define-key *top-map* (kbd "s-(") "gmove-and-follow 9") (define-key *top-map* (kbd "s-)") "gmove-and-follow 10")
- Binding for
*my-frame-management-keymap*
(define-key *top-map* (kbd "s-w") '*my-frames-management-keymap*)
- Binding for
*my-app-bindings
(define-key *top-map* (kbd "s-x") '*my-app-bindings*)
- escape-key
(define-key *top-map* (kbd "s-quoteleft") "send-raw-key")
- Screenshot
Most keyboard have the
Print
key, so I’ll bind it there(define-key *top-map* (kbd "Print") '*my-screenshot-keymap*)
- Frame movement
Using Emacs keys
(define-key *top-map* (kbd "s-p") "move-focus up") (define-key *top-map* (kbd "s-n") "move-focus down") (define-key *top-map* (kbd "s-f") "move-focus right") (define-key *top-map* (kbd "s-b") "move-focus left")
Using numbers
(define-key *top-map* (kbd "s-M-0") "pull 0") (define-key *top-map* (kbd "s-M-1") "pull 1") (define-key *top-map* (kbd "s-M-2") "pull 2") (define-key *top-map* (kbd "s-M-3") "pull 3") (define-key *top-map* (kbd "s-M-4") "pull 4") (define-key *top-map* (kbd "s-M-5") "pull 5") (define-key *top-map* (kbd "s-M-6") "pull 6") (define-key *top-map* (kbd "s-M-7") "pull 7") (define-key *top-map* (kbd "s-M-8") "pull 8") (define-key *top-map* (kbd "s-M-9") "pull 9")
- Group Map
- Root Map
Function key
This keymap is for keyboard with no media keys. On my laptop the media key is mapped to the Function keys:
Function Key | Media Key | Note |
F1 | XF86Sleep | Already working |
F2 | XF86MonBrightnessDown | bound |
F3 | XF86MonBrightnessUp | bound |
F4 | XF86Display | unbound |
F5 | XF86Mail | bound |
F6 | XF86MyComputer | bound |
F7 | XF86AudioMute | bound |
F8 | XF86AudioLowerVolume | bound |
F9 | XF86AudioRaiseVolume | bound |
F10 | XF86AudioPrev | bound |
F11 | XF86AudioPlay | bound |
F12 | XF86AudioNext | bound |
- Media
(define-key *top-map* (kbd "s-F7") "exec pactl -- set-sink-mute @DEFAULT_SINK@ toggle") (define-key *top-map* (kbd "s-F8") "exec pactl -- set-sink-volume @DEFAULT_SINK@ -1%") (define-key *top-map* (kbd "s-F9") "exec pactl -- set-sink-volume @DEFAULT_SINK@ +1%") (define-key *top-map* (kbd "s-F10") "exec playerctl previous") (define-key *top-map* (kbd "s-F11") "exec playerctl play-pause") (define-key *top-map* (kbd "s-F12") "exec playerctl next")
- Brightneess
(define-key *top-map* (kbd "C-s-F2") "exec brightnessctl set 1%-") (define-key *top-map* (kbd "C-s-F3") "exec brightnessctl set +1%")
Remapped Keys
The define-remapped-keys
function can be configured to translate certain familiar top level keybindings to alternative key sequences that are understood by specific applications. One example is to force the Emacs keybinding C-n
and C-p
to move up and down for any specified application. To use the original application we can use send-raw-key
. And for temporarily disable all remapped keys, set the *remapped-keys-enabled-p*
to nil.
(define-remapped-keys `((,(lambda (win) (string-equal "Firefox" (window-class win))) ("C-n" . "Down") ("C-p" . "Up") ("C-f" . "Right") ("C-b" . "Left") ("C-v" . "Next") ("M-v" . "Prior") ("M-w" . "C-c") ("C-w" . "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-k" . ("C-S-End" "C-x")))))
CANCELLED imdb and duckduckgo
- State “CANCELLED” from
not used
This is from the default configuration, I don’t use this often.
(define-key *root-map* (kbd "M-s") "duckduckgo") (define-key *root-map* (kbd "i") "imdb")
Window rules
First we need to clear the previous rules in order to create a new one.
(clear-window-placement-rules)
Frame preferences
(define-frame-preference "Term" (1 t t :class "XTerm") (1 t t :class "Termite")) (define-frame-preference "Ardour" (0 t t :instance "ardour_editor" :type :normal) (0 t t :title "Ardour - Session Control") (0 nil nil :class "XTerm") (1 t nil :type :normal) (1 t t :instance "ardour_mixer") (2 t t :instance "jvmetro") (1 t t :instance "qjackctl") (3 t t :instance "qjackctl" :role "qjackctlMainForm")) (define-frame-preference "Dev" (2 t t :restore "emacs-editing-dump" :title "...xdvi") (2 t t :create "emacs-dump" :class "Emacs")) (define-frame-preference "Web" (3 t t :create "nyxt-dump" :class "Nyxt") (3 t t :create "firefox-dump" :class "firefox")) (define-frame-preference "Files" (4 t t :create "thunar-dump" :class "Thunar")) (define-frame-preference "Message" (5 t t :create "telegramdesktop-dump" :class "TelegramDesktop")) (define-frame-preference "Multimedia" (6 t t :create "cheese-dump" :class "Cheese") (6 t t :create "spotify-dump" :class "Spotify") (6 t t :create "picard-dump" :class "Picard") (6 t t :create "mpv-dump" :class "mpv") (6 t t :create "vlc-dump" :class "vlc") (6 t t :create "hakuneko-dump" :class "hakuneko-desktop") (6 t t :create "gimp-dump" :class "Gimp")) (define-frame-preference "Remote" (7 t t :create "virtmanager-dump" :class "Virt-manager") (7 t t :create "vncviewer-dump" :class "Vncviewer"))
Running Stumpwm
There are various methods of starting a stumpwm X session. The easiest way is to install the stumpwm package using your distro package manager, then you can run the executable stumpwm
using xinit, startx, or via display managers, since the package will likely to also provide a .desktop
file.
The other (recommended) way is to clone the official repository2, install the required dependencies, build and install stumpwm (usually into /usr/local/
so other user can also use it). Then start it by either startx or xinit, or create additional .desktop
file so the display manager can also use it.
Other method (the one I use), is to install stumpwm via sbcl
(ql:quickload "stumpwm")
And create a startstumpwm
file in /usr/local/bin
:
;;;; -*-lisp-*- (require :stumpwm) (stumpwm:stumpwm)
This file then called from ~/.xinitrc
:
exec sbcl --load /usr/local/bin/startstumpwm
or if using display manager create a .desktop
file:
[Desktop Entry] Name=StumpWM Type=XSession Exec=/usr/local/bin/sbcl --load /usr/local/bin/startstumpwm Name=StumpWM Comment=Stump window manager
The display manager itself must be configured to also pick up the /usr/local/share/xsession
directory, I prefer to put my own compiled packages in /usr/local
to avoid conflict with the ones installed by the distro package manager. Example in lightdm is to add:
sessions-directory=/usr/share/lightdm/sessions:/usr/share/xsessions:/usr/share/wayland-sessions:/usr/local/share/xsessions
in the [LightDM]
section.
This method, and the method using git repository, has the benefit of having all the dependencies in their latest version. This is useful when using sly or slime in emacs.