/*
spacenavd - a free software replacement driver for 6dof space-mice.
Copyright (C) 2007-2025 John Tsiombikas <nuclear@mutantstargoat.com>

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 <math.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include "proto.h"
#include "proto_unix.h"
#include "spnavd.h"
#ifdef USE_X11
#include "kbemu.h"
#endif

#ifndef isfinite
#define isfinite(x)	(!isnan(x))
#endif

static int lsock = -1;


static int handle_request(struct client *c, struct reqresp *req);
static const char *reqstr(int req);

int init_unix(void)
{
	int s;
	mode_t prev_umask;
	struct sockaddr_un addr;

	if(lsock >= 0) return 0;

	if((s = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) {
		logmsg(LOG_ERR, "failed to create socket: %s\n", strerror(errno));
		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) {
		logmsg(LOG_ERR, "failed to bind unix socket: %s: %s\n", SOCK_NAME, strerror(errno));
		close(s);
		return -1;
	}

	umask(prev_umask);

	if(listen(s, 8) == -1) {
		logmsg(LOG_ERR, "listen failed: %s\n", strerror(errno));
		close(s);
		unlink(SOCK_NAME);
		return -1;
	}

	lsock = s;
	return 0;
}

void close_unix(void)
{
	if(lsock != -1) {
		close(lsock);
		lsock = -1;

		unlink(SOCK_NAME);
	}
}

int get_unix_socket(void)
{
	return lsock;
}

void send_uevent(spnav_event *ev, struct client *c)
{
	int i;
	int32_t data[8] = {0};
	float motion_mul;

	if(lsock == -1) return;

	switch(ev->type) {
	case EVENT_MOTION:
		if(!(c->evmask & EVMASK_MOTION)) return;

		data[0] = UEV_MOTION;

		motion_mul = get_client_sensitivity(c);
		for(i=0; i<6; i++) {
			float val = (float)ev->motion.data[i] * motion_mul;
			data[i + 1] = (int32_t)val;
		}
		data[7] = ev->motion.period;
		break;

	case EVENT_RAWAXIS:
		if(!(c->evmask & EVMASK_RAWAXIS)) return;

		data[0] = UEV_RAWAXIS;
		data[1] = ev->axis.idx;
		data[2] = ev->axis.value;
		break;

	case EVENT_BUTTON:
		if(!(c->evmask & EVMASK_BUTTON)) return;

		data[0] = ev->button.press ? UEV_PRESS : UEV_RELEASE;
		data[1] = ev->button.bnum;
		data[2] = ev->button.press;
		break;

	case EVENT_RAWBUTTON:
		if(!(c->evmask & EVMASK_RAWBUTTON)) return;

		data[0] = UEV_RAWBUTTON;
		data[1] = ev->button.bnum;
		data[2] = ev->button.press;
		break;

	case EVENT_DEV:
		if(!(c->evmask & EVMASK_DEV)) return;

		data[0] = UEV_DEV;
		data[1] = ev->dev.op;
		data[2] = ev->dev.id;
		data[3] = ev->dev.devtype;
		data[4] = ev->dev.usbid[0];
		data[5] = ev->dev.usbid[1];
		break;

	case EVENT_CFG:
		if(!(c->evmask & EVMASK_CFG)) return;

		data[0] = UEV_CFG;
		data[1] = ev->cfg.cfg;
		memcpy(data + 2, ev->cfg.data, sizeof ev->cfg.data);
		break;

	default:
		return;
	}

	while(write(get_client_socket(c), data, sizeof data) == -1 && errno == EINTR);
}

int handle_uevents(fd_set *rset)
{
	struct client *citer;
	struct reqresp *req;

	if(lsock == -1) {
		return -1;
	}

	if(FD_ISSET(lsock, rset)) {
		/* got an incoming connection */
		int s;

		if((s = accept(lsock, 0, 0)) == -1) {
			logmsg(LOG_ERR, "error while accepting connection on the UNIX socket: %s\n", strerror(errno));
		} else {
			/* set socket as non-blocking and add client to the list */
			fcntl(s, F_SETFL, fcntl(s, F_GETFL) | O_NONBLOCK);

			if(!add_client(CLIENT_UNIX, &s)) {
				logmsg(LOG_ERR, "failed to add client: %s\n", strerror(errno));
			}
		}
	}

	/* all the UNIX socket clients */
	citer = first_client();
	while(citer) {
		struct client *c = citer;
		citer = next_client();

		if(get_client_type(c) == CLIENT_UNIX) {
			int s = get_client_socket(c);

			if(FD_ISSET(s, rset)) {
				int rdbytes;
				int32_t msg;
				float sens;

				/* handle client requests */
				switch(c->proto) {
				case 0:
					while((rdbytes = read(s, &msg, sizeof msg)) < 0 && errno == EINTR);
					if(rdbytes <= 0) {	/* something went wrong... disconnect client */
						close(get_client_socket(c));
						remove_client(c);
						continue;
					}

					/* handle magic NaN protocol change requests */
					if((msg & 0xffffff00) == (REQ_TAG | REQ_CHANGE_PROTO)) {
						c->proto = msg & 0xff;

						/* if the client requests a protocol version higher than the
						 * daemon supports, return the maximum supported version and
						 * switch to that.
						 */
						if(c->proto > MAX_PROTO_VER) {
							c->proto = MAX_PROTO_VER;
							msg = REQ_TAG | REQ_CHANGE_PROTO | MAX_PROTO_VER;
						}
						write(s, &msg, sizeof msg);

						if(c->proto > 0) {
							/* set default event mask for proto-v1 clients */
							c->evmask = EVMASK_MOTION | EVMASK_BUTTON | EVMASK_DEV;
						}
						continue;
					}

					/* protocol v0: only sensitivity comes from clients */
					sens = *(float*)&msg;
					if(isfinite(sens)) {
						set_client_sensitivity(c, sens);
					}
					break;

				case 1:
					/* protocol v1: accumulate request bytes, and process */
					while((rdbytes = read(s, c->reqbuf + c->reqbytes, sizeof *req - c->reqbytes)) < 0 && errno == EINTR);
					if(rdbytes <= 0) {
						close(s);
						remove_client(c);
						continue;
					}
					c->reqbytes += rdbytes;
					if(c->reqbytes >= sizeof *req) {
						req = (struct reqresp*)c->reqbuf;
						c->reqbytes = 0;
						if(handle_request(c, req) == -1) {
							close(s);
							remove_client(c);
						}
					}
					break;
				}
			}
		}
	}

	return 0;
}

