Skip to content

Commit

Permalink
Add xkbcommon-example
Browse files Browse the repository at this point in the history
This example shows how to use xkbcommon together with x11rb's
XCBConnection. The program prints key presses and releases and it also
detects backspace. It does not really do anything useful, but perhaps it
can still help people...?

Disclaimer: I don't really know that much about XKB and xkbcommon.

I originally wrote this program in the following comment:
#782 (comment)

Signed-off-by: Uli Schlachter <psychon@znc.in>
  • Loading branch information
psychon committed Mar 2, 2024
1 parent 397930b commit d030ec3
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ jobs:
if: matrix.rust == 'nightly'
run: cargo install grcov

- name: Install xkbcommon
run: sudo apt-get install libxkbcommon-x11-dev

# build
- name: cargo build with all features
run: cargo build --workspace --verbose --all-targets --all-features
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ members = [
"x11rb-async",
"cairo-example",
"xtrace-example",
"xkbcommon-example",
]
15 changes: 15 additions & 0 deletions xkbcommon-example/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "xkbcommon-example"
version = "0.0.0"
edition = "2021"
publish = false

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies.x11rb]
path = "../x11rb"
features = ["allow-unsafe-code", "xkb"]

[dependencies.xkbcommon]
version = "0.7"
features = ["x11"]
175 changes: 175 additions & 0 deletions xkbcommon-example/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
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 by printing some details
fn handle_key(event: xproto::KeyPressEvent, press: bool, state: &xkbc::State) {
let kind = if press { " Pressed" } else { "Released" };
let sym = state.key_get_one_sym(event.detail.into());
let utf8 = state.key_get_utf8(event.detail.into());
println!("{} keysym {sym:?} for utf8 '{utf8}'", kind);

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

Check warning on line 34 in xkbcommon-example/src/main.rs

View check run for this annotation

Codecov / codecov/patch

xkbcommon-example/src/main.rs#L24-L34

Added lines #L24 - L34 were not covered by tests

/// Create and return a window
fn create_window(
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)
}

Check warning on line 89 in xkbcommon-example/src/main.rs

View check run for this annotation

Codecov / codecov/patch

xkbcommon-example/src/main.rs#L37-L89

Added lines #L37 - L89 were not covered by tests

fn main() -> Result<(), Box<dyn std::error::Error>> {
let (conn, screen_num) = XCBConnection::connect(None)?;

Check warning on line 92 in xkbcommon-example/src/main.rs

View check run for this annotation

Codecov / codecov/patch

xkbcommon-example/src/main.rs#L91-L92

Added lines #L91 - L92 were not covered by tests

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,
"This program requires the X11 server to support the XKB extension"

Check warning on line 101 in xkbcommon-example/src/main.rs

View check run for this annotation

Codecov / codecov/patch

xkbcommon-example/src/main.rs#L94-L101

Added lines #L94 - L101 were not covered by tests
);

// Ask the X11 server to send us XKB events.
// 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(),
)?;

Check warning on line 125 in xkbcommon-example/src/main.rs

View check run for this annotation

Codecov / codecov/patch

xkbcommon-example/src/main.rs#L106-L125

Added lines #L106 - L125 were not covered by tests

// Set up xkbcommon state and get the current keymap.
let context = xkbc::Context::new(xkbc::CONTEXT_NO_FLAGS);
let device_id = xkbc::x11::get_core_keyboard_device_id(&conn);
assert!(device_id >= 0);
let keymap = xkbc::x11::keymap_new_from_device(
&context,
&conn,
device_id,
xkbc::KEYMAP_COMPILE_NO_FLAGS,
);
let mut state = xkbc::x11::state_new_from_device(&keymap, &conn, device_id);

Check warning on line 137 in xkbcommon-example/src/main.rs

View check run for this annotation

Codecov / codecov/patch

xkbcommon-example/src/main.rs#L128-L137

Added lines #L128 - L137 were not covered by tests

// Create and show a window
let window = create_window(&conn, screen_num, &atoms)?;
conn.map_window(window)?;
conn.flush()?;

Check warning on line 142 in xkbcommon-example/src/main.rs

View check run for this annotation

Codecov / codecov/patch

xkbcommon-example/src/main.rs#L140-L142

Added lines #L140 - L142 were not covered by tests

// Main loop; wait for events
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

Check warning on line 149 in xkbcommon-example/src/main.rs

View check run for this annotation

Codecov / codecov/patch

xkbcommon-example/src/main.rs#L146-L149

Added lines #L146 - L149 were not covered by tests
{
println!("Window was asked to close");
break;
}

Check warning on line 153 in xkbcommon-example/src/main.rs

View check run for this annotation

Codecov / codecov/patch

xkbcommon-example/src/main.rs#L151-L153

Added lines #L151 - L153 were not covered by tests
}
Event::XkbStateNotify(event) => {
if i32::from(event.device_id) == 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(),
);
}

Check warning on line 166 in xkbcommon-example/src/main.rs

View check run for this annotation

Codecov / codecov/patch

xkbcommon-example/src/main.rs#L155-L166

Added lines #L155 - L166 were not covered by tests
}
Event::KeyPress(event) => handle_key(event, true, &state),
Event::KeyRelease(event) => handle_key(event, false, &state),
event => println!("Ignoring event {event:?}"),

Check warning on line 170 in xkbcommon-example/src/main.rs

View check run for this annotation

Codecov / codecov/patch

xkbcommon-example/src/main.rs#L168-L170

Added lines #L168 - L170 were not covered by tests
}
}

Ok(())
}

Check warning on line 175 in xkbcommon-example/src/main.rs

View check run for this annotation

Codecov / codecov/patch

xkbcommon-example/src/main.rs#L174-L175

Added lines #L174 - L175 were not covered by tests

0 comments on commit d030ec3

Please sign in to comment.