psychon / x11rb

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

Keycode to string #782

Closed andreykaere closed 8 months ago

andreykaere commented 1 year ago

How can I convert keycode to the char/string of the key, that has given keycode? I haven't found any function for that

psychon commented 1 year ago

What exactly do you mean with the "char/string of the key"? For example, let's look at my key q (I have a German keyboard layout). xev says that this is keycode 24.

So, I guess the answer to your question is "you do not, because keyboards do not work that way".

These days (that is, ever since the "X keyboard extension" exists, which was released 1996), the "proper way" to do keyboard layouts is via XKB. XKB is complicated enough that there is a library just do it properly: xkbcommon (well, libX11 also includes lots of code for XKB).

You will thus need something from https://crates.io/search?q=xkbcommon. https://crates.io/crates/toy_xcb seems interesting. Personally, I never used anything like this. I'll take a look...

andreykaere commented 1 year ago

But even in xev there is state, which seems to indicate if any modifier is pressed.

Thank you for your answer, I thought that xkb library for Rust contains a lot of unsafe code ...

psychon commented 1 year ago

Well, the state field is also present in KeyPressEvent, it is just not enough to properly implement keyboard layouts with this (AFAIK). @ is usually the key that does not work for programs that use X11 without XKB for keyboard stuff.

andreykaere commented 1 year ago

I am sorry, I can't find how I can get the name of key, that was pressed, I searched for this in xcb, xkb ... I only found something in x11, but it's raw ffi ... Could you help me?

psychon commented 1 year ago

Yup, I am already working on an example using xkbcommon. So far all it does it open a window. :see_no_evil:

psychon commented 1 year ago

Yay. xkbcommon depends on the xcb crate in an unavoidable way. Thus, I need to use an X11 connection from xcb just to pass around a raw pointer. And thanks to xcb not allowing to construct such a connection from a raw pointer, I also need it to establish the connection. Only alternative seems to be using the xkbcommon-sys crate, but that's a bit too raw for my current taste...

Anyway: I basically used https://crates.io/crates/toy_xcb as a template and tried to figure out how to use xkbcommon.

Dependencies:

[dependencies]
xkbcommon = { version = "0.5", features = ["x11"] }
x11rb = { version = "0.11", features = ["allow-unsafe-code", "xkb"] }
xcb = "1.1"

Code:

use x11rb::atom_manager;
use x11rb::connection::{Connection as _, RequestConnection as _};
use x11rb::errors::ReplyOrIdError;
use x11rb::protocol::xkb::{self, ConnectionExt as _};
use x11rb::protocol::xproto::{
    self, ConnectionExt as _, CreateWindowAux, EventMask, PropMode, WindowClass,
};
use x11rb::protocol::Event;
use x11rb::wrapper::ConnectionExt as _;
use x11rb::xcb_ffi::XCBConnection;
use xkbcommon::xkb as xkbc;

// A collection of the atoms we will need.
atom_manager! {
    pub AtomCollection: AtomCollectionCookie {
        WM_PROTOCOLS,
        WM_DELETE_WINDOW,
        _NET_WM_NAME,
        UTF8_STRING,
    }
}

/// Handle a single key press or key release event
fn handle_key(event: xproto::KeyPressEvent, press: bool, state: &xkbc::State) {
    let kind = if press { "press" } else { "release" };
    let sym = state.key_get_one_sym(event.detail.into());
    let utf8 = state.key_get_utf8(event.detail.into());
    println!("Got key {} event for keysym {sym:#x} and utf8 {utf8}", kind);

    // Just as an example on how this works:
    if sym == xkbc::keysyms::KEY_BackSpace {
        println!("Pressed key was backspace");
    }
}

