Zhl 锁屏软件
重磅推出!带有人脸识别的锁屏软件。
你可曾因出去上厕所惨遭机房惨案?你可曾因密码被别人偷看而 AKIOI?你可曾在一顿饭的功夫做出
::::info[不可抗因素背景]
-
本机房因为极域和系统锁屏的冲突,系统锁屏被物理禁止了。
-
系统自带的人像识别需要支持 Windows Hello 的红外摄像头,很多电脑都没有。
这是一款开源、免费且实用的工具,支持密码与刷脸双重识别,同时提供背景自定义及透明设计,并可在锁屏界面上显示时间。有需要的朋友,欢迎线下领取。
Github 地址。
注意:face_detection_yunet_2023mar.onnx 和 face_recognition_sface_2021dec.onnx 这两个库需要自己下,文件有点大,放不进 Github。
制作耗时不知道多少小时,点个赞吧~。
不知道是不是洛谷首发。
项目借助 DeepSeek 生成。
Tips
-
这是开源项目,代码如果不符合你的喜好,可以自己改(显然可以用 AI)。
-
人像识别如果你觉得不准确,可以考虑好好拍照(可以多拍点),实在不行把那个相似度的线降低(
recognize2.exe就是这样的,但是我还没放)。
1. get.py – 人脸照片采集器
-
使用 YuNet 检测人脸,实时显示摄像头画面。
-
手动(空格)或自动(每 2 秒)保存检测到的人脸区域,存入 ref_images/ 文件夹。
-
用于提前录入用户的多角度、多表情人脸样本,供后续识别时生成平均特征。
2. recognize.py – 人脸识别与活体检测(点头)
- 加载 YuNet 检测器和 SFace 特征提取器,计算
ref_images中所有人脸的平均特征(归一化后作为参考)。 - 主程序后台运行,通过 Windows 钩子监控鼠标移动:一旦鼠标被移动,立即启动 30 秒识别窗口。
- 识别流程:摄像头持续检测人脸,同时分析头部 俯仰角(pitch) 变化来判断是否“点头”(要求俯仰差
≥10° 且宽高比变化小)。 - 点头成功后再连续
4 帧验证人脸特征余弦相似度≥0.7 ,全部通过后执行:- 调用
kill.exe杀死lockscreen.exe(解锁)。 - 切换输入法至搜狗/微软拼音(推测用于恢复输入环境)。
- 调用
3. main.py – 配置界面(系统托盘程序)
- 基于
customtkinter的图形界面,提供三个选项卡:- 修改密码:旧密码验证后更新哈希密码(保存在
log/pass)。 - 壁纸与样式:选择锁屏壁纸、开启透明背景、是否显示时间。
- 关于:版本信息。
- 修改密码:旧密码验证后更新哈希密码(保存在
- 设置保存为
log/settings.json,支持浅色/深色/自动主题。 - 程序启动后在系统托盘驻留,提供“启动锁屏”“显示窗口”“退出”菜单。
4. lockscreen.py – 锁屏执行程序
- 以管理员权限运行,创建全屏置顶窗口,拦截所有键盘鼠标输入(低层钩子)。
- 支持自定义壁纸、透明背景、锁屏显示当前时间。
- 按下
ESC键弹出密码输入对话框(输入法强制为美式英语,禁止右键菜单)。 - 密码验证成功则退出锁屏;失败时对话框抖动并清空输入。
- 同时每隔 30 秒重载
settings.json,实现壁纸、时间、透明模式的动态更新。
5. kill.cpp – 解锁辅助程序
- 遍历系统进程,找到
lockscreen.exe并强制终止。 - 供
recognize.py识别成功后调用,解除锁屏状态。
6. start.cpp – 全局热键启动器
- 隐藏控制台窗口,安装低级键盘钩子。
- 监听
Win + A组合键(冷却时间 10 秒),触发时依次启动lockscreen.exe和recognize.exe。 - 实现一键锁屏并开启人脸识别验证。
整体工作流
- 管理员运行
start.exe(后台热键监听)。 - 按
Win + A→ 启动锁屏(lockscreen.exe)和识别程序(recognize.exe)。 - 锁屏界面出现,鼠标键盘被拦截;识别程序后台等待鼠标移动。
- 用户移动鼠标 → 识别程序打开摄像头,要求点头 + 人脸比对。
- 识别成功 → 自动终止
lockscreen.exe并切换输入法,完成解锁。 - 若识别失败,30 秒后自动关闭摄像头,继续等待下一次鼠标移动。
目前暂时没放编译好的文件,下面是代码,可以随便看看,等我找到合适的云盘就把编译好的 exe 放进去。
下面的代码可供爱好者参考。
::::info[get.py]
import cv2
import os
import time
import sys
save_dir = "ref_images"
os.makedirs(save_dir, exist_ok=True)
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
if not cap.isOpened():
print("[ERROR] 无法打开摄像头")
sys.exit(1)
# ------------------------ 加载 YuNet 人脸检测模型 ------------------------
YUNET_MODEL = "model/face_detection_yunet_2023mar.onnx"
if not os.path.exists(YUNET_MODEL):
print(f"[ERROR] 未找到 YuNet 模型: {YUNET_MODEL}")
print("请确保 model/face_detection_yunet_2023mar.onnx 存在")
sys.exit(1)
detector = cv2.FaceDetectorYN.create(YUNET_MODEL, "", (320, 320))
print("[INFO] YuNet 人脸检测器加载成功")
print("=" * 50)
print("【智能训练照片采集器】")
print("功能:自动检测人脸并保存,支持手动/自动抓拍")
print("操作:")
print(" - 保持人脸在绿色框内")
print(" - 按 SPACE 手动抓拍")
print(" - 自动模式:每2秒保存一张(需检测到人脸)")
print(" - 按 ESC 退出")
print("提示:请采集不同微角度(左/右偏<30°)、不同表情(微笑/严肃)")
print(" 确保光线均匀,面部清晰,背景简单")
print("=" * 50)
count = 0
last_cap_time = 0
while True:
ret, frame = cap.read()
if not ret:
continue
h, w = frame.shape[:2]
detector.setInputSize((w, h))
result = detector.detect(frame)
faces = []
if result[1] is not None:
# 将 YuNet 输出的 face 转成 (x, y, w, h) 矩形框
for face in result[1]:
x, y, width, height = face[0:4].astype(int)
faces.append((x, y, width, height))
cv2.rectangle(frame, (x, y), (x+width, y+height), (0, 255, 0), 2)
# 显示信息
cv2.putText(frame, f"Saved: {count}", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2)
cv2.putText(frame, "SPACE: save | ESC: quit", (10, 65),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
cv2.imshow("Train Image Collector", frame)
key = cv2.waitKey(1) & 0xFF
now = time.time()
if len(faces) > 0:
# 手动抓拍或自动抓拍
if key == 32 or (now - last_cap_time >= 2):
# 取面积最大的人脸
x, y, w, h = max(faces, key=lambda r: r[2]*r[3])
face_img = frame[y:y+h, x:x+w]
# 保存
filename = os.path.join(save_dir, f"me_{count:04d}.jpg")
cv2.imwrite(filename, face_img)
print(f"已保存: {filename}")
count += 1
last_cap_time = now
if key == 27: # ESC
break
cap.release()
cv2.destroyAllWindows()
print(f"\n共采集 {count} 张照片,保存在 '{save_dir}' 文件夹。")
print("现在请重新运行识别程序。")
::::
::::info[recognize.py]
import cv2
import numpy as np
import time
import os
import sys
import subprocess
import ctypes
from ctypes import wintypes
from collections import deque
# ------------------------ 模型路径 ------------------------
YUNET_MODEL = "model/face_detection_yunet_2023mar.onnx"
SFACE_MODEL = "model/face_recognition_sface_2021dec.onnx"
if not os.path.exists(YUNET_MODEL):
print(f"[ERROR] 缺少 Yunet 模型: {YUNET_MODEL}")
sys.exit(1)
if not os.path.exists(SFACE_MODEL):
print(f"[ERROR] 缺少 SFace 模型: {SFACE_MODEL}")
sys.exit(1)
# ------------------------ 初始化检测器与特征提取器 ------------------------
detector = cv2.FaceDetectorYN.create(YUNET_MODEL, "", (320, 320))
feature_extractor = cv2.FaceRecognizerSF.create(SFACE_MODEL, "")
print("[INFO] YuNet 检测器 + SFace 特征模型加载成功")
# ------------------------ 人脸对齐函数 ------------------------
def align_face(img, face):
std_points = np.array([
[30.2946, 51.6963], # 左眼
[65.5318, 51.6963], # 右眼
[48.0252, 71.7366], # 鼻尖
[33.5493, 92.3655], # 左嘴角
[62.7299, 92.3655] # 右嘴角
], dtype=np.float32)
landmarks = face[4:14].reshape(5, 2).astype(np.float32)
src_points = np.array([
landmarks[0], # 左眼
landmarks[1], # 右眼
landmarks[2], # 鼻尖
landmarks[4], # 左嘴角
landmarks[3] # 右嘴角
])
M, _ = cv2.estimateAffine2D(src_points, std_points)
aligned = cv2.warpAffine(img, M, (112, 112))
return aligned
# ------------------------ 加载参考人脸特征 ------------------------
def get_ref_feat():
features = []
if os.path.exists("my_photo.jpg"):
img = cv2.imread("my_photo.jpg")
if img is not None:
h, w = img.shape[:2]
detector.setInputSize((w, h))
result = detector.detect(img)
if result[1] is not None and result[1].shape[0] > 0:
face = result[1][0]
aligned = align_face(img, face)
feat = feature_extractor.feature(aligned).ravel()
features.append(feat)
print("[INFO] 已添加 my_photo.jpg 特征")
ref_dir = "ref_images"
if os.path.exists(ref_dir):
for f in sorted(os.listdir(ref_dir)):
if f.lower().endswith(('.jpg', '.jpeg', '.png')):
img_path = os.path.join(ref_dir, f)
img = cv2.imread(img_path)
if img is None: continue
h, w = img.shape[:2]
detector.setInputSize((w, h))
result = detector.detect(img)
if result[1] is not None and result[1].shape[0] > 0:
face = result[1][0]
aligned = align_face(img, face)
feat = feature_extractor.feature(aligned).ravel()
features.append(feat)
if not features:
return None
avg_feat = np.mean(features, axis=0)
norm = np.linalg.norm(avg_feat)
if norm > 0:
avg_feat = avg_feat / norm
avg_feat = avg_feat.reshape(1, -1)
print(f"[INFO] 使用 {len(features)} 张照片的平均特征")
return avg_feat
ref_feat = get_ref_feat()
if ref_feat is None:
print("[ERROR] 未找到参考人脸")
sys.exit(1)
THRESHOLD = 0.7
# ------------------------ 点头检测参数 ------------------------
NOD_WINDOW_SEC = 2.0
NOD_DIFF_THRESH = 10.0
ASPECT_RATIO_CHANGE_MAX = 0.1
# ------------------------ 鼠标移动检测(低CPU轮询)-----------------------
user32 = ctypes.windll.user32
point = wintypes.POINT()
last_x, last_y = -1, -1
def mouse_moved():
global last_x, last_y
user32.GetCursorPos(ctypes.byref(point))
if last_x == -1 and last_y == -1:
last_x, last_y = point.x, point.y
return False
moved = (point.x != last_x) or (point.y != last_y)
last_x, last_y = point.x, point.y
return moved
# ------------------------ 执行 kill.exe ------------------------
def run_kill_exe():
# 获取当前脚本所在目录
base_dir = os.path.dirname(os.path.abspath(__file__))
kill_path = os.path.join(base_dir, "kill.exe")
if not os.path.exists(kill_path):
kill_path = "kill.exe" # 尝试当前路径
try:
subprocess.run(kill_path, check=False, shell=False)
print("[INFO] kill.exe 已执行")
return True
except Exception as e:
print(f"[ERROR] 执行 kill.exe 失败: {e}")
return False
# ------------------------ 输入法切换 ------------------------
def switch_to_sogou_or_ms_pinyin():
"""
优先切换至搜狗输入法,如果不存在则切换至微软拼音
"""
def enum_keyboard_layouts():
num = user32.GetKeyboardLayoutList(0, None)
if num == 0:
return []
hkls = (wintypes.HKL * num)()
user32.GetKeyboardLayoutList(num, hkls)
return list(hkls)
layouts = enum_keyboard_layouts()
for hkl in layouts:
name_buf = ctypes.create_unicode_buffer(9)
if user32.GetKeyboardLayoutNameW(name_buf):
if "sogou" in name_buf.value.lower():
user32.ActivateKeyboardLayout(hkl, 0)
print("[INFO] 已切换至搜狗输入法")
return True
# 没有搜狗,切换到微软拼音
ms_hkl = user32.LoadKeyboardLayoutW("00000804", 1)
if ms_hkl:
user32.ActivateKeyboardLayout(ms_hkl, 0)
print("[INFO] 已切换至微软拼音输入法")
return True
else:
print("[WARN] 无法切换输入法")
return False
# ------------------------ 30秒识别窗口 ------------------------
def run_recognition_window():
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
if not cap.isOpened():
print("[ERROR] 无法打开摄像头")
return False
start_time = time.time()
detect_end_time = start_time + 30
print(f"[INFO] 识别窗口已开启,持续30秒(至 {time.strftime('%H:%M:%S', time.localtime(detect_end_time))})")
print("[INFO] 请面对摄像头,做出点头动作(低头再抬头)")
history = deque(maxlen=300)
verify_frames_left = 0
try:
while time.time() < detect_end_time:
ret, frame = cap.read()
if not ret:
time.sleep(0.05)
continue
current_time = time.time()
h, w = frame.shape[:2]
detector.setInputSize((w, h))
_, faces = detector.detect(frame)
pitch = None
aspect_ratio = None
face = None
if faces is not None:
face = faces[0]
bbox = face[:4].astype(int)
x, y, w_face, h_face = bbox
aspect_ratio = w_face / h_face
pitch = face[5]
history.append((current_time, pitch, aspect_ratio))
while history and history[0][0] < current_time - NOD_WINDOW_SEC:
history.popleft()
# 点头检测
if verify_frames_left == 0 and len(history) >= 5:
pitches = [p for _, p, _ in history]
aspects = [a for _, _, a in history]
pitch_diff = max(pitches) - min(pitches)
aspect_change = max(aspects) - min(aspects)
if pitch_diff >= NOD_DIFF_THRESH and aspect_change <= ASPECT_RATIO_CHANGE_MAX:
print(f"[{time.strftime('%H:%M:%S')}] 检测到点头 (pitch差值={pitch_diff:.1f})")
verify_frames_left = 4
# 验证阶段
if verify_frames_left > 0 and face is not None:
aligned = align_face(frame, face)
feat = feature_extractor.feature(aligned)
sim = feature_extractor.match(ref_feat, feat, cv2.FaceRecognizerSF_FR_COSINE)
print(f"[{time.strftime('%H:%M:%S')}] 验证剩余{verify_frames_left}帧,相似度={sim:.3f}")
if sim >= THRESHOLD:
print(f"[{time.strftime('%H:%M:%S')}] 识别成功!相似度 {sim:.3f} ≥ 0.7")
return True
verify_frames_left -= 1
if verify_frames_left == 0:
print("[INFO] 4帧验证结束,未达标,继续等待点头...")
elif verify_frames_left > 0 and face is None:
print(f"[{time.strftime('%H:%M:%S')}] 验证帧剩余{verify_frames_left},未检测到人脸,跳过")
verify_frames_left -= 1
time.sleep(0.01)
finally:
cap.release()
return False
# ------------------------ 主循环 ------------------------
def main():
print("[INFO] 程序已启动,移动鼠标将触发30秒人脸识别窗口")
print("[INFO] 识别成功后先执行 kill.exe,再切换输入法,然后退出")
print("[INFO] 按 Ctrl+C 退出程序")
while True:
try:
if mouse_moved():
print(f"[{time.strftime('%H:%M:%S')}] 检测到鼠标移动,启动识别窗口")
success = run_recognition_window()
if success:
# 1. 执行 kill.exe
run_kill_exe()
# 2. 切换输入法
switch_to_sogou_or_ms_pinyin()
print("[INFO] 任务完成,程序退出")
break
else:
print(f"[{time.strftime('%H:%M:%S')}] 30秒内未识别到目标人脸,回到空闲状态,等待下次鼠标移动")
else:
time.sleep(0.05)
except Exception as e:
print(f"[ERROR] 主循环异常: {e}")
time.sleep(0.5)
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("\n[INFO] 用户中断,程序退出")
finally:
cv2.destroyAllWindows()
::::
::::info[main.py]
# config.py - 最终稳定版 | 托盘图标 | 4:3 | 自动配色 | @Zhl2010
import os
import sys
import json
import hashlib
import subprocess
import threading
import customtkinter as ctk
from tkinter import filedialog
from PIL import Image, ImageDraw
# 确保 pystray 已安装
try:
import pystray
except ImportError:
print("请先安装 pystray: pip install pystray")
sys.exit(1)
# ------------------------------- 路径与目录 -------------------------------
LOG_DIR = "log"
PASSWORD_FILE = os.path.join(LOG_DIR, "pass")
SETTINGS_FILE = os.path.join(LOG_DIR, "settings.json")
ICON_FILE = os.path.join(LOG_DIR, "icon.png")
if not os.path.exists(LOG_DIR):
os.makedirs(LOG_DIR)
# ------------------------------- 密码管理器 -------------------------------
class PasswordManager:
PASSWORD_FILE = PASSWORD_FILE
SALT = b"lockscreen_secure_salt"
@classmethod
def _hash(cls, pwd):
h = hashlib.sha256()
h.update(cls.SALT)
h.update(pwd.encode('utf-8'))
return h.hexdigest()
@classmethod
def initialize(cls):
if not os.path.exists(cls.PASSWORD_FILE):
cls.save_password("123456")
print("初始密码已设为 123456")
@classmethod
def save_password(cls, new_pwd):
with open(cls.PASSWORD_FILE, "w") as f:
f.write(cls._hash(new_pwd))
@classmethod
def verify(cls, pwd):
if not os.path.exists(cls.PASSWORD_FILE):
cls.initialize()
try:
with open(cls.PASSWORD_FILE, "r") as f:
stored = f.read().strip()
return stored == cls._hash(pwd)
except:
return False
# ------------------------------- 设置管理器 -------------------------------
class SettingsManager:
SETTINGS_FILE = SETTINGS_FILE
@classmethod
def load(cls):
if os.path.exists(cls.SETTINGS_FILE):
try:
with open(cls.SETTINGS_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
except:
pass
return {"wallpaper": "", "show_time": False, "transparent_bg": False, "theme": "auto"}
@classmethod
def save(cls, settings):
with open(cls.SETTINGS_FILE, 'w', encoding='utf-8') as f:
json.dump(settings, f, indent=4, ensure_ascii=False)
# ------------------------------- 主应用程序 -------------------------------
class ConfigApp(ctk.CTk):
def __init__(self):
super().__init__()
self.title("锁屏设置") # 修改标题
self.geometry("800x600") # 4:3
self.resizable(False, False)
self.center_window()
self.attributes('-alpha', 0.97)
ctk.set_default_color_theme("green") # 绿色主题,自动适配深浅色
PasswordManager.initialize()
self.settings = SettingsManager.load()
theme = self.settings.get("theme", "auto")
self._apply_theme(theme)
self._notify_after_id = None
# 主容器 (透明背景,让主题完全控制)
self.main_frame = ctk.CTkFrame(self, corner_radius=28, fg_color="transparent")
self.main_frame.pack(fill="both", expand=True, padx=16, pady=16)
# 顶部栏
top_bar = ctk.CTkFrame(self.main_frame, fg_color="transparent")
top_bar.pack(fill="x", pady=(0, 12))
title_lab = ctk.CTkLabel(top_bar, text="锁屏设置", font=ctk.CTkFont(size=26, weight="bold"))
title_lab.pack(side="left", padx=10)
self.theme_var = ctk.StringVar(value=self._theme_to_display(theme))
theme_seg = ctk.CTkSegmentedButton(
top_bar, values=["浅色", "深色", "自动"],
variable=self.theme_var, command=self._change_theme,
font=ctk.CTkFont(size=12), width=170, corner_radius=18
)
theme_seg.pack(side="right", padx=10)
# 分段选择器
self.seg_btn = ctk.CTkSegmentedButton(
self.main_frame, values=["修改密码", "壁纸与样式", "关于"],
command=self._switch_tab,
font=ctk.CTkFont(size=14),
corner_radius=24
)
self.seg_btn.pack(pady=(6, 18), padx=20, fill="x")
self.seg_btn.set("修改密码")
# 卡片容器
self.card_container = ctk.CTkFrame(self.main_frame, fg_color="transparent")
self.card_container.pack(fill="both", expand=True, padx=12, pady=(0, 12))
self.card_pw = ctk.CTkFrame(self.card_container, corner_radius=22)
self.card_wall = ctk.CTkFrame(self.card_container, corner_radius=22)
self.card_about = ctk.CTkFrame(self.card_container, corner_radius=22)
for card in (self.card_pw, self.card_wall, self.card_about):
card.pack(fill="both", expand=True)
self._build_password_ui()
self._build_wallpaper_ui()
self._build_about_ui()
self._switch_tab("修改密码")
# 底部通知栏
self.notify_label = ctk.CTkLabel(self.main_frame, text="", font=ctk.CTkFont(size=12), corner_radius=14, padx=14, pady=6)
self.notify_label.pack(pady=(0, 10))
self.notify_label.pack_forget()
# 延迟启动托盘线程 (确保图标出现)
self.after(100, self._start_tray_thread)
self.protocol("WM_DELETE_WINDOW", self.hide_window)
self.after(50, self._bring_to_front)
self.after(200, self._bring_to_front)
def _apply_theme(self, mode):
if mode == "light":
ctk.set_appearance_mode("light")
elif mode == "dark":
ctk.set_appearance_mode("dark")
else:
ctk.set_appearance_mode("system")
def _theme_to_display(self, mode):
mapping = {"light": "浅色", "dark": "深色", "system": "自动"}
return mapping.get(mode, "自动")
def center_window(self):
self.update_idletasks()
sw, sh = self.winfo_screenwidth(), self.winfo_screenheight()
self.geometry(f"800x600+{sw//2-400}+{sh//2-300}")
def _bring_to_front(self):
self.lift()
self.focus_force()
self.attributes('-topmost', True)
self.after(200, lambda: self.attributes('-topmost', False))
def hide_window(self):
self.withdraw()
def show_window(self):
self.deiconify()
self._bring_to_front()
def _change_theme(self, choice):
mapping = {"浅色": "light", "深色": "dark", "自动": "system"}
mode = mapping[choice]
self._apply_theme(mode)
self.settings["theme"] = mode
SettingsManager.save(self.settings)
self._show_notification(f"主题已切换为{choice}")
def _show_notification(self, msg, is_err=False):
if self._notify_after_id:
self.after_cancel(self._notify_after_id)
color = "#E67E22" if is_err else "#2E8B57"
self.notify_label.configure(text=f"• {msg}", fg_color=color, text_color="white")
self.notify_label.pack(pady=(0, 10))
self._notify_after_id = self.after(2500, self._hide_notification)
def _hide_notification(self):
self.notify_label.pack_forget()
self._notify_after_id = None
def _switch_tab(self, tab_name):
for card in (self.card_pw, self.card_wall, self.card_about):
card.pack_forget()
if tab_name == "修改密码":
self.card_pw.pack(fill="both", expand=True)
elif tab_name == "壁纸与样式":
self.card_wall.pack(fill="both", expand=True)
else:
self.card_about.pack(fill="both", expand=True)
# ==================== 修改密码页面 ====================
def _build_password_ui(self):
self.card_pw.grid_columnconfigure(0, weight=1)
self.card_pw.grid_columnconfigure(1, weight=1)
ctk.CTkLabel(self.card_pw, text="原密码", font=ctk.CTkFont(size=15)).grid(row=0, column=0, padx=10, pady=(45,20), sticky='e')
self.old_entry = ctk.CTkEntry(self.card_pw, show="*", width=320, corner_radius=18, border_width=1)
self.old_entry.grid(row=0, column=1, padx=10, pady=(45,20), sticky='w')
ctk.CTkLabel(self.card_pw, text="新密码", font=ctk.CTkFont(size=15)).grid(row=1, column=0, padx=10, pady=18, sticky='e')
self.new_entry = ctk.CTkEntry(self.card_pw, show="*", width=320, corner_radius=18, border_width=1)
self.new_entry.grid(row=1, column=1, padx=10, pady=18, sticky='w')
ctk.CTkLabel(self.card_pw, text="确认密码", font=ctk.CTkFont(size=15)).grid(row=2, column=0, padx=10, pady=18, sticky='e')
self.confirm_entry = ctk.CTkEntry(self.card_pw, show="*", width=320, corner_radius=18, border_width=1)
self.confirm_entry.grid(row=2, column=1, padx=10, pady=18, sticky='w')
btn_frame = ctk.CTkFrame(self.card_pw, fg_color="transparent")
btn_frame.grid(row=3, column=0, columnspan=2, pady=45)
ctk.CTkButton(btn_frame, text="确认修改", command=self._change_password, width=160, corner_radius=28, font=ctk.CTkFont(size=13)).pack(side='left', padx=20)
ctk.CTkButton(btn_frame, text="清空字段", command=self._clear_password_fields, width=160, corner_radius=28, fg_color="#B85C5C", hover_color="#9E4646", font=ctk.CTkFont(size=13)).pack(side='left', padx=20)
def _change_password(self):
old = self.old_entry.get()
new = self.new_entry.get()
cf = self.confirm_entry.get()
if not PasswordManager.verify(old):
self._show_notification("原密码错误", is_err=True)
self.old_entry.delete(0, 'end')
return
if new != cf:
self._show_notification("两次密码不一致", is_err=True)
self.confirm_entry.delete(0, 'end')
return
PasswordManager.save_password(new)
self._show_notification("密码修改成功,下次锁屏生效")
self._clear_password_fields()
def _clear_password_fields(self):
self.old_entry.delete(0, 'end')
self.new_entry.delete(0, 'end')
self.confirm_entry.delete(0, 'end')
# ==================== 壁纸与样式页面 ====================
def _build_wallpaper_ui(self):
scroll_frame = ctk.CTkScrollableFrame(self.card_wall, fg_color="transparent", corner_radius=0)
scroll_frame.pack(fill="both", expand=True)
self.trans_var = ctk.BooleanVar(value=self.settings.get("transparent_bg", False))
trans_sw = ctk.CTkSwitch(
scroll_frame, text="透明背景模式 (忽略壁纸)", variable=self.trans_var,
command=self._on_transparent_toggle, font=ctk.CTkFont(size=14), switch_width=50
)
trans_sw.pack(anchor='w', pady=(28, 16), padx=28)
self.wall_group = ctk.CTkFrame(scroll_frame, fg_color="transparent")
self.wall_group.pack(fill='x', padx=28, pady=6)
path_frame = ctk.CTkFrame(self.wall_group, fg_color="transparent")
path_frame.pack(fill='x', pady=8)
self.wall_entry = ctk.CTkEntry(path_frame, placeholder_text="选择壁纸图片", width=500, corner_radius=18)
self.wall_entry.pack(side='left', padx=(0, 12))
self.wall_entry.insert(0, self.settings.get("wallpaper", ""))
self.browse_btn = ctk.CTkButton(path_frame, text="浏览", command=self._browse_wallpaper, width=90, corner_radius=18)
self.browse_btn.pack(side='left')
preview_container = ctk.CTkFrame(self.wall_group, fg_color="transparent")
preview_container.pack(fill='x', pady=(20, 16))
self.preview_label = ctk.CTkLabel(preview_container, text="壁纸预览", anchor='w', font=ctk.CTkFont(size=13))
self.preview_label.pack(anchor='w', pady=(0, 8))
self.preview_img = ctk.CTkLabel(
preview_container, text="", width=680, height=180,
corner_radius=22, fg_color=("#EFF3F8", "#2D2D3F")
)
self.preview_img.pack()
if not self.trans_var.get() and self.settings.get("wallpaper"):
self._update_preview(self.settings["wallpaper"])
else:
self.preview_img.configure(text="(暂无预览)")
self.time_var = ctk.BooleanVar(value=self.settings.get("show_time", False))
time_sw = ctk.CTkSwitch(
scroll_frame, text="锁屏时显示当前时间 (左下角)", variable=self.time_var,
command=self._on_time_toggle, font=ctk.CTkFont(size=14), switch_width=50
)
time_sw.pack(anchor='w', pady=(18, 30), padx=28)
self._update_wallpaper_access()
def _update_wallpaper_access(self):
if self.trans_var.get():
self.wall_entry.configure(state="disabled")
self.browse_btn.configure(state="disabled")
self.preview_img.configure(image=None, text="透明模式已开启,不显示壁纸")
else:
self.wall_entry.configure(state="normal")
self.browse_btn.configure(state="normal")
wp = self.settings.get("wallpaper", "")
if wp:
self._update_preview(wp)
else:
self.preview_img.configure(image=None, text="(未选择壁纸)")
def _on_transparent_toggle(self):
new_val = self.trans_var.get()
self.settings["transparent_bg"] = new_val
SettingsManager.save(self.settings)
self._update_wallpaper_access()
self._show_notification("透明模式已" + ("开启" if new_val else "关闭"))
def _browse_wallpaper(self):
if self.trans_var.get():
return
path = filedialog.askopenfilename(filetypes=[("图片", "*.png *.jpg *.jpeg *.bmp *.gif")])
if path:
self.wall_entry.delete(0, 'end')
self.wall_entry.insert(0, path)
self._save_wallpaper_path(path)
def _save_wallpaper_path(self, path):
if not path or not os.path.isfile(path):
self._show_notification("文件不存在", is_err=True)
return
self.settings["wallpaper"] = path
if self.settings["transparent_bg"]:
self.settings["transparent_bg"] = False
self.trans_var.set(False)
self._update_wallpaper_access()
SettingsManager.save(self.settings)
self._update_preview(path)
self._show_notification("壁纸已保存")
def _update_preview(self, path):
try:
img = Image.open(path)
img.thumbnail((660, 170), Image.Resampling.LANCZOS)
photo = ctk.CTkImage(img, size=(img.width, img.height))
self.preview_img.configure(image=photo, text="")
self.preview_img.image = photo
except Exception:
self.preview_img.configure(image=None, text="预览失败,请检查图片文件")
def _on_time_toggle(self):
self.settings["show_time"] = self.time_var.get()
SettingsManager.save(self.settings)
self._show_notification("时间显示" + ("已开启" if self.time_var.get() else "已关闭"))
# ==================== 关于页面 ====================
def _build_about_ui(self):
info = """锁屏设置 · 4:3 版本
• 自定义壁纸 / 透明背景
• 锁屏实时时钟
• 自动保存 / 双主题
• 系统托盘控制
@Zhl2010"""
about_lab = ctk.CTkLabel(self.card_about, text=info, justify='left', font=ctk.CTkFont(size=13), padx=40, pady=30)
about_lab.pack()
# ==================== 系统托盘 ====================
def _start_tray_thread(self):
self.tray_thread = threading.Thread(target=self._setup_tray, daemon=True)
self.tray_thread.start()
def _create_tray_icon(self):
size = 64
img = Image.new('RGBA', (size, size), (0, 0, 0, 0))
draw = ImageDraw.Draw(img)
draw.arc((20, 12, 44, 36), start=0, end=180, fill=(100, 150, 100), width=5)
draw.rounded_rectangle((14, 30, 50, 58), radius=10, fill=(120, 180, 120), outline=(60, 100, 60))
draw.ellipse((28, 42, 36, 50), fill=(40, 70, 40))
draw.rectangle((30, 48, 34, 55), fill=(40, 70, 40))
return img
def _setup_tray(self):
# 确保图标存在
if os.path.exists(ICON_FILE):
try:
icon_img = Image.open(ICON_FILE).resize((64, 64), Image.Resampling.LANCZOS)
except:
icon_img = self._create_tray_icon()
else:
icon_img = self._create_tray_icon()
menu = pystray.Menu(
pystray.MenuItem("启动锁屏", self._launch_lockscreen),
pystray.MenuItem("显示窗口", self.show_window),
pystray.MenuItem("退出程序", self._quit_app)
)
icon = pystray.Icon("lock_config", icon_img, "锁屏设置", menu)
icon.run()
def _launch_lockscreen(self):
base = os.path.dirname(os.path.abspath(sys.argv[0]))
exe = os.path.join(base, 'lockscreen.exe')
py = os.path.join(base, 'lockscreen.py')
if os.path.exists(exe):
subprocess.Popen([exe])
elif os.path.exists(py):
subprocess.Popen([sys.executable, py])
else:
self._show_notification("未找到锁屏程序", is_err=True)
def _quit_app(self):
self.quit()
os._exit(0)
if __name__ == "__main__":
app = ConfigApp()
app.mainloop()
::::
::::info[lockscreen.py]
"""
锁屏程序 - 普通版(完整鼠标拦截,强制英文,允许 Shift)
彻底重构钩子与日志,解决卡顿、钩子失效、窗口抖动偏移问题
功能:
- 全屏置顶,拦截键盘鼠标,仅ESC唤出密码框
- 自动切换至美式英语键盘布局,杜绝中文输入
- 允许 Shift 键(大小写切换)
- 支持自定义壁纸、透明背景、时间显示
- 密码错误抖动
- 调试模式(10秒自动解锁)
- 解锁后恢复原始键盘布局
- **精确鼠标拦截**:密码框显示时只能点击密码框内控件,无法点击其他区域
- 彻底禁用右键菜单
- 低CPU占用,无频繁磁盘I/O
- 【已删除托盘图标功能】
- **钩子永不失效**:ESC直接触发主线程回调,无需鼠标干预
"""
import os
import sys
import json
import ctypes
import ctypes.wintypes
import hashlib
import threading
import subprocess
import tkinter as tk
from tkinter import messagebox
from PIL import Image, ImageTk
import time
import atexit
import signal
import traceback
from datetime import datetime
# ---------- 加载 Windows API ----------
user32 = ctypes.WinDLL('user32', use_last_error=True)
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
# 输入法相关
imm32 = ctypes.WinDLL('imm32')
ImmAssociateContext = imm32.ImmAssociateContext
ImmAssociateContext.argtypes = [ctypes.wintypes.HWND, ctypes.wintypes.HANDLE]
ImmAssociateContext.restype = ctypes.wintypes.HANDLE
# 键盘布局
LoadKeyboardLayoutW = user32.LoadKeyboardLayoutW
LoadKeyboardLayoutW.argtypes = [ctypes.c_wchar_p, ctypes.c_uint]
LoadKeyboardLayoutW.restype = ctypes.wintypes.HANDLE
ActivateKeyboardLayout = user32.ActivateKeyboardLayout
ActivateKeyboardLayout.argtypes = [ctypes.wintypes.HANDLE, ctypes.c_uint]
ActivateKeyboardLayout.restype = ctypes.wintypes.HANDLE
# 窗口句柄相关
WindowFromPoint = user32.WindowFromPoint
WindowFromPoint.argtypes = [ctypes.wintypes.POINT]
WindowFromPoint.restype = ctypes.wintypes.HWND
IsChild = user32.IsChild
IsChild.argtypes = [ctypes.wintypes.HWND, ctypes.wintypes.HWND]
IsChild.restype = ctypes.wintypes.BOOL
KLF_ACTIVATE = 1
KLF_SETFORPROCESS = 0x100
ENGLISH_US_LAYOUT = "00000409" # 美式英语
LOG_DIR = "log"
BG_ALPHA = 0.0
PASSWORD_FILE = os.path.join(LOG_DIR, "pass")
SETTINGS_FILE = os.path.join(LOG_DIR, "settings.json")
ERROR_LOG_PATH = os.path.join(LOG_DIR, "crash.log")
LOCK_SIGNAL_FILE = os.path.join(LOG_DIR, "lock_signal.txt")
if not os.path.exists(LOG_DIR):
os.makedirs(LOG_DIR)
def log_error(msg):
"""仅记录严重错误,不记录正常信息"""
try:
with open(ERROR_LOG_PATH, 'a', encoding='utf-8') as f:
f.write(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - {msg}\n")
f.flush()
except:
pass
def load_settings():
if os.path.exists(SETTINGS_FILE):
try:
with open(SETTINGS_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
except:
pass
return {"wallpaper": "", "show_time": False, "transparent_bg": False}
# ---------- 钩子常量 ----------
WH_KEYBOARD_LL = 13
WH_MOUSE_LL = 14
WM_KEYDOWN = 0x0100
WM_KEYUP = 0x0101
WM_SYSKEYDOWN = 0x0104
WM_SYSKEYUP = 0x0105
WM_LBUTTONDOWN = 0x0201
WM_LBUTTONUP = 0x0202
WM_RBUTTONDOWN = 0x0204
WM_RBUTTONUP = 0x0205
WM_MBUTTONDOWN = 0x0207
WM_MBUTTONUP = 0x0208
WM_MOUSEWHEEL = 0x020A
WM_XBUTTONDOWN = 0x020B
WM_XBUTTONUP = 0x020C
WM_NCRBUTTONDOWN = 0x00A4
WM_NCRBUTTONUP = 0x00A5
VK_SHIFT = 0x10
VK_LSHIFT = 0xA0
VK_RSHIFT = 0xA1
VK_CTRL = 0x11
VK_ALT = 0x12
VK_LWIN = 0x5B
VK_RWIN = 0x5C
VK_ESC = 0x1B
VK_BACK = 0x08
VK_TAB = 0x09
VK_ENTER = 0x0D
VK_CAPSLOCK = 0x14
VK_DELETE = 0x2E
VK_HOME = 0x24
VK_END = 0x23
VK_LEFT = 0x25
VK_RIGHT = 0x27
VK_UP = 0x26
VK_DOWN = 0x28
class KBDLLHOOKSTRUCT(ctypes.Structure):
_fields_ = [
('vkCode', ctypes.wintypes.DWORD),
('scanCode', ctypes.wintypes.DWORD),
('flags', ctypes.wintypes.DWORD),
('time', ctypes.wintypes.DWORD),
('dwExtraInfo', ctypes.POINTER(ctypes.c_ulong))
]
class MSLLHOOKSTRUCT(ctypes.Structure):
_fields_ = [
('pt', ctypes.wintypes.POINT),
('mouseData', ctypes.wintypes.DWORD),
('flags', ctypes.wintypes.DWORD),
('time', ctypes.wintypes.DWORD),
('dwExtraInfo', ctypes.POINTER(ctypes.c_ulong))
]
HOOKPROC = ctypes.WINFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.wintypes.WPARAM, ctypes.wintypes.LPARAM)
g_lock = None
# ---------- 密码管理 ----------
class PasswordManager:
PASSWORD_FILE = PASSWORD_FILE
SALT = b"lockscreen_secure_salt"
@classmethod
def _hash(cls, pwd):
h = hashlib.sha256()
h.update(cls.SALT)
h.update(pwd.encode('utf-8'))
return h.hexdigest()
@classmethod
def initialize(cls):
if not os.path.exists(cls.PASSWORD_FILE):
cls.save_password("123456")
print("初始密码已设为 123456")
@classmethod
def save_password(cls, new_pwd):
with open(cls.PASSWORD_FILE, "w") as f:
f.write(cls._hash(new_pwd))
@classmethod
def verify(cls, pwd):
if not os.path.exists(cls.PASSWORD_FILE):
cls.initialize()
try:
with open(cls.PASSWORD_FILE, "r") as f:
stored = f.read().strip()
return stored == cls._hash(pwd)
except:
return False
# ---------- 钩子回调 ----------
@HOOKPROC
def keyboard_proc(nCode, wParam, lParam):
try:
if nCode >= 0:
kb = ctypes.cast(lParam, ctypes.POINTER(KBDLLHOOKSTRUCT)).contents
vkCode = kb.vkCode
if wParam in (WM_KEYDOWN, WM_SYSKEYDOWN):
if g_lock and g_lock.should_block_key(vkCode):
return 1
except Exception:
pass
return user32.CallNextHookEx(None, nCode, wParam, lParam)
@HOOKPROC
def mouse_proc(nCode, wParam, lParam):
try:
if nCode >= 0 and g_lock and g_lock.is_locked:
ms = ctypes.cast(lParam, ctypes.POINTER(MSLLHOOKSTRUCT)).contents
is_click = wParam in (WM_LBUTTONDOWN, WM_LBUTTONUP,
WM_RBUTTONDOWN, WM_RBUTTONUP,
WM_MBUTTONDOWN, WM_MBUTTONUP,
WM_XBUTTONDOWN, WM_XBUTTONUP,
WM_MOUSEWHEEL,
WM_NCRBUTTONDOWN, WM_NCRBUTTONUP)
if not is_click:
return 0
if g_lock.dialog_shown:
hwnd_clicked = WindowFromPoint(ms.pt)
if hwnd_clicked and g_lock.password_dialog:
if hwnd_clicked == g_lock.password_dialog.winfo_id() or IsChild(g_lock.password_dialog.winfo_id(), hwnd_clicked):
return 0
return 1
else:
return 1
except Exception:
pass
return user32.CallNextHookEx(None, nCode, wParam, lParam)
# ---------- 主类 ----------
class LockScreen:
def __init__(self, debug_mode=False):
self._init(debug_mode)
def _init(self, debug_mode):
self.debug_mode = debug_mode
self.is_locked = True
self.dialog_shown = False
self.password_dialog = None
self.timer_id = None
self.timeout_id = None
self.keyboard_hook = None
self.mouse_hook = None
self.settings = load_settings()
self.canvas = None
self.time_text_id = None
self.bg_image_id = None
self.signal_check_id = None
self.reload_id = None
self.original_layout = None
self.keepalive_id = None
if not ctypes.windll.shell32.IsUserAnAdmin():
messagebox.showerror("错误", "请以管理员身份运行!")
sys.exit(1)
# 保存原始键盘布局
try:
self.original_layout = ActivateKeyboardLayout(0, 0)
except Exception:
pass
# 强制英文布局
self._force_english_layout()
PasswordManager.initialize()
self.root = tk.Tk()
self.root.title("")
self.root.overrideredirect(True)
self.root.attributes('-topmost', True)
self.root.geometry(f"{self.root.winfo_screenwidth()}x{self.root.winfo_screenheight()}+0+0")
self.root.focus_force()
self.root.protocol("WM_DELETE_WINDOW", lambda: None)
self.root.grab_set_global()
self.canvas = tk.Canvas(
self.root,
width=self.root.winfo_screenwidth(),
height=self.root.winfo_screenheight(),
highlightthickness=0,
bg='#000000'
)
self.canvas.pack()
# 设置背景
transparent_bg = self.settings.get("transparent_bg", False)
if transparent_bg:
self.root.attributes('-alpha', BG_ALPHA)
else:
wallpaper = self.settings.get("wallpaper", "")
if wallpaper and os.path.isfile(wallpaper):
self._set_wallpaper(wallpaper)
self.root.attributes('-alpha', 1.0)
else:
self.root.attributes('-alpha', BG_ALPHA)
if self.settings.get("show_time", False):
self._create_time_display()
self._check_lock_signal()
if self.debug_mode:
self.timer_id = self.root.after(10000, self.debug_unlock)
self._reload_settings_periodic()
global g_lock
g_lock = self
atexit.register(self.cleanup)
signal.signal(signal.SIGTERM, self.signal_handler)
signal.signal(signal.SIGINT, self.signal_handler)
self.hook_thread = threading.Thread(target=self._hook_message_loop, daemon=True)
self.hook_thread.start()
# 保活机制:每5秒调用一次空函数,确保主线程消息循环活跃
self._keepalive()
def _keepalive(self):
"""空函数,仅用于唤醒主线程消息循环"""
self.keepalive_id = self.root.after(5000, self._keepalive)
def _force_english_layout(self):
try:
hkl = LoadKeyboardLayoutW(ENGLISH_US_LAYOUT, KLF_ACTIVATE | KLF_SETFORPROCESS)
if hkl:
ActivateKeyboardLayout(hkl, 0)
except Exception:
pass
def _reload_settings_periodic(self):
"""每隔30秒重新加载设置,且仅在文件真正改变时才更新壁纸等,不写日志"""
try:
new_settings = load_settings()
if new_settings != self.settings:
# 设置发生变化,需要更新
old_wall = self.settings.get("wallpaper")
new_wall = new_settings.get("wallpaper")
if new_wall != old_wall:
if new_wall and os.path.isfile(new_wall):
self._set_wallpaper(new_wall)
else:
# 清除壁纸
if self.bg_image_id:
self.canvas.delete(self.bg_image_id)
self.bg_image_id = None
# 更新时间显示
if new_settings.get("show_time") != self.settings.get("show_time"):
if new_settings.get("show_time"):
if not self.time_text_id:
self._create_time_display()
else:
if self.time_text_id:
self.canvas.delete(self.time_text_id)
self.time_text_id = None
# 更新透明背景
if new_settings.get("transparent_bg") != self.settings.get("transparent_bg"):
alpha = BG_ALPHA if new_settings.get("transparent_bg") else 1.0
self.root.attributes('-alpha', alpha)
self.settings = new_settings
except Exception:
pass
self.reload_id = self.root.after(30000, self._reload_settings_periodic)
def _set_wallpaper(self, path):
try:
img = Image.open(path)
sw = self.root.winfo_screenwidth()
sh = self.root.winfo_screenheight()
img = img.resize((sw, sh), Image.Resampling.LANCZOS)
self.bg_photo = ImageTk.PhotoImage(img)
if self.bg_image_id:
self.canvas.delete(self.bg_image_id)
self.bg_image_id = self.canvas.create_image(0, 0, image=self.bg_photo, anchor='nw')
self.canvas.tag_lower(self.bg_image_id)
except Exception:
pass
def _create_time_display(self):
self.time_text_id = self.canvas.create_text(
50, self.root.winfo_screenheight() - 50,
text='', fill='white', font=('Segoe UI', 60, 'normal'), anchor='sw'
)
self._update_time()
def _update_time(self):
if self.time_text_id:
now = datetime.now()
self.canvas.itemconfig(self.time_text_id, text=f"{now.strftime('%H:%M')}\n{now.strftime('%Y-%m-%d')}")
self.root.after(1000, self._update_time)
def _check_lock_signal(self):
try:
if os.path.exists(LOCK_SIGNAL_FILE):
self.show_password_dialog()
os.remove(LOCK_SIGNAL_FILE)
except Exception:
pass
self.signal_check_id = self.root.after(500, self._check_lock_signal)
def signal_handler(self, signum, frame):
self.cleanup()
sys.exit(0)
def cleanup(self):
try:
if self.keyboard_hook:
user32.UnhookWindowsHookEx(self.keyboard_hook)
if self.mouse_hook:
user32.UnhookWindowsHookEx(self.mouse_hook)
if self.signal_check_id:
self.root.after_cancel(self.signal_check_id)
if self.reload_id:
self.root.after_cancel(self.reload_id)
if self.keepalive_id:
self.root.after_cancel(self.keepalive_id)
except Exception:
pass
def _hook_message_loop(self):
"""钩子线程,无限重连"""
while True:
try:
self.keyboard_hook = user32.SetWindowsHookExW(
WH_KEYBOARD_LL, keyboard_proc,
kernel32.GetModuleHandleW(None), 0
)
if not self.keyboard_hook:
raise Exception("Keyboard hook failed")
self.mouse_hook = user32.SetWindowsHookExW(
WH_MOUSE_LL, mouse_proc,
kernel32.GetModuleHandleW(None), 0
)
if not self.mouse_hook:
raise Exception("Mouse hook failed")
msg = ctypes.wintypes.MSG()
while user32.GetMessageW(ctypes.byref(msg), None, 0, 0) != 0:
user32.TranslateMessage(ctypes.byref(msg))
user32.DispatchMessageW(ctypes.byref(msg))
# 正常退出WM_QUIT - 重启线程
except Exception:
pass
finally:
if self.keyboard_hook:
user32.UnhookWindowsHookEx(self.keyboard_hook)
self.keyboard_hook = None
if self.mouse_hook:
user32.UnhookWindowsHookEx(self.mouse_hook)
self.mouse_hook = None
time.sleep(1) # 等待后重试
def should_block_key(self, vk):
"""仅做逻辑判断,不调用任何Tk方法,避免跨线程问题"""
if not self.is_locked:
return False
if self.dialog_shown:
# 密码输入模式:放行字母、数字、Shift、功能键等
if 0x30 <= vk <= 0x39 or 0x41 <= vk <= 0x5A or 0x60 <= vk <= 0x69:
return False
if vk in (VK_SHIFT, VK_LSHIFT, VK_RSHIFT):
return False
allowed = {VK_BACK, VK_TAB, VK_ENTER, VK_DELETE, VK_HOME, VK_END,
VK_LEFT, VK_RIGHT, VK_UP, VK_DOWN, VK_CAPSLOCK}
if vk in allowed:
return False
if vk in (VK_LWIN, VK_RWIN):
return True
if vk == VK_ESC:
# 直接通过 after 调用主线程方法(线程安全)
self.root.after(0, self.show_password_dialog)
return True
return True
else:
# 锁屏模式:仅ESC有效
if vk in (VK_LWIN, VK_RWIN):
return True
if vk == VK_ESC:
self.root.after(0, self.show_password_dialog)
return True
return True
def show_password_dialog(self):
if self.dialog_shown:
return
try:
self.dialog_shown = True
dlg = tk.Toplevel(self.root)
dlg.title("")
dlg.overrideredirect(True)
dlg.attributes('-topmost', True)
dlg.transient(self.root)
dlg.grab_set_global()
dlg.configure(bg='#1e1e1e')
dlg.attributes('-alpha', 0.95)
w, h = 380, 200
sw, sh = self.root.winfo_screenwidth(), self.root.winfo_screenheight()
x = sw // 2 - w // 2
y = sh // 2 - h // 2
dlg.geometry(f"{w}x{h}+{x}+{y}")
# 保存原始坐标供抖动使用
self.dialog_orig_x = x
self.dialog_orig_y = y
canvas = tk.Canvas(dlg, width=w, height=h, bg='#1e1e1e', highlightthickness=0)
canvas.place(x=0, y=0)
canvas.create_round_rect(2, 2, w-2, h-2, radius=25, fill='#2d2d2d', outline='')
canvas.create_text(w//2, 35, text="解锁屏幕", fill='#e0e0e0', font=('Segoe UI', 16, 'bold'))
ex, ey = w//2-120, 70
canvas.create_round_rect(ex, ey, ex+240, ey+40, radius=10, fill='#3c3c3c', outline='')
def validate_input(char, action):
if action == '0':
return True
if len(char) == 0:
return True
return 32 <= ord(char) <= 126
vcmd = (self.root.register(validate_input), '%S', '%d')
entry = tk.Entry(canvas, show='●', font=('Segoe UI', 14), bg='#3c3c3c',
fg='white', insertbackground='#6ab04c', bd=0, highlightthickness=0,
validate='key', validatecommand=vcmd)
entry.place(x=ex+10, y=ey+8, width=220, height=24)
hwnd = entry.winfo_id()
ImmAssociateContext(hwnd, None)
entry.bind('<Button-3>', lambda e: 'break')
canvas.bind('<Button-3>', lambda e: 'break')
dlg.bind('<Button-3>', lambda e: 'break')
def on_focus_in(event):
self._force_english_layout()
ImmAssociateContext(entry.winfo_id(), None)
entry.bind('<FocusIn>', on_focus_in)
btn_y = 130
unlock = tk.Button(canvas, text="解锁", command=lambda: self.check_password(entry),
bg='#6ab04c', fg='white', font=('Segoe UI', 11),
relief='flat', bd=0, cursor='hand2')
unlock.place(x=w//2-110, y=btn_y, width=100, height=35)
cancel = tk.Button(canvas, text="取消", command=self.hide_password_dialog,
bg='#d63031', fg='white', font=('Segoe UI', 11),
relief='flat', bd=0, cursor='hand2')
cancel.place(x=w//2+10, y=btn_y, width=100, height=35)
close_btn = tk.Button(canvas, text="✕", bg='#2d2d2d', fg='#a0a0a0',
font=('Segoe UI', 10), bd=0, command=self.hide_password_dialog,
activebackground='#c42b1c')
close_btn.place(x=w-35, y=5, width=25, height=25)
dlg.lift()
dlg.focus_force()
dlg.update()
dlg.lift()
dlg.focus_force()
entry.focus_set()
def reset_timeout():
if self.timeout_id:
dlg.after_cancel(self.timeout_id)
self.timeout_id = dlg.after(3000, self.hide_password_dialog)
entry.bind('<Key>', lambda e: reset_timeout())
reset_timeout()
entry.bind('<Return>', lambda e: self.check_password(entry))
self.password_dialog = dlg
self.password_entry = entry
except Exception as e:
log_error(f"show_password_dialog error: {traceback.format_exc()}")
self.dialog_shown = False
def hide_password_dialog(self):
try:
if self.password_dialog:
if self.timeout_id:
self.password_dialog.after_cancel(self.timeout_id)
self.timeout_id = None
self.password_dialog.grab_release()
self.password_dialog.destroy()
self.password_dialog = None
self.dialog_shown = False
except Exception:
pass
def check_password(self, entry):
try:
if PasswordManager.verify(entry.get()):
self.unlock()
else:
self.shake_window()
entry.delete(0, tk.END)
if self.password_dialog:
self.password_dialog.lift()
self.password_dialog.focus_force()
entry.focus_set()
self._force_english_layout()
ImmAssociateContext(entry.winfo_id(), None)
except Exception as e:
log_error(f"check_password error: {e}")
def shake_window(self):
if not self.password_dialog:
return
dlg = self.password_dialog
orig_x = getattr(self, 'dialog_orig_x', None)
orig_y = getattr(self, 'dialog_orig_y', None)
if orig_x is None or orig_y is None:
orig_x = dlg.winfo_x()
orig_y = dlg.winfo_y()
offsets = [5, -5, 5, -5, 3, -3, 2, -2, 0]
idx = 0
def step():
nonlocal idx
if idx >= len(offsets):
dlg.geometry(f"+{orig_x}+{orig_y}")
return
off = offsets[idx]
dlg.geometry(f"+{orig_x + off}+{orig_y}")
idx += 1
dlg.after(30, step)
step()
def unlock(self):
self.is_locked = False
if self.timer_id:
self.root.after_cancel(self.timer_id)
self.cleanup()
if hasattr(self, 'original_layout') and self.original_layout:
try:
ActivateKeyboardLayout(self.original_layout, 0)
except Exception:
pass
self.root.quit()
self.root.destroy()
os._exit(0)
def debug_unlock(self):
if self.is_locked:
print("调试模式自动解锁")
self.unlock()
def open_config(self):
try:
base = os.path.dirname(os.path.abspath(sys.argv[0]))
exe = os.path.join(base, 'config.exe')
py = os.path.join(base, 'config.py')
if getattr(sys, 'frozen', False):
subprocess.Popen([exe] if os.path.exists(exe) else [py])
else:
subprocess.Popen([sys.executable, py] if os.path.exists(py) else [exe])
except Exception:
pass
def run(self):
try:
self.root.mainloop()
except Exception as e:
log_error(f"run error: {traceback.format_exc()}")
finally:
self.cleanup()
def _create_round_rect(self, x1, y1, x2, y2, radius=20, **kwargs):
points = [x1+radius, y1,
x2-radius, y1,
x2, y1,
x2, y1+radius,
x2, y2-radius,
x2, y2,
x2-radius, y2,
x1+radius, y2,
x1, y2,
x1, y2-radius,
x1, y1+radius,
x1, y1]
return self.create_polygon(points, smooth=True, **kwargs)
tk.Canvas.create_round_rect = _create_round_rect
if __name__ == "__main__":
try:
print("锁屏程序启动(普通版,无托盘图标,钩子直接回调)")
lock = LockScreen(debug_mode=False)
lock.run()
except Exception as e:
log_error(traceback.format_exc())
:::: ::::info[kill.cpp]
#include<bits/stdc++.h>
#include <conio.h>
#include<windows.h>
#include<time.h>
#include<tlhelp32.h>
#include<iostream>
#include<map>
#include<string>
#include<thread>
#include <psapi.h>
#pragma comment(lib, "psapi.lib")
#pragma comment(lib, "kernel32.lib")
using namespace std;
const int Year=2025,Month=10,Day=22;
void killProcess(DWORD pid) {
HANDLE hProcess=OpenProcess(PROCESS_TERMINATE,FALSE,pid);
if(hProcess) {
TerminateProcess(hProcess,0);
CloseHandle(hProcess);
}
}
struct node {
string rode,name;
};
unordered_map<string,int>mp;
vector<string>vv;
string S;
void bfs_kill() {
HANDLE hSnapshot=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
if(hSnapshot) {
PROCESSENTRY32 p;
p.dwSize=sizeof(PROCESSENTRY32);
if(Process32First(hSnapshot,&p)) {
do {
string s=p.szExeFile;
// cout<<s<<endl;
if(s==S) {
killProcess(p.th32ProcessID);
}
} while(Process32Next(hSnapshot,&p));
}
CloseHandle(hSnapshot);
}
}
int main(int argc,char* argv[]) {
S="lockscreen.exe";
bfs_kill();
return 0;
}
::::
::::info[start.cpp]
#include <windows.h>
#include <shellapi.h>
#include<bits/stdc++.h>
using namespace std;
string EXE_PATH = "lockscreen.exe";
HHOOK g_hHook = NULL;
bool g_winPressed = false;
DWORD g_lastTriggerTime = 0;
const DWORD COOLDOWN_MS = 10000;
LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) {
if (nCode == HC_ACTION) {
KBDLLHOOKSTRUCT* pKb = (KBDLLHOOKSTRUCT*)lParam;
if (pKb->vkCode == VK_LWIN || pKb->vkCode == VK_RWIN) {
if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN)
g_winPressed = true;
else if (wParam == WM_KEYUP || wParam == WM_SYSKEYUP)
g_winPressed = false;
}
if ((wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) && pKb->vkCode == 'A' && g_winPressed) {
DWORD now = GetTickCount();
if (g_lastTriggerTime == 0 || (now - g_lastTriggerTime) >= COOLDOWN_MS) {
string cmd= "start " + EXE_PATH;
g_lastTriggerTime = now;
system(cmd.c_str());
// Sleep(5000);
cmd="start recognize.exe";
system(cmd.c_str());
// ShellExecuteW(NULL, L"open", EXE_PATH, NULL, NULL, SW_SHOW);
}
}
}
return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}
int main() {
HWND hWnd = GetConsoleWindow(); // 获取控制台窗口的句柄
ShowWindow(hWnd, SW_HIDE); // 隐藏控制台窗口
// ... 其他代码
g_hHook = SetWindowsHookExW(WH_KEYBOARD_LL, LowLevelKeyboardProc, GetModuleHandleW(NULL), 0);
if (!g_hHook) return 1;
MSG msg;
while (GetMessageW(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
UnhookWindowsHookEx(g_hHook);
return 0;
}
::::
未经允许禁止转载、套用。