diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 0cf0a70419..86b3629934 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -591,6 +591,14 @@ "messageformat": "{count, plural, one {# Unread Message} other {# Unread Messages}}", "description": "Text for unread message separator, with count" }, + "icu:taskbarMarkedUnread": { + "messageformat": "Marked Unread", + "description": "On Windows, description for screen readers for the OS taskbar icon's overlay badge when the user has marked chat(s) as unread." + }, + "icu:taskbarUnreadMessages": { + "messageformat": "{count, plural, one {# Unread Message} other {# Unread Messages}}", + "description": "On Windows, description for screen readers for the OS taskbar icon's overlay badge when there are unread messages." + }, "icu:messageHistoryUnsynced": { "messageformat": "For your security, chat history isn't transferred to new linked devices.", "description": "Shown in the conversation history when a user links a new device to explain what is not supported." diff --git a/app/main.ts b/app/main.ts index f87d8de70c..78baf7ff2b 100644 --- a/app/main.ts +++ b/app/main.ts @@ -114,6 +114,7 @@ import type { ParsedSignalRoute } from '../ts/util/signalRoutes'; import { parseSignalRoute } from '../ts/util/signalRoutes'; import * as dns from '../ts/util/dns'; import { ZoomFactorService } from '../ts/services/ZoomFactorService'; +import { getMarkedUnreadIcon, getUnreadIcon } from '../ts/util/unreadIcon'; const STICKER_CREATOR_PARTITION = 'sticker-creator'; @@ -2261,10 +2262,44 @@ ipc.on( if (process.platform === 'darwin') { // Will show a ● on macOS when undefined app.setBadgeCount(undefined); + } else if (process.platform === 'win32') { + // setBadgeCount is unsupported on Windows, so we use setOverlayIcon + let description; + try { + description = getResolvedMessagesLocale().i18n( + 'icu:taskbarMarkedUnread' + ); + } catch { + description = 'Unread'; + } + mainWindow?.setOverlayIcon(getMarkedUnreadIcon(), description); } else { // All other OS's need a number app.setBadgeCount(1); } + return; + } + + if (process.platform === 'win32') { + if (!mainWindow) { + return; + } + + let description; + try { + description = getResolvedMessagesLocale().i18n( + 'icu:taskbarUnreadMessages', + { count: badge } + ); + } catch { + description = String(badge); + } + + if (badge === 0) { + mainWindow.setOverlayIcon(null, ''); + } else { + mainWindow.setOverlayIcon(getUnreadIcon(badge), description); + } } else { app.setBadgeCount(badge); } diff --git a/images/unread-icon/1.png b/images/unread-icon/1.png new file mode 100644 index 0000000000..842d2f2ddd Binary files /dev/null and b/images/unread-icon/1.png differ diff --git a/images/unread-icon/2.png b/images/unread-icon/2.png new file mode 100644 index 0000000000..767d459c8f Binary files /dev/null and b/images/unread-icon/2.png differ diff --git a/images/unread-icon/3.png b/images/unread-icon/3.png new file mode 100644 index 0000000000..3bf14aaf54 Binary files /dev/null and b/images/unread-icon/3.png differ diff --git a/images/unread-icon/4.png b/images/unread-icon/4.png new file mode 100644 index 0000000000..e1d4e7e399 Binary files /dev/null and b/images/unread-icon/4.png differ diff --git a/images/unread-icon/5.png b/images/unread-icon/5.png new file mode 100644 index 0000000000..f50dbcf191 Binary files /dev/null and b/images/unread-icon/5.png differ diff --git a/images/unread-icon/6.png b/images/unread-icon/6.png new file mode 100644 index 0000000000..50f7261cea Binary files /dev/null and b/images/unread-icon/6.png differ diff --git a/images/unread-icon/7.png b/images/unread-icon/7.png new file mode 100644 index 0000000000..87a7084ddb Binary files /dev/null and b/images/unread-icon/7.png differ diff --git a/images/unread-icon/8.png b/images/unread-icon/8.png new file mode 100644 index 0000000000..5fca2fd6be Binary files /dev/null and b/images/unread-icon/8.png differ diff --git a/images/unread-icon/9-plus.png b/images/unread-icon/9-plus.png new file mode 100644 index 0000000000..5facbea074 Binary files /dev/null and b/images/unread-icon/9-plus.png differ diff --git a/images/unread-icon/9.png b/images/unread-icon/9.png new file mode 100644 index 0000000000..0e81060472 Binary files /dev/null and b/images/unread-icon/9.png differ diff --git a/images/unread-icon/marked-unread.png b/images/unread-icon/marked-unread.png new file mode 100644 index 0000000000..a38f9f6558 Binary files /dev/null and b/images/unread-icon/marked-unread.png differ diff --git a/ts/util/unreadIcon.ts b/ts/util/unreadIcon.ts new file mode 100644 index 0000000000..f68221f376 --- /dev/null +++ b/ts/util/unreadIcon.ts @@ -0,0 +1,26 @@ +// Copyright 2024 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import { join } from 'path'; +import type { NativeImage } from 'electron'; +import { nativeImage } from 'electron'; + +export function getUnreadIcon(unreadCount: number): NativeImage { + const filename = + unreadCount > 9 ? '9-plus.png' : `${String(unreadCount)}.png`; + const path = join(__dirname, '..', '..', 'images', 'unread-icon', filename); + // if path does not exist, this returns an empty NativeImage + return nativeImage.createFromPath(path); +} + +export function getMarkedUnreadIcon(): NativeImage { + const path = join( + __dirname, + '..', + '..', + 'images', + 'unread-icon', + 'marked-unread.png' + ); + return nativeImage.createFromPath(path); +}