Class: AX::Application

Inherits:
Element show all
Includes:
Accessibility::Keyboard
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 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?, #screenshot, #search, #size_of, #to_h, #to_point, #to_s

Methods included from Accessibility::PrettyPrinter

#pp_checkbox, #pp_children, #pp_enabled, #pp_focused, #pp_identifier, #pp_position

Constructor Details

#initialize(arg) ⇒ Application

Note:

Initialization with bundle identifiers is case-sensitive (e.g. 'com.apple.iCal' is correct, 'com.apple.ical' is wrong)

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, the bundle identifier string (e.g. 'com.company.appName'), 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 578
AX::Application.new 'com.apple.iCal'
AX::Application.new 'Calendar'
AX::Application.new 3782
AX::Application.new 'com.apple.AddressBook'
AX::Application.new 'Contacts'
AX::Application.new 43567

Parameters:

  • arg (Number, String, NSRunningApplication)


121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/ax/application.rb', line 121

def initialize arg
  @app = case arg
         when String
           init_with_bundle_id(arg) || init_with_name(arg) || try_launch(arg)
         when Fixnum
           NSRunningApplication.runningApplicationWithProcessIdentifier arg
         when NSRunningApplication
           arg
         else # assume it is an AXUIElementRef (Accessibility::Element)
           NSRunningApplication.runningApplicationWithProcessIdentifier arg.pid
         end
  super Accessibility::Element.application_for @app.processIdentifier
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)


159
160
161
162
# File 'lib/ax/application.rb', line 159

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)


139
140
141
142
143
144
145
# File 'lib/ax/application.rb', line 139

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)


201
202
203
# File 'lib/ax/application.rb', line 201

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:



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

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

#hidden?Boolean

Ask the app whether or not it is hidden.

Returns:

  • (Boolean)


168
169
170
171
# File 'lib/ax/application.rb', line 168

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)


261
262
263
# File 'lib/ax/application.rb', line 261

def hide
  perform :hide
end

#hold_modifier(key) ⇒ Number?

Press the given modifier key and hold it down while yielding to the given block. As with #type, the key events apply to the control element which is currently focused.

Examples:


hold_modifier "\\CONTROL" do
  drag_mouse_to point
end

Parameters:

  • key (String)

Returns:

  • (Number, nil)


326
327
328
329
330
331
332
333
334
# File 'lib/ax/application.rb', line 326

def hold_modifier key
  code = EventGenerator::CUSTOM[key]
  raise ArgumentError, "Invalid modifier `#{key}' given" unless code
  KeyCoder.post_event([code, true])
  yield
ensure
  KeyCoder.post_event([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:



214
215
216
# File 'lib/ax/application.rb', line 214

def info_plist
  bundle.infoDictionary
end

#inspectObject

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



405
406
407
# File 'lib/ax/application.rb', line 405

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)


361
362
363
364
365
366
367
368
369
370
371
372
373
# File 'lib/ax/application.rb', line 361

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) ⇒ Object

See Also:



276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
# File 'lib/ax/application.rb', line 276

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)


347
348
349
350
351
# File 'lib/ax/application.rb', line 347

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



181
182
183
184
185
186
187
188
189
190
# File 'lib/ax/application.rb', line 181

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)


380
381
382
383
384
# File 'lib/ax/application.rb', line 380

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)


394
395
396
397
398
# File 'lib/ax/application.rb', line 394

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)


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

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)


251
252
253
# File 'lib/ax/application.rb', line 251

def terminate!
  perform :force_terminate
end

#terminated?Boolean

Ask the app whether or not it is still running.

Returns:

  • (Boolean)


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

def terminated?
  spin
  @app.terminated?
end

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

Send keyboard input to the focused control element; this is not necessarily an element belonging to the receiving app.

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

Parameters:

  • string (String)

Returns:

  • (Boolean)


305
306
307
308
309
310
# File 'lib/ax/application.rb', line 305

def type string
  perform(:unhide) unless focused?
  keyboard_events_for(string).each do |event|
    KeyCoder.post_event event
  end
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)


271
272
273
# File 'lib/ax/application.rb', line 271

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)


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

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)


148
149
150
151
152
153
# File 'lib/ax/application.rb', line 148

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