Merge branch 'daemon'

reverse-scroll
nirenjan 2021-08-01 03:01:24 -07:00
commit 78e4f3334f
118 changed files with 6662 additions and 1491 deletions

View File

@ -1,4 +1,4 @@
#!/bin/bash
#!/bin/bash -x
# Install dependencies to build and test on Ubuntu runners
brew install \
autoconf \
@ -11,6 +11,15 @@ brew install \
hidapi \
doxygen \
rsync \
meson \
cmocka
# inih cannot be installed via Homebrew, install it manually
INIH_VER=r53
curl -LO https://github.com/benhoyt/inih/archive/refs/tags/${INIH_VER}.tar.gz
tar xvf ${INIH_VER}.tar.gz
cd inih-${INIH_VER}
meson build
meson install -C build
exit 0

View File

@ -11,6 +11,7 @@ sudo apt-get install -y \
autopoint \
libusb-1.0-0-dev \
libhidapi-dev \
libinih-dev \
libevdev-dev \
doxygen \
rsync \

4
.gitignore vendored
View File

@ -7,9 +7,13 @@
a.out
utils/cli/x52cli*
utils/test/x52test*
utils/evtest/x52evtest*
lib/libx52util/util_char_map.c
util/x52charmapgen*
lib/libusbx52/x52test*
udev/*.rules
daemon/x52d*
!daemon/x52d_*.*
x52pro-linux-*.tar.gz
# Module files

View File

@ -8,6 +8,8 @@ The format is based upon [Keep a Changelog].
### Added
- IO library to read and parse events from a supported joystick.
- Event test utility which displays the events similar to evtest.
- Import pinelog library for daemon logging.
- Daemon to control and update the X52 joystick.
### Changed
- Linux kernel driver to correctly handle the X52/X52 Pro. This is not required
@ -20,6 +22,8 @@ The format is based upon [Keep a Changelog].
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

View File

@ -1,12 +1,10 @@
Installation instructions for x52pro-linux
==========================================
[![Build Status](https://www.travis-ci.org/nirenjan/x52pro-linux.svg?branch=master)](https://www.travis-ci.org/nirenjan/x52pro-linux)
Build has been tested on the following operating systems (x86-64 only):
* Ubuntu 16.04 LTS
* Ubuntu 18.04 LTS
* Ubuntu 20.04 LTS
* OS X 10.13.6
# Prerequisites
@ -18,6 +16,7 @@ Build has been tested on the following operating systems (x86-64 only):
* autopoint
* gettext
* hidapi
* [inih](https://github.com/benhoyt/inih)
* libtool
* libusb-1.0 + headers
* pkg-config
@ -27,9 +26,16 @@ Build has been tested on the following operating systems (x86-64 only):
| Platform | Install instructions |
| -------- | -------------------- |
| Ubuntu | `sudo apt-get install automake autoconf gettext autopoint libhidapi-dev libtool libusb-1.0-0-dev pkg-config python3` |
| MacOS + Homebrew | `brew install automake autoconf gettext hidapi libtool libusb pkg-config python3` |
| Arch Linux | `pacman -S base-devel libusb hidapi python` |
| Ubuntu | `sudo apt-get install automake autoconf gettext autopoint libhidapi-dev libinih-dev libtool libusb-1.0-0-dev pkg-config python3` |
| MacOS + Homebrew | `brew install automake autoconf gettext hidapi libtool libusb meson pkg-config python3` |
| Arch Linux | `pacman -S base-devel libusb hidapi libinih python` |
On MacOS, `inih` is not available as a Homebrew formula. You need to build and
install it manually using the following steps:
* Download and extract inih from Github
* From the inih source directory, run `meson build`, then run `meson install -C
build`.
## Optional Packages
@ -39,10 +45,7 @@ the utilities, you will need the following packages:
* doxygen
* rsync
You will also need the following packages to run the unit tests:
* faketime
* cmocka
You will also need the `cmocka` package to run the unit tests.
# Installation Instructions

View File

@ -6,28 +6,44 @@
ACLOCAL_AMFLAGS = -I m4
# Build any support libraries first
SUBDIRS = lib
if USE_NLS
po_SUBDIRS = po
SUBDIRS += po
endif
SUBDIRS = $(po_SUBDIRS) lib utils tests udev
#######################################################################
# Defaults
#######################################################################
bin_PROGRAMS =
check_PROGRAMS =
lib_LTLIBRARIES =
check_LTLIBRARIES =
pkgconfig_DATA =
TESTS =
EXTRA_DIST =
CLEANFILES =
# Extra files that need to be in the distribution
EXTRA_DIST = \
ABOUT-NLS \
AUTHORS \
ChangeLog.md \
CONTRIBUTING.md \
Doxyfile.in \
INSTALL.md \
LICENSE \
README.md \
config.rpath \
gettext.h \
usb-ids.h \
po/README.md
x52includedir = $(includedir)/libx52
x52include_HEADERS =
LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) $(top_srcdir)/tap-driver.sh
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
#######################################################################
# Doxygen support
#######################################################################
if HAVE_DOXYGEN
DXGEN = $(DXGEN_@AM_V@)
DXGEN_ = $(DXGEN_@AM_DEFAULT_V@)
@ -53,3 +69,20 @@ uninstall-local:
rm -rf $(DESTDIR)$(mandir)/man1/x52cli.1
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 \
gettext.h \
usb-ids.h \
po/README.md

View File

@ -25,4 +25,4 @@ for your needs as it provides a graphical interface to control the MFD and LEDs.
# Building and installing
See [INSTALL.md](https://github.com/nirenjan/x52pro-linux/blob/master/INSTALL.md)
See [INSTALL.md](INSTALL.md)

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

177
cli/test_x52_cli.c 100644
View File

@ -0,0 +1,177 @@
/*
* 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)
{
*dev = NULL;
return LIBX52_SUCCESS;
}
int libx52_connect(libx52_device *dev)
{
return LIBX52_SUCCESS;
}
int libx52_update(libx52_device *dev)
{
return LIBX52_SUCCESS;
}
void libx52_exit(libx52_device *dev)
{
}
const char *libx52_strerror(libx52_error_code rc)
{
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

View File

@ -1,7 +1,7 @@
/*
* Saitek X52 Pro MFD & LED driver
*
* Copyright (C) 2015 Nirenjan Krishnan (nirenjan@nirenjan.org)
* Copyright (C) 2015-2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
@ -469,7 +469,11 @@ static void do_help(const struct command_handler *cmd)
}
}
#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;

View File

@ -1,10 +1,10 @@
# Autoconf settings for x52pro-linux
#
# Copyright (C) 2012-2020 Nirenjan Krishnan (nirenjan@nirenjan.org)
# Copyright (C) 2012-2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
#
# SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
AC_INIT([x52pro-linux], [0.2.1], [nirenjan@gmail.com])
AC_INIT([x52pro-linux], [0.2.2], [nirenjan@gmail.com])
AC_CONFIG_MACRO_DIR([m4])
AM_INIT_AUTOMAKE([-Wall foreign subdir-objects])
AC_REQUIRE_AUX_FILE([tap-driver.sh])
@ -18,6 +18,11 @@ 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
@ -31,7 +36,7 @@ AM_CONDITIONAL([LINUX], [test "x${build_linux}" = "xyes"])
# Internationalization
AM_GNU_GETTEXT([external])
AM_GNU_GETTEXT_VERSION(0.18)
AM_GNU_GETTEXT_VERSION(0.19)
AM_CONDITIONAL([USE_NLS], [test "x${USE_NLS}" == "xyes"])
# Check for libusb-1.0
@ -41,13 +46,25 @@ AC_SUBST([LIBUSB_CFLAGS])
AC_SUBST([LIBUSB_LDFLAGS])
AC_SUBST([LIBUSB_LIBS])
AC_SUBST([X52_PKG_VERSION], [0.1])
AC_SUBST([X52_INCLUDE], ["-I \$(top_srcdir)/lib/libx52"])
# Check for libinih
PKG_CHECK_MODULES([INIH], [inih], [],
[
# Older versions of Ubuntu don't provide a .pc file
AC_CHECK_HEADERS([ini.h])
AC_SEARCH_LIBS([ini_parse], [inih])
])
AC_SUBST([INIH_CFLAGS])
AC_SUBST([INIH_LDFLAGS])
AC_SUBST([INIH_LIBS])
# Pinelog configuration
AC_SUBST([PINELOG_CFLAGS], ["-DPINELOG_SHOW_DATE=1 -DPINELOG_SHOW_LEVEL=1 -DPINELOG_SHOW_BACKTRACE=1"])
# 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])
AX_PKG_CHECK_MODULES([HIDAPI], [${hidapi_backend}])
PKG_CHECK_MODULES([HIDAPI], [${hidapi_backend}])
AC_SUBST([HIDAPI_CFLAGS])
AC_SUBST([HIDAPI_LDFLAGS])
AC_SUBST([HIDAPI_LIBS])
@ -66,7 +83,7 @@ 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
AX_PKG_CHECK_MODULES([UDEV], [udev], [], [have_udev=yes], [have_udev=no])
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]),
@ -88,7 +105,7 @@ AM_COND_IF([HAVE_DOXYGEN],
[AC_MSG_WARN(["Doxygen not found; continuing without doxygen support"])])
# cmocka unit tests
AX_PKG_CHECK_MODULES([CMOCKA], [cmocka >= 1.1], [], [have_cmocka=yes], [have_cmocka=no])
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"])])
@ -107,17 +124,8 @@ AC_CONFIG_HEADERS([config.h])
AC_CONFIG_FILES([ po/Makefile.in
Makefile
lib/Makefile
lib/libx52/Makefile
lib/libx52/libx52.pc
lib/libusbx52/Makefile
lib/libx52util/Makefile
lib/libx52io/Makefile
udev/Makefile
libx52/libx52.pc
lib/pinelog/Makefile
udev/60-saitek-x52-x52pro.rules
utils/Makefile
utils/cli/Makefile
utils/test/Makefile
utils/evtest/Makefile
tests/Makefile
])
AC_OUTPUT

44
daemon/Makefile.am 100644
View File

@ -0,0 +1,44 @@
# 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
# Service daemon that manages the X52 device
x52d_SOURCES = \
daemon/x52d_main.c \
daemon/x52d_config_parser.c \
daemon/x52d_config.c \
daemon/x52d_device.c \
daemon/x52d_clock.c \
daemon/x52d_led.c
x52d_CFLAGS = \
-I $(top_srcdir) \
-I $(top_srcdir)/libx52io \
-I $(top_srcdir)/libx52 \
-I $(top_srcdir)/libx52util \
-I $(top_srcdir)/lib/pinelog \
-DSYSCONFDIR=\"$(sysconfdir)\" \
-DLOCALEDIR=\"$(localedir)\" \
-DLOGDIR=\"$(localstatedir)/log\" \
-DRUNDIR=\"$(runstatedir)\" \
@INIH_CFLAGS@ @PTHREAD_CFLAGS@ $(WARN_CFLAGS)
x52d_LDFLAGS = @INIH_LIBS@ @PTHREAD_LIBS@ $(WARN_LDFLAGS)
x52d_LDADD = \
lib/pinelog/libpinelog.la \
libx52.la \
@LTLIBINTL@
x52dconfdir = @sysconfdir@/x52d
x52dconf_DATA = daemon/x52d.conf
EXTRA_DIST += \
daemon/x52d_clock.h \
daemon/x52d_config.def \
daemon/x52d_config.h \
daemon/x52d_const.h \
daemon/x52d_device.h \
daemon/x52d.conf

97
daemon/x52d.conf 100644
View File

@ -0,0 +1,97 @@
#######################################################################
# 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
######################################################################
# Profiles - only valid on Linux
######################################################################
[Profiles]
# 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 #
##################

202
daemon/x52d_clock.c 100644
View File

@ -0,0 +1,202 @@
/*
* 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>
#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 *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);
timeval = localtime(&t);
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)
{
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)
{
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)
{
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,56 @@
/*
* 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 "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);
}
}
/* 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(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,82 @@
/**********************************************************************
* 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, 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_parser, 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_parser, 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_parser, 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_parser, UTC)
// Clock format for the primary clock
CFG(Clock, FormatPrimary, clock_format[LIBX52_CLOCK_1], clock_format_parser, 12hr)
// Clock format for the secondary clock
CFG(Clock, FormatSecondary, clock_format[LIBX52_CLOCK_2], clock_format_parser, 12hr)
// Clock format for the tertiary clock
CFG(Clock, FormatTertiary, clock_format[LIBX52_CLOCK_3], clock_format_parser, 12hr)
// Date format for the date display
CFG(Clock, DateFormat, date_format, date_format_parser, 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_parser, on)
CFG(LED, Throttle, leds[LIBX52_LED_THROTTLE], led_parser, on)
CFG(LED, A, leds[LIBX52_LED_A], led_parser, green)
CFG(LED, B, leds[LIBX52_LED_B], led_parser, green)
CFG(LED, D, leds[LIBX52_LED_D], led_parser, green)
CFG(LED, E, leds[LIBX52_LED_E], led_parser, green)
CFG(LED, T1, leds[LIBX52_LED_T1], led_parser, green)
CFG(LED, T2, leds[LIBX52_LED_T2], led_parser, green)
CFG(LED, T3, leds[LIBX52_LED_T3], led_parser, green)
CFG(LED, POV, leds[LIBX52_LED_POV], led_parser, green)
CFG(LED, Clutch, leds[LIBX52_LED_CLUTCH], led_parser, green)
/**********************************************************************
* Brightness Settings
*********************************************************************/
// The brightness settings map the brightness value to the LEDs/MFD.
CFG(Brightness, MFD, brightness[0], int_parser, 128)
CFG(Brightness, LED, brightness[1], int_parser, 128)
/**********************************************************************
* Profiles - only valid on Linux
*********************************************************************/
// Directory is the location of the folder containing the individual profiles.
CFG(Profiles, Directory, profiles_dir, string_parser, /etc/x52d/profiles.d)
// ClutchEnabled determines if the clutch button is treated specially
CFG(Profiles, ClutchEnabled, clutch_enabled, bool_parser, 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_parser, false)
#undef CFG

View File

