diff --git a/src/config/mod.rs b/src/config/mod.rs index 03bdacda..d2d15b77 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -69,6 +69,7 @@ pub struct DynamicConfig { outputs: (Option, OutputsConfig), numlock: (Option, NumlockStateConfig), pub accessibility_zoom: (Option, ZoomState), + accessibility_filter: (Option, ScreenFilter), } #[derive(Debug, Deserialize, Serialize)] @@ -182,6 +183,29 @@ pub struct ZoomState { pub last_level: f64, } +#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq)] +pub struct ScreenFilter { + pub inverted: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub color_filter: Option, +} + +impl ScreenFilter { + pub fn is_noop(&self) -> bool { + self.inverted == false && self.color_filter.is_none() + } +} + +#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Eq, Hash)] +#[repr(u8)] +// these values need to match with offscreen.frag +pub enum ColorFilter { + Greyscale = 1, + Protanopia = 2, + Deuteranopia = 3, + Tritanopia = 4, +} + impl Config { pub fn load(loop_handle: &LoopHandle<'_, State>) -> Config { let config = cosmic_config::Config::new("com.system76.CosmicComp", 1).unwrap(); @@ -311,6 +335,18 @@ impl Config { ), }; + let _ = loop_handle.insert_idle(|state| { + let filter_conf = state.common.config.dynamic_conf.screen_filter(); + state + .common + .a11y_state + .set_screen_inverted(filter_conf.inverted); + state + .common + .a11y_state + .set_screen_filter(filter_conf.color_filter); + }); + Config { dynamic_conf: Self::load_dynamic(xdg.as_ref(), &cosmic_comp_config), cosmic_conf: cosmic_comp_config, @@ -337,10 +373,17 @@ impl Config { xdg.and_then(|base| base.place_state_file("cosmic-comp/a11y_zoom.ron").ok()); let zoom = Self::load_zoom_state(&zoom_path, cosmic); + let filter_path = xdg.and_then(|base| { + base.place_state_file("cosmic-comp/a11y_screen_filter.ron") + .ok() + }); + let filter = Self::load_filter_state(&filter_path); + DynamicConfig { outputs: (output_path, outputs), numlock: (numlock_path, numlock), accessibility_zoom: (zoom_path, zoom), + accessibility_filter: (filter_path, filter), } } @@ -435,6 +478,29 @@ impl Config { } } + fn load_filter_state(path: &Option) -> ScreenFilter { + if let Some(path) = path.as_ref() { + if path.exists() { + match ron::de::from_reader::<_, ScreenFilter>( + OpenOptions::new().read(true).open(path).unwrap(), + ) { + Ok(config) => return config, + Err(err) => { + warn!(?err, "Failed to read screen_filter state, resetting.."); + if let Err(err) = std::fs::remove_file(path) { + error!(?err, "Failed to remove screen_filter state."); + } + } + }; + } + } + + ScreenFilter { + inverted: false, + color_filter: None, + } + } + pub fn shortcut_for_action(&self, action: &shortcuts::Action) -> Option { self.shortcuts.shortcut_for_action(action) } @@ -486,6 +552,7 @@ impl Config { if let Err(err) = backend.apply_config_for_outputs( false, loop_handle, + self.dynamic_conf.screen_filter(), shell.clone(), workspace_state, xdg_activation_state, @@ -511,6 +578,7 @@ impl Config { if let Err(err) = backend.apply_config_for_outputs( false, loop_handle, + self.dynamic_conf.screen_filter(), shell.clone(), workspace_state, xdg_activation_state, @@ -553,6 +621,7 @@ impl Config { if let Err(err) = backend.apply_config_for_outputs( false, loop_handle, + self.dynamic_conf.screen_filter(), shell.clone(), workspace_state, xdg_activation_state, @@ -720,6 +789,17 @@ impl DynamicConfig { &mut self.accessibility_zoom.1, ) } + + pub fn screen_filter(&self) -> &ScreenFilter { + &self.accessibility_filter.1 + } + + pub fn screen_filter_mut(&mut self) -> PersistenceGuard<'_, ScreenFilter> { + PersistenceGuard( + self.accessibility_filter.0.clone(), + &mut self.accessibility_filter.1, + ) + } } fn get_config( diff --git a/src/state.rs b/src/state.rs index 7bccb412..c49a8d9e 100644 --- a/src/state.rs +++ b/src/state.rs @@ -7,7 +7,7 @@ use crate::{ winit::WinitState, x11::X11State, }, - config::{Config, OutputConfig, OutputState}, + config::{Config, OutputConfig, OutputState, ScreenFilter}, input::{gestures::GestureState, PointerFocusState}, shell::{grabs::SeatMoveGrabState, CosmicSurface, SeatExt, Shell}, utils::prelude::OutputExt, @@ -451,6 +451,10 @@ impl BackendData { _ => unreachable!("No backend set when getting offscreen renderer"), } } + + pub fn update_screen_filter(&mut self, screen_filter: &ScreenFilter) -> anyhow::Result<()> { + let _ = screen_filter; // TODO + } } pub struct KmsNodes {