A bar widget for Omarchy 4.x (Quattro) that puts USBGuard where you can actually use it: a shield in the bar that turns urgent the moment a USB device is blocked, and a panel that lets you allow, block, or reject any device with one click.
- Shield in the bar. Themed like everything else; it takes the urgent color and grows a count as soon as a device is sitting blocked.
- Live, not polled. State comes from
usbguard watch, so the bar reacts the instant a device is plugged in. A 30 second poll backs it up, and the watch reconnects with backoff whenever the daemon goes away. - A notification when something is blocked, with a click that opens the panel on the device in question.
- Allow / block / reject per device. Left click applies the change to this
session; right click writes a permanent rule (
usbguard ... -p) so it survives a reboot. - Device interface classes, so you can see that the stick you just plugged in also claims to be a keyboard before you allow it.
- No sudo, no polkit. The widget talks to
usbguard-daemonover its IPC socket as your own user. The one privileged thing it offers — editing the rule file — is handed tosudoeditin a terminal.
- Omarchy 4.x (Quattro), i.e. the Quickshell-based
omarchy-shell usbguard, withusbguard-daemonrunning and your user inIPCAllowedUsers
setup.sh does all of that for you.
Everything the plugin shells out to, and why:
| Command | Where | Privilege | Why |
|---|---|---|---|
usbguard list-devices / watch |
widget, continuously | none — your user, over the daemon's IPC socket | Read device state and events |
usbguard allow-device / block-device / reject-device |
widget, on your click | none — same IPC socket | Apply a policy decision |
omarchy-notification-send |
widget, when a device is blocked | none | Raise the notification |
omarchy-launch-floating-terminal-with-presentation |
widget, Rules / Run setup buttons | none itself | Open a terminal for the two commands below |
sudoedit /etc/usbguard/rules.conf |
that terminal, Rules button | root, interactive | Edit the rule file |
setup.sh |
that terminal, or by hand | root, interactive, and only what it prints | See below |
The widget itself never elevates and never runs a second Quickshell process.
Installing the plugin runs no code at all — omarchy plugin add only clones
files — so setup.sh runs when, and only when, you run it.
setup.sh uses sudo for: pacman -S usbguard; writing
/etc/usbguard/rules.conf (the existing file is backed up first, and kept
untouched unless you pass --regenerate-policy); five keys in
/etc/usbguard/usbguard-daemon.conf (IPCAllowedUsers, which it merges rather
than replaces, plus the four policy targets listed above);
/etc/usbguard/.omaguard-setup-state, the record of what it changed; and
systemctl enable --now usbguard.service. It touches nothing under $HOME,
installs no sudoers rule, and asks before any of it. All of it is reversible
with setup.sh --undo.
omarchy plugin add https://github.com/fcsonline/omaguard --enable
~/.config/omarchy/plugins/io.github.fcsonline.omaguard/setup.shThe plugin lands in ~/.config/omarchy/plugins/io.github.fcsonline.omaguard/ (the
directory is named after the manifest id, not the repo).
Place the widget wherever you want it:
omarchy bar move io.github.fcsonline.omaguard --section right- Installs
usbguard. - Generates the baseline policy from the devices connected right now, so
the hardware you are using keeps working (
usbguard generate-policy). - Adds your user to
IPCAllowedUsersin/etc/usbguard/usbguard-daemon.conf, which is what lets the widget work without elevation. - Sets
ImplicitPolicyTarget=blockandInsertedDevicePolicy=apply-policy: anything plugged in from now on is blocked until you allow it. - Enables and starts
usbguard.service.
It is idempotent — run it again after plugging in hardware you want in the
baseline, with --regenerate-policy to rebuild the rule file (the old one is
backed up first). --yes skips the prompt, --user <name> grants IPC access to
someone other than the invoking user.
Every file it touches is copied aside before the first edit, and what it did is
recorded in /etc/usbguard/.omaguard-setup-state, so setup.sh --undo puts the
machine back: the daemon config restored from that copy, the policy file
restored or removed, the service stopped and disabled. It leaves the usbguard
package alone and prints the pacman -Rns line instead of running it.
Plug in everything you rely on before running it, and keep a wired keyboard attached while you test. USBGuard's whole job is to stop the kernel from authorizing devices your policy does not cover, and that includes the keyboard you would need to undo it.
setup.sh deliberately leaves PresentDevicePolicy=keep, so devices that are
already connected when the daemon starts are never de-authorized — a policy
generated at an awkward moment cannot lock you out at boot. Once you trust your
rule file, switch it to apply-policy for a stricter setup.
Bar
| Action | What happens |
|---|---|
| Left click | Open/close the panel |
| Right click | Re-read the device list |
Panel
| Key | Action |
|---|---|
j / k, arrows |
Move the cursor |
Enter / Space |
Toggle the selected device between allowed and blocked |
a / A |
Allow for this session / permanently |
b / B |
Block for this session / permanently |
x |
Reject the selected device |
r |
Refresh |
e |
Edit the rule file with sudoedit |
Esc |
Close |
Mouse: hover moves the cursor, but only the three chips on a row change anything — a row click never changes a policy by accident. Left click on a chip acts for this session, right click writes a permanent rule. The chip matching a device's current state is shown as selected, so right-clicking a highlighted Allow is how you make a device you already trust permanent.
The dot on the left of each row is the device's current target: accent for allowed, urgent for blocked, dim for rejected. A device whose interface list claims both HID and mass storage gets its detail line in the urgent color — that combination is the classic BadUSB shape.
Set these per widget instance in ~/.config/omarchy/shell.json, or through
Setup > Bar:
| Key | Default | What it does |
|---|---|---|
refreshIntervalSec |
30 |
Backstop poll interval; events arrive live regardless |
notifyOnBlocked |
true |
Raise a notification when an inserted device is blocked |
rulesPath |
/etc/usbguard/rules.conf |
What the Rules button opens |
{ "id": "io.github.fcsonline.omaguard", "refreshIntervalSec": 15, "notifyOnBlocked": true }The widget registers its own IPC target, so scripts and keybinds can drive it:
omarchy-shell io.github.fcsonline.omaguard toggle # open/close the panel
omarchy-shell io.github.fcsonline.omaguard refresh
omarchy-shell io.github.fcsonline.omaguard status # JSON: counts and daemon health
omarchy-shell io.github.fcsonline.omaguard allow 9 # by usbguard device id
omarchy-shell io.github.fcsonline.omaguard block 9
omarchy-shell io.github.fcsonline.omaguard reject 9Only digits are accepted as a device id; usbguard allow-device also takes a
whole rule, and this never passes one on.
A USB device supplies its own name and serial. Both are attacker-controlled strings, so:
- everything that reaches a label is stripped of control characters and
truncated (
Model.sanitize) - every
Textin the panel is pinned toText.PlainText. QML's default isText.AutoText, which sniffs a string and switches to rich text on its own — a device that called itself<img src="http://…">would otherwise have the panel fetch it - the rule tokenizer honours usbguard's backslash escaping, so a device whose
name contains a
"cannot end the value early and push the rest of its name back through the parser as attribute/value pairs. Before that, a stick namedUltra Fit"dropped its ownwith-interfacelist — and the BadUSB warning that depends on it — out of the panel - notifications go out through
omarchy-notification-send, which passes the text to the notification daemon as typed D-Bus parameters rather than as command-line arguments; the strings are stripped of leading dashes on the way in, and HTML-escaped, because a notification body is markup - notifications are capped as a whole, not just per device, so a device that re-enumerates under a fresh identity cannot spray critical toasts
- device ids are validated as digits before they reach a command line
- paths handed to the terminal launcher are shell-quoted: that launcher joins
its arguments with
$*and runs the result throughbash -c
The plugin itself runs unsandboxed inside omarchy-shell, like every Omarchy
plugin. Read it before you enable it.
~/.config/omarchy/plugins/io.github.fcsonline.omaguard/setup.sh --undo
omarchy plugin remove io.github.fcsonline.omaguard--undo restores /etc/usbguard/usbguard-daemon.conf (including the
IPCAllowedUsers entry setup.sh added), restores or removes
/etc/usbguard/rules.conf, and stops and disables usbguard.service. Run it
before removing the plugin, while the script is still on disk. To drop the
package too:
sudo pacman -Rns usbguardomarchy plugin validate .
qmllint -I "$OMARCHY_PATH/shell" Panel.qml Service.qml # qs.* imports resolve from there
qs log -p "$OMARCHY_PATH/shell" --tail 100Saving a file under ~/.config/omarchy/plugins/ reloads the plugin
automatically; omarchy-shell shell rescanPlugins forces it.
Layout:
| File | What it is |
|---|---|
Panel.qml |
Bar widget and panel (the barWidget entry point) |
Service.qml |
usbguard CLI plumbing: watch stream, polling, actions |
Model.js |
Rule/event parsing and presentation helpers |
setup.sh |
Installs and configures USBGuard |
MIT — see LICENSE.
