Compare commits

...

77 Commits

Author SHA1 Message Date
nirenjan aa555f5e66 doc: Update INSTALL.md with Meson instructions
This change removes the old autotools based documentation and switches
to using Meson exclusively. In addition, the build action will build
using meson compile and meson test instead of ninja/ninja test.
2026-03-22 19:13:00 -07:00
nirenjan 842e7e53ed fix: Scope the list of man pages installed
The previous commit to address #63 added a number of extra unwanted man
pages. This commit addresses that and scopes down the actual installed
man pages to only x52cli and x52bugreport.

GH-Issue: #63
GH-Issue-URL: https://github.com/nirenjan/libx52/issues/63
2026-03-22 15:59:35 -07:00
nirenjan dfbe3e6d21 fix: Enable install for libx52dcomm
The change to build using Meson broke the install target, causing x52ctl
and x52d to fail with a missing libx52dcomm library. This was fixed by
setting `install: true` in the library call.

In addition, several features were used that were leftover from my
earlier attempts to migrate to Meson, but targeted older Meson versions.
Some of these features were deprecated in newer Meson versions, and
therefore, cause warnings to show up during meson setup.

GH-Issue: #63
GH-Issue-URL: https://github.com/nirenjan/libx52/issues/63
2026-03-22 15:47:13 -07:00
nirenjan 732bc21b65 fix: Address compiler warnings
When enabling --warnlevel=3 during Meson setup, the build threw up
several warnings, mostly related to either unused parameters, or
sometimes an integer type mismatch. This commit addresses all of those
changes and ensures that the build does not contain any unnecessary
warnings.
2026-03-19 00:10:18 -07:00
nirenjan 3c1abd57d5 fix(pinelog): Cleanup unused parameter warnings
With a recent enough version, the compiler reports a number of unused
parameter warnings when Meson is configured with `--warnlevel=3`. This
commit addresses those warnings.
2026-03-18 22:25:26 -07:00
nirenjan b0b457d14e doc: Update Doxyfile.in to 1.9.8 syntax
Doxygen in Ubuntu 24.04 has removed several features, and introduced a
few new ones. This commit updates the Doxyfile.in template to use the
current syntax.

CI: [skip ci]
2026-03-18 00:54:55 -07:00
nirenjan ae077dbed8 fix: Add library version for libx52dcomm
This change adds the library version for libx52dcomm, so that it can be
versioned if necessary.
2026-03-17 16:43:54 -07:00
nirenjan d29be6213f doc: Add badges for SonarQube and CodeCov
CI: [skip ci] [skip doxy]
2026-03-16 23:22:23 -07:00
nirenjan 273ed22f8e refactor(x52ctl): Break out interactive mode
SonarQube keeps complaining about issues with cognitive complexity and
bad practices, this commit addresses those commits.
2026-03-16 23:07:39 -07:00
nirenjan cdb00739ca fix(x52ctl): Remove unnecessary goto statements
The `goto cleanup` statements are not really needed, and can be safely
replaced with `break` statements in x52ctl.
2026-03-16 22:50:11 -07:00
nirenjan 08a6b0a736 build: Add coverage support with codecov.io
This change adds code coverage to the Meson builds, and reports them to
codecov.io, to help keep the code coverage high.
2026-03-16 22:39:12 -07:00
nirenjan 45d561e0d8 test: Fix SMP test in libx52util
The SMP test was incorrectly calculating the codepoints, resulting in a
test of the codepoints inside the BMP, instead of the individual SMPs.
2026-03-16 22:23:00 -07:00
nirenjan b626a9367f fix(pinelog): Mark benchmark tests as such in Meson
Prior to this change, the pinelog benchmark suite was running as a
regular test, however, this is not ideal since it can result in timing
issues and giving false data to the runners.

This change explicitly marks them as benchmarks, so they can run using
`meson test --benchmark`
2026-03-16 15:56:38 -07:00
nirenjan a1098bc134 feat: Add benchmarking for libx52util-bmp-test 2026-03-16 11:16:41 -07:00
nirenjan 569902be76 fix: Add support for Automake tests
In order to continue to support Automake builds, we need to update the
source to comply with C90 standards, as well as ignoring the warnings in
the test binary.
2026-03-16 10:45:02 -07:00
nirenjan 0cb137bbe0 feat: Handle the entire BMP in libx52util
Prior to this change, the libx52util_convert_utf8_string function had a
limited set of characters that it would convert to the MFD character
map, these characters were derived from the x52_char_map.cfg file.
However, this is a tiny subset of the actual supported characters in the
Basic Multilingual Plane (BMP), since many characters in the BMP can be
normalized to a different character (or character sequence) that has a
corresponding glyph on the X52 MFD.

One example of this is the half-width Katakana characters which are
mapped in the display, however the corresponding full-width characters
were not explicitly mapped. With this commit, the generator script now
automatically detects that the half-width characters can be normalized
to the corresponding full width forms, and maps the full width forms
back to the correct characters on the MFD.

A second benefit of this change is that the MFD can now show characters
that would otherwise never be seen, for example, the 3/4 symbol or 5/8
symbol have no corresponding glyph in the MFD, but they can be
translated to the sequence `3` `/` `4`, giving us much more flexibility
on the characters that can actually be displayed.

Finally, with this change, the function also maps missing or unsupported
characters to the box character (0xDB in the display), making it clearer
that there was something there that could not be displayed. Earlier, it
would have simply skipped that character.
2026-03-16 10:18:16 -07:00
nirenjan 899ea57bf7 doc: Update ChangeLog.md to list Meson bugs
Because several bugs were found in the Meson build infrastructure since
the release of v0.3.3, I've had to keep the existing Autotools
infrastructure running for a bit longer while I address the bugs. As of
this commit, Autotools is still the preferred way to build libx52,
though it is technically deprecated and will be replaced with Meson as
the source of truth.

CI: [skip ci] [skip doxy]
2026-03-13 09:54:23 -07:00
nirenjan e1e020a4f5 fix: Handle test dependency on x52ctl
Prior to this change, running meson test without running meson compile
first would cause the daemon communication tests to fail since it
wouldn't find the x52ctl binary. While I could rewrite the test runner
to directly talk to the daemon, it's faster to just ensure the
dependencies are setup correctly.
2026-03-13 09:27:51 -07:00
nirenjan 7cbf091dc7 fix: Ensure man pages and docs are installed via Meson 2026-03-12 16:19:49 -07:00
nirenjan 9d180531b9 fix: Treat NLS=auto as enabled
Prior to this change, the check was for an explicit -Dnls=enabled,
however, if the option was never set, it defaulted to disabled. With
this change, unless explicitly disabled, the Meson build system will
automatically build with NLS support.
2026-03-12 15:53:30 -07:00
nirenjan 74229b391d fix: Update pkgconfig to use correct details 2026-03-12 15:35:11 -07:00
nirenjan 5f8177f16b fix: Use correct paths for local,sysconf,localstate dirs 2026-03-12 15:17:00 -07:00
nirenjan c5ec15231f feat: Add meson support for configuring udev rules dir 2026-03-12 14:45:08 -07:00
nirenjan 33bbafe970 fix: Fix systemd service installation in Meson
The Meson build change broke the systemd service file installation. This
commit fixes that, while retaining Autotools support.
2026-03-12 13:21:59 -07:00
nirenjan e9a806a6a2 fix: Update meson.build to handle localization
The previous version of the Meson build files did not handle the po
directory correctly, and lost a lot of information. As part of the
migration away from Autotools, this is one more item that needs to be
checked off.

CI: [ci skip] [doxy skip]
2026-03-12 12:48:23 -07:00
nirenjan ad30bfff7b fix: Typo in release changelog generation 2026-03-12 12:10:26 -07:00
nirenjan cccb561020 feat: Add Changelog generation script for releases
This change automates the release workflow and reduces manual touch.
2026-03-12 12:06:24 -07:00
nirenjan 6743c60dfd doc: Update for version 0.3.3 2026-03-12 11:34:54 -07:00
nirenjan b4ec8d4629 build: Migrate to meson build
Meson is a far more robust build framework, compared to autotools. This
greatly simplifies adding new features, since it's far easier to
maintain a set of meson.build files vs the autotools mishmash.

DEPRECATION NOTICE: Autotools based build is deprecated and will be
removed in the future.
2026-03-12 10:20:01 -07:00
nirenjan 3fb0d72124
Merge pull request #62 from nirenjan/remove-inih-dependency
build!: Update build to use system inih
2026-03-12 09:57:44 -07:00
nirenjan 74fe559f4a build!: Update build to use system inih
When the x52d daemon was originally implemented, the inih library was
not bundled with any major distribution, and had to be compiled from
source everytime. However, with recent distributions (starting with
Ubuntu 22.04 LTS), this is no longer an issue, and inih is available in
the distro package manager. As a result, there is no longer a need to
vendor the inih sources with thiis repository.

However, as a result of this change, third party packaging scripts such
as those on the AUR or other similar registries that directly query the
git repository will fail unless they update the dependencies.

BREAKING CHANGE: Packaging scripts (AUR, etc.) need dependency update
2026-03-12 09:50:58 -07:00
nirenjan b6e61fc54e fix: Avoid backward jumps using goto
SonarQube cloud identified a maintainability issue based on MISRA C
guidelines that prohibit backward jumps. While not a mandatory fix, it
helps to clean up the codebase and improves readability.

