From 83b8ad0e22e6011d69ce83d4f3837883360f9225 Mon Sep 17 00:00:00 2001 From: Ivan Maslov Date: Sun, 5 May 2019 20:52:50 +0300 Subject: [PATCH] =?UTF-8?q?#Robot=5F=D0=A3=D0=B2=D1=8F=D0=B7=D0=BA=D0=B0GU?= =?UTF-8?q?Ipy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Robot/GUI.py | 1128 ++++++++++++++++++++++++++++++++++ Robot/ProcessCommunicator.py | 1 + Robot/Robot.py | 1 - 3 files changed, 1129 insertions(+), 1 deletion(-) create mode 100644 Robot/GUI.py diff --git a/Robot/GUI.py b/Robot/GUI.py new file mode 100644 index 00000000..c77ec702 --- /dev/null +++ b/Robot/GUI.py @@ -0,0 +1,1128 @@ +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) + + +################ +###GeneralClipboardGet +################ +def GeneralClipboardGet(): + win32clipboard.OpenClipboard() + lResult = win32clipboard.GetClipboardData() + win32clipboard.CloseClipboard() + return lResult +################ +###GeneralClipboardSet +################ +def GeneralClipboardSet(inText): + win32clipboard.OpenClipboard() + win32clipboard.EmptyClipboard() + win32clipboard.SetClipboardText(inText) + win32clipboard.CloseClipboard() +#Уснуть на 2 секунды +def GeneralSleep2s(): + time.sleep(2) + +#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)) + + diff --git a/Robot/ProcessCommunicator.py b/Robot/ProcessCommunicator.py index 57664249..1875b8b9 100644 --- a/Robot/ProcessCommunicator.py +++ b/Robot/ProcessCommunicator.py @@ -1,6 +1,7 @@ import json import subprocess import zlib +import sys import os ############################################ ####Межпроцессное взаимодействие diff --git a/Robot/Robot.py b/Robot/Robot.py index 48056227..425009f3 100644 --- a/Robot/Robot.py +++ b/Robot/Robot.py @@ -1,5 +1,4 @@ import pdb -import pywinauto import json import subprocess import zlib