imitator_x 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+ }