Files
work-secretfile-selfcheck/UmiOCR-data/py_src/event_bus/key_mouse/keyboard.py

199 lines
7.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from pynput import keyboard
from PySide2.QtCore import QMutex
from time import time
from umi_log import logger
from ...platform import Platform
from ..pubsub_service import PubSubService
# 按键转换器
class _KeyTranslator:
# 回调函数的 KeyCode 类型 转为键名字符串
@staticmethod
def key2name(key):
return Platform.getKeyName(key)
# 键名字符串 转为KeyCode
@staticmethod
def name2key(char):
if hasattr(keyboard.Key, char): # 控制键返回Code
return getattr(keyboard.Key, char).value
else: # 非控制键返回自己
return char
# 多个键名的字符串 转为集合
@staticmethod
def names2set(char):
return set(char.split("+"))
# 集合转键名
def set2names(keys):
keys = keys.copy()
# 优先级键名
highPriority = ("win", "cmd", "shift", "ctrl", "alt")
names = ""
# 添加高优先级键
for h in highPriority:
if h in keys:
if names != "":
names += "+"
names += h
keys.discard(h)
# 添加剩下的键
for k in keys:
if names != "":
names += "+"
names += k
return names
# 热键控制器类
class _HotkeyController:
# ========================= 【接口】 =========================
# 添加一组快捷键对应触发事件为title。 press 为0时按下触发1抬起触发
def addHotkey(self, keysName, title, press=0):
self._start()
if press != 0 and press != 1:
return f"[Error] press只能为0按下或1抬起不能为 {press}"
keySet = _KeyTranslator.names2set(keysName)
self._hotkeyMutex.lock()
kl = self._hotkeyList[press]
for k in kl: # 检测重复
if k["keySet"] == keySet: # 键集合相同
self._hotkeyMutex.unlock()
msg = "[Success] 注册事件相同的重复快捷键。"
if k["title"] != title: # 事件标题不同
msg = f'[Warning] Registering same hotkey. The existing event for {keysName} is {k["title"]}, new event is {title} .'
return msg
# 加入列表
kl.append({"keySet": keySet, "title": title})
self._hotkeyMutex.unlock()
return "[Success]"
# 移除一组快捷键,传入键名或事件之一
def delHotkey(self, keysName="", title="", press=0):
if press != 0 and press != 1:
logger.error(f"press只能为0按下或1抬起不能为 {press}")
return
keySet = _KeyTranslator.names2set(keysName)
self._hotkeyMutex.lock()
kListOld = self._hotkeyList[press] # 旧列表
kListNew = [] # 新列表
for k in kListOld:
# 忽略键集合相同或标题相同
if (keysName and k["keySet"] == keySet) or (title and title == k["title"]):
pass
# 其余键写入新列表
else:
kListNew.append(k)
self._hotkeyList[press] = kListNew
self._hotkeyMutex.unlock()
# 开始录制快捷键。过程发送事件为runningTitle完毕发送事件为finishTitle
def readHotkey(
self, runningTitle="<<readHotkeyRunning>>", finishTitle="<<readHotkeyFinish>>"
):
self._start()
if self._status == 1:
return (
"[Warning] Recording is running. 当前快捷键录制已在进行,不能同时录制!"
)
self._status = 1
self._readRunningTitle = runningTitle
self._readFinishTitle = finishTitle
return "[Success]"
# ========================= 【实现】 =========================
def __init__(self):
self._listener = None # 监听器
# 热键列表,[0]存放按下触发,[1]存放抬起触发
# 每个元素为:{"keySet":按键集合, "title":事件标题}
self._hotkeyList = [[], []]
self._hotkeyMutex = QMutex() # 热键列表的锁
self._status = 0 # 状态0正常1录制中
self._pressSet = set() # 当前已按下的按键集合
self._strict = True # 键集合相等的判定T为严格F为宽松
self._ttl = 30 # 长按键超时忽略时间,秒
self._ttlDict = {} # 存放当前已按下按键的超时时间
self._readRunningTitle = ""
self._readFinishTitle = ""
# 注册默认监听的热键
self.addHotkey("esc", "<<esc>>")
# 第一次注册热键时,启动监听
def _start(self):
if not self._listener:
self._listener = keyboard.Listener(
on_press=self._onPress, on_release=self._onRelease
)
self._listener.start()
# 按键按下的回调
def _onPress(self, key):
keyName = _KeyTranslator.key2name(key)
# 禁止重复触发
if keyName in self._pressSet:
return
self._checkTTL() # 检查超时
self._pressSet.add(keyName) # 加入集合
self._ttlDict[keyName] = time() + self._ttl # 记录超时时间
if self._status == 0: # 正常运行
self._checkKeyEvent(0, keyName) # 检查按下模式的快捷键
elif self._status == 1: # 录制中
self._readRunning()
# 按键抬起的回调
def _onRelease(self, key):
keyName = _KeyTranslator.key2name(key)
if not keyName in self._pressSet:
return
self._checkTTL() # 检查超时
if self._status == 0: # 正常运行
self._checkKeyEvent(1, keyName) # 检查抬起模式的快捷键
elif self._status == 1: # 录制结束
self._readFinish()
if keyName in self._pressSet:
self._pressSet.discard(keyName) # 从集合中删除
del self._ttlDict[keyName] # 删除超时时间
# 检查并触发按键事件
def _checkKeyEvent(self, press, nowKey):
# 对比每组按键集合。一致触发,则发送事件
if self._strict: # 严格模式,要求完全一致
for k in self._hotkeyList[press]:
if k["keySet"] == self._pressSet:
PubSubService.publish(k["title"])
else: # 宽松模式,只要求当前组合中包含指定按键,且当前按下的按键在指定按键中
for k in self._hotkeyList[press]:
if k["keySet"] <= self._pressSet and nowKey in k["keySet"]:
PubSubService.publish(k["title"])
# 更新录制
def _readRunning(self):
names = _KeyTranslator.set2names(self._pressSet)
PubSubService.publish(self._readRunningTitle, names)
# 录制结束
def _readFinish(self):
self._status = 0
if "esc" in self._pressSet: # 含esc则为退出
PubSubService.publish(self._readFinishTitle, "")
else:
names = _KeyTranslator.set2names(self._pressSet)
PubSubService.publish(self._readFinishTitle, names)
# 检查已按键的超时时间。若超时,则删除该键
def _checkTTL(self):
nowTime = time()
for k in self._pressSet.copy():
if nowTime >= self._ttlDict[k]:
logger.debug(f"忽略超时按键 {k}")
del self._ttlDict[k]
self._pressSet.discard(k)
HotkeyCtrl = _HotkeyController()