diff options
| author | Ivan Molodetskikh <yalterz@gmail.com> | 2024-09-03 12:13:04 +0300 |
|---|---|---|
| committer | Ivan Molodetskikh <yalterz@gmail.com> | 2024-09-03 13:48:08 +0300 |
| commit | f0157e03e72714264e684295fac226e2046f0b38 (patch) | |
| tree | 7bfd198f59697704c5464c8498d3f8d7ff80131d | |
| parent | 4b7c16b04a7c80f5f9b6fcbc4a1d8c9448dffbdb (diff) | |
| download | niri-f0157e03e72714264e684295fac226e2046f0b38.tar.gz niri-f0157e03e72714264e684295fac226e2046f0b38.tar.bz2 niri-f0157e03e72714264e684295fac226e2046f0b38.zip | |
Use libdisplay-info for make/model/serial parsing, implement throughout
| -rw-r--r-- | .github/workflows/ci.yml | 10 | ||||
| -rw-r--r-- | Cargo.lock | 31 | ||||
| -rw-r--r-- | Cargo.toml | 1 | ||||
| -rw-r--r-- | niri-config/src/lib.rs | 167 | ||||
| -rw-r--r-- | niri-ipc/src/lib.rs | 4 | ||||
| -rw-r--r-- | niri-visual-tests/src/cases/layout.rs | 8 | ||||
| -rw-r--r-- | niri.spec.rpkg | 1 | ||||
| -rw-r--r-- | src/backend/mod.rs | 8 | ||||
| -rw-r--r-- | src/backend/tty.rs | 203 | ||||
| -rw-r--r-- | src/backend/winit.rs | 10 | ||||
| -rw-r--r-- | src/dbus/mutter_display_config.rs | 81 | ||||
| -rw-r--r-- | src/handlers/xdg_shell.rs | 9 | ||||
| -rw-r--r-- | src/ipc/client.rs | 12 | ||||
| -rw-r--r-- | src/ipc/server.rs | 3 | ||||
| -rw-r--r-- | src/layout/mod.rs | 34 | ||||
| -rw-r--r-- | src/layout/workspace.rs | 25 | ||||
| -rw-r--r-- | src/niri.rs | 75 | ||||
| -rw-r--r-- | src/protocols/output_management.rs | 18 | ||||
| -rw-r--r-- | src/utils/mod.rs | 7 | ||||
| -rw-r--r-- | wiki/Configuration:-Named-Workspaces.md | 2 | ||||
| -rw-r--r-- | wiki/Configuration:-Outputs.md | 12 | ||||
| -rw-r--r-- | wiki/Configuration:-Window-Rules.md | 4 |
22 files changed, 528 insertions, 197 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6e6e4544..8710ae34 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,7 @@ jobs: - name: Install dependencies run: | apt-get update -y - apt-get install -y curl gcc clang libudev-dev libgbm-dev libxkbcommon-dev libegl1-mesa-dev libwayland-dev libinput-dev libdbus-1-dev libsystemd-dev libseat-dev libpipewire-0.3-dev libpango1.0-dev + apt-get install -y curl gcc clang libudev-dev libgbm-dev libxkbcommon-dev libegl1-mesa-dev libwayland-dev libinput-dev libdbus-1-dev libsystemd-dev libseat-dev libpipewire-0.3-dev libpango1.0-dev libdisplay-info-dev - uses: dtolnay/rust-toolchain@stable @@ -85,7 +85,7 @@ jobs: - name: Install dependencies run: | apt-get update -y - apt-get install -y curl gcc clang libudev-dev libgbm-dev libxkbcommon-dev libegl1-mesa-dev libwayland-dev libinput-dev libdbus-1-dev libsystemd-dev libseat-dev libpipewire-0.3-dev libpango1.0-dev libadwaita-1-dev + apt-get install -y curl gcc clang libudev-dev libgbm-dev libxkbcommon-dev libegl1-mesa-dev libwayland-dev libinput-dev libdbus-1-dev libsystemd-dev libseat-dev libpipewire-0.3-dev libpango1.0-dev libadwaita-1-dev libdisplay-info-dev - uses: dtolnay/rust-toolchain@stable @@ -110,7 +110,7 @@ jobs: - name: Install dependencies run: | apt-get update -y - apt-get install -y curl gcc clang libudev-dev libgbm-dev libxkbcommon-dev libegl1-mesa-dev libwayland-dev libinput-dev libdbus-1-dev libsystemd-dev libseat-dev libpipewire-0.3-dev libpango1.0-dev libadwaita-1-dev + apt-get install -y curl gcc clang libudev-dev libgbm-dev libxkbcommon-dev libegl1-mesa-dev libwayland-dev libinput-dev libdbus-1-dev libsystemd-dev libseat-dev libpipewire-0.3-dev libpango1.0-dev libadwaita-1-dev libdisplay-info-dev - uses: dtolnay/rust-toolchain@1.77.0 @@ -134,7 +134,7 @@ jobs: - name: Install dependencies run: | apt-get update -y - apt-get install -y curl gcc clang libudev-dev libgbm-dev libxkbcommon-dev libegl1-mesa-dev libwayland-dev libinput-dev libdbus-1-dev libsystemd-dev libseat-dev libpipewire-0.3-dev libpango1.0-dev libadwaita-1-dev + apt-get install -y curl gcc clang libudev-dev libgbm-dev libxkbcommon-dev libegl1-mesa-dev libwayland-dev libinput-dev libdbus-1-dev libsystemd-dev libseat-dev libpipewire-0.3-dev libpango1.0-dev libadwaita-1-dev libdisplay-info-dev - uses: dtolnay/rust-toolchain@stable with: @@ -172,7 +172,7 @@ jobs: - name: Install dependencies run: | sudo dnf update -y - sudo dnf install -y cargo gcc libudev-devel libgbm-devel libxkbcommon-devel wayland-devel libinput-devel dbus-devel systemd-devel libseat-devel pipewire-devel pango-devel cairo-gobject-devel clang libadwaita-devel + sudo dnf install -y cargo gcc libudev-devel libgbm-devel libxkbcommon-devel wayland-devel libinput-devel dbus-devel systemd-devel libseat-devel pipewire-devel pango-devel cairo-gobject-devel clang libadwaita-devel libdisplay-info-devel - uses: Swatinem/rust-cache@v2 - run: cargo build --all @@ -1943,6 +1943,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] +name = "libdisplay-info" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb6dd47a677df2378a8bb88d08a593f51e8dddf4b61d2db5f2ceb35e67f9389d" +dependencies = [ + "bitflags 2.6.0", + "libc", + "libdisplay-info-derive", + "libdisplay-info-sys", + "thiserror", +] + +[[package]] +name = "libdisplay-info-derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea1cd31036b732a546d845f9485c56b1b606b5e476b0821c680dd66c8cd6fcee" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "libdisplay-info-sys" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea8cec1fa7872b621f40c756bc1304b1a975461282e250b0e76737b037c0c236" + +[[package]] name = "libloading" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2238,6 +2268,7 @@ dependencies = [ "k9", "keyframe", "libc", + "libdisplay-info", "log", "niri-config", "niri-ipc", @@ -59,6 +59,7 @@ glam = "0.28.0" input = { version = "0.9.0", features = ["libinput_1_21"] } keyframe = { version = "1.1.1", default-features = false } libc = "0.2.155" +libdisplay-info = "0.1.0" log = { version = "0.4.22", features = ["max_level_trace", "release_max_level_debug"] } niri-config = { version = "0.1.8", path = "niri-config" } niri-ipc = { version = "0.1.8", path = "niri-ipc", features = ["clap"] } diff --git a/niri-config/src/lib.rs b/niri-config/src/lib.rs index 9092cd21..bee1288f 100644 --- a/niri-config/src/lib.rs +++ b/niri-config/src/lib.rs @@ -359,6 +359,14 @@ impl Default for Output { } } +#[derive(Debug, Clone)] +pub struct OutputName { + pub connector: String, + pub make: Option<String>, + pub model: Option<String>, + pub serial: Option<String>, +} + #[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq, Eq)] pub struct Position { #[knuffel(property)] @@ -1720,14 +1728,95 @@ impl FromIterator<Output> for Outputs { } impl Outputs { - pub fn find(&self, name: &str) -> Option<&Output> { - self.0.iter().find(|o| o.name.eq_ignore_ascii_case(name)) + pub fn find(&self, name: &OutputName) -> Option<&Output> { + self.0.iter().find(|o| name.matches(&o.name)) } - pub fn find_mut(&mut self, name: &str) -> Option<&mut Output> { - self.0 - .iter_mut() - .find(|o| o.name.eq_ignore_ascii_case(name)) + pub fn find_mut(&mut self, name: &OutputName) -> Option<&mut Output> { + self.0.iter_mut().find(|o| name.matches(&o.name)) + } +} + +impl OutputName { + pub fn from_ipc_output(output: &niri_ipc::Output) -> Self { + Self { + connector: output.name.clone(), + make: (output.make != "Unknown").then(|| output.make.clone()), + model: (output.model != "Unknown").then(|| output.model.clone()), + serial: output.serial.clone(), + } + } + + /// Returns an output description matching what Smithay's `Output::new()` does. + pub fn format_description(&self) -> String { + format!( + "{} - {} - {}", + self.make.as_deref().unwrap_or("Unknown"), + self.model.as_deref().unwrap_or("Unknown"), + self.connector, + ) + } + + /// Returns an output name that will match by make/model/serial or, if they are missing, by + /// connector. + pub fn format_make_model_serial_or_connector(&self) -> String { + if self.make.is_none() && self.model.is_none() && self.serial.is_none() { + self.connector.to_string() + } else { + let make = self.make.as_deref().unwrap_or("Unknown"); + let model = self.model.as_deref().unwrap_or("Unknown"); + let serial = self.serial.as_deref().unwrap_or("Unknown"); + format!("{make} {model} {serial}") + } + } + + pub fn matches(&self, target: &str) -> bool { + // Match by connector. + if target.eq_ignore_ascii_case(&self.connector) { + return true; + } + + // If no other fields are available, don't try to match by them. + // + // This is used by niri msg output. + if self.make.is_none() && self.model.is_none() && self.serial.is_none() { + return false; + } + + // Match by "make model serial" with Unknown if something is missing. + let make = self.make.as_deref().unwrap_or("Unknown"); + let model = self.model.as_deref().unwrap_or("Unknown"); + let serial = self.serial.as_deref().unwrap_or("Unknown"); + + let Some(target_make) = target.get(..make.len()) else { + return false; + }; + let rest = &target[make.len()..]; + if !target_make.eq_ignore_ascii_case(make) { + return false; + } + if !rest.starts_with(' ') { + return false; + } + let rest = &rest[1..]; + + let Some(target_model) = rest.get(..model.len()) else { + return false; + }; + let rest = &rest[model.len()..]; + if !target_model.eq_ignore_ascii_case(model) { + return false; + } + if !rest.starts_with(' ') { + return false; + } + + let rest = &rest[1..]; + if !rest.eq_ignore_ascii_case(serial) { + return false; + } + + true } } @@ -3351,4 +3440,70 @@ mod tests { assert_eq!(config.input.keyboard.repeat_delay, 600); assert_eq!(config.input.keyboard.repeat_rate, 25); } + + #[test] + fn test_output_name_match() { + fn check( + target: &str, + connector: &str, + make: Option<&str>, + model: Option<&str>, + serial: Option<&str>, + ) -> bool { + let name = OutputName { + connector: connector.to_string(), + make: make.map(|x| x.to_string()), + model: model.map(|x| x.to_string()), + serial: serial.map(|x| x.to_string()), + }; + name.matches(target) + } + + assert!(check("dp-2", "DP-2", None, None, None)); + assert!(!check("dp-1", "DP-2", None, None, None)); + assert!(check("dp-2", "DP-2", Some("a"), Some("b"), Some("c"))); + assert!(check( + "some company some monitor 1234", + "DP-2", + Some("Some Company"), + Some("Some Monitor"), + Some("1234") + )); + assert!(!check( + "some other company some monitor 1234", + "DP-2", + Some("Some Company"), + Some("Some Monitor"), + Some("1234") + )); + assert!(!check( + "make model serial ", + "DP-2", + Some("make"), + Some("model"), + Some("serial") + )); + assert!(check( + "make serial", + "DP-2", + Some("make"), + Some(""), + Some("serial") + )); + assert!(check( + "make model unknown", + "DP-2", + Some("Make"), + Some("Model"), + None + )); + assert!(check( + "unknown unknown serial", + "DP-2", + None, + None, + Some("Serial") + )); + assert!(!check("unknown unknown unknown", "DP-2", None, None, None)); + } } diff --git a/niri-ipc/src/lib.rs b/niri-ipc/src/lib.rs index 22a1a63b..32377bd6 100644 --- a/niri-ipc/src/lib.rs +++ b/niri-ipc/src/lib.rs @@ -71,7 +71,7 @@ pub enum Response { Version(String), /// Information about connected outputs. /// - /// Map from connector name to output info. + /// Map from output name to output info. Outputs(HashMap<String, Output>), /// Information about workspaces. Workspaces(Vec<Workspace>), @@ -466,6 +466,8 @@ pub struct Output { pub make: String, /// Textual description of the model. pub model: String, + /// Serial of the output, if known. + pub serial: Option<String>, /// Physical width and height of the output in millimeters, if known. pub physical_size: Option<(u32, u32)>, /// Available modes for the output. diff --git a/niri-visual-tests/src/cases/layout.rs b/niri-visual-tests/src/cases/layout.rs index 5a1a0ae4..8dc32154 100644 --- a/niri-visual-tests/src/cases/layout.rs +++ b/niri-visual-tests/src/cases/layout.rs @@ -5,7 +5,7 @@ use niri::layout::workspace::ColumnWidth; use niri::layout::{LayoutElement as _, Options}; use niri::render_helpers::RenderTarget; use niri::utils::get_monotonic_time; -use niri_config::{Color, FloatOrInt}; +use niri_config::{Color, FloatOrInt, OutputName}; use smithay::backend::renderer::element::RenderElement; use smithay::backend::renderer::gles::GlesRenderer; use smithay::desktop::layer_map_for_output; @@ -41,6 +41,12 @@ impl Layout { refresh: 60000, }); output.change_current_state(mode, None, None, None); + output.user_data().insert_if_missing(|| OutputName { + connector: String::new(), + make: None, + model: None, + serial: None, + }); let options = Options { focus_ring: niri_config::FocusRing { diff --git a/niri.spec.rpkg b/niri.spec.rpkg index 0e1b8fc9..71ef9494 100644 --- a/niri.spec.rpkg +++ b/niri.spec.rpkg @@ -68,6 +68,7 @@ BuildRequires: pkgconfig(libinput) BuildRequires: pkgconfig(dbus-1) BuildRequires: pkgconfig(systemd) BuildRequires: pkgconfig(libseat) +BuildRequires: pkgconfig(libdisplay-info) BuildRequires: pipewire-devel BuildRequires: pango-devel BuildRequires: cairo-gobject-devel diff --git a/src/backend/mod.rs b/src/backend/mod.rs index bbacf1cf..2213b8bd 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -174,6 +174,14 @@ impl Backend { } } + pub fn tty_checked(&mut self) -> Option<&mut Tty> { + if let Self::Tty(v) = self { + Some(v) + } else { + None + } + } + pub fn tty(&mut self) -> &mut Tty { if let Self::Tty(v) = self { v diff --git a/src/backend/tty.rs b/src/backend/tty.rs index 17b16eec..b4c892a8 100644 --- a/src/backend/tty.rs +++ b/src/backend/tty.rs @@ -4,7 +4,6 @@ use std::fmt::Write; use std::iter::zip; use std::num::NonZeroU64; use std::os::fd::AsFd; -use std::panic::{catch_unwind, AssertUnwindSafe}; use std::path::Path; use std::rc::Rc; use std::sync::{Arc, Mutex}; @@ -14,7 +13,7 @@ use std::{io, mem}; use anyhow::{anyhow, bail, ensure, Context}; use bytemuck::cast_slice_mut; use libc::dev_t; -use niri_config::Config; +use niri_config::{Config, OutputName}; use smithay::backend::allocator::dmabuf::Dmabuf; use smithay::backend::allocator::format::FormatSet; use smithay::backend::allocator::gbm::{GbmAllocator, GbmBufferFlags, GbmDevice}; @@ -52,7 +51,6 @@ use smithay::wayland::drm_lease::{ DrmLease, DrmLeaseBuilder, DrmLeaseRequest, DrmLeaseState, LeaseRejected, }; use smithay_drm_extras::drm_scanner::{DrmScanEvent, DrmScanner}; -use smithay_drm_extras::edid::EdidInfo; use wayland_protocols::wp::linux_dmabuf::zv1::server::zwp_linux_dmabuf_feedback_v1::TrancheFlags; use wayland_protocols::wp::presentation_time::server::wp_presentation_feedback; @@ -178,7 +176,7 @@ struct TtyOutputState { } struct Surface { - name: String, + name: OutputName, compositor: GbmDrmCompositor, connector: connector::Handle, dmabuf_feedback: Option<SurfaceDmabufFeedback>, @@ -440,7 +438,7 @@ impl Tty { continue; }; let Some(output_state) = niri.output_state.get_mut(&output) else { - error!("missing state for output {:?}", surface.name); + error!("missing state for output {:?}", surface.name.connector); continue; }; @@ -746,26 +744,22 @@ impl Tty { connector: connector::Info, crtc: crtc::Handle, ) -> anyhow::Result<()> { - let output_name = format!( - "{}-{}", - connector.interface().as_str(), - connector.interface_id(), - ); - debug!("connecting connector: {output_name}"); + let connector_name = format_connector_name(&connector); + debug!("connecting connector: {connector_name}"); let device = self.devices.get_mut(&node).context("missing device")?; + let output_name = make_output_name(&device.drm, connector.handle(), connector_name.clone()); + let non_desktop = find_drm_property(&device.drm, connector.handle(), "non-desktop") .and_then(|(_, info, value)| info.value_type().convert_value(value).as_boolean()) .unwrap_or(false); if non_desktop { debug!("output is non desktop"); - let description = get_edid_info(&device.drm, connector.handle()) - .map(|info| truncate_to_nul(info.model)) - .unwrap_or_else(|| "Unknown".into()); + let description = output_name.format_description(); if let Some(lease_state) = &mut device.drm_lease_state { - lease_state.add_connector::<State>(connector.handle(), output_name, description); + lease_state.add_connector::<State>(connector.handle(), connector_name, description); } device .non_desktop_connectors @@ -881,22 +875,13 @@ impl Tty { // Update the output mode. let (physical_width, physical_height) = connector.size().unwrap_or((0, 0)); - let (make, model) = get_edid_info(&device.drm, connector.handle()) - .map(|info| { - ( - truncate_to_nul(info.manufacturer), - truncate_to_nul(info.model), - ) - }) - .unwrap_or_else(|| ("Unknown".into(), "Unknown".into())); - let output = Output::new( - output_name.clone(), + connector_name.clone(), PhysicalProperties { size: (physical_width as i32, physical_height as i32).into(), subpixel: connector.subpixel().into(), - model, - make, + model: output_name.model.as_deref().unwrap_or("Unknown").to_owned(), + make: output_name.make.as_deref().unwrap_or("Unknown").to_owned(), }, ); @@ -907,6 +892,7 @@ impl Tty { output .user_data() .insert_if_missing(|| TtyOutputState { node, crtc }); + output.user_data().insert_if_missing(|| output_name.clone()); let mut planes = surface.planes().clone(); @@ -993,17 +979,18 @@ impl Tty { } let vblank_frame_name = - tracy_client::FrameName::new_leak(format!("vblank on {output_name}")); - let time_since_presentation_plot_name = - tracy_client::PlotName::new_leak(format!("{output_name} time since presentation, ms")); + tracy_client::FrameName::new_leak(format!("vblank on {connector_name}")); + let time_since_presentation_plot_name = tracy_client::PlotName::new_leak(format!( + "{connector_name} time since presentation, ms" + )); let presentation_misprediction_plot_name = tracy_client::PlotName::new_leak(format!( - "{output_name} presentation misprediction, ms" + "{connector_name} presentation misprediction, ms" )); let sequence_delta_plot_name = - tracy_client::PlotName::new_leak(format!("{output_name} sequence delta")); + tracy_client::PlotName::new_leak(format!("{connector_name} sequence delta")); let surface = Surface { - name: output_name.clone(), + name: output_name, connector: connector.handle(), compositor, dmabuf_feedback, @@ -1066,7 +1053,7 @@ impl Tty { return; }; - debug!("disconnecting connector: {:?}", surface.name); + debug!("disconnecting connector: {:?}", surface.name.connector); let output = niri .global_space @@ -1108,7 +1095,7 @@ impl Tty { // Finish the Tracy frame, if any. drop(surface.vblank_frame.take()); - let name = &surface.name; + let name = &surface.name.connector; trace!("vblank on {name} {meta:?}"); span.emit_text(name); @@ -1311,7 +1298,7 @@ impl Tty { return rv; }; - span.emit_text(&surface.name); + span.emit_text(&surface.name.connector); if !device.drm.is_active() { warn!("device is inactive"); @@ -1526,22 +1513,10 @@ impl Tty { for (node, device) in &self.devices { for (connector, crtc) in device.drm_scanner.crtcs() { - let name = format!( - "{}-{}", - connector.interface().as_str(), - connector.interface_id(), - ); - + let connector_name = format_connector_name(connector); let physical_size = connector.size(); - - let (make, model) = get_edid_info(&device.drm, connector.handle()) - .map(|info| { - ( - truncate_to_nul(info.manufacturer), - truncate_to_nul(info.model), - ) - }) - .unwrap_or_else(|| ("Unknown".into(), "Unknown".into())); + let output_name = + make_output_name(&device.drm, connector.handle(), connector_name.clone()); let surface = device.surfaces.get(&crtc); let current_crtc_mode = surface.map(|surface| surface.compositor.pending_mode()); @@ -1589,9 +1564,10 @@ impl Tty { .map(logical_output); let ipc_output = niri_ipc::Output { - name, - make, - model, + name: connector_name, + make: output_name.make.unwrap_or_else(|| "Unknown".into()), + model: output_name.model.unwrap_or_else(|| "Unknown".into()), + serial: output_name.serial, physical_size, modes, current_mode, @@ -1736,7 +1712,7 @@ impl Tty { continue; }; let Some(output_state) = niri.output_state.get_mut(&output) else { - error!("missing state for output {:?}", surface.name); + error!("missing state for output {:?}", surface.name.connector); continue; }; @@ -1759,7 +1735,7 @@ impl Tty { warn!( "output {:?}: configured mode {}x{}{} could not be found, \ falling back to preferred", - surface.name, + surface.name.connector, target.width, target.height, if let Some(refresh) = target.refresh { @@ -1770,7 +1746,10 @@ impl Tty { ); } - debug!("output {:?}: picking mode: {mode:?}", surface.name); + debug!( + "output {:?}: picking mode: {mode:?}", + surface.name.connector + ); if let Err(err) = surface.compositor.use_mode(mode) { warn!("error changing mode: {err:?}"); continue; @@ -1796,12 +1775,8 @@ impl Tty { continue; } - let output_name = format!( - "{}-{}", - connector.interface().as_str(), - connector.interface_id(), - ); - + let connector_name = format_connector_name(connector); + let output_name = make_output_name(&device.drm, connector.handle(), connector_name); let config = self .config .borrow() @@ -1845,6 +1820,30 @@ impl Tty { pub fn get_device_from_node(&mut self, node: DrmNode) -> Option<&mut OutputDevice> { self.devices.get_mut(&node) } + + pub fn disconnected_connector_name_by_name_match(&self, target: &str) -> Option<OutputName> { + for device in self.devices.values() { + for (connector, crtc) in device.drm_scanner.crtcs() { + // Check if connected. + if connector.state() != connector::State::Connected { + continue; + } + + // Check if already enabled. + if device.surfaces.contains_key(&crtc) { + continue; + } + + let connector_name = format_connector_name(connector); + let output_name = make_output_name(&device.drm, connector.handle(), connector_name); + if output_name.matches(target) { + return Some(output_name); + } + } + } + + None + } } impl GammaProps { @@ -2277,23 +2276,21 @@ fn pick_mode( mode.map(|m| (*m, fallback)) } -fn truncate_to_nul(mut s: String) -> String { - if let Some(index) = s.find('\0') { - s.truncate(index); - } - s -} - -fn get_edid_info(device: &DrmDevice, connector: connector::Handle) -> Option<EdidInfo> { - match catch_unwind(AssertUnwindSafe(move || { - EdidInfo::for_connector(device, connector) - })) { - Ok(info) => info, - Err(err) => { - warn!("edid-rs panicked: {err:?}"); - None - } - } +fn get_edid_info( + device: &DrmDevice, + connector: connector::Handle, +) -> anyhow::Result<libdisplay_info::info::Info> { + let (_, info, value) = + find_drm_property(device, connector, "EDID").context("no EDID property")?; + let blob = info + .value_type() + .convert_value(value) + .as_blob() + .context("EDID was not blob type")?; + let data = device + .get_property_blob(blob) + .context("error getting EDID blob value")?; + libdisplay_info::info::Info::parse_edid(&data).context("error parsing EDID") } fn set_max_bpc(device: &DrmDevice, connector: connector::Handle, bpc: u64) -> anyhow::Result<u64> { @@ -2415,41 +2412,47 @@ fn try_to_change_vrr( match set_vrr_enabled(device, crtc, enable_vrr) { Ok(enabled) => { if enabled != enable_vrr { - warn!("output {:?}: failed {} VRR", surface.name, word); + warn!("output {:?}: failed {} VRR", surface.name.connector, word); } surface.vrr_enabled = enabled; output_state.frame_clock.set_vrr(enabled); } Err(err) => { - warn!("output {:?}: error {} VRR: {err:?}", surface.name, word); + warn!( + "output {:?}: error {} VRR: {err:?}", + surface.name.connector, word + ); } } } else if enable_vrr { warn!( "output {:?}: cannot enable VRR because connector is not vrr_capable", - surface.name + surface.name.connector ); } } -#[cfg(test)] -mod tests { - use super::*; - - #[track_caller] - fn check(input: &str, expected: &str) { - let input = String::from(input); - assert_eq!(truncate_to_nul(input), expected); - } +fn format_connector_name(connector: &connector::Info) -> String { + format!( + "{}-{}", + connector.interface().as_str(), + connector.interface_id(), + ) +} - #[test] - fn truncate_to_nul_works() { - check("", ""); - check("qwer", "qwer"); - check("abc\0def", "abc"); - check("\0as", ""); - check("a\0\0\0b", "a"); - check("bb😁\0cc", "bb😁"); +fn make_output_name( + device: &DrmDevice, + connector: connector::Handle, + connector_name: String, +) -> OutputName { + let info = get_edid_info(device, connector) + .map_err(|err| warn!("error getting EDID info for {connector_name}: {err:?}")) + .ok(); + OutputName { + connector: connector_name, + make: info.as_ref().and_then(|info| info.make()), + model: info.as_ref().and_then(|info| info.model()), + serial: info.as_ref().and_then(|info| info.serial()), } } diff --git a/src/backend/winit.rs b/src/backend/winit.rs index 61744e5e..500215f7 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -5,7 +5,7 @@ use std::rc::Rc; use std::sync::{Arc, Mutex}; use std::time::Duration; -use niri_config::Config; +use niri_config::{Config, OutputName}; use smithay::backend::allocator::dmabuf::Dmabuf; use smithay::backend::renderer::damage::OutputDamageTracker; use smithay::backend::renderer::gles::GlesRenderer; @@ -59,6 +59,13 @@ impl Winit { output.change_current_state(Some(mode), None, None, None); output.set_preferred(mode); + output.user_data().insert_if_missing(|| OutputName { + connector: "winit".to_string(), + make: Some("Smithay".to_string()), + model: Some("Winit".to_string()), + serial: None, + }); + let physical_properties = output.physical_properties(); let ipc_outputs = Arc::new(Mutex::new(HashMap::from([( OutputId::next(), @@ -66,6 +73,7 @@ impl Winit { name: output.name(), make: physical_properties.make, model: physical_properties.model, + serial: None, physical_size: None, modes: vec