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
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ anchor: '@file'
created: 2026-10-27T15:34:41Z
norm: '1'
sig: f5f542a9730901ae
body_hash: 0d3d7801d2eeec11
raw_hash: b7c0ca684f0d3c15
body_hash: 9d55ad18b75658fb
raw_hash: 31da00fc4410d54a
vouched: 2026-10-27T15:52:56Z
lines: 1-249
---

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
id: 01M5Z10R62MZBHB2W46VYCP5ND
anchor: '@file'
created: 2026-10-27T15:52:44Z
norm: '1'
sig: f5f542a9730901ae
body_hash: 9d55ad18b75658fb
raw_hash: 31da00fc4410d54a
lines: 1-266
---

a relaunch must be followed without a click, since make run restarts the app on every change
8 changes: 8 additions & 0 deletions apps/app/src/panels/live.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,21 @@ export function Live(
) {
const ref = useRef<HTMLElement>(null);
const relink = useRef<{ app?: string }>(undefined);
// After Ace restarts, only the tab knows the app, so a lost window tries it once on its own.
const tried = useRef(false);
const [lost, setLost] = useState(false);

const report = useEffectEvent((status: LiveStatus | null) => {
if (!status) return;
if (status.state === "lost" && app && !tried.current) {
tried.current = true;
relink.current = { app };
return;
}
setLost(status.state === "lost");
// A relink moves the tab to the app's new window, and the app is kept for the next one.
if (status.state !== "live") return;
tried.current = false;
if (status.window !== window || status.app !== app) {
update({ window: status.window, app: status.app });
}
Expand Down
21 changes: 19 additions & 2 deletions apps/desktop/native/live.m
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ @interface AceLiveView : NSView <SCStreamOutput, SCStreamDelegate>
@property(nonatomic, strong) NSTimer *watch;
@property(nonatomic) BOOL stopped;
@property(nonatomic) BOOL lost;
/** Until when a closed window's app is followed to its next window, as after a rebuild. */
@property(nonatomic, strong) NSDate *deadline;
/** Bumped by each start or relink, so a slow lookup can't attach a superseded window. */
@property(nonatomic) NSUInteger generation;
@end
Expand Down Expand Up @@ -58,6 +60,8 @@ - (void)start:(CGWindowID)target {
[self stop];
self.target = target;
self.bundle = nil;
self.deadline = nil;
self.layer.contents = nil;
[self attach:nil];
}

Expand All @@ -72,7 +76,6 @@ - (void)relink:(NSString *)app {
- (void)attach:(NSString *)app {
self.stopped = NO;
self.lost = NO;
self.layer.contents = nil;
NSUInteger generation = ++self.generation;
CGWindowID target = self.target;
[SCShareableContent getShareableContentExcludingDesktopWindows:YES
Expand Down Expand Up @@ -101,6 +104,7 @@ - (void)attach:(NSString *)app {
- (void)capture:(SCWindow *)window {
self.bundle = window.owningApplication.bundleIdentifier;
self.current = window.windowID;
self.deadline = nil;
SCContentFilter *filter = [[SCContentFilter alloc] initWithDesktopIndependentWindow:window];
SCStreamConfiguration *config = [SCStreamConfiguration new];
CGFloat scale = filter.pointPixelScale;
Expand Down Expand Up @@ -137,9 +141,22 @@ - (void)lost:(SCStream *)stream {
});
}

// Shows the page's relink prompt underneath; the app may have quit or relaunched.
// A rebuilt app relaunches with a new window ID, so its app is followed for a while, keeping the
// last frame, before the page's relink prompt shows underneath.
- (void)lose {
[self stop];
if (!self.bundle) return [self give];
if (!self.deadline) self.deadline = [NSDate dateWithTimeIntervalSinceNow:10];
if (self.deadline.timeIntervalSinceNow <= 0) return [self give];
self.stopped = NO;
NSUInteger generation = self.generation;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC / 2), dispatch_get_main_queue(), ^{
if (!self.stopped && self.generation == generation) [self attach:self.bundle];
});
}

- (void)give {
self.deadline = nil;
self.lost = YES;
self.layer.contents = nil;
}
Expand Down