Skip to content

Commit bb86c24

Browse files
fix(desktop): raise the window when a thread notification is clicked … (#12567)
Co-authored-by: Julius Marminge <julius0216@outlook.com>
1 parent b2fa5d3 commit bb86c24

8 files changed

Lines changed: 65 additions & 0 deletions

File tree

‎apps/desktop/src/ipc/DesktopIpcHandlers.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import {
4848
checkSystemPermission,
4949
pasteAsText,
5050
probeRemoteEditors,
51+
revealWindow,
5152
pickFolder,
5253
pickProjectFavicon,
5354
pickThemeFiles,
@@ -150,6 +151,7 @@ export const installDesktopIpcHandlers = Effect.fn("desktop.ipc.installHandlers"
150151
yield* ipc.handle(openSystemSettings);
151152
yield* ipc.handle(checkSystemPermission);
152153
yield* ipc.handle(pasteAsText);
154+
yield* ipc.handle(revealWindow);
153155
yield* ipc.handle(probeRemoteEditors);
154156
yield* ipc.handle(getUpdateState);
155157
yield* ipc.handle(setUpdateChannel);

‎apps/desktop/src/ipc/channels.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export const OPEN_SYSTEM_SETTINGS_CHANNEL = "desktop:open-system-settings";
99
export const PROBE_REMOTE_EDITORS_CHANNEL = "desktop:probe-remote-editors";
1010
export const MENU_ACTION_CHANNEL = "desktop:menu-action";
1111
export const PASTE_AS_TEXT_CHANNEL = "desktop:paste-as-text";
12+
export const REVEAL_WINDOW_CHANNEL = "desktop:reveal-window";
1213
export const SNAP_SHOT_EVENT_CHANNEL = "desktop:snap-shot-event";
1314
export const WEB_LINK_OPEN_CHANNEL = "desktop:web-link-open";
1415
export const WEB_LINK_READY_CHANNEL = "desktop:web-link-ready";

‎apps/desktop/src/ipc/methods/window.test.ts‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
pasteAsText,
3232
pickProjectFavicon,
3333
probeRemoteEditors,
34+
revealWindow,
3435
} from "./window.ts";
3536

3637
const readyWslConfig: DesktopBackendManager.DesktopBackendStartConfig = {
@@ -257,6 +258,26 @@ describe("pasteAsText", () => {
257258
);
258259
});
259260

261+
describe("revealWindow", () => {
262+
it.effect("reveals the main window only when it asks", () => {
263+
const window = { webContents: { id: 42 } } as unknown as Electron.BrowserWindow;
264+
const reveal = vi.fn(() => Effect.void);
265+
return Effect.gen(function* () {
266+
yield* revealWindow.handler(undefined, { sender: { id: 99 } });
267+
assert.equal(reveal.mock.calls.length, 0);
268+
yield* revealWindow.handler(undefined, { sender: { id: 42 } });
269+
assert.deepEqual(reveal.mock.calls, [[window]]);
270+
}).pipe(
271+
Effect.provide(
272+
Layer.mock(ElectronWindow.ElectronWindow)({
273+
main: Effect.succeedSome(window),
274+
reveal,
275+
}),
276+
),
277+
);
278+
});
279+
});
280+
260281
describe("pickProjectFavicon", () => {
261282
const pickerLayer = (
262283
pickFiles: () => Effect.Effect<Array<string>>,

‎apps/desktop/src/ipc/methods/window.ts‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,24 @@ export const pasteAsText = DesktopIpc.makeIpcMethod({
352352
}),
353353
});
354354

355+
export const revealWindow = DesktopIpc.makeIpcMethod({
356+
channel: IpcChannels.REVEAL_WINDOW_CHANNEL,
357+
payload: Schema.Undefined,
358+
result: Schema.Void,
359+
handler: Effect.fn("desktop.ipc.window.revealWindow")(function* (_input, event) {
360+
const electronWindow = yield* ElectronWindow.ElectronWindow;
361+
const window = yield* electronWindow.main;
362+
if (
363+
event === undefined ||
364+
Option.isNone(window) ||
365+
window.value.webContents.id !== event.sender.id
366+
) {
367+
return;
368+
}
369+
yield* electronWindow.reveal(window.value);
370+
}),
371+
});
372+
355373
const PICKED_THEME_FILE_MAX_BYTES = 256 * 1024;
356374

357375
export const pickThemeFiles = DesktopIpc.makeIpcMethod({

‎apps/desktop/src/preload.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ contextBridge.exposeInMainWorld("desktopBridge", {
9696
ipcRenderer.on(IpcChannels.TRACKPAD_SCROLL_END_CHANNEL, handler);
9797
return () => ipcRenderer.removeListener(IpcChannels.TRACKPAD_SCROLL_END_CHANNEL, handler);
9898
},
99+
revealWindow: () => ipcRenderer.invoke(IpcChannels.REVEAL_WINDOW_CHANNEL),
99100
getSystemLocale: () => {
100101
const result = ipcRenderer.sendSync(IpcChannels.GET_SYSTEM_LOCALE_CHANNEL);
101102
return typeof result === "string" ? result : null;

‎apps/web/src/components/ThreadNotificationCoordinator.browser.test.tsx‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,4 +359,20 @@ describe("thread notifications", () => {
359359
silent: true,
360360
});
361361
});
362+
363+
it("reveals the desktop window and opens the thread when a system alert is clicked", async () => {
364+
const revealWindow = vi.fn(() => Promise.resolve());
365+
vi.stubGlobal("desktopBridge", { revealWindow });
366+
state.mode = "notifications";
367+
state.focused = false;
368+
await render();
369+
await complete();
370+
const notification: EventTarget = state.notification.mock.results[0]?.value;
371+
notification.dispatchEvent(new Event("click"));
372+
expect(revealWindow).toHaveBeenCalledTimes(1);
373+
expect(state.navigate).toHaveBeenCalledWith({
374+
to: "/$environmentId/$threadId",
375+
params: { environmentId: "env-1", threadId: "thread-1" },
376+
});
377+
});
362378
});

‎apps/web/src/components/ThreadNotificationCoordinator.tsx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ function EnvironmentNotifications({
226226
notification.addEventListener("click", () => {
227227
notification.close();
228228
window.focus();
229+
void window.desktopBridge?.revealWindow?.().catch(() => undefined);
229230
void navigate({
230231
to: "/$environmentId/$threadId",
231232
params: { environmentId, threadId: thread.id },

‎packages/contracts/src/ipc.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1032,6 +1032,11 @@ export interface DesktopBridge {
10321032
setNotificationBadge?: (badge: { count: number; image: string | null }) => Promise<void>;
10331033
onNotificationBadgeClear?: (listener: () => void) => () => void;
10341034
onTrackpadScrollEnd?: (listener: () => void) => () => void;
1035+
/**
1036+
* Renderer `window.focus()` cannot raise an Electron window, and on Wayland
1037+
* only the main process can spend a notification click's activation token.
1038+
*/
1039+
revealWindow?: () => Promise<void>;
10351040
/**
10361041
* The OS locale as a BCP-47 tag, which the renderer cannot read for itself:
10371042
* the packaged app ships only the `en-US` Chromium locale pak, so

0 commit comments

Comments
 (0)