/*
* jslisten
*
* Created by Nikolai Rinas on 27.03.15.
* Copyright (c) 2015 Nikolai Rinas. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "minIni.h"
#include "axbtnmap.h"
//---------------------------------
// Some static stuff
//---------------------------------
#define sizearray(a) (sizeof(a) / sizeof((a)[0]))
#define BUTTON_DEFINED_RANGE -2147483647 // sizeof(long)
#define true 0
#define false 1
#define NAME_LENGTH 128
#define MYPROGNAME "jslisten"
#define myConfFile "/.jslisten"
#define myGlConfFile "/etc/jslisten.cfg"
//#define MY_LOG_LEVEL LOG_NOTICE //LOG_DEBUG //LOG_NOTICE
#define INI_BUFFERSIZE 512
#define MAX_HOTKEYS 99
//---------------------------------
// Global definitions
//---------------------------------
char iniFile[INI_BUFFERSIZE];
char myDevPath[NAME_LENGTH];
int joyFD;
struct KeySet{
// Set Default unassigned
long button1;
int button1Active;
long button2;
int button2Active;
long button3;
int button3Active;
long button4;
int button4Active;
int activeButtons;
int isTriggered;
char swFilename[100];
};
struct KeySet myKeys[MAX_HOTKEYS];
int numHotkeys = 0;
//int buttonActive = 0;
int logLevel = LOG_NOTICE;
//---------------------------------
// Check if the button was assigned
//---------------------------------
int buttonDefined(int val) {
if ( val > BUTTON_DEFINED_RANGE ) {
return true;
}else{
return false;
}
}
//---------------------------------
// get the configuration file
//---------------------------------
int getConfigFile() {
int rc=1; // Default nothing found
// Determine home dir
if ( getenv("HOME") != NULL ) {
syslog(LOG_INFO, "taking user config %s\n", iniFile);
strcat(strcpy(iniFile, getenv("HOME")), myConfFile);
// Look for personal file
if( access( iniFile, R_OK ) != -1 ) {
// file exists
rc = 0;
syslog(LOG_INFO, "reading config %s\n", iniFile);
}
}
if ( rc > 0 ) {
// file doesn't exist, check global
if( access( myGlConfFile, R_OK ) != -1 ) {
strcpy(iniFile, myGlConfFile);
rc = 0;
syslog(LOG_INFO, "reading config %s\n", iniFile);
}else{
// Write a default file to the home dir
FILE *f = fopen(iniFile, "w");
if (f == NULL) {
syslog(LOG_ERR, "err: failed write config file %s\n", myConfFile);
}else{
const char *defaultConfig = "[Generic]\nprogram=\nbutton1=\nbutton2=\nbutton3=\nbutton4=\n";
fprintf(f, "%s\n", defaultConfig);
syslog(LOG_ERR, "err: no config found. Please maintain all required values in %s\n", iniFile);
}
}
}
return rc;
}
//---------------------------------------
// Get configuration items from the file
//---------------------------------------
void readConfig(void) {
char str[100];
int s, k;
char section[50];
long l;
int n;
/* section/key enumeration */
for (s = 0; ini_getsection(s, section, sizearray(section), iniFile) > 0; s++) {
if ( numHotkeys < MAX_HOTKEYS ) {
for (k = 0; ini_getkey(section, k, str, sizearray(str), iniFile) > 0; k++) {
if ( strncmp("program", str, 7) == 0 ) { // Key found
n = ini_gets(section, str, "dummy", myKeys[numHotkeys].swFilename, sizearray(myKeys[numHotkeys].swFilename), iniFile);
if ( n > 5 && strncmp("dummy", myKeys[numHotkeys].swFilename, 5) != 0 ) { // Value is not empty
syslog(LOG_INFO, "Filename: %s\n", myKeys[numHotkeys].swFilename);
}
}
if ( strncmp("button1", str, 7) == 0 ) { // Key found
l = ini_getl(section, str, BUTTON_DEFINED_RANGE, iniFile);
if ( buttonDefined(l) == true ) { // Value is not empty
syslog(LOG_INFO, "button1: %ld\n", l);
myKeys[numHotkeys].button1 = l;
myKeys[numHotkeys].activeButtons++;
}
}
if ( strncmp("button2", str, 7) == 0 ) { // Key found
l = ini_getl(section, str, BUTTON_DEFINED_RANGE, iniFile);
if ( buttonDefined(l) == true ) { // Value is not empty
syslog(LOG_INFO, "button2: %ld\n", l);
myKeys[numHotkeys].button2 = l;
myKeys[numHotkeys].activeButtons++;
}
}
if ( strncmp("button3", str, 7) == 0 ) { // Key found
l = ini_getl(section, str, BUTTON_DEFINED_RANGE, iniFile);
if ( buttonDefined(l) == true ) { // Value is not empty
syslog(LOG_INFO, "button3: %ld\n", l);
myKeys[numHotkeys].button3 = l;
myKeys[numHotkeys].activeButtons++;
}
}
if ( strncmp("button4", str, 7) == 0 ) { // Key found
l = ini_getl(section, str, BUTTON_DEFINED_RANGE, iniFile);
if ( buttonDefined(l) == true ){ // Value is not empty
syslog(LOG_INFO, "button4: %ld\n", l);
myKeys[numHotkeys].button4 = l;
myKeys[numHotkeys].activeButtons++;
}
}
} /* for */
}
numHotkeys++; // Remember how many sections we have
} /* for */
}
//---------------------------------------------
// Validity check of the provided config items
//---------------------------------------------
int checkConfig(void) {
int rc=0;
int i;
for (i=0;i 0 && FD_ISSET(fd, &fds)) {
syslog(LOG_DEBUG,"\nselect() says there should be data\n");
/* Make the call to receive the device.
select() ensured that this will not block. */
dev = udev_monitor_receive_device(mon);
if (dev) {
syslog(LOG_DEBUG, "Got Device\n");
syslog(LOG_DEBUG, " Node: %s\n", udev_device_get_devnode(dev));
syslog(LOG_DEBUG, " Subsystem: %s\n", udev_device_get_subsystem(dev));
syslog(LOG_DEBUG, " Devtype: %s\n", udev_device_get_devtype(dev));
syslog(LOG_DEBUG, " Action: %s\n",udev_device_get_action(dev));
if ( strncmp(udev_device_get_action(dev), "add", 3) == 0 ) { // Device added
/* enumerate joypad devices */
/* Create a list of the devices in the 'input' subsystem. */
enumerate = udev_enumerate_new(udev);
udev_enumerate_add_match_subsystem(enumerate, "input");
udev_enumerate_scan_devices(enumerate);
devices = udev_enumerate_get_list_entry(enumerate);
udev_list_entry_foreach(dev_list_entry, devices) {
const char* name;
const char* sysPath;
const char* devPath;
name = udev_list_entry_get_name(dev_list_entry);
mydev = udev_device_new_from_syspath(udev, name);
sysPath = udev_device_get_syspath(mydev);
devPath = udev_device_get_devnode(mydev);
if (sysPath != NULL && devPath != NULL && strstr(sysPath, myDevPath) != 0) {
syslog(LOG_NOTICE, "Found Device: %s\n", devPath);
if ((joyFD = open(devPath, O_RDONLY)) < 0) { // Open the file descriptor
syslog(LOG_INFO, "error: failed to open fd\n");
}
}
udev_device_unref(mydev);
}
/* cleanup */
udev_enumerate_unref(enumerate);
}else{
if ( strncmp(udev_device_get_action(dev), "remove", 6) == 0 ) { // Device remove
if ( joyFD >= 0 ) {
close(joyFD);
}
joyFD = -1; // Reset
}
}
}else{
syslog(LOG_WARNING, "No Device from receive_device(). An error occured.\n");
}
udev_device_unref(dev);
}
usleep(250*1000);
syslog(LOG_DEBUG, ".");
fflush(stdout);
}
}
udev_unref(udev);
}
//---------------------------------------------
// Listen on the input and call the program
//---------------------------------------------
int bindJoy(void) {
char *axis_names[ABS_MAX + 1] = { "X", "Y", "Z", "Rx", "Ry", "Rz", "Throttle", "Rudder",
"Wheel", "Gas", "Brake", "?", "?", "?", "?", "?",
"Hat0X", "Hat0Y", "Hat1X", "Hat1Y", "Hat2X", "Hat2Y", "Hat3X", "Hat3Y",
"?", "?", "?", "?", "?", "?", "?",
};
char *button_names[KEY_MAX - BTN_MISC + 1] = {
"Btn0", "Btn1", "Btn2", "Btn3", "Btn4", "Btn5", "Btn6", "Btn7", "Btn8", "Btn9", "?", "?", "?", "?", "?", "?",
"LeftBtn", "RightBtn", "MiddleBtn", "SideBtn", "ExtraBtn", "ForwardBtn", "BackBtn", "TaskBtn", "?", "?", "?", "?", "?", "?", "?", "?",
"Trigger", "ThumbBtn", "ThumbBtn2", "TopBtn", "TopBtn2", "PinkieBtn", "BaseBtn", "BaseBtn2", "BaseBtn3", "BaseBtn4", "BaseBtn5", "BaseBtn6", "BtnDead",
"BtnA", "BtnB", "BtnC", "BtnX", "BtnY", "BtnZ", "BtnTL", "BtnTR", "BtnTL2", "BtnTR2", "BtnSelect", "BtnStart", "BtnMode", "BtnThumbL", "BtnThumbR", "?",
"?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?",
"WheelBtn", "Gear up",
};
unsigned char axes = 2;
unsigned char buttons = 2;
int version = 0x000800;
char name[NAME_LENGTH] = "Unknown";
uint16_t btnmap[BTNMAP_SIZE];
uint8_t axmap[AXMAP_SIZE];
int btnmapok = 1;
int i;
ioctl(joyFD, JSIOCGVERSION, &version);
ioctl(joyFD, JSIOCGAXES, &axes);
ioctl(joyFD, JSIOCGBUTTONS, &buttons);
ioctl(joyFD, JSIOCGNAME(NAME_LENGTH), name);
getaxmap(joyFD, axmap);
getbtnmap(joyFD, btnmap);
syslog(LOG_INFO, "Driver version is %d.%d.%d.\n",
version >> 16, (version >> 8) & 0xff, version & 0xff);
/* Determine whether the button map is usable. */
for (i = 0; btnmapok && i < buttons; i++) {
if (btnmap[i] < BTN_MISC || btnmap[i] > KEY_MAX) {
btnmapok = 0;
break;
}
}
if (!btnmapok) {
/* btnmap out of range for names. Don't print any. */
syslog(LOG_WARNING, "jslisten is not fully compatible with your kernel. Unable to retrieve button map!\n");
syslog(LOG_INFO, "Joystick (%s) has %d axes ", name, axes);
syslog(LOG_INFO, "and %d buttons.\n", buttons);
} else {
syslog(LOG_INFO, "Joystick (%s) has %d axes (", name, axes);
for (i = 0; i < axes; i++) {
syslog(LOG_INFO, "%s%s", i > 0 ? ", " : "", axis_names[axmap[i]]);
}
syslog(LOG_INFO, ")\n");
syslog(LOG_INFO, "and %d buttons (", buttons);
for (i = 0; i < buttons; i++) {
syslog(LOG_INFO, "%s%s", i > 0 ? ", " : "", button_names[btnmap[i] - BTN_MISC]);
}
syslog(LOG_INFO, ").\n");
}
// Non-blocking reading
struct js_event js;
int needTrigger;
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");
}
// reset state, so we call only once
resetHotkeys();
}
}
if (errno != EAGAIN) {
syslog(LOG_DEBUG, "\njslistent: error reading"); // Regular exit if the joystick disconnect
return 1;
}
usleep(10000);
}
return 0;
}
//---------------------------------------------
// Exit function
//---------------------------------------------
void signal_callback_handler(int signum) {
syslog(LOG_NOTICE, "Exit. Caught signal %d\n",signum);
// Cleanup and close up stuff here
closelog();
// Terminate program
exit(0);
}
//---------------------------------------------
// 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);
// Init Defaults
int i;
for (i=0;i 1 ) {
for (int i=1;i 1 ) {
for (int i=1;i i+1 ) {
if ( strlen(argv[i+1]) < NAME_LENGTH ){
strcpy(myDevPath, argv[i+1]);
syslog(LOG_NOTICE, "Using device path %s\n", myDevPath);
}
}else{
syslog(LOG_NOTICE, "Missing device path parameter\n");
}
}
}
}
// Get the configuration file
rc = getConfigFile();
if ( rc > 0 ) {
return rc;
}
// Read the configuration
readConfig();
// Check if we have everything
rc = checkConfig();
if ( rc > 0 ) {
return rc;
}
// If everything is set up, run ...
if ( rc == 0 ) {
// Main endless loop
while (1) {
listenJoy(); // Find our joystick
if ( joyFD > 0 ) {
bindJoy(); // If found, use it and listen to the keys
}
}
}
return 0;
}