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.

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()
end

Each 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.