imitator_x 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/ext/keyboard.c ADDED
@@ -0,0 +1,711 @@
1
+ /*********************************************************************************
2
+ Imitator for X is a library allowing you to fake input to systems using X11.
3
+ Copyright � 2010 Marvin G�lker
4
+
5
+ This file is part of Imitator for X.
6
+
7
+ Imitator for X is free software: you can redistribute it and/or modify
8
+ it under the terms of the GNU Lesser General Public License as published by
9
+ the Free Software Foundation, either version 3 of the License, or
10
+ (at your option) any later version.
11
+
12
+ Imitator for X is distributed in the hope that it will be useful,
13
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ GNU Lesser General Public License for more details.
16
+
17
+ You should have received a copy of the GNU Lesser General Public License
18
+ along with Imitator for X. If not, see <http://www.gnu.org/licenses/>.
19
+ *********************************************************************************/
20
+ #include "x.h"
21
+ #include "keyboard.h"
22
+
23
+ /*
24
+ *When coding this file, I found out that the easiest way
25
+ *to get the KeySym of a character is just to encode it in UTF-8
26
+ *and then call String#ord on it. That directly gives the value of
27
+ *the XK_<your_char_here> constants.
28
+ *
29
+ *The XTEST extension cannot fake special keys directly (sadly).
30
+ *In order to fake keystrokes like those for uppercase characters
31
+ *you have to to specify the full needed keystroke sequence
32
+ *(this explains much of the complexity of the code here), i.e.
33
+ *[Shift]+[T] is required to get an uppercase letter T. */
34
+
35
+ /*Document-module: Imitator::X::Keyboard
36
+ *This module allows interaction with the keyboard. Via the
37
+ *Keyboard.key method you can send key combinations � la
38
+ *[CTRL]+[ALT]+[DEL], and by using Keyboard.simulate you
39
+ *can fake whole textual data. Please note that you cannot simulate keystrokes
40
+ *for characters that you cannot type manually on your keyboard.
41
+ *
42
+ *Keyboard.simulate requires one further note, though. Since there
43
+ *are many characters out there which aren't created by the same
44
+ *keystrokes all over the world, I had to think about how I can implement
45
+ *this best, without needing to test my code on every single locale existing
46
+ *somewhere in the world (take the at sign @ for instance: On my German keyboard
47
+ *I'd press [ALT_GR]+[Q] to get it, in Switzerland it would be [ALT_GR]+[2]).
48
+ *Finally I created a file "imitator_x_special_chars.yml" in Imitator for X's gem
49
+ *directory that maps the special characters to their key combinations. You are
50
+ *encouraged to change the mapping temporarily by modifying the Keyboard::SPECIAL_CHARS
51
+ *hash or permanently by changing the contents of that file. If you do so, you
52
+ *may should think about sending me an email to sutniuq<>gmx<>net and attach
53
+ *your "imitator_x_special_chars.yml" file and mention your locale - sometime
54
+ *in the future I may be able to set up a locale selector then that automatically
55
+ *chooses the right key combinations.
56
+ *
57
+ *The best way to find out how keys are generated is to run the following command
58
+ *and then press the wanted key:
59
+ * xev | grep keysym
60
+ */
61
+
62
+ /********************Helper functions***********************/
63
+
64
+ /*This function returns the KeyCode for the Ruby string specified by
65
+ *+rkey+. It first tries to convert it directly to a KeySym, and if that fails,
66
+ *it looks into the ALIASES hash (a constant of Keyboard) if there is an alias
67
+ *defined. If so, the KeySym for the alias is used. If not, the connection to the
68
+ *X server is closed and a XError is thrown.
69
+ *Afterwards, the KeySym is converted to the desired KeyCode.
70
+ */
71
+ KeyCode get_keycode(Display * p_display, VALUE rkey)
72
+ {
73
+ KeySym sym;
74
+ KeyCode code;
75
+ VALUE ralias;
76
+
77
+ /*First try direct conversion*/
78
+ sym = XStringToKeysym(StringValuePtr(rkey));
79
+ if (sym == NoSymbol) /*If direct conversion failed*/
80
+ {
81
+ /*Look into the ALIASES hash for an alias*/
82
+ ralias = rb_hash_lookup(rb_const_get(Keyboard, rb_intern("ALIASES")), rkey);
83
+ if (NIL_P(ralias)) /*If no alias found*/
84
+ {
85
+ XSetErrorHandler(NULL);
86
+ XCloseDisplay(p_display);
87
+ rb_raise(XError, "Invalid key '%s'!", StringValuePtr(rkey));
88
+ }
89
+ else /*Use the alias for direct conversion*/
90
+ code = get_keycode(p_display, ralias); /*The hash only stores valid keysym names; otherwise an endless recursion would occur here*/
91
+ }
92
+ else
93
+ code = XKeysymToKeycode(p_display, sym);
94
+ return code;
95
+ }
96
+
97
+ /*Converts a text into a command list. Returns an array of form
98
+ * [[keycode, bool], [keycode, bool], ...]
99
+ *where +keycode+ is the key to press or release and +bool+ indicates
100
+ *wheather the key should be pressed or released.
101
+ *Note that everything is returned as a Ruby object. */
102
+ VALUE convert_rtext_to_rkeycodes(Display * p_display, VALUE rtext)
103
+ {
104
+ VALUE rtokens, rcommands, rcommands2, rtemp, rkeycodes, rkeycode, rentry, rparts, rkeysym;
105
+ int length, length2, i, j;
106
+
107
+ rtokens = rb_str_split(rtext, ""); /*Every single letter has to be processed by it's own*/
108
+ length = NUM2INT(rb_funcall(rtokens, rb_intern("length"), 0));
109
+
110
+ rcommands = rb_ary_new();
111
+ /*First, replace all characters that need modkey presses
112
+ *with the full keypress sequence, as specified in the SPECIAL_CHARS hash*/
113
+ for(i = 0;i < length;i++)
114
+ {
115
+ rtemp = rb_ary_entry(rtokens, i);
116
+ if ( RTEST(rentry = rb_hash_lookup(rb_const_get(Keyboard, rb_intern("SPECIAL_CHARS")), rtemp)) )
117
+ rb_ary_push(rcommands, rentry);
118
+ else /*We don't need a replacement*/
119
+ rb_ary_push(rcommands, rtemp);
120
+ }
121
+
122
+ rcommands2 = rb_ary_new();
123
+ /*Second, replace the keypress and keypress sequences with command arrays as
124
+ *[38, true]
125
+ *(this means press "a" down on my Ubuntu Karmic 64-bit machine). */
126
+ for(i = 0;i < length;i++)
127
+ {
128
+ rtemp = rb_ary_entry(rcommands, i);
129
+ if (NUM2INT(rb_funcall(rtemp, rb_intern("length"), 0)) > 1) /*Character that need modkey press*/
130
+ {
131
+ /*Each char is separated from others by the + sign, so split up there*/
132
+ rparts = rb_str_split(rtemp, "+");
133
+ length2 = NUM2INT(rb_funcall(rparts, rb_intern("length"), 0));
134
+
135
+ /*Convert each character to it's corresponding keycode*/
136
+ rkeycodes = rb_ary_new();
137
+ for(j = 0; j < length2; j++)
138
+ rb_ary_push(rkeycodes, INT2NUM(get_keycode(p_display, rb_ary_entry(rparts, j))));
139
+
140
+ /*Insert the keypress events in the rcommands2 array. */
141
+ for(j = 0; j < length2; j++)
142
+ {
143
+ rentry = rb_ary_new();
144
+ rb_ary_push(rentry, rb_ary_entry(rkeycodes, j));
145
+ rb_ary_push(rentry, Qtrue);
146
+ rb_ary_push(rcommands2, rentry);
147
+ }
148
+
149
+ /*Insert the keyrelease events in the rcommands2 array, but in reverse
150
+ *order*/
151
+ for(j = length2 - 1; j >= 0; j--)
152
+ {
153
+ rentry = rb_ary_new();
154
+ rb_ary_push(rentry, rb_ary_entry(rkeycodes, j));
155
+ rb_ary_push(rentry, Qfalse);
156
+ rb_ary_push(rcommands2, rentry);
157
+ }
158
+ }
159
+ else /*Normal key*/
160
+ {
161
+ /*X stores the keysyms by the value of the unicode codepoint of a
162
+ *character - Ruby easily allows us to access the unicode codepoint of a
163
+ *character by using the String#ord method on an UTF-8-encoded character. */
164
+ rkeysym = rb_funcall(rtemp, rb_intern("ord"), 0);
165
+ /*Convert the keysym to the local keycode*/
166
+ rkeycode = INT2NUM(XKeysymToKeycode(p_display, NUM2INT(rkeysym)));
167
+
168
+ /*Down*/
169
+ rentry = rb_ary_new();
170
+ rb_ary_push(rentry, rkeycode);
171
+ rb_ary_push(rentry, Qtrue);
172
+ rb_ary_push(rcommands2, rentry);
173
+ /*Up*/
174
+ rentry = rb_ary_new();
175
+ rb_ary_push(rentry, rkeycode);
176
+ rb_ary_push(rentry, Qfalse);
177
+ rb_ary_push(rcommands2, rentry);
178
+ }
179
+ }
180
+ return rcommands2;
181
+ }
182
+
183
+ /********************Module functions**********************/
184
+
185
+ /*
186
+ *call-seq:
187
+ * Keyboard.key(str) ==> anArray
188
+ *
189
+ *Simulates one single key press. This method can also be used to send
190
+ *combinations like [CTRL]+[A] or [CTRL]+[ALT]+[DEL].
191
+ *===Parameters
192
+ *[+str+] The keypress to simulate. In order to press more than one key, separate them by a plus + sign.
193
+ *===Return value
194
+ *An array of how the +str+ has been interpreted. That means, +str+ split by plus.
195
+ *===Example
196
+ * #Sends a single lower a
197
+ * Imitator::X::Keyboard.key("a")
198
+ * #Sends a single upper A
199
+ * Imitator::X::Keyboard.key("Shift+a")
200
+ * #Sends [CTRL]+[L]
201
+ * Imitator::X::Keyboard.key("Ctrl+l")
202
+ * #Sends [CTRL]+[ALT]+[DEL]
203
+ * Imitator::X::Keyboard.key("Ctrl+Alt+Delete")
204
+ * #Sends character �
205
+ * Imitator::X::Keyboard.key("adiaeresis")
206
+ */
207
+ static VALUE m_key(VALUE self, VALUE keystr)
208
+ {
209
+ Display * p_display;
210
+ KeyCode * keycodes;
211
+ VALUE keys;
212
+ int arylen, i;
213
+
214
+ /*Split the keystr into single key names*/
215
+ keys = rb_str_split(keystr, "+");
216
+ /*Prepare conversion into an array of KeyCodes*/
217
+ arylen = NUM2INT(rb_funcall(keys, rb_intern("length"), 0));
218
+ keycodes = (KeyCode *) malloc(sizeof(KeyCode) * arylen);
219
+
220
+ p_display = XOpenDisplay(NULL);
221
+ XSetErrorHandler(handle_x_errors);
222
+
223
+ /*Convert the array of Ruby key names into one of KeyCodes*/
224
+ for(i = 0;i < arylen;i++)
225
+ keycodes[i] = get_keycode(p_display, rb_ary_entry(keys, i));
226
+
227
+ /*Press all buttons down*/
228
+ for(i = 0;i < arylen;i++)
229
+ XTestFakeKeyEvent(p_display, keycodes[i], True, CurrentTime);
230
+
231
+ /*Release all buttons in reverse order*/
232
+ for(i = arylen - 1; i >= 0; i--)
233
+ XTestFakeKeyEvent(p_display, keycodes[i], False, CurrentTime);
234
+
235
+ /*Cleanup actions*/
236
+ free(keycodes);
237
+ XSetErrorHandler(NULL);
238
+ XCloseDisplay(p_display);
239
+ return keys;
240
+ }
241
+
242
+ /*
243
+ *call-seq:
244
+ * Keyboard.simulate( text [, raw = false ] ) ==> aString
245
+ *
246
+ *Simulates the given sequence of characters as keypress and keyrelease
247
+ *events to the X server.
248
+ *===Parameters
249
+ *[+text+] The characters whose keystrokes you want to simulate.
250
+ *[+raw+] If true, escape sequences via { and } are ignored. See _Remarks_.
251
+ *===Return value
252
+ *The interpreted string. That is, the +text+ you passed in with minor modifications
253
+ *as ASCII TAB replaced by {TAB}.
254
+ *===Raises
255
+ *[XError] Invalid key name in escape sequence or ASCII Tab or Newline found in raw string.
256
+ *===Example
257
+ * #Simulate [A], [B] and [C] keystrokes
258
+ * Imitator::X::Keyboard.simulate("abc")
259
+ * #Simulate [A], [SHIFT]+[B] and [C] keystrokes
260
+ * Imitator::X::Keyboard.simulate("aBc")
261
+ * #Simulate [A], [ESC] and [B] keystrokes
262
+ * Imitator::X::Keyboard.simulate("a{ESC}b")
263
+ *===Remarks
264
+ *The +text+ parameter may contain special escape sequences which are included in braces
265
+ *{ and }. These are ignored if the +raw+ parameter is set to true, otherwise they cause the
266
+ *following keys to be pessed (and released, of course):
267
+ * Escape sequence | Resulting keystroke
268
+ * ================+=========================
269
+ * {Shift} | [SHIFT_L]
270
+ * ----------------+-------------------------
271
+ * {Alt} | [ALT_L]
272
+ * ----------------+-------------------------
273
+ * {AltGr} | [ALT_GR]
274
+ * ----------------+-------------------------
275
+ * {Ctrl} | [CTRL_L]
276
+ * ----------------+-------------------------
277
+ * {Win} | [WIN_L]
278
+ * ----------------+-------------------------
279
+ * {Super} | [WIN_L]
280
+ * ----------------+-------------------------
281
+ * {CapsLock} | [CAPS_LOCK}
282
+ * ----------------+-------------------------
283
+ * {Caps_Lock} | [CAPS_LOCK]
284
+ * ----------------+-------------------------
285
+ * {ScrollLock} | [SCROLL_LOCK]
286
+ * ----------------+-------------------------
287
+ * {Scroll_Lock} | [SCROLL_LOCK]
288
+ * ----------------+-------------------------
289
+ * {NumLock} | [NUM_LOCK]
290
+ * ----------------+-------------------------
291
+ * {Num_Lock} | [NUM_LOCK]
292
+ * ----------------+-------------------------
293
+ * {Del} | [DEL]
294
+ * ----------------+-------------------------
295
+ * {DEL} | [DEL]
296
+ * ----------------+-------------------------
297
+ * {Delete} | [DEL]
298
+ * ----------------+-------------------------
299
+ * {BS} | [BACKSPACE]
300
+ * ----------------+-------------------------
301
+ * {BackSpace} | [BACKSPACE]
302
+ * ----------------+-------------------------
303
+ * {Enter} | [RETURN]
304
+ * ----------------+-------------------------
305
+ * {Return} | [RETURN]
306
+ * ----------------+-------------------------
307
+ * {Tab} | [TAB]
308
+ * ----------------+-------------------------
309
+ * {TabStop} | [TAB]
310
+ * ----------------+-------------------------
311
+ * {PUp} | [PRIOR] (page up)
312
+ * ----------------+-------------------------
313
+ * {Prior} | [PRIOR] (page up)
314
+ * ----------------+-------------------------
315
+ * {PDown} | [NEXT] (page down)
316
+ * ----------------+-------------------------
317
+ * {Next} | [NEXT] (page down)
318
+ * ----------------+-------------------------
319
+ * {Pos1} | [HOME]
320
+ * ----------------+-------------------------
321
+ * {Home} | [HOME]
322
+ * ----------------+-------------------------
323
+ * {End} | [END]
324
+ * ----------------+-------------------------
325
+ * {Insert} | [INS]
326
+ * ----------------+-------------------------
327
+ * {Ins} | [INS]
328
+ * ----------------+-------------------------
329
+ * {INS} | [INS]
330
+ * ----------------+-------------------------
331
+ * {Pause} | [PAUSE]
332
+ * ----------------+-------------------------
333
+ * {Menu} | [MENU]
334
+ * ----------------+-------------------------
335
+ * {Esc} | [ESC]
336
+ * ----------------+-------------------------
337
+ * {ESC} | [ESC]
338
+ * ----------------+-------------------------
339
+ * {Escape} | [ESC]
340
+ * ----------------+-------------------------
341
+ * {F1}..{F12} | [F1]..[F12]
342
+ * ----------------+-------------------------
343
+ * {KP_0}..{KP_9} | [0]..[9] (keypad)
344
+ * ----------------+-------------------------
345
+ * {KP_Enter} | [ENTER] (keypad)
346
+ * ----------------+-------------------------
347
+ * {KP_Add} | [+] (keypad)
348
+ * ----------------+-------------------------
349
+ * {KP_Subtract} | [-] (keypad)
350
+ * ----------------+-------------------------
351
+ * {KP_Multiply} | [*] (keypad)
352
+ * ----------------+-------------------------
353
+ * {KP_Divide} | [/] (keypad)
354
+ * ----------------+-------------------------
355
+ * {KP_Separator} | [,] (keypad)
356
+ */
357
+ static VALUE m_simulate(int argc, VALUE argv[], VALUE self)
358
+ {
359
+ VALUE rtext, rraw, rcommands, rcommand, rtokens, rscanner, rtemp, rlast_post;
360
+ VALUE args[2];
361
+ int raw = 0;
362
+ int i, length;
363
+ KeyCode keycode;
364
+ Display * p_display;
365
+
366
+ rb_scan_args(argc, argv, "11", &rtext, &rraw);
367
+ if (RTEST(rraw))
368
+ raw = 1;
369
+
370
+ /*Ensure we're working with UTF-8-encoded strings*/
371
+ rtext = rb_str_export_to_enc(rtext, rb_utf8_encoding());
372
+
373
+ p_display = XOpenDisplay(NULL);
374
+ XSetErrorHandler(handle_x_errors);
375
+
376
+ if (raw) /*Raw string - no special keypresses*/
377
+ {
378
+ /*Convert the string into a sequence of keypress and keyrelease commands*/
379
+ rcommands = convert_rtext_to_rkeycodes(p_display, rtext);
380
+ length = NUM2INT(rb_funcall(rcommands, rb_intern("length"), 0));
381
+ /*Execute the commands*/
382
+ for(i = 0;i < length; i++)
383
+ {
384
+ rcommand = rb_ary_entry(rcommands, i);
385
+ keycode = NUM2INT(rb_ary_entry(rcommand, 0));
386
+ if (RTEST(rb_ary_entry(rcommand, 1))) /*Keypress*/
387
+ XTestFakeKeyEvent(p_display, keycode, True, CurrentTime);
388
+ else /*Keyrelease*/
389
+ XTestFakeKeyEvent(p_display, keycode, False, CurrentTime);
390
+ }
391
+ }
392
+ else /*With special keypresses in braces { and }. */
393
+ {
394
+ /*Ensure that ASCII newline and ASCII tab are treated correctly*/
395
+ rtext = rb_obj_dup(rtext); /*We don't want to change the original string*/
396
+ rb_funcall(rtext, rb_intern("gsub!"), 2, RUBY_UTF8_STR("\n"), RUBY_UTF8_STR("{Return}"));
397
+ rb_funcall(rtext, rb_intern("gsub!"), 2, RUBY_UTF8_STR("\t"), RUBY_UTF8_STR("{Tab}"));
398
+ /*We create a command array here, of form [ [string, bool], [string, bool] ],
399
+ *where the +string+s are the characters to simulate and +bool+ indicates
400
+ *wheather +string+ is one special key press or a sequence of normal ones. */
401
+ rtokens = rb_ary_new();
402
+ rlast_post = rtext; /*Needed for the case that no {...}-sequences are in the string*/
403
+ args[0] = rtext; /*We need only one argument here*/
404
+ /*Create a StringScanner object for tokenizing the input string*/
405
+ rscanner = rb_class_new_instance(1, args, rb_const_get(rb_cObject, rb_intern("StringScanner")));
406
+ /*Now scan until no special characters can be found anymore*/
407
+ while ( RTEST(rb_funcall(rscanner, rb_intern("scan_until"), 1, rb_reg_new("(.*?){(\\w+?)}", 13, 0))) )
408
+ {
409
+ /*Get the characters before the {...}-sequence and mark them as normal text*/
410
+ rtemp = rb_ary_new();
411
+ rb_ary_push(rtemp, rb_funcall(rscanner, rb_intern("[]"), 1, INT2FIX(1)));
412
+ rb_ary_push(rtemp, Qfalse);
413
+ rb_ary_push(rtokens, rtemp);
414
+
415
+ /*Get the characters in the {...}-sequence and mark them as one special char*/
416
+ rtemp = rb_ary_new();
417
+ rb_ary_push(rtemp, rb_funcall(rscanner, rb_intern("[]"), 1, INT2FIX(2)));
418
+ rb_ary_push(rtemp, Qtrue);
419
+ rb_ary_push(rtokens, rtemp);
420
+
421
+ /*Reset rlast_post; this ensures that we don't ignore text beyond the last {...}-sequence*/
422
+ rlast_post = rb_funcall(rscanner, rb_intern("post_match"), 0);
423
+ }
424
+ /*Append text beyond the last {...}-sequence to the command queue if there's any*/
425
+ if (!RTEST(rb_funcall(rlast_post, rb_intern("empty?"), 0)))
426
+ {
427
+ rtemp = rb_ary_new();
428
+ rb_ary_push(rtemp, rlast_post);
429
+ rb_ary_push(rtemp, Qfalse);
430
+ rb_ary_push(rtokens, rtemp);
431
+ }
432
+
433
+ /*Now execute the command queue*/
434
+ length = NUM2INT(rb_funcall(rtokens, rb_intern("length"), 0));
435
+ for(i = 0;i < length;i++)
436
+ {
437
+ /*Get the next command, of form [str, bool] where str is the string to simulate and bool wheather it's a special char or not*/
438
+ rtemp = rb_ary_entry(rtokens, i);
439
+ if (RTEST(rb_ary_entry(rtemp, 1))) /*This means we got a special character*/
440
+ {
441
+ keycode = get_keycode(p_display, rb_ary_entry(rtemp, 0));
442
+ XTestFakeKeyEvent(p_display, keycode, True, CurrentTime);
443
+ XTestFakeKeyEvent(p_display, keycode, False, CurrentTime);
444
+ }
445
+ else /*Just normal text*/
446
+ {
447
+ args[0] = rb_ary_entry(rtemp, 0);
448
+ args[1] = Qtrue;
449
+ /*Recursively call this method, sending the normal text with the "raw" parameter set to true*/
450
+ m_simulate(2, args, self);
451
+ }
452
+ /*Ensure that the event(s) got processed before we send more -
453
+ *otherwise it could happen that in a sequence "ab{BS}c" the [A], [B] and [C]
454
+ *keypresses are executed before the [BackSpace] press. */
455
+ XSync(p_display, False);
456
+ }
457
+ }
458
+
459
+ /*Cleanup actions*/
460
+ XSetErrorHandler(handle_x_errors);
461
+ XCloseDisplay(p_display);
462
+ return rtext;
463
+ }
464
+
465
+ /*
466
+ *call-seq:
467
+ * Keyboard.delete( del = false ) ==> nil
468
+ *
469
+ *Simulates a delete keystroke.
470
+ *===Parameters
471
+ *[+del+] (false) If true, [DEL] is simulated. Otherwise, [BACKSPACE].
472
+ *===Return value
473
+ *nil.
474
+ *===Example
475
+ * #Send a [BACKSPACE] keystroke
476
+ * Imitator::X::Keyboard.delete
477
+ * #Send a [DEL] keystroke
478
+ * Imitator::X::Keyboard.delete(true)
479
+ */
480
+ static VALUE m_delete(int argc, VALUE argv[], VALUE self)
481
+ {
482
+ Display * p_display;
483
+ VALUE del;
484
+ KeyCode keycode;
485
+
486
+ rb_scan_args(argc, argv, "01", &del);
487
+
488
+ p_display = XOpenDisplay(NULL);
489
+ XSetErrorHandler(handle_x_errors);
490
+
491
+ if (RTEST(del))
492
+ keycode = XKeysymToKeycode(p_display, XStringToKeysym("Delete"));
493
+ else
494
+ keycode = XKeysymToKeycode(p_display, XStringToKeysym("BackSpace"));
495
+
496
+ XTestFakeKeyEvent(p_display, keycode, True, CurrentTime);
497
+ XTestFakeKeyEvent(p_display, keycode, False, CurrentTime);
498
+
499
+ XSetErrorHandler(NULL);
500
+ XCloseDisplay(p_display);
501
+ return Qnil;
502
+ }
503
+
504
+ /*
505
+ *call-seq:
506
+ * Keyboard.down( key ) ==> nil
507
+ *
508
+ *Holds the specified key down.
509
+ *===Parameters
510
+ *[+key+] The key to hold down. This cannot be a key combination.
511
+ *===Return value
512
+ *nil.
513
+ *===Raises
514
+ *[XError] Invalid key specified.
515
+ *===Example
516
+ * #Hold the [A] key for one second down
517
+ * Imitator::X::Keyboard.down("a")
518
+ * sleep 1
519
+ * Imitator::X::Keyboard.up("a")
520
+ * #Hold [SHIFT]+[A] one second down. Since you cannot
521
+ * #send key combinations with this method, we need more than one call.
522
+ * Imitator::X::Keyboard.down("Shift")
523
+ * Imitator::X::Keyboard.down("a")
524
+ * sleep 1
525
+ * Imitator::X::Keyboard.up("a") #The release order doesn't matter, but...
526
+ * Imitator::X::Keyboard.up("Shift") #...I like this more.
527
+ *===Remarks
528
+ *Don't forget to release the key sometime...
529
+ */
530
+ static VALUE m_down(VALUE self, VALUE key)
531
+ {
532
+ Display * p_display;
533
+ KeyCode keycode;
534
+
535
+ p_display = XOpenDisplay(NULL);
536
+ XSetErrorHandler(handle_x_errors);
537
+
538
+ keycode = get_keycode(p_display, key);
539
+ XTestFakeKeyEvent(p_display, keycode, True, CurrentTime);
540
+
541
+ XSetErrorHandler(NULL);
542
+ XCloseDisplay(p_display);
543
+ return Qnil;
544
+ }
545
+
546
+ /*
547
+ *call-seq:
548
+ * Keyboard.up( key ) ==> nil
549
+ *
550
+ *Releases the specified key.
551
+ *===Parameters
552
+ *[+key+] The key to release. This cannot be a key combination.
553
+ *===Return value
554
+ *nil.
555
+ *===Raises
556
+ *[XError] Invalid key specified.
557
+ *===Example
558
+ * #Hold the [A] key for one second down
559
+ * Imitator::X::Keyboard.down("a")
560
+ * sleep 1
561
+ * Imitator::X::Keyboard.up("a")
562
+ * #Hold [SHIFT]+[A] one second down. Since you cannot
563
+ * #send key combinations with this method, we need more than one call.
564
+ * Imitator::X::Keyboard.down("Shift")
565
+ * Imitator::X::Keyboard.down("a")
566
+ * sleep 1
567
+ * Imitator::X::Keyboard.up("a") #The release order doesn't matter, but...
568
+ * Imitator::X::Keyboard.up("Shift") #...I like this more.
569
+ *===Remarks
570
+ *You may want to call Keyboard.down before calling this method?
571
+ */
572
+ static VALUE m_up(VALUE self, VALUE key)
573
+ {
574
+ Display * p_display;
575
+ KeyCode keycode;
576
+
577
+ p_display = XOpenDisplay(NULL);
578
+ XSetErrorHandler(handle_x_errors);
579
+
580
+ keycode = get_keycode(p_display, key);
581
+ XTestFakeKeyEvent(p_display, keycode, False, CurrentTime);
582
+
583
+ XSetErrorHandler(NULL);
584
+ XCloseDisplay(p_display);
585
+ return Qnil;
586
+ }
587
+
588
+ /*
589
+ *call-seq:
590
+ * Keyboard.method_missing(sym, *args, &block) ==> aString
591
+ * Keyboard.any_undefined_method() ==> aString
592
+ *
593
+ *Allows you to use shortcuts like <tt>Imitator::X::Keyboard.ctrl_c</tt>.
594
+ *===Return value
595
+ *The method name after being subject to some modifications (see _Remarks_).
596
+ *===Raises
597
+ *[ArgumentError] Calls with arguments aren't allowed.
598
+ *===Example
599
+ * #Instead of Imitator::X::Keyboard.key("Ctrl+c"):
600
+ * Imitator::X::Keyboard.ctrl_c
601
+ *===Remarks
602
+ *The method's name is split up by the underscore _ sign, then each
603
+ *resulting item is capitalized. At last, the items are joined by a plus + sign
604
+ *and send to the Keyboard.key method.
605
+ *This means, you cannot send the plus + or underscore _ signs (and whitespace, of course)
606
+ *this way.
607
+ */
608
+ static VALUE m_method_missing(int argc, VALUE argv[], VALUE self)
609
+ { /*def method_missing(sym, *args, &block)*/
610
+ VALUE rary, rkey;
611
+ int i, length;
612
+
613
+ if (argc > 1)
614
+ rb_raise(rb_eArgError, "Cannot turn arguments into method calls!");
615
+
616
+ /*Split the array up by _*/
617
+ rary = rb_str_split(argv[0], "_");
618
+ length = NUM2INT(rb_funcall(rary, rb_intern("length"), 0));
619
+
620
+ /*Capitalize every key*/
621
+ for(i = 0;i < length;i++)
622
+ rb_funcall(rb_ary_entry(rary, i), rb_intern("capitalize!"), 0);
623
+ /*Join the keys by a plus + sign*/
624
+ rkey = rb_ary_join(rary, RUBY_UTF8_STR("+"));
625
+
626
+ /*Call Keyboard.key with the joined string*/
627
+ m_key(self, rkey);
628
+
629
+ return rkey;
630
+ }
631
+ /*****************Init function***********************/
632
+
633
+ void Init_keyboard(void)
634
+ {
635
+ VALUE hsh;
636
+ VALUE charfile_path;
637
+
638
+ Keyboard = rb_define_module_under(X, "Keyboard");
639
+
640
+ /*It would be a very long and unneccassary work to define all the key combinations here.
641
+ *Instead, to shorten this and because YAML is in the Stdlib, I made a YAML file for this and load
642
+ *it. This also allows you to modify the alias list at runtime.
643
+ *Thanks to Ruby 1.9, the Gem module is loaded by default and I can use it to search for my character aliases file. */
644
+ if(RTEST(rb_gv_get("$imitator_x_charfile_path"))) /*This allows to override the default search path - important for debugging where the gem is not installed*/
645
+ charfile_path = rb_str_export_to_enc(rb_gv_get("$imitator_x_charfile_path"), rb_utf8_encoding());
646
+ else
647
+ {
648
+ /*Use Gem.find_files("imitator_x_special_chars.yml") to get the file*/
649
+ charfile_path = rb_funcall(rb_const_get(rb_cObject, rb_intern("Gem")), rb_intern("find_files"), 1, RUBY_UTF8_STR("imitator_x_special_chars.yml"));
650
+ /*Check if we found a file and raise an error if not*/
651
+ if (RTEST(rb_funcall(charfile_path, rb_intern("empty?"), 0)))
652
+ rb_raise(rb_eRuntimeError, "Could not find file 'imitator_x_special_chars.yml'!");
653
+ /*Get the first found file and ensure that we got it in UTF-8*/
654
+ charfile_path = rb_str_export_to_enc(rb_ary_entry(charfile_path, 0), rb_utf8_encoding());
655
+ }
656
+
657
+ if (RTEST(rb_gv_get("$DEBUG")))
658
+ rb_funcall(rb_mKernel, rb_intern("print"), 3, RUBY_UTF8_STR("Found key combination file at '"), charfile_path, RUBY_UTF8_STR(".'\n"));
659
+
660
+ /*Actually load the YAML file*/
661
+ hsh = rb_funcall(rb_const_get(rb_cObject, rb_intern("YAML")), rb_intern("load_file"), 1, charfile_path);
662
+
663
+ /*
664
+ *This hash contains key combinations for special characters like the euro sign.
665
+ *Since they're highly locale dependent, you are encouraged to modify the key sequences
666
+ *which have been tested on my German keyboard. If you want to change them permanently,
667
+ *have a look at the "imitator_x_special_chars.yml" file in Imitator for X's gem directory.
668
+ *See also the description of the Keyboard module for further information.
669
+ */
670
+ rb_define_const(Keyboard, "SPECIAL_CHARS", hsh);
671
+
672
+ hsh = rb_hash_new();
673
+ /*Modkey aliases*/
674
+ rb_hash_aset(hsh, RUBY_UTF8_STR("Shift"), RUBY_UTF8_STR("Shift_L"));
675
+ rb_hash_aset(hsh, RUBY_UTF8_STR("Control"), RUBY_UTF8_STR("Control_L"));
676
+ rb_hash_aset(hsh, RUBY_UTF8_STR("Alt"), RUBY_UTF8_STR("Alt_L"));
677
+ rb_hash_aset(hsh, RUBY_UTF8_STR("Ctrl"), RUBY_UTF8_STR("Control_L"));
678
+ rb_hash_aset(hsh, RUBY_UTF8_STR("AltGr"), RUBY_UTF8_STR("ISO_Level3_Shift"));
679
+ rb_hash_aset(hsh, RUBY_UTF8_STR("Win"), RUBY_UTF8_STR("Super_L"));
680
+ rb_hash_aset(hsh, RUBY_UTF8_STR("Super"), RUBY_UTF8_STR("Super_L"));
681
+ /*Special key aliases*/
682
+ rb_hash_aset(hsh, RUBY_UTF8_STR("Del"), RUBY_UTF8_STR("Delete"));
683
+ rb_hash_aset(hsh, RUBY_UTF8_STR("DEL"), RUBY_UTF8_STR("Delete"));
684
+ rb_hash_aset(hsh, RUBY_UTF8_STR("Ins"), RUBY_UTF8_STR("Insert"));
685
+ rb_hash_aset(hsh, RUBY_UTF8_STR("INS"), RUBY_UTF8_STR("Insert"));
686
+ rb_hash_aset(hsh, RUBY_UTF8_STR("BS"), RUBY_UTF8_STR("BackSpace"));
687
+ rb_hash_aset(hsh, RUBY_UTF8_STR("Enter"), RUBY_UTF8_STR("Return"));
688
+ rb_hash_aset(hsh, RUBY_UTF8_STR("TabStop"), RUBY_UTF8_STR("Tab"));
689
+ rb_hash_aset(hsh, RUBY_UTF8_STR("PUp"), RUBY_UTF8_STR("Prior"));
690
+ rb_hash_aset(hsh, RUBY_UTF8_STR("PDown"), RUBY_UTF8_STR("Next"));
691
+ rb_hash_aset(hsh, RUBY_UTF8_STR("Pos1"), RUBY_UTF8_STR("Home"));
692
+ rb_hash_aset(hsh, RUBY_UTF8_STR("ESC"), RUBY_UTF8_STR("Escape"));
693
+ rb_hash_aset(hsh, RUBY_UTF8_STR("Esc"), RUBY_UTF8_STR("Escape"));
694
+ rb_hash_aset(hsh, RUBY_UTF8_STR("CapsLock"), RUBY_UTF8_STR("Caps_Lock"));
695
+ rb_hash_aset(hsh, RUBY_UTF8_STR("ScrollLock"), RUBY_UTF8_STR("Scroll_Lock"));
696
+ rb_hash_aset(hsh, RUBY_UTF8_STR("NumLock"), RUBY_UTF8_STR("Num_Lock"));
697
+
698
+ /*
699
+ *This hash defines the aliases for keys. For example, the string "Ctrl"
700
+ *is mapped to "Control_L" what is the key name in X for the left Ctrl
701
+ *key. It's just for making life easier.
702
+ */
703
+ rb_define_const(Keyboard, "ALIASES", hsh);
704
+
705
+ rb_define_module_function(Keyboard, "key", m_key, 1);
706
+ rb_define_module_function(Keyboard, "simulate", m_simulate, -1);
707
+ rb_define_module_function(Keyboard, "delete", m_delete, -1);
708
+ rb_define_module_function(Keyboard, "down", m_down, 1);
709
+ rb_define_module_function(Keyboard, "up", m_up, 1);
710
+ rb_define_module_function(Keyboard, "method_missing", m_method_missing, -1);
711
+ }