├── .gitignore ├── AutoHotkey.dll ├── README.md ├── __init__.py ├── comproxy.py └── py4ahk.ahk /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | -------------------------------------------------------------------------------- /AutoHotkey.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thqby/pyahk/f3b9a12453e604414e17e1f1ba70aa3fed47a447/AutoHotkey.dll -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python and AutoHotkey V2 call each other, ctypes implementation 2 | 3 | ## The installation 4 | - [Download ZIP](https://github.com/thqby/pyahk/archive/refs/heads/main.zip) 5 | - Unzip to `Python folder\Lib\site-packages\pyahk` 6 | - Updating [latest AutoHotkey.dll file](https://github.com/thqby/AutoHotkey_H/releases/latest), choose between 32-bit or 64-bit dll depending on the version of python you have installed 7 | - If using in ahk, copy `py4ahk.ahk` into the lib folder of ahk 8 | 9 | 10 | ## Use AutoHotkey V2 in Python 11 | #### example1 Gui, Hotkey ... 12 | ```python 13 | from ctypes import c_wchar_p 14 | from pyahk import * 15 | 16 | AhkApi.initialize() # init ahk 17 | AhkApi.addScript(''' 18 | ; show tray icon 19 | A_IconHidden:=0 20 | f4::exitapp 21 | #HotIf WinActive('ahk_exe notepad.exe') 22 | ''') 23 | 24 | # import ahk's global vars, or AhkApi[varname] 25 | from pyahk import Gui, MsgBox, FileAppend, Array, Map, JSON, Hotkey, HotIf, WinActive 26 | 27 | def onbutton(ob, inf): 28 | v = ob.Gui['Edit1'].Value 29 | MsgBox(v) 30 | 31 | g = Gui() 32 | g.AddEdit('w320 h80', 'hello python') 33 | g.AddButton(None, 'press').OnEvent('Click', onbutton) 34 | g.show('NA') 35 | arr = Array(8585, 'ftgy', 85, Map(85,244,'fyg', 58)) 36 | FileAppend(JSON.stringify(arr), '*') 37 | 38 | def onf6(key): 39 | MsgBox(key) 40 | 41 | @WINFUNCTYPE(None, c_wchar_p) 42 | def onf7(key): 43 | MsgBox(key) 44 | 45 | # Take effect under this condition 46 | HotIf(lambda key: WinActive(g.hwnd)) 47 | Hotkey('F6', onf6) # use ComProxy callback 48 | # Use an existing conditional expression in ahk 49 | HotIf("WinActive('ahk_exe notepad.exe')") 50 | Hotkey('F7', onf7) # use CFuncType callback 51 | HotIf() 52 | 53 | # ahk's message pump, block until the AHK interpreter exits 54 | AhkApi.pumpMessages() 55 | ``` 56 | 57 | #### example2 ahk's VarRef 58 | ```python 59 | from pyahk import * 60 | 61 | AhkApi.initialize() # init ahk 62 | AhkApi.addScript(''' 63 | mul(&v) { 64 | v := v * v 65 | return v 66 | } 67 | ''') 68 | 69 | from pyahk import MouseGetPos, mul 70 | 71 | print(MouseGetPos(Var(), Var(), Var(), Var())) 72 | print(mul(InputVar(32)), mul(Var(32))) 73 | 74 | # exit AHK interpreter 75 | AhkApi.finalize() 76 | ``` 77 | 78 | ## Use Python in AutoHotkey V2 79 | 80 | ```ahk 81 | dllpath := '' ; set the path to your python.dll, the default is 'python39.dll' 82 | py := Python(dllpath) 83 | ; import py's lib 84 | ; stdout := py.__import__('sys').stdout 85 | py.exec('from sys import stdout') 86 | ahk_arr := [213,4,'das',423.24,'fdg'] 87 | ; ahk array to py list 88 | ; v2h.exe, IObject interface 89 | ; py.print(ahk_arr, l := py.list(ahk_arr)) ; [213, 4, 'das', 423.24, 'fdg'] 90 | 91 | ; v2l.exe, IDispatch interface 92 | l := py.list() 93 | for it in ahk_arr 94 | l.append(it) 95 | py.print(ahk_arr, l) ; [213, 4, 'das', 423.24, 'fdg'] 96 | 97 | py.stdout.flush() 98 | ; py list to ahk array 99 | arr := [l*] 100 | l := 0 101 | ``` 102 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | from .comproxy import * 2 | from ctypes import CDLL, POINTER, WINFUNCTYPE, _CFuncPtr, _FUNCFLAG_CDECL, Structure, Union, addressof, byref, c_bool, c_double, c_int, c_ubyte 3 | from ctypes import c_int64, c_size_t, c_uint, c_void_p, c_wchar_p, cast, create_unicode_buffer, pointer, py_object, wstring_at 4 | from threading import get_native_id, local 5 | 6 | __all__ = ['AhkApi', 'IAhkObject', 'WINFUNCTYPE', 'Var', 'InputVar'] 7 | 8 | class Var(Structure): 9 | _fields_ = (('Contents', c_int64), 10 | ('CharContents', c_wchar_p), 11 | ('ByteLength', c_size_t), 12 | ('ByteCapacity', c_size_t), 13 | ('HowAllocated', c_ubyte), 14 | ('Attrib', c_ubyte), 15 | ('Scope', c_ubyte), 16 | ('Type', c_ubyte), 17 | ('Name', c_wchar_p)) 18 | _out = True 19 | def __init__(self, val = None): 20 | self.Name = '' 21 | self.Type = 1 22 | self.Attrib = 2 23 | if val != None: 24 | _local.api.VarAssign(byref(self), byref(ExprTokenType(val))) 25 | 26 | def __del__(self): 27 | if not (self.Attrib & 2): 28 | self.free() 29 | 30 | def free(self): 31 | _local.api.VarFree(byref(self), 13) 32 | 33 | @property 34 | def value(self): 35 | _local.api.VarToToken(byref(self), byref(TTK)) 36 | v = TTK.value 37 | TTK.free() 38 | return v 39 | 40 | def InputVar(val): 41 | val = Var(val) 42 | val._out = False 43 | return val 44 | 45 | class ExprTokenType(Structure): 46 | class _(Union): 47 | class _(Structure): 48 | _fields_ = (('ptr', c_void_p), ('length', c_size_t)) 49 | _fields_ = (('int64', c_int64), ('double', c_double), ('_', _)) 50 | _anonymous_ = ('_',) 51 | _fields_ = (('_', _), ('symbol', c_int)) 52 | _anonymous_ = ('_',) 53 | 54 | def __init__(self, *val): 55 | if val: 56 | self.value = val[0] 57 | 58 | @property 59 | def value(self): 60 | symbol = self.symbol 61 | if symbol == 1: 62 | return self.int64 63 | elif symbol == 2: 64 | return self.double 65 | elif symbol == 0: 66 | return wstring_at(self.ptr) 67 | elif symbol == 5: 68 | obj = IAhkObject(self.ptr) 69 | obj.AddRef() 70 | if obj.Type() == 'ComObject': 71 | pdisp = IDispatch.from_address(self.ptr + PTR_SIZE * 2) 72 | pdisp._free = True 73 | try: 74 | pdisp.QueryInterface(IID_PYOBJECT) 75 | return py_object.from_address(pdisp.value + PTR_SIZE).value 76 | except: 77 | try: 78 | pdisp.QueryInterface(IID_AHKOBJECT) 79 | pdisp._free = False 80 | return IAhkObject(pdisp) 81 | except: 82 | pdisp.AddRef() 83 | return Dispatch(pdisp) 84 | return obj 85 | 86 | @value.setter 87 | def value(self, val): 88 | if isinstance(val, str): 89 | self._objcache = val = create_unicode_buffer(val) 90 | self.ptr = addressof(val) 91 | self.length = len(val) - 1 92 | self.symbol = 0 93 | else: 94 | self.length = 0 95 | if isinstance(val, int): 96 | self.int64 = val 97 | if self.int64 == val: 98 | self.symbol = 1 99 | else: self.value = str(val) 100 | elif isinstance(val, float): 101 | self.double = val 102 | self.symbol = 2 103 | elif val == None: 104 | self.symbol = 3 105 | elif isinstance(val, Var): 106 | self.ptr = addressof(val) 107 | self.symbol = 4 108 | self.length = 4 109 | else: 110 | self.ptr = self._objcache = AhkApi._wrap_pyobj(val) 111 | self.symbol = 5 112 | 113 | BUF1 = create_unicode_buffer(1) 114 | BUF256 = create_unicode_buffer(256) 115 | class ResultToken(ExprTokenType): 116 | _fields_ = (('buf', c_void_p), ('mem_to_free', c_void_p), ('func', c_void_p), ('result', c_int)) 117 | 118 | def __init__(self): 119 | self.value = '' 120 | self.result = 1 121 | self.buf = addressof(BUF256) 122 | 123 | def free(self): 124 | _local.api.ResultTokenFree(byref(self)) 125 | 126 | TTK = ResultToken() 127 | IID_AHKOBJECT = GUID("{619f7e25-6d89-4eb4-b2fb-18e7c73c0ea6}") 128 | class IAhkObject(IDispatch): 129 | class Property: 130 | __slots__ = ('obj', 'prop') 131 | def __init__(self, obj, prop): 132 | self.obj = obj 133 | self.prop = prop 134 | 135 | def __call__(self, *args): 136 | return self.obj.Call(self.prop, *args) 137 | 138 | def __getitem__(self, index): 139 | if isinstance(index, tuple): 140 | return self.obj.Invoke(0, self.props, *index) 141 | return self.obj.Invoke(0, self.prop, index) 142 | 143 | def __setitem__(self, index, value): 144 | if isinstance(index, tuple): 145 | return self.obj.Invoke(1, self.prop, value, *index) 146 | return self.obj.Invoke(1, self.prop, value, index) 147 | 148 | __Invoke = instancemethod(WINFUNCTYPE(c_int, POINTER(ResultToken), c_int, c_wchar_p, POINTER(ExprTokenType), POINTER(POINTER(ExprTokenType)), c_int)(7, 'Invoke', iid=IID_AHKOBJECT)) 149 | Type = instancemethod(WINFUNCTYPE(c_wchar_p)(8, 'Type', iid=IID_AHKOBJECT)) 150 | Property = instancemethod(Property) 151 | 152 | def Invoke(self, flags, name, *args, to_ptr=False): 153 | global _ahk_throwerr 154 | _ahk_throwerr = True 155 | result = ResultToken() 156 | this = ExprTokenType(self) 157 | l, params = len(args), None 158 | if l: 159 | params = (POINTER(ExprTokenType) * l)() 160 | for i, arg in enumerate(args): 161 | params[i] = pointer(ExprTokenType(arg)) 162 | 163 | rt = self.__Invoke(byref(result), flags, name, byref(this), params, l) 164 | if rt == 0 or rt == 8: 165 | if _ahk_throwerr != True: 166 | _ahk_throwerr = Exception(*_ahk_throwerr) 167 | raise _ahk_throwerr 168 | return None 169 | if rt == 4: 170 | return None 171 | _ahk_throwerr = None 172 | if to_ptr and result.symbol == 5: 173 | result.symbol = 1 174 | v = result.value 175 | result.free() 176 | return v 177 | 178 | def __init__(self, *args): 179 | if l := len(args): 180 | val = args[0] 181 | object.__setattr__(self, 'value', val if isinstance(val, int) else val.value) 182 | free = args[1] if l > 1 else True 183 | if free: 184 | object.__setattr__(self, '_free', _local.threadid) 185 | 186 | def __call__(self, *args): # call 187 | return self.Call(None, *args) 188 | 189 | def __getitem__(self, index): # __item.get 190 | if isinstance(index, tuple): 191 | return self.Invoke(0, None, *index) 192 | return self.Invoke(0, None, index) 193 | 194 | def __setitem__(self, index, value): # __item.set 195 | if isinstance(index, tuple): 196 | return self.Invoke(1, None, value, *index) 197 | return self.Invoke(1, None, value, index) 198 | 199 | def __getattr__(self, prop): 200 | flags = self.Invoke(2, 'HasProp', prop) 201 | if not flags: 202 | raise AttributeError 203 | elif flags == 1: 204 | return self.Invoke(0, prop) 205 | else: 206 | return self.Property(prop) 207 | 208 | def __setattr__(self, prop, value): 209 | self.Invoke(1, prop, value) 210 | 211 | def __iter__(self): 212 | r = self.Invoke(2, '__Enum', 1) 213 | if r == None: 214 | raise NotImplementedError 215 | object.__setattr__(r, '_free', 0) 216 | var = Var() 217 | class _Enum(IAhkObject): 218 | def __next__(self): 219 | if _local.api.CallEnumerator(self.value, pointer(pointer(ExprTokenType(var))), 1): 220 | return var.value 221 | var.free() 222 | raise StopIteration 223 | return _Enum(r.value) 224 | 225 | def Get(self, prop, *args): # prop.get 226 | return self.Invoke(0, prop, *args) 227 | 228 | def Set(self, prop, value, *args): # prop.set 229 | return self.Invoke(1, prop, value, *args) 230 | 231 | def Call(self, prop, *args): # prop.call 232 | ret = self.Invoke(2, prop, *args) 233 | outs = tuple(arg.value for arg in args if isinstance(arg, Var) and arg._out) 234 | if outs: 235 | return (ret,) + outs 236 | return ret 237 | 238 | _local = local() 239 | _ahkdll = None 240 | _ahk_throwerr = None 241 | def _ahk_onerr(thr, mode): 242 | global _ahk_throwerr 243 | if _ahk_throwerr != True: 244 | return 0 245 | if isinstance(thr, IAhkObject): 246 | _ahk_throwerr = (thr.Message, thr.What, thr.Extra) 247 | else: 248 | _ahk_throwerr = (thr,) 249 | return 1 250 | 251 | def _TTK_INIT(): 252 | TTK.ptr = addressof(BUF1) 253 | TTK.symbol = 0 254 | 255 | class AhkApi(c_void_p): 256 | @classmethod 257 | def initialize(cls, hmod = None): 258 | global _ahkdll, IID_AHKOBJECT 259 | if _ahkdll is None: 260 | dllpath = __file__ + '/../AutoHotkey.dll' 261 | if isinstance(hmod, str): 262 | dllpath, hmod = hmod, None 263 | _ahkdll = CDLL(dllpath, handle=hmod) 264 | _ahkdll.ahkGetApi.restype = AhkApi 265 | _ahkdll.addScript.restype = c_void_p 266 | _ahkdll.addScript.argtypes = (c_wchar_p, c_int, c_uint) 267 | _ahkdll.ahkExec.argtypes = (c_wchar_p, c_uint) 268 | try: 269 | IID_AHKOBJECT = GUID.from_address(cast(_ahkdll.IID_IObjectComCompatible, c_void_p).value) 270 | except: 271 | pass 272 | _local.api = _ahkdll.ahkGetApi(None) 273 | _local.threadid = get_native_id() 274 | if cls['A_AhkVersion'].startswith('2.0'): 275 | TTK.free = _TTK_INIT 276 | if hmod is None: 277 | cls['OnError'](_ahk_onerr) 278 | 279 | VarAssign = instancemethod(WINFUNCTYPE(c_bool, POINTER(Var), POINTER(ExprTokenType))(8, 'VarAssign')) 280 | VarToToken = instancemethod(WINFUNCTYPE(None, POINTER(Var), POINTER(ResultToken))(9, 'VarToToken')) 281 | VarFree = instancemethod(WINFUNCTYPE(None, POINTER(Var), c_int)(10, 'VarFree')) 282 | ResultTokenFree = instancemethod(WINFUNCTYPE(None, POINTER(ResultToken))(14, 'ResultTokenFree')) 283 | CallEnumerator = instancemethod(WINFUNCTYPE(c_bool, c_void_p, POINTER(POINTER(ExprTokenType)), c_int)(25, 'CallEnumerator')) 284 | 285 | @staticmethod 286 | @WINFUNCTYPE(c_int, c_wchar_p, c_int) 287 | def __onexit(reason, code): 288 | from gc import get_objects 289 | threadid = get_native_id() 290 | for obj in filter(lambda obj: isinstance(obj, IAhkObject) and obj._free == threadid, get_objects()): 291 | obj.Release() 292 | object.__setattr__(obj, '_free', 0) 293 | return 0 294 | __PumpMessages = instancemethod(WINFUNCTYPE(None)(45, 'PumpMessages')) 295 | @classmethod 296 | def pumpMessages(cls): 297 | if _ := cls['OnExit']: 298 | _(cls.__onexit) 299 | _local.api.__PumpMessages() 300 | 301 | @classmethod 302 | def addScript(cls, script) -> int: 303 | return _ahkdll.addScript(script, 1, _local.threadid) 304 | 305 | @classmethod 306 | def execLine(cls, pline) -> int: 307 | return _ahkdll.ahkExecuteLine(c_void_p.from_param(pline), 1, 1, _local.threadid) 308 | 309 | @classmethod 310 | def exec(cls, script) -> int: 311 | return _ahkdll.ahkExec(script, _local.threadid) 312 | 313 | __GetVar = instancemethod(WINFUNCTYPE(c_bool, c_wchar_p, POINTER(ResultToken))(19, 'GetVar')) 314 | def __class_getitem__(cls, varname): 315 | if _local.api.__GetVar(varname, byref(TTK)): 316 | v = TTK.value 317 | TTK.free() 318 | return v 319 | 320 | __SetVar = instancemethod(WINFUNCTYPE(c_bool, c_wchar_p, POINTER(ExprTokenType))(20, 'SetVar')) 321 | @classmethod 322 | def setVar(cls, varname, value): 323 | _local.api.__SetVar(varname, byref(ExprTokenType(value))) 324 | 325 | @classmethod 326 | def _wrap_cfunc(cls, cfunc): 327 | def gettp(tp): 328 | if tp is None: 329 | raise TypeError(tp) 330 | if tp == IAhkObject: 331 | return 'o' 332 | tp = tp._type_ 333 | r, t = '', tp.lower() 334 | y = {'h': 'h', 'l': 'i', 'i': 'i', 'f': 'f', 'd': 'd', 'q': 'i6', 'b': 'c', 'c': 'c', 'z': 'a', 'p': 't', '?': 'c', 'u': 'h'} 335 | if tp == 'Z': 336 | r = 'w' 337 | elif t == 'q': 338 | r = 'i6' 339 | elif (r := y.get(t, None)) is None: 340 | raise TypeError(tp) 341 | elif tp.isupper(): 342 | r = 'u' + r 343 | return r 344 | 345 | df = '' 346 | if cfunc._restype_: 347 | df = df + gettp(cfunc._restype_) 348 | df += '=' 349 | if cfunc._flags_ & _FUNCFLAG_CDECL: 350 | df += '=' 351 | if cfunc._argtypes_: 352 | for it in cfunc._argtypes_: 353 | df += gettp(it) 354 | return cls['DynaCall'](cast(cfunc, c_void_p).value, df) 355 | 356 | @classmethod 357 | def _wrap_pyobj(cls, obj): 358 | if isinstance(obj, IAhkObject): 359 | return obj 360 | if isinstance(obj, Dispatch): 361 | obj = obj._comobj_ 362 | elif not isinstance(obj, IDispatch): 363 | if isinstance(obj, _CFuncPtr): 364 | return cls._wrap_cfunc(obj) 365 | obj = ComProxy(obj).as_IDispatch() 366 | obj.AddRef() 367 | return IAhkObject(cls['ComObjFromPtr'].Invoke(2, None, obj.value, to_ptr=True)) 368 | 369 | @classmethod 370 | def finalize(cls): 371 | if exit := cls['ExitApp']: 372 | exit(); exit = None 373 | cls.pumpMessages() 374 | 375 | @classmethod 376 | def loadAllFuncs(cls): 377 | ''' 378 | docs: 379 | https://lexikos.github.io/v2/docs/AutoHotkey.htm 380 | https://hotkeyit.github.io/v2/docs/AutoHotkey.htm 381 | ''' 382 | ahkfuncs = {} 383 | allnames = ['Abs','ACos','Alias','ASin','ATan','BlockInput','CallbackCreate','CallbackFree','CaretGetPos','Cast','Ceil','Chr','Click','ClipWait','ComCall','ComObjActive','ComObjConnect','ComObjDll','ComObjFlags','ComObjFromPtr','ComObjGet','ComObjQuery','ComObjType','ComObjValue','ControlAddItem','ControlChooseIndex','ControlChooseString','ControlClick','ControlDeleteItem','ControlFindItem','ControlFocus','ControlGetChecked','ControlGetChoice','ControlGetClassNN','ControlGetEnabled','ControlGetExStyle','ControlGetFocus','ControlGetHwnd','ControlGetIndex','ControlGetItems','ControlGetPos','ControlGetStyle','ControlGetText','ControlGetVisible','ControlHide','ControlHideDropDown','ControlMove','ControlSend','ControlSendText','ControlSetChecked','ControlSetEnabled','ControlSetExStyle','ControlSetStyle','ControlSetText','ControlShow','ControlShowDropDown','CoordMode','Cos','Critical','CryptAES','DateAdd','DateDiff','DetectHiddenText','DetectHiddenWindows','DirCopy','DirCreate','DirDelete','DirExist','DirMove','DirSelect','DllCall','Download','DriveEject','DriveGetCapacity','DriveGetFileSystem','DriveGetLabel','DriveGetList','DriveGetSerial','DriveGetSpaceFree','DriveGetStatus','DriveGetStatusCD','DriveGetType','DriveLock','DriveRetract','DriveSetLabel','DriveUnlock','DynaCall','EditGetCurrentCol','EditGetCurrentLine','EditGetLine','EditGetLineCount','EditGetSelectedText','EditPaste','EnvGet','EnvSet','Exit','ExitApp','Exp','FileAppend','FileCopy','FileCreateShortcut','FileDelete','FileEncoding','FileExist','FileGetAttrib','FileGetShortcut','FileGetSize','FileGetTime','FileGetVersion','FileInstall','FileMove','FileOpen','FileRead','FileRecycle','FileRecycleEmpty','FileSelect','FileSetAttrib','FileSetTime','Floor','Format','FormatTime','GetKeyName','GetKeySC','GetKeyState','GetKeyVK','GetMethod','GetVar','GroupActivate','GroupAdd','GroupClose','GroupDeactivate','GuiCtrlFromHwnd','GuiFromHwnd','HasBase','HasMethod','HasProp','HotIf','HotIfWinActive','HotIfWinExist','HotIfWinNotActive','HotIfWinNotExist','Hotkey','Hotstring','IL_Add','IL_Create','IL_Destroy','ImageSearch','IniDelete','IniRead','IniWrite','InputBox','InstallKeybdHook','InstallMouseHook','InStr','IsAlnum','IsAlpha','IsDate','IsDigit','IsFloat','IsInteger','IsLabel','IsLower','IsNumber','IsObject','IsSet','IsSetRef','IsSpace','IsTime','IsUpper','IsXDigit','KeyHistory','KeyWait','ListLines','ListViewGetContent','Ln','LoadPicture','Log','LTrim','Max','MemoryCallEntryPoint','MemoryFindResource','MemoryFreeLibrary','MemoryGetProcAddress','MemoryLoadLibrary','MemoryLoadResource','MemoryLoadString','MemorySizeOfResource','MenuFromHandle','MenuSelect','Min','Mod','MonitorGet','MonitorGetCount','MonitorGetName','MonitorGetPrimary','MonitorGetWorkArea','MouseClick','MouseClickDrag','MouseGetPos','MouseMove','MsgBox','NumGet','NumPut','ObjAddRef','ObjBindMethod','ObjDump','ObjFromPtr','ObjFromPtrAddRef','ObjGetBase','ObjGetCapacity','ObjHasOwnProp','ObjLoad','ObjOwnPropCount','ObjOwnProps','ObjPtr','ObjPtrAddRef','ObjRelease','ObjSetBase','ObjSetCapacity','OnClipboardChange','OnError','OnExit','OnMessage','Ord','OutputDebug','Pause','Persistent','PixelGetColor','PixelSearch','PostMessage','ProcessClose','ProcessExist','ProcessSetPriority','ProcessWait','ProcessWaitClose','Random','RegDelete','RegDeleteKey','RegExMatch','RegExReplace','RegRead','RegWrite','Reload','ResourceLoadLibrary','Round','RTrim','Run','RunAs','RunWait','Send','SendEvent','SendInput','SendLevel','SendMessage','SendMode','SendPlay','SendRaw','SendText','SetCapsLockState','SetControlDelay','SetDefaultMouseSpeed','SetKeyDelay','SetMouseDelay','SetNumLockState','SetRegView','SetScrollLockState','SetStoreCapsLockMode','SetTimer','SetTitleMatchMode','SetWinDelay','SetWorkingDir','Shutdown','Sin','sizeof','Sleep','Sort','SoundBeep','SoundGetInterface','SoundGetMute','SoundGetName','SoundGetVolume','SoundPlay','SoundSetMute','SoundSetVolume','SplitPath','Sqrt','StatusBarGetText','StatusBarWait','StrCompare','StrGet','StrLen','StrLower','StrPtr','StrPut','StrReplace','StrSplit','StrTitle','StrUpper','SubStr','Suspend','Swap','SysGet','SysGetIPAddresses','Tan','Thread','ToolTip','TraySetIcon','TrayTip','Trim','Type','UArray','UMap','UnZip','UnZipBuffer','UnZipRawMemory','UObject','VarSetStrCapacity','VerCompare','WinActivate','WinActivateBottom','WinActive','WinClose','WinExist','WinGetClass','WinGetClientPos','WinGetControls','WinGetControlsHwnd','WinGetCount','WinGetExStyle','WinGetID','WinGetIDLast','WinGetList','WinGetMinMax','WinGetPID','WinGetPos','WinGetProcessName','WinGetProcessPath','WinGetStyle','WinGetText','WinGetTitle','WinGetTransColor','WinGetTransparent','WinHide','WinKill','WinMaximize','WinMinimize','WinMove','WinMoveBottom','WinMoveTop','WinRedraw','WinRestore','WinSetAlwaysOnTop','WinSetEnabled','WinSetExStyle','WinSetRegion','WinSetStyle','WinSetTitle','WinSetTransColor','WinSetTransparent','WinShow','WinWait','WinWaitActive','WinWaitClose','WinWaitNotActive','ZipAddBuffer','ZipAddFile','ZipAddFolder','ZipCloseBuffer','ZipCloseFile','ZipCreateBuffer','ZipCreateFile','ZipInfo','ZipOptions','ZipRawMemory',] 384 | for n in allnames: 385 | ahkfuncs[n] = cls[n] 386 | return ahkfuncs 387 | 388 | def VARIANT_value_getter(self, _VARIANT_value_getter = VARIANT.value.fget): 389 | if self.vt == 9: 390 | try: 391 | val = self.c_void_p 392 | IDispatch.QueryInterface(c_void_p(val), IID_AHKOBJECT) 393 | return IAhkObject(val) 394 | except: 395 | pass 396 | return _VARIANT_value_getter(self) 397 | VARIANT.value = property(VARIANT_value_getter, VARIANT.value.fset) 398 | 399 | def __getattr__(name): 400 | symbol = AhkApi[name] 401 | if symbol is None: 402 | raise AttributeError 403 | return symbol 404 | -------------------------------------------------------------------------------- /comproxy.py: -------------------------------------------------------------------------------- 1 | from ctypes import * 2 | from _ctypes import COMError, CopyComPointer, _SimpleCData 3 | from ctypes.wintypes import VARIANT_BOOL 4 | from sys import exc_info 5 | 6 | 7 | _IncRef = pythonapi.Py_IncRef 8 | _DecRef = pythonapi.Py_DecRef 9 | _IncRef.argtypes = [c_void_p] 10 | _DecRef.argtypes = [c_void_p] 11 | _IncRef.restype = _DecRef.restype = None 12 | _CLSIDFromString = oledll.ole32.CLSIDFromString 13 | 14 | class GUID(Structure): 15 | _fields_ = (("Data1", c_ulong), ("Data2", c_ushort), ("Data3", c_ushort), ("Data4", c_byte * 8)) 16 | 17 | def __init__(self, name=None): 18 | if name is not None: 19 | _CLSIDFromString(name, byref(self)) 20 | 21 | def __eq__(self, other): 22 | return isinstance(other, GUID) and bytes(self) == bytes(other) 23 | 24 | def __hash__(self): 25 | return hash(bytes(self)) 26 | 27 | IID_IUnknown = GUID("{00000000-0000-0000-C000-000000000046}") 28 | IID_IDispatch = GUID("{00020400-0000-0000-C000-000000000046}") 29 | IID_PYOBJECT = GUID("{0E814CA7-00C1-479B-B0EF-D2CEA22BFC34}") 30 | riid_null = byref(GUID()) 31 | 32 | PTR_SIZE = sizeof(c_void_p()) 33 | VT_EMPTY = 0 34 | VT_NULL = 1 35 | VT_I2 = 2 36 | VT_I4 = 3 37 | VT_R4 = 4 38 | VT_R8 = 5 39 | VT_BSTR = 8 40 | VT_DISPATCH = 9 41 | VT_ERROR = 10 42 | VT_BOOL = 11 43 | VT_VARIANT = 12 44 | VT_UNKNOWN = 13 45 | VT_I8 = 20 46 | VT_HRESULT = 25 47 | VT_SAFEARRAY = 27 48 | VT_BYREF = 16384 49 | VT_TYPEMASK = 4095 50 | 51 | class BSTR(_SimpleCData): 52 | "The windows BSTR data type" 53 | _type_ = "X" 54 | _needsfree = False 55 | def __repr__(self): 56 | return "%s(%r)" % (self.__class__.__name__, self.value) 57 | 58 | def __ctypes_from_outparam__(self): 59 | self._needsfree = True 60 | return self.value 61 | 62 | def __del__(self): 63 | # Free the string if self owns the memory 64 | # or if instructed by __ctypes_from_outparam__. 65 | if self._b_base_ is None or self._needsfree: 66 | windll.oleaut32.SysFreeString(self) 67 | 68 | @classmethod 69 | def from_param(cls, value): 70 | """Convert into a foreign function call parameter.""" 71 | if isinstance(value, cls): 72 | return value 73 | return cls(value) 74 | 75 | class SAFEARRAYBOUND(Structure): 76 | _fields_ = (('cElements', c_uint), ('lLbound', c_long)) 77 | 78 | class SAFEARRAY(Structure): 79 | _fields_ = (('cDims', c_ushort), 80 | ('fFeatures', c_ushort), 81 | ('cbElements', c_uint), 82 | ('cLocks', c_uint), 83 | ('pvData', c_void_p), 84 | ('rgsabound', SAFEARRAYBOUND * 1)) 85 | 86 | class DECIMAL(Structure): 87 | _fields_ = (("wReserved", c_ushort), ("scale", c_ubyte), ("sign", c_ubyte), ("Hi32", c_ulong), ("Lo64", c_ulonglong)) 88 | 89 | class ComProxy(Structure): 90 | _dyna_names_ = [] 91 | 92 | def __init__(self, obj): 93 | self.pvtbl = pointer(self._vtbl_) 94 | self.obj = obj 95 | self.pself = id(self) 96 | self.pdisp = cast(byref(self), c_void_p) 97 | 98 | def as_IDispatch(self): 99 | IDispatch.AddRef(c_void_p(self.pdisp)) 100 | return IDispatch(self.pdisp) 101 | 102 | @classmethod 103 | def _make_vtbl(cls): 104 | def QueryInterface(this, riid, ppvObj): 105 | if riid[0] in (IID_IUnknown, IID_IDispatch, IID_PYOBJECT): 106 | CopyComPointer(c_void_p(this), ppvObj) 107 | return 0 108 | ppvObj[0] = None 109 | return -2147467262 # E_NOINTERFACE 110 | 111 | def AddRef(this): 112 | _IncRef(p := c_void_p.from_address(this + PTR_SIZE * 2)) 113 | return c_ssize_t.from_address(p.value).value 114 | 115 | def Release(this): 116 | p = c_void_p.from_address(this + PTR_SIZE * 2) 117 | t = c_ssize_t.from_address(p.value).value 118 | _DecRef(p) 119 | return t - 1 120 | 121 | def GetTypeInfoCount(this, pctinfo): 122 | pctinfo[0] = 0 123 | return 0 124 | 125 | def GetTypeInfo(this, itinfo, lcid, ptinfo): 126 | ptinfo[0] = None 127 | return -2147467263 128 | 129 | def GetIDsOfNames(this, riid, rgszNames, cNames, lcid, rgDispId): 130 | obj = py_object.from_address(this + PTR_SIZE).value 131 | names = py_object.from_address(this + PTR_SIZE * 2).value._dyna_names_ 132 | name = rgszNames[0] 133 | if hasattr(obj, name): 134 | try: 135 | rgDispId[0] = names.index(name) + 1 136 | except: 137 | names.append(name) 138 | rgDispId[0] = len(names) 139 | else: 140 | return -2147352570 141 | i = 1 142 | while i < cNames: 143 | rgDispId[i] = -1 144 | i += 1 145 | return 0 if i == 1 else -2147352570 146 | 147 | def Invoke(this, dispIdMember, riid, lcid, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr): 148 | obj = py_object.from_address(this + PTR_SIZE).value 149 | params = pDispParams[0] 150 | args = tuple(params.rgvarg[i].value for i in range(params.cArgs)[::-1]) 151 | excep = pExcepInfo[0] if pExcepInfo else EXCEPINFO() 152 | try: 153 | if dispIdMember == 0: 154 | if (wFlags & 1) and callable(obj): 155 | pVarResult[0].value = obj(*args) 156 | elif wFlags & 2: 157 | pVarResult[0].value = obj[args[0] if len(args) == 1 else args] 158 | elif wFlags & (4|8): 159 | obj[args[0]] = args[1] 160 | else: return -2147352573 161 | elif dispIdMember > 0 : 162 | try: 163 | name = py_object.from_address(this + PTR_SIZE * 2).value._dyna_names_[dispIdMember - 1] 164 | except: 165 | return -2147352573 # DISP_E_MEMBERNOTFOUND 166 | if wFlags & (4 | 8): 167 | setattr(obj, name, args[0]) 168 | else: 169 | try: 170 | obj = getattr(obj, name) 171 | except: 172 | return -2147352573 173 | if callable(obj): 174 | if wFlags & 1: 175 | pVarResult[0].value = obj(*args) 176 | else: return -2147352573 177 | else: 178 | if wFlags & 2: 179 | if args: 180 | pVarResult[0].value = obj[args[0] if len(args) == 1 else args] 181 | else: pVarResult[0].value = obj 182 | else: return -2147352573 183 | elif dispIdMember == -4: 184 | if hasattr(obj, '__iter__'): 185 | class _Enum: 186 | __slots__ = ('obj',) 187 | def __init__(self, obj): 188 | self.obj = iter(obj) 189 | def __call__(self, *args): 190 | try: 191 | v = self.obj.__next__() 192 | l = len(args) 193 | if l == 1: 194 | args[0][0] = v 195 | else: 196 | for i in range(l): 197 | args[i][0] = v[i] 198 | return 1 199 | except: 200 | return 0 201 | pVarResult[0].value = ComProxy(_Enum(obj)).as_IDispatch() 202 | else: 203 | excep.scode = -2147352573 204 | return -2147352573 205 | else: return -2147352573 206 | except: 207 | typ, value, tb = exc_info() 208 | excep.scode = -2147352567 209 | if value.args: 210 | excep.bstrDescription = f'{typ.__name__}: {value.args[0]}' 211 | excep.bstrSource = obj.__name__ if callable(obj) else str(obj) 212 | excep.bstrHelpFile = tb.tb_frame.f_code.co_filename 213 | excep.dwHelpContext = tb.tb_lineno 214 | return -2147352567 215 | return 0 216 | 217 | local = locals() 218 | methods = [] 219 | for field in VTbl_IDispatch._fields_: 220 | name, functype = field 221 | methods.append(functype(local[name])) 222 | cls._vtbl_ = VTbl_IDispatch(*methods) 223 | 224 | class VARIANT(Structure): 225 | class U_VARIANT1(Union): 226 | class __tagVARIANT(Structure): 227 | class U_VARIANT2(Union): 228 | _fields_ = (("VT_BOOL", VARIANT_BOOL), 229 | ("VT_I1", c_byte), 230 | ("VT_I2", c_short), 231 | ("VT_I4", c_long), 232 | ("VT_I8", c_longlong), 233 | ("VT_INT", c_int), 234 | ("VT_UI1", c_ubyte), 235 | ("VT_UI2", c_ushort), 236 | ("VT_UI4", c_ulong), 237 | ("VT_UI8", c_ulonglong), 238 | ("VT_UINT", c_uint), 239 | ("VT_R4", c_float), 240 | ("VT_R8", c_double), 241 | ("c_wchar_p", c_wchar_p), 242 | ("c_void_p", c_void_p), 243 | ("pparray", POINTER(POINTER(SAFEARRAY))), 244 | ("bstrVal", BSTR), 245 | ("_tagBRECORD", c_void_p * 2)) 246 | _fields_ = (("vt", c_ushort), 247 | ("wReserved1", c_ushort), 248 | ("wReserved2", c_ushort), 249 | ("wReserved3", c_ushort), 250 | ("_", U_VARIANT2)) 251 | _anonymous_ = ["_"] 252 | _fields_ = (("__VARIANT_NAME_2", __tagVARIANT), ("decVal", DECIMAL)) 253 | _anonymous_ = ["__VARIANT_NAME_2"] 254 | _fields_ = (("__VARIANT_NAME_1", U_VARIANT1),) 255 | _anonymous_ = ["__VARIANT_NAME_1"] 256 | 257 | def __init__(self, *args): 258 | if args: 259 | self.value = args[0] 260 | 261 | def __del__(self): 262 | if self._b_needsfree_: 263 | # XXX This does not work. _b_needsfree_ is never 264 | # set because the buffer is internal to the object. 265 | _VariantClear(self) 266 | 267 | def __repr__(self): 268 | if self.vt & VT_BYREF: 269 | return "VARIANT(vt=0x%x, byref(%r))" % (self.vt, self[0]) 270 | return "VARIANT(vt=0x%x, %r)" % (self.vt, self.value) 271 | 272 | @classmethod 273 | def from_param(cls, value): 274 | if isinstance(value, cls): 275 | return value 276 | return cls(value) 277 | 278 | def __setitem__(self, index, value): 279 | # This method allows to change the value of a 280 | # (VT_BYREF|VT_xxx) variant in place. 281 | if index != 0: 282 | raise IndexError(index) 283 | if not self.vt & VT_BYREF: 284 | raise TypeError("set_byref requires a VT_BYREF VARIANT instance") 285 | typ = _vartype_to_ctype[self.vt & ~VT_BYREF] 286 | cast(self.c_void_p, POINTER(typ))[0].value = value 287 | 288 | @property 289 | def value(self): 290 | vt = self.vt 291 | if vt == VT_BSTR: 292 | return self.bstrVal 293 | elif vt == VT_I4: 294 | return self.VT_I4 295 | elif vt == VT_R8: 296 | return self.VT_R8 297 | elif vt == VT_DISPATCH: 298 | val = self.c_void_p 299 | if not val: 300 | return None 301 | idisp = IDispatch(val) 302 | try: 303 | idisp.QueryInterface(IID_PYOBJECT) 304 | return py_object.from_address(val + PTR_SIZE).value 305 | except: 306 | idisp.AddRef() 307 | return Dispatch(idisp) 308 | elif vt == VT_I8: 309 | return self.VT_I8 310 | elif vt == VT_BOOL: 311 | return self.VT_BOOL 312 | elif vt == VT_UNKNOWN: 313 | return self.c_void_p 314 | elif vt in (VT_EMPTY, VT_NULL): 315 | return None 316 | elif self.vt & VT_BYREF: 317 | return self 318 | else: 319 | raise NotImplementedError("typecode %d = 0x%x)" % (vt, vt)) 320 | 321 | @value.setter 322 | def value(self, value): 323 | _VariantClear(self) 324 | if isinstance(value, int): 325 | self.vt = VT_I8 326 | self.VT_I8 = value 327 | elif isinstance(value, float): 328 | self.vt = VT_R8 329 | self.VT_R8 = value 330 | elif isinstance(value, str): 331 | self.vt = VT_BSTR 332 | self.c_void_p = _SysAllocStringLen(value, len(value)) 333 | elif isinstance(value, bool): 334 | self.vt = VT_BOOL 335 | self.VT_BOOL = value 336 | elif value is None: 337 | self.vt = VT_NULL 338 | elif isinstance(value, VARIANT): 339 | _VariantCopy(self, value) 340 | else: 341 | if isinstance(value, Dispatch): 342 | self.c_void_p = p = value._comobj_ 343 | elif isinstance(value, IDispatch): 344 | self.c_void_p = p = value 345 | else: 346 | if not isinstance(value, ComProxy): 347 | value = ComProxy(value) 348 | self.c_void_p = p = c_void_p(value.pdisp) 349 | self.vt = VT_DISPATCH 350 | IDispatch.AddRef(p) 351 | 352 | def __getitem__(self, index): 353 | if index != 0: 354 | raise IndexError(index) 355 | if self.vt == VT_BYREF|VT_VARIANT: 356 | v = VARIANT() 357 | # apparently VariantCopyInd doesn't work always with 358 | # VT_BYREF|VT_VARIANT, so do it manually. 359 | v = cast(self.c_void_p, POINTER(VARIANT))[0] 360 | return v.value 361 | else: 362 | v = VARIANT() 363 | _VariantCopyInd(v, self) 364 | return v.value 365 | 366 | def __ctypes_from_outparam__(self): 367 | # XXX Manual resource management, because of the VARIANT bug: 368 | result = self.value 369 | self.value = None 370 | return result 371 | 372 | 373 | class DISPPARAMS(Structure): 374 | _fields_ = (('rgvarg', POINTER(VARIANT)), 375 | ('rgdispidNamedArgs', POINTER(c_long)), 376 | ('cArgs', c_uint), 377 | ('cNamedArgs', c_uint)) 378 | def __del__(self): 379 | if self._b_needsfree_: 380 | for i in range(self.cArgs): 381 | self.rgvarg[i].value = None 382 | 383 | class EXCEPINFO(Structure): 384 | _fields_ = (('wCode', c_ushort), 385 | ('wReserved', c_ushort), 386 | ('bstrSource', BSTR), 387 | ('bstrDescription', BSTR), 388 | ('bstrHelpFile', BSTR), 389 | ('dwHelpContext', c_ulong), 390 | ('pvReserved', c_void_p), 391 | ('pfnDeferredFillIn', c_void_p), 392 | ('scode', c_long)) 393 | def __repr__(self): 394 | return "" % ((self.wCode, self.bstrSource, self.bstrDescription, self.bstrHelpFile, self.dwHelpContext, self.pfnDeferredFillIn, self.scode),) 395 | 396 | _vartype_to_ctype = { 397 | VT_I4: c_long, 398 | VT_I8: c_longlong, 399 | VT_R4: c_float, 400 | VT_R8: c_double, 401 | VT_BOOL: VARIANT_BOOL, 402 | VT_BSTR: BSTR, 403 | VT_VARIANT: VARIANT, 404 | VT_BYREF|VT_VARIANT: POINTER(VARIANT), 405 | VT_BYREF|VT_BSTR: POINTER(BSTR) 406 | } 407 | 408 | _oleaut32 = oledll.oleaut32 409 | _VariantChangeType = _oleaut32.VariantChangeType 410 | _VariantChangeType.argtypes = (POINTER(VARIANT), POINTER(VARIANT), c_ushort, c_ushort) 411 | 412 | _VariantClear = _oleaut32.VariantClear 413 | _VariantClear.argtypes = (POINTER(VARIANT),) 414 | 415 | _SysAllocStringLen = windll.oleaut32.SysAllocStringLen 416 | _SysAllocStringLen.argtypes = (c_wchar_p, c_uint) 417 | _SysAllocStringLen.restype = c_void_p 418 | 419 | _VariantCopy = _oleaut32.VariantCopy 420 | _VariantCopy.argtypes = (POINTER(VARIANT), POINTER(VARIANT)) 421 | 422 | _VariantCopyInd = _oleaut32.VariantCopyInd 423 | _VariantCopyInd.argtypes = (POINTER(VARIANT), POINTER(VARIANT)) 424 | 425 | # some commonly used VARIANT instances 426 | VARIANT.null = VARIANT(None) 427 | VARIANT.empty = VARIANT() 428 | VARIANT.missing = v = VARIANT() 429 | v.vt = VT_ERROR 430 | v.VT_I4 = 0x80020004 431 | del v 432 | 433 | instancemethod = PYFUNCTYPE(py_object, py_object)(('PyInstanceMethod_New', pythonapi)) 434 | class IDispatch(c_void_p): 435 | QueryInterface = instancemethod(WINFUNCTYPE( 436 | HRESULT, POINTER(GUID), POINTER(c_void_p))( 437 | 0, 'QueryInterface', ((1,),(2,)), IID_IDispatch)) 438 | AddRef = instancemethod(WINFUNCTYPE(c_ulong)(1, 'AddRef', iid=IID_IDispatch)) 439 | Release = instancemethod(WINFUNCTYPE(c_ulong)(2, 'Release', iid=IID_IDispatch)) 440 | __GetIDsOfNames = instancemethod(WINFUNCTYPE( 441 | HRESULT, POINTER(GUID), POINTER(c_wchar_p), c_uint, c_uint, POINTER(c_long))( 442 | 5, 'GetIDsOfNames', iid=IID_IDispatch)) 443 | __Invoke = instancemethod(WINFUNCTYPE(HRESULT, c_long, POINTER(GUID), c_uint, 444 | c_ushort, POINTER(DISPPARAMS), POINTER(VARIANT), POINTER(EXCEPINFO), POINTER(c_uint))( 445 | 6, 'Invoke', iid=IID_IDispatch)) 446 | 447 | def GetIDsOfNames(self, name): 448 | dispid = c_long() 449 | self.__GetIDsOfNames(riid_null, byref(c_wchar_p(name)), 1, 0x400, byref(dispid)) 450 | return dispid.value 451 | 452 | def Invoke(self, dispid, invkind, *args): 453 | result = VARIANT() 454 | excepinfo = EXCEPINFO() 455 | argerr = c_uint() 456 | array = (VARIANT * len(args))() 457 | dp = DISPPARAMS() 458 | for i, a in enumerate(args[::-1]): 459 | array[i].value = a 460 | if args: 461 | dp.cArgs = len(args) 462 | dp.rgvarg = array 463 | 464 | if invkind in (4, 8): 465 | dp.cNamedArgs = 1 466 | dp.rgdispidNamedArgs = pointer(c_long(-3)) 467 | rresult = None 468 | else: 469 | dp.cNamedArgs = 0 470 | rresult = byref(result) 471 | 472 | try: 473 | self.__Invoke(dispid, riid_null, 0x400, invkind, byref(dp), rresult, byref(excepinfo), byref(argerr)) 474 | except Exception as err: 475 | (errno, text, details, hresult, n) = err.args 476 | if hresult == -2147352567: # DISP_E_EXCEPTION 477 | details = (excepinfo.bstrDescription, excepinfo.bstrSource, excepinfo.bstrHelpFile, excepinfo.dwHelpContext, excepinfo.scode) 478 | raise COMError(hresult, text, details) 479 | elif hresult == -2147352572: # DISP_E_PARAMNOTFOUND 480 | raise COMError(hresult, text, argerr.value) 481 | elif hresult == -2147352571: # DISP_E_TYPEMISMATCH 482 | raise COMError(hresult, text, 483 | ("TypeError: Parameter %s" % (argerr.value + 1), args)) 484 | raise COMError(hresult, text, None) 485 | return result.value 486 | 487 | _free = False 488 | 489 | def __init__(self, *args): 490 | if l := len(args): 491 | val = args[0] 492 | self.value = val if isinstance(val, int) else val.value 493 | self._free = args[1] if l > 1 else True 494 | 495 | def __del__(self): 496 | if self._free and self.value: 497 | self.Release() 498 | 499 | class VTbl_IDispatch(Structure): 500 | _fields_ = ( 501 | ('QueryInterface', WINFUNCTYPE(HRESULT, c_void_p, POINTER(GUID), POINTER(c_void_p))), 502 | ('AddRef', WINFUNCTYPE(c_ulong, c_void_p)), 503 | ('Release', WINFUNCTYPE(c_ulong, c_void_p)), 504 | ('GetTypeInfoCount', WINFUNCTYPE(HRESULT, c_void_p, POINTER(c_uint))), 505 | ('GetTypeInfo', WINFUNCTYPE(HRESULT, c_void_p, c_uint, c_uint, POINTER(c_void_p))), 506 | ('GetIDsOfNames', WINFUNCTYPE(HRESULT, c_void_p, POINTER(GUID), POINTER(c_wchar_p), c_uint, c_uint, POINTER(c_long))), 507 | ('Invoke', WINFUNCTYPE(HRESULT, c_void_p, c_long, POINTER(GUID), c_uint, c_ushort, POINTER(DISPPARAMS), POINTER(VARIANT), POINTER(EXCEPINFO), POINTER(c_uint))) 508 | ) 509 | 510 | ComProxy._fields_ = (('pvtbl', POINTER(VTbl_IDispatch)), ('obj', py_object), ('pself', c_void_p), ('pdisp', c_void_p)) 511 | ComProxy._make_vtbl() 512 | 513 | class Dispatch: 514 | __slots__ = ('_comobj_', '_this_', '_dispids_') 515 | _comobj_: IDispatch 516 | _dispids_: dict 517 | 518 | def __init__(self, comobj, dispids = None, bindthis = True): 519 | object.__setattr__(self, '_comobj_', comobj) 520 | object.__setattr__(self, '_this_', None if bindthis else False) 521 | object.__setattr__(self, '_dispids_', dispids or {}) 522 | 523 | def __dir__(self): 524 | return self.__slots__ 525 | 526 | def __call__(self, *args): 527 | if self._this_: 528 | v = self._comobj_.Invoke(0, 1, self._this_, *args) 529 | else: v = self._comobj_.Invoke(0, 1, *args) 530 | if isinstance(v, Dispatch): 531 | object.__setattr__(v, '_dispids_', self._dispids_) 532 | return v 533 | 534 | def __getitem__(self, index, dispid = 0): 535 | if isinstance(index, tuple): 536 | v = self._comobj_.Invoke(dispid, 2, *index) 537 | else: v = self._comobj_.Invoke(dispid, 2, index) 538 | if isinstance(v, Dispatch): 539 | object.__setattr__(v, '_dispids_', self._dispids_) 540 | return v 541 | 542 | def __setitem__(self, index, value, dispid = 0): 543 | if isinstance(index, tuple): 544 | self._comobj_.Invoke(dispid, 4, *index, value) 545 | else: self._comobj_.Invoke(dispid, 4, index, value) 546 | 547 | def __getattr__(self, attr): 548 | if not (dispid := self._dispids_.get(attr, None)): 549 | self._dispids_[attr] = dispid = self._comobj_.GetIDsOfNames(attr) 550 | try: 551 | v = self._comobj_.Invoke(dispid, 2) 552 | if isinstance(v, Dispatch): 553 | object.__setattr__(v, '_dispids_', self._dispids_) 554 | if v._this_ is None: 555 | object.__setattr__(v, '_this_', self) 556 | return v 557 | except Exception as err: 558 | if err.hresult in [-2147352567]: 559 | class MethodCaller: 560 | __slots__ = ('dispid', 'obj') 561 | def __init__(self, obj, dispid): 562 | self.obj = obj 563 | self.dispid = dispid 564 | 565 | def __call__(self, *args): 566 | return self.obj._comobj_.Invoke(self.dispid, 1, *args) 567 | 568 | def __getitem__(self, index): 569 | return self.obj.__getitem__(index, self.dispid) 570 | 571 | def __setitem__(self, index, value): 572 | self.obj.__setitem__(index, value, self.dispid) 573 | 574 | return MethodCaller(self, dispid) 575 | else: 576 | raise 577 | 578 | def __setattr__(self, attr, value): 579 | if not (dispid := self._dispids_.get(attr, None)): 580 | self._dispids_[attr] = dispid = self._comobj_.GetIDsOfNames(attr) 581 | self._comobj_.Invoke(dispid, 4, value) 582 | 583 | __all__ = ['ComProxy', 'Dispatch', 'VARIANT', 'GUID', 'IDispatch', 'instancemethod', 'IID_PYOBJECT', 'IID_IDispatch', 'PTR_SIZE'] -------------------------------------------------------------------------------- /py4ahk.ahk: -------------------------------------------------------------------------------- 1 | class Python { 2 | static __instance__ := 0 3 | static Call(dllpath := 'python39.dll') { 4 | if this.__instance__ 5 | return this.__instance__ 6 | if !(mod := DllCall('GetModuleHandle', 'str', dllpath, 'ptr') || DllCall('LoadLibrary', 'str', dllpath, 'ptr')) 7 | throw OSError() 8 | DllCall(addr('Py_Initialize')) 9 | if !DllCall(addr('Py_IsInitialized')) 10 | throw Error('py is not initialized') 11 | Py_Finalize := addr('Py_Finalize') 12 | this.DefineProp('__Delete', {call: (self) => (self.__instance__ := 0, DllCall(Py_Finalize))}) 13 | pyscript := 14 | ( 15 | 'from builtins import *`n__import__ = __import__`n' 16 | 'class ScriptInterpreter:`n' 17 | ' def __getattr__(self, __name, __globals = globals()):`n' 18 | ' try: return __globals.__getitem__(__name)`n' 19 | ' except: raise AttributeError`n' 20 | ' def __setattr__(self, __name, __value, __globals = globals()):`n' 21 | ' __globals.__setitem__(__name, __value)`n' 22 | ' def eval(self, __code, __globals = globals(), __locals = None):`n' 23 | ' return eval(__code, __globals, __locals)`n' 24 | ' def exec(self, __code, __globals = globals(), __locals = None):`n' 25 | ' exec(__code, __globals, __locals)`n' 26 | ' @classmethod`n' 27 | ' def _init(cls):`n' 28 | ' from pyahk.comproxy import ComProxy`n' 29 | ' from ctypes import POINTER, c_void_p, cast`n' 30 | ' idisp = ComProxy(cls()).as_IDispatch()`n' 31 | ' idisp.AddRef()`n' 32 | ' cast({}, POINTER(c_void_p))[0] = idisp.value`n' 33 | ' {}`n' 34 | 'ScriptInterpreter._init(); del ScriptInterpreter._init; del ScriptInterpreter' 35 | ) 36 | if DllCall('GetProcAddress', 'ptr', 0, 'astr', 'ahkGetApi', 'ptr') { 37 | ahk_mod := DllCall('GetModuleHandleW', 'ptr', 0, 'ptr') 38 | v2hext := 'from pyahk import AhkApi; AhkApi.initialize(cast(' ahk_mod ', c_void_p).value); AhkApi.pumpMessages()' 39 | } else v2hext := '' 40 | pyscript := Format(pyscript, (buf := Buffer(A_PtrSize, 0)).Ptr, v2hext) 41 | if DllCall(addr('PyRun_SimpleString'), 'astr', pyscript) 42 | throw Error('Error in executing initialization script. See stderr for details.') 43 | return this.__instance__ := ComObjFromPtr(NumGet(buf, 'ptr')) 44 | addr(f) => DllCall('GetProcAddress', 'ptr', mod, 'astr', f, 'ptr') 45 | } 46 | ; __Call(name, params) => any ; call `__main__`'s global var 47 | ; __Get(name, params) => any ; get `__main__`'s global var 48 | ; __Set(name, params, value) => void ; set `__main__`'s global var 49 | ; exec(code, globals, locals) => void 50 | ; eval(code, globals, locals) => any 51 | } --------------------------------------------------------------------------------