@ -0,0 +1,86 @@
/*
* 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];
uint16_t brightness[2];
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_Profiles_Directory(char* param);
void x52d_cfg_set_Profiles_ClutchEnabled(bool param);
void x52d_cfg_set_Profiles_ClutchLatched(bool param);
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(void);
#endif // !defined X52D_CONFIG_H

View File

@ -0,0 +1,357 @@
/*
* 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>
#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;
}
*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;
}
/* Map for config->param */
#define CFG(section, key, name, parser, def) {#section, #key, parser, offsetof(struct x52d_config, name)},
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}
};
static int process_config_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 = process_config_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, process_config_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 = process_config_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,22 @@
/*
* 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"
#include "gettext.h"
#define N_(x) gettext_noop(x)
#define _(x) gettext(x)
#endif // !defined X52D_CONST_H

View File

@ -0,0 +1,228 @@
/*
* 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 "x52d_const.h"
#include "x52d_device.h"
#include "libx52.h"
#include "libx52io.h"
#include "pinelog.h"
static libx52_device *x52_dev;
static pthread_mutex_t device_mutex = PTHREAD_MUTEX_INITIALIZER;
/*
* Device acquisition thread
* This is a thread that scans for and opens a supported X52 joystick.
*/
static pthread_t device_acq_thr;
static volatile bool device_acq_thr_enable;
static volatile bool device_upd_thr_enable;
static void *x52_dev_acq(void *param)
{
int rc;
PINELOG_INFO(_("Starting X52 device acquisition thread"));
// Check if the device is connected in a loop
for (;;) {
#define RECONNECT_DELAY 5
if (!device_acq_thr_enable) {
PINELOG_TRACE("Device acquisition thread disabled. Checking again in %d seconds", RECONNECT_DELAY);
sleep(RECONNECT_DELAY);
continue;
}
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", RECONNECT_DELAY);
sleep(RECONNECT_DELAY);
} else {
PINELOG_TRACE("Found device, disabling acquisition thread, enable update thread");
device_acq_thr_enable = false;
device_upd_thr_enable = true;
}
} else {
PINELOG_TRACE("Device is connected, disable acquisition thread, enable update thread");
device_acq_thr_enable = false;
device_upd_thr_enable = true;
}
#undef RECONNECT_DELAY
}
return NULL;
}
/*
* Device update thread
* This is a thread that updates the joystick.
*/
static pthread_t device_upd_thr;
static volatile bool device_update_needed;
static void *x52_dev_upd(void *param)
{
PINELOG_INFO(_("Starting X52 device update thread"));
// Check if the device needs to be updated in a loop
for (;;) {
#define UPDATE_CHECK_DELAY 50000 // Wait for this many useconds
if (!device_update_needed || !device_upd_thr_enable) {
usleep(UPDATE_CHECK_DELAY);
continue;
}
(void)x52d_dev_update();
#undef UPDATE_CHECK_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 threads
pthread_create(&device_acq_thr, NULL, x52_dev_acq, NULL);
// With libx52.so.2.3.0, libx52_init will also attempt to connect to a
// supported joystick. Check if a device is already connected before
// enabling the device acquisition thread.
device_acq_thr_enable = !libx52_is_connected(x52_dev);
pthread_create(&device_upd_thr, NULL, x52_dev_upd, NULL);
device_update_needed = false;
device_upd_thr_enable = libx52_is_connected(x52_dev);
}
void x52d_dev_exit(void)
{
// Shutdown any threads
PINELOG_INFO(_("Shutting down X52 device acquisition thread"));
pthread_cancel(device_acq_thr);
PINELOG_INFO(_("Shutting down X52 device update thread"));
pthread_cancel(device_upd_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 and spawn thread to reconnect
PINELOG_TRACE("Disconnecting detached device");
libx52_disconnect(x52_dev);
PINELOG_TRACE("Disabling device update thread");
device_upd_thr_enable = false;
PINELOG_TRACE("Signaling device search thread");
device_acq_thr_enable = true;
} 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

94
daemon/x52d_led.c 100644
View File

@ -0,0 +1,94 @@
/*
* 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>
#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", \
libx52_led_id_to_str(LIBX52_LED_ ## led), \
libx52_led_state_to_str(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);
}

201
daemon/x52d_main.c 100644
View File

@ -0,0 +1,201 @@
/*
* 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 <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 "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 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 = 0;
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] [-l log-file] [-o override] [-c config-file]\n"),
X52D_APP_NAME);
exit(exit_code);
}
int main(int argc, char **argv)
{
int verbosity = 0;
bool quiet = false;
bool foreground = false;
char *log_file = NULL;
char *conf_file = NULL;
int opt;
/* Initialize gettext */
#if ENABLE_NLS
setlocale(LC_ALL, "");
bindtextdomain(PACKAGE, LOCALEDIR);
textdomain(PACKAGE);
#endif
/* Set system defaults */
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
*/
while ((opt = getopt(argc, argv, "fvql:o:c: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 '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);
set_log_file(foreground, log_file);
x52d_config_load(conf_file);
// Initialize signal handlers
listen_signal(SIGINT, termination_handler);
listen_signal(SIGTERM, termination_handler);
listen_signal(SIGQUIT, termination_handler);
listen_signal(SIGHUP, reload_handler);
// Start device threads
x52d_dev_init();
x52d_clock_init();
// Apply configuration
x52d_config_apply();
flag_quit = 0;
while(!flag_quit) {
// TODO: Replace with main event loop
// Let all threads run in background forever
sleep(600);
/* 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;
}
}
// Stop device threads
x52d_clock_exit();
x52d_dev_exit();
PINELOG_INFO(_("Shutting down X52 daemon"));
return 0;
}

View File

@ -4,12 +4,10 @@
#
# SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
ACLOCAL_AMFLAGS = -I m4
bin_PROGRAMS = x52evtest
bin_PROGRAMS += x52evtest
# Event test utility that works similarly to the Linux evtest
x52evtest_SOURCES = ev_test.c
x52evtest_CFLAGS = -I $(top_srcdir)/lib/libx52io -I $(top_srcdir) -DLOCALEDIR=\"$(localedir)\" $(WARN_CFLAGS)
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 = ../../lib/libx52io/libx52io.la
x52evtest_LDADD = libx52io.la

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

View File

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

View File

@ -1,32 +0,0 @@
# Automake for libusbx52 and associated utilities
#
# 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
# Use the pthread compiler variables
LIBS += $(PTHREAD_LIBS)
AM_CFLAGS = $(PTHREAD_CFLAGS)
CC = $(PTHREAD_CC)
# libusb stub library for use by test programs
check_LTLIBRARIES = libusbx52.la
libusbx52_la_SOURCES = usb_x52_stub.c fopen_env.c
libusbx52_la_CFLAGS = @LIBUSB_CFLAGS@ $(WARN_CFLAGS)
libusbx52_la_LDFLAGS = -rpath /nowhere -module $(WARN_LDFLAGS)
# Utility programs for use by tests
check_PROGRAMS = x52test_create_device_list x52test_log_actions
x52test_create_device_list_SOURCES = util/create_device_list.c $(libusbx52_la_SOURCES)
x52test_create_device_list_CFLAGS = @LIBUSB_CFLAGS@ $(WARN_CFLAGS)
x52test_create_device_list_LDFLAGS = $(WARN_LDFLAGS)
x52test_log_actions_SOURCES = util/log_actions.c $(libusbx52_la_SOURCES)
x52test_log_actions_CFLAGS = @X52_INCLUDE@ @LIBUSB_CFLAGS@ $(WARN_CFLAGS)
x52test_log_actions_LDFLAGS = $(WARN_LDFLAGS)
EXTRA_DIST = README.md libusbx52.h

View File

@ -1,32 +0,0 @@
# Automake for libx52util
#
# 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
lib_LTLIBRARIES = libx52util.la
# libx52 utility library
# This library provides extra utilities for ease of use
nodist_libx52util_la_SOURCES = util_char_map.c
libx52util_la_SOURCES = x52_char_map_lookup.c
libx52util_la_CFLAGS = -I $(top_srcdir)/lib/libx52 $(WARN_CFLAGS)
libx52util_la_LDFLAGS = -version-info 1:0:0 $(WARN_LDFLAGS)
libx52util_la_LIBADD = ../libx52/libx52.la
# Header files that need to be copied
x52includedir = $(includedir)/libx52
x52include_HEADERS = libx52util.h
# Extra files that need to be in the distribution
EXTRA_DIST = x52_char_map.cfg \
x52_char_map.h \
x52_char_map_gen.py
# Autogenerated file that needs to be cleaned up
CLEANFILES = util_char_map.c
util_char_map.c: $(srcdir)/x52_char_map.cfg x52_char_map_gen.py
$(AM_V_GEN) $(PYTHON) $(srcdir)/x52_char_map_gen.py $(srcdir)/x52_char_map.cfg $@

37
lib/pinelog/.gitignore vendored 100644
View File

@ -0,0 +1,37 @@
# Compiled object files
*.o
# Generated objects (source, executables, tarballs, etc.)
# 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
*.log
*.trs
*.pc
# Build directory
/build/

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Nirenjan Krishnan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,134 @@
# Top level Automake for pinelog
#
# Copyright (C) 2012-2018 Nirenjan Krishnan (nirenjan@nirenjan.org)
#
# SPDX-License-Identifier: MIT
ACLOCAL_AMFLAGS = -I m4
# Extra files that need to be in the distribution
EXTRA_DIST = \
LICENSE \
README.md \
pinelog.h
noinst_LTLIBRARIES = libpinelog.la
# pinelog logging library
libpinelog_la_SOURCES = pinelog.c
libpinelog_la_CFLAGS = @PINELOG_CFLAGS@ $(WARN_CFLAGS) -I $(top_builddir)
libpinelog_la_LDFLAGS = $(WARN_LDFLAGS)
test_SRCFILES = test_pinelog.c $(libpinelog_la_SOURCES)
bench_SRCFILES = bench_pinelog.c $(libpinelog_la_SOURCES)
test_CFLAGS = \
-DPINELOG_FATAL_STR='"F"' \
-DPINELOG_ERROR_STR='"E"' \
-DPINELOG_WARNING_STR='"W"' \
-DPINELOG_INFO_STR='"I"' \
-DPINELOG_DEBUG_STR='"D"' \
-DPINELOG_TRACE_STR='"T"' \
-DPINELOG_DEFAULT_LEVEL=PINELOG_LVL_TRACE \
-DPINELOG_DEFAULT_STREAM=stderr \
-DPINELOG_TEST -I $(top_builddir)
LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) $(top_srcdir)/tap-driver.sh
TESTS = \
test_ts_lvl_tr \
test_ts_lvl_notr \
test_ts_nolvl_tr \
test_ts_nolvl_notr \
test_nots_lvl_tr \
test_nots_lvl_notr \
test_nots_nolvl_tr \
test_nots_nolvl_notr \
bench_ts_lvl_tr \
bench_ts_lvl_notr \
bench_ts_nolvl_tr \
bench_ts_nolvl_notr \
bench_nots_lvl_tr \
bench_nots_lvl_notr \
bench_nots_nolvl_tr \
bench_nots_nolvl_notr
check_PROGRAMS = $(TESTS)
test_ts_lvl_tr_SOURCES = $(test_SRCFILES)
test_ts_lvl_tr_CFLAGS = $(WARN_CFLAGS) $(test_CFLAGS) \
-DPINELOG_SHOW_DATE=1 -DPINELOG_SHOW_LEVEL=1 -DPINELOG_SHOW_BACKTRACE=1
test_ts_lvl_tr_LDFLAGS = $(WARN_LDFLAGS)
test_ts_lvl_notr_SOURCES = $(test_SRCFILES)
test_ts_lvl_notr_CFLAGS = $(WARN_CFLAGS) $(test_CFLAGS) \
-DPINELOG_SHOW_DATE=1 -DPINELOG_SHOW_LEVEL=1 -DPINELOG_SHOW_BACKTRACE=0
test_ts_lvl_notr_LDFLAGS = $(WARN_LDFLAGS)
test_ts_nolvl_tr_SOURCES = $(test_SRCFILES)
test_ts_nolvl_tr_CFLAGS = $(WARN_CFLAGS) $(test_CFLAGS) \
-DPINELOG_SHOW_DATE=1 -DPINELOG_SHOW_LEVEL=0 -DPINELOG_SHOW_BACKTRACE=1
test_ts_nolvl_tr_LDFLAGS = $(WARN_LDFLAGS)
test_ts_nolvl_notr_SOURCES = $(test_SRCFILES)
test_ts_nolvl_notr_CFLAGS = $(WARN_CFLAGS) $(test_CFLAGS) \
-DPINELOG_SHOW_DATE=1 -DPINELOG_SHOW_LEVEL=0 -DPINELOG_SHOW_BACKTRACE=0
test_ts_nolvl_notr_LDFLAGS = $(WARN_LDFLAGS)
test_nots_lvl_tr_SOURCES = $(test_SRCFILES)
test_nots_lvl_tr_CFLAGS = $(WARN_CFLAGS) $(test_CFLAGS) \
-DPINELOG_SHOW_DATE=0 -DPINELOG_SHOW_LEVEL=1 -DPINELOG_SHOW_BACKTRACE=1
test_nots_lvl_tr_LDFLAGS = $(WARN_LDFLAGS)
test_nots_lvl_notr_SOURCES = $(test_SRCFILES)
test_nots_lvl_notr_CFLAGS = $(WARN_CFLAGS) $(test_CFLAGS) \
-DPINELOG_SHOW_DATE=0 -DPINELOG_SHOW_LEVEL=1 -DPINELOG_SHOW_BACKTRACE=0
test_nots_lvl_notr_LDFLAGS = $(WARN_LDFLAGS)
test_nots_nolvl_tr_SOURCES = $(test_SRCFILES)
test_nots_nolvl_tr_CFLAGS = $(WARN_CFLAGS) $(test_CFLAGS) \
-DPINELOG_SHOW_DATE=0 -DPINELOG_SHOW_LEVEL=0 -DPINELOG_SHOW_BACKTRACE=1
test_nots_nolvl_tr_LDFLAGS = $(WARN_LDFLAGS)
test_nots_nolvl_notr_SOURCES = $(test_SRCFILES)
test_nots_nolvl_notr_CFLAGS = $(WARN_CFLAGS) $(test_CFLAGS) \
-DPINELOG_SHOW_DATE=0 -DPINELOG_SHOW_LEVEL=0 -DPINELOG_SHOW_BACKTRACE=0
test_nots_nolvl_notr_LDFLAGS = $(WARN_LDFLAGS)
bench_ts_lvl_tr_SOURCES = $(bench_SRCFILES)
bench_ts_lvl_tr_CFLAGS = $(WARN_CFLAGS) $(test_CFLAGS) \
-DPINELOG_SHOW_DATE=1 -DPINELOG_SHOW_LEVEL=1 -DPINELOG_SHOW_BACKTRACE=1
bench_ts_lvl_tr_LDFLAGS = $(WARN_LDFLAGS)
bench_ts_lvl_notr_SOURCES = $(bench_SRCFILES)
bench_ts_lvl_notr_CFLAGS = $(WARN_CFLAGS) $(test_CFLAGS) \
-DPINELOG_SHOW_DATE=1 -DPINELOG_SHOW_LEVEL=1 -DPINELOG_SHOW_BACKTRACE=0
bench_ts_lvl_notr_LDFLAGS = $(WARN_LDFLAGS)
bench_ts_nolvl_tr_SOURCES = $(bench_SRCFILES)
bench_ts_nolvl_tr_CFLAGS = $(WARN_CFLAGS) $(test_CFLAGS) \
-DPINELOG_SHOW_DATE=1 -DPINELOG_SHOW_LEVEL=0 -DPINELOG_SHOW_BACKTRACE=1
bench_ts_nolvl_tr_LDFLAGS = $(WARN_LDFLAGS)
bench_ts_nolvl_notr_SOURCES = $(bench_SRCFILES)
bench_ts_nolvl_notr_CFLAGS = $(WARN_CFLAGS) $(test_CFLAGS) \
-DPINELOG_SHOW_DATE=1 -DPINELOG_SHOW_LEVEL=0 -DPINELOG_SHOW_BACKTRACE=0
bench_ts_nolvl_notr_LDFLAGS = $(WARN_LDFLAGS)
bench_nots_lvl_tr_SOURCES = $(bench_SRCFILES)
bench_nots_lvl_tr_CFLAGS = $(WARN_CFLAGS) $(test_CFLAGS) \
-DPINELOG_SHOW_DATE=0 -DPINELOG_SHOW_LEVEL=1 -DPINELOG_SHOW_BACKTRACE=1
bench_nots_lvl_tr_LDFLAGS = $(WARN_LDFLAGS)
bench_nots_lvl_notr_SOURCES = $(bench_SRCFILES)
bench_nots_lvl_notr_CFLAGS = $(WARN_CFLAGS) $(test_CFLAGS) \
-DPINELOG_SHOW_DATE=0 -DPINELOG_SHOW_LEVEL=1 -DPINELOG_SHOW_BACKTRACE=0
bench_nots_lvl_notr_LDFLAGS = $(WARN_LDFLAGS)
bench_nots_nolvl_tr_SOURCES = $(bench_SRCFILES)
bench_nots_nolvl_tr_CFLAGS = $(WARN_CFLAGS) $(test_CFLAGS) \
-DPINELOG_SHOW_DATE=0 -DPINELOG_SHOW_LEVEL=0 -DPINELOG_SHOW_BACKTRACE=1
bench_nots_nolvl_tr_LDFLAGS = $(WARN_LDFLAGS)
bench_nots_nolvl_notr_SOURCES = $(bench_SRCFILES)
bench_nots_nolvl_notr_CFLAGS = $(WARN_CFLAGS) $(test_CFLAGS) \
-DPINELOG_SHOW_DATE=0 -DPINELOG_SHOW_LEVEL=0 -DPINELOG_SHOW_BACKTRACE=0
bench_nots_nolvl_notr_LDFLAGS = $(WARN_LDFLAGS)

View File

@ -0,0 +1,135 @@
Pinelog - a lightweight logging API
===================================
Pinelog is a lightweight logging API for C programs that's designed to be
included in your program source code. Parameters for Pinelog are configured at
build time by means of preprocessor flags.
# Usage
## Logging macros
Pinelog uses `printf` style formatting, using the following list of macros. The
macro indicates the level at which the message is logged.
* `PINELOG_FATAL`
* `PINELOG_ERROR`
* `PINELOG_WARN`
* `PINELOG_INFO`
* `PINELOG_DEBUG`
* `PINELOG_TRACE`
**Note:** `PINELOG_FATAL` is used when the program encounters a fatal condition
and needs to abort. This will log the fatal message and terminate the program
with an exit code of 1.
### Example
```C
PINELOG_INFO("configuration file %s not found, using defaults", config_file);
```
## Logging levels
The default logging level is `ERROR`, and this can be controlled by the
preprocessor flag `PINELOG_DEFAULT_LEVEL`.
The program can control the level at which messages can be logged at runtime,
by using the `pinelog_set_level` function. This function takes in the level
definition, which is one of the following, in increasing order of priority.
* `PINELOG_LVL_TRACE`
* `PINELOG_LVL_DEBUG`
* `PINELOG_LVL_INFO`
* `PINELOG_LVL_WARNING`
* `PINELOG_LVL_ERROR`
* `PINELOG_LVL_FATAL`
* `PINELOG_LVL_NONE`
Setting the level to a given priority suppresses all log messages of lower
priority, i.e., if the level is set to `PINELOG_LVL_ERROR`, messages at
`WARNING` level and below will be suppressed, but `ERROR` and `FATAL` messages
will be logged.
**Note:** `PINELOG_LVL_NONE` suppresses all log messages, but `PINELOG_FATAL`
will still terminate the program, even though nothing is logged.
### Example
```C
pinelog_set_level(PINELOG_LVL_WARNING);
```
```
-DPINELOG_DEFAULT_LEVEL=PINELOG_LVL_WARNING
```
## Output redirection
Pinelog defaults to writing the log messages to standard output, and this can
be controlled by the preprocessor flag `PINELOG_DEFAULT_STREAM`.
However, the application can redirect log messages at runtime to a different
`FILE *` stream, or to a file by using one of the following two methods:
```C
FILE *out = fopen("/run/app.fifo", "w");
pinelog_set_output_stream(out);
pinelog_set_output_file("/var/log/app.log");
```
```
-DPINELOG_DEFAULT_STREAM=stderr
```
## Logging format
Pinelog uses an opinionated logging format that is fixed as follows. Fields
within `[]` are optional and controlled by build time flags.
[2021-07-14 11:08:04 ][ERROR: ][./test_pinelog.c:108 ]formatted message.
The program can be controlled by the following preprocessor flags, all of which
default to `0` (disabled). Set the flag to `1` to enable it.
* `PINELOG_SHOW_DATE` - Display the ISO 8601 date and time when the message is
logged.
* `PINELOG_SHOW_LEVEL` - Display the level at which the message is logged.
* `PINELOG_SHOW_BACKTRACE` - Display the file and line where the message is
logged.
Set these flags by using the `-D` compiler argument, .e.g.
`-DPINELOG_SHOW_LEVEL=1 -DPINELOG_SHOW_DATE=1`
### Level strings
The application can control the level strings displayed by means of preprocessor
flags, if the application wishes to display the log messages in a language other
than English. This can be achieved by means of the following preprocessor
definitions.
* `PINELOG_FATAL_STR`
* `PINELOG_ERROR_STR`
* `PINELOG_WARNING_STR`
* `PINELOG_INFO_STR`
* `PINELOG_DEBUG_STR`
* `PINELOG_TRACE_STR`
### Example
```
-DPINELOG_ERROR_STR=\"E\" -DPINELOG_FATAL_STR=\"F\"
```
# Integrating Pinelog
Pinelog is intended to be integrated into your application source tree, either
by means of including the sources directly, or by including the repository as
a Git submodule or subtree.
The default build of Pinelog uses an autotools generated `config.h` file, which
includes checks for the following GCC attributes. If you don't care about these,
then either create a dummy config.h which includes the macros
`HAVE_FUNC_ATTRIBUTE_CONSTRUCTOR`, `HAVE_FUNC_ATTRIBUTE_DESTRUCTOR` and
`HAVE_FUNC_ATTRIBUTE_FORMAT`, or use the `AX_GCC_FUNC_ATTRIBUTE` macro to check
for the `constructor`, `destructor` and `format` attributes in your
application's `configure.ac` file.

View File

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

View File

@ -0,0 +1,83 @@
/*
* Pinelog lightweight logging library - test harness
*
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: MIT
*/
#define pinelog_exit(_)
#include "pinelog.h"
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <time.h>
#define BENCH_COUNT 100000
static void print_time_difference(const char *type, struct timespec *ts)
{
struct timespec ret;
uint64_t timeper;
uint64_t tp_usec;
uint64_t tp_nsec;
// ts is a pointer to a 2 element array, second is always later
ret.tv_sec = ts[1].tv_sec - ts[0].tv_sec;
ret.tv_nsec = ts[1].tv_nsec - ts[0].tv_nsec;
if (ts[0].tv_nsec > ts[1].tv_nsec) {
ret.tv_nsec += 1000000000;
ret.tv_sec--;
}
timeper = (ret.tv_sec * 1000000000 + ret.tv_nsec) / BENCH_COUNT;
tp_usec = timeper / 1000;
tp_nsec = timeper % 1000;
printf("# %s %"PRIu64".%03"PRIu64"\u03BCs/log (Total %lu.%09lds)\n",
type, tp_usec, tp_nsec, ret.tv_sec, ret.tv_nsec);
}
int main(int argc, char **argv)
{
struct timespec ts_wall[2];
struct timespec ts_cpu[2];
int i;
/* Set up defaults */
pinelog_set_level(PINELOG_LVL_ERROR);
pinelog_set_output_file("/dev/null");
printf("# Timing logging for %u iterations\n", BENCH_COUNT);
if (clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts_cpu[0]) < 0) {
perror("clock_gettime cputime 0");
return 1;
}
if (clock_gettime(CLOCK_MONOTONIC, &ts_wall[0]) < 0) {
perror("clock_gettime monotonic 0");
return 1;
}
for (i = 0; i < BENCH_COUNT; i++) {
PINELOG_ERROR("Testing error log #%u of %u", i, BENCH_COUNT);
}
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts_cpu[1]);
clock_gettime(CLOCK_MONOTONIC, &ts_wall[1]);
// Add a test plan and to avoid the TAP harness from flagging this as a
// failed test.
puts("1..1");
printf("ok 1 Benchmark pinelog %stimestamp, %slevel, %sbacktrace\n",
PINELOG_SHOW_DATE ? "": "no ",
PINELOG_SHOW_LEVEL ? "": "no ",
PINELOG_SHOW_BACKTRACE ? "": "no ");
print_time_difference("cpu time", ts_cpu);
print_time_difference("wall time", ts_wall);
return 0;
}

View File

@ -0,0 +1,29 @@
# Autoconf settings for pinelog
#
# Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
#
# SPDX-License-Identifier: MIT
AC_INIT([pinelog], [1.0.0], [nirenjan@nirenjan.org])
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
AM_PROG_AR
LT_INIT
PKG_PROG_PKG_CONFIG
PKG_INSTALLDIR
AX_COMPILER_FLAGS
AX_GCC_FUNC_ATTRIBUTE([constructor])
AX_GCC_FUNC_ATTRIBUTE([destructor])
AX_GCC_FUNC_ATTRIBUTE([format])
AC_SUBST([PINELOG_CFLAGS])
# Configuration headers
AC_CONFIG_HEADERS([config.h])
AC_CONFIG_FILES([Makefile])
AC_OUTPUT

View File

@ -0,0 +1,46 @@
# ============================================================================
# https://www.gnu.org/software/autoconf-archive/ax_append_compile_flags.html
# ============================================================================
#
# SYNOPSIS
#
# AX_APPEND_COMPILE_FLAGS([FLAG1 FLAG2 ...], [FLAGS-VARIABLE], [EXTRA-FLAGS], [INPUT])
#
# DESCRIPTION
#
# For every FLAG1, FLAG2 it is checked whether the compiler works with the
# flag. If it does, the flag is added FLAGS-VARIABLE
#
# If FLAGS-VARIABLE is not specified, the current language's flags (e.g.
# CFLAGS) is used. During the check the flag is always added to the
# current language's flags.
#
# If EXTRA-FLAGS is defined, it is added to the current language's default
# flags (e.g. CFLAGS) when the check is done. The check is thus made with
# the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to
# force the compiler to issue an error when a bad flag is given.
#
# INPUT gives an alternative input source to AC_COMPILE_IFELSE.
#
# NOTE: This macro depends on the AX_APPEND_FLAG and
# AX_CHECK_COMPILE_FLAG. Please keep this macro in sync with
# AX_APPEND_LINK_FLAGS.
#
# LICENSE
#
# Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com>
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice
# and this notice are preserved. This file is offered as-is, without any
# warranty.
#serial 7
AC_DEFUN([AX_APPEND_COMPILE_FLAGS],
[AX_REQUIRE_DEFINED([AX_CHECK_COMPILE_FLAG])
AX_REQUIRE_DEFINED([AX_APPEND_FLAG])
for flag in $1; do
AX_CHECK_COMPILE_FLAG([$flag], [AX_APPEND_FLAG([$flag], [$2])], [], [$3], [$4])
done
])dnl AX_APPEND_COMPILE_FLAGS

View File

@ -0,0 +1,50 @@
# ===========================================================================
# https://www.gnu.org/software/autoconf-archive/ax_append_flag.html
# ===========================================================================
#
# SYNOPSIS
#
# AX_APPEND_FLAG(FLAG, [FLAGS-VARIABLE])
#
# DESCRIPTION
#
# FLAG is appended to the FLAGS-VARIABLE shell variable, with a space
# added in between.
#
# If FLAGS-VARIABLE is not specified, the current language's flags (e.g.
# CFLAGS) is used. FLAGS-VARIABLE is not changed if it already contains
# FLAG. If FLAGS-VARIABLE is unset in the shell, it is set to exactly
# FLAG.
#
# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION.
#
# LICENSE
#
# Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de>
# Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com>
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice
# and this notice are preserved. This file is offered as-is, without any
# warranty.
#serial 8
AC_DEFUN([AX_APPEND_FLAG],
[dnl
AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_SET_IF
AS_VAR_PUSHDEF([FLAGS], [m4_default($2,_AC_LANG_PREFIX[FLAGS])])
AS_VAR_SET_IF(FLAGS,[
AS_CASE([" AS_VAR_GET(FLAGS) "],
[*" $1 "*], [AC_RUN_LOG([: FLAGS already contains $1])],
[
AS_VAR_APPEND(FLAGS,[" $1"])
AC_RUN_LOG([: FLAGS="$FLAGS"])
])
],
[
AS_VAR_SET(FLAGS,[$1])
AC_RUN_LOG([: FLAGS="$FLAGS"])
])
AS_VAR_POPDEF([FLAGS])dnl
])dnl AX_APPEND_FLAG

View File

@ -0,0 +1,44 @@
# ===========================================================================
# https://www.gnu.org/software/autoconf-archive/ax_append_link_flags.html
# ===========================================================================
#
# SYNOPSIS
#
# AX_APPEND_LINK_FLAGS([FLAG1 FLAG2 ...], [FLAGS-VARIABLE], [EXTRA-FLAGS], [INPUT])
#
# DESCRIPTION
#
# For every FLAG1, FLAG2 it is checked whether the linker works with the
# flag. If it does, the flag is added FLAGS-VARIABLE
#
# If FLAGS-VARIABLE is not specified, the linker's flags (LDFLAGS) is
# used. During the check the flag is always added to the linker's flags.
#
# If EXTRA-FLAGS is defined, it is added to the linker's default flags
# when the check is done. The check is thus made with the flags: "LDFLAGS
# EXTRA-FLAGS FLAG". This can for example be used to force the linker to
# issue an error when a bad flag is given.
#
# INPUT gives an alternative input source to AC_COMPILE_IFELSE.
#
# NOTE: This macro depends on the AX_APPEND_FLAG and AX_CHECK_LINK_FLAG.
# Please keep this macro in sync with AX_APPEND_COMPILE_FLAGS.
#
# LICENSE
#
# Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com>
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice
# and this notice are preserved. This file is offered as-is, without any
# warranty.
#serial 7
AC_DEFUN([AX_APPEND_LINK_FLAGS],
[AX_REQUIRE_DEFINED([AX_CHECK_LINK_FLAG])
AX_REQUIRE_DEFINED([AX_APPEND_FLAG])
for flag in $1; do
AX_CHECK_LINK_FLAG([$flag], [AX_APPEND_FLAG([$flag], [m4_default([$2], [LDFLAGS])])], [], [$3], [$4])
done
])dnl AX_APPEND_LINK_FLAGS

View File

@ -0,0 +1,53 @@
# ===========================================================================
# https://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html
# ===========================================================================
#
# SYNOPSIS
#
# AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT])
#
# DESCRIPTION
#
# Check whether the given FLAG works with the current language's compiler
# or gives an error. (Warnings, however, are ignored)
#
# ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on
# success/failure.
#
# If EXTRA-FLAGS is defined, it is added to the current language's default
# flags (e.g. CFLAGS) when the check is done. The check is thus made with
# the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to
# force the compiler to issue an error when a bad flag is given.
#
# INPUT gives an alternative input source to AC_COMPILE_IFELSE.
#
# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this
# macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG.
#
# LICENSE
#
# Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de>
# Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com>
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice
# and this notice are preserved. This file is offered as-is, without any
# warranty.
#serial 6
AC_DEFUN([AX_CHECK_COMPILE_FLAG],
[AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF
AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl
AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [
ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS
_AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1"
AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])],
[AS_VAR_SET(CACHEVAR,[yes])],
[AS_VAR_SET(CACHEVAR,[no])])
_AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags])
AS_VAR_IF(CACHEVAR,yes,
[m4_default([$2], :)],
[m4_default([$3], :)])
AS_VAR_POPDEF([CACHEVAR])dnl
])dnl AX_CHECK_COMPILE_FLAGS

