1 /**
2  * @file dock.c
3  * @author Joe Wingbermuehle
4  * @date 2006
5  *
6  * @brief Dock tray component.
7  *
8  */
9 
10 #include "jwm.h"
11 #include "dock.h"
12 #include "tray.h"
13 #include "main.h"
14 #include "error.h"
15 #include "color.h"
16 #include "misc.h"
17 #include "settings.h"
18 
19 #define SYSTEM_TRAY_REQUEST_DOCK    0
20 #define SYSTEM_TRAY_BEGIN_MESSAGE   1
21 #define SYSTEM_TRAY_CANCEL_MESSAGE  2
22 
23 #define SYSTEM_TRAY_ORIENTATION_HORZ 0
24 #define SYSTEM_TRAY_ORIENTATION_VERT 1
25 
26 /** Structure to represent a docked window. */
27 typedef struct DockNode {
28 
29    Window window;
30    char needs_reparent;
31 
32    struct DockNode *next;
33 
34 } DockNode;
35 
36 /** Structure to represent a dock tray component. */
37 typedef struct DockType {
38 
39    TrayComponentType *cp;
40 
41    Window window;
42    int itemSize;
43 
44    DockNode *nodes;
45 
46 } DockType;
47 
48 static const char BASE_SELECTION_NAME[] = "_NET_SYSTEM_TRAY_S%d";
49 
50 static DockType *dock = NULL;
51 static char owner;
52 static Atom dockAtom;
53 static unsigned long orientation;
54 
55 static void SetSize(TrayComponentType *cp, int width, int height);
56 static void Create(TrayComponentType *cp);
57 static void Resize(TrayComponentType *cp);
58 
59 static void DockWindow(Window win);
60 static void UpdateDock(void);
61 static void GetDockItemSize(int *size);
62 static void GetDockSize(int *width, int *height);
63 
64 /** Initialize dock data. */
InitializeDock(void)65 void InitializeDock(void)
66 {
67    owner = 0;
68 }
69 
70 /** Startup the dock. */
StartupDock(void)71 void StartupDock(void)
72 {
73 
74    char *selectionName;
75 
76    if(!dock) {
77       /* No dock has been requested. */
78       return;
79    }
80 
81    if(dock->window == None) {
82 
83       /* No dock yet. */
84 
85       /* Get the selection atom. */
86       selectionName = AllocateStack(sizeof(BASE_SELECTION_NAME));
87       snprintf(selectionName, sizeof(BASE_SELECTION_NAME),
88                BASE_SELECTION_NAME, rootScreen);
89       dockAtom = JXInternAtom(display, selectionName, False);
90       ReleaseStack(selectionName);
91 
92       /* The location and size of the window doesn't matter here. */
93       dock->window = JXCreateSimpleWindow(display, rootWindow,
94          /* x, y, width, height */ 0, 0, 1, 1,
95          /* border_size, border_color */ 0, 0,
96          /* background */ colors[COLOR_TRAY_BG2]);
97       JXSelectInput(display, dock->window,
98            SubstructureNotifyMask
99          | SubstructureRedirectMask
100          | EnterWindowMask
101          | PointerMotionMask | PointerMotionHintMask);
102 
103    }
104    dock->cp->window = dock->window;
105 
106 }
107 
108 /** Shutdown the dock. */
ShutdownDock(void)109 void ShutdownDock(void)
110 {
111 
112    DockNode *np;
113 
114    if(dock) {
115 
116       /* Release memory used by the dock list. */
117       while(dock->nodes) {
118          np = dock->nodes->next;
119          JXReparentWindow(display, dock->nodes->window, rootWindow, 0, 0);
120          Release(dock->nodes);
121          dock->nodes = np;
122       }
123 
124       /* Release the selection. */
125       if(owner) {
126          JXSetSelectionOwner(display, dockAtom, None, CurrentTime);
127       }
128 
129       /* Destroy the dock window. */
130       JXDestroyWindow(display, dock->window);
131 
132    }
133 
134 }
135 
136 /** Destroy dock data. */
DestroyDock(void)137 void DestroyDock(void)
138 {
139    if(dock) {
140       Release(dock);
141       dock = NULL;
142    }
143 }
144 
145 /** Create a dock component. */
CreateDock(int width)146 TrayComponentType *CreateDock(int width)
147 {
148    TrayComponentType *cp;
149 
150    if(JUNLIKELY(dock != NULL && dock->cp != NULL)) {
151       Warning(_("only one Dock allowed"));
152       return NULL;
153    } else if(dock == NULL) {
154       dock = Allocate(sizeof(DockType));
155       dock->nodes = NULL;
156       dock->window = None;
157    }
158 
159    cp = CreateTrayComponent();
160    cp->object = dock;
161    cp->requestedWidth = 1;
162    cp->requestedHeight = 1;
163    dock->cp = cp;
164    dock->itemSize = width;
165 
166    cp->SetSize = SetSize;
167    cp->Create = Create;
168    cp->Resize = Resize;
169 
170    return cp;
171 
172 }
173 
174 /** Set the size of a dock component. */
SetSize(TrayComponentType * cp,int width,int height)175 void SetSize(TrayComponentType *cp, int width, int height)
176 {
177 
178    Assert(cp);
179    Assert(dock);
180 
181    /* Set the orientation. */
182    if(width == 0) {
183       orientation = SYSTEM_TRAY_ORIENTATION_HORZ;
184    } else if(height == 0) {
185       orientation = SYSTEM_TRAY_ORIENTATION_VERT;
186    }
187 
188    /* Get the size. */
189    cp->width = width;
190    cp->height = height;
191    GetDockSize(&cp->width, &cp->height);
192    if(width == 0) {
193       cp->requestedWidth = cp->width;
194       cp->requestedHeight = 0;
195    } else {
196       cp->requestedWidth = 0;
197       cp->requestedHeight = cp->height;
198    }
199 
200 }
201 
202 /** Initialize a dock component. */
Create(TrayComponentType * cp)203 void Create(TrayComponentType *cp)
204 {
205 
206    XEvent event;
207 
208    Assert(cp);
209 
210    /* Map the dock window. */
211    if(cp->window != None) {
212       JXResizeWindow(display, cp->window, cp->width, cp->height);
213       JXMapRaised(display, cp->window);
214    }
215 
216    /* Set the orientation atom. */
217    SetCardinalAtom(dock->cp->window, ATOM_NET_SYSTEM_TRAY_ORIENTATION,
218                    orientation);
219 
220    /* Get the selection if we don't already own it.
221     * If we did already own it, getting it again would cause problems
222     * with some clients due to the way restarts are handled.
223     */
224    if(!owner) {
225 
226       owner = 1;
227       JXSetSelectionOwner(display, dockAtom, dock->cp->window, CurrentTime);
228       if(JUNLIKELY(JXGetSelectionOwner(display, dockAtom)
229                    != dock->cp->window)) {
230 
231          owner = 0;
232          Warning(_("could not acquire system tray selection"));
233 
234       } else {
235 
236          memset(&event, 0, sizeof(event));
237          event.xclient.type = ClientMessage;
238          event.xclient.window = rootWindow;
239          event.xclient.message_type = atoms[ATOM_MANAGER];
240          event.xclient.format = 32;
241          event.xclient.data.l[0] = CurrentTime;
242          event.xclient.data.l[1] = dockAtom;
243          event.xclient.data.l[2] = dock->cp->window;
244          event.xclient.data.l[3] = 0;
245          event.xclient.data.l[4] = 0;
246 
247          JXSendEvent(display, rootWindow, False, StructureNotifyMask, &event);
248 
249       }
250 
251    }
252 
253 }
254 
255 /** Resize a dock component. */
Resize(TrayComponentType * cp)256 void Resize(TrayComponentType *cp)
257 {
258    JXResizeWindow(display, cp->window, cp->width, cp->height);
259    UpdateDock();
260 }
261 
262 /** Handle a dock event. */
HandleDockEvent(const XClientMessageEvent * event)263 void HandleDockEvent(const XClientMessageEvent *event)
264 {
265    Assert(event);
266    switch(event->data.l[1]) {
267    case SYSTEM_TRAY_REQUEST_DOCK:
268       DockWindow(event->data.l[2]);
269       break;
270    case SYSTEM_TRAY_BEGIN_MESSAGE:
271       break;
272    case SYSTEM_TRAY_CANCEL_MESSAGE:
273       break;
274    default:
275       Debug("invalid opcode in dock event");
276       break;
277    }
278 }
279 
280 /** Handle a resize request event. */
HandleDockResizeRequest(const XResizeRequestEvent * event)281 char HandleDockResizeRequest(const XResizeRequestEvent *event)
282 {
283    DockNode *np;
284 
285    Assert(event);
286 
287    if(!dock) {
288       return 0;
289    }
290 
291    for(np = dock->nodes; np; np = np->next) {
292       if(np->window == event->window) {
293          UpdateDock();
294          return 1;
295       }
296    }
297 
298    return 0;
299 }
300 
301 /** Handle a configure request event. */
HandleDockConfigureRequest(const XConfigureRequestEvent * event)302 char HandleDockConfigureRequest(const XConfigureRequestEvent *event)
303 {
304 
305    DockNode *np;
306 
307    Assert(event);
308 
309    if(!dock) {
310       return 0;
311    }
312 
313    for(np = dock->nodes; np; np = np->next) {
314       if(np->window == event->window) {
315          UpdateDock();
316          return 1;
317       }
318    }
319 
320    return 0;
321 
322 }
323 
324 /** Handle a reparent notify event. */
HandleDockReparentNotify(const XReparentEvent * event)325 char HandleDockReparentNotify(const XReparentEvent *event)
326 {
327 
328    DockNode *np;
329    char handled;
330 
331    Assert(event);
332 
333    /* Just return if there is no dock. */
334    if(!dock) {
335       return 0;
336    }
337 
338    /* Check each docked window. */
339    handled = 0;
340    for(np = dock->nodes; np; np = np->next) {
341       if(np->window == event->window) {
342          if(event->parent != dock->cp->window) {
343             /* For some reason the application reparented the window.
344              * We make note of this condition and reparent every time
345              * the dock is updated. Unfortunately we can't do this for
346              * all applications because some won't deal with it.
347              */
348             np->needs_reparent = 1;
349             handled = 1;
350          }
351       }
352    }
353 
354    /* Layout the stuff on the dock again if something happened. */
355    if(handled) {
356       UpdateDock();
357    }
358 
359    return handled;
360 
361 }
362 
363 /** Handle a selection clear event. */
HandleDockSelectionClear(const XSelectionClearEvent * event)364 char HandleDockSelectionClear(const XSelectionClearEvent *event)
365 {
366    if(event->selection == dockAtom) {
367       Debug("lost _NET_SYSTEM_TRAY selection");
368       owner = 0;
369    }
370    return 0;
371 }
372 
373 /** Add a window to the dock. */
DockWindow(Window win)374 void DockWindow(Window win)
375 {
376    DockNode *np;
377 
378    /* If no dock is running, just return. */
379    if(!dock) {
380       return;
381    }
382 
383    /* Make sure we have a valid window to add. */
384    if(JUNLIKELY(win == None)) {
385       return;
386    }
387 
388    /* If this window is already docked ignore it. */
389    for(np = dock->nodes; np; np = np->next) {
390       if(np->window == win) {
391          return;
392       }
393    }
394 
395    /* Add the window to our list. */
396    np = Allocate(sizeof(DockNode));
397    np->window = win;
398    np->needs_reparent = 0;
399    np->next = dock->nodes;
400    dock->nodes = np;
401 
402    /* Update the requested size. */
403    GetDockSize(&dock->cp->requestedWidth, &dock->cp->requestedHeight);
404 
405    /* It's safe to reparent at (0, 0) since we call
406     * ResizeTray which will invoke the Resize callback.
407     */
408    JXAddToSaveSet(display, win);
409    JXReparentWindow(display, win, dock->cp->window, 0, 0);
410    JXMapRaised(display, win);
411 
412    /* Resize the tray containing the dock. */
413    ResizeTray(dock->cp->tray);
414 
415 }
416 
417 /** Remove a window from the dock. */
HandleDockDestroy(Window win)418 char HandleDockDestroy(Window win)
419 {
420    DockNode **np;
421 
422    /* If no dock is running, just return. */
423    if(!dock) {
424       return 0;
425    }
426 
427    for(np = &dock->nodes; *np; np = &(*np)->next) {
428       DockNode *dp = *np;
429       if(dp->window == win) {
430 
431          /* Remove the window from our list. */
432          *np = dp->next;
433          Release(dp);
434 
435          /* Update the requested size. */
436          GetDockSize(&dock->cp->requestedWidth, &dock->cp->requestedHeight);
437 
438          /* Resize the tray. */
439          ResizeTray(dock->cp->tray);
440          return 1;
441       }
442    }
443 
444    return 0;
445 }
446 
447 /** Layout items on the dock. */
UpdateDock(void)448 void UpdateDock(void)
449 {
450 
451    XConfigureEvent event;
452    DockNode *np;
453    int x, y;
454    int itemSize;
455 
456    Assert(dock);
457 
458    /* Determine the size of items in the dock. */
459    GetDockItemSize(&itemSize);
460 
461    x = 0;
462    y = 0;
463    memset(&event, 0, sizeof(event));
464    for(np = dock->nodes; np; np = np->next) {
465 
466       JXMoveResizeWindow(display, np->window, x, y, itemSize, itemSize);
467 
468       /* Reparent if this window likes to go other places. */
469       if(np->needs_reparent) {
470          JXReparentWindow(display, np->window, dock->cp->window, x, y);
471       }
472 
473       event.type = ConfigureNotify;
474       event.event = np->window;
475       event.window = np->window;
476       event.x = dock->cp->screenx + x;
477       event.y = dock->cp->screeny + y;
478       event.width = itemSize;
479       event.height = itemSize;
480       JXSendEvent(display, np->window, False, StructureNotifyMask,
481                   (XEvent*)&event);
482 
483       if(orientation == SYSTEM_TRAY_ORIENTATION_HORZ) {
484          x += itemSize + settings.dockSpacing;
485       } else {
486          y += itemSize + settings.dockSpacing;
487       }
488 
489    }
490 
491 }
492 
493 /** Get the size of a particular window on the dock. */
GetDockItemSize(int * size)494 void GetDockItemSize(int *size)
495 {
496    /* Determine the default size of items in the dock. */
497    if(orientation == SYSTEM_TRAY_ORIENTATION_HORZ) {
498       *size = dock->cp->height;
499    } else {
500       *size = dock->cp->width;
501    }
502    if(dock->itemSize > 0 && *size > dock->itemSize) {
503       *size = dock->itemSize;
504    }
505 }
506 
507 /** Get the size of the dock. */
GetDockSize(int * width,int * height)508 void GetDockSize(int *width, int *height)
509 {
510    DockNode *np;
511    int itemSize;
512 
513    Assert(dock != NULL);
514 
515    /* Get the dock item size. */
516    GetDockItemSize(&itemSize);
517 
518    /* Determine the size of the items on the dock. */
519    for(np = dock->nodes; np; np = np->next) {
520       const unsigned spacing = (np->next ? settings.dockSpacing : 0);
521       if(orientation == SYSTEM_TRAY_ORIENTATION_HORZ) {
522          /* Horizontal tray; height fixed, placement is left to right. */
523          *width += itemSize + spacing;
524       } else {
525          /* Vertical tray; width fixed, placement is top to bottom. */
526          *height += itemSize + spacing;
527       }
528    }
529 
530    /* Don't allow the dock to have zero size since a size of
531     * zero indicates a variable sized component. */
532    if(orientation == SYSTEM_TRAY_ORIENTATION_HORZ) {
533       *width = Max(*width, 1);
534    } else {
535       *height = Max(*height, 1);
536    }
537 
538 }
539