From e36bc9c39ea5aa2ce8ad8b3ae91f7e6639523787 Mon Sep 17 00:00:00 2001 From: Quintijn Hoogenboom Date: Wed, 21 Feb 2024 12:22:02 +0100 Subject: [PATCH] Getwindowinfo branch (#12) * revisiting autohotkeyactions.py, beginning to work again... * extenvvars.py tidied up, the "remember" functionality across calls removed, now all directory settings in natlinkstatus are found with %natlinkdir% or even %natlink% as directory content. also %unimacrouser% finds the %unimacrouserdirectory% setting (both work) * bring unimacroactions.py in clipboard functions under try finally for CloseClipboard() (Issue 11, which also blocked the clipboard at times) * small improvement to inivars, giving less errors... * edit some documentation in sendkeys.py --- src/dtactions/autohotkeyactions.py | 35 +++- src/dtactions/sendkeys.py | 16 +- src/dtactions/unimacro/extenvvars.py | 231 ++++++------------------ src/dtactions/unimacro/inivars.py | 41 ++++- src/dtactions/unimacro/unimacroutils.py | 87 +++++---- 5 files changed, 174 insertions(+), 236 deletions(-) diff --git a/src/dtactions/autohotkeyactions.py b/src/dtactions/autohotkeyactions.py index 293faa2..1c86734 100644 --- a/src/dtactions/autohotkeyactions.py +++ b/src/dtactions/autohotkeyactions.py @@ -67,17 +67,28 @@ def do_ahk_script(script, filename=None): filename = filename or 'tempscript.ahk' if not ahk_is_active(): print('ahk is not active, cannot run script') - return + return 0 #print 'AHK with script: %s'% script + if script.strip().endswith('.ahk'): + script = script.strip() + scriptPath = ahkscriptfolder/script + if scriptPath.is_file(): + call_ahk_script_path(scriptPath) + return 1 + print(f'not a valid filepath for AHK script: "{str(scriptPath)}"') + return 0 + scriptPath = ahkscriptfolder/filename + if isinstance(script, (list, tuple)): script = '\n'.join(script) if not script.endswith('\n'): script += '\n' - with open(scriptPath, 'w') as fp: + with open(scriptPath, 'w', encoding='utf-8') as fp: fp.write(script) call_ahk_script_path(scriptPath) + return 1 def call_ahk_script_path(scriptPath): """call the specified ahk script @@ -96,7 +107,7 @@ def call_ahk_script_path(scriptPath): else: raise ValueError(f'autohotkeyactions, call_ahk_script_path: path should end with ".ahk", or ".exe"\n path: {scriptPath}') if result: - print('non-zero result of call_ahk_script_path "%s": %s'% (scriptPath, result)) + print(f'non-zero result of call_ahk_script_path "{scriptPath}": {result}') ProgInfo = collections.namedtuple('ProgInfo', 'progpath prog title toporchild classname hndle'.split(' ')) @@ -150,7 +161,7 @@ def getProgInfoScript(info_file): def getProgInfoResult(info_file): """extract the contents of the info_file, and return the progInfo """ - with open(info_file, 'r') as fp: + with open(info_file, 'r', encoding='utf-8') as fp: progInfo = fp.read().split('\n') # note ahk returns 5 lines, but ProgInfo has 6 items. @@ -289,7 +300,8 @@ def ahkBringup(app, filepath=None, title=None, extra=None, waitForStart=1): ## do the script!! do_ahk_script(script) - message = open(ErrorFile, 'r').read().strip() + with open(ErrorFile, 'r', encoding='utf-8') as fp: + message = fp.read().strip() if message: return message @@ -468,7 +480,7 @@ def GetForegroundWindow(): script = script.replace('##hndlefile##', str(HndleFile)) do_ahk_script(script, filename="getforegroundwindow.ahk") - with open(HndleFile, 'r') as fp: + with open(HndleFile, 'r', encoding='utf-8') as fp: gotHndle = fp.read().strip() try: if gotHndle: @@ -521,7 +533,8 @@ def SetForegroundWindow(hndle): mess = f'Error with SetForegroundWindow to {hndle}, InfoFile cannot be found' return mess - winHndle = open(ProgInfoFile, 'r').read().strip() + with open(ProgInfoFile, 'r', encoding='utf-8') as fp: + winHndle = fp.read().strip() if winHndle: try: winHndle = int(winHndle) @@ -613,7 +626,7 @@ def clearErrorMessagesFile(): return the path of the ErrorMessagesFile """ ErrorFile = ahkscriptfolder/"errormessagefromahk.txt" - with open(ErrorFile, 'w') as f: + with open(ErrorFile, 'w', encoding='utf-8') as f: f.write('\n') return ErrorFile @@ -621,7 +634,7 @@ def readErrorMessagesFile(): """get the error messages if any """ ErrorFile = ahkscriptfolder/"errormessagefromahk.txt" - with open(ErrorFile, 'r') as f: + with open(ErrorFile, 'r', encoding='utf-8') as f: mess = f.read() if mess.strip(): return f'autohotkeyactions.ahkBringup failed:\n===={mess}' @@ -724,7 +737,9 @@ def test(): Result = ahkBringup("notepad") print(f'\nresult of ahkBringup("notepad"):\n{repr(Result)}') if Result.hndle: - killWindow(Result.hndle) + killWindow(Result.hndle, silent=False) + Result = do_ahk_script('showmessageswindow.ahk') + pass diff --git a/src/dtactions/sendkeys.py b/src/dtactions/sendkeys.py index 501ceae..8ea4dc7 100644 --- a/src/dtactions/sendkeys.py +++ b/src/dtactions/sendkeys.py @@ -11,10 +11,7 @@ and then in a function: :code:`sendkeys("keystrokes")` - -This module now adopts the Dragonfly :code:`action_key` module, -so `"{alt+w}"` is (in the function) converted to `"a-w"` etc. - + Optionally, you can also use sendsystemkeys, which is implmented via Dragon SendSystemKeys (via natlink.execScript) (Quintijn Hoogenboom, 2021-04-04) @@ -28,6 +25,8 @@ def sendkeys(keys): :code:`"{shift+right 4}"` +This functions is similar to the Dragonfly `sendkeys` function, but now works via the Vocola Keys extension. + Tested at bottom of this file interactively... """ ext_keys.send_input(keys) @@ -47,8 +46,13 @@ def sendsystemkeys(keys): # sendkeys("{a 3}") #aaa # sendkeys("x y z ") # sendkeys("test, test, met komma.{home}") - sendkeys("{ctrl+end}{up 2}{home}{shift+end}{del}this is wrong{shift+left 5}right") + # try: + # natlink.natConnect() + # sendsystemkeys("a{win+e}b") + # finally: + # natlink.natDisconnect() + + # sendkeys("{ctrl+end}{up 2}{home}{shift+end}{del}this is wrong{shift+left 5}right") # ## """ - """ diff --git a/src/dtactions/unimacro/extenvvars.py b/src/dtactions/unimacro/extenvvars.py index 69b7ef2..ca43849 100644 --- a/src/dtactions/unimacro/extenvvars.py +++ b/src/dtactions/unimacro/extenvvars.py @@ -15,31 +15,31 @@ """ import os import re +import copy from win32com.shell import shell, shellcon -from natlinkcore import natlinkstatus -status = natlinkstatus.NatlinkStatus() +try: + import natlink + natlinkAvailable = True +except ImportError: + natlinkAvailable = False + +if natlinkAvailable: + if not natlink.isNatSpeakRunning(): + natlinkAvailable = False + +if natlinkAvailable: + from natlinkcore import natlinkstatus + status = natlinkstatus.NatlinkStatus() + status_dict_full = copy.copy(status.getNatlinkStatusDict()) + + status_dict = {key.upper():value for (key, value) in status_dict_full.items() if os.path.isdir(value)} + del status_dict_full +else: + status = None + status_dict = None # for extended environment variables: reEnv = re.compile('(%[A-Z_]+%)', re.I) -# keep track of found env variables, fill, if you wish, with -# getAllFolderEnvironmentVariables. -# substitute back with substituteEnvVariableAtStart. -# and substite forward with expandEnvVariableAtStart -# in all cases a private envDict can be user, or the global dict recentEnv -# -# to collect all env variables, call getAllFolderEnvironmentVariables, see below -recentEnv = {} - -def addToRecentEnv(name, value): - """to be filled for NATLINK variables from natlinkstatus - """ - recentEnv[name] = value - -def deleteFromRecentEnv(name): - """to possibly delete from recentEnv, from natlinkstatus - """ - if name in recentEnv: - del recentEnv[name] def getFolderFromLibraryName(fName): """from windows library names extract the real folder @@ -139,7 +139,7 @@ def matchesStart(listOfDirs, checkDir, caseSensitive): -def getExtendedEnv(var, envDict=None, displayMessage=1): +def getExtendedEnv(var): """get from environ or windows CSLID HOME is environ['HOME'] or CSLID_PERSONAL @@ -152,33 +152,20 @@ def getExtendedEnv(var, envDict=None, displayMessage=1): Note: these settings are case sensitive! You can leave out Dir or Directory. - As envDict for recent results either a private (passed in) dict is taken, or - the global recentEnv. - - This is merely for "caching results" - """ - if envDict is None: - myEnvDict = recentEnv - else: - myEnvDict = envDict ## var = var.strip() - var = var.strip("% ") - - result = getDirectoryFromNatlinkstatus(var) - if result: - return result - var = var.upper() + var = var.strip("% ").upper() + if status_dict: + result = getDirectoryFromNatlinkstatus(var) + if result: + return result + if var == "~": var = 'HOME' - if var in myEnvDict: - return myEnvDict[var] - if var in os.environ: - myEnvDict[var] = os.environ[var] - return myEnvDict[var] + return os.environ[var] if var == 'DROPBOX': result = getDropboxFolder() @@ -208,138 +195,53 @@ def getExtendedEnv(var, envDict=None, displayMessage=1): if shellnumber < 0: # on some systems have SYSTEMROOT instead of SYSTEM: if var == 'SYSTEM': - return getExtendedEnv('SYSTEMROOT', envDict=envDict) + return getExtendedEnv('SYSTEMROOT') return '' # raise ValueError('getExtendedEnv, cannot find in environ or CSIDL: "%s"'% var2) try: result = shell.SHGetFolderPath (0, shellnumber, 0, 0) except: - if displayMessage: - print('getExtendedEnv, cannot find in environ or CSIDL: "%s"'% var2) return '' result = str(result) result = os.path.normpath(result) - myEnvDict[var] = result + if result and os.path.isdir(result): # on some systems apparently: - if var == 'SYSTEMROOT': - - myEnvDict['SYSTEM'] = result - return result - + return result + if result: + print(f'getExtendedEnv: no valid path found for "{var}": "{result}"') + else: + print(f'getExtendedEnv: no path found for "{var}"') + return None + def getDirectoryFromNatlinkstatus(envvar): """see if directory can can be retrieved from envvar """ + # if natlink not available: + if not natlinkAvailable: + # print(f'natlink not available for get "{envvar}"') + return None + # try if function in natlinkstatus: - for extra in ('', 'Directory', 'Dir'): + if not status_dict: + return None + + for extra in ('', 'DIRECTORY', 'DIR'): var2 = envvar + extra - if var2 in status.__dict__: - funcName = f'get{var2}' - func = getattr(status, funcName) - result = func() - if result: - return result + result = status_dict.get(var2, "") + if result: + return result return None - - - -def clearRecentEnv(): - """for testing, clears above global dictionary - """ - recentEnv.clear() - -def getAllFolderEnvironmentVariables(fillRecentEnv=None): - """return, as a dict, all the environ AND all CSLID variables that result into a folder - - Now also implemented: Also include NATLINK, UNIMACRO, VOICECODE, DRAGONFLY, VOCOLAUSERDIR, UNIMACROUSERDIR - This is done by calling from natlinkstatus, see there and example in natlinkmain. - - Optionally put them in recentEnv, if you specify fillRecentEnv to 1 (True) - - """ - #pylint:disable=W0603 - D = {} - - for k in dir(shellcon): - if k.startswith("CSIDL_"): - kStripped = k[6:] - try: - v = getExtendedEnv(kStripped, displayMessage=None) - except ValueError: - continue - if len(v) > 2 and os.path.isdir(v): - D[kStripped] = v - elif v == '.': - D[kStripped] = os.getcwd - # os.environ overrules CSIDL: - for k in os.environ: - v = os.environ[k] - if os.path.isdir(v): - v = os.path.normpath(v) - if k in D and D[k] != v: - print('warning, CSIDL also exists for key: %s, take os.environ value: %s'% (k, v)) - D[k] = v - if isinstance(fillRecentEnv, dict): - recentEnv.update(D) - return D - -#def setInRecentEnv(key, value): -# if key in recentEnv: -# if recentEnv[key] == value: -# print 'already set (the same): %s, %s'% (key, value) -# else: -# print 'already set (but different): %s, %s'% (key, value) -# return -# print 'setting in recentEnv: %s to %s'% (key, value) -# recentEnv[key] = value - -def substituteEnvVariableAtStart(filepath, envDict=None): - r"""try to substitute back one of the (preused) environment variables back - - into the start of a filename - - if ~ (HOME) is D:\My documents, - the path "D:\My documents\folder\file.txt" should return "~\folder\file.txt" - - pass in a dict of possible environment variables, which can be taken from recent calls, or - from envDict = getAllFolderEnvironmentVariables(). - - Alternatively you can call getAllFolderEnvironmentVariables once, and use the recentEnv - of this module! getAllFolderEnvironmentVariables(fillRecentEnv) - - If you do not pass such a dict, recentEnv is taken, but this recentEnv holds only what has been - asked for in the session, so no complete list! - - """ - if envDict is None: - envDict = recentEnv - Keys = list(envDict.keys()) - # sort, longest result first, shortest keyname second: - decorated = [(-len(envDict[k]), len(k), k) for k in Keys] - decorated.sort() - Keys = [k for (dummy1,dummy2, k) in decorated] - for k in Keys: - val = envDict[k] - if filepath.lower().startswith(val.lower()): - if k in ("HOME", "PERSONAL"): - k = "~" - else: - k = "%" + k + "%" - filepart = filepath[len(val):] - filepart = filepart.strip('/\\ ') - return os.path.join(k, filepart) - # no hit, return original: - return filepath -def expandEnvVariableAtStart(filepath, envDict=None): +def expandEnvVariableAtStart(filepath): """try to substitute environment variable into a path name """ filepath = filepath.strip() if filepath.startswith('~'): - folderpart = getExtendedEnv('~', envDict) + folderpart = getExtendedEnv('~') filepart = filepath[1:] filepart = filepart.strip('/\\ ') return os.path.normpath(os.path.join(folderpart, filepart)) @@ -347,7 +249,7 @@ def expandEnvVariableAtStart(filepath, envDict=None): envVar = reEnv.match(filepath).group(1) # get the envVar... try: - folderpart = getExtendedEnv(envVar, envDict) + folderpart = getExtendedEnv(envVar) except ValueError: print('invalid (extended) environment variable: %s'% envVar) else: @@ -358,7 +260,7 @@ def expandEnvVariableAtStart(filepath, envDict=None): # no match return filepath -def expandEnvVariables(filepath, envDict=None): +def expandEnvVariables(filepath): """try to substitute environment variable into a path name, ~ only at the start, @@ -369,7 +271,7 @@ def expandEnvVariables(filepath, envDict=None): filepath = filepath.strip() if filepath.startswith('~'): - folderpart = getExtendedEnv('~', envDict) + folderpart = getExtendedEnv('~') filepart = filepath[1:] filepart = filepart.strip('/\\ ') filepath = os.path.normpath(os.path.join(folderpart, filepart)) @@ -383,7 +285,7 @@ def expandEnvVariables(filepath, envDict=None): continue if part == "~" or (part.startswith("%") and part.endswith("%")): try: - folderpart = getExtendedEnv(part, envDict) + folderpart = getExtendedEnv(part) except ValueError: folderpart = part List2.append(folderpart) @@ -394,40 +296,23 @@ def expandEnvVariables(filepath, envDict=None): # no match return filepath -def printAllEnvVariables(): - for k in sorted(recentEnv.keys()): - print("%s\t%s"% (k, recentEnv[k])) if __name__ == "__main__": - Vars = getAllFolderEnvironmentVariables() - for kk in sorted(Vars): - print('%s: %s'% (kk, Vars[kk])) - if not os.path.isdir(Vars[kk]): - print('----- not a directory: %s (%s)'% (Vars[kk], kk)) print('testing expandEnvVariableAtStart') print('also see expandEnvVar in natlinkstatus!!') - for p in ("D:\\natlink\\unimacro", "~/unimacroqh", + for p in ("~", "%home%", "D:\\natlink\\unimacro", "~/unimacroqh", "%HOME%/personal", "%WINDOWS%\\folder\\strange testfolder"): expanded = expandEnvVariableAtStart(p) print('expandEnvVariablesAtStart: %s: %s'% (p, expanded)) print('testing expandEnvVariables') - for p in ("%DROPBOX%/QuintijnHerold/jachthutten", "D:\\%username%", "%NATLINK%\\unimacro", "%UNIMACROUSER%", + for p in ("%NATLINK%\\unimacro", "%DROPBOX%/QuintijnHerold/jachthutten", "D:\\%username%", "%UNIMACROUSER%", "%HOME%/personal", "%HOME%", "%personal%" "%WINDOWS%\\folder\\strange testfolder"): expanded = expandEnvVariables(p) print('expandEnvVariables: %s: %s'% (p, expanded)) - # testIniSection = NatlinkstatusInifileSection() - # print testIniSection.keys() - # testIniSection.set("test", "een test") - # testval = testIniSection.get("test") - # print 'testval: %s'% testval - # testIniSection.delete("test") - # testval = testIniSection.get("test") - # print 'testval: %s'% testval - print('recentEnv: %s'% len(recentEnv)) np = getExtendedEnv("NOTEPAD") print(np) for lName in ['Snelle toegang', 'Quick access', 'Documenten', 'Documents', 'Muziek', 'Afbeeldingen', 'Dropbox', 'OneDrive', 'Desktop', 'Bureaublad']: diff --git a/src/dtactions/unimacro/inivars.py b/src/dtactions/unimacro/inivars.py index f44f850..d352c97 100644 --- a/src/dtactions/unimacro/inivars.py +++ b/src/dtactions/unimacro/inivars.py @@ -207,6 +207,16 @@ def getIniList(t, sep=(";", "\n")): the end of the string. Raises error if anything else than a space his met + # edge cases: + + >>> list(getIniList("''''''")) + ['abc'] + >>> list(getIniList("'")) + ["'"] + + # single item, single quotes + >>> list(getIniList('a')) + ['a'] >>> list(getIniList("a; c")) ['a', 'c'] >>> list(getIniList("a;c")) @@ -229,6 +239,9 @@ def getIniList(t, sep=(";", "\n")): l = len(t) state = 0 hadQuote = '' + if l == 1: + yield t + return ## print '---------------------------length: %s'% l for j in range(l): c = t[j] @@ -1436,6 +1449,8 @@ def getInt(self, section, key, default=0): ... except: pass >>> ini = IniVars('getint.ini') >>> ini.set('s', 'three', 3) + >>> ini.set('s', 'booltrue', 'T') + >>> ini.set('s', 'boolfalse', 'F') >>> ini.getInt('s', 'three') 3 >>> ini.getInt('s', 'unknown') @@ -1443,6 +1458,17 @@ def getInt(self, section, key, default=0): >>> ini.getInt('s', 'unknowndefault', 11) 11 + + ##Edge cases: if t T f F (bool)return 1 or 0 + ## two error cases for less errors in practice... + >>> ini.getInt('s', 'booltrue') + ini method getInt, got "T", return 1 + 1 + >>> ini.getInt('s', 'boolfalse') + ini method getInt, got "F", return 0 + 0 + + """ try: i = self[section][key] @@ -1456,6 +1482,12 @@ def getInt(self, section, key, default=0): try: return int(i) except ValueError as exc: + if i in ('t', 'T'): + print('ini method getInt, got "%s", return 1'% i) + return 1 + if i in ('f', 'F'): + print('ini method getInt, got "%s", return 0'% i) + return 0 raise IniError('ini method getInt, value not a valid integer: %s (section: %s, key: %s)'% (section, key, i)) from exc else: return default @@ -1468,6 +1500,9 @@ def getBool(self, section, key, default=False): t, T, true, True, Waar, waar, w, W, 1 -->> True empty, o, f, F, False, false, Onwaar, o, none -->> False + Errors: return False + + """ try: i = self[section][key] @@ -1480,9 +1515,9 @@ def getBool(self, section, key, default=False): return True if i.lower()[0] in ['f', 'o', '0']: return False - raise IniError('inivars, getBool, unexpected value: "%s" (section: %s, key: %s)'% - (i, section, key)) - + print('inivars, getBool, unexpected value: "%s" (section: %s, key: %s), return False'% (i, section, key)) + return False + def getFloat(self, section, key, default=0.0): """get a value and convert into a float diff --git a/src/dtactions/unimacro/unimacroutils.py b/src/dtactions/unimacro/unimacroutils.py index 6331542..9747100 100644 --- a/src/dtactions/unimacro/unimacroutils.py +++ b/src/dtactions/unimacro/unimacroutils.py @@ -49,8 +49,7 @@ class UnimacroError(Exception): longWaitFactor = 10 # times 3 for visible wait shortWaitFactor = 0.3 # times 10 for long wait # times 0.3 for short wait -#debugMode 0 = not, -1 == dvcMode, 1 is light, 2 = normal, 3 = heavy -debugMode = 1 +debugMode = 0 #-1 pendingExecScripts = [] @@ -209,7 +208,7 @@ def getProgInfo(modInfo=None): prog always lowercase - title now with capital letters. + title now with mixed (lower and upper case) letters. toporchild 'top' or 'child', or '' if no valid window """ @@ -227,7 +226,7 @@ def getProgInfo(modInfo=None): ## assume desktop, no foreground window, treat as top... return ProgInfo("", "", "", "top", "", 0) progpath = modInfo[0] - prog = Path(modInfo[0].lower()).stem + prog = Path(modInfo[0].lower()).stem title = modInfo[1] if isTopWindow(modInfo[2]): toporchild = 'top' @@ -734,7 +733,7 @@ def doMouse(absorrel, screenorwindow, xpos, ypos, mouse='left', nClick=1, modifi btn = 0 nclick = 0 #nClick = nClick or nclick # take things from "mouse" if nClick not used - #print 'btn: %s, nClick: %s, current mouseState: %s'% (btn, nClick, mouseState) + print('btn: %s, nClick: %s, current mouseState: %s'% (btn, nClick, mouseState)) if onlyMove: print('onlyMove to %s, %x'% (xp, yp)) natlink.playEvents([(natlinkutils.wm_mousemove, xp, yp)]) @@ -812,17 +811,23 @@ def buttonClick(button='left', nclick=1, modifiers=None): """do a natspeak buttonclick, but release mouse first if necessary """ # make button numeric: - #if button in buttons: - # button = buttons[button] - #if button not in [1, 2, 4]: - # raise UnimacroError('buttonClick invalid button: %s'% button) - #if nclick not in [1,2]: - # raise UnimacroError('buttonClick invalid number of clicks: %s'% nclick) + buttons = {'left':1, 'right':2, 'middle':4} + if button in buttons: + button = buttons[button] + if button not in [1, 2, 4]: + raise UnimacroError('buttonClick invalid button: %s'% button) + if nclick not in [1,2]: + raise UnimacroError('buttonClick invalid number of clicks: %s'% nclick) if mouseState: releaseMouse() - natlinkutils.buttonClick(button, nclick, modifiers) - #natlink.execScript("ButtonClick %s,%s"%(button, nclick)) + # natlinkutils.buttonClick(button, nclick, modifiers) + if button == 1 and nclick == 1: + script = 'ButtonClick' + else: + script = f'ButtonClick {button},{nclick}' + print(f'buttonClick via execScript: "{script}"') + natlink.execScript(script) @@ -1425,23 +1430,13 @@ def deleteWordIfNecessary(w): if isInActiveVoc: natlink.deleteWord(w) -if DEBUG: - fOutName = 'c:\\DEBUG '+__name__+'.txt' - debugFile = open(fOutName, 'w', encoding='utf-8') - print('DEBUG uitvoer naar: %s'% fOutName) - def debugPrint(t): """print to debug file (is this working???) """ if not DEBUG: return - if isinstance(t, str): - debugFile.write(t) - else: - debugFile.write(repr(t)) - debugFile.write('\n') - debugFile.flush() - + print(t) + def GetForegroundWindow(): """return the handle of the current foreground window """ @@ -1688,20 +1683,22 @@ def restoreClipboard(): print('No "previousClipboardText" available, empty clipboard...') t = None return - for _i in range(10): - try: - win32clipboard.OpenClipboard() - break - except: - time.sleep(0.1) - continue - else: - print("could not restore clipboard") - return - win32clipboard.EmptyClipboard() - if t: - win32clipboard.SetClipboardData(format_unicode, t) - win32clipboard.CloseClipboard() + try: + for _i in range(10): + try: + win32clipboard.OpenClipboard() + break + except: + time.sleep(0.1) + continue + else: + print("could not restore clipboard") + return + win32clipboard.EmptyClipboard() + if t: + win32clipboard.SetClipboardData(format_unicode, t) + finally: + win32clipboard.CloseClipboard() def getClipboard(): """get clipboard through natlink, and strips off backslash r @@ -1736,11 +1733,13 @@ def setClipboard(t, format=1): format = win32con.CF_UNICODETEXT (13): as unicode """ - #pylint:disable=W0622 - win32clipboard.OpenClipboard() - win32clipboard.EmptyClipboard() - win32clipboard.SetClipboardData(format, t) - win32clipboard.CloseClipboard() + #pylint:disable=W0622 + try: + win32clipboard.OpenClipboard() + win32clipboard.EmptyClipboard() + win32clipboard.SetClipboardData(format, t) + finally: + win32clipboard.CloseClipboard() def checkLists(one, two): """returns to lists, only in first, only in second