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.
This commit is contained in:
Ian Douglas Scott 2024-07-29 13:56:42 -07:00 committed by Victoria Brekenfeld
parent 031fdc389a
commit 29320bdab5
7 changed files with 215 additions and 11 deletions

20
Cargo.lock generated
View file

@ -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]]

View file

@ -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());
}

View file

@ -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::<Self>(dh);
SinglePixelBufferState::new::<Self>(dh);
FixesState::new::<Self>(dh);
let keyboard_layout_state = KeyboardLayoutState::new::<State, _>(&dh, client_not_sandboxed);
let idle_notifier_state = IdleNotifierState::<Self>::new(dh, handle.clone());
let idle_inhibit_manager_state = IdleInhibitManagerState::new::<State>(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,

View file

@ -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
}
}

View file

@ -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;

View file

@ -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<Layout>)>,
}
impl KeyboardLayoutState {
pub fn new<D, F>(dh: &DisplayHandle, client_filter: F) -> Self
where
D: GlobalDispatch<ZcosmicKeyboardLayoutManagerV1, LayoutGlobalData> + 'static,
F: for<'a> Fn(&'a Client) -> bool + Send + Sync + 'static,
{
let global = dh.create_global::<D, ZcosmicKeyboardLayoutManagerV1, _>(
1,
LayoutGlobalData {
filter: Box::new(client_filter),
},
);
KeyboardLayoutState {
global,
keyboard_layouts: Vec::new(),
}
}
pub fn refresh<D>(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::<LayoutUserData<D>>() {
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<dyn for<'a> Fn(&'a Client) -> bool + Send + Sync>,
}
struct LayoutManagerData;
impl<D> GlobalDispatch2<ZcosmicKeyboardLayoutManagerV1, D> for LayoutGlobalData
where
D: Dispatch<ZcosmicKeyboardLayoutManagerV1, LayoutManagerData> + 'static,
{
fn bind(
&self,
_state: &mut D,
_handle: &DisplayHandle,
_client: &Client,
resource: New<ZcosmicKeyboardLayoutManagerV1>,
data_init: &mut DataInit<'_, D>,
) {
data_init.init(resource, LayoutManagerData);
}
fn can_view(&self, client: &Client) -> bool {
(self.filter)(client)
}
}
impl<D> Dispatch2<ZcosmicKeyboardLayoutManagerV1, D> for LayoutManagerData
where
D: Dispatch<ZcosmicKeyboardLayoutV1, LayoutUserData<D>>,
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::<D>::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<D: SeatHandler> {
handle: Option<KeyboardHandle<D>>,
}
impl<D> Dispatch2<ZcosmicKeyboardLayoutV1, D> for LayoutUserData<D>
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);
}
}
}

View file

@ -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;