commit 5e649f682ab3f404919717b8757c9c8ebcfde52f Author: Peter Hutterer Date: Tue Jul 14 14:41:32 2020 +1000 Initial commit This is the outline of the API intended with a minimal compiler just to verify there are no immediate parsing issues. diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..fc27585 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,30 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +trim_trailing_whitespace = true +insert_final_newline = true + +[meson.build] +indent_style = space +indent_size = 4 +tab_width = 4 + +[*.md] +indent_style = space +tab_width = 4 + +[*.{c,h}] +indent_style = tab +tab_width = 8 + +[*.py] +indent_style = space +indent_size = 4 +charset = utf-8 + +[*.{yaml,tmpl}] +indent_style = space +indent_size = 2 +charset = utf-8 diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..96a5fdb --- /dev/null +++ b/COPYING @@ -0,0 +1,20 @@ +Copyright © 2020 Red Hat, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice (including the next +paragraph) shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f25483d --- /dev/null +++ b/README.md @@ -0,0 +1,138 @@ +libei +===== + +**libei** is a library for Emulated Input, primarily aimed at the Wayland +stack. It provides two parts: +- 🥚 EI for the client side (`libei`) +- 🍦 EIS for the server side (`libeis`) + +The communication between the two is an implementation detail, neither +client nor server need to care about the details. Let's call it the BRidge +for EI, or 🥣 brei. + +For the purpose of this document, **libei** refers to the project, +`libei`/`libeis` to the two libraries provided. + +In the Wayland stack, the EIS server component is part of the +compositor, the EI client component is part of the Wayland client. + + +``` + +--------------------+ +------------------+ + | Wayland compositor |---wayland---| Wayland client B | + +--------------------+\ +------------------+ + | libinput | libeis | \_wayland______ + +----------+---------+ \ + | | +-------+------------------+ + /dev/input/ +---brei----| libei | Wayland client A | + +-------+------------------+ +``` + +The use-cases **libei** attempts to solve are: +- on-demand input device emulation, e.g. `xdotool` or more generically the + XTEST extension +- input forwarding, e.g. `synergy` + +**libei** provides three benefits: +- separation +- distinction +- control + +**libei** provides **separation** of emulated input from normal input. +Emulated input is a distinct channel for the compositor and can thus be +handled accordingly. For example, the server may show warning sign in the +task bar while emulated input is active. + +The second benefit is **distinction**. Each **libei** client has its own +input device set, the server is always aware of which client is requesting +input at any time. It is possible for the server to treat input from +different emulated input devices differently. + +The server is in **control** of emulated input - it can filter input or +discard at will. For example, if the current focus window is a password +prompt, the server can simply discard any emulated input. If the screen is +locked, the server can cancel all emulated input devices. + +For the use-case of fowarding input (e.g. `synergy`) **libei** provides +capability monitoring. As with input emulation same benefits apply - +input can only be forwarded if the compositor explicitly does so. + +High-level summary +------------------ + +A pseudo-code implementation for server and client are available in +the [`examples/`](https://gitlab.freedesktop.org/whot/libei/-/tree/master/examples) +directory. + +The server starts a `libeis` context (which can be integrated with flatpak +portals) and uses the `libeis` file descriptor to monitor for +client requests. + +A client starts a `libei` context and connects to the server - either +directly, via DBus or via a portal. The server (or the portal) approves or +denies the client. After successful authentications the client can request +the creation of a device with capabilities `pointer`, `keyboard` or `touch`. + +The client triggers input events on this device, the server receives those +as events through `libeis` and can forwards them as if they were libinput +events. The server has control of the client stream. If the stream is +paused, events from the client are discarded. If the stream is resumed, the +server will receive the events (but may discard them anyway depending on +local state). + +The above caters for the `xdotool` use-case. + +The client may request to monitor a capability. When the server deems the +client to be in-focus, it forwards events from real devices to the client. +The decision of what constitutes logical focus and what events to forward +are up to the server. + +For a `synergy` use-case, the setup requires: +- `synergy-client` on host A monitoring the mouse and keyboard capabilities +- `synergy-server` on host B requesting a mouse/keyboard capability device +- when `synergy-client` receives events via `libei` from compositor A it + forwards those to the remote `synergy-server` which sends them via `libei` + to the compositor B. + +The compositor may choose to implement a hotkey to start/stop the events or +it may implement the screen edges to be the hot key. + + +Open questions +-------------- + +### Flatpak integration + +Where flatpak portals are in use, `libei` will communicate with +the portal and `libeis` with the portal implementation (e.g. +`xdg-desktop-portal-gdk`). The portal is reponsible for +allowing the client to connect and restrictions on the devices a client may +create. `libeis` will run in a private namespace of the compositor. + +The portal **may** control suspending/resuming devices (in addition to the +server). The UI for this is not yet sorted. + +### Authentication + +Sandboxing is addressed via flatpak portals but a further level is likely +desirable, esp. outside flatpak. The simplest solution is the client +announcing the name so the UI can be adjusted accordingly. API wise-maybe an +opaque key/value system so the exact auth can be left to the implementation. + +### Triggers + +For `synergy` we need capability monitoring started by triggers, e.g. the +client requests a pointer capability monitoring when the real pointer hits +the screen edge. Or in response to a keyboard shortcut. + +### Keyboard layouts + +The emulated input may require a specific keyboard layout, we need some +keymap notification. Keymaps are effectively one-directional (keycode → +keysym is easy, keysym → keycode can be ambiguous), if the server decides on +a different layout than the client provides it's hard for the client to +figure out what keycodes it needs to send. + +Possible solution here: protocol guarantees that keymap events are only sent +while the keyboard is suspended, this way the client knows that if *no* +keymap event has been sent before resume, the client keymap was accepted. diff --git a/examples/libei-client.pseudo b/examples/libei-client.pseudo new file mode 100644 index 0000000..45d447d --- /dev/null +++ b/examples/libei-client.pseudo @@ -0,0 +1,75 @@ +# This is pseudocode, illustrating a libei client implementation + +function main(): + ctx = ei_new() + ei_portal_connect(ctx) + + # let's say this is a blocking wait + event = ei_get_event(); + if ei_event_get_type(event) == EI_EVENT_TYPE_DISCONNECT: + print("Sorry, server denied us") + return + + # Could also create one device here with both caps + ptr = ei_create_device(ctx, CAP_POINTER, "pseudopointer"); + kbd = ei_create_device(ctx, CAP_KEYBOARD, "pseudokeyboard"); + keymap_fd = compile_keymap("us", "dvorak") + ei_device_keyboard_set_keymap(kbd, FORMAT_XKB, keymap_fd); + + event = ei_get_event() + device = ei_event_get_device() + if ei_event_get_type(event) == EI_EVENT_TYPE_DEVICE_ADDED: + if device == ptr: + # The server may not use the name we suggested but it does + # tell us the chosen name + print("Pointer was created: %s", ei_device_get_name(device)) + else: + print("Keyboard was created: %s", ei_device_get_name(device)) + else if ei_event_get_type(event) == EI_EVENT_TYPE_DEVICE_REMOVED: + if device == ptr: + print("We're not allowed a pointer device") + elif device == kbd: + print("We're not allowed a keyboard device") + + + # Our devices start in suspended mode + suspended = True + + while True: + poll(ei_get_fd()): + if suspended: + wait_for_event(EI_EVENT_POINTER_RESUME or + EI_EVENT_KEYBOARD_RESUME) + suspended = False + continue + + event = ei_get_event(); + handler = functions[ei_event_get_type(event)] + handler(event) + + ei_disconnect(ctx) + + +function event_disconnect(event): + print("Ooops, the server kicked us off") + ei_unref(ctx); + sys.exit(1) + + +function event_keyboard_keymap(event): + # see README comments + keymap_fd = ei_event_keyboard_get_keymap(event)) + recompile_macros(keymap_fd) + + +# Called by our actual application to emulate input +function move_pointer(x, y): + ei_device_pointer_motion(ptr, x, y) + + +function macro(macro): + for key in macro: + ei_device_keyboard_key(kbd, key, true) + msleep(12); # fake an interval + ei_device_keyboard_key(kbd, key, false) + msleep(12); # fake an interval diff --git a/examples/libeis-server.pseudo b/examples/libeis-server.pseudo new file mode 100644 index 0000000..1b807ad --- /dev/null +++ b/examples/libeis-server.pseudo @@ -0,0 +1,117 @@ +# This is pseudocode, illustrating a libeis server implementation +# +# This pseudocode assumes to be part of a compositor that can handle +# input events. It assumes policy decisions as to whether a client may +# connect are made in the portal or the compositor. +# + +function main(): + ctx = eis_new() + eis_portal_init(ctx) + + while True: + poll(eis_get_fd()): + event = eis_get_event(); + handler = functions[eis_event_get_type(event)] + handler(event) + + for client in myclients: + eis_client_disconnect(client) + + eis_unref(ctx) + + +function event_client_connect(event): + client = eis_event_get_client(event) + + if do_not_allow_emulated_input: + eis_client_disconnect(client) + return + + # for the portal backend we're assuming that the client has been + # authenticated by the user, otherwise some authentication needs to + # be done here + eis_client_connect(client) + myclients[client] = eis_client_ref(client) + + +function event_client_disconnect(event): + client = eis_event_get_client(event) + eis_client_unref(client) + + +function event_device_added(event): + client = eis_event_get_client(event) + device = eis_event_get_device(event) + + if client has too many devices: + eis_device_disconnect(device) + return + + capabilities = eis_device_get_capabilties() + if disallow_touch_input: + capabilities &= ~EIS_CAP_TOUCH + + if capabilities & EIS_CAP_POINTER_ABSOLUTE: + # this is the fixed range we give the client, actual screen + # mapping is performed by us later + eis_device_pointer_set_width(1024) + eis_device_pointer_set_height(768) + + eis_device_connect(device) + # Allow the client to send events for all capabilities + eis_device_resume_capability(capabilities) + + +function event_pointer_motion(event): + x = eis_event_pointer_get_x(event) + y = eis_event_pointer_get_y(event) + compositor_handle_pointer_motion(x, y) + + +function event_pointer_motion_absolute(event): + x = eis_event_pointer_get_absolute_x(event) + y = eis_event_pointer_get_absolute_y(event) + + screen = compositor_get_screen() + sx = x/1024 * screen->width + sy = y/768 * screen->height + + compositor_handle_pointer_motion_absolute(sx, sy) + + +function event_pointer_keyboard_key(event): + device = eis_event_get_device(event) + keymap = eis_device_keyboard_get_keymap(device) + # do some libxkbcommon stuff if need be, or send the keymap down the + # wire + + key = eis_event_keyboard_get_key(event) + press = eis_event_keyboard_get_key_is_press(event) + + # double escape toggles pointer input, as an example + if press and key == KEY_ESC and last_key == KEY_ESC: + if pointer_is_suspended: + eis_device_resume_capability(EIS_CAP_POINTER_ABSOLUTE|EIS_CAP_POINTER) + else + eis_device_suspend_capability(EIS_CAP_POINTER_ABSOLUTE|EIS_CAP_POINTER) + return + + compositor_handle_key(key, press) + + +function compositor_vt_switch_callback(): + for client in myclients: + for device in devices[client]: + if vt_in: + eis_device_resume_capability(all_caps) + else + eis_device_suspend_capability(all_caps) + + +# Compositor callback if no more emulated input should be handled +# from a specific client +function compositor_callback_terminate_EI(condition): + for client in myclients: + if client matches condition: + eis_client_disconnect(client) diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..62b0244 --- /dev/null +++ b/meson.build @@ -0,0 +1,39 @@ +project('libei', 'c', + version: '0.1', + license: 'MIT/Expat', + default_options: [ 'c_std=gnu99', 'warning_level=2' ], + meson_version: '>= 0.50.0') + +pkgconfig = import('pkgconfig') + +cc = meson.get_compiler('c') +cppflags = ['-Wno-unused-parameter', '-fvisibility=hidden'] +cflags = cppflags + ['-Wmissing-prototypes', '-Wstrict-prototypes'] +add_project_arguments(cflags, language : 'c') +add_project_arguments(cppflags, language : 'cpp') + +lib_libei = shared_library('ei', + 'src/libei.h', + 'src/libei.c', + install: true +) + +pkgconfig.generate(lib_libei, + filebase: 'libei', + name : 'libEI', + description : 'Emulated Input client library', + version : meson.project_version(), +) + +lib_libeis = shared_library('eis', + 'src/libeis.h', + 'src/libeis.c', + install: true +) +pkgconfig.generate(lib_libeis, + filebase: 'libeis', + name : 'libEIS', + description : 'Emulated Input server library', + version : meson.project_version(), + libraries : lib_libei +) diff --git a/src/libei.c b/src/libei.c new file mode 100644 index 0000000..e92ee69 --- /dev/null +++ b/src/libei.c @@ -0,0 +1,24 @@ +/* + * Copyright © 2020 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "libei.h" diff --git a/src/libei.h b/src/libei.h new file mode 100644 index 0000000..35a0067 --- /dev/null +++ b/src/libei.h @@ -0,0 +1,413 @@ +/* + * Copyright © 2020 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#pragma once + +#include +#include + +struct ei_ctx; +struct ei_device; +struct ei_event; +struct ei_event_client; +struct ei_event_pointer; +struct ei_event_keyboard; +struct ei_event_touch; + +enum ei_device_capability { + EI_DEVICE_CAP_POINTER, + EI_DEVICE_CAP_POINTER_ABSOLUTE, + EI_DEVICE_CAP_KEYBOARD, + EI_DEVICE_CAP_TOUCH, +}; + +enum ei_keymap_type { + EI_KEYMAP_TYPE_XKB = 1, +}; + +enum ei_event_type { + /** + * No event ever has this type, this type exists only as potential + * return value for ei_next_event_type(). + */ + EI_EVENT_NONE = 0, + /** + * The server has approved the connection to this client. + */ + EI_EVENT_CONNECT, + /** + * The server has disconnected this client - all resources left to + * reference this server are now obsolete. Once this event has been + * received, the @ref struct ei_ctx and all its associated resources + * should be released. + */ + EI_EVENT_DISCONNECT, + + /** + * The server has added a device for this client. The capabilities + * of the device may not match the requested capabilities - it is up + * to the client to verify the minimum required capabilities are + * indeed set. + */ + EI_EVENT_DEVICE_ADDED, + /** + * The server has removed a device belonging to this client. + */ + EI_EVENT_DEVICE_REMOVED, + + + /** + * The client may send events. + */ + EI_EVENT_POINTER_RESUME = 300, + /** + * Any events sent will be discarded until the next resume. + */ + EI_EVENT_POINTER_SUSPEND, + /** + * Update pointer ranges. This provides the client with the allowed + * axis ranges for absolute pointer motion. The axis ranges may not + * match the available screen ranges and the axis range may only map + * to a portion of the available screen size. + */ + EI_EVENT_POINTER_RANGES_CHANGED, + + /** + * The client may send events. + */ + EI_EVENT_KEYBOARD_RESUME = 400, + /** + * Any events sent will be discarded until the next resume. + */ + EI_EVENT_KEYBOARD_SUSPEND, + /** + * Notification of the keymap to be used by the client. + */ + EI_EVENT_KEYBOARD_KEYMAP, + + /** + * The client may send events. + */ + EI_EVENT_TOUCH_RESUME = 500, + /** + * Any events sent will be discarded until the next resume. + */ + EI_EVENT_TOUCH_SUSPEND, + /** + * Update touch ranges. This provides the client with the allowed + * axis ranges for absolute pointer motion. The axis ranges may not + * match the available screen ranges and the axis range may only map + * to a portion of the available screen size. + */ + EI_EVENT_TOUCH_RANGES_CHANGED, +}; + +struct ei_ctx * +ei_new(void); + +struct ei_ctx * +ei_ref(struct ei_ctx *ctx); + +struct ei_ctx * +ei_unref(struct ei_ctx *ei); + +/** + * Connect to the libeis server via the org.freedesktop.portal + * + * @return 0 on success or a negative errno on failure + */ +int +ei_portal_connect(struct ei_ctx *ei); + +/** + * Connect to the libeis server on the org.freedesktop.Ei bus name. + * + * @return 0 on success or a negative errno on failure + */ +int +ei_dbus_connect(struct ei_ctx *ei); + +int +ei_get_fd(struct ei_ctx *ei); + +void +ei_dispatch(struct ei_ctx *ei); + +struct ei_event * +ei_get_event(struct ei_ctx *ei); + +enum ei_event_type +ei_next_event_type(struct ei_ctx *ei); + +struct ei_event * +ei_event_unref(struct ei_event *event); + +struct ei_device * +ei_device_ref(struct ei_device *device); + +struct ei_device * +ei_device_unref(struct ei_device *device); + +/** + * Request the creation of a device with suggested capabilities and a + * suggested name. Note that the server may ignore the name and may ignore + * all capabilities. + * + * The device returned is a stub device maintained by the library and should + * only used for initial device configuration. It does not represent the + * server-created device until the @ref EI_EVENT_DEVICE_ADDED for this + * device has been received. + * + * This does not increase the refcount of the device. Use ei_device_ref() to + * keep a reference beyond the immediate scope. + * + * @param ei The context + * @param capabilities A bitmask of @ref ei_device_capability + * @param name A name suggestion for the device + */ +struct ei_device * +ei_create_device(struct ei_ctx *ei, uint32_t capabilities, const char *name); + +/** + * Request that the device be added to the server. + * The server will respond with an @ref EI_EVENT_DEVICE_ADDED or @ref + * EI_EVENT_DEVICE_REMOVED event. + */ +void +ei_device_add(struct ei_device *device); + +/** + * Notify the server that the device is no longer required. The server will + * reply with a @ref EI_EVENT_DEVICE_REMOVED event once the device has been + * removed. + * + * This does not release any resources associated with this device, use + * ei_device_unref() for any references held by the client. + */ +void +ei_device_remove(struct ei_device *device); + + +/** + * @return the name of the device (if any) or NULL + */ +const char * +ei_device_get_name(struct ei_device *device); + +bool +ei_device_has_capability(struct ei_device *device, + enum ei_device_capability cap); + +struct ei_ctx * +ei_device_get_context(struct ei_device *device); + +/** + * Generate a relative motion event on a device with + * the @ref EI_DEVICE_CAP_POINTER capability. + * + * @param x The x movement in 1/1000th of a logical pixel + * @param y The y movement in 1/1000th of a logical pixel + */ +void +ei_device_pointer_motion(struct ei_device *device, int32_t x, int32_t y); + +/** + * Generate an absolute motion event on a device with + * the @ref EI_DEVICE_CAP_POINTER_ABSOLUTE capability. + * + * The required conditions are: + * - 0 <= x < width + * - 0 <= y < height + * + * @param x The x position in 1/1000th of a logical pixel + * @param y The y position in 1/1000th of a logical pixel + */ +void +ei_device_pointer_motion_absolute(struct ei_device *device, + uint32_t x, uint32_t y); + +/** + * Generate a button event on a device with + * the @ref EI_DEVICE_CAP_POINTER_ABSOLUTE or + * @ref EI_DEVICE_CAP_POINTER capability. + * + * Buttons are merely sequentially numbered. It is up to the server to map + * button numbers into semantic button codes (if any). A server may silently + * ignore button events outside meaningful ranges. + * + * @param button A sequentially numbered button, starting at index 1 + * @param is_press true for button press, false for button release + */ +void +ei_device_pointer_button(struct ei_device *device, + uint32_t button, bool is_press); + +/** + * Generate a scroll event on a device with + * the @ref EI_DEVICE_CAP_POINTER_ABSOLUTE or + * @ref EI_DEVICE_CAP_POINTER capability. + * + * The server emulates discrete scrolling based on the pixel value, + * do not call ei_device_pointer_scroll_discrete() for the + * same input event. + * + * @param x The x scroll distance in 1/1000th of a logical pixel + * @param y The y scroll distance in 1/1000th of a logical pixel + * + * @see ei_device_pointer_scroll_discrete + */ +void +ei_device_pointer_scroll(struct ei_device *device, int32_t x, int32_t y); + +/** + * Generate a discrete scroll event on a device with + * the @ref EI_DEVICE_CAP_POINTER_ABSOLUTE or + * @ref EI_DEVICE_CAP_POINTER capability. + * + * A discrete scroll event is based logical scroll units (equivalent to one + * mouse wheel click). The value for one scroll unit is 120, a fraction or + * multiple thereof represents a fraction or multiple of a wheel click. + * + * The server emulates pixel-based scrolling based on the discrete value, + * do not call ei_device_pointer_scroll() for the + * same input event. + * + * @param x The x scroll distance in fractions or multiples of 120 + * @param y The y scroll distance in fractions or multiples of 120 + * + * @see ei_device_pointer_scroll + */ +void +ei_device_pointer_scroll_discrete(struct ei_device *device, int32_t x, int32_t y); + +/** + * Generate a key event on a device with + * the @ref EI_DEVICE_CAP_KEYBOARD capability. + * + * Keys use the evdev scan codes as defined in + * linux/input-event-codes.h + * + * @param keycode The key code + * @param is_press true for key down, false for key up + */ +void +ei_device_keyboard_key(struct ei_device *device, uint32_t keycode, bool is_press); + + +/** + * Set the keymap on the device. This function has no effect if called + * after ei_device_add(). The keymap for this device is a suggestion to + * the server, the actual keymap used by this device is provided with + * the @ref EI_EVENT_KEYBOARD_KEYMAP event. + * + * @param type the type of the keymap + * @param fd a memmap-able file descriptor to the keymap + */ +void +ei_device_keyboard_set_keymap(struct ei_device *device, + enum ei_keymap_type type, + int fd); + +/** + * Return the device from this event. + * + * This does not increase the refcount of the device. Use eis_device_ref() + * to keep a reference beyond the immediate scope. + */ +struct ei_device * +ei_event_get_device(struct ei_event *event); + + +/** + * @return the event time in microseconds + */ +uint64_t +ei_event_pointer_get_time(struct ei_event_pointer *event); + +/** + * For an event of type @ref EI_EVENT_POINTER_RANGES_CHANGED, this function + * defines the allowable range for x - 0 (inclusive) to width (exclusive). + * + * The value is in 1/1000th of logical pixels, i.e. the value 1000 000 + * refers to 1000 pixels. + * + * It is a client bug to send pointer values outside this range. + * + * If the touch range changes at runtime, an event of @ref + * EI_EVENT_POINTER_RANGES_CHANGED is generated. + */ +uint32_t +ei_event_pointer_get_width(struct ei_event_pointer *event); + +/** + * @see ei_event_pointer_get_width + */ +uint32_t +ei_event_pointer_get_height(struct ei_event_pointer *event); + +/** + * @return the event time in microseconds + */ +uint64_t +ei_event_keyboard_get_time(struct ei_event_keyboard *event); + +/** + * For an event of type @ref EI_EVENT_TOUCH_RANGES_CHANGED, this function + * defines the allowable range for x - 0 (inclusive) to width (exclusive). + * + * The value is in 1/1000th of logical pixels, i.e. the value 1000 000 + * refers to 1000 pixels. + * + * It is a client bug to send touch values outside this range. + * + * If the touch range changes at runtime, an event of @ref + * EI_EVENT_TOUCH_RANGES_CHANGED is generated. + */ +uint32_t +ei_event_touch_get_width(struct ei_event_touch *event); + +/** + * @see ei_event_touch_get_width + */ +uint32_t +ei_event_touch_get_height(struct ei_event_touch *event); + +/** + * @return the event time in microseconds + */ +uint64_t +ei_event_touch_get_time(struct ei_event_touch *event); + +/** + * For an event of type @ref EI_EVENT_KEYBOARD_KEYMAP return the + * type of the keyamp. + */ +enum ei_keymap_type +ei_event_keyboard_get_keymap_type(struct ei_event_keyboard *kbdev); + +/** + * For an event of type @ref EI_EVENT_KEYBOARD_KEYMAP return the + * memmap-able file descriptor to the keymap. + */ +int +ei_event_keyboard_get_keymap(struct ei_event_keyboard *kbdev); diff --git a/src/libeis.c b/src/libeis.c new file mode 100644 index 0000000..2eaae5f --- /dev/null +++ b/src/libeis.c @@ -0,0 +1,24 @@ +/* + * Copyright © 2020 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "libeis.h" diff --git a/src/libeis.h b/src/libeis.h new file mode 100644 index 0000000..75e2a35 --- /dev/null +++ b/src/libeis.h @@ -0,0 +1,392 @@ +/* + * Copyright © 2020 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + + +#pragma once + +#include +#include + +struct eis_ctx; +struct eis_client; +struct eis_device; +struct eis_event; +struct eis_event_client; +struct eis_event_pointer; +struct eis_event_keyboard; +struct eis_event_touch; + +enum eis_device_capability { + EIS_DEVICE_CAP_POINTER = (1 << 0), + EIS_DEVICE_CAP_POINTER_ABSOLUTE = (1 << 1), + EIS_DEVICE_CAP_KEYBOARD = (1 << 2), + EIS_DEVICE_CAP_TOUCH = (1 << 3), +}; + +enum eis_keymap_type { + EIS_KEYMAP_TYPE_XKB = 1, +}; + +enum eis_event_type { + /** + * No event ever has this type, this type exists only as potential + * return value for eis_next_event_type(). + */ + EIS_EVENT_NONE = 0, + /** + * A client has connected. This is the first event from any new + * client. + * The server is expected to either call eis_event_client_allow() or + * eis_event_client_deny(). + */ + EIS_EVENT_CLIENT_CONNECT, + /** + * The client has disconnected, any pending requests for this client + * should be discarded. + */ + EIS_EVENT_CLIENT_DISCONNECT, + + /** + * The client requests creation of a device with a given set of + * capabilities. A client may create more than one device and more + * than one device with the same capabilities. The server may filter + * the capabilities or deny them altogether. + * + */ + EIS_EVENT_DEVICE_ADDED, + /** + * The device created by the client was removed. + * + * libeis guarantees this event is generated before + * @ref EIS_EVENT_CLIENT_DISCONNECT. + */ + EIS_EVENT_DEVICE_REMOVED, + + /** + * The client requests monitoring of a capability. + */ + EIS_EVENT_REQUEST_CAPABILITY, + /** + * The client cancelled the request to monitor that capability. + */ + EIS_EVENT_CANCEL_CAPABILITY, + + EIS_EVENT_POINTER_MOTION = 300, + EIS_EVENT_POINTER_MOTION_ABSOLUTE, + EIS_EVENT_POINTER_BUTTON, + EIS_EVENT_POINTER_SCROLL, + EIS_EVENT_POINTER_SCROLL_DISCRETE, + + EIS_EVENT_KEYBOARD_KEY = 400, + EIS_EVENT_KEYBOARD_KEYMAP, + + EIS_EVENT_TOUCH_DOWN = 500, + EIS_EVENT_TOUCH_UP, + EIS_EVENT_TOUCH_MOTION, +}; + +/** + * Create a new libeis context with a refcount of 1. + */ +struct eis_ctx * +eis_new(void); + +struct eis_ctx * +eis_ref(struct eis_ctx *eis); + +struct eis_ctx * +eis_unref(struct eis_ctx *eis); + +/** + * Initialize the context with org.freedesktop.portal integration. + */ +int +eis_portal_init(struct eis_ctx *ctx); + +/** + * Initialize the context with a DBus backend on the org.freedesktop.Ei bus + * name. + */ +int +eis_dbus_init(struct eis_ctx *ctx); + +int +eis_get_fd(struct eis_ctx *eis); + +void +eis_dispatch(struct eis_ctx *eis); + +struct eis_event * +eis_get_event(struct eis_ctx *eis); + +enum eis_event_type +eis_next_event_type(struct eis_ctx *eis); + +struct eis_client * +eis_client_ref(struct eis_client *client); + +struct eis_client * +eis_client_unref(struct eis_client *client); + +/** + * Allow connection from the client. This can only be done once, further + * calls to this functions are ignored. + * + * When receiving an event of type @ref EIS_EVENT_CLIENT_CONNECT, the server + * should connect client as soon as possible to allow communication with the + * server. If the client is not authorized to talk to the server, call + * eis_client_disconnect(). + */ +void +eis_client_connect(struct eis_client *client); + +/** + * Disconnect this client. Once disconnected the client may no longer talk + * to this context, all resources associated with this client should be + * released. + * + * It is not necessary to call this function when an @ref + * EIS_EVENT_CLIENT_DISCONNECT event is received. + */ +void +eis_client_disconnect(struct eis_client *client); + +struct eis_client * +eis_event_get_client(struct eis_event *event); + + +struct eis_device * +eis_device_ref(struct eis_device *device); + +struct eis_device * +eis_device_unref(struct eis_device *device); + +const char * +eis_device_get_name(struct eis_device *device); + +/** + * Set the name of the device. This function has no effect if called after + * eis_device_connect() + */ +const char * +eis_device_set_name(struct eis_device *device, const char *name); + +uint32_t +eis_device_get_capabilities(struct eis_device *device); + +/** + * Set the capabilities of the device. This function has no effect if called after + * eis_device_connect() + */ +void +eis_device_set_capabilities(struct eis_device *device, uint32_t caps); + +/** + * Connects the device. + * + * This function should be called in response to an @ref + * EIS_EVENT_DEVICE_ADDED if the server accepts the device creation. + * Any changes to the device, e.g. eis_device_set_name() and + * eis_device_disable_capability() must be performed before connecting the + * device. + * + * Calling eis_device_connect() on a device with all capabilities set to + * zero is a bug. + * + * If the device is rejected, call eis_device_disconnect() instead. + * + * The device's capabilities are suspended, use + * eis_device_resume_capability() to enable events from the client. + */ +void +eis_device_connect(struct eis_device *device); + +/** + * Disconnect the device. + * This does not release any resources associated with this device, use + * eils_device_unref() for any references held by the caller. + */ +void +eis_device_disconnect(struct eis_device *device); + +/** + * Notify the client that the capabilities are suspended and that no events + * from the client for this capability will be processed. + * + * @param device A connected device + * @param cap A bitmask of the capabilities to suspend. + */ +void +eis_device_suspend_capability(struct eis_device *device, uint32_t cap); + +/** + * Notify the client that the capabilities are resumed and that events + * from the client for this capability will be processed. + * + * @param device A connected device + * @param cap A bitmask of the capabilities to resume. + */ +void +eis_device_resume_capability(struct eis_device *device, uint32_t cap); + +/** + * Notify the client of a new range for absolute pointer events. + * The range is 0 (inclusive) to width (exclusive). + * + * The value is in 1/1000th of logical pixels, i.e. the value 1000 000 + * refers to 1000 pixels. + * + * Due to inherent race conditions with changing the width of a device in an + * asynchronous communication channel, a server should alway suspend the + * pointer capability before changing the width. + * + * @param width The new width in 1/1000th of logical pixels + */ +void +eis_device_pointer_set_width(struct eis_device *device, uint32_t width); +/** + * @see eis_device_pointer_set_width + */ +void +eis_device_pointer_set_height(struct eis_device *device, uint32_t height); + +/** + * Set the keymap on the device. + * Due to inherent race conditions with changing the keymap on a device in + * an asynchronous communication channel, a server should alway suspend the + * keyboard capability before changing the keymap. + * + * @param type the type of the keymap + * @param fd a memmap-able file descriptor to the keymap + */ +void +eis_device_keyboard_set_keymap(struct eis_device *device, + enum eis_keymap_type type, + int fd); + +/** + * Return the device from this event. + * + * This does not increase the refcount of the device. Use eis_device_ref() + * to keep a reference beyond the immediate scope. + */ +struct eis_device * +eis_event_get_device(struct eis_event *event); + +/** + * For an event of type @ref EIS_EVENT_POINTER_MOTION return the x movement + * in 1/1000th of a logical pixel. + */ +int32_t +eis_event_pointer_get_x(struct eis_event_pointer *ptrev); + +/** + * For an event of type @ref EIS_EVENT_POINTER_MOTION return the y movement + * in 1/1000th of a logical pixel. + */ +int32_t +eis_event_pointer_get_y(struct eis_event_pointer *ptrev); + +/** + * For an event of type @ref EIS_EVENT_POINTER_MOTION_ABSOLUTE return the x + * position in 1/1000th of a logical pixel. + */ +uint32_t +eis_event_pointer_get_absolute_x(struct eis_event_pointer *ptrev); + +/** + * For an event of type @ref EIS_EVENT_POINTER_MOTION_ABSOLUTE return the y + * position in 1/1000th of a logical pixel. + */ +uint32_t +eis_event_pointer_get_absolute_y(struct eis_event_pointer *ptrev); + +/** + * For an event of type @ref EIS_EVENT_POINTER_BUTTON return the button + * index (starting at 1). + */ +uint32_t +eis_event_pointer_get_button(struct eis_event_pointer *ptrev); + +/** + * For an event of type @ref EIS_EVENT_POINTER_BUTTON return true if the + * event is a button press, false for a release. + */ +bool +eis_event_pointer_get_button_is_press(struct eis_event_pointer *ptrev); + +/** + * For an event of type @ref EIS_EVENT_POINTER_SCROLL return the x scroll + * distance in 1/1000th of a logical pixel. + */ +int32_t +eis_event_pointer_get_scroll_x(struct eis_event_pointer *ptrev); + +/** + * For an event of type @ref EIS_EVENT_POINTER_SCROLL return the y scroll + * distance in 1/1000th of a logical pixel. + */ +int32_t +eis_event_pointer_get_scroll_y(struct eis_event_pointer *ptrev); + +/** + * For an event of type @ref EIS_EVENT_POINTER_SCROLL_DISCRETE return the x + * scroll distance in fractions or multiples of 120. + */ +int32_t +eis_event_pointer_get_scroll_discrete_x(struct eis_event_pointer *ptrev); + +/** + * For an event of type @ref EIS_EVENT_POINTER_SCROLL_DISCRETE return the y + * scroll distance in fractions or multiples of 120. + */ +int32_t +eis_event_pointer_get_scroll_discrete_y(struct eis_event_pointer *ptrev); + +/** + * For an event of type @ref EIS_EVENT_KEYBOARD_KEY return the key code (as + * defined in include/linux/input-event-codes.h). + */ +uint32_t +eis_event_keyboard_get_key(struct eis_event_keyboard *kbdev); + +/** + * For an event of type @ref EIS_EVENT_KEYBOARD_KEY return true if the + * event is a key down, false for a release. + */ +bool +eis_event_keyboard_get_key_is_press(struct eis_event_keyboard *kbdev); + +/** + * For an event of type @ref EIS_EVENT_KEYBOARD_KEYMAP return the + * type of the keyamp. + */ +enum eis_keymap_type +eis_event_keyboard_get_keymap_type(struct eis_event_keyboard *kbdev); + +/** + * For an event of type @ref EIS_EVENT_KEYBOARD_KEYMAP return the + * memmap-able file descriptor to the keymap. + */ +int +eis_event_keyboard_get_keymap(struct eis_event_keyboard *kbdev);