mirror of
https://github.com/SqrtMinusOne/dotfiles.git
synced 2025-12-10 19:23:03 +03:00
4277 lines
140 KiB
Org Mode
4277 lines
140 KiB
Org Mode
#+TITLE: Desktop
|
|
#+TODO: TODO(t) CHECK(s) | OFF(o)
|
|
#+PROPERTY: header-args :mkdirp yes
|
|
#+PROPERTY: header-args:emacs-lisp :eval never-export
|
|
#+PROPERTY: header-args:conf-space :comments link
|
|
#+PROPERTY: header-args:js :comments link
|
|
#+PROPERTY: header-args:conf-unix :comments link
|
|
#+PROPERTY: header-args:conf-windows :comments link
|
|
#+PROPERTY: header-args:conf-xdefaults :comments link
|
|
#+PROPERTY: header-args:sh :tangle-mode (identity #o755) :comments link :shebang "#!/usr/bin/env bash"
|
|
#+PROPERTY: header-args:bash :tangle-mode (identity #o755) :comments link :shebang "#!/usr/bin/env bash"
|
|
#+OPTIONS: broken-links:auto h:6 toc:nil
|
|
|
|
My general desktop environment configuration.
|
|
|
|
Parts prefixed with (OFF) are not used, but kept for historic purposes. For some reason GitHub's org renderer ignores TODO status, hence such a prefix. Round brackets instead of square ones to prevent GitHub's org renderer from screwing up.
|
|
|
|
References:
|
|
- [[https://sqrtminusone.xyz/posts/2022-02-12-literate/][A few cases of literate configuration]]. A few interesting ways in which literate configuration is used in this file.
|
|
|
|
|
|
* Some remarks
|
|
Removed features:
|
|
| Feature | Last commit |
|
|
|-----------+------------------------------------------|
|
|
| rofi-buku | e22476b0cc6315e104e5ce4de5559a61c830c429 |
|
|
|
|
* Global customization
|
|
** Colors
|
|
I used to define color codes here (see [[https://github.com/SqrtMinusOne/dotfiles/commit/2dbf1cdd008ec0061456782cca3ebd76e603b31e][previous version of the file]]), now I just get colors from the current Emacs theme.
|
|
|
|
To use them, let's define a noweb block:
|
|
#+NAME: get-color
|
|
#+begin_src emacs-lisp :var name="black" quote=0
|
|
(let ((color (or (my/color-value name))))
|
|
(if (> quote 0)
|
|
(concat "\"" color "\"")
|
|
color))
|
|
#+end_src
|
|
|
|
Test:
|
|
#+begin_src emacs-lisp :noweb yes
|
|
<<get-color(name="red", quote=1)>>
|
|
#+end_src
|
|
|
|
#+RESULTS:
|
|
: #f07178
|
|
|
|
Also, get a foreground for the current color:
|
|
#+NAME: get-fg-for-color
|
|
#+begin_src emacs-lisp :var name="black" quote=0
|
|
(let ((val (if (ct-light-p (my/color-value name))
|
|
(my/color-value 'black)
|
|
(my/color-value 'white))))
|
|
(if (eq quote 1)
|
|
(concat "\"" val "\"")
|
|
val))
|
|
#+end_src
|
|
|
|
Test;
|
|
#+begin_src emacs-lisp :noweb yes
|
|
<<get-fg-for-color(name="red", quote=1)>>
|
|
#+end_src
|
|
|
|
#+RESULTS:
|
|
: #fafafa
|
|
|
|
This table used to have values, now it has only keys:
|
|
#+tblname: colors
|
|
| color | key |
|
|
|---------------+---------|
|
|
| black | color0 |
|
|
| red | color1 |
|
|
| green | color2 |
|
|
| yellow | color3 |
|
|
| blue | color4 |
|
|
| magenta | color5 |
|
|
| cyan | color6 |
|
|
| white | color7 |
|
|
| light-black | color8 |
|
|
| light-red | color9 |
|
|
| light-green | color10 |
|
|
| light-yellow | color11 |
|
|
| light-blue | color12 |
|
|
| light-magenta | color13 |
|
|
| light-cyan | color14 |
|
|
| light-white | color15 |
|
|
|
|
** Xresources
|
|
*** Colors in Xresources
|
|
Some programs get their colors from =XResources=. Let's generate that file.
|
|
|
|
#+NAME: get-xresources
|
|
#+begin_src emacs-lisp :var table=colors
|
|
(mapconcat
|
|
(lambda (elem)
|
|
(concat "*" (nth 1 elem) ": " (my/color-value (nth 0 elem))))
|
|
(seq-filter
|
|
(lambda (elem) (and (nth 1 elem)
|
|
(not (string-empty-p (nth 1 elem)))))
|
|
table)
|
|
"\n")
|
|
#+end_src
|
|
|
|
#+begin_src conf-xdefaults :noweb yes :tangle ~/.Xresources
|
|
<<get-xresources()>>
|
|
|
|
*background: <<get-color(name="bg")>>
|
|
*foreground: <<get-color(name="fg")>>
|
|
#+end_src
|
|
|
|
Run =xrdb -load ~/.Xresources= to apply the changes.
|
|
*** Fonts
|
|
Also, Xresources are used to set =Xft= settings. Unfortunately, the DPI setting has to be unique for each machine, which means I cannot commit =Xresources= to the repo.
|
|
|
|
#+NAME: get-dpi
|
|
#+begin_src emacs-lisp
|
|
(let ((hostname (system-name)))
|
|
(cond ((string-equal hostname "azure") 120)
|
|
((string-equal hostname "eminence") 120)
|
|
((string-equal hostname "violet") 120)
|
|
((string-equal hostname "iris") 120)
|
|
(t 96)))
|
|
#+end_src
|
|
|
|
#+begin_src conf-xdefaults :noweb yes :tangle ~/.Xresources
|
|
Xft.dpi: <<get-dpi()>>
|
|
#+end_src
|
|
** Themes
|
|
A few programs I use to customize the apperance are listed below.
|
|
|
|
| Guix dependency | Description |
|
|
|-----------------------+-------------------------|
|
|
| matcha-theme | My preferred GTK theme |
|
|
| papirus-icon-theme | My preferred Icon theme |
|
|
| gnome-themes-standard | |
|
|
| xsettingsd | X11 settings daemon |
|
|
| gnome-themes-extra | |
|
|
|
|
[[https://github.com/derat/xsettingsd][xsettingsd]] is a lightweight daemon which configures X11 applications. It is launched with shepherd in the [[*Services][Services]] section.
|
|
|
|
#+NAME: get-gtk-theme
|
|
#+begin_src emacs-lisp
|
|
(if (my/light-p)
|
|
"Matcha-light-azul"
|
|
"Matcha-dark-azul")
|
|
#+end_src
|
|
|
|
#+NAME: get-icons-theme
|
|
#+begin_src emacs-lisp
|
|
(if (my/light-p)
|
|
"Papirus"
|
|
"Papirus-Dark")
|
|
#+end_src
|
|
|
|
#+begin_src conf-space :tangle ~/.config/xsettingsd/xsettingsd.conf :noweb yes
|
|
Net/ThemeName "<<get-gtk-theme()>>"
|
|
Net/IconThemeName "<<get-icons-theme()>>"
|
|
Gtk/DecorationLayout "menu:minimize,maximize,close"
|
|
Gtk/FontName "Sans 10"
|
|
Gtk/MonospaceFontName "JetBrainsMono Nerd Mono 12"
|
|
Gtk/CursorThemeName "Adwaita"
|
|
Xft/Antialias 1
|
|
Xft/Hinting 0
|
|
Xft/HintStyle "hintnone"
|
|
#+end_src
|
|
** MIME
|
|
Setting the default MIME types
|
|
#+begin_src conf-unix :tangle ~/.config/mimeapps.list
|
|
[Default Applications]
|
|
text/html=firefox.desktop
|
|
x-scheme-handler/http=firefox.desktop
|
|
x-scheme-handler/https=firefox.desktop
|
|
x-scheme-handler/about=firefox.desktop
|
|
x-scheme-handler/unknown=firefox.desktop
|
|
x-scheme-handler/tg=userapp-Telegram Desktop-7PVWF1.desktop
|
|
image/png=feh.desktop
|
|
image/jpg=feh.desktop
|
|
image/jpeg=feh.desktop
|
|
application/pdf=org.pwmt.zathura.desktop
|
|
|
|
[Added Associations]
|
|
x-scheme-handler/tg=userapp-Telegram Desktop-7PVWF1.desktop;
|
|
application/pdf=org.pwmt.zathura.desktop
|
|
#+end_src
|
|
** Device-specific settings
|
|
| Guix dependency | Description |
|
|
|-----------------+--------------------------------------------|
|
|
| xrandr | X11 CLI to RandR |
|
|
| xgamma | A tool to alter monitor's gamma correction |
|
|
| xinput | Configure input devices |
|
|
|
|
Set screen layout & other params depending on hostname
|
|
#+begin_src sh :tangle ~/bin/scripts/screen-layout
|
|
hostname=$(hostname)
|
|
if [ "$hostname" = "indigo" ]; then
|
|
xrandr --output DisplayPort-0 --off --output HDMI-A-0 --mode 1920x1080 --pos 0x0 --rotate normal --output DVI-D-0 --mode 1920x1080 --pos 1920x0 --rotate normal
|
|
elif [ "$hostname" = "eminence" ]; then
|
|
xgamma -gamma 1.25
|
|
elif [ "$hostname" = "violet" ]; then
|
|
xrandr --output HDMI-0 --primary --mode 1920x1080 --pos 0x0 --rotate normal --output DP-0 --off --output DP-1 --mode 1920x1080 --pos 1920x0 --rotate normal --output DP-2 --off --output DP-3 --off --output DP-4 --off --output DP-5 --off --output None-1-1 --off
|
|
fi
|
|
#+end_src
|
|
* EXWM
|
|
:PROPERTIES:
|
|
:header-args+: :tangle ~/.emacs.d/desktop.el
|
|
:END:
|
|
Settings for [[https://github.com/ch11ng/exwm][Emacs X Window Manager]], a tiling WM implemented in Emacs Lisp. This part has a few bits copied from my blog post.
|
|
|
|
References:
|
|
- [[https://github.com/ch11ng/exwm/wiki][EXWM Wiki]]
|
|
- [[https://github.com/daviwil/emacs-from-scratch/blob/master/Desktop.org][Emacs From Scratch config]]
|
|
- [[https://sqrtminusone.xyz/posts/2022-01-03-exwm/][Using EXWM and perspective.el on a multi-monitor setup]]
|
|
|
|
** Startup & UI
|
|
*** Xsession
|
|
First things first, Emacs has to be launched as a window manager. On a more conventional system I'd create a .desktop file in some system folder that can be seen by a login manager, but in the case of Guix it's a bit more complicated, because all such folders are not meant to be changed manually.
|
|
|
|
| Category | Guix dependency |
|
|
|--------------+-----------------|
|
|
| desktop-misc | xinit |
|
|
| desktop-misc | xss-lock |
|
|
|
|
However, GDM, the login manager that seems to be the default on Guix, launches =~/.xsession= on the startup if it's present, which is just fine for my purposes.
|
|
|
|
#+begin_src sh :tangle ~/.xsession
|
|
# Source .profile
|
|
. ~/.profile
|
|
|
|
# Disable access control for the current user
|
|
xhost +SI:localuser:$USER
|
|
|
|
# Fix for Java applications
|
|
export _JAVA_AWT_WM_NONREPARENTING=1
|
|
|
|
# Apply XResourses
|
|
xrdb -merge ~/.Xresources
|
|
|
|
# Turn off the system bell
|
|
xset -b
|
|
|
|
# Use i3lock as a screen locker
|
|
xss-lock -- i3lock -f -i /home/pavel/Pictures/lock-wallpaper.png &
|
|
|
|
# Some apps that have to be launched only once.
|
|
picom &
|
|
# nm-applet &
|
|
dunst &
|
|
copyq &
|
|
|
|
# Run the Emacs startup script as a session.
|
|
# exec dbus-launch --exit-with-session ~/.emacs.d/run-exwm.sh
|
|
exec dbus-launch --exit-with-session emacs -mm --debug-init -l ~/.emacs.d/desktop.el
|
|
#+end_src
|
|
*** Startup apps
|
|
Now that Emacs is launched, it is necessary to set up the EXWM-specific parts of config.
|
|
|
|
I want to launch some apps from EXWM instead of the Xsession file for two purposes:
|
|
- the app may need to have the entire desktop environment set up
|
|
- or it may need to be restarted if Emacs is killed.
|
|
|
|
As of now, these are polybar, feh and, shepherd:
|
|
#+begin_src emacs-lisp
|
|
(defun my/exwm-run-polybar ()
|
|
(interactive)
|
|
(call-process "~/bin/polybar.sh"))
|
|
|
|
(defun my/exwm-set-wallpaper ()
|
|
(call-process-shell-command "feh --bg-fill ~/Pictures/wallpaper.jpg"))
|
|
|
|
(defun my/exwm-run-shepherd ()
|
|
(when (string-empty-p (shell-command-to-string "pgrep -u pavel shepherd"))
|
|
(call-process "shepherd")))
|
|
#+end_src
|
|
*** Pinentry
|
|
The GUI pinentry doesn't work too well with EXWM because of issues with popup windows, so we will use the Emacs one.
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package pinentry
|
|
:straight t
|
|
:after (exwm)
|
|
:config
|
|
(setenv "GPG_AGENT_INFO" nil) ;; use emacs pinentry
|
|
(setq auth-source-debug t)
|
|
|
|
(setq epg-gpg-program "gpg") ;; not necessary
|
|
(require 'epa-file)
|
|
(epa-file-enable)
|
|
(setq epa-pinentry-mode 'loopback)
|
|
(setq epg-pinentry-mode 'loopback)
|
|
(pinentry-start))
|
|
#+end_src
|
|
|
|
#+NAME: find-pinentry
|
|
#+begin_src emacs-lisp :tangle no
|
|
(executable-find "pinentry")
|
|
#+end_src
|
|
|
|
#+RESULTS: find-pinentry
|
|
: /home/pavel/.guix-profile/bin/pinentry
|
|
|
|
#+begin_src conf-space :tangle ~/.gnupg/gpg-agent.conf :noweb yes
|
|
default-cache-ttl 3600
|
|
max-cache-ttl 3600
|
|
allow-emacs-pinentry
|
|
allow-loopback-pinentry
|
|
pinentry-program <<find-pinentry()>>
|
|
#+end_src
|
|
*** Modeline
|
|
Show the current workspace in the modeline.
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package exwm-modeline
|
|
:straight t
|
|
:config
|
|
(add-hook 'exwm-init-hook #'exwm-modeline-mode)
|
|
(my/use-colors
|
|
(exwm-modeline-current-workspace
|
|
:foreground (my/color-value 'yellow)
|
|
:weight 'bold)))
|
|
#+end_src
|
|
*** Misc
|
|
Check if running Arch and not Guix.
|
|
|
|
#+begin_src emacs-lisp
|
|
(defun my/is-arch ()
|
|
(file-exists-p "/etc/arch-release"))
|
|
#+end_src
|
|
** Windows
|
|
A bunch of functions related to managing windows in EXWM.
|
|
|
|
*** Moving windows
|
|
As I wrote in my [[https://sqrtminusone.xyz/posts/2021-10-04-emacs-i3/][Emacs and i3]] post, I want to have a rather specific behavior when moving windows (which does resemble i3 in some way):
|
|
- if there is space in the required direction, move the Emacs window there;
|
|
- if there is no space in the required direction, but space in two orthogonal directions, move the Emacs window so that there is no more space in the orthogonal directions;
|
|
|
|
I can't say it's better or worse than the built-in functionality or one provided by evil, but I'm used to it and I think it fits better for managing a lot of windows.
|
|
|
|
So, first, we need a predicate that checks whether there is space in the given direction:
|
|
#+begin_src emacs-lisp
|
|
(require 'windmove)
|
|
|
|
(defun my/exwm-direction-exists-p (dir)
|
|
"Check if there is space in the direction DIR.
|
|
|
|
Does not take the minibuffer into account."
|
|
(cl-some (lambda (dir)
|
|
(let ((win (windmove-find-other-window dir)))
|
|
(and win (not (window-minibuffer-p win)))))
|
|
(pcase dir
|
|
('width '(left right))
|
|
('height '(up down)))))
|
|
#+end_src
|
|
|
|
And a function to implement that:
|
|
#+begin_src emacs-lisp
|
|
(defun my/exwm-move-window (dir)
|
|
"Move the current window in the direction DIR."
|
|
(let ((other-window (windmove-find-other-window dir))
|
|
(other-direction (my/exwm-direction-exists-p
|
|
(pcase dir
|
|
('up 'width)
|
|
('down 'width)
|
|
('left 'height)
|
|
('right 'height)))))
|
|
(cond
|
|
((and other-window (not (window-minibuffer-p other-window)))
|
|
(window-swap-states (selected-window) other-window))
|
|
(other-direction
|
|
(evil-move-window dir)))))
|
|
#+end_src
|
|
|
|
My preferred keybindings for this part are, of course, =s-<H|J|K|L>=.
|
|
*** Resizing windows
|
|
I find this odd that there are different commands to resize tiling and floating windows. So let's define one command to perform both resizes depending on the context:
|
|
#+begin_src emacs-lisp
|
|
(setq my/exwm-resize-value 5)
|
|
|
|
(defun my/exwm-resize-window (dir kind &optional value)
|
|
"Resize the current window in the direction DIR.
|
|
|
|
DIR is either 'height or 'width, KIND is either 'shrink or
|
|
'grow. VALUE is `my/exwm-resize-value' by default.
|
|
|
|
If the window is an EXWM floating window, execute the
|
|
corresponding command from the exwm-layout group, execute the
|
|
command from the evil-window group."
|
|
(unless value
|
|
(setq value my/exwm-resize-value))
|
|
(let* ((is-exwm-floating
|
|
(and (derived-mode-p 'exwm-mode)
|
|
exwm--floating-frame))
|
|
(func (if is-exwm-floating
|
|
(intern
|
|
(concat
|
|
"exwm-layout-"
|
|
(pcase kind ('shrink "shrink") ('grow "enlarge"))
|
|
"-window"
|
|
(pcase dir ('height "") ('width "-horizontally"))))
|
|
(intern
|
|
(concat
|
|
"evil-window"
|
|
(pcase kind ('shrink "-decrease-") ('grow "-increase-"))
|
|
(symbol-name dir))))))
|
|
(when is-exwm-floating
|
|
(setq value (* 5 value)))
|
|
(funcall func value)))
|
|
#+end_src
|
|
|
|
This function will call =exwm-layout-<shrink|grow>[-horizontally]= for EXWM floating window and =evil-window-<decrease|increase>-<width|height>= otherwise.
|
|
|
|
This function can be bound to the required keybindings directly, but I prefer a hydra to emulate the i3 submode:
|
|
#+begin_src emacs-lisp
|
|
(defhydra my/exwm-resize-hydra (:color pink :hint nil :foreign-keys run)
|
|
"
|
|
^Resize^
|
|
_l_: Increase width _h_: Decrease width _j_: Increase height _k_: Decrease height
|
|
|
|
_=_: Balance "
|
|
("h" (lambda () (interactive) (my/exwm-resize-window 'width 'shrink)))
|
|
("j" (lambda () (interactive) (my/exwm-resize-window 'height 'grow)))
|
|
("k" (lambda () (interactive) (my/exwm-resize-window 'height 'shrink)))
|
|
("l" (lambda () (interactive) (my/exwm-resize-window 'width 'grow)))
|
|
("=" balance-windows)
|
|
("q" nil "quit" :color blue))
|
|
#+end_src
|
|
*** Improving splitting windows
|
|
=M-x evil-window-[v]split= (bound to =C-w v= and =C-w s= by default) are the default evil command to do splits.
|
|
|
|
One EXWM-related issue though is that by default doing such a split "copies" the current buffer to the new window. But as EXWM buffer cannot be "copied" like that, some other buffer is displayed in the split, and generally, that's not a buffer I want.
|
|
|
|
For instance, I prefer to have Chrome DevTools as a separate window. When I click "Inspect" on something, the DevTools window replaces my Ungoogled Chromium window. I press =C-w v=, and most often I have something like =*scratch*= buffer in the opened split instead of the previous Chromium window.
|
|
|
|
To implement better behavior, I define the following advice:
|
|
#+begin_src emacs-lisp
|
|
(defun my/exwm-fill-other-window (&rest _)
|
|
"Open the most recently used buffer in the next window."
|
|
(interactive)
|
|
(when (and (eq major-mode 'exwm-mode) (not (eq (next-window) (get-buffer-window))))
|
|
(let ((other-exwm-buffer
|
|
(cl-loop with other-buffer = (persp-other-buffer)
|
|
for buf in (sort (persp-current-buffers) (lambda (a _) (eq a other-buffer)))
|
|
with current-buffer = (current-buffer)
|
|
when (and (not (eq current-buffer buf))
|
|
(buffer-live-p buf)
|
|
(not (string-match-p (persp--make-ignore-buffer-rx) (buffer-name buf)))
|
|
(not (get-buffer-window buf)))
|
|
return buf)))
|
|
(when other-exwm-buffer
|
|
(with-selected-window (next-window)
|
|
(switch-to-buffer other-exwm-buffer))))))
|
|
#+end_src
|
|
|
|
This is meant to be called after doing an either vertical or horizontal split, so it's advised like that:
|
|
#+begin_src emacs-lisp
|
|
(advice-add 'evil-window-split :after #'my/exwm-fill-other-window)
|
|
(advice-add 'evil-window-vsplit :after #'my/exwm-fill-other-window)
|
|
#+end_src
|
|
|
|
This works as follows. If the current buffer is an EXWM buffer and there are other windows open (that is, =(next-window)= is not the current window), the function tries to find another suitable buffer to be opened in the split. And that also takes the perspectives into account, so buffers are searched only within the current perspective, and the buffer returned by =persp-other-buffer= will be the top candidate.
|
|
** Perspectives
|
|
[[https://github.com/nex3/perspective-el][perspective.el]] is one package I like that provides workspaces for Emacs, called "perspectives". Each perspective has a separate buffer list, window layout, and a few other things that make it easier to separate things within Emacs.
|
|
|
|
One feature I'd like to highlight is integration between perspective.el and [[https://github.com/Alexander-Miller/treemacs][treemacs]], where one perspective can have a separate treemacs tree. Although now tab-bar.el seems to be getting into shape to compete with perspective.el, as of the time of this writing, there's no such integration, at least not out of the box.
|
|
|
|
perspective.el works with EXWM more or less as one would expect - each EXWM workspace has its own set of perspectives. That way it feels somewhat like having multiple Emacs frames in a tiling window manager, although, of course, much more integrated with Emacs.
|
|
|
|
However, there are still some issues. For instance, I was having strange behaviors with floating windows, EXWM buffers in perspectives, etc. So I've made a package called [[https://github.com/SqrtMinusOne/perspective-exwm.el][perspective-exwm.el]] that does two things:
|
|
- Advices away the issues I had. Take a look at the package homepage for more detail on that.
|
|
- Provides some additional functionality that makes use of both perspective.el and EXWM.
|
|
|
|
References:
|
|
- [[https://github.com/SqrtMinusOne/perspective-exwm.el][perspective-exwm.el repo]]
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package perspective-exwm
|
|
:straight t
|
|
:config
|
|
(setq perspective-exwm-override-initial-name
|
|
'((0 . "misc")
|
|
(1 . "core")
|
|
(2 . "browser")
|
|
(3 . "comms")
|
|
(4 . "dev")))
|
|
(setq perspective-exwm-cycle-max-message-length 180)
|
|
(general-define-key
|
|
:keymaps 'perspective-map
|
|
"e" #'perspective-exwm-move-to-workspace
|
|
"E" #'perspective-exwm-copy-to-workspace))
|
|
#+end_src
|
|
|
|
By default, a new Emacs buffer opens in the current perspective in the current workspace, but sure enough, it's possible to change that.
|
|
|
|
For EXWM windows, the =perspective-exwm= package provides a function called =perspective-exwm-assign-window=, which is intended to be used in =exwm-manage-finish-hook=, for instance:
|
|
#+begin_src emacs-lisp
|
|
(defun my/exwm-configure-window ()
|
|
(interactive)
|
|
(unless exwm--floating-frame
|
|
(pcase exwm-class-name
|
|
((or "Firefox" "Nightly")
|
|
(perspective-exwm-assign-window
|
|
:workspace-index 2
|
|
:persp-name "browser"))
|
|
("Nyxt"
|
|
(perspective-exwm-assign-window
|
|
:workspace-index 2
|
|
:persp-name "browser"))
|
|
("Alacritty"
|
|
(perspective-exwm-assign-window
|
|
:persp-name "term"))
|
|
((or "VK" "Slack" "discord" "TelegramDesktop" "Rocket.Chat")
|
|
(perspective-exwm-assign-window
|
|
:workspace-index 3
|
|
:persp-name "comms"))
|
|
((or "Chromium-browser" "jetbrains-datagrip")
|
|
(perspective-exwm-assign-window
|
|
:workspace-index 4
|
|
:persp-name "dev")))))
|
|
|
|
(add-hook 'exwm-manage-finish-hook #'my/exwm-configure-window)
|
|
#+end_src
|
|
** Workspaces and multi-monitor setup
|
|
A section about improving management of EXWM workspaces.
|
|
|
|
Some features, common in other tiling WMs, are missing in EXWM out of the box, namely:
|
|
- a command to [[https://i3wm.org/docs/userguide.html#_focusing_moving_containers][switch to another monitor]];
|
|
- a command to [[https://i3wm.org/docs/userguide.html#move_to_outputs][move the current workspace to another monitor]];
|
|
- using the same commands to switch between windows and monitors.
|
|
|
|
Here's my take on implementing them.
|
|
|
|
*** Tracking recently used workspaces
|
|
First up though, we need to track the workspaces in the usage order. I'm not sure if there's some built-in functionality in EXWM for that, but it seems simple enough to implement.
|
|
|
|
Here is a snippet of code that does it:
|
|
#+begin_src emacs-lisp
|
|
(setq my/exwm-last-workspaces '(1))
|
|
|
|
(defun my/exwm-store-last-workspace ()
|
|
"Save the last workspace to `my/exwm-last-workspaces'."
|
|
(setq my/exwm-last-workspaces
|
|
(seq-uniq (cons exwm-workspace-current-index
|
|
my/exwm-last-workspaces))))
|
|
|
|
(add-hook 'exwm-workspace-switch-hook
|
|
#'my/exwm-store-last-workspace)
|
|
#+end_src
|
|
|
|
The variable =my/exwm-last-workspaces= stores the workspace indices; the first item is the index of the current workspace, the second item is the index of the previous workspace, and so on.
|
|
|
|
One note here is that workspaces may also disappear (e.g. after =M-x exwm-workspace-delete=), so we also need a function to clean the list:
|
|
#+begin_src emacs-lisp
|
|
(defun my/exwm-last-workspaces-clear ()
|
|
"Clean `my/exwm-last-workspaces' from deleted workspaces."
|
|
(setq my/exwm-last-workspaces
|
|
(seq-filter
|
|
(lambda (i) (nth i exwm-workspace--list))
|
|
my/exwm-last-workspaces)))
|
|
#+end_src
|
|
|
|
*** The monitor list
|
|
The second piece of the puzzle is getting the monitor list in the right order.
|
|
|
|
While it is possible to retrieve the monitor list from =exwm-randr-workspace-monitor-plist=, this won't scale well beyond two monitors, mainly because changing this variable may screw up the order.
|
|
|
|
So the easiest way is to just define the variable like that:
|
|
#+begin_src emacs-lisp :eval no
|
|
(setq my/exwm-monitor-list
|
|
(pcase (system-name)
|
|
("indigo" '(nil "DVI-D-0"))
|
|
("violet" '(nil "DP-1"))
|
|
(_ '(nil))))
|
|
#+end_src
|
|
|
|
If you are changing the RandR configuration on the fly, this variable will also need to be changed, but for now, I don't have such a necessity.
|
|
|
|
A function to get the current monitor:
|
|
#+begin_src emacs-lisp :eval no
|
|
(defun my/exwm-get-current-monitor ()
|
|
"Return the current monitor name or nil."
|
|
(plist-get exwm-randr-workspace-monitor-plist
|
|
(cl-position (selected-frame)
|
|
exwm-workspace--list)))
|
|
#+end_src
|
|
|
|
And a function to cycle the monitor list in either direction:
|
|
#+begin_src emacs-lisp
|
|
(defun my/exwm-get-other-monitor (dir)
|
|
"Cycle the monitor list in the direction DIR.
|
|
|
|
DIR is either 'left or 'right."
|
|
(nth
|
|
(% (+ (cl-position
|
|
(my/exwm-get-current-monitor)
|
|
my/exwm-monitor-list
|
|
:test #'string-equal)
|
|
(length my/exwm-monitor-list)
|
|
(pcase dir
|
|
('right 1)
|
|
('left -1)))
|
|
(length my/exwm-monitor-list))
|
|
my/exwm-monitor-list))
|
|
#+end_src
|
|
*** Switch to another monitor
|
|
With the functions from the previous two sections, we can implement switching to another monitor by switching to the most recently used workspace on that monitor.
|
|
|
|
One caveat here is that on the startup the =my/exwm-last-workspaces= variable won't have any values from other monitor(s), so this list is concatenated with the list of available workspace indices.
|
|
#+begin_src emacs-lisp
|
|
(defun my/exwm-switch-to-other-monitor (&optional dir)
|
|
"Switch to another monitor."
|
|
(interactive)
|
|
(my/exwm-last-workspaces-clear)
|
|
(let ((mouse-autoselect-window nil))
|
|
(exwm-workspace-switch
|
|
(cl-loop with other-monitor = (my/exwm-get-other-monitor (or dir 'right))
|
|
for i in (append my/exwm-last-workspaces
|
|
(cl-loop for i from 0
|
|
for _ in exwm-workspace--list
|
|
collect i))
|
|
if (if other-monitor
|
|
(string-equal (plist-get exwm-randr-workspace-monitor-plist i)
|
|
other-monitor)
|
|
(not (plist-get exwm-randr-workspace-monitor-plist i)))
|
|
return i))))
|
|
#+end_src
|
|
|
|
I bind this function to =s-q=, as I'm used from i3.
|
|
*** Move the workspace to another monitor
|
|
Now, moving the workspace to another monitor.
|
|
|
|
This is actually quite easy to pull off - one just has to update =exwm-randr-workspace-monitor-plist= accordingly and run =exwm-randr-refresh=. I just add another check there because I don't want some monitor to remain without workspaces at all.
|
|
#+begin_src emacs-lisp
|
|
(defun my/exwm-workspace-switch-monitor ()
|
|
"Move the current workspace to another monitor."
|
|
(interactive)
|
|
(let ((new-monitor (my/exwm-get-other-monitor 'right))
|
|
(current-monitor (my/exwm-get-current-monitor)))
|
|
(when (and current-monitor
|
|
(>= 1
|
|
(cl-loop for (key value) on exwm-randr-workspace-monitor-plist
|
|
by 'cddr
|
|
if (string-equal value current-monitor) sum 1)))
|
|
(error "Can't remove the last workspace on the monitor!"))
|
|
(setq exwm-randr-workspace-monitor-plist
|
|
(map-delete exwm-randr-workspace-monitor-plist exwm-workspace-current-index))
|
|
(when new-monitor
|
|
(setq exwm-randr-workspace-monitor-plist
|
|
(plist-put exwm-randr-workspace-monitor-plist
|
|
exwm-workspace-current-index
|
|
new-monitor))))
|
|
(exwm-randr-refresh))
|
|
#+end_src
|
|
|
|
In my configuration this is bound to =s-<tab>=.
|
|
*** Windmove between monitors
|
|
And the final (for now) piece of the puzzle is using the same command to switch between windows and monitors. E.g. when the focus is on the right-most window on one monitor, I want the command to switch to the left-most window on the monitor to the right instead of saying "No window right from the selected window", as =windmove-right= does.
|
|
|
|
So here is my implementation of that. It always does =windmove-do-select-window= for ='down= and ='up=. For ='right= and ='left= though, the function calls the previously defined function to switch to other monitor if =windmove-find-other-window= doesn't return anything.
|
|
#+begin_src emacs-lisp
|
|
(defun my/exwm-windmove (dir)
|
|
"Move to window or monitor in the direction DIR."
|
|
(if (or (eq dir 'down) (eq dir 'up))
|
|
(windmove-do-window-select dir)
|
|
(let ((other-window (windmove-find-other-window dir))
|
|
(other-monitor (my/exwm-get-other-monitor dir))
|
|
(opposite-dir (pcase dir
|
|
('left 'right)
|
|
('right 'left))))
|
|
(if other-window
|
|
(windmove-do-window-select dir)
|
|
(let ((mouse-autoselect-window nil))
|
|
(my/exwm-switch-to-other-monitor dir))
|
|
(cl-loop while (windmove-find-other-window opposite-dir)
|
|
do (windmove-do-window-select opposite-dir))))))
|
|
#+end_src
|
|
** Completions
|
|
Setting up some completion interfaces that fit particularly well to use with EXWM. While rofi also works, I want to use Emacs functionality wherever possible to have one completion interface everywhere.
|
|
|
|
*** ivy-posframe
|
|
[[https://github.com/tumashu/ivy-posframe][ivy-posframe]] is an extension to show ivy candidates in a posframe.
|
|
|
|
Take a look at [[https://github.com/ch11ng/exwm/issues/550][this issue]] in the EXWM repo about setting it up.
|
|
|
|
Edit [2022-04-09 Sat]: This looks nice, but unfortunately too unstable. Disabling it.
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package ivy-posframe
|
|
:straight t
|
|
:disabled
|
|
:config
|
|
(setq ivy-posframe-parameters '((left-fringe . 10)
|
|
(right-fringe . 10)
|
|
(parent-frame . nil)
|
|
(max-width . 80)))
|
|
(setq ivy-posframe-height-alist '((t . 20)))
|
|
(setq ivy-posframe-width 180)
|
|
(setq ivy-posframe-min-height 5)
|
|
(setq ivy-posframe-display-functions-alist
|
|
'((swiper . ivy-display-function-fallback)
|
|
(swiper-isearch . ivy-display-function-fallback)
|
|
(t . ivy-posframe-display)))
|
|
(ivy-posframe-mode 1))
|
|
#+end_src
|
|
**** Disable mouse movement
|
|
*SOURCE*: https://github.com/ch11ng/exwm/issues/550#issuecomment-744784838
|
|
|
|
#+begin_src emacs-lisp
|
|
(defun my/advise-fn-suspend-follow-mouse (fn &rest args)
|
|
(let ((focus-follows-mouse nil)
|
|
(mouse-autoselect-window nil)
|
|
(pos (x-mouse-absolute-pixel-position)))
|
|
(unwind-protect
|
|
(apply fn args)
|
|
(x-set-mouse-absolute-pixel-position (car pos)
|
|
(cdr pos)))))
|
|
(with-eval-after-load 'ivy-posframe
|
|
(advice-add #'ivy-posframe--read :around #'my/advise-fn-suspend-follow-mouse))
|
|
#+end_src
|
|
**** Disable changing focus
|
|
Not sure about that. The cursor occasionally changes focus when I'm exiting posframe, and this doesn't catch all the cases.
|
|
|
|
#+begin_src emacs-lisp
|
|
(defun my/setup-posframe (&rest args)
|
|
(mapc
|
|
(lambda (var)
|
|
(kill-local-variable var)
|
|
(setf (symbol-value var) nil))
|
|
'(exwm-workspace-warp-cursor
|
|
mouse-autoselect-window
|
|
focus-follows-mouse)))
|
|
|
|
(defun my/restore-posframe (&rest args)
|
|
(run-with-timer
|
|
0.25
|
|
(lambda ()
|
|
(mapc
|
|
(lambda (var)
|
|
(kill-local-variable var)
|
|
(setf (symbol-value var) t))
|
|
'(exwm-workspace-warp-cursor
|
|
mouse-autoselect-window
|
|
focus-follows-mouse)))))
|
|
|
|
(with-eval-after-load 'ivy-posframe
|
|
(advice-add #'posframe--create-posframe :after #'my/setup-posframe)
|
|
(advice-add #'ivy-posframe-cleanup :after #'my/restore-posframe))
|
|
#+end_src
|
|
*** Linux app
|
|
=counsel-linux-app= is a counsel interface to select a Linux desktop application.
|
|
|
|
By default, it also shows paths from =/gnu/store=, so there is a custom formatter function.
|
|
#+begin_src emacs-lisp
|
|
(defun my/counsel-linux-app-format-function (name comment _exec)
|
|
(format "% -45s%s"
|
|
(propertize
|
|
(ivy--truncate-string name 45)
|
|
'face 'counsel-application-name)
|
|
(if comment
|
|
(concat ": " (ivy--truncate-string comment 100))
|
|
"")))
|
|
|
|
(setq counsel-linux-app-format-function #'my/counsel-linux-app-format-function)
|
|
#+end_src
|
|
|
|
Also, by default it tries to launch stuff with =gtk-launch=, which is in the =gtk+= package.
|
|
|
|
| Category | Guix dependency |
|
|
|--------------+-----------------|
|
|
| desktop-misc | gtk+:bin |
|
|
*** password-store-ivy
|
|
[[https://github.com/SqrtMinusOne/password-store-ivy][password-store-ivy]] is another package of mine, inspired by [[https://github.com/carnager/rofi-pass][rofi-pass]].
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package password-store-ivy
|
|
:straight (:host github :repo "SqrtMinusOne/password-store-ivy")
|
|
:after (exwm))
|
|
#+end_src
|
|
*** emojis
|
|
[[https://github.com/iqbalansari/emacs-emojify][emojify]] is an Emacs package that adds emoji display to Emacs. While its primary capacity is no longer necessary in Emacs 28, it a few functions to insert emojis are still handy.
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package emojify
|
|
:straight t)
|
|
#+end_src
|
|
|
|
Because I occasionally want to type emojis to other programs, I reuse a function from =password-store-ivy=:
|
|
#+begin_src emacs-lisp
|
|
(defun my/emojify-type ()
|
|
"Type an emoji."
|
|
(interactive)
|
|
(let ((emoji (emojify-completing-read "Type emoji: ")))
|
|
(kill-new emoji)
|
|
(password-store-ivy--async-commands
|
|
(list
|
|
(password-store-ivy--get-wait-command 10)
|
|
"xdotool key Shift+Insert"))))
|
|
#+end_src
|
|
** Keybindings
|
|
*** EXWM keybindings
|
|
Setting keybindings for EXWM. This actually has to be in the =:config= block of the =use-package= form, that is it has to be run after EXWM is loaded, so I use noweb to put this block in the correct place.
|
|
|
|
First, some prefixes for keybindings that are always passed to EXWM instead of the X application in =line-mode=:
|
|
#+begin_src emacs-lisp :tangle no :noweb-ref exwm-keybindings
|
|
(setq exwm-input-prefix-keys
|
|
`(?\C-x
|
|
?\C-w
|
|
?\M-x
|
|
?\M-u))
|
|
#+end_src
|
|
|
|
Also other local keybindings, that are also available only in =line-mode=:
|
|
#+begin_src emacs-lisp :tangle no :noweb-ref exwm-keybindings
|
|
(defmacro my/app-command (command)
|
|
`(lambda () (interactive) (my/run-in-background ,command)))
|
|
|
|
(general-define-key
|
|
:keymaps '(exwm-mode-map)
|
|
"C-q" #'exwm-input-send-next-key
|
|
"<print>" (my/app-command "flameshot gui")
|
|
"<mode-line> s-<mouse-4>" #'perspective-exwm-cycle-all-buffers-backward
|
|
"<mode-line> s-<mouse-5>" #'perspective-exwm-cycle-all-buffers-forward
|
|
"M-x" #'counsel-M-x
|
|
"M-SPC" (general-key "SPC"))
|
|
#+end_src
|
|
|
|
Simulation keys.
|
|
#+begin_src emacs-lisp :tangle no :noweb-ref exwm-keybindings
|
|
(setq exwm-input-simulation-keys `((,(kbd "M-w") . ,(kbd "C-w"))
|
|
(,(kbd "M-c") . ,(kbd "C-c"))))
|
|
#+end_src
|
|
|
|
A quit function with a confirmation.
|
|
#+begin_src emacs-lisp
|
|
(defun my/exwm-quit ()
|
|
(interactive)
|
|
(when (or (not (eq (selected-window) (next-window)))
|
|
(y-or-n-p "This is the last window. Are you sure?"))
|
|
(evil-quit)))
|
|
#+end_src
|
|
|
|
And keybindings that are available in both =char-mode= and =line-mode=:
|
|
#+begin_src emacs-lisp :tangle no :noweb-ref exwm-keybindings
|
|
(setq exwm-input-global-keys
|
|
`(
|
|
;; Reset to line-mode
|
|
(,(kbd "s-R") . exwm-reset)
|
|
|
|
;; Switch windows
|
|
(,(kbd "s-<left>") . (lambda () (interactive) (my/exwm-windmove 'left)))
|
|
(,(kbd "s-<right>") . (lambda () (interactive) (my/exwm-windmove 'right)))
|
|
(,(kbd "s-<up>") . (lambda () (interactive) (my/exwm-windmove 'up)))
|
|
(,(kbd "s-<down>") . (lambda () (interactive) (my/exwm-windmove 'down)))
|
|
|
|
(,(kbd "s-h"). (lambda () (interactive) (my/exwm-windmove 'left)))
|
|
(,(kbd "s-l") . (lambda () (interactive) (my/exwm-windmove 'right)))
|
|
(,(kbd "s-k") . (lambda () (interactive) (my/exwm-windmove 'up)))
|
|
(,(kbd "s-j") . (lambda () (interactive) (my/exwm-windmove 'down)))
|
|
|
|
;; Moving windows
|
|
(,(kbd "s-H") . (lambda () (interactive) (my/exwm-move-window 'left)))
|
|
(,(kbd "s-L") . (lambda () (interactive) (my/exwm-move-window 'right)))
|
|
(,(kbd "s-K") . (lambda () (interactive) (my/exwm-move-window 'up)))
|
|
(,(kbd "s-J") . (lambda () (interactive) (my/exwm-move-window 'down)))
|
|
|
|
;; Fullscreen
|
|
(,(kbd "s-f") . exwm-layout-toggle-fullscreen)
|
|
(,(kbd "s-F") . exwm-floating-toggle-floating)
|
|
|
|
;; Quit
|
|
(,(kbd "s-Q") . my/exwm-quit)
|
|
|
|
;; Split windows
|
|
(,(kbd "s-s") . evil-window-vsplit)
|
|
(,(kbd "s-v") . evil-window-hsplit)
|
|
|
|
;; Switch perspectives
|
|
(,(kbd "s-,") . persp-prev)
|
|
(,(kbd "s-.") . persp-next)
|
|
|
|
;; Switch buffers
|
|
(,(kbd "s-e") . persp-ivy-switch-buffer)
|
|
(,(kbd "s-E") . my/persp-ivy-switch-buffer-other-window)
|
|
|
|
;; Resize windows
|
|
(,(kbd "s-r") . my/exwm-resize-hydra/body)
|
|
|
|
;; Apps & stuff
|
|
(,(kbd "s-p") . counsel-linux-app)
|
|
(,(kbd "s-P") . async-shell-command)
|
|
(,(kbd "s-;") . my/exwm-apps-hydra/body)
|
|
(,(kbd "s--") . password-store-ivy)
|
|
(,(kbd "s-=") . my/emojify-type)
|
|
(,(kbd "s-i") . ,(my/app-command "copyq menu"))
|
|
|
|
;; Basic controls
|
|
(,(kbd "<XF86AudioRaiseVolume>") . ,(my/app-command "ponymix increase 5 --max-volume 150"))
|
|
(,(kbd "<XF86AudioLowerVolume>") . ,(my/app-command "ponymix decrease 5 --max-volume 150"))
|
|
(,(kbd "<XF86MonBrightnessUp>") . ,(my/app-command "light -A 5"))
|
|
(,(kbd "<XF86MonBrightnessDown>") . ,(my/app-command "light -U 5"))
|
|
(,(kbd "<XF86AudioMute>") . ,(my/app-command "ponymix toggle"))
|
|
|
|
(,(kbd "<XF86AudioPlay>") . ,(my/app-command "mpc toggle"))
|
|
(,(kbd "<XF86AudioPause>") . ,(my/app-command "mpc pause"))
|
|
(,(kbd "<print>") . ,(my/app-command "flameshot gui"))
|
|
|
|
;; Input method
|
|
(,(kbd "M-\\") . my/toggle-input-method)
|
|
|
|
;; Switch workspace
|
|
(,(kbd "s-q") . my/exwm-switch-to-other-monitor)
|
|
(,(kbd "s-w") . exwm-workspace-switch)
|
|
(,(kbd "s-W") . exwm-workspace-move-window)
|
|
(,(kbd "s-<tab>") . my/exwm-workspace-switch-monitor)
|
|
|
|
;; Perspectives
|
|
(,(kbd "s-{") . perspective-exwm-cycle-all-buffers-backward)
|
|
(,(kbd "s-}") . perspective-exwm-cycle-all-buffers-forward)
|
|
(,(kbd "s-[") . perspective-exwm-cycle-exwm-buffers-backward)
|
|
(,(kbd "s-]") . perspective-exwm-cycle-exwm-buffers-forward)
|
|
(,(kbd "s-<mouse-4>") . perspective-exwm-cycle-exwm-buffers-backward)
|
|
(,(kbd "s-<mouse-5>") . perspective-exwm-cycle-exwm-buffers-forward)
|
|
(,(kbd "s-`") . perspective-exwm-switch-perspective)
|
|
(,(kbd "s-o") . ,(my/app-command "rofi -show window"))
|
|
|
|
;; 's-N': Switch to certain workspace with Super (Win) plus a number key (0 - 9)
|
|
,@(mapcar (lambda (i)
|
|
`(,(kbd (format "s-%d" i)) .
|
|
(lambda ()
|
|
(interactive)
|
|
(when (or (< ,i (exwm-workspace--count))
|
|
(y-or-n-p (format "Create workspace %d" ,i)))
|
|
(exwm-workspace-switch-create ,i) ))))
|
|
(number-sequence 0 9))))
|
|
#+end_src
|
|
|
|
A function to apply changes to =exwm-input-global-keys=.
|
|
#+begin_src emacs-lisp
|
|
(defun my/exwm-update-global-keys ()
|
|
(interactive)
|
|
(setq exwm-input--global-keys nil)
|
|
(dolist (i exwm-input-global-keys)
|
|
(exwm-input--set-key (car i) (cdr i)))
|
|
(when exwm--connection
|
|
(exwm-input--update-global-prefix-keys)))
|
|
#+end_src
|
|
*** App shortcuts
|
|
A +transient+ hydra for shortcuts for the most frequent apps.
|
|
#+begin_src emacs-lisp
|
|
(defhydra my/exwm-apps-hydra (:color blue :hint nil)
|
|
"
|
|
^Apps^
|
|
_t_: Terminal (Alacritty)
|
|
_b_: Browser (Firefox)
|
|
_s_: Rocket.Chat
|
|
_e_: Telegram
|
|
_d_: Discord
|
|
"
|
|
("t" (lambda () (interactive) (my/run-in-background "alacritty")))
|
|
("b" (lambda () (interactive) (my/run-in-background "firefox")))
|
|
("s" (lambda () (interactive) (my/run-in-background "flatpak run chat.rocket.RocketChat")))
|
|
("e" (lambda () (interactive) (my/run-in-background "telegram-desktop")))
|
|
("d" (lambda () (interactive) (my/run-in-background "flatpak run com.discordapp.Discord"))))
|
|
#+end_src
|
|
*** Locking up
|
|
Run i3lock.
|
|
|
|
#+begin_src emacs-lisp
|
|
(defun my/exwm-lock ()
|
|
(interactive)
|
|
(my/run-in-background "i3lock -f -i /home/pavel/Pictures/lock-wallpaper.png"))
|
|
#+end_src
|
|
** Fixes
|
|
*** Catch and report all errors raised when invoking command hooks
|
|
- *CREDIT*: Thanks David! https://github.com/daviwil/exwm/commit/7b1be884124711af0a02eac740bdb69446bc54cc
|
|
|
|
#+begin_src emacs-lisp :noweb-ref exwm-fixes :tangle no
|
|
(defun exwm-input--fake-last-command ()
|
|
"Fool some packages into thinking there is a change in the buffer."
|
|
(setq last-command #'exwm-input--noop)
|
|
(condition-case hook-error
|
|
(progn
|
|
(run-hooks 'pre-command-hook)
|
|
(run-hooks 'post-command-hook))
|
|
((error)
|
|
(exwm--log "Error occurred while running command hooks: %s\n\nBacktrace:\n\n%s"
|
|
hook-error
|
|
(with-temp-buffer
|
|
(setq-local standard-output (current-buffer))
|
|
(backtrace)
|
|
(buffer-string))))))
|
|
#+end_src
|
|
*** Improve floating windows behavior
|
|
These 3 settings seem to cause particular trouble with floating windows. Setting them to =nil= improves the stability greatly.
|
|
|
|
#+begin_src emacs-lisp
|
|
(defun my/fix-exwm-floating-windows ()
|
|
(setq-local exwm-workspace-warp-cursor nil)
|
|
(setq-local mouse-autoselect-window nil)
|
|
(setq-local focus-follows-mouse nil))
|
|
|
|
(add-hook 'exwm-floating-setup-hook #'my/fix-exwm-floating-windows)
|
|
#+end_src
|
|
*** Fix exwm--on-ClientMessage
|
|
It seems like this strange commit: [[https://github.com/ch11ng/exwm/commit/ce2191c444ae29edf669790a1002238b8fc90ac4][c90ac4]] breaks focusing on an X frame when switching to a workspace, at least on Emacs <= 28. This reverts to the previous version.
|
|
|
|
#+begin_src emacs-lisp
|
|
(defun exwm--on-ClientMessage-old (raw-data _synthetic)
|
|
"Handle ClientMessage event."
|
|
(let ((obj (make-instance 'xcb:ClientMessage))
|
|
type id data)
|
|
(xcb:unmarshal obj raw-data)
|
|
(setq type (slot-value obj 'type)
|
|
id (slot-value obj 'window)
|
|
data (slot-value (slot-value obj 'data) 'data32))
|
|
(exwm--log "atom=%s(%s)" (x-get-atom-name type exwm-workspace--current)
|
|
type)
|
|
(cond
|
|
;; _NET_NUMBER_OF_DESKTOPS.
|
|
((= type xcb:Atom:_NET_NUMBER_OF_DESKTOPS)
|
|
(let ((current (exwm-workspace--count))
|
|
(requested (elt data 0)))
|
|
;; Only allow increasing/decreasing the workspace number by 1.
|
|
(cond
|
|
((< current requested)
|
|
(make-frame))
|
|
((and (> current requested)
|
|
(> current 1))
|
|
(let ((frame (car (last exwm-workspace--list))))
|
|
(exwm-workspace--get-remove-frame-next-workspace frame)
|
|
(delete-frame frame))))))
|
|
;; _NET_CURRENT_DESKTOP.
|
|
((= type xcb:Atom:_NET_CURRENT_DESKTOP)
|
|
(exwm-workspace-switch (elt data 0)))
|
|
;; _NET_ACTIVE_WINDOW.
|
|
((= type xcb:Atom:_NET_ACTIVE_WINDOW)
|
|
(let ((buffer (exwm--id->buffer id))
|
|
iconic window)
|
|
(when (buffer-live-p buffer)
|
|
(with-current-buffer buffer
|
|
(when (eq exwm--frame exwm-workspace--current)
|
|
(if exwm--floating-frame
|
|
(select-frame exwm--floating-frame)
|
|
(setq iconic (exwm-layout--iconic-state-p))
|
|
(when iconic
|
|
;; State change: iconic => normal.
|
|
(set-window-buffer (frame-selected-window exwm--frame)
|
|
(current-buffer)))
|
|
;; Focus transfer.
|
|
(setq window (get-buffer-window nil t))
|
|
(when (or iconic
|
|
(not (eq window (selected-window))))
|
|
(select-window window))))))))
|
|
;; _NET_CLOSE_WINDOW.
|
|
((= type xcb:Atom:_NET_CLOSE_WINDOW)
|
|
(let ((buffer (exwm--id->buffer id)))
|
|
(when (buffer-live-p buffer)
|
|
(exwm--defer 0 #'kill-buffer buffer))))
|
|
;; _NET_WM_MOVERESIZE
|
|
((= type xcb:Atom:_NET_WM_MOVERESIZE)
|
|
(let ((direction (elt data 2))
|
|
(buffer (exwm--id->buffer id)))
|
|
(unless (and buffer
|
|
(not (buffer-local-value 'exwm--floating-frame buffer)))
|
|
(cond ((= direction
|
|
xcb:ewmh:_NET_WM_MOVERESIZE_SIZE_KEYBOARD)
|
|
;; FIXME
|
|
)
|
|
((= direction
|
|
xcb:ewmh:_NET_WM_MOVERESIZE_MOVE_KEYBOARD)
|
|
;; FIXME
|
|
)
|
|
((= direction xcb:ewmh:_NET_WM_MOVERESIZE_CANCEL)
|
|
(exwm-floating--stop-moveresize))
|
|
;; In case it's a workspace frame.
|
|
((and (not buffer)
|
|
(catch 'break
|
|
(dolist (f exwm-workspace--list)
|
|
(when (or (eq id (frame-parameter f 'exwm-outer-id))
|
|
(eq id (frame-parameter f 'exwm-id)))
|
|
(throw 'break t)))
|
|
nil)))
|
|
(t
|
|
;; In case it's a floating frame,
|
|
;; move the corresponding X window instead.
|
|
(unless buffer
|
|
(catch 'break
|
|
(dolist (pair exwm--id-buffer-alist)
|
|
(with-current-buffer (cdr pair)
|
|
(when
|
|
(and exwm--floating-frame
|
|
(or (eq id
|
|
(frame-parameter exwm--floating-frame
|
|
'exwm-outer-id))
|
|
(eq id
|
|
(frame-parameter exwm--floating-frame
|
|
'exwm-id))))
|
|
(setq id exwm--id)
|
|
(throw 'break nil))))))
|
|
;; Start to move it.
|
|
(exwm-floating--start-moveresize id direction))))))
|
|
;; _NET_REQUEST_FRAME_EXTENTS
|
|
((= type xcb:Atom:_NET_REQUEST_FRAME_EXTENTS)
|
|
(let ((buffer (exwm--id->buffer id))
|
|
top btm)
|
|
(if (or (not buffer)
|
|
(not (buffer-local-value 'exwm--floating-frame buffer)))
|
|
(setq top 0
|
|
btm 0)
|
|
(setq top (window-header-line-height)
|
|
btm (window-mode-line-height)))
|
|
(xcb:+request exwm--connection
|
|
(make-instance 'xcb:ewmh:set-_NET_FRAME_EXTENTS
|
|
:window id
|
|
:left 0
|
|
:right 0
|
|
:top top
|
|
:bottom btm)))
|
|
(xcb:flush exwm--connection))
|
|
;; _NET_WM_DESKTOP.
|
|
((= type xcb:Atom:_NET_WM_DESKTOP)
|
|
(let ((buffer (exwm--id->buffer id)))
|
|
(when (buffer-live-p buffer)
|
|
(exwm-workspace-move-window (elt data 0) id))))
|
|
;; _NET_WM_STATE
|
|
((= type xcb:Atom:_NET_WM_STATE)
|
|
(let ((action (elt data 0))
|
|
(props (list (elt data 1) (elt data 2)))
|
|
(buffer (exwm--id->buffer id))
|
|
props-new)
|
|
;; only support _NET_WM_STATE_FULLSCREEN / _NET_WM_STATE_ADD for frames
|
|
(when (and (not buffer)
|
|
(memq xcb:Atom:_NET_WM_STATE_FULLSCREEN props)
|
|
(= action xcb:ewmh:_NET_WM_STATE_ADD))
|
|
(xcb:+request
|
|
exwm--connection
|
|
(make-instance 'xcb:ewmh:set-_NET_WM_STATE
|
|
:window id
|
|
:data (vector xcb:Atom:_NET_WM_STATE_FULLSCREEN)))
|
|
(xcb:flush exwm--connection))
|
|
(when buffer ;ensure it's managed
|
|
(with-current-buffer buffer
|
|
;; _NET_WM_STATE_FULLSCREEN
|
|
(when (or (memq xcb:Atom:_NET_WM_STATE_FULLSCREEN props)
|
|
(memq xcb:Atom:_NET_WM_STATE_ABOVE props))
|
|
(cond ((= action xcb:ewmh:_NET_WM_STATE_ADD)
|
|
(unless (exwm-layout--fullscreen-p)
|
|
(exwm-layout-set-fullscreen id))
|
|
(push xcb:Atom:_NET_WM_STATE_FULLSCREEN props-new))
|
|
((= action xcb:ewmh:_NET_WM_STATE_REMOVE)
|
|
(when (exwm-layout--fullscreen-p)
|
|
(exwm-layout-unset-fullscreen id)))
|
|
((= action xcb:ewmh:_NET_WM_STATE_TOGGLE)
|
|
(if (exwm-layout--fullscreen-p)
|
|
(exwm-layout-unset-fullscreen id)
|
|
(exwm-layout-set-fullscreen id)
|
|
(push xcb:Atom:_NET_WM_STATE_FULLSCREEN props-new)))))
|
|
;; _NET_WM_STATE_DEMANDS_ATTENTION
|
|
;; FIXME: check (may require other properties set)
|
|
(when (memq xcb:Atom:_NET_WM_STATE_DEMANDS_ATTENTION props)
|
|
(when (= action xcb:ewmh:_NET_WM_STATE_ADD)
|
|
(unless (eq exwm--frame exwm-workspace--current)
|
|
(set-frame-parameter exwm--frame 'exwm-urgency t)
|
|
(setq exwm-workspace--switch-history-outdated t)))
|
|
;; xcb:ewmh:_NET_WM_STATE_REMOVE?
|
|
;; xcb:ewmh:_NET_WM_STATE_TOGGLE?
|
|
)
|
|
(xcb:+request exwm--connection
|
|
(make-instance 'xcb:ewmh:set-_NET_WM_STATE
|
|
:window id :data (vconcat props-new)))
|
|
(xcb:flush exwm--connection)))))
|
|
((= type xcb:Atom:WM_PROTOCOLS)
|
|
(let ((type (elt data 0)))
|
|
(cond ((= type xcb:Atom:_NET_WM_PING)
|
|
(setq exwm-manage--ping-lock nil))
|
|
(t (exwm--log "Unhandled WM_PROTOCOLS of type: %d" type)))))
|
|
((= type xcb:Atom:WM_CHANGE_STATE)
|
|
(let ((buffer (exwm--id->buffer id)))
|
|
(when (and (buffer-live-p buffer)
|
|
(= (elt data 0) xcb:icccm:WM_STATE:IconicState))
|
|
(with-current-buffer buffer
|
|
(if exwm--floating-frame
|
|
(call-interactively #'exwm-floating-hide)
|
|
(bury-buffer))))))
|
|
(t
|
|
(exwm--log "Unhandled: %s(%d)"
|
|
(x-get-atom-name type exwm-workspace--current) type)))))
|
|
|
|
(with-eval-after-load 'exwm
|
|
(advice-add 'exwm--on-ClientMessage :override #'exwm--on-ClientMessage-old))
|
|
#+end_src
|
|
|
|
** Application-specific settings
|
|
Start Nyxt in =char-mode=.
|
|
|
|
#+begin_src emacs-lisp
|
|
(setq exwm-manage-configurations
|
|
'(((member exwm-class-name '("Nyxt"))
|
|
char-mode t)))
|
|
#+end_src
|
|
** EXWM config
|
|
And the EXWM config itself.
|
|
|
|
#+begin_src emacs-lisp :noweb yes
|
|
(defun my/exwm-init ()
|
|
(exwm-workspace-switch 1)
|
|
|
|
(my/exwm-run-polybar)
|
|
(my/exwm-set-wallpaper)
|
|
(my/exwm-run-shepherd)
|
|
(my/run-in-background "gpgconf --reload gpg-agent")
|
|
(when (my/is-arch)
|
|
(my/run-in-background "set_layout")))
|
|
|
|
(defun my/exwm-update-class ()
|
|
(exwm-workspace-rename-buffer (format "EXWM :: %s" exwm-class-name)))
|
|
|
|
(defun my/exwm-set-alpha (alpha)
|
|
(setf (alist-get 'alpha default-frame-alist)
|
|
`(,alpha . ,alpha))
|
|
(cl-loop for frame being the frames
|
|
do (set-frame-parameter frame 'alpha `(,alpha . ,alpha))))
|
|
|
|
(use-package exwm
|
|
:straight t
|
|
:config
|
|
(setq exwm-workspace-number 5)
|
|
(add-hook 'exwm-init-hook #'my/exwm-init)
|
|
(add-hook 'exwm-update-class-hook #'my/exwm-update-class)
|
|
|
|
(require 'exwm-randr)
|
|
(exwm-randr-enable)
|
|
(start-process-shell-command "xrandr" nil "~/bin/scripts/screen-layout")
|
|
(when (string= (system-name) "violet")
|
|
(setq my/exwm-another-monitor "DP-1")
|
|
(setq exwm-randr-workspace-monitor-plist `(2 ,my/exwm-another-monitor 3 ,my/exwm-another-monitor)))
|
|
|
|
(setq exwm-workspace-warp-cursor t)
|
|
(setq mouse-autoselect-window t)
|
|
(setq focus-follows-mouse t)
|
|
|
|
<<exwm-workspace-config>>
|
|
<<exwm-keybindings>>
|
|
<<exwm-mode-line-config>>
|
|
<<exwm-fixes>>
|
|
|
|
(if (my/light-p)
|
|
(my/exwm-set-alpha 100)
|
|
(my/exwm-set-alpha 90))
|
|
|
|
(perspective-exwm-mode)
|
|
(exwm-enable))
|
|
#+end_src
|
|
* i3wm
|
|
:PROPERTIES:
|
|
:header-args+: :tangle ./.config/i3/config
|
|
:END:
|
|
|
|
| Guix dependency | Disabled |
|
|
|-----------------+----------|
|
|
| i3-gaps | |
|
|
| i3lock | true |
|
|
|
|
=i3lock= is disabled because the global one has to be used.
|
|
|
|
[[https://i3wm.org/][i3wm]] is a manual tiling window manager, which is currently my window manager of choice. I've tried several alternatives, including [[https://xmonad.org/][xmonad]] & [[https://github.com/ch11ng/exwm][EXWM]], +but i3 seems to fit my workflow best+ and decided to switch to EXWM. This section is kept for a few cases when I need to be extra sure that my WM doesn't fail.
|
|
|
|
[[https://github.com/Airblader/i3][i3-gaps]] is an i3 fork with a few features like window gaps. I like to enable inner gaps when there is at least one container in a workspace.
|
|
|
|
References:
|
|
- [[https://i3wm.org/docs/][i3wm docs]]
|
|
- [[https://github.com/Airblader/i3/wiki][i3-gaps wiki]]
|
|
|
|
** General settings
|
|
#+begin_src conf-space
|
|
set $mod Mod4
|
|
font pango:monospace 10
|
|
|
|
# Use Mouse+$mod to drag floating windows to their wanted position
|
|
floating_modifier $mod
|
|
|
|
# Move cursor between monitors
|
|
mouse_warping output
|
|
|
|
# Apply XFCE Settings
|
|
# exec xfsettingsd
|
|
# exec xiccd
|
|
|
|
# Set screen layout
|
|
exec ~/bin/scripts/screen-layout
|
|
|
|
# Most needed keybindigs
|
|
# reload the configuration file
|
|
bindsym $mod+Shift+c reload
|
|
|
|
# restart i3 inplace (preserves your layout/session, can be used to upgrade i3)
|
|
bindsym $mod+Shift+r restart
|
|
|
|
# exit i3 (logs you out of your X session)
|
|
bindsym $mod+Shift+e exec "i3-nagbar -t warning -m 'You pressed the exit shortcut. Do you really want to exit i3? This will end your X session.' -b 'Yes, exit i3' 'i3-msg exit'"
|
|
#+end_src
|
|
** Managing windows
|
|
| Guix dependency |
|
|
|---------------------|
|
|
| rust-i3-switch-tabs |
|
|
|
|
Some keybindings for managing windows.
|
|
|
|
=emacs-i3-integration= is a script to pass some command to Emacs to get a consistent set of keybindings in both i3 and Emacs. Check out [[file:Emacs.org::i3 integration][the section in Emacs.org]] for details.
|
|
|
|
Kill focused windows
|
|
#+begin_src conf-space
|
|
bindsym $mod+Shift+q exec emacs-i3-integration kill
|
|
#+end_src
|
|
|
|
Change focus
|
|
#+begin_src conf-space
|
|
bindsym $mod+h exec emacs-i3-integration focus left
|
|
bindsym $mod+j exec emacs-i3-integration focus down
|
|
bindsym $mod+k exec emacs-i3-integration focus up
|
|
bindsym $mod+l exec emacs-i3-integration focus right
|
|
|
|
bindsym $mod+Left exec emacs-i3-integration focus left
|
|
bindsym $mod+Down exec emacs-i3-integration focus down
|
|
bindsym $mod+Up exec emacs-i3-integration focus up
|
|
bindsym $mod+Right exec emacs-i3-integration focus right
|
|
#+end_src
|
|
|
|
Move windows around
|
|
#+begin_src conf-space
|
|
bindsym $mod+Shift+h exec emacs-i3-integration move left
|
|
bindsym $mod+Shift+j exec emacs-i3-integration move down
|
|
bindsym $mod+Shift+k exec emacs-i3-integration move up
|
|
bindsym $mod+Shift+l exec emacs-i3-integration move right
|
|
|
|
bindsym $mod+Shift+Left exec emacs-i3-integration move left
|
|
bindsym $mod+Shift+Down exec emacs-i3-integration move down
|
|
bindsym $mod+Shift+Up exec emacs-i3-integration move up
|
|
bindsym $mod+Shift+Right exec emacs-i3-integration move right
|
|
#+end_src
|
|
|
|
Split windows
|
|
#+begin_src conf-space
|
|
bindsym $mod+s exec emacs-i3-integration split h
|
|
bindsym $mod+v exec emacs-i3-integration split v
|
|
#+end_src
|
|
|
|
Switch tabs
|
|
#+begin_src conf-space
|
|
bindsym $mod+period exec i3-switch-tabs right
|
|
bindsym $mod+comma exec i3-switch-tabs left
|
|
#+end_src
|
|
|
|
Enter fullscreen mode
|
|
#+begin_src conf-space
|
|
# enter fullscreen mode for the focused container
|
|
bindsym $mod+f fullscreen toggle
|
|
bindsym $mod+c fullscreen toggle global
|
|
#+end_src
|
|
|
|
Changing layout
|
|
#+begin_src conf-space
|
|
bindsym $mod+w layout stacking
|
|
bindsym $mod+t layout tabbed
|
|
bindsym $mod+e exec emacs-i3-integration layout toggle split
|
|
#+end_src
|
|
|
|
Toggle tiling/floating, switch between tiled and floating windows
|
|
#+begin_src conf-space
|
|
bindsym $mod+Shift+f floating toggle
|
|
bindsym $mod+z focus mode_toggle
|
|
#+end_src
|
|
|
|
Switching outputs
|
|
#+begin_src conf-space
|
|
bindsym $mod+Tab move workspace to output right
|
|
bindsym $mod+q focus output right
|
|
#+end_src
|
|
|
|
Focus parent and child container
|
|
#+begin_src conf-space
|
|
bindsym $mod+a focus parent
|
|
bindsym $mod+Shift+A focus child
|
|
#+end_src
|
|
|
|
Toggle sticky
|
|
#+begin_src conf-space
|
|
bindsym $mod+Shift+i sticky toggle
|
|
#+end_src
|
|
|
|
Set windows as floating and sticky, move to the top right.
|
|
#+begin_src conf-space
|
|
bindsym $mod+x floating enable; sticky enable; move position 1220 0; resize set width 700 px
|
|
#+end_src
|
|
** Workspaces
|
|
#+begin_src conf-space
|
|
set $w1 "1 🚀"
|
|
set $w2 "2 🌍"
|
|
set $w3 "3 💬"
|
|
set $w4 "4 🛠️️"
|
|
set $w7 "7 🛰️"
|
|
set $w8 "8 📝"
|
|
set $w9 "9 🎵"
|
|
set $w10 "10 📦"
|
|
|
|
bindsym $mod+1 workspace $w1
|
|
bindsym $mod+2 workspace $w2
|
|
bindsym $mod+3 workspace $w3
|
|
bindsym $mod+4 workspace $w4
|
|
bindsym $mod+5 workspace 5
|
|
bindsym $mod+6 workspace 6
|
|
bindsym $mod+7 workspace $w7
|
|
bindsym $mod+8 workspace $w8
|
|
bindsym $mod+9 workspace $w9
|
|
bindsym $mod+0 workspace $w10
|
|
|
|
# move focused container to workspace
|
|
bindsym $mod+Shift+1 move container to workspace $w1
|
|
bindsym $mod+Shift+2 move container to workspace $w2
|
|
bindsym $mod+Shift+3 move container to workspace $w3
|
|
bindsym $mod+Shift+4 move container to workspace $w4
|
|
bindsym $mod+Shift+5 move container to workspace 5
|
|
bindsym $mod+Shift+6 move container to workspace 6
|
|
bindsym $mod+Shift+7 move container to workspace $w7
|
|
bindsym $mod+Shift+8 move container to workspace $w8
|
|
bindsym $mod+Shift+9 move container to workspace $w9
|
|
bindsym $mod+Shift+0 move container to workspace $w10
|
|
#+end_src
|
|
|
|
** Rules
|
|
Rules to automatically assign applications to workspaces and do other stuff, like enable floating.
|
|
|
|
Most apps can be distinguished by a WM class (you can get one with [[https://www.x.org/releases/X11R7.5/doc/man/man1/xprop.1.html][xprop]]), but in some cases it doesn't work, e.g. for terminal applications. In that case rules can be based on a window title, for instance.
|
|
|
|
However, watch out for the following: rule such as ~for_window [title="ncmpcpp.*"] move to workspace $w9~ will move *any* window with a title starting with =ncmpcpp= to workspace =$w9=. For instance, it moves your browser when you google "ncmpcpp".
|
|
|
|
#+begin_src conf-space
|
|
assign [class="Emacs"] $w1
|
|
assign [class="qutebrowser"] $w2
|
|
assign [class="firefox"] $w2
|
|
assign [class="VK"] $w3
|
|
assign [class="Slack"] $w3
|
|
assign [class="discord"] $w3
|
|
assign [class="TelegramDesktop"] $w3
|
|
assign [class="Postman"] $w4
|
|
assign [class="Chromium-browse"] $w4
|
|
assign [class="chromium"] $w4
|
|
assign [class="google-chrome"] $w4
|
|
assign [title="Vue Developer Tools"] $w4
|
|
assign [class="Google Play Music Desktop Player"] $w9
|
|
assign [class="jetbrains-datagrip"] $w4
|
|
assign [class="zoom"] $w7
|
|
assign [class="skype"] $w7
|
|
assign [class="Mailspring"] $w8
|
|
assign [class="Thunderbird"] $w8
|
|
assign [class="Joplin"] $w8
|
|
assign [class="keepassxc"] $w10
|
|
|
|
for_window [title="VirtScreen"] floating enable
|
|
|
|
for_window [title="ncmpcpp.*"] move to workspace $w9
|
|
for_window [title="newsboat.*"] move to workspace $w9
|
|
for_window [title=".*run_wego"] move to workspace $w9
|
|
for_window [class="cinnamon-settings*"] floating enable
|
|
for_window [title="Picture-in-Picture"] sticky enable
|
|
for_window [window_role="GtkFileChooserDialog"] resize set width 1000 px height 800 px
|
|
for_window [window_role="GtkFileChooserDialog"] move position center
|
|
#+end_src
|
|
** Scratchpad
|
|
Scratch terminal, inspired by [[https://www.youtube.com/watch?v=q-l7DnDbiiU][this Luke Smith's video]].
|
|
*** Launch script
|
|
First of all, we have to distinguish a scratchpad terminal from a normal one. To do that, one can create st with a required classname.
|
|
|
|
Then, it would be cool not to duplicate scratchpads, so the following script first looks for a window with a created classname. If it exists, the script just toggles the scratchpad visibility. Otherwise, a new instance of a window is created.
|
|
#+begin_src bash :tangle ./bin/scripts/dropdown
|
|
CLASSNAME="dropdown_tmux"
|
|
COMMAND="alacritty --class $CLASSNAME -e tmux new-session -s $CLASSNAME"
|
|
pid=$(xdotool search --classname "dropdown_tmux")
|
|
if [[ ! -z $pid ]]; then
|
|
i3-msg scratchpad show
|
|
else
|
|
setsid -f ${COMMAND}
|
|
fi
|
|
#+end_src
|
|
*** i3 config
|
|
#+begin_src conf-space
|
|
# Scratchpad
|
|
for_window [instance="dropdown_*"] floating enable
|
|
for_window [instance="dropdown_*"] move scratchpad
|
|
for_window [instance="dropdown_*"] sticky enable
|
|
for_window [instance="dropdown_*"] scratchpad show
|
|
for_window [instance="dropdown_*"] move position center
|
|
|
|
bindsym $mod+u exec ~/bin/scripts/dropdown
|
|
#+end_src
|
|
** Gaps & borders
|
|
The main reason to use i3-gaps
|
|
#+begin_src conf-space
|
|
# Borders
|
|
# for_window [class=".*"] border pixel 0
|
|
default_border pixel 3
|
|
hide_edge_borders both
|
|
|
|
# Gaps
|
|
set $default_inner 10
|
|
set $default_outer 0
|
|
|
|
gaps inner $default_inner
|
|
gaps outer $default_outer
|
|
|
|
smart_gaps on
|
|
#+end_src
|
|
*** Keybindings
|
|
#+begin_src conf-space
|
|
mode "inner gaps" {
|
|
bindsym plus gaps inner current plus 5
|
|
bindsym minus gaps inner current minus 5
|
|
bindsym Shift+plus gaps inner all plus 5
|
|
bindsym Shift+minus gaps inner all minus 5
|
|
bindsym 0 gaps inner current set 0
|
|
bindsym Shift+0 gaps inner all set 0
|
|
|
|
bindsym r gaps inner current set $default_inner
|
|
bindsym Shift+r gaps inner all set $default_inner
|
|
|
|
bindsym Return mode "default"
|
|
bindsym Escape mode "default"
|
|
}
|
|
|
|
mode "outer gaps" {
|
|
bindsym plus gaps outer current plus 5
|
|
bindsym minus gaps outer current minus 5
|
|
bindsym Shift+plus gaps outer all plus 5
|
|
bindsym Shift+minus gaps outer all minus 5
|
|
bindsym 0 gaps outer current set 0
|
|
bindsym Shift+0 gaps outer all set 0
|
|
|
|
bindsym r gaps outer current set $default_outer
|
|
bindsym Shift+r gaps outer all set $default_outer
|
|
|
|
bindsym Return mode "default"
|
|
bindsym Escape mode "default"
|
|
}
|
|
|
|
bindsym $mod+g mode "inner gaps"
|
|
bindsym $mod+Shift+g mode "outer gaps"
|
|
#+end_src
|
|
** Move & resize windows
|
|
| Guix dependency |
|
|
|-----------------------------|
|
|
| python-i3-balance-workspace |
|
|
|
|
A more or less standard set of keybindings to move & resize floating windows. Just be careful to always make a way to return from these new modes, otherwise you'd end up in a rather precarious situation.
|
|
|
|
[[https://github.com/atreyasha/i3-balance-workspace][i3-balance-workspace]] is a small Python package to balance the i3 windows, but for the Emacs integration I also want this button to balance the Emacs windows, so here is a small script to do just that.
|
|
|
|
#+begin_src bash :tangle ~/bin/scripts/i3-emacs-balance-windows
|
|
if [[ $(xdotool getactivewindow getwindowname) =~ ^emacs(:.*)?@.* ]]; then
|
|
emacsclient -e "(balance-windows)" &
|
|
fi
|
|
i3_balance_workspace
|
|
#+end_src
|
|
|
|
#+begin_src conf-space
|
|
mode "resize" {
|
|
|
|
bindsym h exec emacs-i3-integration resize shrink width 10 px or 10 ppt
|
|
bindsym j exec emacs-i3-integration resize grow height 10 px or 10 ppt
|
|
bindsym k exec emacs-i3-integration resize shrink height 10 px or 10 ppt
|
|
bindsym l exec emacs-i3-integration resize grow width 10 px or 10 ppt
|
|
|
|
bindsym Shift+h exec emacs-i3-integration resize shrink width 100 px or 100 ppt
|
|
bindsym Shift+j exec emacs-i3-integration resize grow height 100 px or 100 ppt
|
|
bindsym Shift+k exec emacs-i3-integration resize shrink height 100 px or 100 ppt
|
|
bindsym Shift+l exec emacs-i3-integration resize grow width 100 px or 100 ppt
|
|
|
|
# same bindings, but for the arrow keys
|
|
bindsym Left exec emacs-i3-integration resize shrink width 10 px or 10 ppt
|
|
bindsym Down exec emacs-i3-integration resize grow height 10 px or 10 ppt
|
|
bindsym Up exec emacs-i3-integration resize shrink height 10 px or 10 ppt
|
|
bindsym Right exec emacs-i3-integration resize grow width 10 px or 10 ppt
|
|
|
|
bindsym Shift+Left exec emacs-i3-integration resize shrink width 100 px or 100 ppt
|
|
bindsym Shift+Down exec emacs-i3-integration resize grow height 100 px or 100 ppt
|
|
bindsym Shift+Up exec emacs-i3-integration resize shrink height 100 px or 100 ppt
|
|
bindsym Shift+Right exec emacs-i3-integration resize grow width 100 px or 100 ppt
|
|
|
|
bindsym equal exec i3-emacs-balance-windows
|
|
|
|
# back to normal: Enter or Escape
|
|
bindsym Return mode "default"
|
|
bindsym Escape mode "default"
|
|
}
|
|
|
|
bindsym $mod+r mode "resize"
|
|
|
|
mode "move" {
|
|
bindsym $mod+Tab focus right
|
|
|
|
bindsym Left move left
|
|
bindsym Down move down
|
|
bindsym Up move up
|
|
bindsym Right move right
|
|
|
|
bindsym h move left
|
|
bindsym j move down
|
|
bindsym k move up
|
|
bindsym l move right
|
|
|
|
# back to normal: Enter or Escape
|
|
bindsym Return mode "default"
|
|
bindsym Escape mode "default"
|
|
}
|
|
|
|
bindsym $mod+m mode "move" focus floating
|
|
#+end_src
|
|
** OFF (OFF) Intergration with dmenu
|
|
[[https://tools.suckless.org/dmenu/][dmenu]] is a dynamic menu program for X. I've opted out of using it in favour of rofi, but here is a relevant bit of config.
|
|
|
|
Scripts are located in the =bin/scripts= folder.
|
|
#+begin_src conf-space :tangle no
|
|
# dmenu
|
|
bindsym $mod+d exec i3-dmenu-desktop --dmenu="dmenu -l 10"
|
|
bindsym $mod+apostrophe mode "dmenu"
|
|
|
|
mode "dmenu" {
|
|
bindsym d exec i3-dmenu-desktop --dmenu="dmenu -l 10"; mode default
|
|
bindsym p exec dmenu_run -l 10; mode default
|
|
bindsym m exec dmenu-man; mode default
|
|
bindsym b exec dmenu-buku; mode default
|
|
bindsym f exec dmenu-explore; mode default
|
|
bindsym t exec dmenu-tmuxp; mode default
|
|
bindsym Escape mode "default"
|
|
}
|
|
|
|
bindsym $mod+b exec --no-startup-id dmenu-buku
|
|
#+end_src
|
|
** Integration with rofi
|
|
Keybindings to launch [[https://github.com/davatorium/rofi][rofi]]. For more detail, look the [[*Rofi]] section.
|
|
#+begin_src conf-space
|
|
bindsym $mod+p exec "rofi -modi 'drun,run' -show drun"
|
|
bindsym $mod+b exec --no-startup-id rofi-buku-mine
|
|
bindsym $mod+minus exec rofi-pass
|
|
bindsym $mod+equal exec rofimoji
|
|
|
|
bindsym $mod+apostrophe mode "rofi"
|
|
|
|
mode "rofi" {
|
|
bindsym d exec "rofi -modi 'drun,run' -show drun"
|
|
bindsym m exec rofi-man; mode default
|
|
bindsym b exec rofi-buku-mine; mode default
|
|
bindsym k exec rofi-pass; mode default
|
|
bindsym Escape mode "default"
|
|
}
|
|
#+end_src
|
|
** Launching apps & misc keybindings
|
|
I prefer to use a separate mode to launch most of my apps, with some exceptions.
|
|
*** Apps
|
|
#+begin_src conf-space
|
|
# Launch apps
|
|
# start a terminal at workspace 1
|
|
bindsym $mod+Return exec "i3-msg 'workspace 1 🚀; exec alacritty'"
|
|
|
|
bindsym $mod+i exec "copyq menu"
|
|
bindsym $mod+Shift+x exec "i3lock -f -i /home/pavel/Pictures/lock-wallpaper.png"
|
|
|
|
bindsym $mod+semicolon mode "apps"
|
|
|
|
mode "apps" {
|
|
bindsym Escape mode "default"
|
|
bindsym b exec firefox; mode default
|
|
bindsym v exec vk; mode default
|
|
bindsym s exec slack-wrapper; mode default;
|
|
bindsym d exec "flatpak run com.discordapp.Discord"; mode default;
|
|
bindsym m exec "alacritty -e ncmpcpp"; mode default
|
|
bindsym c exec "copyq toggle"; mode default
|
|
bindsym k exec "keepassxc"; mode default
|
|
# bindsym e exec mailspring; mode default
|
|
bindsym a exec emacs; mode default
|
|
bindsym n exec "alacritty -e newsboat"; mode default
|
|
bindsym w exec "alacritty /home/pavel/bin/scripts/run_wego"; mode default
|
|
# bindsym a exec emacsclient -c; mode default
|
|
# bindsym Shift+a exec emacs; mode default
|
|
}
|
|
#+end_src
|
|
*** Media controls & brightness
|
|
#+begin_src conf-space
|
|
# Pulse Audio controls
|
|
bindsym XF86AudioRaiseVolume exec --no-startup-id "ponymix increase 5 --max-volume 150"
|
|
bindsym XF86AudioLowerVolume exec --no-startup-id "ponymix decrease 5 --max-volume 150"
|
|
bindsym XF86AudioMute exec --no-startup-id "ponymix toggle"
|
|
|
|
exec --no-startup-id xmodmap -e 'keycode 135 = Super_R' && xset -r 135
|
|
bindsym $mod+F2 exec --no-startup-id "ponymix increase 5"
|
|
bindsym $mod+F3 exec --no-startup-id "ponymix decrease 5"
|
|
|
|
# Media player controls
|
|
bindsym XF86AudioPlay exec mpc toggle
|
|
bindsym XF86AudioPause exec mpc pause
|
|
bindsym XF86AudioNext exec mpc next
|
|
bindsym XF86AudioPrev exec mpc prev
|
|
|
|
# Screen brightness
|
|
bindsym XF86MonBrightnessUp exec light -A 5
|
|
bindsym XF86MonBrightnessDown exec light -U 5
|
|
#+end_src
|
|
*** Screenshots
|
|
#+begin_src conf-space
|
|
# Screenshots
|
|
bindsym --release Print exec "flameshot gui"
|
|
bindsym --release Shift+Print exec "xfce4-screenshooter"
|
|
#+end_src
|
|
** Colors
|
|
Application of the XResources theme to the WM.
|
|
#+begin_src conf-space
|
|
exec xrdb -merge $HOME/.Xresources
|
|
|
|
# Colors
|
|
set_from_resource $bg-color background
|
|
set_from_resource $active-color color4
|
|
set_from_resource $inactive-bg-color color8
|
|
set_from_resource $text-color foreground
|
|
set_from_resource $inactive-text-color color7
|
|
set_from_resource $urgent-bg-color color1
|
|
set_from_resource $urgent-text-color color0
|
|
|
|
# window colors
|
|
# border background text indicator child border
|
|
client.focused $active-color $bg-color $text-color $bg-color $active-color
|
|
client.unfocused $bg-color $inactive-bg-color $inactive-text-color $bg-color $bg-color
|
|
client.focused_inactive $active-color $inactive-bg-color $inactive-text-color $bg-color $bg-color
|
|
client.urgent $urgent-bg-color $urgent-bg-color $urgent-text-color $bg-color $urgent-bg-color
|
|
#+end_src
|
|
** OFF (OFF) i3blocks
|
|
I've opted out of i3bar & [[https://github.com/vivien/i3blocks][i3blocks]] for [[https://github.com/polybar/polybar][polybar]]
|
|
#+begin_src conf-space :tangle no
|
|
bar {
|
|
status_command i3blocks -c ~/.config/i3/i3blocks.conf
|
|
i3bar_command i3bar
|
|
font pango:monospace 12
|
|
output HDMI-A-0
|
|
tray_output none
|
|
colors {
|
|
background $bg-color
|
|
separator #757575
|
|
# border background text
|
|
focused_workspace $bg-color $bg-color $text-color
|
|
inactive_workspace $inactive-bg-color $inactive-bg-color $inactive-text-color
|
|
urgent_workspace $urgent-bg-color $urgent-bg-color $urgent-text-color
|
|
}
|
|
}
|
|
|
|
bar {
|
|
status_command i3blocks -c ~/.config/i3/i3blocks.conf
|
|
i3bar_command i3bar
|
|
font pango:monospace 10
|
|
output DVI-D-0
|
|
colors {
|
|
background $bg-color
|
|
separator #757575
|
|
# border background text
|
|
focused_workspace $bg-color $bg-color $text-color
|
|
inactive_workspace $inactive-bg-color $inactive-bg-color $inactive-text-color
|
|
urgent_workspace $urgent-bg-color $urgent-bg-color $urgent-text-color
|
|
}
|
|
}
|
|
#+end_src
|
|
** Keyboard Layout
|
|
A script to set Russian-English keyboard layout:
|
|
#+begin_src bash :tangle ./bin/scripts/set_layout
|
|
setxkbmap -layout us,ru
|
|
setxkbmap -model pc105 -option 'grp:win_space_toggle' -option 'grp:alt_shift_toggle'
|
|
#+end_src
|
|
|
|
A script to toggle the layout
|
|
#+begin_src bash :tangle ./bin/scripts/toggle_layout
|
|
if setxkbmap -query | grep -q us,ru; then
|
|
setxkbmap -layout us
|
|
setxkbmap -option
|
|
else
|
|
setxkbmap -layout us,ru
|
|
setxkbmap -model pc105 -option 'grp:win_space_toggle' -option 'grp:alt_shift_toggle'
|
|
fi
|
|
#+end_src
|
|
|
|
And the relevant i3 settings:
|
|
#+begin_src conf-space
|
|
# Layout
|
|
exec_always --no-startup-id set_layout
|
|
bindsym $mod+slash exec toggle_layout
|
|
#+end_src
|
|
** Autostart
|
|
#+begin_src conf-space
|
|
# Polybar
|
|
exec_always --no-startup-id "bash /home/pavel/bin/polybar.sh"
|
|
|
|
# Wallpaper
|
|
exec_always "feh --bg-fill ~/Pictures/wallpaper.jpg"
|
|
|
|
# Picom
|
|
exec picom
|
|
|
|
# Keynav
|
|
exec keynav
|
|
|
|
# Applets
|
|
exec --no-startup-id nm-applet
|
|
# exec --no-startup-id /usr/bin/blueman-applet
|
|
|
|
exec shepherd
|
|
exec dunst
|
|
exec copyq
|
|
exec "xmodmap ~/.Xmodmap"
|
|
# exec "xrdb -merge ~/.Xresources"
|
|
# exec "bash ~/bin/autostart.sh"
|
|
#+end_src
|
|
* Polybar
|
|
:PROPERTIES:
|
|
:header-args:conf-windows: :tangle ./.config/polybar/config :comments link
|
|
:END:
|
|
|
|
| Category | Guix dependency | Description |
|
|
|-----------------+-----------------+-------------|
|
|
| desktop-polybar | polybar | statusbar |
|
|
|
|
[[https://github.com/polybar/polybar][Polybar]] is a nice-looking, WM-agnostic statusbar program.
|
|
|
|
Don't forget to install the Google Noto Color Emoji font. Guix package with all Noto fonts is way too large.
|
|
|
|
References:
|
|
- [[https://github.com/polybar/polybar/wiki][polybar docs]]
|
|
|
|
** General settings
|
|
This is the most +crazy+ advanced piece of my literate configuration so far.
|
|
|
|
My polybar has:
|
|
- colors from the general color theme;
|
|
- powerline-ish decorations between modules.
|
|
|
|
*** Colors
|
|
+The "colors" part is straightforward enough+. Once upon the time it was so...
|
|
|
|
Polybar can use =Xresources=, but the problem with that is you're supposed to use =colorX= as foreground, not as background. This usually works fine with dark themes from =doom-themes=, but not so much with high-contrast =modus-themes=.
|
|
|
|
So...
|
|
#+NAME: get-polybar-colors
|
|
#+begin_src emacs-lisp :var table=colors :tangle no
|
|
(mapconcat
|
|
(lambda (elem)
|
|
(format "%s = %s" (car elem) (cdr elem)))
|
|
(append
|
|
(nreverse
|
|
(cl-reduce
|
|
(lambda (acc name)
|
|
(let* ((color (my/color-value name)))
|
|
(unless (member name '("black"))
|
|
(setq color (ct-iterate
|
|
color
|
|
(lambda (c) (ct-edit-hsl-l-inc c 2))
|
|
(lambda (c)
|
|
(ct-light-p c 65)))))
|
|
(push (cons name color) acc)
|
|
(push (cons (format "light-%s" name)
|
|
(ct-edit-lab-l-inc
|
|
color
|
|
my/alpha-for-light))
|
|
acc)
|
|
(push (cons (format "dark-%s" name)
|
|
(ct-edit-lab-l-dec
|
|
color
|
|
my/alpha-for-light))
|
|
acc) )
|
|
acc)
|
|
'("black" "red" "green" "yellow" "blue" "magenta" "cyan" "white")
|
|
:initial-value nil))
|
|
`(("background" . ,(or (my/color-value 'bg-active)
|
|
(my/color-value 'bg)))
|
|
("foreground" . "#000000")))
|
|
"\n")
|
|
#+end_src
|
|
|
|
#+RESULTS: get-polybar-colors
|
|
#+begin_example
|
|
black = #222222
|
|
light-black = #303030
|
|
dark-black = #131313
|
|
red = #e28b8b
|
|
light-red = #f69d9d
|
|
dark-red = #cd7879
|
|
green = #31ba54
|
|
light-green = #4acd65
|
|
dark-green = #09a642
|
|
yellow = #e49300
|
|
light-yellow = #faa522
|
|
dark-yellow = #ce8000
|
|
blue = #8f9fe3
|
|
light-blue = #a2b1f7
|
|
dark-blue = #7b8ccf
|
|
magenta = #db81cf
|
|
light-magenta = #ef93e2
|
|
dark-magenta = #c66ebb
|
|
cyan = #66a7e4
|
|
light-cyan = #7ab9f8
|
|
dark-cyan = #5094d0
|
|
white = #fff8f0
|
|
light-white = #fffef6
|
|
dark-white = #eae3dc
|
|
background = #c7c0ba
|
|
foreground = #000000
|
|
#+end_example
|
|
|
|
#+begin_src conf-windows :noweb yes
|
|
[colors]
|
|
<<get-polybar-colors()>>
|
|
#+end_src
|
|
*** Glyph settings
|
|
As for the module decorations though, I find it ironic that with all this fancy rendering around I have to resort to Unicode glyphs.
|
|
|
|
Anyhow, the approach is to put a glyph between two blocks like this:
|
|
#+begin_example
|
|
block1 block2
|
|
#+end_example
|
|
|
|
And set the foreground and background colors like that:
|
|
| | block1 | glyph | block2 |
|
|
|------------+--------+-------+---------|
|
|
| foreground | F1 | B2 | F2 |
|
|
| background | B1 | B1 | B2 |
|
|
|
|
So, that's a start. First, let's define the glyph symbols in the polybar config:
|
|
#+begin_src conf-windows
|
|
[glyph]
|
|
gleft =
|
|
gright =
|
|
#+end_src
|
|
*** Defining modules
|
|
As we want to interweave polybar modules with these glyphs in the right order and with the right colors, it is reasonable to define a single source of truth:
|
|
#+NAME: polybar_modules
|
|
| Index | Module | Color | Glyph |
|
|
|-------+-------------+---------------+-------|
|
|
| 1 | pulseaudio | light-magenta | + |
|
|
| 2 | mpd | magenta | + |
|
|
| 16 | nvidia | light-cyan | + |
|
|
| 3 | cpu | cyan | + |
|
|
| 15 | temperature | cyan | + |
|
|
| 9 | battery | cyan | + |
|
|
| 4 | ram-memory | light-green | + |
|
|
| 5 | swap-memory | green | + |
|
|
| 6 | bandwidth | light-red | + |
|
|
| 7 | openvpn | light-red | |
|
|
| 8 | xkeyboard | red | + |
|
|
| 10 | weather | light-yellow | + |
|
|
| 12 | sun | yellow | + |
|
|
| 13 | aw-afk | light-blue | + |
|
|
| 14 | date | blue | + |
|
|
|
|
Also excluding some modules from certain monitors, which for now is about excluding =battery= from the monitors of my desktop PC:
|
|
|
|
#+NAME: polybar_modules_exclude
|
|
| Monitor | Exclude |
|
|
|----------+---------|
|
|
| DVI-D-0 | battery |
|
|
| HDMI-A-0 | battery |
|
|
| HDMI-0 | battery |
|
|
| DP-1 | battery |
|
|
| eDP | nvidia |
|
|
| eDP-1 | nvidia |
|
|
| DVI-D-0 | nvidia |
|
|
| HDMI-A-0 | nvidia |
|
|
| HDMI-1 | nvidia |
|
|
|
|
Another thing we need to do is to set the color of modules in accordance with the =polybar_modules= table. The background can be determined from the =Color= column with the following code block:
|
|
#+NAME: get-polybar-bg
|
|
#+begin_src emacs-lisp :var table=polybar_modules module="pulseaudio"
|
|
(format
|
|
"${colors.%s}"
|
|
(nth
|
|
2
|
|
(seq-find
|
|
(lambda (el) (string-equal (nth 1 el) module))
|
|
table)))
|
|
#+end_src
|
|
|
|
That block is meant to be invoked in each module definition.
|
|
|
|
*** Generating glyphs
|
|
To generate the required set of glyphs, we need a glyph for every possible combination of adjacent colors that can occur in polybar.
|
|
|
|
Most of these combinations can be inferred from the =polybar_modules= table, the rest are defined in another table:
|
|
#+NAME: polybar_extra_colors
|
|
| Color 1 | Color 2 |
|
|
|------------+---------------|
|
|
| background | white |
|
|
| background | light-magenta |
|
|
| blue | background |
|
|
|
|
#+NAME: polybar-generate-glyphs
|
|
#+begin_src emacs-lisp :var table=polybar_modules exclude-table=polybar_modules_exclude extra=polybar_extra_colors
|
|
(let* ((monitors
|
|
(thread-last
|
|
exclude-table
|
|
(seq-map (lambda (el) (nth 0 el)))
|
|
(seq-uniq)))
|
|
(exclude-combinations
|
|
(seq-map
|
|
(lambda (monitor)
|
|
(seq-map
|
|
(lambda (el) (nth 1 el))
|
|
(seq-filter
|
|
(lambda (el) (and (string-equal (nth 0 el) monitor)
|
|
(nth 1 el)))
|
|
exclude-table)))
|
|
`(,@monitors "")))
|
|
(module-glyph-combinations
|
|
(thread-last
|
|
exclude-combinations
|
|
(seq-map
|
|
(lambda (exclude)
|
|
(thread-last
|
|
table
|
|
(seq-filter
|
|
(lambda (elt)
|
|
(not (or
|
|
(member (nth 1 elt) exclude)
|
|
(not (string-equal (nth 3 elt) "+")))))))))
|
|
(seq-uniq)))
|
|
(color-changes nil))
|
|
(dolist (e extra)
|
|
(add-to-list
|
|
'color-changes
|
|
(concat (nth 0 e) "--" (nth 1 e))))
|
|
(dolist (comb module-glyph-combinations)
|
|
(dotimes (i (1- (length comb)))
|
|
(add-to-list
|
|
'color-changes
|
|
(concat (nth 2 (nth i comb))
|
|
"--"
|
|
(nth 2 (nth (1+ i) comb))))))
|
|
(mapconcat
|
|
(lambda (el)
|
|
(let ((colors (split-string el "--")))
|
|
(format "
|
|
[module/glyph-%s--%s]
|
|
type = custom/text
|
|
content-background = ${colors.%s}
|
|
content-foreground = ${colors.%s}
|
|
content = ${glyph.gright}
|
|
content-font = 5"
|
|
(nth 0 colors)
|
|
(nth 1 colors)
|
|
(nth 0 colors)
|
|
(nth 1 colors))))
|
|
color-changes
|
|
"\n"))
|
|
#+end_src
|
|
|
|
Here's a rough outline of how the code works:
|
|
- =monitors= is a list of unique monitors in =exclude-table=
|
|
- =exclude-combilnations= is a list of lists of module names to be excluded for each monitor
|
|
- =module-glyphs-combinations= is a list of lists of actual modules for each monitor
|
|
- =color-changes= is a list of unique adjacent colors across modules in all monitors
|
|
|
|
Finally, =color-changes= is used to generate glyph modules that look like this:
|
|
#+begin_src conf-windows :tangle no
|
|
[module/glyph-light-cyan--cyan]
|
|
type = custom/text
|
|
content-background = ${colors.light-cyan}
|
|
content-foreground = ${colors.cyan}
|
|
content = ${glyph.gright}
|
|
content-font = 5
|
|
#+end_src
|
|
|
|
As of now, 15 of such modules is generated.
|
|
|
|
Include this to the polybar config itself:
|
|
#+begin_src conf-windows :noweb yes
|
|
<<polybar-generate-glyphs()>>
|
|
#+end_src
|
|
*** Generating set of modules
|
|
To configure polybar itself, we need to generate a set of modules for each monitor.
|
|
|
|
The parameters here, excluding the two required tables, are:
|
|
- =monitor= - the current monitor on which to filter out the blocks by the =polybar_modules_exclude= table,
|
|
- =first-color= - the first color of the first glyph,
|
|
- =last-color= - the second color of the last glyph.
|
|
|
|
#+NAME: polybar-generate-modules
|
|
#+begin_src emacs-lisp :var table=polybar_modules exclude-table=polybar_modules_exclude monitor="DVI-D-0" first-color="background" last-color="background"
|
|
(let* ((exclude-modules
|
|
(thread-last
|
|
exclude-table
|
|
(seq-filter (lambda (el) (string-equal (nth 0 el) monitor)))
|
|
(seq-map (lambda (el) (nth 1 el)))))
|
|
(modules
|
|
(thread-last
|
|
table
|
|
(seq-filter (lambda (el) (not (member (nth 1 el) exclude-modules))))))
|
|
(prev-color first-color)
|
|
(ret nil))
|
|
(concat
|
|
(mapconcat
|
|
(lambda (el)
|
|
(apply
|
|
#'concat
|
|
(list
|
|
(when (string-equal (nth 3 el) "+")
|
|
(setq ret (format "glyph-%s--%s " prev-color (nth 2 el)))
|
|
(setq prev-color (nth 2 el))
|
|
ret)
|
|
(nth 1 el))))
|
|
modules
|
|
" ")
|
|
(unless (string-empty-p last-color) (format " glyph-%s--%s " prev-color last-color))))
|
|
#+end_src
|
|
|
|
The polybar config doesn't support conditional statements, but it does support environment variables, so I pass the parameters from in the launch script.
|
|
|
|
*** Global bar config
|
|
Global bar configuration.
|
|
|
|
Monitor config and base colors.
|
|
|
|
#+begin_src conf-windows
|
|
[bar/mybar]
|
|
monitor = ${env:MONITOR:}
|
|
width = 100%
|
|
height = ${env:HEIGHT:27}
|
|
fixed-center = false
|
|
bottom = ${env:POLYBAR_BOTTOM:true}
|
|
|
|
background = ${colors.background}
|
|
foreground = ${colors.black}
|
|
#+end_src
|
|
|
|
Some geometry settings. These are set this way to make glyphs look the way they should
|
|
|
|
#+begin_src conf-windows
|
|
; line-size = 3
|
|
line-color = #f00
|
|
|
|
padding = 0
|
|
|
|
module-margin-left = 0
|
|
module-margin-right = 0
|
|
margin-bottom = 0
|
|
margin-top = 0
|
|
|
|
; underline-size = 0
|
|
border-size = 0
|
|
|
|
offset-x = 0
|
|
offset-y = 0
|
|
radius = 0.0
|
|
#+end_src
|
|
|
|
Fonts
|
|
#+begin_src conf-windows
|
|
; font-0 = ${env:FONT0:pango:monospace:size=10;1}
|
|
; font-1 = ${env:FONT1:NotoEmoji:scale=10:antialias=false;0}
|
|
; font-2 = ${env:FONT2:fontawesome:pixelsize=10;1}
|
|
; font-3 = ${env:FONT3:JetBrains Mono Nerd Font:monospace:size=10;1}
|
|
|
|
font-0 = pango:monospace:size=13;2
|
|
font-1 = NotoEmoji:scale=10:antialias=false;1
|
|
font-2 = fontawesome:pixelsize=13;3
|
|
font-3 = JetBrains Mono Nerd Font:monospace:size=13;4
|
|
font-4 = JetBrains Mono Nerd Font:monospace:size=17;4
|
|
#+end_src
|
|
|
|
Modules. Because I sometimes set up different blocks on different monitors, they are set via environment variables.
|
|
#+begin_src conf-windows
|
|
modules-left = i3
|
|
; modules-center = test
|
|
modules-right = ${env:RIGHT_BLOCKS}
|
|
|
|
tray-position = ${env:TRAY:right}
|
|
tray-padding = 0
|
|
tray-maxsize = 16
|
|
tray-background = ${colors.background}
|
|
|
|
wm-restack = i3
|
|
; override-redirect = true
|
|
|
|
scroll-up = i3wm-wsnext
|
|
scroll-down = i3wm-wsprev
|
|
|
|
; cursor-click = pointer
|
|
; cursor-scroll = ns-resize
|
|
#+end_src
|
|
|
|
Misc settings.
|
|
#+begin_src conf-windows
|
|
[settings]
|
|
screenchange-reload = true
|
|
compositing-background = source
|
|
compositing-foreground = over
|
|
compositing-overline = over
|
|
compositing-underline = over
|
|
compositing-border = over
|
|
|
|
[global/wm]
|
|
margin-top = 0
|
|
margin-bottom = 0
|
|
#+end_src
|
|
*** Launch script
|
|
The script below allows me to:
|
|
- have different blocks on my two different-sized monitors and my laptop;
|
|
- have different settings on my desktop PC and laptop;
|
|
|
|
#+begin_src bash :tangle ./bin/polybar.sh :noweb yes
|
|
hostname=$(hostname)
|
|
# Settings varying on the hostname
|
|
if [ "$hostname" = "azure" ]; then
|
|
TRAY_MONITOR="eDP-1"
|
|
elif [ "$hostname" = "eminence" ]; then
|
|
if xrandr --query | grep " connected" | cut -d" " -f1 | grep -q "HDMI-A-0"; then
|
|
TRAY_MONITOR="HDMI-A-0"
|
|
else
|
|
TRAY_MONITOR="eDP"
|
|
fi
|
|
elif [ "$hostname" = "iris" ]; then
|
|
TRAY_MONITOR="HDMI-1"
|
|
else
|
|
TRAY_MONITOR="DP-1"
|
|
fi
|
|
|
|
# Setting varying on the monitor
|
|
declare -A FONT_SIZES=(
|
|
["eDP"]="13"
|
|
["eDP-1"]="13"
|
|
["DVI-D-0"]="13"
|
|
["HDMI-A-0"]="13"
|
|
["HDMI-1"]="13"
|
|
["HDMI-0"]="13"
|
|
["DP-1"]="13"
|
|
)
|
|
declare -A EMOJI_SCALE=(
|
|
["eDP"]="9"
|
|
["eDP-1"]="9"
|
|
["DVI-D-0"]="10"
|
|
["HDMI-A-0"]="10"
|
|
["HDMI-1"]="10"
|
|
["HDMI-0"]="10"
|
|
["DP-1"]="10"
|
|
)
|
|
declare -A BAR_HEIGHT=(
|
|
["eDP"]="29"
|
|
["eDP-1"]="29"
|
|
["DVI-D-0"]="29"
|
|
["HDMI-A-0"]="29"
|
|
["HDMI-1"]="29"
|
|
["HDMI-0"]="29"
|
|
["DP-1"]="29"
|
|
)
|
|
declare -A BLOCKS=(
|
|
["eDP"]="<<polybar-generate-modules(monitor="eDP")>>"
|
|
["eDP-1"]="<<polybar-generate-modules(monitor="eDP-1")>>"
|
|
["DVI-D-0"]="<<polybar-generate-modules(monitor="DVI-D-0")>>"
|
|
["HDMI-A-0"]="<<polybar-generate-modules(monitor="HDMI-A-0")>>"
|
|
["HDMI-1"]="<<polybar-generate-modules(monitor="HDMI-1")>>"
|
|
["HDMI-0"]="<<polybar-generate-modules(monitor="HDMI-0")>>"
|
|
["DP-1"]="<<polybar-generate-modules(monitor="DP-1")>>"
|
|
)
|
|
|
|
declare -A TEMP_HWMON_PATHS=(
|
|
["eminence"]="/sys/devices/pci0000:00/0000:00:18.3/hwmon/hwmon2/temp1_input"
|
|
["indigo"]="/sys/devices/platform/coretemp.0/hwmon/hwmon2/temp1_input"
|
|
["violet"]="/sys/devices/platform/coretemp.0/hwmon/hwmon2/temp1_input"
|
|
)
|
|
|
|
# Geolocation for some modules
|
|
export LOC="SPB"
|
|
|
|
# export IPSTACK_API_KEY=$(pass show My_Online/APIs/ipstack | head -n 1)
|
|
|
|
pkill polybar
|
|
for m in $(xrandr --query | grep " connected" | cut -d" " -f1); do
|
|
export MONITOR=$m
|
|
if [ "$MONITOR" = "$TRAY_MONITOR" ]; then
|
|
export TRAY="right"
|
|
else
|
|
export TRAY="none"
|
|
fi
|
|
SIZE=${FONT_SIZES[$MONITOR]}
|
|
SCALE=${EMOJI_SCALE[$MONITOR]}
|
|
TEMP=${TEMP_HWMON_PATHS[$(hostname)]}
|
|
if [[ -z "$SCALE" ]]; then
|
|
continue
|
|
fi
|
|
# export FONT0="pango:monospace:size=$SIZE;1"
|
|
# export FONT1="NotoEmoji:scale=$SCALE:antialias=false;1"
|
|
# export FONT2="fontawesome:pixelsize=$SIZE;1"
|
|
# export FONT3="JetBrains Mono Nerd Font:monospace:size=15;1"
|
|
export HEIGHT=${BAR_HEIGHT[$MONITOR]}
|
|
export RIGHT_BLOCKS=${BLOCKS[$MONITOR]}
|
|
export TEMP_HWMON_PATH=${TEMP}
|
|
polybar mybar &
|
|
done
|
|
#+end_src
|
|
|
|
** Individual modules
|
|
Some of the custom modules below use Org mode noweb to evaluate colors, because it's faster than querying =xrdb= at runtime. I wish I could reference polybar values there, but [[https://github.com/polybar/polybar/issues/615][it looks like this is impossible]].
|
|
|
|
If you want to copy something, you can go to the [[file:bin/polybar/][bin/polybar]] folder.
|
|
|
|
*** pulseaudio
|
|
PulseAudio status
|
|
#+begin_src conf-windows :noweb yes
|
|
[module/pulseaudio]
|
|
type = internal/pulseaudio
|
|
use-ui-max = true
|
|
|
|
bar-volume-width = 7
|
|
; bar-volume-foreground-0 = ${colors.white}
|
|
; bar-volume-foreground-1 = ${colors.yellow}
|
|
; bar-volume-foreground-2 = ${colors.yellow}
|
|
; bar-volume-foreground-3 = ${colors.blue}
|
|
; bar-volume-foreground-4 = ${colors.blue}
|
|
; bar-volume-foreground-5 = ${colors.green}
|
|
; bar-volume-foreground-6 = ${colors.green}
|
|
bar-volume-gradient = false
|
|
bar-volume-indicator = |
|
|
bar-volume-indicator-font = 2
|
|
bar-volume-fill = ─
|
|
bar-volume-fill-font = 2
|
|
bar-volume-empty = ─
|
|
bar-volume-empty-font = 2
|
|
; bar-volume-empty-foreground = ${colors.light-white}
|
|
|
|
format-volume = ♪ <ramp-volume> <label-volume>
|
|
label-volume = %percentage%%
|
|
|
|
ramp-volume-0 = ▁
|
|
ramp-volume-1 = ▂
|
|
ramp-volume-2 = ▃
|
|
ramp-volume-3 = ▄
|
|
ramp-volume-4 = ▅
|
|
ramp-volume-5 = ▆
|
|
ramp-volume-6 = ▇
|
|
ramp-volume-7 = █
|
|
|
|
format-muted = ♪ <label-muted>
|
|
label-muted = MUTE
|
|
|
|
format-volume-background = <<get-polybar-bg(module="pulseaudio")>>
|
|
format-muted-background = <<get-polybar-bg(module="pulseaudio")>>
|
|
format-volume-foreground = ${colors.foreground}
|
|
format-muted-foreground = ${colors.foreground}
|
|
|
|
; format-volume-underline = ${colors.white}
|
|
; format-muted-underline = ${colors.light-black}
|
|
#+end_src
|
|
|
|
*** mpd
|
|
[[https://www.musicpd.org/][Music Player Daemon]] status
|
|
#+begin_src conf-windows :noweb yes
|
|
[module/mpd]
|
|
type = internal/mpd
|
|
|
|
format-playing = <toggle> <label-time> <label-song>
|
|
format-paused = <toggle> <label-time> <label-song>
|
|
format-stopped = " "
|
|
label-song = [%album-artist%] %title%
|
|
label-time = %elapsed%/%total%
|
|
|
|
label-song-maxlen = 30
|
|
label-song-ellipsis = true
|
|
|
|
; format-playing-underline = ${colors.yellow}
|
|
; format-paused-underline = ${colors.yellow}
|
|
; format-stopped-underline = ${colors.yellow}
|
|
|
|
format-playing-background = <<get-polybar-bg(module="mpd")>>
|
|
format-paused-background = <<get-polybar-bg(module="mpd")>>
|
|
format-stopped-background = <<get-polybar-bg(module="mpd")>>
|
|
format-playing-foreground = ${colors.foreground}
|
|
format-paused-foreground = ${colors.foreground}
|
|
format-stopped-foreground = ${colors.foreground}
|
|
|
|
label-separator = 0
|
|
separator-foreground = ${colors.red}
|
|
|
|
icon-pause =
|
|
icon-play =
|
|
icon-stop =
|
|
icon-prev = 1
|
|
icon-next = 2
|
|
#+end_src
|
|
|
|
*** cpu
|
|
CPU usage
|
|
#+begin_src conf-windows :noweb yes
|
|
[module/cpu]
|
|
type = internal/cpu
|
|
format = " <label>"
|
|
label = %percentage%%
|
|
format-background = <<get-polybar-bg(module="cpu")>>
|
|
format-foreground = ${colors.foreground}
|
|
#+end_src
|
|
*** nvidia
|
|
Display NVIDIA usage with nvidia-smi
|
|
|
|
#+begin_src bash :tangle ~/bin/polybar/nvidia.sh
|
|
nvidia-smi --query-gpu=utilization.gpu,power.draw,temperature.gpu,memory.used --format=csv,noheader | sed -s 's/ %/%/;s/W, [0-9]\+/&°C/;s/,/ /g'
|
|
#+end_src
|
|
|
|
#+begin_src conf-windows :noweb yes
|
|
[module/nvidia]
|
|
type = custom/script
|
|
exec = /home/pavel/bin/polybar/nvidia.sh
|
|
interval = 2
|
|
format = <label>
|
|
; tail = true
|
|
|
|
format-background = <<get-polybar-bg(module="nvidia")>>
|
|
format-foreground = ${colors.foreground}
|
|
#+end_src
|
|
|
|
*** ram-memory
|
|
RAM usage
|
|
#+begin_src conf-windows :noweb yes
|
|
[module/ram-memory]
|
|
type = internal/memory
|
|
interval = 10
|
|
|
|
ramp-used-0 = ▁
|
|
ramp-used-1 = ▂
|
|
ramp-used-2 = ▃
|
|
ramp-used-3 = ▄
|
|
ramp-used-4 = ▅
|
|
ramp-used-5 = ▆
|
|
ramp-used-6 = ▇
|
|
ramp-used-7 = █
|
|
|
|
format = <label>
|
|
label=%gb_used:.1f%
|
|
|
|
; format-underline = ${colors.blue}
|
|
format-background = <<get-polybar-bg(module="ram-memory")>>
|
|
format-foreground = ${colors.foreground}
|
|
#+end_src
|
|
|
|
*** swap-memory
|
|
Swap usage
|
|
#+begin_src conf-windows :noweb yes
|
|
[module/swap-memory]
|
|
type = internal/memory
|
|
interval = 10
|
|
|
|
label= %gb_swap_used:.1f%
|
|
format-background = <<get-polybar-bg(module="swap-memory")>>
|
|
format-foreground = ${colors.foreground}
|
|
#+end_src
|
|
|
|
*** network
|
|
Upload/download speed
|
|
|
|
UPD <2022-07-24 Sun>: Somehow it doesn't work with my current internet setup.
|
|
|
|
#+begin_src conf-windows :noweb yes
|
|
[module/network]
|
|
type = internal/network
|
|
interval = 1
|
|
|
|
interface = ${env:WLAN_INTERFACE}
|
|
|
|
; format-connected = [<ramp-signal>] <label-connected>
|
|
|
|
label-connected = ↓ %downspeed% ↑ %upspeed%
|
|
label-disconnected = X
|
|
|
|
; format-connected-underline = ${colors.green}
|
|
; format-disconnected-underline = ${colors.red}
|
|
format-connected-background = <<get-polybar-bg(module="network")>>
|
|
format-disconnected-background = <<get-polybar-bg(module="network")>>
|
|
format-connected-foreground = ${colors.foreground}
|
|
format-disconnected-foreground = ${colors.foreground}
|
|
|
|
ramp-signal-0 = 0
|
|
ramp-signal-1 = 1
|
|
ramp-signal-2 = 2
|
|
ramp-signal-3 = 3
|
|
ramp-signal-4 = 4
|
|
ramp-signal-5 = 5
|
|
#+end_src
|
|
|
|
*** bandwidth
|
|
[[file:bin/polybar/bandwidth3.sh][My adaption]] of an i3blocks script called "[[https://github.com/vivien/i3blocks-contrib/tree/master/bandwidth3][bandwidth3]]". I've only changed some defaults that are awkward to set with polybar.
|
|
|
|
#+begin_src conf-windows :noweb yes
|
|
[module/bandwidth]
|
|
type = custom/script
|
|
exec = /home/pavel/bin/polybar/bandwidth3.sh
|
|
interval = 0
|
|
tail = true
|
|
|
|
format-background = <<get-polybar-bg(module="bandwidth")>>
|
|
format-foreground = ${colors.foreground}
|
|
#+end_src
|
|
|
|
#+begin_src bash :tangle ./bin/polybar/bandwidth3.sh
|
|
# Copyright (C) 2015 James Murphy
|
|
# Copyright (C) 2022 Pavel Korytov
|
|
# Licensed under the terms of the GNU GPL v2 only.
|
|
|
|
iface="${BLOCK_INSTANCE}"
|
|
iface="${IFACE:-$iface}"
|
|
dt="${DT:-1}"
|
|
unit="${UNIT:-KB}"
|
|
printf_command="${PRINTF_COMMAND:-"printf \"↓ %-2.1f ↑ %2.1f [%s/s]\\n\", rx, wx, unit;"}"
|
|
|
|
function default_interface {
|
|
ip route | awk '/^default via/ {print $5; exit}'
|
|
}
|
|
|
|
function check_proc_net_dev {
|
|
if [ ! -f "/proc/net/dev" ]; then
|
|
echo "/proc/net/dev not found"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
function list_interfaces {
|
|
check_proc_net_dev
|
|
echo "Interfaces in /proc/net/dev:"
|
|
grep -o "^[^:]\\+:" /proc/net/dev | tr -d " :"
|
|
}
|
|
|
|
while getopts i:t:u:p:lh opt; do
|
|
case "$opt" in
|
|
i) iface="$OPTARG" ;;
|
|
t) dt="$OPTARG" ;;
|
|
u) unit="$OPTARG" ;;
|
|
p) printf_command="$OPTARG" ;;
|
|
l) list_interfaces && exit 0 ;;
|
|
h) printf \
|
|
"Usage: bandwidth3 [-i interface] [-t time] [-u unit] [-p printf_command] [-l] [-h]
|
|
Options:
|
|
-i\tNetwork interface to measure. Default determined using \`ip route\`.
|
|
-t\tTime interval in seconds between measurements. Default: 3
|
|
-u\tUnits to measure bytes in. Default: Mb
|
|
\tAllowed units: Kb, KB, Mb, MB, Gb, GB, Tb, TB
|
|
\tUnits may have optional it/its/yte/ytes on the end, e.g. Mbits, KByte
|
|
-p\tAwk command to be called after a measurement is made.
|
|
\tDefault: printf \"<span font='FontAwesome'> </span>%%-5.1f/%%5.1f %%s/s\\\\n\", rx, wx, unit;
|
|
\tExposed variables: rx, wx, tx, unit, iface
|
|
-l\tList available interfaces in /proc/net/dev
|
|
-h\tShow this help text
|
|
" && exit 0;;
|
|
esac
|
|
done
|
|
|
|
check_proc_net_dev
|
|
|
|
iface="${iface:-$(default_interface)}"
|
|
while [ -z "$iface" ]; do
|
|
echo No default interface
|
|
sleep "$dt"
|
|
iface=$(default_interface)
|
|
done
|
|
|
|
case "$unit" in
|
|
Kb|Kbit|Kbits) bytes_per_unit=$((1024 / 8));;
|
|
KB|KByte|KBytes) bytes_per_unit=$((1024));;
|
|
Mb|Mbit|Mbits) bytes_per_unit=$((1024 * 1024 / 8));;
|
|
MB|MByte|MBytes) bytes_per_unit=$((1024 * 1024));;
|
|
Gb|Gbit|Gbits) bytes_per_unit=$((1024 * 1024 * 1024 / 8));;
|
|
GB|GByte|GBytes) bytes_per_unit=$((1024 * 1024 * 1024));;
|
|
Tb|Tbit|Tbits) bytes_per_unit=$((1024 * 1024 * 1024 * 1024 / 8));;
|
|
TB|TByte|TBytes) bytes_per_unit=$((1024 * 1024 * 1024 * 1024));;
|
|
*) echo Bad unit "$unit" && exit 1;;
|
|
esac
|
|
|
|
scalar=$((bytes_per_unit * dt))
|
|
init_line=$(cat /proc/net/dev | grep "^[ ]*$iface:")
|
|
if [ -z "$init_line" ]; then
|
|
echo Interface not found in /proc/net/dev: "$iface"
|
|
exit 1
|
|
fi
|
|
|
|
init_received=$(awk '{print $2}' <<< $init_line)
|
|
init_sent=$(awk '{print $10}' <<< $init_line)
|
|
|
|
(while true; do cat /proc/net/dev; sleep "$dt"; done) |\
|
|
stdbuf -oL grep "^[ ]*$iface:"|\
|
|
awk -v scalar="$scalar" -v unit="$unit" -v iface="$iface" '
|
|
BEGIN{old_received='"$init_received"';old_sent='"$init_sent"'}
|
|
{
|
|
received=$2
|
|
sent=$10
|
|
rx=(received-old_received)/scalar;
|
|
wx=(sent-old_sent)/scalar;
|
|
tx=rx+wr;
|
|
old_received=received;
|
|
old_sent=sent;
|
|
if(rx >= 0 && wx >= 0){
|
|
'"$printf_command"';
|
|
fflush(stdout);
|
|
}
|
|
}
|
|
'
|
|
#+end_src
|
|
|
|
*** ipstack-vpn
|
|
| Category | Guix dependency | Description |
|
|
|-----------------+-----------------+-------------------------|
|
|
| desktop-polybar | bind:utils | Provides dig |
|
|
| desktop-polybar | curl | |
|
|
| desktop-polybar | jq | util to work with JSONs |
|
|
|
|
A module to get a country of the current IP and openvpn status. Uses [[https://ipstack.com/][ipstack]] API.
|
|
|
|
#+begin_src bash :tangle ./bin/polybar/ipstack-vpn.sh :noweb yes
|
|
ip=$(dig +short +timeout=1 myip.opendns.com @resolver1.opendns.com 2> /dev/null)
|
|
# API_KEY="$(pass show My_Online/APIs/ipstack | head -n 1)"
|
|
API_KEY=$IPSTACK_API_KEY
|
|
if [[ -z $ip || $ip == *"timed out"* ]]; then
|
|
echo "%{u<<get-color(name="red")>>}%{+u} ?? %{u-}"
|
|
exit
|
|
fi
|
|
ip_info=$(curl -s http://api.ipstack.com/${ip}?access_key=${API_KEY})
|
|
# emoji=$(echo $ip_info | jq -r '.location.country_flag_emoji')
|
|
code=$(echo $ip_info | jq -r '.country_code' 2> /dev/null)
|
|
vpn=$(pgrep -a openvpn$ | head -n 1 | awk '{print $NF }' | cut -d '.' -f 1)
|
|
|
|
if [[ -z $code ]]; then
|
|
code="??"
|
|
fi
|
|
|
|
if [ -n "$vpn" ]; then
|
|
echo "%{u<<get-color(name="blue")>>}%{+u} $code %{u-}"
|
|
else
|
|
echo "%{u<<get-color(name="red")>>}%{+u} $code %{u-}"
|
|
fi
|
|
#+end_src
|
|
|
|
#+begin_src conf-windows
|
|
[module/ipstack-vpn]
|
|
type = custom/script
|
|
exec = /home/pavel/bin/polybar/ipstack-vpn.sh
|
|
interval = 1200
|
|
#+end_src
|
|
*** openvpn
|
|
A module to check if openvpn is running.
|
|
|
|
#+begin_src bash :tangle ./bin/polybar/openvpn.sh :noweb yes
|
|
vpn=$(pgrep -a openvpn$ | head -n 1 | awk '{print $NF }' | cut -d '.' -f 1)
|
|
if [ -n "$vpn" ]; then
|
|
echo " "
|
|
else
|
|
echo " "
|
|
fi
|
|
#+end_src
|
|
|
|
#+begin_src conf-windows :noweb yes
|
|
[module/openvpn]
|
|
type = custom/script
|
|
exec = /home/pavel/bin/polybar/openvpn.sh
|
|
format-background = <<get-polybar-bg(module="openvpn")>>
|
|
format-foreground = ${colors.foreground}
|
|
interval = 1200
|
|
#+end_src
|
|
*** xkeyboard
|
|
Current keyboard layout
|
|
#+begin_src conf-windows :noweb yes
|
|
[module/xkeyboard]
|
|
type = internal/xkeyboard
|
|
format = <label-layout>
|
|
|
|
; format-underline = ${colors.magenta}
|
|
format-background = <<get-polybar-bg(module="xkeyboard")>>
|
|
format-foreground = ${colors.foreground}
|
|
label-layout = %icon%
|
|
layout-icon-0 = ru;RU
|
|
layout-icon-1 = us;US
|
|
#+end_src
|
|
|
|
*** battery
|
|
#+begin_src conf-windows :noweb yes
|
|
[module/battery]
|
|
type = internal/battery
|
|
battery = BAT0
|
|
adapter = ADP0
|
|
|
|
time-format = %H:%M
|
|
format-discharging = <ramp-capacity> <label-discharging>
|
|
format-discharging-background = <<get-polybar-bg(module="battery")>>
|
|
format-charging-background = <<get-polybar-bg(module="battery")>>
|
|
format-full-background = <<get-polybar-bg(module="battery")>>
|
|
format-foreground = ${colors.foreground}
|
|
label-discharging = %percentage%% %time%
|
|
label-charging = %percentage%% %time%
|
|
|
|
ramp-capacity-0 =
|
|
ramp-capacity-1 =
|
|
ramp-capacity-2 =
|
|
ramp-capacity-3 =
|
|
ramp-capacity-4 =
|
|
#+end_src
|
|
|
|
*** temperature
|
|
#+begin_src conf-windows :noweb yes
|
|
[module/temperature]
|
|
type = internal/temperature
|
|
interval = 2
|
|
|
|
hwmon-path = ${env:TEMP_HWMON_PATH}
|
|
|
|
format = <label>
|
|
format-foreground = ${colors.foreground}
|
|
format-background = <<get-polybar-bg(module="battery")>>
|
|
format-warn = <label-warn>
|
|
format-warn-foreground = ${colors.foreground}
|
|
format-warn-background = <<get-polybar-bg(module="battery")>>
|
|
#+end_src
|
|
*** weather
|
|
Gets current weather from [[http://wttr.in/][wttr.in]]
|
|
#+begin_src bash :tangle ./bin/polybar/weather.sh
|
|
bar_format="${BAR_FORMAT:-"%t"}"
|
|
location="${LOCATION:-"Saint-Petersburg"}"
|
|
format_1=${FORMAT_1:-"qF"}
|
|
format_2=${FORMAT_1:-"format=v2n"}
|
|
|
|
bar_weather=$(curl -s wttr.in/${location}?format=${bar_format} || echo "??")
|
|
if [ -z "$bar_weather" ]; then
|
|
exit 1
|
|
elif [[ "$bar_weather" == *"Unknown"* || "$bar_weather" == *"Sorry"* || "$bar_weather" == *"Bad Gateway"* ]]; then
|
|
echo "??"
|
|
exit 1
|
|
else
|
|
echo ${bar_weather}
|
|
fi
|
|
#+end_src
|
|
|
|
#+begin_src conf-windows :noweb yes
|
|
[module/weather]
|
|
type = custom/script
|
|
exec = /home/pavel/bin/polybar/weather.sh
|
|
; format-underline = ${colors.red}
|
|
format-background = <<get-polybar-bg(module="weather")>>
|
|
format-foreground = ${colors.foreground}
|
|
interval = 1200
|
|
#+end_src
|
|
*** sun
|
|
| Category | Guix dependency |
|
|
|-----------------+-----------------|
|
|
| desktop-polybar | sunwait |
|
|
|
|
Prints out the time of sunrise/sunset. Uses [[https://github.com/risacher/sunwait][sunwait]]
|
|
|
|
#+begin_src bash :tangle ./bin/polybar/sun.sh :noweb yes
|
|
declare -A LAT_DATA=(
|
|
["TMN"]="57.15N"
|
|
["SPB"]="59.9375N"
|
|
)
|
|
declare -A LON_DATA=(
|
|
["TMN"]="65.533333E"
|
|
["SPB"]="30.308611E"
|
|
)
|
|
if [ -z "$LOC" ]; then
|
|
echo "LOC?"
|
|
exit -1
|
|
fi
|
|
LAT=${LAT_DATA[$LOC]}
|
|
LON=${LON_DATA[$LOC]}
|
|
|
|
time=$(sunwait poll daylight rise ${LAT} $LON)
|
|
|
|
if [[ ${time} == 'DAY' ]]; then
|
|
sunset=$(sunwait list daylight set ${LAT} ${LON})
|
|
# echo "%{u<<get-color(name="yellow")>>}%{+u} $sunset %{u-}"
|
|
echo $sunset
|
|
else
|
|
sunrise=$(sunwait list daylight rise ${LAT} ${LON})
|
|
# echo "%{u<<get-color(name="red")>>}%{+u} $sunrise %{u-}"
|
|
echo $sunrise
|
|
fi
|
|
#+end_src
|
|
|
|
#+begin_src conf-windows :noweb yes
|
|
[module/sun]
|
|
type = custom/script
|
|
exec = /home/pavel/bin/polybar/sun.sh
|
|
format-background = <<get-polybar-bg(module="sun")>>
|
|
format-foreground = ${colors.foreground}
|
|
interval = 60
|
|
#+end_src
|
|
*** aw-afk
|
|
Prints out a current uptime and non-AFK time from [[https://github.com/ActivityWatch][ActivityWatch]] server
|
|
|
|
| Category | Guix dependency |
|
|
|-----------------+-----------------|
|
|
| desktop-polybar | dateutils |
|
|
|
|
#+begin_src bash :tangle ./bin/polybar/aw_afk.sh :noweb yes
|
|
afk_event=$(curl -s -X GET "http://localhost:5600/api/0/buckets/aw-watcher-afk_$(hostname)/events?limit=1" -H "accept: application/json")
|
|
status=$(echo ${afk_event} | jq -r '.[0].data.status')
|
|
afk_time=$(echo "${afk_event}" | jq -r '.[0].duration' | xargs -I ! date -u -d @! +"%H:%M")
|
|
|
|
uptime=$(uptime | awk '{ print substr($3, 0, length($3) - 1) }' | xargs -I ! date -d ! +"%H:%M")
|
|
res="${afk_time} / ${uptime}"
|
|
if [[ $status == 'afk' ]]; then
|
|
# echo "%{u<<get-color(name="red")>>}%{+u} [AFK] $res %{u-}"
|
|
echo "[AFK] $res"
|
|
else
|
|
# echo "%{u<<get-color(name="blue")>>}%{+u} $res %{u-}"
|
|
echo "$res"
|
|
fi
|
|
#+end_src
|
|
|
|
#+begin_src conf-windows :noweb yes
|
|
[module/aw-afk]
|
|
type = custom/script
|
|
exec = /home/pavel/bin/polybar/aw_afk.sh
|
|
interval = 60
|
|
format-background = <<get-polybar-bg(module="aw-afk")>>
|
|
format-foreground = ${colors.foreground}
|
|
#+end_src
|
|
*** date
|
|
Current date
|
|
#+begin_src conf-windows :noweb yes
|
|
[module/date]
|
|
type = internal/date
|
|
interval = 5
|
|
|
|
date =
|
|
date-alt = "%Y-%m-%d"
|
|
|
|
time = %H:%M
|
|
time-alt = %H:%M:%S
|
|
|
|
format-background = <<get-polybar-bg(module="date")>>
|
|
format-foreground = ${colors.foreground}
|
|
label = "%date% %time%"
|
|
#+end_src
|
|
|
|
*** pomm
|
|
Pomodoro module.
|
|
#+begin_src bash :tangle ./bin/polybar/pomm.sh
|
|
if ps -e | grep emacs >> /dev/null; then
|
|
emacsclient --eval "(if (boundp 'pomm-current-mode-line-string) pomm-current-mode-line-string \"\") " | xargs echo -e
|
|
fi
|
|
#+end_src
|
|
|
|
#+begin_src conf-windows
|
|
[module/pomm]
|
|
type = custom/script
|
|
exec = /home/pavel/bin/polybar/pomm.sh
|
|
interval = 1
|
|
format-underline = ${colors.light-green}
|
|
format-foreground = ${colors.foreground}
|
|
#+end_src
|
|
*** SEP
|
|
A simple separator
|
|
#+begin_src conf-windows
|
|
[module/SEP]
|
|
type = custom/text
|
|
content = "|"
|
|
content-foreground = ${colors.magenta}
|
|
content-padding = 0
|
|
content-margin = 0
|
|
interval = 100000
|
|
#+end_src
|
|
*** TSEP
|
|
A separator, which appears only if monitor is set to have a tray in the launch script
|
|
#+begin_src bash :tangle ./bin/polybar/tray-sep.sh
|
|
if [ ! -z "$TRAY" ] && [ "$TRAY" != "none" ]; then
|
|
echo "| "
|
|
fi
|
|
#+end_src
|
|
|
|
#+begin_src conf-windows
|
|
[module/TSEP]
|
|
type = custom/script
|
|
exec = /home/pavel/bin/polybar/tray-sep.sh
|
|
format-foreground = ${colors.magenta}
|
|
interval = 100000
|
|
#+end_src
|
|
|
|
*** i3
|
|
Show i3wm workspaces
|
|
|
|
#+begin_src conf-windows
|
|
[module/i3]
|
|
type = internal/i3
|
|
format = <label-state> <label-mode>
|
|
index-sort = true
|
|
wrapping-scroll = false
|
|
|
|
; Only show workspaces on the same output as the bar
|
|
pin-workspaces = true
|
|
|
|
label-mode-padding = 1
|
|
label-mode-foreground = ${colors.white}
|
|
label-mode-background = ${colors.blue}
|
|
|
|
; focused = Active workspace on focused monitor
|
|
label-focused = %name%
|
|
label-focused-background = ${colors.blue}
|
|
label-focused-underline= ${colors.blue}
|
|
label-focused-padding = 1
|
|
|
|
; unfocused = Inactive workspace on any monitor
|
|
label-unfocused = %name%
|
|
label-unfocused-padding = 1
|
|
label-unfocused-foreground = ${colors.white}
|
|
|
|
; visible = Active workspace on unfocused monitor
|
|
label-visible = %name%
|
|
; label-visible-background = ${self.label-focused-background}
|
|
label-visible-underline = ${self.label-focused-underline}
|
|
label-visible-padding = ${self.label-focused-padding}
|
|
|
|
; urgent = Workspace with urgency hint set
|
|
label-urgent = %name%
|
|
label-urgent-background = ${colors.red}
|
|
label-urgent-foreground = ${colors.black}
|
|
label-urgent-padding = 1
|
|
#+end_src
|
|
|
|
* Rofi
|
|
| Category | Guix dependency |
|
|
|--------------+-----------------|
|
|
| desktop-rofi | rofi |
|
|
|
|
[[https://github.com/davatorium/rofi][rofi]] is another dynamic menu generator. It can act as dmenu replacement but offers a superset of dmenu's features.
|
|
|
|
** Theme
|
|
A theme based on the current Emacs theme. Inspired by [[https://github.com/dracula/rofi][dracula theme]].
|
|
|
|
#+name: get-rofi-colors
|
|
#+begin_src emacs-lisp :var table=colors
|
|
(apply
|
|
#'concat
|
|
(mapcar
|
|
(lambda (elem)
|
|
(concat (nth 0 elem) ": " (my/color-value (nth 0 elem)) ";\n"))
|
|
table))
|
|
#+end_src
|
|
|
|
#+begin_src css :tangle ./.config/rofi/config.rasi :noweb yes
|
|
/* Generated from [[file:../../Desktop.org::*Theme][Theme:1]] */
|
|
,* {
|
|
<<get-rofi-colors()>>
|
|
|
|
foreground: <<get-color(name="fg")>>;
|
|
background: <<get-color(name="bg")>>;
|
|
background-color: <<get-color(name="bg")>>;
|
|
separatorcolor: @blue;
|
|
border-color: <<get-color(name="border")>>;
|
|
selected-normal-background: <<get-color(name="blue")>>;
|
|
selected-normal-foreground: <<get-fg-for-color(name="blue")>>;
|
|
selected-active-background: <<get-color(name="light-blue")>>;
|
|
selected-active-foreground: <<get-fg-for-color(name="light-blue")>>;
|
|
selected-urgent-background: <<get-color(name="red")>>;
|
|
selected-urgent-foreground: <<get-fg-for-color(name="red")>>;
|
|
normal-foreground: @foreground;
|
|
normal-background: @background;
|
|
active-foreground: @blue;
|
|
active-background: @background;
|
|
urgent-foreground: @red;
|
|
urgent-background: @background;
|
|
alternate-normal-background: <<get-color(name="bg-alt")>>;
|
|
alternate-normal-foreground: @foreground;
|
|
alternate-active-background: <<get-fg-for-color(name="light-blue")>>;
|
|
alternate-active-foreground: <<get-color(name="light-blue")>>;
|
|
alternate-urgent-background: <<get-fg-for-color(name="red")>>;
|
|
alternate-urgent-foreground: <<get-color(name="red")>>;
|
|
spacing: 2;
|
|
}
|
|
window {
|
|
background-color: @background;
|
|
border: 1;
|
|
padding: 5;
|
|
}
|
|
mainbox {
|
|
border: 0;
|
|
padding: 0;
|
|
}
|
|
message {
|
|
border: 1px dash 0px 0px ;
|
|
border-color: @separatorcolor;
|
|
padding: 1px ;
|
|
}
|
|
textbox {
|
|
text-color: @foreground;
|
|
}
|
|
listview {
|
|
fixed-height: 0;
|
|
border: 2px dash 0px 0px ;
|
|
border-color: @separatorcolor;
|
|
spacing: 2px ;
|
|
scrollbar: true;
|
|
padding: 2px 0px 0px ;
|
|
}
|
|
element {
|
|
border: 0;
|
|
padding: 1px ;
|
|
}
|
|
element normal.normal {
|
|
background-color: @normal-background;
|
|
text-color: @normal-foreground;
|
|
}
|
|
element normal.urgent {
|
|
background-color: @urgent-background;
|
|
text-color: @urgent-foreground;
|
|
}
|
|
element normal.active {
|
|
background-color: @active-background;
|
|
text-color: @active-foreground;
|
|
}
|
|
element selected.normal {
|
|
background-color: @selected-normal-background;
|
|
text-color: @selected-normal-foreground;
|
|
}
|
|
element selected.urgent {
|
|
background-color: @selected-urgent-background;
|
|
text-color: @selected-urgent-foreground;
|
|
}
|
|
element selected.active {
|
|
background-color: @selected-active-background;
|
|
text-color: @selected-active-foreground;
|
|
}
|
|
element alternate.normal {
|
|
background-color: @alternate-normal-background;
|
|
text-color: @alternate-normal-foreground;
|
|
}
|
|
element alternate.urgent {
|
|
background-color: @alternate-urgent-background;
|
|
text-color: @alternate-urgent-foreground;
|
|
}
|
|
element alternate.active {
|
|
background-color: @alternate-active-background;
|
|
text-color: @alternate-active-foreground;
|
|
}
|
|
scrollbar {
|
|
width: 4px ;
|
|
border: 0;
|
|
handle-color: @normal-foreground;
|
|
handle-width: 8px ;
|
|
padding: 0;
|
|
}
|
|
sidebar {
|
|
border: 2px dash 0px 0px ;
|
|
border-color: @separatorcolor;
|
|
}
|
|
button {
|
|
spacing: 0;
|
|
text-color: @normal-foreground;
|
|
}
|
|
button selected {
|
|
background-color: @selected-normal-background;
|
|
text-color: @selected-normal-foreground;
|
|
}
|
|
inputbar {
|
|
spacing: 0px;
|
|
text-color: @normal-foreground;
|
|
padding: 1px ;
|
|
children: [ prompt,textbox-prompt-colon,entry,case-indicator ];
|
|
}
|
|
case-indicator {
|
|
spacing: 0;
|
|
text-color: @normal-foreground;
|
|
}
|
|
entry {
|
|
spacing: 0;
|
|
text-color: @normal-foreground;
|
|
}
|
|
prompt {
|
|
spacing: 0;
|
|
text-color: @normal-foreground;
|
|
}
|
|
textbox-prompt-colon {
|
|
expand: false;
|
|
str: ":";
|
|
margin: 0px 0.3000em 0.0000em 0.0000em ;
|
|
text-color: inherit;
|
|
}
|
|
#+end_src
|
|
|
|
** Scripts
|
|
*** Man pages
|
|
Inspired by [[https://www.youtube.com/watch?v=8E8sUNHdzG8][this Luke Smith's video]].
|
|
|
|
A script to open a man page with zathura. There is no particular reason why one should look through man pages in pdf viewer rather than in console, but why not.
|
|
#+begin_src bash :tangle ./bin/scripts/rofi-man
|
|
SELECTED=$(man -k . | rofi -dmenu -l 20 | awk '{print $1}')
|
|
if [[ ! -z $SELECTED ]]; then
|
|
man -Tpdf $SELECTED | zathura -
|
|
fi
|
|
#+end_src
|
|
*** Emojis
|
|
| Category | Guix dependency |
|
|
|--------------+-----------------|
|
|
| desktop-rofi | python-rofimoji |
|
|
*** pass
|
|
| Category | Guix dependency |
|
|
|--------------+-----------------|
|
|
| desktop-rofi | rofi-pass |
|
|
| desktop-rofi | xset |
|
|
|
|
A nice [[https://github.com/carnager/rofi-pass][pass frontend for Rofi]], which is even packaged for Guix.
|
|
|
|
#+begin_src bash :tangle ~/.config/rofi-pass/config
|
|
USERNAME_field='username'
|
|
EDITOR=vim
|
|
default_autotype='username :tab pass'
|
|
clip=both
|
|
#+end_src
|
|
* Flameshot
|
|
| Guix dependency |
|
|
|-----------------|
|
|
| flameshot |
|
|
|
|
[[https://github.com/flameshot-org/flameshot][flameshot]] is my program of choice to make screenshots.
|
|
|
|
As it overwrites its own config all the time, I do not keep the file in VC.
|
|
|
|
#+begin_src conf-unix :tangle ./.config/flameshot/flameshot.ini :comments no :noweb yes
|
|
[General]
|
|
disabledTrayIcon=false
|
|
drawColor=#ff0000
|
|
drawThickness=3
|
|
savePath=/home/pavel/Pictures
|
|
savePathFixed=false
|
|
showStartupLaunchMessage=false
|
|
uiColor=<<get-color(name="blue")>>
|
|
|
|
[Shortcuts]
|
|
TYPE_ARROW=A
|
|
TYPE_CIRCLE=C
|
|
TYPE_CIRCLECOUNT=
|
|
TYPE_COMMIT_CURRENT_TOOL=Ctrl+Return
|
|
TYPE_COPY=Ctrl+C
|
|
TYPE_DRAWER=D
|
|
TYPE_EXIT=Ctrl+Q
|
|
TYPE_IMAGEUPLOADER=Return
|
|
TYPE_MARKER=M
|
|
TYPE_MOVESELECTION=Ctrl+M
|
|
TYPE_MOVE_DOWN=Down
|
|
TYPE_MOVE_LEFT=Left
|
|
TYPE_MOVE_RIGHT=Right
|
|
TYPE_MOVE_UP=Up
|
|
TYPE_OPEN_APP=Ctrl+O
|
|
TYPE_PENCIL=P
|
|
TYPE_PIN=
|
|
TYPE_PIXELATE=B
|
|
TYPE_RECTANGLE=R
|
|
TYPE_REDO=Ctrl+Shift+Z
|
|
TYPE_RESIZE_DOWN=Shift+Down
|
|
TYPE_RESIZE_LEFT=Shift+Left
|
|
TYPE_RESIZE_RIGHT=Shift+Right
|
|
TYPE_RESIZE_UP=Shift+Up
|
|
TYPE_SAVE=Ctrl+S
|
|
TYPE_SELECTION=S
|
|
TYPE_SELECTIONINDICATOR=
|
|
TYPE_SELECT_ALL=Ctrl+A
|
|
TYPE_TEXT=T
|
|
TYPE_TOGGLE_PANEL=Space
|
|
TYPE_UNDO=Ctrl+Z
|
|
#+end_src
|
|
* dunst
|
|
| Guix dependency |
|
|
|-----------------|
|
|
| dunst |
|
|
| libnotify |
|
|
|
|
[[https://github.com/dunst-project/dunst][dunst]] is a lightweight notification daemon.
|
|
|
|
My customizations of the original config consist mostly of changing colors. Check out the default config or =man dunst= for the description of settings.
|
|
|
|
References:
|
|
- [[https://dunst-project.org/documentation/][dunst documentation]]
|
|
|
|
#+begin_src conf-space :tangle ./.config/dunst/dunstrc :noweb yes
|
|
[global]
|
|
monitor = 0
|
|
follow = mouse
|
|
geometry = "300x5-30+20"
|
|
indicate_hidden = yes
|
|
shrink = no
|
|
transparency = 15
|
|
notification_height = 0
|
|
separator_height = 2
|
|
padding = 8
|
|
horizontal_padding = 8
|
|
frame_width = 1
|
|
frame_color = <<get-color(name="border", quote=1)>>
|
|
separator_color = frame
|
|
sort = yes
|
|
idle_threshold = 120
|
|
|
|
### Text ###
|
|
font = DejaVu Sans 9
|
|
|
|
line_height = 0
|
|
markup = full
|
|
|
|
# The format of the message. Possible variables are:
|
|
# %a appname
|
|
# %s summary
|
|
# %b body
|
|
# %i iconname (including its path)
|
|
# %I iconname (without its path)
|
|
# %p progress value if set ([ 0%] to [100%]) or nothing
|
|
# %n progress value if set without any extra characters
|
|
# %% Literal %
|
|
# Markup is allowed
|
|
format = "<b>%s</b>\n%b"
|
|
alignment = left
|
|
show_age_threshold = 60
|
|
word_wrap = yes
|
|
ellipsize = middle
|
|
ignore_newline = no
|
|
stack_duplicates = true
|
|
hide_duplicate_count = false
|
|
show_indicators = yes
|
|
|
|
### Icons ###
|
|
icon_position = left
|
|
max_icon_size = 32
|
|
icon_path = /usr/share/icons/Mint-Y/status/32/;/usr/share/icons/Mint-Y/devices/32
|
|
|
|
### History ###
|
|
sticky_history = yes
|
|
history_length = 20
|
|
|
|
### Misc/Advanced ###
|
|
dmenu = /usr/bin/dmenu -p dunst:
|
|
browser = /home/pavel/.guix-extra-profiles/browsers/browsers/bin/firefox
|
|
always_run_script = true
|
|
title = Dunst
|
|
class = Dunst
|
|
startup_notification = false
|
|
verbosity = mesg
|
|
corner_radius = 0
|
|
|
|
### Legacy
|
|
force_xinerama = false
|
|
|
|
### mouse
|
|
mouse_left_click = close_current
|
|
mouse_middle_click = do_action
|
|
mouse_right_click = close_all
|
|
|
|
[experimental]
|
|
per_monitor_dpi = false
|
|
|
|
[shortcuts]
|
|
close = ctrl+space
|
|
close_all = ctrl+shift+space
|
|
history = ctrl+grave
|
|
context = ctrl+shift+period
|
|
|
|
[urgency_low]
|
|
background = <<get-color(name="bg-other", quote=1)>>
|
|
frame_color = <<get-color(name="border", quote=1)>>
|
|
foreground = <<get-color(name="fg", quote=1)>>
|
|
timeout = 10
|
|
|
|
[urgency_normal]
|
|
background = <<get-color(name="bg", quote=1)>>
|
|
frame_color = <<get-color(name="border", quote=1)>>
|
|
foreground = <<get-color(name="fg", quote=1)>>
|
|
timeout = 10
|
|
|
|
[urgency_critical]
|
|
background = <<get-color(name="red", quote=1)>>
|
|
foreground = <<get-fg-for-color(name="red", quote=1)>>
|
|
frame_color = <<get-color(name="red", quote=1)>>
|
|
timeout = 0
|
|
#+end_src
|
|
* Firefox
|
|
[[https://www.mozilla.org/en-US/firefox/new/][Firefox]] is my web browser of choice.
|
|
|
|
** Tridactyl
|
|
[[https://github.com/tridactyl/tridactyl][Tridactyl]] is a Firefox add-on that provides vim-like interface.
|
|
|
|
Run =:nativeinstall= at the first start.
|
|
|
|
*** Config
|
|
The native messenger allows to configure the addon with a config file.
|
|
|
|
#+begin_src conf :tangle ~/.tridactylrc
|
|
sanitize tridactyllocal tridactylsync
|
|
|
|
bind gn tabnew
|
|
bind gN tabclose
|
|
|
|
bind O fillcmdline tabopen
|
|
|
|
bind n findnext 1
|
|
bind N findnext -1
|
|
bind F hint -t
|
|
|
|
unbind <C-f>
|
|
|
|
set smoothscroll false
|
|
set findcase sensitive
|
|
colorscheme emacs
|
|
|
|
bind j scrollline 3
|
|
bind k scrollline -3
|
|
bind --mode=normal <C-i> mode ignore
|
|
bind --mode=ignore <C-i> mode normal
|
|
|
|
guiset_quiet gui full
|
|
guiset_quiet statuspanel left
|
|
guiset_quiet navbar none
|
|
guiset_quiet tabs always
|
|
|
|
set searchurls.g https://google.com/search?q=
|
|
|
|
set newtab about:blank
|
|
|
|
command fixamo_quiet jsb tri.excmds.setpref("privacy.resistFingerprinting.block_mozAddonManager", "true").then(tri.excmds.setpref("extensions.webextensions.restrictedDomains", '""'))
|
|
command fixamo js tri.excmds.setpref("privacy.resistFingerprinting.block_mozAddonManager", "true").then(tri.excmds.setpref("extensions.webextensions.restrictedDomains", '""').then(tri.excmds.fillcmdline_tmp(3000, "Permissions added to user.js. Please restart Firefox to make them take affect.")))
|
|
fixamo_quiet
|
|
#+end_src
|
|
|
|
*** Theme
|
|
Then, the package has its separate theme.
|
|
|
|
I based it on =base16-dracula= by [[http://chriskempson.com][Chris Kempson]], but replaced the colors with my Emacs theme.
|
|
|
|
#+begin_src css :tangle ~/themes/emacs.css :noweb yes
|
|
:root {
|
|
--tridactyl-fg: <<get-color(name="fg")>>;
|
|
--tridactyl-bg: <<get-color(name="bg")>>;
|
|
--tridactyl-url-fg: <<get-color(name="red")>>;
|
|
--tridactyl-url-bg: <<get-color(name="bg")>>;
|
|
--tridactyl-highlight-box-bg: <<get-color(name="blue")>>;
|
|
--tridactyl-highlight-box-fg: <<get-fg-for-color(name="blue")>>;
|
|
|
|
/* Command line */
|
|
--tridactyl-cmdl-bg: <<get-color(name="bg-alt")>>
|
|
--tridactyl-cmdl-fg: <<get-color(name="fg")>>
|
|
|
|
/* Hint character tags */
|
|
--tridactyl-hintspan-fg: <<get-fg-for-color(name="blue")>> !important;
|
|
--tridactyl-hintspan-bg: <<get-color(name="blue")>> !important;
|
|
|
|
/* Element Highlights */
|
|
--tridactyl-hint-active-fg: none;
|
|
--tridactyl-hint-active-bg: none;
|
|
--tridactyl-hint-active-outline: none;
|
|
/* --tridactyl-hint-activy-outline: var(--base08); */
|
|
--tridactyl-hint-bg: none;
|
|
--tridactyl-hint-outline: none;
|
|
/* --tridactyl-hint-outline: var(--base08); */
|
|
}
|
|
|
|
/* a { */
|
|
/* color: var(--base04); */
|
|
/* } */
|
|
|
|
#command-line-holder {
|
|
order: 1;
|
|
border: 2px solid <<get-color(name="blue")>>;
|
|
background: <<get-color(name="bg")>>;
|
|
}
|
|
|
|
#tridactyl-input {
|
|
padding: 1rem;
|
|
color: var(--tridactyl-fg);
|
|
width: 90%;
|
|
font-size: 1.2rem;
|
|
line-height: 1.5;
|
|
background: var(--tridactyl-bg);
|
|
padding-left: unset;
|
|
padding: 1rem;
|
|
}
|
|
|
|
#completions table {
|
|
font-size: 0.8rem;
|
|
font-weight: 200;
|
|
border-spacing: 0;
|
|
table-layout: fixed;
|
|
padding: 1rem;
|
|
padding-top: 1rem;
|
|
padding-bottom: 1rem;
|
|
}
|
|
|
|
#completions > div {
|
|
max-height: calc(20 * var(--option-height));
|
|
min-height: calc(10 * var(--option-height));
|
|
}
|
|
|
|
/* COMPLETIONS */
|
|
|
|
#completions {
|
|
--option-height: 1.4em;
|
|
color: var(--tridactyl-fg);
|
|
background: var(--tridactyl-bg);
|
|
display: inline-block;
|
|
font-size: unset;
|
|
font-weight: 200;
|
|
overflow: hidden;
|
|
width: 100%;
|
|
border-top: unset;
|
|
order: 2;
|
|
}
|
|
|
|
/* Olie doesn't know how CSS inheritance works */
|
|
#completions .HistoryCompletionSource {
|
|
max-height: unset;
|
|
min-height: unset;
|
|
}
|
|
|
|
#completions .HistoryCompletionSource table {
|
|
width: 100%;
|
|
font-size: 11pt;
|
|
border-spacing: 0;
|
|
table-layout: fixed;
|
|
}
|
|
|
|
/* redundancy 2: redundancy 2: more redundancy */
|
|
#completions .BmarkCompletionSource {
|
|
max-height: unset;
|
|
min-height: unset;
|
|
}
|
|
|
|
#completions table tr td.prefix,#completions table tr td.privatewindow,#completions table tr td.container,#completions table tr td.icon {
|
|
display: none;
|
|
}
|
|
|
|
#completions .BufferCompletionSource table {
|
|
width: unset;
|
|
font-size: unset;
|
|
border-spacing: unset;
|
|
table-layout: unset;
|
|
}
|
|
|
|
#completions table tr .title {
|
|
width: 50%;
|
|
}
|
|
|
|
#completions table tr {
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
#completions .sectionHeader {
|
|
background: unset;
|
|
font-weight: 200;
|
|
border-bottom: unset;
|
|
padding: 1rem !important;
|
|
padding-left: unset;
|
|
padding-bottom: 0.2rem;
|
|
}
|
|
|
|
#cmdline_iframe {
|
|
position: fixed !important;
|
|
bottom: unset;
|
|
top: 25% !important;
|
|
left: 10% !important;
|
|
z-index: 2147483647 !important;
|
|
width: 80% !important;
|
|
box-shadow: rgba(0, 0, 0, 0.5) 0px 0px 20px !important;
|
|
}
|
|
|
|
.TridactylStatusIndicator {
|
|
position: fixed !important;
|
|
bottom: 0 !important;
|
|
background: var(--tridactyl-bg) !important;
|
|
border: unset !important;
|
|
border: 1px <<get-color(name="blue")>> solid !important;
|
|
font-size: 12pt !important;
|
|
/*font-weight: 200 !important;*/
|
|
padding: 0.8ex !important;
|
|
}
|
|
|
|
#completions .focused {
|
|
background: <<get-color(name="blue")>>;
|
|
color: <<get-fg-for-color(name="blue")>>;
|
|
}
|
|
|
|
#completions .focused .url {
|
|
background: <<get-color(name="blue")>>;
|
|
color: <<get-fg-for-color(name="blue")>>;
|
|
}
|
|
/* #Ocean-normal { */
|
|
/* border-color: green !important; */
|
|
/* } */
|
|
|
|
/* #Ocean-insert { */
|
|
/* border-color: yellow !important; */
|
|
/* } */
|
|
#+end_src
|
|
** Firefox Color
|
|
[[https://color.firefox.com/][Firefox Color]] is a system that allows for easy experimentation with Firefox themes.
|
|
|
|
It can serialize themes into URLs like =https://color.firefox.com/?theme=<theme>=, so I thought it would be a piece of cake to generate one from my Emacs theme, right? Well...
|
|
|
|
As it turns out, Firefox uses npm package called [[https://github.com/masotime/json-url][json-url]] to create =<theme>=, which this package does by the following sequence:
|
|
- msgpack v5
|
|
- lzma
|
|
- url-safe base64
|
|
I tried to reproduce the above in Emacs, but in the end gave up and used the package in a simple node script:
|
|
|
|
#+begin_src js :tangle ~/bin/firefox-theme/main.js
|
|
const JsonUrl = require('json-url');
|
|
const jsonCodec = JsonUrl('lzma');
|
|
|
|
const json = JSON.parse(process.argv[2]);
|
|
jsonCodec.compress(json).then((r) => process.stdout.write(r));
|
|
#+end_src
|
|
|
|
Which I then can use to create the URL.
|
|
|
|
#+begin_src emacs-lisp
|
|
(defun my/firefox-encode-json (string)
|
|
(with-output-to-string
|
|
(with-current-buffer standard-output
|
|
(call-process "node" nil t nil
|
|
(expand-file-name "~/bin/firefox-theme/main.js")
|
|
string))))
|
|
|
|
(defun my/color-value-rgb (color)
|
|
(let ((color (if (stringp color)
|
|
color
|
|
(my/color-value color))))
|
|
`((r . ,(* 2.55 (ct-get-rgb-r color)))
|
|
(g . ,(* 2.55 (ct-get-rgb-g color)))
|
|
(b . ,(* 2.55 (ct-get-rgb-b color))))))
|
|
|
|
(defun my/firefox-get-json ()
|
|
(let ((toolbar-color
|
|
(my/color-value-rgb
|
|
(or
|
|
(my/color-value 'bg-mode-line-active)
|
|
(my/color-value 'bg-mode-line)
|
|
(if (my/light-p)
|
|
(ct-edit-hsl-l-dec (my/color-value 'bg-alt) 10)
|
|
(ct-edit-hsl-l-inc (my/color-value 'bg-alt) 15)))))
|
|
(text-color
|
|
(my/color-value-rgb
|
|
(if (my/light-p) 'fg 'yellow))))
|
|
`((colors . ((toolbar . ,toolbar-color)
|
|
(toolbar_text . ,text-color)
|
|
(frame . ,(my/color-value-rgb 'bg))
|
|
(tab_background_text . ,(my/color-value-rgb 'fg))
|
|
(toolbar_field . ,(my/color-value-rgb 'bg))
|
|
(toolbar_field_text . ,(my/color-value-rgb 'blue))
|
|
(tab_line . ,text-color)
|
|
(popup . ,(my/color-value-rgb 'bg-alt))
|
|
(popup_text . ,(my/color-value-rgb 'fg))
|
|
(tab_loading . ,text-color))))))
|
|
|
|
(defun my/firefox-get-color-url ()
|
|
(concat
|
|
"https://color.firefox.com/?theme="
|
|
(my/firefox-encode-json
|
|
(json-encode
|
|
(my/firefox-get-json)))))
|
|
|
|
(defun my/firefox-kill-color-url ()
|
|
(interactive)
|
|
(kill-new (my/firefox-get-color-url)))
|
|
|
|
(my/firefox-get-color-url)
|
|
#+end_src
|
|
|
|
#+RESULTS:
|
|
: https://color.firefox.com/?theme=XQAAAAJFAQAAAAAAAABAqYhm849SCia3ftKEGccwS-xMDPsqcRvjh8JMhYPDf9_kNjVRdqrKsHr5AamG1FlOJ8DH_BqRXLhVF02YoR2FXVUIEYoXiV-3q19EVo-NqESyeWWEIwj-0QxR3X-JxWYJLJYc6tAeNGGDXNNrM0pNWpwesvR43yXL_fJfr9Q919y2QwP0cK7ZXO1lRou4HkpwWW4LWdO3V6ox_BN9yA
|
|
|
|
* keynav
|
|
| Guix dependency |
|
|
|-----------------|
|
|
| keynav |
|
|
|
|
| Type | Note |
|
|
|---------+--------------------------------|
|
|
| SYMLINK | ./config/keynavrc -> .keynavrc |
|
|
|
|
[[https://github.com/jordansissel/keynav][keynav]] is a program for controlling mouse with keyboard, mostly by screen bisection. This is a poor replacement for a proper keyboard-drived sofware, but...
|
|
|
|
References:
|
|
- [[https://github.com/jordansissel/keynav/blob/master/keynav.pod][keynav documentation]]
|
|
** Config
|
|
#+begin_src conf-space :tangle ./.config/keynav/keynavrc
|
|
# clear all previous keybindings
|
|
clear
|
|
|
|
# Start & stop
|
|
ctrl+semicolon start
|
|
Super_L+bracketright start
|
|
Super_R+bracketright start
|
|
Escape end
|
|
ctrl+bracketleft end
|
|
|
|
# Macros
|
|
q record ~/.keynav_macros
|
|
shift+at playback
|
|
|
|
# Bisecting
|
|
a history-back
|
|
Left cut-left
|
|
Right cut-right
|
|
Down cut-down
|
|
Up cut-up
|
|
h cut-left
|
|
j cut-down
|
|
k cut-up
|
|
l cut-right
|
|
t windowzoom # Zoom to the current window
|
|
c cursorzoom 300 300 # Limit the bisection area by 300x300
|
|
|
|
# Move the bisecting area
|
|
shift+h move-left
|
|
shift+j move-down
|
|
shift+k move-up
|
|
shift+l move-right
|
|
shift+Left move-left
|
|
shift+Right move-right
|
|
shift+Up move-up
|
|
shift+Down move-down
|
|
|
|
# Actions
|
|
space warp,click 3,end # Right click
|
|
Return warp,click 1,end # Left click
|
|
Shift+Return warp,doubleclick 1,end # Double left click
|
|
semicolon warp,end # Move the cursor and exit
|
|
w warp # Just move the cursor
|
|
e end # exit
|
|
u warp,click 4 # scroll up
|
|
d warp,click 5 # scroll down
|
|
1 click 1
|
|
2 click 2
|
|
3 click 3
|
|
4 click 4
|
|
5 click 5
|
|
#+end_src
|
|
** Using with picom
|
|
I've noticed that the program does not play nice with picom's fade effect. To fix that, add the following to you config:
|
|
#+begin_src conf-unix :tangle no
|
|
fade-exclude = [
|
|
"class_i = 'keynav'",
|
|
"class_g = 'keynav'",
|
|
]
|
|
#+end_src
|
|
* Picom
|
|
:PROPERTIES:
|
|
:header-args+: :tangle ./.config/picom.conf
|
|
:END:
|
|
|
|
| Guix dependency |
|
|
|-----------------|
|
|
| picom |
|
|
|
|
[[https://github.com/yshui/picom][picom]] is a compositor for X11. It allows effects such as transparency, blurring, etc.
|
|
|
|
Check out the sample configuration to get an idea on what's possible. I only have some basic settings in mine.
|
|
|
|
Also, there are some fancy forks of picom (e.g. [[https://github.com/ibhagwan/picom][ibhagwan/picom]] adds rounded corners).
|
|
|
|
References:
|
|
- [[https://github.com/yshui/picom/wiki][picom wiki]]
|
|
- [[https://wiki.archlinux.org/index.php/Picom][Picom on ArchWiki]]
|
|
- [[https://github.com/yshui/picom/blob/next/picom.sample.conf][Sample configuration]]
|
|
|
|
** Shadows
|
|
#+begin_src conf-unix
|
|
shadow = true;
|
|
shadow-radius = 2;
|
|
shadow-offset-x = -2;
|
|
shadow-offset-y = -2;
|
|
|
|
shadow-exclude = [
|
|
"name = 'Notification'",
|
|
"class_g = 'Conky'",
|
|
"name ?= 'cpt_frame_window'",
|
|
"class_g ?= 'Notify-osd'",
|
|
"class_g = 'Cairo-clock'",
|
|
"_GTK_FRAME_EXTENTS@:c"
|
|
];
|
|
#+end_src
|
|
** Fading
|
|
#+begin_src conf-unix
|
|
fading = true
|
|
|
|
fade-in-step = 0.03;
|
|
fade-out-step = 0.03;
|
|
fade-delta = 10
|
|
|
|
fade-exclude = [
|
|
"class_i = 'keynav'",
|
|
"class_g = 'keynav'",
|
|
"class_i = 'emacs'",
|
|
"class_g = 'emacs'",
|
|
]
|
|
#+end_src
|
|
** Opacity
|
|
I don't use stuff like transparency for inactive windows.
|
|
|
|
The first 5 lines of =opacity-rule= make i3wm's hidden windows 100% transparent, so I see the background behind the semi-transparent windows in i3wm's stacked and tabbed layout. Here is [[https://unix.stackexchange.com/questions/281131/compton-i3-tabbed-stacked-transparency-background-image][StackExchange question]] about that.
|
|
|
|
I also noticed that for some reason it doesn't play well with Emacs's built-in transparency, so the last line sets up Emacs transparency at 90%.
|
|
|
|
#+begin_src conf-unix
|
|
inactive-opacity = 1;
|
|
|
|
frame-opacity = 1.0;
|
|
inactive-opacity-override = false;
|
|
focus-exclude = [ "class_g = 'Cairo-clock'" ];
|
|
|
|
opacity-rule = [
|
|
"0:_NET_WM_STATE@[0]:32a = '_NET_WM_STATE_HIDDEN'",
|
|
"0:_NET_WM_STATE@[1]:32a = '_NET_WM_STATE_HIDDEN'",
|
|
"0:_NET_WM_STATE@[2]:32a = '_NET_WM_STATE_HIDDEN'",
|
|
"0:_NET_WM_STATE@[3]:32a = '_NET_WM_STATE_HIDDEN'",
|
|
"0:_NET_WM_STATE@[4]:32a = '_NET_WM_STATE_HIDDEN'",
|
|
"90:class_g = 'Emacs'"
|
|
];
|
|
#+end_src
|
|
** General settings
|
|
Default general settings. Editing some of these may be neeeded in case of performance issues.
|
|
|
|
#+begin_src conf-unix
|
|
backend = "xrender";
|
|
vsync = true
|
|
mark-wmwin-focused = true;
|
|
mark-ovredir-focused = true;
|
|
detect-rounded-corners = true;
|
|
detect-client-opacity = true;
|
|
refresh-rate = 0
|
|
detect-transient = true
|
|
detect-client-leader = true
|
|
use-damage = true
|
|
log-level = "warn";
|
|
|
|
wintypes:
|
|
{
|
|
tooltip = { fade = true; shadow = true; opacity = 0.75; focus = true; full-shadow = false; };
|
|
dock = { shadow = false; }
|
|
dnd = { shadow = false; }
|
|
popup_menu = { opacity = 1; }
|
|
dropdown_menu = { opacity = 1; }
|
|
};
|
|
#+end_src
|
|
* Zathura
|
|
| Category | Guix dependency |
|
|
|----------+-------------------|
|
|
| office | zathura |
|
|
| office | zathura-ps |
|
|
| office | zathura-pdf-mupdf |
|
|
| office | zathura-djvu |
|
|
|
|
[[https://pwmt.org/projects/zathura/][Zathura]] is a pdf viewer with vim-like keybindings.
|
|
|
|
#+NAME: zathura-recolor
|
|
#+begin_src emacs-lisp
|
|
(if (my/light-p) "false" "true")
|
|
#+end_src
|
|
|
|
#+begin_src conf-space :noweb yes :tangle .config/zathura/zathurarc
|
|
set abort-clear-search false
|
|
set guioptions cs
|
|
set selection-clipboard clipboard
|
|
set recolor <<zathura-recolor()>>
|
|
map <C-r> set recolor false
|
|
map <C-R> set recolor true
|
|
|
|
set recolor-lightcolor <<get-color(name="black", quote=1)>>
|
|
|
|
set completion-bg <<get-color(name="bg", quote=1)>>
|
|
set completion-fg <<get-color(name="fg", quote=1)>>
|
|
set completion-group-bg <<get-color(name="bg", quote=1)>>
|
|
set completion-group-fg <<get-color(name="fg", quote=1)>>
|
|
set completion-highlight-bg <<get-color(name="magenta", quote=1)>>
|
|
set completion-highlight-fg <<get-fg-for-color(name="magenta", quote=1)>>
|
|
|
|
set inputbar-bg <<get-color(name="light-black", quote=1)>>
|
|
set inputbar-fg <<get-color(name="white", quote=1)>>
|
|
set statusbar-bg <<get-color(name="light-black", quote=1)>>
|
|
set statusbar-fg <<get-color(name="white", quote=1)>>
|
|
|
|
set notification-error-bg <<get-color(name="red", quote=1)>>
|
|
set notification-error-fg <<get-fg-for-color(name="red", quote=1)>>
|
|
set notification-warning-bg <<get-color(name="yellow", quote=1)>>
|
|
set notification-warning-fg <<get-fg-for-color(name="yellow", quote=1)>>
|
|
#+end_src
|
|
* Various software
|
|
This section generates manifests for various desktop software that I'm using.
|
|
|
|
** Browsers
|
|
| Category | Guix dependency |
|
|
|----------+--------------------|
|
|
| browsers | ungoogled-chromium |
|
|
| browsers | firefox |
|
|
** Office & Multimedia
|
|
| Category | Guix dependency |
|
|
|----------+-----------------|
|
|
| office | libreoffice |
|
|
| office | gimp |
|
|
| office | krita |
|
|
| office | ffmpeg |
|
|
| office | kdenlive |
|
|
| office | inkscape |
|
|
| office | okular |
|
|
| office | obs |
|
|
** LaTeX
|
|
| Category | Guix dependency |
|
|
|----------+-------------------------------|
|
|
| latex | texlive |
|
|
| latex | texlab-bin |
|
|
| latex | biber |
|
|
| latex | python-pygments |
|
|
| latex | font-microsoft-web-core-fonts |
|
|
** Dev
|
|
| Category | Guix dependency | Disabled |
|
|
|----------+-------------------+----------|
|
|
| dev | micromamba-bin | |
|
|
| dev | pandoc | |
|
|
| dev | docker-compose | |
|
|
| dev | postgresql | |
|
|
| dev | virt-manager | |
|
|
| dev | dnsmasq | |
|
|
| dev | git-filter-repo | |
|
|
| dev | node | |
|
|
| dev | openjdk:jdk | |
|
|
| dev | go | |
|
|
| dev | gopls | |
|
|
| dev | pkg-config | |
|
|
| dev | gcc-toolchain | |
|
|
| dev | lua | |
|
|
| dev | libfaketime | |
|
|
| dev | hugo-extended | |
|
|
| dev | make | |
|
|
| dev | sbcl | t |
|
|
| dev | git-lfs | |
|
|
| dev | mysql | t |
|
|
| dev | gource | |
|
|
| dev | php | |
|
|
| dev | python | |
|
|
| dev | python-virtualenv | |
|
|
| dev | leiningen | |
|
|
| dev | socat | |
|
|
| dev | wireshark | |
|
|
| dev | python-chess | |
|
|
| dev | python-cairosvg | |
|
|
** Manifests
|
|
#+NAME: packages
|
|
#+begin_src emacs-lisp :tangle no :var category=""
|
|
(my/format-guix-dependencies category)
|
|
#+end_src
|
|
|
|
Dev
|
|
#+begin_src scheme :tangle .config/guix/manifests/dev.scm :noweb yes
|
|
(specifications->manifest
|
|
'(
|
|
<<packages("dev")>>))
|
|
#+end_src
|
|
|
|
Browsers
|
|
#+begin_src scheme :tangle .config/guix/manifests/browsers.scm :noweb yes
|
|
(specifications->manifest
|
|
'(
|
|
<<packages("browsers")>>))
|
|
#+end_src
|
|
|
|
Music
|
|
#+begin_src scheme :tangle .config/guix/manifests/music.scm :noweb yes
|
|
(specifications->manifest
|
|
'(
|
|
<<packages("music")>>))
|
|
#+end_src
|
|
|
|
Office
|
|
#+begin_src scheme :tangle .config/guix/manifests/office.scm :noweb yes
|
|
(specifications->manifest
|
|
'(
|
|
<<packages("office")>>))
|
|
#+end_src
|
|
|
|
LaTeX
|
|
#+begin_src scheme :tangle .config/guix/manifests/latex.scm :noweb yes
|
|
(specifications->manifest
|
|
'(
|
|
<<packages("latex")>>))
|
|
#+end_src
|
|
|
|
Desktop Misc
|
|
#+begin_src scheme :tangle .config/guix/manifests/desktop-misc.scm :noweb yes
|
|
(specifications->manifest
|
|
'(
|
|
<<packages("desktop-misc")>>))
|
|
#+end_src
|
|
|
|
Desktop polybar
|
|
#+begin_src scheme :tangle .config/guix/manifests/desktop-polybar.scm :noweb yes
|
|
(specifications->manifest
|
|
'(
|
|
<<packages("desktop-polybar")>>))
|
|
#+end_src
|
|
|
|
Desktop rofi
|
|
#+begin_src scheme :tangle .config/guix/manifests/desktop-rofi.scm :noweb yes
|
|
(specifications->manifest
|
|
'(
|
|
<<packages("desktop-rofi")>>))
|
|
#+end_src
|
|
** Flatpak
|
|
A lot of proprietary desktop applications can be installed most easily with flatpak & flathub.
|
|
|
|
| Guix dependency |
|
|
|--------------------|
|
|
| flatpak |
|
|
| xdg-desktop-portal |
|
|
|
|
After installation, add the following repositories:
|
|
#+begin_example
|
|
flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
|
|
flatpak remote-add --user --if-not-exists flathub-beta https://flathub.org/beta-repo/flathub-beta.flatpakrepo
|
|
#+end_example
|
|
|
|
Installation syntax is as follows:
|
|
#+begin_example
|
|
flatpak install --user <remote> <package>
|
|
#+end_example
|
|
|
|
Packages to install:
|
|
#+NAME: flatpak-deps
|
|
| Flatpak dependency | Channel |
|
|
|------------------------------+---------|
|
|
| com.github.wwmm.pulseeffects | flathub |
|
|
| com.discordapp.Discord | flathub |
|
|
| com.jetbrains.DataGrip | flathub |
|
|
| chat.rocket.RocketChat | flathub |
|
|
|
|
#+begin_src emacs-lisp :var table=flatpak-deps :wrap example
|
|
(mapconcat
|
|
(lambda (c) (concat "flatpak install -y --user " (nth 1 c) " " (nth 0 c)))
|
|
table
|
|
"\n")
|
|
#+end_src
|
|
|
|
#+RESULTS:
|
|
#+begin_example
|
|
flatpak install -y --user flathub com.github.wwmm.pulseeffects
|
|
flatpak install -y --user flathub com.discordapp.Discord
|
|
flatpak install -y --user flathub us.zoom.Zoom
|
|
flatpak install -y --user flathub com.slack.Slack
|
|
#+end_example
|
|
** Nix
|
|
| Type | Description |
|
|
|------+--------------------|
|
|
| TODO | Make nix manifest? |
|
|
|
|
I probably should've used nix, as almost every program I packaged so far exists in the Nix repo.
|
|
|
|
But it's easy enough to use Nix on Guix.
|
|
#+begin_src conf :tangle ~/.nix-channels
|
|
https://nixos.org/channels/nixpkgs-unstable nixpkgs
|
|
#+end_src
|
|
|
|
Don't forget to run the following after the first installation:
|
|
#+begin_src sh
|
|
nix-channel --update
|
|
#+end_src
|
|
|
|
Installing packages:
|
|
#+begin_src
|
|
nix-env -i slack
|
|
#+end_src
|
|
* Services
|
|
:PROPERTIES:
|
|
:header-args+: :tangle ~/.config/shepherd/init.scm
|
|
:END:
|
|
[[https://www.gnu.org/software/shepherd/manual/html_node/index.html][GNU Shepherd]] is a service management system for GNU Guix.
|
|
|
|
I previously used supervisor, but shepherd also seems pretty capable.
|
|
|
|
| Guix dependency |
|
|
|-----------------|
|
|
| shepherd |
|
|
|
|
** Music
|
|
| Category | Guix dependency |
|
|
|----------+-----------------|
|
|
| music | mpd |
|
|
| music | ncmpcpp |
|
|
| music | picard |
|
|
| music | mpd-mpc |
|
|
| music | shntool |
|
|
| music | cuetools |
|
|
| music | flac |
|
|
|
|
Music player daemon
|
|
#+begin_src scheme
|
|
(define mpd
|
|
(make <service>
|
|
#:provides '(mpd)
|
|
#:respawn? #t
|
|
#:start (make-forkexec-constructor '("mpd" "--no-daemon"))
|
|
#:stop (make-kill-destructor)))
|
|
#+end_src
|
|
|
|
MPD watcher
|
|
#+begin_src scheme
|
|
(define sqrt-data-agent-mpd
|
|
(make <service>
|
|
#:provides '(sqrt-data-agent-mpd)
|
|
#:respawn? #t
|
|
#:start (make-forkexec-constructor '("sqrt_data_agent_mpd"))
|
|
#:stop (make-kill-destructor)
|
|
#:requires '(mpd)))
|
|
#+end_src
|
|
** GNU Mcron
|
|
[[https://www.gnu.org/software/mcron/][GNU Mcron]] is a replacement for cron, written in Scheme.
|
|
|
|
#+begin_src scheme
|
|
(define mcron
|
|
(make <service>
|
|
#:provides '(mcron)
|
|
#:respawn? #t
|
|
#:start (make-forkexec-constructor '("mcron"))
|
|
#:stop (make-kill-destructor)))
|
|
#+end_src
|
|
** ActivityWatch
|
|
[[https://activitywatch.net/][ActivityWatch]] is a FOSS time tracker. It tracks screen and application usage and has integrations with browsers, Emacs, etc.
|
|
|
|
| Guix dependency |
|
|
|-------------------|
|
|
| activitywatch-bin |
|
|
|
|
aw-server
|
|
#+begin_src scheme
|
|
(define aw-server
|
|
(make <service>
|
|
#:provides '(aw-server)
|
|
#:respawn? #t
|
|
#:start (make-forkexec-constructor '("aw-server"))
|
|
#:stop (make-kill-destructor)))
|
|
#+end_src
|
|
|
|
=aw-watcher-afk= has some problems with statup, so there is a wrapper script
|
|
|
|
#+begin_src sh :tangle ~/bin/scripts/aw-watcher-afk-wrapper
|
|
sleep 5
|
|
aw-watcher-afk
|
|
#+end_src
|
|
|
|
aw-watcher-afk
|
|
#+begin_src scheme
|
|
(define aw-watcher-afk
|
|
(make <service>
|
|
#:provides '(aw-watcher-afk)
|
|
#:requires '(aw-server)
|
|
#:respawn? #t
|
|
#:start (make-forkexec-constructor '("/home/pavel/bin/scripts/aw-watcher-afk-wrapper"))
|
|
#:stop (make-kill-destructor)))
|
|
#+end_src
|
|
|
|
aw-watcher-window
|
|
#+begin_src scheme
|
|
(define aw-watcher-window
|
|
(make <service>
|
|
#:provides '(aw-watcher-window)
|
|
#:requires '(aw-server)
|
|
#:respawn? #t
|
|
#:start (make-forkexec-constructor '("aw-watcher-window"))
|
|
#:stop (make-kill-destructor)))
|
|
#+end_src
|
|
** PulseEffects
|
|
#+begin_src scheme
|
|
(define pulseeffects
|
|
(make <service>
|
|
#:provides '(pulseeffects)
|
|
#:respawn? #t
|
|
#:start (make-forkexec-constructor '("flatpak" "run" "com.github.wwmm.pulseeffects" "--gapplication-service"))
|
|
#:stop (make-kill-destructor)))
|
|
#+end_src
|
|
** xsettingsd
|
|
#+begin_src scheme
|
|
(define xsettingsd
|
|
(make <service>
|
|
#:provides '(xsettingsd)
|
|
#:respawn? #t
|
|
#:start (make-forkexec-constructor '("xsettingsd"))
|
|
#:stop (make-kill-destructor)))
|
|
#+end_src
|
|
** nm-applet
|
|
#+begin_src scheme
|
|
(define nm-applet
|
|
(make <service>
|
|
#:provides '(nm-applet)
|
|
#:respawn? #t
|
|
#:start (make-forkexec-constructor '("nm-applet"))
|
|
#:stop (make-kill-destructor)))
|
|
#+end_src
|
|
** Discord rich presence
|
|
|
|
References:
|
|
- [[https://github.com/flathub/com.discordapp.Discord/wiki/Rich-Precense-(discord-rpc)][Rich Precense (discord rpc)]]
|
|
|
|
#+begin_src scheme
|
|
(define discord-rich-presence
|
|
(make <service>
|
|
#:provides '(discord-rich-presence)
|
|
#:one-shot? #t
|
|
#:start (make-system-constructor "ln -sf {app/com.discordapp.Discord,$XDG_RUNTIME_DIR}/discord-ipc-0")))
|
|
#+end_src
|
|
** Polkit Authentication agent
|
|
Launch an authentication agent. Necessary for stuff like =pkexec=. I suspect I'm not doing that the intended way, but it seems to work.
|
|
|
|
#+begin_src scheme
|
|
(define polkit-gnome
|
|
(make <service>
|
|
#:provides '(polkit-gnome)
|
|
#:respawn? #t
|
|
#:start (make-forkexec-constructor '("/home/pavel/.guix-extra-profiles/desktop-misc/desktop-misc/libexec/polkit-gnome-authentication-agent-1"))
|
|
#:stop (make-kill-destructor)))
|
|
#+end_src
|
|
** Xmodmap
|
|
#+begin_src scheme
|
|
(define xmodmap
|
|
(make <service>
|
|
#:provides '(xmodmap)
|
|
#:one-shot? #t
|
|
#:start (make-system-constructor "xmodmap /home/pavel/.Xmodmap")))
|
|
#+end_src
|
|
** VPN
|
|
Run my [[file:Guix.org::*OpenVPN][OpenVPN setup]]. Not lauching this automatially, as it requires an active connection.
|
|
|
|
#+begin_src scheme
|
|
(define vpn
|
|
(make <service>
|
|
#:provides '(vpn)
|
|
#:respawn? #t
|
|
#:start (make-forkexec-constructor '("/home/pavel/bin/scripts/vpn-start"))
|
|
#:stop (make-kill-destructor)))
|
|
#+end_src
|
|
** Davmail
|
|
#+begin_src scheme
|
|
(define davmail
|
|
(make <service>
|
|
#:provides '(davmail)
|
|
#:respawn? #t
|
|
#:start (make-forkexec-constructor '("/home/pavel/bin/davmail"))
|
|
#:stop (make-kill-destructor)))
|
|
#+end_src
|
|
** vnstatd
|
|
#+begin_src scheme
|
|
(define vnstatd
|
|
(make <service>
|
|
#:provides '(vnstatd)
|
|
#:respawn? #t
|
|
#:start (make-forkexec-constructor '("vnstatd" "-n"))
|
|
#:stop (make-kill-destructor)))
|
|
#+end_src
|
|
** opensnitch
|
|
[[https://github.com/evilsocket/opensnitch][opensnitch]] is a linux firewall.
|
|
|
|
Install it via nix:
|
|
#+begin_src bash :tangle no
|
|
nix-env -I opensnitchd opensnitch-ui
|
|
#+end_src
|
|
|
|
=sudoers= has to be modified this to work.
|
|
#+begin_src scheme
|
|
(define opensnitchd
|
|
(make <service>
|
|
#:provides '(opensnitchd)
|
|
#:respawn? #t
|
|
#:start (make-forkexec-constructor '("sudo" "opensnitchd"))
|
|
#:stop (make-kill-destructor)))
|
|
|
|
(define opensnitch-ui
|
|
(make <service>
|
|
#:provides '(opensnitch-ui)
|
|
#:respawn? #t
|
|
#:start (make-forkexec-constructor '("sudo" "opensnitch-ui"))
|
|
#:stop (make-kill-destructor)))
|
|
#+end_src
|
|
** ollama
|
|
#+begin_src scheme
|
|
(define ollama
|
|
(make <service>
|
|
#:provides '(ollama)
|
|
#:respawn? #t
|
|
#:start (make-forkexec-constructor '("/home/pavel/bin/ollama" "serve"))
|
|
#:stop (make-kill-destructor)))
|
|
#+end_src
|
|
|
|
** Shepherd config
|
|
For some reason, running start on a one-shot service started to hang shepherd, not sure why... Turining these off for now.
|
|
|
|
Register services:
|
|
#+begin_src scheme
|
|
(register-services
|
|
mpd
|
|
sqrt-data-agent-mpd
|
|
mcron
|
|
aw-server
|
|
aw-watcher-afk
|
|
aw-watcher-window
|
|
pulseeffects
|
|
xsettingsd
|
|
;; discord-rich-presence
|
|
polkit-gnome
|
|
vpn
|
|
davmail
|
|
;; xmodmap
|
|
nm-applet
|
|
vnstatd
|
|
;; opensnitchd
|
|
;; opensnitch-ui
|
|
ollama)
|
|
#+end_src
|
|
|
|
Daemonize shepherd
|
|
#+begin_src scheme
|
|
(action 'shepherd 'daemonize)
|
|
#+end_src
|
|
|
|
Run services
|
|
#+begin_src scheme
|
|
(for-each start '(mpd
|
|
sqrt-data-agent-mpd
|
|
mcron
|
|
aw-server
|
|
aw-watcher-afk
|
|
aw-watcher-window
|
|
pulseeffects
|
|
xsettingsd
|
|
;; discord-rich-presence
|
|
;; polkit-gnome
|
|
davmail
|
|
;; ; xmodmap
|
|
;; nm-applet
|
|
vnstatd
|
|
;; opensnitchd
|
|
;; opensnitch-ui
|
|
))
|
|
#+end_src
|
|
* Guix settings
|
|
Other desktop programs I use are listed below.
|
|
|
|
| Category | Guix dependency | Description |
|
|
|--------------+------------------------+-------------------------------------------|
|
|
| desktop-misc | xprop | Tool to display properties of X windows |
|
|
| desktop-misc | arandr | GUI to xrandr |
|
|
| desktop-misc | light | Control screen brightness |
|
|
| desktop-misc | ponymix | Control PulseAudio CLI |
|
|
| desktop-misc | pavucontrol | Control PulseAudio GUI |
|
|
| desktop-misc | network-manager-applet | Applet to manage network connections |
|
|
| desktop-misc | xmodmap | Program to modify keybindings on X server |
|
|
| desktop-misc | fontconfig | |
|
|
| desktop-misc | polkit-gnome | Polkit authentication agent |
|
|
| desktop-misc | feh | Image viewer. Used to set background |
|
|
| desktop-misc | copyq | Clipboard manager |
|
|
| desktop-misc | thunar | My preferred GUI file manager |
|
|
| desktop-misc | xdg-utils | gives xdg-open and stuff |
|
|
| desktop-misc | gnome-font-viewer | view fonts |
|
|
| desktop-misc | qbittorrent | torrent client |
|
|
| desktop-misc | anydesk | Remote desktop software |
|
|
| desktop-misc | gnome-disk-utility | Manage disks |
|
|
| desktop-misc | gparted | Manage partitions |
|
|
| desktop-misc | xev | Test input |
|
|
| desktop-misc | bluez | Provides bluetoothctl |
|
|
| desktop-misc | telegram-desktop | |
|
|
| desktop-misc | font-google-noto-emoji | |
|
|
| desktop-misc | remmina | |
|
|
| desktop-misc | android-file-transfer | |
|
|
| desktop-misc | mcron | |
|
|
|
|
#+NAME: packages
|
|
#+begin_src emacs-lisp :tangle no
|
|
(my/format-guix-dependencies)
|
|
#+end_src
|
|
|
|
#+begin_src scheme :tangle .config/guix/manifests/desktop.scm :noweb yes
|
|
(specifications->manifest
|
|
'(
|
|
<<packages()>>))
|
|
#+end_src
|