-
Notifications
You must be signed in to change notification settings - Fork 1
widget skins
Different visual treatments for the same widget type. Picked per widget from the widget config modal — no markup changes needed, no plugin install, no reload.
Each widget type in wallpaper/index.html's WIDGET_REGISTRY can
declare an optional skins block. Each skin defines its own
markup() (the initial inner HTML for the widget body) plus an
optional render(rec) that the widget's tick callback delegates
to when that skin is active.
WIDGET_REGISTRY.weather = {
label: "Weather",
markup: () => "...default layout...",
tick: (rec) => {
const skin = _widgetActiveSkin(rec);
if (skin && skin.render) skin.render(rec);
else renderWeatherCache(rec);
// ...other tick work...
},
skins: {
compact: { markup: () => "...", render: (rec) => { /* ... */ } },
hexagon: { markup: () => "...", render: (rec) => { /* ... */ } },
},
};A widget's active skin is whatever its opts.skin value resolves
to (default = "default", which is implicit — falls back to the
top-level markup + the existing tick render path). Changing
the skin in the Configurator's widget config modal regenerates
the widget body and adds a widget-skin-<id> class so the
matching CSS rules in <style> take effect.
The Weather widget ships three skins as the proof-of-concept set:
| Skin id | Look |
|---|---|
default |
Icon left · location + condition centre · temperature right · stats row below |
compact |
One-line icon + temperature, location + condition combined underneath, no extras |
hexagon |
Big hexagonal tile in the centre (icon + temp + condition), small extras row below |
Pick a skin from the widget's gear → Skin dropdown in the Configurator. Switch lives instantly — no reload, no re-create-from-scratch dance.
The Configurator hardcodes the skin list per widget type for
v2.3.4-beta. The bridge also serves the catalog at
GET /widgets/skins[?type=weather] so future iterations (and 3rd
party tooling) can fetch it programmatically:
{
"type": "weather",
"skins": [
{"id": "default", "label": "Default"},
{"id": "compact", "label": "Compact"},
{"id": "hexagon", "label": "Hexagon"}
]
}Today every skin is bundled — there's no user-skin install path yet. To add a new skin for an existing widget type:
- Define it under
WIDGET_REGISTRY[type].skins.<id>inwallpaper_bridge/wallpaper/index.html. Provide amarkup()returning the body HTML and an optionalrender(rec). The tick callback for that widget needs to delegate to_widgetActiveSkin(rec).renderwhen present (the Weather widget shows the pattern). - Add matching
.widget-<type>.widget-skin-<id> { … }CSS in the same<style>block. The widget-skin-<id>class is automatically applied to the widget's root element. - Append the new id + label to the same widget's entry in
SKIN_CATALOG_MIRROR(wallpaper) and toWIDGET_CATALOG.<type>.schema's skin select options (configurator.html) and to the server-side catalog inbridge.py's/widgets/skinshandler. Three places, kept in sync manually for v2.3.4-beta.
Planned for a follow-up — likely
%LOCALAPPDATA%\SignalRGBWallpaper\widget-skins\<widget>\<id>\
with a manifest.json + body.html + style.css pair, signed
or hash-pinned to prevent drive-by replacement. Until then, every
skin is bundled with the wallpaper code so security is the
build-time review process — no script execution from external
sources.
The existing Plugin API is for entirely new
widget types, served as sandboxed iframes with their own
manifest.json discovery path. Skins are a lighter mechanism for
re-styling existing types — same data flow, same options
schema, just a different visual rendering. Pick plugins when you
need a new kind of widget; pick skins when you want the existing
clock / weather / calendar to look different.
Getting started
Using the app
Reference
Project