View File

@ -0,0 +1,53 @@
# ===========================================================================
# https://www.gnu.org/software/autoconf-archive/ax_check_link_flag.html
# ===========================================================================
#
# SYNOPSIS
#
# AX_CHECK_LINK_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT])
#
# DESCRIPTION
#
# Check whether the given FLAG works with the linker or gives an error.
# (Warnings, however, are ignored)
#
# ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on
# success/failure.
#
# If EXTRA-FLAGS is defined, it is added to the linker's default flags
# when the check is done. The check is thus made with the flags: "LDFLAGS
# EXTRA-FLAGS FLAG". This can for example be used to force the linker to
# issue an error when a bad flag is given.
#
# INPUT gives an alternative input source to AC_LINK_IFELSE.
#
# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this
# macro in sync with AX_CHECK_{PREPROC,COMPILE}_FLAG.
#
# LICENSE
#
# Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de>
# Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com>
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice
# and this notice are preserved. This file is offered as-is, without any
# warranty.
#serial 6
AC_DEFUN([AX_CHECK_LINK_FLAG],
[AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF
AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_ldflags_$4_$1])dnl
AC_CACHE_CHECK([whether the linker accepts $1], CACHEVAR, [
ax_check_save_flags=$LDFLAGS
LDFLAGS="$LDFLAGS $4 $1"
AC_LINK_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])],
[AS_VAR_SET(CACHEVAR,[yes])],
[AS_VAR_SET(CACHEVAR,[no])])
LDFLAGS=$ax_check_save_flags])
AS_VAR_IF(CACHEVAR,yes,
[m4_default([$2], :)],
[m4_default([$3], :)])
AS_VAR_POPDEF([CACHEVAR])dnl
])dnl AX_CHECK_LINK_FLAGS

