version bump
[blackbox.git] / lib / Application.cc
blobf905f06ac489a56b4db30c29eca8420f14a1bebe
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:
14 // The above copyright notice and this permission notice shall be included in
15 // all copies or substantial portions of the Software.
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.
25 #include "Application.hh"
26 #include "Display.hh"
27 #include "EventHandler.hh"
28 #include "Menu.hh"
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
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>
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
62 static bt::Application *base_app = 0;
63 static sig_atomic_t pending_signals = 0;
66 static int handleXErrors(Display *d, XErrorEvent *e) {
67 #ifdef DEBUG
68 char errtxt[128];
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
80 return 0;
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)
87 static void signalhandler(int sig)
88 { pending_signals |= (1 << sig); }
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)
96 assert(base_app == 0);
97 ::base_app = this;
99 _display = new Display(dpy_name, multi_head);
101 struct sigaction action;
102 action.sa_handler = signalhandler;
103 action.sa_mask = sigset_t();
104 action.sa_flags = SA_NOCLDSTOP;
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);
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
124 XSetErrorHandler(handleXErrors);
126 NumLockMask = ScrollLockMask = 0;
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
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);
145 for (size_t cnt = 0; cnt < size; ++cnt) {
146 if (!modmap->modifiermap[cnt])
147 continue;
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];
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]);
166 if (modmap)
167 XFreeModifiermap(const_cast<XModifierKeymap*>(modmap));
169 XrmInitialize();
171 ::timeval tv;
172 gettimeofday(&tv, 0);
173 currentTime = tv;
177 bt::Application::~Application(void) {
178 delete _display;
179 ::base_app = 0;
183 ::Display *bt::Application::XDisplay(void) const
184 { return _display->XDisplay(); }
187 void bt::Application::startup(void)
191 void bt::Application::shutdown(void)
195 void bt::Application::run(void) {
196 startup();
198 setRunState(RUNNING);
200 const int xfd = XConnectionNumber(_display->XDisplay());
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;
210 pending_signals &= ~(1u << sig);
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);
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();
228 do {
229 XEvent e;
230 while (run_state == RUNNING
231 && XEventsQueued(_display->XDisplay(), QueuedAlready)) {
232 XNextEvent(_display->XDisplay(), &e);
233 process_event(&e);
235 } while (run_state == RUNNING
236 && XEventsQueued(_display->XDisplay(), QueuedAfterFlush));
238 if (run_state != RUNNING)
239 break;
241 fd_set rfds;
242 ::timeval now, tm, *timeout = 0;
244 FD_ZERO(&rfds);
245 FD_SET(xfd, &rfds);
247 if (!timerList.empty()) {
248 const bt::Timer* const timer = timerList.top();
250 gettimeofday(&now, 0);
251 tm = timer->timeRemaining(now);
253 timeout = &tm;
256 int ret = select(xfd + 1, &rfds, 0, 0, timeout);
257 if (ret < 0)
258 continue; // perhaps a signal interrupted select(2)
260 // check for timer timeout
261 gettimeofday(&now, 0);
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;
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.
278 unsigned int i = 0u;
279 while (!timerList.empty() && i++ < 100u) {
280 bt::Timer *timer = timerList.top();
281 if (!timer->shouldFire(now))
282 break;
284 timerList.pop();
286 timer->fireTimeout();
287 timer->halt();
288 if (timer->isRecurring())
289 timer->start();
293 shutdown();
296 void bt::Application::process_event(XEvent *event) {
297 bt::EventHandler *handler = findEventHandler(event->xany.window);
298 if (!handler)
299 return;
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());
312 break;
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;
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;
329 default:
330 break;
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;
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;
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;
362 // if we have compressed some motion events, use the last one
363 if (i > 0)
364 event = &realevent;
366 // strip the lock key modifiers
367 event->xbutton.state &= ~(NumLockMask | ScrollLockMask | LockMask);
368 handler->motionNotifyEvent(&event->xmotion);
369 break;
372 case EnterNotify: {
373 xserver_time = event->xcrossing.time;
374 handler->enterNotifyEvent(&event->xcrossing);
375 break;
378 case LeaveNotify: {
379 xserver_time = event->xcrossing.time;
380 handler->leaveNotifyEvent(&event->xcrossing);
381 break;
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;
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;
400 case MapNotify: {
401 handler->mapNotifyEvent(&event->xmap);
402 break;
405 case UnmapNotify: {
406 handler->unmapNotifyEvent(&event->xunmap);
407 break;
410 case ReparentNotify: {
411 handler->reparentNotifyEvent(&event->xreparent);
412 break;
415 case DestroyNotify: {
416 handler->destroyNotifyEvent(&event->xdestroywindow);
417 break;
420 case PropertyNotify: {
421 xserver_time = event->xproperty.time;
422 handler->propertyNotifyEvent(&event->xproperty);
423 break;
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;
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);
445 if (i > 0)
446 event = &realevent;
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;
454 handler->exposeEvent(&event->xexpose);
455 break;
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;
468 // if we have compressed some configure notify events, use the last one
469 if (i > 0)
470 event = &realevent;
472 handler->configureNotifyEvent(&event->xconfigure);
473 break;
476 case ClientMessage: {
477 handler->clientMessageEvent(&event->xclient);
478 break;
481 case NoExpose: {
482 // not handled, ignore
483 break;
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
494 fprintf(stderr, "unhandled event %d\n", event->type);
496 #endif // DEBUG
497 break;
499 } // switch
503 void bt::Application::addTimer(bt::Timer *timer) {
504 if (!timer)
505 return;
506 timerList.push(timer);
510 void bt::Application::removeTimer(bt::Timer *timer) {
511 timerList.release(timer);
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.
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.
522 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);
538 * Releases the grab on a button, and ungrabs all possible combinations of the
539 * keyboard lock keys.
541 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);
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;
562 case SIGCHLD:
563 int unused;
564 while (waitpid(-1, &unused, WNOHANG | WUNTRACED) > 0)
566 break;
568 default:
569 // generate a core dump for unknown signals
570 return false;
573 return true;
577 void bt::Application::insertEventHandler(Window window,
578 bt::EventHandler *handler) {
579 eventhandlers.insert(std::pair<Window,bt::EventHandler*>(window, handler));
583 void bt::Application::removeEventHandler(Window window) {
584 eventhandlers.erase(window);
588 bt::EventHandler *bt::Application::findEventHandler(Window window)
590 EventHandlerMap::iterator it = eventhandlers.find(window);
591 if (it == eventhandlers.end())
592 return 0;
593 return it->second;
597 void bt::Application::openMenu(Menu *menu) {
598 menus.push_front(menu);
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);
609 menu_grab = true;
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();
620 menus.pop_front();
621 if (!menus.empty())
622 return;
624 XUngrabKeyboard(_display->XDisplay(), xserver_time);
625 XUngrabPointer(_display->XDisplay(), xserver_time);
627 XSync(_display->XDisplay(), False);
628 menu_grab = false;
632 void bt::Application::adjustTimers(const timeval &offset)
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);
643 assert(timerList.empty());
644 while (!tmp.empty()) {
645 Timer *t = tmp.top();
646 tmp.pop();
647 timerList.push(t);