From e00eb589ac55e19af18b7a4123683ef1b627f51a Mon Sep 17 00:00:00 2001 From: Ivan Maslov Date: Thu, 14 Nov 2019 21:11:24 +0300 Subject: [PATCH] #1.0.25 #Safe call #UIDesktop (no Robot.py is needed) #Need test --- .gitignore | 8 +- Robot/Examples/UIDesktopTestBitness/Script.py | 16 + .../UIDesktopTestBitness/ScriptStart_x64.cmd | 3 + .../Examples/UIDesktopTestBitness/Settings.py | 18 + Sources/pyOpenRPA/Robot/ProcessBitness.py | 81 --- Sources/pyOpenRPA/Robot/UIDesktop.py | 488 ++++++++++++------ .../pyOpenRPA/Robot/Utils/ProcessBitness.py | 85 +++ Sources/pyOpenRPA/Robot/Utils/__init__.py | 1 + Sources/pyOpenRPA/Robot/__main__.py | 1 + changelog.md | 20 + v1.0.24 => v1.0.25 | 0 11 files changed, 479 insertions(+), 242 deletions(-) create mode 100644 Robot/Examples/UIDesktopTestBitness/Script.py create mode 100644 Robot/Examples/UIDesktopTestBitness/ScriptStart_x64.cmd create mode 100644 Robot/Examples/UIDesktopTestBitness/Settings.py delete mode 100644 Sources/pyOpenRPA/Robot/ProcessBitness.py create mode 100644 Sources/pyOpenRPA/Robot/Utils/ProcessBitness.py create mode 100644 Sources/pyOpenRPA/Robot/Utils/__init__.py rename v1.0.24 => v1.0.25 (100%) diff --git a/.gitignore b/.gitignore index 2eb6a28f..8aa3235f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,11 +3,11 @@ #Игнорируем папки распакованного блокнота /**/Resources/Editor/Notepad++_7_7_x64/* /**/Resources/Editor/Notepad++_7_7_x32/* -/**/Orchestrator/Reports -/**/Sources/Reports +/**/Orchestrator/**/Reports +/**/Robot/**/Reports /**/Sources/build /**/Sources/dist -/**/Studio/Reports +/**/Studio/**/Reports /**/OpenRPA_Orchestrator.exe /**/OpenRPAOrchestrator.exe /**/OpenRPARobotGUIx32.exe @@ -16,5 +16,5 @@ /**/OpenRPARobot.exe /**/breakpoints.lst /**/Orchestrator/screenshot.png -.idea/** +**/.idea/** /**/screenshot.png \ No newline at end of file diff --git a/Robot/Examples/UIDesktopTestBitness/Script.py b/Robot/Examples/UIDesktopTestBitness/Script.py new file mode 100644 index 00000000..e9fa8d7c --- /dev/null +++ b/Robot/Examples/UIDesktopTestBitness/Script.py @@ -0,0 +1,16 @@ +import keyboard +import subprocess +import time +import Settings +import sys +sys.path.insert(0,"C:\\Abs\\Archive\\scopeSrcUL\\OpenRPA\\Sources") +from pyOpenRPA.Robot import UIDesktop #OpenRPA UIDesktop + +global mSettingsDict +mSettingsDict = Settings.Settings() +#UIDesktop initialization +UIDesktop.Utils.ProcessBitness.SettingsInit(mSettingsDict.get("pyOpenRPA",{}).get("Robot",{}).get("UIDesktop",{}).get("Utils",{}).get("ProcessBitness",None)) + +#Robot script +#UIDesktop.UIOSelector_Get_UIO([{"title_re":".*Configu.*", "backend":"uia"}]).draw_outline() +UIDesktop.UIOSelectorUIOActivity_Run_Dict([{"title_re":".*Configu.*", "backend":"uia"}],"draw_outline") \ No newline at end of file diff --git a/Robot/Examples/UIDesktopTestBitness/ScriptStart_x64.cmd b/Robot/Examples/UIDesktopTestBitness/ScriptStart_x64.cmd new file mode 100644 index 00000000..8aa6e4a7 --- /dev/null +++ b/Robot/Examples/UIDesktopTestBitness/ScriptStart_x64.cmd @@ -0,0 +1,3 @@ +cd %~dp0 +..\..\..\Resources\WPy64-3720\python-3.7.2.amd64\python.exe "Script.py" +pause >nul \ No newline at end of file diff --git a/Robot/Examples/UIDesktopTestBitness/Settings.py b/Robot/Examples/UIDesktopTestBitness/Settings.py new file mode 100644 index 00000000..63db19d4 --- /dev/null +++ b/Robot/Examples/UIDesktopTestBitness/Settings.py @@ -0,0 +1,18 @@ +def Settings(): + lResult={ + "pyOpenRPA":{ + "Robot":{ + "UIDesktop":{ + "Utils":{ + "ProcessBitness":{ + "Python32FullPath": "..\\..\\..\\Resources\\WPy32-3720\\python-3.7.2\\python.exe", #Set from user: "..\\Resources\\WPy32-3720\\python-3.7.2\\OpenRPARobotGUIx32.exe" + "Python64FullPath": None, #Set from user + "Python32ProcessName": "OpenRPA_UIDesktopX32.exe", #Config set once + "Python64ProcessName": "OpenRPA_UIDesktopX64.exe" #Config set once + } + } + } + } + } + } + return lResult \ No newline at end of file diff --git a/Sources/pyOpenRPA/Robot/ProcessBitness.py b/Sources/pyOpenRPA/Robot/ProcessBitness.py deleted file mode 100644 index 8c3f7a2c..00000000 --- a/Sources/pyOpenRPA/Robot/ProcessBitness.py +++ /dev/null @@ -1,81 +0,0 @@ -import pywinauto #Is needed to detect OS bitness -import struct # Need to detect Current process bitness -import subprocess #Need to create subprocess -import os # Is needed to check file/folder path -############################################ -####Module, which control the Bitness between 32 and 64 python (needed for pywinauto framework to work correctly) -############################################ -global mSettingsDict -mSettingsDict = { - "BitnessProcessCurrent": "64", # "64" or "32" - "BitnessOS": "64", # "64" or "32" - "Python32FullPath": None, #Set from user: "..\\Resources\\WPy32-3720\\python-3.7.2\\OpenRPARobotGUIx32.exe" - "Python64FullPath": None, #Set from user - "Python32ProcessName": "OpenRPAUIDesktopX32.exe", #Config set once - "Python64ProcessName": "OpenRPAUIDesktopX64.exe", #Config set once - "Python32Process":None, - "Python64Process":None, - "PythonArgs":["-m","pyOpenRPA"] #Start other bitness openRPA process with PIPE channel -} -#Init the global configuration -def SettingsInit(inSettingsDict): - global mSettingsDict - #Update values in settings from input - mSettingsDict.update(inSettingsDict) - #mSettingsDict = inSettingsDict - #################### - #Detect OS bitness - ####BitnessOS####### - lBitnessOS="32"; - if pywinauto.sysinfo.is_x64_OS(): - lBitnessOS="64"; - inSettingsDict["BitnessOS"]=lBitnessOS - #################### - #Detect current process bitness - ####BitnessProcessCurrent####### - lBitnessProcessCurrent = str(struct.calcsize("P") * 8) - inSettingsDict["BitnessProcessCurrent"]=lBitnessProcessCurrent - ##################################### - #Create the other bitness process if OS is 64 and we have another Python path - ########################################################################## - #Check if OS is x64, else no 64 is applicable - if mSettingsDict["BitnessOS"]=="64": - #Check if current bitness is 64 - if mSettingsDict["BitnessProcessCurrent"]=="64": - #create x32 if Python 32 path is exists - if mSettingsDict["Python32FullPath"] and mSettingsDict["Python32ProcessName"]: - #Calculate python.exe folder path - lPython32FolderPath= "\\".join(mSettingsDict["Python32FullPath"].split("\\")[:-1]) - lPython32NewNamePath = f"{lPython32FolderPath}\\{mSettingsDict["Python32ProcessName"]}" - if not os.path.isfile(lPython32NewNamePath): - shutil.copyfile(mSettingsDict["Python32FullPath"],lPython32NewNamePath) - mSettingsDict["Python32Process"] = subprocess.Popen([lPython32NewNamePath] + mSettingsDict["Python32FullPath"],stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE) - else: - #bitness current process is 32 - #return x64 if it is exists - if mSettingsDict["Python64Process"]: - #Calculate python.exe folder path - lPython64FolderPath= "\\".join(mSettingsDict["Python64FullPath"].split("\\")[:-1]) - lPython64NewNamePath = f"{lPython64FolderPath}\\{mSettingsDict["Python64ProcessName"]}" - if not os.path.isfile(lPython64NewNamePath): - shutil.copyfile(mSettingsDict["Python32FullPath"],lPython64NewNamePath) - mSettingsDict["Python64Process"] = subprocess.Popen([lPython64NewNamePath] + mSettingsDict["Python64FullPath"],stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE) -#Return the other module bitnes -def OtherBitnessGet(): - #Result template - lResult = None - global mSettingsDict - #Check if OS is x64, else no 64 is applicable - if mSettingsDict["BitnessOS"]=="64": - #Check if current bitness is 64 - if mSettingsDict["BitnessProcessCurrent"]=="64": - #return x32 if it is exists - if mSettingsDict["Python32Process"]: - lResult = mSettingsDict["Python32Process"] - else: - #bitness current process is 32 - #return x64 if it is exists - if mSettingsDict["Python64Process"]: - lResult = mSettingsDict["Python64Process"] - #Exit - return lResult \ No newline at end of file diff --git a/Sources/pyOpenRPA/Robot/UIDesktop.py b/Sources/pyOpenRPA/Robot/UIDesktop.py index c2a6badf..8c86eea5 100644 --- a/Sources/pyOpenRPA/Robot/UIDesktop.py +++ b/Sources/pyOpenRPA/Robot/UIDesktop.py @@ -14,6 +14,7 @@ import time import traceback from . import ProcessCommunicator from . import JSONNormalize +from . import Utils #For ProcessBitness from threading import Timer import datetime import logging @@ -34,7 +35,16 @@ mRobotLoggerFormatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname) mRobotLoggerFH.setFormatter(mRobotLoggerFormatter) # add handler to logger object mRobotLogger.addHandler(mRobotLoggerFH) - +############################################ +#When import UIDesktop init the other bitness python +#For this type UIDesktop.Utils.ProcessBitness.SettingsInit(inSettingsDict) +#inSettingsDict = { +# "Python32FullPath": None, #Set from user: "..\\Resources\\WPy32-3720\\python-3.7.2\\OpenRPARobotGUIx32.exe" +# "Python64FullPath": None, #Set from user +# "Python32ProcessName": "OpenRPAUIDesktopX32.exe", #Config set once +# "Python64ProcessName": "OpenRPAUIDesktopX64.exe" #Config set once +#} +############################################ #logging.basicConfig(filename="Reports\ReportRobotGUIRun_"+datetime.datetime.now().strftime("%Y_%m_%d")+".log", level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s") @@ -258,14 +268,32 @@ def UIOSelector_Get_UIO (inSpecificationList,inElement=None,inFlagRaiseException return lResult ################################################################################################# #Check if UIO exist (Identified by the UIOSelector) -#inSpecificationList - UIOSelector +#!!!!!Safe call is included (you can set activity and UIDesktop will choose the bitness and return the result)!!!!! +#UIOSelector #old name - - -def UIOSelector_Exist_Bool (inSpecificationList): +def UIOSelector_Exist_Bool (inUIOSelector): lResult=False - #Получить родительский объект если на вход ничего не поступило - lResultList=UIOSelector_Get_UIOList(inSpecificationList,None,False) - if len(lResultList)>0: - lResult=True + #Check the bitness + lSafeOtherProcess = UIOSelector_SafeOtherGet_Process(inUIOSelector) + if lSafeOtherProcess is None: + #Получить родительский объект если на вход ничего не поступило + lResultList=UIOSelector_Get_UIOList(inUIOSelector, None, False) + if len(lResultList)>0: + lResult=True + else: + # Run function from other process with help of PIPE + lPIPEResuestDict = {"ModuleName": "UIDesktop", "ActivityName": "UIOSelector_Exist_Bool", + "ArgumentList": [inUIOSelector], + "ArgumentDict": {}} + # Отправить запрос в дочерний процесс, который отвечает за работу с Windows окнами + ProcessCommunicator.ProcessChildSendObject(lSafeOtherProcess, lPIPEResuestDict) + # Get answer from child process + lPIPEResponseDict = ProcessCommunicator.ProcessChildReadWaitObject(lSafeOtherProcess) + if lPIPEResponseDict["ErrorFlag"]: + raise Exception( + f"Exception was occured in child process (message): {lPIPEResponseDict['ErrorMessage']}, (traceback): {lPIPEResponseDict['ErrorTraceback']}") + else: + lResult = lPIPEResponseDict["Result"] return lResult ################################################################################################# #Wait for UIO is appear (at least one of them or all at the same time) @@ -385,6 +413,21 @@ def UIOSelector_Get_BitnessInt (inSpecificationList): lResult=32 return lResult ################################################################################################# +#Get process bitness ("32" or "64") +#inSpecificationList - UIOSelector +#old name - None +#return None (if Process not found), int 32, or int 64 +def UIOSelector_Get_BitnessStr (inSpecificationList): + lResult=None + #Получить объект Application (Для проверки разрядности) + lRootElement=PWASpecification_Get_PWAApplication(inSpecificationList) + if lRootElement is not None: + if lRootElement.is64bit(): + lResult="64" + else: + lResult="32" + return lResult +################################################################################################# #Get OS bitness (32 or 64) #old name - None #return int 32, or int 64 @@ -393,6 +436,18 @@ def Get_OSBitnessInt (): if pywinauto.sysinfo.is_x64_OS(): lResult=64; return lResult; +################################################################################################# +#Safe get other process or None if destination app is the other/same bitness +#inUIOSelector - selector of the destination +#return None or process (of the other bitness) +def UIOSelector_SafeOtherGet_Process(inUIOSelector): + #Default value + lResult = None + #Get selector bitness + lUIOSelectorAppBitness = UIOSelector_Get_BitnessStr(inUIOSelector) + if lUIOSelectorAppBitness and Utils.ProcessBitness.mSettingsDict["BitnessProcessCurrent"] != lUIOSelectorAppBitness: + lResult = Utils.ProcessBitness.OtherProcessGet() + return lResult ################################################################################################## #inControlSpecificationArray - List of dict, dict in pywinauto.find_windows notation #Backend selection - attribute "backend" ("win32" || "uia") in 1-st list element @@ -523,48 +578,66 @@ def UIOSelector_SearchChildByMouse_UIO(inElementSpecification): #################################################################################################### #inElementSpecification - UIOSelector +#!!!!!Safe call is included (you can set activity and UIDesktop will choose the bitness and return the result)!!!!! #old name - AutomationSearchMouseElementHierarchy -def UIOSelector_SearchChildByMouse_UIOTree(inElementSpecification): +def UIOSelector_SearchChildByMouse_UIOTree(inUIOSelector): lItemInfo = [] - #Запустить функцию поиска элемента по мыши - lElementList = UIOSelector_SearchChildByMouse_UIO(inElementSpecification) - lElement = lElementList[-1]['element'] - #Detect backend of the elements - lFlagIsBackendWin32 = True - #Если объект имеется (не None), то выполнить построение иерархии - if lElement is not None: - if lElement.backend.name == 'uia': - lFlagIsBackendWin32 = False - #Циклическое создание дерева - #while lElement is not None: - lListIterator=0 - lItemInfo2=lItemInfo - for lListItem in lElementList: - lElement = lListItem["element"] - #Продолжать построение иерархии во всех случаях кроме бэк uia & parent() is None - #if not lFlagIsBackendWin32 and lElement.parent() is None: - # lElement = None - #else: - #Получить информацию про объект - lItemInfo2.append(UIOEI_Convert_UIOInfo(lElement.element_info)) - #Дообогатить информацией об индексе ребенка в родительском объекте - if "index" in lListItem: - if lListItem["index"] is not None: - lItemInfo2[-1]['ctrl_index']=lListItem["index"] + #Check the bitness + lSafeOtherProcess = UIOSelector_SafeOtherGet_Process(inUIOSelector) + if lSafeOtherProcess is None: + #Запустить функцию поиска элемента по мыши + lElementList = UIOSelector_SearchChildByMouse_UIO(inUIOSelector) + lElement = lElementList[-1]['element'] + #Detect backend of the elements + lFlagIsBackendWin32 = True + #Если объект имеется (не None), то выполнить построение иерархии + if lElement is not None: + if lElement.backend.name == 'uia': + lFlagIsBackendWin32 = False + #Циклическое создание дерева + #while lElement is not None: + lListIterator=0 + lItemInfo2=lItemInfo + for lListItem in lElementList: + lElement = lListItem["element"] + #Продолжать построение иерархии во всех случаях кроме бэк uia & parent() is None + #if not lFlagIsBackendWin32 and lElement.parent() is None: + # lElement = None + #else: + #Получить информацию про объект + lItemInfo2.append(UIOEI_Convert_UIOInfo(lElement.element_info)) + #Дообогатить информацией об индексе ребенка в родительском объекте + if "index" in lListItem: + if lListItem["index"] is not None: + lItemInfo2[-1]['ctrl_index']=lListItem["index"] + else: + if "ctrl_index" in lListItem: + lItemInfo2[-1]['ctrl_index']=lListItem["ctrl_index"] else: if "ctrl_index" in lListItem: lItemInfo2[-1]['ctrl_index']=lListItem["ctrl_index"] - else: - if "ctrl_index" in lListItem: - lItemInfo2[-1]['ctrl_index']=lListItem["ctrl_index"] - #Оборачиваем потомка в массив, потому что у родителя по структуре интерфейса может быть больше одного наследников - lItemInfo2[-1]['SpecificationChild']=[] - lItemInfo2=lItemInfo2[-1]['SpecificationChild'] - #Переход на родительский объект - #lElement = lElement.parent() - lListIterator=lListIterator+1 - #Добавить информацию о Backend в первый объект - lItemInfo[0]["backend"]=lElement.backend.name + #Оборачиваем потомка в массив, потому что у родителя по структуре интерфейса может быть больше одного наследников + lItemInfo2[-1]['SpecificationChild']=[] + lItemInfo2=lItemInfo2[-1]['SpecificationChild'] + #Переход на родительский объект + #lElement = lElement.parent() + lListIterator=lListIterator+1 + #Добавить информацию о Backend в первый объект + lItemInfo[0]["backend"]=lElement.backend.name + else: + # Run function from other process with help of PIPE + lPIPEResuestDict = {"ModuleName": "UIDesktop", "ActivityName": "UIOSelector_SearchChildByMouse_UIOTree", + "ArgumentList": [inUIOSelector], + "ArgumentDict": {}} + # Отправить запрос в дочерний процесс, который отвечает за работу с Windows окнами + ProcessCommunicator.ProcessChildSendObject(lSafeOtherProcess, lPIPEResuestDict) + # Get answer from child process + lPIPEResponseDict = ProcessCommunicator.ProcessChildReadWaitObject(lSafeOtherProcess) + if lPIPEResponseDict["ErrorFlag"]: + raise Exception( + f"Exception was occured in child process (message): {lPIPEResponseDict['ErrorMessage']}, (traceback): {lPIPEResponseDict['ErrorTraceback']}") + else: + lItemInfo = lPIPEResponseDict["Result"] #Вернуть результат return lItemInfo #################################################################################################### @@ -600,41 +673,36 @@ def UIO_GetCtrlIndex_Int(inElement): return lResult #################################################################################################### -#Получить список информационных объектов, который удовлетворяет условиям +# Get the UIO Info list for the selected criteria +#!!!!!Safe call is included (you can set activity and UIDesktop will choose the bitness and return the result)!!!!! #inSpecificationList - UIOSelector #old name - PywinautoExtElementsGetInfo -def UIOSelector_Get_UIOInfoList (inSpecificationList,inElement=None): - #Получить родительский объект если на вход ничего не поступило - lResultList=UIOSelector_Get_UIOList(inSpecificationList,inElement) - lIterator = 0 - for lItem in lResultList: - lResultList[lIterator]=UIOEI_Convert_UIOInfo(lResultList[lIterator].element_info) - lIterator = lIterator + 1 +def UIOSelector_Get_UIOInfoList (inUIOSelector, inElement=None): + #Check the bitness + lSafeOtherProcess = UIOSelector_SafeOtherGet_Process(inUIOSelector) + if lSafeOtherProcess is None: + #Получить родительский объект если на вход ничего не поступило + lResultList=UIOSelector_Get_UIOList(inUIOSelector, inElement) + lIterator = 0 + for lItem in lResultList: + lResultList[lIterator]=UIOEI_Convert_UIOInfo(lResultList[lIterator].element_info) + lIterator = lIterator + 1 + else: + # Run function from other process with help of PIPE + lPIPEResuestDict = {"ModuleName": "UIDesktop", "ActivityName": "UIOSelector_Get_UIOInfoList", + "ArgumentList": [inUIOSelector, inElement], + "ArgumentDict": {}} + # Отправить запрос в дочерний процесс, который отвечает за работу с Windows окнами + ProcessCommunicator.ProcessChildSendObject(lSafeOtherProcess, lPIPEResuestDict) + # Get answer from child process + lPIPEResponseDict = ProcessCommunicator.ProcessChildReadWaitObject(lSafeOtherProcess) + if lPIPEResponseDict["ErrorFlag"]: + raise Exception( + f"Exception was occured in child process (message): {lPIPEResponseDict['ErrorMessage']}, (traceback): {lPIPEResponseDict['ErrorTraceback']}") + else: + lResultList = lPIPEResponseDict["Result"] return lResultList -#################################################################################################### -#Check is the UIO/UIO's by the UIOSelector exist -#inSpecificationList - UIOSelector -#old name - PywinautoExtElementExist -def UIOSelector_IsExist_Bool (inSpecificationList): - return len(UIOSelector_Get_UIOList(inSpecificationList))>0 - -#################################################################################################### -#Wait for the UIO by the UIOSelector appear -#inSpecificationList - UIOSelector -#result - { } -#old name - PywinautoExtElementWaitAppear -############# -#Внимание! Старая функция (на замену ей пришла UIOSelectorSecs_WaitAppear_Bool) -############# -def UIOSelector_WaitAppear_Dict(inSpecificationList,inTimeout=60): - lTimeoutSeconds = 0 - while (not UIOSelector_IsExist_Bool(inSpecificationList) and inTimeout>lTimeoutSeconds): - lTimeoutSeconds = lTimeoutSeconds + 0.5 - #Заснуть на полсекунды - time.sleep(0.5) - return UIOSelector_IsExist_Bool(inSpecificationList) - #################################################################################################### #Try to restore (maximize) window, if it's was minimized #(особенность uia backend - он не может прицепиться к окну, если оно свернуто) @@ -655,72 +723,124 @@ def UIOSelector_TryRestore_Dict(inSpecificationList): return lResult #################################################################################################### #Get the list of the UI object activities +#!!!!!Safe call is included (you can set activity and UIDesktop will choose the bitness and return the result)!!!!! #inControlSpecificationArray - UIOSelector #old name - ElementActionGetList -def UIOSelector_Get_UIOActivityList (inControlSpecificationArray): - #Получить объект - lObject=UIOSelector_Get_UIO(inControlSpecificationArray) - lActionList=dir(lObject) - lResult=dir(lObject) - #Выполнить чистку списка от неактуальных методов - for lActionItem in lActionList: - #Удалить те, которые начинаются на _ - if lActionItem[0]=='_': - lResult.remove(lActionItem) - #Удалить те, которые начинаются с символа верхнего регистра - if lActionItem[0].isupper(): - lResult.remove(lActionItem) +def UIOSelector_Get_UIOActivityList (inUIOSelector): + #Check the bitness + lSafeOtherProcess = UIOSelector_SafeOtherGet_Process(inUIOSelector) + if lSafeOtherProcess is None: + #Получить объект + lObject=UIOSelector_Get_UIO(inUIOSelector) + lActionList=dir(lObject) + lResult=dir(lObject) + #Выполнить чистку списка от неактуальных методов + for lActionItem in lActionList: + #Удалить те, которые начинаются на _ + if lActionItem[0]=='_': + lResult.remove(lActionItem) + #Удалить те, которые начинаются с символа верхнего регистра + if lActionItem[0].isupper(): + lResult.remove(lActionItem) + else: + # Run function from other process with help of PIPE + lPIPEResuestDict = {"ModuleName": "UIDesktop", "ActivityName": "UIOSelector_Get_UIOActivityList", + "ArgumentList": [inUIOSelector], + "ArgumentDict": {}} + # Отправить запрос в дочерний процесс, который отвечает за работу с Windows окнами + ProcessCommunicator.ProcessChildSendObject(lSafeOtherProcess, lPIPEResuestDict) + # Get answer from child process + lPIPEResponseDict = ProcessCommunicator.ProcessChildReadWaitObject(lSafeOtherProcess) + if lPIPEResponseDict["ErrorFlag"]: + raise Exception( + f"Exception was occured in child process (message): {lPIPEResponseDict['ErrorMessage']}, (traceback): {lPIPEResponseDict['ErrorTraceback']}") + else: + lResult = lPIPEResponseDict["Result"] return lResult #################################################################################################### #Run the activity in UIO (UI Object) -#inControlSpecificationArray - UIOSelector +#!!!!!Safe call is included (you can set activity and UIDesktop will choose the bitness and return the result)!!!!! +#inUIOSelector #inActionName - UIOActivity (name) from Pywinauto #old name - ElementRunAction -def UIOSelectorUIOActivity_Run_Dict(inControlSpecificationArray,inActionName,inArgumentList=[],inkwArgumentObject={}): +def UIOSelectorUIOActivity_Run_Dict(inUIOSelector, inActionName, inArgumentList=[], inkwArgumentObject={}): lResult={} - #Определить объект - lObject=UIOSelector_Get_UIO(inControlSpecificationArray) - #Получить метод для вызова - lFunction = getattr(lObject, inActionName) - #Выполнить действие - #Обернуто в безопасную обработку, тк для некоторых объектов метод не работает и может выдавать ошибку типа: NotImplementedError: This method not work properly for WinForms DataGrid, use cells() - try: - return lFunction(*inArgumentList,**inkwArgumentObject) - except Exception as e: - #Если ошибка возникла на action get_properties - if inActionName=="get_properties": - lResult={} - #Ручное формирование - lResult["class_name"]=lObject.class_name() - lResult["friendly_class_name"]=lObject.friendly_class_name() - lResult["texts"]=lObject.texts() - lResult["control_id"]=lObject.control_id() - lResult["control_count"]=lObject.control_count() - lResult["automation_id"]=lObject.automation_id() - return lResult + #Check the bitness + lSafeOtherProcess = UIOSelector_SafeOtherGet_Process(inUIOSelector) + #Run activity if SafeOtherProcess is None + if lSafeOtherProcess is None: + #Определить объект + lObject=UIOSelector_Get_UIO(inUIOSelector) + #Получить метод для вызова + 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 + else: + #Run function from other process with help of PIPE + lPIPEResuestDict = {"ModuleName": "UIDesktop", "ActivityName": "UIOSelectorUIOActivity_Run_Dict", + "ArgumentList": [inUIOSelector, inActionName, inArgumentList, inkwArgumentObject], + "ArgumentDict": {}} + # Отправить запрос в дочерний процесс, который отвечает за работу с Windows окнами + ProcessCommunicator.ProcessChildSendObject(lSafeOtherProcess, lPIPEResuestDict) + # Get answer from child process + lPIPEResponseDict = ProcessCommunicator.ProcessChildReadWaitObject(lSafeOtherProcess) + if lPIPEResponseDict["ErrorFlag"]: + raise Exception(f"Exception was occured in child process (message): {lPIPEResponseDict['ErrorMessage']}, (traceback): {lPIPEResponseDict['ErrorTraceback']}") else: - raise e + lResult = lPIPEResponseDict["Result"] return lResult #################################################################################################### #Get the UIO dict of the attributes +#!!!!!Safe call is included (you can set activity and UIDesktop will choose the bitness and return the result)!!!!! #old name - ElementGetInfo -def UIOSelector_Get_UIOInfo(inControlSpecificationArray): - #Подготовка входного массива - inControlSpecificationArray=UIOSelector_SearchUIONormalize_UIOSelector(inControlSpecificationArray) - #Выполнить идентификацию объектов, если передан массив - lResultList=[]; - if len(inControlSpecificationArray) > 0: - #Получить объект - lTempObject=UIOSelector_Get_UIO(inControlSpecificationArray) - #Получить инфо объект - lTempObjectInfo = lTempObject.element_info - #Добавить информацию об обнаруженом объекте - lResultList.append(UIOEI_Convert_UIOInfo(lTempObjectInfo)); +def UIOSelector_Get_UIOInfo(inUIOSelector): + #Check the bitness + lSafeOtherProcess = UIOSelector_SafeOtherGet_Process(inUIOSelector) + if lSafeOtherProcess is None: + #Подготовка входного массива + inUIOSelector=UIOSelector_SearchUIONormalize_UIOSelector(inUIOSelector) + #Выполнить идентификацию объектов, если передан массив + lResultList=[]; + if len(inUIOSelector) > 0: + #Получить объект + lTempObject=UIOSelector_Get_UIO(inUIOSelector) + #Получить инфо объект + lTempObjectInfo = lTempObject.element_info + #Добавить информацию об обнаруженом объекте + lResultList.append(UIOEI_Convert_UIOInfo(lTempObjectInfo)) + else: + # Run function from other process with help of PIPE + lPIPEResuestDict = {"ModuleName": "UIDesktop", "ActivityName": "UIOSelector_Get_UIOInfo", + "ArgumentList": [inUIOSelector], + "ArgumentDict": {}} + # Отправить запрос в дочерний процесс, который отвечает за работу с Windows окнами + ProcessCommunicator.ProcessChildSendObject(lSafeOtherProcess, lPIPEResuestDict) + # Get answer from child process + lPIPEResponseDict = ProcessCommunicator.ProcessChildReadWaitObject(lSafeOtherProcess) + if lPIPEResponseDict["ErrorFlag"]: + raise Exception( + f"Exception was occured in child process (message): {lPIPEResponseDict['ErrorMessage']}, (traceback): {lPIPEResponseDict['ErrorTraceback']}") + else: + lResultList = lPIPEResponseDict["Result"] return lResultList - - #################################################################################################### #Search child UIO by the: Parent UIO, X, Y #inHierarchyList: [{"index":<>,"element":<>}] - technical argument for internal purpose @@ -812,35 +932,53 @@ def UIOXY_SearchChild_ListDict(inRootElement,inX,inY,inHierarchyList=[]): ################################################################################################### #Get list of child UIO's by Parent UIOSelector +#!!!!!Safe call is included (you can set activity and UIDesktop will choose the bitness and return the result)!!!!! #inControlSpecificationArray- UIOSelector #old name - ElementGetChildElementList -def UIOSelector_GetChildList_UIOList(inControlSpecificationArray=[],inBackend=mDefaultPywinautoBackend): - #Подготовка входного массива - inControlSpecificationArray=UIOSelector_SearchUIONormalize_UIOSelector(inControlSpecificationArray) - #Выполнить идентификацию объектов, если передан массив - lResultList=[]; - #ctypes.windll.user32.MessageBoxW(0, str(inControlSpecificationArray), "Your title", 1) - if len(inControlSpecificationArray) > 0: - #Получить объект - lTempObject = UIOSelector_Get_UIO(inControlSpecificationArray) - #Получить список дочерних объектов - lTempChildList = lTempObject.children() - lIterator=0 - #Подготовить результирующий объект - for lChild in lTempChildList: - lTempObjectInfo=lChild.element_info - #Добавить информацию об обнаруженом объекте - lObjectInfoItem=UIOEI_Convert_UIOInfo(lTempObjectInfo) - #Итератор внутри объекта (для точной идентификации) - lObjectInfoItem['ctrl_index']=lIterator; - lResultList.append(lObjectInfoItem); - #Инкремент счетчика - lIterator=lIterator+1 +def UIOSelector_GetChildList_UIOList(inUIOSelector=[], inBackend=mDefaultPywinautoBackend): + #Check the bitness + lSafeOtherProcess = UIOSelector_SafeOtherGet_Process(inUIOSelector) + if lSafeOtherProcess is None: + #Подготовка входного массива + inUIOSelector=UIOSelector_SearchUIONormalize_UIOSelector(inUIOSelector) + #Выполнить идентификацию объектов, если передан массив + lResultList=[]; + #ctypes.windll.user32.MessageBoxW(0, str(inControlSpecificationArray), "Your title", 1) + if len(inUIOSelector) > 0: + #Получить объект + lTempObject = UIOSelector_Get_UIO(inUIOSelector) + #Получить список дочерних объектов + lTempChildList = lTempObject.children() + lIterator=0 + #Подготовить результирующий объект + for lChild in lTempChildList: + lTempObjectInfo=lChild.element_info + #Добавить информацию об обнаруженом объекте + lObjectInfoItem=UIOEI_Convert_UIOInfo(lTempObjectInfo) + #Итератор внутри объекта (для точной идентификации) + lObjectInfoItem['ctrl_index']=lIterator; + lResultList.append(lObjectInfoItem); + #Инкремент счетчика + lIterator=lIterator+1 + else: + lResultList=BackendStr_GetTopLevelList_UIOInfo(inBackend) + #Установка бэк-енда на первый элемент + for lItem in lResultList: + lItem["backend"]=inBackend else: - lResultList=BackendStr_GetTopLevelList_UIOInfo(inBackend) - #Установка бэк-енда на первый элемент - for lItem in lResultList: - lItem["backend"]=inBackend + # Run function from other process with help of PIPE + lPIPEResuestDict = {"ModuleName": "UIDesktop", "ActivityName": "UIOSelector_GetChildList_UIOList", + "ArgumentList": [inUIOSelector, inBackend], + "ArgumentDict": {}} + # Отправить запрос в дочерний процесс, который отвечает за работу с Windows окнами + ProcessCommunicator.ProcessChildSendObject(lSafeOtherProcess, lPIPEResuestDict) + # Get answer from child process + lPIPEResponseDict = ProcessCommunicator.ProcessChildReadWaitObject(lSafeOtherProcess) + if lPIPEResponseDict["ErrorFlag"]: + raise Exception( + f"Exception was occured in child process (message): {lPIPEResponseDict['ErrorMessage']}, (traceback): {lPIPEResponseDict['ErrorTraceback']}") + else: + lResultList = lPIPEResponseDict["Result"] return lResultList #################################################################################################### @@ -1080,17 +1218,53 @@ def BackendStr_GetTopLevelList_UIOInfo(inBackend=mDefaultPywinautoBackend): ################################################################################################### #Highlight the UI object +#!!!!!Safe call is included (you can set activity and UIDesktop will choose the bitness and return the result)!!!!! #old name - ElementDrawOutlineNew -def UIOSelector_Highlight(inSpecificationArray): - UIO_Highlight(UIOSelector_Get_UIO(inSpecificationArray)) - return +def UIOSelector_Highlight(inUIOSelector): + #Check the bitness + lSafeOtherProcess = UIOSelector_SafeOtherGet_Process(inUIOSelector) + if lSafeOtherProcess is None: + UIO_Highlight(UIOSelector_Get_UIO(inUIOSelector)) + else: + # Run function from other process with help of PIPE + lPIPEResuestDict = {"ModuleName": "UIDesktop", "ActivityName": "UIOSelector_Exist_Bool", + "ArgumentList": [inUIOSelector], + "ArgumentDict": {}} + # Отправить запрос в дочерний процесс, который отвечает за работу с Windows окнами + ProcessCommunicator.ProcessChildSendObject(lSafeOtherProcess, lPIPEResuestDict) + # Get answer from child process + lPIPEResponseDict = ProcessCommunicator.ProcessChildReadWaitObject(lSafeOtherProcess) + if lPIPEResponseDict["ErrorFlag"]: + raise Exception( + f"Exception was occured in child process (message): {lPIPEResponseDict['ErrorMessage']}, (traceback): {lPIPEResponseDict['ErrorTraceback']}") + else: + return lPIPEResponseDict["Result"] + return True ################################################################################################### #inSpecificationArray - UIOSelector +#!!!!!Safe call is included (you can set activity and UIDesktop will choose the bitness and return the result)!!!!! #old name - ElementDrawOutlineNewFocus -def UIOSelector_FocusHighlight(inSpecificationArray): - UIO_FocusHighlight(UIOSelector_Get_UIO(inSpecificationArray)) - return +def UIOSelector_FocusHighlight(inUIOSelector): + #Check the bitness + lSafeOtherProcess = UIOSelector_SafeOtherGet_Process(inUIOSelector) + if lSafeOtherProcess is None: + UIO_FocusHighlight(UIOSelector_Get_UIO(inUIOSelector)) + else: + # Run function from other process with help of PIPE + lPIPEResuestDict = {"ModuleName": "UIDesktop", "ActivityName": "UIOSelector_Exist_Bool", + "ArgumentList": [inUIOSelector], + "ArgumentDict": {}} + # Отправить запрос в дочерний процесс, который отвечает за работу с Windows окнами + ProcessCommunicator.ProcessChildSendObject(lSafeOtherProcess, lPIPEResuestDict) + # Get answer from child process + lPIPEResponseDict = ProcessCommunicator.ProcessChildReadWaitObject(lSafeOtherProcess) + if lPIPEResponseDict["ErrorFlag"]: + raise Exception( + f"Exception was occured in child process (message): {lPIPEResponseDict['ErrorMessage']}, (traceback): {lPIPEResponseDict['ErrorTraceback']}") + else: + return lPIPEResponseDict["Result"] + return True ################################################################################################### #old name - draw_outline_new diff --git a/Sources/pyOpenRPA/Robot/Utils/ProcessBitness.py b/Sources/pyOpenRPA/Robot/Utils/ProcessBitness.py new file mode 100644 index 00000000..6ffd4a99 --- /dev/null +++ b/Sources/pyOpenRPA/Robot/Utils/ProcessBitness.py @@ -0,0 +1,85 @@ +import pywinauto #Is needed to detect OS bitness +import struct # Need to detect Current process bitness +import subprocess #Need to create subprocess +import os # Is needed to check file/folder path +import shutil #os operations +import pdb +############################################ +####Module, which control the Bitness between 32 and 64 python (needed for pywinauto framework to work correctly) +############################################ +global mSettingsDict +mSettingsDict = { + "BitnessProcessCurrent": "64", # "64" or "32" + "BitnessOS": "64", # "64" or "32" + "Python32FullPath": None, #Set from user: "..\\Resources\\WPy32-3720\\python-3.7.2\\OpenRPARobotGUIx32.exe" + "Python64FullPath": None, #Set from user + "Python32ProcessName": "OpenRPAUIDesktopX32.exe", #Config set once + "Python64ProcessName": "OpenRPAUIDesktopX64.exe", #Config set once + "Python32Process":None, + "Python64Process":None, + "PythonArgs":["-m","pyOpenRPA.Robot"] #Start other bitness openRPA process with PIPE channel +} +#Init the global configuration +def SettingsInit(inSettingsDict): + if inSettingsDict: + global mSettingsDict + #Update values in settings from input + mSettingsDict.update(inSettingsDict) + #mSettingsDict = inSettingsDict + #################### + #Detect OS bitness + ####BitnessOS####### + lBitnessOS="32"; + if pywinauto.sysinfo.is_x64_OS(): + lBitnessOS="64"; + inSettingsDict["BitnessOS"]=lBitnessOS + #################### + #Detect current process bitness + ####BitnessProcessCurrent####### + lBitnessProcessCurrent = str(struct.calcsize("P") * 8) + inSettingsDict["BitnessProcessCurrent"]=lBitnessProcessCurrent + ##################################### + #Create the other bitness process if OS is 64 and we have another Python path + ########################################################################## + #Check if OS is x64, else no 64 is applicable + if mSettingsDict["BitnessOS"]=="64": + #Check if current bitness is 64 + if mSettingsDict["BitnessProcessCurrent"]=="64": + #create x32 if Python 32 path is exists + if mSettingsDict["Python32FullPath"] and mSettingsDict["Python32ProcessName"]: + #Calculate python.exe folder path + lPython32FolderPath= "\\".join(mSettingsDict["Python32FullPath"].split("\\")[:-1]) + lPython32NewNamePath = f"{lPython32FolderPath}\\{mSettingsDict['Python32ProcessName']}" + if not os.path.isfile(lPython32NewNamePath): + shutil.copyfile(mSettingsDict["Python32FullPath"],lPython32NewNamePath) + #pdb.set_trace() + mSettingsDict["Python32Process"] = subprocess.Popen([lPython32NewNamePath] + mSettingsDict["PythonArgs"],stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE) + else: + #bitness current process is 32 + #return x64 if it is exists + if mSettingsDict["Python64Process"]: + #Calculate python.exe folder path + lPython64FolderPath= "\\".join(mSettingsDict["Python64FullPath"].split("\\")[:-1]) + lPython64NewNamePath = f"{lPython64FolderPath}\\{mSettingsDict['Python64ProcessName']}" + if not os.path.isfile(lPython64NewNamePath): + shutil.copyfile(mSettingsDict["Python64FullPath"],lPython64NewNamePath) + mSettingsDict["Python64Process"] = subprocess.Popen([lPython64NewNamePath] + mSettingsDict["PythonArgs"],stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE) +#Return the other module bitnes +def OtherProcessGet(): + #Result template + lResult = None + global mSettingsDict + #Check if OS is x64, else no 64 is applicable + if mSettingsDict["BitnessOS"]=="64": + #Check if current bitness is 64 + if mSettingsDict["BitnessProcessCurrent"]=="64": + #return x32 if it is exists + if mSettingsDict["Python32Process"]: + lResult = mSettingsDict["Python32Process"] + else: + #bitness current process is 32 + #return x64 if it is exists + if mSettingsDict["Python64Process"]: + lResult = mSettingsDict["Python64Process"] + #Exit + return lResult \ No newline at end of file diff --git a/Sources/pyOpenRPA/Robot/Utils/__init__.py b/Sources/pyOpenRPA/Robot/Utils/__init__.py new file mode 100644 index 00000000..069ade8c --- /dev/null +++ b/Sources/pyOpenRPA/Robot/Utils/__init__.py @@ -0,0 +1 @@ +from . import ProcessBitness \ No newline at end of file diff --git a/Sources/pyOpenRPA/Robot/__main__.py b/Sources/pyOpenRPA/Robot/__main__.py index 286d12e6..6ccc71bd 100644 --- a/Sources/pyOpenRPA/Robot/__main__.py +++ b/Sources/pyOpenRPA/Robot/__main__.py @@ -28,6 +28,7 @@ while True: lFunction = getattr(UIDesktop,lJSONInput['ActivityName']) lProcessResponse["Result"]=JSONNormalize.JSONNormalizeDictListStrIntBool(lFunction(*lJSONInput['ArgumentList'],**lJSONInput['ArgumentDict'])) except Exception as e: + lProcessResponse["Result"] = None #Установить флаг ошибки lProcessResponse["ErrorFlag"]=True #Зафиксировать traceback diff --git a/changelog.md b/changelog.md index 5467f8a3..22cc8da2 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,24 @@ Beta before 1.0.1 (new way of OpenRPA with improovments. Sorry, but no backward compatibility)/ Backward compatibility will start from 1.0.1 +[1.0.25] +*Dont upload to PyPi* - Not tested +Created safe call function in UIDesktop +UIOSelector_SafeOtherGet_Process + +Safe call in UIDesktop for: +- UIOSelectorUIOActivity_Run_Dict +- UIOSelector_Exist_Bool +- UIOSelector_Highlight +- UIOSelector_FocusHighlight +- UIOSelector_SearchChildByMouse_UIOTree +- UIOSelector_GetChildList_UIOList +- UIOSelector_Get_UIOInfoList +- UIOSelector_Get_UIOInfo +- UIOSelector_Get_UIOActivityList + +UIOSelectorSecs_WaitAppear_Bool +UIOSelectorSecs_WaitDisappear_Bool +UIOSelectorsSecs_WaitAppear_List +UIOSelectorsSecs_WaitDisappear_List [1.0.24] 1.0.1 Beta Refactoring (Studio Orchestrator in pyOpenRPA package) diff --git a/v1.0.24 b/v1.0.25 similarity index 100% rename from v1.0.24 rename to v1.0.25