From 21944b4621d9f7b5d0eb29e276ca425546bc1205 Mon Sep 17 00:00:00 2001 From: Hsdi Date: Sun, 15 Mar 2026 01:05:43 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 234 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 234 insertions(+) create mode 100644 main.py diff --git a/main.py b/main.py new file mode 100644 index 0000000..90bdbed --- /dev/null +++ b/main.py @@ -0,0 +1,234 @@ +import time +import tkinter as tk + +import keyboard +import win32api +import win32con +import win32gui + + +POTPLAYER_CLASSES = {"PotPlayer64", "PotPlayer"} +APP_TITLE = "PotPlayer 同步器" +WINDOW_SIZE = "420x240" +PLAY_PAUSE_COOLDOWN = 0.25 + +WM_APPCOMMAND = 0x0319 +APPCOMMAND_MEDIA_PLAY_PAUSE = 14 + + +def safe_get_class_name(hwnd): + try: + return win32gui.GetClassName(hwnd) + except win32gui.error: + return "" + + +def safe_get_window_text(hwnd): + try: + return win32gui.GetWindowText(hwnd) + except win32gui.error: + return "" + + +def is_potplayer_window(hwnd): + return safe_get_class_name(hwnd) in POTPLAYER_CLASSES + + +def build_key_lparam(vk_code, is_keydown): + scan_code = win32api.MapVirtualKey(vk_code, 0) + is_extended = vk_code in { + win32con.VK_LEFT, + win32con.VK_RIGHT, + win32con.VK_UP, + win32con.VK_DOWN, + win32con.VK_CONTROL, + win32con.VK_RCONTROL, + win32con.VK_MENU, + win32con.VK_RMENU, + } + + lparam = 1 | (scan_code << 16) | (int(is_extended) << 24) + if not is_keydown: + lparam |= (1 << 30) | (1 << 31) + return lparam + + +def get_potplayer_windows(): + hwnds = [] + + def callback(hwnd, _extra): + if win32gui.IsWindowVisible(hwnd) and is_potplayer_window(hwnd): + hwnds.append(hwnd) + return True + + win32gui.EnumWindows(callback, None) + return hwnds + + +class PotPlayerSyncApp: + def __init__(self): + self.root = tk.Tk() + self.root.title(APP_TITLE) + self.root.geometry(WINDOW_SIZE) + self.root.resizable(False, False) + self.root.attributes("-topmost", True) + self.root.protocol("WM_DELETE_WINDOW", self.on_close) + + self.status_var = tk.StringVar(value="等待操作...") + self.last_play_pause_at = 0.0 + self.hotkey_handles = [] + + self.build_ui() + self.register_hotkeys() + self.refresh_initial_status() + + def build_ui(self): + tk.Label( + self.root, + text="PotPlayer 联动控制台", + font=("Microsoft YaHei", 13, "bold"), + pady=10, + ).pack() + + tips = ( + "只在 PotPlayer 窗口或本工具窗口内操作时生效\n" + "\n" + "空格: 同步播放 / 暂停\n" + "Left / Right: 同步快退 / 快进\n" + "Shift + Left / Right: 同步微调进度\n" + "Ctrl + Left / Right: 同步大幅调整进度" + ) + tk.Label( + self.root, + text=tips, + font=("Microsoft YaHei", 10), + justify="left", + fg="#333333", + padx=16, + pady=4, + ).pack(anchor="w") + + tk.Label( + self.root, + textvariable=self.status_var, + font=("Microsoft YaHei", 10, "bold"), + pady=10, + fg="#0066CC", + wraplength=380, + justify="left", + ).pack() + + def register_hotkeys(self): + self.hotkey_handles.extend( + [ + keyboard.on_press_key("space", self.handle_space, suppress=False), + keyboard.on_press_key("left", self.handle_left, suppress=False), + keyboard.on_press_key("right", self.handle_right, suppress=False), + ] + ) + + def refresh_initial_status(self): + count = len(get_potplayer_windows()) + if count: + self.update_status(f"已检测到 {count} 个 PotPlayer 窗口,等待操作...") + else: + self.update_status("暂未检测到 PotPlayer 窗口。") + + def update_status(self, message): + self.root.after(0, lambda: self.status_var.set(message)) + + def is_app_window(self, hwnd): + return safe_get_window_text(hwnd) == APP_TITLE + + def get_target_windows(self): + foreground_hwnd = win32gui.GetForegroundWindow() + in_potplayer = is_potplayer_window(foreground_hwnd) + in_app = self.is_app_window(foreground_hwnd) + + if not (in_potplayer or in_app): + return [], "请先聚焦 PotPlayer 窗口或同步器窗口。" + + hwnds = get_potplayer_windows() + if not hwnds: + return [], "未检测到可同步的 PotPlayer 窗口。" + + targets = [hwnd for hwnd in hwnds if hwnd != foreground_hwnd] if in_potplayer else hwnds + if not targets: + return [], "当前只有一个 PotPlayer 窗口,无需同步。" + + return targets, "" + + def post_play_pause(self, hwnd): + lparam = APPCOMMAND_MEDIA_PLAY_PAUSE << 16 + win32gui.PostMessage(hwnd, WM_APPCOMMAND, 0, lparam) + + def post_combo(self, hwnd, vk_codes): + for vk_code in vk_codes: + win32gui.PostMessage(hwnd, win32con.WM_KEYDOWN, vk_code, build_key_lparam(vk_code, True)) + for vk_code in reversed(vk_codes): + win32gui.PostMessage(hwnd, win32con.WM_KEYUP, vk_code, build_key_lparam(vk_code, False)) + + def sync_action(self, action_name, sender): + target_hwnds, message = self.get_target_windows() + if not target_hwnds: + self.update_status(message) + return + + for hwnd in target_hwnds: + try: + sender(hwnd) + except win32gui.error: + continue + + self.update_status(f"已同步: {action_name} -> {len(target_hwnds)} 个窗口") + + def handle_space(self, _event): + now = time.monotonic() + if now - self.last_play_pause_at < PLAY_PAUSE_COOLDOWN: + return + + self.last_play_pause_at = now + self.sync_action("空格(播放/暂停)", self.post_play_pause) + + def handle_left(self, _event): + if keyboard.is_pressed("shift"): + vk_codes = [win32con.VK_SHIFT, win32con.VK_LEFT] + action_name = "Shift + Left(微调后退)" + elif keyboard.is_pressed("ctrl"): + vk_codes = [win32con.VK_CONTROL, win32con.VK_LEFT] + action_name = "Ctrl + Left(大步后退)" + else: + vk_codes = [win32con.VK_LEFT] + action_name = "Left(后退)" + + self.sync_action(action_name, lambda hwnd: self.post_combo(hwnd, vk_codes)) + + def handle_right(self, _event): + if keyboard.is_pressed("shift"): + vk_codes = [win32con.VK_SHIFT, win32con.VK_RIGHT] + action_name = "Shift + Right(微调前进)" + elif keyboard.is_pressed("ctrl"): + vk_codes = [win32con.VK_CONTROL, win32con.VK_RIGHT] + action_name = "Ctrl + Right(大步前进)" + else: + vk_codes = [win32con.VK_RIGHT] + action_name = "Right(前进)" + + self.sync_action(action_name, lambda hwnd: self.post_combo(hwnd, vk_codes)) + + def on_close(self): + for handle in self.hotkey_handles: + keyboard.unhook(handle) + self.root.destroy() + + def run(self): + self.root.mainloop() + + +def main(): + app = PotPlayerSyncApp() + app.run() + + +if __name__ == "__main__": + main()