Rekipjan's picture
Upload folder using huggingface_hub
06bc836 verified
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()