psychon / x11rb

X11 bindings for the rust programming language, similar to xcb being the X11 C bindings
Apache License 2.0
359 stars 38 forks source link

Add wrappers similar to libxcb-icccm and libxcb-ewmh #164

Open psychon opened 4 years ago

psychon commented 4 years ago

See e.g. https://github.com/psychon/x11rb/issues/163#issuecomment-573145278.

Would this make more sense as a separate repo or in here? I guess "split things up into multiple repos" is better done in the future and not too early.

Edit: https://gitlab.freedesktop.org/xorg/lib/libxcb-wm

dalcde commented 4 years ago

Having something like Xft would be extremely helpful, but that is presumably very different from icccm and ewmh support

psychon commented 4 years ago

From a quick look at /usr/include/X11/Xft/Xft.h: Xft depends on fontconfig. Thus, one would need to also use one of the available fontconfig bindings. Besides that, Xft.h has 500 lines of code and many functions.

I guess FFI bindings to Xft would better get their own create.

(icccm and ewmh are X11-protocol-only while font stuff needs to interact with files on the local system (and font files plus the whole font stack are complicated, I think))

psychon commented 4 years ago

Hm... After taking a closer look: libxcb-wm's icccm utilties do not really do much. For example, the WM_TRANSIENT_FOR utility... meh. If anyone wants to have some fun with macros, they can implement all of these. This just is a thin wrapper around a property with format 32. And the stuff it does do is ugly and really should be implemented here. So, I'll only write utilities for WM_CLASS, WM_SIZE_HINTS and WM_HINTS (since all of them involve actual parsing).

dalcde commented 4 years ago

I suspect the morally correct thing to do would be to come up with some sort of xml description for icccm and ewmh, and generate the bindings automatically.

psychon commented 4 years ago

libxcb-wm heavily uses m4 to generate its API for EWMH. The icccm API is hand-written (but could easily use some macro-magic to make it less duplicated).

So... something machine readable would of course be nice, but I would also be fine with something that heavily uses macros instead.

psychon commented 4 years ago

Some more details of what I imagine here.

An example from xcb-util-icccm:

/* WM_TRANSIENT_FOR */

xcb_void_cookie_t
xcb_icccm_set_wm_transient_for_checked(xcb_connection_t *c, xcb_window_t window,
                                       xcb_window_t transient_for_window)
{
  return xcb_change_property_checked(c, XCB_PROP_MODE_REPLACE, window,
                                     XCB_ATOM_WM_TRANSIENT_FOR,
                                     XCB_ATOM_WINDOW, 32, 1,
                                     &transient_for_window);
}

xcb_void_cookie_t
xcb_icccm_set_wm_transient_for(xcb_connection_t *c, xcb_window_t window,
                               xcb_window_t transient_for_window)
{
  return xcb_change_property(c, XCB_PROP_MODE_REPLACE, window,
                             XCB_ATOM_WM_TRANSIENT_FOR, XCB_ATOM_WINDOW, 32,
                             1, &transient_for_window);
}

xcb_get_property_cookie_t
xcb_icccm_get_wm_transient_for(xcb_connection_t *c, xcb_window_t window)
{
  return xcb_get_property(c, 0, window, XCB_ATOM_WM_TRANSIENT_FOR, XCB_ATOM_WINDOW, 0, 1);
}

xcb_get_property_cookie_t
xcb_icccm_get_wm_transient_for_unchecked(xcb_connection_t *c, xcb_window_t window)
{
  return xcb_get_property_unchecked(c, 0, window, XCB_ATOM_WM_TRANSIENT_FOR, XCB_ATOM_WINDOW, 0, 1);
}

uint8_t
xcb_icccm_get_wm_transient_for_from_reply(xcb_window_t *prop,
                                          xcb_get_property_reply_t *reply)
{
  if(!reply || reply->type != XCB_ATOM_WINDOW || reply->format != 32 || !reply->length)
    return 0;

  *prop = *((xcb_window_t *) xcb_get_property_value(reply));

  return 1;
}