View File

@ -0,0 +1,158 @@
# ===========================================================================
# https://www.gnu.org/software/autoconf-archive/ax_compiler_flags.html
# ===========================================================================
#
# SYNOPSIS
#
# AX_COMPILER_FLAGS([CFLAGS-VARIABLE], [LDFLAGS-VARIABLE], [IS-RELEASE], [EXTRA-BASE-CFLAGS], [EXTRA-YES-CFLAGS], [UNUSED], [UNUSED], [UNUSED], [EXTRA-BASE-LDFLAGS], [EXTRA-YES-LDFLAGS], [UNUSED], [UNUSED], [UNUSED])
#
# DESCRIPTION
#
# Check for the presence of an --enable-compile-warnings option to
# configure, defaulting to "error" in normal operation, or "yes" if
# IS-RELEASE is equal to "yes". Return the value in the variable
# $ax_enable_compile_warnings.
#
# Depending on the value of --enable-compile-warnings, different compiler
# warnings are checked to see if they work with the current compiler and,
# if so, are appended to CFLAGS-VARIABLE and LDFLAGS-VARIABLE. This
# allows a consistent set of baseline compiler warnings to be used across
# a code base, irrespective of any warnings enabled locally by individual
# developers. By standardising the warnings used by all developers of a
# project, the project can commit to a zero-warnings policy, using -Werror
# to prevent compilation if new warnings are introduced. This makes
# catching bugs which are flagged by warnings a lot easier.
#
# By providing a consistent --enable-compile-warnings argument across all
# projects using this macro, continuous integration systems can easily be
# configured the same for all projects. Automated systems or build
# systems aimed at beginners may want to pass the --disable-Werror
# argument to unconditionally prevent warnings being fatal.
#
# --enable-compile-warnings can take the values:
#
# * no: Base compiler warnings only; not even -Wall.
# * yes: The above, plus a broad range of useful warnings.
# * error: The above, plus -Werror so that all warnings are fatal.
# Use --disable-Werror to override this and disable fatal
# warnings.
#
# The set of base and enabled flags can be augmented using the
# EXTRA-*-CFLAGS and EXTRA-*-LDFLAGS variables, which are tested and
# appended to the output variable if --enable-compile-warnings is not
# "no". Flags should not be disabled using these arguments, as the entire
# point of AX_COMPILER_FLAGS is to enforce a consistent set of useful
# compiler warnings on code, using warnings which have been chosen for low
# false positive rates. If a compiler emits false positives for a
# warning, a #pragma should be used in the code to disable the warning
# locally. See:
#
# https://gcc.gnu.org/onlinedocs/gcc-4.9.2/gcc/Diagnostic-Pragmas.html#Diagnostic-Pragmas
#
# The EXTRA-* variables should only be used to supply extra warning flags,
# and not general purpose compiler flags, as they are controlled by
# configure options such as --disable-Werror.
#
# IS-RELEASE can be used to disable -Werror when making a release, which
# is useful for those hairy moments when you just want to get the release
# done as quickly as possible. Set it to "yes" to disable -Werror. By
# default, it uses the value of $ax_is_release, so if you are using the
# AX_IS_RELEASE macro, there is no need to pass this parameter. For
# example:
#
# AX_IS_RELEASE([git-directory])
# AX_COMPILER_FLAGS()
#
# CFLAGS-VARIABLE defaults to WARN_CFLAGS, and LDFLAGS-VARIABLE defaults
# to WARN_LDFLAGS. Both variables are AC_SUBST-ed by this macro, but must
# be manually added to the CFLAGS and LDFLAGS variables for each target in
# the code base.
#
# If C++ language support is enabled with AC_PROG_CXX, which must occur
# before this macro in configure.ac, warning flags for the C++ compiler
# are AC_SUBST-ed as WARN_CXXFLAGS, and must be manually added to the
# CXXFLAGS variables for each target in the code base. EXTRA-*-CFLAGS can
# be used to augment the base and enabled flags.
#
# Warning flags for g-ir-scanner (from GObject Introspection) are
# AC_SUBST-ed as WARN_SCANNERFLAGS. This variable must be manually added
# to the SCANNERFLAGS variable for each GIR target in the code base. If
# extra g-ir-scanner flags need to be enabled, the AX_COMPILER_FLAGS_GIR
# macro must be invoked manually.
#
# AX_COMPILER_FLAGS may add support for other tools in future, in addition
# to the compiler and linker. No extra EXTRA-* variables will be added
# for those tools, and all extra support will still use the single
# --enable-compile-warnings configure option. For finer grained control
# over the flags for individual tools, use AX_COMPILER_FLAGS_CFLAGS,
# AX_COMPILER_FLAGS_LDFLAGS and AX_COMPILER_FLAGS_* for new tools.
#
# The UNUSED variables date from a previous version of this macro, and are
# automatically appended to the preceding non-UNUSED variable. They should
# be left empty in new uses of the macro.
#
# LICENSE
#
# Copyright (c) 2014, 2015 Philip Withnall <philip@tecnocode.co.uk>
# Copyright (c) 2015 David King <amigadave@amigadave.com>
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice
# and this notice are preserved. This file is offered as-is, without any
# warranty.
#serial 14
# _AX_COMPILER_FLAGS_LANG([LANGNAME])
m4_defun([_AX_COMPILER_FLAGS_LANG],
[m4_ifdef([_AX_COMPILER_FLAGS_LANG_]$1[_enabled], [],
[m4_define([_AX_COMPILER_FLAGS_LANG_]$1[_enabled], [])dnl
AX_REQUIRE_DEFINED([AX_COMPILER_FLAGS_]$1[FLAGS])])dnl
])
AC_DEFUN([AX_COMPILER_FLAGS],[
# C support is enabled by default.
_AX_COMPILER_FLAGS_LANG([C])
# Only enable C++ support if AC_PROG_CXX is called. The redefinition of
# AC_PROG_CXX is so that a fatal error is emitted if this macro is called
# before AC_PROG_CXX, which would otherwise cause no C++ warnings to be
# checked.
AC_PROVIDE_IFELSE([AC_PROG_CXX],
[_AX_COMPILER_FLAGS_LANG([CXX])],
[m4_define([AC_PROG_CXX], defn([AC_PROG_CXX])[_AX_COMPILER_FLAGS_LANG([CXX])])])
AX_REQUIRE_DEFINED([AX_COMPILER_FLAGS_LDFLAGS])
# Default value for IS-RELEASE is $ax_is_release
ax_compiler_flags_is_release=m4_tolower(m4_normalize(ifelse([$3],,
[$ax_is_release],
[$3])))
AC_ARG_ENABLE([compile-warnings],
AS_HELP_STRING([--enable-compile-warnings=@<:@no/yes/error@:>@],
[Enable compiler warnings and errors]),,
[AS_IF([test "$ax_compiler_flags_is_release" = "yes"],
[enable_compile_warnings="yes"],
[enable_compile_warnings="error"])])
AC_ARG_ENABLE([Werror],
AS_HELP_STRING([--disable-Werror],
[Unconditionally make all compiler warnings non-fatal]),,
[enable_Werror=maybe])
# Return the user's chosen warning level
AS_IF([test "$enable_Werror" = "no" -a \
"$enable_compile_warnings" = "error"],[
enable_compile_warnings="yes"
])
ax_enable_compile_warnings=$enable_compile_warnings
AX_COMPILER_FLAGS_CFLAGS([$1],[$ax_compiler_flags_is_release],
[$4],[$5 $6 $7 $8])
m4_ifdef([_AX_COMPILER_FLAGS_LANG_CXX_enabled],
[AX_COMPILER_FLAGS_CXXFLAGS([WARN_CXXFLAGS],
[$ax_compiler_flags_is_release],
[$4],[$5 $6 $7 $8])])
AX_COMPILER_FLAGS_LDFLAGS([$2],[$ax_compiler_flags_is_release],
[$9],[$10 $11 $12 $13])
AX_COMPILER_FLAGS_GIR([WARN_SCANNERFLAGS],[$ax_compiler_flags_is_release])
])dnl AX_COMPILER_FLAGS

View File