/// Create and return a window
fn create_window<'a>(
    conn: &XCBConnection,
    screen_num: usize,
    atoms: &AtomCollection,
) -> Result<xproto::Window, ReplyOrIdError> {
    let screen = &conn.setup().roots[screen_num];
    let window = conn.generate_id()?;
    conn.create_window(
        screen.root_depth,
        window,
        screen.root,
        0,
        0,
        100,
        100,
        0,
        WindowClass::INPUT_OUTPUT,
        screen.root_visual,
        &CreateWindowAux::new()
            .background_pixel(screen.white_pixel)
            .event_mask(EventMask::KEY_PRESS | EventMask::KEY_RELEASE),
    )?;
    let title = "Keyboard tester";
    conn.change_property8(
        PropMode::REPLACE,
        window,
        xproto::AtomEnum::WM_NAME,
        xproto::AtomEnum::STRING,
        title.as_bytes(),
    )?;
    conn.change_property8(
        PropMode::REPLACE,
        window,
        atoms._NET_WM_NAME,
        atoms.UTF8_STRING,
        title.as_bytes(),
    )?;
    conn.change_property32(
        PropMode::REPLACE,
        window,
        atoms.WM_PROTOCOLS,
        xproto::AtomEnum::ATOM,
        &[atoms.WM_DELETE_WINDOW],
    )?;
    conn.change_property8(
        PropMode::REPLACE,
        window,
        xproto::AtomEnum::WM_CLASS,
        xproto::AtomEnum::STRING,
        b"simple_window\0simple_window\0",
    )?;
    Ok(window)
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // The XCB crate requires ownership of the connection, so we need to use it to connect to the
    // X11 server.
    let (xcb_conn, screen_num) = xcb::Connection::connect(None)?;
    let screen_num = usize::try_from(screen_num).unwrap();
    // Now get us an x11rb connection using the same underlying libxcb connection
    let conn = {
        let raw_conn = xcb_conn.get_raw_conn().cast();
        unsafe { XCBConnection::from_raw_xcb_connection(raw_conn, false) }
    }?;

    conn.prefetch_extension_information(xkb::X11_EXTENSION_NAME)?;
    let atoms = AtomCollection::new(&conn)?;
    let xkb = conn.xkb_use_extension(1, 0)?;
    let atoms = atoms.reply()?;
    let xkb = xkb.reply()?;
    assert!(xkb.supported);

    // TODO: No idea what to pick here. I guess this is asking unnecessarily for too much?
    let events = xkb::EventType::NEW_KEYBOARD_NOTIFY
        | xkb::EventType::MAP_NOTIFY
        | xkb::EventType::STATE_NOTIFY;
    // TODO: No idea what to pick here. I guess this is asking unnecessarily for too much?
    let map_parts = xkb::MapPart::KEY_TYPES
        | xkb::MapPart::KEY_SYMS
        | xkb::MapPart::MODIFIER_MAP
        | xkb::MapPart::EXPLICIT_COMPONENTS
        | xkb::MapPart::KEY_ACTIONS
        | xkb::MapPart::KEY_BEHAVIORS
        | xkb::MapPart::VIRTUAL_MODS
        | xkb::MapPart::VIRTUAL_MOD_MAP;
    conn.xkb_select_events(
        xkb::ID::USE_CORE_KBD.into(),
        0u8.into(),
        events,
        map_parts,
        map_parts,
        &xkb::SelectEventsAux::new(),
    )?;

    let context = xkbc::Context::new(xkbc::CONTEXT_NO_FLAGS);
    let device_id = xkbc::x11::get_core_keyboard_device_id(&xcb_conn);
    let keymap = xkbc::x11::keymap_new_from_device(
        &context,
        &xcb_conn,
        device_id,
        xkbc::KEYMAP_COMPILE_NO_FLAGS,
    );
    let mut state = xkbc::x11::state_new_from_device(&keymap, &xcb_conn, device_id);

    let window = create_window(&conn, screen_num, &atoms)?;
    conn.map_window(window)?;
    conn.flush()?;

    loop {
        match conn.wait_for_event()? {
            Event::ClientMessage(event) => {
                let data = event.data.as_data32();
                if event.format == 32 && event.window == window && data[0] == atoms.WM_DELETE_WINDOW
                {
                    println!("Window was asked to close");
                    break;
                }
            }
            Event::XkbStateNotify(event) => {
                if i32::try_from(event.device_id).unwrap() == device_id {
                    // Inform xkbcommon that the keyboard state changed
                    state.update_mask(
                        event.base_mods.into(),
                        event.latched_mods.into(),
                        event.locked_mods.into(),
                        event.base_group.try_into().unwrap(),
                        event.latched_group.try_into().unwrap(),
                        event.locked_group.into(),
                    );
                }
            }
            Event::KeyPress(event) => handle_key(event, true, &state),
            Event::KeyRelease(event) => handle_key(event, false, &state),
            event => println!("Ignoring event {event:?}"),
        }
        conn.flush()?;
    }

    Ok(())
}
psychon commented 1 year ago

