<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Beneath the abstraction]]></title><description><![CDATA[Articles on systems, going deep and understanding why things work the way they do.]]></description><link>https://beneaththeabstraction.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Thu, 12 Nov 2026 07:41:10 GMT</lastBuildDate><atom:link href="https://beneaththeabstraction.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[On Nvim RPC]]></title><description><![CDATA[Or what changing my display mode taught me about RPC]]></description><link>https://beneaththeabstraction.hashnode.dev/on-nvim-rpc</link><guid isPermaLink="true">https://beneaththeabstraction.hashnode.dev/on-nvim-rpc</guid><category><![CDATA[nvim, ]]></category><category><![CDATA[RPC]]></category><category><![CDATA[alacritty]]></category><category><![CDATA[tmux]]></category><category><![CDATA[i3]]></category><dc:creator><![CDATA[Adarsh Kishore]]></dc:creator><pubDate>Sun, 08 Nov 2026 18:48:09 GMT</pubDate><content:encoded><![CDATA[<p>Recently, I have been experimenting with a lot of different stuff, which I hope to talk about here soon, but one of them stemmed from a pragmatic issue: taking care of my eyes.</p>
<p>As a real programmer, I love dark mode universally. But now that winter is coming, I like to switch between sitting at my desk and sitting in the balcony while working. This obviously means sitting in the sun, or at least in diffused sunlight, and my laptop has a really bad habit: it automatically changes brightness according to what is displayed on screen. Put something white, and brightness increases, put something dark, things go dark pretty quickly.</p>
<p>This is not just a side effect of colour intensity though, the luminescence does change in real time. I don’t know why the manufacturers thought this would be a good feature. Worse, I can’t even turn this “screen-adaptive” brightness off (at least not so far). Combine my love of dark mode with this moody screen and you get a good old dim screen. Normally, this is not a problem inside my room, but this can become especially taxing for my eyes in the balcony.</p>
<p>I use Manjaro Linux with i3wm, and hence, I wanted to create a script that can toggle my screen from light to dark and vice versa (A truly programmer move).</p>
<h2>Initial script</h2>
<p>It was initially a routine task and hence I handed it off to Claude to come up with a basic script (btw these things are now quite competent at a lot of stuff).</p>
<pre><code class="language-bash">#!/usr/bin/env bash
state=~/.cache/theme-mode
mode=$(cat "$state" 2&gt;/dev/null || echo dark)
[ "$mode" = dark ] &amp;&amp; new=light || new=dark

if [ "$new" = light ]; then
  gsettings set org.gnome.desktop.interface color-scheme prefer-light
  gsettings set org.gnome.desktop.interface gtk-theme Adwaita
else
  gsettings set org.gnome.desktop.interface color-scheme prefer-dark
  gsettings set org.gnome.desktop.interface gtk-theme Adwaita-dark
fi

# Terminal: point a symlink at light.toml or dark.toml
ln -sf ~/.config/alacritty/$new.toml ~/.config/alacritty/theme.toml

echo "$new" &gt; "$state"
</code></pre>
<p>This solved mostly everything. The way it works is mostly self-explanatory: it reads a state variable stored in <code>~/.cache/theme-mode</code>, toggles it, and uses <code>gsettings</code> to change the system theme accordingly.</p>
<p>My <code>alacritty</code> terminal also gets a different colorscheme based on the mode, and so everything happens correctly. Except one thing.</p>
<h2>The tmux issue (apparently)</h2>
<p>I use NeoVim as my text editor. When changing colors, sometimes my NeoVim would often get into a glitchy state, where the background colors swapped fine, but the foreground colors got stuck, giving either a white-on-white or black-on-black reading experience (And that is not easy on the eyes). Moreover, some random event like closing or opening a new window fixed it, but it was not consistent, sometimes it fixed at once, sometimes on opening the fifth window, sometimes not at all.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aef22822f2f28abe7351fad/bc1ca5c4-8859-4f1b-9747-36fc9a630a8e.png" alt="Light-on-light" style="display:block;margin-left:auto" />

<p>My initial hunch was that this was a <code>tmux</code> issue. I have had these issues before where <code>tmux</code> doesn't show true terminal colors properly, so a glitch in that was my first hunch.</p>
<blockquote>
<p>Minor detour: I recently observed that Claude made my life a little too easy, it took the challenging parts of being a software engineer away by just handing out the answer. So I changed the system prompt of Claude:</p>
<pre><code class="language-plaintext">When I paste an error or describe a bug, classify it first:

