dogtail.rb 4.73 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
module Dogtail
  module Mouse
    LEFT_CLICK = 1
    MIDDLE_CLICK = 2
    RIGHT_CLICK = 3
  end

  # We want to keep this class immutable so that handles always are
  # left intact when doing new (proxied) method calls.  This was we
  # can support stuff like:
  #
  #     Dogtail::Application.interact('gedit') do |app|
  #         menu = app.menu('Menu')
  #         menu.click()
  #         menu.something_else()
  #         menu.click()
  #     end
  #
  # i.e. the object referenced by `menu` is never modified by method
  # calls and can be used as expected. This explains why
  # `proxy_call()` below returns a new instance instead of adding
  # appending the new component the proxied method call would result
  # in.

  class ScriptProxy

    def initialize(init_lines = [], init_components = [], opts = {})
      @module_import_lines = []
      @init_lines = init_lines
      @opts = opts
      @components = init_components
    end

    def build_script(lines)
      (
        ["#!/usr/bin/python"] +
        @module_import_lines +
        @init_lines +
        lines
      ).join("\n")
    end

anonym's avatar
anonym committed
43
44
45
46
    def build_line
      @components.join('.')
    end

47
    def run(lines = nil)
48
      @opts[:user] ||= LIVE_USER
49
50
51
      lines ||= [build_line]
      lines = [lines] if lines.class != Array
      script = build_script(lines)
52
53
54
55
56
57
58
59
60
61
62
63
      script_path = $vm.execute_successfully('mktemp', @opts).stdout.chomp
      $vm.file_overwrite(script_path, script, @opts[:user])
      args = ["/usr/bin/python '#{script_path}'", @opts]
      if @opts[:allow_failure]
        $vm.execute(*args)
      else
        $vm.execute_successfully(*args)
      end
    ensure
      $vm.execute("rm -f '#{script_path}'")
    end

64
    def self.args_to_s(args)
65
66
67
68
69
      args_list = args
      args_hash = nil
      if args_list.class == Array && args_list.last.class == Hash
        *args_list, args_hash = args_list
      end
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
      argify = Proc.new do |v|
        if v == true
          'True'
        elsif v == false
          'False'
        elsif v.class == String
          "\"#{v}\""
        elsif [Fixnum, Float].include?(v.class)
          v.to_s
        else
          raise "#{self.class.name} does not know how to handle argument type '#{v.class}'"
        end
      end
      (
        (args_list.nil? ? [] : args_list.map { |e| argify.call(e) }) +
        (args_hash.nil? ? [] : args_hash.map { |k, v| "#{k}=#{argify.call(v)}" })
86
      ).join(', ')
87
88
89
90
    end

    def proxy_call(method, args)
      args_str = self.class.args_to_s(args)
91
92
93
94
95
      component = "#{method.to_s}(#{args_str})"
      final_components = @components + [component]
      self.class.new(@init_lines, final_components,  @opts)
    end

anonym's avatar
anonym committed
96
97
98
    def get_field(key)
      run("print(#{build_line}.#{key})").stdout
    end
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
  end

  TREE_API_NODE_ACTIONS = [
    :click,
    :doubleClick,
    :grabFocus,
    :keyCombo,
    :point,
    :typeText,
  ]

  TREE_API_NODE_SEARCHES = [
    :button,
    :child,
    :childLabelled,
    :childNamed,
    :dialog,
    :menu,
    :menuItem,
    :tab,
    :text,
    :textentry,
    :window,
  ]

  TREE_API_NODE_METHODS = (TREE_API_NODE_ACTIONS + TREE_API_NODE_SEARCHES)

  class TreeAPIScriptProxy < ScriptProxy

    def initialize(*args)
      super(*args)
130
131
132
133
134
      @module_import_lines += [
        'from dogtail import tree',
        'from dogtail.config import config',
      ]
      @init_lines = ['config.searchShowingOnly = True'] + @init_lines
135
136
137
138
139
140
141
142
143
144
145
146
    end

    TREE_API_NODE_METHODS.each do |method|
      define_method(method) do |*args|
        proxy = proxy_call(method, args)
        # If it's not an action we are calling, we just want a
        # "reference" for use later, so we just return ScriptProxy
        # object representing the current state of the call.
        TREE_API_NODE_ACTIONS.include?(method) ? proxy.run : proxy
      end
    end

anonym's avatar
anonym committed
147
148
149
150
151
152
153
154
    def wait(timeout = nil)
      if timeout
        try_for(timeout) { run }
      else
        run
      end
    end

155
156
  end

157
158
159
160
161
162
163
  LOCAL_TREE_API_NODE_METHODS = [
    :wait,
    :get_field,
  ]

  ALL_TREE_API_NODE_METHODS = TREE_API_NODE_METHODS + LOCAL_TREE_API_NODE_METHODS

164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
  class Application

    def initialize(app)
      @app = app
    end

    def self.init_lines(app)
      ["application = tree.root.application(\"#{app}\")"]
    end

    def self.init_components
      ['application']
    end

    def self.interact(app, opts = {}, &block)
      yield TreeAPIScriptProxy.new(self.init_lines(app),
                                   self.init_components,
                                   opts)
    end

    def interact(*args, &block)
      self.class.interact(@app, *args, &block)
    end

188
    ALL_TREE_API_NODE_METHODS.each do |method|
189
190
191
192
193
194
195
196
197
      define_method(method) do |*args|
        interact do |app|
          app.method(method).call(*args)
        end
      end
    end

  end
end