-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.py
More file actions
274 lines (232 loc) · 10.4 KB
/
main.py
File metadata and controls
274 lines (232 loc) · 10.4 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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
"""KeyMouse主程序
这个模块实现了键盘控制鼠标的核心功能。
典型用法:
python main.py [--gui]
属性:
region_selector_process: 区域选择器子进程
keyboard_listener: 键盘监听器实例
"""
import pynput
import time
import threading
import os
import sys
import argparse
import traceback
import ctypes
import subprocess
import logging
# 导入新的日志管理器和配置加载器
from log_manager import setup_logging, get_log_manager
import config_loader
import json
import tempfile
import queue
from typing import Optional, Set, Dict, Tuple
#特定平台依赖
from win_platform import WinPlatformScroller
import win32con
import win32api
#项目其它模块导入
from scroll_controller import ScrollController
from utool import KEY_TO_VK, parse_keys_string
import modeswitch
import config_loader
from gui import run_gui
from tray_icon import TrayIcon
from modeswitch import AppMode
from app_runtime import AppRuntime
try:
# 初始化配置以获取日志设置
config = config_loader.AppConfig()
# 使用配置文件中的日志设置初始化日志系统
log_config = {
'log_level': config.config_parser.get('Logging', 'log_level', fallback='INFO'),
'log_format': config.config_parser.get('Logging', 'log_format',
fallback='%(asctime)s - [%(levelname)s] - %(message)s'),
'log_file_path': config.config_parser.get('Logging', 'log_file_path',
fallback=os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), 'main.log')),
'log_max_size': config.config_parser.get('Logging', 'log_max_size', fallback='10485760'),
'log_backup_count': config.config_parser.get('Logging', 'log_backup_count', fallback='5'),
'error_log_file': config.config_parser.get('Logging', 'error_log_file', fallback=None)
}
setup_logging(log_config)
except Exception as e:
print(f"初始化日志失败: {e}")
def is_admin() -> bool:
"""检查当前进程是否具有管理员权限"""
try:
return ctypes.windll.shell32.IsUserAnAdmin()
except:
return False
region_selector_process: Optional[subprocess.Popen] = None
if __name__ == "__main__":
"""主程序入口点
这是程序的主入口点,只有当此文件被直接运行时才会执行。
主要功能包括:
1. 检查并请求管理员权限(如果配置要求)
2. 解析命令行参数
3. 初始化日志系统
4. 创建并启动主要的程序组件:
- 鼠标控制器
- 键盘监听器
- 系统托盘图标
- 鼠标移动线程
5. 处理程序退出
参考资料:
- Python文档: https://docs.python.org/3/library/__main__.html
- Google Python风格指南: https://google.github.io/styleguide/pyguide.html#24-main
典型用法:
python main.py [--gui] [--restarted-as-admin]
"""
# 声明全局变量,供restart_program函数使用
global tray, stop_event, keyboard_listener
# 声明在finally块中需要的变量
app = None
try:
logging.info("检查是否为管理员重启...")
if '--restarted-as-admin' not in sys.argv:
logging.info("非管理员重启,正常启动...")
try:
logging.info("正在加载配置...")
config = config_loader.AppConfig()
logging.info("配置加载完成")
except Exception as e:
logging.error(f"加载配置失败: {e}")
ctypes.windll.user32.MessageBoxW(None,
f"无法加载 'config.ini'。\n错误: {e}",
"KeyMouse 致命错误", 0x10)
sys.exit(1)
# 如果配置要求以管理员权限运行,且当前不是管理员权限
if config.RUN_AS_ADMIN and not is_admin():
# 获取Python解释器路径
executable = sys.executable
# 准备命令行参数列表
params = []
# 如果是源码运行(非打包exe),需要添加主程序路径作为第一个参数
if not getattr(sys, 'frozen', False):
params.append(f'"{sys.argv[0]}"')
# 添加所有原始命令行参数
params.extend([f'"{arg}"' for arg in sys.argv[1:]])
# 添加标记表示这是管理员权限重启
params.append('--restarted-as-admin')
# 将所有参数拼接成命令行字符串
params_str = " ".join(params)
try:
# 使用ShellExecuteW以管理员权限重启程序
# runas参数表示以管理员身份运行
# 返回值大于32表示成功启动
rtn = ctypes.windll.shell32.ShellExecuteW(None, "runas",
executable, params_str, None, 1)
# 如果返回值小于等于32,表示启动失败
if rtn <= 32:
# 弹出错误提示框
ctypes.windll.user32.MessageBoxW(None,
f"请求管理员权限失败。\n错误代码: {rtn}",
"KeyMouse 错误", 0x10)
# 退出当前进程
sys.exit(0)
except Exception as e:
# 捕获其他可能的异常并显示错误信息
ctypes.windll.user32.MessageBoxW(None,
f"尝试以管理员身份重启时发生意外错误。\n错误: {e}",
"KeyMouse 错误", 0x10)
sys.exit(1)
# 创建命令行参数解析器,用于解析启动参数
parser = argparse.ArgumentParser(description="KeyMouse")
# 添加--gui/-g 参数,用于启动图形界面模式
parser.add_argument("--gui", "-g", action="store_true")
# 添加一个隐藏的参数,用于标记程序是否以管理员权限重启
parser.add_argument('--restarted-as-admin', action='store_true',
help=argparse.SUPPRESS)
# 解析命令行参数
args = parser.parse_args()
logging.info(f"解析的参数: args.gui = {args.gui}")
# 初始化tray变量,供GUI使用
tray = None
if args.gui:
try:
logging.info("启动GUI模式...")
run_gui(tray_icon=tray, app_config=config)
logging.info("GUI模式启动完成,程序退出")
sys.exit(0)
except Exception as e:
logging.error(f"无法启动GUI: {e}", exc_info=True)
sys.exit(1)
config = config_loader.AppConfig()
log_file_path = os.path.join(config_loader.get_base_path(), 'main.log')
# 创建 AppRuntime 实例
logging.info("创建 AppRuntime 实例...")
app = AppRuntime(config)
logging.info("AppRuntime 实例创建完成")
keyboard_listener = pynput.keyboard.Listener(
on_press=app.event_handler.on_press,
on_release=app.event_handler.on_release,
win32_event_filter=app.event_handler.win32_event_filter
)
# 设置EventHandler的键盘监听器引用,用于调用suppress_event()
app.event_handler.set_keyboard_listener(keyboard_listener)
# 设置托盘图标的键盘监听器引用
app.tray_icon.listener = keyboard_listener
# 启动键盘监听器
keyboard_listener.start()
# 移除所有已存在的日志处理器,避免重复添加
for handler in logging.root.handlers[:]:
logging.root.removeHandler(handler)
# 重新配置日志系统 - 仅输出到文件,不输出到控制台
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - [%(levelname)s] - %(message)s',
handlers=[
logging.FileHandler(log_file_path, 'w', 'utf-8') # 仅写入文件,移除控制台输出
]
)
if is_admin():
logging.info("程序已在 [管理员权限] 下运行。")
# AppRuntime 已在第799行创建,包含所有组件的实例化
try:
# 启动 AppRuntime 的所有组件
logging.info("启动 AppRuntime 组件...")
app.start()
logging.info("AppRuntime 组件启动完成")
# 运行托盘图标的主循环(这将阻塞主线程)
logging.info("启动托盘图标...")
app.tray_icon.run()
logging.info("托盘图标运行结束")
# 主循环,等待停止事件
logging.info("进入主循环...")
loop_count = 0
while not app.stop_event.is_set():
time.sleep(1)
loop_count += 1
if loop_count % 10 == 0: # 每10秒记录一次状态
logging.info(f"主循环运行中,已运行 {loop_count} 秒")
logging.info(f"主循环结束,stop_event已被触发,共运行 {loop_count} 秒")
except Exception as e:
logging.error(f"程序遇到致命错误: {e}", exc_info=True)
ctypes.windll.user32.MessageBoxW(None,
f"程序遇到致命错误。\n请查看 main.log。\n\n错误: {e}",
"KeyMouse 致命错误", 0x10)
traceback.print_exc()
finally:
# 确保程序优雅退出
logging.info(">>> 开始程序清理和退出流程")
try:
if 'keyboard_listener' in locals() and keyboard_listener.running:
logging.info("正在停止键盘监听器...")
keyboard_listener.stop()
logging.info("键盘监听器已停止")
else:
logging.info("键盘监听器不存在或已停止")
except Exception as e:
logging.error(f"停止键盘监听器时发生异常: {e}", exc_info=True)
try:
if app is not None:
logging.info("正在停止 AppRuntime...")
app.stop()
logging.info("AppRuntime 已停止")
else:
logging.info("AppRuntime 不存在")
except Exception as e:
logging.error(f"停止 AppRuntime 时发生异常: {e}", exc_info=True)
logging.info(">>> 程序已安全退出")