[Asm] 纯文本查看 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
儿童电脑管控程序 V1.0
基于Tkinter的桌面应用程序,用于家长管理儿童电脑使用时间
"""
import tkinter as tk
from tkinter import ttk, messagebox, simpledialog
import configparser
import json
import datetime
import time
import threading
import subprocess
import sys
import os
import platform
import psutil
from typing import List, Dict, Tuple, Optional, Any
# 版本信息
VERSION = "1.0.0"
AUTHOR = "J"
# ==========================================
# 核心逻辑层 (Model & Controller)
# ==========================================
class ConfigManager:
"""配置与数据管理器"""
def __init__(self):
# 路径设置
if getattr(sys, 'frozen', False):
self.base_dir = os.path.dirname(sys.executable)
else:
self.base_dir = os.path.dirname(os.path.abspath(__file__))
self.config_file = os.path.join(self.base_dir, "config.ini")
self.usage_file = os.path.join(self.base_dir, "usage_data.json")
self.blacklist_file = os.path.join(self.base_dir, "app_blacklist.json")
# 运行时状态
self.today = datetime.date.today().isoformat()
self.used_seconds = 0
self.app_blacklist = {}
# 配置项缓存
self.daily_limit = 90
self.password = "123456"
self.password_enabled = True
self.shutdown_delay = 60
self.warning_time = 120
self.time_slots = []
# 加载所有数据
self.load_all()
def load_all(self):
"""加载所有配置和数据"""
self.load_config()
self.load_usage_data()
self.load_blacklist()
def load_config(self):
"""加载 config.ini"""
config = configparser.ConfigParser()
# 默认配置
default_config = {
'TimeLimits': {
'DailyLimit': '90',
'Password': '123456',
'PasswordEnabled': '0', # 1=Enabled, 0=Disabled
'1': '09:00-23:00',
'2': '09:00-23:00',
'3': '09:30-12:00,12:50-23:00',
'4': '09:30-12:00,12:50-23:00',
'5': '09:30-12:00,12:50-18:00,19:00-23:30',
'6': '09:30-12:00,12:50-18:00,19:00-23:30',
'7': '09:30-12:00,12:50-18:00,19:00-23:30',
},
'Settings': {
'ShutdownDelay': '60',
'WarningTime': '120',
}
}
if not os.path.exists(self.config_file):
self._create_default_config(config, default_config)
else:
try:
config.read(self.config_file, encoding='utf-8')
if not config.has_section('TimeLimits'):
self._create_default_config(config, default_config)
except:
self._create_default_config(config, default_config)
# 解析配置到内存
try:
self.daily_limit = config.getint('TimeLimits', 'DailyLimit', fallback=90)
self.password = config.get('TimeLimits', 'Password', fallback='123456')
self.password_enabled = config.getboolean('TimeLimits', 'PasswordEnabled', fallback=True)
self.shutdown_delay = config.getint('Settings', 'ShutdownDelay', fallback=60)
self.warning_time = config.getint('Settings', 'WarningTime', fallback=120)
# 解析今日时间段
weekday = datetime.datetime.today().weekday() + 1 # 1-7
time_slot_str = config.get('TimeLimits', str(weekday), fallback="")
self.time_slots = self._parse_time_slots(time_slot_str)
except Exception as e:
print(f"配置解析错误: {e}")
def _create_default_config(self, config, default_data):
"""创建默认配置文件"""
for section, values in default_data.items():
if not config.has_section(section):
config.add_section(section)
for k, v in values.items():
config.set(section, k, v)
try:
with open(self.config_file, 'w', encoding='utf-8') as f:
config.write(f)
except Exception as e:
print(f"写入配置文件失败: {e}")
def save_config(self, new_config: Dict):
"""保存配置"""
config = configparser.ConfigParser()
config.read(self.config_file, encoding='utf-8')
for section, items in new_config.items():
if not config.has_section(section):
config.add_section(section)
for k, v in items.items():
config.set(section, str(k), str(v))
try:
with open(self.config_file, 'w', encoding='utf-8') as f:
config.write(f)
self.load_config()
return True
except Exception as e:
print(f"保存配置失败: {e}")
return False
def load_usage_data(self):
"""加载使用数据"""
if os.path.exists(self.usage_file):
try:
with open(self.usage_file, 'r', encoding='utf-8') as f:
data = json.load(f)
if data.get('date') == self.today:
self.used_seconds = data.get('used_seconds', 0)
else:
self.used_seconds = 0
self.save_usage_data()
except:
self.used_seconds = 0
else:
self.used_seconds = 0
self.save_usage_data()
def save_usage_data(self):
"""保存使用数据"""
data = {
'date': self.today,
'used_seconds': self.used_seconds,
'last_updated': datetime.datetime.now().isoformat()
}
try:
with open(self.usage_file, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
except Exception as e:
print(f"保存使用数据失败: {e}")
def load_blacklist(self):
"""加载黑名单"""
if os.path.exists(self.blacklist_file):
try:
with open(self.blacklist_file, 'r', encoding='utf-8') as f:
self.app_blacklist = json.load(f)
except:
self.app_blacklist = {}
else:
# 默认黑名单
self.app_blacklist = {
"douyin.exe": {"enabled": True, "process_name": "douyin.exe", "display_name": "抖音"},
"msedge.exe": {"enabled": False, "process_name": "msedge.exe", "display_name": "EDGE浏览器"},
"wechat.exe": {"enabled": False, "process_name": "wechat.exe", "display_name": "微信"},
}
self.save_blacklist()
def save_blacklist(self):
"""保存黑名单"""
try:
with open(self.blacklist_file, 'w', encoding='utf-8') as f:
json.dump(self.app_blacklist, f, ensure_ascii=False, indent=2)
except Exception as e:
print(f"保存黑名单失败: {e}")
def check_date_change(self):
"""检查日期变更"""
current_date = datetime.date.today().isoformat()
if current_date != self.today:
self.today = current_date
self.used_seconds = 0
self.save_usage_data()
self.load_config()
return True
return False
def _parse_time_slots(self, time_slot_str: str) -> List[Tuple[int, int]]:
"""解析时间段字符串"""
slots = []
if not time_slot_str:
return slots
try:
for slot in time_slot_str.split(','):
slot = slot.strip()
if not slot: continue
start_str, end_str = slot.split('-')
sh, sm = map(int, start_str.split(':'))
eh, em = map(int, end_str.split(':'))
slots.append((sh * 60 + sm, eh * 60 + em))
except:
pass
return slots
class SystemController:
"""系统控制与监控"""
def __init__(self, config_manager: ConfigManager):
self.cfg = config_manager
self.is_windows = platform.system() == "Windows"
self._shutdown_scheduled = False
self._shutdown_deadline = 0
def shutdown(self, delay: int = None, reason: str = "使用时间结束"):
"""执行系统关机"""
if delay is None:
delay = self.cfg.shutdown_delay
print(f"执行关机: {delay}秒后, 原因: {reason}")
self._shutdown_scheduled = True
self._shutdown_deadline = int(time.time() + delay)
if self.is_windows:
subprocess.run(f'shutdown /s /t {delay} /c "{reason}"', shell=True)
else:
minutes = max(1, delay // 60)
subprocess.run(['shutdown', '-h', f'+{minutes}'], check=False)
def cancel_shutdown(self):
"""取消系统关机"""
if self._shutdown_scheduled:
print("取消关机")
if self.is_windows:
subprocess.run('shutdown /a', shell=True)
else:
subprocess.run(['shutdown', '-c'], check=False)
self._shutdown_scheduled = False
self._shutdown_deadline = 0
def is_shutdown_scheduled(self):
return self._shutdown_scheduled
def get_manual_shutdown_time_str(self) -> str:
"""获取手动/已计划关机时间字符串 HH:MM"""
if not self._shutdown_scheduled:
return ""
dt = datetime.datetime.fromtimestamp(self._shutdown_deadline)
return dt.strftime("%H:%M")
def kill_blacklisted_apps(self):
"""检查并关闭黑名单应用"""
if not self.cfg.app_blacklist:
return []
killed_apps = []
target_names = {
info['process_name'].lower()
for info in self.cfg.app_blacklist.values()
if info.get('enabled', False)
}
if not target_names:
return []
try:
for proc in psutil.process_iter(['pid', 'name']):
try:
p_name = proc.info['name'].lower()
for target in target_names:
if target in p_name:
proc.kill()
killed_apps.append(p_name)
break
except (psutil.NoSuchProcess, psutil.AccessDenied):
continue
except Exception as e:
pass
return killed_apps
# ==========================================
# 用户界面层 (View)
# ==========================================
class ChildControlApp:
"""主应用程序"""
def __init__(self, root: tk.Tk):
self.root = root
self.cfg = ConfigManager()
self.sys_ctrl = SystemController(self.cfg)
# 状态标志
self.manual_shutdown_mode = False
self.is_allowed_now = False
self.setup_ui()
self.start_timers()
def setup_ui(self):
"""初始化界面"""
self.root.title(f"儿童电脑使用时间管控 v{VERSION}")
self.root.geometry("375x400")
self.root.resizable(False, False)
style = ttk.Style()
if platform.system() == "Windows":
style.theme_use('vista')
# 主容器
main_frame = tk.Frame(self.root, padx=15, pady=15, bg="#f0f0f0")
main_frame.pack(fill=tk.BOTH, expand=True)
self.root.configure(bg="#f0f0f0")
# 1. 标题区
header_frame = tk.Frame(main_frame, bg="#f0f0f0")
header_frame.pack(fill=tk.X, pady=(0, 10))
tk.Label(header_frame, text="儿童电脑使用时间管控", font=("微软雅黑", 14, "bold"),
bg="#f0f0f0", fg="#0d6efd").pack()
self.time_display = tk.Label(header_frame, text="Loading...", font=("Consolas", 10, "bold"),
bg="#0d6efd", fg="white", padx=10, pady=4, relief=tk.RAISED)
self.time_display.pack(pady=(8, 0))
# 2. 状态信息面板
self.status_frame = tk.LabelFrame(main_frame, text="状态: 初始化中...", font=("微软雅黑", 10, "bold"),
bg="#f0f0f0", fg="#0d6efd", padx=10, pady=5)
self.status_frame.pack(fill=tk.X, pady=(0, 10))
self.lbl_blacklist = self._create_status_row(self.status_frame, "禁用应用:", "无")
self.lbl_slots = self._create_status_row(self.status_frame, "允许时段:", "--:--")
self.lbl_shutdown_time = self._create_status_row(self.status_frame, "关机时间:", "--:--")
self.lbl_progress_text = self._create_status_row(self.status_frame, "今日进度:", "0 / 90 分钟")
self.progress_canvas = tk.Canvas(self.status_frame, height=12, bg="#e9ecef", relief=tk.SUNKEN, bd=1)
self.progress_canvas.pack(fill=tk.X, pady=(5, 5))
# 3. 提示信息区
self.lbl_info = tk.Label(main_frame, text="系统正常运行中", font=("微软雅黑", 9),
bg="#f0f0f0", fg="green", wraplength=340)
self.lbl_info.pack(pady=(0, 10))
# 4. 控制按钮区 (合并为一行)
btn_frame = tk.Frame(main_frame, bg="#f0f0f0")
btn_frame.pack(fill=tk.X, side=tk.BOTTOM, pady=10)
# 顺序:手动关机 | 禁用应用 | 修改设置
self._create_btn(btn_frame, "🛑 手动关机", self.show_manual_shutdown, "#dc3545").pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5))
self._create_btn(btn_frame, "🚫 禁用应用", self.show_blacklist, "#17a2b8").pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5))
self._create_btn(btn_frame, "⚙️ 修改设置", self.show_settings, "#6c757d").pack(side=tk.LEFT, fill=tk.X, expand=True)
# 拦截关闭事件,执行退出逻辑
self.root.protocol("WM_DELETE_WINDOW", self.exit_app)
def _create_status_row(self, parent, label, value):
frame = tk.Frame(parent, bg="#f0f0f0")
frame.pack(fill=tk.X, pady=1)
tk.Label(frame, text=label, width=9, anchor="w", bg="#f0f0f0", font=("微软雅黑", 9)).pack(side=tk.LEFT)
val_lbl = tk.Label(frame, text=value, anchor="w", bg="#f0f0f0", font=("微软雅黑", 9))
val_lbl.pack(side=tk.LEFT, fill=tk.X, expand=True)
return val_lbl
def _create_btn(self, parent, text, command, color):
return tk.Button(parent, text=text, command=command, bg=color, fg="white",
font=("微软雅黑", 9), relief=tk.RAISED, bd=0, padx=2, pady=8, cursor="hand2",
activebackground=color, activeforeground="white")
# ================= 业务逻辑 =================
def start_timers(self):
self.timer_loop()
threading.Thread(target=self.app_monitor_loop, daemon=True).start()
def timer_loop(self):
now = datetime.datetime.now()
# 1. 更新顶部时间
date_str = now.strftime("%Y-%m-%d")
week_str = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"][now.weekday()]
time_str = now.strftime("%H:%M:%S")
self.time_display.config(text=f"{date_str} {week_str} {time_str}")
# 2. 跨天检查
if self.cfg.check_date_change():
self.manual_shutdown_mode = False
self.sys_ctrl.cancel_shutdown()
# 3. 权限检查
current_minutes = now.hour * 60 + now.minute
in_time_slot = False
current_slot_end = -1
for start, end in self.cfg.time_slots:
if start <= current_minutes < end:
in_time_slot = True
current_slot_end = end
break
limit_reached = self.cfg.used_seconds >= (self.cfg.daily_limit * 60)
# --- 修改:计时逻辑只要运行就累加 ---
self.cfg.used_seconds += 1
if self.cfg.used_seconds % 60 == 0:
self.cfg.save_usage_data()
# 状态判断
if self.manual_shutdown_mode:
self.status_frame.config(text="状态: 手动计划执行中")
self.is_allowed_now = True
else:
if not in_time_slot:
self.is_allowed_now = False
self.status_frame.config(text="状态: 禁止使用 (非时段)")
elif limit_reached:
self.is_allowed_now = False
self.status_frame.config(text="状态: 禁止使用 (超限额)")
else:
self.is_allowed_now = True
self.status_frame.config(text="状态: 允许使用")
# 4. 关机逻辑控制
self._handle_shutdown_logic(limit_reached, in_time_slot)
# 5. 更新UI信息
self._update_status_ui(now, current_minutes, current_slot_end)
self.root.after(1000, self.timer_loop)
def _handle_shutdown_logic(self, limit_reached, in_time_slot):
if self.manual_shutdown_mode:
return
if not self.is_allowed_now:
if not self.sys_ctrl.is_shutdown_scheduled():
reason = "使用时间已耗尽" if limit_reached else "非允许使用时段"
self.sys_ctrl.shutdown(self.cfg.shutdown_delay, reason)
else:
if self.sys_ctrl.is_shutdown_scheduled():
self.sys_ctrl.cancel_shutdown()
def _update_status_ui(self, now_dt, current_minutes, current_slot_end):
# 更新允许时段
slots_str = ",".join([f"{s//60:02d}:{s%60:02d}-{e//60:02d}:{e%60:02d}" for s, e in self.cfg.time_slots])
self.lbl_slots.config(text=slots_str if slots_str else "无")
# 更新黑名单
enabled_apps = [v['display_name'] for v in self.cfg.app_blacklist.values() if v.get('enabled')]
self.lbl_blacklist.config(text=",".join(enabled_apps[:2]) + ("..." if len(enabled_apps)>2 else "") or "无")
# 更新进度
limit_mins = self.cfg.daily_limit
used_mins = self.cfg.used_seconds // 60
if used_mins > limit_mins:
over_mins = used_mins - limit_mins
self.lbl_progress_text.config(text=f"{used_mins}分 / {limit_mins}分 (超时 {over_mins}分)")
else:
remain_mins = max(0, limit_mins - used_mins)
self.lbl_progress_text.config(text=f"{used_mins}分 / {limit_mins}分 (剩余 {remain_mins}分)")
# 绘制进度条
pct = min(1.0, self.cfg.used_seconds / (limit_mins * 60)) if limit_mins > 0 else 1.0
if self.cfg.used_seconds > (limit_mins * 60):
pct = 1.0
w = self.progress_canvas.winfo_width()
self.progress_canvas.delete("all")
color = "#28a745" # Green
if pct > 0.8: color = "#ffc107" # Yellow
if pct > 0.95: color = "#dc3545" # Red
if used_mins > limit_mins: color = "#dc3545"
self.progress_canvas.create_rectangle(0, 0, w * pct, 20, fill=color, outline="")
# --- 计算并显示关机时间 (优化跳动问题) ---
shutdown_time_str = "--:--"
if self.sys_ctrl.is_shutdown_scheduled():
shutdown_time_str = self.sys_ctrl.get_manual_shutdown_time_str()
elif self.is_allowed_now:
minutes_until_slot_end = 9999
if current_slot_end != -1:
minutes_until_slot_end = current_slot_end - current_minutes
remain_seconds = max(0, (limit_mins * 60) - self.cfg.used_seconds)
# 使用 ceil 向上取整分钟,或者加 59秒 再整除,避免 59s 显示少1分钟
minutes_until_limit = (remain_seconds + 59) // 60
actual_minutes_left = min(minutes_until_slot_end, minutes_until_limit)
if actual_minutes_left < 9999:
# 统一去掉秒数的影响,只按当前分钟整点计算
base_time = now_dt.replace(second=0, microsecond=0)
shutdown_dt = base_time + datetime.timedelta(minutes=actual_minutes_left)
shutdown_time_str = shutdown_dt.strftime("%H:%M")
self.lbl_shutdown_time.config(text=shutdown_time_str)
# 更新警告信息
if self.sys_ctrl.is_shutdown_scheduled():
# 修改警告样式
self.lbl_info.config(text=f"⚠️ 系统将在 {shutdown_time_str} 关机", fg="red")
elif (limit_mins - used_mins) < (self.cfg.warning_time // 60):
remain = max(0, limit_mins - used_mins)
self.lbl_info.config(text=f"⚠️ 剩余时间不足 {remain} 分钟", fg="#ff9900")
else:
self.lbl_info.config(text="✅ 正常使用中", fg="green")
def app_monitor_loop(self):
while True:
self.sys_ctrl.kill_blacklisted_apps()
time.sleep(10)
# ================= 对话框与交互 =================
def verify_password(self):
"""密码验证"""
if not self.cfg.password_enabled:
return True
pwd = simpledialog.askstring("密码验证", "请输入管理员密码:", show="*", parent=self.root)
if pwd == self.cfg.password:
return True
if pwd is not None:
messagebox.showerror("错误", "密码错误")
return False
def show_manual_shutdown(self):
"""手动关机对话框"""
if not self.verify_password(): return
win = tk.Toplevel(self.root)
win.title("手动关机设置")
win.geometry("300x250")
win.resizable(False, False)
win.grab_set()
win.geometry(f"+{self.root.winfo_x()+30}+{self.root.winfo_y()+70}")
v_mode = tk.IntVar(value=1) # 1=倒计时, 2=定时
tk.Label(win, text="请选择关机方式:", font=("微软雅黑", 10, "bold")).pack(pady=10)
f1 = tk.Frame(win)
f1.pack(fill=tk.X, padx=20, pady=5)
tk.Radiobutton(f1, text="倒计时关机", variable=v_mode, value=1).pack(side=tk.LEFT)
v_minutes = tk.StringVar(value="30")
tk.Entry(f1, textvariable=v_minutes, width=5).pack(side=tk.LEFT, padx=5)
tk.Label(f1, text="分钟后").pack(side=tk.LEFT)
f2 = tk.Frame(win)
f2.pack(fill=tk.X, padx=20, pady=5)
tk.Radiobutton(f2, text="指定时间关机", variable=v_mode, value=2).pack(side=tk.LEFT)
now_next = datetime.datetime.now() + datetime.timedelta(hours=1)
v_time = tk.StringVar(value=now_next.strftime("%H:%M"))
tk.Entry(f2, textvariable=v_time, width=8).pack(side=tk.LEFT, padx=5)
def apply():
try:
delay = 0
target_dt = None
if v_mode.get() == 1:
mins = int(v_minutes.get())
if mins <= 0: raise ValueError
delay = mins * 60
target_dt = datetime.datetime.now() + datetime.timedelta(seconds=delay)
else:
t_str = v_time.get()
th, tm = map(int, t_str.split(":"))
now = datetime.datetime.now()
target_dt = now.replace(hour=th, minute=tm, second=0, microsecond=0)
if target_dt <= now:
target_dt += datetime.timedelta(days=1)
delay = int((target_dt - now).total_seconds())
# 构造包含时间的提示信息
time_str = target_dt.strftime("%H:%M")
reason = f"家长手动设置{time_str}关机"
self.sys_ctrl.cancel_shutdown()
self.sys_ctrl.shutdown(delay, reason)
self.manual_shutdown_mode = True
messagebox.showinfo("成功", f"已设置 {time_str} 关机")
win.destroy()
except:
messagebox.showerror("错误", "时间格式无效")
def cancel_manual():
self.sys_ctrl.cancel_shutdown()
self.manual_shutdown_mode = False
messagebox.showinfo("提示", "已取消手动关机计划,恢复自动管控")
win.destroy()
btn_box = tk.Frame(win, pady=20)
btn_box.pack()
tk.Button(btn_box, text="执行关机", command=apply, bg="#dc3545", fg="white").pack(side=tk.LEFT, padx=5)
tk.Button(btn_box, text="恢复自动", command=cancel_manual).pack(side=tk.LEFT, padx=5)
def show_settings(self):
"""设置对话框 (单页美化版)"""
if not self.verify_password(): return
win = tk.Toplevel(self.root)
win.title("系统设置")
win.geometry("380x450")
win.resizable(False, False)
win.grab_set()
win.geometry(f"+{self.root.winfo_x()+10}+{self.root.winfo_y()+10}")
# 容器
container = tk.Frame(win, padx=15, pady=15)
container.pack(fill=tk.BOTH, expand=True)
# 1. 常规设置
lf1 = tk.LabelFrame(container, text="基本设置", font=("微软雅黑", 9, "bold"), fg="#0d6efd", padx=10, pady=10)
lf1.pack(fill=tk.X, pady=(0, 10))
tk.Label(lf1, text="每日限额 (分钟):").grid(row=0, column=0, sticky=tk.W, pady=5)
v_limit = tk.StringVar(value=str(self.cfg.daily_limit))
tk.Entry(lf1, textvariable=v_limit, width=10).grid(row=0, column=1, sticky=tk.W, padx=10)
# 2. 时间段
lf2 = tk.LabelFrame(container, text="今日时段配置", font=("微软雅黑", 9, "bold"), fg="#0d6efd", padx=10, pady=10)
lf2.pack(fill=tk.X, pady=(0, 10))
weekday = datetime.datetime.now().weekday() + 1
tk.Label(lf2, text=f"今天是周{weekday},允许时间段:", anchor=tk.W).pack(fill=tk.X)
config = configparser.ConfigParser()
config.read(self.cfg.config_file)
current_slots_str = config.get('TimeLimits', str(weekday), fallback="")
v_slots = tk.StringVar(value=current_slots_str)
tk.Entry(lf2, textvariable=v_slots).pack(fill=tk.X, pady=5)
tk.Label(lf2, text="示例: 09:00-12:00,14:00-18:00\n(修改仅今日生效,永久修改请编辑config.ini)",
fg="gray", font=("微软雅黑", 8), justify=tk.LEFT).pack(anchor=tk.W)
# 3. 安全设置
lf3 = tk.LabelFrame(container, text="安全验证", font=("微软雅黑", 9, "bold"), fg="#0d6efd", padx=10, pady=10)
lf3.pack(fill=tk.X, pady=(0, 10))
v_pwd_enabled = tk.BooleanVar(value=self.cfg.password_enabled)
tk.Checkbutton(lf3, text="启用密码保护", variable=v_pwd_enabled).pack(anchor=tk.W)
f_pwd = tk.Frame(lf3)
f_pwd.pack(fill=tk.X, pady=(5, 0))
tk.Label(f_pwd, text="管理密码:").pack(side=tk.LEFT)
v_pwd = tk.StringVar(value=self.cfg.password)
tk.Entry(f_pwd, textvariable=v_pwd, show="*", width=15).pack(side=tk.LEFT, padx=10)
# 底部按钮
def save():
try:
limit = int(v_limit.get())
slots = v_slots.get().strip()
pwd = v_pwd.get().strip()
# 简单验证
self.cfg._parse_time_slots(slots)
updates = {
'TimeLimits': {
'DailyLimit': limit,
'Password': pwd,
'PasswordEnabled': '1' if v_pwd_enabled.get() else '0',
str(weekday): slots
}
}
if self.cfg.save_config(updates):
messagebox.showinfo("成功", "设置已保存")
win.destroy()
else:
messagebox.showerror("错误", "保存失败")
except Exception as e:
messagebox.showerror("错误", f"输入无效: {e}")
tk.Button(container, text="保存设置", command=save, bg="#0d6efd", fg="white",
padx=20, pady=5, relief=tk.FLAT).pack(side=tk.BOTTOM)
def show_blacklist(self):
"""黑名单管理 (内嵌添加功能)"""
if not self.verify_password(): return
win = tk.Toplevel(self.root)
win.title("应用黑名单")
win.geometry("500x500") # 增加高度以容纳提示
win.grab_set()
# 顶部添加区域
add_frame = tk.LabelFrame(win, text="添加新规则", padx=10, pady=10)
add_frame.pack(fill=tk.X, padx=10, pady=10)
f_row = tk.Frame(add_frame)
f_row.pack(fill=tk.X)
tk.Label(f_row, text="应用名:").pack(side=tk.LEFT)
v_name = tk.StringVar()
tk.Entry(f_row, textvariable=v_name, width=10).pack(side=tk.LEFT, padx=5)
tk.Label(f_row, text="进程名(.exe):").pack(side=tk.LEFT)
v_proc = tk.StringVar()
tk.Entry(f_row, textvariable=v_proc, width=15).pack(side=tk.LEFT, padx=5)
# 提示信息
tk.Label(add_frame, text="提示: 进程名必须包含 .exe 后缀,如 chrome.exe",
fg="gray", font=("微软雅黑", 8)).pack(anchor=tk.W, pady=(5, 0))
# Treeview区域
list_frame = tk.Frame(win, padx=10)
list_frame.pack(fill=tk.BOTH, expand=True)
columns = ("name", "proc", "status")
tree = ttk.Treeview(list_frame, columns=columns, show="headings")
tree.heading("name", text="应用名称")
tree.heading("proc", text="进程名")
tree.heading("status", text="状态")
tree.column("name", width=120)
tree.column("proc", width=120)
tree.column("status", width=60)
scroll = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=tree.yview)
tree.configure(yscrollcommand=scroll.set)
tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scroll.pack(side=tk.RIGHT, fill=tk.Y)
def refresh_list():
for item in tree.get_children():
tree.delete(item)
for k, v in self.cfg.app_blacklist.items():
status = "已禁用" if v['enabled'] else "未禁用" # 修改状态文本
tree.insert("", tk.END, values=(v['display_name'], v['process_name'], status), tags=(k,))
refresh_list()
def do_add():
name = v_name.get().strip()
proc = v_proc.get().strip()
if not name or not proc:
messagebox.showwarning("提示", "请填写完整信息")
return
key = proc.lower()
self.cfg.app_blacklist[key] = {
"enabled": True, "process_name": proc, "display_name": name
}
self.cfg.save_blacklist()
v_name.set("")
v_proc.set("")
refresh_list()
tk.Button(f_row, text="添加", command=do_add, bg="#28a745", fg="white").pack(side=tk.LEFT, padx=10)
# 底部操作栏
btn_frame = tk.Frame(win, pady=10)
btn_frame.pack(fill=tk.X)
def toggle_app():
sel = tree.selection()
if not sel: return
key = tree.item(sel[0], "tags")[0]
self.cfg.app_blacklist[key]['enabled'] = not self.cfg.app_blacklist[key]['enabled']
self.cfg.save_blacklist()
refresh_list()
def del_app():
sel = tree.selection()
if not sel: return
key = tree.item(sel[0], "tags")[0]
if messagebox.askyesno("确认", "删除此规则?"):
del self.cfg.app_blacklist[key]
self.cfg.save_blacklist()
refresh_list()
tk.Button(btn_frame, text="切换 禁用/解禁", command=toggle_app).pack(side=tk.LEFT, padx=20, expand=True)
tk.Button(btn_frame, text="删除选中规则", command=del_app, fg="red").pack(side=tk.LEFT, padx=20, expand=True)
def exit_app(self):
"""完全退出 (由X按钮触发)"""
if not self.verify_password(): return
if messagebox.askyesno("确认", "确定要退出管控吗?\n退出后将不再限制使用时间。"):
self.sys_ctrl.cancel_shutdown()
self.root.destroy()
sys.exit(0)
def main():
# 隐藏控制台 (Windows Only)
if platform.system() == "Windows":
try:
import ctypes
ctypes.windll.user32.ShowWindow(ctypes.windll.kernel32.GetConsoleWindow(), 0)
except:
pass
root = tk.Tk()
app = ChildControlApp(root)
root.mainloop()
if __name__ == "__main__":
main()