2026-02-08.18-15-03.mp4
See all layouts in action at mangowm.github.io
mango-lua is a fork of MangoWC that adds a Lua plugin system for scripting compositor behavior at runtime. Everything outside this section is the upstream MangoWC README and applies unchanged.
It installs alongside the original mango as a separate Wayland session (mango-lua.desktop) and reads the same ~/.config/mango/config.conf. Both can coexist without conflict. Only mango-lua loads ~/.config/mango/lua/init.lua.
⚠️ Provided as-is, and may not be up to date with the upstream. This is a personal fork tracking MangoWC; it is not an official MangoWC project and carries no guarantee of being synced with upstream changes. Use at your own risk.Rebase status (Aug 2026): the fork was rebuilt on upstream 0.15.6 (
c33047e, socket-IPC era). The pre-rebase fork (0.12-era, dwl-ipc) is preserved at tagpre-upstream-rebase-lua.
- User script loaded from
~/.config/mango/lua/init.lua(hot-reloadable viammsg dispatch reload_config) - Hook API — react to window map/unmap/focus/destroy, config reload, status updates, keybindings
- Rule API (
on_rule) — dynamic window placement: override monitor, tags, floating state, geometry per client based on arbitrary Lua logic - IPC bridge — call any Lua global from a shell:
mmsg dispatch lua,<fn>[,arg1,arg2,...] - Timer system —
mango.timer(secs, fn [, repeat])backed by the compositor's event loop - Session save/restore —
SUPER+F2saves,SUPER+F3restores; windows go to the correct monitor and tag - Plugin loader — drop
.luafiles into~/.config/mango/lua/plugins/for auto-loading with lifecycle hooks (setup/teardown)
A Rust daemon (mango-ui) provides native Wayland UI widgets callable from Lua:
mango.ui.menu{...}— interactive picker, blocks the Lua coroutine until the user selectsmango.ui.form{...}— multi-field dialogmango.ui.toast{...}— fire-and-forget notification overlaymango.ui.hud_show{...}/mango.ui.hud_hide{...}— persistent HUD overlays keyed by id
All mango.ui.* calls degrade gracefully when the daemon isn't running.
session— session save/restore (see below)per_tag_mode— switch monitor resolution/refresh rate per tag (e.g. 1080p144 on a gaming tag, 4K60 on a desktop tag)
Same dependencies as upstream MangoWC, plus LuaJIT (preferred) or Lua 5.4:
# Arch / CachyOS
yay -S luajit
# Build
meson setup build
ninja -C build
# Install as a parallel Wayland session (does NOT replace /usr/bin/mango)
sudo ./install-lua-session.sh
# → /usr/local/bin/mango-lua
# → /usr/share/wayland-sessions/mango-lua.desktopThe Lua plugin system is auto-detected at build time (-DLUA_PLUGIN is added automatically when LuaJIT or Lua 5.4 is found). If neither is present the build falls back to unmodified upstream behavior.
# Copy the default init.lua (plugin auto-loader)
mkdir -p ~/.config/mango/lua/plugins
cp assets/init.lua.default ~/.config/mango/lua/init.lua
# Set up autostart (starts mango-ui if installed; add your own entries below)
cp assets/autostart.sh.example ~/.config/mango/autostart.sh
chmod +x ~/.config/mango/autostart.shLog in via the Mango (Lua) session in your display manager.
mango.on("on_rule", function(c) ... end) -- dynamic window rules (return overrides table or nil)
mango.on("on_client_map", function(c) ... end) -- window appears on screen
mango.on("on_client_unmap", function(c) ... end) -- window hidden
mango.on("on_client_destroy", function(appid, title, pid) ... end)
mango.on("on_focus_change", function(c, prev) ... end)
mango.on("on_config_reload", function() ... end)
mango.on("on_before_reload", function() ... end) -- teardown before Lua state is rebuilt
mango.on("on_status_change", function() ... end) -- compositor status pulse (layout/focus/tag changes)
mango.on("on_tag_view", function(mon, new_tags, old_tags) ... end)Return a table from on_rule to override placement; return nil to fall through to the next rule:
mango.on("on_rule", function(c)
if c.app_id == "kitty" then
return {
tags = 2, -- tag bitmask (bit 1 = tag 1, bit 2 = tag 2, ...)
monitor = "DP-1", -- monitor name as reported by mango
is_floating = false,
}
end
if c.app_id == "org.kde.dolphin" then
return { tags = 4, monitor = "DP-2", is_floating = true,
float_x = 100, float_y = 100, float_w = 1200, float_h = 800 }
end
end)| Field | Type | Description |
|---|---|---|
c.handle |
int | Stable client handle (pass to API calls) |
c.id |
int | Compositor client id (read-only; correlates with mmsg get client <id>) |
c.app_id |
string | XDG app_id or X11 class |
c.title |
string | Window title |
c.pid |
int | Process ID |
c.monitor |
string | Current monitor name |
c.tags |
int | Current tag bitmask |
c.is_floating |
bool | Floating state |
c.is_fullscreen |
bool | True fullscreen state |
c.is_maximized |
bool | Maximized state (separate from fullscreen) |
c.stack_index |
int | Stacking position (< 0 means not yet mapped) |
Tag count is dynamic (1..31, set by tag_num in config.conf) — never hardcode 9. Use mango.tag_count() or monitor.tag_count.
-- Window management
mango.move_to_monitor(handle, "DP-2")
mango.set_tags(handle, bitmask)
mango.focus(handle)
mango.set_opacity(handle, focused_opacity, unfocused_opacity) -- -1 to skip either
-- Process / spawn
mango.spawn(cmd) -- fork+exec, no return value
mango.spawn_pid(cmd) -- fork+exec, returns child PID
mango.proc_ancestors(pid) -- walks /proc PPid chain, returns list of PIDs
-- Client queries
mango.get_clients_by_appid("kitty") -- array of client tables
mango.get_client_by_pid(pid) -- single client table or nil
-- Config
mango.get_config("border_width")
mango.set_config("border_width", "2") -- hot-applies the config change
-- Timers
local id = mango.timer(secs, fn) -- one-shot
local id = mango.timer(secs, fn, true) -- repeating
mango.timer_cancel(id)
-- Output / monitor
mango.get_output_modes("DP-1") -- list of {w,h,refresh_mhz} tables
mango.set_output_mode("DP-1", {w=1920, h=1080, refresh_mhz=144000})
-- Filesystem helpers
mango.list_dir(path) -- array of entry names
mango.stat(path) -- {is_file, is_dir, size} or nil
-- Misc
mango.log("message") -- prints to compositor log (stderr)
mango.setenv("VAR", "value") -- sets env for future spawned children
mango.view_tag(monitor_name, bitmask) -- switch a monitor to view a tag
mango.tag_count() -- dynamic tag count (config.tag_num, 1..31)The fork rides on upstream's socket IPC (mmsg, see docs/ipc.md) and adds one dispatch target, lua, which invokes a Lua global in the live compositor:
# Invoke a Lua global (up to ~5 comma-separated string args)
mmsg dispatch lua,<function_name>[,arg1,arg2,...]
# e.g. trigger the bundled session plugin
mmsg dispatch lua,session_save
mmsg dispatch lua,session_restore
# Hot-reload config + init.lua (runs on_before_reload then re-sources the file)
mmsg dispatch reload_configmmsg requires the MANGO_INSTANCE_SIGNATURE environment variable, which the compositor sets automatically for processes it spawns (terminals, autostart entries). Any mmsg built from this tree works — upstream and fork now share the same socket protocol; only the lua,... dispatch target is fork-specific.
Migrating from the old (dwl-ipc) CLI:
| Old (≤ dwl-ipc era) | New |
|---|---|
mmsg -s -d lua,session_save |
mmsg dispatch lua,session_save |
mmsg -s -d reload_config |
mmsg dispatch reload_config |
mmsg -g |
mmsg get focusing-client (or all-clients, …) |
mmsg -w |
mmsg watch all-clients (or any watch target) |
Built-in session persistence, wired to SUPER+F2 (save) and SUPER+F3 (restore) by default:
SUPER+F2— saves current window layout to~/.config/mango/session.jsonSUPER+F3— spawns windows from the session file and places them on the correct monitor + tag- Rotating backups (
session-bak-<timestamp>.json, up to 8 kept, max 1 per 30 min) guard against accidental overwrites - Autosave fires every 5 minutes but is disarmed until the first manual
SUPER+F2save, preventing a bad restore from overwriting a good session - Outcome verification: after each window maps, the restore system compares actual placement against the stored entry and force-corrects any mismatch
Mango starts where dwl ends. It keeps the lightweight, fast-build philosophy while adding the features that make a compositor actually usable day-to-day — without the bloat.
- Lightweight & fast — as lean as dwl, builds in seconds, no functionality compromised
- Excellent xwayland support — run X11 apps without friction
- Tags, not workspaces — each tag maintains its own independent window layout
- Smooth animations — window open/move/close, tag transitions, layer surfaces
- Flexible layouts — scroller, master-stack, monocle, dwindle, grid, and more
- Rich window states — swallow, minimize, maximize, global, overlay, fakefullscreen
- Window effects — blur, shadow, corner radius, opacity (via scenefx)
- Excellent input method support — text-input v2/v3
- Sway-like scratchpad — named scratchpad support included
- Hycov-style overview — see all windows at a glance
- IPC — send/receive messages from external programs
- Hot-reload config — no restart needed for keybinding changes
- Zero flickering — every frame is correct
Stability first. After months of testing, Mango is solid enough for daily use. Breaking changes will be minimal.
Practicality over novelty. Features get added when they genuinely improve daily workflows — not for the sake of completeness.
Focused scope. Niche requests are evaluated by community interest. Significant upvotes move things forward.
yay -S mangowm-git- install dependencies
yay -S rofi foot xdg-desktop-portal-wlr swaybg waybar wl-clip-persist cliphist wl-clipboard wlsunset xfce-polkit swaync pamixer wlr-dpms sway-audio-idle-inhibit-git swayidle dimland-git brightnessctl swayosd wlr-randr grim slurp satty swaylock-effects-git wlogout sox
- clone config
git clone https://github.com/DreamMaoMao/mango-config.git ~/.config/mango
See the Installation Guide for Fedora, Gentoo, Guix, NixOS, openSUSE, PikaOS, AerynOS, and building from source.
- mangowm.github.io — website docs with configuration reference, keybindings, layouts, IPC, and more
- GitHub Wiki — community-maintained wiki
Join us on Discord
- wlroots — Wayland protocol implementation
- dwl — the foundation Mango builds on
- scenefx — window effects library
- owl — animation groundwork
- sway — protocol reference
If Mango makes your desktop better, consider supporting its development.
Thanks to everyone who has sponsored this project:
|
dl09r |
tonybanters |
vinthara |
Crypto donations accepted:
|
Network: BEP20 (BSC) Address: 0xf9cda472f2556671d2504afc4c35340ec5615da1
|
|

