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