From edbfcfa2e5da9e465284bf70e9d888aea3b4d195 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Wed, 13 Apr 2022 22:59:14 +0200 Subject: [PATCH] wayland: Add support for wlr_output_configuration --- Cargo.lock | 62 ++++---- src/backend/kms/mod.rs | 240 +++++++++++++++++++------------ src/backend/winit.rs | 49 ++++++- src/backend/x11.rs | 68 ++++++--- src/config.rs | 19 +-- src/shell/layout/floating/mod.rs | 6 +- src/shell/mod.rs | 199 +++++++++++++------------ src/state.rs | 157 ++++++++++++++++++-- 8 files changed, 543 insertions(+), 257 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ed2a030e..0d3340d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -349,9 +349,9 @@ checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" [[package]] name = "darling" -version = "0.13.2" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e92cb285610dd935f60ee8b4d62dd1988bd12b7ea50579bd6a138201525318e" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" dependencies = [ "darling_core", "darling_macro", @@ -359,9 +359,9 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.13.2" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c29e95ab498b18131ea460b2c0baa18cbf041231d122b0b7bfebef8c8e88989" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" dependencies = [ "fnv", "ident_case", @@ -373,9 +373,9 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.13.2" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b21dd6b221dd547528bd6fb15f1a3b7ab03b9a06f76bff288a8c629bcfbe7f0e" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" dependencies = [ "darling_core", "quote", @@ -715,9 +715,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "js-sys" -version = "0.3.56" +version = "0.3.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" +checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" dependencies = [ "wasm-bindgen", ] @@ -736,9 +736,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.121" +version = "0.2.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" +checksum = "cb691a747a7ab48abc15c5b42066eaafde10dc427e3b6ee2a1cf43db04c763bd" [[package]] name = "libloading" @@ -1087,18 +1087,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1" dependencies = [ "unicode-xid", ] [[package]] name = "quote" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "632d02bff7f874a36f33ea8bb416cd484b90cc66c1194b1a1110d067a7013f58" +checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" dependencies = [ "proc-macro2", ] @@ -1311,7 +1311,7 @@ checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" [[package]] name = "smithay" version = "0.3.0" -source = "git+https://github.com/pop-os/smithay?branch=main#d8bf081cb5d2c6c666daaf238e2a6360ca963716" +source = "git+https://github.com/pop-os/smithay?branch=main#a9aea2275645cc4b2fc290103cc2072f9a2bfbb1" dependencies = [ "appendlist", "bitflags", @@ -1350,9 +1350,9 @@ dependencies = [ [[package]] name = "smithay-client-toolkit" -version = "0.15.3" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1325f292209cee78d5035530932422a30aa4c8fda1a16593ac083c1de211e68a" +checksum = "8a28f16a97fa0e8ce563b2774d1e732dd5d4025d2772c5dba0a41a0f90a29da3" dependencies = [ "bitflags", "calloop", @@ -1395,9 +1395,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.90" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704df27628939572cd88d33f171cd6f896f4eaca85252c6e0a72d8d8287ee86f" +checksum = "b683b2b825c8eef438b77c36a06dc262294da3d5a5813fac20da149241dcd44d" dependencies = [ "proc-macro2", "quote", @@ -1534,9 +1534,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.79" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" +checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -1544,9 +1544,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.79" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" +checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" dependencies = [ "bumpalo", "lazy_static", @@ -1559,9 +1559,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.79" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" +checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1569,9 +1569,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.79" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" +checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" dependencies = [ "proc-macro2", "quote", @@ -1582,9 +1582,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.79" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" +checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" [[package]] name = "wayland-client" @@ -1692,9 +1692,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.56" +version = "0.3.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb" +checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index 69cdc35e..25ae198b 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -5,9 +5,9 @@ use crate::state::Fps; use crate::{ backend::render, - config::OutputConfig, + config::{Config, OutputConfig}, + shell::Shell, state::{BackendData, Common, State}, - utils::GlobalDrop, }; use anyhow::{Context, Result}; @@ -32,7 +32,7 @@ use smithay::{ drm::control::{connector, crtc, Device as ControlDevice, ModeTypeFlags}, input::Libinput, nix::{fcntl::OFlag, sys::stat::dev_t}, - wayland_server::{protocol::wl_output, Display}, + wayland_server::protocol::wl_output, }, utils::signaling::{Linkable, Signaler}, wayland::output::{Mode as OutputMode, Output, PhysicalProperties}, @@ -73,9 +73,9 @@ pub struct Device { } pub struct Surface { - surface: GbmBufferedSurface>>, SessionFd>, + surface: Option>>, SessionFd>>, + connector: connector::Handle, output: Output, - _global: GlobalDrop, last_submit: Option, refresh_rate: u32, vrr: bool, @@ -241,8 +241,8 @@ impl State { DrmEvent::VBlank(crtc) => { if let Some(device) = state.backend.kms().devices.get_mut(&drm_node) { if let Some(surface) = device.surfaces.get_mut(&crtc) { - match surface.surface.frame_submitted() { - Ok(_) => { + match surface.surface.as_mut().map(|x| x.frame_submitted()) { + Some(Ok(_)) => { surface.last_submit = metadata.take().map(|data| data.time); surface.pending = false; state @@ -254,7 +254,10 @@ impl State { state.common.start_time.elapsed().as_millis() as u32 ); } - Err(err) => slog_scope::warn!("Failed to submit frame: {}", err), + Some(Err(err)) => { + slog_scope::warn!("Failed to submit frame: {}", err) + } + None => {} // got disabled }; } } @@ -293,23 +296,30 @@ impl State { }; let outputs = device.enumerate_surfaces()?.added; // There are no removed outputs on newly added devices + let mut wl_outputs = Vec::new(); for (crtc, conn) in outputs { match device.setup_surface( &drm_node, crtc, conn, self.backend.kms().signaler.clone(), - &mut self.common.display.borrow_mut(), &mut self.common.event_loop_handle, ) { - Ok(output) => self.common.shell.map_output( - &output, - &mut self.backend, - &mut self.common.config, - ), + Ok(output) => { + self.common.shell.map_output( + &output, + &mut self.backend, + &mut self.common.config, + ); + wl_outputs.push(output); + } Err(err) => slog_scope::warn!("Failed to initialize output: {}", err), }; } + self.common.output_conf.add_heads(wl_outputs.iter()); + self.common + .output_conf + .update(&mut *self.common.display.borrow_mut()); self.backend.kms().devices.insert(drm_node, device); Ok(()) @@ -336,7 +346,6 @@ impl State { crtc, conn, signaler.clone(), - &mut self.common.display.borrow_mut(), &mut self.common.event_loop_handle, ) { Ok(output) => { @@ -347,16 +356,21 @@ impl State { } } + self.common.output_conf.remove_heads(outputs_removed.iter()); for output in outputs_removed.into_iter() { self.common .shell - .unmap_output(&output, &mut self.backend, &self.common.config); + .unmap_output(&output, &mut self.backend, &mut self.common.config); } + self.common.output_conf.add_heads(outputs_added.iter()); for output in outputs_added.into_iter() { self.common .shell .map_output(&output, &mut self.backend, &mut self.common.config) } + self.common + .output_conf + .update(&mut self.common.display.borrow_mut()); Ok(()) } @@ -377,11 +391,15 @@ impl State { self.common.event_loop_handle.remove(socket.token); } } + self.common.output_conf.remove_heads(outputs_removed.iter()); for output in outputs_removed.into_iter() { self.common .shell - .unmap_output(&output, &mut self.backend, &self.common.config); + .unmap_output(&output, &mut self.backend, &mut self.common.config); } + self.common + .output_conf + .update(&mut *self.common.display.borrow_mut()); Ok(()) } @@ -402,29 +420,18 @@ impl Device { let surfaces = self .surfaces .iter() - .map(|(c, s)| (*c, s.surface.current_connectors().into_iter().next())) - .collect::>>(); + .map(|(c, s)| (*c, s.connector)) + .collect::>(); let added = config .iter() - .filter(|(conn, crtc)| { - surfaces - .get(&crtc) - .map(|c| c.as_ref() != Some(*conn)) - .unwrap_or(true) - }) + .filter(|(conn, crtc)| surfaces.get(&crtc).map(|c| c != *conn).unwrap_or(true)) .map(|(conn, crtc)| (crtc, conn)) .map(|(crtc, conn)| (*crtc, *conn)) .collect::>(); let removed = surfaces .iter() - .filter(|(crtc, conn)| { - if let Some(conn) = conn { - config.get(conn).map(|c| c != *crtc).unwrap_or(true) - } else { - true - } - }) + .filter(|(crtc, conn)| config.get(conn).map(|c| c != *crtc).unwrap_or(true)) .map(|(crtc, _)| *crtc) .collect::>(); @@ -437,7 +444,6 @@ impl Device { crtc: crtc::Handle, conn: connector::Handle, signaler: Signaler, - display: &mut Display, loop_handle: &mut LoopHandle<'static, State>, ) -> Result { let drm = &mut *self.drm.as_source_mut(); @@ -446,10 +452,14 @@ impl Device { let vrr = drm_helpers::set_vrr(drm, crtc, conn, true).unwrap_or(false); let interface = drm_helpers::interface_name(drm, conn)?; let edid_info = drm_helpers::edid_info(drm, conn)?; - let mode = crtc_info.mode().unwrap_or_else(|| - conn_info.modes().iter().find(|mode| mode.mode_type().contains(ModeTypeFlags::PREFERRED)) - .copied().unwrap_or(conn_info.modes()[0]) - ); + let mode = crtc_info.mode().unwrap_or_else(|| { + conn_info + .modes() + .iter() + .find(|mode| mode.mode_type().contains(ModeTypeFlags::PREFERRED)) + .copied() + .unwrap_or(conn_info.modes()[0]) + }); let refresh_rate = drm_helpers::calculate_refresh_rate(mode); let mut surface = drm.create_surface(crtc, mode, &[conn])?; surface.link(signaler); @@ -464,8 +474,7 @@ impl Device { refresh: refresh_rate as i32, }; let (phys_w, phys_h) = conn_info.size().unwrap_or((0, 0)); - let (output, output_global) = Output::new( - display, + let output = Output::new( interface, PhysicalProperties { size: (phys_w as i32, phys_h as i32).into(), @@ -476,7 +485,15 @@ impl Device { }, None, ); - output.set_preferred(output_mode.clone()); + for mode in conn_info.modes() { + let refresh_rate = drm_helpers::calculate_refresh_rate(*mode); + let mode = OutputMode { + size: (mode.size().0 as i32, mode.size().1 as i32).into(), + refresh: refresh_rate as i32, + }; + output.add_mode(mode); + } + output.set_preferred(output_mode); output.change_current_state( Some(output_mode), // TODO: Readout property for monitor rotation @@ -486,10 +503,7 @@ impl Device { ); output.user_data().insert_if_missing(|| { RefCell::new(OutputConfig { - mode: ( - (output_mode.size.w, output_mode.size.h), - Some(format!("{}x{}@{}", mode.size().0, mode.size().1, refresh_rate)), - ), + mode: ((output_mode.size.w, output_mode.size.h), Some(refresh_rate)), vrr, ..Default::default() }) @@ -519,8 +533,8 @@ impl Device { let data = Surface { output: output.clone(), - _global: output_global.into(), - surface: target, + surface: Some(target), + connector: conn, vrr, refresh_rate, last_submit: None, @@ -545,6 +559,10 @@ impl Surface { target_node: &DrmNode, state: &mut Common, ) -> Result<()> { + if self.surface.is_none() { + return Ok(()); + } + let nodes = state .shell .active_space(&self.output) @@ -578,8 +596,8 @@ impl Surface { let mut renderer = api.renderer(render_node, &target_node).unwrap(); - let (buffer, age) = self - .surface + let surface = self.surface.as_mut().unwrap(); + let (buffer, age) = surface .next_buffer() .with_context(|| "Failed to allocate buffer")?; @@ -598,12 +616,12 @@ impl Surface { &mut self.fps, ) { Ok(_) => { - self.surface + surface .queue_buffer() .with_context(|| "Failed to submit buffer for display")?; } Err(err) => { - self.surface.reset_buffers(); + surface.reset_buffers(); anyhow::bail!("Rendering failed: {}", err); } }; @@ -615,67 +633,107 @@ impl KmsState { pub fn apply_config_for_output( &mut self, output: &Output, - ) -> Result<(), Box> { - if let Some(device) = self + config: &mut Config, + shell: &mut Shell, + test_only: bool, + ) -> Result<(), anyhow::Error> { + let recreated = if let Some(device) = self .devices .values_mut() .find(|dev| dev.surfaces.values().any(|s| s.output == *output)) { - let mut surface = device + let (crtc, mut surface) = device .surfaces - .values_mut() - .find(|s| s.output == *output) + .iter_mut() + .find(|(_, s)| s.output == *output) .unwrap(); - let config = output + let output_config = output .user_data() .get::>() .unwrap() .borrow(); - let conn_info = device.drm.as_source_ref().get_connector( - surface - .surface - .current_connectors() - .into_iter() - .next() - .unwrap_or_else( - || surface.surface - .pending_connectors() - .into_iter() - .next() - .unwrap() - ), - )?; - let mode = conn_info - .modes() - .iter() - .find(|mode| { - let refresh_rate = drm_helpers::calculate_refresh_rate(**mode); - format!("{}x{}@{}", mode.size().0, mode.size().1, refresh_rate) - == *config.mode.1.as_ref().unwrap() - }) - .ok_or(anyhow::anyhow!("Unknown mode"))?; - surface.surface.use_mode(*mode).unwrap(); + if !output_config.enabled { + if !test_only { + if surface.surface.take().is_some() { + // just drop it + shell.disable_output(output, config); + } + } + false + } else { + let drm = &mut *device.drm.as_source_mut(); + let conn = surface.connector; + let conn_info = drm.get_connector(conn)?; + let mode = conn_info + .modes() + .iter() + .min_by_key(|mode| { + let refresh_rate = drm_helpers::calculate_refresh_rate(**mode); + (output_config.mode.1.unwrap() as i32 - refresh_rate as i32).abs() + }) + .ok_or(anyhow::anyhow!("Unknown mode"))?; - if config.vrr != surface.vrr { - surface.vrr = drm_helpers::set_vrr( - &*device.drm.as_source_ref(), - surface.surface.crtc(), - conn_info.handle(), - config.vrr, - )?; + if !test_only { + if let Some(gbm_surface) = surface.surface.as_mut() { + if output_config.vrr != surface.vrr { + surface.vrr = drm_helpers::set_vrr( + drm, + *crtc, + conn_info.handle(), + output_config.vrr, + )?; + } + gbm_surface.use_mode(*mode).unwrap(); + false + } else { + surface.vrr = drm_helpers::set_vrr(drm, *crtc, conn, output_config.vrr) + .unwrap_or(false); + surface.refresh_rate = drm_helpers::calculate_refresh_rate(*mode); + let mut drm_surface = drm.create_surface(*crtc, *mode, &[conn])?; + drm_surface.link(self.signaler.clone()); + + let target = GbmBufferedSurface::new( + drm_surface, + device.allocator.clone(), + device.formats.clone(), + None, + ) + .with_context(|| { + format!( + "Failed to initialize Gbm surface for {}", + drm_helpers::interface_name(drm, conn) + .unwrap_or_else(|_| String::from("Unknown")) + ) + })?; + surface.surface = Some(target); + shell.enable_output(output, config); + true + } + } else { + false + } } + } else { + false + }; + + if recreated { + self.schedule_render(output); } Ok(()) } pub fn schedule_render(&mut self, output: &Output) { - if let Some((device, surface)) = self + if let Some((device, crtc, surface)) = self .devices .iter_mut() - .flat_map(|(node, d)| d.surfaces.values_mut().map(move |s| (node, s))) - .find(|(_, s)| s.output == *output) + .flat_map(|(node, d)| d.surfaces.iter_mut().map(move |(c, s)| (node, c, s))) + .find(|(_, _, s)| s.output == *output) { + if surface.surface.is_none() { + return; + } if !surface.pending { surface.pending = true; let duration = surface @@ -688,7 +746,7 @@ impl KmsState { DrmEventTime::Realtime(time) => time.duration_since(SystemTime::now()).ok(), }) .unwrap_or(Duration::ZERO); // + Duration::from_secs_f64((1.0 / surface.refresh_rate as f64) - 20.0); - let data = (*device, surface.surface.crtc()); + let data = (*device, *crtc); if surface.vrr { surface.render_timer.add_timeout(Duration::ZERO, data); } else { diff --git a/src/backend/winit.rs b/src/backend/winit.rs index 4a95062f..f3ab4e94 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -72,6 +72,32 @@ impl WinitState { Ok(()) } + + pub fn apply_config_for_output( + &mut self, + output: &Output, + test_only: bool, + ) -> Result<(), anyhow::Error> { + // TODO: if we ever have multiple winit outputs, don't ignore config.enabled + // reset size + let size = self.backend.borrow().window_size(); + let mut config = output + .user_data() + .get::>() + .unwrap() + .borrow_mut(); + if dbg!(config.mode.0) != dbg!((size.physical_size.w as i32, size.physical_size.h as i32)) { + if !test_only { + config.mode = ( + (size.physical_size.w as i32, size.physical_size.h as i32), + None, + ); + } + Err(anyhow::anyhow!("Cannot set window size")) + } else { + Ok(()) + } + } } pub fn init_backend(event_loop: &mut EventLoop, state: &mut State) -> Result<()> { @@ -93,7 +119,7 @@ pub fn init_backend(event_loop: &mut EventLoop, state: &mut State) -> Res size: (size.physical_size.w as i32, size.physical_size.h as i32).into(), refresh: 60_000, }; - let (output, _global) = Output::new(&mut *state.common.display.borrow_mut(), name, props, None); + let output = Output::new(name, props, None); //let _global = global.into(); output.change_current_state( Some(mode), @@ -146,7 +172,7 @@ pub fn init_backend(event_loop: &mut EventLoop, state: &mut State) -> Res state.common.shell.unmap_output( &output, &mut state.backend, - &state.common.config, + &mut state.common.config, ); if let Some(token) = token.take() { event_loop_handle.remove(token); @@ -163,6 +189,11 @@ pub fn init_backend(event_loop: &mut EventLoop, state: &mut State) -> Res #[cfg(feature = "debug")] fps: Fps::default(), }); + state.common.output_conf.add_heads(std::iter::once(&output)); + state + .common + .output_conf + .update(&mut *state.common.display.borrow_mut()); state .common .shell @@ -224,10 +255,22 @@ impl State { size, refresh: 60_000, }; + + { + let mut config = output + .user_data() + .get::>() + .unwrap() + .borrow_mut(); + config.mode.0 = size.into(); + } output.delete_mode(output.current_mode().unwrap()); - output.change_current_state(Some(mode), None, None, None); output.set_preferred(mode); + output.change_current_state(Some(mode), None, None, None); layer_map_for_output(output).arrange(); + self.common + .output_conf + .update(&mut *self.common.display.borrow_mut()); render_ping.ping(); } WinitEvent::Refresh => render_ping.ping(), diff --git a/src/backend/x11.rs b/src/backend/x11.rs index c1c64e4e..f04bc7a1 100644 --- a/src/backend/x11.rs +++ b/src/backend/x11.rs @@ -5,7 +5,6 @@ use crate::{ config::OutputConfig, input::{set_active_output, Devices}, state::{BackendData, Common, State}, - utils::GlobalDrop, }; use anyhow::{Context, Result}; use smithay::{ @@ -20,10 +19,7 @@ use smithay::{ reexports::{ calloop::{ping, EventLoop, LoopHandle}, gbm::{Device as GbmDevice, FdWrapper}, - wayland_server::{ - protocol::wl_output::{Subpixel, WlOutput}, - Display, - }, + wayland_server::{protocol::wl_output::Subpixel, Display}, }, wayland::{ dmabuf::init_dmabuf_global, @@ -48,11 +44,7 @@ pub struct X11State { } impl X11State { - pub fn add_window( - &mut self, - display: &mut Display, - handle: LoopHandle<'_, State>, - ) -> Result { + pub fn add_window(&mut self, handle: LoopHandle<'_, State>) -> Result { let window = WindowBuilder::new() .title("COSMIC") .build(&self.handle) @@ -83,8 +75,7 @@ impl X11State { size: (size.w as i32, size.h as i32).into(), refresh: 60_000, }; - let (output, global) = Output::new(display, name, props, None); - let _global = global.into(); + let output = Output::new(name, props, None); output.change_current_state(Some(mode), None, None, Some((0, 0).into())); output.set_preferred(mode); output.user_data().insert_if_missing(|| { @@ -125,7 +116,6 @@ impl X11State { pending: true, #[cfg(feature = "debug")] fps: Fps::default(), - _global, }); // schedule first render @@ -141,6 +131,35 @@ impl X11State { } } } + + pub fn apply_config_for_output( + &mut self, + output: &Output, + test_only: bool, + ) -> Result<(), anyhow::Error> { + // TODO: if we ever have multiple winit outputs, don't ignore config.enabled + // reset size + let size = self + .surfaces + .iter() + .find(|s| s.output == *output) + .unwrap() + .window + .size(); + let mut config = output + .user_data() + .get::>() + .unwrap() + .borrow_mut(); + if config.mode.0 != (size.w as i32, size.h as i32) { + if !test_only { + config.mode = ((size.w as i32, size.h as i32), None); + } + Err(anyhow::anyhow!("Cannot set window size")) + } else { + Ok(()) + } + } } pub struct Surface { @@ -152,7 +171,6 @@ pub struct Surface { pending: bool, #[cfg(feature = "debug")] fps: Fps, - _global: GlobalDrop, } impl Surface { @@ -234,8 +252,13 @@ pub fn init_backend(event_loop: &mut EventLoop, state: &mut State) -> Res let output = state .backend .x11() - .add_window(&mut *state.common.display.borrow_mut(), event_loop.handle()) + .add_window(event_loop.handle()) .with_context(|| "Failed to create wl_output")?; + state.common.output_conf.add_heads(std::iter::once(&output)); + state + .common + .output_conf + .update(&mut *state.common.display.borrow_mut()); state .common .shell @@ -266,7 +289,7 @@ pub fn init_backend(event_loop: &mut EventLoop, state: &mut State) -> Res state.common.shell.unmap_output( &output, &mut state.backend, - &state.common.config, + &mut state.common.config, ); } } @@ -287,10 +310,23 @@ pub fn init_backend(event_loop: &mut EventLoop, state: &mut State) -> Res .find(|s| s.window.id() == window_id) { let output = &surface.output; + { + let mut config = output + .user_data() + .get::>() + .unwrap() + .borrow_mut(); + config.mode.0 = size.into(); + } + output.delete_mode(output.current_mode().unwrap()); output.change_current_state(Some(mode), None, None, None); output.set_preferred(mode); layer_map_for_output(output).arrange(); + state + .common + .output_conf + .update(&mut *state.common.display.borrow_mut()); surface.dirty = true; if !surface.pending { surface.render.ping(); diff --git a/src/config.rs b/src/config.rs index 5ae69cf9..aacc5425 100644 --- a/src/config.rs +++ b/src/config.rs @@ -52,14 +52,20 @@ impl From for OutputInfo { } } +fn default_enabled() -> bool { + true +} + #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] pub struct OutputConfig { - pub mode: ((i32, i32), Option), + pub mode: ((i32, i32), Option), pub vrr: bool, pub scale: f64, #[serde(with = "TransformDef")] pub transform: Transform, pub position: (i32, i32), + #[serde(default = "default_enabled")] + pub enabled: bool, } impl Default for OutputConfig { @@ -70,6 +76,7 @@ impl Default for OutputConfig { scale: 1.0, transform: Transform::Normal, position: (0, 0), + enabled: true, } } } @@ -79,14 +86,8 @@ impl OutputConfig { self.mode.0.into() } - pub fn mode_refresh(&self) -> i32 { - self.mode - .1 - .as_deref() - .and_then(|x| x.split("@").nth(1)) - .and_then(|x| str::parse::(x).ok()) - .map(|x| x.round() as i32) - .unwrap_or(60) + pub fn mode_refresh(&self) -> u32 { + self.mode.1.unwrap_or(60_000) } } diff --git a/src/shell/layout/floating/mod.rs b/src/shell/layout/floating/mod.rs index eba979fa..5260d442 100644 --- a/src/shell/layout/floating/mod.rs +++ b/src/shell/layout/floating/mod.rs @@ -143,11 +143,7 @@ impl FloatingLayout { Default::default() } - fn map_window( - space: &mut Space, - window: &Window, - output: &Output, - ) { + fn map_window(space: &mut Space, window: &Window, output: &Output) { let win_geo = window.bbox(); let layers = layer_map_for_output(&output); let geometry = layers.non_exclusive_zone(); diff --git a/src/shell/mod.rs b/src/shell/mod.rs index a9ac6a2f..4aff0cfd 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -92,43 +92,14 @@ impl Shell { } } - fn apply_config( - output: &Output, - backend: &mut BackendData, - ) -> Result<(), Box> { - backend.apply_config_for_output(output)?; - - let final_config = output - .user_data() - .get::>() - .unwrap() - .borrow(); - let mode = Some(OutputMode { - size: final_config.mode_size(), - refresh: final_config.mode_refresh(), - }) - .filter(|m| match output.current_mode() { - None => true, - Some(c_m) => m.size != c_m.size || m.refresh != c_m.refresh, - }); - let transform = - Some(final_config.transform.into()).filter(|x| *x != output.current_transform()); - let scale = Some(final_config.scale.ceil() as i32).filter(|x| *x != output.current_scale()); - let location = - Some(final_config.position.into()).filter(|x| *x != output.current_location()); - output.change_current_state(mode, transform, scale, location); - - Ok(()) - } - - fn refresh_config(&mut self, backend: &mut BackendData, config: &Config) -> bool { + fn refresh_config(&mut self, backend: &mut BackendData, config: &mut Config) -> bool { let mut infos = self .outputs() .cloned() .map(Into::::into) .collect::>(); infos.sort(); - if let Some(configs) = config.dynamic_conf.outputs().config.get(&infos) { + if let Some(configs) = config.dynamic_conf.outputs().config.get(&infos).cloned() { let mut reset = false; let known_good_configs = self .outputs @@ -143,18 +114,20 @@ impl Shell { }) .collect::>(); - for (name, config) in infos - .iter() - .map(|o| &o.connector) - .zip(configs.into_iter().cloned()) + for (name, output_config) in infos.iter().map(|o| &o.connector).zip(configs.into_iter()) { - let output = self.outputs.iter().find(|o| &o.name() == name).unwrap(); + let output = self + .outputs + .iter() + .find(|o| &o.name() == name) + .unwrap() + .clone(); *output .user_data() .get::>() .unwrap() - .borrow_mut() = config; - if let Err(err) = Self::apply_config(output, backend) { + .borrow_mut() = output_config; + if let Err(err) = backend.apply_config_for_output(&output, false, config, self) { slog_scope::warn!( "Failed to set new config for output {}: {}", output.name(), @@ -166,13 +139,19 @@ impl Shell { } if reset { - for (output, config) in self.outputs.iter().zip(known_good_configs.into_iter()) { + for (output, output_config) in self + .outputs + .clone() + .into_iter() + .zip(known_good_configs.into_iter()) + { *output .user_data() .get::>() .unwrap() - .borrow_mut() = config; - if let Err(err) = Self::apply_config(output, backend) { + .borrow_mut() = output_config; + if let Err(err) = backend.apply_config_for_output(&output, false, config, self) + { slog_scope::error!( "Failed to reset config for output {}: {}", output.name(), @@ -212,6 +191,30 @@ impl Shell { } } + pub fn save_config(&self, config: &mut Config) { + let mut infos = self + .outputs() + .cloned() + .map(|o| { + ( + Into::::into(o.clone()), + o.user_data() + .get::>() + .unwrap() + .borrow() + .clone(), + ) + }) + .collect::>(); + infos.sort_by(|&(ref a, _), &(ref b, _)| a.cmp(b)); + let (infos, configs) = infos.into_iter().unzip(); + config + .dynamic_conf + .outputs_mut() + .config + .insert(infos, configs); + } + fn assign_next_free_output<'a>( spaces: &'a mut [Workspace], output: &Output, @@ -233,9 +236,49 @@ impl Shell { workspace } + fn add_output(&mut self, output: &Output) { + let config = output + .user_data() + .get::>() + .unwrap() + .borrow(); + + match self.mode { + Mode::OutputBound => { + let workspace = Self::assign_next_free_output(&mut self.spaces, output); + workspace.space.map_output(output, config.scale, (0, 0)); + } + Mode::Global { active } => { + let workspace = &mut self.spaces[active]; + workspace + .space + .map_output(output, config.scale, config.position); + } + } + } + + fn remove_output(&mut self, output: &Output) { + match self.mode { + Mode::OutputBound => { + if let Some(idx) = output + .user_data() + .get::() + .and_then(|a| a.get()) + { + self.spaces[idx].space.unmap_output(output); + self.outputs.retain(|o| o != output); + } + } + Mode::Global { active } => { + self.spaces[active].space.unmap_output(output); + self.outputs.retain(|o| o != output); + // TODO move windows and outputs farther on the right / or load save config for remaining monitors + } + } + } + pub fn map_output(&mut self, output: &Output, backend: &mut BackendData, config: &mut Config) { self.outputs.push(output.clone()); - if !self.refresh_config(backend, config) { let new_pos_x = self .outputs() @@ -267,64 +310,32 @@ impl Shell { .position = new_pos; output.change_current_state(None, None, None, Some(new_pos.into())); - match self.mode { - Mode::OutputBound => { - let workspace = Self::assign_next_free_output(&mut self.spaces, output); - workspace.space.map_output(output, 1.0, (0, 0)); - } - Mode::Global { active } => { - // just put new outputs on the right of the previous ones. - // in the future we will only need that as a fallback and need to read saved configurations here - let workspace = &mut self.spaces[active]; - workspace.space.map_output(output, 1.0, new_pos); - } - } - - let mut infos = self - .outputs() - .cloned() - .map(|o| { - ( - Into::::into(o.clone()), - o.user_data() - .get::>() - .unwrap() - .borrow() - .clone(), - ) - }) - .collect::>(); - infos.sort_by(|&(ref a, _), &(ref b, _)| a.cmp(b)); - let (infos, configs) = infos.into_iter().unzip(); - config - .dynamic_conf - .outputs_mut() - .config - .insert(infos, configs); + self.add_output(output); + self.save_config(config); } } - pub fn unmap_output(&mut self, output: &Output, backend: &mut BackendData, config: &Config) { - match self.mode { - Mode::OutputBound => { - if let Some(idx) = output - .user_data() - .get::() - .and_then(|a| a.get()) - { - self.spaces[idx].space.unmap_output(output); - self.outputs.retain(|o| o != output); - } - } - Mode::Global { active } => { - self.spaces[active].space.unmap_output(output); - self.outputs.retain(|o| o != output); - // TODO move windows and outputs farther on the right / or load save config for remaining monitors - } - } + pub fn unmap_output( + &mut self, + output: &Output, + backend: &mut BackendData, + config: &mut Config, + ) { + self.remove_output(output); self.refresh_config(backend, config); } + pub fn enable_output(&mut self, output: &Output, config: &mut Config) { + self.outputs.push(output.clone()); + self.add_output(output); + self.save_config(config); + } + + pub fn disable_output(&mut self, output: &Output, config: &mut Config) { + self.remove_output(output); + self.save_config(config); + } + pub fn output_size(&self, output: &Output) -> Size { let workspace = self.active_space(output); workspace diff --git a/src/state.rs b/src/state.rs index 0cec8eb7..78163dc7 100644 --- a/src/state.rs +++ b/src/state.rs @@ -2,7 +2,7 @@ use crate::{ backend::{kms::KmsState, winit::WinitState, x11::X11State}, - config::Config, + config::{Config, OutputConfig}, logger::LogState, shell::{init_shell, Shell}, }; @@ -13,7 +13,13 @@ use smithay::{ }, wayland::{ data_device::{default_action_chooser, init_data_device, DataDeviceEvent}, - output::{xdg::init_xdg_output_manager, Output}, + output::{ + wlr_configuration::{ + self, init_wlr_output_configuration, ConfigurationManager, ModeConfiguration, + }, + xdg::init_xdg_output_manager, + Mode as OutputMode, Output, + }, seat::Seat, shell::xdg::ToplevelSurface, shm::init_shm_global, @@ -42,6 +48,7 @@ pub struct Common { pub socket: OsString, pub event_loop_handle: LoopHandle<'static, State>, + pub output_conf: ConfigurationManager, pub shell: Shell, pub pending_toplevels: Vec, pub dirty_flag: Arc, @@ -108,14 +115,45 @@ impl BackendData { pub fn apply_config_for_output( &mut self, output: &Output, - ) -> Result<(), Box> { - match self { - BackendData::Kms(ref mut state) => state.apply_config_for_output(output), - _ => { - // TODO: reset the mode for nested backends, because we have no control over it - Ok(()) + test_only: bool, + config: &mut Config, + shell: &mut Shell, + ) -> Result<(), anyhow::Error> { + let result = match self { + BackendData::Kms(ref mut state) => { + state.apply_config_for_output(output, config, shell, test_only) } + BackendData::Winit(ref mut state) => state.apply_config_for_output(output, test_only), + BackendData::X11(ref mut state) => state.apply_config_for_output(output, test_only), + _ => unreachable!("No backend set when applying output config"), + }; + + if result.is_ok() { + // apply to Output + let final_config = output + .user_data() + .get::>() + .unwrap() + .borrow(); + let mode = Some(OutputMode { + size: final_config.mode_size(), + refresh: final_config.mode_refresh() as i32, + }) + .filter(|m| match output.current_mode() { + None => true, + Some(c_m) => m.size != c_m.size || m.refresh != c_m.refresh, + }); + let transform = + Some(final_config.transform.into()).filter(|x| *x != output.current_transform()); + let scale = + Some(final_config.scale.ceil() as i32).filter(|x| *x != output.current_scale()); + let location = + Some(final_config.position.into()).filter(|x| *x != output.current_location()); + output.change_current_state(mode, transform, scale, location); + shell.save_config(config); } + + result } pub fn schedule_render(&mut self, output: &Output) { @@ -176,6 +214,108 @@ impl State { default_action_chooser, None, ); + let (output_conf, _) = init_wlr_output_configuration( + &mut display, + |_| true, + |conf, test_only, mut ddata| { + let state = ddata.get::().unwrap(); + if conf.iter().all(|(_, conf)| conf.is_none()) { + return false; // we don't allow the user to accidentally disable all their outputs + } + + let mut backups = Vec::new(); + for (output, conf) in &conf { + { + let mut current_config = output + .user_data() + .get::>() + .unwrap() + .borrow_mut(); + backups.push((output, current_config.clone())); + + if let Some(conf) = conf { + match conf.mode { + Some(ModeConfiguration::Mode(mode)) => { + current_config.mode = + ((mode.size.w, mode.size.h), Some(mode.refresh as u32)); + } + Some(ModeConfiguration::Custom { size, refresh }) => { + current_config.mode = + ((size.w, size.h), refresh.map(|x| x as u32)); + } + _ => {} + } + if let Some(scale) = conf.scale { + current_config.scale = scale; + } + if let Some(transform) = conf.transform { + current_config.transform = transform; + } + if let Some(position) = conf.position { + current_config.position = position.into(); + } + current_config.enabled = true; + } else { + current_config.enabled = false; + } + } + + if let Err(err) = state.backend.apply_config_for_output( + output, + test_only, + &mut state.common.config, + &mut state.common.shell, + ) { + slog_scope::warn!( + "Failed to apply config to {}: {}. Resetting", + output.name(), + err + ); + for (output, backup) in backups { + { + let mut current_config = output + .user_data() + .get::>() + .unwrap() + .borrow_mut(); + *current_config = backup; + } + if !test_only { + if let Err(err) = state.backend.apply_config_for_output( + output, + false, + &mut state.common.config, + &mut state.common.shell, + ) { + slog_scope::error!( + "Failed to reset output config for {}: {}", + output.name(), + err + ); + } + } + } + return false; + } + } + + for output in conf.iter().filter(|(_, c)| c.is_some()).map(|(o, _)| o) { + wlr_configuration::enable_head(output); + } + for output in conf.iter().filter(|(_, c)| c.is_none()).map(|(o, _)| o) { + wlr_configuration::disable_head(output); + } + state.common.event_loop_handle.insert_idle(move |state| { + state + .common + .output_conf + .update(&mut *state.common.display.borrow_mut()); + }); + + true + }, + None, + ); #[cfg(not(feature = "debug"))] let dirty_flag = Arc::new(AtomicBool::new(false)); @@ -189,6 +329,7 @@ impl State { socket, event_loop_handle: handle, + output_conf, shell, pending_toplevels: Vec::new(), dirty_flag,