std/desktop
Desktop applications: the application, its windows, and the screens they show on.
import "std/desktop"
AppOptions options = AppOptions("Notes", "dev.example.notes", single_instance: true)
app_run(options, func(App app) do
Window main = app.window(WindowOptions(title: "Notes", remember: "main"))
main.on(func(WindowEvent e) do
if e.kind == WindowEventKind.CLOSE_REQUESTED and unsaved() then
e.cancel()
end
end)
app.on(func(AppEvent e) do
if e.kind == AppEventKind.OPEN_FILES then
open_documents(e.paths)
end
end)
end)?app_run makes the system's window loop the event loop's wait, so the
timers, processes, files, and sockets of std/loop keep working
while windows are open, and while one is moved or resized. It returns when
the application quits: when its last window closes, unless
quit_on_last_window is false, or when App.quit is called.
Windows are Win32's on Windows, with per-monitor DPI awareness; Cocoa's
on macOS; and GTK 3's on Linux and the BSDs, on X11 or Wayland, where GTK
is loaded when the application starts, so a program builds without it and
app_run gives an error of kind "unsupported" where it, or a display, is
missing. What each system lacks:
| System | Differences |
|---|---|
| macOS | menus are the application's, with its own menu first; schemes, extensions, and notifications need the program in an application bundle; positions count from the top of the main screen |
| Linux and the BSDs | global shortcuts and backdrop materials are not there; Wayland has no window positions; notifications need a notification server, secrets the Secret Service and libsecret, and tray icons a desktop with a system tray |
Sizes are in pixels at a scale of 100%, so a window 800 wide is 1600 pixels wide on a screen at 200%, and it keeps its size as it moves between screens of different scales. Positions are the system's own screen coordinates, which on Windows are the screen's pixels.
The application
| Function or method | Result |
|---|---|
AppOptions(name, id, version:, icon:, single_instance:, quit_on_last_window:) |
what the application is: id is a reverse domain name, as "dev.example.notes", that the system groups its windows by; icon is an .ico file for its windows |
app_run(options, setup) |
runs the application: calls setup with the App, then the event loop until it quits; true, or false at once for a second launch of a single instance, which hands its arguments to the first |
app.on(handler) |
calls handler with each AppEvent |
app.window(options) |
a new Window |
app.windows() |
the windows that are open, in the order they were made |
app.quit() |
asks to quit: QUIT_REQUESTED, which a handler can cancel, then each window closes without asking; whether it quit |
app.add_recent(path) |
adds a file to the system's list of recent documents |
app.register_scheme(scheme, description), app.unregister_scheme(scheme) |
makes the system open URLs of scheme, as "notes" for notes://today, with this program, for this user, or stops it |
app.register_extension(extension, description), app.unregister_extension(extension) |
the same for files of extension, as ".note" |
app.start_at_login(on) |
starts the program when the user logs in, or stops doing so |
app.set_menu(items) |
the menu bar of each window without one of its own; on macOS, the application's menu |
app.set_menu_enabled(id, on), app.set_menu_checked(id, on), app.set_menu_label(id, label) |
changes the items of id in every menu; checking a radio item unchecks the rest of its group |
app.add_global_shortcut(shortcut, id), app.remove_global_shortcut(id) |
registers keys with the system, which give SHORTCUT events whichever program is in front; false when another program has them |
app.add_tray_icon(icon, tooltip, menu) |
a TrayIcon in the notification area, or the menu bar on macOS, which keeps the application running until remove(); set_icon, set_tooltip, and set_menu change it, and its menu's items are MENU events without a window |
monitors() |
the screens, as Monitors, the primary one first |
dark_mode() |
whether the system's applications use the dark theme |
An AppEvent has a kind:
| Kind | When | Fields |
|---|---|---|
STARTED |
setup has run |
|
ACTIVATED, DEACTIVATED |
one of its windows became the active one, or none is now | |
REOPENED |
macOS: the Dock icon was clicked with no window open | |
QUIT_REQUESTED |
app.quit(), or the session is ending; e.cancel() keeps it running |
|
INSTANCE |
a second launch of a single instance | args, dir: its arguments and directory |
OPEN_FILES |
files given on the command line, to a second launch, or by the system for a registered extension | paths, in full |
OPEN_URL |
a URL of a registered scheme, given the same ways | url |
THEME_CHANGED |
the system changed between dark and light | dark |
MENU |
a menu item was chosen | item, its id or role; checked, its check mark after the choice; window, the window of the menu |
SHORTCUT |
a global shortcut was pressed, whichever program was in front | item, its id |
CLIPBOARD_CHANGED |
what the clipboard has changed, copied by this program or another | |
NOTIFICATION |
a notification was chosen while the program runs | item, its id; action, the label of the button chosen, or "" |
TRAY |
a tray icon was clicked | tray; item, "click", "double_click", or "menu" for the right button, which shows its menu |
SETTINGS_CHANGED |
a setting of the system changed | item: "accent", "high_contrast", "font", or "locale" |
POWER |
the computer goes to sleep, wakes, or its battery changes | item: "sleep", "wake", or "battery" |
A Monitor has x, y, width, and height; work_x, work_y,
work_width, and work_height for the area without the taskbar or Dock;
scale in percent; and primary.
A second launch of a single instance finds the first by id, hands it its
arguments and directory, and ends; a registered scheme or extension runs
the program with the URL or the path as its argument, which reaches the
first instance the same way.
Windows
WindowOptions say what a window is when it is made:
| Field | Default | Meaning |
|---|---|---|
title |
"" |
|
width, height |
800, 600 | the area inside the frame |
position |
centered | Option.SOME((x, y)) for where the frame starts |
min_width, min_height, max_width, max_height |
0, no limit | |
resizable, maximized, fullscreen, topmost |
true, false, false, false |
|
visible |
true |
whether it shows when made |
frameless |
false |
no title bar or frame: the program draws them, and set_drag_regions says where it is dragged by |
opacity |
1.0 | from 0, invisible |
backdrop |
"" |
Windows 11: "mica", "acrylic", or "tabbed" |
background |
the system's | the color where nothing is drawn, as "#20242c" |
owner, modal |
null, false |
the window it belongs to, which it stays above and closes with, and whether the owner takes no input while it is open |
remember |
"" |
a name to keep its position, size, and state under across launches, in the user's configuration directory |
accept_drops |
true |
whether files and text can be dropped on it |
| Method | Result |
|---|---|
w.on(handler) |
calls handler with each WindowEvent |
w.request_close(), w.close() |
asks it to close, as its close button does, or closes it without asking |
w.is_open() |
whether it has not closed |
w.title(), w.set_title(text) |
|
w.size(), w.resize(width, height) |
the area inside the frame, as (width, height) |
w.position(), w.move_to(x, y), w.center() |
where the frame starts; center puts it in the middle of its screen's work area |
w.set_min_size(width, height), w.set_max_size(width, height) |
|
w.set_resizable(on), w.is_resizable() |
|
w.show(), w.hide(), w.is_visible() |
|
w.focus(), w.is_focused() |
brings it to the front with the keyboard's input |
w.minimize(), w.maximize(), w.restore(), w.is_minimized(), w.is_maximized() |
|
w.set_fullscreen(on), w.is_fullscreen() |
covers its whole screen without a frame |
w.set_topmost(on), w.is_topmost() |
above the windows of other applications |
w.set_drag_regions(regions) |
the rectangles (x, y, width, height) a frameless window is dragged by; its edges still resize it |
w.set_opacity(value), w.set_backdrop(material), w.set_background(color) |
the last two say whether the system has it |
w.scale(), w.monitor() |
the scale of its screen in percent, and the screen |
w.state(), w.restore_state(text) |
where it is, its size, and whether it is maximized, as text to keep, and putting it back |
w.set_progress(percent), w.set_badge(count), w.request_attention(on) |
its taskbar button or Dock icon: a progress bar, below 0 for none; a count, 0 for none; flashing until it is used |
w.set_menu(items) |
its own menu bar, instead of the application's; null for none |
w.popup_menu(items, at) |
a context menu at Option.SOME((x, y)) inside the window, or where the pointer is, until an item is chosen or it is dismissed: the id chosen, or "" |
w.set_accept_drops(on) |
whether files and text can be dropped on it |
w.drag_files(paths), w.drag_text(text) |
drags them out of the window while the mouse's left button stays down, as a handler of a press starts it: "copied", "moved", or "none" |
w.capture() |
what it shows inside its frame, as a BMP file's bytes, even when it is covered |
w.handle() |
the system's handle, an HWND on Windows, for C |
A WindowEvent has a kind and the window:
| Kind | Fields |
|---|---|
CLOSE_REQUESTED |
e.cancel() keeps it open |
CLOSED |
|
RESIZED |
width, height |
MOVED |
x, y |
FOCUSED, BLURRED, MINIMIZED, MAXIMIZED, RESTORED |
|
SCALE_CHANGED |
scale, as it moves to a screen of another scale |
THEME_CHANGED |
dark |
DRAG_ENTERED, DRAG_MOVED, DRAG_LEFT |
dragging, "files" or "text", and x, y inside the window, as something is dragged over it |
FILES_DROPPED |
paths, in full, and x, y |
TEXT_DROPPED |
text, and x, y |
A handler cancels only while it runs for the event.
Menus and shortcuts
app.set_menu([
submenu("&File", [
menu_item("&Open…", "open", "mod+o"),
menu_item("&Save", "save", "mod+s"),
menu_separator(),
role_item("quit"),
]),
submenu("&Edit", [role_item("undo"), role_item("redo"), menu_separator(),
role_item("cut"), role_item("copy"), role_item("paste")]),
submenu("&View", [menu_checkbox("&Word Wrap", "wrap", true),
menu_radio("&Small", "small", true), menu_radio("&Large", "large")]),
])
app.set_menu_enabled("save", false)A MenuItem has a label, where & marks the letter that chooses it
from the keyboard; an id, the program's name for it, which MENU events
carry; a shortcut; a kind, NORMAL, CHECKBOX, RADIO, SEPARATOR,
or SUBMENU with its items; checked, enabled, an icon file shown
beside the label (.ico on Windows), and a role. menu_item,
menu_checkbox, menu_radio, menu_separator, submenu, and
role_item(role) make them. A checkbox turns over and a radio item checks
itself alone in its group, the radio items next to each other, before the
MENU event says so.
A shortcut is keys joined with +, as "mod+shift+s": the modifiers
ctrl, alt, shift, super, and mod, which is Command on macOS and
Ctrl elsewhere, then a letter, a digit, a punctuation key, f1 to f24,
or a name such as enter, escape, tab, space, delete, up, or
pagedown. A window's menu bar takes its items' shortcuts before the
window's controls see the keys. shortcut_label(shortcut) is how the
system shows it: Ctrl+Shift+S, or ⇧⌘S on macOS.
A role makes a standard item, labeled and given the shortcut each system
gives it, which does what the system does, then gives a MENU event with
the role as item unless the item has an id:
| Role | What it does |
|---|---|
undo, redo, cut, copy, paste, delete, select_all |
sends the command to the control with the keyboard's input; their shortcuts are shown and left to the control |
quit |
app.quit(): Exit on Windows, without a shortcut |
close_window, minimize, zoom, toggle_fullscreen |
to the window of the menu |
about, preferences |
nothing but the event, for the program to show its window; Settings, with mod+, |
hide, hide_others, show_all |
macOS only: left out elsewhere |
window_list |
a submenu of the open windows, each item focusing its window, kept current as they open and close |
Dialogs
str[] paths = open_file_dialog(FileDialogOptions(filters: [FileFilter("Notes", ["*.note"])], window: main))
MessageAnswer answer = message_box(MessageOptions("Save changes to notes.txt?",
buttons: ["Save", "Don't Save", "Cancel"], cancel_button: 2, icon: MessageIcon.QUESTION,
window: main))
if answer.button == 0 then
save()
endEach dialog is the system's own and waits until it closes, while the
event loop's timers and sources keep running; each has a form ending in
_async that gives a Task instead and returns at once.
| Function | Result |
|---|---|
open_file_dialog(options), open_file_dialog_async(options) |
the full paths of the files, or folders, chosen; none when cancelled |
save_file_dialog(options), save_file_dialog_async(options) |
Option.SOME(path) to save to, or NONE |
message_box(options), message_box_async(options) |
a MessageAnswer: button, the place in buttons of the one pressed, or cancel_button when dismissed, and checked, the checkbox |
pick_color(initial, window), pick_color_async(...) |
a color as "#336699", or NONE |
pick_font(initial, window), pick_font_async(...) |
a Font with family, size in points, bold, and italic, or NONE |
FileDialogOptions has title, the directory it starts in, the name
it starts with, filters of FileFilter(name, patterns), multiple,
folders to choose folders instead of files, show_hidden, an
extension added to a name saved without one, confirm_overwrite, and
the window it belongs to, which takes no input while it is open.
MessageOptions has the message, a detail below it, a title (the
application's name without one), buttons, default_button,
cancel_button (-1 for a dialog that only a button closes), an icon
(MessageIcon.NONE, INFO, WARNING, ERROR, or QUESTION), a
checkbox label with checked, as for "Do not ask again", and window.
Tests answer dialogs with desktop_simulate("dialog", answer) from a
timer: "cancel", "button 1", "check", or "accept name", which
types a file name and presses the dialog's main button. It is false while
no dialog shows yet. desktop_simulate also takes "press" with a window
and a shortcut, "click" with a window and a menu item's label, "key",
"end_menu", and "hotkey", so a test can stand in for a person at each
part of an application.
The clipboard
set_clipboard_text("hello")
match clipboard_text() do
when SOME(text) then
print(text)
when NONE then
end
set_clipboard(ClipboardContent(text: "bold", html: "<b>bold</b>"))| Function | Result |
|---|---|
clipboard_text(), set_clipboard_text(text) |
the text, or NONE; lines end with \n in the program and as the system has them on the clipboard |
clipboard_html(), clipboard_rtf() |
the HTML fragment that was copied, or the RTF, or NONE |
clipboard_image() |
a PNG file's bytes when the clipboard has one, and a BMP file's otherwise, or NONE |
clipboard_files() |
the files copied, as in the file manager, in full |
clipboard_data(format) |
the bytes of a format of a program's own, or NONE |
clipboard_formats() |
"text", "html", "rtf", "image", "files", and the names of the others it has |
set_clipboard(content) |
replaces what it has with each form a ClipboardContent has: text, html, rtf, image (a BMP or PNG file), files, and data, a map[str, bytes] of formats of the program's own |
clear_clipboard() |
empties it |
primary_selection(), set_primary_selection(text) |
Linux's primary selection, which a middle click pastes; NONE and false elsewhere |
The clipboard works in programs without windows too. Inside app_run,
each change to it, by any program, is a CLIPBOARD_CHANGED event.
The system
notify(Notification("Export finished", body: "notes.pdf is ready", id: "export", actions: ["Open"]))
app.on(func(AppEvent e) do
if e.kind == AppEventKind.NOTIFICATION and e.action == "Open" then
open_path(exported)
end
end)
save_secret("notes-sync", "ana@example.com", token)| Function | Result |
|---|---|
notify(notification) |
shows a Notification in the system's notification center: title, body, an id its events carry, actions (button labels), an image file, silent, and quiet, which puts it only in the center; whether the system took it |
clear_notifications() |
removes the application's notifications from the center |
dark_mode(), accent_color(), high_contrast() |
the theme: whether apps are dark, the accent as "#0078d4", and high contrast |
system_font() |
the Font the system's text is in, as Segoe UI at 9 points |
user_locale(), preferred_languages() |
the locale, as "es-AR", and the languages the user prefers, the first first |
battery() |
Option.SOME(Battery), with percent, charging, and on_battery, or NONE without a battery |
keep_awake(on, display:, reason:) |
keeps the system from sleeping, and the screen from turning off when display, until called with false |
open_url(url), open_path(path), show_in_folder(path) |
opens a URL or a file in the program the system has for it, or shows a file selected in the file manager; false when it is not there |
save_secret(service, account, secret), load_secret(service, account), delete_secret(service, account) |
a secret, such as a token, in the system's credential store: Windows Credential Manager, the macOS Keychain, or the Secret Service on Linux |
Notifications on Windows are toasts of the notification center, with the
application registered under its id for the current user, as programs
without a package need; choosing one while the program runs is a
NOTIFICATION event. Starting at log-in is app.start_at_login. A screen
reader reads a window's title and the native dialogs and menus; the
contents a program draws get their accessibility from the web view of
phase 8.