From 2d1e17f03b1c138f7e671475e2dc2550c998073d Mon Sep 17 00:00:00 2001 From: Lucien Cartier-Tilet Date: Sun, 28 Jan 2024 05:31:58 +0100 Subject: [PATCH] feat: split StumpWM config into different files, restore some pages --- docs/.vuepress/config.ts | 180 ++-- docs/stumpwm.org | 1920 ---------------------------------- docs/stumpwm/colours.org | 85 ++ docs/stumpwm/commands.org | 53 + docs/stumpwm/groups.org | 95 ++ docs/stumpwm/index.org | 92 ++ docs/stumpwm/init.org | 153 +++ docs/stumpwm/keybindings.org | 776 ++++++++++++++ docs/stumpwm/mode-line.org | 191 ++++ docs/stumpwm/theme.org | 202 ++++ docs/stumpwm/utilities.org | 372 +++++++ 11 files changed, 2132 insertions(+), 1987 deletions(-) delete mode 100644 docs/stumpwm.org create mode 100644 docs/stumpwm/colours.org create mode 100644 docs/stumpwm/commands.org create mode 100644 docs/stumpwm/groups.org create mode 100644 docs/stumpwm/index.org create mode 100644 docs/stumpwm/init.org create mode 100644 docs/stumpwm/keybindings.org create mode 100644 docs/stumpwm/mode-line.org create mode 100644 docs/stumpwm/theme.org create mode 100644 docs/stumpwm/utilities.org diff --git a/docs/.vuepress/config.ts b/docs/.vuepress/config.ts index c79b2f9..8bc842b 100644 --- a/docs/.vuepress/config.ts +++ b/docs/.vuepress/config.ts @@ -2,6 +2,108 @@ import { defineUserConfig, defaultTheme } from 'vuepress'; import { removeHtmlExtensionPlugin } from 'vuepress-plugin-remove-html-extension'; import head from './head'; +interface ChildPage { + text: string; + link: string; +} + +const emacsChildPages: ChildPage[] = [ + { + text: 'Basic Configuration', + link: '/emacs/basic-config', + }, + { + text: 'Custom Elisp', + link: '/emacs/custom-elisp', + }, + { + text: 'Package Manager', + link: '/emacs/package-manager', + }, + { + text: 'Keybindings Managers', + link: '/emacs/keybinding-managers', + }, + { + text: 'Packages - Autocompletion', + link: '/emacs/packages/autocompletion', + }, + { + text: 'Packages - Applications', + link: '/emacs/packages/applications', + }, + { + text: 'Packages - Editing', + link: '/emacs/packages/editing', + }, + { + text: 'Packages - Emacs Built-ins', + link: '/emacs/packages/emacs-builtin', + }, + { + text: 'Packages - Making My Life Easier', + link: '/emacs/packages/helpful', + }, + { + text: 'Packages - LaTeX', + link: '/emacs/packages/latex', + }, + { + text: 'Packages - Org Mode', + link: '/emacs/packages/org', + }, + { + text: 'Packages - Programming', + link: '/emacs/packages/programming', + }, + { + text: 'Packages - Visual Configuration', + link: '/emacs/packages/visual-config', + }, + { + text: 'Packages - Misc', + link: '/emacs/packages/misc', + }, + { + text: 'Keybindings', + link: '/emacs/keybindings', + }, +]; +const stumpwmChildPages: ChildPage[] = [ + { + text: 'Basic Configuration', + link: '/stumpwm/init', + }, + { + text: 'Colours', + link: '/stumpwm/colours', + }, + { + text: 'Mode-Line', + link: '/stumpwm/mode-line', + }, + { + text: 'Groups and Placement', + link: '/stumpwm/groups', + }, + { + text: 'Theme', + link: '/stumpwm/theme', + }, + { + text: 'Commands', + link: '/stumpwm/commands', + }, + { + text: 'Keybindings', + link: '/stumpwm/keybindings', + }, + { + text: 'Utilities', + link: '/stumpwm/utilities', + }, +]; + export default defineUserConfig({ lang: 'en-US', title: "Phundrak's Dotfiles", @@ -26,79 +128,23 @@ export default defineUserConfig({ text: 'Emacs', link: '/emacs/', collapsible: true, - children: [ - { - text: 'Basic Configuration', - link: '/emacs/basic-config', - }, - { - text: 'Custom Elisp', - link: '/emacs/custom-elisp', - }, - { - text: 'Package Manager', - link: '/emacs/package-manager', - }, - { - text: 'Keybindings Managers', - link: '/emacs/keybinding-managers', - }, - { - text: 'Packages - Autocompletion', - link: '/emacs/packages/autocompletion', - }, - { - text: 'Packages - Applications', - link: '/emacs/packages/applications', - }, - { - text: 'Packages - Editing', - link: '/emacs/packages/editing', - }, - { - text: 'Packages - Emacs Built-ins', - link: '/emacs/packages/emacs-builtin', - }, - { - text: 'Packages - Making My Life Easier', - link: '/emacs/packages/helpful', - }, - { - text: 'Packages - LaTeX', - link: '/emacs/packages/latex', - }, - { - text: 'Packages - Org Mode', - link: '/emacs/packages/org', - }, - { - text: 'Packages - Programming', - link: '/emacs/packages/programming', - }, - { - text: 'Packages - Visual Configuration', - link: '/emacs/packages/visual-config', - }, - { - text: 'Packages - Misc', - link: '/emacs/packages/misc', - }, - { - text: 'Keybindings', - link: '/emacs/keybindings', - }, - ], + children: emacsChildPages, }, '/scripts', '/desktop', - // '/fish', - // '/git', - // '/mpd', + '/fish', + '/git', + '/mpd', '/neofetch', '/picom', '/rustfmt', - '/stumpwm', - // '/tmux', + { + text: 'StumpWM', + link: '/stumpwm/', + collapsible: true, + children: stumpwmChildPages, + }, + '/tmux', '/bootstrap', { text: 'Deprecated Configs', diff --git a/docs/stumpwm.org b/docs/stumpwm.org deleted file mode 100644 index e0e441b..0000000 --- a/docs/stumpwm.org +++ /dev/null @@ -1,1920 +0,0 @@ -#+TITLE: StumpWM config -#+setupfile: headers -#+property: header-args:emacs-lisp :tangle no :exports results :cache yes :noweb yes - -[[file:img/stumpwm.png]] - -* StumpWM -** Introduction -*** What is StumpWM? -[[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 a -dynamic tiling window manager like [[file:Deprecated/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? -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 -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 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 -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~ :: This file defines colours that will be used 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; -- ~utilities.lisp~ :: Here you can find my StumpWM configuration that - isn’t really related to the rest of the config, for instance utility - code for connecting by SSH to some host. -- ~theme.lisp~ :: manages the colour theme of StumpWM, the default - placement of some windows and StumpWM’s gaps. - -You will also find below my ~xinit~ file for StumpWM, exported to -~$HOME/.xinitrc.stumpwm~, which I use to start Stump through ~startx -~/.xinitrc.stumpwm~. -#+begin_src sh :tangle ~/.xinitrc.stumpwm :shebang "#!/bin/sh" -# this makes it work in Ubuntu -xhost +SI:localuser:$USER - -# Set fallback pointer -xsetroot -cursor_name left_ptr - -# Fix scrolling on some GTK3 applications -export GDK_CORE_DEVICE_EVENTS=1 - -# in case Java applications display /nothing/ -# wmname LG3D -# export _JAVA_AWT_WM_NONREPARENTING=1 - -autorandr -l home - -exec stumpwm -#+end_src - -** Init file -:PROPERTIES: -:header-args:lisp: :mkdirp yes :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 "autostart") -#+end_src - -Next I need to register the AltGr key so it works correctly when used. -On my system, the value of ~*altgr-offset*~ is 4, but on yours it might -be 6, so be careful and refer to the manual on that matter. -#+begin_src lisp -(setf *altgr-offset* 4) -(register-altgr-as-modifier) -#+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 | -|-------------------| -| bluetooth.lisp | -| commands.lisp | -| placement.lisp | -| keybindings.lisp | -| theme.lisp | -| utilities.lisp | -| modeline.lisp | -| systemd.lisp | - -#+name: gen-load-files -#+header: :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[ed4f3fe4f7f82b11cd3cd262578abc7146f5807d]: gen-load-files -#+begin_src lisp -(load "~/.stumpwm.d/bluetooth.lisp") -(load "~/.stumpwm.d/commands.lisp") -(load "~/.stumpwm.d/placement.lisp") -(load "~/.stumpwm.d/keybindings.lisp") -(load "~/.stumpwm.d/theme.lisp") -(load "~/.stumpwm.d/utilities.lisp") -(load "~/.stumpwm.d/modeline.lisp") -(load "~/.stumpwm.d/systemd.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 to use the super key to move floating -windows and window focus to transfer from one window to another only -on click. -#+begin_src lisp -(setf *mouse-focus-policy* :click - ,*float-window-modifier* :SUPER) -#+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 | -|-----------------+--------------------------------------------------| -| 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 | -| mpd | Interact with MPD | -| stump-backlight | Native management of backlight in StumpWM | -| urgentwindows | Get urgent windows | - -#+name: gen-load-modules -#+header: :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[0cbba236372280cb2eb6a1e277cda84938e15d46]: gen-load-modules -#+begin_src lisp -(load-module "beckon") -(load-module "end-session") -(load-module "globalwindows") -(load-module "mpd") -(load-module "stump-backlight") -(load-module "urgentwindows") -#+end_src - -In order to be able to use MPD from StumpWM itself, we’ll need to -connect to it. -#+begin_src lisp -(mpd:mpd-connect) -#+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. - -** Colours -:PROPERTIES: -:header-args:lisp: :mkdirp yes :tangle ~/.stumpwm.d/colors.lisp -:END: -If you’ve had 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 colours: -#+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 -#+header: :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 - -Finally, let’s also modify the default colors StumpWM has. I’ll try to -respect the original colours while converting them to Nord. We also -need to reload them now that we modified them. -#+begin_src lisp -(setq *colors* - `(,phundrak-nord1 ;; 0 black - ,phundrak-nord11 ;; 1 red - ,phundrak-nord14 ;; 2 green - ,phundrak-nord13 ;; 3 yellow - ,phundrak-nord10 ;; 4 blue - ,phundrak-nord14 ;; 5 magenta - ,phundrak-nord8 ;; 6 cyan - ,phundrak-nord5)) ;; 7 white - -(when *initializing* - (update-color-map (current-screen))) -#+end_src - -And with that we’re done! - -** Mode-Line -:PROPERTIES: -:header-args:lisp: :mkdirp yes :tangle ~/.stumpwm.d/modeline.lisp -:END: -The timeout of the modeline indicates how often it refreshes in -seconds. I think two seconds is good. -#+begin_src lisp -(setf *mode-line-timeout* 2) -#+end_src - -*** Formatting Options -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: %30t") -#+end_src - -*** Mode-Line Theme -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 colours 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 colour to Nord1, just in case. -#+begin_src lisp -(setf *mode-line-border-color* phundrak-nord1 - ,*mode-line-border-width* 0) -#+end_src - -*** Mode-Line Modules -Here are some modules that we will load for the modeline: -#+name: modeline-modules -| Module Name | Why Do I Need It? | -|------------------+--------------------------------------------------| -| battery-portable | Get information on the battery level of a laptop | -| cpu | Get the CPU usage | -| mpd | Display MPD’s status | -| mem | Get the memory usage | - -#+name: gen-load-modeline-modules -#+header: :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[20a1d5d9c6e0136d6a130b1c1b4bd4d742aead8a]: gen-load-modeline-modules -#+begin_src lisp -(load-module "battery-portable") -(load-module "cpu") -(load-module "mpd") -(load-module "mem") -#+end_src - -We need to set some variables, so modules can be displayed correctly. -Note that the character between the font switchers in the second CPU -formatter is U+E082, which symbolizes the CPU. -#+begin_src lisp -(setf cpu::*cpu-modeline-fmt* "%c" - cpu::*cpu-usage-modeline-fmt* "^f2^f0^[~A~2D%^]" - mem::*mem-modeline-fmt* "%a%p" - mpd:*mpd-modeline-fmt* "%a - %t" - mpd:*mpd-status-fmt* "%a - %t" - ,*hidden-window-color* "^**" - ,*mode-line-highlight-template* "«~A»") -#+end_src - -*** Generating the Mode-Line -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 [[file:./stumpwm.md#fonts][Fonts]]). -#+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 align to the right | | -| ~docker-running~ | Display number of docker containers currently running | yes | -| ~mu-unread~ | Display number of unread emails | yes | -| ~%m~ | Display current MPD song | | -| ~%C~ | Display CPU usage | | -| ~%M~ | Display RAM usage | | -| ~%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[4246baab1293d54bcd2223590f274152f24934c3]: modeline-format-gen -: (("%g") ("%W") ("^>") ("docker-running" . t) ("mu-unread" . t) ("%m") ("%C") ("%M") ("%B") ("%d")) - -#+begin_src lisp :noweb yes -(defvar *mode-line-formatter-list* - '<> - "List of formatters for the modeline.") -#+end_src - -As you can see, ~generate-modeline~ generates the string defining -~*screen-mode-line-format*~ from the list of formatters we gave it with -the table [[modeline-format]]. -#+begin_src lisp -(defun generate-modeline (elements &optional not-invertedp rightp) - "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 (format nil - " ^[~A^]^(:bg \"~A\") " - (format nil "^(:fg \"~A\")^(:bg \"~A\")^f1~A^f0" - (if (xor not-invertedp rightp) phundrak-nord1 phundrak-nord3) - (if (xor not-invertedp rightp) phundrak-nord3 phundrak-nord1) - (if rightp "" "")) - (if not-invertedp phundrak-nord3 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) - (if (string= "^>" (caar elements)) t rightp))))))) -#+end_src - -It is then easy to define a command that can call this function and -set this variable, so we can sort of reload the mode-line. -#+begin_src lisp -(defcommand reload-modeline () () - "Reload modeline." - (sb-thread:make-thread - (lambda () - (setf *screen-mode-line-format* - (cdr (generate-modeline *mode-line-formatter-list*)))))) -#+end_src - -And actually, let’s reload the modeline immediately. -#+begin_src lisp -(reload-modeline) -#+end_src - -*** TODO Investigate why ~stumptray~ acts up :noexport: -Systray overlaps with the far-right part of the modeline. - -# 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: -:header-args:lisp: :mkdirp yes :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 | -|---------+--------+---------+--------| -| [EMACS] | 1 | | Tiling | -| [TERM] | 2 | | Tiling | -| [WWW] | 3 | | Tiling | -| [PRIV] | 4 | | Tiling | -| [FILES] | 5 | | Tiling | - -#+name: gen-groups -#+header: :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" `(,(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[4dad230a37c25af58c4f38eef1dd4f762b295f71]: gen-groups -: (grename "[EMACS]") -: (gnewbg "[TERM]") -: (gnewbg "[WWW]") -: (gnewbg "[PRIV]") -: (gnewbg "[FILES]") - -Groups are specified this way: -#+begin_src lisp -(when *initializing* - <>) -#+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 behaviour. -#+begin_src lisp -(clear-window-placement-rules) -#+end_src - -#+name: gen-rules -#+header: :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 - -Dynamic groups, if any is created, 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: -:header-args:lisp: :mkdirp yes :tangle ~/.stumpwm.d/theme.lisp :noweb yes -:END: -As in the modeline file, the first thing we’ll do is to load our -colours. -#+begin_src lisp -(load "~/.stumpwm.d/colors.lisp") -#+end_src - -We can now go onto more serious business. - -*** Fonts -This gave me quite the headache when I tried to set this up: in order -to use TTF fonts (note: it is not possible to use OTF fonts, see -below), 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). - -In order for it to work, install [[https://www.quicklisp.org/beta/][quicklisp]] and don’t forget to run -src_lisp[:exports code]{(ql:add-to-init-file)} so it is loaded each -time you start your Lisp interpreter. ~SBCL~ should be your CommonLisp -interpreter of choice since StumpWM is generally compiled with it. The -main advantage is also that SBCL supports multithreading, unlike -CLisp. In case StumpWM doesn’t find your font, spin up SBCL and -execute the following lines: -#+begin_src lisp :tangle no -(ql:quickload :clx-truetype) -(xft:cache-fonts) -#+end_src - -If you want a list of font families available, you can execute the -following: -#+begin_src lisp :tangle no -(clx-truetype:get-font-families) -#+end_src - -If you want to know the subfamilies of a certain family, you can -execute this: -#+begin_src lisp :tangle no -(clx-truetype:get-font-subfamilies "Family Name") -#+end_src - -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~ (~~ 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 | -| siji | Medium | 10 | -| FantasqueSansMono Nerd Font Mono | Regular | 9.5 | - -#+name: gen-fonts -#+header: :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[1693001a9a9c0e274a9b7097665e9795783ae8a2]: 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) - ,(make-instance 'xft:font :family "siji" :subfamily "Medium" :size 10 :antialias t) - ,(make-instance 'xft:font :family "FantasqueSansMono Nerd Font Mono" :subfamily "Regular" :size 9.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 -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 colours 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 - -I also have a [[https://github.com/Phundrak/stumpwm/tree/feature/no-hardcoded-which-key-format][StumpWM fork]] that introduces two new variables for -customizing which-key keybindings. I submitted a [[https://github.com/stumpwm/stumpwm/pull/931][pull request]], so it -might come one day to StumpWM. -#+begin_src lisp -(setf *key-seq-color* "^2") -(setf *which-key-format* (concat *key-seq-color* "*~5a^n ~a")) -#+end_src - -*** Message and Input Windows -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 -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* 40) -#+end_src - -Finally, let’s enable our gaps: -#+begin_src lisp -(when *initializing* - (swm-gaps:toggle-gaps)) -#+end_src - -** Commands -:PROPERTIES: -:header-args:lisp: :mkdirp yes :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." - (sb-thread:make-thread (lambda () (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}." - (sb-thread:make-thread - (lambda () - (run-shell-command (if program - (format nil "kitty ~A" program) - "kitty"))))) -#+end_src - -** Keybinds -:PROPERTIES: -:header-args:lisp: :mkdirp yes :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 literally 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*~. - -Anyway, as mentioned 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 (my/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 | -|--------+-----------| -| 1 | ~"~ | -| 2 | ~«~ | -| 3 | ~»~ | -| 4 | ~(~ | -| 5 | ~)~ | -| 6 | ~@~ | -| 7 | ~+~ | -| 8 | ~-~ | -| 9 | ~/~ | -| 0 | ~*~ | - -So if you see any weird keybind involving these characters, this is -because of my layout. - -Something a bit annoying though is Lisp doesn’t know some characters -by their actual name, rather by another one that I find too long and -too bothersome to remember. So here’s a list, if you see any of the -characters on the left column in my config, with the function -described below, my actual config will use their name as specified in -the right column. -#+name: tbl-char-to-name -| Character | Name | -|-----------+----------------| -| ~«~ | ~guillemotleft~ | -| ~»~ | ~guillemotright~ | - -#+name: chars-table-to-list -#+header: :exports none :noweb yes :results verbatim -#+begin_src emacs-lisp :var chars=tbl-char-to-name -;; chars -(let ((filter (lambda (str) - (replace-regexp-in-string "^~\\|~$" "" str)))) - (mapcar (lambda (row) - `(,(apply filter `(,(car row))) . ,(apply filter `(,(cadr row))))) - chars)) -#+end_src - -#+RESULTS[8ceb9b882276931ad0dba7dcf38d163f7674f547]: chars-table-to-list -: (("«" . "guillemotleft") ("»" . "guillemotright")) - -To convert these characters, I have my own macro which is a wrapper -around the function ~kbd~. - -#+name: my-kbd-defun -#+begin_src lisp :noweb yes -(defun my/kbd (keys) - "Prepares KEYS for function `stumpwm:kbd'. -If a character declared in the car of a member of the variable char, -it is replaced with its cdr. This allows the user to input characters -such as « or » and have them replaced with their actual name when -`stumpwm:kbd' is called." - (kbd (let ((chars '<>)) - (dolist (row chars keys) - (setf keys (cl-ppcre:regex-replace-all (car row) keys (cdr row))))))) -#+end_src - -#+header: :exports none -#+begin_src lisp :noweb yes :tangle ~/.stumpwm.d/bluetooth.lisp -<> -#+end_src - -#+header: :exports none -#+begin_src lisp :noweb yes :tangle ~/.stumpwm.d/utilities.lisp -<> -#+end_src - -*** Applications -When I speak about applications, I speak about programs and scripts in -general. With these keymaps, I can launch programs I have often 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))) - <> - m)) -#+end_src - -Let’s also create a keymap for screenshots. -#+name: screenshot-keymap -#+caption: ~*my-screenshot-keymap*~ -| Keychord | Function | -|----------+----------------------------| -| ~d~ | ~exec flameshot gui -d 3000~ | -| ~s~ | ~exec flameshot full~ | -| ~S~ | ~exec flameshot gui~ | - -Here’s the equivalent in Common Lisp. -#+begin_src lisp -(defvar *my-screenshot-keymap* - (let ((m (make-sparse-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~ | -| ~B~ | ~exec qutebrowser~ | -| ~d~ | ~exec discord~ | -| ~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))) - <> - m)) -#+end_src - -The application keymap can now be bound to the root map like so: -#+begin_src lisp -(define-key *root-map* (my/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, -and the ~XF86Mail~ key opens mu4e in Emacs. -#+begin_src lisp -(define-key *top-map* (my/kbd "s-RET") "term") -(define-key *top-map* (my/kbd "Print") '*my-screenshot-keymap*) -(define-key *top-map* (my/kbd "XF86Mail") "exec emacsclient -c -e \"(mu4e)\"") -#+end_src - -*** End of Session, Powering Off, and the Likes -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 -wishes 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))) - <> - m)) -#+end_src - -Which is bound in the root map to ~q~: -#+begin_src lisp -(define-key *root-map* (my/kbd "q") '*my-end-session-keymap*) -#+end_src - -*** Groups -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) - (my/kbd ,(format "%s-%s" - mod - (if (string= "yes" convert) - (format "<>" group-nbr) - (number-to-string group-nbr)))) - ,(format "%s %d" action group-nbr))))) - groups - "\n") -#+end_src - -#+RESULTS[282113d17ea21f02dc7c87059d787e62b5886b16]: group-keybind-gen -: "(define-key *top-map* (my/kbd \"s-1\") \"gselect 1\") -: (define-key *top-map* (my/kbd \"s-2\") \"gselect 2\") -: (define-key *top-map* (my/kbd \"s-3\") \"gselect 3\") -: (define-key *top-map* (my/kbd \"s-4\") \"gselect 4\") -: (define-key *top-map* (my/kbd \"s-5\") \"gselect 5\")" - -#+header: :cache yes :noweb yes :wrap src lisp -#+begin_src emacs-lisp -<> -#+end_src - -#+RESULTS[35268fd11d1fe4fa4065c98a0b7bc723a56c09a7]: -#+begin_src lisp -(define-key *top-map* (my/kbd "s-<>") "gselect 1") -(define-key *top-map* (my/kbd "s-<>") "gselect 2") -(define-key *top-map* (my/kbd "s-<>") "gselect 3") -(define-key *top-map* (my/kbd "s-<>") "gselect 4") -(define-key *top-map* (my/kbd "s-<>") "gselect 5") -#+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 -<> -#+end_src - -#+RESULTS[bd9b499dfad0fdf1f54db146ded1a60c6674f8ea]: -#+begin_src lisp -(define-key *top-map* (my/kbd "s-1") "gmove-and-follow 1") -(define-key *top-map* (my/kbd "s-2") "gmove-and-follow 2") -(define-key *top-map* (my/kbd "s-3") "gmove-and-follow 3") -(define-key *top-map* (my/kbd "s-4") "gmove-and-follow 4") -(define-key *top-map* (my/kbd "s-5") "gmove-and-follow 5") -#+end_src - -If I want to send a window to another group without following it, I’ll -use ~s-S-C-~, which gives us the following: -#+begin_src emacs-lisp :wrap src lisp -<> -#+end_src - -#+RESULTS[68f918c24a6cc0efa1503ed57841c40f4ec2ec4a]: -#+begin_src lisp -(define-key *top-map* (my/kbd "s-C-1") "gmove-and-follow 1") -(define-key *top-map* (my/kbd "s-C-2") "gmove-and-follow 2") -(define-key *top-map* (my/kbd "s-C-3") "gmove-and-follow 3") -(define-key *top-map* (my/kbd "s-C-4") "gmove-and-follow 4") -(define-key *top-map* (my/kbd "s-C-5") "gmove-and-follow 5") -#+end_src - -And if I want to bring the windows of another group into the current -group, I’ll use ~s-C-~: -#+begin_src emacs-lisp :wrap src lisp :exports results -<> -#+end_src - -#+RESULTS[36b3ded631cd0bad400c4847f6937cde4f11b4b7]: -#+begin_src lisp -(define-key *top-map* (my/kbd "s-C-<>") "gmove-and-follow 1") -(define-key *top-map* (my/kbd "s-C-<>") "gmove-and-follow 2") -(define-key *top-map* (my/kbd "s-C-<>") "gmove-and-follow 3") -(define-key *top-map* (my/kbd "s-C-<>") "gmove-and-follow 4") -(define-key *top-map* (my/kbd "s-C-<>") "gmove-and-follow 5") -#+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* (my/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* (my/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* (my/kbd "s-ESC") "gother") -#+end_src - -*** Frames and Windows management -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))) - <> - m)) - -(defvar *my-frames-management-keymap* - (let ((m (make-sparse-keymap))) - <> - m)) -#+end_src - -Let’s bind ~*my-frames-management-keymap*~ in ~*root-keymap*~: -#+begin_src lisp -(define-key *root-map* (my/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 -<> -#+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 - :exit-on ((kbd "RET") (kbd "ESC") - (kbd "C-g") (kbd "q"))) - ((my/kbd "c") "resize-direction left") - ((my/kbd "t") "resize-direction down") - ((my/kbd "s") "resize-direction up") - ((my/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, and I also grew used to ~s-o~ -doing the same thing. -#+begin_src lisp -(define-key *top-map* (my/kbd "s-TAB") "other-window") -(define-key *top-map* (my/kbd "s-o") "other-window") -#+end_src - -*** Windows management -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~ | ~delete-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))) - <> - m)) - -(define-key *root-map* (my/kbd "b") '*my-buffers-management-keymap*) -#+end_src - -*** Media and Media Control -My music is managed through MPD, and I often use ~playerctl~ commands in -order to interact with it without any GUI application. So, we’ll see a -lot of its usage here, and numerous commands used here come from the -~mpd~ minor mode loaded [[file:./stumpwm.md#init-file][above]]. - -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~ | ~mpd-prev~ | -| ~t~ | ~mpd-volume-down~ | -| ~s~ | ~mpd-volume-up~ | -| ~r~ | ~mpd-next~ | - -This can be translated in CommonLisp as: -#+begin_src lisp -<> -#+end_src - -We need to indicate also how much the volume is affected by -~mpd-volume-down~ and ~mpd-volume-up~. -#+begin_src lisp -(setf *mpd-volume-step* 2) -#+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 -perceived -dec 2~ | -| ~t~ | ~exec amixer -q set Master 2%- unmute~ | -| ~s~ | ~exec amixer -q set Master 2%+ unmute~ | -| ~r~ | ~exec xbacklight -perceived -inc 2~ | -| ~m~ | ~exec amixer -q set Master 1+ toggle~ | - -#+begin_src lisp -<> -#+end_src - -Then, let’s declare a keymap for our media controls. -#+name: mpd-add-map -#+caption: ~*my-mpd-add-map*~ -| Keychord | Function | -|----------+---------------------------| -| ~a~ | ~mpd-search-and-add-artist~ | -| ~A~ | ~mpd-search-and-add-album~ | -| ~f~ | ~mpd-search-and-add-file~ | -| ~F~ | ~mpd-add-file~ | -| ~g~ | ~mpd-search-and-add-genre~ | -| ~t~ | ~mpd-search-and-add-title~ | - -#+name: mpd-browse-map -#+caption: ~*my-mpd-browse-map*~ -| Keychord | Function | -|----------+---------------------| -| ~a~ | ~mpd-browse-artists~ | -| ~A~ | ~mpd-browse-albums~ | -| ~g~ | ~mpd-browse-genres~ | -| ~p~ | ~mpd-browse-playlist~ | -| ~t~ | ~mpd-browse-tracks~ | - -#+name: media-management -#+caption: ~*my-media-keymap*~ -| Keychord | Function | -|----------+-----------------------------------| -| ~.~ | ~media-interactive~ | -| ~«~ | ~exec playerctl previous~ | -| ~»~ | ~exec playerctl next~ | -| ~a~ | ~'*my-mpd-add-map*~ | -| ~b~ | ~'*my-mpd-browse-map*~ | -| ~c~ | ~mpd-clear~ | -| ~m~ | ~mpc-interactive~ | -| ~p~ | ~exec playerctl play-pause~ | -| ~s~ | ~exec playerctl stop~ | -| ~u~ | ~mpd-update~ | -| ~n~ | ~exec kitty ncmpcpp -q~ | -| ~v~ | ~exec kitty ncmpcpp -qs visualizer~ | - -Let’s translate this table in CommonLisp: -#+begin_src lisp -(defvar *my-mpd-add-map* - (let ((m (make-sparse-keymap))) - <> - m)) - -(defvar *my-mpd-browse-map* - (let ((m (make-sparse-keymap))) - <> - m)) - -(defvar *my-media-keymap* - (let ((m (make-sparse-keymap))) - <> - m)) - -(define-key *root-map* (my/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 playerctl play-pause~ | -| ~XF86AudioPause~ | ~exec playerctl pause~ | -| ~XF86AudioStop~ | ~exec playerctl stop~ | -| ~XF86AudioPrev~ | ~exec playerctl previous~ | -| ~XF86AudioNext~ | ~exec playerctl next~ | -| ~XF86AudioRewind~ | ~exec playerctl position -1~ | -| ~XF86AudioForward~ | ~exec playerctl position +1~ | -| ~XF86AudioRaiseVolume~ | ~exec pamixer -i 2~ | -| ~XF86AudioLowerVolume~ | ~exec pamixer -d 2~ | -| ~XF86AudioMute~ | ~exec pamixer -t~ | -| ~XF86MonBrightnessDown~ | ~exec xbacklight -perceived -dec 2~ | -| ~XF86MonBrightnessUp~ | ~exec xbacklight -perceived -inc 2~ | - -#+begin_src lisp -<> -#+end_src - -*** Misc -Finally, some misc keybinds on the root map which don’t really fit -anywhere else: -#+name: misc-root-map -| Keychord | Function | -|----------+------------| -| ~SPC~ | ~colon~ | -| ~B~ | ~beckon~ | -| ~C-b~ | ~banish~ | -| ~l~ | ~exec plock~ | -| ~r~ | ~reload~ | - -#+begin_src lisp -<> -#+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))) - <> - m)) - -(define-key *root-map* (my/kbd "k") '*my-keyboard-layout-keymap*) -#+end_src - -** Utilities -:PROPERTIES: -:header-args:lisp: :mkdirp yes :tangle ~/.stumpwm.d/utilities.lisp :noweb yes -:END: -Part of my configuration is not really related to StumpWM itself, or -rather it adds new behaviour StumpWM doesn’t have. ~utilities.lisp~ -stores all this code in one place. - -*** Binwarp -Binwarp allows the user to control their mouse from the keyboard, -basically eliminating the need for a physical mouse in daily usage of -the workstation (though a physical mouse stays useful for games and -such). -#+begin_src lisp -(load-module "binwarp") -#+end_src - -Next, I’ll define my keybinds for when using Binwarp for emulating -mouse clicks as well as bépo-compatible mouse movements. This new -Binwarp mode is now available from the keybind ~s-m~ at top level. -#+begin_src lisp -(binwarp:define-binwarp-mode my-binwarp-mode "s-m" (:map *top-map*) - ((my/kbd "SPC") "ratclick 1") - ((my/kbd "RET") "ratclick 3") - ((my/kbd "c") "binwarp left") - ((my/kbd "t") "binwarp down") - ((my/kbd "s") "binwarp up") - ((my/kbd "r") "binwarp right") - ((my/kbd "i") "init-binwarp") - ((my/kbd "q") "exit-binwarp")) -#+end_src - -*** Bluetooth -:PROPERTIES: -:header-args:lisp: :mkdirp yes :tangle ~/.stumpwm.d/bluetooth.lisp :noweb yes -:END: -Although there is a Bluetooth module for the modeline, this is about -the extent to which StumpWM can interact with the system’s Bluetooth. -However, I wish for some more interactivity, like powering on and -off Bluetooth, connecting to devices and so on. - -Firstly, our code relies on ~cl-ppcre~, so let’s quickload it. -#+begin_src lisp -(ql:quickload :cl-ppcre) -#+end_src - -Let’s indicate which command we’ll be using. -#+begin_src lisp -(defvar *bluetooth-command* "bluetoothctl" - "Base command for interacting with bluetooth.") -#+end_src - -**** Utilities -We’ll need a couple of functions that will take care of stuff for us, -so we don’t have to repeat ourselves. The first one is a way for us to -share a message. The function ~bluetooth-message~ will first display -~Bluetooth:~ in green, then it will display the message we want it to -display. -#+begin_src lisp -(defun bluetooth-message (&rest message) - (message (format nil - "^2Bluetooth:^7 ~{~A~^ ~}" - message))) -#+end_src - -This function is a builder function which will create our commands. -For instance, src_lisp[:exports code]{(bluetooth-make-command "power" -"on")} will return ~"bluetoothctl power on"~ with ~*bluetooth-ctl*~ set as -~"bluetoothctl"~ --- simply put, it joins ~*bluetooth-command*~ with ~args~ -with a space as their separator. -#+begin_src lisp -(defun bluetooth-make-command (&rest args) - (format nil - "~a ~{~A~^ ~}" - ,*bluetooth-command* - args)) -#+end_src - -Now we can put ~bluetooth-make-command~ to use with ~bluetooth-command~ -which will actually run the result of the former. As you can see, it -also collects the output, so we can display it later in another -function. -#+begin_src lisp -(defmacro bluetooth-command (&rest args) - `(run-shell-command (bluetooth-make-command ,@args) t)) -#+end_src - -Finally, ~bluetooth-message-command~ is the function that both executes -and also displays the result of the bluetooth command we wanted to see -executed. Each argument of the command is a separate string. For -instance, if we want to power on the bluetooth on our device, we can -call src_lisp[:exports code]{(bluetooth-message-command "power" -"on")}. -#+begin_src lisp -(defmacro bluetooth-message-command (&rest args) - `(bluetooth-message (bluetooth-command ,@args))) -#+end_src - -**** Toggle Bluetooth On and Off -This part is easy. Now that we can call our Bluetooth commands easily, -we can easily define how to turn on Bluetooth. -#+begin_src lisp -(defcommand bluetooth-turn-on () () - "Turn on bluetooth." - (bluetooth-message-command "power" "on")) -#+end_src - -And how to power it off. -#+begin_src lisp -(defcommand bluetooth-turn-off () () - "Turn off bluetooth." - (bluetooth-message-command "power" "off")) -#+end_src - -**** Bluetooth Devices -In order to manipulate Bluetooth device, which we can represent as a -MAC address and a name, we can create a structure that will make use -of a constructor for simpler use. The constructor -~make-bluetooth-device-from-command~ expects an entry such as ~Device -00:00:00:00:00:00 Home Speaker~. The constructor discards the term -~Device~ and stores the MAC address separately from the rest of the -string which is assumed to be the full name of the device. -#+begin_src lisp -(defstruct (bluetooth-device - (:constructor - make-bluetooth-device (&key (address "") - (name nil))) - (:constructor - make-bluetooth-device-from-command - (&key (raw-name "") - &aux (address (cadr (cl-ppcre:split " " raw-name))) - (full-name (format nil "~{~A~^ ~}" (cddr (cl-ppcre:split " " raw-name))))))) - address - (full-name (progn - (format nil "~{~A~^ ~}" name)))) -#+end_src - -We can now collect our devices easily. -#+begin_src lisp -(defun bluetooth-get-devices () - (let ((literal-devices (bluetooth-command "devices"))) - (mapcar (lambda (device) - (make-bluetooth-device-from-command :raw-name device)) - (cl-ppcre:split "\\n" literal-devices)))) -#+end_src - -**** Connect to a device -When we want to connect to a Bluetooth device, we always need -Bluetooth turned on, so ~bluetooth-turn-on~ will always be called. Then -the function will attempt to connect to the device specified by the -~device~ argument, whether the argument is a Bluetooth structure as -defined above or a plain MAC address. -#+begin_src lisp -(defun bluetooth-connect-device (device) - (progn - (bluetooth-turn-on) - (cond ((bluetooth-device-p device) ;; it is a bluetooth-device structure - (bluetooth-message-command "connect" - (bluetooth-device-address device))) - ((stringp device) ;; assume it is a MAC address - (bluetooth-message-command "connect" device)) - (t (message (format nil "Cannot work with device ~a" device)))))) -#+end_src - -The command to connect to a device displays a choice between the -collected Bluetooth device and the user only has to select it. It will -then attempt to connect to it. -#+begin_src lisp -(defcommand bluetooth-connect () () - (sb-thread:make-thread - (lambda () - (let* ((devices (bluetooth-get-devices)) - (choice (cadr (stumpwm:select-from-menu - (stumpwm:current-screen) - (mapcar (lambda (device) - `(,(bluetooth-device-full-name device) ,device)) - devices))))) - (bluetooth-connect-device choice))))) -#+end_src - -**** Keybinds -It’s all nice and all, but typing manually the commands with ~s-SPC ;~ -is a bit tiring, so let’s define our Bluetooth keymap which we will -bind to ~s-SPC B~. -#+name: bluetooth-keymap -| Keychord | Command | -|----------+--------------------| -| ~c~ | ~bluetooth-connect~ | -| ~o~ | ~bluetooth-turn-on~ | -| ~O~ | ~bluetooth-turn-off~ | - -#+begin_src lisp -(defvar *my-bluetooth-keymap* - (let ((m (make-sparse-keymap))) - <> - m)) - -(define-key *root-map* (my/kbd "B") '*my-bluetooth-keymap*) -#+end_src - -*** NetworkManager integration -It is possible to have some kind of integration between StumpWM and -NetworkManager. To do so, we have to load the related module, then -create the two keybinds described in [[nm-keybinds]]. -#+name: nm-keybinds -#+caption: ~*my-nm-keybinds*~ -| Keychord | Command | -|----------+---------------------------| -| ~W~ | ~nm-list-wireless-networks~ | - -A call to src_lisp[:exports code]{(ql:quickload :dbus)} is necessary -for this module. Installing the ~dbus~ module in turn requires the -library ~libfixposix~ installed on the user’s machine. On Arch, you can -install it like so using ~paru~: -#+begin_src fish -paru -S libfixposix --noconfirm -#+end_src - -#+begin_src lisp -(ql:quickload :dbus) - -(load-module "stump-nm") - -<> -#+end_src - -*** Pinentry -Out with GTK2’s pinentry program! Let’s use StumpWM’s! At least that’s -what I’d like to say, but unfortunately there is a bug in the text -reading devices of StumpWM that prevent the user from using modifiers -when entering a password such as AltGr, so I can’t use it : / -#+begin_src lisp -;; (load-module "pinentry") -#+end_src - -*** Sly -[[https://github.com/joaotavora/sly][Sly]] is a fork of SLIME with which I can connect StumpWM and Emacs -together. Technically this is already done to some level with -~stumpwm-mode~, but the latter doesn’t provide auto-completion or stuff -like that. - -The first thing to do is load ~slynk~, SLY’s server: -#+begin_src lisp -(ql:quickload :slynk) -#+end_src - -Now we can define a command to launch the server. I don’t want it to -run all the time, just when I need it. -#+begin_src lisp -(stumpwm:defcommand sly-start-server () () - "Start a slynk server for sly." - (sb-thread:make-thread (lambda () (slynk:create-server :dont-close t)))) - -(stumpwm:defcommand sly-stop-server () () - "Stop current slynk server for sly." - (sb-thread:make-thread (lambda () (slynk:stop-server 4005)))) -#+end_src - -*** ~swm-ssh~ -This module from the contrib repository scans the user’s ssh -configuration file and offers them a quick way of connecting to their -remote hosts. -#+begin_src lisp -(load-module "swm-ssh") -#+end_src - -The default terminal needs to be set, otherwise the module will try to -call ~urxvtc~ which is not installed on my system. -#+begin_src lisp -(setq swm-ssh:*swm-ssh-default-term* "kitty") -#+end_src - -Now, to call the main command of this module we can define the -following keybind. -#+begin_src lisp -(define-key *root-map* (my/kbd "s") "swm-ssh-menu") -#+end_src - -*** Systemd -:PROPERTIES: -:header-args:lisp: :mkdirp yes :tangle ~/.stumpwm.d/systemd.lisp :noweb yes -:END: -I’m currently in the process of writing functions to interact with -Systemd directly through StumpWM. For now, not much work is done, but -it’s a start. - -Firstly, I have the following function that lists all the system or -user services. -#+begin_src lisp -(defun systemd-get-services (&key user-p) - "Collect all systemd services running. - -If USER-P is t, collect user services, otherwise collect system -services. - -The value returned is a list of lists. The first element is the -service’s name, the second is its load state, the third the high-level -activation state of the service, and the fourth its low-level -activation state." - (mapcar (lambda (elt) - (multiple-value-bind (_ result) - (ppcre:scan-to-strings "(.*\\.service) *([^ ]+) *([^ ]+) *([^ ]+).*" - elt) - result)) - (ppcre:split - " *\\n●? *" - (ppcre:regex-replace - "^ *" - (run-shell-command (concat "systemctl list-units --type service --all -q" - (if user-p " --user" "")) - t) - "")))) -#+end_src - -The only command I have right now is for listing the system or user -services with ~message~. Unfortunately, if there are too many services, -the list will overflow the screen. I do not know how to fix that yet. -I set the timeout to 600 seconds in order to have all the time in the -world to read the services list. It goes away as soon as something -else appears, such as a ~s-SPC C-g~ since I have ~which-key-mode~ enabled. -#+begin_src lisp -(defcommand systemd-list-services (user-p) ((:y-or-n "User services? ")) - (let ((stumpwm::*timeout-wait* 600)) - (message (format nil "~{~a~^~&~}" - (mapcar (lambda (service) - (let ((name (aref service 0)) - (load (aref service 1)) - (active (aref service 2)) - (sub (aref service 3))) - (cond ((member load '("not-found" "bad-setting" - "error" "masked") - :test #'string=) - (format nil - "^~A~A^0 ^> Load: ~12@A" - (if (string= "masked" load) 4 1) - name load)) - ((member active '("failed" "reloading" "activating" - "deactivating" "inactive") - :test #'string=) - (format nil "^~A~A^0 ^>Active: ~12@A" - (case active - ("failed" 1) - ("inactive" 0) - (t 3)) - name - active)) - (t (format nil "^2~A^0 ^> Sub: ~12@A" name sub))))) - (systemd-get-services :user-p user-p)))))) -#+end_src - -** org functions :noexport: -#+name: keybinds-gen -#+header: :wrap "src lisp :exports none" :exports none :noweb yes -#+begin_src emacs-lisp :var map="m" keybinds=media-management -(mapconcat (lambda (keybind) - (format "%s" (let* ((filter (lambda (str) - (replace-regexp-in-string "^~\\|~$" "" str))) - (key (funcall filter (car keybind))) - (function (funcall filter (cadr keybind)))) - `(define-key ,map - (my/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 (:exit-on ((kbd \"RET\") (kbd \"ESC\")" - "\n (kbd \"C-g\") (kbd \"q\")))" - "\n " - ,(mapconcat (lambda (keybind) - (format "%s" - (let* ((filter (lambda (str) - (replace-regexp-in-string "^~\\|~$" "" str))) - (key (funcall filter (car keybind))) - (command (funcall filter (cadr keybind)))) - `((my/kbd ,(format "\"%s\"" key)) - ,(format "\"%s\"" command))))) - keys - "\n "))) -#+end_src - -#+RESULTS[b7d91bafe659a77aef5059ae17859a7fc715255e]: interactive-gen -#+begin_src lisp -(define-interactive-keymap inter - (:exit-on ((kbd "RET") (kbd "ESC") - (kbd "C-g") (kbd "q"))) - ((my/kbd "c") "mpd-prev") - ((my/kbd "t") "mpd-volume-down") - ((my/kbd "s") "mpd-volume-up") - ((my/kbd "r") "mpd-next")) -#+end_src - -#+name: num-to-char -#+begin_src emacs-lisp :var table=number-to-char-table num=2 -(let* ((filter (lambda (str) - (replace-regexp-in-string "^~\\|~$" "" str))) - (char (funcall filter (cadr (assoc num table))))) - (if (string= char "\"") - "\\\"" - char)) -#+end_src - -#+RESULTS[f66a1c4f98f4e8def9867862da252249b6a65749]: num-to-char -: « diff --git a/docs/stumpwm/colours.org b/docs/stumpwm/colours.org new file mode 100644 index 0000000..69dd770 --- /dev/null +++ b/docs/stumpwm/colours.org @@ -0,0 +1,85 @@ +#+title: StumpWM — Colours +#+setupfile: ../headers +#+property: header-args:emacs-lisp :tangle no :exports results :cache yes :noweb yes + +* StumpWM — Colours +** Colours +:PROPERTIES: +:header-args:lisp: :mkdirp yes :tangle ~/.stumpwm.d/colors.lisp +:END: +If you’ve had 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 colours: + +#+name: nord-colours +#+caption: Nord Theme +| 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 +#+header: :wrap src lisp +#+begin_src emacs-lisp :var colors=nord-colours +(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 + +Finally, let’s also modify the default colors StumpWM has. I’ll try to +respect the original colours while converting them to Nord. We also +need to reload them now that we modified them. +#+begin_src lisp +(setq *colors* + `(,phundrak-nord1 ;; 0 black + ,phundrak-nord11 ;; 1 red + ,phundrak-nord14 ;; 2 green + ,phundrak-nord13 ;; 3 yellow + ,phundrak-nord10 ;; 4 blue + ,phundrak-nord14 ;; 5 magenta + ,phundrak-nord8 ;; 6 cyan + ,phundrak-nord5)) ;; 7 white + +(when *initializing* + (update-color-map (current-screen))) +#+end_src + +And with that we’re done! diff --git a/docs/stumpwm/commands.org b/docs/stumpwm/commands.org new file mode 100644 index 0000000..d198d4a --- /dev/null +++ b/docs/stumpwm/commands.org @@ -0,0 +1,53 @@ +#+title: StumpWM — Commands +#+setupfile: ../headers +#+property: header-args:emacs-lisp :tangle no :exports results :cache yes :noweb yes + +* StumpWM — Commands +** Commands +:PROPERTIES: +:header-args:lisp: :mkdirp yes :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." + (sb-thread:make-thread (lambda () (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}." + (sb-thread:make-thread + (lambda () + (run-shell-command (if program + (format nil "kitty ~A" program) + "kitty"))))) +#+end_src diff --git a/docs/stumpwm/groups.org b/docs/stumpwm/groups.org new file mode 100644 index 0000000..bdaa76f --- /dev/null +++ b/docs/stumpwm/groups.org @@ -0,0 +1,95 @@ +#+title: StumpWM — Groups and Placement +#+setupfile: ../headers +#+property: header-args:emacs-lisp :tangle no :exports results :cache yes :noweb yes + +* StumpWM — Groups and Placement +** Groups and Placement +:PROPERTIES: +:header-args:lisp: :mkdirp yes :tangle ~/.stumpwm.d/placement.lisp :noweb yes +:END: +I don’t need many groups, rarely more than five. Hence, here are my +five default groups. + +#+name: list-groups +#+caption: Five default groups for my StumpWM setup +| Groups | Number | Type | +|---------+--------+--------| +| [EMACS] | 1 | Tiling | +| [TERM] | 2 | Tiling | +| [WWW] | 3 | Tiling | +| [PRIV] | 4 | Tiling | +| [FILES] | 5 | Tiling | + +#+name: gen-groups +#+header: :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 2 group))) + (format "%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[4dad230a37c25af58c4f38eef1dd4f762b295f71]: gen-groups +: (grename "[EMACS]") +: (gnewbg "[TERM]") +: (gnewbg "[WWW]") +: (gnewbg "[PRIV]") +: (gnewbg "[FILES]") + +Groups are specified this way: +#+begin_src lisp +(when *initializing* + <>) +#+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 behaviour. +#+begin_src lisp +(clear-window-placement-rules) +#+end_src + +#+name: gen-rules +#+header: :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 + +Dynamic groups, if any is created, should have a split ratio of half +of the available space. +#+begin_src lisp +(setf *dynamic-group-master-split-ratio* 1/2) +#+end_src diff --git a/docs/stumpwm/index.org b/docs/stumpwm/index.org new file mode 100644 index 0000000..d369727 --- /dev/null +++ b/docs/stumpwm/index.org @@ -0,0 +1,92 @@ +#+title: StumpWM +#+setupfile: ../headers +#+property: header-args:emacs-lisp :tangle no :exports results :cache yes :noweb yes + +[[file:../img/stumpwm.png]] + +* StumpWM +** Introduction +*** What is StumpWM? +[[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 a +dynamic tiling window manager like [[file:Deprecated/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? +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 +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 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 +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~ :: This file defines colours that will be used 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; +- ~utilities.lisp~ :: Here you can find my StumpWM configuration that + isn’t really related to the rest of the config, for instance utility + code for connecting by SSH to some host. +- ~theme.lisp~ :: manages the colour theme of StumpWM, the default + placement of some windows and StumpWM’s gaps. + +You will also find below my ~xinit~ file for StumpWM, exported to +~$HOME/.xinitrc.stumpwm~, which I use to start Stump through ~startx +~/.xinitrc.stumpwm~. +#+begin_src sh :tangle ~/.xinitrc.stumpwm :shebang "#!/bin/sh" +# this makes it work in Ubuntu +xhost +SI:localuser:$USER + +# Set fallback pointer +xsetroot -cursor_name left_ptr + +# Fix scrolling on some GTK3 applications +export GDK_CORE_DEVICE_EVENTS=1 + +# in case Java applications display /nothing/ +# wmname LG3D +# export _JAVA_AWT_WM_NONREPARENTING=1 + +autorandr -l home + +exec stumpwm +#+end_src diff --git a/docs/stumpwm/init.org b/docs/stumpwm/init.org new file mode 100644 index 0000000..2834291 --- /dev/null +++ b/docs/stumpwm/init.org @@ -0,0 +1,153 @@ +#+title: StumpWM — Basic Configuration +#+setupfile: ../headers +#+property: header-args:emacs-lisp :tangle no :exports results :cache yes :noweb yes + +* StumpWM — Basic Configuration +** Init File +:PROPERTIES: +:header-args:lisp: :mkdirp yes :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:../scripts.md#autostart][seen here]], + +#+begin_src lisp +(run-shell-command "autostart") +#+end_src + +Next I need to register the AltGr key so it works correctly when used. +On my system, the value of ~*altgr-offset*~ is 4, but on yours it might +be 6, so be careful and refer to the manual on that matter. +#+begin_src lisp +(setf *altgr-offset* 4) +(register-altgr-as-modifier) +#+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 | +|-------------------| +| bluetooth.lisp | +| commands.lisp | +| placement.lisp | +| keybindings.lisp | +| theme.lisp | +| utilities.lisp | +| modeline.lisp | +| systemd.lisp | + +#+name: gen-load-files +#+header: :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[ed4f3fe4f7f82b11cd3cd262578abc7146f5807d]: gen-load-files +#+begin_src lisp +(load "~/.stumpwm.d/bluetooth.lisp") +(load "~/.stumpwm.d/commands.lisp") +(load "~/.stumpwm.d/placement.lisp") +(load "~/.stumpwm.d/keybindings.lisp") +(load "~/.stumpwm.d/theme.lisp") +(load "~/.stumpwm.d/utilities.lisp") +(load "~/.stumpwm.d/modeline.lisp") +(load "~/.stumpwm.d/systemd.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 to use the super key to move floating +windows and window focus to transfer from one window to another only +on click. +#+begin_src lisp +(setf *mouse-focus-policy* :click + ,*float-window-modifier* :SUPER) +#+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 | +|-----------------+--------------------------------------------------| +| 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 | +| mpd | Interact with MPD | +| stump-backlight | Native management of backlight in StumpWM | +| urgentwindows | Get urgent windows | + +#+name: gen-load-modules +#+header: :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[0cbba236372280cb2eb6a1e277cda84938e15d46]: gen-load-modules +#+begin_src lisp +(load-module "beckon") +(load-module "end-session") +(load-module "globalwindows") +(load-module "mpd") +(load-module "stump-backlight") +(load-module "urgentwindows") +#+end_src + +In order to be able to use MPD from StumpWM itself, we’ll need to +connect to it. +#+begin_src lisp +(mpd:mpd-connect) +#+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. diff --git a/docs/stumpwm/keybindings.org b/docs/stumpwm/keybindings.org new file mode 100644 index 0000000..55cdde9 --- /dev/null +++ b/docs/stumpwm/keybindings.org @@ -0,0 +1,776 @@ +#+title: StumpWM — Keybindings +#+setupfile: ../headers +#+property: header-args:emacs-lisp :tangle no :exports results :cache yes :noweb yes + +* StumpWM — Keybindings +** Keybinds +:PROPERTIES: +:header-args:lisp: :mkdirp yes :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 literally 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*~. + +Anyway, as mentioned 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 (my/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 +#+caption: Characters equivalent to the 1 to 0 keys in the bépo layout +| Number | Character | +|--------+-----------| +| 1 | ~"~ | +| 2 | ~«~ | +| 3 | ~»~ | +| 4 | ~(~ | +| 5 | ~)~ | +| 6 | ~@~ | +| 7 | ~+~ | +| 8 | ~-~ | +| 9 | ~/~ | +| 0 | ~*~ | + +So if you see any weird keybind involving these characters, this is +because of my layout. + +Something a bit annoying though is Lisp doesn’t know some characters +by their actual name, rather by another one that I find too long and +too bothersome to remember. So here’s a list, if you see any of the +characters on the left column in my config, with the function +described below, my actual config will use their name as specified in +the right column. + +#+name: tbl-char-to-name +#+caption: Internal name of some characters +| Character | Name | +|-----------+----------------| +| ~«~ | ~guillemotleft~ | +| ~»~ | ~guillemotright~ | + +#+name: chars-table-to-list +#+header: :exports none :noweb yes :results verbatim +#+begin_src emacs-lisp :var chars=tbl-char-to-name +;; chars +(let ((filter (lambda (str) + (replace-regexp-in-string "^~\\|~$" "" str)))) + (mapcar (lambda (row) + `(,(apply filter `(,(car row))) . ,(apply filter `(,(cadr row))))) + chars)) +#+end_src + +#+RESULTS[8ceb9b882276931ad0dba7dcf38d163f7674f547]: chars-table-to-list +: (("«" . "guillemotleft") ("»" . "guillemotright")) + +To convert these characters, I have my own macro which is a wrapper +around the function ~kbd~. + +#+name: my-kbd-defun +#+begin_src lisp :noweb yes +(defun my/kbd (keys) + "Prepares KEYS for function `stumpwm:kbd'. +If a character declared in the car of a member of the variable char, +it is replaced with its cdr. This allows the user to input characters +such as « or » and have them replaced with their actual name when +`stumpwm:kbd' is called." + (kbd (let ((chars '<>)) + (dolist (row chars keys) + (setf keys (cl-ppcre:regex-replace-all (car row) keys (cdr row))))))) +#+end_src + +#+header: :exports none +#+begin_src lisp :noweb yes :tangle ~/.stumpwm.d/bluetooth.lisp +<> +#+end_src + +#+header: :exports none +#+begin_src lisp :noweb yes :tangle ~/.stumpwm.d/utilities.lisp +<> +#+end_src + +*** Applications +When I speak about applications, I speak about programs and scripts in +general. With these keymaps, I can launch programs I have often 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))) + <> + m)) +#+end_src + +Let’s also create a keymap for screenshots. + +#+name: screenshot-keymap +#+caption: ~*my-screenshot-keymap*~ +| Keychord | Function | +|----------+----------------------------| +| ~d~ | ~exec flameshot gui -d 3000~ | +| ~s~ | ~exec flameshot full~ | +| ~S~ | ~exec flameshot gui~ | + +Here’s the equivalent in Common Lisp. +#+begin_src lisp +(defvar *my-screenshot-keymap* + (let ((m (make-sparse-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~ | +| ~B~ | ~exec qutebrowser~ | +| ~d~ | ~exec discord~ | +| ~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))) + <> + m)) +#+end_src + +The application keymap can now be bound to the root map like so: +#+begin_src lisp +(define-key *root-map* (my/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, +and the ~XF86Mail~ key opens mu4e in Emacs. +#+begin_src lisp +(define-key *top-map* (my/kbd "s-RET") "term") +(define-key *top-map* (my/kbd "Print") '*my-screenshot-keymap*) +(define-key *top-map* (my/kbd "XF86Mail") "exec emacsclient -c -e \"(mu4e)\"") +#+end_src + +*** End of Session, Powering Off, and the Likes +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 +wishes to do. + +#+name: end-session-keymap +#+caption: ~*my-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))) + <> + m)) +#+end_src + +Which is bound in the root map to ~q~: +#+begin_src lisp +(define-key *root-map* (my/kbd "q") '*my-end-session-keymap*) +#+end_src + +*** Groups +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) + (my/kbd ,(format "%s-%s" + mod + (if (string= "yes" convert) + (format "<>" group-nbr) + (number-to-string group-nbr)))) + ,(format "%s %d" action group-nbr))))) + groups + "\n") +#+end_src + +#+RESULTS[282113d17ea21f02dc7c87059d787e62b5886b16]: group-keybind-gen +: "(define-key *top-map* (my/kbd \"s-1\") \"gselect 1\") +: (define-key *top-map* (my/kbd \"s-2\") \"gselect 2\") +: (define-key *top-map* (my/kbd \"s-3\") \"gselect 3\") +: (define-key *top-map* (my/kbd \"s-4\") \"gselect 4\") +: (define-key *top-map* (my/kbd \"s-5\") \"gselect 5\")" + +#+header: :cache yes :noweb yes :wrap src lisp +#+begin_src emacs-lisp +<> +#+end_src + +#+RESULTS[35268fd11d1fe4fa4065c98a0b7bc723a56c09a7]: +#+begin_src lisp +(define-key *top-map* (my/kbd "s-<>") "gselect 1") +(define-key *top-map* (my/kbd "s-<>") "gselect 2") +(define-key *top-map* (my/kbd "s-<>") "gselect 3") +(define-key *top-map* (my/kbd "s-<>") "gselect 4") +(define-key *top-map* (my/kbd "s-<>") "gselect 5") +#+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 +<> +#+end_src + +#+RESULTS[bd9b499dfad0fdf1f54db146ded1a60c6674f8ea]: +#+begin_src lisp +(define-key *top-map* (my/kbd "s-1") "gmove-and-follow 1") +(define-key *top-map* (my/kbd "s-2") "gmove-and-follow 2") +(define-key *top-map* (my/kbd "s-3") "gmove-and-follow 3") +(define-key *top-map* (my/kbd "s-4") "gmove-and-follow 4") +(define-key *top-map* (my/kbd "s-5") "gmove-and-follow 5") +#+end_src + +If I want to send a window to another group without following it, I’ll +use ~s-S-C-~, which gives us the following: +#+begin_src emacs-lisp :wrap src lisp +<> +#+end_src + +#+RESULTS[68f918c24a6cc0efa1503ed57841c40f4ec2ec4a]: +#+begin_src lisp +(define-key *top-map* (my/kbd "s-C-1") "gmove-and-follow 1") +(define-key *top-map* (my/kbd "s-C-2") "gmove-and-follow 2") +(define-key *top-map* (my/kbd "s-C-3") "gmove-and-follow 3") +(define-key *top-map* (my/kbd "s-C-4") "gmove-and-follow 4") +(define-key *top-map* (my/kbd "s-C-5") "gmove-and-follow 5") +#+end_src + +And if I want to bring the windows of another group into the current +group, I’ll use ~s-C-~: +#+begin_src emacs-lisp :wrap src lisp :exports results +<> +#+end_src + +#+RESULTS[36b3ded631cd0bad400c4847f6937cde4f11b4b7]: +#+begin_src lisp +(define-key *top-map* (my/kbd "s-C-<>") "gmove-and-follow 1") +(define-key *top-map* (my/kbd "s-C-<>") "gmove-and-follow 2") +(define-key *top-map* (my/kbd "s-C-<>") "gmove-and-follow 3") +(define-key *top-map* (my/kbd "s-C-<>") "gmove-and-follow 4") +(define-key *top-map* (my/kbd "s-C-<>") "gmove-and-follow 5") +#+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* (my/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* (my/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* (my/kbd "s-ESC") "gother") +#+end_src + +*** Frames and Windows management +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))) + <> + m)) + +(defvar *my-frames-management-keymap* + (let ((m (make-sparse-keymap))) + <> + m)) +#+end_src + +Let’s bind ~*my-frames-management-keymap*~ in ~*root-keymap*~: +#+begin_src lisp +(define-key *root-map* (my/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 +#+caption: ~*top-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 +<> +#+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 + :exit-on ((kbd "RET") (kbd "ESC") + (kbd "C-g") (kbd "q"))) + ((my/kbd "c") "resize-direction left") + ((my/kbd "t") "resize-direction down") + ((my/kbd "s") "resize-direction up") + ((my/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, and I also grew used to ~s-o~ +doing the same thing. +#+begin_src lisp +(define-key *top-map* (my/kbd "s-TAB") "other-window") +(define-key *top-map* (my/kbd "s-o") "other-window") +#+end_src + +*** Windows management +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~ | ~delete-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))) + <> + m)) + +(define-key *root-map* (my/kbd "b") '*my-buffers-management-keymap*) +#+end_src + +*** Media and Media Control +My music is managed through MPD, and I often use ~playerctl~ commands in +order to interact with it without any GUI application. So, we’ll see a +lot of its usage here, and numerous commands used here come from the +~mpd~ minor mode loaded [[file:./stumpwm.md#init-file][above]]. + +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~ | ~mpd-prev~ | +| ~t~ | ~mpd-volume-down~ | +| ~s~ | ~mpd-volume-up~ | +| ~r~ | ~mpd-next~ | + +This can be translated in CommonLisp as: +#+begin_src lisp +<> +#+end_src + +We need to indicate also how much the volume is affected by +~mpd-volume-down~ and ~mpd-volume-up~. +#+begin_src lisp +(setf *mpd-volume-step* 2) +#+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 -perceived -dec 2~ | +| ~t~ | ~exec amixer -q set Master 2%- unmute~ | +| ~s~ | ~exec amixer -q set Master 2%+ unmute~ | +| ~r~ | ~exec xbacklight -perceived -inc 2~ | +| ~m~ | ~exec amixer -q set Master 1+ toggle~ | + +#+begin_src lisp +<> +#+end_src + +Then, let’s declare a keymap for our media controls. + +#+name: mpd-add-map +#+caption: ~*my-mpd-add-map*~ +| Keychord | Function | +|----------+---------------------------| +| ~a~ | ~mpd-search-and-add-artist~ | +| ~A~ | ~mpd-search-and-add-album~ | +| ~f~ | ~mpd-search-and-add-file~ | +| ~F~ | ~mpd-add-file~ | +| ~g~ | ~mpd-search-and-add-genre~ | +| ~t~ | ~mpd-search-and-add-title~ | + +#+name: mpd-browse-map +#+caption: ~*my-mpd-browse-map*~ +| Keychord | Function | +|----------+---------------------| +| ~a~ | ~mpd-browse-artists~ | +| ~A~ | ~mpd-browse-albums~ | +| ~g~ | ~mpd-browse-genres~ | +| ~p~ | ~mpd-browse-playlist~ | +| ~t~ | ~mpd-browse-tracks~ | + +#+name: media-management +#+caption: ~*my-media-keymap*~ +| Keychord | Function | +|----------+-----------------------------------| +| ~.~ | ~media-interactive~ | +| ~«~ | ~exec playerctl previous~ | +| ~»~ | ~exec playerctl next~ | +| ~a~ | ~'*my-mpd-add-map*~ | +| ~b~ | ~'*my-mpd-browse-map*~ | +| ~c~ | ~mpd-clear~ | +| ~m~ | ~mpc-interactive~ | +| ~p~ | ~exec playerctl play-pause~ | +| ~s~ | ~exec playerctl stop~ | +| ~u~ | ~mpd-update~ | +| ~n~ | ~exec kitty ncmpcpp -q~ | +| ~v~ | ~exec kitty ncmpcpp -qs visualizer~ | + +Let’s translate this table in CommonLisp: +#+begin_src lisp +(defvar *my-mpd-add-map* + (let ((m (make-sparse-keymap))) + <> + m)) + +(defvar *my-mpd-browse-map* + (let ((m (make-sparse-keymap))) + <> + m)) + +(defvar *my-media-keymap* + (let ((m (make-sparse-keymap))) + <> + m)) + +(define-key *root-map* (my/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 playerctl play-pause~ | +| ~XF86AudioPause~ | ~exec playerctl pause~ | +| ~XF86AudioStop~ | ~exec playerctl stop~ | +| ~XF86AudioPrev~ | ~exec playerctl previous~ | +| ~XF86AudioNext~ | ~exec playerctl next~ | +| ~XF86AudioRewind~ | ~exec playerctl position -1~ | +| ~XF86AudioForward~ | ~exec playerctl position +1~ | +| ~XF86AudioRaiseVolume~ | ~exec pamixer -i 2~ | +| ~XF86AudioLowerVolume~ | ~exec pamixer -d 2~ | +| ~XF86AudioMute~ | ~exec pamixer -t~ | +| ~XF86MonBrightnessDown~ | ~exec xbacklight -perceived -dec 2~ | +| ~XF86MonBrightnessUp~ | ~exec xbacklight -perceived -inc 2~ | + +#+begin_src lisp +<> +#+end_src + +*** Misc +Finally, some misc keybinds on the root map which don’t really fit +anywhere else: + +#+name: misc-root-map +#+caption: ~*root-map*~ +| Keychord | Function | +|----------+------------| +| ~SPC~ | ~colon~ | +| ~B~ | ~beckon~ | +| ~C-b~ | ~banish~ | +| ~l~ | ~exec plock~ | +| ~r~ | ~reload~ | + +#+begin_src lisp +<> +#+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 +#+caption: ~*my-keyboard-layout-keymap*~ +| 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))) + <> + m)) + +(define-key *root-map* (my/kbd "k") '*my-keyboard-layout-keymap*) +#+end_src + +* PRIVATE org functions :noexport: +#+name: keybinds-gen +#+header: :wrap "src lisp :exports none" :exports none :noweb yes +#+begin_src emacs-lisp :var map="m" keybinds=media-management +(mapconcat (lambda (keybind) + (format "%s" (let* ((filter (lambda (str) + (replace-regexp-in-string "^~\\|~$" "" str))) + (key (funcall filter (car keybind))) + (function (funcall filter (cadr keybind)))) + `(define-key ,map + (my/kbd ,(format "\"%s\"" key)) + ,(if (string-prefix-p "'" function t) + function + (format "\"%s\"" function)))))) + keybinds + "\n") +#+end_src + +#+name: num-to-char +#+begin_src emacs-lisp :var table=number-to-char-table num=2 +(let* ((filter (lambda (str) + (replace-regexp-in-string "^~\\|~$" "" str))) + (char (funcall filter (cadr (assoc num table))))) + (if (string= char "\"") + "\\\"" + char)) +#+end_src + +#+RESULTS[f66a1c4f98f4e8def9867862da252249b6a65749]: num-to-char +: « + +#+name: interactive-gen +#+begin_src emacs-lisp :var name="inter" keys=inter-mpc +(format "%s" + `(define-interactive-keymap ,name + "\n (:exit-on ((kbd \"RET\") (kbd \"ESC\")" + "\n (kbd \"C-g\") (kbd \"q\")))" + "\n " + ,(mapconcat (lambda (keybind) + (format "%s" + (let* ((filter (lambda (str) + (replace-regexp-in-string "^~\\|~$" "" str))) + (key (funcall filter (car keybind))) + (command (funcall filter (cadr keybind)))) + `((my/kbd ,(format "\"%s\"" key)) + ,(format "\"%s\"" command))))) + keys + "\n "))) +#+end_src + +#+RESULTS[b7d91bafe659a77aef5059ae17859a7fc715255e]: interactive-gen +#+begin_src lisp +(define-interactive-keymap inter + (:exit-on ((kbd "RET") (kbd "ESC") + (kbd "C-g") (kbd "q"))) + ((my/kbd "c") "mpd-prev") + ((my/kbd "t") "mpd-volume-down") + ((my/kbd "s") "mpd-volume-up") + ((my/kbd "r") "mpd-next")) +#+end_src + +#+RESULTS[b7d91bafe659a77aef5059ae17859a7fc715255e]: interactive-gen +#+begin_src lisp +(define-interactive-keymap inter + (:exit-on ((kbd "RET") (kbd "ESC") + (kbd "C-g") (kbd "q"))) + ((my/kbd "c") "mpd-prev") + ((my/kbd "t") "mpd-volume-down") + ((my/kbd "s") "mpd-volume-up") + ((my/kbd "r") "mpd-next")) +#+end_src + +#+name: list-groups +#+caption: Five default groups for my StumpWM setup +| Groups | Number | Type | +|---------+--------+--------| +| [EMACS] | 1 | Tiling | +| [TERM] | 2 | Tiling | +| [WWW] | 3 | Tiling | +| [PRIV] | 4 | Tiling | +| [FILES] | 5 | Tiling | diff --git a/docs/stumpwm/mode-line.org b/docs/stumpwm/mode-line.org new file mode 100644 index 0000000..94294a4 --- /dev/null +++ b/docs/stumpwm/mode-line.org @@ -0,0 +1,191 @@ +#+title: StumpWM — Mode-Line +#+setupfile: ../headers +#+property: header-args:emacs-lisp :tangle no :exports results :cache yes :noweb yes + +* StumpWM — Mode-Line +** Mode-Line +:PROPERTIES: +:header-args:lisp: :mkdirp yes :tangle ~/.stumpwm.d/modeline.lisp +:END: +The timeout of the modeline indicates how often it refreshes in +seconds. I think two seconds is good. +#+begin_src lisp +(setf *mode-line-timeout* 2) +#+end_src + +*** Formatting Options +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: %30t") +#+end_src + +*** Mode-Line Theme +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 colours 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 colour to Nord1, just in case. +#+begin_src lisp +(setf *mode-line-border-color* phundrak-nord1 + ,*mode-line-border-width* 0) +#+end_src + +*** Mode-Line Modules +Here are some modules that we will load for the modeline: + +#+name: modeline-modules +#+caption: Modules used by the modeline +| Module Name | Why Do I Need It? | +|------------------+--------------------------------------------------| +| battery-portable | Get information on the battery level of a laptop | +| cpu | Get the CPU usage | +| mpd | Display MPD’s status | +| mem | Get the memory usage | + +#+name: gen-load-modeline-modules +#+header: :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[20a1d5d9c6e0136d6a130b1c1b4bd4d742aead8a]: gen-load-modeline-modules +#+begin_src lisp +(load-module "battery-portable") +(load-module "cpu") +(load-module "mpd") +(load-module "mem") +#+end_src + +We need to set some variables, so modules can be displayed correctly. +Note that the character between the font switchers in the second CPU +formatter is U+E082, which symbolizes the CPU. +#+begin_src lisp +(setf cpu::*cpu-modeline-fmt* "%c" + cpu::*cpu-usage-modeline-fmt* "^f2^f0^[~A~2D%^]" + mem::*mem-modeline-fmt* "%a%p" + mpd:*mpd-modeline-fmt* "%a - %t" + mpd:*mpd-status-fmt* "%a - %t" + ,*hidden-window-color* "^**" + ,*mode-line-highlight-template* "«~A»") +#+end_src + +*** Generating the Mode-Line +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 [[file:./stumpwm.md#fonts][Fonts]]). + +#+name: modeline-format +#+caption: Formatters for the modeline +| 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 align to the right | | +| ~docker-running~ | Display number of docker containers currently running | yes | +| ~mu-unread~ | Display number of unread emails | yes | +| ~%m~ | Display current MPD song | | +| ~%C~ | Display CPU usage | | +| ~%M~ | Display RAM usage | | +| ~%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[4246baab1293d54bcd2223590f274152f24934c3]: modeline-format-gen +: (("%g") ("%W") ("^>") ("docker-running" . t) ("mu-unread" . t) ("%m") ("%C") ("%M") ("%B") ("%d")) + +#+begin_src lisp :noweb yes +(defvar *mode-line-formatter-list* + '<> + "List of formatters for the modeline.") +#+end_src + +As you can see, ~generate-modeline~ generates the string defining +~*screen-mode-line-format*~ from the list of formatters we gave it with +the table [[modeline-format]]. +#+begin_src lisp +(defun generate-modeline (elements &optional not-invertedp rightp) + "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 (format nil + " ^[~A^]^(:bg \"~A\") " + (format nil "^(:fg \"~A\")^(:bg \"~A\")^f1~A^f0" + (if (xor not-invertedp rightp) phundrak-nord1 phundrak-nord3) + (if (xor not-invertedp rightp) phundrak-nord3 phundrak-nord1) + (if rightp "" "")) + (if not-invertedp phundrak-nord3 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) + (if (string= "^>" (caar elements)) t rightp))))))) +#+end_src + +It is then easy to define a command that can call this function and +set this variable, so we can sort of reload the mode-line. +#+begin_src lisp +(defcommand reload-modeline () () + "Reload modeline." + (sb-thread:make-thread + (lambda () + (setf *screen-mode-line-format* + (cdr (generate-modeline *mode-line-formatter-list*)))))) +#+end_src + +And actually, let’s reload the modeline immediately. +#+begin_src lisp +(reload-modeline) +#+end_src + +*** TODO Investigate why ~stumptray~ acts up :noexport: +Systray overlaps with the far-right part of the modeline. + +# 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. diff --git a/docs/stumpwm/theme.org b/docs/stumpwm/theme.org new file mode 100644 index 0000000..810fa43 --- /dev/null +++ b/docs/stumpwm/theme.org @@ -0,0 +1,202 @@ +#+title: StumpWM — Theme +#+setupfile: ../headers +#+property: header-args:emacs-lisp :tangle no :exports results :cache yes :noweb yes + +* StumpWM — Theme +** Theme +:PROPERTIES: +:header-args:lisp: :mkdirp yes :tangle ~/.stumpwm.d/theme.lisp :noweb yes +:END: +As in the modeline file, the first thing we’ll do is to load our +colours. +#+begin_src lisp +(load "~/.stumpwm.d/colors.lisp") +#+end_src + +We can now go onto more serious business. + +*** Fonts +This gave me quite the headache when I tried to set this up: in order +to use TTF fonts (note: it is not possible to use OTF fonts, see +below), 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). + +In order for it to work, install [[https://www.quicklisp.org/beta/][quicklisp]] and don’t forget to run +src_lisp[:exports code]{(ql:add-to-init-file)} so it is loaded each +time you start your Lisp interpreter. ~SBCL~ should be your CommonLisp +interpreter of choice since StumpWM is generally compiled with it. The +main advantage is also that SBCL supports multithreading, unlike +CLisp. In case StumpWM doesn’t find your font, spin up SBCL and +execute the following lines: +#+begin_src lisp :tangle no +(ql:quickload :clx-truetype) +(xft:cache-fonts) +#+end_src + +If you want a list of font families available, you can execute the +following: +#+begin_src lisp :tangle no +(clx-truetype:get-font-families) +#+end_src + +If you want to know the subfamilies of a certain family, you can +execute this: +#+begin_src lisp :tangle no +(clx-truetype:get-font-subfamilies "Family Name") +#+end_src + +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~ (~~ 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 | +| siji | Medium | 10 | +| FantasqueSansMono Nerd Font Mono | Regular | 9.5 | + +#+name: gen-fonts +#+header: :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[1693001a9a9c0e274a9b7097665e9795783ae8a2]: 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) + ,(make-instance 'xft:font :family "siji" :subfamily "Medium" :size 10 :antialias t) + ,(make-instance 'xft:font :family "FantasqueSansMono Nerd Font Mono" :subfamily "Regular" :size 9.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 +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 colours 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 + +I also have a [[https://github.com/Phundrak/stumpwm/tree/feature/no-hardcoded-which-key-format][StumpWM fork]] that introduces two new variables for +customizing which-key keybindings. I submitted a [[https://github.com/stumpwm/stumpwm/pull/931][pull request]], so it +might come one day to StumpWM. +#+begin_src lisp +(setf *key-seq-color* "^2") +(setf *which-key-format* (concat *key-seq-color* "*~5a^n ~a")) +#+end_src + +*** Message and Input Windows +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 +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* 40) +#+end_src + +Finally, let’s enable our gaps: +#+begin_src lisp +(when *initializing* + (swm-gaps:toggle-gaps)) +#+end_src diff --git a/docs/stumpwm/utilities.org b/docs/stumpwm/utilities.org new file mode 100644 index 0000000..f84a647 --- /dev/null +++ b/docs/stumpwm/utilities.org @@ -0,0 +1,372 @@ +#+title: StumpWM — Utilities +#+setupfile: ../headers +#+property: header-args:emacs-lisp :tangle no :exports results :cache yes :noweb yes + +* StumpWM — Utilities +** Utilities +:PROPERTIES: +:header-args:lisp: :mkdirp yes :tangle ~/.stumpwm.d/utilities.lisp :noweb yes +:END: +Part of my configuration is not really related to StumpWM itself, or +rather it adds new behaviour StumpWM doesn’t have. ~utilities.lisp~ +stores all this code in one place. + +*** Binwarp +Binwarp allows the user to control their mouse from the keyboard, +basically eliminating the need for a physical mouse in daily usage of +the workstation (though a physical mouse stays useful for games and +such). +#+begin_src lisp +(load-module "binwarp") +#+end_src + +Next, I’ll define my keybinds for when using Binwarp for emulating +mouse clicks as well as bépo-compatible mouse movements. This new +Binwarp mode is now available from the keybind ~s-m~ at top level. +#+begin_src lisp +(binwarp:define-binwarp-mode my-binwarp-mode "s-m" (:map *top-map*) + ((my/kbd "SPC") "ratclick 1") + ((my/kbd "RET") "ratclick 3") + ((my/kbd "c") "binwarp left") + ((my/kbd "t") "binwarp down") + ((my/kbd "s") "binwarp up") + ((my/kbd "r") "binwarp right") + ((my/kbd "i") "init-binwarp") + ((my/kbd "q") "exit-binwarp")) +#+end_src + +*** Bluetooth +:PROPERTIES: +:header-args:lisp: :mkdirp yes :tangle ~/.stumpwm.d/bluetooth.lisp :noweb yes +:END: +Although there is a Bluetooth module for the modeline, this is about +the extent to which StumpWM can interact with the system’s Bluetooth. +However, I wish for some more interactivity, like powering on and +off Bluetooth, connecting to devices and so on. + +Firstly, our code relies on ~cl-ppcre~, so let’s quickload it. +#+begin_src lisp +(ql:quickload :cl-ppcre) +#+end_src + +Let’s indicate which command we’ll be using. +#+begin_src lisp +(defvar *bluetooth-command* "bluetoothctl" + "Base command for interacting with bluetooth.") +#+end_src + +**** Utilities +We’ll need a couple of functions that will take care of stuff for us, +so we don’t have to repeat ourselves. The first one is a way for us to +share a message. The function ~bluetooth-message~ will first display +~Bluetooth:~ in green, then it will display the message we want it to +display. +#+begin_src lisp +(defun bluetooth-message (&rest message) + (message (format nil + "^2Bluetooth:^7 ~{~A~^ ~}" + message))) +#+end_src + +This function is a builder function which will create our commands. +For instance, src_lisp[:exports code]{(bluetooth-make-command "power" +"on")} will return ~"bluetoothctl power on"~ with ~*bluetooth-ctl*~ set as +~"bluetoothctl"~ --- simply put, it joins ~*bluetooth-command*~ with ~args~ +with a space as their separator. +#+begin_src lisp +(defun bluetooth-make-command (&rest args) + (format nil + "~a ~{~A~^ ~}" + ,*bluetooth-command* + args)) +#+end_src + +Now we can put ~bluetooth-make-command~ to use with ~bluetooth-command~ +which will actually run the result of the former. As you can see, it +also collects the output, so we can display it later in another +function. +#+begin_src lisp +(defmacro bluetooth-command (&rest args) + `(run-shell-command (bluetooth-make-command ,@args) t)) +#+end_src + +Finally, ~bluetooth-message-command~ is the function that both executes +and also displays the result of the bluetooth command we wanted to see +executed. Each argument of the command is a separate string. For +instance, if we want to power on the bluetooth on our device, we can +call src_lisp[:exports code]{(bluetooth-message-command "power" +"on")}. +#+begin_src lisp +(defmacro bluetooth-message-command (&rest args) + `(bluetooth-message (bluetooth-command ,@args))) +#+end_src + +**** Toggle Bluetooth On and Off +This part is easy. Now that we can call our Bluetooth commands easily, +we can easily define how to turn on Bluetooth. +#+begin_src lisp +(defcommand bluetooth-turn-on () () + "Turn on bluetooth." + (bluetooth-message-command "power" "on")) +#+end_src + +And how to power it off. +#+begin_src lisp +(defcommand bluetooth-turn-off () () + "Turn off bluetooth." + (bluetooth-message-command "power" "off")) +#+end_src + +**** Bluetooth Devices +In order to manipulate Bluetooth device, which we can represent as a +MAC address and a name, we can create a structure that will make use +of a constructor for simpler use. The constructor +~make-bluetooth-device-from-command~ expects an entry such as ~Device +00:00:00:00:00:00 Home Speaker~. The constructor discards the term +~Device~ and stores the MAC address separately from the rest of the +string which is assumed to be the full name of the device. +#+begin_src lisp +(defstruct (bluetooth-device + (:constructor + make-bluetooth-device (&key (address "") + (name nil))) + (:constructor + make-bluetooth-device-from-command + (&key (raw-name "") + &aux (address (cadr (cl-ppcre:split " " raw-name))) + (full-name (format nil "~{~A~^ ~}" (cddr (cl-ppcre:split " " raw-name))))))) + address + (full-name (progn + (format nil "~{~A~^ ~}" name)))) +#+end_src + +We can now collect our devices easily. +#+begin_src lisp +(defun bluetooth-get-devices () + (let ((literal-devices (bluetooth-command "devices"))) + (mapcar (lambda (device) + (make-bluetooth-device-from-command :raw-name device)) + (cl-ppcre:split "\\n" literal-devices)))) +#+end_src + +**** Connect to a device +When we want to connect to a Bluetooth device, we always need +Bluetooth turned on, so ~bluetooth-turn-on~ will always be called. Then +the function will attempt to connect to the device specified by the +~device~ argument, whether the argument is a Bluetooth structure as +defined above or a plain MAC address. +#+begin_src lisp +(defun bluetooth-connect-device (device) + (progn + (bluetooth-turn-on) + (cond ((bluetooth-device-p device) ;; it is a bluetooth-device structure + (bluetooth-message-command "connect" + (bluetooth-device-address device))) + ((stringp device) ;; assume it is a MAC address + (bluetooth-message-command "connect" device)) + (t (message (format nil "Cannot work with device ~a" device)))))) +#+end_src + +The command to connect to a device displays a choice between the +collected Bluetooth device and the user only has to select it. It will +then attempt to connect to it. +#+begin_src lisp +(defcommand bluetooth-connect () () + (sb-thread:make-thread + (lambda () + (let* ((devices (bluetooth-get-devices)) + (choice (cadr (stumpwm:select-from-menu + (stumpwm:current-screen) + (mapcar (lambda (device) + `(,(bluetooth-device-full-name device) ,device)) + devices))))) + (bluetooth-connect-device choice))))) +#+end_src + +**** Keybinds +It’s all nice and all, but typing manually the commands with ~s-SPC ;~ +is a bit tiring, so let’s define our Bluetooth keymap which we will +bind to ~s-SPC B~. +#+name: bluetooth-keymap +| Keychord | Command | +|----------+--------------------| +| ~c~ | ~bluetooth-connect~ | +| ~o~ | ~bluetooth-turn-on~ | +| ~O~ | ~bluetooth-turn-off~ | + +#+begin_src lisp +(defvar *my-bluetooth-keymap* + (let ((m (make-sparse-keymap))) + <> + m)) + +(define-key *root-map* (my/kbd "B") '*my-bluetooth-keymap*) +#+end_src + +*** NetworkManager integration +It is possible to have some kind of integration between StumpWM and +NetworkManager. To do so, we have to load the related module, then +create the two keybinds described in [[nm-keybinds]]. +#+name: nm-keybinds +#+caption: ~*my-nm-keybinds*~ +| Keychord | Command | +|----------+---------------------------| +| ~W~ | ~nm-list-wireless-networks~ | + +A call to src_lisp[:exports code]{(ql:quickload :dbus)} is necessary +for this module. Installing the ~dbus~ module in turn requires the +library ~libfixposix~ installed on the user’s machine. On Arch, you can +install it like so using ~paru~: +#+begin_src fish +paru -S libfixposix --noconfirm +#+end_src + +#+begin_src lisp +(ql:quickload :dbus) + +(load-module "stump-nm") + +<> +#+end_src + +*** Pinentry +Out with GTK2’s pinentry program! Let’s use StumpWM’s! At least that’s +what I’d like to say, but unfortunately there is a bug in the text +reading devices of StumpWM that prevent the user from using modifiers +when entering a password such as AltGr, so I can’t use it : / +#+begin_src lisp +;; (load-module "pinentry") +#+end_src + +*** Sly +[[https://github.com/joaotavora/sly][Sly]] is a fork of SLIME with which I can connect StumpWM and Emacs +together. Technically this is already done to some level with +~stumpwm-mode~, but the latter doesn’t provide auto-completion or stuff +like that. + +The first thing to do is load ~slynk~, SLY’s server: +#+begin_src lisp +(ql:quickload :slynk) +#+end_src + +Now we can define a command to launch the server. I don’t want it to +run all the time, just when I need it. +#+begin_src lisp +(stumpwm:defcommand sly-start-server () () + "Start a slynk server for sly." + (sb-thread:make-thread (lambda () (slynk:create-server :dont-close t)))) + +(stumpwm:defcommand sly-stop-server () () + "Stop current slynk server for sly." + (sb-thread:make-thread (lambda () (slynk:stop-server 4005)))) +#+end_src + +*** ~swm-ssh~ +This module from the contrib repository scans the user’s ssh +configuration file and offers them a quick way of connecting to their +remote hosts. +#+begin_src lisp +(load-module "swm-ssh") +#+end_src + +The default terminal needs to be set, otherwise the module will try to +call ~urxvtc~ which is not installed on my system. +#+begin_src lisp +(setq swm-ssh:*swm-ssh-default-term* "kitty") +#+end_src + +Now, to call the main command of this module we can define the +following keybind. +#+begin_src lisp +(define-key *root-map* (my/kbd "s") "swm-ssh-menu") +#+end_src + +*** Systemd +:PROPERTIES: +:header-args:lisp: :mkdirp yes :tangle ~/.stumpwm.d/systemd.lisp :noweb yes +:END: +I’m currently in the process of writing functions to interact with +Systemd directly through StumpWM. For now, not much work is done, but +it’s a start. + +Firstly, I have the following function that lists all the system or +user services. +#+begin_src lisp +(defun systemd-get-services (&key user-p) + "Collect all systemd services running. + +If USER-P is t, collect user services, otherwise collect system +services. + +The value returned is a list of lists. The first element is the +service’s name, the second is its load state, the third the high-level +activation state of the service, and the fourth its low-level +activation state." + (mapcar (lambda (elt) + (multiple-value-bind (_ result) + (ppcre:scan-to-strings "(.*\\.service) *([^ ]+) *([^ ]+) *([^ ]+).*" + elt) + result)) + (ppcre:split + " *\\n●? *" + (ppcre:regex-replace + "^ *" + (run-shell-command (concat "systemctl list-units --type service --all -q" + (if user-p " --user" "")) + t) + "")))) +#+end_src + +The only command I have right now is for listing the system or user +services with ~message~. Unfortunately, if there are too many services, +the list will overflow the screen. I do not know how to fix that yet. +I set the timeout to 600 seconds in order to have all the time in the +world to read the services list. It goes away as soon as something +else appears, such as a ~s-SPC C-g~ since I have ~which-key-mode~ enabled. +#+begin_src lisp +(defcommand systemd-list-services (user-p) ((:y-or-n "User services? ")) + (let ((stumpwm::*timeout-wait* 600)) + (message (format nil "~{~a~^~&~}" + (mapcar (lambda (service) + (let ((name (aref service 0)) + (load (aref service 1)) + (active (aref service 2)) + (sub (aref service 3))) + (cond ((member load '("not-found" "bad-setting" + "error" "masked") + :test #'string=) + (format nil + "^~A~A^0 ^> Load: ~12@A" + (if (string= "masked" load) 4 1) + name load)) + ((member active '("failed" "reloading" "activating" + "deactivating" "inactive") + :test #'string=) + (format nil "^~A~A^0 ^>Active: ~12@A" + (case active + ("failed" 1) + ("inactive" 0) + (t 3)) + name + active)) + (t (format nil "^2~A^0 ^> Sub: ~12@A" name sub))))) + (systemd-get-services :user-p user-p)))))) +#+end_src + +* PRIVATE org functions :noexport: +#+name: keybinds-gen +#+header: :wrap "src lisp :exports none" :exports none :noweb yes +#+begin_src emacs-lisp :var map="m" keybinds=bluetooth-keymap +(mapconcat (lambda (keybind) + (format "%s" (let* ((filter (lambda (str) + (replace-regexp-in-string "^~\\|~$" "" str))) + (key (funcall filter (car keybind))) + (function (funcall filter (cadr keybind)))) + `(define-key ,map + (my/kbd ,(format "\"%s\"" key)) + ,(if (string-prefix-p "'" function t) + function + (format "\"%s\"" function)))))) + keybinds + "\n") +#+end_src