Ubuntu Desktop切换笔记本和外置显示器的小程序

因为自己笔记本接了个外置显示器,有时需要关闭笔记本的内置的,但是我的笔记本是岔开站着扣在桌面方便散热,所以每次都要翻过来弄,感觉不方便,就叫AI写了个python小程序,方便在桌面就能看到状态而且可以开关任一个显示器.

第一步先安装gnone-randar.

# 进入临时目录
cd /tmp

# 克隆仓库
git clone https://gitlab.com/Oschowa/gnome-randr.git

# 进入目录
cd gnome-randr

# 复制脚本到系统路径
sudo cp gnome-randr.py /usr/local/bin/gnome-randr

# 赋予执行权限
sudo chmod +x /usr/local/bin/gnome-randr

然后把pythone代码保存在自己喜欢的路径,名字叫display_switcher.py.

#!/usr/bin/env python3
import subprocess
import re
import tkinter as tk
from tkinter import messagebox

INTERNAL = "eDP-1"
EXTERNAL = "DP-2"

def run_gnome_randr(*args):
    cmd = ["gnome-randr"] + list(args)
    print(f"[执行] {' '.join(cmd)}")
    try:
        subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE)
        return True
    except subprocess.CalledProcessError as e:
        err = e.stderr.decode() if e.stderr else "未知错误"
        print(f"[错误] {err}")
        messagebox.showerror("执行失败", f"命令出错:\n{err}")
        return False

def get_status(display):
    """
    通过解析 gnome-randr 输出来判断显示器是否启用。
    逻辑:查找 "logical monitor" 块,如果显示器名称出现在该块的 "associated physical monitors" 列表中,则为 ON,否则为 OFF。
    """
    try:
        output = subprocess.check_output(["gnome-randr"], text=True)
    except Exception as e:
        print(f"[解析错误] 无法执行 gnome-randr: {e}")
        return "error"
    
    # 检查物理连接
    if display not in output:
        return "disconnected"
    
    # 分割 logical monitor 块
    blocks = re.split(r'(?=^logical monitor \d+:)', output, flags=re.MULTILINE)
    for block in blocks:
        if not block.startswith('logical monitor'):
            continue
        # 提取 associated physical monitors 下面的显示器列表
        # 格式示例:
        # associated physical monitors:
        # 	DP-2 27C1U        
        lines = block.splitlines()
        in_assoc = False
        for line in lines:
            if 'associated physical monitors:' in line:
                in_assoc = True
                continue
            if in_assoc:
                # 如果遇到空行或下一个 logical monitor,停止
                if line.strip() == '' or line.startswith('logical monitor'):
                    break
                # 检查显示器名称(去除后面的型号描述)
                if line.strip().startswith(display):
                    return "ON"
    return "OFF"

