|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
#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))
|
|
|
|
|
|
|
|
|
|
|