diff options
Diffstat (limited to 'public/x11-systemd.html')
-rw-r--r-- | public/x11-systemd.html | 386 |
1 files changed, 386 insertions, 0 deletions
diff --git a/public/x11-systemd.html b/public/x11-systemd.html new file mode 100644 index 0000000..a238c73 --- /dev/null +++ b/public/x11-systemd.html @@ -0,0 +1,386 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <title>My X11 setup with systemd — Luke T. Shumaker</title> + <link rel="stylesheet" href="assets/style.css"> + <link rel="alternate" type="application/atom+xml" href="./index.atom" name="web log entries"/> +</head> +<body> +<header><a href="/">Luke T. Shumaker</a> » <a href=/blog>blog</a> » x11-systemd</header> +<article> +<h1 id="my-x11-setup-with-systemd">My X11 setup with systemd</h1> +<p>Somewhere along the way, I decided to use systemd user sessions to +manage the various parts of my X11 environment would be a good idea. If +that was a good idea or not… we’ll see.</p> +<p>I’ve sort-of been running this setup as my daily-driver for <a +href="https://lukeshu.com/git/dotfiles.git/commit/?id=a9935b7a12a522937d91cb44a0e138132b555e16">a +bit over a year</a>, continually tweaking it though.</p> +<p>My setup is substantially different than the one on <a +href="https://wiki.archlinux.org/index.php/Systemd/User">ArchWiki</a>, +because the ArchWiki solution assumes that there is only ever one X +server for a user; I like the ability to run <code>Xorg</code> on my +real monitor, and also have <code>Xvnc</code> running headless, or start +my desktop environment on a remote X server. Though, I would like to +figure out how to use systemd socket activation for the X server, as the +ArchWiki solution does.</p> +<p>This means that all of my graphical units take <code>DISPLAY</code> +as an <code>@</code> argument. To get this to all work out, this goes in +each <code>.service</code> file, unless otherwise noted:</p> +<pre><code>[Unit] +After=X11@%i.target +Requisite=X11@%i.target +[Service] +Environment=DISPLAY=%I</code></pre> +<p>We’ll get to <code>X11@.target</code> later, what it says is “I +should only be running if X11 is running”.</p> +<p>I eschew complex XDMs or <code>startx</code> wrapper scripts, opting +for the more simple <code>xinit</code>, which I either run on login for +some boxes (my media station), or type <code>xinit</code> when I want +X11 on others (most everything else). Essentially, what +<code>xinit</code> does is run <code>~/.xserverrc</code> (or +<code>/etc/X11/xinit/xserverrc</code>) to start the server, then once +the server is started (which it takes a substantial amount of magic to +detect) it runs run <code>~/.xinitrc</code> (or +<code>/etc/X11/xinit/xinitrc</code>) to start the clients. Once +<code>.xinitrc</code> finishes running, it stops the X server and exits. +Now, when I say “run”, I don’t mean execute, it passes each file to the +system shell (<code>/bin/sh</code>) as input.</p> +<p>Xorg requires a TTY to run on; if we log in to a TTY with +<code>logind</code>, it will give us the <code>XDG_VTNR</code> variable +to tell us which one we have, so I pass this to <code>X</code> in <a +href="https://lukeshu.com/git/dotfiles.git/tree/.config/X11/serverrc">my +<code>.xserverrc</code></a>:</p> +<pre><code>#!/hint/sh +if [ -z "$XDG_VTNR" ]; then + exec /usr/bin/X -nolisten tcp "$@" +else + exec /usr/bin/X -nolisten tcp "$@" vt$XDG_VTNR +fi</code></pre> +<p>This was the default for <a +href="https://projects.archlinux.org/svntogit/packages.git/commit/trunk/xserverrc?h=packages/xorg-xinit&id=f9f5de58df03aae6c8a8c8231a83327d19b943a1">a +while</a> in Arch, to support <code>logind</code>, but was <a +href="https://projects.archlinux.org/svntogit/packages.git/commit/trunk/xserverrc?h=packages/xorg-xinit&id=5a163ddd5dae300e7da4b027e28c37ad3b535804">later +removed</a> in part because <code>startx</code> (which calls +<code>xinit</code>) started adding it as an argument as well, so +<code>vt$XDG_VTNR</code> was being listed as an argument twice, which is +an error. IMO, that was a problem in <code>startx</code>, and they +shouldn’t have removed it from the default system +<code>xserverrc</code>, but that’s just me. So I copy/pasted it into my +user <code>xserverrc</code>.</p> +<p>That’s the boring part, though. Where the magic starts happening is +in <a +href="https://lukeshu.com/git/dotfiles.git/tree/.config/X11/clientrc">my +<code>.xinitrc</code></a>:</p> +<pre><code>#!/hint/sh + +if [ -z "$XDG_RUNTIME_DIR" ]; then + printf "XDG_RUNTIME_DIR isn't set\n" >&2 + exit 6 +fi + +_DISPLAY="$(systemd-escape -- "$DISPLAY")" +trap "rm -f $(printf '%q' "${XDG_RUNTIME_DIR}/x11-wm@${_DISPLAY}")" EXIT +mkfifo "${XDG_RUNTIME_DIR}/x11-wm@${_DISPLAY}" + +cat < "${XDG_RUNTIME_DIR}/x11-wm@${_DISPLAY}" & +systemctl --user start "X11@${_DISPLAY}.target" & +wait +systemctl --user stop "X11@${_DISPLAY}.target"</code></pre> +<p>There are two contracts/interfaces here: the +<code>X11@DISPLAY.target</code> systemd target, and the +<code>${XDG_RUNTIME_DIR}/x11-wm@DISPLAY</code> named pipe. The systemd +<code>.target</code> should be pretty self explanatory; the most +important part is that it starts the window manager. The named pipe is +just a hacky way of blocking until the window manager exits +(“traditional” <code>.xinitrc</code> files end with the line +<code>exec your-window-manager</code>, so this mimics that behavior). It +works by assuming that the window manager will open the pipe at startup, +and keep it open (without necessarily writing anything to it); when the +window manager exits, the pipe will get closed, sending EOF to the +<code>wait</code>ed-for <code>cat</code>, allowing it to exit, letting +the script resume. The window manager (WMII) is made to have the pipe +opened by executing it this way in <a +href="https://lukeshu.com/git/dotfiles/tree/.config/systemd/user/wmii@.service">its +<code>.service</code> file</a>:</p> +<pre><code>ExecStart=/usr/bin/env bash -c 'exec 8>${XDG_RUNTIME_DIR}/x11-wm@%I; exec wmii'</code></pre> +<p>which just opens the file on file descriptor 8, then launches the +window manager normally. The only further logic required by the window +manager with regard to the pipe is that in the window manager <a +href="https://lukeshu.com/git/dotfiles.git/tree/.config/wmii-hg/config.sh">configuration</a>, +I should close that file descriptor after forking any process that isn’t +“part of” the window manager:</p> +<pre><code>runcmd() ( + ... + exec 8>&- # xinit/systemd handshake + ... +)</code></pre> +<p>So, back to the <code>X11@DISPLAY.target</code>; I configure what it +“does” with symlinks in the <code>.requires</code> and +<code>.wants</code> directories:</p> +<ul class="tree"> +<li> +<p><a +href="https://lukeshu.com/git/dotfiles/tree/.config/systemd/user">.config/systemd/user/</a></p> +<ul> +<li><a +href="https://lukeshu.com/git/dotfiles/tree/.config/systemd/user/X11@.target">X11@.target</a></li> +<li><a +href="https://lukeshu.com/git/dotfiles/tree/.config/systemd/user/X11@.target.requires">X11@.target.requires</a>/ +<ul> +<li>wmii@.service -> ../<a +href="https://lukeshu.com/git/dotfiles/tree/.config/systemd/user/wmii@.service">wmii@.service</a></li> +</ul></li> +<li><a +href="https://lukeshu.com/git/dotfiles/tree/.config/systemd/user/X11@.target.wants">X11@.target.wants</a>/ +<ul> +<li>xmodmap@.service -> ../<a +href="https://lukeshu.com/git/dotfiles/tree/.config/systemd/user/xmodmap@.service">xmodmap@.service</a></li> +<li>xresources-dpi@.service -> ../<a +href="https://lukeshu.com/git/dotfiles/tree/.config/systemd/user/xresources-dpi@.service">xresources-dpi@.service</a></li> +<li>xresources@.service -> ../<a +href="https://lukeshu.com/git/dotfiles/tree/.config/systemd/user/xresources@.service">xresources@.service</a></li> +</ul></li> +</ul> +</li> +</ul> +<p>The <code>.requires</code> directory is how I configure which window +manager it starts. This would allow me to configure different window +managers on different displays, by creating a <code>.requires</code> +directory with the <code>DISPLAY</code> included, +e.g. <code>X11@:2.requires</code>.</p> +<p>The <code>.wants</code> directory is for general X display setup; +it’s analogous to <code>/etc/X11/xinit/xinitrc.d/</code>. All of the +files in it are simple <code>Type=oneshot</code> service files. The <a +href="https://lukeshu.com/git/dotfiles/tree/.config/systemd/user/xmodmap@.service">xmodmap</a> +and <a +href="https://lukeshu.com/git/dotfiles/tree/.config/systemd/user/xresources@.service">xresources</a> +files are pretty boring, they’re just systemd versions of the couple +lines that just about every traditional <code>.xinitrc</code> contains, +the biggest difference being that they look at <a +href="https://lukeshu.com/git/dotfiles.git/tree/.config/X11/modmap"><code>~/.config/X11/modmap</code></a> +and <a +href="https://lukeshu.com/git/dotfiles.git/tree/.config/X11/resources"><code>~/.config/X11/resources</code></a> +instead of the traditional locations <code>~/.xmodmap</code> and +<code>~/.Xresources</code>.</p> +<p>What’s possibly of note is <a +href="https://lukeshu.com/git/dotfiles/tree/.config/systemd/user/xresources-dpi@.service"><code>xresources-dpi@.service</code></a>. +In X11, there are two sources of DPI information, the X display +resolution, and the XRDB <code>Xft.dpi</code> setting. It isn’t defined +which takes precedence (to my knowledge), and even if it were (is), +application authors wouldn’t be arsed to actually do the right thing. +For years, Firefox (well, Iceweasel) happily listened to the X display +resolution, but recently it decided to only look at +<code>Xft.dpi</code>, which objectively seems a little silly, since the +X display resolution is always present, but <code>Xft.dpi</code> isn’t. +Anyway, Mozilla’s change drove me to to create a <a +href="https://lukeshu.com/git/dotfiles/tree/.local/bin/xrdb-set-dpi">script</a> +to make the <code>Xft.dpi</code> setting match the X display resolution. +Disclaimer: I have no idea if it works if the X server has multiple +displays (with possibly varying resolution).</p> +<pre><code>#!/usr/bin/env bash +dpi=$(LC_ALL=C xdpyinfo|sed -rn 's/^\s*resolution:\s*(.*) dots per inch$/\1/p') +xrdb -merge <<<"Xft.dpi: ${dpi}"</code></pre> +<p>Since we want XRDB to be set up before any other programs launch, we +give both of the <code>xresources</code> units +<code>Before=X11@%i.target</code> (instead of <code>After=</code> like +everything else). Also, two programs writing to <code>xrdb</code> at the +same time has the same problem as two programs writing to the same file; +one might trash the other’s changes. So, I stuck +<code>Conflicts=xresources@:i.service</code> into +<code>xresources-dpi.service</code>.</p> +<p>And that’s the “core” of my X11 systemd setup. But, you generally +want more things running than just the window manager, like a desktop +notification daemon, a system panel, and an X composition manager +(unless your window manager is bloated and has a composition manager +built in). Since these things are probably window-manager specific, I’ve +stuck them in a directory <code>wmii@.service.wants</code>:</p> +<ul class="tree"> +<li> +<p><a +href="https://lukeshu.com/git/dotfiles/tree/.config/systemd/user">.config/systemd/user/</a></p> +<ul> +<li><a +href="https://lukeshu.com/git/dotfiles/tree/.config/systemd/user/wmii@.service.wants">wmii@.service.wants</a>/ +<ul> +<li>dunst@.service -> ../<a +href="https://lukeshu.com/git/dotfiles/tree/.config/systemd/user/dunst@.service">dunst@.service</a> +# a notification daemon</li> +<li>lxpanel@.service -> ../<a +href="https://lukeshu.com/git/dotfiles/tree/.config/systemd/user/lxpanel@.service">lxpanel@.service</a> +# a system panel</li> +<li>rbar@97_acpi.service -> ../<a +href="https://lukeshu.com/git/dotfiles/tree/.config/systemd/user/rbar@.service">rbar@.service</a> +# wmii stuff</li> +<li>rbar@99_clock.service -> ../<a +href="https://lukeshu.com/git/dotfiles/tree/.config/systemd/user/rbar@.service">rbar@.service</a> +# wmii stuff</li> +<li>xcompmgr@.service -> ../<a +href="https://lukeshu.com/git/dotfiles/tree/.config/systemd/user/xcompmgr@.service">xcompmgr@.service</a> +# an X composition manager</li> +</ul></li> +</ul> +</li> +</ul> +<p>For the window manager <code>.service</code>, I <em>could</em> just +say <code>Type=simple</code> and call it a day (and I did for a while). +But, I like to have <code>lxpanel</code> show up on all of my WMII tags +(desktops), so I have <a +href="https://lukeshu.com/git/dotfiles.git/tree/.config/wmii-hg/config.sh">my +WMII configuration</a> stick this in the WMII <a +href="https://lukeshu.com/git/dotfiles.git/tree/.config/wmii-hg/rules"><code>/rules</code></a>:</p> +<pre><code>/panel/ tags=/.*/ floating=always</code></pre> +<p>Unfortunately, for this to work, <code>lxpanel</code> must be started +<em>after</em> that gets inserted into WMII’s rules. That wasn’t a +problem pre-systemd, because <code>lxpanel</code> was started by my WMII +configuration, so ordering was simple. For systemd to get this right, I +must have a way of notifying systemd that WMII’s fully started, and it’s +safe to start <code>lxpanel</code>. So, I stuck this in <a +href="https://lukeshu.com/git/dotfiles/tree/.config/systemd/user/wmii@.service">my +WMII <code>.service</code> file</a>:</p> +<pre><code># This assumes that you write READY=1 to $NOTIFY_SOCKET in wmiirc +Type=notify +NotifyAccess=all</code></pre> +<p>and this in <a +href="https://lukeshu.com/git/dotfiles.git/tree/.config/wmii-hg/wmiirc">my +WMII configuration</a>:</p> +<pre><code>systemd-notify --ready || true</code></pre> +<p>Now, this setup means that <code>NOTIFY_SOCKET</code> is set for all +the children of <code>wmii</code>; I’d rather not have it leak into the +applications that I start from the window manager, so I also stuck +<code>unset NOTIFY_SOCKET</code> after forking a process that isn’t part +of the window manager:</p> +<pre><code>runcmd() ( + ... + unset NOTIFY_SOCKET # systemd + ... + exec 8>&- # xinit/systemd handshake + ... +)</code></pre> +<p>Unfortunately, because of a couple of <a +href="https://github.com/systemd/systemd/issues/2739">bugs</a> and <a +href="https://github.com/systemd/systemd/issues/2737">race +conditions</a> in systemd, <code>systemd-notify</code> isn’t reliable. +If systemd can’t receive the <code>READY=1</code> signal from my WMII +configuration, there are two consequences:</p> +<ol type="1"> +<li><code>lxpanel</code> will never start, because it will always be +waiting for <code>wmii</code> to be ready, which will never happen.</li> +<li>After a couple of minutes, systemd will consider <code>wmii</code> +to be timed out, which is a failure, so then it will kill +<code>wmii</code>, and exit my X11 session. That’s no good!</li> +</ol> +<p>Using <code>socat</code> to send the message to systemd instead of +<code>systemd-notify</code> “should” always work, because it tries to +read from both ends of the bi-directional stream, and I can’t imagine +that getting EOF from the <code>UNIX-SENDTO</code> end will ever be +faster than the systemd manager from handling the datagram that got +sent. Which is to say, “we work around the race condition by being slow +and shitty.”</p> +<pre><code>socat STDIO UNIX-SENDTO:"$NOTIFY_SOCKET" <<<READY=1 || true</code></pre> +<p>But, I don’t like that. I’d rather write my WMII configuration to the +world as I wish it existed, and have workarounds encapsulated elsewhere; +<a +href="http://blog.robertelder.org/interfaces-most-important-software-engineering-concept/">“If +you have to cut corners in your project, do it inside the +implementation, and wrap a very good interface around it.”</a>. So, I +wrote a <code>systemd-notify</code> compatible <a +href="https://lukeshu.com/git/dotfiles.git/tree/.config/wmii-hg/workarounds.sh">function</a> +that ultimately calls <code>socat</code>:</p> +<pre><code>## +# Just like systemd-notify(1), but slower, which is a shitty +# workaround for a race condition in systemd. +## +systemd-notify() { + local args + args="$(getopt -n systemd-notify -o h -l help,version,ready,pid::,status:,booted -- "$@")" + ret=$?; [[ $ret == 0 ]] || return $ret + eval set -- "$args" + + local arg_ready=false + local arg_pid=0 + local arg_status= + while [[ $# -gt 0 ]]; do + case "$1" in + -h|--help) command systemd-notify --help; return $?;; + --version) command systemd-notify --version; return $?;; + --ready) arg_ready=true; shift 1;; + --pid) arg_pid=${2:-$$}; shift 2;; + --status) arg_status=$2; shift 2;; + --booted) command systemd-notify --booted; return $?;; + --) shift 1; break;; + esac + done + + local our_env=() + if $arg_ready; then + our_env+=("READY=1") + fi + if [[ -n "$arg_status" ]]; then + our_env+=("STATUS=$arg_status") + fi + if [[ "$arg_pid" -gt 0 ]]; then + our_env+=("MAINPID=$arg_pid") + fi + our_env+=("$@") + local n + printf -v n '%s\n' "${our_env[@]}" + socat STDIO UNIX-SENDTO:"$NOTIFY_SOCKET" <<<"$n" +}</code></pre> +<p>So, one day when the systemd bugs have been fixed (and presumably the +Linux kernel supports passing the cgroup of a process as part of its +credentials), I can remove that from <code>workarounds.sh</code>, and +not have to touch anything else in my WMII configuration (I do use +<code>systemd-notify</code> in a couple of other, non-essential, places +too; this wasn’t to avoid having to change just 1 line).</p> +<p>So, now that <code>wmii@.service</code> properly has +<code>Type=notify</code>, I can just stick +<code>After=wmii@.service</code> into my <code>lxpanel@.service</code>, +right? Wrong! Well, I <em>could</em>, but my <code>lxpanel</code> +service has nothing to do with WMII; why should I couple them? Instead, +I create <a +href="https://lukeshu.com/git/dotfiles/tree/.config/systemd/user/wm-running@.target"><code>wm-running@.target</code></a> +that can be used as a synchronization point:</p> +<pre><code># wmii@.service +Before=wm-running@%i.target + +# lxpanel@.service +After=X11@%i.target wm-running@%i.target +Requires=wm-running@%i.target</code></pre> +<p>Finally, I have my desktop started and running. Now, I’d like for +programs that aren’t part of the window manager to not dump their stdout +and stderr into WMII’s part of the journal, like to have a record of +which graphical programs crashed, and like to have a prettier +cgroup/process graph. So, I use <code>systemd-run</code> to run external +programs from the window manager:</p> +<pre><code>runcmd() ( + ... + unset NOTIFY_SOCKET # systemd + ... + exec 8>&- # xinit/systemd handshake + exec systemd-run --user --scope -- sh -c "$*" +)</code></pre> +<p>I run them as a scope instead of a service so that they inherit +environment variables, and don’t have to mess with getting +<code>DISPLAY</code> or <code>XAUTHORITY</code> into their units (as I +<em>don’t</em> want to make them global variables in my systemd user +session).</p> +<p>I’d like to get <code>lxpanel</code> to also use +<code>systemd-run</code> when launching programs, but it’s a low +priority because I don’t really actually use <code>lxpanel</code> to +launch programs, I just have the menu there to make sure that I didn’t +break the icons for programs that I package (I did that once back when I +was Parabola’s packager for Iceweasel and IceCat).</p> +<p>And that’s how I use systemd with X11.</p> + +</article> +<footer> + <aside class="sponsor"><p>I'd love it if you <a class="em" + href="/sponsor/">sponsored me</a>. It will allow me to continue + my work on the GNU/Linux ecosystem. Thanks!</p></aside> + +<p>The content of this page is Copyright © 2016 <a href="mailto:lukeshu@lukeshu.com">Luke T. Shumaker</a>.</p> +<p>This page is licensed under the <a href="https://creativecommons.org/licenses/by-sa/4.0/">CC BY-SA 4.0</a> license.</p> +</footer> +</body> +</html> |