class MonitorSwitcher:
    def __init__(self, root):
        self.root = root
        root.title("显示器切换器")
        root.geometry("380x180")
        root.resizable(False, False)
        
        try:
            subprocess.run(["gnome-randr"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True)
        except:
            messagebox.showerror("错误", "未找到 'gnome-randr',请先安装。")
            root.destroy()
            return
        
        tk.Label(root, text="点击按钮切换显示器状态\n绿色=开启  红色=关闭  灰色=未连接",
                 font=("Arial", 10), pady=10).pack()
        frame = tk.Frame(root)
        frame.pack(pady=5)
        
        self.btn_internal = tk.Button(frame, text=f"内置 ({INTERNAL})", width=15,
                                      command=self.toggle_internal)
        self.btn_internal.pack(side=tk.LEFT, padx=10)
        self.btn_external = tk.Button(frame, text=f"外接 ({EXTERNAL})", width=15,
                                      command=self.toggle_external)
        self.btn_external.pack(side=tk.LEFT, padx=10)
        
        tk.Button(root, text="刷新状态", command=self.update_buttons, width=15).pack(pady=5)
        tk.Label(root, text="※ 关闭内置后合盖不影响外接使用", fg="gray").pack()
        
        self.update_buttons()
        print("=== 初始化完成 ===")
    
    def update_buttons(self):
        s_int = get_status(INTERNAL)
        s_ext = get_status(EXTERNAL)
        print(f"[刷新] {INTERNAL}: {s_int}, {EXTERNAL}: {s_ext}")
        
        # 内置按钮
        if s_int == "ON":
            self.btn_internal.config(bg="lightgreen", state=tk.NORMAL, text=f"内置 ({INTERNAL})")
        elif s_int == "OFF":
            self.btn_internal.config(bg="lightcoral", state=tk.NORMAL, text=f"内置 ({INTERNAL})")
        else:
            self.btn_internal.config(bg="gray", state=tk.DISABLED, text="内置 (未连接)")
        
        # 外接按钮
        if s_ext == "ON":
            self.btn_external.config(bg="lightgreen", state=tk.NORMAL, text=f"外接 ({EXTERNAL})")
        elif s_ext == "OFF":
            self.btn_external.config(bg="lightcoral", state=tk.NORMAL, text=f"外接 ({EXTERNAL})")
        elif s_ext == "disconnected":
            self.btn_external.config(bg="gray", state=tk.DISABLED, text="外接 (未连接)")
        else:
            self.btn_external.config(bg="gray", state=tk.DISABLED)
    
    def toggle_internal(self):
        current = get_status(INTERNAL)
        ext_status = get_status(EXTERNAL)
        print(f"[操作] 切换内置,当前状态: {current}")
        
        if current == "ON":
            # 关闭内置,确保外接为主
            if ext_status != "ON":
                messagebox.showerror("错误", "外接显示器未开启,无法单独关闭内置屏幕")
                return
            success = run_gnome_randr(
                "--output", INTERNAL, "--off",
                "--output", EXTERNAL, "--auto", "--primary"
            )
        elif current == "OFF":
            if ext_status == "ON":
                # 关键修复:使用 --auto 且不重复指定显示器,顺序:先主后副
                success = run_gnome_randr(
                    "--output", EXTERNAL, "--auto", "--primary",
                    "--output", INTERNAL, "--auto", "--right-of", EXTERNAL
                )
            else:
                success = run_gnome_randr("--output", INTERNAL, "--auto", "--primary")
        else:
            messagebox.showerror("错误", f"{INTERNAL} 未连接")
            return
        
        if success:
            self.root.after(1000, self.update_buttons)  # 给 Mutter 足够的配置时间
    
    def toggle_external(self):
        current = get_status(EXTERNAL)
        int_status = get_status(INTERNAL)
        print(f"[操作] 切换外接,当前状态: {current}")
        
        if current == "ON":
            if int_status != "ON":
                messagebox.showerror("错误", "内置显示器未开启,无法单独关闭外接屏幕")
                return
            success = run_gnome_randr(
                "--output", EXTERNAL, "--off",
                "--output", INTERNAL, "--auto", "--primary"
            )
        elif current == "OFF":
            if int_status == "ON":
                success = run_gnome_randr(
                    "--output", INTERNAL, "--auto", "--primary",
                    "--output", EXTERNAL, "--auto", "--left-of", INTERNAL
                )
            else:
                success = run_gnome_randr("--output", EXTERNAL, "--auto", "--primary")
        else:
            messagebox.showerror("错误", f"{EXTERNAL} 未连接")
            return
        
        if success:
            self.root.after(1000, self.update_buttons)

if __name__ == "__main__":
    root = tk.Tk()
    app = MonitorSwitcher(root)
    root.mainloop()

# 赋予执行权限(如果还没有)
chmod +x ~/display_switcher.py

python3 ~/display_switcher.py
看是否工作.

确定没问题后,在桌面建立快捷方式.

在桌面文件夹建立

display_switcher.desktop

修改里面的内容如下.

[Desktop Entry]
Version=1.0
Type=Application
Name=显示器切换器
Comment=开关内置/外接显示器
Exec=/home/macro/display_switcher.py
Icon=preferences-desktop-display
Terminal=false
Categories=Utility;

右键桌面图标 → “允许启动”,之后双击即可使用。

样子如下

Leave a Reply

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注