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