1# Platforms
2
3Snowbound is one core with thin hosts around it. The file format, sync, the
4page editor and the renderer are identical everywhere. What changes per
5platform is the window, the input plumbing, and how much of the interface is
6drawn by Snowbound versus the operating system.
7
8```text
9 macOS Linux Windows iOS
10shell snowbound + ui snowbound + ui snowbound + ui UIKit (apps/ios)
11glue snowbound/src/macos snowbound/src/linux snowbound/src/windows crates/mobile (C ABI)
12page canvas ─────────────────────────────────────────────────────────────────────►
13paint draw (Metal or GL) draw (Vulkan or GL) draw (D3D12, D3D11, GL) draw (Metal)
14data notebook::session + embedded SMB client ────────────────────────────────────►
15format onestore ───────────────────────────────────────────────────────────────────►
16```
17
18## Desktop: `snowbound`
19
20The desktop app is a winit window with the `ui` kit's chrome and a `canvas`
21page inside it. `snowbound` picks a platform module at compile time
22(`macos.rs`, `linux.rs` or `windows.rs`, each mounted as `platform`), and
23everything else in the crate is shared: library and settings, the sidebar,
24menus, page and section management, templates, and screenshot and replay
25support. Accessibility goes through AccessKit's winit adapter on all three:
26the interface's tree, with the page's grafted into it.
27Each platform draws through one of several backends (the table's `paint` row; in a
28browser WebGPU, WebGL 2 or the 2D canvas). By default the first that starts draws;
29Options' Renderer, the settings' `renderer`, `SNOWBOUND_RENDERER` or `--renderer`
30(each over the last) picks one, and one that fails to start falls back to the
31default and says so. Choosing another in Options starts it at once: the renderer and
32surface go and new ones begin, the window and everything in it staying as they are.
33`crash-report` captures panics for desktop, browser and iOS, hiding paths and registered
34notebook names. Desktop keeps the report beside the settings, the browser in local
35storage, and iOS in Application Support. Native faults keep the signal or exception code
36and address without allocating or locking in a signal handler. The next launch offers
37Send Report, Show Report and Don't Send; declining or a successful upload removes it,
38and a failed upload keeps it. Desktop screenshots and replays keep no report.
39`commands.rs` is the one table of commands: each one's title, its chords on
40macOS and elsewhere, when it is enabled or checked, and what it does. The
41keyboard, the toolbar and the macOS menu bar all run commands from it.
42
43### macOS
44
45- The toolbar's row is the title bar. An empty `NSToolbar`, unified compact
46 from macOS 11, with the title hidden makes the title bar 38 pt, and AppKit
47 centres the traffic lights in it beside the row's buttons; the row ends 4 pt
48 short of it, so the tabs stand as near the buttons as a tab bar would. A
49 press in the row's empty space drags the window and a double press zooms or
50 minimizes it, as Desktop & Dock says; presses in a group of buttons do
51 neither, so AppKit is told the view never moves the window. The title is still set, for the Window menu, Mission Control and
52 VoiceOver. `--screenshot` paints the lights where the hidden window's AppKit
53 put them. The app draws the row as part of the same frame as the rest of the
54 chrome. Under the whole
55 window lies AppKit's title bar material, an `NSVisualEffectView`, and the
56 chrome is drawn transparent over it, so the title bar, toolbar, tab row and
57 sidebar take the system's desktop tint, appearance and focus state exactly.
58- AppKit supplies the open and save panels, alerts, the date picker, date
59 formatting for new page titles and conflict labels, the account's full name
60 (used as the author, as OneNote uses Office's user name), the caret and
61 selection colours, and the spell checker, `NSSpellChecker`, which the
62 spelling thread calls on the main thread (10.6 has it too).
63- Frames present inside Core Animation's transaction, so a resize pairs each
64 frame with the window's new size instead of stretching the last one.
65- A notebook on a mounted SMB share is opened through the embedded SMB client,
66 signed in with the password the keychain keeps for that mount (see
67 [sync](sync.md) for why the mount itself isn't enough). Open Notebook from Server reaches one
68 by address instead, and keeps a password it asks to remember as the Finder does, an
69 SMB internet password.
70- The menu bar (`menubar.rs`) is laid out as OneNote for Mac's. Its items are
71 the command table's, validated from the statuses each frame publishes, and
72 their key equivalents are the table's chords, so AppKit takes a chord the
73 menu enables before winit sees the key. Linux has no menu bar.
74- `tools/canvas/build_macos.py` builds and ad-hoc signs `target/Snowbound.app`.
75
76### Linux
77
78- X11 and Wayland. Title bars come from the window manager, or on Wayland from
79 the compositor where it offers server-side decorations, and the toolbar's
80 row lies beneath them, as in Dolphin and Kate. Elsewhere on Wayland, as on
81 GNOME, the row is the title bar, as a GTK 4 header bar holding tools:
82 winit's Adwaita frame keeps its shadow, corners and resize edges but has no
83 header, and the window's buttons sit at the row's ends as GNOME's
84 `button-layout` places them, kept current through the settings portal. On
85 GNOME they are libadwaita's, sized and coloured as the release that came
86 with the running GNOME Shell draws them; elsewhere they are the toolbar's
87 own buttons, not an imitation of a theme Snowbound can't read. Under that
88 frame on GNOME the window erases its bottom corners to transparent pixels
89 and the frame draws libadwaita's window edge round it: radius, shadows and
90 outline. KWin rounds Breeze's corners itself.
91- The toolbar and the rest of the chrome continue the title bar's fill,
92 focused and not: on KDE the colour scheme's header colours from
93 `kdeglobals`, as KWin paints its title bars, and on GNOME libadwaita's
94 header bar's. A settings portal signal re-reads them when the scheme changes.
95- Menus and other popups take the desktop's look and motion: libadwaita's
96 popover menus on GNOME, shown and hidden at once as GTK 4 does, and Breeze's
97 on KDE, faded as KWin fades popups and scaled by Plasma's animation speed.
98 The desktop is read once from `XDG_CURRENT_DESKTOP`; elsewhere the kit's own.
99- The desktop portal provides the file pickers; alerts, questions and the page
100 date and time are the kit's own dialogs in the window, as is the file picker
101 where no portal answers. No dialog waits on the event loop's thread. The XDG settings portal
102 provides the colour scheme. Text conventions come from the C library's
103 locale. Fontconfig is loaded at run time, so builds need no headers for it,
104 and so is Enchant, which checks spelling with whatever dictionaries its
105 providers have; without it, words go unmarked.
106- Wayland's clipboard goes through the window's own connection, since not every
107 compositor offers a clipboard to clients without a window. Its queue wakes the
108 event loop, which answers other apps' pastes with no thread of its own. Text,
109 pages, pictures and files are read there too, not through Xwayland. X11
110 sessions use arboard.
111- The executable carries its desktop entry and icon (`desktop_linux.rs`). The
112 window is `net.paperclover.snowbound` to Wayland and X11 alike. Where a
113 Wayland compositor lacks xdg-toplevel-icon, as GNOME's does, it finds the
114 icon only through an entry of that name, so a run that isn't installed
115 writes a hidden one pointing at an icon in the runtime folder, marked with
116 its process ID, and removes both on exit or at the next start after a crash.
117 KWin takes the icon from the window. Install on the welcome copies the
118 executable to `~/.local/bin` and writes the visible entry under the same
119 name, so the menu never lists two; Uninstall in Options removes it.
120
121### Windows
122
123- One executable runs on Windows 7 SP1 through 11. It is built from macOS
124 with llvm-mingw against `msvcrt.dll`, which every Windows has
125 (`platform/windows/cargo.sh`): x86_64 on nightly's tier-3
126 `x86_64-win7-windows-gnu`, whose standard library avoids Windows 8's
127 imports, and aarch64 for Windows 11 on Arm. The few imports of Windows 8
128 and later that dependencies still name are answered by
129 `platform/windows/rt/shims.c`; everything newer is looked up at run time.
130- `draw` builds three backends on Windows, tried in turn by default:
131 Direct3D 12 through wgpu where Windows has it (10 and 11), otherwise
132 Direct3D 11, as on Windows 7, on WARP where no device reaches feature
133 level 10_0, and OpenGL 2.1 on a WGL context only where neither starts
134 (`surface_windows.rs`). Direct3D 11 presents through a blit-model DXGI
135 swap chain, as 7 has no flip model, and through DirectComposition on 10,
136 whose window has no redirection surface; OpenGL drivers such as Intel's
137 place their frames below the caption the system would draw even where the
138 row takes its place.
139- The toolbar's row is the title bar wherever the desktop composes windows.
140 On Windows 7 with Aero and on 11, the system's frame keeps its sides,
141 its caption gives way to the row, its material (Aero glass, Mica) lies
142 under the whole window, and the system hit-tests and runs its own
143 caption buttons over the row, which brings 11's snap layouts. 7 draws
144 them over the glass; 11 doesn't over the Direct3D surface, so the row
145 draws them as 11 does, lit where the system reports the pointer. On 8
146 and 10, whose frames are opaque, the window has no system frame and the
147 row draws and runs caption buttons as 10 does, over acrylic on 10. Over
148 7's glass, which takes any colour and whatever lies behind the window,
149 the toolbar keeps opaque faces under its icons: by default the strip is
150 opaque, as Explorer's command bar is under its glass, stopping short of the
151 caption buttons 7 draws beneath the window's pixels, and
152 `SNOWBOUND_W7_CHROME` tries the alternatives, `pills` for a face per group
153 of tools over the glass, `frost` for a strip the glass tints through, and
154 `tint` and `tint-tiles` for one panel or a tile per group in the glass's
155 colour at a lightness text keeps its contrast on, as Mica tints.
156 The theme and material follow the system's colour mode as it changes.
157 With Windows 7's basic or classic theme the system draws the title bar
158 and the row lies beneath it, as on KDE.
159- A notebook on a share opens by its UNC path through Windows' own SMB
160 client, which takes OneNote's opens and locks natively: `onestore` opens a
161 section as OneNote does (a reader shares it with everyone, a writer denies
162 other writers) and takes OneNote's coordination bytes with byte-range
163 locks, so both apps can have a section open on one machine.
164 ReadDirectoryChangesW reports changes below a notebook, including other
165 clients' on a share. Open Notebook from Server still uses the embedded
166 client, its passwords in the Credential Manager.
167- The common file dialogs, task dialogs, the date and time picker, and the
168 Spell Checking API (from Windows 8) are the system's; audio plays and
169 records through MCI. Dates follow the user's locale as OneNote's do.
170 Windows has no menu bar: the toolbar and the command palette run the
171 command table, with OneNote 2010's Ctrl chords.
172
173### The browser
174
175- The same `snowbound`, built for `wasm32-unknown-unknown`: `web.rs` is its platform
176 module, and `web/index.html` and `web/glue.js` are the page around it. `glue.js` brings the
177 canvas's pointer, wheel and touch, and a hidden text area's keys, composition and paste, as
178 the `ui::Event`s winit would; `web.rs` stands in for winit's window and event loop and runs
179 `State` a turn per animation frame. `tools/release_web.py` builds it and deploys it to https://snowbound.paperclover.net.
180- The page has one thread. The notebook's section thread, sync worker and background run as
181 tasks on its event loop (`notebook::task`), and work the desktop gives a thread runs once
182 the frame is done (`spawn`).
183- Files are `notebook::fs`'s: std's elsewhere, here held in memory, with SQLite's VFS over
184 the same files, so replicas and notebooks live side by side under `/Notebooks` and
185 `/Cache`. A storage worker keeps them in the origin's private file system, writing the
186 byte ranges each burst changed through OPFS's synchronous handles, which only workers get.
187 One tab at a time holds them.
188- Live Share joins desktop hosts through sealed WebSockets. Approval, device removal,
189 presence and edits use the desktop protocol; replicas await remote operations and an
190 ordered OPFS flush before publishing or returning a durable receipt. Hosting and section
191 organization stay on desktop.
192- Open Notebook, where the browser has the File System Access API (Chromium), opens a folder
193 of the user's: mirrored under `/Folders`, its handle kept in IndexedDB, other apps' writes
194 read every few seconds. A browser takes no locks, so a commit there stands only once the
195 page has written the file and found nothing else wrote it since it was read; until then
196 it is uncertain, as one whose answer was lost, and it goes again on top of another app's
197 write. The sync popup says the folder isn't locked.
198- Menus are the kit's own, as on Linux, with the PC's chords, which a Mac takes and shows with ⌘ for Ctrl and ⌥ for Alt; the
199 browser keeps its own window and tab chords, so New Page and New Section add Alt to
200 theirs. Dialogs are the browser's; Insert, and Open
201 without a folder to give, ask for files to copy in; printing downloads the PDF. Servers and
202 recording are still to come.
203- Spelling is Hunspell's dictionaries through `spellbook`, each fetched beside the module the
204 first time a word in its language is checked, text no run tags counting as the browser's
205 language; Add to Dictionary keeps its words in the browser's files. Text the bundled faces
206 lack takes Noto's, fetched by script the first time a page holds it (a CJK face cut to
207 the national standards' characters by `tools/web/subset_cjk.py`; emoji in Noto Color
208 Emoji's COLRv1 outlines, which `draw` paints itself), and the page is laid out again.
209- Frames draw through WebGPU, else WebGL 2, both by wgpu, else the 2D canvas, which a
210 browser with WebGL turned off still has; `?renderer=` in the address picks one, as
211 `--renderer` does. The canvas draws each of `draw`'s quads with its own calls (paths,
212 `drawImage` from the glyph atlas, glyphs of a colour from a sheet coloured once), and
213 corrects translucent colours' opacity for blending sRGB-encoded. A popup leaning in as it
214 opens draws into a canvas of its own laid over the page and leaned by a CSS 3D transform
215 of the same perspective, which the browser's compositor draws. A canvas keeps the first
216 kind of context it gives, so each renderer starts on a canvas of its own, which then takes
217 the page's place and its input. Only where even the 2D canvas fails does `start` reject,
218 with a `NoGraphicsError`.
219- Updating is a reload. With Update automatically on, the page checks hourly whether
220 `index.html` names another build's folder than the one its module came from; finding
221 one, it fetches that build's module, JavaScript and fonts into the HTTP cache, then
222 reloads at a quiet moment (idle 90 seconds, or when the user comes back to the tab, with
223 no popup or dialog open and its files written), back on the same page and scroll. A
224 reload that didn't bring the build isn't tried again.
225- AccessKit has no web adapter, so once a screen reader asks for it (a visually hidden
226 button, then on every visit) the trees AccessKit would get are mirrored as hidden
227 elements with ARIA roles; acting on one sends AccessKit's action back.
228
229## iOS: native around the canvas
230
231On iOS the split moves. Touch text editing depends on affordances that users
232know by feel and that are expensive to imitate: the loupe, selection handles,
233the edit menu, autocorrect, dictation and hardware keyboard commands. So UIKit
234owns everything around the page:
235
236- **UIKit** handles navigation (a three-column split view of notebooks, pages
237 and the page), scrolling and zoom (`UIScrollView`), the keyboard and text
238 input (`UITextInput`), caret, selection highlight and handles, the format
239 bar, and connecting to servers.
240- **`canvas`** draws the page into a `CAMetalLayer` and makes every editing
241 decision. The C surface exposes the active outline's text as the flat UTF-16
242 model `UITextInput` speaks. That is the same unit `onestore` ops measure
243 text in, so autocorrect and dictation become ordinary text ops with no
244 translation layer.
245- **`crates/mobile`** is that surface: a static library with a C header,
246 covering libraries, shares, sections and views, built by an Xcode build
247 phase (`apps/ios/build-rust.sh`). All views share one GPU device and one
248 renderer, and text engines are pooled across views.
249
250No `ui` crate ships on iOS. Everything below the view is the desktop's code:
251`notebook::session` with its replica and background publishing, the embedded
252SMB client (credentials kept in the Keychain, as Files keeps its own), conflict
253pages, search, spelling (through `UITextChecker`), and page management through
254the same ops. Notebooks from Files
255are read and written under `NSFileCoordinator`, so file providers see every
256change.
257
258Record Audio and Record Video record through AVFoundation and hand `crates/mobile` the
259WAV file, or the camera's pictures and sound, which it stores as the desktop does. Audio
260keeps recording in the background and with the screen locked, under the `audio`
261background mode, as a lecture outlasts the screen's timeout; a call pauses it until the
262system says to go on. The camera stops in the background, which ends a video recording
263and saves it. A tap on a recording plays it with `AVPlayer` in a bar over the page's foot.
264
265## What stays shared, on purpose
266
267- **Behaviour**: the editor, hit-testing, the placement grid, undo, conflicts
268 and search all live in `canvas` or `notebook`. A host that reimplements one
269 of them has made a bug.
270- **Storage**: every platform writes through the same ops and the same
271 replica, and publishes through the same sync step.
272- **Look of the page**: the page renders through `draw` everywhere, so a page
273 looks the same on a phone as on the desktop.