mirror of https://github.com/nirenjan/libx52.git
Compare commits
124 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
57676cf540 | |
|
|
b8f059a881 | |
|
|
03d7336234 | |
|
|
a710ab5591 | |
|
|
130a1f67de | |
|
|
4422ee89c0 | |
|
|
9aaec8b2f0 | |
|
|
3f4990de4d | |
|
|
fb913a06a2 | |
|
|
c373ca9647 | |
|
|
9cddfe9cef | |
|
|
5fecdd3929 | |
|
|
772017661d | |
|
|
fdf884cb1c | |
|
|
e0ab5bbab7 | |
|
|
3c02fe5ec2 | |
|
|
dbf891f951 | |
|
|
79676250fc | |
|
|
a2ce2e2218 | |
|
|
c487508b7b | |
|
|
84e151389a | |
|
|
75f0125f54 | |
|
|
f52e328a8b | |
|
|
806a88c93d | |
|
|
c0c8787331 | |
|
|
de465fbf6a | |
|
|
583d4fd646 | |
|
|
836206d93f | |
|
|
9c1eaaa4b2 | |
|
|
9c47e78fc5 | |
|
|
7bdaea442e | |
|
|
8b139a05c4 | |
|
|
c49689c1ee | |
|
|
0fdcb725af | |
|
|
03d58c62e8 | |
|
|
991a307191 | |
|
|
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 | |
|
|
ad30bfff7b | |
|
|
cccb561020 | |
|
|
6743c60dfd | |
|
|
b4ec8d4629 | |
|
|
3fb0d72124 | |
|
|
74fe559f4a | |
|
|
b6e61fc54e | |
|
|
e479e338a2 | |
|
|
69ae9626c7 | |
|
|
e9f4e1b3a8 | |
|
|
f43ba6b902 | |
|
|
378cbbd931 | |
|
|
47da6e22d1 | |
|
|
e98b8b4bc3 | |
|
|
7b7065f8f0 | |
|
|
2fa9f52ddb | |
|
|
a17312dcbc | |
|
|
2be7792024 | |
|
|
f51b777ca0 | |
|
|
b3dff7182b | |
|
|
0356a2d610 | |
|
|
c1e3c85738 | |
|
|
accd2a1f4e | |
|
|
e8208e97cb | |
|
|
421e2964b3 | |
|
|
2378ba7dc4 | |
|
|
762a3468b2 | |
|
|
ef4cbee127 | |
|
|
c63b924705 | |
|
|
1b00bf4a69 | |
|
|
a40546bda3 | |
|
|
108293abdf | |
|
|
9361c7af5c | |
|
|
004eca2418 | |
|
|
1902ca0d27 | |
|
|
6330d28c4d | |
|
|
5c37c4a9db | |
|
|
863e43e4ad | |
|
|
49c57f4a6a | |
|
|
b0b9123a2e | |
|
|
50a911160f | |
|
|
7a56af032b | |
|
|
c46cec3138 | |
|
|
21050e40a8 | |
|
|
9e2e8cb8ff | |
|
|
5f4dfe4c01 | |
|
|
0870518598 | |
|
|
d7b4a694fa | |
|
|
326ac992ac | |
|
|
d3973a0abf | |
|
|
ebca9566d7 | |
|
|
03c0376e7c |
|
|
@ -1 +1,5 @@
|
|||
/version-info ident
|
||||
*/meson.build ident
|
||||
/.github/ export-ignore
|
||||
.gitignore export-ignore
|
||||
.gitattributes export-ignore
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
version: 2
|
||||
updates:
|
||||
# Maintain dependencies for Github Actions
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
open-pull-requests-limit: 5
|
||||
|
|
@ -2,16 +2,32 @@
|
|||
# Run the build and tests
|
||||
set -e
|
||||
|
||||
./autogen.sh
|
||||
mkdir build
|
||||
cd build
|
||||
../configure
|
||||
make -j V=0
|
||||
make -j check V=0
|
||||
make -j distcheck
|
||||
BUILDDIR="${1:-build}"
|
||||
|
||||
rm -rf "$BUILDDIR"
|
||||
|
||||
# Handle the meson dist failure in CI
|
||||
if [[ "$GITHUB_ACTIONS" == "true" ]]
|
||||
then
|
||||
# If in a container, then use the system directory
|
||||
git config --system --add safe.directory '*' || \
|
||||
git config --global --add safe.directory '*'
|
||||
fi
|
||||
|
||||
meson setup -Dprefix=/usr -Dsysconfdir=/etc -Dlocalstatedir=/var -Dnls=enabled "$BUILDDIR"
|
||||
cd "$BUILDDIR"
|
||||
meson compile
|
||||
meson test
|
||||
|
||||
if [[ $(printf "%s\n" "0.62.0" "$(meson --version)" | sort -V | head -1) == "0.62.0" ]]
|
||||
then
|
||||
meson dist --allow-dirty # Required to fix CI build
|
||||
else
|
||||
meson dist
|
||||
fi
|
||||
|
||||
# Print bugreport output
|
||||
./x52bugreport
|
||||
./bugreport/x52bugreport
|
||||
|
||||
# Make sure that there are no changes to the source code
|
||||
# This may happen if the source have changed with differences to the
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@
|
|||
# Generate Doxygen documentation
|
||||
set -e
|
||||
|
||||
./autogen.sh
|
||||
mkdir build
|
||||
meson setup -Dprefix=/usr -Dsysconfdir=/etc -Dlocalstatedir=/var -Dnls=enabled build
|
||||
cd build
|
||||
../configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var
|
||||
make docs/.stamp
|
||||
ninja docs
|
||||
|
|
|
|||
|
|
@ -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,38 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Generate a changelog for the latest release and dump it to stdout"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
def get_git_root():
|
||||
script_path = Path(__file__).resolve()
|
||||
|
||||
# This is always going to reside at <git-root>/.github/scripts/*.py
|
||||
scripts_dir = Path(script_path.parent)
|
||||
gh_dir = Path(scripts_dir.parent)
|
||||
|
||||
return Path(gh_dir.parent)
|
||||
|
||||
def main():
|
||||
git_root = get_git_root()
|
||||
|
||||
changelog_file = git_root / 'ChangeLog.md'
|
||||
|
||||
latest = False
|
||||
with open(changelog_file) as cfd:
|
||||
for line in cfd:
|
||||
if line.startswith('## '):
|
||||
if 'Unreleased' in line:
|
||||
continue
|
||||
|
||||
if latest:
|
||||
break
|
||||
|
||||
latest = True
|
||||
continue
|
||||
|
||||
if latest:
|
||||
print(line, end='')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
@ -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'
|
||||
|
|
@ -1,15 +1,16 @@
|
|||
#!/bin/bash -x
|
||||
# Install dependencies to build and test on Ubuntu runners
|
||||
brew install \
|
||||
autoconf \
|
||||
automake \
|
||||
libtool \
|
||||
pkg-config \
|
||||
python3 \
|
||||
gettext \
|
||||
libusb \
|
||||
hidapi \
|
||||
inih \
|
||||
doxygen \
|
||||
cmocka
|
||||
cmocka \
|
||||
meson \
|
||||
ninja \
|
||||
inih
|
||||
|
||||
exit 0
|
||||
|
|
|
|||
|
|
@ -2,9 +2,6 @@
|
|||
# Install dependencies to build and test on Ubuntu runners
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
autoconf \
|
||||
automake \
|
||||
libtool \
|
||||
pkg-config \
|
||||
python3 \
|
||||
gettext \
|
||||
|
|
@ -12,8 +9,11 @@ sudo apt-get install -y \
|
|||
libusb-1.0-0-dev \
|
||||
libhidapi-dev \
|
||||
libevdev-dev \
|
||||
libinih-dev \
|
||||
doxygen \
|
||||
libcmocka-dev \
|
||||
faketime
|
||||
faketime \
|
||||
meson \
|
||||
ninja-build
|
||||
|
||||
exit 0
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
name: Build/Test
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
|
|
@ -7,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-') || (matrix.os == 'ubuntu-22.04') }}
|
||||
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-20.04', 'ubuntu-22.04', 'macos-11', 'macos-12']
|
||||
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@v2
|
||||
|
||||
- name: Install dependencies (Ubuntu)
|
||||
run: ./.github/scripts/install-dependencies-ubuntu.sh
|
||||
if: ${{ startsWith(matrix.os, 'ubuntu-') }}
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- 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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
name: "CodeQL"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
|
|
@ -14,7 +18,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
|
|
@ -30,26 +34,13 @@ jobs:
|
|||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
uses: github/codeql-action/init@v4
|
||||
# Override language selection by uncommenting this and choosing your languages
|
||||
# with:
|
||||
# languages: go, javascript, csharp, python, cpp, java
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
- name: Build
|
||||
run: ./.github/scripts/build-and-test.sh
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
uses: github/codeql-action/analyze@v4
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -5,14 +5,22 @@ on:
|
|||
branches:
|
||||
- 'master'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write # Required to push to the Pages server
|
||||
id-token: write # Required to verify the deployment is legitimate
|
||||
|
||||
jobs:
|
||||
doxygen:
|
||||
if: "!(contains(github.event.head_commit.message, '[doxy skip]') || contains(github.event.head_commit.message, '[skip doxy]'))"
|
||||
runs-on: 'ubuntu-latest'
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install dependencies
|
||||
run: ./.github/scripts/install-dependencies-ubuntu.sh
|
||||
|
|
@ -23,8 +31,11 @@ jobs:
|
|||
- name: Dump generated files
|
||||
run: find ./build -type f -print
|
||||
|
||||
- name: Deploy generated documentation to Github pages
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
- name: Upload built pages
|
||||
uses: actions/upload-pages-artifact@v5
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./build/docs/html
|
||||
path: './build/docs/html'
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v5
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
name: Kernel Module
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ '*' ]
|
||||
|
|
@ -16,11 +19,11 @@ jobs:
|
|||
|
||||
strategy:
|
||||
matrix:
|
||||
os: ['ubuntu-18.04', 'ubuntu-20.04']
|
||||
os: ['ubuntu-22.04', 'ubuntu-24.04']
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install kernel dependencies
|
||||
run: ./.github/scripts/install-kernel-dependencies.sh
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
name: Create Release
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
|
|
@ -11,7 +14,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install dependencies
|
||||
run: ./.github/scripts/install-dependencies-ubuntu.sh
|
||||
|
|
@ -19,30 +22,40 @@ jobs:
|
|||
- name: Build project
|
||||
run: ./.github/scripts/build-and-test.sh
|
||||
|
||||
- name: Find release tarball
|
||||
id: find_release
|
||||
- name: Prepare release assets
|
||||
run: |
|
||||
echo "::set-output name=path::$(find $PWD -name 'libx52*.tar*')"
|
||||
echo "::set-output name=asset::$(find . -name 'libx52*.tar*' -exec basename {} \; | sed 's/libx52-\(.*\)\.tar/libx52_\1.orig.tar/')"
|
||||
# Find the Meson generated tarball
|
||||
# meson dist usuall creates a tar.xz, but be prepared to handle
|
||||
# additional compression formats
|
||||
DIST_FILE=$(find build/meson-dist -name 'libx52-*.tar.*' -a ! -name '*.tar.*sum')
|
||||
|
||||
# Extract the version from the filename
|
||||
VERSION=$(echo "$DIST_FILE" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
|
||||
EXTENSION="${DIST_FILE#*${VERSION}}"
|
||||
|
||||
ASSET_NAME="libx52_${VERSION}.orig${EXTENSION}"
|
||||
ASSET_PATH="build/meson-dist/${ASSET_NAME}"
|
||||
|
||||
# Rename the file
|
||||
mv -v "$DIST_FILE" "$ASSET_PATH"
|
||||
|
||||
cd build/meson-dist
|
||||
rm *.sha256sum
|
||||
sha256sum "$ASSET_NAME" > "${ASSET_NAME}.sha256sum"
|
||||
cd ../..
|
||||
|
||||
- name: Generate changelog
|
||||
run: ./.github/scripts/generate_changelog.py > ${{ github.workspace }}/CHANGELOG.txt
|
||||
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
uses: softprops/action-gh-release@v3
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: Release ${{ github.ref }}
|
||||
name: Release ${{ github.ref_name }}
|
||||
body_path: ${{ github.workspace }}/CHANGELOG.txt
|
||||
draft: false
|
||||
prerelease: false
|
||||
|
||||
- name: Upload Release Tarball
|
||||
id: upload-release-tarball
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ${{ steps.find_release.outputs.path }}
|
||||
asset_name: ${{ steps.find_release.outputs.asset }}
|
||||
asset_content_type: application/gzip
|
||||
files: |
|
||||
build/meson-dist/*
|
||||
|
|
|
|||
|
|
@ -10,10 +10,13 @@ x52test*
|
|||
x52evtest*
|
||||
libx52/test_*
|
||||
libx52test*
|
||||
libx52util/util_char_map.c
|
||||
libx52util/char_map.c
|
||||
udev/*.rules
|
||||
# Built artifacts / local installs named x52d*; daemon sources use short names.
|
||||
# Keep tracked daemon files that still use the x52d prefix (headers, configs).
|
||||
x52d*
|
||||
!daemon/x52d*.*
|
||||
!libx52/x52dcomm.h
|
||||
test-*
|
||||
libx52-*.tar.gz
|
||||
|
||||
|
|
@ -26,6 +29,10 @@ Module.symvers
|
|||
# Vim swap files
|
||||
.*.swp
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
|
||||
# Autotools objects
|
||||
.deps
|
||||
.dirstamp
|
||||
|
|
|
|||
73
ChangeLog.md
73
ChangeLog.md
|
|
@ -6,6 +6,73 @@ The format is based upon [Keep a Changelog].
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
|
||||
- Migrated CI builds to run in multiple distro containers.
|
||||
- Improved virtual mouse motion to use a smoother approach, as well as allow an isometric speed calculation. This change deprecates the old `Mouse.Speed` configuration option and replaces it with a Sensitivity percentage option.
|
||||
|
||||
### 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.
|
||||
- Added a [security policy](SECURITY.md) to help securely report vulnerabilities.
|
||||
- Added Dependabot configuration to keep Action files up to date.
|
||||
- Added a changelog script to automatically generate the latest release changelog.
|
||||
|
||||
### Changed
|
||||
- **BREAKING**: Removed vendored inih package and switched build framework to use inih from the system package manager.
|
||||
- `x52bugreport` tool now strips out potentially identifying information.
|
||||
- Removed the use of a 3rd party action to deploy generated Doxygen pages to the gh-pages branch. This now uses the modern gh-pages deployment action.
|
||||
- Updated release action to use softprops/action-gh-release@v2, since the original actions are no longer maintained.
|
||||
|
||||
### Deprecated
|
||||
- Autotools build framework is now deprecated, and will be removed in the next release.
|
||||
|
||||
### Fixed
|
||||
- Github Actions updated to use current set of runners
|
||||
- Fixed handling malformed UTF-8 input in libx52util
|
||||
- Fixed boundary check issue in libx52util that incorrectly returned `-E2BIG` if the output buffer was the exact size to capture the translated string and the null terminator.
|
||||
- Fixed potential UB in libx52-string-test
|
||||
- Fixed NULL pointer dereference in `libx52_exit`
|
||||
- Fixed errors identified by the GCC `-fanalyzer` flag
|
||||
|
||||
### Security
|
||||
- Updated action files to include permission blocks
|
||||
|
||||
## [0.3.2] - 2024-06-09
|
||||
### Added
|
||||
- Updated bug report utility to add details about build host details and
|
||||
compiler information.
|
||||
|
||||
### Fixed
|
||||
- Updated syntax check for calloc calls. See
|
||||
[#52](https://github.com/nirenjan/libx52/issues/52)
|
||||
- Fixed a tooling bug where running make check on a system without cmocka
|
||||
library installed would fail during daemon testing.
|
||||
- Cleaned up daemon protocol documentation
|
||||
|
||||
### Changed
|
||||
- Moved socket code around to make it easier to reuse the communication logic
|
||||
out in both client(s) and server.
|
||||
|
||||
## 0.3.1 - 2024-06-08
|
||||
|
||||
**Important:** Tag 0.3.1 has a bad Version file and should not be used. This has
|
||||
been superseded by 0.3.2 with corrected metadata. The changes from the previous
|
||||
release are the same.
|
||||
|
||||
## [0.3.0] - 2022-12-25
|
||||
### Added
|
||||
- Bug report utility to make it easier to gather system and build information
|
||||
|
|
@ -158,8 +225,10 @@ The format is based upon [Keep a Changelog].
|
|||
[Keep a Changelog]: http://keepachangelog.com/en/1.0.0/
|
||||
[Semantic Versioning]: http://semver.org/spec/v2.0.0.html
|
||||
[TAP]: https://testanything.org
|
||||
[Unreleased]: https://github.com/nirenjan/libx52/compare/v0.3.0...HEAD
|
||||
[0.2.3]: https://github.com/nirenjan/libx52/compare/v0.2.3...v0.3.0
|
||||
[Unreleased]: https://github.com/nirenjan/libx52/compare/v0.3.3...HEAD
|
||||
[0.3.3]: https://github.com/nirenjan/libx52/compare/v0.3.2...v0.3.3
|
||||
[0.3.2]: https://github.com/nirenjan/libx52/compare/v0.3.0...v0.3.2
|
||||
[0.3.0]: https://github.com/nirenjan/libx52/compare/v0.2.3...v0.3.0
|
||||
[0.2.3]: https://github.com/nirenjan/libx52/compare/v0.2.2...v0.2.3
|
||||
[0.2.2]: https://github.com/nirenjan/libx52/compare/v0.2.1...v0.2.2
|
||||
[0.2.1]: https://github.com/nirenjan/libx52/compare/v0.2.0...v0.2.1
|
||||
|
|
|
|||
845
Doxyfile.in
845
Doxyfile.in
File diff suppressed because it is too large
Load Diff
59
INSTALL.md
59
INSTALL.md
|
|
@ -3,23 +3,24 @@ Installation instructions for libx52
|
|||
|
||||
Build has been tested on the following operating systems (x86-64 only):
|
||||
|
||||
* Ubuntu 20.04 LTS
|
||||
* Ubuntu 22.04 LTS
|
||||
* macOS Big Sur 11
|
||||
* macOS Monterey 12
|
||||
* Ubuntu 24.04 LTS
|
||||
* 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
|
||||
* libtool
|
||||
* inih
|
||||
* 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)
|
||||
|
|
@ -28,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 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 python git` |
|
||||
| Fedora | `sudo dnf install autoconf automake gettext-devel findutils libtool hidapi-devel libusb-devel libevdev-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 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
|
||||
|
||||
|
|
@ -45,40 +47,39 @@ Build has been tested on the following operating systems (x86-64 only):
|
|||
git clone https://github.com/nirenjan/libx52.git
|
||||
```
|
||||
|
||||
2. Run autogen.sh
|
||||
2. Configure the build (from the repository root)
|
||||
```
|
||||
cd ./libx52
|
||||
./autogen.sh
|
||||
cd libx52
|
||||
meson setup build -Dprefix=/usr
|
||||
```
|
||||
|
||||
3. Run the following commands:
|
||||
3. Compile and install:
|
||||
```
|
||||
./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
|
||||
|
|
@ -88,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
|
||||
```
|
||||
|
|
|
|||
110
Makefile.am
110
Makefile.am
|
|
@ -1,110 +0,0 @@
|
|||
# Top level Automake for libx52
|
||||
#
|
||||
# Copyright (C) 2012-2018 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
|
||||
ACLOCAL_AMFLAGS = -I m4
|
||||
|
||||
# Build any support libraries first
|
||||
SUBDIRS = lib
|
||||
|
||||
if USE_NLS
|
||||
SUBDIRS += po
|
||||
endif
|
||||
|
||||
#######################################################################
|
||||
# Defaults
|
||||
#######################################################################
|
||||
bin_PROGRAMS =
|
||||
check_PROGRAMS =
|
||||
lib_LTLIBRARIES =
|
||||
check_LTLIBRARIES =
|
||||
pkgconfig_DATA =
|
||||
TESTS =
|
||||
EXTRA_DIST =
|
||||
CLEANFILES =
|
||||
BUILT_SOURCES =
|
||||
|
||||
x52includedir = $(includedir)/libx52
|
||||
x52include_HEADERS =
|
||||
|
||||
LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) $(top_srcdir)/tap-driver.sh
|
||||
|
||||
########################################################################
|
||||
# Get build version
|
||||
########################################################################
|
||||
BUILT_SOURCES += version-info.h
|
||||
CLEANFILES += version-info.h
|
||||
|
||||
version-info.h: ${top_srcdir}/version-info
|
||||
${top_srcdir}/version-info ${top_srcdir} >$@
|
||||
|
||||
########################################################################
|
||||
# Include automake stubs
|
||||
########################################################################
|
||||
include libx52/Makefile.am
|
||||
include libx52util/Makefile.am
|
||||
include libx52io/Makefile.am
|
||||
include libusbx52/Makefile.am
|
||||
|
||||
include cli/Makefile.am
|
||||
include joytest/Makefile.am
|
||||
include evtest/Makefile.am
|
||||
include daemon/Makefile.am
|
||||
include udev/Makefile.am
|
||||
|
||||
include bugreport/Makefile.am
|
||||
include docs/Makefile.am
|
||||
|
||||
#######################################################################
|
||||
# Doxygen support
|
||||
#######################################################################
|
||||
if HAVE_DOXYGEN
|
||||
DXGEN = $(DXGEN_@AM_V@)
|
||||
DXGEN_ = $(DXGEN_@AM_DEFAULT_V@)
|
||||
DXGEN_0 = @printf " DXGEN $<\n";
|
||||
|
||||
SYSCONFDIR=@sysconfdir@
|
||||
LOCALSTATEDIR=@localstatedir@
|
||||
export SYSCONFDIR
|
||||
export LOCALSTATEDIR
|
||||
docs/.stamp: Doxyfile
|
||||
$(DXGEN)$(DOXYGEN) $<
|
||||
$(AM_V_at)touch $@
|
||||
|
||||
all-local: docs/.stamp
|
||||
clean-local:
|
||||
rm -rf $(top_builddir)/docs
|
||||
|
||||
man1_MANS = docs/man/man1/x52cli.1 docs/man/man1/x52bugreport.1
|
||||
$(man1_MANS): docs/.stamp
|
||||
|
||||
# Install Doxygen generated HTML documentation and manpages
|
||||
install-data-local:
|
||||
$(INSTALL) -d $(DESTDIR)$(docdir)
|
||||
cp -R -P $(top_builddir)/docs/html $(DESTDIR)$(docdir)
|
||||
|
||||
uninstall-local:
|
||||
rm -rf $(DESTDIR)$(docdir)
|
||||
|
||||
endif
|
||||
|
||||
# Extra files that need to be in the distribution
|
||||
EXTRA_DIST += \
|
||||
ABOUT-NLS \
|
||||
AUTHORS \
|
||||
ChangeLog.md \
|
||||
CONTRIBUTING.md \
|
||||
Doxyfile.in \
|
||||
DoxygenLayout.xml \
|
||||
INSTALL.md \
|
||||
LICENSE \
|
||||
README.md \
|
||||
config.rpath \
|
||||
version-info \
|
||||
Version \
|
||||
gettext.h \
|
||||
usb-ids.h \
|
||||
po/README.md
|
||||
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
# Privacy
|
||||
|
||||
This document describes how the libx52 project handles data, for compliance with
|
||||
privacy laws such as the GDPR and CCPA.
|
||||
|
||||
## Summary
|
||||
|
||||
- **No automatic collection**: The software does not transmit any data to the
|
||||
project or third parties. No telemetry, analytics, or crash reporting is
|
||||
implemented.
|
||||
- **Local operation**: All processing is on your machine. Configuration and logs
|
||||
stay local unless you choose to share them (e.g. when opening a bug report).
|
||||
|
||||
## Data that may be displayed or stored locally
|
||||
|
||||
| Data | Where | Purpose |
|
||||
|------|--------|---------|
|
||||
| **Device serial number** | `evtest` only | Shown when you run evtest against a connected device (for local identification). **Not** included in **x52bugreport** output. |
|
||||
| **Device type info** | `x52bugreport`, `evtest` | Vendor ID, product ID, device version, manufacturer and product name (e.g. "Saitek" / "X52 Pro"). No serial number or hostname in bugreport. |
|
||||
| **System information** | `x52bugreport` only | Kernel name/release, machine architecture, kernel version string. **Hostname is not included.** |
|
||||
| **Build environment** | `x52bugreport` only | Compiler, build date, kernel/arch/OS version at build time. No hostname or other machine identifier. |
|
||||
| **Paths** | Daemon logs (if enabled) | Log file path, config path, socket path. Default paths use system directories (e.g. `/var`, `/run`, `/etc`), not your home directory. |
|
||||
| **Configuration** | Config files (e.g. under `/etc/x52d/`) | MFD/LED and daemon settings. Stored only on your system. |
|
||||
|
||||
None of this data is sent anywhere by the software. The only way it leaves your
|
||||
system is if you voluntarily paste it (e.g. into a GitHub issue).
|
||||
|
||||
## Bug reports
|
||||
|
||||
**x52bugreport** output is designed to avoid personal and device identifiers:
|
||||
|
||||
- **Device serial number** and **system hostname** are **not** included in the output.
|
||||
- Included: package/build version, compiler, build date, kernel and machine type, library versions, device vendor/product ID and device name (manufacturer/product strings only).
|
||||
|
||||
You may still redact any line before posting if you prefer. For most bugs, the information above is sufficient.
|
||||
|
||||
## Your rights (GDPR / CCPA style)
|
||||
|
||||
- **No account or sign-up** is required to use the software, so we do not hold
|
||||
an account-based profile on you.
|
||||
- **No selling of data**: We do not collect or sell personal data.
|
||||
- **Transparency**: This document describes what the software can display or
|
||||
store locally.
|
||||
- **Control**: You decide whether to run `x52bugreport` and what to include when
|
||||
opening an issue.
|
||||
|
||||
## Third-party services
|
||||
|
||||
- **Source and issues**: If you clone the repo or open an issue on GitHub,
|
||||
GitHub’s privacy policy applies to that interaction ([GitHub Privacy
|
||||
Statement](https://docs.github.com/en/site-policy/privacy-policies/github-privacy-statement)).
|
||||
- **Packages**: Installation via Ubuntu PPA or Arch AUR is subject to
|
||||
Canonical’s or the AUR’s respective terms and privacy practices.
|
||||
|
||||
## Changes
|
||||
|
||||
We may update this document to reflect changes in the software or in legal
|
||||
requirements. The current version is in the project repository.
|
||||
|
|
@ -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)
|
||||
|
|
@ -18,6 +20,8 @@ capable of reading the joystick, but it cannot control the MFD or LEDs.
|
|||
Most of the extra functionality can be handled from userspace. See
|
||||
the individual folders for README information.
|
||||
|
||||
For data handling and privacy (e.g. GDPR/CCPA), see [PRIVACY.md](PRIVACY.md).
|
||||
|
||||
**Note:** This repository currently only provides commandline interfaces to
|
||||
control the MFD and LEDs. If you are not comfortable working in the commandline,
|
||||
then the [gx52](https://gitlab.com/leinardi/gx52) project might be a better fit
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
# Security Policy
|
||||
|
||||
## Reporting Security Vulnerabiltiies
|
||||
|
||||
You may report a security vulnerability by [creating a new security advisory](https://github.com/nirenjan/libx52/security/advisories/new).
|
||||
|
||||
## Supported Versions
|
||||
|
||||
All security fixes will be made to the `master` branch. Older versions are not supported.
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
#!/bin/sh -x
|
||||
|
||||
autoreconf --install
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
# Automake for libx52-bugreport
|
||||
#
|
||||
# Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
|
||||
bin_PROGRAMS += x52bugreport
|
||||
|
||||
# Bug report program that reports the build and linked library versions
|
||||
x52bugreport_SOURCES = bugreport/bugreport.c
|
||||
x52bugreport_CFLAGS = \
|
||||
-I$(top_srcdir)/libx52io \
|
||||
@LIBUSB_CFLAGS@ \
|
||||
@HIDAPI_CFLAGS@ \
|
||||
$(WARN_CFLAGS)
|
||||
|
||||
x52bugreport_LDFLAGS = \
|
||||
@LIBUSB_LIBS@ \
|
||||
@HIDAPI_LIBS@ \
|
||||
$(WARN_LDFLAGS)
|
||||
|
||||
x52bugreport_LDADD = libx52io.la
|
||||
|
||||
EXTRA_DIST += bugreport/bugreport.dox
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "build-config.h"
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
#include "libusb.h"
|
||||
#include "hidapi.h"
|
||||
#include "libx52io.h"
|
||||
#include <libx52/libx52io.h>
|
||||
#include "version-info.h"
|
||||
|
||||
static void print_sysinfo(void)
|
||||
|
|
@ -59,15 +59,13 @@ static void print_devinfo(void)
|
|||
printf("Device name: '%s' '%s'\n",
|
||||
libx52io_get_manufacturer_string(ctx),
|
||||
libx52io_get_product_string(ctx));
|
||||
printf("Serial number: '%s'\n",
|
||||
libx52io_get_serial_number_string(ctx));
|
||||
|
||||
libx52io_close(ctx);
|
||||
devinfo_cleanup:
|
||||
libx52io_exit(ctx);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
int main(void)
|
||||
{
|
||||
const struct libusb_version *libusb;
|
||||
|
||||
|
|
@ -75,7 +73,12 @@ int main(int argc, char **argv)
|
|||
puts("================");
|
||||
printf("Package version: %s\n", VERSION);
|
||||
printf("Build version: %s\n", BUILD_VERSION);
|
||||
printf("Built on: %s\n", BUILD_DATE);
|
||||
printf("Build host kernel: %s\n", BUILD_KERNEL);
|
||||
printf("Build host architecture: %s\n", BUILD_ARCH);
|
||||
printf("Build host version: %s\n", BUILD_OS_VERSION);
|
||||
printf("Build target: %s\n", BUILD_TARGET);
|
||||
printf("Compiler: %s\n", BUILD_COMPILER);
|
||||
printf("Build date: %s\n", BUILD_DATE);
|
||||
printf("version-info %s\n", BUILD_VERSION_INFO_IDENT);
|
||||
|
||||
puts("");
|
||||
|
|
|
|||
|
|
@ -15,6 +15,10 @@
|
|||
current system and build environment. The reported information can be provided
|
||||
when raising a bug report on https://github.com/nirenjan/libx52/issues.
|
||||
|
||||
The output does not include device serial number or system hostname. You may
|
||||
redact any line before posting if you prefer. See the project PRIVACY.md for
|
||||
details.
|
||||
|
||||
# USAGE
|
||||
|
||||
\b x52bugreport
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
#######################################################################
|
||||
# Version information
|
||||
#######################################################################
|
||||
compiler_version = run_command(compiler.cmd_array(), '--version',
|
||||
capture: true,
|
||||
check: true).stdout().split('\n')[0]
|
||||
|
||||
build_date = run_command('date', '+%Y-%m-%dT%H:%M:%S%z',
|
||||
capture: true,
|
||||
check: true).stdout().strip()
|
||||
|
||||
build_kernel = run_command('uname', '-sr',
|
||||
capture: true,
|
||||
check: true).stdout().strip()
|
||||
|
||||
build_arch = run_command('uname', '-mp',
|
||||
capture: true,
|
||||
check: true).stdout().strip()
|
||||
|
||||
build_os_version = run_command('uname', '-v',
|
||||
capture: true,
|
||||
check: true).stdout().strip()
|
||||
|
||||
built_for = '@0@ @1@ @2@-endian'.format(
|
||||
host_machine.system(),
|
||||
host_machine.cpu(),
|
||||
host_machine.endian(),
|
||||
)
|
||||
|
||||
git = find_program('git', required: false)
|
||||
if git.found()
|
||||
vcs_describe = run_command(git, 'describe', '--dirty',
|
||||
capture: true,
|
||||
check: false).stdout().strip()
|
||||
if vcs_describe == ''
|
||||
vcs_describe = meson.project_version()
|
||||
endif
|
||||
else
|
||||
vcs_describe = meson.project_version()
|
||||
endif
|
||||
|
||||
version_data = configuration_data()
|
||||
version_data.set_quoted('BUILD_VERSION', vcs_describe)
|
||||
version_data.set_quoted('BUILD_DATE', build_date)
|
||||
version_data.set_quoted('BUILD_KERNEL', build_kernel)
|
||||
version_data.set_quoted('BUILD_ARCH', build_arch)
|
||||
version_data.set_quoted('BUILD_OS_VERSION', build_os_version)
|
||||
version_data.set_quoted('BUILD_COMPILER', compiler_version)
|
||||
version_data.set_quoted('BUILD_TARGET', built_for)
|
||||
version_data.set_quoted('BUILD_VERSION_INFO_IDENT', '$Id$')
|
||||
|
||||
version_info_h = configure_file(
|
||||
input: 'version-info.h.meson',
|
||||
output: 'version-info.h',
|
||||
configuration: version_data
|
||||
)
|
||||
|
||||
# x52bugreport
|
||||
exe_bugreport = executable('x52bugreport', 'bugreport.c',
|
||||
install: true,
|
||||
include_directories: [includes],
|
||||
dependencies: [dep_libusb, dep_hidapi],
|
||||
link_with: [lib_libx52io])
|
||||
|
||||
# Test only to get code coverage
|
||||
test('x52bugreport', exe_bugreport, protocol:'exitcode')
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
#mesondefine BUILD_VERSION
|
||||
#mesondefine BUILD_DATE
|
||||
#mesondefine BUILD_KERNEL
|
||||
#mesondefine BUILD_ARCH
|
||||
#mesondefine BUILD_OS_VERSION
|
||||
#mesondefine BUILD_COMPILER
|
||||
#mesondefine BUILD_TARGET
|
||||
#mesondefine BUILD_VERSION_INFO_IDENT
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
# Automake for x52cli
|
||||
#
|
||||
# Copyright (C) 2012-2018 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
|
||||
bin_PROGRAMS += x52cli
|
||||
|
||||
# Command line utility that front ends the core library
|
||||
x52cli_SOURCES = cli/x52_cli.c
|
||||
x52cli_CFLAGS = -I $(top_srcdir)/libx52 $(WARN_CFLAGS)
|
||||
x52cli_LDFLAGS = $(WARN_LDFLAGS)
|
||||
x52cli_LDADD = libx52.la
|
||||
|
||||
if HAVE_CMOCKA
|
||||
TESTS += test-cli
|
||||
check_PROGRAMS += test-cli
|
||||
|
||||
test_cli_SOURCES = cli/x52_cli.c cli/test_x52_cli.c
|
||||
test_cli_CFLAGS = -DX52_CLI_TESTING -I $(top_srcdir)/libx52
|
||||
test_cli_LDFLAGS = @CMOCKA_LIBS@ $(WARN_LDFLAGS)
|
||||
|
||||
# Add a dependency on test_x52_cli_tests.c
|
||||
cli/test_x52_cli.c: cli/test_x52_cli_tests.c
|
||||
endif
|
||||
|
||||
EXTRA_DIST += cli/test_x52_cli_tests.c
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
# x52cli
|
||||
executable('x52cli', 'x52_cli.c',
|
||||
install: true,
|
||||
include_directories: [includes],
|
||||
link_with: lib_libx52)
|
||||
|
||||
test_cli = executable('test-cli', 'x52_cli.c', 'test_x52_cli.c',
|
||||
build_by_default: false,
|
||||
c_args: ['-DX52_CLI_TESTING'],
|
||||
include_directories: [includes],
|
||||
dependencies: [dep_cmocka],
|
||||
)
|
||||
|
||||
test('test-cli', test_cli, protocol: 'tap')
|
||||
|
||||
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
#include <setjmp.h>
|
||||
#include <cmocka.h>
|
||||
|
||||
#include "libx52.h"
|
||||
#include <libx52/libx52.h>
|
||||
|
||||
extern int run_main(int argc, char **argv);
|
||||
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ possibly through \b sudo(8)
|
|||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include "config.h"
|
||||
#include "build-config.h"
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
|
@ -158,7 +158,7 @@ possibly through \b sudo(8)
|
|||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "libx52.h"
|
||||
#include <libx52/libx52.h>
|
||||
|
||||
struct string_map {
|
||||
const char *key;
|
||||
|
|
@ -462,12 +462,12 @@ static void do_help(const struct command_handler *cmd)
|
|||
if (cmd) {
|
||||
fprintf(stderr, "Command usage: %s\n", cmd->help);
|
||||
} else {
|
||||
printf("\nCommands:\n");
|
||||
fprintf(stderr, "\nCommands:\n");
|
||||
for (i = 0; i < X52_CTL_CMD_MAX; i++) {
|
||||
printf("\t%s\n", handlers[i].help);
|
||||
fprintf(stderr, "\t%s\n", handlers[i].help);
|
||||
}
|
||||
|
||||
printf("\nWARNING: raw command may damage your device\n\n");
|
||||
fprintf(stderr, "\nWARNING: raw command may damage your device\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
146
configure.ac
146
configure.ac
|
|
@ -1,146 +0,0 @@
|
|||
# Autoconf settings for libx52
|
||||
#
|
||||
# Copyright (C) 2012-2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
|
||||
AC_INIT([libx52], [m4_esyscmd_s([cat ./Version])], [nirenjan@gmail.com])
|
||||
AC_CONFIG_MACRO_DIR([m4])
|
||||
AM_INIT_AUTOMAKE([-Wall foreign subdir-objects])
|
||||
AC_REQUIRE_AUX_FILE([tap-driver.sh])
|
||||
AC_PROG_CC
|
||||
AC_PROG_CC_STDC
|
||||
AC_PROG_AWK
|
||||
AC_PROG_SED
|
||||
AC_PROG_MKDIR_P
|
||||
AM_PROG_AR
|
||||
AM_PATH_PYTHON([3.5])
|
||||
LT_INIT
|
||||
PKG_PROG_PKG_CONFIG
|
||||
PKG_INSTALLDIR
|
||||
AX_COMPILER_FLAGS
|
||||
AC_CANONICAL_HOST
|
||||
AX_GCC_FUNC_ATTRIBUTE([constructor])
|
||||
AX_GCC_FUNC_ATTRIBUTE([destructor])
|
||||
AX_GCC_FUNC_ATTRIBUTE([format])
|
||||
AX_GCC_FUNC_ATTRIBUTE([noreturn])
|
||||
AC_C_TYPEOF
|
||||
|
||||
AC_MSG_NOTICE([Detected host OS is ${host_os}])
|
||||
build_linux=no
|
||||
# Detect target system
|
||||
case "${host_os}" in
|
||||
linux*)
|
||||
build_linux=yes
|
||||
;;
|
||||
esac
|
||||
AM_CONDITIONAL([LINUX], [test "x${build_linux}" = "xyes"])
|
||||
|
||||
# Internationalization
|
||||
AM_GNU_GETTEXT([external])
|
||||
AM_GNU_GETTEXT_VERSION(0.19)
|
||||
AM_CONDITIONAL([USE_NLS], [test "x${USE_NLS}" == "xyes"])
|
||||
|
||||
# Check for libusb-1.0
|
||||
PKG_CHECK_MODULES([LIBUSB], [libusb-1.0])
|
||||
AC_SUBST([LIBUSB_PC], [libusb-1.0])
|
||||
|
||||
# systemd support
|
||||
PKG_CHECK_MODULES([SYSTEMD], [systemd], [have_systemd=yes], [have_systemd=no])
|
||||
AC_ARG_ENABLE([systemd],
|
||||
[AS_HELP_STRING([--disable-systemd], [Disable systemd support])]
|
||||
)
|
||||
AC_ARG_WITH([systemdsystemunitdir],
|
||||
[AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd unit files])],
|
||||
[systemdsystemunitdir=$withval],
|
||||
[systemdsystemunitdir=$($PKG_CONFIG --variable=systemdsystemunitdir systemd)]
|
||||
)
|
||||
AC_SUBST([systemdsystemunitdir], [$systemdsystemunitdir])
|
||||
AM_CONDITIONAL([HAVE_SYSTEMD], [test "x$have_systemd" = "xyes" -a "x$enable_systemd" != "xno"])
|
||||
AM_COND_IF([HAVE_SYSTEMD],,
|
||||
[
|
||||
AC_MSG_NOTICE([systemd not found or disabled. Enabling timestamps in logs])
|
||||
AX_APPEND_FLAG([-DPINELOG_SHOW_DATE=1], [PINELOG_CFLAGS])
|
||||
]
|
||||
)
|
||||
|
||||
# evdev support
|
||||
# This is only on Linux machines, so we need to set an automake conditional
|
||||
PKG_CHECK_MODULES([EVDEV], [libevdev], [have_evdev=yes], [have_evdev=no])
|
||||
AM_CONDITIONAL([HAVE_EVDEV], [test "x$have_evdev" = "xyes"])
|
||||
|
||||
# Pinelog configuration
|
||||
AX_APPEND_FLAG([-DPINELOG_SHOW_LEVEL=1], [PINELOG_CFLAGS])
|
||||
AX_APPEND_FLAG([-DPINELOG_SHOW_BACKTRACE=1], [PINELOG_CFLAGS])
|
||||
AX_APPEND_FLAG([-DPINELOG_BUFFER_SZ=1024], [PINELOG_CFLAGS])
|
||||
AC_SUBST([PINELOG_CFLAGS])
|
||||
|
||||
# Check for hidapi. This uses a different pkg-config file on Linux vs other
|
||||
# hosts, so check accordingly
|
||||
AM_COND_IF([LINUX], [hidapi_backend=hidapi-hidraw], [hidapi_backend=hidapi])
|
||||
PKG_CHECK_MODULES([HIDAPI], [${hidapi_backend}])
|
||||
AC_SUBST([HIDAPI_PC], [${hidapi_backend}])
|
||||
|
||||
# Check for pthreads
|
||||
ACX_PTHREAD
|
||||
|
||||
# make distcheck doesn't work if some files are installed outside $prefix.
|
||||
# Check for a prefix ending in /_inst, if this is found, we can assume this
|
||||
# to be a make distcheck, and disable some of the installcheck stuff.
|
||||
AS_CASE([$prefix], [*/_inst],
|
||||
[AC_MSG_NOTICE([[Prefix ends in /_inst; this looks like a 'make distcheck']])
|
||||
is_make_distcheck=yes])
|
||||
AM_CONDITIONAL([IS_MAKE_DISTCHECK], [test "x$is_make_distcheck" = xyes])
|
||||
AC_MSG_CHECKING([final decision IS_MAKE_DISTCHECK (running "make distcheck"?)])
|
||||
AM_COND_IF([IS_MAKE_DISTCHECK], [AC_MSG_RESULT([yes])], [AC_MSG_RESULT([no])])
|
||||
|
||||
# udev support
|
||||
PKG_CHECK_MODULES([UDEV], [udev], [have_udev=yes], [have_udev=no])
|
||||
AM_CONDITIONAL([HAVE_UDEV], [test "x$have_udev" = xyes])
|
||||
AC_ARG_WITH([udevrulesdir],
|
||||
AS_HELP_STRING([--with-udevrulesdir=DIR], [Directory for udev rules]),
|
||||
[udevrulesdir=$withval],
|
||||
[udevrulesdir=$($PKG_CONFIG --variable=udevdir udev)"/rules.d"])
|
||||
AC_SUBST([udevrulesdir], [$udevrulesdir])
|
||||
|
||||
AC_ARG_WITH([input-group],
|
||||
AS_HELP_STRING([--with-input-group=GROUP], [Group allowed to access input devices]),
|
||||
[input_group=$withval],
|
||||
[input_group=plugdev])
|
||||
AC_SUBST([input_group], [$input_group])
|
||||
|
||||
# Doxygen Support
|
||||
AC_CHECK_PROGS([DOXYGEN], [doxygen])
|
||||
AM_CONDITIONAL([HAVE_DOXYGEN], [test -n "$DOXYGEN"])
|
||||
AM_COND_IF([HAVE_DOXYGEN],
|
||||
[AC_CONFIG_FILES([Doxyfile])],
|
||||
[AC_MSG_WARN(["Doxygen not found; continuing without doxygen support"])])
|
||||
|
||||
# cmocka unit tests
|
||||
PKG_CHECK_MODULES([CMOCKA], [cmocka >= 1.1], [have_cmocka=yes], [have_cmocka=no])
|
||||
AM_CONDITIONAL([HAVE_CMOCKA], [test "x$have_cmocka" = xyes])
|
||||
AM_COND_IF([HAVE_CMOCKA], [],
|
||||
[AC_MSG_WARN(["cmocka not found; disabling unit test build"])])
|
||||
|
||||
# Check for the presence of tm_gmtoff in struct tm. If we have this, then we
|
||||
# can use it to determine the true GMT offset
|
||||
AC_CHECK_MEMBERS([struct tm.tm_gmtoff],,
|
||||
[AC_MSG_WARN(["Cannot find tm_gmtoff in struct tm, using slower method"])],
|
||||
[#define _GNU_SOURCE
|
||||
#include <time.h>
|
||||
])
|
||||
|
||||
# Configuration headers
|
||||
AC_CONFIG_HEADERS([config.h])
|
||||
|
||||
AC_CONFIG_FILES([ po/Makefile.in
|
||||
Makefile
|
||||
lib/Makefile
|
||||
libx52/libx52.pc
|
||||
libx52io/libx52io.pc
|
||||
libx52util/libx52util.pc
|
||||
lib/pinelog/Makefile
|
||||
lib/inih/Makefile
|
||||
udev/60-saitek-x52-x52pro.rules
|
||||
])
|
||||
AC_OUTPUT
|
||||
|
|
@ -1,143 +0,0 @@
|
|||
# Automake for x52d
|
||||
#
|
||||
# Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
bin_PROGRAMS += x52d x52ctl
|
||||
|
||||
# Service daemon that manages the X52 device
|
||||
x52d_SOURCES = \
|
||||
daemon/x52d_main.c \
|
||||
daemon/x52d_config_parser.c \
|
||||
daemon/x52d_config_dump.c \
|
||||
daemon/x52d_config.c \
|
||||
daemon/x52d_device.c \
|
||||
daemon/x52d_client.c \
|
||||
daemon/x52d_clock.c \
|
||||
daemon/x52d_mouse.c \
|
||||
daemon/x52d_notify.c \
|
||||
daemon/x52d_led.c \
|
||||
daemon/x52d_command.c \
|
||||
daemon/x52d_comm_internal.c \
|
||||
daemon/x52d_comm_client.c
|
||||
|
||||
x52d_CFLAGS = \
|
||||
-I $(top_srcdir) \
|
||||
-I $(top_srcdir)/libx52io \
|
||||
-I $(top_srcdir)/libx52 \
|
||||
-I $(top_srcdir)/libx52util \
|
||||
-I $(top_srcdir)/lib/pinelog \
|
||||
-I $(top_srcdir)/lib/inih \
|
||||
-DSYSCONFDIR=\"$(sysconfdir)\" \
|
||||
-DLOCALEDIR=\"$(localedir)\" \
|
||||
-DLOGDIR=\"$(localstatedir)/log\" \
|
||||
-DRUNDIR=\"$(localstatedir)/run\" \
|
||||
@PTHREAD_CFLAGS@ $(WARN_CFLAGS)
|
||||
|
||||
x52d_LDFLAGS = @PTHREAD_LIBS@ $(WARN_LDFLAGS)
|
||||
x52d_LDADD = \
|
||||
lib/pinelog/libpinelog.la \
|
||||
lib/inih/libinih.la \
|
||||
libx52.la \
|
||||
@LTLIBINTL@
|
||||
|
||||
if HAVE_EVDEV
|
||||
x52d_SOURCES += \
|
||||
daemon/x52d_io.c \
|
||||
daemon/x52d_mouse_evdev.c
|
||||
|
||||
x52d_CFLAGS += -DHAVE_EVDEV @EVDEV_CFLAGS@
|
||||
x52d_LDFLAGS += @EVDEV_LIBS@
|
||||
x52d_LDADD += libx52io.la
|
||||
endif
|
||||
|
||||
lib_LTLIBRARIES += libx52dcomm.la
|
||||
|
||||
# Client library to communicate with X52 daemon
|
||||
libx52dcomm_la_SOURCES = \
|
||||
daemon/x52d_comm_client.c \
|
||||
daemon/x52d_comm_internal.c
|
||||
libx52dcomm_la_CFLAGS = \
|
||||
-I $(top_srcdir) \
|
||||
-DSYSCONFDIR=\"$(sysconfdir)\" \
|
||||
-DLOCALEDIR=\"$(localedir)\" \
|
||||
-DLOGDIR=\"$(localstatedir)/log\" \
|
||||
-DRUNDIR=\"$(localstatedir)/run\" \
|
||||
$(WARN_CFLAGS)
|
||||
libx52dcomm_la_LDFLAGS = $(WARN_LDFLAGS)
|
||||
|
||||
x52include_HEADERS += daemon/x52dcomm.h
|
||||
|
||||
x52ctl_SOURCES = daemon/x52ctl.c
|
||||
x52ctl_CFLAGS = \
|
||||
-I $(top_srcdir) \
|
||||
$(WARN_CFLAGS)
|
||||
x52ctl_LDFLAGS = $(WARN_LDFLAGS)
|
||||
x52ctl_LDADD = libx52dcomm.la @LTLIBINTL@
|
||||
|
||||
x52dconfdir = @sysconfdir@/x52d
|
||||
x52dconf_DATA = daemon/x52d.conf
|
||||
|
||||
install-exec-hook:
|
||||
$(MKDIR_P) $(DESTDIR)$(localstatedir)/log
|
||||
$(MKDIR_P) $(DESTDIR)$(localstatedir)/run
|
||||
|
||||
EXTRA_DIST += \
|
||||
daemon/daemon.dox \
|
||||
daemon/protocol.dox \
|
||||
daemon/x52d.service.in \
|
||||
daemon/x52d_client.h \
|
||||
daemon/x52d_clock.h \
|
||||
daemon/x52d_config.def \
|
||||
daemon/x52d_config.h \
|
||||
daemon/x52d_const.h \
|
||||
daemon/x52d_device.h \
|
||||
daemon/x52d_io.h \
|
||||
daemon/x52d_mouse.h \
|
||||
daemon/x52d_notify.h \
|
||||
daemon/x52d_command.h \
|
||||
daemon/x52dcomm.h \
|
||||
daemon/x52dcomm-internal.h \
|
||||
daemon/x52d.conf
|
||||
|
||||
# Test cases
|
||||
EXTRA_DIST += \
|
||||
daemon/test_daemon_comm.py \
|
||||
daemon/tests/config/args.tc \
|
||||
daemon/tests/config/clock.tc \
|
||||
daemon/tests/config/led.tc \
|
||||
daemon/tests/config/mouse.tc \
|
||||
daemon/tests/logging/error.tc \
|
||||
daemon/tests/logging/global.tc \
|
||||
daemon/tests/logging/module.tc \
|
||||
daemon/tests/cli.tc
|
||||
|
||||
check_PROGRAMS += x52d-mouse-test
|
||||
|
||||
x52d_mouse_test_SOURCES = \
|
||||
daemon/x52d_mouse_test.c \
|
||||
daemon/x52d_mouse.c
|
||||
x52d_mouse_test_CFLAGS = \
|
||||
-DLOCALEDIR='"$(localedir)"' \
|
||||
-I $(top_srcdir) \
|
||||
-I $(top_srcdir)/libx52 \
|
||||
-I $(top_srcdir)/libx52io \
|
||||
-I $(top_srcdir)/lib/pinelog \
|
||||
$(WARN_CFLAGS)
|
||||
x52d_mouse_test_LDFLAGS = @CMOCKA_LIBS@ $(WARN_LDFLAGS)
|
||||
x52d_mouse_test_LDADD = \
|
||||
lib/pinelog/libpinelog.la \
|
||||
@LTLIBINTL@
|
||||
|
||||
TESTS += \
|
||||
daemon/test_daemon_comm.py \
|
||||
x52d-mouse-test
|
||||
|
||||
if HAVE_SYSTEMD
|
||||
if !IS_MAKE_DISTCHECK
|
||||
SED_ARGS = s,%bindir%,$(bindir),g
|
||||
x52d.service: daemon/x52d.service.in
|
||||
$(AM_V_GEN) $(SED) -e '$(SED_ARGS)' $< > $@
|
||||
systemdsystemunit_DATA = x52d.service
|
||||
endif
|
||||
endif
|
||||
|
|
@ -12,8 +12,8 @@
|
|||
#include <unistd.h>
|
||||
|
||||
#include "pinelog.h"
|
||||
#include "x52d_client.h"
|
||||
#include "x52dcomm-internal.h"
|
||||
#include <daemon/client.h>
|
||||
#include <daemon/x52dcomm-internal.h>
|
||||
|
||||
void x52d_client_init(int client_fd[X52D_MAX_CLIENTS])
|
||||
{
|
||||
|
|
@ -27,6 +27,11 @@ bool x52d_client_register(int client_fd[X52D_MAX_CLIENTS], int sock_fd)
|
|||
int fd;
|
||||
int i;
|
||||
|
||||
#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ >= 13
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wanalyzer-fd-leak"
|
||||
#endif
|
||||
|
||||
fd = accept(sock_fd, NULL, NULL);
|
||||
if (fd < 0) {
|
||||
PINELOG_ERROR(_("Error accepting client connection on socket fd %d: %s"),
|
||||
|
|
@ -40,6 +45,11 @@ bool x52d_client_register(int client_fd[X52D_MAX_CLIENTS], int sock_fd)
|
|||
goto error;
|
||||
}
|
||||
|
||||
#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ >= 13
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
|
||||
for (i = 0; i < X52D_MAX_CLIENTS; i++) {
|
||||
if (client_fd[i] == INVALID_CLIENT) {
|
||||
PINELOG_TRACE("Accepted client %d on socket %d, slot %d", fd, sock_fd, i);
|
||||
|
|
@ -109,12 +119,11 @@ int x52d_client_poll(int client_fd[X52D_MAX_CLIENTS], struct pollfd pfd[MAX_CONN
|
|||
|
||||
PINELOG_TRACE("Polling %d file descriptors", pfd_count);
|
||||
|
||||
retry_poll:
|
||||
rc = poll(pfd, pfd_count, -1);
|
||||
do {
|
||||
rc = poll(pfd, pfd_count, -1);
|
||||
} while (rc < 0 && errno == EINTR);
|
||||
|
||||
if (rc < 0) {
|
||||
if (errno == EINTR) {
|
||||
goto retry_poll;
|
||||
}
|
||||
PINELOG_ERROR(_("Error %d when polling %d descriptors: %s"),
|
||||
errno, pfd_count, strerror(errno));
|
||||
} else if (rc == 0) {
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
#include <stdbool.h>
|
||||
#include <poll.h>
|
||||
|
||||
#include "x52d_const.h"
|
||||
#include <daemon/constants.h>
|
||||
|
||||
#define MAX_CONN (X52D_MAX_CLIENTS + 1)
|
||||
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "build-config.h"
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
|
|
@ -15,10 +15,10 @@
|
|||
|
||||
#define PINELOG_MODULE X52D_MOD_CLOCK
|
||||
#include "pinelog.h"
|
||||
#include "x52d_config.h"
|
||||
#include "x52d_clock.h"
|
||||
#include "x52d_const.h"
|
||||
#include "x52d_device.h"
|
||||
#include <daemon/config.h>
|
||||
#include <daemon/clock.h>
|
||||
#include <daemon/constants.h>
|
||||
#include <daemon/device.h>
|
||||
|
||||
static bool clock_enabled = false;
|
||||
static int clock_primary_is_local = false;
|
||||
|
|
@ -97,8 +97,11 @@ cleanup:
|
|||
if (orig_tz == NULL) {
|
||||
unsetenv("TZ");
|
||||
} else {
|
||||
setenv("TZ", orig_tz_copy, true);
|
||||
free(orig_tz_copy);
|
||||
// If the copy is NULL, then we didn't change TZ, so don't bother
|
||||
if (orig_tz_copy != NULL) {
|
||||
setenv("TZ", orig_tz_copy, true);
|
||||
free(orig_tz_copy);
|
||||
}
|
||||
}
|
||||
|
||||
if (new_tz != NULL) {
|
||||
|
|
@ -166,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 (;;) {
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "build-config.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
|
@ -15,8 +15,8 @@
|
|||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "x52dcomm.h"
|
||||
#include "x52dcomm-internal.h"
|
||||
#include <libx52/x52dcomm.h>
|
||||
#include <daemon/x52dcomm-internal.h>
|
||||
|
||||
static int _setup_socket(struct sockaddr_un *remote, int len)
|
||||
{
|
||||
|
|
@ -6,15 +6,15 @@
|
|||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "build-config.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "x52dcomm-internal.h"
|
||||
#include "x52d_const.h"
|
||||
#include <daemon/x52dcomm-internal.h>
|
||||
#include <daemon/constants.h>
|
||||
|
||||
const char * x52d_command_sock_path(const char *sock_path)
|
||||
{
|
||||
|
|
@ -54,7 +54,7 @@ static int _setup_sockaddr(struct sockaddr_un *remote, const char *sock_path)
|
|||
remote->sun_family = AF_UNIX;
|
||||
/* We've already verified that sock_path will fit, so we don't need strncpy */
|
||||
strcpy(remote->sun_path, sock_path);
|
||||
len += sizeof(remote->sun_family);
|
||||
len += sizeof(*remote) - sizeof(remote->sun_path);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
|
@ -89,6 +89,22 @@ sock_failure:
|
|||
return -1;
|
||||
}
|
||||
|
||||
int x52d_listen_socket(struct sockaddr_un *local, int len, int sock_fd)
|
||||
{
|
||||
/* Cleanup any existing socket */
|
||||
unlink(local->sun_path);
|
||||
if (bind(sock_fd, (struct sockaddr *)local, (socklen_t)len) < 0) {
|
||||
/* Failure binding socket */
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (listen(sock_fd, X52D_MAX_CLIENTS) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void x52d_split_args(int *argc, char **argv, char *buffer, int buflen)
|
||||
{
|
||||
int i = 0;
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "build-config.h"
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
|
|
@ -21,11 +21,11 @@
|
|||
|
||||
#define PINELOG_MODULE X52D_MOD_COMMAND
|
||||
#include "pinelog.h"
|
||||
#include "x52d_const.h"
|
||||
#include "x52d_command.h"
|
||||
#include "x52d_config.h"
|
||||
#include "x52d_client.h"
|
||||
#include "x52dcomm-internal.h"
|
||||
#include <daemon/constants.h>
|
||||
#include <daemon/command.h>
|
||||
#include <daemon/config.h>
|
||||
#include <daemon/client.h>
|
||||
#include <daemon/x52dcomm-internal.h>
|
||||
|
||||
static int client_fd[X52D_MAX_CLIENTS];
|
||||
|
||||
|
|
@ -208,77 +208,21 @@ static void cmd_config(char *buffer, int *buflen, int argc, char **argv)
|
|||
ERR_fmt("Unknown subcommand '%s' for 'config' command", argv[1]);
|
||||
}
|
||||
|
||||
struct level_map {
|
||||
int level;
|
||||
const char *string;
|
||||
};
|
||||
#define DATA_LMAP(level, resp) do {\
|
||||
int input_level_ ## __LINE__ = level; \
|
||||
const char *lmap_level_ ## __LINE__ = lookup_level_by_id(input_level_ ## __LINE__); \
|
||||
char lmap_unknown_level ## __LINE__[32] = {0}; \
|
||||
if (lmap_level_ ## __LINE__ == NULL) { \
|
||||
snprintf(lmap_unknown_level ## __LINE__, sizeof(lmap_unknown_level ## __LINE__), \
|
||||
"unknown (%d)", input_level_ ## __LINE__); \
|
||||
lmap_level_ ## __LINE__ = lmap_unknown_level ## __LINE__; \
|
||||
} \
|
||||
DATA(resp, lmap_level_ ## __LINE__); \
|
||||
} while(0)
|
||||
|
||||
static int lmap_get_level(const struct level_map *map, const char *string, int notfound)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; map[i].string != NULL; i++) {
|
||||
if (strcasecmp(map[i].string, string) == 0) {
|
||||
return map[i].level;
|
||||
}
|
||||
}
|
||||
|
||||
return notfound;
|
||||
}
|
||||
|
||||
static const char *lmap_get_string(const struct level_map *map, int level)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; map[i].string != NULL; i++) {
|
||||
if (map[i].level == level) {
|
||||
return map[i].string;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int array_find_index(const char **array, int nmemb, const char *string)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < nmemb; i++) {
|
||||
if (strcasecmp(array[i], string) == 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return nmemb;
|
||||
}
|
||||
|
||||
static void cmd_logging(char *buffer, int *buflen, int argc, char **argv)
|
||||
{
|
||||
static const char *modules[X52D_MOD_MAX] = {
|
||||
[X52D_MOD_CONFIG] = "config",
|
||||
[X52D_MOD_CLOCK] = "clock",
|
||||
[X52D_MOD_DEVICE] = "device",
|
||||
[X52D_MOD_IO] = "io",
|
||||
[X52D_MOD_LED] = "led",
|
||||
[X52D_MOD_MOUSE] = "mouse",
|
||||
[X52D_MOD_COMMAND] = "command",
|
||||
[X52D_MOD_CLIENT] = "client",
|
||||
[X52D_MOD_NOTIFY] = "notify",
|
||||
};
|
||||
|
||||
// This corresponds to the levels in pinelog
|
||||
static const struct level_map loglevels[] = {
|
||||
{PINELOG_LVL_NOTSET, "default"},
|
||||
{PINELOG_LVL_NONE, "none"},
|
||||
{PINELOG_LVL_FATAL, "fatal"},
|
||||
{PINELOG_LVL_ERROR, "error"},
|
||||
{PINELOG_LVL_WARNING, "warning"},
|
||||
{PINELOG_LVL_INFO, "info"},
|
||||
{PINELOG_LVL_DEBUG, "debug"},
|
||||
{PINELOG_LVL_TRACE, "trace"},
|
||||
{0, NULL},
|
||||
};
|
||||
|
||||
if (argc < 2) {
|
||||
ERR("Insufficient arguments for 'logging' command");
|
||||
return;
|
||||
|
|
@ -287,14 +231,13 @@ static void cmd_logging(char *buffer, int *buflen, int argc, char **argv)
|
|||
// logging show [module]
|
||||
MATCH(1, "show") {
|
||||
if (argc == 2) {
|
||||
// Show default logging level
|
||||
DATA("global", lmap_get_string(loglevels, pinelog_get_level()));
|
||||
DATA_LMAP(pinelog_get_level(), "global");
|
||||
} else if (argc == 3) {
|
||||
int module = array_find_index(modules, X52D_MOD_MAX, argv[2]);
|
||||
if (module == X52D_MOD_MAX) {
|
||||
int module = lookup_module_by_name(argv[2]);
|
||||
if (module == INT_MAX) {
|
||||
ERR_fmt("Invalid module '%s'", argv[2]);
|
||||
} else {
|
||||
DATA(argv[2], lmap_get_string(loglevels, pinelog_get_module_level(module)));
|
||||
DATA_LMAP(pinelog_get_module_level(module), argv[2]);
|
||||
}
|
||||
} else {
|
||||
ERR_fmt("Unexpected arguments for 'logging show' command; got %d, expected 2 or 3", argc);
|
||||
|
|
@ -306,7 +249,7 @@ static void cmd_logging(char *buffer, int *buflen, int argc, char **argv)
|
|||
// logging set [module] <level>
|
||||
MATCH(1, "set") {
|
||||
if (argc == 3) {
|
||||
int level = lmap_get_level(loglevels, argv[2], INT_MAX);
|
||||
int level = lookup_level_by_name(argv[2]);
|
||||
if (level == INT_MAX) {
|
||||
ERR_fmt("Unknown level '%s' for 'logging set' command", argv[2]);
|
||||
} else if (level == PINELOG_LVL_NOTSET) {
|
||||
|
|
@ -316,10 +259,10 @@ static void cmd_logging(char *buffer, int *buflen, int argc, char **argv)
|
|||
OK("logging", "set", argv[2]);
|
||||
}
|
||||
} else if (argc == 4) {
|
||||
int level = lmap_get_level(loglevels, argv[3], INT_MAX);
|
||||
int module = array_find_index(modules, X52D_MOD_MAX, argv[2]);
|
||||
int level = lookup_level_by_name(argv[3]);
|
||||
int module = lookup_module_by_name(argv[2]);
|
||||
|
||||
if (module == X52D_MOD_MAX) {
|
||||
if (module == INT_MAX) {
|
||||
ERR_fmt("Invalid module '%s'", argv[2]);
|
||||
return;
|
||||
}
|
||||
|
|
@ -397,6 +340,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"),
|
||||
|
|
@ -412,7 +356,6 @@ int x52d_command_init(const char *sock_path)
|
|||
int sock_fd;
|
||||
int len;
|
||||
struct sockaddr_un local;
|
||||
int flags;
|
||||
|
||||
x52d_client_init(client_fd);
|
||||
|
||||
|
|
@ -431,33 +374,17 @@ int x52d_command_init(const char *sock_path)
|
|||
return -1;
|
||||
}
|
||||
|
||||
/* Mark the socket as non-blocking */
|
||||
flags = fcntl(sock_fd, F_GETFL);
|
||||
if (flags < 0) {
|
||||
PINELOG_ERROR(_("Error getting command socket flags: %s"), strerror(errno));
|
||||
goto sock_failure;
|
||||
}
|
||||
if (fcntl(sock_fd, F_SETFL, flags | O_NONBLOCK) < 0) {
|
||||
PINELOG_ERROR(_("Error setting command socket flags: %s"), strerror(errno));
|
||||
goto sock_failure;
|
||||
}
|
||||
|
||||
/* Cleanup any existing socket */
|
||||
unlink(local.sun_path);
|
||||
if (bind(sock_fd, (struct sockaddr *)&local, (socklen_t)len) < 0) {
|
||||
/* Failure binding socket */
|
||||
PINELOG_ERROR(_("Error binding to command socket: %s"), strerror(errno));
|
||||
goto listen_failure;
|
||||
}
|
||||
|
||||
if (listen(sock_fd, X52D_MAX_CLIENTS) < 0) {
|
||||
PINELOG_ERROR(_("Error listening on command socket: %s"), strerror(errno));
|
||||
goto listen_failure;
|
||||
}
|
||||
|
||||
command_sock_fd = sock_fd;
|
||||
if (command_sock_fd < 0) {
|
||||
command_sock_fd = -1;
|
||||
|
||||
/* Mark the socket as non-blocking */
|
||||
if (x52d_set_socket_nonblocking(sock_fd) < 0) {
|
||||
PINELOG_ERROR(_("Error marking command socket as nonblocking: %s"),
|
||||
strerror(errno));
|
||||
goto sock_failure;
|
||||
}
|
||||
|
||||
if (x52d_listen_socket(&local, len, sock_fd) < 0) {
|
||||
PINELOG_ERROR(_("Error listening on command socket: %s"), strerror(errno));
|
||||
goto listen_failure;
|
||||
}
|
||||
|
||||
|
|
@ -6,13 +6,13 @@
|
|||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "build-config.h"
|
||||
#include <errno.h>
|
||||
|
||||
#define PINELOG_MODULE X52D_MOD_CONFIG
|
||||
#include "pinelog.h"
|
||||
#include "x52d_config.h"
|
||||
#include "x52d_const.h"
|
||||
#include <daemon/config.h>
|
||||
#include <daemon/constants.h>
|
||||
|
||||
static struct x52d_config x52d_config;
|
||||
|
||||
|
|
@ -97,7 +97,7 @@ void x52d_config_apply_immediate(const char *section, const char *key)
|
|||
x52d_cfg_set_ ## c_sec ## _ ## c_key(x52d_config . name); \
|
||||
} else
|
||||
|
||||
#include "x52d_config.def"
|
||||
#include <daemon/config.def>
|
||||
// Dummy to capture the trailing else
|
||||
// Wrap it in braces in case tracing has been disabled
|
||||
{ PINELOG_TRACE("Ignoring apply_immediate(%s.%s)", section, key); }
|
||||
|
|
@ -108,5 +108,5 @@ void x52d_config_apply(void)
|
|||
#define CFG(section, key, name, parser, def) \
|
||||
PINELOG_TRACE("Calling configuration callback for " #section "." #key); \
|
||||
x52d_cfg_set_ ## section ## _ ## key(x52d_config . name);
|
||||
#include "x52d_config.def"
|
||||
#include <daemon/config.def>
|
||||
}
|
||||
|
|
@ -71,13 +71,26 @@ CFG(Brightness, LED, brightness[1], int, 128)
|
|||
// Enabled controls whether the virtual mouse is enabled or not.
|
||||
CFG(Mouse, Enabled, mouse_enabled, bool, true)
|
||||
|
||||
// Speed is a value that is proportional to the speed of updates to the
|
||||
// virtual mouse
|
||||
// DEPRECATED: Speed is a value that is proportional to the speed of updates to
|
||||
// the virtual mouse
|
||||
CFG(Mouse, Speed, mouse_speed, int, 0)
|
||||
|
||||
// Sensitivity is a percentage that is used to scale the speed of the virtual
|
||||
// mouse. This replaces the old speed value.
|
||||
CFG(Mouse, Sensitivity, mouse_sensitivity, int, 0)
|
||||
|
||||
// ReverseScroll controls the scrolling direction
|
||||
CFG(Mouse, ReverseScroll, mouse_reverse_scroll, bool, false)
|
||||
|
||||
// IsometricMode controls whether to use linear or isometric speed calculations
|
||||
CFG(Mouse, IsometricMode, mouse_isometric_mode, bool, false)
|
||||
|
||||
// CurveFactor controls the speed curve
|
||||
CFG(Mouse, CurveFactor, mouse_curve_factor, int, 3)
|
||||
|
||||
// Deadzone controls the deadzone range for the thumbstick
|
||||
CFG(Mouse, Deadzone, mouse_deadzone_factor, int, 0)
|
||||
|
||||
/**********************************************************************
|
||||
* Profiles - only valid on Linux
|
||||
*********************************************************************/
|
||||
|
|
@ -92,4 +105,8 @@ CFG(Profiles, ClutchEnabled, clutch_enabled, bool, false)
|
|||
// be held down to remain in clutch mode.
|
||||
CFG(Profiles, ClutchLatched, clutch_latched, bool, false)
|
||||
|
||||
// KeyboardLayout is the default keyboard layout used when mapping
|
||||
// profile keys to keyboard events.
|
||||
CFG(Profiles, KeyboardLayout, profile_keyboard_layout, string, us)
|
||||
|
||||
#undef CFG
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <limits.h>
|
||||
#include "libx52.h"
|
||||
#include <libx52/libx52.h>
|
||||
|
||||
/**
|
||||
* @brief Configuration structure
|
||||
|
|
@ -39,12 +39,18 @@ struct x52d_config {
|
|||
|
||||
bool mouse_enabled;
|
||||
int mouse_speed;
|
||||
int mouse_sensitivity;
|
||||
bool mouse_reverse_scroll;
|
||||
bool mouse_isometric_mode;
|
||||
int mouse_curve_factor;
|
||||
int mouse_deadzone_factor;
|
||||
|
||||
bool clutch_enabled;
|
||||
bool clutch_latched;
|
||||
|
||||
char profiles_dir[NAME_MAX];
|
||||
|
||||
char profile_keyboard_layout[NAME_MAX];
|
||||
};
|
||||
|
||||
/* Callback functions for configuration */
|
||||
|
|
@ -72,10 +78,15 @@ void x52d_cfg_set_Brightness_MFD(uint16_t param);
|
|||
void x52d_cfg_set_Brightness_LED(uint16_t param);
|
||||
void x52d_cfg_set_Mouse_Enabled(bool param);
|
||||
void x52d_cfg_set_Mouse_Speed(int param);
|
||||
void x52d_cfg_set_Mouse_Sensitivity(int param);
|
||||
void x52d_cfg_set_Mouse_ReverseScroll(bool param);
|
||||
void x52d_cfg_set_Mouse_IsometricMode(bool param);
|
||||
void x52d_cfg_set_Mouse_CurveFactor(int param);
|
||||
void x52d_cfg_set_Mouse_Deadzone(int param);
|
||||
void x52d_cfg_set_Profiles_Directory(char* param);
|
||||
void x52d_cfg_set_Profiles_ClutchEnabled(bool param);
|
||||
void x52d_cfg_set_Profiles_ClutchLatched(bool param);
|
||||
void x52d_cfg_set_Profiles_KeyboardLayout(char *param);
|
||||
|
||||
int x52d_config_process_kv(void *user, const char *section, const char *key, const char *value);
|
||||
const char *x52d_config_get_param(struct x52d_config *cfg, const char *section, const char *key);
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "build-config.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stddef.h>
|
||||
|
|
@ -16,9 +16,9 @@
|
|||
|
||||
#define PINELOG_MODULE X52D_MOD_CONFIG
|
||||
#include "pinelog.h"
|
||||
#include "libx52.h"
|
||||
#include "x52d_config.h"
|
||||
#include "x52d_const.h"
|
||||
#include <libx52/libx52.h>
|
||||
#include <daemon/config.h>
|
||||
#include <daemon/constants.h>
|
||||
|
||||
// Create a pointer "name" of type "type", which stores the value of the
|
||||
// corresponding element within the config struct.
|
||||
|
|
@ -130,7 +130,7 @@ int x52d_config_save_file(struct x52d_config *cfg, const char *cfg_file)
|
|||
fprintf(cfg_fp, "%s = %s\n", #key, value); \
|
||||
} \
|
||||
} while (0);
|
||||
#include "x52d_config.def"
|
||||
#include <daemon/config.def>
|
||||
|
||||
exit_dump:
|
||||
free(current_section);
|
||||
|
|
@ -145,7 +145,7 @@ const char *x52d_config_get_param(struct x52d_config *cfg, const char *section,
|
|||
return type ## _dumper(section, key, cfg, offsetof(struct x52d_config, name)); \
|
||||
} \
|
||||
} while (0);
|
||||
#include "x52d_config.def"
|
||||
#include <daemon/config.def>
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "build-config.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stddef.h>
|
||||
|
|
@ -17,8 +17,8 @@
|
|||
#define PINELOG_MODULE X52D_MOD_CONFIG
|
||||
#include "ini.h"
|
||||
#include "pinelog.h"
|
||||
#include "x52d_config.h"
|
||||
#include "x52d_const.h"
|
||||
#include <daemon/config.h>
|
||||
#include <daemon/constants.h>
|
||||
|
||||
/* Parser function typedef */
|
||||
typedef int (*parser_fn)(struct x52d_config *, size_t, const char *);
|
||||
|
|
@ -142,7 +142,7 @@ static const struct config_map {
|
|||
parser_fn parser;
|
||||
size_t offset;
|
||||
} config_map[] = {
|
||||
#include "x52d_config.def"
|
||||
#include <daemon/config.def>
|
||||
|
||||
// Terminating entry
|
||||
{NULL, NULL, NULL, 0}
|
||||
|
|
@ -195,7 +195,7 @@ int x52d_config_set_defaults(struct x52d_config *cfg) {
|
|||
if (rc != 0) { \
|
||||
return rc; \
|
||||
}
|
||||
#include "x52d_config.def"
|
||||
#include <daemon/config.def>
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
{
|
||||
"_comment": "The configuration registry is a historic record of\nall configuration identifiers. Do NOT edit this file manually, or else, the\ncommunication protocol may break.",
|
||||
"sections": {
|
||||
"CLOCK": 1,
|
||||
"LED": 2,
|
||||
"BRIGHTNESS": 3,
|
||||
"MOUSE": 4,
|
||||
"PROFILES": 5
|
||||
},
|
||||
"options": {
|
||||
"CLOCK": {
|
||||
"ENABLED": 1,
|
||||
"PRIMARYISLOCAL": 2,
|
||||
"SECONDARY": 3,
|
||||
"TERTIARY": 4,
|
||||
"FORMATPRIMARY": 5,
|
||||
"FORMATSECONDARY": 6,
|
||||
"FORMATTERTIARY": 7,
|
||||
"DATEFORMAT": 8
|
||||
},
|
||||
"LED": {
|
||||
"FIRE": 1,
|
||||
"THROTTLE": 2,
|
||||
"A": 3,
|
||||
"B": 4,
|
||||
"D": 5,
|
||||
"E": 6,
|
||||
"T1": 7,
|
||||
"T2": 8,
|
||||
"T3": 9,
|
||||
"POV": 10,
|
||||
"CLUTCH": 11
|
||||
},
|
||||
"BRIGHTNESS": {
|
||||
"MFD": 1,
|
||||
"LED": 2
|
||||
},
|
||||
"MOUSE": {
|
||||
"ENABLED": 1,
|
||||
"SENSITIVITY": 2,
|
||||
"SPEED": 3,
|
||||
"REVERSESCROLL": 4,
|
||||
"ISOMETRICMODE": 5,
|
||||
"CURVEFACTOR": 6,
|
||||
"DEADZONE": 7
|
||||
},
|
||||
"PROFILES": {
|
||||
"DIRECTORY": 1,
|
||||
"CLUTCHENABLED": 2,
|
||||
"CLUTCHLATCHED": 3,
|
||||
"KEYBOARDLAYOUT": 4
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -26,18 +26,6 @@
|
|||
|
||||
#define X52D_MAX_CLIENTS 63
|
||||
|
||||
enum {
|
||||
X52D_MOD_CONFIG,
|
||||
X52D_MOD_CLOCK,
|
||||
X52D_MOD_DEVICE,
|
||||
X52D_MOD_IO,
|
||||
X52D_MOD_LED,
|
||||
X52D_MOD_MOUSE,
|
||||
X52D_MOD_COMMAND,
|
||||
X52D_MOD_CLIENT,
|
||||
X52D_MOD_NOTIFY,
|
||||
|
||||
X52D_MOD_MAX
|
||||
};
|
||||
#include "module-map.h" // For module IDs
|
||||
|
||||
#endif // !defined X52D_CONST_H
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - CRC-32 (zlib / Python zlib.crc32 compatible)
|
||||
*
|
||||
* Copyright (C) 2026 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include <daemon/crc32.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* Table matches zlib's crc_table (reflect, poly 0xEDB88320). */
|
||||
static const uint32_t x52_crc32_table[256] = {
|
||||
0x00000000u, 0x77073096u, 0xee0e612cu, 0x990951bau, 0x076dc419u, 0x706af48fu, 0xe963a535u,
|
||||
0x9e6495a3u, 0x0edb8832u, 0x79dcb8a4u, 0xe0d5e91eu, 0x97d2d988u, 0x09b64c2bu, 0x7eb17cbdu,
|
||||
0xe7b82d07u, 0x90bf1d91u, 0x1db71064u, 0x6ab020f2u, 0xf3b97148u, 0x84be41deu, 0x1adad47du,
|
||||
0x6ddde4ebu, 0xf4d4b551u, 0x83d385c7u, 0x136c9856u, 0x646ba8c0u, 0xfd62f97au, 0x8a65c9ecu,
|
||||
0x14015c4fu, 0x63066cd9u, 0xfa0f3d63u, 0x8d080df5u, 0x3b6e20c8u, 0x4c69105eu, 0xd56041e4u,
|
||||
0xa2677172u, 0x3c03e4d1u, 0x4b04d447u, 0xd20d85fdu, 0xa50ab56bu, 0x35b5a8fau, 0x42b2986cu,
|
||||
0xdbbbc9d6u, 0xacbcf940u, 0x32d86ce3u, 0x45df5c75u, 0xdcd60dcfu, 0xabd13d59u, 0x26d930acu,
|
||||
0x51de003au, 0xc8d75180u, 0xbfd06116u, 0x21b4f4b5u, 0x56b3c423u, 0xcfba9599u, 0xb8bda50fu,
|
||||
0x2802b89eu, 0x5f058808u, 0xc60cd9b2u, 0xb10be924u, 0x2f6f7c87u, 0x58684c11u, 0xc1611dabu,
|
||||
0xb6662d3du, 0x76dc4190u, 0x01db7106u, 0x98d220bcu, 0xefd5102au, 0x71b18589u, 0x06b6b51fu,
|
||||
0x9fbfe4a5u, 0xe8b8d433u, 0x7807c9a2u, 0x0f00f934u, 0x9609a88eu, 0xe10e9818u, 0x7f6a0dbbu,
|
||||
0x086d3d2du, 0x91646c97u, 0xe6635c01u, 0x6b6b51f4u, 0x1c6c6162u, 0x856530d8u, 0xf262004eu,
|
||||
0x6c0695edu, 0x1b01a57bu, 0x8208f4c1u, 0xf50fc457u, 0x65b0d9c6u, 0x12b7e950u, 0x8bbeb8eau,
|
||||
0xfcb9887cu, 0x62dd1ddfu, 0x15da2d49u, 0x8cd37cf3u, 0xfbd44c65u, 0x4db26158u, 0x3ab551ceu,
|
||||
0xa3bc0074u, 0xd4bb30e2u, 0x4adfa541u, 0x3dd895d7u, 0xa4d1c46du, 0xd3d6f4fbu, 0x4369e96au,
|
||||
0x346ed9fcu, 0xad678846u, 0xda60b8d0u, 0x44042d73u, 0x33031de5u, 0xaa0a4c5fu, 0xdd0d7cc9u,
|
||||
0x5005713cu, 0x270241aau, 0xbe0b1010u, 0xc90c2086u, 0x5768b525u, 0x206f85b3u, 0xb966d409u,
|
||||
0xce61e49fu, 0x5edef90eu, 0x29d9c998u, 0xb0d09822u, 0xc7d7a8b4u, 0x59b33d17u, 0x2eb40d81u,
|
||||
0xb7bd5c3bu, 0xc0ba6cadu, 0xedb88320u, 0x9abfb3b6u, 0x03b6e20cu, 0x74b1d29au, 0xead54739u,
|
||||
0x9dd277afu, 0x04db2615u, 0x73dc1683u, 0xe3630b12u, 0x94643b84u, 0x0d6d6a3eu, 0x7a6a5aa8u,
|
||||
0xe40ecf0bu, 0x9309ff9du, 0x0a00ae27u, 0x7d079eb1u, 0xf00f9344u, 0x8708a3d2u, 0x1e01f268u,
|
||||
0x6906c2feu, 0xf762575du, 0x806567cbu, 0x196c3671u, 0x6e6b06e7u, 0xfed41b76u, 0x89d32be0u,
|
||||
0x10da7a5au, 0x67dd4accu, 0xf9b9df6fu, 0x8ebeeff9u, 0x17b7be43u, 0x60b08ed5u, 0xd6d6a3e8u,
|
||||
0xa1d1937eu, 0x38d8c2c4u, 0x4fdff252u, 0xd1bb67f1u, 0xa6bc5767u, 0x3fb506ddu, 0x48b2364bu,
|
||||
0xd80d2bdau, 0xaf0a1b4cu, 0x36034af6u, 0x41047a60u, 0xdf60efc3u, 0xa867df55u, 0x316e8eefu,
|
||||
0x4669be79u, 0xcb61b38cu, 0xbc66831au, 0x256fd2a0u, 0x5268e236u, 0xcc0c7795u, 0xbb0b4703u,
|
||||
0x220216b9u, 0x5505262fu, 0xc5ba3bbeu, 0xb2bd0b28u, 0x2bb45a92u, 0x5cb36a04u, 0xc2d7ffa7u,
|
||||
0xb5d0cf31u, 0x2cd99e8bu, 0x5bdeae1du, 0x9b64c2b0u, 0xec63f226u, 0x756aa39cu, 0x026d930au,
|
||||
0x9c0906a9u, 0xeb0e363fu, 0x72076785u, 0x05005713u, 0x95bf4a82u, 0xe2b87a14u, 0x7bb12baeu,
|
||||
0x0cb61b38u, 0x92d28e9bu, 0xe5d5be0du, 0x7cdcefb7u, 0x0bdbdf21u, 0x86d3d2d4u, 0xf1d4e242u,
|
||||
0x68ddb3f8u, 0x1fda836eu, 0x81be16cdu, 0xf6b9265bu, 0x6fb077e1u, 0x18b74777u, 0x88085ae6u,
|
||||
0xff0f6a70u, 0x66063bcau, 0x11010b5cu, 0x8f659effu, 0xf862ae69u, 0x616bffd3u, 0x166ccf45u,
|
||||
0xa00ae278u, 0xd70dd2eeu, 0x4e048354u, 0x3903b3c2u, 0xa7672661u, 0xd06016f7u, 0x4969474du,
|
||||
0x3e6e77dbu, 0xaed16a4au, 0xd9d65adcu, 0x40df0b66u, 0x37d83bf0u, 0xa9bcae53u, 0xdebb9ec5u,
|
||||
0x47b2cf7fu, 0x30b5ffe9u, 0xbdbdf21cu, 0xcabac28au, 0x53b39330u, 0x24b4a3a6u, 0xbad03605u,
|
||||
0xcdd70693u, 0x54de5729u, 0x23d967bfu, 0xb3667a2eu, 0xc4614ab8u, 0x5d681b02u, 0x2a6f2b94u,
|
||||
0xb40bbe37u, 0xc30c8ea1u, 0x5a05df1bu, 0x2d02ef8du,
|
||||
};
|
||||
|
||||
uint32_t x52_crc32_init(void)
|
||||
{
|
||||
return X52_CRC32_INIT;
|
||||
}
|
||||
|
||||
uint32_t x52_crc32_update(uint32_t crc, const void *data, size_t len)
|
||||
{
|
||||
const unsigned char *p = data;
|
||||
|
||||
if (len == 0) {
|
||||
return crc;
|
||||
}
|
||||
|
||||
crc = ~crc;
|
||||
|
||||
while (len != 0) {
|
||||
crc = x52_crc32_table[(crc ^ *p++) & 0xffu] ^ (crc >> 8);
|
||||
len--;
|
||||
}
|
||||
|
||||
return ~crc;
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - CRC-32 (zlib / Python zlib.crc32 compatible)
|
||||
*
|
||||
* Copyright (C) 2026 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file crc32.h
|
||||
* @brief IEEE/ZIP CRC-32 matching @c zlib.crc32 / Python @c zlib.crc32 (polynomial 0xEDB88320).
|
||||
*
|
||||
* Incremental use: @code
|
||||
* uint32_t crc = x52_crc32_init();
|
||||
* crc = x52_crc32_update(crc, chunk0, len0);
|
||||
* crc = x52_crc32_update(crc, chunk1, len1);
|
||||
* // crc is final value (unsigned 32-bit, same encoding as Python after & 0xFFFFFFFF)
|
||||
* @endcode
|
||||
*/
|
||||
#ifndef X52D_CRC32_H
|
||||
#define X52D_CRC32_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Initial accumulator value (same as first argument to zlib @c crc32 for a new stream). */
|
||||
#define X52_CRC32_INIT 0u
|
||||
|
||||
/** Start a new CRC computation. */
|
||||
uint32_t x52_crc32_init(void);
|
||||
|
||||
/**
|
||||
* Feed zero or more bytes into a running CRC.
|
||||
*
|
||||
* @param crc Value from @ref x52_crc32_init or a prior @ref x52_crc32_update
|
||||
* @param data Input bytes; must be non-NULL if @p len is non-zero
|
||||
* @param len Number of bytes
|
||||
* @return Updated CRC-32 (same as Python <tt>zlib.crc32(data, crc) & 0xFFFFFFFF</tt>
|
||||
* when @p data is the new chunk only)
|
||||
*/
|
||||
uint32_t x52_crc32_update(uint32_t crc, const void *data, size_t len);
|
||||
|
||||
/** Alias for @ref x52_crc32_update (zlib-style name). */
|
||||
static inline uint32_t x52_crc32(uint32_t crc, const void *data, size_t len)
|
||||
{
|
||||
return x52_crc32_update(crc, data, len);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* X52D_CRC32_H */
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - CRC-32 unit tests
|
||||
*
|
||||
* Copyright (C) 2026 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include "build-config.h"
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdarg.h>
|
||||
#include <setjmp.h>
|
||||
#include <cmocka.h>
|
||||
|
||||
#include <daemon/crc32.h>
|
||||
|
||||
/* Golden values: Python zlib.crc32(data) & 0xFFFFFFFF */
|
||||
|
||||
static void test_init_zero(void **state)
|
||||
{
|
||||
(void)state;
|
||||
assert_int_equal((int)x52_crc32_init(), 0);
|
||||
assert_int_equal((int)X52_CRC32_INIT, 0);
|
||||
}
|
||||
|
||||
static void test_empty(void **state)
|
||||
{
|
||||
(void)state;
|
||||
uint32_t crc = x52_crc32_init();
|
||||
crc = x52_crc32_update(crc, NULL, 0);
|
||||
assert_true(crc == 0u);
|
||||
crc = x52_crc32_update(crc, "", 0);
|
||||
assert_true(crc == 0u);
|
||||
}
|
||||
|
||||
static void test_len0_preserves(void **state)
|
||||
{
|
||||
(void)state;
|
||||
const char *s = "abc";
|
||||
uint32_t crc = x52_crc32_update(0, s, 3);
|
||||
uint32_t again = x52_crc32_update(crc, s, 0);
|
||||
assert_true(again == crc);
|
||||
}
|
||||
|
||||
static void test_string_123456789(void **state)
|
||||
{
|
||||
(void)state;
|
||||
static const char s[] = "123456789";
|
||||
uint32_t crc = x52_crc32_update(0, s, sizeof(s) - 1u);
|
||||
assert_true(crc == 0xcbf43926u);
|
||||
}
|
||||
|
||||
static void test_bytes_0_to_255(void **state)
|
||||
{
|
||||
(void)state;
|
||||
unsigned char buf[256];
|
||||
for (unsigned i = 0; i < 256; i++) {
|
||||
buf[i] = (unsigned char)i;
|
||||
}
|
||||
uint32_t crc = x52_crc32_update(0, buf, sizeof(buf));
|
||||
assert_true(crc == 0x29058c73u);
|
||||
}
|
||||
|
||||
static void test_incremental_matches_one_shot(void **state)
|
||||
{
|
||||
(void)state;
|
||||
static const char s[] = "123456789";
|
||||
uint32_t a = x52_crc32_update(0, s, sizeof(s) - 1u);
|
||||
|
||||
uint32_t b = x52_crc32_init();
|
||||
b = x52_crc32_update(b, s, 3);
|
||||
b = x52_crc32_update(b, s + 3, 3);
|
||||
b = x52_crc32_update(b, s + 6, 3);
|
||||
|
||||
assert_true(b == a);
|
||||
}
|
||||
|
||||
static void test_chaining_second_segment(void **state)
|
||||
{
|
||||
(void)state;
|
||||
static const char h[] = "hello";
|
||||
static const char w[] = "world";
|
||||
static const char hw[] = "helloworld";
|
||||
|
||||
uint32_t c = x52_crc32_update(0, h, sizeof(h) - 1u);
|
||||
c = x52_crc32_update(c, w, sizeof(w) - 1u);
|
||||
|
||||
uint32_t whole = x52_crc32_update(0, hw, sizeof(hw) - 1u);
|
||||
assert_true(c == whole);
|
||||
assert_true(c == 0xf9eb20adu);
|
||||
}
|
||||
|
||||
static void test_one_byte_at_a_time(void **state)
|
||||
{
|
||||
(void)state;
|
||||
static const char s[] = "123456789";
|
||||
uint32_t expect = x52_crc32_update(0, s, sizeof(s) - 1u);
|
||||
uint32_t crc = x52_crc32_init();
|
||||
for (size_t i = 0; i < sizeof(s) - 1u; i++) {
|
||||
crc = x52_crc32_update(crc, s + i, 1);
|
||||
}
|
||||
assert_true(crc == expect);
|
||||
}
|
||||
|
||||
static void test_x52_crc32_alias(void **state)
|
||||
{
|
||||
(void)state;
|
||||
static const char s[] = "123456789";
|
||||
uint32_t u = x52_crc32_update(0, s, sizeof(s) - 1u);
|
||||
uint32_t v = x52_crc32(0, s, sizeof(s) - 1u);
|
||||
assert_true(u == v);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
const struct CMUnitTest tests[] = {
|
||||
cmocka_unit_test(test_init_zero),
|
||||
cmocka_unit_test(test_empty),
|
||||
cmocka_unit_test(test_len0_preserves),
|
||||
cmocka_unit_test(test_string_123456789),
|
||||
cmocka_unit_test(test_bytes_0_to_255),
|
||||
cmocka_unit_test(test_incremental_matches_one_shot),
|
||||
cmocka_unit_test(test_chaining_second_segment),
|
||||
cmocka_unit_test(test_one_byte_at_a_time),
|
||||
cmocka_unit_test(test_x52_crc32_alias),
|
||||
};
|
||||
|
||||
cmocka_set_message_output(CM_OUTPUT_TAP);
|
||||
return cmocka_run_group_tests(tests, NULL, NULL);
|
||||
}
|
||||
|
|
@ -40,7 +40,7 @@ string "quit", or terminates input by using Ctrl+D.
|
|||
default socket.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "build-config.h"
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
|
|
@ -48,8 +48,8 @@ string "quit", or terminates input by using Ctrl+D.
|
|||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "x52d_const.h"
|
||||
#include "x52dcomm.h"
|
||||
#include <daemon/constants.h>
|
||||
#include <libx52/x52dcomm.h>
|
||||
|
||||
#define APP_NAME "x52ctl"
|
||||
#if HAVE_FUNC_ATTRIBUTE_NORETURN
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -6,17 +6,17 @@
|
|||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "build-config.h"
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define PINELOG_MODULE X52D_MOD_DEVICE
|
||||
#include "x52d_const.h"
|
||||
#include "x52d_config.h"
|
||||
#include "x52d_device.h"
|
||||
#include "x52d_notify.h"
|
||||
#include "libx52.h"
|
||||
#include <daemon/constants.h>
|
||||
#include <daemon/config.h>
|
||||
#include <daemon/device.h>
|
||||
#include <daemon/notify.h>
|
||||
#include <libx52/libx52.h>
|
||||
#include "pinelog.h"
|
||||
|
||||
static libx52_device *x52_dev;
|
||||
|
|
@ -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
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
#ifndef X52D_DEVICE_H
|
||||
#define X52D_DEVICE_H
|
||||
|
||||
#include "libx52.h"
|
||||
#include <libx52/libx52.h>
|
||||
|
||||
void x52d_dev_init(void);
|
||||
void x52d_dev_exit(void);
|
||||
|
|
@ -6,16 +6,16 @@
|
|||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "build-config.h"
|
||||
#include <stdbool.h>
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include "x52d_const.h"
|
||||
#include "x52d_config.h"
|
||||
#include "x52d_io.h"
|
||||
#include "x52d_mouse.h"
|
||||
#include "libx52io.h"
|
||||
#include <daemon/constants.h>
|
||||
#include <daemon/config.h>
|
||||
#include <daemon/io.h>
|
||||
#include <daemon/mouse.h>
|
||||
#include <libx52/libx52io.h>
|
||||
|
||||
#define PINELOG_MODULE X52D_MOD_IO
|
||||
#include "pinelog.h"
|
||||
|
|
@ -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 */
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - keyboard layout from config
|
||||
*
|
||||
* Copyright (C) 2026 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include "build-config.h"
|
||||
|
||||
#define PINELOG_MODULE X52D_MOD_KEYBOARD_LAYOUT
|
||||
#include "pinelog.h"
|
||||
#include <daemon/constants.h>
|
||||
#include <daemon/keyboard_layout.h>
|
||||
#include <daemon/layout_format.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static x52_layout *active_layout;
|
||||
|
||||
/**
|
||||
* Normal install: @c $DATADIR/x52d/<basename>.x52l
|
||||
*
|
||||
* If @c X52D_LAYOUT_DIR is set (non-empty), load @c $X52D_LAYOUT_DIR/<basename>.x52l instead so
|
||||
* uninstalled tests can point at the Meson build directory.
|
||||
*/
|
||||
static int load_layout_for_basename(const char *basename, x52_layout **out)
|
||||
{
|
||||
const char *layout_dir = getenv("X52D_LAYOUT_DIR");
|
||||
|
||||
if (layout_dir != NULL && layout_dir[0] != '\0') {
|
||||
char path[PATH_MAX];
|
||||
int n = snprintf(path, sizeof path, "%s/%s.x52l", layout_dir, basename);
|
||||
if (n < 0) {
|
||||
return EIO;
|
||||
}
|
||||
if ((size_t)n >= sizeof path) {
|
||||
return ENAMETOOLONG;
|
||||
}
|
||||
return x52_layout_load_path(path, out);
|
||||
}
|
||||
|
||||
return x52_layout_load_datadir(DATADIR, basename, out);
|
||||
}
|
||||
|
||||
const x52_layout *x52d_keyboard_layout_get(void)
|
||||
{
|
||||
return active_layout;
|
||||
}
|
||||
|
||||
void x52d_keyboard_layout_fini(void)
|
||||
{
|
||||
x52_layout_free(active_layout);
|
||||
active_layout = NULL;
|
||||
}
|
||||
|
||||
void x52d_keyboard_layout_reload(char *profile_keyboard_layout_value)
|
||||
{
|
||||
char basename[256];
|
||||
bool rejected = false;
|
||||
|
||||
x52_layout_normalize_keyboard_basename(profile_keyboard_layout_value, basename, sizeof basename, &rejected);
|
||||
if (rejected) {
|
||||
PINELOG_WARN(_("Invalid Profiles.KeyboardLayout value; using default layout basename 'us'"));
|
||||
}
|
||||
|
||||
x52_layout *new_layout = NULL;
|
||||
int err = load_layout_for_basename(basename, &new_layout);
|
||||
|
||||
if (err != 0 && strcmp(basename, "us") != 0) {
|
||||
PINELOG_WARN(
|
||||
_("Keyboard layout '%s' could not be loaded (%s); loading default 'us'"),
|
||||
basename, strerror(err > 0 ? err : EIO));
|
||||
err = load_layout_for_basename("us", &new_layout);
|
||||
}
|
||||
|
||||
if (err != 0) {
|
||||
PINELOG_FATAL(_("Could not load keyboard layout from %s/x52d (%s)"), DATADIR,
|
||||
strerror(err > 0 ? err : EIO));
|
||||
}
|
||||
|
||||
x52_layout *old_layout = active_layout;
|
||||
active_layout = new_layout;
|
||||
x52_layout_free(old_layout);
|
||||
|
||||
const char *desc = x52_layout_description(active_layout);
|
||||
PINELOG_INFO(_("Keyboard layout ready: %s (%s)"), x52_layout_name(active_layout),
|
||||
desc[0] != '\0' ? desc : _("no description"));
|
||||
}
|
||||
|
||||
void x52d_cfg_set_Profiles_KeyboardLayout(char *param)
|
||||
{
|
||||
x52d_keyboard_layout_reload(param);
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - keyboard layout from config
|
||||
*
|
||||
* Copyright (C) 2026 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#ifndef X52D_KEYBOARD_LAYOUT_H
|
||||
#define X52D_KEYBOARD_LAYOUT_H
|
||||
|
||||
#include <daemon/layout_format.h>
|
||||
|
||||
/**
|
||||
* @brief Load or reload layout from @c profile_keyboard_layout (@c Profiles.KeyboardLayout).
|
||||
*
|
||||
* Resolves @c $datadir/x52d/<basename>.x52l with basename hardening; falls back to @c us once if the
|
||||
* requested file is missing. Exits the process if no layout can be loaded.
|
||||
*/
|
||||
void x52d_keyboard_layout_reload(char *profile_keyboard_layout_value);
|
||||
|
||||
/** Active layout after @ref x52d_keyboard_layout_reload, or @c NULL before the first apply. */
|
||||
const x52_layout *x52d_keyboard_layout_get(void);
|
||||
|
||||
void x52d_keyboard_layout_fini(void);
|
||||
|
||||
#endif /* X52D_KEYBOARD_LAYOUT_H */
|
||||
|
|
@ -0,0 +1,197 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - x52layout v1 binary format
|
||||
*
|
||||
* Copyright (C) 2026 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file layout_format.h
|
||||
* @brief On-disk keyboard layout (@c .x52l) v1: constants, documentation, load and lookup API.
|
||||
*
|
||||
* The file is a dense index: entry @c entries[c] maps Unicode scalar @c c when
|
||||
* @c 0 <= c < codepoint_limit. For @c c >= codepoint_limit there is no mapping.
|
||||
*
|
||||
* **Header (128 bytes, all multi-byte integers big-endian / network order):**
|
||||
* - **0..3:** magic @c 'X' @c '5' @c '2' @c 'L'
|
||||
* - **4..5:** @c version (must be @ref X52_LAYOUT_FORMAT_VERSION)
|
||||
* - **6..7:** @c flags (v1: only @ref X52_LAYOUT_FLAG_NAME_TRUNCATED and/or
|
||||
* @ref X52_LAYOUT_FLAG_DESCRIPTION_TRUNCATED; other bits are reserved and must be zero)
|
||||
* - **8..11:** @c codepoint_limit — exclusive end of range; number of two-byte rows in @c entries
|
||||
* - **12..15:** @c checksum — CRC-32 (ZIP/IEEE, same as Python @c zlib.crc32) over the full
|
||||
* file with bytes 12..15 taken as zero when computing the digest
|
||||
* - **16..47:** @c layout_name (required: at least one character before @c NUL; remainder zero)
|
||||
* - **48..111:** @c description (optional, NUL-terminated, remainder zero)
|
||||
* - **112..127:** reserved (ignored on read in v1)
|
||||
*
|
||||
* **128+:** @c entries[] — pairs @c (modifiers, usage_key) for HID page 0x07; @c (0, 0) is empty.
|
||||
*
|
||||
* **File size:** exactly @c 128 + 2 * @c codepoint_limit bytes.
|
||||
*/
|
||||
#ifndef X52D_LAYOUT_FORMAT_H
|
||||
#define X52D_LAYOUT_FORMAT_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Four-byte magic at offset 0 (not NUL-terminated in the file). */
|
||||
#define X52_LAYOUT_MAGIC_0 'X'
|
||||
#define X52_LAYOUT_MAGIC_1 '5'
|
||||
#define X52_LAYOUT_MAGIC_2 '2'
|
||||
#define X52_LAYOUT_MAGIC_3 'L'
|
||||
|
||||
/** Total header size and byte offset of the @c entries table. */
|
||||
#define X52_LAYOUT_HEADER_BYTES 128u
|
||||
|
||||
#define X52_LAYOUT_FORMAT_VERSION 1u
|
||||
|
||||
/** Inclusive maximum for @c codepoint_limit - 1 (Unicode scalar space + sentinel ceiling). */
|
||||
#define X52_LAYOUT_CODEPOINT_LIMIT_MAX 0x110000u
|
||||
|
||||
/** @c layout_name field size in the header (offset 16). */
|
||||
#define X52_LAYOUT_NAME_FIELD_BYTES 32u
|
||||
|
||||
/** @c description field size in the header (offset 48). */
|
||||
#define X52_LAYOUT_DESCRIPTION_FIELD_BYTES 64u
|
||||
|
||||
/**
|
||||
* v1 header @c flags (big-endian on disk). @ref x52_layout_flags returns the host-endian value.
|
||||
*/
|
||||
#define X52_LAYOUT_FLAG_NAME_TRUNCATED 1u
|
||||
#define X52_LAYOUT_FLAG_DESCRIPTION_TRUNCATED 2u
|
||||
|
||||
/** Bitmask of flags defined for v1; other bits must be zero. */
|
||||
#define X52_LAYOUT_FLAGS_KNOWN (X52_LAYOUT_FLAG_NAME_TRUNCATED | X52_LAYOUT_FLAG_DESCRIPTION_TRUNCATED)
|
||||
|
||||
/** Loaded layout snapshot (opaque): full file copy, validated at load time. */
|
||||
typedef struct x52_layout x52_layout;
|
||||
|
||||
/**
|
||||
* @brief Read a layout file into a malloc'd snapshot and validate it (no @c mmap).
|
||||
*
|
||||
* @param path Path to the @c .x52l file; must not be @c NULL (otherwise @c EINVAL)
|
||||
* @param out On success, receives a new @ref x52_layout; must not be @c NULL; caller must @ref x52_layout_free
|
||||
*
|
||||
* @returns 0 on success, or a positive @c errno value (@c EINVAL, @c ENOMEM, @c EIO, @c ENOENT, …)
|
||||
*/
|
||||
int x52_layout_load_path(const char *path, x52_layout **out);
|
||||
|
||||
/**
|
||||
* @brief Turn @c Profiles.KeyboardLayout INI value into a safe layout basename.
|
||||
*
|
||||
* Empty or @c NULL yields @c "us" with @p rejected_out @c false. Values containing @c '/' ,
|
||||
* @c '\\' , @c ".." , disallowed characters, or oversize strings are rejected: @p out becomes
|
||||
* @c "us" and @p rejected_out is @c true (caller should log once). Requires @p out_sz @c >= 3.
|
||||
*
|
||||
* Allowed characters: ASCII alphanumeric, @c '_' , @c '-'.
|
||||
*/
|
||||
void x52_layout_normalize_keyboard_basename(const char *cfg_value, char *out, size_t out_sz, bool *rejected_out);
|
||||
|
||||
/**
|
||||
* @brief Build @c <datadir>/x52d/<basename>.x52l into @p path.
|
||||
*
|
||||
* @returns 0 on success, or @c ENAMETOOLONG if the path does not fit
|
||||
*/
|
||||
int x52_layout_join_file_path(char *path, size_t path_sz, const char *datadir, const char *basename);
|
||||
|
||||
/**
|
||||
* @brief Load @c join(datadir, "x52d", basename + ".x52l") after the same validation as @ref x52_layout_load_path.
|
||||
*
|
||||
* @returns 0 on success, or a positive @c errno (e.g. @c ENOENT, @c EINVAL, @c ENAMETOOLONG)
|
||||
*/
|
||||
int x52_layout_load_datadir(const char *datadir, const char *basename, x52_layout **out);
|
||||
|
||||
/**
|
||||
* @brief Copy @p data into an owned buffer and validate it.
|
||||
*
|
||||
* Same validation rules as @ref x52_layout_load_path (magic, version, flags, size, CRC-32, entries,
|
||||
* non-empty @c layout_name, etc.).
|
||||
*
|
||||
* @param data Layout file bytes; may be @c NULL only if @p len is zero (otherwise @c EINVAL)
|
||||
* @param len Number of bytes in @p data
|
||||
* @param out On success, receives a new @ref x52_layout; must not be @c NULL; caller must @ref x52_layout_free
|
||||
*
|
||||
* @returns 0 on success, or a positive @c errno value (@c EINVAL, @c ENOMEM, …)
|
||||
*/
|
||||
int x52_layout_load_memory(const void *data, size_t len, x52_layout **out);
|
||||
|
||||
/**
|
||||
* @brief Release a layout loaded by @ref x52_layout_load_path or @ref x52_layout_load_memory.
|
||||
*
|
||||
* @param layout Layout to free; @c NULL is a no-op
|
||||
*/
|
||||
void x52_layout_free(x52_layout *layout);
|
||||
|
||||
/**
|
||||
* @brief Exclusive end of the Unicode scalar range covered by @c entries (same as on-disk @c codepoint_limit).
|
||||
*
|
||||
* Lookups for @c code_point >= this value are not in the table.
|
||||
*
|
||||
* @param layout Loaded layout, or @c NULL
|
||||
*
|
||||
* @returns The limit value, or @c 0 if @p layout is @c NULL
|
||||
*/
|
||||
uint32_t x52_layout_codepoint_limit(const x52_layout *layout);
|
||||
|
||||
/**
|
||||
* @brief Host-endian copy of the on-disk @c flags field at header offset 6.
|
||||
*
|
||||
* Only bits in @ref X52_LAYOUT_FLAGS_KNOWN may be set in valid files; the loader rejects unknown bits.
|
||||
*
|
||||
* @param layout Loaded layout, or @c NULL
|
||||
*
|
||||
* @returns Flag word, or @c 0 if @p layout is @c NULL
|
||||
*/
|
||||
uint16_t x52_layout_flags(const x52_layout *layout);
|
||||
|
||||
/**
|
||||
* @brief Layout name from the header @c layout_name field.
|
||||
*
|
||||
* If @ref X52_LAYOUT_FLAG_NAME_TRUNCATED is set, the returned string is the on-disk name plus
|
||||
* @c "<truncated>". The pointer remains valid until @ref x52_layout_free; do not modify the string.
|
||||
*
|
||||
* @param layout Loaded layout, or @c NULL
|
||||
*
|
||||
* @returns Read-only NUL-terminated UTF-8 string; empty string if @p layout is @c NULL
|
||||
*/
|
||||
const char *x52_layout_name(const x52_layout *layout);
|
||||
|
||||
/**
|
||||
* @brief Optional description from the header @c description field.
|
||||
*
|
||||
* If @ref X52_LAYOUT_FLAG_DESCRIPTION_TRUNCATED is set, the returned string is the on-disk text plus
|
||||
* @c "<truncated>". The pointer remains valid until @ref x52_layout_free; do not modify the string.
|
||||
*
|
||||
* @param layout Loaded layout, or @c NULL
|
||||
*
|
||||
* @returns Read-only NUL-terminated UTF-8 string, or empty string if @p layout is @c NULL or the field is empty
|
||||
*/
|
||||
const char *x52_layout_description(const x52_layout *layout);
|
||||
|
||||
/**
|
||||
* @brief O(1) lookup of the HID chord for Unicode scalar @p code_point.
|
||||
*
|
||||
* Returns @c false when @p code_point is out of range, when the slot is empty @c (modifiers, usage_key) = (0, 0),
|
||||
* or when any argument is invalid. On @c false, @p modifiers_out and @p usage_key_out are left unchanged.
|
||||
*
|
||||
* @param layout Loaded layout; if @c NULL, returns @c false
|
||||
* @param code_point Unicode scalar value
|
||||
* @param[out] modifiers_out HID keyboard modifier byte (@ref vkm_key_modifiers bits); if @c NULL, returns @c false
|
||||
* @param[out] usage_key_out HID usage (page 0x07); if @c NULL, returns @c false
|
||||
*
|
||||
* @returns @c true if a non-empty mapping exists and was written to @p modifiers_out and @p usage_key_out
|
||||
*/
|
||||
bool x52_layout_lookup(const x52_layout *layout, uint32_t code_point, uint8_t *modifiers_out,
|
||||
uint8_t *usage_key_out);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* X52D_LAYOUT_FORMAT_H */
|
||||
|
|
@ -0,0 +1,392 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - x52layout v1 loader
|
||||
*
|
||||
* Copyright (C) 2026 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include "build-config.h"
|
||||
|
||||
#include <daemon/crc32.h>
|
||||
#include <daemon/layout_format.h>
|
||||
#include <daemon/layout_usage_allowlist.h>
|
||||
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
struct x52_layout {
|
||||
uint8_t *data;
|
||||
size_t size;
|
||||
uint32_t codepoint_limit;
|
||||
uint16_t flags;
|
||||
char *name;
|
||||
char *description;
|
||||
};
|
||||
|
||||
static size_t meta_field_len(const uint8_t *field, size_t nbytes)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < nbytes; i++) {
|
||||
if (field[i] == '\0') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
static char *copy_meta_string(const uint8_t *field, size_t nbytes, bool add_trunc_suffix)
|
||||
{
|
||||
static const char suf[] = "<truncated>";
|
||||
const size_t suf_len = sizeof(suf) - 1u;
|
||||
size_t base_len = meta_field_len(field, nbytes);
|
||||
size_t total = base_len + (add_trunc_suffix ? suf_len : 0);
|
||||
char *s = (char *)malloc(total + 1u);
|
||||
|
||||
if (s == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
if (base_len != 0) {
|
||||
memcpy(s, field, base_len);
|
||||
}
|
||||
if (add_trunc_suffix) {
|
||||
memcpy(s + base_len, suf, suf_len);
|
||||
}
|
||||
s[total] = '\0';
|
||||
return s;
|
||||
}
|
||||
|
||||
static uint16_t read_be16(const uint8_t *p)
|
||||
{
|
||||
return (uint16_t)((uint16_t)p[0] << 8 | (uint16_t)p[1]);
|
||||
}
|
||||
|
||||
static uint32_t read_be32(const uint8_t *p)
|
||||
{
|
||||
return (uint32_t)p[0] << 24 | (uint32_t)p[1] << 16 | (uint32_t)p[2] << 8 | (uint32_t)p[3];
|
||||
}
|
||||
|
||||
static uint32_t x52_layout_file_crc(const uint8_t *buf, size_t len)
|
||||
{
|
||||
uint32_t crc = x52_crc32_init();
|
||||
crc = x52_crc32_update(crc, buf, 12u);
|
||||
static const uint8_t zero_chk[4] = {0, 0, 0, 0};
|
||||
crc = x52_crc32_update(crc, zero_chk, 4u);
|
||||
if (len > 16u) {
|
||||
crc = x52_crc32_update(crc, buf + 16u, len - 16u);
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
static int validate_entries(const uint8_t *buf, uint32_t codepoint_limit)
|
||||
{
|
||||
const uint8_t *base = buf + X52_LAYOUT_HEADER_BYTES;
|
||||
|
||||
for (uint32_t i = 0; i < codepoint_limit; i++) {
|
||||
uint8_t usage = base[2u * (size_t)i + 1u];
|
||||
if (usage == 0) {
|
||||
continue;
|
||||
}
|
||||
if (!x52_layout_usage_key_allowed(usage)) {
|
||||
return EINVAL;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int layout_validate_and_adopt(uint8_t *buf, size_t len, x52_layout **out)
|
||||
{
|
||||
if (out == NULL) {
|
||||
free(buf);
|
||||
return EINVAL;
|
||||
}
|
||||
*out = NULL;
|
||||
|
||||
if (len < X52_LAYOUT_HEADER_BYTES) {
|
||||
free(buf);
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
if (memcmp(buf, "X52L", 4) != 0) {
|
||||
free(buf);
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
uint16_t version = read_be16(buf + 4);
|
||||
uint16_t flags = read_be16(buf + 6);
|
||||
uint32_t codepoint_limit = read_be32(buf + 8);
|
||||
uint32_t stored_crc = read_be32(buf + 12);
|
||||
|
||||
if (version != X52_LAYOUT_FORMAT_VERSION) {
|
||||
free(buf);
|
||||
return EINVAL;
|
||||
}
|
||||
if ((flags & (uint16_t)~X52_LAYOUT_FLAGS_KNOWN) != 0) {
|
||||
free(buf);
|
||||
return EINVAL;
|
||||
}
|
||||
if (codepoint_limit > X52_LAYOUT_CODEPOINT_LIMIT_MAX) {
|
||||
free(buf);
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
size_t body = (size_t)codepoint_limit * 2u;
|
||||
size_t expected = X52_LAYOUT_HEADER_BYTES + body;
|
||||
if (expected < X52_LAYOUT_HEADER_BYTES || len != expected) {
|
||||
free(buf);
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
if (meta_field_len(buf + 16, X52_LAYOUT_NAME_FIELD_BYTES) == 0) {
|
||||
free(buf);
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
uint32_t calc = x52_layout_file_crc(buf, len);
|
||||
if (calc != stored_crc) {
|
||||
free(buf);
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
int en = validate_entries(buf, codepoint_limit);
|
||||
if (en != 0) {
|
||||
free(buf);
|
||||
return en;
|
||||
}
|
||||
|
||||
x52_layout *L = (x52_layout *)malloc(sizeof *L);
|
||||
if (L == NULL) {
|
||||
free(buf);
|
||||
return ENOMEM;
|
||||
}
|
||||
L->data = buf;
|
||||
L->size = len;
|
||||
L->codepoint_limit = codepoint_limit;
|
||||
L->flags = flags;
|
||||
L->name = copy_meta_string(buf + 16, X52_LAYOUT_NAME_FIELD_BYTES,
|
||||
(flags & X52_LAYOUT_FLAG_NAME_TRUNCATED) != 0);
|
||||
if (L->name == NULL) {
|
||||
free(L);
|
||||
free(buf);
|
||||
return ENOMEM;
|
||||
}
|
||||
L->description = copy_meta_string(buf + 48, X52_LAYOUT_DESCRIPTION_FIELD_BYTES,
|
||||
(flags & X52_LAYOUT_FLAG_DESCRIPTION_TRUNCATED) != 0);
|
||||
if (L->description == NULL) {
|
||||
free(L->name);
|
||||
free(L);
|
||||
free(buf);
|
||||
return ENOMEM;
|
||||
}
|
||||
*out = L;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void x52_layout_normalize_keyboard_basename(const char *cfg_value, char *out, size_t out_sz, bool *rejected_out)
|
||||
{
|
||||
*rejected_out = false;
|
||||
if (out_sz < 3) {
|
||||
if (out_sz > 0) {
|
||||
out[0] = '\0';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (cfg_value == NULL || cfg_value[0] == '\0') {
|
||||
out[0] = 'u';
|
||||
out[1] = 's';
|
||||
out[2] = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
if (strchr(cfg_value, '/') != NULL || strchr(cfg_value, '\\') != NULL ||
|
||||
strstr(cfg_value, "..") != NULL) {
|
||||
goto failed_normalization;
|
||||
}
|
||||
|
||||
size_t len = strlen(cfg_value);
|
||||
if (len >= out_sz) {
|
||||
goto failed_normalization;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
unsigned char c = (unsigned char)cfg_value[i];
|
||||
if (!isalnum((int)c) && c != '_' && c != '-') {
|
||||
goto failed_normalization;
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(out, cfg_value, len + 1u);
|
||||
return;
|
||||
|
||||
failed_normalization:
|
||||
*rejected_out = true;
|
||||
out[0] = 'u';
|
||||
out[1] = 's';
|
||||
out[2] = '\0';
|
||||
}
|
||||
|
||||
int x52_layout_join_file_path(char *path, size_t path_sz, const char *datadir, const char *basename)
|
||||
{
|
||||
if (path == NULL || datadir == NULL || basename == NULL || path_sz == 0) {
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
int n = snprintf(path, path_sz, "%s/x52d/%s.x52l", datadir, basename);
|
||||
if (n < 0) {
|
||||
return EIO;
|
||||
}
|
||||
if ((size_t)n >= path_sz) {
|
||||
return ENAMETOOLONG;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int x52_layout_load_datadir(const char *datadir, const char *basename, x52_layout **out)
|
||||
{
|
||||
char path[PATH_MAX];
|
||||
|
||||
int rc = x52_layout_join_file_path(path, sizeof path, datadir, basename);
|
||||
if (rc != 0) {
|
||||
return rc;
|
||||
}
|
||||
return x52_layout_load_path(path, out);
|
||||
}
|
||||
|
||||
int x52_layout_load_path(const char *path, x52_layout **out)
|
||||
{
|
||||
if (path == NULL) {
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
FILE *fp = fopen(path, "rb");
|
||||
if (fp == NULL) {
|
||||
return errno != 0 ? errno : EIO;
|
||||
}
|
||||
|
||||
if (fseek(fp, 0, SEEK_END) != 0) {
|
||||
int e = errno != 0 ? errno : EIO;
|
||||
fclose(fp);
|
||||
return e;
|
||||
}
|
||||
|
||||
long pos = ftell(fp);
|
||||
if (pos < 0) {
|
||||
int e = errno != 0 ? errno : EIO;
|
||||
fclose(fp);
|
||||
return e;
|
||||
}
|
||||
if (fseek(fp, 0, SEEK_SET) != 0) {
|
||||
int e = errno != 0 ? errno : EIO;
|
||||
fclose(fp);
|
||||
return e;
|
||||
}
|
||||
|
||||
size_t len = (size_t)pos;
|
||||
if ((long)len != pos || pos < (long)X52_LAYOUT_HEADER_BYTES) {
|
||||
fclose(fp);
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
uint8_t *buf = (uint8_t *)malloc(len);
|
||||
if (buf == NULL) {
|
||||
fclose(fp);
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
size_t n = fread(buf, 1, len, fp);
|
||||
fclose(fp);
|
||||
if (n != len) {
|
||||
free(buf);
|
||||
return EIO;
|
||||
}
|
||||
|
||||
return layout_validate_and_adopt(buf, len, out);
|
||||
}
|
||||
|
||||
int x52_layout_load_memory(const void *data, size_t len, x52_layout **out)
|
||||
{
|
||||
if (data == NULL && len != 0) {
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
uint8_t *buf = (uint8_t *)malloc(len);
|
||||
if (buf == NULL) {
|
||||
return ENOMEM;
|
||||
}
|
||||
if (len != 0) {
|
||||
memcpy(buf, data, len);
|
||||
}
|
||||
return layout_validate_and_adopt(buf, len, out);
|
||||
}
|
||||
|
||||
void x52_layout_free(x52_layout *layout)
|
||||
{
|
||||
if (layout == NULL) {
|
||||
return;
|
||||
}
|
||||
free(layout->name);
|
||||
free(layout->description);
|
||||
free(layout->data);
|
||||
free(layout);
|
||||
}
|
||||
|
||||
uint32_t x52_layout_codepoint_limit(const x52_layout *layout)
|
||||
{
|
||||
if (layout == NULL) {
|
||||
return 0;
|
||||
}
|
||||
return layout->codepoint_limit;
|
||||
}
|
||||
|
||||
uint16_t x52_layout_flags(const x52_layout *layout)
|
||||
{
|
||||
if (layout == NULL) {
|
||||
return 0;
|
||||
}
|
||||
return layout->flags;
|
||||
}
|
||||
|
||||
const char *x52_layout_name(const x52_layout *layout)
|
||||
{
|
||||
if (layout == NULL || layout->name == NULL) {
|
||||
return "";
|
||||
}
|
||||
return layout->name;
|
||||
}
|
||||
|
||||
const char *x52_layout_description(const x52_layout *layout)
|
||||
{
|
||||
if (layout == NULL || layout->description == NULL) {
|
||||
return "";
|
||||
}
|
||||
return layout->description;
|
||||
}
|
||||
|
||||
bool x52_layout_lookup(const x52_layout *layout, uint32_t code_point, uint8_t *modifiers_out,
|
||||
uint8_t *usage_key_out)
|
||||
{
|
||||
if (layout == NULL || modifiers_out == NULL || usage_key_out == NULL) {
|
||||
return false;
|
||||
}
|
||||
if (code_point >= layout->codepoint_limit) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t off = X52_LAYOUT_HEADER_BYTES + 2u * (size_t)code_point;
|
||||
uint8_t mod = layout->data[off];
|
||||
uint8_t usage = layout->data[off + 1u];
|
||||
if (usage == 0) {
|
||||
return false;
|
||||
}
|
||||
*modifiers_out = mod;
|
||||
*usage_key_out = usage;
|
||||
return true;
|
||||
}
|
||||
|
|
@ -0,0 +1,463 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - x52layout loader tests
|
||||
*
|
||||
* Copyright (C) 2026 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include "build-config.h"
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <setjmp.h>
|
||||
#include <cmocka.h>
|
||||
|
||||
#include <daemon/crc32.h>
|
||||
#include <daemon/layout_format.h>
|
||||
#include <vkm/vkm.h>
|
||||
|
||||
static void write_be16(uint8_t *p, uint16_t v)
|
||||
{
|
||||
p[0] = (uint8_t)(v >> 8);
|
||||
p[1] = (uint8_t)v;
|
||||
}
|
||||
|
||||
static void write_be32(uint8_t *p, uint32_t v)
|
||||
{
|
||||
p[0] = (uint8_t)(v >> 24);
|
||||
p[1] = (uint8_t)(v >> 16);
|
||||
p[2] = (uint8_t)(v >> 8);
|
||||
p[3] = (uint8_t)v;
|
||||
}
|
||||
|
||||
static uint32_t read_be32(const uint8_t *p)
|
||||
{
|
||||
return (uint32_t)p[0] << 24 | (uint32_t)p[1] << 16 | (uint32_t)p[2] << 8 | (uint32_t)p[3];
|
||||
}
|
||||
|
||||
/** Patch checksum (offsets 12..15) after building the rest; @p len is full file size. */
|
||||
static void finalize_crc(uint8_t *buf, size_t len)
|
||||
{
|
||||
uint32_t crc = x52_crc32_init();
|
||||
crc = x52_crc32_update(crc, buf, 12u);
|
||||
static const uint8_t z[4] = {0, 0, 0, 0};
|
||||
crc = x52_crc32_update(crc, z, 4u);
|
||||
if (len > 16u) {
|
||||
crc = x52_crc32_update(crc, buf + 16u, len - 16u);
|
||||
}
|
||||
write_be32(buf + 12, crc);
|
||||
}
|
||||
|
||||
/** Python @c zlib.crc32 over @c minimal v1 layout (@c limit=1, name @c "x", empty entry). */
|
||||
#define X52_LAYOUT_TEST_MINIMAL_ZLIB_CRC32 0xc951bfaau
|
||||
|
||||
static void test_load_minimal_lookup(void **state)
|
||||
{
|
||||
(void)state;
|
||||
const uint32_t limit = 98u; /* enough for 'a' at 97 */
|
||||
size_t len = X52_LAYOUT_HEADER_BYTES + 2u * (size_t)limit;
|
||||
uint8_t *buf = (uint8_t *)calloc(1, len);
|
||||
assert_non_null(buf);
|
||||
|
||||
memcpy(buf, "X52L", 4);
|
||||
write_be16(buf + 4, X52_LAYOUT_FORMAT_VERSION);
|
||||
write_be16(buf + 6, 0);
|
||||
write_be32(buf + 8, limit);
|
||||
memcpy(buf + 16, "minimal", 8);
|
||||
/* chord for 'a': VKM_KEY_A */
|
||||
size_t off = X52_LAYOUT_HEADER_BYTES + 2u * 97u;
|
||||
buf[off] = 0x02; /* LSHIFT */
|
||||
buf[off + 1] = (uint8_t)VKM_KEY_A;
|
||||
finalize_crc(buf, len);
|
||||
|
||||
x52_layout *L = NULL;
|
||||
assert_int_equal(x52_layout_load_memory(buf, len, &L), 0);
|
||||
assert_non_null(L);
|
||||
assert_int_equal((int)x52_layout_codepoint_limit(L), (int)limit);
|
||||
|
||||
uint8_t m = 0xff;
|
||||
uint8_t u = 0xff;
|
||||
assert_true(x52_layout_lookup(L, 97u, &m, &u));
|
||||
assert_int_equal((int)m, 0x02);
|
||||
assert_int_equal((int)u, (int)VKM_KEY_A);
|
||||
|
||||
m = 0;
|
||||
u = 0;
|
||||
assert_false(x52_layout_lookup(L, 0u, &m, &u));
|
||||
assert_false(x52_layout_lookup(L, limit, &m, &u));
|
||||
|
||||
assert_int_equal((int)x52_layout_flags(L), 0);
|
||||
assert_string_equal(x52_layout_name(L), "minimal");
|
||||
assert_string_equal(x52_layout_description(L), "");
|
||||
|
||||
x52_layout_free(L);
|
||||
free(buf);
|
||||
}
|
||||
|
||||
static void test_reject_bad_checksum(void **state)
|
||||
{
|
||||
(void)state;
|
||||
const uint32_t limit = 1u;
|
||||
size_t len = X52_LAYOUT_HEADER_BYTES + 2u;
|
||||
uint8_t *buf = (uint8_t *)calloc(1, len);
|
||||
assert_non_null(buf);
|
||||
memcpy(buf, "X52L", 4);
|
||||
write_be16(buf + 4, X52_LAYOUT_FORMAT_VERSION);
|
||||
write_be16(buf + 6, 0);
|
||||
write_be32(buf + 8, limit);
|
||||
memcpy(buf + 16, "x", 2);
|
||||
write_be32(buf + 12, 0xdeadbeefu);
|
||||
|
||||
x52_layout *L = NULL;
|
||||
assert_int_equal(x52_layout_load_memory(buf, len, &L), EINVAL);
|
||||
assert_null(L);
|
||||
free(buf);
|
||||
}
|
||||
|
||||
static void test_layout_crc_matches_python_zlib_minimal(void **state)
|
||||
{
|
||||
(void)state;
|
||||
const uint32_t limit = 1u;
|
||||
size_t len = X52_LAYOUT_HEADER_BYTES + 2u;
|
||||
uint8_t *buf = (uint8_t *)calloc(1, len);
|
||||
assert_non_null(buf);
|
||||
memcpy(buf, "X52L", 4);
|
||||
write_be16(buf + 4, X52_LAYOUT_FORMAT_VERSION);
|
||||
write_be16(buf + 6, 0);
|
||||
write_be32(buf + 8, limit);
|
||||
memcpy(buf + 16, "x", 2);
|
||||
finalize_crc(buf, len);
|
||||
assert_int_equal((int)read_be32(buf + 12), (int)X52_LAYOUT_TEST_MINIMAL_ZLIB_CRC32);
|
||||
|
||||
x52_layout *L = NULL;
|
||||
assert_int_equal(x52_layout_load_memory(buf, len, &L), 0);
|
||||
assert_non_null(L);
|
||||
assert_string_equal(x52_layout_name(L), "x");
|
||||
x52_layout_free(L);
|
||||
free(buf);
|
||||
}
|
||||
|
||||
static void test_reject_tampered_checksum_byte(void **state)
|
||||
{
|
||||
(void)state;
|
||||
const uint32_t limit = 1u;
|
||||
size_t len = X52_LAYOUT_HEADER_BYTES + 2u;
|
||||
uint8_t *buf = (uint8_t *)calloc(1, len);
|
||||
assert_non_null(buf);
|
||||
memcpy(buf, "X52L", 4);
|
||||
write_be16(buf + 4, X52_LAYOUT_FORMAT_VERSION);
|
||||
write_be16(buf + 6, 0);
|
||||
write_be32(buf + 8, limit);
|
||||
memcpy(buf + 16, "x", 2);
|
||||
finalize_crc(buf, len);
|
||||
buf[12] ^= 0x01u;
|
||||
|
||||
x52_layout *L = NULL;
|
||||
assert_int_equal(x52_layout_load_memory(buf, len, &L), EINVAL);
|
||||
assert_null(L);
|
||||
free(buf);
|
||||
}
|
||||
|
||||
static void test_reject_codepoint_limit_not_big_endian(void **state)
|
||||
{
|
||||
(void)state;
|
||||
/* Little-endian uint32_t 1 in the codepoint_limit field: read_be32 → 0x01000000. */
|
||||
const uint32_t limit = 1u;
|
||||
size_t len = X52_LAYOUT_HEADER_BYTES + 2u * (size_t)limit;
|
||||
uint8_t *buf = (uint8_t *)calloc(1, len);
|
||||
assert_non_null(buf);
|
||||
memcpy(buf, "X52L", 4);
|
||||
write_be16(buf + 4, X52_LAYOUT_FORMAT_VERSION);
|
||||
write_be16(buf + 6, 0);
|
||||
buf[8] = 0x01u;
|
||||
buf[9] = 0x00u;
|
||||
buf[10] = 0x00u;
|
||||
buf[11] = 0x00u;
|
||||
memcpy(buf + 16, "x", 2);
|
||||
finalize_crc(buf, len);
|
||||
|
||||
x52_layout *L = NULL;
|
||||
assert_int_equal(x52_layout_load_memory(buf, len, &L), EINVAL);
|
||||
assert_null(L);
|
||||
free(buf);
|
||||
}
|
||||
|
||||
static void test_reject_version_word_not_big_endian_one(void **state)
|
||||
{
|
||||
(void)state;
|
||||
const uint32_t limit = 1u;
|
||||
size_t len = X52_LAYOUT_HEADER_BYTES + 2u;
|
||||
uint8_t *buf = (uint8_t *)calloc(1, len);
|
||||
assert_non_null(buf);
|
||||
memcpy(buf, "X52L", 4);
|
||||
/* Native little-endian 0x0001 would appear as 01 00 — not BE version 1 (00 01). */
|
||||
buf[4] = 0x01u;
|
||||
buf[5] = 0x00u;
|
||||
write_be16(buf + 6, 0);
|
||||
write_be32(buf + 8, limit);
|
||||
memcpy(buf + 16, "x", 2);
|
||||
finalize_crc(buf, len);
|
||||
|
||||
x52_layout *L = NULL;
|
||||
assert_int_equal(x52_layout_load_memory(buf, len, &L), EINVAL);
|
||||
assert_null(L);
|
||||
free(buf);
|
||||
}
|
||||
|
||||
static void test_reject_size_mismatch(void **state)
|
||||
{
|
||||
(void)state;
|
||||
const uint32_t limit = 4u;
|
||||
size_t len = X52_LAYOUT_HEADER_BYTES + 2u * (size_t)limit;
|
||||
uint8_t *buf = (uint8_t *)calloc(1, len);
|
||||
assert_non_null(buf);
|
||||
memcpy(buf, "X52L", 4);
|
||||
write_be16(buf + 4, X52_LAYOUT_FORMAT_VERSION);
|
||||
write_be16(buf + 6, 0);
|
||||
write_be32(buf + 8, limit);
|
||||
memcpy(buf + 16, "x", 2);
|
||||
finalize_crc(buf, len);
|
||||
|
||||
x52_layout *L = NULL;
|
||||
assert_int_equal(x52_layout_load_memory(buf, len - 1u, &L), EINVAL);
|
||||
assert_null(L);
|
||||
free(buf);
|
||||
}
|
||||
|
||||
static void test_reject_disallowed_usage(void **state)
|
||||
{
|
||||
(void)state;
|
||||
const uint32_t limit = 1u;
|
||||
size_t len = X52_LAYOUT_HEADER_BYTES + 2u;
|
||||
uint8_t *buf = (uint8_t *)calloc(1, len);
|
||||
assert_non_null(buf);
|
||||
memcpy(buf, "X52L", 4);
|
||||
write_be16(buf + 4, X52_LAYOUT_FORMAT_VERSION);
|
||||
write_be16(buf + 6, 0);
|
||||
write_be32(buf + 8, limit);
|
||||
memcpy(buf + 16, "x", 2);
|
||||
buf[X52_LAYOUT_HEADER_BYTES + 1u] = 0x3A; /* VKM_KEY_F1 — not in allowlist */
|
||||
finalize_crc(buf, len);
|
||||
|
||||
x52_layout *L = NULL;
|
||||
assert_int_equal(x52_layout_load_memory(buf, len, &L), EINVAL);
|
||||
assert_null(L);
|
||||
free(buf);
|
||||
}
|
||||
|
||||
static void test_metadata_plain(void **state)
|
||||
{
|
||||
(void)state;
|
||||
const uint32_t limit = 1u;
|
||||
size_t len = X52_LAYOUT_HEADER_BYTES + 2u;
|
||||
uint8_t *buf = (uint8_t *)calloc(1, len);
|
||||
assert_non_null(buf);
|
||||
memcpy(buf, "X52L", 4);
|
||||
write_be16(buf + 4, X52_LAYOUT_FORMAT_VERSION);
|
||||
write_be16(buf + 6, 0);
|
||||
write_be32(buf + 8, limit);
|
||||
memcpy(buf + 16, "us", 3);
|
||||
memcpy(buf + 48, "US QWERTY", 10);
|
||||
finalize_crc(buf, len);
|
||||
|
||||
x52_layout *L = NULL;
|
||||
assert_int_equal(x52_layout_load_memory(buf, len, &L), 0);
|
||||
assert_non_null(L);
|
||||
assert_string_equal(x52_layout_name(L), "us");
|
||||
assert_string_equal(x52_layout_description(L), "US QWERTY");
|
||||
x52_layout_free(L);
|
||||
free(buf);
|
||||
}
|
||||
|
||||
static void test_metadata_truncated_suffix(void **state)
|
||||
{
|
||||
(void)state;
|
||||
const uint32_t limit = 1u;
|
||||
size_t len = X52_LAYOUT_HEADER_BYTES + 2u;
|
||||
uint8_t *buf = (uint8_t *)calloc(1, len);
|
||||
assert_non_null(buf);
|
||||
memcpy(buf, "X52L", 4);
|
||||
write_be16(buf + 4, X52_LAYOUT_FORMAT_VERSION);
|
||||
write_be16(buf + 6,
|
||||
(uint16_t)(X52_LAYOUT_FLAG_NAME_TRUNCATED | X52_LAYOUT_FLAG_DESCRIPTION_TRUNCATED));
|
||||
write_be32(buf + 8, limit);
|
||||
memcpy(buf + 16, "longish", 8);
|
||||
memcpy(buf + 48, "desc", 5);
|
||||
finalize_crc(buf, len);
|
||||
|
||||
x52_layout *L = NULL;
|
||||
assert_int_equal(x52_layout_load_memory(buf, len, &L), 0);
|
||||
assert_non_null(L);
|
||||
assert_int_equal((int)x52_layout_flags(L),
|
||||
(int)(X52_LAYOUT_FLAG_NAME_TRUNCATED | X52_LAYOUT_FLAG_DESCRIPTION_TRUNCATED));
|
||||
assert_string_equal(x52_layout_name(L), "longish<truncated>");
|
||||
assert_string_equal(x52_layout_description(L), "desc<truncated>");
|
||||
x52_layout_free(L);
|
||||
free(buf);
|
||||
}
|
||||
|
||||
static void test_metadata_name_truncated_flag_only(void **state)
|
||||
{
|
||||
(void)state;
|
||||
const uint32_t limit = 1u;
|
||||
size_t len = X52_LAYOUT_HEADER_BYTES + 2u;
|
||||
uint8_t *buf = (uint8_t *)calloc(1, len);
|
||||
assert_non_null(buf);
|
||||
memcpy(buf, "X52L", 4);
|
||||
write_be16(buf + 4, X52_LAYOUT_FORMAT_VERSION);
|
||||
write_be16(buf + 6, X52_LAYOUT_FLAG_NAME_TRUNCATED);
|
||||
write_be32(buf + 8, limit);
|
||||
memcpy(buf + 16, "nm", 3);
|
||||
memcpy(buf + 48, "plain", 6);
|
||||
finalize_crc(buf, len);
|
||||
|
||||
x52_layout *L = NULL;
|
||||
assert_int_equal(x52_layout_load_memory(buf, len, &L), 0);
|
||||
assert_non_null(L);
|
||||
assert_int_equal((int)x52_layout_flags(L), (int)X52_LAYOUT_FLAG_NAME_TRUNCATED);
|
||||
assert_string_equal(x52_layout_name(L), "nm<truncated>");
|
||||
assert_string_equal(x52_layout_description(L), "plain");
|
||||
x52_layout_free(L);
|
||||
free(buf);
|
||||
}
|
||||
|
||||
static void test_reject_unknown_flags(void **state)
|
||||
{
|
||||
(void)state;
|
||||
const uint32_t limit = 1u;
|
||||
size_t len = X52_LAYOUT_HEADER_BYTES + 2u;
|
||||
uint8_t *buf = (uint8_t *)calloc(1, len);
|
||||
assert_non_null(buf);
|
||||
memcpy(buf, "X52L", 4);
|
||||
write_be16(buf + 4, X52_LAYOUT_FORMAT_VERSION);
|
||||
write_be16(buf + 6, 0x8000);
|
||||
write_be32(buf + 8, limit);
|
||||
memcpy(buf + 16, "x", 2);
|
||||
finalize_crc(buf, len);
|
||||
|
||||
x52_layout *L = NULL;
|
||||
assert_int_equal(x52_layout_load_memory(buf, len, &L), EINVAL);
|
||||
assert_null(L);
|
||||
free(buf);
|
||||
}
|
||||
|
||||
static void test_reject_empty_name(void **state)
|
||||
{
|
||||
(void)state;
|
||||
const uint32_t limit = 1u;
|
||||
size_t len = X52_LAYOUT_HEADER_BYTES + 2u;
|
||||
uint8_t *buf = (uint8_t *)calloc(1, len);
|
||||
assert_non_null(buf);
|
||||
memcpy(buf, "X52L", 4);
|
||||
write_be16(buf + 4, X52_LAYOUT_FORMAT_VERSION);
|
||||
write_be16(buf + 6, 0);
|
||||
write_be32(buf + 8, limit);
|
||||
finalize_crc(buf, len);
|
||||
|
||||
x52_layout *L = NULL;
|
||||
assert_int_equal(x52_layout_load_memory(buf, len, &L), EINVAL);
|
||||
assert_null(L);
|
||||
free(buf);
|
||||
}
|
||||
|
||||
static void test_basename_normalize_and_join(void **state)
|
||||
{
|
||||
(void)state;
|
||||
char out[256];
|
||||
bool rej;
|
||||
|
||||
x52_layout_normalize_keyboard_basename(NULL, out, sizeof out, &rej);
|
||||
assert_false(rej);
|
||||
assert_string_equal(out, "us");
|
||||
|
||||
x52_layout_normalize_keyboard_basename("", out, sizeof out, &rej);
|
||||
assert_false(rej);
|
||||
assert_string_equal(out, "us");
|
||||
|
||||
x52_layout_normalize_keyboard_basename("de", out, sizeof out, &rej);
|
||||
assert_false(rej);
|
||||
assert_string_equal(out, "de");
|
||||
|
||||
x52_layout_normalize_keyboard_basename("ab_cd-9", out, sizeof out, &rej);
|
||||
assert_false(rej);
|
||||
assert_string_equal(out, "ab_cd-9");
|
||||
|
||||
x52_layout_normalize_keyboard_basename("../x", out, sizeof out, &rej);
|
||||
assert_true(rej);
|
||||
assert_string_equal(out, "us");
|
||||
|
||||
x52_layout_normalize_keyboard_basename("a/b", out, sizeof out, &rej);
|
||||
assert_true(rej);
|
||||
assert_string_equal(out, "us");
|
||||
|
||||
x52_layout_normalize_keyboard_basename("bad name", out, sizeof out, &rej);
|
||||
assert_true(rej);
|
||||
assert_string_equal(out, "us");
|
||||
|
||||
x52_layout_normalize_keyboard_basename("a\\b", out, sizeof out, &rej);
|
||||
assert_true(rej);
|
||||
assert_string_equal(out, "us");
|
||||
|
||||
x52_layout_normalize_keyboard_basename("x..y", out, sizeof out, &rej);
|
||||
assert_true(rej);
|
||||
assert_string_equal(out, "us");
|
||||
|
||||
memset(out, 0, sizeof out);
|
||||
x52_layout_normalize_keyboard_basename("almost..", out, sizeof out, &rej);
|
||||
assert_true(rej);
|
||||
assert_string_equal(out, "us");
|
||||
|
||||
char path[PATH_MAX];
|
||||
assert_int_equal(x52_layout_join_file_path(path, sizeof path, "/usr/share", "us"), 0);
|
||||
assert_string_equal(path, "/usr/share/x52d/us.x52l");
|
||||
assert_int_equal(x52_layout_join_file_path(path, 20, "/usr/share", "us"), ENAMETOOLONG);
|
||||
}
|
||||
|
||||
static void test_reject_version(void **state)
|
||||
{
|
||||
(void)state;
|
||||
const uint32_t limit = 1u;
|
||||
size_t len = X52_LAYOUT_HEADER_BYTES + 2u;
|
||||
uint8_t *buf = (uint8_t *)calloc(1, len);
|
||||
assert_non_null(buf);
|
||||
memcpy(buf, "X52L", 4);
|
||||
write_be16(buf + 4, 99);
|
||||
write_be16(buf + 6, 0);
|
||||
write_be32(buf + 8, limit);
|
||||
memcpy(buf + 16, "v", 2);
|
||||
finalize_crc(buf, len);
|
||||
|
||||
x52_layout *L = NULL;
|
||||
assert_int_equal(x52_layout_load_memory(buf, len, &L), EINVAL);
|
||||
assert_null(L);
|
||||
free(buf);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
const struct CMUnitTest tests[] = {
|
||||
cmocka_unit_test(test_load_minimal_lookup),
|
||||
cmocka_unit_test(test_metadata_plain),
|
||||
cmocka_unit_test(test_metadata_truncated_suffix),
|
||||
cmocka_unit_test(test_metadata_name_truncated_flag_only),
|
||||
cmocka_unit_test(test_reject_unknown_flags),
|
||||
cmocka_unit_test(test_reject_empty_name),
|
||||
cmocka_unit_test(test_reject_bad_checksum),
|
||||
cmocka_unit_test(test_layout_crc_matches_python_zlib_minimal),
|
||||
cmocka_unit_test(test_reject_tampered_checksum_byte),
|
||||
cmocka_unit_test(test_reject_codepoint_limit_not_big_endian),
|
||||
cmocka_unit_test(test_reject_version_word_not_big_endian_one),
|
||||
cmocka_unit_test(test_reject_size_mismatch),
|
||||
cmocka_unit_test(test_reject_disallowed_usage),
|
||||
cmocka_unit_test(test_reject_version),
|
||||
cmocka_unit_test(test_basename_normalize_and_join),
|
||||
};
|
||||
|
||||
cmocka_set_message_output(CM_OUTPUT_TAP);
|
||||
return cmocka_run_group_tests(tests, NULL, NULL);
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - Keyboard layout HID usage allowlist
|
||||
*
|
||||
* Copyright (C) 2026 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include <daemon/layout_usage_allowlist.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <vkm/vkm.h>
|
||||
|
||||
/* layout binary and compiler must stay aligned with vkm_key numeric values. */
|
||||
_Static_assert((unsigned)VKM_KEY_A == 0x04u, "vkm_key main block start");
|
||||
_Static_assert((unsigned)VKM_KEY_CAPS_LOCK == 0x39u, "vkm_key main block end");
|
||||
_Static_assert((unsigned)VKM_KEY_INTL_BACKSLASH == 0x64u, "vkm_key ISO backslash");
|
||||
|
||||
bool x52_layout_usage_key_allowed(uint8_t usage)
|
||||
{
|
||||
if (usage == 0) {
|
||||
return false;
|
||||
}
|
||||
if (usage >= 0x04 && usage <= 0x39) {
|
||||
return true;
|
||||
}
|
||||
if (usage == 0x64) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - Keyboard layout HID usage allowlist
|
||||
*
|
||||
* Copyright (C) 2026 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file layout_usage_allowlist.h
|
||||
* @brief HID keyboard/page (0x07) usages permitted as layout chord \c usage_key bytes.
|
||||
*
|
||||
* The set is the USB HID "main block" (usages @c 0x04-@c 0x39, i.e. @c VKM_KEY_A
|
||||
* through @c VKM_KEY_CAPS_LOCK) plus @c VKM_KEY_INTL_BACKSLASH (@c 0x64). It excludes
|
||||
* @c VKM_KEY_NONE, modifiers, function row, navigation cluster, keypad, and all other
|
||||
* @ref vkm_key values; same rule as @c tools/x52compile_layout.py.
|
||||
*/
|
||||
#ifndef X52D_LAYOUT_USAGE_ALLOWLIST_H
|
||||
#define X52D_LAYOUT_USAGE_ALLOWLIST_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Return whether @p usage may appear as the non-modifier byte in a layout entry.
|
||||
*
|
||||
* @param usage HID usage ID (page 0x07), same encoding as @ref vkm_key.
|
||||
*
|
||||
* @returns true if @p usage is in the main-block allowlist; false for @c VKM_KEY_NONE
|
||||
* and for any disallowed usage.
|
||||
*/
|
||||
bool x52_layout_usage_key_allowed(uint8_t usage);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* X52D_LAYOUT_USAGE_ALLOWLIST_H */
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - layout HID usage allowlist tests
|
||||
*
|
||||
* Copyright (C) 2026 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include "build-config.h"
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <setjmp.h>
|
||||
#include <cmocka.h>
|
||||
|
||||
#include <daemon/layout_usage_allowlist.h>
|
||||
#include <vkm/vkm.h>
|
||||
|
||||
static void test_allows_main_block(void **state)
|
||||
{
|
||||
(void)state;
|
||||
|
||||
assert_true(x52_layout_usage_key_allowed(VKM_KEY_A));
|
||||
assert_true(x52_layout_usage_key_allowed(VKM_KEY_Z));
|
||||
assert_true(x52_layout_usage_key_allowed(VKM_KEY_1));
|
||||
assert_true(x52_layout_usage_key_allowed(VKM_KEY_0));
|
||||
assert_true(x52_layout_usage_key_allowed(VKM_KEY_SPACE));
|
||||
assert_true(x52_layout_usage_key_allowed(VKM_KEY_CAPS_LOCK));
|
||||
assert_true(x52_layout_usage_key_allowed(VKM_KEY_INTL_BACKSLASH));
|
||||
assert_true(x52_layout_usage_key_allowed(VKM_KEY_NONUS_HASH));
|
||||
}
|
||||
|
||||
static void test_rejects_disallowed(void **state)
|
||||
{
|
||||
(void)state;
|
||||
|
||||
assert_false(x52_layout_usage_key_allowed(0));
|
||||
assert_false(x52_layout_usage_key_allowed(VKM_KEY_F1));
|
||||
assert_false(x52_layout_usage_key_allowed(VKM_KEY_LEFT_CTRL));
|
||||
assert_false(x52_layout_usage_key_allowed(VKM_KEY_KEYPAD_1));
|
||||
assert_false(x52_layout_usage_key_allowed(0x03));
|
||||
assert_false(x52_layout_usage_key_allowed(0x3A));
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
const struct CMUnitTest tests[] = {
|
||||
cmocka_unit_test(test_allows_main_block),
|
||||
cmocka_unit_test(test_rejects_disallowed),
|
||||
};
|
||||
|
||||
cmocka_set_message_output(CM_OUTPUT_TAP);
|
||||
return cmocka_run_group_tests(tests, NULL, NULL);
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "build-config.h"
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
|
|
@ -15,9 +15,9 @@
|
|||
|
||||
#define PINELOG_MODULE X52D_MOD_LED
|
||||
#include "pinelog.h"
|
||||
#include "x52d_config.h"
|
||||
#include "x52d_const.h"
|
||||
#include "x52d_device.h"
|
||||
#include <daemon/config.h>
|
||||
#include <daemon/constants.h>
|
||||
#include <daemon/device.h>
|
||||
|
||||
#define SET_LED_STATE(led, state) \
|
||||
PINELOG_TRACE("Setting LED %s state to %s (%d)", \
|
||||
|
|
@ -6,26 +6,28 @@
|
|||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "build-config.h"
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <signal.h>
|
||||
#include <inttypes.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "x52d_clock.h"
|
||||
#include "x52d_const.h"
|
||||
#include "x52d_config.h"
|
||||
#include "x52d_device.h"
|
||||
#include "x52d_io.h"
|
||||
#include "x52d_mouse.h"
|
||||
#include "x52d_command.h"
|
||||
#include "x52d_notify.h"
|
||||
#include "x52dcomm-internal.h"
|
||||
#include "x52dcomm.h"
|
||||
#include <daemon/clock.h>
|
||||
#include <daemon/constants.h>
|
||||
#include <daemon/config.h>
|
||||
#include <daemon/device.h>
|
||||
#include <daemon/io.h>
|
||||
#include <daemon/mouse.h>
|
||||
#include <daemon/command.h>
|
||||
#include <daemon/notify.h>
|
||||
#include <daemon/x52dcomm-internal.h>
|
||||
#include <daemon/keyboard_layout.h>
|
||||
#include <libx52/x52dcomm.h>
|
||||
#include "pinelog.h"
|
||||
|
||||
static volatile int flag_quit;
|
||||
|
|
@ -38,12 +40,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 +115,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 +330,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);
|
||||
|
|
@ -361,15 +365,14 @@ int main(int argc, char **argv)
|
|||
PINELOG_INFO(_("Received termination signal %s"), strsignal(flag_quit));
|
||||
|
||||
cleanup:
|
||||
x52d_keyboard_layout_fini();
|
||||
// Stop device threads
|
||||
x52d_clock_exit();
|
||||
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);
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
# x52d (dep_config_h: Meson build-config.h; private API is daemon/config.h)
|
||||
config_defs = custom_target('config-defs',
|
||||
depend_files: ['x52d_map_config.py', 'x52d.conf'],
|
||||
input: [
|
||||
'x52d.conf',
|
||||
'config_registry.json'
|
||||
],
|
||||
output: [
|
||||
'config-defs.h',
|
||||
'config-defs.c',
|
||||
'config_defs.py'
|
||||
],
|
||||
command: [
|
||||
python, meson.current_source_dir() / 'x52d_map_config.py',
|
||||
'@INPUT0@', '@INPUT1@',
|
||||
'@OUTPUT0@', '@OUTPUT1@', '@OUTPUT2@'
|
||||
])
|
||||
|
||||
module_defs = custom_target('module-defs',
|
||||
depend_files: ['x52d_gen_module.py', 'module_defs.py'],
|
||||
output: ['module-map.h', 'module-map.c'],
|
||||
command: [python, meson.current_source_dir() / 'x52d_gen_module.py',
|
||||
'@OUTPUT0@', '@OUTPUT1@'])
|
||||
|
||||
# Header only: ordering on module-map.h without compiling module-map.c per target.
|
||||
dep_module_map_gen = declare_dependency(sources: module_defs[0])
|
||||
|
||||
# Full module_defs: name-id-map.c includes module-map.h (generated with the .c).
|
||||
slib_comm_defs = static_library('x52dcommdefs',
|
||||
config_defs[1],
|
||||
module_defs,
|
||||
'name-id-map.c',
|
||||
)
|
||||
|
||||
libx52dcomm_version = '1.0.0'
|
||||
|
||||
libx52dcomm_sources = [
|
||||
'comm_client.c',
|
||||
'comm_internal.c'
|
||||
]
|
||||
|
||||
root_includes = include_directories('..')
|
||||
|
||||
lib_libx52dcomm = library('x52dcomm', libx52dcomm_sources,
|
||||
dependencies: [dep_intl, dep_config_h, dep_module_map_gen],
|
||||
version: libx52dcomm_version,
|
||||
c_args: sym_hidden_cargs,
|
||||
install: true,
|
||||
include_directories: [includes, root_includes])
|
||||
|
||||
pkgconfig.generate(lib_libx52dcomm,
|
||||
name: 'x52dcomm',
|
||||
description: 'Client library for communicating with the x52d X52 daemon.',
|
||||
version: libx52dcomm_version,
|
||||
)
|
||||
|
||||
x52d_sources = [
|
||||
'main.c',
|
||||
'config_parser.c',
|
||||
'config_dump.c',
|
||||
'config.c',
|
||||
'device.c',
|
||||
'client.c',
|
||||
'clock.c',
|
||||
'mouse.c',
|
||||
'notify.c',
|
||||
'led.c',
|
||||
'command.c',
|
||||
'io.c',
|
||||
'mouse_handler.c',
|
||||
'layout_usage_allowlist.c',
|
||||
'layout_load.c',
|
||||
'keyboard_layout.c',
|
||||
'crc32.c',
|
||||
]
|
||||
|
||||
dep_threads = dependency('threads')
|
||||
|
||||
# Comm sources are compiled into x52d (same as Autotools); libx52dcomm is only for x52ctl.
|
||||
x52d_linkwith = [lib_libx52, lib_vkm, lib_libx52io, slib_comm_defs]
|
||||
x52d_deps = [dep_pinelog, dep_inih, dep_threads, dep_math, dep_intl, dep_config_h,
|
||||
dep_module_map_gen]
|
||||
x52d_cflags = []
|
||||
|
||||
exe_x52d = executable('x52d', x52d_sources + libx52dcomm_sources,
|
||||
install: true,
|
||||
include_directories: [includes, root_includes],
|
||||
c_args: sym_hidden_cargs + x52d_cflags,
|
||||
dependencies: x52d_deps,
|
||||
link_with: x52d_linkwith)
|
||||
|
||||
exe_x52ctl = executable('x52ctl', 'daemon_control.c',
|
||||
install: true,
|
||||
dependencies: [dep_intl, dep_config_h, dep_module_map_gen],
|
||||
include_directories: [includes, root_includes],
|
||||
link_with: lib_libx52dcomm)
|
||||
|
||||
install_data('x52d.conf',
|
||||
install_dir: join_paths(get_option('sysconfdir'), 'x52d'))
|
||||
|
||||
us_x52l = custom_target(
|
||||
'us-x52l',
|
||||
input: files('../data/layouts/us.layout'),
|
||||
output: 'us.x52l',
|
||||
command: [
|
||||
python,
|
||||
join_paths(meson.project_source_root(), 'tools', 'x52compile_layout.py'),
|
||||
'@INPUT@',
|
||||
'@OUTPUT@',
|
||||
],
|
||||
install: true,
|
||||
install_dir: join_paths(get_option('datadir'), 'x52d'))
|
||||
|
||||
test('daemon-communication', files('test_daemon_comm.py')[0],
|
||||
depends: [exe_x52d, exe_x52ctl], protocol: 'tap')
|
||||
|
||||
x52d_mouse_test_sources = ['mouse_test.c', 'mouse.c']
|
||||
x52d_mouse_test = executable('x52d-mouse-test', x52d_mouse_test_sources,
|
||||
include_directories: [includes, root_includes],
|
||||
dependencies: [dep_pinelog, dep_cmocka, dep_intl, dep_math, dep_config_h,
|
||||
dep_module_map_gen])
|
||||
|
||||
test('x52d-mouse-test', x52d_mouse_test, protocol: 'tap')
|
||||
|
||||
layout_usage_allowlist_test = executable('layout-usage-allowlist-test',
|
||||
'layout_usage_allowlist_test.c',
|
||||
'layout_usage_allowlist.c',
|
||||
build_by_default: false,
|
||||
include_directories: [includes, root_includes],
|
||||
dependencies: [dep_cmocka, dep_config_h])
|
||||
|
||||
test('layout-usage-allowlist', layout_usage_allowlist_test, protocol: 'tap')
|
||||
|
||||
crc32_test = executable('crc32-test', 'crc32_test.c', 'crc32.c',
|
||||
build_by_default: false,
|
||||
include_directories: [includes, root_includes],
|
||||
dependencies: [dep_cmocka, dep_config_h])
|
||||
|
||||
test('crc32', crc32_test, protocol: 'tap')
|
||||
|
||||
layout_load_test = executable('layout-load-test',
|
||||
'layout_load_test.c',
|
||||
'layout_load.c',
|
||||
'layout_usage_allowlist.c',
|
||||
'crc32.c',
|
||||
build_by_default: false,
|
||||
include_directories: [includes, root_includes],
|
||||
dependencies: [dep_cmocka, dep_config_h])
|
||||
|
||||
test('layout-load', layout_load_test, protocol: 'tap')
|
||||
|
||||
pymod_daemon = import('python')
|
||||
python_layout_test = pymod_daemon.find_installation('python3')
|
||||
test('layout-usage-allowlist-sync', python_layout_test,
|
||||
args: [join_paths(meson.project_source_root(), 'tools', 'test_layout_allowlist_sync.py')],
|
||||
protocol: 'tap')
|
||||
|
||||
test('layout-compile-py', python_layout_test,
|
||||
args: [join_paths(meson.project_source_root(), 'tools', 'test_x52compile_layout.py')],
|
||||
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
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
"""Module name to identifier mapping"""
|
||||
|
||||
from enum import Enum
|
||||
|
||||
class Module(Enum):
|
||||
"""Module name to identifier"""
|
||||
CONFIG = 0
|
||||
CLOCK = 1
|
||||
DEVICE = 2
|
||||
IO = 3
|
||||
LED = 4
|
||||
MOUSE = 5
|
||||
COMMAND = 6
|
||||
CLIENT = 7
|
||||
NOTIFY = 8
|
||||
KEYBOARD_LAYOUT = 9
|
||||
|
||||
class LogLevel(Enum):
|
||||
"""Map log level names to pinelog levels"""
|
||||
# This is hard coded to the pinelog levels
|
||||
NOTSET = -2
|
||||
NONE = -1
|
||||
FATAL = 0
|
||||
ERROR = 1
|
||||
WARNING = 2
|
||||
INFO = 3
|
||||
DEBUG = 4
|
||||
TRACE = 5
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - Mouse driver
|
||||
*
|
||||
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include "build-config.h"
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <math.h>
|
||||
|
||||
#define PINELOG_MODULE X52D_MOD_MOUSE
|
||||
#include "pinelog.h"
|
||||
#include <daemon/config.h>
|
||||
#include <daemon/constants.h>
|
||||
#include <daemon/mouse.h>
|
||||
|
||||
// Mouse speed is the delay in microseconds between subsequent mouse reports
|
||||
#define DEFAULT_MOUSE_DELAY 70000
|
||||
#define MOUSE_DELAY_DELTA 5000
|
||||
#define MOUSE_DELAY_MIN 10000
|
||||
#define MOUSE_MULT_FACTOR 4
|
||||
#define MAX_MOUSE_MULT 5
|
||||
#define MIN_SENSITIVITY 10
|
||||
#define MAX_SENSITIVITY 500
|
||||
|
||||
volatile int mouse_scroll_dir = 1;
|
||||
volatile bool mouse_isometric_mode = false;
|
||||
volatile int mouse_curve_factor = 3;
|
||||
volatile int mouse_deadzone_factor = 0;
|
||||
volatile int mouse_sensitivity = 0;
|
||||
|
||||
static int clamp_int(const char *description, int value, int min, int max)
|
||||
{
|
||||
if (value < min) {
|
||||
PINELOG_DEBUG(_("Clamping %s value %d to range [%d..%d]"),
|
||||
description, value, min, max);
|
||||
return min;
|
||||
}
|
||||
|
||||
if (value > max) {
|
||||
PINELOG_DEBUG(_("Clamping %s value %d to range [%d..%d]"),
|
||||
description, value, min, max);
|
||||
return max;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
void x52d_cfg_set_Mouse_Enabled(bool enabled)
|
||||
{
|
||||
PINELOG_DEBUG(_("Setting mouse enable to %s"),
|
||||
enabled ? _("on") : _("off"));
|
||||
x52d_mouse_thread_control(enabled);
|
||||
}
|
||||
|
||||
void x52d_cfg_set_Mouse_Speed(int speed)
|
||||
{
|
||||
// DEPRECATED, calculate the sensitivity instead
|
||||
int new_delay;
|
||||
int new_mult;
|
||||
|
||||
int max_base_speed = (DEFAULT_MOUSE_DELAY - MOUSE_DELAY_MIN) / MOUSE_DELAY_DELTA;
|
||||
int max_speed = max_base_speed + MAX_MOUSE_MULT * MOUSE_MULT_FACTOR;
|
||||
|
||||
double sensitivity;
|
||||
|
||||
if (mouse_sensitivity == 0) {
|
||||
PINELOG_WARN(_("Config option 'mouse.speed' is DEPRECATED. Please use 'mouse.sensitivity' instead"));
|
||||
}
|
||||
|
||||
speed = clamp_int("mouse speed", speed, 0, max_speed);
|
||||
if (speed <= max_base_speed) {
|
||||
new_delay = DEFAULT_MOUSE_DELAY - speed * MOUSE_DELAY_DELTA;
|
||||
new_mult = MOUSE_MULT_FACTOR;
|
||||
} else {
|
||||
// speed between max_base_speed & max_speed
|
||||
new_delay = MOUSE_DELAY_MIN;
|
||||
new_mult = MOUSE_MULT_FACTOR + (speed - max_base_speed);
|
||||
}
|
||||
|
||||
sensitivity = round(1e6 / new_delay * new_mult / (double)MOUSE_MULT_FACTOR);
|
||||
|
||||
PINELOG_INFO(_("Migrating legacy mouse speed '%d' to sensitivity '%d' (percentage)"),
|
||||
speed, (int)sensitivity);
|
||||
mouse_sensitivity = clamp_int(_("speed -> sensitivity"), (int)sensitivity,
|
||||
MIN_SENSITIVITY, MAX_SENSITIVITY);
|
||||
}
|
||||
|
||||
void x52d_cfg_set_Mouse_ReverseScroll(bool enabled)
|
||||
{
|
||||
PINELOG_DEBUG(_("Setting mouse reverse scroll to %s"),
|
||||
enabled ? _("on") : _("off"));
|
||||
|
||||
if (enabled) {
|
||||
mouse_scroll_dir = -1;
|
||||
} else {
|
||||
mouse_scroll_dir = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void x52d_cfg_set_Mouse_IsometricMode(bool enabled)
|
||||
{
|
||||
PINELOG_DEBUG(_("Setting mouse isometric mode to %s"),
|
||||
enabled ? _("on") : _("off"));
|
||||
mouse_isometric_mode = enabled;
|
||||
}
|
||||
|
||||
void x52d_cfg_set_Mouse_Sensitivity(int factor)
|
||||
{
|
||||
mouse_sensitivity = clamp_int(_("sensitivity"), factor,
|
||||
MIN_SENSITIVITY, MAX_SENSITIVITY);
|
||||
|
||||
PINELOG_DEBUG(_("Setting mouse sensitivity to %d%%"), mouse_sensitivity);
|
||||
}
|
||||
|
||||
void x52d_cfg_set_Mouse_CurveFactor(int factor)
|
||||
{
|
||||
// Factor ranges from 1-5, clamp it in this range
|
||||
// Shift by 1 so it uses the correct index
|
||||
mouse_curve_factor = clamp_int(_("curve factor"), factor, 1, 5) - 1;
|
||||
PINELOG_DEBUG(_("Setting mouse curve factor to %d"), mouse_curve_factor);
|
||||
}
|
||||
|
||||
void x52d_cfg_set_Mouse_Deadzone(int factor)
|
||||
{
|
||||
// Factor ranges from 0-12, clamp it in this range
|
||||
mouse_deadzone_factor = clamp_int(_("deadzone factor"), factor, 0, 11);
|
||||
PINELOG_DEBUG(_("Setting mouse deadzone to %d"), mouse_deadzone_factor);
|
||||
}
|
||||
|
|
@ -10,17 +10,17 @@
|
|||
#define X52D_MOUSE_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "libx52io.h"
|
||||
#include <libx52/libx52io.h>
|
||||
|
||||
extern volatile int mouse_delay;
|
||||
extern volatile int mouse_mult;
|
||||
extern volatile bool mouse_isometric_mode;
|
||||
extern volatile int mouse_scroll_dir;
|
||||
extern volatile int mouse_curve_factor;
|
||||
extern volatile int mouse_deadzone_factor;
|
||||
extern volatile int mouse_sensitivity;
|
||||
|
||||
#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
|
||||
|
|
@ -0,0 +1,318 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - Mouse driver
|
||||
*
|
||||
* Copyright (C) 2021-2026 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include "build-config.h"
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <libx52/libx52io.h>
|
||||
#include <vkm/vkm.h>
|
||||
|
||||
#define PINELOG_MODULE X52D_MOD_MOUSE
|
||||
#include "pinelog.h"
|
||||
#include <daemon/config.h>
|
||||
#include <daemon/constants.h>
|
||||
#include <daemon/mouse.h>
|
||||
|
||||
static pthread_t mouse_thr;
|
||||
static bool mouse_thr_enabled = false;
|
||||
|
||||
static vkm_context *mouse_context;
|
||||
|
||||
static volatile libx52io_report old_report;
|
||||
static volatile libx52io_report new_report;
|
||||
|
||||
static int report_button_change(vkm_mouse_button button, int index)
|
||||
{
|
||||
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) {
|
||||
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 == VKM_SUCCESS);
|
||||
}
|
||||
|
||||
static int report_wheel(void)
|
||||
{
|
||||
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
|
||||
wheel = 1 * mouse_scroll_dir;
|
||||
} else if (scroll_dn) {
|
||||
// Scroll down event
|
||||
wheel = -1 * mouse_scroll_dir;
|
||||
}
|
||||
|
||||
if (wheel != 0) {
|
||||
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 == VKM_SUCCESS);
|
||||
}
|
||||
|
||||
static inline int fsgn(double f)
|
||||
{
|
||||
return (f >= 0 ? 1 : -1);
|
||||
}
|
||||
|
||||
static const double MOUSE_CURVE_FACTORS[5] = {
|
||||
1.0, 1.2, 1.5, 1.8, 2.0
|
||||
};
|
||||
|
||||
static const double MOUSE_DEADZONES[12] = {
|
||||
0.0, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4, 1.5
|
||||
};
|
||||
|
||||
static int report_axis(void)
|
||||
{
|
||||
#define MAX_TICK_SPEED 250.0
|
||||
|
||||
static double accum_x = 0.0;
|
||||
static double accum_y = 0.0;
|
||||
|
||||
/* Center raw HID values (0,15) => (-8, 7) */
|
||||
int dx = new_report.axis[LIBX52IO_AXIS_THUMBX] - 8;
|
||||
int dy = new_report.axis[LIBX52IO_AXIS_THUMBY] - 8;
|
||||
|
||||
/* Calculate radial magnitude */
|
||||
double mag = sqrt((double)(dx * dx + dy * dy));
|
||||
double cfg_deadzone = MOUSE_DEADZONES[mouse_deadzone_factor];
|
||||
|
||||
/* Radial deadzone check */
|
||||
if (mag <= cfg_deadzone) {
|
||||
accum_x = 0.0;
|
||||
accum_y = 0.0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Calculate gain */
|
||||
double gain = (double)mouse_sensitivity / 100.0;
|
||||
double exponent = MOUSE_CURVE_FACTORS[mouse_curve_factor];
|
||||
|
||||
/* Normalize magnitude */
|
||||
double adj_mag = mag - cfg_deadzone;
|
||||
double out_x = 0.0;
|
||||
double out_y = 0.0;
|
||||
|
||||
if (mouse_isometric_mode) {
|
||||
/* Isometric mode: speed is a function of total distance */
|
||||
double speed = gain * pow(adj_mag, exponent);
|
||||
|
||||
/* Clamp total speed before breaking into components */
|
||||
if (speed > MAX_TICK_SPEED) {
|
||||
speed = MAX_TICK_SPEED;
|
||||
}
|
||||
|
||||
/* Unit vector * speed */
|
||||
out_x = (dx / mag) * speed;
|
||||
out_y = (dy / mag) * speed;
|
||||
} else {
|
||||
/* Linear mode: speed is independently calculated for X & Y axes */
|
||||
double ratio = adj_mag / mag;
|
||||
double cur_x = dx * ratio;
|
||||
double cur_y = dy * ratio;
|
||||
|
||||
out_x = fsgn(cur_x) * gain * pow(fabs(cur_x), exponent);
|
||||
out_y = fsgn(cur_y) * gain * pow(fabs(cur_y), exponent);
|
||||
|
||||
/* Clamp individual axis speeds */
|
||||
if (fabs(out_x) > MAX_TICK_SPEED) {
|
||||
out_x = fsgn(out_x) * MAX_TICK_SPEED;
|
||||
}
|
||||
|
||||
if (fabs(out_y) > MAX_TICK_SPEED) {
|
||||
out_y = fsgn(out_y) * MAX_TICK_SPEED;
|
||||
}
|
||||
}
|
||||
|
||||
/* Accumulate movement and independent resets */
|
||||
accum_x += out_x;
|
||||
accum_y += out_y;
|
||||
|
||||
if (dx == 0) {
|
||||
accum_x = 0.0;
|
||||
}
|
||||
if (dy == 0) {
|
||||
accum_y = 0.0;
|
||||
}
|
||||
|
||||
/* Extract integer values for VKM injection */
|
||||
int move_x = (int)accum_x;
|
||||
int move_y = (int)accum_y;
|
||||
|
||||
accum_x -= move_x;
|
||||
accum_y -= move_y;
|
||||
|
||||
vkm_result rc;
|
||||
rc = vkm_mouse_move(mouse_context, move_x, move_y);
|
||||
if (rc != VKM_SUCCESS && rc != VKM_ERROR_NO_CHANGE) {
|
||||
PINELOG_ERROR(_("Error %d writing mouse axis event (dx %d, dy %d)"),
|
||||
rc, move_x, move_y);
|
||||
}
|
||||
|
||||
return (rc == VKM_SUCCESS);
|
||||
}
|
||||
|
||||
static void report_sync(void)
|
||||
{
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
static void reset_reports(void)
|
||||
{
|
||||
memset((void *)&old_report, 0, sizeof(old_report));
|
||||
/* Set the default thumbstick values to the mid-point */
|
||||
old_report.axis[LIBX52IO_AXIS_THUMBX] = 8;
|
||||
old_report.axis[LIBX52IO_AXIS_THUMBY] = 8;
|
||||
memcpy((void *)&new_report, (void *)&old_report, sizeof(new_report));
|
||||
}
|
||||
|
||||
static void * x52_mouse_thr(void *param)
|
||||
{
|
||||
(void)param;
|
||||
|
||||
PINELOG_INFO(_("Starting X52 virtual mouse driver thread"));
|
||||
for (;;) {
|
||||
if (report_axis()) {
|
||||
report_sync();
|
||||
}
|
||||
|
||||
usleep(10000);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void x52d_mouse_thr_init(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
PINELOG_TRACE("Initializing virtual mouse driver");
|
||||
rc = pthread_create(&mouse_thr, NULL, x52_mouse_thr, NULL);
|
||||
if (rc != 0) {
|
||||
PINELOG_FATAL(_("Error %d initializing mouse thread: %s"),
|
||||
rc, strerror(rc));
|
||||
}
|
||||
}
|
||||
|
||||
static void x52d_mouse_thr_exit(void)
|
||||
{
|
||||
PINELOG_INFO(_("Shutting down X52 virtual mouse driver thread"));
|
||||
pthread_cancel(mouse_thr);
|
||||
}
|
||||
|
||||
void x52d_mouse_thread_control(bool enabled)
|
||||
{
|
||||
if (!vkm_is_ready(mouse_context)) {
|
||||
PINELOG_INFO(_("Virtual mouse not created. Ignoring thread state change"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
if (mouse_thr_enabled) {
|
||||
PINELOG_TRACE("Ignoring re-enable mouse thread");
|
||||
return;
|
||||
} else {
|
||||
reset_reports();
|
||||
x52d_mouse_thr_init();
|
||||
}
|
||||
} else {
|
||||
if (!mouse_thr_enabled) {
|
||||
PINELOG_TRACE("Ignoring re-disable mouse thread");
|
||||
return;
|
||||
} else {
|
||||
x52d_mouse_thr_exit();
|
||||
}
|
||||
}
|
||||
mouse_thr_enabled = enabled;
|
||||
}
|
||||
|
||||
void x52d_mouse_report_event(libx52io_report *report)
|
||||
{
|
||||
bool state_changed;
|
||||
if (report) {
|
||||
memcpy((void *)&new_report, report, sizeof(new_report));
|
||||
|
||||
if (!vkm_is_ready(mouse_context) || !mouse_thr_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
state_changed = false;
|
||||
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()) || state_changed;
|
||||
|
||||
if (state_changed) {
|
||||
report_sync();
|
||||
}
|
||||
} else {
|
||||
reset_reports();
|
||||
}
|
||||
}
|
||||
|
||||
void x52d_mouse_handler_init(void)
|
||||
{
|
||||
vkm_result rc;
|
||||
|
||||
rc = vkm_init(&mouse_context);
|
||||
if (rc != VKM_SUCCESS) {
|
||||
PINELOG_ERROR(_("Error %d creating X52 virtual mouse"), rc);
|
||||
return;
|
||||
}
|
||||
|
||||
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_handler_exit(void)
|
||||
{
|
||||
x52d_mouse_thread_control(false);
|
||||
vkm_exit(mouse_context);
|
||||
mouse_context = NULL;
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "build-config.h"
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
|
@ -15,110 +15,102 @@
|
|||
|
||||
#define PINELOG_MODULE X52D_MOD_MOUSE
|
||||
#include "pinelog.h"
|
||||
#include "x52d_config.h"
|
||||
#include "x52d_const.h"
|
||||
#include "x52d_mouse.h"
|
||||
#include <daemon/config.h>
|
||||
#include <daemon/constants.h>
|
||||
#include <daemon/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);
|
||||
}
|
||||
|
||||
/* The following tests are dependent on the values in mouse.c */
|
||||
static void test_mouse_speed_negative(void **state)
|
||||
{
|
||||
int orig_mouse_delay = mouse_delay;
|
||||
int orig_mouse_mult = mouse_mult;
|
||||
|
||||
(void)state;
|
||||
x52d_cfg_set_Mouse_Speed(-1);
|
||||
assert_int_equal(mouse_delay, orig_mouse_delay);
|
||||
assert_int_equal(mouse_mult, orig_mouse_mult);
|
||||
assert_int_equal(mouse_sensitivity, 14);
|
||||
}
|
||||
|
||||
/* 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);
|
||||
assert_int_equal(mouse_sensitivity, 14);
|
||||
}
|
||||
|
||||
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);
|
||||
assert_int_equal(mouse_sensitivity, 25);
|
||||
}
|
||||
|
||||
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);
|
||||
assert_int_equal(mouse_sensitivity, 100);
|
||||
}
|
||||
|
||||
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);
|
||||
assert_int_equal(mouse_sensitivity, 125);
|
||||
}
|
||||
|
||||
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);
|
||||
assert_int_equal(mouse_sensitivity, 350);
|
||||
}
|
||||
|
||||
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);
|
||||
assert_int_equal(mouse_sensitivity, 500);
|
||||
}
|
||||
|
||||
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);
|
||||
assert_int_equal(mouse_mult, orig_mouse_mult);
|
||||
assert_int_equal(mouse_sensitivity, 500);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Name ID map - needed to map module/loglevel names to numeric v alues
|
||||
*
|
||||
* Copyright (C) 2026 Nirenjan Krishnan <nirenjan@nirenjan.org>
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "name-id-map.h"
|
||||
#include "module-map.h"
|
||||
|
||||
static int map_get_id(const struct name_id_map *map, const char *string)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; map[i].name != NULL; i++) {
|
||||
if (strcasecmp(map[i].name, string) == 0) {
|
||||
return map[i].id;
|
||||
}
|
||||
}
|
||||
|
||||
// We've broken out of the loop, return the current ID
|
||||
return map[i].id;
|
||||
}
|
||||
|
||||
static const char *map_get_name(const struct name_id_map *map, int id)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; map[i].name != NULL; i++) {
|
||||
if (map[i].id == id) {
|
||||
return map[i].name;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int lookup_module_by_name(const char *name)
|
||||
{
|
||||
return map_get_id(module_map, name);
|
||||
}
|
||||
|
||||
const char * lookup_module_by_id(int id)
|
||||
{
|
||||
return map_get_name(module_map, id);
|
||||
}
|
||||
|
||||
int lookup_level_by_name(const char *name)
|
||||
{
|
||||
return map_get_id(loglevel_map, name);
|
||||
}
|
||||
|
||||
const char * lookup_level_by_id(int id)
|
||||
{
|
||||
return map_get_name(loglevel_map, id);
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Name ID map - needed to map module/loglevel names to numeric v alues
|
||||
*
|
||||
* Copyright (C) 2026 Nirenjan Krishnan <nirenjan@nirenjan.org>
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#ifndef NAME_ID_MAP_H
|
||||
#define NAME_ID_MAP_H
|
||||
|
||||
struct name_id_map {
|
||||
char *name;
|
||||
int id;
|
||||
};
|
||||
|
||||
extern const struct name_id_map module_map[];
|
||||
extern const struct name_id_map loglevel_map[];
|
||||
|
||||
#endif // !defined NAME_ID_MAP_H
|
||||
|
|
@ -13,11 +13,11 @@
|
|||
|
||||
#define PINELOG_MODULE X52D_MOD_NOTIFY
|
||||
#include "pinelog.h"
|
||||
#include "x52d_const.h"
|
||||
#include "x52d_notify.h"
|
||||
#include "x52d_client.h"
|
||||
#include "x52dcomm.h"
|
||||
#include "x52dcomm-internal.h"
|
||||
#include <daemon/constants.h>
|
||||
#include <daemon/notify.h>
|
||||
#include <daemon/client.h>
|
||||
#include <libx52/x52dcomm.h>
|
||||
#include <daemon/x52dcomm-internal.h>
|
||||
|
||||
static pthread_t notify_thr;
|
||||
static pthread_t notify_listen;
|
||||
|
|
@ -52,15 +52,7 @@ static int listen_notify(const char *notify_sock_path)
|
|||
return -1;
|
||||
}
|
||||
|
||||
/* Cleanup any existing socket */
|
||||
unlink(local.sun_path);
|
||||
if (bind(sock_fd, (struct sockaddr *)&local, (socklen_t)len) < 0) {
|
||||
/* Failure binding socket */
|
||||
PINELOG_ERROR(_("Error binding to notification socket: %s"), strerror(errno));
|
||||
goto listen_failure;
|
||||
}
|
||||
|
||||
if (listen(sock_fd, X52D_MAX_CLIENTS) < 0) {
|
||||
if (x52d_listen_socket(&local, len, sock_fd) < 0) {
|
||||
PINELOG_ERROR(_("Error listening on notification socket: %s"), strerror(errno));
|
||||
goto listen_failure;
|
||||
}
|
||||
|
|
@ -79,47 +71,35 @@ static void * x52_notify_thr(void * param)
|
|||
char buffer[X52D_BUFSZ];
|
||||
uint16_t bufsiz;
|
||||
int rc;
|
||||
(void)param;
|
||||
|
||||
for (;;) {
|
||||
read_pipe_size:
|
||||
rc = read(notify_pipe[0], &bufsiz, sizeof(bufsiz));
|
||||
if (rc < 0) {
|
||||
if (errno == EINTR) {
|
||||
goto read_pipe_size;
|
||||
} else {
|
||||
PINELOG_ERROR(_("Error %d reading from pipe: %s"),
|
||||
errno, strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
rc = read(notify_pipe[0], &bufsiz, sizeof(bufsiz));
|
||||
} while (rc < 0 && errno == EINTR);
|
||||
if (rc < 0) {
|
||||
PINELOG_ERROR(_("Error %d reading from pipe: %s"),
|
||||
errno, strerror(errno));
|
||||
// Error condition, try again
|
||||
continue;
|
||||
}
|
||||
|
||||
read_pipe_data:
|
||||
rc = read(notify_pipe[0], buffer, bufsiz);
|
||||
if (rc < 0) {
|
||||
if (errno == EINTR) {
|
||||
goto read_pipe_data;
|
||||
} else {
|
||||
PINELOG_ERROR(_("Error %d reading from pipe: %s"),
|
||||
errno, strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
rc = read(notify_pipe[0], buffer, bufsiz);
|
||||
} while (rc < 0 && errno == EINTR);
|
||||
if (rc < 0) {
|
||||
PINELOG_ERROR(_("Error %d reading from pipe: %s"),
|
||||
errno, strerror(errno));
|
||||
// Error condition, try again
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int i = 0; i < X52D_MAX_CLIENTS; i++) {
|
||||
// Broadcast to every connected client
|
||||
if (client_fd[i] != INVALID_CLIENT) {
|
||||
write_client_notification:
|
||||
rc = write(client_fd[i], buffer, bufsiz);
|
||||
if (rc < 0 && errno == EINTR) {
|
||||
goto write_client_notification;
|
||||
}
|
||||
do {
|
||||
rc = write(client_fd[i], buffer, bufsiz);
|
||||
} while (rc < 0 && errno == EINTR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -167,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);
|
||||
|
|
@ -193,10 +193,9 @@ A side effect of this is that the client could request a set for any arbitrary
|
|||
section and key pair, and if that pair was not recognized, it would be ignored,
|
||||
but the daemon would still send an `OK` response.
|
||||
|
||||
Finally, this will only set the value within the configuration memory
|
||||
structures, and will not invoke any callback to update the rest of the threads
|
||||
or device state. The client will need to call the `apply` subcommand to actually
|
||||
invoke the necessary callbacks.
|
||||
This will set the value within the configuration memory structures, and will
|
||||
immediately invoke the relevant callback to update the rest of the threads or
|
||||
device state.
|
||||
|
||||
\b Arguments
|
||||
|
||||
|
|
@ -221,22 +220,6 @@ invoke the necessary callbacks.
|
|||
ERR\0Error 22 setting 'led.fire'='none': Invalid argument\0
|
||||
```
|
||||
|
||||
# Apply configuration
|
||||
|
||||
The `config apply` command will invoke all the callbacks and ensure that the
|
||||
configuration is applied to the running state.
|
||||
|
||||
\b Arguments
|
||||
|
||||
- `config`
|
||||
- `apply`
|
||||
|
||||
\b Returns
|
||||
|
||||
- `OK`
|
||||
- `config`
|
||||
- `apply`
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
|
|
@ -259,12 +242,14 @@ user to fine tune the logging while the daemon is running.
|
|||
of modules is below:
|
||||
|
||||
- \c Config
|
||||
- \c Cllient
|
||||
- \c Clock
|
||||
- \c Command
|
||||
- \c Device
|
||||
- \c IO
|
||||
- \c LED
|
||||
- \c Mouse
|
||||
- \c Notify
|
||||
|
||||
# Logging levels
|
||||
|
||||
|
|
|
|||
|
|
@ -134,7 +134,11 @@ class Test:
|
|||
with open(daemon_cmdline[4], 'w', encoding='utf-8'):
|
||||
pass
|
||||
|
||||
self.daemon = subprocess.Popen(daemon_cmdline) # pylint: disable=consider-using-with
|
||||
env = os.environ.copy()
|
||||
# Uninstalled build: us.x52l lives next to the x52d binary (see daemon/meson.build).
|
||||
env['X52D_LAYOUT_DIR'] = os.path.dirname(self.program)
|
||||
|
||||
self.daemon = subprocess.Popen(daemon_cmdline, env=env) # pylint: disable=consider-using-with
|
||||
|
||||
print("# Sleeping 2 seconds for daemon to start")
|
||||
time.sleep(2)
|
||||
|
|
|
|||
|
|
@ -82,12 +82,44 @@ LED=128
|
|||
# Enabled controls whether the virtual mouse is enabled or not.
|
||||
Enabled=yes
|
||||
|
||||
# Speed is proportional to the speed of updates to the virtual mouse
|
||||
# Sensitivity is the sensitivity percentage of the virtual mouse. This
|
||||
# replaces the old Speed option, and is a percentage value by which to
|
||||
# scale the input. The sensitivity can vary from 10% to 500%.
|
||||
Sensitivity=100
|
||||
|
||||
# DEPRECATED: Speed is proportional to the speed of updates to the virtual mouse
|
||||
# This used a calculation with delays and multiplication factors to simulate
|
||||
# the mouse moves, but it felt choppy at lower speeds.
|
||||
Speed=0
|
||||
|
||||
# ReverseScroll reverses the direction of the virtual scroll wheel
|
||||
ReverseScroll=no
|
||||
|
||||
# Isometric mode controls if the mouse movement is computed based on
|
||||
# both X and Y movements. If enabled, the behavior is similar to the
|
||||
# mouse nubs found on some laptops. Otherwise, the X and Y movements
|
||||
# are independent of each other.
|
||||
IsometricMode=no
|
||||
|
||||
# Curve factor controls the speed curve in an exponential manner, so
|
||||
# that the user can get finer control at the lower end of motion, while
|
||||
# increasing speeds at the upper end. Values range from 1-5, with the
|
||||
# following descriptions. Values are clamped in this range.
|
||||
# 1: Linear motion - no curve
|
||||
# 2: Soft curve: slight dampening in the lower ranges
|
||||
# 3: Standard: Feels like a Thinkpad
|
||||
# 4: Precision: heavy dampening in lower ranges, high speed elsewhere
|
||||
# 5: Aggressive: "sniper" mode in the lower rnages, "flick" elsewhere
|
||||
CurveFactor=3
|
||||
|
||||
# Deadzone is a configurable value from 0-11, with 0 being no deadzone
|
||||
# and the deadzone size increasing with increasing values. This is useful
|
||||
# when there is a loose thumbstick and you want to restrict the motion
|
||||
# when there's no user input. A deadzone of 0 is perfectly fine for a
|
||||
# new joystick, but keep in mind that the higher values will require
|
||||
# you to push more to get any motion out of the virtual mouse.
|
||||
Deadzone=0
|
||||
|
||||
######################################################################
|
||||
# Profiles - only valid on Linux
|
||||
######################################################################
|
||||
|
|
@ -107,8 +139,12 @@ ClutchEnabled=no
|
|||
# be held down to remain in clutch mode.
|
||||
ClutchLatched=no
|
||||
|
||||
# KeyboardLayout is a basename only (alphanumeric, underscore, hyphen), not a path.
|
||||
# Resolves to $datadir/x52d/<basename>.x52l; default us uses the installed us.x52l pack.
|
||||
KeyboardLayout=us
|
||||
|
||||
##################
|
||||
#X52 Input Servic#
|
||||
#Version 0.2.2 #
|
||||
#Version 0.3.3 #
|
||||
#OS: Linux #
|
||||
##################
|
||||
|
|
|
|||
|
|
@ -0,0 +1,68 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Generate the module name to map for use by the daemon"""
|
||||
|
||||
import os.path
|
||||
import sys
|
||||
|
||||
import module_defs
|
||||
|
||||
def main():
|
||||
|
||||
if len(sys.argv) != 3:
|
||||
print("Usage: {sys.argv[0]} <output-header> <output-source>", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
with open(sys.argv[1], 'w', encoding='utf-8') as out_fd:
|
||||
# Generate the header
|
||||
print("// Autogenerated module/loglevel header - DO NOT EDIT\n",
|
||||
file=out_fd)
|
||||
|
||||
include_guard = os.path.basename(sys.argv[1]).replace('-', '_').replace('.', '_').upper()
|
||||
print(f"#ifndef {include_guard}", file=out_fd)
|
||||
print(f"#define {include_guard}\n", file=out_fd)
|
||||
|
||||
for mod in module_defs.Module:
|
||||
print(f"#define X52D_MOD_{mod.name} {mod.value}", file=out_fd)
|
||||
|
||||
print(f"#define X52D_MOD_GLOBAL 0xFF", file=out_fd)
|
||||
print(f"#define X52D_MOD_MAX {len(module_defs.Module)}\n", file=out_fd)
|
||||
|
||||
print(f"int lookup_module_by_name(const char *name);", file=out_fd)
|
||||
print(f"const char * lookup_module_by_id(int id);", file=out_fd)
|
||||
print(f"int lookup_level_by_name(const char *name);", file=out_fd)
|
||||
print(f"const char * lookup_level_by_id(int id);", file=out_fd)
|
||||
|
||||
print(f"\n#endif // !defined {include_guard}", file=out_fd)
|
||||
|
||||
with open(sys.argv[2], 'w', encoding='utf-8') as out_fd:
|
||||
print("// Autogenerated module/loglevel tables - DO NOT EDIT\n",
|
||||
file=out_fd)
|
||||
|
||||
print('#include <stddef.h>', file=out_fd)
|
||||
print('#include <limits.h>\n', file=out_fd)
|
||||
|
||||
print(f'#include "{os.path.basename(sys.argv[1])}"', file=out_fd)
|
||||
print('#include "name-id-map.h"\n', file=out_fd)
|
||||
|
||||
print('const struct name_id_map module_map[] = {', file=out_fd)
|
||||
for mod in module_defs.Module:
|
||||
print(f' {{ "{mod.name.lower()}", {mod.value} }},', file=out_fd)
|
||||
|
||||
print(' { NULL, INT_MAX }', file=out_fd)
|
||||
print('};\n', file=out_fd)
|
||||
|
||||
print('const struct name_id_map loglevel_map[] = {', file=out_fd)
|
||||
for level in module_defs.LogLevel:
|
||||
if level == module_defs.LogLevel.NOTSET:
|
||||
level_name = 'default'
|
||||
else:
|
||||
level_name = level.name.lower()
|
||||
|
||||
print(f' {{ "{level_name}", {level.value} }},', file=out_fd)
|
||||
|
||||
print(' { NULL, INT_MAX }', file=out_fd)
|
||||
|
||||
print('};\n', file=out_fd)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
@ -0,0 +1,197 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Read the default configuration file, and create ID enums for sections
|
||||
and options.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import configparser
|
||||
import json
|
||||
import os.path
|
||||
|
||||
from collections import defaultdict
|
||||
from itertools import count
|
||||
from pprint import pprint
|
||||
|
||||
class ConfigToEnum:
|
||||
"""ConfigToEnum scans a configuration file, and dumps the secions and
|
||||
options within a section as Python Enums"""
|
||||
|
||||
REGISTRY_COMMENT = """The configuration registry is a historic record of
|
||||
all configuration identifiers. Do NOT edit this file manually, or else, the
|
||||
communication protocol may break."""
|
||||
|
||||
def __init__(self, cfg_file, config_ids):
|
||||
"""Initialize the object"""
|
||||
self.config = configparser.ConfigParser(default_section=None, interpolation=None)
|
||||
self.config.optionxform = str
|
||||
self.config.read(cfg_file)
|
||||
|
||||
self.registry_file = config_ids
|
||||
|
||||
try:
|
||||
with open(self.registry_file, encoding='utf-8') as regfd:
|
||||
self.config_ids = json.load(regfd)
|
||||
except Exception:
|
||||
# On any error, ignore it and start with a clean slate
|
||||
self.config_ids = {}
|
||||
|
||||
self.sections = {}
|
||||
self.options = {}
|
||||
|
||||
def parse(self):
|
||||
"""Parse the config object and assign IDs"""
|
||||
self._parse_sections()
|
||||
for section in self.config.sections():
|
||||
self._parse_options(section)
|
||||
|
||||
def _parse_sections(self):
|
||||
"""Assign IDs to each section"""
|
||||
sections = {}
|
||||
unassigned = []
|
||||
for section in self.config.sections():
|
||||
section = section.upper()
|
||||
section_id = self.config_ids.get('sections', {}).get(section)
|
||||
if section_id is None:
|
||||
unassigned.append(section)
|
||||
else:
|
||||
sections[section] = section_id
|
||||
|
||||
if not sections:
|
||||
counter = count(1)
|
||||
else:
|
||||
counter = count(max(sections.values()) + 1)
|
||||
|
||||
sections.update({k:v for k, v in zip(unassigned, counter)})
|
||||
|
||||
orig_sections = self.config_ids.get('sections', {})
|
||||
sections.update({k:v for k, v in orig_sections.items() if k not in sections})
|
||||
|
||||
self.sections = sections
|
||||
|
||||
def _parse_options(self, section):
|
||||
options = {}
|
||||
unassigned = []
|
||||
for option in self.config.options(section):
|
||||
option = option.upper()
|
||||
section = section.upper()
|
||||
option_id = self.config_ids.get('options', {}).get(section, {}).get(option)
|
||||
if option_id is None:
|
||||
unassigned.append(option)
|
||||
else:
|
||||
options[option] = option_id
|
||||
|
||||
if not options:
|
||||
counter = count(1)
|
||||
else:
|
||||
counter = count(max(options.values()) + 1)
|
||||
|
||||
options.update({k:v for k, v in zip(unassigned, counter)})
|
||||
orig_options = self.config_ids.get('options', {}).get(section, {})
|
||||
|
||||
# Make sure that we have all the entries already
|
||||
options.update({k:v for k, v in orig_options.items() if k not in options})
|
||||
|
||||
self.options[section] = options
|
||||
|
||||
def save_registry(self):
|
||||
"""Save the generated registry"""
|
||||
registry = {
|
||||
"_comment": self.REGISTRY_COMMENT,
|
||||
"sections": self.sections,
|
||||
"options": self.options,
|
||||
}
|
||||
|
||||
with open(self.registry_file, 'w', encoding='utf-8') as regfd:
|
||||
json.dump(registry, regfd, indent=4)
|
||||
|
||||
def generate_c_definitions(self, output_header, output_source):
|
||||
"""Generate the C definitions"""
|
||||
with open(output_header, 'w', encoding='utf-8') as out_fd:
|
||||
include_guard = os.path.basename(output_header).replace('-', '_').replace('.', '_').upper()
|
||||
|
||||
print("// Autogenerated config identifiers - DO NOT EDIT\n", file=out_fd)
|
||||
print(f"#ifndef {include_guard}", file=out_fd)
|
||||
print(f"#define {include_guard}", file=out_fd)
|
||||
print(file=out_fd)
|
||||
|
||||
max_sec_val = max(self.sections.values()) + 1
|
||||
max_opt_val_global = 0
|
||||
for section, value in self.sections.items():
|
||||
print(f"#define CFG_SECTION_{section} {value}", file=out_fd)
|
||||
max_opt_val = max(self.options[section].values()) + 1
|
||||
max_opt_val_global = max(max_opt_val, max_opt_val_global)
|
||||
|
||||
for option, value in self.options[section].items():
|
||||
print(f"#define CFG_OPTION_{section}_{option} {value}", file=out_fd)
|
||||
|
||||
print(f"#define CFG_OPTION_{section}_MAX_OPTIONS {max_opt_val}\n", file=out_fd)
|
||||
|
||||
print(f"#define CFG_SECTION_MAX {max_sec_val}\n", file=out_fd)
|
||||
print(f"#define CFG_SECTION_MAX_OPT_VAL {max_opt_val_global}\n", file=out_fd)
|
||||
|
||||
print("extern const char * section_names[CFG_SECTION_MAX];", file=out_fd)
|
||||
print("extern const char * option_names[CFG_SECTION_MAX][CFG_SECTION_MAX_OPT_VAL];", file=out_fd)
|
||||
|
||||
print(f"#endif // !defined {include_guard}", file=out_fd)
|
||||
|
||||
with open(output_source, 'w', encoding='utf-8') as out_fd:
|
||||
print("// Autogenerated config string table - DO NOT EDIT\n", file=out_fd)
|
||||
print(f'#include "{os.path.basename(output_header)}"', file=out_fd)
|
||||
|
||||
print("const char * section_names[CFG_SECTION_MAX] = {", file=out_fd)
|
||||
for section, value in self.sections.items():
|
||||
print(f' [{value}] = "{section.lower()}",', file=out_fd)
|
||||
print("};\n", file=out_fd)
|
||||
|
||||
print("const char * options_names[CFG_SECTION_MAX][CFG_SECTION_MAX_OPT_VAL] = {", file=out_fd)
|
||||
for section, value in self.sections.items():
|
||||
print(f' [{value}] =', '{', file=out_fd)
|
||||
for option, value in self.options[section].items():
|
||||
print(f' [{value}] = "{option.lower()}",', file=out_fd)
|
||||
print(' },', file=out_fd)
|
||||
print("};\n", file=out_fd)
|
||||
|
||||
|
||||
def generate_py_definitions(self, output_file):
|
||||
"""Generate the Python definitions"""
|
||||
try:
|
||||
out_fd = open(output_file, 'w', encoding='utf-8')
|
||||
|
||||
print("'''Autogenerated config identifiers from x52d.conf'''", file=out_fd)
|
||||
print("# DO NOT EDIT\n", file=out_fd)
|
||||
print("from enum import Enum", file=out_fd)
|
||||
|
||||
print("\nclass Section(Enum):", file=out_fd)
|
||||
print(" '''Section identifiers'''", file=out_fd)
|
||||
for section, value in self.sections.items():
|
||||
print(f" {section} = {value}", file=out_fd)
|
||||
|
||||
for section in self.sections.keys():
|
||||
print(f"\nclass {section}(Enum):", file=out_fd)
|
||||
print(f" '''Section {section} identifiers'''", file=out_fd)
|
||||
for option, value in self.options[section].items():
|
||||
print(f" {option} = {value}", file=out_fd)
|
||||
|
||||
finally:
|
||||
out_fd.close()
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Generate C enum and python enum for config")
|
||||
parser.add_argument('input_file')
|
||||
parser.add_argument('registry')
|
||||
parser.add_argument('output_c_header')
|
||||
parser.add_argument('output_c_strings')
|
||||
parser.add_argument('output_py_defs')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
c2e = ConfigToEnum(args.input_file, args.registry)
|
||||
c2e.parse()
|
||||
c2e.save_registry()
|
||||
|
||||
c2e.generate_c_definitions(args.output_c_header, args.output_c_strings)
|
||||
c2e.generate_py_definitions(args.output_py_defs)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - Mouse driver
|
||||
*
|
||||
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define PINELOG_MODULE X52D_MOD_MOUSE
|
||||
#include "pinelog.h"
|
||||
#include "x52d_config.h"
|
||||
#include "x52d_const.h"
|
||||
#include "x52d_mouse.h"
|
||||
|
||||
// Mouse speed is the delay in microseconds between subsequent mouse reports
|
||||
#define DEFAULT_MOUSE_DELAY 70000
|
||||
#define MOUSE_DELAY_DELTA 5000
|
||||
#define MOUSE_DELAY_MIN 10000
|
||||
#define MAX_MOUSE_MULT 5
|
||||
|
||||
volatile int mouse_delay = DEFAULT_MOUSE_DELAY;
|
||||
volatile int mouse_mult = MOUSE_MULT_FACTOR;
|
||||
volatile int mouse_scroll_dir = 1;
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
void x52d_cfg_set_Mouse_Speed(int speed)
|
||||
{
|
||||
int new_delay;
|
||||
int new_mult;
|
||||
|
||||
int max_base_speed = (DEFAULT_MOUSE_DELAY - MOUSE_DELAY_MIN) / MOUSE_DELAY_DELTA;
|
||||
int max_speed = max_base_speed + MAX_MOUSE_MULT * MOUSE_MULT_FACTOR;
|
||||
|
||||
if (speed < 0 || speed > max_speed) {
|
||||
PINELOG_INFO(_("Ignoring mouse speed %d outside supported range (0-%d)"),
|
||||
speed, max_speed);
|
||||
return;
|
||||
} else if (speed <= max_base_speed) {
|
||||
new_delay = DEFAULT_MOUSE_DELAY - speed * MOUSE_DELAY_DELTA;
|
||||
new_mult = MOUSE_MULT_FACTOR;
|
||||
} else {
|
||||
// speed between max_base_speed & max_speed
|
||||
new_delay = MOUSE_DELAY_MIN;
|
||||
new_mult = MOUSE_MULT_FACTOR + (speed - max_base_speed);
|
||||
}
|
||||
|
||||
PINELOG_DEBUG(_("Setting mouse speed to %d (delay %d ms, multiplier %f)"),
|
||||
speed, new_delay / 1000, new_mult / (double)MOUSE_MULT_FACTOR);
|
||||
mouse_delay = new_delay;
|
||||
mouse_mult = new_mult;
|
||||
}
|
||||
|
||||
void x52d_cfg_set_Mouse_ReverseScroll(bool enabled)
|
||||
{
|
||||
PINELOG_DEBUG(_("Setting mouse reverse scroll to %s"),
|
||||
enabled ? _("on") : _("off"));
|
||||
|
||||
if (enabled) {
|
||||
mouse_scroll_dir = -1;
|
||||
} else {
|
||||
mouse_scroll_dir = 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,248 +0,0 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - Mouse driver
|
||||
*
|
||||
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "libevdev/libevdev.h"
|
||||
#include "libevdev/libevdev-uinput.h"
|
||||
#include "libx52io.h"
|
||||
|
||||
#include "pinelog.h"
|
||||
#include "x52d_config.h"
|
||||
#include "x52d_const.h"
|
||||
#include "x52d_mouse.h"
|
||||
|
||||
static pthread_t mouse_thr;
|
||||
static bool mouse_thr_enabled = false;
|
||||
|
||||
static struct libevdev_uinput *mouse_uidev;
|
||||
static bool mouse_uidev_created = false;
|
||||
|
||||
static volatile libx52io_report old_report;
|
||||
static volatile libx52io_report new_report;
|
||||
|
||||
static int report_button_change(int button, int index)
|
||||
{
|
||||
int rc = 1;
|
||||
bool old_button = old_report.button[index];
|
||||
bool new_button = new_report.button[index];
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int report_wheel(void)
|
||||
{
|
||||
int rc = 1;
|
||||
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];
|
||||
|
||||
if (scroll_up) {
|
||||
// Scroll up event
|
||||
wheel = 1 * mouse_scroll_dir;
|
||||
} else if (scroll_dn) {
|
||||
// Scroll down event
|
||||
wheel = -1 * mouse_scroll_dir;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int report_axis(int axis, int index)
|
||||
{
|
||||
int rc = 1;
|
||||
|
||||
int axis_val = new_report.axis[index];
|
||||
|
||||
/*
|
||||
* Axis value ranges from 0 to 15, with the default midpoint at 8.
|
||||
* We need to translate this to a range of -7 to +7. Since the midpoint
|
||||
* is slightly off-center, we will shift the values left, and subtract
|
||||
* 15, effectively, giving us a range of -15 to +15. Shifting right again
|
||||
* will reduce the range to -7 to +7, and effectively ignore the reported
|
||||
* values of 7 and 8.
|
||||
*/
|
||||
axis_val = ((axis_val << 1) - 15) >> 1;
|
||||
|
||||
/*
|
||||
* Factor in the multiplicative factor for the axis. This deliberately
|
||||
* uses integer division, since the uinput event only accepts integers.
|
||||
* For the speed purposes, this should be good enough.
|
||||
*/
|
||||
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 rc;
|
||||
}
|
||||
|
||||
static void report_sync(void)
|
||||
{
|
||||
int rc;
|
||||
rc = libevdev_uinput_write_event(mouse_uidev, EV_SYN, SYN_REPORT, 0);
|
||||
if (rc != 0) {
|
||||
PINELOG_ERROR(_("Error writing mouse sync event"));
|
||||
} else {
|
||||
memcpy((void *)&old_report, (void *)&new_report, sizeof(old_report));
|
||||
}
|
||||
}
|
||||
|
||||
static void reset_reports(void)
|
||||
{
|
||||
memset((void *)&old_report, 0, sizeof(old_report));
|
||||
/* Set the default thumbstick values to the mid-point */
|
||||
old_report.axis[LIBX52IO_AXIS_THUMBX] = 8;
|
||||
old_report.axis[LIBX52IO_AXIS_THUMBY] = 8;
|
||||
memcpy((void *)&new_report, (void *)&old_report, sizeof(new_report));
|
||||
}
|
||||
|
||||
static void * x52_mouse_thr(void *param)
|
||||
{
|
||||
bool state_changed;
|
||||
|
||||
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) {
|
||||
report_sync();
|
||||
}
|
||||
|
||||
usleep(mouse_delay);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void x52d_mouse_thr_init(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
PINELOG_TRACE("Initializing virtual mouse driver");
|
||||
rc = pthread_create(&mouse_thr, NULL, x52_mouse_thr, NULL);
|
||||
if (rc != 0) {
|
||||
PINELOG_FATAL(_("Error %d initializing mouse thread: %s"),
|
||||
rc, strerror(rc));
|
||||
}
|
||||
}
|
||||
|
||||
static void x52d_mouse_thr_exit(void)
|
||||
{
|
||||
PINELOG_INFO(_("Shutting down X52 virtual mouse driver thread"));
|
||||
pthread_cancel(mouse_thr);
|
||||
}
|
||||
|
||||
void x52d_mouse_evdev_thread_control(bool enabled)
|
||||
{
|
||||
if (!mouse_uidev_created) {
|
||||
PINELOG_INFO(_("Virtual mouse not created. Ignoring thread state change"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
if (mouse_thr_enabled) {
|
||||
PINELOG_TRACE("Ignoring re-enable mouse thread");
|
||||
return;
|
||||
} else {
|
||||
reset_reports();
|
||||
x52d_mouse_thr_init();
|
||||
}
|
||||
} else {
|
||||
if (!mouse_thr_enabled) {
|
||||
PINELOG_TRACE("Ignoring re-disable mouse thread");
|
||||
return;
|
||||
} else {
|
||||
x52d_mouse_thr_exit();
|
||||
}
|
||||
}
|
||||
mouse_thr_enabled = enabled;
|
||||
}
|
||||
|
||||
void x52d_mouse_report_event(libx52io_report *report)
|
||||
{
|
||||
bool state_changed;
|
||||
if (report) {
|
||||
memcpy((void *)&new_report, report, sizeof(new_report));
|
||||
|
||||
if (!mouse_uidev_created || !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());
|
||||
|
||||
if (state_changed) {
|
||||
report_sync();
|
||||
}
|
||||
} else {
|
||||
reset_reports();
|
||||
}
|
||||
}
|
||||
|
||||
void x52d_mouse_evdev_init(void)
|
||||
{
|
||||
int rc;
|
||||
struct libevdev *dev;
|
||||
|
||||
/* 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 = 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;
|
||||
}
|
||||
}
|
||||
|
||||
void x52d_mouse_evdev_exit(void)
|
||||
{
|
||||
x52d_mouse_evdev_thread_control(false);
|
||||
mouse_uidev_created = false;
|
||||
libevdev_uinput_destroy(mouse_uidev);
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ int x52d_setup_command_sock(const char *sock_path, struct sockaddr_un *remote);
|
|||
const char *x52d_notify_sock_path(const char *sock_path);
|
||||
int x52d_setup_notify_sock(const char *sock_path, struct sockaddr_un *remote);
|
||||
int x52d_set_socket_nonblocking(int sock_fd);
|
||||
int x52d_listen_socket(struct sockaddr_un *local, int len, int sock_fd);
|
||||
void x52d_split_args(int *argc, char **argv, char *buffer, int buflen);
|
||||
|
||||
#endif // !defined X52DCOMM_INTERNAL_H
|
||||
|
|
|
|||
|
|
@ -0,0 +1,111 @@
|
|||
# US QWERTY — main block (letters, digits, punctuation, space, controls)
|
||||
name: us
|
||||
description: US QWERTY main block
|
||||
|
||||
U+0008 BACKSPACE
|
||||
U+0009 TAB
|
||||
U+000A ENTER
|
||||
U+000D ENTER
|
||||
U+001B ESCAPE
|
||||
U+0020 SPACE
|
||||
|
||||
a a
|
||||
b b
|
||||
c c
|
||||
d d
|
||||
e e
|
||||
f f
|
||||
g g
|
||||
h h
|
||||
i i
|
||||
j j
|
||||
k k
|
||||
l l
|
||||
m m
|
||||
n n
|
||||
o o
|
||||
p p
|
||||
q q
|
||||
r r
|
||||
s s
|
||||
t t
|
||||
u u
|
||||
v v
|
||||
w w
|
||||
x x
|
||||
y y
|
||||
z z
|
||||
|
||||
A SHIFT+a
|
||||
B SHIFT+b
|
||||
C SHIFT+c
|
||||
D SHIFT+d
|
||||
E SHIFT+e
|
||||
F SHIFT+f
|
||||
G SHIFT+g
|
||||
H SHIFT+h
|
||||
I SHIFT+i
|
||||
J SHIFT+j
|
||||
K SHIFT+k
|
||||
L SHIFT+l
|
||||
M SHIFT+m
|
||||
N SHIFT+n
|
||||
O SHIFT+o
|
||||
P SHIFT+p
|
||||
Q SHIFT+q
|
||||
R SHIFT+r
|
||||
S SHIFT+s
|
||||
T SHIFT+t
|
||||
U SHIFT+u
|
||||
V SHIFT+v
|
||||
W SHIFT+w
|
||||
X SHIFT+x
|
||||
Y SHIFT+y
|
||||
Z SHIFT+z
|
||||
|
||||
1 1
|
||||
2 2
|
||||
3 3
|
||||
4 4
|
||||
5 5
|
||||
6 6
|
||||
7 7
|
||||
8 8
|
||||
9 9
|
||||
0 0
|
||||
! SHIFT+1
|
||||
@ SHIFT+2
|
||||
# SHIFT+3
|
||||
$ SHIFT+4
|
||||
% SHIFT+5
|
||||
^ SHIFT+6
|
||||
& SHIFT+7
|
||||
* SHIFT+8
|
||||
( SHIFT+9
|
||||
) SHIFT+0
|
||||
_ SHIFT+MINUS
|
||||
+ SHIFT+EQUAL
|
||||
|
||||
[ LEFT_BRACKET
|
||||
{ SHIFT+LEFT_BRACKET
|
||||
] RIGHT_BRACKET
|
||||
} SHIFT+RIGHT_BRACKET
|
||||
\ BACKSLASH
|
||||
| SHIFT+BACKSLASH
|
||||
|
||||
; SEMICOLON
|
||||
: SHIFT+SEMICOLON
|
||||
' APOSTROPHE
|
||||
" SHIFT+APOSTROPHE
|
||||
` GRAVE
|
||||
~ SHIFT+GRAVE
|
||||
|
||||
, COMMA
|
||||
< SHIFT+COMMA
|
||||
. PERIOD
|
||||
> SHIFT+PERIOD
|
||||
/ SLASH
|
||||
? SHIFT+SLASH
|
||||
|
||||
- MINUS
|
||||
= EQUAL
|
||||
|
|
@ -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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue