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