summaryrefslogtreecommitdiff
path: root/public/x11-systemd.html
diff options
context:
space:
mode:
Diffstat (limited to 'public/x11-systemd.html')
-rw-r--r--public/x11-systemd.html388
1 files changed, 388 insertions, 0 deletions
diff --git a/public/x11-systemd.html b/public/x11-systemd.html
new file mode 100644
index 0000000..2313b21
--- /dev/null
+++ b/public/x11-systemd.html
@@ -0,0 +1,388 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+ <title>My X11 setup with systemd — Luke T. Shumaker</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <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 &quot;$XDG_VTNR&quot; ]; then
+ exec /usr/bin/X -nolisten tcp &quot;$@&quot;
+else
+ exec /usr/bin/X -nolisten tcp &quot;$@&quot; 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&amp;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&amp;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 &quot;$XDG_RUNTIME_DIR&quot; ]; then
+ printf &quot;XDG_RUNTIME_DIR isn&#39;t set\n&quot; &gt;&amp;2
+ exit 6
+fi
+
+_DISPLAY=&quot;$(systemd-escape -- &quot;$DISPLAY&quot;)&quot;
+trap &quot;rm -f $(printf &#39;%q&#39; &quot;${XDG_RUNTIME_DIR}/x11-wm@${_DISPLAY}&quot;)&quot; EXIT
+mkfifo &quot;${XDG_RUNTIME_DIR}/x11-wm@${_DISPLAY}&quot;
+
+cat &lt; &quot;${XDG_RUNTIME_DIR}/x11-wm@${_DISPLAY}&quot; &amp;
+systemctl --user start &quot;X11@${_DISPLAY}.target&quot; &amp;
+wait
+systemctl --user stop &quot;X11@${_DISPLAY}.target&quot;</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 &#39;exec 8&gt;${XDG_RUNTIME_DIR}/x11-wm@%I; exec wmii&#39;</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&gt;&amp;- # 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 -&gt; ../<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 -&gt; ../<a
+href="https://lukeshu.com/git/dotfiles/tree/.config/systemd/user/xmodmap@.service">xmodmap@.service</a></li>
+<li>xresources-dpi@.service -&gt; ../<a
+href="https://lukeshu.com/git/dotfiles/tree/.config/systemd/user/xresources-dpi@.service">xresources-dpi@.service</a></li>
+<li>xresources@.service -&gt; ../<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 &#39;s/^\s*resolution:\s*(.*) dots per inch$/\1/p&#39;)
+xrdb -merge &lt;&lt;&lt;&quot;Xft.dpi: ${dpi}&quot;</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 -&gt; ../<a
+href="https://lukeshu.com/git/dotfiles/tree/.config/systemd/user/dunst@.service">dunst@.service</a>       
+# a notification daemon</li>
+<li>lxpanel@.service -&gt; ../<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 -&gt; ../<a
+href="https://lukeshu.com/git/dotfiles/tree/.config/systemd/user/rbar@.service">rbar@.service</a>  
+# wmii stuff</li>
+<li>rbar@99_clock.service -&gt; ../<a
+href="https://lukeshu.com/git/dotfiles/tree/.config/systemd/user/rbar@.service">rbar@.service</a> 
+# wmii stuff</li>
+<li>xcompmgr@.service -&gt; ../<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&gt;&amp;- # 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:&quot;$NOTIFY_SOCKET&quot; &lt;&lt;&lt;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=&quot;$(getopt -n systemd-notify -o h -l help,version,ready,pid::,status:,booted -- &quot;$@&quot;)&quot;
+ ret=$?; [[ $ret == 0 ]] || return $ret
+ eval set -- &quot;$args&quot;
+
+ local arg_ready=false
+ local arg_pid=0
+ local arg_status=
+ while [[ $# -gt 0 ]]; do
+ case &quot;$1&quot; 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+=(&quot;READY=1&quot;)
+ fi
+ if [[ -n &quot;$arg_status&quot; ]]; then
+ our_env+=(&quot;STATUS=$arg_status&quot;)
+ fi
+ if [[ &quot;$arg_pid&quot; -gt 0 ]]; then
+ our_env+=(&quot;MAINPID=$arg_pid&quot;)
+ fi
+ our_env+=(&quot;$@&quot;)
+ local n
+ printf -v n &#39;%s\n&#39; &quot;${our_env[@]}&quot;
+ socat STDIO UNIX-SENDTO:&quot;$NOTIFY_SOCKET&quot; &lt;&lt;&lt;&quot;$n&quot;
+}</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&gt;&amp;- # xinit/systemd handshake
+ exec systemd-run --user --scope -- sh -c &quot;$*&quot;
+)</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
+ <a class="em" href="/imworkingon/">my work</a> 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>