<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Plugins on onelegdave.dev</title><link>https://www.onelegdave.dev/tags/plugins/</link><description>Recent posts in Plugins on onelegdave.dev</description><language>en-us</language><generator>Hugo</generator><ttl>60</ttl><image><url>https://www.onelegdave.dev/images/avatar.jpg</url><title>Plugins on onelegdave.dev</title><link>https://www.onelegdave.dev/</link></image><lastBuildDate>Fri, 11 Sep 2026 14:00:00 -0700</lastBuildDate><atom:link href="https://www.onelegdave.dev/tags/plugins/index.xml" rel="self" type="application/rss+xml"/><item><title>The Droid You Are Looking For</title><link>https://www.onelegdave.dev/posts/omadroid/</link><guid isPermaLink="true">https://www.onelegdave.dev/posts/omadroid/</guid><pubDate>Fri, 11 Sep 2026 14:00:00 -0700</pubDate><dc:creator>OneLegDave</dc:creator><category>Plugins</category><category>Linux</category><description>My second Omarchy plugin mirrors an Android phone onto the desktop. Most of the actual work was deciding when software is allowed to guess on your behalf, and when it absolutely is not.</description><media:content url="https://www.onelegdave.dev/images/posts/omadroid.jpg" medium="image" type="image/jpeg" width="1200" height="630"/><media:thumbnail url="https://www.onelegdave.dev/images/posts/omadroid.jpg" width="1200" height="630"/><content:encoded>&lt;p&gt;&lt;img src="https://www.onelegdave.dev/images/posts/omadroid.jpg" alt="The Droid You Are Looking For" width="1200" height="630"&gt;&lt;/p&gt;&lt;p&gt;Second plugin. This one puts your Android phone on your desktop: mirrored screen, mouse and keyboard control, no root, no Samsung account, no paid tier, no separate app installed on the phone begging for permissions it doesn&amp;rsquo;t need.&lt;/p&gt;&#10;&lt;p&gt;Under the hood it&amp;rsquo;s scrcpy doing the actual mirroring, because scrcpy is excellent and rewriting it would be idiotic. KDE Connect is optional on top for battery status and Ring. What I built is the part that makes those two things pleasant to live with instead of a pile of terminal commands you look up every single time.&lt;/p&gt;&#10;&lt;p&gt;Currently sitting in the Omarchy marketplace queue waiting on security review, which feels appropriate given how much of this post is about security decisions.&lt;/p&gt;&#10;&lt;h2 id="the-thing-that-took-the-most-thought"&gt;The thing that took the most thought&lt;/h2&gt;&#10;&lt;p&gt;Getting a phone mirrored is not hard. Getting a phone mirrored is &lt;code&gt;scrcpy&lt;/code&gt; and about four minutes of reading. The hard part is everything around it, and specifically one question that came up over and over in different costumes: when is this software allowed to guess?&lt;/p&gt;&#10;&lt;p&gt;Take phone identity. Multiple connections can point at the same physical phone. USB and Wi-Fi at the same time, IPv4 and IPv6, an address that changed when your router handed out a new lease. You want those grouped into one card, obviously, because they&amp;rsquo;re one phone.&lt;/p&gt;&#10;&lt;p&gt;But grouping is a claim. If I merge two connections into one card, I&amp;rsquo;m asserting they&amp;rsquo;re the same device, and if I&amp;rsquo;m wrong your input goes somewhere you didn&amp;rsquo;t intend. So merging only happens on hardware identity that Android actually reports. Matching model names never merges anything. Matching nicknames never merges anything. Two Pixels with the same nickname are two phones until the hardware says otherwise, because &amp;ldquo;they&amp;rsquo;re both called Pixel&amp;rdquo; is a coincidence, not evidence.&lt;/p&gt;&#10;&lt;p&gt;If Android refuses the identity query and discovery can&amp;rsquo;t work it out, the connection stays separate as an unresolved entry rather than getting quietly filed under whichever phone looks closest. An unmerged duplicate is a minor annoyance. A wrong merge sends your keystrokes to the wrong device.&lt;/p&gt;&#10;&lt;h2 id="reconnecting-without-being-creepy-about-it"&gt;Reconnecting without being creepy about it&lt;/h2&gt;&#10;&lt;p&gt;Remembered phones reconnect automatically when discovery advertises the same identity, even if the address and port both changed. That&amp;rsquo;s genuinely convenient and it&amp;rsquo;s the behavior you want.&lt;/p&gt;&#10;&lt;p&gt;What it does not do is automatically pair with phones it&amp;rsquo;s never seen. It does not start a mirror session on its own. Retries are capped at once per address every 30 seconds, and you can turn the whole thing off. After a successful pairing there&amp;rsquo;s a ten minute window where discovery can finish the connection, and then it stops.&lt;/p&gt;&#10;&lt;p&gt;The difference matters. Reconnecting to a phone you already explicitly authorized is completing something you started. Pairing with an unknown phone because it happened to show up on the network is a decision nobody asked it to make.&lt;/p&gt;&#10;&lt;p&gt;Same principle as the GPU selection in my last plugin: auto mode gets to be clever, explicit choices get respected exactly as made.&lt;/p&gt;&#10;&lt;h2 id="wake-is-not-unlock"&gt;Wake is not unlock&lt;/h2&gt;&#10;&lt;p&gt;There&amp;rsquo;s a Wake/unlock button for when the phone&amp;rsquo;s asleep. It sends Android&amp;rsquo;s wake command and asks Android to show its normal unlock prompt. Then it stops and you unlock the phone yourself.&lt;/p&gt;&#10;&lt;p&gt;It does not submit credentials. It does not bypass a secure lock. It cannot, and it shouldn&amp;rsquo;t be able to, and I&amp;rsquo;m not interested in building the version that tries. Lock screens and capture-protected apps keep enforcing their restrictions through the mirror exactly as they should, and if that&amp;rsquo;s inconvenient in some specific app, good. That&amp;rsquo;s the lock working.&lt;/p&gt;&#10;&lt;h2 id="what-it-does-not-do-deliberately"&gt;What it does not do, deliberately&lt;/h2&gt;&#10;&lt;p&gt;No telemetry. No advertising. No cloud relay. No automatic update check. No crash upload. Nothing phones home, ever.&lt;/p&gt;&#10;&lt;p&gt;Runtime phone endpoints are restricted to private local-network addresses. There&amp;rsquo;s no support for pointing it at a public Internet endpoint or overriding the remote ADB server, because a phone-mirroring tool that can be aimed at an arbitrary remote host is a substantially different and worse thing than one that can only talk to your own LAN.&lt;/p&gt;&#10;&lt;p&gt;Automatic clipboard sharing is off. You paste with an explicit keystroke or it doesn&amp;rsquo;t happen. Pairing codes go through standard input, get cleared from the panel, and never touch disk or appear in a process command line where anything reading the process list could scoop them up.&lt;/p&gt;&#10;&lt;p&gt;Nothing gets installed just because you opened the panel. There are Install buttons for missing tools, and they do exactly nothing until clicked.&lt;/p&gt;&#10;&lt;p&gt;And it&amp;rsquo;s honest about what authorization actually means: ADB pairing gives this computer debugging access to your phone, disabling the plugin does not revoke that, and if you stop trusting a machine you need to forget it in Android&amp;rsquo;s own settings. That&amp;rsquo;s in the README rather than buried, because someone who installs this should understand what they just handed out.&lt;/p&gt;&#10;&lt;h2 id="the-bit-im-quietly-pleased-with"&gt;The bit I&amp;rsquo;m quietly pleased with&lt;/h2&gt;&#10;&lt;p&gt;The four-step connect guide. Desktop, Phone, Pair, Ready. Every one of those steps can fail in its own specific way and each failure gets its own specific message instead of a generic &amp;ldquo;could not connect&amp;rdquo; that leaves you guessing which of five things went wrong.&lt;/p&gt;&#10;&lt;p&gt;Wireless pairing on Android has a genuine trap in it: the pairing port and the connection port are different numbers, and nothing about the Android UI makes that obvious. People get it wrong constantly and then have no idea why nothing works. So the plugin calls it out directly, in bold, right where you&amp;rsquo;d hit it.&lt;/p&gt;&#10;&lt;p&gt;That&amp;rsquo;s not clever engineering. It&amp;rsquo;s just having actually used the thing and noticed where I personally got stuck.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;omarchy plugin add https://github.com/onelegdave/omadroid --enable&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Waiting on security review for the marketplace. In the meantime it installs fine straight from the repo, and every command it can run and every network endpoint it can reach is inventoried in SECURITY.md if you&amp;rsquo;d rather check before trusting it. Which, for something that gets debugging access to your phone, you probably should.&lt;/p&gt;&#10;</content:encoded></item><item><title>Zero Is a Lie</title><link>https://www.onelegdave.dev/posts/zero-is-a-lie/</link><guid isPermaLink="true">https://www.onelegdave.dev/posts/zero-is-a-lie/</guid><pubDate>Sun, 06 Sep 2026 11:00:00 -0700</pubDate><dc:creator>OneLegDave</dc:creator><category>Plugins</category><category>Linux</category><description>My first published Omarchy plugin started as a lazy wrapper around btop and ended with me ripping btop out entirely. Along the way I had to decide what a system monitor is allowed to say when it genuinely does not know something.</description><media:content url="https://www.onelegdave.dev/images/posts/zero-is-a-lie.jpg" medium="image" type="image/jpeg" width="1200" height="630"/><media:thumbnail url="https://www.onelegdave.dev/images/posts/zero-is-a-lie.jpg" width="1200" height="630"/><content:encoded>&lt;p&gt;&lt;img src="https://www.onelegdave.dev/images/posts/zero-is-a-lie.jpg" alt="Zero Is a Lie" width="1200" height="630"&gt;&lt;/p&gt;&lt;p&gt;I wanted a system monitor in my bar that actually matched my theme instead of sitting there as a mismatched floating terminal window I had to manually position every single time I logged in. The honest first version of this project was embarrassing: wrap btop, throw it in a panel, call it done. It even shipped under that name internally. Then I actually sat with it for a week and got annoyed enough to rebuild the whole thing properly.&lt;/p&gt;&#10;&lt;p&gt;What exists now is System QuikView, a native Omarchy Shell plugin. No btop. No root service. No cloud account, no pip packages, nothing phoning home. It reads Linux telemetry directly off &lt;code&gt;/proc&lt;/code&gt; and sysfs, renders it through Quickshell&amp;rsquo;s own QML, and pulls every single color it displays from whatever Omarchy theme happens to be active. Switch themes, the widget updates live, because it was never allowed to hardcode a color to begin with.&lt;/p&gt;&#10;&lt;h2 id="the-migration-path-i-owe-past-me"&gt;The migration path I owe past me&lt;/h2&gt;&#10;&lt;p&gt;The installer still knows how to find and migrate that original btop-wrapper prototype: same placement, same preferences, straight upgrade path, no starting over. Small thing, but I&amp;rsquo;m glad I didn&amp;rsquo;t just delete the evidence and pretend the finished version sprang out fully formed. It didn&amp;rsquo;t. It started as duct tape around someone else&amp;rsquo;s tool and got replaced piece by piece until none of the original tape was left.&lt;/p&gt;&#10;&lt;h2 id="the-design-decision-that-actually-took-thought"&gt;The design decision that actually took thought&lt;/h2&gt;&#10;&lt;p&gt;Most of the surface area here is fairly mechanical: read a number, put it on a graph, color it by theme. The one part that made me stop and actually think came from GPU handling, and specifically from a question that sounds trivial until you sit with it: what does the widget show when a sensor genuinely has no reading?&lt;/p&gt;&#10;&lt;p&gt;The lazy answer is zero. Zero is wrong, and it&amp;rsquo;s wrong in a way that&amp;rsquo;s actively dangerous for exactly the same reason a blank medication list is dangerous. Zero degrees is not &amp;ldquo;no reading available,&amp;rdquo; it&amp;rsquo;s a specific, false, freezing-cold claim about a piece of hardware that might currently be on fire. A missing value and an actual value of zero are two completely different facts, and collapsing them into the same digit is a decision to lie every single time the sensor doesn&amp;rsquo;t answer.&lt;/p&gt;&#10;&lt;p&gt;So a missing sensor shows a plain dash. Not a number pretending to be data. An honest admission that nothing came back.&lt;/p&gt;&#10;&lt;p&gt;That same principle runs through the multi-GPU handling too. If you explicitly pick a specific GPU by name and it disconnects, the widget does not quietly fail over to whichever GPU happens to still be plugged in and keep drawing a graph like nothing happened. It shows that reading as unavailable and waits for you to notice, because silently substituting a different device&amp;rsquo;s numbers under the same label is its own kind of lie, just dressed up as helpfulness.&lt;/p&gt;&#10;&lt;h2 id="auto-mode-gets-to-be-smart-explicit-mode-does-not"&gt;Auto mode gets to be smart, explicit mode does not&lt;/h2&gt;&#10;&lt;p&gt;There&amp;rsquo;s a real tension between &amp;ldquo;just work&amp;rdquo; and &amp;ldquo;never guess wrong,&amp;rdquo; so I split it into two modes on purpose instead of picking one philosophy and forcing it everywhere.&lt;/p&gt;&#10;&lt;p&gt;Auto mode is allowed to be clever: prefer a dedicated GPU when one&amp;rsquo;s available and reporting, fall back to whatever else exists otherwise, and it can pick different actual devices for usage versus temperature versus VRAM if that&amp;rsquo;s genuinely what&amp;rsquo;s available. That&amp;rsquo;s a real convenience and I don&amp;rsquo;t think it&amp;rsquo;s dishonest, because nobody who picks &amp;ldquo;auto&amp;rdquo; is claiming to know or care which specific piece of silicon answers.&lt;/p&gt;&#10;&lt;p&gt;But the moment you pin a specific device by name, you&amp;rsquo;ve made a claim, and the widget&amp;rsquo;s job flips from being helpful to being accurate to that claim. It doesn&amp;rsquo;t get to quietly reinterpret &amp;ldquo;show me this GPU&amp;rdquo; as &amp;ldquo;show me a GPU&amp;rdquo; just because the first one went away. Same underlying data source, two completely different obligations depending on whether a human explicitly chose a specific thing or asked for the software&amp;rsquo;s best guess.&lt;/p&gt;&#10;&lt;h2 id="what-actually-shipped"&gt;What actually shipped&lt;/h2&gt;&#10;&lt;p&gt;Bar reading you can set to CPU, memory, GPU usage, temperature, VRAM, network speed, disk usage, or nothing but an icon. Click it, get a full dashboard: history graphs, per-process ranking by CPU then memory, temperature gauges that use the hardware&amp;rsquo;s own reported warning thresholds when it has them and fall back to a plain labeled scale when it doesn&amp;rsquo;t. Sections reorder and collapse, and that layout survives a restart instead of resetting itself every time like it&amp;rsquo;s personally offended you touched it.&lt;/p&gt;&#10;&lt;p&gt;Tests cover the boring-but-load-bearing stuff: no GPU present, one GPU, several GPUs, a GPU that vanishes mid-session, missing readings across the board. &lt;code&gt;python3 -m unittest&lt;/code&gt;, a separate Node suite for the selection logic in &lt;code&gt;Model.js&lt;/code&gt;, a QML linter, and a plugin validator Omarchy itself ships to catch structural mistakes before they become someone else&amp;rsquo;s bug report.&lt;/p&gt;&#10;&lt;p&gt;Installed like any other Omarchy plugin:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;omarchy plugin add https://github.com/onelegdave/system-quikview.git --enable&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;First one of these I&amp;rsquo;ve actually finished and put out publicly rather than leaving half-built in a folder labeled &amp;ldquo;someday.&amp;rdquo; The btop wrapper would have shipped faster. It also would have kept lying to me about GPU temperature the first time a sensor didn&amp;rsquo;t answer, and I&amp;rsquo;d rather have spent the extra week than ship a monitor I can&amp;rsquo;t actually trust the numbers on.&lt;/p&gt;&#10;</content:encoded></item></channel></rss>