]> icculus.org git repositories - dana/openbox.git/blob - util/epist/screen.cc
toggleGrabs action added
[dana/openbox.git] / util / epist / screen.cc
1 // -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*-
2 // screen.cc for Epistrophy - 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 #include "config.hh"
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   _grabbed = true;
63
64   // find a window manager supporting NETWM, waiting for it to load if we must
65   int count = 20;  // try for 20 seconds
66   _managed = false;
67   while (! (_epist->doShutdown() || _managed || count <= 0)) {
68     if (! (_managed = findSupportingWM()))
69       sleep(1);
70     --count;
71   }
72   if (_managed)
73     cout << "Found compatible window manager '" << _wm_name << "' for screen "
74          << _number << ".\n";
75   else {
76     cout << "Unable to find a compatible window manager for screen " <<
77       _number << ".\n";
78     return;
79   }
80  
81   XSelectInput(_epist->getXDisplay(), _root, PropertyChangeMask);
82 }
83
84 screen::~screen() {
85   if (_managed)
86     XSelectInput(_epist->getXDisplay(), _root, None);
87 }
88
89
90 bool screen::findSupportingWM() {
91   Window support_win;
92   if (! _xatom->getValue(_root, XAtom::net_supporting_wm_check, XAtom::window,
93                          support_win) || support_win == None)
94     return false;
95
96   string title;
97   _xatom->getValue(support_win, XAtom::net_wm_name, XAtom::utf8, title);
98   _wm_name = title;
99   return true;
100 }
101
102
103 XWindow *screen::findWindow(const XEvent &e) const {
104   assert(_managed);
105
106   WindowList::const_iterator it, end = _clients.end();
107   for (it = _clients.begin(); it != end; ++it)
108     if (**it == e.xany.window)
109       break;
110   if(it == end)
111     return 0;
112   return *it;
113 }
114
115
116 void screen::processEvent(const XEvent &e) {
117   assert(_managed);
118   assert(e.xany.window == _root);
119
120   switch (e.type) {
121   case PropertyNotify:
122     // root window
123     if (e.xproperty.atom == _xatom->getAtom(XAtom::net_number_of_desktops))
124       updateNumDesktops();
125     else if (e.xproperty.atom == _xatom->getAtom(XAtom::net_current_desktop))
126       updateActiveDesktop();
127     else if (e.xproperty.atom == _xatom->getAtom(XAtom::net_active_window))
128       updateActiveWindow();
129     else if (e.xproperty.atom == _xatom->getAtom(XAtom::net_client_list)) {
130       // catch any window unmaps first
131       XEvent ev;
132       if (XCheckTypedWindowEvent(_epist->getXDisplay(), e.xany.window,
133                                  DestroyNotify, &ev) ||
134           XCheckTypedWindowEvent(_epist->getXDisplay(), e.xany.window,
135                                  UnmapNotify, &ev)) {
136         processEvent(ev);
137       }
138
139       updateClientList();
140     }
141     break;
142   case KeyPress:
143     handleKeypress(e);
144     break;
145   }
146 }
147
148 void screen::handleKeypress(const XEvent &e) {
149   int scrolllockMask, numlockMask;
150   _epist->getLockModifiers(numlockMask, scrolllockMask);
151   
152   // Mask out the lock modifiers. We want our keys to always work
153   // This should be made an option
154   unsigned int state = e.xkey.state & ~(LockMask|scrolllockMask|numlockMask);
155   keytree &ktree = _epist->getKeyTree();
156   const Action *it = ktree.getAction(e, state, this);
157
158   if (!it)
159     return;
160
161   switch (it->type()) {
162   case Action::nextScreen:
163     _epist->cycleScreen(_number, true);
164     return;
165
166   case Action::prevScreen:
167     _epist->cycleScreen(_number, false);
168     return;
169
170   case Action::nextWorkspace:
171     cycleWorkspace(true, it->number() != 0 ? it->number(): 1);
172     return;
173
174   case Action::prevWorkspace:
175     cycleWorkspace(false, it->number() != 0 ? it->number(): 1);
176     return;
177
178   case Action::nextWindow:
179     
180     cycleWindow(true, it->number() != 0 ? it->number(): 1);
181     return;
182
183   case Action::prevWindow:
184     cycleWindow(false, it->number() != 0 ? it->number(): 1);
185     return;
186
187   case Action::nextWindowOnAllWorkspaces:
188     cycleWindow(true, it->number() != 0 ? it->number(): 1,  false, true);
189     return;
190
191   case Action::prevWindowOnAllWorkspaces:
192     cycleWindow(false, it->number() != 0 ? it->number(): 1, false, true);
193     return;
194
195   case Action::nextWindowOnAllScreens:
196     cycleWindow(true, it->number() != 0 ? it->number(): 1, true);
197     return;
198
199   case Action::prevWindowOnAllScreens:
200     cycleWindow(false, it->number() != 0 ? it->number(): 1, true);
201     return;
202
203   case Action::nextWindowOfClass:
204     cycleWindow(true, it->number() != 0 ? it->number(): 1,
205                 false, false, true, it->string());
206     return;
207
208   case Action::prevWindowOfClass:
209     cycleWindow(false, it->number() != 0 ? it->number(): 1,
210                 false, false, true, it->string());
211     return;
212       
213   case Action::nextWindowOfClassOnAllWorkspaces:
214     cycleWindow(true, it->number() != 0 ? it->number(): 1,
215                 false, true, true, it->string());
216     return;
217       
218   case Action::prevWindowOfClassOnAllWorkspaces:
219     cycleWindow(false, it->number() != 0 ? it->number(): 1,
220                 false, true, true, it->string());
221     return;
222
223   case Action::changeWorkspace:
224     changeWorkspace(it->number());
225     return;
226
227   case Action::upWorkspace:
228     changeWorkspaceVert(-1);
229     return;
230
231   case Action::downWorkspace:
232     changeWorkspaceVert(1);
233     return;
234
235   case Action::leftWorkspace:
236     changeWorkspaceHorz(-1);
237     return;
238
239   case Action::rightWorkspace:
240     changeWorkspaceHorz(1);
241     return;
242
243   case Action::execute:
244     execCommand(it->string());
245     return;
246
247   case Action::showRootMenu:
248     _xatom->sendClientMessage(rootWindow(), XAtom::openbox_show_root_menu,
249                               None);
250     return;
251
252   case Action::showWorkspaceMenu:
253     _xatom->sendClientMessage(rootWindow(), XAtom::openbox_show_workspace_menu,
254                               None);
255     return;
256
257   case Action::toggleGrabs: {
258     if (_grabbed) {
259       ktree.ungrabDefaults(this);
260       _grabbed = false;
261     } else {
262       ktree.grabDefaults(this);
263       _grabbed = true;
264     }
265     return;
266   }
267
268   default:
269     break;
270   }
271
272   // these actions require an active window
273   if (_active != _clients.end()) {
274     XWindow *window = *_active;
275       
276     switch (it->type()) {
277     case Action::iconify:
278       window->iconify();
279       return;
280
281     case Action::close:
282       window->close();
283       return;
284
285     case Action::raise:
286       window->raise();
287       return;
288
289     case Action::lower:
290       window->lower();
291       return;
292
293     case Action::sendToWorkspace:
294       window->sendTo(it->number());
295       return;
296
297     case Action::toggleomnipresent:
298       if (window->desktop() == 0xffffffff)
299         window->sendTo(_active_desktop);
300       else
301         window->sendTo(0xffffffff);
302       return;
303
304     case Action::moveWindowUp:
305       window->move(window->x(), window->y() -
306                    (it->number() != 0 ? it->number(): 1));
307       return;
308       
309     case Action::moveWindowDown:
310       window->move(window->x(), window->y() +
311                    (it->number() != 0 ? it->number(): 1));
312       return;
313       
314     case Action::moveWindowLeft:
315       window->move(window->x() - (it->number() != 0 ? it->number(): 1),
316                    window->y());
317       return;
318       
319     case Action::moveWindowRight:
320       window->move(window->x() + (it->number() != 0 ? it->number(): 1),
321                    window->y());
322       return;
323       
324     case Action::resizeWindowWidth:
325       window->resizeRel(it->number(), 0);
326       return;
327       
328     case Action::resizeWindowHeight:
329       window->resizeRel(0, it->number());
330       return;
331       
332     case Action::toggleshade:
333       window->shade(! window->shaded());
334       return;
335       
336     case Action::toggleMaximizeHorizontal:
337       window->toggleMaximize(XWindow::Max_Horz);
338       return;
339       
340     case Action::toggleMaximizeVertical:
341       window->toggleMaximize(XWindow::Max_Vert);
342       return;
343       
344     case Action::toggleMaximizeFull:
345       window->toggleMaximize(XWindow::Max_Full);
346       return;
347
348     case Action::toggleDecorations:
349       window->decorate(! window->decorated());
350       return;
351       
352     default:
353       assert(false);  // unhandled action type!
354       break;
355     }
356   }
357 }
358
359 // do we want to add this window to our list?
360 bool screen::doAddWindow(Window window) const {
361   assert(_managed);
362
363   Atom type;
364   if (! _xatom->getValue(window, XAtom::net_wm_window_type, XAtom::atom,
365                          type))
366     return True;
367
368   if (type == _xatom->getAtom(XAtom::net_wm_window_type_dock) ||
369       type == _xatom->getAtom(XAtom::net_wm_window_type_menu))
370     return False;
371
372   return True;
373 }
374
375
376 void screen::updateEverything() {
377   updateNumDesktops();
378   updateActiveDesktop();
379   updateClientList();
380   updateActiveWindow();
381 }
382
383
384 void screen::updateNumDesktops() {
385   assert(_managed);
386
387   if (! _xatom->getValue(_root, XAtom::net_number_of_desktops, XAtom::cardinal,
388                          (unsigned long)_num_desktops))
389     _num_desktops = 1;  // assume that there is at least 1 desktop!
390 }
391
392
393 void screen::updateActiveDesktop() {
394   assert(_managed);
395
396   if (! _xatom->getValue(_root, XAtom::net_current_desktop, XAtom::cardinal,
397                          (unsigned long)_active_desktop))
398     _active_desktop = 0;  // there must be at least one desktop, and it must
399                           // be the current one
400 }
401
402
403 void screen::updateClientList() {
404   assert(_managed);
405
406   WindowList::iterator insert_point = _active;
407   if (insert_point != _clients.end())
408     ++insert_point; // get to the item client the focused client
409   
410   // get the client list from the root window
411   Window *rootclients = 0;
412   unsigned long num = (unsigned) -1;
413   if (! _xatom->getValue(_root, XAtom::net_client_list, XAtom::window, num,
414                          &rootclients))
415     num = 0;
416
417   WindowList::iterator it;
418   const WindowList::iterator end = _clients.end();
419   unsigned long i;
420   
421   // insert new clients after the active window
422   for (i = 0; i < num; ++i) {
423     for (it = _clients.begin(); it != end; ++it)
424       if (**it == rootclients[i])
425         break;
426     if (it == end) {  // didn't already exist
427       if (doAddWindow(rootclients[i])) {
428         //        cout << "Added window: 0x" << hex << rootclients[i] << dec << endl;
429         _clients.insert(insert_point, new XWindow(_epist, this,
430                                                   rootclients[i]));
431       }
432     }
433   }
434
435   // remove clients that no longer exist (that belong to this screen)
436   for (it = _clients.begin(); it != end;) {
437     WindowList::iterator it2 = it;
438     ++it;
439
440     // is on another screen?
441     if ((*it2)->getScreen() != this)
442       continue;
443
444     for (i = 0; i < num; ++i)
445       if (**it2 == rootclients[i])
446         break;
447     if (i == num)  { // no longer exists
448       //      cout << "Removed window: 0x" << hex << (*it2)->window() << dec << endl;
449       // watch for the active and last-active window
450       if (it2 == _active)
451         _active = _clients.end();
452       if (it2 == _last_active)
453         _last_active = _clients.end();
454       delete *it2;
455       _clients.erase(it2);
456     }
457   }
458
459   if (rootclients) delete [] rootclients;
460 }
461
462
463 const XWindow *screen::lastActiveWindow() const {
464   if (_last_active != _clients.end())
465     return *_last_active;
466
467   // find a window if one exists
468   WindowList::const_iterator it, end = _clients.end();
469   for (it = _clients.begin(); it != end; ++it)
470     if ((*it)->getScreen() == this && ! (*it)->iconic() &&
471         ((*it)->desktop() == 0xffffffff || (*it)->desktop() == _active_desktop))
472       return *it;
473
474   // no windows on this screen
475   return 0;
476 }
477
478
479 void screen::updateActiveWindow() {
480   assert(_managed);
481
482   Window a = None;
483   _xatom->getValue(_root, XAtom::net_active_window, XAtom::window, a);
484   
485   WindowList::iterator it, end = _clients.end();
486   for (it = _clients.begin(); it != end; ++it) {
487     if (**it == a) {
488       if ((*it)->getScreen() != this)
489         return;
490       break;
491     }
492   }
493   _active = it;
494   if (it != end)
495     _last_active = it;
496
497   /*  cout << "Active window is now: ";
498       if (_active == _clients.end()) cout << "None\n";
499       else cout << "0x" << hex << (*_active)->window() << dec << endl;
500   */
501 }
502
503
504 void screen::execCommand(const string &cmd) const {
505   pid_t pid;
506   if ((pid = fork()) == 0) {
507     // make the command run on the correct screen
508     if (putenv(const_cast<char*>(_info->displayString().c_str()))) {
509       cout << "warning: couldn't set environment variable 'DISPLAY'\n";
510       perror("putenv()");
511     }
512     execl("/bin/sh", "sh", "-c", cmd.c_str(), NULL);
513     exit(-1);
514   } else if (pid == -1) {
515     cout << _epist->getApplicationName() <<
516       ": Could not fork a process for executing a command\n";
517   }
518 }
519
520
521 void screen::cycleWindow(const bool forward, const int increment,
522                          const bool allscreens, const bool alldesktops,
523                          const bool sameclass, const string &cn) const {
524   assert(_managed);
525   assert(increment > 0);
526
527   if (_clients.empty()) return;
528
529   string classname(cn);
530   if (sameclass && classname.empty() && _active != _clients.end())
531     classname = (*_active)->appClass();
532
533   WindowList::const_iterator target = _active,
534     begin = _clients.begin(),
535     end = _clients.end();
536
537   const XWindow *t = 0;
538   
539   for (int x = 0; x < increment; ++x) {
540     while (1) {
541       if (forward) {
542         if (target == end) {
543           target = begin;
544         } else {
545           ++target;
546         }
547       } else {
548         if (target == begin)
549           target = end;
550         --target;
551       }
552
553       // must be no window to focus
554       if (target == _active)
555         return;
556
557       // start back at the beginning of the loop
558       if (target == end)
559         continue;
560
561       // determine if this window is invalid for cycling to
562       t = *target;
563       if (t->iconic()) continue;
564       if (! allscreens && t->getScreen() != this) continue;
565       if (! alldesktops && ! (t->desktop() == _active_desktop ||
566                               t->desktop() == 0xffffffff)) continue;
567       if (sameclass && ! classname.empty() &&
568           t->appClass() != classname) continue;
569       if (! t->canFocus()) continue;
570
571       // found a good window so break out of the while, and perhaps continue
572       // with the for loop
573       break;
574     }
575   }
576
577   // phew. we found the window, so focus it.
578   t->focus();
579 }
580
581
582 void screen::cycleWorkspace(const bool forward, const int increment,
583                             const bool loop) const {
584   assert(_managed);
585   assert(increment > 0);
586
587   unsigned int destination = _active_desktop;
588
589   for (int x = 0; x < increment; ++x) {
590     if (forward) {
591       if (destination < _num_desktops - 1)
592         ++destination;
593       else if (loop)
594         destination = 0;
595     } else {
596       if (destination > 0)
597         --destination;
598       else if (loop)
599         destination = _num_desktops - 1;
600     }
601   }
602
603   if (destination != _active_desktop) 
604     changeWorkspace(destination);
605 }
606
607
608 void screen::changeWorkspace(const int num) const {
609   assert(_managed);
610
611   _xatom->sendClientMessage(_root, XAtom::net_current_desktop, _root, num);
612 }
613
614 void screen::changeWorkspaceVert(const int num) const {
615   assert(_managed);
616   const Config *conf = _epist->getConfig();
617   int width = conf->getNumberValue(Config::workspaceColumns);
618   int num_desktops = (signed)_num_desktops;
619   int active_desktop = (signed)_active_desktop;
620   int wnum = 0;
621
622   if (width > num_desktops || width <= 0)
623     return;
624
625   // a cookie to the person that makes this pretty
626   if (num < 0) {
627     wnum = active_desktop - width;
628     if (wnum < 0) {
629       wnum = num_desktops/width * width + active_desktop;
630       if (wnum >= num_desktops)
631         wnum = num_desktops - 1;
632     }
633   }
634   else {
635     wnum = active_desktop + width;
636     if (wnum >= num_desktops) {
637       wnum = (active_desktop + width) % num_desktops - 1;
638       if (wnum < 0)
639         wnum = 0;
640     }
641   }
642   changeWorkspace(wnum);
643 }
644
645 void screen::changeWorkspaceHorz(const int num) const {
646   assert(_managed);
647   const Config *conf = _epist->getConfig();
648   int width = conf->getNumberValue(Config::workspaceColumns);
649   int num_desktops = (signed)_num_desktops;
650   int active_desktop = (signed)_active_desktop;
651   int wnum = 0;
652   
653   if (width > num_desktops || width <= 0)
654     return;
655
656   if (num < 0) {
657     if (active_desktop % width != 0)
658       changeWorkspace(active_desktop - 1);
659     else {
660       wnum = active_desktop + width - 1;
661       if (wnum >= num_desktops)
662         wnum = num_desktops - 1;
663     }
664   }
665   else {
666     if (active_desktop % width != width - 1) {
667       wnum = active_desktop + 1;
668       if (wnum >= num_desktops)
669         wnum = num_desktops / width * width;
670     }
671     else
672       wnum = active_desktop - width + 1;
673   }
674   changeWorkspace(wnum);
675 }
676
677 void screen::grabKey(const KeyCode keyCode, const int modifierMask) const {
678
679   Display *display = _epist->getXDisplay();
680   int numlockMask, scrolllockMask;
681
682   _epist->getLockModifiers(numlockMask, scrolllockMask);
683
684   XGrabKey(display, keyCode, modifierMask,
685            _root, True, GrabModeAsync, GrabModeAsync);
686   XGrabKey(display, keyCode, 
687            modifierMask|LockMask,
688            _root, True, GrabModeAsync, GrabModeAsync);
689   XGrabKey(display, keyCode, 
690            modifierMask|scrolllockMask,
691            _root, True, GrabModeAsync, GrabModeAsync);
692   XGrabKey(display, keyCode, 
693            modifierMask|numlockMask,
694            _root, True, GrabModeAsync, GrabModeAsync);
695     
696   XGrabKey(display, keyCode, 
697            modifierMask|LockMask|scrolllockMask,
698            _root, True, GrabModeAsync, GrabModeAsync);
699   XGrabKey(display, keyCode, 
700            modifierMask|scrolllockMask|numlockMask,
701            _root, True, GrabModeAsync, GrabModeAsync);
702   XGrabKey(display, keyCode, 
703            modifierMask|numlockMask|LockMask,
704            _root, True, GrabModeAsync, GrabModeAsync);
705     
706   XGrabKey(display, keyCode, 
707            modifierMask|numlockMask|LockMask|scrolllockMask,
708            _root, True, GrabModeAsync, GrabModeAsync);
709 }
710
711 void screen::ungrabKey(const KeyCode keyCode, const int modifierMask) const {
712
713   Display *display = _epist->getXDisplay();
714   int numlockMask, scrolllockMask;
715
716   _epist->getLockModifiers(numlockMask, scrolllockMask);
717
718   XUngrabKey(display, keyCode, modifierMask, _root);
719   XUngrabKey(display, keyCode, modifierMask|LockMask, _root);
720   XUngrabKey(display, keyCode, modifierMask|scrolllockMask, _root);
721   XUngrabKey(display, keyCode, modifierMask|numlockMask, _root);
722   XUngrabKey(display, keyCode, modifierMask|LockMask|scrolllockMask, _root);
723   XUngrabKey(display, keyCode, modifierMask|scrolllockMask|numlockMask, _root);
724   XUngrabKey(display, keyCode, modifierMask|numlockMask|LockMask, _root);
725   XUngrabKey(display, keyCode, modifierMask|numlockMask|LockMask|
726              scrolllockMask, _root);
727 }