1 // -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*-
2 // Application.cc for Blackbox - an X11 Window manager
3 // Copyright (c) 2001 - 2005 Sean 'Shaleh' Perry <shaleh at debian.org>
4 // Copyright (c) 1997 - 2000, 2002 - 2005
5 //         Bradley T Hughes <bhughes at trolltech.com>
6 //
7 // Permission is hereby granted, free of charge, to any person obtaining a
8 // copy of this software and associated documentation files (the "Software"),
9 // to deal in the Software without restriction, including without limitation
10 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
11 // and/or sell copies of the Software, and to permit persons to whom the
12 // Software is furnished to do so, subject to the following conditions:
13 //
14 // The above copyright notice and this permission notice shall be included in
15 // all copies or substantial portions of the Software.
16 //
17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
20 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23 // DEALINGS IN THE SOFTWARE.
24 
25 #include "Application.hh"
26 #include "Display.hh"
27 #include "EventHandler.hh"
28 #include "Menu.hh"
29 
30 #include <X11/Xlib.h>
31 #include <X11/Xatom.h>
32 #include <X11/Xutil.h>
33 #include <X11/keysym.h>
34 #ifdef    SHAPE
35 #  include <X11/extensions/shape.h>
36 #endif // SHAPE
37 
38 #include <sys/types.h>
39 #if defined(__EMX__)
40 #  include <sys/select.h>
41 #endif
42 #include <sys/time.h>
43 #include <sys/wait.h>
44 #include <assert.h>
45 #include <fcntl.h>
46 #include <signal.h>
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <string.h>
50 #include <unistd.h>
51 
52 
53 #if defined(__GNUC__)
54 #  if __GNUC__ == 3 && __GNUC_MINOR__ == 3
55 // work around a gcc 3.3 compiler bug where base_app below would be
56 // initialized to ~0 instead of 0.
57 static void *workaround __attribute__((__unused__)) = 0;
58 #  endif
59 #endif
60 
61 
62 static bt::Application *base_app = 0;
63 static sig_atomic_t pending_signals = 0;
64 
65 
handleXErrors(Display * d,XErrorEvent * e)66 static int handleXErrors(Display *d, XErrorEvent *e) {
67 #ifdef    DEBUG
68   char errtxt[128];
69 
70   XGetErrorText(d, e->error_code, errtxt, 128);
71   fprintf(stderr, "%s:  X error: %s(%d) opcodes %d/%d\n  resource 0x%lx\n",
72           base_app->applicationName().c_str(), errtxt, e->error_code,
73           e->request_code, e->minor_code, e->resourceid);
74 #else
75   // shutup gcc
76   (void) d;
77   (void) e;
78 #endif // DEBUG
79 
80   return 0;
81 }
82 
83 
84 // generic signal handler - this sets a bit in pending_signals, which
85 // will be handled later by the event loop (ie. if signal 2 is caught,
86 // bit 2 is set)
signalhandler(int sig)87 static void signalhandler(int sig)
88 { pending_signals |= (1 << sig); }
89 
90 
Application(const std::string & app_name,const char * dpy_name,bool multi_head)91 bt::Application::Application(const std::string &app_name, const char *dpy_name,
92                              bool multi_head)
93   : _app_name(bt::basename(app_name)), run_state(STARTUP),
94     xserver_time(CurrentTime), menu_grab(false)
95 {
96   assert(base_app == 0);
97   ::base_app = this;
98 
99   _display = new Display(dpy_name, multi_head);
100 
101   struct sigaction action;
102   action.sa_handler = signalhandler;
103   action.sa_mask = sigset_t();
104   action.sa_flags = SA_NOCLDSTOP;
105 
106   // non-fatal signals
107   sigaction(SIGHUP,  &action, NULL);
108   sigaction(SIGINT,  &action, NULL);
109   sigaction(SIGQUIT, &action, NULL);
110   sigaction(SIGTERM, &action, NULL);
111   sigaction(SIGPIPE, &action, NULL);
112   sigaction(SIGCHLD, &action, NULL);
113   sigaction(SIGUSR1, &action, NULL);
114   sigaction(SIGUSR2, &action, NULL);
115 
116 #ifdef    SHAPE
117   shape.extensions = XShapeQueryExtension(_display->XDisplay(),
118                                           &shape.event_basep,
119                                           &shape.error_basep);
120 #else // !SHAPE
121   shape.extensions = False;
122 #endif // SHAPE
123 
124   XSetErrorHandler(handleXErrors);
125 
126   NumLockMask = ScrollLockMask = 0;
127 
128   const XModifierKeymap* const modmap =
129     XGetModifierMapping(_display->XDisplay());
130   if (modmap && modmap->max_keypermod > 0) {
131     const int mask_table[] = {
132       ShiftMask, LockMask, ControlMask, Mod1Mask,
133       Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask
134     };
135     const size_t size = (sizeof(mask_table) / sizeof(mask_table[0])) *
136       modmap->max_keypermod;
137     // get the values of the keyboard lock modifiers
138     // Note: Caps lock is not retrieved the same way as Scroll and Num lock
139     // since it doesn't need to be.
140     const KeyCode num_lock =
141       XKeysymToKeycode(_display->XDisplay(), XK_Num_Lock);
142     const KeyCode scroll_lock =
143       XKeysymToKeycode(_display->XDisplay(), XK_Scroll_Lock);
144 
145     for (size_t cnt = 0; cnt < size; ++cnt) {
146       if (!modmap->modifiermap[cnt])
147         continue;
148 
149       if (num_lock == modmap->modifiermap[cnt])
150         NumLockMask = mask_table[cnt / modmap->max_keypermod];
151       if (scroll_lock == modmap->modifiermap[cnt])
152         ScrollLockMask = mask_table[cnt / modmap->max_keypermod];
153     }
154   }
155 
156   MaskList[0] = 0;
157   MaskList[1] = LockMask;
158   MaskList[2] = NumLockMask;
159   MaskList[3] = LockMask | NumLockMask;
160   MaskList[4] = ScrollLockMask;
161   MaskList[5] = ScrollLockMask | LockMask;
162   MaskList[6] = ScrollLockMask | NumLockMask;
163   MaskList[7] = ScrollLockMask | LockMask | NumLockMask;
164   MaskListLength = sizeof(MaskList) / sizeof(MaskList[0]);
165 
166   if (modmap)
167     XFreeModifiermap(const_cast<XModifierKeymap*>(modmap));
168 
169   XrmInitialize();
170 
171   ::timeval tv;
172   gettimeofday(&tv, 0);
173   currentTime = tv;
174 }
175 
176 
~Application(void)177 bt::Application::~Application(void) {
178   delete _display;
179   ::base_app = 0;
180 }
181 
182 
XDisplay(void) const183 ::Display *bt::Application::XDisplay(void) const
184 { return _display->XDisplay(); }
185 
186 
startup(void)187 void bt::Application::startup(void)
188 { }
189 
190 
shutdown(void)191 void bt::Application::shutdown(void)
192 { }
193 
194 
run(void)195 void bt::Application::run(void) {
196   startup();
197 
198   setRunState(RUNNING);
199 
200   const int xfd = XConnectionNumber(_display->XDisplay());
201 
202   while (run_state == RUNNING) {
203     if (pending_signals) {
204       // handle any pending signals
205       const unsigned int sigmax = sizeof(pending_signals) * 8;
206       for (unsigned int sig = 0; sig < sigmax; ++sig) {
207         if (!(pending_signals & (1u << sig)))
208           continue;
209 
210         pending_signals &= ~(1u << sig);
211 
212         setRunState(SIGNALLED);
213         if (process_signal(sig)) {
214           // reset run_state if it has not been set to something else
215           if (run_state == SIGNALLED)
216             setRunState(RUNNING);
217         }
218 
219         if (run_state == SIGNALLED) {
220           // dump core for unhandled signals
221           fprintf(stderr, "%s: caught unknown signal '%u', dumping core.\n",
222                   _app_name.c_str(), sig);
223           abort();
224         }
225       }
226     }
227 
228     do {
229       XEvent e;
230       while (run_state == RUNNING
231              && XEventsQueued(_display->XDisplay(), QueuedAlready)) {
232         XNextEvent(_display->XDisplay(), &e);
233         process_event(&e);
234       }
235     } while (run_state == RUNNING
236              && XEventsQueued(_display->XDisplay(), QueuedAfterFlush));
237 
238     if (run_state != RUNNING)
239       break;
240 
241     fd_set rfds;
242     ::timeval now, tm, *timeout = 0;
243 
244     FD_ZERO(&rfds);
245     FD_SET(xfd, &rfds);
246 
247     if (!timerList.empty()) {
248       const bt::Timer* const timer = timerList.top();
249 
250       gettimeofday(&now, 0);
251       tm = timer->timeRemaining(now);
252 
253       timeout = &tm;
254     }
255 
256     int ret = select(xfd + 1, &rfds, 0, 0, timeout);
257     if (ret < 0)
258       continue; // perhaps a signal interrupted select(2)
259 
260     // check for timer timeout
261     gettimeofday(&now, 0);
262 
263     {
264       // if the clock has rolled back, adjust all timers
265       timeval tv = now;
266       if (tv < currentTime)
267         adjustTimers(tv - currentTime);
268       currentTime = tv;
269     }
270 
271     /*
272       there is a small chance for deadlock here:
273       *IF* the timer list keeps getting refreshed *AND* the time between
274       timer->start() and timer->shouldFire() is within the timer's period
275       then the timer will keep firing.  This should be VERY near impossible.
276       But to be safe, let's use a counter here.
277     */
278     unsigned int i = 0u;
279     while (!timerList.empty() && i++ < 100u) {
280       bt::Timer *timer = timerList.top();
281       if (!timer->shouldFire(now))
282         break;
283 
284       timerList.pop();
285 
286       timer->fireTimeout();
287       timer->halt();
288       if (timer->isRecurring())
289         timer->start();
290     }
291   }
292 
293   shutdown();
294 }
295 
process_event(XEvent * event)296 void bt::Application::process_event(XEvent *event) {
297   bt::EventHandler *handler = findEventHandler(event->xany.window);
298   if (!handler)
299     return;
300 
301   // if there is an active menu, pre-process the events
302   if (menu_grab) {
303     switch (event->type) {
304     case ButtonPress:
305     case ButtonRelease:
306     case MotionNotify: {
307       if (!dynamic_cast<Menu*>(handler)) {
308         // current handler is not a menu.  send the event to the most
309         // recent menu instead.
310         handler = dynamic_cast<EventHandler*>(menus.front());
311       }
312       break;
313     }
314     case EnterNotify:
315     case LeaveNotify: {
316       // we have active menus.  we should only send enter/leave events
317       // to the menus themselves, not to normal windows
318       if (!dynamic_cast<Menu*>(handler))
319         return;
320       break;
321     }
322     case KeyPress:
323     case KeyRelease: {
324       // we have active menus.  we should send all key events to the most
325       // recent popup menu, regardless of where the pointer is
326       handler = dynamic_cast<EventHandler*>(menus.front());
327       break;
328     }
329     default:
330       break;
331     }
332   }
333 
334   // deliver the event
335   switch (event->type) {
336   case ButtonPress: {
337     xserver_time = event->xbutton.time;
338     // strip the lock key modifiers
339     event->xbutton.state &= ~(NumLockMask | ScrollLockMask | LockMask);
340     handler->buttonPressEvent(&event->xbutton);
341     break;
342   }
343 
344   case ButtonRelease: {
345     xserver_time = event->xbutton.time;
346     // strip the lock key modifiers
347     event->xbutton.state &= ~(NumLockMask | ScrollLockMask | LockMask);
348     handler->buttonReleaseEvent(&event->xbutton);
349     break;
350   }
351 
352   case MotionNotify: {
353     xserver_time = event->xmotion.time;
354     // compress motion notify events
355     XEvent realevent;
356     unsigned int i = 0;
357     while (XCheckTypedWindowEvent(_display->XDisplay(), event->xmotion.window,
358                                   MotionNotify, &realevent)) {
359       ++i;
360     }
361 
362     // if we have compressed some motion events, use the last one
363     if (i > 0)
364       event = &realevent;
365 
366     // strip the lock key modifiers
367     event->xbutton.state &= ~(NumLockMask | ScrollLockMask | LockMask);
368     handler->motionNotifyEvent(&event->xmotion);
369     break;
370   }
371 
372   case EnterNotify: {
373     xserver_time = event->xcrossing.time;
374     handler->enterNotifyEvent(&event->xcrossing);
375     break;
376   }
377 
378   case LeaveNotify: {
379     xserver_time = event->xcrossing.time;
380     handler->leaveNotifyEvent(&event->xcrossing);
381     break;
382   }
383 
384   case KeyPress: {
385     xserver_time = event->xkey.time;
386     // strip the lock key modifiers, except numlock, which can be useful
387     event->xkey.state &= ~(ScrollLockMask | LockMask);
388     handler->keyPressEvent(&event->xkey);
389     break;
390   }
391 
392   case KeyRelease: {
393     xserver_time = event->xkey.time;
394     // strip the lock key modifiers, except numlock, which can be useful
395     event->xkey.state &= ~(ScrollLockMask | LockMask);
396     handler->keyReleaseEvent(&event->xkey);
397     break;
398   }
399 
400   case MapNotify: {
401     handler->mapNotifyEvent(&event->xmap);
402     break;
403   }
404 
405   case UnmapNotify: {
406     handler->unmapNotifyEvent(&event->xunmap);
407     break;
408   }
409 
410   case ReparentNotify: {
411     handler->reparentNotifyEvent(&event->xreparent);
412     break;
413   }
414 
415   case DestroyNotify: {
416     handler->destroyNotifyEvent(&event->xdestroywindow);
417     break;
418   }
419 
420   case PropertyNotify: {
421     xserver_time = event->xproperty.time;
422     handler->propertyNotifyEvent(&event->xproperty);
423     break;
424   }
425 
426   case Expose: {
427     // compress expose events
428     XEvent realevent;
429     unsigned int i = 0;
430     int ex1, ey1, ex2, ey2;
431     ex1 = event->xexpose.x;
432     ey1 = event->xexpose.y;
433     ex2 = ex1 + event->xexpose.width - 1;
434     ey2 = ey1 + event->xexpose.height - 1;
435     while (XCheckTypedWindowEvent(_display->XDisplay(), event->xexpose.window,
436                                   Expose, &realevent)) {
437       ++i;
438 
439       // merge expose area
440       ex1 = std::min(realevent.xexpose.x, ex1);
441       ey1 = std::min(realevent.xexpose.y, ey1);
442       ex2 = std::max(realevent.xexpose.x + realevent.xexpose.width - 1, ex2);
443       ey2 = std::max(realevent.xexpose.y + realevent.xexpose.height - 1, ey2);
444     }
445     if (i > 0)
446       event = &realevent;
447 
448     // use the merged area
449     event->xexpose.x = ex1;
450     event->xexpose.y = ey1;
451     event->xexpose.width = ex2 - ex1 + 1;
452     event->xexpose.height = ey2 - ey1 + 1;
453 
454     handler->exposeEvent(&event->xexpose);
455     break;
456   }
457 
458   case ConfigureNotify: {
459     // compress configure notify events
460     XEvent realevent;
461     unsigned int i = 0;
462     while (XCheckTypedWindowEvent(_display->XDisplay(),
463                                   event->xconfigure.window,
464                                   ConfigureNotify, &realevent)) {
465       ++i;
466     }
467 
468     // if we have compressed some configure notify events, use the last one
469     if (i > 0)
470       event = &realevent;
471 
472     handler->configureNotifyEvent(&event->xconfigure);
473     break;
474   }
475 
476   case ClientMessage: {
477     handler->clientMessageEvent(&event->xclient);
478     break;
479   }
480 
481   case NoExpose: {
482     // not handled, ignore
483     break;
484   }
485 
486   default: {
487 #ifdef SHAPE
488     if (shape.extensions && event->type == shape.event_basep) {
489       handler->shapeEvent(event);
490     } else
491 #endif // SHAPE
492 #ifdef    DEBUG
493       {
494         fprintf(stderr, "unhandled event %d\n", event->type);
495       }
496 #endif // DEBUG
497     break;
498   }
499   } // switch
500 }
501 
502 
addTimer(bt::Timer * timer)503 void bt::Application::addTimer(bt::Timer *timer) {
504   if (!timer)
505     return;
506   timerList.push(timer);
507 }
508 
509 
removeTimer(bt::Timer * timer)510 void bt::Application::removeTimer(bt::Timer *timer) {
511   timerList.release(timer);
512 }
513 
514 
515 /*
516  * Grabs a button, but also grabs the button in every possible combination
517  * with the keyboard lock keys, so that they do not cancel out the event.
518 
519  * if allow_scroll_lock is true then only the top half of the lock mask
520  * table is used and scroll lock is ignored.  This value defaults to false.
521  */
grabButton(unsigned int button,unsigned int modifiers,Window grab_window,bool owner_events,unsigned int event_mask,int pointer_mode,int keyboard_mode,Window confine_to,Cursor cursor,bool allow_scroll_lock) const522 void bt::Application::grabButton(unsigned int button, unsigned int modifiers,
523                              Window grab_window, bool owner_events,
524                              unsigned int event_mask, int pointer_mode,
525                              int keyboard_mode, Window confine_to,
526                              Cursor cursor, bool allow_scroll_lock) const {
527   const size_t length =
528     (allow_scroll_lock) ? MaskListLength / 2 : MaskListLength;
529   for (size_t cnt = 0; cnt < length; ++cnt) {
530     XGrabButton(_display->XDisplay(), button, modifiers | MaskList[cnt],
531                 grab_window, owner_events, event_mask, pointer_mode,
532                 keyboard_mode, confine_to, cursor);
533   }
534 }
535 
536 
537 /*
538  * Releases the grab on a button, and ungrabs all possible combinations of the
539  * keyboard lock keys.
540  */
ungrabButton(unsigned int button,unsigned int modifiers,Window grab_window) const541 void bt::Application::ungrabButton(unsigned int button, unsigned int modifiers,
542                                Window grab_window) const {
543   for (size_t cnt = 0; cnt < MaskListLength; ++cnt) {
544     XUngrabButton(_display->XDisplay(), button, modifiers | MaskList[cnt],
545                   grab_window);
546   }
547 }
548 
549 
process_signal(int signal)550 bool bt::Application::process_signal(int signal) {
551   switch (signal) {
552   case SIGHUP:
553   case SIGINT:
554   case SIGQUIT:
555   case SIGTERM:
556   case SIGPIPE:
557   case SIGUSR1:
558   case SIGUSR2:
559     setRunState(SHUTDOWN);
560     break;
561 
562   case SIGCHLD:
563     int unused;
564     while (waitpid(-1, &unused, WNOHANG | WUNTRACED) > 0)
565       ;
566     break;
567 
568   default:
569     // generate a core dump for unknown signals
570     return false;
571   }
572 
573   return true;
574 }
575 
576 
insertEventHandler(Window window,bt::EventHandler * handler)577 void bt::Application::insertEventHandler(Window window,
578                                          bt::EventHandler *handler) {
579   eventhandlers.insert(std::pair<Window,bt::EventHandler*>(window, handler));
580 }
581 
582 
removeEventHandler(Window window)583 void bt::Application::removeEventHandler(Window window) {
584   eventhandlers.erase(window);
585 }
586 
587 
findEventHandler(Window window)588 bt::EventHandler *bt::Application::findEventHandler(Window window)
589 {
590   EventHandlerMap::iterator it = eventhandlers.find(window);
591   if (it == eventhandlers.end())
592     return 0;
593   return it->second;
594 }
595 
596 
openMenu(Menu * menu)597 void bt::Application::openMenu(Menu *menu) {
598   menus.push_front(menu);
599 
600   if (!menu_grab) {
601     // grab mouse and keyboard for the menu
602     XGrabKeyboard(_display->XDisplay(), menu->windowID(), True,
603                   GrabModeAsync, GrabModeAsync, xserver_time);
604     XGrabPointer(_display->XDisplay(), menu->windowID(), True,
605                  (ButtonPressMask | ButtonReleaseMask | ButtonMotionMask |
606                   PointerMotionMask | LeaveWindowMask),
607                  GrabModeAsync, GrabModeAsync, None, None, xserver_time);
608   }
609   menu_grab = true;
610 }
611 
612 
closeMenu(Menu * menu)613 void bt::Application::closeMenu(Menu *menu) {
614   if (menus.empty() || menu != menus.front()) {
615     fprintf(stderr, "BaseDisplay::closeMenu: menu %p not valid.\n",
616             static_cast<void *>(menu));
617     abort();
618   }
619 
620   menus.pop_front();
621   if (!menus.empty())
622     return;
623 
624   XUngrabKeyboard(_display->XDisplay(), xserver_time);
625   XUngrabPointer(_display->XDisplay(), xserver_time);
626 
627   XSync(_display->XDisplay(), False);
628   menu_grab = false;
629 }
630 
631 
adjustTimers(const timeval & offset)632 void bt::Application::adjustTimers(const timeval &offset)
633 {
634   // since we don't allow TimerQueues to be copied, we have to do it
635   // this way
636   TimerQueue tmp;
637   while (!timerList.empty()) {
638     Timer *t = timerList.top();
639     timerList.pop();
640     t->adjustStartTime(offset);
641     tmp.push(t);
642   }
643   assert(timerList.empty());
644   while (!tmp.empty()) {
645     Timer *t = tmp.top();
646     tmp.pop();
647     timerList.push(t);
648   }
649 }
650