version bump
[blackbox.git] / src / blackbox.cc
blobd750fe61eeb416edaa96f8af6b7ad72346e2dc1e
1 // -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*-
2 // blackbox.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 "blackbox.hh"
26 #include "Screen.hh"
27 #include "Slit.hh"
28 #include "Window.hh"
30 #include <Pen.hh>
31 #include <PixmapCache.hh>
32 #include <Util.hh>
34 #include <X11/Xresource.h>
35 #include <sys/types.h>
36 #include <sys/stat.h>
37 #include <assert.h>
38 #include <signal.h>
39 #include <unistd.h>
41 // #define FOCUS_DEBUG
42 #ifdef FOCUS_DEBUG
43 static const char *Mode[] = {
44 "Normal",
45 "Grab",
46 "Ungrab",
47 "WhileGrabbed"
50 static const char *Detail[] = {
51 "Ancestor",
52 "Virtual",
53 "Inferior",
54 "Nonlinear",
55 "NonlinearVirtual",
56 "Pointer",
57 "PointerRoot",
58 "DetailNone"
60 #endif // FOCUS_DEBUG
63 void Blackbox::save_rc(void)
64 { _resource.save(*this); }
67 void Blackbox::load_rc(void)
68 { _resource.load(*this); }
71 void Blackbox::reload_rc(void) {
72 load_rc();
73 reconfigure();
77 void Blackbox::init_icccm(void) {
78 char* atoms[7] = {
79 "WM_COLORMAP_WINDOWS",
80 "WM_PROTOCOLS",
81 "WM_STATE",
82 "WM_CHANGE_STATE",
83 "WM_DELETE_WINDOW",
84 "WM_TAKE_FOCUS",
85 "_MOTIF_WM_HINTS"
87 Atom atoms_return[7];
88 XInternAtoms(XDisplay(), atoms, 7, false, atoms_return);
89 xa_wm_colormap_windows = atoms_return[0];
90 xa_wm_protocols = atoms_return[1];
91 xa_wm_state = atoms_return[2];
92 xa_wm_change_state = atoms_return[3];
93 xa_wm_delete_window = atoms_return[4];
94 xa_wm_take_focus = atoms_return[5];
95 motif_wm_hints = atoms_return[6];
97 _ewmh = new bt::EWMH(display());
101 void Blackbox::updateActiveWindow() const {
102 Window active = (focused_window) ? focused_window->clientWindow() : None;
103 for (unsigned int i = 0; i < display().screenCount(); ++i)
104 _ewmh->setActiveWindow(display().screenInfo(i).rootWindow(), active);
108 void Blackbox::shutdown(void) {
109 bt::Application::shutdown();
111 XGrabServer();
113 XSetInputFocus(XDisplay(), PointerRoot, RevertToPointerRoot, XTime());
115 std::for_each(screen_list, screen_list + screen_list_count,
116 std::mem_fun(&BScreen::shutdown));
118 XSync(XDisplay(), false);
120 XUngrabServer();
124 static Bool scanForFocusIn(Display *, XEvent *e, XPointer) {
125 if (e->type == FocusIn
126 && e->xfocus.mode != NotifyGrab
127 && (e->xfocus.detail == NotifyNonlinearVirtual
128 || e->xfocus.detail == NotifyVirtual)) {
129 return true;
131 return false;
135 void Blackbox::process_event(XEvent *e) {
136 switch (e->type) {
137 case MapRequest: {
138 #ifdef DEBUG
139 fprintf(stderr, "Blackbox::process_event(): MapRequest for 0x%lx\n",
140 e->xmaprequest.window);
141 #endif // DEBUG
143 BlackboxWindow *win = findWindow(e->xmaprequest.window);
145 if (win) {
146 if ((!activeScreen() || activeScreen() == win->screen()) &&
147 (win->isTransient() || _resource.focusNewWindows()))
148 win->activate();
149 } else {
150 BScreen *screen = findScreen(e->xmaprequest.parent);
152 if (! screen) {
154 we got a map request for a window who's parent isn't root. this
155 can happen in only one circumstance:
157 a client window unmapped a managed window, and then remapped it
158 somewhere between unmapping the client window and reparenting it
159 to root.
161 regardless of how it happens, we need to find the screen that
162 the window is on
164 XWindowAttributes wattrib;
165 if (! XGetWindowAttributes(XDisplay(), e->xmaprequest.window,
166 &wattrib)) {
167 // failed to get the window attributes, perhaps the window has
168 // now been destroyed?
169 break;
172 screen = findScreen(wattrib.root);
173 assert(screen != 0); // this should never happen
175 screen->addWindow(e->xmaprequest.window);
178 break;
181 case ConfigureRequest: {
182 BlackboxWindow *win = findWindow(e->xconfigurerequest.window);
183 if (win) {
184 // a window wants to resize
185 win->configureRequestEvent(&e->xconfigurerequest);
186 break;
189 Slit *slit =
190 dynamic_cast<Slit *>(findEventHandler(e->xconfigurerequest.parent));
191 if (slit) {
192 // something in the slit wants to resize
193 slit->configureRequestEvent(&e->xconfigurerequest);
194 break;
198 handle configure requests for windows that have no EventHandlers
199 by simply configuring them as requested.
201 note: the event->window parameter points to the window being
202 configured, and event->parent points to the window that received
203 the event (in this case, the root window, since
204 SubstructureRedirect has been selected).
206 XWindowChanges xwc;
207 xwc.x = e->xconfigurerequest.x;
208 xwc.y = e->xconfigurerequest.y;
209 xwc.width = e->xconfigurerequest.width;
210 xwc.height = e->xconfigurerequest.height;
211 xwc.border_width = e->xconfigurerequest.border_width;
212 xwc.sibling = e->xconfigurerequest.above;
213 xwc.stack_mode = e->xconfigurerequest.detail;
214 XConfigureWindow(XDisplay(),
215 e->xconfigurerequest.window,
216 e->xconfigurerequest.value_mask,
217 &xwc);
218 break;
221 case FocusIn: {
222 #ifdef FOCUS_DEBUG
223 printf("FocusIn : window %8lx mode %s detail %s\n",
224 e->xfocus.window, Mode[e->xfocus.mode], Detail[e->xfocus.detail]);
225 #endif
227 if (e->xfocus.mode == NotifyGrab
228 || (e->xfocus.detail != NotifyNonlinearVirtual
229 && e->xfocus.detail != NotifyVirtual)) {
231 don't process FocusIns when:
232 1. they are the result of a grab
233 2. the new focus window isn't an ancestor or inferior of the
234 old focus window (NotifyNonlinearVirtual and NotifyVirtual)
236 break;
239 BlackboxWindow *win = findWindow(e->xfocus.window);
240 if (!win || win->isFocused())
241 break;
243 #ifdef FOCUS_DEBUG
244 printf(" win %p got focus\n", win);
245 #endif
246 win->setFocused(true);
247 setFocusedWindow(win);
250 set the event window to None. when the FocusOut event handler calls
251 this function recursively, it uses this as an indication that focus
252 has moved to a known window.
254 e->xfocus.window = None;
256 break;
259 case FocusOut: {
260 #ifdef FOCUS_DEBUG
261 printf("FocusOut: window %8lx mode %s detail %s\n",
262 e->xfocus.window, Mode[e->xfocus.mode], Detail[e->xfocus.detail]);
263 #endif
265 if (e->xfocus.mode == NotifyGrab
266 || (e->xfocus.detail != NotifyNonlinearVirtual
267 && e->xfocus.detail != NotifyVirtual)) {
269 don't process FocusOuts when:
270 1. they are the result of a grab
271 2. the new focus window isn't an ancestor or inferior of the
272 old focus window (NotifyNonlinearVirtual and NotifyNonlinearVirtual)
274 break;
277 BlackboxWindow *win = findWindow(e->xfocus.window);
278 if (!win || !win->isFocused())
279 break;
281 bool lost_focus = true; // did the window really lose focus?
282 bool no_focus = true; // did another window get focus?
284 XEvent event;
285 if (XCheckIfEvent(XDisplay(), &event, scanForFocusIn, NULL)) {
286 process_event(&event);
288 if (event.xfocus.window == None)
289 no_focus = false;
290 } else {
291 XWindowAttributes attr;
292 Window w;
293 int unused;
294 XGetInputFocus(XDisplay(), &w, &unused);
295 if (w != None
296 && XGetWindowAttributes(XDisplay(), w, &attr)
297 && attr.override_redirect) {
298 #ifdef FOCUS_DEBUG
299 printf(" focused moved to an override_redirect window\n");
300 #endif
301 lost_focus = (e->xfocus.mode == NotifyNormal);
305 if (lost_focus) {
306 #ifdef FOCUS_DEBUG
307 printf(" win %p lost focus\n", win);
308 #endif
309 win->setFocused(false);
311 if (no_focus) {
312 #ifdef FOCUS_DEBUG
313 printf(" no window has focus\n");
314 #endif
315 setFocusedWindow(0);
319 break;
322 default:
323 // Send the event through the default EventHandlers.
324 bt::Application::process_event(e);
325 break;
326 } // switch
330 bool Blackbox::process_signal(int sig) {
331 switch (sig) {
332 case SIGHUP:
333 reconfigure();
334 break;
336 case SIGUSR1:
337 reload_rc();
338 break;
340 case SIGUSR2:
341 rereadMenu();
342 break;
344 default:
345 return bt::Application::process_signal(sig);
346 } // switch
348 return true;
352 void Blackbox::timeout(bt::Timer *) {
353 XrmDatabase new_blackboxrc = (XrmDatabase) 0;
355 std::string style = "session.styleFile: ";
356 style += _resource.styleFilename();
357 XrmPutLineResource(&new_blackboxrc, style.c_str());
359 XrmDatabase old_blackboxrc = XrmGetFileDatabase(_resource.rcFilename());
361 XrmMergeDatabases(new_blackboxrc, &old_blackboxrc);
362 XrmPutFileDatabase(old_blackboxrc, _resource.rcFilename());
363 if (old_blackboxrc) XrmDestroyDatabase(old_blackboxrc);
365 std::for_each(menuTimestamps.begin(), menuTimestamps.end(),
366 bt::PointerAssassin());
367 menuTimestamps.clear();
369 std::for_each(screen_list, screen_list + screen_list_count,
370 std::mem_fun(&BScreen::reconfigure));
372 bt::Font::clearCache();
373 bt::PixmapCache::clearCache();
374 bt::Pen::clearCache();
376 // clear the color cache here to allow the pen cache to deallocate
377 // all unused colors
378 bt::Color::clearCache();
382 Blackbox::Blackbox(char **m_argv, const char *dpy_name,
383 const std::string& rc, bool multi_head)
384 : bt::Application(m_argv[0], dpy_name, multi_head),
385 grab_count(0u), _resource(rc)
387 if (! XSupportsLocale())
388 fprintf(stderr, "X server does not support locale\n");
390 if (XSetLocaleModifiers("") == NULL)
391 fprintf(stderr, "cannot set locale modifiers\n");
393 argv = m_argv;
395 active_screen = 0;
396 focused_window = (BlackboxWindow *) 0;
397 _ewmh = (bt::EWMH*) 0;
399 init_icccm();
401 if (! multi_head || display().screenCount() == 1)
402 screen_list_count = 1;
403 else
404 screen_list_count = display().screenCount();
406 _resource.load(*this);
408 screen_list = new BScreen*[screen_list_count];
409 unsigned int managed = 0;
410 for (unsigned int i = 0; i < screen_list_count; ++i) {
411 BScreen *screen = new BScreen(this, i);
413 if (! screen->isScreenManaged()) {
414 delete screen;
415 continue;
418 screen_list[i] = screen;
419 ++managed;
422 if (managed == 0) {
423 fprintf(stderr, "%s: no managable screens found, exiting...\n",
424 applicationName().c_str());
425 ::exit(3);
428 screen_list_count = managed;
430 // start with the first managed screen as the active screen
431 setActiveScreen(screen_list[0]);
433 XSynchronize(XDisplay(), false);
434 XSync(XDisplay(), false);
436 timer = new bt::Timer(this, this);
437 timer->setTimeout(0l);
441 Blackbox::~Blackbox(void) {
442 std::for_each(screen_list, screen_list + screen_list_count,
443 bt::PointerAssassin());
445 delete [] screen_list;
446 std::for_each(menuTimestamps.begin(), menuTimestamps.end(),
447 bt::PointerAssassin());
449 delete timer;
450 delete _ewmh;
454 void Blackbox::XGrabServer(void) {
455 if (grab_count++ == 0)
456 ::XGrabServer(XDisplay());
460 void Blackbox::XUngrabServer(void) {
461 if (--grab_count == 0)
462 ::XUngrabServer(XDisplay());
466 BScreen *Blackbox::findScreen(Window window) const {
467 for (unsigned int i = 0; i < screen_list_count; ++i)
468 if (screen_list[i]->screenInfo().rootWindow() == window)
469 return screen_list[i];
470 return 0;
474 void Blackbox::setActiveScreen(BScreen *screen) {
475 if (active_screen && active_screen == screen) // nothing to do
476 return;
478 assert(screen != 0);
479 active_screen = screen;
481 // install screen colormap
482 XInstallColormap(XDisplay(), active_screen->screenInfo().colormap());
484 if (! focused_window || focused_window->screen() != active_screen)
485 setFocusedWindow(0);
489 BScreen* Blackbox::screenNumber(unsigned int n) {
490 assert(n < screen_list_count);
491 return screen_list[n];
495 BlackboxWindow *Blackbox::findWindow(Window window) const {
496 WindowLookup::const_iterator it = windowSearchList.find(window);
497 if (it != windowSearchList.end())
498 return it->second;
499 return 0;
503 void Blackbox::insertWindow(Window window, BlackboxWindow *data)
504 { windowSearchList.insert(WindowLookupPair(window, data)); }
507 void Blackbox::removeWindow(Window window)
508 { windowSearchList.erase(window); }
511 BWindowGroup *Blackbox::findWindowGroup(Window window) const {
512 GroupLookup::const_iterator it = groupSearchList.find(window);
513 if (it != groupSearchList.end())
514 return it->second;
515 return 0;
519 void Blackbox::insertWindowGroup(Window window, BWindowGroup *data)
520 { groupSearchList.insert(GroupLookupPair(window, data)); }
523 void Blackbox::removeWindowGroup(Window window)
524 { groupSearchList.erase(window); }
527 void Blackbox::setFocusedWindow(BlackboxWindow *win) {
528 if (focused_window && focused_window == win) // nothing to do
529 return;
531 if (win && !win->isIconic()) {
532 // the active screen is the one with the newly focused window...
533 active_screen = win->screen();
534 focused_window = win;
535 } else {
536 // nothing has focus
537 focused_window = 0;
538 assert(active_screen != 0);
539 XSetInputFocus(XDisplay(), active_screen->noFocusWindow(),
540 RevertToPointerRoot, XTime());
543 updateActiveWindow();
547 void Blackbox::restart(const std::string &prog) {
548 setRunState(bt::Application::SHUTDOWN);
551 since we don't allow control to return to the eventloop, we need
552 to call shutdown() explicitly
554 shutdown();
556 if (! prog.empty()) {
557 putenv(const_cast<char *>
558 (display().screenInfo(0).displayString().c_str()));
559 execlp(prog.c_str(), prog.c_str(), NULL);
560 perror(prog.c_str());
563 // fall back in case the above execlp doesn't work
564 execvp(argv[0], argv);
565 std::string name = bt::basename(argv[0]);
566 execvp(name.c_str(), argv);
570 void Blackbox::reconfigure(void) {
571 if (! timer->isTiming())
572 timer->start();
576 void Blackbox::saveMenuFilename(const std::string& filename) {
577 assert(!filename.empty());
578 bool found = false;
580 MenuTimestampList::iterator it = menuTimestamps.begin();
581 for (; it != menuTimestamps.end() && !found; ++it)
582 found = (*it)->filename == filename;
583 if (found)
584 return;
586 struct stat buf;
587 if (stat(filename.c_str(), &buf) != 0)
588 return; // file doesn't exist
590 MenuTimestamp *ts = new MenuTimestamp;
591 ts->filename = filename;
592 ts->timestamp = buf.st_ctime;
593 menuTimestamps.push_back(ts);
597 void Blackbox::checkMenu(void) {
598 bool reread = false;
599 MenuTimestampList::iterator it = menuTimestamps.begin();
600 for(; it != menuTimestamps.end(); ++it) {
601 MenuTimestamp *tmp = *it;
602 struct stat buf;
604 if (! stat(tmp->filename.c_str(), &buf)) {
605 if (tmp->timestamp != buf.st_ctime)
606 reread = true;
607 } else {
608 reread = true;
612 if (reread)
613 rereadMenu();
617 void Blackbox::rereadMenu(void) {
618 std::for_each(menuTimestamps.begin(), menuTimestamps.end(),
619 bt::PointerAssassin());
620 menuTimestamps.clear();
622 std::for_each(screen_list, screen_list + screen_list_count,
623 std::mem_fun(&BScreen::rereadMenu));