"""Windows 터치 키보드(TabTip) 표시/숨김 — ITipInvocation COM API 사용.""" import ctypes import os import sys from ctypes import wintypes _TABTIP_PATHS = ( r"C:\Program Files\Common Files\microsoft shared\ink\TabTip.exe", r"C:\Program Files (x86)\Common Files\microsoft shared\ink\TabTip.exe", ) CLSID_UIHostNoLaunch = ( 0x4CE576FA, 0x83DC, 0x4F88, (0x95, 0x1C, 0x9D, 0x07, 0x82, 0xB4, 0xE3, 0x76), ) IID_ITipInvocation = ( 0x37C994E7, 0x432B, 0x4834, (0xA2, 0xF7, 0xDC, 0xE1, 0xF1, 0x3B, 0x83, 0x4B), ) REGDB_E_CLASSNOTREG = -2147221164 # 0x80040154 SW_SHOW = 5 CLSCTX_LOCAL_SERVER = 4 class GUID(ctypes.Structure): _fields_ = [ ("Data1", wintypes.DWORD), ("Data2", wintypes.WORD), ("Data3", wintypes.WORD), ("Data4", wintypes.BYTE * 8), ] def __init__(self, guid_tuple): super().__init__() self.Data1 = guid_tuple[0] self.Data2 = guid_tuple[1] self.Data3 = guid_tuple[2] self.Data4[:] = guid_tuple[3] def _tabtip_path() -> str | None: for path in _TABTIP_PATHS: if os.path.exists(path): return path return None def _launch_tabtip() -> bool: path = _tabtip_path() if not path: return False ret = ctypes.windll.shell32.ShellExecuteW( None, "open", path, None, None, SW_SHOW, ) return ret > 32 def _create_tip_invocation(ole32): clsid = GUID(CLSID_UIHostNoLaunch) iid = GUID(IID_ITipInvocation) obj = ctypes.c_void_p() hr = ole32.CoCreateInstance( ctypes.byref(clsid), None, CLSCTX_LOCAL_SERVER, ctypes.byref(iid), ctypes.byref(obj), ) if hr == REGDB_E_CLASSNOTREG: if not _launch_tabtip(): return None hr = ole32.CoCreateInstance( ctypes.byref(clsid), None, CLSCTX_LOCAL_SERVER, ctypes.byref(iid), ctypes.byref(obj), ) if hr != 0: return None return obj def _toggle_touch_keyboard() -> bool: ole32 = ctypes.windll.ole32 user32 = ctypes.windll.user32 obj = _create_tip_invocation(ole32) if not obj: return False vtable_ptr = ctypes.cast(obj, ctypes.POINTER(ctypes.c_void_p)).contents.value vtable = ctypes.cast(vtable_ptr, ctypes.POINTER(ctypes.c_void_p)) toggle_fn = ctypes.WINFUNCTYPE( ctypes.c_long, ctypes.c_void_p, wintypes.HWND, )(vtable[3]) hr = toggle_fn(obj, user32.GetDesktopWindow()) release_fn = ctypes.WINFUNCTYPE(ctypes.c_ulong, ctypes.c_void_p)(vtable[2]) release_fn(obj) return hr == 0 def show_touch_keyboard() -> bool: """터치 키보드를 화면에 표시한다.""" if sys.platform != "win32": return False ole32 = ctypes.windll.ole32 ole32.CoInitialize(None) try: return _toggle_touch_keyboard() finally: ole32.CoUninitialize() def hide_touch_keyboard() -> bool: """표시 중인 터치 키보드를 숨긴다 (Toggle).""" return show_touch_keyboard()