import sys import keyboard from PySide6.QtWidgets import QApplication from PySide6.QtCore import QThread, Signal, QTimer, QObject import state from api import fetch_candidates from ui import CandidateWindow # --- 核心修复:信号桥接类 --- class EventBridge(QObject): """负责将子线程的键盘事件安全转发到主线程""" request_refresh_sig = Signal() reset_ui_sig = Signal() # --- 异步请求线程 --- class FetchThread(QThread): result_ready = Signal(list) def __init__(self, text): super().__init__() self.text = text def run(self): # 执行耗时的网络请求 res = fetch_candidates(self.text) self.result_ready.emit(res) class IMEController: def __init__(self): self.app = QApplication(sys.argv) self.window = CandidateWindow() self.bridge = EventBridge() self.worker = None # 防抖定时器:必须在主线程初始化 self.debounce_timer = QTimer() self.debounce_timer.setSingleShot(True) # 信号绑定:确保所有 UI/Timer 操作都在主线程执行 self.bridge.request_refresh_sig.connect(self._handle_refresh_request) self.bridge.reset_ui_sig.connect(self.reset_ime) self.debounce_timer.timeout.connect(self._start_fetch) def on_key_event(self, event): """键盘钩子回调(运行在非主线程)""" if event.event_type == "up": return True name = event.name # 1. 输入字母 if len(name) == 1 and name.isalpha(): state.buffer += name self.bridge.request_refresh_sig.emit() # 安全转发 return False # 2. 退格 elif name == "backspace": if state.buffer: state.buffer = state.buffer[:-1] self.bridge.request_refresh_sig.emit() return False return True # 3. 选择候选词 elif name == "space" or (name in "12345" and state.buffer): if state.candidates: idx = 0 if name == "space" else int(name) - 1 if idx < len(state.candidates): keyboard.write(state.candidates[idx]) self.bridge.reset_ui_sig.emit() return False return True # 4. 确认/取消 elif name in ["enter", "esc"]: if state.buffer: if name == "enter": keyboard.write(state.buffer) self.bridge.reset_ui_sig.emit() return False return True return True def _handle_refresh_request(self): """主线程逻辑:控制定时器""" if not state.buffer: self.reset_ime() return # 只有在主线程调用的 start(150) 才不会报错 self.debounce_timer.start(70) def _start_fetch(self): """主线程逻辑:启动异步拉取词条""" if self.worker and self.worker.isRunning(): self.worker.terminate() self.worker.wait() self.worker = FetchThread(state.buffer) self.worker.result_ready.connect(self._on_data_received) self.worker.start() def _on_data_received(self, candidates): """主线程逻辑:更新UI内容""" state.candidates = candidates if candidates: self.window.update_candidates(candidates) else: self.window.hide() def reset_ime(self): """主线程逻辑:重置状态并隐藏UI""" state.buffer = "" state.candidates = [] self.window.hide() if self.debounce_timer.isActive(): self.debounce_timer.stop() def run(self): # 启动全局钩子 keyboard.hook(self.on_key_event) # 进入 Qt 事件循环 sys.exit(self.app.exec()) if __name__ == "__main__": # 注意:运行此脚本通常需要管理员/Root权限 ime = IMEController() ime.run()