diff --git a/cosmic-comp-config/src/lib.rs b/cosmic-comp-config/src/lib.rs index 5a656e52..6ec97a92 100644 --- a/cosmic-comp-config/src/lib.rs +++ b/cosmic-comp-config/src/lib.rs @@ -7,14 +7,29 @@ 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] +#[version = 2] pub struct CosmicCompConfig { pub workspaces: workspace::WorkspaceConfig, pub input_default: input::InputConfig, 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..6c723c17 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -2,8 +2,12 @@ use crate::state::State; use 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}; +use xkbcommon::xkb::keysyms; pub mod render; @@ -51,13 +55,44 @@ pub fn init_backend_auto( .next() .with_context(|| "Backend initialized without output") .cloned()?; - let initial_seat = crate::shell::create_seat( + let (initial_seat, keyboard) = crate::shell::create_seat( dh, &mut state.common.seat_state, &output, &state.common.config, "seat-0".into(), ); + + // Restore numlock state based on config. + let turn_on = match state + .common + .config + .cosmic_conf + .keyboard_config + .numlock_state + { + NumlockState::BootOff => false, + NumlockState::BootOn => true, + NumlockState::LastBoot => state.common.config.dynamic_conf.numlock().last_state, + }; + + // If we're enabling numlock... + if turn_on { + let mut input = |key_state| { + keyboard.input( + state, + smithay_input::Keycode::from(keysyms::KEY_Num_Lock), + key_state, + SERIAL_COUNTER.next_serial(), + 0, + // TODO update internal modifiers? + |_, _, _| smithay::input::keyboard::FilterResult::Intercept(()), + ) + }; + // Press and release the numlock key to get modifiers updated. + input(smithay_input::KeyState::Pressed); + input(smithay_input::KeyState::Released); + } state .common .shell diff --git a/src/config/mod.rs b/src/config/mod.rs index f8bd9a2f..33741a17 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -63,6 +63,7 @@ pub struct Config { #[derive(Debug)] pub struct DynamicConfig { outputs: (Option, OutputsConfig), + numlock: (Option, NumlockStateConfig), } #[derive(Debug, Deserialize, Serialize)] @@ -88,6 +89,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 { @@ -318,9 +324,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), } } @@ -368,6 +378,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_else(|| Default::default()) + } + pub fn shortcut_for_action(&self, action: &shortcuts::Action) -> Option { self.shortcuts.shortcut_for_action(action) } @@ -609,6 +637,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( diff --git a/src/input/mod.rs b/src/input/mod.rs index 8b40908e..09a9169e 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::{ @@ -69,7 +69,7 @@ use smithay::{ }, }; use tracing::{error, trace}; -use xkbcommon::xkb::{Keycode, Keysym}; +use xkbcommon::xkb::{keysyms, Keycode, Keysym}; use std::{ any::Any, @@ -1449,6 +1449,19 @@ impl State { event.time() as u64 * 1000, ); + // 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 numlock key is pressed ... + if event.key_code().raw() == keysyms::KEY_Num_Lock && event.state() == KeyState::Pressed + { + // ... then record the updated config. + // 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 = modifiers.num_lock; + } + } + // Leave move overview mode, if any modifier was released if let Some(Trigger::KeyboardMove(action_modifiers)) = shell.overview_mode().0.active_trigger() diff --git a/src/shell/seats.rs b/src/shell/seats.rs index 8fa33069..acf3bd43 100644 --- a/src/shell/seats.rs +++ b/src/shell/seats.rs @@ -12,7 +12,7 @@ use smithay::{ backend::input::{Device, DeviceCapability}, desktop::utils::bbox_from_surface_tree, input::{ - keyboard::{LedState, XkbConfig}, + keyboard::{KeyboardHandle, LedState, XkbConfig}, pointer::{CursorImageAttributes, CursorImageStatus}, Seat, SeatState, }, @@ -172,7 +172,7 @@ pub fn create_seat( output: &Output, config: &Config, name: String, -) -> Seat { +) -> (Seat, KeyboardHandle) { let mut seat = seat_state.new_wl_seat(dh, name); let userdata = seat.user_data(); userdata.insert_if_missing_threadsafe(SeatId::default); @@ -197,26 +197,28 @@ 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( - xkb_config_to_wl(&conf), - (conf.repeat_delay as i32).abs(), - (conf.repeat_rate as i32).abs(), - ) { - warn!( - ?err, - "Failed to load provided xkb config. Trying default...", - ); - seat.add_keyboard( - XkbConfig::default(), + let 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...", + ); + seat.add_keyboard( + XkbConfig::default(), + (conf.repeat_delay as i32).abs(), + (conf.repeat_rate as i32).abs(), + ) + }) .expect("Failed to load xkb configuration files"); - } seat.add_pointer(); seat.add_touch(); - seat + (seat, keyboard) } pub trait SeatExt {