Skip to content

Commit

Permalink
x11: Add support for get_monitors() (#1804)
Browse files Browse the repository at this point in the history
* x11: Add support for get_monitors()

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 <[email protected]>

* Apply rustfmt's suggestion

Signed-off-by: Uli Schlachter <[email protected]>

* Silence a clippy warning

Signed-off-by: Uli Schlachter <[email protected]>

* rustfmt had more complaints...

Signed-off-by: Uli Schlachter <[email protected]>

* Update druid-shell/src/platform/x11/screen.rs

Re-use existing App instance, as suggested by @maan2003

Co-authored-by: Manmeet Maan <[email protected]>

* Ignore CRTCs with width/height = 0

Signed-off-by: Uli Schlachter <[email protected]>

Co-authored-by: Manmeet Maan <[email protected]>
Co-authored-by: jneem <[email protected]>
  • Loading branch information
3 people authored Jun 23, 2021
1 parent 3297ba4 commit 18c0b3b
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 2 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ You can find its changes [documented below](#070---2021-01-01).
- `lens` macro can access nested fields ([#1764] by [@Maan2003])
- X11 backend now supports custom cursors ([#1801] by [@psychon])
- X11: Add support for transparent windows ([#1803] by [@psychon])
- X11: Added support for `get_monitors` ([#1804] by [@psychon])
- `has_focus` method on `WidgetPod` ([#1825] by [@ForLoveOfCats])

### Changed
Expand Down Expand Up @@ -732,6 +733,7 @@ Last release without a changelog :(
[#1801]: https://github.com/linebender/druid/pull/1800
[#1802]: https://github.com/linebender/druid/pull/1802
[#1803]: https://github.com/linebender/druid/pull/1803
[#1804]: https://github.com/linebender/druid/pull/1804
[#1820]: https://github.com/linebender/druid/pull/1820
[#1825]: https://github.com/linebender/druid/pull/1825

Expand Down
138 changes: 136 additions & 2 deletions druid-shell/src/platform/x11/screen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,143 @@

//! X11 Monitors and Screen information.
use x11rb::connection::Connection;
use x11rb::errors::ReplyOrIdError;
use x11rb::protocol::randr::{self, ConnectionExt as _, Crtc};
use x11rb::protocol::xproto::{Screen, Timestamp};

use crate::kurbo::Rect;
use crate::screen::Monitor;

fn monitor<Pos>(primary: bool, (x, y): (Pos, Pos), (width, height): (u16, u16)) -> Monitor
where
Pos: Into<i32>,
{
let rect = Rect::from_points(
(x.into() as f64, y.into() as f64),
(width as f64, height as f64),
);
// TODO: Support for work_rect. It's complicated...
Monitor::new(primary, rect, rect)
}

pub(crate) fn get_monitors() -> Vec<Monitor> {
tracing::warn!("Screen::get_monitors() is currently unimplemented for X11 platforms.");
Vec::new()
let result = if let Some(app) = crate::Application::try_global() {
let app = app.platform_app;
get_monitors_impl(app.connection().as_ref(), app.screen_num() as usize)
} else {
let (conn, screen_num) = match x11rb::connect(None) {
Ok(res) => res,
Err(err) => {
tracing::error!("Error in Screen::get_monitors(): {:?}", err);
return Vec::new();
}
};
get_monitors_impl(&conn, screen_num)
};
match result {
Ok(monitors) => monitors,
Err(err) => {
tracing::error!("Error in Screen::get_monitors(): {:?}", err);
Vec::new()
}
}
}

fn get_monitors_impl(
conn: &impl Connection,
screen_num: usize,
) -> Result<Vec<Monitor>, ReplyOrIdError> {
let screen = &conn.setup().roots[screen_num];

if conn
.extension_information(randr::X11_EXTENSION_NAME)?
.is_none()
{
return get_monitors_core(screen);
}

// Monitor support was added in RandR 1.5
let version = conn.randr_query_version(1, 5)?.reply()?;
match (version.major_version, version.minor_version) {
(major, _) if major >= 2 => get_monitors_randr_monitors(conn, screen),
(1, minor) if minor >= 5 => get_monitors_randr_monitors(conn, screen),
(1, minor) if minor >= 3 => get_monitors_randr_screen_resources_current(conn, screen),
(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> {
Ok(vec![monitor(
true,
(0, 0),
(screen.width_in_pixels, screen.height_in_pixels),
)])
}

fn get_monitors_randr_monitors(
conn: &impl Connection,
screen: &Screen,
) -> Result<Vec<Monitor>, ReplyOrIdError> {
let result = conn
.randr_get_monitors(screen.root, true)?
.reply()?
.monitors
.iter()
.map(|info| monitor(info.primary, (info.x, info.y), (info.width, info.height)))
.collect();
Ok(result)
}

fn get_monitors_randr_screen_resources_current(
conn: &impl Connection,
screen: &Screen,
) -> Result<Vec<Monitor>, ReplyOrIdError> {
let reply = conn
.randr_get_screen_resources_current(screen.root)?
.reply()?;
get_monitors_randr_crtcs_timestamp(conn, &reply.crtcs, reply.config_timestamp)
}

fn get_monitors_randr_screen_resources(
conn: &impl Connection,
screen: &Screen,
) -> Result<Vec<Monitor>, ReplyOrIdError> {
let reply = conn.randr_get_screen_resources(screen.root)?.reply()?;
get_monitors_randr_crtcs_timestamp(conn, &reply.crtcs, reply.config_timestamp)
}

// This function first sends a number of requests, collect()ing them into a Vec and then gets the
// replies. This saves round-trips. Without the collect(), there would be one round-trip per CRTC.
#[allow(clippy::needless_collect)]
fn get_monitors_randr_crtcs_timestamp(
conn: &impl Connection,
crtcs: &[Crtc],
config_timestamp: Timestamp,
) -> Result<Vec<Monitor>, ReplyOrIdError> {
// Request information about all CRTCs
let requests = crtcs
.iter()
.map(|&crtc| conn.randr_get_crtc_info(crtc, config_timestamp))
.collect::<Vec<_>>();

// Deal with CRTC information
let mut result = Vec::new();
for request in requests.into_iter() {
let reply = request?.reply()?;
if reply.width != 0 && reply.height != 0 {
// First CRTC is assumed to be the primary output
let primary = result.is_empty();
result.push(monitor(
primary,
(reply.x, reply.y),
(reply.width, reply.height),
));
}
}
// TODO: I think we need to deduplicate monitors. In clone mode, each "clone" appears as its
// own monitor otherwise.

Ok(result)
}

0 comments on commit 18c0b3b

Please sign in to comment.