├── .gitattributes ├── .github └── FUNDING.yml ├── .gitignore ├── Docs ├── HeaderDark.png ├── HeaderDarkMobile.png ├── HeaderDarkOptimized.webp ├── HeaderDarkUWP.png ├── HeaderLight.png ├── HeaderLightMobile.png ├── Marketing2.webp ├── NotificationDark.png ├── NotificationLight.png ├── RenderDark.blend ├── RenderLight.blend ├── Screenshot1.pdn ├── Screenshot1.png ├── Screenshot2.pdn ├── Screenshot2.png ├── Screenshot3.pdn └── Screenshot3.png ├── LICENSE ├── README.md ├── TopNotify.Native ├── TopNotify.Native.vcxproj ├── TopNotify.Native.vcxproj.filters ├── WindowFinder.cpp ├── dllmain.cpp └── packages.config └── TopNotify ├── Common ├── AppDiscovery.cs ├── AppReference.cs ├── Logging.cs ├── MonitorData.cs ├── NotificationTester.cs ├── ProcessCommandLine.cs ├── Settings.cs └── Util.cs ├── Daemon ├── Daemon.cs ├── DaemonError.cs ├── DaemonErrorHandler.cs ├── ExtendedStyleManager.cs ├── Interceptor.cs ├── InterceptorManager.cs ├── Interceptors │ ├── DiscoveryInterceptor.cs │ ├── NativeInterceptor.cs │ ├── ReadAloudInterceptor.cs │ └── SoundInterceptor.cs ├── Language.cs └── ResolutionFinder.cs ├── Finish Build ARM64.bat ├── Finish Build Beta.bat ├── Finish Build.bat ├── GUI ├── DragMode.cs ├── MainCommands.cs ├── SoundFinder.cs ├── TrayIcon.cs ├── WallpaperFinder.cs └── WindowExtensions.cs ├── Install MSIX For Testing.bat ├── MSIX ARM64 ├── AppxManifest.xml ├── AppxMetadata │ └── CodeIntegrity.cat ├── Assets │ ├── Logo150.png │ ├── Logo44.png │ ├── StoreLogo.png │ ├── StoreLogo1080.png │ └── StoreLogo300.png └── [Content_Types].xml ├── MSIX Beta ├── AppxManifest.xml ├── AppxMetadata │ └── CodeIntegrity.cat ├── Assets │ ├── Logo150.png │ ├── Logo44.png │ └── StoreLogo.png └── [Content_Types].xml ├── MSIX ├── AppxManifest.xml ├── AppxMetadata │ └── CodeIntegrity.cat ├── Assets │ ├── Logo150.png │ ├── Logo44.png │ ├── StoreLogo.png │ ├── StoreLogo1080.png │ └── StoreLogo300.png └── [Content_Types].xml ├── Program.cs ├── Properties └── launchSettings.json ├── TopNotify.csproj ├── TopNotify.sln ├── iv2runtime ├── TopNotify.igniteview ├── win-arm64 │ └── native │ │ ├── TopNotify.Native.dll │ │ ├── TopNotify.Native.exp │ │ └── TopNotify.Native.lib └── win-x64 │ └── native │ ├── TopNotify.Native.dll │ ├── TopNotify.Native.exp │ └── TopNotify.Native.lib └── src-vite ├── .eslintrc.cjs ├── .gitignore ├── index.html ├── package-lock.json ├── package.json ├── public ├── Audio │ ├── internal │ │ └── silent.wav │ └── windows │ │ ├── win10.wav │ │ ├── win10alt.wav │ │ ├── win11.wav │ │ ├── win7.wav │ │ ├── winxp.wav │ │ └── winxp_error.wav ├── Font │ ├── InterVariable.woff2 │ └── Tabler.ttf ├── Image │ ├── BackgroundDark.png │ ├── BackgroundDecoration.jpg │ ├── BackgroundLight.png │ ├── Blank.png │ ├── DefaultAppReferenceIcon.svg │ ├── Icon.ico │ ├── Icon.png │ ├── IconSmall.png │ ├── IconTiny.png │ ├── LeftClick.png │ ├── NoSound.svg │ ├── NotificationPreview.png │ ├── PartyWordmarkIconMono.png │ ├── Sound.svg │ ├── Taskbar.png │ └── ThirdParty │ │ ├── Discord.svg │ │ ├── Outlook.svg │ │ ├── WhatsApp.svg │ │ ├── Windows10.svg │ │ ├── Windows11.svg │ │ ├── Windows7.svg │ │ └── WindowsXP.png ├── Meta │ ├── AppxManifest.xml │ ├── NotificationNames.csv │ ├── SoundPacks.json │ ├── WinGet │ │ ├── SamsidParty.TopNotifyWG.installer.yaml │ │ ├── SamsidParty.TopNotifyWG.locale.en-US.yaml │ │ └── SamsidParty.TopNotifyWG.yaml │ └── manifest.xml └── drag.html ├── src ├── About.jsx ├── App.jsx ├── CSS │ ├── About.css │ ├── App.css │ ├── ChakraOverrides.css │ ├── NotificationSounds.css │ └── Preview.css ├── ClickThrough.jsx ├── DebugMenu.jsx ├── Helper.jsx ├── MonitorSelect.jsx ├── NotificationSounds.jsx ├── Preview.jsx ├── ReadAloud.jsx ├── SoundInterceptionToggle.jsx ├── TestNotification.jsx ├── Transparency.jsx └── main.jsx └── vite.config.js /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js linguist-vendored 2 | PrimeReact.css linguist-generated 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: SamsidParty 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | BUILD/ 2 | PACK/ 3 | target/ 4 | 5 | # User-specific files 6 | *.rsuser 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Mono auto generated files 16 | mono_crash.* 17 | 18 | # Build results 19 | [Dd]ebug/ 20 | [Dd]ebugPublic/ 21 | [Rr]elease/ 22 | [Rr]eleases/ 23 | x64/ 24 | x86/ 25 | [Ww][Ii][Nn]32/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # ASP.NET Scaffolding 65 | ScaffoldingReadMe.txt 66 | 67 | # StyleCop 68 | StyleCopReport.xml 69 | 70 | # Files built by Visual Studio 71 | *_i.c 72 | *_p.c 73 | *_h.h 74 | *.ilk 75 | *.meta 76 | *.obj 77 | *.iobj 78 | *.pch 79 | *.pdb 80 | *.ipdb 81 | *.pgc 82 | *.pgd 83 | *.rsp 84 | *.sbr 85 | *.tlb 86 | *.tli 87 | *.tlh 88 | *.tmp 89 | *.tmp_proj 90 | *_wpftmp.csproj 91 | *.log 92 | *.tlog 93 | *.vspscc 94 | *.vssscc 95 | .builds 96 | *.pidb 97 | *.svclog 98 | *.scc 99 | 100 | # Chutzpah Test files 101 | _Chutzpah* 102 | 103 | # Visual C++ cache files 104 | ipch/ 105 | *.aps 106 | *.ncb 107 | *.opendb 108 | *.opensdf 109 | *.sdf 110 | *.cachefile 111 | *.VC.db 112 | *.VC.VC.opendb 113 | 114 | # Visual Studio profiler 115 | *.psess 116 | *.vsp 117 | *.vspx 118 | *.sap 119 | 120 | # Visual Studio Trace Files 121 | *.e2e 122 | 123 | # TFS 2012 Local Workspace 124 | $tf/ 125 | 126 | # Guidance Automation Toolkit 127 | *.gpState 128 | 129 | # ReSharper is a .NET coding add-in 130 | _ReSharper*/ 131 | *.[Rr]e[Ss]harper 132 | *.DotSettings.user 133 | 134 | # TeamCity is a build add-in 135 | _TeamCity* 136 | 137 | # DotCover is a Code Coverage Tool 138 | *.dotCover 139 | 140 | # AxoCover is a Code Coverage Tool 141 | .axoCover/* 142 | !.axoCover/settings.json 143 | 144 | # Coverlet is a free, cross platform Code Coverage Tool 145 | coverage*.json 146 | coverage*.xml 147 | coverage*.info 148 | 149 | # Visual Studio code coverage results 150 | *.coverage 151 | *.coveragexml 152 | 153 | # NCrunch 154 | _NCrunch_* 155 | .*crunch*.local.xml 156 | nCrunchTemp_* 157 | 158 | # MightyMoose 159 | *.mm.* 160 | AutoTest.Net/ 161 | 162 | # Web workbench (sass) 163 | .sass-cache/ 164 | 165 | # Installshield output folder 166 | [Ee]xpress/ 167 | 168 | # DocProject is a documentation generator add-in 169 | DocProject/buildhelp/ 170 | DocProject/Help/*.HxT 171 | DocProject/Help/*.HxC 172 | DocProject/Help/*.hhc 173 | DocProject/Help/*.hhk 174 | DocProject/Help/*.hhp 175 | DocProject/Help/Html2 176 | DocProject/Help/html 177 | 178 | # Click-Once directory 179 | publish/ 180 | 181 | # Publish Web Output 182 | *.[Pp]ublish.xml 183 | *.azurePubxml 184 | # Note: Comment the next line if you want to checkin your web deploy settings, 185 | # but database connection strings (with potential passwords) will be unencrypted 186 | *.pubxml 187 | *.publishproj 188 | 189 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 190 | # checkin your Azure Web App publish settings, but sensitive information contained 191 | # in these scripts will be unencrypted 192 | PublishScripts/ 193 | 194 | # NuGet Packages 195 | *.nupkg 196 | # NuGet Symbol Packages 197 | *.snupkg 198 | # The packages folder can be ignored because of Package Restore 199 | **/[Pp]ackages/* 200 | # except build/, which is used as an MSBuild target. 201 | !**/[Pp]ackages/build/ 202 | # Uncomment if necessary however generally it will be regenerated when needed 203 | #!**/[Pp]ackages/repositories.config 204 | # NuGet v3's project.json files produces more ignorable files 205 | *.nuget.props 206 | *.nuget.targets 207 | 208 | # Microsoft Azure Build Output 209 | csx/ 210 | *.build.csdef 211 | 212 | # Microsoft Azure Emulator 213 | ecf/ 214 | rcf/ 215 | 216 | # Windows Store app package directories and files 217 | AppPackages/ 218 | BundleArtifacts/ 219 | Package.StoreAssociation.xml 220 | _pkginfo.txt 221 | *.appx 222 | *.appxbundle 223 | *.appxupload 224 | 225 | # Visual Studio cache files 226 | # files ending in .cache can be ignored 227 | *.[Cc]ache 228 | # but keep track of directories ending in .cache 229 | !?*.[Cc]ache/ 230 | 231 | # Others 232 | ClientBin/ 233 | ~$* 234 | *~ 235 | *.dbmdl 236 | *.dbproj.schemaview 237 | *.jfm 238 | *.pfx 239 | *.publishsettings 240 | orleans.codegen.cs 241 | 242 | # Including strong name files can present a security risk 243 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 244 | #*.snk 245 | 246 | # Since there are multiple workflows, uncomment next line to ignore bower_components 247 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 248 | #bower_components/ 249 | 250 | # RIA/Silverlight projects 251 | Generated_Code/ 252 | 253 | # Backup & report files from converting an old project file 254 | # to a newer Visual Studio version. Backup files are not needed, 255 | # because we have git ;-) 256 | _UpgradeReport_Files/ 257 | Backup*/ 258 | UpgradeLog*.XML 259 | UpgradeLog*.htm 260 | ServiceFabricBackup/ 261 | *.rptproj.bak 262 | 263 | # SQL Server files 264 | *.mdf 265 | *.ldf 266 | *.ndf 267 | 268 | # Business Intelligence projects 269 | *.rdl.data 270 | *.bim.layout 271 | *.bim_*.settings 272 | *.rptproj.rsuser 273 | *- [Bb]ackup.rdl 274 | *- [Bb]ackup ([0-9]).rdl 275 | *- [Bb]ackup ([0-9][0-9]).rdl 276 | 277 | # Microsoft Fakes 278 | FakesAssemblies/ 279 | 280 | # GhostDoc plugin setting file 281 | *.GhostDoc.xml 282 | 283 | # Node.js Tools for Visual Studio 284 | .ntvs_analysis.dat 285 | node_modules/ 286 | 287 | # Visual Studio 6 build log 288 | *.plg 289 | 290 | # Visual Studio 6 workspace options file 291 | *.opt 292 | 293 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 294 | *.vbw 295 | 296 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 297 | *.vbp 298 | 299 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 300 | *.dsw 301 | *.dsp 302 | 303 | # Visual Studio 6 technical files 304 | *.ncb 305 | *.aps 306 | 307 | # Visual Studio LightSwitch build output 308 | **/*.HTMLClient/GeneratedArtifacts 309 | **/*.DesktopClient/GeneratedArtifacts 310 | **/*.DesktopClient/ModelManifest.xml 311 | **/*.Server/GeneratedArtifacts 312 | **/*.Server/ModelManifest.xml 313 | _Pvt_Extensions 314 | 315 | # Paket dependency manager 316 | .paket/paket.exe 317 | paket-files/ 318 | 319 | # FAKE - F# Make 320 | .fake/ 321 | 322 | # CodeRush personal settings 323 | .cr/personal 324 | 325 | # Python Tools for Visual Studio (PTVS) 326 | __pycache__/ 327 | *.pyc 328 | 329 | # Cake - Uncomment if you are using it 330 | # tools/** 331 | # !tools/packages.config 332 | 333 | # Tabs Studio 334 | *.tss 335 | 336 | # Telerik's JustMock configuration file 337 | *.jmconfig 338 | 339 | # BizTalk build output 340 | *.btp.cs 341 | *.btm.cs 342 | *.odx.cs 343 | *.xsd.cs 344 | 345 | # OpenCover UI analysis results 346 | OpenCover/ 347 | 348 | # Azure Stream Analytics local run output 349 | ASALocalRun/ 350 | 351 | # MSBuild Binary and Structured Log 352 | *.binlog 353 | 354 | # NVidia Nsight GPU debugger configuration file 355 | *.nvuser 356 | 357 | # MFractors (Xamarin productivity tool) working folder 358 | .mfractor/ 359 | 360 | # Local History for Visual Studio 361 | .localhistory/ 362 | 363 | # Visual Studio History (VSHistory) files 364 | .vshistory/ 365 | 366 | # BeatPulse healthcheck temp database 367 | healthchecksdb 368 | 369 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 370 | MigrationBackup/ 371 | 372 | # Ionide (cross platform F# VS Code tools) working folder 373 | .ionide/ 374 | 375 | # Fody - auto-generated XML schema 376 | FodyWeavers.xsd 377 | 378 | # VS Code files for those working on multiple tools 379 | .vscode/* 380 | !.vscode/settings.json 381 | !.vscode/tasks.json 382 | !.vscode/launch.json 383 | !.vscode/extensions.json 384 | *.code-workspace 385 | 386 | # Local History for Visual Studio Code 387 | .history/ 388 | 389 | # Windows Installer files from build outputs 390 | *.cab 391 | *.msi 392 | *.msix 393 | *.msm 394 | *.msp 395 | 396 | # JetBrains Rider 397 | *.sln.iml 398 | TopNotify/.DS_Store 399 | -------------------------------------------------------------------------------- /Docs/HeaderDark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/Docs/HeaderDark.png -------------------------------------------------------------------------------- /Docs/HeaderDarkMobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/Docs/HeaderDarkMobile.png -------------------------------------------------------------------------------- /Docs/HeaderDarkOptimized.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/Docs/HeaderDarkOptimized.webp -------------------------------------------------------------------------------- /Docs/HeaderDarkUWP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/Docs/HeaderDarkUWP.png -------------------------------------------------------------------------------- /Docs/HeaderLight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/Docs/HeaderLight.png -------------------------------------------------------------------------------- /Docs/HeaderLightMobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/Docs/HeaderLightMobile.png -------------------------------------------------------------------------------- /Docs/Marketing2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/Docs/Marketing2.webp -------------------------------------------------------------------------------- /Docs/NotificationDark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/Docs/NotificationDark.png -------------------------------------------------------------------------------- /Docs/NotificationLight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/Docs/NotificationLight.png -------------------------------------------------------------------------------- /Docs/RenderDark.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/Docs/RenderDark.blend -------------------------------------------------------------------------------- /Docs/RenderLight.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/Docs/RenderLight.blend -------------------------------------------------------------------------------- /Docs/Screenshot1.pdn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/Docs/Screenshot1.pdn -------------------------------------------------------------------------------- /Docs/Screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/Docs/Screenshot1.png -------------------------------------------------------------------------------- /Docs/Screenshot2.pdn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/Docs/Screenshot2.pdn -------------------------------------------------------------------------------- /Docs/Screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/Docs/Screenshot2.png -------------------------------------------------------------------------------- /Docs/Screenshot3.pdn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/Docs/Screenshot3.pdn -------------------------------------------------------------------------------- /Docs/Screenshot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/Docs/Screenshot3.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 | 6 | TopNotify Logo 7 | 8 | 9 |

TopNotify

10 |

11 | The ultimate notification customization tool for Windows 12 |
13 |
14 |

