2025-01-02 03:29:42 +03:00
|
|
|
use std::num::NonZeroU32;
|
2024-07-23 19:59:37 +02:00
|
|
|
|
2023-08-29 14:01:25 -07:00
|
|
|
use x11rb::connection::RequestConnection;
|
|
|
|
|
use x11rb::protocol::randr::{self, ConnectionExt as _};
|
|
|
|
|
use x11rb::protocol::xproto;
|
2018-05-14 08:14:57 -04:00
|
|
|
|
2023-08-29 14:01:25 -07:00
|
|
|
use super::{util, X11Error, XConnection};
|
2025-01-02 03:29:42 +03:00
|
|
|
use crate::dpi::PhysicalPosition;
|
|
|
|
|
use crate::monitor::VideoMode;
|
2024-07-07 18:38:50 +02:00
|
|
|
|
2019-07-29 21:16:14 +03:00
|
|
|
// Used for testing. This should always be committed as false.
|
2018-05-14 08:14:57 -04:00
|
|
|
const DISABLE_MONITOR_LIST_CACHING: bool = false;
|
|
|
|
|
|
2023-08-29 14:01:25 -07:00
|
|
|
impl XConnection {
|
|
|
|
|
pub fn invalidate_cached_monitor_list(&self) -> Option<Vec<MonitorHandle>> {
|
|
|
|
|
// We update this lazily.
|
|
|
|
|
self.monitor_handles.lock().unwrap().take()
|
|
|
|
|
}
|
2018-05-14 08:14:57 -04:00
|
|
|
}
|
|
|
|
|
|
2019-07-29 21:16:14 +03:00
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
2023-12-26 22:12:33 +01:00
|
|
|
pub struct VideoModeHandle {
|
2024-07-21 00:01:43 +02:00
|
|
|
pub(crate) current: bool,
|
2025-01-02 03:29:42 +03:00
|
|
|
pub(crate) mode: VideoMode,
|
2023-08-29 14:01:25 -07:00
|
|
|
pub(crate) native_mode: randr::Mode,
|
2019-07-29 21:16:14 +03:00
|
|
|
pub(crate) monitor: Option<MonitorHandle>,
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-02 03:29:42 +03:00
|
|
|
impl From<VideoModeHandle> for VideoMode {
|
|
|
|
|
fn from(handle: VideoModeHandle) -> Self {
|
|
|
|
|
handle.mode
|
2019-07-29 21:16:14 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-14 08:14:57 -04:00
|
|
|
#[derive(Debug, Clone)]
|
2019-02-05 10:30:33 -05:00
|
|
|
pub struct MonitorHandle {
|
2017-09-07 09:33:46 +01:00
|
|
|
/// The actual id
|
2023-08-29 14:01:25 -07:00
|
|
|
pub(crate) id: randr::Crtc,
|
2017-09-07 09:33:46 +01:00
|
|
|
/// The name of the monitor
|
2018-06-14 19:42:18 -04:00
|
|
|
pub(crate) name: String,
|
2017-09-07 09:33:46 +01:00
|
|
|
/// The position of the monitor in the X screen
|
2024-07-23 19:59:37 +02:00
|
|
|
pub(crate) position: (i32, i32),
|
2017-09-07 09:33:46 +01:00
|
|
|
/// If the monitor is the primary one
|
|
|
|
|
primary: bool,
|
2018-05-14 08:14:57 -04:00
|
|
|
/// The DPI scale factor
|
2020-01-03 14:52:27 -05:00
|
|
|
pub(crate) scale_factor: f64,
|
2018-05-14 08:14:57 -04:00
|
|
|
/// Used to determine which windows are on this monitor
|
2018-07-01 11:01:46 -04:00
|
|
|
pub(crate) rect: util::AaRect,
|
2019-06-12 21:07:25 +03:00
|
|
|
/// Supported video modes on this monitor
|
2025-01-02 03:29:42 +03:00
|
|
|
pub(crate) video_modes: Vec<VideoModeHandle>,
|
2017-09-07 09:33:46 +01:00
|
|
|
}
|
2015-05-07 13:14:09 +02:00
|
|
|
|
2019-07-29 21:16:14 +03:00
|
|
|
impl PartialEq for MonitorHandle {
|
|
|
|
|
fn eq(&self, other: &Self) -> bool {
|
|
|
|
|
self.id == other.id
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Eq for MonitorHandle {}
|
|
|
|
|
|
|
|
|
|
impl PartialOrd for MonitorHandle {
|
|
|
|
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
2022-01-01 13:00:11 +11:00
|
|
|
Some(self.cmp(other))
|
2019-07-29 21:16:14 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Ord for MonitorHandle {
|
|
|
|
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
|
|
|
|
self.id.cmp(&other.id)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl std::hash::Hash for MonitorHandle {
|
|
|
|
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
|
|
|
|
self.id.hash(state);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-08 13:25:56 +03:00
|
|
|
#[inline]
|
2024-07-23 19:59:37 +02:00
|
|
|
pub fn mode_refresh_rate_millihertz(mode: &randr::ModeInfo) -> Option<NonZeroU32> {
|
2023-08-29 14:01:25 -07:00
|
|
|
if mode.dot_clock > 0 && mode.htotal > 0 && mode.vtotal > 0 {
|
2022-12-22 21:35:33 +02:00
|
|
|
#[allow(clippy::unnecessary_cast)]
|
2024-07-23 19:59:37 +02:00
|
|
|
NonZeroU32::new(
|
|
|
|
|
(mode.dot_clock as u64 * 1000 / (mode.htotal as u64 * mode.vtotal as u64)) as u32,
|
|
|
|
|
)
|
2022-07-08 13:25:56 +03:00
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-05 10:30:33 -05:00
|
|
|
impl MonitorHandle {
|
2019-07-29 21:16:14 +03:00
|
|
|
fn new(
|
2018-06-14 19:42:18 -04:00
|
|
|
xconn: &XConnection,
|
2023-08-29 14:01:25 -07:00
|
|
|
resources: &ScreenResources,
|
|
|
|
|
id: randr::Crtc,
|
|
|
|
|
crtc: &randr::GetCrtcInfoReply,
|
2018-05-14 08:14:57 -04:00
|
|
|
primary: bool,
|
2018-11-17 15:51:39 -05:00
|
|
|
) -> Option<Self> {
|
2023-08-29 14:01:25 -07:00
|
|
|
let (name, scale_factor, video_modes) = xconn.get_output_info(resources, crtc)?;
|
|
|
|
|
let dimensions = (crtc.width as u32, crtc.height as u32);
|
|
|
|
|
let position = (crtc.x as i32, crtc.y as i32);
|
2022-07-08 13:25:56 +03:00
|
|
|
|
2018-07-01 11:01:46 -04:00
|
|
|
let rect = util::AaRect::new(position, dimensions);
|
2022-07-08 13:25:56 +03:00
|
|
|
|
2024-07-21 00:40:57 +02:00
|
|
|
Some(MonitorHandle { id, name, scale_factor, position, primary, rect, video_modes })
|
2017-09-07 09:33:46 +01:00
|
|
|
}
|
2014-09-19 15:42:47 +02:00
|
|
|
|
2019-11-10 13:55:29 -07:00
|
|
|
pub fn dummy() -> Self {
|
2019-09-23 11:45:29 -07:00
|
|
|
MonitorHandle {
|
|
|
|
|
id: 0,
|
|
|
|
|
name: "<dummy monitor>".into(),
|
2020-01-03 14:52:27 -05:00
|
|
|
scale_factor: 1.0,
|
2019-09-23 11:45:29 -07:00
|
|
|
position: (0, 0),
|
|
|
|
|
primary: true,
|
|
|
|
|
rect: util::AaRect::new((0, 0), (1, 1)),
|
|
|
|
|
video_modes: Vec::new(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-04 10:18:20 -07:00
|
|
|
pub(crate) fn is_dummy(&self) -> bool {
|
|
|
|
|
// Zero is an invalid XID value; no real monitor will have it
|
|
|
|
|
self.id == 0
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-29 21:29:54 -04:00
|
|
|
pub fn name(&self) -> Option<String> {
|
2017-09-07 09:33:46 +01:00
|
|
|
Some(self.name.clone())
|
2014-09-19 15:42:47 +02:00
|
|
|
}
|
|
|
|
|
|
2015-09-21 14:42:05 +02:00
|
|
|
#[inline]
|
2019-05-29 21:29:54 -04:00
|
|
|
pub fn native_identifier(&self) -> u32 {
|
2022-12-22 21:35:33 +02:00
|
|
|
self.id as _
|
2015-03-16 13:52:58 -07:00
|
|
|
}
|
|
|
|
|
|
2024-07-23 19:59:37 +02:00
|
|
|
pub fn position(&self) -> Option<PhysicalPosition<i32>> {
|
|
|
|
|
Some(self.position.into())
|
2014-09-19 15:42:47 +02:00
|
|
|
}
|
2017-10-17 14:56:38 +03:00
|
|
|
|
|
|
|
|
#[inline]
|
2020-01-03 14:52:27 -05:00
|
|
|
pub fn scale_factor(&self) -> f64 {
|
|
|
|
|
self.scale_factor
|
2017-10-17 14:56:38 +03:00
|
|
|
}
|
2019-06-12 21:07:25 +03:00
|
|
|
|
2024-07-21 00:01:43 +02:00
|
|
|
#[inline]
|
2025-01-02 03:29:42 +03:00
|
|
|
pub fn current_video_mode(&self) -> Option<VideoMode> {
|
|
|
|
|
self.video_modes.iter().find(|mode| mode.current).cloned().map(Into::into)
|
2024-07-21 00:01:43 +02:00
|
|
|
}
|
|
|
|
|
|
2019-06-12 21:07:25 +03:00
|
|
|
#[inline]
|
2025-01-02 03:29:42 +03:00
|
|
|
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
|
|
|
|
|
self.video_modes.clone().into_iter().map(Into::into)
|
2019-06-12 21:07:25 +03:00
|
|
|
}
|
2014-09-19 15:42:47 +02:00
|
|
|
}
|
2018-05-14 08:14:57 -04:00
|
|
|
|
2018-06-14 19:42:18 -04:00
|
|
|
impl XConnection {
|
2023-08-29 14:01:25 -07:00
|
|
|
pub fn get_monitor_for_window(
|
|
|
|
|
&self,
|
|
|
|
|
window_rect: Option<util::AaRect>,
|
|
|
|
|
) -> Result<MonitorHandle, X11Error> {
|
|
|
|
|
let monitors = self.available_monitors()?;
|
2019-09-23 11:45:29 -07:00
|
|
|
|
|
|
|
|
if monitors.is_empty() {
|
|
|
|
|
// Return a dummy monitor to avoid panicking
|
2023-08-29 14:01:25 -07:00
|
|
|
return Ok(MonitorHandle::dummy());
|
2019-09-23 11:45:29 -07:00
|
|
|
}
|
|
|
|
|
|
2023-10-27 00:56:23 +04:00
|
|
|
let default = monitors.first().unwrap();
|
2018-05-14 08:14:57 -04:00
|
|
|
|
2018-06-14 19:42:18 -04:00
|
|
|
let window_rect = match window_rect {
|
|
|
|
|
Some(rect) => rect,
|
2023-08-29 14:01:25 -07:00
|
|
|
None => return Ok(default.to_owned()),
|
2018-06-14 19:42:18 -04:00
|
|
|
};
|
2018-05-14 08:14:57 -04:00
|
|
|
|
2018-06-14 19:42:18 -04:00
|
|
|
let mut largest_overlap = 0;
|
|
|
|
|
let mut matched_monitor = default;
|
|
|
|
|
for monitor in &monitors {
|
|
|
|
|
let overlapping_area = window_rect.get_overlapping_area(&monitor.rect);
|
|
|
|
|
if overlapping_area > largest_overlap {
|
|
|
|
|
largest_overlap = overlapping_area;
|
2022-01-01 13:00:11 +11:00
|
|
|
matched_monitor = monitor;
|
2018-06-14 19:42:18 -04:00
|
|
|
}
|
2018-05-14 08:14:57 -04:00
|
|
|
}
|
|
|
|
|
|
2023-08-29 14:01:25 -07:00
|
|
|
Ok(matched_monitor.to_owned())
|
2018-06-14 19:42:18 -04:00
|
|
|
}
|
2018-05-14 08:14:57 -04:00
|
|
|
|
2023-08-29 14:01:25 -07:00
|
|
|
fn query_monitor_list(&self) -> Result<Vec<MonitorHandle>, X11Error> {
|
|
|
|
|
let root = self.default_root();
|
2023-12-29 22:04:27 -08:00
|
|
|
let resources =
|
|
|
|
|
ScreenResources::from_connection(self.xcb_connection(), root, self.randr_version())?;
|
2023-08-29 14:01:25 -07:00
|
|
|
|
|
|
|
|
// Pipeline all of the get-crtc requests.
|
|
|
|
|
let mut crtc_cookies = Vec::with_capacity(resources.crtcs().len());
|
|
|
|
|
for &crtc in resources.crtcs() {
|
|
|
|
|
crtc_cookies
|
|
|
|
|
.push(self.xcb_connection().randr_get_crtc_info(crtc, x11rb::CURRENT_TIME)?);
|
|
|
|
|
}
|
2018-05-14 08:14:57 -04:00
|
|
|
|
2023-08-29 14:01:25 -07:00
|
|
|
// Do this here so we do all of our requests in one shot.
|
|
|
|
|
let primary = self.xcb_connection().randr_get_output_primary(root.root)?.reply()?.output;
|
|
|
|
|
|
|
|
|
|
let mut crtc_infos = Vec::with_capacity(crtc_cookies.len());
|
|
|
|
|
for cookie in crtc_cookies {
|
|
|
|
|
let reply = cookie.reply()?;
|
|
|
|
|
crtc_infos.push(reply);
|
|
|
|
|
}
|
2018-05-14 08:14:57 -04:00
|
|
|
|
2023-08-29 14:01:25 -07:00
|
|
|
let mut has_primary = false;
|
|
|
|
|
let mut available_monitors = Vec::with_capacity(resources.crtcs().len());
|
|
|
|
|
for (crtc_id, crtc) in resources.crtcs().iter().zip(crtc_infos.iter()) {
|
|
|
|
|
if crtc.width == 0 || crtc.height == 0 || crtc.outputs.is_empty() {
|
|
|
|
|
continue;
|
2018-05-22 09:07:46 -04:00
|
|
|
}
|
|
|
|
|
|
2023-08-29 14:01:25 -07:00
|
|
|
let is_primary = crtc.outputs[0] == primary;
|
|
|
|
|
has_primary |= is_primary;
|
|
|
|
|
let monitor = MonitorHandle::new(self, &resources, *crtc_id, crtc, is_primary);
|
|
|
|
|
available_monitors.extend(monitor);
|
2018-06-14 19:42:18 -04:00
|
|
|
}
|
2023-08-29 14:01:25 -07:00
|
|
|
|
|
|
|
|
// If we don't have a primary monitor, just pick one ourselves!
|
|
|
|
|
if !has_primary {
|
|
|
|
|
if let Some(ref mut fallback) = available_monitors.first_mut() {
|
|
|
|
|
// Setting this here will come in handy if we ever add an `is_primary` method.
|
|
|
|
|
fallback.primary = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(available_monitors)
|
2018-05-14 08:14:57 -04:00
|
|
|
}
|
|
|
|
|
|
2023-08-29 14:01:25 -07:00
|
|
|
pub fn available_monitors(&self) -> Result<Vec<MonitorHandle>, X11Error> {
|
|
|
|
|
let mut monitors_lock = self.monitor_handles.lock().unwrap();
|
2023-12-24 19:27:02 +01:00
|
|
|
match *monitors_lock {
|
|
|
|
|
Some(ref monitors) => Ok(monitors.clone()),
|
|
|
|
|
None => {
|
|
|
|
|
let monitors = self.query_monitor_list()?;
|
|
|
|
|
if !DISABLE_MONITOR_LIST_CACHING {
|
|
|
|
|
*monitors_lock = Some(monitors.clone());
|
|
|
|
|
}
|
|
|
|
|
Ok(monitors)
|
|
|
|
|
},
|
|
|
|
|
}
|
2018-06-14 19:42:18 -04:00
|
|
|
}
|
2018-05-14 08:14:57 -04:00
|
|
|
|
2018-06-14 19:42:18 -04:00
|
|
|
#[inline]
|
2023-08-29 14:01:25 -07:00
|
|
|
pub fn primary_monitor(&self) -> Result<MonitorHandle, X11Error> {
|
|
|
|
|
Ok(self
|
|
|
|
|
.available_monitors()?
|
2018-06-14 19:42:18 -04:00
|
|
|
.into_iter()
|
|
|
|
|
.find(|monitor| monitor.primary)
|
2023-08-29 14:01:25 -07:00
|
|
|
.unwrap_or_else(MonitorHandle::dummy))
|
2018-06-14 19:42:18 -04:00
|
|
|
}
|
2018-05-14 08:14:57 -04:00
|
|
|
|
2023-08-29 14:01:25 -07:00
|
|
|
pub fn select_xrandr_input(&self, root: xproto::Window) -> Result<u8, X11Error> {
|
|
|
|
|
use randr::NotifyMask;
|
|
|
|
|
|
|
|
|
|
// Get extension info.
|
|
|
|
|
let info = self
|
|
|
|
|
.xcb_connection()
|
|
|
|
|
.extension_information(randr::X11_EXTENSION_NAME)?
|
2024-12-02 12:51:26 +01:00
|
|
|
.ok_or(X11Error::MissingExtension(randr::X11_EXTENSION_NAME))?;
|
2023-08-29 14:01:25 -07:00
|
|
|
|
|
|
|
|
// Select input data.
|
|
|
|
|
let event_mask =
|
|
|
|
|
NotifyMask::CRTC_CHANGE | NotifyMask::OUTPUT_PROPERTY | NotifyMask::SCREEN_CHANGE;
|
|
|
|
|
self.xcb_connection().randr_select_input(root, event_mask)?;
|
|
|
|
|
|
|
|
|
|
Ok(info.first_event)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-09 06:32:03 +04:00
|
|
|
pub struct ScreenResources {
|
2023-08-29 14:01:25 -07:00
|
|
|
/// List of attached modes.
|
|
|
|
|
modes: Vec<randr::ModeInfo>,
|
|
|
|
|
|
|
|
|
|
/// List of attached CRTCs.
|
|
|
|
|
crtcs: Vec<randr::Crtc>,
|
|
|
|
|
}
|
2018-05-14 08:14:57 -04:00
|
|
|
|
2023-08-29 14:01:25 -07:00
|
|
|
impl ScreenResources {
|
|
|
|
|
pub(crate) fn modes(&self) -> &[randr::ModeInfo] {
|
|
|
|
|
&self.modes
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) fn crtcs(&self) -> &[randr::Crtc] {
|
|
|
|
|
&self.crtcs
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) fn from_connection(
|
|
|
|
|
conn: &impl x11rb::connection::Connection,
|
|
|
|
|
root: &x11rb::protocol::xproto::Screen,
|
2023-12-29 22:04:27 -08:00
|
|
|
(major_version, minor_version): (u32, u32),
|
2023-08-29 14:01:25 -07:00
|
|
|
) -> Result<Self, X11Error> {
|
2023-12-29 22:04:27 -08:00
|
|
|
if (major_version == 1 && minor_version >= 3) || major_version > 1 {
|
2023-08-29 14:01:25 -07:00
|
|
|
let reply = conn.randr_get_screen_resources_current(root.root)?.reply()?;
|
|
|
|
|
Ok(Self::from_get_screen_resources_current_reply(reply))
|
|
|
|
|
} else {
|
|
|
|
|
let reply = conn.randr_get_screen_resources(root.root)?.reply()?;
|
|
|
|
|
Ok(Self::from_get_screen_resources_reply(reply))
|
2018-06-14 19:42:18 -04:00
|
|
|
}
|
2023-08-29 14:01:25 -07:00
|
|
|
}
|
2018-05-14 08:14:57 -04:00
|
|
|
|
2023-08-29 14:01:25 -07:00
|
|
|
pub(crate) fn from_get_screen_resources_reply(reply: randr::GetScreenResourcesReply) -> Self {
|
|
|
|
|
Self { modes: reply.modes, crtcs: reply.crtcs }
|
|
|
|
|
}
|
2018-05-14 08:14:57 -04:00
|
|
|
|
2023-08-29 14:01:25 -07:00
|
|
|
pub(crate) fn from_get_screen_resources_current_reply(
|
|
|
|
|
reply: randr::GetScreenResourcesCurrentReply,
|
|
|
|
|
) -> Self {
|
|
|
|
|
Self { modes: reply.modes, crtcs: reply.crtcs }
|
2018-06-14 19:42:18 -04:00
|
|
|
}
|
2018-05-14 08:14:57 -04:00
|
|
|
}
|