import
tkinter as tk
from
tkinter
import
ttk, messagebox
import
psutil
import
time
import
os
import
threading
import
json
from
datetime
import
datetime, timedelta
import
ctypes
from
threading
import
Lock
import
sys
def
is_admin():
try
:
return
ctypes.windll.shell32.IsUserAnAdmin()
except
Exception:
return
False
if
not
is_admin():
ctypes.windll.shell32.ShellExecuteW(
None
,
"runas"
, sys.executable,
" "
.join(sys.argv),
None
,
1
)
sys.exit()
CONFIG_FILE
=
"config.json"
class
ShutdownManagerApp:
def
__init__(
self
, root):
self
.root
=
root
self
.root.title(
"关机助手"
)
self
.root.geometry(
"900x800"
)
self
.lock
=
Lock()
self
.config
=
self
.load_config()
self
.shutdown_scheduled
=
False
self
.style
=
ttk.Style()
self
.style.theme_use(
"clam"
)
self
.root.configure(bg
=
"#ecf0f1"
) # 淡雅灰蓝背景
self
.style.configure(
"Title.TLabel"
,
font
=
(
"Segoe UI"
,
16
,
"bold"
),
foreground
=
"#2c3e50"
,
background
=
"#ecf0f1"
)
self
.style.configure(
"Speed.TLabel"
,
font
=
(
"Consolas"
,
28
,
"bold"
),
foreground
=
"#27ae60"
,
background
=
"#ecf0f1"
,
padding
=
10
)
self
.style.configure(
"Primary.TButton"
,
font
=
(
"Segoe UI"
,
12
,
"bold"
),
foreground
=
"white"
,
background
=
"#2980b9"
,
padding
=
6
)
self
.style.
map
(
"Primary.TButton"
,
background
=
[(
"active"
,
"#1f6391"
)])
self
.style.configure(
"Secondary.TButton"
,
font
=
(
"Segoe UI"
,
12
),
foreground
=
"white"
,
background
=
"#7f8c8d"
,
padding
=
6
)
self
.style.
map
(
"Secondary.TButton"
,
background
=
[(
"active"
,
"#606b6d"
)])
self
.style.configure(
"Danger.TButton"
,
font
=
(
"Segoe UI"
,
12
),
foreground
=
"white"
,
background
=
"#e74c3c"
,
padding
=
6
)
self
.style.
map
(
"Danger.TButton"
,
background
=
[(
"active"
,
"#c0392b"
)])
self
.style.configure(
"Warning.TButton"
,
font
=
(
"Segoe UI"
,
12
),
foreground
=
"white"
,
background
=
"#f39c12"
,
padding
=
6
)
self
.style.
map
(
"Warning.TButton"
,
background
=
[(
"active"
,
"#d35400"
)])
self
.create_widgets()
self
.setup_config()
self
.running_tasks
=
{
"monitor"
:
False
,
"timer"
:
False
}
self
.check_admin()
def
check_admin(
self
):
if
not
self
.is_admin():
messagebox.showwarning(
"权限提示"
,
"请以管理员身份运行本程序以确保关机功能正常!"
)
def
is_admin(
self
):
try
:
return
ctypes.windll.shell32.IsUserAnAdmin()
except
Exception:
return
False
def
load_config(
self
):
default_config
=
{
"unit"
:
"KB/s"
,
"monitor"
: {
"interface"
: "
", "
threshold
": 10, "
duration
": 5, "
interval":
5
},
"timer"
: {
"time"
:
"23:00"
}
}
try
:
with
open
(CONFIG_FILE,
'r'
, encoding
=
'utf-8'
) as f:
user_config
=
json.load(f)
return
{
*
*
default_config,
*
*
user_config}
except
Exception:
return
default_config
def
save_config(
self
):
with
open
(CONFIG_FILE,
'w'
, encoding
=
'utf-8'
) as f:
json.dump(
self
.config, f, ensure_ascii
=
False
, indent
=
4
)
def
setup_config(
self
):
if
'geometry'
in
self
.config:
self
.root.geometry(
self
.config[
'geometry'
])
self
.root.after(
30000
,
self
.auto_save_config)
def
auto_save_config(
self
):
self
.save_config()
self
.root.after(
30000
,
self
.auto_save_config)
def
create_widgets(
self
):
main_frame
=
ttk.Frame(
self
.root, padding
=
15
)
main_frame.pack(fill
=
tk.BOTH, expand
=
True
)
control_frame
=
ttk.Frame(main_frame)
control_frame.pack(fill
=
tk.X, pady
=
10
)
status_frame
=
ttk.Frame(main_frame)
status_frame.pack(fill
=
tk.BOTH, expand
=
True
, pady
=
10
)
action_frame
=
ttk.Frame(main_frame)
action_frame.pack(fill
=
tk.X, pady
=
10
)
self
.notebook
=
ttk.Notebook(status_frame)
self
.notebook.pack(fill
=
tk.BOTH, expand
=
True
)
self
.monitor_tab
=
self
.create_monitor_tab()
self
.notebook.add(
self
.monitor_tab, text
=
"网卡监控模式"
)
self
.timer_tab
=
self
.create_timer_tab()
self
.notebook.add(
self
.timer_tab, text
=
"定时关机模式"
)
self
.status_bar
=
ttk.Label(action_frame, text
=
"就绪"
, relief
=
tk.SUNKEN,
anchor
=
"w"
, background
=
"#bdc3c7"
, foreground
=
"#2c3e50"
, padding
=
5
)
self
.status_bar.pack(side
=
tk.BOTTOM, fill
=
tk.X)
def
create_monitor_tab(
self
):
frame
=
ttk.Frame(
self
.notebook, padding
=
10
)
ttk.Label(frame, text
=
"智能监控"
, style
=
"Title.TLabel"
).grid(row
=
0
, column
=
0
, sticky
=
tk.W, columnspan
=
3
, pady
=
(
0
,
10
))
ttk.Label(frame, text
=
"监控接口:"
, background
=
"#ecf0f1"
, font
=
(
"Segoe UI"
,
12
)).grid(row
=
1
, column
=
0
, pady
=
5
, sticky
=
tk.W)
self
.interface_var
=
tk.StringVar()
self
.interface_combobox
=
ttk.Combobox(frame, textvariable
=
self
.interface_var, width
=
20
, state
=
"readonly"
)
self
.interface_combobox.grid(row
=
1
, column
=
1
, pady
=
5
, sticky
=
tk.W)
ttk.Button(frame, text
=
"刷新"
, command
=
self
.refresh_interfaces, style
=
"Secondary.TButton"
).grid(row
=
1
, column
=
2
, padx
=
5
)
speed_frame
=
ttk.LabelFrame(frame, text
=
"实时速度监控"
, padding
=
10
)
speed_frame.grid(row
=
2
, column
=
0
, columnspan
=
3
, sticky
=
tk.EW, pady
=
10
)
self
.speed_label
=
ttk.Label(speed_frame, text
=
"0.00 KB/s"
, style
=
"Speed.TLabel"
)
self
.speed_label.pack(pady
=
20
)
threshold_frame
=
ttk.LabelFrame(frame, text
=
"关机条件"
, padding
=
10
)
threshold_frame.grid(row
=
3
, column
=
0
, columnspan
=
3
, sticky
=
tk.EW, pady
=
10
)
ttk.Label(threshold_frame, text
=
"持续低速阈值(KB/s):"
, font
=
(
"Segoe UI"
,
11
)).grid(row
=
0
, column
=
0
, pady
=
5
, sticky
=
tk.W)
self
.threshold_entry
=
ttk.Entry(threshold_frame, width
=
10
, font
=
(
"Segoe UI"
,
11
))
self
.threshold_entry.insert(
0
,
"10"
)
self
.threshold_entry.grid(row
=
0
, column
=
1
, pady
=
5
, sticky
=
tk.W)
ttk.Label(threshold_frame, text
=
"持续时长(分钟):"
, font
=
(
"Segoe UI"
,
11
)).grid(row
=
1
, column
=
0
, pady
=
5
, sticky
=
tk.W)
self
.duration_entry
=
ttk.Entry(threshold_frame, width
=
10
, font
=
(
"Segoe UI"
,
11
))
self
.duration_entry.insert(
0
,
"5"
)
self
.duration_entry.grid(row
=
1
, column
=
1
, pady
=
5
, sticky
=
tk.W)
ttk.Label(threshold_frame, text
=
"采样间隔(秒):"
, font
=
(
"Segoe UI"
,
11
)).grid(row
=
2
, column
=
0
, pady
=
5
, sticky
=
tk.W)
self
.interval_entry
=
ttk.Entry(threshold_frame, width
=
10
, font
=
(
"Segoe UI"
,
11
))
self
.interval_entry.insert(
0
,
"5"
)
self
.interval_entry.grid(row
=
2
, column
=
1
, pady
=
5
, sticky
=
tk.W)
self
.monitor_btn
=
ttk.Button(frame, text
=
"开始监控"
, style
=
"Primary.TButton"
, command
=
self
.toggle_monitor)
self
.monitor_btn.grid(row
=
4
, column
=
0
, pady
=
15
, sticky
=
tk.W)
log_frame
=
ttk.Frame(frame)
log_frame.grid(row
=
5
, column
=
0
, columnspan
=
3
, sticky
=
"nsew"
, padx
=
10
, pady
=
5
)
frame.rowconfigure(
5
, weight
=
1
)
frame.columnconfigure(
0
, weight
=
1
)
self
.monitor_status
=
tk.Text(log_frame, height
=
8
, state
=
tk.DISABLED, font
=
(
"Consolas"
,
10
), bg
=
"#ecf0f1"
, wrap
=
"word"
)
self
.monitor_status.pack(side
=
tk.LEFT, fill
=
tk.BOTH, expand
=
True
)
monitor_scroll
=
ttk.Scrollbar(log_frame, orient
=
tk.VERTICAL, command
=
self
.monitor_status.yview)
monitor_scroll.pack(side
=
tk.RIGHT, fill
=
tk.Y)
self
.monitor_status.config(yscrollcommand
=
monitor_scroll.
set
)
return
frame
def
create_timer_tab(
self
):
frame
=
ttk.Frame(
self
.notebook, padding
=
10
)
ttk.Label(frame, text
=
"定时关机"
, style
=
"Title.TLabel"
).grid(row
=
0
, column
=
0
, sticky
=
tk.W, columnspan
=
3
, pady
=
(
0
,
10
))
ttk.Label(frame, text
=
"关机时间(HH:MM):"
, background
=
"#ecf0f1"
, font
=
(
"Segoe UI"
,
12
)).grid(row
=
1
, column
=
0
, pady
=
10
, sticky
=
tk.W)
self
.time_entry
=
ttk.Entry(frame, width
=
15
, font
=
(
"Segoe UI"
,
11
))
self
.time_entry.insert(
0
,
self
.config[
'timer'
][
'time'
])
self
.time_entry.grid(row
=
1
, column
=
1
, pady
=
10
, sticky
=
tk.W)
quick_frame
=
ttk.Frame(frame)
quick_frame.grid(row
=
1
, column
=
2
, pady
=
10
, sticky
=
tk.W)
ttk.Button(quick_frame, text
=
"1小时后"
, style
=
"Secondary.TButton"
, command
=
lambda
:
self
.set_quick_time(
1
)).grid(row
=
0
, column
=
0
, padx
=
5
)
ttk.Button(quick_frame, text
=
"2小时后"
, style
=
"Secondary.TButton"
, command
=
lambda
:
self
.set_quick_time(
2
)).grid(row
=
0
, column
=
1
, padx
=
5
)
action_frame
=
ttk.Frame(frame)
action_frame.grid(row
=
2
, column
=
0
, columnspan
=
3
, pady
=
15
)
self
.timer_btn
=
ttk.Button(action_frame, text
=
"设置定时"
, style
=
"Primary.TButton"
, command
=
self
.toggle_timer)
self
.timer_btn.grid(row
=
0
, column
=
0
, padx
=
10
)
self
.shutdown_timer_btn
=
ttk.Button(action_frame, text
=
"立即关机"
, style
=
"Danger.TButton"
, command
=
self
.toggle_shutdown_timer)
self
.shutdown_timer_btn.grid(row
=
0
, column
=
1
, padx
=
10
)
log_frame
=
ttk.Frame(frame)
log_frame.grid(row
=
3
, column
=
0
, columnspan
=
3
, sticky
=
"nsew"
, padx
=
10
, pady
=
5
)
frame.rowconfigure(
3
, weight
=
1
)
frame.columnconfigure(
0
, weight
=
1
)
self
.timer_status
=
tk.Text(log_frame, height
=
8
, state
=
tk.DISABLED, font
=
(
"Consolas"
,
10
), bg
=
"#ecf0f1"
, wrap
=
"word"
)
self
.timer_status.pack(side
=
tk.LEFT, fill
=
tk.BOTH, expand
=
True
)
timer_scroll
=
ttk.Scrollbar(log_frame, orient
=
tk.VERTICAL, command
=
self
.timer_status.yview)
timer_scroll.pack(side
=
tk.RIGHT, fill
=
tk.Y)
self
.timer_status.config(yscrollcommand
=
timer_scroll.
set
)
return
frame
def
set_quick_time(
self
, hours):
now
=
datetime.now()
target
=
now
+
timedelta(hours
=
hours)
self
.time_entry.delete(
0
, tk.END)
self
.time_entry.insert(
0
, target.strftime(
"%H:%M"
))
def
refresh_interfaces(
self
):
interfaces
=
psutil.net_io_counters(pernic
=
True
).keys()
self
.interface_combobox[
'values'
]
=
list
(interfaces)
if
interfaces
and
not
self
.interface_var.get():
self
.interface_var.
set
(
next
(
iter
(interfaces)))
def
toggle_monitor(
self
):
with
self
.lock:
if
not
self
.running_tasks[
'monitor'
]:
try
:
interface
=
self
.interface_var.get()
threshold
=
float
(
self
.threshold_entry.get())
duration
=
float
(
self
.duration_entry.get())
*
60
interval
=
float
(
self
.interval_entry.get())
except
Exception as e:
messagebox.showerror(
"输入错误"
, f
"无效参数: {str(e)}"
)
return
self
.running_tasks[
'monitor'
]
=
True
self
.monitor_btn.config(text
=
"停止监控"
)
self
.log_monitor(
"=== 监控模式启动 ==="
)
self
.log_monitor(f
"网卡: {interface}"
)
self
.log_monitor(f
"阈值: {threshold:.2f} KB/s"
)
self
.log_monitor(f
"持续时长: {duration / 60} 分钟"
)
threading.Thread(
target
=
self
.monitor_loop,
args
=
(interface, threshold, duration, interval),
daemon
=
True
).start()
else
:
self
.running_tasks[
'monitor'
]
=
False
self
.monitor_btn.config(text
=
"开始监控"
)
self
.log_monitor(
"监控已停止"
)
def
monitor_loop(
self
, interface, threshold, duration, interval):
try
:
last_bytes
=
psutil.net_io_counters(pernic
=
True
)[interface].bytes_recv
start_time
=
None
while
self
.running_tasks[
'monitor'
]:
current_bytes
=
psutil.net_io_counters(pernic
=
True
)[interface].bytes_recv
speed
=
(current_bytes
-
last_bytes)
/
interval
/
1024
last_bytes
=
current_bytes
self
.root.after(
0
,
lambda
s
=
speed:
self
.speed_label.config(text
=
f
"{s:.2f} KB/s"
))
status_msg
=
f
"{datetime.now().strftime('%H:%M:%S')} - 速度: {speed:.2f} KB/s | "
if
speed < threshold:
if
start_time
is
None
:
start_time
=
time.time()
elapsed
=
time.time()
-
start_time
status_msg
+
=
f
"倒计时: {max(duration - elapsed, 0):.0f}s"
if
elapsed >
=
duration:
self
.log_monitor(
"== 达到关机条件 =="
)
self
.log_monitor(f
"速度连续 {duration / 60} 分钟低于阈值"
)
self
.execute_shutdown(
60
)
break
else
:
start_time
=
None
status_msg
+
=
"状态: 正常"
self
.log_monitor(status_msg)
time.sleep(interval)
except
KeyError:
self
.log_monitor(f
"错误:网卡 {interface} 不存在或已断开!"
)
except
Exception as e:
self
.log_monitor(f
"监控错误: {str(e)}"
)
finally
:
self
.running_tasks[
'monitor'
]
=
False
self
.root.after(
0
,
lambda
:
self
.monitor_btn.config(text
=
"开始监控"
))
def
toggle_timer(
self
):
with
self
.lock:
if
not
self
.running_tasks[
'timer'
]:
time_str
=
self
.time_entry.get()
try
:
shutdown_time
=
datetime.strptime(time_str,
"%H:%M"
)
now
=
datetime.now()
target
=
now.replace(hour
=
shutdown_time.hour, minute
=
shutdown_time.minute, second
=
0
)
if
target < now:
target
+
=
timedelta(days
=
1
)
delay
=
(target
-
now).total_seconds()
if
delay <
60
:
raise
ValueError(
"关机时间必须大于当前时间至少1分钟"
)
except
Exception as e:
messagebox.showerror(
"输入错误"
, f
"无效时间格式: {str(e)}\n请输入HH:MM格式(24小时制)"
)
return
self
.running_tasks[
'timer'
]
=
True
self
.timer_btn.config(text
=
"取消定时"
)
self
.log_timer(
"=== 定时关机设置 ==="
)
self
.log_timer(f
"目标时间: {target.strftime('%Y-%m-%d %H:%M')}"
)
self
.log_timer(f
"当前时间: {now.strftime('%H:%M:%S')}"
)
threading.Thread(
target
=
self
.timer_loop,
args
=
(delay,),
daemon
=
True
).start()
else
:
self
.running_tasks[
'timer'
]
=
False
self
.timer_btn.config(text
=
"设置定时"
)
self
.log_timer(
"定时已取消"
)
def
timer_loop(
self
, delay):
start_time
=
time.time()
while
self
.running_tasks[
'timer'
]
and
(time.time()
-
start_time) < delay:
remaining
=
delay
-
(time.time()
-
start_time)
self
.log_timer(f
"剩余时间: {str(timedelta(seconds=int(remaining)))}"
)
time.sleep(
1
)
if
self
.running_tasks[
'timer'
]:
self
.log_timer(
"== 定时关机时间到 =="
)
self
.execute_shutdown(
0
)
self
.running_tasks[
'timer'
]
=
False
def
toggle_shutdown_timer(
self
):
if
not
self
.shutdown_scheduled:
os.system(
"shutdown -s -t 60"
)
self
.log_timer(
"已安排关机,60秒后关机"
)
self
.shutdown_timer_btn.config(text
=
"取消关机"
)
self
.shutdown_scheduled
=
True
self
.status_bar.config(text
=
"60秒后关机(输入 shutdown -a 可取消)"
)
else
:
os.system(
"shutdown -a"
)
self
.log_timer(
"已取消关机计划"
)
self
.shutdown_timer_btn.config(text
=
"立即关机"
)
self
.shutdown_scheduled
=
False
self
.status_bar.config(text
=
"关机计划已取消"
)
def
execute_shutdown(
self
, delay
=
60
):
try
:
self
.log_monitor(f
"准备关机,{delay}秒后关闭系统..."
)
self
.log_timer(f
"系统将在{delay}秒后关闭"
)
os.system(f
"shutdown -s -t {max(delay, 10)}"
)
self
.status_bar.config(text
=
f
"{delay}秒后关机(输入 shutdown -a 可取消)"
)
except
Exception as e:
self
.log_monitor(f
"关机失败: {str(e)}"
)
def
cancel_shutdown(
self
):
result
=
os.system(
"shutdown -a"
)
if
result
=
=
0
:
self
.log_timer(
"已取消关机计划"
)
else
:
self
.log_timer(
"无活动的关机计划"
)
self
.status_bar.config(text
=
"关机计划已取消"
)
def
log_monitor(
self
, msg):
self
.monitor_status.config(state
=
tk.NORMAL)
if
int
(
self
.monitor_status.index(
'end-1c'
).split(
'.'
)[
0
]) >
500
:
self
.monitor_status.delete(
"1.0"
,
"3.0"
)
self
.monitor_status.insert(tk.END, msg
+
"\n"
)
self
.monitor_status.yview_moveto(
1.0
)
self
.monitor_status.config(state
=
tk.DISABLED)
print
(f
"[MONITOR] {msg}"
)
def
log_timer(
self
, msg):
self
.timer_status.config(state
=
tk.NORMAL)
self
.timer_status.insert(tk.END, msg
+
"\n"
)
self
.timer_status.yview_moveto(
1.0
)
self
.timer_status.config(state
=
tk.DISABLED)
print
(f
"[TIMER] {msg}"
)
def
on_closing(
self
):
self
.config[
'geometry'
]
=
self
.root.geometry()
self
.save_config()
self
.root.destroy()
if
__name__
=
=
"__main__"
:
root
=
tk.Tk()
app
=
ShutdownManagerApp(root)
root.protocol(
"WM_DELETE_WINDOW"
, app.on_closing)
root.mainloop()