from pywinauto import win32defines, win32structures, win32functions import pdb import pywinauto import json import sys import ctypes import struct import os import select import zlib import win32api import win32clipboard import time import traceback import ProcessCommunicator import JSONNormalize from threading import Timer import datetime import logging import re import copy #Создать файл логирования # add filemode="w" to overwrite if not os.path.exists("Reports"): os.makedirs("Reports") logging.basicConfig(filename="Reports\ReportRobotGUIRun_"+datetime.datetime.now().strftime("%Y_%m_%d")+".log", level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s") #####Внимание####### #TODO В перспективе нужно реализовать алгоритм определения разрядности не в Robot.py, а в GUI.py, тк начинают появляться функции, на входе в которые еще неизвестна разрядность элемента + селектор может охватить сразу два элемента из 2-х разных разрядностей - обрабатываться это должно непосредственно при выполнении #################################### #Info: GUI module of the Robot app (OpenRPA - Robot) #################################### # GUI Module - interaction with Desktop application #GUI Naming convention #__ #UIO - UI Object (class of pywinauto UI object) #UIOSelector - List of dict (key attributes) #PWA - PyWinAuto #PWASpecification - List of dict (key attributes in pywinauto.find_window notation) #UIOTree - Recursive Dict of Dict ... (UI Parent -> Child hierarchy) #UIOInfo - Dict of UIO attributes #UIOActivity - Activity of the UIO (UI object) from the Pywinauto module #UIOEI - UI Object info object #inActivitySpecificationDict: #{ # ModuleName: <"GUI", str>, - optional # ActivityName: , # ArgumentList: [, ...] - optional, # ArgumentDict: {:, ...} - optional #} #outActivityResultDict: #{ # ActivitySpecificationDict: { # ModuleName: <"GUI", str>, -optional # ActivityName: , # ArgumentList: [, ...] - optional, # ArgumentDict: {: , ...} - optional # }, # ErrorFlag: , # ErrorMessage: - required if ErrorFlag is true, # ErrorTraceback: - required if ErrorFlag is true, # Result: - required if ErrorFlag is false #} #inUIOSelector: #[ # { # "index":<Позиция элемента в родительском объекте>, # "depth_start" - глубина, с которой начинается поиск (по умолчанию 1), # "depth_end" - глубина, до которой ведется поиск (по умолчанию 1), # "class_name" - наименование класса, который требуется искать, # "title" - наименование заголовка, # "rich_text" - наименование rich_text, # "backend": <"win32"||"uia", only for the 1-st list element> - if not specified, use mDefaultPywinautoBackend # }, # { ... } # #] #Default parameters mDefaultPywinautoBackend="win32" ############################ #Новая версия ############################ #Получить список элементов, который удовлетворяет условиям через расширенный движок поиска #[ # { # "index":<Позиция элемента в родительском объекте>, # "depth_start" - глубина, с которой начинается поиск (по умолчанию 1) # "depth_end" - глубина, до которой ведется поиск (по умолчанию 1) # "class_name" - наименование класса, который требуется искать # "title" - наименование заголовка # "rich_text" - наименование rich_text # } #] ################ #return: List of UI Object #inElement - Входной элемент - показатель, что не требуется выполнять коннект к процессу #inFlagRaiseException - Флаг True - выкинуть ошибку в случае обнаружении пустого списка #old name - PywinautoExtElementsGet def UIOSelector_Get_UIOList (inSpecificationList,inElement=None,inFlagRaiseException=True): lResultList=[] lChildrenList=[] #Получить родительский объект если на вход ничего не поступило if inElement is None: #сформировать спецификацию на получение элемента lRootElementSpecification=[inSpecificationList[0]] lRootElementList=PWASpecification_Get_UIO(lRootElementSpecification) for lRootItem in lRootElementList: if lRootItem is not None: lChildrenList.append(lRootItem.wrapper_object()) #Елемент на вход поступил - выполнить его анализ else: #Получить список элементов lElementChildrenList=inElement.children() #Поступил index - точное добавление if 'index' in inSpecificationList[0]: if inSpecificationList[0]['index']1: lFlagGoCheck=False #Циклический обход по детям, на предмет соответствия всем условиям for lChildrenItem in lElementChildrenList: #Обработка глубины depth (рекурсивный вызов для всех детей с занижением индекса глубины) #По умолчанию значение глубины 1 if 'depth_end' in inSpecificationList[0]: if inSpecificationList[0]['depth_end']>1: #Подготовка новой версии спецификации lChildrenItemNewSpecificationList=inSpecificationList.copy() lChildrenItemNewSpecificationList[0]=lChildrenItemNewSpecificationList[0].copy() lChildrenItemNewSpecificationList[0]["depth_end"]=lChildrenItemNewSpecificationList[0]["depth_end"]-1 if 'depth_start' in lChildrenItemNewSpecificationList[0]: lChildrenItemNewSpecificationList[0]["depth_start"]=lChildrenItemNewSpecificationList[0]["depth_start"]-1 #Циклический вызов для всех детей со скорректированной спецификацией lResultList.extend(UIOSelector_Get_UIOList(lChildrenItemNewSpecificationList,lChildrenItem,inFlagRaiseException)) #Фильтрация if lFlagGoCheck: lFlagAddChild=True #Фильтрация по title if 'title' in inSpecificationList[0]: if lChildrenItem.element_info.name != inSpecificationList[0]["title"]: lFlagAddChild=False #Фильтрация по title_re (regexp) if 'title_re' in inSpecificationList[0]: if re.fullmatch(inSpecificationList[0]["title_re"],lChildrenItem.element_info.name) is None: lFlagAddChild=False #Фильтрация по rich_text if 'rich_text' in inSpecificationList[0]: if lChildrenItem.element_info.rich_text != inSpecificationList[0]["rich_text"]: lFlagAddChild=False #Фильтрация по rich_text_re (regexp) if 'rich_text_re' in inSpecificationList[0]: if re.fullmatch(inSpecificationList[0]["rich_text_re"],lChildrenItem.element_info.rich_text) is None: lFlagAddChild=False #Фильтрация по class_name if 'class_name' in inSpecificationList[0]: if lChildrenItem.element_info.class_name != inSpecificationList[0]["class_name"]: lFlagAddChild=False #Фильтрация по class_name_re (regexp) if 'class_name_re' in inSpecificationList[0]: if re.fullmatch(inSpecificationList[0]["class_name_re"],lChildrenItem.element_info.class_name) is None: lFlagAddChild=False #Фильтрация по friendly_class_name if 'friendly_class_name' in inSpecificationList[0]: if lChildrenItem.friendly_class_name() != inSpecificationList[0]["friendly_class_name"]: lFlagAddChild=False #Фильтрация по friendly_class_name_re (regexp) if 'friendly_class_name_re' in inSpecificationList[0]: if re.fullmatch(inSpecificationList[0]["friendly_class_name_re"],lChildrenItem.friendly_class_name) is None: lFlagAddChild=False #Фильтрация по control_type if 'control_type' in inSpecificationList[0]: if lChildrenItem.element_info.control_type != inSpecificationList[0]["control_type"]: lFlagAddChild=False #Фильтрация по control_type_re (regexp) if 'control_type_re' in inSpecificationList[0]: if re.fullmatch(inSpecificationList[0]["control_type_re"],lChildrenItem.element_info.control_type) is None: lFlagAddChild=False ##### #Все проверки пройдены - флаг добавления if lFlagAddChild: lChildrenList.append(lChildrenItem) #Выполнить рекурсивный вызов (уменьшение количества спецификаций), если спецификация больше одного элемента #????????Зачем в условии ниже is not None ??????????? if len(inSpecificationList)>1 and len(lChildrenList)>0 is not None: #Вызвать рекурсивно функцию получения следующего объекта, если в спецификации есть следующий объект for lChildElement in lChildrenList: lResultList.extend(UIOSelector_Get_UIOList(inSpecificationList[1:],lChildElement,inFlagRaiseException)) else: lResultList.extend(lChildrenList) #Условие, если результирующий список пустой и установлен флаг создания ошибки (и inElement is None - не следствие рекурсивного вызова) if inElement is None and len(lResultList)==0 and inFlagRaiseException: raise pywinauto.findwindows.ElementNotFoundError("Robot can't find element by the UIOSelector") return lResultList ################################################################################################# #Get first (in more than one) UIO (UI Object) #inSpecificationList - UIOSelector #inElement - Входной элемент - показатель, что не требуется выполнять коннект к процессу #inFlagRaiseException - Флаг True - выкинуть ошибку в случае обнаружении пустого списка #old name - PywinautoExtElementGet def UIOSelector_Get_UIO (inSpecificationList,inElement=None,inFlagRaiseException=True): lResult=None #Получить родительский объект если на вход ничего не поступило lResultList=UIOSelector_Get_UIOList(inSpecificationList,inElement,False) if len(lResultList)>0: lResult=lResultList[0] #Условие, если результирующий список пустой и установлен флаг создания ошибки (и inElement is None - не следствие рекурсивного вызова) if lResult is None and inFlagRaiseException: raise pywinauto.findwindows.ElementNotFoundError("Robot can't find element by the UIOSelector") return lResult ################################################################################################# #Check if UIO exist (Identified by the UIOSelector) #inSpecificationList - UIOSelector #old name - - def UIOSelector_Exist_Bool (inSpecificationList): lResult=False #Получить родительский объект если на вход ничего не поступило lResultList=UIOSelector_Get_UIOList(inSpecificationList,None,False) if len(lResultList)>0: lResult=True return lResult ################################################################################################# #Wait for UIO is appear (at least one of them or all at the same time) #inSpecificationListList - List of the UIOSelector #inWaitSecs - Время ожидания объекта в секундах #inFlagWaitAllInMoment - доп. условие - ожидать появление всех UIOSelector одновременно #return: [0,1,2] - index of UIOSpecification, which is appear #old name - - #####Внимание##### ##Функция ожидания появления элементов (тк элементы могут быть недоступны, неизвестно в каком фреймворке каждый из них может появиться) def UIOSelectorsSecs_WaitAppear_List (inSpecificationListList,inWaitSecs,inFlagWaitAllInMoment=False): lResultFlag=False lSecsSleep = 1 #Настроечный параметр lSecsDone = 0 lResultList = None #Цикл проверки while lResultFlag == False and lSecsDone0: #Условие выполнено lResultFlag=True #Если флаг не изменился - увеличить время и уснуть if lResultFlag == False: lSecsDone=lSecsDone+lSecsSleep time.sleep(lSecsSleep) return lResultList ################################################################################################# #Wait for UIO is appear (at least one of them or all at the same time) #inSpecificationList - UIOSelector #inWaitSecs - Время ожидания объекта в секундах #return: Bool - True - UIO is appear #old name - - def UIOSelectorSecs_WaitAppear_Bool (inSpecificationList,inWaitSecs): lWaitAppearList=UIOSelectorsSecs_WaitAppear_List([inSpecificationList],inWaitSecs) lResult=False if len(lWaitAppearList)>0: lResult=True return lResult ################################################################################################# #Get process bitness (32 or 64) #inSpecificationList - UIOSelector #old name - None #return None (if Process not found), int 32, or int 64 def UIOSelector_Get_BitnessInt (inSpecificationList): lResult=None #Получить объект Application (Для проверки разрядности) lRootElement=PWASpecification_Get_PWAApplication(inSpecificationList) if lRootElement is not None: if lRootElement.is64bit(): lResult=64 else: lResult=32 return lResult ################################################################################################# #Get OS bitness (32 or 64) #old name - None #return int 32, or int 64 def Get_OSBitnessInt (): lResult=32; if pywinauto.sysinfo.is_x64_OS(): lResult=64; return lResult; ################################################################################################## #inControlSpecificationArray - List of dict, dict in pywinauto.find_windows notation #Backend selection - attribute "backend" ("win32" || "uia") in 1-st list element #return list of UIO object #old name - GetControl def PWASpecification_Get_UIO(inControlSpecificationArray): #Определение backend lBackend=mDefaultPywinautoBackend if "backend" in inControlSpecificationArray[0]: lBackend=inControlSpecificationArray[0]["backend"] inControlSpecificationArray[0].pop("backend") #Подготовка входного массива inControlSpecificationOriginArray=copy.deepcopy(inControlSpecificationArray) inControlSpecificationArray=UIOSelector_SearchProcessNormalize_UIOSelector(inControlSpecificationArray) #Выполнить идентификацию объектов, если передан массив lResultList=[]; lTempObject=None if len(inControlSpecificationArray) > 0: #Сформировать выборку элементов, которые подходят под первый уровень спецификации lSpecificationLvL1List = pywinauto.findwindows.find_elements(**inControlSpecificationArray[0]) for lItem in lSpecificationLvL1List: #Сделать независимую копию и установить информацию о process_id и handle lItemControlSpecificationArray=copy.deepcopy(inControlSpecificationArray) lItemControlSpecificationArray[0]["process_id"]=lItem.process_id lItemControlSpecificationArray[0]["handle"]=lItem.handle lItemControlSpecificationOriginArray=copy.deepcopy(inControlSpecificationOriginArray) lItemControlSpecificationOriginArray[0]["process_id"]=lItem.process_id lItemControlSpecificationOriginArray[0]["handle"]=lItem.handle #Выполнить подключение к объекту lRPAApplication = pywinauto.Application(backend=lBackend) #Проверка разрядности try: lRPAApplication.connect(**lItemControlSpecificationArray[0]) except Exception as e: UIOSelector_TryRestore_Dict(lItemControlSpecificationArray) try: lRPAApplication.connect(**lItemControlSpecificationArray[0]) except Exception as e: lRPAApplication = None if lRPAApplication is not None: #lTempObject=lRPAApplication.window(**lItemControlSpecificationArray[0]) #Скорректировано из-за недопонимания структуры lTempObject=lRPAApplication #Нормализация массива для целей выборки объекта (удаление конфликтующих ключей) lItemControlSpecificationArray=UIOSelector_SearchUIONormalize_UIOSelector(lItemControlSpecificationOriginArray) #Циклическое прохождение к недрам объекта for lWindowSpecification in lItemControlSpecificationArray[0:]: lTempObject=lTempObject.window(**lWindowSpecification) #Добавить объект в результирующий массив lResultList.append(lTempObject) return lResultList ################################################################################################## #inControlSpecificationArray - List of dict, dict in pywinauto.find_windows notation #Backend selection - attribute "backend" ("win32" || "uia") in 1-st list element #return process application object #old name - None def PWASpecification_Get_PWAApplication(inControlSpecificationArray): #Определение backend lBackend=mDefaultPywinautoBackend if "backend" in inControlSpecificationArray[0]: lBackend=inControlSpecificationArray[0]["backend"] inControlSpecificationArray[0].pop("backend") #Подготовка входного массива inControlSpecificationOriginArray=inControlSpecificationArray inControlSpecificationArray=UIOSelector_SearchProcessNormalize_UIOSelector(inControlSpecificationArray) #Выполнить идентификацию объектов, если передан массив lResultList=[]; lTempObject=None if len(inControlSpecificationArray) > 0: #Выполнить подключение к объекту lRPAApplication = pywinauto.Application(backend=lBackend) #Проверка разрядности try: lRPAApplication.connect(**inControlSpecificationArray[0]) except Exception as e: UIOSelector_TryRestore_Dict(inControlSpecificationArray) try: lRPAApplication.connect(**inControlSpecificationArray[0]) except Exception as e: lRPAApplication = None if lRPAApplication is not None: #lTempObject=lRPAApplication.window(**inControlSpecificationArray[0]) #Скорректировано из-за недопонимания структуры lTempObject=lRPAApplication return lTempObject ########################################################################################################### #inElementSpecificationList = UIOSelector (see description on the top of the document) #result = pywinauto element wrapper instance or None #old name - AutomationSearchMouseElement def UIOSelector_SearchChildByMouse_UIO(inElementSpecification): lGUISearchElementSelected=None #Настройка - частота обновления подсвечивания lTimeSleepSeconds=0.4 lElementFoundedList=[] #Ветка поиска в режиме реального времени #Сбросить нажатие Ctrl, если оно было bool(win32api.GetAsyncKeyState(17)) #Оптимизация - получить объект для опроса единажды lUIORoot=UIOSelector_Get_UIO(inElementSpecification) lFlagLoop = True while lFlagLoop: #Проверить, нажата ли клавиша Ctrl (код 17) lFlagKeyPressedCtrl=bool(win32api.GetAsyncKeyState(17)) #Подсветить объект, если мышка наведена над тем объектом, который не подсвечивался в прошлый раз if not lFlagKeyPressedCtrl: #Получить координаты мыши (lX,lY) = win32api.GetCursorPos() lElementFounded={} #Создать карту пикселей и элементов #####Внимание! Функция UIOXY_SearchChild_ListDict не написана lElementFoundedList=UIOXY_SearchChild_ListDict(lUIORoot,lX,lY) #print(lElementFoundedList) lElementFounded=lElementFoundedList[-1]["element"] #Подсветить объект, если он мышь раньше стояла на другом объекте if lGUISearchElementSelected != lElementFounded: lGUISearchElementSelected = lElementFounded #Доработанная функция отрисовки if lElementFounded is not None: UIO_Highlight(lElementFounded) else: #Была нажата клавиша Ctrl - выйти из цикла lFlagLoop=False; #Заснуть до следующего цикла time.sleep(lTimeSleepSeconds) #Вернуть результат поиска return lElementFoundedList #################################################################################################### #inElementSpecification - UIOSelector #old name - AutomationSearchMouseElementHierarchy def UIOSelector_SearchChildByMouse_UIOTree(inElementSpecification): lItemInfo = [] #Запустить функцию поиска элемента по мыши lElementList = UIOSelector_SearchChildByMouse_UIO(inElementSpecification) lElement = lElementList[-1]['element'] #Detect backend of the elements lFlagIsBackendWin32 = True #Если объект имеется (не None), то выполнить построение иерархии if lElement is not None: if lElement.backend.name == 'uia': lFlagIsBackendWin32 = False #Циклическое создание дерева #while lElement is not None: lListIterator=0 lItemInfo2=lItemInfo for lListItem in lElementList: lElement = lListItem["element"] #Продолжать построение иерархии во всех случаях кроме бэк uia & parent() is None #if not lFlagIsBackendWin32 and lElement.parent() is None: # lElement = None #else: #Получить информацию про объект lItemInfo2.append(UIOEI_Convert_UIOInfo(lElement.element_info)) #Дообогатить информацией об индексе ребенка в родительском объекте if "index" in lListItem: if lListItem["index"] is not None: lItemInfo2[-1]['ctrl_index']=lListItem["index"] else: if "ctrl_index" in lListItem: lItemInfo2[-1]['ctrl_index']=lListItem["ctrl_index"] else: if "ctrl_index" in lListItem: lItemInfo2[-1]['ctrl_index']=lListItem["ctrl_index"] #Оборачиваем потомка в массив, потому что у родителя по структуре интерфейса может быть больше одного наследников lItemInfo2[-1]['SpecificationChild']=[] lItemInfo2=lItemInfo2[-1]['SpecificationChild'] #Переход на родительский объект #lElement = lElement.parent() lListIterator=lListIterator+1 #Добавить информацию о Backend в первый объект lItemInfo[0]["backend"]=lElement.backend.name #Вернуть результат return lItemInfo #################################################################################################### #inElement- UIO (UI Object) #old name - PywinautoExtElementCtrlIndexGet def UIO_GetCtrlIndex_Int(inElement): lResult = None #Выполнить алгоритм, если есть Element if inElement is not None: lElementParent = inElement.parent() if lElementParent is not None: lResult = 0 lFlagFind = True #Получить список потомков lElementParentChildrenList = lElementParent.children() #Циклический поиск до того момента, пока не упремся в текущий элемент while lFlagFind: if lResult=len(lElementParentChildrenList): lResult = None lFlagFind = False else: lResult = lResult + 1 else: lResult=-1 lFlagFind=False #Вернуть результат return lResult #################################################################################################### #Получить список информационных объектов, который удовлетворяет условиям #inSpecificationList - UIOSelector #old name - PywinautoExtElementsGetInfo def UIOSelector_Get_UIOInfoList (inSpecificationList,inElement=None): #Получить родительский объект если на вход ничего не поступило lResultList=UIOSelector_Get_UIOList(inSpecificationList,inElement) lIterator = 0 for lItem in lResultList: lResultList[lIterator]=UIOEI_Convert_UIOInfo(lResultList[lIterator].element_info) lIterator = lIterator + 1 return lResultList #################################################################################################### #Check is the UIO/UIO's by the UIOSelector exist #inSpecificationList - UIOSelector #old name - PywinautoExtElementExist def UIOSelector_IsExist_Bool (inSpecificationList): return len(UIOSelector_Get_UIOList(inSpecificationList))>0 #################################################################################################### #Wait for the UIO by the UIOSelector appear #inSpecificationList - UIOSelector #result - { } #old name - PywinautoExtElementWaitAppear def UIOSelector_WaitAppear_Dict(inSpecificationList,inTimeout=60): lTimeoutSeconds = 0 while (not UIOSelector_IsExist_Bool(inSpecificationList) and inTimeout>lTimeoutSeconds): lTimeoutSeconds = lTimeoutSeconds + 0.5 #Заснуть на полсекунды time.sleep(0.5) return UIOSelector_IsExist_Bool(inSpecificationList) #################################################################################################### #Try to restore (maximize) window, if it's was minimized #(особенность uia backend - он не может прицепиться к окну, если оно свернуто) #inSpecificationList - UIOSelector #old name - PywinautoExtTryToRestore def UIOSelector_TryRestore_Dict(inSpecificationList): lResult={} try: #Подготовка взодного массива inControlSpecificationArray=UIOSelector_SearchUIONormalize_UIOSelector(inSpecificationList) #Выполнить подключение к объекту. Восстановление необходимо только в бэке win32, #так как в uia свернутое окно не распознается lRPAApplication = pywinauto.Application(backend="win32") lRPAApplication.connect(**inSpecificationList[0]) lRPAApplication.top_window().restore() except Exception: True==False return lResult #################################################################################################### #Get the list of the UI object activities #inControlSpecificationArray - UIOSelector #old name - ElementActionGetList def UIOSelector_Get_UIOActivityList (inControlSpecificationArray): #Получить объект lObject=UIOSelector_Get_UIO(inControlSpecificationArray) lActionList=dir(lObject) lResult=dir(lObject) #Выполнить чистку списка от неактуальных методов for lActionItem in lActionList: #Удалить те, которые начинаются на _ if lActionItem[0]=='_': lResult.remove(lActionItem) #Удалить те, которые начинаются с символа верхнего регистра if lActionItem[0].isupper(): lResult.remove(lActionItem) return lResult #################################################################################################### #Run the activity in UIO (UI Object) #inControlSpecificationArray - UIOSelector #inActionName - UIOActivity (name) from Pywinauto #old name - ElementRunAction def UIOSelectorUIOActivity_Run_Dict(inControlSpecificationArray,inActionName,inArgumentList=[],inkwArgumentObject={}): lResult={} #Определить объект lObject=UIOSelector_Get_UIO(inControlSpecificationArray) #Получить метод для вызова lFunction = getattr(lObject, inActionName) #Выполнить действие #Обернуто в безопасную обработку, тк для некоторых объектов метод не работает и может выдавать ошибку типа: NotImplementedError: This method not work properly for WinForms DataGrid, use cells() try: return lFunction(*inArgumentList,**inkwArgumentObject) except Exception as e: #Если ошибка возникла на action get_properties if inActionName=="get_properties": lResult={} #Ручное формирование lResult["class_name"]=lObject.class_name() lResult["friendly_class_name"]=lObject.friendly_class_name() lResult["texts"]=lObject.texts() lResult["control_id"]=lObject.control_id() lResult["control_count"]=lObject.control_count() lResult["automation_id"]=lObject.automation_id() return lResult else: raise e return lResult #################################################################################################### #Get the UIO dict of the attributes #old name - ElementGetInfo def UIOSelector_Get_UIOInfo(inControlSpecificationArray): #Подготовка входного массива inControlSpecificationArray=UIOSelector_SearchUIONormalize_UIOSelector(inControlSpecificationArray) #Выполнить идентификацию объектов, если передан массив lResultList=[]; if len(inControlSpecificationArray) > 0: #Получить объект lTempObject=UIOSelector_Get_UIO(inControlSpecificationArray) #Получить инфо объект lTempObjectInfo = lTempObject.element_info #Добавить информацию об обнаруженом объекте lResultList.append(UIOEI_Convert_UIOInfo(lTempObjectInfo)); return lResultList #################################################################################################### #Search child UIO by the: Parent UIO, X, Y #inHierarchyList: [{"index":<>,"element":<>}] - technical argument for internal purpose #result -List of dict [{"index":<>,"element":<>}] -- list of element hierarchy specifications #old name - GUISearchElementByRootXY def UIOXY_SearchChild_ListDict(inRootElement,inX,inY,inHierarchyList=[]): #Инициализация результирующего значения lResultElement = None lResultElementX1 = None lResultElementX2 = None lResultElementY1 = None lResultElementY2 = None lResultHierarchyList=[{'index':None,'element':None}] #Получить координаты текущего объекта try: lRootElementRectX1=inRootElement.element_info.rectangle.left lRootElementRectX2=inRootElement.element_info.rectangle.right lRootElementRectY1=inRootElement.element_info.rectangle.top lRootElementRectY2=inRootElement.element_info.rectangle.bottom #Добавить объект в результирующий, если координаты попадают в него if inX>=lRootElementRectX1 and inX<=lRootElementRectX2 and inY>=lRootElementRectY1 and inY<=lRootElementRectY2: lResultElement = inRootElement lResultElementX1 = lRootElementRectX1 lResultElementX2 = lRootElementRectX2 lResultElementY1 = lRootElementRectY1 lResultElementY2 = lRootElementRectY2 #Сформировать результирующий обьъект lParentHierarchy = inHierarchyList if len(lParentHierarchy)==0: lParentHierarchy.append({"index":None,"element":lResultElement}) else: lParentHierarchy[-1]["element"] = lResultElement lResultHierarchyList=lParentHierarchy #Получить список детей и добавить в карту lChildIterator=0 for lChildElement in inRootElement.children(): #Сформировать результирующий массив lChildFoundedHierarchyList = lParentHierarchy.copy() lChildFoundedHierarchyList.append({'index': lChildIterator}) lChildFoundedHierarchyList = UIOXY_SearchChild_ListDict(lChildElement,inX,inY, lChildFoundedHierarchyList) lChildFoundedElement = lChildFoundedHierarchyList[-1]["element"] #Установить обнаруженный элемент, если текущий результат пустой if lResultElement is None and lChildFoundedElement is not None: lResultElement = lChildFoundedElement lResultElementX1 = lResultElement.element_info.rectangle.left lResultElementX2 = lResultElement.element_info.rectangle.right lResultElementY1 = lResultElement.element_info.rectangle.top lResultElementY2 = lResultElement.element_info.rectangle.bottom lResultHierarchyList = lChildFoundedHierarchyList #Выполнить сверку lChildFoundedElement и lResultElement если оба имеются elif lResultElement is not None and lChildFoundedElement is not None: #Правила перезатирания карты, если имеется старый объект #[Накладываемый объект] - НО - ElementNew #[Имеющийся объект] - ИО - ElementOld #3 типа вхождения объектов #тип 1 - [имеющийся объект] полностью входит в [накладываемый объект] (ИО X1 Y1 >= НО X1 Y1; ИО X2 Y2 <= НО X2 Y2) - не вносить НО в bitmap в эти диапазоны #тип 2 - [имеющийся объект] полностью выходит за пределы [накладываемого объекта] (ИО X1 Y1 < НО X1 Y1; ИО X2 Y2 > НО X2 Y2) - вносить НО в bitmap #тип 3 - [имеющийся объект] частично входит в [накладываемый объект] (все остальные случаи)- вносить НО в bitmap #Получить координаты ИО lChildFoundedElementInfo = lChildFoundedElement.element_info #lElementNew = inElement lChildFoundedElementX1 = lChildFoundedElementInfo.rectangle.left lChildFoundedElementX2 = lChildFoundedElementInfo.rectangle.right lChildFoundedElementY1 = lChildFoundedElementInfo.rectangle.top lChildFoundedElementY2 = lChildFoundedElementInfo.rectangle.bottom #Проверка вхождения по типу 1 if (lResultElementX1>=lChildFoundedElementX1) and (lResultElementY1>=lChildFoundedElementY1) and (lResultElementX2<=lChildFoundedElementX2) and (lResultElementY2<=lChildFoundedElementY2): False == True #Проверка вхождения по типу 3 elif (lResultElementX1lChildFoundedElementX2) and (lResultElementY2>lChildFoundedElementY2): lResultElement = lChildFoundedElement lResultElementX1 = lChildFoundedElementX1 lResultElementX2 = lChildFoundedElementX2 lResultElementY1 = lChildFoundedElementY1 lResultElementY2 = lChildFoundedElementY2 lResultHierarchyList = lChildFoundedHierarchyList #Проверка вхождения по типу 2 else: lResultElement = lChildFoundedElement lResultElementX1 = lChildFoundedElementX1 lResultElementX2 = lChildFoundedElementX2 lResultElementY1 = lChildFoundedElementY1 lResultElementY2 = lChildFoundedElementY2 lResultHierarchyList = lChildFoundedHierarchyList lChildIterator=lChildIterator+1 except Exception as e: False == False return lResultHierarchyList ################################################################################################### #Get list of child UIO's by Parent UIOSelector #inControlSpecificationArray- UIOSelector #old name - ElementGetChildElementList def UIOSelector_GetChildList_UIOList(inControlSpecificationArray=[],inBackend=mDefaultPywinautoBackend): #Подготовка входного массива inControlSpecificationArray=UIOSelector_SearchUIONormalize_UIOSelector(inControlSpecificationArray) #Выполнить идентификацию объектов, если передан массив lResultList=[]; #ctypes.windll.user32.MessageBoxW(0, str(inControlSpecificationArray), "Your title", 1) if len(inControlSpecificationArray) > 0: #Получить объект lTempObject = UIOSelector_Get_UIO(inControlSpecificationArray) #Получить список дочерних объектов lTempChildList = lTempObject.children() lIterator=0 #Подготовить результирующий объект for lChild in lTempChildList: lTempObjectInfo=lChild.element_info #Добавить информацию об обнаруженом объекте lObjectInfoItem=UIOEI_Convert_UIOInfo(lTempObjectInfo) #Итератор внутри объекта (для точной идентификации) lObjectInfoItem['ctrl_index']=lIterator; lResultList.append(lObjectInfoItem); #Инкремент счетчика lIterator=lIterator+1 else: lResultList=BackendStr_GetTopLevelList_UIOInfo(inBackend) #Установка бэк-енда на первый элемент for lItem in lResultList: lItem["backend"]=inBackend return lResultList #################################################################################################### #Подготовить массив для обращшения к поиску элемементов #inControlSpecificationArray - UIOSelector (can be dirty) #old name 1 - ElementSpecificationArraySearchPrepare #old name 2 - ElementSpecificationListNormalize def UIOSelector_SearchUIONormalize_UIOSelector (inControlSpecificationArray): lResult=[] #Циклический обход for lSpecificationItem in inControlSpecificationArray: lSpecificationItemNew=lSpecificationItem.copy() #Перебор всех элементов for lItemKey,lItemValue in lSpecificationItem.items(): #Флаг удаления атрибута lFlagRemoveAttribute=False ############################# #Если является вложенным словарем - удалить if type(lItemValue) is dict: lFlagRemoveAttribute=True #Является типом None if lItemValue is None: lFlagRemoveAttribute=True #Проверка допустимого ключевого слова if ( lItemKey == "class_name" or lItemKey == "class_name_re" or lItemKey == "parent" or lItemKey == "process" or lItemKey == "title" or lItemKey == "title_re" or lItemKey == "top_level_only" or lItemKey == "visible_only" or lItemKey == "enabled_only" or lItemKey == "best_match" or lItemKey == "handle" or lItemKey == "ctrl_index" or lItemKey == "found_index" or lItemKey == "predicate_func" or lItemKey == "active_only" or lItemKey == "control_id" or lItemKey == "control_type" or lItemKey == "auto_id" or lItemKey == "framework_id" or lItemKey == "backend"): True == True else: lFlagRemoveAttribute=True ############################# #Конструкция по удалению ключа из словаря if lFlagRemoveAttribute: lSpecificationItemNew.pop(lItemKey) #Проверит наличие ctrl_index - если он есть, то удалить control_id и control_type из-за того, что они мешают друг другу if 'ctrl_index' in lSpecificationItemNew: if "control_id" in lSpecificationItemNew: lSpecificationItemNew.pop("control_id") if "control_type" in lSpecificationItemNew: lSpecificationItemNew.pop("control_type") #Проверить наличие handle - если он есть, то удалить process, control_id и control_type из-за того, что они мешают друг другу if 'handle' in lSpecificationItemNew: if "control_id" in lSpecificationItemNew: lSpecificationItemNew.pop("control_id") if "control_type" in lSpecificationItemNew: lSpecificationItemNew.pop("control_type") if "process" in lSpecificationItemNew: lSpecificationItemNew.pop("process") #Иначе Проверить наличие process - если он есть, то удалить тк он нужен только при подключении к процессу if 'process' in lSpecificationItemNew: lSpecificationItemNew.pop("process") #Добавить строку в результирующий массив lResult.append(lSpecificationItemNew) #Вернуть результат return lResult #################################################################################################### #Подготовить массив для обращшения к поиску процесса (отличается от поиска элемента, тк данная функция нужна для нормализации спецификации для подключения к процессу с окнами) #inControlSpecificationArray - UIOSelector (can be dirty) #old name 1 - ElementSpecificationArraySearchPrepare #old name 2 - ElementSpecificationListNormalize def UIOSelector_SearchProcessNormalize_UIOSelector (inControlSpecificationArray): lResult=[] #Циклический обход for lSpecificationItem in inControlSpecificationArray: lSpecificationItemNew=lSpecificationItem.copy() #Перебор всех элементов for lItemKey,lItemValue in lSpecificationItem.items(): #Флаг удаления атрибута lFlagRemoveAttribute=False ############################# #Если является вложенным словарем - удалить if type(lItemValue) is dict: lFlagRemoveAttribute=True #Является типом None if lItemValue is None: lFlagRemoveAttribute=True #Проверка допустимого ключевого слова if ( lItemKey == "class_name" or lItemKey == "class_name_re" or lItemKey == "parent" or lItemKey == "process" or lItemKey == "title" or lItemKey == "title_re" or lItemKey == "top_level_only" or lItemKey == "visible_only" or lItemKey == "enabled_only" or lItemKey == "best_match" or lItemKey == "handle" or lItemKey == "ctrl_index" or lItemKey == "found_index" or lItemKey == "predicate_func" or lItemKey == "active_only" or lItemKey == "control_id" or lItemKey == "control_type" or lItemKey == "auto_id" or lItemKey == "framework_id" or lItemKey == "backend"): True == True else: lFlagRemoveAttribute=True ############################# #Конструкция по удалению ключа из словаря if lFlagRemoveAttribute: lSpecificationItemNew.pop(lItemKey) #Проверит наличие ctrl_index - если он есть, то удалить control_id и control_type из-за того, что они мешают друг другу if 'ctrl_index' in lSpecificationItemNew: if "control_id" in lSpecificationItemNew: lSpecificationItemNew.pop("control_id") if "control_type" in lSpecificationItemNew: lSpecificationItemNew.pop("control_type") #Проверить наличие handle - если он есть, то удалить process, control_id и control_type из-за того, что они мешают друг другу if 'handle' in lSpecificationItemNew: if "control_id" in lSpecificationItemNew: lSpecificationItemNew.pop("control_id") if "control_type" in lSpecificationItemNew: lSpecificationItemNew.pop("control_type") if "process" in lSpecificationItemNew: lSpecificationItemNew.pop("process") #Иначе Проверить наличие process - если он есть, то удалить title, control_id и control_type из-за того, что они мешают друг другу elif 'process' in lSpecificationItemNew: if "control_id" in lSpecificationItemNew: lSpecificationItemNew.pop("control_id") if "control_type" in lSpecificationItemNew: lSpecificationItemNew.pop("control_type") if "title" in lSpecificationItemNew: lSpecificationItemNew.pop("title") #Добавить строку в результирующий массив lResult.append(lSpecificationItemNew) #Вернуть результат return lResult #################################################################################################### #Transfer UI object element info (pywinauto) to UIOInfo (dict of attributes) #inElementInfo - UIOEI #old name - ElementInfoExportObject def UIOEI_Convert_UIOInfo(inElementInfo): #Подготовить выходную структуру данных lResult = {"title":None,"rich_text":None,"process_id":None,"process":None,"handle":None,"class_name":None,"control_type":None,"control_id":None,"rectangle":{"left":None,"top":None,"right":None,"bottom":None}, 'runtime_id':None} #Проверка name try: lResult['title']=inElementInfo.name except Exception as e: True == False #Проверка rich_text try: lResult['rich_text']=inElementInfo.rich_text except Exception as e: True == False #Проверка process_id try: lResult['process_id']=inElementInfo.process_id lResult['process']=inElementInfo.process_id except Exception as e: True == False #Проверка handle try: lResult['handle']=inElementInfo.handle except Exception as e: True == False #Проверка class_name try: lResult['class_name']=inElementInfo.class_name except Exception as e: True == False #Проверка control_type try: lResult['control_type']=inElementInfo.control_type except Exception as e: True == False #Проверка control_id try: if inElementInfo.control_id!=0: lResult['control_id']=inElementInfo.control_id except Exception as e: True == False #Проверка rectangle left try: lResult['rectangle']['left']=inElementInfo.rectangle.left except Exception as e: True == False #Проверка rectangle right try: lResult['rectangle']['right']=inElementInfo.rectangle.right except Exception as e: True == False #Проверка rectangle top try: lResult['rectangle']['top']=inElementInfo.rectangle.top except Exception as e: True == False #Проверка rectangle bottom try: lResult['rectangle']['bottom']=inElementInfo.rectangle.bottom except Exception as e: True == False #Проверка runtime_id try: lResult['runtime_id']=inElementInfo.runtime_id except Exception as e: True == False #Вернуть результат return lResult ################################################################################################### #Get list of top level #old name - GetRootElementList def BackendStr_GetTopLevelList_UIOInfo(inBackend=mDefaultPywinautoBackend): #Получить список объектов lResultList=pywinauto.findwindows.find_elements(top_level_only=True,backend=inBackend) lResultList2=[] for lI in lResultList: lTempObjectInfo=lI lResultList2.append(UIOEI_Convert_UIOInfo(lI)); return lResultList2 ################################################################################################### #Highlight the UI object #old name - ElementDrawOutlineNew def UIOSelector_Highlight(inSpecificationArray): UIO_Highlight(UIOSelector_Get_UIO(inSpecificationArray)) return ################################################################################################### #inSpecificationArray - UIOSelector #old name - ElementDrawOutlineNewFocus def UIOSelector_FocusHighlight(inSpecificationArray): UIO_FocusHighlight(UIOSelector_Get_UIO(inSpecificationArray)) return ################################################################################################### #old name - draw_outline_new def UIO_Highlight(lWrapperObject,colour='green',thickness=2,fill=win32defines.BS_NULL,rect=None,inFlagSetFocus=False): if lWrapperObject is not None: """ Draw an outline around the window. * **colour** can be either an integer or one of 'red', 'green', 'blue' (default 'green') * **thickness** thickness of rectangle (default 2) * **fill** how to fill in the rectangle (default BS_NULL) * **rect** the coordinates of the rectangle to draw (defaults to the rectangle of the control) """ if inFlagSetFocus: #Установить фокус на объект, чтобы было видно выделение lWrapperObject.set_focus() time.sleep(0.5) # don't draw if dialog is not visible #if not lWrapperObject.is_visible(): # return colours = { "green": 0x00ff00, "blue": 0xff0000, "red": 0x0000ff, } # if it's a known colour if colour in colours: colour = colours[colour] if rect is None: rect = lWrapperObject.rectangle() # create the pen(outline) pen_handle = win32functions.CreatePen( win32defines.PS_SOLID, thickness, colour) # create the brush (inside) brush = win32structures.LOGBRUSH() brush.lbStyle = fill brush.lbHatch = win32defines.HS_DIAGCROSS brush_handle = win32functions.CreateBrushIndirect(ctypes.byref(brush)) # get the Device Context dc = win32functions.CreateDC("DISPLAY", None, None, None ) # push our objects into it win32functions.SelectObject(dc, brush_handle) win32functions.SelectObject(dc, pen_handle) # draw the rectangle to the DC win32functions.Rectangle( dc, rect.left, rect.top, rect.right, rect.bottom) # Delete the brush and pen we created win32functions.DeleteObject(brush_handle) win32functions.DeleteObject(pen_handle) # delete the Display context that we created win32functions.DeleteDC(dc) ################################################################################################### #Аналог подсвечивания + установка фокуса #old name - draw_outline_new_focus def UIO_FocusHighlight(lWrapperObject,colour='green',thickness=2,fill=win32defines.BS_NULL,rect=None): UIO_Highlight(lWrapperObject,'green',2,win32defines.BS_NULL,None,True) #Определить разрядность процесса lProcessBitnessStr = str(struct.calcsize("P") * 8) ############################ #Старая версия # Определять флаг Debug, если как второй входной параметр не поступил ключ RELEASE ############################ mFlagIsDebug=True if (len(sys.argv)>=2): if (sys.argv[1].upper()=="RELEASE"): mFlagIsDebug=False #Оповещение о выбранном режиме if mFlagIsDebug: logging.info("Robot/GUI: Debug mode, x"+lProcessBitnessStr) print ("Robot/GUI: Debug mode, x"+lProcessBitnessStr) else: logging.info("Robot/GUI: Release mode, x"+lProcessBitnessStr) #Нельзя делать print в release mode тк print делает вывод в PIPE поток, что нарушает последовательность взаимодействия с родительским процессом #print ("Robot/GUI: Release mode, x"+lProcessBitnessStr) #for line in sys.stdin: # lText=lText+line; #ctypes.windll.user32.MessageBoxW(0, lText, "Your title", 1) buffer = "" lJSONInputString="" #Выполнить чтение буфера, если не отладка библиотеки if not mFlagIsDebug: while True: lProcessResponse={"ErrorFlag":False} try: #Ожидаем синхронно поступление объекта lJSONInput = ProcessCommunicator.ProcessParentReadWaitObject() lProcessResponse["ActivitySpecificationDict"]=lJSONInput #Выполнить вызов функции lProcessResponse["Result"]=JSONNormalize.JSONNormalizeDictListStrIntBool(locals()[lJSONInput['ActivityName']](*lJSONInput['ArgumentList'],**lJSONInput['ArgumentDict'])) except Exception as e: #Установить флаг ошибки lProcessResponse["ErrorFlag"]=True #Зафиксировать traceback lProcessResponse["ErrorTraceback"]=traceback.format_exc() #Зафиксировать Error message lProcessResponse["ErrorMessage"]=str(e) #lProcessResponse["ErrorArgs"]=str(e.args) #Отправить ответ в родительский процесс ProcessCommunicator.ProcessParentWriteObject(lProcessResponse) else: print('Debug mode is turned on!') #if __name__ == '__main__': # if len(sys.argv) > 1: # lFunctionArgs = sys.argv[2:] # print(locals()[sys.argv[1]](*lFunctionArgs))