mirror of
https://github.com/workinghard/jslisten.git
synced 2025-12-13 19:02:09 +00:00
Merge pull request #14 from Gemba/master
added alternative button trigger mode, getopt_long for command line o…
This commit is contained in:
42
README.md
42
README.md
@@ -11,17 +11,17 @@
|
||||
## Overview
|
||||
|
||||
This program listen in the background for gamepad inputs. If a special button combination is getting pressed,
|
||||
the provided command line will be invoked. It runs a command once defined buttons were pressed. It was developed for Raspbian and PS3 wireless controller but should work everywhere with udev and [joystick](https://sourceforge.net/projects/linuxconsole/) support.
|
||||
the provided command line will be invoked. It runs a command once defined buttons were pressed. It was developed for Raspbian and PS3 wireless controller but should work everywhere with udev and [joystick](https://sourceforge.net/projects/linuxconsole/) support.
|
||||
|
||||
## Usage
|
||||
|
||||
I have a Raspberry Pi. The installed operation system is raspbian. The Kodi package runs pretty well as a media center. The remote control is an App on a smartphone. So no other controller is needed.
|
||||
I have a Raspberry Pi. The installed operation system is raspbian. The Kodi package runs pretty well as a media center. The remote control is an App on a smartphone. So no other controller is needed.
|
||||
|
||||
After couple of month I found this great [RetroPie](https://retropie.org.uk) project. There is also another great one which brings it on a software level instead making a boot image. It's called [EmulationStation](http://www.emulationstation.org). The setup with my preferred PS3 controller and the configuration went well and it worked fantastic. The game console works perfectly fine and brings me back to the good old jump'n'run 90s but now with big screen and cool wireless controller!
|
||||
|
||||
But one thing was an issue. I wanted to use the same hardware and don't miss my system (raspbian). Dual boot is not cool and takes too long. So basically what i needed is some kind of trigger to end any X11/Kodi processes and start EmulationStation ... and of course vice versa.
|
||||
|
||||
In my configuration Kodi runs as default. Media center is still the primary usage. But the idea was to take the PS3 controller press some button combination and the EmulationStation will apear. After I played enough, just press this buttons again and media center will apear and I can put the PS3 controller back.
|
||||
In my configuration Kodi runs as default. Media center is still the primary usage. But the idea was to take the PS3 controller press some button combination and the EmulationStation will apear. After I played enough, just press this buttons again and media center will apear and I can put the PS3 controller back.
|
||||
|
||||
After searching the internet, I found nothing really interesting. Kodi addon will not work because gamepad support is still missing. EmulationStation addon might work but than ... Kodi solution will be missing. So I had to go one level up to the OS. But even here I found nothing really easy to setup or working. So I decided to write this program. It runs as a daemon in the background and listening in nonblocking mode to /dev/input/js* devices. To make it hotplug capable, it's monitoring the udev signals if no device is present.
|
||||
|
||||
@@ -35,11 +35,30 @@ Changed the default service user to "pi" in jslisten.service
|
||||
|
||||
Use user root only if it's really required.
|
||||
|
||||
<b>Update 02/16/2019:</b>
|
||||
Added a different mode to easier trigger scripts.
|
||||
|
||||
When started with `--mode plain` (the default) the program behaves as used. This is fine if you need the button combination once in a while.
|
||||
|
||||
However if you want to activate a script a few times in a short time, lets say to adjust your audio volume or if you have a led stripe installed in your arcade and want to adjust the brightness, then
|
||||
the `--mode hold` comes in handy.
|
||||
|
||||
When started with `--mode hold` and you have at least two buttons defined to enter an action, then you hold down the first one, while tipping the second one. If you have three buttons defined you can
|
||||
hold the second two and then just tip the third. In a four button set you will have to hold the first three.
|
||||
|
||||
As soon as you release any but the last button (according to the order in the `jslisten.cfg`) you will have to press the all buttons again.
|
||||
|
||||
So the difference to the plain mode is that once you have entered the engaged/elevated state with a three button set for example, you hold down the first two and can quickly trigger the action by
|
||||
pressing the third. In plain mode you would have to press all three buttons over again to do audio or brightness adjustment.
|
||||
|
||||
Also added `--help` for the command line for a brief summary of options.
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
Following example for Raspbian. Should work for many other distributions almost the same way.
|
||||
* Use the precompiled binary in bin/ or run "# make" to create the binary
|
||||
* Place the binary to "/opt/bin" (if you change the folder, please update your init script)
|
||||
* Place the binary to "/opt/bin" (if you change the folder, please update your init script)
|
||||
* Copy the configuration script to /etc/jslisten.cfg
|
||||
* Modify the configuration script to your needs
|
||||
* Copy the service script "utils/jslisten.service" to "/etc/systemd/system"
|
||||
@@ -49,13 +68,13 @@ Following example for Raspbian. Should work for many other distributions almost
|
||||
|
||||
## Configuration
|
||||
|
||||
* I assume your joystick setup is already done and you can see with "jstest /dev/input/js0" nice looking key strokes coming up. There are many tutorials out there and you should not proceed if this doesn't work for you!
|
||||
* Run ./jslisten once to create ~/.jslisten config file or copy utils/jslisten.cfg to /etc
|
||||
* Think which button combination will work for your gamepad. You can configure up to 4 button combination. In my case i picked "Left Shoulder" + "Right Shoulder" + SELECT. I'm pretty sure i will not need to press this buttons at the same time for any games.
|
||||
* I assume your joystick setup is already done and you can see with "jstest /dev/input/js0" nice looking key strokes coming up. There are many tutorials out there and you should not proceed if this doesn't work for you!
|
||||
* Run ./jslisten once to create ~/.jslisten config file or copy utils/jslisten.cfg to /etc
|
||||
* Think which button combination will work for your gamepad. You can configure up to 4 button combination. In my case i picked "Left Shoulder" + "Right Shoulder" + SELECT. I'm pretty sure i will not need to press this buttons at the same time for any games.
|
||||
* Get the number ID's for this buttons with jstest (or any other similar program)
|
||||
* Edit the configuration file (either /etc/jslisten.cfg or ~/.jslisten) and maintain the button ID's and the program which you want to run.
|
||||
* Edit the configuration file (either /etc/jslisten.cfg or ~/.jslisten) and maintain the button ID's and the program which you want to run.
|
||||
* Make sure this script/program can handle multiple simultanios calls! Even if the daemon will wait till this program ends, you never know... And we like to press key combinations many times, if something doesn't work immediately...
|
||||
|
||||
|
||||
Here is my config example:
|
||||
```
|
||||
[Generic]
|
||||
@@ -68,9 +87,9 @@ button4=
|
||||
|
||||
## Known limitations
|
||||
|
||||
* Kodi and X11 are blocking /dev/input/* events. For X11 you can add an exception in /usr/share/X11/xorg.conf.d/10-quirks.conf but Kodi is ... nasty ... As long as they don't implement a nice
|
||||
* Kodi and X11 are blocking /dev/input/* events. For X11 you can add an exception in /usr/share/X11/xorg.conf.d/10-quirks.conf but Kodi is ... nasty ... As long as they don't implement a nice
|
||||
unified unput support, my workaround is to revoke the kodi group rights to the input devices. :(
|
||||
* If you experience any issues, feel free to use '--debug' option
|
||||
* If you experience any issues, feel free to use '--loglevel debug' option and check syslog.
|
||||
|
||||
## Multiple key combinations
|
||||
You can have different key sets to run different programs:
|
||||
@@ -89,4 +108,3 @@ button2=3
|
||||
button3=
|
||||
button4=
|
||||
```
|
||||
|
||||
|
||||
247
src/jslisten.c
247
src/jslisten.c
@@ -18,28 +18,29 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <getopt.h>
|
||||
#include <libudev.h>
|
||||
#include <locale.h>
|
||||
#include <pwd.h>
|
||||
#include <signal.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <locale.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <signal.h>
|
||||
#include <syslog.h>
|
||||
#include <pwd.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <linux/input.h>
|
||||
#include <linux/joystick.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include "minIni.h"
|
||||
|
||||
#include "minIni.h"
|
||||
#include "axbtnmap.h"
|
||||
|
||||
//---------------------------------
|
||||
@@ -85,7 +86,27 @@ struct KeySet myKeys[MAX_HOTKEYS];
|
||||
|
||||
int numHotkeys = 0;
|
||||
//int buttonActive = 0;
|
||||
int logLevel = LOG_NOTICE;
|
||||
int logLevel = LOG_INFO;
|
||||
|
||||
// recognized values for --loglevel
|
||||
#define LVL_DEBUG_STR "debug"
|
||||
#define LVL_NOTICE_STR "notice"
|
||||
|
||||
// recognized values for --mode
|
||||
#define MODE_PLAIN_STR "plain"
|
||||
#define MODE_HOLD_STR "hold"
|
||||
|
||||
static struct option long_options[] = {
|
||||
{"device", required_argument, 0, 'd'},
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{"loglevel", required_argument, 0, 'l'},
|
||||
{"mode", required_argument, 0, 'm'},
|
||||
{0, 0, 0, 0}
|
||||
};
|
||||
|
||||
typedef enum {PLAIN, HOLD} mode_type;
|
||||
mode_type mode = PLAIN;
|
||||
|
||||
|
||||
//---------------------------------
|
||||
// Check if the button was assigned
|
||||
@@ -198,8 +219,7 @@ void readConfig(void) {
|
||||
//---------------------------------------------
|
||||
int checkConfig(void) {
|
||||
int rc=0;
|
||||
int i;
|
||||
for (i=0; i<numHotkeys; i++) {
|
||||
for (int i=0; i<numHotkeys; i++) {
|
||||
if ( sizearray(myKeys[i].swFilename) < 3 ) { // no program make no sense
|
||||
syslog(LOG_ERR, "err: no valid filename provided in section %d. Please check ini file\n", i);
|
||||
rc = 1;
|
||||
@@ -225,17 +245,15 @@ int checkButtonPressed(struct js_event js) {
|
||||
for (i=0; i<numHotkeys; i++) {
|
||||
if ( js.number == myKeys[i].button1 ) {
|
||||
myKeys[i].button1Active = js.value;
|
||||
}
|
||||
if ( js.number == myKeys[i].button2 ) {
|
||||
} else if ( js.number == myKeys[i].button2 ) {
|
||||
myKeys[i].button2Active = js.value;
|
||||
}
|
||||
if ( js.number == myKeys[i].button3 ) {
|
||||
} else if ( js.number == myKeys[i].button3 ) {
|
||||
myKeys[i].button3Active = js.value;
|
||||
}
|
||||
if ( js.number == myKeys[i].button4 ) {
|
||||
} else if ( js.number == myKeys[i].button4 ) {
|
||||
myKeys[i].button4Active = js.value;
|
||||
}
|
||||
}
|
||||
|
||||
// Analyse combinations
|
||||
for (i=0; i<numHotkeys; i++) {
|
||||
switch (myKeys[i].activeButtons) {
|
||||
@@ -265,9 +283,8 @@ int checkButtonPressed(struct js_event js) {
|
||||
//---------------------------------------------
|
||||
// Reset the keys
|
||||
//---------------------------------------------
|
||||
void resetHotkeys(){
|
||||
int i;
|
||||
for (i=0; i<numHotkeys; i++) {
|
||||
void resetHotkeys() {
|
||||
for (int i=0; i<numHotkeys; i++) {
|
||||
myKeys[i].button1Active = 0;
|
||||
myKeys[i].button2Active = 0;
|
||||
myKeys[i].button3Active = 0;
|
||||
@@ -426,6 +443,33 @@ void listenJoy (void) {
|
||||
|
||||
}
|
||||
|
||||
//---------------------------------------------
|
||||
// check if any of the n-1 buttons is released.
|
||||
//
|
||||
// only to be called when js.value is 0. if the release event relates to
|
||||
// one of the n-1 buttons the "engaged" level will be left.
|
||||
//
|
||||
// returns 1 if all but the last buttons are no longer pressed or
|
||||
// if only one button is defined to trigger an action
|
||||
// returns 0 if any of the n-1 is still pressed
|
||||
//---------------------------------------------
|
||||
int button_held(int js_btn_number, int button_set_idx) {
|
||||
int activeKeys = myKeys[button_set_idx].activeButtons - 1;
|
||||
switch (activeKeys) {
|
||||
case 1:
|
||||
return js_btn_number == myKeys[button_set_idx].button1;
|
||||
case 2:
|
||||
return js_btn_number == myKeys[button_set_idx].button1
|
||||
|| js_btn_number == myKeys[button_set_idx].button2;
|
||||
case 3:
|
||||
return js_btn_number == myKeys[button_set_idx].button1
|
||||
|| js_btn_number == myKeys[button_set_idx].button2
|
||||
|| js_btn_number == myKeys[button_set_idx].button3;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------
|
||||
// Listen on the input and call the program
|
||||
//---------------------------------------------
|
||||
@@ -494,29 +538,42 @@ int bindJoy(void) {
|
||||
// Non-blocking reading
|
||||
struct js_event js;
|
||||
int needTrigger;
|
||||
int lastTriggeredSet = -1;
|
||||
fcntl(joyFD, F_SETFL, O_NONBLOCK);
|
||||
|
||||
while (1) {
|
||||
while (read(joyFD, &js, sizeof(struct js_event)) == sizeof(struct js_event)) {
|
||||
syslog(LOG_DEBUG, "Event: type %d, time %d, number %d, value %d\n",
|
||||
js.type, js.time, js.number, js.value);
|
||||
needTrigger = checkButtonPressed(js);
|
||||
if ( needTrigger > -1 ) { // We have found one key section
|
||||
syslog(LOG_INFO, "Swtching mode. ...\n");
|
||||
// call external program
|
||||
int rc = system(myKeys[needTrigger].swFilename);
|
||||
if ( rc == 0 ) {
|
||||
syslog(LOG_INFO, "Call succesfull\n");
|
||||
} else {
|
||||
syslog(LOG_INFO, "Call failed\n");
|
||||
if (js.type == JS_EVENT_BUTTON) {
|
||||
syslog(LOG_DEBUG, "Event: type %d, time %d, number %d, value %d\n",
|
||||
js.type, js.time, js.number, js.value);
|
||||
if (mode == HOLD && js.value == 0 && lastTriggeredSet > -1 && button_held(js.number, lastTriggeredSet)) {
|
||||
resetHotkeys();
|
||||
lastTriggeredSet = -1;
|
||||
}
|
||||
needTrigger = checkButtonPressed(js);
|
||||
if ( needTrigger > -1 ) { // We have found one key section
|
||||
if (mode == HOLD) {
|
||||
// set to "engaged" mode (lastTriggeredSet > -1) and "fire" command once
|
||||
lastTriggeredSet = needTrigger;
|
||||
}
|
||||
syslog(LOG_INFO, "Swtching mode. ...\n");
|
||||
// call external program
|
||||
int rc = system(myKeys[needTrigger].swFilename);
|
||||
if ( rc == 0 ) {
|
||||
syslog(LOG_INFO, "Call succesfull\n");
|
||||
} else {
|
||||
syslog(LOG_INFO, "Call failed\n");
|
||||
}
|
||||
if (mode == PLAIN) {
|
||||
// reset state, so we call only once
|
||||
resetHotkeys();
|
||||
}
|
||||
}
|
||||
// reset state, so we call only once
|
||||
resetHotkeys();
|
||||
}
|
||||
}
|
||||
|
||||
if (errno != EAGAIN) {
|
||||
syslog(LOG_DEBUG, "\njslistent: error reading"); // Regular exit if the joystick disconnect
|
||||
syslog(LOG_DEBUG, "\njslisten: error reading"); // Regular exit if the joystick disconnect
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -525,6 +582,7 @@ int bindJoy(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------
|
||||
// Exit function
|
||||
//---------------------------------------------
|
||||
@@ -539,21 +597,73 @@ void signal_callback_handler(int signum) {
|
||||
}
|
||||
|
||||
|
||||
void usage() {
|
||||
printf("jslisten [<options>], where <options> are:\n");
|
||||
printf(" --device <path>\t use explicit game controller path e.g. /dev/input/by-id/... instead of first one found.\n");
|
||||
printf(" --loglevel <level>\t defines the minimum syslog log level. <level> is one of '%s' or '%s'. Defaults to 'info'.\n", LVL_DEBUG_STR, LVL_NOTICE_STR);
|
||||
printf(" --mode <mode>\t\t defines the button trigger behaviour. <mode> is either '%s' or '%s'. Defaults to '%s'\n", MODE_PLAIN_STR, MODE_HOLD_STR, MODE_PLAIN_STR);
|
||||
printf(" --help\t\t print this help and exit\n\n");
|
||||
printf("For more information please visit: https://github.com/workinghard/jslisten\n");
|
||||
}
|
||||
|
||||
//---------------------------------------------
|
||||
// main function
|
||||
// parse the command line parameters
|
||||
//---------------------------------------------
|
||||
int main(int argc, char* argv[]) {
|
||||
int rc;
|
||||
// Register signal and signal handler
|
||||
signal(SIGINT, signal_callback_handler);
|
||||
signal(SIGKILL, signal_callback_handler);
|
||||
signal(SIGTERM, signal_callback_handler);
|
||||
signal(SIGHUP, signal_callback_handler);
|
||||
void parse_command_line(int argc, char* argv[]) {
|
||||
int c;
|
||||
while (1) {
|
||||
int option_index = 0;
|
||||
|
||||
c = getopt_long (argc, argv, "hl:d:m:", long_options, &option_index);
|
||||
|
||||
if (c == -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
switch (c) {
|
||||
case 'h':
|
||||
usage();
|
||||
exit(0);
|
||||
case 'l':
|
||||
if (strncmp(optarg, LVL_DEBUG_STR, strlen(LVL_DEBUG_STR)) == 0) {
|
||||
logLevel = LOG_DEBUG;
|
||||
} else if ((strncmp(optarg, LVL_NOTICE_STR, strlen(LVL_NOTICE_STR)) == 0)) {
|
||||
logLevel = LOG_NOTICE;
|
||||
}
|
||||
break;
|
||||
case 'd':
|
||||
if (strlen(optarg) < NAME_LENGTH) {
|
||||
strncpy(myDevPath, optarg, NAME_LENGTH-1);
|
||||
} else {
|
||||
syslog(LOG_WARNING, "--device <path> parameter too long. Using default.\n");
|
||||
}
|
||||
break;
|
||||
case 'm':
|
||||
if (strncmp(optarg, MODE_HOLD_STR, strlen(MODE_HOLD_STR)) == 0) {
|
||||
mode = HOLD;
|
||||
}
|
||||
if (strncmp(optarg, MODE_PLAIN_STR, strlen(MODE_PLAIN_STR)) == 0) {
|
||||
mode = PLAIN;
|
||||
} else {
|
||||
syslog(LOG_WARNING, "--mode %s parameter unknown. Using default.\n", optarg);
|
||||
|
||||
}
|
||||
break;
|
||||
case '?':
|
||||
syslog(LOG_ERR, "Misconfigured command line parameters. Exiting. ...\n");
|
||||
exit(1);
|
||||
default:
|
||||
abort ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Init Defaults
|
||||
int i;
|
||||
for (i=0; i<MAX_HOTKEYS; i++) {
|
||||
//---------------------------------------------
|
||||
// init button keysets to defaults
|
||||
//---------------------------------------------
|
||||
void init_button_keysets() {
|
||||
for (int i=0; i<MAX_HOTKEYS; i++) {
|
||||
myKeys[i].button1 = BUTTON_DEFINED_RANGE;
|
||||
myKeys[i].button1Active = 0;
|
||||
myKeys[i].button2 = BUTTON_DEFINED_RANGE;
|
||||
@@ -565,39 +675,34 @@ int main(int argc, char* argv[]) {
|
||||
myKeys[i].activeButtons = 0;
|
||||
myKeys[i].isTriggered = false;
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------
|
||||
// main function
|
||||
//---------------------------------------------
|
||||
int main(int argc, char* argv[]) {
|
||||
int rc;
|
||||
// Register signal and signal handler
|
||||
signal(SIGINT, signal_callback_handler);
|
||||
signal(SIGKILL, signal_callback_handler);
|
||||
signal(SIGTERM, signal_callback_handler);
|
||||
signal(SIGHUP, signal_callback_handler);
|
||||
|
||||
// Default device path to check
|
||||
strncpy(myDevPath, "/js", NAME_LENGTH-1);
|
||||
|
||||
// Parse parameters to set debug level
|
||||
if ( argc > 1 ) {
|
||||
for (int i=1; i<argc; i++) {
|
||||
if (strncmp(argv[i], "--debug", 7) == 0) {
|
||||
logLevel = LOG_DEBUG;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Open Syslog
|
||||
setlogmask (LOG_UPTO (logLevel));
|
||||
openlog (MYPROGNAME, LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL1);
|
||||
|
||||
parse_command_line(argc, argv);
|
||||
|
||||
setlogmask (LOG_UPTO (logLevel));
|
||||
|
||||
syslog(LOG_NOTICE, "Using device path: %s\n", myDevPath);
|
||||
syslog(LOG_NOTICE, "Using button mode: %s\n", mode == PLAIN ? MODE_PLAIN_STR : MODE_HOLD_STR);
|
||||
|
||||
syslog(LOG_NOTICE, "Listen to joystick inputs ...\n");
|
||||
|
||||
// Parse parameters to set device path
|
||||
if ( argc > 1 ) {
|
||||
for (int i=1; i<argc; i++) {
|
||||
if (strncmp(argv[i], "--device", 8) == 0) {
|
||||
if ( argc > i+1 ) {
|
||||
if ( strlen(argv[i+1]) < NAME_LENGTH ) {
|
||||
strncpy(myDevPath, argv[i+1], NAME_LENGTH-1);
|
||||
syslog(LOG_NOTICE, "Using device path %s\n", myDevPath);
|
||||
}
|
||||
} else {
|
||||
syslog(LOG_NOTICE, "Missing device path parameter. Using default %s\n", myDevPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Get the configuration file
|
||||
rc = getConfigFile();
|
||||
if ( rc > 0 ) {
|
||||
|
||||
Reference in New Issue
Block a user