xdo 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,125 @@
1
+ #Encoding: UTF-8
2
+ require_relative("../xdo")
3
+
4
+ module XDo
5
+
6
+ #Some methods to interact with disk drives.
7
+ #The value of the +drive+ parameter of many methods can be
8
+ #either a mount point like <tt>/media/my_cdrom</tt>, a device file like <tt>scd0</tt>
9
+ #or the default name "cdrom" for the default drive.
10
+ #
11
+ #If you don't pass in a drive name, the return value of #default will
12
+ #be used.
13
+ module Drive
14
+
15
+ class << self
16
+
17
+ #Opens a drive.
18
+ #===Parameters
19
+ #[drive] (<tt>default()</tt>) The drive to open.
20
+ #===Return value
21
+ # True.
22
+ #===Raises
23
+ #[XError] You're using a laptop whose drive has to be closed manually.
24
+ #[XError] +eject+ failed.
25
+ #===Example
26
+ # XDo::Drive.eject("scd0")
27
+ # XDo::Drive.eject("/media/my_cdrom")
28
+ #===Remarks
29
+ #This method may silently fail if the device is blocked by e.g. a
30
+ #CD burning program. Have a look at #release if you want to force
31
+ #it to open.
32
+ def eject(drive = default)
33
+ err = ""
34
+ Open3.popen3("#{XDo::EJECT} #{drive}"){|stdin, stdout, stderr| err << stderr.read}
35
+ raise(XDo::XError, err) unless err.empty?
36
+ true
37
+ end
38
+
39
+ #Closes a drive.
40
+ #===Parameters
41
+ #[drive] (<tt>default()</tt>) The drive to close.
42
+ #===Return value
43
+ #Undefined.
44
+ #===Raises
45
+ #[XError] +eject+ failed.
46
+ #===Example
47
+ # XDo::Drive.eject("scd0")
48
+ # XDo::Drive.close
49
+ #
50
+ # XDo::Drive.eject("/dev/my_cdrom")
51
+ # XDo::Drive.close("scd0") #A mount point doesn't make sense here
52
+ def close(drive = default)
53
+ err = ""
54
+ Open3.popen3("eject -t #{drive}"){|stdin, stdout, stderr| err << stderr.read}
55
+ raise(XDo::XError, err) unless err.empty?
56
+ end
57
+
58
+ #Returns the mount point of the default drive.
59
+ #You can use it as a value for a +drive+ parameter.
60
+ #===Return value
61
+ #The default drive's name. Usually <tt>"cdrom"</tt>.
62
+ #===Raises
63
+ #[XError] +eject+ failed.
64
+ #===Example
65
+ # p XDo::Drive.default #=> "cdrom"
66
+ # XDo::Drive.eject(XDo::Drive.default)
67
+ def default
68
+ err = ""
69
+ out = ""
70
+ Open3.popen3("#{XDo::EJECT} -d"){|stdin, stdout, stderr| out << stdout.read; err << stderr.read}
71
+ raise(XDo::XError, err) unless err.empty?
72
+ out.match(/`(.*)'/)[1]
73
+ end
74
+
75
+ #Locks a drive, so that it can't be opened by
76
+ #using the eject button or the +eject+ command.
77
+ #===Parameters
78
+ #[drive] (<tt>default()</tt>) The drive to lock.
79
+ #===Return value
80
+ #true.
81
+ #===Raises
82
+ #[XError] +eject+ failed.
83
+ #===Example
84
+ # XDo::Drive.lock("scd0")
85
+ # XDo::Drive.eject # fails
86
+ # XDo::Drive.release
87
+ # XDo::Drive.eject("scd0") #succeeds
88
+ #===Remarks
89
+ #Note that the lock doesn't get released if your process exits.
90
+ #You should probably register a at_exit handler to avoid confusion
91
+ #when your program exits with an exception.
92
+ def lock(drive = default)
93
+ err = ""
94
+ Open3.popen3("#{XDo::EJECT} -i on #{drive}"){|stdin, stdout, stderr| err << stderr.read}
95
+ raise(XDo::XError, err) unless err.empty?
96
+ true
97
+ end
98
+
99
+ #Unlocks a drive, so that it can be opened
100
+ #by neither the eject button nor the +eject+ command.
101
+ #===Parameters
102
+ #[drive] The drive to remove the lock from.
103
+ #===Return value
104
+ #true.
105
+ #===Raises
106
+ #[XError] +eject+ failed.
107
+ #===Example
108
+ # XDo::Drive.lock("scd0")
109
+ # XDo::Drive.eject # fails
110
+ # XDo::Drive.release
111
+ # XDo::Drive.eject("scd0") #succeeds
112
+ #===Remarks
113
+ #Use with caution. If a burning program locked the drive and
114
+ #you force it to open, the resulting CD-ROM is garbage.
115
+ def release(drive = default)
116
+ drive = default unless drive
117
+ err = ""
118
+ Open3.popen3("#{XDo::EJECT} -i off #{drive}"){|stdin, stdout, stderr| err << stderr.read}
119
+ raise(XDo::XError,err) unless err.empty?
120
+ true
121
+ end
122
+
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,381 @@
1
+ #Encoding: UTF-8
2
+ #This file is part of Xdo.
3
+ #Copyright © 2009, 2010 Marvin Gülker
4
+ # Initia in potestate nostra sunt, de eventu fortuna iudicat.
5
+ require_relative("../xdo")
6
+
7
+ module XDo
8
+
9
+ #A namespace encabsulating methods to simulate keyboard input. You can
10
+ #send input to special windows, just pass in the window's ID or a XWindow
11
+ #object via the +w_id+ parameter.
12
+ module Keyboard
13
+
14
+ #Aliases for key names in escape sequences.
15
+ ALIASES = {
16
+ "BS" => "BackSpace",
17
+ "BACKSPACE" => "BackSpace",
18
+ "DEL" => "Delete",
19
+ "ESC" => "Escape",
20
+ "INS" => "Insert",
21
+ "PGUP" => "Prior",
22
+ "PGDN" => "Next",
23
+ "NUM1" => "KP_End",
24
+ "NUM2" => "KP_Down",
25
+ "NUM3" => "KP_Next",
26
+ "NUM4" => "KP_Left",
27
+ "NUM5" => "KP_Begin",
28
+ "NUM6" => "KP_Right",
29
+ "NUM7" => "KP_Home",
30
+ "NUM8" => "KP_Up",
31
+ "NUM9" => "KP_Prior",
32
+ "NUM_DIV" => "KP_Divide",
33
+ "NUM_MUL" => "KP_Multiply",
34
+ "NUM_SUB" => "KP_Subtract",
35
+ "NUM_ADD" => "KP_Add",
36
+ "NUM_ENTER" => "KP_Enter",
37
+ "NUM_DEL" => "KP_Delete",
38
+ "NUM_COMMA" => "KP_Separator",
39
+ "NUM_INS" => "KP_Insert",
40
+ "NUM0" => "KP_0",
41
+ "CTRL" => "Control_L",
42
+ "ALT" => "Alt_L",
43
+ "ALT_GR" => "ISO_Level3_Shift",
44
+ "WIN" => "Super_L",
45
+ "SUPER" => "Super_L"
46
+ }.freeze
47
+
48
+ #The names of some keyboard symbols. The latest release of
49
+ #xdotool is capable of sending keysymbols directly, i.e.
50
+ # xdotool key Adiaeresis
51
+ #results in Ä being sent.
52
+ #This hash defines how those special characters can be
53
+ #sent. Feel free to add characters that are missing! You
54
+ #can use the +xev+ program to obtain their keycodes.
55
+ SPECIAL_CHARS = {
56
+ "ä" => "adiaeresis",
57
+ "Ä" => "Adiaeresis",
58
+ "ö" => "odiaeresis",
59
+ "Ö" => "Odiaeresis",
60
+ "ü" => "udiaeresis",
61
+ "Ü" => "Udiaeresis",
62
+ "ë" => "ediaeresis",
63
+ "Ë" => "Ediaeresis", #Does not work with xdotool
64
+ "ï" => "idiaeresis",
65
+ "Ï" => "Idiaeresis", #Does not work with xdotool
66
+ "ß" => "ssharp",
67
+ "\n" => "Return",
68
+ "\t" => "Tab",
69
+ "\b" => "BackSpace",
70
+ "§" => "section",
71
+ "[" => "bracketleft",
72
+ "]" => "bracketright",
73
+ "{" => "braceright",
74
+ "}" => "braceleft",
75
+ "@" => "at",
76
+ "€" => "EuroSign",
77
+ "|" => "bar",
78
+ "?" => "question"
79
+ }
80
+
81
+ class << self
82
+
83
+ #Types a character sequence, but without any special chars.
84
+ #===Parameters
85
+ #[+str+] The string to type.
86
+ #[+w_id+] (0) The ID of the window you want the input send to (or an XWindow object). 0 means the active window.
87
+ #===Return value
88
+ #nil.
89
+ #===Example
90
+ # XDo::Keyboard.type("test")
91
+ # XDo::Keyboard.type("täst") #=> I don't what key produces '�', skipping.
92
+ #===Remarks
93
+ #This function is a bit faster then #simulate.
94
+ def type(str, w_id = 0)
95
+ out = Open3.popen3("#{XDOTOOL} type #{w_id.nonzero? ? "--window #{w_id.to_i} " : ""}'#{str}'") do |stdin, stdout, stderr|
96
+ stdin.close_write
97
+ str = stderr.read
98
+ warn(str) unless str.empty?
99
+ end
100
+ nil
101
+ end
102
+
103
+ #Types a character sequence. You can use the escape sequence {...} to send special
104
+ #keystrokes.
105
+ #===Parameters
106
+ #[+str+] The string to simulate.
107
+ #[+raw+] (false) If true, escape sequences via {...} are disabled. See _Remarks_.
108
+ #[+w_id+] (0) The ID of the window you want the input send to (or an XWindow object). 0 means the active window.
109
+ #===Return value
110
+ #The string that was simulated.
111
+ #===Raises
112
+ #[ParseError] Your string was invalid.
113
+ #===Example
114
+ # XDo::Keyboard.simulate("test")
115
+ # XDo::Keyboard.simulate("täst")
116
+ # XDo::Keyboard.simulate("tex{BS}st")
117
+ #===Remarks
118
+ #This method recognizes many special chars like ? and ä, even if you disable
119
+ #the escape syntax {..} via setting the +raw+ parameter to true (that's the only way to send the { and } chars).
120
+ #
121
+ #+str+ may contain escape sequences in braces { and }. The letters between those two indicate
122
+ #what special character to send - this way you can simulate non-letter keypresses like [ESC]!
123
+ #You may use the following escape sequences:
124
+ # Escape seq. | Keystroke | Comment
125
+ # ============+===================+=================
126
+ # ALT | [Alt_L] |
127
+ # ------------+-------------------------------------
128
+ # ALT_GR | [ISO_Level3_Shift]| Not on USA
129
+ # | | keyboard
130
+ # ------------+-------------------------------------
131
+ # BS | [BackSpace] |
132
+ # ------------+-------------------------------------
133
+ # BACKSPACE | [BackSpace] |
134
+ # ------------+-------------------------------------
135
+ # CTRL | [Control_L] |
136
+ # ------------+-------------------------------------
137
+ # DEL | [Delete] |
138
+ # ------------+-------------------------------------
139
+ # END | [End] |
140
+ # ------------+-------------------------------------
141
+ # ESC | [Escape] |
142
+ # ------------+-------------------------------------
143
+ # INS | [Insert] |
144
+ # ------------+-------------------------------------
145
+ # HOME | [Home] |
146
+ # ------------+-------------------------------------
147
+ # MENU | [Menu] | Usually right-
148
+ # | | click menu
149
+ # ------------+-------------------------------------
150
+ # NUM0..NUM9 | [KP_0]..[KP_9] | Numpad keys
151
+ # ------------+-------------------------------------
152
+ # NUM_DIV | [KP_Divide] | Numpad key
153
+ # ------------+-------------------------------------
154
+ # NUM_MUL | [KP_Multiply] | Numpad key
155
+ # ------------+-------------------------------------
156
+ # NUM_SUB | [KP_Subtract] | Numpad key
157
+ # ------------+-------------------------------------
158
+ # NUM_ADD | [KP_Add] | Numpad key
159
+ # ------------+-------------------------------------
160
+ # NUM_ENTER | [KP_Enter] | Numpad key
161
+ # ------------+-------------------------------------
162
+ # NUM_DEL | [KP_Delete] | Numpad key
163
+ # ------------+-------------------------------------
164
+ # NUM_COMMA | [KP_Separator] | Numpad key
165
+ # ------------+-------------------------------------
166
+ # NUM_INS | [KP_Insert] | Numpad key
167
+ # ------------+-------------------------------------
168
+ # PAUSE | [Pause] |
169
+ # ------------+-------------------------------------
170
+ # PGUP | [Prior] | Page up
171
+ # ------------+-------------------------------------
172
+ # PGDN | [Next] | Page down
173
+ # ------------+-------------------------------------
174
+ # PRINT | [Print] |
175
+ # ------------+-------------------------------------
176
+ # SUPER | [Super_L] | Windows key
177
+ # ------------+-------------------------------------
178
+ # TAB | [Tab] |
179
+ # ------------+-------------------------------------
180
+ # WIN | [Super_L] | Windows key
181
+ def simulate(str, raw = false, w_id = 0)
182
+ raise(XDo::XError, "Invalid number of open and close braces!") unless str.scan(/{/).size == str.scan(/}/).size
183
+
184
+ tokens = tokenize(str)
185
+
186
+ tokens.each do |sym, s|
187
+ case sym
188
+ when :plain then type(s, w_id.to_i)
189
+ when :esc then
190
+ if raw
191
+ type("{#{s}}", w_id.to_i) #The braces should be preserved when using +raw+.
192
+ else
193
+ if ALIASES.has_key?(s)
194
+ key(ALIASES[s])
195
+ else
196
+ char(s.split("_").map(&:capitalize).join("_"), w_id.to_i)
197
+ end
198
+ end
199
+ when :special then
200
+ if SPECIAL_CHARS.has_key?(s)
201
+ char(SPECIAL_CHARS[s], w_id.to_i)
202
+ else
203
+ raise(XDo::ParseError, "No key symbol known for '#{s}'!")
204
+ end
205
+ else #Write a bug report if you get here. That really shouldn't happen.
206
+ raise(XDo::ParseError, "Invalid token named #{sym.inspect}! This is an internal error - please write a bug report at http://github.com/Quintus/Automations/issues or email me at sutniuq@@gmx@net.")
207
+ end
208
+ end
209
+ str
210
+ end
211
+
212
+ #Simulate a single char directly via the +key+ command of +xdotool+.
213
+ #===Parameters
214
+ #[+c+] A single char like "a" or a combination like "shift+a".
215
+ #[+w_id+] (0) The ID of the window you want the input send to (or an XWindow object). 0 means the active window.
216
+ #===Return value
217
+ #The +c+ you passed in.
218
+ #===Raises
219
+ #[XError] Invalid keyname.
220
+ #===Example
221
+ # XDo::Keyboard.char("a") #=> a
222
+ # XDo::Keyboard.char("A") #=> A
223
+ # XDo::Keyboard.char("ctrl+c")
224
+ def char(c, w_id = 0)
225
+ Open3.popen3("#{XDOTOOL} key #{w_id.nonzero? ? "--window #{w_id.to_i} " : ""}#{c}") do |stdin, stdout, stderr|
226
+ stdin.close_write
227
+ raise(XDo::XError, "Invalid character '#{c}'!") if stderr.read =~ /No such key name/
228
+ end
229
+ c
230
+ end
231
+ alias key char
232
+
233
+ #Holds a key down.
234
+ #===Parameters
235
+ #[+key+] The key to hold down.
236
+ #[+w_id+] (0) The ID of the window you want the input send to (or an XWindow object). 0 means the active window.
237
+ #===Return value
238
+ #+key+.
239
+ #===Raises
240
+ #[XError] Invalid keyname.
241
+ #===Example
242
+ # XDo::Keyboard.key_down("a")
243
+ # sleep 2
244
+ # XDo::Keyboard.key_up("a")
245
+ #===Remarks
246
+ #You should release the key sometime via Keyboard.key_up.
247
+ def key_down(key, w_id = 0)
248
+ Open3.popen3("#{XDOTOOL} keydown #{w_id.nonzero? ? "--window #{w_id.to_i} " : "" }#{check_for_special_key(key)}") do |stdin, stdout, stderr|
249
+ stdin.close_write
250
+ raise(XDo::XError, "Invalid character '#{key}'!") if stderr.read =~ /No such key name/
251
+ end
252
+ key
253
+ end
254
+
255
+ #Releases a key hold down by #key_down.
256
+ #===Parameters
257
+ #[+key+] The key to release.
258
+ #[+w_id+] (0) The ID of the window you want the input send to (or an XWindow object). 0 means the active window.
259
+ #===Return value
260
+ #+key+.
261
+ #===Raises
262
+ #[XError] Invalid keyname.
263
+ #===Example
264
+ # XDo::Keyboard.key_down("a")
265
+ # sleep 2
266
+ # XDo::Keyboard.key_up("a")
267
+ #===Remarks
268
+ #This has no effect on already released keys.
269
+ def key_up(key, w_id = 0)
270
+ Open3.popen3("#{XDOTOOL} keyup #{w_id.nonzero? ? "--window #{w_id.to_i} " : "" }#{check_for_special_key(key)}") do |stdin, stdout, stderr|
271
+ stdin.close_write
272
+ raise(XDo::XError, "Invalid character '#{key}'!") if stderr.read =~ /No such key name/
273
+ end
274
+ key
275
+ end
276
+
277
+ #Deletes a char.
278
+ #===Parameters
279
+ #[right] (false) If this is true, +del_char+ uses the DEL key for deletion, otherwise the BackSpace key.
280
+ #===Return value
281
+ #nil.
282
+ #===Example
283
+ # XDo::Keyboard.delete
284
+ # XDo::Keyboard.delete(true)
285
+ def delete(right = false)
286
+ Keyboard.simulate(right ? "\b" : "{DEL}")
287
+ nil
288
+ end
289
+
290
+ #Allows you to things like this:
291
+ # XDo::Keyboard.ctrl_c
292
+ #The string will be capitalized and every _ will be replaced by a + and then passed into #char.
293
+ #You can't use this way to send whitespace or _ characters.
294
+ def method_missing(sym, *args, &block)
295
+ super if args.size > 1 or block
296
+ char(sym.to_s.capitalize.gsub("_", "+"), args[0].nil? ? 0 : args[0])
297
+ end
298
+
299
+ private
300
+
301
+ #Tokenizes a string into an array of form
302
+ # [[:plain, "nonspecial"], [:special, "a"], [:esc, "INS"], ...]
303
+ def tokenize(str)
304
+ tokens = []
305
+ #We need a binary version of our string as StringScanner isn't able to work
306
+ #with encodings.
307
+ ss = StringScanner.new(str.dup.force_encoding("BINARY")) #String#force_encoding always returns self
308
+ until ss.eos?
309
+ pos = ss.pos
310
+ if ss.scan_until(/{/)
311
+ #Get the string between the last and the recent match. We have to subtract 2 here,
312
+ #since a StringScanner position is always ahead of the string character by 1 (since 0 in
313
+ #a SmallScanner means "before the first character") and the matched brace shouldn't be
314
+ #included.
315
+ tokens << [:plain, ss.string[Range.new(pos, ss.pos - 2)]] unless ss.pos == 1 #This means, the escape sequence is at the beginning of the string - no :plain text before.
316
+ pos = ss.pos
317
+ ss.scan_until(/}/)
318
+ tokens << [:esc, ss.string[Range.new(pos, ss.pos - 2)]] #See above for comment on -2
319
+ else #We're behind the last escape sequence now - there must be some characters left, otherwise this wouldn't be triggered.
320
+ tokens << [:plain, ss.rest]
321
+ ss.terminate
322
+ end
323
+ end
324
+ #Now hunt for special character like ä which can't be send using xdotool's type command.
325
+ regexp = Regexp.union(*SPECIAL_CHARS.keys.map{|st| st}) #Regexp.union escapes automatically, no need for Regexp.escape
326
+ tokens.map! do |ary|
327
+ #But first, we have to remedy from that insane forced encoding for StringScanner.
328
+ #Force every string's encoding back to the original encoding.
329
+ ary[1].force_encoding(str.encoding)
330
+ next([ary]) unless ary[0] == :plain #Extra array since we flatten(1) it afterwards
331
+ tokens2 = []
332
+ ss = StringScanner.new(ary[1])
333
+ until ss.eos?
334
+ pos = ss.pos
335
+ if ss.scan_until(regexp)
336
+ #Same as for the first StringScanner encoding problem goes here, but since I now have to use a UTF-8 regexp
337
+ #I have to put the string into the StringScanner as UTF-8, but because the StringScanner returns positions for
338
+ #a BINARY-encoded string I have to get the string, grep the position from the BINARY version and then reforce
339
+ #it to the correct encoding.
340
+ tokens2 << [:plain, ss.string.dup.force_encoding("BINARY")[Range.new(pos, ss.pos - 2)].force_encoding(str.encoding)] unless ss.pos == 1
341
+ tokens2 << [:special, ss.matched]
342
+ pos = ss.pos
343
+ else
344
+ tokens2 << [:plain, ss.rest]
345
+ ss.terminate
346
+ end
347
+ end
348
+ tokens2
349
+ end
350
+ #Make the token sequence 1-dimensional
351
+ tokens.flatten!(1)
352
+ #Now delete empty :plain tokens, they don't have to be handled.
353
+ #They are created by strings like "abc{ESC}{ESC}", where they are
354
+ #recognized between the two escapes.
355
+ #Empty escape sequences are an error in any case.
356
+ tokens.delete_if do |sym, st|
357
+ if st.empty?
358
+ if sym == :esc
359
+ raise(XDo::ParseError, "Empty escape sequence found!")
360
+ else
361
+ true
362
+ end
363
+ end
364
+ end
365
+
366
+ #Return the tokens array.
367
+ tokens
368
+ end
369
+
370
+ #Checks wheather +key+ is a special character (i.e. contained
371
+ #in the SPECIAL_CHARS hash) and returns the key symbol for it if so,
372
+ #otherwise returns +key+.
373
+ def check_for_special_key(key)
374
+ SPECIAL_CHARS.has_key?(key) ? SPECIAL_CHARS[key] : key
375
+ end
376
+
377
+ end
378
+
379
+ end
380
+
381
+ end