| OLD | NEW |
| (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 | |
| OLD | NEW |