SpaceDesk电脑屏幕扩展+python实现多屏展示

SpaceDesk软件可以给windows进行屏幕扩展(将扩展屏幕显示在其他PC电脑或手机上)。起因是我想调研下python是否能实现多屏应用开发,奈何手头只有一个显示器,经过一番折腾终于是使用【SpaceDesk软件+虚拟机/模拟器】解决了,在只有一台电脑&屏幕的情况下测试多屏应用展示效果。

一、SpaceDesk多屏软件使用

1、软件地址大家网上下载吧,它的windows客户端只要跟 服务端在同一个局域网或可访问网段就可以连接作为副屏使用,安卓端可以通过局域网ip连接电脑,也可以通过usb线连接电脑。

2、如果需要安卓手机通过usb连接电脑作为副屏,记得打开安卓usb开关。打开/关闭时屏幕可能会短暂黑屏。

3、在安卓及windwos上安装客户端软件后,可以手动添加 主屏幕所在电脑ip进行添加(如果客户端无法自动扫描到主屏幕电脑ip的话)。我这里同电脑上安卓模拟器及虚拟机下都可以链接成功(手机添加的)。

4、所有副屏连接完毕后,可以在主屏电脑系统设置中设置或检测多屏。

二、python实现多屏应用

这个多屏实现demo是问了AI之后整出来的,通过下图可以看到正常识别出了3个屏幕(安卓的usb屏幕我拔线了),而且2个副屏是最大化展示的。

最后贴上代码:

import datetime
import tkinter as tk
import screeninfo
import threading
import time
import ctypes

from ctypes import wintypes
import pygetwindow as gw

# Windows API常量
SW_MAXIMIZE = 3
HWND_TOP = 0


def set_window_maximize_on_monitor(hwnd, monitor):
    """使用Windows API将窗口在指定显示器上最大化"""
    try:
        user32 = ctypes.windll.user32

        # 将窗口移动到目标显示器
        user32.SetWindowPos(hwnd, HWND_TOP,
                            monitor.x, monitor.y,
                            monitor.width, monitor.height, 0)

        # 最大化窗口
        user32.ShowWindow(hwnd, SW_MAXIMIZE)

        return True
    except Exception as e:
        print(f"Windows API调用失败: {e}")
        return False


def create_window_on_monitor_pygetwindow(monitor_index, monitor):
    """使用pygetwindow库实现在副屏最大化"""
    root = tk.Tk()

    window_title = f"{'主屏幕' if monitor.is_primary else '副屏幕'} {monitor_index + 1}"
    root.title(window_title)

    if monitor.is_primary:
        # 主屏幕:居中显示
        width, height = 800, 600
        x = monitor.x + (monitor.width - width) // 2
        y = monitor.y + (monitor.height - height) // 2
        root.geometry(f"{width}x{height}+{x}+{y}")
        bg_color = "lightcoral"
    else:
        # 副屏幕:先创建普通窗口,然后最大化到副屏
        root.geometry("400x300")
        bg_color = "lightblue"

    root.configure(bg=bg_color)

    # 窗口内容
    title = tk.Label(root, text=window_title, font=("Arial", 18, "bold"), bg=bg_color)
    title.pack(pady=30)

    info_text = f"显示器: {monitor.width} x {monitor.height}n位置: ({monitor.x}, {monitor.y})"
    info_label = tk.Label(root, text=info_text, font=("Arial", 12), bg=bg_color)
    info_label.pack(pady=10)

    status_label = tk.Label(root, text="初始化中...", font=("Arial", 10), bg=bg_color)
    status_label.pack(pady=5)

    def maximize_on_monitor():
        """将窗口最大化到指定显示器"""
        if monitor.is_primary:
            return

        time.sleep(1)  # 等待窗口完全创建

        try:
            # 查找当前窗口
            windows = gw.getWindowsWithTitle(window_title)
            if windows:
                win = windows[0]

                # 移动窗口到目标显示器并最大化
                win.moveTo(monitor.x, monitor.y)
                win.resizeTo(monitor.width, monitor.height)
                win.maximize()

                status_label.config(text="已最大化在副屏")
                root.title(f"副屏幕 {monitor_index + 1} - 最大化")
            else:
                status_label.config(text="未找到窗口句柄")

        except Exception as e:
            status_label.config(text=f"最大化失败: {e}")
            # 备用方案
            root.geometry(f"{monitor.width}x{monitor.height}+{monitor.x}+{monitor.y}")

    # 在副屏上执行最大化
    if not monitor.is_primary:
        threading.Thread(target=maximize_on_monitor, daemon=True).start()

    # 时间显示
    time_label = tk.Label(root, text="", font=("Arial", 14), bg=bg_color)
    time_label.pack(pady=10)

    def update_time():
        current_time = time.strftime("%H:%M:%S")
        time_label.config(text=f"时间: {current_time}")
        root.after(1000, update_time)

    update_time()

    root.mainloop()


