JWM Source Documentation
move.c
Go to the documentation of this file.
1 
10 #include "jwm.h"
11 #include "move.h"
12 
13 #include "border.h"
14 #include "client.h"
15 #include "clientlist.h"
16 #include "cursor.h"
17 #include "event.h"
18 #include "key.h"
19 #include "outline.h"
20 #include "pager.h"
21 #include "screen.h"
22 #include "status.h"
23 #include "tray.h"
24 #include "desktop.h"
25 #include "settings.h"
26 #include "timing.h"
27 
28 typedef struct {
29  int left, right;
30  int top, bottom;
31  char valid;
33 
34 static char shouldStopMove;
35 static char atLeft;
36 static char atRight;
37 static char atBottom;
38 static char atTop;
39 static char atSideFirst;
42 
43 static void StopMove(ClientNode *np, int doMove, int oldx, int oldy);
44 static void MoveController(int wasDestroyed);
45 
46 static void DoSnap(ClientNode *np);
47 static void DoSnapScreen(ClientNode *np);
48 static void DoSnapBorder(ClientNode *np);
49 static char ShouldSnap(const ClientNode *np);
50 static void GetClientRectangle(const ClientNode *np, RectangleType *r);
51 
52 static char CheckOverlapTopBottom(const RectangleType *a,
53  const RectangleType *b);
54 static char CheckOverlapLeftRight(const RectangleType *a,
55  const RectangleType *b);
56 
57 static char CheckLeftValid(const RectangleType *client,
58  const RectangleType *other,
59  const RectangleType *left);
60 static char CheckRightValid(const RectangleType *client,
61  const RectangleType *other,
62  const RectangleType *right);
63 static char CheckTopValid(const RectangleType *client,
64  const RectangleType *other,
65  const RectangleType *top);
66 static char CheckBottomValid(const RectangleType *client,
67  const RectangleType *other,
68  const RectangleType *bottom);
69 
70 static void SignalMove(const TimeType *now, int x, int y, Window w, void *data);
71 static void UpdateDesktop(const TimeType *now);
72 
74 void MoveController(int wasDestroyed)
75 {
76 
78  ClearOutline();
79  }
80 
81  JXUngrabPointer(display, CurrentTime);
82  JXUngrabKeyboard(display, CurrentTime);
83 
85  shouldStopMove = 1;
86  atTop = 0;
87  atBottom = 0;
88  atLeft = 0;
89  atRight = 0;
90  atSideFirst = 0;
91 }
92 
94 char MoveClient(ClientNode *np, int startx, int starty)
95 {
96 
97  XEvent event;
98  int oldx, oldy;
99  int doMove;
100  int north, south, east, west;
101  int height;
102 
103  Assert(np);
104 
105  if(!(np->state.border & BORDER_MOVE)) {
106  return 0;
107  }
108  if(np->state.status & STAT_FULLSCREEN) {
109  return 0;
110  }
111 
112  if(!GrabMouseForMove()) {
113  return 0;
114  }
115 
116  RegisterCallback(0, SignalMove, NULL);
118  shouldStopMove = 0;
119 
120  oldx = np->x;
121  oldy = np->y;
122 
123  if(!(GetMouseMask() & (Button1Mask | Button2Mask))) {
124  StopMove(np, 0, oldx, oldy);
125  return 0;
126  }
127 
128  GetBorderSize(&np->state, &north, &south, &east, &west);
129 
130  startx -= west;
131  starty -= north;
132 
133  currentClient = np;
135  doMove = 0;
136  for(;;) {
137 
138  WaitForEvent(&event);
139 
140  if(shouldStopMove) {
141  np->controller = NULL;
144  return doMove;
145  }
146 
147  switch(event.type) {
148  case ButtonRelease:
149  if(event.xbutton.button == Button1
150  || event.xbutton.button == Button2) {
151  StopMove(np, doMove, oldx, oldy);
152  return doMove;
153  }
154  break;
155  case MotionNotify:
156 
157  DiscardMotionEvents(&event, np->window);
158 
159  np->x = event.xmotion.x_root - startx;
160  np->y = event.xmotion.y_root - starty;
161 
162  /* Get the move time used for desktop switching. */
163  if(!(atLeft | atTop | atRight | atBottom)) {
164  if(event.xmotion.state & Mod1Mask) {
165  moveTime.seconds = 0;
166  moveTime.ms = 0;
167  } else {
168  GetCurrentTime(&moveTime);
169  }
170  }
171 
172  /* Determine if we are at a border for desktop switching. */
173  atLeft = atTop = atRight = atBottom = 0;
174  if(event.xmotion.x_root == 0) {
175  atLeft = 1;
176  } else if(event.xmotion.x_root == rootWidth - 1) {
177  atRight = 1;
178  }
179  if(event.xmotion.y_root == 0) {
180  atTop = 1;
181  } else if(event.xmotion.y_root == rootHeight - 1) {
182  atBottom = 1;
183  }
184 
185  if(event.xmotion.state & Mod1Mask) {
186  /* Switch desktops immediately if alt is pressed. */
187  if(atLeft | atRight | atTop | atBottom) {
188  TimeType now;
189  GetCurrentTime(&now);
190  UpdateDesktop(&now);
191  }
192  } else {
193  /* If alt is not pressed, snap to borders. */
194  if(np->state.status & STAT_AEROSNAP) {
195  MaxFlags flags = MAX_NONE;
196  if(atTop & atLeft) {
197  if(atSideFirst) {
198  flags = MAX_TOP | MAX_LEFT;
199  } else {
200  flags = MAX_TOP | MAX_HORIZ;
201  }
202  } else if(atTop & atRight) {
203  if(atSideFirst) {
204  flags = MAX_TOP | MAX_RIGHT;
205  } else {
206  flags = MAX_TOP | MAX_HORIZ;
207  }
208  } else if(atBottom & atLeft) {
209  if(atSideFirst) {
210  flags = MAX_BOTTOM | MAX_LEFT;
211  } else {
212  flags = MAX_BOTTOM | MAX_HORIZ;
213  }
214  } else if(atBottom & atRight) {
215  if(atSideFirst) {
216  flags = MAX_BOTTOM | MAX_RIGHT;
217  } else {
218  flags = MAX_BOTTOM | MAX_HORIZ;
219  }
220  } else if(atLeft) {
221  flags = MAX_LEFT | MAX_VERT;
222  atSideFirst = 1;
223  } else if(atRight) {
224  flags = MAX_RIGHT | MAX_VERT;
225  atSideFirst = 1;
226  } else if(atTop | atBottom) {
227  flags = MAX_VERT | MAX_HORIZ;
228  atSideFirst = 0;
229  }
230  MaximizeClient(np, flags);
231  if(!np->state.maxFlags) {
232  DoSnap(np);
233  }
234  } else {
235  DoSnap(np);
236  }
237  }
238 
239  if(!doMove && (abs(np->x - oldx) > MOVE_DELTA
240  || abs(np->y - oldy) > MOVE_DELTA)) {
241 
242  if(np->state.maxFlags) {
244  startx = np->width / 2;
245  starty = -north / 2;
246  if(np->parent != None) {
247  MoveMouse(np->parent, startx, starty);
248  } else {
249  MoveMouse(np->window, startx, starty);
250  }
251  }
252 
253  CreateMoveWindow(np);
254  doMove = 1;
255  }
256 
257  if(doMove) {
258 
260  ClearOutline();
261  height = north + south;
262  if(!(np->state.status & STAT_SHADED)) {
263  height += np->height;
264  }
265  DrawOutline(np->x - west, np->y - north,
266  np->width + west + east, height);
267  } else {
268  if(np->parent != None) {
269  JXMoveWindow(display, np->parent, np->x - west,
270  np->y - north);
271  } else {
272  JXMoveWindow(display, np->window, np->x, np->y);
273  }
274  SendConfigureEvent(np);
275  }
276  UpdateMoveWindow(np);
278  }
279 
280  break;
281  default:
282  break;
283  }
284  }
285 }
286 
289 {
290 
291  XEvent event;
292  int oldx, oldy;
293  int moved;
294  int height;
295  int north, south, east, west;
296  Window win;
297 
298  Assert(np);
299 
300  if(!(np->state.border & BORDER_MOVE)) {
301  return 0;
302  }
303  if(np->state.status & STAT_FULLSCREEN) {
304  return 0;
305  }
306 
307  if(np->state.maxFlags != MAX_NONE) {
309  }
310 
311  win = np->parent != None ? np->parent : np->window;
312  if(JUNLIKELY(JXGrabKeyboard(display, win, True, GrabModeAsync,
313  GrabModeAsync, CurrentTime))) {
314  Debug("MoveClient: could not grab keyboard");
315  return 0;
316  }
317  if(!GrabMouseForMove()) {
318  JXUngrabKeyboard(display, CurrentTime);
319  return 0;
320  }
321 
322  GetBorderSize(&np->state, &north, &south, &east, &west);
323 
324  oldx = np->x;
325  oldy = np->y;
326 
327  RegisterCallback(0, SignalMove, NULL);
329  shouldStopMove = 0;
330 
331  CreateMoveWindow(np);
332  UpdateMoveWindow(np);
333 
334  MoveMouse(rootWindow, np->x, np->y);
335  DiscardMotionEvents(&event, np->window);
336 
337  if(np->state.status & STAT_SHADED) {
338  height = 0;
339  } else {
340  height = np->height;
341  }
342 
343  for(;;) {
344 
345  WaitForEvent(&event);
346 
347  if(shouldStopMove) {
348  np->controller = NULL;
351  return 1;
352  }
353 
354  moved = 0;
355 
356  if(event.type == KeyPress) {
357 
358  DiscardKeyEvents(&event, np->window);
359  switch(GetKey(&event.xkey) & 0xFF) {
360  case KEY_UP:
361  if(np->y + height > 0) {
362  np->y -= 10;
363  }
364  break;
365  case KEY_DOWN:
366  if(np->y < rootHeight) {
367  np->y += 10;
368  }
369  break;
370  case KEY_RIGHT:
371  if(np->x < rootWidth) {
372  np->x += 10;
373  }
374  break;
375  case KEY_LEFT:
376  if(np->x + np->width > 0) {
377  np->x -= 10;
378  }
379  break;
380  default:
381  StopMove(np, 1, oldx, oldy);
382  return 1;
383  }
384 
385  MoveMouse(rootWindow, np->x, np->y);
386  DiscardMotionEvents(&event, np->window);
387 
388  moved = 1;
389 
390  } else if(event.type == MotionNotify) {
391 
392  DiscardMotionEvents(&event, np->window);
393 
394  np->x = event.xmotion.x;
395  np->y = event.xmotion.y;
396 
397  moved = 1;
398 
399  } else if(event.type == ButtonRelease) {
400 
401  StopMove(np, 1, oldx, oldy);
402  return 1;
403 
404  }
405 
406  if(moved) {
407 
409  ClearOutline();
410  DrawOutline(np->x - west, np->y - west,
411  np->width + west + east, height + north + west);
412  } else {
413  JXMoveWindow(display, win, np->x - west, np->y - north);
414  SendConfigureEvent(np);
415  }
416 
417  UpdateMoveWindow(np);
419 
420  }
421 
422  }
423 
424 }
425 
427 void StopMove(ClientNode *np, int doMove, int oldx, int oldy)
428 {
429 
430  int north, south, east, west;
431 
432  Assert(np);
433  Assert(np->controller);
434 
435  (np->controller)(0);
436 
437  np->controller = NULL;
438 
441 
442  if(!doMove) {
443  np->x = oldx;
444  np->y = oldy;
445  return;
446  }
447 
448  GetBorderSize(&np->state, &north, &south, &east, &west);
449 
450  if(np->parent != None) {
451  JXMoveWindow(display, np->parent, np->x - west, np->y - north);
452  } else {
453  JXMoveWindow(display, np->window, np->x - west, np->y - north);
454  }
455  SendConfigureEvent(np);
456 }
457 
460 {
461  switch(settings.snapMode) {
462  case SNAP_BORDER:
463  DoSnapBorder(np);
464  DoSnapScreen(np);
465  break;
466  case SNAP_SCREEN:
467  DoSnapScreen(np);
468  break;
469  default:
470  break;
471  }
472 }
473 
476 {
477 
478  RectangleType client;
479  int screen;
480  const ScreenType *sp;
481  int screenCount;
482  int north, south, east, west;
483 
484  GetClientRectangle(np, &client);
485 
486  GetBorderSize(&np->state, &north, &south, &east, &west);
487 
488  screenCount = GetScreenCount();
489  for(screen = 0; screen < screenCount; screen++) {
490 
491  sp = GetScreen(screen);
492 
493  if(abs(client.right - sp->width - sp->x) <= settings.snapDistance) {
494  np->x = sp->x + sp->width - west - np->width;
495  }
496  if(abs(client.left - sp->x) <= settings.snapDistance) {
497  np->x = sp->x + east;
498  }
499  if(abs(client.bottom - sp->height - sp->y) <= settings.snapDistance) {
500  np->y = sp->y + sp->height - south;
501  if(!(np->state.status & STAT_SHADED)) {
502  np->y -= np->height;
503  }
504  }
505  if(abs(client.top - sp->y) <= settings.snapDistance) {
506  np->y = north + sp->y;
507  }
508 
509  }
510 
511 }
512 
515 {
516 
517  const ClientNode *tp;
518  const TrayType *tray;
519  RectangleType client, other;
520  RectangleType left = { 0 };
521  RectangleType right = { 0 };
522  RectangleType top = { 0 };
523  RectangleType bottom = { 0 };
524  int layer;
525  int north, south, east, west;
526 
527  GetClientRectangle(np, &client);
528 
529  GetBorderSize(&np->state, &north, &south, &east, &west);
530 
531  other.valid = 1;
532 
533  /* Work from the bottom of the window stack to the top. */
534  for(layer = 0; layer < LAYER_COUNT; layer++) {
535 
536  /* Check tray windows. */
537  for(tray = GetTrays(); tray; tray = tray->next) {
538 
539  if(tray->hidden) {
540  continue;
541  }
542 
543  other.left = tray->x;
544  other.right = tray->x + tray->width;
545  other.top = tray->y;
546  other.bottom = tray->y + tray->height;
547 
548  left.valid = CheckLeftValid(&client, &other, &left);
549  right.valid = CheckRightValid(&client, &other, &right);
550  top.valid = CheckTopValid(&client, &other, &top);
551  bottom.valid = CheckBottomValid(&client, &other, &bottom);
552 
553  if(CheckOverlapTopBottom(&client, &other)) {
554  if(abs(client.left - other.right) <= settings.snapDistance) {
555  left = other;
556  }
557  if(abs(client.right - other.left) <= settings.snapDistance) {
558  right = other;
559  }
560  }
561  if(CheckOverlapLeftRight(&client, &other)) {
562  if(abs(client.top - other.bottom) <= settings.snapDistance) {
563  top = other;
564  }
565  if(abs(client.bottom - other.top) <= settings.snapDistance) {
566  bottom = other;
567  }
568  }
569 
570  }
571 
572  /* Check client windows. */
573  for(tp = nodeTail[layer]; tp; tp = tp->prev) {
574 
575  if(tp == np || !ShouldSnap(tp)) {
576  continue;
577  }
578 
579  GetClientRectangle(tp, &other);
580 
581  /* Check if this border invalidates any previous value. */
582  left.valid = CheckLeftValid(&client, &other, &left);
583  right.valid = CheckRightValid(&client, &other, &right);
584  top.valid = CheckTopValid(&client, &other, &top);
585  bottom.valid = CheckBottomValid(&client, &other, &bottom);
586 
587  /* Compute the new snap values. */
588  if(CheckOverlapTopBottom(&client, &other)) {
589  if(abs(client.left - other.right) <= settings.snapDistance) {
590  left = other;
591  }
592  if(abs(client.right - other.left) <= settings.snapDistance) {
593  right = other;
594  }
595  }
596  if(CheckOverlapLeftRight(&client, &other)) {
597  if(abs(client.top - other.bottom) <= settings.snapDistance) {
598  top = other;
599  }
600  if(abs(client.bottom - other.top) <= settings.snapDistance) {
601  bottom = other;
602  }
603  }
604 
605  }
606 
607  }
608 
609  if(right.valid) {
610  np->x = right.left - np->width - west;
611  }
612  if(left.valid) {
613  np->x = left.right + east;
614  }
615  if(bottom.valid) {
616  np->y = bottom.top - south;
617  if(!(np->state.status & STAT_SHADED)) {
618  np->y -= np->height;
619  }
620  }
621  if(top.valid) {
622  np->y = top.bottom + north;
623  }
624 
625 }
626 
628 char ShouldSnap(const ClientNode *np)
629 {
630  if(np->state.status & STAT_HIDDEN) {
631  return 0;
632  } else if(np->state.status & STAT_MINIMIZED) {
633  return 0;
634  } else {
635  return 1;
636  }
637 }
638 
641 {
642 
643  int north, south, east, west;
644 
645  GetBorderSize(&np->state, &north, &south, &east, &west);
646 
647  r->left = np->x - west;
648  r->right = np->x + np->width + east;
649  r->top = np->y - north;
650  if(np->state.status & STAT_SHADED) {
651  r->bottom = np->y + south;
652  } else {
653  r->bottom = np->y + np->height + south;
654  }
655 
656  r->valid = 1;
657 
658 }
659 
662 {
663  if(a->top >= b->bottom) {
664  return 0;
665  } else if(a->bottom <= b->top) {
666  return 0;
667  } else {
668  return 1;
669  }
670 }
671 
674 {
675  if(a->left >= b->right) {
676  return 0;
677  } else if(a->right <= b->left) {
678  return 0;
679  } else {
680  return 1;
681  }
682 }
683 
691 char CheckLeftValid(const RectangleType *client,
692  const RectangleType *other, const RectangleType *left)
693 {
694 
695  if(!left->valid) {
696  return 0;
697  }
698 
699  if(left->right > other->right) {
700  return 1;
701  }
702 
703  /* If left and client go higher than other then still valid. */
704  if(left->top < other->top && client->top < other->top) {
705  return 1;
706  }
707 
708  /* If left and client go lower than other then still valid. */
709  if(left->bottom > other->bottom && client->bottom > other->bottom) {
710  return 1;
711  }
712 
713  if(other->left >= left->right) {
714  return 1;
715  }
716 
717  return 0;
718 
719 }
720 
722 char CheckRightValid(const RectangleType *client,
723  const RectangleType *other, const RectangleType *right)
724 {
725 
726  if(!right->valid) {
727  return 0;
728  }
729 
730  if(right->left < other->left) {
731  return 1;
732  }
733 
734  /* If right and client go higher than other then still valid. */
735  if(right->top < other->top && client->top < other->top) {
736  return 1;
737  }
738 
739  /* If right and client go lower than other then still valid. */
740  if(right->bottom > other->bottom && client->bottom > other->bottom) {
741  return 1;
742  }
743 
744  if(other->right <= right->left) {
745  return 1;
746  }
747 
748  return 0;
749 
750 }
751 
753 char CheckTopValid(const RectangleType *client,
754  const RectangleType *other, const RectangleType *top)
755 {
756 
757  if(!top->valid) {
758  return 0;
759  }
760 
761  if(top->bottom > other->bottom) {
762  return 1;
763  }
764 
765  /* If top and client are to the left of other then still valid. */
766  if(top->left < other->left && client->left < other->left) {
767  return 1;
768  }
769 
770  /* If top and client are to the right of other then still valid. */
771  if(top->right > other->right && client->right > other->right) {
772  return 1;
773  }
774 
775  if(other->top >= top->bottom) {
776  return 1;
777  }
778 
779  return 0;
780 
781 }
782 
784 char CheckBottomValid(const RectangleType *client,
785  const RectangleType *other, const RectangleType *bottom)
786 {
787 
788  if(!bottom->valid) {
789  return 0;
790  }
791 
792  if(bottom->top < other->top) {
793  return 1;
794  }
795 
796  /* If bottom and client are to the left of other then still valid. */
797  if(bottom->left < other->left && client->left < other->left) {
798  return 1;
799  }
800 
801  /* If bottom and client are to the right of other then still valid. */
802  if(bottom->right > other->right && client->right > other->right) {
803  return 1;
804  }
805 
806  if(other->bottom <= bottom->top) {
807  return 1;
808  }
809 
810  return 0;
811 
812 }
813 
815 void SignalMove(const TimeType *now, int x, int y, Window w, void *data)
816 {
817  UpdateDesktop(now);
818 }
819 
821 void UpdateDesktop(const TimeType *now)
822 {
823  if(settings.desktopDelay == 0) {
824  return;
825  }
826  if(GetTimeDifference(now, &moveTime) < settings.desktopDelay) {
827  return;
828  }
829  moveTime = *now;
830 
831  if(atLeft && LeftDesktop()) {
832  SetClientDesktop(currentClient, currentDesktop);
833  RequireRestack();
834  } else if(atRight && RightDesktop()) {
835  SetClientDesktop(currentClient, currentDesktop);
836  RequireRestack();
837  } else if(atTop && AboveDesktop()) {
838  SetClientDesktop(currentClient, currentDesktop);
839  RequireRestack();
840  } else if(atBottom && BelowDesktop()) {
841  SetClientDesktop(currentClient, currentDesktop);
842  RequireRestack();
843  }
844 }

joewing.net / Projects / JWM