JWM Source Documentation
event.c
Go to the documentation of this file.
1 
10 #include "jwm.h"
11 #include "event.h"
12 
13 #include "client.h"
14 #include "clientlist.h"
15 #include "confirm.h"
16 #include "cursor.h"
17 #include "desktop.h"
18 #include "dock.h"
19 #include "icon.h"
20 #include "key.h"
21 #include "move.h"
22 #include "place.h"
23 #include "resize.h"
24 #include "root.h"
25 #include "swallow.h"
26 #include "taskbar.h"
27 #include "timing.h"
28 #include "winmenu.h"
29 #include "settings.h"
30 #include "tray.h"
31 #include "popup.h"
32 #include "pager.h"
33 #include "grab.h"
34 
35 #define MIN_TIME_DELTA 50
36 
37 Time eventTime = CurrentTime;
38 
39 typedef struct CallbackNode {
41  int freq;
43  void *data;
44  struct CallbackNode *next;
45 } CallbackNode;
46 
47 static CallbackNode *callbacks = NULL;
48 
49 static char restack_pending = 0;
50 static char task_update_pending = 0;
51 static char pager_update_pending = 0;
52 
53 static void Signal(void);
54 static void DispatchBorderButtonEvent(const XButtonEvent *event,
55  ClientNode *np);
56 
57 static void HandleConfigureRequest(const XConfigureRequestEvent *event);
58 static char HandleConfigureNotify(const XConfigureEvent *event);
59 static char HandleExpose(const XExposeEvent *event);
60 static char HandlePropertyNotify(const XPropertyEvent *event);
61 static void HandleClientMessage(const XClientMessageEvent *event);
62 static void HandleColormapChange(const XColormapEvent *event);
63 static char HandleDestroyNotify(const XDestroyWindowEvent *event);
64 static void HandleMapRequest(const XMapEvent *event);
65 static void HandleUnmapNotify(const XUnmapEvent *event);
66 static void HandleButtonEvent(const XButtonEvent *event);
67 static void ToggleMaximized(ClientNode *np, MaxFlags flags);
68 static void HandleKeyPress(const XKeyEvent *event);
69 static void HandleKeyRelease(const XKeyEvent *event);
70 static void HandleEnterNotify(const XCrossingEvent *event);
71 static void HandleMotionNotify(const XMotionEvent *event);
72 static char HandleSelectionClear(const XSelectionClearEvent *event);
73 
74 static void HandleNetMoveResize(const XClientMessageEvent *event,
75  ClientNode *np);
76 static void HandleNetWMMoveResize(const XClientMessageEvent *evnet,
77  ClientNode *np);
78 static void HandleNetRestack(const XClientMessageEvent *event,
79  ClientNode *np);
80 static void HandleNetWMState(const XClientMessageEvent *event,
81  ClientNode *np);
82 static void HandleFrameExtentsRequest(const XClientMessageEvent *event);
83 static void UpdateState(ClientNode *np);
84 static void DiscardEnterEvents();
85 
86 #ifdef USE_SHAPE
87 static void HandleShapeEvent(const XShapeEvent *event);
88 #endif
89 
91 char WaitForEvent(XEvent *event)
92 {
93  struct timeval timeout;
94  CallbackNode *cp;
95  fd_set fds;
96  long sleepTime;
97  int fd;
98  char handled;
99 
100 #ifdef ConnectionNumber
101  fd = ConnectionNumber(display);
102 #else
104 #endif
105 
106  /* Compute how long we should sleep. */
107  sleepTime = 10 * 1000; /* 10 seconds. */
108  for(cp = callbacks; cp; cp = cp->next) {
109  if(cp->freq > 0 && cp->freq < sleepTime) {
110  sleepTime = cp->freq;
111  }
112  }
113 
114  do {
115 
116  while(JXPending(display) == 0) {
117  FD_ZERO(&fds);
118  FD_SET(fd, &fds);
119  timeout.tv_sec = sleepTime / 1000;
120  timeout.tv_usec = (sleepTime % 1000) * 1000;
121  if(select(fd + 1, &fds, NULL, NULL, &timeout) <= 0) {
122  Signal();
123  }
124  if(JUNLIKELY(shouldExit)) {
125  return 0;
126  }
127  }
128 
129  Signal();
130 
131  JXNextEvent(display, event);
132  UpdateTime(event);
133 
134  switch(event->type) {
135  case ConfigureRequest:
136  HandleConfigureRequest(&event->xconfigurerequest);
137  handled = 1;
138  break;
139  case MapRequest:
140  HandleMapRequest(&event->xmap);
141  handled = 1;
142  break;
143  case PropertyNotify:
144  handled = HandlePropertyNotify(&event->xproperty);
145  break;
146  case ClientMessage:
147  HandleClientMessage(&event->xclient);
148  handled = 1;
149  break;
150  case UnmapNotify:
151  HandleUnmapNotify(&event->xunmap);
152  handled = 1;
153  break;
154  case Expose:
155  handled = HandleExpose(&event->xexpose);
156  break;
157  case ColormapNotify:
158  HandleColormapChange(&event->xcolormap);
159  handled = 1;
160  break;
161  case DestroyNotify:
162  handled = HandleDestroyNotify(&event->xdestroywindow);
163  break;
164  case SelectionClear:
165  handled = HandleSelectionClear(&event->xselectionclear);
166  break;
167  case ResizeRequest:
168  handled = HandleDockResizeRequest(&event->xresizerequest);
169  break;
170  case MotionNotify:
171  SetMousePosition(event->xmotion.x_root, event->xmotion.y_root,
172  event->xmotion.window);
173  handled = 0;
174  break;
175  case ButtonPress:
176  case ButtonRelease:
177  SetMousePosition(event->xbutton.x_root, event->xbutton.y_root,
178  event->xbutton.window);
179  handled = 0;
180  break;
181  case EnterNotify:
182  SetMousePosition(event->xcrossing.x_root, event->xcrossing.y_root,
183  event->xcrossing.window);
184  handled = 0;
185  break;
186  case LeaveNotify:
187  SetMousePosition(event->xcrossing.x_root, event->xcrossing.y_root,
188  None);
189  handled = 0;
190  break;
191  case ReparentNotify:
192  HandleDockReparentNotify(&event->xreparent);
193  handled = 1;
194  break;
195  case ConfigureNotify:
196  handled = HandleConfigureNotify(&event->xconfigure);
197  break;
198  case CreateNotify:
199  case MapNotify:
200  case GraphicsExpose:
201  case NoExpose:
202  handled = 1;
203  break;
204  default:
205  if(0) {
206 #ifdef USE_SHAPE
207  } else if(haveShape && event->type == shapeEvent) {
208  HandleShapeEvent((XShapeEvent*)event);
209  handled = 1;
210 #endif
211  } else {
212  handled = 0;
213  }
214  break;
215  }
216 
217  if(!handled) {
218  handled = ProcessTrayEvent(event);
219  }
220  if(!handled) {
221  handled = ProcessDialogEvent(event);
222  }
223  if(!handled) {
224  handled = ProcessSwallowEvent(event);
225  }
226  if(!handled) {
227  handled = ProcessPopupEvent(event);
228  }
229 
230  } while(handled && JLIKELY(!shouldExit));
231 
232  return !handled;
233 
234 }
235 
237 void Signal(void)
238 {
239  static TimeType last = ZERO_TIME;
240 
241  CallbackNode *cp;
242  TimeType now;
243  Window w;
244  int x, y;
245 
246  if(restack_pending) {
247  RestackClients();
248  restack_pending = 0;
249  }
250  if(task_update_pending) {
251  UpdateTaskBar();
253  }
255  UpdatePager();
257  }
258 
259  GetCurrentTime(&now);
260  if(GetTimeDifference(&now, &last) < MIN_TIME_DELTA) {
261  return;
262  }
263  last = now;
264 
265  GetMousePosition(&x, &y, &w);
266  for(cp = callbacks; cp; cp = cp->next) {
267  if(cp->freq == 0 || GetTimeDifference(&now, &cp->last) >= cp->freq) {
268  cp->last = now;
269  (cp->callback)(&now, x, y, w, cp->data);
270  }
271  }
272 }
273 
275 void ProcessEvent(XEvent *event)
276 {
277  switch(event->type) {
278  case ButtonPress:
279  case ButtonRelease:
280  HandleButtonEvent(&event->xbutton);
281  break;
282  case KeyPress:
283  HandleKeyPress(&event->xkey);
284  break;
285  case KeyRelease:
286  HandleKeyRelease(&event->xkey);
287  break;
288  case EnterNotify:
289  HandleEnterNotify(&event->xcrossing);
290  break;
291  case MotionNotify:
292  while(JXCheckTypedEvent(display, MotionNotify, event));
293  UpdateTime(event);
294  HandleMotionNotify(&event->xmotion);
295  break;
296  case LeaveNotify:
297  case DestroyNotify:
298  case Expose:
299  case ConfigureNotify:
300  break;
301  default:
302  Debug("Unknown event type: %d", event->type);
303  break;
304  }
305 }
306 
309 {
310  XEvent event;
311  JXSync(display, False);
312  while(JXCheckMaskEvent(display, ButtonPressMask | ButtonReleaseMask,
313  &event)) {
314  UpdateTime(&event);
315  }
316 }
317 
319 void DiscardMotionEvents(XEvent *event, Window w)
320 {
321  XEvent temp;
322  JXSync(display, False);
323  while(JXCheckTypedEvent(display, MotionNotify, &temp)) {
324  UpdateTime(&temp);
325  SetMousePosition(temp.xmotion.x_root, temp.xmotion.y_root,
326  temp.xmotion.window);
327  if(temp.xmotion.window == w) {
328  *event = temp;
329  }
330  }
331 }
332 
334 void DiscardKeyEvents(XEvent *event, Window w)
335 {
336  JXSync(display, False);
337  while(JXCheckTypedWindowEvent(display, w, KeyPress, event)) {
338  UpdateTime(event);
339  }
340 }
341 
344 {
345  XEvent event;
346  JXSync(display, False);
347  while(JXCheckMaskEvent(display, EnterWindowMask, &event)) {
348  UpdateTime(&event);
349  SetMousePosition(event.xmotion.x_root, event.xmotion.y_root,
350  event.xmotion.window);
351  }
352 }
353 
355 char HandleSelectionClear(const XSelectionClearEvent *event)
356 {
357  if(event->selection == managerSelection) {
358  /* Lost WM selection. */
359  shouldExit = 1;
360  return 1;
361  }
362  return HandleDockSelectionClear(event);
363 }
364 
366 void HandleButtonEvent(const XButtonEvent *event)
367 {
368 
369  ClientNode *np;
370  int north, south, east, west;
371 
372  np = FindClientByParent(event->window);
373  if(np) {
374  if(event->type == ButtonPress) {
375  FocusClient(np);
376  RaiseClient(np);
377  }
378  DispatchBorderButtonEvent(event, np);
379  } else if(event->window == rootWindow && event->type == ButtonPress) {
380  if(!ShowRootMenu(event->button, event->x, event->y, 0)) {
381  if(event->button == Button4) {
382  LeftDesktop();
383  } else if(event->button == Button5) {
384  RightDesktop();
385  }
386  }
387  } else {
388  const unsigned int mask = event->state & ~lockMask;
389  np = FindClientByWindow(event->window);
390  if(np) {
391  const char move_resize = (np->state.status & STAT_DRAG)
392  || ((mask == settings.moveMask)
393  && !(np->state.status & STAT_NODRAG));
394  switch(event->button) {
395  case Button1:
396  case Button2:
397  FocusClient(np);
398  RaiseClient(np);
399  if(move_resize) {
400  GetBorderSize(&np->state, &north, &south, &east, &west);
401  MoveClient(np, event->x + west, event->y + north);
402  }
403  break;
404  case Button3:
405  if(move_resize) {
406  GetBorderSize(&np->state, &north, &south, &east, &west);
408  event->x + west, event->y + north);
409  } else {
410  FocusClient(np);
411  RaiseClient(np);
412  }
413  break;
414  default:
415  break;
416  }
417  JXAllowEvents(display, ReplayPointer, eventTime);
418  }
419 
420  }
421 
422 }
423 
426 {
427  if(np) {
428  if(np->state.maxFlags == flags) {
430  } else {
431  MaximizeClient(np, flags);
432  }
433  }
434 }
435 
437 void HandleKeyPress(const XKeyEvent *event)
438 {
439  ClientNode *np;
440  KeyType key;
441 
442  SetMousePosition(event->x_root, event->y_root, event->window);
443  key = GetKey(event);
444  np = GetActiveClient();
445  switch(key & 0xFF) {
446  case KEY_EXEC:
447  RunKeyCommand(event);
448  break;
449  case KEY_DESKTOP:
450  ChangeDesktop((key >> 8) - 1);
451  break;
452  case KEY_RDESKTOP:
453  RightDesktop();
454  break;
455  case KEY_LDESKTOP:
456  LeftDesktop();
457  break;
458  case KEY_UDESKTOP:
459  AboveDesktop();
460  break;
461  case KEY_DDESKTOP:
462  BelowDesktop();
463  break;
464  case KEY_SHOWDESK:
465  ShowDesktop();
466  break;
467  case KEY_SHOWTRAY:
468  ShowAllTrays();
469  break;
470  case KEY_NEXT:
471  StartWindowWalk();
472  FocusNext();
473  break;
474  case KEY_NEXTSTACK:
476  WalkWindowStack(1);
477  break;
478  case KEY_PREV:
479  StartWindowWalk();
480  FocusPrevious();
481  break;
482  case KEY_PREVSTACK:
484  WalkWindowStack(0);
485  break;
486  case KEY_CLOSE:
487  if(np) {
488  DeleteClient(np);
489  }
490  break;
491  case KEY_SHADE:
492  if(np) {
493  if(np->state.status & STAT_SHADED) {
494  UnshadeClient(np);
495  } else {
496  ShadeClient(np);
497  }
498  }
499  break;
500  case KEY_STICK:
501  if(np) {
502  if(np->state.status & STAT_STICKY) {
503  SetClientSticky(np, 0);
504  } else {
505  SetClientSticky(np, 1);
506  }
507  }
508  break;
509  case KEY_MOVE:
510  if(np) {
511  MoveClientKeyboard(np);
512  }
513  break;
514  case KEY_RESIZE:
515  if(np) {
517  }
518  break;
519  case KEY_MIN:
520  if(np) {
521  MinimizeClient(np, 1);
522  }
523  break;
524  case KEY_MAX:
526  break;
527  case KEY_RESTORE:
528  if(np) {
529  if(np->state.maxFlags) {
531  } else {
532  MinimizeClient(np, 1);
533  }
534  }
535  break;
536  case KEY_MAXTOP:
538  break;
539  case KEY_MAXBOTTOM:
541  break;
542  case KEY_MAXLEFT:
544  break;
545  case KEY_MAXRIGHT:
547  break;
548  case KEY_MAXV:
550  break;
551  case KEY_MAXH:
553  break;
554  case KEY_ROOT:
555  ShowKeyMenu(event);
556  break;
557  case KEY_WIN:
558  if(np) {
559  RaiseClient(np);
560  ShowWindowMenu(np, np->x, np->y, 1);
561  }
562  break;
563  case KEY_RESTART:
564  Restart();
565  break;
566  case KEY_EXIT:
567  Exit(1);
568  break;
569  case KEY_FULLSCREEN:
570  if(np) {
571  if(np->state.status & STAT_FULLSCREEN) {
572  SetClientFullScreen(np, 0);
573  } else {
574  SetClientFullScreen(np, 1);
575  }
576  }
577  break;
578  case KEY_SENDR:
579  if(np) {
580  const unsigned desktop = GetRightDesktop(np->state.desktop);
581  SetClientDesktop(np, desktop);
582  ChangeDesktop(desktop);
583  }
584  break;
585  case KEY_SENDL:
586  if(np) {
587  const unsigned desktop = GetLeftDesktop(np->state.desktop);
588  SetClientDesktop(np, desktop);
589  ChangeDesktop(desktop);
590  }
591  break;
592  case KEY_SENDU:
593  if(np) {
594  const unsigned desktop = GetAboveDesktop(np->state.desktop);
595  SetClientDesktop(np, desktop);
596  ChangeDesktop(desktop);
597  }
598  break;
599  case KEY_SENDD:
600  if(np) {
601  const unsigned desktop = GetBelowDesktop(np->state.desktop);
602  SetClientDesktop(np, desktop);
603  ChangeDesktop(desktop);
604  }
605  break;
606  default:
607  break;
608  }
610 }
611 
613 void HandleKeyRelease(const XKeyEvent *event)
614 {
615  KeyType key;
616  key = GetKey(event) & 0xFF;
617  if( key != KEY_NEXTSTACK && key != KEY_NEXT
618  && key != KEY_PREV && key != KEY_PREVSTACK) {
619  StopWindowWalk();
620  }
621 }
622 
624 void HandleConfigureRequest(const XConfigureRequestEvent *event)
625 {
626  XWindowChanges wc;
627  ClientNode *np;
628 
629  if(HandleDockConfigureRequest(event)) {
630  return;
631  }
632 
633  np = FindClientByWindow(event->window);
634  if(np) {
635 
636  int deltax, deltay;
637  char changed = 0;
638  char resized = 0;
639 
640  GetGravityDelta(np, np->gravity, &deltax, &deltay);
641  if((event->value_mask & CWWidth) && (event->width != np->width)) {
642  switch(np->gravity) {
643  case EastGravity:
644  case NorthEastGravity:
645  case SouthEastGravity:
646  /* Right side should not move. */
647  np->x -= event->width - np->width;
648  break;
649  case WestGravity:
650  case NorthWestGravity:
651  case SouthWestGravity:
652  /* Left side should not move. */
653  break;
654  case CenterGravity:
655  /* Center of the window should not move. */
656  np->x -= (event->width - np->width) / 2;
657  break;
658  default:
659  break;
660  }
661  np->width = event->width;
662  changed = 1;
663  resized = 1;
664  }
665  if((event->value_mask & CWHeight) && (event->height != np->height)) {
666  switch(np->gravity) {
667  case NorthGravity:
668  case NorthEastGravity:
669  case NorthWestGravity:
670  /* Top should not move. */
671  break;
672  case SouthGravity:
673  case SouthEastGravity:
674  case SouthWestGravity:
675  /* Bottom should not move. */
676  np->y -= event->height - np->height;
677  break;
678  case CenterGravity:
679  /* Center of the window should not move. */
680  np->y -= (event->height - np->height) / 2;
681  break;
682  default:
683  break;
684  }
685  np->height = event->height;
686  changed = 1;
687  resized = 1;
688  }
689  if((event->value_mask & CWX) && (event->x - deltax != np->x)) {
690  np->x = event->x - deltax;
691  changed = 1;
692  }
693  if((event->value_mask & CWY) && (event->y - deltay != np->y)) {
694  np->y = event->y - deltay;
695  changed = 1;
696  }
697 
698  /* Update stacking. */
699  if((event->value_mask & CWStackMode)) {
700  Window above = None;
701  if(event->value_mask & CWSibling) {
702  above = event->above;
703  }
704  RestackClient(np, above, event->detail);
705  }
706 
707  /* Return early if there's nothing to do. */
708  if(!changed) {
709  return;
710  }
711 
712  if(np->controller) {
713  (np->controller)(0);
714  }
715  if(np->state.maxFlags) {
717  }
718 
719  if(np->state.border & BORDER_CONSTRAIN) {
720  resized = 1;
721  }
722  if(resized) {
723  ConstrainSize(np);
724  ConstrainPosition(np);
725  ResetBorder(np);
726  } else {
727  int north, south, east, west;
728  GetBorderSize(&np->state, &north, &south, &east, &west);
729  if(np->parent != None) {
730  JXMoveWindow(display, np->parent, np->x - west, np->y - north);
731  } else {
732  JXMoveWindow(display, np->window, np->x, np->y);
733  }
734  }
735 
736  SendConfigureEvent(np);
738 
739  } else {
740 
741  /* We don't know about this window, just let the configure through. */
742 
743  wc.stack_mode = event->detail;
744  wc.sibling = event->above;
745  wc.border_width = event->border_width;
746  wc.x = event->x;
747  wc.y = event->y;
748  wc.width = event->width;
749  wc.height = event->height;
750  JXConfigureWindow(display, event->window, event->value_mask, &wc);
751 
752  }
753 }
754 
756 char HandleConfigureNotify(const XConfigureEvent *event)
757 {
758  if(event->window != rootWindow) {
759  return 0;
760  }
761  if(rootWidth != event->width || rootHeight != event->height) {
762  rootWidth = event->width;
763  rootHeight = event->height;
764  shouldRestart = 1;
765  shouldExit = 1;
766  }
767  return 1;
768 }
769 
771 void HandleEnterNotify(const XCrossingEvent *event)
772 {
773  ClientNode *np;
774  Cursor cur;
775  np = FindClient(event->window);
776  if(np) {
777  if( !(np->state.status & STAT_ACTIVE)
778  && (settings.focusModel == FOCUS_SLOPPY)) {
779  FocusClient(np);
780  }
781  if(np->parent == event->window) {
782  np->borderAction = GetBorderActionType(np, event->x, event->y);
783  cur = GetFrameCursor(np->borderAction);
784  JXDefineCursor(display, np->parent, cur);
785  } else if(np->borderAction != BA_NONE) {
787  np->borderAction = BA_NONE;
788  }
789  }
790 
791 }
792 
794 char HandleExpose(const XExposeEvent *event)
795 {
796  ClientNode *np;
797  np = FindClientByParent(event->window);
798  if(np) {
799  if(event->count == 0) {
800  DrawBorder(np);
801  }
802  return 1;
803  } else {
804  np = FindClientByWindow(event->window);
805  if(np) {
806  if(np->state.status & STAT_WMDIALOG) {
807 
808  /* Dialog expose events are handled elsewhere. */
809  return 0;
810 
811  } else {
812 
813  /* Ignore other expose events for client windows. */
814  return 1;
815 
816  }
817  }
818  return event->count ? 1 : 0;
819  }
820 }
821 
823 char HandlePropertyNotify(const XPropertyEvent *event)
824 {
825  ClientNode *np = FindClientByWindow(event->window);
826  if(np) {
827  char changed = 0;
828  switch(event->atom) {
829  case XA_WM_NAME:
830  ReadWMName(np);
831  changed = 1;
832  break;
833  case XA_WM_NORMAL_HINTS:
834  ReadWMNormalHints(np);
835  if(ConstrainSize(np)) {
836  ResetBorder(np);
837  }
838  changed = 1;
839  break;
840  case XA_WM_HINTS:
841  if(np->state.status & STAT_URGENT) {
843  }
844  ReadWMHints(np->window, &np->state, 1);
845  if(np->state.status & STAT_URGENT) {
847  }
848  WriteState(np);
849  break;
850  case XA_WM_TRANSIENT_FOR:
852  break;
853  case XA_WM_ICON_NAME:
854  case XA_WM_CLIENT_MACHINE:
855  break;
856  default:
857  if(event->atom == atoms[ATOM_WM_COLORMAP_WINDOWS]) {
858  ReadWMColormaps(np);
860  } else if(event->atom == atoms[ATOM_WM_PROTOCOLS]) {
861  ReadWMProtocols(np->window, &np->state);
862  } else if(event->atom == atoms[ATOM_NET_WM_ICON]) {
863  LoadIcon(np);
864  changed = 1;
865  } else if(event->atom == atoms[ATOM_NET_WM_NAME]) {
866  ReadWMName(np);
867  changed = 1;
868  } else if(event->atom == atoms[ATOM_NET_WM_STRUT_PARTIAL]) {
869  ReadClientStrut(np);
870  } else if(event->atom == atoms[ATOM_NET_WM_STRUT]) {
871  ReadClientStrut(np);
872  } else if(event->atom == atoms[ATOM_MOTIF_WM_HINTS]) {
873  UpdateState(np);
874  WriteState(np);
875  ResetBorder(np);
876  changed = 1;
877  } else if(event->atom == atoms[ATOM_NET_WM_WINDOW_OPACITY]) {
878  ReadWMOpacity(np->window, &np->state.opacity);
879  if(np->parent != None) {
880  SetOpacity(np, np->state.opacity, 1);
881  }
882  }
883  break;
884  }
885 
886  if(changed) {
887  DrawBorder(np);
890  }
891  if(np->state.status & STAT_WMDIALOG) {
892  return 0;
893  } else {
894  return 1;
895  }
896  }
897 
898  return 1;
899 }
900 
902 void HandleClientMessage(const XClientMessageEvent *event)
903 {
904 
905  ClientNode *np;
906 #ifdef DEBUG
907  char *atomName;
908 #endif
909 
910  np = FindClientByWindow(event->window);
911  if(np) {
912  if(event->message_type == atoms[ATOM_WM_CHANGE_STATE]) {
913 
914  if(np->controller) {
915  (np->controller)(0);
916  }
917 
918  switch(event->data.l[0]) {
919  case WithdrawnState:
920  SetClientWithdrawn(np);
921  break;
922  case IconicState:
923  MinimizeClient(np, 1);
924  break;
925  case NormalState:
926  RestoreClient(np, 1);
927  break;
928  default:
929  break;
930  }
931 
932  } else if(event->message_type == atoms[ATOM_NET_ACTIVE_WINDOW]) {
933 
934  RestoreClient(np, 1);
935  UnshadeClient(np);
936  FocusClient(np);
937 
938  } else if(event->message_type == atoms[ATOM_NET_WM_DESKTOP]) {
939 
940  if(event->data.l[0] == ~0L) {
941  SetClientSticky(np, 1);
942  } else {
943 
944  if(np->controller) {
945  (np->controller)(0);
946  }
947 
948  if( event->data.l[0] >= 0
949  && event->data.l[0] < (long)settings.desktopCount) {
950  np->state.status &= ~STAT_STICKY;
951  SetClientDesktop(np, event->data.l[0]);
952  }
953  }
954 
955  } else if(event->message_type == atoms[ATOM_NET_CLOSE_WINDOW]) {
956 
957  DeleteClient(np);
958 
959  } else if(event->message_type == atoms[ATOM_NET_MOVERESIZE_WINDOW]) {
960 
961  HandleNetMoveResize(event, np);
962 
963  } else if(event->message_type == atoms[ATOM_NET_WM_MOVERESIZE]) {
964 
965  HandleNetWMMoveResize(event, np);
966 
967  } else if(event->message_type == atoms[ATOM_NET_RESTACK_WINDOW]) {
968 
969  HandleNetRestack(event, np);
970 
971  } else if(event->message_type == atoms[ATOM_NET_WM_STATE]) {
972 
973  HandleNetWMState(event, np);
974 
975  } else {
976 
977 #ifdef DEBUG
978  atomName = JXGetAtomName(display, event->message_type);
979  Debug("Unknown ClientMessage to client: %s", atomName);
980  JXFree(atomName);
981 #endif
982 
983  }
984 
985  } else if(event->window == rootWindow) {
986 
987  if(event->message_type == atoms[ATOM_JWM_RESTART]) {
988  Restart();
989  } else if(event->message_type == atoms[ATOM_JWM_EXIT]) {
990  Exit(0);
991  } else if(event->message_type == atoms[ATOM_JWM_RELOAD]) {
992  ReloadMenu();
993  } else if(event->message_type == atoms[ATOM_NET_CURRENT_DESKTOP]) {
994  ChangeDesktop(event->data.l[0]);
995  } else if(event->message_type == atoms[ATOM_NET_SHOWING_DESKTOP]) {
996  ShowDesktop();
997  } else {
998 #ifdef DEBUG
999  atomName = JXGetAtomName(display, event->message_type);
1000  Debug("Unknown ClientMessage to root: %s", atomName);
1001  JXFree(atomName);
1002 #endif
1003  }
1004 
1005  } else if(event->message_type == atoms[ATOM_NET_REQUEST_FRAME_EXTENTS]) {
1006 
1008 
1009  } else if(event->message_type == atoms[ATOM_NET_SYSTEM_TRAY_OPCODE]) {
1010 
1011  HandleDockEvent(event);
1012 
1013  } else {
1014 #ifdef DEBUG
1015  atomName = JXGetAtomName(display, event->message_type);
1016  Debug("ClientMessage to unknown window (0x%x): %s",
1017  event->window, atomName);
1018  JXFree(atomName);
1019 #endif
1020  }
1021 
1022 }
1023 
1025 void HandleNetMoveResize(const XClientMessageEvent *event, ClientNode *np)
1026 {
1027 
1028  long flags;
1029  int gravity;
1030  int deltax, deltay;
1031 
1032  Assert(event);
1033  Assert(np);
1034 
1035  flags = event->data.l[0] >> 8;
1036  gravity = event->data.l[0] & 0xFF;
1037  if(gravity == 0) {
1038  gravity = np->gravity;
1039  }
1040  GetGravityDelta(np, gravity, &deltax, &deltay);
1041 
1042  if(flags & (1 << 2)) {
1043  const long width = event->data.l[3];
1044  switch(gravity) {
1045  case EastGravity:
1046  case NorthEastGravity:
1047  case SouthEastGravity:
1048  /* Right side should not move. */
1049  np->x -= width - np->width;
1050  break;
1051  case WestGravity:
1052  case NorthWestGravity:
1053  case SouthWestGravity:
1054  /* Left side should not move. */
1055  break;
1056  case CenterGravity:
1057  /* Center of the window should not move. */
1058  np->x -= (width - np->width) / 2;
1059  break;
1060  default:
1061  break;
1062  }
1063  np->width = width;
1064  }
1065  if(flags & (1 << 3)) {
1066  const long height = event->data.l[4];
1067  switch(gravity) {
1068  case NorthGravity:
1069  case NorthEastGravity:
1070  case NorthWestGravity:
1071  /* Top should not move. */
1072  break;
1073  case SouthGravity:
1074  case SouthEastGravity:
1075  case SouthWestGravity:
1076  /* Bottom should not move. */
1077  np->y -= height - np->height;
1078  break;
1079  case CenterGravity:
1080  /* Center of the window should not move. */
1081  np->y -= (height - np->height) / 2;
1082  break;
1083  default:
1084  break;
1085  }
1086  np->height = height;
1087  }
1088  if(flags & (1 << 0)) {
1089  np->x = event->data.l[1] - deltax;
1090  }
1091  if(flags & (1 << 1)) {
1092  np->y = event->data.l[2] - deltay;
1093  }
1094 
1095  /* Don't let maximized clients be moved or resized. */
1096  if(JUNLIKELY(np->state.status & STAT_FULLSCREEN)) {
1097  SetClientFullScreen(np, 0);
1098  }
1099  if(JUNLIKELY(np->state.maxFlags)) {
1100  MaximizeClient(np, MAX_NONE);
1101  }
1102 
1103  ConstrainSize(np);
1104  ResetBorder(np);
1105  SendConfigureEvent(np);
1107 
1108 }
1109 
1111 void HandleNetWMMoveResize(const XClientMessageEvent *event, ClientNode *np)
1112 {
1113 
1114  long x = event->data.l[0] - np->x;
1115  long y = event->data.l[1] - np->y;
1116  const long direction = event->data.l[2];
1117  int deltax, deltay;
1118 
1119  GetGravityDelta(np, np->gravity, &deltax, &deltay);
1120  x -= deltax;
1121  y -= deltay;
1122 
1123  switch(direction) {
1124  case 0: /* top-left */
1126  break;
1127  case 1: /* top */
1128  ResizeClient(np, BA_RESIZE | BA_RESIZE_N, x, y);
1129  break;
1130  case 2: /* top-right */
1132  break;
1133  case 3: /* right */
1134  ResizeClient(np, BA_RESIZE | BA_RESIZE_E, x, y);
1135  break;
1136  case 4: /* bottom-right */
1138  break;
1139  case 5: /* bottom */
1140  ResizeClient(np, BA_RESIZE | BA_RESIZE_S, x, y);
1141  break;
1142  case 6: /* bottom-left */
1144  break;
1145  case 7: /* left */
1146  ResizeClient(np, BA_RESIZE | BA_RESIZE_W, x, y);
1147  break;
1148  case 8: /* move */
1149  MoveClient(np, x, y);
1150  break;
1151  case 9: /* resize-keyboard */
1153  break;
1154  case 10: /* move-keyboard */
1155  MoveClientKeyboard(np);
1156  break;
1157  case 11: /* cancel */
1158  if(np->controller) {
1159  (np->controller)(0);
1160  }
1161  break;
1162  default:
1163  break;
1164  }
1165 
1166 }
1167 
1169 void HandleNetRestack(const XClientMessageEvent *event, ClientNode *np)
1170 {
1171  const Window sibling = event->data.l[1];
1172  const int detail = event->data.l[2];
1173  RestackClient(np, sibling, detail);
1174 }
1175 
1177 void HandleNetWMState(const XClientMessageEvent *event, ClientNode *np)
1178 {
1179 
1180  unsigned int x;
1181  MaxFlags maxFlags;
1182  char actionStick;
1183  char actionShade;
1184  char actionFullScreen;
1185  char actionMinimize;
1186  char actionNolist;
1187  char actionNopager;
1188  char actionBelow;
1189  char actionAbove;
1190 
1191  /* Up to two actions to be applied together. */
1192  maxFlags = MAX_NONE;
1193  actionStick = 0;
1194  actionShade = 0;
1195  actionFullScreen = 0;
1196  actionMinimize = 0;
1197  actionNolist = 0;
1198  actionNopager = 0;
1199  actionBelow = 0;
1200  actionAbove = 0;
1201 
1202  for(x = 1; x <= 2; x++) {
1203  if(event->data.l[x]
1204  == (long)atoms[ATOM_NET_WM_STATE_STICKY]) {
1205  actionStick = 1;
1206  } else if(event->data.l[x]
1208  maxFlags |= MAX_VERT;
1209  } else if(event->data.l[x]
1211  maxFlags |= MAX_HORIZ;
1212  } else if(event->data.l[x]
1213  == (long)atoms[ATOM_NET_WM_STATE_SHADED]) {
1214  actionShade = 1;
1215  } else if(event->data.l[x]
1216  == (long)atoms[ATOM_NET_WM_STATE_FULLSCREEN]) {
1217  actionFullScreen = 1;
1218  } else if(event->data.l[x]
1219  == (long)atoms[ATOM_NET_WM_STATE_HIDDEN]) {
1220  actionMinimize = 1;
1221  } else if(event->data.l[x]
1223  actionNolist = 1;
1224  } else if(event->data.l[x]
1225  == (long)atoms[ATOM_NET_WM_STATE_SKIP_PAGER]) {
1226  actionNopager = 1;
1227  } else if(event->data.l[x]
1228  == (long)atoms[ATOM_NET_WM_STATE_BELOW]) {
1229  actionBelow = 1;
1230  } else if(event->data.l[x]
1231  == (long)atoms[ATOM_NET_WM_STATE_ABOVE]) {
1232  actionAbove = 1;
1233  }
1234  }
1235 
1236  switch(event->data.l[0]) {
1237  case 0: /* Remove */
1238  if(actionStick) {
1239  SetClientSticky(np, 0);
1240  }
1241  if(maxFlags != MAX_NONE && np->state.maxFlags) {
1242  MaximizeClient(np, np->state.maxFlags & ~maxFlags);
1243  }
1244  if(actionShade) {
1245  UnshadeClient(np);
1246  }
1247  if(actionFullScreen) {
1248  SetClientFullScreen(np, 0);
1249  }
1250  if(actionMinimize) {
1251  RestoreClient(np, 0);
1252  }
1253  if(actionNolist && !(np->state.status & STAT_ILIST)) {
1254  np->state.status &= ~STAT_NOLIST;
1256  }
1257  if(actionNopager && !(np->state.status & STAT_IPAGER)) {
1258  np->state.status &= ~STAT_NOPAGER;
1260  }
1261  if(actionBelow && np->state.layer == LAYER_BELOW) {
1263  }
1264  if(actionAbove && np->state.layer == LAYER_ABOVE) {
1266  }
1267  break;
1268  case 1: /* Add */
1269  if(actionStick) {
1270  SetClientSticky(np, 1);
1271  }
1272  if(maxFlags != MAX_NONE) {
1273  MaximizeClient(np, np->state.maxFlags | maxFlags);
1274  }
1275  if(actionShade) {
1276  ShadeClient(np);
1277  }
1278  if(actionFullScreen) {
1279  SetClientFullScreen(np, 1);
1280  }
1281  if(actionMinimize) {
1282  MinimizeClient(np, 1);
1283  }
1284  if(actionNolist && !(np->state.status & STAT_ILIST)) {
1285  np->state.status |= STAT_NOLIST;
1287  }
1288  if(actionNopager && !(np->state.status & STAT_IPAGER)) {
1289  np->state.status |= STAT_NOPAGER;
1291  }
1292  if(actionBelow) {
1294  }
1295  if(actionAbove) {
1297  }
1298  break;
1299  case 2: /* Toggle */
1300  if(actionStick) {
1301  if(np->state.status & STAT_STICKY) {
1302  SetClientSticky(np, 0);
1303  } else {
1304  SetClientSticky(np, 1);
1305  }
1306  }
1307  if(maxFlags) {
1308  MaximizeClient(np, np->state.maxFlags ^ maxFlags);
1309  }
1310  if(actionShade) {
1311  if(np->state.status & STAT_SHADED) {
1312  UnshadeClient(np);
1313  } else {
1314  ShadeClient(np);
1315  }
1316  }
1317  if(actionFullScreen) {
1318  if(np->state.status & STAT_FULLSCREEN) {
1319  SetClientFullScreen(np, 0);
1320  } else {
1321  SetClientFullScreen(np, 1);
1322  }
1323  }
1324  if(actionBelow) {
1325  if(np->state.layer == LAYER_BELOW) {
1327  } else {
1329  }
1330  }
1331  if(actionAbove) {
1332  if(np->state.layer == LAYER_ABOVE) {
1334  } else {
1336  }
1337  }
1338  /* Note that we don't handle toggling of hidden per EWMH
1339  * recommendations. */
1340  if(actionNolist && !(np->state.status & STAT_ILIST)) {
1341  np->state.status ^= STAT_NOLIST;
1343  }
1344  if(actionNopager && !(np->state.status & STAT_IPAGER)) {
1345  np->state.status ^= STAT_NOPAGER;
1347  }
1348  break;
1349  default:
1350  Debug("bad _NET_WM_STATE action: %ld", event->data.l[0]);
1351  break;
1352  }
1353 
1354  /* Update _NET_WM_STATE if needed.
1355  * The state update is handled elsewhere for the other actions.
1356  */
1357  if(actionNolist | actionNopager | actionAbove | actionBelow) {
1358  WriteState(np);
1359  }
1360 
1361 }
1362 
1364 void HandleFrameExtentsRequest(const XClientMessageEvent *event)
1365 {
1366  ClientState state;
1367  state = ReadWindowState(event->window, 0);
1368  WriteFrameExtents(event->window, &state);
1369 }
1370 
1372 void HandleMotionNotify(const XMotionEvent *event)
1373 {
1374 
1375  ClientNode *np;
1376  Cursor cur;
1377 
1378  if(event->is_hint) {
1379  return;
1380  }
1381 
1382  np = FindClientByParent(event->window);
1383  if(np) {
1384  BorderActionType action;
1385  action = GetBorderActionType(np, event->x, event->y);
1386  if(np->borderAction != action) {
1387  np->borderAction = action;
1388  cur = GetFrameCursor(action);
1389  JXDefineCursor(display, np->parent, cur);
1390  }
1391  }
1392 
1393 }
1394 
1396 #ifdef USE_SHAPE
1397 void HandleShapeEvent(const XShapeEvent *event)
1398 {
1399  ClientNode *np;
1400  np = FindClientByWindow(event->window);
1401  if(np) {
1402  np->state.status |= STAT_SHAPED;
1403  ResetBorder(np);
1404  }
1405 }
1406 #endif /* USE_SHAPE */
1407 
1409 void HandleColormapChange(const XColormapEvent *event)
1410 {
1411  ClientNode *np;
1412  if(event->new == True) {
1413  np = FindClientByWindow(event->window);
1414  if(np) {
1415  np->cmap = event->colormap;
1417  }
1418  }
1419 }
1420 
1422 void HandleMapRequest(const XMapEvent *event)
1423 {
1424  ClientNode *np;
1425  Assert(event);
1426  if(CheckSwallowMap(event->window)) {
1427  return;
1428  }
1429  np = FindClientByWindow(event->window);
1430  if(!np) {
1431  GrabServer();
1432  np = AddClientWindow(event->window, 0, 1);
1433  if(np) {
1434  if(!(np->state.status & STAT_NOFOCUS)) {
1435  FocusClient(np);
1436  }
1437  } else {
1438  JXMapWindow(display, event->window);
1439  }
1440  UngrabServer();
1441  } else {
1442  if(!(np->state.status & STAT_MAPPED)) {
1443  UpdateState(np);
1444  np->state.status |= STAT_MAPPED;
1445  XMapWindow(display, np->window);
1446  if(np->parent != None) {
1447  XMapWindow(display, np->parent);
1448  }
1449  if(!(np->state.status & STAT_STICKY)) {
1451  }
1452  if(!(np->state.status & STAT_NOFOCUS)) {
1453  FocusClient(np);
1454  RaiseClient(np);
1455  }
1456  WriteState(np);
1459  }
1460  }
1461  RequireRestack();
1462 }
1463 
1465 void HandleUnmapNotify(const XUnmapEvent *event)
1466 {
1467  ClientNode *np;
1468  XEvent e;
1469 
1470  Assert(event);
1471 
1472  if(event->window != event->event) {
1473  /* Allow ICCCM synthetic UnmapNotify events through. */
1474  if (event->event != rootWindow || !event->send_event) {
1475  return;
1476  }
1477  }
1478 
1479  np = FindClientByWindow(event->window);
1480  if(np) {
1481 
1482  /* Grab the server to prevent the client from destroying the
1483  * window after we check for a DestroyNotify. */
1484  GrabServer();
1485 
1486  if(np->controller) {
1487  (np->controller)(1);
1488  }
1489 
1490  if(JXCheckTypedWindowEvent(display, np->window, DestroyNotify, &e)) {
1491  UpdateTime(&e);
1492  RemoveClient(np);
1493  } else if((np->state.status & STAT_MAPPED) || event->send_event) {
1494  if(!(np->state.status & STAT_HIDDEN)) {
1495  np->state.status &= ~STAT_MAPPED;
1496  JXUngrabButton(display, AnyButton, AnyModifier, np->window);
1497  GravitateClient(np, 1);
1498  JXReparentWindow(display, np->window, rootWindow, np->x, np->y);
1499  WriteState(np);
1501  RemoveClient(np);
1502  }
1503  }
1504  UngrabServer();
1505 
1506  }
1507 }
1508 
1510 char HandleDestroyNotify(const XDestroyWindowEvent *event)
1511 {
1512  ClientNode *np;
1513  np = FindClientByWindow(event->window);
1514  if(np) {
1515  if(np->controller) {
1516  (np->controller)(1);
1517  }
1518  RemoveClient(np);
1519  return 1;
1520  } else {
1521  return HandleDockDestroy(event->window);
1522  }
1523 }
1524 
1526 void DispatchBorderButtonEvent(const XButtonEvent *event,
1527  ClientNode *np)
1528 {
1529  static Time lastClickTime = 0;
1530  static int lastX = 0, lastY = 0;
1531  static char doubleClickActive = 0;
1532  BorderActionType action;
1533  int bsize;
1534 
1535  /* Middle click starts a move unless it's over the maximize button. */
1536  action = GetBorderActionType(np, event->x, event->y);
1537  if(event->button == Button2 && action != BA_MAXIMIZE) {
1538  MoveClient(np, event->x, event->y);
1539  return;
1540  }
1541 
1542  /* Determine the size of the border. */
1543  if(np->state.border & BORDER_OUTLINE) {
1544  bsize = settings.borderWidth;
1545  } else {
1546  bsize = 0;
1547  }
1548 
1549  /* Other buttons are context sensitive. */
1550  switch(action & 0x0F) {
1551  case BA_RESIZE: /* Border */
1552  if(event->type == ButtonPress) {
1553  if(event->button == Button1) {
1554  ResizeClient(np, action, event->x, event->y);
1555  } else if(event->button == Button3) {
1556  const unsigned titleHeight = GetTitleHeight();
1557  const int x = np->x + event->x - bsize;
1558  const int y = np->y + event->y - titleHeight - bsize;
1559  ShowWindowMenu(np, x, y, 0);
1560  }
1561  }
1562  break;
1563  case BA_MOVE: /* Title bar */
1564  if(event->button == Button1) {
1565  if(event->type == ButtonPress) {
1566  if(doubleClickActive
1567  && event->time != lastClickTime
1568  && event->time - lastClickTime <= settings.doubleClickSpeed
1569  && abs(event->x - lastX) <= settings.doubleClickDelta
1570  && abs(event->y - lastY) <= settings.doubleClickDelta) {
1572  doubleClickActive = 0;
1573  } else {
1574  if(MoveClient(np, event->x, event->y)) {
1575  doubleClickActive = 0;
1576  } else {
1577  doubleClickActive = 1;
1578  lastClickTime = event->time;
1579  lastX = event->x;
1580  lastY = event->y;
1581  }
1582  }
1583  }
1584  } else if(event->button == Button3) {
1585  const unsigned titleHeight = GetTitleHeight();
1586  const int x = np->x + event->x - bsize;
1587  const int y = np->y + event->y - titleHeight - bsize;
1588  ShowWindowMenu(np, x, y, 0);
1589  } else if(event->button == Button4) {
1590  ShadeClient(np);
1591  } else if(event->button == Button5) {
1592  UnshadeClient(np);
1593  }
1594  break;
1595  case BA_MENU: /* Menu button */
1596  if(event->button == Button4) {
1597  ShadeClient(np);
1598  } else if(event->button == Button5) {
1599  UnshadeClient(np);
1600  } else if(event->type == ButtonPress) {
1601  const unsigned titleHeight = GetTitleHeight();
1602  const int x = np->x + event->x - bsize;
1603  const int y = np->y + event->y - titleHeight - bsize;
1604  ShowWindowMenu(np, x, y, 0);
1605  }
1606  break;
1607  case BA_CLOSE: /* Close button */
1608  if(event->type == ButtonRelease
1609  && (event->button == Button1 || event->button == Button3)) {
1610  DeleteClient(np);
1611  }
1612  break;
1613  case BA_MAXIMIZE: /* Maximize button */
1614  if(event->type == ButtonRelease) {
1615  switch(event->button) {
1616  case Button1:
1618  break;
1619  case Button2:
1621  break;
1622  case Button3:
1624  break;
1625  default:
1626  break;
1627  }
1628  }
1629  break;
1630  case BA_MINIMIZE: /* Minimize button */
1631  if(event->type == ButtonRelease) {
1632  if(event->button == Button3) {
1633  if(np->state.status & STAT_SHADED) {
1634  UnshadeClient(np);
1635  } else {
1636  ShadeClient(np);
1637  }
1638  } else if(event->button == Button1) {
1639  MinimizeClient(np, 1);
1640  }
1641  }
1642  break;
1643  default:
1644  break;
1645  }
1647 }
1648 
1651 {
1652  const char alreadyMapped = (np->state.status & STAT_MAPPED) ? 1 : 0;
1653  const char active = (np->state.status & STAT_ACTIVE) ? 1 : 0;
1654 
1655  /* Remove from the layer list. */
1656  if(np->prev != NULL) {
1657  np->prev->next = np->next;
1658  } else {
1659  Assert(nodes[np->state.layer] == np);
1660  nodes[np->state.layer] = np->next;
1661  }
1662  if(np->next != NULL) {
1663  np->next->prev = np->prev;
1664  } else {
1665  Assert(nodeTail[np->state.layer] == np);
1666  nodeTail[np->state.layer] = np->prev;
1667  }
1668 
1669  /* Read the state (and new layer). */
1670  if(np->state.status & STAT_URGENT) {
1672  }
1673  np->state = ReadWindowState(np->window, alreadyMapped);
1674  if(np->state.status & STAT_URGENT) {
1676  }
1677 
1678  /* We don't handle mapping the window, so restore its mapped state. */
1679  if(!alreadyMapped) {
1680  np->state.status &= ~STAT_MAPPED;
1681  }
1682 
1683  /* Add to the layer list. */
1684  np->prev = NULL;
1685  np->next = nodes[np->state.layer];
1686  if(np->next == NULL) {
1687  nodeTail[np->state.layer] = np;
1688  } else {
1689  np->next->prev = np;
1690  }
1691  nodes[np->state.layer] = np;
1692 
1693  if(active) {
1694  FocusClient(np);
1695  }
1696 
1697 }
1698 
1700 void UpdateTime(const XEvent *event)
1701 {
1702  Time t = CurrentTime;
1703  Assert(event);
1704  switch(event->type) {
1705  case KeyPress:
1706  case KeyRelease:
1707  t = event->xkey.time;
1708  break;
1709  case ButtonPress:
1710  case ButtonRelease:
1711  t = event->xbutton.time;
1712  break;
1713  case MotionNotify:
1714  t = event->xmotion.time;
1715  break;
1716  case EnterNotify:
1717  case LeaveNotify:
1718  t = event->xcrossing.time;
1719  break;
1720  case PropertyNotify:
1721  t = event->xproperty.time;
1722  break;
1723  case SelectionClear:
1724  t = event->xselectionclear.time;
1725  break;
1726  case SelectionRequest:
1727  t = event->xselectionrequest.time;
1728  break;
1729  case SelectionNotify:
1730  t = event->xselection.time;
1731  break;
1732  default:
1733  break;
1734  }
1735  if(t != CurrentTime) {
1736  if(t > eventTime || t < eventTime - 60000) {
1737  eventTime = t;
1738  }
1739  }
1740 }
1741 
1743 void RegisterCallback(int freq, SignalCallback callback, void *data)
1744 {
1745  CallbackNode *cp;
1746  cp = Allocate(sizeof(CallbackNode));
1747  cp->last.seconds = 0;
1748  cp->last.ms = 0;
1749  cp->freq = freq;
1750  cp->callback = callback;
1751  cp->data = data;
1752  cp->next = callbacks;
1753  callbacks = cp;
1754 }
1755 
1757 void UnregisterCallback(SignalCallback callback, void *data)
1758 {
1759  CallbackNode **cp;
1760  for(cp = &callbacks; *cp; cp = &(*cp)->next) {
1761  if((*cp)->callback == callback && (*cp)->data == data) {
1762  CallbackNode *temp = *cp;
1763  *cp = (*cp)->next;
1764  Release(temp);
1765  return;
1766  }
1767  }
1768  Assert(0);
1769 }
1770 
1773 {
1774  restack_pending = 1;
1775 }
1776 
1779 {
1780  task_update_pending = 1;
1781 }
1782 
1785 {
1787 }

joewing.net / Projects / JWM