Skip to content

Repository files navigation

Mango Logo

Mango Wayland Compositor

A fast, feature-rich Wayland compositor built on dwl

Stars License Packaged in Discord


2026-02-08.18-15-03.mp4

See all layouts in action at mangowm.github.io


mango-lua (fork)

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 tag pre-upstream-rebase-lua.

What this fork adds

Lua plugin system

  • User script loaded from ~/.config/mango/lua/init.lua (hot-reloadable via mmsg 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 systemmango.timer(secs, fn [, repeat]) backed by the compositor's event loop
  • Session save/restoreSUPER+F2 saves, SUPER+F3 restores; windows go to the correct monitor and tag
  • Plugin loader — drop .lua files into ~/.config/mango/lua/plugins/ for auto-loading with lifecycle hooks (setup / teardown)

mango-ui companion daemon

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 selects
  • mango.ui.form{...} — multi-field dialog
  • mango.ui.toast{...} — fire-and-forget notification overlay
  • mango.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.

Bundled plugins (assets/plugins/)

  • 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)

Build & parallel install

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.desktop

The 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.

Quick start

# 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.sh

Log in via the Mango (Lua) session in your display manager.

Lua API

Hooks

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)

Window rule hook

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)

Client fields available in hooks

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.

Runtime API

-- 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)

IPC from shell

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_config

mmsg 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)

Session save / restore

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.json
  • SUPER+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+F2 save, 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

Why Mango?

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

Vision

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.

Installation

Packaging status

Arch Linux

yay -S mangowm-git

use my config

  • 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

Other distributions

See the Installation Guide for Fedora, Gentoo, Guix, NixOS, openSUSE, PikaOS, AerynOS, and building from source.

Documentation

  • mangowm.github.io — website docs with configuration reference, keybindings, layouts, IPC, and more
  • GitHub Wiki — community-maintained wiki

Community

Join us on Discord

Acknowledgements

  • wlroots — Wayland protocol implementation
  • dwl — the foundation Mango builds on
  • scenefx — window effects library
  • owl — animation groundwork
  • sway — protocol reference

Sponsor

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
sponsor QR

About

MangoWC fork with Lua plugin system

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages