summaryrefslogtreecommitdiff
path: root/public/x11-systemd.html
blob: a238c732d7a0ac97c4abd0c6beb2a93c1a9ccd6b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
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 &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
      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>