Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Monitor names are swapped on X11 #202

Open
WieeRd opened this issue Nov 18, 2024 · 8 comments · May be fixed by #203
Open

Monitor names are swapped on X11 #202

WieeRd opened this issue Nov 18, 2024 · 8 comments · May be fixed by #203

Comments

@WieeRd
Copy link

WieeRd commented Nov 18, 2024

Describe the bug

I have 2 monitors connected, HDMI-0 and DP-0.

❯ xrandr | rg " connected "
HDMI-0 connected 1080x1920+2560+0 right (normal left inverted right x axis y axis) 527mm x 296mm
DP-0 connected primary 2560x1440+0+0 (normal left inverted right x axis y axis) 597mm x 336mm

image

The above screenshot was captured on DP-0, the 2560x1440 one.
For some reason, the monitor names seem to be swapped with each other.
When I set a wallpaper for HDMI-0 it is applied to DP-0 and vice versa.

❯ xrandr --output HDMI-0 --off
❯ xrandr | rg " connected "
HDMI-0 connected (normal left inverted right x axis y axis)
DP-0 connected primary 2560x1440+0+0 (normal left inverted right x axis y axis) 597mm x 336mm

image

If I disable HDMI-0 and have only DP-0 enabled, only HDMI-0 is shown in Azote.
And of course, it is actually linked with DP-0.

I have yet to test what happens if I plug in 3 monitors because I don't have a third one.
Perhaps is there an off-by-one indexing error for getting the monitor names?

Desktop (please complete the following information):

  • Linux distribution: Arch
  • WM: Qtile (X11)

Azote version (please state it clearly if you use -git version):

  • 1.13.1
@WieeRd
Copy link
Author

WieeRd commented Nov 21, 2024

TL;DR - I have 1: DP-0 (2560x1440) and 2: HDMI-0 (1920x1080).
In Azote their names and resolutions are swapped and shown as 1: HDMI-0 (1920x1080) and 2: DP-0 (2560x1440).
When only DP-0 is enabled, somehow the resolution is correct while the name is not and shown as HDMI-0 (2560x1440).

This is very strange...

@nwg-piotr
Copy link
Owner

nwg-piotr commented Nov 21, 2024

LOL, you made me launch Openbox first time in a couple of years. If configured well with arandr, outputs show up properly on my side. I have no idea what goes wrong on your machine. I abandoned X11 years ago.

2024-11-22-004517_5560x1920_scrot

❯ xrandr | rg " connected "
eDP connected primary 1920x1080+3640+544 (normal left inverted right x axis y axis) 344mm x 193mm
DisplayPort-1-0 connected 2560x1440+1080+300 (normal left inverted right x axis y axis) 597mm x 336mm
HDMI-A-1-0 connected 1080x1920+0+0 left (normal left inverted right x axis y axis) 518mm x 324mm

@WieeRd
Copy link
Author

WieeRd commented Nov 22, 2024

As a X11 user I can confirm I don't remember what happiness feels like.
This bug doesn't really hinder the features of Azote too much since the wallpaper settings still work.
However it is such a weird issue that I'm willing to track it down just to see what caused it, out of curiosity.

Any idea where to take a look or ways to debug this? None of the other tools such as arandr or wallpaper / monitor configuration tools from KDE/GNOME seem to report monitor names incorrectly.

@nwg-piotr
Copy link
Owner

On X11 Azote detects monitors on the basis of the xrandr output, see:

try:

The code is old, untouched for years, and could be optimized, but it should probably work well.

@WieeRd
Copy link
Author

WieeRd commented Nov 22, 2024

import subprocess

names = (
    subprocess.check_output("xrandr | awk '/ connected/{print $1}'", shell=True)
    .decode("utf-8")
    .splitlines()
)
res = (
    subprocess.check_output("xrandr | awk '/[*]/{print $1}'", shell=True)
    .decode("utf-8")
    .splitlines()
)
coords = (
    subprocess.check_output("xrandr --listmonitors | awk '{print $3}'", shell=True)
    .decode("utf-8")
    .splitlines()
)

