diff --git a/Cargo.lock b/Cargo.lock index aec3b9902..edd194524 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -924,7 +924,7 @@ dependencies = [ [[package]] name = "cosmic-settings-config" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-settings-daemon#e37160f14d1e7ee428f973cd2848b4e95f83dfe1" +source = "git+https://github.com/krakotay/cosmic-settings-daemon?branch=caps-lock-bindings#da097d919fa5b660c72c9a0e6a517f795d449ff0" dependencies = [ "cosmic-config", "ron 0.11.0", @@ -945,7 +945,7 @@ dependencies = [ [[package]] name = "cosmic-settings-daemon-config" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-settings-daemon#e37160f14d1e7ee428f973cd2848b4e95f83dfe1" +source = "git+https://github.com/krakotay/cosmic-settings-daemon?branch=caps-lock-bindings#da097d919fa5b660c72c9a0e6a517f795d449ff0" dependencies = [ "cosmic-config", "cosmic-theme", diff --git a/Cargo.toml b/Cargo.toml index fb9fa0bcb..b337d7a09 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,10 @@ rust-version = "1.93" [workspace] members = ["cosmic-comp-config"] +[patch.'https://github.com/pop-os/cosmic-settings-daemon'] +cosmic-settings-config = { git = "https://github.com/krakotay/cosmic-settings-daemon", branch = "caps-lock-bindings"} +cosmic-settings-daemon-config = { git = "https://github.com/krakotay/cosmic-settings-daemon", branch = "caps-lock-bindings"} + [dependencies] anyhow = { version = "1.0.102", features = ["backtrace"] } bitflags = "2.11.0" diff --git a/src/config/mod.rs b/src/config/mod.rs index 1d9b9c921..d3202180a 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -5,7 +5,8 @@ use crate::{ state::{BackendData, State}, utils::prelude::OutputExt, wayland::protocols::{ - output_configuration::OutputConfigurationState, workspace::WorkspaceUpdateGuard, + keyboard_layout::KeyboardLayoutState, output_configuration::OutputConfigurationState, + workspace::WorkspaceUpdateGuard, }, }; use anyhow::Context; @@ -807,19 +808,17 @@ fn config_changed(config: cosmic_config::Config, keys: Vec, state: &mut 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); + } else if keyboard.set_modifier_state(old_modifier_state) != 0 { + keyboard.advertise_modifier_state(state); + ::led_state_changed( + state, + &seat, + keyboard.led_state(), + ); } } } + KeyboardLayoutState::refresh(state); state.common.config.cosmic_conf.xkb_config = value; } "keyboard_config" => { diff --git a/src/input/actions.rs b/src/input/actions.rs index 3d017697d..3ce75b4b3 100644 --- a/src/input/actions.rs +++ b/src/input/actions.rs @@ -9,7 +9,8 @@ use crate::{ }, utils::prelude::*, wayland::{ - handlers::xdg_activation::ActivationContext, protocols::workspace::WorkspaceUpdateGuard, + handlers::xdg_activation::ActivationContext, + protocols::{keyboard_layout::KeyboardLayoutState, workspace::WorkspaceUpdateGuard}, }, }; use cosmic_comp_config::{TileBehavior, workspace::WorkspaceLayout}; @@ -23,6 +24,7 @@ use smithay::{ #[cfg(not(feature = "debug"))] use tracing::info; use tracing::{error, warn}; +use xkbcommon::xkb::Keysym; use std::{os::unix::process::CommandExt, thread}; @@ -35,6 +37,24 @@ fn propagate_by_default(action: &shortcuts::Action) -> bool { ) } +fn rotate_layouts(layouts: &mut String, variants: &mut String, active_layout: usize) { + let mut layout_entries = layouts.split(',').collect::>(); + let mut variant_entries = variants + .split(',') + .chain(std::iter::repeat("")) + .take(layout_entries.len()) + .collect::>(); + if !layout_entries.is_empty() { + let offset = active_layout % layout_entries.len(); + layout_entries.rotate_left(offset); + variant_entries.rotate_left(offset); + } + let new_layouts = layout_entries.join(","); + let new_variants = variant_entries.join(","); + *layouts = new_layouts; + *variants = new_variants; +} + impl State { pub fn handle_action( &mut self, @@ -45,6 +65,30 @@ impl State { pattern: shortcuts::Binding, direction: Option, ) { + if let Some(keyboard) = seat.get_keyboard() { + let mut modifiers = keyboard.modifier_state(); + let clear_lock = match pattern.key { + Some(Keysym::Caps_Lock) if modifiers.caps_lock => { + modifiers.caps_lock = false; + true + } + Some(Keysym::Num_Lock) if modifiers.num_lock => { + modifiers.num_lock = false; + true + } + _ => false, + }; + + if clear_lock && keyboard.set_modifier_state(modifiers) != 0 { + keyboard.advertise_modifier_state(self); + ::led_state_changed( + self, + seat, + keyboard.led_state(), + ); + } + } + // TODO: Detect if started from login manager or tty, and only allow // `Terminate` if it will return to login manager. if self.common.shell.read().session_lock.is_some() @@ -1013,6 +1057,31 @@ impl State { self.common.shell.write().toggle_sticky_current(seat); } + Action::System(shortcuts::action::System::InputSourceSwitch) => { + if let Some(keyboard) = seat.get_keyboard() { + let active_layout = keyboard.with_xkb_state(self, |mut context| { + context.cycle_next_layout(); + context.xkb().lock().unwrap().active_layout().0 as usize + }); + KeyboardLayoutState::refresh(self); + + let mut xkb_config = self.common.config.cosmic_conf.xkb_config.clone(); + rotate_layouts( + &mut xkb_config.layout, + &mut xkb_config.variant, + active_layout, + ); + if let Err(err) = self + .common + .config + .cosmic_helper + .set("xkb_config", &xkb_config) + { + error!(?err, "Failed to persist active input source"); + } + } + } + // Gets the configured command for a given system action. Action::System(system) => { if let Some(command) = self.common.config.system_actions.get(&system) { @@ -1178,3 +1247,19 @@ fn to_previous_workspace( workspace_state, ) } + +#[cfg(test)] +mod tests { + use super::rotate_layouts; + + #[test] + fn rotating_layouts_keeps_variants_aligned() { + let mut layouts = "us,ru,de".to_string(); + let mut variants = ",phonetic".to_string(); + + rotate_layouts(&mut layouts, &mut variants, 1); + + assert_eq!(layouts, "ru,de,us"); + assert_eq!(variants, "phonetic,,"); + } +} diff --git a/src/input/mod.rs b/src/input/mod.rs index 39bff9dc0..39813e429 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -28,6 +28,7 @@ use crate::{ image_copy_capture::{SessionHolder, cursor_capture_constraints}, xwayland_keyboard_grab::XWaylandGrabSeat, }, + wayland::protocols::keyboard_layout::KeyboardLayoutState, }; use calloop::{ RegistrationToken, @@ -304,6 +305,12 @@ impl State { keyboard.modifier_state().num_lock; } } + + if previous_modifiers.serialized.layout_effective + != keyboard.modifier_state().serialized.layout_effective + { + KeyboardLayoutState::refresh(self); + } } } @@ -1633,6 +1640,8 @@ impl State { let key_matches = |binding_key: Keysym| -> bool { raw_syms.contains(&binding_key) || latin_sym.is_some_and(|sym| sym == binding_key) }; + let keycode_matches = + |binding_keycode: u32| -> bool { event.key_code().raw() == binding_keycode }; let mut shell = self.common.shell.write(); @@ -1919,6 +1928,7 @@ impl State { // is this a released (triggered) modifier-only binding? if binding.key.is_none() + && binding.keycode.is_none() && event.state() == KeyState::Released && !cosmic_modifiers_eq_smithay(&binding.modifiers, modifiers) && modifiers_queue.take(binding) @@ -1932,6 +1942,7 @@ impl State { // could this potentially become a modifier-only binding? if binding.key.is_none() + && binding.keycode.is_none() && event.state() == KeyState::Pressed && cosmic_modifiers_eq_smithay(&binding.modifiers, modifiers) { @@ -1940,9 +1951,9 @@ impl State { } // is this a normal binding? - if binding.key.is_some() + if (binding.key.is_some_and(key_matches) + || binding.keycode.is_some_and(keycode_matches)) && event.state() == KeyState::Pressed - && key_matches(binding.key.unwrap()) && cosmic_modifiers_eq_smithay(&binding.modifiers, modifiers) { modifiers_queue.clear(); diff --git a/src/wayland/protocols/keyboard_layout.rs b/src/wayland/protocols/keyboard_layout.rs index f3a44f0a8..108d1710e 100644 --- a/src/wayland/protocols/keyboard_layout.rs +++ b/src/wayland/protocols/keyboard_layout.rs @@ -161,6 +161,7 @@ where handle.with_xkb_state(state, |mut context| { context.set_layout(Layout(group)); }); + KeyboardLayoutState::refresh(state); } } zcosmic_keyboard_layout_v1::Request::Destroy => {}