Class: Autogui::Application

Inherits:
Object
  • Object
show all
Includes:
Logging, Windows::Handle, Windows::Process, Windows::Synchronize
Defined in:
lib/win32/autogui/application.rb

Overview

The Application class wraps a binary application so that it can be started and controlled via Ruby. This class is meant to be subclassed.

Examples:


class Calculator < Autogui::Application

  def initialize(options = {})
    defaults = {
                 :name => "calc",
                 :title => "Calculator",
                 :logger_logfile => 'log/calc.log'
               }
    super defaults.merge(options)
  end

  def edit_window
    main_window.children.find {|w| w.window_class == 'Edit'}
  end

  def dialog_about
    Autogui::EnumerateDesktopWindows.new.find do |w|
      w.title.match(/About Calculator/) && (w.pid == pid)
    end
  end

  def clear_entry
    set_focus
    keystroke(VK_DELETE)
  end
end

Constant Summary

Constants included from Logging

Logging::DEBUG, Logging::ERROR, Logging::FATAL, Logging::INFO, Logging::STANDARD_LOGGER, Logging::WARN

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Logging

#logger

Constructor Details

#initialize(options = {}) ⇒ Application

Returns a new instance of Application.

Examples:

initialize an application on the path


app = Application.new :name => "calc"

initialize with relative DOS path


app = Application.new :name => "binaries\\mybinary.exe"

initialize with full DOS path


app = Application.new :name => "\\windows\\system32\\calc.exe"

initialize with logging to file at the default WARN level (STDOUT logging is the default)


app = Application.new :name => "calc", :logger_logfile => 'log/calc.log', :logger_trunc => false

initialize with logging to file at DEBUG level


include Autogui::Logging
app = Application.new :name => "calc", :logger_logfile => 'log/calc.log', :logger.level => Autogui::Logging::DEBUG

initialize without logging to file and turn it on later


include Autogui::Logging
app = Application.new :name => "calc"
logger.logfile = 'app.log'

Parameters:

  • options (Hash) (defaults to: {})

    initialize options

Options Hash (options):

  • :name (String)

    a valid win32 exe name with optional path

  • :title (String)

    the application window title, used along with the pid to locate the application main window, defaults to :name

  • :parameters (Number)

    command line parameters used by Process.create

  • :create_process_timeout (Number) — default: 10

    timeout in seconds to wait for the create_process to return

  • :main_window_timeout (Number) — default: 10

    timeout in seconds to wait for main_window to appear

  • :logger_trunc (String) — default: true

    truncate the logger logfile at logfile initialization

  • :logger_logfile (String) — default: nil

    initialize logger’s output filename

  • :logger_level (String) — default: Autogui::Logging::WARN

    initialize logger’s initial level



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/win32/autogui/application.rb', line 130

def initialize(options = {})

  unless options.kind_of?(Hash)
    raise_error ArgumentError, 'Initialize expecting options to be a Hash'
  end

  @name = options[:name] || name
  @title = options[:title] || name
  @main_window_timeout = options[:main_window_timeout] || 10
  @create_process_timeout = options[:create_process_timeout] || 10
  @parameters = options[:parameters]

  # logger setup
  if options.include?(:logger_logfile)
    logger.trunc = options.include?(:logger_trunc) ? options[:logger_trunc] : true
  end
  logger.logfile = options[:logger_logfile] if options[:logger_logfile]
  logger.level = options[:logger_level] if options[:logger_level]

  # sanity checks
  raise_error 'application name not set' unless name

  start
end

Instance Attribute Details

#create_process_timeoutNumber

Returns the wait timeout in seconds used by Process.create.

Returns:

  • (Number)

    the wait timeout in seconds used by Process.create



91
92
93
# File 'lib/win32/autogui/application.rb', line 91

def create_process_timeout
  @create_process_timeout
end

#main_window_timeoutNumber

Returns the main_window wait timeout in seconds.

Returns:

  • (Number)

    the main_window wait timeout in seconds



88
89
90
# File 'lib/win32/autogui/application.rb', line 88

def main_window_timeout
  @main_window_timeout
end

#nameString

Returns the executable name of the application.

Returns:

  • (String)

    the executable name of the application



73
74
75
# File 'lib/win32/autogui/application.rb', line 73

def name
  @name
end

#parametersString

Returns the executable application parameters.

Returns:

  • (String)

    the executable application parameters



76
77
78
# File 'lib/win32/autogui/application.rb', line 76

def parameters
  @parameters
end

#pidNumber (readonly)

Returns the process identifier (PID) returned by Process.create.

Returns:

  • (Number)

    the process identifier (PID) returned by Process.create



