aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIvan Molodetskikh <yalterz@gmail.com>2024-09-03 12:13:04 +0300
committerIvan Molodetskikh <yalterz@gmail.com>2024-09-03 13:48:08 +0300
commitf0157e03e72714264e684295fac226e2046f0b38 (patch)
tree7bfd198f59697704c5464c8498d3f8d7ff80131d
parent4b7c16b04a7c80f5f9b6fcbc4a1d8c9448dffbdb (diff)
downloadniri-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.yml10
-rw-r--r--Cargo.lock31
-rw-r--r--Cargo.toml1
-rw-r--r--niri-config/src/lib.rs167
-rw-r--r--niri-ipc/src/lib.rs4
-rw-r--r--niri-visual-tests/src/cases/layout.rs8
-rw-r--r--niri.spec.rpkg1
-rw-r--r--src/backend/mod.rs8
-rw-r--r--src/backend/tty.rs203
-rw-r--r--src/backend/winit.rs10
-rw-r--r--src/dbus/mutter_display_config.rs81
-rw-r--r--src/handlers/xdg_shell.rs9
-rw-r--r--src/ipc/client.rs12
-rw-r--r--src/ipc/server.rs3
-rw-r--r--src/layout/mod.rs34
-rw-r--r--src/layout/workspace.rs25
-rw-r--r--src/niri.rs75
-rw-r--r--src/protocols/output_management.rs18
-rw-r--r--src/utils/mod.rs7
-rw-r--r--wiki/Configuration:-Named-Workspaces.md2
-rw-r--r--wiki/Configuration:-Outputs.md12
-rw-r--r--wiki/Configuration:-Window-Rules.md4
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
diff --git a/Cargo.lock b/Cargo.lock
index 02cb81ea..131a0c75 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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",
diff --git a/Cargo.toml b/Cargo.toml
index 6262d052..e8a4574b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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![niri_ipc::Mode {
width: backend.window_size().w.clamp(0, u16::MAX as i32) as u16,
diff --git a/src/dbus/mutter_display_config.rs b/src/dbus/mutter_display_config.rs
index 146174f8..e1d12870 100644
--- a/src/dbus/mutter_display_config.rs
+++ b/src/dbus/mutter_display_config.rs
@@ -64,18 +64,13 @@ impl DisplayConfig {
// Loosely matches the check in Mutter.
let c = &output.name;
let is_laptop_panel = matches!(c.get(..4), So