Compare commits

...

2 commits

2 changed files with 521 additions and 1 deletions

View file

@ -214,7 +214,7 @@ PORTAGE_LOGDIR_CLEAN="find \"\${PORTAGE_LOGDIR}\" -type f ! -name \"summary.log*
ACCEPT_LICENSE="-* @FREE"
# USE flags
USE="${USE} -debug -telemetry -modemmanager -ext* -ppp -systemd -elogind -X -kde -gnome -gtk-doc -webengine hardened dist-kernel udev initramfs hostonly secureboot modules-sign apparmor acpi networkmanager dbus hwaccel bash-completion man pam pipewire vulkan wayland jpeg png" #(2)!
USE="${USE} -debug -telemetry -modemmanager -ext* -ppp -systemd -elogind -X -kde -gnome -gtk-doc -webengine hardened dist-kernel udev initramfs hostonly secureboot modules-sign apparmor acpi networkmanager dbus hwaccel bash-completion man pam pipewire vulkan wayland jpeg png svg" #(2)!
# Emerge flags
EMERGE_DEFAULT_OPTS="${EMERGE_DEFAULT_OPTS} --with-bdeps y --quiet-build y"

View file

@ -0,0 +1,520 @@
---
title: A minimal graphical session with River, PipeWire and OpenRC
slug: minimal-graphical-session-with-river-pipewire-and-openrc
date: 2025-08-08
draft: false
authors:
- luc
tags:
- Gentoo Linux
categories:
- Graphical session
---
Graphical sessions are often full of unnecessary features, increasing (unnecessarily) the overall complexity of a desktop system. In this blog entry we intend to go back to the bare minimum, which defines itself as a reasonable step forward in practicality compared the default `tty`. Like being able to run graphical applications in windows and being able to manage these windows efficiently. We will meet but not exceed these requirements with [River](https://isaacfreund.com/software/river/) as our tiling window manager, [PipeWire](https://pipewire.org/) as our multimedia framework, and [OpenRC](https://wiki.gentoo.org/wiki/Project:OpenRC) as our user-service manager.
<!-- more -->
## User services with OpenRC
As of `openrc-0.60` user services are supported by `openrc` (1). To enable a `openrc` user session we require a runtime dir (`XDG_RUNTIME_DIR`), which can be supplied by [turnstile](https://github.com/chimera-linux/turnstile) (2). Therefore emerge `turnstile`:
{ .annotate }
1. Presently the versions `>=0.60` of `openrc` are unstable. Enable unstable releases of `openrc` with:
``` shell title="/etc/portage/package.accept_keyword/openrc"
sys-apps/openrc ~amd64
```
2. The `turnstile` ebuild can be found in the [portage-ample](https://git.ampel.dev/ampel/portage-ample) repository.
``` shell-session
sh# emerge -a sys-auth/turnstile
```
and enable runtime dir management by `turnstile` by setting `manage_rundir` to yes in `turnstiled.conf`:
``` shell title="/etc/turnstile/turnstiled.conf"
manage_rundir = yes
```
Add the `turnstile` daemon to the default runlevel:
``` shell-session
sh# rc-update add turnstiled default
```
`pam` enables us to generate this `rundir` and start the `openrc` user session when the user is logged in by enabling the `turnstile` and `openrc` `pam` modules in `pam.d/system-login`:
``` shell title="/etc/pam.d/system-login"
session optional pam_turnstile.so
session optional pam_openrc.so
```
This user session contains the usual runlevels (`boot`, `default`, `shutdown`) that are run in their respective regimes (1). One approach is to add the window manager in the `default` runlevel, such that it will be started at login. But there are particular use cases where the graphical session is not required or wanted (only taking up precious resources), it is therefore wise to configure the graphical session in its own runlevel, to have the freedom to enable it any time you want with `openrc -U <runlevel-graphical-session>` (2).
{ .annotate }
1. Though `boot` and `shutdown` should rather be interpreted as `login` and `logout` of the user session in this particular case.
2. Or to link it to the `default` runlevel.
Besides the `<runlevel-graphical-session>`, denoted with `g`, we will also define a `<runlevel-post-graphical-session>`, denoted with `h`, for the services depending on the graphical-session. The creation of these runlevels can be simply performed with `mkdir`:
``` shell-session
sh$ mkdir ~/.config/rc/runlevels/g
sh$ mkdir ~/.config/rc/runlevels/h
```
and stack them accordingly:
``` shell-session
sh$ ln -s /home/<user>/.config/rc/runlevels/default /home/<user>/.config/rc/runlevels/g
sh$ ln -s /home/<user>/.config/rc/runlevels/g /home/<user>/.config/rc/runlevels/h
```
A set of minimal applications consisting of the terminal emulator (`foot`), application launcher (`fuzzel`(1)), notification daemon (`mako`) and display configurator (`kanshi`(2)) that will be of use in the graphical session may be emerged via:
{ .annotate }
1. Presently `fuzzel` is unstable in `GURU`. Enable unstable releases of `fuzzel` with:
``` shell title="/etc/portage/package.accept_keywords/fuzzel"
gui-apps/fuzzel ~amd64
```
2. Presently `kanshi` and its dependency `libsfcg` is unstable in `GURU`. Enable unstable releases of `kanshi` and `libscfg` with:
``` shell title="/etc/portage/package.accept_keywords/kanshi"
gui-apps/kanshi ~amd64
dev-libs/libscfg ~amd64
```
``` shell-session
sh# emerge -a gui-apps/foot gui-apps/fuzzel gui-apps/mako gui-apps/kanshi
```
These applications require `WAYLAND_DISPLAY` to be set by the window manager. Therefore allow this variable to propagate in the user session:
``` shell title="~/.config/rc/rc.conf"
rc_env_allow="WAYLAND_DISPLAY"
```
The terminal emulator (`foot`) requires the backend `footserver` for its clients. Therefore create a user service that will supervise `footserver`:
``` shell title="~/.config/rc/init.d/footserver"
#!/usr/bin/openrc-run
depend() {
need graphical-session
}
supervisor=supervise-daemon
command="/usr/bin/foot"
command_args="--server"
```
Also the notification daemon (`mako`) and the display configurator (`kanshi`) require to be supervised, thus create a user service for them as well:
``` shell title="~/.config/rc/init.d/mako"
#!/usr/bin/openrc-run
depend() {
need graphical-session
}
supervisor=supervise-daemon
command="/usr/bin/mako"
```
``` shell title="~/.config/rc/init.d/kanshi"
#!/usr/bin/openrc-run
depend() {
need graphical-session
}
supervisor=supervise-daemon
command="/usr/bin/kanshi"
```
Adding these user services to `<runlevel-post-graphical-session>` can simply be performed with `rc-update -U`:
``` shell-session
sh$ rc-update -U add footserver h
sh$ rc-update -U add mako h
sh$ rc-update -U add kanshi h
```
## Window management with River
We require a seat manager as the mediating layer between the window manager and the hardware, to be able to run the window manager in the user session. We will use `seatd` as the seat management daemon, which requires us to set the following `USE` flags:
``` shell title="/etc/portage/package.use/seatd"
sys-auth/seatd server -builtin
```
Now emerge `seatd`:
``` shell-session
sh# emerge -a seatd
```
and add the daemon to the default runlevel:
``` shell-session
sh# rc-update add seatd default
```
> We need to be part of the `video` and `seat` group to effectively communicate with seat manager:
>
> ``` shell-session
> sh# usermod --append --groups video,seat <user>
> ```
Video drivers are necessary for a graphical session to function. In Gentoo it is as simple as specifying what video card the system uses using the `USE` flags:
``` shell title="/etc/portage/package.use/video"
*/* VIDEO_CARDS: -* <amdgpu radeonsi || intel || nvidia>
```
By setting the `VIDEO_CARDS` `USE` flag the dependencies of `river` are compiled for the video card of the system, thus emerge `river` (1):
{ .annotate }
1. Presently `river` is unstable in `GURU`. Enable unstable releases of `river` with:
``` shell title="/etc/portage/package.accept_keywords/river"
gui-wm/river ~amd64
```
``` shell-session
sh# emerge -a gui-wm/river
```
> Buckle up, this is quite a hefty emerge.
`river` starts by executing an init (POSIX shell) script present in `~/.conig/river/init`, this scipt should contain or link to all the configuration of `river`. A minimal configuration of the `river/init` script is given below:
``` shell title="~/.config/river/init"
#!/bin/sh
# Set background and colours
swaybg -m fill -c 000000 -i $HOME/.local/share/backgrounds/<background> & #(1)!
riverctl border-color-focused 0xFFFFFFCC
riverctl border-color-unfocsed 0xFFFFFF66
riverctl border-color-urgent 0xFFFFFF
# Set border-width
riverctl border-width 2
# Set layout
riverctl default-layout rivertile
rivertile --view-padding 4 --outer-padding 4 &
# Source the keybinds
source ~/.config/river/binds
# Switch to post-graphical runlevel
openrc -U h
## Device input settings ##
# Keyboard layout
riverctl keyboard-layout qwerty
# Set keyboard repeat rate
riverctl set-repeat 50 300
# Set focus-follow-cursor
riverctl focus-follow-cursor normal
```
1. This line is optional, but if you want to spruce up your graphical session, then emerge:
``` shell-session
sh# emerge -a gui-apps/swaybg
```
with the `river/binds` given by:
``` shell title="~/.config/river/binds"
#!/bin/sh
# Super+Shift+Q to exit river
riverctl map normal Super+Shift Q spawn "openrc -U g && openrc -U default"
## Aplication mapping ##
# Super+Backspace to spawn waylock
riverctl map normal Super BackSpace spawn "waylock -ignore-empty-password" #(1)!
# Super+Return to spawn foot
riverctl map normal Super Return spawn footclient
# Super+Space to spawn fuzzel
riverctl map normal Super Space spawn fuzzel
# Super+D to dismiss mako notification
riverctl map normal Super D spawn "makoctl dismiss"
# Super+T to spawn datetime notification
riverctl map normal Super T spawn "$HOME/.local/bin/datetime" #(2)!
# Super+B to spawn battery state notification
riverctl map normal Super B spawn "$HOME/.local/bin/battery" #(3)!
# MonBrightnessUp or MonBrightnessDown to spawn brightness notification (4)
rivcertl map normal None XF86_MonBrightnessUp spawn "$HOME/.local/bin/brightness"
riverctl map normal None XF86_MonBrightnessDown spawn "$HOME/.local/bin/brightness"
# Print to spawn interactive screenshot
riverctl map normal None Print spawn "grim -g "(slurp -d)" - | wl-copy -t image/jpeg" #(5)!
# Shift+Print to spawn screenshot
riverctl map normal Shift Print spawn "grim - | wl-copy -t image/jpeg" #(6)!
## Focused view mapping ##
# Super+Q to close the focused view
riverctl map normal Super Q close
# Super+F to fullscreen toggle the focused view
riverctl map normal Super F toggle-fullscreen
# Super+BTN_MIDDLE to toggle float of the focused view
riverctl map-pointer normal Super BTN_MIDDLE toggle-float
# Super+BTN_LEFT to move the floated focused view
riverctl map-pointer normal Super BTN_LEFT move-view
# Super+BTN_RIGHT to resize the floated focused view
riverctl map-pointer normal Super BTN_RIGHT resize-view
# Super+{K;J} to focus next/previous view
riverctl map normal Super K focus-view next
riverctl map normal Super J focus-view previous
# Super+Shift+{K;J} to swap the focused view with the next/previous view
riverctl map normal Super+Shift K swap next
riverctl map normal Super+Shift J swap previous
# Super+{.;,} to focus the next/previous output
riverctl map normal Super Period focus-output next
riverctl map normal Super Comma focus-output previous
# Super+Shift+{.;,} to send the focused view to the next/previous output
riverctl map normal Super+Shift Period send-to-output next
riverctl map normal Super+Shift Comma send-to-output previous
## Rivertile mapping ##
# Super+{Up;Right;Down;Left} to change layout orientation
riverctl map normal Super Up send-layout-cmd rivertile "main-location top"
riverctl map normal Super Right send-layout-cmd rivertile "main-location right"
riverctl map normal Super Down send-layout-cmd rivertile "main-location down"
riverctl map normal Super Left send-layout-cmd rivertile "main-location left"
# Super+Shift+{Up;Down} to increment/decrement the main count of rivertile
riverctl map normal Super+Shift Up send-layout-cmd rivertile "main-count +1"
riverctl map normal Super+Shift Down send-layout-cmd rivertile "main-count -1"
# Super+Shift+{Right;Left} to increase/decrease the main ratio of rivertile
riverctl map normal Super+Shift Right send-layout-cmd rivertile "main-ratio +0.05"
riverctl map normal Super+Shift Left send-layout-cmd rivertile "main-ratio -0.05"
## Tag mapping ##
for i in $(seq 1 9)
do
j=$((1 << ($i - 1)))
# Super+[1-9] to focus tag [0-8]
riverctl map normal Super $i set-focused-tags $j
# Super+Shift+[1-9] to tag focused view with tag [0-8]
riverctl map normal Super+Shift $i set-view-tags $j
# Super+Control+[1-9] to toggle focus of tag [0-8]
riverctl map normal Super+Control $i toggle-focused-tags $j
# Super+Shift+Control+[1-9] to toggle tag [0-8] of focused view
riverctl map normal Super+Shift+Control $i toggle-view-tags $j
done
all_tags=$(((1 << 32) - 1))
# Super+O to focus all tags
riverctl map normal Super O set-focused-tags $all_tags
# Super+Shift+O to tag focused view with all tags
riverctl map normal Super+Shift O set-view-tags $all_tags
## Media mapping ## (7)
# AudioRaiseVolume to increase volume audio sink by 5%
riverctl map normal None XF86AudioRaiseVolume spawn "$HOME/.local/bin/audio sink volup"
# AudioLowerVolume to decrease volume audio sink by 5%
riverctl map normal None XF86AudioLowerVolume spawn "$HOME/.local/bin/audio sink voldown"
# AudioMute to toggle mute audio sink
riverctl map normal None XF86AudioMute spawn "$HOME/.local/bin/audio sink toggle"
# AudioMicRaiseVolume to increase volume audio source by 5%
riverctl map normal None XF86AudioMicRaiseVolume spawn "$HOME/.local/bin/audio source volup"
# AudioMicLowerVolume to decrease volume audio source by 5%
riverctl map normal None XF86AudioMicLowerVolume spawn "$HOME/.local/bin/audio source voldown"
# AudioMicMute to toggle mute audio source
riverctl map normal None XF86AudioMute spawn "$HOME/.local/bin/audio source toggle"
```
1. Optionally, it could be useful to lock your session, therefore emerge `waylock`(1):
{ .annotate }
1. Presently `waylock` is unstable in `GURU`. Enable unstable releases of `waylock` with:
``` shell title="/etc/portage/package.accept_keywords/fuzzel"
gui-apps/waylock ~amd64
```
``` shell-session
sh# emerge -a gui-apps/waylock
```
2. Optionally, it could be useful to see the datetime as a notification when you long for it, which is part of [ampel/notifiers](https://git.ampel.dev/ampel/notifiers).
3. Optionally, it could be useful to see the battery state as a notification, especially when you have a laptop. This functionality is part of [ampel/notifiers](https://git.ampel.dev/ampel/notifiers).
4. Optionally, it could be useful to see the brightness state of your display as a notification, which is part of [ampel/notifiers](https://git.ampel.dev/ampel/notifiers). As an additional remark, the default of configuration of `acpid` in Gentoo sucks, setting brightness capability with the same keys requires some changes in the configuration that can be found [here](https://git.lucbijl.nl/luc/gentoo-desktop/src/branch/main/etc/acpi).
5. Optional screenshot capability.
6. Optional screenshot capability.
7. Optionally, it could be useful to set and get the state of the audio sink and source, which is part of [ampel/notifiers](https://git.ampel.dev/ampel/notifiers).
To be able to run our window manager (`river`) supervised, we require the `river` user service:
``` shell title="~/.config/rc/init.d/river"
#!/usr/bin/openrc-run
depend() {
need dbus
provide graphical-session
}
supervisor=supervise-daemon
command="/usr/bin/river"
```
and add it to `<runlevel-graphical-session>`:
```
sh$ rc-update -U add river g
```
## Interfacing audio and video with PipeWire
We will interface audio and video with `pipewire` with `wireplumber` as fronted and `pulseaudio` support, which requires us to set the following `USE` flags:
``` shell title="/etc/portage/package.use/pipewire"
media-video/pipewire sound-server loudness echo-cancel bluetooth
media-sound/pulseaudio -daemon
```
Emerge `libpulse` for `pulseaudio` support, `pipewire` and `wireplumber`:
``` shell-session
sh# emerge -a media-libs/libpulse media-video/pipewire media-video/wireplumber
```
and copy the default configuration of `pipewire` and `wireplumber` to their respective `~/.config` folders:
``` shell-session
sh$ cp /usr/share/pipewire/pipewire.conf ~/.config/pipewire/
sh$ cp /usr/share/wirelumber/wireplumber.conf ~/.config/wireplumber/
```
Now create the corresponding user services for the `pipewire-pulse` support layer, `pipewire` itself, and `wireplumber`:
``` shell title="~/.config/rc/init.d/pipewire-pulse"
#!/usr/bin/openrc-run
depend() {
need audio-session
}
supervisor=supervise-daemon
command="/usr/bin/pipewire-pulse"
```
``` shell title="~/.config/rc/init.d/pipewire"
#!/usr/bin/openrc-run
depend() {
need graphical-session
provide audio-session
}
supervisor=supervise-daemon
command="/usr/bin/pipewire"
```
``` shell title="~/.config/rc/init.d/wireplumber"
#!/usr/bin/openrc-run
depend() {
need audio-session
}
supervisor=supervise-daemon
command="/usr/bin/wireplumber"
```
and add them to `<runlevel-post-graphical-session>`:
``` shell-session
sh$ rc-update -U add pipewire h
sh$ rc-update -U add pipewire-pulse h
sh$ rc-update -U add wireplumber h
```
> We need to be part of the `pipewire` group for `pipewire` and dependencies to work accordingly:
>
> ``` shell-session
> sh# usermod --append --groups pipewire <user>
> ```
For screencast and screenshot support in the graphical session we require the `xdg-desktop-portal` layer for rivers' backend `wlroots`, together with some tools (`grim`, `slurp`, `wl-clipboard`) to perform and save screenshots to the clipboard:
``` shell-session
sh# emerge -a gui-libs/xdg-desktop-portal-wlr gui-apps/grim gui-app/slurp gui-apps/wl-clipboard
```
The `xdg-desktop-portal` should be run supervised, therefore create the `xdg-desktop-portal-wlr` user service:
``` shell title="~/.config/rc/init.d/xdg-desktop-portal-wlr"
depend() {
need graphical-session
need audio-session
}
supervisor=supervise-daemon
command="/usr/libexec/xdg-desktop-portal-wlr"
```
and add it to `<runlevel-post-graphical-session>`:
```
sh$ rc-update -U add xdg-desktop-portal-wlr h
```
## Concluding remarks
Well, I think we are there, a minimal graphical session suitable for everyday use. Regarding the customisation of the installed gui-apps, you may have a look at [my gentoo-desktop repository](https://git.lucbijl.nl/luc/gentoo-desktop), or if you think you have a better taste for aesthetic, then do it yourself you cynical connoisseur!