import sys import time import datetime import threading import ctypes from ctypes import wintypes import tkinter as tk from tkinter import ttk, messagebox VERSION = "1.0" # 每次修改 +0.1;大改版 +1 需經小威同意 if sys.platform != "win32": print("此程式僅支援 Windows 系統!") sys.exit(1) user32 = ctypes.windll.user32 # Win32 常數 SW_RESTORE = 9 VK_MENU = 0x12 # Alt VK_CONTROL = 0x11 # Ctrl VK_RETURN = 0x0D # Enter VK_TAB = 0x09 # Tab VK_SPACE = 0x20 # Space VK_ESCAPE = 0x1B # Esc VK_V = 0x56 # V 鍵 KEYEVENTF_KEYUP = 0x0002 EnumWindowsProc = ctypes.WINFUNCTYPE(ctypes.c_bool, wintypes.HWND, wintypes.LPARAM) def get_window_text(hwnd): length = user32.GetWindowTextLengthW(hwnd) if length > 0: buff = ctypes.create_unicode_buffer(length + 1) user32.GetWindowTextW(hwnd, buff, length + 1) return buff.value return "" def list_open_windows(): win_list = [] def enum_cb(hwnd, lParam): if user32.IsWindowVisible(hwnd): title = get_window_text(hwnd).strip() # 過濾背景無標題或特定系統底層程式 if title and title not in ("Program Manager", "Settings", "Windows 輸入體驗"): win_list.append((hwnd, title)) return True user32.EnumWindows(EnumWindowsProc(enum_cb), 0) return win_list class AdvancedWindowController(tk.Tk): def __init__(self): super().__init__() self.title(f"視窗定時自動輸入 v{VERSION}") self.geometry("520x460") self.resizable(False, False) self.windows_map = {} self.is_running = False self.cancel_event = threading.Event() self.setup_ui() self.refresh_windows() def setup_ui(self): pad_x, pad_y = 12, 6 # 1. 目標視窗選擇 f_win = ttk.LabelFrame(self, text="1. 選擇目標視窗") f_win.pack(fill="x", padx=pad_x, pady=pad_y) self.combo_var = tk.StringVar() self.combo = ttk.Combobox(f_win, textvariable=self.combo_var, state="readonly", width=42) self.combo.pack(side="left", padx=8, pady=8, fill="x", expand=True) btn_refresh = ttk.Button(f_win, text="重新整理", command=self.refresh_windows) btn_refresh.pack(side="right", padx=8, pady=8) # 2. 時間觸發設定 f_time = ttk.LabelFrame(self, text="2. 設定觸發時機") f_time.pack(fill="x", padx=pad_x, pady=pad_y) self.mode_var = tk.StringVar(value="countdown") # 模式 A: 倒數秒數 rb_count = ttk.Radiobutton(f_time, text="倒數秒數:", value="countdown", variable=self.mode_var) rb_count.grid(row=0, column=0, sticky="w", padx=8, pady=4) self.entry_sec = ttk.Entry(f_time, width=8) self.entry_sec.insert(0, "5") self.entry_sec.grid(row=0, column=1, sticky="w", padx=4, pady=4) ttk.Label(f_time, text="秒後執行").grid(row=0, column=2, sticky="w") # 模式 B: 指定時間點 rb_clock = ttk.Radiobutton(f_time, text="固定時間點:", value="clock", variable=self.mode_var) rb_clock.grid(row=1, column=0, sticky="w", padx=8, pady=4) f_clock_inputs = ttk.Frame(f_time) f_clock_inputs.grid(row=1, column=1, columnspan=3, sticky="w", pady=4) now = datetime.datetime.now() self.hour_var = tk.StringVar(value=f"{now.hour:02d}") self.min_var = tk.StringVar(value=f"{now.minute:02d}") self.sec_var = tk.StringVar(value="00") self.spin_hour = ttk.Spinbox(f_clock_inputs, from_=0, to=23, format="%02.0f", width=4, textvariable=self.hour_var) self.spin_hour.pack(side="left") ttk.Label(f_clock_inputs, text=" : ").pack(side="left") self.spin_min = ttk.Spinbox(f_clock_inputs, from_=0, to=59, format="%02.0f", width=4, textvariable=self.min_var) self.spin_min.pack(side="left") ttk.Label(f_clock_inputs, text=" : ").pack(side="left") self.spin_sec = ttk.Spinbox(f_clock_inputs, from_=0, to=59, format="%02.0f", width=4, textvariable=self.sec_var) self.spin_sec.pack(side="left") btn_plus1 = ttk.Button(f_clock_inputs, text="+1 分鐘", command=self.set_plus_one_minute) btn_plus1.pack(side="left", padx=8) # 3. 發送內容設定 f_action = ttk.LabelFrame(self, text="3. 設定輸入內容與按鍵") f_action.pack(fill="x", padx=pad_x, pady=pad_y) ttk.Label(f_action, text="輸入文字:").grid(row=0, column=0, sticky="w", padx=8, pady=6) self.text_content = ttk.Entry(f_action, width=32) self.text_content.grid(row=0, column=1, sticky="w", padx=4, pady=6) ttk.Label(f_action, text="(若只送按鍵可留空)").grid(row=0, column=2, sticky="w", padx=4) ttk.Label(f_action, text="附帶按鍵:").grid(row=1, column=0, sticky="w", padx=8, pady=6) self.key_var = tk.StringVar(value="Enter") self.key_combo = ttk.Combobox(f_action, textvariable=self.key_var, values=["Enter", "Tab", "Space", "Esc", "無"], state="readonly", width=10) self.key_combo.grid(row=1, column=1, sticky="w", padx=4, pady=6) # 4. 狀態列與控制按鈕 self.lbl_status = ttk.Label(self, text="狀態:準備就緒", foreground="#333333", font=("Microsoft JhengHei", 10, "bold")) self.lbl_status.pack(pady=8) f_btn = ttk.Frame(self) f_btn.pack(pady=4) self.btn_start = ttk.Button(f_btn, text="開始執行", width=16, command=self.start_task) self.btn_start.pack(side="left", padx=8, ipady=4) self.btn_cancel = ttk.Button(f_btn, text="取消等待", width=16, state="disabled", command=self.cancel_task) self.btn_cancel.pack(side="left", padx=8, ipady=4) def set_plus_one_minute(self): target = datetime.datetime.now() + datetime.timedelta(minutes=1) self.hour_var.set(f"{target.hour:02d}") self.min_var.set(f"{target.minute:02d}") self.sec_var.set("00") def refresh_windows(self): windows = list_open_windows() self.windows_map.clear() display_list = [] for hwnd, title in windows: disp = f"{title[:40]}... (ID:{hwnd})" if len(title) > 40 else f"{title} (ID:{hwnd})" self.windows_map[disp] = hwnd display_list.append(disp) self.combo['values'] = display_list if display_list: self.combo.current(0) def start_task(self): selected = self.combo_var.get() if not selected or selected not in self.windows_map: messagebox.showwarning("提示", "請先選擇目標視窗!") return target_hwnd = self.windows_map[selected] mode = self.mode_var.get() if mode == "countdown": try: seconds = int(self.entry_sec.get()) if seconds < 0: raise ValueError except ValueError: messagebox.showwarning("提示", "倒數秒數請輸入正整數!") return target_time = None else: try: h = int(self.hour_var.get()) m = int(self.min_var.get()) s = int(self.sec_var.get()) now = datetime.datetime.now() target_time = now.replace(hour=h, minute=m, second=s, microsecond=0) # 若設定時間已過,自動視為明天同一時間 if target_time <= now: target_time += datetime.timedelta(days=1) except ValueError: messagebox.showwarning("提示", "請輸入有效的時間!") return seconds = None self.is_running = True self.cancel_event.clear() self.btn_start.config(state="disabled") self.btn_cancel.config(state="normal") threading.Thread( target=self.worker_thread, args=(target_hwnd, mode, seconds, target_time), daemon=True ).start() def cancel_task(self): if self.is_running: self.cancel_event.set() self.lbl_status.config(text="狀態:已取消等待", foreground="#c0392b") self.reset_btn_state() def reset_btn_state(self): self.is_running = False self.btn_start.config(state="normal") self.btn_cancel.config(state="disabled") def worker_thread(self, hwnd, mode, seconds, target_time): if mode == "countdown": for s in range(seconds, 0, -1): if self.cancel_event.is_set(): return self.lbl_status.config(text=f"狀態:倒數中,剩餘 {s} 秒...", foreground="#2980b9") time.sleep(1) else: while True: if self.cancel_event.is_set(): return now = datetime.datetime.now() remain = int((target_time - now).total_seconds()) if remain <= 0: break self.lbl_status.config( text=f"狀態:等待至 {target_time.strftime('%H:%M:%S')} (剩餘 {remain} 秒)...", foreground="#2980b9" ) time.sleep(0.5) self.lbl_status.config(text="狀態:正在啟用視窗並送出內容...", foreground="#27ae60") self.execute_action(hwnd) self.lbl_status.config(text="狀態:執行完成!", foreground="#27ae60") self.reset_btn_state() def execute_action(self, hwnd): if not user32.IsWindow(hwnd): self.after(0, lambda: messagebox.showerror("錯誤", "目標視窗已關閉或不存在!")) return # 1. 還原並喚醒前台 user32.ShowWindow(hwnd, SW_RESTORE) user32.keybd_event(VK_MENU, 0, 0, 0) user32.keybd_event(VK_MENU, 0, KEYEVENTF_KEYUP, 0) user32.SetForegroundWindow(hwnd) time.sleep(0.3) # 2. 若有文字,透過剪貼簿貼上 (完美支援中文與長字串) text = self.text_content.get() if text: self.clipboard_clear() self.clipboard_append(text) self.update() # 確保剪貼簿同步 time.sleep(0.1) # 模擬 Ctrl + V user32.keybd_event(VK_CONTROL, 0, 0, 0) user32.keybd_event(VK_V, 0, 0, 0) time.sleep(0.05) user32.keybd_event(VK_V, 0, KEYEVENTF_KEYUP, 0) user32.keybd_event(VK_CONTROL, 0, KEYEVENTF_KEYUP, 0) time.sleep(0.15) # 3. 發送指定特殊鍵 key_map = { "Enter": VK_RETURN, "Tab": VK_TAB, "Space": VK_SPACE, "Esc": VK_ESCAPE } chosen_key = self.key_var.get() if chosen_key in key_map: vk = key_map[chosen_key] user32.keybd_event(vk, 0, 0, 0) time.sleep(0.05) user32.keybd_event(vk, 0, KEYEVENTF_KEYUP, 0) if __name__ == "__main__": app = AdvancedWindowController() app.mainloop()