fix(gamepad): build libevdev from a vendored copy

The Linux gamepad backend included <libevdev/libevdev.h> from
/usr/include/libevdev-1.0 and linked the system libevdev, but the
Flatpak SDK crafter-build runs in (org.freedesktop.Sdk 26.08) ships no
libevdev headers, so Crafter.Graphics stopped building there.
lib/libevdev now carries libevdev 1.14.0 (MIT), built once as its own
static library that Crafter.Graphics depends on. As part of cfg it would
be compiled again in every test configuration, since they copy
cfg.cFiles.

libevdev's generated event-names.h names every input code by its macro,
so the library compiles against the kernel input headers that table was
generated from (lib/libevdev/kernel-headers), not the target's. That
directory is deliberately not called include: Crafter.Build puts every
include directory of a dependency on its consumers' include path, where
those headers would shadow each consumer's own <linux/input.h>. The
paths are absolute because Crafter.Build only resolves the returned
configuration's paths against the project root.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jorijn van der Graaf 2026-10-19 03:03:16 +02:00
commit dfb70bc088
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB
14 changed files with 8515 additions and 5 deletions

View file

@ -129,7 +129,8 @@ shaded through an intersection shader with an any-hit cut-out.
per-vertex attribs) into global mesh heaps and builds the TLAS in a
library compute pass.
- **Clipboard / Input / Gamepad / Router / Dom** — input plumbing.
Gamepad uses libudev+libevdev on Linux and WGI on Windows; the DOM
Gamepad uses libudev+libevdev on Linux (libevdev is vendored in
`lib/libevdev`) and WGI on Windows; the DOM
backend exposes the host page DOM (`Dom::HtmlElement`) and a router
for hash-routed wasm apps.

32
lib/libevdev/COPYING Normal file
View file

@ -0,0 +1,32 @@
SPDX-License-Identifier: MIT
Copyright © 2013 Red Hat, Inc.
Copyright © 2013 David Herrmann <dh.herrmann@gmail.com>
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.
The following license is from a Linux kernel header file and there is no GPL
code this package links to.
Copyright (c) 1999-2002 Vojtech Pavlik
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License version 2 as published by
the Free Software Foundation.

30
lib/libevdev/README.md Normal file
View file

