버전 업그레이드

This commit is contained in:
2026-06-18 13:38:27 +09:00
parent a48a4b5fe5
commit ba33a78fec
37 changed files with 3355 additions and 1165 deletions

123
utils/touch_keyboard.py Normal file
View File

@@ -0,0 +1,123 @@
"""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()