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 ®ex, 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