kopia lustrzana https://github.com/FreeSpacenav/spacenavd
544 wiersze
11 KiB
C
544 wiersze
11 KiB
C
/*
|
|
spacenavd - a free software replacement driver for 6dof space-mice.
|
|
Copyright (C) 2007-2021 John Tsiombikas <nuclear@member.fsf.org>
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#include "config.h"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <signal.h>
|
|
#include <assert.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <sys/select.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/un.h>
|
|
#include "spnavd.h"
|
|
#include "logger.h"
|
|
#include "dev.h"
|
|
#include "hotplug.h"
|
|
#include "client.h"
|
|
#include "proto_unix.h"
|
|
#ifdef USE_X11
|
|
#include "proto_x11.h"
|
|
#endif
|
|
|
|
static void print_usage(const char *argv0);
|
|
static void cleanup(void);
|
|
static void redir_log(int fallback_syslog);
|
|
static void daemonize(void);
|
|
static int write_pid_file(void);
|
|
static int find_running_daemon(void);
|
|
static void handle_events(fd_set *rset);
|
|
static void sig_handler(int s);
|
|
static char *fix_path(char *str);
|
|
|
|
char *cfgfile = DEF_CFGFILE;
|
|
static char *logfile = DEF_LOGFILE;
|
|
static int pfd[2];
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
int i, pid, ret, become_daemon = 1;
|
|
int force_logfile = 0;
|
|
|
|
for(i=1; i<argc; i++) {
|
|
if(argv[i][0] == '-') {
|
|
if(argv[i][1] == 'v') {
|
|
char *p = argv[i] + 1;
|
|
while(*p++) verbose++;
|
|
|
|
} else if(argv[i][2] == 0) {
|
|
switch(argv[i][1]) {
|
|
case 'd':
|
|
become_daemon = !become_daemon;
|
|
break;
|
|
|
|
case 'c':
|
|
if(!argv[++i]) {
|
|
fprintf(stderr, "-c must be followed by the config file name\n");
|
|
return 1;
|
|
}
|
|
cfgfile = fix_path(argv[i]);
|
|
break;
|
|
|
|
case 'l':
|
|
if(!argv[++i]) {
|
|
fprintf(stderr, "-l must be followed by a logfile name or \"syslog\"\n");
|
|
return 1;
|
|
}
|
|
if(strcmp(argv[i], "syslog") == 0) {
|
|
logfile = 0;
|
|
} else {
|
|
logfile = fix_path(argv[i]);
|
|
if(strcmp(logfile, argv[i]) != 0) {
|
|
printf("logfile: %s\n", logfile);
|
|
}
|
|
/* when the user specifies a log file in the command line
|
|
* the expectation is to use it, regardless of whether
|
|
* spacenavd is started daemonized or not.
|
|
*/
|
|
force_logfile = 1;
|
|
}
|
|
break;
|
|
|
|
case 'V':
|
|
printf("spacenavd " VERSION "\n");
|
|
return 0;
|
|
|
|
case 'h':
|
|
print_usage(argv[0]);
|
|
return 0;
|
|
|
|
default:
|
|
fprintf(stderr, "invalid option: %s\n", argv[i]);
|
|
return 1;
|
|
}
|
|
|
|
} else if(strcmp(argv[i], "-version") == 0) {
|
|
printf("spacenavd " VERSION "\n");
|
|
return 0;
|
|
|
|
} else if(strcmp(argv[i], "-help") == 0 || strcmp(argv[i], "--help") == 0) {
|
|
print_usage(argv[0]);
|
|
return 0;
|
|
|
|
} else {
|
|
fprintf(stderr, "invalid option: %s\n\n", argv[i]);
|
|
print_usage(argv[0]);
|
|
return 1;
|
|
}
|
|
|
|
} else {
|
|
fprintf(stderr, "unexpected argument: %s\n\n", argv[i]);
|
|
print_usage(argv[0]);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if((pid = find_running_daemon()) != -1) {
|
|
fprintf(stderr, "Spacenav daemon already running (pid: %d). Aborting.\n", pid);
|
|
return 1;
|
|
}
|
|
|
|
if(become_daemon) {
|
|
daemonize();
|
|
} else {
|
|
if(force_logfile) {
|
|
redir_log(0);
|
|
}
|
|
}
|
|
write_pid_file();
|
|
|
|
logmsg(LOG_INFO, "Spacenav daemon " VERSION "\n");
|
|
|
|
read_cfg(cfgfile, &cfg);
|
|
prev_cfg = cfg;
|
|
pipe(pfd);
|
|
|
|
signal(SIGINT, sig_handler);
|
|
signal(SIGTERM, sig_handler);
|
|
signal(SIGSEGV, sig_handler);
|
|
signal(SIGHUP, sig_handler);
|
|
signal(SIGUSR1, sig_handler);
|
|
signal(SIGUSR2, sig_handler);
|
|
signal(SIGPIPE, SIG_IGN);
|
|
|
|
init_devices();
|
|
init_hotplug();
|
|
|
|
init_unix();
|
|
#ifdef USE_X11
|
|
init_x11();
|
|
#endif
|
|
|
|
atexit(cleanup);
|
|
|
|
for(;;) {
|
|
fd_set rset;
|
|
int fd, max_fd = 0;
|
|
struct client *client_iter;
|
|
struct device *dev;
|
|
|
|
FD_ZERO(&rset);
|
|
|
|
dev = get_devices();
|
|
while(dev) {
|
|
if((fd = get_device_fd(dev)) != -1) {
|
|
FD_SET(fd, &rset);
|
|
if(fd > max_fd) max_fd = fd;
|
|
}
|
|
dev = dev->next;
|
|
}
|
|
|
|
if((fd = get_hotplug_fd()) != -1) {
|
|
FD_SET(fd, &rset);
|
|
if(fd > max_fd) max_fd = fd;
|
|
}
|
|
|
|
/* the UNIX domain socket listening for connections */
|
|
if((fd = get_unix_socket()) != -1) {
|
|
FD_SET(fd, &rset);
|
|
if(fd > max_fd) max_fd = fd;
|
|
}
|
|
|
|
/* all the UNIX socket clients */
|
|
client_iter = first_client();
|
|
while(client_iter) {
|
|
if(get_client_type(client_iter) == CLIENT_UNIX) {
|
|
int s = get_client_socket(client_iter);
|
|
assert(s >= 0);
|
|
|
|
FD_SET(s, &rset);
|
|
if(s > max_fd) max_fd = s;
|
|
}
|
|
client_iter = next_client();
|
|
}
|
|
|
|
/* and the X server socket */
|
|
#ifdef USE_X11
|
|
if((fd = get_x11_socket()) != -1) {
|
|
FD_SET(fd, &rset);
|
|
if(fd > max_fd) max_fd = fd;
|
|
}
|
|
#endif
|
|
|
|
/* also the self-pipe read-end for safe SIGHUP handling */
|
|
FD_SET(pfd[0], &rset);
|
|
if(pfd[0] > max_fd) max_fd = fd;
|
|
|
|
do {
|
|
/* if there is at least one device out of the deadzone and repeat is enabled
|
|
* wait for only as long as specified in cfg.repeat_msec
|
|
*/
|
|
struct timeval tv, *timeout = 0;
|
|
if(cfg.repeat_msec >= 0) {
|
|
dev = get_devices();
|
|
while(dev) {
|
|
if(is_device_valid(dev) && !in_deadzone(dev)) {
|
|
tv.tv_sec = cfg.repeat_msec / 1000;
|
|
tv.tv_usec = cfg.repeat_msec % 1000;
|
|
timeout = &tv;
|
|
break;
|
|
}
|
|
dev = dev->next;
|
|
}
|
|
}
|
|
|
|
ret = select(max_fd + 1, &rset, 0, 0, timeout);
|
|
} while(ret == -1 && errno == EINTR);
|
|
|
|
if(ret > 0) {
|
|
handle_events(&rset);
|
|
} else {
|
|
if(cfg.repeat_msec >= 0) {
|
|
dev = get_devices();
|
|
while(dev) {
|
|
if(!in_deadzone(dev)) {
|
|
repeat_last_event(dev);
|
|
}
|
|
dev = dev->next;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return 0; /* unreachable */
|
|
}
|
|
|
|
static void print_usage(const char *argv0)
|
|
{
|
|
printf("usage: %s [options]\n", argv0);
|
|
printf("options:\n");
|
|
printf(" -d: do not daemonize\n");
|
|
printf(" -c <file>: config file path (default: " DEF_CFGFILE ")\n");
|
|
printf(" -l <file>|syslog: log file path or log to syslog (default: " DEF_LOGFILE ")\n");
|
|
printf(" -v: verbose output (use multiple times for greater effect)\n");
|
|
printf(" -V,-version: print version number and exit\n");
|
|
printf(" -h,-help: print usage information and exit\n");
|
|
}
|
|
|
|
static void cleanup(void)
|
|
{
|
|
struct device *dev;
|
|
|
|
#ifdef USE_X11
|
|
close_x11(); /* call to avoid leaving garbage in the X server's root windows */
|
|
#endif
|
|
close_unix();
|
|
|
|
shutdown_hotplug();
|
|
|
|
dev = get_devices();
|
|
while(dev) {
|
|
struct device *tmp = dev;
|
|
dev = dev->next;
|
|
remove_device(tmp);
|
|
}
|
|
|
|
remove(PIDFILE);
|
|
}
|
|
|
|
static void redir_log(int fallback_syslog)
|
|
{
|
|
int i, fd = -1;
|
|
|
|
if(logfile) {
|
|
fd = start_logfile(logfile);
|
|
}
|
|
|
|
if(fd >= 0 || fallback_syslog) {
|
|
/* redirect standard input/output/error
|
|
* best effort attempt to make either the logfile or the syslog socket
|
|
* accessible through stdout/stderr, just in case any printfs survived
|
|
* the logmsg conversion.
|
|
*/
|
|
for(i=0; i<3; i++) {
|
|
close(i);
|
|
}
|
|
|
|
open("/dev/zero", O_RDONLY);
|
|
|
|
if(fd == -1) {
|
|
fd = start_syslog(SYSLOG_ID);
|
|
dup(1); /* not guaranteed to work */
|
|
} else {
|
|
dup(fd);
|
|
}
|
|
}
|
|
|
|
setvbuf(stdout, 0, _IOLBF, 0);
|
|
setvbuf(stderr, 0, _IONBF, 0);
|
|
}
|
|
|
|
static void daemonize(void)
|
|
{
|
|
int pid;
|
|
|
|
chdir("/");
|
|
|
|
redir_log(1);
|
|
|
|
/* release controlling terminal */
|
|
if((pid = fork()) == -1) {
|
|
perror("failed to fork");
|
|
exit(1);
|
|
} else if(pid) {
|
|
exit(0);
|
|
}
|
|
|
|
setsid();
|
|
}
|
|
|
|
static int write_pid_file(void)
|
|
{
|
|
FILE *fp;
|
|
int pid = getpid();
|
|
|
|
if(!(fp = fopen(PIDFILE, "w"))) {
|
|
return -1;
|
|
}
|
|
fprintf(fp, "%d\n", pid);
|
|
fclose(fp);
|
|
return 0;
|
|
}
|
|
|
|
static int find_running_daemon(void)
|
|
{
|
|
FILE *fp;
|
|
int s, pid;
|
|
struct sockaddr_un addr;
|
|
|
|
/* try to open the pid-file */
|
|
if(!(fp = fopen(PIDFILE, "r"))) {
|
|
return -1;
|
|
}
|
|
if(fscanf(fp, "%d\n", &pid) != 1) {
|
|
fclose(fp);
|
|
return -1;
|
|
}
|
|
fclose(fp);
|
|
|
|
/* make sure it's not just a stale pid-file */
|
|
if((s = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) {
|
|
return -1;
|
|
}
|
|
memset(&addr, 0, sizeof addr);
|
|
addr.sun_family = AF_UNIX;
|
|
strncpy(addr.sun_path, SOCK_NAME, sizeof addr.sun_path);
|
|
|
|
if(connect(s, (struct sockaddr*)&addr, sizeof addr) == -1) {
|
|
close(s);
|
|
return -1;
|
|
}
|
|
|
|
/* managed to connect alright, it's running... */
|
|
close(s);
|
|
return pid;
|
|
}
|
|
|
|
static void handle_events(fd_set *rset)
|
|
{
|
|
int dev_fd, hotplug_fd;
|
|
struct device *dev;
|
|
struct dev_input inp;
|
|
|
|
/* handle signal pipe */
|
|
if(FD_ISSET(pfd[0], rset)) {
|
|
int tmp;
|
|
read(pfd[0], &tmp, sizeof tmp); /* eat up the junk char */
|
|
|
|
read_cfg(cfgfile, &cfg);
|
|
cfg_changed();
|
|
}
|
|
|
|
/* handle anything coming through the UNIX socket */
|
|
handle_uevents(rset);
|
|
|
|
#ifdef USE_X11
|
|
/* handle any X11 events (magellan protocol) */
|
|
handle_xevents(rset);
|
|
#endif
|
|
|
|
/* finally read any pending device input data */
|
|
dev = get_devices();
|
|
while(dev) {
|
|
/* keep the next pointer because read_device can potentially destroy
|
|
* the device node if the read fails.
|
|
*/
|
|
struct device *next = dev->next;
|
|
|
|
if((dev_fd = get_device_fd(dev)) != -1 && FD_ISSET(dev_fd, rset)) {
|
|
/* read an event from the device ... */
|
|
while(read_device(dev, &inp) != -1) {
|
|
/* ... and process it, possibly dispatching a spacenav event to clients */
|
|
process_input(dev, &inp);
|
|
}
|
|
/* flush any pending events if we run out of input */
|
|
inp.type = INP_FLUSH;
|
|
process_input(dev, &inp);
|
|
}
|
|
dev = next;
|
|
}
|
|
|
|
if((hotplug_fd = get_hotplug_fd()) != -1) {
|
|
if(FD_ISSET(hotplug_fd, rset)) {
|
|
handle_hotplug();
|
|
}
|
|
}
|
|
}
|
|
|
|
void cfg_changed(void)
|
|
{
|
|
if(cfg.led != prev_cfg.led) {
|
|
struct device *dev = get_devices();
|
|
while(dev) {
|
|
if(is_device_valid(dev)) {
|
|
if(verbose) {
|
|
logmsg(LOG_INFO, "led %s, device: %s\n", cfg.led ? (cfg.led == LED_AUTO ? "auto" : "on"): "off", dev->name);
|
|
}
|
|
if(cfg.led == LED_ON || (cfg.led == LED_AUTO && first_client())) {
|
|
set_device_led(dev, 1);
|
|
} else {
|
|
set_device_led(dev, 0);
|
|
}
|
|
}
|
|
dev = dev->next;
|
|
}
|
|
}
|
|
|
|
if(strcmp(cfg.serial_dev, prev_cfg.serial_dev) != 0) {
|
|
struct device *dev, *iter = get_devices();
|
|
while(iter) {
|
|
dev = iter;
|
|
iter = iter->next;
|
|
if(strcmp(dev->path, prev_cfg.serial_dev) == 0) {
|
|
remove_device(dev);
|
|
}
|
|
}
|
|
init_devices_serial();
|
|
}
|
|
|
|
prev_cfg = cfg;
|
|
}
|
|
|
|
/* signals usr1 & usr2 are sent by the spnav_x11 script to start/stop the
|
|
* daemon's connection to the X server.
|
|
*/
|
|
static void sig_handler(int s)
|
|
{
|
|
switch(s) {
|
|
case SIGHUP:
|
|
write(pfd[1], &s, 1); /* write *something* to the pipe to trigger a re-read */
|
|
break;
|
|
|
|
case SIGSEGV:
|
|
logmsg(LOG_ERR, "Segmentation fault caught, trying to exit gracefully\n");
|
|
case SIGINT:
|
|
case SIGTERM:
|
|
exit(0);
|
|
|
|
#ifdef USE_X11
|
|
case SIGUSR1:
|
|
init_x11();
|
|
break;
|
|
|
|
case SIGUSR2:
|
|
close_x11();
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static char *fix_path(char *str)
|
|
{
|
|
char *buf, *tmp;
|
|
int sz, len;
|
|
|
|
if(str[0] == '/') return str;
|
|
|
|
len = strlen(str) + 1; /* +1 for the path separator */
|
|
sz = PATH_MAX;
|
|
|
|
if(!(buf = malloc(sz + len))) {
|
|
perror("failed to allocate path buffer");
|
|
return 0;
|
|
}
|
|
|
|
while(!getcwd(buf, sz)) {
|
|
if(errno == ERANGE) {
|
|
sz *= 2;
|
|
if(!(tmp = realloc(buf, sz + len))) {
|
|
perror("failed to reallocate path buffer");
|
|
free(buf);
|
|
return 0;
|
|
}
|
|
buf = tmp;
|
|
} else {
|
|
perror("getcwd failed");
|
|
free(buf);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
sprintf(buf + strlen(buf), "/%s", str);
|
|
return buf;
|
|
}
|