Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions mac/Sources/Wind/DrawZoom.swift
Original file line number Diff line number Diff line change
Expand Up @@ -345,11 +345,25 @@ final class DrawZoom: NSObject, SCStreamOutput, SCStreamDelegate, @unchecked Sen
private static let lockOwners: Set<String> = {
var s: Set<String> = ["loginwindow", "ScreenSaverEngine"]
#if WIND_TEST_CHANNEL
if let v = ProcessInfo.processInfo.environment["WIND_FAKE_LOCK"] { s.insert(v) } // a test window stands in for it
for k in ["WIND_FAKE_LOCK", "WIND_FAKE_LOGIN_DIALOG"] { if let v = ProcessInfo.processInfo.environment[k] { s.insert(v) } } // a test window stands in
#endif
return s
}()

/// Owners whose windows are not always the lock screen.
private static let loginDialogOwners: Set<String> = {
var s: Set<String> = ["loginwindow"]
#if WIND_TEST_CHANNEL
if let v = ProcessInfo.processInfo.environment["WIND_FAKE_LOGIN_DIALOG"] { s.insert(v) } // a test window stands in for one
#endif
return s
}()

/// The session really is locked, whatever windows are up.
private static var screenLocked: Bool {
(CGSessionCopyCurrentDictionary() as? [String: Any])?["CGSSessionScreenIsLocked"] as? Bool ?? false
}

/// Whether something is on screen the drawn zoom cannot show right: a window stacked above the
/// overlay (it would show twice, unzoomed on top of its magnified copy), a window that opts out
/// of screen capture (it would be hidden), or the lock screen. Every other window, prompts
Expand Down Expand Up @@ -380,7 +394,14 @@ final class DrawZoom: NSObject, SCStreamOutput, SCStreamDelegate, @unchecked Sen
if alpha < 0.05 || !big || owner == "Window Server" { continue } // the recording indicator is 28x28
if ignore.contains(owner) { continue }
let id = w[kCGWindowNumber as String] as? Int ?? 0
if lockOwners.contains(owner) { return (id, true, true) }
if lockOwners.contains(owner) {
// loginwindow also shows the Restart and Shut Down prompts with nothing locked. Taken for the
// lock screen with Handover off, they gave the real cursor back: the tiny cursor beside
// the magnified view (owner 2026-11-04). Below the overlay such a prompt is simply drawn;
// above it the drawn cursor cannot reach it, and the real one has to work.
if handoverAllowed || !loginDialogOwners.contains(owner) || screenLocked || aboveOverlay { return (id, true, true) }
continue
}
if !handoverAllowed { continue }
if sharing == 0 { return (id, false, false) }
// Above the overlay (listed before it). With the overlay not on screen, judge by level:
Expand Down
2 changes: 1 addition & 1 deletion mac/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.5.0
0.5.1
2 changes: 2 additions & 0 deletions mac/docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -1090,6 +1090,8 @@ above the overlay, so it stays unmagnified on the zoomed view. It cannot be cove
- `handover` (Settings, under Engine; greyed on Transform; Render too): off, nothing goes to macOS's zoom (owner 2026-11-04, the lock screen too).
Protected windows and windows above the overlay stay as drawn, macOS's screenshot modes run under the overlay with their real crosshair
unmagnified, and the lock screen shows unmagnified above the overlay: `DrawZoom.lockUp` gives the real cursor back meanwhile and restarts a capture it stopped.
loginwindow also owns the Restart and Shut Down prompts: only `CGSSessionScreenIsLocked`, or a window above the overlay, counts as the lock screen (owner
2026-11-04: the prompt brought the tiny cursor). Tests never open the real prompt, which restarts by itself after 60 s.
- No pixels reach Wind, so no xBR: `renderScaling` 0 is Nearest, 1 is `cubic` (the Scaling row is Render's only for now).
- What is below reaches the screen only as far as a commit dirties the backdrop, so `keepAliveTick` nudges the backdrop layer itself 0.001 pt
on every refresh the view does not move it. A nudge of the cursor layer alone, or a tick with the view still, redrew part of the view: a
Expand Down
13 changes: 13 additions & 0 deletions mac/scripts/backdrop_truth.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,19 @@ def torn(label):
s = W.ctl("state"); W.check("handover off: the cursor drawn again after the lock screen", s["drawn"] and s.get("realCursorClear"), f"drawn {s['drawn']} clear {s.get('realCursorClear')}")
W.set_config(handover=None); W.ctl("level 1"); time.sleep(0.5)

# Handover off and a loginwindow prompt with nothing locked (Restart, owner 2026-11-04): not the lock screen, so
# the drawn cursor stays and the real one stays out of sight. A test window stands in: never the real prompt,
# which restarts the Mac by itself after 60 s.
W.kill_all_wind()
os.environ.pop("WIND_FAKE_LOCK"); os.environ["WIND_FAKE_LOGIN_DIALOG"] = "fakeprompt"
subprocess.Popen([os.path.join(W.ROOT, "build/test/Wind.app/Contents/MacOS/Wind")], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, start_new_session=True)
time.sleep(2); W.set_config(handover=False); time.sleep(0.8)
W.ctl("warp 960 500"); time.sleep(0.3); W.ctl(f"level {a.level}"); time.sleep(1.5)
p = subprocess.Popen([PROMPT, "4"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL); time.sleep(1.5)
s = W.ctl("state")
W.check("handover off: a login prompt with nothing locked keeps the drawn cursor", s["drawn"] and not s["handedOver"] and s.get("realCursorClear") and not s["realCursorVisible"], f"drawn {s['drawn']} clear {s.get('realCursorClear')} real {s['realCursorVisible']}")
p.wait(timeout=7); W.set_config(handover=None); W.ctl("level 1"); time.sleep(0.5)

# Switched from Render while zoomed, the capture running: it must end, and the dot with it (owner 2026-11-04).
W.set_config(engine="draw"); time.sleep(1); W.ctl("level 3"); time.sleep(2)
s = W.ctl("state"); W.check("Render captures, as the switch's starting point", s["drawCapturing"] and indicators() > 0, f"capturing {s['drawCapturing']}, {indicators()} indicators")
Expand Down
Loading