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
5 changes: 5 additions & 0 deletions .changeset/ca7a0a92.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
bump: patch
---

Refresh tile aspect ratio when a window is resized between overlay opens. Previously, resizing a window while the overlay was hidden would leave the next cmd-cmd showing the tile at its old aspect ratio (and capturing the live preview at the old dimensions) until something else added or removed a window.
44 changes: 36 additions & 8 deletions Sources/cmdcmd/Overlay.swift
Original file line number Diff line number Diff line change
Expand Up @@ -318,8 +318,25 @@ final class Overlay {
let currentIDs = Set(allTiles.map { CGWindowID($0.scWindow.windowID) })
let addedIDs = newIDs.subtracting(currentIDs)
let removedIDs = currentIDs.subtracting(newIDs)
guard !addedIDs.isEmpty || !removedIDs.isEmpty else { return }
Log.debug("reconcile: +\(addedIDs.count) -\(removedIDs.count) (was \(currentIDs.count), now \(newIDs.count))")

// Refresh kept tiles' SCWindow so .frame reflects the current size.
// Without this, a window resized between prewarm and show keeps a
// stale frame and the tile renders at the old aspect ratio.
let candidateMap = Dictionary(uniqueKeysWithValues: candidates.map { (CGWindowID($0.windowID), $0) })
var resized: [Tile] = []
for t in allTiles {
let id = CGWindowID(t.scWindow.windowID)
guard !removedIDs.contains(id), let fresh = candidateMap[id] else { continue }
let oldSize = t.scWindow.frame.size
let newSize = fresh.frame.size
t.scWindow = fresh
if abs(oldSize.width - newSize.width) > 1 || abs(oldSize.height - newSize.height) > 1 {
resized.append(t)
}
}

guard !addedIDs.isEmpty || !removedIDs.isEmpty || !resized.isEmpty else { return }
Log.debug("reconcile: +\(addedIDs.count) -\(removedIDs.count) ~\(resized.count) (was \(currentIDs.count), now \(newIDs.count))")

let added: [Tile] = candidates.compactMap { w -> Tile? in
let id = CGWindowID(w.windowID)
Expand Down Expand Up @@ -382,12 +399,23 @@ final class Overlay {
}

let live = config.livePreviewsEnabled
Task {
await withTaskGroup(of: Void.self) { group in
for t in added {
group.addTask {
await t.snapshot()
if live { await t.start() }
if !added.isEmpty {
Task {
await withTaskGroup(of: Void.self) { group in
for t in added {
group.addTask {
await t.snapshot()
if live { await t.start() }
}
}
}
}
}
if !resized.isEmpty {
Task {
await withTaskGroup(of: Void.self) { group in
for t in resized {
group.addTask { await t.refreshAfterResize(live: live) }
}
}
}
Expand Down
21 changes: 20 additions & 1 deletion Sources/cmdcmd/Tile.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ final class Tile: NSObject, SCStreamOutput, SCStreamDelegate {
return NSColor(srgbRed: r, green: g, blue: b, alpha: 1)
}

let scWindow: SCWindow
var scWindow: SCWindow
let ownerPID: pid_t
let ignoreKey: String
let layer: CALayer
Expand Down Expand Up @@ -476,6 +476,25 @@ final class Tile: NSObject, SCStreamOutput, SCStreamDelegate {
try? await s.stopCapture()
}

/// Underlying window resized after capture started. Tear down the existing
/// stream (its config is fixed at the old dimensions) and rebuild from the
/// fresh `scWindow.frame`.
func refreshAfterResize(live: Bool) async {
if cancelled { return }
if let s = stream {
self.stream = nil
stopWatchdog()
try? await s.stopCapture()
}
if cancelled { return }
hasRenderedLiveFrame = false
loggedFirstLiveFrame = false
await snapshot()
if live && !cancelled {
await start()
}
}

func stopSync(group: DispatchGroup) {
cancelled = true
suppressFrames = true
Expand Down
Loading