Lightcord is no longer maintained and its repository has been archived. Good night, sweet prince...
1469 lines
56 KiB
Org Mode
1469 lines
56 KiB
Org Mode
#+TITLE: StumpWM config
|
||
#+setupfile: headers
|
||
#+OPTIONS: auto-id:t
|
||
#+HTML_HEAD_EXTRA: <meta name="description" content="Phundrak's StumpWM config" />
|
||
#+HTML_HEAD_EXTRA: <meta property="og:title" content="Phundrak's StumpWM config" />
|
||
#+HTML_HEAD_EXTRA: <meta property="og:description" content="Description of the Stump Window Manager configuration files of Phundrak" />
|
||
#+property: header-args:emacs-lisp :tangle no :exports results :cache yes :noweb yes
|
||
|
||
[[file:img/stumpwm.png]]
|
||
|
||
* Introduction
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Introduction-9vda1z81u5j0
|
||
:END:
|
||
** What is StumpWM?
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Introduction-What-is-StumpWM-oyycyb91u5j0
|
||
:END:
|
||
[[https://stumpwm.github.io/][StumpWM]] is a tiling window manager inheriting from [[http://www.nongnu.org/ratpoison/][RatPoison]], written
|
||
entirely in [[https://common-lisp.net/][Common Lisp]] and compiled with [[http://www.sbcl.org/][SBCL]]. While it is not an
|
||
dynamic tiling window manager like [[file:awesome.org][Awesome]] is, its ability of managing
|
||
windows in frames and using keychords with keymaps like Emacs does is
|
||
a huge plus for me, not to mention the fact its configuration file is
|
||
written in Common Lisp, a general programming language, a bit like
|
||
Awesome. This makes it an [[file:Deprecated/i3.org][i3]] on steroids, sort of. It also uses a lot
|
||
of Emacs’ concepts, which is great for an Emacs user such as myself.
|
||
|
||
** Why not EXWM then?
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Introduction-Why-not-EXWM-then-670dyb91u5j0
|
||
:END:
|
||
Sometimes, some actions within Emacs are blocking actions, making the
|
||
computer not usable while the command runs. It also does not play nice
|
||
with video games (pun intended), which is also a negative point for
|
||
me. And I also find EXWM more confusing overall than StumpWM.
|
||
|
||
** What this file is for
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Introduction-What-this-file-is-for-pnyg92a1u5j0
|
||
:END:
|
||
This file has two main goals:
|
||
- This will be the actual source code of my StumpWM
|
||
configuration, thanks to Emacs’ org-mode, and thanks to org-mode’s
|
||
literate config capabilities.
|
||
|
||
Almost all of the visible source blocks if not all will be included
|
||
in my configuration files through tangling, which can be done in
|
||
Emacs when this file is opened through ~M-x org-babel-tangle~, which
|
||
will write my configuration files based on the source blocks present
|
||
in this document. This file is not only my config’s documentation,
|
||
it /*is*/ my configuration.
|
||
- Be my documentation on my StumpWM configuration. That way, I’ll
|
||
never forget which block of code does what.
|
||
|
||
And maybe, hopefully, someone could learn a thing or two if they
|
||
want to get into StumpWM but don’t know where to begin. You should
|
||
be able to read this document as a book, with each chapter dedicated
|
||
to a different aspect of StumpWM.
|
||
|
||
** Organization of my files
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Introduction-Organization-of-my-files-40vjne91u5j0
|
||
:END:
|
||
While I could make this file write everything to the same file (the
|
||
actual source will be in a single file after all), I find it easier to
|
||
debug StumpWM if everything’s split up. For now, my configuration
|
||
follows this architecture:
|
||
- ~init.el~ :: My main configuration file, glues everything together. It
|
||
loads all of my configuration files as well as some modules I find
|
||
useful;
|
||
- ~colors.lisp~ :: In this file are defined colors that will be used in
|
||
common in my ~theme.lisp~ and ~modeline.lisp~ files. Let’s make my code
|
||
DRY, or as I prefer to say, DRYD (/Don’t Repeat Yourself Dummy/).
|
||
- ~commands.lisp~ :: Lisp commands, in case I want to bind some
|
||
complicated actions to a keybind that is not just a simple shell
|
||
command;
|
||
- ~keybindings.lisp~ :: My list of keymaps and keybinds which make
|
||
StumpWM actually usable;
|
||
- ~modeline.lisp~ :: This defines the modeline, a concept taken from
|
||
Emacs which can display various information such as a list of
|
||
workspaces, including the current one;
|
||
- ~placement.lisp~ :: This file manages my workspaces and the default
|
||
placement of various windows;
|
||
- ~theme.lisp~ :: manages the color theme of StumpWM, the default
|
||
placement of some windows and StumpWM’s gaps.
|
||
|
||
* Init file
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Init-file-l3q4snd1u5j0
|
||
:header-args:lisp: :mkdirp :tangle ~/.stumpwm.d/init.lisp
|
||
:END:
|
||
As mentioned in [[https://stumpwm.github.io/git/stumpwm-git_1.html#Init-File][the documentation]], the configuration files can be in
|
||
different locations, but I chose an Emacs-like configuration: put
|
||
everything in ~~/.stumpwm.d/~. We begin by indicating quicklisp how to
|
||
properly initialize:
|
||
#+begin_src lisp
|
||
#-quicklisp
|
||
(let ((quicklisp-init (merge-pathnames "quicklisp/setup.lisp"
|
||
(user-homedir-pathname))))
|
||
(when (probe-file quicklisp-init)
|
||
(load quicklisp-init)))
|
||
#+end_src
|
||
|
||
Then, our first StumpWM-related code is declaring we are using the
|
||
~stumpwm~ package, and this is also our default package. This will allow
|
||
us to avoid using the prefix ~stumpwm:~ each time we are using a
|
||
function or a variable from this package.
|
||
#+begin_src lisp
|
||
(in-package :stumpwm)
|
||
(setf *default-package* :stumpwm)
|
||
#+end_src
|
||
|
||
Since I install StumpWM with my package manager (I use the AUR’s
|
||
~stumpwm-git~ package), StumpWM’s modules are installed to
|
||
~/usr/share/stupmwm/contrib/utils/~, let’s indicate that to StumpWM.
|
||
#+begin_src lisp
|
||
(set-module-dir "/usr/share/stupmwm/contrib/")
|
||
#+end_src
|
||
|
||
A startup message can be used when initializing StumpWM. For now,
|
||
let’s set it to ~nil~.
|
||
#+begin_src lisp
|
||
(setf *startup-message* nil)
|
||
#+end_src
|
||
|
||
The first thing I want to do after that is to set some decent cursor
|
||
pointer as well as get a bunch of stuff started. To see what’s in the
|
||
~autostart~ script, [[file:bin.org::#Autostart-a99e99e7][see here]].
|
||
#+begin_src lisp
|
||
(run-shell-command "xsetroot -cursor_name left_ptr")
|
||
(run-shell-command "autostart")
|
||
#+end_src
|
||
|
||
Now, we’ll load a couple of my custom files that will be described below:
|
||
#+name: first-loaded-files
|
||
| File to be loaded |
|
||
|-------------------|
|
||
| commands.lisp |
|
||
| placement.lisp |
|
||
| keybindings.lisp |
|
||
| theme.lisp |
|
||
| modeline.lisp |
|
||
|
||
#+name: gen-load-files
|
||
#+headers: :wrap src lisp
|
||
#+begin_src emacs-lisp :var files=first-loaded-files
|
||
(mapconcat (lambda (file)
|
||
(format "(load \"~/.stumpwm.d/%s\")" (car file)))
|
||
files
|
||
"\n")
|
||
#+end_src
|
||
|
||
This is equivalent to the Common Lisp code:
|
||
#+RESULTS[942558619eb0d0a3d694a7808d0b600f0bc4c14c]: gen-load-files
|
||
#+begin_src lisp
|
||
(load "~/.stumpwm.d/commands.lisp")
|
||
(load "~/.stumpwm.d/placement.lisp")
|
||
(load "~/.stumpwm.d/keybindings.lisp")
|
||
(load "~/.stumpwm.d/theme.lisp")
|
||
(load "~/.stumpwm.d/modeline.lisp")
|
||
#+end_src
|
||
|
||
Once the modeline file is loaded, let’s indicate StumpWM to activate
|
||
it:
|
||
#+begin_src lisp
|
||
(when *initializing*
|
||
(mode-line))
|
||
#+end_src
|
||
|
||
Another thing I want to set is how focus is linked to my mouse: only
|
||
on click. I /HATE/ it when focus follows my mouse like some damn dog
|
||
after its ball. Also, the meta key will be used to move floating
|
||
windows.
|
||
#+begin_src lisp
|
||
(setf *mouse-focus-policy* :click
|
||
,*float-window-modifier* :META)
|
||
#+end_src
|
||
|
||
Next, some modules will be loaded from the ~stumpwm-contrib~ package
|
||
(which is included in ~stumpwm-git~ in the AUR). Here is a short list
|
||
including a short description of what they are for:
|
||
#+name: loaded-modules
|
||
| Module Name | Why It Is Loaded |
|
||
|-----------------+------------------------------------------------------------|
|
||
| alert-me | Creates notifications, can also create timed notifications |
|
||
| beckon | Bring the mouse cursor to the current window |
|
||
| end-session | Gracefully end programs when ending user session |
|
||
| globalwindows | Navigate between windows from all workspaces |
|
||
| stump-backlight | Native management of backlight in StumpWM |
|
||
| urgentwindows | Get urgent windows |
|
||
|
||
#+name: gen-load-modules
|
||
#+headers: :wrap src lisp
|
||
#+begin_src emacs-lisp :var modules=loaded-modules
|
||
(mapconcat (lambda (module)
|
||
(format "(load-module \"%s\")" (car module)))
|
||
modules
|
||
"\n")
|
||
#+end_src
|
||
|
||
#+RESULTS[508e36f9747f1da901bbee63582416a8a6ba2c2f]: gen-load-modules
|
||
#+begin_src lisp
|
||
(load-module "alert-me")
|
||
(load-module "beckon")
|
||
(load-module "end-session")
|
||
(load-module "globalwindows")
|
||
(load-module "stump-backlight")
|
||
(load-module "urgentwindows")
|
||
#+end_src
|
||
|
||
Finally, we can notify the user everything is ready.
|
||
#+begin_src lisp
|
||
(setf *startup-message* "StumpWM is ready!")
|
||
#+end_src
|
||
|
||
And it’s done! We can now move on to the creation of the other CLisp files.
|
||
|
||
* Commands
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Commands-1wagy001v5j0
|
||
:header-args:lisp: :mkdirp :tangle ~/.stumpwm.d/commands.lisp
|
||
:END:
|
||
The first command I declare in this file is a command that will avoid
|
||
me invoking too many Firefox instances. Either Firefox is not already
|
||
running and an instance is launched, or one already is and we are
|
||
brought to it. This is done like so:
|
||
#+begin_src lisp
|
||
(defcommand firefox () ()
|
||
"Run or raise Firefox."
|
||
(run-or-raise "firefox" '(:class "Firefox") t nil))
|
||
#+end_src
|
||
|
||
Next, this command will not only close the current window, but it will
|
||
also close the current frame.
|
||
#+begin_src lisp
|
||
(defcommand delete-window-and-frame () ()
|
||
"Delete the current frame with its window."
|
||
(delete-window)
|
||
(remove-split))
|
||
#+end_src
|
||
|
||
The two following commands will create a new frame to the right and
|
||
below the current frame respectively, then focus it.
|
||
#+begin_src lisp
|
||
(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))
|
||
#+end_src
|
||
|
||
Now, let’s create a command for invoking the terminal, optionally with
|
||
a program.
|
||
#+begin_src lisp
|
||
(defcommand term (&optional program) ()
|
||
"Invoke a terminal, possibly with a @arg{program}."
|
||
(run-shell-command (if program
|
||
(format nil "kitty ~A" program)
|
||
"kitty")))
|
||
#+end_src
|
||
|
||
And done! Next!
|
||
|
||
* Colors
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Colors-w5493d01v5j0
|
||
:header-args:lisp: :mkdirp :tangle ~/.stumpwm.d/colors.lisp
|
||
:END:
|
||
If you’ve taken a look at the rest of my dotfiles, you may have
|
||
noticed I really like the [[https://www.nordtheme.com/][Nord theme]]. No wonder we can find it here
|
||
again! Here is a small table listing the Nord colors:
|
||
#+name: nord-colors
|
||
| Name | Value |
|
||
|--------+---------|
|
||
| nord0 | #2e3440 |
|
||
| nord1 | #3b4252 |
|
||
| nord2 | #434c5e |
|
||
| nord3 | #4c566a |
|
||
| nord4 | #d8dee9 |
|
||
| nord5 | #e5e9f0 |
|
||
| nord6 | #eceff4 |
|
||
| nord7 | #8fbcbb |
|
||
| nord8 | #88c0d0 |
|
||
| nord9 | #81a1c1 |
|
||
| nord10 | #5e81ac |
|
||
| nord11 | #bf616a |
|
||
| nord12 | #d08770 |
|
||
| nord13 | #ebcb8b |
|
||
| nord14 | #a3be8c |
|
||
| nord15 | #b48ead |
|
||
|
||
I’ll prefix the variables’ name with ~phundrak-~ just in case it might
|
||
conflict with another package I might use in the future, so the CLisp
|
||
code looks like so:
|
||
#+name: gen-colors
|
||
#+headers: :wrap src lisp
|
||
#+begin_src emacs-lisp :var colors=nord-colors
|
||
(mapconcat (lambda (color)
|
||
(format "(defvar phundrak-%s \"%s\")" (car color) (cadr color)))
|
||
colors
|
||
"\n")
|
||
#+end_src
|
||
|
||
#+RESULTS[08b3db7a2b4f31d641bcd096ff265eae06879244]: gen-colors
|
||
#+begin_src lisp
|
||
(defvar phundrak-nord0 "#2e3440")
|
||
(defvar phundrak-nord1 "#3b4252")
|
||
(defvar phundrak-nord2 "#434c5e")
|
||
(defvar phundrak-nord3 "#4c566a")
|
||
(defvar phundrak-nord4 "#d8dee9")
|
||
(defvar phundrak-nord5 "#e5e9f0")
|
||
(defvar phundrak-nord6 "#eceff4")
|
||
(defvar phundrak-nord7 "#8fbcbb")
|
||
(defvar phundrak-nord8 "#88c0d0")
|
||
(defvar phundrak-nord9 "#81a1c1")
|
||
(defvar phundrak-nord10 "#5e81ac")
|
||
(defvar phundrak-nord11 "#bf616a")
|
||
(defvar phundrak-nord12 "#d08770")
|
||
(defvar phundrak-nord13 "#ebcb8b")
|
||
(defvar phundrak-nord14 "#a3be8c")
|
||
(defvar phundrak-nord15 "#b48ead")
|
||
#+end_src
|
||
|
||
And with that we’re done!
|
||
|
||
* Modeline
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Modeline-g2ofyw01v5j0
|
||
:header-args:lisp: :mkdirp :tangle ~/.stumpwm.d/modeline.lisp
|
||
:END:
|
||
The modeline is pretty easy. First, let’s load the ~colors.lisp~ file we just created:
|
||
#+begin_src lisp
|
||
(load "~/.stumpwm.d/colors.lisp")
|
||
#+end_src
|
||
|
||
Next, we can set some colors for the modeline. Let’s set the
|
||
background of the modeline to Nord1 and the foreground to Nord5, I
|
||
think this is a pretty good combination.
|
||
#+begin_src lisp
|
||
(setf *mode-line-background-color* phundrak-nord1
|
||
,*mode-line-foreground-color* phundrak-nord5)
|
||
#+end_src
|
||
|
||
We /could/ also use some borders in the modeline. But we won’t. Let’s
|
||
still set its color to Nord1, just in case.
|
||
#+begin_src lisp
|
||
(setf *mode-line-border-color* phundrak-nord1
|
||
,*mode-line-border-width* 0)
|
||
#+end_src
|
||
|
||
The timeout of the modeline indicates how often it refreshes in
|
||
seconds. I think one second is good.
|
||
#+begin_src lisp
|
||
(setf *mode-line-timeout* 1)
|
||
#+end_src
|
||
|
||
Next we get to the content of the modeline. This format follows the
|
||
format indicated in the manpage of ~date~.
|
||
#+begin_src lisp
|
||
(setf *time-modeline-string* "%F %H:%M")
|
||
#+end_src
|
||
|
||
Let’s also indicate how the groupname is displayed.
|
||
#+begin_src lisp
|
||
(setf *group-format* "%t")
|
||
#+end_src
|
||
|
||
The window format should display first its window number, then its
|
||
titled, limited to 30 characters.
|
||
#+begin_src lisp
|
||
(setf *window-format* "%n: %t")
|
||
#+end_src
|
||
|
||
Here are some modules that we will load for the modeline:
|
||
#+name: modeline-modules
|
||
| Module Name | Why It Is Loaded |
|
||
|------------------+--------------------------------------------------|
|
||
| battery-portable | Get information on the battery level of a laptop |
|
||
| cpu | Get the CPU usage of the computer |
|
||
| mem | Get the memory usage of the computer |
|
||
| wifi | Display information about Wifi connectivity |
|
||
|
||
#+name: gen-load-modeline-modules
|
||
#+headers: :wrap src lisp
|
||
#+begin_src emacs-lisp :var modules=modeline-modules
|
||
(mapconcat (lambda (module)
|
||
(format "(load-module \"%s\")" (car module)))
|
||
modules
|
||
"\n")
|
||
#+end_src
|
||
|
||
#+RESULTS[125d7bbaa15ee28f0baf4cad59c4742ac372853b]: gen-load-modeline-modules
|
||
#+begin_src lisp
|
||
(load-module "battery-portable")
|
||
(load-module "cpu")
|
||
(load-module "mem")
|
||
(load-module "wifi")
|
||
#+end_src
|
||
|
||
#+begin_src lisp
|
||
(setq cpu::*cpu-modeline-fmt* "%c"
|
||
mem::*mem-modeline-fmt* "%a %p"
|
||
wifi:*wifi-modeline-fmt* "%e %P"
|
||
wifi:*use-colors* nil
|
||
,*mode-line-highlight-template* "<~A>"
|
||
,*hidden-window-color* "^**")
|
||
#+end_src
|
||
|
||
We can indicate what to display in our modeline. Each formatter will
|
||
be separated by a Powerline separator with the code point ~0xE0B0~ in
|
||
the font I am using (see §[[#Theme-Fonts-28pc8141v5j0]]).
|
||
#+name: modeline-format
|
||
| Formatter | What it does | Command? |
|
||
|-----------+-------------------------------------------------------+----------|
|
||
| ~%g~ | Display list of groups | |
|
||
| ~%W~ | Display list of windows in the current group and head | |
|
||
| ~^>~ | Rest of the modeline will be aligned to the right | |
|
||
| ~mu-unread~ | Display number of unread emails | yes |
|
||
| ~%I~ | Display Wifi status | |
|
||
| ~%C~ | Display CPU status | |
|
||
| ~%M~ | Display RAM status | |
|
||
| ~%B~ | Display battery status | |
|
||
| ~%d~ | Display date | |
|
||
|
||
#+name: modeline-format-gen
|
||
#+begin_src emacs-lisp :var elements=modeline-format :exports none
|
||
(mapcar (lambda (element)
|
||
(cons (format "\"%s\""
|
||
(string-replace (regexp-quote "~")
|
||
""
|
||
(car element)))
|
||
(string= "yes" (caddr element))))
|
||
elements)
|
||
#+end_src
|
||
|
||
#+RESULTS[5d43e832fca88ddfffecd2a55d63bbb767e2e315]: modeline-format-gen
|
||
: (("%g") ("%W") ("^>") ("mu-unread" . t) ("%I") ("%C") ("%M") ("%B") ("%d"))
|
||
|
||
#+begin_src lisp :noweb yes
|
||
(defun generate-modeline (elements &optional not-invertedp)
|
||
"Generate a modeline for StumpWM.
|
||
ELEMENTS should be a list of `cons'es which `car' is the modeline
|
||
formatter or the shell command to run, and their `cdr' is either nil
|
||
when the `car' is a formatter and t when it is a shell command."
|
||
(when elements
|
||
(cons (if not-invertedp
|
||
(format nil
|
||
" ^(:fg \"~A\")^(:bg \"~A\")^f1^f0^(:fg \"~A\") "
|
||
phundrak-nord1
|
||
phundrak-nord15
|
||
phundrak-nord3)
|
||
(format nil
|
||
" ^(:fg \"~A\")^(:bg \"~A\")^f1^f0^** "
|
||
phundrak-nord15
|
||
phundrak-nord1))
|
||
(let* ((current-element (car elements))
|
||
(formatter (car current-element))
|
||
(commandp (cdr current-element)))
|
||
(cons (if commandp
|
||
`(:eval (run-shell-command ,formatter t))
|
||
(format nil "~A" formatter))
|
||
(generate-modeline (cdr elements) (not not-invertedp)))))))
|
||
|
||
(defcommand reload-modeline () ()
|
||
"Reload modeline."
|
||
(setf *screen-mode-line-format*
|
||
(cdr (generate-modeline '<<modeline-format-gen()>>))))
|
||
|
||
(reload-modeline)
|
||
#+end_src
|
||
|
||
This variable as you can see is a list of elements, although here I am
|
||
only using one string. But it is completely possible to insert some
|
||
CLisp code in here that returns some string if the user needs some
|
||
code to return data that cannot be easily accesible otherwise. I might
|
||
add some at some point, but not today yet.
|
||
|
||
** TODO Investigate why ~stumptray~ doesn’t work :noexport:
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Modeline-Investigate-why-stumptray-doesn-t-work-0juh13g0m6j0
|
||
:END:
|
||
|
||
# Also, let’s enable a system tray.
|
||
# #+begin_src lisp
|
||
# (load-module "stumptray")
|
||
# (stumptray::stumptray)
|
||
# #+end_src
|
||
|
||
# Don’t forget to run src_lisp[:exports code]{(ql:quickload :xembed)} in
|
||
# ~sbcl~ at least once to install its dependencies.
|
||
|
||
* Groups and placement
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Placement-mhc3sr21v5j0
|
||
:header-args:lisp: :mkdirp :tangle ~/.stumpwm.d/placement.lisp :noweb yes
|
||
:END:
|
||
I’ve been used to ten groups, or workspaces, or tags, since I began
|
||
using tiling window managers. I shall then continue this habit. Here
|
||
is the list of groups I will be using:
|
||
#+name: list-groups
|
||
| Groups | Number | Windows | Type |
|
||
|---------+--------+---------------------+---------|
|
||
| [DEV] | 1 | Emacs, Virt-manager | |
|
||
| [SYS] | 2 | | Dynamic |
|
||
| [WWW] | 3 | Firefox | |
|
||
| [FILES] | 4 | Nemo | |
|
||
| [MEDIA] | 5 | Gimp | |
|
||
| [SOC] | 6 | Signal, discord | |
|
||
| [PRIV] | 7 | | |
|
||
| [GAM] | 8 | Steam | |
|
||
|
||
#+name: gen-groups
|
||
#+headers: :exports none
|
||
#+begin_src emacs-lisp :var groups=list-groups
|
||
(let ((make-group (lambda (group &optional first-p)
|
||
(let ((group-name (car group))
|
||
(group-type (nth 3 group)))
|
||
(format "(%s \"%s\")"
|
||
(if first-p
|
||
"grename"
|
||
(pcase group-type
|
||
("Dynamic" "gnewbg-dynamic")
|
||
("Floating" "gnewbg-float")
|
||
(otherwise "gnewbg")))
|
||
group-name)))))
|
||
(string-join `(,(funcall make-group (car groups) t)
|
||
,@(mapcar (lambda (group)
|
||
(funcall make-group group))
|
||
(cdr groups)))
|
||
"\n"))
|
||
#+end_src
|
||
|
||
#+RESULTS[212d0f619fcfa8df9da1b051a3855491e53431b2]: gen-groups
|
||
: (grename "[DEV]")
|
||
: (gnewbg-dynamic "[SYS]")
|
||
: (gnewbg "[WWW]")
|
||
: (gnewbg "[FILES]")
|
||
: (gnewbg "[MEDIA]")
|
||
: (gnewbg "[SOC]")
|
||
: (gnewbg "[PRIV]")
|
||
: (gnewbg "[GAM]")
|
||
|
||
Groups are specified this way:
|
||
#+begin_src lisp
|
||
(when *initializing*
|
||
<<gen-groups()>>)
|
||
#+end_src
|
||
|
||
By default, if nothing is specified as per the group type, my groups
|
||
are manual tiling groups. Otherwise, as you can see above, they can
|
||
also be dynamic tiling groups or floating groups.
|
||
|
||
Next, let’s make sure no previous window placement rule is in place,
|
||
this will avoid unexpected and hard-to-debug behavior.
|
||
#+begin_src lisp
|
||
(clear-window-placement-rules)
|
||
#+end_src
|
||
|
||
As you can see in the table [[list-groups]] above, I also indicated my
|
||
window placement preferences. For now, they all rely on the window’s
|
||
class, so it will be pretty straightforward to the corresponding code.
|
||
#+name: gen-rules
|
||
#+headers: :wrap src lisp
|
||
#+begin_src emacs-lisp :var rules=list-groups
|
||
(require 'seq)
|
||
(let ((output "")
|
||
(rules (seq-filter (lambda (rule) rule)
|
||
(mapcar (lambda (line)
|
||
(let ((classes (caddr line)))
|
||
(unless (string= "" classes)
|
||
(cons
|
||
(split-string classes "," t "[[:space:]]*")
|
||
(car line)))))
|
||
rules))))
|
||
(progn
|
||
(seq-do (lambda (rule)
|
||
(let ((classes (car rule))
|
||
(group (cdr rule)))
|
||
(dolist (class classes)
|
||
(setf output (format "%s\n%s"
|
||
`(define-frame-preference ,(format "\"%s\"" group)
|
||
(nil t t :class ,(format "\"%s\"" class)))
|
||
output)))))
|
||
rules)
|
||
output))
|
||
#+end_src
|
||
|
||
This can be written this way:
|
||
#+RESULTS[4affc294b6e643c957fceec5e084fae4933ac7f3]: gen-rules
|
||
#+begin_src lisp
|
||
(define-frame-preference "[GAM]" (nil t t :class "Steam"))
|
||
(define-frame-preference "[SOC]" (nil t t :class "discord"))
|
||
(define-frame-preference "[SOC]" (nil t t :class "Signal"))
|
||
(define-frame-preference "[MEDIA]" (nil t t :class "Gimp"))
|
||
(define-frame-preference "[FILES]" (nil t t :class "Nemo"))
|
||
(define-frame-preference "[WWW]" (nil t t :class "Firefox"))
|
||
(define-frame-preference "[DEV]" (nil t t :class "Virt-manager"))
|
||
(define-frame-preference "[DEV]" (nil t t :class "Emacs"))
|
||
#+end_src
|
||
|
||
Now, let’s do some automation. The reason why I want the first group
|
||
to be a dynamic group is so that all terminal windows I’ll have there
|
||
will be automatically arranged. If I switch to the ~[SYS]~ group, I want
|
||
three terminal windows to open by default:
|
||
- an ~htop~ window
|
||
- and two terminals
|
||
This can be done like so:
|
||
#+begin_src lisp
|
||
(defun my-term-init (current-group _last-group)
|
||
"Create terminals in the first group when none are already there."
|
||
(let ((term-group (select-group (current-screen) "2"))
|
||
(windows (group-windows current-group)))
|
||
(when (and (equal current-group term-group)
|
||
(null windows))
|
||
(unless (= 1 (length (group-frames current-group)))
|
||
(only))
|
||
(term "htop")
|
||
(term)
|
||
(term))))
|
||
#+end_src
|
||
|
||
Let’s add a hook for that now:
|
||
#+begin_src lisp
|
||
(add-hook *focus-group-hook* 'my-term-init)
|
||
#+end_src
|
||
|
||
By the way, dynamic groups should have a split ratio of half of the
|
||
available space.
|
||
#+begin_src lisp
|
||
(setf *dynamic-group-master-split-ratio* 1/2)
|
||
#+end_src
|
||
|
||
* Theme
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Theme-1x3c2u31v5j0
|
||
:header-args:lisp: :mkdirp :tangle ~/.stumpwm.d/theme.lisp :noweb yes
|
||
:END:
|
||
As in the modeline file, the first thing we’ll do is to load our colors.
|
||
#+begin_src lisp
|
||
(load "~/.stumpwm.d/colors.lisp")
|
||
#+end_src
|
||
|
||
We can now go onto more serious business.
|
||
|
||
** Fonts
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Theme-Fonts-28pc8141v5j0
|
||
:END:
|
||
This gave me quite the headache when I tried to set this up: in order
|
||
to use TTF or OTF fonts, we need to use the ~ttf-fonts~ module which
|
||
relies on the ~clx-truetype~ library. A few years back, it should have
|
||
been possible to get it installed with a call to src_lisp[:exports
|
||
code]{(ql:quickload :clx-truetype)}, but it is no longer available!
|
||
There’s a quickfix available while we wait for ~clx-truetype~ to be once
|
||
again available: clone it in quicklisp’s local projects. You will
|
||
obviously need to have quicklisp installed (for that, follow the
|
||
[[https://www.quicklisp.org/beta/#installation][official instructions]]), then execute the following shell commands:
|
||
#+begin_src sh :dir ~/quicklisp/local-projects
|
||
cd ~/quicklisp/local-projects/
|
||
git clone https://github.com/lihebi/clx-truetype.git
|
||
#+end_src
|
||
|
||
This will make ~clx-truetype~ available to quicklisp, and you can run
|
||
again src_lisp[:exports code]{(ql:quickload :clx-truetype)} without an
|
||
issue (running it again is necessary to install its dependencies).
|
||
|
||
Now that this is out of the way, let’s add two lines so we can use TTF
|
||
fonts:
|
||
#+begin_src lisp
|
||
(ql:quickload :clx-truetype)
|
||
(load-module "ttf-fonts")
|
||
#+end_src
|
||
|
||
The documentation says we should be able to also use OTF fonts, but so
|
||
far I’ve had no luck loading one.
|
||
|
||
Loading more than one font to use
|
||
some fallback fonts also doesn’t seem to work, unlike specified in the
|
||
documentation (I wanted to use a CJK font, but it doesn’t appear to
|
||
work), we need to manually change the font used which isn’t very
|
||
user-friendly, especially if you might have CJK characters appear in
|
||
otherwise regular text.
|
||
|
||
Something that didn’t click immediately for me (and I think StumpWM’s
|
||
documentation on this could be improved) is that ~set-font~ can be used
|
||
to set either one main font for StumpWM, as one might guess reading
|
||
the documentation --- or you can set a list of them! And this is
|
||
great, since my main font does not support some characters I regularly
|
||
have in my windows’ title, such as CJK characters! However, be aware
|
||
*the second font and further aren’t fallback fonts*. They are additional
|
||
fonts you can switch to manually through the use of ~^f<n>~ (~<n>~ being
|
||
the desired’s font index in the 0-indexed font list). But if a font
|
||
cannot render a character, it will simply display an empty rectangle
|
||
instead of falling back to another font. That’s annoying… Here is my
|
||
list of fonts I want loaded:
|
||
#+name: list-fonts
|
||
| Family | Subfamily | Size |
|
||
|--------------------------------+-----------+------|
|
||
| Unifont-JP | Regular | 10 |
|
||
| DejaVu Sans Mono for Powerline | Book | 8.5 |
|
||
|
||
#+name: gen-fonts
|
||
#+headers: :wrap src lisp
|
||
#+begin_src emacs-lisp :var fonts=list-fonts
|
||
(format "(set-font `(%s))"
|
||
(mapconcat (lambda (font)
|
||
(let ((family (nth 0 font))
|
||
(subfamily (nth 1 font))
|
||
(size (nth 2 font)))
|
||
(format ",%s" `(make-instance 'xft:font
|
||
:family ,(format "\"%s\"" family)
|
||
:subfamily ,(format "\"%s\"" subfamily)
|
||
:size ,size
|
||
:antialias t))))
|
||
fonts
|
||
"\n "))
|
||
#+end_src
|
||
|
||
The code equivalent of this table can be seen below:
|
||
#+RESULTS[2bcc2bbfd79a25c20b4dfa9d9867b91797089ffc]: gen-fonts
|
||
#+begin_src lisp
|
||
(set-font `(,(make-instance 'xft:font :family "Unifont-JP" :subfamily "Regular" :size 10 :antialias t)
|
||
,(make-instance 'xft:font :family "DejaVu Sans Mono for Powerline" :subfamily "Book" :size 8.5 :antialias t)))
|
||
#+end_src
|
||
|
||
As far as I know, Unifont is the only font I’ve tested that displays
|
||
monospaced Japanese characters in StumpWM. I tried DejaVu, IBM Plex,
|
||
and a couple of others but only this one works correctly. DejaVu is
|
||
here for the Powerline separator. If you know of another monospaced
|
||
font that displays Japanese characters, or even better CJK characters,
|
||
please tell me! My email address is at the bottom of this webpage.
|
||
|
||
** Colors
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Theme-Colors-ctlclb51v5j0
|
||
:END:
|
||
We can now set a couple of colors for StumpWM. Not that we will see
|
||
them often since I don’t like borders on my windows, but in case I
|
||
want to get them back, they’ll be nice to have.
|
||
#+begin_src lisp
|
||
(set-border-color phundrak-nord1)
|
||
(set-focus-color phundrak-nord1)
|
||
(set-unfocus-color phundrak-nord3)
|
||
(set-float-focus-color phundrak-nord1)
|
||
(set-float-unfocus-color phundrak-nord3)
|
||
#+end_src
|
||
|
||
Let’s also set the colors of the message and input windows:
|
||
#+begin_src lisp
|
||
(set-fg-color phundrak-nord4)
|
||
(set-bg-color phundrak-nord1)
|
||
#+end_src
|
||
|
||
As I said, I don’t like borders, so I’ll remove them. I’ll still keep
|
||
the window’s title bar available when it’s floating, and this is also
|
||
where I can set the format of its title: its number as well as its
|
||
name, limited to thirty characters.
|
||
#+begin_src lisp
|
||
(setf *normal-border-width* 0
|
||
,*float-window-border* 0
|
||
,*float-window-title-height* 15
|
||
,*window-border-style* :none
|
||
,*window-format* "%n:%t")
|
||
#+end_src
|
||
|
||
** Message and Input Windows
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Theme-Message-and-Input-Windows-jxwhch51v5j0
|
||
:END:
|
||
The Input windows as well as the message windows should both be at the
|
||
top of my screen. And I believe a padding of five pixels for the
|
||
message windows is good.
|
||
#+begin_src lisp
|
||
(setf *input-window-gravity* :top
|
||
,*message-window-padding* 10
|
||
,*message-window-y-padding* 10
|
||
,*message-window-gravity* :top)
|
||
#+end_src
|
||
|
||
** Gaps Between Frames
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Theme-Gaps-Between-Frames-bqngnt51v5j0
|
||
:END:
|
||
I love gaps. When I was using i3, I used the ~i3-gaps~ package, not just
|
||
plain ~i3~. In Awesome, I still have gaps. And in StumpWM, I shall still
|
||
use gaps. In order to use them, let’s load a module dedicated to gaps
|
||
in StumpWM:
|
||
#+begin_src lisp
|
||
(load-module "swm-gaps")
|
||
#+end_src
|
||
|
||
Now that this is done, I can now set some variables bound to this
|
||
package.
|
||
#+begin_src lisp
|
||
(setf swm-gaps:*head-gaps-size* 0
|
||
swm-gaps:*inner-gaps-size* 5
|
||
swm-gaps:*outer-gaps-size* 10)
|
||
#+end_src
|
||
|
||
Finally, let’s enable our gaps:
|
||
#+begin_src lisp
|
||
(when *initializing*
|
||
(swm-gaps:toggle-gaps))
|
||
#+end_src
|
||
|
||
* Keybinds
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Keybinds-c6wgf961v5j0
|
||
:header-args:lisp: :mkdirp :tangle ~/.stumpwm.d/keybindings.lisp :noweb yes
|
||
:END:
|
||
Buckle up, this chapter is going to be *long*, because me loves LOTS of keybinds.
|
||
|
||
First, let’s declare again we are using the default package ~stumpwm~:
|
||
#+begin_src lisp
|
||
(in-package :stumpwm)
|
||
#+end_src
|
||
|
||
This will avoid us always repeating ~stumpwm:define-key~ or ~stumpwm:kbd~
|
||
instead of simply ~define-key~ and ~kbd~.
|
||
|
||
StumpWM behaves a bit like Emacs in terms of keybinds. You have
|
||
keymaps, which are a collection of keybinds, which in turn call CLisp
|
||
functions. However, unlike Emacs, you have to declare a lot of
|
||
keymaps, because StumpWM cannot (/yet/) understand keybinds such as
|
||
src_lisp[:exports code]{(kbd "C-x c l")}, so you end up creating a
|
||
keybind to a keymap which contains other keybinds, which might contain
|
||
a couple of keybinds to other keymaps. I hope this will get improved
|
||
soon.
|
||
|
||
There are also two keymaps you need to be aware of:
|
||
- ~*top-map*~ :: This is the keymap available litteraly everywhere. With
|
||
this keymap, you can emulate most of your keybinds you have in other
|
||
window managers. For instance, I cannot live without ~s-RET~ for
|
||
creating new shells, so I’ll bind it to ~*top-map*~. But it’s good
|
||
practice to avoid polluting ~*top-map*~ with too many keybinds.
|
||
- ~*root-map*~ :: This keymap is the default keymap that is already
|
||
somewhat populated. It is available after hitting the prefix key set
|
||
with ~set-prefix-key~ which we will see just below.
|
||
|
||
It is interesting to note that once you entered any keymap, except
|
||
~*top-map*~, if you hit ~?~ you will see the list of available keybinds.
|
||
I’d like it if something similar to ~general~ in Emacs too could be
|
||
implemented: give any arbitrary name to the keybind you just declared
|
||
which would be displayed instead of the actual function or keymap
|
||
called by keybind. It would be nicer to see ~frames~ rather than
|
||
~*my-frames-management-keymap*~.
|
||
|
||
Anyways, as mentionned above, ~*root-map*~ is already pre-populated with
|
||
some cool stuff for you, and you can access it with a prefix which is
|
||
by default ~C-t~. But if this doesn’t suit you, you can always redefine
|
||
it with ~set-prefix-key~. I personally like to have my space key as a
|
||
leader key, but in order to not have it conflict with Emacs, I also
|
||
need to press the super key too.
|
||
#+begin_src lisp
|
||
(set-prefix-key (kbd "s-SPC"))
|
||
#+end_src
|
||
|
||
Also, let’s enable ~which-key~:
|
||
#+begin_src lisp
|
||
(which-key-mode)
|
||
#+end_src
|
||
|
||
Lastly, before we get more into details, keep in mind that I use the
|
||
[[https://bepo.fr][bépo]] layout, as I often say in my different documents. This means the
|
||
characters found in the numbers’ row when pressing shift are actually
|
||
the numbers themselves. Also, some characters are not recognized as is
|
||
by ~kbd~, so we need to use a special name (not fun…). Below are the
|
||
following characters:
|
||
#+name: number-to-char-table
|
||
| Number | Character | Lisp Character |
|
||
|--------+-----------+----------------|
|
||
| 1 | ~"~ | |
|
||
| 2 | ~«~ | ~guillemotleft~ |
|
||
| 3 | ~»~ | ~guillemotright~ |
|
||
| 4 | ~(~ | |
|
||
| 5 | ~)~ | |
|
||
| 6 | ~@~ | |
|
||
| 7 | ~+~ | |
|
||
| 8 | ~-~ | |
|
||
| 9 | ~/~ | |
|
||
| 0 | ~*~ | |
|
||
|
||
So if you see any weird keybind involving these characters, this is
|
||
because of my layout.
|
||
|
||
** Applications
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Keybinds-Applications-2t512k00w5j0
|
||
:END:
|
||
When I speak about applications, I speak about programs and scripts in
|
||
general. With these keymaps, I can launch programs I often have use
|
||
for, but I can also launch some scripts as well as take screenshots.
|
||
|
||
First, let’s create my ~rofi~ scripts keymap.
|
||
#+name: rofi-scripts
|
||
#+caption: ~*my-rofi-keymap*~
|
||
| Keychord | Function |
|
||
|----------+-----------------------------------------------|
|
||
| ~a~ | ~exec awiki~ |
|
||
| ~r~ | ~exec rofi -combi-modi drun,window -show combi~ |
|
||
| ~s~ | ~exec rofi -show ssh~ |
|
||
| ~p~ | ~exec rofi-pass -t~ |
|
||
| ~P~ | ~exec rofi-pass~ |
|
||
| ~e~ | ~exec rofi-emoji~ |
|
||
| ~m~ | ~exec rofi-mount~ |
|
||
| ~u~ | ~exec rofi-umount~ |
|
||
| ~w~ | ~exec wacom-setup~ |
|
||
| ~y~ | ~exec ytplay~ |
|
||
| ~Y~ | ~exec rofi-ytdl~ |
|
||
|
||
Here’s the equivalent in Common Lisp.
|
||
#+begin_src lisp
|
||
(defvar *my-rofi-keymap*
|
||
(let ((m (make-sparse-keymap)))
|
||
<<keybinds-gen(map="m", keybinds=rofi-scripts)>>
|
||
m))
|
||
#+end_src
|
||
|
||
Let’s also create a keymap for screenshots.
|
||
#+name: screenshot-keymap
|
||
#+caption: ~*my-screenshot-keymap*~
|
||
| Keychord | Function |
|
||
|----------+-------------------------------------------------------|
|
||
| ~d~ | ~exec scrot -d 3 -e 'mv $f ~/Pictures/Screenshots'~ |
|
||
| ~s~ | ~exec scrot -e 'mv $f ~/Pictures/Screenshots'~ |
|
||
| ~S~ | ~exec scrot -s -e 'mv $f ~/Pictures/Screenshots'~ |
|
||
| ~g~ | ~exec scrot -e 'gimp $f; mv $f ~/Pictures/Screenshots'~ |
|
||
|
||
Here’s the equivalent in Common Lisp.
|
||
#+begin_src lisp
|
||
(defvar *my-screenshot-keymap*
|
||
(let ((m (make-sparse-keymap)))
|
||
<<keybinds-gen(map="m", keybinds=screenshot-keymap)>>
|
||
m))
|
||
#+end_src
|
||
|
||
We can now define our applications keymap which will reference both
|
||
the above keymaps.
|
||
#+name: application-keymap
|
||
#+caption: ~*my-applications-keymap*~
|
||
| Keychord | Function |
|
||
|----------+-------------------------|
|
||
| ~b~ | ~firefox~ |
|
||
| ~d~ | ~exec discord-canary~ |
|
||
| ~e~ | ~exec emacsclient -c~ |
|
||
| ~g~ | ~exec gimp~ |
|
||
| ~n~ | ~exec nemo~ |
|
||
| ~r~ | ~'*my-rofi-keymap*~ |
|
||
| ~s~ | ~'*my-screenshot-keymap*~ |
|
||
| ~w~ | ~exec select-pape~ |
|
||
|
||
This translates to:
|
||
#+begin_src lisp
|
||
(defvar *my-applications-keymap*
|
||
(let ((m (make-sparse-keymap)))
|
||
<<keybinds-gen(map="m", keybinds=application-keymap)>>
|
||
m))
|
||
#+end_src
|
||
|
||
The application keymap can now be bound to the root map like so:
|
||
#+begin_src lisp
|
||
(define-key *root-map* (kbd "a") '*my-applications-keymap*)
|
||
#+end_src
|
||
|
||
I will also bind to the top map ~s-RET~ in order to open a new terminal
|
||
window. The screenshot keymap is also bound to the ScreenPrint key.
|
||
#+begin_src lisp
|
||
(define-key *top-map* (kbd "s-RET") "term")
|
||
(define-key *top-map* (kbd "Print") '*my-screenshot-keymap*)
|
||
#+end_src
|
||
|
||
** End of Session, Powering Off, and the Likes
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Keybinds-End-of-Session-Powering-Off-and-the-Likes-mgz02z40w5j0
|
||
:END:
|
||
The module ~end-session~ provides functions for gracefully ending the
|
||
user session, powering off, restarting, and suspending the computer.
|
||
It also provides a function that interactively asks what the user
|
||
whishes to do.
|
||
#+name: end-session-keymap
|
||
| Keychord | Function |
|
||
|----------+-------------------|
|
||
| ~q~ | ~end-session~ |
|
||
| ~l~ | ~logout~ |
|
||
| ~s~ | ~suspend-computer~ |
|
||
| ~S~ | ~shutdown-computer~ |
|
||
| ~r~ | ~loadrc~ |
|
||
| ~R~ | ~restart-hard~ |
|
||
| ~C-r~ | ~restart-computer~ |
|
||
|
||
This translates to:
|
||
#+begin_src lisp
|
||
(defvar *my-end-session-keymap*
|
||
(let ((m (make-sparse-keymap)))
|
||
<<keybinds-gen(map="m", keybinds=end-session-keymap)>>
|
||
m))
|
||
#+end_src
|
||
|
||
Which is bound in the root map to ~q~:
|
||
#+begin_src lisp
|
||
(define-key *root-map* (kbd "q") '*my-end-session-keymap*)
|
||
#+end_src
|
||
|
||
** Groups
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Keybinds-Groups-daxfwu40a7j0
|
||
:END:
|
||
A basic keybind I need for groups is to be able to switch from one
|
||
another. I’m very used to the ability of being able to jump between
|
||
them with the keybind Super + /number of the group/, so let’s define
|
||
this:
|
||
|
||
#+name: group-keybind-gen
|
||
#+header: :noweb no :results verbatim :exports none :var convert="no"
|
||
#+begin_src emacs-lisp :var groups=list-groups mod="s" action="gselect" map="*top-map*" convert="yes"
|
||
(mapconcat (lambda (group)
|
||
(let ((group-nbr (nth 1 group)))
|
||
(format "%S" `(define-key
|
||
,(make-symbol map)
|
||
(kbd ,(format "%s-%s"
|
||
mod
|
||
(if (string= "yes" convert)
|
||
(format "<<num-to-char(num=%s)>>" group-nbr)
|
||
(number-to-string group-nbr))))
|
||
,(format "%s %d" action group-nbr)))))
|
||
groups
|
||
"\n")
|
||
#+end_src
|
||
|
||
#+RESULTS[09b139b0e127a88b3e4e2a05a609ccfcb7825b3c]: group-keybind-gen
|
||
: "(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\")"
|
||
|
||
#+header: :cache yes :noweb yes :wrap src lisp
|
||
#+begin_src emacs-lisp
|
||
<<group-keybind-gen(mod="s", action="gselect", convert="yes")>>
|
||
#+end_src
|
||
|
||
#+RESULTS[627ef5c7e456944dd624c322529699e11f2a041b]:
|
||
#+begin_src lisp
|
||
(define-key *top-map* (kbd "s-<<num-to-char(num=1)>>") "gselect 1")
|
||
(define-key *top-map* (kbd "s-<<num-to-char(num=2)>>") "gselect 2")
|
||
(define-key *top-map* (kbd "s-<<num-to-char(num=3)>>") "gselect 3")
|
||
(define-key *top-map* (kbd "s-<<num-to-char(num=4)>>") "gselect 4")
|
||
(define-key *top-map* (kbd "s-<<num-to-char(num=5)>>") "gselect 5")
|
||
(define-key *top-map* (kbd "s-<<num-to-char(num=6)>>") "gselect 6")
|
||
(define-key *top-map* (kbd "s-<<num-to-char(num=7)>>") "gselect 7")
|
||
(define-key *top-map* (kbd "s-<<num-to-char(num=8)>>") "gselect 8")
|
||
#+end_src
|
||
|
||
Another batch of keybinds I use a lot is keybinds to send the
|
||
currently active window to another group, using Super + Shift + /number
|
||
of the group/. As mentioned before, due to my keyboard layout Shift +
|
||
/number/ is actually just /number/ for me (e.g. Shift + ~"~ results in ~1~),
|
||
so there’s no need to convert the group number to another character.
|
||
#+begin_src emacs-lisp :wrap src lisp
|
||
<<group-keybind-gen(mod="s", action="gmove-and-follow", convert="no")>>
|
||
#+end_src
|
||
|
||
#+RESULTS[6577510905e5cce124ff563a6d68a7f64fc8683c]:
|
||
#+begin_src lisp
|
||
(define-key *top-map* (kbd "s-1") "gmove-and-follow 1")
|
||
(define-key *top-map* (kbd "s-2") "gmove-and-follow 2")
|
||
(define-key *top-map* (kbd "s-3") "gmove-and-follow 3")
|
||
(define-key *top-map* (kbd "s-4") "gmove-and-follow 4")
|
||
(define-key *top-map* (kbd "s-5") "gmove-and-follow 5")
|
||
(define-key *top-map* (kbd "s-6") "gmove-and-follow 6")
|
||
(define-key *top-map* (kbd "s-7") "gmove-and-follow 7")
|
||
#+end_src
|
||
|
||
If I want to send a window to another group without following it, I’ll
|
||
use ~s-S-C-<group number>~, which gives us the following:
|
||
#+begin_src emacs-lisp :wrap src lisp
|
||
<<group-keybind-gen(mod="s-C", action="gmove-and-follow", convert="no")>>
|
||
#+end_src
|
||
|
||
#+RESULTS[55852a5a035c23f078ba0a97120151c059fa955f]:
|
||
#+begin_src lisp
|
||
(define-key *top-map* (kbd "s-C-1") "gmove-and-follow 1")
|
||
(define-key *top-map* (kbd "s-C-2") "gmove-and-follow 2")
|
||
(define-key *top-map* (kbd "s-C-3") "gmove-and-follow 3")
|
||
(define-key *top-map* (kbd "s-C-4") "gmove-and-follow 4")
|
||
(define-key *top-map* (kbd "s-C-5") "gmove-and-follow 5")
|
||
(define-key *top-map* (kbd "s-C-6") "gmove-and-follow 6")
|
||
(define-key *top-map* (kbd "s-C-7") "gmove-and-follow 7")
|
||
#+end_src
|
||
|
||
And if I want to bring the windows of another group into the current
|
||
group, I’ll use ~s-C-<group number>~:
|
||
#+begin_src emacs-lisp :wrap src lisp :exports results
|
||
<<group-keybind-gen(mod="s-C", action="gmove-and-follow", convert="yes")>>
|
||
#+end_src
|
||
|
||
#+RESULTS[b536bb0359e6e9e10e98635c82bed3d348d75ac5]:
|
||
#+begin_src lisp
|
||
(define-key *top-map* (kbd "s-C-<<num-to-char(num=1)>>") "gmove-and-follow 1")
|
||
(define-key *top-map* (kbd "s-C-<<num-to-char(num=2)>>") "gmove-and-follow 2")
|
||
(define-key *top-map* (kbd "s-C-<<num-to-char(num=3)>>") "gmove-and-follow 3")
|
||
(define-key *top-map* (kbd "s-C-<<num-to-char(num=4)>>") "gmove-and-follow 4")
|
||
(define-key *top-map* (kbd "s-C-<<num-to-char(num=5)>>") "gmove-and-follow 5")
|
||
(define-key *top-map* (kbd "s-C-<<num-to-char(num=6)>>") "gmove-and-follow 6")
|
||
(define-key *top-map* (kbd "s-C-<<num-to-char(num=7)>>") "gmove-and-follow 7")
|
||
#+end_src
|
||
|
||
StumpWM also has already a nice keymap for managing groups called
|
||
~*groups-map*~, so let’s bind it to ~*root-map*~ too! (It’s actually
|
||
already bound, but since I plan on erasing ~*root-map*~ in the near
|
||
future before binding stuff to it, I prefer to bind it already)
|
||
#+begin_src lisp
|
||
(define-key *root-map* (kbd "g") '*groups-map*)
|
||
#+end_src
|
||
|
||
And a binding to ~vgroups~ is done on ~*groups-map*~ in order to regroup
|
||
similar keybinds.
|
||
#+begin_src lisp
|
||
(define-key *groups-map* (kbd "G") "vgroups")
|
||
#+end_src
|
||
|
||
I grew accustomed to ~s-ESC~ bringing me to the previous group when
|
||
using AwesomeWM, so let’s define that:
|
||
#+begin_src lisp
|
||
(define-key *top-map* (kbd "s-ESC") "gother")
|
||
#+end_src
|
||
|
||
** Frames and Windows management
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Keybinds-Frames-and-Windows-management-g4s6j371v5j0
|
||
:END:
|
||
As you’ll see, I have loads of keybinds related to frames and windows
|
||
management. They are all categorized in a specific keymap, called
|
||
~*my-frames-management-keymap*~. But before that, let’s define the
|
||
keymap ~*my-frames-float-keymap*~, with keybinds dedicated to actions
|
||
related with floating windows and frames.
|
||
|
||
#+name: frames-float
|
||
#+caption: ~*my-frames-float-keymap*~
|
||
| Keychord | Function |
|
||
|----------+----------------|
|
||
| ~f~ | ~float-this~ |
|
||
| ~F~ | ~unfloat-this~ |
|
||
| ~u~ | ~unfloat-this~ |
|
||
| ~C-f~ | ~flatten-floats~ |
|
||
|
||
We can now pass onto ~*my-frames-management-keymap*~. My keybinds are organized this way:
|
||
#+name: frames-and-window-management
|
||
#+caption: ~*my-frames-management-keymap*~
|
||
| Keychord | Function |
|
||
|----------+---------------------------|
|
||
| ~c~ | ~move-focus left~ |
|
||
| ~t~ | ~move-focus down~ |
|
||
| ~s~ | ~move-focus up~ |
|
||
| ~r~ | ~move-focus right~ |
|
||
| ~C~ | ~move-window left~ |
|
||
| ~T~ | ~move-window down~ |
|
||
| ~S~ | ~move-window up~ |
|
||
| ~R~ | ~move-window right~ |
|
||
| ~C-c~ | ~exchange-direction left~ |
|
||
| ~C-t~ | ~exchange-direction down~ |
|
||
| ~C-s~ | ~exchange-direction up~ |
|
||
| ~C-r~ | ~exchange-direction right~ |
|
||
| ~/~ | ~hsplit-and-focus~ |
|
||
| ~-~ | ~vsplit-and-focus~ |
|
||
| ~h~ | ~hsplit~ |
|
||
| ~v~ | ~vsplit~ |
|
||
| ~H~ | ~hsplit-equally~ |
|
||
| ~V~ | ~vsplit-equally~ |
|
||
| ~.~ | ~iresize~ |
|
||
| ~+~ | ~balance-frames~ |
|
||
| ~d~ | ~remove-split~ |
|
||
| ~D~ | ~only~ |
|
||
| ~e~ | ~expose~ |
|
||
| ~f~ | ~fullscreen~ |
|
||
| ~F~ | ~'*my-frames-float-keymap*~ |
|
||
| ~i~ | ~info~ |
|
||
| ~I~ | ~show-window-properties~ |
|
||
| ~m~ | ~meta~ |
|
||
| ~s~ | ~sibling~ |
|
||
| ~u~ | ~next-urgent~ |
|
||
| ~U~ | ~unmaximize~ |
|
||
|
||
As you can see, with the binding to ~F~, we make use of the
|
||
~*my-frames-float-keymap*~ keymap declared above, which means if we find
|
||
ourselves in ~*my-frames-management-keymap*~, pressing ~F~ will bring us
|
||
in ~*my-frames-float-keymap*~.
|
||
|
||
#+begin_src lisp
|
||
(defvar *my-frames-float-keymap*
|
||
(let ((m (make-sparse-keymap)))
|
||
<<keybinds-gen(map="m", keybinds=frames-float)>>
|
||
m))
|
||
|
||
(defvar *my-frames-management-keymap*
|
||
(let ((m (make-sparse-keymap)))
|
||
<<keybinds-gen(map="m", keybinds=frames-and-window-management)>>
|
||
m))
|
||
#+end_src
|
||
|
||
Let’s bind ~*my-frames-management-keymap*~ in ~*root-keymap*~:
|
||
#+begin_src lisp
|
||
(define-key *root-map* (kbd "w") '*my-frames-management-keymap*)
|
||
#+end_src
|
||
|
||
That way, if we want for instance to split our current frame
|
||
vertically, we’ll be able to type ~s-SPC w -~ and ~vsplit~ will be called.
|
||
|
||
I also bound a couple of these functions to the top keymap for easier access:
|
||
#+name: top-window-map
|
||
| Keychord | Function |
|
||
|----------+--------------------------|
|
||
| ~s-c~ | ~move-focus left~ |
|
||
| ~s-t~ | ~move-focus down~ |
|
||
| ~s-s~ | ~move-focus up~ |
|
||
| ~s-r~ | ~move-focus right~ |
|
||
| ~s-C~ | ~move-window left~ |
|
||
| ~s-T~ | ~move-window down~ |
|
||
| ~s-S~ | ~move-window up~ |
|
||
| ~s-R~ | ~move-window right~ |
|
||
| ~s-M-c~ | ~exchange-direction left~ |
|
||
| ~s-M-t~ | ~exchange-direction down~ |
|
||
| ~s-M-s~ | ~exchange-direction up~ |
|
||
| ~s-M-r~ | ~exchange-direction right~ |
|
||
|
||
This translates to:
|
||
#+begin_src lisp
|
||
<<keybinds-gen(map="*top-map*", keybinds=top-window-map)>>
|
||
#+end_src
|
||
|
||
Being a [[https://bepo.fr/wiki/Accueil][bépo layout]] user, the ~hjkl~ keys don’t exactly fit me, as you
|
||
might have noticed with my use of ~ctsr~ which is its equivalent. Due to
|
||
this, the interactive keymap for ~iresize~ is not ideal for me, let me
|
||
redefine it:
|
||
#+begin_src lisp
|
||
(define-interactive-keymap (iresize tile-group) (:on-enter #'setup-iresize
|
||
:on-exit #'resize-unhide
|
||
:abort-if #'abort-resize-p)
|
||
((kbd "c") "resize-direction left")
|
||
((kbd "t") "resize-direction down")
|
||
((kbd "s") "resize-direction up")
|
||
((kbd "r") "resize-direction right"))
|
||
#+end_src
|
||
|
||
As with groups management, I grew used to ~s-TAB~ in AwesomeWM bringing
|
||
me back to the previously focused window.
|
||
#+begin_src lisp
|
||
(define-key *top-map* (kbd "s-TAB") "other-window")
|
||
#+end_src
|
||
|
||
** Windows management
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Keybinds-Windows-management-ylf903j0x5j0
|
||
:END:
|
||
When it comes to windows management, I will treat them a bit like I do
|
||
with Emacs’ buffers.
|
||
|
||
#+name: window-management
|
||
#+caption: ~*my-buffers-management-keymap*~
|
||
| Keychord | Function |
|
||
|----------+-------------------------|
|
||
| ~b~ | ~windowlist~ |
|
||
| ~d~ | ~delete-window~ |
|
||
| ~D~ | ~window-window-and-frame~ |
|
||
| ~k~ | ~kill-window~ |
|
||
| ~n~ | ~next~ |
|
||
| ~o~ | ~other-window~ |
|
||
| ~p~ | ~prev~ |
|
||
|
||
#+begin_src lisp
|
||
(defvar *my-buffers-management-keymap*
|
||
(let ((m (make-sparse-keymap)))
|
||
<<keybinds-gen(map="m", keybinds=window-management)>>
|
||
m))
|
||
|
||
(define-key *root-map* (kbd "b") '*my-buffers-management-keymap*)
|
||
#+end_src
|
||
|
||
** Media and Media Control
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Keybinds-Media-and-Media-Control-hbv5uk91z5j0
|
||
:END:
|
||
My music is managed through MPD, and I often use ~mpc~ commands in order
|
||
to interact with it without any GUI application. So, we’ll see a lot
|
||
of its usage here.
|
||
|
||
First, let’s declare an interactive keymap in order to easily change
|
||
several times in a row either the current song playing or the volume
|
||
of MPD.
|
||
#+name: inter-mpc
|
||
#+caption: Interactive keybinds for ~mpc~
|
||
| Keychord | Function |
|
||
|----------+--------------------|
|
||
| ~c~ | ~exec mpc prev~ |
|
||
| ~t~ | ~exec mpc volume -2~ |
|
||
| ~s~ | ~exec mpc volume +2~ |
|
||
| ~r~ | ~exec mpc next~ |
|
||
|
||
Cela donne le code suivant:
|
||
#+begin_src lisp
|
||
<<interactive-gen(name="mpc-interactive", keys=inter-mpc)>>
|
||
#+end_src
|
||
|
||
Another one will be defined for the general audio of my computer. And
|
||
I know it isn’t technically media keybinds, but I’ll add in keybinds
|
||
for my screen’s backlight.
|
||
#+name: inter-media
|
||
#+caption: Interactive keybinds for general media interaction
|
||
| Keys | Function |
|
||
|------+--------------------------------------|
|
||
| ~c~ | ~exec xbacklight -dec 2~ |
|
||
| ~t~ | ~exec amixer -q set Master 2%- unmute~ |
|
||
| ~s~ | ~exec amixer -q set Master 2%+ unmute~ |
|
||
| ~r~ | ~exec xbacklight -inc 2~ |
|
||
| ~m~ | ~exec amixer -q set Master 1+ toggle~ |
|
||
|
||
#+begin_src lisp
|
||
<<interactive-gen(name="media-interactive", keys=inter-media)>>
|
||
#+end_src
|
||
|
||
Then, let’s declare a keymap for our media controls.
|
||
#+name: media-management
|
||
#+caption: ~*my-media-keymap*~
|
||
| Keychord | Function |
|
||
|----------+-----------------------------|
|
||
| ~.~ | ~media-interactive~ |
|
||
| ~m~ | ~mpc-interactive~ |
|
||
| ~p~ | ~exec mpc prev~ |
|
||
| ~n~ | ~exec mpc next~ |
|
||
| ~t~ | ~exec mpc toggle~ |
|
||
| ~s~ | ~exec mpc stop~ |
|
||
| ~N~ | ~term ncmpcpp -q~ |
|
||
| ~v~ | ~term ncmpcpp -qs visualizer~ |
|
||
|
||
Let’s translate this table in CommonLisp:
|
||
#+begin_src lisp
|
||
(defvar *my-media-keymap*
|
||
(let ((m (make-sparse-keymap)))
|
||
<<keybinds-gen(map="m", keybinds=media-management)>>
|
||
m))
|
||
|
||
(define-key *root-map* (kbd "m") '*my-media-keymap*)
|
||
#+end_src
|
||
|
||
I will also define on ~*top-map*~ some basic volume management keybinds
|
||
so that they are immediately accessible. Again, this isn’t technically
|
||
media-related, but I’ll add keybinds for my screen’s backlight.
|
||
#+name: media-top-level
|
||
#+caption: Top-level media keys
|
||
| Keychord | Function |
|
||
|-----------------------+--------------------------------------|
|
||
| ~XF86AudioPlay~ | ~exec mpc play~ |
|
||
| ~XF86AudioPause~ | ~exec mpc pause~ |
|
||
| ~XF86AudioPrev~ | ~exec mpc prev~ |
|
||
| ~XF86AudioNext~ | ~exec mpc next~ |
|
||
| ~XF86AudioRaiseVolume~ | ~exec amixer -q set Master 2%+ unmute~ |
|
||
| ~XF86AudioLowerVolume~ | ~exec amixer -q set Master 2%- unmute~ |
|
||
| ~XF86AudioMute~ | ~exec amixer -q set Master 1+ toggle~ |
|
||
| ~XF86MonBrightnessDown~ | ~exec xbacklight -dec 2~ |
|
||
| ~XF86MonBrightnessUp~ | ~exec xbacklight -inc 2~ |
|
||
|
||
#+begin_src lisp
|
||
<<keybinds-gen(map="*top-map*", keybinds=media-top-level)>>
|
||
#+end_src
|
||
|
||
** Misc
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Keybinds-Misc-455iuh50w5j0
|
||
:END:
|
||
Finally, some misc keybinds on the root map which don’t really fit
|
||
anywhere else:
|
||
#+name: misc-root-map
|
||
| Keychord | Function |
|
||
|----------+------------|
|
||
| ~B~ | ~beckon~ |
|
||
| ~C-b~ | ~banish~ |
|
||
| ~l~ | ~exec plock~ |
|
||
| ~r~ | ~reload~ |
|
||
|
||
#+begin_src lisp
|
||
<<keybinds-gen(map="*root-map*", keybinds=misc-root-map)>>
|
||
#+end_src
|
||
|
||
From time to time, I need to switch between different keyboard
|
||
layouts, especially to the US Qwerty layout when I’m playing some
|
||
games and the bépo layout most of the time. I’ll use the command
|
||
~switch-layout~ defined above.
|
||
#+name: keyboard-layout-map
|
||
| Keychord | Function |
|
||
|----------+------------------------------|
|
||
| ~b~ | ~exec setxkbmap fr bepo_afnor~ |
|
||
| ~u~ | ~exec setxkbmap us~ |
|
||
|
||
#+begin_src lisp
|
||
(defvar *my-keyboard-layout-keymap*
|
||
(let ((m (make-sparse-keymap)))
|
||
<<keybinds-gen(map="m", keybinds=keyboard-layout-map)>>
|
||
m))
|
||
|
||
(define-key *root-map* (kbd "k") '*my-keyboard-layout-keymap*)
|
||
#+end_src
|
||
|
||
* org functions :noexport:
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: org-functions-syqgzgg0m6j0
|
||
:END:
|
||
|
||
#+name: keybinds-gen
|
||
#+begin_src emacs-lisp :var map="m" keybinds=frames-float
|
||
(mapconcat (lambda (keybind)
|
||
(format "%s" (let ((key (let ((s (car keybind)))
|
||
(substring-no-properties s 1 (1- (length s)))))
|
||
(function (let ((s (cadr keybind)))
|
||
(substring-no-properties s 1 (1- (length s))))))
|
||
`(define-key ,map
|
||
(kbd ,(format "\"%s\"" key))
|
||
,(if (string-prefix-p "'" function t)
|
||
function
|
||
(format "\"%s\"" function))))))
|
||
keybinds
|
||
"\n")
|
||
#+end_src
|
||
|
||
#+name: interactive-gen
|
||
#+begin_src emacs-lisp :var name="inter" keys=inter-mpc
|
||
(format "%s"
|
||
`(define-interactive-keymap ,name ()
|
||
"\n "
|
||
,(mapconcat (lambda (keybind)
|
||
(format "%s"
|
||
(let ((key (let ((s (car keybind)))
|
||
(substring-no-properties s
|
||
1
|
||
(1- (length s)))))
|
||
(command (let ((s (cadr keybind)))
|
||
(substring-no-properties s
|
||
1
|
||
(1- (length s))))))
|
||
`((kbd ,(format "\"%s\"" key))
|
||
,(format "\"%s\"" command)))))
|
||
keys
|
||
"\n ")))
|
||
#+end_src
|
||
|
||
#+name: num-to-char
|
||
#+begin_src emacs-lisp :var table=number-to-char-table num=0
|
||
(let ((char (replace-regexp-in-string (regexp-quote "~")
|
||
""
|
||
(let* ((row (assoc num table))
|
||
(char (cadr row))
|
||
(lispchar (caddr row)))
|
||
(if (string= "" lispchar)
|
||
char
|
||
lispchar)))))
|
||
(if (string= char "\"")
|
||
"\\\""
|
||
char))
|
||
#+end_src
|
||
|
||
#+RESULTS[6934c27c10c3f968f70b0112d4639298e519fe61]: num-to-char
|
||
: *
|