Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add API BaseEvent::is_from_send_event #262

Merged
merged 2 commits into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,7 @@ name = "screenshot"
[[example]]
name = "xkb_init"
required-features = ["xkb"]

[[example]]
name = "xkb_keyboard_mouse_event"
required-features = ["xkb"]
231 changes: 231 additions & 0 deletions examples/xkb_keyboard_mouse_event.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
use xcb::{x, xkb, BaseEvent, Xid};

fn setup_xcb() -> xcb::Result<xcb::Connection> {
let (conn, screen_num) =
xcb::Connection::connect_with_extensions(None, &[xcb::Extension::Xkb], &[])?;

// we need at least xkb-1.0 to be available on client machine
{
let xkb_ver = conn.wait_for_reply(conn.send_request(&xkb::UseExtension {
wanted_major: 1,
wanted_minor: 0,
}))?;

assert!(xkb_ver.supported(), "xkb-1.0 support is required");
}

// we now select what events we want to receive
// such as map change, keyboard hotplug ...
// note that key strokes are given directly by
// the x::Event::KeyPress, not by xkb
{
let events = xkb::EventType::NEW_KEYBOARD_NOTIFY
| xkb::EventType::MAP_NOTIFY
| xkb::EventType::STATE_NOTIFY;

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;

let cookie = conn.send_request_checked(&xkb::SelectEvents {
device_spec: xkb::Id::UseCoreKbd as xkb::DeviceSpec,
affect_which: events,
clear: xkb::EventType::empty(),
select_all: events,
affect_map: map_parts,
map: map_parts,
details: &[],
});

conn.check_request(cookie)?;
}

// proceed with window creation
{
let setup = conn.get_setup();
let screen = setup.roots().nth(screen_num as usize).unwrap();
let window: x::Window = conn.generate_id();

conn.send_request(&x::CreateWindow {
depth: x::COPY_FROM_PARENT as u8,
wid: window,
parent: screen.root(),
x: 0,
y: 0,
width: 500,
height: 500,
border_width: 10,
class: x::WindowClass::InputOutput,
visual: screen.root_visual(),
value_list: &[
x::Cw::BackPixel(screen.white_pixel()),
// Register to receive keyboard-related events for this window
x::Cw::EventMask(
x::EventMask::FOCUS_CHANGE
| x::EventMask::KEY_PRESS
| x::EventMask::KEY_RELEASE
| x::EventMask::BUTTON_PRESS
| x::EventMask::BUTTON_RELEASE
| x::EventMask::BUTTON_MOTION,
),
],
});
// Make the window visible
conn.send_request(&x::MapWindow { window });

// Set window title
let title = "XCB Keyboard Event Tester";
conn.send_request(&x::ChangeProperty {
mode: x::PropMode::Replace,
window,
property: x::ATOM_WM_NAME,
r#type: x::ATOM_STRING,
data: title.as_bytes(),
});

conn.flush()?;
}
Ok(conn)
}

fn print_key_event(
key_event: &x::KeyPressEvent,
is_pressed: bool,
last_xkb_state_event: Option<&xkb::StateNotifyEvent>,
) {
let winid = key_event.event().resource_id();
let keycode = key_event.detail();
let event_mods = key_event.state();
let down_up = if is_pressed { "DOWN" } else { "UP" };
println!("Window {winid:#x} received key {down_up}");
if key_event.is_from_send_event() {
println!(" (event is synthetic, simulated)");
}
println!(" Keycode: {keycode}");
println!(" Event modifiers: {event_mods:#?}");

if let Some(xkb_state) = last_xkb_state_event {
print_xkb_state(&xkb_state);
}
}

fn print_button_event(
button_event: &x::ButtonPressEvent,
is_pressed: bool,
last_xkb_state_event: Option<&xkb::StateNotifyEvent>,
) {
let winid = button_event.event().resource_id();
let button = button_event.detail();
let event_mods = button_event.state();
let down_up = if is_pressed { "DOWN" } else { "UP" };
println!("Window {winid:#x} received mouse button {down_up}");
if button_event.is_from_send_event() {
println!(" (event is synthetic, simulated)");
}
println!(" Button: {button:#?}");
println!(" Event modifiers: {event_mods:#?}");

if let Some(xkb_state) = last_xkb_state_event {
print_xkb_state(&xkb_state);
}
}

fn print_xkb_state(xkb_state: &xkb::StateNotifyEvent) {
let has_mods = !xkb_state.mods().is_empty();
let has_btns = !xkb_state.ptr_btn_state().is_empty();
if has_mods || has_btns {
println!(" XKB last state:");
println!(" Change: {changed:#?}", changed = xkb_state.changed());
if has_mods {
println!(" Modifiers: {mods:#?}", mods = xkb_state.mods());
}
if has_btns {
println!(" Buttons: {btns:#?}", btns = xkb_state.ptr_btn_state());
}
}
}

