124 lines
3.0 KiB
Python
124 lines
3.0 KiB
Python
"""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()
|