static int sendresp(struct client *c, struct reqresp *rr, int status)
{
	rr->data[6] = status;
	return write(get_client_socket(c), rr, sizeof *rr);
}

#define AXIS_VALID(x)	((x) >= 0 && (x) < MAX_AXES)
#define BN_VALID(x)		((x) >= 0 && (x) < MAX_BUTTONS)
#define BNACT_VALID(x)	((x) >= 0 && (x) < MAX_BNACT)

static int handle_request(struct client *c, struct reqresp *req)
{
	int i, idx, res;
	float fval, fvec[6];
	struct device *dev;
	const char *str = 0;

	logmsg(LOG_DEBUG, "request %s - %x %x %x %x %x %x\n", reqstr(req->type), req->data[0],
			req->data[1], req->data[2], req->data[3], req->data[4], req->data[5], req->data[6]);

	switch(req->type & 0xffff) {
	case REQ_SET_NAME:
		if((res = spnav_recv_str(&c->strbuf, req)) == -1) {
			logmsg(LOG_ERR, "SET_NAME: failed to receive string\n");
			break;
		}
		if(res) {
			c->name = c->strbuf.buf;
			c->strbuf.buf = 0;
			logmsg(LOG_INFO, "client name: %s\n", c->name);
		}
		break;

	case REQ_SET_SENS:
		fval = *(float*)req->data;
		if(isfinite(fval)) {
			set_client_sensitivity(c, fval);
			sendresp(c, req, 0);
		} else {
			logmsg(LOG_WARNING, "client attempted to set invalid client sensitivity\n");
			sendresp(c, req, -1);
		}
		break;

	case REQ_GET_SENS:
		fval = get_client_sensitivity(c);
		req->data[0] = *(int*)&fval;
		sendresp(c, req, 0);
		break;

	case REQ_SET_EVMASK:
		c->evmask = req->data[0];
		sendresp(c, req, 0);
		break;

	case REQ_GET_EVMASK:
		req->data[0] = c->evmask;
		sendresp(c, req, 0);
		break;

	case REQ_DEV_NAME:
		if((dev = get_client_device(c))) {
			spnav_send_str(get_client_socket(c), req->type, dev->name);
		} else {
			sendresp(c, req, -1);
		}
		break;

	case REQ_DEV_PATH:
		if((dev = get_client_device(c))) {
			spnav_send_str(get_client_socket(c), req->type, dev->path);
		} else {
			sendresp(c, req, -1);
		}
		break;

	case REQ_DEV_NAXES:
		if((dev = get_client_device(c))) {
			req->data[0] = dev->num_axes;
			sendresp(c, req, 0);
		} else {
			sendresp(c, req, -1);
		}
		break;

	case REQ_DEV_NBUTTONS:
		if((dev = get_client_device(c))) {
			req->data[0] = dev->num_buttons;
			sendresp(c, req, 0);
		} else {
			sendresp(c, req, -1);
		}
		break;

	case REQ_DEV_USBID:
		if((dev = get_client_device(c)) && dev->usbid[0] && dev->usbid[1]) {
			req->data[0] = dev->usbid[0];
			req->data[1] = dev->usbid[1];
			sendresp(c, req, 0);
		} else {
			sendresp(c, req, -1);
		}
		break;

	case REQ_DEV_TYPE:
		if((dev = get_client_device(c))) {
			req->data[0] = dev->type;
			sendresp(c, req, 0);
		} else {
			sendresp(c, req, -1);
		}
		break;

	case REQ_SCFG_SENS:
		fval = *(float*)req->data;
		if(isfinite(fval)) {
			cfg.sensitivity = fval;
			sendresp(c, req, 0);
		} else {
			logmsg(LOG_WARNING, "client attempted to set invalid global sensitivity\n");
			sendresp(c, req, -1);
		}
		break;

	case REQ_GCFG_SENS:
		req->data[0] = *(int*)&cfg.sensitivity;
		sendresp(c, req, 0);
		break;

	case REQ_SCFG_SENS_AXIS:
		for(i=0; i<6; i++) {
			fvec[i] = ((float*)req->data)[i];
			if(!isfinite(fvec[i])) {
				logmsg(LOG_WARNING, "client attempted to set invalid axis %d sensitivity\n", i);
				sendresp(c, req, -1);
				return 0;
			}
		}
		for(i=0; i<3; i++) {
			cfg.sens_trans[i] = fvec[i];
			cfg.sens_rot[i] = fvec[i + 3];
		}
		sendresp(c, req, 0);
		break;

	case REQ_GCFG_SENS_AXIS:
		for(i=0; i<3; i++) {
			req->data[i] = *(int*)(cfg.sens_trans + i);
			req->data[i + 3] = *(int*)(cfg.sens_rot + i);
		}
		sendresp(c, req, 0);
		break;

	case REQ_SCFG_DEADZONE:
		if(!AXIS_VALID(req->data[0])) {
			logmsg(LOG_WARNING, "client attempted to set invalid axis deadzone: %d\n", req->data[0]);
			sendresp(c, req, -1);
			return 0;
		}
		cfg.dead_threshold[req->data[0]] = req->data[1];
		sendresp(c, req, 0);
		break;

	case REQ_GCFG_DEADZONE:
		if(!AXIS_VALID(req->data[0])) {
			logmsg(LOG_WARNING, "client requested invalid axis deadzone: %d\n", req->data[0]);
			sendresp(c, req, -1);
			return 0;
		}
		req->data[1] = cfg.dead_threshold[req->data[0]];
		sendresp(c, req, 0);
		break;

	case REQ_SCFG_INVERT:
		for(i=0; i<6; i++) {
			cfg.invert[i] = req->data[i] ? 1 : 0;
		}
		sendresp(c, req, 0);
		break;

	case REQ_GCFG_INVERT:
		memcpy(req->data, cfg.invert, 6 * sizeof(int));
		sendresp(c, req, 0);
		break;

	case REQ_SCFG_AXISMAP:
		if(!AXIS_VALID(req->data[0]) || req->data[1] < -1 || req->data[1] >= 6) {
			logmsg(LOG_WARNING, "client attempted to set invalid axis mapping: %d -> %d\n",
					req->data[0], req->data[1]);
			sendresp(c, req, -1);
			return 0;
		}
		cfg.map_axis[req->data[0]] = req->data[1];
		sendresp(c, req, 0);
		break;

	case REQ_GCFG_AXISMAP:
		if(!AXIS_VALID(req->data[0])) {
			logmsg(LOG_WARNING, "client queried mapping of invalid axis: %d\n",
					req->data[0]);
			sendresp(c, req, -1);
			return 0;
		}
		req->data[1] = cfg.map_axis[req->data[0]];
		sendresp(c, req, 0);
		break;

	case REQ_SCFG_BNMAP:
		if(!BN_VALID(req->data[0]) || !BN_VALID(req->data[1])) {
			logmsg(LOG_WARNING, "client attempted to set invalid button mapping: %d -> %d\n",
					req->data[0], req->data[1]);
			sendresp(c, req, -1);
			return 0;
		}
		cfg.map_button[req->data[0]] = req->data[1];
		sendresp(c, req, 0);
		break;

	case REQ_GCFG_BNMAP:
		if(!BN_VALID(req->data[0])) {
			logmsg(LOG_WARNING, "client queried mapping of invalid button: %d\n", req->data[0]);
			sendresp(c, req, -1);
			return 0;
		}
		req->data[1] = cfg.map_button[req->data[0]];
		sendresp(c, req, 0);
		break;

	case REQ_SCFG_BNACTION:
		if(!BN_VALID(req->data[0]) || !BNACT_VALID(req->data[1])) {
			logmsg(LOG_WARNING, "client attempted to set invalid button action: %d -> %d\n",
					req->data[0], req->data[1]);
			sendresp(c, req, -1);
			return 0;
		}
		cfg.bnact[req->data[0]] = req->data[1];
		sendresp(c, req, 0);
		break;

	case REQ_GCFG_BNACTION:
		if(!BN_VALID(req->data[0])) {
			logmsg(LOG_WARNING, "client queried action bound to invalid button: %d\n", req->data[0]);
			sendresp(c, req, -1);
			return 0;
		}
		req->data[1] = cfg.bnact[req->data[0]];
		sendresp(c, req, 0);
		break;

	case REQ_SCFG_KBMAP:
#ifdef USE_X11
		idx = req->data[0];
		if(!BN_VALID(idx) || (req->data[1] > 0 && !(str = kbemu_keyname(req->data[1])))) {
			logmsg(LOG_WARNING, "client attempted to set invalid key map: %d -> %x\n",
					idx, (unsigned int)req->data[1]);
			sendresp(c, req, -1);
			return 0;
		}
		cfg.kbmap[idx] = req->data[1];
		free(cfg.kbmap_str[idx]);
		cfg.kbmap_str[idx] = req->data[1] > 0 ? strdup(str) : 0;
		sendresp(c, req, 0);
#else
		logmsg(LOG_WARNING, "unable to set keyboard mappings, daemon compiled without X11 support\n");
		sendresp(c, req, -1);
#endif
		break;

	case REQ_GCFG_KBMAP:
#ifdef USE_X11
		idx = req->data[0];
		if(!BN_VALID(idx)) {
			logmsg(LOG_WARNING, "client queried keyboard mapping for invalid button: %d\n", idx);
			sendresp(c, req, -1);
			return 0;
		}
		if(cfg.kbmap_str[idx]) {
			if(!cfg.kbmap[idx]) {
				cfg.kbmap[idx] = kbemu_keysym(cfg.kbmap_str[idx]);
			}
			req->data[1] = cfg.kbmap[idx];
		} else {
			req->data[1] = 0;
		}
		sendresp(c, req, 0);
#else
		logmsg(LOG_WARNING, "unable to query keyboard mappings, daemon compiled without X11 support\n");
		sendresp(c, req, -1);
#endif
		break;

	case REQ_SCFG_SWAPYZ:
		cfg.swapyz = req->data[0] ? 1 : 0;
		sendresp(c, req, 0);

	case REQ_GCFG_SWAPYZ:
		req->data[0] = cfg.swapyz;
		sendresp(c, req, 0);
		break;

	case REQ_SCFG_LED:
		if(req->data[0] < 0 || req->data[0] >= 3) {
			sendresp(c, req, -1);
			break;
		}
		cfg.led = req->data[0];
		cfg_changed();
		sendresp(c, req, 0);
		break;

	case REQ_GCFG_LED:
		req->data[0] = cfg.led;
		sendresp(c, req, 0);
		break;

	case REQ_SCFG_GRAB:
		cfg.grab_device = req->data[0] ? 1 : 0;
		sendresp(c, req, 0);
		break;

	case REQ_GCFG_GRAB:
		req->data[0] = cfg.grab_device;
		sendresp(c, req, 0);
		break;

	case REQ_SCFG_SERDEV:
		if((res = spnav_recv_str(&c->strbuf, req)) == -1) {
			logmsg(LOG_ERR, "SCFG_SERDEV: failed to receive string\n");
			break;
		}
		if(res) {
			strncpy(cfg.serial_dev, c->strbuf.buf, sizeof cfg.serial_dev - 1);
			cfg.serial_dev[sizeof cfg.serial_dev - 1] = 0;
			cfg_changed();
		}
		break;

	case REQ_GCFG_SERDEV:
		spnav_send_str(c->sock, req->type, cfg.serial_dev);
		break;

	case REQ_SCFG_REPEAT:
		cfg.repeat_msec = req->data[0];
		sendresp(c, req, 0);
		break;

	case REQ_GCFG_REPEAT:
		req->data[0] = cfg.repeat_msec;
		sendresp(c, req, 0);
		break;

	case REQ_CFG_SAVE:
		sendresp(c, req, write_cfg(cfgfile, &cfg));
		break;

	case REQ_CFG_RESTORE:
		if(read_cfg(cfgfile, &cfg) == -1) {
			logmsg(LOG_INFO, "config restore requested but failed to read %s, restoring defaults instead\n",
					cfgfile);
			default_cfg(&cfg);
		}
		cfg_changed();
		sendresp(c, req, 0);
		break;

	case REQ_CFG_RESET:
		default_cfg(&cfg);
		cfg_changed();
		sendresp(c, req, 0);
		break;

	default:
		logmsg(LOG_WARNING, "invalid client request: %s\n", reqstr(req->type));
		sendresp(c, req, -1);
	}

	return 0;
}

static const char *reqstr(int req)
{
	static char buf[8];

	req &= 0xffff;

	if(req >= 0x1000 && req < 0x1000 + spnav_reqnames_1000_size) {
		return spnav_reqnames_1000[req - 0x1000];
	}
	if(req >= 0x2000 && req < 0x2000 + spnav_reqnames_2000_size) {
		return spnav_reqnames_2000[req - 0x2000];
	}
	if(req >= 0x3000 && req < 0x3000 + spnav_reqnames_3000_size) {
		return spnav_reqnames_3000[req - 0x3000];
	}
	switch(req) {
	case REQ_CFG_SAVE:
		return "CFG_SAVE";
	case REQ_CFG_RESTORE:
		return "CFG_RESTORE";
	case REQ_CFG_RESET:
		return "CFG_RESET";
	default:
		break;
	}

	sprintf(buf, "0x%04x", req);
	return buf;
}
