Put clicks where the cursor is on scaled desktop screens

With a screen's scale set to anything but 100%, clicks landed away from
the cursor, further off the further from the top left (#3). ft-screens
hands KWin panel positions in buffer pixels, and KWin's nested Wayland
backend (6.2.5, WaylandInputDevice) adds surface coordinates to its
output's logical position without dividing by the output's scale. At
125% a click at the middle of a 3440x1440 screen, (1720, 720), reached
KWin as logical (1720, 720), pixel (2150, 900).

ft-screens now keeps a scale per screen and divides pointer positions by
it. ft-layout sends each screen's scale, as KWin reports it after
applying, with a new "scale N s" command, whenever it applies scales:
at desktop start and from Frametop Display Settings. Screens default to
1, so an ft-layout that never sends it keeps the old behaviour.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
DeeJanuzandClaude Opus 5.5 committed 2026-09-28 21:33:03 -06:00
1 parent 0857a54fab
commit 18aa0fec3a
3 files changed
+39 -5

No files matched your search

+16
View File
@@ -645,6 +645,21 @@ def arrangement(count):
return [[b["i"] for b in sorted(c, key=lambda b: -b["pitch"])] for c in columns] + [[i] for i in pinned]
def send_scales(outs):
"""KWin's scale for each screen, as it is now, to ft-screens: KWin's nested backend
doesn't undo its scale on pointer input, so ft-screens does (panel pixels / scale)."""
if backend() != "screens":
return
try:
sock = screens_socket()
for i, o in enumerate(outs):
reply = sock.ask(f"scale {i + 1} {float(o.get('scale', 1)):g}")
if not reply.startswith("ok"):
log(f"screen {i + 1}: scale: {reply}")
except RuntimeError as e:
log(f"scale: {e}")
def apply_scales():
"""Per-screen scale and rotation, positions side by side, and the primary screen (the
taskbar goes there) to KWin, which keeps them in the session's config."""
@@ -670,6 +685,7 @@ def apply_scales():
# Laid out as you see the screens around you (arrangement), centred on one line, so
# the pointer and dragged windows cross to the screen you see next to this one.
outs = outputs(env)
send_scales(outs)
# kscreen's "size" is in pixels (already turned for a rotation); positions are in
# logical units, the pixels divided by the scale (KWin rounds up).
sizes = [(math.ceil(o["size"]["width"] / float(o.get("scale", 1)) - 1e-6),