Repository: wezterm/wezterm (language: rust)

Issue:
Wezterm windows hang when launched under chromeos's crostini
### What Operating System(s) are you seeing this problem on?

Linux Wayland

### Which Wayland compositor or X11 Window manager(s) are you using?

ChromeOS implements a wayland proxy compositor known as somellier

### WezTerm version

latest 

### Did you try the latest nightly build to see if the issue is better (or worse!) than your current version?

Yes, and I updated the version box above to show the version of the nightly that I tried

### Describe the bug

So when launched under chromeos's linux containers, most wayland apps work fine.
However, wezterm hangs.  

What is happening under the covers is that it is waiting for a configure event after creating the window, but the event never comes.
This is true regardless of backend (Software, OpenGL, WebGpu).
This is true regardless of window decorations between server or client or none.
enable_wayland = false works fine.

I actually implemented support for wayland's activation protocol  (since it was like 5 lines of code) but it turns out the proxy compositor doesn't use it.

I also played around with where commit is called, no luck.

Note that all of the smithay client toolkit examples work fine and happily show windows.

Logs at the trace level show nothing interesting.
Happy to debug on my own, produce logs, etc, if anyone has any ideas on where this might be going wrong.


### To Reproduce

_No response_

### Configuration

no config

### Expected Behavior

Wezterm is visible

### Logs

_No response_

### Anything else?

_No response_


Discussion / hints:
So  this is caused by the get_keyboard call in the seat state handling.
As soon as that happens, everything stops.
If i move it to happen after window creation, we get the configure, but everything still stops after the get_keyboard call.

The toolkit examples use the keyboard functionality of the toolkit, and work.

Once we receive the keymap and repeat info, everything dies :)

>  0.8206 A: wl_keyboard@11a.keymap(format=1:xkb_v1, fd=fd 23, size=47395) ↲
>  0.8206 A: wl_keyboard@11a.repeat_info(rate=20, delay=500) ↲
> 

When we try to read the keymap, we think it's a pipe but it's not, and get stuck forever.

What is happening is that later versions of the  protocol require that we mmap the file descriptor, so neither read_at nor treating it like a pipe work.

I have working versions of two fixes:

1. We move to using they smithay toolkit's keyboardhandler (under the covers it uses xkbcommon::xkb::new_from_fd which mmap's the file), and get rid of the low level handling we use.  This is a big hammer, and just moves the problem of keeping up with whatever we have to do here to the toolkit, and we will never see it again.
There is a pretty darn close mapping between the handler events and what we need.  The only slightly annoying thing is dealing with modifiers (we don't get the masks, so we have to move to using xkb's update_key)

or

2. We use the xkbcommon::xkb::new_from_fd on the keymap we get ourselves, and change nothing else.  This works now but who knows what the protocol will do next.


(Of course, i can submit PR's for both and we could just start with #2 and move to #1 later or whatever)


Fix patch:
diff --git a/window/src/os/wayland/keyboard.rs b/window/src/os/wayland/keyboard.rs
index e9c03ddafb8..6542b7065f2 100644
--- a/window/src/os/wayland/keyboard.rs
+++ b/window/src/os/wayland/keyboard.rs
@@ -1,10 +1,9 @@
 use std::borrow::BorrowMut;
-use std::io::Read;
-use std::os::fd::{AsRawFd, FromRawFd};
-use std::os::unix::fs::FileExt;
 
 use wayland_client::protocol::wl_keyboard::{Event as WlKeyboardEvent, KeymapFormat, WlKeyboard};
 use wayland_client::{Dispatch, Proxy};
+use xkbcommon::xkb;
+use xkbcommon::xkb::CONTEXT_NO_FLAGS;
 
 use crate::x11::KeyboardWithFallback;
 
@@ -59,37 +58,37 @@ impl Dispatch<WlKeyboard, KeyboardData> for WaylandState {
                 *state.key_repeat_delay.borrow_mut() = *delay;
             }
             WlKeyboardEvent::Keymap { format, fd, size } => {
-                let mut file = unsafe { std::fs::File::from_raw_fd(fd.as_raw_fd()) };
                 match format.into_result().unwrap() {
                     KeymapFormat::XkbV1 => {
-                        let mut data = vec![0u8; *size as usize];
-                        // If we weren't passed a pipe, be sure to explicitly
-                        // read from the start of the file
-                        match file.read_exact_at(&mut data, 0) {
-                            Ok(_) => {}
-                            Err(err) => {
-                                // ideally: we check for:
-                                // err.kind() == std::io::ErrorKind::NotSeekable
-                                // but that is not yet in stable rust
-                                if err.raw_os_error() == Some(libc::ESPIPE) {
-                                    // It's a pipe, which cannot be seeked, so we
-                                    // just try reading from the current pipe position
-                                    file.read(&mut data).expect("read from Keymap fd/pipe");
-                                } else {
-                                    return Err(err).expect("read_exact_at from Keymap fd");
+                        // In later protocol versions, the fd must be privately mmap'd.
+                        // We let xkb handle this and then turn it back into a string.
+                        #[allow(unused_unsafe)] // Upstream release will change this
+                        match unsafe {
+                            let context = xkb::Context::new(CONTEXT_NO_FLAGS);
+                            let cloned_fd = fd.try_clone().expect("Couldn't clone owned fd");
+                            xkb::Keymap::new_from_fd(
+                                &context,
+                                cloned_fd,
+                                *size as usize,
+                                xkb::KEYMAP_FORMAT_TEXT_V1,
+                                xkb::COMPILE_NO_FLAGS,
+                            )
+                        } {
+                            Ok(Some(keymap)) => {
+                                let s = keymap.get_as_string(xkb::KEYMAP_FORMAT_TEXT_V1);
+                                match KeyboardWithFallback::new_from_string(s) {
+                                    Ok(k) => {
+                                        state.keyboard_mapper.replace(k);
+                                    }
+                                    Err(err) => {
+                                        log::error!("Error processing keymap change: {:#}", err);
+                                    }
                                 }
                             }
-                        }
-                        // Dance around CString panicing on the NUL terminator
-                        // in the xkbcommon crate
-                        while let Some(0) = data.last() {
-                            data.pop();
-                        }
-                        let s = String::from_utf8(data).expect("Failed to read string from data");
-                        match KeyboardWithFallback::new_from_string(s) {
-                            Ok(k) => {
-                                state.keyboard_mapper.replace(k);
+                            Ok(None) => {
+                                log::error!("invalid keymap");
                             }
+
                             Err(err) => {
                                 log::error!("Error processing keymap change: {:#}", err);
                             }


Test patch: