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 /src | |
| 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
Diffstat (limited to 'src')
| -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 |
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 |
