初步版本
This commit is contained in:
234
main.py
Normal file
234
main.py
Normal file
@@ -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()
|
||||||
Reference in New Issue
Block a user