From f24ef8442e0bfd1d4e3882e61e7f74f8e8e91645 Mon Sep 17 00:00:00 2001 From: onyx-and-iris Date: Sun, 24 Sep 2023 17:25:43 +0100 Subject: [PATCH] move vm_path and dll loading into cdll.py define binds explicitly in Binds. bump to version 0.3 --- addon/globalPlugins/voicemeeter/binds.py | 70 ++++++++----------- addon/globalPlugins/voicemeeter/cdll.py | 49 +++++++++++++ addon/globalPlugins/voicemeeter/controller.py | 20 +++--- addon/globalPlugins/voicemeeter/error.py | 11 ++- buildVars.py | 2 +- 5 files changed, 100 insertions(+), 52 deletions(-) create mode 100644 addon/globalPlugins/voicemeeter/cdll.py diff --git a/addon/globalPlugins/voicemeeter/binds.py b/addon/globalPlugins/voicemeeter/binds.py index 27e01f1..8d5e37d 100644 --- a/addon/globalPlugins/voicemeeter/binds.py +++ b/addon/globalPlugins/voicemeeter/binds.py @@ -1,51 +1,41 @@ import ctypes as ct -import winreg -from pathlib import Path +from ctypes.wintypes import CHAR, FLOAT, LONG -from .error import VMError +from .cdll import libc +from .error import VMCAPIError class Binds: - VM_KEY = "VB:Voicemeeter {17359A74-1236-5467}" - BITS = 64 if ct.sizeof(ct.c_voidp) == 8 else 32 + bind_login = libc.VBVMR_Login + bind_login.restype = LONG + bind_login.argtypes = None - def __init__(self): - dll_path = Path(self.__vmpath()).parent.joinpath( - f'VoicemeeterRemote{"64" if self.BITS == 64 else ""}.dll' - ) - if self.BITS == 64: - self.libc = ct.CDLL(str(dll_path)) - else: - self.libc = ct.WinDLL(str(dll_path)) + bind_logout = libc.VBVMR_Logout + bind_logout.restype = LONG + bind_logout.argtypes = None - def __vmpath(self): - with winreg.OpenKey( - winreg.HKEY_LOCAL_MACHINE, - r"{}".format( - "\\".join( - ( - "\\".join( - filter( - None, - ( - "SOFTWARE", - "WOW6432Node" if self.BITS == 64 else "", - "Microsoft", - "Windows", - "CurrentVersion", - "Uninstall", - ), - ) - ), - self.VM_KEY, - ) - ) - ), - ) as vm_key: - return winreg.QueryValueEx(vm_key, r"UninstallString")[0] + bind_run_voicemeeter = libc.VBVMR_RunVoicemeeter + bind_run_voicemeeter.restype = LONG + bind_run_voicemeeter.argtypes = [LONG] + + bind_get_voicemeeter_type = libc.VBVMR_GetVoicemeeterType + bind_get_voicemeeter_type.restype = LONG + bind_get_voicemeeter_type.argtypes = [ct.POINTER(LONG)] + + bind_is_parameters_dirty = libc.VBVMR_IsParametersDirty + bind_is_parameters_dirty.restype = LONG + bind_is_parameters_dirty.argtypes = None + + bind_get_parameter_float = libc.VBVMR_GetParameterFloat + bind_get_parameter_float.restype = LONG + bind_get_parameter_float.argtypes = [ct.POINTER(CHAR), ct.POINTER(FLOAT)] + + bind_set_parameter_float = libc.VBVMR_SetParameterFloat + bind_set_parameter_float.restype = LONG + bind_set_parameter_float.argtypes = [ct.POINTER(CHAR), FLOAT] def call(self, fn, *args, ok=(0,)): - retval = getattr(self.libc, fn)(*args) + retval = fn(*args) if retval not in ok: - raise VMError(f"{fn} returned {retval}") + raise VMCAPIError(fn.bind_namebind_, retval) return retval diff --git a/addon/globalPlugins/voicemeeter/cdll.py b/addon/globalPlugins/voicemeeter/cdll.py new file mode 100644 index 0000000..3f31a5e --- /dev/null +++ b/addon/globalPlugins/voicemeeter/cdll.py @@ -0,0 +1,49 @@ +import ctypes as ct +import platform +import winreg +from pathlib import Path + +from .error import VMError + +BITS = 64 if ct.sizeof(ct.c_voidp) == 8 else 32 + +if platform.system() != "Windows": + raise VMError("Only Windows OS supported") + + +VM_KEY = "VB:Voicemeeter {17359A74-1236-5467}" +REG_KEY = "\\".join( + filter( + None, + ( + "SOFTWARE", + "WOW6432Node" if BITS == 64 else "", + "Microsoft", + "Windows", + "CurrentVersion", + "Uninstall", + ), + ) +) + + +def get_vmpath(): + with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"{}".format("\\".join((REG_KEY, VM_KEY)))) as vm_key: + return winreg.QueryValueEx(vm_key, r"UninstallString")[0] + + +try: + vm_parent = Path(get_vmpath()).parent +except FileNotFoundError as e: + raise VMError(f"Unable to fetch DLL path from the registry") from e + +DLL_NAME = f'VoicemeeterRemote{"64" if BITS == 64 else ""}.dll' + +dll_path = vm_parent.joinpath(DLL_NAME) +if not dll_path.is_file(): + raise VMError(f"Could not find {dll_path}") + +if BITS == 64: + libc = ct.CDLL(str(dll_path)) +else: + libc = ct.WinDLL(str(dll_path)) diff --git a/addon/globalPlugins/voicemeeter/controller.py b/addon/globalPlugins/voicemeeter/controller.py index 41c3f6e..b471c96 100644 --- a/addon/globalPlugins/voicemeeter/controller.py +++ b/addon/globalPlugins/voicemeeter/controller.py @@ -3,45 +3,45 @@ import ctypes as ct from logHandler import log from .binds import Binds +from .cdll import BITS from .context import Context, StripStrategy from .kinds import KindId -class Controller: +class Controller(Binds): def __init__(self): - self.binds = Binds() self.ctx = Context(StripStrategy(self, 0)) def login(self): - retval = self.binds.call("VBVMR_Login", ok=(0, 1)) + retval = self.call(self.bind_login, ok=(0, 1)) log.info("INFO - logged into Voicemeeter Remote API") return retval def logout(self): - self.binds.call("VBVMR_Logout") + self.call(self.bind_logout) log.info("NFO - logged out of Voicemeeter Remote API") @property def kind_id(self): c_type = ct.c_long() - self.binds.call("VBVMR_GetVoicemeeterType", ct.byref(c_type)) + self.call(self.bind_get_voicemeeter_type, ct.byref(c_type)) return KindId(c_type.value).name.lower() def run_voicemeeter(self, kind_id): val = kind_id.value - if val == 3 and Binds.BITS == 64: + if val == 3 and BITS == 64: val = 6 - self.binds.call("VBVMR_RunVoicemeeter", val) + self.call(self.bind_run_voicemeeter, val) def __clear(self): - while self.binds.call("VBVMR_IsParametersDirty", ok=(0, 1)) == 1: + while self.call(self.bind_is_parameters_dirty, ok=(0, 1)) == 1: pass def _get(self, param): self.__clear() buf = ct.c_float() - self.binds.call("VBVMR_GetParameterFloat", param.encode(), ct.byref(buf)) + self.call(self.bind_get_parameter_float, param.encode(), ct.byref(buf)) return buf.value def _set(self, param, val): - self.binds.call("VBVMR_SetParameterFloat", param.encode(), ct.c_float(float(val))) + self.call(self.bind_set_parameter_float, param.encode(), ct.c_float(float(val))) diff --git a/addon/globalPlugins/voicemeeter/error.py b/addon/globalPlugins/voicemeeter/error.py index a979c40..43f7043 100644 --- a/addon/globalPlugins/voicemeeter/error.py +++ b/addon/globalPlugins/voicemeeter/error.py @@ -1,2 +1,11 @@ class VMError(Exception): - """Base VMError class""" + """Base voicemeeterlib exception class.""" + + +class VMCAPIError(VMError): + """Exception raised when the C-API returns an error code""" + + def __init__(self, fn_name, code): + self.fn_name = fn_name + self.code = code + super().__init__(f"{self.fn_name} returned {self.code}") diff --git a/buildVars.py b/buildVars.py index d78f50f..5c115dd 100644 --- a/buildVars.py +++ b/buildVars.py @@ -28,7 +28,7 @@ addon_info = { The add-on requires Voicemeeter to be installed.""" ), # version - "addon_version": "0.2", + "addon_version": "0.3", # Author(s) "addon_author": "onyx-and-iris ", # URL for the add-on documentation support