cosmic-comp/src/config/mod.rs
Hojjat f4ee52c46a Ei protocol support
- Use the configured keyboard layout, track ei seats to reconfigure their keyboards
- DBus interface
- Disambiguate input devices per backend instance
- Add support for `ei_keysym` and `ei_text`
- Act as input method for ei_text UTF-8 injection
- Create a region per output for abosolute pointer position
- Map ei region for touchscreen emulation
- Release pointer buttons and keys on removal
- Send modifiers to EI connections
2026-08-17 12:22:35 -07:00

1014 lines
38 KiB
Rust

// SPDX-License-Identifier: GPL-3.0-only
use crate::{
input::InputBackendId,
shell::Shell,
state::{BackendData, State},
utils::prelude::OutputExt,
wayland::protocols::{
output_configuration::OutputConfigurationState, workspace::WorkspaceUpdateGuard,
},
};
use anyhow::Context;
use cosmic_config::{ConfigGet, CosmicConfigEntry};
use cosmic_settings_config::window_rules::ApplicationException;
use cosmic_settings_config::{Shortcuts, shortcuts, window_rules};
use serde::{Deserialize, Serialize};
use smithay::utils::{Clock, Monotonic};
use smithay::wayland::xdg_activation::XdgActivationState;
pub use smithay::{
backend::input::{self as smithay_input, KeyState},
input::keyboard::{Keysym, ModifiersState, keysyms as KeySyms},
output::{Mode, Output},
reexports::{
calloop::LoopHandle,
input::{
AccelProfile, ClickMethod, Device as InputDevice, ScrollMethod, SendEventsMode,
TapButtonMap,
},
},
utils::{Logical, Physical, Point, SERIAL_COUNTER, Size, Transform},
};
use std::{
cell::{Ref, RefCell},
collections::{BTreeMap, HashMap},
fs::OpenOptions,
io::Write,
path::PathBuf,
sync::{Arc, atomic::AtomicBool},
};
use tracing::{error, warn};
mod input_config;
pub mod key_bindings;
mod types;
use cosmic::config::CosmicTk;
pub use cosmic_comp_config::EdidProduct;
use cosmic_comp_config::{
ActivationPolicy, AppearanceConfig, CosmicCompConfig, KeyboardConfig, TileBehavior, XkbConfig,
XwaylandDescaling, XwaylandEavesdropping, ZoomConfig,
input::{DeviceState as InputDeviceState, InputConfig, TouchpadOverride},
output::comp::{
OutputConfig, OutputInfo, OutputState, OutputsConfig, TransformDef, load_outputs,
},
workspace::WorkspaceConfig,
};
pub use key_bindings::{Action, PrivateAction};
use types::WlXkbConfig;
#[derive(Debug)]
pub struct Config {
pub dynamic_conf: DynamicConfig,
pub cosmic_helper: cosmic_config::Config,
/// cosmic-config comp configuration for `com.system76.CosmicComp`
pub cosmic_conf: CosmicCompConfig,
/// cosmic-config context for `com.system76.CosmicSettings.Shortcuts`
pub settings_context: cosmic_config::Config,
/// Key bindings from `com.system76.CosmicSettings.Shortcuts`
pub shortcuts: Shortcuts,
// Tiling exceptions from `com.system76.CosmicSettings.WindowRules`
pub tiling_exceptions: Vec<ApplicationException>,
/// System actions from `com.system76.CosmicSettings.Shortcuts`
pub system_actions: BTreeMap<shortcuts::action::System, String>,
}
#[derive(Debug)]
pub struct DynamicConfig {
outputs: (Option<PathBuf>, OutputsConfig),
numlock: (Option<PathBuf>, NumlockStateConfig),
accessibility_filter: (Option<PathBuf>, ScreenFilter),
}
#[derive(Default, Debug, Deserialize, Serialize)]
pub struct NumlockStateConfig {
pub last_state: bool,
}
pub struct CompOutputConfig<'a>(pub Ref<'a, OutputConfig>);
impl CompOutputConfig<'_> {
pub fn mode_size(&self) -> Size<i32, Physical> {
self.0.mode.0.into()
}
pub fn mode_refresh(&self) -> u32 {
self.0.mode.1.unwrap_or(60_000)
}
pub fn transformed_size(&self) -> Size<i32, Physical> {
self.transform().transform_size(self.mode_size())
}
pub fn output_mode(&self) -> Mode {
Mode {
size: self.mode_size(),
refresh: self.mode_refresh() as i32,
}
}
pub fn transform(&self) -> Transform {
Transform::from(CompTransformDef(self.0.transform))
}
}
pub struct CompTransformDef(pub TransformDef);
impl From<Transform> for CompTransformDef {
fn from(transform: Transform) -> Self {
let def = match transform {
Transform::Normal => TransformDef::Normal,
Transform::_90 => TransformDef::_90,
Transform::_180 => TransformDef::_180,
Transform::_270 => TransformDef::_270,
Transform::Flipped => TransformDef::Flipped,
Transform::Flipped90 => TransformDef::Flipped90,
Transform::Flipped180 => TransformDef::Flipped180,
Transform::Flipped270 => TransformDef::Flipped270,
};
CompTransformDef(def)
}
}
impl From<CompTransformDef> for Transform {
fn from(comp_transform: CompTransformDef) -> Self {
match comp_transform.0 {
TransformDef::Normal => Transform::Normal,
TransformDef::_90 => Transform::_90,
TransformDef::_180 => Transform::_180,
TransformDef::_270 => Transform::_270,
TransformDef::Flipped => Transform::Flipped,
TransformDef::Flipped90 => Transform::Flipped90,
TransformDef::Flipped180 => Transform::Flipped180,
TransformDef::Flipped270 => Transform::Flipped270,
}
}
}
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq)]
pub struct ScreenFilter {
pub inverted: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub color_filter: Option<ColorFilter>,
}
impl ScreenFilter {
pub fn is_noop(&self) -> bool {
!self.inverted && 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();
let source = cosmic_config::calloop::ConfigWatchSource::new(&config).unwrap();
loop_handle
.insert_source(source, |(config, keys), (), state| {
config_changed(config, keys, state);
})
.expect("Failed to add cosmic-config to the event loop");
let xdg = xdg::BaseDirectories::new();
let cosmic_comp_config =
CosmicCompConfig::get_entry(&config).unwrap_or_else(|(errs, c)| {
if cfg!(debug_assertions) {
for err in errs {
warn!(?err, "");
}
}
c
});
// Listen for updates to the toolkit config
if let Ok(tk_config) = cosmic_config::Config::new("com.system76.CosmicTk", 1) {
fn handle_new_toolkit_config(config: CosmicTk, state: &mut State) {
if cosmic::icon_theme::default() != config.icon_theme {
cosmic::icon_theme::set_default(config.icon_theme.clone());
state.common.update_xwayland_settings();
}
let mut workspace_guard = state.common.workspace_state.update();
state.common.shell.write().update_toolkit(
config,
&state.common.xdg_activation_state,
&mut workspace_guard,
);
}
let config = CosmicTk::get_entry(&tk_config).unwrap_or_else(|(errs, c)| {
if cfg!(debug_assertions) {
for err in errs {
warn!(?err, "");
}
}
c
});
let _ = loop_handle.insert_idle(move |state| {
handle_new_toolkit_config(config, state);
});
match cosmic_config::calloop::ConfigWatchSource::new(&tk_config) {
Ok(source) => {
if let Err(err) =
loop_handle.insert_source(source, |(config, _keys), (), state| {
let config =
CosmicTk::get_entry(&config).unwrap_or_else(|(errs, c)| {
if cfg!(debug_assertions) {
for err in errs {
warn!(?err, "");
}
}
c
});
handle_new_toolkit_config(config, state);
})
{
warn!(?err, "Failed to watch com.system76.CosmicTk config");
}
}
Err(err) => warn!(
?err,
"failed to create config watch source for com.system76.CosmicTk"
),
}
}
// Source key bindings from com.system76.CosmicSettings.Shortcuts
let settings_context = shortcuts::context().expect("Failed to load shortcuts config");
let system_actions = shortcuts::system_actions(&settings_context);
let shortcuts = shortcuts::shortcuts(&settings_context);
// Listen for updates to the keybindings config.
match cosmic_config::calloop::ConfigWatchSource::new(&settings_context) {
Ok(source) => {
if let Err(err) = loop_handle.insert_source(source, |(config, keys), (), state| {
for key in keys {
match key.as_str() {
// Reload the keyboard shortcuts config.
"custom" | "defaults" => {
state.common.config.shortcuts = shortcuts::shortcuts(&config);
}
"system_actions" => {
state.common.config.system_actions =
shortcuts::system_actions(&config);
}
_ => (),
}
}
}) {
warn!(
?err,
"Failed to watch com.system76.CosmicSettings.Shortcuts config"
);
}
}
Err(err) => warn!(
?err,
"failed to create config watch source for com.system76.CosmicSettings.Shortcuts"
),
};
let window_rules_context =
window_rules::context().expect("Failed to load window rules config");
let tiling_exceptions = window_rules::tiling_exceptions(&window_rules_context);
match cosmic_config::calloop::ConfigWatchSource::new(&window_rules_context) {
Ok(source) => {
if let Err(err) = loop_handle.insert_source(source, |(config, keys), (), state| {
for key in keys {
match key.as_str() {
"tiling_exception_defaults" | "tiling_exception_custom" => {
let new_exceptions = window_rules::tiling_exceptions(&config);
state.common.config.tiling_exceptions = new_exceptions;
state.common.shell.write().update_tiling_exceptions(
state.common.config.tiling_exceptions.iter(),
);
}
_ => (),
}
}
}) {
warn!(
?err,
"Failed to watch com.system76.CosmicSettings.WindowRules config"
);
}
}
Err(err) => warn!(
?err,
"failed to create config watch source for com.system76.CosmicSettings.WindowRules"
),
};
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),
cosmic_conf: cosmic_comp_config,
cosmic_helper: config,
settings_context,
shortcuts,
system_actions,
tiling_exceptions,
}
}
fn load_dynamic(xdg: &xdg::BaseDirectories) -> DynamicConfig {
let output_path = xdg.place_state_file("cosmic-comp/outputs.ron").ok();
let outputs = load_outputs(output_path.as_ref());
let numlock_path = xdg.place_state_file("cosmic-comp/numlock.ron").ok();
let numlock = Self::load_numlock(&numlock_path);
let filter_path = xdg
.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_filter: (filter_path, filter),
}
}
fn load_numlock(path: &Option<PathBuf>) -> NumlockStateConfig {
path.as_deref()
.filter(|path| path.exists())
.and_then(|path| {
ron::de::from_reader::<_, NumlockStateConfig>(
OpenOptions::new().read(true).open(path).unwrap(),
)
.map_err(|err| {
warn!(?err, "Failed to read numlock.ron, resetting..");
if let Err(err) = std::fs::remove_file(path) {
error!(?err, "Failed to remove numlock.ron.");
}
})
.ok()
})
.unwrap_or_default()
}
fn load_filter_state(path: &Option<PathBuf>) -> ScreenFilter {
if let Some(path) = path.as_ref()
&& 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<String> {
self.shortcuts.shortcut_for_action(action)
}
pub fn read_outputs(
&mut self,
output_state: &mut OutputConfigurationState<State>,
backend: &mut BackendData,
shell: &Arc<parking_lot::RwLock<Shell>>,
loop_handle: &LoopHandle<'static, State>,
workspace_state: &mut WorkspaceUpdateGuard<'_, State>,
xdg_activation_state: &XdgActivationState,
startup_done: Arc<AtomicBool>,
clock: &Clock<Monotonic>,
) -> anyhow::Result<()> {
let outputs = output_state.outputs().collect::<Vec<_>>();
let mut infos = outputs
.iter()
.cloned()
.map(Into::<crate::config::CompOutputInfo>::into)
.map(|i| i.0)
.collect::<Vec<_>>();
infos.sort();
if let Some(configs) = self
.dynamic_conf
.outputs()
.config
.get(&infos)
.filter(|configs| {
if configs
.iter()
.all(|config| config.enabled == OutputState::Disabled)
{
if !configs.is_empty() {
error!(
"Broken config, all outputs disabled. Resetting... {:?}",
configs
);
}
false
} else {
true
}
})
.cloned()
{
let known_good_configs = outputs
.iter()
.map(|output| {
output
.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow()
.clone()
})
.collect::<Vec<_>>();
let mut found_outputs = Vec::new();
for (name, output_config) in infos.iter().map(|o| &o.connector).zip(configs) {
let output = outputs.iter().find(|o| &o.name() == name).unwrap().clone();
let enabled = output_config.enabled.clone();
*output
.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow_mut() = output_config;
found_outputs.push((output.clone(), enabled));
}
let mut backend = backend.lock();
if let Err(err) = backend.apply_config_for_outputs(
false,
loop_handle,
self.dynamic_conf.screen_filter(),
shell.clone(),
workspace_state,
xdg_activation_state,
startup_done.clone(),
clock,
) {
warn!(?err, "Failed to set new config.");
found_outputs.clear();
for (output, output_config) in outputs.clone().into_iter().zip(known_good_configs) {
let enabled = output_config.enabled.clone();
*output
.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow_mut() = output_config;
found_outputs.push((output.clone(), enabled));
}
backend
.apply_config_for_outputs(
false,
loop_handle,
self.dynamic_conf.screen_filter(),
shell.clone(),
workspace_state,
xdg_activation_state,
startup_done,
clock,
)
.context("Failed to reset config")?;
for (output, enabled) in found_outputs {
if enabled == OutputState::Enabled {
output_state.enable_head(&output);
} else {
output_state.disable_head(&output);
}
}
} else {
for (output, enabled) in found_outputs {
if enabled == OutputState::Enabled {
output_state.enable_head(&output);
} else {
output_state.disable_head(&output);
}
}
}
output_state.update();
self.write_outputs(output_state.outputs());
} else {
if outputs
.iter()
.all(|o| o.config().enabled == OutputState::Disabled)
{
for output in &outputs {
output.config_mut().enabled = OutputState::Enabled;
}
}
// we don't have a config, so lets generate somewhat sane positions
let mut w = 0;
if !outputs.iter().any(|o| o.config().xwayland_primary) {
// if we don't have a primary output for xwayland from a previous config, pick one
if let Some(primary) = outputs.iter().find(|o| o.mirroring().is_none()) {
primary.config_mut().xwayland_primary = true;
}
}
for output in outputs.iter().filter(|o| o.mirroring().is_none()) {
{
let mut config = output.config_mut();
config.position = (w, 0);
}
w += output.geometry().size.w as u32;
}
let mut backend = backend.lock();
backend
.apply_config_for_outputs(
false,
loop_handle,
self.dynamic_conf.screen_filter(),
shell.clone(),
workspace_state,
xdg_activation_state,
startup_done.clone(),
clock,
)
.context("Failed to set new config")?;
for output in outputs {
if output
.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow()
.enabled
== OutputState::Enabled
{
output_state.enable_head(&output);
} else {
output_state.disable_head(&output);
}
}
output_state.update();
self.write_outputs(output_state.outputs());
}
Ok(())
}
pub fn write_outputs(
&mut self,
outputs: impl Iterator<Item = impl std::borrow::Borrow<Output>>,
) {
let mut infos = outputs
.map(|o| {
let o = o.borrow();
(
Into::<CompOutputInfo>::into(o.clone()).0,
o.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow()
.clone(),
)
})
.collect::<Vec<(OutputInfo, OutputConfig)>>();
infos.sort_by(|(a, _), (b, _)| a.cmp(b));
let (infos, configs) = infos.into_iter().unzip();
self.dynamic_conf
.outputs_mut()
.config
.insert(infos, configs);
}
pub fn xkb_config(&self) -> XkbConfig {
self.cosmic_conf.xkb_config.clone()
}
pub fn read_device(&self, device: &mut InputDevice) {
let (device_config, default_config) = self.get_device_config(device);
input_config::update_device(device, device_config.as_ref(), default_config);
}
pub fn scroll_factor(&self, device: &InputDevice) -> f64 {
let (device_config, default_config) = self.get_device_config(device);
input_config::get_config(device_config.as_ref(), default_config, |x| {
x.scroll_config.as_ref()?.scroll_factor
})
.map_or(1.0, |x| x.0)
}
pub fn map_to_output(&self, device: &InputDevice) -> Option<String> {
let (device_config, default_config) = self.get_device_config(device);
Some(
input_config::get_config(device_config.as_ref(), default_config, |x| {
x.map_to_output.clone()
})?
.0,
)
}
fn get_device_config(&self, device: &InputDevice) -> (Option<InputConfig>, &InputConfig) {
let is_touchpad = device.config_tap_finger_count() > 0;
let default_config = if is_touchpad {
&self.cosmic_conf.input_touchpad
} else {
&self.cosmic_conf.input_default
};
let mut device_config = self.cosmic_conf.input_devices.get(&*device.name()).cloned();
if is_touchpad && self.cosmic_conf.input_touchpad_override == TouchpadOverride::ForceDisable
{
device_config = Some({
let mut config = device_config.unwrap_or_default();
config.state = InputDeviceState::Disabled;
config
});
}
(device_config, default_config)
}
}
pub struct PersistenceGuard<'a, T: Serialize>(Option<PathBuf>, &'a mut T);
impl<T: Serialize> std::ops::Deref for PersistenceGuard<'_, T> {
type Target = T;
fn deref(&self) -> &T {
self.1
}
}
impl<T: Serialize> std::ops::DerefMut for PersistenceGuard<'_, T> {
fn deref_mut(&mut self) -> &mut T {
self.1
}
}
impl<T: Serialize> Drop for PersistenceGuard<'_, T> {
fn drop(&mut self) {
if let Some(path) = self.0.as_ref() {
let content = match ron::ser::to_string_pretty(&self.1, Default::default()) {
Ok(content) => content,
Err(err) => {
warn!("Failed to serialize: {:?}", err);
return;
}
};
let mut writer = match OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.open(path)
{
Ok(writer) => writer,
Err(err) => {
warn!(?err, "Failed to persist {}.", path.display());
return;
}
};
if let Err(err) = writer.write_all(content.as_bytes()) {
warn!(?err, "Failed to persist {}", path.display());
} else {
let _ = writer.flush();
}
}
}
}
impl DynamicConfig {
pub fn outputs(&self) -> &OutputsConfig {
&self.outputs.1
}
pub fn outputs_mut(&mut self) -> PersistenceGuard<'_, OutputsConfig> {
PersistenceGuard(self.outputs.0.clone(), &mut self.outputs.1)
}
pub fn numlock(&self) -> &NumlockStateConfig {
&self.numlock.1
}
pub fn numlock_mut(&mut self) -> PersistenceGuard<'_, NumlockStateConfig> {
PersistenceGuard(self.numlock.0.clone(), &mut self.numlock.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,
)
}
}
pub fn xkb_config_to_wl(config: &XkbConfig) -> WlXkbConfig<'_> {
WlXkbConfig {
rules: &config.rules,
model: &config.model,
layout: &config.layout,
variant: &config.variant,
options: config.options.clone(),
}
}
fn get_config<T: Default + serde::de::DeserializeOwned>(
config: &cosmic_config::Config,
key: &str,
) -> T {
config.get(key).unwrap_or_else(|err| {
error!(?err, "Failed to read config '{}'", key);
T::default()
})
}
fn update_input(state: &mut State) {
if let BackendData::Kms(kms_state) = &mut state.backend {
for device in kms_state.input_devices.values_mut() {
state.common.config.read_device(device);
}
}
}
pub fn change_modifier_state(
keyboard: &smithay::input::keyboard::KeyboardHandle<State>,
scan_code: u32,
state: &mut State,
) {
/// Offset used to convert Linux scancode to X11 keycode.
const X11_KEYCODE_OFFSET: u32 = 8;
let mut input = |key_state, scan_code| {
let time = state.common.clock.now().as_millis();
let _ = keyboard.input(
state,
smithay_input::Keycode::new(scan_code + X11_KEYCODE_OFFSET),
key_state,
SERIAL_COUNTER.next_serial(),
time,
|_, _, _| smithay::input::keyboard::FilterResult::<()>::Forward,
);
};
input(smithay_input::KeyState::Pressed, scan_code);
input(smithay_input::KeyState::Released, scan_code);
}
fn config_changed(config: cosmic_config::Config, keys: Vec<String>, state: &mut State) {
for key in &keys {
match key.as_str() {
"xkb_config" => {
let value = get_config::<XkbConfig>(&config, "xkb_config");
let seats = state
.common
.shell
.read()
.seats
.iter()
.cloned()
.collect::<Vec<_>>();
for seat in seats.into_iter() {
if let Some(keyboard) = seat.get_keyboard() {
let old_modifier_state = keyboard.modifier_state();
keyboard.change_repeat_info(
(value.repeat_rate as i32).abs(), // Negative values are illegal
(value.repeat_delay as i32).abs(),
);
if let Err(err) = keyboard.set_xkb_config(state, xkb_config_to_wl(&value)) {
error!(?err, "Failed to load provided xkb config");
// TODO Revert to default?
}
// Press and release the numlock key to update modifiers.
if old_modifier_state.num_lock != keyboard.modifier_state().num_lock {
const NUMLOCK_SCANCODE: u32 = 69;
change_modifier_state(&keyboard, NUMLOCK_SCANCODE, state);
}
if old_modifier_state.caps_lock != keyboard.modifier_state().caps_lock {
const CAPSLOCK_SCANCODE: u32 = 58;
change_modifier_state(&keyboard, CAPSLOCK_SCANCODE, state);
}
}
}
let ei_connections = state
.common
.ei_keyboard_source
.keys()
.cloned()
.collect::<Vec<_>>();
for conn in &ei_connections {
state.release_ei_keyboard(conn);
state.clear_input_source_state(&InputBackendId::Ei(conn.clone()));
}
for ei_seat in state.common.ei_seats.values() {
if let Err(err) =
ei_seat.add_keyboard("virtual keyboard", xkb_config_to_wl(&value))
{
warn!(?err, "Failed to update libei keyboard keymap");
}
}
if !state.common.ei_seats.is_empty() {
let seat = state.common.shell.read().seats.last_active().clone();
state.broadcast_ei_keyboard_modifiers(&seat);
}
state.common.config.cosmic_conf.xkb_config = value;
}
"keyboard_config" => {
let value = get_config::<KeyboardConfig>(&config, "keyboard_config");
state.common.config.cosmic_conf.keyboard_config = value;
let shell = state.common.shell.read();
let seat = shell.seats.last_active();
state.common.config.dynamic_conf.numlock_mut().last_state =
seat.get_keyboard().unwrap().modifier_state().num_lock;
}
"input_default" => {
let value = get_config::<InputConfig>(&config, "input_default");
state.common.config.cosmic_conf.input_default = value;
update_input(state);
}
"input_touchpad" => {
let value = get_config::<InputConfig>(&config, "input_touchpad");
state.common.config.cosmic_conf.input_touchpad = value;
update_input(state);
}
"input_touchpad_override" => {
let value = get_config::<TouchpadOverride>(&config, "input_touchpad_override");
state.common.config.cosmic_conf.input_touchpad_override = value;
update_input(state)
}
"input_devices" => {
let value = get_config::<HashMap<String, InputConfig>>(&config, "input_devices");
state.common.config.cosmic_conf.input_devices = value;
update_input(state);
}
"workspaces" => {
state.common.config.cosmic_conf.workspaces =
get_config::<WorkspaceConfig>(&config, "workspaces");
state.common.update_config();
}
"autotile" => {
let new = get_config::<bool>(&config, "autotile");
if new != state.common.config.cosmic_conf.autotile {
state.common.config.cosmic_conf.autotile = new;
let mut shell = state.common.shell.write();
let shell_ref = &mut *shell;
shell_ref.workspaces.update_autotile(
new,
&mut state.common.workspace_state.update(),
shell_ref.seats.iter(),
);
}
}
"autotile_behavior" => {
let new = get_config::<TileBehavior>(&config, "autotile_behavior");
if new != state.common.config.cosmic_conf.autotile_behavior {
state.common.config.cosmic_conf.autotile_behavior = new;
let mut shell = state.common.shell.write();
let shell_ref = &mut *shell;
shell_ref.workspaces.update_autotile_behavior(
new,
&mut state.common.workspace_state.update(),
shell_ref.seats.iter(),
);
}
}
"active_hint" => {
let new = get_config::<bool>(&config, "active_hint");
if new != state.common.config.cosmic_conf.active_hint {
state.common.config.cosmic_conf.active_hint = new;
state.common.update_config();
}
}
"descale_xwayland" => {
let new = get_config::<XwaylandDescaling>(&config, "descale_xwayland");
if new != state.common.config.cosmic_conf.descale_xwayland {
state.common.config.cosmic_conf.descale_xwayland = new;
state.common.update_xwayland_settings();
}
}
"xwayland_eavesdropping" => {
let new = get_config::<XwaylandEavesdropping>(&config, "xwayland_eavesdropping");
if new != state.common.config.cosmic_conf.xwayland_eavesdropping {
state.common.config.cosmic_conf.xwayland_eavesdropping = new;
state
.common
.xwayland_reset_eavesdropping(SERIAL_COUNTER.next_serial());
}
}
"focus_follows_cursor" => {
let new = get_config::<bool>(&config, "focus_follows_cursor");
if new != state.common.config.cosmic_conf.focus_follows_cursor {
state.common.config.cosmic_conf.focus_follows_cursor = new;
}
}
"cursor_follows_focus" => {
let new = get_config::<bool>(&config, "cursor_follows_focus");
if new != state.common.config.cosmic_conf.cursor_follows_focus {
state.common.config.cosmic_conf.cursor_follows_focus = new;
}
}
"focus_follows_cursor_delay" => {
let new = get_config::<u64>(&config, "focus_follows_cursor_delay");
if new != state.common.config.cosmic_conf.focus_follows_cursor_delay {
state.common.config.cosmic_conf.focus_follows_cursor_delay = new;
}
}
"edge_snap_threshold" => {
let new = get_config::<u32>(&config, "edge_snap_threshold");
if new != state.common.config.cosmic_conf.edge_snap_threshold {
state.common.config.cosmic_conf.edge_snap_threshold = new;
}
}
"accessibility_zoom" => {
let new = get_config::<ZoomConfig>(&config, "accessibility_zoom");
if new != state.common.config.cosmic_conf.accessibility_zoom {
state.common.config.cosmic_conf.accessibility_zoom = new;
state.common.update_config();
}
}
"appearance_settings" => {
let new = get_config::<AppearanceConfig>(&config, "appearance_settings");
if new != state.common.config.cosmic_conf.appearance_settings {
state.common.config.cosmic_conf.appearance_settings = new;
state.common.update_config();
for output in state.common.shell.read().outputs() {
state.backend.schedule_render(output);
}
}
}
"cursor_hide_timeout" => {
let new = get_config::<Option<u32>>(&config, "cursor_hide_timeout");
if new != state.common.config.cosmic_conf.cursor_hide_timeout {
state.common.config.cosmic_conf.cursor_hide_timeout = new;
let seats: Vec<_> = state.common.shell.read().seats.iter().cloned().collect();
let mut needs_render = false;
for seat in seats {
needs_render |=
crate::backend::render::cursor::notify_cursor_activity(state, &seat);
}
if needs_render {
let outputs: Vec<_> =
state.common.shell.read().outputs().cloned().collect();
for output in outputs {
state.backend.schedule_render(&output);
}
}
}
}
"activation_policy" => {
let new = get_config::<ActivationPolicy>(&config, "activation_policy");
if new != state.common.config.cosmic_conf.activation_policy {
state.common.config.cosmic_conf.activation_policy = new;
}
}
_ => {}
}
}
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CompOutputInfo(OutputInfo);
impl From<Output> for CompOutputInfo {
fn from(o: Output) -> CompOutputInfo {
let physical = o.physical_properties();
CompOutputInfo(OutputInfo {
connector: o.name(),
make: physical.make,
model: physical.model,
})
}
}