XinCb 发表于 2024-9-25 20:12

关于python写显示器输入源切换的问题



最近有几台电脑需要共用一台显示器,每次手动osd里面改。比较麻烦
于是用gpt写了一个方便快捷切换。
目前遇到一个问题,就是只能从HDMI设备切换到另外俩个,无法从另两个输入源切换回HDMI。
大佬看看是什么问题。下是代码


import sys
import customtkinter as ctk
from monitorcontrol import get_monitors

# 定义切换信号源的函数
def switch_input_source(source):
    monitors = get_monitors()
    for monitor in monitors:
      with monitor:
            monitor.set_input_source(source)

# 定义按钮点击事件
def on_click(source):
    # 直接切换输入源
    switch_input_source(source)
    # 更新标签显示当前输入源
    label.configure(text=f"当前输入源: {source}")

# 创建主窗口
class InputSwitcher(ctk.CTk):
    def __init__(self):
      super().__init__()

      self.initUI()
      self.protocol("WM_DELETE_WINDOW", self.on_closing)

    def initUI(self):
      self.title('输入源切换器')
      self.geometry('300x250')

      global label
      label = ctk.CTkLabel(self, text='当前输入源: 未知')
      label.pack(pady=20)

      hdmi_button = ctk.CTkButton(self, text='HDMI', command=lambda: on_click(1))
      hdmi_button.pack(pady=10)

      dp_button = ctk.CTkButton(self, text='DisplayPort', command=lambda: on_click(2))
      dp_button.pack(pady=10)

      typec_button = ctk.CTkButton(self, text='Type-C', command=lambda: on_click(3))
      typec_button.pack(pady=10)

    def on_closing(self):
      self.destroy()
      sys.exit()

# 主函数
if __name__ == '__main__':
    ctk.set_appearance_mode("System")
    ctk.set_default_color_theme("blue")
    app = InputSwitcher()
    app.mainloop()

XinCb 发表于 2024-9-26 22:10

本帖最后由 ccb666 于 2024-9-26 22:11 编辑

已解决

成品:https://www.52pojie.cn/thread-1967992-1-1.html
源码:
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

# 列出所有可能的输入源,包括 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 = 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 is not None:
      if button_name == "HDMI":
            hdmi_button.configure(text=f"HDMI ({button_sources})")
      elif button_name == "DP":
            dp_button.configure(text=f"DP ({button_sources})")
      elif button_name == "USB-C":
            usbc_button.configure(text=f"USB-C ({button_sources})")

# 获取输入框中的源编号,并在点击按钮时切换
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 is not None:
      # 如果输入框没有手动值,则使用已保存的默认值
      switch_input_source(button_sources)
    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} (Code: {source})\n")

    # 加载配置并更新按钮文本
    load_config()

    # 注册热键
    register_hotkeys()

    root.mainloop()

if __name__ == "__main__":
    create_gui()

xixicoco 发表于 2024-9-25 23:05

ddc代码不支持非hdmi的运行,一般要自定义的,很多厂家不支持,都是私有代码

XinCb 发表于 2024-9-26 00:30

本帖最后由 ccb666 于 2024-9-26 00:31 编辑



搞定,由于不同显示器的输出源编号不同,只能是列出所有可能的,然后手动输入确定好后保存到按钮上,比如我的HDMI是16,1-15都是切到DP/tpyc的
虽然麻烦点,最终还是达到了我的需求。


hubindong 发表于 2024-9-26 08:02

这个想法很好,正好也有类似需求。

bachelor66 发表于 2024-9-26 08:42

大佬,有完整的码学习吗?                              

XinCb 发表于 2024-9-26 09:43

bachelor66 发表于 2024-9-26 08:42
大佬,有完整的码学习吗?

还有一些小问题没完成

xmp788 发表于 2024-9-26 10:18

程序在这电脑上运行,显示器切换到其他电脑去了,如何切换回来不会用

chenzhigang 发表于 2024-9-26 10:21

插眼 后续学习一下

52bulesky 发表于 2024-9-26 10:37

牛逼 牛逼 牛逼 牛逼

llyaomo 发表于 2024-9-26 12:04

xmp788 发表于 2024-9-26 10:18
程序在这电脑上运行,显示器切换到其他电脑去了,如何切换回来不会用

有道理,程序在A电脑上,把显示器切给了B,那怎么切回来?B电脑也放个软件吗?这样的话,我觉得,还是不如遥控器或者显示器直接切换,不算更麻烦的吧?
页: [1] 2
查看完整版本: 关于python写显示器输入源切换的问题