使用 subprocess.call() 时防止光标加载动画

Prevent cursor loading animation when using subprocess.call()

我正在尝试创建一个 Python 程序,该程序从 Arduino 获取输入以更改显示器的输入端口。目前一切正常,但是当程序运行时,Windows 上的光标不断显示一个旋转的圆圈。超级烦人,后来发现跟下面这段代码有关

# Check serial monitor to see if button has been pressed
while True:
    try:
        data = arduino.readline()[:-2]
        if data:
            ... Some irrelevant code ...
        # Check if input has changed unexpectedly
        elif (subprocess.call(r'ControlMyMonitor.exe /GetValue "\.\DISPLAY1\Monitor0" 60', startupinfo=si) == 15 and current == 17) or (subprocess.Popen(r'ControlMyMonitor.exe /GetValue "\.\DISPLAY1\Monitor0" 60', startupinfo=si) == 17 and current == 15):
            print("Detected monitor switch")
            if current == HDMI:
                arduino.write('1'.encode())
                current = DP
            elif current == DP:
                arduino.write('2'.encode())
                current = HDMI
    except:
        sys.exit("Communication to Arduino was interrupted")

我每秒多次调用程序 ControlMyMonitor.exe 来验证输入端口值,这导致程序在 运行 时光标永远旋转。有没有什么办法可以避免,或者我应该放弃检查显示器输入是否意外改变(即手动切换后没有设备连接到 HDMI 端口,显示器自动切换回 DP)?

我最终通过切断中间人解决了这个问题。我添加了一个名为 moncontrol.py 的文件,该文件粘贴在包含我的更改的原始代码片段下方。我添加了函数“get_monitor_input”,其中 returns 是所选监视器的整数值。这样,代码可以保持相对相同。

from datetime import datetime
from ctypes import windll, WinError
import time, os, sys, serial, moncontrol

while True:
        handles = []
        for handle in moncontrol.iter_physical_monitors(False):
            handles.append(handle)

        try:
            data = arduino.readline()[:-2]
            currMon = moncontrol.get_monitor_input(handles[monitor], 0x60)
            if data:
               ... irrelevant code ...
            # Check if input has changed unexpectedly
            elif (currMon == DP and current == HDMI) or (currMon == HDMI and current == DP):
                if current == HDMI:
                    arduino.write('1'.encode())
                    current = DP
                elif current == DP:
                    arduino.write('2'.encode())
                    current = HDMI
                else:
                    destroyHandles(handles)
        except serial.SerialException:
            destroyHandles(handles)
            createLog()

        destroyHandles(handles)

def destroyHandles(handles):
    for handle in handles:
        windll.dxva2.DestroyPhysicalMonitor(handle)

moncontrol.py:

from ctypes import windll, byref, Structure, WinError, POINTER, WINFUNCTYPE
from ctypes.wintypes import BOOL, HMONITOR, HDC, RECT, LPARAM, DWORD, BYTE, WCHAR, HANDLE


_MONITORENUMPROC = WINFUNCTYPE(BOOL, HMONITOR, HDC, POINTER(RECT), LPARAM)


class _PHYSICAL_MONITOR(Structure):
    _fields_ = [('handle', HANDLE),
                ('description', WCHAR * 128)]


def iter_physical_monitors(close_handles=True):
    """Iterates physical monitors.
    The handles are closed automatically whenever the iterator is advanced.
    This means that the iterator should always be fully exhausted!
    If you want to keep handles e.g. because you need to store all of them and
    use them later, set `close_handles` to False and close them manually."""

    def callback(hmonitor, hdc, lprect, lparam):
        monitors.append(HMONITOR(hmonitor))
        return True

    monitors = []
    if not windll.user32.EnumDisplayMonitors(None, None, _MONITORENUMPROC(callback), None):
        raise WinError('EnumDisplayMonitors failed')

    for monitor in monitors:
        # Get physical monitor count
        count = DWORD()
        windll.dxva2.GetNumberOfPhysicalMonitorsFromHMONITOR(monitor, byref(count))

        # Get physical monitor handles
        physical_array = (_PHYSICAL_MONITOR * count.value)()
        windll.dxva2.GetPhysicalMonitorsFromHMONITOR(monitor, count.value, physical_array)
        for physical in physical_array:
            yield physical.handle
            if close_handles:
                if not windll.dxva2.DestroyPhysicalMonitor(physical.handle):
                    raise WinError()


def get_monitor_input(monitor, code):
    current = DWORD()
    windll.dxva2.GetVCPFeatureAndVCPFeatureReply(monitor, code, None, byref(current), None)
    return current.value


def set_vcp_feature(monitor, code, value):
    """Sends a DDC command to the specified monitor.
    See this link for a list of commands:
    ftp://ftp.cis.nctu.edu.tw/pub/csie/Software/X11/private/VeSaSpEcS/VESA_Document_Center_Monitor_Interface/mccsV3.pdf
    """
    if not windll.dxva2.SetVCPFeature(HANDLE(monitor), BYTE(code), DWORD(value)):
        raise WinError()