From bb974da3d079120a828e918dd82593cd26aeacb5 Mon Sep 17 00:00:00 2001 From: DevL0rd Date: Sun, 18 Oct 2026 09:05:33 +0700 Subject: [PATCH] Add a Bring to front button and keep the mirror above the popup in the tray The pinned scrcpy window could end up below the widget. The Phone Screen widget now has a Bring to front button that raises the mirror and keeps it on top while the widget claims it, and the tray popup always pins the mirror above and raises it when opened. Closes #2 --- bin/phonescreenctl | 13 +++++---- .../contents/ui/main.qml | 14 +++++++++- src/core/phonescreen.py | 27 ++++++++++++++++++- 3 files changed, 45 insertions(+), 9 deletions(-) diff --git a/bin/phonescreenctl b/bin/phonescreenctl index 6a1e358..cde2af7 100755 --- a/bin/phonescreenctl +++ b/bin/phonescreenctl @@ -13,6 +13,7 @@ Usage: phonescreenctl state phonescreenctl claim [serial] [--above 0|1] [--borderless 0|1] [--extra "args"] phonescreenctl release + phonescreenctl raise # bring the mirror to the front and keep it there phonescreenctl window [serial] # pop out: movable window (pauses pinned) phonescreenctl lock [serial] # lock the phone (instant hide) phonescreenctl unlock [serial] # wake + PIN-unlock (instant show) @@ -91,13 +92,11 @@ def cmd_release(args): def cmd_raise(): - """Raise the pinned mirror to the top of the stack — used by the tray popup so - its embedded mirror sits above the popup window. No-op if kdotool isn't set up.""" - try: - subprocess.run(["kdotool", "search", "--name", ps.TITLE_TOKEN, "windowraise"], - capture_output=True, timeout=5) - except (OSError, subprocess.TimeoutExpired): - pass + """Bring the pinned mirror to the front and keep it there while a widget claims + it, even if it was pinned below normal windows. No-op if kdotool isn't set up.""" + ps.request_front() + time.sleep(0.4) + ps.raise_window() return 0 diff --git a/plasmoids/org.devl0rd.phonescreen/contents/ui/main.qml b/plasmoids/org.devl0rd.phonescreen/contents/ui/main.qml index 1a73473..6159a59 100644 --- a/plasmoids/org.devl0rd.phonescreen/contents/ui/main.qml +++ b/plasmoids/org.devl0rd.phonescreen/contents/ui/main.qml @@ -73,7 +73,7 @@ PlasmoidItem { if (!_pinTarget || !root.dataWanted) return var r = screenRect() var a = "claim desktop 1 " + r.x + " " + r.y + " " + r.w + " " + r.h + root.serialArg - a += " --above " + (Plasmoid.configuration.keepBelow ? 0 : 1) + a += " --above " + (root.inPanel || !Plasmoid.configuration.keepBelow ? 1 : 0) a += " --borderless " + (Plasmoid.configuration.borderless ? 1 : 0) var extra = (Plasmoid.configuration.extraArgs || "").replace(/'/g, "").trim() if (extra !== "") a += " --extra '" + extra + "'" @@ -83,6 +83,8 @@ PlasmoidItem { if (root.dataWanted) claimMirror() else ctl("release desktop") } + onExpandedChanged: if (root.inPanel && root.expanded) ctl("raise") + function bringToFront() { ctl("raise") } function popOut() { ctl("window" + root.serialArg) } function toggleLock() { root.pendingLock = !root.displayLocked @@ -122,6 +124,16 @@ PlasmoidItem { statusColor: root.statusColor statusText: root.mirrorView.text + PlasmaComponents.ToolButton { + icon.name: "go-top" + display: QQC2.AbstractButton.IconOnly + enabled: feed.ready && feed.reachable && feed.status !== "external" + onClicked: root.bringToFront() + text: i18n("Bring to front") + QQC2.ToolTip.text: text + QQC2.ToolTip.visible: hovered + QQC2.ToolTip.delay: 600 + } PlasmaComponents.ToolButton { icon.name: "window-new" display: QQC2.AbstractButton.IconOnly diff --git a/src/core/phonescreen.py b/src/core/phonescreen.py index b6d1413..cf182b4 100644 --- a/src/core/phonescreen.py +++ b/src/core/phonescreen.py @@ -19,6 +19,7 @@ STATUS_PATH = os.path.join(RUNTIME_DIR, "phonescreen.json") CLAIM_GLOB = os.path.join(RUNTIME_DIR, "phonescreen_claim_*.json") WINDOW_PATH = os.path.join(RUNTIME_DIR, "phonescreen_window") +FRONT_PATH = os.path.join(RUNTIME_DIR, "phonescreen_front") LOG_PATH = os.path.join(RUNTIME_DIR, "phonescreen.log") LAUNCHER = cam.LAUNCHER @@ -70,6 +71,29 @@ def read_claims(): return out +def request_front(): + touch(FRONT_PATH) + + +def clear_front(): + try: + os.remove(FRONT_PATH) + except OSError: + pass + + +def front_requested(): + return os.path.exists(FRONT_PATH) + + +def raise_window(): + try: + subprocess.run(["kdotool", "search", "--name", TITLE_TOKEN, "windowraise"], + capture_output=True, timeout=5) + except (OSError, subprocess.TimeoutExpired): + pass + + def pick_winner(claims): live = [c for c in claims if c.get("visible")] if not live: @@ -581,6 +605,7 @@ def wants(self, serial): def reconcile(self): win = pick_winner(read_claims()) if win is None: + clear_front() if not self.persist or self.last_geom is None: return x, y, w, h, above, borderless = self.last_geom @@ -588,7 +613,7 @@ def reconcile(self): else: x, y = int(win.get("x", 0)), int(win.get("y", 0)) w, h = int(win.get("w", 400)), int(win.get("h", 800)) - above = bool(win.get("above", False)) + above = bool(win.get("above", False)) or front_requested() borderless = bool(win.get("borderless", True)) self.last_geom = (x, y, w, h, above, borderless) serial = win.get("serial") or resolve_serial()