-
Notifications
You must be signed in to change notification settings - Fork 41
example register icon
A one-PHP-file companion plugin that puts a shortcut tile on the desktop wallpaper. Clicking the tile opens a native window showing a random Marvel quote. Modeled after hello.php — the whole plugin is under 60 lines of PHP plus a small JS render callback.
jorvy/jorvy.php:
<?php
/**
* Plugin Name: Jorvy
* Description: A random Marvel quote lives on your desktop.
*/
defined( 'ABSPATH' ) || exit;
// 1. The native window — a small panel the shell renders into.
openstation_register_window( 'jorvy', array(
'title' => __( 'Jorvy', 'jorvy' ),
'icon' => 'dashicons-star-filled',
'width' => 320,
'height' => 180,
'script' => 'jorvy-desktop',
// Optional: where the dock tile sorts among system tiles,
// ascending. Defaults to 0, which puts a plugin launcher ahead of
// the shell's own trailing cluster (Mio 10, Overview 20, System
// 30, Trash 40) — usually what you want. Set it only if the tile
// has a reason to sit somewhere specific; registration order can't
// express that, because tiles land when their script resolves.
'dock_order' => 0,
// Optional: associate a registered style handle with the window.
// The shell injects a `<link rel="stylesheet">` for it on
// mid-session activation — without this, a peer plugin activated
// from inside an open shell renders its window with no CSS until
// the user reloads, because `wp_print_styles` already ran for the
// parent shell page.
'style' => 'jorvy-desktop',
'template' => function () {
?>
<div class="jorvy">
<p class="jorvy__quote"></p>
<cite class="jorvy__attr"></cite>
</div>
<?php
},
) );
// 2. The shortcut tile on the wallpaper — clicking it opens the
// registered native window (matched by id).
openstation_register_icon( 'jorvy', array(
'title' => __( 'Jorvy', 'jorvy' ),
'icon' => 'dashicons-star-filled',
'window' => 'jorvy',
'position' => 10,
) );
// 3. The render script — declares itself on
// `window.openStationNativeWindows[ 'jorvy' ]` so the shell can
// invoke it when the window opens.
add_action( 'admin_enqueue_scripts', function () {
if ( ! function_exists( 'openstation_is_enabled' ) || ! openstation_is_enabled() ) {
return;
}
wp_enqueue_script(
'jorvy-desktop',
plugin_dir_url( __FILE__ ) . 'jorvy-desktop.js',
array( 'openstation' ),
'1.0.0',
true
);
// Match: register the style handle named in `'style' => …` above.
// `wp_register_style` is enough — `openstation_register_window()`
// resolves the handle on its own; the shell decides whether to
// print it at boot or lazy-inject it mid-session.
wp_register_style(
'jorvy-desktop',
plugin_dir_url( __FILE__ ) . 'jorvy-desktop.css',
array(),
'1.0.0'
);
} );jorvy/jorvy-desktop.js:
( function () {
const QUOTES = [
{ q: 'I am Iron Man.', by: 'Tony Stark, Iron Man' },
{ q: 'Hulk smash.', by: 'Bruce Banner, The Avengers' },
{ q: 'I love you 3000.', by: 'Morgan Stark, Endgame' },
{ q: 'On your left.', by: 'Captain America' },
];
function pick() { return QUOTES[ Math.floor( Math.random() * QUOTES.length ) ]; }
window.openStationNativeWindows = window.openStationNativeWindows || {};
window.openStationNativeWindows.jorvy = function ( body ) {
const q = body.querySelector( '.jorvy__quote' );
const a = body.querySelector( '.jorvy__attr' );
const render = () => {
const { q: text, by } = pick();
q.textContent = '"' + text + '"';
a.textContent = '— ' + by;
};
render();
const timer = setInterval( render, 10000 );
return () => clearInterval( timer );
};
} )();Declares the native window — its title, icon, initial dimensions, template markup, and render script. Returns true on success, WP_Error on any validation failure (missing title, non-callable template, unmet capability). script is optional — omit it for a purely declarative window whose body is exactly the cloned template.
Drops a clickable tile on the wallpaper at the position you specify (lower numbers render top-left). The window key must match the id of a registered native window; the alternative is url (either a same-origin admin URL that opens as an iframe window, or an off-site URL that opens in a new browser tab). Mutually exclusive.
The icon arg accepts three formats. A fourth (icon_svg) is a convenience wrapper that produces the third for you.
// 1. Dashicons class — simplest, best for built-in glyphs.
'icon' => 'dashicons-star-filled',
// 2. http(s) URL to an image asset — useful for plugin-hosted PNGs / SVGs.
'icon' => plugin_dir_url( __FILE__ ) . 'assets/jorvy.svg',
// 3. data:image/svg+xml URI — inline SVG, base64 or URL-encoded.
'icon' => 'data:image/svg+xml;base64,' . base64_encode( '<svg …>…</svg>' ),
// 4. icon_svg shorthand — pass raw SVG and the framework
// encodes it for you. Wins over `icon` when both are given.
'icon_svg' => file_get_contents( __DIR__ . '/assets/jorvy.svg' ),Drawing the SVG in currentColor makes it a silhouette — the framework paints it as a CSS mask filled with the surface's text colour, so one drawing stays legible on the dark dock, on a light title bar, and on hover. Use fixed colours only for art that should keep them (a brand mark, a full-colour app icon). See Silhouette icons for the full rule.
The shared sanitizer rejects javascript: URIs and any non-image/svg+xml data: scheme. SVG markup with an embedded <script> tag is rejected outright when passed via icon_svg (defence-in-depth — browsers also sandbox scripts inside <img src="data:…"> SVGs, but we belt-and-braces). All four forms run through openstation_sanitize_dock_icon, so a malformed value silently falls back to dashicons-admin-generic.
Pass pinned => true for built-in shortcuts that should always sit in the same place. Pinned icons render before any unpinned icon regardless of position, and the framework treats them as non-draggable surface — useful for "always there" launchers like the in-tree pinned WP Explorer.
openstation_register_icon( 'my-wordpress', array(
'title' => openstation_site_title(),
'icon' => 'dashicons-wordpress',
'window' => 'desktop-mode-my-wordpress',
'pinned' => true,
'position' => -1, // sort below pinned siblings if you ever add more
) );The flag is intentionally minimal — there is no "lock" persistence layer. Reserve it for shortcuts that are part of the desktop's identity, not user content.
Native windows render in JS because a render( body ) callback can't cross the PHP→client wire. The script declares its render function on window.openStationNativeWindows[ <id> ]; the shell invokes it when the window opens and captures the return value as a teardown (interval cleanup, DOM detach, whatever the plugin needs).
The body comes pre-populated. Before invoking the callback, the shell clones the registered template into the window body — so body.querySelector( '.jorvy__quote' ) returns the <p> declared in the PHP template above, with no manual cloning. Render callbacks are pure enhancement: query the mount points your template declared, light them up. To start from a blank canvas anyway, call body.replaceChildren() first.
- Activate the plugin at Plugins → Jorvy.
- Enable OpenStation via the admin-bar toggle.
- A star icon labeled Jorvy appears on the wallpaper.
- Click it — the Marvel-quote panel opens; the quote rotates every ten seconds.
- Check the action history:
openstation_native_window_registeredandopenstation_icon_registeredeach fired once.
If you want to see the error path in action, comment out the 'title' argument in the icon registration and watch the error log:
[jorvy] registration failed: openstation_missing_title — Desktop icon registration requires a non-empty `title`.
The WP_Error contract means you find typos at plugin-load time, not at first-click time.
When you need to enhance the wallpaper icons themselves — a cursor adornment, a status dot, a drag handle — subscribe to HOOKS.DESKTOP_ICONS_RENDERED. The payload hands you the rendered container and a map of id → tile element, so your decorator doesn't have to query the DOM (and doesn't have to re-query on every live menu refresh — the hook fires exactly when the grid is rebuilt):
wp.os.hooks.addAction(
wp.os.HOOKS.DESKTOP_ICONS_RENDERED,
'my-plugin/icon-status-dot',
( payload ) => {
const { ids, container, tiles } = payload;
if ( ! ids.includes( 'jorvy' ) ) {
return;
}
const tile = tiles.get( 'jorvy' );
if ( ! tile ) {
return;
}
const dot = document.createElement( 'span' );
dot.className = 'my-plugin__status-dot';
tile.appendChild( dot );
// `container` is the <div class="os-icons"> grid root —
// use it if your decoration spans multiple tiles (a connector
// line, a hover halo) instead of decorating a single tile.
}
);The hook is suppressed when the rendered DOM is unchanged (fingerprint short-circuit), so decorators run exactly when the grid actually rebuilds — not on every live menu refresh.
-
openstation_register_window()— full argument reference and error-code table. -
openstation_icon_registered— the post-registration action. -
openstation_icons— filter for hiding/reordering icons registered by others.
This wiki is generated from the docs/ directory — edits made here are overwritten by the next sync.
To change a page, open a pull request against docs/.
Guides
- Development guide
- Releasing openstation
- Agents security model
- API Index
- Architecture
- Bridge protocol — wiring overview
- <os-*> component reference
- Native Desktop Host — Experimental
- Desktop themes
- Dock customization — two registries, one mental model
- The event-driven framework
- Files on the Desktop
- Folder sharing
- Getting Started
- Hooks Reference
- Icons
- JavaScript Reference
- The Living Tree — algorithm definition
- Mio
- Native Windows & Framework Interop
- Plugin compatibility layer
- Progressive Web App (PWA)
- Station Home
- Using openstation from your own plugin
Migration notes
- Migration: built-in activity channels move to the os/ namespace
- Migration: window, wallpaper and widget bundles load on demand
- Migration — the navigation model
- Migration: a native window's tabs move to the window chrome
All examples
- AI Agents — extend and invoke from a plugin
- wp.os.ai.ask() — programmatic AI Copilot
- Tune the AI model config
- Custom arrange-menu action
- Open a child window its owner can't cover
- Style a specific admin page inside the iframe
- Code Blue — register your plugin's log file
- Open a file in the Code editor (deep-link from any window)
- Connect to a window — title-bar button + iframe pub/sub
- Content changes — live-refresh every window listing your type
- Custom window chrome (Experimental)
- Register a custom unfocused-window effect
- Example: render a data table
- Real file storage — react to uploads, gate policy, share from PHP
- React to a window being set free onto the real desktop
- Cross-window devtools — instrumentation primitives
- Add a dock item with a badge
- Decorate the dock without forking the renderer
- Replace the dock rail entirely
- Retune the Drafts widget's AI writing assistant
- Gate OpenStation by role
- Iframe-initiated window opens
- Build a feed reader without the bookkeeping
- Inject data into openStationConfig
- Render a list without losing clicks — renderKeyedList()
- Example: layout primitives (body → panel → row → col)
- Use <os-*> components from a plugin that ships as a zip
- Restyle and drive Mio
- Add an action that works on a whole selection
- WP Explorer — custom post types and their folder
- Add an action button to a WP Explorer preview pane
- Example: native Posts window
- Example: native window with tabs
- Native windows
- Customize note → post conversion
- Send a notification
- OAuth relay — connect to an external service
- OS-file drop
- <os-flyout> — window-scoped sliding card
- Plugins window — extras
- Track who's around — wp.os.presence
- Example: progress bar
- PWA install — surface your own button
- React to window events
- Example: extend the Trash
- Register a slash-command
- Register a desktop theme from a plugin
- Register a game
- Example: register a desktop icon (Jorvy)
- Register a wallpaper
- Register a widget
- Related entities — extend the title bar's "Related" menu
- The native-window render ctx
- Programmatic folder sharing
- Share state across multi-bundle plugins — wp.os.createSharedStore()
- Example: loading spinner
- Add an opt-in card to Station Home
- Accept drops on your desktop icon
- Give a tile two icons, one per state
- Add a row to a window's ⋯ menu
- Example: window activity & the status ring
- Window controls
- Subscribe to window lifecycle events
- Window links — relate windows and restyle the ties (Experimental)
- Window loading state — spinner overlay & ready signal
- Show a banner at the top of a window
- Pulse a window's icon — Window.requestAttention()
- Register a custom window reveal
- Window slots
- Window themes
- Native window with bundle-bound config