1 // -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*-
2 // -- KeyClient.cpp --
3 // Copyright (c) 2001 - 2003 Jason 'vanRijn' Kasper <vR at movingparts dot 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 // E_O_H_VR
24 
25 #include "../config.h"
26 
27 extern "C" {
28 
29 #ifdef    HAVE_SIGNAL_H
30 #  include <signal.h>
31 #endif // HAVE_SIGNAL_H
32 
33 #ifdef    HAVE_SYS_SIGNAL_H
34 #  include <sys/signal.h>
35 #endif // HAVE_SYS_SIGNAL_H
36 
37 #ifdef	  HAVE_UNISTD_H
38 # include <unistd.h>
39 # include <sys/types.h>
40 #endif // HAVE_UNISTD_H
41 
42 #ifdef    HAVE_SYS_STAT_H
43 #  include <sys/types.h>
44 #  include <sys/stat.h>
45 #endif // HAVE_SYS_STAT_H
46 
47 #include <sys/types.h>
48 #include <sys/wait.h>
49 
50 }
51 
52 #include "version.h"
53 
54 #include "KeyClient.h"
55 #include "LocalUtil.h"
56 #include "actions.hh"
57 
58 #include <iostream>
59 #include <algorithm>
60 #include <vector>
61 #include <strings.h>
62 
63 //--------------------------------------------------------
64 // Constructor/Destructor
65 //--------------------------------------------------------
KeyClient(int argc,char ** argv,Config & config,std::string display)66 KeyClient::KeyClient (int argc, char **argv,
67                       Config & config, std::string display):
68   bt::Application(BBTOOL, display.c_str(), true), _config(config),
69   _keybindings(0), config_check_timer(0)
70 {
71 
72   // save off what we're constructed with for reconfiguring later...
73   _argc = argc;
74   _argv = argv;
75 
76   // initialize our keyword map for the file tokenizer
77   initKeywords(_keywordMap);
78 
79   // now connect to the X server
80   _display = XDisplay();
81   if (! _display ) {
82     cerr << BBTOOL << ": " << "KeyClient: ERROR: Can't connect to X Server. Bummer! Exiting\n";
83     exit(2);
84   }
85 
86   // check to see if we've been handed another config-file to use
87   _configFileName = bt::expandTilde(_config.getStringValue("config",
88                                     "~/.bbkeysrc") );
89 
90   struct stat buf;
91   if (0 != stat(_configFileName.c_str(), &buf) ||!S_ISREG(buf.st_mode)) {
92     cerr << BBTOOL << ": " << "KeyClient: ERROR: Couldn't load rc-file: [" << _configFileName
93          << "], falling back to default: [" << DEFAULTRC << "]\n";
94     _configFileName = DEFAULTRC;
95   } else {
96     _last_time_config_changed = buf.st_mtime;
97   }
98 
99   _debug = _config.getBoolValue("debug", false);
100 
101   // here's our friendly little general-purpose keygrabber
102   _keyGrabber = new KeyGrabber(_display, numLockMask(), scrollLockMask() );
103 
104   _netclient = new Netclient(this->display());
105   _active = _clients.end();
106 
107   // Initialize uninitialized pointers to NULL
108   _keybindings = NULL;
109   config_check_timer = NULL;
110 
111 
112   initialize();
113 }
114 
~KeyClient()115 KeyClient::~KeyClient ()
116 {
117 
118   // delete all screens
119   for_each(screenList.begin(), screenList.end(), bt::PointerAssassin());
120 
121   if (_keybindings) delete _keybindings;
122   if (_netclient) delete _netclient;
123   if (_keyGrabber) delete _keyGrabber;
124 }
125 
initialize()126 void KeyClient::initialize() {
127 
128   // now, read in our configuration file and set both program settings
129   // and keybindings we're asked to handle
130   handleConfigFile();
131 
132   // parse command options again to override what we read in from config file
133   parseOptions( _argc, _argv, _config );
134 
135 
136   // now create a screen handler for each screen that exists
137   for (unsigned int i = 0; i < bt::Application::display().screenCount(); i++) {
138     ScreenHandler *screen = new ScreenHandler(this, i);
139     if (! screen->isManaged()) {
140       delete screen;
141       continue;
142     }
143 
144     screen->initialize();
145 
146     // add this screen to our collection
147     screenList.push_back(screen);
148   }
149 
150   if (screenList.empty()) {
151     cerr << BBTOOL << ": " << "KeyClient: initialize: no compatible window managers found, aborting.\n";
152     ::exit(3);
153   }
154 
155   _autoConfigCheckTimeout = (_config.getNumberValue("autoConfigCheckTimeout", 10)) * 1000;
156   _autoConfig = _config.getBoolValue("autoConfig", true);
157   if (_autoConfig) {
158     if (!config_check_timer) {
159       config_check_timer = new bt::Timer(this, this);
160     }
161     config_check_timer->setTimeout(_autoConfigCheckTimeout);
162     config_check_timer->recurring(True);
163     config_check_timer->start();
164   }
165 
166 }
167 
168 //--------------------------------------------------------
169 // reconfigure
170 //--------------------------------------------------------
reconfigure()171 void KeyClient::reconfigure ()
172 {
173   std::cout << BBTOOL << ": " <<
174     "KeyClient: reconfigure: hey, goodie! I got a reconfigure request!!\n";
175 
176   // delete all screens
177   for_each(screenList.begin(), screenList.end(), bt::PointerAssassin());
178   screenList.clear();
179 
180   // initialize and/or clear our config
181   _config.reset();
182 
183   // reset our timer
184   if (config_check_timer) {
185     config_check_timer->halt();
186   }
187 
188   initialize();
189 
190 }
191 
192 
handleConfigFile()193 void KeyClient::handleConfigFile() {
194 
195   FileTokenizer tokenizer(_keywordMap, _configFileName.c_str());
196 
197   // clear off any of our keybindings we have and get them ready to go
198   if (_keybindings) {
199     _keybindings->unloadBindings();
200   } else {
201     _keybindings = new keytree(_display);
202   }
203 
204   _keybindings->reset();
205 
206   bool _doingConfig = false, _doingKeybindings = false;
207 
208   TokenBlock *block = 0;
209   while ((block = tokenizer.next())) {
210     switch (block->tag) {
211     case ConfigOpts::begin: // um, we ignore these. =:)
212       break;
213     case ConfigOpts::end:
214       if (_doingConfig) {
215         _doingConfig = false;
216       }
217       break;
218     case ConfigOpts::config:
219       _doingConfig = true;
220       break;
221     case ConfigOpts::option:
222       if (_debug)
223         cout << BBTOOL << ": " << "got a config option!, setting key: [" << block->name
224              << "] to value: [" << block->data << "]\n";
225 
226       _config.setOption(block->name, block->data);
227       break;
228     case ConfigOpts::keybindings:
229       _doingKeybindings = true;
230       setKeybindings(tokenizer);
231 
232       if (_debug)
233         _keybindings->showTree();
234 
235       break;
236     default:
237       cerr << BBTOOL << ": " << "unknown tag found in ConfigOpts block: ["
238            << block->tag << "], name: [" << block->name
239            << "], data: [" << block->data << "]\n";
240       break;
241     }
242     delete block;
243   }
244 
245   if (_debug) {
246     cout << "-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n";
247     _config.showOptions();
248   }
249 
250 }
251 
setKeybindings(FileTokenizer & tokenizer)252 void KeyClient::setKeybindings(FileTokenizer & tokenizer) {
253 
254   // our modifier masks
255   struct {
256     const char *str;
257     unsigned int mask;
258   }
259   modifiers[] = {
260     { "mod1", Mod1Mask },
261     { "mod2", Mod2Mask },
262     { "mod3", Mod3Mask },
263     { "mod4", Mod4Mask },
264     { "mod5", Mod5Mask },
265     { "control", ControlMask },
266     { "shift", ShiftMask },
267     { "", 0 }
268   };
269 
270   // this tells us how many levels deep we're nested. this will tell us when to return
271   int _iLevels =0;
272 
273   TokenBlock *block = 0;
274   while ((block = tokenizer.next())) {
275 
276     // if we hit an end, return
277     if (block->tag == ConfigOpts::end) {
278       --_iLevels;
279       _keybindings->retract();
280       // 0 is our root level, so if we're below that, we need to bail out
281       if (_iLevels <0) {
282         if (block) delete block;
283         return;
284       }
285     } else {
286 
287       string fullKey = block->name;
288 
289       if (fullKey.size() <=0) {
290         cerr << BBTOOL << ": " << "ERROR: No key or modifier given. Ignoring this one, Jimmy.\n";
291         if (block) delete block;
292         continue;
293       }
294       // first, split our string containing our keys/modifiers and separate
295       // the keys from the modifiers
296       vector<string> results;
297       int matches = LocalUtil::splitString(fullKey, "-", results);
298 
299       // here's our keyname. make sure it's a valid key
300       string _key = results[results.size() -1];
301 
302       KeySym sym = XStringToKeysym(_key.c_str());
303       if (sym == 0) {
304         cerr << BBTOOL << ": " << "ERROR: Invalid key (" << _key << ")! This may cause odd behavior.\n";
305       }
306 
307       // now iterate through our modifiers and try to match the given string
308       // to a modifier mask. if we find it, xor it together with what we already have
309       unsigned int _mask=0;
310 
311       for (int j=0; j < (matches -1); j++) {
312 
313         bool found=false;
314         string mod = results[j];
315 
316         for (int i = 0; modifiers[i].str[0] != '\0'; ++i) {
317           if ( strcasecmp(modifiers[i].str, mod.c_str()) == 0 ) {
318             _mask |= modifiers[i].mask;
319             found = true;
320             break;
321           }
322         }
323         if (!found) cerr << BBTOOL << ": " << "ERROR: Couldn't find modifier for mod: [" << mod << "]\n";
324       }
325 
326       // now, if we have a chain, nest down a level and add the keybinding
327       if (block->tag == Action::chain) {
328         _iLevels++;
329         _keybindings->advanceOnNewNode();
330         _keybindings->setCurrentNodeProps(static_cast<Action::ActionType>(block->tag),
331                                           _mask, _key, block->data);
332       } else {
333         _keybindings->addAction(static_cast<Action::ActionType>(block->tag),
334                                 _mask, _key, block->data);
335       }
336     }
337     if (block) delete block;
338   }
339 }
340 
initKeywords(KeywordMap & keywords)341 void KeyClient::initKeywords(KeywordMap& keywords) {
342 
343   // load our map with our keybinding labels
344   keywords.insert(KeywordMap::value_type("execute", Action::execute));
345   keywords.insert(KeywordMap::value_type("iconify", Action::iconify));
346   keywords.insert(KeywordMap::value_type("raise", Action::raise));
347   keywords.insert(KeywordMap::value_type("lower", Action::lower));
348   keywords.insert(KeywordMap::value_type("close", Action::close));
349   keywords.insert(KeywordMap::value_type("toggleshade", Action::toggleShade));
350   keywords.insert(KeywordMap::value_type("toggleomnipresent", Action::toggleOmnipresent));
351   keywords.insert(KeywordMap::value_type("movewindowup", Action::moveWindowUp));
352   keywords.insert(KeywordMap::value_type("movewindowdown", Action::moveWindowDown));
353   keywords.insert(KeywordMap::value_type("movewindowleft", Action::moveWindowLeft));
354   keywords.insert(KeywordMap::value_type("movewindowright", Action::moveWindowRight));
355   keywords.insert(KeywordMap::value_type("resizewindowwidth", Action::resizeWindowWidth));
356   keywords.insert(KeywordMap::value_type("resizewindowheight", Action::resizeWindowHeight));
357 
358   keywords.insert(KeywordMap::value_type("togglemaximizefull", Action::toggleMaximizeFull));
359   keywords.insert(KeywordMap::value_type("togglemaximizevertical", Action::toggleMaximizeVertical));
360   keywords.insert(KeywordMap::value_type("togglemaximizehorizontal", Action::toggleMaximizeHorizontal));
361 
362   keywords.insert(KeywordMap::value_type("sendtoworkspace", Action::sendToWorkspace));
363   keywords.insert(KeywordMap::value_type("sendtonextworkspace", Action::sendToNextWorkspace));
364   keywords.insert(KeywordMap::value_type("sendtoprevworkspace", Action::sendToPrevWorkspace));
365 
366   keywords.insert(KeywordMap::value_type("nextwindow", Action::nextWindow));
367   keywords.insert(KeywordMap::value_type("prevwindow", Action::prevWindow));
368   keywords.insert(KeywordMap::value_type("nextwindowonallworkspaces", Action::nextWindowOnAllWorkspaces));
369   keywords.insert(KeywordMap::value_type("prevwindowonallworkspaces", Action::prevWindowOnAllWorkspaces));
370 
371   keywords.insert(KeywordMap::value_type("changeworkspace", Action::changeWorkspace));
372   keywords.insert(KeywordMap::value_type("nextworkspace", Action::nextWorkspace));
373   keywords.insert(KeywordMap::value_type("prevworkspace", Action::prevWorkspace));
374 
375   keywords.insert(KeywordMap::value_type("upworkspace", Action::upWorkspace));
376   keywords.insert(KeywordMap::value_type("downworkspace", Action::downWorkspace));
377   keywords.insert(KeywordMap::value_type("leftworkspace", Action::leftWorkspace));
378   keywords.insert(KeywordMap::value_type("rightworkspace", Action::rightWorkspace));
379 
380   keywords.insert(KeywordMap::value_type("nextscreen", Action::nextScreen));
381   keywords.insert(KeywordMap::value_type("prevscreen", Action::prevScreen));
382 
383   keywords.insert(KeywordMap::value_type("showrootmenu", Action::showRootMenu));
384   keywords.insert(KeywordMap::value_type("showworkspacemenu", Action::showWorkspaceMenu));
385   keywords.insert(KeywordMap::value_type("toggledecorations", Action::toggleDecorations));
386 
387   keywords.insert(KeywordMap::value_type("togglegrabs", Action::toggleGrabs));
388   keywords.insert(KeywordMap::value_type("chain", Action::chain));
389 
390   // the words associated with our high-level file labels
391   keywords.insert(KeywordMap::value_type("begin", ConfigOpts::begin));
392   keywords.insert(KeywordMap::value_type("end", ConfigOpts::end));
393   keywords.insert(KeywordMap::value_type("config", ConfigOpts::config));
394   keywords.insert(KeywordMap::value_type("keybindings", ConfigOpts::keybindings));
395   keywords.insert(KeywordMap::value_type("option", ConfigOpts::option));
396 
397 }
398 
process_signal(int sig)399 bool KeyClient::process_signal(int sig) {
400   switch (sig) {
401   case SIGHUP:
402     reconfigure();
403     return true;
404     break;
405 
406   default:
407     return (bt::Application::process_signal(sig) );
408   }
409 
410 }
411 
process_event(XEvent * e)412 void KeyClient::process_event(XEvent *e) {
413   // Send the event through the default EventHandlers.
414   bt::Application::process_event(e);
415 }
416 
cycleScreen(int current,bool forward) const417 void KeyClient::cycleScreen(int current, bool forward) const {
418   unsigned int i;
419   for (i = 0; i < screenList.size(); ++i)
420     if (screenList[i]->getScreenNumber() == current) {
421       current = i;
422       break;
423     }
424   assert(i < screenList.size());  // current is for an unmanaged screen
425 
426   int dest = current + (forward ? 1 : -1);
427 
428   if (dest < 0) dest = (signed)screenList.size() - 1;
429   else if (dest >= (signed)screenList.size()) dest = 0;
430 
431   const XWindow *target = screenList[dest]->lastActiveWindow();
432   if (target) target->focus();
433 }
434 
timeout(bt::Timer * timer)435 void KeyClient::timeout(bt::Timer *timer) {
436   if (timer == config_check_timer) {
437     checkConfigFile();
438   }
439 }
440 
checkConfigFile()441 void KeyClient::checkConfigFile() {
442 
443   struct stat file_status;
444 
445   if (stat(_configFileName.c_str(), &file_status) != 0) {
446     if (_debug)
447       std::cerr << BBTOOL << ": " << "Could not open config file: [" << _configFileName << "]";
448   } else if (file_status.st_mtime != _last_time_config_changed) {
449     if (_debug)
450       std::cout << BBTOOL << ": " << "KeyClient: checkConfigFile: config file time changed..." << std::endl;
451     _last_time_config_changed = file_status.st_mtime;
452     reconfigure();
453   }
454 }
455 
456