From a9d6b8c3d76888c739e990723c8d08315f5c55c2 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Wed, 30 Mar 2022 13:47:06 +0200 Subject: [PATCH] shell: read in config, if available --- Cargo.lock | 2 +- src/backend/kms/mod.rs | 8 ++- src/backend/winit.rs | 3 +- src/backend/x11.rs | 2 +- src/config.rs | 160 +++++++++++++++++++++++++++++++++++++++-- src/input/mod.rs | 2 +- src/shell/mod.rs | 133 +++++++++++++++++++++++++--------- src/state.rs | 12 +++- 8 files changed, 276 insertions(+), 46 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7e5ae527..838ba79e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1311,7 +1311,7 @@ checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" [[package]] name = "smithay" version = "0.3.0" -source = "git+https://github.com/pop-os/smithay?branch=main#bca8fab55e41474a6466ade0ae5073c0ecc57ccd" +source = "git+https://github.com/pop-os/smithay?branch=main#f3e6921298e061ed4289c68bbbec798a2534d132" dependencies = [ "appendlist", "bitflags", diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index cf0f6cca..3e3f51ad 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -301,7 +301,7 @@ impl State { &mut self.common.display.borrow_mut(), &mut self.common.event_loop_handle, ) { - Ok(output) => self.common.shell.map_output(&output), + Ok(output) => self.common.shell.map_output(&output, &mut self.backend, &self.common.config), Err(err) => slog_scope::warn!("Failed to initialize output: {}", err), }; } @@ -332,7 +332,7 @@ impl State { &mut self.common.display.borrow_mut(), &mut self.common.event_loop_handle, ) { - Ok(output) => self.common.shell.map_output(&output), + Ok(output) => self.common.shell.map_output(&output, &mut self.backend, &self.common.config), Err(err) => slog_scope::warn!("Failed to initialize output: {}", err), }; } @@ -571,6 +571,10 @@ impl Surface { } impl KmsState { + pub fn apply_config_for_output(&mut self, output: &Output) -> Result<(), impl std::error::Error> { + + } + 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 22a96c74..7386e794 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -102,8 +102,6 @@ pub fn init_backend(event_loop: &mut EventLoop, state: &mut State) -> Res ); output.set_preferred(mode); - state.common.shell.map_output(&output); - let (event_ping, event_source) = ping::make_ping().with_context(|| "Failed to init eventloop timer for winit")?; let (render_ping, render_source) = @@ -150,6 +148,7 @@ pub fn init_backend(event_loop: &mut EventLoop, state: &mut State) -> Res #[cfg(feature = "debug")] fps: Fps::default(), }); + state.common.shell.map_output(&output, &mut state.backend, &state.common.config); Ok(()) } diff --git a/src/backend/x11.rs b/src/backend/x11.rs index e9a297ca..61890890 100644 --- a/src/backend/x11.rs +++ b/src/backend/x11.rs @@ -229,7 +229,7 @@ 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); + state.common.shell.map_output(&output, &mut state.backend, &state.common.config); event_loop .handle() diff --git a/src/config.rs b/src/config.rs index 3f5e6024..19da6d4d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,25 +1,105 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::shell::layout::FocusDirection; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use smithay::wayland::seat::Keysym; pub use smithay::{ backend::input::KeyState, - wayland::seat::{keysyms as KeySyms, ModifiersState as KeyModifiers}, + 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; -#[derive(Debug, Deserialize)] pub struct Config { + pub static_conf: StaticConfig, + pub dynamic_conf: DynamicConfig, +} + +#[derive(Debug, Deserialize)] +pub struct StaticConfig { pub key_bindings: HashMap, pub workspace_mode: crate::shell::Mode, } +pub struct DynamicConfig { + outputs: (Option, OutputsConfig), +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct OutputsConfig { + pub config: HashMap, Vec>, +} + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct OutputInfo { + pub connector: String, + pub make: String, + pub model: String, +} + +impl From for OutputInfo { + fn from(o: Output) -> OutputInfo { + let physical = o.physical_properties(); + OutputInfo { + connector: o.name(), + make: physical.make, + model: physical.model, + } + } +} + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] +pub struct OutputConfig { + pub mode: ((i32, i32), Option), + pub vrr: bool, + pub scale: f64, + #[serde(with="TransformDef")] + pub transform: Transform, + pub position: (i32, i32), +} + +impl OutputConfig { + 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) + } +} + +#[derive(Serialize, Deserialize)] +#[serde(remote = "Transform")] +enum TransformDef { + Normal, + _90, + _180, + _270, + Flipped, + Flipped90, + Flipped180, + Flipped270, +} + impl Config { pub fn load() -> Config { - let mut locations = if let Ok(base) = xdg::BaseDirectories::new() { - base.list_config_files_once("cosmic-comp.ron") + let xdg = xdg::BaseDirectories::new().ok(); + Config { + static_conf: Self::load_static(xdg.as_ref()), + dynamic_conf: Self::load_dynamic(xdg.as_ref()), + } + } + + fn load_static(xdg: Option<&xdg::BaseDirectories>) -> StaticConfig { + let mut locations = if let Some(base) = xdg { + vec![ + base.get_config_file("cosmic-comp.ron"), + base.get_config_file("cosmic-comp/config.ron"), + ] } else { Vec::with_capacity(3) }; @@ -33,17 +113,85 @@ impl Config { locations.push(PathBuf::from("/etc/cosmic-comp.ron")); for path in locations { + slog_scope::debug!("Trying config location: {}", path.display()); if path.exists() { + slog_scope::info!("Using config at {}", path.display()); return ron::de::from_reader(OpenOptions::new().read(true).open(path).unwrap()) .expect("Malformed config file"); } } - Config { + StaticConfig { key_bindings: HashMap::new(), workspace_mode: crate::shell::Mode::global(), } } + + 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 outputs = Self::load_outputs(&output_path); + + DynamicConfig { + outputs: (output_path, outputs), + } + } + + fn load_outputs(path: &Option) -> OutputsConfig { + if let Some(path) = path.as_ref() { + if path.exists() { + match ron::de::from_reader(OpenOptions::new().read(true).open(path).unwrap()) { + Ok(config) => return config, + Err(err) => { + slog_scope::warn!("Failed to read output_config ({}), resetting..", err); + std::fs::remove_file(path); + } + }; + } + } + + OutputsConfig { + config: HashMap::new(), + } + } +} + +pub struct PersistenceGuard<'a, T: Serialize>(Option, &'a mut T); + +impl<'a, T: Serialize> std::ops::Deref for PersistenceGuard<'a, T> { + type Target = T; + fn deref(&self) -> &T { + &self.1 + } +} + +impl<'a, T: Serialize> std::ops::DerefMut for PersistenceGuard<'a, T> { + fn deref_mut(&mut self) -> &mut T { + &mut self.1 + } +} + +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) { + Ok(writer) => writer, + Err(err) => { + slog_scope::warn!("Failed to persist {}: {}", path.display(), err); + } + }; + ron::ser::to_writer(writer, &self.1); + } + } +} + +impl DynamicConfig { + pub fn outputs(&self) -> &OutputsConfig { + &self.outputs.1 + } + + pub fn outputs_mut<'a>(&'a mut self) -> PersistenceGuard<'a, OutputsConfig> { + PersistenceGuard(self.outputs.0.clone(), &mut self.outputs.1) + } } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] diff --git a/src/input/mod.rs b/src/input/mod.rs index f7b6d888..20336037 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -237,7 +237,7 @@ impl Common { } // here we can handle global shortcuts and the like - for (binding, action) in self.config.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/mod.rs b/src/shell/mod.rs index 8cce488c..cd0de213 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -1,12 +1,16 @@ // SPDX-License-Identifier: GPL-3.0-only -use crate::{config::Config, input::active_output, state::Common}; +use crate::{ + config::{Config, OutputInfo, OutputConfig}, + input::active_output, + state::{BackendData, Common}, +}; pub use smithay::{ desktop::{PopupGrab, PopupManager, PopupUngrabStrategy, Space, Window}, reexports::wayland_server::protocol::wl_surface::WlSurface, utils::{Logical, Point, Rectangle, Size}, wayland::{ - compositor::with_states, output::Output, seat::Seat, + compositor::with_states, output::{Mode as OutputMode, Output}, seat::Seat, shell::xdg::XdgToplevelSurfaceRoleAttributes, Serial, SERIAL_COUNTER, }, }; @@ -73,7 +77,7 @@ impl Shell { fn new(config: &Config) -> Self { Shell { popups: PopupManager::new(None), - mode: config.workspace_mode, + mode: config.static_conf.workspace_mode, outputs: Vec::new(), spaces: unsafe { let mut spaces = [UNINIT_SPACE; MAX_WORKSPACES]; @@ -85,38 +89,103 @@ impl Shell { } } - pub fn map_output(&mut self, output: &Output) { - match self.mode { - Mode::OutputBound => { - output - .user_data() - .insert_if_missing(|| ActiveWorkspace::new()); + fn apply_config(output: &Output, backend: &mut BackendData) -> Result<(), impl std::error::Error> { + backend.apply_config_for_output(output)?; - 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); - workspace.space.map_output(output, 1.0, (0, 0)); - self.outputs.push(output.clone()); + 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(()) + } + + 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::>(); + 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::>(); + + 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; + if let Err(err) = Self::apply_config(output, backend) { + slog_scope::warn!("Failed to set new config for output {}: {}", output.name(), err); + reset = true; + break; + } } - 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 + + if reset { + for (output, config) in self.outputs.iter().zip(known_good_configs.into_iter()) { + *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); + } + } + } + + if let Mode::Global { active } = self.mode { let workspace = &mut self.spaces[active]; - let x = workspace - .space - .outputs() - .map(|output| workspace.space.output_geometry(&output).unwrap()) - .fold(0, |acc, geo| std::cmp::max(acc, geo.loc.x + geo.size.w)); - workspace.space.map_output(output, 1.0, (x, 0)); - self.outputs.push(output.clone()); + for output in self.outputs.iter() { + let config = output.user_data().get::>().unwrap().borrow(); + workspace.space.map_output(output, config.scale, config.position); + } + } + } 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); + + let new_pos = (new_pos_x, 0); + 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); + 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); + } } } } diff --git a/src/state.rs b/src/state.rs index ab72f4c8..2d2f6b78 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}, }; @@ -105,6 +105,16 @@ impl BackendData { } } + pub fn apply_config_for_output(&mut self, output: &Output) -> Result<(), impl std::error::Error> { + 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(()) + }, + } + } + pub fn schedule_render(&mut self, output: &Output) { match self { BackendData::Winit(_) => {} // We cannot do this on the winit backend.