Skip to content

Windows, events, and menus

#[Window(
name: 'settings',
title: 'Settings',
page: 'resources/settings.html',
width: 760,
height: 620,
)]
final readonly class SettingsWindow extends DesktopWindow
{
}

Register it on the application:

protected function windows(): array
{
return [SettingsWindow::class];
}

Then inject the exact window:

#[Command]
public function openSettings(SettingsWindow $settings): void
{
$settings->show()->focus();
}

DesktopWindow collects title, visibility, focus, close, fullscreen, and maximize effects. It never exposes Winit, Servo, or an operating-system handle.

final readonly class DocumentSaved
{
public function __construct(
public int $documentId,
public int $revision,
) {}
}
$events->emit(new DocumentSaved($document->id, $document->revision));

The default wire name is document.saved; public properties form the payload. Implement Event for an explicit stable name or serialized shape.

Frontend events map back to typed methods:

await window.pam.emit(
"editor.changed",
{ documentId: "doc-42" },
{ timeout: 5_000, signal: abortController.signal },
);
#[Listen('editor.changed')]
public function editorChanged(
string $documentId,
WindowHandle $window,
): void {
$window->title("Editing {$documentId}");
}

pam.emit(name, payload, options) rejects empty event names and preserves the same timeout, cancellation, worker-recovery, and typed-error behavior as pam.invoke(). Use pam.on() for PHP-to-frontend events; it returns an unsubscribe closure that the owning screen or window must call.

#[Menu(
id: 'application',
label: 'My desktop app',
close: TrayCloseBehavior::Hide,
)]
final class ApplicationMenu
{
#[MenuItem('Show window', shortcut: 'CmdOrCtrl+Shift+KeyP')]
public function show(WindowHandle $window): void
{
$window->show()->focus();
}
#[MenuSeparator]
public function separator(): void {}
#[MenuItem('Background mode', checkbox: true)]
public function background(): void {}
#[MenuItem('Quit')]
public function quit(ApplicationControl $application): void
{
$application->quit();
}
}
protected function menus(): array
{
return [ApplicationMenu::class];
}

The menu item ID defaults to the method’s dotted name. A declared shortcut is validated once and registered as a native global shortcut with the same action. Linux currently supports one tray-backed application menu.