2026-04-15 12:15:00 +08:00
|
|
|
import os
|
|
|
|
|
import sys
|
2026-04-19 15:53:45 +08:00
|
|
|
from ctypes import c_long, c_longlong, windll
|
|
|
|
|
|
2026-04-15 12:15:00 +08:00
|
|
|
import pythoncom
|
2026-04-19 15:53:45 +08:00
|
|
|
import win32com.client
|
|
|
|
|
|
2026-04-15 12:15:00 +08:00
|
|
|
|
|
|
|
|
class HardwareController:
|
|
|
|
|
_instance = None
|
|
|
|
|
_wyhkm = None
|
|
|
|
|
|
|
|
|
|
def __new__(cls, *args, **kwargs):
|
|
|
|
|
if not cls._instance:
|
|
|
|
|
cls._instance = super(HardwareController, cls).__new__(cls)
|
|
|
|
|
return cls._instance
|
|
|
|
|
|
2026-04-19 15:53:45 +08:00
|
|
|
def _runtime_base_dir(self):
|
|
|
|
|
if getattr(sys, "frozen", False):
|
|
|
|
|
return os.path.dirname(sys.executable)
|
|
|
|
|
return os.path.dirname(os.path.abspath(__file__))
|
|
|
|
|
|
|
|
|
|
def _resolve_resource_path(self, relative_path):
|
|
|
|
|
if os.path.isabs(relative_path):
|
|
|
|
|
return relative_path if os.path.exists(relative_path) else None
|
|
|
|
|
|
|
|
|
|
candidates = []
|
|
|
|
|
if getattr(sys, "frozen", False):
|
|
|
|
|
exe_dir = os.path.dirname(sys.executable)
|
|
|
|
|
if exe_dir:
|
|
|
|
|
candidates.append(os.path.join(exe_dir, relative_path))
|
|
|
|
|
meipass = getattr(sys, "_MEIPASS", "")
|
|
|
|
|
if meipass:
|
|
|
|
|
candidates.append(os.path.join(meipass, relative_path))
|
|
|
|
|
|
|
|
|
|
module_dir = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
|
candidates.append(os.path.join(module_dir, relative_path))
|
|
|
|
|
candidates.append(os.path.abspath(relative_path))
|
|
|
|
|
|
|
|
|
|
seen = set()
|
|
|
|
|
for candidate in candidates:
|
|
|
|
|
candidate = os.path.abspath(candidate)
|
|
|
|
|
if candidate in seen:
|
|
|
|
|
continue
|
|
|
|
|
seen.add(candidate)
|
|
|
|
|
if os.path.exists(candidate):
|
|
|
|
|
return candidate
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
def _log(self, message):
|
|
|
|
|
print(message)
|
|
|
|
|
try:
|
|
|
|
|
log_path = os.path.join(self._runtime_base_dir(), "hardware_control.log")
|
|
|
|
|
with open(log_path, "a", encoding="utf-8") as f:
|
|
|
|
|
f.write(message + "\n")
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
|
2026-04-15 12:15:00 +08:00
|
|
|
def __init__(self, dll_path="ddl/wyhkm.dll"):
|
|
|
|
|
if self._wyhkm:
|
|
|
|
|
return
|
2026-04-19 15:53:45 +08:00
|
|
|
|
|
|
|
|
self.dll_path = self._resolve_resource_path(dll_path)
|
|
|
|
|
if not self.dll_path:
|
|
|
|
|
self._log(f">>> [hardware_control] DLL not found: {dll_path}")
|
2026-04-15 12:15:00 +08:00
|
|
|
return
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
hkmdll = windll.LoadLibrary(self.dll_path)
|
|
|
|
|
hkmdll.DllInstall.argtypes = (c_long, c_longlong)
|
2026-04-19 15:53:45 +08:00
|
|
|
|
2026-04-15 12:15:00 +08:00
|
|
|
if hkmdll.DllInstall(1, 2) < 0:
|
2026-04-19 15:53:45 +08:00
|
|
|
self._log(">>> [hardware_control] DllInstall failed")
|
2026-04-15 12:15:00 +08:00
|
|
|
return
|
|
|
|
|
|
2026-04-19 15:53:45 +08:00
|
|
|
self._log(f">>> [hardware_control] DllInstall ok: {self.dll_path}")
|
|
|
|
|
|
2026-04-15 12:15:00 +08:00
|
|
|
pythoncom.CoInitialize()
|
|
|
|
|
try:
|
|
|
|
|
self._wyhkm = win32com.client.Dispatch("wyp.hkm")
|
|
|
|
|
except Exception as e:
|
2026-04-19 15:53:45 +08:00
|
|
|
self._log(f">>> [hardware_control] COM Dispatch failed: {e}")
|
2026-04-15 12:15:00 +08:00
|
|
|
return
|
2026-04-19 15:53:45 +08:00
|
|
|
|
2026-04-15 12:15:00 +08:00
|
|
|
dev_id = self._wyhkm.SearchDevice(0x2612, 0x1701, 0)
|
|
|
|
|
if dev_id == -1:
|
2026-04-19 15:53:45 +08:00
|
|
|
self._log(">>> [hardware_control] Device not found (Index 0)")
|
|
|
|
|
self._wyhkm = None
|
2026-04-15 12:15:00 +08:00
|
|
|
return
|
|
|
|
|
|
|
|
|
|
if not self._wyhkm.Open(dev_id, 0):
|
2026-04-19 15:53:45 +08:00
|
|
|
self._log(">>> [hardware_control] Open device failed")
|
|
|
|
|
self._wyhkm = None
|
2026-04-15 12:15:00 +08:00
|
|
|
return
|
2026-04-19 15:53:45 +08:00
|
|
|
|
|
|
|
|
self._wyhkm.SetMode(1, 1)
|
|
|
|
|
self._wyhkm.SetMode(2, 1)
|
2026-04-15 12:15:00 +08:00
|
|
|
self._wyhkm.SetKeyInterval(30, 50)
|
|
|
|
|
self._wyhkm.SetMouseInterval(30, 50)
|
|
|
|
|
|
2026-04-19 15:53:45 +08:00
|
|
|
self._log(f">>> [hardware_control] Device opened and initialized: {dev_id}")
|
2026-04-15 12:15:00 +08:00
|
|
|
except Exception as e:
|
2026-04-19 15:53:45 +08:00
|
|
|
self._log(f">>> [hardware_control] Init failed: {e}")
|
2026-04-15 12:15:00 +08:00
|
|
|
self._wyhkm = None
|
|
|
|
|
|
|
|
|
|
def is_available(self):
|
|
|
|
|
if not self._wyhkm:
|
|
|
|
|
return False
|
|
|
|
|
try:
|
|
|
|
|
return self._wyhkm.IsOpen(0)
|
2026-04-19 15:53:45 +08:00
|
|
|
except Exception:
|
2026-04-15 12:15:00 +08:00
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
def delay_rnd(self, min_ms, max_ms):
|
|
|
|
|
if self.is_available():
|
2026-04-19 15:53:45 +08:00
|
|
|
try:
|
|
|
|
|
self._wyhkm.DelayRnd(int(min_ms), int(max_ms))
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
2026-04-15 12:15:00 +08:00
|
|
|
|
|
|
|
|
def key_down(self, key_str):
|
|
|
|
|
if self.is_available():
|
2026-04-19 15:53:45 +08:00
|
|
|
try:
|
|
|
|
|
self._wyhkm.KeyDown(str(key_str).upper())
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
2026-04-15 12:15:00 +08:00
|
|
|
|
|
|
|
|
def key_up(self, key_str):
|
|
|
|
|
if self.is_available():
|
2026-04-19 15:53:45 +08:00
|
|
|
try:
|
|
|
|
|
self._wyhkm.KeyUp(str(key_str).upper())
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
2026-04-15 12:15:00 +08:00
|
|
|
|
|
|
|
|
def key_press(self, key_str):
|
|
|
|
|
if self.is_available():
|
2026-04-19 15:53:45 +08:00
|
|
|
try:
|
|
|
|
|
self._wyhkm.KeyPress(str(key_str).upper())
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
2026-04-15 12:15:00 +08:00
|
|
|
|
2026-04-19 15:53:45 +08:00
|
|
|
def keyDown(self, key_str):
|
|
|
|
|
self.key_down(key_str)
|
|
|
|
|
|
|
|
|
|
def keyUp(self, key_str):
|
|
|
|
|
self.key_up(key_str)
|
|
|
|
|
|
|
|
|
|
def press(self, key_str):
|
|
|
|
|
self.key_press(key_str)
|
2026-04-15 12:15:00 +08:00
|
|
|
|
|
|
|
|
def move_to(self, x, y):
|
|
|
|
|
if self.is_available():
|
2026-04-19 15:53:45 +08:00
|
|
|
try:
|
|
|
|
|
return bool(self._wyhkm.MoveTo(int(x), int(y)))
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self._log(f">>> [hardware_control] MoveTo failed ({x}, {y}): {e}")
|
|
|
|
|
return False
|
2026-04-15 12:15:00 +08:00
|
|
|
|
|
|
|
|
def move_r(self, dx, dy):
|
|
|
|
|
if self.is_available():
|
2026-04-19 15:53:45 +08:00
|
|
|
try:
|
|
|
|
|
return bool(self._wyhkm.MoveR(int(dx), int(dy)))
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self._log(f">>> [hardware_control] MoveR failed ({dx}, {dy}): {e}")
|
|
|
|
|
return False
|
2026-04-15 12:15:00 +08:00
|
|
|
|
2026-04-19 15:53:45 +08:00
|
|
|
def MoveR(self, dx, dy):
|
2026-04-15 12:15:00 +08:00
|
|
|
self.move_r(dx, dy)
|
|
|
|
|
|
|
|
|
|
def left_click(self):
|
|
|
|
|
if self.is_available():
|
2026-04-19 15:53:45 +08:00
|
|
|
try:
|
|
|
|
|
return bool(self._wyhkm.LeftClick())
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self._log(f">>> [hardware_control] LeftClick failed: {e}")
|
|
|
|
|
return False
|
2026-04-15 12:15:00 +08:00
|
|
|
|
|
|
|
|
def right_click(self):
|
|
|
|
|
if self.is_available():
|
2026-04-19 15:53:45 +08:00
|
|
|
try:
|
|
|
|
|
return bool(self._wyhkm.RightClick())
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self._log(f">>> [hardware_control] RightClick failed: {e}")
|
|
|
|
|
return False
|
2026-04-15 12:15:00 +08:00
|
|
|
|
|
|
|
|
def left_down(self):
|
|
|
|
|
if self.is_available():
|
2026-04-19 15:53:45 +08:00
|
|
|
try:
|
|
|
|
|
return bool(self._wyhkm.LeftDown())
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self._log(f">>> [hardware_control] LeftDown failed: {e}")
|
|
|
|
|
return False
|
2026-04-15 12:15:00 +08:00
|
|
|
|
|
|
|
|
def left_up(self):
|
|
|
|
|
if self.is_available():
|
2026-04-19 15:53:45 +08:00
|
|
|
try:
|
|
|
|
|
return bool(self._wyhkm.LeftUp())
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self._log(f">>> [hardware_control] LeftUp failed: {e}")
|
|
|
|
|
return False
|
|
|
|
|
|
2026-04-15 12:15:00 +08:00
|
|
|
|
|
|
|
|
hw_ctrl = HardwareController()
|