kopia lustrzana https://github.com/FreeSpacenav/spacenavd
				
				
				
			
		
			
				
	
	
		
			955 wiersze
		
	
	
		
			20 KiB
		
	
	
	
		
			C
		
	
	
			
		
		
	
	
			955 wiersze
		
	
	
		
			20 KiB
		
	
	
	
		
			C
		
	
	
| /*
 | |
| spacenavd - a free software replacement driver for 6dof space-mice.
 | |
| Copyright (C) 2007 John Tsiombikas <nuclear@siggraph.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 <stdio.h>
 | |
| #include <stdlib.h>
 | |
| #include <string.h>
 | |
| #include <ctype.h>
 | |
| #include <signal.h>
 | |
| #include <errno.h>
 | |
| #include <assert.h>
 | |
| #include <unistd.h>
 | |
| #include <fcntl.h>
 | |
| #include <sys/types.h>
 | |
| #include <sys/stat.h>
 | |
| #include <sys/socket.h>
 | |
| #include <sys/un.h>
 | |
| #include <sys/time.h>
 | |
| 
 | |
| #include <linux/types.h>
 | |
| #include <linux/input.h>
 | |
| 
 | |
| /* sometimes the rotation events are missing from linux/input.h */
 | |
| #ifndef REL_RX
 | |
| #define REL_RX	3
 | |
| #endif
 | |
| #ifndef REL_RY
 | |
| #define REL_RY	4
 | |
| #endif
 | |
| #ifndef REL_RZ
 | |
| #define REL_RZ	5
 | |
| #endif
 | |
| 
 | |
| 
 | |
| #ifdef USE_X11
 | |
| #include <X11/Xlib.h>
 | |
| #include <X11/Xutil.h>
 | |
| 
 | |
| enum cmd_msg {
 | |
| 	CMD_NONE,
 | |
| 	CMD_APP_WINDOW = 27695,	/* set client window */
 | |
| 	CMD_APP_SENS			/* set app sensitivity */
 | |
| };
 | |
| #endif
 | |
| 
 | |
| /* client types */
 | |
| enum {CLIENT_X11, CLIENT_UNIX};
 | |
| 
 | |
| struct client {
 | |
| 	int type;
 | |
| 
 | |
| 	int sock;		/* UNIX domain socket */
 | |
| #ifdef USE_X11
 | |
| 	Window win;		/* X11 client window */
 | |
| #endif
 | |
| 
 | |
| 	float sens;			/* sensitivity */
 | |
| 
 | |
| 	struct client *next;
 | |
| };
 | |
| 
 | |
| 
 | |
| void daemonize(void);
 | |
| int select_all(fd_set *rd_set);
 | |
| void handle_events(fd_set *rd_set);
 | |
| int add_client(int type, void *cdata);
 | |
| int init_dev(void);
 | |
| int init_unix(void);
 | |
| void send_uevent(struct input_event *inp);
 | |
| 
 | |
| #ifdef USE_X11
 | |
| int init_x11(void);
 | |
| void close_x11(void);
 | |
| void send_xevent(struct input_event *inp);
 | |
| int catch_badwin(Display *dpy, XErrorEvent *err);
 | |
| void set_client_window(Window win);
 | |
| void remove_client_window(Window win);
 | |
| #endif
 | |
| 
 | |
| char *get_dev_path(void);
 | |
| int open_dev(const char *path);
 | |
| void sig_handler(int s);
 | |
| 
 | |
| unsigned int msec_dif(struct timeval tv1, struct timeval tv2);
 | |
| 
 | |
| int read_cfg(const char *fname);
 | |
| 
 | |
| 
 | |
| int dev_fd;
 | |
| char dev_name[128];
 | |
| unsigned char evtype_mask[(EV_MAX + 7) / 8];
 | |
| #define TEST_BIT(b, ar)	(ar[b / 8] & (1 << (b % 8)))
 | |
| 
 | |
| int evrel[6];
 | |
| int evbut[24];
 | |
| 
 | |
| int lsock;
 | |
| 
 | |
| #ifdef USE_X11
 | |
| Display *dpy;
 | |
| Window win;
 | |
| Atom event_motion, event_bpress, event_brelease, event_cmd;
 | |
| 
 | |
| float x11_sens = 1.0;	/* XXX This stands in for the client sensitivity. Due
 | |
| 						 * to the bad design of the original magellan protocol,
 | |
| 						 * we can't know which client requested the sensitivity
 | |
| 						 * change, so we have to keep it global for all X
 | |
| 						 * clients.
 | |
| 						 */
 | |
