dogtail.rb 5.82 KB
Newer Older
1
2
3
4
5
6
7
module Dogtail
  module Mouse
    LEFT_CLICK = 1
    MIDDLE_CLICK = 2
    RIGHT_CLICK = 3
  end

anonym's avatar
anonym committed
8
9
10
11
12
  TREE_API_NODE_SEARCHES = [
    :button,
    :child,
    :childLabelled,
    :childNamed,
anonym's avatar
anonym committed
13
    :dialog,
anonym's avatar
anonym committed
14
15
16
17
18
19
    :menu,
    :menuItem,
    :tab,
    :textentry,
  ]

20
21
22
23
  TREE_API_NODE_SEARCH_FIELDS = [
    :parent,
  ]

anonym's avatar
anonym committed
24
25
26
27
28
29
30
31
32
33
34
35
36
37
  TREE_API_NODE_ACTIONS = [
    :click,
    :doubleClick,
    :grabFocus,
    :keyCombo,
    :point,
    :typeText,
  ]

  TREE_API_APP_SEARCHES = TREE_API_NODE_SEARCHES + [
    :dialog,
    :window,
  ]

38
  # We want to keep this class immutable so that handles always are
anonym's avatar
anonym committed
39
  # left intact when doing new (proxied) method calls.  This way we
40
41
  # can support stuff like:
  #
anonym's avatar
anonym committed
42
43
44
45
46
  #     app = Dogtail::Application.new('gedit')
  #     menu = app.menu('Menu')
  #     menu.click()
  #     menu.something_else()
  #     menu.click()
47
48
  #
  # i.e. the object referenced by `menu` is never modified by method
49
  # calls and can be used as expected.
50

anonym's avatar
anonym committed
51
  class Application
52
    @@node_counter ||= 0
53

anonym's avatar
anonym committed
54
    def initialize(app_name, opts = {})
55
      @var = "node#{@@node_counter += 1}"
anonym's avatar
anonym committed
56
      @app_name = app_name
57
      @opts = opts
58
59
60
61
62
63
64
65
66
67
68
      @opts[:user] ||= LIVE_USER
      @find_code = "dogtail.tree.root.application('#{@app_name}')"
      script_lines = [
        "import dogtail.config",
        "import dogtail.tree",
        "import dogtail.predicate",
        "dogtail.config.logDebugToFile = False",
        "dogtail.config.logDebugToStdOut = False",
        "dogtail.config.blinkOnActions = True",
        "dogtail.config.searchShowingOnly = True",
        "#{@var} = #{@find_code}",
anonym's avatar
anonym committed
69
      ]
70
      run(script_lines)
71
72
    end

73
74
    def to_s
      @var
75
76
    end

77
78
79
80
81
82
83
    def run(code)
      code = code.join("\n") if code.class == Array
      c = RemoteShell::PythonCommand.new($vm, code, user: @opts[:user])
      if c.failure?
        raise RuntimeError.new("The Dogtail script raised: #{c.exception}")
      end
      return c
anonym's avatar
anonym committed
84
85
    end

86
87
88
89
90
91
    def child?(*args)
      !!child(*args)
    rescue
      false
    end

92
93
94
95
96
97
98
99
    def exist?
      run("dogtail.config.searchCutoffCount = 0")
      run(@find_code)
      return true
    rescue
      return false
    ensure
      run("dogtail.config.searchCutoffCount = 20")
100
101
    end

