Compare commits

...

580 Commits

Author SHA1 Message Date
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
nirenjan d4412aba3e Update po files for v0.3.0 2022-12-25 22:08:23 -08:00
nirenjan e08c46f6c4 Update for release 0.3.0 2022-12-25 21:57:28 -08:00
nirenjan 708ace20be Fix builds on macOS 2022-09-23 21:15:19 -07:00
nirenjan 4b411d6767 Add notifications when device is connected/disconnected 2022-09-23 15:15:49 -07:00
nirenjan 8b2c0e4a2f Add test cases to verify libx52 identifier to string conversion 2022-09-23 14:28:19 -07:00
nirenjan b810c457f9 Use x52ctl to test daemon communication
This allows us to test x52ctl as well as the daemon communication
infrastructure.
2022-09-23 10:02:17 -07:00
nirenjan ccabb5c953 Add test case for setting unknown configuration value 2022-09-22 22:14:44 -07:00
nirenjan b2f292bf58 Cleanup config dump routine 2022-09-22 21:31:49 -07:00
nirenjan 7f59984357 Ensure notification socket is bound when starting daemon 2022-09-22 21:28:20 -07:00
nirenjan c2c2e91089 Update translation files 2022-09-22 14:45:31 -07:00
nirenjan d60ab7e1e4 Add logging test cases to EXTRA_DIST 2022-09-22 14:45:15 -07:00
nirenjan 2b7c643537 Add test cases for logging command 2022-09-22 14:23:30 -07:00
nirenjan d96b86a817 Return module name in "logging show" command 2022-09-22 14:23:13 -07:00
nirenjan fea095dc50 Add notify module to logging list
The notify module controls the logging settings for the notify socket
code. While missing this doesn't cause any impact to the notify code, an
invalid module name sent on the command socket could cause the daemon to
crash with a NULL pointer access.
2022-09-22 14:21:14 -07:00
nirenjan 1dcadb9428 Rename test cases and add them to EXTRA_DIST 2022-09-22 11:21:53 -07:00
nirenjan ecfb865c58 Fix pattern for daemon tests and sort by filename 2022-09-22 09:39:30 -07:00
nirenjan b1c7a16eac Update daemon documentation to include notify socket
[skip ci]
2022-09-21 20:08:58 -07:00
nirenjan b38a75462d Update translations for new files 2022-09-21 09:31:53 -07:00
nirenjan 619d516ccc Add notify API
This change adds a notification API to libx52dcomm. It also pulls in the
logic to join multiple arguments into a single buffer for later use.
2022-09-06 23:19:03 -07:00
nirenjan 88159d4fc5 Disable deprecated Github-hosted runners 2022-09-02 23:03:53 -07:00
nirenjan c3a4fea139 Update int_parser to reject invalid input 2022-09-01 20:50:36 -07:00
nirenjan 1d1e5781c2 Merge branch 'fix-hotplug' 2022-08-07 09:15:04 -07:00
nirenjan 1a02ad22d9 Handle libusb events when checking if device is connected
libusb requires the application to call one of the
`libusb_handle_events` functions in order for hotplug events to actually
get dispatched. We don't need to wait for any timeout, so we use a
default timeout of 0, which should process any pending events, and then
return immediately.

A test of repeatedly disconnecting and reconnecting a virtual device
using USBIP was done, and no crashes of the daemon were observed.

Github-Issue: https://github.com/nirenjan/libx52/issues/43
2022-08-06 16:49:28 -07:00
nirenjan b822d3aed8 Move command processing into separate thread
This will help in moving a lot of the functionality out of the main
thread, and limit the main thread to just signal handling.
2022-07-27 12:47:53 -07:00
nirenjan dfa78ff2a9 Remove deprecated macos-10.15 runner
See: actions/virtual-environments#5583

References:
- https://github.com/actions/virtual-environments/issues/5583
2022-07-27 11:40:48 -07:00
nirenjan d3d32cf278 Block signals on child threads
This change adds logic to block signals on child threads. This is done
so that we can migrate signal handling to the main thread, even if we
add additional threads in the future.
2022-07-27 11:33:44 -07:00
nirenjan b0f9006594 Update Changelog.md
[skip ci] [skip doxy]
2022-07-24 21:55:38 -07:00
nirenjan 35f5da6b50 Upgrade CodeQL actions to v2
See: https://github.blog/changelog/2022-04-27-code-scanning-deprecation-of-codeql-action-v1/
2022-07-03 21:59:00 -07:00
nirenjan 41812e3c1e Add unit tests for mouse reverse scroll 2022-07-03 16:00:13 -07:00
nirenjan 95a10b5ac9 Add option to reverse mouse scroll direction
This change adds a ReverseScroll parameter to the configuration, which
if set, will change the direction of the scroll wheel of the virtual
mouse.

Github-Issue: #45
2022-06-14 09:36:13 -07:00
nirenjan d0f0232dae Add beta versions of runners to CI build
This change adds support for the public beta of Ubuntu 22.04 and
macOS 12. As this is a beta, this change also updates the
continue-on-error field to indicate that a build failure on the 22.04
runner should not cause the rest of the build to fail.

[skip doxy]
2022-06-13 01:58:46 -07:00
nirenjan ba9348b888 Include locale.h in gettext.h
When building with coverage enabled, the compiler complains about
missing definitions for setlocale and LC_ALL. These can be found in
locale.h, and by including it in gettext.h, we can ensure that we won't
see any more such issues.
2022-06-13 01:45:34 -07:00
nirenjan 0ac3a3d0c4 Add additional tests for x52cli
This change adds additional tests to verify the parser behavior, as well
as the handling of any error conditions from libx52 itself.
2022-06-12 03:37:21 -07:00
nirenjan 552e638b27 Remove obsolete check for invalid handler
Since the introduction of X macros to the CLI handling code, there will
never be a condition where the handler will be NULL. This is a legacy
check from when the code had individual maps that were maintained by
hand.
2022-06-12 03:34:53 -07:00
nirenjan 44fba0c6cd Always return 1 from x52cli for any failure
Prior to this change, on failure condition, x52cli would exit with a
code that mapped directly to the return code of the function. This was
inconsistent with the behaviour if the initialization or connect steps
failed for any reason.
2022-06-12 03:32:25 -07:00
nirenjan a119fe2c60 Fix translation objects 2022-05-16 10:48:07 -07:00
nirenjan 8c8f261c80 Disable use of PID file when running in foreground
When running in foreground, it is likely that it is running within
systemd. In this case, a stale PID file is likely to have a PID that
corresponds to a different process, which can still be kill'ed by the
root user. This results in a false positive that the process is still
running and causes the daemon to abort prematurely.

Fixes #42
2022-05-16 10:33:04 -07:00
nirenjan 6a8dff0a17 Fix mouse test linker failure on macOS 2022-04-28 02:18:13 -07:00
nirenjan 8f9ab9cefd Add tests for mouse configuration
This change adds a test suite for validating mouse configuration. This
only tests the logic for mouse thread enable/disable and mouse speed
calculations.
2022-04-28 01:54:09 -07:00
nirenjan 3168061c99 Add output of x52bugreport to CI run 2022-04-27 00:10:27 -07:00
nirenjan 24ce9bc97b Generate Git version info at call to make
Prior to this change, the Git version was only getting generated when
the call was made to autoreconf. This was resulting in stale version
info.

This change, by contrast, generates the build info at the call to make.
While this doesn't completely eliminate the stale version issue, it's
easier to work around, by simply running `make clean`, `make`.
2022-04-27 00:05:02 -07:00
nirenjan 251ccfde0d Close command socket in test harness before terminating daemon 2022-04-08 22:59:37 -07:00
nirenjan 611bc9d965 Add mouse speed test cases 2022-04-08 17:30:34 -07:00
nirenjan 69426c62d8 Add tests for LED parser 2022-04-08 17:00:45 -07:00
nirenjan 054df1ab7a Cleanup libx52io init/open calls 2022-04-08 16:47:46 -07:00
nirenjan 527d4d5a1e Run daemon communication tests only on Linux
For some reason, the CI runs on Github Actions block the macOS runners,
and they take forever to run the tests. The tests are really needed only
on Linux, and the errno values that it uses are only verified to work on
Linux.
2022-04-08 00:26:44 -07:00
nirenjan c87c7caa68 Add test cases for clock configuration 2022-04-08 00:16:57 -07:00
nirenjan 0f7cd3fdb3 Add test cases for config get/set with wrong argc 2022-04-08 00:16:39 -07:00
nirenjan 33e940606c Add daemon communication test cases
This change adds an automated test harness that will spin up an instance
of the X52 daemon, connect to its command socket, send commands and
validate the responses. This first set of test cases simply validates
the basic configuration file handling. Subsequent commits will enhance
the tests to improve code coverage.
2022-04-07 23:24:24 -07:00
nirenjan f0ad185421 Fix formatted responses to include the trailing NUL 2022-04-02 08:23:55 -07:00
nirenjan 98822190ed Add a callback to apply one configuration item immediately
The configuration processing works as follows - the configuration
parameters are set in memory, and an apply function applies all the
changes directly to the device. While this works during startup and
reload, it adds a needless overhead when processing config set commands
from the command socket.

This change makes it such that a `config set` command would update the
configuration for that particular key, and then immediately call the
callback function for that key. This has the effect that individual
configuration changes are visible immediately, without having to reapply
every other configuration that hasn't changed.

This commit also removes the `config apply` command, since it is no
longer needed. The `config load` and `config reload` already handle
applying the configuration after reading it.
2022-02-11 13:30:17 -08:00
nirenjan cf6811d923 Check if mouse is enabled before reporting buttons
Prior to this change, the IO thread would always report a mouse button
event, even if the mouse was disabled in configuration, or the uinput
device was not created. This results in unexpected button/wheel events.

This change checks that the uinput device has been created and the mouse
has been enabled in the configuration before reporting a button or wheel
event.
2022-02-03 11:11:41 -08:00
nirenjan 5ab4784b21 Remove unnecessary code from x52d_config_save_file
This is a legacy from when there was a separate print_section function
2021-11-22 10:08:21 -08:00
nirenjan 385f1ca574 Fix typo in logging error handler 2021-11-22 10:06:23 -08:00
nirenjan cf89e3d610 Override `time` from libc
Prior to this change, there were spurious build failures seen in Github
CI, especially on macOS builds, where one test out of ~2000 would fail
randomly. After adding an option to display the diagnostics, it was
determined that the failure was only in the tests that included the
timestamp. This was because the time call was returning different values
between `test_setup` and `pinelog_log_message`. Even though they may
have been called milliseconds apart, the time skew was enough to have a
1 second difference between the two returned values.

This change overrides the `time` method from libc, and returns a static
value. With this change, CI should work fine regardless of how slow the
tests run.
2021-11-11 07:02:21 -08:00
nirenjan e9d4e81a4d Display TAP diagnostics in build log 2021-11-11 06:21:20 -08:00
nirenjan 3896fa0d3e Update protocol documentation for logging command 2021-11-10 22:21:47 -08:00
nirenjan f6c6db6c61 Add command processing for logging configuration
This change adds the `logging show` and `logging set` commands to the
command processor module. This allows setting the log levels of the
individual modules via the socket.
2021-11-10 21:49:54 -08:00
nirenjan 2119e00647 Integrate pinelog module logging functionality 2021-11-10 09:29:27 -08:00
nirenjan f4a8e7c4d5 Do not print NULL module names
Prior to this change, if a module had not registered a name, then the
output would display `(null): `, which was not really helpful. This
change eliminates that by checking if the module name is not NULL prior
to calling the print routines.
2021-11-10 09:27:40 -08:00
nirenjan 417641dee0 Use string buffer when logging messages 2021-11-08 16:22:56 -08:00
nirenjan 86f599fc6e Update logging library to include module and string buffer
This commit adds the following changes to pinelog:

- Optional buffer to write the message to prior to writing to the output
  stream. This reduces the likelihood of log messages from multiple
  threads interleaving due to multiple calls to fputs/fprintf, etc. The
  default is to still write directly to the output stream, but the
  integrator can add a define of PINELOG_BUFFER_SZ to the CFLAGS, and
  this will allow the application to log messages that are shorter than
  the above size, including the timestamp, level and backtrace if any.

- Optional module level logging. This allows more fine-grained
  debugging, where the application can control the log levels of the
  individual modules. By default, when modules are configured, they
  default to the global log level, but this can be overridden by the
  application.
2021-11-08 16:18:30 -08:00
nirenjan 09740e0fe9 Update documentation to include communication protocol 2021-11-07 16:38:06 -08:00
nirenjan 7448334824 Add `config apply` command 2021-11-07 16:06:49 -08:00
nirenjan f2b0110380 Allow upto 1024 arguments
Prior to this change, it was possible for a malicious client to send a
buffer of 1024 NUL bytes, which would cause the parser to overflow the
argv array and eventually crash the program.

This change makes the length of the argv array the same as the length of
the recv buffer, which means that even an input of all 0 bytes would not
cause any issues. The client would just get a bunch of ERR responses in
return.
2021-11-07 15:07:10 -08:00
nirenjan 87bf5881dd Update Changelog to include info about communication 2021-11-07 07:55:48 -08:00
nirenjan e82f9032eb Add 'config get' command to retrieve configuration
The command allows the client to retrieve individual parameters from the
configuration. This follows a similar syntax to the 'config set'
command, with the client supplying the section and key, and if there is
a matching entry in the configuration, it will return the corresponding
value.
2021-11-07 07:52:11 -08:00
nirenjan 2fe7b8af43 Cleanup response protocol
This change removes the unnecessary response length field from the
output buffer. It was added to check for possible issues in the
communication between client and server, but is not needed anymore. This
also makes the communication protocol standardized between client and
server.
2021-11-07 05:47:31 -08:00
nirenjan 20abe8974c Fix x52d_send_command prototype
Prior to this change, the recv call was using the same buflen as that of
the send, which meant that the response would be truncated at by the
client, while the server was sending the entire message. This was
evident by running a Python client which manually called recv with the
maximum buffer size.

This change updates the prototype to take both a bufin (length of the
input buffer), and a bufout (length of the output buffer) argument,
instead of a single buflen. With this change, commands work as expected
in x52ctl.
2021-11-07 05:40:08 -08:00
nirenjan 116b9e2c0c Add config set command to daemon command processor
This change allows setting the configuration from the socket. It behaves
similar to the override, and requires the client to send the section,
key and value, and responds with an OK or error value.
2021-11-05 15:27:24 -07:00
nirenjan 7b8c71dd35 Add command parsing and response logic
This change adds the generic command parsing logic, as well as the
response for the following commands:

* config load <file>
* config reload
* config save
* config dump <file>
2021-11-05 15:10:20 -07:00
nirenjan eb98804607 Make x52ctl send commands as individual args
This change makes the protocol more strict, in that the buffer sent to
the daemon must be a series of NUL separated arguments. This makes it a
little easier to handle the strings, especially those that may have
embedded whitespace.
2021-11-04 22:37:43 -07:00
nirenjan d8a5a2c3b8 Add daemon command handler loop
This change adds the logic to read a packet from the socket, accept
connections from clients, and close connections from clients that have
hung up. This commit does not yet have support for parsing and handling
the commands, and simply echoes the request back to the client.
2021-11-04 18:32:18 -07:00
nirenjan 5a78492140 Make X52 daemon listen on a Unix socket
This change makes X52 daemon listen on a Unix socket. This is in
preparation for changes that will read from the socket and allow clients
to communicate with and control the daemon.
2021-11-04 13:48:18 -07:00
nirenjan abc74d6e37 Fix linking with libintl on macOS 2021-11-04 11:05:52 -07:00
nirenjan 3225d37e6e Add translations for x52ctl.c 2021-11-04 10:52:14 -07:00
nirenjan 931f945133 Add x52ctl daemon communication program 2021-11-04 10:50:10 -07:00
nirenjan 18c0c72c74 Add client communication library
This change adds a library to connect to the X52 daemon and send
commands and receive responses. The library is a thin wrapper around the
POSIX sockets API. While a client could implement the functions
themselves, the library makes it a little bit easier, as well as
allowing for third-party clients to connect to and communicate with the
daemon.
2021-11-04 10:48:59 -07:00
nirenjan 293ba0a99d Remove duplicate check for default PID file 2021-11-03 11:44:24 -07:00
nirenjan 52429e8dc3 Add translations for x52d_config_dump.c 2021-11-03 11:39:19 -07:00
nirenjan 6c3efa44f5 Simplify configuration dumping
Prior to this change, there was a lot of duplicated code within the dump
routines, which would call out to a common `print_section` routine that
had global state. This causes problems from a multi-threaded perspective
in that multiple calls to `x52d_config_save_file` were not MT-safe.

In addition, the dump logic was written such that it could only be used
in the config dump. It is desired that we add functionality to return
the formatted config value as a string in a different part of the code
as well.

This change brings in the shared state into a stack variable, and
changes the dump functions to return a const char *, thereby allowing
for greater reuse, as well as getting rid of the shared state. However,
there is still a little bit of shared state in the `int_dumper` routine.
This can be ignored for now, but we should possibly figure out how to
get rid of the shared state altogether.
2021-11-02 23:25:55 -07:00
nirenjan 6d78ab1940 Add noreturn attribute to tap_bailout
This change is necessary since clang warns on a missing noreturn
attribute.
2021-10-25 12:56:41 -07:00
nirenjan 3c006d0929 Merge commit '6c17e73284fbe49a6bc7890d3e221b95db29d56f' 2021-10-25 12:51:04 -07:00
nirenjan 6c17e73284 Squashed 'lib/pinelog/' changes from 0256807..349b2d6
349b2d6 Use an in-memory pipe for tests

git-subtree-dir: lib/pinelog
git-subtree-split: 349b2d6e8610ca6c62cfc9211c50ded52ebe38ed
2021-10-25 12:51:04 -07:00
nirenjan 581d2c0bbd Replace macos-latest with macos-10.15
This is necessary since Github will soon change macos-latest to point
to macos-11
2021-10-12 12:15:27 -07:00
nirenjan 8d38c4d16b Fix Makefile to build on alternative make implementations
On some systems (notably FreeBSD), the make implementation makes a
distinction between $(builddir)/<path> and <path>, even when builddir is
`.`. This commit removes $(builddir) from all references to generated
files, so that these implementations don't fail. Keeping $(builddir)
causes older versions automake (1.15 and older) to generate a broken
Makefile.
2021-10-12 12:10:36 -07:00
nirenjan 41be44cc94 Add documentation for x52bugreport 2021-10-11 09:54:27 -07:00
nirenjan 2cc3cc5bfe Update bug issue template to use x52bugreport output 2021-10-11 07:22:29 -07:00
nirenjan 8fd16d544a Add bugreport utility
This change adds a utility that generates some output that can be used
to figure out what the user system is, specifically, the versions of the
following:

* libusb
* hidapi (if available)
* kernel
* device info

These parameters can be used to determine if the user is running some
non-standard environment, and make it easier to track down bugs.
2021-10-11 07:22:21 -07:00
nirenjan 1c0d98c474 Fix link to AUR package
Fixes #39
[skip ci] [skip doxy]
2021-10-10 08:35:57 -07:00
nirenjan ad178d3f6d Make doxygen CI build use release parameters.
The previous commit used the value of `sysconfdir` to store the location
of the daemon configuration file inside the generated documentation.
However, the automatically generated documentation was using the default
values, which meant that the config file was located at
`/usr/local/etc/x52d/x52d.conf`, instead of just `/etc/x52d/x52d.conf`.
Updating the arguments to `./configure` to match that of the release
ensures that it will always stay up to date.
2021-10-07 01:14:05 -07:00
nirenjan d9eed14e0d Include default location of x52d.conf in documentation
This change adds the default location of x52d.conf in the release builds
to the public documentation.
2021-10-06 23:44:16 -07:00
nirenjan fec67b5994 Revert "Enable verbose logging in systemd service"
This reverts commit 53957d0813. It was
supposed to be a temporary commit, but it was forgotten about. Reverting
this will restore the old behavior of only logging INFO and higher
priority logs.

Fixes #38.
2021-10-05 22:38:34 -07:00
nirenjan 247e98c5dc Add kernel driver for Saitek X65F joystick
This commit adds the kernel driver for the Saitek X65F joystick. This is
necessary, since it has the same limitation with the thumbstick not
getting recognized on older kernels. The quirks fix has been pushed to
newer stable kernels, and therefore do not require this driver.
2021-09-29 12:59:34 -07:00
nirenjan 9a39e971f1 Disable Ubuntu 16.04 builds in kernel workflow
[skip ci]
2021-09-29 00:35:51 -07:00
nirenjan e7d91fd3a4 Replace all references to x52pro-linux with libx52 2021-09-29 00:30:55 -07:00
nirenjan f51985dd20 Update main documentation page to reflect use as driver
This change de-emphasizes the use of libx52 as a library, and emphasizes
it as a driver for users to be able to utilize the full capabilities of
the X52 joystick.
2021-09-28 21:26:40 -07:00
nirenjan b45dc59ae0 Add link to AUR
[skip ci] [skip doxy]
2021-09-23 09:47:57 -07:00
nirenjan aef1b6fade Remove rsync dependency and add Fedora packages
This change eliminates the dependency on rsync to copy the Doxygen
generated files, and instead falls back to using `cp -R -P`. Since the
generated HTML needs no special permissions, `cp` is more than
sufficient, and it's already installed on all Unix systems as a core
utility.
2021-09-22 23:37:05 -07:00
nirenjan 794b09e766 Rename package to libx52
Prior to this change, all the generated HTML documentation and locale
files were using the `x52pro-linux` name. That name is no longer
reflective of the project, since it works on macOS and with the non-Pro
X52 as well.

This change is also reflective of the patch used in the released PPA,
which takes care of this anyway, but is being added to the sources to
better integrate with PKGBUILD/AUR/MPR.
2021-09-22 02:46:13 -07:00
nirenjan ba936df6f8 Add Version file and associated scripts
When building from source, it is desired that we embed the version
string into the resulting binaries so that we can determine exactly what
version of the sources were used.

This change adds a Version file, which always holds the latest release
version, and a configure.version script, which tries to get the version
information from Git, before falling back to using the version embedded
in the above file. The generated configure script will then have the
version embedded within it, which will then create the BUILD_VERSION
definition in config.h. Applications can then use this definition as
needed.
2021-09-22 01:37:57 -07:00
nirenjan 5f21ccd2e9 Update for release 0.2.3 2021-09-20 13:24:14 -07:00
nirenjan 3374bb98dc Add instructions to install from PPA
[skip ci] [skip doxy]
2021-09-20 10:16:17 -07:00
nirenjan 00ed62b72e Update translation templates to reflect new version 2021-09-19 16:46:26 -07:00
nirenjan a5a25c307c Update version in configure.ac 2021-09-19 16:41:35 -07:00
nirenjan fe54730447 Update bug template and contributing guidelines
[skip ci]
2021-09-19 16:39:49 -07:00
nirenjan 4d2736e03c Indicate profiles are not yet supported.
See discussion in #37.

[skip ci]
2021-09-19 16:39:49 -07:00
nirenjan 4f427b2ebe Update build instructions
[skip ci]
2021-09-19 16:39:49 -07:00
nirenjan e8abbd0374 Allow for a greater range in mouse speeds
Prior to this change, the virtual mouse update was restricted to
updating once every `mouse_delay` microseconds, and the allowed values
were a small fixed set. Some users reported that even at the highest
speed, the speed was slower than they were used to (with a high DPI
mouse).

This change modifies the speed calculation algorithm as follows. It
keeps the slowest speed to refresh the mouse every 70 ms. As the speed
increases, the refresh rate drops by 5 ms for every increment in speed,
until the refresh rate caps at once every 10 ms. Beyond that, a
multiplicative factor begins to take effect, with each speed increase
adding 0.25 to the factor. That is, speed 13 would multiply the axis
components by 1.25 _and_ refresh every 10 ms. Speed 14 would bump the
factor to 1.50, speed 15 to 1.75, and so on, until the factor tops out
at 6.0.
2021-09-19 16:39:49 -07:00
nirenjan d8fc859e44 Change mouse speed parameters
Prior to this change, the mouse delays were between 50 ms to 250 ms,
with a difference of 50 ms between steps. Unfortunately, this was too
slow at lower speeds, therefore, the delays have been changed to vary
from 30 ms to 70 ms with a difference of 10 ms between steps. This gives
a much smoother mouse response.
2021-09-15 09:25:26 -07:00
nirenjan 4365e86f2a Fix logging indicating mouse speed range 2021-09-15 09:17:15 -07:00
nirenjan 16e53b897f Use $(localstatedir)/run instead of $(runstatedir)
`runstatedir` is only available in Autoconf 2.70, but unless the
distribution is a bleeding edge system, it most likely uses Autoconf
2.69. That said, several major distributions have backported runstatedir
support to the older versions, hiding the issue. See #35.

This change replaces all references to runstatedir to use
$localstatedir/run instead, which is what is recommended by the autoconf
manual.

This also updates the build instructions to add --localstatedir and
--sysconfdir. This is because the lack of the options would have them
default to `$(prefix)/var` and `$(prefix)/etc` respectively, and with
prefix set to `/usr`, these would be the bogus directories `/usr/var`
and `/usr/etc`.
2021-09-15 09:06:31 -07:00
nirenjan 8deb6a1513 Merge device acquisition and update threads 2021-09-15 00:17:20 -07:00
nirenjan c56f715155 Fix use of libx52io return codes
Prior to this change, we were treating ERROR_NO_DEVICE as if the hidapi
library itself would return such a code. However, that is not the case,
and we should be treating any error condition as a critical issue and
treat it as if the device was disconnected. The worst case scenario is
that it would have to re-enumerate the HID device list and reopen the
joystick.
2021-09-15 00:06:43 -07:00
nirenjan 025a06351a Apply mouse button events immediately
Prior to this change, the button change events were only happening on
periodic intervals corresponding to the change in the mouse REL_X and
REL_Y values. However, this has the issue that it tends to miss a few
events, especially those related to the scroll wheel.

This change reports button and wheel events immediately when receiving
the report, but it leaves the motion to be updated by the thread.
2021-09-14 23:35:05 -07:00
nirenjan 53957d0813 Enable verbose logging in systemd service 2021-09-14 17:34:20 -07:00
nirenjan 2a8ca8424e Reset reports on thread create and device disconnection
This ensures that if the device gets disconnected while the axis is held
outside of the default position, the axis will get reset.
2021-09-14 17:34:07 -07:00
nirenjan 276b512478 Update dependencies and changelog 2021-09-14 17:18:46 -07:00
nirenjan 7f29f5f5fe Add mouse update thread
This change adds a thread to translate thumbstick events to a virtual
mouse.
2021-09-14 17:08:01 -07:00
nirenjan 42850bc4cd Create I/O thread to read and process events
This change adds a separate thread to initialize and read reports from
the supported X52 device. This will then process and raise input events
for a virtual device.
2021-09-14 13:33:36 -07:00
nirenjan b9e5f34aa4 Add support for building on macOS
Prior to this change, the build would fail on macOS systems because the
evdev sources were only included on Linux systems, and macOS does not
have evdev/libevdev. By separating out the configuration and update
threads, this should build on macOS, but the configuration would be
ignored.
2021-09-14 10:40:48 -07:00
nirenjan 016851478a Add framework for virtual mouse driver
This change adds the configuration and build related changes for
supporting the virtual mouse. Subsequent commits will add support for
reading the IO interface and translating it to mouse commands.
2021-09-14 10:02:21 -07:00
nirenjan 8874a282aa Add configure check for libevdev
This change is the first in a series of commits to support a virtual
mouse controlled by means of the thumbstick on the throttle unit.
2021-09-14 09:34:11 -07:00
nirenjan 3a81acf828 Fix build with newer version of libusb 2021-09-14 09:28:00 -07:00
nirenjan fa1d54f9da Add CI support for macos-11 2021-09-14 09:21:55 -07:00
nirenjan 7d757dd40f Disable device check routine in daemon
Prior to this change, the device check was sending a vendor specific
command with wIndex and wValue both set to 0 every 50 ms. On some
systems, this was causing issues with the joystick flapping the state,
and reporting weird values from the stick, and generally sluggish
response.

This change uses the updated libx52 library which uses the hotplug
notification to determine if the device is connected, and should resolve
the issues seen.

Fixes #33
2021-09-14 09:10:52 -07:00
nirenjan 627c1fb004 Use libusb hotplug API to automatically detect disconnection
Prior to this commit, the libx52_is_connected API was simply checking if
the device handle was non-NULL. However, this was insufficient, since
the device disconnection would not reset the handle, and was relying on
the daemon to manually disconnect.

The libusb hotplug API provides functionality to register a callback on
device insertion/removal. libx52 only registers for removal, and will
automatically disconnect the device on receiving the callback. This also
modifies libx52_is_connected to fallback to checking if the kernel
driver is active if the linked libusb does not support hotplug (unlikely).

Finally, this commit adds support for the new hotplug related functions
to the libusbx52 preload library. While the preload library doesn't
actually support hotplug, it is sufficient to pretend that it does.
2021-09-14 09:03:23 -07:00
nirenjan f2884c57b7 Print integer representation of LED state when setting it 2021-09-13 15:49:20 -07:00
nirenjan 5fcac86999 Fix gitignore to ignore generated files
[skip ci]
2021-09-03 12:25:41 -07:00
nirenjan a3cc0adb84 Update for release 0.2.2 2021-09-03 11:31:48 -07:00
nirenjan f34f84a3ee Add routine to check device connectivity
Prior to this change, if the clock thread is disabled, then
disconnecting and reconnecting the X52 device would cause the daemon to
not detect the transition. As a result, the daemon would stay in a state
where it thinks the device is still connected, and therefore, not
actually apply any of the saved configuration, until it received a
SIGHUP to refresh the configuration.

This change adds a routine that sends a dummy vendor command. This
vendor command does nothing on my X52 Pro (VID 06a3, PID 0762), but
serves as a check to see if the daemon can send vendor commands to the
device. If the device is indeed disconnected, then that is a sufficient
indicator to disable the update thread and re-enable the acquisition
thread.
2021-08-30 12:33:52 -07:00
nirenjan 0f83cd5a95 Ignore changes to clock settings if clock is disabled 2021-08-30 12:01:41 -07:00
nirenjan 2290900da6 Call tzset before computing timezone offset
POSIX.1-2004 requires that localtime() is required to behave as if
tzset() was called, but there is no such requirement imposed upon
localtime_r(). Therefore, we need to call tzset ourselves to ensure that
the timezone fields are updated.
2021-08-30 11:53:35 -07:00
nirenjan 91f378c4fc Apply configuration immediately when device is connected 2021-08-30 10:41:21 -07:00
nirenjan e0f6813028 Update translations 2021-08-30 10:33:20 -07:00
nirenjan 0899df60c2 Add signal handler to dump configuration to disk on SIGUSR1 2021-08-30 10:28:29 -07:00
nirenjan 76b1b99717 Make sure config file is closed after dumping configuration 2021-08-30 10:28:26 -07:00
nirenjan 38dfc7d7b0 Update translations to include new messages 2021-08-26 22:26:06 -07:00
nirenjan 1174f7f1c4 Bail out if there are changes after make distcheck 2021-08-26 22:22:39 -07:00
nirenjan a39945f461 Import inih library into source tree
Prior to this change, the user needed to install inih as a dependency,
either from the distribution repositories, or from source. On some
platforms (notably macOS), inih is not available prepackaged, and must
be installed by the user. This tends to cause needless friction.

This change imports the ini.c and ini.h files from the upstream inih
repository into the X52 source tree. This will allow us to build the
repository on any system with the original set of dependencies, and not
have to force the user to install packages themselves.
2021-08-26 22:16:38 -07:00
nirenjan 4c9ef85223 Squashed 'lib/pinelog/' changes from 27a5eab..0256807
0256807 Use strrchr only if the compiler supports __builtin_strrchr
204aade Use __builtin_strrchr to get file base name
aa0a554 Remove the use of config.h
8983bf8 Ignore test and benchmark programs

git-subtree-dir: lib/pinelog
git-subtree-split: 02568074170f0725196de4e52450db024cc39895
2021-08-25 14:22:04 -07:00
nirenjan 52d6920352 Merge commit '4c9ef85223ec6e638976eb279b82b9e9ea676b35' 2021-08-25 14:22:04 -07:00
nirenjan 5be91b6e50 Fix stringification of libx52_led_state
A missing comma at the end of the STRINGIFY line was causing builds with
clang to have segfaults with the default configuration. It turned out
that due to the missing comma, the N_("Unknown LED state %d") and
N_("off") parameters were getting merged into a single parameter by
clang, but interestingly, not by GCC.

As a result, when building with clang, the array is "on", "red",
"amber", "green" - note the missing "off" at the beginning of the array.
This causes clang generated builds to segfault when attemping to log a
trace message when configuring LED A (which defaults to green, and the
index of LIBX52_LED_STATE_GREEN exceeds the array bounds).
2021-08-25 14:09:28 -07:00
nirenjan ff10525028 Make inih a mandatory package.
AC_CHECK_HEADERS and AC_SEARCH_LIBS do not abort when the header or
library respectively are not found. Since inih is a hard requirement,
make sure that the action-if-not-found fields call AC_MSG_ERROR.

Fixes #32
2021-08-11 22:22:50 -07:00
nirenjan 874c46705a Add config save routines 2021-08-09 22:16:30 -07:00
nirenjan 3d15da385f Change the config macro definitions to use a type
This change changes the parser element in the CFG macros to be a "type"
element instead. This is handled in the config parser source, where the
macro definition appends `_parser` to the type field. This allows us to
(in the future) add a `_dump` function to dump the configuration to a
file.
2021-08-09 21:25:06 -07:00
nirenjan 2b664513a7 Add Install section to service file to allow enabling the service 2021-08-05 11:46:44 -07:00
nirenjan a09a8bee84 Enable INFO log level in systemd unit file 2021-08-05 10:43:13 -07:00
nirenjan e7af5df69b Use automake rules to install man pages 2021-08-05 10:35:31 -07:00
nirenjan 86960e7e20 Create the log and run directories for daemon to store files 2021-08-05 10:25:45 -07:00
nirenjan 0cf6f247be Add systemd unit file to start X52 daemon 2021-08-05 10:21:28 -07:00
nirenjan 7f5b5a2eaf Disable -Wpacked compiler flag
See issue #31. In systems with a newer version of libusb (1.0.24
onwards), GCC raises a warning that the packed attribute is unnecessary
for `libusb_control_setup`. This is not really a problem for libx52,
since it doesn't call libusb_control_setup directly, but since the
libx52 build framework treats all warnings as errors by default, it will
prevent the build from working.

This change removes the -Wpacked flag from AX_COMPILER_FLAGS, and this
should let the build pass without any issues.
2021-08-05 07:54:53 -07:00
nirenjan 65f4ec9659 Add PID file argument to documentation 2021-08-05 05:47:44 -07:00
nirenjan 0fae24b5d0 Allow x52d to daemonize
Prior to this change, x52d could only run in the foreground, regardless
of the value of the foreground flag. This change adds the standard
double-fork routine to daemonize the program.

This change also adds a PID file argument to x52d, which is used to
ensure that only one instance of the x52d daemon is running at any time.
2021-08-04 13:13:12 -07:00
nirenjan f5331cdef3 Use localtime_r instead of localtime 2021-08-03 13:04:46 -07:00
nirenjan e968656672 Fix Cflags and add libx52util.pc 2021-08-03 12:53:26 -07:00
nirenjan 446fec3b9f Add libx52io pkgconfig file 2021-08-03 12:50:09 -07:00
nirenjan f6cfc59cb6 Fix libx52 pkgconfig file 2021-08-03 12:42:15 -07:00
nirenjan dc72e43f1e Squashed 'lib/pinelog/' changes from bdee493..27a5eab
27a5eab Use localtime_r instead of localtime

git-subtree-dir: lib/pinelog
git-subtree-split: 27a5eab8b75c18d13ec0b69c01deaa82c996cf57
2021-08-03 11:26:06 -07:00
nirenjan d9c1c80163 Merge commit 'dc72e43f1e43a0149c48a684d3b5fd40441ef83c' 2021-08-03 11:26:06 -07:00
nirenjan 699f663df3 Use TEST_DEF as a function macro, instead of #if/#endif blocks 2021-08-02 10:35:37 -07:00
nirenjan 3efdce5abe Fix make distcheck 2021-08-01 23:56:37 -07:00
nirenjan 38917ed6e5 Add documentation for x52d 2021-08-01 23:47:24 -07:00
nirenjan 78e4f3334f Merge branch 'daemon' 2021-08-01 03:01:24 -07:00
nirenjan e358aa9688 Update ChangeLog.md 2021-08-01 03:00:21 -07:00
nirenjan 2cdf22b8c1 Remove dependency on faketime 2021-07-30 13:31:00 -07:00
nirenjan dbd683a98b Add vendor command test cases
This change adds test cases to validate the `raw` command in x52cli.
2021-07-30 12:02:02 -07:00
nirenjan 377aea90b5 Remove spurious test cases
Prior to this change, the tests file contained every test that was in
the original script based test suite. However, we don't need to test
every single scenario within libx52, since those are already handled in
the libx52 specific tests. This also causes a slow compilation, since
the tests are written as a series of macros, and the result is a 2000+
line file before preprocessing.

This change removes some of the spurious test cases, and simply checks
that the parameters passed to the corresponding libx52 function are as
expected for a few test cases.
2021-07-30 11:48:14 -07:00
nirenjan 9047485204 Include CLI tests file into distribution 2021-07-30 03:09:21 -07:00
nirenjan 23ff7c7202 Don't use TESTS when updating check_PROGRAMS 2021-07-30 03:04:12 -07:00
nirenjan 51913094cb Use cmocka to test CLI 2021-07-30 03:03:16 -07:00
nirenjan abdf47d721 Delete obsolete files 2021-07-29 23:55:07 -07:00
nirenjan ac68ee07e5 Use non-recursive Automake 2021-07-29 23:53:21 -07:00
nirenjan 23fa0daf4f Move evtest to top level 2021-07-29 22:26:15 -07:00
nirenjan a94b079cf5 Move x52test to top level 2021-07-29 22:14:48 -07:00
nirenjan 711e4385c1 Move x52cli to top level 2021-07-29 22:11:19 -07:00
nirenjan 3b2378a54b Move libusbx52 to top level 2021-07-29 21:57:19 -07:00
nirenjan 7f30863e5d Move libx52io to top level 2021-07-29 21:51:11 -07:00
nirenjan 34adeaec45 Move libx52util to top level 2021-07-29 21:47:07 -07:00
nirenjan e3bccd3ac3 Move libx52 to top level 2021-07-29 21:41:07 -07:00
nirenjan 0eeab91a8d Remove libx52 as a dependency of libx52util 2021-07-29 21:23:00 -07:00
nirenjan 8db1be2ba8 Squashed 'lib/pinelog/' changes from 321d9aa..bdee493
bdee493 Add preprocessor definition to disable traces
5b2686a Fix pinelog builds on MacOS
48bd049 Merge commit '9fa1a428a45eabff0122e199a687068f0e6280dd' into daemon
68d5aae Update README to include destructor attribute

git-subtree-dir: lib/pinelog
git-subtree-split: bdee493f0357eb0f3f6d4b792140de78999c828e
2021-07-27 17:02:49 -07:00
nirenjan 7a4d63adc1 Merge commit '8db1be2ba8dc66d3abf69a8360990c659bf28f16' into daemon 2021-07-27 17:02:49 -07:00
nirenjan 06b8d15dda Make stringification function names similar to libx52io 2021-07-27 09:34:57 -07:00
nirenjan 50906b0a92 Ensure x52d.conf is included in the distribution tarball 2021-07-27 03:07:52 -07:00
nirenjan 009fba0151 Install x52d.conf to sysconfdir 2021-07-27 02:26:32 -07:00
nirenjan 386174d2a4 Update package version 2021-07-27 02:18:35 -07:00
nirenjan d003e7f7c4 Make daemon use libx52 stringification functions 2021-07-27 02:13:59 -07:00
nirenjan 018852a012 Add stringification functions to libx52 2021-07-27 02:00:39 -07:00
nirenjan 77606ae906 Use lookup table to implement *_strerror 2021-07-27 01:35:31 -07:00
nirenjan 602071612d Add DoxygenLayout.xml to distribution 2021-07-26 17:07:11 -07:00
nirenjan f422202ec8 Update translations and logs for setting config parameters 2021-07-26 11:02:38 -07:00
nirenjan bd2dbbb9cc Make thread startup and shutdown logs as INFO 2021-07-26 10:30:29 -07:00
nirenjan 52232b1a14 Stop device threads on daemon termination 2021-07-26 10:12:23 -07:00
nirenjan e0d15961e0 Add signal handlers to handle termination and reload 2021-07-25 15:39:20 -07:00
nirenjan e4d1b6aff2 Add update thread enable flag
Prior to this change, the update thread was only checking on
device_update_needed. However, this causes an issue when calling any of
the set methods where the update thread fires, fails to find a device,
and signals the acquisition thread every 50 ms, since the update flag
was never cleared.

This change adds a thread enable flag for the update thread. The update
thread will check that both the thread enable flag and the update needed
flag are set before proceeding with the call to libx52_update.
2021-07-25 08:42:33 -07:00
nirenjan 9941234bbe Replace printf with PINELOG_DEBUG in main 2021-07-25 08:42:12 -07:00
nirenjan 9970a8edc4 Fix key names for clock formats in default configuration file 2021-07-23 14:31:58 -07:00
nirenjan 2cecd1890a Allow set_led_state calls to ignore unsupported feature 2021-07-23 14:31:14 -07:00
nirenjan bd682cd5c7 Add LED state and brightness functionality 2021-07-23 14:24:22 -07:00
nirenjan 88955418b8 Fix trace log to print section.key 2021-07-23 14:23:11 -07:00
nirenjan 0fa638fa16 Add support for clock and date format 2021-07-23 13:43:21 -07:00
nirenjan 499cad666f Add clock update functionality 2021-07-23 10:13:18 -07:00
nirenjan ae13480717 Allow libx52 calls to return TRY_AGAIN
Some libx52 APIs, notably the clock related ones, can return
LIBX52_ERROR_TRY_AGAIN. This is not a real error, but it is useful
information. It indicates to the application that there is no change
applied to the internal state, and that it should wait until trying
again.

Given that the clock thread calls the libx52_set_clock method every
second, treating the TRY_AGAIN state as a failure and logging it causes
a lot of spurious noise in the logs.  This change ensures that the API
returns a real error before logging it.
2021-07-23 09:43:02 -07:00
nirenjan 6175dcabe6 Remove trace logging in device update thread
Having the trace log in there adds unnecessary logging for no real
reason. It is better to disable the log here to help trace other
portions of the daemon.
2021-07-23 09:41:18 -07:00
nirenjan c45a84bd38 Replace autogenerated stubs with extern functions
This allows us to slowly replace the stubs with the real functions
2021-07-22 15:56:12 -07:00
nirenjan 3bca8da541 Add check for typeof support in compiler 2021-07-22 15:12:37 -07:00
nirenjan 50f77119ff Add stub to apply configuration 2021-07-21 10:49:16 -07:00
nirenjan ac8bb6cdd9 Add threads to find and update X52 device 2021-07-21 00:01:49 -07:00
nirenjan c9cb89f833 Fix syntax of PKG_CHECK_MODULES
Commit 2ce9ff22 replaced the calls to AX_PKG_CHECK_MODULES with
PKG_CHECK_MODULES, but it was done blindly, since PKG_CHECK_MODULES
takes a different number of arguments. As a result, even if the checks
for cmocka and udev succeed, they are still disabled because the
corresponding have_ variable is not set to 'yes'.
2021-07-20 01:38:19 -07:00
nirenjan b81d89aad3 Use relative links in Markdown
[skip ci]
2021-07-20 01:17:34 -07:00
nirenjan 98cc439f05 Remove build status badge from INSTALL.md
[skip ci]
2021-07-19 11:16:28 -07:00
nirenjan e54f6037d4 Fix pinelog builds on MacOS 2021-07-19 11:15:42 -07:00
nirenjan 1b598c2d78 Fix pkg-config check for inih and enable Ubuntu 18.04 builds 2021-07-19 11:09:41 -07:00
nirenjan 13f54588a6 Disable Ubuntu 16.04 builds
Ubuntu 16.04 reached EOL in April 2021, and Github will disable Ubuntu
16.04 runners in September 2021. In order to avoid build failures, this
commit disables builds on 16.04.
2021-07-19 10:35:27 -07:00
nirenjan 9fa1a428a4 Squashed 'lib/pinelog/' changes from b5457f4..321d9aa
321d9aa Add benchmark program to tests
a6e789e Remove config.h.in - this is autogenerated
a3eb2c0 Add method to close output stream and reset to default
fb9b51a Fix format-literal error in clang

git-subtree-dir: lib/pinelog
git-subtree-split: 321d9aaa0eee4b4af4be52c228662c3d32c32ab6
2021-07-19 09:50:42 -07:00
nirenjan 3a68148472 Merge commit '9fa1a428a45eabff0122e199a687068f0e6280dd' into daemon 2021-07-19 09:50:42 -07:00
nirenjan 82c778c7de Update configure.ac to handle the new pinelog method 2021-07-16 16:25:43 -07:00
nirenjan 27eb123062 Update README to include destructor attribute 2021-07-16 08:33:22 -07:00
nirenjan 0d407d77fe Add method to close output stream and reset to default 2021-07-16 08:29:11 -07:00
nirenjan ab946b4a1a Add device manager implementation 2021-07-16 00:30:27 -07:00
nirenjan 5a283672c4 Update instructions to install inih on Arch and MacOS
[skip ci]
2021-07-15 22:20:40 -07:00
nirenjan 2ce9ff2280 Disable AX_PKG_CHECK_MODULES on optional modules 2021-07-15 22:00:14 -07:00
nirenjan 81002444d7 Add LTLIBINTL to enable i18n 2021-07-15 21:53:42 -07:00
nirenjan 8545e28d09 Build and install inih manually on macOS 2021-07-15 19:31:46 -07:00
nirenjan e32f836485 Fix format-literal error in clang 2021-07-15 18:31:01 -07:00
nirenjan fc9bebbe5a Add noreturn attribute to fix compiler warnings 2021-07-15 17:57:51 -07:00
nirenjan 738879f79f Disable building for Ubuntu 18.04
libinih-dev on Bionic does not provide a .pc file, so PKG_CHECK_MODULES
fails on this target. This commit disables the CI build on Bionic until
we have a workaround.
2021-07-15 17:44:08 -07:00
nirenjan fa298455aa Use PKG_CHECK_MODULES instead of AX_PKG_CHECK_MODULES 2021-07-15 17:43:51 -07:00
nirenjan 19859b79c5 Disable Ubuntu 16.04 builds 2021-07-15 16:14:17 -07:00
nirenjan cbe7f00a5a Add daemon framework
This change adds the daemon configuration parser and command line
argument parser. This also adds the associated strings to the
translation files, and integrates the daemon into the existing autotools
build framework.
2021-07-15 15:53:56 -07:00
nirenjan fc8e7b6b95 Add new generated files to .gitignore 2021-07-15 15:47:39 -07:00
nirenjan f82c31a6eb Add support for including inih configuration parser library 2021-07-15 13:52:46 -07:00
nirenjan 4b1d524d39 Integrate pinelog into autotools build framework 2021-07-15 10:33:52 -07:00
nirenjan 50dc946c31 Squashed 'lib/pinelog/' content from commit b5457f4
git-subtree-dir: lib/pinelog
git-subtree-split: b5457f405ecbbe676aea26001780a81426191176
2021-07-14 15:54:58 -07:00
nirenjan fd79166a89 Merge commit '50dc946c3177f5006e4bd63eef4bf67331f59dea' as 'lib/pinelog' 2021-07-14 15:54:58 -07:00
nirenjan dcd878b7cc Move Doxygen generation to separate job
This must run only for commits to master
2021-02-12 21:54:33 -08:00
nirenjan a28c622941 Fix Doxygen generation workflow to use doxygen from apt 2021-02-11 03:55:53 -08:00
nirenjan 36dfdd0ad3 Move Doxygen generation to new job 2021-02-09 07:27:09 -08:00
nirenjan 16b2cf7348 Fix deploy directory 2021-02-09 00:26:50 -08:00
nirenjan 3e2b960c0e Add filter to Doxygen 2021-02-09 00:24:53 -08:00
nirenjan b294a1a950 Add Doxygen generation to build action 2021-02-09 00:21:28 -08:00
nirenjan 2c522b9a66 Add reference to libx52_connect in integration documentation 2021-02-09 00:09:48 -08:00
nirenjan 7116af8f66 Remove hotplugging section from caveats list 2021-02-09 00:06:40 -08:00
nirenjan d8c6c8d574 Add note regarding clock update to x52cli documentation 2021-02-08 01:46:25 -08:00
nirenjan d41762df11 Fix link to gx52 project 2021-01-27 00:23:23 -08:00
nirenjan bea668b87e Add reference to gx52 project 2021-01-27 00:21:28 -08:00
nirenjan 121c86a190 Remove link to gitter.im
Now that GitHub discussions has been enabled on the project, there is no
longer a need to outsource the discussions to an external provider.
2021-01-27 00:20:21 -08:00
nirenjan e3758a2f29
Remove udev rules from default configure command.
The configuration options describes the method to override the udev rules directory.
It is not necessary to add it as the default in the build/install instructions.
2021-01-26 22:14:05 -08:00
nirenjan b9ef8a82d6 Update Changelog.md to list LKM and udev changes
[skip ci]
2020-12-26 15:02:25 -08:00
nirenjan abb366d89c Allow udev rules to be configured at build time [#25]
Prior to this change, the udev rules were fixed to allow read-write
access only to the `plugdev` group. However, while this is the default
group for USB input devices on Debian and its derivatives, this is not
true for some systems such as openSUSE, which use `input` as the group.

This change adds a `--with-input-group` argument to `configure`, which
defaults to `plugdev`. This change also updates the install
documentation to reflect the new options.
2020-12-25 15:17:52 -08:00
nirenjan 59c4643474
Update install instructions in INSTALL.md
Fixes #24
2020-12-25 13:53:37 -08:00
nirenjan 1df4f29d4e
Add kernel versions that have the quirks fix 2020-09-17 17:06:39 -07:00
nirenjan 7f554d7ac6 Make button reports consistent between devices
The X52 and X52 Pro report the buttons in different orders, e.g., the
mouse primary button is button 30 on X52, but is button 15 on the Pro.
This change ensures that the reported values is consistent between the
different devices.
2020-08-16 01:59:27 -07:00
nirenjan 482c5980ab Fix axis report to correctly report slider and rotaries 2020-08-13 15:18:44 -07:00
nirenjan b0150c46b8 Remove unnecessary probe and remove functions
There's no need to create custom probe and remove functions - the
default behavior is sufficient.
2020-08-13 15:17:14 -07:00
nirenjan 4388eceec0 Create workflow to build kernel module
This workflow runs only if there is a change to the kernel_module path.
Consequently, commits that only impact the kernel_module will be ignored
for the standard userspace driver build.

This commit also updates the CodeQL workflow to only run on a scheduled
basis and on pull requests, but not on every push, since this is a
fairly slow script.

Finally, this commit also removes the obsolete kernel module sources,
since they are no longer maintained, and it also provides a hook for
Github actions to pick up and execute the kernel workflow.
2020-08-13 03:17:35 -07:00
nirenjan d3c55da89d Create HID driver for Saitek X52 and X52 Pro
This change replaces the old USB driver with a more modern HID driver.
This uses the HID framework, which means that we don't have to deal with
the USB transport, and only have to parse and report input events.

[skip ci]
2020-08-12 01:11:15 -07:00
nirenjan 3409a7bad6 Remove Travis CI badge
[skip ci]
2020-08-07 23:25:34 -07:00
nirenjan 1fa4cb4eb4 Remove integration with Travis CI
[skip ci]
2020-08-07 23:22:41 -07:00
nirenjan dcb5b60cdf Add actions badge to README
[skip ci]
2020-08-07 23:16:51 -07:00
nirenjan d77342ced9 Skip CI jobs if corresponding tag is in HEAD commit 2020-08-07 23:16:23 -07:00
nirenjan 1119fe3373 Add CI using Github Actions
This commit adds workflows to handle the continuous integration builds
as well as the CodeQL analysis on each push. This also adds a workflow
to create a release and upload the orig.tar.gz file when pushing a tag.
2020-08-07 23:01:24 -07:00
nirenjan 4f39078998 Fix install error on macOS when doxygen is found
The `install` command on macOS does not support the `-D` flag. It is not
required anyway, since the directory components are created by the
previous line.

This commit also updates the options to rsync to not copy in 'archive'
mode. Because it is used in `make install`, it is likely to be run as
root, and therefore, the root user should own the files in the
destination directory.
2020-08-07 23:00:50 -07:00
nirenjan 53f9c33ffa Allow failure when installing udev rules
When testing make install in a container, or if being run with fakeroot,
it is possible that the udevadm commands may fail, which will abort the
install. Therefore, allow the udev install hook to always pass.
2020-07-19 22:19:25 -07:00
nirenjan 55c1fadba6 Update package list for Arch Linux
[skip ci]
2020-07-19 15:17:17 -07:00
nirenjan 9fb2d246c6 Move headers to $(includedir)/libx52 2020-07-17 15:27:42 -07:00
nirenjan 9486d1dbe4 Merge branch 'interface-io' 2020-07-16 16:19:27 -07:00
nirenjan ed654f501a Update changelog
[skip ci]
2020-07-16 04:32:40 -07:00
nirenjan 104fcb46f9 Add denoising to x52evtest 2020-07-16 04:25:48 -07:00
nirenjan 798714dd1c Add thumbstick test cases 2020-07-16 03:53:25 -07:00
nirenjan 453f9517d9 Add button and hat test cases 2020-07-16 03:41:32 -07:00
nirenjan 869d564aa3 Add more parser test cases 2020-07-14 00:47:13 -07:00
nirenjan 1d5e1073ce Update translation templates 2020-07-13 18:05:22 -07:00
nirenjan 22d4218189
Merge pull request #22 from gitter-badger/gitter-badge
Add a Gitter chat badge to README.md
2020-07-13 14:19:50 -07:00
The Gitter Badger 5108e34ce8 Add Gitter badge 2020-07-13 21:16:53 +00:00
nirenjan afb442d9c4 Add device info to x52evtest output 2020-07-12 02:13:03 -07:00
nirenjan f6136fcef0 Add device info API 2020-07-12 00:08:33 -07:00
nirenjan c8ad37b3f7 Save USB device strings when opening HID device
This change saves the manufacturer, product and serial number strings in
a multibyte format, so that it can be used by the clients of the library
to print details about the connected device. This also ensures that
those multibyte strings are freed when closing the device.
2020-07-11 17:13:27 -07:00
nirenjan 81cb7367f8 Fix test builds on OSX 2020-07-11 11:31:09 -07:00
nirenjan b766fb75fa Make timeval printing portable 2020-07-11 10:46:04 -07:00
nirenjan 597c73ab35 Enable i18n for x52evtest 2020-07-11 00:21:28 -07:00
nirenjan 46bd78bdd9 Add event test utility
This change is a wrapper around libx52io to print the raw events coming
from the X52 device.
2020-07-11 00:15:14 -07:00
nirenjan 4982071764 Map hat to axis 2020-07-11 00:09:39 -07:00
nirenjan 62894dea43 Add string representations and associated i18n 2020-07-10 18:01:53 -07:00
nirenjan a0b7769dab Add HATX and HATY axis values 2020-07-10 17:26:23 -07:00
nirenjan 2c40785c2b Add test suite for parser - WIP 2020-07-10 02:35:53 -07:00
nirenjan 4bd3ae69fe Fix builds on OSX 2020-07-09 16:21:40 -07:00
nirenjan 16cb1e4698 Implement libx52io read APIs 2020-07-09 15:12:55 -07:00
nirenjan 9ab3cce73e Fix report format to correctly handle mouse stick
Prior to this change, the report parser treated the mouse stick axes in
the reverse order of the USB HID report descriptor.

This change fixes that issue and also updates the specification
documentation to reflect it correctly.
2020-07-09 02:44:31 -07:00
nirenjan 4f18aa3dc8 Add tests for axis API 2020-07-09 01:53:54 -07:00
nirenjan cf6c458fae Update dependencies in INSTALL.md
[skip ci]
2020-07-08 17:39:29 -07:00
nirenjan 63a2f465d2 Replace hard-coded values with #define's 2020-07-08 17:35:17 -07:00
nirenjan 9d3acfd35a Add report parser implementation 2020-07-08 17:28:06 -07:00
nirenjan aebd5e14f9 Add axis API implementation 2020-07-08 16:43:20 -07:00
nirenjan 329274e6c9 Add initial version of libx52io 2020-07-08 00:33:03 -07:00
nirenjan bcc90ac24e Add HIDAPI to build dependencies 2020-07-05 16:19:04 -07:00
nirenjan 1cbad472df Revert "Save PID in libx52_device structure"
This reverts commit f963991161.

The updated design uses hidapi to create a separate connection to the
X52 device, and libx52 is limited to only dealing with the vendor
specific commands.
2020-07-05 16:10:23 -07:00
nirenjan f963991161 Save PID in libx52_device structure
This will support a parser interface which will parse the HID report
from the X52, but given that the device has different PIDs, the parser
will need to behave differently depending on the PID.
2020-07-04 23:29:25 -07:00
nirenjan 945ddc63a3 Update for release 0.2.1 2020-06-28 13:31:25 -07:00
nirenjan 729bbcaf90 Rename CLI tests from libx52 to x52cli 2020-06-28 12:01:14 -07:00
nirenjan 4311c020a0 Merge branch 'cmocka-tests' 2020-06-28 11:49:39 -07:00
nirenjan c40847b833 Add clock tests to verify PDT/PST/UTC 2020-06-28 11:32:19 -07:00
nirenjan 3981b873e0 Add date format tests 2020-06-28 08:57:23 -07:00
nirenjan 87ad48a37f Cleanup generated test formatting 2020-06-28 08:57:01 -07:00
nirenjan 7b423f4ea0 Update libx52_set_led_state to use check_feature 2020-06-28 08:35:14 -07:00
nirenjan efd984ef63 Parse and add setup_hook and fields
This change allows updates to the dev structure for individual test
cases. This defines the "setup_hook" array, and "fields" map, at both
the test group level and at the individual test case level, with the
ones at the test case level overriding any parent level fields and/or
hooks.

The primary use case for this is to add new test definitions that would
not be setup correctly by the default infrastructure, such as testing
the LED function for the non-Pro X52, or setting up clock tests for
local timezone, etc.
2020-06-28 08:24:49 -07:00
nirenjan 108b7e2522 Add cmocka to OSX build 2020-06-27 22:20:40 -07:00
nirenjan 681a8e8aa1 Change libx52 mock tests to use preprocessor overrides
Prior to this change, libx52 tests needed a linker that supported the
--wrap argument. This is not available on OSX, and therefore, we had to
disable the unit tests on non-Linux systems.

Since we needed to rebuild the libx52 library anyway for the test, it is
simpler to just define libusb_control_transfer to point to our mock
function. This allows us to verify libx52 on all supported platforms.
2020-06-27 22:04:56 -07:00
nirenjan 57cda79320 Add specifications for Saitek X52
[skip ci]
2020-06-21 08:49:14 -07:00
nirenjan 8388f3308e Reword configure warning on non-Linux hosts 2020-06-21 08:48:44 -07:00
nirenjan c4696f6055 Add clock time tests 2020-06-17 18:52:07 -07:00
nirenjan 34b023b1fa Add cmocka to Travis CI builds on Ubuntu 2020-06-17 16:18:06 -07:00
nirenjan e9167b4c20 Disable cmocka tests on non-Linux hosts 2020-06-17 16:00:25 -07:00
nirenjan 3afe999fe8 Ensure generated sources are not packaged in distribution 2020-06-17 15:12:01 -07:00
nirenjan 9f37cde784 Add generated tests to verify libx52 functionality
This change adds a suite of tests in JSON format using a Python script
to generate the cmocka based test program. Because we need to wrap some
of libusb functionality, we need to rebuild and relink the libx52
sources with the -Wl,--wrap option.
2020-06-17 15:06:19 -07:00
nirenjan bf9b1bdfbd Remove manual tests in favor of cmocka
Prior to this change, we needed to add a manual override function to
mock the vendor command. Given that cmocka has built-in support for
mocking functions, it's better to use that instead.

This change simply removes the manual override and any tests that rely
on it.
2020-06-16 17:33:15 -07:00
nirenjan f0ed2f39e3 Set minimum version of Python to 3.5
On Xenial, the version of Python 3 is 3.5. Restricting the version to
3.6 or newer causes the Travis CI build to break on Xenial.
2020-06-16 15:39:31 -07:00
nirenjan c4acd0ce49 Update makefiles to use Python 3
Prior to this change, generation of the character lookup table could use
Python 2 or Python 3, depending on what the first instance of python in
the PATH pointed to.  On most systems, python is a symbolic link to
python2.7. However, given that Python 2 is EOL, it makes sense to switch
the code to use Python 3 now, rather than later.

This change updates the requirements to use Python 3.6 or later, and
updates the Makefile to invoke the script with the detected python
binary, rather than relying on the shebang to use the system Python.
2020-06-16 15:17:53 -07:00
nirenjan 3eaee7b8f4 Reduce duplicate code in libx52 test code 2020-06-14 00:07:40 -07:00
nirenjan e1915bc734 Update paths of scripts to allow building on BSD
Prior to this change, the assumption in all shell and Python scripts was
that the Bash interpreter would always be available at `/bin/bash`, and
Python would always be available at `/usr/bin/python`. However, on a VM
running FreeBSD, installing bash and python using the pkg command
installs them under /usr/local/bin.

This change updates the paths to use /usr/bin/env in the shebang. While
this is not a standard path either, it is more likely to be available at
this location.

This change also updates the find command in common_infra.sh to use
`-perm -+x` in lieu of `-executable`, which is not a condition defined
by POSIX. This allows running the tests on BSD.
2020-06-12 23:26:51 -07:00
nirenjan 21a5da3c70 Update POT with new source line numbers 2020-06-12 23:16:33 -07:00
nirenjan f754533a67 Update gettext.h to fix warnings when compiling with --disable-nls
With the introduction of AX_COMPILE_FLAGS in configure.ac, building with
--disable-nls in configuration raises additional warnings.

This change updates gettext.h in order to address those warnings.
2020-06-12 22:05:28 -07:00
nirenjan 16a7801e59 Update offset tests to use common test infrastructure.
Prior to the introduction of common test infrastructure, the offset test
was using an undocumented function in libx52 to validate the timezone
offset calculations.

This change allows us to hide that undocumented function and not have to
expose internal functionality in order to perform testing.
2020-06-12 17:18:04 -07:00
nirenjan 02165a8712 Fix comparision issue in test_common.c
Prior to this change, the comparision loop ran until it found a zero
value in either the index or the value field, rather than looping until
both were zero.

This change fixes that bug, and also adds additional debug functions to
print the expected and observed values.
2020-06-12 17:09:09 -07:00
nirenjan 8b49b91267 Enable -Werror in compiler flags 2020-06-11 23:39:28 -07:00
nirenjan dbacc27164 libusbx52: Clean up compiler warnings 2020-06-11 23:39:28 -07:00
nirenjan 1e2dd5699f libx52util: Clean up compiler warnings 2020-06-11 23:39:28 -07:00
nirenjan 3b8b98e74c libx52: Clean up compiler warnings 2020-06-11 23:39:28 -07:00
nirenjan 65c889827a x52cli: Clean up compiler warnings 2020-06-11 23:39:28 -07:00
nirenjan 09eb7d31e8 x52test: Clean up compiler warnings 2020-06-11 23:39:28 -07:00
nirenjan 37162510ac Enable additional compiler warnings
This change adds additional compiler warnings as detected by the
autoconf archive. Because the code is susceptible to these additional
warnings, disable treating warnings as errors for now, until we can fix
the warnings.
2020-06-11 23:38:44 -07:00
nirenjan fffb0bb69e Add test cases for blink and shift functionality 2020-06-11 22:29:59 -07:00
nirenjan 49d162fa07 Return result from _x52_vendor_command 2020-06-11 16:44:41 -07:00
nirenjan 1efcaf8970 Remove unnecessary pass statement 2020-06-11 16:04:12 -07:00
nirenjan 071162a907 Replace backticks with $(...) in test scripts 2020-06-11 16:03:16 -07:00
nirenjan c96ba7fec4 Add test cases for LEDs
This change adds tests for setting the X52Pro LEDs using the new x52
test infrastructure. This should be used in addition to the x52cli based
test suite.
2020-06-11 15:07:27 -07:00
nirenjan 92b0eb584f Allow overriding the vendor command functionality
This change allows for a test framework to override the functionality of
libx52_vendor_command. By doing so, it can build tests that change the
libx52_device structure, and therefore, not need to rely on libusbx52 to
get the commands written.

This allows for tests that can be built and run on other OSes as well,
since the libusbx52 based tests cannot run on OSX and have not been
tested on BSD systems.
2020-06-10 17:04:24 -07:00
nirenjan 2cb3474861 Add test case for offset calculation 2020-06-09 15:29:26 -07:00
nirenjan 4d93df1d58 Break offset calculation out into a separate function
This change will allow for a test program to verify that the offset
computation is valid.
2020-06-09 14:43:59 -07:00
nirenjan c9ffb415c8 Move opening from environment into separate function 2020-06-09 14:31:20 -07:00
nirenjan d53e56c491 Use $(...) instead of `...` in test scripts 2020-06-08 23:55:27 -07:00
nirenjan 23a980e250 Cleanup spurious whitespace 2020-06-08 23:52:27 -07:00
nirenjan 5715b19326 Reduce libx52_update function complexity
With the introduction of automatic code review tools, libx52_update was
found to be "too complex". This change simply breaks out the switch
cases into a set of functions indexed through a lookup table.
2020-06-08 22:59:41 -07:00
nirenjan e70a1b74e9 libx52: create separate group for connection functions 2020-06-08 17:31:32 -07:00
nirenjan 2d46b395a1 Add libx52_is_connected function 2020-06-08 17:22:33 -07:00
nirenjan 0cf977b751 Automatically close open handle when no device detected
Prior to this change, libx52_vendor_command would leave the device
handle untouched, even if the joystick was disconnected.

This change will force applications to reconnect to the joystick after
they encounter such a failure.
2020-06-08 16:23:11 -07:00
nirenjan cdd5e773e2 Update Changelog.md 2020-06-07 02:30:14 -07:00
nirenjan 88d57958f9 Remove daylight savings offset bug from documentation 2020-06-07 02:26:32 -07:00
nirenjan 85d2fc3522 Update libx52 version to reflect fix 2020-06-06 18:34:32 -07:00
nirenjan af49ce6500 Fix daylight savings offset calculation bug
This commit changes the local timezone calculation to use tm.tm_gmtoff,
and falling back to calculating it manually from the output of strftime
when tm_gmtoff is not a member of struct tm.

This also fixes the tests so that they are no longer expected to fail.

Fixes #20.
2020-06-06 17:48:17 -07:00
nirenjan f5145de36b Force _GNU_SOURCE in libusbx52 2020-06-06 17:26:43 -07:00
nirenjan 4f22983739 Revert "Enable compiling libx52 with weak symbol binding"
The hope was that I could compile some tests that would override
libx52_vendor_command, and run those on OSX to bypass the skipped tests
that used x52cli. However, a Travis-CI run indicated that the compiler
on OSX doesn't support weak symbols, which renders the point moot.
2020-06-06 17:05:17 -07:00
nirenjan 5b7afa6ae1 Add ax_sys_weak_alias.m4 2020-06-06 16:39:33 -07:00
nirenjan 780d9b4da4 Reduce variable scope as suggested by code inspector 2020-06-06 16:35:24 -07:00
nirenjan 95bc71859b Enable compiling libx52 with weak symbol binding
This change allows exporting libx52_vendor_command as a weak symbol,
thereby allowing it to be overridden by a test runner to validate that
the library is indeed behaving as per the spec.

Because this is something that may not be necessarily desirable on a
production environment, add a configure time flag to disable building
with weak symbols. This will also disable any tests that may rely on
libx52_vendor_command being a weak function.
2020-06-06 16:24:31 -07:00
nirenjan 01e815fc3b Add link to Github in Doxygen generated output 2020-06-06 08:54:59 -07:00
nirenjan 99bfb7d36a Disable dot in Doxyfile 2020-06-06 08:54:12 -07:00
nirenjan 1188bea444 Use TAP for tests
Prior to this change, the tests were using the automake simple test
harness. The limitation was that for each set of test cases (e.g.
timezone tests), even if one test failed, it would report every test in
that set as failed. Migrating to TAP allows fine-grained reporting on
every single test case, and allows better investigation into checking
which individual tests failed.

This change also updates the timezone tests to explicitly mark the ones
that check when the timezone is Pacific Daylight Time as expected to
fail. It also updates the clock tests to improve the formatting of the
test case identifier.
2020-06-06 08:33:30 -07:00
nirenjan 758d1d05d2 Update gitignore with Doxygen man output directory 2020-06-06 08:16:52 -07:00
nirenjan bac20b410d Hide libx52 internal symbols
Prior to this change, all symbols were being exported in the generated
library. This change adds libtool directives to only export the public
symbols in libx52.
2020-06-06 07:41:13 -07:00
nirenjan 89c233e244 Update translation template after source update 2020-06-06 07:40:55 -07:00
nirenjan 88f02bc5da Add _GNU_SOURCE to all files
If configured with CFLAGS=-std=c99, then quite a few warnings are
raised, as well as a couple of errors. By forcing the system to use
_GNU_SOURCE, we can allow the compilation to succeed even if using a
user defined C standard.
2020-06-05 16:37:20 -07:00
nirenjan 543aec85b1 Add config header to all C files 2020-06-05 15:19:56 -07:00
nirenjan 011bb737af Update libusbx52 to address code-inspector violations
This change addresses the code inspector violations reported in
https://frontend.code-inspector.com/analysis/result/8899/550515
2020-06-04 17:03:02 -07:00
nirenjan 8e77d6f09b Correct type of loop variable
If `-Wpedantic` is given as part of CFLAGS, this flags a warning stating
that `int` and `size_t` are of different sizes. This simply changes the
type of `i` to `size_t` to match the output of `strlen`.
2020-06-04 15:48:57 -07:00
nirenjan b6ebdef7ef Handle time out of range in libx52_set_clock
Prior to this change, the assumption was that localtime/gmtime would
never fail, regardless of the time value provided to it. However,
testing on an Ubuntu 20.04 machine revealed that the representable
range of time_t was about 56 bits, values that exceed a 56 bit
representation would cause localtime/gmtime to return a NULL pointer.

This change replaces the use of localtime/gmtime with their
corresponding thread-safe variants, and checks the return value against
NULL. If it matches a NULL value, then it will return an error and not
update the clocks.
2020-06-04 15:34:03 -07:00
nirenjan cdc6a594e4 Add contribution guidelines 2020-06-02 17:20:03 -07:00
nirenjan dbfe26f709 Update issue templates 2020-06-02 17:18:00 -07:00
nirenjan a7d5b7e34d Update issue templates 2020-06-02 17:12:45 -07:00
nirenjan cb96d297ab Create INSTALL.md
[skip ci]
2020-06-02 16:36:01 -07:00
nirenjan c2c852cee1 Add faketime to Travis packages list
This change adds the faketime package when building on Linux. faketime
is needed for the timezone tests.
2020-06-02 15:03:26 -07:00
nirenjan 79bd8466c1 Add test suite for timezone tests
Previously, when testing the clock command of x52cli, we had forced the
system timezone to UTC. As a result, the offset calculations for clocks
2 and 3 were never computed and always resulted in 0, which hid the bug
when the local time and local standard time did not match (#20).

This commit adds a test case that uses faketime to test setting the
clock to local (Pacific Time) and verifying that the offsets are
computed correctly. Given that the bug is still present as of this
commit, add the test suite to XFAIL_TESTS as well, so that it doesn't
break the CI build.
2020-06-02 14:48:46 -07:00
nirenjan 7ae5cad0cc Clear warning when linking x52test_log_actions
Prior to this change, we were linking the log_actions program against
the stub library, since we needed to use the logging capabilities in the
stub library to save the expected values to a file for the test harness
to use. However, doing so gives us the following warning:

*** Warning: Linking the executable x52test_log_actions against the loadable module
*** libusbx52.so is not portable!

Since we don't really need to have dynamic linking in this case, simply
including the stub library source into the log_actions program sources
list is sufficient, and bypasses this warning.
2020-06-02 07:50:51 -07:00
nirenjan a43cbc83a5 Add libx52_set_clock bug (#20) to documentation 2020-06-01 17:45:33 -07:00
nirenjan 57f7758dd1 x52test: Check device type during reset
Prior to this change, x52test would always assume that the device was an
X52Pro when resetting the device state. This change fixes that
assumption by checking the feature flags, since only the X52Pro has LED
support.

This change also updates the translation files due to the automatic
update because of line numbers being changed.
2020-06-01 00:10:01 -07:00
nirenjan e7d14d7b53 x52test: Cleanup if successful or cancelled
Commit be1f7e0 fixed the error handling, but missed this line. The
original behavior was to restore the X52 LED and MFD state after
completing all the tests, or if the user cancelled the tests using
Ctrl-C.

This commit fixes that regression, so that it will preserve the state
only if it encountered an error during the test, not if it was
cancelled.
2020-05-31 23:57:03 -07:00
nirenjan a16b1822aa Delete obsolete man pages
Prior to this change, a separate copy of the man pages for the libx52
functions was being maintained since I couldn't get Doxygen to work
properly at the time (I don't remember, it's been over 5 years).

The man pages are out of date and don't match up with the current
version of libx52, and haven't been for some time. Now that we have
functional documentation generated via Doxygen, it is time to get rid of
the obsolete stuff.

This change deletes the entire man folder, and doesn't change anything
else, since it was self-contained.

[skip ci]
2020-05-31 15:50:59 -07:00
nirenjan 127ab10995 Update Doxygen documentation
This change cleans up the documentation and adds examples for some
functions.
2020-05-31 08:45:34 -07:00
nirenjan 2db24e8759 Add udev rules to distribution
Prior to this change, a user had to manually install their own udev
rules on Linux if they wanted to access the joystick without having to
run as root. The most common usecase was on systems based on Debian,
where the user would be a member of the plugdev group, and they would
create their own rule to allow members of the group to write to the
joystick.

This change adds a validated udev rule to the distribution, so if the
user compiles from source and does a make install, the rule to allow
plugdev group members to access the joystick is installed.
2020-05-30 16:38:58 -07:00
nirenjan e5ce827d7e Update installation instructions in README 2020-05-30 16:38:27 -07:00
nirenjan b0a07fe364 Ensure that doxygen docs are removed on uninstall 2020-05-30 16:36:55 -07:00
nirenjan a711f0a882 Remove consolidated API documentation page
This change ensures that there is a separate link to all libx52.h
documentation. As a result, there is no longer a need to manually
maintain a list of functions, structs, enums, etc. This also updates
the main page to link directly to the documented files, rather than
to the API page.
2020-05-28 16:27:44 -07:00
nirenjan 7889124217 Update readme [skip ci] 2020-05-28 15:40:46 -07:00
nirenjan f9be0b3172 Move x52cli documentation into Doxygen
Prior to this change, the x52cli man page was a manually created file
that was out of date with the source. Rather than update that file, and
maintain a separate document for the HTML sources, it makes sense to
consolidate both the HTML and manpage documentation to a Doxygen page
block within the source and update the Doxyfile to generate man pages as
well.

This change also removes the obsolete manually maintained manpage, and
falls back to Doxygen to build and install the manpage.
2020-05-28 15:31:08 -07:00
nirenjan 374fd94fcd Update gitignore list with additional generated files [skip ci] 2020-05-26 16:33:58 -07:00
nirenjan c86e3f027a Add install rule for Doxygen generated docs 2020-05-26 16:16:54 -07:00
nirenjan 973348e537 Add Stale bot integration
This change enables the Probot stale issues bot to automatically close
inactive issues.

[skip ci]
2020-05-25 23:39:10 -07:00
nirenjan 491e5dffeb Format silent rules for doxygen generation
This change changes the silent rule for doxygen docs to show `DXGEN`
instead of just `GEN`.
2020-05-25 01:25:53 -07:00
nirenjan aff5576106 Disable doxygen MAN generation 2020-05-25 01:25:45 -07:00
nirenjan d9ae8d4b79 Add connect/disconnect methods to libx52
Prior to this change, libx52_init needed a supported joystick to be
plugged in, otherwise it would fail with LIBX52_ERROR_NO_DEVICE. This
change modifies the behavior so that libx52_init would still succeed,
but the application should call libx52_connect to make sure that the
device handle is valid. libx52_init still tries to connect to the
joystick, but absence is no longer treated as a failure.

This change also modifies x52cli to check that the joystick is actually
connected before calling the individual handlers. Because libx52_init no
longer fails if the joystick is absent, we need to rely on the return
code from libx52_connect.
2020-05-23 02:18:40 -07:00
nirenjan 93f1091b95 Ignore additional generated files
[skip ci]
2020-05-22 23:32:17 -07:00
nirenjan 6dc5d51461 Commit automatic changes to PO files
The previous commit to restore the test order in x52test did not fix the
order in the PO files, which got automatically modified when `make
update-po` ran. This commit restores those changes.
2020-05-22 23:26:48 -07:00
nirenjan dfdf6468bc Update Doxygen documentation
This updates the Doxygen documentation so that the generated
documentation is more readable and accessible to users of libx52
2020-05-22 18:05:07 -07:00
nirenjan fd6afde59c Update libx52 soname to 2.2.0 2020-05-22 16:02:59 -07:00
nirenjan 132b72f562 Ignore generated .mo files
This change cleans up a few of the dummy translations in xx_PL using the
open source [Poedit](https://www.poedit.net) editor. Since the editor
also generates the corresponding .mo file, it makes sense to add .mo as
an ignored extension.

[skip ci]
2020-05-22 13:36:53 -07:00
nirenjan d89cce807b Add check for LED support in x52test
This commit uses the new `libx52_check_feature` API to check if the
device supports LED control. If not, it prints an error message and
exits the LED tests gracefully.

This also reverts commit 45f009ac90, which
had moved the LED tests to the end. Since this is no longer necessary,
it is moved back to avoid any issues with anything that may have relied
on the old order.
2020-05-22 11:15:27 -07:00
nirenjan 482943e7a3 Add feature check method to libx52
This change allows applications to query if the connected device
supports the requested feature. This is so that applications like
x52test can skip test routines that aren't supported, instead of bailing
out of the loop.
2020-05-22 10:39:47 -07:00
nirenjan 0b6bc8f074 Clear the structure memory on exiting libx52 2020-05-22 10:33:33 -07:00
nirenjan cc8d6e9344 Revert hotplug support in libx52
This removes the earlier work done in libx52 to support USB hotplug.
This wasn't adequately tested, and the reason to have hotplug support
was to address perceived deficiencies in the standard API.

However, on recent reflection and experimentation, it seems to be easier
to support adding methods to connect to an X52/X52Pro joystick
dynamically after initializing the library. This approach also lends
itself to adding checks when sending control packets to close the device
handle when it detects device disconnection. Also, one could add a
disconnect method to disconnect from any connected joysticks.

Finally, this commit reverts a series of commits that chronicled my
journey into implementing hotplug support and simulating it in
libusbx52. By coalescing the revert into a single commit, it makes it
easier to revert the revert in the future, if necessary.
2020-05-22 00:42:01 -07:00
nirenjan 97743d4ebd Add internationalization support for x52test
This change updates x52test to use the gettext APIs. This also adds a
fake message catalogue to verify that the code is converted correctly
and the translations are displayed.

The fake message catalogue translates the English strings into Pig
Latin, which makes it easy enough for a maintainer to verify that the
changes have been made correctly.

Finally, this also adds some documentation to tell the maintainer or
translator how to make the relevant changes.
2020-05-20 14:36:40 -07:00
nirenjan db8629a6a9 Add automatically generated objects to gitignore 2020-05-20 13:15:04 -07:00
nirenjan 84a7e0fe30 Speed up OSX Travis builds
The previous commit made a call to `brew upgrade`, but this tends to take
a very long time. More often than not, the default version in the OSX VM
image is sufficient, so we just need to call `brew install`.
2020-05-20 01:57:50 -07:00
nirenjan 94a262f13a Force OSX Travis builds to install libusb and gettext 2020-05-20 01:51:42 -07:00
nirenjan 2ea6dcd748 [skip ci] Update README 2020-05-19 16:05:15 -07:00
nirenjan 254bf6baaa Allow builds on OSX to fail without affecting the overall build 2020-05-19 14:20:18 -07:00
nirenjan 1b6736c0f8 Add autopoint to packages list
This change adds the necessary packages to install the autopoint binary
on both Ubuntu and OSX. This is necessary since the build needs the
gettext utilities.

Also remove the installation for realpath, since we are no longer
building for Trusty.
2020-05-19 12:45:00 -07:00
nirenjan ec9443dcdd Add gettext support to libx52
This change adds gettext support to libx52 using the Autotools
framework. This should allow translators to translate the error messages
provided by libx52_strerror into their corresponding localized versions.
2020-05-19 08:07:10 -07:00
nirenjan 3949550b65 Update ChangeLog.md
[skip ci]
2020-05-18 15:08:54 -07:00
nirenjan 1c822f9d6b Update ChangeLog.md 2020-05-18 15:06:19 -07:00
nirenjan 45f009ac90 Move LED tests in x52test to the end
x52test doesn't perform any checks to see if the connected device
supports setting individual LEDs. Therefore, if an X52 (non-Pro) was
connected when running x52test, it would fail when running the LED
tests, and not perform any of the subsequent tests.

By moving the LED tests to the end, this allows the other tests to run
on a non-Pro X52. Although the tests would still fail, it won't actually
break anything.

Addresses #19.
2020-05-18 14:59:24 -07:00
nirenjan be1f7e0d5a Fix error handling in x52test
x52test used to assume that a positive return code indicated a signal
was received and a negative return code indicated an error in libx52.
However, libx52 was changed a while back to return only a positive error
code of type `libx52_error_code`.

This commit changes that assumption, so that:

* The first check is always against `LIBX52_SUCCESS`, to ensure that any
  change in the enumeration won't break the rest of the code.
* Tests terminated by a signal return the negated value of the signal,
  i.e. -15 is SIGTERM and -2 is SIGINT.
* Tests that fail within libx52 return a standard `libx52_error_code`.
* Error printing uses `libx52_strerror`.

Addresses #19.
2020-05-18 14:58:31 -07:00
nirenjan 16b4ad693b Fix error reporting in x52cli
Prior to this change, x52cli assumed that the return code from the
handler was a standard error code, but negated. This is an incorrect
assumption as the libx52 API returns a `libx52_error_code` enum, which
is a positive integer.

This change fixes the printing of the error message to call
`libx52_strerror`, which translates it correctly. It also adds error
printing to any failure of `libx52_init`, as well as explicitly
specifying `LIBX52_SUCCESS` instead of `0`.

Addresses #19.
2020-05-18 14:38:49 -07:00
nirenjan 665dba187d Add build directory to gitignore 2020-05-18 14:23:08 -07:00
nirenjan 74b828a790 Update Doxygen comments
This disables the include file list and dependency graph in the file
view. In addition, the groups are updated to include `libx52` in their
names, to avoid any potential conflict.

[skip ci]
2020-04-18 02:21:12 -07:00
nirenjan 0204103ccd Add Doxygen support
This change adds Doxygen support in the configure/make step. Doxygen is
optional, and the absence of Doxygen should not break the build.
2020-04-18 01:35:57 -07:00
nirenjan 34dc1b8a32 Declare hotplug functions in libx52.h
This change exposes the libx52_hotplug_* functions in libx52.h to allow
other programs to link against them.

This commit also adds Doxygen comments, and groups the declarations by
their function, for later use in Doxygen integration.
2020-04-18 01:30:53 -07:00
nirenjan cb050f2c30 Allow NULL cb_handle in libx52_hotplug_register_callback
This allows the caller to register a callback and forget about it. This
is useful in cases where a particular callback needs to be run
everytime, and there is no point in deregistering it until the
application terminates, at which point `libx52_hotplug_exit` will take
care of the cleanup.
2020-04-18 01:28:12 -07:00
nirenjan 79b1f930b8 Change return type of libx52_hotplug_exit
There is really no need for a return code, if the service pointer is
NULL, or any of the parameters are NULL, then simply return.
2020-04-18 01:23:41 -07:00
nirenjan d45b1f7bfd Disable trusty builds on Travis
Ubuntu Trusty doesn't ship with a new enough pkg-config, which causes
the PKG_INSTALLDIR macro to fail on those systems. Since Trusty is EOL
anyway, simply disable the CI build on it.
2020-04-17 00:11:19 -07:00
nirenjan 116f7b3a57 Install libx52.pc during make install 2020-04-17 00:04:09 -07:00
nirenjan 06fa56bb9f Add missing documentation to distribution tarball 2020-04-17 00:00:46 -07:00
nirenjan 848d70fcf2 Generate pkg-config file for libx52 2020-04-16 22:10:48 -07:00
nirenjan e49261c8d6 Move pthread build logic to configure.ac
Prior to this change, the libusbx52 Makefile was manually specifying the
pthread flags to indicate that the linker needed to link against the
pthread libraries.

This change moves the pthread detection logic out to configure.ac, and
updates the flags in libusbx52 Makefile to use the pthread compiler.
2020-04-15 17:31:46 -07:00
nirenjan 9dc92eb52e Force the use of STDC when building 2020-04-14 23:45:35 -07:00
nirenjan 42f416af1d Update version and changelog for hotplug support 2020-04-14 18:38:12 -07:00
nirenjan aa259bf343 Add hotplug support to libusbx52 2020-04-14 18:36:44 -07:00
nirenjan 946916f456 Add hotplug support to libx52
Prior to this change, libx52 would require that the X52/Pro be plugged
in before initialization. This change allows the application to
initialize libx52 even before the device is plugged in.
2020-04-14 18:36:44 -07:00
nirenjan 152a3e7932 Update for release 0.2.0 2020-04-14 18:30:16 -07:00
nirenjan f9639a9a00 Update ChangeLog
[skip ci]
2020-04-13 00:44:33 -07:00
nirenjan 243b0330af Update build status in README
[skip ci]
2020-04-12 22:23:47 -07:00
nirenjan a5b69124a4 Update build matrix to include OSX, Xenial and Bionic
This commit adds OSX to the build matrix, as well as building on Trusty,
Xenial and Bionic, both with GCC and CLang on all distributions. For
now, the OSX build uses the default image provided by Travis CI, but we
may decide to add more images in the future.

Also move realpath installation out of addons::apt::packages, since on
Xenial and newer distributions, realpath is included in the coreutils
package and doesn't need to be explicitly installed. However, we still
need it on Trusty, so add a line to manually install it.
2020-04-12 22:04:43 -07:00
nirenjan dc80a0f2f1 Add option to run test without delay
x52test by default expects to run with an attached X52 unit. Since the
primary goal of the program is to test the hardware, it has embedded
delays to allow the user to verify the individual LEDs, MFD, brightness,
etc.

However, the complete test takes about 6 minutes. When using the stub
libusb library, the goal is to capture the requests, not verify it
against the hardware. In this case, the delays serve no useful purpose.

This change adds a nodelay flag, which is controlled by the presence of
a `NO_DELAY` environment variable, or the `LD_PRELOAD` environment
variable. As long as either of these variables are present in the
environment block, there will be no delays in the test execution.
2020-04-12 16:48:29 -07:00
nirenjan a7caba19df Set debug output to be line buffered
The stub libusb library used for testing writes the control request to a
dump file. By default, this file is block-buffered, and on a typical
Linux system, it waits until it receives about a page worth of data
before flushing it to disk. This results in a delay in monitoring
packets when running a debug program.

This change makes the file line-buffered instead, which results in the
debug output for every single packet getting flushed to disk without
having to wait for a full page of data.
2020-04-12 16:29:22 -07:00
nirenjan 40b2e9bdac Remove debugs from MFD test script 2020-03-31 23:22:04 -07:00
nirenjan 40c14fed24 Update calls to libusb_set_debug
With the release of libusb 1.0.22, `libusb_set_debug` has been
deprecated and replaced by `libusb_set_option`. This function is a new
generic API to add additional functionality in the future without having
to introduce new functions.

This change checks for `LIBUSB_API_VERSION` of at least `0x01000106`,
which is the version corresponding to 1.0.22, and if it matches, it
replaces the calls to `libusb_set_debug` with equivalent calls to
`libusb_set_option`.
2019-02-01 21:31:57 -08:00
nirenjan 1d51429f10 Make x52test use X macros 2018-07-17 17:21:21 -07:00
nirenjan 9e581bf051 Let libx52_init also translate libusb error codes 2018-07-17 16:00:19 -07:00
nirenjan b1139806f5 Use X-macros for x52cli commands 2018-07-17 15:46:54 -07:00
nirenjan 7dcd3049ec Add SPDX license identifiers in all files 2018-04-18 12:19:29 -07:00
nirenjan e31f1e442b Move license text to LICENSE file 2018-04-18 12:16:48 -07:00
nirenjan 0913212ecc Always return translated error code
LIBUSB_SUCCESS is also handled by libx52_translate_libusb_error.
2017-10-27 14:20:19 -07:00
nirenjan 02c24cc964 Fix timezone offset calculations for large negative offsets 2017-10-27 14:19:38 -07:00
nirenjan 46cba64e6b Update build requirements in README
[skip ci]
2017-10-12 21:35:58 -07:00
nirenjan 86642e5b16 Use pkg-config for libusb 2017-09-10 17:13:38 -07:00
nirenjan 3845c81229 Add libx52_strerror API 2017-08-28 22:54:46 -07:00
nirenjan 6f3f8d7c46 Add more error codes and translation from libusb
These new error codes correspond to similar ones returned by libusb, but
abstract away the usage of libusb underneath libx52. This lets the
application rely solely on libx52 error codes to perform error handling.
2017-08-28 19:10:41 -07:00
nirenjan d4afbd6de2 Build libusbx52 only for tests 2017-08-25 06:39:04 -07:00
nirenjan e5ea621899 Fix libtool version info for libx52
Also update package version to indicate it is in pre-release stage.
2017-08-25 06:04:36 -07:00
nirenjan f3270def9d API change for libx52
- libx52_init returns the libx52_device in an output parameter and
  returns a libx52_error_code
- Make all functions return libx52_error_code
- Update package version to indicate incompatible API change
2017-08-23 19:30:35 -07:00
nirenjan fb222dda89 Add Changelog 2017-08-19 15:59:57 -07:00
nirenjan 82fa0cea28 Bump version number 2017-08-18 07:50:12 -07:00
nirenjan 21d6b503a7 travis-ci: Enable parallel make 2017-08-18 07:32:50 -07:00
nirenjan d54e02be5a Add test cases for raw time and date APIs 2017-08-18 07:28:23 -07:00
nirenjan acdcebc52e Add CLI commands to access raw date and time APIs 2017-08-18 07:26:50 -07:00
nirenjan 52abd335ab Add raw time and date APIs to libx52
The raw time and date APIs bypass the timezone calculation and update
the internal data structures with the requested time in hh:mm and date
in dd/mm/yy formats.

This is unlikely to be used often, but it is useful during testing.
2017-08-18 07:23:49 -07:00
nirenjan 6b89a9d7f9 Clarify Doxygen comments for libx52 2017-08-16 23:38:19 -07:00
nirenjan 533a472b10 Fix formatting for x52cli manual 2017-08-16 00:05:05 -07:00
nirenjan 780447122c Reduce number of included headers for libx52util.h 2017-08-16 00:04:10 -07:00
nirenjan b5f4e72148 Add test cases for MFD text 2017-08-14 22:58:22 -07:00
nirenjan 2e96378f80 Add test cases for MFD clock display 2017-08-12 08:43:13 -07:00
nirenjan b9a7e5de1f Remove redundant make check from Travis build
make distcheck already runs make check in the new location, so any
failures should start showing up there.

[skip ci]
2017-08-11 18:19:29 -07:00
nirenjan 3eb837df1b Merge tests into corresponding test-suites
This eliminates the large number of small generated test case files and
somewhat simplifies the actual management of test cases and test suites.
2017-08-11 18:10:53 -07:00
nirenjan 994c39ce3c Add test cases for blink and shift indicators 2017-08-04 08:26:21 -07:00
nirenjan 8f7c262ea6 Merge pull request #13 from Polynomial-C/newline
x52_cli: Print error messages with trailing newline.
2017-08-04 08:25:13 -07:00
Lars Wendler 44d46b70e0
x52_cli: Print error messages with trailing newline. 2017-08-04 11:12:24 +02:00
nirenjan e0d7cb8341 Add brightness test cases 2017-08-03 19:14:29 -07:00
nirenjan 2c2dbb3c42 Reorganize test scripts
This allows for a cleaner organization of different test categories.
2017-08-03 16:59:07 -07:00
nirenjan d9732d498d Fix environment for parallel tests 2017-08-03 16:56:06 -07:00
nirenjan b776101cb6 Add test-driver to .gitignore 2017-08-01 22:05:03 -07:00
nirenjan 0a8db132dc Make test setup routines use environment variables 2017-08-01 21:49:58 -07:00
nirenjan f6bf25d66f Add environment file to enable parallel tests 2017-08-01 21:45:02 -07:00
nirenjan 991218a8b0 Skip tests on OSX 2017-07-29 10:07:12 -07:00
nirenjan 0a45bd5ddf Use x52test_log_action to capture test output 2017-07-27 23:34:39 -07:00
nirenjan 55963ba824 Merge branch 'feature/automated-tests' 2017-07-27 20:52:09 -07:00
nirenjan a2496d5d28 Add realpath to Travis-CI
This package is necessary for the tests, otherwise the script will error
out with a missing "realpath" error.
2017-07-27 20:47:59 -07:00
nirenjan c79373676f Fix distcheck failure 2017-07-27 20:10:21 -07:00
nirenjan e463d9b890 Add tests framework and LED tests 2017-07-27 20:00:35 -07:00
nirenjan f0a0a7dcaf Reorganize source layout 2017-07-27 17:56:51 -07:00
nirenjan 7bc0ba522c Use LIBUSB_* values instead of magic numbers 2017-07-27 17:37:58 -07:00
nirenjan 74eeb27ad4 Fix build breakage on Travis-CI
Travis uses an older version of GCC which doesn't seem to support C99
mode by default. This fixes it by moving the variable declarations out
of the for loop and to the beginning of the function.
2017-07-27 17:22:36 -07:00
nirenjan dc352c58da Move build script out of .travis.yml 2017-07-27 17:09:16 -07:00
nirenjan 45c66a4f1a Fix distcheck breakage 2017-07-27 17:05:06 -07:00
nirenjan ea14d1132d Add implementation of stub libusb library
This commit adds the implementation of the stub libusb library in order
to use it in an LD_PRELOAD environment. This also adds the utility
programs to create a device list and sample output to compare against.
2017-07-27 16:43:01 -07:00
nirenjan dc7300db26 Add stub routines for mocking libusb
This is to be used for running automated tests on libx52 and associated
libraries and/or programs
2017-07-26 17:34:29 -07:00
nirenjan 59652c0aff Travis-CI: Compile using both GCC & clang 2017-07-21 22:47:26 -07:00
nirenjan f0e6836195 Upgrade Travis-CI distribution to Trusty 2017-07-21 09:11:36 -07:00
nirenjan 262d125cd4 Refactor util_char_map_gen.py to comply with PEP8 2017-06-14 11:04:53 -07:00
nirenjan 9070d88588 Add mappings for missing characters
Add mappings for Halfwidth kana, CJK ideographics, and some other
missing symbols.

Issue: #6
2017-01-11 11:09:32 -08:00
nirenjan 5c69289cff Add missing file to libx52util_la_SOURCES 2017-01-11 08:34:26 -08:00
nirenjan e053e1ac1c Add support for the X52 (non-Pro model)
This uses the published device IDs for the Saitek X52 (non-Pro) model.
However, based on my knowledge, the X52 uses single color LEDs compared
to the X52 Pro's tri-color LEDs (Red/Amber/Green). For the time being,
until we can determine the actual control messages being sent to the
X52, setting the LED state will not be supported.

This commit also operates on the assumption that the other controls are
the same as the X52 Pro, specifically the following:
- Setting MFD/LED brightness
- Setting MFD text
- Setting blink and shift
- Seting time and date on the MFD clock

Issue: #11
2017-01-10 22:03:13 -08:00
nirenjan 793cd519a2 Add check for X52 vs X52 Pro
This doesn't do anything at the moment, but this should make it easier
to distinguish between the Pro and non-pro models.

Issue: #11
2017-01-10 21:18:26 -08:00
nirenjan 0f7b5e5668 Remove unused members from struct libx52_device 2017-01-10 21:15:14 -08:00
nirenjan 1efd88d770 travis: Make initial build quiet in Travis-CI 2016-12-29 14:09:25 -08:00
nirenjan a7806c43b9 Add new LICENSE file to distribution 2016-12-29 14:08:55 -08:00
nirenjan 8892b3ef7e util: Remove generated file from distribution tarball
util_char_map.c is generated at compile time by the Python script
x52_char_map_gen.py from the configuration x52_char_map.cfg. However,
because it was listed in libx52util_la_SOURCES, it was getting added to
the distribution.

In addition, removing it from the distribution caused `make distcheck`
to fail, since the generation script was called relative to the current
directory, instead of relative to `$(srcdir)`. This commit also fixes
that issue.
2016-12-29 14:01:38 -08:00
nirenjan 56faafca1e Add license clarification and authors file
Closes #12
2016-12-27 13:13:37 -08:00
nirenjan 4532c0d868 Add codepoint translation status chart
This is intended to give the pending codepoints for translation at a
quick glance.
2016-10-08 17:33:19 -07:00
nirenjan c9dd29199d Fix sign-compare warnings 2016-10-08 14:18:56 -07:00
nirenjan 6d8d5a4fd4 Fix return from void function
While gcc on Linux (Travis CI) throws a warning, but otherwise ignores
this return value from void function, clang on OSX throws a fit.
2016-10-08 09:44:44 -07:00
nirenjan 1b9c52ea07 Merge pull request #10 from ryandrake08/master
Clang/OSX cleanups
2016-10-08 09:31:53 -07:00
Ryan Drake ae97d58bd5 Use zero instead of LIBUSB_ERROR_OTHER as default value for rc 2016-10-08 09:22:17 -07:00
nirenjan e5229b6aa2 Clean up enum_conversion warnings on clang/OSX 2016-10-07 22:27:13 -07:00
Ryan Drake aaab4c6b1d Conditionalize use of ELIBACC on __linux__ as it is linux-specific 2016-10-07 19:18:55 -07:00
Ryan Drake 95e933e27c Fix warnings about uninitialized variables 2016-10-06 15:43:32 -07:00
Ryan Drake 15b8abf3c6 Fix warnings about unused variables 2016-10-06 15:28:18 -07:00
Ryan Drake 30622eefa8 Fix control may reach end of non-void function error 2016-10-06 15:19:00 -07:00
nirenjan bbe86d554f Clear up return-type warnings 2016-10-06 10:39:16 -07:00
nirenjan b39c13fe1b x52_cli: Add stdlib.h include to prevent warnings 2016-07-31 12:25:07 -07:00
nirenjan ea2927859b Fix includedir location for headers 2016-05-16 22:15:19 -07:00
nirenjan 710ef00109 Add support for Python3 in parser script 2016-05-06 07:39:02 -07:00
nirenjan 224b08a8e2 Add python to required dependencies 2016-05-05 09:32:00 -07:00
nirenjan bc9001705d Convert character map generator to Python script
The conversion makes it simpler to allow cross-compilation, since we
should be using the host Python interpreter to build the generated
character map, rather than relying on the automake infrastructure.
2016-05-05 09:18:05 -07:00
nirenjan abaa36f35e Add math symbols to character map translation 2015-12-15 08:41:38 -08:00
nirenjan 6528459645 Add Man pages in RONN format 2015-12-14 18:04:26 -08:00
nirenjan db19e4dcae Install headers to includedir 2015-12-13 13:44:32 -08:00
nirenjan 059ec6af1a Make libx52_set_clock return 0 only if fields changed
This is used by an application to check if it needs to call
libx52_update. If libx52_set_clock returns 0, it can be assumed that the
clock did change values that require an update, but any other value
indicates that an update is not required.
2015-12-12 23:11:36 -08:00
nirenjan b0309b1b40 Reduce number of control messages for clock update
Compare the stored clock value against the requested value and only
send the USB vendor control messages if necessary. This should reduce
the number of messages from 5 for every clock update to 1-3, depending
on whether the date has changed or not. Only if the timezone of the
default clock changes should the additional 2 messages for the offset
clocks be sent.
2015-12-11 09:04:54 -08:00
nirenjan 320e3c85dc Allow user to select tests to run 2015-12-11 07:52:35 -08:00
nirenjan ce9f58c15a Add README files to distribution 2015-12-10 22:44:44 -08:00
nirenjan 3849024816 Merge branch 'feature/travis' 2015-12-10 22:14:03 -08:00
nirenjan f0b2e6fecc Add generated objects to .gitignore 2015-12-10 21:56:03 -08:00
nirenjan b18391b3a6 Create the m4 directory to fix the autogen failure 2015-12-10 10:22:59 -08:00
nirenjan 67e6e68fb3 Switch to container based building and update build script 2015-12-10 10:15:43 -08:00
nirenjan 4dcba4bfe1 Enable Travis-CI 2015-12-10 10:09:24 -08:00
nirenjan 367a367ff9 Add table walker to libx52util
This file walks the generated table to find the corresponding character
map values for the given UTF-8 string.
2015-12-10 09:56:15 -08:00
nirenjan e752be9805 Use autoconf macros for if-checks 2015-12-10 07:40:38 -08:00
nirenjan b45c9fd9a2 Add man page for x52cli 2015-12-09 09:25:03 -08:00
nirenjan 7614e2f961 Print estimated completion time 2015-12-09 07:36:27 -08:00
nirenjan 450cdbabee Add Latin and Greek characters 2015-12-09 07:13:00 -08:00
nirenjan 090fbe6a3b Merge pull request #7 from nirenjan/feature/restructure
Restructure code layout
2015-12-09 05:54:38 -08:00
nirenjan c87e785a18 Restructure code layout
Copy files to individual folders. This makes it cleaner to add new
functionality in the future.
2015-12-08 21:46:42 -08:00
nirenjan 326075406a Fix check for libusb in configure.ac 2015-12-08 20:07:04 -08:00
nirenjan 7ddde96cc6 Add character map parser generator
This also adds automake rules to generate the utility library from the
generated source file.
2015-12-08 07:55:54 -08:00
nirenjan 0ad71bd24f Add X52 character mapping configuration file
This file is parsed during compilation time to generate a static lookup
table which is used to convert UTF-8 text to a character supported by
the X52 MFD.
2015-12-07 23:08:21 -08:00
nirenjan b7141a3e8b Add check for joystick when initializing library
Fix C++ inclusion
2015-12-06 12:57:15 -08:00
nirenjan f2ee9707cb Wrap public header in extern C 2015-12-05 23:22:11 -08:00
nirenjan 531c92ecab Add header file to x52test_SOURCES
The absence was causing `make distcheck' to fail.
2015-12-04 20:33:08 -08:00
nirenjan b7fe3e484c Enhance clock test to test all portions of clock 1 2015-12-04 19:34:19 -08:00
nirenjan f8a7257b54 Export vendor command interface
The vendor command interface allows the user to write a vendor request
packet using the known vendor interface. However, it is not intended for
regular use, since the existing API is better suited to abstract it away
from the user. Rather, the prime goal is for a test harness interface
that can be used to debug problems seen during regular library accesses.
2015-12-04 18:27:21 -08:00
nirenjan cd4fca0d2e Add tests for blink, shift & clock display 2015-12-04 17:58:34 -08:00
nirenjan ebf566d9be Add retry to libx52_vendor_command
Sometimes the vendor control request failes with LIBUSB_PIPE_ERROR. Most
of the time a retry fixes it right away. To allow for a transient
failure, make the function retry the control transfer up to 3 times
before failing.
2015-12-04 16:44:41 -08:00
nirenjan 21f5440349 Make x52test more verbose 2015-12-04 10:48:24 -08:00
nirenjan 9dcc3507ed Fix Readme & help output 2015-12-03 08:39:01 -08:00
nirenjan 5d0684a154 Add x52cli command for interfacing with the X52Pro
This adds a CLI frontend to the X52 library so that the user can send
the joystick commands without having to write any code.
2015-12-02 19:11:06 -08:00
nirenjan f4a81aba83 Move spec documents under docs folder 2015-12-02 08:29:45 -08:00
nirenjan 8813be2de2 Fix file extension
GitHub screws up the formatting if it is saved with .md extension
2015-12-01 08:32:20 -08:00
nirenjan 87fe93f6e9 Add parsed HID report
Report descriptor can be obtained from
/sys/bus/hid/devices/.../report_descriptor.

Once dumped in hex, the parser at
http://eleccelerator.com/usbdescreqparser/ can convert it to a human
readable format.
2015-12-01 08:29:57 -08:00
nirenjan dbac37b8ac Add README for kernel module 2015-12-01 08:29:12 -08:00
nirenjan b72eba6d4c Fix permissions on device attribute files
Build fails on Linux kernel version >= 4.0.0, due to a permissions check
disallowing S_IWOTH.
2015-12-01 08:23:28 -08:00
nirenjan c1f3b6abdf Add installation instructions 2015-11-30 23:37:22 -08:00
nirenjan edfff0a5f6 Add readme for libx52 2015-11-30 23:23:27 -08:00
nirenjan 77cf6c490f Move test program into src directory 2015-11-30 22:52:12 -08:00
nirenjan 032dda1dd4 Cleanup autotools code and add gitignore 2015-11-30 22:30:14 -08:00
nirenjan 5e4a6dc826 Use autotools to generate library 2015-11-30 22:12:16 -08:00
nirenjan d688334eb9 Add code to write clocks 2015-11-30 20:54:55 -08:00
nirenjan 995c5b3c63 Breakout functionality into individual files 2015-11-30 20:12:34 -08:00
nirenjan 2ccfd79bd6 Remove deprecated API functions 2015-11-30 20:02:54 -08:00
nirenjan 548f2e9357 Document X52 library functions
This is still a relative work-in-progress, since some of the functions
are being cleaned and enhanced to provide proper functionality similar
to the Windows driver.
2015-11-29 22:50:36 -08:00
nirenjan 4a1ca0badb Retrieve hidraw file from command line 2015-11-28 10:06:38 -08:00
nirenjan 0a541e18ba Update test cases for X52 library 2015-11-28 10:04:12 -08:00
nirenjan b5d33226c5 Implement all X52 Pro vendor commands
The current contents of the libx52 folder contain all the source code
necessary to implement the X52 Pro vendor API. These have been tested,
although the committed test file does not list all the APIs.
2015-10-31 23:19:10 -07:00
208 changed files with 29051 additions and 2130 deletions

1
.gitattributes vendored 100644
View File

@ -0,0 +1 @@
/version-info ident

View File

@ -0,0 +1,30 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Run '...'
2. Scroll down to '....'
3. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots/Logs**
* If applicable, add screenshots to help explain your problem.
* Attach the detailed X52 daemon logs.
**Environment (please complete the following information):**
- Output of `x52bugreport`
**Additional context**
Add any other context about the problem here.

View File

@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@ -0,0 +1,10 @@
---
name: Generic issue
about: Generic issue that isn't a bug report or feature request
title: ''
labels: question
assignees: ''
---

View File

@ -0,0 +1,20 @@
#!/bin/bash
# Run the build and tests
set -e
./autogen.sh
mkdir build
cd build
../configure
make -j V=0
make -j check V=0
make -j distcheck
# Print bugreport output
./x52bugreport
# Make sure that there are no changes to the source code
# This may happen if the source have changed with differences to the
# translation files and templates. Enabling this will allow us to catch
# missing/modified translations.
git diff --exit-code

View File

@ -0,0 +1,9 @@
#!/bin/bash
# Generate Doxygen documentation
set -e
./autogen.sh
mkdir build
cd build
../configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var
make docs/.stamp

View File

@ -0,0 +1,15 @@
#!/bin/bash -x
# Install dependencies to build and test on Ubuntu runners
brew install \
autoconf \
automake \
libtool \
pkg-config \
python3 \
gettext \
libusb \
hidapi \
doxygen \
cmocka
exit 0

View File

@ -0,0 +1,19 @@
#!/bin/bash
# Install dependencies to build and test on Ubuntu runners
sudo apt-get update
sudo apt-get install -y \
autoconf \
automake \
libtool \
pkg-config \
python3 \
gettext \
autopoint \
libusb-1.0-0-dev \
libhidapi-dev \
libevdev-dev \
doxygen \
libcmocka-dev \
faketime
exit 0

View File

@ -0,0 +1,4 @@
#!/bin/bash
# Install dependencies to build kernel modules on Ubuntu runners
sudo apt-get update
sudo apt-get install -y linux-headers-$(uname -r)

17
.github/stale.yml vendored 100644
View File

@ -0,0 +1,17 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 180
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 14
# Issues with these labels will never be considered stale
exemptLabels:
- pinned
- security
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity in the past 6 months. It will be closed within 2 weeks
if no further activity occurs. Thank you for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false

40
.github/workflows/build.yml vendored 100644
View File

@ -0,0 +1,40 @@
name: Build/Test
on:
push:
branches:
- '*'
- '!gh-pages'
paths-ignore:
- 'kernel_module/**'
pull_request:
branches: [ master ]
jobs:
build:
if: "!(contains(github.event.head_commit.message, '[ci skip]') || contains(github.event.head_commit.message, '[skip ci]'))"
name: ${{ join(matrix.*, '/') }}
runs-on: ${{ matrix.os }}
continue-on-error: ${{ startsWith(matrix.os, 'macos-') || (matrix.os == 'ubuntu-24.04') }}
env:
CC: ${{ matrix.cc }}
strategy:
matrix:
os: ['ubuntu-20.04', 'ubuntu-22.04', 'ubuntu-22.04', 'macos-13', 'macos-14']
cc: ['gcc', 'clang']
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install dependencies (Ubuntu)
run: ./.github/scripts/install-dependencies-ubuntu.sh
if: ${{ startsWith(matrix.os, 'ubuntu-') }}
- name: Install dependencies (MacOS)
run: ./.github/scripts/install-dependencies-macos.sh
if: ${{ startsWith(matrix.os, 'macos-') }}
- name: Build and Test
run: ./.github/scripts/build-and-test.sh

View File

@ -0,0 +1,55 @@
name: "CodeQL"
on:
pull_request:
# The branches below must be a subset of the branches above
branches: [master]
schedule:
- cron: '30 7 * * 1,3,5'
jobs:
analyse:
name: Analyse
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
- name: Install dependencies
run: ./.github/scripts/install-dependencies-ubuntu.sh
# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
# 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: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2

30
.github/workflows/doxygen.yml vendored 100644
View File

@ -0,0 +1,30 @@
name: Doxygen
on:
push:
branches:
- 'master'
jobs:
doxygen:
if: "!(contains(github.event.head_commit.message, '[doxy skip]') || contains(github.event.head_commit.message, '[skip doxy]'))"
runs-on: 'ubuntu-latest'
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install dependencies
run: ./.github/scripts/install-dependencies-ubuntu.sh
- name: Generate Doxygen documentation
run: ./.github/scripts/build-doxygen.sh
- name: Dump generated files
run: find ./build -type f -print
- name: Deploy generated documentation to Github pages
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./build/docs/html

31
.github/workflows/kernel.yml vendored 100644
View File

@ -0,0 +1,31 @@
name: Kernel Module
on:
push:
branches: [ '*' ]
paths:
- 'kernel_module/**'
pull_request:
branches: [ master ]
jobs:
build:
if: "!(contains(github.event.head_commit.message, '[ci skip]') || contains(github.event.head_commit.message, '[skip ci]'))"
name: ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: ['ubuntu-20.04', 'ubuntu-22.04']
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install kernel dependencies
run: ./.github/scripts/install-kernel-dependencies.sh
- name: Build kernel module
run: |
cd kernel_module
make

48
.github/workflows/release.yml vendored 100644
View File

@ -0,0 +1,48 @@
name: Create Release
on:
push:
tags:
- 'v*'
jobs:
build:
name: Upload Release Asset
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install dependencies
run: ./.github/scripts/install-dependencies-ubuntu.sh
- name: Build project
run: ./.github/scripts/build-and-test.sh
- name: Find release tarball
id: find_release
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/')"
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
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

65
.gitignore vendored
View File

@ -1,10 +1,21 @@
# Compiled object files
*.ko
*.o
*.mod.*
*.mod*
# Compiled executables
# Generated objects (source, executables, tarballs, etc.)
a.out
x52cli*
x52test*
x52evtest*
libx52/test_*
libx52test*
libx52util/util_char_map.c
udev/*.rules
x52d*
!daemon/x52d*.*
test-*
libx52-*.tar.gz
# Module files
modules.order
@ -14,3 +25,53 @@ Module.symvers
# Vim swap files
.*.swp
# Autotools objects
.deps
.dirstamp
.libs
ar-lib
autom4te.cache
m4
compile
config.*
configure
depcomp
install-sh
libtool
ltmain.sh
missing
Makefile
Makefile.in
*.la
*.lo
*.m4
stamp-h1
tap-driver.sh
test-driver
tests/test-suite.log
tests/**/*.log
tests/**/*.trs
*.pc
# Autotools Gettext objects
po/Makevars.template
po/*.in
po/*.sin
po/*.sed
po/*.header
po/Rules-quot
ABOUT-NLS
po/*.gmo
po/*.mo
po/POTFILES
po/stamp-po
# Doxygen files
Doxyfile
docs/.stamp
docs/html
docs/man
# Build directory
/build/

2
AUTHORS 100644
View File

@ -0,0 +1,2 @@
Nirenjan Krishnan
Ryan Drake

44
CONTRIBUTING.md 100644
View File

@ -0,0 +1,44 @@
Contribution Instructions
=========================
This project welcomes contributions. Contributions are managed through GitHub
pull requests.
# Issue guidelines
If you find an issue with any code in this project, feel free to open an issue.
Templates exist for bug reports and feature requests.
# Pull request guidelines
* Fork this repository.
* Create a branch off master, and make your commits in that branch.
* Test your changes locally before raising a pull request.
# Commit guidelines
* Isolate each commit to a single component/folder as far as possible
* Use a standard message template. I follow the template from [this
link](https://codeinthehole.com/tips/a-useful-template-for-commit-messages/)
* Ensure that you have any necessary tests included to test your changes.
## Additional commit requirements
* Commits should have a `Signed-off-by` line. You can use `git commit -s` to
automatically append this to your message.
* Any generated objects should be included in `.gitignore`.
* A commit should be self-contained, i.e., if I check out any commit in a clean
workspace, I should be able to run `./.github/scripts/build-and-test.sh` and
not encounter any failures.
* Any additional dependencies should be called out in `INSTALL.md`
# Contribution License
The project is licensed under GPLv2, with a linking exception. Your
contributions will also be licensed the same way. However, you maintain the
copyright to your contributions.
If this is your first contribution to this project, you may update the `AUTHORS`
file with your full name. Please keep the change to this file in a separate
commit.

194
ChangeLog.md 100644
View File

@ -0,0 +1,194 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based upon [Keep a Changelog].
## [Unreleased]
## [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
when reporting issues.
- Communication infrastructure to communicate with the daemon. This includes the
`x52ctl` utility which can be used either interactively or non-interactively
from a separate program.
- Links to prebuilt packages in Ubuntu PPA and Arch Linux AUR
- Ability to change mouse scroll direction. See
[#45](https://github.com/nirenjan/libx52/issues/45)
### Changed
- Renamed project from `x52pro-linux` to `libx52`
### Fixed
- Removed dependency on `rsync` during `make install`
- Reduced default logging level of daemon to error only. See
[#38](https://github.com/nirenjan/libx52/issues/38)
- Fixed daemon crash when disconnecting/reconnecting the joystick. See
[#43](https://github.com/nirenjan/libx52/issues/43)
## [0.2.3] - 2021-09-20
### Added
- CI for macOS 11 (Big Sur)
- Virtual mouse driver (on Linux only)
### Fixed
- Device erratic behavior when running daemon. See
[#33](https://github.com/nirenjan/libx52/issues/33).
- `make install` on OpenSUSE Tumbleweed. See
[#35](https://github.com/nirenjan/libx52/issues/35).
## [0.2.2] - 2021-09-03
### Added
- IO library to read and parse events from a supported joystick.
- Event test utility which displays the events similar to evtest.
- Daemon to control and update the X52 joystick.
- Import pinelog library for daemon logging.
- Import [inih](https://github.com/benhoyt/inih) library sources for daemon
configuration parsing.
### Changed
- Linux kernel driver to correctly handle the X52/X52 Pro. This is not required
for users running kernels with at least the following versions:
- 5.9+
- 5.8.10+
- 5.4.66+
- 4.19.146+
- Make udev rules customizable at build time, so that the right input group can
be used in the actual rules file. This allows systems such as openSUSE which
use `input` as the group for input devices to behave the same as Ubuntu and
other similar systems.
- Code layout changed to improve parallel builds.
- x52cli tests modified to use cmocka tests.
## [0.2.1] - 2020-06-28
### Added
- Connect/Disconnect methods in libx52. These allow for dynamically connecting
or disconnecting from a supported joystick without having to reinitialize the
library.
- Internationalization for the following:
* libx52
* x52test
- Doxygen generation of HTML documentation for libx52 methods.
- Tests for libx52 that run on all supported platforms.
### Changed
- libx52_init no longer fails when a supported joystick is not connected.
- Tests now use [TAP].
- Python build scripts now use Python 3.
### Fixed
- Error reporting in x52cli and x52test commands.
- Handling of very large time_t values in `libx52_set_clock`
- Secondary and tertiary clock setting when primary clock is set to local time
and local timezone is observing daylight savings time (summer time). See
[#20](https://github.com/nirenjan/libx52/issues/20).
## [0.2.0] - 2020-04-14
### Changed
- `libx52_init` now returns a `libx52_error_code`, and returns the
`libx52_device` pointer in an output parameter.
- All libx52 APIs now return a `libx52_error_code` indicating the error.
- libx52 now checks the version of libusb and calls the appropriate method
to set logging level.
- x52test has an option to not sleep between consecutive calls to the libx52
APIs.
### Fixed
- `libx52_write_time` handling of large timezone offsets.
## [0.1.2] - 2017-08-17
### Added
- Autotools based unit tests - tests run on Linux only
- libusb mock library for use by test programs
- License file and usage clarification
- Automatic builds on Ubuntu Trusty (14.04) with both GCC and clang on Travis
- Enhanced documentation for libx52
- Support for X52 (non-Pro) version
- New raw time and date APIs for libx52
- Support raw time and date commands in x52cli
- Unicode translation points for halfwidth CJK and Katakana symbols
### Changed
- Update Python character map generator to comply with PEP-8 guidelines
### Fixed
- Compilation on OSX
## [0.1.1] - 2016-05-06
### Added
- Manpage for x52cli
- Manpages for libx52 in RONN format
- Unicode translation points for Latin, Greek and mathematical symbols
- Travis-CI based automatic compilation
### Changed
- libx52 clock API will return -EAGAIN if no update is needed.
- x52test accepts a list of tests to run, defaulting to ALL
- UTF-8 parser rewritten in Python
## [0.1.0] - 2015-12-09
### Added
- Support for semantic LED names instead of numbers in libx52
- Simpler API to control clocks
- Add documentation for X52 design and USB interface
- Add CLI application to interface with libx52
- Add test application to test all aspects of libx52
- Add API to convert UTF-8 string to X52 character map
### Changed
- Migrate project to autotools
### Deprecated
- Mark kernel driver as proof-of-concept and unsuitable for production
## [0.0.2] - 2014-10-18
### Added
- Proposed design documentation for MFD pages and input mapping
### Fixed
- Kernel module compilation on Linux v3.5 and later
## [0.0.1] - 2012-10-25
### Added
- Kernel module for Saitek X52 Pro Joystick
- First release of userspace application
[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.2...HEAD
[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
[0.2.0]: https://github.com/nirenjan/libx52/compare/v0.1.2...v0.2.0
[0.1.2]: https://github.com/nirenjan/libx52/compare/v0.1.1...v0.1.2
[0.1.1]: https://github.com/nirenjan/libx52/compare/v0.1.0...v0.1.1
[0.1.0]: https://github.com/nirenjan/libx52/compare/v0.0.2...v0.1.0
[0.0.2]: https://github.com/nirenjan/libx52/compare/v0.0.1...v0.0.2
[0.0.1]: https://github.com/nirenjan/libx52/releases/tag/v0.0.1

2540
Doxyfile.in 100644

File diff suppressed because it is too large Load Diff

227
DoxygenLayout.xml 100644
View File

@ -0,0 +1,227 @@
<doxygenlayout version="1.0">
<!-- Generated by doxygen 1.8.17 -->
<!-- Navigation index tabs for HTML output -->
<navindex>
<tab type="mainpage" visible="yes" title=""/>
<tab type="pages" visible="yes" title="" intro=""/>
<tab type="modules" visible="yes" title="" intro=""/>
<tab type="namespaces" visible="yes" title="">
<tab type="namespacelist" visible="yes" title="" intro=""/>
<tab type="namespacemembers" visible="yes" title="" intro=""/>
</tab>
<tab type="interfaces" visible="yes" title="">
<tab type="interfacelist" visible="yes" title="" intro=""/>
<tab type="interfaceindex" visible="$ALPHABETICAL_INDEX" title=""/>
<tab type="interfacehierarchy" visible="yes" title="" intro=""/>
</tab>
<tab type="classes" visible="yes" title="">
<tab type="classlist" visible="yes" title="" intro=""/>
<tab type="classindex" visible="$ALPHABETICAL_INDEX" title=""/>
<tab type="hierarchy" visible="yes" title="" intro=""/>
<tab type="classmembers" visible="yes" title="" intro=""/>
</tab>
<tab type="structs" visible="yes" title="">
<tab type="structlist" visible="yes" title="" intro=""/>
<tab type="structindex" visible="$ALPHABETICAL_INDEX" title=""/>
</tab>
<tab type="exceptions" visible="yes" title="">
<tab type="exceptionlist" visible="yes" title="" intro=""/>
<tab type="exceptionindex" visible="$ALPHABETICAL_INDEX" title=""/>
<tab type="exceptionhierarchy" visible="yes" title="" intro=""/>
</tab>
<tab type="files" visible="yes" title="">
<tab type="filelist" visible="yes" title="" intro=""/>
<tab type="globals" visible="yes" title="" intro=""/>
</tab>
<tab type="examples" visible="yes" title="" intro=""/>
<tab type="user" url="https://github.com/nirenjan/libx52" title="View on GitHub" />
</navindex>
<!-- Layout definition for a class page -->
<class>
<briefdescription visible="yes"/>
<includes visible="$SHOW_INCLUDE_FILES"/>
<inheritancegraph visible="$CLASS_GRAPH"/>
<collaborationgraph visible="$COLLABORATION_GRAPH"/>
<memberdecl>
<nestedclasses visible="yes" title=""/>
<publictypes title=""/>
<services title=""/>
<interfaces title=""/>
<publicslots title=""/>
<signals title=""/>
<publicmethods title=""/>
<publicstaticmethods title=""/>
<publicattributes title=""/>
<publicstaticattributes title=""/>
<protectedtypes title=""/>
<protectedslots title=""/>
<protectedmethods title=""/>
<protectedstaticmethods title=""/>
<protectedattributes title=""/>
<protectedstaticattributes title=""/>
<packagetypes title=""/>
<packagemethods title=""/>
<packagestaticmethods title=""/>
<packageattributes title=""/>
<packagestaticattributes title=""/>
<properties title=""/>
<events title=""/>
<privatetypes title=""/>
<privateslots title=""/>
<privatemethods title=""/>
<privatestaticmethods title=""/>
<privateattributes title=""/>
<privatestaticattributes title=""/>
<friends title=""/>
<related title="" subtitle=""/>
<membergroups visible="yes"/>
</memberdecl>
<detaileddescription title=""/>
<memberdef>
<inlineclasses title=""/>
<typedefs title=""/>
<enums title=""/>
<services title=""/>
<interfaces title=""/>
<constructors title=""/>
<functions title=""/>
<related title=""/>
<variables title=""/>
<properties title=""/>
<events title=""/>
</memberdef>
<allmemberslink visible="yes"/>
<usedfiles visible="$SHOW_USED_FILES"/>
<authorsection visible="yes"/>
</class>
<!-- Layout definition for a namespace page -->
<namespace>
<briefdescription visible="yes"/>
<memberdecl>
<nestednamespaces visible="yes" title=""/>
<constantgroups visible="yes" title=""/>
<interfaces visible="yes" title=""/>
<classes visible="yes" title=""/>
<structs visible="yes" title=""/>
<exceptions visible="yes" title=""/>
<typedefs title=""/>
<sequences title=""/>
<dictionaries title=""/>
<enums title=""/>
<functions title=""/>
<variables title=""/>
<membergroups visible="yes"/>
</memberdecl>
<detaileddescription title=""/>
<memberdef>
<inlineclasses title=""/>
<typedefs title=""/>
<sequences title=""/>
<dictionaries title=""/>
<enums title=""/>
<functions title=""/>
<variables title=""/>
</memberdef>
<authorsection visible="yes"/>
</namespace>
<!-- Layout definition for a file page -->
<file>
<briefdescription visible="yes"/>
<includes visible="$SHOW_INCLUDE_FILES"/>
<includegraph visible="$INCLUDE_GRAPH"/>
<includedbygraph visible="$INCLUDED_BY_GRAPH"/>
<sourcelink visible="yes"/>
<memberdecl>
<interfaces visible="yes" title=""/>
<classes visible="yes" title=""/>
<structs visible="yes" title=""/>
<exceptions visible="yes" title=""/>
<namespaces visible="yes" title=""/>
<constantgroups visible="yes" title=""/>
<defines title=""/>
<typedefs title=""/>
<sequences title=""/>
<dictionaries title=""/>
<enums title=""/>
<functions title=""/>
<variables title=""/>
<membergroups visible="yes"/>
</memberdecl>
<detaileddescription title=""/>
<memberdef>
<inlineclasses title=""/>
<defines title=""/>
<typedefs title=""/>
<sequences title=""/>
<dictionaries title=""/>
<enums title=""/>
<functions title=""/>
<variables title=""/>
</memberdef>
<authorsection/>
</file>
<!-- Layout definition for a group page -->
<group>
<briefdescription visible="yes"/>
<groupgraph visible="$GROUP_GRAPHS"/>
<memberdecl>
<nestedgroups visible="yes" title=""/>
<dirs visible="yes" title=""/>
<files visible="yes" title=""/>
<namespaces visible="yes" title=""/>
<classes visible="yes" title=""/>
<defines title=""/>
<typedefs title=""/>
<sequences title=""/>
<dictionaries title=""/>
<enums title=""/>
<enumvalues title=""/>
<functions title=""/>
<variables title=""/>
<signals title=""/>
<publicslots title=""/>
<protectedslots title=""/>
<privateslots title=""/>
<events title=""/>
<properties title=""/>
<friends title=""/>
<membergroups visible="yes"/>
</memberdecl>
<detaileddescription title=""/>
<memberdef>
<pagedocs/>
<inlineclasses title=""/>
<defines title=""/>
<typedefs title=""/>
<sequences title=""/>
<dictionaries title=""/>
<enums title=""/>
<enumvalues title=""/>
<functions title=""/>
<variables title=""/>
<signals title=""/>
<publicslots title=""/>
<protectedslots title=""/>
<privateslots title=""/>
<events title=""/>
<properties title=""/>
<friends title=""/>
</memberdef>
<authorsection visible="yes"/>
</group>
<!-- Layout definition for a directory page -->
<directory>
<briefdescription visible="yes"/>
<directorygraph visible="yes"/>
<memberdecl>
<dirs visible="yes"/>
<files visible="yes"/>
</memberdecl>
<detaileddescription title=""/>
</directory>
</doxygenlayout>

100
INSTALL.md 100644
View File

@ -0,0 +1,100 @@
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
# Prerequisites
## Required Packages
* automake
* autoconf
* autopoint
* gettext
* hidapi + headers
* libtool
* libusb-1.0 + headers
* libevdev + headers (on Linux)
* pkg-config
* python3 (3.6 or greater)
* git (not required for builds, but necessary to clone the repository)
### Installation instructions
| 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` |
## Optional Packages
* doxygen - to generate HTML documentation and man pages
* libcmocka (1.1 or greater) + headers - to run unit tests
# Installation Instructions
1. Clone the repository
```
git clone https://github.com/nirenjan/libx52.git
```
2. Run autogen.sh
```
cd ./libx52
./autogen.sh
```
3. Run the following commands:
```
./configure --prefix=/usr --localstatedir=/var --sysconfdir=/etc
make && sudo make install
```
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`.
## 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`:
```
--with-udevrulesdir=/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`:
```
--with-input-group=group
```
### Systemd support
The X52 daemon can run either as a foreground process, or it can daemonize
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`
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`.
```
--with-systemdsystemunitdir=/path/to/systemd/system
```

View File

@ -337,3 +337,23 @@ proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
-----------------------------------------------------------------------
X52Pro-Linux is licensed under the terms of the GNU General Public License with
the following clarification and special exception.
Linking this module statically or dynamically with other modules is making a
combined work based on this module. Thus, the terms and conditions of the GNU
General Public License cover the whole combination.
As a special exception, the copyright holders of this module give you
permission to link this module with independent modules to produce an
executable, regardless of the license terms of these independent modules, and
to copy and distribute the resulting executable under terms of your choice,
provided that you also meet, for each linked independent module, the terms and
conditions of the license of that module. An independent module is a module
which is not derived from or based on this module. If you modify this module,
you may extend this exception to your version of the module, but you are not
obliged to do so. If you do not wish to do so, delete this exception statement
from your version.

110
Makefile.am 100644
View File

@ -0,0 +1,110 @@
# Top level Automake for libx52
#
# Copyright (C) 2012-2018 Nirenjan Krishnan (nirenjan@nirenjan.org)
#
# SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
ACLOCAL_AMFLAGS = -I m4
# Build any support libraries first
SUBDIRS = lib
if USE_NLS
SUBDIRS += po
endif
#######################################################################
# Defaults
#######################################################################
bin_PROGRAMS =
check_PROGRAMS =
lib_LTLIBRARIES =
check_LTLIBRARIES =
pkgconfig_DATA =
TESTS =
EXTRA_DIST =
CLEANFILES =
BUILT_SOURCES =
x52includedir = $(includedir)/libx52
x52include_HEADERS =
LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) $(top_srcdir)/tap-driver.sh
########################################################################
# Get build version
########################################################################
BUILT_SOURCES += version-info.h
CLEANFILES += version-info.h
version-info.h: ${top_srcdir}/version-info
CC=${CC} ${top_srcdir}/version-info ${top_srcdir} >$@
########################################################################
# Include automake stubs
########################################################################
include libx52/Makefile.am
include libx52util/Makefile.am
include libx52io/Makefile.am
include libusbx52/Makefile.am
include cli/Makefile.am
include joytest/Makefile.am
include evtest/Makefile.am
include daemon/Makefile.am
include udev/Makefile.am
include bugreport/Makefile.am
include docs/Makefile.am
#######################################################################
# Doxygen support
#######################################################################
if HAVE_DOXYGEN
DXGEN = $(DXGEN_@AM_V@)
DXGEN_ = $(DXGEN_@AM_DEFAULT_V@)
DXGEN_0 = @printf " DXGEN $<\n";
SYSCONFDIR=@sysconfdir@
LOCALSTATEDIR=@localstatedir@
export SYSCONFDIR
export LOCALSTATEDIR
docs/.stamp: Doxyfile
$(DXGEN)$(DOXYGEN) $<
$(AM_V_at)touch $@
all-local: docs/.stamp
clean-local:
rm -rf $(top_builddir)/docs
man1_MANS = docs/man/man1/x52cli.1 docs/man/man1/x52bugreport.1
$(man1_MANS): docs/.stamp
# Install Doxygen generated HTML documentation and manpages
install-data-local:
$(INSTALL) -d $(DESTDIR)$(docdir)
cp -R -P $(top_builddir)/docs/html $(DESTDIR)$(docdir)
uninstall-local:
rm -rf $(DESTDIR)$(docdir)
endif
# Extra files that need to be in the distribution
EXTRA_DIST += \
ABOUT-NLS \
AUTHORS \
ChangeLog.md \
CONTRIBUTING.md \
Doxyfile.in \
DoxygenLayout.xml \
INSTALL.md \
LICENSE \
README.md \
config.rpath \
version-info \
Version \
gettext.h \
usb-ids.h \
po/README.md

View File

@ -1,6 +1,10 @@
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)
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)
with 7 axes, 39 buttons, 1 hat and 1 thumbstick and a multi-function
@ -8,16 +12,37 @@ display which is programmable.
Currently, only Windows drivers are available from Saitek PLC, which
led me to develop a new Linux driver which can program the MFD and
the individual LEDs on the joystick. Although the standard usbhid
driver is capable of reading the joystick, it is not sufficient to
really utilize all the capabilities of this system.
the individual LEDs on the joystick. The standard usbhid driver is
capable of reading the joystick, but it cannot control the MFD or LEDs.
This project is currently a work-in-progress. However a high level
outline of the current objectives are listed below:
Most of the extra functionality can be handled from userspace. See
the individual folders for README information.
* Write a kernel module and export sysfs interfaces to act as a
driver.
* Write a userspace program that can configure the kernel module
and create custom button mappings to keyboard or mouse events.
* Add interrupt handling and export a /dev/input/jsX interface.
* Allow userspace programs to register callbacks on MFD button events.
**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
for your needs as it provides a graphical interface to control the MFD and LEDs.
# Installing released versions
Beginning from version v0.2.3, prebuilt packages are available on Ubuntu PPA and
the Arch User Repository.
## Ubuntu
This project has been released as a PPA on Ubuntu. To install the package, run
the following commands in the terminal.
```
sudo apt-add-repository ppa:nirenjan/libx52
sudo apt update
sudo apt install libx52-1
```
## Arch Linux
This is available on the [AUR](https://aur.archlinux.org/packages/libx52)
# Building and installing from source
See [INSTALL.md](INSTALL.md)

1
Version 100644
View File

@ -0,0 +1 @@
0.3.2

3
autogen.sh 100755
View File

@ -0,0 +1,3 @@
#!/bin/sh -x
autoreconf --install

View File

@ -0,0 +1,24 @@
# Automake for libx52-bugreport
#
# Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
#
# SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
bin_PROGRAMS += x52bugreport
# Bug report program that reports the build and linked library versions
x52bugreport_SOURCES = bugreport/bugreport.c
x52bugreport_CFLAGS = \
-I$(top_srcdir)/libx52io \
@LIBUSB_CFLAGS@ \
@HIDAPI_CFLAGS@ \
$(WARN_CFLAGS)
x52bugreport_LDFLAGS = \
@LIBUSB_LIBS@ \
@HIDAPI_LIBS@ \
$(WARN_LDFLAGS)
x52bugreport_LDADD = libx52io.la
EXTRA_DIST += bugreport/bugreport.dox

View File

@ -0,0 +1,107 @@
/*
* libx52 bugreport utility
*
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
#include "config.h"
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/utsname.h>
#include "libusb.h"
#include "hidapi.h"
#include "libx52io.h"
#include "version-info.h"
static void print_sysinfo(void)
{
struct utsname uts;
puts("");
puts("System info:");
puts("============");
if (uname(&uts) < 0) {
printf("Unable to get system info: %s\n", strerror(errno));
} else {
printf("%s %s %s (%s)\n", uts.sysname, uts.release, uts.machine, uts.version);
}
}
static void print_devinfo(void)
{
libx52io_context *ctx;
int rc;
puts("");
puts("Device info:");
puts("============");
rc = libx52io_init(&ctx);
if (rc != LIBX52IO_SUCCESS) {
puts(libx52io_strerror(rc));
return;
}
rc = libx52io_open(ctx);
if (rc != LIBX52IO_SUCCESS) {
puts(libx52io_strerror(rc));
goto devinfo_cleanup;
}
printf("Device ID: vendor 0x%04x product 0x%04x version 0x%04x\n",
libx52io_get_vendor_id(ctx),
libx52io_get_product_id(ctx),
libx52io_get_device_version(ctx));
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)
{
const struct libusb_version *libusb;
puts("libx52 bugreport");
puts("================");
printf("Package version: %s\n", VERSION);
printf("Build version: %s\n", BUILD_VERSION);
printf("Built on: %s\n", BUILD_HOST);
printf("Compiler: %s\n", BUILD_COMPILER);
printf("Build date: %s\n", BUILD_DATE);
printf("version-info %s\n", BUILD_VERSION_INFO_IDENT);
puts("");
puts("Built against:");
puts("==============");
printf("libusb API version: 0x%08x\n", LIBUSB_API_VERSION);
#if defined HID_API_VERSION_STR
printf("hidapi version: %s\n", HID_API_VERSION_STR);
#endif
libusb = libusb_get_version();
puts("");
puts("System versions:");
puts("================");
printf("libusb: %d.%d.%d.%d%s (%s)\n",
libusb->major, libusb->minor, libusb->micro, libusb->nano,
libusb->rc, libusb->describe);
#if defined HID_API_VERSION_STR
printf("hidapi: %s\n", hid_version_str());
#endif
print_sysinfo();
print_devinfo();
return 0;
}

View File

@ -0,0 +1,22 @@
/**
@page x52bugreport System information utility for bug reports
\htmlonly
<b>x52bugreport</b> - System information utility for bug reports
\endhtmlonly
# SYNOPSIS
<tt>\b x52bugreport</tt>
# DESCRIPTION
\b x52bugreport is a utility that collects and reports information on the
current system and build environment. The reported information can be provided
when raising a bug report on https://github.com/nirenjan/libx52/issues.
# USAGE
\b x52bugreport
*/

27
cli/Makefile.am 100644
View File

@ -0,0 +1,27 @@
# Automake for x52cli
#
# Copyright (C) 2012-2018 Nirenjan Krishnan (nirenjan@nirenjan.org)
#
# SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
bin_PROGRAMS += x52cli
# Command line utility that front ends the core library
x52cli_SOURCES = cli/x52_cli.c
x52cli_CFLAGS = -I $(top_srcdir)/libx52 $(WARN_CFLAGS)
x52cli_LDFLAGS = $(WARN_LDFLAGS)
x52cli_LDADD = libx52.la
if HAVE_CMOCKA
TESTS += test-cli
check_PROGRAMS += test-cli
test_cli_SOURCES = cli/x52_cli.c cli/test_x52_cli.c
test_cli_CFLAGS = @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
cli/test_x52_cli.c: cli/test_x52_cli_tests.c
endif
EXTRA_DIST += cli/test_x52_cli_tests.c

181
cli/test_x52_cli.c 100644
View File

@ -0,0 +1,181 @@
/*
* Saitek X52 Pro MFD & LED driver - CLI test harness
*
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <setjmp.h>
#include <cmocka.h>
#include "libx52.h"
extern int run_main(int argc, char **argv);
/* Wrapper functions for libx52 */
int libx52_init(libx52_device **dev)
{
function_called();
*dev = NULL;
return mock();
}
int libx52_connect(libx52_device *dev)
{
function_called();
return mock();
}
int libx52_update(libx52_device *dev)
{
return LIBX52_SUCCESS;
}
void libx52_exit(libx52_device *dev)
{
return;
}
const char *libx52_strerror(libx52_error_code rc)
{
function_called();
return "";
}
int libx52_set_text(libx52_device *x52, uint8_t line, const char *text, uint8_t length)
{
function_called();
assert_ptr_equal(x52, NULL);
check_expected(line);
check_expected(text);
check_expected(length);
return mock();
}
int libx52_set_led_state(libx52_device *x52, libx52_led_id id, libx52_led_state state)
{
function_called();
assert_ptr_equal(x52, NULL);
check_expected(id);
check_expected(state);
return mock();
}
int libx52_set_clock(libx52_device *x52, time_t time, int local)
{
function_called();
assert_ptr_equal(x52, NULL);
check_expected(time);
check_expected(local);
return mock();
}
int libx52_set_clock_timezone(libx52_device *x52, libx52_clock_id clock, int offset)
{
function_called();
assert_ptr_equal(x52, NULL);
check_expected(clock);
check_expected(offset);
return mock();
}
int libx52_set_clock_format(libx52_device *x52, libx52_clock_id clock, libx52_clock_format format)
{
function_called();
assert_ptr_equal(x52, NULL);
check_expected(clock);
check_expected(format);
return mock();
}
int libx52_set_time(libx52_device *x52, uint8_t hour, uint8_t minute)
{
function_called();
assert_ptr_equal(x52, NULL);
check_expected(hour);
check_expected(minute);
return mock();
}
int libx52_set_date(libx52_device *x52, uint8_t dd, uint8_t mm, uint8_t yy)
{
function_called();
assert_ptr_equal(x52, NULL);
check_expected(dd);
check_expected(mm);
check_expected(yy);
return mock();
}
int libx52_set_date_format(libx52_device *x52, libx52_date_format format)
{
function_called();
assert_ptr_equal(x52, NULL);
check_expected(format);
return mock();
}
int libx52_set_brightness(libx52_device *x52, uint8_t mfd, uint16_t brightness)
{
function_called();
assert_ptr_equal(x52, NULL);
check_expected(mfd);
check_expected(brightness);
return mock();
}
int libx52_set_shift(libx52_device *x52, uint8_t state)
{
function_called();
assert_ptr_equal(x52, NULL);
check_expected(state);
return mock();
}
int libx52_set_blink(libx52_device *x52, uint8_t state)
{
function_called();
assert_ptr_equal(x52, NULL);
check_expected(state);
return mock();
}
int libx52_vendor_command(libx52_device *x52, uint16_t index, uint16_t value)
{
function_called();
assert_ptr_equal(x52, NULL);
check_expected(index);
check_expected(value);
return mock();
}
#include "test_x52_cli_tests.c"
#define TEST_LIST
const struct CMUnitTest tests[] = {
#include "test_x52_cli_tests.c"
};
int main(int argc, char **argv)
{
cmocka_set_message_output(CM_OUTPUT_TAP);
cmocka_run_group_tests(tests, NULL, NULL);
return 0;
}

File diff suppressed because it is too large Load Diff

547
cli/x52_cli.c 100644
View File

@ -0,0 +1,547 @@
/*
* Saitek X52 Pro MFD & LED driver
*
* Copyright (C) 2015-2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
/**
@page x52cli Command Line Interface to libx52
\htmlonly
<b>x52cli</b> - Command line interface to libx52
\endhtmlonly
# SYNOPSIS
<tt>\b x52cli \a commands [\a command-options]</tt>
# DESCRIPTION
\b x52cli is a command line interface to the X52 library that allows you to set
the LEDs and different parameters on the multifunction display (MFD).
Running \b x52cli without any arguments will display a brief help message.
# COMMANDS
Commands are not case sensitive.
- <tt>\b bri { \b mfd | \b led } < \a brightness >
</tt>\n \manonly \fR \endmanonly
Set the brightness of the \b MFD or <b>LED</b>s. \a brightness can be any
numeric value between 0 and 128. Higher values are accepted, but may not have
the desired effect.
- <tt>\b mfd < \a line > < \a text > </tt>\n \manonly \fR \endmanonly
Set the text on the MFD \a line. \a line can be \c 0, \c 1 or \c 2, and refers
to the first, second or third line of the multifunction display respectively.
\a text cannot have embedded NUL characters (0x00) and must correspond with
the character map fo the MFD. \a text should be quoted in order to preserve
embedded whitespace. To pass raw hex values, use \b printf(1) as shown in the
\ref x52cli_examples section. Note that \a text is limited to a length of 16
characters. While you can pass in longer strings, they will be silenty
truncated.
- <tt>\b led < \a led-id > < \a state > </tt>\n \manonly \fR \endmanonly
Set the LED \a led-id to \a state. See \ref x52cli_leds for a list of
supported values.
- <tt>\b blink { \b on | \b off } </tt>\n \manonly \fR \endmanonly
Turn the \b blink state \b on or \b off.
- <tt>\b shift { \b on | \b off } </tt>\n \manonly \fR \endmanonly
Turn the \b shift indicator in the MFD \b on or \b off.
- <tt>\b clock { \b local | \b gmt } { \b 12hr | \b 24hr }
{ \b ddmmyy | \b mmddyy | \b yymmdd }</tt>\n \manonly \fR \endmanonly
Set the clock 1 display to the current \b local or \b GMT time and date.
Clock can be configured to display in either \b 12hr or \b 24hr mode. Date
can be displayed in one of the following formats: \b DD-MM-YY, \b MM-DD-YY,
or \b YY-MM-DD.
- <tt>\b offset { \b 2 | \b 3 } < \a offset-val >
{ \b 12hr | \b 24hr }</tt>\n \manonly \fR \endmanonly
Set the offset for clock \b 2 or \b 3 and configure them to display in either
\b 12hr or \b 24hr mode. \a offset-val is in minutes east of \b UTC and can
range from \a -1440 to \a +1440.
- <tt>\b time < \a hour > < \a minute >
{ \b 12hr | \b 24hr }</tt>\n \manonly \fR \endmanonly
Set the time for clock 1 to <em>hour:minute</em> and configure it to display
in \b 12hr or \b 24hr mode.
- <tt>\b date <\a dd > <\a mm > <\a yy>
{ \b ddmmyy | \b mmddyy | \b yymmdd }</tt>\n \manonly \fR \endmanonly
Set the date on the MFD to the values represented by \a dd, \a mm and \a yy in
the requested format.
- <tt>\b raw < \a wIndex > < \a wValue ></tt>\n \manonly \fR \endmanonly
Send a raw vendor control request to the connected joystick.\n
\warning You should only use the raw command if you know what you are doing.
Sending an invalid control sequence can potentially damage or destroy your
device.
@section x52cli_leds LEDs
This is the list of LED IDs and corresponding states supported by the X52 Pro.
Note that the \b on state is only allowed for the \b fire and \b throttle LEDs,
and they do not support the \b red, \b amber and \b green states. The remaining
LEDs do not support the \b on state, but support all the other states.
\note The \b led command is only supported on the X52 Pro, not on the X52.
## LED IDs
- \b fire
- \b a
- \b b
- \b d
- \b e
- \b t1
- \b t2
- \b t3
- \b pov
- \b clutch
- \b throttle
## States
- \b off
- \b on
- \b red
- \b amber
- \b green
# LIMITATIONS
\b x52cli does not maintain any state between invocations. As a result the
\b clock command will reset the relative offsets for clocks 2 and 3 back to 0
and configure them to be in 12 hour mode. To work around this, use the \b date
and \b time commands instead to manually configure the date and time.
\note The device does not have an internal clock; as a result, the MFD
display will not advance automatically. You must call the \b clock or \b date
and \b time commands periodically to update the time on the device. However, if
you are running \b x52d and the clock manager is enabled, then \b x52d will
manage and automatically update the clock on the X52 MFD display.
# PERMISSIONS
You must have write permissions to the USB device in order to use the \b libx52
library, and by extension, \b x52cli.
The simplest method to obtain such permissions is to run \b x52cli as root,
possibly through \b sudo(8)
@section x52cli_examples EXAMPLES
- Turn off the T1 LED.
> <tt>\b x52cli led t1 off</tt>
- Turn the B LED to Amber.
> <tt>\b x52cli led B amber</tt>
- Set line 1 of the MFD to display "Hello World"
> <tt>\b x52cli mfd 0 "Hello World"</tt>
- Set line 2 of the MFD to display "¿Cómo Estás?"
> <tt>\b x52cli mfd 1 "$(printf '\\x9FC\\xE2mo Est\\xE0s?')"</tt>
*/
#define _GNU_SOURCE
#include "config.h"
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include "libx52.h"
struct string_map {
const char *key;
union {
libx52_clock_id clock_id;
libx52_clock_format clock_format;
libx52_date_format date_format;
libx52_led_id led_id;
libx52_led_state led_state;
int int_val;
intptr_t ptr_val;
} value;
};
// Max 4 arguments for now
#define MAX_ARGS 4
typedef int (*handler_cb)(libx52_device *x52, void *args[]);
struct command_handler {
handler_cb handler;
int num_args;
const struct string_map *maps[MAX_ARGS];
const char *help;
};
#define SAVE_ARGUMENT(type, name, arg) type name = (type)(uintptr_t)(arg)
#define PARSE_ARGUMENT(type, arg) ((type)(uintptr_t)(arg))
#define MAP(name) name##_map
#define DEFINE_MAP(name) static const struct string_map MAP(name)[]
#define MAP_CLOCK_ID(id) {.key = #id, .value.clock_id = LIBX52_CLOCK_ ## id}
#define MAP_CLOCK_FORMAT(fmt) {.key = #fmt, .value.clock_format = LIBX52_CLOCK_FORMAT_ ## fmt}
#define MAP_DATE_FORMAT(fmt) {.key = #fmt, .value.date_format = LIBX52_DATE_FORMAT_ ## fmt}
#define MAP_LED_ID(id) {.key = #id, .value.led_id = LIBX52_LED_ ## id}
#define MAP_LED_STATE(state) {.key = #state, .value.led_state = LIBX52_LED_STATE_ ## state}
#define MAP_INT(str, val) {.key = str, .value.int_val = val}
#define MAP_TERMINATOR MAP_INT(NULL, -1)
/**
* Parse a string and match it with a corresponding value
* Maps are arrays which contain a terminating entry with a NULL key.
*
* Return 0 if no match
*/
static int map_lookup(const struct string_map *map, const char *str, struct string_map *result)
{
const struct string_map *map_index = map;
/* Search through the map for a matching key */
while (map_index->key) {
if (!strcasecmp(str, map_index->key)) {
result->value = map_index->value;
return 1;
}
map_index++;
}
return 0;
}
/* Map for LED state */
DEFINE_MAP(led_state) = {
MAP_LED_STATE(OFF),
MAP_LED_STATE(ON),
MAP_LED_STATE(RED),
MAP_LED_STATE(AMBER),
MAP_LED_STATE(GREEN),
MAP_TERMINATOR
};
/* Map for LED identifier */
DEFINE_MAP(led_id) = {
MAP_LED_ID(FIRE),
MAP_LED_ID(A),
MAP_LED_ID(B),
MAP_LED_ID(D),
MAP_LED_ID(E),
MAP_LED_ID(T1),
MAP_LED_ID(T2),
MAP_LED_ID(T3),
MAP_LED_ID(POV),
MAP_LED_ID(CLUTCH),
MAP_LED_ID(THROTTLE),
MAP_TERMINATOR
};
/* Map for date format */
DEFINE_MAP(date_format) = {
MAP_DATE_FORMAT(DDMMYY),
MAP_DATE_FORMAT(MMDDYY),
MAP_DATE_FORMAT(YYMMDD),
MAP_TERMINATOR
};
/* Map for brightness setting */
DEFINE_MAP(brightness_targets) = {
MAP_INT( "mfd", 1 ),
MAP_INT( "led", 0 ),
MAP_TERMINATOR
};
/* Map for blink/shift on/off */
DEFINE_MAP(on_off) = {
MAP_INT( "off", 0 ),
MAP_INT( "on", 1 ),
MAP_TERMINATOR
};
/* Map for clock 0 timezone */
DEFINE_MAP(clock0_timezone) = {
MAP_INT( "gmt", 0 ),
MAP_INT( "local", 1 ),
MAP_TERMINATOR
};
/* Map for identifying the clock for the timezone */
DEFINE_MAP(clocks) = {
MAP_CLOCK_ID(1),
MAP_CLOCK_ID(2),
MAP_CLOCK_ID(3),
MAP_TERMINATOR
};
/* Map for identifying the time format */
DEFINE_MAP(time_format) = {
MAP_CLOCK_FORMAT(12HR),
MAP_CLOCK_FORMAT(24HR),
MAP_TERMINATOR
};
static int update_led(libx52_device *x52, void *args[])
{
return libx52_set_led_state(x52,
PARSE_ARGUMENT(libx52_led_id, args[0]),
PARSE_ARGUMENT(libx52_led_state, args[1]));
}
static int update_bri(libx52_device *x52, void *args[])
{
unsigned long int brightness = strtoul(args[1], NULL, 0);
return libx52_set_brightness(x52,
PARSE_ARGUMENT(uint8_t, args[0]), (uint16_t)brightness);
}
static int update_mfd(libx52_device *x52, void *args[])
{
uint8_t line = (uint8_t)strtoul(args[0], NULL, 0);
uint8_t length = strlen(args[1]);
return libx52_set_text(x52, line, args[1], length);
}
static int update_blink(libx52_device *x52, void *args[])
{
return libx52_set_blink(x52, PARSE_ARGUMENT(int, args[0]));
}
static int update_shift(libx52_device *x52, void *args[])
{
return libx52_set_shift(x52, PARSE_ARGUMENT(int, args[0]));
}
static int update_clock(libx52_device *x52, void *args[])
{
int rc;
rc = libx52_set_clock(x52, time(NULL),
PARSE_ARGUMENT(int, args[0]));
if (!rc) {
rc = libx52_set_clock_format(x52, LIBX52_CLOCK_1,
PARSE_ARGUMENT(libx52_clock_format, args[1]));
}
if (!rc) {
rc = libx52_set_date_format(x52,
PARSE_ARGUMENT(libx52_date_format, args[2]));
}
return rc;
}
static int update_offset(libx52_device *x52, void *args[])
{
int offset = (int)strtol(args[1], NULL, 0);
int rc;
SAVE_ARGUMENT(libx52_clock_id, clock, args[0]);
rc = libx52_set_clock_timezone(x52, clock, offset);
if (!rc) {
rc = libx52_set_clock_format(x52, clock,
PARSE_ARGUMENT(libx52_clock_format, args[2]));
}
return rc;
}
static int update_time(libx52_device *x52, void *args[])
{
int hh = (int)strtol(args[0], NULL, 0);
int mm = (int)strtol(args[1], NULL, 0);
int rc;
/* Set the time value */
rc = libx52_set_time(x52, hh, mm);
if (!rc) {
rc = libx52_set_clock_format(x52, LIBX52_CLOCK_1,
PARSE_ARGUMENT(libx52_clock_format, args[2]));
}
return rc;
}
static int update_date(libx52_device *x52, void *args[])
{
int dd = (int)strtol(args[0], NULL, 0);
int mm = (int)strtol(args[1], NULL, 0);
int yy = (int)strtol(args[2], NULL, 0);
int rc;
/* Set the date value */
rc = libx52_set_date(x52, dd, mm, yy);
if (!rc) {
rc = libx52_set_date_format(x52,
PARSE_ARGUMENT(libx52_date_format, args[3]));
}
return rc;
}
static int update_raw(libx52_device *x52, void *args[])
{
uint16_t wIndex = (uint16_t)strtoul(args[0], NULL, 0);
uint16_t wValue = (uint16_t)strtoul(args[1], NULL, 0);
return libx52_vendor_command(x52, wIndex, wValue);
}
/* Commands for CLI */
#define COMMANDS \
X(led, LED_STATE, "<led-id> <state>", 2, \
MAP(led_id), MAP(led_state)) \
X(bri, BRIGHTNESS, "{mfd | led} <brightness level>", 2, \
MAP(brightness_targets), NULL) \
X(mfd, MFD_TEXT, "<line> <text in quotes>", 2, \
NULL, NULL) \
X(blink, BLINK, "{ on | off }", 1, \
MAP(on_off)) \
X(shift, SHIFT, "{ on | off }", 1, \
MAP(on_off)) \
X(clock, CLOCK, \
"{local | gmt} {12hr | 24hr} {ddmmyy | mmddyy | yymmdd}", \
3, MAP(clock0_timezone), MAP(time_format), MAP(date_format)) \
X(offset, OFFSET, \
"{2 | 3} <offset from clock 1 in minutes> {12hr | 24hr}", \
3, MAP(clocks), NULL, MAP(time_format)) \
X(time, TIME, \
"<hour> <minute> {12hr | 24hr}", 3, \
NULL, NULL, MAP(time_format)) \
X(date, DATE, \
"<dd> <mm> <yy> {ddmmyy | mmddyy | yymmdd}", 4, \
NULL, NULL, NULL, MAP(date_format)) \
X(raw, RAW, "<wIndex> <wValue>", 2, NULL, NULL)
/* Enums for command identification */
#define X(cmd, en, help, args, ...) X52_CTL_CMD_ ## en,
enum {
COMMANDS
X52_CTL_CMD_MAX
};
#undef X
/* Map for commands */
#define X(cmd, en, help, args, ...) MAP_INT( #cmd, X52_CTL_CMD_ ## en),
DEFINE_MAP(command) = {
COMMANDS
MAP_TERMINATOR
};
#undef X
/* Command handlers */
#define X(cmd, en, help, args, ...) \
[X52_CTL_CMD_ ## en] = { \
update_ ## cmd, \
args, \
{ __VA_ARGS__ }, \
#cmd " " help \
},
const struct command_handler handlers[X52_CTL_CMD_MAX] = {
COMMANDS
};
#undef X
static void do_help(const struct command_handler *cmd)
{
int i;
if (cmd) {
fprintf(stderr, "Command usage: %s\n", cmd->help);
} else {
printf("\nCommands:\n");
for (i = 0; i < X52_CTL_CMD_MAX; i++) {
printf("\t%s\n", handlers[i].help);
}
printf("\nWARNING: raw command may damage your device\n\n");
}
}
#ifdef X52_CLI_TESTING
int run_main(int argc, char **argv)
#else
int main(int argc, char **argv)
#endif
{
libx52_device *x52;
struct string_map result;
const struct command_handler *cmd;
int i;
void *args[MAX_ARGS];
int rc;
if (argc < 2) {
fprintf(stderr, "Usage: %s <command> [arguments]\n", argv[0]);
do_help(NULL);
return 1;
}
if (!map_lookup(command_map, argv[1], &result)) {
fprintf(stderr, "Unsupported command %s\n", argv[1]);
do_help(NULL);
return 1;
}
cmd = &handlers[result.value.int_val];
if (cmd->num_args > argc - 2) {
fprintf(stderr, "Insufficient arguments for command %s\n", argv[1]);
do_help(cmd);
return 1;
}
/* Clear the arguments array */
memset(args, 0, sizeof(args));
for (i = 0; i < cmd->num_args; i++) {
if (cmd->maps[i]) {
if (!map_lookup(cmd->maps[i], argv[2+i], &result)) {
fprintf(stderr, "Invalid argument %s\n", argv[2+i]);
return 1;
}
args[i] = (void *)result.value.ptr_val;
} else {
args[i] = argv[2+i];
}
}
/* Initialize libx52 */
rc = libx52_init(&x52);
if (rc != LIBX52_SUCCESS) {
fprintf(stderr, "Error initializing X52 library: %s\n", libx52_strerror(rc));
return 1;
}
/* Make sure we are connected to the joystick */
rc = libx52_connect(x52);
if (rc != LIBX52_SUCCESS) {
fprintf(stderr, "Error connecting to joystick: %s\n", libx52_strerror(rc));
return 1;
}
rc = (*(cmd->handler))(x52, args);
if (rc != LIBX52_SUCCESS) {
fprintf(stderr, "Error: %s\n", libx52_strerror(rc));
rc = 1;
}
libx52_update(x52);
libx52_exit(x52);
return rc;
}

146
configure.ac 100644
View File

@ -0,0 +1,146 @@
# Autoconf settings for libx52
#
# Copyright (C) 2012-2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
#
# SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
AC_INIT([libx52], [m4_esyscmd_s([cat ./Version])], [nirenjan@gmail.com])
AC_CONFIG_MACRO_DIR([m4])
AM_INIT_AUTOMAKE([-Wall foreign subdir-objects])
AC_REQUIRE_AUX_FILE([tap-driver.sh])
AC_PROG_CC
AC_PROG_CC_STDC
AC_PROG_AWK
AC_PROG_SED
AC_PROG_MKDIR_P
AM_PROG_AR
AM_PATH_PYTHON([3.5])
LT_INIT
PKG_PROG_PKG_CONFIG
PKG_INSTALLDIR
AX_COMPILER_FLAGS
AC_CANONICAL_HOST
AX_GCC_FUNC_ATTRIBUTE([constructor])
AX_GCC_FUNC_ATTRIBUTE([destructor])
AX_GCC_FUNC_ATTRIBUTE([format])
AX_GCC_FUNC_ATTRIBUTE([noreturn])
AC_C_TYPEOF
AC_MSG_NOTICE([Detected host OS is ${host_os}])
build_linux=no
# Detect target system
case "${host_os}" in
linux*)
build_linux=yes
;;
esac
AM_CONDITIONAL([LINUX], [test "x${build_linux}" = "xyes"])
# Internationalization
AM_GNU_GETTEXT([external])
AM_GNU_GETTEXT_VERSION(0.19)
AM_CONDITIONAL([USE_NLS], [test "x${USE_NLS}" == "xyes"])
# Check for libusb-1.0
PKG_CHECK_MODULES([LIBUSB], [libusb-1.0])
AC_SUBST([LIBUSB_PC], [libusb-1.0])
# systemd support
PKG_CHECK_MODULES([SYSTEMD], [systemd], [have_systemd=yes], [have_systemd=no])
AC_ARG_ENABLE([systemd],
[AS_HELP_STRING([--disable-systemd], [Disable systemd support])]
)
AC_ARG_WITH([systemdsystemunitdir],
[AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd unit files])],
[systemdsystemunitdir=$withval],
[systemdsystemunitdir=$($PKG_CONFIG --variable=systemdsystemunitdir systemd)]
)
AC_SUBST([systemdsystemunitdir], [$systemdsystemunitdir])
AM_CONDITIONAL([HAVE_SYSTEMD], [test "x$have_systemd" = "xyes" -a "x$enable_systemd" != "xno"])
AM_COND_IF([HAVE_SYSTEMD],,
[
AC_MSG_NOTICE([systemd not found or disabled. Enabling timestamps in logs])
AX_APPEND_FLAG([-DPINELOG_SHOW_DATE=1], [PINELOG_CFLAGS])
]
)
# evdev support
# This is only on Linux machines, so we need to set an automake conditional
PKG_CHECK_MODULES([EVDEV], [libevdev], [have_evdev=yes], [have_evdev=no])
AM_CONDITIONAL([HAVE_EVDEV], [test "x$have_evdev" = "xyes"])
# Pinelog configuration
AX_APPEND_FLAG([-DPINELOG_SHOW_LEVEL=1], [PINELOG_CFLAGS])
AX_APPEND_FLAG([-DPINELOG_SHOW_BACKTRACE=1], [PINELOG_CFLAGS])
AX_APPEND_FLAG([-DPINELOG_BUFFER_SZ=1024], [PINELOG_CFLAGS])
AC_SUBST([PINELOG_CFLAGS])
# Check for hidapi. This uses a different pkg-config file on Linux vs other
# hosts, so check accordingly
AM_COND_IF([LINUX], [hidapi_backend=hidapi-hidraw], [hidapi_backend=hidapi])
PKG_CHECK_MODULES([HIDAPI], [${hidapi_backend}])
AC_SUBST([HIDAPI_PC], [${hidapi_backend}])
# Check for pthreads
ACX_PTHREAD
# make distcheck doesn't work if some files are installed outside $prefix.
# Check for a prefix ending in /_inst, if this is found, we can assume this
# to be a make distcheck, and disable some of the installcheck stuff.
AS_CASE([$prefix], [*/_inst],
[AC_MSG_NOTICE([[Prefix ends in /_inst; this looks like a 'make distcheck']])
is_make_distcheck=yes])
AM_CONDITIONAL([IS_MAKE_DISTCHECK], [test "x$is_make_distcheck" = xyes])
AC_MSG_CHECKING([final decision IS_MAKE_DISTCHECK (running "make distcheck"?)])
AM_COND_IF([IS_MAKE_DISTCHECK], [AC_MSG_RESULT([yes])], [AC_MSG_RESULT([no])])
# udev support
PKG_CHECK_MODULES([UDEV], [udev], [have_udev=yes], [have_udev=no])
AM_CONDITIONAL([HAVE_UDEV], [test "x$have_udev" = xyes])
AC_ARG_WITH([udevrulesdir],
AS_HELP_STRING([--with-udevrulesdir=DIR], [Directory for udev rules]),
[udevrulesdir=$withval],
[udevrulesdir=$($PKG_CONFIG --variable=udevdir udev)"/rules.d"])
AC_SUBST([udevrulesdir], [$udevrulesdir])
AC_ARG_WITH([input-group],
AS_HELP_STRING([--with-input-group=GROUP], [Group allowed to access input devices]),
[input_group=$withval],
[input_group=plugdev])
AC_SUBST([input_group], [$input_group])
# Doxygen Support
AC_CHECK_PROGS([DOXYGEN], [doxygen])
AM_CONDITIONAL([HAVE_DOXYGEN], [test -n "$DOXYGEN"])
AM_COND_IF([HAVE_DOXYGEN],
[AC_CONFIG_FILES([Doxyfile])],
[AC_MSG_WARN(["Doxygen not found; continuing without doxygen support"])])
# cmocka unit tests
PKG_CHECK_MODULES([CMOCKA], [cmocka >= 1.1], [have_cmocka=yes], [have_cmocka=no])
AM_CONDITIONAL([HAVE_CMOCKA], [test "x$have_cmocka" = xyes])
AM_COND_IF([HAVE_CMOCKA], [],
[AC_MSG_WARN(["cmocka not found; disabling unit test build"])])
# Check for the presence of tm_gmtoff in struct tm. If we have this, then we
# can use it to determine the true GMT offset
AC_CHECK_MEMBERS([struct tm.tm_gmtoff],,
[AC_MSG_WARN(["Cannot find tm_gmtoff in struct tm, using slower method"])],
[#define _GNU_SOURCE
#include <time.h>
])
# Configuration headers
AC_CONFIG_HEADERS([config.h])
AC_CONFIG_FILES([ po/Makefile.in
Makefile
lib/Makefile
libx52/libx52.pc
libx52io/libx52io.pc
libx52util/libx52util.pc
lib/pinelog/Makefile
lib/inih/Makefile
udev/60-saitek-x52-x52pro.rules
])
AC_OUTPUT

145
daemon/Makefile.am 100644
View File

@ -0,0 +1,145 @@
# Automake for x52d
#
# Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
#
# SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
bin_PROGRAMS += x52d x52ctl
# Service daemon that manages the X52 device
x52d_SOURCES = \
daemon/x52d_main.c \
daemon/x52d_config_parser.c \
daemon/x52d_config_dump.c \
daemon/x52d_config.c \
daemon/x52d_device.c \
daemon/x52d_client.c \
daemon/x52d_clock.c \
daemon/x52d_mouse.c \
daemon/x52d_notify.c \
daemon/x52d_led.c \
daemon/x52d_command.c \
daemon/x52d_comm_internal.c \
daemon/x52d_comm_client.c
x52d_CFLAGS = \
-I $(top_srcdir) \
-I $(top_srcdir)/libx52io \
-I $(top_srcdir)/libx52 \
-I $(top_srcdir)/libx52util \
-I $(top_srcdir)/lib/pinelog \
-I $(top_srcdir)/lib/inih \
-DSYSCONFDIR=\"$(sysconfdir)\" \
-DLOCALEDIR=\"$(localedir)\" \
-DLOGDIR=\"$(localstatedir)/log\" \
-DRUNDIR=\"$(localstatedir)/run\" \
@PTHREAD_CFLAGS@ $(WARN_CFLAGS)
x52d_LDFLAGS = @PTHREAD_LIBS@ $(WARN_LDFLAGS)
x52d_LDADD = \
lib/pinelog/libpinelog.la \
lib/inih/libinih.la \
libx52.la \
@LTLIBINTL@
if HAVE_EVDEV
x52d_SOURCES += \
daemon/x52d_io.c \
daemon/x52d_mouse_evdev.c
x52d_CFLAGS += -DHAVE_EVDEV @EVDEV_CFLAGS@
x52d_LDFLAGS += @EVDEV_LIBS@
x52d_LDADD += libx52io.la
endif
lib_LTLIBRARIES += libx52dcomm.la
# Client library to communicate with X52 daemon
libx52dcomm_la_SOURCES = \
daemon/x52d_comm_client.c \
daemon/x52d_comm_internal.c
libx52dcomm_la_CFLAGS = \
-I $(top_srcdir) \
-DSYSCONFDIR=\"$(sysconfdir)\" \
-DLOCALEDIR=\"$(localedir)\" \
-DLOGDIR=\"$(localstatedir)/log\" \
-DRUNDIR=\"$(localstatedir)/run\" \
$(WARN_CFLAGS)
libx52dcomm_la_LDFLAGS = $(WARN_LDFLAGS)
x52include_HEADERS += daemon/x52dcomm.h
x52ctl_SOURCES = daemon/x52ctl.c
x52ctl_CFLAGS = \
-I $(top_srcdir) \
$(WARN_CFLAGS)
x52ctl_LDFLAGS = $(WARN_LDFLAGS)
x52ctl_LDADD = libx52dcomm.la @LTLIBINTL@
x52dconfdir = @sysconfdir@/x52d
x52dconf_DATA = daemon/x52d.conf
install-exec-hook:
$(MKDIR_P) $(DESTDIR)$(localstatedir)/log
$(MKDIR_P) $(DESTDIR)$(localstatedir)/run
EXTRA_DIST += \
daemon/daemon.dox \
daemon/protocol.dox \
daemon/x52d.service.in \
daemon/x52d_client.h \
daemon/x52d_clock.h \
daemon/x52d_config.def \
daemon/x52d_config.h \
daemon/x52d_const.h \
daemon/x52d_device.h \
daemon/x52d_io.h \
daemon/x52d_mouse.h \
daemon/x52d_notify.h \
daemon/x52d_command.h \
daemon/x52dcomm.h \
daemon/x52dcomm-internal.h \
daemon/x52d.conf
# Test cases
EXTRA_DIST += \
daemon/test_daemon_comm.py \
daemon/tests/config/args.tc \
daemon/tests/config/clock.tc \
daemon/tests/config/led.tc \
daemon/tests/config/mouse.tc \
daemon/tests/logging/error.tc \
daemon/tests/logging/global.tc \
daemon/tests/logging/module.tc \
daemon/tests/cli.tc
TESTS += daemon/test_daemon_comm.py
if HAVE_CMOCKA
check_PROGRAMS += x52d-mouse-test
x52d_mouse_test_SOURCES = \
daemon/x52d_mouse_test.c \
daemon/x52d_mouse.c
x52d_mouse_test_CFLAGS = \
-DLOCALEDIR='"$(localedir)"' \
-I $(top_srcdir) \
-I $(top_srcdir)/libx52 \
-I $(top_srcdir)/libx52io \
-I $(top_srcdir)/lib/pinelog \
$(WARN_CFLAGS) @CMOCKA_CFLAGS@
x52d_mouse_test_LDFLAGS = @CMOCKA_LIBS@ $(WARN_LDFLAGS)
x52d_mouse_test_LDADD = \
lib/pinelog/libpinelog.la \
@LTLIBINTL@
TESTS += x52d-mouse-test
endif
if HAVE_SYSTEMD
if !IS_MAKE_DISTCHECK
SED_ARGS = s,%bindir%,$(bindir),g
x52d.service: daemon/x52d.service.in
$(AM_V_GEN) $(SED) -e '$(SED_ARGS)' $< > $@
systemdsystemunit_DATA = x52d.service
endif
endif

47
daemon/daemon.dox 100644
View File

@ -0,0 +1,47 @@
/**
@page x52d X52 driver daemon
\b x52d is a daemon program that manages the X52 device in a similar fashion to
the Windows X52 driver. It currently manages the following:
- LED state
- LED brightness
- MFD brightness
- Clock display on MFD
# Command line arguments
- \c -f - Run daemon in foreground (default: no)
- \c -v - Increase logging verbosity (default: log warnings)
- \c -q - Reduce logging verbosity to minimum (default: no)
- \c -l - Path to log file
- \c -c - Path to configuration file
- \c -p - Path to PID file
- \c -o - Configuration override - only applied during startup
- \c -s - Path to command socket (see \ref x52d_protocol)
- \c -b - Path to notify socket
# Configuration file
\b x52d can be controlled by means of a configuration file. The default location
of the configuration file is in `$(SYSCONFDIR)/x52d/x52d.conf`. The configuration
file is an INI style file, and the default configuration is as listed below:
\include x52d.conf
## Configuration overrides
Configuration overrides are a means of testing a configuration parameter for a
single instance of \b x52d, or to override the default configuration. The syntax
for an override is \c section.key=value, where \c section, \c key and \c value
correspond to the configuration \b section, \b key and \b value respectively.
For example, to override the secondary clock timezone to US Eastern Time, use
the following syntax. Note that while the section and key are case-insensitive,
the value may be case-sensitive, depending on which parameter is being
overridden.
@code{.unparsed}
-o clock.secondary=America/New_York
@endcode
*/

308
daemon/protocol.dox 100644
View File

@ -0,0 +1,308 @@
/**
@page x52d_protocol X52 daemon socket communication protocol
The X52 daemon creates a Unix domain stream socket, by default at
`$(LOCALSTATEDIR)/run/x52d.cmd` and listens for connection requests from
clients at this location. This can be overridden by passing the -s flag when
starting the daemon.
# Protocol Overview
\b x52d requires that clients send it commands as a series of NUL terminated
strings, without any interleaving space. The command should be sent in a
single `send` call, and the client may expect a response in a single `recv`
call.
The `send` call must send exactly the number of bytes in the command text.
Extra bytes will be treated as additional arguments, which would cause the
command to fail. It is recommended that the `recv` call uses a 1024 byte buffer
to read the data. Responses will never exceed this length.
# Responses
The daemon sends the response as a series of NUL terminated strings, without
any interleaving space. The first string is always one of the following:
- \c OK
- \c ERR
- \c DATA
This determines whether the request was successful or not, and subsequent
strings describe the action, error or requested data.
# Examples
## Reloading configuration
- \b send <tt>config\0reload\0</tt>
- \b recv <tt>OK\0config\0reload\0</tt>
## Reading mouse speed
- \b send <tt>config\0get\0mouse\0speed\0</tt>
- \b recv <tt>DATA\0mouse\0speed\010\0</tt>
## Sending an invalid command
- \b send <tt>config reload</tt>
- \b recv <tt>ERR\0Unknown command 'config reload'\0</tt>
# Commands
\b x52d commands are arranged in a hierarchical fashion as follows:
```
<command-group> [<sub-command-group> [<sub-command-group> [...]]] <command> [<arguments>]
```
The list of supported commands are shown below:
- @subpage proto_config
- @subpage proto_logging
*/
/**
@page proto_config Configuration management
The \c config commands deal with \b x52d configuration subsystem, and have the
following subcommands.
@tableofcontents
# Load configuration
The `config load` subgroup allows you to load a configuration from a given file
(discarding anything that was already in memory), or reload the configuration
from the command-line specified configuration file (or default configuration
file if no option was given on the command line.)
## Load from file
\b Arguments
- `config`
- `load`
- \a path-to-file
\b Returns
- `OK`
- `config`
- `load`
- \a path-to-file
\b Error
- `ERR`
- <tt>Invalid file '/none' for 'config load' command</tt>
## Reload system configuration
\b Arguments
- `config`
- `reload`
\b Returns
- `OK`
- `config`
- `reload`
# Save configuration
The `config save` subgroup requests the \b x52d daemon to save the current state
of the configuration to disk. This is either the system configuration file, or
may be a user specified configuration file. Note that this will be created with
the permissions of the running daemon, which may be running as root.
## Dump configuration to file
\b Arguments
- `config`
- `dump`
- \a path-to-file
\b Returns
- `OK`
- `config`
- `dump`
- \a path-to-file
\b Error
- `ERR`
- <tt>Invalid file '/none' for 'config dump' command</tt>
## Save system configuration
\b Arguments
- `config`
- `save`
\b Returns
- `OK`
- `config`
- `save`
# Retrieve configuration parameter
The `config get` command requests a specific configuration value, given the
section and the key. Refer to \ref x52d for the section and key names, as these
are derived from the base configuration.
\b Arguments
- `config`
- `get`
- \a section
- \a key
\b Returns
- `DATA`
- \a section
- \a key
- \a value
\b Example
```
DATA\0mouse\0enabled\0true\0
```
<b>Error example</b>
```
ERR\0Error getting 'foo.bar'\0
```
# Set configuration parameter
The `config set` command requests the \b x52d daemon to set the given (section,
key) parameter to the given value. The daemon will treat it the same way as if
it was being read from the configuration file, i.e., it will follow identical
parsing logic, including ignoring unknown keys and not reporting errors for them.
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.
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
- `config`
- `set`
- \a section
- \a key
- \a value
\b Returns
- `OK`
- `config`
- `set`
- \a section
- \a key
- \a value
<b>Error example</b>
```
ERR\0Error 22 setting 'led.fire'='none': Invalid argument\0
```
*/
/**
@page proto_logging Logging management
The \c logging commands allow the user to fine tune the logging configuration
of \c x52d as well as adjust the log levels for either all the modules, or for
each of the modules individually.
While the `-v` and `-q` command line options allow you to either increase the
logging verbosity or suppress it entirely, they are required to be specified at
program startup. On the other hand, having the `logging` commands allows the
user to fine tune the logging while the daemon is running.
@tableofcontents
# Modules
\c x52d is split into several modules as far as logging is concerned. The list
of modules is below:
- \c Config
- \c Cllient
- \c Clock
- \c Command
- \c Device
- \c IO
- \c LED
- \c Mouse
- \c Notify
# Logging levels
The following is a list of supported logging levels. Each level logs the ones
above it as well as the current level
- \c none - Disable logging entirely
- \c fatal - Log fatal messages
- \c error - Log error messages
- \c warning - Log warning messages
- \c info - Log informational messages
- \c debug - Log debug messages
- \c trace - Log trace messages - useful for tracing program flow.
- \c default - Not a level, but used when configuring module log levels, makes
the module log level fallback to the global log level.
# Show logging configuration
The `logging show` command takes in an optional module name, as listed in the
Modules section above. It returns the module name, if specified, and the log
level for that module. If the module is configured to fallback to the global
level, then it will return the global level.
\b Arguments
- `logging`
- `show`
- \a module-name (Optional)
\b Returns
- `DATA`
- <tt>\a module-name</tt> (if specified)
- \a log-level
# Set logging configuration
The `logging set` command takes in the optional module name and the log level
and sets the log level for that module, if specified, or the global level
otherwise.
\b Arguments
- `logging`
- `set`
- \a module-name (Optional)
- \a log-level
\b Returns
- `OK`
- `logging`
- `set`
- <tt>\a module-name</tt> (if specified)
- \a log-level
*/

View File

@ -0,0 +1,203 @@
#!/usr/bin/env python3
"""Test communication with x52d and verify that the behavior is as expected"""
# pylint: disable=consider-using-f-string
import glob
import os
import os.path
import platform
import shlex
import signal
import subprocess
import tempfile
import time
import sys
class TestCase:
"""TestCase class handles an individual test case"""
def __init__(self, data):
"""Create a new test case"""
self.desc = None
self.in_cmd = None
self.exp_resp = None
self.parse(data)
def parse(self, data):
"""Parses a string of the following form:
<description>
space separated input line, possibly quoted
space separated expected response, possibly quoted
"""
self.desc, in_cmd, exp_resp = data.splitlines()
self.in_cmd = shlex.split(in_cmd)
self.exp_resp = '\0'.join(shlex.split(exp_resp)).encode() + b'\0'
def execute(self, index, suite):
"""Execute the test case and return the result in TAP format"""
def dump_failed(name, value):
"""Dump the failed test case"""
print("# {}".format(name))
for argv in value.decode().split('\0'):
print("#\t {}".format(argv))
print()
def print_result(passed):
"""Print the test case result and description"""
out = "ok {} - {}".format(index+1, self.desc)
if not passed:
out = "not " + out
print(out)
cmd = [suite.find_control_program(),
'-s', suite.command, '--',
*self.in_cmd]
testcase = subprocess.run(cmd, stdout=subprocess.PIPE, check=False)
if testcase.returncode != 0:
print_result(False)
print("# x52ctl returned code: {}".format(testcase.returncode))
dump_failed("Expected", self.exp_resp)
dump_failed("Got", testcase.stdout)
elif testcase.stdout != self.exp_resp:
print_result(False)
dump_failed("Expected", self.exp_resp)
dump_failed("Got", testcase.stdout)
else:
print_result(True)
class Test:
"""Test class runs a series of unit tests"""
def __init__(self):
"""Create a new instance of the Test class"""
self.program = self.find_daemon_program()
self.tmpdir = tempfile.TemporaryDirectory() # pylint: disable=consider-using-with
self.command = os.path.join(self.tmpdir.name, "x52d.cmd")
self.notify = os.path.join(self.tmpdir.name, "x52d.notify")
self.daemon = None
self.testcases = []
def __enter__(self):
"""Context manager entry"""
self.launch_daemon()
return self
def __exit__(self, *exc):
"""Context manager exit"""
self.terminate_daemon()
self.tmpdir.cleanup()
@staticmethod
def find_daemon_program():
"""Find the daemon program. This script should be run from the
root of the build directory"""
daemon_candidates = glob.glob('**/x52d', recursive=True)
if not daemon_candidates:
print("Bail out! Unable to find X52 daemon.")
sys.exit(1)
return os.path.realpath(daemon_candidates[0])
@staticmethod
def find_control_program():
"""Find the control program. This script should be run from the
root of the build directory"""
ctl_candidates = glob.glob('**/x52ctl', recursive=True)
if not ctl_candidates:
print("Bail out! Unable to find x52ctl.")
sys.exit(1)
return os.path.realpath(ctl_candidates[0])
def launch_daemon(self):
"""Launch an instance of the running daemon"""
if self.daemon is not None:
# We've already started the daemon, check if it is still running
if self.daemon.poll() is None:
return
self.daemon = None
daemon_cmdline = [
self.program,
"-f", # Run in foreground
"-q", # Quiet logging
"-c", os.path.join(self.tmpdir.name, "x52d.cfg"), # Default config file
"-l", os.path.join(self.tmpdir.name, "x52d.log"), # Output logs to log file
"-p", os.path.join(self.tmpdir.name, "x52d.pid"), # PID file
"-s", self.command, # Command socket path
"-b", self.notify, # Notification socket path
]
# Create empty config file
with open(daemon_cmdline[4], 'w', encoding='utf-8'):
pass
self.daemon = subprocess.Popen(daemon_cmdline) # pylint: disable=consider-using-with
print("# Sleeping 2 seconds for daemon to start")
time.sleep(2)
def terminate_daemon(self):
"""Terminate a running daemon"""
if self.daemon is None:
return
# Send a SIGTERM to the daemon
os.kill(self.daemon.pid, signal.SIGTERM)
try:
self.daemon.wait(timeout=15)
except subprocess.TimeoutExpired:
# Forcibly kill the running process
self.daemon.kill()
finally:
self.daemon = None
def append(self, testcase):
"""Add one testcase to the test case list"""
self.testcases.append(testcase)
def extend(self, testcases):
"""Add one or more testcases to the test case list"""
self.testcases.extend(testcases)
@staticmethod
def dump_failed(name, value):
"""Dump the failed test case"""
print("# {}".format(name))
for argv in value.decode().split('\0'):
print("#\t {}".format(argv))
print()
def run_tests(self):
"""Run test cases"""
print("1..{}".format(len(self.testcases)))
for index, testcase in enumerate(self.testcases):
testcase.execute(index, self)
def find_and_parse_testcase_files(self):
"""Find and parse *.tc files"""
basedir = os.path.dirname(os.path.realpath(__file__))
pattern = os.path.join(basedir, 'tests', '**', '*.tc')
tc_files = sorted(glob.glob(pattern, recursive=True))
for tc_file in tc_files:
with open(tc_file, encoding='utf-8') as tc_fd:
# Test cases are separated by blank lines
testcases = tc_fd.read().split('\n\n')
self.extend(TestCase(tc_data) for tc_data in testcases)
def main():
"""Main routine adds test cases to the Test class and runs them"""
# Only run the tests on Linux platform
if platform.system() != 'Linux':
print('1..0 # Skipping tests on', platform.system())
return
with Test() as test:
test.find_and_parse_testcase_files()
test.run_tests()
if __name__ == '__main__':
main()

View File

@ -0,0 +1,3 @@
Check for invalid command
foo
ERR "Unknown command 'foo'"

View File

@ -0,0 +1,83 @@
Configuration with insufficient arguments
config
ERR "Insufficient arguments for 'config' command"
Load with invalid file argument
config load ''
ERR "Invalid file '' for 'config load' command"
Load with nonexistent file argument
config load /nonexistent
ERR "Invalid file '/nonexistent' for 'config load' command"
Load with empty file argument
config load /dev/null
OK config load /dev/null
Load with extra arguments
config load /dev/null ''
ERR "Unexpected arguments for 'config load' command; got 4, expected 3"
Load with missing argument
config load
ERR "Unexpected arguments for 'config load' command; got 2, expected 3"
Reload configuration
config reload
OK config reload
Reload configuration with extra arguments
config reload ''
ERR "Unexpected arguments for 'config reload' command; got 3, expected 2"
Dump configuration with insufficient arguments
config dump
ERR "Unexpected arguments for 'config dump' command; got 2, expected 3"
Dump configuration with invalid file
config dump ''
ERR "Invalid file '' for 'config dump' command"
Dump configuration with extra arguments
config dump /dev/null ''
ERR "Unexpected arguments for 'config dump' command; got 4, expected 3"
Dump configuration to /dev/null
config dump /dev/null
OK config dump /dev/null
Save configuration
config save
OK config save
Save configuration with extra arguments
config save ''
ERR "Unexpected arguments for 'config save' command; got 3, expected 2"
Config command with empty subcommand
config ''
ERR "Unknown subcommand '' for 'config' command"
Config command with unknown subcommand
config foo
ERR "Unknown subcommand 'foo' for 'config' command"
Get configuration with fewer arguments
config get
ERR "Unexpected arguments for 'config get' command; got 2, expected 4"
Get configuration with extra arguments
config get foo bar baz
ERR "Unexpected arguments for 'config get' command; got 5, expected 4"
Set configuration with fewer arguments
config set
ERR "Unexpected arguments for 'config set' command; got 2, expected 5"
Set configuration with extra arguments
config set foo bar baz quux
ERR "Unexpected arguments for 'config set' command; got 6, expected 5"
Get configuration of unknown parameter
config get foo bar
ERR "Error getting 'foo.bar'"

View File

@ -0,0 +1,183 @@
Set clock enabled to false
config set clock enabled false
OK config set clock enabled false
Verify clock enabled was set to false
config get clock enabled
DATA clock enabled false
Set clock enabled to true
config set clock enabled true
OK config set clock enabled true
Verify clock enabled was set to true
config get clock enabled
DATA clock enabled true
Set clock PrimaryIsLocal to no
config set clock PrimaryIsLocal no
OK config set clock PrimaryIsLocal no
Verify clock PrimaryIsLocal was set to false (no)
config get clock PrimaryIsLocal
DATA clock PrimaryIsLocal false
Set clock PrimaryIsLocal to yes
config set clock PrimaryIsLocal yes
OK config set clock PrimaryIsLocal yes
Verify clock PrimaryIsLocal was set to true (yes)
config get clock PrimaryIsLocal
DATA clock PrimaryIsLocal true
Set clock PrimaryIsLocal to invalid value
config set clock PrimaryIsLocal foo
ERR "Error 22 setting 'clock.PrimaryIsLocal'='foo': Invalid argument"
Verify clock PrimaryIsLocal was not changed
config get clock PrimaryIsLocal
DATA clock PrimaryIsLocal true
Set clock secondary to America/Los_Angeles
config set clock secondary America/Los_Angeles
OK config set clock secondary America/Los_Angeles
Verify clock secondary was set to America/Los_Angeles
config get clock secondary
DATA clock secondary America/Los_Angeles
Set clock secondary to UTC
config set clock secondary UTC
OK config set clock secondary UTC
Verify clock secondary was set to UTC
config get clock secondary
DATA clock secondary UTC
Set clock tertiary to America/Los_Angeles
config set clock tertiary America/Los_Angeles
OK config set clock tertiary America/Los_Angeles
Verify clock tertiary was set to America/Los_Angeles
config get clock tertiary
DATA clock tertiary America/Los_Angeles
Set clock tertiary to UTC
config set clock tertiary UTC
OK config set clock tertiary UTC
Verify clock tertiary was set to UTC
config get clock tertiary
DATA clock tertiary UTC
Set clock formatprimary to 24hr
config set clock formatprimary 24hr
OK config set clock formatprimary 24hr
Verify clock formatprimary was set to 24hr
config get clock formatprimary
DATA clock formatprimary '24 hour'
Set clock formatprimary to 12hr
config set clock formatprimary 12hr
OK config set clock formatprimary 12hr
Verify clock formatprimary was set to 12hr
config get clock formatprimary
DATA clock formatprimary '12 hour'
Set clock formatsecondary to 24hr
config set clock formatsecondary 24hr
OK config set clock formatsecondary 24hr
Verify clock formatsecondary was set to 24hr
config get clock formatsecondary
DATA clock formatsecondary '24 hour'
Set clock formatsecondary to 12hr
config set clock formatsecondary 12hr
OK config set clock formatsecondary 12hr
Verify clock formatsecondary was set to 12hr
config get clock formatsecondary
DATA clock formatsecondary '12 hour'
Set clock formattertiary to 24
config set clock formattertiary 24
OK config set clock formattertiary 24
Verify clock formattertiary was set to 24
config get clock formattertiary
DATA clock formattertiary '24 hour'
Set clock formattertiary to 12
config set clock formattertiary 12
OK config set clock formattertiary 12
Verify clock formattertiary was set to 12
config get clock formattertiary
DATA clock formattertiary '12 hour'
Set clock formattertiary to invalid value
config set clock formattertiary '12 hour'
ERR "Error 22 setting 'clock.formattertiary'='12 hour': Invalid argument"
Verify clock formattertiary was not changed
config get clock formattertiary
DATA clock formattertiary '12 hour'
Set clock dateformat to yymmdd
config set clock dateformat yymmdd
OK config set clock dateformat yymmdd
Verify clock dateformat was set to yymmdd
config get clock dateformat
DATA clock dateformat YY-MM-DD
Set clock dateformat to mmddyy
config set clock dateformat mmddyy
OK config set clock dateformat mmddyy
Verify clock dateformat was set to mmddyy
config get clock dateformat
DATA clock dateformat MM-DD-YY
Set clock dateformat to ddmmyy
config set clock dateformat ddmmyy
OK config set clock dateformat ddmmyy
Verify clock dateformat was set to ddmmyy
config get clock dateformat
DATA clock dateformat DD-MM-YY
Set clock dateformat to yy-mm-dd
config set clock dateformat yy-mm-dd
OK config set clock dateformat yy-mm-dd
Verify clock dateformat was set to yy-mm-dd
config get clock dateformat
DATA clock dateformat YY-MM-DD
Set clock dateformat to mm-dd-yy
config set clock dateformat mm-dd-yy
OK config set clock dateformat mm-dd-yy
Verify clock dateformat was set to mm-dd-yy
config get clock dateformat
DATA clock dateformat MM-DD-YY
Set clock dateformat to dd-mm-yy
config set clock dateformat dd-mm-yy
OK config set clock dateformat dd-mm-yy
Verify clock dateformat was set to dd-mm-yy
config get clock dateformat
DATA clock dateformat DD-MM-YY
Set clock dateformat to invalid value
config set clock dateformat foo-bar-baz
ERR "Error 22 setting 'clock.dateformat'='foo-bar-baz': Invalid argument"
Verify clock dateformat was not changed
config get clock dateformat
DATA clock dateformat DD-MM-YY

View File

@ -0,0 +1,47 @@
Set LED Fire to off
config set led fire off
OK config set led fire off
Verify LED Fire was set to off
config get led fire
DATA led fire off
Set LED Fire to on
config set led fire on
OK config set led fire on
Verify LED Fire was set to on
config get led fire
DATA led fire on
Set LED Fire to red TODO: This passes since the LED parser doesn't know the LED name
config set led fire red
OK config set led fire red
Verify LED Fire was set to red
config get led fire
DATA led fire red
Set LED Fire to amber TODO: This passes since the LED parser doesn't know the LED name
config set led fire amber
OK config set led fire amber
Verify LED Fire was set to amber
config get led fire
DATA led fire amber
Set LED Fire to green TODO: This passes since the LED parser doesn't know the LED name
config set led fire green
OK config set led fire green
Verify LED Fire was set to green
config get led fire
DATA led fire green
Set LED Fire to invalid value
config set led fire foo
ERR "Error 22 setting 'led.fire'='foo': Invalid argument"
Verify LED Fire was not changed
config get led fire
DATA led fire green

View File

@ -0,0 +1,47 @@
Set mouse speed to 32
config set mouse speed 32
OK config set mouse speed 32
Verify mouse speed is set to 32
config get mouse speed
DATA mouse speed 32
Set mouse speed to invalid value
config set mouse speed off
ERR "Error 22 setting 'mouse.speed'='off': Invalid argument"
Verify mouse speed is unchanged
config get mouse speed
DATA mouse speed 32
Set mouse speed to 33 (Exceeds max speed)
config set mouse speed 33
OK config set mouse speed 33
Set mouse speed to 20 (In multiplier range)
config set mouse speed 20
OK config set mouse speed 20
Set mouse speed to negative value
config set mouse speed -1
OK config set mouse speed -1
Reset mouse speed to minimum
config set mouse speed 0
OK config set mouse speed 0
Set mouse reverse scroll to enabled
config set mouse reversescroll true
OK config set mouse reversescroll true
Check if reverse scrolling is enabled
config get mouse reversescroll
DATA mouse reversescroll true
Set mouse reverse scroll to disabled
config set mouse reversescroll false
OK config set mouse reversescroll false
Check if reverse scrolling is disabled
config get mouse reversescroll
DATA mouse reversescroll false

View File

@ -0,0 +1,15 @@
Logging with insufficient arguments
logging
ERR "Insufficient arguments for 'logging' command"
Get logging level with extra arguments
logging show foo bar
ERR "Unexpected arguments for 'logging show' command; got 4, expected 2 or 3"
Set logging level with insufficient arguments
logging set
ERR "Unexpected arguments for 'logging set' command; got 2, expected 3 or 4"
Invalid logging subcommand
logging foo
ERR "Unknown subcommand 'foo' for 'logging' command"

View File

@ -0,0 +1,15 @@
Set global logging level to error
logging set error
OK logging set error
Get global logging level (should be error)
logging show
DATA global error
Set global logging level to default - should return error
logging set default
ERR "'default' level is not valid without a module"
Set global logging level to unknown value - should return error
logging set foo
ERR "Unknown level 'foo' for 'logging set' command"

View File

@ -0,0 +1,27 @@
Set config module logging level to error
logging set config error
OK logging set config error
Get config module logging level
logging show config
DATA config error
Get configuration for invalid module
logging show foo
ERR "Invalid module 'foo'"
Set module logging level for invalid module
logging set foo error
ERR "Invalid module 'foo'"
Set invalid logging level for module
logging set config foo
ERR "Unknown level 'foo' for 'logging set' command"
Set module logging level to default
logging set config default
OK logging set config default
Get module logging level (should be error - same as global)
logging show config
DATA config error

188
daemon/x52ctl.c 100644
View File

@ -0,0 +1,188 @@
/*
* Saitek X52 Pro MFD & LED driver - Daemon controller
*
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
/**
@page x52ctl Command Line controller to X52 daemon
\htmlonly
<b>x52ctl</b> - Command line controller to X52 daemon
\endhtmlonly
# SYNOPSIS
<tt>\b x52ctl [\a -i] [\a -s socket-path] [command] </tt>
# DESCRIPTION
x52ctl is a program that can be used to communicate with the X52 daemon. It can
be used either as a one-shot program that can be run from another program or
script, or it can be run interactively.
Commands are sent to the running daemon, and responses are written to standard
output.
If not running interactively, then you must specify a command, or the program
will exit with a failure exit code. If running interactively, the program will
request input and send that to the daemon, until the user either enters the
string "quit", or terminates input by using Ctrl+D.
# OPTIONS
- <tt>\b -i</tt>
Run in interactive mode. Any additional non-option arguments are ignored.
- <tt>\b -s < \a socket-path ></tt>
Use the socket at the given path. If this is not specified, then it uses a
default socket.
*/
#include "config.h"
#include <ctype.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include "x52d_const.h"
#include "x52dcomm.h"
#define APP_NAME "x52ctl"
#if HAVE_FUNC_ATTRIBUTE_NORETURN
__attribute__((noreturn))
#endif
static void usage(int exit_code)
{
fprintf(stderr, _("Usage: %s [-i] [-s socket-path] [command]\n"), APP_NAME);
exit(exit_code);
}
static int send_command(int sock_fd, int argc, char **argv)
{
int rc;
char buffer[1024];
int buflen;
buflen = x52d_format_command(argc, (const char **)argv, buffer, sizeof(buffer));
if (buflen < 0) {
if (errno == E2BIG) {
fprintf(stderr, _("Argument length too long\n"));
}
return -1;
}
rc = x52d_send_command(sock_fd, buffer, buflen, sizeof(buffer));
if (rc >= 0) {
if (write(STDOUT_FILENO, buffer, rc) < 0) {
perror("write");
return -1;
}
} else {
perror("x52d_send_command");
return -1;
}
return 0;
}
int main(int argc, char **argv)
{
bool interactive = false;
char *socket_path = NULL;
int opt;
int sock_fd;
int rc = EXIT_SUCCESS;
char buffer[1024];
/*
* Parse command line arguments
*
* -i Interactive
* -s Socket path
*/
while ((opt = getopt(argc, argv, "is:h")) != -1) {
switch (opt) {
case 'i':
interactive = true;
break;
case 's':
socket_path = optarg;
break;
case 'h':
usage(EXIT_SUCCESS);
break;
default:
usage(EXIT_FAILURE);
break;
}
}
if (!interactive && optind >= argc) {
usage(EXIT_FAILURE);
}
/* Connect to the socket */
sock_fd = x52d_dial_command(socket_path);
if (sock_fd < 0) {
perror("x52d_dial_command");
return EXIT_FAILURE;
}
if (interactive) {
if (optind < argc) {
fprintf(stderr,
_("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);
}
} else {
if (send_command(sock_fd, argc - optind, &argv[optind])) {
rc = EXIT_FAILURE;
goto cleanup;
}
}
cleanup:
close(sock_fd);
return rc;
}

114
daemon/x52d.conf 100644
View File

@ -0,0 +1,114 @@
#######################################################################
# X52 Daemon Configuration
######################################################################
# The settings below are the defaults. Note that the section and key
# strings are case insensitive, but the values are not necessarily so,
# especially for those referring to paths or timezone names.
######################################################################
# Clock Settings
######################################################################
[Clock]
# Enabled controls whether the clock is enabled or not. Set this to no to
# disable the clock update. Keep in mind that if the clock was originally
# enabled on the X52, then disabling it here won't make the clock disappear on
# the MFD. You will need to unplug and reattach the X52 to make the clock
# disappear
Enabled=yes
# PrimaryIsLocal controls whether the primary clock displays local time or UTC.
# Set this to yes to display local time, no for UTC.
PrimaryIsLocal=yes
# Secondary controls the timezone of the secondary clock. Use the standard
# timezone name as defined by the Olson time database.
Secondary=UTC
# Tertiary controls the timezone of the tertiary clock. Use the standard
# timezone name as defined by the Olson time database.
Tertiary=UTC
# PrimaryFormat controls the clock format of the primary clock. This is
# either 12hr or 24hr, and can be abbreviated to 12 or 24
FormatPrimary=12hr
# SecondaryFormat controls the clock format of the secondary clock. This is
# either 12hr or 24hr, and can be abbreviated to 12 or 24
FormatSecondary=12hr
# TertiaryFormat controls the clock format of the tertiary clock. This is
# either 12hr or 24hr, and can be abbreviated to 12 or 24
FormatTertiary=12hr
# DateFormat controls the format of the date display. This can be one of
# ddmmyy, mmddyy or yymmdd. Alternate representations of these are
# dd-mm-yy, mm-dd-yy or yy-mm-dd respectively.
DateFormat=ddmmyy
######################################################################
# LED Settings - only applicable to X52Pro
######################################################################
[LED]
# The LED settings map a color code or state to the corresponding LED.
Fire=on
Throttle=on
A=green
B=green
D=green
E=green
T1=green
T2=green
T3=green
POV=green
Clutch=green
######################################################################
# Brightness Settings
######################################################################
[Brightness]
# The brightness settings map the brightness value to the LEDs/MFD.
MFD=128
LED=128
######################################################################
# Mouse - only valid on Linux
######################################################################
[Mouse]
# Enabled controls whether the virtual mouse is enabled or not.
Enabled=yes
# Speed is proportional to the speed of updates to the virtual mouse
Speed=0
# ReverseScroll reverses the direction of the virtual scroll wheel
ReverseScroll=no
######################################################################
# Profiles - only valid on Linux
######################################################################
[Profiles]
# TODO: Profiles are used to map the buttons and axis to keyboard events, and
# can be used to write macros. This is a placeholder only for now, and is not
# supported yet.
# Directory is the location of the folder containing the individual profiles.
Directory=/etc/x52d/profiles.d
# ClutchEnabled determines if the clutch button is treated specially
ClutchEnabled=no
# ClutchLatched controls if the clutch button (if enabled) is a latched button
# (press once to enter clutch mode, press again to exit clutch mode), or must
# be held down to remain in clutch mode.
ClutchLatched=no
##################
#X52 Input Servic#
#Version 0.2.2 #
#OS: Linux #
##################

View File

@ -0,0 +1,10 @@
[Unit]
Description=X52 driver daemon
[Service]
Type=simple
ExecStart=%bindir%/x52d -f -v
ExecReload=kill -HUP $MAINPID
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,146 @@
/*
* Saitek X52 Pro MFD & LED driver - Client handling
*
* Copyright (C) 2022 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include "pinelog.h"
#include "x52d_client.h"
#include "x52dcomm-internal.h"
void x52d_client_init(int client_fd[X52D_MAX_CLIENTS])
{
for (int i = 0; i < X52D_MAX_CLIENTS; i++) {
client_fd[i] = INVALID_CLIENT;
}
}
bool x52d_client_register(int client_fd[X52D_MAX_CLIENTS], int sock_fd)
{
int fd;
int i;
fd = accept(sock_fd, NULL, NULL);
if (fd < 0) {
PINELOG_ERROR(_("Error accepting client connection on socket fd %d: %s"),
sock_fd, strerror(errno));
return false;
}
if (x52d_set_socket_nonblocking(fd) < 0) {
PINELOG_ERROR(_("Error marking client fd %d as nonblocking: %s"),
fd, strerror(errno));
goto error;
}
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);
client_fd[i] = fd;
return true;
}
}
/*
* At this point, we've looped through the entirity of client_fd, but
* have not registered an empty slot. We need to close the socket and
* tell the caller that we haven't been able to register the client.
*/
PINELOG_TRACE("Maximum connections reached, closing socket %d", fd);
error:
close(fd);
return false;
}
bool x52d_client_deregister(int client_fd[X52D_MAX_CLIENTS], int fd)
{
bool deregistered = false;
for (int i = 0; i < X52D_MAX_CLIENTS; i++) {
if (client_fd[i] == fd) {
client_fd[i] = INVALID_CLIENT;
deregistered = true;
close(fd);
PINELOG_TRACE("Disconnected client %d from socket", fd);
break;
}
}
return deregistered;
}
bool x52d_client_error(int client_fd[X52D_MAX_CLIENTS], int fd)
{
int error;
socklen_t errlen = sizeof(error);
getsockopt(fd, SOL_SOCKET, SO_ERROR, (void *)&error, &errlen);
PINELOG_ERROR(_("Error when polling socket: FD %d, error %d, len %lu"),
fd, error, (unsigned long int)errlen);
return x52d_client_deregister(client_fd, fd);
}
int x52d_client_poll(int client_fd[X52D_MAX_CLIENTS], struct pollfd pfd[MAX_CONN], int listen_fd)
{
int pfd_count;
int rc;
memset(pfd, 0, sizeof(*pfd) * MAX_CONN);
pfd_count = 1;
pfd[0].fd = listen_fd;
pfd[0].events = POLLIN | POLLERR;
for (int i = 0; i < X52D_MAX_CLIENTS; i++) {
if (client_fd[i] != INVALID_CLIENT) {
pfd[pfd_count].fd = client_fd[i];
pfd[pfd_count].events = POLLIN | POLLERR | POLLHUP;
pfd_count++;
}
}
PINELOG_TRACE("Polling %d file descriptors", pfd_count);
retry_poll:
rc = poll(pfd, pfd_count, -1);
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) {
PINELOG_INFO(_("Timed out when polling"));
}
return rc;
}
void x52d_client_handle(int client_fd[X52D_MAX_CLIENTS], struct pollfd *pfd, int listen_fd, x52d_poll_handler handler)
{
for (int i = 0; i < MAX_CONN; i++) {
if (pfd[i].revents & POLLHUP) {
/* Remote hungup */
x52d_client_deregister(client_fd, pfd[i].fd);
} else if (pfd[i].revents & POLLERR) {
/* Error reading from the socket */
x52d_client_error(client_fd, pfd[i].fd);
} else if (pfd[i].revents & POLLIN) {
if (pfd[i].fd == listen_fd) {
x52d_client_register(client_fd, listen_fd);
} else {
if (handler != NULL) {
handler(pfd[i].fd);
}
}
}
}
}

View File

@ -0,0 +1,30 @@
/*
* Saitek X52 Pro MFD & LED driver - Client handling
*
* Copyright (C) 2022 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
#ifndef X52D_CLIENT_H
#define X52D_CLIENT_H
#include <stdbool.h>
#include <poll.h>
#include "x52d_const.h"
#define MAX_CONN (X52D_MAX_CLIENTS + 1)
#define INVALID_CLIENT -1
typedef void (*x52d_poll_handler)(int);
void x52d_client_init(int client_fd[X52D_MAX_CLIENTS]);
bool x52d_client_register(int client_fd[X52D_MAX_CLIENTS], int sock_fd);
bool x52d_client_deregister(int client_fd[X52D_MAX_CLIENTS], int fd);
bool x52d_client_error(int client_fd[X52D_MAX_CLIENTS], int fd);
int x52d_client_poll(int client_fd[X52D_MAX_CLIENTS], struct pollfd pfd[MAX_CONN], int listen_fd);
void x52d_client_handle(int client_fd[X52D_MAX_CLIENTS], struct pollfd *pfd, int listen_fd, x52d_poll_handler handler);
#endif //!defined X52D_CLIENT_H

211
daemon/x52d_clock.c 100644
View File

@ -0,0 +1,211 @@
/*
* Saitek X52 Pro MFD & LED driver - Clock manager
*
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
#include "config.h"
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>
#include <string.h>
#define PINELOG_MODULE X52D_MOD_CLOCK
#include "pinelog.h"
#include "x52d_config.h"
#include "x52d_clock.h"
#include "x52d_const.h"
#include "x52d_device.h"
static bool clock_enabled = false;
static int clock_primary_is_local = false;
void x52d_cfg_set_Clock_Enabled(bool enabled)
{
PINELOG_DEBUG(_("Setting clock enable to %s"),
enabled ? _("on") : _("off"));
clock_enabled = enabled;
}
void x52d_cfg_set_Clock_PrimaryIsLocal(bool param)
{
PINELOG_DEBUG(_("Setting %s clock timezone to %s"),
libx52_clock_id_to_str(LIBX52_CLOCK_1),
param ? _("local") : _("UTC"));
clock_primary_is_local = !!param;
}
static int get_tz_offset(const char *tz)
{
char *orig_tz = NULL;
char *orig_tz_copy = NULL;
time_t t;
struct tm tmp;
struct tm *timeval;
char *new_tz = NULL;
size_t new_tz_len;
int offset = 0;
new_tz_len = strlen(tz) + 2;
new_tz = malloc(new_tz_len);
if (new_tz == NULL) {
PINELOG_WARN(_("Unable to allocate memory for timezone. Falling back to UTC"));
goto cleanup;
}
snprintf(new_tz, new_tz_len, ":%s", tz);
orig_tz = getenv("TZ");
if (orig_tz != NULL) {
/* TZ was set in the environment */
orig_tz_copy = strdup(orig_tz);
if (orig_tz_copy == NULL) {
PINELOG_WARN(_("Unable to backup timezone environment. Falling back to UTC"));
goto cleanup;
}
}
setenv("TZ", new_tz, true);
t = time(NULL);
tzset();
timeval = localtime_r(&t, &tmp);
if (timeval != NULL) {
#if HAVE_STRUCT_TM_TM_GMTOFF
/* If valid, then timeval.tm_gmtoff contains the offset in seconds east
* of GMT. Divide by 60 to get the offset in minutes east of GMT.
*/
offset = (int)(timeval->tm_gmtoff / 60);
#else
/* The compiler does not provide tm_gmtoff. Fallback to using the
* timezone variable, which is in seconds west of GMT. Divide by -60 to
* get the offset in minutes east of GMT.
*
* ============
* XXX NOTE XXX
* ============
* timezone is always the default (non-summer) timezone offset from GMT.
* Therefore, this may not be accurate during the summer time months
* for the region in question.
*/
offset = (int)(timezone / -60);
#endif
}
cleanup:
if (orig_tz == NULL) {
unsetenv("TZ");
} else {
setenv("TZ", orig_tz_copy, true);
free(orig_tz_copy);
}
if (new_tz != NULL) {
free(new_tz);
}
tzset();
PINELOG_TRACE("Offset for timezone '%s' is %d", tz, offset);
return offset;
}
static void set_clock_offset(libx52_clock_id id, const char *param)
{
if (clock_enabled) {
PINELOG_DEBUG(_("Setting %s clock timezone to %s"),
libx52_clock_id_to_str(id), param);
x52d_dev_set_clock_timezone(id, get_tz_offset(param));
}
}
void x52d_cfg_set_Clock_Secondary(char* param)
{
set_clock_offset(LIBX52_CLOCK_2, param);
}
void x52d_cfg_set_Clock_Tertiary(char* param)
{
set_clock_offset(LIBX52_CLOCK_3, param);
}
static void set_clock_format(libx52_clock_id id, libx52_clock_format fmt)
{
if (clock_enabled) {
PINELOG_DEBUG(_("Setting %s clock format to %s"),
libx52_clock_id_to_str(id), libx52_clock_format_to_str(fmt));
x52d_dev_set_clock_format(id, fmt);
}
}
void x52d_cfg_set_Clock_FormatPrimary(libx52_clock_format fmt)
{
set_clock_format(LIBX52_CLOCK_1, fmt);
}
void x52d_cfg_set_Clock_FormatSecondary(libx52_clock_format fmt)
{
set_clock_format(LIBX52_CLOCK_2, fmt);
}
void x52d_cfg_set_Clock_FormatTertiary(libx52_clock_format fmt)
{
set_clock_format(LIBX52_CLOCK_3, fmt);
}
void x52d_cfg_set_Clock_DateFormat(libx52_date_format fmt)
{
if (clock_enabled) {
PINELOG_DEBUG(_("Setting date format to %s"), libx52_date_format_to_str(fmt));
x52d_dev_set_date_format(fmt);
}
}
static pthread_t clock_thr;
static void * x52_clock_thr(void *param)
{
int rc;
PINELOG_INFO(_("Starting X52 clock manager thread"));
for (;;) {
time_t cur_time;
sleep(1);
if (!clock_enabled) {
/* Clock thread is disabled, check again next time */
continue;
}
if (time(&cur_time) < 0) {
PINELOG_WARN(_("Error %d retrieving current time: %s"),
errno, strerror(errno));
continue;
}
rc = x52d_dev_set_clock(cur_time, clock_primary_is_local);
if (rc == LIBX52_SUCCESS) {
// Device manager will update the clock, this is only for debugging
PINELOG_TRACE("Setting X52 clock to %ld", cur_time);
}
}
return NULL;
}
void x52d_clock_init(void)
{
int rc;
PINELOG_TRACE("Initializing clock manager");
rc = pthread_create(&clock_thr, NULL, x52_clock_thr, NULL);
if (rc != 0) {
PINELOG_FATAL(_("Error %d initializing clock thread: %s"),
rc, strerror(rc));
}
}
void x52d_clock_exit(void)
{
PINELOG_INFO(_("Shutting down X52 clock manager thread"));
pthread_cancel(clock_thr);
}

View File

@ -0,0 +1,15 @@
/*
* Saitek X52 Pro MFD & LED driver - Clock manager
*
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
#ifndef X52D_CLOCK_H
#define X52D_CLOCK_H
void x52d_clock_init(void);
void x52d_clock_exit(void);
#endif // !defined X52D_CLOCK_H

View File

@ -0,0 +1,177 @@
/*
* Saitek X52 Pro MFD & LED driver - Client communication library
*
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
#include "config.h"
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include "x52dcomm.h"
#include "x52dcomm-internal.h"
static int _setup_socket(struct sockaddr_un *remote, int len)
{
int sock;
int saved_errno;
/* Create a socket */
sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock == -1) {
/* Failure creating the socket, abort early */
return -1;
}
/* Connect to the socket */
if (connect(sock, (struct sockaddr *)remote, (socklen_t)len) == -1) {
/* Failure connecting to the socket. Cleanup */
saved_errno = errno;
/* close may modify errno, so we save it prior to the call */
close(sock);
sock = -1;
errno = saved_errno;
}
return sock;
}
int x52d_dial_command(const char *sock_path)
{
int len;
struct sockaddr_un remote;
len = x52d_setup_command_sock(sock_path, &remote);
if (len < 0) {
/* Error when setting up sockaddr */
return -1;
}
return _setup_socket(&remote, len);
}
int x52d_dial_notify(const char *sock_path)
{
int len;
struct sockaddr_un remote;
len = x52d_setup_notify_sock(sock_path, &remote);
if (len < 0) {
/* Error when setting up sockaddr */
return -1;
}
return _setup_socket(&remote, len);
}
int x52d_format_command(int argc, const char **argv, char *buffer, size_t buflen)
{
int msglen;
int i;
if (argc == 0 || argv == NULL || buffer == NULL || buflen < X52D_BUFSZ) {
errno = EINVAL;
return -1;
}
memset(buffer, 0, buflen);
msglen = 0;
for (i = 0; i < argc; i++) {
int arglen = strlen(argv[i]) + 1;
if ((size_t)(msglen + arglen) >= buflen) {
errno = E2BIG;
return -1;
}
memcpy(&buffer[msglen], argv[i], arglen);
msglen += arglen;
}
return msglen;
}
int x52d_send_command(int sock_fd, char *buffer, size_t bufin, size_t bufout)
{
int rc;
for (;;) {
/*
* Unix sockets should have sufficient capacity to send the full
* datagram in a single message. Assume that is the case.
*/
rc = send(sock_fd, buffer, bufin, 0);
if (rc < 0) {
// Error
if (errno == EINTR) {
// System call interrupted due to signal. Try again
continue;
} else {
// Failed. Return early
return -1;
}
}
break;
}
/* Wait till we get a response */
for (;;) {
rc = recv(sock_fd, buffer, bufout, 0);
if (rc < 0) {
// Error
if (errno == EINTR) {
// System call interrupted due to signal. Try again
continue;
} else {
// Failed. Return early
return -1;
}
}
break;
}
return rc;
}
int x52d_recv_notification(int sock_fd, x52d_notify_callback_fn callback)
{
int rc;
char buffer[X52D_BUFSZ];
int argc;
char *argv[X52D_BUFSZ];
if (callback == NULL) {
errno = EINVAL;
return -1;
}
/* Wait till we get a response */
for (;;) {
rc = recv(sock_fd, buffer, sizeof(buffer), 0);
if (rc < 0) {
// Error
if (errno == EINTR) {
// System call interrupted due to signal. Try again
continue;
} else {
// Failed. Return early
return -1;
}
}
break;
}
/* Split into individual arguments */
x52d_split_args(&argc, argv, buffer, rc);
return callback(argc, argv);
}

View File

@ -0,0 +1,127 @@
/*
* Saitek X52 Pro MFD & LED driver - Client communication library
*
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
#include "config.h"
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include "x52dcomm-internal.h"
#include "x52d_const.h"
const char * x52d_command_sock_path(const char *sock_path)
{
if (sock_path == NULL) {
sock_path = X52D_SOCK_COMMAND;
}
return sock_path;
}
const char * x52d_notify_sock_path(const char *sock_path)
{
if (sock_path == NULL) {
sock_path = X52D_SOCK_NOTIFY;
}
return sock_path;
}
static int _setup_sockaddr(struct sockaddr_un *remote, const char *sock_path)
{
int len;
if (remote == NULL) {
errno = EINVAL;
return -1;
}
len = strlen(sock_path);
if ((size_t)len >= sizeof(remote->sun_path)) {
/* Socket path will not fit inside sun_path */
errno = E2BIG;
return -1;
}
/* Setup the sockaddr structure */
memset(remote, 0, sizeof(*remote));
remote->sun_family = AF_UNIX;
/* We've already verified that sock_path will fit, so we don't need strncpy */
strcpy(remote->sun_path, sock_path);
len += sizeof(remote->sun_family);
return len;
}
int x52d_setup_command_sock(const char *sock_path, struct sockaddr_un *remote)
{
return _setup_sockaddr(remote, x52d_command_sock_path(sock_path));
}
int x52d_setup_notify_sock(const char *sock_path, struct sockaddr_un *remote)
{
return _setup_sockaddr(remote, x52d_notify_sock_path(sock_path));
}
int x52d_set_socket_nonblocking(int sock_fd)
{
int flags;
/* Mark the socket as non-blocking */
flags = fcntl(sock_fd, F_GETFL);
if (flags < 0) {
goto sock_failure;
}
if (fcntl(sock_fd, F_SETFL, flags | O_NONBLOCK) < 0) {
goto sock_failure;
}
return 0;
sock_failure:
close(sock_fd);
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;
while (i < buflen) {
if (buffer[i]) {
argv[*argc] = buffer + i;
(*argc)++;
for (; i < buflen && buffer[i]; i++);
// At this point, buffer[i] = '\0'
// Skip to the next character.
i++;
} else {
// We should never reach here, unless we have two NULs in a row
argv[*argc] = buffer + i;
(*argc)++;
i++;
}
}
}

View File

@ -0,0 +1,480 @@
/*
* Saitek X52 Pro MFD & LED driver - Command processor
*
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
#include "config.h"
#include <stdbool.h>
#include <string.h>
#include <stdarg.h>
#include <errno.h>
#include <pthread.h>
#include <poll.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <fcntl.h>
#define PINELOG_MODULE X52D_MOD_COMMAND
#include "pinelog.h"
#include "x52d_const.h"
#include "x52d_command.h"
#include "x52d_config.h"
#include "x52d_client.h"
#include "x52dcomm-internal.h"
static int client_fd[X52D_MAX_CLIENTS];
static pthread_t command_thr;
static int command_sock_fd;
static const char *command_sock;
#if defined __has_attribute
# if __has_attribute(format)
__attribute((format(printf, 4, 5)))
# endif
#endif
static void response_formatted(char *buffer, int *buflen, const char *type,
const char *fmt, ...)
{
va_list ap;
char response[X52D_BUFSZ];
int resplen;
int typelen;
typelen = strlen(type) + 1;
strcpy(response, type);
resplen = typelen;
if (*fmt) {
va_start(ap, fmt);
resplen += vsnprintf(response + typelen, sizeof(response) - typelen, fmt, ap) + 1;
va_end(ap);
}
memcpy(buffer, response, resplen);
*buflen = resplen;
}
static void response_strings(char *buffer, int *buflen, const char *type, int count, ...)
{
va_list ap;
char response[X52D_BUFSZ];
int resplen;
int arglen;
int i;
char *arg;
arglen = strlen(type) + 1;
strcpy(response, type);
resplen = arglen;
va_start(ap, count);
for (i = 0; i < count; i++) {
arg = va_arg(ap, char *);
arglen = strlen(arg) + 1;
if ((size_t)(arglen + resplen) >= sizeof(response)) {
PINELOG_ERROR("Too many arguments for response_strings %s", type);
break;
}
strcpy(response + resplen, arg);
resplen += arglen;
}
va_end(ap);
memcpy(buffer, response, resplen);
*buflen = resplen;
}
#define NUMARGS(...) (sizeof((const char *[]){__VA_ARGS__}) / sizeof(const char *))
#define ERR(...) response_strings(buffer, buflen, "ERR", NUMARGS(__VA_ARGS__), ##__VA_ARGS__)
#define ERR_fmt(fmt, ...) response_formatted(buffer, buflen, "ERR", fmt, ##__VA_ARGS__)
#define OK(...) response_strings(buffer, buflen, "OK", NUMARGS(__VA_ARGS__), ##__VA_ARGS__)
#define OK_fmt(fmt, ...) response_formatted(buffer, buflen, "OK", fmt, ##__VA_ARGS__)
#define DATA(...) response_strings(buffer, buflen, "DATA", NUMARGS(__VA_ARGS__), ##__VA_ARGS__)
#define MATCH(idx, cmd) if (strcasecmp(argv[idx], cmd) == 0)
static bool check_file(const char *file_path, int mode)
{
if (*file_path == '\0') {
return false;
}
if (mode && access(file_path, mode)) {
return false;
}
return true;
}
static void cmd_config(char *buffer, int *buflen, int argc, char **argv)
{
if (argc < 2) {
ERR("Insufficient arguments for 'config' command");
return;
}
MATCH(1, "load") {
if (argc == 3) {
if (!check_file(argv[2], R_OK)) {
ERR_fmt("Invalid file '%s' for 'config load' command", argv[2]);
return;
}
x52d_config_load(argv[2]);
x52d_config_apply();
OK("config", "load", argv[2]);
} else {
// Invalid number of args
ERR_fmt("Unexpected arguments for 'config load' command; got %d, expected 3", argc);
}
return;
}
MATCH(1, "reload") {
if (argc == 2) {
raise(SIGHUP);
OK("config", "reload");
} else {
ERR_fmt("Unexpected arguments for 'config reload' command; got %d, expected 2", argc);
}
return;
}
MATCH(1, "dump") {
if (argc == 3) {
if (!check_file(argv[2], 0)) {
ERR_fmt("Invalid file '%s' for 'config dump' command", argv[2]);
return;
}
x52d_config_save(argv[2]);
OK("config", "dump", argv[2]);
} else {
ERR_fmt("Unexpected arguments for 'config dump' command; got %d, expected 3", argc);
}
return;
}
MATCH(1, "save") {
if (argc == 2) {
raise(SIGUSR1);
OK("config", "save");
} else {
ERR_fmt("Unexpected arguments for 'config save' command; got %d, expected 2", argc);
}
return;
}
MATCH(1, "set") {
if (argc == 5) {
int rc = x52d_config_set(argv[2], argv[3], argv[4]);
if (rc != 0) {
ERR_fmt("Error %d setting '%s.%s'='%s': %s", rc,
argv[2], argv[3], argv[4], strerror(rc));
} else {
x52d_config_apply_immediate(argv[2], argv[3]);
OK("config", "set", argv[2], argv[3], argv[4]);
}
} else {
ERR_fmt("Unexpected arguments for 'config set' command; got %d, expected 5", argc);
}
return;
}
MATCH(1, "get") {
if (argc == 4) {
const char *rv = x52d_config_get(argv[2], argv[3]);
if (rv == NULL) {
ERR_fmt("Error getting '%s.%s'", argv[2], argv[3]);
} else {
DATA(argv[2], argv[3], rv);
}
} else {
ERR_fmt("Unexpected arguments for 'config get' command; got %d, expected 4", argc);
}
return;
}
ERR_fmt("Unknown subcommand '%s' for 'config' command", argv[1]);
}
struct level_map {
int level;
const char *string;
};
static int lmap_get_level(const struct level_map *map, const char *string, int notfound)
{
int i;
for (i = 0; map[i].string != NULL; i++) {
if (strcasecmp(map[i].string, string) == 0) {
return map[i].level;
}
}
return notfound;
}
static const char *lmap_get_string(const struct level_map *map, int level)
{
int i;
for (i = 0; map[i].string != NULL; i++) {
if (map[i].level == level) {
return map[i].string;
}
}
return NULL;
}
static int array_find_index(const char **array, int nmemb, const char *string)
{
int i;
for (i = 0; i < nmemb; i++) {
if (strcasecmp(array[i], string) == 0) {
return i;
}
}
return nmemb;
}
static void cmd_logging(char *buffer, int *buflen, int argc, char **argv)
{
static const char *modules[X52D_MOD_MAX] = {
[X52D_MOD_CONFIG] = "config",
[X52D_MOD_CLOCK] = "clock",
[X52D_MOD_DEVICE] = "device",
[X52D_MOD_IO] = "io",
[X52D_MOD_LED] = "led",
[X52D_MOD_MOUSE] = "mouse",
[X52D_MOD_COMMAND] = "command",
[X52D_MOD_CLIENT] = "client",
[X52D_MOD_NOTIFY] = "notify",
};
// This corresponds to the levels in pinelog
static const struct level_map loglevels[] = {
{PINELOG_LVL_NOTSET, "default"},
{PINELOG_LVL_NONE, "none"},
{PINELOG_LVL_FATAL, "fatal"},
{PINELOG_LVL_ERROR, "error"},
{PINELOG_LVL_WARNING, "warning"},
{PINELOG_LVL_INFO, "info"},
{PINELOG_LVL_DEBUG, "debug"},
{PINELOG_LVL_TRACE, "trace"},
{0, NULL},
};
if (argc < 2) {
ERR("Insufficient arguments for 'logging' command");
return;
}
// logging show [module]
MATCH(1, "show") {
if (argc == 2) {
// Show default logging level
DATA("global", lmap_get_string(loglevels, pinelog_get_level()));
} 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)));
}
} else {
ERR_fmt("Unexpected arguments for 'logging show' command; got %d, expected 2 or 3", argc);
}
return;
}
// logging set [module] <level>
MATCH(1, "set") {
if (argc == 3) {
int level = lmap_get_level(loglevels, argv[2], INT_MAX);
if (level == INT_MAX) {
ERR_fmt("Unknown level '%s' for 'logging set' command", argv[2]);
} else if (level == PINELOG_LVL_NOTSET) {
ERR("'default' level is not valid without a module");
} else {
pinelog_set_level(level);
OK("logging", "set", argv[2]);
}
} else if (argc == 4) {
int level = lmap_get_level(loglevels, argv[3], INT_MAX);
int module = array_find_index(modules, X52D_MOD_MAX, argv[2]);
if (module == X52D_MOD_MAX) {
ERR_fmt("Invalid module '%s'", argv[2]);
return;
}
if (level == INT_MAX) {
ERR_fmt("Unknown level '%s' for 'logging set' command", argv[3]);
} else {
pinelog_set_module_level(module, level);
OK("logging", "set", argv[2], argv[3]);
}
} else {
ERR_fmt("Unexpected arguments for 'logging set' command; got %d, expected 3 or 4", argc);
}
return;
}
ERR_fmt("Unknown subcommand '%s' for 'logging' command", argv[1]);
}
static void command_parser(char *buffer, int *buflen)
{
int argc = 0;
char *argv[X52D_BUFSZ] = { 0 };
x52d_split_args(&argc, argv, buffer, *buflen);
MATCH(0, "config") {
cmd_config(buffer, buflen, argc, argv);
} else MATCH(0, "logging") {
cmd_logging(buffer, buflen, argc, argv);
} else {
ERR_fmt("Unknown command '%s'", argv[0]);
}
}
static void client_handler(int fd)
{
char buffer[X52D_BUFSZ] = { 0 };
int sent;
int rc;
rc = recv(fd, buffer, sizeof(buffer), 0);
if (rc < 0) {
PINELOG_ERROR(_("Error reading from client %d: %s"),
fd, strerror(errno));
return;
}
// Parse and handle command.
command_parser(buffer, &rc);
PINELOG_TRACE("Sending %d bytes in response '%s'", rc, buffer);
sent = send(fd, buffer, rc, 0);
if (sent != rc) {
PINELOG_ERROR(_("Short write to client %d; expected %d bytes, wrote %d bytes"),
fd, rc, sent);
}
}
int x52d_command_loop(int sock_fd)
{
struct pollfd pfd[MAX_CONN];
int rc;
rc = x52d_client_poll(client_fd, pfd, sock_fd);
if (rc <= 0) {
return -1;
}
x52d_client_handle(client_fd, pfd, sock_fd, client_handler);
return 0;
}
static void * x52d_command_thread(void *param)
{
for (;;) {
if (x52d_command_loop(command_sock_fd) < 0) {
PINELOG_FATAL(_("Error %d during command loop: %s"),
errno, strerror(errno));
}
}
return NULL;
}
int x52d_command_init(const char *sock_path)
{
int sock_fd;
int len;
struct sockaddr_un local;
x52d_client_init(client_fd);
command_sock = sock_path;
command_sock_fd = -1;
len = x52d_setup_command_sock(command_sock, &local);
if (len < 0) {
return -1;
}
sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock_fd < 0) {
/* Failure creating the socket. Abort early */
PINELOG_ERROR(_("Error creating command socket: %s"), strerror(errno));
return -1;
}
command_sock_fd = sock_fd;
/* 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;
}
PINELOG_INFO(_("Starting command processing thread"));
pthread_create(&command_thr, NULL, x52d_command_thread, NULL);
return 0;
listen_failure:
unlink(local.sun_path);
sock_failure:
if (command_sock_fd >= 0) {
close(command_sock_fd);
command_sock_fd = -1;
}
return -1;
}
void x52d_command_exit(void)
{
PINELOG_INFO(_("Shutting down command processing thread"));
pthread_cancel(command_thr);
// Close the socket and remove the socket file
if (command_sock_fd >= 0) {
command_sock = x52d_command_sock_path(command_sock);
PINELOG_TRACE("Closing command socket %s", command_sock);
close(command_sock_fd);
command_sock_fd = -1;
unlink(command_sock);
command_sock = NULL;
}
}

View File

@ -0,0 +1,16 @@
/*
* Saitek X52 Pro MFD & LED driver - Command processor
*
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
#ifndef X52D_COMMAND_H
#define X52D_COMMAND_H
int x52d_command_init(const char *sock_path);
void x52d_command_exit(void);
int x52d_command_loop(int sock_fd);
#endif // !defined X52D_COMMAND_H

View File

@ -0,0 +1,112 @@
/*
* Saitek X52 Pro MFD & LED driver - Configuration parser
*
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
#include "config.h"
#include <errno.h>
#define PINELOG_MODULE X52D_MOD_CONFIG
#include "pinelog.h"
#include "x52d_config.h"
#include "x52d_const.h"
static struct x52d_config x52d_config;
void x52d_config_load(const char *cfg_file)
{
int rc;
if (cfg_file == NULL) {
cfg_file = X52D_SYS_CFG_FILE;
}
rc = x52d_config_set_defaults(&x52d_config);
if (rc != 0) {
PINELOG_FATAL(_("Error %d setting configuration defaults: %s"),
rc, strerror(rc));
}
rc = x52d_config_load_file(&x52d_config, cfg_file);
if (rc != 0) {
exit(EXIT_FAILURE);
}
// Apply overrides
rc = x52d_config_apply_overrides(&x52d_config);
x52d_config_clear_overrides();
if (rc != 0) {
exit(EXIT_FAILURE);
}
}
void x52d_config_save(const char *cfg_file)
{
int rc;
if (cfg_file == NULL) {
cfg_file = X52D_SYS_CFG_FILE;
}
rc = x52d_config_save_file(&x52d_config, cfg_file);
if (rc != 0) {
PINELOG_ERROR(_("Error %d saving configuration file: %s"),
rc, strerror(rc));
}
}
int x52d_config_set(const char *section, const char *key, const char *value)
{
if (section == NULL || key == NULL || value == NULL) {
return EINVAL;
}
PINELOG_TRACE("Processing config set '%s.%s'='%s'", section, key, value);
return x52d_config_process_kv(&x52d_config, section, key, value);
}
const char *x52d_config_get(const char *section, const char *key)
{
const char *value;
if (section == NULL || key == NULL) {
return NULL;
}
value = x52d_config_get_param(&x52d_config, section, key);
PINELOG_TRACE("Processed config get '%s.%s'='%s'", section, key, value);
return value;
}
/* Callback stubs
* TODO: Remove the ones below when their implementation is complete
*/
void x52d_cfg_set_Profiles_Directory(char* param) { (void)param; }
void x52d_cfg_set_Profiles_ClutchEnabled(bool param) { (void)param; }
void x52d_cfg_set_Profiles_ClutchLatched(bool param) { (void)param; }
void x52d_config_apply_immediate(const char *section, const char *key)
{
#define CFG(c_sec, c_key, name, parser, def) \
if (!strcasecmp(section, #c_sec) && !strcasecmp(key, #c_key)) { \
PINELOG_TRACE("Invoking " #c_sec "." #c_key " callback"); \
x52d_cfg_set_ ## c_sec ## _ ## c_key(x52d_config . name); \
} else
#include "x52d_config.def"
// Dummy to capture the trailing else
// Wrap it in braces in case tracing has been disabled
{ PINELOG_TRACE("Ignoring apply_immediate(%s.%s)", section, key); }
}
void x52d_config_apply(void)
{
#define CFG(section, key, name, parser, def) \
PINELOG_TRACE("Calling configuration callback for " #section "." #key); \
x52d_cfg_set_ ## section ## _ ## key(x52d_config . name);
#include "x52d_config.def"
}

View File

@ -0,0 +1,95 @@
/**********************************************************************
* X52 Daemon Configuration
*********************************************************************/
// The settings below are the defaults. Note that the section and key
// strings are case insensitive, but the values are not necessarily so,
// especially for those referring to paths or timezone names.
/* CFG(section, key, name, parser/dumper type, default) */
/**********************************************************************
* Clock Settings
*********************************************************************/
// Enabled controls whether the clock is enabled or not. Set this to no to
// disable the clock update. Keep in mind that if the clock was originally
// enabled on the X52, then disabling it here won't make the clock disappear
// on the MFD. You will need to unplug and reattach the X52 to make the
// clock disappear
CFG(Clock, Enabled, clock_enabled, bool, true)
// PrimaryIsLocal controls whether the primary clock displays local time or UTC.
// Set this to yes to display local time, no for UTC.
CFG(Clock, PrimaryIsLocal, primary_clock_local, bool, true)
// Secondary controls the timezone of the secondary clock. Use the standard
// timezone name as defined by the Olson time database.
CFG(Clock, Secondary, clock_2_tz, string, UTC)
// Tertiary controls the timezone of the tertiary clock. Use the standard
// timezone name as defined by the Olson time database.
CFG(Clock, Tertiary, clock_3_tz, string, UTC)
// Clock format for the primary clock
CFG(Clock, FormatPrimary, clock_format[LIBX52_CLOCK_1], clock_format, 12hr)
// Clock format for the secondary clock
CFG(Clock, FormatSecondary, clock_format[LIBX52_CLOCK_2], clock_format, 12hr)
// Clock format for the tertiary clock
CFG(Clock, FormatTertiary, clock_format[LIBX52_CLOCK_3], clock_format, 12hr)
// Date format for the date display
CFG(Clock, DateFormat, date_format, date_format, ddmmyy)
/**********************************************************************
* LED Settings - only applicable to X52Pro
*********************************************************************/
// The LED settings map a color code or state to the corresponding LED.
CFG(LED, Fire, leds[LIBX52_LED_FIRE], led, on)
CFG(LED, Throttle, leds[LIBX52_LED_THROTTLE], led, on)
CFG(LED, A, leds[LIBX52_LED_A], led, green)
CFG(LED, B, leds[LIBX52_LED_B], led, green)
CFG(LED, D, leds[LIBX52_LED_D], led, green)
CFG(LED, E, leds[LIBX52_LED_E], led, green)
CFG(LED, T1, leds[LIBX52_LED_T1], led, green)
CFG(LED, T2, leds[LIBX52_LED_T2], led, green)
CFG(LED, T3, leds[LIBX52_LED_T3], led, green)
CFG(LED, POV, leds[LIBX52_LED_POV], led, green)
CFG(LED, Clutch, leds[LIBX52_LED_CLUTCH], led, green)
/**********************************************************************
* Brightness Settings
*********************************************************************/
// The brightness settings map the brightness value to the LEDs/MFD.
CFG(Brightness, MFD, brightness[0], int, 128)
CFG(Brightness, LED, brightness[1], int, 128)
/**********************************************************************
* Mouse Settings
*********************************************************************/
// Enabled controls whether the virtual mouse is enabled or not.
CFG(Mouse, Enabled, mouse_enabled, bool, true)
// Speed is a value that is proportional to the speed of updates to the
// virtual mouse
CFG(Mouse, Speed, mouse_speed, int, 0)
// ReverseScroll controls the scrolling direction
CFG(Mouse, ReverseScroll, mouse_reverse_scroll, bool, false)
/**********************************************************************
* Profiles - only valid on Linux
*********************************************************************/
// Directory is the location of the folder containing the individual profiles.
CFG(Profiles, Directory, profiles_dir, string, /etc/x52d/profiles.d)
// ClutchEnabled determines if the clutch button is treated specially
CFG(Profiles, ClutchEnabled, clutch_enabled, bool, false)
// ClutchLatched controls if the clutch button (if enabled) is a latched button
// (press once to enter clutch mode, press again to exit clutch mode), or must
// be held down to remain in clutch mode.
CFG(Profiles, ClutchLatched, clutch_latched, bool, false)
#undef CFG

View File

@ -0,0 +1,103 @@
/*
* Saitek X52 Pro MFD & LED driver - Configuration parser header
*
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
#ifndef X52D_CONFIG_H
#define X52D_CONFIG_H
#include <stdint.h>
#include <stdbool.h>
#include <limits.h>
#include "libx52.h"
/**
* @brief Configuration structure
*
* Keep this in sync with the sample configuration
*/
struct x52d_config {
bool clock_enabled;
bool primary_clock_local;
// Since we don't have a _MAX identifier for libx52_clock_id, use
// the maximum clock ID + 1 as the length
libx52_clock_format clock_format[LIBX52_CLOCK_3 + 1];
libx52_date_format date_format;
char clock_2_tz[NAME_MAX];
char clock_3_tz[NAME_MAX];
// Since we don't have a _MAX identifier for libx52_led_id, hardcode
// the length in the following declaration.
libx52_led_state leds[21];
int brightness[2];
bool mouse_enabled;
int mouse_speed;
bool mouse_reverse_scroll;
bool clutch_enabled;
bool clutch_latched;
char profiles_dir[NAME_MAX];
};
/* Callback functions for configuration */
// These functions are defined in the individual modules
void x52d_cfg_set_Clock_Enabled(bool param);
void x52d_cfg_set_Clock_PrimaryIsLocal(bool param);
void x52d_cfg_set_Clock_Secondary(char* param);
void x52d_cfg_set_Clock_Tertiary(char* param);
void x52d_cfg_set_Clock_FormatPrimary(libx52_clock_format param);
void x52d_cfg_set_Clock_FormatSecondary(libx52_clock_format param);
void x52d_cfg_set_Clock_FormatTertiary(libx52_clock_format param);
void x52d_cfg_set_Clock_DateFormat(libx52_date_format param);
void x52d_cfg_set_LED_Fire(libx52_led_state param);
void x52d_cfg_set_LED_Throttle(libx52_led_state param);
void x52d_cfg_set_LED_A(libx52_led_state param);
void x52d_cfg_set_LED_B(libx52_led_state param);
void x52d_cfg_set_LED_D(libx52_led_state param);
void x52d_cfg_set_LED_E(libx52_led_state param);
void x52d_cfg_set_LED_T1(libx52_led_state param);
void x52d_cfg_set_LED_T2(libx52_led_state param);
void x52d_cfg_set_LED_T3(libx52_led_state param);
void x52d_cfg_set_LED_POV(libx52_led_state param);
void x52d_cfg_set_LED_Clutch(libx52_led_state param);
void x52d_cfg_set_Brightness_MFD(uint16_t param);
void x52d_cfg_set_Brightness_LED(uint16_t param);
void x52d_cfg_set_Mouse_Enabled(bool param);
void x52d_cfg_set_Mouse_Speed(int param);
void x52d_cfg_set_Mouse_ReverseScroll(bool param);
void x52d_cfg_set_Profiles_Directory(char* param);
void x52d_cfg_set_Profiles_ClutchEnabled(bool param);
void x52d_cfg_set_Profiles_ClutchLatched(bool param);
int x52d_config_process_kv(void *user, const char *section, const char *key, const char *value);
const char *x52d_config_get_param(struct x52d_config *cfg, const char *section, const char *key);
int x52d_config_set_defaults(struct x52d_config *cfg);
int x52d_config_load_file(struct x52d_config *cfg, const char *cfg_file);
int x52d_config_save_override(const char *override_str);
int x52d_config_apply_overrides(struct x52d_config *cfg);
void x52d_config_clear_overrides(void);
void x52d_config_load(const char *cfg_file);
void x52d_config_apply_immediate(const char *section, const char *key);
void x52d_config_apply(void);
int x52d_config_save_file(struct x52d_config *cfg, const char *cfg_file);
void x52d_config_save(const char *cfg_file);
int x52d_config_set(const char *section, const char *key, const char *value);
const char *x52d_config_get(const char *section, const char *key);
#endif // !defined X52D_CONFIG_H

View File

@ -0,0 +1,151 @@
/*
* Saitek X52 Pro MFD & LED driver - Configuration dumper
*
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
#include "config.h"
#include <stdio.h>
#include <string.h>
#include <stddef.h>
#include <stdlib.h>
#include <errno.h>
#include <stdbool.h>
#define PINELOG_MODULE X52D_MOD_CONFIG
#include "pinelog.h"
#include "libx52.h"
#include "x52d_config.h"
#include "x52d_const.h"
// Create a pointer "name" of type "type", which stores the value of the
// corresponding element within the config struct.
#define CONFIG_PTR(type, name) type name = (type)((uintptr_t)cfg + offset)
// Check if the parameters are all valid
#define CHECK_PARAMS() do { if (cfg == NULL || section == NULL || key == NULL) { return NULL; } } while(0)
static const char * bool_dumper(const char *section, const char *key, const struct x52d_config *cfg, size_t offset)
{
CONFIG_PTR(bool *, config);
CHECK_PARAMS();
PINELOG_TRACE("Printing bool value %s.%s from offset %lu value = %d",
section, key, offset, *config);
return *config ? "true" : "false";
}
static const char * string_dumper(const char *section, const char *key, struct x52d_config *cfg, size_t offset)
{
CONFIG_PTR(char *, config);
CHECK_PARAMS();
PINELOG_TRACE("Printing string value %s.%s from offset %lu value = %s",
section, key, offset, config);
return config;
}
static const char * int_dumper(const char *section, const char *key, struct x52d_config *cfg, size_t offset)
{
static char dump[256];
CONFIG_PTR(int *, config);
CHECK_PARAMS();
PINELOG_TRACE("Printing int value %s.%s from offset %lu value = %d",
section, key, offset, *config);
snprintf(dump, sizeof(dump), "%d", *config);
return dump;
}
static const char * led_dumper(const char *section, const char *key, struct x52d_config *cfg, size_t offset)
{
CONFIG_PTR(libx52_led_state *, config);
CHECK_PARAMS();
PINELOG_TRACE("Printing led value %s.%s from offset %lu value = %d",
section, key, offset, *config);
return libx52_led_state_to_str(*config);
}
static const char * clock_format_dumper(const char *section, const char *key, struct x52d_config *cfg, size_t offset)
{
CONFIG_PTR(libx52_clock_format *, config);
CHECK_PARAMS();
PINELOG_TRACE("Printing clock format value %s.%s from offset %lu value = %d",
section, key, offset, *config);
return libx52_clock_format_to_str(*config);
}
static const char * date_format_dumper(const char *section, const char *key, struct x52d_config *cfg, size_t offset)
{
CONFIG_PTR(libx52_date_format *, config);
CHECK_PARAMS();
PINELOG_TRACE("Printing date format value %s.%s from offset %lu value = %d",
section, key, offset, *config);
return libx52_date_format_to_str(*config);
}
#undef CHECK_PARAMS
#undef CONFIG_PTR
int x52d_config_save_file(struct x52d_config *cfg, const char *cfg_file)
{
FILE *cfg_fp;
char *current_section = NULL;
const char *value;
if (cfg == NULL || cfg_file == NULL) {
return EINVAL;
}
cfg_fp = fopen(cfg_file, "w");
if (cfg_fp == NULL) {
PINELOG_ERROR(_("Unable to save config file %s - code %d: %s"),
cfg_file, errno, strerror(errno));
return 1;
}
PINELOG_TRACE("Saving configuration to file %s", cfg_file);
#define CFG(section, key, name, type, def) do { \
if (current_section == NULL || strcasecmp(current_section, #section)) { \
if (current_section != NULL) { \
free(current_section); \
} \
current_section = strdup(#section); \
PINELOG_TRACE("Printing section header %s", #section); \
fprintf(cfg_fp, "[%s]\n", #section); \
} \
PINELOG_TRACE("Dumping " #section "." #key " to file %s", cfg_file); \
value = type ## _dumper(#section, #key, cfg, offsetof(struct x52d_config, name)); \
if (value == NULL) { \
PINELOG_ERROR(_("Failed to dump %s.%s to config file %s"), \
#section, #key, cfg_file); \
goto exit_dump; \
} else { \
fprintf(cfg_fp, "%s = %s\n", #key, value); \
} \
} while (0);
#include "x52d_config.def"
exit_dump:
free(current_section);
fclose(cfg_fp);
return (value == NULL);
}
const char *x52d_config_get_param(struct x52d_config *cfg, const char *section, const char *key)
{
#define CFG(section_c, key_c, name, type, def) do { \
if (strcasecmp(section, #section_c) == 0 && strcasecmp(key, #key_c) == 0) { \
return type ## _dumper(section, key, cfg, offsetof(struct x52d_config, name)); \
} \
} while (0);
#include "x52d_config.def"
return NULL;
}

View File

@ -0,0 +1,365 @@
/*
* Saitek X52 Pro MFD & LED driver - Configuration parser
*
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
#include "config.h"
#include <stdio.h>
#include <string.h>
#include <stddef.h>
#include <stdlib.h>
#include <errno.h>
#include <stdbool.h>
#define PINELOG_MODULE X52D_MOD_CONFIG
#include "ini.h"
#include "pinelog.h"
#include "x52d_config.h"
#include "x52d_const.h"
/* Parser function typedef */
typedef int (*parser_fn)(struct x52d_config *, size_t, const char *);
// Check if the parameters are all valid
#define CHECK_PARAMS() do { if (cfg == NULL || value == NULL) { return EINVAL; } } while(0)
// Create a pointer "name" of type "type", which stores the pointer to the
// corresponding element within the config struct.
#define CONFIG_PTR(type, name) type name = (type)((uintptr_t)cfg + offset)
static int bool_parser(struct x52d_config *cfg, size_t offset, const char *value)
{
CONFIG_PTR(bool *, config);
CHECK_PARAMS();
if (!strcasecmp(value, "yes") || !strcasecmp(value, "true")) {
*config = true;
} else if (!strcasecmp(value, "no") || !strcasecmp(value, "false")) {
*config = false;
} else {
return EINVAL;
}
return 0;
}
static int string_parser(struct x52d_config *cfg, size_t offset, const char *value)
{
CONFIG_PTR(char *, config);
CHECK_PARAMS();
/* String parameters are all NAME_MAX len */
strncpy(config, value, NAME_MAX-1);
config[NAME_MAX-1] = '\0';
return 0;
}
static int int_parser(struct x52d_config *cfg, size_t offset, const char *value)
{
CONFIG_PTR(int *, config);
char *endptr;
int retval;
CHECK_PARAMS();
errno = 0;
retval = strtol(value, &endptr, 0);
if (errno != 0) {
return errno;
}
if (*endptr != '\0') {
// Invalid characters in string
return EINVAL;
}
*config = retval;
return 0;
}
static int led_parser(struct x52d_config *cfg, size_t offset, const char *value)
{
CONFIG_PTR(libx52_led_state *, config);
CHECK_PARAMS();
#define MATCH_STATE(val) if (!strcasecmp(value, #val)) { *config = LIBX52_LED_STATE_ ## val ; }
MATCH_STATE(OFF)
else MATCH_STATE(ON)
else MATCH_STATE(RED)
else MATCH_STATE(AMBER)
else MATCH_STATE(GREEN)
else return EINVAL;
#undef MATCH_STATE
return 0;
}
static int clock_format_parser(struct x52d_config *cfg, size_t offset, const char *value)
{
CONFIG_PTR(libx52_clock_format *, config);
CHECK_PARAMS();
if (!strcasecmp(value, "12hr") || !strcasecmp(value, "12")) {
*config = LIBX52_CLOCK_FORMAT_12HR;
} else if (!strcasecmp(value, "24hr") || !strcasecmp(value, "24")) {
*config = LIBX52_CLOCK_FORMAT_24HR;
} else {
return EINVAL;
}
return 0;
}
static int date_format_parser(struct x52d_config *cfg, size_t offset, const char *value)
{
CONFIG_PTR(libx52_date_format *, config);
CHECK_PARAMS();
if (!strcasecmp(value, "ddmmyy") || !strcasecmp(value, "dd-mm-yy")) {
*config = LIBX52_DATE_FORMAT_DDMMYY;
} else if (!strcasecmp(value, "mmddyy") || !strcasecmp(value, "mm-dd-yy")) {
*config = LIBX52_DATE_FORMAT_MMDDYY;
} else if (!strcasecmp(value, "yymmdd") || !strcasecmp(value, "yy-mm-dd")) {
*config = LIBX52_DATE_FORMAT_YYMMDD;
} else {
return EINVAL;
}
return 0;
}
#undef CHECK_PARAMS
#undef CONFIG_PTR
/* Map for config->param */
#define CFG(section, key, name, type, def) {#section, #key, type ## _parser, offsetof(struct x52d_config, name)},
static const struct config_map {
const char *section;
const char *key;
parser_fn parser;
size_t offset;
} config_map[] = {
#include "x52d_config.def"
// Terminating entry
{NULL, NULL, NULL, 0}
};
int x52d_config_process_kv(void *user, const char *section, const char *key, const char *value)
{
int i;
int rc = 0;
bool found = false;
struct x52d_config *cfg = (struct x52d_config*)user;
for (i = 0; config_map[i].key != NULL; i++) {
rc = 0;
if (!strcasecmp(config_map[i].key, key) &&
!strcasecmp(config_map[i].section, section)) {
found = true;
PINELOG_TRACE("Setting '%s.%s'='%s'",
config_map[i].section, config_map[i].key, value);
rc = config_map[i].parser(cfg, config_map[i].offset, value);
break;
}
}
if (!found) {
// Print error message, but continue
PINELOG_INFO(_("Ignoring unknown key '%s.%s'"), section, key);
}
return rc;
}
/**
* @brief Set configuration defaults
*
* @param[in] cfg Pointer to config struct
*
* @returns 0 on success, non-zero error code on failure
*/
int x52d_config_set_defaults(struct x52d_config *cfg) {
int rc;
if (cfg == NULL) {
return EINVAL;
}
PINELOG_TRACE("Setting configuration defaults");
#define CFG(section, key, name, parser, def) \
rc = x52d_config_process_kv(cfg, #section, #key, #def); \
if (rc != 0) { \
return rc; \
}
#include "x52d_config.def"
return 0;
}
int x52d_config_load_file(struct x52d_config *cfg, const char *cfg_file)
{
int rc;
if (cfg == NULL || cfg_file == NULL) {
return EINVAL;
}
PINELOG_TRACE("Loading configuration from file %s", cfg_file);
rc = ini_parse(cfg_file, x52d_config_process_kv, cfg);
if (rc < 0) {
PINELOG_ERROR(_("Failed processing configuration file %s - code %d"),
cfg_file, rc);
return EIO;
}
return 0;
}
struct x52d_config_override {
char *section;
char *key;
char *value;
struct x52d_config_override *next;
};
static struct x52d_config_override *override_head;
static struct x52d_config_override *override_tail;
int x52d_config_save_override(const char *override_str)
{
// Parse override string of the form section.key=value
struct x52d_config_override *override;
char *string = NULL;
char *free_ptr = NULL;
char *ptr;
int rc;
PINELOG_TRACE("Allocating memory (%lu bytes) for override structure", sizeof(*override));
override = calloc(1, sizeof(*override));
if (override == NULL) {
PINELOG_ERROR(_("Failed to allocate memory for override structure"));
rc = ENOMEM;
goto cleanup;
}
errno = 0;
PINELOG_TRACE("Duplicating override string");
string = strdup(override_str);
if (string == NULL) {
PINELOG_ERROR(_("Failed to allocate memory for override string"));
rc = errno;
goto cleanup;
}
free_ptr = string;
override->section = string;
// Ensure that the string is of the form ([^.]+\.[^=]+=.*)
ptr = strchr(string, '.');
if (ptr == NULL || ptr == string) {
// No section found
PINELOG_ERROR(_("No section found in override string '%s'"), string);
rc = EINVAL;
goto cleanup;
}
// Reset the . to NUL
*ptr = '\0';
ptr++;
PINELOG_TRACE("Splitting override string to '%s' and '%s'", string, ptr);
string = ptr;
override->key = string;
ptr = strchr(string, '=');
if (ptr == NULL || ptr == string) {
// No key found
PINELOG_ERROR(_("No key found in override string '%s'"), string);
rc = EINVAL;
goto cleanup;
}
// Reset the = to NUL
*ptr = '\0';
ptr++;
PINELOG_TRACE("Splitting override string to '%s' and '%s'", string, ptr);
if (*ptr == '\0') {
// No value found
PINELOG_ERROR(_("No value found in override string '%s'"), string);
rc = EINVAL;
goto cleanup;
}
override->value = ptr;
// Add the override to the linked list
if (override_tail != NULL) {
PINELOG_TRACE("Linking override to list tail");
override_tail->next = override;
}
PINELOG_TRACE("Setting list tail to override");
override_tail = override;
if (override_head == NULL) {
PINELOG_TRACE("Setting list head to override");
override_head = override;
}
return 0;
cleanup:
if (free_ptr != NULL) {
free(free_ptr);
}
if (override != NULL) {
free(override);
}
return rc;
}
int x52d_config_apply_overrides(struct x52d_config *cfg)
{
int rc;
struct x52d_config_override *tmp = override_head;
if (cfg == NULL) {
return EINVAL;
}
while (tmp != NULL) {
PINELOG_TRACE("Processing override '%s.%s=%s'",
tmp->section,
tmp->key,
tmp->value);
rc = x52d_config_process_kv(cfg,
tmp->section,
tmp->key,
tmp->value);
if (rc != 0) {
PINELOG_ERROR(_("Error processing override '%s.%s=%s'"),
tmp->section,
tmp->key,
tmp->value);
return rc;
}
tmp = tmp->next;
}
return 0;
}
void x52d_config_clear_overrides(void)
{
struct x52d_config_override *tmp;
while (override_head != NULL) {
tmp = override_head;
override_head = override_head->next;
PINELOG_TRACE("Freeing override '%s.%s=%s'",
tmp->section,
tmp->key,
tmp->value);
free(tmp);
}
override_tail = NULL;
}

View File

@ -0,0 +1,43 @@
/*
* Saitek X52 Pro MFD & LED driver - Application constants
*
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
#ifndef X52D_CONST_H
#define X52D_CONST_H
#define X52D_APP_NAME "x52d"
#define X52D_LOG_FILE LOGDIR "/" X52D_APP_NAME ".log"
#define X52D_SYS_CFG_FILE SYSCONFDIR "/" X52D_APP_NAME "/" X52D_APP_NAME ".conf"
#define X52D_PID_FILE RUNDIR "/" X52D_APP_NAME ".pid"
#define X52D_SOCK_COMMAND RUNDIR "/" X52D_APP_NAME ".cmd"
#define X52D_SOCK_NOTIFY RUNDIR "/" X52D_APP_NAME ".notify"
#include "gettext.h"
#define N_(x) gettext_noop(x)
#define _(x) gettext(x)
#define X52D_MAX_CLIENTS 63
enum {
X52D_MOD_CONFIG,
X52D_MOD_CLOCK,
X52D_MOD_DEVICE,
X52D_MOD_IO,
X52D_MOD_LED,
X52D_MOD_MOUSE,
X52D_MOD_COMMAND,
X52D_MOD_CLIENT,
X52D_MOD_NOTIFY,
X52D_MOD_MAX
};
#endif // !defined X52D_CONST_H

View File

@ -0,0 +1,187 @@
/*
* Saitek X52 Pro MFD & LED driver - Device manager
*
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
#include "config.h"
#include <unistd.h>
#include <pthread.h>
#include <stdbool.h>
#define PINELOG_MODULE X52D_MOD_DEVICE
#include "x52d_const.h"
#include "x52d_config.h"
#include "x52d_device.h"
#include "x52d_notify.h"
#include "libx52.h"
#include "pinelog.h"
static libx52_device *x52_dev;
static pthread_mutex_t device_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_t device_thr;
static volatile bool device_update_needed;
static void *x52_dev_thr(void *param)
{
int rc;
#define DEV_ACQ_DELAY 5 // seconds
#define DEV_UPD_DELAY 50000 // microseconds
PINELOG_INFO(_("Starting X52 device manager thread"));
for (;;) {
if (!libx52_is_connected(x52_dev)) {
PINELOG_TRACE("Attempting to connect to X52 device");
rc = libx52_connect(x52_dev);
if (rc != LIBX52_SUCCESS) {
if (rc != LIBX52_ERROR_NO_DEVICE) {
PINELOG_ERROR(_("Error %d connecting to device: %s"),
rc, libx52_strerror(rc));
} else {
PINELOG_TRACE("No compatible X52 device found");
}
PINELOG_TRACE("Sleeping for %d seconds before trying to acquire device again", DEV_ACQ_DELAY);
sleep(DEV_ACQ_DELAY);
} else {
/* Successfully connected */
PINELOG_INFO(_("Device connected, writing configuration"));
X52D_NOTIFY("CONNECTED");
x52d_config_apply();
}
} else {
if (!device_update_needed) {
usleep(DEV_UPD_DELAY);
continue;
}
(void)x52d_dev_update();
}
}
#undef DEV_ACQ_DELAY
#undef DEV_UPD_DELAY
return NULL;
}
void x52d_dev_init(void)
{
int rc;
PINELOG_INFO(_("Initializing libx52"));
rc = libx52_init(&x52_dev);
if (rc != LIBX52_SUCCESS) {
PINELOG_FATAL(_("Failure %d initializing libx52: %s"),
rc, libx52_strerror(rc));
}
// Create and initialize the thread
pthread_create(&device_thr, NULL, x52_dev_thr, NULL);
}
void x52d_dev_exit(void)
{
// Shutdown any threads
PINELOG_INFO(_("Shutting down X52 device manager thread"));
pthread_cancel(device_thr);
libx52_exit(x52_dev);
}
#define WRAP_LIBX52(func) \
int rc; \
pthread_mutex_lock(&device_mutex); \
rc = func; \
pthread_mutex_unlock(&device_mutex); \
if (rc != LIBX52_SUCCESS) { \
if (rc != LIBX52_ERROR_TRY_AGAIN) { \
PINELOG_ERROR(_("Error %d when updating X52 parameter: %s"), \
rc, libx52_strerror(rc)); \
} \
} else { \
device_update_needed = true; \
} \
return rc
int x52d_dev_set_text(uint8_t line, const char *text, uint8_t length)
{
WRAP_LIBX52(libx52_set_text(x52_dev, line, text, length));
}
int x52d_dev_set_led_state(libx52_led_id led, libx52_led_state state)
{
if (libx52_check_feature(x52_dev, LIBX52_FEATURE_LED) != LIBX52_ERROR_NOT_SUPPORTED) {
WRAP_LIBX52(libx52_set_led_state(x52_dev, led, state));
}
// If the target device does not support setting individual LEDs,
// then ignore the set and let the caller think it succeeded.
PINELOG_TRACE("Ignoring set LED state call as the device does not support it");
return LIBX52_SUCCESS;
}
int x52d_dev_set_clock(time_t time, int local)
{
WRAP_LIBX52(libx52_set_clock(x52_dev, time, local));
}
int x52d_dev_set_clock_timezone(libx52_clock_id clock, int offset)
{
WRAP_LIBX52(libx52_set_clock_timezone(x52_dev, clock, offset));
}
int x52d_dev_set_clock_format(libx52_clock_id clock, libx52_clock_format format)
{
WRAP_LIBX52(libx52_set_clock_format(x52_dev, clock, format));
}
int x52d_dev_set_time(uint8_t hour, uint8_t minute)
{
WRAP_LIBX52(libx52_set_time(x52_dev, hour, minute));
}
int x52d_dev_set_date(uint8_t dd, uint8_t mm, uint8_t yy)
{
WRAP_LIBX52(libx52_set_date(x52_dev, dd, mm, yy));
}
int x52d_dev_set_date_format(libx52_date_format format)
{
WRAP_LIBX52(libx52_set_date_format(x52_dev, format));
}
int x52d_dev_set_brightness(uint8_t mfd, uint16_t brightness)
{
WRAP_LIBX52(libx52_set_brightness(x52_dev, mfd, brightness));
}
int x52d_dev_set_shift(uint8_t state)
{
WRAP_LIBX52(libx52_set_shift(x52_dev, state));
}
int x52d_dev_set_blink(uint8_t state)
{
WRAP_LIBX52(libx52_set_blink(x52_dev, state));
}
int x52d_dev_update(void)
{
int rc;
pthread_mutex_lock(&device_mutex);
rc = libx52_update(x52_dev);
pthread_mutex_unlock(&device_mutex);
if (rc != LIBX52_SUCCESS) {
if (rc == LIBX52_ERROR_NO_DEVICE) {
// Detach from the existing device, the next thread run will
// pick it up.
PINELOG_TRACE("Disconnecting detached device");
libx52_disconnect(x52_dev);
X52D_NOTIFY("DISCONNECTED");
} else {
PINELOG_ERROR(_("Error %d when updating X52 device: %s"),
rc, libx52_strerror(rc));
}
} else {
device_update_needed = false;
}
return rc;
}

View File

@ -0,0 +1,31 @@
/*
* Saitek X52 Pro MFD & LED driver - Device manager header
*
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
#ifndef X52D_DEVICE_H
#define X52D_DEVICE_H
#include "libx52.h"
void x52d_dev_init(void);
void x52d_dev_exit(void);
/* Wrapper methods for libx52 calls */
int x52d_dev_set_text(uint8_t line, const char *text, uint8_t length);
int x52d_dev_set_led_state(libx52_led_id led, libx52_led_state state);
int x52d_dev_set_clock(time_t time, int local);
int x52d_dev_set_clock_timezone(libx52_clock_id clock, int offset);
int x52d_dev_set_clock_format(libx52_clock_id clock, libx52_clock_format format);
int x52d_dev_set_time(uint8_t hour, uint8_t minute);
int x52d_dev_set_date(uint8_t dd, uint8_t mm, uint8_t yy);
int x52d_dev_set_date_format(libx52_date_format format);
int x52d_dev_set_brightness(uint8_t mfd, uint16_t brightness);
int x52d_dev_set_shift(uint8_t state);
int x52d_dev_set_blink(uint8_t state);
int x52d_dev_update(void);
#endif // !defined X52D_DEVICE_H

119
daemon/x52d_io.c 100644
View File

@ -0,0 +1,119 @@
/*
* Saitek X52 Pro MFD & LED driver - I/O driver
*
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
#include "config.h"
#include <stdbool.h>
#include <unistd.h>
#include <pthread.h>
#include "x52d_const.h"
#include "x52d_config.h"
#include "x52d_io.h"
#include "x52d_mouse.h"
#include "libx52io.h"
#define PINELOG_MODULE X52D_MOD_IO
#include "pinelog.h"
static libx52io_context *io_ctx;
static pthread_t io_thr;
static void process_report(libx52io_report *report, libx52io_report *prev)
{
// TODO: Process changes
x52d_mouse_report_event(report);
memcpy(prev, report, sizeof(*prev));
}
static void *x52_io_thr(void *param)
{
int rc;
libx52io_report report;
libx52io_report prev_report;
#define IO_READ_TIMEOUT 50 /* milliseconds */
#define IO_ACQ_TIMEOUT 5 /* seconds */
PINELOG_INFO(_("Starting X52 I/O thread"));
// Reset the previous report, so that process_report can handle changes.
memset(&prev_report, 0, sizeof(prev_report));
for (;;) {
rc = libx52io_read_timeout(io_ctx, &report, IO_READ_TIMEOUT);
switch (rc) {
case LIBX52IO_SUCCESS:
// Found a report
process_report(&report, &prev_report);
break;
case LIBX52IO_ERROR_TIMEOUT:
// No report received within the timeout
break;
case LIBX52IO_ERROR_NO_DEVICE:
PINELOG_TRACE("Device disconnected, trying to connect");
rc = libx52io_open(io_ctx);
if (rc != LIBX52IO_SUCCESS) {
if (rc != LIBX52IO_ERROR_NO_DEVICE) {
PINELOG_ERROR(_("Error %d opening X52 I/O device: %s"),
rc, libx52io_strerror(rc));
} else {
PINELOG_TRACE("No compatible X52 I/O device found. Sleeping %d seconds before trying again.",
IO_ACQ_TIMEOUT);
}
sleep(IO_ACQ_TIMEOUT);
}
break;
default:
PINELOG_ERROR(_("Error %d reading from X52 I/O device: %s"),
rc, libx52io_strerror(rc));
/*
* Possibly disconnected, better to force disconnect now, and try
* to reconnect later
*/
libx52io_close(io_ctx);
/* Report a NULL report to reset the mouse to default state */
x52d_mouse_report_event(NULL);
break;
}
}
#undef IO_READ_TIMEOUT
#undef IO_ACQ_TIMEOUT
return NULL;
}
void x52d_io_init(void)
{
int rc;
PINELOG_TRACE("Initializing I/O driver");
rc = libx52io_init(&io_ctx);
if (rc != LIBX52IO_SUCCESS) {
PINELOG_FATAL(_("Error %d initializing X52 I/O library: %s"),
rc, libx52io_strerror(rc));
}
rc = pthread_create(&io_thr, NULL, x52_io_thr, NULL);
if (rc != 0) {
PINELOG_FATAL(_("Error %d initializing I/O driver thread: %s"),
rc, strerror(rc));
}
}
void x52d_io_exit(void)
{
PINELOG_INFO(_("Shutting down X52 I/O driver thread"));
pthread_cancel(io_thr);
libx52io_exit(io_ctx);
}

15
daemon/x52d_io.h 100644
View File

@ -0,0 +1,15 @@
/*
* Saitek X52 Pro MFD & LED driver - I/O driver
*
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
#ifndef X52D_IO_H
#define X52D_IO_H
void x52d_io_init(void);
void x52d_io_exit(void);
#endif // !defined X52D_IO_H

95
daemon/x52d_led.c 100644
View File

@ -0,0 +1,95 @@
/*
* Saitek X52 Pro MFD & LED driver - Clock manager
*
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
#include "config.h"
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>
#include <string.h>
#define PINELOG_MODULE X52D_MOD_LED
#include "pinelog.h"
#include "x52d_config.h"
#include "x52d_const.h"
#include "x52d_device.h"
#define SET_LED_STATE(led, state) \
PINELOG_TRACE("Setting LED %s state to %s (%d)", \
libx52_led_id_to_str(LIBX52_LED_ ## led), \
libx52_led_state_to_str(state), state); \
x52d_dev_set_led_state(LIBX52_LED_ ## led, state);
void x52d_cfg_set_LED_Fire(libx52_led_state state)
{
SET_LED_STATE(FIRE, state);
}
void x52d_cfg_set_LED_Throttle(libx52_led_state state)
{
SET_LED_STATE(THROTTLE, state);
}
void x52d_cfg_set_LED_A(libx52_led_state state)
{
SET_LED_STATE(A, state);
}
void x52d_cfg_set_LED_B(libx52_led_state state)
{
SET_LED_STATE(B, state);
}
void x52d_cfg_set_LED_D(libx52_led_state state)
{
SET_LED_STATE(D, state);
}
void x52d_cfg_set_LED_E(libx52_led_state state)
{
SET_LED_STATE(E, state);
}
void x52d_cfg_set_LED_T1(libx52_led_state state)
{
SET_LED_STATE(T1, state);
}
void x52d_cfg_set_LED_T2(libx52_led_state state)
{
SET_LED_STATE(T2, state);
}
void x52d_cfg_set_LED_T3(libx52_led_state state)
{
SET_LED_STATE(T3, state);
}
void x52d_cfg_set_LED_POV(libx52_led_state state)
{
SET_LED_STATE(POV, state);
}
void x52d_cfg_set_LED_Clutch(libx52_led_state state)
{
SET_LED_STATE(CLUTCH, state);
}
#define SET_BRIGHTNESS(mfd, brightness) \
PINELOG_TRACE("Setting %s brightness to %u", mfd ? "MFD" : "LED", brightness); \
x52d_dev_set_brightness(mfd, brightness);
void x52d_cfg_set_Brightness_MFD(uint16_t brightness)
{
SET_BRIGHTNESS(1, brightness);
}
void x52d_cfg_set_Brightness_LED(uint16_t brightness)
{
SET_BRIGHTNESS(0, brightness);
}

381
daemon/x52d_main.c 100644
View File

@ -0,0 +1,381 @@
/*
* Saitek X52 Pro MFD & LED driver - Service daemon
*
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
#include "config.h"
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
#include "x52d_clock.h"
#include "x52d_const.h"
#include "x52d_config.h"
#include "x52d_device.h"
#include "x52d_io.h"
#include "x52d_mouse.h"
#include "x52d_command.h"
#include "x52d_notify.h"
#include "x52dcomm-internal.h"
#include "x52dcomm.h"
#include "pinelog.h"
static volatile int flag_quit;
static void termination_handler(int signum)
{
flag_quit = signum;
}
static volatile bool flag_reload;
static void reload_handler(int signum)
{
flag_reload = true;
}
static volatile bool flag_save_cfg;
static void save_config_handler(int signum)
{
flag_save_cfg = true;
}
static void set_log_file(bool foreground, const char *log_file)
{
int rc = 0;
if (log_file != NULL) {
rc = pinelog_set_output_file(log_file);
} else {
if (foreground) {
rc = pinelog_set_output_stream(stdout);
} else {
rc = pinelog_set_output_file(X52D_LOG_FILE);
}
}
if (rc != 0) {
fprintf(stderr, _("Error %d setting log file: %s\n"), rc, strerror(rc));
exit(EXIT_FAILURE);
}
}
static void listen_signal(int signum, void (*handler)(int))
{
struct sigaction action;
int rc;
action.sa_handler = handler;
sigemptyset(&action.sa_mask);
action.sa_flags = SA_RESTART;
rc = sigaction(signum, &action, NULL);
if (rc < 0) {
PINELOG_FATAL(_("Error %d installing handler for signal %d: %s"),
errno, signum, strerror(errno));
}
}
#if HAVE_FUNC_ATTRIBUTE_NORETURN
__attribute__((noreturn))
#endif
static void usage(int exit_code)
{
fprintf(stderr,
_("Usage: %s [-f] [-v] [-q]\n"
"\t[-l log-file] [-o override]\n"
"\t[-c config-file] [-p pid-file]\n"
"\t[-s command-socket-path]\n"
"\t[-b notify-socket-path]\n"),
X52D_APP_NAME);
exit(exit_code);
}
static void start_daemon(bool foreground, const char *pid_file)
{
pid_t pid;
FILE *pid_fd;
if (pid_file == NULL) {
pid_file = X52D_PID_FILE;
}
if (!foreground) {
/* Check if there is an existing daemon process running */
pid_fd = fopen(pid_file, "r");
if (pid_fd != NULL) {
int rc;
/* File exists, read the PID and check if it exists */
rc = fscanf(pid_fd, "%u", &pid);
fclose(pid_fd);
if (rc != 1) {
perror("fscanf");
} else {
rc = kill(pid, 0);
if (rc == 0 || (rc < 0 && errno == EPERM)) {
PINELOG_FATAL(_("Daemon is already running as PID %u"), pid);
}
}
}
/* Fork off the parent process */
pid = fork();
if (pid < 0) {
/* Error occurred during first fork */
perror("fork");
exit(EXIT_FAILURE);
} else if (pid > 0) {
/* Terminate the parent process */
exit(EXIT_SUCCESS);
}
/* Make child process a session leader */
if (setsid() < 0) {
perror("setsid");
exit(EXIT_FAILURE);
}
}
/* Initialize signal handlers. This step is the same whether in foreground
* or background mode
*/
listen_signal(SIGINT, termination_handler);
listen_signal(SIGTERM, termination_handler);
listen_signal(SIGQUIT, termination_handler);
listen_signal(SIGHUP, reload_handler);
listen_signal(SIGUSR1, save_config_handler);
if (!foreground) {
/* Fork off for the second time */
pid = fork();
if (pid < 0) {
/* Error occurred during second fork */
perror("fork");
exit(EXIT_FAILURE);
} else if (pid > 0) {
/* Terminate the parent */
exit(EXIT_SUCCESS);
}
/* Write the PID to the pid_file */
pid_fd = fopen(pid_file, "w");
if (pid_fd == NULL) {
/* Unable to open PID file */
perror("fopen");
exit(EXIT_FAILURE);
}
if (fprintf(pid_fd, "%u\n", getpid()) < 0) {
perror("fprintf");
exit(EXIT_FAILURE);
}
if (fclose(pid_fd) != 0) {
perror("fclose");
exit(EXIT_FAILURE);
}
/* Set new file permissions */
umask(0);
/* Change the working directory */
if (chdir("/")) {
/* Error changing the directory */
perror("chdir");
exit(EXIT_FAILURE);
}
/* Close all open file descriptors */
for (int x = sysconf(_SC_OPEN_MAX); x >= 0; x--) {
close(x);
}
}
}
int main(int argc, char **argv)
{
int verbosity = 0;
bool quiet = false;
bool foreground = false;
char *log_file = NULL;
char *conf_file = NULL;
const char *pid_file = NULL;
const char *command_sock = NULL;
const char *notify_sock = NULL;
int opt;
int rc;
sigset_t sigblockset;
/* Initialize gettext */
#if ENABLE_NLS
setlocale(LC_ALL, "");
bindtextdomain(PACKAGE, LOCALEDIR);
textdomain(PACKAGE);
#endif
/* Set system defaults */
pinelog_init(X52D_MOD_MAX);
pinelog_set_level(PINELOG_LVL_WARNING);
/*
* Parse command line arguments
*
* -f run in foreground
* -c path to config file
* -o option overrides
* -v verbose logging
* -q silent behavior
* -l path to log file
* -p path to PID file (only used if running in background)
* -s path to command socket
* -b path to notify socket
*/
while ((opt = getopt(argc, argv, "fvql:o:c:p:s:b:h")) != -1) {
switch (opt) {
case 'f':
foreground = true;
break;
case 'v':
if (!quiet) {
if (verbosity <= PINELOG_LVL_TRACE) {
verbosity++;
pinelog_set_level(pinelog_get_level() + 1);
}
}
break;
case 'q':
quiet = true;
pinelog_set_level(PINELOG_LVL_ERROR);
break;
case 'l':
log_file = optarg;
break;
case 'o':
if (x52d_config_save_override(optarg)) {
fprintf(stderr,
_("Unable to parse configuration override '%s'\n"),
optarg);
exit(EXIT_FAILURE);
}
break;
case 'c':
conf_file = optarg;
break;
case 'p':
pid_file = optarg;
break;
case 's':
command_sock = optarg;
break;
case 'b':
notify_sock = optarg;
break;
case 'h':
usage(EXIT_SUCCESS);
break;
default:
usage(EXIT_FAILURE);
break;
}
}
PINELOG_DEBUG(_("Foreground = %s"), foreground ? _("true") : _("false"));
PINELOG_DEBUG(_("Quiet = %s"), quiet ? _("true") : _("false"));
PINELOG_DEBUG(_("Verbosity = %d"), verbosity);
PINELOG_DEBUG(_("Log file = %s"), log_file);
PINELOG_DEBUG(_("Config file = %s"), conf_file);
PINELOG_DEBUG(_("PID file = %s"), pid_file);
PINELOG_DEBUG(_("Command socket = %s"), command_sock);
PINELOG_DEBUG(_("Notify socket = %s"), notify_sock);
start_daemon(foreground, pid_file);
set_log_file(foreground, log_file);
x52d_config_load(conf_file);
// Disable pthread signals
sigfillset(&sigblockset);
rc = pthread_sigmask(SIG_BLOCK, &sigblockset, NULL);
if (rc != 0) {
PINELOG_FATAL(_("Error %d blocking signals on child threads: %s"),
errno, strerror(errno));
}
// Start device threads
x52d_dev_init();
x52d_clock_init();
if (x52d_command_init(command_sock) < 0) {
goto cleanup;
}
x52d_notify_init(notify_sock);
#if defined(HAVE_EVDEV)
x52d_io_init();
x52d_mouse_evdev_init();
#endif
// Re-enable signals
rc = pthread_sigmask(SIG_UNBLOCK, &sigblockset, NULL);
if (rc != 0) {
PINELOG_FATAL(_("Error %d unblocking signals on child threads: %s"),
errno, strerror(errno));
}
// Apply configuration
x52d_config_apply();
flag_quit = 0;
while(!flag_quit) {
pause();
/* Check if we need to reload configuration */
if (flag_reload) {
PINELOG_INFO(_("Reloading X52 configuration"));
x52d_config_load(conf_file);
x52d_config_apply();
flag_reload = false;
}
if (flag_save_cfg) {
PINELOG_INFO(_("Saving X52 configuration to disk"));
x52d_config_save(conf_file);
flag_save_cfg = false;
}
}
PINELOG_INFO(_("Received termination signal %s"), strsignal(flag_quit));
cleanup:
// Stop device threads
x52d_clock_exit();
x52d_dev_exit();
x52d_command_exit();
x52d_notify_exit();
#if defined(HAVE_EVDEV)
x52d_mouse_evdev_exit();
x52d_io_exit();
#endif
// Remove the PID file
PINELOG_TRACE("Removing PID file %s", pid_file);
unlink(pid_file);
PINELOG_INFO(_("Shutting down X52 daemon"));
return 0;
}

View File

@ -0,0 +1,75 @@
/*
* Saitek X52 Pro MFD & LED driver - Mouse driver
*
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
#include "config.h"
#include <stdio.h>
#include <stdbool.h>
#define PINELOG_MODULE X52D_MOD_MOUSE
#include "pinelog.h"
#include "x52d_config.h"
#include "x52d_const.h"
#include "x52d_mouse.h"
// Mouse speed is the delay in microseconds between subsequent mouse reports
#define DEFAULT_MOUSE_DELAY 70000
#define MOUSE_DELAY_DELTA 5000
#define MOUSE_DELAY_MIN 10000
#define MAX_MOUSE_MULT 5
volatile int mouse_delay = DEFAULT_MOUSE_DELAY;
volatile int mouse_mult = MOUSE_MULT_FACTOR;
volatile int mouse_scroll_dir = 1;
void x52d_cfg_set_Mouse_Enabled(bool enabled)
{
PINELOG_DEBUG(_("Setting mouse enable to %s"),
enabled ? _("on") : _("off"));
#if defined HAVE_EVDEV
x52d_mouse_evdev_thread_control(enabled);
#endif
}
void x52d_cfg_set_Mouse_Speed(int speed)
{
int new_delay;
int new_mult;
int max_base_speed = (DEFAULT_MOUSE_DELAY - MOUSE_DELAY_MIN) / MOUSE_DELAY_DELTA;
int max_speed = max_base_speed + MAX_MOUSE_MULT * MOUSE_MULT_FACTOR;
if (speed < 0 || speed > max_speed) {
PINELOG_INFO(_("Ignoring mouse speed %d outside supported range (0-%d)"),
speed, max_speed);
return;
} else if (speed <= max_base_speed) {
new_delay = DEFAULT_MOUSE_DELAY - speed * MOUSE_DELAY_DELTA;
new_mult = MOUSE_MULT_FACTOR;
} else {
// speed between max_base_speed & max_speed
new_delay = MOUSE_DELAY_MIN;
new_mult = MOUSE_MULT_FACTOR + (speed - max_base_speed);
}
PINELOG_DEBUG(_("Setting mouse speed to %d (delay %d ms, multiplier %f)"),
speed, new_delay / 1000, new_mult / (double)MOUSE_MULT_FACTOR);
mouse_delay = new_delay;
mouse_mult = new_mult;
}
void x52d_cfg_set_Mouse_ReverseScroll(bool enabled)
{
PINELOG_DEBUG(_("Setting mouse reverse scroll to %s"),
enabled ? _("on") : _("off"));
if (enabled) {
mouse_scroll_dir = -1;
} else {
mouse_scroll_dir = 1;
}
}

View File

@ -0,0 +1,26 @@
/*
* Saitek X52 Pro MFD & LED driver - Mouse driver
*
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
#ifndef X52D_MOUSE_H
#define X52D_MOUSE_H
#include <stdbool.h>
#include "libx52io.h"
extern volatile int mouse_delay;
extern volatile int mouse_mult;
extern volatile int mouse_scroll_dir;
#define MOUSE_MULT_FACTOR 4
void x52d_mouse_evdev_thread_control(bool enabled);
void x52d_mouse_evdev_init(void);
void x52d_mouse_evdev_exit(void);
void x52d_mouse_report_event(libx52io_report *report);
#endif // !defined X52D_MOUSE_H

View File

@ -0,0 +1,248 @@
/*
* Saitek X52 Pro MFD & LED driver - Mouse driver
*
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
#include "config.h"
#include <stdio.h>
#include <stdbool.h>
#include <pthread.h>
#include <unistd.h>
#include "libevdev/libevdev.h"
#include "libevdev/libevdev-uinput.h"
#include "libx52io.h"
#include "pinelog.h"
#include "x52d_config.h"
#include "x52d_const.h"
#include "x52d_mouse.h"
static pthread_t mouse_thr;
static bool mouse_thr_enabled = false;
static struct libevdev_uinput *mouse_uidev;
static bool mouse_uidev_created = false;
static volatile libx52io_report old_report;
static volatile libx52io_report new_report;
static int report_button_change(int button, int index)
{
int rc = 1;
bool old_button = old_report.button[index];
bool new_button = new_report.button[index];
if (old_button != new_button) {
rc = libevdev_uinput_write_event(mouse_uidev, EV_KEY, button,
(int)new_button);
if (rc != 0) {
PINELOG_ERROR(_("Error writing mouse button event (button %d, state %d)"),
button, (int)new_button);
}
}
return rc;
}
static int report_wheel(void)
{
int rc = 1;
int wheel = 0;
bool scroll_up = new_report.button[LIBX52IO_BTN_MOUSE_SCROLL_UP];
bool scroll_dn = new_report.button[LIBX52IO_BTN_MOUSE_SCROLL_DN];
if (scroll_up) {
// Scroll up event
wheel = 1 * mouse_scroll_dir;
} else if (scroll_dn) {
// Scroll down event
wheel = -1 * mouse_scroll_dir;
}
if (wheel != 0) {
rc = libevdev_uinput_write_event(mouse_uidev, EV_REL, REL_WHEEL, wheel);
if (rc != 0) {
PINELOG_ERROR(_("Error writing mouse wheel event %d"), wheel);
}
}
return rc;
}
static int report_axis(int axis, int index)
{
int rc = 1;
int axis_val = new_report.axis[index];
/*
* Axis value ranges from 0 to 15, with the default midpoint at 8.
* We need to translate this to a range of -7 to +7. Since the midpoint
* is slightly off-center, we will shift the values left, and subtract
* 15, effectively, giving us a range of -15 to +15. Shifting right again
* will reduce the range to -7 to +7, and effectively ignore the reported
* values of 7 and 8.
*/
axis_val = ((axis_val << 1) - 15) >> 1;
/*
* Factor in the multiplicative factor for the axis. This deliberately
* uses integer division, since the uinput event only accepts integers.
* For the speed purposes, this should be good enough.
*/
axis_val = (axis_val * mouse_mult) / MOUSE_MULT_FACTOR;
if (axis_val) {
rc = libevdev_uinput_write_event(mouse_uidev, EV_REL, axis, axis_val);
if (rc != 0) {
PINELOG_ERROR(_("Error writing mouse axis event (axis %d, value %d)"),
axis, axis_val);
}
}
return rc;
}
static void report_sync(void)
{
int rc;
rc = libevdev_uinput_write_event(mouse_uidev, EV_SYN, SYN_REPORT, 0);
if (rc != 0) {
PINELOG_ERROR(_("Error writing mouse sync event"));
} else {
memcpy((void *)&old_report, (void *)&new_report, sizeof(old_report));
}
}
static void reset_reports(void)
{
memset((void *)&old_report, 0, sizeof(old_report));
/* Set the default thumbstick values to the mid-point */
old_report.axis[LIBX52IO_AXIS_THUMBX] = 8;
old_report.axis[LIBX52IO_AXIS_THUMBY] = 8;
memcpy((void *)&new_report, (void *)&old_report, sizeof(new_report));
}
static void * x52_mouse_thr(void *param)
{
bool state_changed;
PINELOG_INFO(_("Starting X52 virtual mouse driver thread"));
for (;;) {
state_changed = false;
state_changed |= (0 == report_axis(REL_X, LIBX52IO_AXIS_THUMBX));
state_changed |= (0 == report_axis(REL_Y, LIBX52IO_AXIS_THUMBY));
if (state_changed) {
report_sync();
}
usleep(mouse_delay);
}
return NULL;
}
static void x52d_mouse_thr_init(void)
{
int rc;
PINELOG_TRACE("Initializing virtual mouse driver");
rc = pthread_create(&mouse_thr, NULL, x52_mouse_thr, NULL);
if (rc != 0) {
PINELOG_FATAL(_("Error %d initializing mouse thread: %s"),
rc, strerror(rc));
}
}
static void x52d_mouse_thr_exit(void)
{
PINELOG_INFO(_("Shutting down X52 virtual mouse driver thread"));
pthread_cancel(mouse_thr);
}
void x52d_mouse_evdev_thread_control(bool enabled)
{
if (!mouse_uidev_created) {
PINELOG_INFO(_("Virtual mouse not created. Ignoring thread state change"));
return;
}
if (enabled) {
if (mouse_thr_enabled) {
PINELOG_TRACE("Ignoring re-enable mouse thread");
return;
} else {
reset_reports();
x52d_mouse_thr_init();
}
} else {
if (!mouse_thr_enabled) {
PINELOG_TRACE("Ignoring re-disable mouse thread");
return;
} else {
x52d_mouse_thr_exit();
}
}
mouse_thr_enabled = enabled;
}
void x52d_mouse_report_event(libx52io_report *report)
{
bool state_changed;
if (report) {
memcpy((void *)&new_report, report, sizeof(new_report));
if (!mouse_uidev_created || !mouse_thr_enabled) {
return;
}
state_changed = false;
state_changed |= (0 == report_button_change(BTN_LEFT, LIBX52IO_BTN_MOUSE_PRIMARY));
state_changed |= (0 == report_button_change(BTN_RIGHT, LIBX52IO_BTN_MOUSE_SECONDARY));
state_changed |= (0 == report_wheel());
if (state_changed) {
report_sync();
}
} else {
reset_reports();
}
}
void x52d_mouse_evdev_init(void)
{
int rc;
struct libevdev *dev;
/* Create a new mouse device */
dev = libevdev_new();
libevdev_set_name(dev, "X52 virtual mouse");
libevdev_enable_event_type(dev, EV_REL);
libevdev_enable_event_code(dev, EV_REL, REL_X, NULL);
libevdev_enable_event_code(dev, EV_REL, REL_Y, NULL);
libevdev_enable_event_code(dev, EV_REL, REL_WHEEL, NULL);
libevdev_enable_event_type(dev, EV_KEY);
libevdev_enable_event_code(dev, EV_KEY, BTN_LEFT, NULL);
libevdev_enable_event_code(dev, EV_KEY, BTN_RIGHT, NULL);
rc = libevdev_uinput_create_from_device(dev, LIBEVDEV_UINPUT_OPEN_MANAGED,
&mouse_uidev);
if (rc != 0) {
PINELOG_ERROR(_("Error %d creating X52 virtual mouse: %s"),
-rc, strerror(-rc));
} else {
mouse_uidev_created = true;
}
}
void x52d_mouse_evdev_exit(void)
{
x52d_mouse_evdev_thread_control(false);
mouse_uidev_created = false;
libevdev_uinput_destroy(mouse_uidev);
}

View File

@ -0,0 +1,147 @@
/*
* Saitek X52 Pro MFD & LED driver - Mouse driver test harness
*
* Copyright (C) 2022 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
#include "config.h"
#include <stdio.h>
#include <stdbool.h>
#include <stddef.h>
#include <setjmp.h>
#include <cmocka.h>
#define PINELOG_MODULE X52D_MOD_MOUSE
#include "pinelog.h"
#include "x52d_config.h"
#include "x52d_const.h"
#include "x52d_mouse.h"
#if defined HAVE_EVDEV
/* Stub for evdev */
void x52d_mouse_evdev_thread_control(bool enabled)
{
function_called();
check_expected(enabled);
}
#endif
static void test_mouse_thread_enabled(void **state)
{
#if defined HAVE_EVDEV
expect_function_calls(x52d_mouse_evdev_thread_control, 1);
expect_value(x52d_mouse_evdev_thread_control, enabled, true);
#endif
x52d_cfg_set_Mouse_Enabled(true);
}
static void test_mouse_thread_disabled(void **state)
{
#if defined HAVE_EVDEV
expect_function_calls(x52d_mouse_evdev_thread_control, 1);
expect_value(x52d_mouse_evdev_thread_control, enabled, false);
#endif
x52d_cfg_set_Mouse_Enabled(false);
}
static void test_mouse_speed_negative(void **state)
{
int orig_mouse_delay = mouse_delay;
int orig_mouse_mult = mouse_mult;
x52d_cfg_set_Mouse_Speed(-1);
assert_int_equal(mouse_delay, orig_mouse_delay);
assert_int_equal(mouse_mult, orig_mouse_mult);
}
/* The following tests are dependent on the values in x52d_mouse.c */
static void test_mouse_speed_0(void **state)
{
x52d_cfg_set_Mouse_Speed(0);
assert_int_equal(mouse_delay, 70000);
assert_int_equal(mouse_mult, 4);
}
static void test_mouse_speed_mid_base(void **state)
{
x52d_cfg_set_Mouse_Speed(6);
assert_int_equal(mouse_delay, 40000);
assert_int_equal(mouse_mult, 4);
}
static void test_mouse_speed_max_base(void **state)
{
x52d_cfg_set_Mouse_Speed(12);
assert_int_equal(mouse_delay, 10000);
assert_int_equal(mouse_mult, 4);
}
static void test_mouse_speed_min_hyper(void **state)
{
x52d_cfg_set_Mouse_Speed(13);
assert_int_equal(mouse_delay, 10000);
assert_int_equal(mouse_mult, 5);
}
static void test_mouse_speed_mid_hyper(void **state)
{
x52d_cfg_set_Mouse_Speed(22);
assert_int_equal(mouse_delay, 10000);
assert_int_equal(mouse_mult, 14);
}
static void test_mouse_speed_max_hyper(void **state)
{
x52d_cfg_set_Mouse_Speed(32);
assert_int_equal(mouse_delay, 10000);
assert_int_equal(mouse_mult, 24);
}
static void test_mouse_speed_above_max(void **state)
{
int orig_mouse_delay = mouse_delay;
int orig_mouse_mult = mouse_mult;
x52d_cfg_set_Mouse_Speed(33);
assert_int_equal(mouse_delay, orig_mouse_delay);
assert_int_equal(mouse_mult, orig_mouse_mult);
}
static void test_mouse_reverse_scroll_enabled(void **state)
{
x52d_cfg_set_Mouse_ReverseScroll(true);
assert_int_equal(mouse_scroll_dir, -1);
}
static void test_mouse_reverse_scroll_disabled(void **state)
{
x52d_cfg_set_Mouse_ReverseScroll(false);
assert_int_equal(mouse_scroll_dir, 1);
}
const struct CMUnitTest tests[] = {
cmocka_unit_test(test_mouse_thread_enabled),
cmocka_unit_test(test_mouse_thread_disabled),
cmocka_unit_test(test_mouse_speed_negative),
cmocka_unit_test(test_mouse_speed_0),
cmocka_unit_test(test_mouse_speed_mid_base),
cmocka_unit_test(test_mouse_speed_max_base),
cmocka_unit_test(test_mouse_speed_min_hyper),
cmocka_unit_test(test_mouse_speed_mid_hyper),
cmocka_unit_test(test_mouse_speed_max_hyper),
cmocka_unit_test(test_mouse_speed_above_max),
cmocka_unit_test(test_mouse_reverse_scroll_enabled),
cmocka_unit_test(test_mouse_reverse_scroll_disabled),
};
int main(void)
{
cmocka_set_message_output(CM_OUTPUT_TAP);
cmocka_run_group_tests(tests, NULL, NULL);
return 0;
}

View File

@ -0,0 +1,213 @@
/*
* Saitek X52 Pro MFD & LED driver - Notification manager
*
* Copyright (C) 2022 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
#include <stdint.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>
#define PINELOG_MODULE X52D_MOD_NOTIFY
#include "pinelog.h"
#include "x52d_const.h"
#include "x52d_notify.h"
#include "x52d_client.h"
#include "x52dcomm.h"
#include "x52dcomm-internal.h"
static pthread_t notify_thr;
static pthread_t notify_listen;
static pthread_mutex_t notify_mutex = PTHREAD_MUTEX_INITIALIZER;
static int notify_pipe[2];
static int notify_sock;
static int client_fd[X52D_MAX_CLIENTS];
/* Bind and listen to the notify socket */
static int listen_notify(const char *notify_sock_path)
{
int sock_fd;
int len;
struct sockaddr_un local;
len = x52d_setup_notify_sock(notify_sock_path, &local);
if (len < 0) {
return -1;
}
sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock_fd < 0) {
/* Failure creating the socket. Abort early */
PINELOG_ERROR(_("Error creating notification socket: %s"), strerror(errno));
return -1;
}
if (x52d_set_socket_nonblocking(sock_fd) < 0) {
PINELOG_ERROR(_("Error marking notification socket as nonblocking: %s"), strerror(errno));
return -1;
}
if (x52d_listen_socket(&local, len, sock_fd) < 0) {
PINELOG_ERROR(_("Error listening on notification socket: %s"), strerror(errno));
goto listen_failure;
}
return sock_fd;
listen_failure:
unlink(local.sun_path);
close(sock_fd);
PINELOG_FATAL(_("Error setting up notification socket"));
return -1;
}
static void * x52_notify_thr(void * param)
{
char buffer[X52D_BUFSZ];
uint16_t bufsiz;
int rc;
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));
}
}
if (rc < 0) {
// 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));
}
}
if (rc < 0) {
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;
}
}
}
}
return NULL;
}
void x52d_notify_send(int argc, const char **argv)
{
char buffer[X52D_BUFSZ + sizeof(uint16_t)];
uint16_t bufsiz;
uint16_t written;
int rc;
bufsiz = (uint16_t)x52d_format_command(argc, argv, buffer + sizeof(uint16_t), X52D_BUFSZ);
memcpy(buffer, &bufsiz, sizeof(bufsiz));
pthread_mutex_lock(&notify_mutex);
written = 0;
while (written < bufsiz) {
rc = write(notify_pipe[1], buffer + written, bufsiz - written);
if (rc < 0) {
if (errno == EINTR) {
continue;
}
PINELOG_ERROR(_("Error %d writing notification pipe: %s"),
errno, strerror(errno));
} else {
written += rc;
}
}
pthread_mutex_unlock(&notify_mutex);
}
static void client_handler(int fd)
{
char buffer[X52D_BUFSZ] = { 0 };
int rc;
rc = recv(fd, buffer, sizeof(buffer), 0);
PINELOG_TRACE("Received and discarded %d bytes from notification client %d", rc, fd);
}
static void * x52_notify_loop(void * param)
{
struct pollfd pfd[MAX_CONN];
int rc;
for (;;) {
rc = x52d_client_poll(client_fd, pfd, notify_sock);
if (rc <= 0) {
continue;
}
x52d_client_handle(client_fd, pfd, notify_sock, client_handler);
}
return NULL;
}
void x52d_notify_init(const char *notify_sock_path)
{
int rc;
PINELOG_TRACE("Initializing notification manager");
x52d_client_init(client_fd);
PINELOG_TRACE("Creating notifications pipe");
rc = pipe(notify_pipe);
if (rc != 0) {
PINELOG_FATAL(_("Error %d creating notification pipe: %s"),
errno, strerror(errno));
}
PINELOG_TRACE("Opening notification listener socket");
notify_sock = listen_notify(notify_sock_path);
rc = pthread_create(&notify_thr, NULL, x52_notify_thr, NULL);
if (rc != 0) {
PINELOG_FATAL(_("Error %d initializing notify thread: %s"),
rc, strerror(rc));
}
rc = pthread_create(&notify_listen, NULL, x52_notify_loop, NULL);
if (rc != 0) {
PINELOG_FATAL(_("Error %d initializing notify listener: %s"),
rc, strerror(rc));
}
}
void x52d_notify_exit(void)
{
close(notify_pipe[0]);
close(notify_pipe[1]);
close(notify_sock);
pthread_cancel(notify_thr);
pthread_cancel(notify_listen);
}

View File

@ -0,0 +1,22 @@
/*
* Saitek X52 Pro MFD & LED driver - Notification manager
*
* Copyright (C) 2022 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
#ifndef X52D_NOTIFY_H
#define X52D_NOTIFY_H
void x52d_notify_init(const char *notify_sock_path);
void x52d_notify_exit(void);
void x52d_notify_send(int argc, const char **argv);
#define X52D_NOTIFY(...) do { \
const char *argv ## __LINE__ [] = {__VA_ARGS__}; \
x52d_notify_send(sizeof(argv ## __LINE__ )/sizeof(argv ## __LINE__ [0]), argv ## __LINE__ ); \
} while(0)
#endif // !defined X52D_NOTIFY_H

View File

@ -0,0 +1,26 @@
/*
* Saitek X52 Pro MFD & LED driver - communication library interal functions
*
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
#ifndef X52DCOMM_INTERNAL_H
#define X52DCOMM_INTERNAL_H
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#define X52D_BUFSZ 1024
const char *x52d_command_sock_path(const char *sock_path);
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

158
daemon/x52dcomm.h 100644
View File

@ -0,0 +1,158 @@
/*
* Saitek X52 Pro MFD & LED driver
*
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
/**
* @file x52dcomm.h
* @brief Functions, structures and enumerations for the Saitek X52 MFD & LED
* daemon communication library.
*
* This file contains the type, enum and function prototypes for the Saitek X52
* daemon communication library. These functions allow a client application to
* communicate with a running X52 daemon, execute commands and retrieve data.
*
* @author Nirenjan Krishnan (nirenjan@nirenjan.org)
*/
#ifndef X52DCOMM_H
#define X52DCOMM_H
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @defgroup x52dcomm Daemon communication
*
* These functions are used to communicate with the X52 daemon.
*
* @{
*/
/**
* @brief Open a connection to the daemon command socket.
*
* This method opens a socket connection to the daemon command socket. This
* socket allows the client to issue commands and retrieve data. The \p sock_path
* parameter may be NULL, in which case, it will use the default socket path.
*
* The client will need to use the returned descriptor to communicate with the
* daemon using \ref x52d_send_command. Once finished, the client may use the
* \c close(2) method to close the file descriptor.
*
* @param[in] sock_path Path to the daemon command socket.
*
* @returns Non-negative socket file descriptor on success.
* @returns -1 on failure, and set \c errno accordingly.
*
* @exception E2BIG returned if the passed socket path is too big
*/
int x52d_dial_command(const char *sock_path);
/**
* @brief Open a connection to the daemon notify socket.
*
* This method opens a socket connection to the daemon notify socket. This
* socket allows the client to receive notifications from the daemon. Thej
* \p sock_path parameter may be NULL, in which case, it will use the default
* socket path.
*
* The client will need to use the returned descriptor to communicate with the
* daemon using \ref x52d_recv_notification. Once finished, the client may use
* the \c close(2) method to close the file descriptor.
*
* @param[in] sock_path Path to the daemon command socket.
*
* @returns Non-negative socket file descriptor on success.
* @returns -1 on failure, and set \c errno accordingly.
*
* @exception E2BIG returned if the passed socket path is too big
*/
int x52d_dial_notify(const char *sock_path);
/**
* @brief Format a series of command strings into a buffer
*
* The client sends the command and parameters as a series of NUL terminated
* strings. This function concatenates the commands into a single buffer that
* can be passed to \ref x52d_send_command.
*
* \p buffer should be at least 1024 bytes long.
*
* @param[in] argc Number of arguments to fit in the buffer
* @param[in] argv Pointer to an array of arguments.
* @param[out] buffer Buffer to store the formatted command
* @param[in] buflen Length of the buffer
*
* @returns number of bytes in the formatted command
* @returns -1 on an error condition, and \c errno is set accordingly.
*/
int x52d_format_command(int argc, const char **argv, char *buffer, size_t buflen);
/**
* @brief Send a command to the daemon and retrieve the response.
*
* The client sends the command and parameters as a series of NUL terminated
* strings, and retrieves the response in the same manner. Depending on the
* result, the return status is either a positive integer or -1, and \c errno
* is set accordingly.
*
* \p buffer should contain sufficient space to accomodate the returned
* response string.
*
* This is a blocking function and will not return until either a response is
* received from the server, or an exception condition occurs.
*
* @param[in] sock_fd Socket descriptor returned from
* \ref x52d_dial_command
*
* @param[inout] buffer Pointer to the string containing the command and
* parameters. This is also used to save the returned
* response.
*
* @param[in] bufin Length of the command in the input buffer
* @param[in] bufout Maximum length of the response
*
* @returns number of bytes returned from the server
* @returns -1 on an error condition, and \c errno is set accordingly.
*/
int x52d_send_command(int sock_fd, char *buffer, size_t bufin, size_t bufout);
/**
* @brief Notification callback function type
*/
typedef int (* x52d_notify_callback_fn)(int argc, char **argv);
/**
* @brief Receive a notification from the daemon
*
* This function blocks until it receives a notification from the daemon. Once
* it receives a notification successfully, it will call the callback function
* with the arguments as string pointers. It will return the return value of
* the callback function, if it was called.
*
* This is a blocking function and will not return until either a notification
* is received from the server, or an exception condition occurs.
*
* @param[in] sock_fd Socket descriptor returned from
* \ref x52d_dial_notify
*
* @param[in] callback Pointer to the callback function
*
* @returns return code of the callback function on success
* @returns -1 on an error condition, and \c errno is set accordingly.
*/
int x52d_recv_notification(int sock_fd, x52d_notify_callback_fn callback);
/** @} */
#ifdef __cplusplus
}
#endif
#endif // !defined X52DCOMM_H

10
docs/Makefile.am 100644
View File

@ -0,0 +1,10 @@
# Automake for documentation
#
# Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
#
# SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
EXTRA_DIST += \
docs/main.dox \
docs/caveats.dox \
docs/integration.dox

21
docs/caveats.dox 100644
View File

@ -0,0 +1,21 @@
/**
@page libx52_caveats Caveats
@section general General Caveats
libx52 uses [libusb](https://libusb.info) under the hood. Therefore, any
[caveats](http://libusb.sourceforge.net/api-1.0/libusb_caveats.html) that apply
to libusb also apply to libx52.
@section threads Thread Safety
libx52 is not designed to be thread-safe. The application must ensure that
calls to libx52 from multiple threads are protected by a semaphore/mutex.
@section leds LED Support
The Saitek X52 does not support controlling the color of individual LEDs. This
is only supported on the X52 Pro. The application can use \ref
libx52_check_feature to verify support.
*/

View File

@ -0,0 +1,35 @@
LibUSB X52 stub library
=======================
The libusbx52 stub library is a convenience library to help test the libusb
functions used by libx52 and associated code. It simulates the behavior of the
libusb functions used by the libx52 library, but doesn't actually control any
real hardware, and simply updates a few in-memory data structures.
The idea behind `libusbx52.so` is to use it as an LD_PRELOAD library, where it
will override the real functions used by `libx52.so`. The use case for this
scenario is in an automated testing environment, where a test runner could set
up the list of devices manually and simulate various scenarios.
# Design Overview
Unfortunately, the automake infrastructure does not support the use of
LD_PRELOAD because it is deemed "non-portable" in the automake sense. As a
result, this is now up to a test runner application to implement a method to
control the data passed between two processes.
# Data Structures
The server process is responsible for setting up the initial set of USB devices.
As far as libx52 is concerned, the only fields that it uses in the USB
descriptor are the idVendor and idProduct fields. These are written to a file
that is read by the libusbx52 stubs to populate the device list.
Once the file has been written by the server, the libusb_init stub function in
the client can read the file and populate the internal data structures as
necessary.
The client can also write to a separate file to record the USB communication
sent across to the simulated device.

View File

@ -0,0 +1,77 @@
/**
@page libx52_integration Integration
libx52 performs all its operations with a device context pointer. The
application must call \ref libx52_init before performing any operation, and
must call \ref libx52_exit prior to terminating.
Also, the application must call \ref libx52_connect to ensure that it connects
to a supported joystick. This function must be called after \ref libx52_init
<b>Example Initialization/Deinitialization Code</b>
@code{.c}
#include <libx52.h>
libx52_device* init_libx52(void)
{
libx52_device *dev;
int rc;
// Initialize the libx52 library
rc = libx52_init(&dev);
if (rc != LIBX52_SUCCESS) {
fputs(libx52_strerror(rc), stderr);
return NULL;
}
// Connect to the supported joystick
rc = libx52_connect(dev);
if (rc != LIBX52_SUCCESS) {
fputs(libx52_strerror(rc), stderr);
// A failure usually just means that there is no joystick connected
// Look at the return codes for more information.
}
return dev;
}
// Close the library and any associated devices
libx52_exit(dev);
@endcode
# Joystick Updates
Most libx52 functions to update the joystick state do not directly write to the
connected joystick, but only update internal data structures within the device
context. In order to actually update the joystick, the application must call
\ref libx52_update. This function writes the updates to the joystick and resets
any internal state.
\b Example
@code{.c}
libx52_set_text(dev, 0, " Saitek ", 16);
libx52_set_text(dev, 1, " X52 Flight ", 16);
libx52_set_text(dev, 2, " Control System ", 16);
libx52_update(dev);
@endcode
# Error handling
Most libx52 functions return a standard \ref libx52_error_code integer value
that indicates the status of the operation. As long as the operation succeeded,
the function will return \ref LIBX52_SUCCESS. Other values returned indicate a
failure of some sort.
\ref libx52_strerror can convert the return code into a descriptive string that
may be displayed to users.
## Internationalization of error strings
\ref libx52_strerror automatically handles internationalization. As long as your
application sets up the locale correctly, and the error strings have been
translated to that locale, the returned strings will correspond to the translated
values for your locale.
*/

52
docs/main.dox 100644
View File

@ -0,0 +1,52 @@
/**
@mainpage Saitek X52/X52Pro drivers for Linux/Unix
@section intro Introduction
libx52 is an open source library that allows you to communicate with a
Saitek X52 or Saitek X52Pro joystick on Linux and Unix machines. Saitek
only provides Windows drivers for their joysticks, necessitating the need
for this project for users to be able to control the LEDs and MFD text on
a Linux or Unix device.
@section start Getting Started
@subsection install Installation
libx52 is available on Ubuntu as a PPA. Follow the instructions below to
install on Ubuntu:
@code{.unparsed}
sudo add-apt-repository nirenjan:libx52
sudo apt update
sudo apt install -y libx52-1
@endcode
@subsection building Building from source
Please follow the instructions in [INSTALL.md](https://github.com/nirenjan/libx52/blob/master/INSTALL.md) specific to your system to build from source.
@section features Features
libx52 supports setting the following parameters on the joystick
1. Text on the multifunction display (MFD).
2. All 3 clocks on the MFD.
4. Date display on the MFD.
5. Blinking of the POV hat and clutch LEDs (both blinking or not).
6. Shift indicator on the MFD.
7. Setting the color of the individual LEDs (*X52 Pro only*).
@section api Application Programming Interface
See the @ref libx52_integration page for details on how to integrate libx52
with your application.
See the documentation for the following files for a complete list of all
functions.
- libx52.h
- libx52io.h
- libx52util.h
*/

View File

@ -0,0 +1,110 @@
Saitek X52 Controller Map
=========================
This page documents the HID Report format and how to decode the report to map
to axis/buttons for the Saitek X52.
> **Note:** I am not sure that the button indices match with real hardware. The
> indices are guessed by comparing the Test screen printed in the manuals for
> both the X52 and X52 Pro, and my own
> [documentation](x52pro_controller_map.md). I also looked at the [source code
> of this project](https://github.com/xythobuz/Saitek-X52-PPM) to figure out
> possible mappings. Given that the above project only uses PID 0x0255, it is
> entirely possible that the report format for the X52 with PID 0x075c is
> different.
Axis
====
* 0 - X axis
* 1 - Y axis
* 2 - Twist axis
* 3 - Throttle axis
* 4 - Clutch button rotary (rotary X)
* 5 - E button rotary (rotary Y)
* 6 - Slider
Buttons
=======
* 0 - Trigger primary
* 1 - FIRE/LAUNCH
* 2 - A
* 3 - B
* 4 - C
* 5 - SHIFT
* 6 - D
* 7 - E
* 8 - T1 Up
* 9 - T1 Down
* 10 - T2 Up
* 11 - T2 Down
* 12 - T3 Up
* 13 - T3 Down
* 14 - Trigger secondary
* 15 - Stick 4-way N
* 16 - Stick 4-way E
* 17 - Stick 4-way S
* 18 - Stick 4-way W
* 19 - Throttle 4-way N (pull up)
* 20 - Throttle 4-way E
* 21 - Throttle 4-way S (push down)
* 22 - Throttle 4-way W
* 23 - MODE 1
* 24 - MODE 2
* 25 - MODE 3
* 26 - FUNCTION
* 27 - START/STOP/Down
* 28 - RESET/Up
* 29 - CLUTCH (i)
* 30 - Mouse button - primary
* 31 - Mouse button - secondary
* 32 - Mouse wheel down
* 33 - Mouse wheel up
The function button toggles between the clock and stopwatch on
the MFD. If in stopwatch mode, the start/stop button starts or
stops the stopwatch. If already stopped, the start/stop button
will restart the stopwatch. The reset button will stop the
stopwatch, if running, and also reset the display to 00:00.
If the MFD is in clock mode, then the start/stop button cycles
backwards through the three on-board clock displays, while the
reset button cycles forward through the displays.
If the primary clock has not been set using the vendor API,
then the MFD will not display anything in clock mode. The
start/stop and reset buttons will also not change anything
on the display. Stopwatch mode is not affected by this.
Refer to the vendor API documentation for details on how to
configure the MFD display.
USB Report Format
=================
The X52 reports 14 bytes of data everytime a joystick event occurs.
The 15 bytes are laid out in little endian format as shown below:
* 11-bits of X axis position
* 11-bits of Y axis position
* 10-bits of twist axis position
* 8 bits of throttle position
* 8 bits of rotary X position
* 8 bits of rotary Y position
* 8 bits of slider position
* 34 bits of button information
* 1 bits of padding
* 4 bits of hat position
* 4 bits of thumbstick X position
* 4 bits of thumbstick Y position
A report would look like the following:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| X axis data | Y axis data | Rz axis data |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Throttle | Rx axis data | Ry axis data | Slider data |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Buttons 7-0 | Buttons 15-8 | Buttons 23-16 | Buttons 31-24 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Hat |///|Btn| MouseY| MouseX|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

View File

@ -0,0 +1,60 @@
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
0x09, 0x04, // Usage (Joystick)
0xA1, 0x01, // Collection (Application)
0x09, 0x01, // Usage (Pointer)
0xA1, 0x00, // Collection (Physical)
0x09, 0x30, // Usage (X)
0x09, 0x31, // Usage (Y)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x07, // Logical Maximum (2047)
0x75, 0x0B, // Report Size (11)
0x95, 0x02, // Report Count (2)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x09, 0x35, // Usage (Rz)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x03, // Logical Maximum (1023)
0x75, 0x0A, // Report Size (10)
0x95, 0x01, // Report Count (1)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x09, 0x32, // Usage (Z)
0x09, 0x33, // Usage (Rx)
0x09, 0x34, // Usage (Ry)
0x09, 0x36, // Usage (Slider)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x00, // Logical Maximum (255)
0x75, 0x08, // Report Size (8)
0x95, 0x04, // Report Count (4)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x09, // Usage Page (Button)
0x19, 0x01, // Usage Minimum (0x01)
0x29, 0x22, // Usage Maximum (0x22)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x95, 0x22, // Report Count (34)
0x75, 0x01, // Report Size (1)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x75, 0x02, // Report Size (2)
0x95, 0x01, // Report Count (1)
0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
0x09, 0x39, // Usage (Hat switch)
0x15, 0x01, // Logical Minimum (1)
0x25, 0x08, // Logical Maximum (8)
0x35, 0x00, // Physical Minimum (0)
0x46, 0x3B, 0x01, // Physical Maximum (315)
0x66, 0x14, 0x00, // Unit (System: English Rotation, Length: Centimeter)
0x75, 0x04, // Report Size (4)
0x95, 0x01, // Report Count (1)
0x81, 0x42, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State)
0x05, 0x05, // Usage Page (Game Ctrls)
0x09, 0x24, // Usage (Move Right/Left)
0x09, 0x26, // Usage (Move Up/Down)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x0F, // Logical Maximum (15)
0x75, 0x04, // Report Size (4)
0x95, 0x02, // Report Count (2)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0, // End Collection
0xC0, // End Collection
// 119 bytes

View File

@ -97,5 +97,5 @@ A report would look like the following:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Buttons 7-0 | Buttons 15-8 | Buttons 23-16 | Buttons 31-24 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|/| Btns 38-32 | Hat |///////| MouseX| MouseY|
|/| Btns 38-32 | Hat |///////| MouseY| MouseX|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

View File

@ -0,0 +1,64 @@
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
0x09, 0x04, // Usage (Joystick)
0xA1, 0x01, // Collection (Application)
0x09, 0x01, // Usage (Pointer)
0xA1, 0x00, // Collection (Physical)
0x09, 0x30, // Usage (X)
0x09, 0x31, // Usage (Y)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x03, // Logical Maximum (1023)
0x75, 0x0A, // Report Size (10)
0x95, 0x02, // Report Count (2)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x75, 0x02, // Report Size (2)
0x95, 0x01, // Report Count (1)
0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x09, 0x35, // Usage (Rz)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x03, // Logical Maximum (1023)
0x75, 0x0A, // Report Size (10)
0x95, 0x01, // Report Count (1)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x09, 0x32, // Usage (Z)
0x09, 0x33, // Usage (Rx)
0x09, 0x34, // Usage (Ry)
0x09, 0x36, // Usage (Slider)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x00, // Logical Maximum (255)
0x75, 0x08, // Report Size (8)
0x95, 0x04, // Report Count (4)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x09, // Usage Page (Button)
0x19, 0x01, // Usage Minimum (0x01)
0x29, 0x27, // Usage Maximum (0x27)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x95, 0x27, // Report Count (39)
0x75, 0x01, // Report Size (1)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x75, 0x05, // Report Size (5)
0x95, 0x01, // Report Count (1)
0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
0x09, 0x39, // Usage (Hat switch)
0x15, 0x01, // Logical Minimum (1)
0x25, 0x08, // Logical Maximum (8)
0x35, 0x00, // Physical Minimum (0)
0x46, 0x3B, 0x01, // Physical Maximum (315)
0x66, 0x14, 0x00, // Unit (System: English Rotation, Length: Centimeter)
0x75, 0x04, // Report Size (4)
0x95, 0x01, // Report Count (1)
0x81, 0x42, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State)
0x05, 0x05, // Usage Page (Game Ctrls)
0x09, 0x24, // Usage (Move Right/Left)
0x09, 0x26, // Usage (Move Up/Down)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x0F, // Logical Maximum (15)
0x75, 0x04, // Report Size (4)
0x95, 0x02, // Report Count (2)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0, // End Collection
0xC0, // End Collection
// 125 bytes

View File

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 104 KiB

13
evtest/Makefile.am 100644
View File

@ -0,0 +1,13 @@
# Automake for x52evtest
#
# Copyright (C) 2012-2020 Nirenjan Krishnan (nirenjan@nirenjan.org)
#
# SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
bin_PROGRAMS += x52evtest
# Event test utility that works similarly to the Linux evtest
x52evtest_SOURCES = evtest/ev_test.c
x52evtest_CFLAGS = -I $(top_srcdir)/libx52io -I $(top_srcdir) -DLOCALEDIR=\"$(localedir)\" $(WARN_CFLAGS)
x52evtest_LDFLAGS = $(WARN_LDFLAGS)
x52evtest_LDADD = libx52io.la

182
evtest/ev_test.c 100644
View File

@ -0,0 +1,182 @@
/*
* Saitek X52 Pro MFD & LED driver - Event test utility
*
* Copyright (C) 2012-2020 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
#include "config.h"
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <sys/time.h>
#include "libx52io.h"
#include "gettext.h"
/*
Output Format
=============
Driver version is 0.0.0
Device ID: bus 0x3 vendor 0x06a3 product 0x0762 version 0x110
Device name: "Saitek X52 Pro Flight Control System"
Testing ... (interrupt to exit)
Event @ 1594431236.817842, ABS_X, value 512
Event @ 1594431236.817842, ABS_Y, value 511
Event @ 1594431236.817842, BTN_TRIGGER, value 1
Event @ 1594431236.817842, BTN_MODE_1, value 1
Event @ 1594431236.847810, BTN_MODE_1, value 0
Event @ 1594431236.877802, BTN_MODE_2, value 1
*/
static bool exit_loop = false;
static void signal_handler(int sig)
{
exit_loop = true;
}
/* Denoising - reduce event noise due to adjacent values being reported */
static bool denoise = true;
/* For i18n */
#define _(x) gettext(x)
int main(int argc, char **argv)
{
libx52io_context *ctx;
libx52io_report last, curr;
int32_t denoise_mask[LIBX52IO_AXIS_MAX] = { 0 };
int rc;
#define CHECK_RC() do { \
if (rc != LIBX52IO_SUCCESS) { \
fprintf(stderr, "%s\n", libx52io_strerror(rc)); \
return rc; \
} \
} while(0)
/* Initialize gettext */
#if ENABLE_NLS
setlocale(LC_ALL, "");
bindtextdomain(PACKAGE, LOCALEDIR);
textdomain(LOCALEDIR);
#endif
memset(&last, 0, sizeof(last));
memset(&curr, 0, sizeof(curr));
/* Initialize libx52io */
rc = libx52io_init(&ctx);
CHECK_RC();
/* Make sure that we have a device to connect to */
rc = libx52io_open(ctx);
CHECK_RC();
/* Initialize denoising */
if (denoise) {
for (int i = LIBX52IO_AXIS_X; i < LIBX52IO_AXIS_MAX; i++) {
int32_t min, max;
rc = libx52io_get_axis_range(ctx, i, &min, &max);
CHECK_RC();
/*
* Denoising algorithm ignores the last few bits of the axis,
* and is based on the maximum value of the axis. The mask is
* ~(max >> 6) which will do nothing for the axis with a small
* range, but reduce the noise on those with a larger range.
*/
denoise_mask[i] = ~(max >> 6);
}
}
/* Set up the signal handler to terminate the loop on SIGTERM or SIGINT */
exit_loop = false;
signal(SIGTERM, signal_handler);
signal(SIGINT, signal_handler);
/* Print the driver version and the connected device */
printf(_("Device ID: vendor 0x%04x product 0x%04x version 0x%04x\n"),
libx52io_get_vendor_id(ctx),
libx52io_get_product_id(ctx),
libx52io_get_device_version(ctx));
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));
puts(_("Testing (interrupt to exit)\n"));
/* Wait until we get an event */
while (!exit_loop) {
struct timeval tv;
bool printed = false;
/* Wait for 1 second before timing out */
rc = libx52io_read_timeout(ctx, &curr, 1000);
if (rc == LIBX52IO_ERROR_TIMEOUT) {
continue;
} else if (rc != LIBX52IO_SUCCESS) {
/* Some other error while reading. Abort the loop */
break;
}
/*
* Successful read, compare the current report against the previous
* one and display the result
*/
if (memcmp(&last, &curr, sizeof(curr)) == 0) {
/* No change, ignore the output */
continue;
}
/* Get the current timeval - we don't need a timezone */
gettimeofday(&tv, NULL);
for (int axis = 0; axis < LIBX52IO_AXIS_MAX; axis++) {
if (last.axis[axis] != curr.axis[axis]) {
/* Account for denoising */
if (denoise) {
int32_t last_v = last.axis[axis] & denoise_mask[axis];
int32_t curr_v = curr.axis[axis] & denoise_mask[axis];
if (last_v == curr_v) {
/* Within the noise threshold */
continue;
}
}
printf(_("Event @ %ld.%06ld: %s, value %d\n"),
(long int)tv.tv_sec, (long int)tv.tv_usec,
libx52io_axis_to_str(axis), curr.axis[axis]);
printed = true;
}
}
for (int btn = 0; btn < LIBX52IO_BUTTON_MAX; btn++) {
if (last.button[btn] != curr.button[btn]) {
printf(_("Event @ %ld.%06ld: %s, value %d\n"),
(long int)tv.tv_sec, (long int)tv.tv_usec,
libx52io_button_to_str(btn), curr.button[btn]);
printed = true;
}
}
if (printed) {
puts("");
}
memcpy(&last, &curr, sizeof(curr));
}
/* Close and exit the libx52io library */
libx52io_close(ctx);
libx52io_exit(ctx);
}

297
gettext.h 100644
View File

@ -0,0 +1,297 @@
/* Convenience header for conditional use of GNU <libintl.h>.
Copyright (C) 1995-1998, 2000-2002, 2004-2006, 2009-2016 Free Software
Foundation, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
#ifndef _LIBGETTEXT_H
#define _LIBGETTEXT_H 1
#ifndef ENABLE_NLS
#define ENABLE_NLS 0
#endif
/* NLS can be disabled through the configure --disable-nls option. */
#if ENABLE_NLS
/* Get declarations of GNU message catalog functions. */
# include <locale.h>
# include <libintl.h>
/* You can set the DEFAULT_TEXT_DOMAIN macro to specify the domain used by
the gettext() and ngettext() macros. This is an alternative to calling
textdomain(), and is useful for libraries. */
# ifdef DEFAULT_TEXT_DOMAIN
# undef gettext
# define gettext(Msgid) \
dgettext (DEFAULT_TEXT_DOMAIN, Msgid)
# undef ngettext
# define ngettext(Msgid1, Msgid2, N) \
dngettext (DEFAULT_TEXT_DOMAIN, Msgid1, Msgid2, N)
# endif
#else
/* Solaris /usr/include/locale.h includes /usr/include/libintl.h, which
chokes if dcgettext is defined as a macro. So include it now, to make
later inclusions of <locale.h> a NOP. We don't include <libintl.h>
as well because people using "gettext.h" will not include <libintl.h>,
and also including <libintl.h> would fail on SunOS 4, whereas <locale.h>
is OK. */
#if defined(__sun)
# include <locale.h>
#endif
/* Many header files from the libstdc++ coming with g++ 3.3 or newer include
<libintl.h>, which chokes if dcgettext is defined as a macro. So include
it now, to make later inclusions of <libintl.h> a NOP. */
#if defined(__cplusplus) && defined(__GNUG__) && (__GNUC__ >= 3)
# include <cstdlib>
# if (__GLIBC__ >= 2 && !defined __UCLIBC__) || _GLIBCXX_HAVE_LIBINTL_H
# include <libintl.h>
# endif
#endif
/* Disabled NLS.
The casts to 'const char *' serve the purpose of producing warnings
for invalid uses of the value returned from these functions.
On pre-ANSI systems without 'const', the config.h file is supposed to
contain "#define const". */
# undef gettext
# define gettext(Msgid) ((const char *) (Msgid))
# undef dgettext
# define dgettext(Domainname, Msgid) (gettext (Msgid))
# undef dcgettext
# define dcgettext(Domainname, Msgid, Category) \
(dgettext (Domainname, Msgid))
# undef ngettext
# define ngettext(Msgid1, Msgid2, N) \
((N) == 1 \
? ((const char *) (Msgid1)) \
: ((const char *) (Msgid2)))
# undef dngettext
# define dngettext(Domainname, Msgid1, Msgid2, N) \
(ngettext (Msgid1, Msgid2, N))
# undef dcngettext
# define dcngettext(Domainname, Msgid1, Msgid2, N, Category) \
(dngettext (Domainname, Msgid1, Msgid2, N))
# undef textdomain
# define textdomain(Domainname) ((const char *) (Domainname))
# undef bindtextdomain
# define bindtextdomain(Domainname, Dirname) \
(void)((void) (Domainname), (const char *) (Dirname))
# undef bind_textdomain_codeset
# define bind_textdomain_codeset(Domainname, Codeset) \
(void)((void) (Domainname), (const char *) (Codeset))
#endif
/* Prefer gnulib's setlocale override over libintl's setlocale override. */
#ifdef GNULIB_defined_setlocale
# undef setlocale
# define setlocale rpl_setlocale
#endif
/* A pseudo function call that serves as a marker for the automated
extraction of messages, but does not call gettext(). The run-time
translation is done at a different place in the code.
The argument, String, should be a literal string. Concatenated strings
and other string expressions won't work.
The macro's expansion is not parenthesized, so that it is suitable as
initializer for static 'char[]' or 'const char[]' variables. */
#define gettext_noop(String) String
/* The separator between msgctxt and msgid in a .mo file. */
#define GETTEXT_CONTEXT_GLUE "\004"
/* Pseudo function calls, taking a MSGCTXT and a MSGID instead of just a
MSGID. MSGCTXT and MSGID must be string literals. MSGCTXT should be
short and rarely need to change.
The letter 'p' stands for 'particular' or 'special'. */
#ifdef DEFAULT_TEXT_DOMAIN
# define pgettext(Msgctxt, Msgid) \
pgettext_aux (DEFAULT_TEXT_DOMAIN, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, LC_MESSAGES)
#else
# define pgettext(Msgctxt, Msgid) \
pgettext_aux (NULL, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, LC_MESSAGES)
#endif
#define dpgettext(Domainname, Msgctxt, Msgid) \
pgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, LC_MESSAGES)
#define dcpgettext(Domainname, Msgctxt, Msgid, Category) \
pgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, Category)
#ifdef DEFAULT_TEXT_DOMAIN
# define npgettext(Msgctxt, Msgid, MsgidPlural, N) \
npgettext_aux (DEFAULT_TEXT_DOMAIN, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, MsgidPlural, N, LC_MESSAGES)
#else
# define npgettext(Msgctxt, Msgid, MsgidPlural, N) \
npgettext_aux (NULL, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, MsgidPlural, N, LC_MESSAGES)
#endif
#define dnpgettext(Domainname, Msgctxt, Msgid, MsgidPlural, N) \
npgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, MsgidPlural, N, LC_MESSAGES)
#define dcnpgettext(Domainname, Msgctxt, Msgid, MsgidPlural, N, Category) \
npgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, MsgidPlural, N, Category)
#ifdef __GNUC__
__inline
#else
#ifdef __cplusplus
inline
#endif
#endif
static const char *
pgettext_aux (const char *domain,
const char *msg_ctxt_id, const char *msgid,
int category)
{
const char *translation = dcgettext (domain, msg_ctxt_id, category);
if (translation == msg_ctxt_id)
return msgid;
else
return translation;
}
#ifdef __GNUC__
__inline
#else
#ifdef __cplusplus
inline
#endif
#endif
static const char *
npgettext_aux (const char *domain,
const char *msg_ctxt_id, const char *msgid,
const char *msgid_plural, unsigned long int n,
int category)
{
const char *translation =
dcngettext (domain, msg_ctxt_id, msgid_plural, n, category);
if (translation == msg_ctxt_id || translation == msgid_plural)
return (n == 1 ? msgid : msgid_plural);
else
return translation;
}
/* The same thing extended for non-constant arguments. Here MSGCTXT and MSGID
can be arbitrary expressions. But for string literals these macros are
less efficient than those above. */
#include <string.h>
#if (((__GNUC__ >= 3 || __GNUG__ >= 2) && !defined __STRICT_ANSI__) \
/* || __STDC_VERSION__ >= 199901L */ )
# define _LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS 1
#else
# define _LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS 0
#endif
#if !_LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS
#include <stdlib.h>
#endif
#define pgettext_expr(Msgctxt, Msgid) \
dcpgettext_expr (NULL, Msgctxt, Msgid, LC_MESSAGES)
#define dpgettext_expr(Domainname, Msgctxt, Msgid) \
dcpgettext_expr (Domainname, Msgctxt, Msgid, LC_MESSAGES)
#ifdef __GNUC__
__inline
#else
#ifdef __cplusplus
inline
#endif
#endif
static const char *
dcpgettext_expr (const char *domain,
const char *msgctxt, const char *msgid,
int category)
{
size_t msgctxt_len = strlen (msgctxt) + 1;
size_t msgid_len = strlen (msgid) + 1;
const char *translation;
#if _LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS
char msg_ctxt_id[msgctxt_len + msgid_len];
#else
char buf[1024];
char *msg_ctxt_id =
(msgctxt_len + msgid_len <= sizeof (buf)
? buf
: (char *) malloc (msgctxt_len + msgid_len));
if (msg_ctxt_id != NULL)
#endif
{
int found_translation;
memcpy (msg_ctxt_id, msgctxt, msgctxt_len - 1);
msg_ctxt_id[msgctxt_len - 1] = '\004';
memcpy (msg_ctxt_id + msgctxt_len, msgid, msgid_len);
translation = dcgettext (domain, msg_ctxt_id, category);
found_translation = (translation != msg_ctxt_id);
#if !_LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS
if (msg_ctxt_id != buf)
free (msg_ctxt_id);
#endif
if (found_translation)
return translation;
}
return msgid;
}
#define npgettext_expr(Msgctxt, Msgid, MsgidPlural, N) \
dcnpgettext_expr (NULL, Msgctxt, Msgid, MsgidPlural, N, LC_MESSAGES)
#define dnpgettext_expr(Domainname, Msgctxt, Msgid, MsgidPlural, N) \
dcnpgettext_expr (Domainname, Msgctxt, Msgid, MsgidPlural, N, LC_MESSAGES)
#ifdef __GNUC__
__inline
#else
#ifdef __cplusplus
inline
#endif
#endif
static const char *
dcnpgettext_expr (const char *domain,
const char *msgctxt, const char *msgid,
const char *msgid_plural, unsigned long int n,
int category)
{
size_t msgctxt_len = strlen (msgctxt) + 1;
size_t msgid_len = strlen (msgid) + 1;
const char *translation;
#if _LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS
char msg_ctxt_id[msgctxt_len + msgid_len];
#else
char buf[1024];
char *msg_ctxt_id =
(msgctxt_len + msgid_len <= sizeof (buf)
? buf
: (char *) malloc (msgctxt_len + msgid_len));
if (msg_ctxt_id != NULL)
#endif
{
int found_translation;
memcpy (msg_ctxt_id, msgctxt, msgctxt_len - 1);
msg_ctxt_id[msgctxt_len - 1] = '\004';
memcpy (msg_ctxt_id + msgctxt_len, msgid, msgid_len);
translation = dcngettext (domain, msg_ctxt_id, msgid_plural, n, category);
found_translation = !(translation == msg_ctxt_id || translation == msgid_plural);
#if !_LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS
if (msg_ctxt_id != buf)
free (msg_ctxt_id);
#endif
if (found_translation)
return translation;
}
return (n == 1 ? msgid : msgid_plural);
}
#endif /* _LIBGETTEXT_H */

View File

@ -0,0 +1,20 @@
# Automake for x52test
#
# Copyright (C) 2012-2018 Nirenjan Krishnan (nirenjan@nirenjan.org)
#
# SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
bin_PROGRAMS += x52test
# Test utility that exercises all the library functions
x52test_SOURCES = \
joytest/x52_test.c \
joytest/x52_test_mfd.c \
joytest/x52_test_led.c \
joytest/x52_test_clock.c
x52test_CFLAGS = -I $(top_srcdir)/libx52 -I $(top_srcdir) -DLOCALEDIR=\"$(localedir)\" $(WARN_CFLAGS)
x52test_LDFLAGS = $(WARN_LDFLAGS)
x52test_LDADD = libx52.la
# Extra files that need to be in the distribution
EXTRA_DIST += joytest/x52_test_common.h

261
joytest/x52_test.c 100644
View File

@ -0,0 +1,261 @@
/*
* Saitek X52 Pro MFD & LED driver
*
* Copyright (C) 2012-2015 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
#define _GNU_SOURCE
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <locale.h>
#include <time.h>
#include "libx52.h"
#include "x52_test_common.h"
libx52_device *dev;
int test_exit;
bool nodelay;
void test_cleanup(void)
{
if (libx52_check_feature(dev, LIBX52_FEATURE_LED) == LIBX52_SUCCESS) {
/* X52 Pro */
libx52_set_text(dev, 0, " Saitek X52 Pro ", 16);
libx52_set_text(dev, 1, " Flight ", 16);
libx52_set_text(dev, 2, " Control System ", 16);
libx52_set_led_state(dev, LIBX52_LED_FIRE, LIBX52_LED_STATE_ON);
libx52_set_led_state(dev, LIBX52_LED_THROTTLE, LIBX52_LED_STATE_ON);
libx52_set_led_state(dev, LIBX52_LED_A, LIBX52_LED_STATE_GREEN);
libx52_set_led_state(dev, LIBX52_LED_B, LIBX52_LED_STATE_GREEN);
libx52_set_led_state(dev, LIBX52_LED_D, LIBX52_LED_STATE_GREEN);
libx52_set_led_state(dev, LIBX52_LED_E, LIBX52_LED_STATE_GREEN);
libx52_set_led_state(dev, LIBX52_LED_T1, LIBX52_LED_STATE_GREEN);
libx52_set_led_state(dev, LIBX52_LED_T2, LIBX52_LED_STATE_GREEN);
libx52_set_led_state(dev, LIBX52_LED_T3, LIBX52_LED_STATE_GREEN);
libx52_set_led_state(dev, LIBX52_LED_POV, LIBX52_LED_STATE_GREEN);
libx52_set_led_state(dev, LIBX52_LED_CLUTCH, LIBX52_LED_STATE_GREEN);
} else {
libx52_set_text(dev, 0, " Saitek ", 16);
libx52_set_text(dev, 1, " X52 Flight ", 16);
libx52_set_text(dev, 2, " Control System ", 16);
}
libx52_set_blink(dev, 0);
libx52_set_shift(dev, 0);
libx52_set_clock(dev, time(NULL), 1); // Display local time
libx52_set_clock_timezone(dev, LIBX52_CLOCK_2, 0); // GMT
libx52_set_clock_timezone(dev, LIBX52_CLOCK_3, 330); // IST
libx52_set_clock_format(dev, LIBX52_CLOCK_1, LIBX52_CLOCK_FORMAT_24HR);
libx52_set_clock_format(dev, LIBX52_CLOCK_2, LIBX52_CLOCK_FORMAT_24HR);
libx52_set_clock_format(dev, LIBX52_CLOCK_3, LIBX52_CLOCK_FORMAT_24HR);
libx52_set_date_format(dev, LIBX52_DATE_FORMAT_MMDDYY);
libx52_set_brightness(dev, 1, 128);
libx52_set_brightness(dev, 0, 128);
libx52_update(dev);
}
void print_banner(const char *message)
{
size_t len = strlen(message);
size_t i;
putchar('\n');
putchar('\t');
for (i = 0; i < len + 2; i++) {
putchar('=');
}
putchar('\n');
putchar('\t');
putchar(' ');
puts(message);
putchar('\t');
for (i = 0; i < len + 2; i++) {
putchar('=');
}
putchar('\n');
}
static void signal_handler(int sig)
{
test_exit = -sig;
}
#define TESTS \
X(brightness, bri, gettext_noop("Test brightness scale (~ 1m)")) \
X(leds, led, gettext_noop("Test LED states (~ 45s)")) \
X(mfd_text, mfd1, gettext_noop("Test MFD string display (~ 30s)")) \
X(mfd_display, mfd2, gettext_noop("Test MFD displays all characters (~ 2m 15s)")) \
X(blink_n_shift, blink, gettext_noop("Test the blink and shift commands (< 10s)")) \
X(clock, clock, gettext_noop("Test the clock commands (~1m)"))
enum {
#define X(en, kw, desc) TEST_BIT_ ## en,
TESTS
#undef X
};
enum {
#define X(en, kw, desc) TEST_ ## en = (1 << TEST_BIT_ ## en),
TESTS
#undef X
};
#define X(en, kw, desc) | TEST_ ## en
const int TEST_ALL = 0 TESTS;
#undef X
static int run_tests(int test_set)
{
#define RUN_TEST(tst) if (test_set & TEST_ ## tst) { rc = test_ ## tst (); if (rc) break; }
int rc = 0;
puts(_("x52test is a suite of tests to write to the X52 Pro device\n"
"and test the extra functionality available in the LEDs and MFD\n"));
if (test_set == TEST_ALL) {
puts(_("These tests take roughly 6 minutes to run"));
}
puts(_("Press Enter to begin the tests, press Ctrl-C to abort anytime"));
getc(stdin);
rc = libx52_init(&dev);
if (rc != LIBX52_SUCCESS) {
fprintf(stderr, _("Unable to initialize X52 library: %s\n"), libx52_strerror(rc));
return 1;
}
signal(SIGTERM, signal_handler);
signal(SIGINT, signal_handler);
test_exit = 0;
do {
#define X(en, cmd, desc) RUN_TEST(en)
TESTS
#undef X
} while (0);
if (rc == LIBX52_SUCCESS) {
puts(_("All tests completed successfully"));
} else if (rc > 0) {
fprintf(stderr, _("Got error %s\n"), libx52_strerror(rc));
} else {
fprintf(stderr, _("Received %s signal, quitting...\n"), strsignal(-rc));
}
if (rc <= 0) test_cleanup();
libx52_exit(dev);
return 0;
}
static void underline(const char *msg) {
size_t i;
puts(msg);
for (i = 0; i < strlen(msg); i++) {
putchar('=');
}
putchar('\n');
}
static void usage(void)
{
puts(_("These are the available tests with a description and\n"
"approximate runtime. Not specifying any tests will run\n"
"all the tests\n"));
underline(_("List of tests:"));
#define X(en, cmd, desc) puts("\t" #cmd "\t" desc);
TESTS
#undef X
puts("");
}
struct test_map {
const char *cmd;
int test_bitmap;
};
const struct test_map tests[] = {
#define X(en, cmd, desc) { #cmd, TEST_ ##en },
TESTS
#undef X
{ NULL, 0 }
};
int main(int argc, char **argv)
{
int test_list;
int i;
const struct test_map *test;
int found;
/* Initialize gettext */
#if ENABLE_NLS
setlocale(LC_ALL, "");
bindtextdomain(PACKAGE, LOCALEDIR);
textdomain(PACKAGE);
#endif
/* Usage: x52test [list of tests] */
if (argc == 1) {
/* Run all tests, if none specified */
test_list = TEST_ALL;
} else {
/* Initialize the test list to run no tests, the commands
* will enable the selective tests
*/
test_list = 0;
}
for (i = 1; i < argc; i++) {
if (!strcmp(argv[i], "help") ||
!strcmp(argv[i], "--help")) {
/* Display help string and exit */
printf(_("Usage: %s [list of tests]\n\n"), argv[0]);
usage();
return 0;
} else {
found = 0;
for (test = tests; test->cmd; test++) {
if (!strcmp(argv[i], test->cmd)) {
test_list |= test->test_bitmap;
found = 1;
break;
}
}
if (found == 0) {
printf(_("Unrecognized test identifier: %s\n\n"), argv[i]);
usage();
return 1;
}
}
}
/* Initialize the nodelay variable */
nodelay = (getenv("LD_PRELOAD") != NULL || getenv("NO_DELAY") != NULL);
if (test_list) {
i = run_tests(test_list);
} else {
puts(_("Not running any tests"));
}
return i;
}

View File

@ -0,0 +1,65 @@
/*
* Saitek X52 Pro MFD & LED driver
*
* Copyright (C) 2012-2015 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
#define _GNU_SOURCE
#include "config.h"
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include "libx52.h"
#include "x52_test_common.h"
int test_clock(void)
{
time_t i;
time_t start;
time_t end;
#define TEST_CLOCK_LOOP(bump) do { \
for (i = start; i < end; i += bump) { \
TEST(clock, i, 0); \
if (!nodelay) usleep(250000); \
} \
} while (0)
print_banner(_("Clock"));
puts(_("This tests the clock display"));
/* Start from midnight Jan 1 2015 GMT */
start = 1420070400;
puts(_("\nTesting clock time minute display for 90 minutes"));
end = start + 90 * 60;
TEST(clock_format, LIBX52_CLOCK_1, LIBX52_CLOCK_FORMAT_12HR);
TEST_CLOCK_LOOP(60);
end = start + 36 * 60 * 60;
puts(_("\nTesting clock time hour display for 36 hours, 12 hour mode"));
TEST_CLOCK_LOOP(3600);
puts(_("\nTesting clock time hour display for 36 hours, 24 hour mode"));
TEST(clock_format, LIBX52_CLOCK_1, LIBX52_CLOCK_FORMAT_24HR);
TEST_CLOCK_LOOP(3600);
end = start + 31 * 24 * 60 * 60;
puts(_("\nTesting clock date display for 31 days, dd-mm-yy"));
TEST(date_format, LIBX52_DATE_FORMAT_DDMMYY);
TEST_CLOCK_LOOP(86400);
puts(_("\nTesting clock date display for 31 days, mm-dd-yy"));
TEST(date_format, LIBX52_DATE_FORMAT_MMDDYY);
TEST_CLOCK_LOOP(86400);
puts(_("\nTesting clock date display for 31 days, yy-mm-dd"));
TEST(date_format, LIBX52_DATE_FORMAT_YYMMDD);
TEST_CLOCK_LOOP(86400);
return 0;
}

View File

@ -0,0 +1,52 @@
/*
* Saitek X52 Pro MFD & LED driver
*
* Copyright (C) 2012-2015 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
#ifndef X52_TEST_COMMON_H
#define X52_TEST_COMMON_H
#include "config.h"
#include <stdio.h>
#include <stdbool.h>
#include "libx52.h"
#include "gettext.h"
extern libx52_device *dev;
extern int test_exit;
extern bool nodelay;
#define TEST(tst, ...) do { \
int rc; \
rc = ( libx52_set_ ## tst (dev, __VA_ARGS__) ); \
if (rc) { \
fprintf(stderr, _("\n%s(%s) failed with %d(%s)\n"), #tst, #__VA_ARGS__, rc, libx52_strerror(rc)); \
return rc; \
} \
if (test_exit) return test_exit; \
rc = libx52_update(dev); \
if (rc) { \
fprintf(stderr, _("\nupdate failed with %d(%s)\n"), rc, libx52_strerror(rc)); \
return rc; \
} \
if (test_exit) return test_exit; \
} while (0)
/* For i18n */
#define _(x) gettext(x)
void test_cleanup(void);
void print_banner(const char *message);
int test_brightness(void);
int test_mfd_display(void);
int test_mfd_text(void);
int test_leds(void);
int test_blink_n_shift(void);
int test_clock(void);
#endif /* X52_TEST_COMMON_H */

View File

@ -0,0 +1,91 @@
/*
* Saitek X52 Pro MFD & LED driver
*
* Copyright (C) 2012-2015 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
#define _GNU_SOURCE
#include "config.h"
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "libx52.h"
#include "x52_test_common.h"
#define TEST_LED(name, state) do { \
printf(_("LED %s - %s\n"), #name, #state); \
TEST(led_state, LIBX52_LED_ ## name, LIBX52_LED_STATE_ ## state); \
if (!nodelay) usleep(500000); \
} while(0)
#define TEST_LED_MONO(name) do { \
printf(_("\nTesting LED %s\n"), #name); \
if (!nodelay) sleep(2); \
TEST_LED(name, OFF); \
TEST_LED(name, ON); \
} while(0)
#define TEST_LED_COLOR(name) do {\
printf(_("\nTesting LED %s\n"), #name); \
if (!nodelay) sleep(2); \
TEST_LED(name, OFF); \
TEST_LED(name, RED); \
TEST_LED(name, AMBER); \
TEST_LED(name, GREEN); \
} while(0)
int test_leds(void)
{
print_banner(_("LEDs"));
{ /* Check if the connected device supports individual LED control */
int rc;
rc = libx52_check_feature(dev, LIBX52_FEATURE_LED);
if (rc == LIBX52_ERROR_NOT_SUPPORTED) {
puts(_("Skipping LED tests since the device does not support LED control"));
return 0;
} else if (rc != LIBX52_SUCCESS) {
return rc;
}
}
puts(_("This cycles the LEDs through all possible states"));
TEST_LED_MONO(FIRE);
TEST_LED_COLOR(A);
TEST_LED_COLOR(B);
TEST_LED_COLOR(D);
TEST_LED_COLOR(E);
TEST_LED_COLOR(T1);
TEST_LED_COLOR(T2);
TEST_LED_COLOR(T3);
TEST_LED_COLOR(POV);
TEST_LED_COLOR(CLUTCH);
TEST_LED_MONO(THROTTLE);
return 0;
}
#define TEST_BLINK_OR_SHIFT(type) do { \
printf(_("\nTesting %s\n"), #type); \
if (!nodelay) sleep(1); \
puts(#type " ON"); \
TEST(type, 1); \
if (!nodelay) sleep(2); \
puts(#type " OFF"); \
TEST(type, 0); \
} while (0)
int test_blink_n_shift(void)
{
print_banner(_("Blink & Shift"));
puts(_("This tests the blink indicator and shift functionality"));
TEST_BLINK_OR_SHIFT(blink);
TEST_BLINK_OR_SHIFT(shift);
return 0;
}

View File

@ -0,0 +1,101 @@
/*
* Saitek X52 Pro MFD & LED driver
*
* Copyright (C) 2012-2015 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
#define _GNU_SOURCE
#include "config.h"
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "libx52.h"
#include "x52_test_common.h"
#define TEST_BRIGHTNESS(mfd, value) TEST(brightness, mfd, value)
int test_brightness(void)
{
int i;
print_banner(_("Brightness"));
puts(_("This test cycles through the MFD and LED brightness scales"));
fputs("\n |+---+---+---+---+---+---+---+---+|", stdout);
fputs(_("\nMFD: "), stdout);
for (i = 0; i < 129; i++) {
if (!(i & 3)) fputs("#", stdout);
fflush(stdout);
TEST_BRIGHTNESS(1, i);
if (!nodelay) usleep(250000);
}
fputs(_("\nLED: "), stdout);
for (i = 0; i < 129; i++) {
if (!(i & 3)) fputs("#", stdout);
fflush(stdout);
TEST_BRIGHTNESS(0, i);
if (!nodelay) usleep(250000);
}
fputs("\n\n", stdout);
return 0;
}
int test_mfd_text(void)
{
int i;
int j;
char str[32];
print_banner(_("MFD text"));
puts(_("This test tests the character displays of the MFD\n"));
for (i = 0; i < 256; i += 16) {
j = snprintf(str, 16, "0x%02x - 0x%02x", i, i + 0xf);
printf(_("Writing characters %s\n"), str);
TEST(text, 0, str, j);
memset(str, ' ', 32);
for (j = 0; j < 16; j++) {
str[j << 1] = i + j;
}
TEST(text, 1, str, 16);
TEST(text, 2, str + 16, 16);
if (!nodelay) sleep(2);
}
return 0;
}
int test_mfd_display(void)
{
int i;
char str[16];
print_banner(_("MFD display"));
puts(_("This test checks if the display elements can display all characters\n"
"You should see the display cycling through each character, with every\n"
"cell displaying the same character\n"));
for (i = 0; i < 256; i++) {
printf(_("Testing character 0x%02x..."), i);
memset(str, i, 16);
TEST(text, 0, str, 16);
TEST(text, 1, str, 16);
TEST(text, 2, str, 16);
puts(_("OK"));
if (!nodelay) usleep(500000);
}
return 0;
}

View File

@ -1,5 +1,6 @@
obj-m := saitek_x52.o
saitek_x52-objs := x52joy.o x52joy_commands.o x52joy_input.o
obj-m := saitek_x52.o saitek_x65.o
saitek_x52-objs := hid-saitek-x52.o
saitek_x65-objs := hid-saitek-x65.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

View File

@ -0,0 +1,52 @@
Kernel driver for Saitek X52 and X52 Pro
========================================
This folder contains a loadable kernel module for the Saitek X52 and X52 Pro
HOTAS. This improves upon the standard `hid-generic` driver by reporting both
the left-right and up-down motion of the thumb stick.
However, it changes the buttons that are reported by the joystick, and thus,
may not be suitable for all applications.
**Note:** The following kernel versions have a fix to handle the thumb stick,
and therefore, do not require this driver.
* 5.9+ (from -rc4 onwards)
* 5.8.10+
* 5.4.66+
* 4.19.146+
# Building
This directory is deliberately not integrated with the top level Autotools
based build framework.
Install the Linux headers for the currently running kernel. On Ubuntu, this
can be done by running `sudo apt-get install -y linux-headers-$(uname -r)`.
Run `make`. This will build the module from source.
# Installing the kernel module
Once you have built the kernel module, run `sudo insmod saitek_x52.ko` from
the current directory. With a recent enough kernel, the driver should switch
automatically. Otherwise, simply disconnect and reconnect your X52.
# Reporting issues
Please report any issues seen as a [Github issue](https://github.com/nirenjan/libx52/issues).
# Notes
This folder also includes a driver for the X65F HOTAS, but it is untested as of
this writing. The same build instructions apply, but you will need to run `sudo
insmod saitek_x65.ko` instead.
As with the X52 driver, the following kernel versions have a fix for the thumb
stick, and do not require this custom driver.
* 5.13+ (from -rc3 onwards)
* 5.12.12+
* 5.10.45+
* 5.4.127+
* 4.9.196+

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