diff --git a/Cargo.lock b/Cargo.lock index d7e3b5586..9597915ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1235,7 +1235,9 @@ dependencies = [ name = "cosmic-applet-input-sources" version = "0.1.0" dependencies = [ + "cosmic-client-toolkit", "cosmic-comp-config", + "futures", "i18n-embed", "i18n-embed-fl", "libcosmic", @@ -1245,6 +1247,7 @@ dependencies = [ "tracing-log", "tracing-subscriber", "xkb-data", + "xkbcommon 0.7.0", ] [[package]] @@ -1434,7 +1437,7 @@ dependencies = [ [[package]] name = "cosmic-client-toolkit" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-protocols//?branch=main#d0e95be25e423cfe523b11111a3666ed7aaf0dc4" +source = "git+https://github.com/pop-os/cosmic-protocols//?branch=keymap#500e76ac0da6bae322dc5b3a2e845bf714164e3c" dependencies = [ "bitflags 2.9.4", "cosmic-protocols", @@ -1570,7 +1573,7 @@ dependencies = [ [[package]] name = "cosmic-protocols" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-protocols//?branch=main#d0e95be25e423cfe523b11111a3666ed7aaf0dc4" +source = "git+https://github.com/pop-os/cosmic-protocols//?branch=keymap#500e76ac0da6bae322dc5b3a2e845bf714164e3c" dependencies = [ "bitflags 2.9.4", "wayland-backend", diff --git a/Cargo.toml b/Cargo.toml index a8c350941..2b0e9baf2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -85,5 +85,7 @@ ignored = ["libcosmic"] sctk = { package = "smithay-client-toolkit", version = "0.20.0" } [patch."https://github.com/pop-os/cosmic-protocols"] -cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols//", branch = "main" } -cosmic-client-toolkit = { git = "https://github.com/pop-os/cosmic-protocols//", branch = "main" } +# cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols//", branch = "main" } +# cosmic-client-toolkit = { git = "https://github.com/pop-os/cosmic-protocols//", branch = "main" } +cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols//", branch = "keymap" } +cosmic-client-toolkit = { git = "https://github.com/pop-os/cosmic-protocols//", branch = "keymap" } diff --git a/cosmic-applet-input-sources/Cargo.toml b/cosmic-applet-input-sources/Cargo.toml index adeac40ad..779903763 100644 --- a/cosmic-applet-input-sources/Cargo.toml +++ b/cosmic-applet-input-sources/Cargo.toml @@ -16,3 +16,6 @@ tracing-log.workspace = true tracing-subscriber.workspace = true tracing.workspace = true xkb-data = "0.2" +cctk.workspace = true +xkbcommon = "0.7.0" +futures.workspace = true diff --git a/cosmic-applet-input-sources/src/lib.rs b/cosmic-applet-input-sources/src/lib.rs index 64de4cf70..6777be29f 100644 --- a/cosmic-applet-input-sources/src/lib.rs +++ b/cosmic-applet-input-sources/src/lib.rs @@ -2,7 +2,9 @@ // SPDX-License-Identifier: GPL-3.0-only mod localize; +mod wayland; +use cctk::cosmic_protocols::keyboard_layout::v1::client::zcosmic_keyboard_layout_v1::ZcosmicKeyboardLayoutV1; use cosmic::iced::{Alignment, Length}; use cosmic::{ app, @@ -77,6 +79,9 @@ pub struct Window { comp_config_handler: Option, layouts: Vec, active_layouts: Vec, + layout_list: Vec, + keyboard_layout: Option, + layout_index: usize, } #[derive(Clone, Debug)] @@ -87,6 +92,7 @@ pub enum Message { SetActiveLayout(usize), KeyboardSettings, Surface(surface::Action), + Wayland(wayland::Event), } #[derive(Debug)] @@ -119,6 +125,9 @@ impl cosmic::Application for Window { popup: None, comp_config: flags.comp_config, active_layouts: Vec::new(), + layout_list: Vec::new(), + keyboard_layout: None, + layout_index: 0, }; (window, Task::none()) } @@ -161,31 +170,11 @@ impl cosmic::Application for Window { tokio::spawn(cosmic::process::spawn(cmd)); } Message::SetActiveLayout(pos) => { - if pos == 0 { - return Task::none(); - } - - self.active_layouts.swap(0, pos); - let mut new_layout = String::new(); - let mut new_variant = String::new(); - - for layout in &self.active_layouts { - new_layout.push_str(&layout.layout); - new_layout.push(','); - new_variant.push_str(&layout.variant); - new_variant.push(','); - } - - let _excess_comma = new_layout.pop(); - let _excess_comma = new_variant.pop(); - - self.comp_config.xkb_config.layout = new_layout; - self.comp_config.xkb_config.variant = new_variant; - if let Some(comp_config_handler) = &self.comp_config_handler { - if let Err(err) = - comp_config_handler.set("xkb_config", &self.comp_config.xkb_config) - { - tracing::error!("Failed to set config 'xkb_config' {err}"); + if let Some(keyboard_layout) = &self.keyboard_layout { + keyboard_layout.set_group(pos as u32); + use cctk::wayland_client::{Connection, Proxy}; + if let Some(backend) = keyboard_layout.backend().upgrade() { + backend.flush(); } } } @@ -194,6 +183,17 @@ impl cosmic::Application for Window { cosmic::app::Action::Surface(a), )); } + Message::Wayland(event) => match event { + wayland::Event::LayoutList(layouts) => { + self.layout_list = layouts; + } + wayland::Event::Layout(index) => { + self.layout_index = index; + } + wayland::Event::KeyboardLayout(keyboard_layout) => { + self.keyboard_layout = Some(keyboard_layout); + } + }, } Task::none() @@ -201,9 +201,9 @@ impl cosmic::Application for Window { fn view(&self) -> Element<'_, Self::Message> { let input_source_text = self.core.applet.text( - self.active_layouts - .first() - .map_or("", |l| l.layout.as_str()), + self.layout_list + .get(self.layout_index) + .map_or("", |s| s.as_str()), ); cosmic::widget::button::custom( @@ -239,15 +239,15 @@ impl cosmic::Application for Window { } = theme::active().cosmic().spacing; let mut content_list = - widget::column::with_capacity(4 + self.active_layouts.len()).padding([8, 0]); - for (id, layout) in self.active_layouts.iter().enumerate() { - let group = widget::column::with_capacity(2) - .push(widget::text::body(layout.description.as_str())) - .push(widget::text::caption(layout.layout.as_str())); + widget::column::with_capacity(4 + self.layout_list.len()).padding([8, 0]); + for (id, layout) in self.layout_list.iter().enumerate() { + let group = widget::column::with_capacity(2).push(widget::text::body(layout)); + //.push(widget::text::body(layout.description.as_str())) + //.push(widget::text::caption(layout.layout.as_str())); content_list = content_list .push(applet::menu_button(group).on_press(Message::SetActiveLayout(id))); } - if !self.active_layouts.is_empty() { + if !self.layout_list.is_empty() { content_list = content_list.push( applet::padded_control(widget::divider::horizontal::default()) .padding([space_xxs, space_s]) @@ -263,18 +263,21 @@ impl cosmic::Application for Window { } fn subscription(&self) -> Subscription { - self.core - .watch_config("com.system76.CosmicComp") - .map(|update| { - if !update.errors.is_empty() { - tracing::error!( - "errors loading config {:?}: {:?}", - update.keys, - update.errors - ); - } - Message::CompConfig(Box::new(update.config)) - }) + Subscription::batch([ + self.core + .watch_config("com.system76.CosmicComp") + .map(|update| { + if !update.errors.is_empty() { + tracing::error!( + "errors loading config {:?}: {:?}", + update.keys, + update.errors + ); + } + Message::CompConfig(Box::new(update.config)) + }), + wayland::subscription().map(Message::Wayland), + ]) } fn style(&self) -> Option { @@ -294,7 +297,6 @@ impl Window { .chain(std::iter::repeat("")); 'outer: for (layout, variant) in layouts.zip(variants) { - println!("{layout} : {variant}"); for xkb_layout in &self.layouts { if layout != xkb_layout.name() { continue; diff --git a/cosmic-applet-input-sources/src/wayland.rs b/cosmic-applet-input-sources/src/wayland.rs new file mode 100644 index 000000000..df795b157 --- /dev/null +++ b/cosmic-applet-input-sources/src/wayland.rs @@ -0,0 +1,218 @@ +use cctk::{ + cosmic_protocols::keyboard_layout::v1::client::zcosmic_keyboard_layout_v1::ZcosmicKeyboardLayoutV1, + keyboard_layout::{KeyboardLayoutHandler, KeyboardLayoutState}, + sctk::{ + self, + registry::{ProvidesRegistryState, RegistryState}, + seat::{ + Capability, SeatHandler, SeatState, + keyboard::{KeyEvent, KeyboardHandler, Keymap, Keysym, Modifiers, RawModifiers}, + }, + }, + wayland_client::{ + Connection, QueueHandle, + globals::registry_queue_init, + protocol::{wl_keyboard, wl_seat, wl_surface}, + }, +}; +use cosmic::iced_futures; +use futures::{channel::mpsc, executor::block_on, SinkExt}; +use std::thread; +use xkbcommon::xkb; + +#[derive(Clone, Debug)] +pub enum Event { + // TODO layout, description, variant? + LayoutList(Vec), + Layout(usize), + KeyboardLayout(ZcosmicKeyboardLayoutV1), +} + +pub fn subscription() -> iced_futures::Subscription { + iced_futures::Subscription::run(|| { + iced_futures::stream::channel(8, |sender| async { + thread::spawn(|| thread(sender)); + }) + }) +} + +struct AppData { + seat_state: SeatState, + registry_state: RegistryState, + keyboard_layout_state: KeyboardLayoutState, + keyboard: Option, + keymap: Option, + sender: mpsc::Sender, +} + +impl KeyboardLayoutHandler for AppData { + fn group( + &mut self, + conn: &Connection, + qh: &QueueHandle, + keyboard: &wl_keyboard::WlKeyboard, + keyboard_layout: &ZcosmicKeyboardLayoutV1, + group: u32, + ) { + block_on(self.sender.send(Event::Layout(group as usize))); + } +} + +impl ProvidesRegistryState for AppData { + fn registry(&mut self) -> &mut RegistryState { + &mut self.registry_state + } + + sctk::registry_handlers![SeatState,]; +} + +impl SeatHandler for AppData { + fn seat_state(&mut self) -> &mut SeatState { + &mut self.seat_state + } + + fn new_seat(&mut self, _: &Connection, _: &QueueHandle, _: wl_seat::WlSeat) {} + + fn new_capability( + &mut self, + _conn: &Connection, + qh: &QueueHandle, + seat: wl_seat::WlSeat, + capability: Capability, + ) { + if capability == Capability::Keyboard { + // TODO multiple seats? + let keyboard = self.seat_state.get_keyboard(qh, &seat, None).unwrap(); + let keyboard_layout = self.keyboard_layout_state.get_keyboard_layout(&keyboard, qh); + self.keyboard = Some(keyboard); + + if let Some(keyboard_layout) = keyboard_layout { + block_on(self.sender.send(Event::KeyboardLayout(keyboard_layout))); + } + } + } + + fn remove_capability( + &mut self, + _conn: &Connection, + _: &QueueHandle, + _: wl_seat::WlSeat, + _capability: Capability, + ) { + } + + fn remove_seat(&mut self, _: &Connection, _: &QueueHandle, _: wl_seat::WlSeat) {} +} + +impl KeyboardHandler for AppData { + fn enter( + &mut self, + _: &Connection, + _: &QueueHandle, + _: &wl_keyboard::WlKeyboard, + _surface: &wl_surface::WlSurface, + _: u32, + _: &[u32], + _keysyms: &[Keysym], + ) { + } + + fn leave( + &mut self, + _: &Connection, + _: &QueueHandle, + _: &wl_keyboard::WlKeyboard, + _surface: &wl_surface::WlSurface, + _: u32, + ) { + } + + fn press_key( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _: &wl_keyboard::WlKeyboard, + _: u32, + _event: KeyEvent, + ) { + } + + fn repeat_key( + &mut self, + _: &Connection, + _: &QueueHandle, + _: &wl_keyboard::WlKeyboard, + _: u32, + _event: KeyEvent, + ) { + } + + fn release_key( + &mut self, + _: &Connection, + _: &QueueHandle, + _: &wl_keyboard::WlKeyboard, + _: u32, + _event: KeyEvent, + ) { + } + + fn update_modifiers( + &mut self, + _: &Connection, + _: &QueueHandle, + _: &wl_keyboard::WlKeyboard, + _serial: u32, + _modifiers: Modifiers, + _raw_modifiers: RawModifiers, + _layout: u32, + ) { + } + + fn update_keymap( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _keyboard: &wl_keyboard::WlKeyboard, + keymap: Keymap<'_>, + ) { + let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS); + let keymap = xkb::Keymap::new_from_string( + &context, + keymap.as_string(), + xkb::KEYMAP_FORMAT_TEXT_V1, + xkb::KEYMAP_COMPILE_NO_FLAGS, + ) + .unwrap(); + + block_on(self.sender.send(Event::LayoutList(keymap.layouts().map(|x| x.to_owned()).collect()))); + + self.keymap = Some(keymap); + } +} + +fn thread(sender: mpsc::Sender) { + let conn = Connection::connect_to_env().unwrap(); + let (globals, mut event_queue) = registry_queue_init(&conn).unwrap(); + let qh = event_queue.handle(); + let registry_state = RegistryState::new(&globals); + let seat_state = SeatState::new(&globals, &qh); + let keyboard_layout_state = KeyboardLayoutState::new(®istry_state, &qh); + + let mut app_data = AppData { + seat_state, + registry_state, + keyboard_layout_state, + keymap: None, + keyboard: None, + sender + }; + loop { + event_queue.blocking_dispatch(&mut app_data).unwrap(); + } +} + +sctk::delegate_registry!(AppData); +sctk::delegate_seat!(AppData); +sctk::delegate_keyboard!(AppData); +cctk::delegate_keyboard_layout!(AppData);