From ec861fadd46b0ffc1acd8fa8cb8e75268a7b9add Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 5 Apr 2022 16:35:58 +0200 Subject: [PATCH] shell: load/update output configuration --- Cargo.lock | 14 +- src/backend/kms/mod.rs | 118 ++++++++++-- src/backend/winit.rs | 26 ++- src/backend/x11.rs | 22 ++- src/config.rs | 47 ++++- src/input/mod.rs | 3 +- src/shell/layout/floating/mod.rs | 75 +++++--- src/shell/layout/mod.rs | 6 +- src/shell/layout/tiling/mod.rs | 6 +- src/shell/mod.rs | 307 ++++++++++++++++++++++--------- src/state.rs | 9 +- 11 files changed, 481 insertions(+), 152 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 838ba79e..ed2a030e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -349,9 +349,9 @@ checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" [[package]] name = "darling" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0d720b8683f8dd83c65155f0530560cba68cd2bf395f6513a483caee57ff7f4" +checksum = "4e92cb285610dd935f60ee8b4d62dd1988bd12b7ea50579bd6a138201525318e" dependencies = [ "darling_core", "darling_macro", @@ -359,9 +359,9 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a340f241d2ceed1deb47ae36c4144b2707ec7dd0b649f894cb39bb595986324" +checksum = "5c29e95ab498b18131ea460b2c0baa18cbf041231d122b0b7bfebef8c8e88989" dependencies = [ "fnv", "ident_case", @@ -373,9 +373,9 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c41b3b7352feb3211a0d743dc5700a4e3b60f51bd2b368892d1e0f9a95f44b" +checksum = "b21dd6b221dd547528bd6fb15f1a3b7ab03b9a06f76bff288a8c629bcfbe7f0e" dependencies = [ "darling_core", "quote", @@ -1311,7 +1311,7 @@ checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" [[package]] name = "smithay" version = "0.3.0" -source = "git+https://github.com/pop-os/smithay?branch=main#f3e6921298e061ed4289c68bbbec798a2534d132" +source = "git+https://github.com/pop-os/smithay?branch=main#d8bf081cb5d2c6c666daaf238e2a6360ca963716" dependencies = [ "appendlist", "bitflags", diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index 3e3f51ad..69cdc35e 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -5,6 +5,7 @@ use crate::state::Fps; use crate::{ backend::render, + config::OutputConfig, state::{BackendData, Common, State}, utils::GlobalDrop, }; @@ -28,7 +29,7 @@ use smithay::{ timer::{Timer, TimerHandle}, Dispatcher, EventLoop, LoopHandle, RegistrationToken, }, - drm::control::{connector, crtc, Device as ControlDevice}, + drm::control::{connector, crtc, Device as ControlDevice, ModeTypeFlags}, input::Libinput, nix::{fcntl::OFlag, sys::stat::dev_t}, wayland_server::{protocol::wl_output, Display}, @@ -301,7 +302,11 @@ impl State { &mut self.common.display.borrow_mut(), &mut self.common.event_loop_handle, ) { - Ok(output) => self.common.shell.map_output(&output, &mut self.backend, &self.common.config), + Ok(output) => self.common.shell.map_output( + &output, + &mut self.backend, + &mut self.common.config, + ), Err(err) => slog_scope::warn!("Failed to initialize output: {}", err), }; } @@ -313,6 +318,8 @@ impl State { fn device_changed(&mut self, dev: dev_t) -> Result<()> { let drm_node = DrmNode::from_dev_id(dev)?; let signaler = self.backend.kms().signaler.clone(); + let mut outputs_removed = Vec::new(); + let mut outputs_added = Vec::new(); if let Some(device) = self.backend.kms().devices.get_mut(&drm_node) { let changes = device.enumerate_surfaces()?; for crtc in changes.removed { @@ -320,7 +327,7 @@ impl State { if let Some(token) = surface.render_timer_token.take() { self.common.event_loop_handle.remove(token); } - self.common.shell.unmap_output(&surface.output); + outputs_removed.push(surface.output.clone()); } } for (crtc, conn) in changes.added { @@ -332,22 +339,36 @@ impl State { &mut self.common.display.borrow_mut(), &mut self.common.event_loop_handle, ) { - Ok(output) => self.common.shell.map_output(&output, &mut self.backend, &self.common.config), + Ok(output) => { + outputs_added.push(output); + } Err(err) => slog_scope::warn!("Failed to initialize output: {}", err), }; } } + + for output in outputs_removed.into_iter() { + self.common + .shell + .unmap_output(&output, &mut self.backend, &self.common.config); + } + for output in outputs_added.into_iter() { + self.common + .shell + .map_output(&output, &mut self.backend, &mut self.common.config) + } Ok(()) } fn device_removed(&mut self, dev: dev_t) -> Result<()> { let drm_node = DrmNode::from_dev_id(dev)?; - if let Some(device) = self.backend.kms().devices.get_mut(&drm_node) { + let mut outputs_removed = Vec::new(); + if let Some(mut device) = self.backend.kms().devices.remove(&drm_node) { for surface in device.surfaces.values_mut() { if let Some(token) = surface.render_timer_token.take() { self.common.event_loop_handle.remove(token); } - self.common.shell.unmap_output(&surface.output); + outputs_removed.push(surface.output.clone()); } if let Some(token) = device.event_token.take() { self.common.event_loop_handle.remove(token); @@ -356,6 +377,12 @@ impl State { self.common.event_loop_handle.remove(socket.token); } } + for output in outputs_removed.into_iter() { + self.common + .shell + .unmap_output(&output, &mut self.backend, &self.common.config); + } + Ok(()) } } @@ -419,7 +446,11 @@ 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(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); @@ -430,7 +461,7 @@ impl Device { //let is_nvidia = driver(drm.device_id()).ok().flatten().map(|x| x == "nvidia").unwrap_or(false); let output_mode = OutputMode { size: (mode.size().0 as i32, mode.size().1 as i32).into(), - refresh: (mode.vrefresh() * 1000) as i32, + refresh: refresh_rate as i32, }; let (phys_w, phys_h) = conn_info.size().unwrap_or((0, 0)); let (output, output_global) = Output::new( @@ -453,6 +484,16 @@ impl Device { None, None, ); + 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)), + ), + vrr, + ..Default::default() + }) + }); let timer = Timer::new()?; let timer_handle = timer.handle(); @@ -481,7 +522,7 @@ impl Device { _global: output_global.into(), surface: target, vrr, - refresh_rate: drm_helpers::calculate_refresh_rate(mode), + refresh_rate, last_submit: None, pending: true, render_timer: timer_handle, @@ -571,10 +612,63 @@ impl Surface { } impl KmsState { - pub fn apply_config_for_output(&mut self, output: &Output) -> Result<(), impl std::error::Error> { - + pub fn apply_config_for_output( + &mut self, + output: &Output, + ) -> Result<(), Box> { + if let Some(device) = self + .devices + .values_mut() + .find(|dev| dev.surfaces.values().any(|s| s.output == *output)) + { + let mut surface = device + .surfaces + .values_mut() + .find(|s| s.output == *output) + .unwrap(); + let 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 config.vrr != surface.vrr { + surface.vrr = drm_helpers::set_vrr( + &*device.drm.as_source_ref(), + surface.surface.crtc(), + conn_info.handle(), + config.vrr, + )?; + } + } + Ok(()) } - + pub fn schedule_render(&mut self, output: &Output) { if let Some((device, surface)) = self .devices diff --git a/src/backend/winit.rs b/src/backend/winit.rs index 7386e794..4a95062f 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -2,6 +2,7 @@ use crate::{ backend::render, + config::OutputConfig, input::{set_active_output, Devices}, state::{BackendData, Common, State}, }; @@ -101,6 +102,16 @@ pub fn init_backend(event_loop: &mut EventLoop, state: &mut State) -> Res Some((0, 0).into()), ); output.set_preferred(mode); + output.user_data().insert_if_missing(|| { + RefCell::new(OutputConfig { + mode: ( + (size.physical_size.w as i32, size.physical_size.h as i32), + None, + ), + transform: Transform::Flipped180.into(), + ..Default::default() + }) + }); let (event_ping, event_source) = ping::make_ping().with_context(|| "Failed to init eventloop timer for winit")?; @@ -131,8 +142,12 @@ pub fn init_backend(event_loop: &mut EventLoop, state: &mut State) -> Res render_ping_handle.ping(); } Err(winit::WinitError::WindowClosed) => { - let winit_state = state.backend.winit(); - state.common.shell.unmap_output(&winit_state.output); + let output = state.backend.winit().output.clone(); + state.common.shell.unmap_output( + &output, + &mut state.backend, + &state.common.config, + ); if let Some(token) = token.take() { event_loop_handle.remove(token); } @@ -144,11 +159,14 @@ pub fn init_backend(event_loop: &mut EventLoop, state: &mut State) -> Res state.backend = BackendData::Winit(WinitState { backend, - output, + output: output.clone(), #[cfg(feature = "debug")] fps: Fps::default(), }); - state.common.shell.map_output(&output, &mut state.backend, &state.common.config); + state + .common + .shell + .map_output(&output, &mut state.backend, &mut state.common.config); Ok(()) } diff --git a/src/backend/x11.rs b/src/backend/x11.rs index 61890890..c1c64e4e 100644 --- a/src/backend/x11.rs +++ b/src/backend/x11.rs @@ -2,6 +2,7 @@ use crate::{ backend::render, + config::OutputConfig, input::{set_active_output, Devices}, state::{BackendData, Common, State}, utils::GlobalDrop, @@ -86,6 +87,12 @@ impl X11State { let _global = global.into(); output.change_current_state(Some(mode), None, None, Some((0, 0).into())); output.set_preferred(mode); + output.user_data().insert_if_missing(|| { + RefCell::new(OutputConfig { + mode: ((size.w as i32, size.h as i32), None), + ..Default::default() + }) + }); let output_ref = output.clone(); let (ping, source) = @@ -229,13 +236,17 @@ pub fn init_backend(event_loop: &mut EventLoop, state: &mut State) -> Res .x11() .add_window(&mut *state.common.display.borrow_mut(), event_loop.handle()) .with_context(|| "Failed to create wl_output")?; - state.common.shell.map_output(&output, &mut state.backend, &state.common.config); + state + .common + .shell + .map_output(&output, &mut state.backend, &mut state.common.config); event_loop .handle() .insert_source(backend, |event, _, state| match event { X11Event::CloseRequested { window_id } => { // TODO: drain_filter + let mut outputs_removed = Vec::new(); for surface in state .backend .x11() @@ -244,13 +255,20 @@ pub fn init_backend(event_loop: &mut EventLoop, state: &mut State) -> Res .filter(|s| s.window.id() == window_id) { surface.window.unmap(); - state.common.shell.unmap_output(&surface.output); + outputs_removed.push(surface.output.clone()); } state .backend .x11() .surfaces .retain(|s| s.window.id() != window_id); + for output in outputs_removed.into_iter() { + state.common.shell.unmap_output( + &output, + &mut state.backend, + &state.common.config, + ); + } } X11Event::Resized { new_size, diff --git a/src/config.rs b/src/config.rs index 19da6d4d..5ae69cf9 100644 --- a/src/config.rs +++ b/src/config.rs @@ -5,11 +5,11 @@ use serde::{Deserialize, Serialize}; use smithay::wayland::seat::Keysym; pub use smithay::{ backend::input::KeyState, + utils::{Logical, Physical, Point, Size, Transform}, wayland::{ output::Output, seat::{keysyms as KeySyms, ModifiersState as KeyModifiers}, }, - utils::{Point, Size, Logical, Physical, Transform}, }; use std::{collections::HashMap, fs::OpenOptions, path::PathBuf}; use xkbcommon::xkb; @@ -57,18 +57,36 @@ pub struct OutputConfig { pub mode: ((i32, i32), Option), pub vrr: bool, pub scale: f64, - #[serde(with="TransformDef")] + #[serde(with = "TransformDef")] pub transform: Transform, pub position: (i32, i32), } +impl Default for OutputConfig { + fn default() -> OutputConfig { + OutputConfig { + mode: ((0, 0), None), + vrr: false, + scale: 1.0, + transform: Transform::Normal, + position: (0, 0), + } + } +} + impl OutputConfig { - fn mode_size(&self) -> Size { + pub fn mode_size(&self) -> Size { self.mode.0.into() } - fn mode_refresh(&self) -> i32 { - self.mode.1.as_deref().map(|x| str::parse::(x).unwrap().round() as i32).unwrap_or(60) + 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) } } @@ -128,7 +146,8 @@ impl Config { } fn load_dynamic(xdg: Option<&xdg::BaseDirectories>) -> DynamicConfig { - let output_path = xdg.and_then(|base| base.place_state_file("cosmic-comp/outputs.ron").ok()); + let output_path = + xdg.and_then(|base| base.place_state_file("cosmic-comp/outputs.ron").ok()); let outputs = Self::load_outputs(&output_path); DynamicConfig { @@ -143,7 +162,9 @@ impl Config { Ok(config) => return config, Err(err) => { slog_scope::warn!("Failed to read output_config ({}), resetting..", err); - std::fs::remove_file(path); + if let Err(err) = std::fs::remove_file(path) { + slog_scope::error!("Failed to remove output_config {}", err); + } } }; } @@ -173,13 +194,21 @@ impl<'a, T: Serialize> std::ops::DerefMut for PersistenceGuard<'a, T> { impl<'a, T: Serialize> Drop for PersistenceGuard<'a, T> { fn drop(&mut self) { if let Some(path) = self.0.as_ref() { - let writer = match OpenOptions::new().write(true).open(path) { + let writer = match OpenOptions::new() + .create(true) + .truncate(true) + .write(true) + .open(path) + { Ok(writer) => writer, Err(err) => { slog_scope::warn!("Failed to persist {}: {}", path.display(), err); + return; } }; - ron::ser::to_writer(writer, &self.1); + if let Err(err) = ron::ser::to_writer_pretty(writer, &self.1, Default::default()) { + slog_scope::warn!("Failed to persist {}: {}", path.display(), err); + } } } } diff --git a/src/input/mod.rs b/src/input/mod.rs index 20336037..e3109db1 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -237,7 +237,8 @@ impl Common { } // here we can handle global shortcuts and the like - for (binding, action) in self.config.static_conf.key_bindings.iter() { + for (binding, action) in self.config.static_conf.key_bindings.iter() + { if state == KeyState::Pressed && binding.modifiers == *modifiers && handle.raw_syms().contains(&binding.key) diff --git a/src/shell/layout/floating/mod.rs b/src/shell/layout/floating/mod.rs index e33b86c4..eba979fa 100644 --- a/src/shell/layout/floating/mod.rs +++ b/src/shell/layout/floating/mod.rs @@ -16,7 +16,10 @@ use smithay::{ mod grabs; pub use self::grabs::*; -pub struct FloatingLayout; +#[derive(Debug, Default)] +pub struct FloatingLayout { + pending_windows: Vec, +} impl Layout for FloatingLayout { fn map_window<'a>( @@ -26,36 +29,26 @@ impl Layout for FloatingLayout { seat: &Seat, _focus_stack: Box + 'a>, ) { - let output = super::output_from_seat(Some(seat), space); - let win_geo = window.bbox(); - let layers = layer_map_for_output(&output); - let geometry = layers.non_exclusive_zone(); - - let position = ( - -geometry.loc.x + (geometry.size.w / 2) - (win_geo.size.w / 2), - -geometry.loc.y + (geometry.size.h / 2) - (win_geo.size.h / 2), - ); - #[allow(irrefutable_let_patterns)] - if let Kind::Xdg(xdg) = &window.toplevel() { - let ret = xdg.with_pending_state(|state| { - state.states.unset(XdgState::TiledLeft); - state.states.unset(XdgState::TiledRight); - state.states.unset(XdgState::TiledTop); - state.states.unset(XdgState::TiledBottom); - }); - if ret.is_ok() { - xdg.send_configure(); - } + if let Some(output) = super::output_from_seat(Some(seat), space) { + Self::map_window(space, window, &output) + } else { + self.pending_windows.push(window.clone()); } - space.map_window(&window, position, false); } - fn refresh(&mut self, _space: &mut Space) { + fn refresh(&mut self, space: &mut Space) { + self.pending_windows.retain(|w| w.toplevel().alive()); + if let Some(output) = super::output_from_seat(None, space) { + for window in self.pending_windows.drain(..) { + Self::map_window(space, &window, &output); + } + } // TODO make sure all windows are still visible on any output or move them } fn unmap_window(&mut self, space: &mut Space, window: &Window) { - space.unmap_window(window) + space.unmap_window(window); + self.pending_windows.retain(|w| w != window); } fn maximize_request(&mut self, space: &mut Space, window: &Window, output: &Output) { @@ -144,3 +137,37 @@ impl Layout for FloatingLayout { } } } + +impl FloatingLayout { + pub fn new() -> FloatingLayout { + Default::default() + } + + 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(); + + let position = ( + -geometry.loc.x + (geometry.size.w / 2) - (win_geo.size.w / 2), + -geometry.loc.y + (geometry.size.h / 2) - (win_geo.size.h / 2), + ); + #[allow(irrefutable_let_patterns)] + if let Kind::Xdg(xdg) = &window.toplevel() { + let ret = xdg.with_pending_state(|state| { + state.states.unset(XdgState::TiledLeft); + state.states.unset(XdgState::TiledRight); + state.states.unset(XdgState::TiledTop); + state.states.unset(XdgState::TiledBottom); + }); + if ret.is_ok() { + xdg.send_configure(); + } + } + space.map_window(&window, position, false); + } +} diff --git a/src/shell/layout/mod.rs b/src/shell/layout/mod.rs index 4de5fa68..16838c25 100644 --- a/src/shell/layout/mod.rs +++ b/src/shell/layout/mod.rs @@ -90,7 +90,7 @@ pub trait Layout { pub fn new_default_layout() -> Box { Box::new(combined::Combined::new( tiling::TilingLayout::new(), - floating::FloatingLayout, + floating::FloatingLayout::new(), |window| { if let Some(surface) = window.toplevel().get_surface() { with_states(surface, |states| { @@ -128,7 +128,7 @@ pub fn new_default_layout() -> Box { )) } -fn output_from_seat(seat: Option<&Seat>, space: &Space) -> Output { +fn output_from_seat(seat: Option<&Seat>, space: &Space) -> Option { seat.and_then(|seat| { seat.user_data() .get::() @@ -139,5 +139,5 @@ fn output_from_seat(seat: Option<&Seat>, space: &Space) -> Output { .cloned() }) }) - .unwrap_or_else(|| space.outputs().next().cloned().unwrap()) + .or_else(|| space.outputs().next().cloned()) } diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index ff2456af..a93e028f 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -85,7 +85,7 @@ impl Layout for TilingLayout { focus_stack: Box + 'a>, ) -> Option { let output = super::output_from_seat(Some(seat), space); - let idx = space.outputs().position(|o| *o == output).unwrap_or(0); + let idx = space.outputs().position(|o| Some(o) == output.as_ref()).unwrap_or(0); let tree = TilingLayout::active_tree(&mut self.trees, idx); if let Some(last_active) = TilingLayout::last_active_window(tree, focus_stack) { let mut node_id = last_active; @@ -141,7 +141,7 @@ impl Layout for TilingLayout { focus_stack: Box + 'a>, ) { let output = super::output_from_seat(Some(seat), space); - let idx = space.outputs().position(|o| *o == output).unwrap_or(0); + let idx = space.outputs().position(|o| Some(o) == output.as_ref()).unwrap_or(0); let tree = TilingLayout::active_tree(&mut self.trees, idx); if let Some(last_active) = TilingLayout::last_active_window(tree, focus_stack) { if let Some((fork, _child)) = TilingLayout::find_fork(tree, last_active) { @@ -288,7 +288,7 @@ impl TilingLayout { focus_stack: Option + 'a>>, ) { let output = super::output_from_seat(seat, space); - let idx = space.outputs().position(|o| *o == output).unwrap_or(0); + let idx = space.outputs().position(|o| Some(o) == output.as_ref()).unwrap_or(0); let tree = TilingLayout::active_tree(&mut self.trees, idx); let new_window = Node::new(Data::Window(window.clone())); diff --git a/src/shell/mod.rs b/src/shell/mod.rs index cd0de213..a9ac6a2f 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::{ - config::{Config, OutputInfo, OutputConfig}, + config::{Config, OutputConfig, OutputInfo}, input::active_output, state::{BackendData, Common}, }; @@ -10,8 +10,11 @@ pub use smithay::{ reexports::wayland_server::protocol::wl_surface::WlSurface, utils::{Logical, Point, Rectangle, Size}, wayland::{ - compositor::with_states, output::{Mode as OutputMode, Output}, seat::Seat, - shell::xdg::XdgToplevelSurfaceRoleAttributes, Serial, SERIAL_COUNTER, + compositor::with_states, + output::{Mode as OutputMode, Output}, + seat::Seat, + shell::xdg::XdgToplevelSurfaceRoleAttributes, + Serial, SERIAL_COUNTER, }, }; use std::{ @@ -89,46 +92,74 @@ impl Shell { } } - fn apply_config(output: &Output, backend: &mut BackendData) -> Result<(), impl std::error::Error> { + 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 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() { + }) + .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 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, - ); + let location = + Some(final_config.position.into()).filter(|x| *x != output.current_location()); + output.change_current_state(mode, transform, scale, location); Ok(()) } - pub fn map_output(&mut self, output: &Output, backend: &mut BackendData, config: &Config) { - self.outputs.push(output.clone()); - - let mut infos = self.outputs().cloned().map(Into::::into).collect::>(); + fn refresh_config(&mut self, backend: &mut BackendData, config: &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) { let mut reset = false; - let known_good_configs = self.outputs.iter().map(|output| { - output.user_data().get::>().unwrap().borrow().clone() - }).collect::>(); + let known_good_configs = self + .outputs + .iter() + .map(|output| { + output + .user_data() + .get::>() + .unwrap() + .borrow() + .clone() + }) + .collect::>(); - for (name, config) in infos.iter().map(|o| &o.connector).zip(configs.into_iter().cloned()) { + for (name, config) in infos + .iter() + .map(|o| &o.connector) + .zip(configs.into_iter().cloned()) + { let output = self.outputs.iter().find(|o| &o.name() == name).unwrap(); - *output.user_data().get::>().unwrap().borrow_mut() = config; + *output + .user_data() + .get::>() + .unwrap() + .borrow_mut() = config; if let Err(err) = Self::apply_config(output, backend) { - slog_scope::warn!("Failed to set new config for output {}: {}", output.name(), err); + slog_scope::warn!( + "Failed to set new config for output {}: {}", + output.name(), + err + ); reset = true; break; } @@ -136,10 +167,17 @@ impl Shell { if reset { for (output, config) in self.outputs.iter().zip(known_good_configs.into_iter()) { - *output.user_data().get::>().unwrap().borrow_mut() = config; + *output + .user_data() + .get::>() + .unwrap() + .borrow_mut() = config; if let Err(err) = Self::apply_config(output, backend) { - slog_scope::error!("Failed to reset config for output {}: {}", output.name(), err); - self.unmap_output(output); + slog_scope::error!( + "Failed to reset config for output {}: {}", + output.name(), + err + ); } } } @@ -147,37 +185,91 @@ impl Shell { if let Mode::Global { active } = self.mode { let workspace = &mut self.spaces[active]; for output in self.outputs.iter() { - let config = output.user_data().get::>().unwrap().borrow(); - workspace.space.map_output(output, config.scale, config.position); + let config = output + .user_data() + .get::>() + .unwrap() + .borrow(); + workspace + .space + .map_output(output, config.scale, config.position); + } + } else { + for output in self.outputs.iter() { + let config = output + .user_data() + .get::>() + .unwrap() + .borrow(); + let workspace = Self::assign_next_free_output(&mut self.spaces, output); + workspace.space.map_output(output, config.scale, (0, 0)); } } + + true } else { - let new_pos_x = self.outputs().map(|o| { - let logical_size = self.active_space(o).space.output_geometry(o).map(|x| x.size).unwrap_or((0, 0).into()); - o.user_data().get::>().unwrap().borrow().position.0 + logical_size.w - }).max().unwrap_or(0); + false + } + } + + fn assign_next_free_output<'a>( + spaces: &'a mut [Workspace], + output: &Output, + ) -> &'a mut Workspace { + output + .user_data() + .insert_if_missing(|| ActiveWorkspace::new()); + let (idx, workspace) = spaces + .iter_mut() + .enumerate() + .find(|(_, x)| x.space.outputs().next().is_none()) + .expect("More then 10 outputs?"); + output + .user_data() + .get::() + .unwrap() + .set(idx); + + workspace + } + + 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() + .take(self.outputs.len() - 1) + .map(|o| { + let logical_size = self + .active_space(o) + .space + .output_geometry(o) + .map(|x| x.size) + .unwrap_or((0, 0).into()); + o.user_data() + .get::>() + .unwrap() + .borrow() + .position + .0 + + logical_size.w + }) + .max() + .unwrap_or(0); let new_pos = (new_pos_x, 0); - output.user_data().get::>().unwrap().borrow_mut().position = new_pos; + output + .user_data() + .get::>() + .unwrap() + .borrow_mut() + .position = new_pos; output.change_current_state(None, None, None, Some(new_pos.into())); match self.mode { Mode::OutputBound => { - output - .user_data() - .insert_if_missing(|| ActiveWorkspace::new()); - - let (idx, workspace) = self - .spaces - .iter_mut() - .enumerate() - .find(|(_, x)| x.space.outputs().next().is_none()) - .expect("More then 10 outputs?"); - output - .user_data() - .get::() - .unwrap() - .set(idx); + let workspace = Self::assign_next_free_output(&mut self.spaces, output); workspace.space.map_output(output, 1.0, (0, 0)); } Mode::Global { active } => { @@ -187,10 +279,32 @@ impl Shell { 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); } } - pub fn unmap_output(&mut self, output: &Output) { + pub fn unmap_output(&mut self, output: &Output, backend: &mut BackendData, config: &Config) { match self.mode { Mode::OutputBound => { if let Some(idx) = output @@ -208,6 +322,7 @@ impl Shell { // TODO move windows and outputs farther on the right / or load save config for remaining monitors } } + self.refresh_config(backend, config); } pub fn output_size(&self, output: &Output) -> Size { @@ -220,12 +335,16 @@ impl Shell { } pub fn global_space(&self) -> Rectangle { - let size = self.outputs.iter().fold((0, 0), |(w, h), output| { - let size = self.output_size(output); - (w + size.w, std::cmp::max(h, size.h)) - }); - - Rectangle::from_loc_and_size((0, 0), size) + self.outputs + .iter() + .fold( + Option::>::None, + |maybe_geo, output| match maybe_geo { + Some(rect) => Some(rect.merge(self.output_geometry(output))), + None => Some(self.output_geometry(output)), + }, + ) + .unwrap_or_else(|| Rectangle::from_loc_and_size((0, 0), (0, 0))) } pub fn space_relative_output_geometry( @@ -246,17 +365,12 @@ impl Shell { // due to our different modes, we cannot just ask the space for the global output coordinates, // because for `Mode::OutputBound` the origin will always be (0, 0) - // TODO: Add a proper grid like structure, for now the outputs just extend to the right - let pos = - self.outputs - .iter() - .take_while(|o| o != &output) - .fold((0, 0), |(x, y), output| { - let size = self.output_size(output); - (x + size.w, y) - }); - - Rectangle::from_loc_and_size(pos, self.output_size(output)) + let config = output + .user_data() + .get::>() + .unwrap() + .borrow(); + Rectangle::from_loc_and_size(config.position, self.output_size(output)) } pub fn activate(&mut self, seat: &Seat, output: &Output, idx: usize) { @@ -294,7 +408,14 @@ impl Shell { if let Some(old_idx) = active.set(idx) { self.spaces[old_idx].space.unmap_output(output); } - self.spaces[idx].space.map_output(output, 1.0, (0, 0)); + let config = output + .user_data() + .get::>() + .unwrap() + .borrow(); + self.spaces[idx] + .space + .map_output(output, config.scale, (0, 0)); self.spaces[idx].refresh(); } } @@ -302,9 +423,15 @@ impl Shell { let old = *active; *active = idx; for output in &self.outputs { - let loc = self.spaces[old].space.output_geometry(output).unwrap().loc; self.spaces[old].space.unmap_output(output); - self.spaces[*active].space.map_output(output, 1.0, loc); + let config = output + .user_data() + .get::>() + .unwrap() + .borrow(); + self.spaces[*active] + .space + .map_output(output, config.scale, config.position); } } }; @@ -347,7 +474,6 @@ impl Shell { .unwrap() }) .unwrap_or(0); - let mut x = 0; for output in &self.outputs { let old_active = output @@ -356,32 +482,45 @@ impl Shell { .unwrap() .clear() .unwrap(); - let width = self.spaces[old_active] - .space - .output_geometry(output) + let config = output + .user_data() + .get::>() .unwrap() - .size - .w; + .borrow(); self.spaces[old_active].space.unmap_output(output); - self.spaces[active].space.map_output(output, 1.0, (x, 0)); - x += width; + self.spaces[active] + .space + .map_output(output, config.scale, config.position); + self.spaces[active].refresh(); } self.mode = Mode::Global { active }; - // TODO move windows into new bounds } (Mode::Global { active }, new @ Mode::OutputBound) => { for output in &self.outputs { self.spaces[*active].space.unmap_output(output); } + let mut active = Some(active.clone()); self.mode = new; - let outputs = self.outputs.drain(..).collect::>(); - for output in &outputs { - self.map_output(output); + for output in &self.outputs { + let config = output + .user_data() + .get::>() + .unwrap() + .borrow(); + let workspace = if let Some(a) = active.take() { + output + .user_data() + .insert_if_missing(|| ActiveWorkspace::new()); + output.user_data().get::().unwrap().set(a); + &mut self.spaces[a] + } else { + Self::assign_next_free_output(&mut self.spaces, output) + }; + workspace.space.map_output(output, config.scale, (0, 0)); + workspace.refresh(); } - // TODO move windows into new bounds - // TODO active should probably be mapped somewhere } _ => {} }; diff --git a/src/state.rs b/src/state.rs index 2d2f6b78..0cec8eb7 100644 --- a/src/state.rs +++ b/src/state.rs @@ -2,7 +2,7 @@ use crate::{ backend::{kms::KmsState, winit::WinitState, x11::X11State}, - config::{Config, OutputConfig}, + config::Config, logger::LogState, shell::{init_shell, Shell}, }; @@ -105,13 +105,16 @@ impl BackendData { } } - pub fn apply_config_for_output(&mut self, output: &Output) -> Result<(), impl std::error::Error> { + 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(()) - }, + } } }