Class: AX::Application

Inherits:
Element show all
Includes:
Accessibility::String
Defined in:
lib/ax/application.rb

Overview

The accessibility object representing the running application. This class contains some additional constructors and conveniences for Application objects.

As this class has evolved, it has gathered some functionality from the NSRunningApplication and NSBundle classes.

Attributes collapse

Actions collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Accessibility::String

#keyboard_events_for

Methods inherited from Element

#==, #actions, #ancestor, #ancestry, #application, #attributes, #blank?, #bounds, #children, #description, #inspect_subtree, #invalid?, #method_missing, #methods, #parameterized_attribute, #parameterized_attributes, #pid, #respond_to?, #search, #size_of, #to_point, #to_s

Methods included from Accessibility::PrettyPrinter

#pp_checkbox, #pp_children, #pp_identifier, #pp_position

Constructor Details

#initialize(arg) ⇒ Application

Standard way of creating a new application object

You can initialize an application object with either the process identifier (pid) of the application, the name of the application, an NSRunningApplication instance for the application, or an accessibility (AXUIElementRef) token.

Given a PID, we try to lookup the application and wrap it.

Given an NSRunningApplication instance, we simply wrap it.

Given a string we do some complicated magic to try and figure out if the string is a bundle identifier or the localized name of the application. Given a bundle identifier we try to launch the app if it is not already running, given a localized name we search the running applications for the app. We wrap what we get back if we get anything back.

Note however, given a bundle identifier to launch the application our implementation is a bit of a hack; I've tried to register for notifications, launch synchronously, etc., but there is always a problem with accessibility not being ready right away, so we will poll the app to see when it is ready with a timeout of ~10 seconds.

If this method fails to find an app then an exception will be raised.

Examples:


AX::Application.new 'com.apple.mail'
AX::Application.new 'Mail'
AX::Application.new 43567

Parameters:

  • arg (Number, String, NSRunningApplication)

111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/ax/application.rb', line 111

def initialize arg
  case arg
  when Fixnum
    super Accessibility::Element.application_for arg
    @app = NSRunningApplication.runningApplicationWithProcessIdentifier arg
  when String
    until @app
      @app =
       (
        app = NSRunningApplication.runningApplicationsWithBundleIdentifier arg
        app.first

       ) || (
         spin
         NSWorkspace.sharedWorkspace.runningApplications.find { |app|
           app.localizedName == arg
         }

       ) || (
         count ||= 0
         if AX::Application.launch arg
           spin 1
           count += 1
           raise "#{arg} failed to launch in time" if count == 10
         else
           raise "#{arg} is not a registered bundle identifier for the system"
         end
      )
    end
    super Accessibility::Element.application_for @app.processIdentifier
  when NSRunningApplication
    super Accessibility::Element.application_for arg.processIdentifier
    @app = arg
  else
    super arg # assume it is an AXUIElementRef
    @app = NSRunningApplication.runningApplicationWithProcessIdentifier pid
  end
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class AX::Element

Class Method Details

.dockAX::Application

Find and return the dock application

Returns:


31
32
33
# File 'lib/ax/application.rb', line 31

def dock
  new 'com.apple.dock'
end

.finderAX::Application

Find and return the dock application

Returns:


39
40
41
# File 'lib/ax/application.rb', line 39

def finder
  new 'com.apple.finder'
end

.frontmost_applicationAX::Application Also known as: frontmost_app

Find and return the application which is frontmost

This is often, but not necessarily, the same as the app that owns the menu bar.

Returns:


60
61
62
# File 'lib/ax/application.rb', line 60

def frontmost_application
  new NSWorkspace.sharedWorkspace.frontmostApplication
end

.launch(bundle) ⇒ Boolean

Asynchronously launch an application with given the bundle identifier

Parameters:

  • bundle (String)

    bundle identifier for the app

Returns:

  • (Boolean)

20
21
22
23
24
25
# File 'lib/ax/application.rb', line 20

def launch bundle
  NSWorkspace.sharedWorkspace.launchAppWithBundleIdentifier bundle,
                                                   options: NSWorkspace::NSWorkspaceLaunchAsync,
                            additionalEventParamDescriptor: nil,
                                          launchIdentifier: nil
end

Find and return the application which owns the menu bar

This is often, but not necessarily, the same as the app that is frontmost.

Returns:


72
73
74
# File 'lib/ax/application.rb', line 72

def menu_bar_owner
  new NSWorkspace.sharedWorkspace.menuBarOwningApplication
end

.notification_centerAX::Application

