1: /*
2: ============================================================================
3: xoblite™ -> an advanced "extended shell" for Microsoft® Windows® 11
4: Copyright © 2002-2026 Karl Henrik Henriksson [qwilk/@xoblite]
5: Copyright © 2001-2004 The Blackbox for Windows Development Team
6: http://xoblite.net/ + https://github.com/xoblite/
7: ============================================================================
8:
9: Blackbox for Windows is free software, released under the
10: GNU General Public License (GPL version 2 or later), with an extension
11: that allows linking of proprietary modules under a controlled interface.
12: What this means is that plugins etc. are allowed to be released
13: under any license the author wishes. Please note, however, that the
14: original Blackbox gradient math code used in Blackbox for Windows
15: is available under the BSD license.
16:
17: http://www.fsf.org/licenses/gpl.html
18: http://www.fsf.org/licenses/gpl-faq.html#LinkingOverControlledInterface
19: http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
20:
21: This program is distributed in the hope that it will be useful,
22: but WITHOUT ANY WARRANTY; without even the implied warranty of
23: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24: GNU General Public License for more details.
25:
26: For additional license information, please read the included license.html.
27:
28: ============================================================================
29: */
30:
31: #include "..\API\BBApi.h"
32: #include "Workspaces.h"
33: #include "..\Settings\Settings.h"
34: #include "..\Toolbar\Toolbar.h"
35: #include "..\Blackbox.h"
36:
37: //===========================================================================
38:
39: const char szWorkspacesName[] = "Workspaces"; // Window class etc.
40:
41: extern Workspaces* pWorkspaces;
42: extern Settings* pSettings;
43: extern Desktop* pDesktop;
44: extern Toolbar* pToolbar;
45: extern Taskbar* pTaskbar;
46:
47: //typedef void (__stdcall *STTWTYPE)(HWND, int);
48: //extern STTWTYPE BBSwitchToThisWindow;
49:
50: int workspacesMessageSubscription[] = { BB_WORKSPACE, BB_RECONFIGURE, BB_SWITCHTON, BB_ACTIVETASK, BB_LISTDESKTOPS, BB_BRINGTOFRONT, 0 };
51:
52: //===========================================================================
53:
54: Workspaces::Workspaces(HINSTANCE hMainInstance)
55: {
56: hWorkspacesInstance = hMainInstance;
57:
58: ScreenWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN);
59: ScreenHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN);
60:
61: UpdateWorkspaceNames();
62:
63: //====================
64:
65: // Register window class...
66: WNDCLASS wc;
67: ZeroMemory(&wc,sizeof(wc));
68: wc.hInstance = hWorkspacesInstance;
69: wc.lpfnWndProc = WndProc; // Window procedure
70: wc.lpszClassName = szWorkspacesName; // Window class name
71:
72: if (!RegisterClass(&wc))
73: {
74: MessageBox(0, "Error registering workspaces window class!", szWorkspacesName, MB_OK);
75: Log("Workspaces", "Error registering window class!");
76: return;
77: }
78:
79: // Create workspaces handling window...
80: hWorkspacesWnd = CreateWindowEx(
81: WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE, // window style
82: szWorkspacesName, // window class
83: NULL, // window name
84: 0, // window parameters
85: 0, // x position
86: 0, // y position
87: 0, // window width
88: 0, // window height
89: GetBBWnd(), // owner window
90: NULL, // no menu
91: hWorkspacesInstance, // hInstance
92: NULL // no window creation data
93: );
94:
95: if (!hWorkspacesWnd)
96: {
97: UnregisterClass(szWorkspacesName, hWorkspacesInstance); // Unregister window class
98: MessageBox(0, "Error creating workspaces window!", szWorkspacesName, MB_OK);
99: Log("Workspaces", "Error creating window!");
100: return;
101: }
102:
103: //====================
104:
105: // Subscribe to Blackbox messages applicable to workspaces handling...
106: SendMessage(GetBBWnd(), BB_REGISTERMESSAGE, (WPARAM)hWorkspacesWnd, (LPARAM)workspacesMessageSubscription);
107:
108: MakeSticky(hWorkspacesWnd);
109: }
110:
111: //===========================================================================
112:
113: Workspaces::~Workspaces()
114: {
115: GatherWindows();
116:
117: // Unsubscribe to previously subscribed Blackbox messages...
118: SendMessage(GetBBWnd(), BB_UNREGISTERMESSAGE, (WPARAM)hWorkspacesWnd, (LPARAM)workspacesMessageSubscription);
119:
120: DestroyWindow(hWorkspacesWnd); // Destroy window...
121: UnregisterClass(szWorkspacesName, hWorkspacesInstance); // Unregister window class...
122:
123: workspaceNames.clear();
124: }
125:
126: //===========================================================================
127:
128: LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
129: {
130: switch (message)
131: {
132: //====================
133:
134: case BB_RECONFIGURE:
135: {
136: pWorkspaces->UpdateWorkspaceNames();
137: }
138: break;
139:
140: //====================
141:
142: case BB_SWITCHTON:
143: {
144: pWorkspaces->SwitchToWorkspace((int)wParam);
145: break;
146: }
147:
148: //====================
149:
150: case BB_ACTIVETASK:
151: {
152: // Change workspaces automatically when the active task changes? (e.g. via Alt+Tab)
153: if (pSettings->followActive) pWorkspaces->SwitchToWorkspace(pWorkspaces->GetWorkspaceByWindow((HWND)wParam));
154: }
155:
156: //====================
157:
158: case BB_LISTDESKTOPS:
159: {
160: DesktopInfo deskInfo;
161: ZeroMemory(&deskInfo, sizeof(deskInfo));
162: for (int i = 0; i < pSettings->numberOfWorkspaces; i++)
163: {
164: deskInfo.isCurrent = (pSettings->currentWorkspace == i);
165: deskInfo.number = i;
166:
167: if (pWorkspaces->workspaceNames[i] == "null")
168: {
169: sprintf_s(deskInfo.name, sizeofArray(deskInfo.name), "Workspace %d", i + 1);
170: }
171: else
172: {
173: strcpy_s(deskInfo.name, sizeofArray(deskInfo.name), pWorkspaces->workspaceNames[i].c_str());
174: }
175:
176: SendMessage((HWND)wParam, BB_DESKTOPINFO, 0, (LPARAM) &deskInfo);
177: }
178: break;
179: }
180:
181: //====================
182:
183: case BB_BRINGTOFRONT:
184: {
185: // Change window focus via message... (+ change workspace if needed)
186: int n = pWorkspaces->GetWorkspaceByWindow((HWND)lParam);
187: if (n != pSettings->currentWorkspace) pWorkspaces->SwitchToWorkspace(n);
188: // BBSwitchToThisWindow((HWND)lParam, FALSE);
189: BringWindowToTop((HWND)lParam);
190: break;
191: }
192:
193: //====================
194:
195: case BB_WORKSPACE:
196: {
197: switch (wParam)
198: {
199: case 0: // "BBWS_PreviousWorkspace"
200: pWorkspaces->PreviousWorkspace();
201: break;
202: case 1: // "BBWS_NextWorkspace"
203: pWorkspaces->NextWorkspace();
204: break;
205: case 2: // "BBWS_NewWorkspace"
206: pWorkspaces->NewWorkspace();
207: break;
208: case 3: // "BBWS_RemoveLastWorkspace"
209: pWorkspaces->RemoveLastWorkspace();
210: break;
211: case 4: // "BBWS_SWITCHTODESK"
212: pWorkspaces->SwitchToWorkspace((int)lParam);
213: break;
214: case 5: // "BBWS_GATHERWINDOWS"
215: pWorkspaces->GatherWindows();
216: break;
217: case 6: // "BBWS_MOVEWINDOWLEFT"
218: pWorkspaces->MoveWindowToPreviousWorkspace((HWND)lParam);
219: break;
220: case 7: // "BBWS_MOVEWINDOWRIGHT"
221: pWorkspaces->MoveWindowToNextWorkspace((HWND)lParam);
222: break;
223: case 8: // "BBWS_PREVWINDOW"
224: if (pTaskbar) pTaskbar->PreviousTask(!(bool)lParam, false);
225: break;
226: case 9: // "BBWS_NEXTWINDOW"
227: if (pTaskbar) pTaskbar->NextTask(!(bool)lParam, false);
228: break;
229: case 10: // "BBWS_LastWorkspace"
230: pWorkspaces->LastWorkspace();
231: break;
232:
233: //====================
234:
235: // Below: Misc stuff included for for basic compatibility
236: // with the bb(C)Lean(Mod)(Zero) modified BBApi...
237: case BBWS_TOGGLESTICKY:
238: case BBWS_MAKESTICKY:
239: case BBWS_CLEARSTICKY:
240: case BBWS_TOGGLEONTOP:
241: {
242: HWND hwnd = (HWND)lParam;
243: if (hwnd == NULL)
244: {
245: if (pTaskbar) hwnd = pTaskbar->GetActiveWindow();
246: if (hwnd == NULL) hwnd = GetForegroundWindow();
247: }
248:
249: if (message == BBWS_TOGGLESTICKY)
250: {
251: if (CheckSticky(hwnd)) RemoveSticky(hwnd);
252: else MakeSticky(hwnd);
253: }
254: else if (message == BBWS_MAKESTICKY) MakeSticky(hwnd);
255: else if (message == BBWS_CLEARSTICKY) RemoveSticky(hwnd);
256: else if (message == BBWS_TOGGLEONTOP)
257: {
258: LONG_PTR windowIsOnTop = GetWindowLongPtr(hwnd, GWL_EXSTYLE);
259: if (windowIsOnTop & WS_EX_TOPMOST) SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
260: else SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
261: }
262: }
263: break;
264:
265: //====================
266:
267: default:
268: break; // Note: Breaking as default avoids unintended behaviour if receiving unknown wParam, e.g. ones defined by the bb(C)Lean(Mod)(Zero) modified BBApi...
269: }
270: }
271: break;
272:
273: //====================
274:
275: case WM_DISPLAYCHANGE:
276: {
277: pWorkspaces->ScreenWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN);
278: pWorkspaces->ScreenHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN);
279: }
280:
281: //====================
282:
283: default:
284: return DefWindowProc(hwnd,message,wParam,lParam);
285: }
286: return 0;
287: }
288:
289: //===========================================================================
290: // Functions: EnumWindowsProc
291: // Purpose: Window enumeration process for the Workspaces window handling
292: //===========================================================================
293:
294: const long magicDWord = 0x49474541;
295:
296: BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
297: {
298: // Perform a few window validation checks...
299: // (nb. the taskbar already performs many such checks for its tasks prior to adding them, so we leverage that here too)
300: if (!IsOnTaskbar(hwnd)) return true;
301: if (CheckSticky(hwnd)) return true;
302: if (GetWindowLong(hwnd, GWLP_USERDATA) == magicDWord) return true; // For legacy backwards compatibility only (iirc this was used by the earliest bb4win sticky windows implementations)
303:
304: // Disregard Rainmeter widgets... (i.e. exclude them from the Workspaces handling altogether to avoid possible conflicts)
305: char temp[MAX_LINE_LENGTH];
306: GetClassName(hwnd, temp, MAX_LINE_LENGTH);
307: if (!_stricmp(temp, "RainmeterMeterWindow")) return true;
308:
309: //====================
310:
311: RECT r;
312: GetWindowRect(hwnd, &r);
313:
314: int addH = (int)lParam;
315: if (addH == -1)
316: {
317: int screenWidth = pWorkspaces->ScreenWidth;
318: int windowWidth = abs(r.right - r.left);
319:
320: // Initial x/width position calculation...
321: if (r.left > screenWidth)
322: {
323: r.left %= (screenWidth + 10);
324: if (r.left > screenWidth) r.left -= (screenWidth + 10);
325: r.right = r.left + windowWidth;
326: }
327: else if (r.left < -10)
328: {
329: r.left %= (screenWidth + 10);
330: while (r.left < -10) r.left += (screenWidth + 10);
331: r.right = r.left + windowWidth;
332: }
333: }
334: else
335: {
336: r.left -= addH;
337: r.right -= addH;
338: }
339:
340: HDWP hdwp = DeferWindowPos(pWorkspaces->dwp, hwnd, NULL, r.left, r.top, r.right-r.left, r.bottom-r.top, SWP_NOZORDER|SWP_NOACTIVATE);
341: if (hdwp != NULL) pWorkspaces->dwp = hdwp;
342: /*
343: else if (pSettings->debugLogging)
344: {
345: char title[MAX_LINE_LENGTH], msg[MAX_LINE_LENGTH];
346: GetWindowText(hwnd, title, MAX_LINE_LENGTH);
347: sprintf_s(msg, sizeofArray(msg), "Workspaces -> DeferWindowPos() failed! -> %s", title);
348: SendMessage(GetBBWnd(), BB_CONSOLEMESSAGE, (WPARAM)CONSOLE_ERROR_MESSAGE, (LPARAM)msg);
349: }
350: */
351: return true;
352: }
353:
354: //===========================================================================
355: // Functions: PreviousWorkspace / NextWorkspace / LastWorkspace
356: // Purpose: ...
357: //===========================================================================
358:
359: void Workspaces::PreviousWorkspace()
360: {
361: int workspace;
362: if (pSettings->currentWorkspace > 0) workspace = pSettings->currentWorkspace - 1;
363: else workspace = pSettings->numberOfWorkspaces - 1;
364: SwitchToWorkspace(workspace);
365: }
366:
367: //====================
368:
369: void Workspaces::NextWorkspace()
370: {
371: int workspace;
372: if (pSettings->currentWorkspace < (pSettings->numberOfWorkspaces - 1)) workspace = pSettings->currentWorkspace + 1;
373: else workspace = 0;
374: SwitchToWorkspace(workspace);
375: }
376:
377: //====================
378:
379: void Workspaces::LastWorkspace()
380: {
381: SwitchToWorkspace(pSettings->lastWorkspace);
382: }
383:
384: //===========================================================================
385: // Function: SwitchToWorkspace
386: // Purpose: ...
387: //===========================================================================
388:
389: void Workspaces::SwitchToWorkspace(int workspace)
390: {
391: if ((workspace != pSettings->currentWorkspace) && (workspace >= 0) && (workspace < pSettings->numberOfWorkspaces))
392: {
393: int addH = ((workspace % pSettings->numberOfWorkspaces) - (pSettings->currentWorkspace % pSettings->numberOfWorkspaces)) * (ScreenWidth + 10);
394:
395: dwp = BeginDeferWindowPos(0);
396: EnumWindows(EnumWindowsProc, (LPARAM)addH);
397: EndDeferWindowPos(dwp);
398:
399: pSettings->lastWorkspace = pSettings->currentWorkspace;
400: pSettings->currentWorkspace = workspace;
401:
402: PlaySoundFX(SFX_WORKSPACE_CHANGE);
403: pDesktop->UpdateDesktopWindow();
404:
405: if (pSettings->wallpaperPerWorkspace)
406: {
407: pSettings->wallpaperPerWorkspaceSemaphore = true;
408: _beginthread(ExecuteRootCommand, 0, NULL);
409: }
410:
411: if (!pSettings->accessLock)
412: {
413: // Update the toolbar... (including workspace label, taskbar if applicable, etc)
414: if (pToolbar) pToolbar->UpdatePosition();
415: }
416: }
417: }
418:
419: //===========================================================================
420: // Functions: NewWorkspace / RemoveLastWorkspace
421: // Purpose: Adds a new / removes the last workspace
422: //===========================================================================
423:
424: void Workspaces::NewWorkspace()
425: {
426: // Note:
427: // "Workspace 254" is used as a wildcard to e.g. list tasks on all workspaces
428: // "Workspace 255" is used for minimized tasks
429: if (pSettings->numberOfWorkspaces == 253) return; // -> We've reached the theoretical maximum number of workspaces... ;)
430:
431: pSettings->numberOfWorkspaces++;
432: UpdateWorkspaceNames();
433:
434: if (pTaskbar)
435: {
436: if (pSettings->taskbarMode != TASKBAR_MODE_HIDDEN) pTaskbar->cachedTaskButtonsExist = false;
437: }
438: if (pToolbar)
439: {
440: // Update the toolbar... (including workspace label, taskbar if applicable, etc)
441: pToolbar->cachedBitmapsExist = false;
442: pToolbar->UpdatePosition();
443: }
444: if (pDesktop) pDesktop->UpdateDesktopWindow(); // Update the number of workspace indicators on the desktop...
445:
446: WriteInt(pSettings->xobrcDefaultFile, "xoblite.workspaces:", pSettings->numberOfWorkspaces);
447: }
448:
449: //====================
450:
451: void Workspaces::RemoveLastWorkspace()
452: {
453: if (pSettings->numberOfWorkspaces != 1)
454: {
455: if (pSettings->currentWorkspace == pSettings->numberOfWorkspaces - 1)
456: {
457: SwitchToWorkspace(pSettings->currentWorkspace-1);
458: }
459:
460: pSettings->numberOfWorkspaces--;
461: UpdateWorkspaceNames();
462:
463: if (pTaskbar)
464: {
465: for (int i = 0; i < (int)pTaskbar->taskList.size(); i++)
466: {
467: if (!CheckSticky(pTaskbar->taskList[i]->hwnd))
468: {
469: if (pTaskbar->taskList[i]->workspace > (pSettings->numberOfWorkspaces - 1)) pTaskbar->taskList[i]->workspace = pSettings->numberOfWorkspaces - 1;
470: }
471: }
472:
473: if (pSettings->taskbarMode != TASKBAR_MODE_HIDDEN) pTaskbar->cachedTaskButtonsExist = false;
474: }
475: if (pToolbar)
476: {
477: // Update the toolbar... (including workspace label, taskbar if applicable, etc)
478: pToolbar->cachedBitmapsExist = false;
479: pToolbar->UpdatePosition();
480: }
481: if (pDesktop) pDesktop->UpdateDesktopWindow(); // Update the number of workspace indicators on the desktop...
482: }
483:
484: WriteInt(pSettings->xobrcDefaultFile, "xoblite.workspaces:", pSettings->numberOfWorkspaces);
485: }
486:
487: //===========================================================================
488: // Function: UpdateWorkspaceNames
489: // Purpose: Extracts individual workspace names from
490: // a single xoblite.rc configuration setting
491: //===========================================================================
492:
493: void Workspaces::UpdateWorkspaceNames()
494: {
495: workspaceNames.clear();
496: char buffer[MAX_LINE_LENGTH], szBuffer[MAX_PATH];
497: strcpy_s(buffer, sizeofArray(buffer), pSettings->workspaceNames);
498:
499: for (int i = 0; i < pSettings->numberOfWorkspaces; i++)
500: {
501: strcpy_s(buffer, sizeofArray(buffer), Tokenize(buffer, szBuffer, ","));
502: if (!strcmp(szBuffer,"")) sprintf_s(szBuffer, sizeofArray(szBuffer), "Workspace #%d", i+1); // -> Default if no name configured
503: workspaceNames.push_back(string(szBuffer));
504: }
505: }
506:
507: //===========================================================================
508: // Functions: GetWorkspaceByWindow / GetWorkspaceByRect
509: // Purpose: ...
510: //===========================================================================
511:
512: int Workspaces::GetWorkspaceByWindow(HWND window)
513: {
514: if (!window || IsIconic(window)) return 255;
515:
516: if (pTaskbar)
517: {
518: int i = pTaskbar->FindTask(window);
519: if (i >= 0) return pTaskbar->taskList[i]->workspace;
520: }
521:
522: // Use legacy GetWorkspaceByRect() based mechanism as fallback... (see below)
523: RECT r;
524: GetWindowRect(window, &r);
525: return (GetWorkspaceByRect(r));
526: }
527:
528: //====================
529:
530: int Workspaces::GetWorkspaceByRect(RECT r)
531: {
532: int offsetX = pSettings->currentWorkspace % pSettings->numberOfWorkspaces;
533: int offsetY = pSettings->currentWorkspace / pSettings->numberOfWorkspaces;
534: int workspace;
535: RECT tempRect, workspaceRect;
536:
537: workspaceRect.left = workspaceRect.top = 0;
538: workspaceRect.right = ScreenWidth;
539: workspaceRect.bottom = ScreenHeight;
540:
541: if (IntersectRect(&tempRect, &r, &workspaceRect)) return pSettings->currentWorkspace;
542:
543: r.left += offsetX * (ScreenWidth + 10);
544: r.top += offsetY * (ScreenHeight + 10);
545:
546: offsetX = ((r.left + 10) / (ScreenWidth + 10));
547: offsetY = ((r.top + 10) / (ScreenHeight + 10));
548:
549: workspace = (offsetY * pSettings->numberOfWorkspaces) + offsetX;
550:
551: if (workspace < 0 || workspace > pSettings->numberOfWorkspaces) workspace = pSettings->currentWorkspace;
552:
553: return workspace;
554: }
555:
556: //===========================================================================
557: // Function: GatherWindows
558: // Purpose: Moves all (top-level) windows to the current workspace
559: //===========================================================================
560:
561: void Workspaces::GatherWindows()
562: {
563: dwp = BeginDeferWindowPos(0);
564: EnumWindows(EnumWindowsProc, (LPARAM)-1);
565: EndDeferWindowPos(dwp);
566:
567: if (pTaskbar)
568: {
569: for (int i = 0; i < (int)pTaskbar->taskList.size(); i++)
570: {
571: if (!CheckSticky(pTaskbar->taskList[i]->hwnd)) pTaskbar->taskList[i]->workspace = pSettings->currentWorkspace;
572: }
573: }
574:
575: if (!pSettings->accessLock)
576: {
577: // Update the toolbar... (including workspace label, taskbar if applicable, etc)
578: if (pToolbar) pToolbar->UpdatePosition();
579: }
580: }
581:
582: //===========================================================================
583: // Functions: MoveWindowToWorkspace +
584: // MoveWindowToPreviousWorkspace / MoveWindowToNextWorkspace
585: // Purpose: ...
586: //===========================================================================
587:
588: void Workspaces::MoveWindowToWorkspace(HWND window, int workspace)
589: {
590: if (CheckSticky(window)) return;
591: if ((workspace < 0) || (workspace >= pSettings->numberOfWorkspaces)) return;
592:
593: SwitchToWorkspace(GetWorkspaceByWindow(window));
594: // BBSwitchToThisWindow(window, FALSE);
595: BringWindowToTop(window);
596:
597: MakeSticky(window);
598: SwitchToWorkspace(workspace);
599: RemoveSticky(window);
600:
601: if (pTaskbar)
602: {
603: int i = pTaskbar->FindTask(window);
604: if (i >= 0) pTaskbar->taskList[i]->workspace = workspace;
605: }
606:
607: if (!pSettings->accessLock)
608: {
609: // Do we need to update the toolbar?
610: if ((pSettings->taskbarMode != TASKBAR_MODE_HIDDEN) && pSettings->taskbarCurrentOnly && pToolbar)
611: {
612: pTaskbar->cachedTaskButtonsExist = false;
613: if (pToolbar) pToolbar->UpdatePosition();
614: }
615: }
616: }
617:
618: //====================
619:
620: void Workspaces::MoveWindowToPreviousWorkspace(HWND window)
621: {
622: int workspace;
623: if (pSettings->currentWorkspace == 0) workspace = pSettings->numberOfWorkspaces - 1;
624: else workspace = pSettings->currentWorkspace - 1;
625: MoveWindowToWorkspace(window, workspace);
626: }
627:
628: //====================
629:
630: void Workspaces::MoveWindowToNextWorkspace(HWND window)
631: {
632: int workspace;
633: if (pSettings->currentWorkspace == (pSettings->numberOfWorkspaces - 1)) workspace = 0;
634: else workspace = pSettings->currentWorkspace + 1;
635: MoveWindowToWorkspace(window, workspace);
636: }
637:
638: //===========================================================================
639: