[Asm] 纯文本查看 复制代码
import time
import sys
import os
import ctypes
import win32gui
import threading
from datetime import datetime
from pywinauto import keyboard
from pywinauto import Application
import subprocess
import tkinter as tk
from tkinter import scrolledtext, ttk, messagebox
# ---------------------- 打包适配配置 ----------------------
# 获取程序运行路径(适配exe打包)
def get_resource_path(relative_path):
"""获取资源路径,适配pyinstaller打包"""
if hasattr(sys, '_MEIPASS'):
# 打包后的临时目录
base_path = sys._MEIPASS
else:
# 开发环境的当前目录
base_path = os.path.abspath(".")
return os.path.join(base_path, relative_path)
# ---------------------- 核心配置 ----------------------
JIYU_WINDOW_TITLE = "极域课堂管理系统软件V6.0 2016 豪华版"
BLACK_SCREEN_KEYWORD = "黑屏安静"
CANCEL_BLACK_KEYWORD = "取消黑屏安静"
Remote_Command_KEYWORD = "远程命令"
# 极域程序路径(改为更通用的路径,也可由用户自行配置)
JIYU_EXE_PATH = r"C:\Program Files (x86)\Mythware\极域课堂管理系统软件V6.0 2016 豪华版\TeacherMain.exe"
# 全局变量
is_running = False
control_thread = None
log_cache = [] # 日志缓存列表
MAX_LOG_LINES = 5 # 最大显示日志行数
# 星期映射关系
WEEKDAY_MAP = {
0: "周一",
1: "周二",
2: "周三",
3: "周四",
4: "周五",
5: "周六",
6: "周日"
}
# 黑屏时间段数量
BLACK_PERIOD_COUNT = 5
# ---------------------- 管理员权限检查 ----------------------
def is_admin():
"""检查是否管理员权限"""
try:
return ctypes.windll.shell32.IsUserAnAdmin()
except:
return False
def run_as_admin():
"""以管理员权限重启程序"""
if not is_admin():
# 重新启动程序并请求管理员权限
ctypes.windll.shell32.ShellExecuteW(
None,
"runas",
sys.executable,
" ".join(sys.argv),
None,
1
)
sys.exit(0)
# ---------------------- 后台连接极域程序 ----------------------
def get_jiyu_hwnd(title_keyword):
"""获取极域窗口句柄"""
hwnd_list = []
def enum_windows_callback(hwnd, extra):
window_title = win32gui.GetWindowText(hwnd)
if title_keyword in window_title and win32gui.IsWindowEnabled(hwnd):
hwnd_list.append(hwnd)
return True
win32gui.EnumWindows(enum_windows_callback, None)
return hwnd_list[0] if hwnd_list else None
def connect_jiyu_window_background():
"""连接极域窗口"""
log_message(f"尝试连接极域窗口...")
try:
# 检查极域程序是否存在
if not os.path.exists(JIYU_EXE_PATH):
log_message(f"极域程序路径不存在:{JIYU_EXE_PATH}")
return None
# 启动极域程序
subprocess.Popen(JIYU_EXE_PATH)
time.sleep(3) # 增加等待时间
jiyu_hwnd = get_jiyu_hwnd(JIYU_WINDOW_TITLE)
if not jiyu_hwnd:
time.sleep(2)
keyboard.send_keys("1{ENTER}")
time.sleep(3)
log_message(f"未找到极域窗口,重试...")
jiyu_hwnd = get_jiyu_hwnd(JIYU_WINDOW_TITLE)
try:
# 连接极域窗口
app = Application(backend="win32").connect(handle=jiyu_hwnd, timeout=10)
main_win = app.window(handle=jiyu_hwnd)
log_message(f"成功连接极域窗口")
return main_win
except Exception as e:
log_message(f"后台连接失败:{str(e)}")
return None
except Exception as e:
log_message(f"连接出错:{str(e)}")
return None
# ---------------------- 操作极域控件 ----------------------
def find_control_by_keyword(main_win, keyword):
"""查找指定控件"""
if not main_win:
return None
try:
all_controls = main_win.descendants()
for ctrl in all_controls:
try:
txt = ctrl.window_text().strip()
if keyword in txt:
return ctrl
except:
continue
return None
except Exception as e:
log_message(f"查找控件出错:{str(e)}")
return None
def click_Remote_Command():
"""执行远程命令"""
main_win = connect_jiyu_window_background()
if not main_win:
return
try:
Remote_ctrl = find_control_by_keyword(main_win, Remote_Command_KEYWORD)
if Remote_ctrl:
try:
Remote_ctrl.click()
except:
Remote_ctrl.invoke()
time.sleep(2)
keyboard.send_keys("{VK_DOWN 6}{ENTER}")
log_message(f"触发远程命令成功")
else:
log_message(f"未找到远程命令控件")
except Exception as e:
log_message(f"远程命令失败:{str(e)}")
def click_black_screen_background():
"""执行黑屏操作"""
main_win = connect_jiyu_window_background()
if not main_win:
return
try:
black_ctrl = find_control_by_keyword(main_win, BLACK_SCREEN_KEYWORD)
if black_ctrl:
try:
black_ctrl.click()
except:
black_ctrl.invoke()
log_message(f"触发黑屏成功")
else:
log_message(f"未找到黑屏控件")
except Exception as e:
log_message(f"黑屏失败:{str(e)}")
def click_cancel_black_screen_background():
"""执行取消黑屏操作"""
main_win = connect_jiyu_window_background()
if not main_win:
return
try:
cancel_ctrl = find_control_by_keyword(main_win, CANCEL_BLACK_KEYWORD)
if cancel_ctrl:
try:
cancel_ctrl.click()
except:
cancel_ctrl.invoke()
log_message(f"触发取消黑屏成功")
else:
log_message(f"未找到取消黑屏控件")
except Exception as e:
log_message(f"取消黑屏失败:{str(e)}")
# ---------------------- 时间判断逻辑 ----------------------
def get_selected_weekdays(checkboxes):
"""获取选中的星期"""
return [i for i, var in checkboxes.items() if var.get()]
def parse_time_entry(entry):
"""解析时间输入框内容"""
try:
value = entry.get().strip()
# 空值返回None(表示该时间段未启用)
if not value:
return None
if ":" in value:
hour, minute = map(int, value.split(":"))
if 0 <= hour < 24 and 0 <= minute < 60:
return hour * 60 + minute
return None
except:
return None
def is_in_any_black_period(black_periods):
"""判断当前是否在任意一个黑屏时间段内"""
now = datetime.now()
current_weekday = now.weekday()
current_total = now.hour * 60 + now.minute
for i in range(BLACK_PERIOD_COUNT):
# 获取第i个黑屏时间段的配置
start_entry = black_periods[i]["start"]
end_entry = black_periods[i]["end"]
weekday_vars = black_periods[i]["weekdays"]
# 解析时间(空值表示该时间段未启用)
black_start = parse_time_entry(start_entry)
black_end = parse_time_entry(end_entry)
# 跳过未配置的时间段
if black_start is None or black_end is None:
continue
# 检查星期是否匹配
selected_weekdays = get_selected_weekdays(weekday_vars)
if current_weekday not in selected_weekdays:
continue
# 检查时间是否在范围内
if black_start <= black_end:
if black_start <= current_total <= black_end:
return True, f"黑屏时段{i+1}"
else:
if current_total >= black_start or current_total <= black_end:
return True, f"黑屏时段{i+1}"
return False, ""
def is_in_remote_period(remote_start_entry, remote_end_entry, remote_weekday_vars):
"""判断当前是否在远程命令时间段内"""
now = datetime.now()
current_weekday = now.weekday()
current_total = now.hour * 60 + now.minute
# 解析时间
remote_start = parse_time_entry(remote_start_entry)
remote_end = parse_time_entry(remote_end_entry)
# 未配置则返回False
if remote_start is None or remote_end is None:
return False, ""
# 检查星期
selected_weekdays = get_selected_weekdays(remote_weekday_vars)
if current_weekday not in selected_weekdays:
return False, ""
# 检查时间范围
if remote_start <= remote_end:
if remote_start <= current_total <= remote_end:
return True, "远程命令时段"
else:
if current_total >= remote_start or current_total <= remote_end:
return True, "远程命令时段"
return False, ""
# ---------------------- GUI相关函数 ----------------------
def log_message(message):
"""在界面上显示日志信息,只保留最新5条"""
global log_cache, MAX_LOG_LINES
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
log_text = f"[{timestamp}] {message}"
# 添加到缓存并截断为最新5条
log_cache.append(log_text)
if len(log_cache) > MAX_LOG_LINES:
log_cache = log_cache[-MAX_LOG_LINES:] # 只保留最后5条
# 在主线程中更新UI
def update_ui():
# 清空日志区域并重新插入最新5条
log_area.config(state=tk.NORMAL)
log_area.delete(1.0, tk.END)
for line in log_cache:
log_area.insert(tk.END, line + "\n")
log_area.see(tk.END) # 滚动到最后
log_area.config(state=tk.DISABLED) # 设为只读
root.after(0, update_ui)
def update_status():
"""更新程序状态显示"""
def set_status(text, color):
status_label.config(text=text, foreground=color)
if is_running:
set_status("程序运行中", "#2E8B57") # 海草绿
else:
set_status("程序已停止", "#DC143C") # 猩红
# 继续定时更新
root.after(1000, update_status)
def validate_time_inputs(black_periods, remote_start_entry, remote_end_entry, remote_weekday_vars):
"""验证时间输入是否有效"""
valid_black_periods = 0
# 验证所有黑屏时间段
for i in range(BLACK_PERIOD_COUNT):
start_entry = black_periods[i]["start"]
end_entry = black_periods[i]["end"]
weekday_vars = black_periods[i]["weekdays"]
# 空值表示未启用,跳过验证
if not start_entry.get().strip() or not end_entry.get().strip():
continue
# 验证时间格式
black_start = parse_time_entry(start_entry)
black_end = parse_time_entry(end_entry)
if not black_start or not black_end:
messagebox.showerror("输入错误", f"黑屏时间段{i+1}格式不正确,请使用HH:MM格式")
return False
# 检查是否选择了星期
if not get_selected_weekdays(weekday_vars):
messagebox.showwarning("选择提醒", f"黑屏时间段{i+1}未选择任何星期,该时间段将不会生效")
else:
valid_black_periods += 1
# 验证远程命令时间段
if remote_start_entry.get().strip() and remote_end_entry.get().strip():
remote_start = parse_time_entry(remote_start_entry)
remote_end = parse_time_entry(remote_end_entry)
if not remote_start or not remote_end:
messagebox.showerror("输入错误", "远程命令时间段格式不正确,请使用HH:MM格式")
return False
if not get_selected_weekdays(remote_weekday_vars):
messagebox.showwarning("选择提醒", "远程命令时间段未选择任何星期,该时间段将不会生效")
else:
valid_black_periods += 1
else:
# 远程命令时间段为空,跳过
pass
# 检查是否有至少一个有效时间段
if valid_black_periods == 0:
messagebox.showwarning("选择提醒", "未配置任何有效时间段,程序运行后将只执行取消黑屏操作")
return True
def start_program(black_periods, remote_start_entry, remote_end_entry, remote_weekday_vars):
"""启动控制程序"""
global is_running, control_thread
if not is_running:
# 验证输入
if not validate_time_inputs(black_periods, remote_start_entry, remote_end_entry, remote_weekday_vars):
return
is_running = True
log_message("程序启动成功")
# 简化启动日志,只显示核心配置
valid_black = []
for i in range(BLACK_PERIOD_COUNT):
start_entry = black_periods[i]["start"]
end_entry = black_periods[i]["end"]
if start_entry.get().strip() and end_entry.get().strip():
valid_black.append(f"时段{i+1}:{start_entry.get()}-{end_entry.get()}")
if valid_black:
log_message(f"黑屏配置:{','.join(valid_black)}")
else:
log_message("黑屏配置:未设置")
if remote_start_entry.get().strip() and remote_end_entry.get().strip():
log_message(f"远程配置:{remote_start_entry.get()}-{remote_end_entry.get()}")
else:
log_message("远程配置:未设置")
# 启动控制线程(传递配置参数)
control_thread = threading.Thread(
target=control_loop,
args=(black_periods, remote_start_entry, remote_end_entry, remote_weekday_vars),
daemon=True
)
control_thread.start()
def stop_program():
"""停止控制程序"""
global is_running
if is_running:
log_message("正在停止程序...")
is_running = False
def control_loop(black_periods, remote_start_entry, remote_end_entry, remote_weekday_vars):
"""控制程序主循环"""
global is_running
# 检查管理员权限
if not is_admin():
log_message("需要管理员权限,正在获取...")
run_as_admin()
return
# 自动安装依赖(打包后注释掉,提前安装)
# try:
# import win32gui
# import pywinauto
# except ImportError:
# log_message("安装依赖...")
# try:
# subprocess.check_call([sys.executable, "-m", "pip", "install", "pywin32", "pywinauto==0.6.8"], timeout=30)
# log_message("依赖安装完成")
# except Exception as e:
# log_message(f"依赖安装失败:{str(e)}")
# is_running = False
# return
log_message("极域控制脚本启动成功!")
while is_running:
try:
now = datetime.now()
current_time = now.strftime("%H:%M:%S")
current_weekday = WEEKDAY_MAP[now.weekday()]
# 简化运行日志,只显示核心状态
log_message(f"当前:{current_time} {current_weekday}")
# 检查是否在任意黑屏时间段
in_black, black_desc = is_in_any_black_period(black_periods)
if in_black:
log_message(f"执行:{black_desc} - 黑屏")
click_black_screen_background()
else:
# 检查是否在远程命令时间段
in_remote, remote_desc = is_in_remote_period(remote_start_entry, remote_end_entry, remote_weekday_vars)
if in_remote:
log_message(f"执行:{remote_desc} - 远程命令")
click_Remote_Command()
else:
log_message(f"执行:正常时段 - 取消黑屏")
click_cancel_black_screen_background()
# 休眠10秒
for _ in range(10):
if not is_running:
break
time.sleep(1)
except Exception as e:
log_message(f"运行错误:{str(e)}")
time.sleep(5)
log_message("程序已停止")
# ---------------------- 创建GUI界面 ----------------------
def create_gui():
global root, log_area, status_label
# 主窗口设置
root = tk.Tk()
root.title("极域课堂自动控制器")
root.geometry("900x600") # 整体窗口高度从700缩小到600
root.resizable(True, True)
# 设置全局字体
default_font = ("Microsoft YaHei", 9)
root.option_add("*Font", default_font)
# ---------------------- 自定义按钮样式 ----------------------
style = ttk.Style(root)
style.theme_use("clam") # 使用clam主题以支持按钮颜色自定义
# 启动按钮样式(绿色)
style.configure("Start.TButton",
background="#28a745",
foreground="white",
font=("Microsoft YaHei", 10, "bold"),
padding=6)
style.map("Start.TButton",
background=[("active", "#218838")], # 鼠标悬停颜色
foreground=[("active", "white")])
# 停止按钮样式(红色)
style.configure("Stop.TButton",
background="#dc3545",
foreground="white",
font=("Microsoft YaHei", 10, "bold"),
padding=6)
style.map("Stop.TButton",
background=[("active", "#c82333")], # 鼠标悬停颜色
foreground=[("active", "white")])
# 配置区域样式
style.configure("Config.TLabelframe",
font=("Microsoft YaHei", 10, "bold"),
foreground="#333333")
style.configure("Config.TLabelframe.Label",
font=("Microsoft YaHei", 10, "bold"),
foreground="#333333")
# ---------------------- 配置区域 ----------------------
config_frame = ttk.LabelFrame(root, text="时间段配置", style="Config.TLabelframe", padding="10")
config_frame.pack(fill=tk.X, padx=10, pady=8)
# ---------------------- 黑屏时间段配置(5个) ----------------------
black_main_frame = ttk.LabelFrame(config_frame, text="黑屏时间段(最多5个)", padding="8")
black_main_frame.pack(fill=tk.X, pady=5)
# 存储5个黑屏时间段的配置
black_periods = []
# 创建5个黑屏时间段的输入框和星期选择
for i in range(BLACK_PERIOD_COUNT):
period_frame = ttk.Frame(black_main_frame)
period_frame.pack(fill=tk.X, pady=2)
# 时间段标题
ttk.Label(period_frame, text=f"时间段{i+1}:", font=("Microsoft YaHei", 9, "bold")).grid(row=0, column=0, padx=5, pady=1, sticky=tk.W)
# 开始时间
ttk.Label(period_frame, text="开始:").grid(row=0, column=1, padx=2, pady=1, sticky=tk.W)
start_entry = ttk.Entry(period_frame, width=10, font=("Microsoft YaHei", 9))
start_entry.grid(row=0, column=2, padx=2, pady=1)
# 默认值(第1个设为09:00,其余为空)
if i == 0:
start_entry.insert(0, "21:40")
if i == 1:
start_entry.insert(1, "16:00")
if i == 2:
start_entry.insert(2, "06:00")
# 结束时间
ttk.Label(period_frame, text="结束:").grid(row=0, column=3, padx=2, pady=1, sticky=tk.W)
end_entry = ttk.Entry(period_frame, width=10, font=("Microsoft YaHei", 9))
end_entry.grid(row=0, column=4, padx=2, pady=1)
# 默认值(第1个设为11:00,其余为空)
if i == 0:
end_entry.insert(0, "22:10")
if i == 1:
end_entry.insert(1, "22:10")
if i == 2:
end_entry.insert(2, "18:10")
# 星期选择
ttk.Label(period_frame, text="星期:").grid(row=0, column=5, padx=2, pady=1, sticky=tk.W)
weekday_vars = {}
for j, day in WEEKDAY_MAP.items():
var = tk.BooleanVar(value=(i == 0)) # 第1个默认全选,其余默认不选
weekday_vars[j] = var
cb = ttk.Checkbutton(period_frame, text=day, variable=var)
cb.grid(row=0, column=6+j, padx=1, pady=1)
# 保存该时间段的配置
black_periods.append({
"start": start_entry,
"end": end_entry,
"weekdays": weekday_vars
})
# ---------------------- 远程命令时间段配置 ----------------------
remote_frame = ttk.LabelFrame(config_frame, text="关机时间段", padding="8")
remote_frame.pack(fill=tk.X, pady=5)
ttk.Label(remote_frame, text="开始时间:").grid(row=0, column=0, padx=5, pady=3, sticky=tk.W)
remote_start_entry = ttk.Entry(remote_frame, width=10, font=("Microsoft YaHei", 9))
remote_start_entry.grid(row=0, column=1, padx=5, pady=3)
remote_start_entry.insert(0, "22:00")
ttk.Label(remote_frame, text="结束时间:").grid(row=0, column=2, padx=5, pady=3, sticky=tk.W)
remote_end_entry = ttk.Entry(remote_frame, width=10, font=("Microsoft YaHei", 9))
remote_end_entry.grid(row=0, column=3, padx=5, pady=3)
remote_end_entry.insert(0, "22:10")
ttk.Label(remote_frame, text="适用星期:").grid(row=0, column=4, padx=5, pady=3, sticky=tk.W)
remote_weekday_vars = {}
for j, day in WEEKDAY_MAP.items():
var = tk.BooleanVar(value=True)
remote_weekday_vars[j] = var
cb = ttk.Checkbutton(remote_frame, text=day, variable=var)
cb.grid(row=0, column=5+j, padx=2, pady=3)
# ---------------------- 控制按钮 ----------------------
button_frame = ttk.Frame(root, padding="10")
button_frame.pack(fill=tk.X)
start_btn = ttk.Button(
button_frame,
text="启动程序",
command=lambda: start_program(black_periods, remote_start_entry, remote_end_entry, remote_weekday_vars),
style="Start.TButton",
width=12
)
start_btn.pack(side=tk.LEFT, padx=8)
stop_btn = ttk.Button(
button_frame,
text="停止程序",
command=stop_program,
style="Stop.TButton",
width=12
)
stop_btn.pack(side=tk.LEFT, padx=8)
# ---------------------- 状态显示 ----------------------
status_frame = ttk.Frame(root, padding="0 0 10 8")
status_frame.pack(fill=tk.X, padx=10)
ttk.Label(status_frame, text="程序状态:", font=("Microsoft YaHei", 9, "bold")).pack(side=tk.LEFT)
status_label = ttk.Label(status_frame, text="程序已停止", font=("Microsoft YaHei", 9, "bold"))
status_label.pack(side=tk.LEFT, padx=5)
# ---------------------- 日志显示(缩小尺寸) ----------------------
log_frame = ttk.LabelFrame(root, text="最新运行日志(仅5条)", style="Config.TLabelframe", padding="8")
log_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
# 缩小日志框高度为4行,宽度适配
log_area = scrolledtext.ScrolledText(
log_frame,
wrap=tk.WORD,
state=tk.DISABLED,
height=4, # 从6行缩小到4行
font=("Consolas", 9), # 等宽字体更适合日志显示
bg="#F8F9FA", # 浅灰色背景
fg="#202124" # 深灰色文字
)
log_area.pack(fill=tk.BOTH, expand=True, padx=2, pady=2)
# 开始更新状态
update_status()
# 主循环
root.mainloop()
# ---------------------- 程序入口 ----------------------
if __name__ == "__main__":
# 强制要求管理员权限运行
if not is_admin():
run_as_admin()
else:
create_gui()