// -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*- // Application.cc for Blackbox - an X11 Window manager // Copyright (c) 2001 - 2005 Sean 'Shaleh' Perry // Copyright (c) 1997 - 2000, 2002 - 2005 // Bradley T Hughes // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. #include "Application.hh" #include "Display.hh" #include "EventHandler.hh" #include "Menu.hh" #include #include #include #include #ifdef SHAPE # include #endif // SHAPE #include #if defined(__EMX__) # include #endif #include #include #include #include #include #include #include #include #include #if defined(__GNUC__) # if __GNUC__ == 3 && __GNUC_MINOR__ == 3 // work around a gcc 3.3 compiler bug where base_app below would be // initialized to ~0 instead of 0. static void *workaround __attribute__((__unused__)) = 0; # endif #endif static bt::Application *base_app = 0; static sig_atomic_t pending_signals = 0; static int handleXErrors(Display *d, XErrorEvent *e) { #ifdef DEBUG char errtxt[128]; XGetErrorText(d, e->error_code, errtxt, 128); fprintf(stderr, "%s: X error: %s(%d) opcodes %d/%d\n resource 0x%lx\n", base_app->applicationName().c_str(), errtxt, e->error_code, e->request_code, e->minor_code, e->resourceid); #else // shutup gcc (void) d; (void) e; #endif // DEBUG return 0; } // generic signal handler - this sets a bit in pending_signals, which // will be handled later by the event loop (ie. if signal 2 is caught, // bit 2 is set) static void signalhandler(int sig) { pending_signals |= (1 << sig); } bt::Application::Application(const std::string &app_name, const char *dpy_name, bool multi_head) : _app_name(bt::basename(app_name)), run_state(STARTUP), xserver_time(CurrentTime), menu_grab(false) { assert(base_app == 0); ::base_app = this; _display = new Display(dpy_name, multi_head); struct sigaction action; action.sa_handler = signalhandler; action.sa_mask = sigset_t(); action.sa_flags = SA_NOCLDSTOP; // non-fatal signals sigaction(SIGHUP, &action, NULL); sigaction(SIGINT, &action, NULL); sigaction(SIGQUIT, &action, NULL); sigaction(SIGTERM, &action, NULL); sigaction(SIGPIPE, &action, NULL); sigaction(SIGCHLD, &action, NULL); sigaction(SIGUSR1, &action, NULL); sigaction(SIGUSR2, &action, NULL); #ifdef SHAPE shape.extensions = XShapeQueryExtension(_display->XDisplay(), &shape.event_basep, &shape.error_basep); #else // !SHAPE shape.extensions = False; #endif // SHAPE XSetErrorHandler(handleXErrors); NumLockMask = ScrollLockMask = 0; const XModifierKeymap* const modmap = XGetModifierMapping(_display->XDisplay()); if (modmap && modmap->max_keypermod > 0) { const int mask_table[] = { ShiftMask, LockMask, ControlMask, Mod1Mask, Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask }; const size_t size = (sizeof(mask_table) / sizeof(mask_table[0])) * modmap->max_keypermod; // get the values of the keyboard lock modifiers // Note: Caps lock is not retrieved the same way as Scroll and Num lock // since it doesn't need to be. const KeyCode num_lock = XKeysymToKeycode(_display->XDisplay(), XK_Num_Lock); const KeyCode scroll_lock = XKeysymToKeycode(_display->XDisplay(), XK_Scroll_Lock); for (size_t cnt = 0; cnt < size; ++cnt) { if (!modmap->modifiermap[cnt]) continue; if (num_lock == modmap->modifiermap[cnt]) NumLockMask = mask_table[cnt / modmap->max_keypermod]; if (scroll_lock == modmap->modifiermap[cnt]) ScrollLockMask = mask_table[cnt / modmap->max_keypermod]; } } MaskList[0] = 0; MaskList[1] = LockMask; MaskList[2] = NumLockMask; MaskList[3] = LockMask | NumLockMask; MaskList[4] = ScrollLockMask; MaskList[5] = ScrollLockMask | LockMask; MaskList[6] = ScrollLockMask | NumLockMask; MaskList[7] = ScrollLockMask | LockMask | NumLockMask; MaskListLength = sizeof(MaskList) / sizeof(MaskList[0]); if (modmap) XFreeModifiermap(const_cast(modmap)); XrmInitialize(); ::timeval tv; gettimeofday(&tv, 0); currentTime = tv; } bt::Application::~Application(void) { delete _display; ::base_app = 0; } ::Display *bt::Application::XDisplay(void) const { return _display->XDisplay(); } void bt::Application::startup(void) { } void bt::Application::shutdown(void) { } void bt::Application::run(void) { startup(); setRunState(RUNNING); const int xfd = XConnectionNumber(_display->XDisplay()); while (run_state == RUNNING) { if (pending_signals) { // handle any pending signals const unsigned int sigmax = sizeof(pending_signals) * 8; for (unsigned int sig = 0; sig < sigmax; ++sig) { if (!(pending_signals & (1u << sig))) continue; pending_signals &= ~(1u << sig); setRunState(SIGNALLED); if (process_signal(sig)) { // reset run_state if it has not been set to something else if (run_state == SIGNALLED) setRunState(RUNNING); } if (run_state == SIGNALLED) { // dump core for unhandled signals fprintf(stderr, "%s: caught unknown signal '%u', dumping core.\n", _app_name.c_str(), sig); abort(); } } } do { XEvent e; while (run_state == RUNNING && XEventsQueued(_display->XDisplay(), QueuedAlready)) { XNextEvent(_display->XDisplay(), &e); process_event(&e); } } while (run_state == RUNNING && XEventsQueued(_display->XDisplay(), QueuedAfterFlush)); if (run_state != RUNNING) break; fd_set rfds; ::timeval now, tm, *timeout = 0; FD_ZERO(&rfds); FD_SET(xfd, &rfds); if (!timerList.empty()) { const bt::Timer* const timer = timerList.top(); gettimeofday(&now, 0); tm = timer->timeRemaining(now); timeout = &tm; } int ret = select(xfd + 1, &rfds, 0, 0, timeout); if (ret < 0) continue; // perhaps a signal interrupted select(2) // check for timer timeout gettimeofday(&now, 0); { // if the clock has rolled back, adjust all timers timeval tv = now; if (tv < currentTime) adjustTimers(tv - currentTime); currentTime = tv; } /* there is a small chance for deadlock here: *IF* the timer list keeps getting refreshed *AND* the time between timer->start() and timer->shouldFire() is within the timer's period then the timer will keep firing. This should be VERY near impossible. But to be safe, let's use a counter here. */ unsigned int i = 0u; while (!timerList.empty() && i++ < 100u) { bt::Timer *timer = timerList.top(); if (!timer->shouldFire(now)) break; timerList.pop(); timer->fireTimeout(); timer->halt(); if (timer->isRecurring()) timer->start(); } } shutdown(); } void bt::Application::process_event(XEvent *event) { bt::EventHandler *handler = findEventHandler(event->xany.window); if (!handler) return; // if there is an active menu, pre-process the events if (menu_grab) { switch (event->type) { case ButtonPress: case ButtonRelease: case MotionNotify: { if (!dynamic_cast(handler)) { // current handler is not a menu. send the event to the most // recent menu instead. handler = dynamic_cast(menus.front()); } break; } case EnterNotify: case LeaveNotify: { // we have active menus. we should only send enter/leave events // to the menus themselves, not to normal windows if (!dynamic_cast(handler)) return; break; } case KeyPress: case KeyRelease: { // we have active menus. we should send all key events to the most // recent popup menu, regardless of where the pointer is handler = dynamic_cast(menus.front()); break; } default: break; } } // deliver the event switch (event->type) { case ButtonPress: { xserver_time = event->xbutton.time; // strip the lock key modifiers event->xbutton.state &= ~(NumLockMask | ScrollLockMask | LockMask); handler->buttonPressEvent(&event->xbutton); break; } case ButtonRelease: { xserver_time = event->xbutton.time; // strip the lock key modifiers event->xbutton.state &= ~(NumLockMask | ScrollLockMask | LockMask); handler->buttonReleaseEvent(&event->xbutton); break; } case MotionNotify: { xserver_time = event->xmotion.time; // compress motion notify events XEvent realevent; unsigned int i = 0; while (XCheckTypedWindowEvent(_display->XDisplay(), event->xmotion.window, MotionNotify, &realevent)) { ++i; } // if we have compressed some motion events, use the last one if (i > 0) event = &realevent; // strip the lock key modifiers event->xbutton.state &= ~(NumLockMask | ScrollLockMask | LockMask); handler->motionNotifyEvent(&event->xmotion); break; } case EnterNotify: { xserver_time = event->xcrossing.time; handler->enterNotifyEvent(&event->xcrossing); break; } case LeaveNotify: { xserver_time = event->xcrossing.time; handler->leaveNotifyEvent(&event->xcrossing); break; } case KeyPress: { xserver_time = event->xkey.time; // strip the lock key modifiers, except numlock, which can be useful event->xkey.state &= ~(ScrollLockMask | LockMask); handler->keyPressEvent(&event->xkey); break; } case KeyRelease: { xserver_time = event->xkey.time; // strip the lock key modifiers, except numlock, which can be useful event->xkey.state &= ~(ScrollLockMask | LockMask); handler->keyReleaseEvent(&event->xkey); break; } case MapNotify: { handler->mapNotifyEvent(&event->xmap); break; } case UnmapNotify: { handler->unmapNotifyEvent(&event->xunmap); break; } case ReparentNotify: { handler->reparentNotifyEvent(&event->xreparent); break; } case DestroyNotify: { handler->destroyNotifyEvent(&event->xdestroywindow); break; } case PropertyNotify: { xserver_time = event->xproperty.time; handler->propertyNotifyEvent(&event->xproperty); break; } case Expose: { // compress expose events XEvent realevent; unsigned int i = 0; int ex1, ey1, ex2, ey2; ex1 = event->xexpose.x; ey1 = event->xexpose.y; ex2 = ex1 + event->xexpose.width - 1; ey2 = ey1 + event->xexpose.height - 1; while (XCheckTypedWindowEvent(_display->XDisplay(), event->xexpose.window, Expose, &realevent)) { ++i; // merge expose area ex1 = std::min(realevent.xexpose.x, ex1); ey1 = std::min(realevent.xexpose.y, ey1); ex2 = std::max(realevent.xexpose.x + realevent.xexpose.width - 1, ex2); ey2 = std::max(realevent.xexpose.y + realevent.xexpose.height - 1, ey2); } if (i > 0) event = &realevent; // use the merged area event->xexpose.x = ex1; event->xexpose.y = ey1; event->xexpose.width = ex2 - ex1 + 1; event->xexpose.height = ey2 - ey1 + 1; handler->exposeEvent(&event->xexpose); break; } case ConfigureNotify: { // compress configure notify events XEvent realevent; unsigned int i = 0; while (XCheckTypedWindowEvent(_display->XDisplay(), event->xconfigure.window, ConfigureNotify, &realevent)) { ++i; } // if we have compressed some configure notify events, use the last one if (i > 0) event = &realevent; handler->configureNotifyEvent(&event->xconfigure); break; } case ClientMessage: { handler->clientMessageEvent(&event->xclient); break; } case NoExpose: { // not handled, ignore break; } default: { #ifdef SHAPE if (shape.extensions && event->type == shape.event_basep) { handler->shapeEvent(event); } else #endif // SHAPE #ifdef DEBUG { fprintf(stderr, "unhandled event %d\n", event->type); } #endif // DEBUG break; } } // switch } void bt::Application::addTimer(bt::Timer *timer) { if (!timer) return; timerList.push(timer); } void bt::Application::removeTimer(bt::Timer *timer) { timerList.release(timer); } /* * Grabs a button, but also grabs the button in every possible combination * with the keyboard lock keys, so that they do not cancel out the event. * if allow_scroll_lock is true then only the top half of the lock mask * table is used and scroll lock is ignored. This value defaults to false. */ void bt::Application::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) const { const size_t length = (allow_scroll_lock) ? MaskListLength / 2 : MaskListLength; for (size_t cnt = 0; cnt < length; ++cnt) { XGrabButton(_display->XDisplay(), button, modifiers | MaskList[cnt], grab_window, owner_events, event_mask, pointer_mode, keyboard_mode, confine_to, cursor); } } /* * Releases the grab on a button, and ungrabs all possible combinations of the * keyboard lock keys. */ void bt::Application::ungrabButton(unsigned int button, unsigned int modifiers, Window grab_window) const { for (size_t cnt = 0; cnt < MaskListLength; ++cnt) { XUngrabButton(_display->XDisplay(), button, modifiers | MaskList[cnt], grab_window); } } bool bt::Application::process_signal(int signal) { switch (signal) { case SIGHUP: case SIGINT: case SIGQUIT: case SIGTERM: case SIGPIPE: case SIGUSR1: case SIGUSR2: setRunState(SHUTDOWN); break; case SIGCHLD: int unused; while (waitpid(-1, &unused, WNOHANG | WUNTRACED) > 0) ; break; default: // generate a core dump for unknown signals return false; } return true; } void bt::Application::insertEventHandler(Window window, bt::EventHandler *handler) { eventhandlers.insert(std::pair(window, handler)); } void bt::Application::removeEventHandler(Window window) { eventhandlers.erase(window); } bt::EventHandler *bt::Application::findEventHandler(Window window) { EventHandlerMap::iterator it = eventhandlers.find(window); if (it == eventhandlers.end()) return 0; return it->second; } void bt::Application::openMenu(Menu *menu) { menus.push_front(menu); if (!menu_grab) { // grab mouse and keyboard for the menu XGrabKeyboard(_display->XDisplay(), menu->windowID(), True, GrabModeAsync, GrabModeAsync, xserver_time); XGrabPointer(_display->XDisplay(), menu->windowID(), True, (ButtonPressMask | ButtonReleaseMask | ButtonMotionMask | PointerMotionMask | LeaveWindowMask), GrabModeAsync, GrabModeAsync, None, None, xserver_time); } menu_grab = true; } void bt::Application::closeMenu(Menu *menu) { if (menus.empty() || menu != menus.front()) { fprintf(stderr, "BaseDisplay::closeMenu: menu %p not valid.\n", static_cast(menu)); abort(); } menus.pop_front(); if (!menus.empty()) return; XUngrabKeyboard(_display->XDisplay(), xserver_time); XUngrabPointer(_display->XDisplay(), xserver_time); XSync(_display->XDisplay(), False); menu_grab = false; } void bt::Application::adjustTimers(const timeval &offset) { // since we don't allow TimerQueues to be copied, we have to do it // this way TimerQueue tmp; while (!timerList.empty()) { Timer *t = timerList.top(); timerList.pop(); t->adjustStartTime(offset); tmp.push(t); } assert(timerList.empty()); while (!tmp.empty()) { Timer *t = tmp.top(); tmp.pop(); timerList.push(t); } }