You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ORPA-pyOpenRPA/3rdParty/pywinauto/findwindows.py

372 lines
14 KiB

6 years ago
# GUI Application automation and testing library
# Copyright (C) 2006-2018 Mark Mc Mahon and Contributors
# https://github.com/pywinauto/pywinauto/graphs/contributors
# http://pywinauto.readthedocs.io/en/latest/credits.html
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of pywinauto nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""Provides functions for iterating and finding windows/elements"""
from __future__ import unicode_literals
import re
import ctypes
import six
from . import win32functions
from . import win32structures
from . import findbestmatch
from . import controls
from .backend import registry
# TODO: we should filter out invalid elements before returning
#=========================================================================
class WindowNotFoundError(Exception):
"""No window could be found"""
pass
#=========================================================================
class WindowAmbiguousError(Exception):
"""There was more then one window that matched"""
pass
#=========================================================================
class ElementNotFoundError(Exception):
"""No element could be found"""
pass
#=========================================================================
class ElementAmbiguousError(Exception):
"""There was more then one element that matched"""
pass
#=========================================================================
def find_element(**kwargs):
"""
Call find_elements and ensure that only one element is returned
Calls find_elements with exactly the same arguments as it is called with
so please see :py:func:`find_elements` for the full parameters description.
"""
elements = find_elements(**kwargs)
if not elements:
raise ElementNotFoundError(kwargs)
if len(elements) > 1:
exception = ElementAmbiguousError(
"There are {0} elements that match the criteria {1}".format(
len(elements),
six.text_type(kwargs),
)
)
exception.elements = elements
raise exception
return elements[0]
#=========================================================================
def find_window(**kwargs):
"""
Call find_elements and ensure that only handle of one element is returned
Calls find_elements with exactly the same arguments as it is called with
so please see :py:func:`find_elements` for the full parameters description.
"""
try:
kwargs['backend'] = 'win32'
element = find_element(**kwargs)
return element.handle
except ElementNotFoundError:
raise WindowNotFoundError
except ElementAmbiguousError:
raise WindowAmbiguousError
#=========================================================================
def find_elements(class_name=None,
class_name_re=None,
parent=None,
process=None,
title=None,
title_re=None,
top_level_only=True,
visible_only=True,
enabled_only=False,
best_match=None,
handle=None,
ctrl_index=None,
found_index=None,
predicate_func=None,
active_only=False,
control_id=None,
control_type=None,
auto_id=None,
framework_id=None,
backend=None,
depth=None
):
"""
Find elements based on criteria passed in
WARNING! Direct usage of this function is not recommended! It's a very low level API.
Better use Application and WindowSpecification objects described in the
Getting Started Guide.
Possible values are:
* **class_name** Elements with this window class
* **class_name_re** Elements whose class matches this regular expression
* **parent** Elements that are children of this
* **process** Elements running in this process
* **title** Elements with this text
* **title_re** Elements whose text matches this regular expression
* **top_level_only** Top level elements only (default=**True**)
* **visible_only** Visible elements only (default=**True**)
* **enabled_only** Enabled elements only (default=False)
* **best_match** Elements with a title similar to this
* **handle** The handle of the element to return
* **ctrl_index** The index of the child element to return
* **found_index** The index of the filtered out child element to return
* **predicate_func** A user provided hook for a custom element validation
* **active_only** Active elements only (default=False)
* **control_id** Elements with this control id
* **control_type** Elements with this control type (string; for UIAutomation elements)
* **auto_id** Elements with this automation id (for UIAutomation elements)
* **framework_id** Elements with this framework id (for UIAutomation elements)
* **backend** Back-end name to use while searching (default=None means current active backend)
"""
if backend is None:
backend = registry.active_backend.name
backend_obj = registry.backends[backend]
# allow a handle to be passed in
# if it is present - just return it
if handle is not None:
return [backend_obj.element_info_class(handle), ]
if isinstance(parent, backend_obj.generic_wrapper_class):
parent = parent.element_info
elif isinstance(parent, six.integer_types):
# check if parent is a handle of element (in case of searching native controls)
parent = backend_obj.element_info_class(parent)
if top_level_only:
# find the top level elements
element = backend_obj.element_info_class()
elements = element.children(process=process,
class_name=class_name,
title=title,
control_type=control_type,
cache_enable=True)
# if we have been given a parent
if parent:
elements = [elem for elem in elements if elem.parent == parent]
# looking for child elements
else:
# if not given a parent look for all children of the desktop
if not parent:
parent = backend_obj.element_info_class()
# look for ALL children of that parent
elements = parent.descendants(class_name=class_name,
title=title,
control_type=control_type,
cache_enable=True,
depth=depth)
# if the ctrl_index has been specified then just return
# that control
if ctrl_index is not None:
return [elements[ctrl_index], ]
# early stop
if not elements:
if found_index is not None:
if found_index > 0:
raise ElementNotFoundError("found_index is specified as {0}, but no windows found".format(
found_index))
return elements
if framework_id is not None and elements:
elements = [elem for elem in elements if elem.framework_id == framework_id]
if control_id is not None and elements:
elements = [elem for elem in elements if elem.control_id == control_id]
if active_only:
# TODO: re-write to use ElementInfo interface
gui_info = win32structures.GUITHREADINFO()
gui_info.cbSize = ctypes.sizeof(gui_info)
# get all the active elements (not just the specified process)
ret = win32functions.GetGUIThreadInfo(0, ctypes.byref(gui_info))
if not ret:
raise ctypes.WinError()
found_active = False
for elem in elements:
if elem.handle == gui_info.hwndActive:
found_active = True
elements = [elem, ]
break
if not found_active:
elements = []
if class_name is not None:
elements = [elem for elem in elements if elem.class_name == class_name]
if class_name_re is not None:
class_name_regex = re.compile(class_name_re)
elements = [elem for elem in elements if class_name_regex.match(elem.class_name)]
if process is not None:
elements = [elem for elem in elements if elem.process_id == process]
if auto_id is not None and elements:
elements = [elem for elem in elements if elem.automation_id == auto_id]
if title is not None:
# TODO: some magic is happenning here
if elements:
elements[0].rich_text
elements = [elem for elem in elements if elem.rich_text == title]
elif title_re is not None:
title_regex = re.compile(title_re)
def _title_match(w):
"""Match a window title to the regexp"""
t = w.rich_text
if t is not None:
return title_regex.match(t)
return False
elements = [elem for elem in elements if _title_match(elem)]
if visible_only:
elements = [elem for elem in elements if elem.visible]
if enabled_only:
elements = [elem for elem in elements if elem.enabled]
if best_match is not None:
# Build a list of wrapped controls.
# Speed up the loop by setting up local pointers
wrapped_elems = []
add_to_wrp_elems = wrapped_elems.append
wrp_cls = backend_obj.generic_wrapper_class
for elem in elements:
try:
add_to_wrp_elems(wrp_cls(elem))
except (controls.InvalidWindowHandle,
controls.InvalidElement):
# skip invalid handles - they have dissapeared
# since the list of elements was retrieved
continue
elements = findbestmatch.find_best_control_matches(best_match, wrapped_elems)
# convert found elements back to ElementInfo
backup_elements = elements[:]
elements = []
for elem in backup_elements:
if hasattr(elem, "element_info"):
elem.element_info.set_cache_strategy(cached=False)
elements.append(elem.element_info)
else:
elements.append(backend_obj.element_info_class(elem.handle))
else:
for elem in elements:
elem.set_cache_strategy(cached=False)
if predicate_func is not None:
elements = [elem for elem in elements if predicate_func(elem)]
# found_index is the last criterion to filter results
if found_index is not None:
if found_index < len(elements):
elements = elements[found_index:found_index + 1]
else:
raise ElementNotFoundError("found_index is specified as {0}, but {1} window/s found".format(
found_index, len(elements)))
return elements
#=========================================================================
def find_windows(**kwargs):
"""
Find elements based on criteria passed in and return list of their handles
Calls find_elements with exactly the same arguments as it is called with
so please see :py:func:`find_elements` for the full parameters description.
"""
try:
kwargs['backend'] = 'win32'
elements = find_elements(**kwargs)
return [elem.handle for elem in elements]
except ElementNotFoundError:
raise WindowNotFoundError
#=========================================================================
def enum_windows():
"""Return a list of handles of all the top level windows"""
windows = []
# The callback function that will be called for each HWND
# all we do is append the wrapped handle
def enum_window_proc(hwnd, lparam):
"""Called for each window - adds handles to a list"""
windows.append(hwnd)
return True
# define the type of the child procedure
enum_win_proc_t = ctypes.WINFUNCTYPE(
ctypes.c_int, ctypes.c_long, ctypes.c_long)
# 'construct' the callback with our function
proc = enum_win_proc_t(enum_window_proc)
# loop over all the children (callback called for each)
win32functions.EnumWindows(proc, 0)
# return the collected wrapped windows
return windows