Files
smoido f68ca08009 Omarchy bar widget: show the active provider, and switch from it
A Quickshell plugin for the Omarchy shell (v4's bar is Quickshell, not Waybar).
The icon is the mode; clicking it opens a panel that switches.

State comes from watching health.json rather than polling the CLI, so the widget
costs nothing while idle and a switch made in a terminal shows up in the bar on
its own. state.json is the fallback, because it exists from install onward where
health.json only appears once claude-mode has run.

The marks are the providers' real logos, drawn as vector paths through
QtQuick.Shapes rather than set as font glyphs - three of the four have no Nerd
Font pictograph at all, and paths take the bar's foreground colour and follow
the theme. Claude, OpenRouter and LM Studio from simple-icons, Z.AI from
lobe-icons; trademarks belong to their owners. Zhipu's mark was rejected for
Z.AI: it is a dense lattice that is unreadable below ~40px.

Rendering them at 13px took three fixes. A layer rasterises the Shape at its own
size and then scales the texture, so a 24px buffer minified to 13 resampled two
pixels into one and the Claude burst lost rays; without the layer the scale is a
transform on the geometry and rasterisation happens once, at final resolution.
CurveRenderer replaces the tessellating default. And the size is forced odd,
because a radially symmetric mark puts its vertical and horizontal arms on the
centre line, which is a pixel centre at odd sizes and the seam between two
pixels at even ones, where each arm splits its coverage and greys out. Measured
against a cairo render at the same size, the result is now identical.

Sizing is measured rather than guessed. Every stock glyph in this bar paints
11px of ink; the marks fill their box instead of carrying a font's padding, so
the box is the smaller number. Each mark also carries an optical scale from two
measurements of a 200px render - LM Studio's filled container covers 69% of its
box against ~38% for the others, and Z.AI and OpenRouter are wide-but-short
marks spanning ~84% of the box height.

Choosing a target does not switch immediately. It runs the CLI's own preflight,
and if that refuses, the panel names what is missing and offers the fix - a
terminal for the hidden key prompt, a re-check once a server is up, or the
server form. If sessions are running it lists them by terminal and directory,
marks any mid-request, and asks whether to restart them, close them, or leave
them. The session action is applied strictly after the write, since restarting
first would only bring them back up on the provider just left.

The server form edits an LM Studio preset's base URL and whether it needs an API
key, reachable from the gear on any LM Studio preset row - per row, because two
presets can point at two different machines - and from the failure card. The
shipped local default is untouched unless it is changed.
2026-08-30 21:07:03 +03:00

63 lines
2.3 KiB
QML

import QtQuick
import QtQuick.Shapes
import qs.Commons
// One provider logo, drawn as a vector path and filled with whatever colour it
// is handed - which is what lets these follow the theme the way a font glyph
// would, instead of being a fixed-colour bitmap that looks pasted on.
//
// Every mark is authored in a 24x24 viewBox (see Modes.js) and scaled to the
// requested size from there.
//
// Two things about how that scaling is done, both of which matter at bar sizes:
//
// 1. No `layer.enabled`. A layer renders the Shape into a texture at the item's
// own size and then scales the *texture*, so a 24x24 buffer minified to 16px
// resamples 1.5 pixels into 1. On the Claude burst, whose rays are under a
// pixel wide at that size, that is not softening - rays merge, drop out, and
// come back at uneven weights as the widget moves. Without the layer the
// scale is a transform on the geometry instead, so rasterisation happens
// once, at final resolution.
//
// 2. CurveRenderer rather than the default geometry renderer. It rasterises
// curves analytically with its own antialiasing instead of tessellating them
// into AA'd triangles, which is what keeps sub-pixel detail smooth rather
// than stair-stepped. Qt 6.6+; this ships against 6.11.
Item {
id: root
property string pathData: ""
property real iconSize: Style.font.icon
property color color: Color.foreground
// Per-mark optical correction; see LOGO_SCALE in Modes.js for why equal
// nominal size is not equal apparent size.
property real opticalScale: 1.0
// Rounded so the mark is laid out on whole pixels. Thin strokes straddling a
// pixel boundary lose half their coverage to each side and read as grey.
readonly property int box: Math.round(iconSize)
implicitWidth: box
implicitHeight: box
visible: pathData !== ""
Shape {
id: shape
width: 24
height: 24
anchors.centerIn: parent
antialiasing: true
preferredRendererType: Shape.CurveRenderer
scale: root.box * root.opticalScale / 24
ShapePath {
// No fillRule set on purpose: these marks are authored for nonzero
// winding, which is the default, and LM Studio's bars are knocked out
// of its container by winding direction alone.
fillColor: root.color
strokeWidth: 0
PathSvg { path: root.pathData }
}
}
}