wlroots-based Wayland compositor with virtual outputs and physical cursor continuity. Originally forked from dwl.
LOC: 5228 total, 2772 vwl.c
- virtual outputs (split physical monitors into independent workspaces)
- physical cursor continuity (smooth cursor movement across monitor gaps)
- master/stack tiling
- tabbed layout
- fullscreen modes (virtual/monitor)
- per-workspace layout state
- XWayland support
mod = logo key
mod+returnspawn terminalmod+dspawn menumod+qkill clientmod+j/kfocus next/prevmod+h/ladjust master widthmod+mzoom (swap master)mod+ftoggle fullscreen (virtual -> monitor -> off)mod+ttoggle tabbed layoutmod+spacecycle layoutmod+shift+equit compositormod+commafocus monitor leftmod+periodfocus monitor rightmod+shift+</>move client to monitor left/rightmod+0-9view workspace0-9mod+shift+0-9move client to workspace0-9mod+ctrl+shift+h/j/k/lmove workspace to virtual output
make./vwlwlroots 0.19wayland-serverxkbcommonlibinputpixman- optional:
xcb,xcb-icccm(for XWayland)
Edit config.def.h and recompile.
Key settings:
- physical cursor gap jumps:
enable_physical_cursor_gap_jumps - virtual output rules:
vorules[] - monitor rules:
monrules[] - keyboard/trackpad settings
Split physical monitors into named regions. Each region gets its own workspace.
Move workspaces between regions with mod+ctrl+shift+hjkl.
static const VirtualOutputRule vorules[] = {
/* monitor name x y w h mfact nmaster lt[0] lt[1] */
{ "DP-1", "left", 0, 0, 960, 1080, 0.55f, 1, &layouts[0], &layouts[1] },
{ "DP-1", "right", 960, 0, 960, 1080, 0.55f, 1, &layouts[0], &layouts[1] },
};w/hof0= expand to monitor's remaining spacemfact= master area factor (0.0-1.0)nmaster= number of master windowslt[0]/lt[1]= primary/secondary layout functions
- virtual fullscreen: fills virtual output region
- monitor fullscreen: fills entire physical monitor
Toggle with mod+f cycles through: off -> virtual -> monitor -> off
Seamless cursor tracking across monitor gaps. Set physical dimensions in monrules[].
static const MonitorRule monrules[] = {
/* name scale transform x y phys{} */
{ "DP-1", 1.0f, WL_OUTPUT_TRANSFORM_NORMAL, 0, 0, {
.width_mm = 520, /* physical width in mm */
.height_mm = 320, /* physical height in mm */
.x_mm = 0, /* physical X position */
.y_mm = 0, /* physical Y position */
.size_is_set = 1, /* use explicit size */
.origin_is_set = 1, /* use explicit origin */
}},
{ NULL, 1, WL_OUTPUT_TRANSFORM_NORMAL, -1, -1, {} }, /* fallback */
};x/y= pixel position (-1,-1 = auto)scale= HiDPI factortransform= rotation (NORMAL/90/180/270/FLIPPED_*)phys{}= real-world dimensions for cursor math
~/.config/vwl/run
#!/bin/sh
waybar &
swayidle -w \
timeout 300 'swaylock -f' \
timeout 600 'wlopm --off \*' \
resume 'wlopm --on \*' \
before-sleep 'swaylock -f' &config.h
static const char *lockcmd[] = { "sh", "-c", "sleep 1 && killall -USR1 swayidle", NULL };
/* ... */
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_L, spawn, {.v = lockcmd} },SIGUSR1 triggers swayidle's lock timeout, ensuring wake-on-input works.
config.h
static const char *screenshotcmd[] = { "sh", "-c",
"grim -g \"$(slurp)\" ~/Pictures/Screenshots/$(date +'%Y-%m-%d_%H-%M-%S').png", NULL };
static const char *screenshotclipboardcmd[] = { "sh", "-c",
"slurp | grim -g - - | wl-copy", NULL };
/* ... */
{ 0, XKB_KEY_Print, spawn, {.v = screenshotcmd} },
{ MODKEY, XKB_KEY_Print, spawn, {.v = screenshotclipboardcmd} },Work in progress, used daily by the dev.