Ref. MISRA C:2012, 15.2 - The goto statement shall jump to a label
declared later in the same function.
2026-03-12 08:34:58 -07:00
nirenjan e479e338a2
Merge pull request #61 from nirenjan/dependabot/github_actions/actions/upload-pages-artifact-4
build(deps): bump actions/upload-pages-artifact from 3 to 4
2026-03-10 08:47:54 -07:00
dependabot[bot] 69ae9626c7
build(deps): bump actions/upload-pages-artifact from 3 to 4
Bumps [actions/upload-pages-artifact](https://github.com/actions/upload-pages-artifact) from 3 to 4.
- [Release notes](https://github.com/actions/upload-pages-artifact/releases)
- [Commits](https://github.com/actions/upload-pages-artifact/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/upload-pages-artifact
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-10 08:18:25 +00:00
nirenjan e9f4e1b3a8 test: Add test cases for libx52util
This change adds a test suite for libx52util, testing all the positive
cases where a character is added to the lookup table. For now, this test
suite only verifies single character mappings, not double character
mappings. A future commit will add test cases for characters not in the
map.
2026-03-09 14:43:33 -07:00
nirenjan f43ba6b902
Merge pull request #60 from nirenjan/add-action-permissions
ci: Add permissions blocks to action definitions
2026-03-09 10:39:41 -07:00
nirenjan 378cbbd931 ci: Add permissions blocks to action definitions
CodeQL identified a medium severity security issue with the action
definitions not including a permissions block as required by modern
security practices. This change ensures that the majority of the actions
force the token to be read-only and not accidentally write content back
into the repository.
2026-03-09 10:34:40 -07:00
nirenjan 47da6e22d1 ci: Fix doxygen.yml to include enviroment 2026-03-09 10:30:00 -07:00
nirenjan e98b8b4bc3 ci: Update doxygen build flow to use modern pages deployment
This change adds the new permissions structure to the action definition,
and migrates away from the 3rd party action to an official action. This
was identified as a possible security vulnerability by CodeQL
2026-03-09 10:24:25 -07:00
nirenjan 7b7065f8f0 po: Update translation files to fix build 2026-03-09 00:47:29 -07:00
nirenjan 2fa9f52ddb build: Fix the GCC pragma to only apply for GCC > 13
The -Wanalyzer-fd-leak flag was introduced in GCC 13, so this pragma
fails to build on older GCC versions.
2026-03-09 00:26:06 -07:00
nirenjan a17312dcbc fix: Fix potential error scenarios
Configuring the build with CFLAGS="-O2 -g -fanalyzer", we ran into some
build errors, which we address in this commit.

First off, GCC identified a false positive file descriptor leak in
x52d_client.c, this instance is suppressed to avoid breaking the build.

There was a bug in x52d_clock.c, where if the original timezone could
not have a copy due to malloc failure, it would fail when resetting the
TZ environment. This is fixed by ensuring the copy is valid.

Finally, there was a potential NULL pointer dereference if the pinelog
module messes up the log levels, and the lmap_get_string method ends up
returning a NULL which was passed to the DATA macro. This is fixed by
checking for NULL and handling it in case of failure.
2026-03-09 00:13:19 -07:00
nirenjan 2be7792024 build: Fix libx52util SOLIB version
Because of the fix in 2378ba7dc4, the
library revision should have been updated.
2026-03-08 23:16:40 -07:00
nirenjan f51b777ca0 fix: Handle NULL pointer dereferencing in libx52
libx52_exit dereferences the device pointer to deinitialize libusb.
However, a user could pass NULL to this function, resulting in a null
pointer dereference.
2026-03-08 23:13:16 -07:00
nirenjan b3dff7182b fix: Handle possible double-free in pinelog
The pinelog_init function frees the module_level and module_name
pointers at the start of the function, but doesn't reset them back to
NULL. If a subsequent malloc fails, then it would attempt to free the
pointer again, resulting in a double-free situation.

However, this is only hit if the pinelog_init function is called more
than once. While this is not likely (given that I'm the only known user
of pinelog at this time), it's still good coding practice.
2026-03-08 23:08:05 -07:00
nirenjan 0356a2d610
Merge pull request #59 from nirenjan/dependabot/github_actions/actions/checkout-6
Bump actions/checkout from 4 to 6
2026-03-08 20:57:04 -07:00
dependabot[bot] c1e3c85738
Bump actions/checkout from 4 to 6
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-09 03:53:36 +00:00
nirenjan accd2a1f4e ci: Add dependabot to keep Github Actions up to date 2026-03-08 20:52:55 -07:00
nirenjan e8208e97cb
Merge pull request #58 from nirenjan/fix/update-codeql-action-version
build: Update CodeQL action to v4
2026-03-08 20:49:41 -07:00
nirenjan 421e2964b3 build: Update CodeQL action to v4
Signed-off-by: nirenjan <nirenjan@gmail.com>
2026-03-08 20:45:11 -07:00
nirenjan 2378ba7dc4 fix: Fix boundary check error in libx52util
Prior to this change, if the input string terminates exactly when `len`
characters have been output, the libx52util_convert_utf8_string function
returns an error of `-E2BIG`, even though the buffer is sufficiently
large. Because the output buffer is not expected to be NUL terminated,
this additional character can be safely placed in the output buffer
without overrun.

This change checks for a non-NUL character when the index matches or
exceeds the output buffer length, and only then will it return -E2BIG.
2026-03-08 20:30:03 -07:00
nirenjan 762a3468b2 fix: Handle negative index in libx52-string-test
The test case uses a negative value to force a test of the ID to string
functions in the abnormal case. However, this ends up with accessing the
expected array with a negative index. This is technically undefined
behavior, and may cause failures in some systems. This change ensures
that the negative values will be mapped directly to the unknown
string, without having to perform a negative index.
2026-03-08 20:25:29 -07:00
nirenjan ef4cbee127 fix: Handle malformed UTF-8 input in libx52util
The libx52util_convert_utf8_string function manually converts the UTF-8
string into the character map supported by the X52/X52Pro MFD. However,
there was a bug when handling malformed UTF-8 input. If the state
machine thinks it is at the start of a word and receives malformed UTF-8
input (between 0x80 and 0xC0), it will ignore the characters, but it
will not reset the entry to the map_root location, thereby causing
subsequent characters to be dropped.

This change ensures that the entry is reset to map_root[*input] after
skipping over an invalid UTF-8 sequence.
2026-03-08 20:25:29 -07:00
nirenjan c63b924705
doc: Add security policy and reporting guidelines 2026-03-06 15:29:31 -08:00
nirenjan 1b00bf4a69 build: Force build timestamp to be in UTC time
As part of improving the privacy around the software, even though time
zone is not considered PII, it's still coarse location data that we
don't need at all. By using UTC, we can eliminate even this last bit of
identifying information from the bug reports.
2026-03-06 13:56:08 -08:00
nirenjan a40546bda3 doc: Fix bullets in PRIVACY.md 2026-03-03 18:00:03 -08:00
nirenjan 108293abdf feat: Minimize identifying information in bugreport
In order to comply with recent privacy laws such as GDPR and CCPA, the
bug report utility has been updated to remove personally identifiable
information such as device serial number and system hostname from the
output.

In addition, this change also adds a PRIVACY.md file which describes how
this project handles data, in compliance with GDPR/CCPA. Documentation
is updated to link to the privacy document as well.
2026-03-03 17:55:26 -08:00
nirenjan 9361c7af5c build: Update Github Actions to use only supported distros 2026-03-03 16:40:45 -08:00
nirenjan 004eca2418 build: Update build workflow to use current runners 2026-02-26 22:40:45 -08:00
nirenjan 1902ca0d27 build: update PO files to reflect new version 2024-06-09 20:24:45 -07:00
nirenjan 6330d28c4d fix: Update Version metadata
Version metadata was not updated to reflect the new version. This fixes
the version metadata and updates the changelog file to reflect the
reason why the older version was deprecated.
2024-06-09 20:21:54 -07:00
nirenjan 5c37c4a9db doc: Update changelog for v0.3.1 2024-06-08 22:24:46 -07:00
nirenjan 863e43e4ad ci: Update stable OS versions for LKM build 2024-06-08 21:51:48 -07:00
nirenjan 49c57f4a6a Update workflows to use actions/checkout@v4
Since Node 16 has been deprecated, Github is requiring all Actions users
to migrate to Node 20, and therefore use actions/checkout@v4. This also
applies to other jobs that use Node 16 as their runtime.

See: https://github.blog/changelog/2023-09-22-github-actions-transitioning-from-node-16-to-node-20/
2024-06-08 21:38:34 -07:00
nirenjan b0b9123a2e fix: Add CMOCKA_CFLAGS to test program CFLAGS
In macos-14, programs that rely on cmocka need to specify CMOCKA_CFLAGS
as part of their CFLAGS. This has not been an issue so far on older
versions of macOS, or on any release of Ubuntu, but it should be done to
ensure that the library headers can be found.
2024-06-08 21:25:42 -07:00
nirenjan 50a911160f fix: Disable macos-12 builds
macos-12 builds fail with the following error message:

    ld: warning: -undefined dynamic_lookup may not work with chained fixups

This causes the overall build status to be marked as fail, even though
macOS builds are not treated as failing the build. Also, macOS 12 is
going to effectively be end-of-lifed in November 2024, so it's not worth
spending the time to look into this.
2024-06-08 20:57:53 -07:00
nirenjan 7a56af032b fix: Disable mouse tests if cmocka is not present
Cmocka is optional for the builds, but the absence of cmocka causes the
Daemon build to fail when running `make check`. This commit addresses
that issue, while keeping tests that don't need cmocka.
2024-06-05 09:18:01 -07:00
nirenjan c46cec3138 ci: Disable macos-11 and add newer versions
Github has deprecated macos-11 runners and will stop them towards the
end of June 2024.
2024-06-05 09:16:22 -07:00
nirenjan 21050e40a8 Fix syntax of calloc calls in pinelog.c
`calloc` requires the count to be the first argument, and the size
parameter to be the second argument. However, this has not really caused
issues in the past, and older compilers were not so strict about it.

However, newer compilers (at least GCC 14) triggers a warning on this
and causes the build to fail.

Fixes #52
2024-06-04 15:02:27 -07:00
nirenjan 9e2e8cb8ff Add compiler details to bugreport 2023-06-02 00:02:05 -07:00
nirenjan 5f4dfe4c01 Add host details to version-info 2023-06-01 23:33:54 -07:00
nirenjan 0870518598 Disable builds on macOS 12
macOS 12 builds are currently failing with the following error:

    ld: warning: -undefined dynamic_lookup may not work with chained fixups

This is causing the overall CI to fail, therefore, I am disabling it
until such time that this can be fixed.
2023-01-21 02:49:45 -08:00
nirenjan d7b4a694fa Update Github action workflows to use actions/checkout@v3
Due to the Node 12 runtime being deprecated, jobs are required to move
to actions/checkout@v3 which uses Node 16 runtime.

See: https://github.blog/changelog/2022-09-22-github-actions-all-actions-will-begin-running-on-node16-instead-of-node12/
2023-01-21 02:39:59 -08:00
nirenjan 326ac992ac Fix hyperlink for ChangeLog.md
[skip ci] [skip doxy]
2023-01-21 02:34:17 -08:00
nirenjan d3973a0abf Update daemon protocol documentation
This change ensures that the documentation is in sync with the code.

[skip ci]
2023-01-04 10:07:20 -08:00
nirenjan ebca9566d7 Move common socket code into x52d_comm_internal.c 2023-01-04 08:42:47 -08:00
nirenjan 03c0376e7c Initialize variables to avoid maybe-uninitialized warnings
When building the package for PPA, gcc throws errors indicating that
some variables may be used uninitialized. This is not a real problem
that shows up during the CI build, but only when building using
dpkg-buildpackage.

This change adds some dummy initialization so that it avoids triggering
those warnings during debuild/dpkg-buildpackage.
2023-01-03 12:26:51 -08:00
111 changed files with 2617 additions and 1489 deletions

4
.gitattributes vendored
View File

@ -1 +1,5 @@
/version-info ident
*/meson.build ident
/.github/ export-ignore
.gitignore export-ignore
.gitattributes export-ignore

8
.github/dependabot.yml vendored 100644
View File

@ -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

View File

@ -2,16 +2,14 @@
# Run the build and tests
set -e
./autogen.sh
mkdir build
meson setup -Dprefix=/usr -Dsysconfdir=/etc -Dlocalstatedir=/var -Dnls=enabled build
cd build
../configure
make -j V=0
make -j check V=0
make -j distcheck
meson compile
meson test
meson dist
# 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

View File

@ -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

View File

@ -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()

View File

@ -9,7 +9,11 @@ brew install \
gettext \
libusb \
hidapi \
inih \
doxygen \
cmocka
cmocka \
meson \
ninja \
inih
exit 0

View File

@ -12,8 +12,12 @@ sudo apt-get install -y \
libusb-1.0-0-dev \
libhidapi-dev \
libevdev-dev \
libinih-dev \
doxygen \
libcmocka-dev \
faketime
faketime \
meson \
ninja-build \
libinih-dev
exit 0

View File

@ -1,5 +1,8 @@
name: Build/Test
permissions:
contents: read
on:
push:
branches:
@ -15,18 +18,18 @@ jobs:
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') }}
continue-on-error: ${{ startsWith(matrix.os, 'macos-') }}
env:
CC: ${{ matrix.cc }}
strategy:
matrix:
os: ['ubuntu-20.04', 'ubuntu-22.04', 'macos-11', 'macos-12']
os: ['ubuntu-22.04', 'ubuntu-24.04', 'macos-latest']
cc: ['gcc', 'clang']
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v6
- name: Install dependencies (Ubuntu)
run: ./.github/scripts/install-dependencies-ubuntu.sh

View File

@ -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

44
.github/workflows/coverage.yml vendored 100644
View File

@ -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@v5
with:
files: buildd/meson-logs/coverage.xml
fail_ci_if_error: true

View File

@ -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@v4
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@v4

View File

@ -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

View File

@ -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@v2
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/*

View File

@ -6,6 +6,68 @@ The format is based upon [Keep a Changelog].
## [Unreleased]
### 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 +220,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

File diff suppressed because it is too large Load Diff

View File

@ -3,23 +3,20 @@ 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
* 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 +25,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 libtool libusb pkg-config python3 git` |
| Arch Linux | `pacman -S base-devel meson libusb hidapi libevdev libinih python git` |
| Fedora | `sudo dnf install meson gettext-devel findutils hidapi-devel libusb-devel libevdev-devel inih-devel pkg-config python3 git` |
## Optional Packages
* doxygen - to generate HTML documentation and man pages
* libcmocka (1.1 or greater) + headers - to run unit tests
* libevdev + headers (on Linux) - to add virtual keyboard/mouse support
# Installation Instructions
@ -48,37 +46,36 @@ git clone https://github.com/nirenjan/libx52.git
2. Run autogen.sh
```
cd ./libx52
./autogen.sh
meson setup build -Dprefix=/usr
```
3. Run the following commands:
```
./configure --prefix=/usr --localstatedir=/var --sysconfdir=/etc
make && sudo make install
meson compile -C build && meson install -C build
```
You may want to remove or edit the `--prefix=/usr` option, most users prefer
non-distro binaries in `/usr/local` (default without `--prefix`) or `/opt`.
You may want to remove or edit the `-Dprefix=/usr` option, most users prefer
non-distro binaries in `/usr/local` (default without `-Dprefix`) or `/opt`.
## Configuration options
### udev
The configuration system should automatically detect the udev rules directory,
but you can override it by using the following argument to `configure`:
but you can override it by using the following argument to `meson setup`:
```
--with-udevrulesdir=/path/to/udev/rules.d
-Dudev-rules-dir=/path/to/udev/rules.d
```
### Input group
The udev rules that are installed provide read/write access to members of the
input devices group. This defaults to `plugdev`, but can be modified using
the following argument to `configure`:
the following argument to `meson setup`:
```
--with-input-group=group
-Dinput-group=group
```
### Systemd support
@ -88,13 +85,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
```

View File

@ -7,7 +7,7 @@
ACLOCAL_AMFLAGS = -I m4
# Build any support libraries first
SUBDIRS = lib
SUBDIRS = subprojects
if USE_NLS
SUBDIRS += po
@ -38,7 +38,7 @@ BUILT_SOURCES += version-info.h
CLEANFILES += version-info.h
version-info.h: ${top_srcdir}/version-info
${top_srcdir}/version-info ${top_srcdir} >$@
CC=${CC} ${top_srcdir}/version-info ${top_srcdir} >$@
########################################################################
# Include automake stubs

58
PRIVACY.md 100644
View File

@ -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,
GitHubs 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
Canonicals or the AURs 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.

View File

@ -4,6 +4,8 @@ Saitek X52Pro joystick driver for Linux
![Build/Test](https://github.com/nirenjan/libx52/workflows/Build/Test/badge.svg)
![Kernel Module](https://github.com/nirenjan/libx52/workflows/Kernel%20Module/badge.svg)
![CodeQL](https://github.com/nirenjan/libx52/workflows/CodeQL/badge.svg)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=nirenjan_libx52&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=nirenjan_libx52)
[![codecov](https://codecov.io/gh/nirenjan/libx52/graph/badge.svg?token=BH1DQQ5N3Y)](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

9
SECURITY.md 100644
View File

@ -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.

View File

@ -1 +1 @@
0.3.0
0.3.3

View File

@ -21,4 +21,6 @@ x52bugreport_LDFLAGS = \
x52bugreport_LDADD = libx52io.la
$(x52bugreport_OBJECTS): version-info.h
EXTRA_DIST += bugreport/bugreport.dox

View File

@ -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("");

View File

@ -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

View File

@ -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')

View File

@ -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

View File

@ -17,7 +17,7 @@ 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_CFLAGS = @CMOCKA_CFLAGS@ -DX52_CLI_TESTING -I $(top_srcdir)/libx52
test_cli_LDFLAGS = @CMOCKA_LIBS@ $(WARN_LDFLAGS)
# Add a dependency on test_x52_cli_tests.c

16
cli/meson.build 100644
View File

@ -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')

View File

@ -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);

View File

@ -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

View File

@ -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");
}
}

39
config.h.meson 100644
View File

@ -0,0 +1,39 @@
/* Define to 1 if translation of program messages to the user's native
language is requested. */
#mesondefine ENABLE_NLS
/* Define to 1 if the system has the `noreturn' function attribute */
#mesondefine HAVE_FUNC_ATTRIBUTE_NORETURN
/* Define to 1 if `tm_gmtoff' is a member of `struct tm'. */
#mesondefine HAVE_STRUCT_TM_TM_GMTOFF
/* Name of package */
#mesondefine PACKAGE
/* Define to the address where bug reports for this package should be sent. */
#mesondefine PACKAGE_BUGREPORT
/* Define to the full name of this package. */
#mesondefine PACKAGE_NAME
/* Define to the version of this package. */
#mesondefine PACKAGE_VERSION
/* Define to the version of this package. */
#mesondefine VERSION
/* Define to the directory where locale files are stored */
#mesondefine LOCALEDIR
/* Define to the location of the configuration directory */
#mesondefine SYSCONFDIR
/* Define to the location of the local state directory */
#mesondefine LOCALSTATEDIR
/* Define to the location of the log directory */
#define LOGDIR LOCALSTATEDIR "/log"
/* Define to the location of the run directory */
#define RUNDIR LOCALSTATEDIR "/run"

View File

@ -81,6 +81,9 @@ 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 inih library, this is now packaged with recent distros
PKG_CHECK_MODULES([INIH], [inih])
# Check for pthreads
ACX_PTHREAD
@ -135,12 +138,11 @@ AC_CONFIG_HEADERS([config.h])
AC_CONFIG_FILES([ po/Makefile.in
Makefile
lib/Makefile
subprojects/Makefile
libx52/libx52.pc
libx52io/libx52io.pc
libx52util/libx52util.pc
lib/pinelog/Makefile
lib/inih/Makefile
subprojects/pinelog/Makefile
udev/60-saitek-x52-x52pro.rules
])
AC_OUTPUT

View File

@ -26,18 +26,16 @@ x52d_CFLAGS = \
-I $(top_srcdir)/libx52io \
-I $(top_srcdir)/libx52 \
-I $(top_srcdir)/libx52util \
-I $(top_srcdir)/lib/pinelog \
-I $(top_srcdir)/lib/inih \
-I $(top_srcdir)/subprojects/pinelog \
-DSYSCONFDIR=\"$(sysconfdir)\" \
-DLOCALEDIR=\"$(localedir)\" \
-DLOGDIR=\"$(localstatedir)/log\" \
-DRUNDIR=\"$(localstatedir)/run\" \
@PTHREAD_CFLAGS@ $(WARN_CFLAGS)
@INIH_CFLAGS@ @PTHREAD_CFLAGS@ $(WARN_CFLAGS)
x52d_LDFLAGS = @PTHREAD_LIBS@ $(WARN_LDFLAGS)
x52d_LDFLAGS = @INIH_LIBS@ @PTHREAD_LIBS@ $(WARN_LDFLAGS)
x52d_LDADD = \
lib/pinelog/libpinelog.la \
lib/inih/libinih.la \
subprojects/pinelog/libpinelog.la \
libx52.la \
@LTLIBINTL@
@ -112,6 +110,9 @@ EXTRA_DIST += \
daemon/tests/logging/module.tc \
daemon/tests/cli.tc
TESTS += daemon/test_daemon_comm.py
if HAVE_CMOCKA
check_PROGRAMS += x52d-mouse-test
x52d_mouse_test_SOURCES = \
@ -122,16 +123,15 @@ x52d_mouse_test_CFLAGS = \
-I $(top_srcdir) \
-I $(top_srcdir)/libx52 \
-I $(top_srcdir)/libx52io \
-I $(top_srcdir)/lib/pinelog \
$(WARN_CFLAGS)
-I $(top_srcdir)/subprojects/pinelog \
$(WARN_CFLAGS) @CMOCKA_CFLAGS@
x52d_mouse_test_LDFLAGS = @CMOCKA_LIBS@ $(WARN_LDFLAGS)
x52d_mouse_test_LDADD = \
lib/pinelog/libpinelog.la \
subprojects/pinelog/libpinelog.la \
@LTLIBINTL@
TESTS += \
daemon/test_daemon_comm.py \
x52d-mouse-test
TESTS += x52d-mouse-test
endif
if HAVE_SYSTEMD
if !IS_MAKE_DISTCHECK

89
daemon/meson.build 100644
View File

@ -0,0 +1,89 @@
# x52d
libx52dcomm_version = '1.0.0'
libx52dcomm_sources = [
'x52d_comm_client.c',
'x52d_comm_internal.c'
]
install_headers('x52dcomm.h', subdir: meson.project_name())
lib_libx52dcomm = library('x52dcomm', libx52dcomm_sources,
dependencies: [dep_intl],
version: libx52dcomm_version,
install: true,
include_directories: includes)
x52d_sources = [
'x52d_main.c',
'x52d_config_parser.c',
'x52d_config_dump.c',
'x52d_config.c',
'x52d_device.c',
'x52d_client.c',
'x52d_clock.c',
'x52d_mouse.c',
'x52d_notify.c',
'x52d_led.c',
'x52d_command.c',
]
dep_threads = dependency('threads')
x52d_linkwith = [lib_libx52, lib_libx52dcomm]
x52d_deps = [dep_pinelog, dep_inih, dep_threads, dep_intl]
x52d_cflags = []
if dep_evdev.found()
x52d_sources += 'x52d_io.c'
x52d_sources += 'x52d_mouse_evdev.c'
x52d_cflags += '-DHAVE_EVDEV'
x52d_linkwith += lib_libx52io
x52d_deps += dep_evdev
endif
exe_x52d = executable('x52d', x52d_sources,
install: true,
include_directories: includes,
c_args: x52d_cflags,
dependencies: x52d_deps,
link_with: x52d_linkwith)
exe_x52ctl = executable('x52ctl', 'x52ctl.c',
install: true,
dependencies: [dep_intl],
include_directories: includes,
link_with: lib_libx52dcomm)
install_data('x52d.conf',
install_dir: join_paths(get_option('sysconfdir'), 'x52d'))
test('daemon-communication', files('test_daemon_comm.py')[0],
depends: [exe_x52d, exe_x52ctl], protocol: 'tap')
x52d_mouse_test_sources = ['x52d_mouse_test.c', 'x52d_mouse.c']
x52d_mouse_test = executable('x52d-mouse-test', x52d_mouse_test_sources,
include_directories: includes,
dependencies: [dep_pinelog, dep_cmocka, dep_intl])
test('x52d-mouse-test', x52d_mouse_test, protocol: 'tap')
# Install service file
if dep_systemd.found()
systemd_system_unit_dir = get_option('systemd-unit-dir')
if systemd_system_unit_dir == ''
systemd_system_unit_dir = dep_systemd.get_variable(
pkgconfig: 'systemd_system_unit_dir',
default_value: '/lib/systemd/system')
endif
sed = find_program('sed')
bindir_path = get_option('prefix') / get_option('bindir')
sed_script = 's|%bindir%|' + bindir_path + '|g'
systemd_service_file = configure_file(
input: 'x52d.service.in',
output: 'x52d.service',
command: [sed, sed_script, '@INPUT@'],
capture: true,
install: true,
install_dir: systemd_system_unit_dir
)
endif

View File

@ -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

View File

@ -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;
}

View File

@ -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) {

View File

@ -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 (;;) {

View File

@ -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;

View File

@ -239,6 +239,19 @@ static const char *lmap_get_string(const struct level_map *map, int level)
return NULL;
}
#define DATA_LMAP(map, level, resp) do {\
int input_level_ ## __LINE__ = level; \
const char *lmap_level_ ## __LINE__ = lmap_get_string(map, 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 array_find_index(const char **array, int nmemb, const char *string)
{
int i;
@ -287,14 +300,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(loglevels, pinelog_get_level(), "global");
} else if (argc == 3) {
int module = array_find_index(modules, X52D_MOD_MAX, argv[2]);
if (module == X52D_MOD_MAX) {
ERR_fmt("Invalid module '%s'", argv[2]);
} else {
DATA(argv[2], lmap_get_string(loglevels, pinelog_get_module_level(module)));
DATA_LMAP(loglevels, pinelog_get_module_level(module), argv[2]);
}
} else {
ERR_fmt("Unexpected arguments for 'logging show' command; got %d, expected 2 or 3", argc);
@ -397,6 +409,7 @@ int x52d_command_loop(int sock_fd)
static void * x52d_command_thread(void *param)
{
(void)param;
for (;;) {
if (x52d_command_loop(command_sock_fd) < 0) {
PINELOG_FATAL(_("Error %d during command loop: %s"),
@ -412,7 +425,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 +443,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;
}

View File

@ -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

View File

@ -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 */

View File

@ -13,6 +13,7 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <inttypes.h>
#include <unistd.h>
#include <errno.h>
@ -38,12 +39,14 @@ static void termination_handler(int signum)
static volatile bool flag_reload;
static void reload_handler(int signum)
{
(void)signum;
flag_reload = true;
}
static volatile bool flag_save_cfg;
static void save_config_handler(int signum)
{
(void)signum;
flag_save_cfg = true;
}
@ -111,14 +114,16 @@ static void start_daemon(bool foreground, const char *pid_file)
pid_fd = fopen(pid_file, "r");
if (pid_fd != NULL) {
int rc;
intmax_t tmp_pid;
/* File exists, read the PID and check if it exists */
rc = fscanf(pid_fd, "%u", &pid);
rc = fscanf(pid_fd, "%" SCNdMAX, &tmp_pid);
fclose(pid_fd);
if (rc != 1) {
perror("fscanf");
} else {
pid = (pid_t)tmp_pid;
rc = kill(pid, 0);
if (rc == 0 || (rc < 0 && errno == EPERM)) {
PINELOG_FATAL(_("Daemon is already running as PID %u"), pid);

View File

@ -130,6 +130,7 @@ static void reset_reports(void)
static void * x52_mouse_thr(void *param)
{
bool state_changed;
(void)param;
PINELOG_INFO(_("Starting X52 virtual mouse driver thread"));
for (;;) {

View File

@ -30,6 +30,7 @@ void x52d_mouse_evdev_thread_control(bool enabled)
static void test_mouse_thread_enabled(void **state)
{
(void)state;
#if defined HAVE_EVDEV
expect_function_calls(x52d_mouse_evdev_thread_control, 1);
expect_value(x52d_mouse_evdev_thread_control, enabled, true);
@ -40,6 +41,7 @@ static void test_mouse_thread_enabled(void **state)
static void test_mouse_thread_disabled(void **state)
{
(void)state;
#if defined HAVE_EVDEV
expect_function_calls(x52d_mouse_evdev_thread_control, 1);
expect_value(x52d_mouse_evdev_thread_control, enabled, false);
@ -50,6 +52,7 @@ static void test_mouse_thread_disabled(void **state)
static void test_mouse_speed_negative(void **state)
{
(void)state;
int orig_mouse_delay = mouse_delay;
int orig_mouse_mult = mouse_mult;
@ -61,6 +64,7 @@ static void test_mouse_speed_negative(void **state)
/* The following tests are dependent on the values in x52d_mouse.c */
static void test_mouse_speed_0(void **state)
{
(void)state;
x52d_cfg_set_Mouse_Speed(0);
assert_int_equal(mouse_delay, 70000);
assert_int_equal(mouse_mult, 4);
@ -68,6 +72,7 @@ static void test_mouse_speed_0(void **state)
static void test_mouse_speed_mid_base(void **state)
{
(void)state;
x52d_cfg_set_Mouse_Speed(6);
assert_int_equal(mouse_delay, 40000);
assert_int_equal(mouse_mult, 4);
@ -75,6 +80,7 @@ static void test_mouse_speed_mid_base(void **state)
static void test_mouse_speed_max_base(void **state)
{
(void)state;
x52d_cfg_set_Mouse_Speed(12);
assert_int_equal(mouse_delay, 10000);
assert_int_equal(mouse_mult, 4);
@ -82,6 +88,7 @@ static void test_mouse_speed_max_base(void **state)
static void test_mouse_speed_min_hyper(void **state)
{
(void)state;
x52d_cfg_set_Mouse_Speed(13);
assert_int_equal(mouse_delay, 10000);
assert_int_equal(mouse_mult, 5);
@ -89,6 +96,7 @@ static void test_mouse_speed_min_hyper(void **state)
static void test_mouse_speed_mid_hyper(void **state)
{
(void)state;
x52d_cfg_set_Mouse_Speed(22);
assert_int_equal(mouse_delay, 10000);
assert_int_equal(mouse_mult, 14);
@ -96,6 +104,7 @@ static void test_mouse_speed_mid_hyper(void **state)
static void test_mouse_speed_max_hyper(void **state)
{
(void)state;
x52d_cfg_set_Mouse_Speed(32);
assert_int_equal(mouse_delay, 10000);
assert_int_equal(mouse_mult, 24);
@ -105,6 +114,7 @@ static void test_mouse_speed_above_max(void **state)
{
int orig_mouse_delay = mouse_delay;
int orig_mouse_mult = mouse_mult;
(void)state;
x52d_cfg_set_Mouse_Speed(33);
assert_int_equal(mouse_delay, orig_mouse_delay);
@ -113,12 +123,14 @@ static void test_mouse_speed_above_max(void **state)
static void test_mouse_reverse_scroll_enabled(void **state)
{
(void)state;
x52d_cfg_set_Mouse_ReverseScroll(true);
assert_int_equal(mouse_scroll_dir, -1);
}
static void test_mouse_reverse_scroll_disabled(void **state)
{
(void)state;
x52d_cfg_set_Mouse_ReverseScroll(false);
assert_int_equal(mouse_scroll_dir, 1);
}

View File

@ -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);

View File

@ -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

View File

@ -44,6 +44,7 @@ static bool exit_loop = false;
static void signal_handler(int sig)
{
(void)sig;
exit_loop = true;
}
@ -52,7 +53,7 @@ static bool denoise = true;
/* For i18n */
#define _(x) gettext(x)
int main(int argc, char **argv)
int main(void)
{
libx52io_context *ctx;
libx52io_report last, curr;

View File

@ -0,0 +1,6 @@
# x52evtest
executable('x52evtest', 'ev_test.c',
install: true,
include_directories: includes,
dependencies: [dep_intl],
link_with: [lib_libx52io])

View File

@ -0,0 +1,25 @@
#!/bin/sh
# Install Doxygen HTML and man trees from the build directory.
# Arguments are paths relative to the install prefix (MESON_INSTALL_DESTDIR_PREFIX).
set -e
doc_html="$1"
mandir="$2"
WANTED_PAGES="man1/x52cli.1 man1/x52bugreport.1"
if [ -d "$MESON_BUILD_ROOT/docs/html" ]; then
mkdir -p "$MESON_INSTALL_DESTDIR_PREFIX/$doc_html"
cp -R "$MESON_BUILD_ROOT/docs/html"/. "$MESON_INSTALL_DESTDIR_PREFIX/$doc_html/"
fi
if [ -d "$MESON_BUILD_ROOT/docs/man" ]; then
MANDIR="$MESON_INSTALL_DESTDIR_PREFIX/$mandir"
mkdir -p "$MANDIR"
for manpage in $WANTED_PAGES
do
section=$(dirname "$manpage")
mkdir -p "$MANDIR/$section"
cp "$MESON_BUILD_ROOT/docs/man/$manpage" "$MANDIR/$section"
done
fi

View File

@ -0,0 +1,10 @@
# x52test
executable('x52test',
'x52_test.c',
'x52_test_mfd.c',
'x52_test_led.c',
'x52_test_clock.c',
install: true,
dependencies: [dep_intl],
include_directories: includes,
link_with: [lib_libx52])

View File

@ -1,27 +0,0 @@
The "inih" library is distributed under the New BSD license:
Copyright (c) 2009, Ben Hoyt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Ben Hoyt nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY BEN HOYT ''AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL BEN HOYT BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,22 +0,0 @@
# Top level Automake for inih
#
# Makefile copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
#
# inih is copyright (C) 2009-2020, Ben Hoyt
# inih is released under the New BSD license (see LICENSE.txt). Go to the
# project home page for more info:
# https://github.com/benhoyt/inih
ACLOCAL_AMFLAGS = -I m4
# Extra files that need to be in the distribution
EXTRA_DIST = \
LICENSE.txt \
ini.h \
README.md
noinst_LTLIBRARIES = libinih.la
libinih_la_SOURCES = ini.c
libinih_la_CFLAGS = $(WARN_CFLAGS) -I $(top_builddir)
libinih_la_LDFLAGS = $(WARN_LDFLAGS)

View File

@ -1,13 +0,0 @@
# inih (INI Not Invented Here)
**inih (INI Not Invented Here)** is a simple [.INI
file](http://en.wikipedia.org/wiki/INI_file) parser written in C. It's only a
couple of pages of code, and it was designed to be _small and simple_, so it's
good for embedded systems. It's also more or less compatible with Python's
[ConfigParser](http://docs.python.org/library/configparser.html) style of .INI
files, including RFC 822-style multi-line syntax and `name: value` entries.
The original project can be found [here](https://github.com/benhoyt/inih). I
have included the sources in this repository because it is not packaged for all
platforms (notably macOS), and the version here supercedes the version that is
distributed in most "stable" Linux distributions such as Debian.

View File

@ -1,298 +0,0 @@
/* inih -- simple .INI file parser
SPDX-License-Identifier: BSD-3-Clause
Copyright (C) 2009-2020, Ben Hoyt
inih is released under the New BSD license (see LICENSE.txt). Go to the project
home page for more info:
https://github.com/benhoyt/inih
*/
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
#define _CRT_SECURE_NO_WARNINGS
#endif
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include "ini.h"
#if !INI_USE_STACK
#if INI_CUSTOM_ALLOCATOR
#include <stddef.h>
void* ini_malloc(size_t size);
void ini_free(void* ptr);
void* ini_realloc(void* ptr, size_t size);
#else
#include <stdlib.h>
#define ini_malloc malloc
#define ini_free free
#define ini_realloc realloc
#endif
#endif
#define MAX_SECTION 50
#define MAX_NAME 50
/* Used by ini_parse_string() to keep track of string parsing state. */
typedef struct {
const char* ptr;
size_t num_left;
} ini_parse_string_ctx;
/* Strip whitespace chars off end of given string, in place. Return s. */
static char* rstrip(char* s)
{
char* p = s + strlen(s);
while (p > s && isspace((unsigned char)(*--p)))
*p = '\0';
return s;
}
/* Return pointer to first non-whitespace char in given string. */
static char* lskip(const char* s)
{
while (*s && isspace((unsigned char)(*s)))
s++;
return (char*)s;
}
/* Return pointer to first char (of chars) or inline comment in given string,
or pointer to NUL at end of string if neither found. Inline comment must
be prefixed by a whitespace character to register as a comment. */
static char* find_chars_or_comment(const char* s, const char* chars)
{
#if INI_ALLOW_INLINE_COMMENTS
int was_space = 0;
while (*s && (!chars || !strchr(chars, *s)) &&
!(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
was_space = isspace((unsigned char)(*s));
s++;
}
#else
while (*s && (!chars || !strchr(chars, *s))) {
s++;
}
#endif
return (char*)s;
}
/* Similar to strncpy, but ensures dest (size bytes) is
NUL-terminated, and doesn't pad with NULs. */
static char* strncpy0(char* dest, const char* src, size_t size)
{
/* Could use strncpy internally, but it causes gcc warnings (see issue #91) */
size_t i;
for (i = 0; i < size - 1 && src[i]; i++)
dest[i] = src[i];
dest[i] = '\0';
return dest;
}
/* See documentation in header file. */
int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
void* user)
{
/* Uses a fair bit of stack (use heap instead if you need to) */
#if INI_USE_STACK
char line[INI_MAX_LINE];
int max_line = INI_MAX_LINE;
#else
char* line;
size_t max_line = INI_INITIAL_ALLOC;
#endif
#if INI_ALLOW_REALLOC && !INI_USE_STACK
char* new_line;
size_t offset;
#endif
char section[MAX_SECTION] = "";
char prev_name[MAX_NAME] = "";
char* start;
char* end;
char* name;
char* value;
int lineno = 0;
int error = 0;
#if !INI_USE_STACK
line = (char*)ini_malloc(INI_INITIAL_ALLOC);
if (!line) {
return -2;
}
#endif
#if INI_HANDLER_LINENO
#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno)
#else
#define HANDLER(u, s, n, v) handler(u, s, n, v)
#endif
/* Scan through stream line by line */
while (reader(line, (int)max_line, stream) != NULL) {
#if INI_ALLOW_REALLOC && !INI_USE_STACK
offset = strlen(line);
while (offset == max_line - 1 && line[offset - 1] != '\n') {
max_line *= 2;
if (max_line > INI_MAX_LINE)
max_line = INI_MAX_LINE;
new_line = ini_realloc(line, max_line);
if (!new_line) {
ini_free(line);
return -2;
}
line = new_line;
if (reader(line + offset, (int)(max_line - offset), stream) == NULL)
break;
if (max_line >= INI_MAX_LINE)
break;
offset += strlen(line + offset);
}
#endif
lineno++;
start = line;
#if INI_ALLOW_BOM
if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
(unsigned char)start[1] == 0xBB &&
(unsigned char)start[2] == 0xBF) {
start += 3;
}
#endif
start = lskip(rstrip(start));
if (strchr(INI_START_COMMENT_PREFIXES, *start)) {
/* Start-of-line comment */
}
#if INI_ALLOW_MULTILINE
else if (*prev_name && *start && start > line) {
/* Non-blank line with leading whitespace, treat as continuation
of previous name's value (as per Python configparser). */
if (!HANDLER(user, section, prev_name, start) && !error)
error = lineno;
}
#endif
else if (*start == '[') {
/* A "[section]" line */
end = find_chars_or_comment(start + 1, "]");
if (*end == ']') {
*end = '\0';
strncpy0(section, start + 1, sizeof(section));
*prev_name = '\0';
#if INI_CALL_HANDLER_ON_NEW_SECTION
if (!HANDLER(user, section, NULL, NULL) && !error)
error = lineno;
#endif
}
else if (!error) {
/* No ']' found on section line */
error = lineno;
}
}
else if (*start) {
/* Not a comment, must be a name[=:]value pair */
end = find_chars_or_comment(start, "=:");
if (*end == '=' || *end == ':') {
*end = '\0';
name = rstrip(start);
value = end + 1;
#if INI_ALLOW_INLINE_COMMENTS
end = find_chars_or_comment(value, NULL);
if (*end)
*end = '\0';
#endif
value = lskip(value);
rstrip(value);
/* Valid name[=:]value pair found, call handler */
strncpy0(prev_name, name, sizeof(prev_name));
if (!HANDLER(user, section, name, value) && !error)
error = lineno;
}
else if (!error) {
/* No '=' or ':' found on name[=:]value line */
#if INI_ALLOW_NO_VALUE
*end = '\0';
name = rstrip(start);
if (!HANDLER(user, section, name, NULL) && !error)
error = lineno;
#else
error = lineno;
#endif
}
}
#if INI_STOP_ON_FIRST_ERROR
if (error)
break;
#endif
}
#if !INI_USE_STACK
ini_free(line);
#endif
return error;
}
/* See documentation in header file. */
int ini_parse_file(FILE* file, ini_handler handler, void* user)
{
return ini_parse_stream((ini_reader)fgets, file, handler, user);
}
/* See documentation in header file. */
int ini_parse(const char* filename, ini_handler handler, void* user)
{
FILE* file;
int error;
file = fopen(filename, "r");
if (!file)
return -1;
error = ini_parse_file(file, handler, user);
fclose(file);
return error;
}
/* An ini_reader function to read the next line from a string buffer. This
is the fgets() equivalent used by ini_parse_string(). */
static char* ini_reader_string(char* str, int num, void* stream) {
ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream;
const char* ctx_ptr = ctx->ptr;
size_t ctx_num_left = ctx->num_left;
char* strp = str;
char c;
if (ctx_num_left == 0 || num < 2)
return NULL;
while (num > 1 && ctx_num_left != 0) {
c = *ctx_ptr++;
ctx_num_left--;
*strp++ = c;
if (c == '\n')
break;
num--;
}
*strp = '\0';
ctx->ptr = ctx_ptr;
ctx->num_left = ctx_num_left;
return str;
}
/* See documentation in header file. */
int ini_parse_string(const char* string, ini_handler handler, void* user) {
ini_parse_string_ctx ctx;
ctx.ptr = string;
ctx.num_left = strlen(string);
return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler,
user);
}

View File

@ -1,157 +0,0 @@
/* inih -- simple .INI file parser
SPDX-License-Identifier: BSD-3-Clause
Copyright (C) 2009-2020, Ben Hoyt
inih is released under the New BSD license (see LICENSE.txt). Go to the project
home page for more info:
https://github.com/benhoyt/inih
*/
#ifndef INI_H
#define INI_H
/* Make this header file easier to include in C++ code */
#ifdef __cplusplus
extern "C" {
#endif
#include <stdio.h>
/* Nonzero if ini_handler callback should accept lineno parameter. */
#ifndef INI_HANDLER_LINENO
#define INI_HANDLER_LINENO 0
#endif
/* Typedef for prototype of handler function. */
#if INI_HANDLER_LINENO
typedef int (*ini_handler)(void* user, const char* section,
const char* name, const char* value,
int lineno);
#else
typedef int (*ini_handler)(void* user, const char* section,
const char* name, const char* value);
#endif
/* Typedef for prototype of fgets-style reader function. */
typedef char* (*ini_reader)(char* str, int num, void* stream);
/* Parse given INI-style file. May have [section]s, name=value pairs
(whitespace stripped), and comments starting with ';' (semicolon). Section
is "" if name=value pair parsed before any section heading. name:value
pairs are also supported as a concession to Python's configparser.
For each name=value pair parsed, call handler function with given user
pointer as well as section, name, and value (data only valid for duration
of handler call). Handler should return nonzero on success, zero on error.
Returns 0 on success, line number of first error on parse error (doesn't
stop on first error), -1 on file open error, or -2 on memory allocation
error (only when INI_USE_STACK is zero).
*/
int ini_parse(const char* filename, ini_handler handler, void* user);
/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't
close the file when it's finished -- the caller must do that. */
int ini_parse_file(FILE* file, ini_handler handler, void* user);
/* Same as ini_parse(), but takes an ini_reader function pointer instead of
filename. Used for implementing custom or string-based I/O (see also
ini_parse_string). */
int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
void* user);
/* Same as ini_parse(), but takes a zero-terminated string with the INI data
instead of a file. Useful for parsing INI data from a network socket or
already in memory. */
int ini_parse_string(const char* string, ini_handler handler, void* user);
/* Nonzero to allow multi-line value parsing, in the style of Python's
configparser. If allowed, ini_parse() will call the handler with the same
name for each subsequent line parsed. */
#ifndef INI_ALLOW_MULTILINE
#define INI_ALLOW_MULTILINE 1
#endif
/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of
the file. See https://github.com/benhoyt/inih/issues/21 */
#ifndef INI_ALLOW_BOM
#define INI_ALLOW_BOM 1
#endif
/* Chars that begin a start-of-line comment. Per Python configparser, allow
both ; and # comments at the start of a line by default. */
#ifndef INI_START_COMMENT_PREFIXES
#define INI_START_COMMENT_PREFIXES ";#"
#endif
/* Nonzero to allow inline comments (with valid inline comment characters
specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match
Python 3.2+ configparser behaviour. */
#ifndef INI_ALLOW_INLINE_COMMENTS
#define INI_ALLOW_INLINE_COMMENTS 1
#endif
#ifndef INI_INLINE_COMMENT_PREFIXES
#define INI_INLINE_COMMENT_PREFIXES ";"
#endif
/* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */
#ifndef INI_USE_STACK
#define INI_USE_STACK 1
#endif
/* Maximum line length for any line in INI file (stack or heap). Note that
this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */
#ifndef INI_MAX_LINE
#define INI_MAX_LINE 200
#endif
/* Nonzero to allow heap line buffer to grow via realloc(), zero for a
fixed-size buffer of INI_MAX_LINE bytes. Only applies if INI_USE_STACK is
zero. */
#ifndef INI_ALLOW_REALLOC
#define INI_ALLOW_REALLOC 0
#endif
/* Initial size in bytes for heap line buffer. Only applies if INI_USE_STACK
is zero. */
#ifndef INI_INITIAL_ALLOC
#define INI_INITIAL_ALLOC 200
#endif
/* Stop parsing on first error (default is to keep parsing). */
#ifndef INI_STOP_ON_FIRST_ERROR
#define INI_STOP_ON_FIRST_ERROR 0
#endif
/* Nonzero to call the handler at the start of each new section (with
name and value NULL). Default is to only call the handler on
each name=value pair. */
#ifndef INI_CALL_HANDLER_ON_NEW_SECTION
#define INI_CALL_HANDLER_ON_NEW_SECTION 0
#endif
/* Nonzero to allow a name without a value (no '=' or ':' on the line) and
call the handler with value NULL in this case. Default is to treat
no-value lines as an error. */
#ifndef INI_ALLOW_NO_VALUE
#define INI_ALLOW_NO_VALUE 0
#endif
/* Nonzero to use custom ini_malloc, ini_free, and ini_realloc memory
allocation functions (INI_USE_STACK must also be 0). These functions must
have the same signatures as malloc/free/realloc and behave in a similar
way. ini_realloc is only needed if INI_ALLOW_REALLOC is set. */
#ifndef INI_CUSTOM_ALLOCATOR
#define INI_CUSTOM_ALLOCATOR 0
#endif
#ifdef __cplusplus
}
#endif
#endif /* INI_H */

View File

@ -31,7 +31,9 @@ static libusb_device_handle *libusbx52_init(void)
int rc;
ssize_t count;
int i;
libusb_device **list;
libusb_device dummy = { 0 };
libusb_device *dummy_list[] = { &dummy };
libusb_device **list = dummy_list;
libusb_device_handle *hdl = NULL;
struct libusb_device_descriptor desc;

View File

@ -12,7 +12,7 @@ lib_LTLIBRARIES += libx52.la
# See: https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html
libx52_v_CUR=6
libx52_v_AGE=4
libx52_v_REV=1
libx52_v_REV=2
libx52_la_SOURCES = \
libx52/x52_control.c \
libx52/x52_core.c \
@ -43,7 +43,7 @@ check_PROGRAMS += libx52test libx52-string-test
nodist_libx52test_SOURCES = libx52/test_libx52.c
libx52test_SOURCES = $(libx52_la_SOURCES)
libx52test_CFLAGS = @LIBUSB_CFLAGS@ -DLOCALEDIR='"$(localedir)"' -I $(top_srcdir) -I $(top_srcdir)/libx52
libx52test_CFLAGS = @CMOCKA_CFLAGS@ @LIBUSB_CFLAGS@ -DLOCALEDIR='"$(localedir)"' -I $(top_srcdir) -I $(top_srcdir)/libx52
libx52test_CFLAGS += -Dlibusb_control_transfer=__wrap_libusb_control_transfer
libx52test_LDFLAGS = @CMOCKA_LIBS@ @LIBUSB_LIBS@
libx52test_LDADD = libx52.la
@ -60,7 +60,7 @@ libx52_string_test_SOURCES = \
libx52/test_strings.c \
libx52/x52_stringify.c \
libx52/x52_strerror.c
libx52_string_test_CFLAGS = -I $(top_srcdir) -I $(top_srcdir)/libx52
libx52_string_test_CFLAGS = @CMOCKA_CFLAGS@ -I $(top_srcdir) -I $(top_srcdir)/libx52
libx52_string_test_LDFLAGS = @CMOCKA_LIBS@
libx52_string_test_LDADD = libx52.la
endif

View File

@ -285,7 +285,6 @@ int libx52_init(libx52_device ** dev);
* cause errors.
*
* @param[in] dev Pointer to the device context
* @returns None
*/
void libx52_exit(libx52_device *dev);

57
libx52/meson.build 100644
View File

@ -0,0 +1,57 @@
libx52_version = '2.4.2'
libx52_files = files(
'x52_control.c',
'x52_core.c',
'x52_date_time.c',
'x52_mfd_led.c',
'x52_strerror.c',
'x52_stringify.c',
)
lib_libx52 = library('x52', libx52_files,
install: true,
version: libx52_version,
dependencies: [dep_libusb, dep_intl],
include_directories: [includes])
install_headers('libx52.h', subdir: meson.project_name())
pkgconfig.generate(lib_libx52,
name: 'libx52',
description: 'Linux/Unix library to control Saitek X52/X52Pro joystick extended functionality.',
subdirs: meson.project_name(),
version: libx52_version)
# Unit tests for libx52
libx52_string_test = executable('libx52-string-test',
'test_strings.c',
'x52_stringify.c',
'x52_strerror.c',
build_by_default: false,
dependencies: [dep_cmocka, dep_intl],
link_with: [lib_libx52],
include_directories: [includes, lib_libx52.private_dir_include()],
)
test('libx52-string-test', libx52_string_test, protocol: 'tap')
test_gen_script = files('x52_test_gen.py')[0]
libx52_test_file = custom_target('libx52-test',
build_by_default: false,
depend_files: ['x52_test_gen.py', 'x52_tests.json'],
command: [python, test_gen_script, '@INPUT@'],
capture: true,
input: 'x52_tests.json',
output: 'test_libx52.c'
)
libx52test = executable('libx52test', libx52_test_file, libx52_files,
c_args: ['-Dlibusb_control_transfer=__wrap_libusb_control_transfer'],
dependencies: [dep_cmocka, dep_libusb, dep_intl],
link_with: [lib_libx52],
build_by_default: false,
include_directories: [includes, lib_libx52.private_dir_include()],
)
test('libx52test', libx52test, protocol: 'tap')

View File

@ -17,8 +17,10 @@
#define TEST_STRINGIFY(name) do { \
char expected[256]; \
for (int i=-1; i < sizeof(name ## _map) / sizeof(name ## _map[0]); i++) { \
if (name ## _map[i] != NULL) { \
for (int i=-1; i < (int)(sizeof(name ## _map) / sizeof(name ## _map[0])); i++) { \
if (i < 0) { \
snprintf(expected, sizeof(expected), unknown_fmt, i); \
} else if (name ## _map[i] != NULL) { \
strncpy(expected, name ## _map[i], sizeof(expected)); \
} else { \
snprintf(expected, sizeof(expected), unknown_fmt, i); \
@ -29,6 +31,8 @@
static void test_led_id_names(void **state)
{
(void)state; // Suppress unused parameter warning
static const char * led_id_map[21] = {
[LIBX52_LED_FIRE] = "Fire",
[LIBX52_LED_A] = "A",
@ -50,6 +54,8 @@ static void test_led_id_names(void **state)
static void test_led_state_names(void **state)
{
(void)state; // Suppress unused parameter warning
static const char * led_state_map[6] = {
[LIBX52_LED_STATE_OFF] = "off",
[LIBX52_LED_STATE_ON] = "on",
@ -64,6 +70,8 @@ static void test_led_state_names(void **state)
}
static void test_clock_id_names(void **state) {
(void)state; // Suppress unused parameter warning
static const char * clock_id_map[4] = {
[LIBX52_CLOCK_1] = "primary",
[LIBX52_CLOCK_2] = "secondary",
@ -76,6 +84,8 @@ static void test_clock_id_names(void **state) {
}
static void test_clock_format_names(void **state) {
(void)state; // Suppress unused parameter warning
static const char * clock_format_map[3] = {
[LIBX52_CLOCK_FORMAT_12HR] = "12 hour",
[LIBX52_CLOCK_FORMAT_24HR] = "24 hour",
@ -87,6 +97,8 @@ static void test_clock_format_names(void **state) {
}
static void test_date_format_names(void **state) {
(void)state; // Suppress unused parameter warning
static const char * date_format_map[4] = {
[LIBX52_DATE_FORMAT_DDMMYY] = "DD-MM-YY",
[LIBX52_DATE_FORMAT_MMDDYY] = "MM-DD-YY",
@ -101,6 +113,8 @@ static void test_date_format_names(void **state) {
#define libx52_error_to_str libx52_strerror
static void test_strerror(void **state) {
(void)state; // Suppress unused parameter warning
static const char *error_map[18] = {
[LIBX52_SUCCESS] = "Success",
[LIBX52_ERROR_INIT_FAILURE] = "Initialization failure",

View File

@ -103,6 +103,8 @@ int libx52_vendor_command(libx52_device *x52, uint16_t index, uint16_t value)
static int _x52_write_shift(libx52_device *x52, uint32_t bit)
{
uint16_t value;
(void)bit;
value = tst_bit(&x52->led_mask, X52_BIT_SHIFT) ? X52_SHIFT_ON : X52_SHIFT_OFF;
return libx52_vendor_command(x52, X52_SHIFT_INDICATOR, value);
}
@ -152,6 +154,7 @@ static int _x52_write_line(libx52_device *x52, uint32_t bit)
static int _x52_write_pov_blink(libx52_device *x52, uint32_t bit)
{
uint16_t value;
(void)bit;
value = tst_bit(&x52->led_mask, X52_BIT_POV_BLINK) ? X52_BLINK_ON : X52_BLINK_OFF;
return libx52_vendor_command(x52, X52_BLINK_INDICATOR, value);
}
@ -177,6 +180,7 @@ static int _x52_write_date(libx52_device *x52, uint32_t bit)
uint16_t value1; //dd-mm
uint16_t value2; //yy
int rc;
(void)bit;
switch (x52->date_format) {
case LIBX52_DATE_FORMAT_YYMMDD:
@ -288,7 +292,7 @@ static int _x52_write_time(libx52_device *x52, uint32_t bit)
typedef int (*x52_handler)(libx52_device *, uint32_t);
const x52_handler _x52_handlers[32] = {
static const x52_handler _x52_handlers[32] = {
[X52_BIT_SHIFT] = _x52_write_shift,
[X52_BIT_LED_FIRE] = _x52_write_led,
[X52_BIT_LED_A_RED] = _x52_write_led,

View File

@ -48,6 +48,8 @@ static int _x52_hotplug_callback(libusb_context *ctx,
{
libx52_device *dev = user_data;
(void)device; // Suppress unused parameter warning
if (dev == NULL) {
return 0;
}
@ -229,6 +231,10 @@ int libx52_init(libx52_device **dev)
void libx52_exit(libx52_device *dev)
{
if (!dev) {
return;
}
libx52_disconnect(dev);
libusb_exit(dev->ctx);

View File

@ -73,6 +73,7 @@ int __wrap_libusb_control_transfer(libusb_device_handle *dev_handle,
uint16_t wLength,
unsigned int timeout)
{
(void)dev_handle;
function_called();
check_expected(wIndex);
check_expected(wValue);

View File

@ -37,12 +37,12 @@ TESTS += test-axis test-parser
check_PROGRAMS += test-axis test-parser
test_axis_SOURCES = libx52io/test_axis.c $(libx52io_la_SOURCES)
test_axis_CFLAGS = $(libx52io_la_CFLAGS)
test_axis_CFLAGS = @CMOCKA_CFLAGS@ $(libx52io_la_CFLAGS)
test_axis_LDFLAGS = @CMOCKA_LIBS@ @HIDAPI_LIBS@ $(WARN_LDFLAGS)
test_axis_LDADD = @LTLIBINTL@
test_parser_SOURCES = libx52io/test_parser.c $(libx52io_la_SOURCES)
test_parser_CFLAGS = $(libx52io_la_CFLAGS)
test_parser_CFLAGS = @CMOCKA_CFLAGS@ $(libx52io_la_CFLAGS)
test_parser_LDFLAGS = @CMOCKA_LIBS@ @HIDAPI_LIBS@ $(WARN_LDFLAGS)
test_parser_LDADD = @LTLIBINTL@

View File

@ -303,7 +303,6 @@ int libx52io_init(libx52io_context **ctx);
* cause errors.
*
* @param[in] ctx Pointer to the device context
* @returns None
*/
void libx52io_exit(libx52io_context *ctx);

View File

@ -0,0 +1,39 @@
libx52io_version = '1.0.0'
libx52io_files = files(
'io_core.c',
'io_axis.c',
'io_parser.c',
'io_strings.c',
'io_device.c',
)
lib_libx52io = library('x52io', libx52io_files,
install: true,
version: libx52io_version,
dependencies: [dep_hidapi, dep_intl],
include_directories: [includes])
install_headers('libx52io.h', subdir: meson.project_name())
pkgconfig.generate(lib_libx52io,
name: 'libx52io',
description: 'Linux/Unix library to read and parse X52 input',
subdirs: meson.project_name(),
version: libx52io_version,
)
test_axis = executable('test-axis', 'test_axis.c', libx52io_files,
build_by_default: false,
dependencies: [dep_cmocka, dep_hidapi, dep_intl],
include_directories: [includes],
)
test('test-axis', test_axis, protocol: 'tap')
test_parser = executable('test-parser', 'test_parser.c', libx52io_files,
build_by_default: false,
dependencies: [dep_cmocka, dep_hidapi, dep_intl],
include_directories: [includes],
)
test('test-parser', test_parser, protocol: 'tap')

View File

@ -89,7 +89,8 @@ static int group_teardown(void **state)
{ \
libx52io_context *ctx = *state; \
int rc; \
int32_t min, max; \
int32_t min = 0; \
int32_t max = 0; \
ctx->pid = X52_PROD_X52 ## prodid; \
_x52io_set_axis_range(ctx); \
rc = libx52io_get_axis_range(ctx, LIBX52IO_AXIS_ ## axis, &min, &max); \

View File

@ -38,9 +38,9 @@ static int group_setup(void **state)
return 0; \
}
TEST_SETUP_FUNCTION(_1);
TEST_SETUP_FUNCTION(_2);
TEST_SETUP_FUNCTION(PRO);
TEST_SETUP_FUNCTION(_1)
TEST_SETUP_FUNCTION(_2)
TEST_SETUP_FUNCTION(PRO)
#undef TEST_SETUP_FUNCTION

View File

@ -11,7 +11,7 @@ lib_LTLIBRARIES += libx52util.la
nodist_libx52util_la_SOURCES = libx52util/util_char_map.c
libx52util_la_SOURCES = libx52util/x52_char_map_lookup.c
libx52util_la_CFLAGS = -I $(top_srcdir)/libx52util $(WARN_CFLAGS)
libx52util_la_LDFLAGS = -version-info 1:0:0 $(WARN_LDFLAGS)
libx52util_la_LDFLAGS = -version-info 1:1:0 $(WARN_LDFLAGS)
# Header files that need to be copied
x52include_HEADERS += libx52util/libx52util.h
@ -20,13 +20,25 @@ x52include_HEADERS += libx52util/libx52util.h
pkgconfig_DATA += libx52util/libx52util.pc
# Autogenerated file that needs to be cleaned up
CLEANFILES += libx52util/util_char_map.c
util_char_map_c_DEPENDS = \
char_map_TARGETS = \
libx52util/util_char_map.c \
libx52util/x52_char_map.bin
CLEANFILES += $(char_map_TARGETS)
char_map_DEPENDS = \
$(srcdir)/libx52util/x52_char_map_gen.py \
$(srcdir)/libx52util/x52_char_map.cfg
libx52util/util_char_map.c: $(util_char_map_c_DEPENDS)
$(AM_V_GEN) $(PYTHON) $(util_char_map_c_DEPENDS) $@
$(char_map_TARGETS): $(char_map_DEPENDS)
$(AM_V_GEN) $(PYTHON) $(char_map_DEPENDS) $(char_map_TARGETS)
TESTS += libx52util/test-runner.sh
check_PROGRAMS += libx52util-bmp-test
libx52util_bmp_test_SOURCES = libx52util/x52_char_map_test.c
libx52util_bmp_test_CFLAGS = -I $(top_srcdir)/libx52util
libx52util_bmp_test_LDADD = libx52util.la
# Extra files that need to be in the distribution
EXTRA_DIST += libx52util/x52_char_map.cfg \

View File

@ -0,0 +1,40 @@
# libx52util
libx52util_version = '1.0.2'
gen_script = files('x52_char_map_gen.py')[0]
util_char_map = custom_target('util-char-map',
build_by_default: false,
depend_files: ['x52_char_map_gen.py', 'x52_char_map.cfg'],
command: [python, gen_script, '@INPUT@', '@OUTPUT0@', '@OUTPUT1@'],
input: 'x52_char_map.cfg',
output: ['util_char_map.c', 'x52_char_map.bin'])
lib_libx52util = library('x52util', util_char_map, 'x52_char_map_lookup.c',
install: true,
version: libx52util_version,
include_directories: [includes],
)
install_headers('libx52util.h', subdir: meson.project_name())
pkgconfig.generate(lib_libx52util,
name: 'libx52util',
description: 'Extra utility functions to manage X52 extended functionality',
subdirs: meson.project_name(),
version: libx52util_version,
)
libx52util_bmp_test = executable(
'libx52util-bmp-test',
'x52_char_map_test.c',
build_by_default: false,
include_directories: [includes, lib_libx52util.private_dir_include()],
link_with: [lib_libx52util]
)
test('libx52util-bmp-test', libx52util_bmp_test,
protocol: 'tap',
args: [util_char_map[1]])
benchmark('libx52util-bmp-bench', libx52util_bmp_test,
protocol: 'tap',
args: [util_char_map[1]])

View File

@ -0,0 +1,12 @@
#!/bin/sh
TEST_RUNNER="./libx52util-bmp-test"
TEST_BIN="./libx52util/x52_char_map.bin"
if [ -e "${TEST_RUNNER}" ] && [ -e "${TEST_BIN}" ];
then
"${TEST_RUNNER}" "${TEST_BIN}"
else
echo "TAP version 13"
echo "1..0 # missing files"
fi

View File

@ -324,3 +324,13 @@
0xFF9E 0xDE # HALFWIDTH KATAKANA VOICED SOUND MARK
0xFF9F 0xDF # HALFWIDTH KATAKANA SEMI-VOICED SOUND MARK
# The following characters are manually added to aid in normalization to the
# X52 character map
0x2215 0x2F # DIVISION SLASH
0x2044 0x2F # FRACTION SLASH
0x00B0 0xDF # DEGREE SIGN
# Note: while Greek letters aren't actually supported by the MFD character map,
# this is manually addded to map the letter 'mu' to ASCII 'u'. This is needed
# in the CJK compatibility page (0x3300-0x33FF) to deal with the square latin
# abbreviations
0x03BC 0x75 # GREEK SMALL LETTER MU

View File

@ -12,20 +12,7 @@
#include <stddef.h>
#include <stdint.h>
enum {
TYPE_INVALID = 0, /* Invalid type (default) */
TYPE_POINTER, /* Pointer target */
TYPE_ENTRY /* Map entry value */
};
struct map_entry {
struct map_entry *next; /* Pointer to the next table */
uint8_t type; /* Type of entry */
uint8_t value; /* Value is valid if this is of TYPE_ENTRY */
};
extern struct map_entry map_root[];
extern const uint16_t *root_table[256];
extern const uint8_t *sequence_table[];
#endif /* !defined X52_CHAR_MAP_H */

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# Character map generator
#
# Copyright (C) 2012-2018 Nirenjan Krishnan (nirenjan@nirenjan.org)
# Copyright (C) 2012-2026 Nirenjan Krishnan (nirenjan@nirenjan.org)
#
# SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
"""
@ -11,191 +11,267 @@ for the X52/X52 Pro MFD
import sys
import re
AUTOGEN_HEADER = """
/*
* Autogenerated character map file for Saitek X52 Pro
* Generated from %s
*/
#include "x52_char_map.h"
"""
class MapTable(object):
"""
Defines a MapTable entry, with each entry storing the value seen so far,
the type of the entry, and the value, if it's a value node.
"""
# Empty list
root = [None] * 256
def __init__(self, value_so_far, map_value=None):
self.next_level = [None] * 256
self.value_so_far = value_so_far
self.map_value = map_value
def output_nodes(self):
"""
Output the individual nodes
"""
output_lines = []
output_count = 0
for node in self.next_level:
if node is not None:
output_lines.extend(node.output_nodes())
output_count += 1
if output_count != 0:
struct_header = 'static struct map_entry table_%x[64] = {' % \
self.value_so_far
output_lines.append(struct_header)
for node_index in range(0, 256):
node = self.next_level[node_index]
if node is not None:
output_lines.append(self.dump_entry_line(0x80, node_index,
node.value_so_far,
node.map_value))
output_lines.extend(['};', ''])
return output_lines
@staticmethod
def dump_entry_line(offset, node_index, value_so_far, map_value):
"""
Dump the array entry for the current node
"""
if map_value is None:
node_entry_line = '\t[0x%02x] = { table_%x, TYPE_POINTER, 0 },' % \
(node_index - offset, value_so_far)
else:
node_entry_line = '\t[0x%02x] = { NULL, TYPE_ENTRY, 0x%02x },' % \
(node_index - offset, map_value)
return node_entry_line
@classmethod
def add_to_table(cls, input_val, map_val):
"""
Add a map value to the lookup table
"""
try:
uchr = unichr(input_val)
except NameError:
# Python 3 doesn't have unichr, but chr should work
uchr = chr(input_val)
utf8_str = uchr.encode('utf-8')
# Python2 returns the encoded result as a string, wheras
# Python3 returns the result as a bytearray. Converting
# the string (or bytearray) into a bytearray ensures that
# this can be run in both Python2 and Python3
utf8_vals = [c for c in bytearray(utf8_str)]
value_so_far = 0
level = cls.root
for index, char in enumerate(utf8_vals):
value_so_far = (value_so_far << 8) | char
if index < (len(utf8_vals) - 1):
node = level[char]
if node is None:
node = cls(value_so_far)
level[char] = node
level = level[char].next_level
else:
node = cls(value_so_far, map_val)
level[char] = node
@classmethod
def output_table_as_list(cls):
"""
Output the map table as a list of lines
"""
output_lines = []
for node in cls.root:
if node is not None:
output_lines.extend(node.output_nodes())
output_lines.append('struct map_entry map_root[256] = {')
for node_index in range(0, 256):
node = cls.root[node_index]
if node is not None:
output_lines.append(cls.dump_entry_line(0x0, node_index,
node.value_so_far,
node.map_value))
output_lines.extend(['};', ''])
return output_lines
import json
import unicodedata
class LineFormatError(ValueError):
"""
Error class for parser
"""
def parse_line(data):
class BMPTable:
"""
Parse a line containing a mapping descriptor. The mapping descriptor
must start with a hexadecimal unicode code point, followed by either a
single character, or a hexadecimal integer that corresponds to the map
value.
Sparse table for Basic Multilingual Plane
"""
# Strip off comments
data = re.sub(re.compile('#.*$'), '', data)
# Strip off leading and trailing whitespace
data = data.strip()
REPLACEMENT_CHAR = 0xDB
# If the line is empty, it is a comment line
if len(data) == 0:
return None, None
HEADER = f"""/*
* Autogenerated tables for X52 MFD character lookup
*
* DO NOT EDIT
*/
# Find the code point and the target value
try:
code_point, target = data.strip().split()
except ValueError:
# Raised when there are either too many, or not enough values in
# the string
raise LineFormatError('Invalid descriptor format "%s"' % data)
#include <stdint.h>
# Convert the string to its equivalent numeric value
try:
code_point = int(code_point, 0)
except ValueError:
raise LineFormatError('Invalid code point "%s"' % code_point)
"""
# Check if the target is a single character
if len(target) == 1:
target = ord(target)
else:
# Try parsing the target as an integer
TABLE_NAME_FORMAT = 'bmp_page_%02x'
TABLE_NAME_DEFAULT = 'bmp_page_default'
TABLE_FORMAT = 'const uint16_t %s[256] = {'
TABLE_FOOTER = '};\n'
def __init__(self, input_file, output_file, output_map):
self.input_file = input_file
self.output_file = output_file
self.output_map = output_map
self.mapping = {}
self.pages = {}
self.sequences = {}
self.root_table = []
self.read_map()
self.build_extended_map()
self.build_tables()
self.generate_test_tables()
@staticmethod
def parse_line(data):
"""
Parse a line containing a mapping descriptor. The mapping descriptor
must start with a hexadecimal unicode code point, followed by either a
single character, or a hexadecimal integer that corresponds to the map
value.
"""
# Strip off comments
data = re.sub(re.compile('#.*$'), '', data)
# Strip off leading and trailing whitespace
data = data.strip()
# If the line is empty, it is a comment line
if len(data) == 0:
return None, None
# Find the code point and the target value
try:
target = int(target, 0)
except ValueError:
raise LineFormatError('Invalid map value "%s"' % target)
code_point, target = data.strip().split()
except ValueError as exc:
# Raised when there are either too many, or not enough values in
# the string
raise LineFormatError(f'Invalid descriptor format "{data}"') from exc
return code_point, target
# Convert the string to its equivalent numeric value
try:
code_point = int(code_point, 0)
except ValueError as exc:
raise LineFormatError(f'Invalid code point "{code_point}"') from exc
# Check if the target is a single character
if len(target) == 1:
target = ord(target)
else:
# Try parsing the target as an integer
try:
target = int(target, 0)
except ValueError as exc:
raise LineFormatError(f'Invalid map value "{target}"') from exc
return code_point, target
def read_map(self):
"""Read the mapping tables from the config file"""
def map_normalized(char, dst):
# Try to normalize the unicode character as NFKC
normalized = unicodedata.normalize('NFKC', chr(char))
if normalized == char:
# This is already in normalized form
return
if len(normalized) == 1:
normalized_char = ord(normalized)
if normalized_char not in self.mapping:
# This is only needed to ensure that we get the normalized
# forms for example, half-width Katakana characters are
# normalized to their corresponding full width versions.
# However, we don't want to overwrite existing mappings,
# since something like Lowercase A with grave could be
# normalized to lowercase A, which would break the translation
self.mapping[normalized_char] = dst
with open(self.input_file, 'r', encoding='utf-8') as infile:
for line in infile:
src, dst = self.parse_line(line)
if src is None:
continue
self.mapping[src] = dst
map_normalized(src, dst)
def build_extended_map(self):
"""Build the extended map for every character in the BMP"""
self.mapping[0] = 0 # Handle NUL
for i in range(0x10000):
# Iterate over the basic multilingual plane
if i in self.mapping:
continue
if 0xD800 <= i <= 0xDFFF:
# UTF16 surrogate pairs - we want to mark it as a box character
self.mapping[i] = self.REPLACEMENT_CHAR
continue
normalized = unicodedata.normalize('NFKC', chr(i))
if len(normalized) == 1:
normalized_ord = ord(normalized)
if normalized_ord in self.mapping:
self.mapping[i] = self.mapping[normalized_ord]
else:
# No single character mapping exists
self.mapping[i] = self.REPLACEMENT_CHAR
continue
# Check that all characters in the normalized are in the mapping table:
sequence = []
for c in normalized:
if ord(c) in self.mapping:
sequence.append(self.mapping[ord(c)])
else:
sequence.append(self.REPLACEMENT_CHAR)
# Check if it only contains the box character, or box char and space,
# and reduce runs to a single instance
if all(c in (self.REPLACEMENT_CHAR, self.mapping[0x20])
for c in sequence):
self.mapping[i] = self.REPLACEMENT_CHAR
continue
sequence = tuple(sequence)
if sequence not in self.sequences:
if not self.sequences:
last_sequence = 256
else:
last_sequence = max(self.sequences.values()) + 1
self.sequences[sequence] = last_sequence
self.mapping[i] = self.sequences[sequence]
def output_c_table(self, page_tuple, out_fd):
"""Output the C table structure"""
page_name = self.pages[page_tuple]
print(self.TABLE_FORMAT % (page_name), file=out_fd)
for i, val in enumerate(page_tuple):
print(f"0x{val:02x}, ", end='', file=out_fd)
if i % 8 == 7:
print(f"// 0x{i-7:02x}-0x{i:02x}", file=out_fd)
print(self.TABLE_FOOTER, file=out_fd)
def build_tables(self):
"""Build the C Tables"""
with open(self.output_file, 'w', encoding='utf-8') as out_fd:
print(self.HEADER, file=out_fd)
default_page = tuple([self.REPLACEMENT_CHAR] * 256)
self.pages[default_page] = self.TABLE_NAME_DEFAULT
self.output_c_table(default_page, out_fd)
for root_idx in range(256):
base_idx = root_idx * 256
page = [self.mapping[idx] for idx in range(base_idx, base_idx+256)]
page_tuple = tuple(page)
if page_tuple not in self.pages:
page_name = self.TABLE_NAME_FORMAT % (root_idx)
self.pages[page_tuple] = page_name
self.output_c_table(page_tuple, out_fd)
self.root_table.append(self.pages[page_tuple])
print(self.TABLE_FORMAT % ('* root_table'), file=out_fd)
for page_id, page_name in enumerate(self.root_table):
print(f" {page_name}, // 0x{page_id:02x}", file=out_fd)
print(self.TABLE_FOOTER, file=out_fd)
print(f"const uint8_t *sequence_table[{len(self.sequences)}] = {{", file=out_fd)
for sequence, seq_id in self.sequences.items():
seq_len = len(sequence)
if seq_len >= 256:
raise RuntimeError("Sequence way too long")
line = [f"0x{seq_len:02X}"]
for seq_elem in sequence:
line.append(f"0x{seq_elem:02X}")
line = ', '.join(line)
print(f' [{seq_id-256}] = (const uint8_t[]){{ {line} }},', file=out_fd)
print(self.TABLE_FOOTER, file=out_fd)
def generate_test_tables(self):
"""Build the test tables used by the test suite"""
# Generate the expected output sequences for every table
# Mapping is a dict mapping the code point as a string to the output
# Sequence is a dict of <seq_tuple>:<seq_id> mappings (seq_id starts from 256)
output = []
sequences = [item[0] for item in sorted(self.sequences.items(),
key=lambda item: item[1])]
# The mapping for the NUL byte (\x00) should be an empty sequence
output.append([])
for i in range(1, 0x10000):
seq = self.mapping[i]
if seq >= 256:
# Pull from sequence table
seq = sequences[seq - 256]
else:
seq = [seq]
output.append(seq)
# Find the longest length sequence (add 1 for the length byte)
longest = max(len(seq) for seq in output) + 1
# Find the next power of two that can hold this sequence
if (longest & (longest - 1)) == 0:
record_length = longest
else:
record_length = 1 << longest.bit_length()
with open(self.output_map, 'wb') as output_map:
pad = [0] * record_length
for seq in output:
record = [len(seq)] + list(seq) + pad
output_map.write(bytes(record[:record_length]))
if __name__ == "__main__":
if len(sys.argv) != 3:
sys.stderr.write('Usage: %s <input-map> <output-c-file>\n' %
sys.argv[0])
if len(sys.argv) != 4:
sys.stderr.write(f"Usage: {sys.argv[0]} <input-map> <output-c-file> <output-json-map>\n")
sys.exit(1)
with open(sys.argv[1], 'r') as infile:
for line in infile:
src, dst = parse_line(line)
if src is not None:
MapTable.add_to_table(src, dst)
with open(sys.argv[2], 'w') as outfile:
outfile.write(AUTOGEN_HEADER % sys.argv[1])
for line in MapTable.output_table_as_list():
outfile.write(line + '\n')
BMPTable(sys.argv[1], sys.argv[2], sys.argv[3])

View File

@ -11,11 +11,64 @@
#include "config.h"
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include "libx52util.h"
#include "x52_char_map.h"
/**
* @brief Converts a UTF8 stream to a uint32_t
*
* @param[in] utf8in Pointer to UTF8 input stream. Must be NUL-terminated
* @param[out] unichr Output character pointer
*
* @returns number of bytes to advance stream by - 0 if NUL or input pointer is NULL
*/
static int utf8_to_u32(const uint8_t *utf8in, uint32_t *unichr)
{
uint8_t b;
if (!utf8in || !*utf8in) return 0;
b = utf8in[0];
// 1-byte (0xxxxxxx)
if (b < 0x80) {
*unichr = b;
return 1;
}
// Invalid leading bytes
if (b < 0xC2 || b > 0xF4) goto error;
// 2-byte (110xxxxx 10xxxxxx)
if ((b & 0xE0) == 0xC0) {
if ((utf8in[1] & 0xC0) != 0x80) goto error;
*unichr = ((b & 0x1F) << 6) | (utf8in[1] & 0x3F);
return 2;
}
// 3-byte (1110xxxx 10xxxxxx 10xxxxxx)
if ((b & 0xF0) == 0xE0) {
if ((utf8in[1] & 0xC0) != 0x80 || (utf8in[2] & 0xC0) != 0x80) goto error;
*unichr = ((b & 0x0F) << 12) | ((utf8in[1] & 0x3F) << 6) | (utf8in[2] & 0x3F);
return 3;
}
// 4-byte (11110xxx 10xxxxxx 10xxxxxx 10xxxxxx)
if ((b & 0xF8) == 0xF0) {
if ((utf8in[1] & 0xC0) != 0x80 || (utf8in[2] & 0xC0) != 0x80 ||
(utf8in[3] & 0xC0) != 0x80) goto error;
*unichr = ((b & 0x07) << 18) | ((utf8in[1] & 0x3F) << 12) |
((utf8in[2] & 0x3F) << 6) | (utf8in[3] & 0x3F);
return 4;
}
error:
*unichr = 0xFFFD; // Unicode Replacement Character
return 1; // Consume lead byte to attempt resync
}
/**
* @brief Convert UTF8 string to X52 character map.
*
@ -32,48 +85,60 @@
int libx52util_convert_utf8_string(const uint8_t *input,
uint8_t *output, size_t *len)
{
struct map_entry *entry;
size_t index;
int retval = 0;
unsigned char local_index;
uint32_t unichr;
int bytes_consumed;
uint16_t translated;
if (!input || !output || !len || !*len) {
return -EINVAL;
}
index = 0;
entry = &map_root[*input];
// Reset the output array
memset(output, 0, *len);
while (*input) {
input++;
if (entry->type == TYPE_ENTRY) {
output[index] = entry->value;
// Length check
if (index >= *len) {
retval = -E2BIG;
break;
}
bytes_consumed = utf8_to_u32(input, &unichr);
if (bytes_consumed == 0) {
// We should never get here, since the while loop should have
// caught it
retval = 0;
break;
}
input += bytes_consumed;
// Check for bytes in the Supplementary planes
if (unichr >= 0x10000) {
unichr = 0xFFFD; // Unicode replacement character
}
translated = root_table[unichr >> 8][unichr & 0xFF];
if (translated < 256) {
// Table entry, push to output
output[index] = (uint8_t)translated;
index++;
if (index >= *len) {
} else {
// We have a sequence, output that
const uint8_t *sequence = sequence_table[translated - 256];
uint8_t seq_len = sequence[0];
// Let's make sure that we can actually output to the buffer
if ((index + seq_len) >= *len) {
retval = -E2BIG;
break;
}
entry = &map_root[*input];
} else if (entry->type == TYPE_POINTER) {
local_index = *input;
if (local_index < 0x80 || local_index >= 0xC0) {
/* Invalid input, skip till we find the start of another
* valid UTF-8 character
*/
while (*input >= 0x80 && *input < 0xC0) {
input++; /* Skip invalid characters */
}
/* New UTF-8 character, reset the entry pointer */
entry = &map_root[*input];
} else {
/* Mask off the upper bits, we only care about the lower 6 bits */
local_index &= 0x3F;
entry = &(entry->next[local_index]);
}
} else {
/* Invalid value, skip */
while (*input >= 0x80 && *input < 0xC0) {
input++; /* Skip invalid characters */
for (int i = 1; i <= seq_len; i++) {
output[index] = sequence[i];
index++;
}
}
}
@ -81,4 +146,3 @@ int libx52util_convert_utf8_string(const uint8_t *input,
*len = index;
return retval;
}

View File

@ -0,0 +1,215 @@
/*
* X52 character map lookup test
*
* Copyright (C) 2026 Nirenjan Krishnan <nirenjan@nirenjan.org>
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#include "libx52util.h"
// Fix this if we ever hit longer sequences
#define RECORD_SIZE 8
// Blindly encode a string into it's smallest UTF8 representation
static void encode_utf8(uint32_t cp, uint8_t *out)
{
if (cp <= 0x7F) {
out[0] = (uint8_t)cp;
} else if (cp <= 0x7FF) {
out[0] = (uint8_t)(0xC0 | (cp >> 6));
out[1] = (uint8_t)(0x80 | (cp & 0x3F));
} else if (cp <= 0xFFFF) {
out[0] = (uint8_t)(0xE0 | (cp >> 12));
out[1] = (uint8_t)(0x80 | ((cp >> 6) & 0x3F));
out[2] = (uint8_t)(0x80 | (cp & 0x3F));
} else if (cp <= 0x1FFFFF) {
out[0] = (uint8_t)(0xF0 | (cp >> 18));
out[1] = (uint8_t)(0x80 | ((cp >> 12) & 0x3F));
out[2] = (uint8_t)(0x80 | ((cp >> 6) & 0x3F));
out[3] = (uint8_t)(0x80 | (cp & 0x3F));
} else if (cp <= 0x3FFFFFF) {
out[0] = (uint8_t)(0xF8 | (cp >> 24));
out[1] = (uint8_t)(0x80 | ((cp >> 18) & 0x3F));
out[2] = (uint8_t)(0x80 | ((cp >> 12) & 0x3F));
out[3] = (uint8_t)(0x80 | ((cp >> 6) & 0x3F));
out[4] = (uint8_t)(0x80 | (cp & 0x3F));
} else if (cp <= 0x7FFFFFFF) {
out[0] = (uint8_t)(0xFC | (cp >> 30));
out[1] = (uint8_t)(0x80 | ((cp >> 24) & 0x3F));
out[2] = (uint8_t)(0x80 | ((cp >> 18) & 0x3F));
out[3] = (uint8_t)(0x80 | ((cp >> 12) & 0x3F));
out[4] = (uint8_t)(0x80 | ((cp >> 6) & 0x3F));
out[5] = (uint8_t)(0x80 | (cp & 0x3F));
} else { // 0x80000000 to 0xFFFFFFFF (7 bytes)
out[0] = (uint8_t)0xFE; // Binary 11111110
out[1] = (uint8_t)(0x80 | ((cp >> 30) & 0x3F));
out[2] = (uint8_t)(0x80 | ((cp >> 24) & 0x3F));
out[3] = (uint8_t)(0x80 | ((cp >> 18) & 0x3F));
out[4] = (uint8_t)(0x80 | ((cp >> 12) & 0x3F));
out[5] = (uint8_t)(0x80 | ((cp >> 6) & 0x3F));
out[6] = (uint8_t)(0x80 | (cp & 0x3F));
}
}
static double get_time_diff(struct timespec start, struct timespec end)
{
return (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9;
}
int main(int argc, char *argv[])
{
uint8_t input[8] = {0};
uint8_t output[RECORD_SIZE];
size_t len;
int result;
int fd;
uint8_t *expected_blob;
bool smp_pages_ok;
struct timespec start, end;
// Argument check
if (argc != 2) {
puts("Bail out! Invalid number of arguments");
puts("# Usage: libx52util-bmp-test <path-to-bin>");
return 1;
}
fd = open(argv[1], O_RDONLY);
if (fd < 0) {
printf("Bail out! Error %d opening bin file %s: %s\n",
errno, argv[1], strerror(errno));
return 1;
}
expected_blob = mmap(NULL, 0x10000 * RECORD_SIZE,
PROT_READ, MAP_SHARED, fd, 0);
if (expected_blob == MAP_FAILED) {
printf("Bail out! MMAP failed with error %d: %s\n",
errno, strerror(errno));
}
puts("TAP version 13");
// Check the 256 BMP Pages, plus the supplementary pages
puts("1..257");
clock_gettime(CLOCK_MONOTONIC, &start);
for (uint32_t page = 0; page < 256; page++) {
bool page_ok = true;
for (uint32_t offset = 0; offset < 256; offset++) {
uint32_t cp = page * 256 + offset;
const uint8_t *rec = &expected_blob[cp * RECORD_SIZE];
memset(input, 0, sizeof(input));
memset(output, 0, sizeof(output));
encode_utf8(cp, input);
len = sizeof(output);
result = libx52util_convert_utf8_string(input, output, &len);
if (result != 0) {
page_ok = false;
printf("# Bad result @ %04X: %d\n", cp, result);
break;
}
// result is OK, check against the expected blob
if (len != rec[0]) {
page_ok = false;
printf("# Length mismatch @ %04X: expected %u, got %zu\n",
cp, rec[0], len);
break;
}
// Length is OK, check the bytes
if (memcmp(output, &rec[1], rec[0]) != 0) {
page_ok = false;
printf("# Output mismatch @ %04X:\n", cp);
printf("# exp/got:");
for (size_t i = 0; i < len; i++) {
printf("%02X/%02X ", rec[i+1], output[i]);
}
puts("");
break;
}
}
printf("%sok - %d Page 0x%02x\n", page_ok ? "": "not ",
page + 1, page);
}
clock_gettime(CLOCK_MONOTONIC, &end);
{
double time_spent = get_time_diff(start, end);
printf("# -- Benchmark results --\n");
printf("# Total time for 64K lookups: %.4f seconds\n", time_spent);
printf("# Throughput: %.2f Mchars/sec\n", (65536.0 / time_spent) / 1e6);
printf("# -----------------------\n");
}
// Handle the supplementary pages
smp_pages_ok = true;
for (uint32_t smp = 0x1; smp <= 0x10; smp++) {
const uint8_t *rec = &expected_blob[0xFFFD * RECORD_SIZE];
for (uint32_t offset = 0; offset < 0x100; offset += 0xFF) {
uint32_t cp = smp * 0x10000 + offset;
memset(input, 0, sizeof(input));
memset(output, 0, sizeof(output));
len = sizeof(output);
encode_utf8(cp, input);
result = libx52util_convert_utf8_string(input, output, &len);
if (result != 0) {
smp_pages_ok = false;
printf("# Bad result @ %08X: %d\n", cp, result);
break;
}
// result is OK, check against the expected blob
if (len != rec[0]) {
smp_pages_ok = false;
printf("# Length mismatch @ %08X: expected %u, got %zu\n",
cp, rec[0], len);
break;
}
// Length is OK, check the bytes
if (memcmp(output, &rec[1], rec[0]) != 0) {
smp_pages_ok = false;
printf("# Output mismatch @ %08X:\n", cp);
printf("# exp/got:");
for (size_t i = 0; i < len; i++) {
printf("%02X/%02X ", rec[i+1], output[i]);
}
puts("");
break;
}
}
if (!smp_pages_ok) {
break;
}
}
printf("%sok - 257 SMP tests\n", smp_pages_ok ? "" : "not ");
// Cleanup
munmap(expected_blob, 0x10000 * RECORD_SIZE);
close(fd);
return 0;
}

149
meson.build 100644
View File

@ -0,0 +1,149 @@
project('libx52', 'C',
license: 'GPL-2.0-only WITH Classpath-exception-2.0',
version: '0.3.3',
meson_version: '>=0.61')
dep_libusb = dependency('libusb-1.0', required: true)
dep_hidapi = dependency('hidapi-hidraw', required: false)
if not dep_hidapi.found()
dep_hidapi = dependency('hidapi', required: true)
endif
dep_evdev = dependency('libevdev', required: false)
dep_systemd = dependency('systemd', required: false)
dep_udev = dependency('udev', required: false)
dep_cmocka = dependency('cmocka', required: false)
if not dep_cmocka.found()
dep_cmocka = disabler()
endif
doxygen_program = find_program('doxygen', required: false)
# pkgconfig module is needed
pkgconfig = import('pkgconfig')
# Python 3.5 or greater is needed
pymod = import('python')
python = pymod.find_installation('python3')
pyversion = python.language_version().split('.')
assert(pyversion[1].to_int() >= 5, 'Require Python >= 3.5')
#######################################################################
# config.h
#######################################################################
compiler = meson.get_compiler('c')
cdata = configuration_data()
cdata.set_quoted('PACKAGE', meson.project_name())
cdata.set_quoted('PACKAGE_BUGREPORT', 'https://github.com/nirenjan/libx52/issues')
cdata.set_quoted('PACKAGE_NAME', meson.project_name())
cdata.set_quoted('LOCALEDIR', get_option('prefix') / get_option('localedir'))
cdata.set_quoted('SYSCONFDIR', get_option('prefix') / get_option('sysconfdir'))
cdata.set_quoted('LOCALSTATEDIR', get_option('prefix') / get_option('localstatedir'))
cdata.set_quoted('PACKAGE_VERSION', meson.project_version())
cdata.set_quoted('VERSION', meson.project_version())
cdata.set10('ENABLE_NLS', not get_option('nls').disabled())
cdata.set10('HAVE_FUNC_ATTRIBUTE_NORETURN', compiler.has_function_attribute('noreturn'))
cdata.set10('HAVE_STRUCT_TM_TM_GMTOFF',
compiler.has_member('struct tm', 'tm_gmtoff', prefix:'#include <time.h>'))
config_h = configure_file(
input: 'config.h.meson',
output: 'config.h',
configuration: cdata
)
#######################################################################
# Internationalization
#######################################################################
i18n = import('i18n')
dep_intl = dependency('intl', required: false)
if not dep_intl.found() and host_machine.system() == 'darwin'
brew = find_program('brew', required: false)
if brew.found()
brew_prefix = run_command(brew, '--prefix', 'gettext',
check: true).stdout().strip()
brew_inc = brew_prefix / 'include'
brew_lib_dir = brew_prefix / 'lib'
dep_intl = compiler.find_library('intl', dirs: brew_lib_dir)
dep_intl = declare_dependency(dependencies: dep_intl, include_directories: brew_inc)
endif
endif
# # define GETTEXT_PACKAGE
if not get_option('nls').disabled()
add_project_arguments(
'-DGETTEXT_PACKAGE="@0@"'.format(meson.project_name()),
language:'C')
subdir('po')
endif
add_project_arguments('-isystem', meson.current_source_dir() / 'sys', language: 'C')
#######################################################################
# Internal dependencies
#######################################################################
# pinelog
pinelog_options = []
if dep_systemd.found() and not get_option('systemd-logs').disabled()
# If systemd logs is enabled or auto, and systemd is found, then hide
# the timestamps in log messages
pinelog_options = ['show-date=false']
endif
sub_pinelog = subproject('pinelog', required: true,
default_options: pinelog_options)
dep_pinelog = sub_pinelog.get_variable('libpinelog_dep')
# inih
# Use system inih
dep_inih = dependency('inih')
#######################################################################
# Shared libraries and programs
#######################################################################
# Includes
includes = include_directories('.', 'libx52', 'libx52io', 'libx52util')
subdir('libx52')
subdir('libx52io')
subdir('libx52util')
subdir('bugreport')
subdir('cli')
subdir('joytest')
subdir('evtest')
subdir('daemon')
subdir('udev')
#######################################################################
# Documentation - doxygen
#######################################################################
if doxygen_program.found()
doxyfile = configure_file(
input: 'Doxyfile.in',
output: 'Doxyfile',
configuration: {
'PACKAGE_NAME': meson.project_name(),
'PACKAGE_VERSION': meson.project_version(),
'abs_top_builddir': meson.project_build_root(),
'abs_top_srcdir': meson.project_source_root(),
}
)
custom_target('docs',
depend_files: [doxyfile, 'DoxygenLayout.xml'],
command: [doxygen_program],
output: 'docs'
)
meson.add_install_script(
'install-doxygen-docs.sh',
get_option('datadir') / 'doc' / meson.project_name(),
get_option('mandir'),
)
endif

21
meson_options.txt 100644
View File

@ -0,0 +1,21 @@
option('systemd-logs',
type: 'feature',
description: 'Hide timestamps in log messages, needed for systemd')
option('systemd-unit-dir',
type: 'string',
value: '',
description: 'Directory for systemd service files (leave empty for auto-detection)')
option('nls',
type: 'feature',
description: 'Enable message translations')
option('udev-rules-dir',
type: 'string',
value: '',
description: 'Directory for udev rules (leave empty for auto-detection)')
option('input-group',
type: 'string', value: 'plugdev',
description: 'Group for input devices')

View File

@ -6,9 +6,9 @@
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: libx52 0.3.0\n"
"Project-Id-Version: libx52 0.3.3\n"
"Report-Msgid-Bugs-To: https://github.com/nirenjan/libx52/issues\n"
"POT-Creation-Date: 2022-12-25 22:03-0800\n"
"POT-Creation-Date: 2026-03-19 00:09-0700\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -233,26 +233,26 @@ msgstr ""
msgid "Read timeout"
msgstr ""
#: evtest/ev_test.c:109
#: evtest/ev_test.c:110
#, c-format
msgid "Device ID: vendor 0x%04x product 0x%04x version 0x%04x\n"
msgstr ""
#: evtest/ev_test.c:113
#: evtest/ev_test.c:114
#, c-format
msgid "Device name: \"%s %s\"\n"
msgstr ""
#: evtest/ev_test.c:116
#: evtest/ev_test.c:117
#, c-format
msgid "Serial number: \"%s\"\n"
msgstr ""
#: evtest/ev_test.c:117
#: evtest/ev_test.c:118
msgid "Testing (interrupt to exit)\n"
msgstr ""
#: evtest/ev_test.c:157 evtest/ev_test.c:165
#: evtest/ev_test.c:158 evtest/ev_test.c:166
#, c-format
msgid "Event @ %ld.%06ld: %s, value %d\n"
msgstr ""
@ -493,17 +493,17 @@ msgstr ""
msgid "OK"
msgstr ""
#: daemon/x52d_main.c:64
#: daemon/x52d_main.c:67
#, c-format
msgid "Error %d setting log file: %s\n"
msgstr ""
#: daemon/x52d_main.c:80
#: daemon/x52d_main.c:83
#, c-format
msgid "Error %d installing handler for signal %d: %s"
msgstr ""
#: daemon/x52d_main.c:91
#: daemon/x52d_main.c:94
#, c-format
msgid ""
"Usage: %s [-f] [-v] [-q]\n"
@ -513,112 +513,112 @@ msgid ""
"\t[-b notify-socket-path]\n"
msgstr ""
#: daemon/x52d_main.c:124
#: daemon/x52d_main.c:129
#, c-format
msgid "Daemon is already running as PID %u"
msgstr ""
#: daemon/x52d_main.c:266
#: daemon/x52d_main.c:271
#, c-format
msgid "Unable to parse configuration override '%s'\n"
msgstr ""
#: daemon/x52d_main.c:298
#: daemon/x52d_main.c:303
#, c-format
msgid "Foreground = %s"
msgstr ""
#: daemon/x52d_main.c:298 daemon/x52d_main.c:299
#: daemon/x52d_main.c:303 daemon/x52d_main.c:304
msgid "true"
msgstr ""
#: daemon/x52d_main.c:298 daemon/x52d_main.c:299
#: daemon/x52d_main.c:303 daemon/x52d_main.c:304
msgid "false"
msgstr ""
#: daemon/x52d_main.c:299
#, c-format
msgid "Quiet = %s"
msgstr ""
#: daemon/x52d_main.c:300
#, c-format
msgid "Verbosity = %d"
msgstr ""
#: daemon/x52d_main.c:301
#, c-format
msgid "Log file = %s"
msgstr ""
#: daemon/x52d_main.c:302
#, c-format
msgid "Config file = %s"
msgstr ""
#: daemon/x52d_main.c:303
#, c-format
msgid "PID file = %s"
msgstr ""
#: daemon/x52d_main.c:304
#, c-format
msgid "Command socket = %s"
msgid "Quiet = %s"
msgstr ""
#: daemon/x52d_main.c:305
#, c-format
msgid "Verbosity = %d"
msgstr ""
#: daemon/x52d_main.c:306
#, c-format
msgid "Log file = %s"
msgstr ""
#: daemon/x52d_main.c:307
#, c-format
msgid "Config file = %s"
msgstr ""
#: daemon/x52d_main.c:308
#, c-format
msgid "PID file = %s"
msgstr ""
#: daemon/x52d_main.c:309
#, c-format
msgid "Command socket = %s"
msgstr ""
#: daemon/x52d_main.c:310
#, c-format
msgid "Notify socket = %s"
msgstr ""
#: daemon/x52d_main.c:316
#: daemon/x52d_main.c:321
#, c-format
msgid "Error %d blocking signals on child threads: %s"
msgstr ""
#: daemon/x52d_main.c:335
#: daemon/x52d_main.c:340
#, c-format
msgid "Error %d unblocking signals on child threads: %s"
msgstr ""
#: daemon/x52d_main.c:348
#: daemon/x52d_main.c:353
msgid "Reloading X52 configuration"
msgstr ""
#: daemon/x52d_main.c:355
#: daemon/x52d_main.c:360
msgid "Saving X52 configuration to disk"
msgstr ""
#: daemon/x52d_main.c:361
#: daemon/x52d_main.c:366
#, c-format
msgid "Received termination signal %s"
msgstr ""
#: daemon/x52d_main.c:378
#: daemon/x52d_main.c:383
msgid "Shutting down X52 daemon"
msgstr ""
#: daemon/x52d_client.c:32
#: daemon/x52d_client.c:37
#, c-format
msgid "Error accepting client connection on socket fd %d: %s"
msgstr ""
#: daemon/x52d_client.c:38
#: daemon/x52d_client.c:43
#, c-format
msgid "Error marking client fd %d as nonblocking: %s"
msgstr ""
#: daemon/x52d_client.c:87
#: daemon/x52d_client.c:97
#, c-format
msgid "Error when polling socket: FD %d, error %d, len %lu"
msgstr ""
#: daemon/x52d_client.c:118
#: daemon/x52d_client.c:127
#, c-format
msgid "Error %d when polling %d descriptors: %s"
msgstr ""
#: daemon/x52d_client.c:121
#: daemon/x52d_client.c:130
msgid "Timed out when polling"
msgstr ""
@ -627,7 +627,7 @@ msgstr ""
msgid "Setting clock enable to %s"
msgstr ""
#: daemon/x52d_clock.c:35 daemon/x52d_clock.c:116
#: daemon/x52d_clock.c:35 daemon/x52d_clock.c:119
#, c-format
msgid "Setting %s clock timezone to %s"
msgstr ""
@ -648,79 +648,69 @@ msgstr ""
msgid "Unable to backup timezone environment. Falling back to UTC"
msgstr ""
#: daemon/x52d_clock.c:135
#: daemon/x52d_clock.c:138
#, c-format
msgid "Setting %s clock format to %s"
msgstr ""
#: daemon/x52d_clock.c:159
#: daemon/x52d_clock.c:162
#, c-format
msgid "Setting date format to %s"
msgstr ""
#: daemon/x52d_clock.c:170
#: daemon/x52d_clock.c:174
msgid "Starting X52 clock manager thread"
msgstr ""
#: daemon/x52d_clock.c:181
#: daemon/x52d_clock.c:185
#, c-format
msgid "Error %d retrieving current time: %s"
msgstr ""
#: daemon/x52d_clock.c:202
#: daemon/x52d_clock.c:206
#, c-format
msgid "Error %d initializing clock thread: %s"
msgstr ""
#: daemon/x52d_clock.c:209
#: daemon/x52d_clock.c:213
msgid "Shutting down X52 clock manager thread"
msgstr ""
#: daemon/x52d_command.c:367
#: daemon/x52d_command.c:379
#, c-format
msgid "Error reading from client %d: %s"
msgstr ""
#: daemon/x52d_command.c:378
#: daemon/x52d_command.c:390
#, c-format
msgid "Short write to client %d; expected %d bytes, wrote %d bytes"
msgstr ""
#: daemon/x52d_command.c:402
#: daemon/x52d_command.c:415
#, c-format
msgid "Error %d during command loop: %s"
msgstr ""
#: daemon/x52d_command.c:430
#: daemon/x52d_command.c:442
#, c-format
msgid "Error creating command socket: %s"
msgstr ""
#: daemon/x52d_command.c:437
#: daemon/x52d_command.c:450
#, c-format
msgid "Error getting command socket flags: %s"
msgid "Error marking command socket as nonblocking: %s"
msgstr ""
#: daemon/x52d_command.c:441
#, c-format
msgid "Error setting command socket flags: %s"
msgstr ""
#: daemon/x52d_command.c:449
#, c-format
msgid "Error binding to command socket: %s"
msgstr ""
#: daemon/x52d_command.c:454
#: daemon/x52d_command.c:456
#, c-format
msgid "Error listening on command socket: %s"
msgstr ""
#: daemon/x52d_command.c:464
#: daemon/x52d_command.c:460
msgid "Starting command processing thread"
msgstr ""
#: daemon/x52d_command.c:482
#: daemon/x52d_command.c:478
msgid "Shutting down command processing thread"
msgstr ""
@ -782,67 +772,67 @@ msgstr ""
msgid "Error processing override '%s.%s=%s'"
msgstr ""
#: daemon/x52d_device.c:36
#: daemon/x52d_device.c:37
msgid "Starting X52 device manager thread"
msgstr ""
#: daemon/x52d_device.c:43
#: daemon/x52d_device.c:44
#, c-format
msgid "Error %d connecting to device: %s"
msgstr ""
#: daemon/x52d_device.c:52
#: daemon/x52d_device.c:53
msgid "Device connected, writing configuration"
msgstr ""
#: daemon/x52d_device.c:75
#: daemon/x52d_device.c:76
msgid "Initializing libx52"
msgstr ""
#: daemon/x52d_device.c:79
#: daemon/x52d_device.c:80
#, c-format
msgid "Failure %d initializing libx52: %s"
msgstr ""
#: daemon/x52d_device.c:90
#: daemon/x52d_device.c:91
msgid "Shutting down X52 device manager thread"
msgstr ""
#: daemon/x52d_device.c:103
#: daemon/x52d_device.c:104
#, c-format
msgid "Error %d when updating X52 parameter: %s"
msgstr ""
#: daemon/x52d_device.c:179
#: daemon/x52d_device.c:180
#, c-format
msgid "Error %d when updating X52 device: %s"
msgstr ""
#: daemon/x52d_io.c:42
#: daemon/x52d_io.c:43
msgid "Starting X52 I/O thread"
msgstr ""
#: daemon/x52d_io.c:64
#: daemon/x52d_io.c:65
#, c-format
msgid "Error %d opening X52 I/O device: %s"
msgstr ""
#: daemon/x52d_io.c:75
#: daemon/x52d_io.c:76
#, c-format
msgid "Error %d reading from X52 I/O device: %s"
msgstr ""
#: daemon/x52d_io.c:102
#: daemon/x52d_io.c:103
#, c-format
msgid "Error %d initializing X52 I/O library: %s"
msgstr ""
#: daemon/x52d_io.c:108
#: daemon/x52d_io.c:109
#, c-format
msgid "Error %d initializing I/O driver thread: %s"
msgstr ""
#: daemon/x52d_io.c:115
#: daemon/x52d_io.c:116
msgid "Shutting down X52 I/O driver thread"
msgstr ""
@ -885,24 +875,24 @@ msgstr ""
msgid "Error writing mouse sync event"
msgstr ""
#: daemon/x52d_mouse_evdev.c:134
#: daemon/x52d_mouse_evdev.c:135
msgid "Starting X52 virtual mouse driver thread"
msgstr ""
#: daemon/x52d_mouse_evdev.c:157
#: daemon/x52d_mouse_evdev.c:158
#, c-format
msgid "Error %d initializing mouse thread: %s"
msgstr ""
#: daemon/x52d_mouse_evdev.c:164
#: daemon/x52d_mouse_evdev.c:165
msgid "Shutting down X52 virtual mouse driver thread"
msgstr ""
#: daemon/x52d_mouse_evdev.c:171
#: daemon/x52d_mouse_evdev.c:172
msgid "Virtual mouse not created. Ignoring thread state change"
msgstr ""
#: daemon/x52d_mouse_evdev.c:236
#: daemon/x52d_mouse_evdev.c:237
#, c-format
msgid "Error %d creating X52 virtual mouse: %s"
msgstr ""
@ -917,41 +907,36 @@ msgstr ""
msgid "Error marking notification socket as nonblocking: %s"
msgstr ""
#: daemon/x52d_notify.c:59
#, c-format
msgid "Error binding to notification socket: %s"
msgstr ""
#: daemon/x52d_notify.c:64
#: daemon/x52d_notify.c:56
#, c-format
msgid "Error listening on notification socket: %s"
msgstr ""
#: daemon/x52d_notify.c:73
#: daemon/x52d_notify.c:65
msgid "Error setting up notification socket"
msgstr ""
#: daemon/x52d_notify.c:90 daemon/x52d_notify.c:106
#: daemon/x52d_notify.c:81 daemon/x52d_notify.c:91
#, c-format
msgid "Error %d reading from pipe: %s"
msgstr ""
#: daemon/x52d_notify.c:148
#: daemon/x52d_notify.c:128
#, c-format
msgid "Error %d writing notification pipe: %s"
msgstr ""
#: daemon/x52d_notify.c:193
#: daemon/x52d_notify.c:174
#, c-format
msgid "Error %d creating notification pipe: %s"
msgstr ""
#: daemon/x52d_notify.c:202
#: daemon/x52d_notify.c:183
#, c-format
msgid "Error %d initializing notify thread: %s"
msgstr ""
#: daemon/x52d_notify.c:208
#: daemon/x52d_notify.c:189
#, c-format
msgid "Error %d initializing notify listener: %s"
msgstr ""
@ -966,7 +951,7 @@ msgstr ""
msgid "Argument length too long\n"
msgstr ""
#: daemon/x52ctl.c:142
#: daemon/x52ctl.c:180
#, c-format
msgid "Running in interactive mode, ignoring extra arguments\n"
msgstr ""

14
po/meson.build 100644
View File

@ -0,0 +1,14 @@
i18n.gettext(meson.project_name(),
args: [
'--directory=' + meson.project_source_root(),
'--msgid-bugs-address=https://github.com/nirenjan/libx52/issues',
'--package-name=' + meson.project_name(),
'--package-version=' + meson.project_version(),
'--copyright-holder=Nirenjan Krishnan',
'--keyword=_', '--keyword=N_',
],
languages: [
'xx_PL',
],
install: true,
)

View File

@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: libx52 0.2.3\n"
"Report-Msgid-Bugs-To: https://github.com/nirenjan/libx52/issues\n"
"POT-Creation-Date: 2022-12-25 22:03-0800\n"
"PO-Revision-Date: 2022-09-22 21:23-0700\n"
"POT-Creation-Date: 2026-03-19 00:09-0700\n"
"PO-Revision-Date: 2023-01-04 08:40-0800\n"
"Last-Translator: Nirenjan Krishnan <nirenjan@gmail.com>\n"
"Language-Team: Dummy Language for testing i18n\n"
"Language: xx_PL\n"
@ -233,26 +233,26 @@ msgstr "I/O erroray"
msgid "Read timeout"
msgstr "Eadray imeouttay"
#: evtest/ev_test.c:109
#: evtest/ev_test.c:110
#, c-format
msgid "Device ID: vendor 0x%04x product 0x%04x version 0x%04x\n"
msgstr "Eviceday IDay: endorvay 0x%04x oductpray 0x%04x ersionvay 0x%04x\n"
#: evtest/ev_test.c:113
#: evtest/ev_test.c:114
#, c-format
msgid "Device name: \"%s %s\"\n"
msgstr "Eviceday amenay: \"%s %s\"\n"
#: evtest/ev_test.c:116
#: evtest/ev_test.c:117
#, c-format
msgid "Serial number: \"%s\"\n"
msgstr "Erialsay umbernay: \"%s\"\n"
#: evtest/ev_test.c:117
#: evtest/ev_test.c:118
msgid "Testing (interrupt to exit)\n"
msgstr "Estingtay (interruptay otay exitay)\n"
#: evtest/ev_test.c:157 evtest/ev_test.c:165
#: evtest/ev_test.c:158 evtest/ev_test.c:166
#, c-format
msgid "Event @ %ld.%06ld: %s, value %d\n"
msgstr "Eventay @ %ld.%06ld: %s, aluevay %d\n"
@ -536,17 +536,17 @@ msgstr "Estingtay aracterchay 0x%02x..."
msgid "OK"
msgstr "OKay"
#: daemon/x52d_main.c:64
#: daemon/x52d_main.c:67
#, c-format
msgid "Error %d setting log file: %s\n"
msgstr "Erroray %d ettingsay oglay ilefay: %s\n"
#: daemon/x52d_main.c:80
#: daemon/x52d_main.c:83
#, c-format
msgid "Error %d installing handler for signal %d: %s"
msgstr "Erroray %d installingay andlerhay orfay ignalsay %d: %s"
#: daemon/x52d_main.c:91
#: daemon/x52d_main.c:94
#, c-format
msgid ""
"Usage: %s [-f] [-v] [-q]\n"
@ -562,112 +562,112 @@ msgstr ""
"\t[-b otifynay-ocketsay-athpay]\n"
"\n"
#: daemon/x52d_main.c:124
#: daemon/x52d_main.c:129
#, c-format
msgid "Daemon is already running as PID %u"
msgstr "Aemonday isay alreadyay unningray asay IDPay %u"
#: daemon/x52d_main.c:266
#: daemon/x52d_main.c:271
#, c-format
msgid "Unable to parse configuration override '%s'\n"
msgstr "Unableay otay arsepay onfigurationcay overrideay '%s'\n"
#: daemon/x52d_main.c:298
#: daemon/x52d_main.c:303
#, c-format
msgid "Foreground = %s"
msgstr "Oregroundfay = %s"
#: daemon/x52d_main.c:298 daemon/x52d_main.c:299
#: daemon/x52d_main.c:303 daemon/x52d_main.c:304
msgid "true"
msgstr "uetray"
#: daemon/x52d_main.c:298 daemon/x52d_main.c:299
#: daemon/x52d_main.c:303 daemon/x52d_main.c:304
msgid "false"
msgstr "alsefay"
#: daemon/x52d_main.c:299
#: daemon/x52d_main.c:304
#, c-format
msgid "Quiet = %s"
msgstr "Uietqay = %s"
#: daemon/x52d_main.c:300
#: daemon/x52d_main.c:305
#, c-format
msgid "Verbosity = %d"
msgstr "Erbosityvay = %d"
#: daemon/x52d_main.c:301
#: daemon/x52d_main.c:306
#, c-format
msgid "Log file = %s"
msgstr "Oglay ilefay = %s"
#: daemon/x52d_main.c:302
#: daemon/x52d_main.c:307
#, c-format
msgid "Config file = %s"
msgstr "Onfigcay ilefay = %s"
#: daemon/x52d_main.c:303
#: daemon/x52d_main.c:308
#, c-format
msgid "PID file = %s"
msgstr "IDPay ilefay = %s"
#: daemon/x52d_main.c:304
#: daemon/x52d_main.c:309
#, c-format
msgid "Command socket = %s"
msgstr "Ommandcay ocketsay = %s"
#: daemon/x52d_main.c:305
#: daemon/x52d_main.c:310
#, c-format
msgid "Notify socket = %s"
msgstr "Otifynay ocketsay = %s"
#: daemon/x52d_main.c:316
#: daemon/x52d_main.c:321
#, c-format
msgid "Error %d blocking signals on child threads: %s"
msgstr "Erroray %d ockingblay ignalssay onay ildchay eadsthray: %s"
#: daemon/x52d_main.c:335
#: daemon/x52d_main.c:340
#, c-format
msgid "Error %d unblocking signals on child threads: %s"
msgstr "Erroray %d unblockingay ignalssay onay ildchay eadsthray: %s"
#: daemon/x52d_main.c:348
#: daemon/x52d_main.c:353
msgid "Reloading X52 configuration"
msgstr "Eloadingray X52 onfigurationcay"
#: daemon/x52d_main.c:355
#: daemon/x52d_main.c:360
msgid "Saving X52 configuration to disk"
msgstr "Avingsay X52 onfigurationcay otay iskday"
#: daemon/x52d_main.c:361
#: daemon/x52d_main.c:366
#, c-format
msgid "Received termination signal %s"
msgstr "Eceivedray erminationtay ignalsay %s"
#: daemon/x52d_main.c:378
#: daemon/x52d_main.c:383
msgid "Shutting down X52 daemon"
msgstr "Uttingshay ownday X52 aemonday"
#: daemon/x52d_client.c:32
#: daemon/x52d_client.c:37
#, c-format
msgid "Error accepting client connection on socket fd %d: %s"
msgstr "Erroray acceptingay ientclay onnectioncay onay ocketsay fday %d: %s"
#: daemon/x52d_client.c:38
#: daemon/x52d_client.c:43
#, c-format
msgid "Error marking client fd %d as nonblocking: %s"
msgstr "Erroray arkingmay ientclay fday %d asay onblockingnay: %s"
#: daemon/x52d_client.c:87
#: daemon/x52d_client.c:97
#, c-format
msgid "Error when polling socket: FD %d, error %d, len %lu"
msgstr "Erroray enwhay ollingpay ocketsay: FDay %d, erroray %d, enlay %lu"
#: daemon/x52d_client.c:118
#: daemon/x52d_client.c:127
#, c-format
msgid "Error %d when polling %d descriptors: %s"
msgstr "Erroray %d enwhay ollingpay %d escriptorsday: %s"
#: daemon/x52d_client.c:121
#: daemon/x52d_client.c:130
msgid "Timed out when polling"
msgstr "Imedtay outay enwhay ollingpay"
@ -676,7 +676,7 @@ msgstr "Imedtay outay enwhay ollingpay"
msgid "Setting clock enable to %s"
msgstr "Ettingsay ockclay enableay otay %s"
#: daemon/x52d_clock.c:35 daemon/x52d_clock.c:116
#: daemon/x52d_clock.c:35 daemon/x52d_clock.c:119
#, c-format
msgid "Setting %s clock timezone to %s"
msgstr "Ettingsay %s ockclay imezonetay otay %s"
@ -700,80 +700,70 @@ msgid "Unable to backup timezone environment. Falling back to UTC"
msgstr ""
"Unableay otay ackupbay imezonetay environmentay. Allingfay ackbay otay UTCay"
#: daemon/x52d_clock.c:135
#: daemon/x52d_clock.c:138
#, c-format
msgid "Setting %s clock format to %s"
msgstr "Ettingsay %s ockclay ormatfay otay %s"
#: daemon/x52d_clock.c:159
#: daemon/x52d_clock.c:162
#, c-format
msgid "Setting date format to %s"
msgstr "Ettingsay ateday ormatfay otay %s"
#: daemon/x52d_clock.c:170
#: daemon/x52d_clock.c:174
msgid "Starting X52 clock manager thread"
msgstr "Artingstay X52 ockclay anagermay eadthray"
#: daemon/x52d_clock.c:181
#: daemon/x52d_clock.c:185
#, c-format
msgid "Error %d retrieving current time: %s"
msgstr "Erroray %d etrievingray urrentcay imetay: %s"
#: daemon/x52d_clock.c:202
#: daemon/x52d_clock.c:206
#, c-format
msgid "Error %d initializing clock thread: %s"
msgstr "Erroray %d initializingay ockclay eadthray: %s"
#: daemon/x52d_clock.c:209
#: daemon/x52d_clock.c:213
msgid "Shutting down X52 clock manager thread"
msgstr "Uttingshay ownday X52 ockclay anagermay eadthray"
#: daemon/x52d_command.c:367
#: daemon/x52d_command.c:379
#, c-format
msgid "Error reading from client %d: %s"
msgstr "Erroray eadingray omfray ientclay %d: %s"
#: daemon/x52d_command.c:378
#: daemon/x52d_command.c:390
#, c-format
msgid "Short write to client %d; expected %d bytes, wrote %d bytes"
msgstr ""
"Ortshay itewray otay ientclay %d; expecteday %d ytesbay, otewray %d ytesbay"
#: daemon/x52d_command.c:402
#: daemon/x52d_command.c:415
#, c-format
msgid "Error %d during command loop: %s"
msgstr "Erroray %d uringday ommandcay ooplay: %s"
#: daemon/x52d_command.c:430
#: daemon/x52d_command.c:442
#, c-format
msgid "Error creating command socket: %s"
msgstr "Erroray eatingcray ommandcay ocketsay: %s"
#: daemon/x52d_command.c:437
#: daemon/x52d_command.c:450
#, c-format
msgid "Error getting command socket flags: %s"
msgstr "Erroray ettinggay ommandcay ocketsay agsflay: %s"
msgid "Error marking command socket as nonblocking: %s"
msgstr "Erroray arkingmay ommandcay ocketsay asay onblockingnay: %s"
#: daemon/x52d_command.c:441
#, c-format
msgid "Error setting command socket flags: %s"
msgstr "Erroray ettingsay ommandcay ocketsay agsflay: %s"
#: daemon/x52d_command.c:449
#, c-format
msgid "Error binding to command socket: %s"
msgstr "Erroray indingbay otay ommandcay ocketsay: %s"
#: daemon/x52d_command.c:454
#: daemon/x52d_command.c:456
#, c-format
msgid "Error listening on command socket: %s"
msgstr "Erroray isteninglay onay ommandcay ocketsay: %s"
#: daemon/x52d_command.c:464
#: daemon/x52d_command.c:460
msgid "Starting command processing thread"
msgstr "Artingstay ommandcay ocessingpray eadthray"
#: daemon/x52d_command.c:482
#: daemon/x52d_command.c:478
msgid "Shutting down command processing thread"
msgstr "Uttingshay ownday ommandcay ocessingpray eadthray"
@ -835,67 +825,67 @@ msgstr "Onay aluevay oundfay inay overrideay ingstray '%s'"
msgid "Error processing override '%s.%s=%s'"
msgstr "Erroray ocessingpray overriday '%s.%s=%s'"
#: daemon/x52d_device.c:36
#: daemon/x52d_device.c:37
msgid "Starting X52 device manager thread"
msgstr "Artingstay X52 eviceday anagermay eadthray"
#: daemon/x52d_device.c:43
#: daemon/x52d_device.c:44
#, c-format
msgid "Error %d connecting to device: %s"
msgstr "Erroray %d onnectingcay otay eviceday: %s"
#: daemon/x52d_device.c:52
#: daemon/x52d_device.c:53
msgid "Device connected, writing configuration"
msgstr "Eviceday onnectedcay, itingwray onfigurationcay"
#: daemon/x52d_device.c:75
#: daemon/x52d_device.c:76
msgid "Initializing libx52"
msgstr "Initializingay libx52"
#: daemon/x52d_device.c:79
#: daemon/x52d_device.c:80
#, c-format
msgid "Failure %d initializing libx52: %s"
msgstr "Ailurefay %d initializeay libx52: %s"
#: daemon/x52d_device.c:90
#: daemon/x52d_device.c:91
msgid "Shutting down X52 device manager thread"
msgstr "Uttingshay ownday X52 eviceday anagermay eadthray"
#: daemon/x52d_device.c:103
#: daemon/x52d_device.c:104
#, c-format
msgid "Error %d when updating X52 parameter: %s"
msgstr "Erroray %d enwhay updatingay X52 arameterpay: %s"
#: daemon/x52d_device.c:179
#: daemon/x52d_device.c:180
#, c-format
msgid "Error %d when updating X52 device: %s"
msgstr "Erroray %d enwhay updatingay X52 eviceday: %s"
#: daemon/x52d_io.c:42
#: daemon/x52d_io.c:43
msgid "Starting X52 I/O thread"
msgstr "Artingstay X52 I/O eadthray"
#: daemon/x52d_io.c:64
#: daemon/x52d_io.c:65
#, c-format
msgid "Error %d opening X52 I/O device: %s"
msgstr "Erroray %d openingay X52 I/O eviceday: %s"
#: daemon/x52d_io.c:75
#: daemon/x52d_io.c:76
#, c-format
msgid "Error %d reading from X52 I/O device: %s"
msgstr "Erroray %d eadingray omfray X52 I/O eviceday: %s"
#: daemon/x52d_io.c:102
#: daemon/x52d_io.c:103
#, c-format
msgid "Error %d initializing X52 I/O library: %s"
msgstr "Erroray %d initializingay X52 ibrarylay: %s"
#: daemon/x52d_io.c:108
#: daemon/x52d_io.c:109
#, c-format
msgid "Error %d initializing I/O driver thread: %s"
msgstr "Erroray %d initializingay I/O iverdray eadthray: %s"
#: daemon/x52d_io.c:115
#: daemon/x52d_io.c:116
msgid "Shutting down X52 I/O driver thread"
msgstr "Uttingshay ownday X52 I/O iverdray eadthray"
@ -938,24 +928,24 @@ msgstr "Erroray itingwray ousemay axisay eventay (axisay %d, aluevay %d)"
msgid "Error writing mouse sync event"
msgstr "Erroray itingwray ousemay yncsay eventay"
#: daemon/x52d_mouse_evdev.c:134
#: daemon/x52d_mouse_evdev.c:135
msgid "Starting X52 virtual mouse driver thread"
msgstr "Artingstay X52 irtualvay ousemay iverdray eadthray"
#: daemon/x52d_mouse_evdev.c:157
#: daemon/x52d_mouse_evdev.c:158
#, c-format
msgid "Error %d initializing mouse thread: %s"
msgstr "Erroray %d initializingay ousemay eadthray: %s"
#: daemon/x52d_mouse_evdev.c:164
#: daemon/x52d_mouse_evdev.c:165
msgid "Shutting down X52 virtual mouse driver thread"
msgstr "Uttingshay ownday X52 irtualvay ousemay iverdray eadthray"
#: daemon/x52d_mouse_evdev.c:171
#: daemon/x52d_mouse_evdev.c:172
msgid "Virtual mouse not created. Ignoring thread state change"
msgstr "Irtualvay ousemay otnay eatedcray. Ignoringa eadthray atestay angechay"
#: daemon/x52d_mouse_evdev.c:236
#: daemon/x52d_mouse_evdev.c:237
#, c-format
msgid "Error %d creating X52 virtual mouse: %s"
msgstr "Erroray %d eatingcray X52 irtualvay ousemay: %s"
@ -970,41 +960,36 @@ msgstr "Erroray eatingcray otificationnay ocketsay: %s"
msgid "Error marking notification socket as nonblocking: %s"
msgstr "Erroray arkingmay otificationnay ocketsay asay onblockingnay: %s"
#: daemon/x52d_notify.c:59
#, c-format
msgid "Error binding to notification socket: %s"
msgstr "Erroray indingbay otay otificationnay ocketsay: %s"
#: daemon/x52d_notify.c:64
#: daemon/x52d_notify.c:56
#, c-format
msgid "Error listening on notification socket: %s"
msgstr "Erroray isteninglay onay otificationnay ocketsay: %s"
#: daemon/x52d_notify.c:73
#: daemon/x52d_notify.c:65
msgid "Error setting up notification socket"
msgstr "Erroray ettingsay upay otificationnay ocketsay: %s"
#: daemon/x52d_notify.c:90 daemon/x52d_notify.c:106
#: daemon/x52d_notify.c:81 daemon/x52d_notify.c:91
#, c-format
msgid "Error %d reading from pipe: %s"
msgstr "Erroray eadingray omfray ipepay %d: %s"
#: daemon/x52d_notify.c:148
#: daemon/x52d_notify.c:128
#, c-format
msgid "Error %d writing notification pipe: %s"
msgstr "Erroray %d itingwray otificationnay ipepay: %s"
#: daemon/x52d_notify.c:193
#: daemon/x52d_notify.c:174
#, c-format
msgid "Error %d creating notification pipe: %s"
msgstr "Erroray %d eatingcray otificationnay ipepay: %s"
#: daemon/x52d_notify.c:202
#: daemon/x52d_notify.c:183
#, c-format
msgid "Error %d initializing notify thread: %s"
msgstr "Erroray %d initializingay otifynay eadthray: %s"
#: daemon/x52d_notify.c:208
#: daemon/x52d_notify.c:189
#, c-format
msgid "Error %d initializing notify listener: %s"
msgstr "Erroray %d initializingay otifynay istenerlay: %s"
@ -1019,7 +1004,7 @@ msgstr "Usageay: %s [-i] [-s ocketsay-athpay] [ommandcay]\n"
msgid "Argument length too long\n"
msgstr "Argumentay engthlay ootay onglay\n"
#: daemon/x52ctl.c:142
#: daemon/x52ctl.c:180
#, c-format
msgid "Running in interactive mode, ignoring extra arguments\n"
msgstr "Unningray inay interactiveay odemay, ignoringay extraay argumentsay\n"

View File

@ -4,5 +4,5 @@
#
# SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
SUBDIRS = pinelog inih
SUBDIRS = pinelog

View File

@ -41,7 +41,7 @@ static void print_time_difference(const char *type, struct timespec *ts)
type, tp_usec, tp_nsec, ret.tv_sec, ret.tv_nsec);
}
int main(int argc, char **argv)
int main(void)
{
struct timespec ts_wall[2];
struct timespec ts_cpu[2];

Some files were not shown because too many files have changed in this diff Show More