4 | Trayy
5 |
6 |
7 |
8 | Ever wished you could tell some apps to chill in the system tray? Say hello to Trayy, your desktop's new bouncer!
9 |
10 |
11 |
12 |
13 |
14 | ## 🎯 Features
15 |
16 | - **Send to Tray**: Send your favorite apps to the system tray for a clutter-free workspace!
17 | - **Hide and Seek Champion**: Enjoy the power to completely hide applications from the taskbar, keeping your desktop neat and tidy.
18 | - **App Support**: Ideal for Progressive Web Apps (PWAs) and (most) apps that didn't get the memo about system tray support such as Thunderbird.
19 | - **Compatible with Windows**: Tested on Windows 10 and 11 with support for toast notifications.
20 | - **Light as a Feather**: Fully portable and extremely lightweight.
21 |
22 | ## 🚀 Getting Started
23 |
24 | 1. Kick things off by downloading `Trayy.zip` and unzipping it to reveal the magic inside!
25 | 2. Make sure `Trayy.exe` and `hook.dll` are hanging out in the same folder. Now, Windows Defender might raise a false positive but fear not! Trayy is harmless and also open source ([VirusTotal report](https://www.virustotal.com/gui/file/688011ba8305871139bac0b7da0da7f2e56370e65f9909bea2350723b9db2822/detection)). Run the app to get the party started.
26 | 3. Time to pick your superpowers:
27 | - **Send to Tray also when Closed**: Even if you hit the X button, your app will just chill in the tray.
28 | - **Do not show on Taskbar**: Your app will become the ultimate hide-and-seek champion, staying off the taskbar completely.
29 | 4. Now, list out your favorite applications (case-sensitive).
30 | - Trayy will keep an eye out for any process name as seen in Task Manager that match your entries. For example for `Notepad.exe` you need to add `Notepad` to your lineup.
31 | - For Web Apps, Trayy will look for browser tab titles that contain your specified string. For example `WhatsApp Web`. Use distinctive keywords to avoid accidental matches!
32 | - Got an app (like Thunderbird) that doesn’t play nice with Trayy's standard functionality? Just add an asterisk `*` to the end of its name (e.g. `Thunderbird*`). This tells Trayy to use a special method to detect minimize or close actions by looking for clicks on the top-right of the titlebar, so even tricky programs can be tucked away smoothly!
33 | - **Heads up:** Universal Windows Platform (UWP) apps (like those from the Microsoft Store) like to do their own thing and aren’t supported by Trayy.
34 | 5. Hit Save and BAM! Depending on your settings, your chosen applications will now be tucked away neatly in the system tray.
35 | 6. Click on a tray icon to bring its application into the spotlight. If it's already in focus, it'll sneak back into the tray. This way, you can quickly peek at your apps without breaking your workflow!
36 |
37 |
38 |
39 |
40 |
41 |
42 | **Pro Move**: Let Trayy join your startup squad! Add Trayy's shortcut to your `shell:startup` folder, along with all your other favorite apps you want to keep tucked away in the system tray. For WebApps, you can ask your browser to add their shortcuts to your Desktop. Then, simply move those shortcuts into the startup folder. Trayy will chill for a bit, letting the startup process finish, then swoop in to tidy everything up! It's a game changer for messaging and productivity apps!
43 |
44 | ## 🙏 Acknowledgements
45 |
46 | This project is inspired by RBTray.
47 |
48 | If Trayy made your workflow smoother, consider supporting its development! ☕✨
49 |
50 | [](https://ko-fi.com/Q5Q21EOKMX)
51 |
--------------------------------------------------------------------------------
/Trayy.cpp:
--------------------------------------------------------------------------------
1 | #include "Trayy.h"
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 |
12 | #pragma comment(lib, "Gdi32.lib")
13 |
14 | // Global variables
15 | UINT WM_TASKBAR_CREATED;
16 | HWINEVENTHOOK hEventHook;
17 | HINSTANCE hInstance;
18 | HMODULE hLib;
19 | HWND hwndBase;
20 | HWND hwndMain;
21 | HWND hwndItems[MAXTRAYITEMS];
22 | HWND hwndForMenu;
23 | std::unordered_set appNames;
24 | std::unordered_set specialAppNames;
25 | bool HOOKBOTH = true;
26 | bool NOTASKBAR = false;
27 | bool updateAvailable = false;
28 |
29 | std::unordered_set ExcludedNames = { L"ApplicationFrameHost.exe", L"MSCTFIME UI", L"Default IME", L"EVR Fullscreen Window" };
30 | std::unordered_set ExcludedProcesses = { L"Explorer.EXE", L"SearchHost.exe", L"svchost.exe", L"taskhostw.exe", L"OneDrive.exe", L"TextInputHost.exe", L"SystemSettings.exe", L"RuntimeBroker.exe", L"SearchUI.exe", L"ShellExperienceHost.exe", L"msedgewebview2.exe", L"pwahelper.exe", L"conhost.exe", L"VCTIP.EXE", L"GameBarFTServer.exe" };
31 | std::unordered_set UseWindowName = { L"thunderbird.exe", L"chrome.exe", L"firefox.exe", L"opera.exe", L"msedge.exe", L"iexplore.exe", L"brave.exe", L"vivaldi.exe", L"chromium.exe" };
32 |
33 | // Shared memory variables
34 | HANDLE hSharedMemory = NULL;
35 | SpecialAppsSharedData* pSharedData = NULL;
36 |
37 | // Shared memory functions
38 | BOOL InitializeSharedMemory() {
39 | // Create shared memory
40 | hSharedMemory = CreateFileMapping(
41 | INVALID_HANDLE_VALUE,
42 | NULL,
43 | PAGE_READWRITE,
44 | 0,
45 | SHARED_MEM_SIZE,
46 | SHARED_MEM_NAME);
47 |
48 | if (hSharedMemory == NULL) {
49 | return FALSE;
50 | }
51 |
52 | // Map view of file
53 | pSharedData = (SpecialAppsSharedData*)MapViewOfFile(
54 | hSharedMemory,
55 | FILE_MAP_ALL_ACCESS,
56 | 0,
57 | 0,
58 | SHARED_MEM_SIZE);
59 |
60 | if (pSharedData == NULL) {
61 | CloseHandle(hSharedMemory);
62 | hSharedMemory = NULL;
63 | return FALSE;
64 | }
65 |
66 | // Initialize
67 | pSharedData->count = 0;
68 |
69 | return TRUE;
70 | }
71 |
72 | void CleanupSharedMemory() {
73 | if (pSharedData) {
74 | UnmapViewOfFile(pSharedData);
75 | pSharedData = NULL;
76 | }
77 |
78 | if (hSharedMemory) {
79 | CloseHandle(hSharedMemory);
80 | hSharedMemory = NULL;
81 | }
82 | }
83 |
84 | BOOL UpdateSpecialAppsList(const std::unordered_set& specialApps) {
85 | if (!pSharedData)
86 | return FALSE;
87 |
88 | // Clear existing data
89 | ZeroMemory(pSharedData, SHARED_MEM_SIZE);
90 |
91 | // Update count and app names
92 | int idx = 0;
93 | for (const auto& app : specialApps) {
94 | if (idx >= MAX_SPECIAL_APPS) break;
95 | wcscpy_s(pSharedData->specialApps[idx], MAX_PATH, app.c_str());
96 | idx++;
97 | }
98 | pSharedData->count = idx;
99 |
100 | return TRUE;
101 | }
102 |
103 | int FindInTray(HWND hwnd) {
104 | for (int i = 0; i < MAXTRAYITEMS; i++) {
105 | if (hwndItems[i] == hwnd) {
106 | return i;
107 | }
108 | }
109 | return -1;
110 | }
111 |
112 | HICON GetWindowIcon(HWND hwnd) {
113 | HICON icon;
114 | if (icon = (HICON)SendMessage(hwnd, WM_GETICON, ICON_SMALL, 0)) {
115 | return icon;
116 | }
117 | if (icon = (HICON)SendMessage(hwnd, WM_GETICON, ICON_BIG, 0)) {
118 | return icon;
119 | }
120 | if (icon = (HICON)GetClassLongPtr(hwnd, GCLP_HICONSM)) {
121 | return icon;
122 | }
123 | if (icon = (HICON)GetClassLongPtr(hwnd, GCLP_HICON)) {
124 | return icon;
125 | }
126 | return LoadIcon(NULL, IDI_WINLOGO);
127 | }
128 |
129 | bool AddToTray(int i) {
130 | NOTIFYICONDATA nid;
131 | ZeroMemory(&nid, sizeof(nid));
132 | nid.cbSize = NOTIFYICONDATA_V2_SIZE;
133 | nid.hWnd = hwndMain;
134 | nid.uID = (UINT)i;
135 | nid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
136 | nid.uCallbackMessage = WM_TRAYCMD;
137 | nid.hIcon = GetWindowIcon(hwndItems[i]);
138 | GetWindowText(hwndItems[i], nid.szTip, sizeof(nid.szTip) / sizeof(nid.szTip[0]));
139 | nid.uVersion = NOTIFYICON_VERSION;
140 | if (!Shell_NotifyIcon(NIM_ADD, &nid)) {
141 | return false;
142 | }
143 | if (!Shell_NotifyIcon(NIM_SETVERSION, &nid)) {
144 | Shell_NotifyIcon(NIM_DELETE, &nid);
145 | return false;
146 | }
147 | return true;
148 | }
149 |
150 | static bool AddWindowToTray(HWND hwnd) {
151 | int i = FindInTray(NULL);
152 | if (i == -1) {
153 | return false;
154 | }
155 | hwndItems[i] = hwnd;
156 | return AddToTray(i);
157 | }
158 |
159 | void MinimizeWindowToTray(HWND hwnd) {
160 | // Don't minimize MDI child windows
161 | if ((UINT)GetWindowLongPtr(hwnd, GWL_EXSTYLE) & WS_EX_MDICHILD) {
162 | return;
163 | }
164 |
165 | // If hwnd is a child window, find parent window
166 | if ((UINT)GetWindowLongPtr(hwnd, GWL_STYLE) & WS_CHILD) {
167 | hwnd = GetAncestor(hwnd, GA_ROOT);
168 | }
169 |
170 | ShowWindow(hwnd, SW_HIDE);
171 |
172 | // Add icon to tray if it's not already there
173 | if (FindInTray(hwnd) == -1) {
174 | if (!AddWindowToTray(hwnd)) {
175 | ShowWindow(hwnd, SW_SHOW);
176 | SetForegroundWindow(hwnd);
177 | return;
178 | }
179 | }
180 | }
181 |
182 | static bool RemoveFromTray(int i) {
183 | NOTIFYICONDATA nid;
184 | ZeroMemory(&nid, sizeof(nid));
185 | nid.cbSize = NOTIFYICONDATA_V2_SIZE;
186 | nid.hWnd = hwndMain;
187 | nid.uID = (UINT)i;
188 | if (!Shell_NotifyIcon(NIM_DELETE, &nid)) {
189 | return false;
190 | }
191 | return true;
192 | }
193 |
194 | bool RemoveWindowFromTray(HWND hwnd) {
195 | int i = FindInTray(hwnd);
196 | if (i == -1) {
197 | return false;
198 | }
199 | if (!RemoveFromTray(i)) {
200 | return false;
201 | }
202 | hwndItems[i] = NULL;
203 | return true;
204 | }
205 |
206 | void RestoreWindowFromTray(HWND hwnd) {
207 | wchar_t windowName[256];
208 | GetWindowText(hwnd, windowName, 256);
209 | if (wcsstr(windowName, NAME) != nullptr) {
210 | ShowWindow(hwnd, SW_SHOW);
211 | SetForegroundWindow(hwnd);
212 | return;
213 | }
214 |
215 | if (NOTASKBAR) {
216 | SetWindowLongPtr(hwnd, GWLP_HWNDPARENT, (LONG_PTR)hwndBase);
217 | }
218 | else {
219 | SetWindowLongPtr(hwnd, GWLP_HWNDPARENT, 0);
220 | }
221 | ShowWindow(hwnd, SW_SHOW);
222 | SetForegroundWindow(hwnd);
223 | }
224 |
225 | void CloseWindowFromTray(HWND hwnd) {
226 | ShowWindow(hwnd, SW_SHOW);
227 | PostMessage(hwnd, WM_CLOSE, 0, 0);
228 |
229 | Sleep(50);
230 | if (IsWindow(hwnd)) {
231 | Sleep(50);
232 | }
233 |
234 | if (!IsWindow(hwnd)) {
235 | RemoveWindowFromTray(hwnd);
236 | }
237 | }
238 |
239 | void RefreshWindowInTray(HWND hwnd) {
240 | int i = FindInTray(hwnd);
241 | if (i == -1) {
242 | return;
243 | }
244 | if (!IsWindow(hwnd)) {
245 | RemoveWindowFromTray(hwnd);
246 | }
247 | else {
248 | NOTIFYICONDATA nid;
249 | ZeroMemory(&nid, sizeof(nid));
250 | nid.cbSize = NOTIFYICONDATA_V2_SIZE;
251 | nid.hWnd = hwnd;
252 | nid.uID = (UINT)i;
253 | nid.uFlags = NIF_TIP;
254 | GetWindowText(hwnd, nid.szTip, sizeof(nid.szTip) / sizeof(nid.szTip[0]));
255 | Shell_NotifyIcon(NIM_MODIFY, &nid);
256 | }
257 | }
258 |
259 | std::wstring getProcessName(HWND hwnd) {
260 | DWORD processId;
261 | GetWindowThreadProcessId(hwnd, &processId);
262 | HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processId);
263 | if (hProcess == NULL)
264 | return L"";
265 |
266 | wchar_t processName[MAX_PATH] = L"";
267 | HMODULE hMod;
268 | DWORD cbNeeded;
269 |
270 | if (EnumProcessModules(hProcess, &hMod, sizeof(hMod), &cbNeeded))
271 | {
272 | GetModuleBaseName(hProcess, hMod, processName, sizeof(processName) / sizeof(TCHAR));
273 | }
274 | CloseHandle(hProcess);
275 | return std::wstring(processName);
276 | }
277 |
278 | bool appCheck(HWND lParam, bool restore) {
279 | wchar_t windowName[256];
280 | GetWindowText(lParam, windowName, 256);
281 |
282 | if (wcslen(windowName) == 0) {
283 | return false;
284 | }
285 |
286 | if (restore) {
287 | if (IsWindowVisible(lParam)) {
288 | return false;
289 | }
290 | }
291 |
292 | // Excluded window names
293 | std::wstring windowNameStr(windowName);
294 | if (ExcludedNames.find(windowNameStr) != ExcludedNames.end()) {
295 | return false;
296 | }
297 |
298 | std::wstring processName = getProcessName(lParam);
299 | if (processName.empty()) {
300 | return false;
301 | }
302 |
303 | // Excluded processes
304 | if (ExcludedProcesses.find(processName) != ExcludedProcesses.end()) {
305 | return false;
306 | }
307 |
308 | // Change to window name if needed
309 | if (UseWindowName.find(processName) != UseWindowName.end()) {
310 | processName = windowName;
311 | }
312 |
313 | for (const auto& appName : appNames) {
314 | if (!appName.empty() && processName.find(appName) != std::wstring::npos) {
315 | return true;
316 | }
317 | }
318 | if (processName == NAME L".exe" && wcscmp(windowName, NAME) == 0) {
319 | return true;
320 | }
321 | return false;
322 | }
323 |
324 | void MinimizeAll() {
325 | HWND hwnd = GetTopWindow(NULL);
326 | while (hwnd) {
327 | if (appCheck(hwnd)) {
328 | MinimizeWindowToTray(hwnd);
329 | }
330 | hwnd = GetNextWindow(hwnd, GW_HWNDNEXT);
331 | }
332 | }
333 |
334 | BOOL CALLBACK EnumChildProc(HWND hwnd, LPARAM lParam) {
335 | char className[256];
336 | GetClassNameA(hwnd, className, sizeof(className));
337 | if (strcmp(className, "TrayNotifyWnd") == 0) {
338 | EnumChildWindows(hwnd, EnumChildProc, lParam);
339 | }
340 | else if (strcmp(className, "SysPager") == 0) {
341 | EnumChildWindows(hwnd, EnumChildProc, lParam);
342 | }
343 | else if (strcmp(className, "ToolbarWindow32") == 0) {
344 | RECT r;
345 | GetClientRect(hwnd, &r);
346 | for (LONG x = 0; x < r.right; x += 5) {
347 | for (LONG y = 0; y < r.bottom; y += 5) {
348 | LPARAM lParam = MAKELPARAM(x, y);
349 | SendMessage(hwnd, WM_MOUSEMOVE, 0, lParam);
350 | }
351 | }
352 | }
353 | return TRUE;
354 | }
355 |
356 | void RefreshTray() {
357 | for (int i = 0; i < MAXTRAYITEMS; i++) {
358 | if (hwndItems[i] && IsWindow(hwndItems[i])) {
359 | HICON hIcon = GetWindowIcon(hwndItems[i]);
360 |
361 | NOTIFYICONDATA nid;
362 | ZeroMemory(&nid, sizeof(nid));
363 | nid.cbSize = NOTIFYICONDATA_V2_SIZE;
364 | nid.hWnd = hwndMain;
365 | nid.uID = (UINT)i;
366 | nid.uFlags = NIF_ICON | NIF_TIP;
367 | nid.hIcon = hIcon;
368 | GetWindowText(hwndItems[i], nid.szTip, sizeof(nid.szTip) / sizeof(nid.szTip[0]));
369 |
370 | Shell_NotifyIcon(NIM_MODIFY, &nid);
371 | }
372 | }
373 | }
374 |
375 | bool IsTopWindow(HWND hwnd) {
376 | HWND hwndTopmost = NULL;
377 | HWND hwnd_ = GetTopWindow(0);
378 | wchar_t name_[256];
379 | GetWindowText(hwnd_, name_, 256);
380 | while (hwnd_) {
381 | if (wcslen(name_) > 0 && IsWindowVisible(hwnd_)) {
382 | hwndTopmost = hwnd_;
383 | break;
384 | }
385 | hwnd_ = GetNextWindow(hwnd_, GW_HWNDNEXT);
386 | GetWindowText(hwnd_, name_, 256);
387 | }
388 | if (hwndTopmost == hwnd) {
389 | return true;
390 | }
391 | return false;
392 | }
393 |
394 | void MinimizeAllInBackground() {
395 | std::thread t([]() {
396 | if (GetTickCount64() < 300000) {
397 | Sleep(10000);
398 | }
399 | MinimizeAll();
400 | });
401 | t.detach();
402 | }
403 |
404 | void LoadSettings() {
405 | appNames.clear();
406 | specialAppNames.clear();
407 | std::wifstream file(SETTINGS_FILE);
408 | if (!file) {
409 | std::wofstream file(SETTINGS_FILE, std::ios::out | std::ios::trunc);
410 | file << L"HOOKBOTH " << (HOOKBOTH ? L"true" : L"false") << std::endl;
411 | file << L"NOTASKBAR " << (NOTASKBAR ? L"true" : L"false") << std::endl;
412 | }
413 | else {
414 | try {
415 | std::wstring line;
416 | std::getline(file, line);
417 | HOOKBOTH = line.find(L"true") != std::string::npos;
418 | std::getline(file, line);
419 | NOTASKBAR = line.find(L"true") != std::string::npos;
420 | while (std::getline(file, line)) {
421 | if (!line.empty()) {
422 | if (line.back() == L'*') {
423 | std::wstring baseName = line.substr(0, line.length() - 1);
424 | appNames.insert(baseName);
425 | specialAppNames.insert(baseName);
426 | }
427 | else {
428 | appNames.insert(line);
429 | }
430 | }
431 | }
432 | file.close();
433 | UpdateSpecialAppsList(specialAppNames);
434 | }
435 | catch (const std::exception&) {
436 | std::wstring backupFile = SETTINGS_FILE L".bak";
437 | CopyFile(SETTINGS_FILE, backupFile.c_str(), FALSE);
438 | std::wofstream file(SETTINGS_FILE, std::ios::out | std::ios::trunc);
439 | file << L"HOOKBOTH " << (HOOKBOTH ? L"true" : L"false") << std::endl;
440 | file << L"NOTASKBAR " << (NOTASKBAR ? L"true" : L"false") << std::endl;
441 | }
442 | }
443 | }
444 |
445 | void CALLBACK WinEventProc(HWINEVENTHOOK hWinEventHook, DWORD event, HWND hwnd, LONG idObject, LONG idChild, DWORD dwEventThread, DWORD dwmsEventTime)
446 | {
447 | if (event == EVENT_SYSTEM_FOREGROUND)
448 | {
449 | if (GetForegroundWindow() == hwnd) { // good for windows things
450 | return;
451 | }
452 | if (appCheck(hwnd))
453 | {
454 | RestoreWindowFromTray(hwnd);
455 | }
456 | }
457 | }
458 |
459 | void SaveSettings() {
460 | std::wofstream file(SETTINGS_FILE, std::ios::out | std::ios::trunc);
461 | file << "HOOKBOTH " << (HOOKBOTH ? "true" : "false") << std::endl;
462 | file << "NOTASKBAR " << (NOTASKBAR ? "true" : "false") << std::endl;
463 |
464 | for (const auto& appName : appNames) {
465 | file << appName << std::endl;
466 | }
467 | file.close();
468 | LoadSettings();
469 | RefreshTray();
470 | }
471 |
472 | // This function handles all window messages
473 | LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
474 | switch (msg) {
475 | case WM_COMMAND:
476 | {
477 | switch (LOWORD(wParam)) {
478 | case IDM_RESTORE:
479 | RestoreWindowFromTray(hwndForMenu);
480 | break;
481 | case IDM_CLOSE:
482 | CloseWindowFromTray(hwndForMenu);
483 | break;
484 | case IDM_ABOUT:
485 | ShellExecute(NULL, L"open", ABOUT_URL, NULL, NULL, SW_SHOWNORMAL);
486 | break;
487 | case ID_CHECKBOX1:
488 | HandleCheckboxClick(hwnd, ID_CHECKBOX1);
489 | break;
490 | case ID_CHECKBOX2:
491 | HandleCheckboxClick(hwnd, ID_CHECKBOX2);
492 | break;
493 | case IDM_EXIT:
494 | SendMessage(hwnd, WM_DESTROY, 0, 0);
495 | break;
496 | case ID_BUTTON:
497 | HandleSaveButtonClick(hwnd);
498 | break;
499 | case ID_UPDATE_BUTTON:
500 | HandleUpdateButtonClick(hwnd);
501 | break;
502 | }
503 | break;
504 | }
505 | case WM_MIN:
506 | HandleMinimizeCommand((HWND)lParam);
507 | break;
508 | case WM_X:
509 | HandleCloseCommand((HWND)lParam);
510 | break;
511 | case WM_REMTRAY:
512 | RestoreWindowFromTray((HWND)lParam);
513 | break;
514 | case WM_REFRTRAY:
515 | RefreshWindowInTray((HWND)lParam);
516 | break;
517 | case WM_TRAYCMD:
518 | {
519 | switch ((UINT)lParam) {
520 | case NIN_SELECT:
521 | {
522 | if (IsWindowVisible(hwndItems[wParam])) {
523 | if (IsTopWindow(hwndItems[wParam])) {
524 | MinimizeWindowToTray(hwndItems[wParam]);
525 | }
526 | else {
527 | ShowWindow(hwndItems[wParam], SW_RESTORE);
528 | SetForegroundWindow(hwndItems[wParam]);
529 | }
530 | }
531 | else {
532 | RestoreWindowFromTray(hwndItems[wParam]);
533 | }
534 | break;
535 | }
536 | case WM_CONTEXTMENU:
537 | hwndForMenu = hwndItems[wParam];
538 | ExecuteMenu();
539 | break;
540 | case WM_MOUSEMOVE:
541 | RefreshWindowInTray(hwndItems[wParam]);
542 | break;
543 | }
544 | break;
545 | }
546 | case WM_SYSCOMMAND:
547 | {
548 | if (wParam == SC_CLOSE) {
549 | SendMessage(GetForegroundWindow(), WM_SYSCOMMAND, SC_MINIMIZE, 0);
550 | }
551 | break;
552 | }
553 | case WM_NOTIFY:
554 | HandleListViewNotifications(hwnd, lParam);
555 | break;
556 | case WM_DESTROY:
557 | {
558 | NOTASKBAR = false;
559 | for (int i = 0; i < MAXTRAYITEMS; i++) {
560 | if (hwndItems[i]) {
561 | RestoreWindowFromTray(hwndItems[i]);
562 | RemoveWindowFromTray(hwndItems[i]);
563 | }
564 | }
565 | if (hLib) {
566 | UnRegisterHook();
567 | UnhookWinEvent(hEventHook);
568 | FreeLibrary(hLib);
569 | }
570 | RefreshTray();
571 | CleanupSharedMemory();
572 |
573 | // Your existing PostQuitMessage call
574 | PostQuitMessage(0);
575 | break;
576 | }
577 | default:
578 | if (msg == WM_TASKBAR_CREATED) {
579 | for (int i = 0; i < MAXTRAYITEMS; i++) {
580 | if (hwndItems[i]) {
581 | AddToTray(i);
582 | }
583 | }
584 | }
585 | break;
586 | }
587 | return DefWindowProc(hwnd, msg, wParam, lParam);
588 | }
589 |
590 | int WINAPI WinMain(_In_ HINSTANCE hInst, _In_opt_ HINSTANCE /*hPrevInstance*/, _In_ LPSTR /*szCmdLine*/, _In_ int /*iCmdShow*/)
591 | {
592 | hInstance = hInst;
593 | InitializeSharedMemory();
594 |
595 | CheckForUpdates();
596 |
597 | LoadSettings();
598 |
599 | HWND existingApp = FindWindow(NAME, NAME);
600 | if (existingApp) {
601 | RefreshTray();
602 | MessageBox(NULL, L"Trayy is already running.", NAME, MB_OK | MB_ICONINFORMATION);
603 | return 0;
604 | }
605 |
606 | if (!(hLib = LoadLibrary(L"hook.dll"))) {
607 | MessageBox(NULL, L"Error loading hook.dll.", NAME, MB_OK | MB_ICONERROR);
608 | return 0;
609 | }
610 |
611 | if (!RegisterHook(hLib)) {
612 | MessageBox(NULL, L"Error setting hook procedure.", NAME, MB_OK | MB_ICONERROR);
613 | return 0;
614 | }
615 |
616 | hEventHook = SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, NULL, WinEventProc, 0, 0, WINEVENT_OUTOFCONTEXT);
617 | if (!hEventHook) {
618 | MessageBox(NULL, L"Error setting event hook procedure.", NAME, MB_OK | MB_ICONERROR);
619 | return 0;
620 | }
621 |
622 | for (int i = 0; i < MAXTRAYITEMS; i++) {
623 | hwndItems[i] = NULL;
624 | }
625 |
626 | WM_TASKBAR_CREATED = RegisterWindowMessage(L"TaskbarCreated");
627 |
628 |
629 | InitializeUI(hInstance);
630 | ShowAppInterface(true);
631 |
632 | MinimizeAllInBackground();
633 | RefreshTray();
634 | if (updateAvailable) {
635 | SetTrayIconUpdate();
636 | }
637 |
638 | MSG msg;
639 | while (GetMessage(&msg, nullptr, 0, 0)) {
640 | TranslateMessage(&msg);
641 | DispatchMessage(&msg);
642 | }
643 |
644 | return (int)msg.wParam;
645 | }
646 |
--------------------------------------------------------------------------------
/Trayy.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #define LEAN_AND_MEAN
3 | #define UNICODE
4 | #include
5 | #include
6 | #include
7 |
8 | #define MAXTRAYITEMS 64
9 | #define MAX_SPECIAL_APPS 16
10 |
11 | #define NAME L"Trayy"
12 | #define VERSION L"v1.0"
13 | #define SETTINGS_FILE L".settings.ini"
14 | #define ABOUT_URL L"https://www.github.com/alirezagsm/Trayy"
15 |
16 | #define IDI_ICON1 101
17 | #define IDI_ICON2 102
18 | #define ID_CHECKBOX1 103
19 | #define ID_CHECKBOX2 104
20 | #define ID_BUTTON 105
21 | #define ID_UPDATE_BUTTON 106
22 | #define ID_APPLIST 107
23 | #define ID_GUI 108
24 |
25 | #define WM_MIN 0x0401
26 | #define WM_X 0x0402
27 | #define WM_REMTRAY 0x0403
28 | #define WM_REFRTRAY 0x0404
29 | #define WM_TRAYCMD 0x0405
30 | #define IDM_RESTORE 0x1001
31 | #define IDM_CLOSE 0x1002
32 | #define IDM_EXIT 0x1003
33 | #define IDM_LIST 0x1004
34 | #define IDM_ABOUT 0x1005
35 |
36 | #define SHARED_MEM_NAME L"TraySpecialApps"
37 | #define SHARED_MEM_SIZE (MAX_SPECIAL_APPS * MAX_PATH * sizeof(wchar_t))
38 |
39 | #define DLLIMPORT __declspec(dllexport)
40 |
41 | // Shared Memory
42 | typedef struct {
43 | int count;
44 | wchar_t specialApps[MAX_SPECIAL_APPS][MAX_PATH];
45 | } SpecialAppsSharedData;
46 |
47 | // Global access
48 | extern HWND hwndMain;
49 | extern HWND hwndBase;
50 | extern HWND hwndForMenu;
51 | extern HINSTANCE hInstance;
52 | extern std::unordered_set appNames;
53 | extern std::unordered_set specialAppNames;
54 | extern bool HOOKBOTH;
55 | extern bool NOTASKBAR;
56 | extern bool updateAvailable;
57 |
58 | // Hook-related functions
59 | BOOL DLLIMPORT RegisterHook(HMODULE);
60 | void DLLIMPORT UnRegisterHook();
61 | extern HANDLE hSharedMemory;
62 | extern SpecialAppsSharedData* pSharedData;
63 |
64 | // Trayy.cpp
65 | void MinimizeWindowToTray(HWND hwnd);
66 | LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
67 | bool appCheck(HWND lParam, bool restore = false);
68 | void MinimizeAll();
69 | void RefreshTray();
70 | void SaveSettings();
71 |
72 |
73 | // Trayy_UI.cpp
74 | void InitializeUI(HINSTANCE hInstance);
75 | void ShowAppInterface(bool minimizeToTray);
76 | void ExecuteMenu();
77 | void HandleMinimizeCommand(HWND hwnd);
78 | void HandleCloseCommand(HWND hwnd);
79 | void HandleCheckboxClick(HWND hwnd, int checkboxId);
80 | void HandleSaveButtonClick(HWND hwnd);
81 | void HandleListViewNotifications(HWND hwnd, LPARAM lParam);
82 | void HandleUpdateButtonClick(HWND hwnd);
83 | void SetTrayIconUpdate();
84 |
85 | // updater.cpp
86 | void CheckForUpdates();
87 | void CheckAndUpdate(const std::wstring& currentVersion);
88 |
--------------------------------------------------------------------------------
/Trayy.rc:
--------------------------------------------------------------------------------
1 | #include "Trayy.h"
2 | #define IDC_STATIC -1
3 | IDI_ICON1 ICON "logo.ico"
4 | IDI_ICON2 ICON "logo_update.ico"
--------------------------------------------------------------------------------
/Trayy_UI.cpp:
--------------------------------------------------------------------------------
1 | #include "Trayy.h"
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 |
10 | // UI parameters and constants
11 | #define DPI_SCALE (GetDpiForWindow(GetDesktopWindow()) / 96.0)
12 | #define WINDOW_WIDTH (int)(300 * DPI_SCALE)
13 | #define WINDOW_HEIGHT (int)(350 * DPI_SCALE)
14 | #define BUTTON_HEIGHT (int)(47 * DPI_SCALE)
15 | #define BOX_HEIGHT (int)(25 * DPI_SCALE)
16 | #define DESKTOP_PADDING (int)(20 * DPI_SCALE)
17 | #define LEFT_PADDING (int)(5 * DPI_SCALE)
18 | #define SAFETY_MARGIN (int)(5 * DPI_SCALE)
19 |
20 | // Color constants
21 | #define COLOR_TEXT_BG RGB(255, 255, 255)
22 | #define COLOR_TEXT RGB(20, 20, 20)
23 | #define COLOR_BG GetSysColor(COLOR_BTNFACE)
24 |
25 | // Font constants
26 | #define FONT_NAME L"Segoe UI"
27 | #define FONT_SIZE_NORMAL (int)(21 * DPI_SCALE)
28 | #define FONT_SIZE_BOLD (int)(17 * DPI_SCALE)
29 | #define FONT_WEIGHT_NORMAL FW_NORMAL
30 | #define FONT_WEIGHT_BOLD FW_DEMIBOLD
31 |
32 | // Global font objects to prevent memory leaks
33 | HFONT g_hFontNormal = NULL;
34 | HFONT g_hFontBold = NULL;
35 | HBRUSH g_hBackgroundBrush = NULL;
36 |
37 | void setLVItems(HWND hwndList) {
38 | ListView_DeleteAllItems(hwndList);
39 |
40 | LVITEM lvi;
41 | lvi.mask = LVIF_TEXT;
42 | lvi.iSubItem = 0;
43 | for (int i = 0; i < MAXTRAYITEMS; i++)
44 | {
45 | lvi.iItem = i;
46 | lvi.pszText = (LPWSTR)L"";
47 | ListView_InsertItem(hwndList, &lvi);
48 | }
49 | int i = 0;
50 |
51 | std::vector appNamesSorted(appNames.begin(), appNames.end());
52 | std::sort(appNamesSorted.begin(), appNamesSorted.end());
53 |
54 | for (const auto& appName : appNamesSorted)
55 | {
56 | lvi.iItem = i;
57 | std::wstring displayName = appName;
58 | if (specialAppNames.find(appName) != specialAppNames.end()) {
59 | displayName += L"*";
60 | }
61 | lvi.pszText = (LPWSTR)displayName.c_str();
62 | ListView_InsertItem(hwndList, &lvi);
63 | i++;
64 | }
65 | }
66 |
67 | void HandleSaveButtonClick(HWND hwnd) {
68 | appNames.clear();
69 | std::set uniqueAppNames;
70 |
71 | for (int i = 0; i < ListView_GetItemCount(GetDlgItem(hwnd, ID_GUI)); i++)
72 | {
73 | wchar_t buffer[256];
74 | ListView_GetItemText(GetDlgItem(hwnd, ID_GUI), i, 0, buffer, 256);
75 | if (wcslen(buffer) > 0) {
76 | uniqueAppNames.insert(buffer);
77 | }
78 | }
79 |
80 | for (const auto& appName : uniqueAppNames) {
81 | appNames.insert(appName);
82 | }
83 |
84 | SaveSettings();
85 | setLVItems(GetDlgItem(hwnd, ID_GUI));
86 | MinimizeAll();
87 | RefreshTray();
88 | }
89 |
90 | void HandleUpdateButtonClick(HWND hwnd) {
91 | std::thread([] {
92 | CheckAndUpdate(VERSION);
93 | }).detach();
94 | }
95 |
96 | void HandleListViewNotifications(HWND hwnd, LPARAM lParam) {
97 | switch (((LPNMHDR)lParam)->code)
98 | {
99 | case NM_DBLCLK:
100 | case NM_CLICK:
101 | {
102 | int index = ListView_GetNextItem(GetDlgItem(hwnd, ID_GUI), -1, LVNI_SELECTED);
103 | if (index >= 0)
104 | {
105 | ListView_EditLabel(GetDlgItem(hwnd, ID_GUI), index);
106 | SendMessage(GetDlgItem(hwnd, ID_GUI), LVM_EDITLABEL, index, 0);
107 | }
108 | break;
109 | }
110 | case LVN_ENDLABELEDIT:
111 | {
112 | NMLVDISPINFO* pDispInfo = (NMLVDISPINFO*)lParam;
113 | if (pDispInfo->item.pszText != NULL)
114 | {
115 | ListView_SetItemText(GetDlgItem(hwnd, ID_GUI), pDispInfo->item.iItem, 0, pDispInfo->item.pszText);
116 | }
117 | break;
118 | }
119 | case LVN_KEYDOWN:
120 | {
121 | LPNMLVKEYDOWN pKey = (LPNMLVKEYDOWN)lParam;
122 | switch (pKey->wVKey)
123 | {
124 | case VK_DELETE:
125 | {
126 | int index = ListView_GetNextItem(GetDlgItem(hwnd, ID_GUI), -1, LVNI_SELECTED);
127 | if (index >= 0)
128 | {
129 | ListView_DeleteItem(GetDlgItem(hwnd, ID_GUI), index);
130 | }
131 | break;
132 | }
133 | case VK_RETURN:
134 | {
135 | int index = ListView_GetNextItem(GetDlgItem(hwnd, ID_GUI), -1, LVNI_SELECTED);
136 | if (index >= 0)
137 | {
138 | ListView_EditLabel(GetDlgItem(hwnd, ID_GUI), index);
139 | }
140 | break;
141 | }
142 | case VK_F1:
143 | {
144 | ShellExecute(NULL, L"open", ABOUT_URL, NULL, NULL, SW_SHOWNORMAL);
145 | break;
146 | }
147 | case VK_UP:
148 | {
149 | int index = ListView_GetNextItem(GetDlgItem(hwnd, ID_GUI), -1, LVNI_SELECTED);
150 | if (index > 0)
151 | {
152 | ListView_SetItemState(GetDlgItem(hwnd, ID_GUI), index - 1, LVIS_SELECTED, LVIS_SELECTED);
153 | ListView_SetItemState(GetDlgItem(hwnd, ID_GUI), index, 0, LVIS_SELECTED);
154 | }
155 | break;
156 | }
157 | case VK_DOWN:
158 | {
159 | int index = ListView_GetNextItem(GetDlgItem(hwnd, ID_GUI), -1, LVNI_SELECTED);
160 | if (index < ListView_GetItemCount(GetDlgItem(hwnd, ID_GUI)) - 1)
161 | {
162 | ListView_SetItemState(GetDlgItem(hwnd, ID_GUI), index + 1, LVIS_SELECTED, LVIS_SELECTED);
163 | ListView_SetItemState(GetDlgItem(hwnd, ID_GUI), index, 0, LVIS_SELECTED);
164 | }
165 | break;
166 | }
167 | }
168 | break;
169 | }
170 | }
171 | }
172 |
173 | void HandleMinimizeCommand(HWND hwnd) {
174 | if (appCheck((HWND)hwnd)) {
175 | MinimizeWindowToTray((HWND)hwnd);
176 | }
177 | else {
178 | SendMessage(GetForegroundWindow(), WM_SYSCOMMAND, SC_MINIMIZE, 0);
179 | }
180 | }
181 |
182 | void HandleCloseCommand(HWND hwnd) {
183 | if (HOOKBOTH && appCheck((HWND)hwnd)) {
184 | MinimizeWindowToTray((HWND)hwnd);
185 | }
186 | else {
187 | SendMessage(GetForegroundWindow(), WM_SYSCOMMAND, SC_CLOSE, 0);
188 | }
189 | }
190 |
191 | void HandleCheckboxClick(HWND hwnd, int checkboxId) {
192 | if (checkboxId == ID_CHECKBOX1) {
193 | HOOKBOTH = IsDlgButtonChecked(hwnd, ID_CHECKBOX1) == BST_CHECKED;
194 | }
195 | else if (checkboxId == ID_CHECKBOX2) {
196 | NOTASKBAR = IsDlgButtonChecked(hwnd, ID_CHECKBOX2) == BST_CHECKED;
197 | }
198 | }
199 |
200 | void UpdateUIFromSettings(HWND hwnd) {
201 | SendMessage(GetDlgItem(hwnd, ID_CHECKBOX1), BM_SETCHECK, HOOKBOTH ? BST_CHECKED : BST_UNCHECKED, 0);
202 | SendMessage(GetDlgItem(hwnd, ID_CHECKBOX2), BM_SETCHECK, NOTASKBAR ? BST_CHECKED : BST_UNCHECKED, 0);
203 |
204 | setLVItems(GetDlgItem(hwnd, ID_GUI));
205 | }
206 |
207 | HWND CreateMainWindow(HINSTANCE hInstance) {
208 | RECT rect;
209 | HWND taskbar = FindWindow(L"Shell_traywnd", NULL);
210 | GetWindowRect(taskbar, &rect);
211 | int x = rect.right - WINDOW_WIDTH - DESKTOP_PADDING;
212 | int y;
213 | if (rect.top == 0) {
214 | y = rect.bottom + DESKTOP_PADDING;
215 | }
216 | else {
217 | y = rect.top - WINDOW_HEIGHT - DESKTOP_PADDING;
218 | }
219 |
220 | // Create window class
221 | WNDCLASS wc = { 0 };
222 | wc.style = 0;
223 | wc.lpfnWndProc = WndProc;
224 | wc.cbClsExtra = 0;
225 | wc.cbWndExtra = 0;
226 | wc.hInstance = hInstance;
227 | wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));
228 | wc.hCursor = NULL;
229 | wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
230 | wc.lpszMenuName = NULL;
231 | wc.lpszClassName = NAME;
232 |
233 | if (!RegisterClass(&wc)) {
234 | MessageBox(NULL, L"Error creating window class", NAME, MB_OK | MB_ICONERROR);
235 | return NULL;
236 | }
237 |
238 | // Create main window
239 | HWND hwndMain = CreateWindowEx(WS_EX_TOPMOST | WS_EX_TOOLWINDOW, NAME, NAME, WS_OVERLAPPED | WS_SYSMENU | WS_CAPTION,
240 | x, y, WINDOW_WIDTH, WINDOW_HEIGHT, hwndBase, NULL, hInstance, NULL);
241 |
242 | if (!hwndMain) {
243 | MessageBox(NULL, L"Error creating window.", NAME, MB_OK | MB_ICONERROR);
244 | return NULL;
245 | }
246 |
247 | return hwndMain;
248 | }
249 |
250 | void SetupListView(HWND hwndList, int width) {
251 | LVCOLUMN lvc;
252 | lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
253 | lvc.fmt = LVCFMT_LEFT;
254 | lvc.cx = width - GetSystemMetrics(SM_CXVSCROLL);
255 | lvc.pszText = (LPWSTR)L"";
256 | lvc.iSubItem = 0;
257 | ListView_InsertColumn(hwndList, 0, &lvc);
258 | setLVItems(hwndList);
259 | ListView_SetExtendedListViewStyle(hwndList, LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_DOUBLEBUFFER);
260 | }
261 |
262 | void ApplyUIStyles(HWND hwndMain, HWND hwndList) {
263 |
264 | // Clean up
265 | if (g_hBackgroundBrush) DeleteObject(g_hBackgroundBrush);
266 | g_hBackgroundBrush = CreateSolidBrush(COLOR_BG);
267 | SetClassLongPtr(hwndMain, GCLP_HBRBACKGROUND, reinterpret_cast(g_hBackgroundBrush));
268 | if (g_hFontNormal) DeleteObject(g_hFontNormal);
269 | if (g_hFontBold) DeleteObject(g_hFontBold);
270 |
271 | // Set parameters
272 | ListView_SetBkColor(hwndList, COLOR_BG);
273 | ListView_SetTextBkColor(hwndList, COLOR_TEXT_BG);
274 | ListView_SetTextColor(hwndList, COLOR_TEXT);
275 |
276 | g_hFontNormal = CreateFont(
277 | FONT_SIZE_NORMAL, 0, 0, 0,
278 | FONT_WEIGHT_NORMAL, FALSE, FALSE, FALSE,
279 | ANSI_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS,
280 | DEFAULT_QUALITY, DEFAULT_PITCH, FONT_NAME);
281 |
282 | g_hFontBold = CreateFont(
283 | FONT_SIZE_BOLD, 0, 0, 0,
284 | FONT_WEIGHT_BOLD, FALSE, FALSE, FALSE,
285 | ANSI_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS,
286 | DEFAULT_QUALITY, DEFAULT_PITCH, FONT_NAME);
287 |
288 | SendMessage(GetDlgItem(hwndMain, ID_GUI), WM_SETFONT, (WPARAM)g_hFontNormal, TRUE);
289 | SendMessage(GetDlgItem(hwndMain, ID_BUTTON), WM_SETFONT, (WPARAM)g_hFontBold, TRUE);
290 | SendMessage(GetDlgItem(hwndMain, ID_UPDATE_BUTTON), WM_SETFONT, (WPARAM)g_hFontBold, TRUE);
291 | SendMessage(GetDlgItem(hwndMain, ID_CHECKBOX1), WM_SETFONT, (WPARAM)g_hFontBold, TRUE);
292 | SendMessage(GetDlgItem(hwndMain, ID_CHECKBOX2), WM_SETFONT, (WPARAM)g_hFontBold, TRUE);
293 | }
294 |
295 | void SetupWindowControls(HWND hwndMain, HINSTANCE hInstance) {
296 | // Get real client size
297 | int scrollBarWidth = GetSystemMetrics(SM_CXVSCROLL);
298 | int scrollBarHeight = GetSystemMetrics(SM_CYHSCROLL);
299 | int width = WINDOW_WIDTH - scrollBarWidth + SAFETY_MARGIN;
300 | int height = WINDOW_HEIGHT - scrollBarHeight + SAFETY_MARGIN;
301 |
302 | int yOffset = 0;
303 | int updateButtonHeight = 0;
304 | // Create update button if needed
305 | if (updateAvailable) {
306 | CreateWindowEx(0, L"BUTTON", L"Update",
307 | WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
308 | -SAFETY_MARGIN, 0,
309 | width + SAFETY_MARGIN, BUTTON_HEIGHT,
310 | hwndMain, (HMENU)ID_UPDATE_BUTTON, hInstance, NULL);
311 | updateButtonHeight = BUTTON_HEIGHT;
312 | yOffset += BUTTON_HEIGHT;
313 | }
314 |
315 | // Add checkboxes
316 | HWND hwndCheckbox1 = CreateWindowEx(0, L"BUTTON", L"Send to Tray also when Closed",
317 | WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX,
318 | LEFT_PADDING, yOffset, width, BOX_HEIGHT,
319 | hwndMain, (HMENU)ID_CHECKBOX1, hInstance, NULL);
320 | yOffset += BOX_HEIGHT;
321 |
322 | HWND hwndCheckbox2 = CreateWindowEx(0, L"BUTTON", L"Do not show on Taskbar",
323 | WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX,
324 | LEFT_PADDING, yOffset, width, BOX_HEIGHT,
325 | hwndMain, (HMENU)ID_CHECKBOX2, hInstance, NULL);
326 | yOffset += BOX_HEIGHT;
327 |
328 | SendMessage(hwndCheckbox1, BM_SETCHECK, HOOKBOTH ? BST_CHECKED : BST_UNCHECKED, 0);
329 | SendMessage(hwndCheckbox2, BM_SETCHECK, NOTASKBAR ? BST_CHECKED : BST_UNCHECKED, 0);
330 |
331 | // Create list view
332 | HWND hwndList = CreateWindowEx(0, WC_LISTVIEW, L"",
333 | WS_CHILD | WS_VISIBLE | LVS_REPORT | LVS_EDITLABELS | LVS_NOCOLUMNHEADER,
334 | 0, yOffset, width,
335 | height - (BUTTON_HEIGHT + BOX_HEIGHT + BOX_HEIGHT + scrollBarHeight + updateButtonHeight),
336 | hwndMain, (HMENU)ID_GUI, hInstance, NULL);
337 |
338 | SetupListView(hwndList, width);
339 |
340 | // Create save button
341 | CreateWindowEx(0, L"BUTTON", L"Save",
342 | WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
343 | -SAFETY_MARGIN, height - (BUTTON_HEIGHT + scrollBarHeight) - SAFETY_MARGIN,
344 | width + SAFETY_MARGIN, BUTTON_HEIGHT,
345 | hwndMain, (HMENU)ID_BUTTON, hInstance, NULL);
346 |
347 | ApplyUIStyles(hwndMain, hwndList);
348 | }
349 |
350 | void CleanupResources() {
351 |
352 | if (g_hFontNormal) {
353 | DeleteObject(g_hFontNormal);
354 | g_hFontNormal = NULL;
355 | }
356 | if (g_hFontBold) {
357 | DeleteObject(g_hFontBold);
358 | g_hFontBold = NULL;
359 | }
360 |
361 | if (g_hBackgroundBrush) {
362 | DeleteObject(g_hBackgroundBrush);
363 | g_hBackgroundBrush = NULL;
364 | }
365 | }
366 |
367 | void ExecuteMenu() {
368 | HMENU hMenu;
369 | POINT point;
370 |
371 | hMenu = CreatePopupMenu();
372 | if (!hMenu) {
373 | MessageBox(NULL, L"Error creating menu.", L"Trayy", MB_OK | MB_ICONERROR);
374 | return;
375 | }
376 | wchar_t buffer[256];
377 | GetWindowText(hwndForMenu, buffer, 256);
378 | if (wcscmp(buffer, NAME) == 0) {
379 | AppendMenu(hMenu, MF_STRING, IDM_RESTORE, L"App List");
380 | AppendMenu(hMenu, MF_STRING, IDM_ABOUT, L"About");
381 | AppendMenu(hMenu, MF_SEPARATOR, 0, NULL); //--------------
382 | AppendMenu(hMenu, MF_STRING, IDM_EXIT, L"Exit Trayy");
383 | }
384 | else {
385 | AppendMenu(hMenu, MF_STRING, IDM_RESTORE, L"Restore");
386 | AppendMenu(hMenu, MF_STRING, IDM_CLOSE, L"Close");
387 | }
388 | GetCursorPos(&point);
389 |
390 | SetForegroundWindow(hwndMain);
391 | TrackPopupMenu(hMenu, TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_RIGHTALIGN | TPM_BOTTOMALIGN, point.x, point.y, 0, hwndMain, NULL);
392 |
393 | PostMessage(hwndMain, WM_USER, 0, 0);
394 | DestroyMenu(hMenu);
395 | }
396 |
397 | void InitializeUI(HINSTANCE hInstance) {
398 | hwndMain = CreateMainWindow(hInstance);
399 | if (hwndMain) {
400 | SetupWindowControls(hwndMain, hInstance);
401 | }
402 | hwndBase = CreateWindowEx(0, WC_STATIC, NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, hInstance, NULL);
403 | }
404 |
405 | void ShowAppInterface(bool minimizeToTray) {
406 | if (minimizeToTray) {
407 | MinimizeWindowToTray(hwndMain);
408 | }
409 | else {
410 | ShowWindow(hwndMain, SW_SHOW);
411 | SetForegroundWindow(hwndMain);
412 | }
413 | }
414 |
--------------------------------------------------------------------------------
/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alirezagsm/Trayy/adc256a181ef8f10aa334a8a8121dbaf6528aef4/demo.gif
--------------------------------------------------------------------------------
/demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alirezagsm/Trayy/adc256a181ef8f10aa334a8a8121dbaf6528aef4/demo.png
--------------------------------------------------------------------------------
/hook.cpp:
--------------------------------------------------------------------------------
1 | #include "Trayy.h"
2 | #include
3 | #include
4 |
5 | static HHOOK _hMouse = NULL;
6 | static HHOOK _hLLMouse = NULL;
7 | static HWND _hLastHit = NULL;
8 | static HANDLE _hSharedMemory = NULL;
9 | static SpecialAppsSharedData* _pSharedData = NULL;
10 |
11 | bool ActivateWindow(HWND hwnd) {
12 | if (!IsWindow(hwnd))
13 | return false;
14 |
15 | if (GetForegroundWindow() == hwnd)
16 | return true;
17 |
18 | if (IsIconic(hwnd))
19 | ShowWindow(hwnd, SW_RESTORE);
20 |
21 | SetForegroundWindow(hwnd);
22 | return (GetForegroundWindow() == hwnd);
23 | }
24 |
25 | inline void SendWindowAction(HWND hwnd, bool isMinimize) {
26 | HWND mainWindow = FindWindow(NAME, NAME);
27 | if (mainWindow) {
28 | PostMessage(mainWindow,
29 | isMinimize ? WM_MIN : WM_X,
30 | 0,
31 | reinterpret_cast(hwnd));
32 | }
33 | }
34 |
35 | bool AccessSharedMemory() {
36 | if (_pSharedData != NULL)
37 | return true;
38 |
39 | _hSharedMemory = OpenFileMapping(
40 | FILE_MAP_ALL_ACCESS,
41 | FALSE,
42 | SHARED_MEM_NAME);
43 |
44 | if (_hSharedMemory == NULL) {
45 | return false;
46 | }
47 |
48 | _pSharedData = (SpecialAppsSharedData*)MapViewOfFile(
49 | _hSharedMemory,
50 | FILE_MAP_ALL_ACCESS,
51 | 0,
52 | 0,
53 | SHARED_MEM_SIZE);
54 |
55 | if (_pSharedData == NULL) {
56 | CloseHandle(_hSharedMemory);
57 | _hSharedMemory = NULL;
58 | return false;
59 | }
60 |
61 | return true;
62 | }
63 |
64 | void ReleaseSharedMemory() {
65 | if (_pSharedData) {
66 | UnmapViewOfFile(_pSharedData);
67 | _pSharedData = NULL;
68 | }
69 |
70 | if (_hSharedMemory) {
71 | CloseHandle(_hSharedMemory);
72 | _hSharedMemory = NULL;
73 | }
74 | }
75 |
76 | bool IsSpecialApp(const std::wstring& processName) {
77 | if (!AccessSharedMemory() || !_pSharedData)
78 | return false;
79 |
80 | std::wstring nameWithoutExt = processName;
81 |
82 | size_t extPos = nameWithoutExt.rfind(L'.');
83 | if (extPos != std::wstring::npos) {
84 | nameWithoutExt = nameWithoutExt.substr(0, extPos);
85 | }
86 |
87 | for (int i = 0; i < _pSharedData->count; i++) {
88 | if (_wcsicmp(_pSharedData->specialApps[i], nameWithoutExt.c_str()) == 0) {
89 | return true;
90 | }
91 | }
92 |
93 | return false;
94 | }
95 |
96 | // Mouse hook to handle non-client area clicks
97 | LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam) {
98 | if (nCode < 0)
99 | return CallNextHookEx(_hMouse, nCode, wParam, lParam);
100 |
101 | MOUSEHOOKSTRUCT* info = reinterpret_cast(lParam);
102 | if (!info)
103 | return CallNextHookEx(_hMouse, nCode, wParam, lParam);
104 |
105 | if ((wParam == WM_NCLBUTTONDOWN) || (wParam == WM_NCLBUTTONUP)) {
106 | if (info->wHitTestCode != HTCLIENT) {
107 | const BOOL isHitMin = (info->wHitTestCode == HTMINBUTTON);
108 | const BOOL isHitX = (info->wHitTestCode == HTCLOSE);
109 |
110 | if ((wParam == WM_NCLBUTTONDOWN) && (isHitMin || isHitX)) {
111 | _hLastHit = info->hwnd;
112 | if (ActivateWindow(info->hwnd))
113 | return 1;
114 | }
115 | else if ((wParam == WM_NCLBUTTONUP) && (isHitMin || isHitX)) {
116 | if (info->hwnd == _hLastHit) {
117 | SendWindowAction(info->hwnd, isHitMin);
118 | _hLastHit = NULL;
119 | return 1;
120 | }
121 | _hLastHit = NULL;
122 | }
123 | else {
124 | _hLastHit = NULL;
125 | }
126 | }
127 | }
128 | else if ((wParam == WM_LBUTTONDOWN) || (wParam == WM_RBUTTONUP)) {
129 | _hLastHit = NULL;
130 | }
131 |
132 | return CallNextHookEx(_hMouse, nCode, wParam, lParam);
133 | }
134 |
135 | std::wstring getProcessName(HWND hwnd) {
136 | DWORD processId;
137 | GetWindowThreadProcessId(hwnd, &processId);
138 | HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processId);
139 | if (hProcess == NULL)
140 | return L"";
141 |
142 | wchar_t processName[MAX_PATH] = L"";
143 | HMODULE hMod;
144 | DWORD cbNeeded;
145 |
146 | if (EnumProcessModules(hProcess, &hMod, sizeof(hMod), &cbNeeded))
147 | {
148 | GetModuleBaseName(hProcess, hMod, processName, sizeof(processName) / sizeof(TCHAR));
149 | }
150 | CloseHandle(hProcess);
151 | return std::wstring(processName);
152 | }
153 |
154 | float GetWindowDpiScale(HWND hwnd) {
155 | UINT dpiX = 96, dpiY = 96;
156 | HMONITOR hMonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
157 | if (hMonitor) {
158 | GetDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY);
159 | }
160 | return dpiY / 96.0f;
161 | }
162 |
163 | // Low-level Mouse hook to handle special apps
164 | LRESULT CALLBACK LLMouseProc(int nCode, WPARAM wParam, LPARAM lParam) {
165 | if (nCode < 0)
166 | return CallNextHookEx(_hLLMouse, nCode, wParam, lParam);
167 |
168 | MSLLHOOKSTRUCT* info = reinterpret_cast(lParam);
169 | if (!info)
170 | return CallNextHookEx(_hLLMouse, nCode, wParam, lParam);
171 |
172 | if (wParam == WM_LBUTTONDOWN || wParam == WM_LBUTTONUP) {
173 | POINT pt = info->pt;
174 | HWND hwnd = WindowFromPoint(pt);
175 |
176 | if (!hwnd)
177 | return CallNextHookEx(_hLLMouse, nCode, wParam, lParam);
178 |
179 | std::wstring processName = getProcessName(hwnd);
180 |
181 | if (!processName.empty() && IsSpecialApp(processName)) {
182 | // Calculate position relative to window
183 | RECT windowRect;
184 | if (!GetWindowRect(hwnd, &windowRect))
185 | return CallNextHookEx(_hLLMouse, nCode, wParam, lParam);
186 |
187 | int windowWidth = windowRect.right - windowRect.left;
188 | int relativeX = pt.x - windowRect.left;
189 | int relativeY = pt.y - windowRect.top;
190 |
191 | // Get DPI scale
192 | float dpiScale = GetWindowDpiScale(hwnd);
193 |
194 | // Calculate sizes
195 | const int captionHeight = GetSystemMetrics(SM_CYCAPTION);
196 | const int frameHeight = GetSystemMetrics(SM_CYFRAME);
197 | const int borderPadding = GetSystemMetrics(SM_CXPADDEDBORDER);
198 | const int baseButtonWidth = GetSystemMetrics(SM_CXSIZE);
199 |
200 | int titleBarHeight = static_cast((captionHeight + frameHeight + borderPadding) * dpiScale) + 4;
201 | int buttonWidth = static_cast(baseButtonWidth * dpiScale * 1.431); // button width to height ratio
202 | int closeButtonLeft = windowWidth - buttonWidth;
203 | int maximizeButtonLeft = closeButtonLeft - buttonWidth;
204 | int minimizeButtonLeft = maximizeButtonLeft - buttonWidth;
205 |
206 | // Determine hits
207 | bool isInTitleBar = (relativeY < titleBarHeight);
208 | bool isHitX = isInTitleBar && (relativeX >= closeButtonLeft);
209 | bool isHitMin = isInTitleBar && (relativeX >= minimizeButtonLeft) && (relativeX < maximizeButtonLeft);
210 |
211 | // Handle button clicks for special apps
212 | if (wParam == WM_LBUTTONDOWN) {
213 | if (isHitX || isHitMin) {
214 | _hLastHit = hwnd;
215 | if (ActivateWindow(hwnd)) {
216 | return 1; // Consume the message
217 | }
218 | } else {
219 | _hLastHit = NULL;
220 | }
221 | }
222 | else if (wParam == WM_LBUTTONUP && hwnd == _hLastHit) {
223 | if (isHitX || isHitMin) {
224 | SendWindowAction(hwnd, isHitMin);
225 | _hLastHit = NULL;
226 | return 1;
227 | }
228 | }
229 | }
230 | }
231 | else if (wParam == WM_RBUTTONUP) {
232 | _hLastHit = NULL;
233 | }
234 |
235 | return CallNextHookEx(_hLLMouse, nCode, wParam, lParam);
236 | }
237 |
238 | BOOL DLLIMPORT RegisterHook(HMODULE hLib) {
239 | // Set DPI awareness
240 | if (FAILED(SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE))) {
241 | SetProcessDPIAware();
242 | }
243 |
244 | _hMouse = SetWindowsHookEx(WH_MOUSE, MouseProc, hLib, 0);
245 | _hLLMouse = SetWindowsHookEx(WH_MOUSE_LL, LLMouseProc, hLib, 0);
246 |
247 | if (_hMouse == NULL && _hLLMouse == NULL) {
248 | UnRegisterHook();
249 | return FALSE;
250 | }
251 | return TRUE;
252 | }
253 |
254 | void DLLIMPORT UnRegisterHook() {
255 | if (_hMouse) {
256 | UnhookWindowsHookEx(_hMouse);
257 | _hMouse = NULL;
258 | }
259 | if (_hLLMouse) {
260 | UnhookWindowsHookEx(_hLLMouse);
261 | _hLLMouse = NULL;
262 | }
263 | ReleaseSharedMemory();
264 | }
265 |
--------------------------------------------------------------------------------
/logo.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alirezagsm/Trayy/adc256a181ef8f10aa334a8a8121dbaf6528aef4/logo.ico
--------------------------------------------------------------------------------
/updater.cpp:
--------------------------------------------------------------------------------
1 | #include "Trayy.h"
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 |
12 | #pragma comment(lib, "winhttp.lib")
13 | #pragma comment(lib, "shlwapi.lib")
14 |
15 | std::wstring GetLatestReleaseTag() {
16 | HINTERNET hSession = WinHttpOpen(L"Trayy Updater/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, NULL, NULL, 0);
17 | if (!hSession) return L"";
18 |
19 | HINTERNET hConnect = WinHttpConnect(hSession, L"api.github.com", INTERNET_DEFAULT_HTTPS_PORT, 0);
20 | if (!hConnect) {
21 | WinHttpCloseHandle(hSession);
22 | return L"";
23 | }
24 |
25 | HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"GET", L"/repos/alirezagsm/Trayy/releases/latest", NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE);
26 | if (!hRequest) {
27 | WinHttpCloseHandle(hConnect);
28 | WinHttpCloseHandle(hSession);
29 | return L"";
30 | }
31 |
32 | if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0)) {
33 | WinHttpCloseHandle(hRequest);
34 | WinHttpCloseHandle(hConnect);
35 | WinHttpCloseHandle(hSession);
36 | return L"";
37 | }
38 |
39 | if (!WinHttpReceiveResponse(hRequest, NULL)) {
40 | WinHttpCloseHandle(hRequest);
41 | WinHttpCloseHandle(hConnect);
42 | WinHttpCloseHandle(hSession);
43 | return L"";
44 | }
45 |
46 | DWORD dwSize = 0;
47 | std::string response;
48 | do {
49 | WinHttpQueryDataAvailable(hRequest, &dwSize);
50 | if (dwSize > 0) {
51 | char* buffer = new char[dwSize + 1];
52 | ZeroMemory(buffer, dwSize + 1);
53 | DWORD dwDownloaded = 0;
54 | WinHttpReadData(hRequest, buffer, dwSize, &dwDownloaded);
55 | response.append(buffer, dwDownloaded);
56 | delete[] buffer;
57 | }
58 | } while (dwSize > 0);
59 |
60 | WinHttpCloseHandle(hRequest);
61 | WinHttpCloseHandle(hConnect);
62 | WinHttpCloseHandle(hSession);
63 |
64 | // Parse tag_name from JSON response (robust UTF-8 parsing)
65 | size_t tagPos = response.find("\"tag_name\":");
66 | if (tagPos != std::string::npos) {
67 | size_t start = response.find('"', tagPos + 11);
68 | if (start != std::string::npos) {
69 | start++;
70 | size_t end = response.find('"', start);
71 | if (end != std::string::npos) {
72 | std::string tag = response.substr(start, end - start);
73 | std::wstring_convert> converter;
74 | return converter.from_bytes(tag);
75 | }
76 | }
77 | }
78 |
79 | return L"";
80 | }
81 |
82 | void SetTrayIconUpdate() {
83 | NOTIFYICONDATA nid = { sizeof(NOTIFYICONDATA) };
84 | nid.hWnd = hwndMain;
85 | nid.uID = 0;
86 | nid.uFlags = NIF_ICON;
87 | nid.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON2));
88 | Shell_NotifyIcon(NIM_MODIFY, &nid);
89 | }
90 |
91 |
92 | bool NeedsUpdate(const std::wstring& latestVersion) {
93 | auto parseVersion = [](const std::wstring& v) -> std::vector {
94 | std::vector parts;
95 | size_t start = (v[0] == L'v' || v[0] == L'V') ? 1 : 0;
96 | size_t end = v.find(L'.', start);
97 | while (end != std::wstring::npos) {
98 | parts.push_back(std::stoi(v.substr(start, end - start)));
99 | start = end + 1;
100 | end = v.find(L'.', start);
101 | }
102 | if (start < v.size())
103 | parts.push_back(std::stoi(v.substr(start)));
104 | return parts;
105 | };
106 |
107 | std::vector curVer = parseVersion(VERSION);
108 | std::vector latVer = parseVersion(latestVersion);
109 |
110 | // Pad shorter version with zeros
111 | size_t maxLen = (std::max)(curVer.size(), latVer.size());
112 | curVer.resize(maxLen, 0);
113 | latVer.resize(maxLen, 0);
114 |
115 | for (size_t i = 0; i < maxLen; ++i) {
116 | if (latVer[i] > curVer[i]) {
117 | return true; // Update needed
118 | }
119 | if (latVer[i] < curVer[i]) {
120 | return false; // Current is newer
121 | }
122 | }
123 | // Versions are equal
124 | return false;
125 | }
126 |
127 | void CheckForUpdates() {
128 | std::wstring latestVersion = GetLatestReleaseTag();
129 | if (latestVersion.empty()) return;
130 | if (NeedsUpdate(latestVersion)) {
131 | updateAvailable = true;
132 | }
133 | }
134 |
135 | bool DownloadLatestRelease(const std::wstring& downloadUrl, const std::wstring& outputPath) {
136 | HINTERNET hSession = WinHttpOpen(L"Trayy Updater/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, NULL, NULL, 0);
137 | if (!hSession) return false;
138 |
139 | URL_COMPONENTS urlComp = { sizeof(URL_COMPONENTS) };
140 | wchar_t hostName[256], urlPath[1024];
141 | urlComp.lpszHostName = hostName;
142 | urlComp.dwHostNameLength = _countof(hostName);
143 | urlComp.lpszUrlPath = urlPath;
144 | urlComp.dwUrlPathLength = _countof(urlPath);
145 |
146 | if (!WinHttpCrackUrl(downloadUrl.c_str(), 0, 0, &urlComp)) {
147 | WinHttpCloseHandle(hSession);
148 | return false;
149 | }
150 |
151 | HINTERNET hConnect = WinHttpConnect(hSession, urlComp.lpszHostName, INTERNET_DEFAULT_HTTPS_PORT, 0);
152 | if (!hConnect) {
153 | WinHttpCloseHandle(hSession);
154 | return false;
155 | }
156 |
157 | HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"GET", urlComp.lpszUrlPath, NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE);
158 | if (!hRequest) {
159 | WinHttpCloseHandle(hConnect);
160 | WinHttpCloseHandle(hSession);
161 | return false;
162 | }
163 |
164 | if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0)) {
165 | WinHttpCloseHandle(hRequest);
166 | WinHttpCloseHandle(hConnect);
167 | WinHttpCloseHandle(hSession);
168 | return false;
169 | }
170 |
171 | if (!WinHttpReceiveResponse(hRequest, NULL)) {
172 | WinHttpCloseHandle(hRequest);
173 | WinHttpCloseHandle(hConnect);
174 | WinHttpCloseHandle(hSession);
175 | return false;
176 | }
177 |
178 | std::ofstream outFile(outputPath, std::ios::binary);
179 | if (!outFile.is_open()) {
180 | WinHttpCloseHandle(hRequest);
181 | WinHttpCloseHandle(hConnect);
182 | WinHttpCloseHandle(hSession);
183 | return false;
184 | }
185 |
186 | DWORD dwSize = 0;
187 | do {
188 | WinHttpQueryDataAvailable(hRequest, &dwSize);
189 | if (dwSize > 0) {
190 | char* buffer = new char[dwSize];
191 | DWORD dwDownloaded = 0;
192 | WinHttpReadData(hRequest, buffer, dwSize, &dwDownloaded);
193 | outFile.write(buffer, dwDownloaded);
194 | delete[] buffer;
195 | }
196 | } while (dwSize > 0);
197 |
198 | outFile.close();
199 | WinHttpCloseHandle(hRequest);
200 | WinHttpCloseHandle(hConnect);
201 | WinHttpCloseHandle(hSession);
202 |
203 | return true;
204 | }
205 |
206 | void PerformUpdate(const std::wstring& currentExePath, const std::wstring& downloadPath) {
207 | std::wstring tempDir = std::filesystem::temp_directory_path().wstring();
208 | std::wstring extractPath = tempDir + L"\\TrayyUpdate";
209 | std::wstring psScriptPath = tempDir + L"\\TrayyUpdater.ps1";
210 | std::wofstream script(psScriptPath);
211 | script << L"Expand-Archive -Path '" << downloadPath << L"' -DestinationPath '" << extractPath << L"' -Force\n";
212 | script << L"Start-Sleep -Seconds 2\n";
213 | script << L"Move-Item -Path '" << extractPath << L"\\Trayy.exe' -Destination '" << currentExePath << L"' -Force\n";
214 | script << L"Move-Item -Path '" << extractPath << L"\\hook.dll' -Destination '"
215 | << (std::filesystem::path(currentExePath).parent_path() / L"hook.dll").wstring() << L"' -Force\n";
216 | script << L"Start-Process -FilePath '" << currentExePath << L"'\n";
217 | script.close();
218 | std::wstring args = L"-ExecutionPolicy Bypass -NoProfile -File \"" + psScriptPath + L"\"";
219 | ShellExecuteW(NULL, L"open", L"powershell.exe", args.c_str(), NULL, SW_HIDE);
220 | SendMessage(GetForegroundWindow(), WM_SYSCOMMAND, SC_CLOSE, 0);
221 | ExitProcess(0);
222 | }
223 |
224 | void CheckAndUpdate(const std::wstring& currentVersion) {
225 | std::wstring latestVersion = GetLatestReleaseTag();
226 | if (latestVersion.empty()) {
227 | MessageBoxW(NULL, L"Failed to check for updates.", L"Update Checker", MB_OK | MB_ICONERROR);
228 | return;
229 | }
230 |
231 | if (latestVersion != currentVersion) {
232 | wchar_t exePath[MAX_PATH];
233 | GetModuleFileNameW(NULL, exePath, MAX_PATH);
234 | std::wstring downloadUrl = L"https://github.com/alirezagsm/Trayy/releases/download/" + latestVersion + L"/Trayy.zip";
235 | std::wstring tempDir = std::filesystem::temp_directory_path().wstring();
236 | std::wstring downloadPath = tempDir + L"\\Trayy.zip";
237 |
238 | if (DownloadLatestRelease(downloadUrl, downloadPath)) {
239 | PerformUpdate(exePath, downloadPath);
240 | }
241 | else {
242 | MessageBoxW(NULL, L"Failed to download the update.", L"Update Error", MB_OK | MB_ICONERROR);
243 | }
244 | }
245 | else {
246 | MessageBoxW(NULL, L"You are using the latest version.", L"Up to Date", MB_OK | MB_ICONINFORMATION);
247 | }
248 | }
--------------------------------------------------------------------------------