haimgel / display-switch

Turn a $30 USB switch into a full-featured multi-monitor KVM switch
https://haim.dev/posts/2020-07-28-dual-monitor-kvm/
MIT License
2.83k stars 110 forks source link

Out of Range #86

Closed blake-mealey closed 2 years ago

blake-mealey commented 2 years ago

I've been getting an "Out of Range" monitor UI blocking the middle of my screen after display-switch switches from HDMI (PC) to DisplayPort (Mac). Any ideas on how to troubleshoot/fix this?

Thanks

haimgel commented 2 years ago

Don't know for sure, but I wonder what happens when you do the same switch using the buttons on your monitor? Also, does the monitor actually switch to the right input, and then you get this message, or it does not switch, and the message is on top of a black screen?

If it's the later, then it's probably because DisplayPort is not the right port to choose in display-switch. These labels are somewhat arbitrary, and monitor vendors do not always follow conventions. It makes sense to switch manually, and then run display-switch, it will report the current setting if it can detect it, it might be not DisplayPort after all.

lauhayden commented 2 years ago

I'm getting this problem as well with my LG 27GL83A-B. This issue on the ddcutil repo seems related: https://github.com/rockowitz/ddcutil/issues/102

Don't know for sure, but I wonder what happens when you do the same switch using the buttons on your monitor?

Switching via buttons on monitor works, and if I'm getting the "Out of Range" UI, switching away and back using the physical buttons fixes it.

Also, does the monitor actually switch to the right input, and then you get this message, or it does not switch, and the message is on top of a black screen?

It switches, then the message appears on top of the new input. It also happens using ddcutil setvcp 60 <value>, so it's a problem with the monitor and not display-switch.

lauhayden commented 2 years ago

Just did some more testing:

Based on these results, it's possible that the "Out of Range" issue only exists when switching to Displayport, so maybe a workaround could be to only use the HDMI inputs.

haimgel commented 2 years ago

Yeah, TBH this looks like some kind of firmware bug in the LG monitor. Not uncommon, unfortunately. The manufacturers rarely test DDC/CI properly, in my opinion, especially in consumer-grade monitors.

nichcuta commented 2 years ago

I have a similar setup with a LG 27GL83A-B Monitor and indeed had the same issues described above although after some config changes it seems to be working reliably.

Setup and Config:

PC1: Windows 10
PC2: Macbook Pro - Catalina

PC1 --> DP cable -->  LG 27GL83A-B Monitor
PC1 --> DP to HDMI Cable --> Dell P2720DC Monitor

PC2 --> USB-C to HDMI Cable --> LG 27GL83A-B Monitor
PC2 --> USB-C Cable --> Dell P2720DC Monitor

Config on PC1

usb_device = "2972:0047"

[monitor1]
monitor_id = "ULTRAGEAR"    # Logitech
on_usb_disconnect = "hdmi1"

[monitor2]
monitor_id = "P2720DC"
on_usb_connect = "hdmi1"  

Config on PC2

usb_device = "2972:0047"

[monitor1]
monitor_id = "27GL850"    # Logitech
on_usb_connect = 17
on_usb_disconnect = 15

[monitor2]
monitor_id = "P2720DC"
on_usb_connect = 27
on_usb_disconnect = 17

The only issue I am facing is with the P2720DC Dell monitor. For some reason it is capable of switching from PC1 to PC2 and back to PC1 but upon trying to change from PC1 to PC2 again, the monitor simply says "No source detected on USB-C". Trying to figure this out but cant understand why this happens on the second cycle. If you have any suggestions, please do tell.

Hope the above config helps you :)

nichcuta commented 2 years ago

The P2720DC monitor goes in soft power off mode during the second cycle (although using on_usb_connect) and the only fix I found was to check for power state, revert to original source (HDMI), wait some seconds, and change back source to USB-C. Below is some basic logic written in python, but was wondering is such logic can be implemented in display-switcher as an enhancement, perhaps thats a question for @haimgel?

from monitorcontrol import get_monitors
from monitorcontrol.monitorcontrol import InputSourceValueError
from time import sleep

for monitor in get_monitors():
    with monitor:
        sleep(5)
        curr_mon_power = monitor.get_power_mode()
        if "PowerMode.off_soft" == str(curr_mon_power):
            monitor.set_input_source(17)    # Reverting back to HDMI Source
            sleep(3)
            monitor.set_input_source(27)    # Set Source to USB-C
haimgel commented 2 years ago

@nichcuta thanks for these comments, very helpful!

is such logic can be implemented in display-switcher as an enhancement

Can be? Yes. Should be? At this time, I don't think device-specific tweaks should be added... I don't have the bandwidth for this, and I need to think about how in general to approach this idea of adding device-specific code. Once we go that route, it can get messy pretty quickly.

