From 931f945133cba68959c022d255e8744003846bad Mon Sep 17 00:00:00 2001 From: nirenjan Date: Thu, 4 Nov 2021 10:50:10 -0700 Subject: [PATCH] Add x52ctl daemon communication program --- Doxyfile.in | 1 + daemon/Makefile.am | 11 ++- daemon/x52ctl.c | 170 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 daemon/x52ctl.c diff --git a/Doxyfile.in b/Doxyfile.in index 0847d5a..0c20486 100644 --- a/Doxyfile.in +++ b/Doxyfile.in @@ -861,6 +861,7 @@ FILE_PATTERNS = \ libx52io.h \ libx52util.h \ x52_cli.c \ + x52ctl.c \ x52dcomm.h \ *.dox diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 9492e39..3c5ecfd 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -3,7 +3,7 @@ # Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org) # # SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0 -bin_PROGRAMS += x52d +bin_PROGRAMS += x52d x52ctl # Service daemon that manages the X52 device x52d_SOURCES = \ @@ -60,6 +60,15 @@ libx52dcomm_la_CFLAGS = \ $(WARN_CFLAGS) libx52dcomm_la_LDFLAGS = $(WARN_LDFLAGS) +x52include_HEADERS += daemon/x52dcomm.h + +x52ctl_SOURCES = daemon/x52ctl.c +x52ctl_CFLAGS = \ + -I $(top_srcdir) \ + $(WARN_CFLAGS) +x52ctl_LDFLAGS = $(WARN_LDFLAGS) +x52ctl_LDADD = libx52dcomm.la + x52dconfdir = @sysconfdir@/x52d x52dconf_DATA = daemon/x52d.conf diff --git a/daemon/x52ctl.c b/daemon/x52ctl.c new file mode 100644 index 0000000..3e95e8c --- /dev/null +++ b/daemon/x52ctl.c @@ -0,0 +1,170 @@ +/* + * Saitek X52 Pro MFD & LED driver - Daemon controller + * + * Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org) + * + * SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0 + */ + +/** +@page x52ctl Command Line controller to X52 daemon + +\htmlonly +x52ctl - Command line controller to X52 daemon +\endhtmlonly + +# SYNOPSIS +\b x52ctl [\a -i] [\a -s socket-path] [command] + +# DESCRIPTION + +x52ctl is a program that can be used to communicate with the X52 daemon. It can +be used either as a one-shot program that can be run from another program or +script, or it can be run interactively. + +Commands are sent to the running daemon, and responses are written to standard +output. + +If not running interactively, then you must specify a command, or the program +will exit with a failure exit code. If running interactively, the program will +request input and send that to the daemon, until the user either enters the +string "quit", or terminates input by using Ctrl+D. +*/ + +#include "config.h" +#include +#include +#include +#include + +#include "x52d_const.h" +#include "x52dcomm.h" + +#define APP_NAME "x52ctl" +#if HAVE_FUNC_ATTRIBUTE_NORETURN +__attribute__((noreturn)) +#endif +static void usage(int exit_code) +{ + fprintf(stderr, _("Usage: %s [-i] [-s socket-path] [command]\n"), APP_NAME); + exit(exit_code); +} + +static int send_command(int sock_fd, char *buffer, size_t buflen) +{ + int rc; + rc = x52d_send_command(sock_fd, buffer, buflen); + if (rc >= 0) { + if (write(STDOUT_FILENO, buffer, rc) < 0) { + perror("write"); + return -1; + } + } else { + perror("x52d_send_command"); + return -1; + } + + fputc('\n', stdout); + return 0; +} + +int main(int argc, char **argv) +{ + bool interactive = false; + char *socket_path = NULL; + int opt; + int i; + int sock_fd; + int rc = EXIT_SUCCESS; + + char buffer[1024]; + size_t buflen; + + /* + * Parse command line arguments + * + * -i Interactive + * -s Socket path + */ + while ((opt = getopt(argc, argv, "is:h")) != -1) { + switch (opt) { + case 'i': + interactive = true; + break; + + case 's': + socket_path = optarg; + break; + + case 'h': + usage(EXIT_SUCCESS); + break; + + default: + usage(EXIT_FAILURE); + break; + } + } + + if (!interactive && optind >= argc) { + usage(EXIT_FAILURE); + } + + /* Connect to the socket */ + sock_fd = x52d_dial_command(socket_path); + if (sock_fd < 0) { + perror("x52d_dial_command"); + return EXIT_FAILURE; + } + + if (interactive) { + if (optind < argc) { + fprintf(stderr, + _("Running in interactive mode, ignoring extra arguments\n")); + } + + fputs("> ", stdout); + while (fgets(buffer, sizeof(buffer), stdin) != NULL) { + if (strcasecmp(buffer, "quit\n") == 0) { + break; + } + + if (send_command(sock_fd, buffer, strlen(buffer))) { + rc = EXIT_FAILURE; + goto cleanup; + } + + fputs("> ", stdout); + } + + } else { + memset(buffer, 0, sizeof(buffer)); + buflen = 0; + for (i = optind; i < argc; i++) { + buflen += snprintf(&buffer[buflen], sizeof(buffer) - buflen, + "%s ", argv[i]); + + if (buflen >= sizeof(buffer)) { + fprintf(stderr, _("Argument length too long\n")); + rc = EXIT_FAILURE; + goto cleanup; + } + } + + if (buflen > sizeof(buffer)) { + buflen = sizeof(buffer); + } + buflen--; + buffer[buflen] = '\0'; + + if (send_command(sock_fd, buffer, buflen)) { + rc = EXIT_FAILURE; + goto cleanup; + } + } + + +cleanup: + close(sock_fd); + return rc; +}