This repository is a MoonBit workspace containing:
modules/window: thewzzc-dev/windowcompatibility library modeled afterwinitmodules/windowing: the backend-neutralMilky2018/windowinghandle module
The window module provides native desktop backends, an experimental Web backend
for wasm-gc, and host-driven mobile event-loop adapters.
- macOS: supported on the
nativetarget through AppKit (wzzc-dev/window/macos) - Windows: preview support on the
nativetarget through Win32 (wzzc-dev/window/windows) - Linux: preview support on the
nativetarget through Wayland + xdg-shell (wzzc-dev/window/linux) - Web: experimental browser support on the
wasm-gctarget (wzzc-dev/window/web) - Android, iOS, HarmonyOS: experimental host-driven adapters on the
nativetarget (wzzc-dev/window/<platform>); the host supplies lifecycle, surface, and input events while the package normalizes them as window events - Placeholder handle types: Xlib and Xcb
- Not supported yet: X11 and other Unix backends
Use the wzzc-dev/window/windows package for Win32 windows and event loops.
The Windows backend currently targets MoonBit native builds.
cmd /k "C:\Program Files (x86)\Microsoft Visual Studio\18\BuildTools\VC\Auxiliary\Build\vcvarsall.bat" x64where.exe cl
moon build modules\window\examples\window_windows --target native
moon run modules\window\examples\window_windows --target nativewhere.exe gcc
moon build modules\window\examples\window_windows --target native
moon run modules\window\examples\window_windows --target nativeUse the wzzc-dev/window/web package for browser-hosted wasm-gc apps.
The Web backend follows the winit Web model: a Window is backed by an
HTMLCanvasElement, DOM events are mapped into @core.WindowEvent, and the
event loop is driven by browser callbacks instead of blocking the current
thread.
Build the example:
moon build modules/window/examples/window_web --target wasm-gcRun the local browser example from the repository root:
node modules/window/examples/window_web/serve.mjsThen open:
http://127.0.0.1:8000/modules/window/examples/window_web/index.html
The generated wasm is loaded from:
_build/wasm-gc/debug/build/examples/window_web/window_web.wasm
Applications using the Web backend need the browser host glue from
modules/window/web/runtime.js. The example index.html shows the expected import object:
window_web: createWindowWebImports()spectest.print_charfor MoonBitprintlnconnectWindowWeb(instance, windowWeb)before calling_start()
The application package must export web_dispatch_event; see
modules/window/examples/window_web/moon.pkg for the link.wasm-gc.exports setting.
Use the wzzc-dev/window/linux package for Wayland windows and event loops.
The first Linux backend supports Wayland + xdg-shell only; X11 is not part of
this backend.
Install the native development dependencies on Linux:
sudo apt install libwayland-dev wayland-protocols wayland-scanner pkg-configBuild and run the example inside a Wayland session or Weston environment:
moon build modules/window/examples/window_linux --target native
moon run modules/window/examples/window_linux --target nativeThe build script uses pkg-config to locate wayland-client and
wayland-scanner to generate the xdg-shell client protocol files during the
prebuild step.
moon add wzzc-dev/windowYou do not need to manually add AppKit/CoreGraphics link flags in your app; the subpackages provide native link configuration.
Import the subpackages you need directly. This module does not expose a
root @wzzc-dev/window package.
Use explicit subpackage imports in your package's moon.pkg:
import {
"wzzc-dev/window/core",
"wzzc-dev/window/macos",
}
supported_targets = "native"
options("is-main": true)Then write the app in main.mbt:
///|
struct App {
mut window : @macos.Window?
}
///|
pub impl @macos.ApplicationHandler for App with can_create_surfaces(
self,
event_loop,
) {
let attrs = @core.WindowAttributes::default().with_title("window demo")
let window : @macos.Window? = Some(event_loop.try_create_window(attrs)) catch {
err => {
println("error creating window: \{err}")
event_loop.exit()
None
}
}
self.window = window
}
///|
pub impl @macos.ApplicationHandler for App with window_event(
self,
event_loop,
_id,
event,
) {
match event {
CloseRequested => event_loop.exit()
SurfaceResized(_) =>
match self.window {
Some(window) => window.request_redraw()
None => ()
}
RedrawRequested => println("redraw requested")
_ => ()
}
}
///|
fn main {
let event_loop = @macos.EventLoop::EventLoop()
event_loop.run_app({ window: None })
}This library follows MoonBit raise-based error handling (typed errors), not
Result. For example:
EventLoop::try_new()may raise@core.EventLoopErrorWindow::set_cursor_position(...)may raise@core.RequestErrorWindow::request_ime_update(...)may raise@core.ImeRequestError
EventLoopmust be created and run on the process main thread.EventLoopBuilder::with_any_thread(true)does not relax this AppKit requirement.- Methods on an existing
Windowmay be called from worker threads. AppKit-backed operations synchronously execute on the process main thread, matching winit's macOS behavior. Do not block the main thread while waiting for a worker that is calling aWindowmethod. monitor_ns_screen(...)returns a retainedNSScreenHandlesnapshot. Keep that handle alive while usingobjc_handle(), and resolve it again after a display reconfiguration.EventLoop::pump_app_events(...)is for host-loop integration, not frame-by-frame rendering. For frame-driven apps, preferrun_app()withControlFlow::PollorControlFlow::WaitUntil.Window::set_cursor_grab(@core.CursorGrabMode::Confined)raises@core.RequestError::NotSupported.Window::drag_resize_window(...)raises@core.RequestError::NotSupportedon macOS.Window::show_window_menu(...)currently has no native AppKit implementation.@core.CustomCursorSource::Url(...)and animation cursors are not supported on macOS.Window::set_prefers_home_indicator_hidden(...),Window::set_prefers_status_bar_hidden(...), andWindow::set_preferred_screen_edges_deferring_system_gestures(...)are parity state setters on macOS (no native AppKit effect).
@macos.Window implements the Milky2018/windowing HasWindowHandle and
HasDisplayHandle contracts. Window::window_handle() returns a structured
WindowHandle whose provider keeps the window owner reachable. Resolve the
platform handle only at the renderer boundary. Renderer packages should import
"Milky2018/windowing" in their moon.pkg:
let raw = try! window.window_handle().as_raw()
match raw {
@windowing.RawWindowHandle::AppKit(handle) => {
let ns_view = handle.ns_view()
// Create the renderer surface from ns_view.
}
_ => abort("renderer does not support this window backend")
}The AppKit variant contains the window content view (NSView*). It is borrowed:
do not release it or retain it beyond the lifetime of the WindowHandle.
Explicitly dropping the underlying window invalidates subsequent as_raw()
calls with HandleError::Unavailable.
For platform-specific integrations that cannot consume windowing,
Window::content_view_handle() remains an AppKit escape hatch and raises
@core.RequestError if the handle is unavailable.
The window package owns the AppKit window/content-view lookup. Renderer
packages such as wgpu_mbt should own Metal or wgpu surface setup on top of
that handle. In particular, downstream code should not scan
NSApplication.windows or use the internal rawId selector to find a window.
For CAMetalLayer integration, create/attach/sync the layer in the renderer
layer using the content-view handle. Keep the layer synchronized with:
Window::scale_factor()forcontentsScaleWindow::surface_size()for physical drawable sizeWindowEvent::SurfaceResizedandWindowEvent::ScaleFactorChangedfor resize/scale resync- the content view bounds for the layer frame
- autoresizing or an explicit renderer-side sync step for future view resizes
Import only the subpackages you need:
@wzzc-dev/window/core: core event/types (WindowEvent,ControlFlow,WindowAttributes, keyboard/mouse/IME data types)@wzzc-dev/window/macos: macOS runtime API (EventLoop,ActiveEventLoop,Window,EventLoopProxy,ApplicationHandler)@wzzc-dev/window/windows: Windows runtime API (EventLoop,ActiveEventLoop,Window,EventLoopProxy,ApplicationHandler)@wzzc-dev/window/linux: Linux Wayland runtime API (EventLoop,ActiveEventLoop,Window,EventLoopProxy,ApplicationHandler) plus Wayland extension APIs exposing display/surface/xdg handles@wzzc-dev/window/web: browserwasm-gcruntime API (EventLoop,ActiveEventLoop,Window,EventLoopProxy,ApplicationHandler) plus Web extension APIs for canvas binding and poll strategy selection@wzzc-dev/window/android,@wzzc-dev/window/ios, and@wzzc-dev/window/harmonyos: host-driven native event-loop adapters for platform lifecycle, surface, and input event normalization@wzzc-dev/window/dpi: logical/physical size and position types@Milky2018/windowing: structured raw handles and provider traits
WindowEvent::into_winit_events() is available when you want a
winit-style compatibility projection.
- Web support currently targets browser environments with
wasm-gc; Node or headless environments without DOM APIs are not the supported runtime. run_app/try_run_app/spawn_appregister browser callbacks and return instead of blocking the thread.ControlFlow::PollusesrequestAnimationFrameby default.Waitresponds to DOM/proxy wakeups, andWaitUntiluses browser timers.- Native-only features such as taskbar integration, native decorations, window levels, system menus, native drag-window, exclusive fullscreen, and precise monitor information are intentionally unsupported or no-op on Web.
- Raw window/display handles return stable placeholder values; use Web
extension APIs such as
Window::canvas_id()for canvas identity.
- Windows support is preview quality and targets desktop Win32 through the
MoonBit
nativebackend. - Use a working C toolchain before building examples. MSVC users should run
vcvarsall.bat; Mingw users should ensuregccis onPATH. - Some APIs that are meaningful on macOS or Web may be state-only, no-op, or
NotSupportedon Windows while parity work continues.
- Linux support currently targets Wayland +
xdg-shell; X11 is intentionally left unsupported in this package. - The first backend attaches a small SHM placeholder buffer so windows map even when the app has not provided a renderer yet.
Window::present_rgba_pixels(...)presents renderer-owned RGBA pixel frames through Waylandwl_shmfor CPU raster renderers such as MoUI Skia.- Keyboard events currently expose native XKB key codes without text decoding; text input and IME are future work.
- Decorations, taskbar integration, system menus, native drag-window, exclusive fullscreen, precise monitor metadata, custom cursors, and rich raw-handle parity are currently unsupported, no-op, or placeholder behavior.
You can also match native event variants directly:
///|
pub impl @macos.ApplicationHandler for App with window_event(
self,
event_loop,
_id,
event,
) {
match event {
PointerMoved(_, position, _, _) =>
println("pointer moved: \{position}")
DragEntered(paths, position) =>
println("drag entered at \{position}: \{paths}")
CloseRequested => event_loop.exit()
_ => ()
}
}The window module includes runnable examples under modules/window/examples/*.
From the repository root, run:
moon run modules/window/examples/window --target native
moon run modules/window/examples/window_windows --target native
moon build modules/window/examples/window_web --target wasm-gc
moon run modules/window/examples/window_linux --target nativeUse the repository gate before publishing or committing backend changes:
scripts/check_ci.shFor the slower upstream-vs-MoonBit example transcript comparison:
RUN_EXAMPLE_TRANSCRIPTS=1 scripts/check_ci.shSee the repository's docs/testing.md for why the local gate uses
moon test --release for framework-linked macOS tests.