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