@ -0,0 +1,161 @@
# =============================================================================
# https://www.gnu.org/software/autoconf-archive/ax_compiler_flags_cflags.html
# =============================================================================
#
# SYNOPSIS
#
# AX_COMPILER_FLAGS_CFLAGS([VARIABLE], [IS-RELEASE], [EXTRA-BASE-FLAGS], [EXTRA-YES-FLAGS])
#
# DESCRIPTION
#
# Add warning flags for the C compiler to VARIABLE, which defaults to
# WARN_CFLAGS. VARIABLE is AC_SUBST-ed by this macro, but must be
# manually added to the CFLAGS variable for each target in the code base.
#
# This macro depends on the environment set up by AX_COMPILER_FLAGS.
# Specifically, it uses the value of $ax_enable_compile_warnings to decide
# which flags to enable.
#
# LICENSE
#
# Copyright (c) 2014, 2015 Philip Withnall <philip@tecnocode.co.uk>
# Copyright (c) 2017, 2018 Reini Urban <rurban@cpan.org>
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice
# and this notice are preserved. This file is offered as-is, without any
# warranty.
#serial 17
AC_DEFUN([AX_COMPILER_FLAGS_CFLAGS],[
AC_REQUIRE([AC_PROG_SED])
AX_REQUIRE_DEFINED([AX_APPEND_COMPILE_FLAGS])
AX_REQUIRE_DEFINED([AX_APPEND_FLAG])
AX_REQUIRE_DEFINED([AX_CHECK_COMPILE_FLAG])
# Variable names
m4_define([ax_warn_cflags_variable],
[m4_normalize(ifelse([$1],,[WARN_CFLAGS],[$1]))])
AC_LANG_PUSH([C])
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
[#ifndef __cplusplus
#error "no C++"
#endif]])],
[ax_compiler_cxx=yes;],
[ax_compiler_cxx=no;])
# Always pass -Werror=unknown-warning-option to get Clang to fail on bad
# flags, otherwise they are always appended to the warn_cflags variable, and
# Clang warns on them for every compilation unit.
# If this is passed to GCC, it will explode, so the flag must be enabled
# conditionally.
AX_CHECK_COMPILE_FLAG([-Werror=unknown-warning-option],[
ax_compiler_flags_test="-Werror=unknown-warning-option"
],[
ax_compiler_flags_test=""
])
# Check that -Wno-suggest-attribute=format is supported
AX_CHECK_COMPILE_FLAG([-Wno-suggest-attribute=format],[
ax_compiler_no_suggest_attribute_flags="-Wno-suggest-attribute=format"
],[
ax_compiler_no_suggest_attribute_flags=""
])
# Base flags
AX_APPEND_COMPILE_FLAGS([ dnl
-fno-strict-aliasing dnl
$3 dnl
],ax_warn_cflags_variable,[$ax_compiler_flags_test])
AS_IF([test "$ax_enable_compile_warnings" != "no"],[
if test "$ax_compiler_cxx" = "no" ; then
# C-only flags. Warn in C++
AX_APPEND_COMPILE_FLAGS([ dnl
-Wnested-externs dnl
-Wmissing-prototypes dnl
-Wstrict-prototypes dnl
-Wdeclaration-after-statement dnl
-Wimplicit-function-declaration dnl
-Wold-style-definition dnl
-Wjump-misses-init dnl
],ax_warn_cflags_variable,[$ax_compiler_flags_test])
fi
# "yes" flags
AX_APPEND_COMPILE_FLAGS([ dnl
-Wall dnl
-Wextra dnl
-Wundef dnl
-Wwrite-strings dnl
-Wpointer-arith dnl
-Wmissing-declarations dnl
-Wredundant-decls dnl
-Wno-unused-parameter dnl
-Wno-missing-field-initializers dnl
-Wformat=2 dnl
-Wcast-align dnl
-Wformat-nonliteral dnl
-Wformat-security dnl
-Wsign-compare dnl
-Wstrict-aliasing dnl
-Wshadow dnl
-Winline dnl
-Wpacked dnl
-Wmissing-format-attribute dnl
-Wmissing-noreturn dnl
-Winit-self dnl
-Wredundant-decls dnl
-Wmissing-include-dirs dnl
-Wunused-but-set-variable dnl
-Warray-bounds dnl
-Wreturn-type dnl
-Wswitch-enum dnl
-Wswitch-default dnl
-Wduplicated-cond dnl
-Wduplicated-branches dnl
-Wlogical-op dnl
-Wrestrict dnl
-Wnull-dereference dnl
-Wdouble-promotion dnl
$4 dnl
$5 dnl
$6 dnl
$7 dnl
],ax_warn_cflags_variable,[$ax_compiler_flags_test])
])
AS_IF([test "$ax_enable_compile_warnings" = "error"],[
# "error" flags; -Werror has to be appended unconditionally because
# it's not possible to test for
#
# suggest-attribute=format is disabled because it gives too many false
# positives
AX_APPEND_FLAG([-Werror],ax_warn_cflags_variable)
AX_APPEND_COMPILE_FLAGS([ dnl
[$ax_compiler_no_suggest_attribute_flags] dnl
],ax_warn_cflags_variable,[$ax_compiler_flags_test])
])
# In the flags below, when disabling specific flags, always add *both*
# -Wno-foo and -Wno-error=foo. This fixes the situation where (for example)
# we enable -Werror, disable a flag, and a build bot passes CFLAGS=-Wall,
# which effectively turns that flag back on again as an error.
for flag in $ax_warn_cflags_variable; do
AS_CASE([$flag],
[-Wno-*=*],[],
[-Wno-*],[
AX_APPEND_COMPILE_FLAGS([-Wno-error=$(AS_ECHO([$flag]) | $SED 's/^-Wno-//')],
ax_warn_cflags_variable,
[$ax_compiler_flags_test])
])
done
AC_LANG_POP([C])
# Substitute the variables
AC_SUBST(ax_warn_cflags_variable)
])dnl AX_COMPILER_FLAGS

View File

@ -0,0 +1,60 @@
# ===========================================================================
# https://www.gnu.org/software/autoconf-archive/ax_compiler_flags_gir.html
# ===========================================================================
#
# SYNOPSIS
#
# AX_COMPILER_FLAGS_GIR([VARIABLE], [IS-RELEASE], [EXTRA-BASE-FLAGS], [EXTRA-YES-FLAGS])
#
# DESCRIPTION
#
# Add warning flags for the g-ir-scanner (from GObject Introspection) to
# VARIABLE, which defaults to WARN_SCANNERFLAGS. VARIABLE is AC_SUBST-ed
# by this macro, but must be manually added to the SCANNERFLAGS variable
# for each GIR target in the code base.
#
# This macro depends on the environment set up by AX_COMPILER_FLAGS.
# Specifically, it uses the value of $ax_enable_compile_warnings to decide
# which flags to enable.
#
# LICENSE
#
# Copyright (c) 2015 Philip Withnall <philip@tecnocode.co.uk>
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice
# and this notice are preserved. This file is offered as-is, without any
# warranty.
#serial 6
AC_DEFUN([AX_COMPILER_FLAGS_GIR],[
AX_REQUIRE_DEFINED([AX_APPEND_FLAG])
# Variable names
m4_define([ax_warn_scannerflags_variable],
[m4_normalize(ifelse([$1],,[WARN_SCANNERFLAGS],[$1]))])
# Base flags
AX_APPEND_FLAG([$3],ax_warn_scannerflags_variable)
AS_IF([test "$ax_enable_compile_warnings" != "no"],[
# "yes" flags
AX_APPEND_FLAG([ dnl
--warn-all dnl
$4 dnl
$5 dnl
$6 dnl
$7 dnl
],ax_warn_scannerflags_variable)
])
AS_IF([test "$ax_enable_compile_warnings" = "error"],[
# "error" flags
AX_APPEND_FLAG([ dnl
--warn-error dnl
],ax_warn_scannerflags_variable)
])
# Substitute the variables
AC_SUBST(ax_warn_scannerflags_variable)
])dnl AX_COMPILER_FLAGS

View File

@ -0,0 +1,111 @@
# ==============================================================================
# https://www.gnu.org/software/autoconf-archive/ax_compiler_flags_ldflags.html
# ==============================================================================
#
# SYNOPSIS
#
# AX_COMPILER_FLAGS_LDFLAGS([VARIABLE], [IS-RELEASE], [EXTRA-BASE-FLAGS], [EXTRA-YES-FLAGS])
#
# DESCRIPTION
#
# Add warning flags for the linker to VARIABLE, which defaults to
# WARN_LDFLAGS. VARIABLE is AC_SUBST-ed by this macro, but must be
# manually added to the LDFLAGS variable for each target in the code base.
#
# This macro depends on the environment set up by AX_COMPILER_FLAGS.
# Specifically, it uses the value of $ax_enable_compile_warnings to decide
# which flags to enable.
#
# LICENSE
#
# Copyright (c) 2014, 2015 Philip Withnall <philip@tecnocode.co.uk>
# Copyright (c) 2017, 2018 Reini Urban <rurban@cpan.org>
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice
# and this notice are preserved. This file is offered as-is, without any
# warranty.
#serial 9
AC_DEFUN([AX_COMPILER_FLAGS_LDFLAGS],[
AX_REQUIRE_DEFINED([AX_APPEND_LINK_FLAGS])
AX_REQUIRE_DEFINED([AX_APPEND_FLAG])
AX_REQUIRE_DEFINED([AX_CHECK_COMPILE_FLAG])
AX_REQUIRE_DEFINED([AX_CHECK_LINK_FLAG])
# Variable names
m4_define([ax_warn_ldflags_variable],
[m4_normalize(ifelse([$1],,[WARN_LDFLAGS],[$1]))])
# Always pass -Werror=unknown-warning-option to get Clang to fail on bad
# flags, otherwise they are always appended to the warn_ldflags variable,
# and Clang warns on them for every compilation unit.
# If this is passed to GCC, it will explode, so the flag must be enabled
# conditionally.
AX_CHECK_COMPILE_FLAG([-Werror=unknown-warning-option],[
ax_compiler_flags_test="-Werror=unknown-warning-option"
],[
ax_compiler_flags_test=""
])
AX_CHECK_LINK_FLAG([-Wl,--as-needed], [
AX_APPEND_LINK_FLAGS([-Wl,--as-needed],
[AM_LDFLAGS],[$ax_compiler_flags_test])
])
AX_CHECK_LINK_FLAG([-Wl,-z,relro], [
AX_APPEND_LINK_FLAGS([-Wl,-z,relro],
[AM_LDFLAGS],[$ax_compiler_flags_test])
])
AX_CHECK_LINK_FLAG([-Wl,-z,now], [
AX_APPEND_LINK_FLAGS([-Wl,-z,now],
[AM_LDFLAGS],[$ax_compiler_flags_test])
])
AX_CHECK_LINK_FLAG([-Wl,-z,noexecstack], [
AX_APPEND_LINK_FLAGS([-Wl,-z,noexecstack],
[AM_LDFLAGS],[$ax_compiler_flags_test])
])
# textonly, retpolineplt not yet
# macOS and cygwin linker do not have --as-needed
AX_CHECK_LINK_FLAG([-Wl,--no-as-needed], [
ax_compiler_flags_as_needed_option="-Wl,--no-as-needed"
], [
ax_compiler_flags_as_needed_option=""
])
# macOS linker speaks with a different accent
ax_compiler_flags_fatal_warnings_option=""
AX_CHECK_LINK_FLAG([-Wl,--fatal-warnings], [
ax_compiler_flags_fatal_warnings_option="-Wl,--fatal-warnings"
])
AX_CHECK_LINK_FLAG([-Wl,-fatal_warnings], [
ax_compiler_flags_fatal_warnings_option="-Wl,-fatal_warnings"
])
# Base flags
AX_APPEND_LINK_FLAGS([ dnl
$ax_compiler_flags_as_needed_option dnl
$3 dnl
],ax_warn_ldflags_variable,[$ax_compiler_flags_test])
AS_IF([test "$ax_enable_compile_warnings" != "no"],[
# "yes" flags
AX_APPEND_LINK_FLAGS([$4 $5 $6 $7],
ax_warn_ldflags_variable,
[$ax_compiler_flags_test])
])
AS_IF([test "$ax_enable_compile_warnings" = "error"],[
# "error" flags; -Werror has to be appended unconditionally because
# it's not possible to test for
#
# suggest-attribute=format is disabled because it gives too many false
# positives
AX_APPEND_LINK_FLAGS([ dnl
$ax_compiler_flags_fatal_warnings_option dnl
],ax_warn_ldflags_variable,[$ax_compiler_flags_test])
])
# Substitute the variables
AC_SUBST(ax_warn_ldflags_variable)
])dnl AX_COMPILER_FLAGS

View File

