2

I'm trying to use rust-xcb to get a window's class given a window ID.

fn get_class(conn: &xcb::Connection, id: &i32) {
    let window: xcb::xproto::Window = *id as u32;
    let class_prop: xcb::xproto::Atom = 67; // XCB_ATOM_WM_CLASS from xproto.h
    let cookie = xcb::xproto::get_property(&conn, false, window, class_prop, 0, 0, 2);
    match cookie.get_reply() {
        Ok(reply) => {
            let x: &[std::os::raw::c_void] = reply.value();
            println!("reply is {:?}", x[0]);
        }   
        Err(err) => println!("err {:?}", err),
    }
}

The documentation is kind of sparse and hasn't been incredibly helpful, though I did find this bit about the GetPropertyReply and of the xcb_get_property_reply_t it wraps.

I looked at this answer in JavaScript but I don't know what the ctypes equivalent in Rust is. I tried just casting the &[c_void] as a &str or String:

 ...
 Ok(reply) => {
     let len = reply.value_len() as usize;
     let buf = reply.value() as &str;
     println!("{}", buf.slice_unchecked(0, len)); // this seems redundant
 }   
 ...

but it returns

error: non-scalar cast: `&[_]` as `&str`

I tried casting the &[c_void] as a &[u8] and then collecting the Vec into a String, which sort of works:

  ...
  Ok(reply) => {
      let value : &[u8] = reply.value();
      let buf : String = value.into_iter().map(|i| *i as char).collect();
      println!("\t{:?}", buf);
  }
  ...

but I'm now getting weird results. for example , when I use xprop on Chrome I see "google-chrome" but for me it is only showing "google-c", and "roxterm" is showing up as "roxterm\u{0}". I'm guessing "\u{0}" is something Unicode related but I'm not sure, and I don't know why stuff is being concatenated either. Maybe I have to check the reply again?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
erp
  • 515
  • 1
  • 5
  • 14
  • 1
    If this is a non-ascii byte string, then this is not the right way to convert it to String, since it is assuming each byte is a character. Instead you should use `String::from_utf8`. – Peter Hall Jun 29 '17 at 20:32
  • oh cool, that takes care of the unicode issue. the other issue is the incomplete strings, which i'm pretty sure is because I'm supposed to re-query the cookie somehow. checking `value.bytes_after()` shows that there are indeed bytes I'm supposed to accept but it's unclear how I'm supposed to do that. re-calling `cookie.get_reply()` just hangs. – erp Jun 29 '17 at 21:01
  • I figured it out, `xcb::xproto::get_property()` has `long_offset` and `long_length` which can be configured to what is needed. – erp Jun 29 '17 at 21:20

1 Answers1

0

Here's my updated function:

fn get_class(conn: &Connection, id: &i32) -> String {
    let window: xproto::Window = *id as u32;
    let long_length: u32 = 8;
    let mut long_offset: u32 = 0;
    let mut buf = Vec::new();
    loop {
        let cookie = xproto::get_property(
            &conn,
            false,
            window,
            xproto::ATOM_WM_CLASS,
            xproto::ATOM_STRING,
            long_offset,
            long_length,
        );
        match cookie.get_reply() {
            Ok(reply) => {
                let value: &[u8] = reply.value();
                buf.extend_from_slice(value);
                match reply.bytes_after() {
                    0 => break,
                    _ => {
                        let len = reply.value_len();
                        long_offset += len / 4;
                    }   
                }
            }   
            Err(err) => {
                println!("{:?}", err);
                break;
            }   
        }
    }
    let result = String::from_utf8(buf).unwrap();
    let results: Vec<&str> = result.split('\0').collect();
    results[0].to_string()
}

There were three main parts to this question:

  1. I put xproto::get_property() in a loop so I could check reply.bytes_after() and accordingly adjust long_offset. I think with an appropriate long_length there will usually only be one read, but just being safe.
  2. As @peter-hall said, converting &[u8] -> String should be done using String::from_utf8, which needs a Vec; so I let mut buf = Vec::new() and buf.extend_from_slice over the loop before creating the result string with String::from_utf8(buf).unwrap()
  3. According to this random page WM_CLASS is actually two consecutive null-terminated strings, so I split the result by \0 and grab the first value.

I might've just been looking in the wrong place, but xcb has absolutely terrible documentation..

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
erp
  • 515
  • 1
  • 5
  • 14