lauhayden commented 2 years ago

I have a similar setup with a LG 27GL83A-B Monitor and indeed had the same issues described above although after some config changes it seems to be working reliably.

Huh, with that specific connect/disconnect setup on each computer it actually works without the "Out of Range" error! Thanks so much! I did notice that there was an additional step required: the manual input selection using the monitor's buttons needs to be set to "DisplayPort". This setup also does not work for a bizarre reason without the on_usb_connect = "Hdmi1" on the HDMI computer, which is ostensibly redundant.

EDIT: Never mind, I spoke too soon, it's not perfect - the LG monitor sometimes gets "stuck" on the HDMI input if there's some sleep/wake involved. Oh well, it's still better than changing by hand every time.

nichcuta commented 2 years ago

This setup also does not work for a bizarre reason without the on_usb_connect = "Hdmi1" on the HDMI computer, which is ostensibly redundant.

Yea, the configuration seems messy at best, but hey it works. You are right, forgot to mention that my primary PC is connected via DP and hence usually switch from it first.

Have not encountered the sleep/wake issue, but hopeful that it can be mitigated via code like the above mentioned solution for the P2720DC monitor.

nichcuta commented 2 years ago

For completeness, below is the python code I've implemented to mitigate the issue with the Dell P2720DC monitor. This is executed via display switch on USB change.

Python Code:

from monitorcontrol import get_monitors    # https://github.com/newAM/monitorcontrol
from time import sleep
import sys

try:
    expectedInputSource = sys.argv[1].upper()     # Expecting HDMI1 or USBC
except:
    print('Missing argument. Ex: HDMI1')
    exit()

for monitor in get_monitors():
    with monitor:
        # Added (USBC = 0x1B) to Line 47 in: C:\Program Files\Python39\Lib\site-packages\monitorcontrol\monitorcontrol.py
        if monitor.get_vcp_capabilities()['model'] == "P2720DC":    # Dell Monitor
            needsChanging = False
            currPowerMode = monitor.get_power_mode()    # Get current Power Mode
            currInputSource = monitor.get_input_source()    # Get current Input Mode
            # Checking if power mode is suspend (monitor reports this state while working fine) and expected input source is the same as current source
            if 'suspend' in str(currPowerMode) and expectedInputSource in str(currInputSource):
                print('Everything looks fine. Sleeping and rechecking')
            else:
                needsChanging = True    # Change is required, skip the below wait and recheck and change input
            # Incase the above returns fine, wait a few sec and recheck
            if not needsChanging:
                sleep(10)
                currPowerMode = monitor.get_power_mode()    # Get current Power Mode
                currInputSource = monitor.get_input_source()    # Get current Input Mode
                # Checking if power mode is suspend (monitor reports this state while working fine) and expected input source is the same as current source
                if 'suspend' in str(currPowerMode) and expectedInputSource in str(currInputSource):
                    print('Everything is ok!')
                else:
                    needsChanging = True    # Change is indeed required!
            if needsChanging:
                print('Changing monitor input to ' + expectedInputSource)
                oldInputSource = currInputSource    # Saving the current monitor input to be able to switchover if required
                monitor.set_input_source(expectedInputSource)   # Set new input source
                sleep(10)
                currPowerMode = monitor.get_power_mode()    # Get current Power Mode
                currInputSource = monitor.get_input_source()    # Get current Input Mode
                # Checking if power mode is suspend (monitor reports this state while working fine) and expected input source is the same as current source
                if 'suspend' in str(currPowerMode) and expectedInputSource in str(currInputSource):
                    print('Everything looks fine. Sleeping and rechecking')
                sleep(10)
                currPowerMode = monitor.get_power_mode()    # Get current Power Mode
                currInputSource = monitor.get_input_source()    # Get current Input Mode
                # Checking if power mode is suspend (monitor reports this state while working fine) and expected input source is the same as current source
                if 'suspend' in str(currPowerMode) and expectedInputSource in str(currInputSource):
                    print('Everything is ok!')
                # First monitor input change resulted in a 'No Signal'. Reverting back to previous input and changing a second time.
                else:
                    monitor.set_input_source(oldInputSource)    # Set old input source
                    sleep(6)
                    monitor.set_input_source(expectedInputSource)   # Set new input source
                    print('Changed monitor input for the second time!')

Display_switch.ini Config Example:

usb_device = "2972:0047" 
on_usb_connect_execute = "'C:\\Program Files\\Python39\\pythonw.exe' 'C:\\Program Files\\DisplaySwitcher\\dell_switch.pyw' HDMI1"
on_usb_disconnect_execute = "'C:\\Program Files\\Python39\\pythonw.exe' 'C:\\Program Files\\DisplaySwitcher\\dell_switch.pyw' USBC"