@ -0,0 +1,242 @@
# ===========================================================================
# https://www.gnu.org/software/autoconf-archive/ax_gcc_func_attribute.html
# ===========================================================================
#
# SYNOPSIS
#
# AX_GCC_FUNC_ATTRIBUTE(ATTRIBUTE)
#
# DESCRIPTION
#
# This macro checks if the compiler supports one of GCC's function
# attributes; many other compilers also provide function attributes with
# the same syntax. Compiler warnings are used to detect supported
# attributes as unsupported ones are ignored by default so quieting
# warnings when using this macro will yield false positives.
#
# The ATTRIBUTE parameter holds the name of the attribute to be checked.
#
# If ATTRIBUTE is supported define HAVE_FUNC_ATTRIBUTE_<ATTRIBUTE>.
#
# The macro caches its result in the ax_cv_have_func_attribute_<attribute>
# variable.
#
# The macro currently supports the following function attributes:
#
# alias
# aligned
# alloc_size
# always_inline
# artificial
# cold
# const
# constructor
# constructor_priority for constructor attribute with priority
# deprecated
# destructor
# dllexport
# dllimport
# error
# externally_visible
# fallthrough
# flatten
# format
# format_arg
# gnu_format
# gnu_inline
# hot
# ifunc
# leaf
# malloc
# noclone
# noinline
# nonnull
# noreturn
# nothrow
# optimize
# pure
# sentinel
# sentinel_position
# unused
# used
# visibility
# warning
# warn_unused_result
# weak
# weakref
#
# Unsupported function attributes will be tested with a prototype
# returning an int and not accepting any arguments and the result of the
# check might be wrong or meaningless so use with care.
#
# LICENSE
#
# Copyright (c) 2013 Gabriele Svelto <gabriele.svelto@gmail.com>
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice
# and this notice are preserved. This file is offered as-is, without any
# warranty.
#serial 13
AC_DEFUN([AX_GCC_FUNC_ATTRIBUTE], [
AS_VAR_PUSHDEF([ac_var], [ax_cv_have_func_attribute_$1])
AC_CACHE_CHECK([for __attribute__(($1))], [ac_var], [
AC_LINK_IFELSE([AC_LANG_PROGRAM([
m4_case([$1],
[alias], [
int foo( void ) { return 0; }
int bar( void ) __attribute__(($1("foo")));
],
[aligned], [
int foo( void ) __attribute__(($1(32)));
],
[alloc_size], [
void *foo(int a) __attribute__(($1(1)));
],
[always_inline], [
inline __attribute__(($1)) int foo( void ) { return 0; }
],
[artificial], [
inline __attribute__(($1)) int foo( void ) { return 0; }
],
[cold], [
int foo( void ) __attribute__(($1));
],
[const], [
int foo( void ) __attribute__(($1));
],
[constructor_priority], [
int foo( void ) __attribute__((__constructor__(65535/2)));
],
[constructor], [
int foo( void ) __attribute__(($1));
],
[deprecated], [
int foo( void ) __attribute__(($1("")));
],
[destructor], [
int foo( void ) __attribute__(($1));
],
[dllexport], [
__attribute__(($1)) int foo( void ) { return 0; }
],
[dllimport], [
int foo( void ) __attribute__(($1));
],
[error], [
int foo( void ) __attribute__(($1("")));
],
[externally_visible], [
int foo( void ) __attribute__(($1));
],
[fallthrough], [
void foo( int x ) {switch (x) { case 1: __attribute__(($1)); case 2: break ; }};
],
[flatten], [
int foo( void ) __attribute__(($1));
],
[format], [
int foo(const char *p, ...) __attribute__(($1(printf, 1, 2)));
],
[gnu_format], [
int foo(const char *p, ...) __attribute__((format(gnu_printf, 1, 2)));
],
[format_arg], [
char *foo(const char *p) __attribute__(($1(1)));
],
[gnu_inline], [
inline __attribute__(($1)) int foo( void ) { return 0; }
],
[hot], [
int foo( void ) __attribute__(($1));
],
[ifunc], [
int my_foo( void ) { return 0; }
static int (*resolve_foo(void))(void) { return my_foo; }
int foo( void ) __attribute__(($1("resolve_foo")));
],
[leaf], [
__attribute__(($1)) int foo( void ) { return 0; }
],
[malloc], [
void *foo( void ) __attribute__(($1));
],
[noclone], [
int foo( void ) __attribute__(($1));
],
[noinline], [
__attribute__(($1)) int foo( void ) { return 0; }
],
[nonnull], [
int foo(char *p) __attribute__(($1(1)));
],
[noreturn], [
void foo( void ) __attribute__(($1));
],
[nothrow], [
int foo( void ) __attribute__(($1));
],
[optimize], [
__attribute__(($1(3))) int foo( void ) { return 0; }
],
[pure], [
int foo( void ) __attribute__(($1));
],
[sentinel], [
int foo(void *p, ...) __attribute__(($1));
],
[sentinel_position], [
int foo(void *p, ...) __attribute__(($1(1)));
],
[returns_nonnull], [
void *foo( void ) __attribute__(($1));
],
[unused], [
int foo( void ) __attribute__(($1));
],
[used], [
int foo( void ) __attribute__(($1));
],
[visibility], [
int foo_def( void ) __attribute__(($1("default")));
int foo_hid( void ) __attribute__(($1("hidden")));
int foo_int( void ) __attribute__(($1("internal")));
int foo_pro( void ) __attribute__(($1("protected")));
],
[warning], [
int foo( void ) __attribute__(($1("")));
],
[warn_unused_result], [
int foo( void ) __attribute__(($1));
],
[weak], [
int foo( void ) __attribute__(($1));
],
[weakref], [
static int foo( void ) { return 0; }
static int bar( void ) __attribute__(($1("foo")));
],
[
m4_warn([syntax], [Unsupported attribute $1, the test may fail])
int foo( void ) __attribute__(($1));
]
)], [])
],
dnl GCC doesn't exit with an error if an unknown attribute is
dnl provided but only outputs a warning, so accept the attribute
dnl only if no warning were issued.
[AS_IF([grep -- -Wattributes conftest.err],
[AS_VAR_SET([ac_var], [no])],
[AS_VAR_SET([ac_var], [yes])])],
[AS_VAR_SET([ac_var], [no])])
])
AS_IF([test yes = AS_VAR_GET([ac_var])],
[AC_DEFINE_UNQUOTED(AS_TR_CPP(HAVE_FUNC_ATTRIBUTE_$1), 1,
[Define to 1 if the system has the `$1' function attribute])], [])
AS_VAR_POPDEF([ac_var])
])

View File

@ -0,0 +1,37 @@
# ===========================================================================
# https://www.gnu.org/software/autoconf-archive/ax_require_defined.html
# ===========================================================================
#
# SYNOPSIS
#
# AX_REQUIRE_DEFINED(MACRO)
#
# DESCRIPTION
#
# AX_REQUIRE_DEFINED is a simple helper for making sure other macros have
# been defined and thus are available for use. This avoids random issues
# where a macro isn't expanded. Instead the configure script emits a
# non-fatal:
#
# ./configure: line 1673: AX_CFLAGS_WARN_ALL: command not found
#
# It's like AC_REQUIRE except it doesn't expand the required macro.
#
# Here's an example:
#
# AX_REQUIRE_DEFINED([AX_CHECK_LINK_FLAG])
#
# LICENSE
#
# Copyright (c) 2014 Mike Frysinger <vapier@gentoo.org>
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice
# and this notice are preserved. This file is offered as-is, without any
# warranty.
#serial 2
AC_DEFUN([AX_REQUIRE_DEFINED], [dnl
m4_ifndef([$1], [m4_fatal([macro ]$1[ is not defined; is a m4 file missing?])])
])dnl AX_REQUIRE_DEFINED

View File

@ -0,0 +1,217 @@
/*
* Pinelog lightweight logging library
*
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: MIT
*/
#include "config.h"
#include <stdio.h>
#include <stdarg.h>
#include <time.h>
#include <errno.h>
#include "pinelog.h"
/**********************************************************************
* Configure defaults
*********************************************************************/
#ifndef PINELOG_DEFAULT_STREAM
#define PINELOG_DEFAULT_STREAM stdout
#endif
#ifndef PINELOG_DEFAULT_LEVEL
#define PINELOG_DEFAULT_LEVEL PINELOG_LVL_ERROR
#endif
/**********************************************************************
* Configure logging parameters
*********************************************************************/
#ifndef PINELOG_SHOW_DATE
#define PINELOG_SHOW_DATE 0
#endif
#ifndef PINELOG_SHOW_LEVEL
#define PINELOG_SHOW_LEVEL 0
#endif
#ifndef PINELOG_SHOW_BACKTRACE
#define PINELOG_SHOW_BACKTRACE 0
#endif
/**********************************************************************
* Configure level strings
*********************************************************************/
#ifndef PINELOG_FATAL_STR
#define PINELOG_FATAL_STR "FATAL"
#endif
#ifndef PINELOG_ERROR_STR
#define PINELOG_ERROR_STR "ERROR"
#endif
#ifndef PINELOG_WARNING_STR
#define PINELOG_WARNING_STR "WARNING"
#endif
#ifndef PINELOG_INFO_STR
#define PINELOG_INFO_STR "INFO"
#endif
#ifndef PINELOG_DEBUG_STR
#define PINELOG_DEBUG_STR "DEBUG"
#endif
#ifndef PINELOG_TRACE_STR
#define PINELOG_TRACE_STR "TRACE"
#endif
/**********************************************************************
* Global variables
*********************************************************************/
/** Stream buffer */
static FILE *output_stream = NULL;
/** Default logging level */
static int log_level = PINELOG_DEFAULT_LEVEL;
/* Initialize defaults */
#if HAVE_FUNC_ATTRIBUTE_CONSTRUCTOR
__attribute__((constructor))
#endif
void pinelog_set_defaults(void)
{
output_stream = PINELOG_DEFAULT_STREAM;
log_level = PINELOG_DEFAULT_LEVEL;
}
#if HAVE_FUNC_ATTRIBUTE_DESTRUCTOR
__attribute__((destructor))
#endif
void pinelog_close_output_stream(void)
{
/* If current output stream is not stdout or stderr, then close it */
if (output_stream != NULL && output_stream != stdout && output_stream != stderr) {
fclose(output_stream);
}
output_stream = PINELOG_DEFAULT_STREAM;
}
int pinelog_set_output_stream(FILE *stream)
{
if (stream == NULL) {
return EINVAL;
}
pinelog_close_output_stream();
setlinebuf(stream);
output_stream = stream;
return 0;
}
#ifdef PINELOG_TEST
FILE * pinelog_get_output_stream(void)
{
return output_stream;
}
#endif
int pinelog_set_output_file(const char *file)
{
FILE *stream;
if (file == NULL) {
return EINVAL;
}
errno = 0;
stream = fopen(file, "w");
if (stream == NULL) {
return errno;
}
return pinelog_set_output_stream(stream);
}
int pinelog_get_level(void)
{
return log_level;
}
int pinelog_set_level(int level)
{
if (level < PINELOG_LVL_NONE || level > PINELOG_LVL_TRACE) {
return EINVAL;
}
log_level = level;
return 0;
}
/**********************************************************************
* Log the message to the output stream
*********************************************************************/
void pinelog_log_message(int level, const char *file, int line, const char *fmt, ...)
{
va_list ap;
/* Don't log anything if the level is not severe enough */
if (level > log_level || level < 0) {
return;
}
/* Cap the log level */
if (level > PINELOG_LVL_TRACE) {
level = PINELOG_LVL_TRACE;
}
#if !HAVE_FUNC_ATTRIBUTE_CONSTRUCTOR
/*
* Validate and set output stream. Only necessary if the compiler doesn't
* support the constructor attribute
*/
if (output_stream == NULL) {
output_stream = PINELOG_DEFAULT_STREAM;
}
#endif
#if PINELOG_SHOW_DATE
do {
time_t t;
struct tm *tmp;
char date_string[30];
t = time(NULL);
tmp = localtime(&t);
strftime(date_string, sizeof(date_string), "%F %T ", tmp);
fputs(date_string, output_stream);
} while (0);
#endif
#if PINELOG_SHOW_LEVEL
do {
static const char *level_strings[] = {
PINELOG_FATAL_STR,
PINELOG_ERROR_STR,
PINELOG_WARNING_STR,
PINELOG_INFO_STR,
PINELOG_DEBUG_STR,
PINELOG_TRACE_STR,
};
fputs(level_strings[level], output_stream);
fputs(": ", output_stream);
} while (0);
#endif
#if PINELOG_SHOW_BACKTRACE
fprintf(output_stream, "%s:%d ", file, line);
#endif
va_start(ap, fmt);
vfprintf(output_stream, fmt, ap);
va_end(ap);
// Append a trailing newline to flush the log message
fputs("\n", output_stream);
}

View File

@ -0,0 +1,183 @@
/*
* Pinelog lightweight logging library
*
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: MIT
*/
/**
* @file logging.h
* @brief Logging utility library
*
* This file contains the prototypes for the pinelog logging library
* used by any programs that need to log messages.
*
* @author Nirenjan Krishnan (nirenjan@nirenjan.org)
*/
#ifndef LOGGING_H
#define LOGGING_H
#include "config.h"
#include <stdio.h>
#ifndef PINELOG_TEST
#include <stdlib.h>
#endif
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Logging levels
*
* The log levels indicate the lowest severity level that will actually be
* logged to the logging framework.
*/
enum {
/** No messages will be logged */
PINELOG_LVL_NONE = -1,
/** Only fatal messages will be logged */
PINELOG_LVL_FATAL,
/** Error messages. This is the default log level */
PINELOG_LVL_ERROR,
/** Warning messages */
PINELOG_LVL_WARNING,
/** Informational messages */
PINELOG_LVL_INFO,
/** Debug messages */
PINELOG_LVL_DEBUG,
/** Trace messages */
PINELOG_LVL_TRACE,
};
/**
* @brief Set the default log level and output stream
*/
void pinelog_set_defaults(void);
/**
* @brief Close the output stream and terminate the logs
*/
void pinelog_close_output_stream(void);
#ifdef PINELOG_TEST
/**
* @brief Get the pointer to the output stream. Only used in test harness.
*
* @returns FILE pointer to output stream
*/
FILE * pinelog_get_output_stream(void);
#endif
/**
* @brief Set the output stream. Must be a FILE pointer.
*
* @param[in] stream Pointer to the output stream
*
* @returns 0 on success, EINVAL if the pointer is not valid.
*/
int pinelog_set_output_stream(FILE *stream);
/**
* @brief Set the output file.
*
* @param[in] file Filename to write to
*
* @returns 0 on success, EINVAL if the filename pointer is not valid, other
* error if the file could not be opened for writing.
*/
int pinelog_set_output_file(const char *file);
/**
* @brief Set the logging level
*
* @param[in] level Level to filter
*
* @returns 0 on success, EINVAL if the level is not valid
*/
int pinelog_set_level(int level);
/**
* @brief Get the logging level
*
* @returns the configured logging level
*/
int pinelog_get_level(void);
/**
* @brief Log a message to the logger
*
* This is the actual function that logs the message. The application should
* never need to call this directly, but instead, should always use the
* \code PINELOG_* macros.
*
* @param[in] level Level to log the message at
* @param[in] fmt Format string
*
* @returns None
*/
#if HAVE_FUNC_ATTRIBUTE_FORMAT
__attribute__((format(printf, 4, 5)))
#endif
void pinelog_log_message(int level, const char *file, int line, const char *fmt, ...);
// Test harness will redefine pinelog_exit
#ifndef PINELOG_TEST
#define pinelog_exit exit
#endif
#define PINELOG_FATAL(fmt, ...) do { \
if (PINELOG_LVL_FATAL <= pinelog_get_level()) { \
pinelog_log_message(PINELOG_LVL_FATAL, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \
} \
pinelog_exit(1); \
} while (0)
#define PINELOG_ERROR(fmt, ...) do { \
if (PINELOG_LVL_ERROR <= pinelog_get_level()) { \
pinelog_log_message(PINELOG_LVL_ERROR, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \
} \
} while (0)
#define PINELOG_WARN(fmt, ...) do { \
if (PINELOG_LVL_WARNING <= pinelog_get_level()) { \
pinelog_log_message(PINELOG_LVL_WARNING, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \
} \
} while(0)
#define PINELOG_INFO(fmt, ...) do { \
if (PINELOG_LVL_INFO <= pinelog_get_level()) { \
pinelog_log_message(PINELOG_LVL_INFO, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \
} \
} while(0)
#define PINELOG_DEBUG(fmt, ...) do { \
if (PINELOG_LVL_DEBUG <= pinelog_get_level()) { \
pinelog_log_message(PINELOG_LVL_DEBUG, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \
} \
} while(0)
/* PINELOG_DISABLE_TRACE allows all traces to be compiled out */
#ifndef PINELOG_DISABLE_TRACE
#define PINELOG_TRACE(fmt, ...) do { \
if (PINELOG_LVL_TRACE <= pinelog_get_level()) { \
pinelog_log_message(PINELOG_LVL_TRACE, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \
} \
} while(0)
#else
#define PINELOG_TRACE(fmt, ...) do { } while(0)
#endif
#ifdef __cplusplus
}
#endif
#endif // !defined LOGGING_H

View File

@ -0,0 +1,191 @@
/*
* Pinelog lightweight logging library - test harness
*
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: MIT
*/
#include "pinelog.h"
#include <stdio.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
/**********************************************************************
* Global variables
*********************************************************************/
// Test ID (of current test case)
static unsigned int test_id;
// Observed output stream
static FILE *observed_stream_w;
static FILE *observed_stream_r;
// Temporary pipe for observed data
static char observed_fifo[NAME_MAX];
// Buffer for expected output
static char expected_output[1024];
static size_t expected_len;
static void test_case(const char *desc, bool test)
{
test_id++;
if (test) {
printf("ok %u %s\n", test_id, desc);
} else {
printf("not ok %u %s\n", test_id, desc);
}
}
static void pinelog_exit(int status)
{
fprintf(observed_stream_w, "EXIT(%d)\n", status);
expected_len += snprintf(&expected_output[expected_len],
sizeof(expected_output) - expected_len,
"EXIT(%d)\n", status);
}
static void dump_data(const char *type, size_t len, char *data)
{
char *line;
printf("# %s (%lu bytes):\n", type, len);
line = strtok(data, "\n");
while (line != NULL) {
printf("#\t%s\n", line);
line = strtok(NULL, "\n");
}
printf("\n");
}
static int test_setup(int level, int filter, const char *file, int line)
{
expected_len = 0;
memset(expected_output, 0, sizeof(expected_output));
if (level <= filter) {
if (PINELOG_SHOW_DATE) {
time_t t;
struct tm *tmp;
t = time(NULL);
tmp = localtime(&t);
expected_len += strftime(&expected_output[expected_len],
sizeof(expected_output) - expected_len,
"%F %T ", tmp);
}
if (PINELOG_SHOW_LEVEL) {
const char * level_string[] = {
PINELOG_FATAL_STR,
PINELOG_ERROR_STR,
PINELOG_WARNING_STR,
PINELOG_INFO_STR,
PINELOG_DEBUG_STR,
PINELOG_TRACE_STR,
};
expected_len += snprintf(&expected_output[expected_len],
sizeof(expected_output) - expected_len,
"%s: ", level_string[level]);
}
if (PINELOG_SHOW_BACKTRACE) {
expected_len += snprintf(&expected_output[expected_len],
sizeof(expected_output) - expected_len,
"%s:%d ", file, line);
}
return 1;
}
return 0;
}
static void test_teardown(const char *desc)
{
// Compare the output
static char observed[1024];
size_t observed_len;
int result;
observed_len = fread(observed, 1, sizeof(observed), observed_stream_r);
result = ((expected_len == observed_len) &&
(memcmp(expected_output, observed, expected_len) == 0));
test_case(desc, result);
if (!result) {
dump_data("expected", expected_len, expected_output);
dump_data("observed", observed_len, observed);
}
}
static void verify_defaults(void)
{
test_case("Get default output stream",
pinelog_get_output_stream() == PINELOG_DEFAULT_STREAM);
test_case("Get default logging level",
pinelog_get_level() == PINELOG_DEFAULT_LEVEL);
}
#define PINELOG_WARNING PINELOG_WARN
#define TEST_LOG(lvl, filter, fmt, ...) do { \
if (test_setup(PINELOG_LVL_ ## lvl, PINELOG_LVL_ ## filter, \
__FILE__, __LINE__)) \
expected_len += snprintf(&expected_output[expected_len], \
sizeof(expected_output) - expected_len, \
fmt "\n", ##__VA_ARGS__); \
PINELOG_ ## lvl (fmt, ##__VA_ARGS__); \
test_teardown("Log " #lvl " filter " #filter); \
} while(0)
#define TEST(filter, fmt, ...) do { \
pinelog_set_level(PINELOG_LVL_ ## filter); \
TEST_LOG(TRACE, filter, fmt, ##__VA_ARGS__); \
TEST_LOG(DEBUG, filter, fmt, ##__VA_ARGS__); \
TEST_LOG(INFO, filter, fmt, ##__VA_ARGS__); \
TEST_LOG(WARNING, filter, fmt, ##__VA_ARGS__); \
TEST_LOG(ERROR, filter, fmt, ##__VA_ARGS__); \
TEST_LOG(FATAL, filter, fmt, ##__VA_ARGS__); \
} while (0)
int main(int argc, char **argv)
{
int fifo_fd_r, fifo_fd_w;
snprintf(observed_fifo, sizeof(observed_fifo), "%s.fifo", argv[0]);
mkfifo(observed_fifo, 0777);
fifo_fd_r = open(observed_fifo, O_RDONLY | O_NONBLOCK);
fifo_fd_w = open(observed_fifo, O_WRONLY | O_NONBLOCK);
observed_stream_r = fdopen(fifo_fd_r, "r");
observed_stream_w = fdopen(fifo_fd_w, "w");
verify_defaults();
pinelog_set_output_stream(observed_stream_w);
TEST(TRACE, "testing %s... %d, %f, %u", "testing", -1, 0.0, 1);
TEST(DEBUG, "testing %s... %d, %f, %u", "testing", -1, 0.0, 1);
TEST(INFO, "testing %s... %d, %f, %u", "testing", -1, 0.0, 1);
TEST(WARNING, "testing %s... %d, %f, %u", "testing", -1, 0.0, 1);
TEST(ERROR, "testing %s... %d, %f, %u", "testing", -1, 0.0, 1);
TEST(FATAL, "testing %s... %d, %f, %u", "testing", -1, 0.0, 1);
TEST(NONE, "testing %s... %d, %f, %u", "testing", -1, 0.0, 1);
printf("1..%u\n", test_id);
pinelog_close_output_stream();
fclose(observed_stream_r);
close(fifo_fd_w);
close(fifo_fd_r);
unlink(observed_fifo);
return 0;
}

View File

@ -0,0 +1,25 @@
# Automake for libusbx52 and associated utilities
#
# Copyright (C) 2012-2018 Nirenjan Krishnan (nirenjan@nirenjan.org)
#
# SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
# libusb stub library for use by test programs
check_LTLIBRARIES += libusbx52.la
libusbx52_la_SOURCES = libusbx52/usb_x52_stub.c libusbx52/fopen_env.c
libusbx52_la_CFLAGS = -I $(top_srcdir)/libusbx52 @LIBUSB_CFLAGS@ $(WARN_CFLAGS)
libusbx52_la_LDFLAGS = -rpath /nowhere -module $(WARN_LDFLAGS)
# Utility programs for use by tests
check_PROGRAMS += x52test_create_device_list x52test_log_actions
x52test_create_device_list_SOURCES = libusbx52/util/create_device_list.c $(libusbx52_la_SOURCES)
x52test_create_device_list_CFLAGS = $(libusbx52_la_CFLAGS)
x52test_create_device_list_LDFLAGS = $(WARN_LDFLAGS)
x52test_log_actions_SOURCES = libusbx52/util/log_actions.c $(libusbx52_la_SOURCES)
x52test_log_actions_CFLAGS = -I $(top_srcdir)/libx52 $(libusbx52_la_CFLAGS)
x52test_log_actions_LDFLAGS = $(WARN_LDFLAGS)
EXTRA_DIST += libusbx52/README.md libusbx52/libusbx52.h

View File

@ -4,20 +4,27 @@
#
# SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
ACLOCAL_AMFLAGS = -I m4
lib_LTLIBRARIES = libx52.la
lib_LTLIBRARIES += libx52.la
# Core libx52 library
# This library handles the USB communication between the host and the X52
# Libtool Version Info
# See: https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html
libx52_v_CUR=5
libx52_v_AGE=3
libx52_v_CUR=6
libx52_v_AGE=4
libx52_v_REV=0
libx52_la_SOURCES = x52_control.c x52_core.c x52_date_time.c x52_mfd_led.c \
x52_strerror.c
libx52_la_CFLAGS = @LIBUSB_CFLAGS@ -DLOCALEDIR=\"$(localedir)\" -I $(top_srcdir) $(WARN_CFLAGS)
libx52_la_SOURCES = \
libx52/x52_control.c \
libx52/x52_core.c \
libx52/x52_date_time.c \
libx52/x52_mfd_led.c \
libx52/x52_strerror.c \
libx52/x52_stringify.c
libx52_la_CFLAGS = \
@LIBUSB_CFLAGS@ \
-DLOCALEDIR=\"$(localedir)\" \
-I $(top_srcdir) \
$(WARN_CFLAGS)
libx52_la_LDFLAGS = \
-export-symbols-regex '^libx52_' \
-version-info $(libx52_v_CUR):$(libx52_v_REV):$(libx52_v_AGE) @LIBUSB_LIBS@ \
@ -25,37 +32,36 @@ libx52_la_LDFLAGS = \
libx52_la_LIBADD = @LTLIBINTL@
# Header files that need to be copied
x52includedir = $(includedir)/libx52
x52include_HEADERS = libx52.h
x52include_HEADERS += libx52/libx52.h
# pkg-config files
pkgconfig_DATA = libx52.pc
pkgconfig_DATA += libx52/libx52.pc
if HAVE_CMOCKA
LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) $(top_srcdir)/tap-driver.sh
TESTS = libx52test
check_PROGRAMS = libx52test
TESTS += libx52test
check_PROGRAMS += libx52test
nodist_libx52test_SOURCES = test_libx52.c
nodist_libx52test_SOURCES = libx52/test_libx52.c
libx52test_SOURCES = $(libx52_la_SOURCES)
libx52test_CFLAGS = @LIBUSB_CFLAGS@ -DLOCALEDIR='"$(localedir)"' -I $(top_srcdir)
libx52test_CFLAGS = @LIBUSB_CFLAGS@ -DLOCALEDIR='"$(localedir)"' -I $(top_srcdir) -I $(top_srcdir)/libx52
libx52test_CFLAGS += -Dlibusb_control_transfer=__wrap_libusb_control_transfer
libx52test_LDFLAGS = @CMOCKA_LIBS@ @LIBUSB_LIBS@
libx52test_LDADD = libx52.la
CLEANFILES = test_libx52.c
test_libx52.c: $(srcdir)/x52_test_gen.py $(srcdir)/x52_tests.json
$(AM_V_GEN) $(PYTHON) $(srcdir)/x52_test_gen.py $(srcdir)/x52_tests.json > $@
CLEANFILES += libx52/test_libx52.c
$(builddir)/libx52/test_libx52.c: $(srcdir)/libx52/x52_test_gen.py $(srcdir)/libx52/x52_tests.json
$(AM_V_GEN) $(PYTHON) $(srcdir)/libx52/x52_test_gen.py $(srcdir)/libx52/x52_tests.json > $@
endif
# Extra files that need to be in the distribution
EXTRA_DIST = libx52.h x52_commands.h x52_common.h README.md
EXTRA_DIST += \
libx52/libx52.h libx52/x52_commands.h libx52/x52_common.h libx52/README.md
# Add test files to the distribution
EXTRA_DIST += x52_test_gen.py x52_tests.json
EXTRA_DIST += libx52/x52_test_gen.py libx52/x52_tests.json
# Add documentation files to the distribution
EXTRA_DIST += \
doc/main.dox \
doc/caveats.dox \
doc/integration.dox
libx52/doc/main.dox \
libx52/doc/caveats.dox \
libx52/doc/integration.dox

View File

@ -673,6 +673,16 @@ int libx52_vendor_command(libx52_device *x52, uint16_t index, uint16_t value);
*/
int libx52_check_feature(libx52_device *x52, libx52_feature feature);
/** @} */
/**
* @defgroup libx52str Stringification
*
* Translation APIs from enumerations to string, primarily for logging.
*
* @{
*/
/**
* @brief Return a string representation of the error code
*
@ -683,6 +693,56 @@ int libx52_check_feature(libx52_device *x52, libx52_feature feature);
*/
const char * libx52_strerror(libx52_error_code error);
/**
* @brief Returns a string representation of the clock ID
*
* @param[in] id Clock ID
*
* @returns Pointer to a NULL terminated string describing the clock ID.
* Returned pointer must not be freed.
*/
const char * libx52_clock_id_to_str(libx52_clock_id id);
/**
* @brief Returns a string representation of the clock format
*
* @param[in] format Clock format
*
* @returns Pointer to a NULL terminated string describing the clock format.
* Returned pointer must not be freed.
*/
const char * libx52_clock_format_to_str(libx52_clock_format format);
/**
* @brief Returns a string representation of the date format
*
* @param[in] format Date format
*
* @returns Pointer to a NULL terminated string describing the date format.
* Returned pointer must not be freed.
*/
const char * libx52_date_format_to_str(libx52_date_format format);
/**
* @brief Returns a string representation of the LED
*
* @param[in] id LED ID
*
* @returns Pointer to a NULL terminated string describing the LED.
* Returned pointer must not be freed.
*/
const char * libx52_led_id_to_str(libx52_led_id id);
/**
* @brief Returns a string representation of the LED state
*
* @param[in] state LED state
*
* @returns Pointer to a NULL terminated string describing the LED state.
* Returned pointer must not be freed.
*/
const char * libx52_led_state_to_str(libx52_led_state state);
/** @} */
#ifdef __cplusplus

View File

@ -1,7 +1,7 @@
/*
* Saitek X52 Pro MFD & LED driver
*
* Copyright (C) 2012-2017 Nirenjan Krishnan (nirenjan@nirenjan.org)
* Copyright (C) 2012-2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
@ -12,65 +12,54 @@
#include "libx52.h"
#include "gettext.h"
/** For future use in i18n */
#define N_(str) gettext_noop(str)
#define _(str) dgettext(PACKAGE, str)
/* Error buffer used for building custom error strings */
static char error_buffer[256];
/* List of error strings */
static const char *error_string[] = {
N_("Success"),
N_("Initialization failure"),
N_("Insufficient memory"),
N_("Invalid parameter"),
N_("Operation not supported"),
N_("Try again"),
N_("Input parameter out of range"),
N_("USB transaction failure"),
N_("USB input/output error"),
N_("Access denied"),
N_("No such device"),
N_("Entity not found"),
N_("Resource busy"),
N_("Operation timeout"),
N_("Overflow"),
N_("Pipe error"),
N_("System call interrupted"),
};
const char * libx52_strerror(libx52_error_code error)
{
switch (error) {
case LIBX52_SUCCESS:
return _("Success");
case LIBX52_ERROR_INIT_FAILURE:
return _("Initialization failure");
case LIBX52_ERROR_OUT_OF_MEMORY:
return _("Insufficient memory");
case LIBX52_ERROR_INVALID_PARAM:
return _("Invalid parameter");
case LIBX52_ERROR_NOT_SUPPORTED:
return _("Operation not supported");
case LIBX52_ERROR_TRY_AGAIN:
return _("Try again");
case LIBX52_ERROR_OUT_OF_RANGE:
return _("Input parameter out of range");
case LIBX52_ERROR_USB_FAILURE:
return _("USB transaction failure");
case LIBX52_ERROR_IO:
return _("USB input/output error");
case LIBX52_ERROR_PERM:
return _("Access denied");
case LIBX52_ERROR_NO_DEVICE:
return _("No such device");
case LIBX52_ERROR_NOT_FOUND:
return _("Entity not found");
case LIBX52_ERROR_BUSY:
return _("Resource busy");
case LIBX52_ERROR_TIMEOUT:
return _("Operation timeout");
case LIBX52_ERROR_OVERFLOW:
return _("Overflow");
case LIBX52_ERROR_PIPE:
return _("Pipe error");
case LIBX52_ERROR_INTERRUPTED:
return _("System call interrupted");
return _(error_string[error]);
default:
snprintf(error_buffer, sizeof(error_buffer),

View File

@ -0,0 +1,96 @@
/*
* Saitek X52 Pro MFD & LED driver - stringification
*
* Copyright (C) 2012-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 "libx52.h"
#include "gettext.h"
#define N_(str) gettext_noop(str)
#define _(str) dgettext(PACKAGE, str)
#define STRINGIFY(name, max_id, errstr, ...) \
const char * libx52_ ## name ## _to_str (libx52_ ## name param) { \
static char invalid[256]; \
static const char *desc[] = { __VA_ARGS__ }; \
if (param >= 0 && param <= max_id) { \
return _(desc[param]); \
} \
snprintf(invalid, sizeof(invalid), _(errstr), param); \
return invalid; \
}
STRINGIFY(clock_id, LIBX52_CLOCK_3, N_("Unknown clock ID %d"),
N_("primary"),
N_("secondary"),
N_("tertiary"),
)
STRINGIFY(clock_format, LIBX52_CLOCK_FORMAT_24HR, N_("Unknown clock format %d"),
N_("12 hour"),
N_("24 hour"),
)
STRINGIFY(date_format, LIBX52_DATE_FORMAT_YYMMDD, N_("Unknown date format %d"),
N_("DD-MM-YY"),
N_("MM-DD-YY"),
N_("YY-MM-DD"),
)
STRINGIFY(led_state, LIBX52_LED_STATE_GREEN, N_("Unknown LED state %d")
N_("off"),
N_("on"),
N_("red"),
N_("amber"),
N_("green"),
)
const char * libx52_led_id_to_str(libx52_led_id id)
{
static char invalid[256];
switch (id) {
case LIBX52_LED_FIRE:
return _("Fire");
case LIBX52_LED_A:
return _("A");
case LIBX52_LED_B:
return _("B");
case LIBX52_LED_D:
return _("D");
case LIBX52_LED_E:
return _("E");
case LIBX52_LED_T1:
return _("T1");
case LIBX52_LED_T2:
return _("T2");
case LIBX52_LED_T3:
return _("T3");
case LIBX52_LED_POV:
return _("POV");
case LIBX52_LED_CLUTCH:
return _("Clutch");
case LIBX52_LED_THROTTLE:
return _("Throttle");
default:
snprintf(invalid, sizeof(invalid), _("Unknown LED ID %d"), id);
return invalid;
}
}

View File

@ -4,7 +4,7 @@
#
# SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
lib_LTLIBRARIES = libx52io.la
lib_LTLIBRARIES += libx52io.la
# X52 IO library
# This library handles the HID parsing of the X52 USB reports
@ -13,7 +13,12 @@ lib_LTLIBRARIES = libx52io.la
libx52io_v_CUR=0
libx52io_v_AGE=0
libx52io_v_REV=0
libx52io_la_SOURCES = io_core.c io_axis.c io_parser.c io_strings.c io_device.c
libx52io_la_SOURCES = \
libx52io/io_core.c \
libx52io/io_axis.c \
libx52io/io_parser.c \
libx52io/io_strings.c \
libx52io/io_device.c
libx52io_la_CFLAGS = @HIDAPI_CFLAGS@ -DLOCALEDIR=\"$(localedir)\" -I $(top_srcdir) $(WARN_CFLAGS)
libx52io_la_LDFLAGS = \
-export-symbols-regex '^libx52io_' \
@ -22,30 +27,31 @@ libx52io_la_LDFLAGS = \
libx52io_la_LIBADD = @LTLIBINTL@
# Header files that need to be copied
x52includedir = $(includedir)/libx52
x52include_HEADERS = libx52io.h
x52include_HEADERS += libx52io/libx52io.h
# pkg-config files
# pkgconfig_DATA = libx52io.pc
if HAVE_CMOCKA
LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) $(top_srcdir)/tap-driver.sh
TESTS = test-axis test-parser
check_PROGRAMS = $(TESTS)
TESTS += test-axis test-parser
check_PROGRAMS += test-axis test-parser
test_axis_SOURCES = test_axis.c $(libx52io_la_SOURCES)
test_axis_SOURCES = libx52io/test_axis.c $(libx52io_la_SOURCES)
test_axis_CFLAGS = $(libx52io_la_CFLAGS)
test_axis_LDFLAGS = @CMOCKA_LIBS@ @HIDAPI_LIBS@ $(WARN_LDFLAGS)
test_axis_LDADD = @LTLIBINTL@
test_parser_SOURCES = test_parser.c $(libx52io_la_SOURCES)
test_parser_SOURCES = libx52io/test_parser.c $(libx52io_la_SOURCES)
test_parser_CFLAGS = $(libx52io_la_CFLAGS)
test_parser_LDFLAGS = @CMOCKA_LIBS@ @HIDAPI_LIBS@ $(WARN_LDFLAGS)
test_parser_LDADD = @LTLIBINTL@
# Add a dependency on test_parser_tests.c
test_parser.c: test_parser_tests.c
libx52io/test_parser.c: libx52io/test_parser_tests.c
endif
# Extra files that need to be in the distribution
EXTRA_DIST = libx52io.h io_common.h test_parser_tests.c
EXTRA_DIST += \
libx52io/libx52io.h \
libx52io/io_common.h \
libx52io/test_parser_tests.c

View File

@ -95,31 +95,31 @@ const char * libx52io_button_to_str(libx52io_button button)
/* Error buffer used for building custom error strings */
static char error_buffer[256];
#define N_(str) gettext_noop(str)
static const char *error_string[] = {
N_("Success"),
N_("Initialization failure"),
N_("No device"),
N_("Invalid arguments"),
N_("Connection failure"),
N_("I/O error"),
N_("Read timeout"),
};
#define _(str) dgettext(PACKAGE, str)
const char * libx52io_strerror(libx52io_error_code code)
{
switch (code) {
case LIBX52IO_SUCCESS:
return _("Success");
case LIBX52IO_ERROR_INIT_FAILURE:
return _("Initialization failure");
case LIBX52IO_ERROR_NO_DEVICE:
return _("No device");
case LIBX52IO_ERROR_INVALID:
return _("Invalid arguments");
case LIBX52IO_ERROR_CONN:
return _("Connection failure");
case LIBX52IO_ERROR_IO:
return _("I/O error");
case LIBX52IO_ERROR_TIMEOUT:
return _("Read timeout");
return _(error_string[code]);
default:
snprintf(error_buffer, sizeof(error_buffer), _("Unknown error %d"), code);

View File

@ -0,0 +1,32 @@
# Automake for libx52util
#
# Copyright (C) 2012-2018 Nirenjan Krishnan (nirenjan@nirenjan.org)
#
# SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
lib_LTLIBRARIES += libx52util.la
# libx52 utility library
# This library provides extra utilities for ease of use
nodist_libx52util_la_SOURCES = libx52util/util_char_map.c
libx52util_la_SOURCES = libx52util/x52_char_map_lookup.c
libx52util_la_CFLAGS = -I $(top_srcdir)/libx52util $(WARN_CFLAGS)
libx52util_la_LDFLAGS = -version-info 1:0:0 $(WARN_LDFLAGS)
# Header files that need to be copied
x52include_HEADERS += libx52util/libx52util.h
# Autogenerated file that needs to be cleaned up
CLEANFILES += libx52util/util_char_map.c
util_char_map_c_DEPENDS = \
$(srcdir)/libx52util/x52_char_map_gen.py \
$(srcdir)/libx52util/x52_char_map.cfg
$(builddir)/libx52util/util_char_map.c: $(util_char_map_c_DEPENDS)
$(AM_V_GEN) $(PYTHON) $(util_char_map_c_DEPENDS) $@
# Extra files that need to be in the distribution
EXTRA_DIST += libx52util/x52_char_map.cfg \
libx52util/x52_char_map.h \
libx52util/x52_char_map_gen.py

View File

@ -0,0 +1,242 @@
# ===========================================================================
# https://www.gnu.org/software/autoconf-archive/ax_gcc_func_attribute.html
# ===========================================================================
#
# SYNOPSIS
#
# AX_GCC_FUNC_ATTRIBUTE(ATTRIBUTE)
#
# DESCRIPTION
#
# This macro checks if the compiler supports one of GCC's function
# attributes; many other compilers also provide function attributes with
# the same syntax. Compiler warnings are used to detect supported
# attributes as unsupported ones are ignored by default so quieting
# warnings when using this macro will yield false positives.
#
# The ATTRIBUTE parameter holds the name of the attribute to be checked.
#
# If ATTRIBUTE is supported define HAVE_FUNC_ATTRIBUTE_<ATTRIBUTE>.
#
# The macro caches its result in the ax_cv_have_func_attribute_<attribute>
# variable.
#
# The macro currently supports the following function attributes:
#
# alias
# aligned
# alloc_size
# always_inline
# artificial
# cold
# const
# constructor
# constructor_priority for constructor attribute with priority
# deprecated
# destructor
# dllexport
# dllimport
# error
# externally_visible
# fallthrough
# flatten
# format
# format_arg
# gnu_format
# gnu_inline
# hot
# ifunc
# leaf
# malloc
# noclone
# noinline
# nonnull
# noreturn
# nothrow
# optimize
# pure
# sentinel
# sentinel_position
# unused
# used
# visibility
# warning
# warn_unused_result
# weak
# weakref
#
# Unsupported function attributes will be tested with a prototype
# returning an int and not accepting any arguments and the result of the
# check might be wrong or meaningless so use with care.
#
# LICENSE
#
# Copyright (c) 2013 Gabriele Svelto <gabriele.svelto@gmail.com>
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice
# and this notice are preserved. This file is offered as-is, without any
# warranty.
#serial 13
AC_DEFUN([AX_GCC_FUNC_ATTRIBUTE], [
AS_VAR_PUSHDEF([ac_var], [ax_cv_have_func_attribute_$1])
AC_CACHE_CHECK([for __attribute__(($1))], [ac_var], [
AC_LINK_IFELSE([AC_LANG_PROGRAM([
m4_case([$1],
[alias], [
int foo( void ) { return 0; }
int bar( void ) __attribute__(($1("foo")));
],
[aligned], [
int foo( void ) __attribute__(($1(32)));
],
[alloc_size], [
void *foo(int a) __attribute__(($1(1)));
],
[always_inline], [
inline __attribute__(($1)) int foo( void ) { return 0; }
],
[artificial], [
inline __attribute__(($1)) int foo( void ) { return 0; }
],
[cold], [
int foo( void ) __attribute__(($1));
],
[const], [
int foo( void ) __attribute__(($1));
],
[constructor_priority], [
int foo( void ) __attribute__((__constructor__(65535/2)));
],
[constructor], [
int foo( void ) __attribute__(($1));
],
[deprecated], [
int foo( void ) __attribute__(($1("")));
],
[destructor], [
int foo( void ) __attribute__(($1));
],
[dllexport], [
__attribute__(($1)) int foo( void ) { return 0; }
],
[dllimport], [
int foo( void ) __attribute__(($1));
],
[error], [
int foo( void ) __attribute__(($1("")));
],
[externally_visible], [
int foo( void ) __attribute__(($1));
],
[fallthrough], [
void foo( int x ) {switch (x) { case 1: __attribute__(($1)); case 2: break ; }};
],
[flatten], [
int foo( void ) __attribute__(($1));
],
[format], [
int foo(const char *p, ...) __attribute__(($1(printf, 1, 2)));
],
[gnu_format], [
int foo(const char *p, ...) __attribute__((format(gnu_printf, 1, 2)));
],
[format_arg], [
char *foo(const char *p) __attribute__(($1(1)));
],
[gnu_inline], [
inline __attribute__(($1)) int foo( void ) { return 0; }
],
[hot], [
int foo( void ) __attribute__(($1));
],
[ifunc], [
int my_foo( void ) { return 0; }
static int (*resolve_foo(void))(void) { return my_foo; }
int foo( void ) __attribute__(($1("resolve_foo")));
],
[leaf], [
__attribute__(($1)) int foo( void ) { return 0; }
],
[malloc], [
void *foo( void ) __attribute__(($1));
],
[noclone], [
int foo( void ) __attribute__(($1));
],
[noinline], [
__attribute__(($1)) int foo( void ) { return 0; }
],
[nonnull], [
int foo(char *p) __attribute__(($1(1)));
],
[noreturn], [
void foo( void ) __attribute__(($1));
],
[nothrow], [
int foo( void ) __attribute__(($1));
],
[optimize], [
__attribute__(($1(3))) int foo( void ) { return 0; }
],
[pure], [
int foo( void ) __attribute__(($1));
],
[sentinel], [
int foo(void *p, ...) __attribute__(($1));
],
[sentinel_position], [
int foo(void *p, ...) __attribute__(($1(1)));
],
[returns_nonnull], [
void *foo( void ) __attribute__(($1));
],
[unused], [
int foo( void ) __attribute__(($1));
],
[used], [
int foo( void ) __attribute__(($1));
],
[visibility], [
int foo_def( void ) __attribute__(($1("default")));
int foo_hid( void ) __attribute__(($1("hidden")));
int foo_int( void ) __attribute__(($1("internal")));
int foo_pro( void ) __attribute__(($1("protected")));
],
[warning], [
int foo( void ) __attribute__(($1("")));
],
[warn_unused_result], [
int foo( void ) __attribute__(($1));
],
[weak], [
int foo( void ) __attribute__(($1));
],
[weakref], [
static int foo( void ) { return 0; }
static int bar( void ) __attribute__(($1("foo")));
],
[
m4_warn([syntax], [Unsupported attribute $1, the test may fail])
int foo( void ) __attribute__(($1));
]
)], [])
],
dnl GCC doesn't exit with an error if an unknown attribute is
dnl provided but only outputs a warning, so accept the attribute
dnl only if no warning were issued.
[AS_IF([grep -- -Wattributes conftest.err],
[AS_VAR_SET([ac_var], [no])],
[AS_VAR_SET([ac_var], [yes])])],
[AS_VAR_SET([ac_var], [no])])
])
AS_IF([test yes = AS_VAR_GET([ac_var])],
[AC_DEFINE_UNQUOTED(AS_TR_CPP(HAVE_FUNC_ATTRIBUTE_$1), 1,
[Define to 1 if the system has the `$1' function attribute])], [])
AS_VAR_POPDEF([ac_var])
])

View File

@ -1,12 +1,19 @@
# List of source files which contain translatable strings.
lib/libx52/x52_strerror.c
libx52/x52_strerror.c
libx52/x52_stringify.c
lib/libx52io/io_strings.c
libx52io/io_strings.c
utils/evtest/ev_test.c
evtest/ev_test.c
utils/test/x52_test.c
utils/test/x52_test_clock.c
utils/test/x52_test_common.h
utils/test/x52_test_led.c
utils/test/x52_test_mfd.c
joytest/x52_test.c
joytest/x52_test_clock.c
joytest/x52_test_common.h
joytest/x52_test_led.c
joytest/x52_test_mfd.c
daemon/x52d_main.c
daemon/x52d_clock.c
daemon/x52d_config.c
daemon/x52d_config_parser.c
daemon/x52d_device.c

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