feat: split StumpWM config into different files, restore some pages
This commit is contained in:
parent
a27e346f30
commit
2d1e17f03b
@ -2,6 +2,108 @@ import { defineUserConfig, defaultTheme } from 'vuepress';
|
|||||||
import { removeHtmlExtensionPlugin } from 'vuepress-plugin-remove-html-extension';
|
import { removeHtmlExtensionPlugin } from 'vuepress-plugin-remove-html-extension';
|
||||||
import head from './head';
|
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({
|
export default defineUserConfig({
|
||||||
lang: 'en-US',
|
lang: 'en-US',
|
||||||
title: "Phundrak's Dotfiles",
|
title: "Phundrak's Dotfiles",
|
||||||
@ -26,79 +128,23 @@ export default defineUserConfig({
|
|||||||
text: 'Emacs',
|
text: 'Emacs',
|
||||||
link: '/emacs/',
|
link: '/emacs/',
|
||||||
collapsible: true,
|
collapsible: true,
|
||||||
children: [
|
children: emacsChildPages,
|
||||||
{
|
|
||||||
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',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
'/scripts',
|
'/scripts',
|
||||||
'/desktop',
|
'/desktop',
|
||||||
// '/fish',
|
'/fish',
|
||||||
// '/git',
|
'/git',
|
||||||
// '/mpd',
|
'/mpd',
|
||||||
'/neofetch',
|
'/neofetch',
|
||||||
'/picom',
|
'/picom',
|
||||||
'/rustfmt',
|
'/rustfmt',
|
||||||
'/stumpwm',
|
{
|
||||||
// '/tmux',
|
text: 'StumpWM',
|
||||||
|
link: '/stumpwm/',
|
||||||
|
collapsible: true,
|
||||||
|
children: stumpwmChildPages,
|
||||||
|
},
|
||||||
|
'/tmux',
|
||||||
'/bootstrap',
|
'/bootstrap',
|
||||||
{
|
{
|
||||||
text: 'Deprecated Configs',
|
text: 'Deprecated Configs',
|
||||||
|
1920
docs/stumpwm.org
1920
docs/stumpwm.org
File diff suppressed because it is too large
Load Diff
85
docs/stumpwm/colours.org
Normal file
85
docs/stumpwm/colours.org
Normal file
@ -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!
|
53
docs/stumpwm/commands.org
Normal file
53
docs/stumpwm/commands.org
Normal file
@ -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
|
95
docs/stumpwm/groups.org
Normal file
95
docs/stumpwm/groups.org
Normal file
@ -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*
|
||||||
|
<<gen-groups()>>)
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
By default, if nothing is specified as per the group type, my groups
|
||||||
|
are manual tiling groups. Otherwise, as you can see above, they can
|
||||||
|
also be dynamic tiling groups or floating groups.
|
||||||
|
|
||||||
|
Next, let’s make sure no previous window placement rule is in place,
|
||||||
|
this will avoid unexpected and hard-to-debug 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
|
92
docs/stumpwm/index.org
Normal file
92
docs/stumpwm/index.org
Normal file
@ -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
|
153
docs/stumpwm/init.org
Normal file
153
docs/stumpwm/init.org
Normal file
@ -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.
|
776
docs/stumpwm/keybindings.org
Normal file
776
docs/stumpwm/keybindings.org
Normal file
@ -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 '<<chars-table-to-list()>>))
|
||||||
|
(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
|
||||||
|
<<my-kbd-defun>>
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
#+header: :exports none
|
||||||
|
#+begin_src lisp :noweb yes :tangle ~/.stumpwm.d/utilities.lisp
|
||||||
|
<<my-kbd-defun>>
|
||||||
|
#+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)))
|
||||||
|
<<keybinds-gen(map="m", keybinds=rofi-scripts)>>
|
||||||
|
m))
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
Let’s also create a keymap for screenshots.
|
||||||
|
|
||||||
|
#+name: screenshot-keymap
|
||||||
|
#+caption: ~*my-screenshot-keymap*~
|
||||||
|
| Keychord | Function |
|
||||||
|
|----------+----------------------------|
|
||||||
|
| ~d~ | ~exec 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)))
|
||||||
|
<<keybinds-gen(map="m", keybinds=screenshot-keymap)>>
|
||||||
|
m))
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
We can now define our applications keymap which will reference both
|
||||||
|
the above keymaps.
|
||||||
|
|
||||||
|
#+name: application-keymap
|
||||||
|
#+caption: ~*my-applications-keymap*~
|
||||||
|
| Keychord | Function |
|
||||||
|
|----------+-------------------------|
|
||||||
|
| ~b~ | ~firefox~ |
|
||||||
|
| ~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)))
|
||||||
|
<<keybinds-gen(map="m", keybinds=application-keymap)>>
|
||||||
|
m))
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
The application keymap can now be bound to the root map like so:
|
||||||
|
#+begin_src lisp
|
||||||
|
(define-key *root-map* (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)))
|
||||||
|
<<keybinds-gen(map="m", keybinds=end-session-keymap)>>
|
||||||
|
m))
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
Which is bound in the root map to ~q~:
|
||||||
|
#+begin_src lisp
|
||||||
|
(define-key *root-map* (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 "<<num-to-char(num=%s)>>" 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
|
||||||
|
<<group-keybind-gen(mod="s", action="gselect", convert="yes")>>
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
#+RESULTS[35268fd11d1fe4fa4065c98a0b7bc723a56c09a7]:
|
||||||
|
#+begin_src lisp
|
||||||
|
(define-key *top-map* (my/kbd "s-<<num-to-char(num=1)>>") "gselect 1")
|
||||||
|
(define-key *top-map* (my/kbd "s-<<num-to-char(num=2)>>") "gselect 2")
|
||||||
|
(define-key *top-map* (my/kbd "s-<<num-to-char(num=3)>>") "gselect 3")
|
||||||
|
(define-key *top-map* (my/kbd "s-<<num-to-char(num=4)>>") "gselect 4")
|
||||||
|
(define-key *top-map* (my/kbd "s-<<num-to-char(num=5)>>") "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
|
||||||
|
<<group-keybind-gen(mod="s", action="gmove-and-follow", convert="no")>>
|
||||||
|
#+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-<group number>~, which gives us the following:
|
||||||
|
#+begin_src emacs-lisp :wrap src lisp
|
||||||
|
<<group-keybind-gen(mod="s-C", action="gmove-and-follow", convert="no")>>
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
#+RESULTS[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-<group number>~:
|
||||||
|
#+begin_src emacs-lisp :wrap src lisp :exports results
|
||||||
|
<<group-keybind-gen(mod="s-C", action="gmove-and-follow", convert="yes")>>
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
#+RESULTS[36b3ded631cd0bad400c4847f6937cde4f11b4b7]:
|
||||||
|
#+begin_src lisp
|
||||||
|
(define-key *top-map* (my/kbd "s-C-<<num-to-char(num=1)>>") "gmove-and-follow 1")
|
||||||
|
(define-key *top-map* (my/kbd "s-C-<<num-to-char(num=2)>>") "gmove-and-follow 2")
|
||||||
|
(define-key *top-map* (my/kbd "s-C-<<num-to-char(num=3)>>") "gmove-and-follow 3")
|
||||||
|
(define-key *top-map* (my/kbd "s-C-<<num-to-char(num=4)>>") "gmove-and-follow 4")
|
||||||
|
(define-key *top-map* (my/kbd "s-C-<<num-to-char(num=5)>>") "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)))
|
||||||
|
<<keybinds-gen(map="m", keybinds=frames-float)>>
|
||||||
|
m))
|
||||||
|
|
||||||
|
(defvar *my-frames-management-keymap*
|
||||||
|
(let ((m (make-sparse-keymap)))
|
||||||
|
<<keybinds-gen(map="m", keybinds=frames-and-window-management)>>
|
||||||
|
m))
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
Let’s bind ~*my-frames-management-keymap*~ in ~*root-keymap*~:
|
||||||
|
#+begin_src lisp
|
||||||
|
(define-key *root-map* (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
|
||||||
|
<<keybinds-gen(map="*top-map*", keybinds=top-window-map)>>
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
Being a [[https://bepo.fr/wiki/Accueil][bépo layout]] user, the ~hjkl~ keys don’t exactly fit me, as you
|
||||||
|
might have noticed with my use of ~ctsr~ which is its equivalent. Due to
|
||||||
|
this, the interactive keymap for ~iresize~ is not ideal for me, let me
|
||||||
|
redefine it:
|
||||||
|
#+begin_src lisp
|
||||||
|
(define-interactive-keymap (iresize tile-group) (:on-enter #'setup-iresize
|
||||||
|
:on-exit #'resize-unhide
|
||||||
|
:abort-if #'abort-resize-p
|
||||||
|
: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)))
|
||||||
|
<<keybinds-gen(map="m", keybinds=window-management)>>
|
||||||
|
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
|
||||||
|
<<interactive-gen(name="mpc-interactive", keys=inter-mpc)>>
|
||||||
|
#+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
|
||||||
|
<<interactive-gen(name="media-interactive", keys=inter-media)>>
|
||||||
|
#+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)))
|
||||||
|
<<keybinds-gen(map="m", keybinds=mpd-add-map)>>
|
||||||
|
m))
|
||||||
|
|
||||||
|
(defvar *my-mpd-browse-map*
|
||||||
|
(let ((m (make-sparse-keymap)))
|
||||||
|
<<keybinds-gen(map="m", keybinds=mpd-browse-map)>>
|
||||||
|
m))
|
||||||
|
|
||||||
|
(defvar *my-media-keymap*
|
||||||
|
(let ((m (make-sparse-keymap)))
|
||||||
|
<<keybinds-gen(map="m", keybinds=media-management)>>
|
||||||
|
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
|
||||||
|
<<keybinds-gen(map="*top-map*", keybinds=media-top-level)>>
|
||||||
|
#+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
|
||||||
|
<<keybinds-gen(map="*root-map*", keybinds=misc-root-map)>>
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
From time to time, I need to switch between different keyboard
|
||||||
|
layouts, especially to the US QWERTY layout when I’m playing some
|
||||||
|
games and the bépo layout most of the time. I’ll use the command
|
||||||
|
~switch-layout~ defined above.
|
||||||
|
|
||||||
|
#+name: keyboard-layout-map
|
||||||
|
#+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)))
|
||||||
|
<<keybinds-gen(map="m", keybinds=keyboard-layout-map)>>
|
||||||
|
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 |
|
191
docs/stumpwm/mode-line.org
Normal file
191
docs/stumpwm/mode-line.org
Normal file
@ -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*
|
||||||
|
'<<modeline-format-gen()>>
|
||||||
|
"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.
|
202
docs/stumpwm/theme.org
Normal file
202
docs/stumpwm/theme.org
Normal file
@ -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<n>~ (~<n>~ being
|
||||||
|
the desired’s font index in the 0-indexed font list). But if a font
|
||||||
|
cannot render a character, it will simply display an empty rectangle
|
||||||
|
instead of falling back to another font. That’s annoying… Here is my
|
||||||
|
list of fonts I want loaded:
|
||||||
|
#+name: list-fonts
|
||||||
|
| Family | Subfamily | Size |
|
||||||
|
|----------------------------------+-----------+------|
|
||||||
|
| Unifont-JP | Regular | 10 |
|
||||||
|
| DejaVu Sans Mono for Powerline | Book | 8.5 |
|
||||||
|
| 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
|
372
docs/stumpwm/utilities.org
Normal file
372
docs/stumpwm/utilities.org
Normal file
@ -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)))
|
||||||
|
<<keybinds-gen(map="m", keybinds=bluetooth-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")
|
||||||
|
|
||||||
|
<<keybinds-gen(map="*root-map*", keybinds=nm-keybinds)>>
|
||||||
|
#+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
|
Loading…
Reference in New Issue
Block a user