### String Encoding Example Source: https://github.com/libinput/libei/blob/main/doc/protocol/doc/types.md Demonstrates the encoding of the string 'hello', including length prefix, null byte, and zero-padding to the nearest multiple of 4 bytes. ```text [0x06, 0x00, 0x00, 0x00, 'h', 'e', 'l', 'l', 'o', '\0', '\0\', '\0'] ``` -------------------------------- ### Create libei Sender Context and Emulate Key Press Source: https://context7.com/libinput/libei/llms.txt This example demonstrates creating a libei sender context to inject input events into the Wayland compositor. It connects to the backend, waits for device events, and emulates pressing and releasing the 'A' key. ```c #include #include #include #include int main(void) { /* Create a sender context (will inject events into the compositor) */ struct ei *ei = ei_new_sender(NULL); if (!ei) { fprintf(stderr, "Failed to allocate ei context\n"); return 1; } ei_configure_name(ei, "my-input-emulator"); /* Connect via a pre-opened socket fd (preferred) or by socket path */ /* int rc = ei_setup_backend_fd(ei, fd_from_portal); */ int rc = ei_setup_backend_socket(ei, NULL); /* uses $LIBEI_SOCKET or $XDG_RUNTIME_DIR/eis-0 */ if (rc < 0) { fprintf(stderr, "Backend setup failed: %d\n", rc); ei_unref(ei); return 1; } struct pollfd pfd = { .fd = ei_get_fd(ei), .events = POLLIN }; struct ei_device *kbd = NULL; bool connected = false; uint32_t seq = 0; while (1) { poll(&pfd, 1, -1); ei_dispatch(ei); struct ei_event *ev; while ((ev = ei_get_event(ei)) != NULL) { switch (ei_event_get_type(ev)) { case EI_EVENT_CONNECT: connected = true; break; case EI_EVENT_SEAT_ADDED: { struct ei_seat *seat = ei_event_get_seat(ev); /* Bind to keyboard capability */ ei_seat_bind_capabilities(seat, EI_DEVICE_CAP_KEYBOARD, NULL); break; } case EI_EVENT_DEVICE_ADDED: { struct ei_device *dev = ei_event_get_device(ev); if (ei_device_has_capability(dev, EI_DEVICE_CAP_KEYBOARD)) kbd = ei_device_ref(dev); break; } case EI_EVENT_DEVICE_RESUMED: if (ei_event_get_device(ev) == kbd && kbd) { /* Emulate pressing and releasing the 'A' key */ ei_device_start_emulating(kbd, ++seq); ei_device_keyboard_key(kbd, KEY_A, true); ei_device_frame(kbd, ei_now(ei)); ei_device_keyboard_key(kbd, KEY_A, false); ei_device_frame(kbd, ei_now(ei)); ei_device_stop_emulating(kbd); } break; case EI_EVENT_DISCONNECT: ei_event_unref(ev); ei_device_unref(kbd); ei_unref(ei); return 0; default: break; } ei_event_unref(ev); } } } ``` -------------------------------- ### Emulate Text Events with ei_device_text_keysym() / ei_device_text_utf8() Source: https://context7.com/libinput/libei/llms.txt Emulates text input using either XKB-compatible keysyms or raw UTF-8 strings. Requires a device with EI_DEVICE_CAP_TEXT capability. Ensure to start and stop emulation and frame events appropriately. ```c /* Requires EI_DEVICE_CAP_TEXT device */ ei_device_start_emulating(text_dev, ++seq); /* Send keysym (XKB_KEY_Return = 0xff0d) */ ei_device_text_keysym(text_dev, 0xff0d, true); /* press Return */ ei_device_frame(text_dev, ei_now(ei)); ei_device_text_keysym(text_dev, 0xff0d, false); /* release */ ei_device_frame(text_dev, ei_now(ei)); /* Send UTF-8 text directly (max 254 chars) */ ei_device_text_utf8(text_dev, "Hello, world!"); ei_device_frame(text_dev, ei_now(ei)); ei_device_stop_emulating(text_dev); ``` -------------------------------- ### Receiving sender events on the EIS side Source: https://context7.com/libinput/libei/llms.txt This section describes how sender clients receive input events from the server using `eis_get_event()`. It provides examples for handling various event types like device start emulation, pointer motion, keyboard keys, scroll deltas, and text input. ```APIDOC ## Receiving sender events on the EIS side For sender clients, `libeis` delivers input events from the client via `eis_get_event()`. The server reads these and routes them as real input events. ```c case EIS_EVENT_DEVICE_START_EMULATING: printf("Client starting emulation seq=%u\n", eis_event_emulating_get_sequence(ev)); break; case EIS_EVENT_POINTER_MOTION: printf("Motion dx=%.2f dy=%.2f\n", eis_event_pointer_get_dx(ev), eis_event_pointer_get_dy(ev)); /* forward to uinput / wl_pointer etc. */ break; case EIS_EVENT_KEYBOARD_KEY: printf("Key %u %s\n", eis_event_keyboard_get_key(ev), eis_event_keyboard_get_key_is_press(ev) ? "down" : "up"); break; case EIS_EVENT_SCROLL_DELTA: printf("Scroll dx=%.2f dy=%.2f\n", eis_event_scroll_get_dx(ev), eis_event_scroll_get_dy(ev)); break; case EIS_EVENT_TEXT_UTF8: /* since 1.6 */ printf("Text input: %s\n", eis_event_text_get_utf8(ev)); break; case EIS_EVENT_FRAME: /* All events in this frame have been delivered; now forward them */ flush_pending_input_to_compositor(); break; ``` ``` -------------------------------- ### Query libei Device Regions Source: https://context7.com/libinput/libei/llms.txt This C code snippet shows how to iterate through the regions associated with an `ei_device`. It demonstrates retrieving region properties like offset, size, scale, and mapping ID. It also includes examples of converting desktop-wide coordinates to region-relative coordinates and finding the region that contains a specific desktop coordinate. ```c struct ei_device *dev = ei_event_get_device(ev); /* EI_EVENT_DEVICE_ADDED */ for (size_t i = 0; ; i++) { struct ei_region *r = ei_device_get_region(dev, i); if (!r) break; printf("Region %zu: offset=(%u,%u) size=%ux%u scale=%.2f id=%s\n", i, ei_region_get_x(r), ei_region_get_y(r), ei_region_get_width(r), ei_region_get_height(r), ei_region_get_physical_scale(r), ei_region_get_mapping_id(r) ?: "(none)"); /* Convert a desktop-wide coordinate to region-relative */ double x = 1920.5, y = 540.0; if (ei_region_convert_point(r, &x, &y)) printf(" -> region-local: (%.1f, %.1f)\n", x, y); } /* Find which region contains a given desktop coordinate */ struct ei_region *hit = ei_device_get_region_at(dev, 960.0, 540.0); if (hit) printf("Point is in region at offset (%u,%u)\n", ei_region_get_x(hit), ei_region_get_y(hit)); ``` -------------------------------- ### eis_new() / eis_setup_backend_fd() / eis_backend_fd_add_client() Source: https://context7.com/libinput/libei/llms.txt Creates a top-level EIS context, sets up a backend (fd-based or socket-based), and adds clients to obtain socket file descriptors. ```APIDOC ## eis_new() / eis_setup_backend_fd() / eis_backend_fd_add_client() ### Description Creates a top-level `eis` context, sets up a backend (fd-based for production, socket-based for testing), then calls `eis_backend_fd_add_client()` for each incoming client to obtain a socket fd to hand to the client side. ### Method C function calls ### Parameters - `eis_new(NULL)`: Initializes a new EIS context. - `eis_setup_backend_fd(eis)`: Sets up an fd-based backend. - `eis_setup_backend_socket(eis, "eis-0")`: Sets up a socket-based backend for testing. - `eis_backend_fd_add_client(eis)`: Adds a client and returns a file descriptor for it. ### Request Example ```c #include #include #include int main(void) { struct eis *eis = eis_new(NULL); /* Preferred backend: private per-client fds */ if (eis_setup_backend_fd(eis) < 0) { /* fallback for testing */ eis_setup_backend_socket(eis, "eis-0"); } struct pollfd pfd = { .fd = eis_get_fd(eis), .events = POLLIN }; /* Simulate handing an fd to an incoming client process */ int client_fd = eis_backend_fd_add_client(eis); /* Pass client_fd to the client (e.g. via socket SCM_RIGHTS) */ printf("Client fd to hand over: %d\n", client_fd); while (1) { poll(&pfd, 1, -1); eis_dispatch(eis); struct eis_event *ev; while ((ev = eis_get_event(eis)) != NULL) { switch (eis_event_get_type(ev)) { case EIS_EVENT_CLIENT_CONNECT: { struct eis_client *c = eis_event_get_client(ev); printf("Client \'%s\' connecting\n", eis_client_get_name(c)); eis_client_connect(c); /* or eis_client_disconnect(c) to reject */ break; } case EIS_EVENT_CLIENT_DISCONNECT: printf("Client disconnected\n"); break; case EIS_EVENT_SEAT_BIND: /* see next example */ break; default: break; } eis_event_unref(ev); } } eis_unref(eis); } ``` ``` -------------------------------- ### Connect to EIS Implementation using File Descriptor Source: https://context7.com/libinput/libei/llms.txt Use `ei_setup_backend_fd()` to connect to an EIS implementation by taking ownership of a pre-opened socket file descriptor. Ensure the file descriptor is not closed manually after being passed to libei. ```c /* Using a pre-opened fd (e.g. from liboeffis / portal handover) */ int eis_fd = oeffis_get_eis_fd(oeffis_ctx); /* see liboeffis section */ struct ei *ei = ei_new_sender(NULL); ei_configure_name(ei, "demo-sender"); int rc = ei_setup_backend_fd(ei, eis_fd); /* eis_fd is now owned by libei — do not close it manually */ if (rc < 0) { perror("ei_setup_backend_fd"); ei_unref(ei); } ``` -------------------------------- ### Compile and Link with libei Source: https://github.com/libinput/libei/blob/main/doc/api/mainpage.dox Rudimentary way to compile and link a C program against the libei library using pkg-config. ```c gcc -o myprogram mylibeiclient.c `pkg-config --cflags --libs libei-1.0` ``` -------------------------------- ### Connect to an EIS implementation Source: https://context7.com/libinput/libei/llms.txt Connects to an EIS implementation using either a pre-opened file descriptor or a socket path. `ei_setup_backend_fd()` is preferred for ownership of existing file descriptors, while `ei_setup_backend_socket()` is useful for testing by connecting via a specified path. ```APIDOC ## `ei_setup_backend_fd()` / `ei_setup_backend_socket()` — Connect to an EIS implementation `ei_setup_backend_fd()` is the preferred entry point: it takes ownership of a pre-opened socket fd (e.g. one obtained from the XDG RemoteDesktop portal). `ei_setup_backend_socket()` connects by path (useful for testing); if `socketpath` is `NULL` it uses `$LIBEI_SOCKET` or `$XDG_RUNTIME_DIR/eis-0`. ```c /* Using a pre-opened fd (e.g. from liboeffis / portal handover) */ int eis_fd = oeffis_get_eis_fd(oeffis_ctx); /* see liboeffis section */ struct ei *ei = ei_new_sender(NULL); ei_configure_name(ei, "demo-sender"); int rc = ei_setup_backend_fd(ei, eis_fd); /* eis_fd is now owned by libei — do not close it manually */ if (rc < 0) { perror("ei_setup_backend_fd"); ei_unref(ei); } /* Using socket path for testing/debugging */ struct ei *ei2 = ei_new_receiver(NULL); ei_configure_name(ei2, "demo-receiver"); rc = ei_setup_backend_socket(ei2, "eis-0"); /* relative to $XDG_RUNTIME_DIR */ ``` ``` -------------------------------- ### eis_client_new_seat() / eis_seat_configure_capability() / eis_seat_add() Source: https://context7.com/libinput/libei/llms.txt After accepting a client, this sequence creates a seat, configures its capabilities, publishes it, and prepares for device binding. ```APIDOC ## eis_client_new_seat() / eis_seat_configure_capability() / eis_seat_add() ### Description After accepting a client, the server creates a seat, configures which capabilities it supports, publishes it with `eis_seat_add()`, then waits for `EIS_EVENT_SEAT_BIND`. On that event it creates and configures devices matching the bound capabilities. ### Method C function calls ### Parameters - `eis_client_new_seat(client, "default")`: Creates a new seat associated with a client. - `eis_seat_configure_capability(seat, capability)`: Configures a capability for the seat (e.g., `EIS_DEVICE_CAP_POINTER`, `EIS_DEVICE_CAP_KEYBOARD`, `EIS_DEVICE_CAP_TOUCH`). - `eis_seat_add(seat)`: Publishes the configured seat. ### Request Example ```c case EIS_EVENT_CLIENT_CONNECT: { struct eis_client *client = eis_event_get_client(ev); eis_client_connect(client); /* Create a seat that supports pointer + keyboard + touch */ struct eis_seat *seat = eis_client_new_seat(client, "default"); eis_seat_configure_capability(seat, EIS_DEVICE_CAP_POINTER); eis_seat_configure_capability(seat, EIS_DEVICE_CAP_KEYBOARD); eis_seat_configure_capability(seat, EIS_DEVICE_CAP_TOUCH); eis_seat_add(seat); eis_seat_unref(seat); break; } case EIS_EVENT_SEAT_BIND: { struct eis_seat *seat = eis_event_get_seat(ev); if (eis_event_seat_has_capability(ev, EIS_DEVICE_CAP_POINTER)) { struct eis_device *dev = eis_seat_new_device(seat); eis_device_configure_name(dev, "virtual pointer"); eis_device_configure_type(dev, EIS_DEVICE_TYPE_VIRTUAL); eis_device_configure_capability(dev, EIS_DEVICE_CAP_POINTER); eis_device_configure_capability(dev, EIS_DEVICE_CAP_BUTTON); eis_device_configure_capability(dev, EIS_DEVICE_CAP_SCROLL); /* Add a 1920x1080 region for absolute pointer use */ struct eis_region *r = eis_device_new_region(dev); eis_region_set_size(r, 1920, 1080); eis_region_set_offset(r, 0, 0); eis_region_add(r); eis_region_unref(r); eis_device_add(dev); eis_device_resume(dev); eis_device_unref(dev); } break; } ``` ``` -------------------------------- ### Connect to EIS Implementation using Socket Path Source: https://context7.com/libinput/libei/llms.txt Use `ei_setup_backend_socket()` to connect to an EIS implementation via a socket path, which is useful for testing. If no path is provided, it defaults to `$LIBEI_SOCKET` or `$XDG_RUNTIME_DIR/eis-0`. ```c /* Using socket path for testing/debugging */ struct ei *ei2 = ei_new_receiver(NULL); ei_configure_name(ei2, "demo-receiver"); rc = ei_setup_backend_socket(ei2, "eis-0"); /* relative to $XDG_RUNTIME_DIR */ ``` -------------------------------- ### Create EIS Context and Handle Clients Source: https://context7.com/libinput/libei/llms.txt Initializes an EIS context, sets up a backend (FD or socket), and adds clients. Handles client connections and disconnections by dispatching events. ```c #include #include #include int main(void) { struct eis *eis = eis_new(NULL); /* Preferred backend: private per-client fds */ if (eis_setup_backend_fd(eis) < 0) { /* fallback for testing */ eis_setup_backend_socket(eis, "eis-0"); } struct pollfd pfd = { .fd = eis_get_fd(eis), .events = POLLIN }; /* Simulate handing an fd to an incoming client process */ int client_fd = eis_backend_fd_add_client(eis); /* Pass client_fd to the client (e.g. via socket SCM_RIGHTS) */ printf("Client fd to hand over: %d\n", client_fd); while (1) { poll(&pfd, 1, -1); eis_dispatch(eis); struct eis_event *ev; while ((ev = eis_get_event(eis)) != NULL) { switch (eis_event_get_type(ev)) { case EIS_EVENT_CLIENT_CONNECT: { struct eis_client *c = eis_event_get_client(ev); printf("Client '%s' connecting\n", eis_client_get_name(c)); eis_client_connect(c); /* or eis_client_disconnect(c) to reject */ break; } case EIS_EVENT_CLIENT_DISCONNECT: printf("Client disconnected\n"); break; case EIS_EVENT_SEAT_BIND: /* see next example */ break; default: break; } eis_event_unref(ev); } } eis_unref(eis); } ``` -------------------------------- ### Compile and Link with libeis Source: https://github.com/libinput/libei/blob/main/doc/api/mainpage.dox Rudimentary way to compile and link a C program against the libeis library using pkg-config. ```c gcc -o myprogram myEISimplementation.c `pkg-config --cflags --libs libeis-1.0` ``` -------------------------------- ### Compile and Link with liboeffis Source: https://github.com/libinput/libei/blob/main/doc/api/mainpage.dox Rudimentary way to compile and link a C program against the liboeffis library using pkg-config. ```c gcc -o myprogram myimpl.c `pkg-config --cflags --libs liboeffis-1.0` ``` -------------------------------- ### Provision Seats and Devices for Clients Source: https://context7.com/libinput/libei/llms.txt Configures and adds seats with specified capabilities for a client, then binds and configures devices based on those capabilities upon receiving a SEAT_BIND event. ```c case EIS_EVENT_CLIENT_CONNECT: { struct eis_client *client = eis_event_get_client(ev); eis_client_connect(client); /* Create a seat that supports pointer + keyboard + touch */ struct eis_seat *seat = eis_client_new_seat(client, "default"); eis_seat_configure_capability(seat, EIS_DEVICE_CAP_POINTER); eis_seat_configure_capability(seat, EIS_DEVICE_CAP_KEYBOARD); eis_seat_configure_capability(seat, EIS_DEVICE_CAP_TOUCH); eis_seat_add(seat); eis_seat_unref(seat); break; } case EIS_EVENT_SEAT_BIND: { struct eis_seat *seat = eis_event_get_seat(ev); if (eis_event_seat_has_capability(ev, EIS_DEVICE_CAP_POINTER)) { struct eis_device *dev = eis_seat_new_device(seat); eis_device_configure_name(dev, "virtual pointer"); eis_device_configure_type(dev, EIS_DEVICE_TYPE_VIRTUAL); eis_device_configure_capability(dev, EIS_DEVICE_CAP_POINTER); eis_device_configure_capability(dev, EIS_DEVICE_CAP_BUTTON); eis_device_configure_capability(dev, EIS_DEVICE_CAP_SCROLL); /* Add a 1920x1080 region for absolute pointer use */ struct eis_region *r = eis_device_new_region(dev); eis_region_set_size(r, 1920, 1080); eis_region_set_offset(r, 0, 0); eis_region_add(r); eis_region_unref(r); eis_device_add(dev); eis_device_resume(dev); eis_device_unref(dev); } break; } ``` -------------------------------- ### Establish Portal Connection with liboeffis Source: https://context7.com/libinput/libei/llms.txt Connects to the XDG RemoteDesktop portal to acquire an EIS file descriptor for remote desktop sessions. The context must remain alive for the EIS connection's lifetime. Requires keyboard and pointer access. ```c #include #include #include #include int main(void) { struct oeffis *oeffis = oeffis_new(NULL); /* Request keyboard + pointer access via the portal */ oeffis_create_session(oeffis, OEFFIS_DEVICE_KEYBOARD | OEFFIS_DEVICE_POINTER); struct pollfd pfd = { .fd = oeffis_get_fd(oeffis), .events = POLLIN }; struct ei *ei = NULL; while (1) { poll(&pfd, 1, -1); oeffis_dispatch(oeffis); enum oeffis_event_type oev; while ((oev = oeffis_get_event(oeffis)) != OEFFIS_EVENT_NONE) { switch (oev) { case OEFFIS_EVENT_CONNECTED_TO_EIS: { int eis_fd = oeffis_get_eis_fd(oeffis); if (eis_fd < 0) { fprintf(stderr, "No EIS fd\n"); break; } ei = ei_new_sender(NULL); ei_configure_name(ei, "portal-demo"); ei_setup_backend_fd(ei, eis_fd); /* takes ownership of eis_fd */ /* Update poll fd to the ei fd from here on */ pfd.fd = ei_get_fd(ei); printf("Connected to EIS via portal\n"); break; } case OEFFIS_EVENT_CLOSED: printf("RemoteDesktop session closed by compositor\n"); goto done; case OEFFIS_EVENT_DISCONNECTED: fprintf(stderr, "Portal error: %s\n", oeffis_get_error_message(oeffis)); goto done; default: break; } } if (ei) { ei_dispatch(ei); struct ei_event *ev; while ((ev = ei_get_event(ei)) != NULL) { /* handle EI events as shown in the libei examples */ ei_event_unref(ev); } } } done: if (ei) ei_unref(ei); oeffis_unref(oeffis); /* closes DBus connection — do this last */ return 0; } ``` -------------------------------- ### Assign XKB Keymap with eis_device_new_keymap() Source: https://context7.com/libinput/libei/llms.txt Assign a per-device XKB keymap to a keyboard device. Pass a file descriptor containing the XKB keymap data. Ensure to call eis_keymap_add() before eis_device_add(). ```c #include #include #include /* Minimal XKB keymap string (real usage would use libxkbcommon) */ const char *xkb_data = "xkb_keymap { ... };"; size_t xkb_size = strlen(xkb_data) + 1; int fd = memfd_create("xkb-keymap", MFD_CLOEXEC); ftruncate(fd, (off_t)xkb_size); void *buf = mmap(NULL, xkb_size, PROT_WRITE, MAP_SHARED, fd, 0); memcpy(buf, xkb_data, xkb_size); munmap(buf, xkb_size); struct eis_keymap *km = eis_device_new_keymap(kbd_device, EIS_KEYMAP_TYPE_XKB, fd, xkb_size); close(fd); /* libeis dup()s the fd */ if (km) { eis_keymap_add(km); /* must be called before eis_device_add() */ eis_keymap_unref(km); } eis_device_add(kbd_device); eis_device_resume(kbd_device); ``` -------------------------------- ### Process Receiver Events with libei Source: https://context7.com/libinput/libei/llms.txt This C code demonstrates the event loop for a libei receiver. It sets up a receiver context, configures the backend, and then enters a loop to poll for and dispatch events. Specific event types like pointer motion, keyboard key presses, button events, touch input, and text input are handled with corresponding accessor functions. It also shows how to handle device addition and disconnection. ```c /* Receiver context event loop */ struct ei *ei = ei_new_receiver(NULL); ei_configure_name(ei, "input-capture"); ei_setup_backend_socket(ei, NULL); struct pollfd pfd = { .fd = ei_get_fd(ei), .events = POLLIN }; while (1) { poll(&pfd, 1, -1); ei_dispatch(ei); struct ei_event *ev; while ((ev = ei_get_event(ei)) != NULL) { switch (ei_event_get_type(ev)) { case EI_EVENT_SEAT_ADDED: ei_seat_bind_capabilities(ei_event_get_seat(ev), EI_DEVICE_CAP_POINTER, EI_DEVICE_CAP_KEYBOARD, NULL); break; case EI_EVENT_POINTER_MOTION: printf("Pointer motion dx=%.2f dy=%.2f\n", ei_event_pointer_get_dx(ev), ei_event_pointer_get_dy(ev)); break; case EI_EVENT_KEYBOARD_KEY: printf("Key %u %s\n", ei_event_keyboard_get_key(ev), ei_event_keyboard_get_key_is_press(ev) ? "down" : "up"); break; case EI_EVENT_BUTTON_BUTTON: printf("Button %u %s\n", ei_event_button_get_button(ev), ei_event_button_get_is_press(ev) ? "pressed" : "released"); break; case EI_EVENT_TOUCH_DOWN: printf("Touch %u down at %.1f,%.1f\n", ei_event_touch_get_id(ev), ei_event_touch_get_x(ev), ei_event_touch_get_y(ev)); break; case EI_EVENT_TEXT_UTF8: /* since 1.6 */ printf("Text: %s\n", ei_event_text_get_utf8(ev)); break; case EI_EVENT_DISCONNECT: ei_event_unref(ev); ei_unref(ei); return 0; default: break; } ei_event_unref(ev); } } ``` -------------------------------- ### Emulate Touch Events with ei_device_touch_new() / ei_touch_down() / ei_touch_motion() / ei_touch_up() Source: https://context7.com/libinput/libei/llms.txt Emulates multi-touch gestures by creating individual touch points, initiating them with `ei_touch_down()`, optionally moving them with `ei_touch_motion()`, and concluding with `ei_touch_up()`. Remember to unreference touch objects when done. ```c ei_device_start_emulating(touch_dev, ++seq); /* Two-finger tap */ struct ei_touch *t1 = ei_device_touch_new(touch_dev); struct ei_touch *t2 = ei_device_touch_new(touch_dev); ei_touch_down(t1, 200.0, 400.0); ei_touch_down(t2, 250.0, 400.0); ei_device_frame(touch_dev, ei_now(ei)); ei_touch_up(t1); ei_touch_up(t2); ei_device_frame(touch_dev, ei_now(ei)); ei_touch_unref(t1); ei_touch_unref(t2); ei_device_stop_emulating(touch_dev); ``` -------------------------------- ### ei-scanner Help Output Source: https://github.com/libinput/libei/blob/main/doc/protocol/doc/ei-scanner.md Displays the help message for the ei-scanner tool, outlining its usage, arguments, and options. Use this to understand how to invoke the scanner and its available configurations. ```bash $ ei-scanner --help usage: ei-scanner [-h] [--component {ei,eis,brei}] [--output OUTPUT] [--jinja-extra-data JINJA_EXTRA_DATA] [--jinja-extra-data-file JINJA_EXTRA_DATA_FILE] protocol template ei-scanner is a tool to parse the EI protocol description XML and pass the data to a Jinja2 template. That template can then be used to generate protocol bindings for the desired language. typical usages: ei-scanner --component=ei protocol.xml my-template.tpl ei-scanner --component=eis --output=bindings.rs protocol.xml bindings.rs.tpl Elements in the XML file are provided as variables with attributes generally matching the XML file. For example, each interface has requests, events and enums, and each of those has a name. ei-scanner additionally provides the following values to the Jinja2 templates: - interface.incoming and interface.outgoing: maps to the requests/events of the interface, depending on the component. - argument.signature: a single-character signature type mapping from the protocol XML type: uint32 -> "u" int32 -> "i" uint64 -> "t" int64 -> "x" float -> "f" fd -> "h" new_id -> "n" object -> "o" string -> "s" ei-scanner adds the following Jinja2 filters for convenience: {{foo|c_type}} ... resolves to "struct foo *" {{foo|as_c_arg}} ... resolves to "struct foo *foo" {{foo_bar|camel}} ... resolves to "FooBar" positional arguments: protocol The protocol XML file template The Jinja2 compatible template file options: -h, --help show this help message and exit --component {ei,eis,brei} --output OUTPUT Output file to write to --jinja-extra-data JINJA_EXTRA_DATA Extra data (in JSON format) to pass through to the Jinja template as 'extra' --jinja-extra-data-file JINJA_EXTRA_DATA_FILE Path to file with extra data to pass through to the Jinja template as 'extra' ``` -------------------------------- ### Portal Connection with liboeffis Source: https://context7.com/libinput/libei/llms.txt Establishes a connection to the XDG RemoteDesktop portal using liboeffis to acquire an EIS file descriptor for direct use with ei_setup_backend_fd(). ```APIDOC ## oeffis_new() / oeffis_create_session() / oeffis_get_eis_fd() ### Description These functions abstract the DBus session handshake with the XDG RemoteDesktop portal, acquiring an EIS file descriptor that can then be passed directly to `ei_setup_backend_fd()`. The context must remain alive for the lifetime of the EIS connection. ### Functions - `oeffis_new(struct oeffis *oeffis)`: Initializes a new oeffis context. - `oeffis_create_session(struct oeffis *oeffis, uint32_t devices)`: Requests device access (e.g., keyboard, pointer) via the portal. - `oeffis_get_eis_fd(struct oeffis *oeffis)`: Retrieves the EIS file descriptor. - `oeffis_dispatch(struct oeffis *oeffis)`: Processes pending portal events. - `oeffis_get_event(struct oeffis *oeffis)`: Retrieves the next oeffis event. - `oeffis_get_fd(struct oeffis *oeffis)`: Gets the file descriptor for the oeffis context. - `oeffis_unref(struct oeffis *oeffis)`: Destroys the oeffis context and closes the DBus connection. ### Example Usage ```c #include #include #include #include int main(void) { struct oeffis *oeffis = oeffis_new(NULL); /* Request keyboard + pointer access via the portal */ oeffis_create_session(oeffis, OEFFIS_DEVICE_KEYBOARD | OEFFIS_DEVICE_POINTER); struct pollfd pfd = { .fd = oeffis_get_fd(oeffis), .events = POLLIN }; struct ei *ei = NULL; while (1) { poll(&pfd, 1, -1); oeffis_dispatch(oeffis); enum oeffis_event_type oev; while ((oev = oeffis_get_event(oeffis)) != OEFFIS_EVENT_NONE) { switch (oev) { case OEFFIS_EVENT_CONNECTED_TO_EIS: { int eis_fd = oeffis_get_eis_fd(oeffis); if (eis_fd < 0) { fprintf(stderr, "No EIS fd\n"); break; } ei = ei_new_sender(NULL); ei_configure_name(ei, "portal-demo"); ei_setup_backend_fd(ei, eis_fd); /* takes ownership of eis_fd */ /* Update poll fd to the ei fd from here on */ pfd.fd = ei_get_fd(ei); printf("Connected to EIS via portal\n"); break; } case OEFFIS_EVENT_CLOSED: printf("RemoteDesktop session closed by compositor\n"); goto done; case OEFFIS_EVENT_DISCONNECTED: fprintf(stderr, "Portal error: %s\n", oeffis_get_error_message(oeffis)); goto done; default: break; } } if (ei) { ei_dispatch(ei); struct ei_event *ev; while ((ev = ei_get_event(ei)) != NULL) { /* handle EI events as shown in the libei examples */ ei_event_unref(ev); } } } done: if (ei) ei_unref(ei); oeffis_unref(oeffis); /* closes DBus connection — do this last */ return 0; } ``` ``` -------------------------------- ### Emulate Input Sequence Lifecycle Source: https://context7.com/libinput/libei/llms.txt Bracket input sequences with `ei_device_start_emulating()` and `ei_device_stop_emulating()`. Use a monotonically increasing sequence number for `ei_device_start_emulating()`. Terminate each set of input events within a frame using `ei_device_frame()` with a `CLOCK_MONOTONIC` timestamp. ```c /* Typical sender sequence for injecting mouse + button */ uint32_t seq = 1; ei_device_start_emulating(device, seq); /* Move mouse 10px right, 5px down */ ei_device_pointer_motion(device, 10.0, 5.0); /* Frame closes this logical event group */ ei_device_frame(device, ei_now(ei)); /* Click left button (BTN_LEFT = 0x110) */ ei_device_button_button(device, BTN_LEFT, true); ei_device_frame(device, ei_now(ei)); ei_device_button_button(device, BTN_LEFT, false); ei_device_frame(device, ei_now(ei)); ei_device_stop_emulating(device); ``` -------------------------------- ### Emulation lifecycle (sender) Source: https://context7.com/libinput/libei/llms.txt Manages the lifecycle of input emulation for a device. Input sequences must be bracketed by `ei_device_start_emulating()` and `ei_device_stop_emulating()`. Each logical frame of input events must be terminated by `ei_device_frame()` with a timestamp. ```APIDOC ## `ei_device_start_emulating()` / `ei_device_stop_emulating()` / `ei_device_frame()` — Emulation lifecycle (sender) Every input sequence must be bracketed by `ei_device_start_emulating()` (with a monotonically increasing sequence number) and `ei_device_stop_emulating()`. Every set of input events within one logical hardware frame must be terminated by `ei_device_frame()` with a CLOCK_MONOTONIC timestamp from `ei_now()`. ```c /* Typical sender sequence for injecting mouse + button */ uint32_t seq = 1; ei_device_start_emulating(device, seq); /* Move mouse 10px right, 5px down */ ei_device_pointer_motion(device, 10.0, 5.0); /* Frame closes this logical event group */ ei_device_frame(device, ei_now(ei)); /* Click left button (BTN_LEFT = 0x110) */ ei_device_button_button(device, BTN_LEFT, true); ei_device_frame(device, ei_now(ei)); ei_device_button_button(device, BTN_LEFT, false); ei_device_frame(device, ei_now(ei)); ei_device_stop_emulating(device); ``` ``` -------------------------------- ### Handle Device Ready with EIS_FLAG_DEVICE_READY Source: https://context7.com/libinput/libei/llms.txt Handle the EIS_FLAG_DEVICE_READY flag to manage ei_device protocol version 3. The server must wait for EIS_EVENT_DEVICE_READY before calling eis_device_resume(). ```c struct eis *eis = eis_new(NULL); eis_set_flag(eis, EIS_FLAG_DEVICE_READY); /* must be before backend setup */ eis_setup_backend_fd(eis); /* ... later in event loop ... */ case EIS_EVENT_DEVICE_READY: { struct eis_device *dev = eis_event_get_device(ev); eis_device_resume(dev); /* safe to resume now */ break; } ``` -------------------------------- ### EIS flag `EIS_FLAG_DEVICE_READY` (since 1.6) Source: https://context7.com/libinput/libei/llms.txt This flag enables protocol version 3 for `ei_device`. When set, compatible clients send `ei_device.ready` after receiving `ei_device.done`, which triggers an `EIS_EVENT_DEVICE_READY` event. The server must wait for this event before calling `eis_device_resume()`. ```APIDOC ## EIS flag `EIS_FLAG_DEVICE_READY` (since 1.6) When this flag is set, libeis announces ei_device protocol version 3. Compatible clients send `ei_device.ready` after receiving `ei_device.done`, triggering an `EIS_EVENT_DEVICE_READY` event. The server must wait for this event before calling `eis_device_resume()`. ```c struct eis *eis = eis_new(NULL); eis_set_flag(eis, EIS_FLAG_DEVICE_READY); /* must be before backend setup */ eis_setup_backend_fd(eis); /* ... later in event loop ... */ case EIS_EVENT_DEVICE_READY: { struct eis_device *dev = eis_event_get_device(ev); eis_device_resume(dev); /* safe to resume now */ break; } ``` ``` -------------------------------- ### Perform Synchronization Roundtrip with ei_ping() / EI_EVENT_PONG Source: https://context7.com/libinput/libei/llms.txt Initiates a synchronization request using `ei_ping()` and waits for `EI_EVENT_PONG`. This is useful for confirming that previously sent events, such as modifier key changes, have been processed by the EIS implementation. User data can be attached to the ping request. ```c struct ei_ping *ping = ei_new_ping(ei); ei_ping_set_user_data(ping, (void *)0xdeadbeef); ei_ping(ping); /* ... process events until EI_EVENT_PONG ... */ case EI_EVENT_PONG: { struct ei_ping *p = ei_event_pong_get_ping(ev); void *tag = ei_ping_get_user_data(p); printf("Pong received for ping %p, tag=%p\n", (void*)p, tag); ei_ping_unref(ping); break; } ``` -------------------------------- ### Receiver Event Processing Source: https://context7.com/libinput/libei/llms.txt This snippet demonstrates the event loop for a receiver context, showing how to read events using `ei_get_event()` and process different event types such as pointer motion, keyboard input, and touch events. ```APIDOC ## Receiver event processing — `EI_EVENT_POINTER_MOTION`, `EI_EVENT_KEYBOARD_KEY`, etc. For a receiver context the EIS implementation sends events to the client. The client reads them via `ei_get_event()` and extracts values through typed accessor functions. ```c /* Receiver context event loop */ struct ei *ei = ei_new_receiver(NULL); ei_configure_name(ei, "input-capture"); ei_setup_backend_socket(ei, NULL); struct pollfd pfd = { .fd = ei_get_fd(ei), .events = POLLIN }; while (1) { poll(&pfd, 1, -1); ei_dispatch(ei); struct ei_event *ev; while ((ev = ei_get_event(ei)) != NULL) { switch (ei_event_get_type(ev)) { case EI_EVENT_SEAT_ADDED: ei_seat_bind_capabilities(ei_event_get_seat(ev), EI_DEVICE_CAP_POINTER, EI_DEVICE_CAP_KEYBOARD, NULL); break; case EI_EVENT_POINTER_MOTION: printf("Pointer motion dx=%.2f dy=%.2f\n", ei_event_pointer_get_dx(ev), ei_event_pointer_get_dy(ev)); break; case EI_EVENT_KEYBOARD_KEY: printf("Key %u %s\n", ei_event_keyboard_get_key(ev), ei_event_keyboard_get_key_is_press(ev) ? "down" : "up"); break; case EI_EVENT_BUTTON_BUTTON: printf("Button %u %s\n", ei_event_button_get_button(ev), ei_event_button_get_is_press(ev) ? "pressed" : "released"); break; case EI_EVENT_TOUCH_DOWN: printf("Touch %u down at %.1f,%.1f\n", ei_event_touch_get_id(ev), ei_event_touch_get_x(ev), ei_event_touch_get_y(ev)); break; case EI_EVENT_TEXT_UTF8: /* since 1.6 */ printf("Text: %s\n", ei_event_text_get_utf8(ev)); break; case EI_EVENT_DISCONNECT: ei_event_unref(ev); ei_unref(ei); return 0; default: break; } ei_event_unref(ev); } } ``` ``` -------------------------------- ### Initial Handshake Sequence Diagram Source: https://github.com/libinput/libei/blob/main/doc/protocol/doc/initial-handshake.md This diagram illustrates the sequence of messages exchanged during the initial connection handshake between a client and the EIS implementation, from socket connection to the first events sent by the client. ```mermaid sequenceDiagram participant client participant EIS client->>EIS: connect to socket Note over client, EIS: ei_handshake object 0 EIS-->>client: ei_handshake.handshake_version(N) client->>EIS: ei_handshake.handshake_version(M) client->>EIS: ei_handshake.context_type(sender) client->>EIS: ei_handshake.name(some client) client->>EIS: ei_handshake.interface_version(ei_connection) client->>EIS: ei_handshake.interface_version(ei_callback) client->>EIS: ei_handshake.interface_version(ei_seat) client->>EIS: ei_handshake.interface_version(ei_device) client->>EIS: ei_handshake.interface_version(ei_pointer) client->>EIS: ei_handshake.interface_version(ei_keyboard) client->>EIS: ei_handshake.finish() EIS-->>client: ei_handshake.interface_version(ei_callback) EIS-->>client: ei_handshake.connection(new_id, version) Note over client, EIS: ei_handshake object is destroyed EIS-->>client: ei_connection.seat(new_id, version) EIS-->>client: ei_seat.name(some seat) EIS-->>client: ei_seat.capabilities() EIS-->>client: ei_seat.done() client->>EIS: ei_seat.bind() EIS-->>client: ei_seat.device(new_id, version) EIS-->>client: ei_device.name(some name) EIS-->>client: ei_device.device_type() EIS-->>client: ei_device.capabilities(some name) EIS-->>client: ei_device.interface(new_id, "ei_pointer", version) EIS-->>client: ei_device.interface(new_id, "ei_keyboard", version) EIS-->>client: ei_seat.device(new_id, version) EIS-->>client: ei_device.name(some name) EIS-->>client: ei_device.device_type() EIS-->>client: ei_device.capabilities(some name) EIS-->>client: ei_device.region() EIS-->>client: ei_device.interface(new_id, "ei_touchscreen", version) EIS-->>client: ei_device.resume() EIS-->>client: ei_device.resume() Note over client, EIS: client may emulate now client->>EIS: ei_pointer.start_emulating() client->>EIS: ei_pointer.motion() client->>EIS: ei_pointer.frame() ``` -------------------------------- ### Keyboard key events (sender) Source: https://context7.com/libinput/libei/llms.txt Generates keyboard key events using raw keycodes. To produce specific keysyms, map them to the appropriate keycodes using the device's keymap obtained via `ei_device_keyboard_get_keymap()`. ```APIDOC ## `ei_device_keyboard_key()` — Keyboard key events (sender) Generates raw keycode events (evdev scan codes from `linux/input-event-codes.h`). To generate a specific keysym, look up the keymap returned by `ei_device_keyboard_get_keymap()` and derive the corresponding keycode. ```c #include /* Type "hi" by pressing H then I */ ei_device_start_emulating(kbd, ++seq); ei_device_keyboard_key(kbd, KEY_H, true); ei_device_frame(kbd, ei_now(ei)); ei_device_keyboard_key(kbd, KEY_H, false); ei_device_frame(kbd, ei_now(ei)); ei_device_keyboard_key(kbd, KEY_I, true); ei_device_frame(kbd, ei_now(ei)); ei_device_keyboard_key(kbd, KEY_I, false); ei_device_frame(kbd, ei_now(ei)); ei_device_stop_emulating(kbd); ``` ```