diff --git a/src/common.rs b/src/common.rs index b284ee5..51db69b 100644 --- a/src/common.rs +++ b/src/common.rs @@ -10,11 +10,14 @@ use cosmic::iced::{self, Rectangle, Size, Subscription}; use cosmic::surface::corner_radius::rounded_rect_strips; use cosmic::widget::rectangle_tracker::{RectangleUpdate, rectangle_tracker_subscription}; use cosmic::widget::{self, RectangleTracker}; -use cosmic_config::{ConfigSet, CosmicConfigEntry}; -use cosmic_greeter_daemon::{BgSource, CosmicCompConfig, UserData}; +use cosmic_greeter_daemon::{BgSource, UserData}; +use cosmic_protocols::keyboard_layout::v1::client::zcosmic_keyboard_layout_v1::ZcosmicKeyboardLayoutV1; use std::collections::{HashMap, HashSet}; use std::sync::Arc; use wayland_client::protocol::wl_output::WlOutput; +use wayland_client::{Connection, Proxy}; + +use crate::keyboard_layout_wayland; pub const DEFAULT_MENU_ITEM_HEIGHT: f32 = 36.; @@ -26,13 +29,15 @@ pub struct ActiveLayout { } pub struct Common { + pub wayland_connection: Option, + pub keyboard_layout: Option, + pub current_keyboard_layout: usize, pub active_layouts: Vec, pub active_surface_id_opt: Option, pub on_battery: bool, pub battery_percent: f64, pub charging_limit: Option, pub caps_lock: bool, - pub comp_config_handler: Option, pub core: Core, pub error_opt: Option, pub fallback_background: widget::image::Handle, @@ -70,6 +75,7 @@ pub enum Message { Tick, Tz(jiff::tz::TimeZone), Rectangle(RectangleUpdate<(SurfaceId, bool)>), + KeyboardLayoutWayland(keyboard_layout_wayland::Event), } impl + Send + 'static> Common { @@ -82,17 +88,6 @@ impl + Send + 'static> Common { core.window.show_minimize = false; core.window.use_template = false; - let comp_config_handler = match cosmic_config::Config::new( - "com.system76.CosmicComp", - CosmicCompConfig::VERSION, - ) { - Ok(config_handler) => Some(config_handler), - Err(err) => { - tracing::error!("failed to create cosmic-comp config handler: {}", err); - None - } - }; - let layouts_opt = match xkb_data::all_keyboard_layouts() { Ok(ok) => Some(Arc::new(ok)), Err(err) => { @@ -102,10 +97,12 @@ impl + Send + 'static> Common { }; let app = Self { + wayland_connection: None, + keyboard_layout: None, + current_keyboard_layout: 0, active_layouts: Vec::new(), active_surface_id_opt: None, caps_lock: false, - comp_config_handler, core, error_opt: None, fallback_background: widget::image::Handle::from_bytes( @@ -142,27 +139,6 @@ impl + Send + 'static> Common { ) } - pub fn set_xkb_config(&self, user_data: &UserData) { - if let Some(mut xkb_config) = user_data.xkb_config_opt.clone() { - xkb_config.layout = String::new(); - xkb_config.variant = String::new(); - for (i, layout) in self.active_layouts.iter().enumerate() { - if i > 0 { - xkb_config.layout.push(','); - xkb_config.variant.push(','); - } - xkb_config.layout.push_str(&layout.layout); - xkb_config.variant.push_str(&layout.variant); - } - if let Some(comp_config_handler) = &self.comp_config_handler { - match comp_config_handler.set("xkb_config", xkb_config) { - Ok(()) => tracing::info!("updated cosmic-comp xkb_config"), - Err(err) => tracing::error!("failed to update cosmic-comp xkb_config: {}", err), - } - } - } - } - pub fn update_wallpapers(&mut self, user_data: &UserData) { for (_output, surface_id) in self.surface_ids.iter() { if self.surface_images.contains_key(surface_id) { @@ -301,6 +277,11 @@ impl + Send + 'static> Common { network_icon_opt.map(|name| widget::icon::from_name(name).into()); } Message::OutputEvent(output_event, output) => { + if self.wayland_connection.is_none() { + if let Some(backend) = output.backend().upgrade() { + self.wayland_connection = Some(Connection::from_backend(backend)); + } + } if let Some(on_output_event) = &self.on_output_event { return Task::done(cosmic::Action::App(on_output_event(output_event, output))); } @@ -349,6 +330,14 @@ impl + Send + 'static> Common { self.rectangle_tracker.replace(tracker); } }, + Message::KeyboardLayoutWayland(w) => match w { + keyboard_layout_wayland::Event::KeyboardLayout(keyboard_layout) => { + self.keyboard_layout = Some(keyboard_layout); + } + keyboard_layout_wayland::Event::Group(group) => { + self.current_keyboard_layout = group; + } + }, } Task::none() } @@ -441,6 +430,12 @@ impl + Send + 'static> Common { _ => None, })); + if let Some(conn) = self.wayland_connection.clone() { + subscriptions.push( + keyboard_layout_wayland::subscription(conn).map(Message::KeyboardLayoutWayland), + ); + } + #[cfg(feature = "networkmanager")] { subscriptions.push(crate::networkmanager::subscription().map(Message::NetworkIcon)); diff --git a/src/greeter.rs b/src/greeter.rs index e10dc3b..929459a 100644 --- a/src/greeter.rs +++ b/src/greeter.rs @@ -586,7 +586,7 @@ impl App { for (i, layout) in self.common.active_layouts.iter().enumerate() { items.push(menu_checklist( &layout.description, - i == 0, + i == self.common.current_keyboard_layout, Message::KeyboardLayout(i), )); } @@ -1029,19 +1029,6 @@ impl App { } } - fn set_xkb_config(&self) { - let user_data = match self - .selected_username - .data_idx - .and_then(|i| self.flags.user_datas.get(i)) - { - Some(some) => some, - None => return, - }; - - self.common.set_xkb_config(user_data); - } - fn update_user_data(&mut self) -> Task { let user_data = match self .selected_username @@ -1056,9 +1043,6 @@ impl App { self.common.update_user_data(user_data); - // Ensure that user's xkb config is used - self.common.set_xkb_config(user_data); - if let Some(builder) = &user_data.theme_builder_opt { self.theme_builder = builder.clone(); } @@ -1572,10 +1556,10 @@ impl cosmic::Application for App { } } Message::KeyboardLayout(layout_i) => { - if layout_i < self.common.active_layouts.len() { - self.common.active_layouts.swap(0, layout_i); - self.set_xkb_config(); + if let Some(keyboard_layout) = &self.common.keyboard_layout { + keyboard_layout.set_group(layout_i as u32); } + self.common.current_keyboard_layout = layout_i; if self.dropdown_opt == Some(Dropdown::Keyboard) { self.dropdown_opt = None } diff --git a/src/keyboard_layout_wayland.rs b/src/keyboard_layout_wayland.rs new file mode 100644 index 0000000..93221d9 --- /dev/null +++ b/src/keyboard_layout_wayland.rs @@ -0,0 +1,157 @@ +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}, + }, + wayland_client::{ + Connection, QueueHandle, delegate_noop, + globals::registry_queue_init, + protocol::{wl_keyboard, wl_seat}, + }, +}; +use cosmic::iced; +use cosmic::iced::futures::{SinkExt, channel::mpsc, executor::block_on}; +use std::{hash::Hash, thread}; + +#[derive(Clone, Debug)] +pub enum Event { + KeyboardLayout(ZcosmicKeyboardLayoutV1), + Group(usize), +} + +pub fn subscription(connection: Connection) -> iced::Subscription { + #[derive(Clone)] + struct WaylandSubscription(Connection); + impl Hash for WaylandSubscription { + fn hash(&self, state: &mut H) { + self.0.backend().display_id().hash(state); + } + } + iced::Subscription::run_with( + WaylandSubscription(connection), + |WaylandSubscription(connection)| { + let connection = connection.clone(); + iced::stream::channel(8, move |sender| async move { + thread::spawn(move || thread(connection, sender)); + }) + }, + ) +} + +struct Keyboard { + seat: wl_seat::WlSeat, + keyboard_layout: ZcosmicKeyboardLayoutV1, +} + +impl Drop for Keyboard { + fn drop(&mut self) { + self.keyboard_layout.destroy(); + } +} + +struct AppData { + seat_state: SeatState, + registry_state: RegistryState, + keyboard_layout_state: KeyboardLayoutState, + keyboard: 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, + ) { + let _ = block_on(self.sender.send(Event::Group(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 && self.keyboard.is_none() { + let keyboard = seat.get_keyboard(qh, ()); + let keyboard_layout = self + .keyboard_layout_state + .get_keyboard_layout(&keyboard, qh); + keyboard.release(); + + if let Some(keyboard_layout) = keyboard_layout { + let _ = block_on( + self.sender + .send(Event::KeyboardLayout(keyboard_layout.clone())), + ); + self.keyboard = Some(Keyboard { + seat, + keyboard_layout, + }); + } else { + keyboard.release(); + } + } + } + + fn remove_capability( + &mut self, + _conn: &Connection, + _: &QueueHandle, + seat: wl_seat::WlSeat, + _capability: Capability, + ) { + self.keyboard.take_if(|x| x.seat == seat); + } + + fn remove_seat(&mut self, _: &Connection, _: &QueueHandle, seat: wl_seat::WlSeat) { + self.keyboard.take_if(|x| x.seat == seat); + } +} + +fn thread(conn: Connection, sender: mpsc::Sender) { + 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, + keyboard: None, + sender, + }; + loop { + event_queue.blocking_dispatch(&mut app_data).unwrap(); + } +} + +sctk::delegate_registry!(AppData); +sctk::delegate_seat!(AppData); +cctk::delegate_keyboard_layout!(AppData); +delegate_noop!(AppData: ignore wl_keyboard::WlKeyboard); diff --git a/src/lib.rs b/src/lib.rs index 8535a1e..91736b9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,8 @@ mod wayland; mod common; +mod keyboard_layout_wayland; + mod localize; #[cfg(feature = "logind")] diff --git a/src/locker.rs b/src/locker.rs index 5bf0569..952d00a 100644 --- a/src/locker.rs +++ b/src/locker.rs @@ -421,7 +421,7 @@ impl App { for (i, layout) in self.common.active_layouts.iter().enumerate() { items.push(menu_checklist( &layout.description, - i == 0, + i == self.common.current_keyboard_layout, Message::KeyboardLayout(i), )); } @@ -1044,10 +1044,10 @@ impl cosmic::Application for App { } }, Message::KeyboardLayout(layout_i) => { - if layout_i < self.common.active_layouts.len() { - self.common.active_layouts.swap(0, layout_i); - self.common.set_xkb_config(&self.flags.user_data); + if let Some(keyboard_layout) = &self.common.keyboard_layout { + keyboard_layout.set_group(layout_i as u32); } + self.common.current_keyboard_layout = layout_i; if self.dropdown_opt == Some(Dropdown::Keyboard) { self.dropdown_opt = None; return self.common.dropdown_blur_rects(false);