Hm.... should the above be added as a new example to this git repo? I am not sure what to think about that. And would love to get rid of the useless xcb dependency... :-(

notgull commented 1 year ago

I have an old project lying around where I was trying to build a wrapper around xkbcommon with breadx. Maybe it's time to pick that up again, and use x11rb instead...

andreykaere commented 1 year ago

@psychon, thank you a lot for above example and your interest to this issue! And I think that is due to my lack of Rust understanding, but what does use foo as _; do? Thanks again!

psychon commented 1 year ago

Sure, no problem. Thanks for making me find missing stuff. Thanks to you, there is now https://github.com/psychon/as-raw-xcb-connection.

what does use foo as _; do?

Long explanation that might be a bit off-topic for this issue Let's answer that with an example. Take the above program and replace this ```rust use x11rb::protocol::xkb::{self, ConnectionExt as _}; ``` with ```rust. use x11rb::protocol::xkb; ``` Compiler error: ``` error[E0599]: no method named `xkb_use_extension` found for struct `XCBConnection` in the current scope --> src/main.rs:105:20 | 105 | let xkb = conn.xkb_use_extension(1, 0)?; | ^^^^^^^^^^^^^^^^^ method not found in `XCBConnection` | ::: /home/psychon/.cargo/registry/src/github.com-1ecc6299db9ec823/x11rb-0.11.0/src/protocol/xkb.rs:493:8 | 493 | fn xkb_use_extension(&self, wanted_major: u16, wanted_minor: u16) -> Result, ConnectionError> | ----------------- the method is available for `XCBConnection` here | = help: items from traits can only be used if the trait is in scope help: the following trait is implemented but not in scope; perhaps add a `use` for it: | 1 | use x11rb::protocol::xkb::ConnectionExt; | error[E0599]: no method named `xkb_select_events` found for struct `XCBConnection` in the current scope --> src/main.rs:123:10 | 123 | conn.xkb_select_events( | ^^^^^^^^^^^^^^^^^ method not found in `XCBConnection` | ::: /home/psychon/.cargo/registry/src/github.com-1ecc6299db9ec823/x11rb-0.11.0/src/protocol/xkb.rs:497:8 | 497 | fn xkb_select_events<'c, 'input>(&'c self, device_spec: DeviceSpec, clear: EventType, select_all: EventType, affect_map: MapPart, map... | ----------------- the method is available for `XCBConnection` here | = help: items from traits can only be used if the trait is in scope help: the following trait is implemented but not in scope; perhaps add a `use` for it: | 1 | use x11rb::protocol::xkb::ConnectionExt; | For more information about this error, try `rustc --explain E0599`. error: could not compile `foo` due to 2 previous errors ``` I think the above explains the need for the import well enough. That leaves the `as _` part: It imports this thing without making it "nameable". When I do `use foo as bar`, I can now use `bar` to refer to `foo`. When I `use foo as _`, I cannot actually name this, but the trait is still imported and can be used. Now, why do I do it like this? Because there are too many things called `ConnectionExt`. If I drop all the `as _`, compilation fails: ``` error[E0252]: the name `ConnectionExt` is defined multiple times --> src/main.rs:6:11 | 4 | use x11rb::protocol::xkb::{self, ConnectionExt}; | ------------- previous import of the trait `ConnectionExt` here 5 | use x11rb::protocol::xproto::{ 6 | self, ConnectionExt, CreateWindowAux, EventMask, PropMode, WindowClass, | ^^^^^^^^^^^^^ `ConnectionExt` reimported here | = note: `ConnectionExt` must be defined only once in the type namespace of this module help: you can use `as` to change the binding name of the import | 6 | self, ConnectionExt as OtherConnectionExt, CreateWindowAux, EventMask, PropMode, WindowClass, | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ```
andreykaere commented 1 year ago

Yes, thanks a lot of explanation!

However, now I am struggling with integrating it into my project, which uses x11rb. Basically, all I want is to implement the function, which will convert keycodes to the strings. So, I copied a first few lines from your main function and now I am trying to get it working, but for some reason I keep getting the following error:

xkbcommon: ERROR: xkb_x11_keymap_new_from_device: illegal device ID: -1
Segmentation fault (core dumped)

I don't really know how to deal with it ... I would really appreciate your help!

psychon commented 1 year ago

Uhm... at least I can turn the abort into a panic. Replace

let device_id = xkbc::x11::get_core_keyboard_device_id(&xcb_conn);

with

let device_id = xkbc::x11::get_core_keyboard_device_id(&xcb_conn);
assert!(device_id >= 0);

But I do not know which this fails in the first place. Is this some weird X11 server that does not support XKB? Does adding conn.sync()?.reply()? after get_core_keyboard_device_id and before new_from_device produce some different kind of error? According to https://github.com/xkbcommon/libxkbcommon/blob/4a576ab1844f68ca1eb8da922e863eb426e38957/src/x11/util.c#L109-L124 this can only really fail because the xcb connection is broken... I am really unsure what is going on, sorry....

andreykaere commented 1 year ago

No no, when I run exactly your example, everything is working fine. But when I try to integrate it into my code it seems to be broken ... I'll give it another try and if I won't succeed, I'll leave another comment, thank you.

UPD: Okay, I got it working, it was my bad, I didn't copy the line, where you connect xkb extension ... I have another question, but it's not related to the issue, so I don't know if I should I ask it here or somewhere else ... Anyway, I am gonna ask here: I don't know how to send, for example, key press to the window (which I fully grabbed keyboard of); I came up with this code, which doesn't work ... Can you give me an advice how to fix it? @psychon

if let Event::KeyPress(e) = event {
    send_event(conn, false, window, EventMask::KEY_PRESS, e)?;
}

where type of event is Event

psychon commented 1 year ago

Can you give me an advice how to fix it?

:see_no_evil:

Not really and that is complicated. X11 clients can tell apart the different keyboards via the xinput extension. This extension has a SendExtensionEvent request that can be used, but I do not really know the details either.

I guess the short version is: You don't. Sorry.

Are you sure you want to send the key presses yourself? xproto has the AllowEvents request that can be used for something like this: https://www.x.org/releases/X11R7.6/doc/xproto/x11protocol.html#requests:AllowEvents

However, I am always confused when I try to understand X11 input stuff. But... somehow it is possible to use AllowEvents to filter out certain events without filtering out others. But I do not really know the details and if one forgets a required AllowEvents request, all input freezes up, so....

andreykaere commented 1 year ago

But how do things like xdotool work then? I'm pretty sure there's a way to simulate key press ... I don't if it has anything to do with xlib or something else related...

psychon commented 1 year ago

But how do things like xdotool work then?

xdotool uses the xtest X11 extension. This has a FakeInput request. However, this really just fakes input as if you pressed a key on a keyboard. This thus does not have a Window argument and goes through the normal X11 event processing. In other words. This cannot be used to bypass your own grabs.

xdotool --type apparently has a --window flag that does not work too well: https://manpages.ubuntu.com/manpages/trusty/man1/xdotool.1.html#sendevent%20notes

   It is important to note that for key and mouse events, we only use XSendEvent when a
  specific window is targeted. Otherwise, we use XTEST.

XSendEvent is the send_event() you already found above which you say does not work. I guess that is the same problem as this xdotool man page describes.

andreykaere commented 1 year ago

Okay, and how again I can grab keyboard without, for example, a key t?

psychon commented 1 year ago

Sorry, what? Why doesn't your keyboard have a key t? Or do you want to grab everything except for the t key? Why would you do that? O.o

andreykaere commented 1 year ago

I want to do the following: fully control keyboard of application. But then I can't simulate any key press. (if I understood you correctly) And to be able to do that I need to allow this key pressed event to be sent to application, therefore not grabbing it in the first place...

psychon commented 1 year ago

Hm, yeah, I fear X11 doesn't really make that possible... or at least not easy. Sorry.

andreykaere commented 1 year ago

But I know a program written in Python, which does it. I just don't know how to implement it using x11rb ...

psychon commented 1 year ago

Which program? How does it do it?

andreykaere commented 1 year ago

It uses send_event ... Maybe I am passing the event wrong?

psychon commented 1 year ago

Well... got a link or something like that?

andreykaere commented 1 year ago

Sure: https://github.com/gillescastel/inkscape-shortcut-manager

Function: press in class in main

psychon commented 1 year ago

x11rb port that works for me against urxvt (detail: 52 is y on a German keyboard layout):

use x11rb::{
    connection::Connection as _,
    protocol::xproto::{ConnectionExt as _, KeyPressEvent, KEY_PRESS_EVENT, KEY_RELEASE_EVENT},
    wrapper::ConnectionExt as _,
};

// KeyPressEvent and KeyReleaseEvent are the same type, but have different response_type
type KeyEvent = KeyPressEvent;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let (conn, screen_idx) = x11rb::connect(None)?;
    let screen = &conn.setup().roots[screen_idx];
    let focus = conn.get_input_focus()?.reply()?.focus;
    println!("Input focus is {focus:x}");
    let send_event = |response_type| {
        let event = KeyEvent {
            sequence: 0,
            response_type,
            time: x11rb::CURRENT_TIME,
            root: screen.root,
            event: focus,
            child: x11rb::NONE,
            // No modifiers pressed
            state: Default::default(),
            // They key that is being toggled
            detail: 52,
            // All of the following are technically wrong?!
            same_screen: false,
            root_x: 0,
            root_y: 0,
            event_x: 0,
            event_y: 0,
        };
        conn.send_event(true, focus, Default::default(), event)
    };
    send_event(KEY_PRESS_EVENT)?;
    send_event(KEY_RELEASE_EVENT)?;
    conn.sync()?;
    Ok(())
}
mWalrus commented 1 year ago

I'll hijack this issue for a similar issue that I'm experiencing right now as I'm trying to write my own tiling window manager. I've gotten to the point where I want to implement keybinds but I just can't get it to work.

So I've begun the implementation by setting up the keyboard with the help of your comment above using xkbcommon and xcb and that all works. The issue presents itself when I want to intercept key press/release events. If I grab the entire keyboard I get these events just fine but I can't type into applications since I grabbed the keyboard.

My new approach, after looking at the dwm source, is to grab the keybinds specifically instead of the entire keyboard and it does that without reporting any errors.

In dwm they do it like this in the setup function:

// they do other stuff before this...
wa.event_mask = SubstructureRedirectMask|SubstructureNotifyMask
    |ButtonPressMask|PointerMotionMask|EnterWindowMask
    |LeaveWindowMask|StructureNotifyMask|PropertyChangeMask;
XChangeWindowAttributes(dpy, root, CWEventMask|CWCursor, &wa);
XSelectInput(dpy, root, wa.event_mask);
grabkeys();

And their grabkeys function looks like this:

void
grabkeys(void)
{
  updatenumlockmask();
  {
  unsigned int i, j, k;
  unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask };
  int start, end, skip;
  KeySym *syms;

  XUngrabKey(dpy, AnyKey, AnyModifier, root);
  XDisplayKeycodes(dpy, &start, &end);
  syms = XGetKeyboardMapping(dpy, start, end - start + 1, &skip);
  if (!syms)
    return;
  for (k = start; k <= end; k++)
    for (i = 0; i < LENGTH(keys); i++)
      /* skip modifier codes, we do that ourselves */
      if (keys[i].keysym == syms[(k - start) * skip])
        for (j = 0; j < LENGTH(modifiers); j++)
          XGrabKey(dpy, k,
              keys[i].mod | modifiers[j],
              root, True,
              GrabModeAsync, GrabModeAsync);
  XFree(syms);
  }
}

I do the grabbing like so:

let keybinds = commands::setup_keybinds();
// trying to replicate grabbing like they do it in dwm 
let numlockmask = {
    let mut nlm: u16 = 0;
    let modmap = conn.get_modifier_mapping()?.reply()?;
    let max_keypermod = modmap.keycodes_per_modifier();
    for i in 0..8 {
        for j in 0..max_keypermod {
            let idx = (i * max_keypermod + j) as usize;
            if modmap.keycodes[idx] == KEY_Num_Lock as u8 {
                nlm = 1 << i;
            }
        }
    }
    ModMask::from(nlm)
};
let modifiers = [
    ModMask::from(0u16),
    ModMask::LOCK,
    numlockmask,
    numlockmask | ModMask::LOCK,
];

conn.ungrab_key(GRAB_ANY, screen.root, ModMask::ANY)?;
for keybind in &keybinds {
    let mod_mask = ModMask::from(u16::from(keybind.mods));
    for m in &modifiers {
        conn.grab_key(
            true,
            screen.root,
            mod_mask | *m,
            keybind.keysym as u8,
            GrabMode::ASYNC,
            GrabMode::ASYNC,
        )?;
    }
}

My event mask on screen.root is the following:

let change = ChangeWindowAttributesAux::default().event_mask(
    EventMask::SUBSTRUCTURE_REDIRECT
        | EventMask::SUBSTRUCTURE_NOTIFY
        | EventMask::KEY_PRESS
        | EventMask::KEY_RELEASE
        | EventMask::STRUCTURE_NOTIFY
        | EventMask::PROPERTY_CHANGE,
);

Lastly, here is the focus method which delegates focus to the window in question:

fn focus(&mut self, frame: Window, window: Window) -> Result<(), ReplyError> {
    println!("setting focus on frame {frame} containing window {window}");
    // simple_window_manager.rs says InputFocus::PARENT but dwm uses InputFocus::POINTER_ROOT
    self.conn
        .set_input_focus(InputFocus::PARENT, window, CURRENT_TIME)?;
    self.conn.configure_window(
        frame,
        &ConfigureWindowAux::new().stack_mode(StackMode::ABOVE),
    )?;

    let focus_aux = ChangeWindowAttributesAux::new().border_pixel(theme::WINDOW_BORDER_FOCUSED);
    self.conn.change_window_attributes(frame, &focus_aux)?;
    self.conn.flush()?;

    self.last_focused = Some((frame, window));

    Ok(())
}

Okay, so, the issue is that no key press/release events are being reported when I input any of the grabbed keybinds. I can't see any, even though I've registered for them in the event mask, grabbed the relevant keybinds according to the keymap, and set up a relevant event handler but I'm missing something...

I know you said you are not very well versed in input stuff above @psychon but I'll ask anyways just in case you might spot something :)

psychon commented 1 year ago

Well... from a quick look: I can't see anything different between the dwm code you posted and what you are doing.

I'll hijack this issue

Could you open a new issue and provide a runnable example (or at least a link to your GitHub repo)? This issue is titled "keycode to string" and your post is about something different. I'll mark this comment and your comment as "off topic".

andreykaere commented 10 months ago

I have a question: why do we have to use Default::default() (or what's the same EventMask::NO_EVENT) here?

conn.send_event(true, focus, Default::default(), event)

Why with EventMask::KEY_PRESS it doesn't work? What does this argument even mean?

P.S. Thank you in advance! And Happy New Year :)

psychon commented 10 months ago

From https://www.x.org/releases/X11R7.6/doc/xproto/x11protocol.html#requests:SendEvent . I suggest reading all of that, but to answer your question:

If the event-mask is the empty set, then the event is sent to the client that created the destination window.

And if that mask is not empty:

then the event is sent to every client selecting on destination any of the event types in event-mask.

andreykaere commented 10 months ago

Got you, thanks a lot! And why would you want to use "propagate" with true value? I mean, it can result in some other window getting the event ... And I still don't quite understand the description of both: "If propagate is False, then the event is sent to every client selecting on destination any of the event types in event-mask.

If propagate is True and no clients have selected on destination any of the event types in event-mask, then destination is replaced with the closest ancestor of destination for which some client has selected a type in event-mask and no intervening window has that type in its do-not-propagate-mask. If no such window exists or if the window is an ancestor of the focus window and InputFocus was originally specified as the destination, then the event is not sent to any clients. Otherwise, the event is reported to every client selecting on the final destination any of the types specified in event-mask."

1) If I am sending signals to some window (like image editor, for example) how do I know what kind of events it is selecting? 2) What does that mean "no intervening window has that type in its do-not-propagate-mask"? 3) What does "final" mean here "Otherwise, the event is reported to every client selecting on the final destination any of the types specified in event-mask."? Does it mean that "otherwise" it just falls back to "false" version?

psychon commented 10 months ago

And why would you want to use "propagate" with true value? I

Because that's how normal X11 event propagating works. If you click in a window and no client subscribed to button clicks on that window, the event is reported to the parent (and its parent, and so on).

If I am sending signals to some window (like image editor, for example) how do I know what kind of events it is selecting?

Uhm... I'll go with "you do not" for my answer, but in theory GetWindowAttributes tells you about event masks. I just cannot think of any good use of that.

What does that mean "no intervening window has that type in its do-not-propagate-mask"?

With a ChangeWindow request, you can ask for events by setting an event mask. Similarly, you can set a do_not_propagate_mask to prevent event propagation through this window. This feature is so heavily used, that no one ever noticed the typo here (propOgate instead of propAgate). /s

What does "final" mean here

Well, the text before that described how a destination window is picked. I guess "final" here just wants to emphasise that the window that was picked as destination according to this rule is used instead of the destination value from the request.

andreykaere commented 10 months ago

Okay, thank you very much! Just want to ask about your opinion: when should one use "propagate" as "false" and when as "true"?

psychon commented 10 months ago

:shrug:

My opinion would be to never use SendEvent (unless one explicitly is required to do so by some specification, e.g. ICCCM, EWMH, XEmbed, ...).

andreykaere commented 10 months ago

But what's the alternative then? If I want to emulate certain key presses, for example? As in xdotool or something ...

psychon commented 10 months ago

Oh, wow, xdotool also uses SendEvent (but prefers XTest if possible): https://github.com/jordansissel/xdotool/blob/7e02cef5d9216bd0ce69b44f62217b587cc7c31e/xdo.c#L1565-L1587

andreykaere commented 10 months ago

But what's wrong with using SendEvent anyway?

psychon commented 10 months ago

It completely ignores the expectations of the target app and just delivers it a raw event.

Does the app use XKB or core X11 for keyboard handling? Does it perhaps use the xinput extension? One cannot know. The approach is basically "we assume that the app uses XYZ and just hope it actually does".

(And I'm not too sure about keyboard state either. What if some weird modifier is currently pressed? Or latched? Or locked? Stuff like Caps-Lock. With core events, this can be dealt with relatively easily since the key events contain the mask state, but XKB should be more complicated (but I don't actually know XKB all that well and thus cannot really say for sure).)

andreykaere commented 10 months ago

But what's the "right" way to do it? And how xtest is better?

psychon commented 10 months ago

I don't know about the right way to do it, but xtest is better, because it lets the X11 server do all its normal input processing. XTest is basically "dear X11 server, pretend that there is an extra keyboard present that just generated the following key presses". The X11 server then treats this just like any other input event, routing it to the correct window, and doing the normal event processing (thus doing all the XKB / XInput stuff "just right").

andreykaere commented 10 months ago

Oh, wow, xdotool also uses SendEvent (but prefers XTest if possible): https://github.com/jordansissel/xdotool/blob/7e02cef5d9216bd0ce69b44f62217b587cc7c31e/xdo.c#L1565-L1587

What is x11rb alternative of XTestFakeKeyEvent? Is it xtest_fake_input? If yes, then what should I put as type_? response_type from KeyPressEvent?

Because I tried this:

    let event = match event {
        Event::KeyPress(e) | Event::KeyRelease(e) => e,
        _ => {
            return Ok(());
        }
    };

    conn.xtest_fake_input(
        event.response_type,
        event.detail,
        event.time,
        event.root,
        event.root_x,
        event.root_y,
        device_id,
    );

And it doesn't work ... Nothing happens except that program hangs :)

psychon commented 10 months ago

What is x11rb alternative of XTestFakeKeyEvent? Is it xtest_fake_input?

Yup.

If yes, then what should I put as type_?

Puh, I don't know. https://www.x.org/releases/X11R7.7/doc/xextproto/xtest.html#Server_Requests says

This must be one of KeyPress, KeyRelease, MotionNotify, ButtonPress, or ButtonRelease, or else a Value error occurs.

andreykaere commented 10 months ago

For some reasonxtest doesn't work for me, nothing happens ... So, I guess, I'll stick to send_event ...