def create_window_on_monitor_winapi(monitor_index, monitor):
    """使用Windows API实现在副屏最大化"""
    root = tk.Tk()
    root.title(f"{'主屏幕' if monitor.is_primary else '副屏幕'} {monitor_index + 1}")

    # 初始设置一个较小的窗口
    root.geometry("400x300")
    root.configure(bg="lightblue")

    # 添加内容
    title_text = f"{'主屏幕' if monitor.is_primary else '副屏幕'} {monitor_index + 1}"
    title = tk.Label(root, text=title_text, font=("Arial", 16, "bold"), bg="lightblue")
    title.pack(pady=20)

    status_label = tk.Label(root, text="正在初始化窗口...", font=("Arial", 12), bg="lightblue")
    status_label.pack(pady=10)

    # 获取窗口句柄
    root.update()
    hwnd = root.winfo_id()

    def maximize_on_target_monitor():
        """在目标显示器上最大化窗口"""
        time.sleep(0.5)  # 等待窗口创建完成

        if not monitor.is_primary:  # 只在副屏上最大化
            if set_window_maximize_on_monitor(hwnd, monitor):
                status_label.config(text=f"已最大化在副屏 {monitor_index + 1}")
                root.title(f"副屏幕 {monitor_index + 1} - 最大化")
            else:
                status_label.config(text="最大化失败,使用备用方案")
                # 备用方案:手动设置大小和位置
                root.geometry(f"{monitor.width}x{monitor.height}+{monitor.x}+{monitor.y}")
        else:
            # 主屏幕居中显示
            width, height = 800, 600
            x = monitor.x + (monitor.width - width) // 2
            y = monitor.y + (monitor.height - height) // 2
            root.geometry(f"{width}x{height}+{x}+{y}")
            status_label.config(text="主屏幕窗口")

    # 在新线程中执行最大化操作
    threading.Thread(target=maximize_on_target_monitor, daemon=True).start()

    # 时间显示
    time_label = tk.Label(root, text="", font=("Arial", 14), bg="lightblue")
    time_label.pack(pady=10)

    def update_time():
        current_time = time.strftime("%H:%M:%S")
        time_label.config(text=f"当前时间: {current_time}")
        root.after(1000, update_time)

    update_time()

    # 关闭按钮
    close_btn = tk.Button(root, text="关闭", command=root.destroy,
                          font=("Arial", 12), bg="white")
    close_btn.pack(pady=20)

    root.mainloop()