15 |
16 | 17 | ![Download Count](https://img.shields.io/github/downloads/SamsidParty/TopNotify/total.svg?style=for-the-badge) 18 | ![Stars Count](https://img.shields.io/github/stars/SamsidParty/TopNotify.svg?style=for-the-badge) 19 | ![Code Size](https://img.shields.io/github/languages/code-size/SamsidParty/TopNotify?style=for-the-badge) 20 | ![Repo Size](https://img.shields.io/github/repo-size/SamsidParty/TopNotify?style=for-the-badge) 21 | ![Get It From Microsoft](https://get.microsoft.com/images/en-us%20dark.svg) 22 | 23 |
24 |
25 | 26 | # Features 🔥 27 | 28 | - Move notification popups anywhere on your screen 29 | - Make notifications show on another monitor 30 | - Change transparency of notifications 31 | - Click-Through notifications 32 | - Customize notification sounds for each app 33 | - Efficient performance with minimal CPU/RAM usage 34 | - Native ARM64 Support 35 | 36 | ![TopNotify Header](/Docs/Screenshot3.png) 37 | 38 | ![TopNotify Screenshot](/Docs/Screenshot2.png) 39 | 40 | ![TopNotify Screenshot](/Docs/Screenshot1.png) 41 | 42 | # Supported Windows Versions 🪟 43 | 44 | - Windows 11 23H2+ 45 | - Windows 10 23H2+ (Requires WebView2 Runtime) 46 | 47 | Earlier versions of Windows 10 may work, but are not officially supported. 48 | 49 | # Download 📦 50 | 51 | Download the latest MSIX/EXE release from the [releases](https://github.com/SamsidParty/TopNotify/releases) page 52 | 53 | # Star History ⭐ 54 | 55 | 56 | 57 | 58 | 59 | Star History Chart 60 | 61 | 62 | -------------------------------------------------------------------------------- /TopNotify.Native/TopNotify.Native.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | Source Files 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /TopNotify.Native/WindowFinder.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define STATUS_BUFFER_TOO_SMALL 0xC0000023 4 | 5 | //This Code Is Based On https://github.com/valinet/WinOverview/blob/master/WinOverview/NtUserBuildHwndList.h#L21 6 | 7 | typedef NTSTATUS(WINAPI* NtUserBuildHwndList) 8 | ( 9 | HDESK in_hDesk, 10 | HWND in_hWndNext, 11 | BOOL in_EnumChildren, 12 | BOOL in_RemoveImmersive, 13 | DWORD in_ThreadID, 14 | UINT in_Max, 15 | HWND* out_List, 16 | UINT* out_Cnt 17 | ); 18 | 19 | // many thanks to: https://stackoverflow.com/questions/38205375/enumwindows-function-in-win10-enumerates-only-desktop-apps 20 | HWND* _Gui_BuildWindowList 21 | ( 22 | NtUserBuildHwndList pNtUserBuildHwndList, 23 | HDESK in_hDesk, 24 | HWND in_hWnd, 25 | BOOL in_EnumChildren, 26 | BOOL in_RemoveImmersive, 27 | UINT in_ThreadID, 28 | INT* out_Cnt 29 | ) 30 | { 31 | /* locals */ 32 | UINT lv_Max; 33 | UINT lv_Cnt; 34 | UINT lv_NtStatus; 35 | HWND* lv_List; 36 | 37 | // initial size of list 38 | lv_Max = 512; 39 | 40 | // retry to get list 41 | for (;;) 42 | { 43 | // allocate list 44 | if ((lv_List = (HWND*)malloc(lv_Max * sizeof(HWND))) == NULL) 45 | break; 46 | 47 | // call the api 48 | lv_NtStatus = pNtUserBuildHwndList( 49 | in_hDesk, in_hWnd, 50 | in_EnumChildren, in_RemoveImmersive, in_ThreadID, 51 | lv_Max, lv_List, &lv_Cnt); 52 | 53 | // success? 54 | if (lv_NtStatus == NOERROR) 55 | break; 56 | 57 | // free allocated list 58 | free(lv_List); 59 | 60 | // clear 61 | lv_List = NULL; 62 | 63 | // other error then buffersize? or no increase in size? 64 | if (lv_NtStatus != STATUS_BUFFER_TOO_SMALL || lv_Cnt <= lv_Max) 65 | break; 66 | 67 | // update max plus some extra to take changes in number of windows into account 68 | lv_Max = lv_Cnt + 16; 69 | } 70 | 71 | // return the count 72 | *out_Cnt = lv_Cnt; 73 | 74 | // return the list, or NULL when failed 75 | return lv_List; 76 | } 77 | 78 | 79 | /********************************************************/ 80 | /* enumerate all top level windows including metro apps */ 81 | /********************************************************/ 82 | 83 | extern "C" { 84 | __declspec(dllexport) BOOL TopNotifyEnumWindows(WNDENUMPROC in_Proc, LPARAM in_Param) 85 | { 86 | /* locals */ 87 | INT lv_Cnt; 88 | HWND lv_hWnd; 89 | BOOL lv_Result; 90 | HWND lv_hFirstWnd; 91 | HWND lv_hDeskWnd; 92 | HWND* lv_List; 93 | 94 | NtUserBuildHwndList pNtUserBuildHwndList = NULL; 95 | HMODULE hpath; 96 | hpath = LoadLibrary(L"win32u.dll"); 97 | pNtUserBuildHwndList = NtUserBuildHwndList(GetProcAddress(hpath, "NtUserBuildHwndList")); 98 | 99 | // no error yet 100 | lv_Result = TRUE; 101 | 102 | // first try api to get full window list including immersive/metro apps 103 | lv_List = _Gui_BuildWindowList(pNtUserBuildHwndList, 0, 0, 0, 0, 0, &lv_Cnt); 104 | 105 | // success? 106 | if (lv_List) 107 | { 108 | // loop through list 109 | while (lv_Cnt-- > 0 && lv_Result) 110 | { 111 | // get handle 112 | lv_hWnd = lv_List[lv_Cnt]; 113 | 114 | // filter out the invalid entry (0x00000001) then call the callback 115 | if (IsWindow(lv_hWnd)) 116 | lv_Result = in_Proc(lv_hWnd, in_Param); 117 | } 118 | 119 | // free the list 120 | free(lv_List); 121 | } 122 | else 123 | { 124 | // get desktop window, this is equivalent to specifying NULL as hwndParent 125 | lv_hDeskWnd = GetDesktopWindow(); 126 | 127 | // fallback to using FindWindowEx, get first top-level window 128 | lv_hFirstWnd = FindWindowEx(lv_hDeskWnd, 0, 0, 0); 129 | 130 | // init the enumeration 131 | lv_Cnt = 0; 132 | lv_hWnd = lv_hFirstWnd; 133 | 134 | // loop through windows found 135 | // - since 2012 the EnumWindows API in windows has a problem (on purpose by MS) 136 | // that it does not return all windows (no metro apps, no start menu etc) 137 | // - luckally the FindWindowEx() still is clean and working 138 | while (lv_hWnd && lv_Result) 139 | { 140 | // call the callback 141 | lv_Result = in_Proc(lv_hWnd, in_Param); 142 | 143 | // get next window 144 | lv_hWnd = FindWindowEx(lv_hDeskWnd, lv_hWnd, 0, 0); 145 | 146 | // protect against changes in window hierachy during enumeration 147 | if (lv_hWnd == lv_hFirstWnd || lv_Cnt++ > 10000) 148 | break; 149 | } 150 | } 151 | 152 | // return the result 153 | return lv_Result; 154 | } 155 | 156 | } 157 | 158 | -------------------------------------------------------------------------------- /TopNotify.Native/dllmain.cpp: -------------------------------------------------------------------------------- 1 | // dllmain.cpp : Defines the entry point for the DLL application. 2 | #include 3 | 4 | BOOL APIENTRY DllMain( HMODULE hModule, 5 | DWORD ul_reason_for_call, 6 | LPVOID lpReserved 7 | ) 8 | { 9 | switch (ul_reason_for_call) 10 | { 11 | case DLL_PROCESS_ATTACH: 12 | case DLL_THREAD_ATTACH: 13 | case DLL_THREAD_DETACH: 14 | case DLL_PROCESS_DETACH: 15 | break; 16 | } 17 | return TRUE; 18 | } 19 | 20 | -------------------------------------------------------------------------------- /TopNotify.Native/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /TopNotify/Common/AppDiscovery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TopNotify.Common 8 | { 9 | public enum AppDiscoveryMethod 10 | { 11 | MatchAlways, // Always Active 12 | MatchMSIX, // Checks If An MSIX Package Exists That Contains The Specified Search Term 13 | MatchCustomPath // Checks If A Specified Path Exists 14 | } 15 | 16 | [Serializable] 17 | public class AppDiscovery 18 | { 19 | /// 20 | /// Determines How TopNotify Will Check If The App Is Installed 21 | /// 22 | public AppDiscoveryMethod Method = AppDiscoveryMethod.MatchAlways; 23 | 24 | /// 25 | /// How This Search Term Is Used Depends On The App Discovery Method 26 | /// 27 | public string SearchTerm; 28 | 29 | /// 30 | /// Cached Output Of Get-AppxPackage, Filtered To Only Include "Name: " Lines 31 | /// 32 | public static string[] AppxPackageLines 33 | { 34 | get 35 | { 36 | if (_AppxPackageLines == null) 37 | { 38 | _AppxPackageLines = Util.SimpleCMD("powershell -c \"Get-AppxPackage | Select Name\"").Split("\n"); 39 | } 40 | 41 | return _AppxPackageLines; 42 | } 43 | } 44 | 45 | private static string[] _AppxPackageLines = null; 46 | 47 | /// 48 | /// Checks If An App Is Installed Based On Multiple Discovery Parameters 49 | /// 50 | public static bool IsAppInstalled(AppDiscovery[] discoveries) 51 | { 52 | foreach (var discovery in discoveries) 53 | { 54 | if (IsAppInstalled(discovery)) 55 | { 56 | return true; 57 | } 58 | } 59 | 60 | return false; 61 | } 62 | 63 | /// 64 | /// Checks If An App Is Installed Based On Its Discovery Parameters 65 | /// 66 | public static bool IsAppInstalled(AppDiscovery discovery) 67 | { 68 | if (discovery == null) { return false; } 69 | 70 | if (discovery.Method == AppDiscoveryMethod.MatchAlways) { return true; } 71 | else if (discovery.Method == AppDiscoveryMethod.MatchMSIX) 72 | { 73 | foreach (var getAppxPackageLine in AppxPackageLines) 74 | { 75 | // Check If The Line Contains The Package Name 76 | if (getAppxPackageLine.Contains(discovery.SearchTerm)) 77 | { 78 | return true; 79 | } 80 | } 81 | } 82 | else if (discovery.Method == AppDiscoveryMethod.MatchCustomPath) 83 | { 84 | // The Search Term May Contain Environment Variables 85 | var pathWithEnvVariables = Environment.ExpandEnvironmentVariables(discovery.SearchTerm); 86 | return Directory.Exists(pathWithEnvVariables) || File.Exists(pathWithEnvVariables); 87 | } 88 | 89 | return false; 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /TopNotify/Common/AppReference.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using TopNotify.Daemon; 8 | using TopNotify.GUI; 9 | using Windows.UI.Notifications; 10 | 11 | namespace TopNotify.Common 12 | { 13 | // Dictates The Value Of AppReference.ID 14 | public enum AppReferenceType 15 | { 16 | AppName, // Identify The App By It's Display Name 17 | WebsiteDomain // Identify The App By It's Domain (For Web Browser Notifications) 18 | } 19 | 20 | /// 21 | /// Stores Settings For Individual Apps 22 | /// 23 | [Serializable] 24 | public class AppReference 25 | { 26 | /// 27 | /// Helps Interceptors Identify Which App Notifications Belong To 28 | /// 29 | public AppReferenceType ReferenceType; 30 | 31 | /// 32 | /// The Value Depending On The ReferenceType 33 | /// 34 | public string ID; 35 | 36 | public string DisplayName; 37 | 38 | /// 39 | /// URL Of The App's Icon, Can Be A Data URL 40 | /// 41 | public string DisplayIcon; 42 | 43 | /// 44 | /// Relative Path To The WAV File Stored In WWW/Audio, Without .wav Extension 45 | /// 46 | public string SoundPath; 47 | 48 | /// 49 | /// The Name Of The Sound Displayed To The User 50 | /// 51 | public string SoundDisplayName; 52 | 53 | /// 54 | /// Identifies An AppReference Based On A Notification 55 | /// 56 | public static AppReference FromNotification(UserNotification notification) 57 | { 58 | var references = Settings.Get().AppReferences; 59 | 60 | foreach (var reference in references) 61 | { 62 | if (reference.ReferenceType == AppReferenceType.AppName && notification.AppInfo.DisplayInfo.DisplayName == reference.ID) 63 | { 64 | return reference; 65 | } 66 | //TODO: Identify The Domain Of Browser Notifications 67 | } 68 | 69 | // Return The Default AppReference 70 | return references.Where((r) => r.ID == "Other").FirstOrDefault(); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /TopNotify/Common/Logging.cs: -------------------------------------------------------------------------------- 1 | using IgniteView.Core; 2 | using IgniteView.Desktop; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Runtime.InteropServices; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace TopNotify.Common 11 | { 12 | internal class Logging 13 | { 14 | /// 15 | /// Writes a copyright watermark as well as debug information to the log 16 | /// 17 | public static void WriteWatermark(string mode) 18 | { 19 | Program.Logger.Information($"Copyright © SamsidParty {DateTime.Now.Year}\nLicensed to you under the GPL v3.0 License"); 20 | Program.Logger.Information($"Launching in {mode} mode..."); 21 | Program.Logger.Information($"System Username: {Environment.UserName}"); 22 | Program.Logger.Information($"System Architecture: {RuntimeInformation.ProcessArchitecture}"); 23 | Program.Logger.Information($"System Name: {Environment.MachineName}"); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /TopNotify/Common/MonitorData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TopNotify.Common 8 | { 9 | [Serializable] 10 | public class MonitorData 11 | { 12 | /// 13 | /// Used By TopNotify To Identify The Monitor 14 | /// 15 | public string ID; 16 | 17 | /// 18 | /// The Path To The Monitor, Eg. \\.\DISPLAY1\... 19 | /// 20 | public string Path; 21 | 22 | /// 23 | /// The Friendly Name Of The Monitor 24 | /// 25 | public string FriendlyName; 26 | 27 | /// 28 | /// The Name Of The GPU As Reported By Windows, Eg: RADEON RX 7800 XT 29 | /// 30 | public string GraphicsDriverName; 31 | 32 | /// 33 | /// The Name Shown To The User 34 | /// 35 | public string DisplayName 36 | { 37 | get 38 | { 39 | return FriendlyName + " (" + Path + ") " + GraphicsDriverName; 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /TopNotify/Common/NotificationTester.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Drawing; 7 | using System.Buffers; 8 | using System.Reflection; 9 | using Microsoft.Toolkit.Uwp.Notifications; 10 | using System.Runtime.InteropServices; 11 | 12 | namespace TopNotify.Common 13 | { 14 | public class NotificationTester 15 | { 16 | [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] 17 | public static extern int MessageBox(IntPtr hWnd, String text, String caption, uint type); 18 | 19 | public static void SpawnTestNotification() 20 | { 21 | Toast("Test Notification", "This Is A Test Notification"); 22 | } 23 | 24 | public static void Toast(string title, string content) 25 | { 26 | new ToastContentBuilder() 27 | .AddText(title) 28 | .AddText(content) 29 | .Show(); 30 | } 31 | 32 | public static void MessageBox(string title, string content) 33 | { 34 | MessageBox(IntPtr.Zero, content, title, 0); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /TopNotify/Common/Settings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.Win32; 7 | using Newtonsoft.Json; 8 | using TopNotify.Daemon; 9 | using Windows.ApplicationModel; 10 | using Windows.Foundation.Metadata; 11 | using static System.Runtime.InteropServices.JavaScript.JSType; 12 | 13 | namespace TopNotify.Common 14 | { 15 | [Serializable] 16 | public class Settings 17 | { 18 | public NotifyLocation Location = NotifyLocation.TopRight; 19 | public bool EnableClickThrough = false; 20 | 21 | // Debug 22 | public bool EnableDebugNotifications = false; 23 | public bool EnableDebugForceFallbackMode = false; 24 | public bool EnableDebugRemoveBoundsCorrection = false; 25 | 26 | // Accessibility 27 | public bool ReadAloud = false; 28 | 29 | // From 0 To 5 (0 Is Fully Opaque, 5 Is Mostly Transparent) 30 | public float Opacity = 0; 31 | 32 | // Position Where Origin Is The Top Left Of The Screen 33 | // 0% On Both Is The Top Left 34 | // 100% On Both Is Bottom Right 35 | public float CustomPositionPercentX = 0; 36 | public float CustomPositionPercentY = 0; 37 | 38 | public string PreferredMonitor = "primary"; 39 | 40 | public List AppReferences = new List(); 41 | 42 | // Dynamic Fields That Are Cached, Useful For Interop 43 | public int __ScreenWidth = 0; 44 | public int __ScreenHeight = 0; 45 | public float __ScreenScale = 1; 46 | public List __MonitorData; 47 | 48 | // Deprecated Settings 49 | [Deprecated("Use CustomPositionPercentX Instead", DeprecationType.Deprecate, 241)] public int CustomPositionX = 0; // Deprecated In Favor Of Percentage Units 50 | [Deprecated("Use CustomPositionPercentY Instead", DeprecationType.Deprecate, 241)] public int CustomPositionY = 0; // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 51 | [Deprecated("Startup Is Now Managed By MSIX", DeprecationType.Deprecate, 244)] public bool RunOnStartup = false; // Startup Is Now Managed By MSIX 52 | 53 | 54 | public static Settings Get() 55 | { 56 | var value = JsonConvert.DeserializeObject(GetRaw()); 57 | return value; 58 | } 59 | 60 | public static string GetRaw() 61 | { 62 | var path = GetFilePath(); 63 | 64 | var content = ""; 65 | using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) 66 | using (var textReader = new StreamReader(fileStream)) 67 | { 68 | content = textReader.ReadToEnd(); 69 | } 70 | 71 | 72 | return content; 73 | } 74 | 75 | /// 76 | /// Returns A JSON String Of The Settings, Modified With Custom Fields For IPC 77 | /// 78 | public static string GetForIPC() 79 | { 80 | var settings = Settings.Get(); 81 | settings.UpdateDynamicFields(); 82 | return JsonConvert.SerializeObject(settings); 83 | } 84 | 85 | /// 86 | /// Returns The Full Path Of The Settings File 87 | /// 88 | public static string GetFilePath() 89 | { 90 | var defaultSettings = JsonConvert.SerializeObject(GetDefaultSettings(), Formatting.Indented); 91 | var value = GetFilePath("Settings.json", Encoding.UTF8.GetBytes(defaultSettings)); 92 | return value; 93 | } 94 | 95 | public static Settings GetDefaultSettings() 96 | { 97 | var defaultSettings = new Settings(); 98 | 99 | // Add The Default App Reference 100 | defaultSettings.AppReferences.Add(new AppReference() 101 | { 102 | ReferenceType = 0, 103 | ID = "Other", 104 | DisplayName = "All Other Apps", 105 | DisplayIcon = "/Image/DefaultAppReferenceIcon.svg", 106 | SoundPath = "windows/win11", 107 | SoundDisplayName = "Notify 11" 108 | } 109 | ); 110 | 111 | return defaultSettings; 112 | } 113 | 114 | /// 115 | /// Returns The Full Path Of fileName Located In TopNotify's AppData Directory 116 | /// Uses defaultValue if the file doesn't exist 117 | /// 118 | public static string GetFilePath(string fileName, byte[] defaultValue) 119 | { 120 | var appFolder = GetAppDataFolder(); 121 | var file = Path.Combine(appFolder, fileName); 122 | if (!File.Exists(file)) 123 | { 124 | //Create Default File 125 | File.WriteAllBytes(file, defaultValue != null ? defaultValue : new byte[0]); // Write default value if it's not null 126 | 127 | if (fileName == "Settings.json") 128 | { 129 | //Show First Launch Notification 130 | NotificationTester.Toast("TopNotify Has Been Installed", "You Can Find The Settings For TopNotify In The System Tray"); 131 | } 132 | 133 | } 134 | return file; 135 | } 136 | 137 | public static string GetAppDataFolder() 138 | { 139 | var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); 140 | var appFolder = Path.Combine(localAppData, "SamsidParty", "TopNotify"); 141 | 142 | if (!Directory.Exists(appFolder)) 143 | { 144 | Directory.CreateDirectory(appFolder); 145 | } 146 | 147 | return appFolder; 148 | } 149 | 150 | 151 | /// 152 | /// Writes The Settings File With New Data 153 | /// 154 | public static void Overwrite(string newData) 155 | { 156 | try 157 | { 158 | File.WriteAllText(Settings.GetFilePath(), newData); 159 | } 160 | catch (Exception ex) 161 | { 162 | } 163 | } 164 | 165 | /// 166 | /// Updates The Temporary Fields Used By Other Parts Of TopNotify (eg. __ScreenWidth) 167 | /// 168 | public void UpdateDynamicFields() 169 | { 170 | __ScreenWidth = ResolutionFinder.GetRealResolution().Width; 171 | __ScreenHeight = ResolutionFinder.GetRealResolution().Height; 172 | __ScreenScale = ResolutionFinder.GetScale(); 173 | __MonitorData = ResolutionFinder.GetMonitors(); 174 | } 175 | 176 | } 177 | 178 | public enum NotifyLocation 179 | { 180 | TopLeft, 181 | TopRight, 182 | BottomLeft, 183 | BottomRight, 184 | Custom 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /TopNotify/Common/Util.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Drawing; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Text.RegularExpressions; 8 | using System.Threading.Tasks; 9 | 10 | namespace TopNotify.Common 11 | { 12 | public class Util 13 | { 14 | 15 | /// 16 | /// Runs A Command Prompt Command And Returns The Output 17 | /// 18 | /// 19 | /// 20 | public static string SimpleCMD(string cmdString) 21 | { 22 | var command = "/c " + cmdString; 23 | var cmdsi = new ProcessStartInfo("cmd.exe"); 24 | cmdsi.Arguments = command; 25 | cmdsi.RedirectStandardOutput = true; 26 | cmdsi.UseShellExecute = false; 27 | cmdsi.CreateNoWindow = true; 28 | var cmd = Process.Start(cmdsi); 29 | var output = cmd.StandardOutput.ReadToEnd(); 30 | 31 | cmd.WaitForExit(); 32 | 33 | output = (new Regex("[ ]{2,}", RegexOptions.None)).Replace(output, " "); //Remove Double Spaces 34 | return output; 35 | } 36 | 37 | public static Icon FindAppIcon() 38 | { 39 | var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "dist", "Image", "Icon.ico"); 40 | return new Icon(path); 41 | } 42 | 43 | public static string FindExe() 44 | { 45 | return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "TopNotify.exe"); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /TopNotify/Daemon/Daemon.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection.Metadata; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using System.Diagnostics; 9 | using TopNotify.Common; 10 | using TopNotify.GUI; 11 | using KdSoft.MailSlot; 12 | 13 | namespace TopNotify.Daemon 14 | { 15 | public class Daemon 16 | { 17 | public static Daemon Instance; 18 | 19 | public InterceptorManager Manager; 20 | 21 | public Daemon() { 22 | Instance = this; 23 | 24 | TrayIcon.Setup(); 25 | 26 | Thread managerThread = new Thread(CreateManager); 27 | managerThread.Start(); 28 | 29 | Task.Run(MailSlotListener); 30 | 31 | TrayIcon.MainLoop(); 32 | } 33 | 34 | /// 35 | /// Listens for messages that affect the app lifecycle 36 | /// 37 | async Task MailSlotListener() 38 | { 39 | var listener = new AsyncMailSlotListener("samsidparty_topnotify", Encoding.ASCII.GetBytes("\n")[0]); 40 | await foreach (var msgBytes in listener.GetNextMessage()) 41 | { 42 | var msg = Encoding.UTF8.GetString(msgBytes); 43 | 44 | if (msg == "UpdateConfig") // Runs when the user changes a setting from the GUI 45 | { 46 | InterceptorManager.Instance.OnSettingsChanged(); 47 | } 48 | } 49 | } 50 | 51 | /// 52 | /// This should be called from an external (non-daemon) process to send a message to the daemon 53 | /// 54 | public static void SendCommandToDaemon(string message) 55 | { 56 | try 57 | { 58 | var buffer = new byte[1024]; 59 | using (var client = MailSlot.CreateClient("samsidparty_topnotify")) 60 | { 61 | var bytes = Encoding.UTF8.GetBytes(message + "\n"); 62 | client.Write(bytes, 0, bytes.Length); 63 | } 64 | } 65 | catch (Exception ex) { } 66 | } 67 | 68 | public void CreateManager() 69 | { 70 | Manager = new InterceptorManager(); 71 | Manager.Start(); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /TopNotify/Daemon/DaemonError.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TopNotify.Daemon 8 | { 9 | [Serializable] 10 | public class DaemonError 11 | { 12 | public string ID = "generic_error"; 13 | public string Text = "Something Went Wrong With TopNotify"; 14 | 15 | public DaemonError(string id, string text) 16 | { 17 | this.ID = id; 18 | this.Text = text; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /TopNotify/Daemon/DaemonErrorHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TopNotify.Common; 7 | 8 | namespace TopNotify.Daemon 9 | { 10 | public class DaemonErrorHandler 11 | { 12 | public static List Errors = new List(); 13 | 14 | /// 15 | /// Displays An Error To The User Without Closing TopNotify 16 | /// 17 | public static void ThrowNonCritical(DaemonError error) 18 | { 19 | Errors.Add(error); 20 | NotificationTester.Toast("Something Went Wrong", error.Text); 21 | } 22 | 23 | /// 24 | /// Displays An Error To The User And Closes TopNotify 25 | /// 26 | public static void ThrowCritical(DaemonError error) 27 | { 28 | Errors.Add(error); 29 | NotificationTester.Toast("Something Went Wrong", error.Text); 30 | Environment.Exit(1); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /TopNotify/Daemon/ExtendedStyleManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.InteropServices; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using TopNotify.Daemon; 8 | 9 | namespace SamsidParty_TopNotify.Daemon 10 | { 11 | public class ExtendedStyleManager 12 | { 13 | #region WinAPI 14 | 15 | [DllImport("user32.dll")] 16 | public static extern int SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong); 17 | 18 | [DllImport("user32.dll")] 19 | public static extern IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex); 20 | 21 | [DllImport("user32.dll")] 22 | public static extern bool SetLayeredWindowAttributes(IntPtr hwnd, uint crKey, byte bAlpha, uint dwFlags); 23 | 24 | [DllImport("user32.dll")] 25 | public static extern short GetKeyState(int nVirtKey); 26 | 27 | public static bool AltKeyDown => ((GetKeyState(0x12) & 0x8000) > 0); 28 | 29 | #endregion 30 | 31 | #region Constants 32 | 33 | public const int GWL_EXSTYLE = -20; 34 | public const int LWA_ALPHA = 0x2; 35 | public const IntPtr WS_EX_LAYERED = 0x80000; 36 | public const IntPtr WS_EX_TRANSPARENT = 0x00000020; 37 | 38 | #endregion 39 | 40 | public List Styles = new List(); 41 | public IntPtr BaseStyle = IntPtr.Zero; 42 | public IntPtr LastHandle = IntPtr.Zero; 43 | public IntPtr LastStyle = IntPtr.Zero; 44 | 45 | public ExtendedStyleManager(IntPtr baseStyle) 46 | { 47 | BaseStyle = baseStyle; 48 | } 49 | 50 | public void Update(IntPtr hwnd) 51 | { 52 | if (hwnd == IntPtr.Zero) { return; } 53 | 54 | LastHandle = hwnd; 55 | 56 | IntPtr styleToApply = AddExtendedStyles(BaseStyle); 57 | foreach (var style in Styles) 58 | { 59 | styleToApply |= style; 60 | } 61 | 62 | // Checks If VK_MENU (Alt Key) is down and reverts to the default style if it is held 63 | // This allows the user to temporarily bypass the click-through window and interact with the notification 64 | if (AltKeyDown) 65 | { 66 | styleToApply = BaseStyle; 67 | } 68 | 69 | if (styleToApply != LastStyle) 70 | { 71 | SetWindowLongPtr(hwnd, GWL_EXSTYLE, styleToApply); 72 | LastStyle = styleToApply; 73 | } 74 | 75 | //Set Window Opacity 76 | SetLayeredWindowAttributes(hwnd, 0, (byte)(42.5 * (6 - InterceptorManager.Instance.CurrentSettings.Opacity)), LWA_ALPHA); 77 | } 78 | 79 | //Like Update, But Can Be Used Without An ExtendedStyleManager Object 80 | //Doesn't Apply Extra Styles From The Instance 81 | public static void AnonymousUpdate(IntPtr hwnd, IntPtr baseStyle) 82 | { 83 | if (hwnd == IntPtr.Zero) { return; } 84 | IntPtr styleToApply = AddExtendedStyles(baseStyle); 85 | SetWindowLongPtr(hwnd, GWL_EXSTYLE, styleToApply); 86 | SetLayeredWindowAttributes(hwnd, 0, (byte)(42.5 * (6 - InterceptorManager.Instance.CurrentSettings.Opacity)), LWA_ALPHA); 87 | } 88 | 89 | //Adds Extended Styles To The Provided Style Number Based On The User Config 90 | public static IntPtr AddExtendedStyles(IntPtr baseStyle) 91 | { 92 | IntPtr styleToApply = baseStyle; 93 | 94 | if (InterceptorManager.Instance.CurrentSettings.EnableClickThrough) 95 | { 96 | styleToApply |= WS_EX_TRANSPARENT; 97 | } 98 | styleToApply |= WS_EX_LAYERED; 99 | 100 | return styleToApply; 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /TopNotify/Daemon/Interceptor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TopNotify.Common; 7 | using Windows.UI.Notifications; 8 | 9 | namespace TopNotify.Daemon 10 | { 11 | public class Interceptor 12 | { 13 | public Settings Settings { get { return InterceptorManager.Instance.CurrentSettings; } } 14 | 15 | 16 | public virtual void Restart() { } 17 | 18 | /// 19 | /// Used To Update The Interceptor When Certain Keys Are Pressed Or Released 20 | /// 21 | public virtual void OnKeyUpdate() { } 22 | 23 | /// 24 | /// Detects Whether The Interceptor Should Run 25 | /// 26 | public virtual bool ShouldEnable() 27 | { 28 | return true; 29 | } 30 | 31 | /// 32 | /// Called When A Notification Pops Up 33 | /// 34 | public virtual void OnNotification(UserNotification notification) 35 | { 36 | 37 | } 38 | 39 | /// 40 | /// Run Often Just To Rediscover Windows/Configs And Such 41 | /// 42 | public virtual void Reflow() { } 43 | public virtual void Start() { } 44 | public virtual void Update() { } 45 | 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /TopNotify/Daemon/InterceptorManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Runtime.InteropServices; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | using TopNotify.Common; 11 | using Windows.UI.Notifications; 12 | using Windows.UI.Notifications.Management; 13 | using static TopNotify.Daemon.NativeInterceptor; 14 | 15 | namespace TopNotify.Daemon 16 | { 17 | public class InterceptorManager 18 | { 19 | #region WinAPI Methods 20 | 21 | #endregion 22 | 23 | public static InterceptorManager Instance; 24 | public List Interceptors = new(); 25 | 26 | public Settings CurrentSettings; 27 | 28 | public int TimeSinceReflow = 0; 29 | public const int ReflowTimeout = 50; 30 | 31 | public ConcurrentDictionary CleanUpFunctions = new ConcurrentDictionary(); // Maps HandledNotifications to the associated clean up function 32 | public UserNotificationListener Listener; 33 | public bool CanListenToNotifications = false; 34 | 35 | public static Interceptor[] InstalledInterceptors = 36 | { 37 | new NativeInterceptor(), 38 | new DiscoveryInterceptor(), 39 | new SoundInterceptor(), 40 | new ReadAloudInterceptor() 41 | }; 42 | 43 | public void Start() 44 | { 45 | Instance = this; 46 | CurrentSettings = Settings.Get(); 47 | 48 | foreach (var possibleInterceptor in InstalledInterceptors) 49 | { 50 | // Check If It's Eligible To Be Enabled 51 | if (possibleInterceptor.ShouldEnable()) 52 | { 53 | Interceptors.Add(possibleInterceptor); 54 | } 55 | } 56 | 57 | Listener = UserNotificationListener.Current; 58 | Task.Run(async () => 59 | { 60 | 61 | //Ask For Permissions To Read Notifications 62 | var access = await Listener.RequestAccessAsync(); 63 | if (access != UserNotificationListenerAccessStatus.Allowed) 64 | { 65 | var msg = "Failed To Start Notification Listener: Permission Denied"; 66 | DaemonErrorHandler.ThrowNonCritical(new DaemonError("listener_failure_no_permission", msg)); 67 | return; 68 | } 69 | 70 | 71 | try 72 | { 73 | //Throws a COM exception if not packaged into an MSIX app 74 | //Currently no workaround 75 | Listener.NotificationChanged += OnNotificationChanged; 76 | } 77 | catch (Exception ex) 78 | { 79 | var msg = "Failed To Start Notification Listener: Not Packaged"; 80 | DaemonErrorHandler.ThrowNonCritical(new DaemonError("listener_failure_not_packaged", msg)); 81 | return; 82 | } 83 | 84 | CanListenToNotifications = true; 85 | 86 | }); 87 | 88 | foreach (Interceptor i in Interceptors) 89 | { 90 | i.Start(); 91 | } 92 | 93 | MainLoop(); 94 | } 95 | 96 | public void MainLoop() 97 | { 98 | while (true) 99 | { 100 | TimeSinceReflow++; 101 | 102 | if (TimeSinceReflow > ReflowTimeout) 103 | { 104 | TimeSinceReflow = 0; 105 | Reflow(); 106 | } 107 | 108 | Update(); 109 | 110 | Thread.Sleep(10); 111 | } 112 | } 113 | 114 | public void Reflow() 115 | { 116 | foreach (Interceptor i in Interceptors) 117 | { 118 | try 119 | { 120 | i.Reflow(); 121 | } 122 | catch (Exception ex) { } 123 | } 124 | } 125 | 126 | public void Update() 127 | { 128 | foreach (Interceptor i in Interceptors) 129 | { 130 | try 131 | { 132 | i.Update(); 133 | } 134 | catch (Exception ex) { } 135 | } 136 | } 137 | 138 | // Called from C++ to safely invoke the OnKeyUpdate method 139 | public static void TryOnKeyUpdate() 140 | { 141 | if (Instance == null) { return; } 142 | Instance.OnKeyUpdate(); 143 | } 144 | 145 | public void OnKeyUpdate() 146 | { 147 | foreach (Interceptor i in Interceptors) 148 | { 149 | try 150 | { 151 | i.OnKeyUpdate(); 152 | } 153 | catch (Exception ex) { } 154 | } 155 | } 156 | 157 | // Runs When A New Notification Is Added Or Removed 158 | public async void OnNotificationChanged(UserNotificationListener sender, UserNotificationChangedEventArgs args) 159 | { 160 | var userNotifications = await Listener.GetNotificationsAsync(NotificationKinds.Toast); 161 | var userNotification = userNotifications.Where((n) => n.Id == args.UserNotificationId).FirstOrDefault(); 162 | 163 | if (args.ChangeKind == UserNotificationChangedKind.Added) 164 | { 165 | foreach (Interceptor i in Interceptors) 166 | { 167 | try 168 | { 169 | i.OnNotification(userNotification); 170 | } 171 | catch { } 172 | } 173 | } 174 | 175 | Update(); 176 | } 177 | 178 | public void OnSettingsChanged() 179 | { 180 | CurrentSettings = Settings.Get(); 181 | 182 | foreach (Interceptor i in Interceptors) 183 | { 184 | try 185 | { 186 | i.Restart(); 187 | i.Reflow(); 188 | } 189 | catch (Exception ex) { } 190 | } 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /TopNotify/Daemon/Interceptors/DiscoveryInterceptor.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using TopNotify.Common; 8 | using TopNotify.Daemon; 9 | using Windows.UI.Notifications; 10 | 11 | namespace TopNotify.Daemon 12 | { 13 | public class DiscoveryInterceptor : Interceptor 14 | { 15 | public override void OnNotification(UserNotification notification) 16 | { 17 | // Store The App's Info, So That In The Future It Can Be Used To Create An AppReference 18 | var appInfo = notification.AppInfo; 19 | var appReferences = Settings.AppReferences; 20 | var alreadyHasAppReference = appReferences.Where((r) => r.ID == appInfo.DisplayInfo.DisplayName).Any(); 21 | 22 | if (!alreadyHasAppReference) 23 | { 24 | // Add The AppInfo To The Settings File 25 | var settingsFile = TopNotify.Common.Settings.Get(); 26 | 27 | var appReference = new AppReference() 28 | { 29 | DisplayName = appInfo.DisplayInfo.DisplayName, 30 | ID = appInfo.DisplayInfo.DisplayName, 31 | SoundPath = "internal/default", 32 | SoundDisplayName = "Default Sound", 33 | ReferenceType = AppReferenceType.AppName 34 | }; 35 | 36 | settingsFile.AppReferences.Add(appReference); 37 | 38 | TopNotify.Common.Settings.Overwrite(JsonConvert.SerializeObject(settingsFile)); // Write The Settings File 39 | InterceptorManager.Instance.OnSettingsChanged(); // Tells The InterceptorManager To Reload The Settings File 40 | } 41 | 42 | base.OnNotification(notification); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /TopNotify/Daemon/Interceptors/NativeInterceptor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Drawing; 5 | using System.Linq; 6 | using System.Runtime.InteropServices; 7 | using System.Text; 8 | using System.Windows; 9 | using System.Threading.Tasks; 10 | using TopNotify.Common; 11 | using SamsidParty_TopNotify.Daemon; 12 | using Windows.UI.Notifications.Management; 13 | using Windows.ApplicationModel.Background; 14 | using Windows.UI.Notifications; 15 | using static TopNotify.Daemon.ResolutionFinder; 16 | 17 | namespace TopNotify.Daemon 18 | { 19 | 20 | public class NativeInterceptor : Interceptor 21 | { 22 | #region WinAPI Methods 23 | 24 | [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)] 25 | public static extern IntPtr FindWindow(string lpClassName, string lpWindowName); 26 | 27 | [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)] 28 | public static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr hWndChildAfter, string className, string windowTitle); 29 | 30 | [DllImport("user32.dll")] 31 | public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); 32 | 33 | [DllImport("user32.dll", EntryPoint = "SetWindowPos")] 34 | public static extern IntPtr SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int x, int Y, int cx, int cy, int wFlags); 35 | 36 | [DllImport("user32.dll")] 37 | public static extern bool GetWindowRect(IntPtr hwnd, ref Rectangle rectangle); 38 | 39 | [DllImport("user32.dll", CharSet = CharSet.Auto)] 40 | public static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam); 41 | 42 | [DllImport("user32.dll", SetLastError = true)] 43 | public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId); 44 | 45 | [DllImport("user32.dll", CharSet = CharSet.Unicode)] 46 | public static extern int GetWindowText(IntPtr hWnd, StringBuilder strText, int maxCount); 47 | 48 | [DllImport("user32.dll", CharSet = CharSet.Unicode)] 49 | public static extern int GetWindowTextLength(IntPtr hWnd); 50 | 51 | const UInt32 WM_CLOSE = 0x0010; 52 | const short SWP_NOMOVE = 0X2; 53 | const short SWP_NOSIZE = 1; 54 | const short SWP_NOZORDER = 0X4; 55 | const int SWP_SHOWWINDOW = 0x0040; 56 | 57 | [DllImport("TopNotify.Native")] 58 | private static extern bool TopNotifyEnumWindows(EnumWindowsProc enumProc, IntPtr lParam); 59 | 60 | public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); 61 | 62 | [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] 63 | private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount); 64 | 65 | #endregion 66 | 67 | public IntPtr hwnd; 68 | public ExtendedStyleManager ExStyleManager; 69 | public int ScaledPreferredDisplayWidth; 70 | public int ScaledPreferredDisplayHeight; 71 | public int RealPreferredDisplayWidth; 72 | public int RealPreferredDisplayHeight; 73 | public float ScaleFactor; 74 | 75 | public override void Start() 76 | { 77 | base.Start(); 78 | ExStyleManager = new ExtendedStyleManager(new IntPtr(0x00200008)); // Magic Number, Default Notification Style 79 | Reflow(); 80 | } 81 | 82 | public override void Restart() 83 | { 84 | base.Restart(); 85 | } 86 | 87 | // Modified From https://stackoverflow.com/a/20276701/18071273 88 | public static IEnumerable FindCoreWindows() 89 | { 90 | IntPtr found = IntPtr.Zero; 91 | List windows = new List(); 92 | 93 | TopNotifyEnumWindows(delegate (IntPtr hwnd, IntPtr param) 94 | { 95 | var classGet = new StringBuilder(1024); 96 | GetClassName(hwnd, classGet, classGet.Capacity); 97 | if (classGet.ToString() == "Windows.UI.Core.CoreWindow") 98 | { 99 | windows.Add(hwnd); 100 | } 101 | 102 | return true; 103 | }, IntPtr.Zero); 104 | 105 | return windows; 106 | } 107 | 108 | public override void Reflow() 109 | { 110 | if (ExStyleManager == null) { return; } // Return If Start() Has Not Been Called Yet 111 | 112 | base.Reflow(); 113 | 114 | try 115 | { 116 | var foundHwnd = FindWindow("Windows.UI.Core.CoreWindow", Language.NotificationName); 117 | 118 | if (Settings.EnableDebugForceFallbackMode) 119 | { 120 | Program.Logger.Information($"Fallback detection is being forced"); 121 | foundHwnd = IntPtr.Zero; // Always use fallback mode if this setting is enabled 122 | } 123 | 124 | ScaledPreferredDisplayWidth = ResolutionFinder.GetScaledResolution().Width; 125 | ScaledPreferredDisplayHeight = ResolutionFinder.GetScaledResolution().Height; 126 | RealPreferredDisplayWidth = ResolutionFinder.GetRealResolution().Width; 127 | RealPreferredDisplayHeight = ResolutionFinder.GetRealResolution().Height; 128 | ScaleFactor = ResolutionFinder.GetInverseScale(); 129 | 130 | //The Notification Isn't In A Supported Language 131 | if (foundHwnd == IntPtr.Zero) 132 | { 133 | Program.Logger.Information($"Couldn't use language-specific window detection, using fallback detection"); 134 | //The Notification Window Is The Only One That Is 396 x 152 135 | foreach (var win in FindCoreWindows()) 136 | { 137 | Rectangle rect = new Rectangle(); 138 | GetWindowRect(win, ref rect); 139 | 140 | if ((ScaledPreferredDisplayWidth - rect.X) == 396) 141 | { 142 | foundHwnd = win; 143 | } 144 | } 145 | } 146 | 147 | if (foundHwnd != IntPtr.Zero && hwnd != foundHwnd) 148 | { 149 | Program.Logger.Information($"Found notification window {foundHwnd}"); 150 | hwnd = foundHwnd; 151 | } 152 | else if (foundHwnd == IntPtr.Zero) 153 | { 154 | Program.Logger.Error($"Couldn't find the handle of the notification window"); 155 | } 156 | 157 | Update(); 158 | 159 | } 160 | catch { } 161 | } 162 | 163 | public override void OnKeyUpdate() 164 | { 165 | // Delay until the keypress has been processed 166 | Task.Run(async () => 167 | { 168 | await Task.Delay(100); 169 | ExStyleManager.Update(hwnd); 170 | }); 171 | 172 | base.OnKeyUpdate(); 173 | } 174 | 175 | public override void Update() 176 | { 177 | base.Update(); 178 | 179 | // Update extended styles 180 | ExStyleManager.Update(hwnd); 181 | 182 | // Find The Bounds Of The Notification Window 183 | Rectangle NotifyRect = new Rectangle(); 184 | GetWindowRect(hwnd, ref NotifyRect); 185 | 186 | // Find The Bounds Of The Preferred Monitor 187 | var hMonitor = ResolutionFinder.GetPreferredDisplay(); 188 | MonitorInfo currentMonitorInfo = new MonitorInfo(); 189 | ResolutionFinder.GetMonitorInfo(hMonitor, currentMonitorInfo); 190 | var originX = currentMonitorInfo.Monitor.Left; 191 | var originY = currentMonitorInfo.Monitor.Top; 192 | 193 | var scaledWidth = (int)((NotifyRect.Width - NotifyRect.X * ScaleFactor)); 194 | var scaledHeight = (int)((NotifyRect.Height - NotifyRect.Y * ScaleFactor)); 195 | var unscaledWidth = (int)((NotifyRect.Width - NotifyRect.X)); 196 | var unscaledHeight = (int)((NotifyRect.Height - NotifyRect.Y)); 197 | 198 | if (Settings.Location == NotifyLocation.TopLeft) 199 | { 200 | //Easy Peesy 201 | SetWindowPos(hwnd, 0, originX + 0, originY + 0, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_SHOWWINDOW); 202 | } 203 | else if (Settings.Location == NotifyLocation.TopRight) 204 | { 205 | SetWindowPos(hwnd, 0, originX + (RealPreferredDisplayWidth - unscaledWidth), 0, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_SHOWWINDOW); 206 | } 207 | else if (Settings.Location == NotifyLocation.BottomLeft) 208 | { 209 | SetWindowPos(hwnd, 0, originX + 0, originY + (RealPreferredDisplayHeight - unscaledHeight - (int)Math.Round(50f)), 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_SHOWWINDOW); 210 | } 211 | else if (Settings.Location == NotifyLocation.BottomRight) // Default In Windows, But Here For Completeness Sake 212 | { 213 | SetWindowPos(hwnd, 0, originX + (RealPreferredDisplayWidth - unscaledWidth), originY + (RealPreferredDisplayHeight - unscaledHeight - (int)Math.Round(50f)), 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_SHOWWINDOW); 214 | } 215 | else // Custom Position 216 | { 217 | var xPosition = (int)(Settings.CustomPositionPercentX / 100f * RealPreferredDisplayWidth); 218 | var yPosition = (int)(Settings.CustomPositionPercentY / 100f * RealPreferredDisplayHeight); 219 | 220 | if (!Settings.EnableDebugRemoveBoundsCorrection) 221 | { 222 | // Make Sure Position Isn't Out Of Bounds 223 | xPosition = Math.Clamp(xPosition, 0, RealPreferredDisplayWidth - unscaledWidth); 224 | yPosition = Math.Clamp(yPosition, 0, RealPreferredDisplayHeight - unscaledHeight); 225 | } 226 | 227 | SetWindowPos(hwnd, 0, originX + xPosition, originY + yPosition, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_SHOWWINDOW); 228 | } 229 | 230 | } 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /TopNotify/Daemon/Interceptors/ReadAloudInterceptor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Speech.Synthesis; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Windows.UI.Notifications; 8 | 9 | namespace TopNotify.Daemon 10 | { 11 | public class ReadAloudInterceptor : Interceptor 12 | { 13 | SpeechSynthesizer Synthesizer; 14 | 15 | public override void OnNotification(UserNotification notification) 16 | { 17 | if (!Settings.ReadAloud) { return; } 18 | 19 | if (Synthesizer == null) { Synthesizer = new SpeechSynthesizer(); } 20 | 21 | Synthesizer.SetOutputToDefaultAudioDevice(); 22 | Synthesizer.Speak(GetNotificationAsText(notification)); 23 | 24 | 25 | base.OnNotification(notification); 26 | } 27 | 28 | 29 | /// 30 | /// Returns A Text-To-Speech Friendly String Based On A Notification's Contents 31 | /// 32 | public string GetNotificationAsText(UserNotification notification) 33 | { 34 | var text = "New notification from"; 35 | text += notification.AppInfo.DisplayInfo.DisplayName + ".\n"; 36 | foreach (var binding in notification.Notification.Visual.Bindings) 37 | { 38 | foreach (var notificationText in binding.GetTextElements()) 39 | { 40 | text += notificationText.Text + ".\n"; 41 | } 42 | } 43 | 44 | return text; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /TopNotify/Daemon/Interceptors/SoundInterceptor.cs: -------------------------------------------------------------------------------- 1 | using IgniteView.Core; 2 | using Microsoft.Win32; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Data; 6 | using System.Diagnostics; 7 | using System.Drawing; 8 | using System.IO; 9 | using System.IO.Pipes; 10 | using System.Linq; 11 | using System.Media; 12 | using System.Security.AccessControl; 13 | using System.Security.Principal; 14 | using System.Text; 15 | using System.Threading.Tasks; 16 | using TopNotify.Common; 17 | using Windows.UI.Notifications; 18 | 19 | namespace TopNotify.Daemon 20 | { 21 | public class SoundInterceptor : Interceptor 22 | { 23 | // This File Is Used To Replace The Default Notification Sounds, So That TopNotify Can Play A Different Sound 24 | const string FAKE_SOUND = "internal/silent"; 25 | 26 | SoundPlayer Player; 27 | bool isPlaying = false; 28 | 29 | bool allowedToPlaySound = false; 30 | 31 | /// 32 | /// Sets The Notification Sound In The Registry To The Fake Sound File 33 | /// TopNotify Doesn't Have Registry Access Because Of MSIX, So Call CMD To Do It For Us 34 | /// 35 | [Command("InstallSoundInRegistry")] 36 | public static void InstallSoundInRegistry() 37 | { 38 | try 39 | { 40 | var command = $"reg add HKCU\\AppEvents\\Schemes\\Apps\\.Default\\Notification.Default\\.Current /t REG_SZ /ve /d \"{GetCopiedSoundPath(FAKE_SOUND)}\" /f"; 41 | Util.SimpleCMD(command); 42 | } 43 | catch (Exception ex) { } 44 | } 45 | 46 | /// 47 | /// Reverts the notification sound to the default 48 | /// 49 | [Command("UninstallSoundInRegistry")] 50 | public static void UninstallSoundInRegistry() 51 | { 52 | try 53 | { 54 | // This is the default soundpath found in the windows 11 registry 55 | var defaultSoundPath = Environment.ExpandEnvironmentVariables("%SystemRoot%\\media\\Windows Notify System Generic.wav"); 56 | var command = $"reg add HKCU\\AppEvents\\Schemes\\Apps\\.Default\\Notification.Default\\.Current /t REG_SZ /ve /d \"{defaultSoundPath}\" /f"; 57 | Util.SimpleCMD(command); 58 | } 59 | catch (Exception ex) { } 60 | } 61 | 62 | /// 63 | /// Detects whether the fake sound file is installed in the registry 64 | /// 65 | [Command("IsSoundInstalledInRegistry")] 66 | public static bool IsSoundInstalledInRegistry() 67 | { 68 | try 69 | { 70 | var command = $"reg query HKCU\\AppEvents\\Schemes\\Apps\\.Default\\Notification.Default\\.Current /t REG_SZ /ve"; 71 | return Util.SimpleCMD(command)?.Contains("TopNotify") ?? false; 72 | } 73 | catch (Exception ex) { } 74 | 75 | return false; 76 | } 77 | 78 | /// 79 | /// Sets The Permissions For The Fake Sound File And Makes Sure It Exists 80 | /// 81 | void EnsureFakeSoundValidity() 82 | { 83 | if (!Directory.Exists(Path.GetDirectoryName(GetCopiedSoundPath(FAKE_SOUND)))) 84 | { 85 | Directory.CreateDirectory(Path.GetDirectoryName(GetCopiedSoundPath(FAKE_SOUND))); 86 | } 87 | 88 | if (!File.Exists(GetCopiedSoundPath(FAKE_SOUND))) 89 | { 90 | // Copy The File If It Doesn't Exist 91 | // This Will Copy The File From The Application Directory (Read Only) To The AppData Directory 92 | // Because We Can't Change Permissions In The Application Directory 93 | File.Copy(GetSourceSoundPath(FAKE_SOUND), GetCopiedSoundPath(FAKE_SOUND)); 94 | } 95 | 96 | // Check Permissions, Add "ALL APPLICATION PACKAGES" If Needed 97 | var fileInfo = new FileInfo(GetCopiedSoundPath(FAKE_SOUND)); 98 | var fileSecurity = fileInfo.GetAccessControl(); 99 | var perms = fileSecurity.GetAccessRules(true, false, typeof(SecurityIdentifier)); 100 | var hasAllAppPerms = false; 101 | 102 | // For Some Reason Linq .Where() Doesn't Work 103 | // Use Normal Iteration 104 | foreach (FileSystemAccessRule perm in perms) 105 | { 106 | if (perm.IdentityReference.Value == "S-1-15-2-1") // S-1-15-2-1 Means All App Packages, https://renenyffenegger.ch/notes/Windows/security/SID/index 107 | { 108 | hasAllAppPerms = true; 109 | } 110 | } 111 | 112 | // Windows Sometimes Won't Play The Sound Unless It Has Permission To Do So, 113 | // Give File Permissions To "ALL APPLICATION PACKAGES" 114 | if (!hasAllAppPerms) 115 | { 116 | try 117 | { 118 | InheritanceFlags iFlags = InheritanceFlags.None; 119 | PropagationFlags pFlags = PropagationFlags.None; 120 | fileSecurity.AddAccessRule(new FileSystemAccessRule("ALL APPLICATION PACKAGES", FileSystemRights.ReadAndExecute, iFlags, pFlags, AccessControlType.Allow)); 121 | fileInfo.SetAccessControl(fileSecurity); 122 | } 123 | catch (Exception ex) 124 | { 125 | 126 | } 127 | } 128 | 129 | allowedToPlaySound = IsSoundInstalledInRegistry(); 130 | 131 | } 132 | 133 | 134 | /// 135 | /// Gets The Path Of A Sound In The AppData Folder 136 | /// 137 | public static string GetCopiedSoundPath(string soundRelativePath) 138 | { 139 | return Path.Combine(Common.Settings.GetAppDataFolder(), "NotificationSounds", soundRelativePath.Replace("/", "\\") + ".wav"); 140 | } 141 | 142 | /// 143 | /// Gets The Path Of The Sound Built In The Application Folder 144 | /// 145 | public static string GetSourceSoundPath(string soundRelativePath) 146 | { 147 | return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "dist", "Audio", soundRelativePath.Replace("/", "\\") + ".wav"); 148 | } 149 | 150 | /// 151 | /// Gets The Path Of The Sound, Automatically Determining If It's In The App Folder, AppData Folder, Or Custom Path 152 | /// 153 | public static string GetSoundPath(string soundPath) 154 | { 155 | if (soundPath == "internal/default") 156 | { 157 | // Find The SoundPath Of The Default AppReference 158 | foreach (var appRef in Settings.Get().AppReferences) 159 | { 160 | if (appRef.ID == "Other" && appRef.SoundPath != "internal/default") 161 | { 162 | return GetSoundPath(appRef.SoundPath); 163 | } 164 | } 165 | } 166 | // Sound Uses A Custom Path 167 | else if (soundPath.StartsWith("custom_sound_path/")) 168 | { 169 | var customPath = soundPath.Replace("custom_sound_path/", ""); 170 | 171 | if (File.Exists(customPath)) 172 | { 173 | return customPath; 174 | } 175 | } 176 | else 177 | { 178 | // Check If Sound Is Present In The AppData Folder 179 | // If It Is, Use That, Else Use The One Inside The App WWW Folder 180 | var copiedPath = GetCopiedSoundPath(soundPath); 181 | var sourcePath = GetSourceSoundPath(soundPath); 182 | 183 | return File.Exists(copiedPath) ? copiedPath : sourcePath; 184 | } 185 | 186 | return GetSourceSoundPath("windows/win11"); 187 | } 188 | 189 | public override void OnNotification(UserNotification notification) 190 | { 191 | if (Settings.ReadAloud || !allowedToPlaySound) { return; } // Don't Play A Sound If Text-To-Speech Is Playing Or If Disabled 192 | 193 | var appRef = AppReference.FromNotification(notification); 194 | var soundFilePath = GetSoundPath(appRef.SoundPath); 195 | 196 | if (!isPlaying) 197 | { 198 | isPlaying = true; 199 | 200 | // Play Sound Without Blocking The Main Thread 201 | Task.Run(() => 202 | { 203 | Player = new SoundPlayer(soundFilePath); 204 | Player.Load(); 205 | Player.PlaySync(); 206 | Player.Dispose(); 207 | isPlaying = false; 208 | }); 209 | } 210 | 211 | base.OnNotification(notification); 212 | } 213 | 214 | /// 215 | /// Plays a sound without any delay or timeout 216 | /// 217 | public static void PlaySoundWithoutTimeout(string soundPath) 218 | { 219 | var soundFilePath = GetSoundPath(soundPath); 220 | 221 | Task.Run(() => 222 | { 223 | using (var p = new SoundPlayer(soundFilePath)) 224 | { 225 | p.Play(); 226 | } 227 | }); 228 | } 229 | 230 | public override void Reflow() 231 | { 232 | base.Reflow(); 233 | } 234 | 235 | public override void Restart() 236 | { 237 | EnsureFakeSoundValidity(); 238 | base.Restart(); 239 | } 240 | 241 | public override void Start() 242 | { 243 | Restart(); 244 | base.Start(); 245 | } 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /TopNotify/Daemon/Language.cs: -------------------------------------------------------------------------------- 1 | using IgniteView.Core; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Globalization; 5 | using System.IO.Enumeration; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace TopNotify.Daemon 11 | { 12 | public class Language 13 | { 14 | /// 15 | /// The Name "New notification" Is Different In Every Language 16 | /// Therefore, To Grab The Notification Window, We Need To Know The Language 17 | /// 18 | public static string NotificationName 19 | { 20 | get 21 | { 22 | if (string.IsNullOrEmpty(_NotificationName)) 23 | { 24 | var currentLanguageID = CultureInfo.CurrentUICulture.Name; 25 | 26 | // Parse the CSV file 27 | var namesCSVFile = File.ReadAllText(Path.Join(AppDomain.CurrentDomain.BaseDirectory, "dist", "Meta", "NotificationNames.csv")); 28 | var namesCSVLines = namesCSVFile.Trim().Split("\n", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); 29 | 30 | // Iterate languages 31 | foreach (var entry in namesCSVLines) { 32 | var entrySplit = entry.Trim().Split(",", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); 33 | var languageID = entrySplit[0]; 34 | var languageNotificationName = entrySplit[1]; 35 | 36 | // In the CSV file, the languages will be stored as xx-*, we need to pattern match them 37 | // Some languages will be stored as their full form like xx-YY, so we still have to check them 38 | if (FileSystemName.MatchesSimpleExpression(languageID, currentLanguageID) || languageID == currentLanguageID) 39 | { 40 | _NotificationName = languageNotificationName; 41 | break; 42 | } 43 | } 44 | } 45 | 46 | return _NotificationName; 47 | } 48 | } 49 | 50 | static string _NotificationName; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /TopNotify/Daemon/ResolutionFinder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Data; 5 | using System.Diagnostics; 6 | using System.Drawing; 7 | using System.Linq; 8 | using System.Runtime.InteropServices; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | using TopNotify.Common; 12 | using Windows.Devices.Display.Core; 13 | using Windows.Media.DialProtocol; 14 | using static TopNotify.Daemon.NativeInterceptor; 15 | 16 | namespace TopNotify.Daemon 17 | { 18 | public class ResolutionFinder 19 | { 20 | #region WinAPI 21 | 22 | 23 | [DllImport("shcore.dll")] 24 | private static extern IntPtr GetDpiForMonitor([In] IntPtr hmonitor, [In] DpiType dpiType, [Out] out uint dpiX, [Out] out uint dpiY); 25 | 26 | [DllImport("user32.dll")] 27 | internal static extern IntPtr MonitorFromPoint([In] Point pt, [In] uint dwFlags); 28 | 29 | [DllImport("user32.dll")] 30 | public static extern bool GetMonitorInfo(IntPtr hMonitor, [In, Out] MonitorInfo lpmi); 31 | 32 | [DllImport("user32.dll", CharSet = CharSet.Unicode)] 33 | static extern bool EnumDisplayDevices(string lpDevice, uint iDevNum, ref DisplayDevice lpDisplayDevice, uint dwFlags); 34 | 35 | [DllImport("user32.dll")] 36 | static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip, EnumMonitorsDelegate lpfnEnum, IntPtr dwData); 37 | 38 | delegate bool EnumMonitorsDelegate(IntPtr hMonitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr dwData); 39 | 40 | 41 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 42 | public struct DisplayDevice 43 | { 44 | [MarshalAs(UnmanagedType.U4)] 45 | public int cb; 46 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] 47 | public string DeviceName; 48 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] 49 | public string DeviceString; 50 | [MarshalAs(UnmanagedType.U4)] 51 | public uint StateFlags; 52 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] 53 | public string DeviceID; 54 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] 55 | public string DeviceKey; 56 | } 57 | 58 | 59 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] 60 | public class MonitorInfo 61 | { 62 | public int Size = 72; 63 | public Rect Monitor = new Rect(); 64 | public Rect WorkArea = new Rect(); 65 | public uint Flags = 0; 66 | 67 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] 68 | public string DeviceName = ""; 69 | } 70 | 71 | [StructLayout(LayoutKind.Sequential)] 72 | public struct Rect 73 | { 74 | public int Left; 75 | public int Top; 76 | public int Right; 77 | public int Bottom; 78 | } 79 | 80 | private enum DpiType 81 | { 82 | Effective = 0, 83 | Angular = 1, 84 | Raw = 2, 85 | } 86 | 87 | #endregion 88 | 89 | public static List GetMonitors() 90 | { 91 | var monitors = new List(); 92 | 93 | EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, 94 | delegate (IntPtr hMonitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr dwData) 95 | { 96 | MonitorInfo currentMonitorInfo = new MonitorInfo(); 97 | 98 | if (GetMonitorInfo(hMonitor, currentMonitorInfo)) 99 | { 100 | monitors.Add(new MonitorData() 101 | { 102 | GraphicsDriverName = "", 103 | FriendlyName = currentMonitorInfo.DeviceName, 104 | ID = currentMonitorInfo.DeviceName, 105 | Path = currentMonitorInfo.DeviceName, 106 | }); 107 | } 108 | return true; 109 | }, IntPtr.Zero); 110 | 111 | return monitors; 112 | } 113 | 114 | /// 115 | /// Returns An HMONITOR IntPtr 116 | /// 117 | public static IntPtr GetPreferredDisplay() 118 | { 119 | var returnedMonitor = IntPtr.Zero; 120 | 121 | // Find Loaded Settings File Or Load It 122 | Settings settingsFile; 123 | 124 | if (InterceptorManager.Instance != null) 125 | { 126 | settingsFile = InterceptorManager.Instance.CurrentSettings; 127 | } 128 | else 129 | { 130 | settingsFile = Settings.Get(); 131 | } 132 | 133 | // Determine Whether To Use A Non-Primary Monitor 134 | if (settingsFile.PreferredMonitor != "primary") 135 | { 136 | // Try To Find The Preferred Monitor 137 | EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, 138 | delegate (IntPtr hMonitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr dwData) 139 | { 140 | MonitorInfo currentMonitorInfo = new MonitorInfo(); 141 | 142 | if (GetMonitorInfo(hMonitor, currentMonitorInfo) && currentMonitorInfo.DeviceName == settingsFile.PreferredMonitor) 143 | { 144 | returnedMonitor = hMonitor; 145 | } 146 | return true; 147 | }, IntPtr.Zero); 148 | } 149 | 150 | // Return Primary Display If returnedMonitor Is 0 151 | return (returnedMonitor != IntPtr.Zero) ? returnedMonitor : MonitorFromPoint(new Point(0, 0), 0x00000001); 152 | } 153 | 154 | public static Rectangle GetScaledResolution() 155 | { 156 | var factor = 1f + (((1f / GetInverseScale()) - 1f) / 2); 157 | var realRes = GetRealResolution(); 158 | 159 | return new Rectangle(0, 0, (int)(realRes.Width * factor), (int)(realRes.Height * factor)); 160 | } 161 | 162 | public static Rectangle GetRealResolution() 163 | { 164 | var display = GetPreferredDisplay(); 165 | MonitorInfo monitorInfo = new MonitorInfo(); 166 | GetMonitorInfo(display, monitorInfo); 167 | return new Rectangle(0, 0, monitorInfo.Monitor.Right - monitorInfo.Monitor.Left, monitorInfo.Monitor.Bottom - monitorInfo.Monitor.Top); 168 | } 169 | 170 | public static float GetInverseScale() 171 | { 172 | uint dpiX; 173 | GetDpiForMonitor(GetPreferredDisplay(), DpiType.Effective, out dpiX, out _); 174 | return 100f / (dpiX * 100 / 96f); 175 | } 176 | 177 | public static float GetScale() 178 | { 179 | return 1f / GetInverseScale(); 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /TopNotify/Finish Build ARM64.bat: -------------------------------------------------------------------------------- 1 | 2 | cd .\bin\production\arm64 3 | signtool sign /f "%USERPROFILE%\Documents\Certificates\SamsidParty Private.pfx" /p %SP_KEY% /fd SHA256 .\TopNotify.exe 4 | 5 | cd ..\..\..\ 6 | 7 | SET F=".\BUILD" 8 | 9 | IF EXIST %F% RMDIR /S /Q %F% 10 | 11 | c:\windows\system32\xcopy.exe ".\MSIX ARM64" .\BUILD /E /H /C /I 12 | c:\windows\system32\xcopy.exe /s .\bin\production\arm64 .\BUILD 13 | 14 | cd .\BUILD 15 | 16 | MakeAppx pack /d .\ /p .\TopNotify.msix 17 | signtool sign /f "%USERPROFILE%\Documents\Certificates\SamsidParty Private.pfx" /p %SP_KEY% /fd SHA256 .\TopNotify.msix -------------------------------------------------------------------------------- /TopNotify/Finish Build Beta.bat: -------------------------------------------------------------------------------- 1 | 2 | cd .\bin\production\x64 3 | signtool sign /f "%USERPROFILE%\Documents\Certificates\SamsidParty Private.pfx" /p %SP_KEY% /fd SHA256 .\TopNotify.exe 4 | 5 | cd ..\..\..\ 6 | 7 | SET F=".\BUILD" 8 | 9 | IF EXIST %F% RMDIR /S /Q %F% 10 | 11 | c:\windows\system32\xcopy.exe ".\MSIX Beta" .\BUILD /E /H /C /I 12 | c:\windows\system32\xcopy.exe /s .\bin\production\x64 .\BUILD 13 | 14 | cd .\BUILD 15 | 16 | MakeAppx pack /d .\ /p .\TopNotify.msix 17 | signtool sign /f "%USERPROFILE%\Documents\Certificates\SamsidParty Private.pfx" /p %SP_KEY% /fd SHA256 .\TopNotify.msix -------------------------------------------------------------------------------- /TopNotify/Finish Build.bat: -------------------------------------------------------------------------------- 1 | 2 | cd .\bin\production\x64 3 | signtool sign /f "%USERPROFILE%\Documents\Certificates\SamsidParty Private.pfx" /p %SP_KEY% /fd SHA256 .\TopNotify.exe 4 | 5 | cd ..\..\..\ 6 | 7 | SET F=".\BUILD" 8 | 9 | IF EXIST %F% RMDIR /S /Q %F% 10 | 11 | c:\windows\system32\xcopy.exe .\MSIX .\BUILD /E /H /C /I 12 | c:\windows\system32\xcopy.exe /s .\bin\production\x64 .\BUILD 13 | 14 | cd .\BUILD 15 | 16 | MakeAppx pack /d .\ /p .\TopNotify.msix 17 | signtool sign /f "%USERPROFILE%\Documents\Certificates\SamsidParty Private.pfx" /p %SP_KEY% /fd SHA256 .\TopNotify.msix -------------------------------------------------------------------------------- /TopNotify/GUI/DragMode.cs: -------------------------------------------------------------------------------- 1 | using IgniteView.Core; 2 | using IgniteView.Desktop; 3 | using Newtonsoft.Json; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Drawing; 7 | using System.Linq; 8 | using System.Runtime.InteropServices; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | using TopNotify.Common; 12 | using TopNotify.Daemon; 13 | using Windows.Security.Credentials; 14 | using static TopNotify.Daemon.ResolutionFinder; 15 | 16 | namespace TopNotify.GUI 17 | { 18 | public class DragModeCommands 19 | { 20 | public static WebWindow DragModeWindow; 21 | 22 | #region WinAPI 23 | 24 | [DllImport("user32.dll")] 25 | public static extern bool GetCursorPos(out Point lpPoint); 26 | 27 | [DllImport("user32.dll", SetLastError = true)] 28 | public static extern IntPtr SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int x, int y, int cx, int cy, int wFlags); 29 | 30 | [DllImport("user32.dll")] 31 | public static extern int ShowCursor(bool bShow); 32 | 33 | [DllImport("user32.dll")] 34 | public static extern bool SetCursorPos(int x, int y); 35 | 36 | [DllImport("user32.dll")] 37 | public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); 38 | 39 | [DllImport("user32.dll")] 40 | public static extern IntPtr SetForegroundWindow(IntPtr hWnd); 41 | 42 | #endregion 43 | 44 | //Called By JavaScript 45 | //Creates A Draggable Window To Position Notifications 46 | [Command("EnterDragMode")] 47 | public static async Task EnterDragMode(WebWindow target) 48 | { 49 | var currentConfig = Settings.Get(); 50 | 51 | //Hide The Main Window 52 | var mainHwnd = target.NativeHandle; 53 | ShowWindow(mainHwnd, 0); 54 | 55 | //Set Mode To Custom Position In Config 56 | currentConfig.Location = NotifyLocation.Custom; 57 | MainCommands.WriteConfigFile(target, JsonConvert.SerializeObject(currentConfig)); 58 | 59 | var windowLocation = new Point((int)(currentConfig.CustomPositionPercentX / 100f * ResolutionFinder.GetRealResolution().Width), (int)(currentConfig.CustomPositionPercentY / 100f * ResolutionFinder.GetRealResolution().Height) + 32); 60 | var windowBounds = new LockedWindowBounds((int)(364f * ResolutionFinder.GetScale()), (int)(109f * ResolutionFinder.GetScale())); 61 | 62 | //Create A Seperate Thread To Lock The Draggable Window To The Cursor Position 63 | var t = new Thread(DragModeThread); 64 | 65 | //Move The Cursor To The Saved Location 66 | ShowCursor(false); 67 | SetCursorPos(windowLocation.X + 30 + (int)(16f * ResolutionFinder.GetScale()), windowLocation.Y + (int)(29f * ResolutionFinder.GetScale())); 68 | 69 | DragModeWindow = 70 | WebWindow.Create("/drag.html") 71 | .WithTitle("") 72 | .WithoutTitleBar() 73 | .WithBounds(windowBounds) 74 | .With((w) => (w as Win32WebWindow).BackgroundMode = Win32WebWindow.WindowBackgroundMode.Acrylic) 75 | .Show(); 76 | 77 | SetForegroundWindow(DragModeWindow.NativeHandle); 78 | t.Start(); 79 | } 80 | 81 | [Command("FocusDragMode")] 82 | public static void FocusDragMode(WebWindow target) 83 | { 84 | SetForegroundWindow(target.NativeHandle); 85 | } 86 | 87 | // Called By JavaScript 88 | // Captures The Draggable Window Position And Writes It To The Config 89 | [Command("ExitDragMode")] 90 | public static void ExitDragMode() 91 | { 92 | if (DragModeWindow != null) 93 | { 94 | var finalWindow = DragModeWindow; 95 | DragModeWindow = null; 96 | 97 | ShowCursor(true); 98 | 99 | //Show The Main Window 100 | var mainWindow = finalWindow.CurrentAppManager.OpenWindows[0]; 101 | var mainHwnd = mainWindow.NativeHandle; 102 | ShowWindow(mainHwnd, 5); 103 | SetForegroundWindow(mainHwnd); 104 | 105 | var hwnd = finalWindow.NativeHandle; 106 | 107 | //Find Position Of Window 108 | Rectangle DragRect = new Rectangle(); 109 | NativeInterceptor.GetWindowRect(hwnd, ref DragRect); 110 | 111 | // Find The Bounds Of The Preferred Monitor 112 | var hMonitor = ResolutionFinder.GetPreferredDisplay(); 113 | MonitorInfo currentMonitorInfo = new MonitorInfo(); 114 | ResolutionFinder.GetMonitorInfo(hMonitor, currentMonitorInfo); 115 | var originX = currentMonitorInfo.Monitor.Left; 116 | var originY = currentMonitorInfo.Monitor.Top; 117 | 118 | // Offset The Bounds Of The Window To Match The Preferred Monitor 119 | DragRect = new Rectangle(DragRect.X - originX, DragRect.Y - originY, DragRect.Width, DragRect.Height); 120 | 121 | //The Window Size Of Notifications Is 396 * 120 Scaled 122 | //The Draw Size Of Notifications Is 364 * 109 Scaled 123 | //Add 32 * 11 Padding 124 | 125 | //Write It To The Config 126 | var currentConfig = Settings.Get(); 127 | currentConfig.CustomPositionPercentX = ((float)DragRect.X - (16f * ResolutionFinder.GetScale())) / (float)ResolutionFinder.GetRealResolution().Width * 100f; 128 | currentConfig.CustomPositionPercentY = ((float)DragRect.Y - (29f * ResolutionFinder.GetScale())) / (float)ResolutionFinder.GetRealResolution().Height * 100f; 129 | MainCommands.WriteConfigFile(mainWindow, JsonConvert.SerializeObject(currentConfig)); 130 | 131 | mainWindow.SendConfig(); 132 | 133 | finalWindow.Close(); 134 | } 135 | } 136 | 137 | //Constantly Updates The Location Of The Draggable Window To The Location Of The Cursor 138 | public static void DragModeThread() 139 | { 140 | Point cursorPos; 141 | IntPtr dragModeHandle = DragModeWindow.NativeHandle; 142 | 143 | while (DragModeWindow != null) 144 | { 145 | //Set The Drag Mode Window To Be At The Cursor's Position 146 | GetCursorPos(out cursorPos); 147 | 148 | //Add Offset To Keep Cursor Inside The Window 149 | //This Prevents The Cursor From Flashing In And Out Due To Being On The Edge Of The Window 150 | cursorPos.X -= 30; 151 | cursorPos.Y -= 30; 152 | 153 | SetWindowPos(dragModeHandle, 0, cursorPos.X, cursorPos.Y, 0, 0, 0x0001); 154 | DragModeWindow?.CallFunction("window.updateCoordinates", cursorPos.X, cursorPos.Y); 155 | 156 | Thread.Sleep(0); 157 | } 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /TopNotify/GUI/MainCommands.cs: -------------------------------------------------------------------------------- 1 | using IgniteView.Core; 2 | using IgniteView.Desktop; 3 | using Newtonsoft.Json; 4 | using SamsidParty_TopNotify.Daemon; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Diagnostics; 8 | using System.Drawing; 9 | using System.Linq; 10 | using System.Reflection.Metadata; 11 | using System.Text; 12 | using System.Threading.Tasks; 13 | using TopNotify.Common; 14 | using TopNotify.Daemon; 15 | using Windows.ApplicationModel.Store; 16 | 17 | namespace TopNotify.GUI 18 | { 19 | public class MainCommands 20 | { 21 | static bool isSaving = false; 22 | 23 | //Called By JavaScript 24 | //Spawns A Test Notification 25 | [Command("SpawnTestNotification")] 26 | public static void SpawnTestNotification() 27 | { 28 | NotificationTester.SpawnTestNotification(); 29 | } 30 | 31 | //Called By JavaScript 32 | //Opens The About Page 33 | [Command("About")] 34 | public static void About() 35 | { 36 | WebWindow.Create("/index.html?about") 37 | .WithTitle("About TopNotify") 38 | .WithBounds(new LockedWindowBounds((int)(400f * ResolutionFinder.GetScale()), (int)(300f * ResolutionFinder.GetScale()))) 39 | .With((w) => (w as Win32WebWindow).BackgroundMode = Win32WebWindow.WindowBackgroundMode.Acrylic) 40 | .WithoutTitleBar() 41 | .Show(); 42 | } 43 | 44 | [Command("Donate")] 45 | public static async void Donate() 46 | { 47 | try 48 | { 49 | var results = await Program.Context.RequestPurchaseAsync("9P92HZT8QC8R"); 50 | 51 | if (results.Status == Windows.Services.Store.StorePurchaseStatus.Succeeded) 52 | { 53 | NotificationTester.MessageBox("Thank You For Donating", "Your contribution is much appreciated ❤️"); 54 | } 55 | else 56 | { 57 | NotificationTester.MessageBox("Failed To Purchase Donation", results.Status.ToString()); 58 | } 59 | } 60 | catch (Exception ex) 61 | { 62 | NotificationTester.MessageBox("Failed To Purchase Donation", ex.Message); 63 | } 64 | } 65 | 66 | [Command("GetVersion")] 67 | public static string GetVersion() 68 | { 69 | // Read version from Appx Manifest 70 | var appxManifest = Path.Join(AppDomain.CurrentDomain.BaseDirectory, "AppxManifest.xml"); 71 | if (File.Exists(appxManifest)) { 72 | var manifestData = File.ReadAllText(appxManifest); 73 | 74 | var from = manifestData.IndexOf("Version=\"") + "Version=\"".Length; 75 | var to = manifestData.LastIndexOf("\""); 76 | 77 | var result = manifestData.Substring(from, to - from); 78 | return " v" + result.Substring(0, 5); 79 | } 80 | 81 | return " Debug"; 82 | } 83 | 84 | //Called By JavaScript 85 | //Sends The Config File 86 | [Command("RequestConfig")] 87 | public static void RequestConfig(WebWindow target) 88 | { 89 | target.SendConfig(); 90 | } 91 | 92 | //Called By JavaScript 93 | //Write Settings File 94 | [Command("WriteConfigFile")] 95 | public static void WriteConfigFile(WebWindow target, string data) 96 | { 97 | 98 | if (isSaving) { return; } 99 | isSaving = true; 100 | 101 | Settings.Overwrite(data); 102 | 103 | Thread.Sleep(100); // Prevent Crashing Daemon From Spamming Button 104 | 105 | // Tell The Daemon The Config Has Changed 106 | Daemon.Daemon.SendCommandToDaemon("UpdateConfig"); 107 | 108 | isSaving = false; 109 | } 110 | 111 | [Command("OpenAppFolder")] 112 | public static async Task OpenAppFolder(WebWindow target) 113 | { 114 | Process.Start("explorer.exe", target.CurrentAppManager.CurrentIdentity.AppDataPath); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /TopNotify/GUI/SoundFinder.cs: -------------------------------------------------------------------------------- 1 | using IgniteView.Core; 2 | using Newtonsoft.Json; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Dynamic; 6 | using System.Linq; 7 | using System.Net; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | using TopNotify.Daemon; 11 | 12 | namespace TopNotify.GUI 13 | { 14 | public class SoundFinder 15 | { 16 | [Command("FindSounds")] 17 | public static string FindSounds() 18 | { 19 | // Read The Current List Of Sound Packs 20 | var jsonFile = File.ReadAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "dist", "Meta", "SoundPacks.json")); 21 | var soundPacks = JsonConvert.DeserializeObject>(jsonFile); 22 | 23 | // Inject Files From Music Folder Into The JSON File 24 | dynamic packToInject = soundPacks.Where((dynamic pack) => pack.ID == "custom_sound_path").FirstOrDefault(); 25 | var wavFiles = GetWAVFilesInMusicFolder(); 26 | 27 | foreach (var wavFile in wavFiles) 28 | { 29 | dynamic soundToInject = new ExpandoObject(); 30 | soundToInject.Path = "custom_sound_path/" + wavFile; 31 | soundToInject.Name = Path.GetFileNameWithoutExtension(wavFile); 32 | soundToInject.Icon = "/Image/Sound.svg"; 33 | packToInject.Sounds.Add(soundToInject); 34 | } 35 | 36 | // Send To GUI 37 | return JsonConvert.SerializeObject(soundPacks); 38 | } 39 | 40 | /// 41 | /// Plays the provided sound ID 42 | /// 43 | [Command("PreviewSound")] 44 | public static void PreviewSound(string soundID) 45 | { 46 | SoundInterceptor.PlaySoundWithoutTimeout(soundID); 47 | } 48 | 49 | /// 50 | /// Returns A List Of WAV Files In The Music Folder 51 | /// 52 | public static string[] GetWAVFilesInMusicFolder() 53 | { 54 | try 55 | { 56 | var musicFolder = Environment.ExpandEnvironmentVariables("%USERPROFILE%\\Music"); 57 | 58 | // Music folder doesn't always exist https://github.com/SamsidParty/TopNotify/issues/40#issuecomment-2692353622 59 | if (Directory.Exists(musicFolder)) 60 | { 61 | return Directory.GetFiles(musicFolder, "*.wav", SearchOption.AllDirectories); 62 | } 63 | } 64 | catch { } 65 | 66 | return new string[0]; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /TopNotify/GUI/TrayIcon.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using TopNotify.Common; 9 | using TopNotify.Daemon; 10 | 11 | namespace TopNotify.GUI 12 | { 13 | public class TrayIcon 14 | { 15 | public static Assembly WinForms; 16 | public static dynamic Application = null; 17 | 18 | 19 | /// 20 | /// Dynamically Loads Winforms And Sets Up A Tray Icon 21 | /// 22 | public static void Setup() 23 | { 24 | WinForms = Assembly.LoadFile(@"C:\Windows\Microsoft.NET\Framework64\v4.0.30319\System.Windows.Forms.dll"); 25 | 26 | AppDomain.CurrentDomain.AssemblyResolve += FindAssembly; 27 | 28 | dynamic notify = null; 29 | dynamic menuStrip = null; 30 | dynamic handler = null; 31 | 32 | //Find WinForms Types 33 | foreach (Type type in WinForms.GetExportedTypes()) 34 | { 35 | if (type.Name == "Application") 36 | { 37 | Application = type.GetMethods() 38 | .Where((method) => method.Name == "Run" && method.IsStatic && method.GetParameters().Length == 0) 39 | .First(); 40 | } 41 | else if (type.Name == "NotifyIcon") 42 | { 43 | // notify = new NotifyIcon(); 44 | notify = Activator.CreateInstance(type); 45 | } 46 | else if (type.Name == "ContextMenuStrip") 47 | { 48 | // menuStrip = new ContextMenuStrip(); 49 | menuStrip = Activator.CreateInstance(type); 50 | } 51 | else if (type.Name == "ToolStripItemClickedEventHandler") 52 | { 53 | // handler = new ToolStripItemClickedEventHandler(Quit); 54 | handler = Delegate.CreateDelegate(type, typeof(TrayIcon).GetMethod(nameof(Quit))); 55 | } 56 | } 57 | 58 | //Use WinForms Methods To Create A Tray Icon 59 | notify.Visible = true; 60 | notify.Icon = Util.FindAppIcon(); 61 | notify.Text = "SamsidParty TopNotify"; 62 | notify.DoubleClick += new EventHandler(LaunchSettingsMode); 63 | notify.ContextMenuStrip = menuStrip; 64 | notify.ContextMenuStrip.Items.Add("Quit TopNotify"); 65 | notify.ContextMenuStrip.ItemClicked += handler; 66 | } 67 | 68 | //Quick And Dirty Method Of Loading WinForms Dependencies 69 | private static Assembly? FindAssembly(object? sender, ResolveEventArgs args) 70 | { 71 | 72 | if (args.Name.StartsWith("Accessibility")) 73 | { 74 | return Assembly.LoadFile(@"C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Accessibility.dll"); 75 | } 76 | 77 | return null; 78 | } 79 | 80 | public static void MainLoop() 81 | { 82 | Application.Invoke(null, null); 83 | } 84 | 85 | 86 | public static void Quit(object Sender, EventArgs e) 87 | { 88 | //Kill Other Instances 89 | var instances = Process.GetProcessesByName("TopNotify"); 90 | foreach (var instance in instances) 91 | { 92 | if (instance.Id != Process.GetCurrentProcess().Id) 93 | { 94 | try 95 | { 96 | instance.Kill(); 97 | } 98 | catch { } 99 | } 100 | } 101 | 102 | 103 | Environment.Exit(0); 104 | } 105 | 106 | public static void LaunchSettingsMode(object Sender, EventArgs e) 107 | { 108 | try 109 | { 110 | var exe = Util.FindExe(); 111 | var psi = new ProcessStartInfo(exe, "--settings" + (Debugger.IsAttached ? " --debug-process" : "")); // Use Debug Args If Needed 112 | psi.UseShellExecute = false; 113 | psi.WorkingDirectory = AppDomain.CurrentDomain.BaseDirectory; 114 | var proc = Process.Start(psi); 115 | } 116 | catch (Exception ex) 117 | { 118 | 119 | } 120 | } 121 | 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /TopNotify/GUI/WallpaperFinder.cs: -------------------------------------------------------------------------------- 1 | using IgniteView.Core; 2 | using MimeMapping; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Net; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using TopNotify.Common; 10 | using WatsonWebserver.Core; 11 | 12 | namespace TopNotify.GUI 13 | { 14 | internal class WallpaperFinder 15 | { 16 | public static async Task WallpaperRoute(HttpContextBase ctx) 17 | { 18 | if ( 19 | ctx.Request.Url != null && 20 | CopyWallpaper() != null 21 | ) 22 | { 23 | 24 | // Send The Current Wallpaper 25 | var wallpaperFile = CopyWallpaper(); 26 | 27 | var fileStream = new FileStream(wallpaperFile, FileMode.Open, FileAccess.Read); 28 | 29 | if (fileStream.CanSeek) 30 | { 31 | ctx.Response.ContentLength = fileStream.Length; 32 | } 33 | 34 | ctx.Response.StatusCode = 200; 35 | ctx.Response.ContentType = "image/jpeg"; 36 | await ctx.Response.Send(fileStream.Length, fileStream); 37 | 38 | await fileStream.DisposeAsync(); 39 | } 40 | 41 | ctx.Response.StatusCode = 404; 42 | } 43 | 44 | public static string CopyWallpaper() 45 | { 46 | //Workaround For File System Write Virtualization 47 | //The UWP Runtime Won't Let Us Read From AppData 48 | //So Call CMD To Copy It Into A Location That We Can Access 49 | 50 | var copiedWallpaperPath = "C:\\Users\\Public\\Downloads\\topnotify_tempwallpaper.jpg"; 51 | 52 | if (File.Exists(copiedWallpaperPath)) 53 | { 54 | return copiedWallpaperPath; 55 | } 56 | 57 | Util.SimpleCMD("copy /b/v/y \"%APPDATA%\\Microsoft\\Windows\\Themes\\TranscodedWallpaper\" \"C:\\Users\\Public\\Downloads\\topnotify_tempwallpaper.jpg\""); 58 | 59 | return File.Exists(copiedWallpaperPath) ? copiedWallpaperPath : null; 60 | } 61 | 62 | public static void CleanUp() 63 | { 64 | var copiedWallpaperPath = "C:\\Users\\Public\\Downloads\\topnotify_tempwallpaper.jpg"; 65 | 66 | if (File.Exists(copiedWallpaperPath)) 67 | { 68 | File.Delete(copiedWallpaperPath); 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /TopNotify/GUI/WindowExtensions.cs: -------------------------------------------------------------------------------- 1 | using IgniteView.Core; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using TopNotify.Common; 8 | 9 | namespace TopNotify.GUI 10 | { 11 | public static class WindowExtensions 12 | { 13 | public static void SendConfig(this WebWindow target) 14 | { 15 | target.CallFunction("SetConfig", Settings.GetForIPC()); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /TopNotify/Install MSIX For Testing.bat: -------------------------------------------------------------------------------- 1 | 2 | SET F=".\BUILD" 3 | 4 | IF EXIST %F% RMDIR /S /Q %F% 5 | 6 | c:\windows\system32\xcopy.exe .\MSIX .\BUILD /E /H /C /I 7 | c:\windows\system32\xcopy.exe /s .\bin\x64\Release\net9.0-windows10.0.17763.0 .\BUILD 8 | 9 | cd .\BUILD 10 | 11 | powershell -c "Add-AppxPackage -Register ./AppxManifest.xml" 12 | 13 | cd ..\ -------------------------------------------------------------------------------- /TopNotify/MSIX ARM64/AppxManifest.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | TopNotify 9 | SamsidParty 10 | None 11 | Assets\StoreLogo.png 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 27 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /TopNotify/MSIX ARM64/AppxMetadata/CodeIntegrity.cat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/MSIX ARM64/AppxMetadata/CodeIntegrity.cat -------------------------------------------------------------------------------- /TopNotify/MSIX ARM64/Assets/Logo150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/MSIX ARM64/Assets/Logo150.png -------------------------------------------------------------------------------- /TopNotify/MSIX ARM64/Assets/Logo44.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/MSIX ARM64/Assets/Logo44.png -------------------------------------------------------------------------------- /TopNotify/MSIX ARM64/Assets/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/MSIX ARM64/Assets/StoreLogo.png -------------------------------------------------------------------------------- /TopNotify/MSIX ARM64/Assets/StoreLogo1080.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/MSIX ARM64/Assets/StoreLogo1080.png -------------------------------------------------------------------------------- /TopNotify/MSIX ARM64/Assets/StoreLogo300.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/MSIX ARM64/Assets/StoreLogo300.png -------------------------------------------------------------------------------- /TopNotify/MSIX ARM64/[Content_Types].xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /TopNotify/MSIX Beta/AppxManifest.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | TopNotify Beta 9 | SamsidParty 10 | None 11 | Assets\StoreLogo.png 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 27 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /TopNotify/MSIX Beta/AppxMetadata/CodeIntegrity.cat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/MSIX Beta/AppxMetadata/CodeIntegrity.cat -------------------------------------------------------------------------------- /TopNotify/MSIX Beta/Assets/Logo150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/MSIX Beta/Assets/Logo150.png -------------------------------------------------------------------------------- /TopNotify/MSIX Beta/Assets/Logo44.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/MSIX Beta/Assets/Logo44.png -------------------------------------------------------------------------------- /TopNotify/MSIX Beta/Assets/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/MSIX Beta/Assets/StoreLogo.png -------------------------------------------------------------------------------- /TopNotify/MSIX Beta/[Content_Types].xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /TopNotify/MSIX/AppxManifest.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | TopNotify 9 | SamsidParty 10 | None 11 | Assets\StoreLogo.png 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 27 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /TopNotify/MSIX/AppxMetadata/CodeIntegrity.cat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/MSIX/AppxMetadata/CodeIntegrity.cat -------------------------------------------------------------------------------- /TopNotify/MSIX/Assets/Logo150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/MSIX/Assets/Logo150.png -------------------------------------------------------------------------------- /TopNotify/MSIX/Assets/Logo44.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/MSIX/Assets/Logo44.png -------------------------------------------------------------------------------- /TopNotify/MSIX/Assets/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/MSIX/Assets/StoreLogo.png -------------------------------------------------------------------------------- /TopNotify/MSIX/Assets/StoreLogo1080.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/MSIX/Assets/StoreLogo1080.png -------------------------------------------------------------------------------- /TopNotify/MSIX/Assets/StoreLogo300.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/MSIX/Assets/StoreLogo300.png -------------------------------------------------------------------------------- /TopNotify/MSIX/[Content_Types].xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /TopNotify/Program.cs: -------------------------------------------------------------------------------- 1 | #define TRACE // Enable Trace.WriteLine 2 | 3 | using System; 4 | using System.Diagnostics; 5 | using System.Drawing; 6 | using Microsoft.Toolkit.Uwp.Notifications; 7 | using TopNotify.Daemon; 8 | using TopNotify.Common; 9 | using TopNotify.GUI; 10 | using IgniteView.Core; 11 | using IgniteView.Desktop; 12 | using System.Reflection; 13 | using System.Runtime.InteropServices; 14 | using Windows.Services.Store; 15 | using Serilog; 16 | using Serilog.Core; 17 | 18 | namespace TopNotify.Common 19 | { 20 | public class Program 21 | { 22 | public static StoreContext Context; 23 | public static Daemon.Daemon Background; 24 | public static AppManager GUI; 25 | public static IEnumerable ValidTopNotifyInstances; 26 | public static Logger Logger; 27 | 28 | public static bool IsDaemonRunning => ValidTopNotifyInstances.Where((p) => { 29 | try 30 | { 31 | string commandLine; 32 | ProcessCommandLine.Retrieve(p, out commandLine, ProcessCommandLine.Parameter.CommandLine); 33 | return !commandLine.ToLower().Contains("--settings"); 34 | } 35 | catch { } 36 | return false; 37 | }).Any(); 38 | 39 | public static bool IsGUIRunning => ValidTopNotifyInstances.Where((p) => { 40 | try 41 | { 42 | string commandLine; 43 | ProcessCommandLine.Retrieve(p, out commandLine, ProcessCommandLine.Parameter.CommandLine); 44 | return commandLine.ToLower().Contains("--settings"); 45 | } 46 | catch { } 47 | return false; 48 | }).Any(); 49 | 50 | [STAThread] 51 | public static void Main(string[] args) 52 | { 53 | AppDomain.CurrentDomain.UnhandledException += (object sender, UnhandledExceptionEventArgs e) => 54 | { 55 | NotificationTester.MessageBox("Something went wrong with TopNotify", "Unfortunately, TopNotify has crashed. Details: " + e.ExceptionObject.ToString()); 56 | }; 57 | 58 | //By Default, The App Will Be Launched In Daemon Mode 59 | //Daemon Mode Is A Background Process That Handles Changing The Position Of Notifications 60 | //If The "--settings" Arg Is Used, Then The App Will Launch In Settings Mode 61 | //Settings Mode Shows A GUI That Can Be Used To Configure The App 62 | //These Mode Switches Ensure All Functions Of The App Use The Same Executable 63 | 64 | //Find Other Instances Of TopNotify 65 | ValidTopNotifyInstances = Process.GetProcessesByName("TopNotify").Where((p) => { 66 | try 67 | { 68 | return !p.HasExited && p.Id != Process.GetCurrentProcess().Id; 69 | } 70 | catch { } 71 | return false; 72 | }); 73 | 74 | var isGUIRunning = IsGUIRunning; 75 | var isDaemonRunning = IsDaemonRunning; 76 | 77 | #if !GUI_DEBUG 78 | if (!args.Contains("--settings") && isDaemonRunning && !isGUIRunning) 79 | { 80 | //Open GUI Instead Of Daemon 81 | TrayIcon.LaunchSettingsMode(null, null); 82 | Environment.Exit(1); 83 | } 84 | else if (args.Contains("--settings") && isGUIRunning) 85 | { 86 | //Exit To Prevent Multiple GUIs 87 | Environment.Exit(2); 88 | } 89 | else if (!args.Contains("--settings") && isDaemonRunning && isGUIRunning) 90 | { 91 | //Exit To Prevent Multiple Daemons 92 | Environment.Exit(3); 93 | } 94 | #endif 95 | 96 | DesktopPlatformManager.Activate(); // Needed here to initiate plugin DLL loading 97 | 98 | #if !GUI_DEBUG 99 | if (args.Contains("--settings")) 100 | #else 101 | if (true) 102 | #endif 103 | { 104 | // Initialize Logging For GUI 105 | Logger = new LoggerConfiguration() 106 | .WriteTo.File(Path.Join(Settings.GetAppDataFolder(), "gui.log"), rollingInterval: RollingInterval.Infinite) 107 | .CreateLogger(); 108 | Logging.WriteWatermark("GUI"); 109 | 110 | // Open The GUI App In Settings Mode 111 | GUI = new ViteAppManager(); 112 | App(); 113 | } 114 | else 115 | { 116 | // Initialize Logging For Daemon 117 | Logger = new LoggerConfiguration() 118 | .WriteTo.File(Path.Join(Settings.GetAppDataFolder(), "daemon.log"), rollingInterval: RollingInterval.Infinite) 119 | .CreateLogger(); 120 | Logging.WriteWatermark("daemon"); 121 | 122 | // Open The Background Daemon 123 | Background = new Daemon.Daemon(); 124 | } 125 | 126 | } 127 | 128 | public static async Task App() 129 | { 130 | // Copy The Wallpaper File So That The GUI Can Access It 131 | WallpaperFinder.CopyWallpaper(); 132 | AppManager.Instance.RegisterDynamicFileRoute("/wallpaper.jpg", WallpaperFinder.WallpaperRoute); 133 | 134 | var mainWindow = 135 | WebWindow.Create() 136 | .WithTitle("TopNotify") 137 | .WithBounds(new LockedWindowBounds((int)(400f * ResolutionFinder.GetScale()), (int)(650f * ResolutionFinder.GetScale()))) 138 | .With((w) => (w as Win32WebWindow).BackgroundMode = Win32WebWindow.WindowBackgroundMode.Acrylic) 139 | .WithoutTitleBar() 140 | .Show(); 141 | 142 | Context = StoreContext.GetDefault(); 143 | WinRT.Interop.InitializeWithWindow.Initialize(Context, mainWindow.NativeHandle); 144 | 145 | // Clean Up 146 | GUI.OnCleanUp += () => 147 | { 148 | WallpaperFinder.CleanUp(); 149 | ToastNotificationManagerCompat.Uninstall(); 150 | }; 151 | 152 | GUI.Run(); 153 | } 154 | 155 | } 156 | } 157 | 158 | -------------------------------------------------------------------------------- /TopNotify/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "TopNotify": { 4 | "commandName": "Project", 5 | "remoteDebugEnabled": false 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /TopNotify/TopNotify.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | net9.0-windows10.0.17763.0 6 | SamsidParty_TopNotify 7 | enable 8 | enable 9 | dist\Image\Icon.ico 10 | dist\Meta\manifest.xml 11 | TopNotify 12 | TopNotify 13 | False 14 | C:\Users\SamarthCat\Documents\Certificates\SamsidParty Private.pfx 15 | OnBuildSuccess 16 | False 17 | AnyCPU;x64;ARM64 18 | Debug;Release;GUI Debug 19 | 20 | 21 | 22 | embedded 23 | 24 | 25 | 26 | embedded 27 | 28 | 29 | 30 | embedded 31 | 32 | 33 | 34 | embedded 35 | 36 | 37 | 38 | embedded 39 | 40 | 41 | 42 | embedded 43 | 44 | 45 | 46 | none 47 | 48 | 49 | 50 | none 51 | 52 | 53 | 54 | none 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | PreserveNewest 91 | true 92 | 93 | 94 | 95 | true 96 | react 97 | 98 | https://raw.githubusercontent.com/SamsidParty/IgniteView/refs/heads/main/IgniteView.Scripts 99 | node -e "fetch('$(ScriptsURL)/Prebuild.js').then((c) => c.text().then(eval))" "$(ScriptsURL)" "$(MSBuildProjectDirectory.Replace('\', '\\'))" "$(Configuration)" "$(JSFramework)" 100 | node -e "fetch('$(ScriptsURL)/Postbuild.js').then((c) => c.text().then(eval))" "$(ScriptsURL)" "$(MSBuildProjectDirectory.Replace('\', '\\'))" "$(Configuration)" "$(JSFramework)" 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /TopNotify/TopNotify.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.4.33205.214 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TopNotify", "TopNotify.csproj", "{F9E7FE42-E9AF-48C2-A17C-72E3315FDD59}" 7 | EndProject 8 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TopNotify.Native", "..\TopNotify.Native\TopNotify.Native.vcxproj", "{28BD2D1B-A248-496F-BA22-5FF2B8412AA3}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|ARM64 = Debug|ARM64 13 | Debug|x64 = Debug|x64 14 | GUI Debug|ARM64 = GUI Debug|ARM64 15 | GUI Debug|x64 = GUI Debug|x64 16 | Release|ARM64 = Release|ARM64 17 | Release|x64 = Release|x64 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {F9E7FE42-E9AF-48C2-A17C-72E3315FDD59}.Debug|ARM64.ActiveCfg = Debug|ARM64 21 | {F9E7FE42-E9AF-48C2-A17C-72E3315FDD59}.Debug|ARM64.Build.0 = Debug|ARM64 22 | {F9E7FE42-E9AF-48C2-A17C-72E3315FDD59}.Debug|x64.ActiveCfg = Debug|x64 23 | {F9E7FE42-E9AF-48C2-A17C-72E3315FDD59}.Debug|x64.Build.0 = Debug|x64 24 | {F9E7FE42-E9AF-48C2-A17C-72E3315FDD59}.GUI Debug|ARM64.ActiveCfg = GUI Debug|ARM64 25 | {F9E7FE42-E9AF-48C2-A17C-72E3315FDD59}.GUI Debug|ARM64.Build.0 = GUI Debug|ARM64 26 | {F9E7FE42-E9AF-48C2-A17C-72E3315FDD59}.GUI Debug|x64.ActiveCfg = GUI Debug|x64 27 | {F9E7FE42-E9AF-48C2-A17C-72E3315FDD59}.GUI Debug|x64.Build.0 = GUI Debug|x64 28 | {F9E7FE42-E9AF-48C2-A17C-72E3315FDD59}.Release|ARM64.ActiveCfg = Release|ARM64 29 | {F9E7FE42-E9AF-48C2-A17C-72E3315FDD59}.Release|ARM64.Build.0 = Release|ARM64 30 | {F9E7FE42-E9AF-48C2-A17C-72E3315FDD59}.Release|x64.ActiveCfg = Release|x64 31 | {F9E7FE42-E9AF-48C2-A17C-72E3315FDD59}.Release|x64.Build.0 = Release|x64 32 | {28BD2D1B-A248-496F-BA22-5FF2B8412AA3}.Debug|ARM64.ActiveCfg = Debug|ARM64 33 | {28BD2D1B-A248-496F-BA22-5FF2B8412AA3}.Debug|ARM64.Build.0 = Debug|ARM64 34 | {28BD2D1B-A248-496F-BA22-5FF2B8412AA3}.Debug|x64.ActiveCfg = Debug|x64 35 | {28BD2D1B-A248-496F-BA22-5FF2B8412AA3}.Debug|x64.Build.0 = Debug|x64 36 | {28BD2D1B-A248-496F-BA22-5FF2B8412AA3}.GUI Debug|ARM64.ActiveCfg = Debug|ARM64 37 | {28BD2D1B-A248-496F-BA22-5FF2B8412AA3}.GUI Debug|ARM64.Build.0 = Debug|ARM64 38 | {28BD2D1B-A248-496F-BA22-5FF2B8412AA3}.GUI Debug|x64.ActiveCfg = Debug|x64 39 | {28BD2D1B-A248-496F-BA22-5FF2B8412AA3}.GUI Debug|x64.Build.0 = Debug|x64 40 | {28BD2D1B-A248-496F-BA22-5FF2B8412AA3}.Release|ARM64.ActiveCfg = Release|ARM64 41 | {28BD2D1B-A248-496F-BA22-5FF2B8412AA3}.Release|ARM64.Build.0 = Release|ARM64 42 | {28BD2D1B-A248-496F-BA22-5FF2B8412AA3}.Release|x64.ActiveCfg = Release|x64 43 | {28BD2D1B-A248-496F-BA22-5FF2B8412AA3}.Release|x64.Build.0 = Release|x64 44 | EndGlobalSection 45 | GlobalSection(SolutionProperties) = preSolution 46 | HideSolutionNode = FALSE 47 | EndGlobalSection 48 | GlobalSection(ExtensibilityGlobals) = postSolution 49 | SolutionGuid = {11B9B45A-D960-44D1-92A4-00A42DE32BDB} 50 | EndGlobalSection 51 | EndGlobal 52 | -------------------------------------------------------------------------------- /TopNotify/iv2runtime/TopNotify.igniteview: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/iv2runtime/TopNotify.igniteview -------------------------------------------------------------------------------- /TopNotify/iv2runtime/win-arm64/native/TopNotify.Native.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/iv2runtime/win-arm64/native/TopNotify.Native.dll -------------------------------------------------------------------------------- /TopNotify/iv2runtime/win-arm64/native/TopNotify.Native.exp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/iv2runtime/win-arm64/native/TopNotify.Native.exp -------------------------------------------------------------------------------- /TopNotify/iv2runtime/win-arm64/native/TopNotify.Native.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/iv2runtime/win-arm64/native/TopNotify.Native.lib -------------------------------------------------------------------------------- /TopNotify/iv2runtime/win-x64/native/TopNotify.Native.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/iv2runtime/win-x64/native/TopNotify.Native.dll -------------------------------------------------------------------------------- /TopNotify/iv2runtime/win-x64/native/TopNotify.Native.exp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/iv2runtime/win-x64/native/TopNotify.Native.exp -------------------------------------------------------------------------------- /TopNotify/iv2runtime/win-x64/native/TopNotify.Native.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/iv2runtime/win-x64/native/TopNotify.Native.lib -------------------------------------------------------------------------------- /TopNotify/src-vite/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:react/recommended', 7 | 'plugin:react/jsx-runtime', 8 | 'plugin:react-hooks/recommended', 9 | ], 10 | ignorePatterns: ['dist', '.eslintrc.cjs'], 11 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, 12 | settings: { react: { version: '18.2' } }, 13 | plugins: ['react-refresh'], 14 | rules: { 15 | 'react/jsx-no-target-blank': 'off', 16 | 'react-refresh/only-export-components': [ 17 | 'warn', 18 | { allowConstantExport: true }, 19 | ], 20 | }, 21 | } 22 | -------------------------------------------------------------------------------- /TopNotify/src-vite/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /TopNotify/src-vite/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /TopNotify/src-vite/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "topnotify", 3 | "private": false, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build --emptyOutDir", 9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@chakra-ui/react": "^2.8.2", 14 | "@emotion/react": "^11.11.4", 15 | "@emotion/styled": "^11.11.5", 16 | "framer-motion": "^11.1.7", 17 | "react": "^18.2.0", 18 | "react-dom": "^18.2.0" 19 | }, 20 | "devDependencies": { 21 | "@types/react": "^18.2.66", 22 | "@types/react-dom": "^18.2.22", 23 | "@vitejs/plugin-react-swc": "^3.5.0", 24 | "eslint": "^8.57.0", 25 | "eslint-plugin-react": "^7.34.1", 26 | "eslint-plugin-react-hooks": "^4.6.0", 27 | "eslint-plugin-react-refresh": "^0.4.6", 28 | "vite": "^5.2.0", 29 | "vite-plugin-transform": "^2.0.1" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /TopNotify/src-vite/public/Audio/internal/silent.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/src-vite/public/Audio/internal/silent.wav -------------------------------------------------------------------------------- /TopNotify/src-vite/public/Audio/windows/win10.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/src-vite/public/Audio/windows/win10.wav -------------------------------------------------------------------------------- /TopNotify/src-vite/public/Audio/windows/win10alt.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/src-vite/public/Audio/windows/win10alt.wav -------------------------------------------------------------------------------- /TopNotify/src-vite/public/Audio/windows/win11.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/src-vite/public/Audio/windows/win11.wav -------------------------------------------------------------------------------- /TopNotify/src-vite/public/Audio/windows/win7.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/src-vite/public/Audio/windows/win7.wav -------------------------------------------------------------------------------- /TopNotify/src-vite/public/Audio/windows/winxp.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/src-vite/public/Audio/windows/winxp.wav -------------------------------------------------------------------------------- /TopNotify/src-vite/public/Audio/windows/winxp_error.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/src-vite/public/Audio/windows/winxp_error.wav -------------------------------------------------------------------------------- /TopNotify/src-vite/public/Font/InterVariable.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/src-vite/public/Font/InterVariable.woff2 -------------------------------------------------------------------------------- /TopNotify/src-vite/public/Font/Tabler.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/src-vite/public/Font/Tabler.ttf -------------------------------------------------------------------------------- /TopNotify/src-vite/public/Image/BackgroundDark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/src-vite/public/Image/BackgroundDark.png -------------------------------------------------------------------------------- /TopNotify/src-vite/public/Image/BackgroundDecoration.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/src-vite/public/Image/BackgroundDecoration.jpg -------------------------------------------------------------------------------- /TopNotify/src-vite/public/Image/BackgroundLight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/src-vite/public/Image/BackgroundLight.png -------------------------------------------------------------------------------- /TopNotify/src-vite/public/Image/Blank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/src-vite/public/Image/Blank.png -------------------------------------------------------------------------------- /TopNotify/src-vite/public/Image/DefaultAppReferenceIcon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /TopNotify/src-vite/public/Image/Icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/src-vite/public/Image/Icon.ico -------------------------------------------------------------------------------- /TopNotify/src-vite/public/Image/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/src-vite/public/Image/Icon.png -------------------------------------------------------------------------------- /TopNotify/src-vite/public/Image/IconSmall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/src-vite/public/Image/IconSmall.png -------------------------------------------------------------------------------- /TopNotify/src-vite/public/Image/IconTiny.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/src-vite/public/Image/IconTiny.png -------------------------------------------------------------------------------- /TopNotify/src-vite/public/Image/LeftClick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/src-vite/public/Image/LeftClick.png -------------------------------------------------------------------------------- /TopNotify/src-vite/public/Image/NoSound.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /TopNotify/src-vite/public/Image/NotificationPreview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/src-vite/public/Image/NotificationPreview.png -------------------------------------------------------------------------------- /TopNotify/src-vite/public/Image/PartyWordmarkIconMono.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/src-vite/public/Image/PartyWordmarkIconMono.png -------------------------------------------------------------------------------- /TopNotify/src-vite/public/Image/Sound.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /TopNotify/src-vite/public/Image/Taskbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/src-vite/public/Image/Taskbar.png -------------------------------------------------------------------------------- /TopNotify/src-vite/public/Image/ThirdParty/Discord.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TopNotify/src-vite/public/Image/ThirdParty/Outlook.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /TopNotify/src-vite/public/Image/ThirdParty/WhatsApp.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /TopNotify/src-vite/public/Image/ThirdParty/Windows10.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /TopNotify/src-vite/public/Image/ThirdParty/Windows11.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /TopNotify/src-vite/public/Image/ThirdParty/WindowsXP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamsidParty/TopNotify/9bb0846ad703d4a26f676b43d3da030c0724fd71/TopNotify/src-vite/public/Image/ThirdParty/WindowsXP.png -------------------------------------------------------------------------------- /TopNotify/src-vite/public/Meta/AppxManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | TopNotify 9 | SamsidParty 10 | None 11 | Assets\StoreLogo.png 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /TopNotify/src-vite/public/Meta/NotificationNames.csv: -------------------------------------------------------------------------------- 1 | en-*,New notification 2 | nl-*,Nieuwe melding 3 | fr-*,Nouvelle notification 4 | he-*,הודעה חדשה 5 | es-ES,Notificación nueva 6 | es-*,Nueva notificación 7 | ja-*,新しい通知 8 | pt-*,Nova notificação 9 | de-*,Neue Benachrichtigung 10 | zh-*,新通知 11 | it-*,Nuova notifica 12 | pl-*,Nowe powiadomienie 13 | sv-*,Ny avisering 14 | da-*,Ny meddelelse 15 | no-*,Ny melding 16 | hu-*,Új értesítés 17 | ru-*,Новое уведомление 18 | hi-*,नई सूचना 19 | ko-*,새로운 알림 20 | sk-*,Nové oznámenie 21 | *,null 22 | -------------------------------------------------------------------------------- /TopNotify/src-vite/public/Meta/SoundPacks.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Name": "Your Collection", 4 | "Description": ".WAV files from your music folder will show up here", 5 | "ID": "custom_sound_path", 6 | "Sounds": [ 7 | { 8 | "Path": "internal/silent", 9 | "Name": "No Sound", 10 | "Icon": "/Image/NoSound.svg" 11 | } 12 | ] 13 | }, 14 | { 15 | "Name": "Windows Sounds", 16 | "Description": "Includes sounds from both modern & classic versions", 17 | "ID": "windows", 18 | "Sounds": [ 19 | { 20 | "Path": "windows/win11", 21 | "Name": "Notify 11", 22 | "Icon": "/Image/ThirdParty/Windows11.svg" 23 | }, 24 | { 25 | "Path": "windows/win10", 26 | "Name": "Notify 10", 27 | "Icon": "/Image/ThirdParty/Windows10.svg" 28 | }, 29 | { 30 | "Path": "windows/win10alt", 31 | "Name": "Notify 10 Alt", 32 | "Icon": "/Image/ThirdParty/Windows10.svg" 33 | }, 34 | { 35 | "Path": "windows/win7", 36 | "Name": "Notify 7", 37 | "Icon": "/Image/ThirdParty/Windows7.svg" 38 | }, 39 | { 40 | "Path": "windows/winxp", 41 | "Name": "Notify XP", 42 | "Icon": "/Image/ThirdParty/WindowsXP.png" 43 | }, 44 | { 45 | "Path": "windows/winxp_error", 46 | "Name": "Error XP", 47 | "Icon": "/Image/ThirdParty/WindowsXP.png" 48 | } 49 | ] 50 | } 51 | ] -------------------------------------------------------------------------------- /TopNotify/src-vite/public/Meta/WinGet/SamsidParty.TopNotifyWG.installer.yaml: -------------------------------------------------------------------------------- 1 | PackageIdentifier: "SamsidParty.TopNotifyWG" 2 | PackageVersion: "%{TOPNOTIFY_VERSION}%" 3 | Platform: 4 | - "Windows.Desktop" 5 | MinimumOSVersion: "10.0.19041.0" 6 | InstallerType: "msix" 7 | InstallModes: 8 | - "silent" 9 | PackageFamilyName: "55968SamsidGameStudios.TopNotify_r9j5xrxak4zje" 10 | Installers: 11 | - Architecture: "x64" 12 | InstallerUrl: "https://github.com/SamsidParty/TopNotify/releases/download/%{TOPNOTIFY_VERSION}%/TopNotify.Msix" 13 | InstallerSha256: 1ab364e2ed182e0c879fd9ec7a730aafa47fd441facac8b5adccf77767026917 14 | ManifestType: "installer" 15 | ManifestVersion: "1.6.0" -------------------------------------------------------------------------------- /TopNotify/src-vite/public/Meta/WinGet/SamsidParty.TopNotifyWG.locale.en-US.yaml: -------------------------------------------------------------------------------- 1 | PackageIdentifier: "SamsidParty.TopNotifyWG" 2 | PackageVersion: "%{TOPNOTIFY_VERSION}%" 3 | PackageLocale: "en-US" 4 | Publisher: "SamsidParty" 5 | PublisherUrl: "https://www.samsidparty.com/" 6 | PrivacyUrl: "https://www.samsidparty.com/docs/support/privacy" 7 | PackageName: "TopNotify" 8 | PackageUrl: "https://www.samsidparty.com/software/topnotify" 9 | License: "GPLv3" 10 | LicenseUrl: "https://github.com/SamsidParty/TopNotify/blob/main/LICENSE" 11 | ShortDescription: "The Ultimate Notification Customization Tool" 12 | ManifestType: "defaultLocale" 13 | ManifestVersion: "1.6.0" -------------------------------------------------------------------------------- /TopNotify/src-vite/public/Meta/WinGet/SamsidParty.TopNotifyWG.yaml: -------------------------------------------------------------------------------- 1 | PackageIdentifier: "SamsidParty.TopNotifyWG" 2 | PackageVersion: "%{TOPNOTIFY_VERSION}%" 3 | DefaultLocale: "en-US" 4 | ManifestType: "version" 5 | ManifestVersion: "1.6.0" -------------------------------------------------------------------------------- /TopNotify/src-vite/public/Meta/manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | Display Notifications At The Top Of The Screen 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | true 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /TopNotify/src-vite/public/drag.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 64 | 65 | 66 | 67 | 68 |
69 |

70 |
71 | 72 |
73 |

Drag to desired position

74 |

Left-click to confirm

75 |
76 |
77 | 78 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /TopNotify/src-vite/src/About.jsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@chakra-ui/react"; 2 | import "./CSS/About.css"; 3 | import { useState } from "react"; 4 | 5 | export default function About() { 6 | 7 | var [version, setVersion] = useState(""); 8 | 9 | if (version == "") { 10 | setVersion(" ..."); 11 | 12 | setTimeout(async () => { 13 | setVersion(await igniteView.commandBridge.GetVersion()); 14 | }); 15 | } 16 | 17 | return ( 18 |
19 |
20 |

About

21 |
22 | 23 |
24 | 25 |
26 | 27 | 28 |

TopNotify{version}

29 |
Developed by SamsidParty • Powered by IgniteView
30 | 31 |
32 | 33 | 34 | 35 | 36 | 37 |
38 |
39 | ) 40 | } -------------------------------------------------------------------------------- /TopNotify/src-vite/src/App.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import { Button, Switch, Divider, Container } from '@chakra-ui/react' 3 | import NotificationTransparency from './Transparency' 4 | import ClickThrough from './ClickThrough' 5 | import TestNotification from './TestNotification' 6 | import ManageNotificationSounds from './NotificationSounds' 7 | import Preview from './Preview' 8 | import { DebugMenu } from './DebugMenu' 9 | import ReadAloud from './ReadAloud' 10 | import MonitorSelect from './MonitorSelect' 11 | import SoundInterceptionToggle from './SoundInterceptionToggle' 12 | import { useFirstRender } from './Helper' 13 | 14 | 15 | window.Config = { 16 | Location: -1, 17 | Opacity: 0, 18 | ReadAloud: false, 19 | AppReferences: [] 20 | } 21 | 22 | // Called By C#, Sets The window.Config Object To The Saved Config File 23 | window.SetConfig = async (config) => { 24 | Config = JSON.parse(config); 25 | window.setRerender(rerender + 1); 26 | } 27 | 28 | window.UploadConfig = () => { 29 | 30 | if (Config.Location == -1) { 31 | //Config Hasn't Loaded Yet 32 | return; 33 | } 34 | 35 | igniteView.commandBridge.WriteConfigFile(JSON.stringify(Config)); 36 | window.setRerender(rerender + 1); 37 | } 38 | 39 | window.ChangeSwitch = function (key, e) { 40 | Config[key] = e.target.checked; 41 | UploadConfig(); 42 | window.setRerender(rerender + 1); 43 | } 44 | 45 | window.ChangeValue = function (key, e) { 46 | Config[key] = e; 47 | UploadConfig(); 48 | window.setRerender(rerender + 1); 49 | } 50 | 51 | function App() { 52 | 53 | var [rerender, setRerender] = useState(0); 54 | window.rerender = rerender; 55 | window.setRerender = setRerender; 56 | 57 | if (useFirstRender()) { 58 | igniteView.commandBridge.invoke("RequestConfig"); 59 | } 60 | 61 | return ( 62 |
0) ? " loaded" : "")}> 63 | 64 | 65 | 66 |
67 | 68 |

