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

1129 lines
58 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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: <Function or procedure name in module, str>,
# ArgumentList: [<Argument 1, any type>, ...] - optional,
# ArgumentDict: {<Argument 1 name, str>:<Argument 1 value, any type>, ...} - optional
#}
#outActivityResultDict:
#{
# ActivitySpecificationDict: {
# ModuleName: <"GUI", str>, -optional
# ActivityName: <Function or procedure name in module, str>,
# ArgumentList: [<Argument 1, any type>, ...] - optional,
# ArgumentDict: {<Argument 1 name, str>: <Argument 1 value, any type>, ...} - optional
# },
# ErrorFlag: <Boolean flag - Activity result has error (true) or not (false), boolean>,
# ErrorMessage: <Error message, str> - required if ErrorFlag is true,
# ErrorTraceback: <Error traceback log, str> - required if ErrorFlag is true,
# Result: <Result, returned from the Activity, int, str, boolean, list, dict> - 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):
#Прекратить поиск, если элемент был обнаружен
if inElement == lElementParentChildrenList[lResult]:
lFlagFind = False
else:
#Прекратить поиски, если итератор вышел за пределы списка
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']<len(lElementChildrenList):
#Получить дочерний элемент - точное добавление
lChildrenList.append(lElementChildrenList[inSpecificationList[0]['index']])
else:
raise ValueError('Object has no children with index: ' + str(inSpecificationList[0]['index']))
#Поступил ctrl_index - точное добавление
elif 'ctrl_index' in inSpecificationList[0]:
if inSpecificationList[0]['ctrl_index']<len(lElementChildrenList):
#Получить дочерний элемент
lChildrenList.append(lElementChildrenList[inSpecificationList[0]['ctrl_index']])
else:
raise ValueError('Object has no children with index: ' + str(inSpecificationList[0]['ctrl_index']))
#Если нет точного обозначения элемента
else:
lFlagGoCheck=True
#Учесть поле depth_start (если указано)
if 'depth_start' in inSpecificationList[0]:
if inSpecificationList[0]["depth_start"]>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 (lResultElementX1<lChildFoundedElementX1) and (lResultElementY1<lChildFoundedElementY1) and (lResultElementX2>lChildFoundedElementX2) 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 (lElementOldX1<lElementRectX1) and (lElementOldY1<lElementRectY1) and (lElementOldX2>lElementRectX2) and (lElementOldY2>lElementRectY2):
#Установка элемента по адресу [<x>,<y>]
inBitmapDict[lX,lY]=inElement
#Инкремент y
lY=lY+1
#Проверка вхождения по типу 2
else:
#Установка элемента по адресу [<x>,<y>]
inBitmapDict[lX,lY]=inElement
#Инкремент y
lY=lY+1
#Если объекта в карте нет
else:
#Установка элемента по адресу [<x>,<y>]
inBitmapDict[lX,lY]=inElement
#Инкремент y
lY=lY+1
else:
#Установка элемента по адресу [<x>,<y>]
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))