def create_window_manual_maximize(monitor_index, monitor):
    """手动设置窗口大小和位置实现副屏'最大化'"""
    root = tk.Tk()

    # 设置窗口标题
    if monitor.is_primary:
        root.title(f"主屏幕 {monitor_index + 1}")
        # 主屏幕使用固定大小
        width, height = 800, 600
        x = monitor.x + (monitor.width - width) // 2
        y = monitor.y + (monitor.height - height) // 2
        bg_color = "lightcoral"
    else:
        root.title(f"副屏幕 {monitor_index + 1} - 全屏")
        # 副屏幕使用整个显示器空间(减去任务栏)
        width, height = monitor.width, monitor.height - 40  # 减去任务栏高度
        x, y = monitor.x, monitor.y
        bg_color = "lightblue"
        # 移除最大化按钮,因为我们要手动控制
        root.resizable(False, False)

    root.geometry(f"{width}x{height}+{x}+{y}")
    root.configure(bg=bg_color)

    # 创建主框架
    main_frame = tk.Frame(root, bg=bg_color)
    main_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=20)

    # 标题
    title_text = f"{'主屏幕' if monitor.is_primary else '副屏幕(全屏)'} {monitor_index + 1}"
    title = tk.Label(main_frame, text=title_text,
                     font=("Arial", 24, "bold"), bg=bg_color)
    title.pack(pady=30)

    # 显示详细信息
    info_frame = tk.Frame(main_frame, bg=bg_color)
    info_frame.pack(pady=20)

    infos = [
        f"显示器分辨率: {monitor.width} x {monitor.height}",
        f"窗口大小: {width} x {height}",
        f"窗口位置: ({x}, {y})",
        f"显示器状态: {'主屏幕' if monitor.is_primary else '副屏幕-全屏'}"
    ]

    for info in infos:
        label = tk.Label(info_frame, text=info, font=("Arial", 12),
                         bg=bg_color, anchor="w")
        label.pack(pady=2)

    # 时间显示
    time_label = tk.Label(main_frame, text="", font=("Arial", 16), bg=bg_color)
    time_label.pack(pady=20)

    def update_time():
        current_time = time.strftime("%Y-%m-%d %H:%M:%S")
        time_label.config(text=f"当前时间: {current_time}")
        root.after(1000, update_time)

    update_time()

    # 副屏专属内容
    if not monitor.is_primary:
        # 添加画布展示区域
        canvas_frame = tk.Frame(main_frame, bg=bg_color)
        canvas_frame.pack(fill=tk.BOTH, expand=True, pady=20)

        canvas = tk.Canvas(canvas_frame, bg="white", highlightthickness=1,
                           highlightbackground="darkblue")
        canvas.pack(fill=tk.BOTH, expand=True)

        def draw_content():
            canvas.delete("all")
            canvas_width = canvas.winfo_width()
            canvas_height = canvas.winfo_height()

            if canvas_width > 10 and canvas_height > 10:
                # 绘制网格
                for i in range(0, canvas_width, 50):
                    canvas.create_line(i, 0, i, canvas_height, fill="lightgray")
                for i in range(0, canvas_height, 50):
                    canvas.create_line(0, i, canvas_width, i, fill="lightgray")

                # 绘制中心文本
                center_x, center_y = canvas_width // 2, canvas_height // 2
                canvas.create_text(center_x, center_y, text="副屏全屏内容区域",
                                   font=("Arial", 20), fill="darkblue")

                # 绘制边框
                canvas.create_rectangle(10, 10, canvas_width - 10, canvas_height - 10,
                                        outline="darkblue", width=2)

        canvas.bind("<Configure>", lambda e: draw_content())

    # 控制按钮
    button_frame = tk.Frame(main_frame, bg=bg_color)
    button_frame.pack(side=tk.BOTTOM, pady=20)

    close_btn = tk.Button(button_frame, text="关闭窗口",
                          command=root.destroy,
                          font=("Arial", 12), bg="white", width=15)
    close_btn.pack()

    # 键盘快捷键
    def on_key_press(event):
        if event.keysym == 'Escape':
            root.destroy()

    root.bind('<KeyPress>', on_key_press)
    root.focus_set()

    print(f"窗口 {monitor_index + 1} 已创建在目标显示器上")
    root.mainloop()


def main():
    """主函数"""
    # 安装所需库: pip install screeninfo pygetwindow
    monitors = screeninfo.get_monitors()

    print(f"检测到 {len(monitors)} 个显示器:")
    for i, monitor in enumerate(monitors):
        status = "主屏幕" if monitor.is_primary else "副屏幕(将全屏显示)"
        print(f"  {i + 1}. {monitor.width}x{monitor.height} - {status}")

    if len(monitors) < 2:
        print("警告: 需要至少两个显示器才能体验完整的多屏效果")

    # 创建窗口线程
    threads = []
    for i, monitor in enumerate(monitors):
        # 选择其中一种方案(取消注释想要使用的方案):

        # 方案一: Windows API(需要管理员权限)
        # thread = threading.Thread(target=create_window_on_monitor_winapi, args=(i, monitor))

        # 方案二: pygetwindow(推荐)
        thread = threading.Thread(target=create_window_on_monitor_pygetwindow, args=(i, monitor))

        # 方案三: 手动设置(最可靠)
        # thread = threading.Thread(target=create_window_manual_maximize, args=(i, monitor))

        thread.daemon = True
        thread.start()
        threads.append(thread)

    print("所有窗口线程已启动...")
    print("按ESC键关闭窗口")

    # 等待线程结束
    for thread in threads:
        thread.join()


if __name__ == "__main__":
    main()

基于互联网精神,在注明出处的前提下本站文章可自由转载!

本文链接:https://ranjuan.cn/spacedesk-python-multi-screen/

赞赏

微信赞赏支付宝赞赏

发表评论