Skip to content

Commit 577ed44

Browse files
Copilotsawka
andcommitted
Add throttled renderer set-is-active activity ping
Co-authored-by: sawka <2722291+sawka@users.noreply.github.com>
1 parent ae643c9 commit 577ed44

6 files changed

Lines changed: 66 additions & 3 deletions

File tree

emain/emain-ipc.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
incrementTermCommandsRemote,
1818
incrementTermCommandsRun,
1919
incrementTermCommandsWsl,
20+
setWasActive,
2021
} from "./emain-activity";
2122
import { createBuilderWindow, getAllBuilderWindows, getBuilderWindowByWebContentsId } from "./emain-builder";
2223
import { callWithOriginalXdgCurrentDesktopAsync, unamePlatform } from "./emain-platform";
@@ -317,6 +318,10 @@ export function initIpcHandlers() {
317318
tabView?.setKeyboardChordMode(true);
318319
});
319320

321+
electron.ipcMain.handle("set-is-active", () => {
322+
setWasActive(true);
323+
});
324+
320325
const fac = new FastAverageColor();
321326
electron.ipcMain.on("update-window-controls-overlay", async (event, rect: Dimensions) => {
322327
if (unamePlatform === "darwin") return;

emain/preload.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ contextBridge.exposeInMainWorld("api", {
7171
setBuilderWindowAppId: (appId: string) => ipcRenderer.send("set-builder-window-appid", appId),
7272
doRefresh: () => ipcRenderer.send("do-refresh"),
7373
saveTextFile: (fileName: string, content: string) => ipcRenderer.invoke("save-text-file", fileName, content),
74+
setIsActive: () => ipcRenderer.invoke("set-is-active"),
7475
});
7576

7677
// Custom event for "new-window"

frontend/app/app.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,12 +200,16 @@ function AppFocusHandler() {
200200
const AppKeyHandlers = () => {
201201
useEffect(() => {
202202
const staticKeyDownHandler = keyutil.keydownWrapper(appHandleKeyDown);
203+
const staticMouseDownHandler = (e: MouseEvent) => {
204+
keyboardMouseDownHandler(e);
205+
void GlobalModel.getInstance().setIsActive();
206+
};
203207
document.addEventListener("keydown", staticKeyDownHandler);
204-
document.addEventListener("mousedown", keyboardMouseDownHandler);
208+
document.addEventListener("mousedown", staticMouseDownHandler);
205209

206210
return () => {
207211
document.removeEventListener("keydown", staticKeyDownHandler);
208-
document.removeEventListener("mousedown", keyboardMouseDownHandler);
212+
document.removeEventListener("mousedown", staticMouseDownHandler);
209213
};
210214
}, []);
211215
return null;
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright 2026, Command Line Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
5+
import { GlobalModel } from "./global-model";
6+
7+
const { setIsActiveMock } = vi.hoisted(() => ({
8+
setIsActiveMock: vi.fn().mockResolvedValue(undefined),
9+
}));
10+
11+
vi.mock("@/store/global", () => ({
12+
getApi: () => ({
13+
setIsActive: setIsActiveMock,
14+
}),
15+
}));
16+
17+
describe("GlobalModel", () => {
18+
beforeEach(() => {
19+
vi.useFakeTimers();
20+
vi.setSystemTime(new Date("2026-01-01T00:00:00Z"));
21+
vi.clearAllMocks();
22+
(GlobalModel as any).instance = null;
23+
});
24+
25+
afterEach(() => {
26+
vi.useRealTimers();
27+
});
28+
29+
it("throttles setIsActive to once every 5 seconds", async () => {
30+
const model = GlobalModel.getInstance();
31+
32+
await model.setIsActive();
33+
await model.setIsActive();
34+
expect(setIsActiveMock).toHaveBeenCalledTimes(1);
35+
36+
vi.setSystemTime(new Date(Date.now() + 5000));
37+
await model.setIsActive();
38+
expect(setIsActiveMock).toHaveBeenCalledTimes(2);
39+
});
40+
});

frontend/app/store/global-model.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@
33

44
import * as WOS from "@/app/store/wos";
55
import { ClientModel } from "@/app/store/client-model";
6+
import { getApi } from "@/store/global";
67
import { atom, Atom } from "jotai";
78

89
class GlobalModel {
910
private static instance: GlobalModel;
11+
private static readonly IsActiveThrottleMs = 5000;
1012

1113
windowId: string;
1214
builderId: string;
1315
platform: NodeJS.Platform;
16+
private lastSetIsActiveTs = 0;
1417

1518
windowDataAtom!: Atom<WaveWindow>;
1619
workspaceAtom!: Atom<Workspace>;
@@ -47,6 +50,15 @@ class GlobalModel {
4750
return WOS.getObjectValue(WOS.makeORef("workspace", windowData.workspaceid), get);
4851
});
4952
}
53+
54+
async setIsActive(): Promise<void> {
55+
const now = Date.now();
56+
if (now - this.lastSetIsActiveTs < GlobalModel.IsActiveThrottleMs) {
57+
return;
58+
}
59+
this.lastSetIsActiveTs = now;
60+
await getApi().setIsActive();
61+
}
5062
}
5163

52-
export { GlobalModel };
64+
export { GlobalModel };

frontend/types/custom.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ declare global {
132132
setBuilderWindowAppId: (appId: string) => void; // set-builder-window-appid
133133
doRefresh: () => void; // do-refresh
134134
saveTextFile: (fileName: string, content: string) => Promise<boolean>; // save-text-file
135+
setIsActive: () => Promise<void>; // set-is-active
135136
};
136137

137138
type ElectronContextMenuItem = {

0 commit comments

Comments
 (0)