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/COPYING.LESSER.rdoc +169 -0
- data/COPYING.rdoc +678 -0
- data/README.rdoc +70 -0
- data/Rakefile.rb +99 -0
- data/TODO.rdoc +8 -0
- data/ext/clipboard.c +429 -0
- data/ext/clipboard.h +61 -0
- data/ext/extconf.rb +55 -0
- data/ext/keyboard.c +711 -0
- data/ext/keyboard.h +29 -0
- data/ext/mouse.c +456 -0
- data/ext/mouse.h +28 -0
- data/ext/x.c +84 -0
- data/ext/x.h +48 -0
- data/ext/xwindow.c +1434 -0
- data/ext/xwindow.h +38 -0
- data/lib/imitator/x/drive.rb +179 -0
- data/lib/imitator/x.rb +29 -0
- data/lib/imitator_x_special_chars.yml +179 -0
- data/test/test_clipboard.rb +46 -0
- data/test/test_keyboard.rb +117 -0
- data/test/test_mouse.rb +40 -0
- data/test/test_xdrive.rb +47 -0
- data/test/test_xwindow.rb +106 -0
- metadata +123 -0
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
|
+
}
|