-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathkey_mapping_mode.py
More file actions
174 lines (149 loc) · 7.39 KB
/
key_mapping_mode.py
File metadata and controls
174 lines (149 loc) · 7.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# key_mapping_mode.py
from pynput import keyboard
from typing import Dict, Any, Callable, Optional
from config_loader import AppConfig # Import AppConfig
class KeyMappingMode:
"""
按键映射模式管理类。
负责在按键映射模式下监听键盘事件,根据用户配置进行按键映射、热键触发和按键屏蔽。
"""
_key_map_str_to_pynput = {
"ctrl": keyboard.Key.ctrl, "alt": keyboard.Key.alt, "shift": keyboard.Key.shift, "win": keyboard.Key.cmd,
"esc": keyboard.Key.esc, "tab": keyboard.Key.tab, "enter": keyboard.Key.enter, "space": keyboard.Key.space,
"backspace": keyboard.Key.backspace, "delete": keyboard.Key.delete,
"f1": keyboard.Key.f1, "f2": keyboard.Key.f2, "f3": keyboard.Key.f3, "f4": keyboard.Key.f4,
"f5": keyboard.Key.f5, "f6": keyboard.Key.f6, "f7": keyboard.Key.f7, "f8": keyboard.Key.f8,
"f9": keyboard.Key.f9, "f10": keyboard.Key.f10, "f11": keyboard.Key.f11, "f12": keyboard.Key.f12,
}
# DEL: [Refactor] Changed config_path to app_config for direct access to loaded config.
# def __init__(self, config_path: str, on_mode_exit: Callable[[], None]):
def __init__(self, app_config: AppConfig, on_mode_exit: Callable[[], None]):
"""
初始化按键映射模式。
Args:
app_config: 应用程序的配置对象。
on_mode_exit: 当按键映射模式退出时调用的回调函数。
"""
self.app_config = app_config
self.on_mode_exit = on_mode_exit
self.key_mappings: Dict[str, str] = {}
self.blocked_keys: set = set() # Currently not used, but kept for future expansion
self.listener: Optional[keyboard.Listener] = None
self._load_key_mappings()
def _load_key_mappings(self):
"""
从 AppConfig 加载按键映射。
"""
print("从 AppConfig 加载按键映射...")
self.key_mappings = self.app_config.command_macros
# self.blocked_keys = self.app_config.blocked_keys # Blocked keys are not part of CommandModeMacros
print(f"加载映射: {self.key_mappings}")
# DEL: [Refactor] Removed _save_key_mappings as AppConfig will handle saving to config.ini.
# def _save_key_mappings(self):
# """
# 保存当前的按键映射和屏蔽列表到配置文件。
# """
# data = {
# 'mappings': self.key_mappings,
# 'blocked_keys': list(self.blocked_keys)
# }
# try:
# with open(self.config_path, 'w', encoding='utf-8') as f:
# json.dump(data, f, ensure_ascii=False, indent=4)
# print(f"按键映射配置已保存到 {self.config_path}。")
# except IOError as e:
# print(f"错误:保存按键映射配置文件 {self.config_path} 失败:{e}")
def _parse_hotkey_string(self, hotkey_str: str):
"""
解析热键字符串,例如 "<ctrl>+a" 或 "<win>+d"。
返回 (修饰键列表, 主键)。
"""
parts = [p.strip() for p in hotkey_str.lower().split('+')]
modifier_keys = []
main_key = None
for part in parts:
key_name = part
if part.startswith('<') and part.endswith('>'):
key_name = part[1:-1]
key_obj = self._key_map_str_to_pynput.get(key_name)
if key_obj:
# 明确的修饰键
if key_obj in {keyboard.Key.ctrl, keyboard.Key.alt, keyboard.Key.shift, keyboard.Key.cmd}:
modifier_keys.append(key_obj)
# 其他特殊键(如f1, tab)作为主键
elif main_key is None:
main_key = key_obj
else:
print(f"警告: 热键 '{hotkey_str}' 包含多个主键 ('{main_key}', '{key_obj}')")
elif len(key_name) == 1:
if main_key is None:
main_key = key_name
else:
print(f"警告: 热键 '{hotkey_str}' 包含多个主键 ('{main_key}', '{key_name}')")
else:
print(f"警告: 无法解析热键 '{hotkey_str}' 中的部分 '{part}'")
if main_key is None:
print(f"警告: 热键 '{hotkey_str}' 缺少有效的主键。")
return [], None
return modifier_keys, main_key
def _get_key_str(self, key) -> Optional[str]:
"""获取按键的规范化字符串表示。"""
if hasattr(key, 'char') and key.char:
return key.char.lower()
if hasattr(key, 'name'):
return key.name.replace('_l', '').replace('_r', '').lower()
return None
def _on_press(self, key):
"""
键盘按下事件处理函数。
重要: 返回 False 会停止监听器。对于需要屏蔽的按键,应该不返回值 (None)。
对于需要传递的按键,应该返回 True。
"""
try:
key_str = self._get_key_str(key)
if not key_str:
return True # 传递未知按键
# 1. 检查是否是退出模式的组合键 (硬编码为ESC,后续可配置)
if key == keyboard.Key.esc:
print("检测到ESC键,退出按键映射模式。")
self.on_mode_exit()
return False # 正确:停止监听器
# 2. 检查是否需要屏蔽
if key_str in self.blocked_keys:
print(f"按键 '{key_str}' 已被屏蔽。")
return # 正确:屏蔽按键,但不停止监听器
# 3. 检查是否有映射
if key_str in self.key_mappings:
target_hotkey_str = self.key_mappings[key_str]
print(f"按键 '{key_str}' 映射到热键:{target_hotkey_str}")
modifier_keys, main_key = self._parse_hotkey_string(target_hotkey_str)
if main_key:
try:
keyboard_controller = keyboard.Controller()
with keyboard_controller.pressed(*modifier_keys):
keyboard_controller.tap(main_key)
except Exception as e:
print(f"错误: 模拟热键 '{target_hotkey_str}' 失败: {e}")
return # 屏蔽原始按键
except Exception as e:
print(f"按键映射模式处理按键事件时发生错误:{e}")
return True # 默认:传递按键
def _on_release(self, key):
"""处理按键释放事件以确保完全屏蔽。"""
key_str = self._get_key_str(key)
if key_str and (key_str in self.key_mappings or key_str in self.blocked_keys):
return # 屏蔽已映射或已屏蔽按键的释放事件
return True
def activate(self):
"""激活按键映射模式。"""
if self.listener is None or not self.listener.running:
# suppress=True 是实现按键屏蔽的关键
self.listener = keyboard.Listener(on_press=self._on_press, on_release=self._on_release, suppress=True)
self.listener.start()
print("按键映射模式已激活,键盘监听器已启动。")
def deactivate(self):
"""去激活按键映射模式。"""
if self.listener is not None and self.listener.running:
self.listener.stop()
self.listener = None
print("按键映射模式已去激活,键盘监听器已停止。")