uint8_t
xcb_icccm_get_wm_transient_for_reply(xcb_connection_t *c,
                                     xcb_get_property_cookie_t cookie,
                                     xcb_window_t *prop,
                                     xcb_generic_error_t **e)
{
  xcb_get_property_reply_t *reply = xcb_get_property_reply(c, cookie, e);
  uint8_t ret = xcb_icccm_get_wm_transient_for_from_reply(prop, reply);
  free(reply);
  return ret;
}

All of this is a really thin wrapper around getting properties. The remaining parts of icccm all look like this, I think:

xcb-util-ewmh is similar, but requires some state to be kept around. This state contains lots of atoms that are needed for the functions.

Edit: Here is a possible Rust version of the above (plus one for WM_PROTOCOLS):

pub struct TransientForCookie<'a, C>(Cookie<'a, C, GetPropertyReply>);

impl<...> TransientForCookie<...> {
    pub fn reply(self) -> Result<TransientFor, ReplyError<...>> { ... }
    pub fn reply_unchecked(self) -> Result<Option<TransientFor>, ConnectionError)>{ ... }
}

pub struct TransientFor(Window);
impl TransientFor {
    pub fn set<'a, C>(conn: &'a C, window: Window, transient: Window) -> Result<VoidCookie, ConnectionError> {
        wrapper::change_property32(
            conn,
            xproto::PropModeReplace,
            window,
            xproto::AtomEnum::WM_TRANSIENT_FOR,
            xproto::AtomEnum::WINDOW,
            &[transient],
        )
    }

    pub fn get<'a, C>(conn: &'a C, window: Window) -> Result<TransientForCookie, ConnectionError> {
        let cookie = xproto::get_property(
            conn,
            false,
            window,
            xproto::AtomEnum::WM_TRANSIENT_FOR,
            xproto::AtomEnum::WINDOW,
            0,
            1,
        )?;
        Ok(TransientForCookie(cookie))
    }

    pub fn from_reply(reply: GetPropertyReply) -> Result<Self, ParseError> {
        if reply.type_ != xproto::AtomEnum::WINDOW.into() || reply.format != 32 || reply.value_len == 0 {
            return Err(ParseError::ParseError);
        }
        // Edit: Hm, what should happen for "No WM_TRANSIENT_FOR set"? I think that would currently return a `ParseError`, which is not all that useful. Should this perhaps instead return `Option<Self>` and use that for "not set"?
        Ok(Self(reply.value32().next().expect("We just checked that value_len is not 0, so this cannot be None")))
    }

    pub fn window(&self) -> Window {
        self.0
    }
}

// The above is for "only a single value". The next has a list, which is a bit more complicated / different

pub struct WmProtocolsCookie<'a, C>(Cookie<'a, C, GetPropertyReply>);
impl { /* just as above, the cookie should be easily macro'ble */ }

// Hm... should this contain a GetPropertyReply instead? That would avoid a copy, but with the copy
// we can offer a better API (return a slice instead of just an iterator). I think the copy is
// worth it. Plus, people who disagree can just use the underlying xproto API directly.
pub struct WmProtocols(Vec<Atom>);
impl WmProtocols {
    pub fn set(conn: &C, window: Window, protocols: &[Atom]) -> Result<VoidCookie, ConnectionError> { ... ]
    pub fn get<'a, C>(conn: &'a C, window: Window) -> Result<WmProtocolsCookie, ConnectionError> { ... }
    pub fn from_reply(reply: GetPropertyReply) -> Result<Self, ParseError> {
        if reply.type_ != xproto::AtomEnum::ATOM.into() || reply.format != 32 {
            return Err(ParseError::ParseError);
        }
       Ok(Self(reply.value32().unwrap().collect()))
    }
    pub fn protocols(&self) -> &[Atom] { ... }
}

This could clearly use the help of some macros when implemented. The cookie seems to be pretty much always identical.