/*
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/>.
*/
#ifdef __linux__

#include "config.h"

#ifdef USE_X11
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/inotify.h>
#include "proto_x11.h"
#include "spnavd.h"

/* TODO implement fallback to polling if inotify is not available */

static int try_xconnect(void);

static int fd = -1;
static int watch_tmp = -1, watch_x11 = -1;

int xdet_start(void)
{
	if((fd = inotify_init()) == -1) {
		logmsg(LOG_ERR, "failed to create inotify queue: %s\n", strerror(errno));
		return -1;
	}
	fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);

	if((watch_x11 = inotify_add_watch(fd, "/tmp/.X11-unix", IN_CREATE)) == -1) {
		if((watch_tmp = inotify_add_watch(fd, "/tmp", IN_CREATE)) == -1) {
			logmsg(LOG_ERR, "failed to watch /tmp for file events: %s\n", strerror(errno));
			close(fd);
			fd = -1;
			return -1;
		}
	}

	if(verbose) {
		logmsg(LOG_INFO, "waiting for the X socket file to appear\n");
	}

	return fd;
}

/* this is called by init_x11 if it's successful */
void xdet_stop(void)
{
	if(fd != -1) {
		if(verbose) {
			logmsg(LOG_INFO, "stopping X watch\n");
		}

		close(fd);
		fd = watch_tmp = watch_x11 = -1;
	}
}

int xdet_get_fd(void)
{
	return fd;
}

int handle_xdet_events(fd_set *rset)
{
	char buf[512];
	struct inotify_event *ev = (struct inotify_event*)buf;
	ssize_t res;

	if(fd == -1 || !FD_ISSET(fd, rset)) {
		return -1;
	}

	for(;;) {
		if((res = read(fd, buf, sizeof buf)) <= 0) {
			if(res == 0) {
				/* kernels before 2.6.14 returned 0 for not enough space */
				errno = EINVAL;
			}
			if(errno == EINTR) continue;
			if(errno != EAGAIN) {
				logmsg(LOG_ERR, "failed to read inotify event: %s\n", strerror(errno));
			}
			return -1;
		}

		if(ev->wd == watch_tmp) {
			if(watch_x11 != -1) {
				inotify_rm_watch(fd, watch_tmp);
				continue;
			}

			if(ev->len > 0 && strcmp(ev->name, ".X11-unix") == 0) {
				if((watch_x11 = inotify_add_watch(fd, "/tmp/.X11-unix", IN_CREATE)) == -1) {
					logmsg(LOG_ERR, "failed to add /tmp/.X11-unix to the watch queue: %s\n", strerror(errno));
					continue;
				}
				if(try_xconnect() == 0) {
					return 0;
				}
			}

		} else if(ev->wd == watch_x11) {
			char *dpystr, sock_file[64];
			int dpynum = 0;

			if((dpystr = getenv("DISPLAY"))) {
				char *tmp = strchr(dpystr, ':');
				if(tmp && isdigit(tmp[1])) {
					dpynum = atoi(tmp + 1);
				}
			}
			sprintf(sock_file, "X%d", dpynum);

			if(ev->len > 0 && strcmp(ev->name, sock_file) == 0) {
				if(verbose) {
					logmsg(LOG_INFO, "found X socket, will now attempt to connect to the X server\n");
				}

				if(try_xconnect() == 0) {
					return 0;
				}
				logmsg(LOG_ERR, "found X socket yet failed to connect\n");
			}
		}
	}

	return -1;
}

static int try_xconnect(void)
{
	int i;
	/* poll for approximately 15 seconds (well a bit more than that) */
	for(i=0; i<15; i++) {
		sleep(1);
		if(init_x11() != -1) {
			return 0; /* success */
		}
	}
	return -1;
}

#endif	/* USE_X11 */

#else
int spacenavd_xdetect_linux_shut_up_empty_source_warning;
#endif	/* __linux__ */
