diff --git a/cosmic-comp-config/src/lib.rs b/cosmic-comp-config/src/lib.rs index 5a656e52..b77a5e21 100644 --- a/cosmic-comp-config/src/lib.rs +++ b/cosmic-comp-config/src/lib.rs @@ -7,6 +7,20 @@ use std::collections::HashMap; pub mod input; pub mod workspace; +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +pub struct KeyboardConfig { + /// Boot state for numlock + pub numlock_state: NumlockState, +} + +#[derive(Copy, Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +pub enum NumlockState { + BootOn, + #[default] + BootOff, + LastBoot, +} + #[derive(Clone, Debug, PartialEq, CosmicConfigEntry)] #[version = 1] pub struct CosmicCompConfig { @@ -15,6 +29,7 @@ pub struct CosmicCompConfig { pub input_touchpad: input::InputConfig, pub input_devices: HashMap, pub xkb_config: XkbConfig, + pub keyboard_config: KeyboardConfig, /// Autotiling enabled pub autotile: bool, /// Determines the behavior of the autotile variable @@ -53,6 +68,7 @@ impl Default for CosmicCompConfig { }, input_devices: Default::default(), xkb_config: Default::default(), + keyboard_config: Default::default(), autotile: Default::default(), autotile_behavior: Default::default(), active_hint: true, diff --git a/src/backend/mod.rs b/src/backend/mod.rs index c4f3335f..fb33bd41 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -1,8 +1,11 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::state::State; -use anyhow::{Context, Result}; +use anyhow::{anyhow, Context, Result}; +use cosmic_comp_config::NumlockState; +use smithay::backend::input::{self as smithay_input}; use smithay::reexports::{calloop::EventLoop, wayland_server::DisplayHandle}; +use smithay::utils::SERIAL_COUNTER; use tracing::{info, warn}; pub mod render; @@ -58,6 +61,11 @@ pub fn init_backend_auto( &state.common.config, "seat-0".into(), ); + + let keyboard = initial_seat + .get_keyboard() + .ok_or_else(|| anyhow!("`shell::create_seat` did not setup keyboard"))?; + state .common .shell @@ -66,6 +74,44 @@ pub fn init_backend_auto( .seats .add_seat(initial_seat); + let desired_numlock = state + .common + .config + .cosmic_conf + .keyboard_config + .numlock_state; + // Restore numlock state based on config. + let toggle_numlock = match desired_numlock { + NumlockState::BootOff => keyboard.modifier_state().num_lock, + NumlockState::BootOn => !keyboard.modifier_state().num_lock, + NumlockState::LastBoot => { + keyboard.modifier_state().num_lock + != state.common.config.dynamic_conf.numlock().last_state + } + }; + + // If we're enabling numlock... + if toggle_numlock { + /// Linux scancode for numlock key. + const NUMLOCK_SCANCODE: u32 = 69; + /// Offset used to convert Linux scancode to X11 keycode. + const X11_KEYCODE_OFFSET: u32 = 8; + + let mut input = |key_state| { + let time = state.common.clock.now().as_millis(); + let _ = keyboard.input( + state, + smithay_input::Keycode::new(NUMLOCK_SCANCODE + X11_KEYCODE_OFFSET), + key_state, + SERIAL_COUNTER.next_serial(), + time, + |_, _, _| smithay::input::keyboard::FilterResult::<()>::Forward, + ); + }; + // Press and release the numlock key to update modifiers. + input(smithay_input::KeyState::Pressed); + input(smithay_input::KeyState::Released); + } { { state diff --git a/src/config/mod.rs b/src/config/mod.rs index e6334392..332dd2a8 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -12,6 +12,8 @@ use cosmic_config::{ConfigGet, CosmicConfigEntry}; use cosmic_settings_config::window_rules::ApplicationException; use cosmic_settings_config::{shortcuts, window_rules, Shortcuts}; use serde::{Deserialize, Serialize}; +use smithay::utils::{Clock, Monotonic}; +use smithay::wayland::xdg_activation::XdgActivationState; pub use smithay::{ backend::input::KeyState, input::keyboard::{keysyms as KeySyms, Keysym, ModifiersState}, @@ -25,10 +27,6 @@ pub use smithay::{ }, utils::{Logical, Physical, Point, Size, Transform}, }; -use smithay::{ - utils::{Clock, Monotonic}, - wayland::xdg_activation::XdgActivationState, -}; use std::{ cell::RefCell, collections::{BTreeMap, HashMap}, @@ -46,7 +44,8 @@ mod types; pub use self::types::*; use cosmic::config::CosmicTk; use cosmic_comp_config::{ - input::InputConfig, workspace::WorkspaceConfig, CosmicCompConfig, TileBehavior, XkbConfig, + input::InputConfig, workspace::WorkspaceConfig, CosmicCompConfig, KeyboardConfig, TileBehavior, + XkbConfig, }; #[derive(Debug)] @@ -68,6 +67,7 @@ pub struct Config { #[derive(Debug)] pub struct DynamicConfig { outputs: (Option, OutputsConfig), + numlock: (Option, NumlockStateConfig), } #[derive(Debug, Deserialize, Serialize)] @@ -93,6 +93,11 @@ impl From for OutputInfo { } } +#[derive(Default, Debug, Deserialize, Serialize)] +pub struct NumlockStateConfig { + pub last_state: bool, +} + #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] #[serde(rename_all = "lowercase")] pub enum OutputState { @@ -323,9 +328,13 @@ impl Config { let output_path = xdg.and_then(|base| base.place_state_file("cosmic-comp/outputs.ron").ok()); let outputs = Self::load_outputs(&output_path); + let numlock_path = + xdg.and_then(|base| base.place_state_file("cosmic-comp/numlock.ron").ok()); + let numlock = Self::load_numlock(&numlock_path); DynamicConfig { outputs: (output_path, outputs), + numlock: (numlock_path, numlock), } } @@ -373,6 +382,24 @@ impl Config { } } + fn load_numlock(path: &Option) -> 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() + } + pub fn shortcut_for_action(&self, action: &shortcuts::Action) -> Option { self.shortcuts.shortcut_for_action(action) } @@ -639,6 +666,14 @@ impl DynamicConfig { 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) + } } fn get_config( @@ -688,6 +723,14 @@ fn config_changed(config: cosmic_config::Config, keys: Vec, state: &mut state.common.atspi_ei.update_keymap(value.clone()); state.common.config.cosmic_conf.xkb_config = value; } + "keyboard_config" => { + let value = get_config::(&config, "keyboard_config"); + state.common.config.cosmic_conf.keyboard_config = value; + let shell = state.common.shell.read().unwrap(); + 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::(&config, "input_default"); state.common.config.cosmic_conf.input_default = value; diff --git a/src/input/mod.rs b/src/input/mod.rs index 2f225bed..2a948c4d 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -33,7 +33,7 @@ use calloop::{ timer::{TimeoutAction, Timer}, RegistrationToken, }; -use cosmic_comp_config::workspace::WorkspaceLayout; +use cosmic_comp_config::{workspace::WorkspaceLayout, NumlockState}; use cosmic_settings_config::shortcuts; use cosmic_settings_config::shortcuts::action::{Direction, ResizeDirection}; use smithay::{ @@ -241,6 +241,22 @@ impl State { } self.handle_action(action, &seat, serial, time, pattern, None, true) } + + // If we want to track numlock state so it can be reused on the next boot... + if let NumlockState::LastBoot = + self.common.config.cosmic_conf.keyboard_config.numlock_state + { + // .. and the state has been updated ... + if self.common.config.dynamic_conf.numlock().last_state + != keyboard.modifier_state().num_lock + { + // ... then record the updated state. + // The call to `numlock_mut` will generate a `PersistenceGuard`. The + // `PersistenceGuard` will write to a file when it's dropped here. + self.common.config.dynamic_conf.numlock_mut().last_state = + keyboard.modifier_state().num_lock; + } + } } } diff --git a/src/shell/seats.rs b/src/shell/seats.rs index ba671878..0a7428fb 100644 --- a/src/shell/seats.rs +++ b/src/shell/seats.rs @@ -203,11 +203,12 @@ pub fn create_seat( // So instead of doing the right thing (and initialize these capabilities as matching // devices appear), we have to surrender to reality and just always expose a keyboard and pointer. let conf = config.xkb_config(); - if let Err(err) = seat.add_keyboard( + seat.add_keyboard( xkb_config_to_wl(&conf), (conf.repeat_delay as i32).abs(), (conf.repeat_rate as i32).abs(), - ) { + ) + .or_else(|err| { warn!( ?err, "Failed to load provided xkb config. Trying default...", @@ -217,8 +218,8 @@ pub fn create_seat( (conf.repeat_delay as i32).abs(), (conf.repeat_rate as i32).abs(), ) - .expect("Failed to load xkb configuration files"); - } + }) + .expect("Failed to load xkb configuration files"); seat.add_pointer(); seat.add_touch();