import win32guiimport win32conimport win32apiimport timeimport threadingimport queueimport keyboardimport tkinter as tk# ---------- 右上角小窗口 ----------class CornerDisplay: def __init__(self, master): self.master = master self.window = None self.q = queue.Queue() master.after(100, self._process_queue) def _create(self): self.window = tk.Toplevel(self.master) sw = self.window.winfo_screenwidth() self.window.geometry(f"80x30+{sw-85}+5") self.window.attributes('-topmost', True, '-alpha', 0.9) self.window.overrideredirect(True) self.window.configure(bg='black') self.label = tk.Label(self.window, font=('Arial',14,'bold'), fg='white', bg='black') self.label.pack(expand=True, fill='both') self.window.withdraw() def _process_queue(self): try: while True: cmd = self.q.get_nowait() if cmd[0] == 'show': if not self.window: self._create() self.label.config(text=cmd[1], fg=cmd[2]) self.window.deiconify() elif cmd[0] == 'hide': if self.window: self.window.withdraw() elif cmd[0] == 'destroy': if self.window: self.window.destroy() return except queue.Empty: pass self.master.after(100, self._process_queue) def show(self, text, color): self.q.put(('show', text, color)) def hide(self): self.q.put(('hide',)) def destroy(self): self.q.put(('destroy',))# ---------- 中央大数字 ----------class CenterDisplay: def __init__(self, master): self.master = master self.window = None self.q = queue.Queue() master.after(100, self._process_queue) def _create(self): self.window = tk.Toplevel(self.master) sw, sh = self.window.winfo_screenwidth(), self.window.winfo_screenheight() self.window.geometry(f"300x300+{(sw-300)//2}+{(sh-300)//2}") self.window.attributes('-topmost', True) self.window.overrideredirect(True) self.window.configure(bg='white') self.window.wm_attributes('-transparentcolor', 'white') self.label = tk.Label(self.window, font=('Arial',200,'bold'), bg='white') self.label.pack(expand=True, fill='both') self.window.withdraw() def _process_queue(self): try: while True: cmd = self.q.get_nowait() if cmd[0] == 'show': if not self.window: self._create() self.label.config(text=cmd[1], fg=cmd[2]) self.window.deiconify() elif cmd[0] == 'hide': if self.window: self.window.withdraw() elif cmd[0] == 'destroy': if self.window: self.window.destroy() return except queue.Empty: pass self.master.after(100, self._process_queue) def show(self, num): colors = {10:'#FF9999',9:'#FF8888',8:'#FF7777',7:'#FF6666',6:'#FF5555', 5:'#FF4444',4:'#FF3333',3:'#FF2222',2:'#FF1111',1:'#FF0000',0:'#FF0000'} self.q.put(('show', str(num), colors.get(num,'#FF0000'))) def hide(self): self.q.put(('hide',)) def destroy(self): self.q.put(('destroy',))# ---------- 提示窗口 ----------class AlertDisplay: def __init__(self, master): self.master = master self.window = None self.q = queue.Queue() self.template = "距汇报结束还剩下{seconds}秒" master.after(100, self._process_queue) def _create(self): self.window = tk.Toplevel(self.master) sw = self.window.winfo_screenwidth() self.window.geometry(f"350x100+{sw-355}+40") self.window.attributes('-topmost', True, '-alpha', 0.85) self.window.overrideredirect(True) self.window.configure(bg='black') self.label = tk.Label(self.window, font=('Microsoft YaHei',16,'bold'), fg='#FFD700', bg='black', wraplength=320) self.label.pack(expand=True, fill='both', padx=10, pady=10) self.window.withdraw() self.timer = None def _process_queue(self): try: while True: cmd = self.q.get_nowait() if cmd[0] == 'show': if not self.window: self._create() self.label.config(text=cmd[1]) self.window.deiconify() if self.timer: self.window.after_cancel(self.timer) self.timer = self.window.after(3000, self.hide) elif cmd[0] == 'hide': if self.window: self.window.withdraw() elif cmd[0] == 'destroy': if self.window: if self.timer: self.window.after_cancel(self.timer) self.window.destroy() return except queue.Empty: pass self.master.after(100, self._process_queue) def show(self, seconds): text = self.template.replace("{seconds}", str(seconds)) self.q.put(('show', text)) def hide(self): self.q.put(('hide',)) def destroy(self): self.q.put(('destroy',)) def set_template(self, template): self.template = template# ---------- 监控核心 ----------class PPTMonitor: def __init__(self, master): self.master = master self.corner = CornerDisplay(master) self.center = CenterDisplay(master) self.alert = AlertDisplay(master) self.total = 30 self.alert_time = 20 self.alert_text = "距汇报结束还剩下{seconds}秒" self.running = False self.paused = False self.stop_event = threading.Event() self.pause_event = threading.Event() self.pause_event.set() self.thread = None def set_params(self, total, alert_time, alert_text): self.total = max(1, min(total, 3600)) self.alert_time = max(0, min(alert_time, 3600)) self.alert.set_template(alert_text) def find_ppt(self): def cb(hwnd, lst): if win32gui.IsWindowVisible(hwnd): title = win32gui.GetWindowText(hwnd).lower() if any(k in title for k in ['powerpoint','幻灯片','.ppt','.pptx','ppt -']): lst.append(hwnd) return True lst = [] win32gui.EnumWindows(cb, lst) return lst[0] if lst else None def is_fullscreen(self, hwnd): try: l,t,r,b = win32gui.GetWindowRect(hwnd) sw = win32api.GetSystemMetrics(win32con.SM_CXSCREEN) sh = win32api.GetSystemMetrics(win32con.SM_CYSCREEN) return (l<=2 and t<=2 and r>=sw-2 and b>=sh-2) except: return False def countdown_loop(self): self.corner.show(self._fmt(self.total), self._color(self.total)) self.alert_shown = False for i in range(self.total, 0, -1): if self.stop_event.is_set() or not self.running: break if self.paused: self.corner.hide() self.center.hide() self.alert.hide() self.pause_event.clear() while self.paused and self.running and not self.stop_event.is_set(): self.pause_event.wait(0.2) if not self.paused: self.pause_event.set() self.corner.show(self._fmt(i), self._color(i)) if i<=10: self.center.show(i) self.corner.show(self._fmt(i), self._color(i)) if i==self.alert_time and not self.alert_shown and self.alert_time>0: self.alert.show(i) self.alert_shown = True if i<=10: self.center.show(i) else: self.center.hide() time.sleep(1) self.corner.hide() self.center.hide() self.alert.hide() if self.running and not self.stop_event.is_set(): self._exit_fullscreen() def _fmt(self, sec): h = sec//3600 m = (sec%3600)//60 s = sec%60 return f"{h:02d}:{m:02d}:{s:02d}" if h else f"{m:02d}:{s:02d}" def _color(self, sec): if sec <= 10: return '#FF0000' elif sec <= self.alert_time: return '#FFFF00' else: return '#00FF00' def _exit_fullscreen(self): hwnd = self.find_ppt() if hwnd: win32gui.SetForegroundWindow(hwnd) time.sleep(0.3) keyboard.send('esc') time.sleep(0.5) def monitor_loop(self): while self.running: hwnd = self.find_ppt() if hwnd and self.is_fullscreen(hwnd): if not self.is_fullscreen_prev: self.is_fullscreen_prev = True if not self.paused: self.stop_event.clear() self.thread = threading.Thread(target=self.countdown_loop, daemon=True) self.thread.start() else: if self.is_fullscreen_prev: self.is_fullscreen_prev = False self.stop_event.set() if self.thread: self.thread.join(timeout=1) self.stop_event.clear() time.sleep(1) def start(self): if self.running: return self.running = True self.is_fullscreen_prev = False self.stop_event.clear() threading.Thread(target=self.monitor_loop, daemon=True).start() def stop(self): self.running = False self.stop_event.set() self.pause_event.set() if self.thread: self.thread.join(timeout=1) self.corner.destroy() self.center.destroy() self.alert.destroy() def pause_resume(self): self.paused = not self.paused if self.paused: self.pause_event.clear() else: self.pause_event.set() return self.paused# ---------- 简单启动示例 ----------if __name__ == "__main__": root = tk.Tk() root.withdraw() monitor = PPTMonitor(root) monitor.set_params(30, 20, "距离结束还剩{seconds}秒") monitor.start() print("监控已启动,按 Ctrl+C 停止") try: root.mainloop() except KeyboardInterrupt: monitor.stop() root.destroy()