displays = []
for i in range(len(res)):
    w_h = res[i].split("x")
    try:
        x_y = coords[i + 1].split("+")
    except:  # noqa: E722
        x_y = (0, 0, 0)

    displays.append({
        "name": names[i],
        "x": x_y[1],
        "y": x_y[2],
        "width": int(w_h[0]),
        "height": int(w_h[1]),
        "xrandr-idx": i,
    })

print(sorted(displays, key=lambda x: (x.get("x"), x.get("y"))))

I've extracted that part of code and tested it in isolation. This is definitely the root cause of the bug.
The messed up shuffled names and resolutions can be observed from the output of this code.

The Problem

It assumes that xrandr and xrandr --listmonitors will print the monitors in the same order, and uses the same index i for getting the information (res[i], coords[i + 1], names[i]).

In reality, xrandr seems to print the monitors in a fixed order decided by the priority of the monitor ports on the motherboard.

❯ xrandr | grep " connected"
HDMI-0 connected 1080x1920+2560+0 right (normal left inverted right x axis y axis) 527mm x 296mm
DP-0 connected primary 2560x1440+0+0 (normal left inverted right x axis y axis) 597mm x 336mm

xrandr --listmonitors seems to always print the primary monitor first.
(When I change the primary monitor to HDMI-0 it is shown as 0: +*HDMI-0 and printed first)

❯ xrandr --listmonitors
Monitors: 2
 0: +*DP-0 2560/597x1440/336+0+0  DP-0
 1: +HDMI-0 1080/527x1920/296+2560+0  HDMI-0

This explains why the name and resolution were swapped when both monitors were enabled.


❯ xrandr --output HDMI-0 --off

When a monitor is disabled using xrandr --off it still counts as "connected" in xrandr output.

❯ xrandr | grep " connected"
HDMI-0 connected (normal left inverted right x axis y axis)
DP-0 connected primary 2560x1440+0+0 (normal left inverted right x axis y axis) 597mm x 336mm

But in xrandr --listmonitors it is completely invisible.

❯ xrandr --listmonitors
Monitors: 1
 0: +*DP-0 2560/597x1440/336+0+0  DP-0

This explains the wrong name and correct resolution in the second screenshot, when I disabled HDMI-0.

Looks like you were extremely lucky (or unlucky?) to have the output of xrandr and listmonitors always aligned every time you tested Azote on your setup LOL

@WieeRd
Copy link
Author

WieeRd commented Nov 22, 2024

It shouldn't be too hard to fix, I'll return with a PR during the weekends. The surrounding code could also use some work.

@WieeRd
Copy link
Author

WieeRd commented Nov 24, 2024

@nwg-piotr Do you mind adding an extra optional dependency, python-xlib?

Dealing with the xrandr output was painful, dirty, and still unsure if I've correctly implemented it. It probably isn't. Probably the reason why you wrote once and never wanted to read for years.

On the other hand, this is how clean it gets if we use a dedicated library instead.

from Xlib import display

display = display.Display()
root = display.screen().root
for i, m in enumerate(root.xrandr_get_monitors().monitors):
    print({
        "name": display.get_atom_name(m.name),
        "x": m.x,
        "y": m.y,
        "width": m.width_in_pixels,
        "height": m.height_in_pixels,
        "xrandr-idx": i,
    })

python-xlib is very much available on PyPI, Arch, Ubuntu, Fedora, and NixOS Stable.
Many of your nwg-shell tools already indirectly require python-xlib as a dependency of python-i3ipc as well.
Also, the xorg-xrandr dependency can be dropped. Did I mention how I hate parsing plaintext outputs without well-defined formats?

@nwg-piotr
Copy link
Owner

nwg-piotr commented Nov 24, 2024

Probably the reason why you wrote once and never wanted to read for years.

Haha, I started this repo right after migrating all my stuff from i3 to sway, back in 2019. I added some X11 support via feh, but never actually used it.

Add the Xlib dependency if you need, but from now on you're responsible for X11-related stuff in this program. ;)

Please make it optional: the program cannot crash when Xlib absent.

@WieeRd WieeRd linked a pull request Nov 25, 2024 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants