mirror of https://github.com/nirenjan/libx52.git
Compare commits
36 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
6ec133488b | |
|
|
667e8e2a7b | |
|
|
357ea96676 | |
|
|
6a36fc7764 | |
|
|
283b476c5e | |
|
|
75e6f253c9 | |
|
|
230951a232 | |
|
|
c99f775b70 | |
|
|
8914184613 | |
|
|
fdafda1d34 | |
|
|
9501813c36 | |
|
|
aa555f5e66 | |
|
|
842e7e53ed | |
|
|
dfbe3e6d21 | |
|
|
732bc21b65 | |
|
|
3c1abd57d5 | |
|
|
b0b457d14e | |
|
|
ae077dbed8 | |
|
|
d29be6213f | |
|
|
273ed22f8e | |
|
|
cdb00739ca | |
|
|
08a6b0a736 | |
|
|
45d561e0d8 | |
|
|
b626a9367f | |
|
|
a1098bc134 | |
|
|
569902be76 | |
|
|
0cb137bbe0 | |
|
|
899ea57bf7 | |
|
|
e1e020a4f5 | |
|
|
7cbf091dc7 | |
|
|
9d180531b9 | |
|
|
74229b391d | |
|
|
5f8177f16b | |
|
|
c5ec15231f | |
|
|
33bbafe970 | |
|
|
e9a806a6a2 |
|
|
@ -2,10 +2,20 @@
|
|||
# Run the build and tests
|
||||
set -e
|
||||
|
||||
meson setup -Dprefix=/usr -Dsysconfdir=/etc -Dlocalstatedir=/var -Dnls=enabled build
|
||||
cd build
|
||||
ninja
|
||||
ninja test
|
||||
BUILDDIR="${1:-build}"
|
||||
|
||||
rm -rf "$BUILDDIR"
|
||||
|
||||
# Handle the meson dist failure in CI
|
||||
if [[ "$GITHUB_ACTIONS" == "true" ]]
|
||||
then
|
||||
git config --global --add safe.directory '*'
|
||||
fi
|
||||
|
||||
meson setup -Dprefix=/usr -Dsysconfdir=/etc -Dlocalstatedir=/var -Dnls=enabled "$BUILDDIR"
|
||||
cd "$BUILDDIR"
|
||||
meson compile
|
||||
meson test
|
||||
meson dist
|
||||
|
||||
# Print bugreport output
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Generate the list of distros as a build matrix"""
|
||||
|
||||
import pathlib
|
||||
import json
|
||||
import sys
|
||||
|
||||
class BuildMatrix:
|
||||
"""Generate a build matrix for Github actions"""
|
||||
|
||||
COMPILER = 'gcc'
|
||||
OS = 'ubuntu-latest'
|
||||
EXPERIMENTAL = False
|
||||
|
||||
def __init__(self, image_prefix):
|
||||
self.matrix = []
|
||||
self.image_prefix = image_prefix
|
||||
self.get_distros()
|
||||
self.add_extra_builds()
|
||||
self.generate_output()
|
||||
|
||||
def build_matrix_obj(self, distro, experimental, os=None, compiler=None, image=None):
|
||||
"""Build the matrix object for the given distro"""
|
||||
matrix_obj = {
|
||||
'distro': distro,
|
||||
'experimental': experimental,
|
||||
'os': os or self.OS,
|
||||
'compiler': compiler or self.COMPILER,
|
||||
}
|
||||
|
||||
if image is None:
|
||||
image = f"{self.image_prefix}/ci-build-{distro}:latest"
|
||||
|
||||
matrix_obj['image'] = image
|
||||
|
||||
return matrix_obj
|
||||
|
||||
def get_distros(self):
|
||||
"""Get the list of distros from the Dockerfiles"""
|
||||
for dockerfile in pathlib.Path('.').glob('docker/Dockerfile.*'):
|
||||
distro = dockerfile.suffix[1:]
|
||||
|
||||
with open(dockerfile, encoding='utf-8') as dfd:
|
||||
experimental = 'experimental="true"' in dfd.read()
|
||||
|
||||
self.matrix.append(self.build_matrix_obj(distro, experimental))
|
||||
|
||||
def add_extra_builds(self):
|
||||
"""Add manual canary builds that don't have a corresponding dockerfile"""
|
||||
canary_build = self.build_matrix_obj('ubuntu22', False, compiler='clang')
|
||||
self.matrix.append(canary_build)
|
||||
|
||||
macos_build = self.build_matrix_obj('macos', True, os='macos-latest',
|
||||
compiler='clang', image='')
|
||||
self.matrix.append(macos_build)
|
||||
|
||||
def generate_output(self):
|
||||
"""Generate the output for github actions"""
|
||||
matrix_data = json.dumps(self.matrix)
|
||||
print(f"matrix={matrix_data}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
BuildMatrix(sys.argv[1])
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
#!/bin/bash
|
||||
# Get the list of changed Dockerfiles
|
||||
# Usage: get-changed-dockerfiles.sh <before-SHA> <head-SHA>
|
||||
|
||||
set -euxo pipefail
|
||||
|
||||
mapfile -t ALL_DOCKERFILES < <(git ls-files 'docker/Dockerfile.*')
|
||||
if [[ -n "${BUILD_DOCKER_MANUAL_ENV:-}" ]]
|
||||
then
|
||||
|
||||
if [[ "${BUILD_DOCKER_MANUAL_ENV}" == "ALL" ]]
|
||||
then
|
||||
DOCKERFILES=("${ALL_DOCKERFILES[@]}")
|
||||
else
|
||||
BUILD_LIST=($BUILD_DOCKER_MANUAL_ENV)
|
||||
DOCKERFILES=()
|
||||
for item in "${BUILD_LIST[@]}"
|
||||
do
|
||||
if [[ -f "docker/Dockerfile.${item}" ]]
|
||||
then
|
||||
DOCKERFILES+=("$item")
|
||||
fi
|
||||
done
|
||||
fi
|
||||
else
|
||||
CHANGED_FILES=$(git diff --name-only --diff-filter=ACMR HEAD^..HEAD)
|
||||
|
||||
mapfile -t DOCKERFILES < <(echo "$CHANGED_FILES" | grep 'docker/Dockerfile')
|
||||
mapfile -t SCRIPT_CHANGES < <(echo "$CHANGED_FILES" | grep 'docker/' | grep '\.sh$')
|
||||
|
||||
for file in "${SCRIPT_CHANGES[@]}"
|
||||
do
|
||||
for dockerfile in "${ALL_DOCKERFILES[@]}"
|
||||
do
|
||||
if grep -q "$(basename "$file")" "$dockerfile"
|
||||
then
|
||||
DOCKERFILES+=("$dockerfile")
|
||||
fi
|
||||
done
|
||||
done
|
||||
fi
|
||||
|
||||
echo -n "matrix="
|
||||
echo "${DOCKERFILES[@]}" | \
|
||||
tr ' ' '\n' | sort -u | sed 's,docker/Dockerfile\.,,' | \
|
||||
jq -Rsc 'split("\n") | map(select(length > 0)) | unique'
|
||||
|
|
@ -10,34 +10,46 @@ on:
|
|||
- '!gh-pages'
|
||||
paths-ignore:
|
||||
- 'kernel_module/**'
|
||||
- 'docker/**'
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: "!(contains(github.event.head_commit.message, '[ci skip]') || contains(github.event.head_commit.message, '[skip ci]'))"
|
||||
name: ${{ join(matrix.*, '/') }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
continue-on-error: ${{ startsWith(matrix.os, 'macos-') }}
|
||||
env:
|
||||
CC: ${{ matrix.cc }}
|
||||
list-distros:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- id: set-matrix
|
||||
run: .github/scripts/generate_build_matrix.py ghcr.io/${{ github.repository }} >> $GITHUB_OUTPUT
|
||||
|
||||
build:
|
||||
needs: list-distros
|
||||
if: "!(contains(github.event.head_commit.message, '[ci skip]') || contains(github.event.head_commit.message, '[skip ci]'))"
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: ['ubuntu-22.04', 'ubuntu-24.04', 'macos-latest']
|
||||
cc: ['gcc', 'clang']
|
||||
include: ${{ fromJson(needs.list-distros.outputs.matrix) }}
|
||||
|
||||
name: ${{ format('{0}/{1}{2}', matrix.distro, matrix.compiler, matrix.experimental == true && ' (experimental)' || '') }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
continue-on-error: ${{ matrix.experimental }}
|
||||
container:
|
||||
image: ${{ matrix.image }}
|
||||
credentials:
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install dependencies (Ubuntu)
|
||||
run: ./.github/scripts/install-dependencies-ubuntu.sh
|
||||
if: ${{ startsWith(matrix.os, 'ubuntu-') }}
|
||||
|
||||
- name: Install dependencies (MacOS)
|
||||
run: ./.github/scripts/install-dependencies-macos.sh
|
||||
if: ${{ startsWith(matrix.os, 'macos-') }}
|
||||
|
||||
- name: Build and Test
|
||||
env:
|
||||
CC: ${{ matrix.compiler }}
|
||||
run: ./.github/scripts/build-and-test.sh
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
name: Code Coverage
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
- '!gh-pages'
|
||||
pull_request:
|
||||
branches:
|
||||
- 'master'
|
||||
|
||||
jobs:
|
||||
coverage:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
./.github/scripts/install-dependencies-ubuntu.sh
|
||||
sudo apt-get install -y gcovr
|
||||
|
||||
- name: Configure and Build
|
||||
run: |
|
||||
meson setup build -Db_coverage=true --buildtype=debug
|
||||
meson compile -C build
|
||||
|
||||
- name: Run Tests
|
||||
run: meson test -C build
|
||||
|
||||
- name: Generate Coverage Report
|
||||
run: |
|
||||
# This generates the XML report for the upload step
|
||||
ninja -C build coverage-xml
|
||||
|
||||
- name: Upload Report to Codecov
|
||||
uses: codecov/codecov-action@v6
|
||||
with:
|
||||
files: buildd/meson-logs/coverage.xml
|
||||
fail_ci_if_error: true
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
name: Build Docker CI Images
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
distros:
|
||||
description: "List distros to build (space separated)"
|
||||
type: string
|
||||
default: "ALL"
|
||||
push:
|
||||
paths:
|
||||
- "docker/Dockerfile.*"
|
||||
- "docker/*.sh"
|
||||
|
||||
jobs:
|
||||
detect-changes:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
BUILD_DOCKER_MANUAL_ENV: ${{ inputs.distros || '' }}
|
||||
outputs:
|
||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0 # Needed for detecting changes
|
||||
|
||||
- name: Set build matrix
|
||||
id: set-matrix
|
||||
run: .github/scripts/get-changed-dockerfiles.sh >> $GITHUB_OUTPUT
|
||||
|
||||
build-and-push:
|
||||
needs: detect-changes
|
||||
if: ${{ needs.detect-changes.outputs.matrix != '[]' }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false # Don't cancel other distros if this one fails
|
||||
matrix:
|
||||
distro: ${{ fromJson(needs.detect-changes.outputs.matrix) }}
|
||||
permissions:
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: docker
|
||||
file: ./docker/Dockerfile.${{ matrix.distro }}
|
||||
push: true
|
||||
tags: ghcr.io/${{ github.repository }}/ci-build-${{ matrix.distro }}:latest
|
||||
|
||||
- name: Cleanup old builds
|
||||
uses: actions/delete-package-versions@v5
|
||||
with:
|
||||
package-name: libx52/ci-build-${{ matrix.distro }}
|
||||
package-type: container
|
||||
delete-only-untagged-versions: true
|
||||
|
|
@ -38,4 +38,4 @@ jobs:
|
|||
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
uses: actions/deploy-pages@v5
|
||||
|
|
|
|||
15
ChangeLog.md
15
ChangeLog.md
|
|
@ -6,8 +6,23 @@ The format is based upon [Keep a Changelog].
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
|
||||
- Migrated CI builds to run in multiple distro containers.
|
||||
|
||||
### Fixed
|
||||
- Addressed meson build bugs found in v0.3.3
|
||||
|
||||
## [0.3.3] - 2026-03-12
|
||||
|
||||
**Note:** While this release does introduce Meson support and deprecate the
|
||||
Autotools framework, there are several bugs in the Meson build files, causing a
|
||||
number of missing builds, notably the systemd service file, the documentation
|
||||
and man pages. In addition, the translation files were not handled at all, and
|
||||
translation services were disabled by default, compared to the Autotools
|
||||
framework where it was enabled by default. These bugs will be addressed in the
|
||||
next release.
|
||||
|
||||
### Added
|
||||
- Updated build infrastructure to use Meson instead of Autotools.
|
||||
- Added a [privacy policy](PRIVACY.md) to comply with GDPR/CCPA regulations.
|
||||
|
|
|
|||
843
Doxyfile.in
843
Doxyfile.in
File diff suppressed because it is too large
Load Diff
50
INSTALL.md
50
INSTALL.md
|
|
@ -5,22 +5,22 @@ Build has been tested on the following operating systems (x86-64 only):
|
|||
|
||||
* Ubuntu 22.04 LTS
|
||||
* Ubuntu 24.04 LTS
|
||||
* macOS Big Sur 11
|
||||
* macOS Monterey 12
|
||||
* Fedora latest (Fedora 42, as of this commit)
|
||||
* Archlinux
|
||||
* Alpine Linux (Experimental)
|
||||
* Ubuntu 26.04 LTS (Experimental as of this commit)
|
||||
* macOS (latest tag on Github, ARM)
|
||||
|
||||
# Prerequisites
|
||||
|
||||
## Required Packages
|
||||
|
||||
* automake
|
||||
* autoconf
|
||||
* autopoint
|
||||
* meson
|
||||
* ninja
|
||||
* gettext
|
||||
* hidapi + headers
|
||||
* inih
|
||||
* libtool
|
||||
* libusb-1.0 + headers
|
||||
* libevdev + headers (on Linux)
|
||||
* pkg-config
|
||||
* python3 (3.6 or greater)
|
||||
* git (not required for builds, but necessary to clone the repository)
|
||||
|
|
@ -29,15 +29,16 @@ Build has been tested on the following operating systems (x86-64 only):
|
|||
|
||||
| Platform | Install instructions |
|
||||
| -------- | -------------------- |
|
||||
| Ubuntu | `sudo apt-get install automake autoconf gettext autopoint libhidapi-dev libevdev-dev libtool libusb-1.0-0-dev libinih-dev pkg-config python3 git` |
|
||||
| MacOS + Homebrew | `brew install automake autoconf gettext hidapi libtool libusb pkg-config python3 git` |
|
||||
| Arch Linux | `pacman -S base-devel libusb hidapi libevdev libinih python git` |
|
||||
| Fedora | `sudo dnf install autoconf automake gettext-devel findutils libtool hidapi-devel libusb-devel libevdev-devel inih-devel pkg-config python3 git` |
|
||||
| Ubuntu | `sudo apt-get install meson gettext libhidapi-dev libevdev-dev libusb-1.0-0-dev libinih-dev pkg-config python3 git` |
|
||||
| MacOS + Homebrew | `brew install meson gettext hidapi libtool libusb pkg-config python3 git` |
|
||||
| Arch Linux | `pacman -S base-devel meson libusb hidapi libevdev libinih python git` |
|
||||
| Fedora | `sudo dnf install meson gettext-devel findutils hidapi-devel libusb-devel libevdev-devel inih-devel pkg-config python3 git` |
|
||||
|
||||
## Optional Packages
|
||||
|
||||
* doxygen - to generate HTML documentation and man pages
|
||||
* libcmocka (1.1 or greater) + headers - to run unit tests
|
||||
* libevdev + headers (on Linux) - to add virtual keyboard/mouse support
|
||||
|
||||
# Installation Instructions
|
||||
|
||||
|
|
@ -49,37 +50,36 @@ git clone https://github.com/nirenjan/libx52.git
|
|||
2. Run autogen.sh
|
||||
```
|
||||
cd ./libx52
|
||||
./autogen.sh
|
||||
meson setup build -Dprefix=/usr
|
||||
```
|
||||
|
||||
3. Run the following commands:
|
||||
```
|
||||
./configure --prefix=/usr --localstatedir=/var --sysconfdir=/etc
|
||||
make && sudo make install
|
||||
meson compile -C build && meson install -C build
|
||||
```
|
||||
|
||||
You may want to remove or edit the `--prefix=/usr` option, most users prefer
|
||||
non-distro binaries in `/usr/local` (default without `--prefix`) or `/opt`.
|
||||
You may want to remove or edit the `-Dprefix=/usr` option, most users prefer
|
||||
non-distro binaries in `/usr/local` (default without `-Dprefix`) or `/opt`.
|
||||
|
||||
## Configuration options
|
||||
|
||||
### udev
|
||||
|
||||
The configuration system should automatically detect the udev rules directory,
|
||||
but you can override it by using the following argument to `configure`:
|
||||
but you can override it by using the following argument to `meson setup`:
|
||||
|
||||
```
|
||||
--with-udevrulesdir=/path/to/udev/rules.d
|
||||
-Dudev-rules-dir=/path/to/udev/rules.d
|
||||
```
|
||||
|
||||
### Input group
|
||||
|
||||
The udev rules that are installed provide read/write access to members of the
|
||||
input devices group. This defaults to `plugdev`, but can be modified using
|
||||
the following argument to `configure`:
|
||||
the following argument to `meson setup`:
|
||||
|
||||
```
|
||||
--with-input-group=group
|
||||
-Dinput-group=group
|
||||
```
|
||||
|
||||
### Systemd support
|
||||
|
|
@ -89,13 +89,13 @@ itself to run in the background. Typical deployments with systemd will have it
|
|||
run in the foreground, and disable timestamps in the logs, since those are
|
||||
inserted automatically by journald.
|
||||
|
||||
Systemd support is enabled by default, but can be disabled with the
|
||||
`--disable-systemd` argument to `configure`
|
||||
Systemd support is enabled by default, which disables timestamps in the program
|
||||
logs, but you can re-enable timestamps by passing `-Dsystemd-logs=disabled`
|
||||
argument to `meson setup`
|
||||
|
||||
It is also possible to configure the directory in which the service file is
|
||||
installed with the following option. This is ignored if you have specified
|
||||
`--disable-systemd`.
|
||||
installed with the following option. This is ignored if systemd is not found.
|
||||
|
||||
```
|
||||
--with-systemdsystemunitdir=/path/to/systemd/system
|
||||
-Dsystemd-unit-dir=/path/to/systemd/system
|
||||
```
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ Saitek X52Pro joystick driver for Linux
|
|||

|
||||

|
||||

|
||||
[](https://sonarcloud.io/summary/new_code?id=nirenjan_libx52)
|
||||
[](https://codecov.io/gh/nirenjan/libx52)
|
||||
|
||||
This project adds a new driver for the Saitek/MadCatz X52 Pro flight
|
||||
control system. The X52 pro is a HOTAS (hand on throttle and stick)
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ devinfo_cleanup:
|
|||
libx52io_exit(ctx);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
int main(void)
|
||||
{
|
||||
const struct libusb_version *libusb;
|
||||
|
||||
|
|
|
|||
|
|
@ -26,22 +26,26 @@ int libx52_init(libx52_device **dev)
|
|||
|
||||
int libx52_connect(libx52_device *dev)
|
||||
{
|
||||
(void)dev;
|
||||
function_called();
|
||||
return mock();
|
||||
}
|
||||
|
||||
int libx52_update(libx52_device *dev)
|
||||
{
|
||||
(void)dev;
|
||||
return LIBX52_SUCCESS;
|
||||
}
|
||||
|
||||
void libx52_exit(libx52_device *dev)
|
||||
{
|
||||
(void)dev;
|
||||
return;
|
||||
}
|
||||
|
||||
const char *libx52_strerror(libx52_error_code rc)
|
||||
{
|
||||
(void)rc;
|
||||
function_called();
|
||||
return "";
|
||||
}
|
||||
|
|
@ -172,7 +176,7 @@ const struct CMUnitTest tests[] = {
|
|||
#include "test_x52_cli_tests.c"
|
||||
};
|
||||
|
||||
int main(int argc, char **argv)
|
||||
int main(void)
|
||||
{
|
||||
cmocka_set_message_output(CM_OUTPUT_TAP);
|
||||
cmocka_run_group_tests(tests, NULL, NULL);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
#ifndef TEST_LIST
|
||||
// Setup the test case function
|
||||
#define TEST_CASE(tc) static void tc(void **state)
|
||||
#define TEST_CASE(tc) static void tc(void **state __attribute__((unused)))
|
||||
#define TEST_DEF(x) x
|
||||
// Function header, this calls the corresponding libx52 function, and expects
|
||||
// a certain number of calls to that function
|
||||
|
|
|
|||
|
|
@ -19,6 +19,11 @@ LT_INIT
|
|||
PKG_PROG_PKG_CONFIG
|
||||
PKG_INSTALLDIR
|
||||
AX_COMPILER_FLAGS
|
||||
VISIBILITY_CFLAGS=
|
||||
if test "x$GCC" = xyes; then
|
||||
VISIBILITY_CFLAGS="-fvisibility=hidden"
|
||||
fi
|
||||
AC_SUBST([VISIBILITY_CFLAGS])
|
||||
AC_CANONICAL_HOST
|
||||
AX_GCC_FUNC_ATTRIBUTE([constructor])
|
||||
AX_GCC_FUNC_ATTRIBUTE([destructor])
|
||||
|
|
|
|||
|
|
@ -61,8 +61,11 @@ libx52dcomm_la_CFLAGS = \
|
|||
-DLOCALEDIR=\"$(localedir)\" \
|
||||
-DLOGDIR=\"$(localstatedir)/log\" \
|
||||
-DRUNDIR=\"$(localstatedir)/run\" \
|
||||
$(VISIBILITY_CFLAGS) \
|
||||
$(WARN_CFLAGS)
|
||||
libx52dcomm_la_LDFLAGS = $(WARN_LDFLAGS)
|
||||
libx52dcomm_la_LDFLAGS = \
|
||||
-export-symbols-regex '^x52d_(dial_command|dial_notify|format_command|send_command|recv_notification)$' \
|
||||
$(WARN_LDFLAGS)
|
||||
|
||||
x52include_HEADERS += daemon/x52dcomm.h
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
# x52d
|
||||
libx52dcomm_version = '1.0.0'
|
||||
|
||||
libx52dcomm_sources = [
|
||||
'x52d_comm_client.c',
|
||||
'x52d_comm_internal.c'
|
||||
|
|
@ -8,6 +10,9 @@ install_headers('x52dcomm.h', subdir: meson.project_name())
|
|||
|
||||
lib_libx52dcomm = library('x52dcomm', libx52dcomm_sources,
|
||||
dependencies: [dep_intl],
|
||||
version: libx52dcomm_version,
|
||||
c_args: sym_hidden_cargs,
|
||||
install: true,
|
||||
include_directories: includes)
|
||||
|
||||
x52d_sources = [
|
||||
|
|
@ -22,28 +27,24 @@ x52d_sources = [
|
|||
'x52d_notify.c',
|
||||
'x52d_led.c',
|
||||
'x52d_command.c',
|
||||
'x52d_io.c',
|
||||
'x52d_mouse_handler.c',
|
||||
]
|
||||
|
||||
dep_threads = dependency('threads')
|
||||
x52d_linkwith = [lib_libx52, lib_libx52dcomm]
|
||||
# Comm sources are compiled into x52d (same as Autotools); libx52dcomm is only for x52ctl.
|
||||
x52d_linkwith = [lib_libx52, lib_vkm, lib_libx52io]
|
||||
x52d_deps = [dep_pinelog, dep_inih, dep_threads, dep_intl]
|
||||
x52d_cflags = []
|
||||
if dep_evdev.found()
|
||||
x52d_sources += 'x52d_io.c'
|
||||
x52d_sources += 'x52d_mouse_evdev.c'
|
||||
x52d_cflags += '-DHAVE_EVDEV'
|
||||
x52d_linkwith += lib_libx52io
|
||||
x52d_deps += dep_evdev
|
||||
endif
|
||||
|
||||
exe_x52d = executable('x52d', x52d_sources,
|
||||
exe_x52d = executable('x52d', x52d_sources + libx52dcomm_sources,
|
||||
install: true,
|
||||
include_directories: includes,
|
||||
c_args: x52d_cflags,
|
||||
c_args: sym_hidden_cargs + x52d_cflags,
|
||||
dependencies: x52d_deps,
|
||||
link_with: x52d_linkwith)
|
||||
|
||||
executable('x52ctl', 'x52ctl.c',
|
||||
exe_x52ctl = executable('x52ctl', 'x52ctl.c',
|
||||
install: true,
|
||||
dependencies: [dep_intl],
|
||||
include_directories: includes,
|
||||
|
|
@ -53,7 +54,7 @@ install_data('x52d.conf',
|
|||
install_dir: join_paths(get_option('sysconfdir'), 'x52d'))
|
||||
|
||||
test('daemon-communication', files('test_daemon_comm.py')[0],
|
||||
depends: exe_x52d, protocol: 'tap')
|
||||
depends: [exe_x52d, exe_x52ctl], protocol: 'tap')
|
||||
|
||||
x52d_mouse_test_sources = ['x52d_mouse_test.c', 'x52d_mouse.c']
|
||||
x52d_mouse_test = executable('x52d-mouse-test', x52d_mouse_test_sources,
|
||||
|
|
@ -62,3 +63,24 @@ x52d_mouse_test = executable('x52d-mouse-test', x52d_mouse_test_sources,
|
|||
|
||||
test('x52d-mouse-test', x52d_mouse_test, protocol: 'tap')
|
||||
|
||||
# Install service file
|
||||
if dep_systemd.found()
|
||||
systemd_system_unit_dir = get_option('systemd-unit-dir')
|
||||
if systemd_system_unit_dir == ''
|
||||
systemd_system_unit_dir = dep_systemd.get_variable(
|
||||
pkgconfig: 'systemd_system_unit_dir',
|
||||
default_value: '/lib/systemd/system')
|
||||
endif
|
||||
sed = find_program('sed')
|
||||
bindir_path = get_option('prefix') / get_option('bindir')
|
||||
sed_script = 's|%bindir%|' + bindir_path + '|g'
|
||||
|
||||
systemd_service_file = configure_file(
|
||||
input: 'x52d.service.in',
|
||||
output: 'x52d.service',
|
||||
command: [sed, sed_script, '@INPUT@'],
|
||||
capture: true,
|
||||
install: true,
|
||||
install_dir: systemd_system_unit_dir
|
||||
)
|
||||
endif
|
||||
|
|
|
|||
|
|
@ -89,15 +89,53 @@ static int send_command(int sock_fd, int argc, char **argv)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void interactive_mode(int sock_fd)
|
||||
{
|
||||
bool keep_running = true;
|
||||
char buffer[1024];
|
||||
|
||||
fputs("> ", stdout);
|
||||
while (keep_running && fgets(buffer, sizeof(buffer), stdin) != NULL) {
|
||||
int sargc;
|
||||
char *sargv[512] = { 0 };
|
||||
int pos;
|
||||
|
||||
if (strcasecmp(buffer, "quit\n") == 0) {
|
||||
keep_running = false;
|
||||
} else {
|
||||
/* Break the buffer into argc/argv */
|
||||
sargc = 0;
|
||||
pos = 0;
|
||||
while (buffer[pos]) {
|
||||
if (isspace(buffer[pos])) {
|
||||
buffer[pos] = '\0';
|
||||
pos++;
|
||||
} else {
|
||||
sargv[sargc] = &buffer[pos];
|
||||
sargc++;
|
||||
for (; buffer[pos] && !isspace(buffer[pos]); pos++);
|
||||
}
|
||||
}
|
||||
|
||||
if (send_command(sock_fd, sargc, sargv)) {
|
||||
keep_running = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (keep_running) {
|
||||
fputs("\n> ", stdout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
bool interactive = false;
|
||||
char *socket_path = NULL;
|
||||
const char *socket_path = NULL;
|
||||
int opt;
|
||||
int sock_fd;
|
||||
int rc = EXIT_SUCCESS;
|
||||
|
||||
char buffer[1024];
|
||||
|
||||
/*
|
||||
* Parse command line arguments
|
||||
|
|
@ -142,47 +180,13 @@ int main(int argc, char **argv)
|
|||
_("Running in interactive mode, ignoring extra arguments\n"));
|
||||
}
|
||||
|
||||
fputs("> ", stdout);
|
||||
while (fgets(buffer, sizeof(buffer), stdin) != NULL) {
|
||||
int sargc;
|
||||
char *sargv[512] = { 0 };
|
||||
int pos;
|
||||
|
||||
if (strcasecmp(buffer, "quit\n") == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Break the buffer into argc/argv */
|
||||
sargc = 0;
|
||||
pos = 0;
|
||||
while (buffer[pos]) {
|
||||
if (isspace(buffer[pos])) {
|
||||
buffer[pos] = '\0';
|
||||
pos++;
|
||||
} else {
|
||||
sargv[sargc] = &buffer[pos];
|
||||
sargc++;
|
||||
for (; buffer[pos] && !isspace(buffer[pos]); pos++);
|
||||
}
|
||||
}
|
||||
|
||||
if (send_command(sock_fd, sargc, sargv)) {
|
||||
rc = EXIT_FAILURE;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
fputs("\n> ", stdout);
|
||||
}
|
||||
|
||||
interactive_mode(sock_fd);
|
||||
} else {
|
||||
if (send_command(sock_fd, argc - optind, &argv[optind])) {
|
||||
rc = EXIT_FAILURE;
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
cleanup:
|
||||
close(sock_fd);
|
||||
return rc;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -169,6 +169,7 @@ static pthread_t clock_thr;
|
|||
static void * x52_clock_thr(void *param)
|
||||
{
|
||||
int rc;
|
||||
(void)param;
|
||||
|
||||
PINELOG_INFO(_("Starting X52 clock manager thread"));
|
||||
for (;;) {
|
||||
|
|
|
|||
|
|
@ -409,6 +409,7 @@ int x52d_command_loop(int sock_fd)
|
|||
|
||||
static void * x52d_command_thread(void *param)
|
||||
{
|
||||
(void)param;
|
||||
for (;;) {
|
||||
if (x52d_command_loop(command_sock_fd) < 0) {
|
||||
PINELOG_FATAL(_("Error %d during command loop: %s"),
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ static volatile bool device_update_needed;
|
|||
static void *x52_dev_thr(void *param)
|
||||
{
|
||||
int rc;
|
||||
(void)param;
|
||||
|
||||
#define DEV_ACQ_DELAY 5 // seconds
|
||||
#define DEV_UPD_DELAY 50000 // microseconds
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ static void *x52_io_thr(void *param)
|
|||
int rc;
|
||||
libx52io_report report;
|
||||
libx52io_report prev_report;
|
||||
(void)param;
|
||||
|
||||
#define IO_READ_TIMEOUT 50 /* milliseconds */
|
||||
#define IO_ACQ_TIMEOUT 5 /* seconds */
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <signal.h>
|
||||
#include <inttypes.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
|
|
@ -38,12 +39,14 @@ static void termination_handler(int signum)
|
|||
static volatile bool flag_reload;
|
||||
static void reload_handler(int signum)
|
||||
{
|
||||
(void)signum;
|
||||
flag_reload = true;
|
||||
}
|
||||
|
||||
static volatile bool flag_save_cfg;
|
||||
static void save_config_handler(int signum)
|
||||
{
|
||||
(void)signum;
|
||||
flag_save_cfg = true;
|
||||
}
|
||||
|
||||
|
|
@ -111,14 +114,16 @@ static void start_daemon(bool foreground, const char *pid_file)
|
|||
pid_fd = fopen(pid_file, "r");
|
||||
if (pid_fd != NULL) {
|
||||
int rc;
|
||||
intmax_t tmp_pid;
|
||||
|
||||
/* File exists, read the PID and check if it exists */
|
||||
rc = fscanf(pid_fd, "%u", &pid);
|
||||
rc = fscanf(pid_fd, "%" SCNdMAX, &tmp_pid);
|
||||
fclose(pid_fd);
|
||||
|
||||
if (rc != 1) {
|
||||
perror("fscanf");
|
||||
} else {
|
||||
pid = (pid_t)tmp_pid;
|
||||
rc = kill(pid, 0);
|
||||
if (rc == 0 || (rc < 0 && errno == EPERM)) {
|
||||
PINELOG_FATAL(_("Daemon is already running as PID %u"), pid);
|
||||
|
|
@ -324,10 +329,8 @@ int main(int argc, char **argv)
|
|||
goto cleanup;
|
||||
}
|
||||
x52d_notify_init(notify_sock);
|
||||
#if defined(HAVE_EVDEV)
|
||||
x52d_io_init();
|
||||
x52d_mouse_evdev_init();
|
||||
#endif
|
||||
x52d_mouse_handler_init();
|
||||
|
||||
// Re-enable signals
|
||||
rc = pthread_sigmask(SIG_UNBLOCK, &sigblockset, NULL);
|
||||
|
|
@ -366,10 +369,8 @@ cleanup:
|
|||
x52d_dev_exit();
|
||||
x52d_command_exit();
|
||||
x52d_notify_exit();
|
||||
#if defined(HAVE_EVDEV)
|
||||
x52d_mouse_evdev_exit();
|
||||
x52d_mouse_handler_exit();
|
||||
x52d_io_exit();
|
||||
#endif
|
||||
|
||||
// Remove the PID file
|
||||
PINELOG_TRACE("Removing PID file %s", pid_file);
|
||||
|
|
|
|||
|
|
@ -30,9 +30,7 @@ void x52d_cfg_set_Mouse_Enabled(bool enabled)
|
|||
{
|
||||
PINELOG_DEBUG(_("Setting mouse enable to %s"),
|
||||
enabled ? _("on") : _("off"));
|
||||
#if defined HAVE_EVDEV
|
||||
x52d_mouse_evdev_thread_control(enabled);
|
||||
#endif
|
||||
x52d_mouse_thread_control(enabled);
|
||||
}
|
||||
|
||||
void x52d_cfg_set_Mouse_Speed(int speed)
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ extern volatile int mouse_scroll_dir;
|
|||
|
||||
#define MOUSE_MULT_FACTOR 4
|
||||
|
||||
void x52d_mouse_evdev_thread_control(bool enabled);
|
||||
void x52d_mouse_evdev_init(void);
|
||||
void x52d_mouse_evdev_exit(void);
|
||||
void x52d_mouse_thread_control(bool enabled);
|
||||
void x52d_mouse_handler_init(void);
|
||||
void x52d_mouse_handler_exit(void);
|
||||
void x52d_mouse_report_event(libx52io_report *report);
|
||||
|
||||
#endif // !defined X52D_MOUSE_H
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - Mouse driver
|
||||
*
|
||||
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
* Copyright (C) 2021-2026 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
|
@ -12,10 +12,10 @@
|
|||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "libevdev/libevdev.h"
|
||||
#include "libevdev/libevdev-uinput.h"
|
||||
#include "libx52io.h"
|
||||
#include "vkm.h"
|
||||
|
||||
#define PINELOG_MODULE X52D_MOD_MOUSE
|
||||
#include "pinelog.h"
|
||||
#include "x52d_config.h"
|
||||
#include "x52d_const.h"
|
||||
|
|
@ -24,36 +24,47 @@
|
|||
static pthread_t mouse_thr;
|
||||
static bool mouse_thr_enabled = false;
|
||||
|
||||
static struct libevdev_uinput *mouse_uidev;
|
||||
static bool mouse_uidev_created = false;
|
||||
static vkm_context *mouse_context;
|
||||
|
||||
static volatile libx52io_report old_report;
|
||||
static volatile libx52io_report new_report;
|
||||
|
||||
static int report_button_change(int button, int index)
|
||||
static int report_button_change(vkm_mouse_button button, int index)
|
||||
{
|
||||
int rc = 1;
|
||||
vkm_result rc = VKM_ERROR_NO_CHANGE;
|
||||
bool old_button = old_report.button[index];
|
||||
bool new_button = new_report.button[index];
|
||||
vkm_button_state state;
|
||||
|
||||
if (old_button != new_button) {
|
||||
rc = libevdev_uinput_write_event(mouse_uidev, EV_KEY, button,
|
||||
(int)new_button);
|
||||
if (rc != 0) {
|
||||
PINELOG_ERROR(_("Error writing mouse button event (button %d, state %d)"),
|
||||
button, (int)new_button);
|
||||
state = new_button ? VKM_BUTTON_PRESSED : VKM_BUTTON_RELEASED;
|
||||
rc = vkm_mouse_click(mouse_context, button, state);
|
||||
if (rc != VKM_SUCCESS && rc != VKM_ERROR_NO_CHANGE) {
|
||||
PINELOG_ERROR(_("Error %d writing mouse button event (button %d, state %d)"),
|
||||
rc, button, (int)new_button);
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
return (rc == VKM_SUCCESS);
|
||||
}
|
||||
|
||||
static int report_wheel(void)
|
||||
{
|
||||
int rc = 1;
|
||||
vkm_result rc = VKM_ERROR_NO_CHANGE;
|
||||
int wheel = 0;
|
||||
bool scroll_up = new_report.button[LIBX52IO_BTN_MOUSE_SCROLL_UP];
|
||||
bool scroll_dn = new_report.button[LIBX52IO_BTN_MOUSE_SCROLL_DN];
|
||||
bool old_scroll_up = old_report.button[LIBX52IO_BTN_MOUSE_SCROLL_UP];
|
||||
bool old_scroll_dn = old_report.button[LIBX52IO_BTN_MOUSE_SCROLL_DN];
|
||||
vkm_mouse_scroll_direction dir;
|
||||
|
||||
/*
|
||||
* Handle multiple scroll button presses in sequence. This happens if a
|
||||
* hardware axis is very noisy and the firmware sends a sequence of reports
|
||||
* with button down, even though this is technically a momentary button.
|
||||
*/
|
||||
scroll_up = scroll_up && !old_scroll_up;
|
||||
scroll_dn = scroll_dn && !old_scroll_dn;
|
||||
|
||||
if (scroll_up) {
|
||||
// Scroll up event
|
||||
|
|
@ -64,19 +75,18 @@ static int report_wheel(void)
|
|||
}
|
||||
|
||||
if (wheel != 0) {
|
||||
rc = libevdev_uinput_write_event(mouse_uidev, EV_REL, REL_WHEEL, wheel);
|
||||
if (rc != 0) {
|
||||
PINELOG_ERROR(_("Error writing mouse wheel event %d"), wheel);
|
||||
dir = (wheel == 1) ? VKM_MOUSE_SCROLL_UP : VKM_MOUSE_SCROLL_DOWN;
|
||||
rc = vkm_mouse_scroll(mouse_context, dir);
|
||||
if (rc != VKM_SUCCESS) {
|
||||
PINELOG_ERROR(_("Error writing mouse wheel event %d"), dir);
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
return (rc == VKM_SUCCESS);
|
||||
}
|
||||
|
||||
static int report_axis(int axis, int index)
|
||||
static int get_axis_val(int index)
|
||||
{
|
||||
int rc = 1;
|
||||
|
||||
int axis_val = new_report.axis[index];
|
||||
|
||||
/*
|
||||
|
|
@ -96,22 +106,29 @@ static int report_axis(int axis, int index)
|
|||
*/
|
||||
axis_val = (axis_val * mouse_mult) / MOUSE_MULT_FACTOR;
|
||||
|
||||
if (axis_val) {
|
||||
rc = libevdev_uinput_write_event(mouse_uidev, EV_REL, axis, axis_val);
|
||||
if (rc != 0) {
|
||||
PINELOG_ERROR(_("Error writing mouse axis event (axis %d, value %d)"),
|
||||
axis, axis_val);
|
||||
}
|
||||
return axis_val;
|
||||
}
|
||||
|
||||
static int report_axis(void)
|
||||
{
|
||||
vkm_result rc;
|
||||
int dx = get_axis_val(LIBX52IO_AXIS_THUMBX);
|
||||
int dy = get_axis_val(LIBX52IO_AXIS_THUMBY);
|
||||
|
||||
rc = vkm_mouse_move(mouse_context, dx, dy);
|
||||
if (rc != VKM_SUCCESS && rc != VKM_ERROR_NO_CHANGE) {
|
||||
PINELOG_ERROR(_("Error %d writing mouse axis event (dx %d, dy %d)"),
|
||||
rc, dx, dy);
|
||||
}
|
||||
|
||||
return rc;
|
||||
return (rc == VKM_SUCCESS);
|
||||
}
|
||||
|
||||
static void report_sync(void)
|
||||
{
|
||||
int rc;
|
||||
rc = libevdev_uinput_write_event(mouse_uidev, EV_SYN, SYN_REPORT, 0);
|
||||
if (rc != 0) {
|
||||
vkm_result rc;
|
||||
rc = vkm_sync(mouse_context);
|
||||
if (rc != VKM_SUCCESS) {
|
||||
PINELOG_ERROR(_("Error writing mouse sync event"));
|
||||
} else {
|
||||
memcpy((void *)&old_report, (void *)&new_report, sizeof(old_report));
|
||||
|
|
@ -129,15 +146,11 @@ static void reset_reports(void)
|
|||
|
||||
static void * x52_mouse_thr(void *param)
|
||||
{
|
||||
bool state_changed;
|
||||
(void)param;
|
||||
|
||||
PINELOG_INFO(_("Starting X52 virtual mouse driver thread"));
|
||||
for (;;) {
|
||||
state_changed = false;
|
||||
state_changed |= (0 == report_axis(REL_X, LIBX52IO_AXIS_THUMBX));
|
||||
state_changed |= (0 == report_axis(REL_Y, LIBX52IO_AXIS_THUMBY));
|
||||
|
||||
if (state_changed) {
|
||||
if (report_axis()) {
|
||||
report_sync();
|
||||
}
|
||||
|
||||
|
|
@ -165,9 +178,9 @@ static void x52d_mouse_thr_exit(void)
|
|||
pthread_cancel(mouse_thr);
|
||||
}
|
||||
|
||||
void x52d_mouse_evdev_thread_control(bool enabled)
|
||||
void x52d_mouse_thread_control(bool enabled)
|
||||
{
|
||||
if (!mouse_uidev_created) {
|
||||
if (!vkm_is_ready(mouse_context)) {
|
||||
PINELOG_INFO(_("Virtual mouse not created. Ignoring thread state change"));
|
||||
return;
|
||||
}
|
||||
|
|
@ -197,14 +210,14 @@ void x52d_mouse_report_event(libx52io_report *report)
|
|||
if (report) {
|
||||
memcpy((void *)&new_report, report, sizeof(new_report));
|
||||
|
||||
if (!mouse_uidev_created || !mouse_thr_enabled) {
|
||||
if (!vkm_is_ready(mouse_context) || !mouse_thr_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
state_changed = false;
|
||||
state_changed |= (0 == report_button_change(BTN_LEFT, LIBX52IO_BTN_MOUSE_PRIMARY));
|
||||
state_changed |= (0 == report_button_change(BTN_RIGHT, LIBX52IO_BTN_MOUSE_SECONDARY));
|
||||
state_changed |= (0 == report_wheel());
|
||||
state_changed = state_changed || (0 == report_button_change(VKM_MOUSE_BTN_LEFT, LIBX52IO_BTN_MOUSE_PRIMARY));
|
||||
state_changed = state_changed || (0 == report_button_change(VKM_MOUSE_BTN_RIGHT, LIBX52IO_BTN_MOUSE_SECONDARY));
|
||||
state_changed = state_changed || (0 == report_wheel());
|
||||
|
||||
if (state_changed) {
|
||||
report_sync();
|
||||
|
|
@ -214,35 +227,27 @@ void x52d_mouse_report_event(libx52io_report *report)
|
|||
}
|
||||
}
|
||||
|
||||
void x52d_mouse_evdev_init(void)
|
||||
void x52d_mouse_handler_init(void)
|
||||
{
|
||||
int rc;
|
||||
struct libevdev *dev;
|
||||
vkm_result rc;
|
||||
|
||||
/* Create a new mouse device */
|
||||
dev = libevdev_new();
|
||||
libevdev_set_name(dev, "X52 virtual mouse");
|
||||
libevdev_enable_event_type(dev, EV_REL);
|
||||
libevdev_enable_event_code(dev, EV_REL, REL_X, NULL);
|
||||
libevdev_enable_event_code(dev, EV_REL, REL_Y, NULL);
|
||||
libevdev_enable_event_code(dev, EV_REL, REL_WHEEL, NULL);
|
||||
libevdev_enable_event_type(dev, EV_KEY);
|
||||
libevdev_enable_event_code(dev, EV_KEY, BTN_LEFT, NULL);
|
||||
libevdev_enable_event_code(dev, EV_KEY, BTN_RIGHT, NULL);
|
||||
rc = vkm_init(&mouse_context);
|
||||
if (rc != VKM_SUCCESS) {
|
||||
PINELOG_ERROR(_("Error %d creating X52 virtual mouse"), rc);
|
||||
return;
|
||||
}
|
||||
|
||||
rc = libevdev_uinput_create_from_device(dev, LIBEVDEV_UINPUT_OPEN_MANAGED,
|
||||
&mouse_uidev);
|
||||
if (rc != 0) {
|
||||
PINELOG_ERROR(_("Error %d creating X52 virtual mouse: %s"),
|
||||
-rc, strerror(-rc));
|
||||
} else {
|
||||
mouse_uidev_created = true;
|
||||
vkm_set_option(mouse_context, VKM_OPT_DEVICE_NAME, "X52 virtual mouse");
|
||||
|
||||
rc = vkm_start(mouse_context);
|
||||
if (rc != VKM_SUCCESS) {
|
||||
PINELOG_ERROR(_("Error %d creating X52 virtual mouse"), rc);
|
||||
}
|
||||
}
|
||||
|
||||
void x52d_mouse_evdev_exit(void)
|
||||
void x52d_mouse_handler_exit(void)
|
||||
{
|
||||
x52d_mouse_evdev_thread_control(false);
|
||||
mouse_uidev_created = false;
|
||||
libevdev_uinput_destroy(mouse_uidev);
|
||||
x52d_mouse_thread_control(false);
|
||||
vkm_exit(mouse_context);
|
||||
mouse_context = NULL;
|
||||
}
|
||||
|
|
@ -19,37 +19,34 @@
|
|||
#include "x52d_const.h"
|
||||
#include "x52d_mouse.h"
|
||||
|
||||
#if defined HAVE_EVDEV
|
||||
/* Stub for evdev */
|
||||
void x52d_mouse_evdev_thread_control(bool enabled)
|
||||
/* Stub for handler */
|
||||
void x52d_mouse_thread_control(bool enabled)
|
||||
{
|
||||
function_called();
|
||||
check_expected(enabled);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void test_mouse_thread_enabled(void **state)
|
||||
{
|
||||
#if defined HAVE_EVDEV
|
||||
expect_function_calls(x52d_mouse_evdev_thread_control, 1);
|
||||
expect_value(x52d_mouse_evdev_thread_control, enabled, true);
|
||||
#endif
|
||||
(void)state;
|
||||
expect_function_calls(x52d_mouse_thread_control, 1);
|
||||
expect_value(x52d_mouse_thread_control, enabled, true);
|
||||
|
||||
x52d_cfg_set_Mouse_Enabled(true);
|
||||
}
|
||||
|
||||
static void test_mouse_thread_disabled(void **state)
|
||||
{
|
||||
#if defined HAVE_EVDEV
|
||||
expect_function_calls(x52d_mouse_evdev_thread_control, 1);
|
||||
expect_value(x52d_mouse_evdev_thread_control, enabled, false);
|
||||
#endif
|
||||
(void)state;
|
||||
expect_function_calls(x52d_mouse_thread_control, 1);
|
||||
expect_value(x52d_mouse_thread_control, enabled, false);
|
||||
|
||||
x52d_cfg_set_Mouse_Enabled(false);
|
||||
}
|
||||
|
||||
static void test_mouse_speed_negative(void **state)
|
||||
{
|
||||
(void)state;
|
||||
int orig_mouse_delay = mouse_delay;
|
||||
int orig_mouse_mult = mouse_mult;
|
||||
|
||||
|
|
@ -61,6 +58,7 @@ static void test_mouse_speed_negative(void **state)
|
|||
/* The following tests are dependent on the values in x52d_mouse.c */
|
||||
static void test_mouse_speed_0(void **state)
|
||||
{
|
||||
(void)state;
|
||||
x52d_cfg_set_Mouse_Speed(0);
|
||||
assert_int_equal(mouse_delay, 70000);
|
||||
assert_int_equal(mouse_mult, 4);
|
||||
|
|
@ -68,6 +66,7 @@ static void test_mouse_speed_0(void **state)
|
|||
|
||||
static void test_mouse_speed_mid_base(void **state)
|
||||
{
|
||||
(void)state;
|
||||
x52d_cfg_set_Mouse_Speed(6);
|
||||
assert_int_equal(mouse_delay, 40000);
|
||||
assert_int_equal(mouse_mult, 4);
|
||||
|
|
@ -75,6 +74,7 @@ static void test_mouse_speed_mid_base(void **state)
|
|||
|
||||
static void test_mouse_speed_max_base(void **state)
|
||||
{
|
||||
(void)state;
|
||||
x52d_cfg_set_Mouse_Speed(12);
|
||||
assert_int_equal(mouse_delay, 10000);
|
||||
assert_int_equal(mouse_mult, 4);
|
||||
|
|
@ -82,6 +82,7 @@ static void test_mouse_speed_max_base(void **state)
|
|||
|
||||
static void test_mouse_speed_min_hyper(void **state)
|
||||
{
|
||||
(void)state;
|
||||
x52d_cfg_set_Mouse_Speed(13);
|
||||
assert_int_equal(mouse_delay, 10000);
|
||||
assert_int_equal(mouse_mult, 5);
|
||||
|
|
@ -89,6 +90,7 @@ static void test_mouse_speed_min_hyper(void **state)
|
|||
|
||||
static void test_mouse_speed_mid_hyper(void **state)
|
||||
{
|
||||
(void)state;
|
||||
x52d_cfg_set_Mouse_Speed(22);
|
||||
assert_int_equal(mouse_delay, 10000);
|
||||
assert_int_equal(mouse_mult, 14);
|
||||
|
|
@ -96,6 +98,7 @@ static void test_mouse_speed_mid_hyper(void **state)
|
|||
|
||||
static void test_mouse_speed_max_hyper(void **state)
|
||||
{
|
||||
(void)state;
|
||||
x52d_cfg_set_Mouse_Speed(32);
|
||||
assert_int_equal(mouse_delay, 10000);
|
||||
assert_int_equal(mouse_mult, 24);
|
||||
|
|
@ -105,6 +108,7 @@ static void test_mouse_speed_above_max(void **state)
|
|||
{
|
||||
int orig_mouse_delay = mouse_delay;
|
||||
int orig_mouse_mult = mouse_mult;
|
||||
(void)state;
|
||||
|
||||
x52d_cfg_set_Mouse_Speed(33);
|
||||
assert_int_equal(mouse_delay, orig_mouse_delay);
|
||||
|
|
@ -113,12 +117,14 @@ static void test_mouse_speed_above_max(void **state)
|
|||
|
||||
static void test_mouse_reverse_scroll_enabled(void **state)
|
||||
{
|
||||
(void)state;
|
||||
x52d_cfg_set_Mouse_ReverseScroll(true);
|
||||
assert_int_equal(mouse_scroll_dir, -1);
|
||||
}
|
||||
|
||||
static void test_mouse_reverse_scroll_disabled(void **state)
|
||||
{
|
||||
(void)state;
|
||||
x52d_cfg_set_Mouse_ReverseScroll(false);
|
||||
assert_int_equal(mouse_scroll_dir, 1);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ static void * x52_notify_thr(void * param)
|
|||
char buffer[X52D_BUFSZ];
|
||||
uint16_t bufsiz;
|
||||
int rc;
|
||||
(void)param;
|
||||
|
||||
for (;;) {
|
||||
do {
|
||||
|
|
@ -146,6 +147,7 @@ static void * x52_notify_loop(void * param)
|
|||
{
|
||||
struct pollfd pfd[MAX_CONN];
|
||||
int rc;
|
||||
(void)param;
|
||||
|
||||
for (;;) {
|
||||
rc = x52d_client_poll(client_fd, pfd, notify_sock);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,16 @@
|
|||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifndef X52DCOMM_API
|
||||
# if defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 303
|
||||
# define X52DCOMM_API __attribute__((visibility("default")))
|
||||
# elif defined(_WIN32)
|
||||
# define X52DCOMM_API __declspec(dllexport)
|
||||
# else
|
||||
# define X52DCOMM_API
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
|
@ -52,7 +62,7 @@ extern "C" {
|
|||
*
|
||||
* @exception E2BIG returned if the passed socket path is too big
|
||||
*/
|
||||
int x52d_dial_command(const char *sock_path);
|
||||
X52DCOMM_API int x52d_dial_command(const char *sock_path);
|
||||
|
||||
/**
|
||||
* @brief Open a connection to the daemon notify socket.
|
||||
|
|
@ -73,7 +83,7 @@ int x52d_dial_command(const char *sock_path);
|
|||
*
|
||||
* @exception E2BIG returned if the passed socket path is too big
|
||||
*/
|
||||
int x52d_dial_notify(const char *sock_path);
|
||||
X52DCOMM_API int x52d_dial_notify(const char *sock_path);
|
||||
|
||||
/**
|
||||
* @brief Format a series of command strings into a buffer
|
||||
|
|
@ -92,7 +102,7 @@ int x52d_dial_notify(const char *sock_path);
|
|||
* @returns number of bytes in the formatted command
|
||||
* @returns -1 on an error condition, and \c errno is set accordingly.
|
||||
*/
|
||||
int x52d_format_command(int argc, const char **argv, char *buffer, size_t buflen);
|
||||
X52DCOMM_API int x52d_format_command(int argc, const char **argv, char *buffer, size_t buflen);
|
||||
|
||||
/**
|
||||
* @brief Send a command to the daemon and retrieve the response.
|
||||
|
|
@ -121,7 +131,7 @@ int x52d_format_command(int argc, const char **argv, char *buffer, size_t buflen
|
|||
* @returns number of bytes returned from the server
|
||||
* @returns -1 on an error condition, and \c errno is set accordingly.
|
||||
*/
|
||||
int x52d_send_command(int sock_fd, char *buffer, size_t bufin, size_t bufout);
|
||||
X52DCOMM_API int x52d_send_command(int sock_fd, char *buffer, size_t bufin, size_t bufout);
|
||||
|
||||
/**
|
||||
* @brief Notification callback function type
|
||||
|
|
@ -147,7 +157,7 @@ typedef int (* x52d_notify_callback_fn)(int argc, char **argv);
|
|||
* @returns return code of the callback function on success
|
||||
* @returns -1 on an error condition, and \c errno is set accordingly.
|
||||
*/
|
||||
int x52d_recv_notification(int sock_fd, x52d_notify_callback_fn callback);
|
||||
X52DCOMM_API int x52d_recv_notification(int sock_fd, x52d_notify_callback_fn callback);
|
||||
|
||||
/** @} */
|
||||
#ifdef __cplusplus
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
FROM archlinux:base-devel
|
||||
|
||||
LABEL org.opencontainers.image.description="INTERNAL CI USE ONLY - Not intended for production"
|
||||
LABEL org.opencontainers.image.authors="Nirenjan Krishnan"
|
||||
|
||||
COPY ./install-dependencies-archlinux.sh ./ci-setup.sh /tmp/
|
||||
|
||||
RUN /tmp/install-dependencies-archlinux.sh && /tmp/ci-setup.sh
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
FROM fedora:latest
|
||||
|
||||
LABEL org.opencontainers.image.description="INTERNAL CI USE ONLY - Not intended for production"
|
||||
LABEL org.opencontainers.image.authors="Nirenjan Krishnan"
|
||||
|
||||
COPY ./install-dependencies-fedora.sh ./ci-setup.sh /tmp/
|
||||
|
||||
RUN /tmp/install-dependencies-fedora.sh && dnf clean all && /tmp/ci-setup.sh
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
FROM ubuntu:22.04
|
||||
|
||||
LABEL org.opencontainers.image.description="INTERNAL CI USE ONLY - Not intended for production"
|
||||
LABEL org.opencontainers.image.authors="Nirenjan Krishnan"
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
COPY ./install-dependencies-ubuntu.sh ./ci-setup.sh /tmp/
|
||||
|
||||
RUN /tmp/install-dependencies-ubuntu.sh && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
/tmp/ci-setup.sh
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
FROM ubuntu:24.04
|
||||
|
||||
LABEL org.opencontainers.image.description="INTERNAL CI USE ONLY - Not intended for production"
|
||||
LABEL org.opencontainers.image.authors="Nirenjan Krishnan"
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
COPY ./install-dependencies-ubuntu.sh ./ci-setup.sh /tmp/
|
||||
|
||||
RUN /tmp/install-dependencies-ubuntu.sh && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
/tmp/ci-setup.sh
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
FROM ubuntu:26.04
|
||||
|
||||
LABEL org.opencontainers.image.description="INTERNAL CI USE ONLY - Not intended for production"
|
||||
LABEL org.opencontainers.image.authors="Nirenjan Krishnan"
|
||||
LABEL com.project.ci.experimental="true"
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
COPY ./install-dependencies-ubuntu.sh ./ci-setup.sh /tmp/
|
||||
|
||||
RUN /tmp/install-dependencies-ubuntu.sh && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
/tmp/ci-setup.sh
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
Dockerfiles for CI builds
|
||||
=========================
|
||||
|
||||
This repo contains a number of Dockerfiles for building libx52 against multiple
|
||||
distros. Github only supports Ubuntu LTS for it's Linux runners, but libx52
|
||||
users run multiple distros, including Arch Linux, Gentoo and Fedora.
|
||||
|
||||
For this reason, it's better to get a prebuilt Docker image that builds the CI
|
||||
environment, pulling in all the relevant dependencies, and staging the CI build
|
||||
environment as a container within Github. The regular build pipeline can then
|
||||
run on these containers, and this will help ensure that the breakages are
|
||||
reduced to a minimum.
|
||||
|
||||
Build Instructions
|
||||
==================
|
||||
|
||||
In order to run the CI build on all the supported platforms, first run the
|
||||
`build-containers.sh` script, it will find the Dockerfiles for all the
|
||||
suppported platforms in the `docker` directory, and build the container images
|
||||
for them with the necessary dependencies included.
|
||||
|
||||
Once the container images are built, you can build against one or all of the
|
||||
container images by running `build-repo.sh`. If you don't specify the container
|
||||
distro, which is basically the same as the extension after `Dockerfile.`, it
|
||||
will run everything. If you specify a distro that doesn't exist, or has not been
|
||||
built, the script will silently exit.
|
||||
|
||||
Extending to a new distro
|
||||
=========================
|
||||
|
||||
To extend the builds to a new distro, create a `Dockerfile.<distro>` with the
|
||||
necessary instructions to build a container image for that distro. Make sure you
|
||||
install the necessary dependencies for the distro. It is strongly recommended to
|
||||
add a `install-dependencies-<distro>.sh` so that you can build against multiple
|
||||
versions of that distro, eg. Ubuntu 22.04 and Ubuntu 24.04. Make sure you copy
|
||||
`ci-setup.sh` to allow setting up the environment, since it is highly likely
|
||||
that the `meson dist` command will fail if you do not run that script.
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
#!/bin/bash
|
||||
# Build containers for all the target environments
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR=$(dirname "$0")
|
||||
if command -v realpath &>/dev/null
|
||||
then
|
||||
SCRIPT_DIR=$(realpath "$SCRIPT_DIR")
|
||||
fi
|
||||
|
||||
GITHUB_SCRIPTS_DIR="$SCRIPT_DIR/../.github/scripts"
|
||||
if command -v realpath &>/dev/null
|
||||
then
|
||||
GITHUB_SCRIPTS_DIR=$(realpath "$GITHUB_SCRIPTS_DIR")
|
||||
fi
|
||||
|
||||
for dockerfile in "$SCRIPT_DIR"/[Dd]ockerfile.*
|
||||
do
|
||||
if [[ -e "$dockerfile" ]]
|
||||
then
|
||||
SUFFIX="$(basename "$dockerfile" | cut -d. -f2)"
|
||||
TAG="ghcr.io/nirenjan/libx52/ci-build-${SUFFIX}:latest"
|
||||
docker build --tag "$TAG" -f "$dockerfile" "${SCRIPT_DIR}"
|
||||
fi
|
||||
done
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
#!/bin/bash
|
||||
# Build the repository for all found directories
|
||||
# Build containers for all the target environments
|
||||
set -euo pipefail
|
||||
|
||||
GIT_ROOT=$(git rev-parse --show-toplevel)
|
||||
CC=${CC:-gcc}
|
||||
|
||||
IMAGE="${1:-*}"
|
||||
|
||||
for image in $(docker images --filter "reference=ghcr.io/nirenjan/libx52/ci-build-${IMAGE}" --format '{{ .Repository}}')
|
||||
do
|
||||
distro=${image##*/ci-build-}
|
||||
container_name="libx52-runner-${distro}"
|
||||
|
||||
if [[ "$(docker ps -aq -f name=$container_name)" ]]
|
||||
then
|
||||
echo "Cleaning up old container for '$distro'"
|
||||
|
||||
docker rm -f $container_name >/dev/null
|
||||
fi
|
||||
|
||||
experimental=$(docker inspect --format='{{.Config.Labels}}' $image | \
|
||||
grep -q 'experimental:true' && echo " (experimental)" || true)
|
||||
|
||||
if docker run --rm --name $container_name \
|
||||
--device /dev/bus/usb:/dev/bus/usb \
|
||||
-v "$GIT_ROOT":/code \
|
||||
-w /code \
|
||||
-e CC="${CC}" \
|
||||
$image \
|
||||
/bin/bash -c ".github/scripts/build-and-test.sh builddir/${distro}" \
|
||||
&> "$GIT_ROOT/build-${distro}.log"
|
||||
then
|
||||
echo "=== ${distro}${experimental} OK ==="
|
||||
else
|
||||
echo "=== ${distro}${experimental} !!! FAIL !!! ==="
|
||||
tail -20 "$GIT_ROOT/build-${distro}.log"
|
||||
fi
|
||||
done
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
#!/bin/sh
|
||||
# Common CI setup
|
||||
|
||||
git config --global --add safe.directory /code
|
||||
git config --global --add safe.directory '*'
|
||||
|
||||
cat >> /root/.bashrc <<"EOF"
|
||||
echo 'WARNING: This is an internal CI container'
|
||||
echo 'Do not use for production'
|
||||
EOF
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# This file is deliberately named with a lowercase d, in order to avoid the
|
||||
# Github action logic from picking this up as a supported distro. The Alpine
|
||||
# image fails in the Github actions, because it needs the /dev/bus/usb device
|
||||
# mounted inside the container, however, attempting to do so causes every
|
||||
# build to fail. Therefore, we disable the Alpine image in CI, but keep it
|
||||
# local, so that we can test the build against Alpine locally if necessary.
|
||||
FROM alpine:latest
|
||||
|
||||
LABEL org.opencontainers.image.description="INTERNAL CI USE ONLY - Not intended for production"
|
||||
LABEL org.opencontainers.image.authors="Nirenjan Krishnan"
|
||||
LABEL com.project.ci.experimental="true"
|
||||
|
||||
COPY ./install-dependencies-alpine.sh ./ci-setup.sh /tmp/
|
||||
RUN /tmp/install-dependencies-alpine.sh
|
||||
RUN /tmp/ci-setup.sh
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
#!/bin/sh
|
||||
# Setup for alpine
|
||||
set -eux
|
||||
|
||||
apk update
|
||||
apk add --no-cache \
|
||||
build-base \
|
||||
meson \
|
||||
bash \
|
||||
git \
|
||||
gettext \
|
||||
libusb-dev \
|
||||
hidapi-dev \
|
||||
libevdev-dev \
|
||||
inih-dev \
|
||||
cmocka-dev \
|
||||
tzdata \
|
||||
musl-libintl \
|
||||
doxygen
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
#!/bin/bash
|
||||
# Install dependencies on Archlinux container
|
||||
# Assumes that it's running off a base-devel tag
|
||||
set -euo pipefail
|
||||
|
||||
pacman -Syu --noconfirm \
|
||||
git \
|
||||
meson \
|
||||
libusb \
|
||||
hidapi \
|
||||
libinih \
|
||||
libevdev \
|
||||
python \
|
||||
gettext
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
#!/bin/bash
|
||||
# Install dependencies on Fedora container
|
||||
set -euo pipefail
|
||||
|
||||
dnf update -y
|
||||
dnf install -y \
|
||||
gcc \
|
||||
git \
|
||||
meson \
|
||||
libusb1-devel \
|
||||
hidapi-devel \
|
||||
inih-devel \
|
||||
libevdev-devel \
|
||||
pkg-config \
|
||||
python3 \
|
||||
gettext-devel
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
#!/bin/bash
|
||||
# Install dependencies to build and test on Ubuntu runners
|
||||
apt-get update && apt-get upgrade -y
|
||||
apt-get install -y \
|
||||
git \
|
||||
gcc clang \
|
||||
meson \
|
||||
gettext \
|
||||
pkg-config \
|
||||
python3 \
|
||||
libusb-1.0-0-dev \
|
||||
libhidapi-dev \
|
||||
libevdev-dev \
|
||||
libinih-dev \
|
||||
doxygen \
|
||||
libcmocka-dev \
|
||||
tzdata
|
||||
|
||||
exit 0
|
||||
|
|
@ -48,5 +48,7 @@ functions.
|
|||
- libx52.h
|
||||
- libx52io.h
|
||||
- libx52util.h
|
||||
- vkm.h
|
||||
- x52dcomm.h
|
||||
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ static bool exit_loop = false;
|
|||
|
||||
static void signal_handler(int sig)
|
||||
{
|
||||
(void)sig;
|
||||
exit_loop = true;
|
||||
}
|
||||
|
||||
|
|
@ -52,7 +53,7 @@ static bool denoise = true;
|
|||
|
||||
/* For i18n */
|
||||
#define _(x) gettext(x)
|
||||
int main(int argc, char **argv)
|
||||
int main(void)
|
||||
{
|
||||
libx52io_context *ctx;
|
||||
libx52io_report last, curr;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
#!/bin/sh
|
||||
# Install Doxygen HTML and man trees from the build directory.
|
||||
# Arguments are paths relative to the install prefix (MESON_INSTALL_DESTDIR_PREFIX).
|
||||
set -e
|
||||
|
||||
doc_html="$1"
|
||||
mandir="$2"
|
||||
|
||||
WANTED_PAGES="man1/x52cli.1 man1/x52bugreport.1"
|
||||
|
||||
if [ -d "$MESON_BUILD_ROOT/docs/html" ]; then
|
||||
mkdir -p "$MESON_INSTALL_DESTDIR_PREFIX/$doc_html"
|
||||
cp -R "$MESON_BUILD_ROOT/docs/html"/. "$MESON_INSTALL_DESTDIR_PREFIX/$doc_html/"
|
||||
fi
|
||||
|
||||
if [ -d "$MESON_BUILD_ROOT/docs/man" ]; then
|
||||
MANDIR="$MESON_INSTALL_DESTDIR_PREFIX/$mandir"
|
||||
mkdir -p "$MANDIR"
|
||||
for manpage in $WANTED_PAGES
|
||||
do
|
||||
section=$(dirname "$manpage")
|
||||
mkdir -p "$MANDIR/$section"
|
||||
cp "$MESON_BUILD_ROOT/docs/man/$manpage" "$MANDIR/$section"
|
||||
done
|
||||
fi
|
||||
|
|
@ -24,6 +24,7 @@ libx52_la_CFLAGS = \
|
|||
@LIBUSB_CFLAGS@ \
|
||||
-DLOCALEDIR=\"$(localedir)\" \
|
||||
-I $(top_srcdir) \
|
||||
$(VISIBILITY_CFLAGS) \
|
||||
$(WARN_CFLAGS)
|
||||
libx52_la_LDFLAGS = \
|
||||
-export-symbols-regex '^libx52_' \
|
||||
|
|
|
|||
|
|
@ -24,6 +24,17 @@
|
|||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/** Applied to public library entry points (default visibility with hidden ELF default). */
|
||||
#ifndef LIBX52_API
|
||||
# if defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 303
|
||||
# define LIBX52_API __attribute__((visibility("default")))
|
||||
# elif defined(_WIN32)
|
||||
# define LIBX52_API __declspec(dllexport)
|
||||
# else
|
||||
# define LIBX52_API
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
|
@ -275,7 +286,7 @@ typedef enum {
|
|||
*
|
||||
* @returns \ref libx52_error_code indicating status
|
||||
*/
|
||||
int libx52_init(libx52_device ** dev);
|
||||
LIBX52_API int libx52_init(libx52_device ** dev);
|
||||
|
||||
/**
|
||||
* @brief Exit the library and free up any resources used
|
||||
|
|
@ -286,7 +297,7 @@ int libx52_init(libx52_device ** dev);
|
|||
*
|
||||
* @param[in] dev Pointer to the device context
|
||||
*/
|
||||
void libx52_exit(libx52_device *dev);
|
||||
LIBX52_API void libx52_exit(libx52_device *dev);
|
||||
|
||||
/** @} */
|
||||
|
||||
|
|
@ -316,7 +327,7 @@ void libx52_exit(libx52_device *dev);
|
|||
*
|
||||
* @returns \ref libx52_error_code indicating status
|
||||
*/
|
||||
int libx52_connect(libx52_device *dev);
|
||||
LIBX52_API int libx52_connect(libx52_device *dev);
|
||||
|
||||
/**
|
||||
* @brief Disconnect from the X52 device
|
||||
|
|
@ -329,7 +340,7 @@ int libx52_connect(libx52_device *dev);
|
|||
*
|
||||
* @returns \ref libx52_error_code indicating status
|
||||
*/
|
||||
int libx52_disconnect(libx52_device *dev);
|
||||
LIBX52_API int libx52_disconnect(libx52_device *dev);
|
||||
|
||||
/**
|
||||
* @brief Check if joystick is connected
|
||||
|
|
@ -350,7 +361,7 @@ int libx52_disconnect(libx52_device *dev);
|
|||
*
|
||||
* @returns Boolean indicating if the internal device handle is valid.
|
||||
*/
|
||||
bool libx52_is_connected(libx52_device *dev);
|
||||
LIBX52_API bool libx52_is_connected(libx52_device *dev);
|
||||
|
||||
/** @} */
|
||||
|
||||
|
|
@ -390,7 +401,7 @@ bool libx52_is_connected(libx52_device *dev);
|
|||
* - \ref LIBX52_ERROR_INVALID_PARAM if either \p x52 is invalid, or \p line is
|
||||
* outside the accepted range.
|
||||
*/
|
||||
int libx52_set_text(libx52_device *x52, uint8_t line, const char *text, uint8_t length);
|
||||
LIBX52_API int libx52_set_text(libx52_device *x52, uint8_t line, const char *text, uint8_t length);
|
||||
|
||||
/**
|
||||
* @brief Set the LED state
|
||||
|
|
@ -415,7 +426,7 @@ int libx52_set_text(libx52_device *x52, uint8_t line, const char *text, uint8_t
|
|||
* not a supported one. The API also returns \ref LIBX52_ERROR_NOT_SUPPORTED
|
||||
* if the probed joystick is not an X52 Pro, but the non-Pro X52 variant.
|
||||
*/
|
||||
int libx52_set_led_state(libx52_device *x52,
|
||||
LIBX52_API int libx52_set_led_state(libx52_device *x52,
|
||||
libx52_led_id led,
|
||||
libx52_led_state state);
|
||||
|
||||
|
|
@ -455,7 +466,7 @@ int libx52_set_led_state(libx52_device *x52,
|
|||
* - \ref LIBX52_ERROR_TRY_AGAIN if no change from previous time
|
||||
* - \ref LIBX52_ERROR_INVALID_PARAM if \p x52 is not valid.
|
||||
*/
|
||||
int libx52_set_clock(libx52_device *x52, time_t time, int local);
|
||||
LIBX52_API int libx52_set_clock(libx52_device *x52, time_t time, int local);
|
||||
|
||||
/**
|
||||
* @brief Set the timezone for the secondary and tertiary clocks.
|
||||
|
|
@ -487,7 +498,7 @@ int libx52_set_clock(libx52_device *x52, time_t time, int local);
|
|||
* - \ref LIBX52_ERROR_NOT_SUPPORTED if \p clock is \ref LIBX52_CLOCK_1
|
||||
* - \ref LIBX52_ERROR_OUT_OF_RANGE if \p offset is more than ± 24 hours.
|
||||
*/
|
||||
int libx52_set_clock_timezone(libx52_device *x52,
|
||||
LIBX52_API int libx52_set_clock_timezone(libx52_device *x52,
|
||||
libx52_clock_id clock,
|
||||
int offset);
|
||||
|
||||
|
|
@ -511,7 +522,7 @@ int libx52_set_clock_timezone(libx52_device *x52,
|
|||
* - \ref LIBX52_ERROR_INVALID_PARAM if \p x52 is not valid, or if either of \p
|
||||
* clock or \p format are outside their respective ranges.
|
||||
*/
|
||||
int libx52_set_clock_format(libx52_device *x52,
|
||||
LIBX52_API int libx52_set_clock_format(libx52_device *x52,
|
||||
libx52_clock_id clock,
|
||||
libx52_clock_format format);
|
||||
|
||||
|
|
@ -530,7 +541,7 @@ int libx52_set_clock_format(libx52_device *x52,
|
|||
* - 0 on success
|
||||
* - \ref LIBX52_ERROR_INVALID_PARAM if \p x52 is not valid
|
||||
*/
|
||||
int libx52_set_time(libx52_device *x52, uint8_t hour, uint8_t minute);
|
||||
LIBX52_API int libx52_set_time(libx52_device *x52, uint8_t hour, uint8_t minute);
|
||||
|
||||
/**
|
||||
* @brief Set the date
|
||||
|
|
@ -547,7 +558,7 @@ int libx52_set_time(libx52_device *x52, uint8_t hour, uint8_t minute);
|
|||
* - 0 on success
|
||||
* - \ref LIBX52_ERROR_INVALID_PARAM if \p x52 is not valid
|
||||
*/
|
||||
int libx52_set_date(libx52_device *x52, uint8_t dd, uint8_t mm, uint8_t yy);
|
||||
LIBX52_API int libx52_set_date(libx52_device *x52, uint8_t dd, uint8_t mm, uint8_t yy);
|
||||
|
||||
/**
|
||||
* @brief Set the date format for the MFD date display
|
||||
|
|
@ -561,7 +572,7 @@ int libx52_set_date(libx52_device *x52, uint8_t dd, uint8_t mm, uint8_t yy);
|
|||
* - 0 on success
|
||||
* - \ref LIBX52_ERROR_INVALID_PARAM if \p x52 is not valid
|
||||
*/
|
||||
int libx52_set_date_format(libx52_device *x52, libx52_date_format format);
|
||||
LIBX52_API int libx52_set_date_format(libx52_device *x52, libx52_date_format format);
|
||||
|
||||
/** @} */
|
||||
|
||||
|
|
@ -586,7 +597,7 @@ int libx52_set_date_format(libx52_device *x52, libx52_date_format format);
|
|||
* - 0 on success
|
||||
* - \ref LIBX52_ERROR_INVALID_PARAM if \p x52 is not valid
|
||||
*/
|
||||
int libx52_set_brightness(libx52_device *x52, uint8_t mfd, uint16_t brightness);
|
||||
LIBX52_API int libx52_set_brightness(libx52_device *x52, uint8_t mfd, uint16_t brightness);
|
||||
|
||||
/**
|
||||
* @brief Set the state of the shift indicator
|
||||
|
|
@ -601,7 +612,7 @@ int libx52_set_brightness(libx52_device *x52, uint8_t mfd, uint16_t brightness);
|
|||
* - 0 on success
|
||||
* - \ref LIBX52_ERROR_INVALID_PARAM if \p x52 is not valid
|
||||
*/
|
||||
int libx52_set_shift(libx52_device *x52, uint8_t state);
|
||||
LIBX52_API int libx52_set_shift(libx52_device *x52, uint8_t state);
|
||||
|
||||
/**
|
||||
* @brief Set the blinking state
|
||||
|
|
@ -615,7 +626,7 @@ int libx52_set_shift(libx52_device *x52, uint8_t state);
|
|||
* - 0 on success
|
||||
* - \ref LIBX52_ERROR_INVALID_PARAM if \p x52 is not valid
|
||||
*/
|
||||
int libx52_set_blink(libx52_device *x52, uint8_t state);
|
||||
LIBX52_API int libx52_set_blink(libx52_device *x52, uint8_t state);
|
||||
|
||||
/** @} */
|
||||
|
||||
|
|
@ -638,7 +649,7 @@ int libx52_set_blink(libx52_device *x52, uint8_t state);
|
|||
*
|
||||
* @returns \ref libx52_error_code indicating status
|
||||
*/
|
||||
int libx52_update(libx52_device *x52);
|
||||
LIBX52_API int libx52_update(libx52_device *x52);
|
||||
|
||||
/**
|
||||
* @brief Write a raw vendor control packet
|
||||
|
|
@ -655,7 +666,7 @@ int libx52_update(libx52_device *x52);
|
|||
*
|
||||
* @returns \ref libx52_error_code indicating status
|
||||
*/
|
||||
int libx52_vendor_command(libx52_device *x52, uint16_t index, uint16_t value);
|
||||
LIBX52_API int libx52_vendor_command(libx52_device *x52, uint16_t index, uint16_t value);
|
||||
|
||||
/**
|
||||
* @brief Check if the device supports the given feature.
|
||||
|
|
@ -670,7 +681,7 @@ int libx52_vendor_command(libx52_device *x52, uint16_t index, uint16_t value);
|
|||
*
|
||||
* @returns \ref libx52_error_code indicating status
|
||||
*/
|
||||
int libx52_check_feature(libx52_device *x52, libx52_feature feature);
|
||||
LIBX52_API int libx52_check_feature(libx52_device *x52, libx52_feature feature);
|
||||
|
||||
/** @} */
|
||||
|
||||
|
|
@ -690,7 +701,7 @@ int libx52_check_feature(libx52_device *x52, libx52_feature feature);
|
|||
* @returns Pointer to a NULL terminated string describing the error.
|
||||
* Returned pointer must not be freed.
|
||||
*/
|
||||
const char * libx52_strerror(libx52_error_code error);
|
||||
LIBX52_API const char *libx52_strerror(libx52_error_code error);
|
||||
|
||||
/**
|
||||
* @brief Returns a string representation of the clock ID
|
||||
|
|
@ -700,7 +711,7 @@ const char * libx52_strerror(libx52_error_code error);
|
|||
* @returns Pointer to a NULL terminated string describing the clock ID.
|
||||
* Returned pointer must not be freed.
|
||||
*/
|
||||
const char * libx52_clock_id_to_str(libx52_clock_id id);
|
||||
LIBX52_API const char *libx52_clock_id_to_str(libx52_clock_id id);
|
||||
|
||||
/**
|
||||
* @brief Returns a string representation of the clock format
|
||||
|
|
@ -710,7 +721,7 @@ const char * libx52_clock_id_to_str(libx52_clock_id id);
|
|||
* @returns Pointer to a NULL terminated string describing the clock format.
|
||||
* Returned pointer must not be freed.
|
||||
*/
|
||||
const char * libx52_clock_format_to_str(libx52_clock_format format);
|
||||
LIBX52_API const char *libx52_clock_format_to_str(libx52_clock_format format);
|
||||
|
||||
/**
|
||||
* @brief Returns a string representation of the date format
|
||||
|
|
@ -720,7 +731,7 @@ const char * libx52_clock_format_to_str(libx52_clock_format format);
|
|||
* @returns Pointer to a NULL terminated string describing the date format.
|
||||
* Returned pointer must not be freed.
|
||||
*/
|
||||
const char * libx52_date_format_to_str(libx52_date_format format);
|
||||
LIBX52_API const char *libx52_date_format_to_str(libx52_date_format format);
|
||||
|
||||
/**
|
||||
* @brief Returns a string representation of the LED
|
||||
|
|
@ -730,7 +741,7 @@ const char * libx52_date_format_to_str(libx52_date_format format);
|
|||
* @returns Pointer to a NULL terminated string describing the LED.
|
||||
* Returned pointer must not be freed.
|
||||
*/
|
||||
const char * libx52_led_id_to_str(libx52_led_id id);
|
||||
LIBX52_API const char *libx52_led_id_to_str(libx52_led_id id);
|
||||
|
||||
/**
|
||||
* @brief Returns a string representation of the LED state
|
||||
|
|
@ -740,7 +751,7 @@ const char * libx52_led_id_to_str(libx52_led_id id);
|
|||
* @returns Pointer to a NULL terminated string describing the LED state.
|
||||
* Returned pointer must not be freed.
|
||||
*/
|
||||
const char * libx52_led_state_to_str(libx52_led_state state);
|
||||
LIBX52_API const char *libx52_led_state_to_str(libx52_led_state state);
|
||||
|
||||
/** @} */
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
libx52_version = '2.4.2'
|
||||
|
||||
libx52_files = files(
|
||||
'x52_control.c',
|
||||
'x52_core.c',
|
||||
|
|
@ -9,12 +11,17 @@ libx52_files = files(
|
|||
|
||||
lib_libx52 = library('x52', libx52_files,
|
||||
install: true,
|
||||
version: '2.4.2',
|
||||
version: libx52_version,
|
||||
c_args: sym_hidden_cargs,
|
||||
dependencies: [dep_libusb, dep_intl],
|
||||
include_directories: [includes])
|
||||
|
||||
install_headers('libx52.h', subdir: meson.project_name())
|
||||
pkgconfig.generate(lib_libx52)
|
||||
pkgconfig.generate(lib_libx52,
|
||||
name: 'libx52',
|
||||
description: 'Linux/Unix library to control Saitek X52/X52Pro joystick extended functionality.',
|
||||
subdirs: meson.project_name(),
|
||||
version: libx52_version)
|
||||
|
||||
# Unit tests for libx52
|
||||
libx52_string_test = executable('libx52-string-test',
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
#define TEST_STRINGIFY(name) do { \
|
||||
char expected[256]; \
|
||||
for (int i=-1; i < sizeof(name ## _map) / sizeof(name ## _map[0]); i++) { \
|
||||
for (int i=-1; i < (int)(sizeof(name ## _map) / sizeof(name ## _map[0])); i++) { \
|
||||
if (i < 0) { \
|
||||
snprintf(expected, sizeof(expected), unknown_fmt, i); \
|
||||
} else if (name ## _map[i] != NULL) { \
|
||||
|
|
@ -31,6 +31,8 @@
|
|||
|
||||
static void test_led_id_names(void **state)
|
||||
{
|
||||
(void)state; // Suppress unused parameter warning
|
||||
|
||||
static const char * led_id_map[21] = {
|
||||
[LIBX52_LED_FIRE] = "Fire",
|
||||
[LIBX52_LED_A] = "A",
|
||||
|
|
@ -52,6 +54,8 @@ static void test_led_id_names(void **state)
|
|||
|
||||
static void test_led_state_names(void **state)
|
||||
{
|
||||
(void)state; // Suppress unused parameter warning
|
||||
|
||||
static const char * led_state_map[6] = {
|
||||
[LIBX52_LED_STATE_OFF] = "off",
|
||||
[LIBX52_LED_STATE_ON] = "on",
|
||||
|
|
@ -66,6 +70,8 @@ static void test_led_state_names(void **state)
|
|||
}
|
||||
|
||||
static void test_clock_id_names(void **state) {
|
||||
(void)state; // Suppress unused parameter warning
|
||||
|
||||
static const char * clock_id_map[4] = {
|
||||
[LIBX52_CLOCK_1] = "primary",
|
||||
[LIBX52_CLOCK_2] = "secondary",
|
||||
|
|
@ -78,6 +84,8 @@ static void test_clock_id_names(void **state) {
|
|||
}
|
||||
|
||||
static void test_clock_format_names(void **state) {
|
||||
(void)state; // Suppress unused parameter warning
|
||||
|
||||
static const char * clock_format_map[3] = {
|
||||
[LIBX52_CLOCK_FORMAT_12HR] = "12 hour",
|
||||
[LIBX52_CLOCK_FORMAT_24HR] = "24 hour",
|
||||
|
|
@ -89,6 +97,8 @@ static void test_clock_format_names(void **state) {
|
|||
}
|
||||
|
||||
static void test_date_format_names(void **state) {
|
||||
(void)state; // Suppress unused parameter warning
|
||||
|
||||
static const char * date_format_map[4] = {
|
||||
[LIBX52_DATE_FORMAT_DDMMYY] = "DD-MM-YY",
|
||||
[LIBX52_DATE_FORMAT_MMDDYY] = "MM-DD-YY",
|
||||
|
|
@ -103,6 +113,8 @@ static void test_date_format_names(void **state) {
|
|||
#define libx52_error_to_str libx52_strerror
|
||||
|
||||
static void test_strerror(void **state) {
|
||||
(void)state; // Suppress unused parameter warning
|
||||
|
||||
static const char *error_map[18] = {
|
||||
[LIBX52_SUCCESS] = "Success",
|
||||
[LIBX52_ERROR_INIT_FAILURE] = "Initialization failure",
|
||||
|
|
|
|||
|
|
@ -103,6 +103,8 @@ int libx52_vendor_command(libx52_device *x52, uint16_t index, uint16_t value)
|
|||
static int _x52_write_shift(libx52_device *x52, uint32_t bit)
|
||||
{
|
||||
uint16_t value;
|
||||
(void)bit;
|
||||
|
||||
value = tst_bit(&x52->led_mask, X52_BIT_SHIFT) ? X52_SHIFT_ON : X52_SHIFT_OFF;
|
||||
return libx52_vendor_command(x52, X52_SHIFT_INDICATOR, value);
|
||||
}
|
||||
|
|
@ -152,6 +154,7 @@ static int _x52_write_line(libx52_device *x52, uint32_t bit)
|
|||
static int _x52_write_pov_blink(libx52_device *x52, uint32_t bit)
|
||||
{
|
||||
uint16_t value;
|
||||
(void)bit;
|
||||
value = tst_bit(&x52->led_mask, X52_BIT_POV_BLINK) ? X52_BLINK_ON : X52_BLINK_OFF;
|
||||
return libx52_vendor_command(x52, X52_BLINK_INDICATOR, value);
|
||||
}
|
||||
|
|
@ -177,6 +180,7 @@ static int _x52_write_date(libx52_device *x52, uint32_t bit)
|
|||
uint16_t value1; //dd-mm
|
||||
uint16_t value2; //yy
|
||||
int rc;
|
||||
(void)bit;
|
||||
|
||||
switch (x52->date_format) {
|
||||
case LIBX52_DATE_FORMAT_YYMMDD:
|
||||
|
|
@ -288,7 +292,7 @@ static int _x52_write_time(libx52_device *x52, uint32_t bit)
|
|||
|
||||
typedef int (*x52_handler)(libx52_device *, uint32_t);
|
||||
|
||||
const x52_handler _x52_handlers[32] = {
|
||||
static const x52_handler _x52_handlers[32] = {
|
||||
[X52_BIT_SHIFT] = _x52_write_shift,
|
||||
[X52_BIT_LED_FIRE] = _x52_write_led,
|
||||
[X52_BIT_LED_A_RED] = _x52_write_led,
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@ static int _x52_hotplug_callback(libusb_context *ctx,
|
|||
{
|
||||
libx52_device *dev = user_data;
|
||||
|
||||
(void)device; // Suppress unused parameter warning
|
||||
|
||||
if (dev == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -229,6 +231,8 @@ int libx52_init(libx52_device **dev)
|
|||
|
||||
void libx52_exit(libx52_device *dev)
|
||||
{
|
||||
volatile unsigned char *vp;
|
||||
|
||||
if (!dev) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -237,7 +241,10 @@ void libx52_exit(libx52_device *dev)
|
|||
libusb_exit(dev->ctx);
|
||||
|
||||
/* Clear the memory to prevent reuse */
|
||||
memset(dev, 0, sizeof(*dev));
|
||||
vp = (volatile unsigned char *)dev;
|
||||
for (int i = 0; i < sizeof(*dev); i++) {
|
||||
vp[i] = (unsigned char)0;
|
||||
}
|
||||
|
||||
free(dev);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ static const char *error_string[] = {
|
|||
N_("System call interrupted"),
|
||||
};
|
||||
|
||||
const char * libx52_strerror(libx52_error_code error)
|
||||
LIBX52_API const char * libx52_strerror(libx52_error_code error)
|
||||
{
|
||||
switch (error) {
|
||||
case LIBX52_SUCCESS:
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
#define _(str) dgettext(PACKAGE, str)
|
||||
|
||||
#define STRINGIFY(name, max_id, errstr, ...) \
|
||||
const char * libx52_ ## name ## _to_str (libx52_ ## name param) { \
|
||||
LIBX52_API const char * libx52_ ## name ## _to_str (libx52_ ## name param) { \
|
||||
static char invalid[256]; \
|
||||
static const char *desc[] = { __VA_ARGS__ }; \
|
||||
if (param >= 0 && param <= max_id) { \
|
||||
|
|
@ -51,7 +51,7 @@ STRINGIFY(led_state, LIBX52_LED_STATE_GREEN, N_("Unknown LED state %d"),
|
|||
N_("green"),
|
||||
)
|
||||
|
||||
const char * libx52_led_id_to_str(libx52_led_id id)
|
||||
LIBX52_API const char * libx52_led_id_to_str(libx52_led_id id)
|
||||
{
|
||||
static char invalid[256];
|
||||
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ int __wrap_libusb_control_transfer(libusb_device_handle *dev_handle,
|
|||
uint16_t wLength,
|
||||
unsigned int timeout)
|
||||
{
|
||||
(void)dev_handle;
|
||||
function_called();
|
||||
check_expected(wIndex);
|
||||
check_expected(wValue);
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ libx52io_la_SOURCES = \
|
|||
libx52io/io_parser.c \
|
||||
libx52io/io_strings.c \
|
||||
libx52io/io_device.c
|
||||
libx52io_la_CFLAGS = @HIDAPI_CFLAGS@ -DLOCALEDIR=\"$(localedir)\" -I $(top_srcdir) $(WARN_CFLAGS)
|
||||
libx52io_la_CFLAGS = @HIDAPI_CFLAGS@ -DLOCALEDIR=\"$(localedir)\" -I $(top_srcdir) $(VISIBILITY_CFLAGS) $(WARN_CFLAGS)
|
||||
libx52io_la_LDFLAGS = \
|
||||
-export-symbols-regex '^libx52io_' \
|
||||
-version-info $(libx52io_v_CUR):$(libx52io_v_REV):$(libx52io_v_AGE) @HIDAPI_LIBS@ \
|
||||
|
|
|
|||
|
|
@ -23,6 +23,16 @@
|
|||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifndef LIBX52IO_API
|
||||
# if defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 303
|
||||
# define LIBX52IO_API __attribute__((visibility("default")))
|
||||
# elif defined(_WIN32)
|
||||
# define LIBX52IO_API __declspec(dllexport)
|
||||
# else
|
||||
# define LIBX52IO_API
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
|
@ -293,7 +303,7 @@ typedef struct libx52io_report libx52io_report;
|
|||
*
|
||||
* @returns \c libx52io_error_code indicating status
|
||||
*/
|
||||
int libx52io_init(libx52io_context **ctx);
|
||||
LIBX52IO_API int libx52io_init(libx52io_context **ctx);
|
||||
|
||||
/**
|
||||
* @brief Exit the library and free up any resources used
|
||||
|
|
@ -304,7 +314,7 @@ int libx52io_init(libx52io_context **ctx);
|
|||
*
|
||||
* @param[in] ctx Pointer to the device context
|
||||
*/
|
||||
void libx52io_exit(libx52io_context *ctx);
|
||||
LIBX52IO_API void libx52io_exit(libx52io_context *ctx);
|
||||
|
||||
/**
|
||||
* @brief Open a connection to a supported joystick
|
||||
|
|
@ -321,7 +331,7 @@ void libx52io_exit(libx52io_context *ctx);
|
|||
* - \ref LIBX52IO_ERROR_NO_DEVICE if no supported joystick is found
|
||||
* - \ref LIBX52IO_ERROR_CONN if the connection fails
|
||||
*/
|
||||
int libx52io_open(libx52io_context *ctx);
|
||||
LIBX52IO_API int libx52io_open(libx52io_context *ctx);
|
||||
|
||||
/**
|
||||
* @brief Close an existing connection to a supported joystick
|
||||
|
|
@ -335,7 +345,7 @@ int libx52io_open(libx52io_context *ctx);
|
|||
* - \ref LIBX52IO_SUCCESS on closing, or if the connection is already closed.
|
||||
* - \ref LIBX52IO_ERROR_INVALID if the context pointer is not valid
|
||||
*/
|
||||
int libx52io_close(libx52io_context *ctx);
|
||||
LIBX52IO_API int libx52io_close(libx52io_context *ctx);
|
||||
|
||||
/**
|
||||
* @brief Read and parse a HID report
|
||||
|
|
@ -356,7 +366,7 @@ int libx52io_close(libx52io_context *ctx);
|
|||
* including if the device was disconnected during the read.
|
||||
* - \ref LIBX52IO_ERROR_TIMEOUT if no report was read before timeout.
|
||||
*/
|
||||
int libx52io_read_timeout(libx52io_context *ctx, libx52io_report *report, int timeout);
|
||||
LIBX52IO_API int libx52io_read_timeout(libx52io_context *ctx, libx52io_report *report, int timeout);
|
||||
|
||||
/**
|
||||
* @brief Read and parse a HID report
|
||||
|
|
@ -374,7 +384,7 @@ int libx52io_read_timeout(libx52io_context *ctx, libx52io_report *report, int ti
|
|||
* - \ref LIBX52IO_ERROR_IO if there was an error reading from the device,
|
||||
* including if the device was disconnected during the read.
|
||||
*/
|
||||
int libx52io_read(libx52io_context *ctx, libx52io_report *report);
|
||||
LIBX52IO_API int libx52io_read(libx52io_context *ctx, libx52io_report *report);
|
||||
|
||||
/**
|
||||
* @brief Retrieve the range of an axis
|
||||
|
|
@ -393,7 +403,7 @@ int libx52io_read(libx52io_context *ctx, libx52io_report *report);
|
|||
* valid, or the requested axis is not a valid axis identifier
|
||||
* - \ref LIBX52IO_ERROR_NO_DEVICE if the device is disconnected
|
||||
*/
|
||||
int libx52io_get_axis_range(libx52io_context *ctx, libx52io_axis axis, int32_t *min, int32_t *max);
|
||||
LIBX52IO_API int libx52io_get_axis_range(libx52io_context *ctx, libx52io_axis axis, int32_t *min, int32_t *max);
|
||||
|
||||
/**
|
||||
* @brief Get the string representation of an error code
|
||||
|
|
@ -402,7 +412,7 @@ int libx52io_get_axis_range(libx52io_context *ctx, libx52io_axis axis, int32_t *
|
|||
*
|
||||
* @returns String representation of the error. This pointer must not be freed.
|
||||
*/
|
||||
const char * libx52io_strerror(libx52io_error_code code);
|
||||
LIBX52IO_API const char *libx52io_strerror(libx52io_error_code code);
|
||||
|
||||
/**
|
||||
* @brief Get the string representation of an axis.
|
||||
|
|
@ -412,7 +422,7 @@ const char * libx52io_strerror(libx52io_error_code code);
|
|||
* @returns String representation of the axis. This pointer must not be freed.
|
||||
* If axis is outside the defined range, then this returns NULL.
|
||||
*/
|
||||
const char * libx52io_axis_to_str(libx52io_axis axis);
|
||||
LIBX52IO_API const char *libx52io_axis_to_str(libx52io_axis axis);
|
||||
|
||||
/**
|
||||
* @brief Get the string representation of a button.
|
||||
|
|
@ -422,7 +432,7 @@ const char * libx52io_axis_to_str(libx52io_axis axis);
|
|||
* @returns String representation of the button. This pointer must not be freed.
|
||||
* If button is outside the defined range, then this returns NULL.
|
||||
*/
|
||||
const char * libx52io_button_to_str(libx52io_button button);
|
||||
LIBX52IO_API const char *libx52io_button_to_str(libx52io_button button);
|
||||
|
||||
/**
|
||||
* @brief Get the vendor ID of the connected X52 device.
|
||||
|
|
@ -431,7 +441,7 @@ const char * libx52io_button_to_str(libx52io_button button);
|
|||
*
|
||||
* @returns Vendor ID of the connected device. Returns 0 if no device is connected.
|
||||
*/
|
||||
uint16_t libx52io_get_vendor_id(libx52io_context *ctx);
|
||||
LIBX52IO_API uint16_t libx52io_get_vendor_id(libx52io_context *ctx);
|
||||
|
||||
/**
|
||||
* @brief Get the product ID of the connected X52 device.
|
||||
|
|
@ -440,7 +450,7 @@ uint16_t libx52io_get_vendor_id(libx52io_context *ctx);
|
|||
*
|
||||
* @returns Product ID of the connected device. Returns 0 if no device is connected.
|
||||
*/
|
||||
uint16_t libx52io_get_product_id(libx52io_context *ctx);
|
||||
LIBX52IO_API uint16_t libx52io_get_product_id(libx52io_context *ctx);
|
||||
|
||||
/**
|
||||
* @brief Get the device version of the connected X52 device.
|
||||
|
|
@ -449,7 +459,7 @@ uint16_t libx52io_get_product_id(libx52io_context *ctx);
|
|||
*
|
||||
* @returns Device version of the connected device. Returns 0 if no device is connected.
|
||||
*/
|
||||
uint16_t libx52io_get_device_version(libx52io_context *ctx);
|
||||
LIBX52IO_API uint16_t libx52io_get_device_version(libx52io_context *ctx);
|
||||
|
||||
/**
|
||||
* @brief Get the manufacturer string of the connected X52 device.
|
||||
|
|
@ -462,7 +472,7 @@ uint16_t libx52io_get_device_version(libx52io_context *ctx);
|
|||
* @returns Pointer to the manufacturer string, which may be NULL. Return value
|
||||
* is always NULL if no device is connected.
|
||||
*/
|
||||
const char * libx52io_get_manufacturer_string(libx52io_context *ctx);
|
||||
LIBX52IO_API const char *libx52io_get_manufacturer_string(libx52io_context *ctx);
|
||||
|
||||
/**
|
||||
* @brief Get the product string of the connected X52 device.
|
||||
|
|
@ -475,7 +485,7 @@ const char * libx52io_get_manufacturer_string(libx52io_context *ctx);
|
|||
* @returns Pointer to the product string, which may be NULL. Return value
|
||||
* is always NULL if no device is connected.
|
||||
*/
|
||||
const char * libx52io_get_product_string(libx52io_context *ctx);
|
||||
LIBX52IO_API const char *libx52io_get_product_string(libx52io_context *ctx);
|
||||
|
||||
/**
|
||||
* @brief Get the serial number of the connected X52 device.
|
||||
|
|
@ -488,7 +498,7 @@ const char * libx52io_get_product_string(libx52io_context *ctx);
|
|||
* @returns Pointer to the serial number string, which may be NULL. Return value
|
||||
* is always NULL if no device is connected.
|
||||
*/
|
||||
const char * libx52io_get_serial_number_string(libx52io_context *ctx);
|
||||
LIBX52IO_API const char *libx52io_get_serial_number_string(libx52io_context *ctx);
|
||||
|
||||
/** @} */
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
libx52io_version = '1.0.0'
|
||||
|
||||
libx52io_files = files(
|
||||
'io_core.c',
|
||||
'io_axis.c',
|
||||
|
|
@ -8,12 +10,18 @@ libx52io_files = files(
|
|||
|
||||
lib_libx52io = library('x52io', libx52io_files,
|
||||
install: true,
|
||||
version: '1.0.0',
|
||||
version: libx52io_version,
|
||||
c_args: sym_hidden_cargs,
|
||||
dependencies: [dep_hidapi, dep_intl],
|
||||
include_directories: [includes])
|
||||
|
||||
install_headers('libx52io.h', subdir: meson.project_name())
|
||||
pkgconfig.generate(lib_libx52io)
|
||||
pkgconfig.generate(lib_libx52io,
|
||||
name: 'libx52io',
|
||||
description: 'Linux/Unix library to read and parse X52 input',
|
||||
subdirs: meson.project_name(),
|
||||
version: libx52io_version,
|
||||
)
|
||||
|
||||
test_axis = executable('test-axis', 'test_axis.c', libx52io_files,
|
||||
build_by_default: false,
|
||||
|
|
|
|||
|
|
@ -38,9 +38,9 @@ static int group_setup(void **state)
|
|||
return 0; \
|
||||
}
|
||||
|
||||
TEST_SETUP_FUNCTION(_1);
|
||||
TEST_SETUP_FUNCTION(_2);
|
||||
TEST_SETUP_FUNCTION(PRO);
|
||||
TEST_SETUP_FUNCTION(_1)
|
||||
TEST_SETUP_FUNCTION(_2)
|
||||
TEST_SETUP_FUNCTION(PRO)
|
||||
|
||||
#undef TEST_SETUP_FUNCTION
|
||||
|
||||
|
|
|
|||
|
|
@ -10,8 +10,11 @@ lib_LTLIBRARIES += libx52util.la
|
|||
# This library provides extra utilities for ease of use
|
||||
nodist_libx52util_la_SOURCES = libx52util/util_char_map.c
|
||||
libx52util_la_SOURCES = libx52util/x52_char_map_lookup.c
|
||||
libx52util_la_CFLAGS = -I $(top_srcdir)/libx52util $(WARN_CFLAGS)
|
||||
libx52util_la_LDFLAGS = -version-info 1:1:0 $(WARN_LDFLAGS)
|
||||
libx52util_la_CFLAGS = -I $(top_srcdir)/libx52util $(VISIBILITY_CFLAGS) $(WARN_CFLAGS)
|
||||
libx52util_la_LDFLAGS = \
|
||||
-export-symbols-regex '^libx52util_' \
|
||||
-version-info 1:1:0 \
|
||||
$(WARN_LDFLAGS)
|
||||
|
||||
# Header files that need to be copied
|
||||
x52include_HEADERS += libx52util/libx52util.h
|
||||
|
|
@ -20,33 +23,25 @@ x52include_HEADERS += libx52util/libx52util.h
|
|||
pkgconfig_DATA += libx52util/libx52util.pc
|
||||
|
||||
# Autogenerated file that needs to be cleaned up
|
||||
CLEANFILES += libx52util/util_char_map.c
|
||||
char_map_TARGETS = \
|
||||
libx52util/util_char_map.c \
|
||||
libx52util/x52_char_map.bin
|
||||
|
||||
util_char_map_c_DEPENDS = \
|
||||
CLEANFILES += $(char_map_TARGETS)
|
||||
|
||||
char_map_DEPENDS = \
|
||||
$(srcdir)/libx52util/x52_char_map_gen.py \
|
||||
$(srcdir)/libx52util/x52_char_map.cfg
|
||||
|
||||
libx52util/util_char_map.c: $(util_char_map_c_DEPENDS)
|
||||
$(AM_V_GEN) $(PYTHON) $(util_char_map_c_DEPENDS) $@
|
||||
$(char_map_TARGETS): $(char_map_DEPENDS)
|
||||
$(AM_V_GEN) $(PYTHON) $(char_map_DEPENDS) $(char_map_TARGETS)
|
||||
|
||||
if HAVE_CMOCKA
|
||||
TESTS += libx52util-map-test
|
||||
TESTS += libx52util/test-runner.sh
|
||||
|
||||
check_PROGRAMS += libx52util-map-test
|
||||
|
||||
CLEANFILES += libx52util/x52_map_test.c
|
||||
x52_map_test_c_DEPENDS = \
|
||||
$(srcdir)/libx52util/x52_map_test_gen.py \
|
||||
$(srcdir)/libx52util/x52_char_map.cfg
|
||||
|
||||
libx52util/x52_map_test.c: $(x52_map_test_c_DEPENDS)
|
||||
$(AM_V_GEN) $(PYTHON) $(x52_map_test_c_DEPENDS) $@
|
||||
|
||||
libx52util_map_test_SOURCES = libx52util/x52_map_test.c
|
||||
libx52util_map_test_CFLAGS = @CMOCKA_CFLAGS@ -I $(top_srcdir) -I $(top_srcdir)/libx52util
|
||||
libx52util_map_test_LDFLAGS = @CMOCKA_LIBS@
|
||||
libx52util_map_test_LDADD = libx52util.la
|
||||
endif
|
||||
check_PROGRAMS += libx52util-bmp-test
|
||||
libx52util_bmp_test_SOURCES = libx52util/x52_char_map_test.c
|
||||
libx52util_bmp_test_CFLAGS = -I $(top_srcdir)/libx52util
|
||||
libx52util_bmp_test_LDADD = libx52util.la
|
||||
|
||||
# Extra files that need to be in the distribution
|
||||
EXTRA_DIST += libx52util/x52_char_map.cfg \
|
||||
|
|
|
|||
|
|
@ -20,6 +20,17 @@
|
|||
#define LIBX52UTIL_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifndef LIBX52UTIL_API
|
||||
# if defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 303
|
||||
# define LIBX52UTIL_API __attribute__((visibility("default")))
|
||||
# elif defined(_WIN32)
|
||||
# define LIBX52UTIL_API __declspec(dllexport)
|
||||
# else
|
||||
# define LIBX52UTIL_API
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
|
|
@ -46,8 +57,8 @@ extern "C" {
|
|||
* @returns 0 on success, -EINVAL on invalid parameters, -E2BIG if the buffer
|
||||
* filled up before converting the entire string.
|
||||
*/
|
||||
int libx52util_convert_utf8_string(const uint8_t *input,
|
||||
uint8_t *output, size_t *len);
|
||||
LIBX52UTIL_API int libx52util_convert_utf8_string(const uint8_t *input,
|
||||
uint8_t *output, size_t *len);
|
||||
|
||||
/** @} */
|
||||
|
||||
|
|
|
|||
|
|
@ -1,37 +1,41 @@
|
|||
# libx52util
|
||||
libx52util_version = '1.0.2'
|
||||
gen_script = files('x52_char_map_gen.py')[0]
|
||||
|
||||
util_char_map = custom_target('util-char-map',
|
||||
build_by_default: false,
|
||||
depend_files: ['x52_char_map_gen.py', 'x52_char_map.cfg'],
|
||||
command: [python, gen_script, '@INPUT@', '@OUTPUT@'],
|
||||
command: [python, gen_script, '@INPUT@', '@OUTPUT0@', '@OUTPUT1@'],
|
||||
input: 'x52_char_map.cfg',
|
||||
output: 'util_char_map.c')
|
||||
output: ['util_char_map.c', 'x52_char_map.bin'])
|
||||
|
||||
lib_libx52util = library('x52util', util_char_map, 'x52_char_map_lookup.c',
|
||||
install: true,
|
||||
version: '1.0.1',
|
||||
version: libx52util_version,
|
||||
c_args: sym_hidden_cargs,
|
||||
include_directories: [includes],
|
||||
)
|
||||
|
||||
install_headers('libx52util.h', subdir: meson.project_name())
|
||||
pkgconfig.generate(lib_libx52util)
|
||||
pkgconfig.generate(lib_libx52util,
|
||||
name: 'libx52util',
|
||||
description: 'Extra utility functions to manage X52 extended functionality',
|
||||
subdirs: meson.project_name(),
|
||||
version: libx52util_version,
|
||||
)
|
||||
|
||||
test_gen_script = files('x52_map_test_gen.py')[0]
|
||||
|
||||
libx52util_map_test_src = custom_target('libx52util-map-test-src',
|
||||
build_by_default: false,
|
||||
depend_files: ['x52_map_test_gen.py', 'x52_char_map.cfg'],
|
||||
command: [python, test_gen_script, '@INPUT@', '@OUTPUT@'],
|
||||
input: 'x52_char_map.cfg',
|
||||
output: 'x52_map_test.c'
|
||||
)
|
||||
|
||||
libx52util_map_test = executable('libx52util-map-test', libx52util_map_test_src,
|
||||
dependencies: [dep_cmocka],
|
||||
link_with: [lib_libx52util],
|
||||
libx52util_bmp_test = executable(
|
||||
'libx52util-bmp-test',
|
||||
'x52_char_map_test.c',
|
||||
build_by_default: false,
|
||||
include_directories: [includes, lib_libx52util.private_dir_include()],
|
||||
link_with: [lib_libx52util]
|
||||
)
|
||||
|
||||
test('libx52util-map-test', libx52util_map_test, protocol: 'tap')
|
||||
test('libx52util-bmp-test', libx52util_bmp_test,
|
||||
protocol: 'tap',
|
||||
args: [util_char_map[1]])
|
||||
|
||||
benchmark('libx52util-bmp-bench', libx52util_bmp_test,
|
||||
protocol: 'tap',
|
||||
args: [util_char_map[1]])
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
#!/bin/sh
|
||||
|
||||
TEST_RUNNER="./libx52util-bmp-test"
|
||||
TEST_BIN="./libx52util/x52_char_map.bin"
|
||||
|
||||
if [ -e "${TEST_RUNNER}" ] && [ -e "${TEST_BIN}" ];
|
||||
then
|
||||
"${TEST_RUNNER}" "${TEST_BIN}"
|
||||
else
|
||||
echo "TAP version 13"
|
||||
echo "1..0 # missing files"
|
||||
fi
|
||||
|
|
@ -324,3 +324,13 @@
|
|||
0xFF9E 0xDE # HALFWIDTH KATAKANA VOICED SOUND MARK
|
||||
0xFF9F 0xDF # HALFWIDTH KATAKANA SEMI-VOICED SOUND MARK
|
||||
|
||||
# The following characters are manually added to aid in normalization to the
|
||||
# X52 character map
|
||||
0x2215 0x2F # DIVISION SLASH
|
||||
0x2044 0x2F # FRACTION SLASH
|
||||
0x00B0 0xDF # DEGREE SIGN
|
||||
# Note: while Greek letters aren't actually supported by the MFD character map,
|
||||
# this is manually addded to map the letter 'mu' to ASCII 'u'. This is needed
|
||||
# in the CJK compatibility page (0x3300-0x33FF) to deal with the square latin
|
||||
# abbreviations
|
||||
0x03BC 0x75 # GREEK SMALL LETTER MU
|
||||
|
|
|
|||
|
|
@ -12,20 +12,7 @@
|
|||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
enum {
|
||||
TYPE_INVALID = 0, /* Invalid type (default) */
|
||||
|
||||
TYPE_POINTER, /* Pointer target */
|
||||
|
||||
TYPE_ENTRY /* Map entry value */
|
||||
};
|
||||
|
||||
struct map_entry {
|
||||
struct map_entry *next; /* Pointer to the next table */
|
||||
uint8_t type; /* Type of entry */
|
||||
uint8_t value; /* Value is valid if this is of TYPE_ENTRY */
|
||||
};
|
||||
|
||||
extern struct map_entry map_root[];
|
||||
extern const uint16_t *root_table[256];
|
||||
extern const uint8_t *sequence_table[];
|
||||
|
||||
#endif /* !defined X52_CHAR_MAP_H */
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# Character map generator
|
||||
#
|
||||
# Copyright (C) 2012-2018 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
# Copyright (C) 2012-2026 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
"""
|
||||
|
|
@ -11,191 +11,267 @@ for the X52/X52 Pro MFD
|
|||
|
||||
import sys
|
||||
import re
|
||||
|
||||
AUTOGEN_HEADER = """
|
||||
/*
|
||||
* Autogenerated character map file for Saitek X52 Pro
|
||||
* Generated from %s
|
||||
*/
|
||||
|
||||
#include "x52_char_map.h"
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class MapTable(object):
|
||||
"""
|
||||
Defines a MapTable entry, with each entry storing the value seen so far,
|
||||
the type of the entry, and the value, if it's a value node.
|
||||
"""
|
||||
# Empty list
|
||||
root = [None] * 256
|
||||
|
||||
def __init__(self, value_so_far, map_value=None):
|
||||
self.next_level = [None] * 256
|
||||
self.value_so_far = value_so_far
|
||||
self.map_value = map_value
|
||||
|
||||
def output_nodes(self):
|
||||
"""
|
||||
Output the individual nodes
|
||||
"""
|
||||
output_lines = []
|
||||
output_count = 0
|
||||
for node in self.next_level:
|
||||
if node is not None:
|
||||
output_lines.extend(node.output_nodes())
|
||||
output_count += 1
|
||||
|
||||
if output_count != 0:
|
||||
struct_header = 'static struct map_entry table_%x[64] = {' % \
|
||||
self.value_so_far
|
||||
output_lines.append(struct_header)
|
||||
|
||||
for node_index in range(0, 256):
|
||||
node = self.next_level[node_index]
|
||||
if node is not None:
|
||||
output_lines.append(self.dump_entry_line(0x80, node_index,
|
||||
node.value_so_far,
|
||||
node.map_value))
|
||||
|
||||
output_lines.extend(['};', ''])
|
||||
|
||||
return output_lines
|
||||
|
||||
@staticmethod
|
||||
def dump_entry_line(offset, node_index, value_so_far, map_value):
|
||||
"""
|
||||
Dump the array entry for the current node
|
||||
"""
|
||||
if map_value is None:
|
||||
node_entry_line = '\t[0x%02x] = { table_%x, TYPE_POINTER, 0 },' % \
|
||||
(node_index - offset, value_so_far)
|
||||
else:
|
||||
node_entry_line = '\t[0x%02x] = { NULL, TYPE_ENTRY, 0x%02x },' % \
|
||||
(node_index - offset, map_value)
|
||||
|
||||
return node_entry_line
|
||||
|
||||
@classmethod
|
||||
def add_to_table(cls, input_val, map_val):
|
||||
"""
|
||||
Add a map value to the lookup table
|
||||
"""
|
||||
try:
|
||||
uchr = unichr(input_val)
|
||||
except NameError:
|
||||
# Python 3 doesn't have unichr, but chr should work
|
||||
uchr = chr(input_val)
|
||||
|
||||
utf8_str = uchr.encode('utf-8')
|
||||
# Python2 returns the encoded result as a string, wheras
|
||||
# Python3 returns the result as a bytearray. Converting
|
||||
# the string (or bytearray) into a bytearray ensures that
|
||||
# this can be run in both Python2 and Python3
|
||||
utf8_vals = [c for c in bytearray(utf8_str)]
|
||||
|
||||
value_so_far = 0
|
||||
level = cls.root
|
||||
for index, char in enumerate(utf8_vals):
|
||||
value_so_far = (value_so_far << 8) | char
|
||||
if index < (len(utf8_vals) - 1):
|
||||
node = level[char]
|
||||
if node is None:
|
||||
node = cls(value_so_far)
|
||||
level[char] = node
|
||||
|
||||
level = level[char].next_level
|
||||
else:
|
||||
node = cls(value_so_far, map_val)
|
||||
level[char] = node
|
||||
|
||||
@classmethod
|
||||
def output_table_as_list(cls):
|
||||
"""
|
||||
Output the map table as a list of lines
|
||||
"""
|
||||
output_lines = []
|
||||
for node in cls.root:
|
||||
if node is not None:
|
||||
output_lines.extend(node.output_nodes())
|
||||
|
||||
output_lines.append('struct map_entry map_root[256] = {')
|
||||
|
||||
for node_index in range(0, 256):
|
||||
node = cls.root[node_index]
|
||||
if node is not None:
|
||||
output_lines.append(cls.dump_entry_line(0x0, node_index,
|
||||
node.value_so_far,
|
||||
node.map_value))
|
||||
|
||||
output_lines.extend(['};', ''])
|
||||
|
||||
return output_lines
|
||||
|
||||
import json
|
||||
import unicodedata
|
||||
|
||||
class LineFormatError(ValueError):
|
||||
"""
|
||||
Error class for parser
|
||||
"""
|
||||
|
||||
def parse_line(data):
|
||||
class BMPTable:
|
||||
"""
|
||||
Parse a line containing a mapping descriptor. The mapping descriptor
|
||||
must start with a hexadecimal unicode code point, followed by either a
|
||||
single character, or a hexadecimal integer that corresponds to the map
|
||||
value.
|
||||
Sparse table for Basic Multilingual Plane
|
||||
"""
|
||||
# Strip off comments
|
||||
data = re.sub(re.compile('#.*$'), '', data)
|
||||
|
||||
# Strip off leading and trailing whitespace
|
||||
data = data.strip()
|
||||
REPLACEMENT_CHAR = 0xDB
|
||||
|
||||
# If the line is empty, it is a comment line
|
||||
if len(data) == 0:
|
||||
return None, None
|
||||
HEADER = f"""/*
|
||||
* Autogenerated tables for X52 MFD character lookup
|
||||
*
|
||||
* DO NOT EDIT
|
||||
*/
|
||||
|
||||
# Find the code point and the target value
|
||||
try:
|
||||
code_point, target = data.strip().split()
|
||||
except ValueError:
|
||||
# Raised when there are either too many, or not enough values in
|
||||
# the string
|
||||
raise LineFormatError('Invalid descriptor format "%s"' % data)
|
||||
#include <stdint.h>
|
||||
|
||||
# Convert the string to its equivalent numeric value
|
||||
try:
|
||||
code_point = int(code_point, 0)
|
||||
except ValueError:
|
||||
raise LineFormatError('Invalid code point "%s"' % code_point)
|
||||
"""
|
||||
|
||||
# Check if the target is a single character
|
||||
if len(target) == 1:
|
||||
target = ord(target)
|
||||
else:
|
||||
# Try parsing the target as an integer
|
||||
TABLE_NAME_FORMAT = 'bmp_page_%02x'
|
||||
TABLE_NAME_DEFAULT = 'bmp_page_default'
|
||||
TABLE_FORMAT = 'const uint16_t %s[256] = {'
|
||||
TABLE_FOOTER = '};\n'
|
||||
|
||||
def __init__(self, input_file, output_file, output_map):
|
||||
self.input_file = input_file
|
||||
self.output_file = output_file
|
||||
self.output_map = output_map
|
||||
self.mapping = {}
|
||||
self.pages = {}
|
||||
self.sequences = {}
|
||||
self.root_table = []
|
||||
|
||||
self.read_map()
|
||||
self.build_extended_map()
|
||||
self.build_tables()
|
||||
self.generate_test_tables()
|
||||
|
||||
@staticmethod
|
||||
def parse_line(data):
|
||||
"""
|
||||
Parse a line containing a mapping descriptor. The mapping descriptor
|
||||
must start with a hexadecimal unicode code point, followed by either a
|
||||
single character, or a hexadecimal integer that corresponds to the map
|
||||
value.
|
||||
"""
|
||||
# Strip off comments
|
||||
data = re.sub(re.compile('#.*$'), '', data)
|
||||
|
||||
# Strip off leading and trailing whitespace
|
||||
data = data.strip()
|
||||
|
||||
# If the line is empty, it is a comment line
|
||||
if len(data) == 0:
|
||||
return None, None
|
||||
|
||||
# Find the code point and the target value
|
||||
try:
|
||||
target = int(target, 0)
|
||||
except ValueError:
|
||||
raise LineFormatError('Invalid map value "%s"' % target)
|
||||
code_point, target = data.strip().split()
|
||||
except ValueError as exc:
|
||||
# Raised when there are either too many, or not enough values in
|
||||
# the string
|
||||
raise LineFormatError(f'Invalid descriptor format "{data}"') from exc
|
||||
|
||||
return code_point, target
|
||||
# Convert the string to its equivalent numeric value
|
||||
try:
|
||||
code_point = int(code_point, 0)
|
||||
except ValueError as exc:
|
||||
raise LineFormatError(f'Invalid code point "{code_point}"') from exc
|
||||
|
||||
# Check if the target is a single character
|
||||
if len(target) == 1:
|
||||
target = ord(target)
|
||||
else:
|
||||
# Try parsing the target as an integer
|
||||
try:
|
||||
target = int(target, 0)
|
||||
except ValueError as exc:
|
||||
raise LineFormatError(f'Invalid map value "{target}"') from exc
|
||||
|
||||
return code_point, target
|
||||
|
||||
def read_map(self):
|
||||
"""Read the mapping tables from the config file"""
|
||||
def map_normalized(char, dst):
|
||||
# Try to normalize the unicode character as NFKC
|
||||
normalized = unicodedata.normalize('NFKC', chr(char))
|
||||
if normalized == char:
|
||||
# This is already in normalized form
|
||||
return
|
||||
|
||||
if len(normalized) == 1:
|
||||
normalized_char = ord(normalized)
|
||||
|
||||
if normalized_char not in self.mapping:
|
||||
# This is only needed to ensure that we get the normalized
|
||||
# forms for example, half-width Katakana characters are
|
||||
# normalized to their corresponding full width versions.
|
||||
# However, we don't want to overwrite existing mappings,
|
||||
# since something like Lowercase A with grave could be
|
||||
# normalized to lowercase A, which would break the translation
|
||||
self.mapping[normalized_char] = dst
|
||||
|
||||
with open(self.input_file, 'r', encoding='utf-8') as infile:
|
||||
for line in infile:
|
||||
src, dst = self.parse_line(line)
|
||||
if src is None:
|
||||
continue
|
||||
|
||||
self.mapping[src] = dst
|
||||
map_normalized(src, dst)
|
||||
|
||||
def build_extended_map(self):
|
||||
"""Build the extended map for every character in the BMP"""
|
||||
self.mapping[0] = 0 # Handle NUL
|
||||
for i in range(0x10000):
|
||||
# Iterate over the basic multilingual plane
|
||||
if i in self.mapping:
|
||||
continue
|
||||
|
||||
if 0xD800 <= i <= 0xDFFF:
|
||||
# UTF16 surrogate pairs - we want to mark it as a box character
|
||||
self.mapping[i] = self.REPLACEMENT_CHAR
|
||||
continue
|
||||
|
||||
normalized = unicodedata.normalize('NFKC', chr(i))
|
||||
if len(normalized) == 1:
|
||||
normalized_ord = ord(normalized)
|
||||
if normalized_ord in self.mapping:
|
||||
self.mapping[i] = self.mapping[normalized_ord]
|
||||
else:
|
||||
# No single character mapping exists
|
||||
self.mapping[i] = self.REPLACEMENT_CHAR
|
||||
|
||||
continue
|
||||
|
||||
# Check that all characters in the normalized are in the mapping table:
|
||||
sequence = []
|
||||
for c in normalized:
|
||||
if ord(c) in self.mapping:
|
||||
sequence.append(self.mapping[ord(c)])
|
||||
else:
|
||||
sequence.append(self.REPLACEMENT_CHAR)
|
||||
|
||||
# Check if it only contains the box character, or box char and space,
|
||||
# and reduce runs to a single instance
|
||||
if all(c in (self.REPLACEMENT_CHAR, self.mapping[0x20])
|
||||
for c in sequence):
|
||||
self.mapping[i] = self.REPLACEMENT_CHAR
|
||||
continue
|
||||
|
||||
sequence = tuple(sequence)
|
||||
if sequence not in self.sequences:
|
||||
if not self.sequences:
|
||||
last_sequence = 256
|
||||
else:
|
||||
last_sequence = max(self.sequences.values()) + 1
|
||||
|
||||
self.sequences[sequence] = last_sequence
|
||||
|
||||
self.mapping[i] = self.sequences[sequence]
|
||||
|
||||
def output_c_table(self, page_tuple, out_fd):
|
||||
"""Output the C table structure"""
|
||||
page_name = self.pages[page_tuple]
|
||||
|
||||
print(self.TABLE_FORMAT % (page_name), file=out_fd)
|
||||
|
||||
for i, val in enumerate(page_tuple):
|
||||
print(f"0x{val:02x}, ", end='', file=out_fd)
|
||||
if i % 8 == 7:
|
||||
print(f"// 0x{i-7:02x}-0x{i:02x}", file=out_fd)
|
||||
|
||||
print(self.TABLE_FOOTER, file=out_fd)
|
||||
|
||||
def build_tables(self):
|
||||
"""Build the C Tables"""
|
||||
with open(self.output_file, 'w', encoding='utf-8') as out_fd:
|
||||
print(self.HEADER, file=out_fd)
|
||||
|
||||
default_page = tuple([self.REPLACEMENT_CHAR] * 256)
|
||||
self.pages[default_page] = self.TABLE_NAME_DEFAULT
|
||||
self.output_c_table(default_page, out_fd)
|
||||
|
||||
for root_idx in range(256):
|
||||
base_idx = root_idx * 256
|
||||
page = [self.mapping[idx] for idx in range(base_idx, base_idx+256)]
|
||||
page_tuple = tuple(page)
|
||||
if page_tuple not in self.pages:
|
||||
page_name = self.TABLE_NAME_FORMAT % (root_idx)
|
||||
self.pages[page_tuple] = page_name
|
||||
self.output_c_table(page_tuple, out_fd)
|
||||
|
||||
self.root_table.append(self.pages[page_tuple])
|
||||
|
||||
print(self.TABLE_FORMAT % ('* root_table'), file=out_fd)
|
||||
|
||||
for page_id, page_name in enumerate(self.root_table):
|
||||
print(f" {page_name}, // 0x{page_id:02x}", file=out_fd)
|
||||
|
||||
print(self.TABLE_FOOTER, file=out_fd)
|
||||
|
||||
print(f"const uint8_t *sequence_table[{len(self.sequences)}] = {{", file=out_fd)
|
||||
for sequence, seq_id in self.sequences.items():
|
||||
seq_len = len(sequence)
|
||||
if seq_len >= 256:
|
||||
raise RuntimeError("Sequence way too long")
|
||||
|
||||
line = [f"0x{seq_len:02X}"]
|
||||
for seq_elem in sequence:
|
||||
line.append(f"0x{seq_elem:02X}")
|
||||
|
||||
line = ', '.join(line)
|
||||
print(f' [{seq_id-256}] = (const uint8_t[]){{ {line} }},', file=out_fd)
|
||||
|
||||
print(self.TABLE_FOOTER, file=out_fd)
|
||||
|
||||
def generate_test_tables(self):
|
||||
"""Build the test tables used by the test suite"""
|
||||
# Generate the expected output sequences for every table
|
||||
# Mapping is a dict mapping the code point as a string to the output
|
||||
# Sequence is a dict of <seq_tuple>:<seq_id> mappings (seq_id starts from 256)
|
||||
output = []
|
||||
sequences = [item[0] for item in sorted(self.sequences.items(),
|
||||
key=lambda item: item[1])]
|
||||
|
||||
# The mapping for the NUL byte (\x00) should be an empty sequence
|
||||
output.append([])
|
||||
|
||||
for i in range(1, 0x10000):
|
||||
seq = self.mapping[i]
|
||||
if seq >= 256:
|
||||
# Pull from sequence table
|
||||
seq = sequences[seq - 256]
|
||||
else:
|
||||
seq = [seq]
|
||||
output.append(seq)
|
||||
|
||||
# Find the longest length sequence (add 1 for the length byte)
|
||||
longest = max(len(seq) for seq in output) + 1
|
||||
# Find the next power of two that can hold this sequence
|
||||
if (longest & (longest - 1)) == 0:
|
||||
record_length = longest
|
||||
else:
|
||||
record_length = 1 << longest.bit_length()
|
||||
|
||||
with open(self.output_map, 'wb') as output_map:
|
||||
pad = [0] * record_length
|
||||
for seq in output:
|
||||
record = [len(seq)] + list(seq) + pad
|
||||
output_map.write(bytes(record[:record_length]))
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != 3:
|
||||
sys.stderr.write('Usage: %s <input-map> <output-c-file>\n' %
|
||||
sys.argv[0])
|
||||
if len(sys.argv) != 4:
|
||||
sys.stderr.write(f"Usage: {sys.argv[0]} <input-map> <output-c-file> <output-json-map>\n")
|
||||
sys.exit(1)
|
||||
|
||||
with open(sys.argv[1], 'r') as infile:
|
||||
for line in infile:
|
||||
src, dst = parse_line(line)
|
||||
if src is not None:
|
||||
MapTable.add_to_table(src, dst)
|
||||
|
||||
with open(sys.argv[2], 'w') as outfile:
|
||||
outfile.write(AUTOGEN_HEADER % sys.argv[1])
|
||||
|
||||
for line in MapTable.output_table_as_list():
|
||||
outfile.write(line + '\n')
|
||||
BMPTable(sys.argv[1], sys.argv[2], sys.argv[3])
|
||||
|
|
|
|||
|
|
@ -11,11 +11,64 @@
|
|||
|
||||
#include "config.h"
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "libx52util.h"
|
||||
#include "x52_char_map.h"
|
||||
|
||||
/**
|
||||
* @brief Converts a UTF8 stream to a uint32_t
|
||||
*
|
||||
* @param[in] utf8in Pointer to UTF8 input stream. Must be NUL-terminated
|
||||
* @param[out] unichr Output character pointer
|
||||
*
|
||||
* @returns number of bytes to advance stream by - 0 if NUL or input pointer is NULL
|
||||
*/
|
||||
static int utf8_to_u32(const uint8_t *utf8in, uint32_t *unichr)
|
||||
{
|
||||
uint8_t b;
|
||||
if (!utf8in || !*utf8in) return 0;
|
||||
|
||||
b = utf8in[0];
|
||||
|
||||
// 1-byte (0xxxxxxx)
|
||||
if (b < 0x80) {
|
||||
*unichr = b;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Invalid leading bytes
|
||||
if (b < 0xC2 || b > 0xF4) goto error;
|
||||
|
||||
// 2-byte (110xxxxx 10xxxxxx)
|
||||
if ((b & 0xE0) == 0xC0) {
|
||||
if ((utf8in[1] & 0xC0) != 0x80) goto error;
|
||||
*unichr = ((b & 0x1F) << 6) | (utf8in[1] & 0x3F);
|
||||
return 2;
|
||||
}
|
||||
|
||||
// 3-byte (1110xxxx 10xxxxxx 10xxxxxx)
|
||||
if ((b & 0xF0) == 0xE0) {
|
||||
if ((utf8in[1] & 0xC0) != 0x80 || (utf8in[2] & 0xC0) != 0x80) goto error;
|
||||
*unichr = ((b & 0x0F) << 12) | ((utf8in[1] & 0x3F) << 6) | (utf8in[2] & 0x3F);
|
||||
return 3;
|
||||
}
|
||||
|
||||
// 4-byte (11110xxx 10xxxxxx 10xxxxxx 10xxxxxx)
|
||||
if ((b & 0xF8) == 0xF0) {
|
||||
if ((utf8in[1] & 0xC0) != 0x80 || (utf8in[2] & 0xC0) != 0x80 ||
|
||||
(utf8in[3] & 0xC0) != 0x80) goto error;
|
||||
*unichr = ((b & 0x07) << 18) | ((utf8in[1] & 0x3F) << 12) |
|
||||
((utf8in[2] & 0x3F) << 6) | (utf8in[3] & 0x3F);
|
||||
return 4;
|
||||
}
|
||||
|
||||
error:
|
||||
*unichr = 0xFFFD; // Unicode Replacement Character
|
||||
return 1; // Consume lead byte to attempt resync
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert UTF8 string to X52 character map.
|
||||
*
|
||||
|
|
@ -32,52 +85,61 @@
|
|||
int libx52util_convert_utf8_string(const uint8_t *input,
|
||||
uint8_t *output, size_t *len)
|
||||
{
|
||||
struct map_entry *entry;
|
||||
size_t index;
|
||||
int retval = 0;
|
||||
unsigned char local_index;
|
||||
uint32_t unichr;
|
||||
int bytes_consumed;
|
||||
uint16_t translated;
|
||||
|
||||
if (!input || !output || !len || !*len) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
index = 0;
|
||||
entry = &map_root[*input];
|
||||
// Reset the output array
|
||||
memset(output, 0, *len);
|
||||
|
||||
while (*input) {
|
||||
input++;
|
||||
if (entry->type == TYPE_ENTRY) {
|
||||
output[index] = entry->value;
|
||||
// Length check
|
||||
if (index >= *len) {
|
||||
retval = -E2BIG;
|
||||
break;
|
||||
}
|
||||
|
||||
bytes_consumed = utf8_to_u32(input, &unichr);
|
||||
if (bytes_consumed == 0) {
|
||||
// We should never get here, since the while loop should have
|
||||
// caught it
|
||||
retval = 0;
|
||||
break;
|
||||
}
|
||||
input += bytes_consumed;
|
||||
|
||||
// Check for bytes in the Supplementary planes
|
||||
if (unichr >= 0x10000) {
|
||||
unichr = 0xFFFD; // Unicode replacement character
|
||||
}
|
||||
|
||||
translated = root_table[unichr >> 8][unichr & 0xFF];
|
||||
if (translated < 256) {
|
||||
// Table entry, push to output
|
||||
output[index] = (uint8_t)translated;
|
||||
index++;
|
||||
if (index >= *len && *input) {
|
||||
} else {
|
||||
// We have a sequence, output that
|
||||
const uint8_t *sequence = sequence_table[translated - 256];
|
||||
uint8_t seq_len = sequence[0];
|
||||
|
||||
// Let's make sure that we can actually output to the buffer
|
||||
if ((index + seq_len) >= *len) {
|
||||
retval = -E2BIG;
|
||||
break;
|
||||
}
|
||||
entry = &map_root[*input];
|
||||
} else if (entry->type == TYPE_POINTER) {
|
||||
local_index = *input;
|
||||
if (local_index < 0x80 || local_index >= 0xC0) {
|
||||
/* Invalid input, skip till we find the start of another
|
||||
* valid UTF-8 character
|
||||
*/
|
||||
while (*input >= 0x80 && *input < 0xC0) {
|
||||
input++; /* Skip invalid characters */
|
||||
}
|
||||
|
||||
/* New UTF-8 character, reset the entry pointer */
|
||||
entry = &map_root[*input];
|
||||
} else {
|
||||
/* Mask off the upper bits, we only care about the lower 6 bits */
|
||||
local_index &= 0x3F;
|
||||
entry = &(entry->next[local_index]);
|
||||
for (int i = 1; i <= seq_len; i++) {
|
||||
output[index] = sequence[i];
|
||||
index++;
|
||||
}
|
||||
} else {
|
||||
/* Invalid value, skip */
|
||||
while (*input >= 0x80 && *input < 0xC0) {
|
||||
input++; /* Skip invalid characters */
|
||||
}
|
||||
|
||||
/* New UTF-8 character, reset the entry pointer */
|
||||
entry = &map_root[*input];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,215 @@
|
|||
/*
|
||||
* X52 character map lookup test
|
||||
*
|
||||
* Copyright (C) 2026 Nirenjan Krishnan <nirenjan@nirenjan.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "libx52util.h"
|
||||
|
||||
// Fix this if we ever hit longer sequences
|
||||
#define RECORD_SIZE 8
|
||||
|
||||
// Blindly encode a string into it's smallest UTF8 representation
|
||||
static void encode_utf8(uint32_t cp, uint8_t *out)
|
||||
{
|
||||
if (cp <= 0x7F) {
|
||||
out[0] = (uint8_t)cp;
|
||||
} else if (cp <= 0x7FF) {
|
||||
out[0] = (uint8_t)(0xC0 | (cp >> 6));
|
||||
out[1] = (uint8_t)(0x80 | (cp & 0x3F));
|
||||
} else if (cp <= 0xFFFF) {
|
||||
out[0] = (uint8_t)(0xE0 | (cp >> 12));
|
||||
out[1] = (uint8_t)(0x80 | ((cp >> 6) & 0x3F));
|
||||
out[2] = (uint8_t)(0x80 | (cp & 0x3F));
|
||||
} else if (cp <= 0x1FFFFF) {
|
||||
out[0] = (uint8_t)(0xF0 | (cp >> 18));
|
||||
out[1] = (uint8_t)(0x80 | ((cp >> 12) & 0x3F));
|
||||
out[2] = (uint8_t)(0x80 | ((cp >> 6) & 0x3F));
|
||||
out[3] = (uint8_t)(0x80 | (cp & 0x3F));
|
||||
} else if (cp <= 0x3FFFFFF) {
|
||||
out[0] = (uint8_t)(0xF8 | (cp >> 24));
|
||||
out[1] = (uint8_t)(0x80 | ((cp >> 18) & 0x3F));
|
||||
out[2] = (uint8_t)(0x80 | ((cp >> 12) & 0x3F));
|
||||
out[3] = (uint8_t)(0x80 | ((cp >> 6) & 0x3F));
|
||||
out[4] = (uint8_t)(0x80 | (cp & 0x3F));
|
||||
} else if (cp <= 0x7FFFFFFF) {
|
||||
out[0] = (uint8_t)(0xFC | (cp >> 30));
|
||||
out[1] = (uint8_t)(0x80 | ((cp >> 24) & 0x3F));
|
||||
out[2] = (uint8_t)(0x80 | ((cp >> 18) & 0x3F));
|
||||
out[3] = (uint8_t)(0x80 | ((cp >> 12) & 0x3F));
|
||||
out[4] = (uint8_t)(0x80 | ((cp >> 6) & 0x3F));
|
||||
out[5] = (uint8_t)(0x80 | (cp & 0x3F));
|
||||
} else { // 0x80000000 to 0xFFFFFFFF (7 bytes)
|
||||
out[0] = (uint8_t)0xFE; // Binary 11111110
|
||||
out[1] = (uint8_t)(0x80 | ((cp >> 30) & 0x3F));
|
||||
out[2] = (uint8_t)(0x80 | ((cp >> 24) & 0x3F));
|
||||
out[3] = (uint8_t)(0x80 | ((cp >> 18) & 0x3F));
|
||||
out[4] = (uint8_t)(0x80 | ((cp >> 12) & 0x3F));
|
||||
out[5] = (uint8_t)(0x80 | ((cp >> 6) & 0x3F));
|
||||
out[6] = (uint8_t)(0x80 | (cp & 0x3F));
|
||||
}
|
||||
}
|
||||
|
||||
static double get_time_diff(struct timespec start, struct timespec end)
|
||||
{
|
||||
return (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
uint8_t input[8] = {0};
|
||||
uint8_t output[RECORD_SIZE];
|
||||
size_t len;
|
||||
int result;
|
||||
|
||||
int fd;
|
||||
uint8_t *expected_blob;
|
||||
bool smp_pages_ok;
|
||||
|
||||
struct timespec start, end;
|
||||
|
||||
// Argument check
|
||||
if (argc != 2) {
|
||||
puts("Bail out! Invalid number of arguments");
|
||||
puts("# Usage: libx52util-bmp-test <path-to-bin>");
|
||||
return 1;
|
||||
}
|
||||
|
||||
fd = open(argv[1], O_RDONLY);
|
||||
if (fd < 0) {
|
||||
printf("Bail out! Error %d opening bin file %s: %s\n",
|
||||
errno, argv[1], strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
|
||||
expected_blob = mmap(NULL, 0x10000 * RECORD_SIZE,
|
||||
PROT_READ, MAP_SHARED, fd, 0);
|
||||
if (expected_blob == MAP_FAILED) {
|
||||
printf("Bail out! MMAP failed with error %d: %s\n",
|
||||
errno, strerror(errno));
|
||||
}
|
||||
|
||||
puts("TAP version 13");
|
||||
// Check the 256 BMP Pages, plus the supplementary pages
|
||||
puts("1..257");
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &start);
|
||||
|
||||
for (uint32_t page = 0; page < 256; page++) {
|
||||
bool page_ok = true;
|
||||
|
||||
for (uint32_t offset = 0; offset < 256; offset++) {
|
||||
uint32_t cp = page * 256 + offset;
|
||||
const uint8_t *rec = &expected_blob[cp * RECORD_SIZE];
|
||||
|
||||
memset(input, 0, sizeof(input));
|
||||
memset(output, 0, sizeof(output));
|
||||
encode_utf8(cp, input);
|
||||
len = sizeof(output);
|
||||
|
||||
result = libx52util_convert_utf8_string(input, output, &len);
|
||||
if (result != 0) {
|
||||
page_ok = false;
|
||||
printf("# Bad result @ %04X: %d\n", cp, result);
|
||||
break;
|
||||
}
|
||||
|
||||
// result is OK, check against the expected blob
|
||||
if (len != rec[0]) {
|
||||
page_ok = false;
|
||||
printf("# Length mismatch @ %04X: expected %u, got %zu\n",
|
||||
cp, rec[0], len);
|
||||
break;
|
||||
}
|
||||
|
||||
// Length is OK, check the bytes
|
||||
if (memcmp(output, &rec[1], rec[0]) != 0) {
|
||||
page_ok = false;
|
||||
printf("# Output mismatch @ %04X:\n", cp);
|
||||
printf("# exp/got:");
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
printf("%02X/%02X ", rec[i+1], output[i]);
|
||||
}
|
||||
puts("");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
printf("%sok - %d Page 0x%02x\n", page_ok ? "": "not ",
|
||||
page + 1, page);
|
||||
}
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &end);
|
||||
{
|
||||
double time_spent = get_time_diff(start, end);
|
||||
printf("# -- Benchmark results --\n");
|
||||
printf("# Total time for 64K lookups: %.4f seconds\n", time_spent);
|
||||
printf("# Throughput: %.2f Mchars/sec\n", (65536.0 / time_spent) / 1e6);
|
||||
|
||||
printf("# -----------------------\n");
|
||||
}
|
||||
|
||||
// Handle the supplementary pages
|
||||
smp_pages_ok = true;
|
||||
for (uint32_t smp = 0x1; smp <= 0x10; smp++) {
|
||||
const uint8_t *rec = &expected_blob[0xFFFD * RECORD_SIZE];
|
||||
for (uint32_t offset = 0; offset < 0x100; offset += 0xFF) {
|
||||
uint32_t cp = smp * 0x10000 + offset;
|
||||
|
||||
memset(input, 0, sizeof(input));
|
||||
memset(output, 0, sizeof(output));
|
||||
len = sizeof(output);
|
||||
encode_utf8(cp, input);
|
||||
|
||||
result = libx52util_convert_utf8_string(input, output, &len);
|
||||
if (result != 0) {
|
||||
smp_pages_ok = false;
|
||||
printf("# Bad result @ %08X: %d\n", cp, result);
|
||||
break;
|
||||
}
|
||||
|
||||
// result is OK, check against the expected blob
|
||||
if (len != rec[0]) {
|
||||
smp_pages_ok = false;
|
||||
printf("# Length mismatch @ %08X: expected %u, got %zu\n",
|
||||
|
||||
cp, rec[0], len);
|
||||
break;
|
||||
}
|
||||
|
||||
// Length is OK, check the bytes
|
||||
if (memcmp(output, &rec[1], rec[0]) != 0) {
|
||||
smp_pages_ok = false;
|
||||
printf("# Output mismatch @ %08X:\n", cp);
|
||||
printf("# exp/got:");
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
printf("%02X/%02X ", rec[i+1], output[i]);
|
||||
}
|
||||
puts("");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!smp_pages_ok) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
printf("%sok - 257 SMP tests\n", smp_pages_ok ? "" : "not ");
|
||||
|
||||
// Cleanup
|
||||
munmap(expected_blob, 0x10000 * RECORD_SIZE);
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1,107 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Generate a test script for the convert function"""
|
||||
|
||||
import argparse
|
||||
import re
|
||||
|
||||
def parse_file(map_file):
|
||||
"""Read the map file, strip out comments, and return a dictionary that
|
||||
maps the UTF-8 encoded string to the X52 MFD character"""
|
||||
|
||||
# If we are running this, then we know that the input map is likely
|
||||
# in a sane format already.
|
||||
char_dict = {}
|
||||
|
||||
with open(map_file, 'r', encoding='utf-8') as map_fd:
|
||||
for line in map_fd:
|
||||
line = re.sub(r'#.*$', '', line).strip()
|
||||
|
||||
if not line:
|
||||
# Comment line, skip
|
||||
continue
|
||||
|
||||
key, out = line.split()
|
||||
in_char = int(key, 0)
|
||||
|
||||
if len(out) == 1:
|
||||
out_byte = ord(out)
|
||||
else:
|
||||
out_byte = int(out, 0)
|
||||
|
||||
char_dict[in_char] = out_byte
|
||||
|
||||
return char_dict
|
||||
|
||||
def generate_positive_test_cases(char_dict):
|
||||
"""Generate a set of positive test cases"""
|
||||
# For every string in the dictionary, generate a test case that tests
|
||||
# the input against the output
|
||||
TEST_CASE_FMT = """
|
||||
static void test_map_{in_char}(void **state) {{
|
||||
(void)state;
|
||||
const uint8_t input_array[] = {{ {in_bytes}, 0 }};
|
||||
const uint8_t expected_output[2] = {{ {out_byte}, 0 }};
|
||||
size_t out_len = 20;
|
||||
uint8_t output[20] = {{ 0 }};
|
||||
int rc;
|
||||
|
||||
rc = libx52util_convert_utf8_string(input_array, output, &out_len);
|
||||
assert_int_equal(rc, 0);
|
||||
assert_int_equal(out_len, 1);
|
||||
assert_memory_equal(output, expected_output, 2);
|
||||
}}
|
||||
"""
|
||||
|
||||
output = ""
|
||||
for in_char, out_byte in char_dict.items():
|
||||
in_bytes = ", ".join(hex(c) for c in chr(in_char).encode('utf-8'))
|
||||
in_tc = hex(in_char)
|
||||
|
||||
output += TEST_CASE_FMT.format(in_char=in_tc, in_bytes=in_bytes, out_byte=out_byte)
|
||||
|
||||
output += """
|
||||
const struct CMUnitTest tests[] = {
|
||||
"""
|
||||
|
||||
for in_char in sorted(char_dict.keys()):
|
||||
output += f" cmocka_unit_test(test_map_{hex(in_char)}),\n"
|
||||
|
||||
output += '};\n'
|
||||
|
||||
return output
|
||||
|
||||
TEST_HEADER = """
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdarg.h>
|
||||
#include <setjmp.h>
|
||||
#include <cmocka.h>
|
||||
|
||||
#include "libx52util.h"
|
||||
"""
|
||||
|
||||
TEST_FOOTER = """
|
||||
int main(void) {
|
||||
cmocka_set_message_output(CM_OUTPUT_TAP);
|
||||
cmocka_run_group_tests(tests, NULL, NULL);
|
||||
return 0;
|
||||
}
|
||||
"""
|
||||
|
||||
def main():
|
||||
"""Generate X52 map test suite"""
|
||||
parser = argparse.ArgumentParser(description='Generate map test cases')
|
||||
parser.add_argument('INPUT_FILE', help="Input character map file")
|
||||
parser.add_argument('OUTPUT_FILE', help="Generated test script")
|
||||
args = parser.parse_args()
|
||||
|
||||
char_dict = parse_file(args.INPUT_FILE)
|
||||
test_cases = generate_positive_test_cases(char_dict)
|
||||
|
||||
with open(args.OUTPUT_FILE, 'w', encoding='utf-8') as out_fd:
|
||||
print(TEST_HEADER, file=out_fd)
|
||||
print(test_cases, file=out_fd)
|
||||
print(TEST_FOOTER, file=out_fd)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
41
meson.build
41
meson.build
|
|
@ -1,6 +1,7 @@
|
|||
project('libx52', 'C',
|
||||
license: 'GPL-2.0-only WITH Classpath-exception-2.0',
|
||||
version: '0.3.3')
|
||||
version: '0.3.3',
|
||||
meson_version: '>=0.61')
|
||||
|
||||
dep_libusb = dependency('libusb-1.0', required: true)
|
||||
dep_hidapi = dependency('hidapi-hidraw', required: false)
|
||||
|
|
@ -8,7 +9,12 @@ if not dep_hidapi.found()
|
|||
dep_hidapi = dependency('hidapi', required: true)
|
||||
endif
|
||||
|
||||
dep_evdev = dependency('libevdev', required: false)
|
||||
if host_machine.system() == 'linux'
|
||||
dep_evdev = dependency('libevdev', required: false)
|
||||
else
|
||||
# Create a dummy dependency
|
||||
dep_evdev = dependency('', required: false)
|
||||
endif
|
||||
|
||||
dep_systemd = dependency('systemd', required: false)
|
||||
dep_udev = dependency('udev', required: false)
|
||||
|
|
@ -33,16 +39,20 @@ assert(pyversion[1].to_int() >= 5, 'Require Python >= 3.5')
|
|||
# config.h
|
||||
#######################################################################
|
||||
compiler = meson.get_compiler('c')
|
||||
sym_hidden_cargs = []
|
||||
if compiler.has_argument('-fvisibility=hidden')
|
||||
sym_hidden_cargs = ['-fvisibility=hidden']
|
||||
endif
|
||||
cdata = configuration_data()
|
||||
cdata.set_quoted('PACKAGE', meson.project_name())
|
||||
cdata.set_quoted('PACKAGE_BUGREPORT', 'https://github.com/nirenjan/libx52/issues')
|
||||
cdata.set_quoted('PACKAGE_NAME', meson.project_name())
|
||||
cdata.set_quoted('LOCALEDIR', get_option('localedir'))
|
||||
cdata.set_quoted('SYSCONFDIR', get_option('sysconfdir'))
|
||||
cdata.set_quoted('LOCALSTATEDIR', get_option('localstatedir'))
|
||||
cdata.set_quoted('LOCALEDIR', get_option('prefix') / get_option('localedir'))
|
||||
cdata.set_quoted('SYSCONFDIR', get_option('prefix') / get_option('sysconfdir'))
|
||||
cdata.set_quoted('LOCALSTATEDIR', get_option('prefix') / get_option('localstatedir'))
|
||||
cdata.set_quoted('PACKAGE_VERSION', meson.project_version())
|
||||
cdata.set_quoted('VERSION', meson.project_version())
|
||||
cdata.set10('ENABLE_NLS', get_option('nls').enabled())
|
||||
cdata.set10('ENABLE_NLS', not get_option('nls').disabled())
|
||||
cdata.set10('HAVE_FUNC_ATTRIBUTE_NORETURN', compiler.has_function_attribute('noreturn'))
|
||||
cdata.set10('HAVE_STRUCT_TM_TM_GMTOFF',
|
||||
compiler.has_member('struct tm', 'tm_gmtoff', prefix:'#include <time.h>'))
|
||||
|
|
@ -73,7 +83,7 @@ if not dep_intl.found() and host_machine.system() == 'darwin'
|
|||
endif
|
||||
|
||||
# # define GETTEXT_PACKAGE
|
||||
if get_option('nls').enabled()
|
||||
if not get_option('nls').disabled()
|
||||
add_project_arguments(
|
||||
'-DGETTEXT_PACKAGE="@0@"'.format(meson.project_name()),
|
||||
language:'C')
|
||||
|
|
@ -81,6 +91,8 @@ if get_option('nls').enabled()
|
|||
subdir('po')
|
||||
endif
|
||||
|
||||
add_project_arguments('-isystem', meson.current_source_dir() / 'sys', language: 'C')
|
||||
|
||||
#######################################################################
|
||||
# Internal dependencies
|
||||
#######################################################################
|
||||
|
|
@ -105,11 +117,12 @@ dep_inih = dependency('inih')
|
|||
# Shared libraries and programs
|
||||
#######################################################################
|
||||
# Includes
|
||||
includes = include_directories('.', 'libx52', 'libx52io', 'libx52util')
|
||||
includes = include_directories('.', 'libx52', 'libx52io', 'libx52util', 'vkm')
|
||||
|
||||
subdir('libx52')
|
||||
subdir('libx52io')
|
||||
subdir('libx52util')
|
||||
subdir('vkm')
|
||||
subdir('bugreport')
|
||||
subdir('cli')
|
||||
subdir('joytest')
|
||||
|
|
@ -127,14 +140,20 @@ if doxygen_program.found()
|
|||
configuration: {
|
||||
'PACKAGE_NAME': meson.project_name(),
|
||||
'PACKAGE_VERSION': meson.project_version(),
|
||||
'abs_top_builddir': meson.build_root(),
|
||||
'abs_top_srcdir': meson.source_root(),
|
||||
'abs_top_builddir': meson.project_build_root(),
|
||||
'abs_top_srcdir': meson.project_source_root(),
|
||||
}
|
||||
)
|
||||
|
||||
docs_tgt = custom_target('docs',
|
||||
custom_target('docs',
|
||||
depend_files: [doxyfile, 'DoxygenLayout.xml'],
|
||||
command: [doxygen_program],
|
||||
output: 'docs'
|
||||
)
|
||||
|
||||
meson.add_install_script(
|
||||
'install-doxygen-docs.sh',
|
||||
get_option('datadir') / 'doc' / meson.project_name(),
|
||||
get_option('mandir'),
|
||||
)
|
||||
endif
|
||||
|
|
|
|||
|
|
@ -2,10 +2,20 @@ option('systemd-logs',
|
|||
type: 'feature',
|
||||
description: 'Hide timestamps in log messages, needed for systemd')
|
||||
|
||||
option('systemd-unit-dir',
|
||||
type: 'string',
|
||||
value: '',
|
||||
description: 'Directory for systemd service files (leave empty for auto-detection)')
|
||||
|
||||
option('nls',
|
||||
type: 'feature',
|
||||
description: 'Enable message translations')
|
||||
|
||||
option('udev-rules-dir',
|
||||
type: 'string',
|
||||
value: '',
|
||||
description: 'Directory for udev rules (leave empty for auto-detection)')
|
||||
|
||||
option('input-group',
|
||||
type: 'string', value: 'plugdev',
|
||||
description: 'Group for input devices')
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ libx52/x52_stringify.c
|
|||
|
||||
libx52io/io_strings.c
|
||||
|
||||
vkm/vkm_common.c
|
||||
|
||||
evtest/ev_test.c
|
||||
|
||||
joytest/x52_test.c
|
||||
|
|
@ -22,6 +24,6 @@ daemon/x52d_config_parser.c
|
|||
daemon/x52d_device.c
|
||||
daemon/x52d_io.c
|
||||
daemon/x52d_mouse.c
|
||||
daemon/x52d_mouse_evdev.c
|
||||
daemon/x52d_mouse_handler.c
|
||||
daemon/x52d_notify.c
|
||||
daemon/x52ctl.c
|
||||
|
|
|
|||
222
po/libx52.pot
222
po/libx52.pot
|
|
@ -6,9 +6,9 @@
|
|||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: libx52 0.3.2\n"
|
||||
"Project-Id-Version: libx52 0.3.3\n"
|
||||
"Report-Msgid-Bugs-To: https://github.com/nirenjan/libx52/issues\n"
|
||||
"POT-Creation-Date: 2026-03-12 08:31-0700\n"
|
||||
"POT-Creation-Date: 2026-03-27 20:52-0700\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
|
@ -17,7 +17,7 @@ msgstr ""
|
|||
"Content-Type: text/plain; charset=CHARSET\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: libx52/x52_strerror.c:23 libx52io/io_strings.c:101
|
||||
#: libx52/x52_strerror.c:23 libx52io/io_strings.c:101 vkm/vkm_common.c:25
|
||||
msgid "Success"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -29,7 +29,7 @@ msgstr ""
|
|||
msgid "Insufficient memory"
|
||||
msgstr ""
|
||||
|
||||
#: libx52/x52_strerror.c:26
|
||||
#: libx52/x52_strerror.c:26 vkm/vkm_common.c:29
|
||||
msgid "Invalid parameter"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -85,7 +85,7 @@ msgstr ""
|
|||
msgid "System call interrupted"
|
||||
msgstr ""
|
||||
|
||||
#: libx52/x52_strerror.c:66 libx52io/io_strings.c:125
|
||||
#: libx52/x52_strerror.c:66 libx52io/io_strings.c:125 vkm/vkm_common.c:52
|
||||
#, c-format
|
||||
msgid "Unknown error %d"
|
||||
msgstr ""
|
||||
|
|
@ -143,12 +143,12 @@ msgid "Unknown LED state %d"
|
|||
msgstr ""
|
||||
|
||||
#: libx52/x52_stringify.c:47 daemon/x52d_clock.c:29 daemon/x52d_mouse.c:32
|
||||
#: daemon/x52d_mouse.c:68
|
||||
#: daemon/x52d_mouse.c:66
|
||||
msgid "off"
|
||||
msgstr ""
|
||||
|
||||
#: libx52/x52_stringify.c:48 daemon/x52d_clock.c:29 daemon/x52d_mouse.c:32
|
||||
#: daemon/x52d_mouse.c:68
|
||||
#: daemon/x52d_mouse.c:66
|
||||
msgid "on"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -233,26 +233,54 @@ msgstr ""
|
|||
msgid "Read timeout"
|
||||
msgstr ""
|
||||
|
||||
#: evtest/ev_test.c:109
|
||||
#: vkm/vkm_common.c:26
|
||||
msgid "Unknown error"
|
||||
msgstr ""
|
||||
|
||||
#: vkm/vkm_common.c:27
|
||||
msgid "Not ready"
|
||||
msgstr ""
|
||||
|
||||
#: vkm/vkm_common.c:28
|
||||
msgid "Out of memory"
|
||||
msgstr ""
|
||||
|
||||
#: vkm/vkm_common.c:30
|
||||
msgid "Not supported"
|
||||
msgstr ""
|
||||
|
||||
#: vkm/vkm_common.c:31
|
||||
msgid "Virtual device failure"
|
||||
msgstr ""
|
||||
|
||||
#: vkm/vkm_common.c:32
|
||||
msgid "Unable to write event"
|
||||
msgstr ""
|
||||
|
||||
#: vkm/vkm_common.c:33
|
||||
msgid "No state change"
|
||||
msgstr ""
|
||||
|
||||
#: evtest/ev_test.c:110
|
||||
#, c-format
|
||||
msgid "Device ID: vendor 0x%04x product 0x%04x version 0x%04x\n"
|
||||
msgstr ""
|
||||
|
||||
#: evtest/ev_test.c:113
|
||||
#: evtest/ev_test.c:114
|
||||
#, c-format
|
||||
msgid "Device name: \"%s %s\"\n"
|
||||
msgstr ""
|
||||
|
||||
#: evtest/ev_test.c:116
|
||||
#: evtest/ev_test.c:117
|
||||
#, c-format
|
||||
msgid "Serial number: \"%s\"\n"
|
||||
msgstr ""
|
||||
|
||||
#: evtest/ev_test.c:117
|
||||
#: evtest/ev_test.c:118
|
||||
msgid "Testing (interrupt to exit)\n"
|
||||
msgstr ""
|
||||
|
||||
#: evtest/ev_test.c:157 evtest/ev_test.c:165
|
||||
#: evtest/ev_test.c:158 evtest/ev_test.c:166
|
||||
#, c-format
|
||||
msgid "Event @ %ld.%06ld: %s, value %d\n"
|
||||
msgstr ""
|
||||
|
|
@ -493,17 +521,17 @@ msgstr ""
|
|||
msgid "OK"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_main.c:64
|
||||
#: daemon/x52d_main.c:67
|
||||
#, c-format
|
||||
msgid "Error %d setting log file: %s\n"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_main.c:80
|
||||
#: daemon/x52d_main.c:83
|
||||
#, c-format
|
||||
msgid "Error %d installing handler for signal %d: %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_main.c:91
|
||||
#: daemon/x52d_main.c:94
|
||||
#, c-format
|
||||
msgid ""
|
||||
"Usage: %s [-f] [-v] [-q]\n"
|
||||
|
|
@ -513,88 +541,88 @@ msgid ""
|
|||
"\t[-b notify-socket-path]\n"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_main.c:124
|
||||
#: daemon/x52d_main.c:129
|
||||
#, c-format
|
||||
msgid "Daemon is already running as PID %u"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_main.c:266
|
||||
#: daemon/x52d_main.c:271
|
||||
#, c-format
|
||||
msgid "Unable to parse configuration override '%s'\n"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_main.c:298
|
||||
#: daemon/x52d_main.c:303
|
||||
#, c-format
|
||||
msgid "Foreground = %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_main.c:298 daemon/x52d_main.c:299
|
||||
#: daemon/x52d_main.c:303 daemon/x52d_main.c:304
|
||||
msgid "true"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_main.c:298 daemon/x52d_main.c:299
|
||||
#: daemon/x52d_main.c:303 daemon/x52d_main.c:304
|
||||
msgid "false"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_main.c:299
|
||||
#, c-format
|
||||
msgid "Quiet = %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_main.c:300
|
||||
#, c-format
|
||||
msgid "Verbosity = %d"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_main.c:301
|
||||
#, c-format
|
||||
msgid "Log file = %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_main.c:302
|
||||
#, c-format
|
||||
msgid "Config file = %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_main.c:303
|
||||
#, c-format
|
||||
msgid "PID file = %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_main.c:304
|
||||
#, c-format
|
||||
msgid "Command socket = %s"
|
||||
msgid "Quiet = %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_main.c:305
|
||||
#, c-format
|
||||
msgid "Verbosity = %d"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_main.c:306
|
||||
#, c-format
|
||||
msgid "Log file = %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_main.c:307
|
||||
#, c-format
|
||||
msgid "Config file = %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_main.c:308
|
||||
#, c-format
|
||||
msgid "PID file = %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_main.c:309
|
||||
#, c-format
|
||||
msgid "Command socket = %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_main.c:310
|
||||
#, c-format
|
||||
msgid "Notify socket = %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_main.c:316
|
||||
#: daemon/x52d_main.c:321
|
||||
#, c-format
|
||||
msgid "Error %d blocking signals on child threads: %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_main.c:335
|
||||
#: daemon/x52d_main.c:338
|
||||
#, c-format
|
||||
msgid "Error %d unblocking signals on child threads: %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_main.c:348
|
||||
#: daemon/x52d_main.c:351
|
||||
msgid "Reloading X52 configuration"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_main.c:355
|
||||
#: daemon/x52d_main.c:358
|
||||
msgid "Saving X52 configuration to disk"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_main.c:361
|
||||
#: daemon/x52d_main.c:364
|
||||
#, c-format
|
||||
msgid "Received termination signal %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_main.c:378
|
||||
#: daemon/x52d_main.c:379
|
||||
msgid "Shutting down X52 daemon"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -658,21 +686,21 @@ msgstr ""
|
|||
msgid "Setting date format to %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_clock.c:173
|
||||
#: daemon/x52d_clock.c:174
|
||||
msgid "Starting X52 clock manager thread"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_clock.c:184
|
||||
#: daemon/x52d_clock.c:185
|
||||
#, c-format
|
||||
msgid "Error %d retrieving current time: %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_clock.c:205
|
||||
#: daemon/x52d_clock.c:206
|
||||
#, c-format
|
||||
msgid "Error %d initializing clock thread: %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_clock.c:212
|
||||
#: daemon/x52d_clock.c:213
|
||||
msgid "Shutting down X52 clock manager thread"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -686,31 +714,31 @@ msgstr ""
|
|||
msgid "Short write to client %d; expected %d bytes, wrote %d bytes"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_command.c:414
|
||||
#: daemon/x52d_command.c:415
|
||||
#, c-format
|
||||
msgid "Error %d during command loop: %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_command.c:441
|
||||
#: daemon/x52d_command.c:442
|
||||
#, c-format
|
||||
msgid "Error creating command socket: %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_command.c:449
|
||||
#: daemon/x52d_command.c:450
|
||||
#, c-format
|
||||
msgid "Error marking command socket as nonblocking: %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_command.c:455
|
||||
#: daemon/x52d_command.c:456
|
||||
#, c-format
|
||||
msgid "Error listening on command socket: %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_command.c:459
|
||||
#: daemon/x52d_command.c:460
|
||||
msgid "Starting command processing thread"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_command.c:477
|
||||
#: daemon/x52d_command.c:478
|
||||
msgid "Shutting down command processing thread"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -772,67 +800,67 @@ msgstr ""
|
|||
msgid "Error processing override '%s.%s=%s'"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_device.c:36
|
||||
#: daemon/x52d_device.c:37
|
||||
msgid "Starting X52 device manager thread"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_device.c:43
|
||||
#: daemon/x52d_device.c:44
|
||||
#, c-format
|
||||
msgid "Error %d connecting to device: %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_device.c:52
|
||||
#: daemon/x52d_device.c:53
|
||||
msgid "Device connected, writing configuration"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_device.c:75
|
||||
#: daemon/x52d_device.c:76
|
||||
msgid "Initializing libx52"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_device.c:79
|
||||
#: daemon/x52d_device.c:80
|
||||
#, c-format
|
||||
msgid "Failure %d initializing libx52: %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_device.c:90
|
||||
#: daemon/x52d_device.c:91
|
||||
msgid "Shutting down X52 device manager thread"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_device.c:103
|
||||
#: daemon/x52d_device.c:104
|
||||
#, c-format
|
||||
msgid "Error %d when updating X52 parameter: %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_device.c:179
|
||||
#: daemon/x52d_device.c:180
|
||||
#, c-format
|
||||
msgid "Error %d when updating X52 device: %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_io.c:42
|
||||
#: daemon/x52d_io.c:43
|
||||
msgid "Starting X52 I/O thread"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_io.c:64
|
||||
#: daemon/x52d_io.c:65
|
||||
#, c-format
|
||||
msgid "Error %d opening X52 I/O device: %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_io.c:75
|
||||
#: daemon/x52d_io.c:76
|
||||
#, c-format
|
||||
msgid "Error %d reading from X52 I/O device: %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_io.c:102
|
||||
#: daemon/x52d_io.c:103
|
||||
#, c-format
|
||||
msgid "Error %d initializing X52 I/O library: %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_io.c:108
|
||||
#: daemon/x52d_io.c:109
|
||||
#, c-format
|
||||
msgid "Error %d initializing I/O driver thread: %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_io.c:115
|
||||
#: daemon/x52d_io.c:116
|
||||
msgid "Shutting down X52 I/O driver thread"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -841,60 +869,60 @@ msgstr ""
|
|||
msgid "Setting mouse enable to %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_mouse.c:47
|
||||
#: daemon/x52d_mouse.c:45
|
||||
#, c-format
|
||||
msgid "Ignoring mouse speed %d outside supported range (0-%d)"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_mouse.c:59
|
||||
#: daemon/x52d_mouse.c:57
|
||||
#, c-format
|
||||
msgid "Setting mouse speed to %d (delay %d ms, multiplier %f)"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_mouse.c:67
|
||||
#: daemon/x52d_mouse.c:65
|
||||
#, c-format
|
||||
msgid "Setting mouse reverse scroll to %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_mouse_evdev.c:43
|
||||
#: daemon/x52d_mouse_handler.c:43
|
||||
#, c-format
|
||||
msgid "Error writing mouse button event (button %d, state %d)"
|
||||
msgid "Error %d writing mouse button event (button %d, state %d)"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_mouse_evdev.c:69
|
||||
#: daemon/x52d_mouse_handler.c:81
|
||||
#, c-format
|
||||
msgid "Error writing mouse wheel event %d"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_mouse_evdev.c:102
|
||||
#: daemon/x52d_mouse_handler.c:120
|
||||
#, c-format
|
||||
msgid "Error writing mouse axis event (axis %d, value %d)"
|
||||
msgid "Error %d writing mouse axis event (dx %d, dy %d)"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_mouse_evdev.c:115
|
||||
#: daemon/x52d_mouse_handler.c:132
|
||||
msgid "Error writing mouse sync event"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_mouse_evdev.c:134
|
||||
#: daemon/x52d_mouse_handler.c:151
|
||||
msgid "Starting X52 virtual mouse driver thread"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_mouse_evdev.c:157
|
||||
#: daemon/x52d_mouse_handler.c:170
|
||||
#, c-format
|
||||
msgid "Error %d initializing mouse thread: %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_mouse_evdev.c:164
|
||||
#: daemon/x52d_mouse_handler.c:177
|
||||
msgid "Shutting down X52 virtual mouse driver thread"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_mouse_evdev.c:171
|
||||
#: daemon/x52d_mouse_handler.c:184
|
||||
msgid "Virtual mouse not created. Ignoring thread state change"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_mouse_evdev.c:236
|
||||
#: daemon/x52d_mouse_handler.c:236 daemon/x52d_mouse_handler.c:244
|
||||
#, c-format
|
||||
msgid "Error %d creating X52 virtual mouse: %s"
|
||||
msgid "Error %d creating X52 virtual mouse"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_notify.c:46
|
||||
|
|
@ -916,27 +944,27 @@ msgstr ""
|
|||
msgid "Error setting up notification socket"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_notify.c:80 daemon/x52d_notify.c:90
|
||||
#: daemon/x52d_notify.c:81 daemon/x52d_notify.c:91
|
||||
#, c-format
|
||||
msgid "Error %d reading from pipe: %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_notify.c:127
|
||||
#: daemon/x52d_notify.c:128
|
||||
#, c-format
|
||||
msgid "Error %d writing notification pipe: %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_notify.c:172
|
||||
#: daemon/x52d_notify.c:174
|
||||
#, c-format
|
||||
msgid "Error %d creating notification pipe: %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_notify.c:181
|
||||
#: daemon/x52d_notify.c:183
|
||||
#, c-format
|
||||
msgid "Error %d initializing notify thread: %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_notify.c:187
|
||||
#: daemon/x52d_notify.c:189
|
||||
#, c-format
|
||||
msgid "Error %d initializing notify listener: %s"
|
||||
msgstr ""
|
||||
|
|
@ -951,7 +979,7 @@ msgstr ""
|
|||
msgid "Argument length too long\n"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52ctl.c:142
|
||||
#: daemon/x52ctl.c:180
|
||||
#, c-format
|
||||
msgid "Running in interactive mode, ignoring extra arguments\n"
|
||||
msgstr ""
|
||||
|
|
|
|||
|
|
@ -1,3 +1,14 @@
|
|||
i18n.gettext(meson.project_name(),
|
||||
args: '--directory=' + meson.source_root(),
|
||||
args: [
|
||||
'--directory=' + meson.project_source_root(),
|
||||
'--msgid-bugs-address=https://github.com/nirenjan/libx52/issues',
|
||||
'--package-name=' + meson.project_name(),
|
||||
'--package-version=' + meson.project_version(),
|
||||
'--copyright-holder=Nirenjan Krishnan',
|
||||
'--keyword=_', '--keyword=N_',
|
||||
],
|
||||
languages: [
|
||||
'xx_PL',
|
||||
],
|
||||
install: true,
|
||||
)
|
||||
|
|
|
|||
195
po/xx_PL.po
195
po/xx_PL.po
|
|
@ -7,17 +7,17 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: libx52 0.2.3\n"
|
||||
"Report-Msgid-Bugs-To: https://github.com/nirenjan/libx52/issues\n"
|
||||
"POT-Creation-Date: 2026-03-12 08:31-0700\n"
|
||||
"PO-Revision-Date: 2023-01-04 08:40-0800\n"
|
||||
"POT-Creation-Date: 2026-03-27 20:52-0700\n"
|
||||
"PO-Revision-Date: 2026-03-27 08:33-0700\n"
|
||||
"Last-Translator: Nirenjan Krishnan <nirenjan@gmail.com>\n"
|
||||
"Language-Team: Dummy Language for testing i18n\n"
|
||||
"Language: xx_PL\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 3.0.1\n"
|
||||
"X-Generator: Poedit 3.4.2\n"
|
||||
|
||||
#: libx52/x52_strerror.c:23 libx52io/io_strings.c:101
|
||||
#: libx52/x52_strerror.c:23 libx52io/io_strings.c:101 vkm/vkm_common.c:25
|
||||
msgid "Success"
|
||||
msgstr "Uccesssay"
|
||||
|
||||
|
|
@ -29,7 +29,7 @@ msgstr "Initializationay ailurefay"
|
|||
msgid "Insufficient memory"
|
||||
msgstr "Insufficientay emorymay"
|
||||
|
||||
#: libx52/x52_strerror.c:26
|
||||
#: libx52/x52_strerror.c:26 vkm/vkm_common.c:29
|
||||
msgid "Invalid parameter"
|
||||
msgstr "Invaliday arameterpay"
|
||||
|
||||
|
|
@ -85,7 +85,7 @@ msgstr "Ipepay erroray"
|
|||
msgid "System call interrupted"
|
||||
msgstr "Ystemsay allcay interrupteday"
|
||||
|
||||
#: libx52/x52_strerror.c:66 libx52io/io_strings.c:125
|
||||
#: libx52/x52_strerror.c:66 libx52io/io_strings.c:125 vkm/vkm_common.c:52
|
||||
#, c-format
|
||||
msgid "Unknown error %d"
|
||||
msgstr "Unknownay erroray %d"
|
||||
|
|
@ -143,12 +143,12 @@ msgid "Unknown LED state %d"
|
|||
msgstr "Unknownay EDLay atestay %d"
|
||||
|
||||
#: libx52/x52_stringify.c:47 daemon/x52d_clock.c:29 daemon/x52d_mouse.c:32
|
||||
#: daemon/x52d_mouse.c:68
|
||||
#: daemon/x52d_mouse.c:66
|
||||
msgid "off"
|
||||
msgstr "offay"
|
||||
|
||||
#: libx52/x52_stringify.c:48 daemon/x52d_clock.c:29 daemon/x52d_mouse.c:32
|
||||
#: daemon/x52d_mouse.c:68
|
||||
#: daemon/x52d_mouse.c:66
|
||||
msgid "on"
|
||||
msgstr "onay"
|
||||
|
||||
|
|
@ -233,26 +233,56 @@ msgstr "I/O erroray"
|
|||
msgid "Read timeout"
|
||||
msgstr "Eadray imeouttay"
|
||||
|
||||
#: evtest/ev_test.c:109
|
||||
#: vkm/vkm_common.c:26
|
||||
#, fuzzy
|
||||
msgid "Unknown error"
|
||||
msgstr "Unknownay erroray %d"
|
||||
|
||||
#: vkm/vkm_common.c:27
|
||||
msgid "Not ready"
|
||||
msgstr ""
|
||||
|
||||
#: vkm/vkm_common.c:28
|
||||
msgid "Out of memory"
|
||||
msgstr ""
|
||||
|
||||
#: vkm/vkm_common.c:30
|
||||
#, fuzzy
|
||||
msgid "Not supported"
|
||||
msgstr "Operationay otnay upportedsay"
|
||||
|
||||
#: vkm/vkm_common.c:31
|
||||
msgid "Virtual device failure"
|
||||
msgstr ""
|
||||
|
||||
#: vkm/vkm_common.c:32
|
||||
msgid "Unable to write event"
|
||||
msgstr ""
|
||||
|
||||
#: vkm/vkm_common.c:33
|
||||
msgid "No state change"
|
||||
msgstr ""
|
||||
|
||||
#: evtest/ev_test.c:110
|
||||
#, c-format
|
||||
msgid "Device ID: vendor 0x%04x product 0x%04x version 0x%04x\n"
|
||||
msgstr "Eviceday IDay: endorvay 0x%04x oductpray 0x%04x ersionvay 0x%04x\n"
|
||||
|
||||
#: evtest/ev_test.c:113
|
||||
#: evtest/ev_test.c:114
|
||||
#, c-format
|
||||
msgid "Device name: \"%s %s\"\n"
|
||||
msgstr "Eviceday amenay: \"%s %s\"\n"
|
||||
|
||||
#: evtest/ev_test.c:116
|
||||
#: evtest/ev_test.c:117
|
||||
#, c-format
|
||||
msgid "Serial number: \"%s\"\n"
|
||||
msgstr "Erialsay umbernay: \"%s\"\n"
|
||||
|
||||
#: evtest/ev_test.c:117
|
||||
#: evtest/ev_test.c:118
|
||||
msgid "Testing (interrupt to exit)\n"
|
||||
msgstr "Estingtay (interruptay otay exitay)\n"
|
||||
|
||||
#: evtest/ev_test.c:157 evtest/ev_test.c:165
|
||||
#: evtest/ev_test.c:158 evtest/ev_test.c:166
|
||||
#, c-format
|
||||
msgid "Event @ %ld.%06ld: %s, value %d\n"
|
||||
msgstr "Eventay @ %ld.%06ld: %s, aluevay %d\n"
|
||||
|
|
@ -536,17 +566,17 @@ msgstr "Estingtay aracterchay 0x%02x..."
|
|||
msgid "OK"
|
||||
msgstr "OKay"
|
||||
|
||||
#: daemon/x52d_main.c:64
|
||||
#: daemon/x52d_main.c:67
|
||||
#, c-format
|
||||
msgid "Error %d setting log file: %s\n"
|
||||
msgstr "Erroray %d ettingsay oglay ilefay: %s\n"
|
||||
|
||||
#: daemon/x52d_main.c:80
|
||||
#: daemon/x52d_main.c:83
|
||||
#, c-format
|
||||
msgid "Error %d installing handler for signal %d: %s"
|
||||
msgstr "Erroray %d installingay andlerhay orfay ignalsay %d: %s"
|
||||
|
||||
#: daemon/x52d_main.c:91
|
||||
#: daemon/x52d_main.c:94
|
||||
#, c-format
|
||||
msgid ""
|
||||
"Usage: %s [-f] [-v] [-q]\n"
|
||||
|
|
@ -562,88 +592,88 @@ msgstr ""
|
|||
"\t[-b otifynay-ocketsay-athpay]\n"
|
||||
"\n"
|
||||
|
||||
#: daemon/x52d_main.c:124
|
||||
#: daemon/x52d_main.c:129
|
||||
#, c-format
|
||||
msgid "Daemon is already running as PID %u"
|
||||
msgstr "Aemonday isay alreadyay unningray asay IDPay %u"
|
||||
|
||||
#: daemon/x52d_main.c:266
|
||||
#: daemon/x52d_main.c:271
|
||||
#, c-format
|
||||
msgid "Unable to parse configuration override '%s'\n"
|
||||
msgstr "Unableay otay arsepay onfigurationcay overrideay '%s'\n"
|
||||
|
||||
#: daemon/x52d_main.c:298
|
||||
#: daemon/x52d_main.c:303
|
||||
#, c-format
|
||||
msgid "Foreground = %s"
|
||||
msgstr "Oregroundfay = %s"
|
||||
|
||||
#: daemon/x52d_main.c:298 daemon/x52d_main.c:299
|
||||
#: daemon/x52d_main.c:303 daemon/x52d_main.c:304
|
||||
msgid "true"
|
||||
msgstr "uetray"
|
||||
|
||||
#: daemon/x52d_main.c:298 daemon/x52d_main.c:299
|
||||
#: daemon/x52d_main.c:303 daemon/x52d_main.c:304
|
||||
msgid "false"
|
||||
msgstr "alsefay"
|
||||
|
||||
#: daemon/x52d_main.c:299
|
||||
#: daemon/x52d_main.c:304
|
||||
#, c-format
|
||||
msgid "Quiet = %s"
|
||||
msgstr "Uietqay = %s"
|
||||
|
||||
#: daemon/x52d_main.c:300
|
||||
#: daemon/x52d_main.c:305
|
||||
#, c-format
|
||||
msgid "Verbosity = %d"
|
||||
msgstr "Erbosityvay = %d"
|
||||
|
||||
#: daemon/x52d_main.c:301
|
||||
#: daemon/x52d_main.c:306
|
||||
#, c-format
|
||||
msgid "Log file = %s"
|
||||
msgstr "Oglay ilefay = %s"
|
||||
|
||||
#: daemon/x52d_main.c:302
|
||||
#: daemon/x52d_main.c:307
|
||||
#, c-format
|
||||
msgid "Config file = %s"
|
||||
msgstr "Onfigcay ilefay = %s"
|
||||
|
||||
#: daemon/x52d_main.c:303
|
||||
#: daemon/x52d_main.c:308
|
||||
#, c-format
|
||||
msgid "PID file = %s"
|
||||
msgstr "IDPay ilefay = %s"
|
||||
|
||||
#: daemon/x52d_main.c:304
|
||||
#: daemon/x52d_main.c:309
|
||||
#, c-format
|
||||
msgid "Command socket = %s"
|
||||
msgstr "Ommandcay ocketsay = %s"
|
||||
|
||||
#: daemon/x52d_main.c:305
|
||||
#: daemon/x52d_main.c:310
|
||||
#, c-format
|
||||
msgid "Notify socket = %s"
|
||||
msgstr "Otifynay ocketsay = %s"
|
||||
|
||||
#: daemon/x52d_main.c:316
|
||||
#: daemon/x52d_main.c:321
|
||||
#, c-format
|
||||
msgid "Error %d blocking signals on child threads: %s"
|
||||
msgstr "Erroray %d ockingblay ignalssay onay ildchay eadsthray: %s"
|
||||
|
||||
#: daemon/x52d_main.c:335
|
||||
#: daemon/x52d_main.c:338
|
||||
#, c-format
|
||||
msgid "Error %d unblocking signals on child threads: %s"
|
||||
msgstr "Erroray %d unblockingay ignalssay onay ildchay eadsthray: %s"
|
||||
|
||||
#: daemon/x52d_main.c:348
|
||||
#: daemon/x52d_main.c:351
|
||||
msgid "Reloading X52 configuration"
|
||||
msgstr "Eloadingray X52 onfigurationcay"
|
||||
|
||||
#: daemon/x52d_main.c:355
|
||||
#: daemon/x52d_main.c:358
|
||||
msgid "Saving X52 configuration to disk"
|
||||
msgstr "Avingsay X52 onfigurationcay otay iskday"
|
||||
|
||||
#: daemon/x52d_main.c:361
|
||||
#: daemon/x52d_main.c:364
|
||||
#, c-format
|
||||
msgid "Received termination signal %s"
|
||||
msgstr "Eceivedray erminationtay ignalsay %s"
|
||||
|
||||
#: daemon/x52d_main.c:378
|
||||
#: daemon/x52d_main.c:379
|
||||
msgid "Shutting down X52 daemon"
|
||||
msgstr "Uttingshay ownday X52 aemonday"
|
||||
|
||||
|
|
@ -710,21 +740,21 @@ msgstr "Ettingsay %s ockclay ormatfay otay %s"
|
|||
msgid "Setting date format to %s"
|
||||
msgstr "Ettingsay ateday ormatfay otay %s"
|
||||
|
||||
#: daemon/x52d_clock.c:173
|
||||
#: daemon/x52d_clock.c:174
|
||||
msgid "Starting X52 clock manager thread"
|
||||
msgstr "Artingstay X52 ockclay anagermay eadthray"
|
||||
|
||||
#: daemon/x52d_clock.c:184
|
||||
#: daemon/x52d_clock.c:185
|
||||
#, c-format
|
||||
msgid "Error %d retrieving current time: %s"
|
||||
msgstr "Erroray %d etrievingray urrentcay imetay: %s"
|
||||
|
||||
#: daemon/x52d_clock.c:205
|
||||
#: daemon/x52d_clock.c:206
|
||||
#, c-format
|
||||
msgid "Error %d initializing clock thread: %s"
|
||||
msgstr "Erroray %d initializingay ockclay eadthray: %s"
|
||||
|
||||
#: daemon/x52d_clock.c:212
|
||||
#: daemon/x52d_clock.c:213
|
||||
msgid "Shutting down X52 clock manager thread"
|
||||
msgstr "Uttingshay ownday X52 ockclay anagermay eadthray"
|
||||
|
||||
|
|
@ -739,31 +769,31 @@ msgid "Short write to client %d; expected %d bytes, wrote %d bytes"
|
|||
msgstr ""
|
||||
"Ortshay itewray otay ientclay %d; expecteday %d ytesbay, otewray %d ytesbay"
|
||||
|
||||
#: daemon/x52d_command.c:414
|
||||
#: daemon/x52d_command.c:415
|
||||
#, c-format
|
||||
msgid "Error %d during command loop: %s"
|
||||
msgstr "Erroray %d uringday ommandcay ooplay: %s"
|
||||
|
||||
#: daemon/x52d_command.c:441
|
||||
#: daemon/x52d_command.c:442
|
||||
#, c-format
|
||||
msgid "Error creating command socket: %s"
|
||||
msgstr "Erroray eatingcray ommandcay ocketsay: %s"
|
||||
|
||||
#: daemon/x52d_command.c:449
|
||||
#: daemon/x52d_command.c:450
|
||||
#, c-format
|
||||
msgid "Error marking command socket as nonblocking: %s"
|
||||
msgstr "Erroray arkingmay ommandcay ocketsay asay onblockingnay: %s"
|
||||
|
||||
#: daemon/x52d_command.c:455
|
||||
#: daemon/x52d_command.c:456
|
||||
#, c-format
|
||||
msgid "Error listening on command socket: %s"
|
||||
msgstr "Erroray isteninglay onay ommandcay ocketsay: %s"
|
||||
|
||||
#: daemon/x52d_command.c:459
|
||||
#: daemon/x52d_command.c:460
|
||||
msgid "Starting command processing thread"
|
||||
msgstr "Artingstay ommandcay ocessingpray eadthray"
|
||||
|
||||
#: daemon/x52d_command.c:477
|
||||
#: daemon/x52d_command.c:478
|
||||
msgid "Shutting down command processing thread"
|
||||
msgstr "Uttingshay ownday ommandcay ocessingpray eadthray"
|
||||
|
||||
|
|
@ -825,67 +855,67 @@ msgstr "Onay aluevay oundfay inay overrideay ingstray '%s'"
|
|||
msgid "Error processing override '%s.%s=%s'"
|
||||
msgstr "Erroray ocessingpray overriday '%s.%s=%s'"
|
||||
|
||||
#: daemon/x52d_device.c:36
|
||||
#: daemon/x52d_device.c:37
|
||||
msgid "Starting X52 device manager thread"
|
||||
msgstr "Artingstay X52 eviceday anagermay eadthray"
|
||||
|
||||
#: daemon/x52d_device.c:43
|
||||
#: daemon/x52d_device.c:44
|
||||
#, c-format
|
||||
msgid "Error %d connecting to device: %s"
|
||||
msgstr "Erroray %d onnectingcay otay eviceday: %s"
|
||||
|
||||
#: daemon/x52d_device.c:52
|
||||
#: daemon/x52d_device.c:53
|
||||
msgid "Device connected, writing configuration"
|
||||
msgstr "Eviceday onnectedcay, itingwray onfigurationcay"
|
||||
|
||||
#: daemon/x52d_device.c:75
|
||||
#: daemon/x52d_device.c:76
|
||||
msgid "Initializing libx52"
|
||||
msgstr "Initializingay libx52"
|
||||
|
||||
#: daemon/x52d_device.c:79
|
||||
#: daemon/x52d_device.c:80
|
||||
#, c-format
|
||||
msgid "Failure %d initializing libx52: %s"
|
||||
msgstr "Ailurefay %d initializeay libx52: %s"
|
||||
|
||||
#: daemon/x52d_device.c:90
|
||||
#: daemon/x52d_device.c:91
|
||||
msgid "Shutting down X52 device manager thread"
|
||||
msgstr "Uttingshay ownday X52 eviceday anagermay eadthray"
|
||||
|
||||
#: daemon/x52d_device.c:103
|
||||
#: daemon/x52d_device.c:104
|
||||
#, c-format
|
||||
msgid "Error %d when updating X52 parameter: %s"
|
||||
msgstr "Erroray %d enwhay updatingay X52 arameterpay: %s"
|
||||
|
||||
#: daemon/x52d_device.c:179
|
||||
#: daemon/x52d_device.c:180
|
||||
#, c-format
|
||||
msgid "Error %d when updating X52 device: %s"
|
||||
msgstr "Erroray %d enwhay updatingay X52 eviceday: %s"
|
||||
|
||||
#: daemon/x52d_io.c:42
|
||||
#: daemon/x52d_io.c:43
|
||||
msgid "Starting X52 I/O thread"
|
||||
msgstr "Artingstay X52 I/O eadthray"
|
||||
|
||||
#: daemon/x52d_io.c:64
|
||||
#: daemon/x52d_io.c:65
|
||||
#, c-format
|
||||
msgid "Error %d opening X52 I/O device: %s"
|
||||
msgstr "Erroray %d openingay X52 I/O eviceday: %s"
|
||||
|
||||
#: daemon/x52d_io.c:75
|
||||
#: daemon/x52d_io.c:76
|
||||
#, c-format
|
||||
msgid "Error %d reading from X52 I/O device: %s"
|
||||
msgstr "Erroray %d eadingray omfray X52 I/O eviceday: %s"
|
||||
|
||||
#: daemon/x52d_io.c:102
|
||||
#: daemon/x52d_io.c:103
|
||||
#, c-format
|
||||
msgid "Error %d initializing X52 I/O library: %s"
|
||||
msgstr "Erroray %d initializingay X52 ibrarylay: %s"
|
||||
|
||||
#: daemon/x52d_io.c:108
|
||||
#: daemon/x52d_io.c:109
|
||||
#, c-format
|
||||
msgid "Error %d initializing I/O driver thread: %s"
|
||||
msgstr "Erroray %d initializingay I/O iverdray eadthray: %s"
|
||||
|
||||
#: daemon/x52d_io.c:115
|
||||
#: daemon/x52d_io.c:116
|
||||
msgid "Shutting down X52 I/O driver thread"
|
||||
msgstr "Uttingshay ownday X52 I/O iverdray eadthray"
|
||||
|
||||
|
|
@ -894,61 +924,62 @@ msgstr "Uttingshay ownday X52 I/O iverdray eadthray"
|
|||
msgid "Setting mouse enable to %s"
|
||||
msgstr "Ettingsay ousemay enableay otay %s"
|
||||
|
||||
#: daemon/x52d_mouse.c:47
|
||||
#: daemon/x52d_mouse.c:45
|
||||
#, c-format
|
||||
msgid "Ignoring mouse speed %d outside supported range (0-%d)"
|
||||
msgstr "Ignoringay ousemay eedspay %d outsideay upportedsay angeray (0-%d)"
|
||||
|
||||
#: daemon/x52d_mouse.c:59
|
||||
#: daemon/x52d_mouse.c:57
|
||||
#, c-format
|
||||
msgid "Setting mouse speed to %d (delay %d ms, multiplier %f)"
|
||||
msgstr "Ettingsay ousemay eedspay otay %d (elayday %d ms, ultipliermay %f)"
|
||||
|
||||
#: daemon/x52d_mouse.c:67
|
||||
#: daemon/x52d_mouse.c:65
|
||||
#, c-format
|
||||
msgid "Setting mouse reverse scroll to %s"
|
||||
msgstr "Ettingsay ousemay everseray ollscray otay %s"
|
||||
|
||||
#: daemon/x52d_mouse_evdev.c:43
|
||||
#: daemon/x52d_mouse_handler.c:43
|
||||
#, c-format
|
||||
msgid "Error writing mouse button event (button %d, state %d)"
|
||||
msgstr "Erroray itingwray ousemay uttonbay eventay (uttonbay %d, atestay %d)"
|
||||
msgid "Error %d writing mouse button event (button %d, state %d)"
|
||||
msgstr ""
|
||||
"Erroray %d itingwray ousemay uttonbay eventay (uttonbay %d, atestay %d)"
|
||||
|
||||
#: daemon/x52d_mouse_evdev.c:69
|
||||
#: daemon/x52d_mouse_handler.c:81
|
||||
#, c-format
|
||||
msgid "Error writing mouse wheel event %d"
|
||||
msgstr "Erroray itingwray ousemay eelwhay eventay %d"
|
||||
|
||||
#: daemon/x52d_mouse_evdev.c:102
|
||||
#: daemon/x52d_mouse_handler.c:120
|
||||
#, c-format
|
||||
msgid "Error writing mouse axis event (axis %d, value %d)"
|
||||
msgstr "Erroray itingwray ousemay axisay eventay (axisay %d, aluevay %d)"
|
||||
msgid "Error %d writing mouse axis event (dx %d, dy %d)"
|
||||
msgstr "Erroray %d itingwray ousemay axisay eventay (xday %d, yday %d)"
|
||||
|
||||
#: daemon/x52d_mouse_evdev.c:115
|
||||
#: daemon/x52d_mouse_handler.c:132
|
||||
msgid "Error writing mouse sync event"
|
||||
msgstr "Erroray itingwray ousemay yncsay eventay"
|
||||
|
||||
#: daemon/x52d_mouse_evdev.c:134
|
||||
#: daemon/x52d_mouse_handler.c:151
|
||||
msgid "Starting X52 virtual mouse driver thread"
|
||||
msgstr "Artingstay X52 irtualvay ousemay iverdray eadthray"
|
||||
|
||||
#: daemon/x52d_mouse_evdev.c:157
|
||||
#: daemon/x52d_mouse_handler.c:170
|
||||
#, c-format
|
||||
msgid "Error %d initializing mouse thread: %s"
|
||||
msgstr "Erroray %d initializingay ousemay eadthray: %s"
|
||||
|
||||
#: daemon/x52d_mouse_evdev.c:164
|
||||
#: daemon/x52d_mouse_handler.c:177
|
||||
msgid "Shutting down X52 virtual mouse driver thread"
|
||||
msgstr "Uttingshay ownday X52 irtualvay ousemay iverdray eadthray"
|
||||
|
||||
#: daemon/x52d_mouse_evdev.c:171
|
||||
#: daemon/x52d_mouse_handler.c:184
|
||||
msgid "Virtual mouse not created. Ignoring thread state change"
|
||||
msgstr "Irtualvay ousemay otnay eatedcray. Ignoringa eadthray atestay angechay"
|
||||
|
||||
#: daemon/x52d_mouse_evdev.c:236
|
||||
#: daemon/x52d_mouse_handler.c:236 daemon/x52d_mouse_handler.c:244
|
||||
#, c-format
|
||||
msgid "Error %d creating X52 virtual mouse: %s"
|
||||
msgstr "Erroray %d eatingcray X52 irtualvay ousemay: %s"
|
||||
msgid "Error %d creating X52 virtual mouse"
|
||||
msgstr "Erroray %d eatingcray X52 irtualvay ousemay"
|
||||
|
||||
#: daemon/x52d_notify.c:46
|
||||
#, c-format
|
||||
|
|
@ -969,27 +1000,27 @@ msgstr "Erroray isteninglay onay otificationnay ocketsay: %s"
|
|||
msgid "Error setting up notification socket"
|
||||
msgstr "Erroray ettingsay upay otificationnay ocketsay: %s"
|
||||
|
||||
#: daemon/x52d_notify.c:80 daemon/x52d_notify.c:90
|
||||
#: daemon/x52d_notify.c:81 daemon/x52d_notify.c:91
|
||||
#, c-format
|
||||
msgid "Error %d reading from pipe: %s"
|
||||
msgstr "Erroray eadingray omfray ipepay %d: %s"
|
||||
|
||||
#: daemon/x52d_notify.c:127
|
||||
#: daemon/x52d_notify.c:128
|
||||
#, c-format
|
||||
msgid "Error %d writing notification pipe: %s"
|
||||
msgstr "Erroray %d itingwray otificationnay ipepay: %s"
|
||||
|
||||
#: daemon/x52d_notify.c:172
|
||||
#: daemon/x52d_notify.c:174
|
||||
#, c-format
|
||||
msgid "Error %d creating notification pipe: %s"
|
||||
msgstr "Erroray %d eatingcray otificationnay ipepay: %s"
|
||||
|
||||
#: daemon/x52d_notify.c:181
|
||||
#: daemon/x52d_notify.c:183
|
||||
#, c-format
|
||||
msgid "Error %d initializing notify thread: %s"
|
||||
msgstr "Erroray %d initializingay otifynay eadthray: %s"
|
||||
|
||||
#: daemon/x52d_notify.c:187
|
||||
#: daemon/x52d_notify.c:189
|
||||
#, c-format
|
||||
msgid "Error %d initializing notify listener: %s"
|
||||
msgstr "Erroray %d initializingay otifynay istenerlay: %s"
|
||||
|
|
@ -1004,7 +1035,7 @@ msgstr "Usageay: %s [-i] [-s ocketsay-athpay] [ommandcay]\n"
|
|||
msgid "Argument length too long\n"
|
||||
msgstr "Argumentay engthlay ootay onglay\n"
|
||||
|
||||
#: daemon/x52ctl.c:142
|
||||
#: daemon/x52ctl.c:180
|
||||
#, c-format
|
||||
msgid "Running in interactive mode, ignoring extra arguments\n"
|
||||
msgstr "Unningray inay interactiveay odemay, ignoringay extraay argumentsay\n"
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ static void print_time_difference(const char *type, struct timespec *ts)
|
|||
type, tp_usec, tp_nsec, ret.tv_sec, ret.tv_nsec);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
int main(void)
|
||||
{
|
||||
struct timespec ts_wall[2];
|
||||
struct timespec ts_cpu[2];
|
||||
|
|
|
|||
|
|
@ -21,39 +21,41 @@ level_class = ['nolvl', 'lvl']
|
|||
backtrace_class = ['notr', 'tr']
|
||||
|
||||
test_files = []
|
||||
test_name_template = '@0@-@1@-@2@-@3@'
|
||||
foreach test_type: ['bench', 'test']
|
||||
test_src = test_type + '_pinelog.c'
|
||||
foreach date_arg: [0, 1]
|
||||
date_def = '-DPINELOG_SHOW_DATE=' + date_arg.to_string()
|
||||
date_name = date_arg == 1 ? 'ts' : 'nots'
|
||||
test_name_template = '-@0@-@1@-@2@'
|
||||
foreach date_arg: [0, 1]
|
||||
date_def = '-DPINELOG_SHOW_DATE=' + date_arg.to_string()
|
||||
date_name = date_arg == 1 ? 'ts' : 'nots'
|
||||
|
||||
foreach level_arg: [0, 1]
|
||||
level_def = '-DPINELOG_SHOW_LEVEL=' + level_arg.to_string()
|
||||
level_name = level_arg == 1 ? 'lvl' : 'nolvl'
|
||||
foreach level_arg: [0, 1]
|
||||
level_def = '-DPINELOG_SHOW_LEVEL=' + level_arg.to_string()
|
||||
level_name = level_arg == 1 ? 'lvl' : 'nolvl'
|
||||
|
||||
foreach backtrace_arg: [0, 1]
|
||||
backtrace_def = '-DPINELOG_SHOW_BACKTRACE=' + backtrace_arg.to_string()
|
||||
backtrace_name = backtrace_arg == 1 ? 'tr' : 'notr'
|
||||
foreach backtrace_arg: [0, 1]
|
||||
backtrace_def = '-DPINELOG_SHOW_BACKTRACE=' + backtrace_arg.to_string()
|
||||
backtrace_name = backtrace_arg == 1 ? 'tr' : 'notr'
|
||||
|
||||
test_name = test_name_template.format(test_type,
|
||||
date_name, level_name, backtrace_name)
|
||||
test_exe = executable(test_name, test_src, 'pinelog.c',
|
||||
c_args: [
|
||||
'-DPINELOG_FATAL_STR="F"',
|
||||
'-DPINELOG_ERROR_STR="E"',
|
||||
'-DPINELOG_WARNING_STR="W"',
|
||||
'-DPINELOG_INFO_STR="I"',
|
||||
'-DPINELOG_DEBUG_STR="D"',
|
||||
'-DPINELOG_TRACE_STR="T"',
|
||||
'-DPINELOG_DEFAULT_LEVEL=PINELOG_LVL_TRACE',
|
||||
'-DPINELOG_DEFAULT_STREAM=stderr',
|
||||
'-DPINELOG_TEST',
|
||||
date_def, level_def, backtrace_def
|
||||
])
|
||||
c_args = [
|
||||
'-DPINELOG_FATAL_STR="F"',
|
||||
'-DPINELOG_ERROR_STR="E"',
|
||||
'-DPINELOG_WARNING_STR="W"',
|
||||
'-DPINELOG_INFO_STR="I"',
|
||||
'-DPINELOG_DEBUG_STR="D"',
|
||||
'-DPINELOG_TRACE_STR="T"',
|
||||
'-DPINELOG_DEFAULT_LEVEL=PINELOG_LVL_TRACE',
|
||||
'-DPINELOG_DEFAULT_STREAM=stderr',
|
||||
'-DPINELOG_TEST',
|
||||
date_def, level_def, backtrace_def
|
||||
]
|
||||
test_name = test_name_template.format(
|
||||
date_name, level_name, backtrace_name)
|
||||
|
||||
test(test_name, test_exe, protocol: 'tap')
|
||||
endforeach
|
||||
test_exe = executable('test' + test_name, 'test_pinelog.c', 'pinelog.c',
|
||||
c_args: c_args)
|
||||
test('test' + test_name, test_exe, protocol: 'tap')
|
||||
|
||||
bench_exe = executable('bench' + test_name, 'bench_pinelog.c', 'pinelog.c',
|
||||
c_args: c_args)
|
||||
benchmark('bench' + test_name, bench_exe, protocol: 'tap')
|
||||
endforeach
|
||||
endforeach
|
||||
endforeach
|
||||
|
|
|
|||
|
|
@ -348,6 +348,10 @@ void pinelog_log_message(int module, int level, const char *file, int line, cons
|
|||
#else
|
||||
fprintf(output_stream, "%s:%d ", file, line);
|
||||
#endif
|
||||
#else
|
||||
// Suppress unused parameter warnings
|
||||
(void)file;
|
||||
(void)line;
|
||||
#endif
|
||||
|
||||
/* Set the module name if it is not the root */
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ static size_t expected_len;
|
|||
|
||||
time_t time(time_t *p)
|
||||
{
|
||||
(void)p;
|
||||
// Override the time function from libc
|
||||
return 1636671600;
|
||||
}
|
||||
|
|
@ -196,7 +197,7 @@ static void tap_bailout(const char *msg)
|
|||
exit(1);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
int main(void)
|
||||
{
|
||||
int fifo_fd[2];
|
||||
int flags;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
# udev rules
|
||||
if dep_udev.found()
|
||||
if meson.version().version_compare('>= 0.58.0')
|
||||
udev_rules_dir = get_option('udev-rules-dir')
|
||||
if udev_rules_dir == ''
|
||||
udev_dir = dep_udev.get_variable('udevdir', default_value:'/lib/udev')
|
||||
else
|
||||
udev_dir = dep_udev.get_pkgconfig_variable('udevdir', default:'/lib/udev')
|
||||
udev_rules_dir = join_paths(udev_dir, 'rules.d')
|
||||
endif
|
||||
udev_rules_dir = join_paths(udev_dir, 'rules.d')
|
||||
udev_file = configure_file(
|
||||
input: '60-saitek-x52-x52pro.rules.in',
|
||||
output: '60-saitek-x52-x52pro.rules',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
Virtual keyboard/mouse infrastructure
|
||||
=====================================
|
||||
|
||||
The virtual keyboard/mouse infrastructure (or VKM), is an API used by an
|
||||
application to inject keyboard and mouse events into the OS stack. The advantage
|
||||
of using a separate API for this is so that this can be handled in a
|
||||
cross-platform manner without having to sprinkle `#ifdef`'s throughout the
|
||||
program.
|
||||
|
||||
Base API
|
||||
========
|
||||
|
||||
The API is based around a context, which is an opaque pointer returned by
|
||||
`vkm_init`. All subsequent VKM calls will take in this pointer as the first
|
||||
argument, and return a signed 32-bit status code. Once done, `vkm_exit` will
|
||||
clean up any data structures and close any file descriptors that were opened as
|
||||
part of the VKM calls.
|
||||
|
||||
VKM can also be configured through the `vkm_config` API call.
|
||||
|
||||
Device handling
|
||||
===============
|
||||
|
||||
`vkm_new_device` is the API to use when creating a new VKM device. While VKM
|
||||
will support both keyboard and mouse events from a single device, there may be
|
||||
cases where the application needs to separate out keyboard and mouse events into
|
||||
different devices. The flags will enable keyboard and/or mouse support.
|
||||
|
||||
Note that the supported event codes (on Linux) are fixed, and cannot be updated.
|
||||
The keyboard will emulate a standard US keyboard, while the mouse will emulate a
|
||||
standard 3 button mouse with a scroll wheel.
|
||||
|
||||
Mouse handling
|
||||
==============
|
||||
|
||||
The mouse is handled as a single API call that passes in dx and dy to move the
|
||||
mouse in the relative X and Y axes. VKM will take care of internally translating
|
||||
the calls to the appropriate framework. This is handled in `vkm_mouse_move`
|
||||
|
||||
The scroll wheel is handled through `vkm_mouse_scroll`. By default, the mouse
|
||||
motion uses standard scrolling, but high resolution scrolling may be enabled.
|
||||
|
||||
The buttons are handled by `vkm_mouse_click`. The API will send the state,
|
||||
depending on the input (pressed or not)
|
||||
|
||||
Keyboard handling
|
||||
=================
|
||||
|
||||
The keyboard is handled through a single call `vkm_keyboard_send`. This sends a
|
||||
single key event, with modifiers enabled (Ctrl, Shift, Alt, GUI).
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
vkm_version = '0.1.0'
|
||||
|
||||
vkm_files = files(
|
||||
'vkm_common.c'
|
||||
)
|
||||
|
||||
vkm_stub_files = files(
|
||||
'vkm_stub.c'
|
||||
)
|
||||
|
||||
if host_machine.system() == 'linux'
|
||||
vkm_dep = dep_evdev
|
||||
if dep_evdev.found()
|
||||
vkm_platform_files = files(
|
||||
'vkm_linux_evdev.c'
|
||||
)
|
||||
else
|
||||
vkm_platform_files = vkm_stub_files
|
||||
endif
|
||||
else
|
||||
vkm_dep = dependency('', required: false)
|
||||
vkm_platform_files = vkm_stub_files
|
||||
endif
|
||||
|
||||
lib_vkm = library('vkm', vkm_files + vkm_platform_files,
|
||||
install: true,
|
||||
version: vkm_version,
|
||||
c_args: sym_hidden_cargs,
|
||||
dependencies: [vkm_dep, dep_intl],
|
||||
include_directories: [includes])
|
||||
|
||||
vkm_strerror_test = executable('vkm-strerror-test',
|
||||
'test_strerror.c',
|
||||
'vkm_common.c',
|
||||
build_by_default: false,
|
||||
dependencies: [dep_cmocka, dep_intl],
|
||||
include_directories: [includes],
|
||||
)
|
||||
|
||||
test('vkm-strerror', vkm_strerror_test, protocol: 'tap')
|
||||
|
||||
if host_machine.system() == 'linux' and dep_evdev.found()
|
||||
dep_evdev_headers = dep_evdev.partial_dependency(compile_args: true, link_args: false)
|
||||
vkm_linux_evdev_test = executable('vkm_linux_evdev_test',
|
||||
files(
|
||||
'vkm_linux_evdev_test.c',
|
||||
'vkm_linux_evdev.c',
|
||||
'vkm_common.c',
|
||||
),
|
||||
include_directories: [includes],
|
||||
dependencies: [dep_cmocka, dep_evdev_headers, dep_intl],
|
||||
)
|
||||
test('vkm_linux_evdev', vkm_linux_evdev_test)
|
||||
endif
|
||||
|
||||
install_headers('vkm.h', subdir: 'vkm')
|
||||
pkgconfig.generate(lib_vkm,
|
||||
name: 'vkm',
|
||||
description: 'Linux/Unix library to control Saitek X52/X52Pro joystick extended functionality.',
|
||||
subdirs: meson.project_name(),
|
||||
version: vkm_version)
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* VKM — strerror unit tests
|
||||
*
|
||||
* Copyright (C) 2026 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
#include <setjmp.h>
|
||||
#include <cmocka.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "vkm.h"
|
||||
|
||||
static void test_strerror(void **state)
|
||||
{
|
||||
(void)state;
|
||||
|
||||
static const char *error_map[VKM_ERROR_MAX] = {
|
||||
[VKM_SUCCESS] = "Success",
|
||||
[VKM_ERROR_UNKNOWN] = "Unknown error",
|
||||
[VKM_ERROR_NOT_READY] = "Not ready",
|
||||
[VKM_ERROR_OUT_OF_MEMORY] = "Out of memory",
|
||||
[VKM_ERROR_INVALID_PARAM] = "Invalid parameter",
|
||||
[VKM_ERROR_NOT_SUPPORTED] = "Not supported",
|
||||
[VKM_ERROR_DEV_FAILURE] = "Virtual device failure",
|
||||
[VKM_ERROR_EVENT] = "Unable to write event",
|
||||
[VKM_ERROR_NO_CHANGE] = "No state change",
|
||||
};
|
||||
|
||||
static const char *unknown_fmt = "Unknown error %d";
|
||||
|
||||
char expected[256];
|
||||
|
||||
for (int i = -1; i <= (int)VKM_ERROR_MAX + 1; i++) {
|
||||
if (i < 0 || i >= (int)VKM_ERROR_MAX || error_map[i] == NULL) {
|
||||
snprintf(expected, sizeof(expected), unknown_fmt, i);
|
||||
} else {
|
||||
strncpy(expected, error_map[i], sizeof(expected));
|
||||
}
|
||||
assert_string_equal(expected, vkm_strerror(i));
|
||||
}
|
||||
}
|
||||
|
||||
static const struct CMUnitTest tests[] = {
|
||||
cmocka_unit_test(test_strerror),
|
||||
};
|
||||
|
||||
int main(void)
|
||||
{
|
||||
cmocka_set_message_output(CM_OUTPUT_TAP);
|
||||
cmocka_run_group_tests(tests, NULL, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* VKM common functions
|
||||
*
|
||||
* Copyright (C) 2026 Nirenjan Krishnan <nirenjan@nirenjan.org>
|
||||
*
|
||||
* SPDX-LicenseIdentifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "vkm.h"
|
||||
|
||||
#ifndef VKM_INTERNAL_H
|
||||
#define VKM_INTERNAL_H
|
||||
|
||||
struct vkm_mouse_button_state {
|
||||
vkm_button_state pressed[VKM_MOUSE_BTN_MAX];
|
||||
};
|
||||
|
||||
vkm_button_state _vkm_get_mouse_button_state(struct vkm_mouse_button_state *state, vkm_mouse_button button);
|
||||
void _vkm_set_mouse_button_state(struct vkm_mouse_button_state *sstate, vkm_mouse_button button, vkm_button_state state);
|
||||
|
||||
#endif // !defined VKM_INTERNAL_H
|
||||
|
|
@ -0,0 +1,619 @@
|
|||
/*
|
||||
* Virtual keyboard/mouse interface
|
||||
*
|
||||
* Copyright (C) 2026 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file vkm.h
|
||||
* @brief Functions, structures and enumerations for the virtual
|
||||
* keyboard/mouse interface library (VKM).
|
||||
*
|
||||
* This file contains the type, enum and function prototypes for VKM.
|
||||
* These functions allow an application to inject keyboard/mouse events
|
||||
* into the host OS, as long as it has the necessary permissions.
|
||||
*
|
||||
* @author Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*/
|
||||
#ifndef VKM_H
|
||||
#define VKM_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifndef VKM_API
|
||||
# if defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 303
|
||||
# define VKM_API __attribute__((visibility("default")))
|
||||
# elif defined(_WIN32)
|
||||
# define VKM_API __declspec(dllexport)
|
||||
# else
|
||||
# define VKM_API
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Opaque structure used by the VKM framework
|
||||
*/
|
||||
struct vkm_context;
|
||||
|
||||
/**
|
||||
* @brief Virtual device context structure used by the VKM framework
|
||||
*
|
||||
* All VKM API functions require the application to pass in a pointer to
|
||||
* a valid context structure. A pointer can be obtained by calling
|
||||
* \ref vkm_init
|
||||
*/
|
||||
typedef struct vkm_context vkm_context;
|
||||
|
||||
/**
|
||||
* @brief Return type used by VKM API functions
|
||||
*/
|
||||
typedef int32_t vkm_result;
|
||||
|
||||
/**
|
||||
* @brief Feature identifiers for \ref vkm_feature_supported
|
||||
*
|
||||
* Bit flags describing optional VKM capabilities on the current platform.
|
||||
* Pass one enumerator at a time to \ref vkm_feature_supported.
|
||||
*/
|
||||
typedef enum {
|
||||
VKM_FEAT_MOUSE = (1 << 0), /**< Relative mouse move, buttons, and wheel */
|
||||
|
||||
/** Full \ref vkm_keyboard_send key map (platform virtual keyboard) */
|
||||
VKM_FEAT_KEYBOARD = (1 << 1),
|
||||
|
||||
/** Separate left/right logical modifiers in \ref vkm_keyboard_send */
|
||||
VKM_FEAT_KEYBOARD_MODIFIERS = (1 << 2),
|
||||
|
||||
/* Additional flags may be added in the future */
|
||||
} vkm_feature;
|
||||
|
||||
/**
|
||||
* @brief Error code list
|
||||
*/
|
||||
typedef enum {
|
||||
/** No error, indicates success */
|
||||
VKM_SUCCESS = 0,
|
||||
|
||||
/** Unknown error, used as a catch-all error state */
|
||||
VKM_ERROR_UNKNOWN,
|
||||
|
||||
/** Not started, must call vkm_start first */
|
||||
VKM_ERROR_NOT_READY,
|
||||
|
||||
/** Out of memory */
|
||||
VKM_ERROR_OUT_OF_MEMORY,
|
||||
|
||||
/** Invalid parameter(s) */
|
||||
VKM_ERROR_INVALID_PARAM,
|
||||
|
||||
/** Not supported */
|
||||
VKM_ERROR_NOT_SUPPORTED,
|
||||
|
||||
/** Unable to create virtual devices */
|
||||
VKM_ERROR_DEV_FAILURE,
|
||||
|
||||
/** Unable to write event */
|
||||
VKM_ERROR_EVENT,
|
||||
|
||||
/** No state change in the event, please retry */
|
||||
VKM_ERROR_NO_CHANGE,
|
||||
|
||||
/* Maximum error states, do not use in external code*/
|
||||
VKM_ERROR_MAX,
|
||||
} vkm_error_code;
|
||||
|
||||
/**
|
||||
* @brief Return a short description for a \ref vkm_result / \ref vkm_error_code value
|
||||
*
|
||||
* The returned pointer refers to static storage and must not be freed. For
|
||||
* unrecognized codes, the same static buffer may be overwritten by a later call.
|
||||
*
|
||||
* When native language support (NLS) is enabled at build time, these messages
|
||||
* are translated like \ref libx52_strerror. Bind the \c libx52 text domain and
|
||||
* set \c LC_MESSAGES as for other libx52 components.
|
||||
*
|
||||
* @param[in] code Value returned from a VKM API function
|
||||
*
|
||||
* @returns Pointer to a NUL-terminated description string
|
||||
*/
|
||||
VKM_API const char *vkm_strerror(vkm_result code);
|
||||
|
||||
/**
|
||||
* @brief Option list
|
||||
*/
|
||||
typedef enum {
|
||||
/**
|
||||
* @brief Set the high resolution scrolling behavior of the mouse
|
||||
*
|
||||
* This option must be passed a boolean which lets VKM know whether to
|
||||
* enable or disable high resolution scrolling.
|
||||
*
|
||||
* Defaults to false. When enabled together with \ref vkm_start,
|
||||
* \ref vkm_mouse_scroll emits high-resolution REL_*_HI_RES events (120 units
|
||||
* per step) in addition to discrete REL_WHEEL / REL_HWHEEL ticks.
|
||||
*/
|
||||
VKM_OPT_HI_RES_SCROLL,
|
||||
|
||||
/**
|
||||
* @brief Enable or disable horizontal scrolling of the mouse
|
||||
*
|
||||
* This option must be passed in a boolean which lets VKM know whether to
|
||||
* enable or disable horizontal scrolling. If horizontal scrolling is
|
||||
* disabled, then any requests to \ref vkm_mouse_scroll with
|
||||
* \ref VKM_MOUSE_SCROLL_LEFT or \ref VKM_MOUSE_SCROLL_RIGHT will return
|
||||
* \ref VKM_ERROR_INVALID_PARAM.
|
||||
*
|
||||
* Defaults to false.
|
||||
*/
|
||||
VKM_OPT_HORIZONTAL_SCROLL,
|
||||
|
||||
/**
|
||||
* @brief Set the virtual device name in the system.
|
||||
*
|
||||
* This option sets the name of the virtual input device in the system.
|
||||
* If not set, the virtual device will have a name determined by the
|
||||
* timestamp at which it was initialized.
|
||||
*
|
||||
* Only applicable on Linux.
|
||||
*/
|
||||
VKM_OPT_DEVICE_NAME,
|
||||
|
||||
/* Max number of options, do not use in external code */
|
||||
VKM_OPT_MAX
|
||||
} vkm_option;
|
||||
|
||||
/**
|
||||
* @brief Button state
|
||||
*/
|
||||
typedef enum {
|
||||
/** Button is released */
|
||||
VKM_BUTTON_RELEASED,
|
||||
|
||||
/** Button is pressed */
|
||||
VKM_BUTTON_PRESSED,
|
||||
|
||||
/* Max number of button states, do not use in external code */
|
||||
VKM_BUTTON_STATE_MAX
|
||||
} vkm_button_state;
|
||||
|
||||
/**
|
||||
* @brief Mouse button identifiers
|
||||
*/
|
||||
typedef enum {
|
||||
/** Mouse left button */
|
||||
VKM_MOUSE_BTN_LEFT,
|
||||
|
||||
/** Mouse right button */
|
||||
VKM_MOUSE_BTN_RIGHT,
|
||||
|
||||
/** Mouse middle button */
|
||||
VKM_MOUSE_BTN_MIDDLE,
|
||||
|
||||
/* Max number of mouse buttons, do not use in external code */
|
||||
VKM_MOUSE_BTN_MAX
|
||||
} vkm_mouse_button;
|
||||
|
||||
/**
|
||||
* @brief Scroll directions
|
||||
*/
|
||||
typedef enum {
|
||||
/** Scroll up */
|
||||
VKM_MOUSE_SCROLL_UP,
|
||||
|
||||
/** Scroll down */
|
||||
VKM_MOUSE_SCROLL_DOWN,
|
||||
|
||||
/** Scroll left (horizontal scrolling) */
|
||||
VKM_MOUSE_SCROLL_LEFT,
|
||||
|
||||
/** Scroll right (horizontal scrolling) */
|
||||
VKM_MOUSE_SCROLL_RIGHT,
|
||||
|
||||
/* Maximum number of scroll states, do not use in external code */
|
||||
VKM_MOUSE_SCROLL_MAX
|
||||
} vkm_mouse_scroll_direction;
|
||||
|
||||
/**
|
||||
* @brief Physical key identifiers (105-key ISO / “international” PC set)
|
||||
*
|
||||
* Logical key codes for \ref vkm_keyboard_send. Identifiers follow the USB HID
|
||||
* Usage Tables (keyboard/keypad page) in meaning; enumerator values are
|
||||
* arbitrary and are not HID usage IDs.
|
||||
*
|
||||
* Shift keys are listed with the alphanumeric block; other modifiers are grouped
|
||||
* at the start of the enumerator list.
|
||||
*/
|
||||
typedef enum {
|
||||
VKM_KEY_NONE = 0, /**< Sentinel: no key (e.g. modifier-only update) */
|
||||
|
||||
VKM_KEY_LEFT_CTRL, /**< Left Control */
|
||||
VKM_KEY_LEFT_ALT, /**< Left Alt */
|
||||
VKM_KEY_LEFT_GUI, /**< Left GUI / Meta / Windows key */
|
||||
VKM_KEY_RIGHT_CTRL, /**< Right Control */
|
||||
VKM_KEY_RIGHT_ALT, /**< Right Alt / AltGr */
|
||||
VKM_KEY_RIGHT_GUI, /**< Right GUI / Meta */
|
||||
VKM_KEY_APPLICATION, /**< Application / Menu key */
|
||||
|
||||
VKM_KEY_ESCAPE, /**< Escape */
|
||||
VKM_KEY_F1, /**< F1 */
|
||||
VKM_KEY_F2, /**< F2 */
|
||||
VKM_KEY_F3, /**< F3 */
|
||||
VKM_KEY_F4, /**< F4 */
|
||||
VKM_KEY_F5, /**< F5 */
|
||||
VKM_KEY_F6, /**< F6 */
|
||||
VKM_KEY_F7, /**< F7 */
|
||||
VKM_KEY_F8, /**< F8 */
|
||||
VKM_KEY_F9, /**< F9 */
|
||||
VKM_KEY_F10, /**< F10 */
|
||||
VKM_KEY_F11, /**< F11 */
|
||||
VKM_KEY_F12, /**< F12 */
|
||||
|
||||
VKM_KEY_GRAVE_ACCENT, /**< Grave accent / tilde (` and ~ on US) */
|
||||
VKM_KEY_1, /**< \c 1 / ! */
|
||||
VKM_KEY_2, /**< \c 2 / @ */
|
||||
VKM_KEY_3, /**< \c 3 / # */
|
||||
VKM_KEY_4, /**< \c 4 / $ */
|
||||
VKM_KEY_5, /**< \c 5 / % */
|
||||
VKM_KEY_6, /**< \c 6 / ^ */
|
||||
VKM_KEY_7, /**< \c 7 / & */
|
||||
VKM_KEY_8, /**< \c 8 / * */
|
||||
VKM_KEY_9, /**< \c 9 / ( */
|
||||
VKM_KEY_0, /**< \c 0 / ) */
|
||||
VKM_KEY_MINUS, /**< Minus / underscore */
|
||||
VKM_KEY_EQUAL, /**< Equals / plus */
|
||||
VKM_KEY_BACKSPACE, /**< Backspace */
|
||||
|
||||
VKM_KEY_TAB, /**< Tab */
|
||||
VKM_KEY_Q, /**< \c Q */
|
||||
VKM_KEY_W, /**< \c W */
|
||||
VKM_KEY_E, /**< \c E */
|
||||
VKM_KEY_R, /**< \c R */
|
||||
VKM_KEY_T, /**< \c T */
|
||||
VKM_KEY_Y, /**< \c Y */
|
||||
VKM_KEY_U, /**< \c U */
|
||||
VKM_KEY_I, /**< \c I */
|
||||
VKM_KEY_O, /**< \c O */
|
||||
VKM_KEY_P, /**< \c P */
|
||||
VKM_KEY_LEFT_BRACKET, /**< Left bracket / brace */
|
||||
VKM_KEY_RIGHT_BRACKET, /**< Right bracket / brace */
|
||||
VKM_KEY_BACKSLASH, /**< Backslash / pipe (US placement; JIS Yen) */
|
||||
|
||||
VKM_KEY_CAPS_LOCK, /**< Caps Lock */
|
||||
VKM_KEY_A, /**< \c A */
|
||||
VKM_KEY_S, /**< \c S */
|
||||
VKM_KEY_D, /**< \c D */
|
||||
VKM_KEY_F, /**< \c F */
|
||||
VKM_KEY_G, /**< \c G */
|
||||
VKM_KEY_H, /**< \c H */
|
||||
VKM_KEY_J, /**< \c J */
|
||||
VKM_KEY_K, /**< \c K */
|
||||
VKM_KEY_L, /**< \c L */
|
||||
VKM_KEY_SEMICOLON, /**< Semicolon / colon */
|
||||
VKM_KEY_APOSTROPHE, /**< Apostrophe / quote */
|
||||
VKM_KEY_NONUS_HASH, /**< ISO non-US # / ~ (HID usage) */
|
||||
VKM_KEY_ENTER, /**< Return / Enter */
|
||||
|
||||
VKM_KEY_LEFT_SHIFT, /**< Left Shift */
|
||||
VKM_KEY_INTL_BACKSLASH, /**< ISO extra key (e.g. \| between left Shift and Z) */
|
||||
VKM_KEY_Z, /**< \c Z */
|
||||
VKM_KEY_X, /**< \c X */
|
||||
VKM_KEY_C, /**< \c C */
|
||||
VKM_KEY_V, /**< \c V */
|
||||
VKM_KEY_B, /**< \c B */
|
||||
VKM_KEY_N, /**< \c N */
|
||||
VKM_KEY_M, /**< \c M */
|
||||
VKM_KEY_COMMA, /**< Comma / less-than */
|
||||
VKM_KEY_PERIOD, /**< Period / greater-than */
|
||||
VKM_KEY_SLASH, /**< Slash / question */
|
||||
VKM_KEY_RIGHT_SHIFT, /**< Right Shift */
|
||||
|
||||
VKM_KEY_SPACE, /**< Space bar */
|
||||
|
||||
VKM_KEY_PRINT_SCREEN, /**< Print Screen */
|
||||
VKM_KEY_SCROLL_LOCK, /**< Scroll Lock */
|
||||
VKM_KEY_PAUSE, /**< Pause / Break */
|
||||
|
||||
VKM_KEY_INSERT, /**< Insert */
|
||||
VKM_KEY_HOME, /**< Home */
|
||||
VKM_KEY_PAGE_UP, /**< Page Up */
|
||||
VKM_KEY_DELETE_FORWARD, /**< Delete (forward) */
|
||||
VKM_KEY_END, /**< End */
|
||||
VKM_KEY_PAGE_DOWN, /**< Page Down */
|
||||
|
||||
VKM_KEY_RIGHT_ARROW, /**< Arrow right */
|
||||
VKM_KEY_LEFT_ARROW, /**< Arrow left */
|
||||
VKM_KEY_DOWN_ARROW, /**< Arrow down */
|
||||
VKM_KEY_UP_ARROW, /**< Arrow up */
|
||||
|
||||
VKM_KEY_KEYPAD_NUM_LOCK, /**< Keypad Num Lock */
|
||||
VKM_KEY_KEYPAD_DIVIDE, /**< Keypad \c / */
|
||||
VKM_KEY_KEYPAD_MULTIPLY,/**< Keypad \c * */
|
||||
VKM_KEY_KEYPAD_MINUS, /**< Keypad \c - */
|
||||
VKM_KEY_KEYPAD_PLUS, /**< Keypad \c + */
|
||||
VKM_KEY_KEYPAD_ENTER, /**< Keypad Enter */
|
||||
VKM_KEY_KEYPAD_1, /**< Keypad \c 1 / End */
|
||||
VKM_KEY_KEYPAD_2, /**< Keypad \c 2 / Down */
|
||||
VKM_KEY_KEYPAD_3, /**< Keypad \c 3 / Page Down */
|
||||
VKM_KEY_KEYPAD_4, /**< Keypad \c 4 / Left */
|
||||
VKM_KEY_KEYPAD_5, /**< Keypad \c 5 */
|
||||
VKM_KEY_KEYPAD_6, /**< Keypad \c 6 / Right */
|
||||
VKM_KEY_KEYPAD_7, /**< Keypad \c 7 / Home */
|
||||
VKM_KEY_KEYPAD_8, /**< Keypad \c 8 / Up */
|
||||
VKM_KEY_KEYPAD_9, /**< Keypad \c 9 / Page Up */
|
||||
VKM_KEY_KEYPAD_0, /**< Keypad \c 0 / Insert */
|
||||
VKM_KEY_KEYPAD_DECIMAL, /**< Keypad decimal / Delete */
|
||||
VKM_KEY_KEYPAD_COMMA, /**< Keypad comma (locale-specific layouts) */
|
||||
|
||||
VKM_KEY_MAX /**< Past last key; do not use in application code */
|
||||
} vkm_key;
|
||||
|
||||
/**
|
||||
* @brief Modifier bitmask for \ref vkm_keyboard_send
|
||||
*
|
||||
* Left and right modifier keys use the same bit layout as the USB HID keyboard
|
||||
* modifier byte. Combine values with bitwise OR. These are separate from
|
||||
* physical modifier key events in \ref vkm_key.
|
||||
*
|
||||
* Convenience macros \c VKM_KEY_MOD_CTRL, \c VKM_KEY_MOD_SHIFT, \c VKM_KEY_MOD_ALT,
|
||||
* and \c VKM_KEY_MOD_GUI alias the left-hand modifiers only; use
|
||||
* \c VKM_KEY_MOD_L* / \c VKM_KEY_MOD_R* when a specific side is required.
|
||||
*/
|
||||
typedef enum {
|
||||
VKM_KEY_MOD_NONE = 0, /**< No modifiers */
|
||||
|
||||
VKM_KEY_MOD_LCTRL = (1 << 0), /**< Left Control (HID modifier byte bit 0) */
|
||||
VKM_KEY_MOD_LSHIFT = (1 << 1), /**< Left Shift (bit 1) */
|
||||
VKM_KEY_MOD_LALT = (1 << 2), /**< Left Alt (bit 2) */
|
||||
VKM_KEY_MOD_LGUI = (1 << 3), /**< Left GUI / Meta (bit 3) */
|
||||
VKM_KEY_MOD_RCTRL = (1 << 4), /**< Right Control (bit 4) */
|
||||
VKM_KEY_MOD_RSHIFT = (1 << 5), /**< Right Shift (bit 5) */
|
||||
VKM_KEY_MOD_RALT = (1 << 6), /**< Right Alt / AltGr (bit 6) */
|
||||
VKM_KEY_MOD_RGUI = (1 << 7), /**< Right GUI / Meta (bit 7) */
|
||||
} vkm_key_modifiers;
|
||||
|
||||
/** Convenience alias for \ref VKM_KEY_MOD_LCTRL */
|
||||
#define VKM_KEY_MOD_CTRL VKM_KEY_MOD_LCTRL
|
||||
/** Convenience alias for \ref VKM_KEY_MOD_LSHIFT */
|
||||
#define VKM_KEY_MOD_SHIFT VKM_KEY_MOD_LSHIFT
|
||||
/** Convenience alias for \ref VKM_KEY_MOD_LALT */
|
||||
#define VKM_KEY_MOD_ALT VKM_KEY_MOD_LALT
|
||||
/** Convenience alias for \ref VKM_KEY_MOD_LGUI */
|
||||
#define VKM_KEY_MOD_GUI VKM_KEY_MOD_LGUI
|
||||
|
||||
/**
|
||||
* @brief Physical key action (press or release) for \ref vkm_keyboard_send
|
||||
*
|
||||
* Determines the \c value sent with the non-modifier \ref vkm_key (and, on
|
||||
* release, how the modifier mask is reconciled after the key event).
|
||||
*/
|
||||
typedef enum {
|
||||
VKM_KEY_STATE_RELEASED, /**< Key or button is up */
|
||||
VKM_KEY_STATE_PRESSED, /**< Key or button is down */
|
||||
VKM_KEY_STATE_MAX /**< Sentinel; do not use in application code */
|
||||
} vkm_key_state;
|
||||
|
||||
/**
|
||||
* @brief Initialize the VKM library
|
||||
*
|
||||
* This function initializes the VKM library, sets up any internal data
|
||||
* structures to send input events, and returns a \ref vkm_context pointer
|
||||
* in the output parameter. All calls to VKM use the returned pointer to
|
||||
* inject keyboard/mouse events.
|
||||
*
|
||||
* @par Example
|
||||
* @code
|
||||
* vkm_result rc;
|
||||
* vkm_context *ctx;
|
||||
* rc = vkm_init(&ctx);
|
||||
* if (rc != LIBX52_SUCCESS) {
|
||||
* // Error handling omitted for brevity
|
||||
* }
|
||||
* // Save ctx for use later
|
||||
* @endcode
|
||||
*
|
||||
* @param[out] ctx Pointer to a \ref vkm_context *. This function will
|
||||
* allocate a context and return the pointer to the context in this variable.
|
||||
*
|
||||
* @returns \ref vkm_error_code indicating status
|
||||
*/
|
||||
VKM_API vkm_result vkm_init(vkm_context **ctx);
|
||||
|
||||
/**
|
||||
* @brief Exit the VKM library and free up any resources used
|
||||
*
|
||||
* This function calls \ref vkm_reset, releases any resources allocated by
|
||||
* \ref vkm_init, and terminates the library. Using the freed context after
|
||||
* this returns is invalid and can cause errors.
|
||||
*
|
||||
* @param[in] ctx Context pointer
|
||||
*/
|
||||
VKM_API void vkm_exit(vkm_context *ctx);
|
||||
|
||||
/**
|
||||
* @brief Release all virtual keys and mouse buttons that are still down
|
||||
*
|
||||
* Synthesizes release events for any keys pressed through \ref vkm_keyboard_send,
|
||||
* clears the logical modifier mask from that API, and releases mouse buttons that
|
||||
* are still pressed. Internal bookkeeping is cleared even if the virtual device
|
||||
* is not ready (no events are written until \ref vkm_start succeeds).
|
||||
*
|
||||
* \ref vkm_exit calls this automatically before freeing the context.
|
||||
*
|
||||
* @param[in] ctx Context pointer
|
||||
*
|
||||
* @returns
|
||||
* - \ref VKM_SUCCESS on success or if there was nothing to release
|
||||
* - \ref VKM_ERROR_INVALID_PARAM on bad pointer
|
||||
* - \ref VKM_ERROR_EVENT if writing a release event failed
|
||||
*/
|
||||
VKM_API vkm_result vkm_reset(vkm_context *ctx);
|
||||
|
||||
/**
|
||||
* @brief Start any virtual keyboard/mouse devices on the platform
|
||||
*
|
||||
* This must be done before injecting any events, and after setting all
|
||||
* options through \ref vkm_set_option.
|
||||
*
|
||||
* @param[in] ctx Context pointer
|
||||
*
|
||||
* @returns
|
||||
* - \ref VKM_SUCCESS on successful start
|
||||
* - \ref VKM_ERROR_INVALID_PARAM on bad pointer
|
||||
* - \ref VKM_ERROR_UNKNOWN on other errors
|
||||
*/
|
||||
VKM_API vkm_result vkm_start(vkm_context *ctx);
|
||||
|
||||
/**
|
||||
* @brief check if VKM is started and ready
|
||||
*
|
||||
* @param[in] ctx Context pointer
|
||||
*
|
||||
* @returns boolean indicating if ready or not.
|
||||
*/
|
||||
VKM_API bool vkm_is_ready(vkm_context *ctx);
|
||||
|
||||
/**
|
||||
* @brief Check if VKM is supported on this platform
|
||||
*
|
||||
* On some platforms, there is no support yet for the virtual keyboard/mouse.
|
||||
* This function will return a boolean indicating if it is supported or not.
|
||||
*
|
||||
* @returns boolean indicating support.
|
||||
*/
|
||||
VKM_API bool vkm_platform_supported(void);
|
||||
|
||||
/**
|
||||
* @brief Check if a particular feature is enabled on this platform
|
||||
*
|
||||
* Features may be limited on a per-platform basis.
|
||||
*
|
||||
* @param[in] feat Feature identifier
|
||||
*
|
||||
* @returns boolean indicating if feature is supported or not.
|
||||
*/
|
||||
VKM_API bool vkm_feature_supported(vkm_feature feat);
|
||||
|
||||
/**
|
||||
* @brief Set an option flag for VKM.
|
||||
*
|
||||
* Option flags control the behavior of VKM. All options must be set before
|
||||
* calling \ref vkm_start.
|
||||
*
|
||||
* @param[in] ctx Context pointer
|
||||
* @param[in] option Which option to set
|
||||
* @param[in] ... Any required arguments for the specified option
|
||||
*
|
||||
* @returns
|
||||
* - \ref VKM_SUCCESS on success
|
||||
* - \ref VKM_ERROR_INVALID_PARAM if the option or arguments are invalid
|
||||
* - \ref VKM_ERROR_NOT_SUPPORTED if the option is valid but not supported on this platform
|
||||
*/
|
||||
VKM_API vkm_result vkm_set_option(vkm_context *ctx, vkm_option option, ...);
|
||||
|
||||
/**
|
||||
* @brief Move the mouse by the specified amount
|
||||
*
|
||||
* The move mouse takes in a delta of x and y coordinates that tell the system
|
||||
* to move the mouse by those relative numbers.
|
||||
*
|
||||
* @param[in] ctx Context pointer
|
||||
* @param[in] dx Delta by which to move the mouse in the horizontal axis
|
||||
* @param[in] dy Delta by which to move the mouse in the vertical axis
|
||||
*
|
||||
* @returns
|
||||
* - \ref VKM_SUCCESS on successful move
|
||||
* - \ref VKM_ERROR_UNKNOWN on a generic error
|
||||
* - \ref VKM_ERROR_NOT_SUPPORTED if the mouse move is not supported on this platform
|
||||
* - \ref VKM_ERROR_NOT_READY if VKM is not started
|
||||
*/
|
||||
VKM_API vkm_result vkm_mouse_move(vkm_context *ctx, int dx, int dy);
|
||||
|
||||
/**
|
||||
* @brief Click the mouse button
|
||||
*
|
||||
* Send a mouse button event, this may be either a button down or button up event.
|
||||
*
|
||||
* @param[in] ctx Context pointer
|
||||
* @param[in] button Button identifier
|
||||
* @param[in] state Button state (press or release)
|
||||
*
|
||||
* @returns
|
||||
* - \ref VKM_SUCCESS on successful move
|
||||
* - \ref VKM_ERROR_UNKNOWN on a generic error
|
||||
* - \ref VKM_ERROR_NOT_SUPPORTED if the mouse button click is not supported on this platform
|
||||
* - \ref VKM_ERROR_NOT_READY if VKM is not started
|
||||
*/
|
||||
VKM_API vkm_result vkm_mouse_click(vkm_context *ctx, vkm_mouse_button button, vkm_button_state state);
|
||||
|
||||
/**
|
||||
* @brief Scroll the mouse wheel
|
||||
*
|
||||
* Send a single scroll event to the mouse wheel (one detent in the chosen
|
||||
* direction). If \ref VKM_OPT_HI_RES_SCROLL was enabled before \ref vkm_start,
|
||||
* also emits REL_WHEEL_HI_RES / REL_HWHEEL_HI_RES using the standard 120
|
||||
* units per detent scale before the corresponding discrete REL_WHEEL /
|
||||
* REL_HWHEEL event.
|
||||
*
|
||||
* @param[in] ctx Context pointer
|
||||
* @param[in] dir Scroll direction
|
||||
*
|
||||
* @returns
|
||||
* - \ref VKM_SUCCESS on successful move
|
||||
* - \ref VKM_ERROR_UNKNOWN on a generic error
|
||||
* - \ref VKM_ERROR_INVALID_PARAM if horizontal scrolling is not enabled
|
||||
* and \p dir is \ref VKM_MOUSE_SCROLL_LEFT or \ref VKM_MOUSE_SCROLL_RIGHT
|
||||
* - \ref VKM_ERROR_NOT_SUPPORTED if the mouse scrolling is not supported on this platform
|
||||
* - \ref VKM_ERROR_NOT_READY if VKM is not started
|
||||
*/
|
||||
VKM_API vkm_result vkm_mouse_scroll(vkm_context *ctx, vkm_mouse_scroll_direction dir);
|
||||
|
||||
/**
|
||||
* @brief Send a single keyboard event
|
||||
*
|
||||
* Send a single keyboard event to the OS. This will send a single key event,
|
||||
* with modifiers enabled (Ctrl, Shift, Alt, GUI).
|
||||
*
|
||||
* @param[in] ctx Context pointer
|
||||
* @param[in] key Key identifier
|
||||
* @param[in] modifiers Modifier keys to enable (Ctrl, Shift, Alt, GUI)
|
||||
* @param[in] state Key state (press or release)
|
||||
*
|
||||
* @returns
|
||||
* - \ref VKM_SUCCESS on successful send
|
||||
* - \ref VKM_ERROR_UNKNOWN on a generic error
|
||||
* - \ref VKM_ERROR_INVALID_PARAM if parameters are invalid
|
||||
* - \ref VKM_ERROR_NOT_SUPPORTED if the keyboard event is not enabled or
|
||||
* supported on this platform
|
||||
* - \ref VKM_ERROR_NOT_READY if VKM is not started
|
||||
*/
|
||||
VKM_API vkm_result vkm_keyboard_send(vkm_context *ctx, vkm_key key, vkm_key_modifiers modifiers, vkm_key_state state);
|
||||
|
||||
/**
|
||||
* @brief Send a sync packet to the OS
|
||||
*
|
||||
* On some platforms, a sync packet is necessary for the previously injected
|
||||
* events to actually get reflected in the system. For platforms where this
|
||||
* is not needed, this is a noop.
|
||||
*
|
||||
* @param[in] ctx Context pointer
|
||||
*
|
||||
* @returns
|
||||
* - \ref VKM_SUCCESS on successful move
|
||||
* - \ref VKM_ERROR_UNKNOWN on a generic error
|
||||
* - \ref VKM_ERROR_INVALID_PARAM if parameters are invalid
|
||||
* - \ref VKM_ERROR_NOT_READY if VKM is not started
|
||||
*/
|
||||
VKM_API vkm_result vkm_sync(vkm_context *ctx);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // !defined VKM_H
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* VKM common functions
|
||||
*
|
||||
* Copyright (C) 2026 Nirenjan Krishnan <nirenjan@nirenjan.org>
|
||||
*
|
||||
* SPDX-LicenseIdentifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "gettext.h"
|
||||
|
||||
#include "vkm-internal.h"
|
||||
|
||||
#define N_(str) gettext_noop(str)
|
||||
#define _(str) dgettext(PACKAGE, str)
|
||||
|
||||
/* Error buffer used for building custom error strings */
|
||||
static char error_buffer[256];
|
||||
|
||||
/* List of error strings (indices must match \ref vkm_error_code) */
|
||||
static const char *error_string[] = {
|
||||
N_("Success"),
|
||||
N_("Unknown error"),
|
||||
N_("Not ready"),
|
||||
N_("Out of memory"),
|
||||
N_("Invalid parameter"),
|
||||
N_("Not supported"),
|
||||
N_("Virtual device failure"),
|
||||
N_("Unable to write event"),
|
||||
N_("No state change"),
|
||||
};
|
||||
|
||||
const char *vkm_strerror(vkm_result code)
|
||||
{
|
||||
switch ((vkm_error_code)code) {
|
||||
case VKM_SUCCESS:
|
||||
case VKM_ERROR_UNKNOWN:
|
||||
case VKM_ERROR_NOT_READY:
|
||||
case VKM_ERROR_OUT_OF_MEMORY:
|
||||
case VKM_ERROR_INVALID_PARAM:
|
||||
case VKM_ERROR_NOT_SUPPORTED:
|
||||
case VKM_ERROR_DEV_FAILURE:
|
||||
case VKM_ERROR_EVENT:
|
||||
case VKM_ERROR_NO_CHANGE:
|
||||
return _(error_string[code]);
|
||||
|
||||
default:
|
||||
snprintf(error_buffer, sizeof(error_buffer),
|
||||
_("Unknown error %d"), (int)code);
|
||||
return error_buffer;
|
||||
}
|
||||
}
|
||||
|
||||
vkm_button_state _vkm_get_mouse_button_state(struct vkm_mouse_button_state *state, vkm_mouse_button button)
|
||||
{
|
||||
if (state == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return state->pressed[button];
|
||||
}
|
||||
|
||||
void _vkm_set_mouse_button_state(struct vkm_mouse_button_state *sstate, vkm_mouse_button button, vkm_button_state state)
|
||||
{
|
||||
if (sstate != NULL) {
|
||||
sstate->pressed[button] = state;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,706 @@
|
|||
/*
|
||||
* VKM Linux evdev implementation
|
||||
*
|
||||
* Copyright (C) 2026 Nirenjan Krishnan <nirenjan@nirenjan.org>
|
||||
*
|
||||
* SPDX-LicenseIdentifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "libevdev/libevdev.h"
|
||||
#include "libevdev/libevdev-uinput.h"
|
||||
|
||||
#include "vkm-internal.h"
|
||||
|
||||
/** evdev codes; index by \ref vkm_key. \c -1 for \ref VKM_KEY_NONE only. */
|
||||
static const int vkm_key_to_evdev[VKM_KEY_MAX] = {
|
||||
[VKM_KEY_NONE] = -1,
|
||||
|
||||
[VKM_KEY_LEFT_CTRL] = KEY_LEFTCTRL,
|
||||
[VKM_KEY_LEFT_ALT] = KEY_LEFTALT,
|
||||
[VKM_KEY_LEFT_GUI] = KEY_LEFTMETA,
|
||||
[VKM_KEY_RIGHT_CTRL] = KEY_RIGHTCTRL,
|
||||
[VKM_KEY_RIGHT_ALT] = KEY_RIGHTALT,
|
||||
[VKM_KEY_RIGHT_GUI] = KEY_RIGHTMETA,
|
||||
[VKM_KEY_APPLICATION] = KEY_MENU,
|
||||
|
||||
[VKM_KEY_ESCAPE] = KEY_ESC,
|
||||
[VKM_KEY_F1] = KEY_F1,
|
||||
[VKM_KEY_F2] = KEY_F2,
|
||||
[VKM_KEY_F3] = KEY_F3,
|
||||
[VKM_KEY_F4] = KEY_F4,
|
||||
[VKM_KEY_F5] = KEY_F5,
|
||||
[VKM_KEY_F6] = KEY_F6,
|
||||
[VKM_KEY_F7] = KEY_F7,
|
||||
[VKM_KEY_F8] = KEY_F8,
|
||||
[VKM_KEY_F9] = KEY_F9,
|
||||
[VKM_KEY_F10] = KEY_F10,
|
||||
[VKM_KEY_F11] = KEY_F11,
|
||||
[VKM_KEY_F12] = KEY_F12,
|
||||
|
||||
[VKM_KEY_GRAVE_ACCENT] = KEY_GRAVE,
|
||||
[VKM_KEY_1] = KEY_1,
|
||||
[VKM_KEY_2] = KEY_2,
|
||||
[VKM_KEY_3] = KEY_3,
|
||||
[VKM_KEY_4] = KEY_4,
|
||||
[VKM_KEY_5] = KEY_5,
|
||||
[VKM_KEY_6] = KEY_6,
|
||||
[VKM_KEY_7] = KEY_7,
|
||||
[VKM_KEY_8] = KEY_8,
|
||||
[VKM_KEY_9] = KEY_9,
|
||||
[VKM_KEY_0] = KEY_0,
|
||||
[VKM_KEY_MINUS] = KEY_MINUS,
|
||||
[VKM_KEY_EQUAL] = KEY_EQUAL,
|
||||
[VKM_KEY_BACKSPACE] = KEY_BACKSPACE,
|
||||
|
||||
[VKM_KEY_TAB] = KEY_TAB,
|
||||
[VKM_KEY_Q] = KEY_Q,
|
||||
[VKM_KEY_W] = KEY_W,
|
||||
[VKM_KEY_E] = KEY_E,
|
||||
[VKM_KEY_R] = KEY_R,
|
||||
[VKM_KEY_T] = KEY_T,
|
||||
[VKM_KEY_Y] = KEY_Y,
|
||||
[VKM_KEY_U] = KEY_U,
|
||||
[VKM_KEY_I] = KEY_I,
|
||||
[VKM_KEY_O] = KEY_O,
|
||||
[VKM_KEY_P] = KEY_P,
|
||||
[VKM_KEY_LEFT_BRACKET] = KEY_LEFTBRACE,
|
||||
[VKM_KEY_RIGHT_BRACKET] = KEY_RIGHTBRACE,
|
||||
[VKM_KEY_BACKSLASH] = KEY_BACKSLASH,
|
||||
|
||||
[VKM_KEY_CAPS_LOCK] = KEY_CAPSLOCK,
|
||||
[VKM_KEY_A] = KEY_A,
|
||||
[VKM_KEY_S] = KEY_S,
|
||||
[VKM_KEY_D] = KEY_D,
|
||||
[VKM_KEY_F] = KEY_F,
|
||||
[VKM_KEY_G] = KEY_G,
|
||||
[VKM_KEY_H] = KEY_H,
|
||||
[VKM_KEY_J] = KEY_J,
|
||||
[VKM_KEY_K] = KEY_K,
|
||||
[VKM_KEY_L] = KEY_L,
|
||||
[VKM_KEY_SEMICOLON] = KEY_SEMICOLON,
|
||||
[VKM_KEY_APOSTROPHE] = KEY_APOSTROPHE,
|
||||
[VKM_KEY_NONUS_HASH] = KEY_NUMERIC_POUND,
|
||||
[VKM_KEY_ENTER] = KEY_ENTER,
|
||||
|
||||
[VKM_KEY_LEFT_SHIFT] = KEY_LEFTSHIFT,
|
||||
[VKM_KEY_INTL_BACKSLASH] = KEY_102ND,
|
||||
[VKM_KEY_Z] = KEY_Z,
|
||||
[VKM_KEY_X] = KEY_X,
|
||||
[VKM_KEY_C] = KEY_C,
|
||||
[VKM_KEY_V] = KEY_V,
|
||||
[VKM_KEY_B] = KEY_B,
|
||||
[VKM_KEY_N] = KEY_N,
|
||||
[VKM_KEY_M] = KEY_M,
|
||||
[VKM_KEY_COMMA] = KEY_COMMA,
|
||||
[VKM_KEY_PERIOD] = KEY_DOT,
|
||||
[VKM_KEY_SLASH] = KEY_SLASH,
|
||||
[VKM_KEY_RIGHT_SHIFT] = KEY_RIGHTSHIFT,
|
||||
|
||||
[VKM_KEY_SPACE] = KEY_SPACE,
|
||||
|
||||
[VKM_KEY_PRINT_SCREEN] = KEY_PRINT,
|
||||
[VKM_KEY_SCROLL_LOCK] = KEY_SCROLLLOCK,
|
||||
[VKM_KEY_PAUSE] = KEY_PAUSE,
|
||||
|
||||
[VKM_KEY_INSERT] = KEY_INSERT,
|
||||
[VKM_KEY_HOME] = KEY_HOME,
|
||||
[VKM_KEY_PAGE_UP] = KEY_PAGEUP,
|
||||
[VKM_KEY_DELETE_FORWARD] = KEY_DELETE,
|
||||
[VKM_KEY_END] = KEY_END,
|
||||
[VKM_KEY_PAGE_DOWN] = KEY_PAGEDOWN,
|
||||
|
||||
[VKM_KEY_RIGHT_ARROW] = KEY_RIGHT,
|
||||
[VKM_KEY_LEFT_ARROW] = KEY_LEFT,
|
||||
[VKM_KEY_DOWN_ARROW] = KEY_DOWN,
|
||||
[VKM_KEY_UP_ARROW] = KEY_UP,
|
||||
|
||||
[VKM_KEY_KEYPAD_NUM_LOCK] = KEY_NUMLOCK,
|
||||
[VKM_KEY_KEYPAD_DIVIDE] = KEY_KPSLASH,
|
||||
[VKM_KEY_KEYPAD_MULTIPLY] = KEY_KPASTERISK,
|
||||
[VKM_KEY_KEYPAD_MINUS] = KEY_KPMINUS,
|
||||
[VKM_KEY_KEYPAD_PLUS] = KEY_KPPLUS,
|
||||
[VKM_KEY_KEYPAD_ENTER] = KEY_KPENTER,
|
||||
[VKM_KEY_KEYPAD_1] = KEY_KP1,
|
||||
[VKM_KEY_KEYPAD_2] = KEY_KP2,
|
||||
[VKM_KEY_KEYPAD_3] = KEY_KP3,
|
||||
[VKM_KEY_KEYPAD_4] = KEY_KP4,
|
||||
[VKM_KEY_KEYPAD_5] = KEY_KP5,
|
||||
[VKM_KEY_KEYPAD_6] = KEY_KP6,
|
||||
[VKM_KEY_KEYPAD_7] = KEY_KP7,
|
||||
[VKM_KEY_KEYPAD_8] = KEY_KP8,
|
||||
[VKM_KEY_KEYPAD_9] = KEY_KP9,
|
||||
[VKM_KEY_KEYPAD_0] = KEY_KP0,
|
||||
[VKM_KEY_KEYPAD_DECIMAL] = KEY_KPDOT,
|
||||
[VKM_KEY_KEYPAD_COMMA] = KEY_KPCOMMA,
|
||||
};
|
||||
|
||||
struct vkm_context {
|
||||
struct libevdev *dev;
|
||||
struct libevdev_uinput *uidev;
|
||||
|
||||
char *name;
|
||||
bool mouse_hi_res_scroll;
|
||||
bool mouse_horizontal_scroll;
|
||||
|
||||
/** Last modifier byte emitted (\ref vkm_key_modifiers bits 0–7). */
|
||||
uint8_t keyboard_modifier_mask;
|
||||
|
||||
/** Per-key depressed state from \ref vkm_keyboard_send (non-\c NONE keys). */
|
||||
bool keyboard_key_pressed[VKM_KEY_MAX];
|
||||
|
||||
// Existing state of buttons
|
||||
struct vkm_mouse_button_state mouse_buttons;
|
||||
};
|
||||
|
||||
static const int mouse_button_map[VKM_MOUSE_BTN_MAX] = {
|
||||
[VKM_MOUSE_BTN_LEFT] = BTN_LEFT,
|
||||
[VKM_MOUSE_BTN_RIGHT] = BTN_RIGHT,
|
||||
[VKM_MOUSE_BTN_MIDDLE] = BTN_MIDDLE,
|
||||
};
|
||||
|
||||
static const int button_value_map[VKM_BUTTON_STATE_MAX] = {
|
||||
[VKM_BUTTON_PRESSED] = 1,
|
||||
[VKM_BUTTON_RELEASED] = 0,
|
||||
};
|
||||
|
||||
/*
|
||||
* High-resolution scroll uses the same scale as REL_WHEEL_HI_RES in the kernel
|
||||
* (120 units per “click” / detent; aligns with libinput and the Windows convention).
|
||||
*/
|
||||
#define VKM_SCROLL_HI_RES_PER_DETENT 120
|
||||
|
||||
vkm_result vkm_init(vkm_context **ctx)
|
||||
{
|
||||
char name_buf[32];
|
||||
char *name;
|
||||
vkm_result rc;
|
||||
if (ctx == NULL) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
*ctx = calloc(1, sizeof(**ctx));
|
||||
if (*ctx == NULL) {
|
||||
return VKM_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
rc = VKM_SUCCESS;
|
||||
// Create a default name for the virtual device, this will be replaced when
|
||||
// the user calls vkm_set_option with the appropriate name field.
|
||||
snprintf(name_buf, sizeof(name_buf), "VKM virtual device @%08" PRIx64,
|
||||
(uint64_t)(time(NULL)));
|
||||
name = strdup(name_buf);
|
||||
if (name == NULL) {
|
||||
rc = VKM_ERROR_OUT_OF_MEMORY;
|
||||
goto error;
|
||||
}
|
||||
(*ctx)->name = name;
|
||||
|
||||
// Set defaults for the flags
|
||||
return VKM_SUCCESS;
|
||||
|
||||
error:
|
||||
if (name) {
|
||||
free(name);
|
||||
}
|
||||
free(*ctx);
|
||||
*ctx = NULL;
|
||||
return rc;
|
||||
}
|
||||
|
||||
void vkm_exit(vkm_context *ctx)
|
||||
{
|
||||
volatile unsigned char *vp;
|
||||
|
||||
if (ctx == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
(void)vkm_reset(ctx);
|
||||
|
||||
free(ctx->name);
|
||||
|
||||
if (ctx->uidev) {
|
||||
libevdev_uinput_destroy(ctx->uidev);
|
||||
}
|
||||
|
||||
if (ctx->dev) {
|
||||
libevdev_free(ctx->dev);
|
||||
}
|
||||
|
||||
/* Clear the memory to prevent reuse */
|
||||
vp = (volatile unsigned char *)ctx;
|
||||
for (int i = 0; i < sizeof(*ctx); i++) {
|
||||
vp[i] = (unsigned char)0;
|
||||
}
|
||||
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
// Enable mouse events
|
||||
static void enable_mouse_events(vkm_context *ctx)
|
||||
{
|
||||
libevdev_enable_event_type(ctx->dev, EV_REL);
|
||||
libevdev_enable_event_code(ctx->dev, EV_REL, REL_X, NULL);
|
||||
libevdev_enable_event_code(ctx->dev, EV_REL, REL_Y, NULL);
|
||||
libevdev_enable_event_code(ctx->dev, EV_REL, REL_WHEEL, NULL);
|
||||
if (ctx->mouse_hi_res_scroll) {
|
||||
libevdev_enable_event_code(ctx->dev, EV_REL, REL_WHEEL_HI_RES, NULL);
|
||||
}
|
||||
|
||||
if (ctx->mouse_horizontal_scroll) {
|
||||
libevdev_enable_event_code(ctx->dev, EV_REL, REL_HWHEEL, NULL);
|
||||
if (ctx->mouse_hi_res_scroll) {
|
||||
libevdev_enable_event_code(ctx->dev, EV_REL, REL_HWHEEL_HI_RES, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
libevdev_enable_event_type(ctx->dev, EV_KEY);
|
||||
libevdev_enable_event_code(ctx->dev, EV_KEY, BTN_LEFT, NULL);
|
||||
libevdev_enable_event_code(ctx->dev, EV_KEY, BTN_RIGHT, NULL);
|
||||
libevdev_enable_event_code(ctx->dev, EV_KEY, BTN_MIDDLE, NULL);
|
||||
}
|
||||
|
||||
static void enable_keyboard_events(vkm_context *ctx)
|
||||
{
|
||||
vkm_key k;
|
||||
|
||||
for (k = (vkm_key)0; k < VKM_KEY_MAX; k++) {
|
||||
int code = vkm_key_to_evdev[k];
|
||||
|
||||
if (code >= 0) {
|
||||
libevdev_enable_event_code(ctx->dev, EV_KEY, (unsigned int)code, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const int vkm_modifier_evdev[8] = {
|
||||
KEY_LEFTCTRL,
|
||||
KEY_LEFTSHIFT,
|
||||
KEY_LEFTALT,
|
||||
KEY_LEFTMETA,
|
||||
KEY_RIGHTCTRL,
|
||||
KEY_RIGHTSHIFT,
|
||||
KEY_RIGHTALT,
|
||||
KEY_RIGHTMETA,
|
||||
};
|
||||
|
||||
static vkm_result apply_modifier_mask(vkm_context *ctx, uint32_t want)
|
||||
{
|
||||
uint32_t have = ctx->keyboard_modifier_mask;
|
||||
uint32_t diff = want ^ have;
|
||||
unsigned bit;
|
||||
|
||||
if (diff == 0) {
|
||||
return VKM_SUCCESS;
|
||||
}
|
||||
|
||||
for (bit = 0; bit < 8; bit++) {
|
||||
uint32_t b = (uint32_t)1 << bit;
|
||||
|
||||
if (diff & b) {
|
||||
int val = (want & b) ? 1 : 0;
|
||||
int rc;
|
||||
|
||||
rc = libevdev_uinput_write_event(ctx->uidev, EV_KEY, vkm_modifier_evdev[bit], val);
|
||||
if (rc != 0) {
|
||||
return VKM_ERROR_EVENT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx->keyboard_modifier_mask = (uint8_t)want;
|
||||
return VKM_SUCCESS;
|
||||
}
|
||||
|
||||
vkm_result vkm_start(vkm_context *ctx)
|
||||
{
|
||||
int rc;
|
||||
if (ctx == NULL) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (ctx->uidev) {
|
||||
// Already initialized, no need to do anything else
|
||||
return VKM_SUCCESS;
|
||||
}
|
||||
|
||||
if (!(ctx->dev)) {
|
||||
// This will happen on the first time this function is called, but
|
||||
// will get cleared up if the uinput device is not created, so this
|
||||
// is just a safety check.
|
||||
ctx->dev = libevdev_new();
|
||||
if (ctx->dev == NULL) {
|
||||
return VKM_ERROR_DEV_FAILURE;
|
||||
}
|
||||
|
||||
libevdev_set_name(ctx->dev, ctx->name);
|
||||
enable_mouse_events(ctx);
|
||||
enable_keyboard_events(ctx);
|
||||
}
|
||||
|
||||
rc = libevdev_uinput_create_from_device(ctx->dev, LIBEVDEV_UINPUT_OPEN_MANAGED,
|
||||
&(ctx->uidev));
|
||||
if (rc != 0) {
|
||||
goto error;
|
||||
}
|
||||
return VKM_SUCCESS;
|
||||
|
||||
error:
|
||||
libevdev_free(ctx->dev);
|
||||
ctx->dev = NULL;
|
||||
return VKM_ERROR_DEV_FAILURE;
|
||||
}
|
||||
|
||||
bool vkm_is_ready(vkm_context *ctx) {
|
||||
if (ctx == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (ctx->dev != NULL && ctx->uidev != NULL);
|
||||
}
|
||||
|
||||
bool vkm_platform_supported(void)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool vkm_feature_supported(vkm_feature feat)
|
||||
{
|
||||
switch (feat) {
|
||||
case VKM_FEAT_MOUSE:
|
||||
return true;
|
||||
|
||||
case VKM_FEAT_KEYBOARD:
|
||||
case VKM_FEAT_KEYBOARD_MODIFIERS:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
vkm_result vkm_set_option(vkm_context *ctx, vkm_option option, ...)
|
||||
{
|
||||
va_list ap;
|
||||
bool flag;
|
||||
char *name;
|
||||
char *name2;
|
||||
vkm_result rc;
|
||||
|
||||
if (ctx == NULL) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
va_start(ap, option);
|
||||
rc = VKM_SUCCESS;
|
||||
switch (option) {
|
||||
case VKM_OPT_HI_RES_SCROLL:
|
||||
// read as int, as bool is promoted to int when passed as a va_arg
|
||||
flag = va_arg(ap, int);
|
||||
ctx->mouse_hi_res_scroll = (bool)flag;
|
||||
break;
|
||||
|
||||
case VKM_OPT_HORIZONTAL_SCROLL:
|
||||
// read as int, as bool is promoted to int when passed as a va_arg
|
||||
flag = va_arg(ap, int);
|
||||
ctx->mouse_horizontal_scroll = (bool)flag;
|
||||
break;
|
||||
|
||||
case VKM_OPT_DEVICE_NAME:
|
||||
name = va_arg(ap, char *);
|
||||
name2 = strdup(name);
|
||||
if (name2 == NULL) {
|
||||
rc = VKM_ERROR_OUT_OF_MEMORY;
|
||||
} else {
|
||||
free(ctx->name);
|
||||
ctx->name = name2;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
rc = VKM_ERROR_INVALID_PARAM;
|
||||
break;
|
||||
}
|
||||
|
||||
va_end(ap);
|
||||
return rc;
|
||||
}
|
||||
|
||||
vkm_result vkm_mouse_move(vkm_context *ctx, int dx, int dy)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if (ctx == NULL) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (!vkm_is_ready(ctx)) {
|
||||
return VKM_ERROR_NOT_READY;
|
||||
}
|
||||
|
||||
if (!dx && !dy) {
|
||||
return VKM_ERROR_NO_CHANGE;
|
||||
}
|
||||
|
||||
if (dx) {
|
||||
rc = libevdev_uinput_write_event(ctx->uidev, EV_REL, REL_X, dx);
|
||||
if (rc != 0) {
|
||||
return VKM_ERROR_EVENT;
|
||||
}
|
||||
}
|
||||
|
||||
if (dy) {
|
||||
rc = libevdev_uinput_write_event(ctx->uidev, EV_REL, REL_Y, dy);
|
||||
if (rc != 0) {
|
||||
return VKM_ERROR_EVENT;
|
||||
}
|
||||
}
|
||||
|
||||
return VKM_SUCCESS;
|
||||
}
|
||||
|
||||
vkm_result vkm_mouse_click(vkm_context *ctx, vkm_mouse_button button, vkm_button_state state)
|
||||
{
|
||||
int rc;
|
||||
vkm_button_state existing;
|
||||
|
||||
if (ctx == NULL) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (button >= VKM_MOUSE_BTN_MAX) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (state >= VKM_BUTTON_STATE_MAX) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (!vkm_is_ready(ctx)) {
|
||||
return VKM_ERROR_NOT_READY;
|
||||
}
|
||||
|
||||
existing = _vkm_get_mouse_button_state(&(ctx->mouse_buttons), button);
|
||||
if (existing == state) {
|
||||
return VKM_ERROR_NO_CHANGE;
|
||||
}
|
||||
_vkm_set_mouse_button_state(&(ctx->mouse_buttons), button, state);
|
||||
|
||||
rc = libevdev_uinput_write_event(ctx->uidev, EV_KEY,
|
||||
mouse_button_map[button], button_value_map[state]);
|
||||
if (rc != 0) {
|
||||
return VKM_ERROR_EVENT;
|
||||
}
|
||||
|
||||
return VKM_SUCCESS;
|
||||
}
|
||||
|
||||
vkm_result vkm_mouse_scroll(vkm_context *ctx, vkm_mouse_scroll_direction dir)
|
||||
{
|
||||
int rc;
|
||||
int sign;
|
||||
|
||||
if (ctx == NULL) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (dir >= VKM_MOUSE_SCROLL_MAX) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (!vkm_is_ready(ctx)) {
|
||||
return VKM_ERROR_NOT_READY;
|
||||
}
|
||||
|
||||
if (dir == VKM_MOUSE_SCROLL_LEFT || dir == VKM_MOUSE_SCROLL_RIGHT) {
|
||||
if (!ctx->mouse_horizontal_scroll) {
|
||||
return VKM_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
sign = (dir == VKM_MOUSE_SCROLL_LEFT) ? -1 : 1;
|
||||
if (ctx->mouse_hi_res_scroll) {
|
||||
rc = libevdev_uinput_write_event(ctx->uidev, EV_REL, REL_HWHEEL_HI_RES,
|
||||
sign * VKM_SCROLL_HI_RES_PER_DETENT);
|
||||
if (rc != 0) {
|
||||
return VKM_ERROR_EVENT;
|
||||
}
|
||||
}
|
||||
rc = libevdev_uinput_write_event(ctx->uidev, EV_REL, REL_HWHEEL, sign);
|
||||
if (rc != 0) {
|
||||
return VKM_ERROR_EVENT;
|
||||
}
|
||||
return VKM_SUCCESS;
|
||||
}
|
||||
|
||||
sign = (dir == VKM_MOUSE_SCROLL_UP) ? 1 : -1;
|
||||
if (ctx->mouse_hi_res_scroll) {
|
||||
rc = libevdev_uinput_write_event(ctx->uidev, EV_REL, REL_WHEEL_HI_RES,
|
||||
sign * VKM_SCROLL_HI_RES_PER_DETENT);
|
||||
if (rc != 0) {
|
||||
return VKM_ERROR_EVENT;
|
||||
}
|
||||
}
|
||||
rc = libevdev_uinput_write_event(ctx->uidev, EV_REL, REL_WHEEL, sign);
|
||||
if (rc != 0) {
|
||||
return VKM_ERROR_EVENT;
|
||||
}
|
||||
|
||||
return VKM_SUCCESS;
|
||||
}
|
||||
|
||||
vkm_result vkm_keyboard_send(vkm_context *ctx, vkm_key key, vkm_key_modifiers modifiers,
|
||||
vkm_key_state state)
|
||||
{
|
||||
vkm_result res;
|
||||
int evcode;
|
||||
int val;
|
||||
uint32_t modmask = (uint32_t)modifiers & 0xffu;
|
||||
|
||||
if (ctx == NULL) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (key >= VKM_KEY_MAX) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (state >= VKM_KEY_STATE_MAX) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (!vkm_is_ready(ctx)) {
|
||||
return VKM_ERROR_NOT_READY;
|
||||
}
|
||||
|
||||
val = (state == VKM_KEY_STATE_PRESSED) ? 1 : 0;
|
||||
|
||||
if (state == VKM_KEY_STATE_PRESSED) {
|
||||
res = apply_modifier_mask(ctx, modmask);
|
||||
if (res != VKM_SUCCESS) {
|
||||
return res;
|
||||
}
|
||||
|
||||
if (key == VKM_KEY_NONE) {
|
||||
return VKM_SUCCESS;
|
||||
}
|
||||
|
||||
evcode = vkm_key_to_evdev[key];
|
||||
if (evcode < 0) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (libevdev_uinput_write_event(ctx->uidev, EV_KEY, (unsigned int)evcode, val) != 0) {
|
||||
return VKM_ERROR_EVENT;
|
||||
}
|
||||
|
||||
ctx->keyboard_key_pressed[key] = true;
|
||||
} else {
|
||||
if (key != VKM_KEY_NONE) {
|
||||
evcode = vkm_key_to_evdev[key];
|
||||
if (evcode < 0) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (libevdev_uinput_write_event(ctx->uidev, EV_KEY, (unsigned int)evcode, val) != 0) {
|
||||
return VKM_ERROR_EVENT;
|
||||
}
|
||||
|
||||
ctx->keyboard_key_pressed[key] = false;
|
||||
}
|
||||
|
||||
res = apply_modifier_mask(ctx, modmask);
|
||||
if (res != VKM_SUCCESS) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
return VKM_SUCCESS;
|
||||
}
|
||||
|
||||
static void keyboard_state_clear_tracking(vkm_context *ctx)
|
||||
{
|
||||
vkm_mouse_button b;
|
||||
|
||||
memset(ctx->keyboard_key_pressed, 0, sizeof(ctx->keyboard_key_pressed));
|
||||
ctx->keyboard_modifier_mask = 0;
|
||||
for (b = 0; b < VKM_MOUSE_BTN_MAX; b++) {
|
||||
_vkm_set_mouse_button_state(&ctx->mouse_buttons, b, VKM_BUTTON_RELEASED);
|
||||
}
|
||||
}
|
||||
|
||||
vkm_result vkm_reset(vkm_context *ctx)
|
||||
{
|
||||
vkm_key k;
|
||||
vkm_mouse_button b;
|
||||
int rc;
|
||||
|
||||
if (ctx == NULL) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (!vkm_is_ready(ctx)) {
|
||||
keyboard_state_clear_tracking(ctx);
|
||||
return VKM_SUCCESS;
|
||||
}
|
||||
|
||||
for (k = (vkm_key)0; k < VKM_KEY_MAX; k++) {
|
||||
if (ctx->keyboard_key_pressed[k]) {
|
||||
int code = vkm_key_to_evdev[k];
|
||||
|
||||
if (code >= 0) {
|
||||
rc = libevdev_uinput_write_event(ctx->uidev, EV_KEY, (unsigned int)code, 0);
|
||||
if (rc != 0) {
|
||||
return VKM_ERROR_EVENT;
|
||||
}
|
||||
}
|
||||
|
||||
ctx->keyboard_key_pressed[k] = false;
|
||||
}
|
||||
}
|
||||
|
||||
rc = apply_modifier_mask(ctx, 0);
|
||||
if (rc != VKM_SUCCESS) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
for (b = 0; b < VKM_MOUSE_BTN_MAX; b++) {
|
||||
if (_vkm_get_mouse_button_state(&ctx->mouse_buttons, b) == VKM_BUTTON_PRESSED) {
|
||||
rc = libevdev_uinput_write_event(ctx->uidev, EV_KEY,
|
||||
mouse_button_map[b], button_value_map[VKM_BUTTON_RELEASED]);
|
||||
if (rc != 0) {
|
||||
return VKM_ERROR_EVENT;
|
||||
}
|
||||
|
||||
_vkm_set_mouse_button_state(&ctx->mouse_buttons, b, VKM_BUTTON_RELEASED);
|
||||
}
|
||||
}
|
||||
|
||||
return vkm_sync(ctx);
|
||||
}
|
||||
|
||||
vkm_result vkm_sync(vkm_context *ctx)
|
||||
{
|
||||
int rc;
|
||||
if (ctx == NULL) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (!vkm_is_ready(ctx)) {
|
||||
return VKM_ERROR_NOT_READY;
|
||||
}
|
||||
|
||||
rc = libevdev_uinput_write_event(ctx->uidev, EV_SYN, SYN_REPORT, 0);
|
||||
if (rc != 0) {
|
||||
return VKM_ERROR_EVENT;
|
||||
}
|
||||
|
||||
return VKM_SUCCESS;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,222 @@
|
|||
/*
|
||||
* VKM stub implementation
|
||||
*
|
||||
* Copyright (C) 2026 Nirenjan Krishnan <nirenjan@nirenjan.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "vkm.h"
|
||||
|
||||
struct vkm_context {
|
||||
bool started;
|
||||
bool mouse_hi_res_scroll;
|
||||
bool mouse_horizontal_scroll;
|
||||
};
|
||||
|
||||
vkm_result vkm_init(vkm_context **ctx)
|
||||
{
|
||||
if (ctx == NULL) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
*ctx = calloc(1, sizeof(**ctx));
|
||||
if (*ctx == NULL) {
|
||||
return VKM_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
return VKM_SUCCESS;
|
||||
}
|
||||
|
||||
vkm_result vkm_reset(vkm_context *ctx)
|
||||
{
|
||||
if (ctx == NULL) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (vkm_is_ready(ctx)) {
|
||||
return vkm_sync(ctx);
|
||||
}
|
||||
|
||||
return VKM_SUCCESS;
|
||||
}
|
||||
|
||||
void vkm_exit(vkm_context *ctx)
|
||||
{
|
||||
if (ctx == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
(void)vkm_reset(ctx);
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
vkm_result vkm_start(vkm_context *ctx)
|
||||
{
|
||||
if (ctx == NULL) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
ctx->started = true;
|
||||
return VKM_SUCCESS;
|
||||
}
|
||||
|
||||
bool vkm_is_ready(vkm_context *ctx)
|
||||
{
|
||||
if (ctx == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ctx->started;
|
||||
}
|
||||
|
||||
bool vkm_platform_supported(void)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool vkm_feature_supported(vkm_feature feat)
|
||||
{
|
||||
(void)feat;
|
||||
return false;
|
||||
}
|
||||
|
||||
vkm_result vkm_set_option(vkm_context *ctx, vkm_option option, ...)
|
||||
{
|
||||
va_list ap;
|
||||
bool flag;
|
||||
char *name;
|
||||
vkm_result rc;
|
||||
|
||||
if (ctx == NULL) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
va_start(ap, option);
|
||||
rc = VKM_SUCCESS;
|
||||
switch (option) {
|
||||
case VKM_OPT_HI_RES_SCROLL:
|
||||
flag = (bool)va_arg(ap, int);
|
||||
ctx->mouse_hi_res_scroll = flag;
|
||||
break;
|
||||
|
||||
case VKM_OPT_HORIZONTAL_SCROLL:
|
||||
flag = (bool)va_arg(ap, int);
|
||||
ctx->mouse_horizontal_scroll = flag;
|
||||
break;
|
||||
|
||||
case VKM_OPT_DEVICE_NAME:
|
||||
name = va_arg(ap, char *);
|
||||
(void)name;
|
||||
rc = VKM_ERROR_NOT_SUPPORTED;
|
||||
break;
|
||||
|
||||
default:
|
||||
rc = VKM_ERROR_INVALID_PARAM;
|
||||
break;
|
||||
}
|
||||
|
||||
va_end(ap);
|
||||
return rc;
|
||||
}
|
||||
|
||||
vkm_result vkm_mouse_move(vkm_context *ctx, int dx, int dy)
|
||||
{
|
||||
if (ctx == NULL) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (!vkm_is_ready(ctx)) {
|
||||
return VKM_ERROR_NOT_READY;
|
||||
}
|
||||
|
||||
if (dx == 0 && dy == 0) {
|
||||
return VKM_ERROR_NO_CHANGE;
|
||||
}
|
||||
|
||||
return VKM_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
vkm_result vkm_mouse_click(vkm_context *ctx, vkm_mouse_button button, vkm_button_state state)
|
||||
{
|
||||
if (ctx == NULL) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (button >= VKM_MOUSE_BTN_MAX) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (state >= VKM_BUTTON_STATE_MAX) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (!vkm_is_ready(ctx)) {
|
||||
return VKM_ERROR_NOT_READY;
|
||||
}
|
||||
|
||||
return VKM_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
vkm_result vkm_mouse_scroll(vkm_context *ctx, vkm_mouse_scroll_direction dir)
|
||||
{
|
||||
if (ctx == NULL) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (dir >= VKM_MOUSE_SCROLL_MAX) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (!vkm_is_ready(ctx)) {
|
||||
return VKM_ERROR_NOT_READY;
|
||||
}
|
||||
|
||||
if (dir == VKM_MOUSE_SCROLL_LEFT || dir == VKM_MOUSE_SCROLL_RIGHT) {
|
||||
if (!ctx->mouse_horizontal_scroll) {
|
||||
return VKM_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
}
|
||||
|
||||
return VKM_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
vkm_result vkm_sync(vkm_context *ctx)
|
||||
{
|
||||
if (ctx == NULL) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (!vkm_is_ready(ctx)) {
|
||||
return VKM_ERROR_NOT_READY;
|
||||
}
|
||||
|
||||
return VKM_SUCCESS;
|
||||
}
|
||||
|
||||
vkm_result vkm_keyboard_send(vkm_context *ctx, vkm_key key, vkm_key_modifiers modifiers,
|
||||
vkm_key_state state)
|
||||
{
|
||||
(void)modifiers;
|
||||
|
||||
if (ctx == NULL) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (key >= VKM_KEY_MAX) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (state >= VKM_KEY_STATE_MAX) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (!vkm_is_ready(ctx)) {
|
||||
return VKM_ERROR_NOT_READY;
|
||||
}
|
||||
|
||||
return VKM_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
Loading…
Reference in New Issue