82
83
84
# File 'lib/win32/autogui/application.rb', line 82

def pid
  @pid
end

#thread_idNumber (readonly)

Returns the process thread id returned by Process.create.

Returns:

  • (Number)

    the process thread id returned by Process.create



85
86
87
# File 'lib/win32/autogui/application.rb', line 85

def thread_id
  @thread_id
end

#titleString

Returns window title of the application.

Returns:

  • (String)

    window title of the application



79
80
81
# File 'lib/win32/autogui/application.rb', line 79

def title
  @title
end

Instance Method Details

#clipboardClipboard

Examples:

set the clipboard text and paste it with Control-V


@calculator.edit_window.set_focus
@calculator.clipboard.text = "12345"
@calculator.edit_window.text.strip.should == "0."
keystroke(VK_CONTROL, VK_V)
@calculator.edit_window.text.strip.should == "12,345."

Returns:



278
279
280
# File 'lib/win32/autogui/application.rb', line 278

def clipboard
  @clipboard ||= Autogui::Clipboard.new
end

#close(options = {}) ⇒ Object

Call the main_window’s close method

PostMessage SC_CLOSE and optionally wait for the window to close

Parameters:

  • options (Hash) (defaults to: {})

Options Hash (options):

  • :wait_for_close (Boolean) — default: true

    sleep while waiting for timeout or close

  • :timeout (Boolean) — default: 5

    wait_for_close timeout in seconds



231
232
233
# File 'lib/win32/autogui/application.rb', line 231

def close(options={})
  main_window.close(options)
end

#combined_textString

The main_window text including all child windows joined together with newlines. Faciliates matching text.

Examples:

partial match of the Window’s calulator’s about dialog copywrite text


dialog_about = @calculator.dialog_about
dialog_about.title.should == "About Calculator"
dialog_about.combined_text.should match(/Microsoft . Calculator/)

Returns:

  • (String)

    with newlines



264
265
266
# File 'lib/win32/autogui/application.rb', line 264

def combined_text
  main_window.combined_text if running?
end

#killObject

Send SIGKILL to force the application to die



236
237
238
# File 'lib/win32/autogui/application.rb', line 236

def kill
  Process::kill(9, pid)
end

#main_windowAutogui::Window

The application main window found by enumerating windows by title and application pid. This method will keep looking unit main_window_timeout (default: 10s) is exceeded.

Returns:

Raises:

  • (Exception)

    if the main window cannot be found

See Also:



200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/win32/autogui/application.rb', line 200

def main_window
  return @main_window if @main_window

  # pre sanity checks
  raise_error "calling main_window without a pid, application not initialized properly" unless @pid
  raise_error "calling main_window without a window title, application not initialized properly" unless @title

  timeout(main_window_timeout) do
    begin
      # There may be multiple instances, use title and pid to id our main window
      @main_window = Autogui::EnumerateDesktopWindows.new.find do |w|
        w.title.match(title) && w.pid == pid
      end
      sleep 0.1
    end until @main_window
  end

  # post sanity checks
  raise_error "cannot find main_window, check application title" unless @main_window

  @main_window
end

#running?Boolean

Returns if the application is currently running.

Returns:

  • (Boolean)

    if the application is currently running



241
242
243
# File 'lib/win32/autogui/application.rb', line 241

def running?
  main_window && (main_window.is_window?)
end

#set_focusNumber

Set the application input focus to the main_window

Returns:

  • (Number)

    nonzero number if sucess, nil or zero if failed



249
250
251
# File 'lib/win32/autogui/application.rb', line 249

def set_focus
  main_window.set_focus if running?
end

#startNumber

Start up the binary application via Process.create and set the window focus to the main_window

Returns:

  • (Number)

    the pid

Raises:

  • (Exception)

    if create_process_timeout exceeded

  • (Exception)

    if start failed for any reason other than create_process_timeout



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/win32/autogui/application.rb', line 163

def start

  command_line = name
  command_line = name + ' ' + parameters if parameters

  # returns a struct, raises an error if fails
  process_info = Process.create(
     :command_line => command_line,
     :close_handles => false,
     :creation_flags => Process::DETACHED_PROCESS
  )
  @pid = process_info.process_id
  @thread_id = process_info.thread_id
  process_handle = process_info.process_handle
  thread_handle = process_info.thread_handle

  # wait for process
  ret = WaitForInputIdle(process_handle, (create_process_timeout * 1000))

  # done with the handles
  CloseHandle(process_handle)
  CloseHandle(thread_handle)

  raise_error "start command failed on create_process_timeout" if ret == WAIT_TIMEOUT
  raise_error "start command failed while waiting for idle input, reason unknown" unless (ret == 0)
  @pid
end