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 from threading import Timer #################################### #Info: GUI module of the Robot app (OpenRPA - Robot) #################################### # GUI Module - interaction with Desktop application #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 #} mFlagIsDebug=False mPywinautoApplication=pywinauto.Application(backend="win32") #mPywinautoActiveBackend="win32" mPywinautoActiveBackend="uia" #mPywinautoApplication=pywinauto.Application(backend="uia") ##### ##Внимание! Нельзя делать print так как он уходит в родительский поток и ломает механизм взаимодействия ##Чтобы его использовать надо писать функцию обертку ##### ########################### ####Модуль Automation ########################### #inElementSpecificationList = [ [{lvl_0},{lvl_1},{lvl_2}],... ] #result = pywinauto element wrapper instance or None def AutomationSearchMouseElement(inElementSpecification,inFlagIsSearchOnline=True): lGUISearchElementSelected=None #Настройка - частота обновления подсвечивания lTimeSleepSeconds=0.4 lElementFoundedList=[] #Ветка поиска в режиме реального времени if inFlagIsSearchOnline: #Сбросить нажатие Ctrl, если оно было bool(win32api.GetAsyncKeyState(17)) lFlagLoop = True while lFlagLoop: #Проверить, нажата ли клавиша Ctrl (код 17) lFlagKeyPressedCtrl=bool(win32api.GetAsyncKeyState(17)) #Подсветить объект, если мышка наведена над тем объектом, который не подсвечивался в прошлый раз if not lFlagKeyPressedCtrl: #Получить координаты мыши (lX,lY) = win32api.GetCursorPos() lElementFounded={} #Создать карту пикселей и элементов #####Внимание! Функция GUISearchElementByRootXY не написана lElementFoundedList=GUISearchElementByRootXY(PywinautoExtElementGet(inElementSpecification),lX,lY) #print(lElementFoundedList) lElementFounded=lElementFoundedList[-1]["element"] #Подсветить объект, если он мышь раньше стояла на другом объекте if lGUISearchElementSelected != lElementFounded: lGUISearchElementSelected = lElementFounded #Доработанная функция отрисовки if lElementFounded is not None: draw_outline_new(lElementFounded) else: #Была нажата клавиша Ctrl - выйти из цикла lFlagLoop=False; #Заснуть до следующего цикла time.sleep(lTimeSleepSeconds) #Ветка поиска по заранее созданной карте else: ################################### #Внимание Старая ветка (неправильный результат) ################################### lBitmap={} #Создать карту пикселей и элементов lBitmap=GUISearchBitmapCreate(PywinautoExtElementGet(inElementSpecification),lBitmap) #Выдать сообщение, что поиск готов к использованию #print("GUISearch: Ready for search!") ########### #Версия с задержкой (без таймеров, событий в отдельных потоках) ########### #Сбросить нажатие Ctrl, если оно было bool(win32api.GetAsyncKeyState(17)) lFlagLoop = True while lFlagLoop: #Проверить, нажата ли клавиша Ctrl (код 17) lFlagKeyPressedCtrl=bool(win32api.GetAsyncKeyState(17)) #Подсветить объект, если мышка наведена над тем объектом, который не подсвечивался в прошлый раз if not lFlagKeyPressedCtrl: #Получить координаты мыши (lX,lY) = win32api.GetCursorPos() #Подсветить объект, если мышь над ним if (lX,lY) in lBitmap: if lGUISearchElementSelected != lBitmap[lX,lY]: lGUISearchElementSelected = lBitmap[lX,lY] #Классическая функция отрисовки (из pywinauto) #lBitmap[lX,lY].draw_outline() #Доработанная функция отрисовки draw_outline_new(lBitmap[lX,lY]) else: lGUISearchElementSelected = None else: #Была нажата клавиша Ctrl - выйти из цикла lFlagLoop=False; #Заснуть до следующего цикла time.sleep(lTimeSleepSeconds) #Вернуть результат поиска return lElementFoundedList def AutomationSearchMouseElementHierarchy(inElementSpecification,inFlagIsSearchOnline=True): lItemInfo = [] #Запустить функцию поиска элемента по мыши lElementList = AutomationSearchMouseElement(inElementSpecification,inFlagIsSearchOnline) lElement = lElementList[-1]['element'] #Detect backend of the elements lFlagIsBackendWin32 = True #Если объект имеется (не None), то выполнить построение иерархии #pdb.set_trace() 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(ElementInfoExportObject(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 #Вернуть результат return lItemInfo #return [1,2,3,4,5,3] ########################### ####Модуль PywinautoExt ########################### def PywinautoExtElementCtrlIndexGet(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 #Получить список элементов, который удовлетворяет условиям через расширенный движок поиска #[ { #"index":<Позиция элемента в родительском объекте>, # "depth_start" - глубина, с которой начинается поиск (по умолчанию 1) # "depth_end" - глубина, до которой ведется поиск (по умолчанию 1) # "class_name" - наименование класса, который требуется искать # "title" - наименование заголовка # "rich_text" - наименование rich_text #} ] def PywinautoExtElementsGet (inSpecificationList,inElement=None): lResultList=[] lChildrenList=[] #Получить родительский объект если на вход ничего не поступило if inElement is None: #сформировать спецификацию на получение элемента lRootElementSpecification=[inSpecificationList[0]] lRootElement=GetControl(lRootElementSpecification) if lRootElement is not None: lChildrenList.append(lRootElement.wrapper_object()) #Елемент на вход поступил - выполнить его анализ else: #Получить список элементов lElementChildrenList=inElement.children() #Поступил index - точное добавление if 'index' in inSpecificationList[0]: if inSpecificationList[0]['index']1: lFlagGoCheck=False #pdb.set_trace() #Циклический обход по детям, на предмет соответствия всем условиям 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 #pdb.set_trace() #Циклический вызов для всех детей со скорректированной спецификацией lResultList.extend(PywinautoExtElementsGet(lChildrenItemNewSpecificationList,lChildrenItem)) #Фильтрация if lFlagGoCheck: lFlagAddChild=True #Фильтрация по title if 'title' in inSpecificationList[0]: if lChildrenItem.element_info.name != inSpecificationList[0]["title"]: lFlagAddChild=False #Фильтрация по rich_text if 'rich_text' in inSpecificationList[0]: if lChildrenItem.element_info.rich_text != inSpecificationList[0]["rich_text"]: lFlagAddChild=False #Фильтрация по class_name if 'class_name' in inSpecificationList[0]: if lChildrenItem.element_info.class_name != inSpecificationList[0]["class_name"]: lFlagAddChild=False #Фильтрация по friendly_class_name if 'friendly_class_name' in inSpecificationList[0]: if lChildrenItem.friendly_class_name() != inSpecificationList[0]["friendly_class_name"]: lFlagAddChild=False #Фильтрация по control_type if 'control_type' in inSpecificationList[0]: if lChildrenItem.element_info.control_type != inSpecificationList[0]["control_type"]: lFlagAddChild=False ##### #Все проверки пройдены - флаг добавления if lFlagAddChild: lChildrenList.append(lChildrenItem) #Выполнить рекурсивный вызов (уменьшение количества спецификаций), если спецификация больше одного элемента if len(inSpecificationList)>1 and len(lChildrenList)>0 is not None: #Вызвать рекурсивно функцию получения следующего объекта, если в спецификации есть следующий объект for lChildElement in lChildrenList: lResultList.extend(PywinautoExtElementsGet(inSpecificationList[1:],lChildElement)) else: lResultList.extend(lChildrenList) return lResultList #Получить список информационных объектов, который удовлетворяет условиям def PywinautoExtElementsGetInfo (inSpecificationList,inElement=None): #Получить родительский объект если на вход ничего не поступило lResultList=PywinautoExtElementsGet(inSpecificationList,inElement) lIterator = 0 for lItem in lResultList: lResultList[lIterator]=ElementInfoExportObject(lResultList[lIterator].element_info) lIterator = lIterator + 1 return lResultList #Получить элемент через расширенный движок поиска #[ { #"index":<Позиция элемента в родительском объекте>, # #} ] def PywinautoExtElementGet (inSpecificationList,inElement=None): lResult=None #Получить родительский объект если на вход ничего не поступило lResultList=PywinautoExtElementsGet(inSpecificationList,inElement) if len(lResultList)>0: lResult=lResultList[0] return lResult #Проверить, существует ли объект def PywinautoExtElementExist (inSpecificationList): #pdb.set_trace() return len(PywinautoExtElementsGet(inSpecificationList))>0 #Ожидать появления элемента def PywinautoExtElementWaitAppear(inSpecificationList,inTimeout=60): lTimeoutSeconds = 0 while (not PywinautoExtElementExist(inSpecificationList) and inTimeout>lTimeoutSeconds): lTimeoutSeconds = lTimeoutSeconds + 0.5 #Заснуть на полсекунды time.sleep(0.5) return PywinautoExtElementExist(inSpecificationList) #Функция, которая попытается восстановить окно, если оно есть, но свернуто (особенность uia backend - он не может прицепиться к окну, если оно свернуто) def PywinautoExtTryToRestore(inSpecificationList): #pdb.set_trace() try: #Подготовка взодного массива inControlSpecificationArray=ElementSpecificationArraySearchPrepare(inSpecificationList) #Выполнить подключение к объекту. Восстановление необходимо только в бэке win32, так как в uia свернутое окно не распознается lRPAApplication = pywinauto.Application(backend="win32") lRPAApplication.connect(**inSpecificationList[0]) lRPAApplication.top_window().restore() except Exception: True==False ################################ #Функция повторяющегося таймера ############################# class RepeatedTimer(object): def __init__(self, interval, function, *args, **kwargs): self._timer = None self.interval = interval self.function = function self.args = args self.kwargs = kwargs self.is_running = False self.start() def _run(self): self.is_running = False self.start() self.function(*self.args, **self.kwargs) def start(self): if not self.is_running: self._timer = Timer(self.interval, self._run) self._timer.start() self.is_running = True def stop(self): self._timer.cancel() self.is_running = False #mTimer.start() # after 30 seconds, "hello, world" will be printed #Флаг отладки напрямую (не выполнять чтение буфера stdin) ################################## ###Методы взаимодействия с GUI интерфейсом ################################## #pywinauto def GetControl(inControlSpecificationArray): #Подготовка взодного массива inControlSpecificationArray=ElementSpecificationArraySearchPrepare(inControlSpecificationArray) #Выполнить идентификацию объектов, если передан массив lResultList=[]; lTempObject=None if len(inControlSpecificationArray) > 0: #Выполнить подключение к объекту lRPAApplication = pywinauto.Application(backend=mPywinautoActiveBackend) #Проверка разрядности try: lRPAApplication.connect(**inControlSpecificationArray[0]) except Exception as e: PywinautoExtTryToRestore(inControlSpecificationArray) try: lRPAApplication.connect(**inControlSpecificationArray[0]) except Exception as e: lRPAApplication = None if lRPAApplication is not None: #lTempObject=lRPAApplication.window(**inControlSpecificationArray[0]) #Скорректировано из-за недопонимания структуры lTempObject=lRPAApplication #Нормализация массива для целей выборки объекта (удаление конфликтующих ключей) inControlSpecificationArray=ElementSpecificationListNormalize(inControlSpecificationArray) #Циклическое прохождение к недрам объекта for lWindowSpecification in inControlSpecificationArray[0:]: lTempObject=lTempObject.window(**lWindowSpecification) return lTempObject #Получить массив свойств и методов у элемента def ElementActionGetList (inControlSpecificationArray): #Получить объект lObject=PywinautoExtElementGet(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 #Выполнить действие над элементом def ElementRunAction(inControlSpecificationArray,inActionName,inArgumentList=[],inkwArgumentObject={}): #Определить объект lObject=PywinautoExtElementGet(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 def ElementGetInfo(inControlSpecificationArray): #Подготовка входного массива inControlSpecificationArray=ElementSpecificationArraySearchPrepare(inControlSpecificationArray) #Выполнить идентификацию объектов, если передан массив lResultList=[]; if len(inControlSpecificationArray) > 0: #Получить объект lTempObject=PywinautoExtElementGet(inControlSpecificationArray) #Получить инфо объект lTempObjectInfo = lTempObject.element_info #Добавить информацию об обнаруженом объекте lResultList.append(ElementInfoExportObject(lTempObjectInfo)); return lResultList ############################################ ###Модуль поиска объекта на экране ############################################ #Инициализация глобальных переменных mBitmap={} mGUISearchElementSelected={} def GUISearchEvent_on_move(lX, lY): global mBitmap global mGUISearchElementSelected if (lX,lY) in mBitmap: if mGUISearchElementSelected != mBitmap[lX,lY]: mGUISearchElementSelected = mBitmap[lX,lY] mBitmap[lX,lY].draw_outline() def GUISearchEvent_on_click(x, y, button, pressed): global mBitmap global mGUISearchElementSelected print('{0} at {1}'.format( 'Pressed' if pressed else 'Released', (x, y))) print(str(len(mBitmap))) if not pressed: # Stop listener del mBitmap mBitmap={} del mGUISearchElementSelected mGUISearchElementSelected={} return False def GUISearchEvent_on_scroll(x, y, dx, dy): print('Scrolled {0} at {1}'.format( 'down' if dy < 0 else 'up', (x, y))) #Создать bitmap карту объектов #GUISearchBitmapCreate #inElement - wrapper_object() # dict: x_y_ : elementObject def GUISearchRun(inSpecificationArray): lBitmap={} lGUISearchElementSelected=None #Создать карту пикселей и элементов lBitmap=GUISearchBitmapCreate(GetControl(inSpecificationArray),lBitmap) #Выдать сообщение, что поиск готов к использованию #print("GUISearch: Ready for search!") #mBitmap=lBitmap # Collect events until released #Версия с Events (занимает поток, чтобы использовать общие переменные) #with mouse.Listener( # on_move=GUISearchEvent_on_move, # on_click=GUISearchEvent_on_click, # on_scroll=GUISearchEvent_on_scroll) as listener: #listener.join() # True == False ##################### ###Версия с таймером ##################### #mTimer = RepeatedTimer(0.5, GUISearchDraw, lBitmap) # it auto-starts, no need of rt.start() #mTimer.start() ########### #Версия с задержкой ########### #Сбросить нажатие Ctrl, если оно было bool(win32api.GetAsyncKeyState(17)) lTimeSleepSeconds=0.7 lFlagLoop = True while lFlagLoop: #Проверить, нажата ли клавиша Ctrl (код 17) lFlagKeyPressedCtrl=bool(win32api.GetAsyncKeyState(17)) #Подсветить объект, если мышка наведена над тем объектом, который не подсвечивался в прошлый раз if not lFlagKeyPressedCtrl: #Получить координаты мыши (lX,lY) = win32api.GetCursorPos() #Подсветить объект, если мышь над ним if (lX,lY) in lBitmap: if lGUISearchElementSelected != lBitmap[lX,lY]: lGUISearchElementSelected = lBitmap[lX,lY] lBitmap[lX,lY].draw_outline() else: lGUISearchElementSelected = None else: #Была нажата клавиша Ctrl - выйти из цикла lFlagLoop=False; #Заснуть до следующего цикла time.sleep(lTimeSleepSeconds) #Вернуть результат поиска return lGUISearchElementSelected ############################################################# #Поиск элементов ############################################################# #inHierarchyList: [{"index":<>,"element":<>}] # #Вернуть словарь [{"index":<>,"element":<>}] #Последний элемент - искомый def GUISearchElementByRootXY(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 = GUISearchElementByRootXY(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 #Техническая функция def GUISearchBitmapCreate (inElement,inBitmapDict={}): #Добавить в карту информацию о текущем объекте inBitmapDict=GUISearchBitmapElementFill(inElement,inBitmapDict) #Получить список детей и добавить в карту for lItem in inElement.children(): inBitmapDict=GUISearchBitmapCreate(lItem,inBitmapDict) return inBitmapDict #GUISearchBitmapElementFill def GUISearchBitmapElementFill (inElement,inBitmapDict): lElementInfo=inElement.element_info #pdb.set_trace() #Получить параметры прямоугольника lElementRectX1 = 0 lElementRectX2 = 0 lElementRectY1 = 0 lElementRectY2 = 0 lFlagHasRect = True try: lElementRectX1=lElementInfo.rectangle.left lElementRectX2=lElementInfo.rectangle.right lElementRectY1=lElementInfo.rectangle.top lElementRectY2=lElementInfo.rectangle.bottom #Выполнить установку элемента, если прямоугольник получить удалось if lFlagHasRect: lX=lElementRectX1 #Циклический обход по оси X while lX<=lElementRectX2: lY=lElementRectY1 #Циклический обход по Оси Y while lY<=lElementRectY2: ###################### #Если объект в карте есть if (lX,lY) in inBitmapDict: if inBitmapDict[lX,lY] 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 #Получить координаты ИО lElementOldInfo = inBitmapDict[lX,lY].element_info #lElementNew = inElement lElementOldX1 = lElementOldInfo.rectangle.left lElementOldX2 = lElementOldInfo.rectangle.right lElementOldY1 = lElementOldInfo.rectangle.top lElementOldY2 = lElementOldInfo.rectangle.bottom #Проверка вхождения по типу 1 if (lElementOldX1>=lElementRectX1) and (lElementOldY1>=lElementRectY1) and (lElementOldX2<=lElementRectX2) and (lElementOldY2<=lElementRectY2): #Выполнить корректировку каретки по lY, чтобы не проходить по всем пикселям в рамках этой линии lY = lElementOldY2 + 1 #Проверка вхождения по типу 3 elif (lElementOldX1lElementRectX2) and (lElementOldY2>lElementRectY2): #Установка элемента по адресу [,] inBitmapDict[lX,lY]=inElement #Инкремент y lY=lY+1 #Проверка вхождения по типу 2 else: #Установка элемента по адресу [,] inBitmapDict[lX,lY]=inElement #Инкремент y lY=lY+1 #Если объекта в карте нет else: #Установка элемента по адресу [,] inBitmapDict[lX,lY]=inElement #Инкремент y lY=lY+1 else: #Установка элемента по адресу [,] inBitmapDict[lX,lY]=inElement #Инкремент y lY=lY+1 ###################### #Инкремент X lX=lX+1 except Exception as e: lFlagHasRect = False return inBitmapDict #debug def ElementGetChildElementList(inControlSpecificationArray=[]): #Подготовка входного массива inControlSpecificationArray=ElementSpecificationArraySearchPrepare(inControlSpecificationArray) #Выполнить идентификацию объектов, если передан массив lResultList=[]; #ctypes.windll.user32.MessageBoxW(0, str(inControlSpecificationArray), "Your title", 1) if len(inControlSpecificationArray) > 0: #Получить объект lTempObject = PywinautoExtElementGet(inControlSpecificationArray) #Получить список дочерних объектов lTempChildList = lTempObject.children() lIterator=0 #Подготовить результирующий объект for lChild in lTempChildList: lTempObjectInfo=lChild.element_info #Добавить информацию об обнаруженом объекте lObjectInfoItem=ElementInfoExportObject(lTempObjectInfo) #Итератор внутри объекта (для точной идентификации) lObjectInfoItem['ctrl_index']=lIterator; lResultList.append(lObjectInfoItem); #Инкремент счетчика lIterator=lIterator+1 else: lResultList=GetRootElementList() return lResultList #Подготовить спецификацию для поиска элемента def ElementSpecificationListNormalize(inElementSpecification): lResult=[] #Циклический обход for lSpecificationItem in inElementSpecification: 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 == "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") #Добавить строку в результирующий массив lResult.append(lSpecificationItemNew) #Вернуть результат return lResult #Подготовить массив для обращшения к поиску элемементов def ElementSpecificationArraySearchPrepare(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") #Добавить строку в результирующий массив lResult.append(lSpecificationItemNew) #Вернуть результат return lResult ############################### ####Нормализация под JSON (в JSON нельзя передавать классы - только null, числа, строки, словари и массивы) ############################### #Нормализация словаря под JSON def JSONNormalizeDictionary(inDictionary): #Сделать копию объекта lResult=inDictionary.copy() #Перебор всех элементов for lItemKey,lItemValue in inDictionary.items(): #Флаг удаления атрибута lFlagRemoveAttribute=False #Если строка или число или массив или объект или None - оставить if ( type(lItemValue) is dict or type(lItemValue) is int or type(lItemValue) is str or type(lItemValue) is list or lItemValue is None): True==True else: lFlagRemoveAttribute=True #Рекурсивный вызов, если объект является словарем if type(lItemValue) is dict: lResult[lItemKey]=JSONNormalizeDictionary(lItemValue) #Рекурсивный вызов, если объект является списком if type(lItemValue) is list: lResult[lItemKey]=JSONNormalizeList(lItemValue) ############################# #Конструкция по удалению ключа из словаря if lFlagRemoveAttribute: lResult.pop(lItemKey) #Вернуть результат return lResult #Нормализация массива под JSON def JSONNormalizeList(inList): lResult=[] #Циклический обход for lItemValue in inList: #Если строка или число или массив или объект или None - оставить if ( type(lItemValue) is int or type(lItemValue) is str or lItemValue is None): lResult.append(lItemValue) #Если является словарем - вызвать функцию нормализации словаря if type(lItemValue) is dict: lResult.append(JSONNormalizeDictionary(lItemValue)) #Если является массиваом - вызвать функцию нормализации массива if type(lItemValue) is list: lResult.append(JSONNormalizeList(lItemValue)) #Вернуть результат return lResult #Определить объект - dict or list - и нормализовать его для JSON def JSONNormalizeDictList(inDictList): lResult={} if type(inDictList) is dict: lResult=JSONNormalizeDictionary(inDictList) if type(inDictList) is list: lResult=JSONNormalizeList(inDictList) return lResult; #Получить объект из атрибутов, которые удалось прочитать def ElementInfoExportObject(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 def GetRootElementList(): #Получить список объектов lResultList=pywinauto.findwindows.find_elements(top_level_only=True,backend=mPywinautoActiveBackend) lResultList2=[] for lI in lResultList: lTempObjectInfo=lI lResultList2.append(ElementInfoExportObject(lI)); return lResultList2 def ElementDrawOutlineNew(inSpecificationArray): draw_outline_new(PywinautoExtElementGet(inSpecificationArray)) return def ElementDrawOutlineNewFocus(inSpecificationArray): draw_outline_new_focus(PywinautoExtElementGet(inSpecificationArray)) return def draw_outline_new(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) #pdb.set_trace() # 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) #Аналог подсвечивания + установка фокуса def draw_outline_new_focus(lWrapperObject,colour='green',thickness=2,fill=win32defines.BS_NULL,rect=None): draw_outline_new(lWrapperObject,'green',2,win32defines.BS_NULL,None,True) #run() lText = "Bitness:" + str(struct.calcsize("P") * 8) #for line in sys.stdin: # lText=lText+line; #ctypes.windll.user32.MessageBoxW(0, lText, "Your title", 1) buffer = "" lJSONInputString="" #Алгоритм включения debug режима (если передано ключевое слово debug как параметр) #Если есть хотя бы один параметр (sys.argv[1+]) if len(sys.argv) > 1: if sys.argv[1] == "debug": mFlagIsDebug=True #Выполнить чтение буфера, если не отладка библиотеки if not mFlagIsDebug: #{'functionName':'', 'argsArray':[]} while True: try: lJSONInput = ProcessCommunicator.ProcessParentReadWaitObject() lJSONInputString=str(lJSONInput) #{'outputObject':''} #Выполнить вызов функции lResult=locals()[lJSONInput['ActivityName']](*lJSONInput['ArgumentList']) lJSONInput['Result']=JSONNormalizeDictList(lResult) ProcessCommunicator.ProcessParentWriteObject(lJSONInput) except Exception as e: #Вывод ошибки в родительский поток ProcessCommunicator.ProcessParentWriteObject({'Error':str(e) + traceback.format_exc(), 'ArgObject':str(lJSONInputString)}) #ctypes.windll.user32.MessageBoxW(0, str(e), "Your title", 1) 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))