In praise of grobi for auto-configuring X11 monitors

Michael Stapelberg Tools

Summary

The article praises grobi, a tool for automatic monitor configuration on X11, describing how it simplifies managing a dual-DP 8K monitor setup, and compares it to alternatives like autorandr.

<p>I have recently started using the <a href="https://github.com/fd0/grobi/"><code>grobi</code> program by Alexander Neumann</a> again and was delighted to discover that it makes using my fiddly (but wonderful) <a href="/posts/2017-12-11-dell-up3218k/">Dell 32-inch 8K monitor (UP3218K)</a> monitor much more convenient — I get a signal more quickly than with my previous, sleep-based approach.</p> <p>Previously, when my PC woke up from suspend-to-RAM, there were two scenarios:</p> <ol> <li>The monitor was connected. My <a href="#zleep">sleep program</a> would power on the monitor (if needed), sleep a little while and then run <a href="https://manpages.debian.org/xrandr.1"><code>xrandr(1)</code></a> to (hopefully) configure the monitor correctly.</li> <li>The monitor was not connected, for example because it was still connected to my work PC.</li> </ol> <p>In scenario ②, or if the one-shot configuration attempt in scenario ① fails, I would need to SSH in from a different computer and run <code>xrandr</code> manually so that the monitor would show a signal:</p> <pre tabindex="0"><code>% DISPLAY=:0 xrandr \ --output DP-4 --mode 3840x4320 --panning 0x0+0+0 \ --output DP-2 --right-of DP-4 --mode 3840x4320 --panning 0x0+3840+0 </code></pre><h2 id="automatic-monitor-configuration-with-grobi">Automatic monitor configuration with grobi</h2> <p>I have now completely solved this problem by creating the following <code>~/.config/grobi.conf</code> file:</p> <div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">rules</span>:<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>- <span style="color:#062873;font-weight:bold">name</span>:<span style="color:#bbb"> </span>UP3218K<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">outputs_connected</span>:<span style="color:#bbb"> </span>[DP-2, DP-4]<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic"># DP-4 is left, DP-2 is right</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">configure_row</span>:<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>- DP-4@3840x4320<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>- DP-2@3840x4320<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic"># atomic instructs grobi to only call xrandr once and configure all the</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic"># outputs. This does not always work with all graphic cards, but is</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic"># needed to successfully configure the UP3218K monitor.</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">atomic</span>:<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">true</span><span style="color:#bbb"> </span></span></span></code></pre></div><p>…and installing / enabling <code>grobi</code> (on Arch Linux) using:</p> <pre tabindex="0"><code>% sudo pacman -S grobi % systemctl --user enable --now grobi </code></pre><p>Whenever <code>grobi</code> detects that my monitor is connected (it listens for <a href="https://cgit.freedesktop.org/xorg/proto/randrproto/tree/randrproto.txt">X11 RandR</a> output change events), it will run <a href="https://manpages.debian.org/xrandr.1"><code>xrandr(1)</code></a> to configure the monitor resolution and positioning.</p> <p>To check what <code>grobi</code> is seeing/doing, you can use:</p> <pre tabindex="0"><code>% systemctl --user status grobi % journalctl --user -u grob </code></pre><p>For example, on my system, I see:</p> <pre tabindex="0"><code>grobi: 18:31:48.823765 outputs: [HDMI-0 (primary) DP-0 DP-1 DP-2 (connected) 3840x2160+ [DEL-16711-808727372-DELL UP3218K-D2HP805I043L] DP-3 DP-4 (connected) 3840x21&gt; grobi: 18:31:48.823783 new rule found: UP3218K grobi: 18:31:48.823785 enable outputs: [DP-4@3840x4320 DP-2@3840x4320] grobi: 18:31:48.823789 using one atomic call to xrandr grobi: 18:31:48.823806 running command /usr/bin/xrandr xrandr --output DP-4 --mode 3840x4320 --output DP-2 --mode 3840x4320 --right-of DP-4 grobi: 18:31:49.285944 new RANDR change event received </code></pre><p>Notably, the instructions for getting out of a bad state (no signal) are now to power off the monitor and power it back on again. This will result in RandR output change events, which will trigger <code>grobi</code>, which will run <code>xrandr</code>, which configures the monitor. Nice!</p> <h2 id="why-not-autorandr">Why not autorandr?</h2> <p>No particular reason. I knew <code>grobi</code>.</p> <p>If nothing else, <code>grobi</code> is written in Go, so it’s likely to keep working smoothly over the years.</p> <h2 id="does-grobi-work-on-wayland">Does grobi work on Wayland?</h2> <p>Probably not. There is no mention of Wayland over on the <a href="https://github.com/fd0/grobi/">grobi repository</a>.</p> <h2 id="zleep">Bonus: my Suspend-to-RAM setup</h2> <p>As a bonus, this section describes the other half of my monitor-related automation.</p> <p>When I suspend my PC to RAM, I either want to wake it up manually later, for example by pressing a key on the keyboard or by sending a Wake-on-LAN packet, or I want it to wake up automatically each morning at 6:50 — that way, daily cron jobs have some time to run before I start using the computer.</p> <p>To accomplish this, I use <code>zleep</code>, a wrapper program around <a href="https://manpages.debian.org/rtcwake.8"><code>rtcwake(8)</code></a> and <code>systemctl suspend</code> that integrates with the myStrom switch smart plug to turn off power to the monitor entirely. This is worthwhile because the monitor draws 30W even in standby!</p> <div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">package</span><span style="color:#bbb"> </span>main<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span>(<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;context&#34;</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;flag&#34;</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;fmt&#34;</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;log&#34;</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;net/http&#34;</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;net/url&#34;</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;os&#34;</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;os/exec&#34;</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;time&#34;</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>)<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>(<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>resume<span style="color:#bbb"> </span>=<span style="color:#bbb"> </span>flag.<span style="color:#06287e">Bool</span>(<span style="color:#4070a0">&#34;resume&#34;</span>,<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">false</span>,<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;run resume behavior only (turn on monitor via smart plug)&#34;</span>)<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>noMonitor<span style="color:#bbb"> </span>=<span style="color:#bbb"> </span>flag.<span style="color:#06287e">Bool</span>(<span style="color:#4070a0">&#34;no_monitor&#34;</span>,<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">false</span>,<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;disable turning off/on monitor&#34;</span>)<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>)<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">func</span><span style="color:#bbb"> </span><span style="color:#06287e">monitorPower</span>(ctx<span style="color:#bbb"> </span>context.Context,<span style="color:#bbb"> </span>method,<span style="color:#bbb"> </span>cmnd<span style="color:#bbb"> </span><span style="color:#902000">string</span>)<span style="color:#bbb"> </span><span style="color:#902000">error</span><span style="color:#bbb"> </span>{<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span><span style="color:#666">*</span>noMonitor<span style="color:#bbb"> </span>{<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>log.<span style="color:#06287e">Printf</span>(<span style="color:#4070a0">&#34;[monitor power] skipping because -no_monitor flag is set&#34;</span>)<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">nil</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>}<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>log.<span style="color:#06287e">Printf</span>(<span style="color:#4070a0">&#34;[monitor power] command: %v&#34;</span>,<span style="color:#bbb"> </span>cmnd)<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>u,<span style="color:#bbb"> </span>err<span style="color:#bbb"> </span><span style="color:#666">:=</span><span style="color:#bbb"> </span>url.<span style="color:#06287e">Parse</span>(<span style="color:#4070a0">&#34;http://myStrom-Switch-A46FD0/&#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>cmnd)<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>err<span style="color:#bbb"> </span><span style="color:#666">!=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">nil</span><span style="color:#bbb"> </span>{<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>err<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>}<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">for</span><span style="color:#bbb"> </span>{<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>err<span style="color:#bbb"> </span><span style="color:#666">:=</span><span style="color:#bbb"> </span>ctx.<span style="color:#06287e">Err</span>();<span style="color:#bbb"> </span>err<span style="color:#bbb"> </span><span style="color:#666">!=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">nil</span><span style="color:#bbb"> </span>{<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>err<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>}<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>req,<span style="color:#bbb"> </span>err<span style="color:#bbb"> </span><span style="color:#666">:=</span><span style="color:#bbb"> </span>http.<span style="color:#06287e">NewRequest</span>(method,<span style="color:#bbb"> </span>u.<span style="color:#06287e">String</span>(),<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">nil</span>)<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>err<span style="color:#bbb"> </span><span style="color:#666">!=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">nil</span><span style="color:#bbb"> </span>{<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>err<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>}<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>ctx,<span style="color:#bbb"> </span>canc<span style="color:#bbb"> </span><span style="color:#666">:=</span><span style="color:#bbb"> </span>context.<span style="color:#06287e">WithTimeout</span>(ctx,<span style="color:#bbb"> </span><span style="color:#40a070">5</span><span style="color:#666">*</span>time.Second)<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">defer</span><span style="color:#bbb"> </span><span style="color:#06287e">canc</span>()<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>req<span style="color:#bbb"> </span>=<span style="color:#bbb"> </span>req.<span style="color:#06287e">WithContext</span>(ctx)<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>resp,<span style="color:#bbb"> </span>err<span style="color:#bbb"> </span><span style="color:#666">:=</span><span style="color:#bbb"> </span>http.DefaultClient.<span style="color:#06287e">Do</span>(req)<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>err<span style="color:#bbb"> </span><span style="color:#666">!=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">nil</span><span style="color:#bbb"> </span>{<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>log.<span style="color:#06287e">Print</span>(err)<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>time.<span style="color:#06287e">Sleep</span>(<span style="color:#40a070">1</span><span style="color:#bbb"> </span><span style="color:#666">*</span><span style="color:#bbb"> </span>time.Second)<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">continue</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>}<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>resp.StatusCode<span style="color:#bbb"> </span><span style="color:#666">!=</span><span style="color:#bbb"> </span>http.StatusOK<span style="color:#bbb"> </span>{<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>log.<span style="color:#06287e">Printf</span>(<span style="color:#4070a0">&#34;unexpected HTTP status code: got %v, want %v&#34;</span>,<span style="color:#bbb"> </span>resp.Status,<span style="color:#bbb"> </span>http.StatusOK)<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>time.<span style="color:#06287e">Sleep</span>(<span style="color:#40a070">1</span><span style="color:#bbb"> </span><span style="color:#666">*</span><span style="color:#bbb"> </span>time.Second)<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">continue</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>}<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>log.<span style="color:#06287e">Printf</span>(<span style="color:#4070a0">&#34;[monitor power] request succeeded&#34;</span>)<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">nil</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>}<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">func</span><span style="color:#bbb"> </span><span style="color:#06287e">nextWakeup</span>(now<span style="color:#bbb"> </span>time.Time)<span style="color:#bbb"> </span>time.Time<span style="color:#bbb"> </span>{<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>midnight<span style="color:#bbb"> </span><span style="color:#666">:=</span><span style="color:#bbb"> </span>time.<span style="color:#06287e">Date</span>(now.<span style="color:#06287e">Year</span>(),<span style="color:#bbb"> </span>now.<span style="color:#06287e">Month</span>(),<span style="color:#bbb"> </span>now.<span style="color:#06287e">Day</span>(),<span style="color:#bbb"> </span><span style="color:#40a070">0</span>,<span style="color:#bbb"> </span><span style="color:#40a070">0</span>,<span style="color:#bbb"> </span><span style="color:#40a070">0</span>,<span style="color:#bbb"> </span><span style="color:#40a070">0</span>,<span style="color:#bbb"> </span>time.Local)<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>now.<span style="color:#06287e">Hour</span>()<span style="color:#bbb"> </span>&lt;<span style="color:#bbb"> </span><span style="color:#40a070">6</span><span style="color:#bbb"> </span>{<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">// wake up today</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>midnight.<span style="color:#06287e">Add</span>(<span style="color:#40a070">6</span><span style="color:#666">*</span>time.Hour<span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span><span style="color:#40a070">50</span><span style="color:#666">*</span>time.Minute)<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>}<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">// wake up tomorrow</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>midnight.<span style="color:#06287e">Add</span>(<span style="color:#40a070">24</span><span style="color:#bbb"> </span><span style="color:#666">*</span><span style="color:#bbb"> </span>time.Hour).<span style="color:#06287e">Add</span>(<span style="color:#40a070">6</span><span style="color:#666">*</span>time.Hour<span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span><span style="color:#40a070">50</span><span style="color:#666">*</span>time.Minute)<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">func</span><span style="color:#bbb"> </span><span style="color:#06287e">runResume</span>()<span style="color:#bbb"> </span><span style="color:#902000">error</span><span style="color:#bbb"> </span>{<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">// Retry for up to one minute to give the network some time to come up</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>ctx,<span style="color:#bbb"> </span>canc<span style="color:#bbb"> </span><span style="color:#666">:=</span><span style="color:#bbb"> </span>context.<span style="color:#06287e">WithTimeout</span>(context.<span style="color:#06287e">Background</span>(),<span style="color:#bbb"> </span><span style="color:#40a070">1</span><span style="color:#666">*</span>time.Minute)<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">defer</span><span style="color:#bbb"> </span><span style="color:#06287e">canc</span>()<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>err<span style="color:#bbb"> </span><span style="color:#666">:=</span><span style="color:#bbb"> </span><span style="color:#06287e">monitorPower</span>(ctx,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;GET&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;relay?state=1&#34;</span>);<span style="color:#bbb"> </span>err<span style="color:#bbb"> </span><span style="color:#666">!=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">nil</span><span style="color:#bbb"> </span>{<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>log.<span style="color:#06287e">Print</span>(err)<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>}<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">nil</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">func</span><span style="color:#bbb"> </span><span style="color:#06287e">zleep</span>()<span style="color:#bbb"> </span><span style="color:#902000">error</span><span style="color:#bbb"> </span>{<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>ctx<span style="color:#bbb"> </span><span style="color:#666">:=</span><span style="color:#bbb"> </span>context.<span style="color:#06287e">Background</span>()<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>now<span style="color:#bbb"> </span><span style="color:#666">:=</span><span style="color:#bbb"> </span>time.<span style="color:#06287e">Now</span>().<span style="color:#06287e">Truncate</span>(<span style="color:#40a070">1</span><span style="color:#bbb"> </span><span style="color:#666">*</span><span style="color:#bbb"> </span>time.Second)<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>wakeup<span style="color:#bbb"> </span><span style="color:#666">:=</span><span style="color:#bbb"> </span><span style="color:#06287e">nextWakeup</span>(now)<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>log.<span style="color:#06287e">Printf</span>(<span style="color:#4070a0">&#34;now : %v&#34;</span>,<span style="color:#bbb"> </span>now)<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>log.<span style="color:#06287e">Printf</span>(<span style="color:#4070a0">&#34;wakeup: %v&#34;</span>,<span style="color:#bbb"> </span>wakeup)<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>log.<span style="color:#06287e">Printf</span>(<span style="color:#4070a0">&#34;wakeup: %v (timestamp)&#34;</span>,<span style="color:#bbb"> </span>wakeup.<span style="color:#06287e">Unix</span>())<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">// assumes hwclock is running in UTC (see timedatectl | grep local)</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">// Power the monitor off in 15 seconds.</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">// mode=on is intentional: https://api.mystrom.ch/#e532f952-36ea-40fb-a180-a57b835f550e</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">// - the switch will be turned on (already on, so this is a no-op)</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">// - the switch will wait for 15 seconds</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">// - the switch will be turned off</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>err<span style="color:#bbb"> </span><span style="color:#666">:=</span><span style="color:#bbb"> </span><span style="color:#06287e">monitorPower</span>(ctx,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;POST&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;timer?mode=on&amp;time=15&#34;</span>);<span style="color:#bbb"> </span>err<span style="color:#bbb"> </span><span style="color:#666">!=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">nil</span><span style="color:#bbb"> </span>{<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>log.<span style="color:#06287e">Print</span>(err)<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>}<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>sleep<span style="color:#bbb"> </span><span style="color:#666">:=</span><span style="color:#bbb"> </span>exec.<span style="color:#06287e">Command</span>(<span style="color:#4070a0">&#34;sh&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;-c&#34;</span>,<span style="color:#bbb"> </span>fmt.<span style="color:#06287e">Sprintf</span>(<span style="color:#4070a0">&#34;sudo rtcwake -m no --verbose --utc -t %v &amp;&amp; sudo systemctl suspend&#34;</span>,<span style="color:#bbb"> </span>wakeup.<span style="color:#06287e">Unix</span>()))<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>sleep.Stdout<span style="color:#bbb"> </span>=<span style="color:#bbb"> </span>os.Stdout<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>sleep.Stderr<span style="color:#bbb"> </span>=<span style="color:#bbb"> </span>os.Stderr<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>fmt.<span style="color:#06287e">Printf</span>(<span style="color:#4070a0">&#34;running %v\n&#34;</span>,<span style="color:#bbb"> </span>sleep.Args)<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>err<span style="color:#bbb"> </span><span style="color:#666">:=</span><span style="color:#bbb"> </span>sleep.<span style="color:#06287e">Run</span>();<span style="color:#bbb"> </span>err<span style="color:#bbb"> </span><span style="color:#666">!=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">nil</span><span style="color:#bbb"> </span>{<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>fmt.<span style="color:#06287e">Errorf</span>(<span style="color:#4070a0">&#34;%v: %v&#34;</span>,<span style="color:#bbb"> </span>sleep.Args,<span style="color:#bbb"> </span>err)<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>}<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">nil</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">func</span><span style="color:#bbb"> </span><span style="color:#06287e">main</span>()<span style="color:#bbb"> </span>{<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>flag.<span style="color:#06287e">Parse</span>()<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span><span style="color:#666">*</span>resume<span style="color:#bbb"> </span>{<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>err<span style="color:#bbb"> </span><span style="color:#666">:=</span><span style="color:#bbb"> </span><span style="color:#06287e">runResume</span>();<span style="color:#bbb"> </span>err<span style="color:#bbb"> </span><span style="color:#666">!=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">nil</span><span style="color:#bbb"> </span>{<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>log.<span style="color:#06287e">Fatal</span>(err)<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>}<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>}<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">else</span><span style="color:#bbb"> </span>{<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>err<span style="color:#bbb"> </span><span style="color:#666">:=</span><span style="color:#bbb"> </span><span style="color:#06287e">zsleep</span>();<span style="color:#bbb"> </span>err<span style="color:#bbb"> </span><span style="color:#666">!=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">nil</span><span style="color:#bbb"> </span>{<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>log.<span style="color:#06287e">Fatal</span>(err)<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>}<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>}<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb"> </span></span></span></code></pre></div><p>To turn power to the monitor on after resuming, I placed the following shell script in <code>/lib/systemd/system-sleep/zleep.sh</code>:</p> <div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#007020">#!/bin/sh </span></span></span><span style="display:flex;"><span><span style="color:#007020"></span> </span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">case</span> <span style="color:#4070a0">&#34;</span><span style="color:#bb60d5">$1</span><span style="color:#4070a0">&#34;</span> in </span></span><span style="display:flex;"><span> pre<span style="color:#666">)</span> <span style="color:#007020">exit</span> <span style="color:#40a070">0</span> </span></span><span style="display:flex;"><span> ;; </span></span><span style="display:flex;"><span> post<span style="color:#666">)</span> /usr/local/bin/zleep -resume </span></span><span style="display:flex;"><span> <span style="color:#007020">exit</span> <span style="color:#40a070">0</span> </span></span><span style="display:flex;"><span> ;; </span></span><span style="display:flex;"><span> *<span style="color:#666">)</span> <span style="color:#007020">exit</span> <span style="color:#40a070">1</span> </span></span><span style="display:flex;"><span> ;; </span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">esac</span> </span></span></code></pre></div><p>Once power is on, grobi will detect and configure the monitor.</p> <p>Here is the program in action:</p> <pre tabindex="0"><code>2025/05/06 21:58:32 now : 2025-05-06 21:58:32 +0200 CEST 2025/05/06 21:58:32 wakeup: 2025-05-07 06:50:00 +0200 CEST 2025/05/06 21:58:32 wakeup: 1746593400 (timestamp) 2025/05/06 21:58:32 [monitor power] command: timer?mode=on&amp;time=15 2025/05/06 21:58:32 [monitor power] request succeeded running [sh -c sudo rtcwake -m no --verbose --utc -t 1746593400 &amp;&amp; sudo systemctl suspend] Using UTC time. delta = 0 tzone = 0 tzname = UTC systime = 1746561512, (UTC) Tue May 6 19:58:32 2025 rtctime = 1746561512, (UTC) Tue May 6 19:58:32 2025 alarm 1746593400, sys_time 1746561512, rtc_time 1746561512, seconds 0 rtcwake: wakeup using /dev/rtc0 at Wed May 7 04:50:00 2025 suspend mode: no; leaving </code></pre>
Original Article
View Cached Full Text