| #endif
 | |
| 
 | |
| struct client *client_list;
 | |
| 
 | |
| /* global configuration options */
 | |
| float sensitivity = 1.0;
 | |
| int dead_threshold = 2;
 | |
| 
 | |
| 
 | |
| int main(int argc, char **argv)
 | |
| {
 | |
| 	int i, become_daemon = 1;
 | |
| 
 | |
| 	for(i=1; i<argc; i++) {
 | |
| 		if(argv[i][0] == '-' && argv[i][2] == 0) {
 | |
| 			switch(argv[i][1]) {
 | |
| 			case 'd':
 | |
| 				become_daemon = !become_daemon;
 | |
| 				break;
 | |
| 
 | |
| 			case 'h':
 | |
| 				printf("usage: %s [options]\n", argv[0]);
 | |
| 				printf("options:\n");
 | |
| 				printf("  -d\tdo not daemonize\n");
 | |
| 				printf("  -h\tprint this usage information\n");
 | |
| 				return 0;
 | |
| 
 | |
| 			default:
 | |
| 				fprintf(stderr, "unrecognized argument: %s\n", argv[i]);
 | |
| 				return 1;
 | |
| 			}
 | |
| 		} else {
 | |
| 			fprintf(stderr, "unexpected argument: %s\n", argv[i]);
 | |
| 			return 1;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if(become_daemon) {
 | |
| 		daemonize();
 | |
| 	}
 | |
| 
 | |
| 	read_cfg("/etc/spnavrc");
 | |
| 
 | |
| 	if(!(client_list = malloc(sizeof *client_list))) {
 | |
| 		perror("failed to allocate client list");
 | |
| 		return 1;
 | |
| 	}
 | |
| 	client_list->next = 0;
 | |
| 
 | |
| 	signal(SIGINT, sig_handler);
 | |
| 	signal(SIGTERM, sig_handler);
 | |
| 	signal(SIGHUP, sig_handler);
 | |
| 	signal(SIGUSR1, sig_handler);
 | |
| 	signal(SIGUSR2, sig_handler);
 | |
| 
 | |
| 	/* initialize the input device and the X11 connection (if available) */
 | |
| 	if(init_dev() == -1) {
 | |
| 		return 1;
 | |
| 	}
 | |
| 	init_unix();
 | |
| #ifdef USE_X11
 | |
| 	init_x11();
 | |
| #endif
 | |
| 
 | |
| 	/* event handling loop */
 | |
| 	while(1) {
 | |
| 		fd_set rd_set;
 | |
| 
 | |
| 		if(dev_fd == -1) {
 | |
| 			if(init_dev() == -1) {
 | |
| 				sleep(30);
 | |
| 				continue;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if(select_all(&rd_set) >= 0) {
 | |
| 			handle_events(&rd_set);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| #ifdef USE_X11
 | |
| 	close_x11();
 | |
| #endif
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| void daemonize(void)
 | |
| {
 | |
| 	int i, pid;
 | |
| 
 | |
| 	if((pid = fork()) == -1) {
 | |
| 		perror("failed to fork");
 | |
| 		exit(1);
 | |
| 	} else if(pid) {
 | |
| 		exit(0);
 | |
| 	}
 | |
| 
 | |
| 	setsid();
 | |
| 	chdir("/");
 | |
| 
 | |
| 	/* redirect standard input/output/error */
 | |
| 	for(i=0; i<3; i++) {
 | |
| 		close(i);
 | |
| 	}
 | |
| 
 | |
| 	open("/dev/zero", O_RDONLY);
 | |
| 	if(open("/tmp/spnav.log", O_WRONLY | O_CREAT | O_TRUNC, 0644) == -1) {
 | |
| 		open("/dev/null", O_WRONLY);
 | |
| 	}
 | |
| 	dup(1);
 | |
| 
 | |
| 	setvbuf(stdout, 0, _IOLBF, 0);
 | |
| 	setvbuf(stderr, 0, _IONBF, 0);
 | |
| }
 | |
| 
 | |
| int select_all(fd_set *rd_set)
 | |
| {
 | |
| 	int ret;
 | |
| 	int max_fd, xcon;
 | |
| 	struct client *cptr;
 | |
| 
 | |
| 	FD_ZERO(rd_set);
 | |
| 
 | |
| 	/* set the device file descriptors */
 | |
| 	FD_SET(dev_fd, rd_set);
 | |
| 	max_fd = dev_fd;
 | |
| 
 | |
| 	/* if we have a listening socket... */
 | |
| 	if(lsock) {
 | |
| 		/* ... set the listening socket itself for incoming connections */
 | |
| 		FD_SET(lsock, rd_set);
 | |
| 		if(lsock > max_fd) max_fd = lsock;
 | |
| 
 | |
| 		/* ... and set all the client's sockets too */
 | |
| 		cptr = client_list->next;
 | |
| 		while(cptr) {
 | |
| 			if(cptr->type == CLIENT_UNIX) {
 | |
| 				FD_SET(cptr->sock, rd_set);
 | |
| 
 | |
| 				if(cptr->sock > max_fd) {
 | |
| 					max_fd = cptr->sock;
 | |
| 				}
 | |
| 			}
 | |
| 			cptr = cptr->next;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/* also if we have an X11 connection, select that as well */
 | |
| #ifdef USE_X11
 | |
| 	if(dpy) {
 | |
| 		xcon = ConnectionNumber(dpy);
 | |
| 		FD_SET(xcon, rd_set);
 | |
| 		
 | |
| 		if(xcon > max_fd) {
 | |
| 			max_fd = xcon;
 | |
| 		}
 | |
| 	}
 | |
| #endif
 | |
| 
 | |
| 	/* wait indefinitely */
 | |
| 	do {
 | |
| 		ret = select(max_fd + 1, rd_set, 0, 0, 0);
 | |
| 	} while(ret == -1 && errno == EINTR);
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| void handle_events(fd_set *rd_set)
 | |
| {
 | |
| 	int xcon, rdbytes = 0;
 | |
| 	struct input_event inp;
 | |
| 	static struct input_event prev_inp;
 | |
| 	static int inp_events_pending;
 | |
| 	struct client *cptr;
 | |
| 
 | |
| 	if(lsock) {
 | |
| 		if(FD_ISSET(lsock, rd_set)) {
 | |
| 			/* got incoming connection */
 | |
| 			int s;
 | |
| 
 | |
| 			if((s = accept(lsock, 0, 0)) == -1) {
 | |
| 				perror("error while accepting connection");
 | |
| 			} else {
 | |
| 				if(add_client(CLIENT_UNIX, &s) == -1) {
 | |
| 					perror("failed to add client");
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		/* check all UNIX clients */
 | |
| 		cptr = client_list;
 | |
| 		while(cptr->next) {
 | |
| 			struct client *client = cptr->next;
 | |
| 
 | |
| 			if(client->type == CLIENT_UNIX) {
 | |
| 				if(FD_ISSET(client->sock, rd_set)) {
 | |
| 					/* Got data from a client. Currently the only thing the client may set
 | |
| 					 * is sensitivity. So get it directly.
 | |
| 					 */
 | |
| 					while((rdbytes = read(client->sock, &client->sens, sizeof client->sens)) <= 0 && errno == EINTR);
 | |
| 					if(rdbytes <= 0) {	/* something went wrong... disconnect client */
 | |
| 						cptr->next = client->next;
 | |
| 						close(client->sock);
 | |
| 						free(client);
 | |
| 						continue;
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 			cptr = cptr->next;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| #ifdef USE_X11
 | |
| 	xcon =  ConnectionNumber(dpy);
 | |
| 
 | |
| 	/* process any pending X events */
 | |
| 	if(dpy && FD_ISSET(xcon, rd_set)) {
 | |
| 		while(XPending(dpy)) {
 | |
| 			XEvent xev;
 | |
| 			XNextEvent(dpy, &xev);
 | |
| 
 | |
| 			if(xev.type == ClientMessage && xev.xclient.message_type == event_cmd) {
 | |
| 				unsigned int win_id;
 | |
| 
 | |
| 				switch(xev.xclient.data.s[2]) {
 | |
| 				case CMD_APP_WINDOW:
 | |
| 					win_id = xev.xclient.data.s[1];
 | |
| 					win_id |= (unsigned int)xev.xclient.data.s[0] << 16;
 | |
| 
 | |
| 					set_client_window((Window)win_id);
 | |
| 					break;
 | |
| 
 | |
| 				case CMD_APP_SENS:
 | |
| 					x11_sens = *(float*)xev.xclient.data.s;	/* see decl of x11_sens for details */
 | |
| 					break;
 | |
| 
 | |
| 				default:
 | |
| 					break;
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| #endif
 | |
| 
 | |
| 	/* read any pending data from the device */
 | |
| 	if(FD_ISSET(dev_fd, rd_set)) {
 | |
| 		do {
 | |
| 			rdbytes = read(dev_fd, &inp, sizeof inp);
 | |
| 		} while(rdbytes == -1 && errno == EINTR);
 | |
| 	}
 | |
| 
 | |
| 	/* disconnect? */
 | |
| 	if(rdbytes == -1) {
 | |
| 		perror("read error");
 | |
| 		close(dev_fd);
 | |
| 		dev_fd = -1;
 | |
| 	}
 | |
| 
 | |
| 	/* if we actually got an event, update our state, and send the appropriate events to all clients */
 | |
| 	if(rdbytes > 0) {
 | |
| 		int val;
 | |
| 		int idx, sign = -1;
 | |
| 
 | |
| 		switch(inp.type) {
 | |
| 		case EV_REL:
 | |
| 			if(abs(inp.value) < dead_threshold) {
 | |
| 				break;
 | |
| 			}
 | |
| 
 | |
| 			idx = inp.code - REL_X;
 | |
| 			val = inp.value;
 | |
| 			if(sensitivity != 1.0) {
 | |
| 				val = (int)((float)inp.value * sensitivity);
 | |
| 			}
 | |
| 
 | |
| 			switch(idx) {
 | |
| 			case REL_RY:
 | |
| 				idx = REL_RZ; break;
 | |
| 			case REL_RZ:
 | |
| 				idx = REL_RY; break;
 | |
| 			case REL_Y:
 | |
| 				idx = REL_Z; break;
 | |
| 			case REL_Z:
 | |
| 				idx = REL_Y; break;
 | |
| 			default:
 | |
| 				sign = 1;
 | |
| 				break;
 | |
| 			}
 | |
| 
 | |
| 			evrel[idx] = sign * val;
 | |
| 			prev_inp = inp;
 | |
| 			inp_events_pending = 1;
 | |
| 			break;
 | |
| 
 | |
| 		case EV_KEY:
 | |
| 			idx = inp.code - BTN_0;
 | |
| 			evbut[idx] = inp.value;
 | |
| 			prev_inp = inp;
 | |
| 			inp_events_pending = 1;
 | |
| 			break;
 | |
| 
 | |
| 		case EV_SYN:
 | |
| 			if(inp_events_pending) {
 | |
| 				inp_events_pending = 0;
 | |
| 
 | |
| 				send_uevent(&prev_inp);
 | |
| #ifdef USE_X11
 | |
| 				/* if we are connected to an X server, send the appropriate X event */
 | |
| 				send_xevent(&prev_inp);
 | |
| #endif/* USE_X11 */
 | |
| 			}
 | |
| 			break;
 | |
| 
 | |
| 		default:
 | |
| 			break;
 | |
| 		}
 | |
| 
 | |
| 	}
 | |
| 
 | |
| }
 | |
| 
 | |
| int add_client(int type, void *cdata)
 | |
| {
 | |
| 	struct client *client;
 | |
| 
 | |
| #ifdef USE_X11
 | |
| 	if(!cdata || (type != CLIENT_UNIX && type != CLIENT_X11)) 
 | |
| #else
 | |
| 	if(!cdata || type != CLIENT_UNIX) 
 | |
| #endif
 | |
| 	{
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	if(!(client = malloc(sizeof *client))) {
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	client->type = type;
 | |
| 	if(type == CLIENT_UNIX) {
 | |
| 		client->sock = *(int*)cdata;
 | |
| #ifdef USE_X11
 | |
| 	} else {
 | |
| 		client->win = *(Window*)cdata;
 | |
| #endif
 | |
| 	}
 | |
| 
 | |
| 	client->sens = 1.0f;
 | |
| 	client->next = client_list->next;
 | |
| 	client_list->next = client;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| int init_dev(void)
 | |
| {
 | |
| 	char *dev_path;
 | |
| 
 | |
| 	if(!(dev_path = get_dev_path())) {
 | |
| 		fprintf(stderr, "failed to find the spaceball device file\n");
 | |
| 		return -1;
 | |
| 	}
 | |
| 	printf("using device: %s\n", dev_path);
 | |
| 
 | |
| 	if(open_dev(dev_path) == -1) {
 | |
| 		return -1;
 | |
| 	}
 | |
| 	printf("device name: %s\n", dev_name);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| #define SOCK_NAME	"/tmp/.spnav.sock"
 | |
| int init_unix(void)
 | |
| {
 | |
| 	int s;
 | |
| 	mode_t prev_umask;
 | |
| 	struct sockaddr_un addr;
 | |
| 
 | |
| 	if(lsock) return 0;
 | |
| 
 | |
| 	if((s = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) {
 | |
| 		perror("failed to create socket");
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	unlink(SOCK_NAME);	/* in case it already exists */
 | |
| 
 | |
| 	memset(&addr, 0, sizeof addr);
 | |
| 	addr.sun_family = AF_UNIX;
 | |
| 	strcpy(addr.sun_path, SOCK_NAME);
 | |
| 
 | |
| 	prev_umask = umask(0);
 | |
| 
 | |
| 	if(bind(s, (struct sockaddr*)&addr, sizeof addr) == -1) {
 | |
| 		fprintf(stderr, "failed to bind unix socket: %s: %s\n", SOCK_NAME, strerror(errno));
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	umask(prev_umask);
 | |
| 
 | |
| 	if(listen(s, 8) == -1) {
 | |
| 		perror("listen failed");
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	lsock = s;
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| enum {
 | |
| 	UEV_TYPE_MOTION,
 | |
| 	UEV_TYPE_PRESS,
 | |
| 	UEV_TYPE_RELEASE
 | |
| };
 | |
| 
 | |
| /* send an event to all UNIX clients */
 | |
| void send_uevent(struct input_event *inp)
 | |
| {
 | |
| 	static struct timeval prev_motion_time;
 | |
| 	struct client *citer;
 | |
| 	int i, data[8];
 | |
| 	unsigned int period;
 | |
| 
 | |
| 	if(!lsock) return;
 | |
| 
 | |
| 	if(inp->type == EV_REL) {
 | |
| 		period = msec_dif(inp->time, prev_motion_time);
 | |
| 		prev_motion_time = inp->time;
 | |
| 	}
 | |
| 
 | |
| 	citer = client_list->next;
 | |
| 	while(citer) {
 | |
| 		if(citer->type == CLIENT_UNIX) {
 | |
| 			float motion_mul;
 | |
| 
 | |
| 			switch(inp->type) {
 | |
| 			case EV_REL:
 | |
| 				data[0] = UEV_TYPE_MOTION;
 | |
| 
 | |
| 				motion_mul = citer->sens * sensitivity;
 | |
| 				for(i=0; i<6; i++) {
 | |
| 					float val = (float)evrel[i] * motion_mul;
 | |
| 					data[i + 1] = (int)val;
 | |
| 				}
 | |
| 				data[7] = period;
 | |
| 				break;
 | |
| 
 | |
| 			case EV_KEY:
 | |
| 				data[0] = inp->value ? UEV_TYPE_PRESS : UEV_TYPE_RELEASE;
 | |
| 				data[1] = inp->code - BTN_0;
 | |
| 				break;
 | |
| 
 | |
| 			default:
 | |
| 				fprintf(stderr, "BUG! this shouldn't happen\n");
 | |
| 				exit(1);
 | |
| 			}
 | |
| 
 | |
| 			while(write(citer->sock, data, sizeof data) == -1 && errno == EINTR);
 | |
| 		}
 | |
| 		citer = citer->next;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| #ifdef USE_X11
 | |
| int init_x11(void)
 | |
| {
 | |
| 	int i, screen, scr_count;
 | |
| 	Window root;
 | |
| 	XSetWindowAttributes xattr;
 | |
| 	Atom wm_delete, cmd_type;
 | |
| 	XTextProperty tp_wname;
 | |
| 	XClassHint class_hint;
 | |
| 	char *win_title = "Magellan Window";
 | |
| 
 | |
| 	if(dpy) return 0;
 | |
| 
 | |
| 	if(!(dpy = XOpenDisplay(":0"))) {
 | |
| 		fprintf(stderr, "failed to open X11 display: \":0\"\n");
 | |
| 		return -1;
 | |
| 	}
 | |
| 	scr_count = ScreenCount(dpy);
 | |
| 	screen = DefaultScreen(dpy);
 | |
| 	root = RootWindow(dpy, screen);
 | |
| 
 | |
| 	/* intern the various atoms used for communicating with the magellan clients */
 | |
| 	event_motion = XInternAtom(dpy, "MotionEvent", False);
 | |
| 	event_bpress = XInternAtom(dpy, "ButtonPressEvent", False);
 | |
| 	event_brelease = XInternAtom(dpy, "ButtonReleaseEvent", False);
 | |
| 	event_cmd = XInternAtom(dpy, "CommandEvent", False);
 | |
| 
 | |
| 	/* Create a dummy window, so that clients are able to send us events
 | |
| 	 * through the magellan API. No need to map the window.
 | |
| 	 */
 | |
| 	xattr.background_pixel = xattr.border_pixel = BlackPixel(dpy, screen);
 | |
| 	xattr.colormap = DefaultColormap(dpy, screen);
 | |
| 
 | |
| 	win = XCreateWindow(dpy, root, 0, 0, 10, 10, 0, CopyFromParent, InputOutput,
 | |
| 			DefaultVisual(dpy, screen), CWColormap | CWBackPixel | CWBorderPixel, &xattr);
 | |
| 
 | |
| 	wm_delete = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
 | |
| 	XSetWMProtocols(dpy, win, &wm_delete, 1);
 | |
| 
 | |
| 	XStringListToTextProperty(&win_title, 1, &tp_wname);
 | |
| 	XSetWMName(dpy, win, &tp_wname);
 | |
| 	XFree(tp_wname.value);
 | |
| 
 | |
| 	class_hint.res_name = "magellan";
 | |
| 	class_hint.res_class = "magellan_win";
 | |
| 	XSetClassHint(dpy, win, &class_hint);
 | |
| 
 | |
| 	/* I believe this is a bit hackish, but the magellan API expects to find the CommandEvent
 | |
| 	 * property on the root window, containing our window id.
 | |
| 	 * The API doesn't look for a specific property type, so I made one up here (MagellanCmdType).
 | |
| 	 */
 | |
| 	cmd_type = XInternAtom(dpy, "MagellanCmdType", False);
 | |
| 	for(i=0; i<scr_count; i++) {
 | |
| 		Window root = RootWindow(dpy, i);
 | |
| 		XChangeProperty(dpy, root, event_cmd, cmd_type, 32, PropModeReplace, (unsigned char*)&win, 1);
 | |
| 	}
 | |
| 	XFlush(dpy);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| void close_x11(void)
 | |
| {
 | |
| 	int i, scr_count;
 | |
| 
 | |
| 	if(!dpy) return;
 | |
| 
 | |
| 	/* first delete all the CommandEvent properties from all root windows */
 | |
| 	scr_count = ScreenCount(dpy);
 | |
| 	for(i=0; i<scr_count; i++) {
 | |
| 		Window root = RootWindow(dpy, i);
 | |
| 		XDeleteProperty(dpy, root, event_cmd);
 | |
| 	}
 | |
| 
 | |
| 	XDestroyWindow(dpy, win);
 | |
| 	XCloseDisplay(dpy);
 | |
| 	dpy = 0;
 | |
| }
 | |
| 
 | |
| void send_xevent(struct input_event *inp)
 | |
| {
 | |
| 	static struct timeval prev_motion_time;
 | |
| 	struct client *citer;
 | |
| 	int (*prev_xerr_handler)(Display*, XErrorEvent*);
 | |
| 	int i;
 | |
| 	XEvent xevent;
 | |
| 	unsigned int period;
 | |
| 
 | |
| 	if(!dpy) return;
 | |
| 
 | |
| 	if(inp->type == EV_REL) {
 | |
| 		period = msec_dif(inp->time, prev_motion_time);
 | |
| 		prev_motion_time = inp->time;
 | |
| 	}
 | |
| 
 | |
| 	/* We can get a BadWindow exception, if any of the registered clients exit
 | |
| 	 * without notice. Thus we must install a custom handler to avoid crashing
 | |
| 	 * the daemon when that happens. Also catch_badwin (see below) removes that
 | |
| 	 * client from the list, to avoid perpetually trying to send events to an
 | |
| 	 * invalid window.
 | |
| 	 */
 | |
| 	prev_xerr_handler = XSetErrorHandler(catch_badwin);
 | |
| 
 | |
| 	xevent.type = ClientMessage;
 | |
| 	xevent.xclient.send_event = False;
 | |
| 	xevent.xclient.display = dpy;
 | |
| 
 | |
| 	citer = client_list->next;
 | |
| 	while(citer) {
 | |
| 		if(citer->type != CLIENT_X11) {
 | |
| 			citer = citer->next;
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		xevent.xclient.window = citer->win;
 | |
| 
 | |
| 		switch(inp->type) {
 | |
| 		case EV_REL:
 | |
| 			xevent.xclient.message_type = event_motion;
 | |
| 			xevent.xclient.format = 16;
 | |
| 
 | |
| 			for(i=0; i<6; i++) {
 | |
| 				float val = (float)evrel[i] * x11_sens * sensitivity;
 | |
| 				xevent.xclient.data.s[i + 2] = (short)val;
 | |
| 			}
 | |
| 			xevent.xclient.data.s[0] = xevent.xclient.data.s[1] = 0;
 | |
| 			xevent.xclient.data.s[8] = period;
 | |
| 			break;
 | |
| 
 | |
| 		case EV_KEY:
 | |
| 			xevent.xclient.message_type = inp->value ? event_bpress : event_brelease;
 | |
| 			xevent.xclient.format = 16;
 | |
| 			xevent.xclient.data.s[2] = inp->code - BTN_0;
 | |
| 			break;
 | |
| 
 | |
| 		default:
 | |
| 			fprintf(stderr, "BUG! this shouldn't happen\n");
 | |
| 			exit(1);
 | |
| 		}
 | |
| 
 | |
| 		XSendEvent(dpy, citer->win, False, 0, &xevent);
 | |
| 		citer = citer->next;
 | |
| 	}
 | |
| 
 | |
| 	XSync(dpy, False);
 | |
| 	XSetErrorHandler(prev_xerr_handler);
 | |
| }
 | |
| 
 | |
| /* X11 error handler for bad-windows */
 | |
| int catch_badwin(Display *dpy, XErrorEvent *err)
 | |
| {
 | |
| 	char buf[256];
 | |
| 
 | |
| 	if(err->error_code == BadWindow) {
 | |
| 		remove_client_window((Window)err->resourceid);
 | |
| 	} else {
 | |
| 		XGetErrorText(dpy, err->error_code, buf, sizeof buf);
 | |
| 		fprintf(stderr, "Caught unexpected X error: %s\n", buf);
 | |
| 	}
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| /* adds a new X11 client to the list, IF it does not already exist */
 | |
| void set_client_window(Window win)
 | |
| {
 | |
| 	int i, scr_count;
 | |
| 	struct client *cnode;
 | |
| 
 | |
| 	/* When a magellan application exits, the SDK sets another window to avoid
 | |
| 	 * crashing the original proprietary daemon.  The new free SDK will set
 | |
| 	 * consistently the root window for that purpose, which we can ignore here
 | |
| 	 * easily.
 | |
| 	 */
 | |
| 	scr_count = ScreenCount(dpy);
 | |
| 	for(i=0; i<scr_count; i++) {
 | |
| 		if(win == RootWindow(dpy, i)) {
 | |
| 			return;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	cnode = client_list->next;
 | |
| 	while(cnode) {
 | |
| 		if(cnode->win == win) {
 | |
| 			return;
 | |
| 		}
 | |
| 		cnode = cnode->next;
 | |
| 	}
 | |
| 
 | |
| 	add_client(CLIENT_X11, &win);
 | |
| }
 | |
| 
 | |
| void remove_client_window(Window win)
 | |
| {
 | |
| 	struct client *cnode, *tmp;
 | |
| 
 | |
| 	cnode = client_list;
 | |
| 	while(cnode->next) {
 | |
| 		if(cnode->next->win == win) {
 | |
| 			tmp = cnode->next;
 | |
| 			cnode->next = tmp->next;
 | |
| 			free(tmp);
 | |
| 			return;
 | |
| 		}
 | |
| 		cnode = cnode->next;
 | |
| 	}
 | |
| }
 | |
| #endif
 | |
| 
 | |
| int open_dev(const char *path)
 | |
| {
 | |
| 	if((dev_fd = open(path, O_RDWR)) == -1) {
 | |
| 		if((dev_fd = open(path, O_RDONLY)) == -1) {
 | |
| 			perror("failed to open device");
 | |
| 			return -1;
 | |
| 		}
 | |
| 		fprintf(stderr, "opened device read-only, LEDs won't work\n");
 | |
| 	}
 | |
| 
 | |
| 	if(ioctl(dev_fd, EVIOCGNAME(sizeof(dev_name)), dev_name) == -1) {
 | |
| 		perror("EVIOCGNAME ioctl failed\n");
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	if(ioctl(dev_fd, EVIOCGBIT(0, sizeof(evtype_mask)), evtype_mask) == -1) {
 | |
| 		perror("EVIOCGBIT ioctl failed\n");
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	if(!TEST_BIT(EV_REL, evtype_mask)) {
 | |
| 		fprintf(stderr, "Wrong device, no relative events reported!\n");
 | |
| 		return -1;
 | |
| 	}
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| #define PROC_DEV	"/proc/bus/input/devices"
 | |
| char *get_dev_path(void)
 | |
| {
 | |
| 	static char path[128];
 | |
| 	int valid_vendor = 0, valid_str = 0;
 | |
| 	char buf[1024];
 | |
| 	FILE *fp;
 | |
| 
 | |
| 	if(!(fp = fopen(PROC_DEV, "r"))) {
 | |
| 		perror("failed to open " PROC_DEV ":");
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	while(fgets(buf, sizeof buf, fp)) {
 | |
| 		if(buf[0] == 'I') {
 | |
| 			valid_vendor = strstr(buf, "Vendor=046d") != 0;
 | |
| 		} else if(buf[0] == 'N') {
 | |
| 			valid_str = strstr(buf, "3Dconnexion") != 0;
 | |
| 		} else if(buf[0] == 'H' && valid_str && valid_vendor) {
 | |
| 			char *ptr, *start;
 | |
| 			
 | |
| 			if(!(start = strchr(buf, '='))) {
 | |
| 				continue;
 | |
| 			}
 | |
| 			start++;
 | |
| 
 | |
| 			if((ptr = strchr(start, ' '))) {
 | |
| 				*ptr = 0;
 | |
| 			}
 | |
| 			if((ptr = strchr(start, '\n'))) {
 | |
| 				*ptr = 0;
 | |
| 			}
 | |
| 
 | |
| 			snprintf(path, sizeof(path), "/dev/input/%s", start);
 | |
| 			fclose(fp);
 | |
| 			return path;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	fclose(fp);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| /* signals usr1 & usr2 are sent by the spnav_x11 script to start/stop the
 | |
|  * daemon's connection to the X server.
 | |
|  */
 | |
| void sig_handler(int s)
 | |
| {
 | |
| 	switch(s) {
 | |
| 	case SIGHUP:
 | |
| 		init_dev();
 | |
| 		break;
 | |
| 
 | |
| 	case SIGINT:
 | |
| 	case SIGTERM:
 | |
| 		close_x11();	/* call to avoid leaving garbage in the X server's root windows */
 | |
| 		exit(0);
 | |
| 
 | |
| #ifdef USE_X11
 | |
| 	case SIGUSR1:
 | |
| 		init_x11();
 | |
| 		break;
 | |
| 
 | |
| 	case SIGUSR2:
 | |
| 		close_x11();
 | |
| 		break;
 | |
| #endif
 | |
| 
 | |
| 	default:
 | |
| 		break;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| unsigned int msec_dif(struct timeval tv1, struct timeval tv2)
 | |
| {
 | |
| 	unsigned int ds, du;
 | |
| 
 | |
| 	ds = tv2.tv_sec - tv1.tv_sec;
 | |
| 	du = tv2.tv_usec - tv1.tv_usec;
 | |
| 	return ds * 1000 + du / 1000;
 | |
| }
 | |
| 
 | |
| int read_cfg(const char *fname)
 | |
| {
 | |
| 	FILE *fp;
 | |
| 	char buf[512];
 | |
| 
 | |
| 	if(!(fp = fopen(fname, "r"))) {
 | |
| 		fprintf(stderr, "failed to open config file %s: %s. using defaults.\n", fname, strerror(errno));
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	while(fgets(buf, sizeof buf, fp)) {
 | |
| 		char *key_str, *val_str, *line = buf;
 | |
| 		while(*line == ' ' || *line == '\t') line++;
 | |
| 
 | |
| 		if(!*line || *line == '\n' || *line == '\r' || *line == '#') {
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		if(!(key_str = strtok(line, " :=\n\t\r"))) {
 | |
| 			fprintf(stderr, "invalid config line: %s, skipping.\n", line);
 | |
| 			continue;
 | |
| 		}
 | |
| 		if(!(val_str = strtok(0, " :=\n\t\r"))) {
 | |
| 			fprintf(stderr, "missing value for config key: %s\n", key_str);
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		if(!isdigit(val_str[0])) {
 | |
| 			fprintf(stderr, "invalid value (%s), for key: %s. expected a number.\n", val_str, key_str);
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		if(strcmp(key_str, "dead-zone") == 0) {
 | |
| 			dead_threshold = atoi(val_str);
 | |
| 			printf("config: dead-zone = %d\n", dead_threshold);
 | |
| 		} else if(strcmp(key_str, "sensitivity") == 0) {
 | |
| 			sensitivity = atof(val_str);
 | |
| 			printf("config: sensitivity = %.3f\n", sensitivity);
 | |
| 		} else {
 | |
| 			fprintf(stderr, "unrecognized config option: %s\n", key_str);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	fclose(fp);
 | |
| 	return 0;
 | |
| }
 |