| 1 | import * as Electron from "electron"; |
| 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as Schema from "effect/Schema"; |
| 4 | import * as HostProcess from "@t3tools/shared/HostProcess"; |
| 5 | |
| 6 | import * as ElectronApp from "../../electron/ElectronApp.ts"; |
| 7 | import * as DesktopIpc from "../DesktopIpc.ts"; |
| 8 | import { SET_NOTIFICATION_BADGE_CHANNEL } from "../channels.ts"; |
| 9 | |
| 10 | const NotificationBadge = Schema.Struct({ |
| 11 | count: Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: 2_147_483_647 })), |
| 12 | image: Schema.NullOr( |
| 13 | Schema.String.check( |
| 14 | Schema.isMaxLength(16_384), |
| 15 | Schema.isPattern(/^data:image\/png;base64,[a-z0-9+/]+={0,2}$/i), |
| 16 | ), |
| 17 | ), |
| 18 | }); |
| 19 | |
| 20 | export function applyNotificationBadge( |
| 21 | platform: NodeJS.Platform, |
| 22 | { count, image }: typeof NotificationBadge.Type, |
| 23 | ): void { |
| 24 | try { |
| 25 | if (Electron.BrowserWindow.getFocusedWindow()) count = 0; |
| 26 | if (platform === "win32") { |
| 27 | const overlay = count > 0 && image ? Electron.nativeImage.createFromDataURL(image) : null; |
| 28 | for (const window of Electron.BrowserWindow.getAllWindows()) { |
| 29 | if (!window.isDestroyed()) { |
| 30 | window.setOverlayIcon( |
| 31 | overlay?.isEmpty() ? null : overlay, |
| 32 | count > 0 ? `${count} threads with new notifications` : "", |
| 33 | ); |
| 34 | } |
| 35 | } |
| 36 | } else if (platform === "darwin" || platform === "linux") { |
| 37 | Electron.app.setBadgeCount(count); |
| 38 | } |
| 39 | } catch (error) { |
| 40 | Effect.runSync(Effect.logWarning("Could not update notification badge", error)); |
| 41 | } |
| 42 | } |
| 43 | |
| 44 | export const installNotificationBadge = Effect.fn("desktop.ipc.installNotificationBadge")( |
| 45 | function* () { |
| 46 | const ipc = yield* DesktopIpc.DesktopIpc; |
| 47 | const app = yield* ElectronApp.ElectronApp; |
| 48 | const platform = yield* HostProcess.Platform; |
| 49 | const clear = () => { |
| 50 | applyNotificationBadge(platform, { count: 0, image: null }); |
| 51 | for (const window of Electron.BrowserWindow.getAllWindows()) { |
| 52 | if (!window.isDestroyed()) window.webContents.send(SET_NOTIFICATION_BADGE_CHANNEL); |
| 53 | } |
| 54 | }; |
| 55 | yield* ipc.handle( |
| 56 | DesktopIpc.makeIpcMethod({ |
| 57 | channel: SET_NOTIFICATION_BADGE_CHANNEL, |
| 58 | payload: NotificationBadge, |
| 59 | result: Schema.Void, |
| 60 | handler: (badge) => |
| 61 | Effect.sync(() => { |
| 62 | if (badge.count > 0 && Electron.BrowserWindow.getFocusedWindow()) clear(); |
| 63 | else applyNotificationBadge(platform, badge); |
| 64 | }), |
| 65 | }), |
| 66 | ); |
| 67 | yield* app.on("browser-window-focus", clear); |
| 68 | yield* app.on("before-quit", clear); |
| 69 | yield* Effect.addFinalizer(() => Effect.sync(clear)); |
| 70 | }, |
| 71 | ); |