102
103
104
105
106
107
108
109
110
111
112
113
114
115
    def self.value_to_s(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

anonym's avatar
anonym committed
116
117
118
119
120
    # Generates a Python-style parameter list from `args`. If the last
    # element of `args` is a Hash, it's used as Python's kwargs dict.
    # In the end, the resulting string should be possible to copy-paste
    # into the parentheses of a Python function call.
    # Example: [42, {:foo => 'bar'}] => "42, foo = 'bar'"
121
    def self.args_to_s(args)
122
      return "" if args.size == 0
123
124
125
126
127
      args_list = args
      args_hash = nil
      if args_list.class == Array && args_list.last.class == Hash
        *args_list, args_hash = args_list
      end
128
      (
129
130
        (args_list.nil? ? [] : args_list.map { |e| self.value_to_s(e) }) +
        (args_hash.nil? ? [] : args_hash.map { |k, v| "#{k}=#{self.value_to_s(v)}" })
131
      ).join(', ')
132
133
    end

134
135
136
    # Equivalent to the Tree API's Node.findChildren(), with the
    # arguments constructing a GenericPredicate to use as parameter.
    def children(*args)
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
      non_predicates = [:recursive, :showingOnly]
      findChildren_opts = []
      findChildren_opts_hash = Hash.new
      if args.last.class == Hash
        args_hash = args.last
        non_predicates.each do |opt|
          if args_hash.has_key?(opt)
            findChildren_opts_hash[opt] = args_hash[opt]
            args_hash.delete(opt)
          end
        end
      end
      findChildren_opts = ""
      if findChildren_opts_hash.size > 0
        findChildren_opts = ", " + self.class.args_to_s([findChildren_opts_hash])
      end
      predicate_opts = self.class.args_to_s(args)
154
155
156
157
      nodes_var = "nodes#{@@node_counter += 1}"
      find_script_lines = [
        "#{nodes_var} = #{@var}.findChildren(dogtail.predicate.GenericPredicate(#{predicate_opts})#{findChildren_opts})",
        "print(len(#{nodes_var}))",
158
      ]
159
160
161
      size = run(find_script_lines).stdout.chomp.to_i
      return size.times.map do |i|
        Node.new("#{nodes_var}[#{i}]", @opts)
162
163
164
      end
    end

anonym's avatar
anonym committed
165
    def get_field(key)
166
      run("print(#{@var}.#{key})").stdout.chomp
167
168
    end

anonym's avatar
anonym committed
169
    def set_field(key, value)
170
      run("#{@var}.#{key} = #{self.class.value_to_s(value)}")
anonym's avatar
anonym committed
171
172
    end

anonym's avatar
anonym committed
173
174
175
176
    def text
      get_field('text')
    end

177
178
179
180
181
182
    def text=(value)
      set_field('text', value)
    end

    def name
      get_field('name')
183
184
185
186
    end

    def roleName
      get_field('roleName')
187
188
    end

anonym's avatar
anonym committed
189
190
    TREE_API_APP_SEARCHES.each do |method|
      define_method(method) do |*args|
191
192
193
        args_str = self.class.args_to_s(args)
        method_call = "#{method.to_s}(#{args_str})"
        Node.new("#{@var}.#{method_call}", @opts)
anonym's avatar
anonym committed
194
      end
195
196
    end

197
198
    TREE_API_NODE_SEARCH_FIELDS.each do |field|
      define_method(field) do
199
        Node.new("#{@var}.#{field}", @opts)
200
201
202
      end
    end

anonym's avatar
anonym committed
203
204
205
  end

  class Node < Application
206

207
208
209
210
211
212
213
214
215
    def initialize(expr, opts = {})
      @expr = expr
      @opts = opts
      @opts[:user] ||= LIVE_USER
      @find_code = expr
      @var = "node#{@@node_counter += 1}"
      run("#{@var} = #{@find_code}")
    end

anonym's avatar
anonym committed
216
217
    TREE_API_NODE_SEARCHES.each do |method|
      define_method(method) do |*args|
218
219
220
        args_str = self.class.args_to_s(args)
        method_call = "#{method.to_s}(#{args_str})"
        Node.new("#{@var}.#{method_call}", @opts)
anonym's avatar
anonym committed
221
      end
222
223
    end

anonym's avatar
anonym committed
224
    TREE_API_NODE_ACTIONS.each do |method|
225
      define_method(method) do |*args|
226
227
228
        args_str = self.class.args_to_s(args)
        method_call = "#{method.to_s}(#{args_str})"
        run("#{@var}.#{method_call}")
229
230
231
232
233
      end
    end

  end
end