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

x11: Add support for get_monitors() #1804

Merged
merged 8 commits into from
Jun 23, 2021

Conversation

psychon
Copy link
Contributor

@psychon psychon commented May 23, 2021

Like everything X11, getting the list of monitors is complicated.

X11 has a concept of "screen" in the core protocol. This is not really
used these days, because it is not possible to move windows between
screens, but that is the common fallback.

Then came the Xinerama extension. Let's ignore that here...

Next up is the RandR extension. It allows to query information about the
actually connected hardware and its configuration via a
GetScreenResources request. Since asking the hardware about its state is
sometimes slow, GetScreenResourcesCurrent was later added, which does
not ask the hardware, but only provides the newest information that is
available to the X11 server.

Next came high resolution displays with resolution so high that they
needed to be connected with two cables. These display appear as two
CRTCs, which is a problem. We don't want applications to think your
display is actually two displays. However, RandR provided way too much
information about CRTCs and stuff and it was not easy to just hide this
all. Thus, RandR monitors were added and a monitor can consist of more
than one CRTC and everything is fine.

Thanks to the above, there are lots of special cases here. I only tested
the RandR monitor case in this commit.

Signed-off-by: Uli Schlachter psychon@znc.in


Edit: All the testing I did:

use druid_shell::Screen;

fn main() {
    println!("{:?}", Screen::get_monitors());
}
[Monitor { primary: true, rect: Rect { x0: 0.0, y0: 0.0, x1: 1920.0, y1: 1080.0 }, work_rect: Rect { x0: 0.0, y0: 0.0, x1: 1920.0, y1: 1080.0 } }, Monitor { primary: false, rect: Rect { x0: 1280.0, y0: 0.0, x1: 1920.0, y1: 1024.0 }, work_rect: Rect { x0: 1280.0, y0: 0.0, x1: 1920.0, y1: 1024.0 } }]

Like everything X11, getting the list of monitors is complicated.

X11 has a concept of "screen" in the core protocol. This is not really
used these days, because it is not possible to move windows between
screens, but that is the common fallback.

Then came the Xinerama extension. Let's ignore that here...

Next up is the RandR extension. It allows to query information about the
actually connected hardware and its configuration via a
GetScreenResources request. Since asking the hardware about its state is
sometimes slow, GetScreenResourcesCurrent was later added, which does
not ask the hardware, but only provides the newest information that is
available to the X11 server.

Next came high resolution displays with resolution so high that they
needed to be connected with two cables. These display appear as two
CRTCs, which is a problem. We don't want applications to think your
display is actually two displays. However, RandR provided way too much
information about CRTCs and stuff and it was not easy to just hide this
all. Thus, RandR monitors were added and a monitor can consist of more
than one CRTC and everything is fine.

Thanks to the above, there are lots of special cases here. I only tested
the RandR monitor case in this commit.

Signed-off-by: Uli Schlachter <psychon@znc.in>
Signed-off-by: Uli Schlachter <psychon@znc.in>
Signed-off-by: Uli Schlachter <psychon@znc.in>
Signed-off-by: Uli Schlachter <psychon@znc.in>
Re-use existing App instance, as suggested by @maan2003

Co-authored-by: Manmeet Maan <49202620+Maan2003@users.noreply.github.com>
Copy link
Collaborator

@jneem jneem left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

I'm going to admit that I only tested it with RandR 1.6. Is there a convenient way to test older versions?

@psychon
Copy link
Contributor Author

psychon commented Jun 12, 2021

Not really. I never tested it at all. I guess one could hack the code so that it "detects" an older version...

If you want, I can remove everything but latest RandR.

@jneem
Copy link
Collaborator

jneem commented Jun 14, 2021

Oh good point, I can try using that to test the other code paths. Maybe @cmyr has an opinion about how much maintenance burden we're willing to take on to support older versions? According to wikipedia, RandR 1.5 was released in 2015, and 1.3 was released in 2009.

Signed-off-by: Uli Schlachter <psychon@znc.in>
@psychon
Copy link
Contributor Author

psychon commented Jun 15, 2021

Well, the different code paths do have different results.

patch
diff --git a/druid-shell/Cargo.toml b/druid-shell/Cargo.toml
index 5815f9d1..c0db8cba 100755
--- a/druid-shell/Cargo.toml
+++ b/druid-shell/Cargo.toml
@@ -36,6 +36,9 @@ tga = ["piet-common/tga"]
 farbfeld = ["piet-common/farbfeld"]
 dxt = ["piet-common/dxt"]
 hdr = ["piet-common/hdr"]
