aboutsummaryrefslogtreecommitdiff
path: root/src
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 /src
parent4b7c16b04a7c80f5f9b6fcbc4a1d8c9448dffbdb (diff)
downloadniri-f0157e03e72714264e684295fac226e2046f0b38.tar.gz
niri-f0157e03e72714264e684295fac226e2046f0b38.tar.bz2
niri-f0157e03e72714264e684295fac226e2046f0b38.zip
Use libdisplay-info for make/model/serial parsing, implement throughout
Diffstat (limited to 'src')
-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
12 files changed, 305 insertions, 180 deletions
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), Some("eDP-" | "LVDS" | "DSI-"));
-
- // FIXME: use proper serial when we have libdisplay-info.
- // A serial is required for correct session restore by xdp-gnome.
- let serial = c.clone();
+ let display_name = make_display_name(output, is_laptop_panel);
let mut properties = HashMap::new();
- if is_laptop_panel {
- properties.insert(
- String::from("display-name"),
- OwnedValue::from(zvariant::Str::from_static("Built-in display")),
- );
- }
+ properties.insert(
+ String::from("display-name"),
+ OwnedValue::from(zvariant::Str::from(display_name)),
+ );
properties.insert(
String::from("is-builtin"),
OwnedValue::from(is_laptop_panel),
@@ -111,8 +106,16 @@ impl DisplayConfig {
.properties
.insert(String::from("is-current"), OwnedValue::from(true));
+ let connector = c.clone();
+ let model = output.model.clone();
+ let make = output.make.clone();
+
+ // Serial is used for session restore, so fall back to the connector name if it's
+ // not available.
+ let serial = output.serial.as_ref().unwrap_or(&connector).clone();
+
let monitor = Monitor {
- names: (c.clone(), String::new(), String::new(), serial),
+ names: (connector, make, model, serial),
modes,
properties,
};
@@ -144,15 +147,8 @@ impl DisplayConfig {
})
.collect();
- // Sort the built-in monitor first, then by connector name.
- monitors.sort_unstable_by(|a, b| {
- let a_is_builtin = a.0.properties.contains_key("display-name");
- let b_is_builtin = b.0.properties.contains_key("display-name");
- a_is_builtin
- .cmp(&b_is_builtin)
- .reverse()
- .then_with(|| a.0.names.0.cmp(&b.0.names.0))
- });
+ // Sort by connector.
+ monitors.sort_unstable_by(|a, b| a.0.names.0.cmp(&b.0.names.0));
let (monitors, logical_monitors) = monitors.into_iter().unzip();
let properties = HashMap::from([(String::from("layout-mode"), OwnedValue::from(1u32))]);
@@ -183,3 +179,48 @@ impl Start for DisplayConfig {
Ok(conn)
}
}
+
+// Adapted from Mutter.
+fn make_display_name(output: &niri_ipc::Output, is_laptop_panel: bool) -> String {
+ if is_laptop_panel {
+ return String::from("Built-in display");
+ }
+
+ let make = &output.make;
+ let model = &output.model;
+ if let Some(diagonal) = output.physical_size.map(|(width_mm, height_mm)| {
+ let diagonal = f64::hypot(f64::from(width_mm), f64::from(height_mm)) / 25.4;
+ format_diagonal(diagonal)
+ }) {
+ format!("{make} {diagonal}")
+ } else if model != "Unknown" {
+ format!("{make} {model}")
+ } else {
+ make.clone()
+ }
+}
+
+fn format_diagonal(diagonal_inches: f64) -> String {
+ let known = [12.1, 13.3, 15.6];
+ if let Some(d) = known.iter().find(|d| (*d - diagonal_inches).abs() < 0.1) {
+ format!("{d:.1}″")
+ } else {
+ format!("{}″", diagonal_inches.round() as u32)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use k9::snapshot;
+
+ use super::*;
+
+ #[test]
+ fn test_format_diagonal() {
+ snapshot!(format_diagonal(12.11), "12.1″");
+ snapshot!(format_diagonal(13.28), "13.3″");
+ snapshot!(format_diagonal(15.6), "15.6″");
+ snapshot!(format_diagonal(23.2), "23″");
+ snapshot!(format_diagonal(24.8), "25″");
+ }
+}
diff --git a/src/handlers/xdg_shell.rs b/src/handlers/xdg_shell.rs
index 2486dd7a..48c798aa 100644
--- a/src/handlers/xdg_shell.rs
+++ b/src/handlers/xdg_shell.rs
@@ -41,7 +41,7 @@ use crate::input::DOUBLE_CLICK_TIME;
use crate::layout::workspace::ColumnWidth;
use crate::niri::{PopupGrabState, State};
use crate::utils::transaction::Transaction;
-use crate::utils::{get_monotonic_time, send_scale_transform, ResizeEdge};
+use crate::utils::{get_monotonic_time, output_matches_name, send_scale_transform, ResizeEdge};
use crate::window::{InitialConfigureState, ResolvedWindowRules, Unmapped, WindowRef};
impl XdgShellHandler for State {
@@ -668,7 +668,12 @@ impl State {
rules
.open_on_output
.as_deref()
- .and_then(|name| self.niri.output_by_name.get(name))
+ .and_then(|name| {
+ self.niri
+ .global_space
+ .outputs()
+ .find(|output| output_matches_name(output, name))
+ })
.and_then(|o| self.niri.layout.monitor_for_output(o))
});
diff --git a/src/ipc/client.rs b/src/ipc/client.rs
index 0f4d6228..1b44a987 100644
--- a/src/ipc/client.rs
+++ b/src/ipc/client.rs
@@ -122,8 +122,8 @@ pub fn handle_msg(msg: Msg, json: bool) -> anyhow::Result<()> {
let mut outputs = outputs.into_iter().collect::<Vec<_>>();
outputs.sort_unstable_by(|a, b| a.0.cmp(&b.0));
- for (connector, output) in outputs.into_iter() {
- print_output(connector, output)?;
+ for (_name, output) in outputs.into_iter() {
+ print_output(output)?;
println!();
}
}
@@ -207,7 +207,7 @@ pub fn handle_msg(msg: Msg, json: bool) -> anyhow::Result<()> {
}
if let Some(output) = output {
- print_output(output.name.clone(), output)?;
+ print_output(output)?;
} else {
println!("No output is focused.");
}
@@ -364,11 +364,12 @@ pub fn handle_msg(msg: Msg, json: bool) -> anyhow::Result<()> {
Ok(())
}
-fn print_output(connector: String, output: Output) -> anyhow::Result<()> {
+fn print_output(output: Output) -> anyhow::Result<()> {
let Output {
name,
make,
model,
+ serial,
physical_size,
modes,
current_mode,
@@ -377,7 +378,8 @@ fn print_output(connector: String, output: Output) -> anyhow::Result<()> {
logical,
} = output;
- println!(r#"Output "{connector}" ({make} - {model} - {name})"#);
+ let serial = serial.as_deref().unwrap_or("Unknown");
+ println!(r#"Output "{make} {model} {serial}" ({name})"#);
if let Some(current) = current_mode {
let mode = *modes
diff --git a/src/ipc/server.rs b/src/ipc/server.rs
index 6990cd41..aeb0fcf0 100644
--- a/src/ipc/server.rs
+++ b/src/ipc/server.rs
@@ -13,6 +13,7 @@ use calloop::io::Async;
use directories::BaseDirs;
use futures_util::io::{AsyncReadExt, BufReader};
use futures_util::{select_biased, AsyncBufReadExt, AsyncWrite, AsyncWriteExt, FutureExt as _};
+use niri_config::OutputName;
use niri_ipc::state::{EventStreamState, EventStreamStatePart as _};
use niri_ipc::{Event, KeyboardLayouts, OutputConfigChanged, Reply, Request, Response, Workspace};
use smithay::input::keyboard::XkbContextHandler;
@@ -296,7 +297,7 @@ async fn process(ctx: &ClientCtx, request: Request) -> Reply {
let ipc_outputs = ctx.ipc_outputs.lock().unwrap();
let found = ipc_outputs
.values()
- .any(|o| o.name.eq_ignore_ascii_case(&output));
+ .any(|o| OutputName::from_ipc_output(o).matches(&output));
let response = if found {
OutputConfigChanged::Applied
} else {
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index 9a9e084d..734512dd 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -54,7 +54,7 @@ use crate::render_helpers::solid_color::{SolidColorBuffer, SolidColorRenderEleme
use crate::render_helpers::texture::TextureBuffer;
use crate::render_helpers::{BakedBuffer, RenderTarget, SplitElements};
use crate::utils::transaction::{Transaction, TransactionBlocker};
-use crate::utils::{output_size, round_logical_in_physical_max1, ResizeEdge};
+use crate::utils::{output_matches_name, output_size, round_logical_in_physical_max1, ResizeEdge};
use crate::window::ResolvedWindowRules;
pub mod closing_window;
@@ -344,8 +344,6 @@ impl<W: LayoutElement> Layout<W> {
}
pub fn add_output(&mut self, output: Output) {
- let id = OutputId::new(&output);
-
self.monitor_set = match mem::take(&mut self.monitor_set) {
MonitorSet::Normal {
mut monitors,
@@ -358,7 +356,7 @@ impl<W: LayoutElement> Layout<W> {
let mut workspaces = vec![];
for i in (0..primary.workspaces.len()).rev() {
- if primary.workspaces[i].original_output == id {
+ if primary.workspaces[i].original_output.matches(&output) {
let ws = primary.workspaces.remove(i);
// FIXME: this can be coded in a way that the workspace switch won't be
@@ -1722,18 +1720,16 @@ impl<W: LayoutElement> Layout<W> {
assert!(after_idx < monitor.workspaces.len());
}
- let monitor_id = OutputId::new(&monitor.output);
-
if idx == primary_idx {
for ws in &monitor.workspaces {
- if ws.original_output == monitor_id {
+ if ws.original_output.matches(&monitor.output) {
// This is the primary monitor's own workspace.
continue;
}
let own_monitor_exists = monitors
.iter()
- .any(|m| OutputId::new(&m.output) == ws.original_output);
+ .any(|m| ws.original_output.matches(&m.output));
assert!(
!own_monitor_exists,
"primary monitor cannot have workspaces for which their own monitor exists"
@@ -1744,7 +1740,7 @@ impl<W: LayoutElement> Layout<W> {
monitor
.workspaces
.iter()
- .any(|workspace| workspace.original_output == monitor_id),
+ .any(|workspace| workspace.original_output.matches(&monitor.output)),
"secondary monitor must not have any non-own workspaces"
);
}
@@ -1881,7 +1877,7 @@ impl<W: LayoutElement> Layout<W> {
.map(|name| {
monitors
.iter_mut()
- .position(|monitor| monitor.output_name().eq_ignore_ascii_case(name))
+ .position(|monitor| output_matches_name(&monitor.output, name))
.unwrap_or(*primary_idx)
})
.unwrap_or(*active_monitor_idx);
@@ -2556,7 +2552,7 @@ impl<W: LayoutElement> Default for MonitorSet<W> {
mod tests {
use std::cell::Cell;
- use niri_config::{FloatOrInt, WorkspaceName};
+ use niri_config::{FloatOrInt, OutputName, WorkspaceName};
use proptest::prelude::*;
use proptest_derive::Arbitrary;
use smithay::output::{Mode, PhysicalProperties, Subpixel};
@@ -2967,7 +2963,7 @@ mod tests {
}
let output = Output::new(
- name,
+ name.clone(),
PhysicalProperties {
size: Size::from((1280, 720)),
subpixel: Subpixel::Unknown,
@@ -2984,6 +2980,12 @@ mod tests {
None,
None,
);
+ output.user_data().insert_if_missing(|| OutputName {
+ connector: name,
+ make: None,
+ model: None,
+ serial: None,
+ });
layout.add_output(output.clone());
}
Op::AddScaledOutput { id, scale } => {
@@ -2993,7 +2995,7 @@ mod tests {
}
let output = Output::new(
- name,
+ name.clone(),
PhysicalProperties {
size: Size::from((1280, 720)),
subpixel: Subpixel::Unknown,
@@ -3010,6 +3012,12 @@ mod tests {
Some(smithay::output::Scale::Fractional(scale)),
None,
);
+ output.user_data().insert_if_missing(|| OutputName {
+ connector: name,
+ make: None,
+ model: None,
+ serial: None,
+ });
layout.add_output(output.clone());
}
Op::RemoveOutput(id) => {
diff --git a/src/layout/workspace.rs b/src/layout/workspace.rs
index f85ec622..a2dcf460 100644
--- a/src/layout/workspace.rs
+++ b/src/layout/workspace.rs
@@ -3,7 +3,9 @@ use std::iter::{self, zip};
use std::rc::Rc;
use std::time::Duration;
-use niri_config::{CenterFocusedColumn, PresetWidth, Struts, Workspace as WorkspaceConfig};
+use niri_config::{
+ CenterFocusedColumn, OutputName, PresetWidth, Struts, Workspace as WorkspaceConfig,
+};
use niri_ipc::SizeChange;
use ordered_float::NotNan;
use smithay::backend::renderer::gles::GlesRenderer;
@@ -117,9 +119,16 @@ pub struct Workspace<W: LayoutElement> {
id: WorkspaceId,
}
-#[derive(Debug, Clone, PartialEq, Eq)]
+#[derive(Debug, Clone)]
pub struct OutputId(String);
+impl OutputId {
+ pub fn matches(&self, output: &Output) -> bool {
+ let output_name = output.user_data().get::<OutputName>().unwrap();
+ output_name.matches(&self.0)
+ }
+}
+
static WORKSPACE_ID_COUNTER: IdCounter = IdCounter::new();
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -274,7 +283,8 @@ struct TileData {
impl OutputId {
pub fn new(output: &Output) -> Self {
- Self(output.name())
+ let output_name = output.user_data().get::<OutputName>().unwrap();
+ Self(output_name.format_make_model_serial_or_connector())
}
}
@@ -401,8 +411,8 @@ impl<W: LayoutElement> Workspace<W> {
) -> Self {
let original_output = OutputId(
config
- .clone()
- .and_then(|c| c.open_on_output)
+ .as_ref()
+ .and_then(|c| c.open_on_output.clone())
.unwrap_or_default(),
);
@@ -559,6 +569,11 @@ impl<W: LayoutElement> Workspace<W> {
self.output = output;
if let Some(output) = &self.output {
+ // Normalize original output: possibly replace connector with make/model/serial.
+ if self.original_output.matches(output) {
+ self.original_output = OutputId::new(output);
+ }
+
let scale = output.current_scale();
let transform = output.current_transform();
let working_area = compute_working_area(output, self.options.struts);
diff --git a/src/niri.rs b/src/niri.rs
index edd56466..4295a039 100644
--- a/src/niri.rs
+++ b/src/niri.rs
@@ -13,7 +13,7 @@ use _server_decoration::server::org_kde_kwin_server_decoration_manager::Mode as
use anyhow::{bail, ensure, Context};
use calloop::futures::Scheduler;
use niri_config::{
- Config, FloatOrInt, Key, Modifiers, PreviewRender, TrackLayout, WorkspaceReference,
+ Config, FloatOrInt, Key, Modifiers, OutputName, PreviewRender, TrackLayout, WorkspaceReference,
DEFAULT_BACKGROUND_COLOR,
};
use smithay::backend::allocator::Fourcc;
@@ -142,7 +142,7 @@ use crate::utils::scale::{closest_representable_scale, guess_monitor_scale};
use crate::utils::spawning::CHILD_ENV;
use crate::utils::{
center, center_f64, get_monotonic_time, ipc_transform_to_smithay, logical_output,
- make_screenshot_path, output_size, send_scale_transform, wri