A normal Omarchy plugin (Quickshell / QtQuick QML) that manages the link with OhmLauncher (Android): discovers the peer on the LAN (mDNS / QR / Bluetooth), shares files, syncs clipboard and themes, backs up photos, and shares the screen between the phone and the desktop.
This is a standalone plugin repository. The Android side (the launcher that exposes the contract) lives at https://github.com/avillagran/ohm-launcher.
Install it like any other Omarchy plugin — clone it into your Omarchy plugins directory and enable it from the Omarchy plugin list:
git clone https://github.com/avillagran/omarchy-link \
~/.config/omarchy/plugins/cl.villagranquiroz.omarchy-link
omarchy-restart-shell(The folder name cl.villagranquiroz.omarchy-link matches the plugin id in
manifest.json.) After enabling cl.villagranquiroz.omarchy-link, the bar
widget appears in the Omarchy bar and the panel opens from the widget button.
Update it later with:
git -C ~/.config/omarchy/plugins/cl.villagranquiroz.omarchy-link pull --ff-only
omarchy-restart-shellmanifest.json # id, kinds: ["bar-widget"], entryPoints.barWidget
BarWidget.qml # manifest entry point (root type `BarWidget`)
Panel.qml # loaded internally via Loader (root type `Panel`)
link_server.py # local link state, theme sync, and message forwarding
omarchy_notify.py # CLI sender for Hermes and desktop automation
README.md # this file
TESTING.md # end-to-end verification guide
examples/minimal/ # minimal BarWidget+Panel starter
{
"schemaVersion": 1,
"id": "cl.villagranquiroz.omarchy-link",
"name": "Omarchy Link",
"version": "0.0.1",
"author": "Ohm Launcher",
"license": "MIT",
"kinds": ["bar-widget"],
"entryPoints": { "barWidget": "BarWidget.qml" }
}Note: the panel is not a separate manifest kind. Omarchy loads
Panel.qmlinternally fromBarWidget.qmlvia aLoader, exactly like the official clock example. Do not usekinds: ["panel"]here.
Root type is BarWidget (from qs.Ui). It must expose:
open() / close() / toggle() / closeForPopoutSwitch()readonly property bool openedreadonly property bool popoutSwitchClosingonBarChanged: injectPanel()and an internalLoader { source: "Panel.qml" }
Root type is Panel with manageIpc: false. It receives bar, anchorItem,
hostWidget from the BarWidget and wraps content in a KeyboardPanel +
PanelKeyCatcher (from qs.Commons / qs.Ui). Example skeleton:
import QtQuick
import Quickshell
import qs.Commons
import qs.Ui
Panel {
id: root
moduleName: "cl.villagranquiroz.omarchy-link"
manageIpc: false
property var anchorItem: null
property var hostWidget: null
function open() { controller.show() }
function close() { controller.hide() }
KeyboardPanel {
id: panel
anchorItem: root.anchorItem
owner: root.hostWidget || root
bar: root.bar
open: root.opened
focusTarget: keyCatcher
PanelKeyCatcher {
id: keyCatcher
anchors.fill: parent
onCloseRequested: root.close()
}
// ... your UI ...
}
}BarWidget,WidgetButton,Panel,KeyboardPanel:import qs.UiPanelKeyCatcher:import qs.CommonsSystemClock,Style:import Quickshell(+qs.UiforStyle)Scope/PanelWindoware Quickshell-core types — Omarchy plugins use theBarWidget/Panelwrappers instead. Use those wrappers.
The phone runs an HTTP+WS server on port 8753 (LAN mode). This plugin:
- calls
setPeer("ohm://<phone-ip>:8753")thenconnect()(discovers viaGET /omarchy/discover), or - (optional) auto-discovers via mDNS
_ohm._tcpusing a helper (README below).
The panel displays a QR omarchy://<pc-ip>:8753?id=omarchy-pc.
Scan it with the phone's camera (the normal system camera app). Android
routes the omarchy:// intent to OhmLauncher, which then connects to this PC.
To receive the phone's calls, this plugin must expose the same contract
over HTTP+WS. Pure QML has no HttpServer; use a small helper:
- a C++
QHttpServer(Qt 6) exposing/omarchy/*, or - a Python/Node sidecar process, or
QtWebSocketsfor the event channel (/omarchy/ws).
| Method | Path | Notes |
|---|---|---|
| GET | /omarchy/discover |
{name, model, lan_ip, port, capabilities} |
| GET | /omarchy/clipboard |
{text} |
| PUT | /omarchy/clipboard |
body {text} |
| GET | /omarchy/theme |
{colors:{...}} |
| PUT | /omarchy/theme |
applies colors |
| POST | /omarchy/file (multipart) |
receives a file |
| GET | /omarchy/file?path= |
downloads a file |
| POST | /omarchy/screen/start | /stop |
starts/stops screen share |
| POST | /omarchy/photos/backup |
lists DCIM photos (peer downloads via /file) |
| POST | /omarchy/notify |
sends a message to the phone's OmarchyNotify widget |
| WS | /omarchy/ws |
events: peer_hello, clipboard_changed |
Screen frames (after /omarchy/screen/start) arrive over WS as
{ "type": "screen_frame", "data": "<base64 JPEG>" }.
The local link_server.py exposes POST http://127.0.0.1:8753/omarchy/notify.
It discovers or reuses the connected OhmLauncher phone and forwards the bounded
JSON payload to the phone. The OmarchyNotify launcher widget updates as soon
as the message arrives.
python3 omarchy_notify.py \
--title "Hermes" \
--source omarchy-hermes \
--level success \
--channel agent \
"Build and tests completed"The message may also be piped on stdin, which avoids shell quoting issues:
printf '%s\n' "Deployment finished" | python3 omarchy_notify.py --title HermesAccepted fields are id, title, message, source, level, channel, and
timestamp. Levels are info, success, warning, and error. The helper
defaults source to omarchy-hermes; it exits nonzero if no phone receives the
message. Message bodies are never written to the Omarchy Link log.
Quickshell/Omarchy has no built-in QR painter. Register a
QQuickImageProvider named qrcode that renders the data string, then use:
Image { source: "image://qrcode/omarchy://192.168.1.50:8753?id=omarchy-pc" }Minimal C++ sketch (Qt 6):
#include <QQuickImageProvider>
#include <QImage>
// build a QImage from the data using any QR lib (e.g. qrcodegen), then:
class QrCodeProvider : public QQuickImageProvider {
public:
QrCodeProvider() : QQuickImageProvider(Image) {}
QImage requestImage(const QString &id, QSize *, const QSize &) override {
return renderQr(id.toUtf8()); // id is the string after "image://qrcode/"
}
};
// in Quickshell init:
engine->addImageProvider("qrcode", new QrCodeProvider);See TESTING.md for the full end-to-end verification guide (smoke test, phone round-trip, and curl checks against the contract).
See examples/minimal for the smallest valid
BarWidget.qml + Panel.qml you can copy to start a new Omarchy plugin.