Muxy

Constraints

Supported platforms, and lessons that shape the code.

Platforms

PartSupported
Desktop appmacOS 14 and later
muxy and muxy-servermacOS 14 and later, and Linux with glibc 2.35 or later, on x86_64 and ARM64
Mobile SDKiOS and Android

Not yet supported: Windows, and musl or Alpine Linux. Linux builds must be built and run natively on both architectures to count as supported.

Experimental Linux desktop

The Linux desktop port is under development. It is tried on Ubuntu 24.04 ARM64 in SwiftVM on Apple Silicon, with GNOME and KDE Plasma, each on Wayland and X11. This does not establish support for other distributions, x86_64 desktop builds, or hardware GPU drivers. SwiftVM uses Mesa's software Vulkan renderer, so its performance is not representative of a native GPU.

The system draws the window: frame, buttons, shadow, resizing and tiling. GNOME on Wayland draws no frame, so there the app shows its own buttons and the window cannot be resized from its edges.

The port covers the terminal desktop, in-app dialogs and menus, desktop-portal file selection, desktop notifications, Linux shortcuts, and xdg-open for external resources. Commands that macOS offers in the menu bar are in the command palette. Copy and paste use Ctrl+Shift+C and Ctrl+Shift+V in terminals; text fields use Ctrl+C and Ctrl+V. Links in a terminal open with Ctrl+click. Existing configured shortcuts remain literal. New configurations use the system monospace font and inline paths for composer images.

One app runs per profile: a second launch, and muxy <folder>, hand their folders to the app that is running. The app, the muxy tool and the server are installed in one directory and find each other there.

Extensions run on the system's WebKitGTK 4.1, version 2.40 or later, which the app loads once an extension is enabled. Without it the app still runs, and Settings says why extensions are off. Scripts use its JavaScriptCore, the engine macOS uses. A GPUI window cannot hold a WebKitGTK view, so pages render off screen and the app draws them and passes them input. Browser panes use the same renderer and need WebKitGTK 2.42 or later for cookie automation. On Linux a page therefore has:

  • no context menu, tooltips, or drag and drop;
  • text only on the clipboard, and no clipboard inside a frame;
  • no text from an input method that composes it, as for Chinese or Japanese;
  • an arrow, a hand, or a text cursor, and no other pointer shape;
  • content processes outside WebKit's sandbox where the system forbids the sandbox, as Ubuntu 24.04 does for an app without an AppArmor profile.

On Ubuntu, download muxy-desktop.apparmor from the latest stable release to allow WebKit's sandbox at the default installation paths for all three Linux formats. An administrator can install it:

sudo install -m 644 muxy-desktop.apparmor /etc/apparmor.d/muxy-desktop
sudo apparmor_parser -r /etc/apparmor.d/muxy-desktop

Adjust its attachment paths for a custom XDG_DATA_HOME, then fully quit and reopen Muxy. The profile permits user-namespace creation; it does not confine the whole terminal app. Installers do not change system security settings. Remove the optional profile only when no installed channel or format needs it.

Rendering needs a working Vulkan 1.1 driver and the extensions required by GPUI's Blade renderer. Use your GPU vendor's driver and your desktop's portal backend: xdg-desktop-portal-gnome, xdg-desktop-portal-kde or xdg-desktop-portal-gtk.

SF Symbols exist only on macOS. Linux draws a symbol from a bundled icon when there is one, and an extension's symbol without one as the extension icon.

Quick Terminal has an experimental X11 backend. Its global shortcut is released while recording a replacement, and hiding the window keeps its terminal session. Wayland and XWayland report it unavailable: the pinned window backend cannot place and hide a dropdown or use a portal activation token, and XWayland grabs do not cover native Wayland apps. The macOS panel is unchanged.

Voice input uses local parec capture and whisper-cli transcription. Install PulseAudio's utilities (pulseaudio-utils on Ubuntu) and whisper.cpp, v1.9.5 or later. The audio service must expose its local PulseAudio socket, as PipeWire's PulseAudio compatibility service does. Muxy records the default input device; choose it and control access in the desktop's sound settings. There is no separate microphone permission dialog in this backend.

Download a multilingual GGML model, such as ggml-base.bin (about 142 MiB), from whisper.cpp's model collection. Select its absolute path in Settings → Composer → Offline voice model, or put it at $XDG_DATA_HOME/muxy/voice/ggml-base.bin (by default ~/.local/share/muxy/voice/ggml-base.bin). English-only models are rejected so they cannot silently override the selected language.

