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:
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 "blackbox.hh"
26 #include "Screen.hh"
27 #include "Slit.hh"
28 #include "Window.hh"
29 
30 #include <cstdlib>
31 
32 #include <Pen.hh>
33 #include <PixmapCache.hh>
34 #include <Util.hh>
35 
36 #include <X11/Xresource.h>
37 #include <sys/types.h>
38 #include <sys/stat.h>
39 #include <assert.h>
40 #include <signal.h>
41 #include <stdlib.h>
42 #include <unistd.h>
43 
44 // #define FOCUS_DEBUG
45 #ifdef FOCUS_DEBUG
46 static const char *Mode[] = {
47   "Normal",
48   "Grab",
49   "Ungrab",
50   "WhileGrabbed"
51 };
52 
53 static const char *Detail[] = {
54   "Ancestor",
55   "Virtual",
56   "Inferior",
57   "Nonlinear",
58   "NonlinearVirtual",
59   "Pointer",
60   "PointerRoot",
61   "DetailNone"
62 };
63 #endif // FOCUS_DEBUG
64 
65 
save_rc(void)66 void Blackbox::save_rc(void)
67 { _resource.save(*this); }
68 
69 
load_rc(void)70 void Blackbox::load_rc(void)
71 { _resource.load(*this); }
72 
73 
reload_rc(void)74 void Blackbox::reload_rc(void) {
75   load_rc();
76   reconfigure();
77 }
78 
79 
init_icccm(void)80 void Blackbox::init_icccm(void) {
81   char* atoms[7] = {
82     "WM_COLORMAP_WINDOWS",
83     "WM_PROTOCOLS",
84     "WM_STATE",
85     "WM_CHANGE_STATE",
86     "WM_DELETE_WINDOW",
87     "WM_TAKE_FOCUS",
88     "_MOTIF_WM_HINTS"
89   };
90   Atom atoms_return[7];
91   XInternAtoms(XDisplay(), atoms, 7, false, atoms_return);
92   xa_wm_colormap_windows = atoms_return[0];
93   xa_wm_protocols = atoms_return[1];
94   xa_wm_state = atoms_return[2];
95   xa_wm_change_state = atoms_return[3];
96   xa_wm_delete_window = atoms_return[4];
97   xa_wm_take_focus = atoms_return[5];
98   motif_wm_hints = atoms_return[6];
99 
100   _ewmh = new bt::EWMH(display());
101 }
102 
103 
updateActiveWindow() const104 void Blackbox::updateActiveWindow() const {
105   Window active = (focused_window) ? focused_window->clientWindow() : None;
106   for (unsigned int i = 0; i < display().screenCount(); ++i)
107     _ewmh->setActiveWindow(display().screenInfo(i).rootWindow(), active);
108 }
109 
110 
shutdown(void)111 void Blackbox::shutdown(void) {
112   bt::Application::shutdown();
113 
114   XGrabServer();
115 
116   XSetInputFocus(XDisplay(), PointerRoot, RevertToPointerRoot, XTime());
117 
118   std::for_each(screen_list, screen_list + screen_list_count,
119                 std::mem_fun(&BScreen::shutdown));
120 
121   XSync(XDisplay(), false);
122 
123   XUngrabServer();
124 }
125 
126 
scanForFocusIn(Display *,XEvent * e,XPointer)127 static Bool scanForFocusIn(Display *, XEvent *e, XPointer) {
128   if (e->type == FocusIn
129       && e->xfocus.mode != NotifyGrab
130       && (e->xfocus.detail == NotifyNonlinearVirtual
131           || e->xfocus.detail == NotifyVirtual)) {
132     return true;
133   }
134   return false;
135 }
136 
137 
process_event(XEvent * e)138 void Blackbox::process_event(XEvent *e) {
139   switch (e->type) {
140   case MapRequest: {
141 #ifdef    DEBUG
142     fprintf(stderr, "Blackbox::process_event(): MapRequest for 0x%lx\n",
143             e->xmaprequest.window);
144 #endif // DEBUG
145 
146     BlackboxWindow *win = findWindow(e->xmaprequest.window);
147 
148     if (win) {
149       if ((!activeScreen() || activeScreen() == win->screen()) &&
150           (win->isTransient() || _resource.focusNewWindows()))
151         win->activate();
152     } else {
153       BScreen *screen = findScreen(e->xmaprequest.parent);
154 
155       if (! screen) {
156         /*
157           we got a map request for a window who's parent isn't root. this
158           can happen in only one circumstance:
159 
160           a client window unmapped a managed window, and then remapped it
161           somewhere between unmapping the client window and reparenting it
162           to root.
163 
164           regardless of how it happens, we need to find the screen that
165           the window is on
166         */
167         XWindowAttributes wattrib;
168         if (! XGetWindowAttributes(XDisplay(), e->xmaprequest.window,
169                                    &wattrib)) {
170           // failed to get the window attributes, perhaps the window has
171           // now been destroyed?
172           break;
173         }
174 
175         screen = findScreen(wattrib.root);
176         assert(screen != 0); // this should never happen
177       }
178       screen->addWindow(e->xmaprequest.window);
179     }
180 
181     break;
182   }
183 
184   case ConfigureRequest: {
185     BlackboxWindow *win = findWindow(e->xconfigurerequest.window);
186     if (win) {
187       // a window wants to resize
188       win->configureRequestEvent(&e->xconfigurerequest);
189       break;
190     }
191 
192     Slit *slit =
193       dynamic_cast<Slit *>(findEventHandler(e->xconfigurerequest.parent));
194     if (slit) {
195       // something in the slit wants to resize
196       slit->configureRequestEvent(&e->xconfigurerequest);
197       break;
198     }
199 
200     /*
201       handle configure requests for windows that have no EventHandlers
202       by simply configuring them as requested.
203 
204       note: the event->window parameter points to the window being
205       configured, and event->parent points to the window that received
206       the event (in this case, the root window, since
207       SubstructureRedirect has been selected).
208     */
209     XWindowChanges xwc;
210     xwc.x = e->xconfigurerequest.x;
211     xwc.y = e->xconfigurerequest.y;
212     xwc.width = e->xconfigurerequest.width;
213     xwc.height = e->xconfigurerequest.height;
214     xwc.border_width = e->xconfigurerequest.border_width;
215     xwc.sibling = e->xconfigurerequest.above;
216     xwc.stack_mode = e->xconfigurerequest.detail;
217     XConfigureWindow(XDisplay(),
218                      e->xconfigurerequest.window,
219                      e->xconfigurerequest.value_mask,
220                      &xwc);
221     break;
222   }
223 
224   case FocusIn: {
225 #ifdef FOCUS_DEBUG
226     printf("FocusIn : window %8lx mode %s detail %s\n",
227            e->xfocus.window, Mode[e->xfocus.mode], Detail[e->xfocus.detail]);
228 #endif
229 
230     if (e->xfocus.mode == NotifyGrab
231         || (e->xfocus.detail != NotifyNonlinearVirtual
232             && e->xfocus.detail != NotifyVirtual)) {
233       /*
234         don't process FocusIns when:
235         1. they are the result of a grab
236         2. the new focus window isn't an ancestor or inferior of the
237         old focus window (NotifyNonlinearVirtual and NotifyVirtual)
238       */
239       break;
240     }
241 
242     BlackboxWindow *win = findWindow(e->xfocus.window);
243     if (!win || win->isFocused())
244       break;
245 
246 #ifdef FOCUS_DEBUG
247     printf("          win %p got focus\n", win);
248 #endif
249     win->setFocused(true);
250     setFocusedWindow(win);
251 
252     /*
253       set the event window to None.  when the FocusOut event handler calls
254       this function recursively, it uses this as an indication that focus
255       has moved to a known window.
256     */
257     e->xfocus.window = None;
258 
259     break;
260   }
261 
262   case FocusOut: {
263 #ifdef FOCUS_DEBUG
264     printf("FocusOut: window %8lx mode %s detail %s\n",
265            e->xfocus.window, Mode[e->xfocus.mode], Detail[e->xfocus.detail]);
266 #endif
267 
268     if (e->xfocus.mode == NotifyGrab
269         || (e->xfocus.detail != NotifyNonlinearVirtual
270             && e->xfocus.detail != NotifyVirtual)) {
271       /*
272         don't process FocusOuts when:
273         1. they are the result of a grab
274         2. the new focus window isn't an ancestor or inferior of the
275         old focus window (NotifyNonlinearVirtual and NotifyNonlinearVirtual)
276       */
277       break;
278     }
279 
280     BlackboxWindow *win = findWindow(e->xfocus.window);
281     if (!win || !win->isFocused())
282       break;
283 
284     bool lost_focus = true; // did the window really lose focus?
285     bool no_focus = true;   // did another window get focus?
286 
287     XEvent event;
288     if (XCheckIfEvent(XDisplay(), &event, scanForFocusIn, NULL)) {
289       process_event(&event);
290 
291       if (event.xfocus.window == None)
292         no_focus = false;
293     } else {
294       XWindowAttributes attr;
295       Window w;
296       int unused;
297       XGetInputFocus(XDisplay(), &w, &unused);
298       if (w != None
299           && XGetWindowAttributes(XDisplay(), w, &attr)
300           && attr.override_redirect) {
301 #ifdef FOCUS_DEBUG
302         printf("          focused moved to an override_redirect window\n");
303 #endif
304         lost_focus = (e->xfocus.mode == NotifyNormal);
305       }
306     }
307 
308     if (lost_focus) {
309 #ifdef FOCUS_DEBUG
310       printf("          win %p lost focus\n", win);
311 #endif
312       win->setFocused(false);
313 
314       if (no_focus) {
315 #ifdef FOCUS_DEBUG
316         printf("          no window has focus\n");
317 #endif
318         setFocusedWindow(0);
319       }
320     }
321 
322     break;
323   }
324 
325   default:
326     // Send the event through the default EventHandlers.
327     bt::Application::process_event(e);
328     break;
329   } // switch
330 }
331 
332 
process_signal(int sig)333 bool Blackbox::process_signal(int sig) {
334   switch (sig) {
335   case SIGHUP:
336     reconfigure();
337     break;
338 
339   case SIGUSR1:
340     reload_rc();
341     break;
342 
343   case SIGUSR2:
344     rereadMenu();
345     break;
346 
347   default:
348     return bt::Application::process_signal(sig);
349   } // switch
350 
351   return true;
352 }
353 
354 
timeout(bt::Timer *)355 void Blackbox::timeout(bt::Timer *) {
356   XrmDatabase new_blackboxrc = (XrmDatabase) 0;
357 
358   std::string style = "session.styleFile: ";
359   style += _resource.styleFilename();
360   XrmPutLineResource(&new_blackboxrc, style.c_str());
361 
362   XrmDatabase old_blackboxrc = XrmGetFileDatabase(_resource.rcFilename());
363 
364   XrmMergeDatabases(new_blackboxrc, &old_blackboxrc);
365   XrmPutFileDatabase(old_blackboxrc, _resource.rcFilename());
366   if (old_blackboxrc) XrmDestroyDatabase(old_blackboxrc);
367 
368   std::for_each(menuTimestamps.begin(), menuTimestamps.end(),
369                 bt::PointerAssassin());
370   menuTimestamps.clear();
371 
372   std::for_each(screen_list, screen_list + screen_list_count,
373                 std::mem_fun(&BScreen::reconfigure));
374 
375   bt::Font::clearCache();
376   bt::PixmapCache::clearCache();
377   bt::Pen::clearCache();
378 
379   // clear the color cache here to allow the pen cache to deallocate
380   // all unused colors
381   bt::Color::clearCache();
382 }
383 
384 
Blackbox(char ** m_argv,const char * dpy_name,const std::string & rc,bool multi_head)385 Blackbox::Blackbox(char **m_argv, const char *dpy_name,
386                    const std::string& rc, bool multi_head)
387   : bt::Application(m_argv[0], dpy_name, multi_head),
388     grab_count(0u), _resource(rc)
389 {
390   if (! XSupportsLocale())
391     fprintf(stderr, "X server does not support locale\n");
392 
393   if (XSetLocaleModifiers("") == NULL)
394     fprintf(stderr, "cannot set locale modifiers\n");
395 
396   argv = m_argv;
397 
398   active_screen = 0;
399   focused_window = (BlackboxWindow *) 0;
400   _ewmh = (bt::EWMH*) 0;
401 
402   init_icccm();
403 
404   if (! multi_head || display().screenCount() == 1)
405     screen_list_count = 1;
406   else
407     screen_list_count = display().screenCount();
408 
409   _resource.load(*this);
410 
411   screen_list = new BScreen*[screen_list_count];
412   unsigned int managed = 0;
413   for (unsigned int i = 0; i < screen_list_count; ++i) {
414     BScreen *screen = new BScreen(this, i);
415 
416     if (! screen->isScreenManaged()) {
417       delete screen;
418       continue;
419     }
420 
421     screen_list[i] = screen;
422     ++managed;
423   }
424 
425   if (managed == 0) {
426     fprintf(stderr, "%s: no managable screens found, exiting...\n",
427             applicationName().c_str());
428     std::exit(3);
429   }
430 
431   screen_list_count = managed;
432 
433   // start with the first managed screen as the active screen
434   setActiveScreen(screen_list[0]);
435 
436   XSynchronize(XDisplay(), false);
437   XSync(XDisplay(), false);
438 
439   timer = new bt::Timer(this, this);
440   timer->setTimeout(0l);
441 }
442 
443 
~Blackbox(void)444 Blackbox::~Blackbox(void) {
445   std::for_each(screen_list, screen_list + screen_list_count,
446                 bt::PointerAssassin());
447 
448   delete [] screen_list;
449   std::for_each(menuTimestamps.begin(), menuTimestamps.end(),
450                 bt::PointerAssassin());
451 
452   delete timer;
453   delete _ewmh;
454 }
455 
456 
XGrabServer(void)457 void Blackbox::XGrabServer(void) {
458   if (grab_count++ == 0)
459     ::XGrabServer(XDisplay());
460 }
461 
462 
XUngrabServer(void)463 void Blackbox::XUngrabServer(void) {
464   if (--grab_count == 0)
465     ::XUngrabServer(XDisplay());
466 }
467 
468 
findScreen(Window window) const469 BScreen *Blackbox::findScreen(Window window) const {
470   for (unsigned int i = 0; i < screen_list_count; ++i)
471     if (screen_list[i]->screenInfo().rootWindow() == window)
472       return screen_list[i];
473   return 0;
474 }
475 
476 
setActiveScreen(BScreen * screen)477 void Blackbox::setActiveScreen(BScreen *screen) {
478   if (active_screen && active_screen == screen) // nothing to do
479     return;
480 
481   assert(screen != 0);
482   active_screen = screen;
483 
484   // install screen colormap
485   XInstallColormap(XDisplay(), active_screen->screenInfo().colormap());
486 
487   if (! focused_window || focused_window->screen() != active_screen)
488     setFocusedWindow(0);
489 }
490 
491 
screenNumber(unsigned int n)492 BScreen* Blackbox::screenNumber(unsigned int n) {
493   assert(n < screen_list_count);
494   return screen_list[n];
495 }
496 
497 
findWindow(Window window) const498 BlackboxWindow *Blackbox::findWindow(Window window) const {
499   WindowLookup::const_iterator it = windowSearchList.find(window);
500   if (it != windowSearchList.end())
501     return it->second;
502   return 0;
503 }
504 
505 
insertWindow(Window window,BlackboxWindow * data)506 void Blackbox::insertWindow(Window window, BlackboxWindow *data)
507 { windowSearchList.insert(WindowLookupPair(window, data)); }
508 
509 
removeWindow(Window window)510 void Blackbox::removeWindow(Window window)
511 { windowSearchList.erase(window); }
512 
513 
findWindowGroup(Window window) const514 BWindowGroup *Blackbox::findWindowGroup(Window window) const {
515   GroupLookup::const_iterator it = groupSearchList.find(window);
516   if (it != groupSearchList.end())
517     return it->second;
518   return 0;
519 }
520 
521 
insertWindowGroup(Window window,BWindowGroup * data)522 void Blackbox::insertWindowGroup(Window window, BWindowGroup *data)
523 { groupSearchList.insert(GroupLookupPair(window, data)); }
524 
525 
removeWindowGroup(Window window)526 void Blackbox::removeWindowGroup(Window window)
527 { groupSearchList.erase(window); }
528 
529 
setFocusedWindow(BlackboxWindow * win)530 void Blackbox::setFocusedWindow(BlackboxWindow *win) {
531   if (focused_window && focused_window == win) // nothing to do
532     return;
533 
534   if (win && !win->isIconic()) {
535     // the active screen is the one with the newly focused window...
536     active_screen = win->screen();
537     focused_window = win;
538   } else {
539     // nothing has focus
540     focused_window = 0;
541     assert(active_screen != 0);
542     XSetInputFocus(XDisplay(), active_screen->noFocusWindow(),
543                    RevertToPointerRoot, XTime());
544   }
545 
546   updateActiveWindow();
547 }
548 
549 
restart(const std::string & prog)550 void Blackbox::restart(const std::string &prog) {
551   setRunState(bt::Application::SHUTDOWN);
552 
553   /*
554     since we don't allow control to return to the eventloop, we need
555     to call shutdown() explicitly
556   */
557   shutdown();
558 
559   if (! prog.empty()) {
560     putenv(const_cast<char *>
561            (display().screenInfo(0).displayString().c_str()));
562     execlp(prog.c_str(), prog.c_str(), NULL);
563     perror(prog.c_str());
564   }
565 
566   // fall back in case the above execlp doesn't work
567   execvp(argv[0], argv);
568   std::string name = bt::basename(argv[0]);
569   execvp(name.c_str(), argv);
570 }
571 
572 
reconfigure(void)573 void Blackbox::reconfigure(void) {
574   if (! timer->isTiming())
575     timer->start();
576 }
577 
578 
saveMenuFilename(const std::string & filename)579 void Blackbox::saveMenuFilename(const std::string& filename) {
580   assert(!filename.empty());
581   bool found = false;
582 
583   MenuTimestampList::iterator it = menuTimestamps.begin();
584   for (; it != menuTimestamps.end() && !found; ++it)
585     found = (*it)->filename == filename;
586   if (found)
587     return;
588 
589   struct stat buf;
590   if (stat(filename.c_str(), &buf) != 0)
591     return; // file doesn't exist
592 
593   MenuTimestamp *ts = new MenuTimestamp;
594   ts->filename = filename;
595   ts->timestamp = buf.st_ctime;
596   menuTimestamps.push_back(ts);
597 }
598 
599 
checkMenu(void)600 void Blackbox::checkMenu(void) {
601   bool reread = false;
602   MenuTimestampList::iterator it = menuTimestamps.begin();
603   for(; it != menuTimestamps.end(); ++it) {
604     MenuTimestamp *tmp = *it;
605     struct stat buf;
606 
607     if (! stat(tmp->filename.c_str(), &buf)) {
608       if (tmp->timestamp != buf.st_ctime)
609         reread = true;
610     } else {
611       reread = true;
612     }
613   }
614 
615   if (reread)
616     rereadMenu();
617 }
618 
619 
rereadMenu(void)620 void Blackbox::rereadMenu(void) {
621   std::for_each(menuTimestamps.begin(), menuTimestamps.end(),
622                 bt::PointerAssassin());
623   menuTimestamps.clear();
624 
625   std::for_each(screen_list, screen_list + screen_list_count,
626                 std::mem_fun(&BScreen::rereadMenu));
627 }
628