xdo 0.0.4

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.
@@ -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