[Python] 纯文本查看 复制代码
import wmi
from monitorcontrol import get_monitors, InputSource
import customtkinter as ctk
import tkinter as tk
import json
import os
import threading
from PIL import Image, ImageTk
import pystray
from pystray import MenuItem as item
import keyboard # 用于捕获全局热键
import sys
# 配置文件路径
CONFIG_FILE = "C:/HDMIfig.json"
# 保存输入源的编号
button_sources = {"HDMI": None, "DP": None, "USB-C": None}
# 托盘图标对象(全局变量,确保只创建一次)
tray_icon = None
# 图标路径
if getattr(sys, 'frozen', False):
ICON_PATH = os.path.join(sys._MEIPASS, "hdmi", "hdmi.ico")
else:
ICON_PATH = "hdmi/hdmi.ico"
# 获取显示器设备信息 (通过 WMI)
def get_display_info():
wmi_service = wmi.WMI(namespace='wmi')
displays = wmi_service.WmiMonitorID()
return [display.InstanceName for display in displays]
# 列出所有可能的输入源,包括 DDC/CI 协议常用编号和厂商自定义源
def list_input_sources():
input_sources = set()
# 方式 1: 从 monitorcontrol 的 InputSource 枚举中获取输入源
for source in InputSource:
input_sources.add((source.value, source.name))
# 方式 2: 常见输入源的硬编码列表,扩展到更多厂商常见编号
common_sources = {
1: "HDMI 1",
2: "HDMI 2",
3: "DisplayPort",
4: "DVI",
5: "VGA",
6: "USB-C",
7: "Thunderbolt",
8: "Composite",
9: "Component",
10: "SCART",
11: "S-Video",
12: "Mini DisplayPort",
13: "eDP",
14: "MHL",
15: "Wireless Display",
16: "USB 3.0",
17: "HDMI 3",
18: "DisplayPort 2",
19: "Thunderbolt 3",
}
for key, value in common_sources.items():
input_sources.add((key, value))
# 方式 3: 通过 WMI 获取设备信息
display_info = get_display_info()
for index, display in enumerate(display_info):
input_sources.add((index + 100, f"Display {index + 1}: {display}"))
return input_sources
# 切换显示器输入源
def switch_input_source(source_value):
try:
monitors = get_monitors()
for monitor in monitors:
with monitor:
monitor.set_input_source(InputSource(source_value))
output_textbox.insert(tk.END, f"成功切换到 {source_value} 输入源\n")
except Exception as e:
output_textbox.insert(tk.END, f"切换失败: {e}\n")
# 保存自定义按钮源编号并更新按钮名称
def save_source(button_name, source_value):
button_sources[button_name] = source_value
update_button_text(button_name)
save_config()
output_textbox.insert(tk.END, f"已保存 {button_name} 为输入源编号 {source_value}\n")
# 更新按钮文本
def update_button_text(button_name):
if button_sources[button_name] is not None:
if button_name == "HDMI":
hdmi_button.configure(text=f"HDMI ({button_sources[button_name]})")
elif button_name == "DP":
dp_button.configure(text=f"DP ({button_sources[button_name]})")
elif button_name == "USB-C":
usbc_button.configure(text=f"USB-C ({button_sources[button_name]})")
# 获取输入框中的源编号,并在点击按钮时切换
def switch_and_save_source(button_name, entry_widget):
# 获取当前输入框的值
source_value = entry_widget.get()
if source_value.isdigit():
source_value = int(source_value)
save_source(button_name, source_value)
switch_input_source(source_value)
elif button_sources[button_name] is not None:
# 如果输入框没有手动值,则使用已保存的默认值
switch_input_source(button_sources[button_name])
else:
output_textbox.insert(tk.END, "请输入有效的数字编号\n")
# 保存配置到文件
def save_config():
with open(CONFIG_FILE, 'w') as f:
json.dump(button_sources, f)
# 从文件加载配置
def load_config():
global button_sources
if os.path.exists(CONFIG_FILE):
with open(CONFIG_FILE, 'r') as f:
button_sources = json.load(f)
output_textbox.insert(tk.END, "配置已加载\n")
# 确保加载后更新按钮名称
update_button_text("HDMI")
update_button_text("DP")
update_button_text("USB-C")
else:
output_textbox.insert(tk.END, "没有找到配置文件,使用默认设置\n")
# 设置默认 HDMI 和 DP 编号
button_sources["HDMI"] = 16
button_sources["DP"] = 1
button_sources["USB-C"] = None
update_button_text("HDMI")
update_button_text("DP")
update_button_text("USB-C")
# 读取当前显示器的输入源
get_current_input_source()
# 读取当前显示器的输入源
def get_current_input_source():
try:
monitors = get_monitors()
for monitor in monitors:
with monitor:
current_source = monitor.get_input_source()
output_textbox.insert(tk.END, f"当前输入源编号为: {current_source.value} ({current_source.name})\n")
return current_source.value
except Exception as e:
output_textbox.insert(tk.END, f"无法读取当前输入源: {e}\n")
return None
# 恢复窗口
def restore_window(icon, item):
global tray_icon
root.after(0, root.deiconify)
tray_icon.stop()
# 最小化到托盘功能
def minimize_to_tray():
global tray_icon
root.withdraw() # 隐藏窗口
if tray_icon is None: # 确保托盘图标只创建一次
image = Image.open(ICON_PATH)
menu = (item('打开主窗口', restore_window), item('关闭程序', quit_application))
tray_icon = pystray.Icon("Monitor Switcher", image, "Monitor Switcher", menu)
threading.Thread(target=tray_icon.run, daemon=True).start()
# 退出程序
def quit_application(icon=None, item=None):
global tray_icon
if tray_icon:
tray_icon.stop()
root.quit()
# 处理窗口最小化事件
def on_minimize(event):
minimize_to_tray()
# 切换到 HDMI 的快捷键处理
def switch_to_hdmi():
if button_sources["HDMI"] is not None:
switch_input_source(button_sources["HDMI"])
# 切换到 DP 的快捷键处理
def switch_to_dp():
if button_sources["DP"] is not None:
switch_input_source(button_sources["DP"])
# 切换到 USB-C 的快捷键处理
def switch_to_usbc():
if button_sources["USB-C"] is not None:
switch_input_source(button_sources["USB-C"])
# 注册全局热键
def register_hotkeys():
keyboard.add_hotkey('alt+1', switch_to_hdmi)
keyboard.add_hotkey('alt+2', switch_to_dp)
keyboard.add_hotkey('alt+3', switch_to_usbc)
# GUI 界面
def create_gui():
global root
# 初始化主窗口
root = ctk.CTk()
root.title("显示器输入源切换器")
root.geometry("300x250")
# 设置窗口图标
root.iconbitmap(ICON_PATH)
# 捕捉窗口最小化事件
root.protocol("WM_DELETE_WINDOW", quit_application) # 点击关闭时退出程序
root.bind("<Unmap>", lambda event: on_minimize(event) if root.state() == 'iconic' else None) # 最小化事件
# 创建按钮和对应的输入框
global hdmi_button, dp_button, usbc_button
# HDMI
hdmi_frame = ctk.CTkFrame(root)
hdmi_frame.pack(pady=2)
hdmi_button = ctk.CTkButton(hdmi_frame, text="HDMI", command=lambda: switch_and_save_source("HDMI", hdmi_entry))
hdmi_button.pack(side="left", padx=1)
hdmi_entry = ctk.CTkEntry(hdmi_frame, width=30)
hdmi_entry.pack(side="left")
# DP
dp_frame = ctk.CTkFrame(root)
dp_frame.pack(pady=2)
dp_button = ctk.CTkButton(dp_frame, text="DP", command=lambda: switch_and_save_source("DP", dp_entry))
dp_button.pack(side="left", padx=1)
dp_entry = ctk.CTkEntry(dp_frame, width=30)
dp_entry.pack(side="left")
# USB-C
usbc_frame = ctk.CTkFrame(root)
usbc_frame.pack(pady=2)
usbc_button = ctk.CTkButton(usbc_frame, text="USB-C", command=lambda: switch_and_save_source("USB-C", usbc_entry))
usbc_button.pack(side="left", padx=1)
usbc_entry = ctk.CTkEntry(usbc_frame, width=30)
usbc_entry.pack(side="left")
# 输出信息框
global output_textbox
output_textbox = ctk.CTkTextbox(root, height=200, width=400)
output_textbox.pack(pady=2)
# 列出所有可能的输入源
output_textbox.insert(tk.END, "可能的输入源:\n")
input_sources = list_input_sources()
for source in input_sources:
output_textbox.insert(tk.END, f"{source[1]} (Code: {source[0]})\n")
# 加载配置并更新按钮文本
load_config()
# 注册热键
register_hotkeys()
root.mainloop()
if __name__ == "__main__":
create_gui()