From 7f1de8e508216bdda9dc98a6a7106bb5388f8e4b Mon Sep 17 00:00:00 2001 From: Albert Zhang Date: Wed, 29 Nov 2023 20:17:26 -0500 Subject: [PATCH 01/12] Tray icon notifications --- package.json | 3 + pnpm-lock.yaml | 302 +++++++++++++++++++++++++++ scripts/build/build.mts | 7 + scripts/build/composeTrayIcons.mts | 221 ++++++++++++++++++++ src/main/appBadge.ts | 33 ++- src/main/mainWindow.ts | 10 +- src/preload/VesktopNative.ts | 2 +- src/renderer/appBadge.ts | 6 +- src/renderer/components/Settings.tsx | 18 +- src/shared/paths.ts | 2 + src/shared/settings.d.ts | 1 + static/dist/.gitignore | 3 +- 12 files changed, 589 insertions(+), 19 deletions(-) create mode 100644 scripts/build/composeTrayIcons.mts diff --git a/package.json b/package.json index fb23c98..95e5364 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,10 @@ "eslint-plugin-prettier": "^5.0.1", "eslint-plugin-simple-import-sort": "^10.0.0", "eslint-plugin-unused-imports": "^3.0.0", + "fast-glob": "3.3", "prettier": "^3.1.0", + "sharp": "^0.33.0", + "sharp-ico": "^0.1.5", "source-map-support": "^0.5.21", "tsx": "^4.6.0", "type-fest": "^4.8.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6d089f4..d1b72d0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -69,9 +69,18 @@ devDependencies: eslint-plugin-unused-imports: specifier: ^3.0.0 version: 3.0.0(@typescript-eslint/eslint-plugin@6.13.1)(eslint@8.54.0) + fast-glob: + specifier: '3.3' + version: 3.3.1 prettier: specifier: ^3.1.0 version: 3.1.0 + sharp: + specifier: ^0.33.0 + version: 0.33.0 + sharp-ico: + specifier: ^0.1.5 + version: 0.1.5 source-map-support: specifier: ^0.5.21 version: 0.5.21 @@ -99,6 +108,10 @@ packages: engines: {node: '>=0.10.0'} dev: true + /@canvas/image-data@1.0.0: + resolution: {integrity: sha512-BxOqI5LgsIQP1odU5KMwV9yoijleOPzHL18/YvNqF9KFSGF2K/DLlYAbDQsWqd/1nbaFuSkYD/191dpMtNh4vw==} + dev: true + /@develar/schema-utils@2.6.5: resolution: {integrity: sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==} engines: {node: '>= 8.9.0'} @@ -175,6 +188,14 @@ packages: - supports-color dev: true + /@emnapi/runtime@0.44.0: + resolution: {integrity: sha512-ZX/etZEZw8DR7zAB1eVQT40lNo0jeqpb6dCgOvctB6FIQ5PoXfMuNY8+ayQfu8tNQbAB8gQWSSJupR8NxeiZXw==} + requiresBuild: true + dependencies: + tslib: 2.6.2 + dev: true + optional: true + /@esbuild/android-arm64@0.18.20: resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} engines: {node: '>=12'} @@ -632,6 +653,194 @@ packages: resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==} dev: true + /@img/sharp-darwin-arm64@0.33.0: + resolution: {integrity: sha512-070tEheekI1LJWTGPC9WlQEa5UoKTXzzlORBHMX4TbfUxMiL336YHR8vBEUNsjse0RJCX8dZ4ZXwT595aEF1ug==} + engines: {glibc: '>=2.26', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.0.0 + dev: true + optional: true + + /@img/sharp-darwin-x64@0.33.0: + resolution: {integrity: sha512-pu/nvn152F3qbPeUkr+4e9zVvEhD3jhwzF473veQfMPkOYo9aoWXSfdZH/E6F+nYC3qvFjbxbvdDbUtEbghLqw==} + engines: {glibc: '>=2.26', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + cpu: [x64] + os: [darwin] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.0.0 + dev: true + optional: true + + /@img/sharp-libvips-darwin-arm64@1.0.0: + resolution: {integrity: sha512-VzYd6OwnUR81sInf3alj1wiokY50DjsHz5bvfnsFpxs5tqQxESoHtJO6xyksDs3RIkyhMWq2FufXo6GNSU9BMw==} + engines: {macos: '>=11', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@img/sharp-libvips-darwin-x64@1.0.0: + resolution: {integrity: sha512-dD9OznTlHD6aovRswaPNEy8dKtSAmNo4++tO7uuR4o5VxbVAOoEQ1uSmN4iFAdQneTHws1lkTZeiXPrcCkh6IA==} + engines: {macos: '>=10.13', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@img/sharp-libvips-linux-arm64@1.0.0: + resolution: {integrity: sha512-xTYThiqEZEZc0PRU90yVtM3KE7lw1bKdnDQ9kCTHWbqWyHOe4NpPOtMGy27YnN51q0J5dqRrvicfPbALIOeAZA==} + engines: {glibc: '>=2.26', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@img/sharp-libvips-linux-arm@1.0.0: + resolution: {integrity: sha512-VwgD2eEikDJUk09Mn9Dzi1OW2OJFRQK+XlBTkUNmAWPrtj8Ly0yq05DFgu1VCMx2/DqCGQVi5A1dM9hTmxf3uw==} + engines: {glibc: '>=2.28', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@img/sharp-libvips-linux-s390x@1.0.0: + resolution: {integrity: sha512-o9E46WWBC6JsBlwU4QyU9578G77HBDT1NInd+aERfxeOPbk0qBZHgoDsQmA2v9TbqJRWzoBPx1aLOhprBMgPjw==} + engines: {glibc: '>=2.28', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@img/sharp-libvips-linux-x64@1.0.0: + resolution: {integrity: sha512-naldaJy4hSVhWBgEjfdBY85CAa4UO+W1nx6a1sWStHZ7EUfNiuBTTN2KUYT5dH1+p/xij1t2QSXfCiFJoC5S/Q==} + engines: {glibc: '>=2.26', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@img/sharp-libvips-linuxmusl-arm64@1.0.0: + resolution: {integrity: sha512-OdorplCyvmSAPsoJLldtLh3nLxRrkAAAOHsGWGDYfN0kh730gifK+UZb3dWORRa6EusNqCTjfXV4GxvgJ/nPDQ==} + engines: {musl: '>=1.2.2', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@img/sharp-libvips-linuxmusl-x64@1.0.0: + resolution: {integrity: sha512-FW8iK6rJrg+X2jKD0Ajhjv6y74lToIBEvkZhl42nZt563FfxkCYacrXZtd+q/sRQDypQLzY5WdLkVTbJoPyqNg==} + engines: {musl: '>=1.2.2', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@img/sharp-linux-arm64@0.33.0: + resolution: {integrity: sha512-dcomVSrtgF70SyOr8RCOCQ8XGVThXwe71A1d8MGA+mXEVRJ/J6/TrCbBEJh9ddcEIIsrnrkolaEvYSHqVhswQw==} + engines: {glibc: '>=2.26', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + cpu: [arm64] + os: [linux] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.0.0 + dev: true + optional: true + + /@img/sharp-linux-arm@0.33.0: + resolution: {integrity: sha512-4horD3wMFd5a0ddbDY8/dXU9CaOgHjEHALAddXgafoR5oWq5s8X61PDgsSeh4Qupsdo6ycfPPSSNBrfVQnwwrg==} + engines: {glibc: '>=2.28', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + cpu: [arm] + os: [linux] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.0.0 + dev: true + optional: true + + /@img/sharp-linux-s390x@0.33.0: + resolution: {integrity: sha512-TiVJbx38J2rNVfA309ffSOB+3/7wOsZYQEOlKqOUdWD/nqkjNGrX+YQGz7nzcf5oy2lC+d37+w183iNXRZNngQ==} + engines: {glibc: '>=2.28', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + cpu: [s390x] + os: [linux] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.0.0 + dev: true + optional: true + + /@img/sharp-linux-x64@0.33.0: + resolution: {integrity: sha512-PaZM4Zi7/Ek71WgTdvR+KzTZpBqrQOFcPe7/8ZoPRlTYYRe43k6TWsf4GVH6XKRLMYeSp8J89RfAhBrSP4itNA==} + engines: {glibc: '>=2.26', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + cpu: [x64] + os: [linux] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.0.0 + dev: true + optional: true + + /@img/sharp-linuxmusl-arm64@0.33.0: + resolution: {integrity: sha512-1QLbbN0zt+32eVrg7bb1lwtvEaZwlhEsY1OrijroMkwAqlHqFj6R33Y47s2XUv7P6Ie1PwCxK/uFnNqMnkd5kg==} + engines: {musl: '>=1.2.2', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + cpu: [arm64] + os: [linux] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.0.0 + dev: true + optional: true + + /@img/sharp-linuxmusl-x64@0.33.0: + resolution: {integrity: sha512-CecqgB/CnkvCWFhmfN9ZhPGMLXaEBXl4o7WtA6U3Ztrlh/s7FUKX4vNxpMSYLIrWuuzjiaYdfU3+Tdqh1xaHfw==} + engines: {musl: '>=1.2.2', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + cpu: [x64] + os: [linux] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.0.0 + dev: true + optional: true + + /@img/sharp-wasm32@0.33.0: + resolution: {integrity: sha512-Hn4js32gUX9qkISlemZBUPuMs0k/xNJebUNl/L6djnU07B/HAA2KaxRVb3HvbU5fL242hLOcp0+tR+M8dvJUFw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + cpu: [wasm32] + requiresBuild: true + dependencies: + '@emnapi/runtime': 0.44.0 + dev: true + optional: true + + /@img/sharp-win32-ia32@0.33.0: + resolution: {integrity: sha512-5HfcsCZi3l5nPRF2q3bllMVMDXBqEWI3Q8KQONfzl0TferFE5lnsIG0A1YrntMAGqvkzdW6y1Ci1A2uTvxhfzg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@img/sharp-win32-x64@0.33.0: + resolution: {integrity: sha512-i3DtP/2ce1yKFj4OzOnOYltOEL/+dp4dc4dJXJBv6god1AFTcmkaA99H/7SwOmkCOBQkbVvA3lCGm3/5nDtf9Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + /@malept/cross-spawn-promise@1.1.1: resolution: {integrity: sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ==} engines: {node: '>= 10'} @@ -1513,6 +1722,13 @@ packages: /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + /color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + dev: true + /color-support@1.1.3: resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} hasBin: true @@ -1520,6 +1736,14 @@ packages: dev: false optional: true + /color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + dev: true + /combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} @@ -1622,6 +1846,23 @@ packages: dependencies: ms: 2.1.2 + /decode-bmp@0.2.1: + resolution: {integrity: sha512-NiOaGe+GN0KJqi2STf24hfMkFitDUaIoUU3eKvP/wAbLe8o6FuW5n/x7MHPR0HKvBokp6MQY/j7w8lewEeVCIA==} + engines: {node: '>=8.6.0'} + dependencies: + '@canvas/image-data': 1.0.0 + to-data-view: 1.1.0 + dev: true + + /decode-ico@0.4.1: + resolution: {integrity: sha512-69NZfbKIzux1vBOd31al3XnMnH+2mqDhEgLdpygErm4d60N+UwA5Sq5WFjmEDQzumgB9fElojGwWG0vybVfFmA==} + engines: {node: '>=8.6'} + dependencies: + '@canvas/image-data': 1.0.0 + decode-bmp: 0.2.1 + to-data-view: 1.1.0 + dev: true + /decode-uri-component@0.2.2: resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} engines: {node: '>=0.10'} @@ -1723,6 +1964,11 @@ packages: dev: false optional: true + /detect-libc@2.0.2: + resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==} + engines: {node: '>=8'} + dev: true + /detect-node@2.1.0: resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} requiresBuild: true @@ -2810,6 +3056,10 @@ packages: engines: {node: '>=14.18.0'} dev: true + /ico-endec@0.1.6: + resolution: {integrity: sha512-ZdLU38ZoED3g1j3iEyzcQj+wAkY2xfWNkymszfJPoxucIUhK7NayQ+/C4Kv0nDFMIsbtbEHldv3V8PU494/ueQ==} + dev: true + /iconv-corefoundation@1.1.7: resolution: {integrity: sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==} engines: {node: ^8.11.2 || >=10} @@ -2899,6 +3149,10 @@ packages: is-typed-array: 1.1.12 dev: true + /is-arrayish@0.3.2: + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + dev: true + /is-bigint@1.0.4: resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} dependencies: @@ -4020,6 +4274,44 @@ packages: split-string: 3.1.0 dev: true + /sharp-ico@0.1.5: + resolution: {integrity: sha512-a3jODQl82NPp1d5OYb0wY+oFaPk7AvyxipIowCHk7pBsZCWgbe0yAkU2OOXdoH0ENyANhyOQbs9xkAiRHcF02Q==} + dependencies: + decode-ico: 0.4.1 + ico-endec: 0.1.6 + sharp: 0.33.0 + dev: true + + /sharp@0.33.0: + resolution: {integrity: sha512-99DZKudjm/Rmz+M0/26t4DKpXyywAOJaayGS9boEn7FvgtG0RYBi46uPE2c+obcJRtA3AZa0QwJot63gJQ1F0Q==} + engines: {libvips: '>=8.15.0', node: ^18.17.0 || ^20.3.0 || >=21.0.0} + requiresBuild: true + dependencies: + color: 4.2.3 + detect-libc: 2.0.2 + semver: 7.5.4 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.33.0 + '@img/sharp-darwin-x64': 0.33.0 + '@img/sharp-libvips-darwin-arm64': 1.0.0 + '@img/sharp-libvips-darwin-x64': 1.0.0 + '@img/sharp-libvips-linux-arm': 1.0.0 + '@img/sharp-libvips-linux-arm64': 1.0.0 + '@img/sharp-libvips-linux-s390x': 1.0.0 + '@img/sharp-libvips-linux-x64': 1.0.0 + '@img/sharp-libvips-linuxmusl-arm64': 1.0.0 + '@img/sharp-libvips-linuxmusl-x64': 1.0.0 + '@img/sharp-linux-arm': 0.33.0 + '@img/sharp-linux-arm64': 0.33.0 + '@img/sharp-linux-s390x': 0.33.0 + '@img/sharp-linux-x64': 0.33.0 + '@img/sharp-linuxmusl-arm64': 0.33.0 + '@img/sharp-linuxmusl-x64': 0.33.0 + '@img/sharp-wasm32': 0.33.0 + '@img/sharp-win32-ia32': 0.33.0 + '@img/sharp-win32-x64': 0.33.0 + dev: true + /shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -4043,6 +4335,12 @@ packages: /signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + /simple-swizzle@0.2.2: + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + dependencies: + is-arrayish: 0.3.2 + dev: true + /simple-update-notifier@2.0.0: resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} engines: {node: '>=10'} @@ -4297,6 +4595,10 @@ packages: rimraf: 3.0.2 dev: true + /to-data-view@1.1.0: + resolution: {integrity: sha512-1eAdufMg6mwgmlojAx3QeMnzB/BTVp7Tbndi3U7ftcT2zCZadjxkkmLmd97zmaxWi+sgGcgWrokmpEoy0Dn0vQ==} + dev: true + /to-object-path@0.3.0: resolution: {integrity: sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==} engines: {node: '>=0.10.0'} diff --git a/scripts/build/build.mts b/scripts/build/build.mts index 27f45cc..a8504ab 100644 --- a/scripts/build/build.mts +++ b/scripts/build/build.mts @@ -8,6 +8,7 @@ import { BuildContext, BuildOptions, context } from "esbuild"; import { copyFile } from "fs/promises"; import vencordDep from "./vencordDep.mjs"; +import { composeTrayIcons } from "./composeTrayIcons.mts"; const isDev = process.argv.includes("--dev"); @@ -51,6 +52,12 @@ async function copyVenmic() { await Promise.all([ copyVenmic(), + composeTrayIcons({ + icon: "./static/icon.png", + badges: "./static/badges/*", + outDir: "./static/dist/tray_icons", + createEmpty: true + }), createContext({ ...NodeCommonOpts, entryPoints: ["src/main/index.ts"], diff --git a/scripts/build/composeTrayIcons.mts b/scripts/build/composeTrayIcons.mts new file mode 100644 index 0000000..8e71e8a --- /dev/null +++ b/scripts/build/composeTrayIcons.mts @@ -0,0 +1,221 @@ +import sharp, { OutputInfo } from "sharp"; +import fastGlob from "fast-glob"; +import type { ImageData } from "sharp-ico"; +import { parse as pathParse, format as pathFormat } from "node:path"; + +interface BadgePosition { + left?: number; + top?: number; + anchorX?: "left" | "right" | "center"; + anchorY?: "top" | "bottom" | "center"; +} + +interface BadgeOptions extends BadgePosition { + width?: number; + height?: number; + resizeOptions?: sharp.ResizeOptions; +} + +const DEFAULT_BADGE_OPTIONS: Required = { + width: 0.5, + height: 0.5, + left: 0.8, + top: 0.8, + anchorX: "center", + anchorY: "center", + resizeOptions: { + kernel: sharp.kernel.cubic + } +}; + +export async function composeTrayIcons({ + icon: iconPath, + badges: badgeGlob, + outDir, + outExt = ".png", + createEmpty = false, + iconOptions = { width: 64, height: 64 }, + badgeOptions = undefined +}: { + icon: string | Buffer | sharp.Sharp; + badges: string; + outDir: string; + outExt?: string; + createEmpty?: boolean; + iconOptions?: ImageDim; + badgeOptions?: BadgeOptions; +}) { + const badges = await fastGlob.glob(badgeGlob); + if (!badges.length) { + throw new Error(`No badges matching glob '${badgeGlob}' found!`); + } + + const badgeOptionsFilled = { ...DEFAULT_BADGE_OPTIONS, ...badgeOptions }; + const { data: iconData, info: iconInfo } = await resolveImageOrIco(iconPath, iconOptions); + const iconName = typeof iconPath === "string" ? pathParse(iconPath).name : "tray_icon"; + + const resizedBadgeDim = { + height: Math.round(badgeOptionsFilled.height * iconInfo.height), + width: Math.round(badgeOptionsFilled.width * iconInfo.width) + }; + + async function doCompose(badgePath: string | sharp.Sharp, ensureSize?: ImageDim | false) { + const { data: badgeData, info: badgeInfo } = await resolveImageOrIco(badgePath, resizedBadgeDim); + if (ensureSize && (badgeInfo.height !== ensureSize.height || badgeInfo.width !== ensureSize.width)) { + throw new Error( + `Badge loaded from ${badgePath} has size ${badgeInfo.height}x${badgeInfo.height} != ${ensureSize.height}x${ensureSize.height}` + ); + } + + const savePath = pathFormat({ + name: iconName + (typeof badgePath === "string" ? "_" + pathParse(badgePath).name : ""), + dir: outDir, + ext: outExt, + base: undefined + }); + let out = composeTrayIcon(iconData, iconInfo, badgeData, badgeInfo, badgeOptionsFilled); + const outputInfo = await out.toFile(savePath); + return { + iconInfo, + badgeInfo, + outputInfo + }; + } + + if (createEmpty) { + const firstComposition = await doCompose(badges[0]); + return await Promise.all([ + firstComposition, + ...badges.map(badge => doCompose(badge, firstComposition.badgeInfo)), + doCompose(emptyImage(firstComposition.badgeInfo).png()) + ]); + } else { + return await Promise.all(badges.map(badge => doCompose(badge))); + } +} + +type SharpInput = string | Buffer; + +interface ImageDim { + width: number; + height: number; +} + +async function resolveImageOrIco(...args: Parameters) { + const image = await loadFromImageOrIco(...args); + const { data, info } = await image.toBuffer({ resolveWithObject: true }); + return { + data, + info: validDim(info) + }; +} + +async function loadFromImageOrIco( + path: string | Buffer | sharp.Sharp, + sizeOptions?: ImageDim & { resizeICO?: boolean } +): Promise { + if (typeof path === "string" && path.endsWith(".ico")) { + const icos = (await import("sharp-ico")).sharpsFromIco(path, undefined, true) as unknown as ImageData[]; + let icoInfo; + if (sizeOptions == null) { + icoInfo = icos[icos.length - 1]; + } else { + icoInfo = icos.reduce((best, ico) => + Math.abs(ico.width - sizeOptions.width) < Math.abs(ico.width - best.width) ? ico : best + ); + } + + if (icoInfo.image == null) { + throw new Error("Bug: sharps-ico found no image in ICO"); + } + + const icoImage = icoInfo.image.png(); + if (sizeOptions?.resizeICO) { + return icoImage.resize(sizeOptions); + } else { + return icoImage; + } + } else { + let image = typeof path !== "string" && "toBuffer" in path ? path : sharp(path); + if (sizeOptions) { + image = image.resize(sizeOptions); + } + return image; + } +} + +function validDim>(meta: T): T & ImageDim { + if (meta?.width == null || meta?.height == null) { + throw new Error("Failed getting icon dimensions"); + } + return meta as T & ImageDim; +} + +function emptyImage(dim: ImageDim) { + return sharp({ + create: { + width: dim.width, + height: dim.height, + channels: 4, + background: { r: 0, b: 0, g: 0, alpha: 0 } + } + }); +} + +function composeTrayIcon( + icon: SharpInput, + iconDim: ImageDim, + badge: SharpInput, + badgeDim: ImageDim, + badgeOptions: Required +): sharp.Sharp { + let badgeLeft = badgeOptions.left * iconDim.width; + switch (badgeOptions.anchorX) { + case "left": + break; + case "right": + badgeLeft -= badgeDim.width; + break; + case "center": + badgeLeft -= badgeDim.width / 2; + break; + } + let badgeTop = badgeOptions.top * iconDim.height; + switch (badgeOptions.anchorY) { + case "top": + break; + case "bottom": + badgeTop -= badgeDim.height / 2; + break; + case "center": + badgeTop -= badgeDim.height / 2; + break; + } + + badgeTop = Math.round(badgeTop); + badgeLeft = Math.round(badgeLeft); + + const padding = Math.max( + 0, + -badgeLeft, + badgeLeft + badgeDim.width - iconDim.width, + -badgeTop, + badgeTop + badgeDim.height - iconDim.height + ); + + return emptyImage({ + width: iconDim.width + 2 * padding, + height: iconDim.height + 2 * padding + }).composite([ + { + input: icon, + left: padding, + top: padding + }, + { + input: badge, + left: badgeLeft + padding, + top: badgeTop + padding + } + ]); +} diff --git a/src/main/appBadge.ts b/src/main/appBadge.ts index 46abe1d..63103e6 100644 --- a/src/main/appBadge.ts +++ b/src/main/appBadge.ts @@ -6,27 +6,43 @@ import { app, NativeImage, nativeImage } from "electron"; import { join } from "path"; -import { BADGE_DIR } from "shared/paths"; +import { BADGE_DIR, TRAY_ICON_DIR, TRAY_ICON_PATH } from "shared/paths"; +import { trayContainer } from "./mainWindow"; +import { Settings } from "./settings"; -const imgCache = new Map(); -function loadBadge(index: number) { - const cached = imgCache.get(index); +const imgCache = new Map(); + +function loadImg(path: string) { + const cached = imgCache.get(path); if (cached) return cached; - const img = nativeImage.createFromPath(join(BADGE_DIR, `${index}.ico`)); - imgCache.set(index, img); + const img = nativeImage.createFromPath(path); + imgCache.set(path, img); return img; } +function loadBadge(index: number) { + return loadImg(join(BADGE_DIR, `${index}.ico`)); +} + +function loadTrayIcon(index: number) { + return loadImg(index === 0 ? TRAY_ICON_PATH : join(TRAY_ICON_DIR, `icon_${index}.png`)); +} + let lastIndex: null | number = -1; export function setBadgeCount(count: number) { + const [index, description] = getBadgeIndexAndDescription(count); + + if (Settings?.store.trayBadge) { + trayContainer.tray?.setImage(loadTrayIcon(index ?? 0)); + } + switch (process.platform) { case "linux": if (count === -1) count = 0; - app.setBadgeCount(count); - break; + app.setBadgeCount(count); // Only works if libunity is installed case "darwin": if (count === 0) { app.dock.setBadge(""); @@ -35,7 +51,6 @@ export function setBadgeCount(count: number) { app.dock.setBadge(count === -1 ? "•" : count.toString()); break; case "win32": - const [index, description] = getBadgeIndexAndDescription(count); if (lastIndex === index) break; lastIndex = index; diff --git a/src/main/mainWindow.ts b/src/main/mainWindow.ts index 6e9a05c..17e1605 100644 --- a/src/main/mainWindow.ts +++ b/src/main/mainWindow.ts @@ -21,7 +21,7 @@ import { isTruthy } from "shared/utils/guards"; import { once } from "shared/utils/once"; import type { SettingsStore } from "shared/utils/SettingsStore"; -import { ICON_PATH } from "../shared/paths"; +import { ICON_PATH, TRAY_ICON_PATH } from "../shared/paths"; import { createAboutWindow } from "./about"; import { initArRPC } from "./arrpc"; import { @@ -41,7 +41,9 @@ import { applyDeckKeyboardFix, askToApplySteamLayout, isDeckGameMode } from "./u import { downloadVencordFiles, ensureVencordFiles } from "./utils/vencordLoader"; let isQuitting = false; -let tray: Tray; +export const trayContainer: { tray: Tray | null } = { + tray: null +}; applyDeckKeyboardFix(); @@ -118,7 +120,7 @@ function initTray(win: BrowserWindow) { } ]); - tray = new Tray(ICON_PATH); + const tray = (trayContainer.tray = new Tray(TRAY_ICON_PATH)); tray.setToolTip("Vesktop"); tray.setContextMenu(trayMenu); tray.on("click", () => win.show()); @@ -332,7 +334,7 @@ function initWindowBoundsListeners(win: BrowserWindow) { function initSettingsListeners(win: BrowserWindow) { addSettingsListener("tray", enable => { if (enable) initTray(win); - else tray?.destroy(); + else trayContainer.tray?.destroy(); }); addSettingsListener("disableMinSize", disable => { if (disable) { diff --git a/src/preload/VesktopNative.ts b/src/preload/VesktopNative.ts index d72329d..09eda0d 100644 --- a/src/preload/VesktopNative.ts +++ b/src/preload/VesktopNative.ts @@ -23,7 +23,7 @@ export const VesktopNative = { app: { relaunch: () => invoke(IpcEvents.RELAUNCH), getVersion: () => sendSync(IpcEvents.GET_VERSION), - setBadgeCount: (count: number) => invoke(IpcEvents.SET_BADGE_COUNT, count), + setAppBadgeCount: (count: number) => invoke(IpcEvents.SET_BADGE_COUNT, count), supportsWindowsTransparency: () => sendSync(IpcEvents.SUPPORTS_WINDOWS_TRANSPARENCY) }, autostart: { diff --git a/src/renderer/appBadge.ts b/src/renderer/appBadge.ts index b55d488..0c1b6af 100644 --- a/src/renderer/appBadge.ts +++ b/src/renderer/appBadge.ts @@ -13,7 +13,9 @@ let GuildReadStateStore: any; let NotificationSettingsStore: any; export function setBadge() { - if (Settings.store.appBadge === false) return; + const { appBadge, trayBadge } = Settings.store; + + if (appBadge === false && trayBadge === false) return; try { const mentionCount = GuildReadStateStore.getTotalMentionCount(); @@ -24,7 +26,7 @@ export function setBadge() { let totalCount = mentionCount + pendingRequests; if (!totalCount && hasUnread && !disableUnreadBadge) totalCount = -1; - VesktopNative.app.setBadgeCount(totalCount); + if (appBadge || trayBadge) VesktopNative.app.setAppBadgeCount(totalCount); } catch (e) { console.error(e); } diff --git a/src/renderer/components/Settings.tsx b/src/renderer/components/Settings.tsx index b90c746..13fea9c 100644 --- a/src/renderer/components/Settings.tsx +++ b/src/renderer/components/Settings.tsx @@ -93,13 +93,27 @@ export default function SettingsUi() { onChange={v => { Settings.appBadge = v; if (v) setBadge(); - else VesktopNative.app.setBadgeCount(0); + else VesktopNative.app.setAppBadgeCount(0); }} - note="Show mention badge on the app icon" + note="Show mention badge on the app (taskbar/panel) icon" > Notification Badge + {Settings.tray && ( + { + Settings.trayBadge = v; + if (v) setBadge(); + else VesktopNative.app.setAppBadgeCount(0); + }} + note="Show mention badge on the tray icon" + > + Tray Notification Badge + + )} + {switches.map(([key, text, note, def, predicate]) => ( Date: Wed, 29 Nov 2023 21:51:02 -0500 Subject: [PATCH 02/12] Build doesn't crash --- src/main/appBadge.ts | 18 +++++++++----- src/main/ipc.ts | 2 +- src/main/mainWindow.ts | 19 ++++++++------- src/preload/VesktopNative.ts | 2 +- src/renderer/appBadge.ts | 7 ++---- src/renderer/components/Settings.tsx | 36 ++++++++++++++-------------- 6 files changed, 44 insertions(+), 40 deletions(-) diff --git a/src/main/appBadge.ts b/src/main/appBadge.ts index 63103e6..fda7eea 100644 --- a/src/main/appBadge.ts +++ b/src/main/appBadge.ts @@ -7,8 +7,7 @@ import { app, NativeImage, nativeImage } from "electron"; import { join } from "path"; import { BADGE_DIR, TRAY_ICON_DIR, TRAY_ICON_PATH } from "shared/paths"; -import { trayContainer } from "./mainWindow"; -import { Settings } from "./settings"; +import type { VencordBrowserWindow } from "./mainWindow"; const imgCache = new Map(); @@ -32,17 +31,24 @@ function loadTrayIcon(index: number) { let lastIndex: null | number = -1; -export function setBadgeCount(count: number) { +let mainWin: null | VencordBrowserWindow; +function getMainWin() { + if (mainWin != null) return mainWin; + return (mainWin = (require("./mainWindow") as typeof import("./mainWindow")).mainWin); +} + +export function setBadgeCount(count: number, tray: boolean = false) { const [index, description] = getBadgeIndexAndDescription(count); - if (Settings?.store.trayBadge) { - trayContainer.tray?.setImage(loadTrayIcon(index ?? 0)); + if (tray) { + getMainWin()._vencord_tray?.setImage(loadTrayIcon(index ?? 0)); } switch (process.platform) { case "linux": if (count === -1) count = 0; app.setBadgeCount(count); // Only works if libunity is installed + break; case "darwin": if (count === 0) { app.dock.setBadge(""); @@ -56,7 +62,7 @@ export function setBadgeCount(count: number) { lastIndex = index; // circular import shenanigans - const { mainWin } = require("./mainWindow") as typeof import("./mainWindow"); + const mainWin = getMainWin(); mainWin.setOverlayIcon(index === null ? null : loadBadge(index), description); break; } diff --git a/src/main/ipc.ts b/src/main/ipc.ts index 84755f4..4003fc7 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -114,7 +114,7 @@ handle(IpcEvents.SELECT_VENCORD_DIR, async () => { return dir; }); -handle(IpcEvents.SET_BADGE_COUNT, (_, count: number) => setBadgeCount(count)); +handle(IpcEvents.SET_BADGE_COUNT, (_, count: number, tray: boolean) => setBadgeCount(count, tray)); function readCss() { return readFile(VENCORD_QUICKCSS_FILE, "utf-8").catch(() => ""); diff --git a/src/main/mainWindow.ts b/src/main/mainWindow.ts index 17e1605..892fe93 100644 --- a/src/main/mainWindow.ts +++ b/src/main/mainWindow.ts @@ -41,9 +41,6 @@ import { applyDeckKeyboardFix, askToApplySteamLayout, isDeckGameMode } from "./u import { downloadVencordFiles, ensureVencordFiles } from "./utils/vencordLoader"; let isQuitting = false; -export const trayContainer: { tray: Tray | null } = { - tray: null -}; applyDeckKeyboardFix(); @@ -51,7 +48,11 @@ app.on("before-quit", () => { isQuitting = true; }); -export let mainWin: BrowserWindow; +type VencordBrowserWindow = BrowserWindow & { + _vencord_tray?: Tray; +}; + +export let mainWin: VencordBrowserWindow; function makeSettingsListenerHelpers(o: SettingsStore) { const listeners = new Map<(data: any) => void, PropertyKey>(); @@ -74,7 +75,7 @@ function makeSettingsListenerHelpers(o: SettingsStore) { const [addSettingsListener, removeSettingsListeners] = makeSettingsListenerHelpers(Settings); const [addVencordSettingsListener, removeVencordSettingsListeners] = makeSettingsListenerHelpers(VencordSettings); -function initTray(win: BrowserWindow) { +function initTray(win: VencordBrowserWindow) { const trayMenu = Menu.buildFromTemplate([ { label: "Open", @@ -120,7 +121,7 @@ function initTray(win: BrowserWindow) { } ]); - const tray = (trayContainer.tray = new Tray(TRAY_ICON_PATH)); + const tray = new Tray(TRAY_ICON_PATH); tray.setToolTip("Vesktop"); tray.setContextMenu(trayMenu); tray.on("click", () => win.show()); @@ -331,10 +332,10 @@ function initWindowBoundsListeners(win: BrowserWindow) { win.on("move", saveBounds); } -function initSettingsListeners(win: BrowserWindow) { +function initSettingsListeners(win: VencordBrowserWindow) { addSettingsListener("tray", enable => { if (enable) initTray(win); - else trayContainer.tray?.destroy(); + else win._vencord_tray?.destroy(); }); addSettingsListener("disableMinSize", disable => { if (disable) { @@ -372,7 +373,7 @@ function initSpellCheck(win: BrowserWindow) { }); } -function createMainWindow() { +function createMainWindow(): VencordBrowserWindow { // Clear up previous settings listeners removeSettingsListeners(); removeVencordSettingsListeners(); diff --git a/src/preload/VesktopNative.ts b/src/preload/VesktopNative.ts index 09eda0d..c5569b8 100644 --- a/src/preload/VesktopNative.ts +++ b/src/preload/VesktopNative.ts @@ -23,7 +23,7 @@ export const VesktopNative = { app: { relaunch: () => invoke(IpcEvents.RELAUNCH), getVersion: () => sendSync(IpcEvents.GET_VERSION), - setAppBadgeCount: (count: number) => invoke(IpcEvents.SET_BADGE_COUNT, count), + setBadgeCount: (count: number, tray: boolean = false) => invoke(IpcEvents.SET_BADGE_COUNT, count, tray), supportsWindowsTransparency: () => sendSync(IpcEvents.SUPPORTS_WINDOWS_TRANSPARENCY) }, autostart: { diff --git a/src/renderer/appBadge.ts b/src/renderer/appBadge.ts index 0c1b6af..be2cbb3 100644 --- a/src/renderer/appBadge.ts +++ b/src/renderer/appBadge.ts @@ -13,10 +13,6 @@ let GuildReadStateStore: any; let NotificationSettingsStore: any; export function setBadge() { - const { appBadge, trayBadge } = Settings.store; - - if (appBadge === false && trayBadge === false) return; - try { const mentionCount = GuildReadStateStore.getTotalMentionCount(); const pendingRequests = RelationshipStore.getPendingCount(); @@ -26,7 +22,8 @@ export function setBadge() { let totalCount = mentionCount + pendingRequests; if (!totalCount && hasUnread && !disableUnreadBadge) totalCount = -1; - if (appBadge || trayBadge) VesktopNative.app.setAppBadgeCount(totalCount); + if (Settings.store.appBadge || Settings.store.trayBadge) + VesktopNative.app.setBadgeCount(totalCount, Settings.store.trayBadge); } catch (e) { console.error(e); } diff --git a/src/renderer/components/Settings.tsx b/src/renderer/components/Settings.tsx index 13fea9c..b020768 100644 --- a/src/renderer/components/Settings.tsx +++ b/src/renderer/components/Settings.tsx @@ -20,7 +20,9 @@ export default function SettingsUi() { const { autostart } = VesktopNative; const [autoStartEnabled, setAutoStartEnabled] = useState(autostart.isEnabled()); - const allSwitches: Array boolean)?]> = [ + const allSwitches: Array< + false | [keyof typeof Settings, string, string, boolean?, (() => boolean)?, ((value: boolean) => void)?] + > = [ isWindows && [ "discordWindowsTitleBar", "Discord Titlebar", @@ -34,6 +36,18 @@ export default function SettingsUi() { true, () => Settings.tray ?? true ], + !isMac && [ + "trayBadge", + "Tray Notification Badge", + "Show mention badge on the tray icon", + false, + () => Settings.tray ?? true, + v => { + Settings.trayBadge = v; + if (v) setBadge(); + else VesktopNative.app.setBadgeCount(0, true); + } + ], ["arRPC", "Rich Presence", "Enables Rich Presence via arRPC", false], [ "disableMinSize", @@ -93,32 +107,18 @@ export default function SettingsUi() { onChange={v => { Settings.appBadge = v; if (v) setBadge(); - else VesktopNative.app.setAppBadgeCount(0); + else VesktopNative.app.setBadgeCount(0, Settings.trayBadge); }} note="Show mention badge on the app (taskbar/panel) icon" > Notification Badge - {Settings.tray && ( - { - Settings.trayBadge = v; - if (v) setBadge(); - else VesktopNative.app.setAppBadgeCount(0); - }} - note="Show mention badge on the tray icon" - > - Tray Notification Badge - - )} - - {switches.map(([key, text, note, def, predicate]) => ( + {switches.map(([key, text, note, def, predicate, onChange]) => ( (Settings[key as any] = v)} + onChange={onChange ?? (v => (Settings[key as any] = v))} note={note} key={key} > From 4725ac467480df4c88aea07d63a176f094900413 Mon Sep 17 00:00:00 2001 From: Albert Zhang Date: Wed, 29 Nov 2023 23:29:09 -0500 Subject: [PATCH 03/12] Resolve issues with circular dependencies --- src/main/appBadge.ts | 14 +++----------- src/main/arrpc.ts | 7 ++++--- src/main/index.ts | 7 ++++--- src/main/ipc.ts | 21 ++++++++++++--------- src/main/mainWindow.ts | 32 ++++++++++++++++++-------------- 5 files changed, 41 insertions(+), 40 deletions(-) diff --git a/src/main/appBadge.ts b/src/main/appBadge.ts index fda7eea..c462c1e 100644 --- a/src/main/appBadge.ts +++ b/src/main/appBadge.ts @@ -7,7 +7,7 @@ import { app, NativeImage, nativeImage } from "electron"; import { join } from "path"; import { BADGE_DIR, TRAY_ICON_DIR, TRAY_ICON_PATH } from "shared/paths"; -import type { VencordBrowserWindow } from "./mainWindow"; +import { globals } from "./mainWindow"; const imgCache = new Map(); @@ -31,17 +31,11 @@ function loadTrayIcon(index: number) { let lastIndex: null | number = -1; -let mainWin: null | VencordBrowserWindow; -function getMainWin() { - if (mainWin != null) return mainWin; - return (mainWin = (require("./mainWindow") as typeof import("./mainWindow")).mainWin); -} - export function setBadgeCount(count: number, tray: boolean = false) { const [index, description] = getBadgeIndexAndDescription(count); if (tray) { - getMainWin()._vencord_tray?.setImage(loadTrayIcon(index ?? 0)); + globals.tray?.setImage(loadTrayIcon(index ?? 0)); } switch (process.platform) { @@ -61,9 +55,7 @@ export function setBadgeCount(count: number, tray: boolean = false) { lastIndex = index; - // circular import shenanigans - const mainWin = getMainWin(); - mainWin.setOverlayIcon(index === null ? null : loadBadge(index), description); + globals.mainWin?.setOverlayIcon(index === null ? null : loadBadge(index), description); break; } } diff --git a/src/main/arrpc.ts b/src/main/arrpc.ts index 1899d9c..dd435bb 100644 --- a/src/main/arrpc.ts +++ b/src/main/arrpc.ts @@ -7,7 +7,7 @@ import Server from "arrpc"; import { IpcEvents } from "shared/IpcEvents"; -import { mainWin } from "./mainWindow"; +import { globals } from "./mainWindow"; import { Settings } from "./settings"; let server: any; @@ -19,12 +19,13 @@ export async function initArRPC() { try { server = await new Server(); - server.on("activity", (data: any) => mainWin.webContents.send(IpcEvents.ARRPC_ACTIVITY, JSON.stringify(data))); + const { mainWin } = globals; + server.on("activity", (data: any) => mainWin!.webContents.send(IpcEvents.ARRPC_ACTIVITY, JSON.stringify(data))); server.on("invite", (invite: string, callback: (valid: boolean) => void) => { invite = String(invite); if (!inviteCodeRegex.test(invite)) return callback(false); - mainWin.webContents + mainWin!.webContents // Safety: Result of JSON.stringify should always be safe to equal // Also, just to be super super safe, invite is regex validated above .executeJavaScript(`Vesktop.openInviteModal(${JSON.stringify(invite)})`) diff --git a/src/main/index.ts b/src/main/index.ts index 832afc8..1268361 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -9,12 +9,12 @@ import "./ipc"; import { app, BrowserWindow } from "electron"; import { checkUpdates } from "updater/main"; +import { Settings } from "./settings"; import { DATA_DIR } from "./constants"; import { createFirstLaunchTour } from "./firstLaunch"; -import { createWindows, mainWin } from "./mainWindow"; +import { createWindows, globals } from "./mainWindow"; import { registerMediaPermissionsHandler } from "./mediaPermissions"; import { registerScreenShareHandler } from "./screenShare"; -import { Settings } from "./settings"; if (IS_DEV) { require("source-map-support").install(); @@ -43,8 +43,9 @@ function init() { ); app.on("second-instance", (_event, _cmdLine, _cwd, data: any) => { + let mainWin; if (data.IS_DEV) app.quit(); - else if (mainWin) { + else if ((mainWin = globals.mainWin)) { if (mainWin.isMinimized()) mainWin.restore(); if (!mainWin.isVisible()) mainWin.show(); mainWin.focus(); diff --git a/src/main/ipc.ts b/src/main/ipc.ts index 4003fc7..9e77c85 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -15,10 +15,11 @@ import { join } from "path"; import { debounce } from "shared/utils/debounce"; import { IpcEvents } from "../shared/IpcEvents"; -import { setBadgeCount } from "./appBadge"; import { autoStart } from "./autoStart"; import { VENCORD_FILES_DIR, VENCORD_QUICKCSS_FILE, VENCORD_THEMES_DIR } from "./constants"; -import { mainWin } from "./mainWindow"; +import { globals } from "./mainWindow"; +// !!IMPORTANT!! ./appBadge import must occur after ./mainWindow +import { setBadgeCount } from "./appBadge"; import { Settings } from "./settings"; import { handle, handleSync } from "./utils/ipcWrappers"; import { isValidVencordInstall } from "./utils/vencordLoader"; @@ -64,6 +65,8 @@ handle(IpcEvents.SHOW_ITEM_IN_FOLDER, (_, path) => { }); handle(IpcEvents.FOCUS, () => { + const mainWin = globals.mainWin!; + if (process.platform === "win32") mainWin.minimize(); // Windows is weird mainWin.restore(); @@ -75,14 +78,14 @@ handle(IpcEvents.CLOSE, e => { }); handle(IpcEvents.MINIMIZE, e => { - mainWin.minimize(); + globals.mainWin!.minimize(); }); handle(IpcEvents.MAXIMIZE, e => { - if (mainWin.isMaximized()) { - mainWin.unmaximize(); + if (globals.mainWin!.isMaximized()) { + globals.mainWin!.unmaximize(); } else { - mainWin.maximize(); + globals.mainWin!.maximize(); } }); @@ -103,7 +106,7 @@ handle(IpcEvents.SPELLCHECK_ADD_TO_DICTIONARY, (e, word: string) => { }); handle(IpcEvents.SELECT_VENCORD_DIR, async () => { - const res = await dialog.showOpenDialog(mainWin!, { + const res = await dialog.showOpenDialog(globals.mainWin!, { properties: ["openDirectory"] }); if (!res.filePaths.length) return "cancelled"; @@ -126,7 +129,7 @@ open(VENCORD_QUICKCSS_FILE, "a+").then(fd => { VENCORD_QUICKCSS_FILE, { persistent: false }, debounce(async () => { - mainWin?.webContents.postMessage("VencordQuickCssUpdate", await readCss()); + globals.mainWin?.webContents.postMessage("VencordQuickCssUpdate", await readCss()); }, 50) ); }); @@ -136,6 +139,6 @@ watch( VENCORD_THEMES_DIR, { persistent: false }, debounce(() => { - mainWin?.webContents.postMessage("VencordThemeUpdate", void 0); + globals.mainWin?.webContents.postMessage("VencordThemeUpdate", void 0); }) ); diff --git a/src/main/mainWindow.ts b/src/main/mainWindow.ts index 892fe93..fb44854 100644 --- a/src/main/mainWindow.ts +++ b/src/main/mainWindow.ts @@ -24,6 +24,7 @@ import type { SettingsStore } from "shared/utils/SettingsStore"; import { ICON_PATH, TRAY_ICON_PATH } from "../shared/paths"; import { createAboutWindow } from "./about"; import { initArRPC } from "./arrpc"; +import { Settings, VencordSettings } from "./settings"; import { DATA_DIR, DEFAULT_HEIGHT, @@ -34,7 +35,6 @@ import { UserAgent, VENCORD_FILES_DIR } from "./constants"; -import { Settings, VencordSettings } from "./settings"; import { createSplashWindow } from "./splash"; import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally"; import { applyDeckKeyboardFix, askToApplySteamLayout, isDeckGameMode } from "./utils/steamOS"; @@ -48,12 +48,12 @@ app.on("before-quit", () => { isQuitting = true; }); -type VencordBrowserWindow = BrowserWindow & { - _vencord_tray?: Tray; +// Fixes circular dependency issues with export const +export const globals = { + tray: null, + mainWin: null }; -export let mainWin: VencordBrowserWindow; - function makeSettingsListenerHelpers(o: SettingsStore) { const listeners = new Map<(data: any) => void, PropertyKey>(); @@ -75,7 +75,7 @@ function makeSettingsListenerHelpers(o: SettingsStore) { const [addSettingsListener, removeSettingsListeners] = makeSettingsListenerHelpers(Settings); const [addVencordSettingsListener, removeVencordSettingsListeners] = makeSettingsListenerHelpers(VencordSettings); -function initTray(win: VencordBrowserWindow) { +function initTray(win: BrowserWindow) { const trayMenu = Menu.buildFromTemplate([ { label: "Open", @@ -121,7 +121,7 @@ function initTray(win: VencordBrowserWindow) { } ]); - const tray = new Tray(TRAY_ICON_PATH); + const tray = (globals.tray = new Tray(TRAY_ICON_PATH)); tray.setToolTip("Vesktop"); tray.setContextMenu(trayMenu); tray.on("click", () => win.show()); @@ -204,7 +204,7 @@ function initMenuBar(win: BrowserWindow) { label: "Settings", accelerator: "CmdOrCtrl+,", async click() { - mainWin.webContents.executeJavaScript( + globals.mainWin!.webContents.executeJavaScript( "Vencord.Webpack.Common.SettingsRouter.open('My Account')" ); } @@ -332,10 +332,14 @@ function initWindowBoundsListeners(win: BrowserWindow) { win.on("move", saveBounds); } -function initSettingsListeners(win: VencordBrowserWindow) { +function initSettingsListeners(win: BrowserWindow) { addSettingsListener("tray", enable => { - if (enable) initTray(win); - else win._vencord_tray?.destroy(); + if (enable) { + initTray(win); + } else if (globals.tray) { + globals.tray.destroy(); + globals.tray = null; + } }); addSettingsListener("disableMinSize", disable => { if (disable) { @@ -373,7 +377,7 @@ function initSpellCheck(win: BrowserWindow) { }); } -function createMainWindow(): VencordBrowserWindow { +function createMainWindow(): BrowserWindow { // Clear up previous settings listeners removeSettingsListeners(); removeVencordSettingsListeners(); @@ -384,7 +388,7 @@ function createMainWindow(): VencordBrowserWindow { const noFrame = frameless === true || (process.platform === "win32" && discordWindowsTitleBar === true); - const win = (mainWin = new BrowserWindow({ + const win = (globals.mainWin = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: false, @@ -452,7 +456,7 @@ export async function createWindows() { await ensureVencordFiles(); runVencordMain(); - mainWin = createMainWindow(); + const mainWin = (globals.mainWin = createMainWindow()); mainWin.webContents.on("did-finish-load", () => { splash.destroy(); From f13961da6790cac690a054270a94f7abdebad93c Mon Sep 17 00:00:00 2001 From: Albert Zhang Date: Wed, 29 Nov 2023 23:48:51 -0500 Subject: [PATCH 04/12] Modify .gitignore files so static/dist/tray_icons is kept --- .gitignore | 6 ++++-- static/dist/.gitignore | 2 +- static/dist/tray_icons/.gitignore | 2 ++ 3 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 static/dist/tray_icons/.gitignore diff --git a/.gitignore b/.gitignore index be8fd61..8a92a9a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ -dist +# Allow static/dist to contain empty folders +./dist + node_modules .env .DS_Store .idea/ -.pnpm-store/ \ No newline at end of file +.pnpm-store/ diff --git a/static/dist/.gitignore b/static/dist/.gitignore index c9b4c1e..51c9238 100644 --- a/static/dist/.gitignore +++ b/static/dist/.gitignore @@ -1,3 +1,3 @@ * !.gitignore -!tray_icons/.gitkeep +!tray_icons/ diff --git a/static/dist/tray_icons/.gitignore b/static/dist/tray_icons/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/static/dist/tray_icons/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore From 3ad9d6e4a27ff8e502514dba750c499e7936901f Mon Sep 17 00:00:00 2001 From: Albert Zhang Date: Thu, 30 Nov 2023 00:36:40 -0500 Subject: [PATCH 05/12] Properly ignore top level /dist --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8a92a9a..d497d19 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ # Allow static/dist to contain empty folders -./dist +/dist/ node_modules .env From a63a3e710fc37b235c468e93f9a12c6349cb6c5f Mon Sep 17 00:00:00 2001 From: Albert Zhang Date: Thu, 30 Nov 2023 00:39:06 -0500 Subject: [PATCH 06/12] Add native param in setBadgeCount() and fix settings menu --- src/main/appBadge.ts | 4 +- src/main/ipc.ts | 4 +- src/preload/VesktopNative.ts | 3 +- src/renderer/appBadge.ts | 2 +- src/renderer/components/Settings.tsx | 64 ++++++++++++++++++---------- 5 files changed, 50 insertions(+), 27 deletions(-) diff --git a/src/main/appBadge.ts b/src/main/appBadge.ts index c462c1e..9ba6b67 100644 --- a/src/main/appBadge.ts +++ b/src/main/appBadge.ts @@ -31,13 +31,15 @@ function loadTrayIcon(index: number) { let lastIndex: null | number = -1; -export function setBadgeCount(count: number, tray: boolean = false) { +export function setBadgeCount(count: number, native: boolean = true, tray: boolean = false) { const [index, description] = getBadgeIndexAndDescription(count); if (tray) { globals.tray?.setImage(loadTrayIcon(index ?? 0)); } + if (!native) return; + switch (process.platform) { case "linux": if (count === -1) count = 0; diff --git a/src/main/ipc.ts b/src/main/ipc.ts index 9e77c85..192fe25 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -117,7 +117,9 @@ handle(IpcEvents.SELECT_VENCORD_DIR, async () => { return dir; }); -handle(IpcEvents.SET_BADGE_COUNT, (_, count: number, tray: boolean) => setBadgeCount(count, tray)); +handle(IpcEvents.SET_BADGE_COUNT, (_, count: number, native: boolean, tray: boolean) => + setBadgeCount(count, native, tray) +); function readCss() { return readFile(VENCORD_QUICKCSS_FILE, "utf-8").catch(() => ""); diff --git a/src/preload/VesktopNative.ts b/src/preload/VesktopNative.ts index c5569b8..431d52e 100644 --- a/src/preload/VesktopNative.ts +++ b/src/preload/VesktopNative.ts @@ -23,7 +23,8 @@ export const VesktopNative = { app: { relaunch: () => invoke(IpcEvents.RELAUNCH), getVersion: () => sendSync(IpcEvents.GET_VERSION), - setBadgeCount: (count: number, tray: boolean = false) => invoke(IpcEvents.SET_BADGE_COUNT, count, tray), + setBadgeCount: (count: number, native: boolean = true, tray: boolean = false) => + invoke(IpcEvents.SET_BADGE_COUNT, count, native, tray), supportsWindowsTransparency: () => sendSync(IpcEvents.SUPPORTS_WINDOWS_TRANSPARENCY) }, autostart: { diff --git a/src/renderer/appBadge.ts b/src/renderer/appBadge.ts index be2cbb3..8eec4cc 100644 --- a/src/renderer/appBadge.ts +++ b/src/renderer/appBadge.ts @@ -23,7 +23,7 @@ export function setBadge() { if (!totalCount && hasUnread && !disableUnreadBadge) totalCount = -1; if (Settings.store.appBadge || Settings.store.trayBadge) - VesktopNative.app.setBadgeCount(totalCount, Settings.store.trayBadge); + VesktopNative.app.setBadgeCount(totalCount, Settings.store.appBadge, Settings.store.trayBadge); } catch (e) { console.error(e); } diff --git a/src/renderer/components/Settings.tsx b/src/renderer/components/Settings.tsx index b020768..b3cc18f 100644 --- a/src/renderer/components/Settings.tsx +++ b/src/renderer/components/Settings.tsx @@ -28,26 +28,6 @@ export default function SettingsUi() { "Discord Titlebar", "Use Discord's custom title bar instead of the Windows one. Requires a full restart." ], - !isMac && ["tray", "Tray Icon", "Add a tray icon for Vesktop", true], - !isMac && [ - "minimizeToTray", - "Minimize to tray", - "Hitting X will make Vesktop minimize to the tray instead of closing", - true, - () => Settings.tray ?? true - ], - !isMac && [ - "trayBadge", - "Tray Notification Badge", - "Show mention badge on the tray icon", - false, - () => Settings.tray ?? true, - v => { - Settings.trayBadge = v; - if (v) setBadge(); - else VesktopNative.app.setBadgeCount(0, true); - } - ], ["arRPC", "Rich Presence", "Enables Rich Presence via arRPC", false], [ "disableMinSize", @@ -107,18 +87,56 @@ export default function SettingsUi() { onChange={v => { Settings.appBadge = v; if (v) setBadge(); - else VesktopNative.app.setBadgeCount(0, Settings.trayBadge); + else VesktopNative.app.setBadgeCount(0, true, false); }} note="Show mention badge on the app (taskbar/panel) icon" > Notification Badge - {switches.map(([key, text, note, def, predicate, onChange]) => ( + {!isMac && ( + <> + { + Settings.tray = v; + if (v && Settings.trayBadge) setBadge(); + }} + note="Add a tray icon for Vesktop" + key="tray" + > + Tray Icon + + (Settings.minimizeToTray = v)} + disabled={!(Settings.tray ?? true)} + note="Hitting X will make Vesktop minimize to the tray instead of closing" + key="minimizeToTray" + > + Minimize to Tray + + { + Settings.trayBadge = v; + if (v) setBadge(); + else VesktopNative.app.setBadgeCount(0, false, true); + }} + disabled={!(Settings.tray ?? true)} + note="Show mention badge on the tray icon" + key="trayBadge" + > + Tray Notification Badge + + + )} + + {switches.map(([key, text, note, def, predicate]) => ( (Settings[key as any] = v))} + onChange={v => (Settings[key as any] = v)} note={note} key={key} > From a2eff72147dfdaa922ad0ec1ec72b5c4d595b6ee Mon Sep 17 00:00:00 2001 From: Albert Zhang Date: Mon, 4 Dec 2023 07:10:16 -0500 Subject: [PATCH 07/12] Update outdated comment on mainWindow.globals --- src/main/mainWindow.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/mainWindow.ts b/src/main/mainWindow.ts index fb44854..5fb3ae7 100644 --- a/src/main/mainWindow.ts +++ b/src/main/mainWindow.ts @@ -48,7 +48,8 @@ app.on("before-quit", () => { isQuitting = true; }); -// Fixes circular dependency issues with export const +// Export a container object of objects that are used by other modules +// but won't be initialized at import time. export const globals = { tray: null, mainWin: null From 5d774ad325a995883a5af1703eecc52e15a72b79 Mon Sep 17 00:00:00 2001 From: Albert Zhang Date: Mon, 4 Dec 2023 07:10:47 -0500 Subject: [PATCH 08/12] Combine repeated accesses to globals.mainWin! --- src/main/ipc.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/ipc.ts b/src/main/ipc.ts index 192fe25..8c94343 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -82,10 +82,11 @@ handle(IpcEvents.MINIMIZE, e => { }); handle(IpcEvents.MAXIMIZE, e => { - if (globals.mainWin!.isMaximized()) { - globals.mainWin!.unmaximize(); + const mainWin = globals.mainWin!; + if (mainWin.isMaximized()) { + mainWin.unmaximize(); } else { - globals.mainWin!.maximize(); + mainWin.maximize(); } }); From 1b9337ef67c875a483b4f73ec9e7198ffebeb4f5 Mon Sep 17 00:00:00 2001 From: Albert Zhang Date: Wed, 27 Dec 2023 20:33:10 -0500 Subject: [PATCH 09/12] Changes in response to review comments and lint --- package.json | 1 - pnpm-lock.yaml | 3 --- scripts/build/build.mts | 18 ++++++++++------ scripts/build/composeTrayIcons.mts | 32 ++++++++++++++++++---------- src/main/appBadge.ts | 8 ++++--- src/main/index.ts | 6 +++--- src/main/ipc.ts | 10 ++++----- src/main/mainWindow.ts | 2 +- src/preload/VesktopNative.ts | 3 +-- src/renderer/appBadge.ts | 5 +++-- src/renderer/components/Settings.tsx | 4 ++-- 11 files changed, 53 insertions(+), 39 deletions(-) diff --git a/package.json b/package.json index 95e5364..5c81ab8 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,6 @@ "eslint-plugin-prettier": "^5.0.1", "eslint-plugin-simple-import-sort": "^10.0.0", "eslint-plugin-unused-imports": "^3.0.0", - "fast-glob": "3.3", "prettier": "^3.1.0", "sharp": "^0.33.0", "sharp-ico": "^0.1.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d1b72d0..f0ae87b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -69,9 +69,6 @@ devDependencies: eslint-plugin-unused-imports: specifier: ^3.0.0 version: 3.0.0(@typescript-eslint/eslint-plugin@6.13.1)(eslint@8.54.0) - fast-glob: - specifier: '3.3' - version: 3.3.1 prettier: specifier: ^3.1.0 version: 3.1.0 diff --git a/scripts/build/build.mts b/scripts/build/build.mts index a8504ab..d8be629 100644 --- a/scripts/build/build.mts +++ b/scripts/build/build.mts @@ -7,8 +7,8 @@ import { BuildContext, BuildOptions, context } from "esbuild"; import { copyFile } from "fs/promises"; -import vencordDep from "./vencordDep.mjs"; import { composeTrayIcons } from "./composeTrayIcons.mts"; +import vencordDep from "./vencordDep.mjs"; const isDev = process.argv.includes("--dev"); @@ -50,14 +50,20 @@ async function copyVenmic() { ]).catch(() => console.warn("Failed to copy venmic. Building without venmic support")); } -await Promise.all([ - copyVenmic(), - composeTrayIcons({ +async function composeTrayIconsIfSupported() { + if (process.platform === "darwin") return; + + await composeTrayIcons({ icon: "./static/icon.png", - badges: "./static/badges/*", + badgeDir: "./static/badges/", outDir: "./static/dist/tray_icons", createEmpty: true - }), + }); +} + +await Promise.all([ + copyVenmic(), + composeTrayIconsIfSupported(), createContext({ ...NodeCommonOpts, entryPoints: ["src/main/index.ts"], diff --git a/scripts/build/composeTrayIcons.mts b/scripts/build/composeTrayIcons.mts index 8e71e8a..021ae0e 100644 --- a/scripts/build/composeTrayIcons.mts +++ b/scripts/build/composeTrayIcons.mts @@ -1,7 +1,14 @@ -import sharp, { OutputInfo } from "sharp"; -import fastGlob from "fast-glob"; -import type { ImageData } from "sharp-ico"; -import { parse as pathParse, format as pathFormat } from "node:path"; +/* + * SPDX-License-Identifier: GPL-3.0 + * Vesktop, a desktop app aiming to give you a snappier Discord Experience + * Copyright (c) 2023 Vendicated and Vencord contributors + */ + +import { readdir, stat } from "node:fs/promises"; +import { format as pathFormat, join, parse as pathParse } from "node:path"; + +import sharp from "sharp"; +import { type ImageData, sharpsFromIco } from "sharp-ico"; interface BadgePosition { left?: number; @@ -30,7 +37,7 @@ const DEFAULT_BADGE_OPTIONS: Required = { export async function composeTrayIcons({ icon: iconPath, - badges: badgeGlob, + badgeDir, outDir, outExt = ".png", createEmpty = false, @@ -38,16 +45,19 @@ export async function composeTrayIcons({ badgeOptions = undefined }: { icon: string | Buffer | sharp.Sharp; - badges: string; + badgeDir: string; outDir: string; outExt?: string; createEmpty?: boolean; iconOptions?: ImageDim; badgeOptions?: BadgeOptions; }) { - const badges = await fastGlob.glob(badgeGlob); - if (!badges.length) { - throw new Error(`No badges matching glob '${badgeGlob}' found!`); + const badges: string[] = []; + for (const filename of await readdir(badgeDir)) { + const path = join(badgeDir, filename); + if (!(await stat(path)).isDirectory()) { + badges.push(path); + } } const badgeOptionsFilled = { ...DEFAULT_BADGE_OPTIONS, ...badgeOptions }; @@ -73,7 +83,7 @@ export async function composeTrayIcons({ ext: outExt, base: undefined }); - let out = composeTrayIcon(iconData, iconInfo, badgeData, badgeInfo, badgeOptionsFilled); + const out = composeTrayIcon(iconData, iconInfo, badgeData, badgeInfo, badgeOptionsFilled); const outputInfo = await out.toFile(savePath); return { iconInfo, @@ -115,7 +125,7 @@ async function loadFromImageOrIco( sizeOptions?: ImageDim & { resizeICO?: boolean } ): Promise { if (typeof path === "string" && path.endsWith(".ico")) { - const icos = (await import("sharp-ico")).sharpsFromIco(path, undefined, true) as unknown as ImageData[]; + const icos = sharpsFromIco(path, undefined, true) as unknown as ImageData[]; let icoInfo; if (sizeOptions == null) { icoInfo = icos[icos.length - 1]; diff --git a/src/main/appBadge.ts b/src/main/appBadge.ts index 9ba6b67..e744eed 100644 --- a/src/main/appBadge.ts +++ b/src/main/appBadge.ts @@ -7,7 +7,9 @@ import { app, NativeImage, nativeImage } from "electron"; import { join } from "path"; import { BADGE_DIR, TRAY_ICON_DIR, TRAY_ICON_PATH } from "shared/paths"; + import { globals } from "./mainWindow"; +import { Settings } from "./settings"; const imgCache = new Map(); @@ -31,14 +33,14 @@ function loadTrayIcon(index: number) { let lastIndex: null | number = -1; -export function setBadgeCount(count: number, native: boolean = true, tray: boolean = false) { +export function setBadgeCount(count: number) { const [index, description] = getBadgeIndexAndDescription(count); - if (tray) { + if (Settings.store.trayBadge) { globals.tray?.setImage(loadTrayIcon(index ?? 0)); } - if (!native) return; + if (!Settings.store.appBadge) return; switch (process.platform) { case "linux": diff --git a/src/main/index.ts b/src/main/index.ts index 1268361..4998361 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -9,12 +9,12 @@ import "./ipc"; import { app, BrowserWindow } from "electron"; import { checkUpdates } from "updater/main"; -import { Settings } from "./settings"; import { DATA_DIR } from "./constants"; import { createFirstLaunchTour } from "./firstLaunch"; import { createWindows, globals } from "./mainWindow"; import { registerMediaPermissionsHandler } from "./mediaPermissions"; import { registerScreenShareHandler } from "./screenShare"; +import { Settings } from "./settings"; if (IS_DEV) { require("source-map-support").install(); @@ -43,9 +43,9 @@ function init() { ); app.on("second-instance", (_event, _cmdLine, _cwd, data: any) => { - let mainWin; + const { mainWin } = globals; if (data.IS_DEV) app.quit(); - else if ((mainWin = globals.mainWin)) { + else if (mainWin) { if (mainWin.isMinimized()) mainWin.restore(); if (!mainWin.isVisible()) mainWin.show(); mainWin.focus(); diff --git a/src/main/ipc.ts b/src/main/ipc.ts index a9e0489..8b57b0e 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -15,11 +15,11 @@ import { join } from "path"; import { debounce } from "shared/utils/debounce"; import { IpcEvents } from "../shared/IpcEvents"; +// !!IMPORTANT!! ./appBadge import must occur after ./mainWindow +import { setBadgeCount } from "./appBadge"; import { autoStart } from "./autoStart"; import { VENCORD_FILES_DIR, VENCORD_QUICKCSS_FILE, VENCORD_THEMES_DIR } from "./constants"; import { globals } from "./mainWindow"; -// !!IMPORTANT!! ./appBadge import must occur after ./mainWindow -import { setBadgeCount } from "./appBadge"; import { Settings } from "./settings"; import { handle, handleSync } from "./utils/ipcWrappers"; import { isDeckGameMode, showGamePage } from "./utils/steamOS"; @@ -122,9 +122,9 @@ handle(IpcEvents.SELECT_VENCORD_DIR, async () => { return dir; }); -handle(IpcEvents.SET_BADGE_COUNT, (_, count: number, native: boolean, tray: boolean) => - setBadgeCount(count, native, tray) -); +handle(IpcEvents.SET_BADGE_COUNT, (_, count: number) => { + setBadgeCount(count); +}); function readCss() { return readFile(VENCORD_QUICKCSS_FILE, "utf-8").catch(() => ""); diff --git a/src/main/mainWindow.ts b/src/main/mainWindow.ts index 5fb3ae7..eeef003 100644 --- a/src/main/mainWindow.ts +++ b/src/main/mainWindow.ts @@ -24,7 +24,6 @@ import type { SettingsStore } from "shared/utils/SettingsStore"; import { ICON_PATH, TRAY_ICON_PATH } from "../shared/paths"; import { createAboutWindow } from "./about"; import { initArRPC } from "./arrpc"; -import { Settings, VencordSettings } from "./settings"; import { DATA_DIR, DEFAULT_HEIGHT, @@ -35,6 +34,7 @@ import { UserAgent, VENCORD_FILES_DIR } from "./constants"; +import { Settings, VencordSettings } from "./settings"; import { createSplashWindow } from "./splash"; import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally"; import { applyDeckKeyboardFix, askToApplySteamLayout, isDeckGameMode } from "./utils/steamOS"; diff --git a/src/preload/VesktopNative.ts b/src/preload/VesktopNative.ts index 431d52e..d72329d 100644 --- a/src/preload/VesktopNative.ts +++ b/src/preload/VesktopNative.ts @@ -23,8 +23,7 @@ export const VesktopNative = { app: { relaunch: () => invoke(IpcEvents.RELAUNCH), getVersion: () => sendSync(IpcEvents.GET_VERSION), - setBadgeCount: (count: number, native: boolean = true, tray: boolean = false) => - invoke(IpcEvents.SET_BADGE_COUNT, count, native, tray), + setBadgeCount: (count: number) => invoke(IpcEvents.SET_BADGE_COUNT, count), supportsWindowsTransparency: () => sendSync(IpcEvents.SUPPORTS_WINDOWS_TRANSPARENCY) }, autostart: { diff --git a/src/renderer/appBadge.ts b/src/renderer/appBadge.ts index 8eec4cc..0cc95b8 100644 --- a/src/renderer/appBadge.ts +++ b/src/renderer/appBadge.ts @@ -22,8 +22,9 @@ export function setBadge() { let totalCount = mentionCount + pendingRequests; if (!totalCount && hasUnread && !disableUnreadBadge) totalCount = -1; - if (Settings.store.appBadge || Settings.store.trayBadge) - VesktopNative.app.setBadgeCount(totalCount, Settings.store.appBadge, Settings.store.trayBadge); + if (Settings.store.appBadge || Settings.store.trayBadge) { + VesktopNative.app.setBadgeCount(totalCount); + } } catch (e) { console.error(e); } diff --git a/src/renderer/components/Settings.tsx b/src/renderer/components/Settings.tsx index b3cc18f..94cef94 100644 --- a/src/renderer/components/Settings.tsx +++ b/src/renderer/components/Settings.tsx @@ -87,7 +87,7 @@ export default function SettingsUi() { onChange={v => { Settings.appBadge = v; if (v) setBadge(); - else VesktopNative.app.setBadgeCount(0, true, false); + else VesktopNative.app.setBadgeCount(0); }} note="Show mention badge on the app (taskbar/panel) icon" > @@ -121,7 +121,7 @@ export default function SettingsUi() { onChange={v => { Settings.trayBadge = v; if (v) setBadge(); - else VesktopNative.app.setBadgeCount(0, false, true); + else VesktopNative.app.setBadgeCount(0); }} disabled={!(Settings.tray ?? true)} note="Show mention badge on the tray icon" From 7db8ecab188ff1be8f8ea642f8c5673a889e0b57 Mon Sep 17 00:00:00 2001 From: Albert Zhang Date: Wed, 27 Dec 2023 20:34:21 -0500 Subject: [PATCH 10/12] Remove comment from .gitignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index d497d19..cdaf499 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -# Allow static/dist to contain empty folders /dist/ node_modules From 2e155c32170b6f14891df4fdd629cbfbcc7ddbb3 Mon Sep 17 00:00:00 2001 From: Albert Zhang Date: Wed, 27 Dec 2023 20:43:31 -0500 Subject: [PATCH 11/12] Use require(...) instead of import ordering for appBadge --- scripts/build/build.mts | 2 +- src/main/ipc.ts | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/scripts/build/build.mts b/scripts/build/build.mts index d8be629..3099f2c 100644 --- a/scripts/build/build.mts +++ b/scripts/build/build.mts @@ -53,7 +53,7 @@ async function copyVenmic() { async function composeTrayIconsIfSupported() { if (process.platform === "darwin") return; - await composeTrayIcons({ + return composeTrayIcons({ icon: "./static/icon.png", badgeDir: "./static/badges/", outDir: "./static/dist/tray_icons", diff --git a/src/main/ipc.ts b/src/main/ipc.ts index 8b57b0e..6cade98 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -15,8 +15,6 @@ import { join } from "path"; import { debounce } from "shared/utils/debounce"; import { IpcEvents } from "../shared/IpcEvents"; -// !!IMPORTANT!! ./appBadge import must occur after ./mainWindow -import { setBadgeCount } from "./appBadge"; import { autoStart } from "./autoStart"; import { VENCORD_FILES_DIR, VENCORD_QUICKCSS_FILE, VENCORD_THEMES_DIR } from "./constants"; import { globals } from "./mainWindow"; @@ -123,7 +121,7 @@ handle(IpcEvents.SELECT_VENCORD_DIR, async () => { }); handle(IpcEvents.SET_BADGE_COUNT, (_, count: number) => { - setBadgeCount(count); + (require("./appBadge") as typeof import("./appBadge")).setBadgeCount(count); }); function readCss() { From 69e555b24040e7cde44ca62f33a007a9ed61b5ea Mon Sep 17 00:00:00 2001 From: Albert Zhang Date: Wed, 27 Dec 2023 21:11:54 -0500 Subject: [PATCH 12/12] Revert to export let; ignore autosorted import order --- src/main/appBadge.ts | 7 +++---- src/main/arrpc.ts | 7 +++---- src/main/index.ts | 3 +-- src/main/ipc.ts | 19 ++++++++----------- src/main/mainWindow.ts | 24 ++++++++++-------------- 5 files changed, 25 insertions(+), 35 deletions(-) diff --git a/src/main/appBadge.ts b/src/main/appBadge.ts index e744eed..ac2659a 100644 --- a/src/main/appBadge.ts +++ b/src/main/appBadge.ts @@ -7,8 +7,7 @@ import { app, NativeImage, nativeImage } from "electron"; import { join } from "path"; import { BADGE_DIR, TRAY_ICON_DIR, TRAY_ICON_PATH } from "shared/paths"; - -import { globals } from "./mainWindow"; +import { tray, mainWin } from "./mainWindow"; import { Settings } from "./settings"; const imgCache = new Map(); @@ -37,7 +36,7 @@ export function setBadgeCount(count: number) { const [index, description] = getBadgeIndexAndDescription(count); if (Settings.store.trayBadge) { - globals.tray?.setImage(loadTrayIcon(index ?? 0)); + tray?.setImage(loadTrayIcon(index ?? 0)); } if (!Settings.store.appBadge) return; @@ -59,7 +58,7 @@ export function setBadgeCount(count: number) { lastIndex = index; - globals.mainWin?.setOverlayIcon(index === null ? null : loadBadge(index), description); + mainWin.setOverlayIcon(index === null ? null : loadBadge(index), description); break; } } diff --git a/src/main/arrpc.ts b/src/main/arrpc.ts index dd435bb..1899d9c 100644 --- a/src/main/arrpc.ts +++ b/src/main/arrpc.ts @@ -7,7 +7,7 @@ import Server from "arrpc"; import { IpcEvents } from "shared/IpcEvents"; -import { globals } from "./mainWindow"; +import { mainWin } from "./mainWindow"; import { Settings } from "./settings"; let server: any; @@ -19,13 +19,12 @@ export async function initArRPC() { try { server = await new Server(); - const { mainWin } = globals; - server.on("activity", (data: any) => mainWin!.webContents.send(IpcEvents.ARRPC_ACTIVITY, JSON.stringify(data))); + server.on("activity", (data: any) => mainWin.webContents.send(IpcEvents.ARRPC_ACTIVITY, JSON.stringify(data))); server.on("invite", (invite: string, callback: (valid: boolean) => void) => { invite = String(invite); if (!inviteCodeRegex.test(invite)) return callback(false); - mainWin!.webContents + mainWin.webContents // Safety: Result of JSON.stringify should always be safe to equal // Also, just to be super super safe, invite is regex validated above .executeJavaScript(`Vesktop.openInviteModal(${JSON.stringify(invite)})`) diff --git a/src/main/index.ts b/src/main/index.ts index 4998361..832afc8 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -11,7 +11,7 @@ import { checkUpdates } from "updater/main"; import { DATA_DIR } from "./constants"; import { createFirstLaunchTour } from "./firstLaunch"; -import { createWindows, globals } from "./mainWindow"; +import { createWindows, mainWin } from "./mainWindow"; import { registerMediaPermissionsHandler } from "./mediaPermissions"; import { registerScreenShareHandler } from "./screenShare"; import { Settings } from "./settings"; @@ -43,7 +43,6 @@ function init() { ); app.on("second-instance", (_event, _cmdLine, _cwd, data: any) => { - const { mainWin } = globals; if (data.IS_DEV) app.quit(); else if (mainWin) { if (mainWin.isMinimized()) mainWin.restore(); diff --git a/src/main/ipc.ts b/src/main/ipc.ts index 6cade98..318b1e3 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -6,6 +6,7 @@ if (process.platform === "linux") import("./virtmic"); +// eslint-disable-next-line simple-import-sort/imports import { execFile } from "child_process"; import { app, BrowserWindow, dialog, RelaunchOptions, session, shell } from "electron"; import { mkdirSync, readFileSync, watch } from "fs"; @@ -17,8 +18,9 @@ import { debounce } from "shared/utils/debounce"; import { IpcEvents } from "../shared/IpcEvents"; import { autoStart } from "./autoStart"; import { VENCORD_FILES_DIR, VENCORD_QUICKCSS_FILE, VENCORD_THEMES_DIR } from "./constants"; -import { globals } from "./mainWindow"; +import { mainWin } from "./mainWindow"; import { Settings } from "./settings"; +import { setBadgeCount } from "./appBadge"; import { handle, handleSync } from "./utils/ipcWrappers"; import { isDeckGameMode, showGamePage } from "./utils/steamOS"; import { isValidVencordInstall } from "./utils/vencordLoader"; @@ -67,8 +69,6 @@ handle(IpcEvents.SHOW_ITEM_IN_FOLDER, (_, path) => { }); handle(IpcEvents.FOCUS, () => { - const mainWin = globals.mainWin!; - if (process.platform === "win32") mainWin.minimize(); // Windows is weird mainWin.restore(); @@ -80,11 +80,10 @@ handle(IpcEvents.CLOSE, e => { }); handle(IpcEvents.MINIMIZE, e => { - globals.mainWin!.minimize(); + mainWin.minimize(); }); handle(IpcEvents.MAXIMIZE, e => { - const mainWin = globals.mainWin!; if (mainWin.isMaximized()) { mainWin.unmaximize(); } else { @@ -109,7 +108,7 @@ handle(IpcEvents.SPELLCHECK_ADD_TO_DICTIONARY, (e, word: string) => { }); handle(IpcEvents.SELECT_VENCORD_DIR, async () => { - const res = await dialog.showOpenDialog(globals.mainWin!, { + const res = await dialog.showOpenDialog(mainWin!, { properties: ["openDirectory"] }); if (!res.filePaths.length) return "cancelled"; @@ -120,9 +119,7 @@ handle(IpcEvents.SELECT_VENCORD_DIR, async () => { return dir; }); -handle(IpcEvents.SET_BADGE_COUNT, (_, count: number) => { - (require("./appBadge") as typeof import("./appBadge")).setBadgeCount(count); -}); +handle(IpcEvents.SET_BADGE_COUNT, (_, count: number) => setBadgeCount(count)); function readCss() { return readFile(VENCORD_QUICKCSS_FILE, "utf-8").catch(() => ""); @@ -134,7 +131,7 @@ open(VENCORD_QUICKCSS_FILE, "a+").then(fd => { VENCORD_QUICKCSS_FILE, { persistent: false }, debounce(async () => { - globals.mainWin?.webContents.postMessage("VencordQuickCssUpdate", await readCss()); + mainWin?.webContents.postMessage("VencordQuickCssUpdate", await readCss()); }, 50) ); }); @@ -144,6 +141,6 @@ watch( VENCORD_THEMES_DIR, { persistent: false }, debounce(() => { - globals.mainWin?.webContents.postMessage("VencordThemeUpdate", void 0); + mainWin?.webContents.postMessage("VencordThemeUpdate", void 0); }) ); diff --git a/src/main/mainWindow.ts b/src/main/mainWindow.ts index eeef003..3651ae8 100644 --- a/src/main/mainWindow.ts +++ b/src/main/mainWindow.ts @@ -48,12 +48,8 @@ app.on("before-quit", () => { isQuitting = true; }); -// Export a container object of objects that are used by other modules -// but won't be initialized at import time. -export const globals = { - tray: null, - mainWin: null -}; +export let mainWin: BrowserWindow; +export let tray: Tray | null = null; function makeSettingsListenerHelpers(o: SettingsStore) { const listeners = new Map<(data: any) => void, PropertyKey>(); @@ -122,7 +118,7 @@ function initTray(win: BrowserWindow) { } ]); - const tray = (globals.tray = new Tray(TRAY_ICON_PATH)); + tray = new Tray(TRAY_ICON_PATH); tray.setToolTip("Vesktop"); tray.setContextMenu(trayMenu); tray.on("click", () => win.show()); @@ -205,7 +201,7 @@ function initMenuBar(win: BrowserWindow) { label: "Settings", accelerator: "CmdOrCtrl+,", async click() { - globals.mainWin!.webContents.executeJavaScript( + mainWin.webContents.executeJavaScript( "Vencord.Webpack.Common.SettingsRouter.open('My Account')" ); } @@ -337,9 +333,9 @@ function initSettingsListeners(win: BrowserWindow) { addSettingsListener("tray", enable => { if (enable) { initTray(win); - } else if (globals.tray) { - globals.tray.destroy(); - globals.tray = null; + } else if (tray) { + tray.destroy(); + tray = null; } }); addSettingsListener("disableMinSize", disable => { @@ -378,7 +374,7 @@ function initSpellCheck(win: BrowserWindow) { }); } -function createMainWindow(): BrowserWindow { +function createMainWindow() { // Clear up previous settings listeners removeSettingsListeners(); removeVencordSettingsListeners(); @@ -389,7 +385,7 @@ function createMainWindow(): BrowserWindow { const noFrame = frameless === true || (process.platform === "win32" && discordWindowsTitleBar === true); - const win = (globals.mainWin = new BrowserWindow({ + const win = (mainWin = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: false, @@ -457,7 +453,7 @@ export async function createWindows() { await ensureVencordFiles(); runVencordMain(); - const mainWin = (globals.mainWin = createMainWindow()); + mainWin = createMainWindow(); mainWin.webContents.on("did-finish-load", () => { splash.destroy();