+randr_15 = []
+randr_13 = []
+randr_12 = []
 
 [dependencies]
 # NOTE: When changing the piet or kurbo versions, ensure that
diff --git a/druid-shell/src/platform/x11/screen.rs b/druid-shell/src/platform/x11/screen.rs
index 3607cf22..195ba5e5 100644
--- a/druid-shell/src/platform/x11/screen.rs
+++ b/druid-shell/src/platform/x11/screen.rs
@@ -73,15 +73,20 @@ fn get_monitors_impl(
     // Monitor support was added in RandR 1.5
     let version = conn.randr_query_version(1, 5)?.reply()?;
     match (version.major_version, version.minor_version) {
+        #[cfg(feature="randr_15")]
         (major, _) if major >= 2 => get_monitors_randr_monitors(conn, screen),
+        #[cfg(feature="randr_15")]
         (1, minor) if minor >= 5 => get_monitors_randr_monitors(conn, screen),
+        #[cfg(feature="randr_13")]
         (1, minor) if minor >= 3 => get_monitors_randr_screen_resources_current(conn, screen),
+        #[cfg(feature="randr_12")]
         (1, minor) if minor >= 2 => get_monitors_randr_screen_resources(conn, screen),
         _ => get_monitors_core(screen),
     }
 }
 
 fn get_monitors_core(screen: &Screen) -> Result<Vec<Monitor>, ReplyOrIdError> {
+    println!("get_monitors_core");
     Ok(vec![monitor(
         true,
         (0, 0),
@@ -93,6 +98,7 @@ fn get_monitors_randr_monitors(
     conn: &impl Connection,
     screen: &Screen,
 ) -> Result<Vec<Monitor>, ReplyOrIdError> {
+    println!("get_monitors_randr_monitors");
     let result = conn
         .randr_get_monitors(screen.root, true)?
         .reply()?
@@ -107,6 +113,7 @@ fn get_monitors_randr_screen_resources_current(
     conn: &impl Connection,
     screen: &Screen,
 ) -> Result<Vec<Monitor>, ReplyOrIdError> {
+    println!("get_monitors_randr_screen_resources_current");
     let reply = conn
         .randr_get_screen_resources_current(screen.root)?
         .reply()?;
@@ -117,6 +124,7 @@ fn get_monitors_randr_screen_resources(
     conn: &impl Connection,
     screen: &Screen,
 ) -> Result<Vec<Monitor>, ReplyOrIdError> {
+    println!("get_monitors_randr_screen_resources");
     let reply = conn.randr_get_screen_resources(screen.root)?.reply()?;
     get_monitors_randr_crtcs_timestamp(conn, &reply.crtcs, reply.config_timestamp)
 }

Output (I removed compiler warnings about unused functions that are a side-effect of my patch):

$ for feat in randr_15 randr_13 randr_12 "" ; do RUST_BACKTRACE=1 cargo -q run --no-default-features --features "x11 $feat" --example=get_monitors ; done                                                     
[...]

get_monitors_randr_monitors
[Monitor { primary: true, rect: Rect { x0: 0.0, y0: 0.0, x1: 1920.0, y1: 1080.0 }, work_rect: Rect { x0: 0.0, y0: 0.0, x1: 1920.0, y1: 1080.0 } }, Monitor { primary: false, rect: Rect { x0: 1280.0, y0: 0.0, x1: 1920.0, y1: 1024.0 }, work_rect: Rect { x0: 1280.0, y0: 0.0, x1: 1920.0, y1: 1024.0 } }]
[...]

get_monitors_randr_screen_resources_current
[Monitor { primary: true, rect: Rect { x0: 0.0, y0: 0.0, x1: 1920.0, y1: 1080.0 }, work_rect: Rect { x0: 0.0, y0: 0.0, x1: 1920.0, y1: 1080.0 } }, Monitor { primary: false, rect: Rect { x0: 1280.0, y0: 0.0, x1: 1920.0, y1: 1024.0 }, work_rect: Rect { x0: 1280.0, y0: 0.0, x1: 1920.0, y1: 1024.0 } }, Monitor { primary: false, rect: Rect { x0: 0.0, y0: 0.0, x1: 0.0, y1: 0.0 }, work_rect: Rect { x0: 0.0, y0: 0.0, x1: 0.0, y1: 0.0 } }]
[...]

get_monitors_randr_screen_resources
[Monitor { primary: true, rect: Rect { x0: 0.0, y0: 0.0, x1: 1920.0, y1: 1080.0 }, work_rect: Rect { x0: 0.0, y0: 0.0, x1: 1920.0, y1: 1080.0 } }, Monitor { primary: false, rect: Rect { x0: 1280.0, y0: 0.0, x1: 1920.0, y1: 1024.0 }, work_rect: Rect { x0: 1280.0, y0: 0.0, x1: 1920.0, y1: 1024.0 } }, Monitor { primary: false, rect: Rect { x0: 0.0, y0: 0.0, x1: 0.0, y1: 0.0 }, work_rect: Rect { x0: 0.0, y0: 0.0, x1: 0.0, y1: 0.0 } }]
[...]

get_monitors_core
[Monitor { primary: true, rect: Rect { x0: 0.0, y0: 0.0, x1: 3200.0, y1: 1080.0 }, work_rect: Rect { x0: 0.0, y0: 0.0, x1: 3200.0, y1: 1080.0 } }]

The truth:

$ xrandr
Screen 0: minimum 320 x 200, current 3200 x 1080, maximum 16384 x 16384
HDMI-1 connected primary 1920x1080+0+0 (normal left inverted right x axis y axis) 477mm x 268mm
   1920x1080     60.00*+
   1680x1050     59.88  
   1400x1050     59.95  
   1600x900      60.00  
   1280x1024     75.02    60.02  
   1440x900      59.90  
   1280x800      59.91  
   1152x864      75.00  
   1280x720      60.00  
   1024x768      75.03    60.00  
   800x600       75.00    60.32  
   640x480       75.00    59.94  
   720x400       70.08  
HDMI-2 disconnected (normal left inverted right x axis y axis)
DP-1 disconnected (normal left inverted right x axis y axis)
HDMI-3 disconnected (normal left inverted right x axis y axis)
DP-2 connected 1280x1024+1920+0 (normal left inverted right x axis y axis) 376mm x 301mm
   1280x1024     60.02*+  75.02  
   1152x864      75.00  
   1024x768      75.03    70.07    60.00  
   832x624       74.55  
   800x600       72.19    75.00    60.32    56.25  
   640x480       75.00    72.81    66.67    59.94  
   720x400       70.08  

Looks pretty much correct except for those "empty" disconnected outputs.

New commit pushed which just ignores CRTCs with width or height equal to zero. New output is:

$ for feat in randr_15 randr_13 randr_12 "" ; do RUST_BACKTRACE=1 cargo -q run --no-default-features --features "x11 $feat" --example=get_monitors ; done
get_monitors_randr_monitors
[Monitor { primary: true, rect: Rect { x0: 0.0, y0: 0.0, x1: 1920.0, y1: 1080.0 }, work_rect: Rect { x0: 0.0, y0: 0.0, x1: 1920.0, y1: 1080.0 } }, Monitor { primary: false, rect: Rect { x0: 1280.0, y0: 0.0, x1: 1920.0, y1: 1024.0 }, work_rect: Rect { x0: 1280.0, y0: 0.0, x1: 1920.0, y1: 1024.0 } }]
get_monitors_randr_screen_resources_current
[Monitor { primary: true, rect: Rect { x0: 0.0, y0: 0.0, x1: 1920.0, y1: 1080.0 }, work_rect: Rect { x0: 0.0, y0: 0.0, x1: 1920.0, y1: 1080.0 } }, Monitor { primary: false, rect: Rect { x0: 1280.0, y0: 0.0, x1: 1920.0, y1: 1024.0 }, work_rect: Rect { x0: 1280.0, y0: 0.0, x1: 1920.0, y1: 1024.0 } }]
get_monitors_randr_screen_resources
[Monitor { primary: true, rect: Rect { x0: 0.0, y0: 0.0, x1: 1920.0, y1: 1080.0 }, work_rect: Rect { x0: 0.0, y0: 0.0, x1: 1920.0, y1: 1080.0 } }, Monitor { primary: false, rect: Rect { x0: 1280.0, y0: 0.0, x1: 1920.0, y1: 1024.0 }, work_rect: Rect { x0: 1280.0, y0: 0.0, x1: 1920.0, y1: 1024.0 } }]
get_monitors_core
[Monitor { primary: true, rect: Rect { x0: 0.0, y0: 0.0, x1: 3200.0, y1: 1080.0 }, work_rect: Rect { x0: 0.0, y0: 0.0, x1: 3200.0, y1: 1080.0 } }]

@maan2003 maan2003 merged commit 18c0b3b into linebender:master Jun 23, 2021
@psychon psychon deleted the x11-get-monitors branch June 23, 2021 12:57
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 this pull request may close these issues.

None yet

3 participants