1 /**
2 * @file pager.c
3 * @author Joe Wingbermuehle
4 * @date 2004-2007
5 *
6 * @brief Pager tray component.
7 *
8 */
9
10 #include "jwm.h"
11 #include "pager.h"
12
13 #include "client.h"
14 #include "clientlist.h"
15 #include "color.h"
16 #include "cursor.h"
17 #include "desktop.h"
18 #include "event.h"
19 #include "tray.h"
20 #include "timing.h"
21 #include "popup.h"
22 #include "font.h"
23 #include "settings.h"
24
25 /** Structure to represent a pager tray component. */
26 typedef struct PagerType {
27
28 TrayComponentType *cp; /**< Common tray component data. */
29
30 int deskWidth; /**< Width of a desktop. */
31 int deskHeight; /**< Height of a desktop. */
32 int scalex; /**< Horizontal scale factor (fixed point). */
33 int scaley; /**< Vertical scale factor (fixed point). */
34 char labeled; /**< Set to label the pager. */
35
36 Pixmap buffer; /**< Buffer for rendering the pager. */
37
38 TimeType mouseTime; /**< Timestamp of last mouse movement. */
39 int mousex, mousey; /**< Coordinates of last mouse location. */
40
41 struct PagerType *next; /**< Next pager in the list. */
42
43 } PagerType;
44
45 static PagerType *pagers = NULL;
46
47 static char shouldStopMove;
48
49 static void Create(TrayComponentType *cp);
50
51 static void SetSize(TrayComponentType *cp, int width, int height);
52
53 static int GetPagerDesktop(PagerType *pp, int x, int y);
54
55 static void ProcessPagerButtonEvent(TrayComponentType *cp,
56 int x, int y, int mask);
57
58 static void ProcessPagerMotionEvent(TrayComponentType *cp,
59 int x, int y, int mask);
60
61 static void StartPagerMove(TrayComponentType *cp, int x, int y);
62
63 static void StopPagerMove(ClientNode *np,
64 int x, int y, int desktop, MaxFlags maxFlags);
65
66 static void PagerMoveController(int wasDestroyed);
67
68 static void DrawPager(const PagerType *pp);
69
70 static void DrawPagerClient(const PagerType *pp, const ClientNode *np);
71
72 static void SignalPager(const TimeType *now, int x, int y, Window w,
73 void *data);
74
75
76 /** Shutdown the pager. */
ShutdownPager(void)77 void ShutdownPager(void)
78 {
79 PagerType *pp;
80 for(pp = pagers; pp; pp = pp->next) {
81 JXFreePixmap(display, pp->buffer);
82 }
83 }
84
85 /** Release pager data. */
DestroyPager(void)86 void DestroyPager(void)
87 {
88 PagerType *pp;
89 while(pagers) {
90 UnregisterCallback(SignalPager, pagers);
91 pp = pagers->next;
92 Release(pagers);
93 pagers = pp;
94 }
95 }
96
97 /** Create a new pager tray component. */
CreatePager(char labeled)98 TrayComponentType *CreatePager(char labeled)
99 {
100
101 TrayComponentType *cp;
102 PagerType *pp;
103
104 pp = Allocate(sizeof(PagerType));
105 pp->next = pagers;
106 pagers = pp;
107 pp->labeled = labeled;
108 pp->mousex = -settings.doubleClickDelta;
109 pp->mousey = -settings.doubleClickDelta;
110 pp->mouseTime.seconds = 0;
111 pp->mouseTime.ms = 0;
112 pp->buffer = None;
113
114 cp = CreateTrayComponent();
115 cp->object = pp;
116 pp->cp = cp;
117 cp->Create = Create;
118 cp->SetSize = SetSize;
119 cp->ProcessButtonPress = ProcessPagerButtonEvent;
120 cp->ProcessMotionEvent = ProcessPagerMotionEvent;
121
122 RegisterCallback(settings.popupDelay / 2, SignalPager, pp);
123
124 return cp;
125 }
126
127 /** Initialize a pager tray component. */
Create(TrayComponentType * cp)128 void Create(TrayComponentType *cp)
129 {
130
131 PagerType *pp;
132
133 Assert(cp);
134
135 pp = (PagerType*)cp->object;
136
137 Assert(pp);
138
139 Assert(cp->width > 0);
140 Assert(cp->height > 0);
141
142 cp->pixmap = JXCreatePixmap(display, rootWindow, cp->width,
143 cp->height, rootDepth);
144 pp->buffer = cp->pixmap;
145
146 }
147
148 /** Set the size of a pager tray component. */
SetSize(TrayComponentType * cp,int width,int height)149 void SetSize(TrayComponentType *cp, int width, int height)
150 {
151
152 PagerType *pp = (PagerType*)cp->object;
153
154 if(width) {
155
156 /* Vertical pager. */
157 cp->width = width;
158
159 pp->deskWidth = width / settings.desktopWidth;
160 pp->deskHeight = (pp->deskWidth * rootHeight) / rootWidth;
161
162 cp->height = pp->deskHeight * settings.desktopHeight
163 + settings.desktopHeight - 1;
164
165 } else if(height) {
166
167 /* Horizontal pager. */
168 cp->height = height;
169
170 pp->deskHeight = height / settings.desktopHeight;
171 pp->deskWidth = (pp->deskHeight * rootWidth) / rootHeight;
172
173 cp->width = pp->deskWidth * settings.desktopWidth
174 + settings.desktopWidth - 1;
175
176 } else {
177 Assert(0);
178 }
179
180 if(pp->buffer != None) {
181 JXFreePixmap(display, pp->buffer);
182 pp->buffer = JXCreatePixmap(display, rootWindow, cp->width,
183 cp->height, rootDepth);
184 cp->pixmap = pp->buffer;
185 DrawPager(pp);
186 }
187
188 pp->scalex = ((pp->deskWidth - 2) << 16) / rootWidth;
189 pp->scaley = ((pp->deskHeight - 2) << 16) / rootHeight;
190
191 }
192
193 /** Get the desktop for a pager given a set of coordinates. */
GetPagerDesktop(PagerType * pp,int x,int y)194 int GetPagerDesktop(PagerType *pp, int x, int y)
195 {
196
197 int pagerx, pagery;
198
199 pagerx = x / (pp->deskWidth + 1);
200 pagery = y / (pp->deskHeight + 1);
201
202 return pagery * settings.desktopWidth + pagerx;
203
204 }
205
206 /** Process a button event on a pager tray component. */
ProcessPagerButtonEvent(TrayComponentType * cp,int x,int y,int mask)207 void ProcessPagerButtonEvent(TrayComponentType *cp, int x, int y, int mask)
208 {
209
210 PagerType *pp;
211
212 switch(mask) {
213 case Button1:
214 case Button2:
215
216 /* Change to the selected desktop. */
217 pp = (PagerType*)cp->object;
218 ChangeDesktop(GetPagerDesktop(pp, x, y));
219 break;
220
221 case Button3:
222
223 /* Move a client and possibly change its desktop. */
224 StartPagerMove(cp, x, y);
225 break;
226
227 case Button4:
228
229 /* Change to the previous desktop. */
230 LeftDesktop();
231 break;
232
233 case Button5:
234
235 /* Change to the next desktop. */
236 RightDesktop();
237 break;
238
239 default:
240 break;
241 }
242 }
243
244 /** Process a motion event on a pager tray component. */
ProcessPagerMotionEvent(TrayComponentType * cp,int x,int y,int mask)245 void ProcessPagerMotionEvent(TrayComponentType *cp, int x, int y, int mask)
246 {
247
248 PagerType *pp = (PagerType*)cp->object;
249
250 pp->mousex = cp->screenx + x;
251 pp->mousey = cp->screeny + y;
252 GetCurrentTime(&pp->mouseTime);
253 }
254
255 /** Start a pager move operation. */
StartPagerMove(TrayComponentType * cp,int x,int y)256 void StartPagerMove(TrayComponentType *cp, int x, int y)
257 {
258
259 XEvent event;
260 PagerType *pp;
261 ClientNode *np;
262 int layer;
263 int desktop;
264 int cx, cy;
265 int cwidth, cheight;
266
267 int north, south, east, west;
268 int oldx, oldy;
269 int oldDesk;
270 int startx, starty;
271 MaxFlags maxFlags;
272
273 pp = (PagerType*)cp->object;
274
275 /* Determine the selected desktop. */
276 desktop = GetPagerDesktop(pp, x, y);
277 x -= (desktop % settings.desktopWidth) * (pp->deskWidth + 1);
278 y -= (desktop / settings.desktopWidth) * (pp->deskHeight + 1);
279
280 /* Find the client under the specified coordinates. */
281 for(layer = LAST_LAYER; layer >= FIRST_LAYER; layer--) {
282 for(np = nodes[layer]; np; np = np->next) {
283
284 /* Skip this client if it isn't mapped. */
285 if(!(np->state.status & STAT_MAPPED)) {
286 continue;
287 }
288 if(np->state.status & STAT_NOPAGER) {
289 continue;
290 }
291
292 /* Skip this client if it isn't on the selected desktop. */
293 if(np->state.status & STAT_STICKY) {
294 if(currentDesktop != desktop) {
295 continue;
296 }
297 } else {
298 if(np->state.desktop != desktop) {
299 continue;
300 }
301 }
302
303 /* Get the offset and size of the client on the pager. */
304 cx = 1 + ((np->x * pp->scalex) >> 16);
305 cy = 1 + ((np->y * pp->scaley) >> 16);
306 cwidth = (np->width * pp->scalex) >> 16;
307 cheight = (np->height * pp->scaley) >> 16;
308
309 /* Normalize the offset and size. */
310 if(cx + cwidth > pp->deskWidth) {
311 cwidth = pp->deskWidth - cx;
312 }
313 if(cy + cheight > pp->deskHeight) {
314 cheight = pp->deskHeight - cy;
315 }
316 if(cx < 0) {
317 cwidth += cx;
318 cx = 0;
319 }
320 if(cy < 0) {
321 cheight += cy;
322 cy = 0;
323 }
324
325 /* Skip the client if we are no longer in bounds. */
326 if(cwidth <= 0 || cheight <= 0) {
327 continue;
328 }
329
330 /* Check the y-coordinate. */
331 if(y < cy || y > cy + cheight) {
332 continue;
333 }
334
335 /* Check the x-coordinate. */
336 if(x < cx || x > cx + cwidth) {
337 continue;
338 }
339
340 /* Found it. Exit. */
341 goto ClientFound;
342
343 }
344 }
345
346 /* Client wasn't found. Just return. */
347 return;
348
349 ClientFound:
350
351 Assert(np);
352
353 /* The selected client was found. Now make sure we can move it. */
354 if(!(np->state.border & BORDER_MOVE)) {
355 return;
356 }
357
358 /* Start the move. */
359 if(!GrabMouseForMove()) {
360 return;
361 }
362
363 /* If the client is maximized, unmaximize it. */
364 maxFlags = np->state.maxFlags;
365 if(np->state.maxFlags) {
366 MaximizeClient(np, MAX_NONE);
367 }
368
369 GetBorderSize(&np->state, &north, &south, &east, &west);
370
371 np->controller = PagerMoveController;
372 shouldStopMove = 0;
373
374 oldx = np->x;
375 oldy = np->y;
376 oldDesk = np->state.desktop;
377
378 startx = x;
379 starty = y;
380
381 if(!(GetMouseMask() & Button3Mask)) {
382 StopPagerMove(np, oldx, oldy, oldDesk, maxFlags);
383 }
384
385 for(;;) {
386
387 WaitForEvent(&event);
388
389 if(shouldStopMove) {
390 np->controller = NULL;
391 return;
392 }
393
394 switch(event.type) {
395 case ButtonRelease:
396
397 /* Done when the 3rd mouse button is released. */
398 if(event.xbutton.button == Button3) {
399 StopPagerMove(np, oldx, oldy, oldDesk, maxFlags);
400 return;
401 }
402 break;
403
404 case MotionNotify:
405
406 SetMousePosition(event.xmotion.x_root, event.xmotion.y_root,
407 event.xmotion.window);
408
409 /* Get the mouse position on the pager. */
410 x = event.xmotion.x_root - cp->screenx;
411 y = event.xmotion.y_root - cp->screeny;
412
413 /* Don't move if we are off of the pager. */
414 if(x < 0 || x > cp->width) {
415 break;
416 }
417 if(y < 0 || y > cp->height) {
418 break;
419 }
420
421 /* Determine the new client desktop. */
422 desktop = GetPagerDesktop(pp, x, y);
423 x -= pp->deskWidth * (desktop % settings.desktopWidth);
424 y -= pp->deskHeight * (desktop / settings.desktopWidth);
425
426 /* If this client isn't sticky and now on a different desktop
427 * change the client's desktop. */
428 if(!(np->state.status & STAT_STICKY)) {
429 if(desktop != oldDesk) {
430 SetClientDesktop(np, (unsigned int)desktop);
431 oldDesk = desktop;
432 }
433 }
434
435 /* Get new client coordinates. */
436 oldx = startx + (x - startx);
437 oldx = (oldx << 16) / pp->scalex;
438 oldx -= (np->width + east + west) / 2;
439 oldy = starty + (y - starty);
440 oldy = (oldy << 16) / pp->scaley;
441 oldy -= (np->height + north + south) / 2;
442
443 /* Move the window. */
444 np->x = oldx;
445 np->y = oldy;
446 JXMoveWindow(display, np->parent, np->x - west, np->y - north);
447 SendConfigureEvent(np);
448 RequirePagerUpdate();
449
450 break;
451
452 default:
453 break;
454 }
455
456 }
457
458 }
459
460 /** Stop an active pager move. */
StopPagerMove(ClientNode * np,int x,int y,int desktop,MaxFlags maxFlags)461 void StopPagerMove(ClientNode *np,
462 int x, int y, int desktop, MaxFlags maxFlags)
463 {
464
465 int north, south, east, west;
466
467 Assert(np);
468 Assert(np->controller);
469
470 /* Release grabs. */
471 (np->controller)(0);
472
473 np->x = x;
474 np->y = y;
475
476 GetBorderSize(&np->state, &north, &south, &east, & west);
477 JXMoveWindow(display, np->parent, np->x - west, np->y - north);
478 SendConfigureEvent(np);
479
480 /* Restore the maximized state of the client. */
481 if(maxFlags != MAX_NONE) {
482 MaximizeClient(np, maxFlags);
483 }
484
485 /* Redraw the pager. */
486 RequirePagerUpdate();
487
488 }
489
490 /** Client-terminated pager move. */
PagerMoveController(int wasDestroyed)491 void PagerMoveController(int wasDestroyed)
492 {
493
494 JXUngrabPointer(display, CurrentTime);
495 JXUngrabKeyboard(display, CurrentTime);
496 shouldStopMove = 1;
497
498 }
499
500 /** Draw a pager. */
DrawPager(const PagerType * pp)501 void DrawPager(const PagerType *pp)
502 {
503 ClientNode *np;
504 Pixmap buffer;
505 int width, height;
506 int deskWidth, deskHeight;
507 unsigned int x;
508 const char *name;
509 int xc, yc;
510 int textWidth, textHeight;
511 int dx, dy;
512
513 buffer = pp->cp->pixmap;
514 width = pp->cp->width;
515 height = pp->cp->height;
516 deskWidth = pp->deskWidth;
517 deskHeight = pp->deskHeight;
518
519 /* Draw the background. */
520 JXSetForeground(display, rootGC, colors[COLOR_PAGER_BG]);
521 JXFillRectangle(display, buffer, rootGC, 0, 0, width, height);
522
523 /* Highlight the current desktop. */
524 JXSetForeground(display, rootGC, colors[COLOR_PAGER_ACTIVE_BG]);
525 dx = currentDesktop % settings.desktopWidth;
526 dy = currentDesktop / settings.desktopWidth;
527 JXFillRectangle(display, buffer, rootGC,
528 dx * (deskWidth + 1), dy * (deskHeight + 1),
529 deskWidth, deskHeight);
530
531 /* Draw the labels. */
532 if(pp->labeled) {
533 textHeight = GetStringHeight(FONT_PAGER);
534 if(textHeight < deskHeight) {
535 for(x = 0; x < settings.desktopCount; x++) {
536 dx = x % settings.desktopWidth;
537 dy = x / settings.desktopWidth;
538 name = GetDesktopName(x);
539 textWidth = GetStringWidth(FONT_PAGER, name);
540 if(textWidth < deskWidth) {
541 xc = dx * (deskWidth + 1) + (deskWidth - textWidth) / 2;
542 yc = dy * (deskHeight + 1) + (deskHeight - textHeight) / 2;
543 RenderString(buffer, FONT_PAGER,
544 COLOR_PAGER_TEXT, xc, yc, deskWidth, name);
545 }
546 }
547 }
548 }
549
550 /* Draw the clients. */
551 for(x = FIRST_LAYER; x <= LAST_LAYER; x++) {
552 for(np = nodeTail[x]; np; np = np->prev) {
553 DrawPagerClient(pp, np);
554 }
555 }
556
557 /* Draw the desktop dividers. */
558 JXSetForeground(display, rootGC, colors[COLOR_PAGER_OUTLINE]);
559 for(x = 1; x < settings.desktopHeight; x++) {
560 JXDrawLine(display, buffer, rootGC,
561 0, (deskHeight + 1) * x - 1,
562 width, (deskHeight + 1) * x - 1);
563 }
564 for(x = 1; x < settings.desktopWidth; x++) {
565 JXDrawLine(display, buffer, rootGC,
566 (deskWidth + 1) * x - 1, 0,
567 (deskWidth + 1) * x - 1, height);
568 }
569
570 }
571
572 /** Update the pager. */
UpdatePager(void)573 void UpdatePager(void)
574 {
575
576 PagerType *pp;
577
578 if(JUNLIKELY(shouldExit)) {
579 return;
580 }
581
582 for(pp = pagers; pp; pp = pp->next) {
583
584 /* Draw the pager. */
585 DrawPager(pp);
586
587 /* Tell the tray to redraw. */
588 UpdateSpecificTray(pp->cp->tray, pp->cp);
589
590 }
591
592 }
593
594 /** Signal pagers (for popups). */
SignalPager(const TimeType * now,int x,int y,Window w,void * data)595 void SignalPager(const TimeType *now, int x, int y, Window w, void *data)
596 {
597 PagerType *pp = (PagerType*)data;
598 if(pp->cp->tray->window == w &&
599 abs(pp->mousex - x) < settings.doubleClickDelta &&
600 abs(pp->mousey - y) < settings.doubleClickDelta) {
601 if(GetTimeDifference(now, &pp->mouseTime) >= settings.popupDelay) {
602 const int desktop = GetPagerDesktop(pp, x - pp->cp->screenx,
603 y - pp->cp->screeny);
604 if(desktop >= 0 && desktop < settings.desktopCount) {
605 const char *desktopName = GetDesktopName(desktop);
606 if(desktopName) {
607 ShowPopup(x, y, desktopName, POPUP_PAGER);
608 }
609 }
610
611 }
612 }
613 }
614
615 /** Draw a client on the pager. */
DrawPagerClient(const PagerType * pp,const ClientNode * np)616 void DrawPagerClient(const PagerType *pp, const ClientNode *np)
617 {
618
619 int x, y;
620 int width, height;
621 int offx, offy;
622
623 /* Don't draw the client if it isn't mapped. */
624 if(!(np->state.status & STAT_MAPPED)) {
625 return;
626 }
627 if(np->state.status & STAT_NOPAGER) {
628 return;
629 }
630
631 /* Determine the desktop for the client. */
632 if(np->state.status & STAT_STICKY) {
633 offx = currentDesktop % settings.desktopWidth;
634 offy = currentDesktop / settings.desktopWidth;
635 } else {
636 offx = np->state.desktop % settings.desktopWidth;
637 offy = np->state.desktop / settings.desktopWidth;
638 }
639 offx *= pp->deskWidth + 1;
640 offy *= pp->deskHeight + 1;
641
642 /* Determine the location and size of the client on the pager. */
643 x = 1 + ((np->x * pp->scalex) >> 16);
644 y = 1 + ((np->y * pp->scaley) >> 16);
645 width = (np->width * pp->scalex) >> 16;
646 height = (np->height * pp->scaley) >> 16;
647
648 /* Normalize the size and offset. */
649 if(x + width > pp->deskWidth) {
650 width = pp->deskWidth - x;
651 }
652 if(y + height > pp->deskHeight) {
653 height = pp->deskHeight - y;
654 }
655 if(x < 0) {
656 width += x;
657 x = 0;
658 }
659 if(y < 0) {
660 height += y;
661 y = 0;
662 }
663
664 /* Return if there's nothing to do. */
665 if(width <= 0 || height <= 0) {
666 return;
667 }
668
669 /* Move to the correct desktop on the pager. */
670 x += offx;
671 y += offy;
672
673 /* Draw the client outline. */
674 JXSetForeground(display, rootGC, colors[COLOR_PAGER_OUTLINE]);
675 JXDrawRectangle(display, pp->cp->pixmap, rootGC, x, y, width, height);
676
677 /* Fill the client if there's room. */
678 if(width > 1 && height > 1) {
679 ColorType fillColor;
680 if((np->state.status & STAT_ACTIVE)
681 && (np->state.desktop == currentDesktop
682 || (np->state.status & STAT_STICKY))) {
683 fillColor = COLOR_PAGER_ACTIVE_FG;
684 } else if(np->state.status & STAT_FLASH) {
685 fillColor = COLOR_PAGER_ACTIVE_FG;
686 } else {
687 fillColor = COLOR_PAGER_FG;
688 }
689 JXSetForeground(display, rootGC, colors[fillColor]);
690 JXFillRectangle(display, pp->cp->pixmap, rootGC, x + 1, y + 1,
691 width - 1, height - 1);
692 }
693
694 }
695
696