]> icculus.org git repositories - mikachu/openbox.git/blob - util/epist/screen.cc
make lastActiveWindow more intelligent
[mikachu/openbox.git] / util / epist / screen.cc
1 // -*- mode: C++; indent-tabs-mode: nil; -*-
2 // screen.cc for Epistophy - a key handler for NETWM/EWMH window managers.
3 // Copyright (c) 2002 - 2002 Ben Jansens <ben at orodu.net>
4 //
5 // Permission is hereby granted, free of charge, to any person obtaining a
6 // copy of this software and associated documentation files (the "Software"),
7 // to deal in the Software without restriction, including without limitation
8 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 // and/or sell copies of the Software, and to permit persons to whom the
10 // Software is furnished to do so, subject to the following conditions:
11 //
12 // The above copyright notice and this permission notice shall be included in
13 // all copies or substantial portions of the Software.
14 //
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 // DEALINGS IN THE SOFTWARE.
22
23 #ifdef    HAVE_CONFIG_H
24 #  include "../../config.h"
25 #endif // HAVE_CONFIG_H
26
27 extern "C" {
28 #ifdef    HAVE_STDIO_H
29 #  include <stdio.h>
30 #endif // HAVE_STDIO_H
31
32 #ifdef    HAVE_UNISTD_H
33 #  include <sys/types.h>
34 #  include <unistd.h>
35 #endif // HAVE_UNISTD_H
36 }
37
38 #include <iostream>
39 #include <string>
40
41 using std::cout;
42 using std::endl;
43 using std::hex;
44 using std::dec;
45 using std::string;
46
47 #include "../../src/BaseDisplay.hh"
48 #include "../../src/XAtom.hh"
49 #include "screen.hh"
50 #include "epist.hh"
51
52
53 screen::screen(epist *epist, int number) 
54   : _clients(epist->clientsList()),
55     _active(epist->activeWindow()) {
56   _epist = epist;
57   _xatom = _epist->xatom();
58   _last_active = _clients.end();
59   _number = number;
60   _info = _epist->getScreenInfo(_number);
61   _root = _info->getRootWindow();
62   
63   cout << "root window on screen " << _number << ": 0x" << hex << _root << 
64     dec << endl;
65   
66   // find a window manager supporting NETWM, waiting for it to load if we must
67   int count = 20;  // try for 20 seconds
68   _managed = false;
69   while (! (_epist->doShutdown() || _managed || count <= 0)) {
70     if (! (_managed = findSupportingWM()))
71       usleep(1000);
72     --count;
73   }
74   if (_managed)
75     cout << "Found compatible window manager '" << _wm_name << "' for screen "
76       << _number << ".\n";
77   else {
78     cout << "Unable to find a compatible window manager for screen " <<
79       _number << ".\n";
80     return;
81   }
82  
83   XSelectInput(_epist->getXDisplay(), _root, PropertyChangeMask);
84 }
85
86 screen::~screen() {
87   if (_managed)
88     XSelectInput(_epist->getXDisplay(), _root, None);
89 }
90
91
92 bool screen::findSupportingWM() {
93   Window support_win;
94   if (! _xatom->getValue(_root, XAtom::net_supporting_wm_check, XAtom::window,
95                          support_win) || support_win == None)
96     return false;
97
98   string title;
99   _xatom->getValue(support_win, XAtom::net_wm_name, XAtom::utf8, title);
100   _wm_name = title;
101   return true;
102 }
103
104
105 XWindow *screen::findWindow(const XEvent &e) const {
106   assert(_managed);
107
108   WindowList::const_iterator it, end = _clients.end();
109   for (it = _clients.begin(); it != end; ++it)
110     if (**it == e.xany.window)
111       break;
112   if(it == end)
113     return 0;
114   return *it;
115 }
116
117
118 void screen::processEvent(const XEvent &e) {
119   assert(_managed);
120   assert(e.xany.window == _root);
121
122   switch (e.type) {
123   case PropertyNotify:
124     // root window
125     if (e.xproperty.atom == _xatom->getAtom(XAtom::net_number_of_desktops))
126       updateNumDesktops();
127     if (e.xproperty.atom == _xatom->getAtom(XAtom::net_current_desktop))
128       updateActiveDesktop();
129     if (e.xproperty.atom == _xatom->getAtom(XAtom::net_active_window))
130       updateActiveWindow();
131     if (e.xproperty.atom == _xatom->getAtom(XAtom::net_client_list)) {
132       // catch any window unmaps first
133       XEvent ev;
134       if (XCheckTypedWindowEvent(_epist->getXDisplay(), e.xany.window,
135                                  DestroyNotify, &ev) ||
136           XCheckTypedWindowEvent(_epist->getXDisplay(), e.xany.window,
137                                  UnmapNotify, &ev)) {
138         processEvent(ev);
139       }
140
141       updateClientList();
142     }
143     break;
144   case KeyPress:
145     handleKeypress(e);
146     break;
147   }
148 }
149
150 void screen::handleKeypress(const XEvent &e) {
151   int scrolllockMask, numlockMask;
152
153   ActionList::const_iterator it = _epist->actions().begin();
154   ActionList::const_iterator end = _epist->actions().end();
155
156   _epist->getLockModifiers(numlockMask, scrolllockMask);
157   
158   for (; it != end; ++it) {
159     unsigned int state = e.xkey.state & ~(LockMask|scrolllockMask|numlockMask);
160     
161     if (e.xkey.keycode == it->keycode() &&
162         state == it->modifierMask()) {
163       switch (it->type()) {
164       case Action::nextScreen:
165         _epist->cycleScreen(_number, true);
166         return;
167
168       case Action::prevScreen:
169         _epist->cycleScreen(_number, false);
170         return;
171
172       case Action::nextWorkspace:
173         cycleWorkspace(true);
174         return;
175
176       case Action::prevWorkspace:
177         cycleWorkspace(false);
178         return;
179
180       case Action::nextWindow:
181         cycleWindow(true);
182         return;
183
184       case Action::prevWindow:
185         cycleWindow(false);
186         return;
187
188       case Action::nextWindowOnAllWorkspaces:
189         cycleWindow(true, false, true);
190         return;
191
192       case Action::prevWindowOnAllWorkspaces:
193         cycleWindow(false, false, true);
194         return;
195
196       case Action::nextWindowOnAllScreens:
197         cycleWindow(true, true);
198         return;
199
200       case Action::prevWindowOnAllScreens:
201         cycleWindow(false, true);
202         return;
203
204       case Action::nextWindowOfClass:
205         cycleWindow(true, false, false, true, it->string());
206         return;
207
208       case Action::prevWindowOfClass:
209         cycleWindow(false, false, false, true, it->string());
210         return;
211
212       case Action::nextWindowOfClassOnAllWorkspaces:
213         cycleWindow(true, false, true, true, it->string());
214         return;
215
216       case Action::prevWindowOfClassOnAllWorkspaces:
217         cycleWindow(false, false, true, true, it->string());
218         return;
219
220       case Action::changeWorkspace:
221         changeWorkspace(it->number());
222         return;
223
224       case Action::execute:
225         execCommand(it->string());
226         return;
227
228       default:
229         break;
230       }
231
232       // these actions require an active window
233       if (_active != _clients.end()) {
234         XWindow *window = *_active;
235
236         switch (it->type()) {
237         case Action::iconify:
238           window->iconify();
239           return;
240
241         case Action::close:
242           window->close();
243           return;
244
245         case Action::raise:
246           window->raise();
247           return;
248
249         case Action::lower:
250           window->lower();
251           return;
252
253         case Action::sendToWorkspace:
254           window->sendTo(it->number());
255           return;
256
257         case Action::toggleomnipresent:
258           if (window->desktop() == 0xffffffff)
259             window->sendTo(_active_desktop);
260           else
261             window->sendTo(0xffffffff);
262           return;
263
264         case Action::moveWindowUp:
265           window->move(window->x(), window->y() - it->number());
266           return;
267       
268         case Action::moveWindowDown:
269           window->move(window->x(), window->y() + it->number());
270           return;
271       
272         case Action::moveWindowLeft:
273           window->move(window->x() - it->number(), window->y());
274           return;
275       
276         case Action::moveWindowRight:
277           window->move(window->x() + it->number(), window->y());
278           return;
279       
280         case Action::resizeWindowWidth:
281           window->resize(window->width() + it->number(), window->height());
282           return;
283       
284         case Action::resizeWindowHeight:
285           window->resize(window->width(), window->height() + it->number());
286           return;
287       
288         case Action::toggleshade:
289           window->shade(! window->shaded());
290           return;
291       
292         case Action::toggleMaximizeHorizontal:
293           window->toggleMaximize(XWindow::Max_Horz);
294           return;
295       
296         case Action::toggleMaximizeVertical:
297           window->toggleMaximize(XWindow::Max_Vert);
298           return;
299       
300         case Action::toggleMaximizeFull:
301           window->toggleMaximize(XWindow::Max_Full);
302           return;
303       
304         default:
305           assert(false);  // unhandled action type!
306           break;
307         }
308       }
309     }
310   }
311 }
312
313 // do we want to add this window to our list?
314 bool screen::doAddWindow(Window window) const {
315   assert(_managed);
316
317   Atom type;
318   if (! _xatom->getValue(window, XAtom::net_wm_window_type, XAtom::atom,
319                          type))
320     return True;
321
322   if (type == _xatom->getAtom(XAtom::net_wm_window_type_dock) ||
323       type == _xatom->getAtom(XAtom::net_wm_window_type_menu))
324     return False;
325
326   return True;
327 }
328
329
330 void screen::updateEverything() {
331   updateNumDesktops();
332   updateActiveDesktop();
333   updateClientList();
334   updateActiveWindow();
335 }
336
337
338 void screen::updateNumDesktops() {
339   assert(_managed);
340
341   if (! _xatom->getValue(_root, XAtom::net_number_of_desktops, XAtom::cardinal,
342                          (unsigned long)_num_desktops))
343     _num_desktops = 1;  // assume that there is at least 1 desktop!
344 }
345
346
347 void screen::updateActiveDesktop() {
348   assert(_managed);
349
350   if (! _xatom->getValue(_root, XAtom::net_current_desktop, XAtom::cardinal,
351                          (unsigned long)_active_desktop))
352     _active_desktop = 0;  // there must be at least one desktop, and it must
353                           // be the current one
354 }
355
356
357 void screen::updateClientList() {
358   assert(_managed);
359
360   WindowList::iterator insert_point = _active;
361   if (insert_point != _clients.end())
362     ++insert_point; // get to the item client the focused client
363   
364   // get the client list from the root window
365   Window *rootclients = 0;
366   unsigned long num = (unsigned) -1;
367   if (! _xatom->getValue(_root, XAtom::net_client_list, XAtom::window, num,
368                          &rootclients))
369     num = 0;
370
371   WindowList::iterator it;
372   const WindowList::iterator end = _clients.end();
373   unsigned long i;
374   
375   // insert new clients after the active window
376   for (i = 0; i < num; ++i) {
377     for (it = _clients.begin(); it != end; ++it)
378       if (**it == rootclients[i])
379         break;
380     if (it == end) {  // didn't already exist
381       if (doAddWindow(rootclients[i])) {
382 //        cout << "Added window: 0x" << hex << rootclients[i] << dec << endl;
383         _clients.insert(insert_point, new XWindow(_epist, this,
384                                                   rootclients[i]));
385       }
386     }
387   }
388
389   // remove clients that no longer exist (that belong to this screen)
390   for (it = _clients.begin(); it != end;) {
391     WindowList::iterator it2 = it;
392     ++it;
393
394     // is on another screen?
395     if ((*it2)->getScreen() != this)
396       continue;
397
398     for (i = 0; i < num; ++i)
399       if (**it2 == rootclients[i])
400         break;
401     if (i == num)  { // no longer exists
402 //      cout << "Removed window: 0x" << hex << (*it2)->window() << dec << endl;
403       // watch for the active and last-active window
404       if (it2 == _active)
405         _active = _clients.end();
406       if (it2 == _last_active)
407         _last_active = _clients.end();
408       delete *it2;
409       _clients.erase(it2);
410     }
411   }
412
413   if (rootclients) delete [] rootclients;
414 }
415
416
417 const XWindow *screen::lastActiveWindow() const {
418   if (_last_active != _clients.end())
419     return *_last_active;
420
421   // find a window if one exists
422   WindowList::const_iterator it, end = _clients.end();
423   for (it = _clients.begin(); it != end; ++it)
424     if ((*it)->getScreen() == this && ! (*it)->iconic() &&
425         ((*it)->desktop() == 0xffffffff || (*it)->desktop() == _active_desktop))
426       return *it;
427
428   // no windows on this screen
429   return 0;
430 }
431
432
433 void screen::updateActiveWindow() {
434   assert(_managed);
435
436   Window a = None;
437   _xatom->getValue(_root, XAtom::net_active_window, XAtom::window, a);
438   
439   WindowList::iterator it, end = _clients.end();
440   for (it = _clients.begin(); it != end; ++it) {
441     if (**it == a) {
442       if ((*it)->getScreen() != this)
443         return;
444       break;
445     }
446   }
447   _active = it;
448   if (it != end)
449     _last_active = it;
450
451 /*  cout << "Active window is now: ";
452   if (_active == _clients.end()) cout << "None\n";
453   else cout << "0x" << hex << (*_active)->window() << dec << endl;
454 */
455 }
456
457
458 void screen::execCommand(const string &cmd) const {
459   pid_t pid;
460   if ((pid = fork()) == 0) {
461     extern char **environ;
462
463     char *const argv[] = {
464       "sh",
465       "-c",
466       const_cast<char *>(cmd.c_str()),
467       0
468     };
469     // make the command run on the correct screen
470     if (putenv(const_cast<char*>(_info->displayString().c_str()))) {
471       cout << "warning: couldn't set environment variable 'DISPLAY'\n";
472       perror("putenv()");
473     }
474     execve("/bin/sh", argv, environ);
475     exit(127);
476   } else if (pid == -1) {
477     cout << _epist->getApplicationName() <<
478       ": Could not fork a process for executing a command\n";
479   }
480 }
481
482
483 void screen::cycleWindow(const bool forward, const bool allscreens,
484                          const bool alldesktops, const bool sameclass,
485                          const string &cn) const {
486   assert(_managed);
487
488   if (_clients.empty()) return;
489   
490   string classname(cn);
491   if (sameclass && classname.empty() && _active != _clients.end())
492     classname = (*_active)->appClass();
493
494   WindowList::const_iterator target = _active,
495                              first = _active,
496                              begin = _clients.begin(),
497                              end = _clients.end();
498  
499   do {
500     if (forward) {
501       if (target == end) {
502         target = begin;
503       } else {
504         ++target;
505         if (target == end)
506           target = begin;
507       }
508     } else {
509       if (target == begin)
510         target = end;
511       --target;
512     }
513
514     // must be no window to focus
515     if (target == first)
516       return;
517   } while ((*target)->iconic() ||
518            (! allscreens && (*target)->getScreen() != this) ||
519            (! alldesktops &&
520             (*target)->desktop() != _active_desktop &&
521             (*target)->desktop() != 0xffffffff) ||
522            (sameclass && ! classname.empty() &&
523             (*target)->appClass() != classname));
524   
525   if (target != _clients.end())
526     (*target)->focus();
527 }
528
529
530 void screen::cycleWorkspace(const bool forward, const bool loop) const {
531   assert(_managed);
532
533   unsigned int destination = _active_desktop;
534
535   if (forward) {
536     if (destination < _num_desktops - 1)
537       ++destination;
538     else if (loop)
539       destination = 0;
540   } else {
541     if (destination > 0)
542       --destination;
543     else if (loop)
544       destination = _num_desktops - 1;
545   }
546
547   if (destination != _active_desktop) 
548     changeWorkspace(destination);
549 }
550
551
552 void screen::changeWorkspace(const int num) const {
553   assert(_managed);
554
555   _xatom->sendClientMessage(_root, XAtom::net_current_desktop, _root, num);
556 }
557
558 void screen::grabKey(const KeyCode keyCode, const int modifierMask) const {
559
560   Display *display = _epist->getXDisplay();
561   int numlockMask, scrolllockMask;
562
563   _epist->getLockModifiers(numlockMask, scrolllockMask);
564
565   XGrabKey(display, keyCode, modifierMask,
566            _root, True, GrabModeAsync, GrabModeAsync);
567   XGrabKey(display, keyCode, 
568            modifierMask|LockMask,
569            _root, True, GrabModeAsync, GrabModeAsync);
570   XGrabKey(display, keyCode, 
571            modifierMask|scrolllockMask,
572            _root, True, GrabModeAsync, GrabModeAsync);
573   XGrabKey(display, keyCode, 
574            modifierMask|numlockMask,
575            _root, True, GrabModeAsync, GrabModeAsync);
576     
577   XGrabKey(display, keyCode, 
578            modifierMask|LockMask|scrolllockMask,
579            _root, True, GrabModeAsync, GrabModeAsync);
580   XGrabKey(display, keyCode, 
581            modifierMask|scrolllockMask|numlockMask,
582            _root, True, GrabModeAsync, GrabModeAsync);
583   XGrabKey(display, keyCode, 
584            modifierMask|numlockMask|LockMask,
585            _root, True, GrabModeAsync, GrabModeAsync);
586     
587   XGrabKey(display, keyCode, 
588            modifierMask|numlockMask|LockMask|scrolllockMask,
589            _root, True, GrabModeAsync, GrabModeAsync);
590 }