Also install the Silero speech detection model ggml-silero-v6.2.0.bin (885,098 bytes) at $XDG_DATA_HOME/muxy/voice/ggml-silero-v6.2.0.bin, or ~/.local/share/muxy/voice/ggml-silero-v6.2.0.bin when XDG_DATA_HOME is unset. Its SHA256 is 2aa269b785eeb53a82983a20501ddf7c1d9c48e33ab63a41391ac6c9f7fb6987. This model is required even when the voice model is stored elsewhere. Muxy checks for it before opening the microphone and uses it to discard audio without detected speech before transcription. This reduces invented text from silence or keyboard noise; speech detection and recognition can still make mistakes. Model downloads are manual; Muxy does not download anything for dictation.

Audio stays in memory and is sent through a pipe to the local model when the user finishes. There is no live transcript or network transcription. Pause releases the microphone, and Cancel stops capture or transcription. A recording pauses at ten minutes, keeping its audio available for Finish. Transcription uses at most four CPU threads and times out after ten minutes. One dictation session can run in the app at a time. macOS keeps its native speech service and resolves language selections imported from Linux to installed speech locales.

Linux releases installed with the user-local installer use a versioned installation with an atomic current link, verified downloads and the shared session-compatibility rules. Debian packages are managed by APT; AppImages are replaced manually. Neither uses the in-app updater. AppImages retain a private, unpacked copy so background servers and CLI commands do not depend on a temporary FUSE mount. See Linux desktop installation and updates. The package baseline is Ubuntu 24.04 and glibc 2.39; this does not change the standalone CLI/server floor of 2.35. Clipboard image submission is unavailable because the current clipboard API cannot safely preserve all MIME types and detect ownership changes. Use inline image paths instead.

To build on Ubuntu, install Rust from rust-toolchain.toml, Zig 0.15.2, and the development packages build-essential pkg-config clang cmake libssl-dev libfontconfig1-dev libfreetype-dev libxkbcommon-dev libxkbcommon-x11-dev libwayland-dev libxcb1-dev libvulkan-dev. Runtime packages include mesa-vulkan-drivers xdg-desktop-portal xdg-desktop-portal-gnome xdg-utils, and libwebkit2gtk-4.1-0 for extensions. The desktop checks also need xvfb and xauth for isolated X11 shortcut and window-lifecycle tests. Then run bash scripts/build-desktop-linux.sh. MUXY_ZIG can point at Zig, and CARGO_TARGET_DIR must point to a guest-local writable directory when the source checkout is shared read-only. Keep muxy-app, muxy, and muxy-server together.

Visual checks are performed by a person: start the app from a desktop terminal, open a project, create and split terminals, exercise shell input and Ctrl+C, copy/paste Unicode text, scroll history, open Settings and a folder picker, then cancel a close confirmation before confirming another. Check both Wayland and X11 sessions. Next milestones are native x86_64 and hardware GPU validation, package installation and upgrade acceptance, and the deferred platform backends.

On Linux hosts that end a user's processes at logout, run loginctl enable-linger so the server keeps running after an SSH session ends.

Turning on mobile access may show a macOS firewall prompt for muxy-server, and the iOS app needs local network permission.

macOS charges a process's privacy permissions, such as Local Network, to the app process that started it. The desktop app starts the server through a small copy of itself that lives as long as the server, so terminals keep Muxy's permissions after the app quits.

Terminal engine

  • Ghostty's terminal isn't thread-safe, so one thread owns each terminal.
  • The history limit is in bytes.
  • The server decides when to compress history, once a session goes quiet.
  • Measure memory as physical footprint, not RSS. Compressed pages are released to the system but not freed, so RSS doesn't show the savings.
  • Ghostty's Rust API is unstable. Pin its version and keep it inside muxy-terminal.

PTYs

  • How fast a terminal fills up depends on how the program writes, not on parsing. In our benchmarks, a program that writes line by line reached about 12 MB/s on macOS. One that writes large blocks reached 280 MB/s through the same PTY.
  • Reads therefore arrive small and often. The reader must be its own blocking thread. Sleeping to batch reads would stall the program, because the kernel's PTY buffer holds only a few kilobytes.

Memory and drawing

  • A queue that grows with output explodes when an app stalls. Merge instead of queueing (D8).
  • Redraw only when something changes. Heavy per-cell color changes are the worst case: merge rectangles by color and skip blank text before reaching for caches.
  • Measure again after changing the terminal engine, the screen format, or flow control.

Shell integration

The server loads its shell hooks for zsh and fish automatically, without editing your startup files. Bash is opt-in. Add this to the file your Bash profile loads for interactive shells:

if [[ ${MUXY_SHELL_INTEGRATION:-0} == 1 ]]; then
    source "$MUXY_SHELL_INTEGRATION_DIR/muxy.bash"
fi

If you already have a Bash DEBUG trap, it is kept. Jumping between prompts still works, but command start and exit status aren't marked. Turning shell integration off in Settings applies to new terminals right away.