From 29320bdab5d8ae651bd6c445368aff27745c63c2 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Mon, 29 Jul 2024 13:56:42 -0700 Subject: [PATCH] Add `cosmic-keyboard-layout-unstable-v1` protocol https://github.com/pop-os/cosmic-protocols/pull/28 Allows clients like `cosmic-applet-input-sources` to show which layout group is active and update it. Currently this is implemented by update the `xkb_config` to change the layout order. --- Cargo.lock | 20 +-- src/lib.rs | 5 +- src/state.rs | 4 + src/wayland/handlers/keyboard_layout.rs | 10 ++ src/wayland/handlers/mod.rs | 1 + src/wayland/protocols/keyboard_layout.rs | 185 +++++++++++++++++++++++ src/wayland/protocols/mod.rs | 1 + 7 files changed, 215 insertions(+), 11 deletions(-) create mode 100644 src/wayland/handlers/keyboard_layout.rs create mode 100644 src/wayland/protocols/keyboard_layout.rs diff --git a/Cargo.lock b/Cargo.lock index 1bc58958..9f664669 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -788,7 +788,7 @@ dependencies = [ [[package]] name = "cosmic-client-toolkit" version = "0.2.0" -source = "git+https://github.com/pop-os//cosmic-protocols?branch=main#160b086abe03cd34a8a375d7fbe47b24308d1f38" +source = "git+https://github.com/pop-os//cosmic-protocols?branch=main#0d0b0b573fe4836ede4c237dadd365033cd28296" dependencies = [ "bitflags 2.11.0", "cosmic-protocols", @@ -918,7 +918,7 @@ dependencies = [ [[package]] name = "cosmic-protocols" version = "0.2.0" -source = "git+https://github.com/pop-os//cosmic-protocols?branch=main#160b086abe03cd34a8a375d7fbe47b24308d1f38" +source = "git+https://github.com/pop-os//cosmic-protocols?branch=main#0d0b0b573fe4836ede4c237dadd365033cd28296" dependencies = [ "bitflags 2.11.0", "wayland-backend", @@ -1204,7 +1204,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -1518,7 +1518,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -2688,7 +2688,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde_core", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -2811,7 +2811,7 @@ version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "770919970f7d2f74fea948900d35e2ef64f44129e8ae4015f59de1f0aca7c2a5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -3421,7 +3421,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -4502,7 +4502,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -5094,7 +5094,7 @@ dependencies = [ "getrandom 0.4.1", "once_cell", "rustix 1.1.4", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -6113,7 +6113,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] diff --git a/src/lib.rs b/src/lib.rs index 09d1b961..4f4e5f83 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,7 +26,9 @@ use std::{ time::{Duration, Instant}, }; use tracing::{error, info, warn}; -use wayland::protocols::overlap_notify::OverlapNotifyState; +use wayland::protocols::{ + keyboard_layout::KeyboardLayoutState, overlap_notify::OverlapNotifyState, +}; use crate::wayland::handlers::compositor::client_compositor_state; @@ -331,5 +333,6 @@ fn refresh(state: &mut State) { state::Common::refresh_focus(state); OverlapNotifyState::refresh(state); state.common.update_x11_stacking_order(); + KeyboardLayoutState::refresh(state); state.last_refresh = LastRefresh::At(Instant::now()); } diff --git a/src/state.rs b/src/state.rs index 18de36ba..68824ea7 100644 --- a/src/state.rs +++ b/src/state.rs @@ -19,6 +19,7 @@ use crate::{ corner_radius::CornerRadiusState, drm::WlDrmState, image_capture_source::CosmicImageCaptureSourceState, + keyboard_layout::KeyboardLayoutState, output_configuration::OutputConfigurationState, output_power::OutputPowerState, overlap_notify::OverlapNotifyState, @@ -279,6 +280,7 @@ pub struct Common { pub overlap_notify_state: OverlapNotifyState, pub a11y_state: A11yState, pub dbus_state: DBusState, + pub keyboard_layout_state: KeyboardLayoutState, // shell-related wayland state pub xdg_shell_state: XdgShellState, @@ -682,6 +684,7 @@ impl State { AlphaModifierState::new::(dh); SinglePixelBufferState::new::(dh); FixesState::new::(dh); + let keyboard_layout_state = KeyboardLayoutState::new::(&dh, client_not_sandboxed); let idle_notifier_state = IdleNotifierState::::new(dh, handle.clone()); let idle_inhibit_manager_state = IdleInhibitManagerState::new::(dh); @@ -792,6 +795,7 @@ impl State { xwayland_shell_state, pointer_focus_state: None, dbus_state, + keyboard_layout_state, #[cfg(feature = "logind")] inhibit_lid_fd: None, diff --git a/src/wayland/handlers/keyboard_layout.rs b/src/wayland/handlers/keyboard_layout.rs new file mode 100644 index 00000000..ac1fda87 --- /dev/null +++ b/src/wayland/handlers/keyboard_layout.rs @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use crate::state::State; +use crate::wayland::protocols::keyboard_layout::{KeyboardLayoutHandler, KeyboardLayoutState}; + +impl KeyboardLayoutHandler for State { + fn keyboard_layout_state(&mut self) -> &mut KeyboardLayoutState { + &mut self.common.keyboard_layout_state + } +} diff --git a/src/wayland/handlers/mod.rs b/src/wayland/handlers/mod.rs index 34d063d0..c6461a2e 100644 --- a/src/wayland/handlers/mod.rs +++ b/src/wayland/handlers/mod.rs @@ -18,6 +18,7 @@ pub mod idle_notify; pub mod image_capture_source; pub mod image_copy_capture; pub mod input_method; +pub mod keyboard_layout; pub mod keyboard_shortcuts_inhibit; pub mod layer_shell; pub mod output; diff --git a/src/wayland/protocols/keyboard_layout.rs b/src/wayland/protocols/keyboard_layout.rs new file mode 100644 index 00000000..0d674f88 --- /dev/null +++ b/src/wayland/protocols/keyboard_layout.rs @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use cosmic_protocols::keyboard_layout::v1::server::{ + zcosmic_keyboard_layout_manager_v1::{self, ZcosmicKeyboardLayoutManagerV1}, + zcosmic_keyboard_layout_v1::{self, ZcosmicKeyboardLayoutV1}, +}; +use smithay::{ + input::{ + SeatHandler, + keyboard::{KeyboardHandle, Layout}, + }, + reexports::wayland_server::{ + Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, + }, + wayland::{Dispatch2, GlobalDispatch2}, +}; +use std::mem; +use wayland_backend::server::{ClientId, GlobalId}; + +pub trait KeyboardLayoutHandler { + fn keyboard_layout_state(&mut self) -> &mut KeyboardLayoutState; +} + +#[derive(Debug)] +pub struct KeyboardLayoutState { + pub global: GlobalId, + keyboard_layouts: Vec<(ZcosmicKeyboardLayoutV1, Option)>, +} + +impl KeyboardLayoutState { + pub fn new(dh: &DisplayHandle, client_filter: F) -> Self + where + D: GlobalDispatch + 'static, + F: for<'a> Fn(&'a Client) -> bool + Send + Sync + 'static, + { + let global = dh.create_global::( + 1, + LayoutGlobalData { + filter: Box::new(client_filter), + }, + ); + KeyboardLayoutState { + global, + keyboard_layouts: Vec::new(), + } + } + + pub fn refresh(state: &mut D) + where + D: SeatHandler + KeyboardLayoutHandler + 'static, + { + let mut keyboard_layouts = mem::take(&mut state.keyboard_layout_state().keyboard_layouts); + for (keyboard_layout, last_layout) in &mut keyboard_layouts { + if let Some(data) = keyboard_layout.data::>() { + if let Some(handle) = &data.handle { + let active_layout = handle.with_xkb_state(state, |context| { + context.xkb().lock().unwrap().active_layout() + }); + if *last_layout != Some(active_layout) { + keyboard_layout.group(active_layout.0); + *last_layout = Some(active_layout); + } + } + } + } + state.keyboard_layout_state().keyboard_layouts = keyboard_layouts; + } +} + +pub struct LayoutGlobalData { + filter: Box Fn(&'a Client) -> bool + Send + Sync>, +} + +struct LayoutManagerData; + +impl GlobalDispatch2 for LayoutGlobalData +where + D: Dispatch + 'static, +{ + fn bind( + &self, + _state: &mut D, + _handle: &DisplayHandle, + _client: &Client, + resource: New, + data_init: &mut DataInit<'_, D>, + ) { + data_init.init(resource, LayoutManagerData); + } + + fn can_view(&self, client: &Client) -> bool { + (self.filter)(client) + } +} + +impl Dispatch2 for LayoutManagerData +where + D: Dispatch>, + D: 'static, + D: SeatHandler, + D: KeyboardLayoutHandler, +{ + fn request( + &self, + state: &mut D, + _client: &Client, + _resource: &ZcosmicKeyboardLayoutManagerV1, + request: zcosmic_keyboard_layout_manager_v1::Request, + _dhandle: &DisplayHandle, + data_init: &mut DataInit<'_, D>, + ) { + match request { + zcosmic_keyboard_layout_manager_v1::Request::GetKeyboardLayout { + keyboard_layout, + keyboard, + } => { + let handle = KeyboardHandle::::from_resource(&keyboard); + let active_layout = handle.as_ref().map(|handle| { + handle.with_xkb_state(state, |context| { + context.xkb().lock().unwrap().active_layout() + }) + }); + let keyboard_layout = data_init.init(keyboard_layout, LayoutUserData { handle }); + if let Some(layout) = active_layout { + keyboard_layout.group(layout.0); + } + state + .keyboard_layout_state() + .keyboard_layouts + .push((keyboard_layout, active_layout)); + } + zcosmic_keyboard_layout_manager_v1::Request::Destroy => {} + _ => unreachable!(), + } + } +} + +#[doc(hidden)] +struct LayoutUserData { + handle: Option>, +} + +impl Dispatch2 for LayoutUserData +where + D: 'static, + D: SeatHandler, + D: KeyboardLayoutHandler, +{ + fn request( + &self, + state: &mut D, + _client: &Client, + _resource: &ZcosmicKeyboardLayoutV1, + request: zcosmic_keyboard_layout_v1::Request, + _dhandle: &DisplayHandle, + _data_init: &mut DataInit<'_, D>, + ) { + match request { + zcosmic_keyboard_layout_v1::Request::SetGroup { group } => { + if let Some(handle) = self.handle.as_ref() { + handle.with_xkb_state(state, |mut context| { + context.set_layout(Layout(group)); + }); + } + } + zcosmic_keyboard_layout_v1::Request::Destroy => {} + _ => unreachable!(), + } + } + + fn destroyed( + &self, + state: &mut D, + _client: ClientId, + keyboard_layout: &ZcosmicKeyboardLayoutV1, + ) { + let keyboard_layouts = &mut state.keyboard_layout_state().keyboard_layouts; + if let Some(idx) = keyboard_layouts + .iter() + .position(|(x, _)| x == keyboard_layout) + { + keyboard_layouts.remove(idx); + } + } +} diff --git a/src/wayland/protocols/mod.rs b/src/wayland/protocols/mod.rs index 57e05b8a..c6c0234a 100644 --- a/src/wayland/protocols/mod.rs +++ b/src/wayland/protocols/mod.rs @@ -4,6 +4,7 @@ pub mod a11y; pub mod corner_radius; pub mod drm; pub mod image_capture_source; +pub mod keyboard_layout; pub mod output_configuration; pub mod output_power; pub mod overlap_notify;