A person who wants to see komorebi tile windows should not have to configure a machine first. `run-kde-x86_64` and `run-kde-aarch64` are the session guest with a demo on it: the NixOS module configures komorebi from a typed `config` (eight workspaces, borders, animation) and the sample skhd bindings, and at login the guest opens two Konsole windows, Kate and Dolphin, which komorebi tiles, and a cheat sheet window that it leaves alone, with the key bindings, `komorebic` commands to try, what KWin draws with, and the output of `komorebic check`. The closure is the same for everyone. No password or key is in it: the host shares a directory, which the guest mounts at /run/run-kde, and a unit copies the RDP password and the ssh public key from it, since the files can belong to a user of the host who is not the guest user. krdp.nix reads the password from the file that `komorebi.krdp.passwordFile` names when it exists. The display device, the port forwards, the memory and whether to insist on KVM are left to the environment of the runner, so that one closure and one disk run with a software virtio-vga or with a virtio-vga-gl that renders on the host GPU. The module is a separate file that does not import plasma-vm.nix, so that a VM suite can put it on the session node. plasma-guests-eval asserts the same of the demo guests.
16 KiB
Plasma guests to develop in
The VM tests boot a machine, run a scenario and power it off. To try komorebi by hand against a real KWin, the flake has NixOS guests with Plasma 6 that stay up. Each guest is made for one kind of host, because the accelerator of a host (HVF on a Mac, KVM on Linux) runs guests of the host's own architecture:
| Guest | Host | Runs under | What it is |
|---|---|---|---|
plasma-vm |
Apple silicon Mac | QEMU from nixpkgs, with HVF | Plasma installed, no login: a console and ssh, to run kwin_wayland --virtual and build komorebi from the repository |
plasma-vm-session |
Apple silicon Mac | QEMU from nixpkgs, with HVF | the same, plus a Plasma Wayland session that logs in by itself, komorebi running in it, and an RDP server |
plasma-utm |
Apple silicon Mac | UTM, with a GPU | the session guest as a disk image that boots by UEFI |
plasma-vm-x86_64, plasma-vm-session-x86_64 |
Linux on x86_64 | QEMU from nixpkgs, with KVM | the first two, as x86_64-linux guests |
plasma-vm-aarch64, plasma-vm-session-aarch64 |
Linux on aarch64 | QEMU from nixpkgs, with KVM | the first two, as aarch64-linux guests |
run-kde-x86_64, run-kde-aarch64 |
Linux on x86_64, on aarch64 | QEMU from nixpkgs, with KVM | the session guest with a demo on it, which run-kde starts (below) |
The guests of a Mac are aarch64-linux. A guest of a Linux host that has no KVM does not start:
the runner refuses, because Plasma under software emulation is too slow to try anything.
They are nixosConfigurations of the flake, made from
nix/dev/plasma-vm.nix,
nix/dev/plasma-utm.nix and
nix/dev/krdp.nix, by mkPlasmaGuest in
flake.nix, which takes the system of the guest and the packages of the machine
that runs QEMU. Each one installs komorebi with the flake's NixOS
module, services.komorebi, so the komorebi in a guest is the one of the commit the guest was
built from, and in the session guests the module's unit starts it at login. They carry no key,
no password and no configuration of anyone's.
In the QEMU guests the guest has a plain virtio-gpu, which has no 3D, so KWin composites on the
CPU (llvmpipe) and every frame then has to be encoded to reach an RDP client. That is slow. On a
Mac the QEMU in nixpkgs cannot do better: it has no 3D at all. UTM's QEMU has virtio-gpu-gl,
which gives the guest OpenGL through Mesa's VirGL, rendered on the Mac's GPU, and UTM shows the
guest in a window of its own, so there is no RDP in the path at all. The QEMU of an x86_64 Linux
host has and a window (GTK), at time of writing (QEMU 11.1.2 from nixpkgs, 2026-10-28), which these guests
do not use.
What a builder has to be, on a Mac
A Mac cannot build a Linux system, so the guests are built on a builder: a machine with Nix that
builds for aarch64-linux and that the Mac can reach as a remote store, such as
ssh-ng://builder@localhost. Any builder will do: the one nix run nixpkgs#darwin.linux-builder
starts, a Linux machine on the network, or the one the VM tests use on a Mac
(vm-tests.md). A Plasma system is a closure of about 10 GB,
and the builder needs room for it and for its build: the defaults of darwin.linux-builder (one
core, 3 GB of memory, a 20 GB disk) are too small. Six cores and 10 GB of memory worked, at time
of writing (2026-10-27).
The examples below use the same variables as scripts/vm-mac:
export KOMOREBI_BUILDER=ssh-ng://builder@localhost # the store URL of your builder
export NIX_SSHOPTS="-p 31022 -i $HOME/.ssh/builder_ed25519 -o IdentitiesOnly=yes" # if it needs them
The QEMU guests on a Mac
The system is built on the builder and copied back, and only the QEMU runner, which is a program
for the Mac, is built on the Mac. A QEMU guest runs the system of its vmVariant (which adds the
disk and the shared directory), not the system of the configuration itself:
guest=plasma-vm-session # or plasma-vm
config=".#nixosConfigurations.$guest.config"
system=$(nix build "$config.virtualisation.vmVariant.system.build.toplevel" \
--eval-store auto --store "$KOMOREBI_BUILDER" --no-link --print-out-paths)
nix copy --from "$KOMOREBI_BUILDER" --substitute-on-destination --no-check-sigs "$system"
nix build "$config.system.build.vm" --builders '' -o .vm/result-$guest
The guest mounts the directory KOMOREBI_REPO names at /repo, and makes its disk,
plasma-vm.qcow2 or plasma-vm-session.qcow2, in the directory it is started from on its first
start, and keeps it there. .vm is ignored by git:
cd .vm && KOMOREBI_REPO=$PWD/.. ./result-$guest/bin/run-$guest-vm
The terminal is the guest's console, logged in as the user komorebi, who can use sudo with no
password. ssh is forwarded from localhost:2222 (plasma-vm) or localhost:2223
(plasma-vm-session) and takes keys only: put yours in ~/.ssh/authorized_keys from the console
once, and the disk keeps it. Both guests can run at once.
In the guest, /repo is the repository and CARGO_TARGET_DIR is in the guest's home, since a
target directory on the shared directory is slow. The session guest shows nothing on the Mac; it
is reached over RDP (below).
The QEMU guests on a Linux machine
A Linux host builds a guest itself, so there is no builder to set up. It needs Nix with flakes, KVM
(a /dev/kvm that the user can open, which on most distributions means the group kvm), and room
for the closure (about 10 GB) and for the guest's disk (20 GB, sparse). The runner is built with
its system, and started the same way as on a Mac:
guest=plasma-vm-session-x86_64 # or plasma-vm-x86_64, or the -aarch64 pair on an aarch64 host
nix build ".#nixosConfigurations.$guest.config.system.build.vm" -o .vm/result-$guest
cd .vm && KOMOREBI_REPO=$PWD/.. ./result-$guest/bin/run-*-vm
The console, the user, the ssh ports (2222 and 2223), the RDP port, the disk and /repo are as
described for a Mac. What differs is what nixpkgs' runner does on a Linux host:
- It starts one
virtiofsdfor each shared directory, as the user who runs the guest, and the guest mounts them by virtiofs where a Mac's guest mounts them by 9p. One of them is the host's/nix/store, so the guest has the closure without a copy of it. - It refuses to start when
/dev/kvmis missing or cannot be opened by the user, since the guests setvirtualisation.qemu.forceAccel.
A guest has one display device, a virtio-gpu, so that KWin has a DRM device to draw on. The machine
of an x86_64 guest has a VGA of its own, which would be a second display next to the virtio-gpu,
so the guest has virtio-vga (a virtio-gpu that is also a VGA) in its place and -vga none. The
session guest shows nothing on the host, as on a Mac: it is reached over RDP (below).
The demo guest
run-kde-x86_64 and run-kde-aarch64 are the session guest with a demo on it, made by
nix/dev/run-kde.nix on top of plasma-vm.nix. komorebi is
configured by the module's typed config (eight workspaces with a layout each, borders and
animation) and by the sample key bindings, docs/skhdrc.sample, in the module's default mode. At
login the guest opens two Konsole windows, Kate and Dolphin, which komorebi tiles, and a cheat sheet
that it leaves alone: the main key bindings, komorebic commands to try, what KWin draws with, and
the output of komorebic check for the configuration the daemon runs. The VM suite run-kde boots
the same module on the session node.
The closure is the same for everyone. No password, key or configuration of a person is in it, and what a person's run needs comes from the environment of the runner:
| Variable | What the guest does with it |
|---|---|
KOMOREBI_REPO |
the directory mounted at /repo |
RUN_KDE_STATE |
the directory mounted at /run/run-kde, with rdp-password (the password for RDP) and ssh_key.pub (the public key that may log in over ssh) |
RUN_KDE_MEMORY |
the memory in MiB, which has to be the -m of the same run |
QEMU_NET_OPTS |
the port forwards, as hostfwd=tcp:127.0.0.1:2223-:22,hostfwd=tcp:127.0.0.1:3389-:3389 |
QEMU_OPTS |
the display device: -vga none -device virtio-vga,xres=1280,yres=800, or -display gtk,gl=on -vga none -device virtio-vga-gl,xres=1280,yres=800 for a GPU |
NIX_DISK_IMAGE |
the disk image, which the runner makes when it is not there |
run-kde sets them; to start the guest by hand, on a Linux machine:
nix build .#nixosConfigurations.run-kde-x86_64.config.system.build.vm -o .vm/result-run-kde
mkdir -m 700 .vm/state && cd .vm
ssh-keygen -q -t ed25519 -N '' -f state/ssh_key
printf %s "a password" > state/rdp-password
KOMOREBI_REPO=$PWD/.. RUN_KDE_STATE=$PWD/state RUN_KDE_MEMORY=6144 \
QEMU_NET_OPTS=hostfwd=tcp:127.0.0.1:2223-:22,hostfwd=tcp:127.0.0.1:3389-:3389 \
QEMU_OPTS='-cpu host -vga none -device virtio-vga,xres=1280,yres=800' \
./result-run-kde/bin/run-komorebi-demo-vm -m 6144 -smp 4
The guest differs from plasma-vm-session in what the runner of nixpkgs would otherwise fix when
the closure is built:
- The display device is not part of the guest (
komorebi.plasmaVm.displayis false). The same closure and the same disk run with the softwarevirtio-vgaor withvirtio-vga-gl, which renders on the host's GPU through VirGL, as the runner's environment says. - The ports are not forwarded by the guest, so that a person can choose them.
- The runner does not insist on KVM (
qemu.forceAccelis false):run-kdechecks for it and lets--tcgstart a guest without. - The shared memory that virtiofs needs is a memory backend of the guest's memory size, which
nixpkgs fixes at
memorySizein the command line of the runner. The guest makes its own, fromRUN_KDE_MEMORY, so that a run can have less memory than the closure's 8 GB. - The directory
/run/run-kdeis mounted after the boot and not in the initrd, where a mount under/runis hidden by the/runthat the booted system mounts. - The files in it can belong to a user of the host who is not the guest's user, and the guest
cannot read the directory then. The unit
run-kde-credentialscopies the two files as root, to/run/run-kde-guest, where the RDP unit (komorebi.krdp.passwordFile) and sshd (AuthorizedKeysFile) read them.
The UTM disk
UTM boots plasma-utm from a disk image. The image is made by installing the system onto a blank
disk from a running Linux system with nixos-install, which needs no virtualization on the
builder: here, the plasma-vm guest, which has the tools for it. Build both systems first: the
plasma-vm runner as above, and the plasma-utm system, on the builder, copied back:
system=$(nix build .#nixosConfigurations.plasma-utm.config.system.build.toplevel \
--eval-store auto --store "$KOMOREBI_BUILDER" --no-link --print-out-paths)
nix copy --from "$KOMOREBI_BUILDER" --substitute-on-destination --no-check-sigs "$system"
The closure goes into the guest as one file that nix-store --export writes, and not through the
guest's view of the Mac's store: macOS stores files whose names differ only by case under other
names (~nix~case~hack~1), which a guest reading the directory sees, and nix then rejects the
hashes. An export undoes that. Then boot plasma-vm with a blank disk of 40 GB attached:
mkdir -p .vm/utm && cd .vm
nix-store --export $(nix-store -qR "$system") > utm/plasma-utm.closure
echo "$system" > utm/plasma-utm.system
nix shell nixpkgs#qemu --command qemu-img create -f raw utm/plasma.raw 40G
QEMU_OPTS="-drive file=$PWD/utm/plasma.raw,format=raw,if=none,id=utm -device virtio-blk-pci,drive=utm" \
KOMOREBI_REPO=$PWD/.. ./result-plasma-vm/bin/run-plasma-vm-vm
In the guest's console, partition the disk, which is /dev/vdb, and install the system onto it:
sudo parted -s /dev/vdb -- mklabel gpt mkpart ESP fat32 1MiB 512MiB set 1 esp on mkpart nixos ext4 512MiB 100%
sudo mkfs.vfat -F 32 -n ESP /dev/vdb1
sudo mkfs.ext4 -q -L nixos /dev/vdb2
sudo mount /dev/vdb2 /mnt && sudo mkdir -p /mnt/boot && sudo mount /dev/vdb1 /mnt/boot
sudo nix-store --import --store /mnt < /repo/.vm/utm/plasma-utm.closure > /dev/null
sudo nixos-install --system "$(cat /repo/.vm/utm/plasma-utm.system)" --root /mnt --no-root-passwd --no-channel-copy
sudo umount /mnt/boot /mnt && sudo poweroff
Back on the Mac, in .vm, the raw disk becomes the qcow2 that UTM is pointed at (about 12 GB
used of 40):
nix shell nixpkgs#qemu --command qemu-img convert -O qcow2 utm/plasma.raw utm/plasma.qcow2
rm utm/plasma.raw utm/plasma-utm.closure
Point UTM at it
- Install UTM (
brew install --cask utm, or from its website). - New VM → Virtualize → Linux. Leave the boot ISO empty. Give it 8192 MB and 6 cores. (The wizard insists on a new disk; delete it in step 3.)
- Before the first start, open the VM's settings:
- Drives: remove the new disk, then Import
.vm/utm/plasma.qcow2(interface VirtIO). - Display: Emulated Display Card
virtio-gpu-gl-pci (GPU Supported). New Linux VMs get it by default. Renderer backend Default (ANGLE on Metal) or Apple Core OpenGL (GL 4.1). - Network: Shared Network (the guest gets an address the Mac can reach), or Emulated VLAN with port forwards 2224→22 and 3389→3389.
- Name it
plasma-utm.
- Drives: remove the new disk, then Import
- Start it. sddm logs in by itself and Plasma comes up with komorebi running.
Using it
The VM's window is the desktop: click into it and use it. The pointer is an absolute tablet, so it moves in and out of the window without being grabbed; if the window does capture it, Control+Option releases it (UTM's default; Command+Option also works, and Control+Option collides with VoiceOver). The Option key is Alt in the guest. macOS takes a few combinations before UTM sees them (Control-arrow switches Spaces, and anything with Command), and those never reach the guest. Resizing the window resizes the guest's screen, and the clipboard is shared (spice-vdagent runs in the guest).
To check that the session is on the GPU, from a terminal in the guest (or over ssh, once a key of
yours is in ~/.ssh/authorized_keys):
eglinfo 2>/dev/null | grep -i renderer # virgl (Apple M...)
journalctl --user -b -t kwin_wayland | grep -i "opengl\|renderer" | head
komorebic state | head
RDP
The session guests run KRDP, Plasma's RDP server, as the user unit krdp, on port 3389 (forwarded
from localhost:3389 for plasma-vm-session). It logs in the session's user, komorebi, with a
password of your choosing, which the unit reads from a file in the guest; until the file is there,
the unit does not start. In the guest:
umask 077 && mkdir -p ~/.config/komorebi-vm && cat > ~/.config/komorebi-vm/rdp-password # type it, then Ctrl-D
systemctl --user start krdp
The server makes a self-signed certificate the first time it starts, so a client warns about it.
The password is passed to krdpserver on its command line, which every user of the guest can read
in the process list. PLATFORM.md says what
is known about RDP into the session, and what is not.
What is not known
At time of writing (2026-10-28), the guests of a Mac, made from these files, have been built on an
aarch64-linux machine, and not booted: no test covers them, and plasma-guests-eval only
evaluates them. The x86_64 pair for a Linux host was built and started under KVM on an x86_64
machine. Both guests reached the login on their serial console and had one DRM device with a render
node, and the session guest drew the Plasma desktop on its display (a screenshot taken through
QEMU's monitor) with komorebi running in the session. The demo guest, run-kde-x86_64, was
started under KVM with a state directory: ssh took the key from it, the RDP unit started with the
password from it, komorebi tiled the four windows, and the cheat sheet opened. The aarch64 guests
for a Linux host were evaluated and not built. The RDP server is the exception to all of this: the
VM suite rdp runs the same unit in the test session. That KWin 6.8 runs well on VirGL under UTM
is not known either: the UTM and Mesa notes say VirGL is the way to render a Linux desktop, and
Plasma works on it for other people, but it has not been measured here.