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>
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">"context"</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#4070a0">"flag"</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#4070a0">"fmt"</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#4070a0">"log"</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#4070a0">"net/http"</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#4070a0">"net/url"</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#4070a0">"os"</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#4070a0">"os/exec"</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#4070a0">"time"</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">"resume"</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">"run resume behavior only (turn on monitor via smart plug)"</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">"no_monitor"</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">"disable turning off/on monitor"</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">"[monitor power] skipping because -no_monitor flag is set"</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">"[monitor power] command: %v"</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">"http://myStrom-Switch-A46FD0/"</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">"unexpected HTTP status code: got %v, want %v"</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">"[monitor power] request succeeded"</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><<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">"GET"</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">"relay?state=1"</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">"now : %v"</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">"wakeup: %v"</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">"wakeup: %v (timestamp)"</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">"POST"</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">"timer?mode=on&time=15"</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">"sh"</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">"-c"</span>,<span style="color:#bbb"> </span>fmt.<span style="color:#06287e">Sprintf</span>(<span style="color:#4070a0">"sudo rtcwake -m no --verbose --utc -t %v && sudo systemctl suspend"</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">"running %v\n"</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">"%v: %v"</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">"</span><span style="color:#bb60d5">$1</span><span style="color:#4070a0">"</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&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
</code></pre>
# 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\! :\)
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.
Dell’s UltraSharp 32 4K QD-OLED monitor (U3226Q) delivers cinematic contrast and built-in auto-calibration, positioning it as a premium choice for both creative work and entertainment.
A user describes a CLI tool that controls the entire desktop via hybrid mouse, keyboard, and screenshot methods, successfully performing tasks like sending email screenshots and remote desktop control. They seek challenging tests to validate its robustness.