Files
admin 45b8ddd483
CI / build + test (push) Canceled after 0s
CI / cargo audit (push) Canceled after 0s
A print dialog with a preview, CUPS and the print portal, a Windows backend, and ltk::dirs for the user's folders
Printing. An app that wanted to print had nothing in ltk to ask the user where and how, nor to reach a printer. `ltk::print` (`src/print/`) is a dialog built as `FileChooser` is: the app holds a `PrintDialog`, puts its `view()` in its own and hands its `PrintDialogMsg`s to `update()`, which returns a `PrintEvent`; `wrap` makes the app's message of the dialog's, so the printer list reaches it through the app's `ChannelSender`. It offers the destination, the printers CUPS knows with their state and "Save as PDF", the number of copies, all pages or a range such as `1-3, 5, 8-`, the paper (A4, Letter, Legal, A3, A5), the orientation, the margins, the scale, two-sided printing, colour and background graphics, the copies, sides and colour only for a printer. `PrintOptions` holds what was chosen and survives between dialogs, `parse_pages` and `format_pages` read and write the ranges. On a desktop the dialog is a card with ltk's `dialog()`, the preview on the left and the settings on the right; `compact()` makes it a whole page for a phone. `.printer( name )` and `.save_as_pdf()` choose where it starts.
The preview. With `.preview( true )` the dialog asks the app for one with `PrintEvent::Preview( options )` whenever a setting that moves the pages changes, and shows what the app hands back to `PrintDialogMsg::previewed`, a `Preview` of some pages as RGBA with the document's total, one page at a time with buttons to move between them, greyed when colour is off. While one is being rendered a new request waits and is sent only if the layout changed meanwhile, so turning the scale quickly does not stack renders; until the first arrives the sheet's box shows "Preparing the preview…", kept to the sheet's width so it does not push the settings aside.
CUPS. `print/cups.rs` loads `libcups.so.2` with `dlopen` at first use, so ltk neither links against it nor needs its headers, and a system without CUPS only loses its printers. It uses the destinations API, which also lists the network's driverless printers that have no queue: `printers()` with the default first, `print( printer, title, options, pdf )` sending the PDF as one job with `copies`, `media` as a PWG name, `sides` and `print-color-mode`, `jobs()`, `cancel()` and `set_default()`, which writes the user's `lpoptions`. `ltk::print::send`, `list_printers`, `jobs`, `cancel_job` and `set_default` run them on a thread and answer through the app's sender; `ltk::print::blocking` exposes them as they are, for code that has its own thread, such as a portal backend. `print/none.rs` stands in on Windows, Android and the browser, where only saving as PDF is offered.
The portal. An app in a Flatpak cannot reach the CUPS socket. When `/.flatpak-info` exists, the dialog shows nothing of its own: it calls `org.freedesktop.portal.Print.PreparePrint` with the options as GTK's print settings and page setup, and the portal's own dialog answers; `Destination::Portal` then carries its token, and `send` passes the PDF to `Print` through a memfd. The Response signal is subscribed to on the request's path before the call, so a quick answer is not missed. `print/gtk.rs` translates `PrintOptions` to and from GTK's keys (`n-copies`, `page-ranges` counted from zero, `duplex`, `use-color`, `scale` in percent, the paper by its PWG name and size in millimetres), which a portal backend uses for the other direction. zbus 4, the one accesskit_unix already brings, joins the `wayland` feature for it.
The dialog's strings are in ltk's seven locales. `file_chooser::view::icon` is shared with the dialog.
Windows. The `windows` feature runs `ltk::run` in a Win32 window on Windows 10 1703 or later, with Windows's own frame, drawn through ANGLE's EGL and OpenGL ES when `libEGL.dll` and `libGLESv2.dll` sit beside the executable and in software through GDI otherwise (`src/platform/windows/`). It sizes the window by `App::window_size_hint` at the display's DPI and follows per-monitor DPI changes at fractional scales and the accessibility text size; it takes the mouse, the wheel, touch through `WM_POINTER`, and keys with Windows's repeat, dead keys and AltGr; it has the system clipboard, the cursor's shape, full screen, and moves and resizes begun from the app's own chrome, and keeps drawing while the window is moved or resized. Themes, fonts, keyboard layouts and MIME globs come from `ltk\` beside the executable, which `ltk::resource_dir` returns there by default; the font fallback chain and the monospace face are Windows's own, the system face stays the embedded Sora, and the file chooser offers each drive's root. A Win32 message loop cannot wait on calloop, so the runtime's timers and channels go through the queue the web backend already used: `build.rs` sets `cfg( queued_sources )` for both, and `AppData::dispatch_sources` moves from the web backend to `event_loop/sources.rs`. `Canvas::present_to` presents a GPU frame stretched over a window that a fractional scale left a pixel off it, and `EglContext::new_windows` and `create_native_surface` give ANGLE's display and an `HWND`'s surface. `App::save_state` is saved into `%LOCALAPPDATA%\<app_id>\` every 30 seconds, on exit and when the user's session ends, and restored as on Wayland: the part of the Wayland session runtime that does not speak the protocol, deciding why the app starts and handing it its bytes, is now `session_state::bootstrap`, which both backends call, with the file modes and the check for a running instance done each platform's way. `RunError::Window` reports a window Windows refused. `make windows-deps` and `make windows-check` typecheck the backend from Debian, compiling std from `rust-src`. `docs/windows.md` describes building, what ships beside the executable and what the backend does.
ltk::dirs. `home`, `data_home`, `config_home`, `cache_home` and `state_home`: XDG's on Linux, with their usual fallbacks under the home folder, and on Windows `USERPROFILE`, `APPDATA` for data and settings and `LOCALAPPDATA` for the cache and state, an `XDG_*` variable that is set winning everywhere. An app finds its files there instead of reading `HOME` and the XDG variables itself, which on Windows are unset; ltk's own lookups of themes, keyboard layouts, MIME globs, the file chooser's folders and the session state now go through it, and `state_dir` takes the base it is given, its tests with it.
Docs. The links to `WidgetId` in the crate's, `types`' and `App`'s documentation name the struct, since the `const fn` of the same name made them ambiguous to rustdoc. `README.md` names the other backends and `CHANGELOG.md` records the Windows backend and `ltk::dirs`.
Not done. The print dialog has no CHANGELOG entry nor a page in `docs/widgets.md`. Under the portal there is no preview, since the portal's dialog gets the document only at the end. The desktop card's settings column does not scroll, so with a list open in a low window it may not fit. Tests cover the page ranges and the translation to GTK's keys, not the dialog, CUPS or the portal. On Windows there are no overlays, secondary surfaces or subsurfaces, no preedit for the input method and no accessibility.
2026-11-01 22:49:18 +01:00

5.4 KiB
Raw Permalink Blame History

Windows

The windows feature builds ltk::run for Windows 10 1703 or later: the app runs in a Win32 window with Windows's own frame, drawn through ANGLE's EGL and OpenGL ES when ANGLE is beside the executable, and in software through GDI otherwise. An app's code is the same as on Linux; only the build and what ships with the executable differ.

Building

The backends are exclusive, so a Windows build turns the default wayland feature off:

[target.'cfg(windows)'.dependencies]
ltk = { version = "0.3", default-features = false, features = [ "windows" ] }

[target.'cfg(not(windows))'.dependencies]
ltk = { version = "0.3" }

On Windows, with the MSVC toolchain:

cargo build --release --target x86_64-pc-windows-msvc

From Debian, which packages no std for Windows, std is compiled from rust-src with -Zbuild-std, as for Android, and MinGW links. make windows-deps installs both, and make windows-check typechecks ltk; a build of the same shape is:

RUSTC_BOOTSTRAP=1 \
CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER=x86_64-w64-mingw32-gcc \
cargo build --release -Zbuild-std=std,panic_abort --target x86_64-pc-windows-gnu --no-default-features --features windows

With rustup instead, rustup target add x86_64-pc-windows-gnu installs the target's std and the plain cargo build --target x86_64-pc-windows-gnu works.

A GUI app should not open a console window beside its own; #![windows_subsystem = "windows"] at the top of its main.rs does that, at the cost of losing ltk's [ltk] lines on standard error, so leave it out while debugging.

What ships beside the executable

app.exe
libEGL.dll          ANGLE, optional
libGLESv2.dll       ANGLE, optional
ltk\
    themes\default\ the theme, as ltk-theme-default installs it on Debian
    fonts\          fonts the theme names, found by file name
    keyboards\      on-screen keyboard layouts, if the app uses them
    mime\globs2     shared-mime-info's globs, for ltk::files' MIME types

ltk\ is the app's resource bundle ([ltk::resource_dir]): themes, fonts, keyboard layouts and MIME globs are looked up there as on Android they are in the APK, and a font a theme names by a Debian path, such as /usr/share/fonts/opentype/sora/Sora-Regular.otf, is found in ltk\fonts\ by its file name. An app that keeps its resources elsewhere calls ltk::set_resource_dir before its theme is first used. LTK_THEMES_DIR and LTK_KEYBOARDS_DIR still take precedence, and the user's own themes go under %APPDATA%\ltk\themes\.

ANGLE's two DLLs come with Chromium-based browsers and with the ANGLE releases built for Windows; any recent build that exposes EGL_DEFAULT_DISPLAY on Direct3D 11 works. Without them, or with LTK_FORCE_SOFTWARE=1, the window is drawn in software, and [ltk] render backend: on standard error says which path was taken.

The executable's icon, which the window, the taskbar and Alt+Tab show, is its icon resource 1, as the embed-resource and winres crates put it.

What the backend does

Area On Windows
Window One main window with Windows's frame, titled by App::window_title, its client area sized by App::window_size_hint (800 × 600 logical pixels by default) at the display's DPI, fixed when App::window_resizable says so. App::fullscreen fills the display without the frame. WindowButton and the app's own chrome move, resize and minimise it through Windows's modal loop. The view keeps drawing while the window is moved or resized.
Scale Per-monitor DPI awareness: the scale is the display's DPI over 96, fractional at 125 % or 150 %, and follows the window from one display to another. The accessibility setting for text size (TextScaleFactor) is followed as GNOME's text scale is on Linux.
Input Mouse, wheel and touchpad scrolling, the back side button, touch through WM_POINTER (several fingers), pens as a mouse, and keys with Windows's own repeat, dead keys and AltGr. The input method's committed text arrives as typed characters.
Clipboard The system clipboard, read again whenever it changes. There is no primary selection; the middle button pastes what was selected within the app.
Fonts ltk's embedded Sora as the face, as on Android; the fallback chain for other scripts and emoji is Segoe UI, Nirmala UI, Leelawadee UI, Segoe UI Symbol, Segoe UI Emoji, Malgun Gothic and Microsoft YaHei from C:\Windows\Fonts. Consolas is the monospace face.
Files ltk::files and the file chooser on the file system; the chooser offers the user's profile folders and the root of each drive.
Locale LANG when set, otherwise the user's locale (es-ES as es_ES), for the on-screen keyboard.
Session state App::save_state is saved into %LOCALAPPDATA%\<app_id>\ every 30 seconds, on exit and when the user signs out or shuts down, and handed back to App::restore_state after a crash, or on every launch when App::restore_on_launch says so, as on Wayland.
Folders ltk::dirs gives the user's folders: APPDATA for data and settings, LOCALAPPDATA for the cache and state, USERPROFILE as the home.

Not done yet: overlays, secondary surfaces and subsurfaces are not created; the input method has no preedit or candidate window placement; accessibility is not exposed (AccessKit's Windows adapter would do it); and the built-in on-screen keyboard is off, as on any desktop, while Windows's touch keyboard does not come up for ltk's fields on its own, so a tablet without a keyboard cannot type into them yet.