fn main() -> xcb::Result<()> {
let conn = setup_xcb()?;

println!();
println!(">>> XCB & XKB initialized, watching events now");
println!(">>> Press `Escape` to quit");

// proceed with the event loop
let mut last_xkb_state_event = None;
let mut last_event_is_packed = false;
while let Ok(event) = conn.wait_for_event() {
// dbg!(&event); // debug event details

// decide when to add a blank line before the event
// used to pack motion events together (without blank line) as they can generate a lot of events
match (last_event_is_packed, &event) {
(false, xcb::Event::X(x::Event::MotionNotify(_))) => {
println!();
last_event_is_packed = false;
}
(true, xcb::Event::X(x::Event::MotionNotify(_))) => {}
(_, xcb::Event::Xkb(xkb::Event::StateNotify(_))) => {}
_ => {
println!();
last_event_is_packed = false;
}
}

match event {
xcb::Event::X(x::Event::FocusIn(focus_event)) => {
let winid = focus_event.event().resource_id();
println!("Window {winid:#x} got input focus");
}
xcb::Event::X(x::Event::FocusOut(focus_event)) => {
let winid = focus_event.event().resource_id();
println!("Window {winid:#x} lost input focus");
}
xcb::Event::X(x::Event::KeyPress(key_event)) => {
print_key_event(&key_event, true, last_xkb_state_event.as_ref());
if key_event.detail() == 9 {
println!();
println!("`Escape` was pressed, exiting..");
break;
}
}
xcb::Event::X(x::Event::KeyRelease(key_event)) => {
print_key_event(&key_event, false, last_xkb_state_event.as_ref());
}
xcb::Event::X(x::Event::ButtonPress(button_event)) => {
print_button_event(&button_event, true, last_xkb_state_event.as_ref());
}
xcb::Event::X(x::Event::ButtonRelease(button_event)) => {
print_button_event(&button_event, false, last_xkb_state_event.as_ref());
}
xcb::Event::X(x::Event::MotionNotify(motion_event)) => {
let winid = motion_event.event().resource_id();
let button = motion_event.state();
// Single line info to use less space, as motions generates a lot of events
print!("Window {winid:#x} received mouse button ({button:#?}) movement: ");
println!(
"(root x={root_x} y={root_y}) | (win x={win_x} y={win_y})",
root_x = motion_event.root_x(),
root_y = motion_event.root_y(),
win_x = motion_event.event_x(),
win_y = motion_event.event_y(),
);
last_event_is_packed = true;
}
xcb::Event::Xkb(xkb::Event::StateNotify(state_event)) => {
last_xkb_state_event = Some(state_event);
}
_ => {
dbg!(event); // debug unhandled event details
}
}
}

Ok(())
}
8 changes: 8 additions & 0 deletions src/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@

/// The number associated to this event
const NUMBER: u32;

/// Check whether this event was emitted by the SendEvent request
/// See `[crate::x::SendEvent]`.
fn is_from_send_event(&self) -> bool {
let ev = self.as_raw();
let response_type = unsafe { (*ev).response_type };
(response_type & 0x80) != 0
}
}

/// A trait for GE_GENERIC events
Expand Down Expand Up @@ -1281,9 +1289,9 @@
unsafe {
let ext: *mut xcb_extension_t = match XGE::EXTENSION {
#[cfg(feature = "xinput")]
Extension::Input => &mut xinput::FFI_EXT as *mut _,

Check warning on line 1292 in src/base.rs

View workflow job for this annotation

GitHub Actions / Build and Test

creating a mutable reference to mutable static is discouraged

Check warning on line 1292 in src/base.rs

View workflow job for this annotation

GitHub Actions / Build and Test

creating a mutable reference to mutable static is discouraged
#[cfg(feature = "present")]
Extension::Present => &mut present::FFI_EXT as *mut _,

Check warning on line 1294 in src/base.rs

View workflow job for this annotation

GitHub Actions / Build and Test

creating a mutable reference to mutable static is discouraged

Check warning on line 1294 in src/base.rs

View workflow job for this annotation

GitHub Actions / Build and Test

creating a mutable reference to mutable static is discouraged
_ => unreachable!("only Input and Present have XGE events"),
};

Expand Down Expand Up @@ -1339,9 +1347,9 @@
unsafe {
let ext: *mut xcb_extension_t = match extension {
#[cfg(feature = "xinput")]
Extension::Input => &mut xinput::FFI_EXT as *mut _,

Check warning on line 1350 in src/base.rs

View workflow job for this annotation

GitHub Actions / Build and Test

creating a mutable reference to mutable static is discouraged

Check warning on line 1350 in src/base.rs

View workflow job for this annotation

GitHub Actions / Build and Test

creating a mutable reference to mutable static is discouraged
#[cfg(feature = "present")]
Extension::Present => &mut present::FFI_EXT as *mut _,

Check warning on line 1352 in src/base.rs

View workflow job for this annotation

GitHub Actions / Build and Test

creating a mutable reference to mutable static is discouraged

Check warning on line 1352 in src/base.rs

View workflow job for this annotation

GitHub Actions / Build and Test

creating a mutable reference to mutable static is discouraged
_ => panic!("only Input and Present have XGE events"),
};

Expand Down
Loading