Use MrSnowball-dev/kwin-osd-reposition instead.
It solves the same problem as a plain KWin script: no compiler, no patched compositor, no re-login, nothing to redo after
pacman -Syu, sliders in System Settings instead of a text file, distro-agnostic, and installable straight from the KDE Store. It keeps the two properties this repo was built for: the OSD still follows the active screen, and the position is a fraction of the screen (X and Y, not just Y), so it lands in the same relative spot on a 1080p laptop panel and a 4K external alike.Below, under "Why this needs a KWin patch at all", this repo claimed:
KWin scripts can't move it. The OSD window reports
moveable=false; geometry writes from a script are silently dropped.That is false. A KWin script can move the OSD: assigning to
window.frameGeometryonWorkspace.windowAdded, guarded bywindow.onScreenDisplay, works on stock, unpatched KWin. Verified here on Arch with the distrokwin6.7.4 package,libkwincarrying none of this repo's patch.Two likely causes of my negative result, both packaging/timing rather than a compositor restriction:
- A missing
"X-Plasma-API": "declarativescript"inmetadata.json. Without it KWin 6 silently skips the script whilekwinrcstill reports it as enabled: no log output, no effect, indistinguishable from "geometry writes are dropped."- No deferral. The write has to be deferred (
Qt.callLater) so it lands after KWin's own force-placement inplaceOnScreenDisplay()rather than being overwritten by it.So patching
src/placement.cppand rebuilding KWin was never necessary. Everything below that depends on the opposite premise is wrong, and the build/patch tooling here is not recommended for anyone.
- The Window Rules decision table (static setup vs. docking laptop vs. mixed-DPI multi-head) is independent of the patch and still accurate.
- The writeup of KWin's OSD internals: it is a Wayland
plasma-surfacewith the OnScreenDisplay role, force-placed inKWin::Placement::placeOnScreenDisplay(), andOsd.qmlis loaded from a resource baked into the binary (prefer :/qt/qml/...) so editing the on-disk copy does nothing.- Why patching core KWin on Wayland forces a re-login, unlike a hot-reloadable effect plugin such as KDE-Rounded-Corners.
Neither is a reason to use this repo:
- Compositor-level placement happens before the window is mapped, so a patched KWin cannot flash at the default spot; a script repositions after
windowAdded. I have not actually observed such a flash with the script, so treat this as theoretical.- The script's live apply-on-Apply path wants
QML_XHR_ALLOW_FILE_READ=1in KWin's environment, becauseKWin.readConfig()caches at script-load time. Without it the script still works, the new position just takes effect at the next login. This patch read its config file natively with no such workaround.
Lower (or raise) the Plasma OSD — the volume/brightness/mic popup — to a height you choose, without losing the things KWin's default placement gives you for free: it follows the active screen and stays horizontally centered. The position is a fraction of the screen height (so it lands in the same relative spot on every monitor regardless of resolution) and is read from a file at runtime, on every popup, so you can move it live with no rebuild and no logout.
./build.sh # patch + build + install kwin (default 0.85)
echo 0.85 > ~/.config/kwin-osd-position # later: move it; next popup obeysFor a lot of people, you don't — KDE's built-in Window Rules are simpler and require no patched compositor. This patch only earns its keep for a specific kind of workflow. Decide with the table below.
| Your situation | Best tool |
|---|---|
| Single monitor, fixed resolution | Window Rules — pin the OSD to one absolute coordinate. Done. |
| Static multi-monitor (same monitors, same layout, always) | Window Rules — pick the screen/coords once; they never need to change. |
| You just want it a bit lower and never replug anything | Window Rules — easiest, no compiling, survives upgrades. |
| Dynamic workflow: laptop that docks/undocks, monitors hot-plugged, resolutions that change, OSD should appear on whatever screen is active | This patch. |
| You want one setting that lands in the same relative spot on a 1080p laptop panel and a 4K external | This patch. |
| You want to nudge the position often and instantly, without fighting a rules dialog | This patch. |
KDE can pin the OSD with System Settings → Window Management → Window Rules
(match the plasmashell / OnScreenDisplay window, force a Position). For a
single monitor or a never-changing setup this is the right answer: no
patched package, nothing to rebuild after updates, a few clicks and you're done.
But a Window Rule can only set an absolute position, and that's exactly what makes it wrong for dynamic setups:
- It's a fixed pixel coordinate.
x=1200, y=900is centered on a 2560-wide screen but off to the left on a 3840-wide one, and near the bottom on 1080p but mid-screen on 4K. Replug a different monitor and it's in the wrong place. - It throws away screen-awareness. The stock OSD appears on whichever screen is currently active (where your focus/cursor is). A Window Rule nails it to one screen/coordinate, so on a multi-head setup the popup stops following you.
- It doesn't track resolution or layout changes — dock/undock, hotplug, or a resolution switch and you're back in the rules dialog editing numbers.
So the rule of thumb: static setup → Window Rules; dynamic setup → this patch. This patch keeps centering and active-screen following, and replaces only the vertical formula with a resolution-independent, file-driven one.
The OSD is positioned by KWin, not by plasmashell. It's a Wayland
plasma-surface with the OnScreenDisplay role, and KWin force-places it in
KWin::Placement::placeOnScreenDisplay() with this hardcoded formula:
const int x = area.left() + (area.width() - size.width()) / 2; // centered
const int y = area.top() + 2 * area.height() / 3 - size.height() / 2; // 2/3 downThat position is compiled into kwin. Everything short of patching has been tried and verified not to work (Plasma 6.6.5 / Wayland):
KWin scripts can't move it. The OSD window reportsRETRACTED, this is false. A KWin script can move the OSD by writingmoveable=false; geometry writes from a script are silently dropped.window.frameGeometryonWorkspace.windowAdded; see the correction at the top of this README. This invalidates the premise of the whole patch.- Editing the QML does nothing.
Osd.qml(/usr/lib/qt6/qml/org/kde/plasma/workspace/osd/Osd.qml) only sets width/height, and is loaded from a resource baked into the binary (prefer :/qt/qml/...), so editing the on-disk copy has no effect. - Window Rules can pin it, but only to an absolute coordinate — which is the static-vs-dynamic tradeoff covered above.
So the only way to keep screen-awareness but move it is to patch that one
line in KWin and rebuild. That's what this repo does — but instead of swapping in
another hardcoded constant, the patch makes the y formula:
-
resolution-independent — the OSD centre is placed at a fraction of the active screen's height, not a fixed pixel count, so it lands in the same relative spot on every monitor. (A fixed
+300pxis ~28 % of a 1080p screen but only ~14 % of a 4K screen — that drift is exactly what this avoids.) -
live-editable —
placeOnScreenDisplay()reads the fraction from a file at runtime, on every popup, so you change the position without rebuilding:// ~/.config/kwin-osd-position : one number, 0.0 (top) .. 1.0 (bottom) const int y = area.top() + qRound(frac * area.height()) - size.height()/2;
Native horizontal centering and active-screen following are kept as-is.
- Arch Linux (or a derivative using the Arch
kwinpackage).build.shfetches the current ArchkwinPKGBUILD, so it tracks upstream version bumps with no edits. Other distros: see Other distributions. - Plasma 6 / KWin on Wayland. Developed and tested on Plasma 6.6.5.
- Standard build tooling:
base-devel(makepkg),curl,perl. The autorun helper additionally useskdialog,strings, and a terminal (konsole). - Run as your normal user — not root.
makepkginstalls withsudoonly when it needs to.
This patch, as shipped, is Arch-specific — build.sh is built around the
Arch kwin PKGBUILD and makepkg/pacman. The technique is not: it's a
one-line change to placeOnScreenDisplay() in KWin's src/placement.cpp, plus
a runtime read of a config file. On Fedora, openSUSE, Debian/Ubuntu, etc. the
same change applies cleanly — only the packaging/build wrapper differs (rpmbuild,
debuild, a manual CMake build of KWin, or your distro's source-package flow).
You're free to fork and adapt this for your own distro or workflow (it's MIT
licensed). The patch snippet and the verification logic in build.sh are the
reusable parts; swap out the fetch-and-build half. PRs adding other distros'
build paths are welcome.
./build.sh # default fraction: 0.85 (85% down the screen)
OSD_FRAC=0.9 ./build.sh # custom default fraction (0.0 top .. 1.0 bottom)build.sh fetches the current Arch kwin PKGBUILD each run, lets makepkg
download/extract/prepare upstream, patches placement.cpp in the extracted
tree, then builds and installs. Because it never hardcodes a version, it keeps
working across kwin releases with no edits. If upstream ever reshapes that
function so the patch can't land cleanly, the script aborts loudly rather
than installing a half-patched build.
On a successful install it also creates ~/.config/kwin-osd-position seeded
with the default fraction, so there's a file to edit immediately. If the file
already exists it's left untouched — re-running after a kwin upgrade never
clobbers the position you chose.
After installing, log out and back in (or reboot) once so the running compositor loads the new code. Then test the popup:
qdbus6 org.kde.plasmashell /org/kde/osdService org.kde.osdService.showText \
"audio-volume-high" "osd position"KWin is hundreds of C++ files, so a from-scratch compile is the slow part of
every rebuild. build.sh uses ccache to avoid repeating
that work: it installs ccache if missing and routes the compiler through it by
prepending /usr/lib/ccache/bin to PATH — no change to your
/etc/makepkg.conf (it's enabled per-build only).
- First build: unchanged in speed — the cache starts empty.
- Re-patching the same
kwinversion (e.g. changing the default fraction): near-instant — onlyplacement.cpprecompiles and relinks. - Re-patching after a
kwinupgrade: the script setsCCACHE_BASEDIRto the extracted source root so the version in the path is stripped before hashing. Files that didn't change between the two releases stay cached, so only the handful that actually changed recompile.
The script prints the cache hit rate after each build. To opt out:
USE_CCACHE=0 ./build.shTunables (all optional): CCACHE_DIR (default ~/.cache/ccache) and
CCACHE_MAXSIZE (default 10G).
OSD_FRAC at build time is only the default, used when the file is absent.
To change the position, write a number to the file — the next volume/
brightness popup picks it up. No rebuild, no logout:
echo 0.85 > ~/.config/kwin-osd-position # then nudge volume to see it0.0= top of the active screen,1.0= bottom,0.5= middle.- Values are clamped to
[0.0, 1.0]and the OSD is always kept fully on-screen, so extreme values can't push it off the edge. - Use a
.decimal point. A missing, unreadable, or non-numeric file falls back to the build-time default — so you can never break the OSD by editing it.
A normal pacman -Syu will replace your patched kwin with the official one,
restoring the high OSD. Your position is safe either way — it lives in
~/.config/kwin-osd-position, which pacman never touches; an upgrade only
removes the file-reading code, not your chosen value. You just need to rebuild
so the reading code comes back.
Modelled on KDE-Rounded-Corners' approach. Install once:
./tools/install-autorun.sh # copies runtime files on-system + adds autostartThis copies the runtime files (build.sh + tools/) into
~/.local/share/osd-position-kde and points the autostart entry at that copy, so
the autorun keeps working no matter where you cloned the repo — even if the clone
lives on a removable or late-mounted partition that isn't present at login.
Re-running is idempotent (it just refreshes the installed copy).
At each login it checks whether the installed libkwin still carries our patch
(it greps the baked-in kwin-osd-position path marker out of the library). If a
kwin upgrade wiped it, it pops a dialog offering to rebuild — running as
you, prompting for your sudo password in a terminal. No root pacman hook, no
passwordless sudo.
Remove with ./tools/install-autorun.sh --remove, or manage it in
System Settings → Autostart.
This patches core KWin (placement.cpp in libkwin), not a loadable effect.
On Wayland, KWin is the display server, so it can't be hot-swapped in place:
kwin_wayland --replace merely exits the running compositor so its wrapper
restarts it — a disruptive restart, not a seamless hand-off, and not a supported
reload path. So the autorun rebuilds/installs now, and the new OSD position takes
effect on your next login. (On X11 you could kwin_x11 --replace & to
reload live — but this is built for Wayland.) Contrast KDE-Rounded-Corners, which
patches an effect plugin it can hot-reload via D-Bus in the same session.
./tools/install-autorun.sh --remove # remove autostart entry + installed copy
sudo pacman -S extra/kwin # reinstall stock kwin
rm -f ~/.config/kwin-osd-position # optional: drop the position fileLog out and back in once to load stock kwin.
build.sh replaces the single stock y-formula line in
src/placement.cpp::placeOnScreenDisplay() with a block that:
- starts from the build-time default fraction (
OSD_FRAC); - reads
~/.config/kwin-osd-position(via$HOME), and if it parses as a number, uses that instead — this happens on every OSD popup, which is what makes it live-editable; - clamps the fraction to
[0.0, 1.0]; - centers the OSD at that fraction of the active screen's height, then clamps
the final
yso the window stays fully on-screen.
The required includes (<QFile>, <algorithm>) are injected alongside the
existing ones. The script verifies post-patch that its marker is present and the
old formula is gone, aborting otherwise. Horizontal centering and active-screen
selection are left as upstream wrote them.
build.sh # fetch + patch + build + install kwin; seeds the position file
tools/
install-autorun.sh # copies runtime files on-system + install/remove autorun
osd-position-autorun.sh # the autorun: detects an upgrade wiped the patch, offers rebuild
osd-position-autorun.desktop # autostart entry template (Exec path filled in on install)
The build/ directory is created and wiped by build.sh on every run (it holds
the fetched kwin sources and built packages) and is git-ignored.
- Arch Linux, Plasma 6.6.5, KWin on Wayland, x86-64.
Other versions should work — build.sh tracks the current Arch PKGBUILD and
fails loudly if upstream changes the patched function's shape.
MIT — see LICENSE. This repo contains only the build/patch tooling; KWin itself is licensed by its own authors (GPL-2.0-or-later).