@ -0,0 +1,30 @@
# libevdev (vendored)
The Linux gamepad backend (`implementations/Crafter.Graphics-Gamepad.cpp`) parses evdev events with
[libevdev](https://gitlab.freedesktop.org/libevdev/libevdev). It is compiled into Crafter.Graphics from
this directory instead of being linked from the system. The Flatpak SDK that crafter-build runs in has no
libevdev headers, and a vendored copy builds the same way for every target, including cross sysroots.
- Upstream: `https://gitlab.freedesktop.org/libevdev/libevdev.git`, tag `libevdev-1.14.0`
(commit `d9af2e4580297e9c14715fef06621dd39d9d8a3a`). MIT, see `COPYING`.
- `libevdev/`: the core library sources, unchanged. The uinput half isn't used and isn't included.
- `libevdev/event-names.h`: generated, the way libevdev's meson build does it, from the kernel headers
below.
- `libevdev/config.h`: what libevdev's meson build writes on Linux (`_GNU_SOURCE`).
- `kernel-headers/linux/`: the kernel uapi `input.h` and `input-event-codes.h` that ship with that
libevdev release. libevdev compiles against its own copy (its meson build puts them first on the path),
because `event-names.h` names every code by its macro and has to see the same set of codes. Only the
libevdev library sees them, and the directory isn't called `include`, because Crafter.Build puts every
`include` directory of a dependency on its consumers' include path.
`project.cpp` builds these sources once, as their own static library, which Crafter.Graphics depends on.
## Updating
Copy `COPYING`, the files under `libevdev/` and upstream's `include/linux/linux/{input.h,input-event-codes.h}`
(into `kernel-headers/linux/`) from the new release, keep `config.h`, then regenerate the name table:
```sh
python3 libevdev/make-event-names.py kernel-headers/linux/input.h kernel-headers/linux/input-event-codes.h \
> libevdev/event-names.h
```

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,539 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
* Copyright (c) 1999-2002 Vojtech Pavlik
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published by
* the Free Software Foundation.
*/
#ifndef _UAPI_INPUT_H
#define _UAPI_INPUT_H
#ifndef __KERNEL__
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <linux/types.h>
#endif
#include "input-event-codes.h"
/*
* The event structure itself
* Note that __USE_TIME_BITS64 is defined by libc based on
* application's request to use 64 bit time_t.
*/
struct input_event {
#if (__BITS_PER_LONG != 32 || !defined(__USE_TIME_BITS64)) && !defined(__KERNEL__)
struct timeval time;
#define input_event_sec time.tv_sec
#define input_event_usec time.tv_usec
#else
__kernel_ulong_t __sec;
#if defined(__sparc__) && defined(__arch64__)
unsigned int __usec;
unsigned int __pad;
#else
__kernel_ulong_t __usec;
#endif
#define input_event_sec __sec
#define input_event_usec __usec
#endif
__u16 type;
__u16 code;
__s32 value;
};
/*
* Protocol version.
*/
#define EV_VERSION 0x010001
/*
* IOCTLs (0x00 - 0x7f)
*/
struct input_id {
__u16 bustype;
__u16 vendor;
__u16 product;
__u16 version;
};
/**
* struct input_absinfo - used by EVIOCGABS/EVIOCSABS ioctls
* @value: latest reported value for the axis.
* @minimum: specifies minimum value for the axis.
* @maximum: specifies maximum value for the axis.
* @fuzz: specifies fuzz value that is used to filter noise from
* the event stream.
* @flat: values that are within this value will be discarded by
* joydev interface and reported as 0 instead.
* @resolution: specifies resolution for the values reported for
* the axis.
*
* Note that input core does not clamp reported values to the
* [minimum, maximum] limits, such task is left to userspace.
*
* The default resolution for main axes (ABS_X, ABS_Y, ABS_Z,
* ABS_MT_POSITION_X, ABS_MT_POSITION_Y) is reported in units
* per millimeter (units/mm), resolution for rotational axes
* (ABS_RX, ABS_RY, ABS_RZ) is reported in units per radian.
* The resolution for the size axes (ABS_MT_TOUCH_MAJOR,
* ABS_MT_TOUCH_MINOR, ABS_MT_WIDTH_MAJOR, ABS_MT_WIDTH_MINOR)
* is reported in units per millimeter (units/mm).
* When INPUT_PROP_ACCELEROMETER is set the resolution changes.
* The main axes (ABS_X, ABS_Y, ABS_Z) are then reported in
* units per g (units/g) and in units per degree per second
* (units/deg/s) for rotational axes (ABS_RX, ABS_RY, ABS_RZ).
*/
struct input_absinfo {
__s32 value;
__s32 minimum;
__s32 maximum;
__s32 fuzz;
__s32 flat;
__s32 resolution;
};
/**
* struct input_keymap_entry - used by EVIOCGKEYCODE/EVIOCSKEYCODE ioctls
* @scancode: scancode represented in machine-endian form.
* @len: length of the scancode that resides in @scancode buffer.
* @index: index in the keymap, may be used instead of scancode
* @flags: allows to specify how kernel should handle the request. For
* example, setting INPUT_KEYMAP_BY_INDEX flag indicates that kernel
* should perform lookup in keymap by @index instead of @scancode
* @keycode: key code assigned to this scancode
*
* The structure is used to retrieve and modify keymap data. Users have
* option of performing lookup either by @scancode itself or by @index
* in keymap entry. EVIOCGKEYCODE will also return scancode or index
* (depending on which element was used to perform lookup).
*/
struct input_keymap_entry {
#define INPUT_KEYMAP_BY_INDEX (1 << 0)
__u8 flags;
__u8 len;
__u16 index;
__u32 keycode;
__u8 scancode[32];
};
struct input_mask {
__u32 type;
__u32 codes_size;
__u64 codes_ptr;
};
#define EVIOCGVERSION _IOR('E', 0x01, int) /* get driver version */
#define EVIOCGID _IOR('E', 0x02, struct input_id) /* get device ID */
#define EVIOCGREP _IOR('E', 0x03, unsigned int[2]) /* get repeat settings */
#define EVIOCSREP _IOW('E', 0x03, unsigned int[2]) /* set repeat settings */
#define EVIOCGKEYCODE _IOR('E', 0x04, unsigned int[2]) /* get keycode */
#define EVIOCGKEYCODE_V2 _IOR('E', 0x04, struct input_keymap_entry)
#define EVIOCSKEYCODE _IOW('E', 0x04, unsigned int[2]) /* set keycode */
#define EVIOCSKEYCODE_V2 _IOW('E', 0x04, struct input_keymap_entry)
#define EVIOCGNAME(len) _IOC(_IOC_READ, 'E', 0x06, len) /* get device name */
#define EVIOCGPHYS(len) _IOC(_IOC_READ, 'E', 0x07, len) /* get physical location */
#define EVIOCGUNIQ(len) _IOC(_IOC_READ, 'E', 0x08, len) /* get unique identifier */
#define EVIOCGPROP(len) _IOC(_IOC_READ, 'E', 0x09, len) /* get device properties */
/**
* EVIOCGMTSLOTS(len) - get MT slot values
* @len: size of the data buffer in bytes
*
* The ioctl buffer argument should be binary equivalent to
*
* struct input_mt_request_layout {
* __u32 code;
* __s32 values[num_slots];
* };
*
* where num_slots is the (arbitrary) number of MT slots to extract.
*
* The ioctl size argument (len) is the size of the buffer, which
* should satisfy len = (num_slots + 1) * sizeof(__s32). If len is
* too small to fit all available slots, the first num_slots are
* returned.
*
* Before the call, code is set to the wanted ABS_MT event type. On
* return, values[] is filled with the slot values for the specified
* ABS_MT code.
*
* If the request code is not an ABS_MT value, -EINVAL is returned.
*/
#define EVIOCGMTSLOTS(len) _IOC(_IOC_READ, 'E', 0x0a, len)
#define EVIOCGKEY(len) _IOC(_IOC_READ, 'E', 0x18, len) /* get global key state */
#define EVIOCGLED(len) _IOC(_IOC_READ, 'E', 0x19, len) /* get all LEDs */
#define EVIOCGSND(len) _IOC(_IOC_READ, 'E', 0x1a, len) /* get all sounds status */
#define EVIOCGSW(len) _IOC(_IOC_READ, 'E', 0x1b, len) /* get all switch states */
#define EVIOCGBIT(ev,len) _IOC(_IOC_READ, 'E', 0x20 + (ev), len) /* get event bits */
#define EVIOCGABS(abs) _IOR('E', 0x40 + (abs), struct input_absinfo) /* get abs value/limits */
#define EVIOCSABS(abs) _IOW('E', 0xc0 + (abs), struct input_absinfo) /* set abs value/limits */
#define EVIOCSFF _IOW('E', 0x80, struct ff_effect) /* send a force effect to a force feedback device */
#define EVIOCRMFF _IOW('E', 0x81, int) /* Erase a force effect */
#define EVIOCGEFFECTS _IOR('E', 0x84, int) /* Report number of effects playable at the same time */
#define EVIOCGRAB _IOW('E', 0x90, int) /* Grab/Release device */
#define EVIOCREVOKE _IOW('E', 0x91, int) /* Revoke device access */
/**
* EVIOCGMASK - Retrieve current event mask
*
* This ioctl allows user to retrieve the current event mask for specific
* event type. The argument must be of type "struct input_mask" and
* specifies the event type to query, the address of the receive buffer and
* the size of the receive buffer.
*
* The event mask is a per-client mask that specifies which events are
* forwarded to the client. Each event code is represented by a single bit
* in the event mask. If the bit is set, the event is passed to the client
* normally. Otherwise, the event is filtered and will never be queued on
* the client's receive buffer.
*
* Event masks do not affect global state of the input device. They only
* affect the file descriptor they are applied to.
*
* The default event mask for a client has all bits set, i.e. all events
* are forwarded to the client. If the kernel is queried for an unknown
* event type or if the receive buffer is larger than the number of
* event codes known to the kernel, the kernel returns all zeroes for those
* codes.
*
* At maximum, codes_size bytes are copied.
*
* This ioctl may fail with ENODEV in case the file is revoked, EFAULT
* if the receive-buffer points to invalid memory, or EINVAL if the kernel
* does not implement the ioctl.
*/
#define EVIOCGMASK _IOR('E', 0x92, struct input_mask) /* Get event-masks */
/**
* EVIOCSMASK - Set event mask
*
* This ioctl is the counterpart to EVIOCGMASK. Instead of receiving the
* current event mask, this changes the client's event mask for a specific
* type. See EVIOCGMASK for a description of event-masks and the
* argument-type.
*
* This ioctl provides full forward compatibility. If the passed event type
* is unknown to the kernel, or if the number of event codes specified in
* the mask is bigger than what is known to the kernel, the ioctl is still
* accepted and applied. However, any unknown codes are left untouched and
* stay cleared. That means, the kernel always filters unknown codes
* regardless of what the client requests. If the new mask doesn't cover
* all known event-codes, all remaining codes are automatically cleared and
* thus filtered.
*
* This ioctl may fail with ENODEV in case the file is revoked. EFAULT is
* returned if the receive-buffer points to invalid memory. EINVAL is returned
* if the kernel does not implement the ioctl.
*/
#define EVIOCSMASK _IOW('E', 0x93, struct input_mask) /* Set event-masks */
#define EVIOCSCLOCKID _IOW('E', 0xa0, int) /* Set clockid to be used for timestamps */
/*
* IDs.
*/
#define ID_BUS 0
#define ID_VENDOR 1
#define ID_PRODUCT 2
#define ID_VERSION 3
#define BUS_PCI 0x01
#define BUS_ISAPNP 0x02
#define BUS_USB 0x03
#define BUS_HIL 0x04
#define BUS_BLUETOOTH 0x05
#define BUS_VIRTUAL 0x06
#define BUS_ISA 0x10
#define BUS_I8042 0x11
#define BUS_XTKBD 0x12
#define BUS_RS232 0x13
#define BUS_GAMEPORT 0x14
#define BUS_PARPORT 0x15
#define BUS_AMIGA 0x16
#define BUS_ADB 0x17
#define BUS_I2C 0x18
#define BUS_HOST 0x19
#define BUS_GSC 0x1A
#define BUS_ATARI 0x1B
#define BUS_SPI 0x1C
#define BUS_RMI 0x1D
#define BUS_CEC 0x1E
#define BUS_INTEL_ISHTP 0x1F
#define BUS_AMD_SFH 0x20
#define BUS_SDW 0x21
/*
* MT_TOOL types
*/
#define MT_TOOL_FINGER 0x00
#define MT_TOOL_PEN 0x01
#define MT_TOOL_PALM 0x02
#define MT_TOOL_DIAL 0x0a
#define MT_TOOL_MAX 0x0f
/*
* Values describing the status of a force-feedback effect
*/
#define FF_STATUS_STOPPED 0x00
#define FF_STATUS_PLAYING 0x01
#define FF_STATUS_MAX 0x01
/*
* Structures used in ioctls to upload effects to a device
* They are pieces of a bigger structure (called ff_effect)
*/
/*
* All duration values are expressed in ms. Values above 32767 ms (0x7fff)
* should not be used and have unspecified results.
*/
/**
* struct ff_replay - defines scheduling of the force-feedback effect
* @length: duration of the effect
* @delay: delay before effect should start playing
*/
struct ff_replay {
__u16 length;
__u16 delay;
};
/**
* struct ff_trigger - defines what triggers the force-feedback effect
* @button: number of the button triggering the effect
* @interval: controls how soon the effect can be re-triggered
*/
struct ff_trigger {
__u16 button;
__u16 interval;
};
/**
* struct ff_envelope - generic force-feedback effect envelope
* @attack_length: duration of the attack (ms)
* @attack_level: level at the beginning of the attack
* @fade_length: duration of fade (ms)
* @fade_level: level at the end of fade
*
* The @attack_level and @fade_level are absolute values; when applying
* envelope force-feedback core will convert to positive/negative
* value based on polarity of the default level of the effect.
* Valid range for the attack and fade levels is 0x0000 - 0x7fff
*/
struct ff_envelope {
__u16 attack_length;
__u16 attack_level;
__u16 fade_length;
__u16 fade_level;
};
/**
* struct ff_constant_effect - defines parameters of a constant force-feedback effect
* @level: strength of the effect; may be negative
* @envelope: envelope data
*/
struct ff_constant_effect {
__s16 level;
struct ff_envelope envelope;
};
/**
* struct ff_ramp_effect - defines parameters of a ramp force-feedback effect
* @start_level: beginning strength of the effect; may be negative
* @end_level: final strength of the effect; may be negative
* @envelope: envelope data
*/
struct ff_ramp_effect {
__s16 start_level;
__s16 end_level;
struct ff_envelope envelope;
};
/**
* struct ff_condition_effect - defines a spring or friction force-feedback effect
* @right_saturation: maximum level when joystick moved all way to the right
* @left_saturation: same for the left side
* @right_coeff: controls how fast the force grows when the joystick moves
* to the right
* @left_coeff: same for the left side
* @deadband: size of the dead zone, where no force is produced
* @center: position of the dead zone
*/
struct ff_condition_effect {
__u16 right_saturation;
__u16 left_saturation;
__s16 right_coeff;
__s16 left_coeff;
__u16 deadband;
__s16 center;
};
/**
* struct ff_periodic_effect - defines parameters of a periodic force-feedback effect
* @waveform: kind of the effect (wave)
* @period: period of the wave (ms)
* @magnitude: peak value
* @offset: mean value of the wave (roughly)
* @phase: 'horizontal' shift
* @envelope: envelope data
* @custom_len: number of samples (FF_CUSTOM only)
* @custom_data: buffer of samples (FF_CUSTOM only)
*
* Known waveforms - FF_SQUARE, FF_TRIANGLE, FF_SINE, FF_SAW_UP,
* FF_SAW_DOWN, FF_CUSTOM. The exact syntax FF_CUSTOM is undefined
* for the time being as no driver supports it yet.
*
* Note: the data pointed by custom_data is copied by the driver.
* You can therefore dispose of the memory after the upload/update.
*/
struct ff_periodic_effect {
__u16 waveform;
__u16 period;
__s16 magnitude;
__s16 offset;
__u16 phase;
struct ff_envelope envelope;
__u32 custom_len;
__s16 *custom_data;
};
/**
* struct ff_rumble_effect - defines parameters of a periodic force-feedback effect
* @strong_magnitude: magnitude of the heavy motor
* @weak_magnitude: magnitude of the light one
*
* Some rumble pads have two motors of different weight. Strong_magnitude
* represents the magnitude of the vibration generated by the heavy one.
*/
struct ff_rumble_effect {
__u16 strong_magnitude;
__u16 weak_magnitude;
};
/**
* struct ff_haptic_effect
* @hid_usage: hid_usage according to Haptics page (WAVEFORM_CLICK, etc.)
* @vendor_id: the waveform vendor ID if hid_usage is in the vendor-defined range
* @vendor_waveform_page: the vendor waveform page if hid_usage is in the vendor-defined range
* @intensity: strength of the effect as percentage
* @repeat_count: number of times to retrigger effect
* @retrigger_period: time before effect is retriggered (in ms)
*/
struct ff_haptic_effect {
__u16 hid_usage;
__u16 vendor_id;
__u8 vendor_waveform_page;
__u16 intensity;
__u16 repeat_count;
__u16 retrigger_period;
};
/**
* struct ff_effect - defines force feedback effect
* @type: type of the effect (FF_CONSTANT, FF_PERIODIC, FF_RAMP, FF_SPRING,
* FF_FRICTION, FF_DAMPER, FF_RUMBLE, FF_INERTIA, or FF_CUSTOM)
* @id: an unique id assigned to an effect
* @direction: direction of the effect
* @trigger: trigger conditions (struct ff_trigger)
* @replay: scheduling of the effect (struct ff_replay)
* @u: effect-specific structure (one of ff_constant_effect, ff_ramp_effect,
* ff_periodic_effect, ff_condition_effect, ff_rumble_effect) further
* defining effect parameters
*
* This structure is sent through ioctl from the application to the driver.
* To create a new effect application should set its @id to -1; the kernel
* will return assigned @id which can later be used to update or delete
* this effect.
*
* Direction of the effect is encoded as follows:
* 0 deg -> 0x0000 (down)
* 90 deg -> 0x4000 (left)
* 180 deg -> 0x8000 (up)
* 270 deg -> 0xC000 (right)
*/
struct ff_effect {
__u16 type;
__s16 id;
__u16 direction;
struct ff_trigger trigger;
struct ff_replay replay;
union {
struct ff_constant_effect constant;
struct ff_ramp_effect ramp;
struct ff_periodic_effect periodic;
struct ff_condition_effect condition[2]; /* One for each axis */
struct ff_rumble_effect rumble;
struct ff_haptic_effect haptic;
} u;
};
/*
* Force feedback effect types
*/
#define FF_HAPTIC 0x4f
#define FF_RUMBLE 0x50
#define FF_PERIODIC 0x51
#define FF_CONSTANT 0x52
#define FF_SPRING 0x53
#define FF_FRICTION 0x54
#define FF_DAMPER 0x55
#define FF_INERTIA 0x56
#define FF_RAMP 0x57
#define FF_EFFECT_MIN FF_HAPTIC
#define FF_EFFECT_MAX FF_RAMP
/*
* Force feedback periodic effect types
*/
#define FF_SQUARE 0x58
#define FF_TRIANGLE 0x59
#define FF_SINE 0x5a
#define FF_SAW_UP 0x5b
#define FF_SAW_DOWN 0x5c
#define FF_CUSTOM 0x5d
#define FF_WAVEFORM_MIN FF_SQUARE
#define FF_WAVEFORM_MAX FF_CUSTOM
/*
* Set ff device properties
*/
#define FF_GAIN 0x60
#define FF_AUTOCENTER 0x61
/*
* ff->playback(effect_id = FF_GAIN) is the first effect_id to
* cause a collision with another ff method, in this case ff->set_gain().
* Therefore the greatest safe value for effect_id is FF_GAIN - 1,
* and thus the total number of effects should never exceed FF_GAIN.
*/
#define FF_MAX_EFFECTS FF_GAIN
#define FF_MAX 0x7f
#define FF_CNT (FF_MAX+1)
#endif /* _UAPI_INPUT_H */

View file

@ -0,0 +1,3 @@
/* SPDX-License-Identifier: MIT */
/* What libevdev's meson.build writes into config.h on Linux. */
#define _GNU_SOURCE 1

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,333 @@
// SPDX-License-Identifier: MIT
/*
* Copyright © 2013 Red Hat, Inc.
*/
#ifndef LIBEVDEV_INT_H
#define LIBEVDEV_INT_H
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <errno.h>
#include "libevdev.h"
#include "libevdev-util.h"
#define MAX_NAME 256
#define ABS_MT_MIN ABS_MT_SLOT
#define ABS_MT_MAX ABS_MT_TOOL_Y
#define ABS_MT_CNT (ABS_MT_MAX - ABS_MT_MIN + 1)
#define LIBEVDEV_EXPORT __attribute__((visibility("default")))
#define ALIAS(_to) __attribute__((alias(#_to)))
/**
* Sync state machine:
* default state: SYNC_NONE
*
* SYNC_NONE → SYN_DROPPED or forced sync → SYNC_NEEDED
* SYNC_NEEDED → libevdev_next_event(LIBEVDEV_READ_FLAG_SYNC) → SYNC_IN_PROGRESS
* SYNC_NEEDED → libevdev_next_event(LIBEVDEV_READ_FLAG_SYNC_NONE) → SYNC_NONE
* SYNC_IN_PROGRESS → libevdev_next_event(LIBEVDEV_READ_FLAG_SYNC_NONE) → SYNC_NONE
* SYNC_IN_PROGRESS → no sync events left → SYNC_NONE
*
*/
enum SyncState {
SYNC_NONE,
SYNC_NEEDED,
SYNC_IN_PROGRESS,
};
/**
* Internal only: log data used to send messages to the respective log
* handler. We re-use the same struct for a global and inside
* struct libevdev.
* For the global, device_handler is NULL, for per-device instance
* global_handler is NULL.
*/
struct logdata {
enum libevdev_log_priority priority; /** minimum logging priority */
libevdev_log_func_t global_handler; /** global handler function */
libevdev_device_log_func_t device_handler; /** per-device handler function */
void *userdata; /** user-defined data pointer */
};
struct libevdev {
int fd;
bool initialized;
char *name;
char *phys;
char *uniq;
struct input_id ids;
int driver_version;
unsigned long bits[NLONGS(EV_CNT)];
unsigned long props[NLONGS(INPUT_PROP_CNT)];
unsigned long key_bits[NLONGS(KEY_CNT)];
unsigned long rel_bits[NLONGS(REL_CNT)];
unsigned long abs_bits[NLONGS(ABS_CNT)];
unsigned long led_bits[NLONGS(LED_CNT)];
unsigned long msc_bits[NLONGS(MSC_CNT)];
unsigned long sw_bits[NLONGS(SW_CNT)];
unsigned long rep_bits[NLONGS(REP_CNT)]; /* convenience, always 1 */
unsigned long ff_bits[NLONGS(FF_CNT)];
unsigned long snd_bits[NLONGS(SND_CNT)];
unsigned long key_values[NLONGS(KEY_CNT)];
unsigned long led_values[NLONGS(LED_CNT)];
unsigned long sw_values[NLONGS(SW_CNT)];
struct input_absinfo abs_info[ABS_CNT];
int *mt_slot_vals; /* [num_slots * ABS_MT_CNT] */
int num_slots; /**< valid slots in mt_slot_vals */
int current_slot;
int rep_values[REP_CNT];
enum SyncState sync_state;
enum libevdev_grab_mode grabbed;
struct input_event *queue;
size_t queue_size; /**< size of queue in elements */
size_t queue_next; /**< next event index */
size_t queue_nsync; /**< number of sync events */
struct timeval last_event_time;
struct logdata log;
};
#define log_msg_cond(dev, priority, ...) \
do { \
if (_libevdev_log_priority(dev) >= priority) \
_libevdev_log_msg(dev, priority, __FILE__, __LINE__, __func__, __VA_ARGS__); \
} while(0)
#define log_error(dev, ...) log_msg_cond(dev, LIBEVDEV_LOG_ERROR, __VA_ARGS__)
#define log_info(dev, ...) log_msg_cond(dev, LIBEVDEV_LOG_INFO, __VA_ARGS__)
#define log_dbg(dev, ...) log_msg_cond(dev, LIBEVDEV_LOG_DEBUG, __VA_ARGS__)
#define log_bug(dev, ...) log_msg_cond(dev, LIBEVDEV_LOG_ERROR, "BUG: "__VA_ARGS__)
extern void
_libevdev_log_msg(const struct libevdev *dev,
enum libevdev_log_priority priority,
const char *file, int line, const char *func,
const char *format, ...) LIBEVDEV_ATTRIBUTE_PRINTF(6, 7);
extern enum libevdev_log_priority
_libevdev_log_priority(const struct libevdev *dev);
static inline void
init_event(struct libevdev *dev, struct input_event *ev, int type, int code, int value)
{
ev->input_event_sec = dev->last_event_time.tv_sec;
ev->input_event_usec = dev->last_event_time.tv_usec;
ev->type = type;
ev->code = code;
ev->value = value;
}
/**
* @return a pointer to the next element in the queue, or NULL if the queue
* is full.
*/
static inline struct input_event*
queue_push(struct libevdev *dev)
{
if (dev->queue_next >= dev->queue_size)
return NULL;
return &dev->queue[dev->queue_next++];
}
static inline bool
queue_push_event(struct libevdev *dev, unsigned int type,
unsigned int code, int value)
{
struct input_event *ev = queue_push(dev);
if (ev)
init_event(dev, ev, type, code, value);
return ev != NULL;
}
/**
* Set ev to the last element in the queue, removing it from the queue.
*
* @return 0 on success, 1 if the queue is empty.
*/
static inline int
queue_pop(struct libevdev *dev, struct input_event *ev)
{
if (dev->queue_next == 0)
return 1;
*ev = dev->queue[--dev->queue_next];
return 0;
}
static inline int
queue_peek(struct libevdev *dev, size_t idx, struct input_event *ev)
{
if (dev->queue_next == 0 || idx > dev->queue_next)
return 1;
*ev = dev->queue[idx];
return 0;
}
/**
* Shift the first n elements into ev and return the number of elements
* shifted.
* ev must be large enough to store n elements.
*
* @param ev The buffer to copy into, or NULL
* @return The number of elements in ev.
*/
static inline int
queue_shift_multiple(struct libevdev *dev, size_t n, struct input_event *ev)
{
size_t remaining;
if (dev->queue_next == 0)
return 0;
remaining = dev->queue_next;
n = min(n, remaining);
remaining -= n;
if (ev)
memcpy(ev, dev->queue, n * sizeof(*ev));
memmove(dev->queue, &dev->queue[n], remaining * sizeof(*dev->queue));
dev->queue_next = remaining;
return n;
}
/**
* Set ev to the first element in the queue, shifting everything else
* forward by one.
*
* @return 0 on success, 1 if the queue is empty.
*/
static inline int
queue_shift(struct libevdev *dev, struct input_event *ev)
{
return queue_shift_multiple(dev, 1, ev) == 1 ? 0 : 1;
}
static inline int
queue_alloc(struct libevdev *dev, size_t size)
{
if (size == 0)
return -ENOMEM;
dev->queue = calloc(size, sizeof(struct input_event));
if (!dev->queue)
return -ENOMEM;
dev->queue_size = size;
dev->queue_next = 0;
return 0;
}
static inline void
queue_free(struct libevdev *dev)
{
free(dev->queue);
dev->queue_size = 0;
dev->queue_next = 0;
}
static inline size_t
queue_num_elements(struct libevdev *dev)
{
return dev->queue_next;
}
static inline size_t
queue_size(struct libevdev *dev)
{
return dev->queue_size;
}
static inline size_t
queue_num_free_elements(struct libevdev *dev)
{
if (dev->queue_size == 0)
return 0;
return dev->queue_size - dev->queue_next;
}
static inline struct input_event *
queue_next_element(struct libevdev *dev)
{
if (dev->queue_next == dev->queue_size)
return NULL;
return &dev->queue[dev->queue_next];
}
static inline int
queue_set_num_elements(struct libevdev *dev, size_t nelem)
{
if (nelem > dev->queue_size)
return 1;
dev->queue_next = nelem;
return 0;
}
#define max_mask(uc, lc) \
case EV_##uc: \
*mask = dev->lc##_bits; \
max = libevdev_event_type_get_max(type); \
break;
static inline int
type_to_mask_const(const struct libevdev *dev, unsigned int type, const unsigned long **mask)
{
int max;
switch(type) {
max_mask(ABS, abs);
max_mask(REL, rel);
max_mask(KEY, key);
max_mask(LED, led);
max_mask(MSC, msc);
max_mask(SW, sw);
max_mask(FF, ff);
max_mask(REP, rep);
max_mask(SND, snd);
default:
max = -1;
break;
}
return max;
}
static inline int
type_to_mask(struct libevdev *dev, unsigned int type, unsigned long **mask)
{
int max;
switch(type) {
max_mask(ABS, abs);
max_mask(REL, rel);
max_mask(KEY, key);
max_mask(LED, led);
max_mask(MSC, msc);
max_mask(SW, sw);
max_mask(FF, ff);
max_mask(REP, rep);
max_mask(SND, snd);
default:
max = -1;
break;
}
return max;
}
#undef max_mask
#endif

View file

@ -0,0 +1,213 @@
// SPDX-License-Identifier: MIT
/*
* Copyright © 2013 David Herrmann <dh.herrmann@gmail.com>
*/
#include "config.h"
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include "libevdev-int.h"
#include "libevdev-util.h"
#include "libevdev.h"
#include "event-names.h"
struct name_lookup {
const char *name;
size_t len;
};
static int cmp_entry(const void *vlookup, const void *ventry)
{
const struct name_lookup *lookup = vlookup;
const struct name_entry *entry = ventry;
int r;
r = strncmp(lookup->name, entry->name, lookup->len);
if (!r) {
if (entry->name[lookup->len])
r = -1;
else
r = 0;
}
return r;
}
static const struct name_entry*
lookup_name(const struct name_entry *array, size_t asize,
struct name_lookup *lookup)
{
const struct name_entry *entry;
entry = bsearch(lookup, array, asize, sizeof(*array), cmp_entry);
if (!entry)
return NULL;
return entry;
}
LIBEVDEV_EXPORT int
libevdev_event_type_from_name(const char *name)
{
return libevdev_event_type_from_name_n(name, strlen(name));
}
LIBEVDEV_EXPORT int
libevdev_event_type_from_name_n(const char *name, size_t len)
{
struct name_lookup lookup;
const struct name_entry *entry;
lookup.name = name;
lookup.len = len;
entry = lookup_name(ev_names, ARRAY_LENGTH(ev_names), &lookup);
return entry ? (int)entry->value : -1;
}
static int type_from_prefix(const char *name, ssize_t len)
{
const char *e;
size_t i;
ssize_t l;
/* MAX_ is not allowed, even though EV_MAX exists */
if (startswith(name, len, "MAX_", 4))
return -1;
/* BTN_ is special as there is no EV_BTN type */
if (startswith(name, len, "BTN_", 4))
return EV_KEY;
/* FF_STATUS_ is special as FF_ is a prefix of it, so test it first */
if (startswith(name, len, "FF_STATUS_", 10))
return EV_FF_STATUS;
for (i = 0; i < ARRAY_LENGTH(ev_names); ++i) {
/* skip EV_ prefix so @e is suffix of [EV_]XYZ */
e = &ev_names[i].name[3];
l = strlen(e);
/* compare prefix and test for trailing _ */
if (len > l && startswith(name, len, e, l) && name[l] == '_')
return ev_names[i].value;
}
return -1;
}
LIBEVDEV_EXPORT int
libevdev_event_code_from_name(unsigned int type, const char *name)
{
return libevdev_event_code_from_name_n(type, name, strlen(name));
}
LIBEVDEV_EXPORT int
libevdev_event_code_from_name_n(unsigned int type, const char *name, size_t len)
{
struct name_lookup lookup;
const struct name_entry *entry;
int real_type;
/* verify that @name is really of type @type */
real_type = type_from_prefix(name, len);
if (real_type < 0 || (unsigned int)real_type != type)
return -1;
/* now look up the name @name and return the constant */
lookup.name = name;
lookup.len = len;
entry = lookup_name(code_names, ARRAY_LENGTH(code_names), &lookup);
return entry ? (int)entry->value : -1;
}
LIBEVDEV_EXPORT int
libevdev_event_value_from_name(unsigned int type, unsigned int code, const char *name)
{
return libevdev_event_value_from_name_n(type, code, name, strlen(name));
}
LIBEVDEV_EXPORT int
libevdev_event_value_from_name_n(unsigned int type, unsigned int code, const char *name, size_t len)
{
struct name_lookup lookup;
const struct name_entry *entry;
if (type != EV_ABS || code != ABS_MT_TOOL_TYPE)
return -1;
lookup.name = name;
lookup.len = len;
entry = lookup_name(tool_type_names, ARRAY_LENGTH(tool_type_names), &lookup);
return entry ? (int)entry->value : -1;
}
LIBEVDEV_EXPORT int
libevdev_property_from_name(const char *name)
{
return libevdev_property_from_name_n(name, strlen(name));
}
LIBEVDEV_EXPORT int
libevdev_property_from_name_n(const char *name, size_t len)
{
struct name_lookup lookup;
const struct name_entry *entry;
lookup.name = name;
lookup.len = len;
entry = lookup_name(prop_names, ARRAY_LENGTH(prop_names), &lookup);
return entry ? (int)entry->value : -1;
}
LIBEVDEV_EXPORT int
libevdev_event_code_from_code_name(const char *name)
{
return libevdev_event_code_from_code_name_n(name, strlen(name));
}
LIBEVDEV_EXPORT int
libevdev_event_code_from_code_name_n(const char *name, size_t len)
{
const struct name_entry *entry;
struct name_lookup lookup;
/* now look up the name @name and return the constant */
lookup.name = name;
lookup.len = len;
entry = lookup_name(code_names, ARRAY_LENGTH(code_names), &lookup);
return entry ? (int)entry->value : -1;
}
LIBEVDEV_EXPORT int
libevdev_event_type_from_code_name(const char *name)
{
return libevdev_event_type_from_code_name_n(name, strlen(name));
}
LIBEVDEV_EXPORT int
libevdev_event_type_from_code_name_n(const char *name, size_t len)
{
const struct name_entry *entry;
struct name_lookup lookup;
/* First look up if the name exists, we dont' want to return a valid
* type for an invalid code name */
lookup.name = name;
lookup.len = len;
entry = lookup_name(code_names, ARRAY_LENGTH(code_names), &lookup);
return entry ? type_from_prefix(name, len) : -1;
}

View file

@ -0,0 +1,69 @@
// SPDX-License-Identifier: MIT
/*
* Copyright © 2013 Red Hat, Inc.
*/
#ifndef _UTIL_H_
#define _UTIL_H_
#include "config.h"
#include <stdbool.h>
#include <string.h>
#define LONG_BITS (sizeof(long) * 8)
#define NLONGS(x) (((x) + LONG_BITS - 1) / LONG_BITS)
#define ARRAY_LENGTH(a) (sizeof(a) / (sizeof((a)[0])))
#define unlikely(x) (__builtin_expect(!!(x),0))
#undef min
#undef max
#ifdef __GNUC__
#define min(a,b) \
({ __typeof__ (a) _a = (a); \
__typeof__ (b) _b = (b); \
_a > _b ? _b : _a; \
})
#define max(a,b) \
({ __typeof__ (a) _a = (a); \
__typeof__ (b) _b = (b); \
_a > _b ? _a : _b; \
})
#else
#define min(a,b) ((a) > (b) ? (b) : (a))
#define max(a,b) ((a) > (b) ? (a) : (b))
#endif
static inline bool
startswith(const char *str, size_t len, const char *prefix, size_t plen)
{
return len >= plen && !strncmp(str, prefix, plen);
}
static inline int
bit_is_set(const unsigned long *array, int bit)
{
return !!(array[bit / LONG_BITS] & (1ULL << (bit % LONG_BITS)));
}
static inline void
set_bit(unsigned long *array, int bit)
{
array[bit / LONG_BITS] |= (1ULL << (bit % LONG_BITS));
}
static inline void
clear_bit(unsigned long *array, int bit)
{
array[bit / LONG_BITS] &= ~(1ULL << (bit % LONG_BITS));
}
static inline void
set_bit_state(unsigned long *array, int bit, int state)
{
if (state)
set_bit(array, bit);
else
clear_bit(array, bit);
}
#endif

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,235 @@
#!/usr/bin/env python3
#
# Parses linux/input.h scanning for #define KEY_FOO 134
# Prints C header files or Python files that can be used as
# mapping and lookup tables.
#
import re
import sys
class Bits(object):
def __init__(self):
self.max_codes = {}
prefixes = [
"EV_",
"REL_",
"ABS_",
"KEY_",
"BTN_",
"LED_",
"SND_",
"MSC_",
"SW_",
"FF_",
"SYN_",
"REP_",
"INPUT_PROP_",
"MT_TOOL_",
]
duplicates = [
"EV_VERSION",
"BTN_MISC",
"BTN_MOUSE",
"BTN_JOYSTICK",
"BTN_GAMEPAD",
"BTN_DIGI",
"BTN_WHEEL",
"BTN_TRIGGER_HAPPY",
"SW_MAX",
"REP_MAX",
]
ignore_prefixes = [
"SND_PROFILE_",
]
btn_additional = [
[0, "BTN_A"],
[0, "BTN_B"],
[0, "BTN_X"],
[0, "BTN_Y"],
]
code_prefixes = [
"REL_",
"ABS_",
"KEY_",
"BTN_",
"LED_",
"SND_",
"MSC_",
"SW_",
"FF_",
"SYN_",
"REP_",
]
def print_bits(bits, prefix):
if not hasattr(bits, prefix):
return
print("static const char * const %s_map[%s_MAX + 1] = {" % (prefix, prefix.upper()))
for val, name in sorted(list(getattr(bits, prefix).items())):
print(" [%s] = \"%s\"," % (name, name))
if prefix == "key":
for val, name in sorted(list(getattr(bits, "btn").items())):
print(" [%s] = \"%s\"," % (name, name))
print("};")
print("")
def print_map(bits):
print("static const char * const * const event_type_map[EV_MAX + 1] = {")
for prefix in prefixes:
if prefix in ["BTN_", "EV_", "INPUT_PROP_", "MT_TOOL_"]:
continue
print(" [EV_%s] = %s_map," % (prefix[:-1], prefix[:-1].lower()))
print("};")
print("")
print("#if __clang__")
print("#pragma clang diagnostic push")
print("#pragma clang diagnostic ignored \"-Winitializer-overrides\"")
print("#elif __GNUC__")
print("#pragma GCC diagnostic push")
print("#pragma GCC diagnostic ignored \"-Woverride-init\"")
print("#endif")
print("static const int ev_max[EV_MAX + 1] = {")
for val in range(bits.max_codes["EV_MAX"] + 1):
if val in bits.ev:
prefix = bits.ev[val][3:]
if prefix + "_" in prefixes:
print(" %s_MAX," % prefix)
continue
print(" -1,")
print("};")
print("#if __clang__")
print("#pragma clang diagnostic pop /* \"-Winitializer-overrides\" */")
print("#elif __GNUC__")
print("#pragma GCC diagnostic pop /* \"-Woverride-init\" */")
print("#endif")
print("")
def print_lookup(bits, prefix):
if not hasattr(bits, prefix):
return
names = sorted(list(getattr(bits, prefix).items()))
if prefix == "btn":
names = names + btn_additional
# We need to manually add the _MAX codes because some are
# duplicates
maxname = "%s_MAX" % (prefix.upper())
if maxname in duplicates:
names.append((bits.max_codes[maxname], maxname))
for val, name in sorted(names, key=lambda e: e[1]):
print(" { .name = \"%s\", .value = %s }," % (name, name))
def print_lookup_table(bits):
print("struct name_entry {")
print(" const char *name;")
print(" unsigned int value;")
print("};")
print("")
print("static const struct name_entry tool_type_names[] = {")
print_lookup(bits, "mt_tool")
print("};")
print("")
print("static const struct name_entry ev_names[] = {")
print_lookup(bits, "ev")
print("};")
print("")
print("static const struct name_entry code_names[] = {")
for prefix in sorted(code_prefixes, key=lambda e: e):
print_lookup(bits, prefix[:-1].lower())
print("};")
print("")
print("static const struct name_entry prop_names[] = {")
print_lookup(bits, "input_prop")
print("};")
print("")
def print_mapping_table(bits):
print("/* THIS FILE IS GENERATED, DO NOT EDIT */")
print("")
print("#ifndef EVENT_NAMES_H")
print("#define EVENT_NAMES_H")
print("")
for prefix in prefixes:
if prefix == "BTN_":
continue
print_bits(bits, prefix[:-1].lower())
print_map(bits)
print_lookup_table(bits)
print("#endif /* EVENT_NAMES_H */")
def parse_define(bits, line):
m = re.match(r"^#define\s+(\w+)\s+(\w+)", line)
if m is None:
return
name = m.group(1)
try:
value = int(m.group(2), 0)
except ValueError:
return
for prefix in prefixes:
if not name.startswith(prefix) or any(name.startswith(p) for p in ignore_prefixes):
continue
if name.endswith("_MAX"):
bits.max_codes[name] = value
if name in duplicates:
return
attrname = prefix[:-1].lower()
if not hasattr(bits, attrname):
setattr(bits, attrname, {})
b = getattr(bits, attrname)
b[value] = name
def parse(lines):
bits = Bits()
for line in lines:
if not line.startswith("#define"):
continue
parse_define(bits, line)
return bits
def usage(prog):
print("Usage: %s <files>".format(prog))
if __name__ == "__main__":
if len(sys.argv) <= 1:
usage(sys.argv[0])
sys.exit(2)
from itertools import chain
lines = chain(*[open(f).readlines() for f in sys.argv[1:]])
bits = parse(lines)
print_mapping_table(bits)

View file

@ -80,11 +80,42 @@ extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> a
cfg.linkFlags.push_back("-lwayland-client");
cfg.linkFlags.push_back("-lxkbcommon");
// Gamepad: libudev for hot-plug + device enumeration; libevdev
// for event parsing + axis calibration. libevdev ships its headers
// under a versioned dir (libevdev-1.0/) so the -I is mandatory.
// for event parsing + axis calibration.
cfg.linkFlags.push_back("-ludev");
cfg.linkFlags.push_back("-levdev");
cfg.compileFlags.push_back("-I/usr/include/libevdev-1.0");
// libevdev is vendored in lib/libevdev rather than linked from the
// system: the Flatpak SDK crafter-build runs in ships no libevdev
// headers, and a vendored copy cross-builds like everything else. It
// is its own static library so it compiles once, not again in every
// test configuration (they copy cfg.cFiles). One per call, since each
// distinct argument list loads this project separately; it lives for
// the whole build like a GitProject's configuration. Its paths are
// absolute because Crafter.Build only resolves the returned
// configuration's paths against this project's root, and this
// project.cpp runs with that root as cwd.
static std::deque<Configuration> libevdevConfigs;
Configuration& libevdev = libevdevConfigs.emplace_back();
libevdev.path = fs::absolute(cfg.path);
libevdev.name = "Crafter.Graphics-libevdev";
libevdev.outputName = "Crafter.Graphics-libevdev";
libevdev.type = ConfigurationType::LibraryStatic;
libevdev.target = cfg.target;
libevdev.march = cfg.march;
libevdev.mtune = cfg.mtune;
libevdev.debug = cfg.debug;
libevdev.sysroot = cfg.sysroot;
libevdev.cFiles = {
fs::absolute("lib/libevdev/libevdev/libevdev"),
fs::absolute("lib/libevdev/libevdev/libevdev-names"),
};
// Its generated event-names.h names every input code by macro, so it
// compiles against the kernel headers that table was generated from
// rather than the target's.
libevdev.compileFlags.push_back(std::format("-I{}", fs::absolute("lib/libevdev/kernel-headers").string()));
cfg.dependencies.push_back(&libevdev);
// For Gamepad's #include <libevdev/libevdev.h>.
cfg.compileFlags.push_back(std::format("-I{}", fs::absolute("lib/libevdev").string()));
cfg.cFiles.push_back("lib/xdg-shell-protocol");
cfg.cFiles.push_back("lib/wayland-xdg-decoration-unstable-v1-client-protocol");
cfg.cFiles.push_back("lib/fractional-scale-v1");