1 #include "keymanager.h"
2 
3 #include <algorithm>
4 #include <memory>
5 #include <stdexcept>
6 #include <utility>
7 
8 #include "arglist.h"
9 #include "client.h"
10 #include "clientmanager.h"
11 #include "command.h"
12 #include "completion.h"
13 #include "ipc-protocol.h"
14 #include "keycombo.h"
15 #include "root.h"
16 #include "utils.h"
17 
18 using std::endl;
19 using std::string;
20 using std::unique_ptr;
21 
~KeyManager()22 KeyManager::~KeyManager() {
23     xKeyGrabber_.ungrabAll();
24 }
25 
addKeybindCommand(Input input,Output output)26 int KeyManager::addKeybindCommand(Input input, Output output) {
27     if (input.size() < 2) {
28         return HERBST_NEED_MORE_ARGS;
29     }
30 
31     auto newBinding = make_unique<KeyBinding>();
32 
33     try {
34         newBinding->keyCombo = KeyCombo::fromString(input.front());
35     } catch (std::exception &error) {
36         output << input.command() << ": " << error.what() << endl;
37         return HERBST_INVALID_ARGUMENT;
38     }
39 
40     input.shift();
41     // Store remaining input as the associated command
42     newBinding->cmd = {input.begin(), input.end()};
43 
44     // newBinding->cmd is not empty because the size before the input.shift() was >= 2
45     if (!Commands::commandExists(newBinding->cmd[0])) {
46         output << input.command() << ": the command \""
47                << newBinding->cmd[0] << "\" does not exist."
48                << " Did you forget \"spawn\"?\n";
49         return HERBST_COMMAND_NOT_FOUND;
50     }
51 
52     // Make sure there is no existing binding with same keysym/modifiers
53     removeKeyBinding(newBinding->keyCombo);
54 
55     if (currentKeyMask_.allowsBinding(newBinding->keyCombo)
56         && currentKeysInactive_.allowsBinding(newBinding->keyCombo))
57     {
58         // Grab for events on this keycode
59         xKeyGrabber_.grabKeyCombo(newBinding->keyCombo);
60         newBinding->grabbed = true;
61     }
62 
63     // Add keybinding to list
64     binds.push_back(std::move(newBinding));
65 
66     ensureKeyMask();
67 
68     return HERBST_EXIT_SUCCESS;
69 }
70 
listKeybindsCommand(Output output) const71 int KeyManager::listKeybindsCommand(Output output) const {
72     for (auto& binding : binds) {
73         // add key combo
74         output << binding->keyCombo.str();
75         // add associated command
76         output << "\t" << ArgList(binding->cmd).join('\t');
77         output << "\n";
78     }
79     return 0;
80 }
81 
removeKeybindCommand(Input input,Output output)82 int KeyManager::removeKeybindCommand(Input input, Output output) {
83     string arg;
84     if (!(input >> arg)) {
85         return HERBST_NEED_MORE_ARGS;
86     }
87 
88     if (arg == "--all" || arg == "-F") {
89         binds.clear();
90         xKeyGrabber_.ungrabAll();
91     } else {
92         KeyCombo comboToRemove = {};
93         try {
94             comboToRemove = KeyCombo::fromString(arg);
95         } catch (std::exception &error) {
96             output << input.command() << ": " << arg << ": " << error.what() << "\n";
97             return HERBST_INVALID_ARGUMENT;
98         }
99 
100         // Remove binding (or moan if none was found)
101         if (removeKeyBinding(comboToRemove)) {
102             regrabAll();
103         } else {
104             output << input.command() << ": Key \"" << arg << "\" is not bound\n";
105         }
106     }
107 
108     return HERBST_EXIT_SUCCESS;
109 }
110 
addKeybindCompletion(Completion & complete)111 void KeyManager::addKeybindCompletion(Completion &complete) {
112     if (complete == 0) {
113         KeyCombo::complete(complete);
114     } else {
115         complete.completeCommands(1);
116     }
117 }
118 
removeKeybindCompletion(Completion & complete)119 void KeyManager::removeKeybindCompletion(Completion &complete) {
120     if (complete == 0) {
121         complete.full({ "-F", "--all" });
122 
123         for (auto& binding : binds) {
124             complete.full(binding->keyCombo.str());
125         }
126     }
127 }
128 
handleKeyPress(XKeyEvent * ev) const129 void KeyManager::handleKeyPress(XKeyEvent* ev) const {
130     KeyCombo pressed = xKeyGrabber_.xEventToKeyCombo(ev);
131 
132     auto found = std::find_if(binds.begin(), binds.end(),
133             [=](const unique_ptr<KeyBinding> &other) {
134                 return pressed == other->keyCombo;
135             });
136     if (found != binds.end()) {
137         // execute the bound command
138         std::ostringstream discardedOutput;
139         auto& cmd = (*found)->cmd;
140         Input input(cmd.front(), {cmd.begin() + 1, cmd.end()});
141         Commands::call(input, discardedOutput);
142     }
143 }
144 
regrabAll()145 void KeyManager::regrabAll() {
146     xKeyGrabber_.updateNumlockMask();
147 
148      // Remove all current grabs:
149     xKeyGrabber_.ungrabAll();
150 
151     for (auto& binding : binds) {
152         // grab precisely those again, that have been grabbed before
153         if (binding->grabbed) {
154             xKeyGrabber_.grabKeyCombo(binding->keyCombo);
155         }
156     }
157 }
158 
159 /*!
160  * Makes sure that the currently active keymask is correct for the currently
161  * focused client and regrabs keys if necessary
162  *
163  * FIXME: The Client* argument only exists because I failed to find a place to
164  * call this function on focus changes where ClientManager::focus is already
165  * updated.
166  */
ensureKeyMask(const Client * client)167 void KeyManager::ensureKeyMask(const Client* client) {
168     if (client == nullptr) {
169         client = Root::get()->clients()->focus();
170     }
171     // if there is still no client, then nothing is focused
172     if (client == nullptr) {
173         clearActiveKeyMask();
174         return;
175     }
176 
177     // keyMask => keybinding is allowed if it matches
178     //            the reg. expression => no negation
179     KeyMask newKeyMask(client->keyMask_(), false);
180     // keysInactive => keybinding is disallowed if it
181     //                 *does not* match the reg. expression => negation!
182     KeyMask newKeysInactive(client->keysInactive_(), true);
183 
184     if (currentKeyMask_ == newKeyMask
185         && currentKeysInactive_ == newKeysInactive)
186     {
187         // nothing to do
188         return;
189     }
190     setActiveKeyMask(newKeyMask, newKeysInactive);
191 }
192 
193 //! Apply new keymask by grabbing/ungrabbing current bindings accordingly
setActiveKeyMask(const KeyMask & keyMask,const KeyMask & keysInactive)194 void KeyManager::setActiveKeyMask(const KeyMask& keyMask, const KeyMask& keysInactive) {
195     for (auto& binding : binds) {
196         auto name = binding->keyCombo.str();
197         bool isAllowed = keysInactive.allowsBinding(binding->keyCombo)
198                          && keyMask.allowsBinding(binding->keyCombo);
199         if (isAllowed && !binding->grabbed) {
200             xKeyGrabber_.grabKeyCombo(binding->keyCombo);
201             binding->grabbed = true;
202         } else if (!isAllowed && binding->grabbed) {
203             xKeyGrabber_.ungrabKeyCombo(binding->keyCombo);
204             binding->grabbed = false;
205         }
206     }
207     currentKeyMask_ = keyMask;
208     currentKeysInactive_ = keysInactive;
209 }
210 
211 //! Set the current key filters to an empty exception
clearActiveKeyMask()212 void KeyManager::clearActiveKeyMask() {
213     setActiveKeyMask({}, {});
214 }
215 
216 /*!
217  * Removes a given key combo from the list of bindings (no ungrabbing)
218  *
219  * \return True if a matching binding was found and removed
220  * \return False if no matching binding was found
221  */
removeKeyBinding(const KeyCombo & comboToRemove)222 bool KeyManager::removeKeyBinding(const KeyCombo& comboToRemove) {
223     // Find binding to remove
224     auto removeIter = binds.begin();
225     for (; removeIter != binds.end(); removeIter++) {
226         if (comboToRemove == (*removeIter)->keyCombo) {
227             break;
228         }
229     }
230 
231     if (removeIter == binds.end()) {
232         return False; // no matching binding found
233     }
234 
235     // Remove binding
236     binds.erase(removeIter);
237     return True;
238 }
239 
240 
241 /*!
242  * Returns true if the string representation of the KeyCombo matches
243  * the given keymask
244  */
KeyMask(const RegexStr & regex,bool negated)245 KeyManager::KeyMask::KeyMask(const RegexStr &regex, bool negated)
246     : regex_(regex)
247     , negated_(negated)
248 {
249 }
250 
KeyMask()251 KeyManager::KeyMask::KeyMask()
252     : regex_(RegexStr::fromStr(""))
253     , negated_(false)
254 {
255 }
256 
allowsBinding(const KeyCombo & combo) const257 bool KeyManager::KeyMask::allowsBinding(const KeyCombo &combo) const
258 {
259     if (regex_.empty()) {
260         // an unset keymask allows every binding, regardless of
261         // the 'negated_' flag
262         return true;
263     } else {
264         bool match = regex_.matches(combo.str());
265         if (negated_) {
266             // only allow keybindings that don't match
267             return !match;
268         } else {
269             // only allow keybindings that match
270             return match;
271         }
272     }
273 }
274