Integrate native Rime and stabilize input, output, and rendering
- Add optional modifier-only Super binding whose tap is cancelled by other input, focus changes, grabs, locks, and session teardown. - Ship the native ferese-rime user service, Rime defaults, installer inventory, package dependencies, session environment, and docs. - Vendor Smithay 8a27023 with the SHM texture-cache format fix and add GPU regressions for format changes and resize snapshots. - Release reused CRTC owners before staging output modes, then restore changed outputs before former owners when a transaction fails. - Wait for resize-capture GPU completion, expand filtered surface damage, and preserve IME popup origin under fractional scaling. - Mark weekends like holidays in both calendar layouts.
This commit is contained in:
1 parent
a2be421974
commit
993bc0f88f
391 files changed
+130908
-76
No files matched your search
+17
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "smithay-drm-extras"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
authors = ["Bartłomiej Maryńczak <[email protected]>"]
|
||||
|
||||
[dependencies]
|
||||
libdisplay-info = { version = "0.3.0", optional = true }
|
||||
drm = { version = "0.14.0" }
|
||||
|
||||
[features]
|
||||
default = ["display-info"]
|
||||
display-info = ["libdisplay-info"]
|
||||
|
||||
[dev-dependencies.smithay]
|
||||
path = "../"
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Bartłomiej Maryńczak
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
# Smithay DRM Extras
|
||||
|
||||
This crate contains some extra abstractions and helpers over DRM
|
||||
|
||||
- `display_info` module is responsible for extraction of information from DRM connectors (`model` and `manufacturer`)
|
||||
- `drm_scanner` module contains helpers for detecting connector connected and disconnected events as well as mapping crtc to them.
|
||||
- `ConnectorScanner` is responsible for tracking connected/disconnected events.
|
||||
- `CrtcMapper` trait and `SimpleCrtcMapper` are meant for mapping crtc to connector.
|
||||
- `DrmScanner<CrtcMapper>` combines two above into single abstraction. If it does not fit your needs you can always drop down to using `ConnectScanner` alone.
|
||||
@@ -0,0 +1,220 @@
|
||||
use std::{collections::HashMap, path::PathBuf, time::Duration};
|
||||
|
||||
use smithay::reexports::drm::control::{connector, crtc};
|
||||
use smithay_drm_extras::{
|
||||
display_info,
|
||||
drm_scanner::{self, DrmScanEvent},
|
||||
};
|
||||
|
||||
use smithay::{
|
||||
backend::{
|
||||
drm::{self, DrmDeviceFd, DrmNode},
|
||||
session::{Session, libseat::LibSeatSession},
|
||||
udev::{UdevBackend, UdevEvent},
|
||||
},
|
||||
reexports::{
|
||||
calloop::{EventLoop, LoopHandle, timer::Timer},
|
||||
rustix::fs::OFlags,
|
||||
},
|
||||
utils::DeviceFd,
|
||||
};
|
||||
|
||||
struct State {
|
||||
handle: LoopHandle<'static, Self>,
|
||||
session: LibSeatSession,
|
||||
devices: HashMap<DrmNode, Device>,
|
||||
}
|
||||
|
||||
struct Device {
|
||||
drm: drm::DrmDevice,
|
||||
drm_scanner: drm_scanner::DrmScanner,
|
||||
surfaces: HashMap<crtc::Handle, Surface>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Surface {
|
||||
// Your gbm surface stuff goes here
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut event_loop = EventLoop::<State>::try_new()?;
|
||||
|
||||
let (session, notify) = LibSeatSession::new().unwrap();
|
||||
|
||||
event_loop.handle().insert_source(notify, |_, _, _| {}).unwrap();
|
||||
|
||||
let mut state = State {
|
||||
handle: event_loop.handle(),
|
||||
session,
|
||||
devices: Default::default(),
|
||||
};
|
||||
|
||||
init_udev(&mut state);
|
||||
|
||||
event_loop
|
||||
.handle()
|
||||
.insert_source(Timer::from_duration(Duration::from_secs(5)), |_, _, _| {
|
||||
panic!("Aborted");
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
event_loop.run(None, &mut state, |_data| {})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init_udev(state: &mut State) {
|
||||
let backend = UdevBackend::new(state.session.seat()).unwrap();
|
||||
for (device_id, path) in backend.device_list() {
|
||||
state.on_udev_event(UdevEvent::Added {
|
||||
device_id,
|
||||
path: path.to_owned(),
|
||||
});
|
||||
}
|
||||
|
||||
state
|
||||
.handle
|
||||
.insert_source(backend, |event, _, state| state.on_udev_event(event))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn on_udev_event(&mut self, event: UdevEvent) {
|
||||
match event {
|
||||
UdevEvent::Added { device_id, path } => {
|
||||
if let Ok(node) = DrmNode::from_dev_id(device_id) {
|
||||
self.device_added(node, path);
|
||||
}
|
||||
}
|
||||
UdevEvent::Changed { device_id } => {
|
||||
if let Ok(node) = DrmNode::from_dev_id(device_id) {
|
||||
self.device_changed(node);
|
||||
}
|
||||
}
|
||||
UdevEvent::Removed { device_id } => {
|
||||
if let Ok(node) = DrmNode::from_dev_id(device_id) {
|
||||
self.device_removed(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn device_added(&mut self, node: DrmNode, path: PathBuf) {
|
||||
let fd = self
|
||||
.session
|
||||
.open(
|
||||
&path,
|
||||
OFlags::RDWR | OFlags::CLOEXEC | OFlags::NOCTTY | OFlags::NONBLOCK,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let fd = DrmDeviceFd::new(DeviceFd::from(fd));
|
||||
|
||||
let (drm, drm_notifier) = drm::DrmDevice::new(fd, false).unwrap();
|
||||
|
||||
self.handle
|
||||
.insert_source(drm_notifier, move |event, _, _| match event {
|
||||
drm::DrmEvent::VBlank(_) => {}
|
||||
drm::DrmEvent::Error(_) => {}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
self.devices.insert(
|
||||
node,
|
||||
Device {
|
||||
drm,
|
||||
drm_scanner: Default::default(),
|
||||
surfaces: Default::default(),
|
||||
},
|
||||
);
|
||||
|
||||
self.device_changed(node);
|
||||
}
|
||||
|
||||
fn connector_connected(&mut self, node: DrmNode, connector: connector::Info, crtc: crtc::Handle) {
|
||||
if let Some(device) = self.devices.get_mut(&node) {
|
||||
let name = format!("{}-{}", connector.interface().as_str(), connector.interface_id());
|
||||
|
||||
let display_info = display_info::for_connector(&device.drm, connector.handle());
|
||||
|
||||
let manufacturer = display_info
|
||||
.as_ref()
|
||||
.and_then(|info| info.make())
|
||||
.unwrap_or_else(|| "Unknown".into());
|
||||
|
||||
let model = display_info
|
||||
.as_ref()
|
||||
.and_then(|info| info.model())
|
||||
.unwrap_or_else(|| "Unknown".into());
|
||||
|
||||
println!("Connected:");
|
||||
dbg!(name);
|
||||
dbg!(manufacturer);
|
||||
dbg!(model);
|
||||
|
||||
device.surfaces.insert(crtc, Surface {});
|
||||
}
|
||||
}
|
||||
|
||||
fn connector_disconnected(&mut self, node: DrmNode, connector: connector::Info, crtc: crtc::Handle) {
|
||||
let name = format!("{}-{}", connector.interface().as_str(), connector.interface_id());
|
||||
|
||||
println!("Disconnected:");
|
||||
dbg!(name);
|
||||
|
||||
if let Some(device) = self.devices.get_mut(&node) {
|
||||
device.surfaces.remove(&crtc);
|
||||
}
|
||||
}
|
||||
|
||||
fn device_changed(&mut self, node: DrmNode) {
|
||||
let device = if let Some(device) = self.devices.get_mut(&node) {
|
||||
device
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
for event in device
|
||||
.drm_scanner
|
||||
.scan_connectors(&device.drm)
|
||||
.expect("failed to scan connectors")
|
||||
{
|
||||
match event {
|
||||
DrmScanEvent::Connected {
|
||||
connector,
|
||||
crtc: Some(crtc),
|
||||
} => {
|
||||
self.connector_connected(node, connector, crtc);
|
||||
}
|
||||
DrmScanEvent::Disconnected {
|
||||
connector,
|
||||
crtc: Some(crtc),
|
||||
} => {
|
||||
self.connector_disconnected(node, connector, crtc);
|
||||
}
|
||||
DrmScanEvent::Changed { .. } => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn device_removed(&mut self, node: DrmNode) {
|
||||
let device = if let Some(device) = self.devices.get_mut(&node) {
|
||||
device
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
let crtcs: Vec<_> = device
|
||||
.drm_scanner
|
||||
.crtcs()
|
||||
.map(|(info, crtc)| (info.clone(), crtc))
|
||||
.collect();
|
||||
|
||||
for (connector, crtc) in crtcs {
|
||||
self.connector_disconnected(node, connector, crtc);
|
||||
}
|
||||
|
||||
self.devices.remove(&node);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
//! # EDID - Extended Display Identification Data
|
||||
//!
|
||||
//! This module is meant to help with extraction of EDID data from connectors
|
||||
//!
|
||||
//! ```no_run
|
||||
//! # mod helpers { include!("./docs/doctest_helpers.rs"); };
|
||||
//! # let drm_device: helpers::FakeDevice = todo!();
|
||||
//! # let connector = todo!();
|
||||
//! use smithay_drm_extras::display_info;
|
||||
//!
|
||||
//! let info = display_info::for_connector(&drm_device, connector).unwrap();
|
||||
//!
|
||||
//! println!("Monitor name: {:?}", info.model());
|
||||
//! println!("Manufacturer name: {:?}", info.make());
|
||||
//! ```
|
||||
|
||||
use drm::control::{Device as ControlDevice, connector};
|
||||
use libdisplay_info::info::Info;
|
||||
|
||||
/// Try to read the [`Info`] from the connector EDID property
|
||||
pub fn for_connector(device: &impl ControlDevice, connector: connector::Handle) -> Option<Info> {
|
||||
let props = device.get_properties(connector).ok()?;
|
||||
|
||||
let (info, value) = props
|
||||
.into_iter()
|
||||
.filter_map(|(handle, value)| {
|
||||
let info = device.get_property(handle).ok()?;
|
||||
|
||||
Some((info, value))
|
||||
})
|
||||
.find(|(info, _)| info.name().to_str() == Ok("EDID"))?;
|
||||
|
||||
let blob = info.value_type().convert_value(value).as_blob()?;
|
||||
let data = device.get_property_blob(blob).ok()?;
|
||||
|
||||
Info::parse_edid(&data).ok()
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
pub struct FakeDevice;
|
||||
impl std::os::unix::prelude::AsFd for FakeDevice {
|
||||
fn as_fd(&self) -> std::os::unix::prelude::BorrowedFd<'_> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
impl drm::Device for FakeDevice {}
|
||||
impl drm::control::Device for FakeDevice {}
|
||||
@@ -0,0 +1,244 @@
|
||||
//! # Drm Scanner
|
||||
//!
|
||||
//! - [`ConnectorScanner`] is responsible for tracking connected/disconnected events.
|
||||
//! - [`CrtcMapper`] trait and [`SimpleCrtcMapper`] are meant for mapping crtc to connector.
|
||||
//! - [`DrmScanner`] combines two above into single abstraction.
|
||||
//! If it does not fit your needs you can always drop down to using [`ConnectScanner`] alone.
|
||||
//!
|
||||
//! ### Example
|
||||
//! ```no_run
|
||||
//! # mod helpers { include!("./docs/doctest_helpers.rs"); };
|
||||
//! # let drm_device: helpers::FakeDevice = todo!();
|
||||
//! use smithay_drm_extras::drm_scanner::{DrmScanner, DrmScanEvent};
|
||||
//!
|
||||
//! let mut scanner: DrmScanner = DrmScanner::new();
|
||||
//!
|
||||
//! for event in scanner.scan_connectors(&drm_device).expect("failed to scan connectors") {
|
||||
//! match event {
|
||||
//! DrmScanEvent::Connected { .. } => {},
|
||||
//! DrmScanEvent::Disconnected { .. } => {},
|
||||
//! DrmScanEvent::Changed { .. } => {},
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
use std::collections::HashMap;
|
||||
|
||||
use drm::control::{Device as ControlDevice, connector, crtc};
|
||||
|
||||
mod connector_scanner;
|
||||
pub use connector_scanner::{ConnectorScanEvent, ConnectorScanResult, ConnectorScanner};
|
||||
|
||||
mod crtc_mapper;
|
||||
pub use crtc_mapper::{CrtcMapper, SimpleCrtcMapper};
|
||||
|
||||
/// Drm Scanner
|
||||
///
|
||||
/// Wrapper over [`ConnectorScanner`] and [`CrtcMapper`]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct DrmScanner<Mapper = SimpleCrtcMapper>
|
||||
where
|
||||
Mapper: CrtcMapper,
|
||||
{
|
||||
connectors: ConnectorScanner,
|
||||
crtc_mapper: Mapper,
|
||||
}
|
||||
|
||||
impl<M> DrmScanner<M>
|
||||
where
|
||||
M: CrtcMapper + Default,
|
||||
{
|
||||
/// Create new DrmScanner with default CRTC mapper.
|
||||
pub fn new() -> Self {
|
||||
Self::new_with_mapper(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<M> DrmScanner<M>
|
||||
where
|
||||
M: CrtcMapper,
|
||||
{
|
||||
/// Create new DrmScanner with custom CRTC mapper
|
||||
pub fn new_with_mapper(mapper: M) -> Self {
|
||||
Self {
|
||||
connectors: Default::default(),
|
||||
crtc_mapper: mapper,
|
||||
}
|
||||
}
|
||||
|
||||
/// [`CrtcMapper`] getter
|
||||
pub fn crtc_mapper(&self) -> &M {
|
||||
&self.crtc_mapper
|
||||
}
|
||||
|
||||
/// Muttable [`CrtcMapper`] getter
|
||||
pub fn crtc_mapper_mut(&mut self) -> &mut M {
|
||||
&mut self.crtc_mapper
|
||||
}
|
||||
|
||||
/// Scan connectors to find out what has changed since last call to this method.
|
||||
///
|
||||
/// Returns [`DrmScanResult`] that contains added and removed connectors,
|
||||
/// and CRTCs that got assigned to them.
|
||||
///
|
||||
/// Should be called on every device changed event
|
||||
///
|
||||
/// ```no_run
|
||||
/// # mod helpers { include!("./docs/doctest_helpers.rs"); };
|
||||
/// # let drm_device: helpers::FakeDevice = todo!();
|
||||
/// use smithay_drm_extras::drm_scanner::{DrmScanner, DrmScanEvent};
|
||||
///
|
||||
/// let mut scanner: DrmScanner = DrmScanner::new();
|
||||
/// let res = scanner.scan_connectors(&drm_device).expect("failed to scan connectors");
|
||||
///
|
||||
/// // You can extract scan info manually
|
||||
/// println!("Plugged {} connectors", res.connected.len());
|
||||
/// println!("Unplugged {} connectors", res.disconnected.len());
|
||||
///
|
||||
/// // Or simply iterate over it
|
||||
/// for event in res {
|
||||
/// match event {
|
||||
/// DrmScanEvent::Connected { .. } => {},
|
||||
/// DrmScanEvent::Disconnected { .. } => {},
|
||||
/// DrmScanEvent::Changed { .. } => {},
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn scan_connectors(&mut self, drm: &impl ControlDevice) -> std::io::Result<DrmScanResult> {
|
||||
let scan = self.connectors.scan(drm)?;
|
||||
|
||||
let removed = scan
|
||||
.disconnected
|
||||
.into_iter()
|
||||
.map(|info| {
|
||||
let crtc = self.crtc_mapper.crtc_for_connector(&info.handle());
|
||||
(info, crtc)
|
||||
})
|
||||
.collect();
|
||||
|
||||
self.crtc_mapper.map(drm, self.connectors.connectors().values());
|
||||
|
||||
let added = scan
|
||||
.connected
|
||||
.into_iter()
|
||||
.map(|info| {
|
||||
let crtc = self.crtc_mapper.crtc_for_connector(&info.handle());
|
||||
(info, crtc)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let changed = scan
|
||||
.changed
|
||||
.into_iter()
|
||||
.map(|info| {
|
||||
let crtc = self.crtc_mapper.crtc_for_connector(&info.handle());
|
||||
(info, crtc)
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(DrmScanResult {
|
||||
disconnected: removed,
|
||||
connected: added,
|
||||
changed,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get map of all connectors, connected and disconnected ones.
|
||||
pub fn connectors(&self) -> &HashMap<connector::Handle, connector::Info> {
|
||||
self.connectors.connectors()
|
||||
}
|
||||
|
||||
/// Get CRTC that is mapped to supplied connector
|
||||
///
|
||||
/// This will query underlying [`CrtcMapper`]
|
||||
pub fn crtc_for_connector(&self, connector: &connector::Handle) -> Option<crtc::Handle> {
|
||||
self.crtc_mapper.crtc_for_connector(connector)
|
||||
}
|
||||
|
||||
/// Get iterator over all `connector -> CRTC` mappings
|
||||
pub fn crtcs(&self) -> impl Iterator<Item = (&connector::Info, crtc::Handle)> {
|
||||
self.connectors()
|
||||
.iter()
|
||||
.filter_map(|(handle, info)| Some((info, self.crtc_for_connector(handle)?)))
|
||||
}
|
||||
}
|
||||
|
||||
type DrmScanItem = (connector::Info, Option<crtc::Handle>);
|
||||
|
||||
/// Result of [`DrmScanner::scan_connectors`]
|
||||
///
|
||||
/// You can use `added` and `removed` fields of this result manually,
|
||||
/// or you can just iterate (using [`IntoIterator`] or [`DrmScanResult::iter`])
|
||||
/// over this result to get [`DrmScanEvent`].
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct DrmScanResult {
|
||||
/// Connectors that got plugged in since last scan
|
||||
pub connected: Vec<DrmScanItem>,
|
||||
/// Connectors that got unplugged since last scan
|
||||
pub disconnected: Vec<DrmScanItem>,
|
||||
/// Connectors whose mode list changed while staying connected
|
||||
pub changed: Vec<DrmScanItem>,
|
||||
}
|
||||
|
||||
impl DrmScanResult {
|
||||
/// Creates event iterator for this result
|
||||
///
|
||||
/// Internally this clones the data so it is equivalent to [`IntoIterator`]
|
||||
pub fn iter(&self) -> impl Iterator<Item = DrmScanEvent> {
|
||||
self.clone().into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
/// Created from [`DrmScanResult`], informs about connector events.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DrmScanEvent {
|
||||
/// A new connector got plugged in since last scan
|
||||
Connected {
|
||||
/// Info about connected connector
|
||||
connector: connector::Info,
|
||||
/// Crtc that got mapped to this connector
|
||||
crtc: Option<crtc::Handle>,
|
||||
},
|
||||
/// A connector got unplugged in since last scan
|
||||
Disconnected {
|
||||
/// Info about disconnected connector
|
||||
connector: connector::Info,
|
||||
/// Crtc that is no longer mapped to this connector
|
||||
crtc: Option<crtc::Handle>,
|
||||
},
|
||||
/// The connector's mode list changed while staying connected
|
||||
Changed {
|
||||
/// Info about the connector whose modes changed
|
||||
connector: connector::Info,
|
||||
/// Crtc that is mapped to this connector
|
||||
crtc: Option<crtc::Handle>,
|
||||
},
|
||||
}
|
||||
|
||||
impl DrmScanEvent {
|
||||
fn connected((connector, crtc): (connector::Info, Option<crtc::Handle>)) -> Self {
|
||||
DrmScanEvent::Connected { connector, crtc }
|
||||
}
|
||||
|
||||
fn disconnected((connector, crtc): (connector::Info, Option<crtc::Handle>)) -> Self {
|
||||
DrmScanEvent::Disconnected { connector, crtc }
|
||||
}
|
||||
|
||||
fn changed((connector, crtc): (connector::Info, Option<crtc::Handle>)) -> Self {
|
||||
DrmScanEvent::Changed { connector, crtc }
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for DrmScanResult {
|
||||
type Item = DrmScanEvent;
|
||||
type IntoIter = std::vec::IntoIter<DrmScanEvent>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.disconnected
|
||||
.into_iter()
|
||||
.map(DrmScanEvent::disconnected)
|
||||
.chain(self.connected.into_iter().map(DrmScanEvent::connected))
|
||||
.chain(self.changed.into_iter().map(DrmScanEvent::changed))
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use drm::control::{Device as ControlDevice, connector};
|
||||
|
||||
/// Responsible for tracking connected/disconnected events.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// # mod helpers { include!("../docs/doctest_helpers.rs"); };
|
||||
/// # let drm_device: helpers::FakeDevice = todo!();
|
||||
/// use smithay_drm_extras::drm_scanner::{ConnectorScanner, ConnectorScanEvent};
|
||||
///
|
||||
/// let mut scanner = ConnectorScanner::new();
|
||||
///
|
||||
/// for event in scanner.scan(&drm_device).expect("failed to scan connectors") {
|
||||
/// match event {
|
||||
/// ConnectorScanEvent::Connected(conn) => {},
|
||||
/// ConnectorScanEvent::Disconnected(conn) => {},
|
||||
/// ConnectorScanEvent::Changed(conn) => {},
|
||||
/// }
|
||||
/// }
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ConnectorScanner {
|
||||
connectors: HashMap<connector::Handle, connector::Info>,
|
||||
}
|
||||
|
||||
impl ConnectorScanner {
|
||||
/// Create new [`ConnectorScanner`]
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Should be called on every device changed event
|
||||
pub fn scan(&mut self, drm: &impl ControlDevice) -> std::io::Result<ConnectorScanResult> {
|
||||
let res_handles = drm.resource_handles()?;
|
||||
let connector_handles = res_handles.connectors();
|
||||
|
||||
let mut added = Vec::new();
|
||||
let mut removed = Vec::new();
|
||||
let mut changed = Vec::new();
|
||||
|
||||
for conn in connector_handles
|
||||
.iter()
|
||||
.filter_map(|conn| drm.get_connector(*conn, true).ok())
|
||||
{
|
||||
let curr_state = conn.state();
|
||||
|
||||
use connector::State;
|
||||
if let Some(old) = self.connectors.insert(conn.handle(), conn.clone()) {
|
||||
match (old.state(), curr_state) {
|
||||
(State::Connected, State::Disconnected) => removed.push(conn),
|
||||
(State::Disconnected | State::Unknown, State::Connected) => added.push(conn),
|
||||
//
|
||||
// Emit Changed when the mode list changes while staying
|
||||
// connected. This covers the EDID race where the kernel
|
||||
// initially reports a connector as Connected with an
|
||||
// empty or fallback mode list (EDID not yet read) and a
|
||||
// later rescan finds the real modes populated.
|
||||
(State::Connected, State::Connected) => {
|
||||
if old.modes() != conn.modes() {
|
||||
changed.push(conn);
|
||||
}
|
||||
}
|
||||
(State::Disconnected, State::Disconnected) => {}
|
||||
//
|
||||
(State::Unknown, _) => {}
|
||||
(_, State::Unknown) => {}
|
||||
}
|
||||
} else if curr_state == State::Connected {
|
||||
added.push(conn)
|
||||
}
|
||||
}
|
||||
|
||||
// Check for connectors that disappeared entirely (e.g., USB-C dock disconnected while
|
||||
// suspended). These won't be in resource_handles() anymore, so handle them specially.
|
||||
let connector_handles_set: HashSet<_> = connector_handles.iter().copied().collect();
|
||||
let to_remove: Vec<_> = self
|
||||
.connectors
|
||||
.keys()
|
||||
.filter(|handle| !connector_handles_set.contains(handle))
|
||||
.copied()
|
||||
.collect();
|
||||
|
||||
for handle in to_remove {
|
||||
if let Some(info) = self.connectors.remove(&handle) {
|
||||
if info.state() == connector::State::Connected {
|
||||
removed.push(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ConnectorScanResult {
|
||||
connected: added,
|
||||
disconnected: removed,
|
||||
changed,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get map of all connectors, connected and disconnected ones.
|
||||
pub fn connectors(&self) -> &HashMap<connector::Handle, connector::Info> {
|
||||
&self.connectors
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of [`ConnectorScanner::scan`]
|
||||
///
|
||||
/// You can use `added` and `removed` fields of this result manually,
|
||||
/// or you can just iterate (using [`IntoIterator`] or [`ConnectorScanResult::iter`])
|
||||
/// over this result to get [`ConnectorScanEvent`].
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct ConnectorScanResult {
|
||||
/// Connectors that got plugged in since last scan
|
||||
pub connected: Vec<connector::Info>,
|
||||
/// Connectors that got unplugged since last scan
|
||||
pub disconnected: Vec<connector::Info>,
|
||||
/// Connectors whose mode list changed while staying connected
|
||||
pub changed: Vec<connector::Info>,
|
||||
}
|
||||
|
||||
/// Created from [`ConnectorScanResult`], informs about connector events.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ConnectorScanEvent {
|
||||
/// A new connector got plugged in since last scan
|
||||
Connected(connector::Info),
|
||||
/// A connector got unplugged in since last scan
|
||||
Disconnected(connector::Info),
|
||||
/// The connector's mode list changed while staying connected
|
||||
Changed(connector::Info),
|
||||
}
|
||||
|
||||
impl ConnectorScanResult {
|
||||
/// Creates event iterator for this result
|
||||
///
|
||||
/// Internally this clones the data so it is equivalent to [`IntoIterator`]
|
||||
pub fn iter(&self) -> impl Iterator<Item = ConnectorScanEvent> {
|
||||
self.clone().into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for ConnectorScanResult {
|
||||
type Item = ConnectorScanEvent;
|
||||
type IntoIter = std::vec::IntoIter<ConnectorScanEvent>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.disconnected
|
||||
.into_iter()
|
||||
.map(ConnectorScanEvent::Disconnected)
|
||||
.chain(self.connected.into_iter().map(ConnectorScanEvent::Connected))
|
||||
.chain(self.changed.into_iter().map(ConnectorScanEvent::Changed))
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use drm::control::{Device as ControlDevice, connector, crtc};
|
||||
|
||||
/// CRTC Mapper trait
|
||||
///
|
||||
/// It exists to allow custom mappers in [`super::DrmScanner`].
|
||||
///
|
||||
/// It is responsible for mapping CRTCs to connectors.
|
||||
/// For each connector it has to pick suitable CRTC.
|
||||
pub trait CrtcMapper {
|
||||
/// Request mapping of CRTCs to supplied connectors
|
||||
///
|
||||
/// Usually called in response to udev device changed event,
|
||||
/// or on device init.
|
||||
fn map<'a>(
|
||||
&mut self,
|
||||
drm: &impl ControlDevice,
|
||||
connectors: impl Iterator<Item = &'a connector::Info> + Clone,
|
||||
);
|
||||
|
||||
/// Query CRTC mapped to supplied connector
|
||||
fn crtc_for_connector(&self, connector: &connector::Handle) -> Option<crtc::Handle>;
|
||||
}
|
||||
|
||||
/// Simple CRTC Mapper
|
||||
///
|
||||
/// This is basic mapper that simply chooses one CRTC for every connector.
|
||||
///
|
||||
/// It is also capable of recovering mappings that were used by display manger or tty
|
||||
/// before the compositor was started up.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct SimpleCrtcMapper {
|
||||
crtcs: HashMap<connector::Handle, crtc::Handle>,
|
||||
}
|
||||
|
||||
impl SimpleCrtcMapper {
|
||||
/// Create new [`SimpleCrtcMapper`]
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
fn is_taken(&self, crtc: &crtc::Handle) -> bool {
|
||||
self.crtcs.values().any(|v| v == crtc)
|
||||
}
|
||||
|
||||
fn is_available(&self, crtc: &crtc::Handle) -> bool {
|
||||
!self.is_taken(crtc)
|
||||
}
|
||||
|
||||
fn restored_for_connector(
|
||||
&self,
|
||||
drm: &impl ControlDevice,
|
||||
connector: &connector::Info,
|
||||
) -> Option<crtc::Handle> {
|
||||
let encoder = connector.current_encoder()?;
|
||||
let encoder = drm.get_encoder(encoder).ok()?;
|
||||
let crtc = encoder.crtc()?;
|
||||
|
||||
self.is_available(&crtc).then_some(crtc)
|
||||
}
|
||||
|
||||
fn pick_next_available_for_connector(
|
||||
&self,
|
||||
drm: &impl ControlDevice,
|
||||
connector: &connector::Info,
|
||||
) -> Option<crtc::Handle> {
|
||||
let res_handles = drm.resource_handles().ok()?;
|
||||
|
||||
connector
|
||||
.encoders()
|
||||
.iter()
|
||||
.flat_map(|encoder_handle| drm.get_encoder(*encoder_handle))
|
||||
.find_map(|encoder_info| {
|
||||
res_handles
|
||||
.filter_crtcs(encoder_info.possible_crtcs())
|
||||
.into_iter()
|
||||
.find(|crtc| self.is_available(crtc))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl super::CrtcMapper for SimpleCrtcMapper {
|
||||
fn map<'a>(
|
||||
&mut self,
|
||||
drm: &impl ControlDevice,
|
||||
connectors: impl Iterator<Item = &'a connector::Info> + Clone,
|
||||
) {
|
||||
// Release CRTC reservations for every connector that is not currently connected. This
|
||||
// covers connectors that are present but disconnected, as well as connectors that have
|
||||
// vanished from the resource list entirely (e.g. DP-MST sink connectors when a dock is
|
||||
// unplugged or the system is suspended).
|
||||
let connected: HashSet<connector::Handle> = connectors
|
||||
.clone()
|
||||
.filter(|conn| conn.state() == connector::State::Connected)
|
||||
.map(|conn| conn.handle())
|
||||
.collect();
|
||||
self.crtcs.retain(|handle, _| connected.contains(handle));
|
||||
|
||||
let mut needs_crtc: Vec<&connector::Info> = connectors
|
||||
.filter(|conn| conn.state() == connector::State::Connected)
|
||||
.filter(|conn| !self.crtcs.contains_key(&conn.handle()))
|
||||
.collect();
|
||||
|
||||
needs_crtc.retain(|connector| {
|
||||
if let Some(crtc) = self.restored_for_connector(drm, connector) {
|
||||
self.crtcs.insert(connector.handle(), crtc);
|
||||
|
||||
// This connector no longer needs crtc so let's remove it
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
|
||||
for connector in needs_crtc {
|
||||
if let Some(crtc) = self.pick_next_available_for_connector(drm, connector) {
|
||||
self.crtcs.insert(connector.handle(), crtc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn crtc_for_connector(&self, connector: &connector::Handle) -> Option<crtc::Handle> {
|
||||
self.crtcs.get(connector).copied()
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
//! # Smithay DRM Extras
|
||||
//!
|
||||
//! This crate contains some extra abstractions and helpers over DRM
|
||||
//!
|
||||
//! - [`display_info`] is responsible for extraction of information from DRM connectors
|
||||
//! - [`drm_scanner`] is responsible for detecting connector connected and
|
||||
//! disconnected events, as well as mapping CRTC to them.
|
||||
//!
|
||||
//! ### Features
|
||||
//! - `display_info` - If enabled `display_info` functionality is enabled through `libdisplay-info` integration
|
||||
|
||||
#![warn(missing_docs, missing_debug_implementations)]
|
||||
|
||||
#[cfg(feature = "display-info")]
|
||||
pub mod display_info;
|
||||
pub mod drm_scanner;
|
||||
Reference in new issue
Block a user