@@ -4,6 +4,7 @@ |
| 4 | 4 | |
| 5 | 5 | use anyhow::{Context, Result}; |
| 6 | 6 | use x11rb::connection::Connection; |
| 7 | +use x11rb::protocol::xkb::{self, ConnectionExt as XkbConnectionExt}; |
| 7 | 8 | use x11rb::protocol::xproto::*; |
| 8 | 9 | use x11rb::rust_connection::RustConnection; |
| 9 | 10 | use x11rb::wrapper::ConnectionExt as _; |
@@ -179,6 +180,10 @@ impl GreeterWindow { |
| 179 | 180 | .context("Failed to set input focus")?; |
| 180 | 181 | conn.flush()?; |
| 181 | 182 | |
| 183 | + // Sync X11 keyboard lock state with kernel state |
| 184 | + // X11 doesn't query kernel state at startup, so we must do it manually |
| 185 | + sync_keyboard_locks(&conn); |
| 186 | + |
| 182 | 187 | tracing::info!(width, height, "Created greeter window"); |
| 183 | 188 | |
| 184 | 189 | Ok(Self { |
@@ -310,3 +315,109 @@ impl Drop for GreeterWindow { |
| 310 | 315 | let _ = self.conn.flush(); |
| 311 | 316 | } |
| 312 | 317 | } |
| 318 | + |
| 319 | +/// Sync X11 keyboard lock state with kernel state |
| 320 | +/// |
| 321 | +/// X11/Xorg doesn't query the kernel's keyboard lock state at startup - it only |
| 322 | +/// tracks state changes from key events. When returning from a Wayland session, |
| 323 | +/// X11 starts fresh and doesn't know that caps/num lock was already active. |
| 324 | +/// |
| 325 | +/// This function queries the kernel's lock state via evdev and sets X11's XKB |
| 326 | +/// state to match, ensuring the greeter's keyboard behavior matches the |
| 327 | +/// physical keyboard state. |
| 328 | +fn sync_keyboard_locks(conn: &RustConnection) { |
| 329 | + |
| 330 | + // Initialize XKB extension |
| 331 | + if let Err(e) = conn.xkb_use_extension(1, 0) { |
| 332 | + tracing::warn!("Failed to initialize XKB: {}", e); |
| 333 | + return; |
| 334 | + } |
| 335 | + |
| 336 | + // Find a keyboard device and query its lock state |
| 337 | + let (caps_on, num_on) = match query_kernel_lock_state() { |
| 338 | + Some(state) => state, |
| 339 | + None => { |
| 340 | + tracing::debug!("Could not query kernel keyboard state"); |
| 341 | + return; |
| 342 | + } |
| 343 | + }; |
| 344 | + |
| 345 | + tracing::info!("Kernel keyboard state: caps_lock={}, num_lock={}", caps_on, num_on); |
| 346 | + |
| 347 | + // Build modifier mask to match kernel state |
| 348 | + let mut mod_locks = ModMask::from(0u16); |
| 349 | + if caps_on { |
| 350 | + mod_locks |= ModMask::LOCK; |
| 351 | + } |
| 352 | + if num_on { |
| 353 | + mod_locks |= ModMask::M2; |
| 354 | + } |
| 355 | + |
| 356 | + // Set X11 XKB state to match kernel |
| 357 | + let affect_locks = ModMask::LOCK | ModMask::M2; |
| 358 | + match conn.xkb_latch_lock_state( |
| 359 | + xkb::ID::USE_CORE_KBD.into(), |
| 360 | + affect_locks, |
| 361 | + mod_locks, |
| 362 | + false, |
| 363 | + xkb::Group::M1, |
| 364 | + ModMask::from(0u16), |
| 365 | + false, |
| 366 | + 0u16, |
| 367 | + ) { |
| 368 | + Ok(_) => { |
| 369 | + let _ = conn.flush(); |
| 370 | + tracing::debug!("Synced X11 lock state with kernel"); |
| 371 | + } |
| 372 | + Err(e) => { |
| 373 | + tracing::warn!("Failed to sync keyboard locks: {}", e); |
| 374 | + } |
| 375 | + } |
| 376 | +} |
| 377 | + |
| 378 | +/// Query kernel keyboard lock state via evdev EVIOCGLED ioctl |
| 379 | +fn query_kernel_lock_state() -> Option<(bool, bool)> { |
| 380 | + use std::fs::{self, File}; |
| 381 | + use std::os::unix::io::AsRawFd; |
| 382 | + |
| 383 | + // evdev LED indices |
| 384 | + const LED_NUML: u8 = 0; |
| 385 | + const LED_CAPSL: u8 = 1; |
| 386 | + |
| 387 | + // EVIOCGLED ioctl - read LED state bitmap |
| 388 | + // _IOR('E', 0x19, len) where len is LED_MAX/8+1 bytes |
| 389 | + // For typical keyboards, we just need 1 byte |
| 390 | + const EVIOCGLED_1: libc::c_ulong = 0x80014519; |
| 391 | + |
| 392 | + // Find keyboard devices |
| 393 | + let input_dir = fs::read_dir("/dev/input").ok()?; |
| 394 | + |
| 395 | + for entry in input_dir.flatten() { |
| 396 | + let path = entry.path(); |
| 397 | + let name = path.file_name()?.to_str()?; |
| 398 | + |
| 399 | + // Only check event devices |
| 400 | + if !name.starts_with("event") { |
| 401 | + continue; |
| 402 | + } |
| 403 | + |
| 404 | + // Try to open and query |
| 405 | + if let Ok(file) = File::open(&path) { |
| 406 | + let mut leds: u8 = 0; |
| 407 | + let fd = file.as_raw_fd(); |
| 408 | + |
| 409 | + // Query LED state |
| 410 | + let ret = unsafe { |
| 411 | + libc::ioctl(fd, EVIOCGLED_1, &mut leds as *mut u8) |
| 412 | + }; |
| 413 | + |
| 414 | + if ret >= 0 { |
| 415 | + let caps_on = (leds & (1 << LED_CAPSL)) != 0; |
| 416 | + let num_on = (leds & (1 << LED_NUML)) != 0; |
| 417 | + return Some((caps_on, num_on)); |
| 418 | + } |
| 419 | + } |
| 420 | + } |
| 421 | + |
| 422 | + None |
| 423 | +} |