TopNotify

69 |
70 | 71 |
72 | 73 |
74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | { 82 | window.errorList?.map((error, i) => { 83 | return () 84 | }) 85 | } 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 |
102 | 103 | 104 |
105 |
106 | ) 107 | } 108 | 109 | function ErrorMessage(props) { 110 | return ( 111 |

{props.error.Text}
112 | ) 113 | } 114 | 115 | 116 | export default App 117 | -------------------------------------------------------------------------------- /TopNotify/src-vite/src/CSS/About.css: -------------------------------------------------------------------------------- 1 | .aboutButtons { 2 | display: block; 3 | justify-content: center; 4 | align-items: center; 5 | text-align: center; 6 | width: 100%; 7 | height: auto; 8 | } 9 | 10 | .aboutButtons button { 11 | margin: 2px; 12 | } 13 | 14 | .about .draggableHeader { 15 | 16 | } 17 | 18 | .about h4 { 19 | font-size: 24px; 20 | text-align: center; 21 | font-weight: 600; 22 | letter-spacing: -0.05em; 23 | } 24 | 25 | .about h6 { 26 | font-size: 12px; 27 | text-align: center; 28 | font-weight: 400; 29 | margin-top: 5px; 30 | margin-bottom: 5px; 31 | } 32 | 33 | .about img { 34 | width: 15vw; 35 | height: auto; 36 | margin: auto; 37 | } -------------------------------------------------------------------------------- /TopNotify/src-vite/src/CSS/App.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: Main; 3 | size-adjust: 110%; 4 | src: url("/Font/InterVariable.woff2"); 5 | } 6 | 7 | @font-face { 8 | font-family: Icon; 9 | src: url("/Font/Tabler.ttf"); 10 | } 11 | 12 | 13 | .app { 14 | transition: opacity 0.3s, transform 0.3s; 15 | opacity: 0; 16 | transform: scale(0.95); 17 | position: relative; 18 | } 19 | 20 | .app.loaded { 21 | opacity: 1; 22 | height: calc(100vh - 27px); 23 | transform: scale(1); 24 | } 25 | 26 | * { 27 | font-family: Main, Icon; 28 | color: var(--col-text); 29 | box-sizing: border-box; 30 | user-select: none; 31 | -webkit-user-select: none; 32 | font-size: 11px; 33 | 34 | --col-bg: white; 35 | --col-overlay: rgba(255, 255, 255, 0.5); 36 | --col-text: #111111; 37 | --col-text-inverted: #F1ECF3; 38 | --col-text-overlay: rgb(107 114 128); 39 | --col-primary: #5536e0; 40 | --col-primary-hover: #492fbb; 41 | --col-secondary: #acee3a; 42 | --col-tertiary: #ffffffab; 43 | --col-contrast: #ebebeb; 44 | --col-border: #bbbbbb; 45 | --col-disabled: #251F29; 46 | --image-bg: url("/Image/BackgroundLight.png"); 47 | 48 | image-rendering: auto; 49 | } 50 | 51 | @media (prefers-color-scheme: dark) { 52 | * { 53 | --col-bg: #222222; 54 | --col-overlay: rgba(0, 0, 0, 0.5); 55 | --col-text: #F1ECF3; 56 | --col-text-inverted: #111111; 57 | --col-text-overlay: rgb(107 114 128); 58 | --col-primary: #767df9; 59 | --col-primary-hover: #6268d3; 60 | --col-secondary: #BEF264; 61 | --col-tertiary: #50505031; 62 | --col-contrast: #343434; 63 | --col-disabled: #251F29; 64 | --col-border: #433e46; 65 | --image-bg: url("/Image/BackgroundDark.png"); 66 | } 67 | } 68 | 69 | *[data-greyed-out="true"] { 70 | pointer-events: none; 71 | opacity: 0.5; 72 | } 73 | 74 | html, body { 75 | width: 100vw; 76 | height: 100vh; 77 | position: relative; 78 | display: flex; 79 | overflow: hidden; 80 | } 81 | 82 | body { 83 | flex-direction: column; 84 | padding: 1.5rem; 85 | padding-top: 7px; 86 | height: 100vh; 87 | width: 100vw; 88 | background-image: var(--image-bg) !important; 89 | background-size: 120vw !important; 90 | background-repeat: no-repeat; 91 | image-rendering: pixelated; 92 | } 93 | 94 | 95 | 96 | .errorMessage { 97 | height: 40px; 98 | border-radius: 5px; 99 | background-color: rgba(0, 0, 0, 0.2); 100 | display: flex; 101 | align-items: center; 102 | font-size: 12px; 103 | padding-left: 10px; 104 | gap: 10px; 105 | margin-top: 12px; 106 | } 107 | 108 | .errorMessage h4 { 109 | font-size: 20px; 110 | color: var(--col-secondary); 111 | } 112 | 113 | .errorMessage.medium { 114 | height: 60px; 115 | } 116 | 117 | .footer { 118 | position: absolute; 119 | width: 100%; 120 | height: 40px; 121 | bottom: 0; 122 | border-radius: 5px; 123 | background-color: var(--col-tertiary); 124 | display: flex; 125 | align-items: center; 126 | } 127 | 128 | .footerLogo { 129 | height: 50%; 130 | margin-left: 10px; 131 | opacity: 0.8; 132 | filter: invert(100); 133 | } 134 | 135 | 136 | @media (prefers-color-scheme: dark) { 137 | .footerLogo { 138 | filter: none; 139 | opacity: 0.6; 140 | } 141 | } 142 | 143 | 144 | .draggableHeader { 145 | display: flex; 146 | margin-right: 15%; 147 | align-items: center; 148 | gap: 0.5rem; 149 | margin-bottom: 4.3vw; 150 | } 151 | 152 | .draggableHeader img { 153 | height: 8vw; 154 | width: auto; 155 | } 156 | 157 | .windowCloseButton { 158 | position: absolute; 159 | right: 0px; 160 | top: 2vw; 161 | } 162 | 163 | .windowCloseButton button { 164 | background-color: rgba(255, 255, 255, 0.06) !important; 165 | color: var(--col-text) !important; 166 | font-weight: 400 !important; 167 | border-radius: 3px !important; 168 | } 169 | 170 | .buttonContainer button { 171 | font-weight: 550 !important; 172 | border-radius: 3px !important; 173 | } 174 | 175 | label { 176 | font-size: 1rem; 177 | } 178 | 179 | button { 180 | width: auto; 181 | height: 25px; 182 | padding: 14px !important; 183 | font-size: 1rem !important; 184 | font-weight: 400 !important; 185 | color: var(--col-text) !important; 186 | border-radius: 3px !important; 187 | } 188 | 189 | button:hover { 190 | opacity: 0.8; 191 | } 192 | 193 | .iconButton { 194 | width: 2rem !important; 195 | min-width: 0px !important; 196 | height: 2rem !important; 197 | padding: 2px !important; 198 | font-size: 1.2rem !important; 199 | color: white !important; 200 | background-color: var(--col-primary) !important; 201 | } 202 | 203 | 204 | *::-webkit-scrollbar { 205 | background: transparent; 206 | } 207 | 208 | *::-webkit-scrollbar-thumb { 209 | height: 56px; 210 | border-radius: 8px; 211 | border: 4px solid transparent; 212 | background-clip: content-box; 213 | background-color: var(--text-color); 214 | opacity: 0.5; 215 | } 216 | 217 | .flexx { 218 | display: flex; 219 | flex-direction: row; 220 | } 221 | 222 | .flexy { 223 | display: flex; 224 | flex-direction: column; 225 | } 226 | 227 | .fillx { 228 | width: 100%; 229 | } 230 | 231 | .filly { 232 | height: 100%; 233 | } 234 | 235 | .fjend { 236 | justify-content: flex-end !important; 237 | } 238 | 239 | .fjcenter { 240 | justify-content: center !important; 241 | } 242 | 243 | .fjstart { 244 | justify-content: flex-start !important; 245 | } 246 | 247 | .faend { 248 | align-items: flex-end !important; 249 | } 250 | 251 | .facenter { 252 | align-items: center !important; 253 | } 254 | 255 | .fastart { 256 | align-items: flex-start !important; 257 | } 258 | 259 | .gap20 { 260 | gap: 2rem; 261 | } 262 | 263 | .gap15 { 264 | gap: 1.5rem; 265 | } 266 | 267 | .gap10 { 268 | gap: 1rem; 269 | } 270 | 271 | .gap5 { 272 | gap: 0.5rem; 273 | } 274 | 275 | .tacenter { 276 | text-align: center; 277 | } 278 | 279 | .taleft { 280 | text-align: left; 281 | } -------------------------------------------------------------------------------- /TopNotify/src-vite/src/CSS/ChakraOverrides.css: -------------------------------------------------------------------------------- 1 | * { 2 | --chakra-colors-blue-500: var(--col-primary) !important; 3 | --chakra-colors-blue-200: var(--col-primary) !important; 4 | --chakra-colors-gray-700: var(--col-bg) !important; 5 | --chakra-colors-chakra-border-color: var(--col-border) !important; 6 | --chakra-colors-whiteAlpha-400: var(--chakra-colors-whiteAlpha-200) !important; 7 | --drawer-bg: transparent !important; 8 | --drawer-box-shadow: none !important; 9 | } 10 | 11 | .chakra-modal__content-container .windowCloseButton { 12 | margin-right: 5vw; 13 | margin-top: 1vh; 14 | } 15 | 16 | .chakra-button, .chakra-button * { 17 | transition: none !important; 18 | } 19 | 20 | h2, .chakra-modal__header { 21 | margin: 0 !important; 22 | line-height: 11vw !important; 23 | font-size: 6.5vw !important; 24 | font-weight: 600 !important; 25 | letter-spacing: -0.05em !important; 26 | } 27 | 28 | .chakra-modal__header { 29 | padding-top: 1.5vw !important; 30 | padding-bottom: 1.5vw !important; 31 | margin-right: 10vw !important; 32 | } 33 | 34 | .chakra-divider { 35 | width: 100%; 36 | margin-top: 0.5rem; 37 | margin-bottom: 0.5rem; 38 | } 39 | 40 | .chakra-modal__content { 41 | height: 100%; 42 | width: 100%; 43 | } 44 | 45 | .chakra-container { 46 | padding: 10px !important; 47 | border: 1px solid var(--col-border); 48 | border-radius: 5px; 49 | margin-top: 10px; 50 | margin-bottom: 10px; 51 | } -------------------------------------------------------------------------------- /TopNotify/src-vite/src/CSS/NotificationSounds.css: -------------------------------------------------------------------------------- 1 | .appReferenceSoundItem { 2 | display: flex; 3 | gap: 10px; 4 | align-items: center; 5 | } 6 | 7 | .appReferenceSoundItem .selectSoundButton { 8 | margin-left: auto; 9 | width: 200px; 10 | } 11 | 12 | .appReferenceSoundItem .selectSoundButton * { 13 | float: right; 14 | } 15 | 16 | .appReferenceSoundItem img { 17 | width: 24px; 18 | height: 24px; 19 | } 20 | 21 | .soundPackList { 22 | display: flex; 23 | justify-content: center; 24 | align-items: center; 25 | flex-direction: column; 26 | gap: 15px; 27 | } 28 | 29 | .soundPack { 30 | background-color: var(--col-tertiary); 31 | border-radius: 5px; 32 | display: flex; 33 | flex-direction: column; 34 | padding: 15px; 35 | width: 100%; 36 | overflow: hidden; 37 | } 38 | 39 | .soundPack h3 { 40 | font-size: 1.3rem; 41 | font-weight: 500; 42 | letter-spacing: -0.05em; 43 | } 44 | 45 | .soundPack h4 { 46 | font-size: 1rem; 47 | font-weight: 400; 48 | letter-spacing: -0.05em; 49 | opacity: 0.6; 50 | } 51 | 52 | .soundList { 53 | display: grid; 54 | justify-content: center; 55 | align-items: center; 56 | grid-template-columns: 1fr 1fr 1fr; 57 | gap: 12px; 58 | } 59 | 60 | .soundItem { 61 | transition: opacity 0.3s; 62 | display: flex; 63 | flex-direction: column; 64 | justify-content: flex-end; 65 | align-items: center; 66 | min-width: 23vw; 67 | max-width: 23vw; 68 | min-height: 23vw; 69 | max-height: 23vw; 70 | background-color: var(--col-tertiary); 71 | border-radius: 5px; 72 | } 73 | 74 | .soundItem .soundItemButton { 75 | width: 100%; 76 | height: calc(17vw); 77 | } 78 | 79 | .soundItem img { 80 | height: 60px; 81 | } 82 | 83 | .soundItem .iconButton { 84 | width: 1.5rem !important; 85 | height: 1.5rem !important; 86 | padding: 0 !important; 87 | font-weight: 400 !important; 88 | } 89 | 90 | .soundItem h5 { 91 | margin: auto; 92 | font-size: 0.8rem; 93 | } -------------------------------------------------------------------------------- /TopNotify/src-vite/src/CSS/Preview.css: -------------------------------------------------------------------------------- 1 | .previewContainer { 2 | width: 100%; 3 | background-image: url("/Image/BackgroundDecoration.jpg"); 4 | background-size: cover; 5 | background-repeat: no-repeat; 6 | background-position: center; 7 | border-radius: 5px; 8 | border-bottom-right-radius: 0px; 9 | border-bottom-left-radius: 0px; 10 | position: relative; 11 | margin-top: 10px; 12 | overflow: hidden; 13 | display: flex; 14 | justify-content: center; 15 | align-items: center; 16 | } 17 | 18 | 19 | .previewContainer .taskbarPreview { 20 | position: absolute; 21 | bottom: 0; 22 | width: 100%; 23 | background-color: var(--col-overlay); 24 | display: flex; 25 | align-items: center; 26 | justify-content: center; 27 | } 28 | 29 | .previewContainer .taskbarPreview img { 30 | height: 80%; 31 | } 32 | 33 | .previewContainer .notificationWindowPreview { 34 | position: absolute; 35 | display: flex; 36 | justify-content: center; 37 | align-items: center; 38 | } 39 | 40 | .previewContainer .notificationWindowPreview .notificationPreview { 41 | background-color: var(--col-text-inverted); 42 | border-radius: 5px; 43 | } 44 | 45 | .previewContainer .notificationWindowPreview .notificationPreview img { 46 | height: 100%; 47 | width: auto; 48 | filter: invert(100%); 49 | } 50 | 51 | .previewContainer .locationSelect { 52 | border-radius: 5px; 53 | display: grid; 54 | grid-template-columns: 1fr 1fr; 55 | gap: 2.2rem; 56 | } 57 | 58 | .previewContainer .locationSelectButton { 59 | background-color: var(--col-overlay) !important; 60 | width: 2.5rem; 61 | height: 2.5rem; 62 | font-size: 1.8rem !important; 63 | font-weight: 100; 64 | } 65 | 66 | .previewContainer .locationSelectButton:hover { 67 | opacity: 0.8; 68 | } 69 | 70 | .previewContainer .customLocationSelectButton { 71 | position: absolute; 72 | margin: auto; 73 | top: 0; 74 | left: 0; 75 | right: 0; 76 | bottom: 0; 77 | } 78 | 79 | .monitorSelect { 80 | border-top-left-radius: 0px !important; 81 | border-top-right-radius: 0px !important; 82 | } 83 | 84 | @media (prefers-color-scheme: dark) { 85 | .previewContainer .notificationWindowPreview .notificationPreview img { 86 | filter: none; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /TopNotify/src-vite/src/ClickThrough.jsx: -------------------------------------------------------------------------------- 1 | 2 | import { Switch } from '@chakra-ui/react' 3 | 4 | export default function ClickThrough() { 5 | 6 | 7 | return ( 8 |
9 | 10 | ChangeSwitch("EnableClickThrough", e)} isChecked={Config.EnableClickThrough} style={{ marginLeft: "auto" }} size='lg' /> 11 |
12 | ) 13 | } -------------------------------------------------------------------------------- /TopNotify/src-vite/src/DebugMenu.jsx: -------------------------------------------------------------------------------- 1 | 2 | import { 3 | Drawer, 4 | DrawerBody, 5 | DrawerFooter, 6 | DrawerHeader, 7 | DrawerOverlay, 8 | DrawerContent, 9 | DrawerCloseButton 10 | } from '@chakra-ui/react' 11 | 12 | import { Button, Switch, Slider, SliderTrack, SliderFilledTrack, SliderThumb, Divider } from '@chakra-ui/react' 13 | import { useState } from 'react' 14 | 15 | export function DebugMenu() { 16 | 17 | var [isOpen, _setIsOpen] = useState(false); 18 | 19 | var setIsOpen = (v) => { 20 | 21 | if (v && rerender < 0) { return; } 22 | 23 | if (v) { 24 | setTimeout(() => setRerender(-9999999), 0); 25 | } 26 | else { 27 | setTimeout(() => setRerender(2), 0); 28 | } 29 | 30 | _setIsOpen(v); 31 | } 32 | 33 | window.openDebugMenu = () => setIsOpen(true); 34 | 35 | return ( 36 | <> 37 | setIsOpen(false)} 42 | > 43 | 44 | 45 |
46 | 47 |
48 | 49 | Debug Menu 50 | 51 | 52 |
53 | 54 | 57 |
58 | 59 | 60 | 61 |
62 | 63 | ChangeSwitch("EnableDebugForceFallbackMode", e)} isChecked={Config.EnableDebugForceFallbackMode} style={{ marginLeft: "auto" }} size='lg' /> 64 |
65 | 66 | 67 | 68 |
69 | 70 | ChangeSwitch("EnableDebugRemoveBoundsCorrection", e)} isChecked={Config.EnableDebugRemoveBoundsCorrection} style={{ marginLeft: "auto" }} size='lg' /> 71 |
72 |
73 | 74 | 75 | 76 | 77 |
78 |
79 | 80 | ) 81 | } 82 | 83 | addEventListener("keydown", (e) => { 84 | if (e.key == "F2") { 85 | window.openDebugMenu(); 86 | e.preventDefault(); 87 | } 88 | }) -------------------------------------------------------------------------------- /TopNotify/src-vite/src/Helper.jsx: -------------------------------------------------------------------------------- 1 | import { useRef } from "react"; 2 | 3 | //https://stackoverflow.com/a/66139558/18071273 4 | export function useFirstRender() { 5 | const ref = useRef(true); 6 | const firstRender = ref.current; 7 | ref.current = false; 8 | return firstRender; 9 | } 10 | 11 | //https://stackoverflow.com/a/52652681/18071273 12 | export function waitUntil(conditionFunction) { 13 | 14 | const poll = resolve => { 15 | if(conditionFunction()) resolve(); 16 | else setTimeout(_ => poll(resolve), 400); 17 | } 18 | 19 | return new Promise(poll); 20 | } -------------------------------------------------------------------------------- /TopNotify/src-vite/src/MonitorSelect.jsx: -------------------------------------------------------------------------------- 1 | import { Button, Switch, Divider, Select } from '@chakra-ui/react' 2 | 3 | export default function MonitorSelect(props) { 4 | return ( 5 | 13 | ) 14 | } -------------------------------------------------------------------------------- /TopNotify/src-vite/src/NotificationSounds.jsx: -------------------------------------------------------------------------------- 1 | import { Button, Divider } from '@chakra-ui/react' 2 | 3 | import { useState, Fragment } from 'react'; 4 | import { useFirstRender } from './Helper.jsx' 5 | 6 | import "./CSS/NotificationSounds.css"; 7 | 8 | import { 9 | Drawer, 10 | DrawerBody, 11 | DrawerFooter, 12 | DrawerHeader, 13 | DrawerOverlay, 14 | DrawerContent, 15 | DrawerCloseButton 16 | } from '@chakra-ui/react' 17 | 18 | export default function ManageNotificationSounds() { 19 | 20 | var [isOpen, _setIsOpen] = useState(false); 21 | var [isPickerOpen, _setIsPickerOpen] = useState(false); 22 | 23 | var setIsOpen = (v) => { 24 | 25 | if (v && rerender < 0) { return; } 26 | 27 | if (v) { 28 | setTimeout(() => setRerender(-9999999), 0); 29 | } 30 | else { 31 | setTimeout(() => setRerender(2), 0); 32 | } 33 | 34 | _setIsOpen(v); 35 | } 36 | 37 | var setIsPickerOpen = (v, id) => { 38 | _setIsOpen(!v); 39 | _setIsPickerOpen(v); 40 | } 41 | 42 | var applySound = (sound) => { 43 | 44 | for (var i = 0; i < Config.AppReferences.length; i++) { 45 | if (Config.AppReferences[i].ID == window.soundPickerReferenceID) { 46 | Config.AppReferences[i].SoundPath = sound.Path; 47 | Config.AppReferences[i].SoundDisplayName = sound.Name; 48 | break; 49 | } 50 | } 51 | 52 | UploadConfig(); 53 | setIsPickerOpen(false); 54 | } 55 | 56 | return ( 57 |
58 | 59 | 62 | setIsOpen(false)} 67 | > 68 | 69 | 70 |
71 | 72 |
73 | 74 | Notification Sounds 75 | 76 | 77 |

