1 /**
2 * @file taskbar.c
3 * @author Joe Wingbermuehle
4 * @date 2005-2006
5 *
6 * @brief Task list tray component.
7 *
8 */
9
10 #include "jwm.h"
11 #include "taskbar.h"
12 #include "tray.h"
13 #include "timing.h"
14 #include "main.h"
15 #include "client.h"
16 #include "clientlist.h"
17 #include "color.h"
18 #include "popup.h"
19 #include "button.h"
20 #include "cursor.h"
21 #include "icon.h"
22 #include "error.h"
23 #include "winmenu.h"
24 #include "screen.h"
25 #include "settings.h"
26 #include "event.h"
27 #include "misc.h"
28 #include "desktop.h"
29
30 typedef struct TaskBarType {
31
32 TrayComponentType *cp;
33 struct TaskBarType *next;
34
35 int maxItemWidth;
36 int userHeight;
37 int itemHeight;
38 int itemWidth;
39 LayoutType layout;
40 char labeled;
41
42 Pixmap buffer;
43
44 TimeType mouseTime;
45 int mousex, mousey;
46
47 } TaskBarType;
48
49 typedef struct ClientEntry {
50 ClientNode *client;
51 struct ClientEntry *next;
52 struct ClientEntry *prev;
53 } ClientEntry;
54
55 typedef struct TaskEntry {
56 ClientEntry *clients;
57 struct TaskEntry *next;
58 struct TaskEntry *prev;
59 } TaskEntry;
60
61 static TaskBarType *bars;
62 static TaskEntry *taskEntries;
63 static TaskEntry *taskEntriesTail;
64
65 static void ComputeItemSize(TaskBarType *tp);
66 static char ShouldShowEntry(const TaskEntry *tp);
67 static char ShouldFocusEntry(const TaskEntry *tp);
68 static TaskEntry *GetEntry(TaskBarType *bar, int x, int y);
69 static void Render(const TaskBarType *bp);
70 static void ShowClientList(TaskBarType *bar, TaskEntry *tp);
71 static void RunTaskBarCommand(MenuAction *action, unsigned button);
72
73 static void SetSize(TrayComponentType *cp, int width, int height);
74 static void Create(TrayComponentType *cp);
75 static void Resize(TrayComponentType *cp);
76 static void ProcessTaskButtonEvent(TrayComponentType *cp,
77 int x, int y, int mask);
78 static void MinimizeGroup(const TaskEntry *tp);
79 static void FocusGroup(const TaskEntry *tp);
80 static void ProcessTaskMotionEvent(TrayComponentType *cp,
81 int x, int y, int mask);
82 static void SignalTaskbar(const TimeType *now, int x, int y, Window w,
83 void *data);
84
85 /** Initialize task bar data. */
InitializeTaskBar(void)86 void InitializeTaskBar(void)
87 {
88 bars = NULL;
89 taskEntries = NULL;
90 taskEntriesTail = NULL;
91 }
92
93 /** Shutdown the task bar. */
ShutdownTaskBar(void)94 void ShutdownTaskBar(void)
95 {
96 TaskBarType *bp;
97 for(bp = bars; bp; bp = bp->next) {
98 JXFreePixmap(display, bp->buffer);
99 }
100 }
101
102 /** Destroy task bar data. */
DestroyTaskBar(void)103 void DestroyTaskBar(void)
104 {
105 TaskBarType *bp;
106 while(bars) {
107 bp = bars->next;
108 UnregisterCallback(SignalTaskbar, bars);
109 Release(bars);
110 bars = bp;
111 }
112 }
113
114 /** Create a new task bar tray component. */
CreateTaskBar()115 TrayComponentType *CreateTaskBar()
116 {
117
118 TrayComponentType *cp;
119 TaskBarType *tp;
120
121 tp = Allocate(sizeof(TaskBarType));
122 tp->next = bars;
123 bars = tp;
124 tp->itemHeight = 0;
125 tp->itemWidth = 0;
126 tp->userHeight = 0;
127 tp->maxItemWidth = 0;
128 tp->layout = LAYOUT_HORIZONTAL;
129 tp->labeled = 1;
130 tp->mousex = -settings.doubleClickDelta;
131 tp->mousey = -settings.doubleClickDelta;
132 tp->mouseTime.seconds = 0;
133 tp->mouseTime.ms = 0;
134
135 cp = CreateTrayComponent();
136 cp->object = tp;
137 tp->cp = cp;
138
139 cp->SetSize = SetSize;
140 cp->Create = Create;
141 cp->Resize = Resize;
142 cp->ProcessButtonPress = ProcessTaskButtonEvent;
143 cp->ProcessMotionEvent = ProcessTaskMotionEvent;
144
145 RegisterCallback(settings.popupDelay / 2, SignalTaskbar, tp);
146
147 return cp;
148
149 }
150
151 /** Set the size of a task bar tray component. */
SetSize(TrayComponentType * cp,int width,int height)152 void SetSize(TrayComponentType *cp, int width, int height)
153 {
154 TaskBarType *tp = (TaskBarType*)cp->object;
155 if(width == 0) {
156 tp->layout = LAYOUT_HORIZONTAL;
157 } else if(height == 0) {
158 tp->layout = LAYOUT_VERTICAL;
159 } else if(width > height) {
160 tp->layout = LAYOUT_HORIZONTAL;
161 } else {
162 tp->layout = LAYOUT_VERTICAL;
163 }
164 }
165
166 /** Initialize a task bar tray component. */
Create(TrayComponentType * cp)167 void Create(TrayComponentType *cp)
168 {
169 TaskBarType *tp = (TaskBarType*)cp->object;
170 cp->pixmap = JXCreatePixmap(display, rootWindow, cp->width, cp->height,
171 rootDepth);
172 tp->buffer = cp->pixmap;
173 ClearTrayDrawable(cp);
174 }
175
176 /** Resize a task bar tray component. */
Resize(TrayComponentType * cp)177 void Resize(TrayComponentType *cp)
178 {
179 TaskBarType *tp = (TaskBarType*)cp->object;
180 if(tp->buffer != None) {
181 JXFreePixmap(display, tp->buffer);
182 }
183 cp->pixmap = JXCreatePixmap(display, rootWindow, cp->width, cp->height,
184 rootDepth);
185 tp->buffer = cp->pixmap;
186 ClearTrayDrawable(cp);
187 }
188
189 /** Determine the size of items in the task bar. */
ComputeItemSize(TaskBarType * tp)190 void ComputeItemSize(TaskBarType *tp)
191 {
192 TrayComponentType *cp = tp->cp;
193 if(tp->layout == LAYOUT_VERTICAL) {
194
195 if(tp->userHeight > 0) {
196 tp->itemHeight = tp->userHeight;
197 } else {
198 tp->itemHeight = GetStringHeight(FONT_TASKLIST) + 12;
199 }
200 tp->itemWidth = cp->width;
201
202 } else {
203
204 TaskEntry *ep;
205 unsigned itemCount = 0;
206
207 tp->itemHeight = cp->height;
208 for(ep = taskEntries; ep; ep = ep->next) {
209 if(ShouldShowEntry(ep)) {
210 itemCount += 1;
211 }
212 }
213 if(itemCount == 0) {
214 return;
215 }
216
217 tp->itemWidth = Max(1, cp->width / itemCount);
218 if(!tp->labeled) {
219 tp->itemWidth = Min(tp->itemHeight, tp->itemWidth);
220 }
221 if(tp->maxItemWidth > 0) {
222 tp->itemWidth = Min(tp->maxItemWidth, tp->itemWidth);
223 }
224 }
225 }
226
227 /** Process a task list button event. */
ProcessTaskButtonEvent(TrayComponentType * cp,int x,int y,int mask)228 void ProcessTaskButtonEvent(TrayComponentType *cp, int x, int y, int mask)
229 {
230
231 TaskBarType *bar = (TaskBarType*)cp->object;
232 TaskEntry *entry = GetEntry(bar, x, y);
233
234 if(entry) {
235 ClientEntry *cp;
236 ClientNode *focused = NULL;
237 char onTop = 0;
238 char hasActive = 0;
239
240 switch(mask) {
241 case Button1: /* Raise or minimize items in this group. */
242 for(cp = entry->clients; cp; cp = cp->next) {
243 int layer;
244 char foundTop = 0;
245 if(cp->client->state.status & STAT_MINIMIZED) {
246 continue;
247 } else if(!ShouldFocus(cp->client, 0)) {
248 continue;
249 }
250 for(layer = LAST_LAYER; layer >= FIRST_LAYER; layer--) {
251 ClientNode *np;
252 for(np = nodes[layer]; np; np = np->next) {
253 if(np->state.status & STAT_MINIMIZED) {
254 continue;
255 } else if(!ShouldFocus(np, 0)) {
256 continue;
257 }
258 if(np == cp->client) {
259 const char isActive = (np->state.status & STAT_ACTIVE)
260 && IsClientOnCurrentDesktop(np);
261 onTop = onTop || !foundTop;
262 if(isActive) {
263 focused = np;
264 }
265 if(!(cp->client->state.status
266 & (STAT_CANFOCUS | STAT_TAKEFOCUS))
267 || isActive) {
268 hasActive = 1;
269 }
270 }
271 if(hasActive && onTop) {
272 goto FoundActiveAndTop;
273 }
274 foundTop = 1;
275 }
276 }
277 }
278 FoundActiveAndTop:
279 if(hasActive && onTop) {
280 ClientNode *nextClient = NULL;
281 int i;
282
283 /* Try to find a client on a different desktop. */
284 for(i = 0; i < settings.desktopCount - 1; i++) {
285 const int target = (currentDesktop + i + 1)
286 % settings.desktopCount;
287 for(cp = entry->clients; cp; cp = cp->next) {
288 ClientNode *np = cp->client;
289 if(!ShouldFocus(np, 0)) {
290 continue;
291 } else if(np->state.status & STAT_STICKY) {
292 continue;
293 } else if(np->state.desktop == target) {
294 if(!nextClient || np->state.status & STAT_ACTIVE) {
295 nextClient = np;
296 }
297 }
298 }
299 if(nextClient) {
300 break;
301 }
302 }
303 /* Focus the next client or minimize the current group. */
304 if(nextClient) {
305 ChangeDesktop(nextClient->state.desktop);
306 RestoreClient(nextClient, 1);
307 } else {
308 MinimizeGroup(entry);
309 }
310 } else {
311 FocusGroup(entry);
312 if(focused) {
313 FocusClient(focused);
314 }
315 }
316 break;
317 case Button3:
318 ShowClientList(bar, entry);
319 break;
320 case Button4:
321 FocusPrevious();
322 break;
323 case Button5:
324 FocusNext();
325 break;
326 default:
327 break;
328 }
329 }
330
331 }
332
333 /** Minimize all clients in a group. */
MinimizeGroup(const TaskEntry * tp)334 void MinimizeGroup(const TaskEntry *tp)
335 {
336 ClientEntry *cp;
337 for(cp = tp->clients; cp; cp = cp->next) {
338 if(ShouldFocus(cp->client, 1)) {
339 MinimizeClient(cp->client, 0);
340 }
341 }
342 }
343
344 /** Raise all clients in a group and focus the top-most. */
FocusGroup(const TaskEntry * tp)345 void FocusGroup(const TaskEntry *tp)
346 {
347 const char *className = tp->clients->client->className;
348 ClientNode **toRestore;
349 const ClientEntry *cp;
350 unsigned restoreCount;
351 int i;
352 char shouldSwitch;
353
354 /* If there is no class name, then there will only be one client. */
355 if(!className || !settings.groupTasks) {
356 if(!(tp->clients->client->state.status & STAT_STICKY)) {
357 ChangeDesktop(tp->clients->client->state.desktop);
358 }
359 RestoreClient(tp->clients->client, 1);
360 FocusClient(tp->clients->client);
361 return;
362 }
363
364 /* If there is a client in the group on this desktop,
365 * then we remain on the same desktop. */
366 shouldSwitch = 1;
367 for(cp = tp->clients; cp; cp = cp->next) {
368 if(IsClientOnCurrentDesktop(cp->client)) {
369 shouldSwitch = 0;
370 break;
371 }
372 }
373
374 /* Switch to the desktop of the top-most client in the group. */
375 if(shouldSwitch) {
376 for(i = 0; i < LAYER_COUNT; i++) {
377 ClientNode *np;
378 for(np = nodes[i]; np; np = np->next) {
379 if(np->className && !strcmp(np->className, className)) {
380 if(ShouldFocus(np, 0)) {
381 if(!(np->state.status & STAT_STICKY)) {
382 ChangeDesktop(np->state.desktop);
383 }
384 break;
385 }
386 }
387 }
388 }
389 }
390
391 /* Build up the list of clients to restore in correct order. */
392 toRestore = AllocateStack(sizeof(ClientNode*) * clientCount);
393 restoreCount = 0;
394 for(i = 0; i < LAYER_COUNT; i++) {
395 ClientNode *np;
396 for(np = nodes[i]; np; np = np->next) {
397 if(!ShouldFocus(np, 1)) {
398 continue;
399 }
400 if(np->className && !strcmp(np->className, className)) {
401 toRestore[restoreCount] = np;
402 restoreCount += 1;
403 }
404 }
405 }
406 Assert(restoreCount <= clientCount);
407 for(i = restoreCount - 1; i >= 0; i--) {
408 RestoreClient(toRestore[i], 1);
409 }
410 for(i = 0; i < restoreCount; i++) {
411 if(toRestore[i]->state.status & (STAT_CANFOCUS | STAT_TAKEFOCUS)) {
412 FocusClient(toRestore[i]);
413 break;
414 }
415 }
416 ReleaseStack(toRestore);
417 }
418
419 /** Process a task list motion event. */
ProcessTaskMotionEvent(TrayComponentType * cp,int x,int y,int mask)420 void ProcessTaskMotionEvent(TrayComponentType *cp, int x, int y, int mask)
421 {
422 TaskBarType *bp = (TaskBarType*)cp->object;
423 bp->mousex = cp->screenx + x;
424 bp->mousey = cp->screeny + y;
425 GetCurrentTime(&bp->mouseTime);
426 }
427
428 /** Show the menu associated with a task list item. */
ShowClientList(TaskBarType * bar,TaskEntry * tp)429 void ShowClientList(TaskBarType *bar, TaskEntry *tp)
430 {
431 Menu *menu;
432 MenuItem *item;
433 ClientEntry *cp;
434
435 const ScreenType *sp;
436 int x, y;
437 Window w;
438
439 if(settings.groupTasks) {
440
441 menu = CreateMenu();
442
443 item = CreateMenuItem(MENU_ITEM_NORMAL);
444 item->name = CopyString(_("Close"));
445 item->action.type = MA_CLOSE | MA_GROUP_MASK;
446 item->action.context = tp;
447 item->next = menu->items;
448 menu->items = item;
449
450 item = CreateMenuItem(MENU_ITEM_NORMAL);
451 item->name = CopyString(_("Minimize"));
452 item->action.type = MA_MINIMIZE | MA_GROUP_MASK;
453 item->action.context = tp;
454 item->next = menu->items;
455 menu->items = item;
456
457 item = CreateMenuItem(MENU_ITEM_NORMAL);
458 item->name = CopyString(_("Restore"));
459 item->action.type = MA_RESTORE | MA_GROUP_MASK;
460 item->action.context = tp;
461 item->next = menu->items;
462 menu->items = item;
463
464 item = CreateMenuItem(MENU_ITEM_SUBMENU);
465 item->name = CopyString(_("Send To"));
466 item->action.type = MA_SENDTO_MENU | MA_GROUP_MASK;
467 item->action.context = tp;
468 item->next = menu->items;
469 menu->items = item;
470
471 /* Load the separator and group actions. */
472 item = CreateMenuItem(MENU_ITEM_SEPARATOR);
473 item->next = menu->items;
474 menu->items = item;
475
476 /* Load the clients into the menu. */
477 for(cp = tp->clients; cp; cp = cp->next) {
478 if(!ShouldFocus(cp->client, 0)) {
479 continue;
480 }
481 item = CreateMenuItem(MENU_ITEM_NORMAL);
482 if(cp->client->state.status & STAT_MINIMIZED) {
483 size_t len = 0;
484 if(cp->client->name) {
485 len = strlen(cp->client->name);
486 }
487 item->name = Allocate(len + 3);
488 item->name[0] = '[';
489 memcpy(&item->name[1], cp->client->name, len);
490 item->name[len + 1] = ']';
491 item->name[len + 2] = 0;
492 } else {
493 item->name = CopyString(cp->client->name);
494 }
495 item->icon = cp->client->icon ? cp->client->icon : GetDefaultIcon();
496 item->action.type = MA_EXECUTE;
497 item->action.context = cp->client;
498 item->next = menu->items;
499 menu->items = item;
500 }
501 } else {
502 /* Not grouping clients. */
503 menu = CreateWindowMenu(tp->clients->client);
504 }
505
506 /* Initialize and position the menu. */
507 InitializeMenu(menu);
508 sp = GetCurrentScreen(bar->cp->screenx, bar->cp->screeny);
509 GetMousePosition(&x, &y, &w);
510 if(bar->layout == LAYOUT_HORIZONTAL) {
511 if(bar->cp->screeny + bar->cp->height / 2 < sp->y + sp->height / 2) {
512 /* Bottom of the screen: menus go up. */
513 y = bar->cp->screeny + bar->cp->height;
514 } else {
515 /* Top of the screen: menus go down. */
516 y = bar->cp->screeny - menu->height;
517 }
518 x -= menu->width / 2;
519 x = Max(x, sp->x);
520 } else {
521 if(bar->cp->screenx + bar->cp->width / 2 < sp->x + sp->width / 2) {
522 /* Left side: menus go right. */
523 x = bar->cp->screenx + bar->cp->width;
524 } else {
525 /* Right side: menus go left. */
526 x = bar->cp->screenx - menu->width;
527 }
528 y -= menu->height / 2;
529 y = Max(y, sp->y);
530 }
531
532 ShowMenu(menu, RunTaskBarCommand, x, y, 0);
533
534 DestroyMenu(menu);
535
536 }
537
538 /** Run a menu action. */
RunTaskBarCommand(MenuAction * action,unsigned button)539 void RunTaskBarCommand(MenuAction *action, unsigned button)
540 {
541 ClientEntry *cp;
542
543 if(action->type & MA_GROUP_MASK) {
544 TaskEntry *tp = action->context;
545 for(cp = tp->clients; cp; cp = cp->next) {
546 if(!ShouldFocus(cp->client, 0)) {
547 continue;
548 }
549 switch(action->type & ~MA_GROUP_MASK) {
550 case MA_SENDTO:
551 SetClientDesktop(cp->client, action->value);
552 break;
553 case MA_CLOSE:
554 DeleteClient(cp->client);
555 break;
556 case MA_RESTORE:
557 RestoreClient(cp->client, 0);
558 break;
559 case MA_MINIMIZE:
560 MinimizeClient(cp->client, 0);
561 break;
562 default:
563 break;
564 }
565 }
566 } else if(action->type == MA_EXECUTE) {
567 if(button == Button3) {
568 Window w;
569 int x, y;
570 GetMousePosition(&x, &y, &w);
571 ShowWindowMenu(action->context, x, y, 0);
572 } else {
573 ClientNode *np = action->context;
574 RestoreClient(np, 1);
575 FocusClient(np);
576 MoveMouse(np->window, np->width / 2, np->height / 2);
577 }
578 } else {
579 RunWindowCommand(action, button);
580 }
581 }
582
583 /** Add a client to the task bar. */
AddClientToTaskBar(ClientNode * np)584 void AddClientToTaskBar(ClientNode *np)
585 {
586 TaskEntry *tp = NULL;
587 ClientEntry *cp = Allocate(sizeof(ClientEntry));
588 cp->client = np;
589
590 if(np->className && settings.groupTasks) {
591 for(tp = taskEntries; tp; tp = tp->next) {
592 const char *className = tp->clients->client->className;
593 if(className && !strcmp(np->className, className)) {
594 break;
595 }
596 }
597 }
598 if(tp == NULL) {
599 tp = Allocate(sizeof(TaskEntry));
600 tp->clients = NULL;
601 tp->next = NULL;
602 tp->prev = taskEntriesTail;
603 if(taskEntriesTail) {
604 taskEntriesTail->next = tp;
605 } else {
606 taskEntries = tp;
607 }
608 taskEntriesTail = tp;
609 }
610
611 cp->next = tp->clients;
612 if(tp->clients) {
613 tp->clients->prev = cp;
614 }
615 cp->prev = NULL;
616 tp->clients = cp;
617
618 RequireTaskUpdate();
619 UpdateNetClientList();
620
621 }
622
623 /** Remove a client from the task bar. */
RemoveClientFromTaskBar(ClientNode * np)624 void RemoveClientFromTaskBar(ClientNode *np)
625 {
626 TaskEntry *tp;
627 for(tp = taskEntries; tp; tp = tp->next) {
628 ClientEntry *cp;
629 for(cp = tp->clients; cp; cp = cp->next) {
630 if(cp->client == np) {
631 if(cp->prev) {
632 cp->prev->next = cp->next;
633 } else {
634 tp->clients = cp->next;
635 }
636 if(cp->next) {
637 cp->next->prev = cp->prev;
638 }
639 Release(cp);
640 if(!tp->clients) {
641 if(tp->prev) {
642 tp->prev->next = tp->next;
643 } else {
644 taskEntries = tp->next;
645 }
646 if(tp->next) {
647 tp->next->prev = tp->prev;
648 } else {
649 taskEntriesTail = tp->prev;
650 }
651 Release(tp);
652 }
653 RequireTaskUpdate();
654 UpdateNetClientList();
655 return;
656 }
657 }
658 }
659 }
660
661 /** Update all task bars. */
UpdateTaskBar(void)662 void UpdateTaskBar(void)
663 {
664 TaskBarType *bp;
665 int lastHeight = -1;
666
667 if(JUNLIKELY(shouldExit)) {
668 return;
669 }
670
671 for(bp = bars; bp; bp = bp->next) {
672 if(bp->layout == LAYOUT_VERTICAL) {
673 TaskEntry *tp;
674 lastHeight = bp->cp->requestedHeight;
675 if(bp->userHeight > 0) {
676 bp->itemHeight = bp->userHeight;
677 } else {
678 bp->itemHeight = GetStringHeight(FONT_TASKLIST) + 12;
679 }
680 bp->cp->requestedHeight = 0;
681 for(tp = taskEntries; tp; tp = tp->next) {
682 if(ShouldShowEntry(tp)) {
683 bp->cp->requestedHeight += bp->itemHeight;
684 }
685 }
686 bp->cp->requestedHeight = Max(1, bp->cp->requestedHeight);
687 if(lastHeight != bp->cp->requestedHeight) {
688 ResizeTray(bp->cp->tray);
689 }
690 }
691 ComputeItemSize(bp);
692 Render(bp);
693 }
694 }
695
696 /** Signal task bar (for popups). */
SignalTaskbar(const TimeType * now,int x,int y,Window w,void * data)697 void SignalTaskbar(const TimeType *now, int x, int y, Window w, void *data)
698 {
699
700 TaskBarType *bp = (TaskBarType*)data;
701 TaskEntry *ep;
702
703 if(w == bp->cp->tray->window &&
704 abs(bp->mousex - x) < settings.doubleClickDelta &&
705 abs(bp->mousey - y) < settings.doubleClickDelta) {
706 if(GetTimeDifference(now, &bp->mouseTime) >= settings.popupDelay) {
707 ep = GetEntry(bp, x - bp->cp->screenx, y - bp->cp->screeny);
708 if(settings.groupTasks) {
709 if(ep && ep->clients->client->className) {
710 ShowPopup(x, y, ep->clients->client->className, POPUP_TASK);
711 }
712 } else {
713 if(ep && ep->clients->client->name) {
714 ShowPopup(x, y, ep->clients->client->name, POPUP_TASK);
715 }
716 }
717 }
718 }
719
720 }
721
722 /** Draw a specific task bar. */
Render(const TaskBarType * bp)723 void Render(const TaskBarType *bp)
724 {
725 TaskEntry *tp;
726 char *displayName;
727 ButtonNode button;
728 int x, y;
729
730 if(JUNLIKELY(shouldExit)) {
731 return;
732 }
733
734 ClearTrayDrawable(bp->cp);
735 if(!taskEntries) {
736 UpdateSpecificTray(bp->cp->tray, bp->cp);
737 return;
738 }
739
740 ResetButton(&button, bp->cp->pixmap);
741 button.border = settings.taskListDecorations == DECO_MOTIF;
742 button.font = FONT_TASKLIST;
743 button.height = bp->itemHeight;
744 button.width = bp->itemWidth;
745 button.text = NULL;
746
747 x = 0;
748 y = 0;
749 for(tp = taskEntries; tp; tp = tp->next) {
750
751 if(!ShouldShowEntry(tp)) {
752 continue;
753 }
754
755 /* Check for an active or urgent window and count clients. */
756 ClientEntry *cp;
757 unsigned clientCount = 0;
758 button.type = BUTTON_TASK;
759 for(cp = tp->clients; cp; cp = cp->next) {
760 if(ShouldFocus(cp->client, 0)) {
761 const char flash = (cp->client->state.status & STAT_FLASH) != 0;
762 const char active = (cp->client->state.status & STAT_ACTIVE)
763 && IsClientOnCurrentDesktop(cp->client);
764 if(flash || active) {
765 if(button.type == BUTTON_TASK) {
766 button.type = BUTTON_TASK_ACTIVE;
767 } else {
768 button.type = BUTTON_TASK;
769 }
770 }
771 clientCount += 1;
772 }
773 }
774 button.x = x;
775 button.y = y;
776 if(!tp->clients->client->icon) {
777 button.icon = GetDefaultIcon();
778 } else {
779 button.icon = tp->clients->client->icon;
780 }
781 displayName = NULL;
782 if(bp->labeled) {
783 if(tp->clients->client->className && settings.groupTasks) {
784 if(clientCount != 1) {
785 const size_t len = strlen(tp->clients->client->className) + 16;
786 displayName = Allocate(len);
787 snprintf(displayName, len, "%s (%u)",
788 tp->clients->client->className, clientCount);
789 button.text = displayName;
790 } else {
791 button.text = tp->clients->client->className;
792 }
793 } else {
794 button.text = tp->clients->client->name;
795 }
796 }
797 DrawButton(&button);
798 if(displayName) {
799 Release(displayName);
800 }
801
802 if(bp->layout == LAYOUT_HORIZONTAL) {
803 x += bp->itemWidth;
804 } else {
805 y += bp->itemHeight;
806 }
807 }
808
809 UpdateSpecificTray(bp->cp->tray, bp->cp);
810
811 }
812
813 /** Focus the next client in the task bar. */
FocusNext(void)814 void FocusNext(void)
815 {
816 TaskEntry *tp;
817
818 /* Find the current entry. */
819 for(tp = taskEntries; tp; tp = tp->next) {
820 ClientEntry *cp;
821 for(cp = tp->clients; cp; cp = cp->next) {
822 if(cp->client->state.status & (STAT_CANFOCUS | STAT_TAKEFOCUS)) {
823 if(ShouldFocus(cp->client, 1)) {
824 if(cp->client->state.status & STAT_ACTIVE) {
825 cp = cp->next;
826 goto ClientFound;
827 }
828 }
829 }
830 }
831 }
832 ClientFound:
833
834 /* Move to the next group. */
835 if(tp) {
836 do {
837 tp = tp->next;
838 } while(tp && !ShouldFocusEntry(tp));
839 }
840 if(!tp) {
841 /* Wrap around; start at the beginning. */
842 for(tp = taskEntries; tp; tp = tp->next) {
843 if(ShouldFocusEntry(tp)) {
844 break;
845 }
846 }
847 }
848
849 /* Focus the group if one exists. */
850 if(tp) {
851 FocusGroup(tp);
852 }
853 }
854
855 /** Focus the previous client in the task bar. */
FocusPrevious(void)856 void FocusPrevious(void)
857 {
858 TaskEntry *tp;
859
860 /* Find the current entry. */
861 for(tp = taskEntries; tp; tp = tp->next) {
862 ClientEntry *cp;
863 for(cp = tp->clients; cp; cp = cp->next) {
864 if(cp->client->state.status & (STAT_CANFOCUS | STAT_TAKEFOCUS)) {
865 if(ShouldFocus(cp->client, 1)) {
866 if(cp->client->state.status & STAT_ACTIVE) {
867 cp = cp->next;
868 goto ClientFound;
869 }
870 }
871 }
872 }
873 }
874 ClientFound:
875
876 /* Move to the previous group. */
877 if(tp) {
878 do {
879 tp = tp->prev;
880 } while(tp && !ShouldFocusEntry(tp));
881 }
882 if(!tp) {
883 /* Wrap around; start at the end. */
884 for(tp = taskEntriesTail; tp; tp = tp->prev) {
885 if(ShouldFocusEntry(tp)) {
886 break;
887 }
888 }
889 }
890
891 /* Focus the group if one exists. */
892 if(tp) {
893 FocusGroup(tp);
894 }
895 }
896
897 /** Determine if there is anything to show for the specified entry. */
ShouldShowEntry(const TaskEntry * tp)898 char ShouldShowEntry(const TaskEntry *tp)
899 {
900 const ClientEntry *cp;
901 for(cp = tp->clients; cp; cp = cp->next) {
902 if(ShouldFocus(cp->client, 0)) {
903 return 1;
904 }
905 }
906 return 0;
907 }
908
909 /** Determine if we should attempt to focus an entry. */
ShouldFocusEntry(const TaskEntry * tp)910 char ShouldFocusEntry(const TaskEntry *tp)
911 {
912 const ClientEntry *cp;
913 for(cp = tp->clients; cp; cp = cp->next) {
914 if(cp->client->state.status & (STAT_CANFOCUS | STAT_TAKEFOCUS)) {
915 if(ShouldFocus(cp->client, 1)) {
916 return 1;
917 }
918 }
919 }
920 return 0;
921 }
922
923 /** Get the item associated with a coordinate on the task bar. */
GetEntry(TaskBarType * bar,int x,int y)924 TaskEntry *GetEntry(TaskBarType *bar, int x, int y)
925 {
926 TaskEntry *tp;
927 int offset;
928
929 offset = 0;
930 for(tp = taskEntries; tp; tp = tp->next) {
931 if(!ShouldShowEntry(tp)) {
932 continue;
933 }
934 if(bar->layout == LAYOUT_HORIZONTAL) {
935 offset += bar->itemWidth;
936 if(x < offset) {
937 return tp;
938 }
939 } else {
940 offset += bar->itemHeight;
941 if(y < offset) {
942 return tp;
943 }
944 }
945 }
946
947 return NULL;
948 }
949
950 /** Set the maximum width of an item in the task bar. */
SetMaxTaskBarItemWidth(TrayComponentType * cp,const char * value)951 void SetMaxTaskBarItemWidth(TrayComponentType *cp, const char *value)
952 {
953 TaskBarType *bp = (TaskBarType*)cp->object;
954 int temp;
955
956 Assert(cp);
957 Assert(value);
958
959 temp = atoi(value);
960 if(JUNLIKELY(temp < 0)) {
961 Warning(_("invalid maxwidth for TaskList: %s"), value);
962 return;
963 }
964 bp->maxItemWidth = temp;
965 }
966
967 /** Set the preferred height of the specified task bar. */
SetTaskBarHeight(TrayComponentType * cp,const char * value)968 void SetTaskBarHeight(TrayComponentType *cp, const char *value)
969 {
970 TaskBarType *bp = (TaskBarType*)cp->object;
971 int temp;
972
973 temp = atoi(value);
974 if(JUNLIKELY(temp < 0)) {
975 Warning(_("invalid height for TaskList: %s"), value);
976 return;
977 }
978 bp->userHeight = temp;
979 }
980
981 /** Set whether the label should be displayed. */
SetTaskBarLabeled(TrayComponentType * cp,char labeled)982 void SetTaskBarLabeled(TrayComponentType *cp, char labeled)
983 {
984 TaskBarType *bp = (TaskBarType*)cp->object;
985 bp->labeled = labeled;
986 }
987
988 /** Maintain the _NET_CLIENT_LIST[_STACKING] properties on the root. */
UpdateNetClientList(void)989 void UpdateNetClientList(void)
990 {
991 TaskEntry *tp;
992 ClientNode *client;
993 Window *windows;
994 unsigned int count;
995 int layer;
996
997 /* Determine how much we need to allocate. */
998 if(clientCount == 0) {
999 windows = NULL;
1000 } else {
1001 windows = AllocateStack(clientCount * sizeof(Window));
1002 }
1003
1004 /* Set _NET_CLIENT_LIST */
1005 count = 0;
1006 for(tp = taskEntries; tp; tp = tp->next) {
1007 ClientEntry *cp;
1008 for(cp = tp->clients; cp; cp = cp->next) {
1009 windows[count] = cp->client->window;
1010 count += 1;
1011 }
1012 }
1013 Assert(count <= clientCount);
1014 JXChangeProperty(display, rootWindow, atoms[ATOM_NET_CLIENT_LIST],
1015 XA_WINDOW, 32, PropModeReplace,
1016 (unsigned char*)windows, count);
1017
1018 /* Set _NET_CLIENT_LIST_STACKING */
1019 count = 0;
1020 for(layer = FIRST_LAYER; layer <= LAST_LAYER; layer++) {
1021 for(client = nodes[layer]; client; client = client->next) {
1022 windows[count] = client->window;
1023 count += 1;
1024 }
1025 }
1026 JXChangeProperty(display, rootWindow, atoms[ATOM_NET_CLIENT_LIST_STACKING],
1027 XA_WINDOW, 32, PropModeReplace,
1028 (unsigned char*)windows, count);
1029
1030 if(windows != NULL) {
1031 ReleaseStack(windows);
1032 }
1033
1034 }
1035