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