Find and return the notification center UI app

Obviously, this will only work on OS X 10.8+

Returns:


49
50
51
# File 'lib/ax/application.rb', line 49

def notification_center
  new 'com.apple.notificationcenterui'
end

Instance Method Details

#active?Boolean Also known as: focused, focused?

Ask the app whether or not it is the active app. This is equivalent to the dynamic #focused? method, but might make more sense to use in some cases.

Returns:

  • (Boolean)

174
175
176
177
# File 'lib/ax/application.rb', line 174

def active?
  spin
  @app.active?
end

#attribute(attr) ⇒ Object

Get the value of an attribute. This method will return nil if the attribute does not have a value or if the element is dead. The execption to the rule is that the :children attribute will always return an array unless the element does not have the :children attribute.

Examples:


element.attribute :position # => #<CGPoint x=123.0 y=456.0>

Parameters:

  • attr (#to_sym)

154
155
156
157
158
159
160
# File 'lib/ax/application.rb', line 154

def attribute attr
  case attr
  when :focused?, :focused then active?
  when :hidden?,  :hidden  then hidden?
  else super
  end
end

#bundle_identifierString

Get the bundle identifier for the application.

Examples:


safari.bundle_identifier 'com.apple.safari'
daylite.bundle_identifier 'com.marketcircle.Daylite'

Returns:

  • (String)

216
217
218
# File 'lib/ax/application.rb', line 216

def bundle_identifier
  @app.bundleIdentifier
end

#element_at(point) ⇒ AX::Element?

Find the element in the receiver that is at point given.

nil will be returned if there was nothing at that point.

Parameters:

Returns:


428
429
430
# File 'lib/ax/application.rb', line 428

def element_at point
  @ref.element_at(point).to_ruby
end

#hidden?Boolean

Ask the app whether or not it is hidden.

Returns:

  • (Boolean)

183
184
185
186
# File 'lib/ax/application.rb', line 183

def hidden?
  spin
  @app.hidden?
end

#hideBoolean

Note:

This is often async and may return before the action is completed

Ask the app to hide itself

Returns:

  • (Boolean)

276
277
278
# File 'lib/ax/application.rb', line 276

def hide
  perform :hide
end

#hold_modifier(key) ⇒ Number?

Press the given modifier key and hold it down while yielding to the given block.

Examples:


hold_key "\\CONTROL" do
  drag_mouse_to point
end

Parameters:

  • key (String)

Returns:

  • (Number, nil)

338
339
340
341
342
343
344
345
346
# File 'lib/ax/application.rb', line 338

def hold_modifier key
  code = EventGenerator::CUSTOM[key]
  raise ArgumentError, "Invalid modifier `#{key}' given" unless code
  @ref.post [[code, true]]
  yield
ensure
  @ref.post [[code,false]] if code
  code
end

#info_plistHash

Return the Info.plist data for the application. This is a plist file that all bundles in OS X must contain.

Many bits of metadata are stored in the plist, check the reference for more details.

Returns:

  • (Hash)

229
230
231
# File 'lib/ax/application.rb', line 229

def info_plist
  bundle.infoDictionary
end

#inspectObject

Override the base class to make sure the pid is included.


417
418
419
# File 'lib/ax/application.rb', line 417

def inspect
  super.sub! />$/, "#{pp_checkbox(:focused)} pid=#{pid}>"
end

Navigate the menu bar menus for the receiver. This method will not select the last item, but it will open each menu along the path.

You may also be interested in #select_menu_item.

Parameters:

  • path (String, Regexp)

Returns:

  • (AX::MenuItem)

373
374
375
376
377
378
379
380
381
382
383
384
385
# File 'lib/ax/application.rb', line 373

def navigate_menu *path
  perform :unhide # can't navigate menus unless the app is up front
  bar_item = item = self.menu_bar.menu_bar_item(title: path.shift)
  path.each do |part|
    item.perform :press
    next_item = item.menu_item(title: part)
    item = next_item
  end
  item
ensure
  # got to the end
  bar_item.perform :cancel unless item.title == path.last
end

#perform(name) ⇒ Boolean

Tell an object to trigger an action.

For instance, you can tell a button to call the same method that would be called when pressing a button, except that the mouse will not move over to the button to press it, nor will the keyboard be used.

Examples:


button.perform :press    # => true
button.perform :make_pie # => false

Parameters:

  • action (#to_sym)

Returns:

  • (Boolean)

    true if successful


291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
# File 'lib/ax/application.rb', line 291

def perform name
  case name
  when :terminate
    return true if terminated?
    @app.terminate; spin 0.25; terminated?
  when :force_terminate
    return true if terminated?
    @app.forceTerminate; spin 0.25; terminated?
  when :hide
    return true if hidden?
    @app.hide; spin 0.25; hidden?
  when :unhide
    return true if active?
    @app.activateWithOptions(NSRunningApplication::NSApplicationActivateIgnoringOtherApps)
    spin 0.25; active?
  else
    super
  end
end

#select_menu_item(*path) ⇒ AX::MenuItem

Navigate the menu bar menus for the receiver and select the menu item at the end of the given path. This method will open each menu in the path.

Examples:


safari.select_menu_item 'Edit', 'Find', /Google/

Parameters:

  • path (String, Regexp)

Returns:

  • (AX::MenuItem)

359
360
361
362
363
# File 'lib/ax/application.rb', line 359

def select_menu_item *path
  target = navigate_menu *path
  target.perform :press
  target
end

#set(attr, value) ⇒ Object

Set a writable attribute on the element to the given value.

Examples:


element.set :value, 'Hello, world!'
element.set :size,  [100, 200].to_size

Parameters:

  • attr (#to_sym)

Returns:

  • the value that you were setting is returned


196
197
198
199
200
201
202
203
204
205
# File 'lib/ax/application.rb', line 196

def set attr, value
  case attr
  when :focused
    perform(value ? :unhide : :hide)
  when :active, :hidden
    perform(value ? :hide : :unhide)
  else
    super
  end
end

#show_about_windowAX::Window

Show the "About" window for the app. Returns the window that is opened.

Returns:

  • (AX::Window)

392
393
394
395
396
# File 'lib/ax/application.rb', line 392

def show_about_window
  windows = self.children.select { |x| x.kind_of? AX::Window }
  select_menu_item self.title, /^About /
  wait_for(:window, parent: self) { |window| !windows.include?(window) }
end

#show_preferences_windowAX::Window

Note:

This method assumes that the app has setup the standard CMD+, hotkey to open the pref window

Try to open the preferences for the app. Returns the window that is opened.

Returns:

  • (AX::Window)

406
407
408
409
410
# File 'lib/ax/application.rb', line 406

def show_preferences_window
  windows = self.children.select { |x| x.kind_of? AX::Window }
  type "\\COMMAND+,"
  wait_for(:window, parent: self) { |window| !windows.include?(window) }
end

#terminateBoolean

Note:

This is often async and may return before the action is completed

Ask the app to quit

Returns:

  • (Boolean)

256
257
258
# File 'lib/ax/application.rb', line 256

def terminate
  perform :terminate
end

#terminate!Boolean

Note:

This is often async and may return before the action is completed

Force the app to quit

Returns:

  • (Boolean)

266
267
268
# File 'lib/ax/application.rb', line 266

def terminate!
  perform :force_terminate
end

#terminated?Boolean

Ask the app whether or not it is still running.

Returns:

  • (Boolean)

190
191
192
193
# File 'lib/ax/application.rb', line 190

def terminated?
  spin
  @app.terminated?
end

#type(string) ⇒ Boolean Also known as: type_string

Send keyboard input to the receiver, the control in the app that currently has focus will receive the key presses.

For details on how to format the string, check out the Keyboarding documentation.

Parameters:

  • string (String)

Returns:

  • (Boolean)

320
321
322
323
# File 'lib/ax/application.rb', line 320

def type string
  @ref.post keyboard_events_for string
  true
end

#unhideBoolean

Note:

This is often async and may return before the action is completed

As the app to unhide itself and bring to front

Returns:

  • (Boolean)

286
287
288
# File 'lib/ax/application.rb', line 286

def unhide
  perform :unhide
end

#versionString

Get the version string for the application.

Examples:


AX::Application.new("Safari").version    # => "5.2"
AX::Application.new("Terminal").version  # => "2.2.2"
AX::Application.new("Daylite").version   # => "3.15 (build 3664)"

Returns:

  • (String)

243
244
245
# File 'lib/ax/application.rb', line 243

def version
  bundle.objectForInfoDictionaryKey 'CFBundleShortVersionString'
end

#writable?(attr) ⇒ Object

Check whether or not an attribute is writable.

Examples:


element.writable? :size  # => true
element.writable? :value # => false

Parameters:

  • attr (#to_sym)

163
164
165
166
167
168
# File 'lib/ax/application.rb', line 163

def writable? attr
  case attr
  when :focused?, :focused, :hidden?, :hidden then true
  else super
  end
end