Some apps will play their own sounds, you may have to turn them off in-app to prevent overlapping audio.
78 | { 79 | window.Config.AppReferences.map((appReference, i) => { 80 | return ( 81 | 82 | 83 | 84 | 85 | ) 86 | }) 87 | } 88 | 89 | { 90 | window.Config.AppReferences.length == 0 ? (

When an app sends a notification, TopNotify will capture it and it will show up here for you to modify the sounds.

) : null 91 | } 92 |
93 | 94 | 95 | 96 | 97 |
98 |
99 | 100 |
101 | ) 102 | } 103 | 104 | function AppReferenceSoundItem(props) { 105 | 106 | var pickSound = () => { 107 | window.soundPickerReferenceID = props.appReference.ID; 108 | props.setIsPickerOpen(true); 109 | } 110 | 111 | return ( 112 |
113 | 114 |

{props.appReference.DisplayName}

115 |
116 | 117 |
118 |
119 | ) 120 | } 121 | 122 | function SoundPicker(props) { 123 | 124 | var [soundPacks, setSoundPacks] = useState([]); 125 | 126 | if (useFirstRender()) { 127 | setTimeout(async () => { 128 | var newSounds = JSON.parse(await igniteView.commandBridge.FindSounds()); 129 | setSoundPacks(newSounds); 130 | }, 0); 131 | } 132 | 133 | return ( 134 | props.setIsPickerOpen(false)} 139 | > 140 | 141 | 142 |
143 | 144 |
145 | 146 | Select Sound 147 | 148 | 149 |
150 | { 151 | soundPacks.map((soundPack, i) => { 152 | return () 153 | }) 154 | } 155 |
156 |
157 | 158 | 159 | 160 | 161 |
162 |
163 | ) 164 | } 165 | 166 | function SoundPack(props) { 167 | 168 | var playSound = (sound) => igniteView.commandBridge.PreviewSound(sound.Path); 169 | 170 | return ( 171 |
172 |

{props.soundPack.Name}

173 |

{props.soundPack.Description}

174 | 175 |
176 | { 177 | props.soundPack.Sounds.map((sound, i) => { 178 | return ( 179 |
180 | 183 |
{sound.Name} 
184 |
185 | ) 186 | }) 187 | } 188 |
189 |
190 | ) 191 | } 192 | -------------------------------------------------------------------------------- /TopNotify/src-vite/src/Preview.jsx: -------------------------------------------------------------------------------- 1 | 2 | import { Button } from "@chakra-ui/react"; 3 | import "./CSS/Preview.css"; 4 | 5 | var previewWidth = 352; 6 | var previewScale = previewWidth / 1920; // Relative to actual scale 7 | 8 | function CalculatePreviewContainerStyle() { 9 | var aspect = 0.5625; // 16:9 10 | 11 | if (window.Config.__ScreenWidth) { 12 | aspect = window.Config.__ScreenHeight / window.Config.__ScreenWidth; 13 | } 14 | 15 | return { width: previewWidth, height: (previewWidth * aspect), backgroundImage: `url('${igniteView.resolverURL + "/wallpaper.jpg"}')` }; 16 | } 17 | 18 | function CalculateTaskbarPreviewStyle() { 19 | //Windows taskbar is 48px high 20 | var standardHeight = 48 * previewScale; 21 | 22 | return { 23 | height: window.Config.__ScreenScale ? (standardHeight * window.Config.__ScreenScale) : standardHeight 24 | }; 25 | } 26 | 27 | function CalculateNotificationWindowPreviewStyle() { 28 | 29 | //The window size (not displayed size) of windows notifications are 396 * 152 30 | var standardWidth = 396 * previewScale; 31 | var standardHeight = 152 * previewScale; 32 | 33 | var style = { 34 | width: window.Config.__ScreenScale ? (standardWidth * window.Config.__ScreenScale) : standardWidth, 35 | height: window.Config.__ScreenScale ? (standardHeight * window.Config.__ScreenScale) : standardHeight 36 | }; 37 | 38 | var posX = 0; 39 | var posY = 0; 40 | var scaledMainDisplayWidth = CalculatePreviewContainerStyle().width; 41 | var scaledMainDisplayHeight = CalculatePreviewContainerStyle().height; 42 | 43 | if (window.Config.Location) { 44 | if (window.Config.Location == 0) { // Top left 45 | posX = 0; 46 | posY = 0; 47 | } 48 | else if (window.Config.Location == 1) { // Top Right 49 | posX = scaledMainDisplayWidth - style.width; 50 | posY = 0; 51 | } 52 | else if (window.Config.Location == 2) { // Bottom Left 53 | posX = 0; 54 | posY = scaledMainDisplayHeight - style.height - (50 * previewScale); 55 | } 56 | else if (window.Config.Location == 3) { // Bottom Right 57 | posX = scaledMainDisplayWidth - style.width; 58 | posY = scaledMainDisplayHeight - style.height - (50 * previewScale); 59 | } 60 | else { // Custom 61 | posX = window.Config.CustomPositionPercentX / 100 * scaledMainDisplayWidth; 62 | posY = window.Config.CustomPositionPercentY / 100 * scaledMainDisplayHeight; 63 | 64 | //Make Sure Position Isn't Out Of Bounds 65 | posX = Math.max(0, Math.min(posX, scaledMainDisplayWidth - style.width)); 66 | posY = Math.max(0, Math.min(posY, scaledMainDisplayHeight - style.height)); 67 | } 68 | } 69 | 70 | style.left = posX; 71 | style.top = posY; 72 | 73 | return style; 74 | } 75 | 76 | function CalculateNotificationPreviwStyle() { 77 | 78 | //The displayed size is 364 * 109, which has a scale of 0.919191917 * 0.717105263 relative to the window 79 | 80 | return { 81 | width: CalculateNotificationWindowPreviewStyle().width * 0.919191917, 82 | height: CalculateNotificationWindowPreviewStyle().height * 0.717105263, 83 | opacity: (Config.Opacity != undefined) ? (1 - (Config.Opacity / 5)) : 0 84 | } 85 | } 86 | 87 | export default function Preview() { 88 | return ( 89 |
90 |
91 |
92 | 93 |
94 |
95 |
96 | 97 | 98 | 99 | 100 | 101 |
102 |
103 |
104 | ) 105 | } 106 | 107 | 108 | // 0 = TopLeft, 109 | // 1 = TopRight, 110 | // 2 = BottomLeft, 111 | // 3 = BottomRight, 112 | // 4 = Custom 113 | function SetPresetPosition(position) { 114 | window.ChangeValue("Location", position); 115 | } -------------------------------------------------------------------------------- /TopNotify/src-vite/src/ReadAloud.jsx: -------------------------------------------------------------------------------- 1 | 2 | import { Switch } from '@chakra-ui/react' 3 | 4 | export default function ReadAloud() { 5 | 6 | 7 | return ( 8 |
9 | 10 | ChangeSwitch("ReadAloud", e)} isChecked={Config.ReadAloud} style={{ marginLeft: "auto" }} size='lg' /> 11 |
12 | ) 13 | } -------------------------------------------------------------------------------- /TopNotify/src-vite/src/SoundInterceptionToggle.jsx: -------------------------------------------------------------------------------- 1 | import { Button, Switch, Slider, SliderTrack, SliderFilledTrack, SliderThumb, Divider } from '@chakra-ui/react' 2 | import { useState } from 'react' 3 | 4 | export default function SoundInterceptionToggle() { 5 | 6 | var [isInterceptionEnabled, setIsInterceptionEnabled] = useState(false); 7 | var [checkState, setCheckState] = useState("none"); 8 | 9 | if (checkState == "none") { 10 | setCheckState("loading"); 11 | setTimeout(async () => { 12 | window.isInterceptionEnabled = await igniteView.commandBridge.IsSoundInstalledInRegistry(); 13 | setIsInterceptionEnabled(window.isInterceptionEnabled); 14 | setCheckState("success"); 15 | window.setRerender(rerender + 1); 16 | }, 0); 17 | } 18 | 19 | var setEnabled = async (isChecked) => { 20 | if (isChecked) { 21 | await igniteView.commandBridge.InstallSoundInRegistry(); 22 | } 23 | else { 24 | await igniteView.commandBridge.UninstallSoundInRegistry(); 25 | } 26 | 27 | UploadConfig(); 28 | 29 | setCheckState("none"); 30 | } 31 | 32 | return ( 33 |
34 | 35 | setEnabled(e.target.checked)} isChecked={isInterceptionEnabled} style={{ marginLeft: "auto" }} size='lg' /> 36 |
37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /TopNotify/src-vite/src/TestNotification.jsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@chakra-ui/react' 2 | 3 | export default function TestNotification() { 4 | return ( 5 |
6 | 7 | 10 |
11 | ) 12 | } -------------------------------------------------------------------------------- /TopNotify/src-vite/src/Transparency.jsx: -------------------------------------------------------------------------------- 1 | import { Button, Switch, Slider, SliderTrack, SliderFilledTrack, SliderThumb, Divider } from '@chakra-ui/react' 2 | 3 | export default function NotificationTransparency() { 4 | return ( 5 |
6 | 7 | { 8 | //Slider Is In Uncontrolled Mode For Performance Reasons 9 | //So We Need To Wait For The Config To Load Before Setting The Default Value 10 | (Config.Location < 0) ? 11 | (<>) : 12 | ( 13 | 14 | 15 | 16 | 17 | 18 | 19 | ) 20 | } 21 |
22 | ) 23 | } 24 | 25 | function ChangeTransparency(opacity) { 26 | Config.Opacity = (opacity * 0.05); 27 | window.UploadConfig(); 28 | window.setRerender(rerender + 1); 29 | } -------------------------------------------------------------------------------- /TopNotify/src-vite/src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App.jsx' 4 | import { ChakraProvider } from '@chakra-ui/react' 5 | import './CSS/App.css' 6 | import './CSS/ChakraOverrides.css' 7 | import { useFirstRender, waitUntil } from './Helper.jsx' 8 | import About from './About.jsx' 9 | 10 | window.serverURL = "http://" + window.location.host + "/"; 11 | 12 | //Chakra UI Color Mode 13 | var defaultTheme = (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) ? "dark" : "light"; 14 | localStorage.setItem("chakra-ui-color-mode", defaultTheme); 15 | document.documentElement.setAttribute("data-theme", defaultTheme); 16 | document.body.setAttribute("chakra-ui-theme", "chakra-ui-" + defaultTheme); 17 | 18 | window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => { 19 | location.reload(); 20 | }); 21 | 22 | 23 | 24 | ReactDOM.createRoot(document.getElementById('root')).render(RootComponent()); 25 | 26 | function RootComponent(params) { 27 | return ( 28 | 29 | 30 | 31 | ) 32 | } 33 | 34 | function Dispatcher() { 35 | var MainMethod = App; 36 | 37 | if (window.location.search.includes("about")) { 38 | MainMethod = About; 39 | } 40 | 41 | return ( 42 | 43 | ) 44 | } -------------------------------------------------------------------------------- /TopNotify/src-vite/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react-swc' 3 | import transformPlugin from 'vite-plugin-transform'; 4 | import { resolve, join } from 'path'; 5 | 6 | var version = "3.0.0"; 7 | 8 | // https://vitejs.dev/config/ 9 | export default defineConfig({ 10 | plugins: [ 11 | react(), 12 | transformPlugin({ 13 | tStart: '%{', 14 | tEnd: '}%', 15 | replace: { 16 | "TOPNOTIFY_VERSION": version 17 | }, 18 | replaceFiles: [ 19 | resolve(join(__dirname, '..\\..\\TopNotify\\dist\\Meta\\AppxManifest.xml')), 20 | resolve(join(__dirname, '..\\..\\TopNotify\\dist\\Meta\\manifest.xml')), 21 | resolve(join(__dirname, '..\\..\\TopNotify\\dist\\Meta\\WinGet\\SamsidParty.TopNotifyWG.yaml')), 22 | resolve(join(__dirname, '..\\..\\TopNotify\\dist\\Meta\\WinGet\\SamsidParty.TopNotifyWG.installer.yaml')), 23 | resolve(join(__dirname, '..\\..\\TopNotify\\dist\\Meta\\WinGet\\SamsidParty.TopNotifyWG.locale.en-US.yaml')), 24 | ] 25 | }) 26 | ], 27 | build: { 28 | outDir: '..\\..\\TopNotify\\dist' 29 | }, 30 | define: { 31 | TOPNOTIFY_VERSION: `"${version}"`, 32 | }, 33 | }) 34 | --------------------------------------------------------------------------------