├── readme.md └── autoitwrapper.rb /readme.md: -------------------------------------------------------------------------------- 1 | # Ruby Wrapper for AutoIt 2 | 3 | **A wrapper for the AutoItX3 DLL on windows to control the windows API** 4 | 5 | ## What is AutoIt? 6 | 7 | _Taken from the [AutoIt Website](http://www.autoitscript.com/autoit3/index.shtml)_ 8 | 9 | AutoIt v3 is a freeware BASIC-like scripting language designed for automating the Windows GUI and general scripting. It uses a combination of simulated keystrokes, mouse movement and window/control manipulation in order to automate tasks in a way not possible or reliable with other languages (e.g. VBScript and SendKeys). AutoIt is also very small, self-contained and will run on all versions of Windows out-of-the-box with no annoying "runtimes" required! 10 | 11 | AutoIt was initially designed for PC "roll out" situations to reliably automate and configure thousands of PCs. Over time it has become a powerful language that supports complex expressions, user functions, loops and everything else that veteran scripters would expect. 12 | 13 | **Also supplied is a combined COM and DLL version of AutoIt called AutoItX that allows you to add the unique features of AutoIt to your own favourite scripting or programming languages!** 14 | 15 | ## Problems with AutoIt and how Ruby can help 16 | 17 | To use AutoIt normally you have to learn an entirely new programming language. This language is similar to Visual Basic, and doesn't have support for a lot of Ruby's advanced features. 18 | 19 | The goal of this project is to bring Windows API access and automation to Ruby. Hopefully this will attract more people to using Ruby on Windows and suppress all doubts that Windows is not a first-class language for windows. 20 | 21 | ## Example Use 22 | 23 | 1. The AutoItX3 DLL must be installed using the [main AutoIt installer](http://www.autoitscript.com/autoit3/downloads.shtml). 24 | 25 | 2. Include the file in your project like this: 26 | 27 | require 'autoitwrapper.rb' # make sure the file is in the same directory 28 | include AutoIt # useful if you don't want to always use AutoIt:: before everything 29 | 30 | 3. Start automating windows. (TODO: more examples to come) 31 | 32 | # Clipboard Access 33 | Clipboard.puts "I love ruby!", " Go to http://github.com/stevenheidel/ruby-autoit to contribute." 34 | Clipboard.gets.upcase[0..11] #=> "I LOVE RUBY!" 35 | 36 | 4. Read the documentation for how to use. (TODO: generate RDoc documentation) 37 | 38 | 5. Contribute your opinions on how you'd like to be able to use AutoIt from Ruby. 39 | 40 | ## Contribute 41 | 42 | Things to do: 43 | 44 | - Add remainder of functions as described in the AutoItX3 help file 45 | - Standardize the return values and error outcomes 46 | - Send me ideas on what functions/classes should do in order to make the most sense 47 | - Add tests, documentation, and more examples 48 | 49 | ## Useful Links 50 | 51 | - [AutoItX3 Support Forum](http://www.autoitscript.com/forum/index.php?s=2d5bc3fac66e734a24edf8caaa6a1842&showforum=14) 52 | - [Good idea to make a library](http://tech.waltco.biz/2008/02/23/use-swig-to-build-a-ruby-extension-to-wrap-a-windows-dll/) 53 | - [Useful Introduction](http://actsasbuffoon.wordpress.com/2008/12/30/introduction-to-autoitx3/) 54 | * [Eventual Goal: No relying on AutoIt](http://raa.ruby-lang.org/project/win32-guitest/) -------------------------------------------------------------------------------- /autoitwrapper.rb: -------------------------------------------------------------------------------- 1 | # Ruby Wrapper for the AutoItX3 DLL/COM interface 2 | # Useful for automating Windows or testing programs 3 | # 4 | # Author:: Steven Heidel (http://www.livingskyweb.ca) 5 | # Contributors:: All help is greatly appreciated 6 | # Copyright:: Copyright (c) 2010 Steven Heidel 7 | # License:: Distributes under the same terms as Ruby 8 | 9 | # This module holds all classes, methods, and the all important 10 | # WIN32OLE AI Object. Loading of this module will fail if the 11 | # AutoItX3 program is not installed correctly. 12 | 13 | module AutoIt 14 | 15 | require 'win32ole' 16 | require 'Win32API' # Will be needed later 17 | 18 | # Make sure the DLL is registered 19 | begin 20 | AI = WIN32OLE.new('AutoItX3.Control') 21 | rescue 22 | raise "AutoItX3 is not installed properly" 23 | end 24 | 25 | # GENERAL METHODS 26 | 27 | # Sets some general AutoIt options 28 | # Returns:: nil 29 | def autoit_options=(options = {}) 30 | options.each do |key, value| 31 | AI.AutoItSetOption(key.to_s, value) 32 | end 33 | # FIXME: return old option values 34 | end 35 | 36 | # Blocks user input, very useful 37 | # Returns:: nil 38 | def block_input 39 | AI.BlockInput(1) 40 | end 41 | 42 | # Unblocks user input, don't forget 43 | # Returns:: nil 44 | def unblock_input 45 | AI.BlockInput(0) 46 | end 47 | 48 | # Detect whether user has admistrator priveleges 49 | # Returns:: true or false 50 | def windows_admin? 51 | true if AI.IsAdmin == 1 52 | false if AI.IsAdmin == 0 53 | end 54 | 55 | # Shuts down the computer, good luck testing this one 56 | # Returns:: 1 on success, 0 on failure 57 | def shutdown(options = {}) 58 | code = 0 59 | code += 1 if options[:shutdown] 60 | code += 2 if options[:reboot] 61 | code += 4 if options[:force] 62 | code += 8 if options[:powerdown] 63 | AI.Shutdown(code) 64 | end 65 | 66 | 67 | # This class holds the clipboard object. Currently the new 68 | # method does nothing, use get and put to interact. 69 | 70 | class Clipboard 71 | 72 | # Get the last text copied to the clipboard 73 | # Returns:: text or 0 on error 74 | def self.gets 75 | AI.ClipGet 76 | # FIXME: return false or raise error 77 | end 78 | 79 | # Use like "puts" to copy text to the clipboard 80 | # Returns:: nil on success or 0 on error 81 | def self.puts(*args) 82 | string = "" 83 | args.each do |arg| 84 | string += arg.to_s 85 | end 86 | 87 | AI.ClipPut(string) 88 | # FIXME: return true and false or raise error 89 | end 90 | end 91 | 92 | 93 | # This class deals with the mapping of network drives. 94 | 95 | class DriveMap 96 | 97 | # New 98 | # Returns:: nil 99 | def initialize 100 | 101 | end 102 | 103 | def add 104 | 105 | end 106 | 107 | def get 108 | 109 | end 110 | 111 | def del 112 | 113 | end 114 | end 115 | 116 | 117 | # This class deals with ini files. The filename is stored so 118 | # each instance should deal with a seperate ini file. 119 | 120 | class Ini 121 | 122 | # Checks to see if file exists then stores the filename 123 | # Returns:: 124 | def initialize(filename) 125 | raise "Ini file does not exist" unless File.exist?(filename) 126 | @filename = filename 127 | end 128 | 129 | # Reads the key from a particular section 130 | # Returns:: the key or nil if not found 131 | def read(section, key) 132 | AI.IniRead(@filename, section, key, nil) 133 | end 134 | 135 | # Writes the value to the key in a particular section 136 | # Returns:: 1 on success, 0 on failure 137 | def write(section, key, value) 138 | AI.IniWrite(@filename, section, key, value) 139 | # FIXME: return true and false or raise error 140 | end 141 | 142 | # Deletes a key or section if no key is specified 143 | # Returns:: 1 on success, 0 on failure 144 | def delete(section, key = nil) 145 | if key.nil? 146 | AI.IniDelete(@filename, section) 147 | else 148 | AI.IniDelete(@filename, section, key) 149 | end 150 | # FIXME: return true and false or raise error 151 | end 152 | end 153 | 154 | 155 | # This class deals with pixels on the screen. This is for 156 | # one pixel only. 157 | 158 | class Pixel 159 | 160 | # Stores the colour of the pixel at x, y 161 | # Returns:: nil 162 | def initialize(x, y) 163 | @colour = AI.PixelGetColour(x, y) 164 | end 165 | 166 | # Gets the colour of the pixel 167 | # Returns:: colour 168 | def colour 169 | @colour.to_s 170 | end 171 | 172 | # Determines whether the pixel has changed (Minesweeper anyone?) 173 | # Returns:: true or false 174 | def changed? 175 | @colour != AI.PixelGetColour(x, y) 176 | end 177 | end 178 | 179 | # This class deals with rectangular areas of pixels 180 | # on the screen. Will eventually include OCR. 181 | 182 | 183 | class PixelArea 184 | 185 | # Generates a checksum for later use on the pixel area 186 | # Returns:: the checksum 187 | def initialize(left, top, right, bottom, step = 1) 188 | @left = left 189 | @top = top 190 | @right = right 191 | @bottom = bottom 192 | @checksum = AI.PixelChecksum(left, top, right, bottom, step) 193 | end 194 | 195 | # Find a colour in an area 196 | # Returns:: coordinates of first colour found or 1 if not found 197 | def search(colour, variation = 0, step = 1) 198 | AI.PixelSearch(left, top, right, bottom, colour, shade-variation, step) 199 | end 200 | 201 | # Determine if anything has changed in the area 202 | # Returns:: true or false 203 | def changed?(step = 1) 204 | @checksum != AI.PixelChecksum(@left, @top, @right, @bottom, step) 205 | end 206 | end 207 | 208 | 209 | # This class deals with the keyboad and simulating key 210 | # presses. Refer to the AutoIt documentation for synthax. 211 | 212 | class Keyboard 213 | 214 | # Send a series of keys using strings 215 | # Returns:: 216 | def self.send(*args) 217 | args.each do |arg| 218 | AI.send(arg.to_s) 219 | end 220 | # FIXME: check for errors 221 | end 222 | end 223 | 224 | 225 | # This class deals with deals with displaying tooltips. 226 | # X and Y coordinates default to mouse position. 227 | 228 | class ToolTip 229 | 230 | # Begin to display a tooltip 231 | # Returns:: nothing 232 | def initialize(text, x=nil, y=nil) 233 | if x.nil? || y.nil? 234 | AI.ToolTip(text) 235 | else 236 | AI.ToolTip(text, x, y) 237 | end 238 | # FIXME: Check for existance of other tooltips 239 | # FIXME: Better support for multi-line tooltips 240 | end 241 | 242 | # Stops displaying the current tooltip 243 | # Returns:: nothing 244 | def destroy 245 | AI.ToolTip("") 246 | end 247 | end 248 | 249 | 250 | # This class deals with the simulation of mouse movement and 251 | # mouse clicks. Uses absolute screen coordinates. 252 | 253 | class Mouse 254 | 255 | # Clicks the mouse based on an hash of options 256 | # Returns:: 1 on success 257 | def self.click(options = {}) 258 | AI.MouseClick(options[:button].to_s, options[:x], options[:y], options[:clicks], options[:speed]) 259 | # FIXME: better options and return values 260 | end 261 | 262 | # Click and drag from point 1 to point 2 263 | # Returns:: 1 on success 264 | def self.click_drag(options = {}) 265 | options[:x_start] ||= posx 266 | options[:y_start] ||= posy 267 | AI.MouseClickDrag(options[:button].to_s, options[:x_start], options[:y_start], options[:x], options[:y], options[:speed]) 268 | end 269 | 270 | # Set the left mouse as either clicked or unclicked 271 | # Returns:: 1 on success 272 | def self.primary_state=(state) 273 | AI.MouseDown("left") if state == "down" 274 | AI.MouseUp("left") if state == "up" 275 | end 276 | 277 | # Set the right mouse as either clicked or unclicked 278 | # Returns:: 1 on success 279 | def self.secondary_state=(state) 280 | AI.MouseDown("right") if state == "down" 281 | AI.MouseUp("right") if state == "up" 282 | end 283 | 284 | # Gets the cursor type, refer to AutoIt for legend 285 | # Returns:: cursor type constant 286 | def self.cursor 287 | AI.MouseGetCursor 288 | end 289 | 290 | # Get the current x position of the mouse 291 | # Returns:: x position 292 | def self.posx 293 | AI.MouseGetPosX 294 | end 295 | 296 | # Get the current y position of the mouse 297 | # Returns:: y position 298 | def self.posy 299 | AI.MouseGetPosY 300 | end 301 | 302 | # Get the current position of the mouse as a hash 303 | # Returns:: position 304 | def self.pos 305 | {:x => posx, :y => posy} 306 | end 307 | 308 | # Moves the mouse pointer to a specified position 309 | # Returns:: 1 on success 310 | def self.move(x, y, speed = 10) 311 | AI.MouseMove(x, y, speed) 312 | end 313 | 314 | # Scrolls the mouse wheel 315 | # Returns:: 1 on success 316 | def self.wheel(direction, clicks = 1) 317 | AI.MouseWheel(direction, clicks) 318 | end 319 | end 320 | 321 | 322 | # This class deals with processes, including the running of 323 | # programs, scripts, etc. 324 | 325 | class Process 326 | 327 | # Runs an executable unless it already exists 328 | # Returns:: multiple possibilities 329 | def initialize(name, options = {}) 330 | # Check if it already exists 331 | unless (@process_id = AI.ProcessExists(name)) == 0 332 | return true 333 | end 334 | 335 | # Check if running as another user 336 | if options[:user] && options[:domain] && options[:password] 337 | AI.RunAsSet(options[:user], options[:domain], options[:password]) 338 | end 339 | 340 | # How to start program 341 | flag = case options[:flag] 342 | when :hide then AI.SW_HIDE 343 | when :minimize then AI.SW_MINIMIZE 344 | when :maximize then AI.SW_MAXIMIZE 345 | else nil 346 | end 347 | 348 | # Are we waiting or not 349 | if options[:wait] 350 | AI.RunWait(name, options[:workingdir], flag) 351 | else 352 | @process_id = AI.Run(name, options[:workingdir], flag) 353 | end 354 | end 355 | 356 | # Terminates the process 357 | # Returns:: 1 on success 358 | def destroy 359 | AI.ProcessClose(@process_id) 360 | end 361 | 362 | # Sets the process priority 363 | # Returns:: 1 on success 0 on failure 364 | def priority=(priority) 365 | AI.ProcessSetPriority(@process_id, priority) 366 | end 367 | 368 | # FIXME: This class doesn't make a lot of sense and is missing some functions 369 | end 370 | 371 | 372 | # Class description 373 | 374 | class Registry 375 | 376 | # Reads a value from the registry 377 | # Returns:: the value or error code 378 | def self.read(key, value) 379 | AI.RegRead(key, value) 380 | end 381 | 382 | # Writes a value of type to the registry 383 | # Returns:: 1 on success, 0 on failure 384 | def self.write(key, valuename, type, value) 385 | AI.RegWrite(key, valuename, type, value) 386 | end 387 | 388 | # Deletes a value or an entire key, DANGEROUS! 389 | # Returns:: 1 on success, 0 on failure 390 | def self.delete(key, value) 391 | if value 392 | AI.RegDeleteKey(key) 393 | else 394 | AI.RegDeleteVal(key, value) 395 | end 396 | end 397 | 398 | # Gets a list of keys under the specified key 399 | # Returns:: an array of size number 400 | def self.keys(key, number = 1) 401 | ret = [] 402 | number.times do |i| 403 | ret << AI.RegEnumKey(key, i) 404 | end 405 | ret 406 | end 407 | 408 | # Gets a list of values under the specified key 409 | # Returns:: an array of size number 410 | def self.values(key, number = 1) 411 | ret = [] 412 | number.times do |i| 413 | ret << AI.RegEnumValue(key, i) 414 | end 415 | ret 416 | end 417 | end 418 | 419 | 420 | # This mega-class deals with windows and window 421 | # management. Controls are a subset of windows. 422 | 423 | class Window 424 | 425 | # New 426 | def initialize 427 | 428 | end 429 | 430 | 431 | # This class deals with the controls inside windows. 432 | # These methods are more reliable than mouse/keyboard ones. 433 | 434 | class Control 435 | 436 | # New 437 | def initialize 438 | 439 | end 440 | end 441 | end 442 | 443 | private 444 | 445 | 446 | # Converts the weird AutoIt returns into familiar true/false 447 | # Also puts the Auto It Error (or "airer" :) to $stderr 448 | def airer(value, show_error = false) 449 | $stderr.puts AI.Error if show_error 450 | 451 | value != 0 452 | end 453 | 454 | end --------------------------------------------------------------------------------