Cached at: 05/16/26, 03:34 AM

# In praise of grobi for auto-configuring X11 monitors Source: [https://michael.stapelberg.ch/posts/2025-05-10-grobi-x11-monitor-autoconfig/](https://michael.stapelberg.ch/posts/2025-05-10-grobi-x11-monitor-autoconfig/) Table of contents- [Automatic monitor configuration with grobi](https://michael.stapelberg.ch/posts/2025-05-10-grobi-x11-monitor-autoconfig/#automatic-monitor-configuration-with-grobi) - [Why not autorandr?](https://michael.stapelberg.ch/posts/2025-05-10-grobi-x11-monitor-autoconfig/#why-not-autorandr) - [Does grobi work on Wayland?](https://michael.stapelberg.ch/posts/2025-05-10-grobi-x11-monitor-autoconfig/#does-grobi-work-on-wayland) - [Bonus: my Suspend\-to\-RAM setup](https://michael.stapelberg.ch/posts/2025-05-10-grobi-x11-monitor-autoconfig/#zleep) I have recently started using the[`grobi`program by Alexander Neumann](https://github.com/fd0/grobi/)again and was delighted to discover that it makes using my fiddly \(but wonderful\)[Dell 32\-inch 8K monitor \(UP3218K\)](https://michael.stapelberg.ch/posts/2017-12-11-dell-up3218k/)monitor much more convenient — I get a signal more quickly than with my previous, sleep\-based approach\. Previously, when my PC woke up from suspend\-to\-RAM, there were two scenarios: 1. The monitor was connected\. My[sleep program](https://michael.stapelberg.ch/posts/2025-05-10-grobi-x11-monitor-autoconfig/#zleep)would power on the monitor \(if needed\), sleep a little while and then run[`xrandr\(1\)`](https://manpages.debian.org/xrandr.1)to \(hopefully\) configure the monitor correctly\. 2. The monitor was not connected, for example because it was still connected to my work PC\. In scenario ②, or if the one\-shot configuration attempt in scenario ① fails, I would need to SSH in from a different computer and run`xrandr`manually so that the monitor would show a signal: ``` % DISPLAY=:0 xrandr \ --output DP-4 --mode 3840x4320 --panning 0x0+0+0 \ --output DP-2 --right-of DP-4 --mode 3840x4320 --panning 0x0+3840+0 ``` ## Automatic monitor configuration with grobi I have now completely solved this problem by creating the following`~/\.config/grobi\.conf`file: ``` rules: - name: UP3218K outputs_connected: [DP-2, DP-4] # DP-4 is left, DP-2 is right configure_row: - DP-4@3840x4320 - DP-2@3840x4320 # atomic instructs grobi to only call xrandr once and configure all the # outputs. This does not always work with all graphic cards, but is # needed to successfully configure the UP3218K monitor. atomic: true ``` …and installing / enabling`grobi`\(on Arch Linux\) using: ``` % sudo pacman -S grobi % systemctl --user enable --now grobi ``` Whenever`grobi`detects that my monitor is connected \(it listens for[X11 RandR](https://cgit.freedesktop.org/xorg/proto/randrproto/tree/randrproto.txt)output change events\), it will run[`xrandr\(1\)`](https://manpages.debian.org/xrandr.1)to configure the monitor resolution and positioning\. To check what`grobi`is seeing/doing, you can use: ``` % systemctl --user status grobi % journalctl --user -u grob ``` For example, on my system, I see: ``` grobi: 18:31:48.823765 outputs: [HDMI-0 (primary) DP-0 DP-1 DP-2 (connected) 3840x2160+ [DEL-16711-808727372-DELL UP3218K-D2HP805I043L] DP-3 DP-4 (connected) 3840x21> grobi: 18:31:48.823783 new rule found: UP3218K grobi: 18:31:48.823785 enable outputs: [DP-4@3840x4320 DP-2@3840x4320] grobi: 18:31:48.823789 using one atomic call to xrandr grobi: 18:31:48.823806 running command /usr/bin/xrandr xrandr --output DP-4 --mode 3840x4320 --output DP-2 --mode 3840x4320 --right-of DP-4 grobi: 18:31:49.285944 new RANDR change event received ``` Notably, the instructions for getting out of a bad state \(no signal\) are now to power off the monitor and power it back on again\. This will result in RandR output change events, which will trigger`grobi`, which will run`xrandr`, which configures the monitor\. Nice\! ## Why not autorandr? No particular reason\. I knew`grobi`\. If nothing else,`grobi`is written in Go, so it’s likely to keep working smoothly over the years\. ## Does grobi work on Wayland? Probably not\. There is no mention of Wayland over on the[grobi repository](https://github.com/fd0/grobi/)\. ## Bonus: my Suspend\-to\-RAM setup As a bonus, this section describes the other half of my monitor\-related automation\. When I suspend my PC to RAM, I either want to wake it up manually later, for example by pressing a key on the keyboard or by sending a Wake\-on\-LAN packet, or I want it to wake up automatically each morning at 6:50 — that way, daily cron jobs have some time to run before I start using the computer\. To accomplish this, I use`zleep`, a wrapper program around[`rtcwake\(8\)`](https://manpages.debian.org/rtcwake.8)and`systemctl suspend`that integrates with the myStrom switch smart plug to turn off power to the monitor entirely\. This is worthwhile because the monitor draws 30W even in standby\! ``` package main import ( "context" "flag" "fmt" "log" "net/http" "net/url" "os" "os/exec" "time" ) var ( resume = flag.Bool("resume", false, "run resume behavior only (turn on monitor via smart plug)") noMonitor = flag.Bool("no_monitor", false, "disable turning off/on monitor") ) func monitorPower(ctx context.Context, method, cmnd string) error { if *noMonitor { log.Printf("[monitor power] skipping because -no_monitor flag is set") return nil } log.Printf("[monitor power] command: %v", cmnd) u, err := url.Parse("http://myStrom-Switch-A46FD0/" + cmnd) if err != nil { return err } for { if err := ctx.Err(); err != nil { return err } req, err := http.NewRequest(method, u.String(), nil) if err != nil { return err } ctx, canc := context.WithTimeout(ctx, 5*time.Second) defer canc() req = req.WithContext(ctx) resp, err := http.DefaultClient.Do(req) if err != nil { log.Print(err) time.Sleep(1 * time.Second) continue } if resp.StatusCode != http.StatusOK { log.Printf("unexpected HTTP status code: got %v, want %v", resp.Status, http.StatusOK) time.Sleep(1 * time.Second) continue } log.Printf("[monitor power] request succeeded") return nil } } func nextWakeup(now time.Time) time.Time { midnight := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local) if now.Hour() < 6 { // wake up today return midnight.Add(6*time.Hour + 50*time.Minute) } // wake up tomorrow return midnight.Add(24 * time.Hour).Add(6*time.Hour + 50*time.Minute) } func runResume() error { // Retry for up to one minute to give the network some time to come up ctx, canc := context.WithTimeout(context.Background(), 1*time.Minute) defer canc() if err := monitorPower(ctx, "GET", "relay?state=1"); err != nil { log.Print(err) } return nil } func zleep() error { ctx := context.Background() now := time.Now().Truncate(1 * time.Second) wakeup := nextWakeup(now) log.Printf("now : %v", now) log.Printf("wakeup: %v", wakeup) log.Printf("wakeup: %v (timestamp)", wakeup.Unix()) // assumes hwclock is running in UTC (see timedatectl | grep local) // Power the monitor off in 15 seconds. // mode=on is intentional: https://api.mystrom.ch/#e532f952-36ea-40fb-a180-a57b835f550e // - the switch will be turned on (already on, so this is a no-op) // - the switch will wait for 15 seconds // - the switch will be turned off if err := monitorPower(ctx, "POST", "timer?mode=on&time=15"); err != nil { log.Print(err) } sleep := exec.Command("sh", "-c", fmt.Sprintf("sudo rtcwake -m no --verbose --utc -t %v && sudo systemctl suspend", wakeup.Unix())) sleep.Stdout = os.Stdout sleep.Stderr = os.Stderr fmt.Printf("running %v\n", sleep.Args) if err := sleep.Run(); err != nil { return fmt.Errorf("%v: %v", sleep.Args, err) } return nil } func main() { flag.Parse() if *resume { if err := runResume(); err != nil { log.Fatal(err) } } else { if err := zsleep(); err != nil { log.Fatal(err) } } } ``` To turn power to the monitor on after resuming, I placed the following shell script in`/lib/systemd/system\-sleep/zleep\.sh`: ``` #!/bin/sh case "$1" in pre) exit 0 ;; post) /usr/local/bin/zleep -resume exit 0 ;; *) exit 1 ;; esac ``` Once power is on, grobi will detect and configure the monitor\. Here is the program in action: ``` 2025/05/06 21:58:32 now : 2025-05-06 21:58:32 +0200 CEST 2025/05/06 21:58:32 wakeup: 2025-05-07 06:50:00 +0200 CEST 2025/05/06 21:58:32 wakeup: 1746593400 (timestamp) 2025/05/06 21:58:32 [monitor power] command: timer?mode=on&time=15 2025/05/06 21:58:32 [monitor power] request succeeded running [sh -c sudo rtcwake -m no --verbose --utc -t 1746593400 && sudo systemctl suspend] Using UTC time. delta = 0 tzone = 0 tzname = UTC systime = 1746561512, (UTC) Tue May 6 19:58:32 2025 rtctime = 1746561512, (UTC) Tue May 6 19:58:32 2025 alarm 1746593400, sys_time 1746561512, rtc_time 1746561512, seconds 0 rtcwake: wakeup using /dev/rtc0 at Wed May 7 04:50:00 2025 suspend mode: no; leaving ``` Did you like this post?[Subscribe to this blog’s RSS feed](https://michael.stapelberg.ch/feed.xml)to not miss any new posts\! I run a blog since 2005, spreading knowledge and experience for over 20 years\! :\)

Similar Articles

Introducing Grok Build (2 minute read)

TLDR AI

Grok Build is a new coding agent that runs from the terminal, now in early beta for SuperGrok Heavy subscribers. It supports plugins, subagents, and deep worktree integrations, including a headless mode for scripting.

Grok Connectors

Product Hunt

X AI launches Grok Connectors, allowing users to integrate their daily apps directly into the Grok AI platform.

Gretl

Product Hunt

Gretl is a visual control panel designed for managing localhost environments.