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>
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"
27 #include "EventHandler.hh"
31 #include <X11/Xatom.h>
32 #include <X11/Xutil.h>
33 #include <X11/keysym.h>
35 # include <X11/extensions/shape.h>
38 #include <sys/types.h>
40 # include <sys/select.h>
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;
62 static bt::Application
*base_app
= 0;
63 static sig_atomic_t pending_signals
= 0;
66 static int handleXErrors(Display
*d
, XErrorEvent
*e
) {
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
);
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,
87 static void signalhandler(int sig
)
88 { pending_signals
|= (1 << sig
); }
91 bt::Application::Application(const std::string
&app_name
, const char *dpy_name
,
93 : _app_name(bt::basename(app_name
)), run_state(STARTUP
),
94 xserver_time(CurrentTime
), menu_grab(false)
96 assert(base_app
== 0);
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
;
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
);
117 shape
.extensions
= XShapeQueryExtension(_display
->XDisplay(),
121 shape
.extensions
= False
;
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
])
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
];
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]);
167 XFreeModifiermap(const_cast<XModifierKeymap
*>(modmap
));
172 gettimeofday(&tv
, 0);
177 bt::Application::~Application(void) {
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) {
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
)))
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
);
230 while (run_state
== RUNNING
231 && XEventsQueued(_display
->XDisplay(), QueuedAlready
)) {
232 XNextEvent(_display
->XDisplay(), &e
);
235 } while (run_state
== RUNNING
236 && XEventsQueued(_display
->XDisplay(), QueuedAfterFlush
));
238 if (run_state
!= RUNNING
)
242 ::timeval now
, tm
, *timeout
= 0;
247 if (!timerList
.empty()) {
248 const bt::Timer
* const timer
= timerList
.top();
250 gettimeofday(&now
, 0);
251 tm
= timer
->timeRemaining(now
);
256 int ret
= select(xfd
+ 1, &rfds
, 0, 0, timeout
);
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
266 if (tv
< currentTime
)
267 adjustTimers(tv
- currentTime
);
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.
279 while (!timerList
.empty() && i
++ < 100u) {
280 bt::Timer
*timer
= timerList
.top();
281 if (!timer
->shouldFire(now
))
286 timer
->fireTimeout();
288 if (timer
->isRecurring())
296 void bt::Application::process_event(XEvent
*event
) {
297 bt::EventHandler
*handler
= findEventHandler(event
->xany
.window
);
301 // if there is an active menu, pre-process the events
303 switch (event
->type
) {
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());
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
))
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());
335 switch (event
->type
) {
337 xserver_time
= event
->xbutton
.time
;
338 // strip the lock key modifiers
339 event
->xbutton
.state
&= ~(NumLockMask
| ScrollLockMask
| LockMask
);
340 handler
->buttonPressEvent(&event
->xbutton
);
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
);
353 xserver_time
= event
->xmotion
.time
;
354 // compress motion notify events
357 while (XCheckTypedWindowEvent(_display
->XDisplay(), event
->xmotion
.window
,
358 MotionNotify
, &realevent
)) {
362 // if we have compressed some motion events, use the last one
366 // strip the lock key modifiers
367 event
->xbutton
.state
&= ~(NumLockMask
| ScrollLockMask
| LockMask
);
368 handler
->motionNotifyEvent(&event
->xmotion
);
373 xserver_time
= event
->xcrossing
.time
;
374 handler
->enterNotifyEvent(&event
->xcrossing
);
379 xserver_time
= event
->xcrossing
.time
;
380 handler
->leaveNotifyEvent(&event
->xcrossing
);
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
);
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
);
401 handler
->mapNotifyEvent(&event
->xmap
);
406 handler
->unmapNotifyEvent(&event
->xunmap
);
410 case ReparentNotify
: {
411 handler
->reparentNotifyEvent(&event
->xreparent
);
415 case DestroyNotify
: {
416 handler
->destroyNotifyEvent(&event
->xdestroywindow
);
420 case PropertyNotify
: {
421 xserver_time
= event
->xproperty
.time
;
422 handler
->propertyNotifyEvent(&event
->xproperty
);
427 // compress expose events
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
)) {
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
);
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
);
458 case ConfigureNotify
: {
459 // compress configure notify events
462 while (XCheckTypedWindowEvent(_display
->XDisplay(),
463 event
->xconfigure
.window
,
464 ConfigureNotify
, &realevent
)) {
468 // if we have compressed some configure notify events, use the last one
472 handler
->configureNotifyEvent(&event
->xconfigure
);
476 case ClientMessage
: {
477 handler
->clientMessageEvent(&event
->xclient
);
482 // not handled, ignore
488 if (shape
.extensions
&& event
->type
== shape
.event_basep
) {
489 handler
->shapeEvent(event
);
494 fprintf(stderr
, "unhandled event %d\n", event
->type
);
503 void bt::Application::addTimer(bt::Timer
*timer
) {
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
],
550 bool bt::Application::process_signal(int signal
) {
559 setRunState(SHUTDOWN
);
564 while (waitpid(-1, &unused
, WNOHANG
| WUNTRACED
) > 0)
569 // generate a core dump for unknown signals
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())
597 void bt::Application::openMenu(Menu
*menu
) {
598 menus
.push_front(menu
);
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
);
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
));
624 XUngrabKeyboard(_display
->XDisplay(), xserver_time
);
625 XUngrabPointer(_display
->XDisplay(), xserver_time
);
627 XSync(_display
->XDisplay(), False
);
632 void bt::Application::adjustTimers(const timeval
&offset
)
634 // since we don't allow TimerQueues to be copied, we have to do it
637 while (!timerList
.empty()) {
638 Timer
*t
= timerList
.top();
640 t
->adjustStartTime(offset
);
643 assert(timerList
.empty());
644 while (!tmp
.empty()) {
645 Timer
*t
= tmp
.top();