Skip to content

Pluggable window API for custom UI applications #210

@stuffbucket

Description

@stuffbucket

Is your feature request related to a problem? Please describe.
I've been working on Lima PRs to enhance VZ display and audio support (#4476, #4480). The current StartGraphicApplication() API bundles everything together — creates a window, wraps the view in a scroll view, and blocks on the event loop. I had to swizzle NSScrollView to disable the scrollbars (they're hardcoded to YES and cut off part of the display). There were concerns from @AkihiroSuda about shipping Objective-C runtime hacks in Lima, so I thought I would add support for custom GUI UX to this project.

Describe the solution you'd like
A pluggable window API that separates window creation from the event loop, enabling multi-VM support and custom UI integration. Minimal Go API surface that maintains backward compatibility while eliminating the need for runtime hacks. Demonstrated through a gui-linux example with VM registry, lifecycle management, and custom menus. Working implementation: stuffbucket/vz@v0.0.1-pluggable-window-api. I considered splitting the API between Go and Objective-C — exposing raw types and letting developers handle the platform layer themselves. That works for applications willing to write Objective-C, but this is a Go library. The approach here surfaces a minimal Go API that allows pluggable UX while maintaining backward compatibility. Applications that need deeper customization can still drop into Objective-C, but common patterns work from Go.

Describe alternatives you've considered
Fork vz for Lima — Adds maintenance burden and diverges from upstream.
Swizzling in Lima — What I have now in #4476. It works, but it's fragile. The swizzle intercepts every NSScrollView.setDocumentView: call and checks if the view is a VZVirtualMachineView.
Add scrollbar option only — Solves the immediate Lima problem but not the broader limitation.
Expose view via callback — Pass a callback to StartGraphicApplication() that receives the view before the event loop starts. Doesn't help with multi-VM.

Additional context
Working implementation: stuffbucket/vz@v0.0.1-pluggable-window-api
Diff against upstream: roughly +2100/-1300 lines, though much of that is moving code between files.

  1. Separate window creation from event loop
    New Go API:

CreateWindow(width, height, opts...) — shows the window, returns immediately
CreateVMView() — returns the raw VZVirtualMachineView* for custom embedding
RunApplication() — runs the event loop when you're ready
StartGraphicApplication() — unchanged, now calls CreateWindow() + RunApplication() internally

Applications can create multiple VM windows before starting the event loop, or use their own event loop.
2. Reorganize Objective-C infrastructure
Moved app lifecycle code from virtualization_12.m into new files:

virtualization_default_app.h — public interface for app infrastructure
virtualization_default_app.m — VMWindowController, AppDelegate, default menus

The library provides standard macOS menus (App menu with About/Quit, Window menu, Help menu). Applications needing custom menus add them in their own Objective-C code. VMWindowController now supports multiple instances tracked by AppDelegate.
3. Updated gui-linux example
Multi-VM pattern demonstration:

VM registry persisted to ~/GUI Linux VM/registry.json
CLI subcommands: start [name], create , list, delete
Custom File menu in app_menu.m with "Start VM…" and "Create New VM…" dialogs
Go exports that Objective-C menu handlers call back into

Testing
Built and ran on macOS 15.2 (Sequoia) and macOS 26.2 (Tahoe):

Library tests: make test passes (code-signed test binary with entitlements)
Single VM: ./virtualization start launches default VM, window works as before
Multi-VM: Started three Fedora 41 live ISO instances concurrently via File menu. Each gets its own window, independent lifecycle. Windows menu shows all three.
CLI workflow: Created VMs via ./virtualization create, listed them, started specific ones, deleted them
Backward compatibility: StartGraphicApplication() still blocks and behaves identically to upstream

The registry tracks VM state across runs — ISOs associated with VMs, bundle directory structure.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions