Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(3)

Side by Side Diff: src/platform/autox/script_runner.cc

Issue 2074007: autox: Remove C++ version. (Closed) Base URL: ssh://git@chromiumos-git/chromeos
Patch Set: Created 10 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « src/platform/autox/script_runner.h ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "autox/script_runner.h"
6
7 #include <cstdlib>
8 #include <unistd.h>
9 #include <utility>
10 #include <vector>
11
12 extern "C" {
13 #include <X11/keysym.h>
14 #include <X11/extensions/XTest.h>
15 }
16
17 #include "base/json/json_reader.h"
18 #include "base/logging.h"
19 #include "base/scoped_ptr.h"
20 #include "base/string_util.h"
21 #include "chromeos/string.h"
22
23 using chromeos::SplitStringUsing;
24 using std::make_pair;
25 using std::map;
26 using std::pair;
27 using std::set;
28 using std::string;
29 using std::vector;
30
31 namespace autox {
32
33 // Check that a command got the expected number of arguments, crashing with
34 // an error otherwise. Helper function for command handlers.
35 static void CheckNumArgs(const ListValue& values,
36 int num_args_expected,
37 int command_num) {
38 string command_name;
39 CHECK(values.GetString(0, &command_name)); // asserted in RunScript()
40 int num_args = values.GetSize() - 1;
41 CHECK(num_args == num_args_expected)
42 << "Command " << command_num << ": " << command_name << " requires "
43 << num_args_expected << " argument" << (num_args_expected == 1 ? "" : "s")
44 << " (got " << num_args << " instead)";
45 }
46
47 // Given a string beginning with '\', interpret a prefix of the following
48 // characters as an escaped keysym name (e.g. "\(Return)". The extracted
49 // keysym is stored in 'keysym_out', and the number of characters that
50 // should be skipped to get to the next character in the string (including
51 // the leading '\') are stored in 'escaped_length_out'. Returns false if
52 // unable to interpret the escaped sequence.
53 static bool ConvertEscapedStringToKeySym(const string& escaped_str,
54 KeySym* keysym_out,
55 int* escaped_length_out) {
56 CHECK(keysym_out);
57 CHECK(escaped_length_out);
58 CHECK(escaped_str[0] == '\\');
59
60 if (escaped_str.size() < 2)
61 return false;
62
63 if (escaped_str[1] == '\\') {
64 *keysym_out = XK_backslash;
65 *escaped_length_out = 2;
66 return true;
67 }
68
69 if (escaped_str[1] != '(')
70 return false;
71 size_t end_pos = escaped_str.find(')', 2);
72 if (end_pos == string::npos || end_pos == 2)
73 return false;
74 *keysym_out = XStringToKeysym(escaped_str.substr(2, end_pos - 2).c_str());
75 *escaped_length_out = end_pos + 1;
76 return (*keysym_out != NoSymbol);
77 }
78
79
80 ScriptRunner::ScriptRunner(Display* display)
81 : display_(display) {
82 // TODO: This is probably incomplete. I can't find any existing function
83 // that does something similar, though.
84 chars_to_keysyms_.insert(make_pair(' ', XK_space));
85 chars_to_keysyms_.insert(make_pair('\n', XK_Return));
86 chars_to_keysyms_.insert(make_pair('\t', XK_Tab));
87 chars_to_keysyms_.insert(make_pair('~', XK_asciitilde));
88 chars_to_keysyms_.insert(make_pair('!', XK_exclam));
89 chars_to_keysyms_.insert(make_pair('@', XK_at));
90 chars_to_keysyms_.insert(make_pair('#', XK_numbersign));
91 chars_to_keysyms_.insert(make_pair('$', XK_dollar));
92 chars_to_keysyms_.insert(make_pair('%', XK_percent));
93 chars_to_keysyms_.insert(make_pair('^', XK_asciicircum));
94 chars_to_keysyms_.insert(make_pair('&', XK_ampersand));
95 chars_to_keysyms_.insert(make_pair('*', XK_asterisk));
96 chars_to_keysyms_.insert(make_pair('(', XK_parenleft));
97 chars_to_keysyms_.insert(make_pair(')', XK_parenright));
98 chars_to_keysyms_.insert(make_pair('-', XK_minus));
99 chars_to_keysyms_.insert(make_pair('_', XK_underscore));
100 chars_to_keysyms_.insert(make_pair('+', XK_plus));
101 chars_to_keysyms_.insert(make_pair('=', XK_equal));
102 chars_to_keysyms_.insert(make_pair('{', XK_braceleft));
103 chars_to_keysyms_.insert(make_pair('[', XK_bracketleft));
104 chars_to_keysyms_.insert(make_pair('}', XK_braceright));
105 chars_to_keysyms_.insert(make_pair(']', XK_bracketright));
106 chars_to_keysyms_.insert(make_pair('|', XK_bar));
107 chars_to_keysyms_.insert(make_pair(':', XK_colon));
108 chars_to_keysyms_.insert(make_pair(';', XK_semicolon));
109 chars_to_keysyms_.insert(make_pair('"', XK_quotedbl));
110 chars_to_keysyms_.insert(make_pair('\'', XK_apostrophe));
111 chars_to_keysyms_.insert(make_pair(',', XK_comma));
112 chars_to_keysyms_.insert(make_pair('<', XK_less));
113 chars_to_keysyms_.insert(make_pair('.', XK_period));
114 chars_to_keysyms_.insert(make_pair('>', XK_greater));
115 chars_to_keysyms_.insert(make_pair('/', XK_slash));
116 chars_to_keysyms_.insert(make_pair('?', XK_question));
117
118 LoadKeyboardMapping();
119 }
120
121 void ScriptRunner::RunScript(const string& script) {
122 CHECK(display_);
123
124 // Reading JSON programmatically is pretty ugly, but the general
125 // structure is a dictionary with "script" mapping to a list of commands,
126 // where each command is itself a list consisting of a command name
127 // followed by the command's arguments:
128 //
129 // { "script": [
130 // [ "motion", 20, 40 ],
131 // [ "button_down", 1 ],
132 // [ "motion", 400, 300 ],
133 // [ "button_up", 1 ],
134 // ],
135 // }
136 //
137 // TODO: The toplevel dictionary is there to support additional
138 // parameters that will inevitably be needed at some point.
139
140 scoped_ptr<Value> toplevel_value(base::JSONReader::Read(script, true));
141 CHECK(toplevel_value.get())
142 << "Unable to parse script as JSON";
143 CHECK(toplevel_value->IsType(Value::TYPE_DICTIONARY))
144 << "Toplevel value must be a dictionary";
145 DictionaryValue* toplevel_dict =
146 static_cast<DictionaryValue*>(toplevel_value.get());
147
148 Value* script_value = NULL;
149 CHECK(toplevel_dict->Get(L"script", &script_value))
150 << "No \"script\" value in toplevel dictionary";
151 CHECK(script_value->IsType(Value::TYPE_LIST))
152 << "\"script\" value must be a list";
153 ListValue* script_list = static_cast<ListValue*>(script_value);
154 const int num_commands = script_list->GetSize();
155
156 for (int command_num = 0; command_num < num_commands; ++command_num) {
157 Value* command_value = NULL;
158 CHECK(script_list->Get(command_num, &command_value));
159 CHECK(command_value->IsType(Value::TYPE_LIST))
160 << "Command " << command_num << ": not a list";
161 ListValue* command_list = static_cast<ListValue*>(command_value);
162
163 CHECK(!command_list->empty())
164 << "Command " << command_num << ": list is empty";
165 Value* command_name_value = NULL;
166 CHECK(command_list->Get(0, &command_name_value));
167 CHECK(command_name_value->IsType(Value::TYPE_STRING))
168 << "Command " << command_num << ": list must start with string";
169 string command_name;
170 CHECK(command_name_value->GetAsString(&command_name));
171
172 if (command_name == "button_down")
173 HandleButtonCommand(command_num, *command_list, true);
174 else if (command_name == "button_up")
175 HandleButtonCommand(command_num, *command_list, false);
176 else if (command_name == "hotkey")
177 HandleHotkeyCommand(command_num, *command_list);
178 else if (command_name == "key_down")
179 HandleKeyCommand(command_num, *command_list, true);
180 else if (command_name == "key_up")
181 HandleKeyCommand(command_num, *command_list, false);
182 else if (command_name == "motion")
183 HandleMotionCommand(command_num, *command_list, true);
184 else if (command_name == "motion_relative")
185 HandleMotionCommand(command_num, *command_list, false);
186 else if (command_name == "sleep")
187 HandleSleepCommand(command_num, *command_list);
188 else if (command_name == "string")
189 HandleStringCommand(command_num, *command_list);
190 else
191 CHECK(false) << "Command " << command_num << ": unknown command \""
192 << command_name << "\"";
193 }
194 }
195
196 void ScriptRunner::LoadKeyboardMapping() {
197 int min_keycode = 0, max_keycode = 0;
198 XDisplayKeycodes(display_, &min_keycode, &max_keycode);
199 int num_keycodes = max_keycode - min_keycode + 1;
200
201 int keysyms_per_keycode = 0;
202 KeySym* keysyms = XGetKeyboardMapping(
203 display_, min_keycode, num_keycodes, &keysyms_per_keycode);
204 CHECK(keysyms);
205 CHECK(keysyms_per_keycode >= 1);
206
207 keysyms_to_keycodes_.clear();
208 for (int i = 0; i < num_keycodes; ++i) {
209 KeyCode keycode = min_keycode + i;
210 for (int j = 0; j < keysyms_per_keycode; ++j) {
211 // This is poorly documented, but it appears to match up with
212 // xmodmap's documentation: the first keysym is typed without any
213 // modifiers, the second keysym is typed with Shift, the third with
214 // Mode_switch, and the fourth with both Shift and Mode_switch ("Up
215 // to eight keysyms may be attached to a key, however the last four
216 // are not used in any major X server implementation"). We only care
217 // about the first two.
218 if (j > 1)
219 continue;
220
221 KeySym keysym = keysyms[i * keysyms_per_keycode + j];
222 if (keysym == NoSymbol)
223 continue;
224
225 // If we already found a way to type this keysym, only replace it if
226 // the old way required Shift but the new one doesn't.
227 if (keysyms_to_keycodes_.count(keysym) &&
228 (!keysyms_to_keycodes_[keysym].second || j != 0))
229 continue;
230
231 bool shift_required = (j == 1);
232 keysyms_to_keycodes_[keysym] = make_pair(keycode, shift_required);
233 }
234 }
235 XFree(keysyms);
236 }
237
238 bool ScriptRunner::ConvertCharToKeySym(char ch, KeySym* keysym_out) {
239 CHECK(keysym_out);
240
241 if (isalnum(ch)) {
242 string keysym_name(1, ch);
243 *keysym_out = XStringToKeysym(keysym_name.c_str());
244 return (keysym_out != NoSymbol);
245 }
246
247 map<char, KeySym>::const_iterator it = chars_to_keysyms_.find(ch);
248 if (it == chars_to_keysyms_.end())
249 return false;
250 *keysym_out = it->second;
251 return true;
252 }
253
254 bool ScriptRunner::KeySymRequiresShift(KeySym keysym) {
255 map<KeySym, pair<KeyCode, bool> >::const_iterator it =
256 keysyms_to_keycodes_.find(keysym);
257 return (it != keysyms_to_keycodes_.end()) ? it->second.second : false;
258 }
259
260 KeyCode ScriptRunner::GetKeyCodeForKeySym(KeySym keysym) {
261 map<KeySym, pair<KeyCode, bool> >::const_iterator it =
262 keysyms_to_keycodes_.find(keysym);
263 return (it != keysyms_to_keycodes_.end()) ? it->second.first : 0;
264 }
265
266 void ScriptRunner::HandleButtonCommand(int command_num,
267 const ListValue& values,
268 bool button_down) {
269 CheckNumArgs(values, 1, command_num);
270 int button = 1;
271 CHECK(values.GetInteger(1, &button)) << "Command " << command_num;
272 XTestFakeButtonEvent(display_, button, button_down ? True : False, 0);
273 XFlush(display_);
274 }
275
276 void ScriptRunner::HandleHotkeyCommand(int command_num,
277 const ListValue& values) {
278 CheckNumArgs(values, 1, command_num);
279 string text;
280 CHECK(values.GetString(1, &text)) << "Command " << command_num;
281 CHECK(!text.empty()) << "Command " << command_num;
282
283 vector<string> parts;
284 SplitStringUsing(text, "-", &parts);
285 CHECK(parts.size() >= 2) << "Command " << command_num;
286
287 bool saw_shift = false;
288
289 vector<KeyCode> keycodes;
290 for (vector<string>::const_iterator it = parts.begin();
291 it != parts.end(); ++it) {
292 string keysym_name = *it;
293 // Map some convenient short names to full keysym names.
294 if (keysym_name == "Ctrl")
295 keysym_name = "Control_L";
296 else if (keysym_name == "Alt")
297 keysym_name = "Alt_L";
298 else if (keysym_name == "Shift")
299 keysym_name = "Shift_L";
300
301 KeySym keysym = XStringToKeysym(keysym_name.c_str());
302 CHECK(keysym != NoSymbol)
303 << "Command " << command_num << ": Unable to look "
304 << "up keysym with name \"" << keysym_name << "\"";
305
306 if (keysym == XK_Shift_L || keysym == XK_Shift_R)
307 saw_shift = true;
308
309 KeyCode keycode = GetKeyCodeForKeySym(keysym);
310 CHECK(keycode != 0) << "Command " << command_num << ": Unable to convert "
311 << "keysym " << keysym << " (\"" << keysym_name
312 << "\") to keycode";
313
314 // Crash if we're being asked to press a key that requires Shift and the
315 // Shift key wasn't pressed already (but let it slide if they're just
316 // asking for an uppercase letter).
317 CHECK(!KeySymRequiresShift(keysym) || saw_shift ||
318 (isupper(keysym_name[0]) && keysym_name[1] == '\0'))
319 << "Command " << command_num << ": Keysym " << keysym_name
320 << " requires the Shift key to be held, but it wasn't seen earlier in "
321 << "the key combo. Either press Shift first or use the keycode's "
322 << "non-shifted keysym";
323
324 keycodes.push_back(keycode);
325 }
326
327 // Press the keys in order and then release them in reverse order.
328 for (vector<KeyCode>::const_iterator it = keycodes.begin();
329 it != keycodes.end(); ++it) {
330 XTestFakeKeyEvent(display_, *it, True, 0);
331 }
332 for (vector<KeyCode>::const_reverse_iterator it = keycodes.rbegin();
333 it != keycodes.rend(); ++it) {
334 XTestFakeKeyEvent(display_, *it, False, 0);
335 }
336 XFlush(display_);
337 }
338
339 void ScriptRunner::HandleKeyCommand(int command_num,
340 const ListValue& values,
341 bool key_down) {
342 CheckNumArgs(values, 1, command_num);
343 string keysym_name;
344 CHECK(values.GetString(1, &keysym_name)) << "Command " << command_num;
345
346 KeySym keysym = XStringToKeysym(keysym_name.c_str());
347 CHECK(keysym != NoSymbol) << "Command " << command_num << ": Unable to look "
348 << "up keysym with name \"" << keysym_name << "\"";
349 KeyCode keycode = GetKeyCodeForKeySym(keysym);
350 CHECK(keycode != 0) << "Command " << command_num << ": Unable to convert "
351 << "keysym " << keysym << " to keycode";
352
353 CHECK(!KeySymRequiresShift(keysym))
354 << "Command " << command_num << ": Keysym " << keysym_name << " cannot "
355 << "be typed with the \"key\" command since it requires the Shift key "
356 << "to be held. Either use \"string\" or use separate \"key\" commands, "
357 << "one with Shift and then one with the keycode's non-shifted keysym";
358
359 XTestFakeKeyEvent(display_, keycode, key_down ? True : False, 0);
360 XFlush(display_);
361 }
362
363 void ScriptRunner::HandleMotionCommand(int command_num,
364 const ListValue& values,
365 bool absolute) {
366 CheckNumArgs(values, 2, command_num);
367 int x = 0, y = 0;
368 CHECK(values.GetInteger(1, &x)) << "Command " << command_num;
369 CHECK(values.GetInteger(2, &y)) << "Command " << command_num;
370 if (absolute)
371 XTestFakeMotionEvent(display_, 0, x, y, 0);
372 else
373 XTestFakeRelativeMotionEvent(display_, x, y, 0);
374 XFlush(display_);
375 }
376
377 void ScriptRunner::HandleSleepCommand(int command_num,
378 const ListValue& values) {
379 CheckNumArgs(values, 1, command_num);
380 int time_ms = 0;
381 CHECK(values.GetInteger(1, &time_ms)) << "Command " << command_num;
382 usleep(1000LL * time_ms);
383 }
384
385 void ScriptRunner::HandleStringCommand(int command_num,
386 const ListValue& values) {
387 CheckNumArgs(values, 1, command_num);
388 string text;
389 CHECK(values.GetString(1, &text)) << "Command " << command_num;
390
391 KeyCode shift_keycode = GetKeyCodeForKeySym(XK_Shift_L);
392 CHECK(shift_keycode) << "Unable to look up keycode for XK_Shift_L";
393
394 for (size_t i = 0; i < text.size(); ++i) {
395 char ch = text[i];
396
397 KeySym keysym;
398 if (ch == '\\') {
399 int num_chars_to_skip = 1;
400 CHECK(ConvertEscapedStringToKeySym(
401 text.substr(i), &keysym, &num_chars_to_skip))
402 << "Command " << command_num << ": Unable to convert escaped "
403 << "sequence at beginning of \"" << text.substr(i) << "\" to keysym";
404 CHECK(num_chars_to_skip >= 1);
405 i += num_chars_to_skip - 1; // - 1 to compensate for loop's ++i
406 } else {
407 CHECK(ConvertCharToKeySym(ch, &keysym))
408 << "Command " << command_num << ": Unable to convert character '"
409 << ch << "' to keysym";
410 }
411 KeyCode keycode = GetKeyCodeForKeySym(keysym);
412 CHECK(keycode != 0) << "Command " << command_num << ": Unable to convert "
413 << "keysym " << keysym << " to keycode";
414
415 bool shift_required = KeySymRequiresShift(keysym);
416 if (shift_required)
417 XTestFakeKeyEvent(display_, shift_keycode, True, 0);
418 XTestFakeKeyEvent(display_, keycode, True, 0);
419 XTestFakeKeyEvent(display_, keycode, False, 0);
420 if (shift_required)
421 XTestFakeKeyEvent(display_, shift_keycode, False, 0);
422 }
423 XFlush(display_);
424 }
425
426 } // namespace autox
OLDNEW
« no previous file with comments | « src/platform/autox/script_runner.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698