ROUTINE (typos, config, syntax, boilerplate, library quirks I won't need to master): give the fix and a one-line cause. No coaching.

WORTH LEARNING (a concept I'm missing, architecture or design decisions, bugs in core areas of my work, an error type I've hit before in this conversation): don't give the answer first. Ask for my hypothesis in one line. If I don't have one, give one hint pointing to where to look. Be a strict teacher and only give the next important hint on asking. Only reveal the full solution after I say "I give up, please explain". Then explain the root cause and what would have let me find it myself.

If unsure which category applies, say which you think it is in five words and proceed. Hint: anything related to computer systems, using git properly, building a project is a learning point for me.

Override: if I write "just fix it", skip all coaching. If I write "coach me", use the learning mode regardless of category.

Keep responses short. No praise like a sycophant.
</code></pre>
<p>This has been quite effective. You can take this for a spin if interested.</p>
</blockquote>
<p>I started investigating with Claude to see what could cause it. My first place to look was in the <code>.tmux.conf</code> file to see what could be amiss. I tried tweaking a few things, like changing the <code>terminal-overrides</code> in <code>tmux</code>, but nothing worked.</p>
<p>Just as a test, I opened an editor outside of <code>tmux</code> and toggled the colorscheme. I noticed that even the outside editor did not render the colorscheme properly and glitched with the same issue. So the problem was never with <code>tmux</code> to begin with. My next suspect was...</p>
<h2>Do you have the <code>alacritty</code> for it?</h2>
<p>I use <code>alacritty</code> as my terminal emulator, so my next thought was that there was some glitch in changing the <code>theme.toml</code> file that I referenced in my script earlier, due to which some colors were not overridden.</p>
<p>I turned on the <code>alacritty msg config</code> to look for any signs of repainting errors, but nothing. I thought maybe there was some extra configuration we had to do, but the documentation said that <code>live_config_reload</code> is on by default.</p>
<p>Replacing <code>ln</code> with <code>cp</code> was disastrous, as not only did it not work, it actually overrote my <code>dark.toml</code> with my <code>light.toml</code> file, and after restoring it I was still back at square one: everything was working except the foreground repainting.</p>
<h2>The real problem</h2>
<p>Once I mentioned the foreground-background thing to Claude, it came up with an alternative: maybe the issue was not in my terminal, but instead in my editor. To test this, it suggested using <code>ls --color=auto</code> in a terminal to print the current directory in color and then invert the theme. That worked properly, there was no weird light-on-light issue. That ruled out my terminal.</p>
<p>Now with Nvim it was simple. Just check <code>:set background?</code> and see if it was toggling correctly or not. And sure enough, it was not: it remained <code>dark</code> when the system went to <code>light</code>, <code>light</code> when it should be <code>dark</code>. And those random events I mentioned earlier flipped the background, only then was it properly displaying things.</p>
<p>A simple way to fix this would be to tell Nvim in my toggle script to also toggle it's <code>background</code> property. Basically, I needed a way to control Nvim programmatically, and a little back-and-forth with Claude told me that RPC (Remote Procedure Call) is how it is done.</p>
<h2>Doing it myself</h2>
<p>At this point, I could ask Claude to write an RPC script for me to get everything working, but this was too good an opportunity to pass. So I set about understanding how Nvim RPC works, dug into some NeoVim documentation, and came across <code>nvr</code>, an open-source application that can be used for RPCing Nvim instances.</p>
<p>It does this by sending commands to the running Nvim server. Every Nvim process creates an RPC Socket, accessible by typing <code>:v:servername</code>. A bit of experimentation told me that all such sockets got created in <code>/run/user/&lt;user_id&gt;</code> and all Nvim sockets have a naming convention of <code>nvim.x.y</code>. And so, what I had to do was iterate through this folder, find all Nvim sockets, and RPC them to toggle their <code>background</code> property.</p>
<p>So in the same script, I added this new piece of code:</p>
<pre><code class="language-bash"># NeoVim: Use `nvr` to set the theme
directory=/run/user/1000/
pushd $directory &gt;/dev/null

for f in *; do
  if [[ $f =~ ^nvim.* ]]; then
    nvr --servername $f -c "set background=$new"
  fi
done

popd &gt;/dev/null
</code></pre>
<p>Not the most elegant way to do it, but this worked. At this point, I gave it to Claude who suggested a few refinements:</p>
<ol>
<li>Instead of hardcoding <code>/run/user/1000</code>, we should use <code>$XDG_RUNTIME</code></li>
<li>No need for this push and pop drama, and testing the string inside the loop; this works:</li>
</ol>
<pre><code class="language-bash">for f in "XDG_RUNTIME"/nvim.*; do
  nvr --servername $f -c "set background=$new"
done
</code></pre>
<ol start="3">
<li>Adding a check if <code>f</code> is a socket is useful: inside the loop, before <code>nvr</code>, we add this:</li>
</ol>
<pre><code class="language-bash">[ -S "$f" ] | continue
</code></pre>
<h2>Final Touches</h2>
<p>There was another suggestion: to set a <code>timeout</code> for <code>nvr</code> so that it doesn't hang everyone up. Initially I thought this was no big deal and ignored it, but I noticed that while testing, if an error came up, the script hung up. So in the end, I added it:</p>
<pre><code class="language-bash">timeout 2 nvr --servername "$f" -c "set background=$new"
</code></pre>
<p>Later, I was curious if it could identify the sockets correctly so I made it <code>echo</code> out the socket name in the loop. Here's what I found if I had three nvim editors open:</p>
<pre><code class="language-bash">$ ./.local/bin/toggle-theme
[/run/user/1000/nvim.*]
[/run/user/1000/nvim.38103.0]
[/run/user/1000/nvim.38529.0]
[/run/user/1000/nvim.39382.0]
</code></pre>
<p>Where did the <code>*</code> come from? I was using it in the loop as a globbing character, not a literal one. A little digging told me that it was <code>nvr</code> itself. While testing, I had at one time called <code>nvr</code> on the literal <code>nvim.*</code> and that process was still alive.</p>
<p>So the thing to do was to first delete that process</p>
<pre><code class="language-bash">rm '/run/user/1000/nvim.*'         # single quotes are essential
</code></pre>
<p>Without the single quotes, it will treat <code>*</code> as a glob character and delete everything beginning with <code>nvim.</code>. Not what we want!</p>
<p>To prevent this sort of thing from happening again, we must instruct <code>nvr</code>:</p>
<pre><code class="language-bash">timeout 2 nvr --nostart --servername "$f" -c "set background=$new"
</code></pre>
<p>The <code>--nostart</code> flag tells <code>nvr</code> not to start its own server if it is not able to find socket <code>f</code>.</p>
<p>Later I also used <code>feh</code> to change my wallpaper, light or dark depending on the system mode. With that fixed, the final script became:</p>
<pre><code class="language-bash">#!/usr/bin/env bash
state=~/.cache/theme-mode
mode=$(cat "$state" 2&gt;/dev/null || echo dark)
[ "$mode" = dark ] &amp;&amp; new=light || new=dark

if [ "$new" = light ]; then
  gsettings set org.gnome.desktop.interface color-scheme prefer-light
  gsettings set org.gnome.desktop.interface gtk-theme Adwaita
else
  gsettings set org.gnome.desktop.interface color-scheme prefer-dark
  gsettings set org.gnome.desktop.interface gtk-theme Adwaita-dark
fi

# Terminal: point a symlink at light.toml or dark.toml
ln -sf ~/.config/alacritty/$new.toml ~/.config/alacritty/theme.toml

# # User `nvr` to change nvim theme
for sock in "$XDG_RUNTIME_DIR"/nvim.*; do
  [ -S "$sock" ] || continue
  timeout 2 nvr --nostart --servername "$sock" -c "set background=$new"
done

# change wallpaper
feh --bg-fill ~/Pictures/Wallpaper/$new.jpg

echo "$new" &gt; "$state"
</code></pre>
<p>And this works like a charm!</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aef22822f2f28abe7351fad/bbedb6e0-b47d-4af2-9760-53ce638d6c9c.png" alt="final-state" style="display:block;margin:0 auto" />

<p>Fixed :D. Also showing off my terminal because why not</p>
<hr />
<p>Thanks for reading all the way till here! If you like my work, consider supporting me: <a href="https://buymeacoffee.com/ashketchumx">https://buymeacoffee.com/ashketchumx</a></p>
]]></content:encoded></item></channel></rss>