aboutsummaryrefslogblamecommitdiffstats
path: root/src/mds-libinput.c
blob: 76bfb5e1640f6a0395206db7bd43a4b9ad48bc4e (plain) (tree)
1
2
3

                                 
                                                                           














                                                                        




                                     

                        
                     



                   



                                 
                                   








                                                                     







                                                   

















                                                            












                                  
                                



                             
                                               























                                                      
                                                   



                                     
                                     

   
                                 
   




                                        
                                     





































                                           
 








                                                             

                                                 


   



                               

                   
 
























                                                                                                          
         










                                                                                    



   




                                                                                

                          
 
                 








                                              

                       
 









                                                             








                                                                               

                           
 














                                                                










                                                                        

                         
 


                                                       








                                                            

                               
 






                                                                












                                                                                         

                                 
 










                                                                      



   




                                                                    

                            
 
                  







                                                         

                     
 
                                                  
  





                                                              









                                                   

                          
 







                                                  



   



                               

                 
 

                                  
  
































                                                                               
         















                                                               



   
                                            
   

                             
   
                            
 


















                                                                                
         







                           



   
                                
   
                                        
   

                  
 













                                                      







                                        

                    
 

                  










                                                               

                                                            
 




                                             








                                                      

                                        
 

                        







                                        

                         
 















                                                                                        





                                  

                        
 






                                                                     








                                             

                                       
 











                                                                                       







                                       

                                          
 











                                                   





                       

                  
 

                                     
  













                                                                                   



   





                                                    

                        
 



                         





                              

               
 










                                                                               
 
/**
 * mds — A micro-display server
 * Copyright © 2014, 2015, 2016  Mattias Andrée (maandree@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 "mds-libinput.h"

#include <libmdsserver/macros.h>
#include <libmdsserver/util.h>
#include <libmdsserver/mds-message.h>

#include <linux/input.h>
#include <sys/select.h>
#include <inttypes.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

#define reconnect_to_display() -1



#define MDS_LIBINPUT_VARS_VERSION 0



/**
 * This variable should declared by the actual server implementation.
 * It must be configured before `main` is invoked.
 * 
 * This tells the server-base how to behave
 */
server_characteristics_t server_characteristics = {
	.require_privileges = 1,
	.require_display = 1,
	.require_respawn_info = 0,
	.sanity_check_argc = 1,
	.fork_for_safety = 0,
	.danger_is_deadly = 0
};



/**
 * Value of the ‘Message ID’ header for the next message
 */
static uint32_t message_id = 1;

/**
 * Buffer for received messages
 */
static mds_message_t received;

/**
 * Whether the server is connected to the display
 */
static int connected = 1;

/**
 * The seat for libinput
 */
static const char* seat = "seat0";

/**
 * libinput context
 */
static struct libinput* li = NULL;

/**
 * udev context
 */
static struct udev *udev = NULL;

/**
 * List of all opened devices
 */
static struct libinput_device **devices = NULL;

/**
 * The number of element slots allocated for `devices`
 */
static size_t devices_size = 0;

/**
 * The last element slot used in `devices`
 */
static size_t devices_used = 0;

/**
 * The index of the first free slot in `devices`
 */
static size_t devices_ptr = 0;

/**
 * The input event listener thread
 */
static pthread_t ev_thread;

/**
 * Whether `ev_thread` has started
 */
static volatile sig_atomic_t ev_thread_started = 0;

/**
 * Message buffer for the main thread
 */
static char *resp_send_buffer = NULL;

/**
 * The size of `resp_send_buffer`
 */
static size_t resp_send_buffer_size = 0;

/**
 * Message buffer for the event thread
 */
static char *anno_send_buffer = NULL;

/**
 * The size of `anno_send_buffer`
 */
static size_t anno_send_buffer_size = 0;

/**
 * File descriptor for the libinput events
 */
static int event_fd = -1;

/**
 * File descriptor set for select(3):ing
 * the libinput events
 */
static fd_set event_fd_set;

/**
 * Whether the server has been signaled to
 * free unneeded memory
 * 
 * For event thread
 */
static volatile sig_atomic_t ev_danger = 0;

/**
 * Whether the server has been signaled to
 * output runtime information
 * 
 * For event thread
 */
static volatile sig_atomic_t info = 0;

/**
 * Mutex that should be used when accessing
 * the device list
 */
static pthread_mutex_t dev_mutex;



/**
 * Send a full message even if interrupted
 * 
 * @param   message:const char*  The message to send
 * @param   length:size_t        The length of the message
 * @return  :int                 Zero on success, -1 on error
 */
#define full_send(message, length)\
	((full_send)(socket_fd, message, length))


/**
 * Parse command line arguments
 * 
 * @return  Non-zero on error
 */
int
parse_cmdline(void)
{
	int i, v;
	char *arg;

	/* Parse command line arguments. */
	for (i = 1; i < argc; i++) {
		arg = argv[i];
		if ((v = strequals(arg, "--initial-spawn")) || /* Initial spawn? */
		    strequals(arg, "--respawn")) {             /* Respawning after crash? */
			exit_if (is_respawn == v,
				 eprintf("conflicting arguments %s and %s cannot be combined.",
					 "--initial-spawn", "--respawn"););
			is_respawn = !v;
		} else if (strequals(arg, "--re-exec")) { /* Re-exec state-marshal. */
			is_reexec = 1;
		} else if (startswith(arg, "--alarm=")) { /* Schedule an alarm signal for forced abort. */
			alarm(min(atou(arg + strlen("--alarm=")), 60)); /* At most 1 minute. */
		} else if (strequals(arg, "--on-init-fork")) { /* Fork process when initialised. */
			on_init_fork = 1;
		} else if (startswith(arg, "--on-init-sh=")) { /* Run a command when initialised. */
			on_init_sh = arg + strlen("--on-init-sh=");
		} else if (strequals(arg, "--immortal")) { /* I return to serve. */
			is_immortal = 1;
		} else if (startswith(arg, "--seat=")) { /* Seat to pass to libinput. */
			seat = arg + strlen("--seat=");
		}
	}
	if (is_reexec) {
		is_respawn = 1;
		eprint("re-exec performed.");
	}

	/* Check that mandatory arguments have been specified. */
	if (server_characteristics.require_respawn_info)
		exit_if (is_respawn < 0,
		         eprintf("missing state argument, require either %s or %s.",
		                 "--initial-spawn", "--respawn"););
	return 0;
}


/**
 * This function will be invoked before `initialise_server` (if not re-exec:ing)
 * or before `unmarshal_server` (if re-exec:ing)
 * 
 * @return  Non-zero on error
 */
int __attribute__((const))
preinitialise_server(void)
{
	return 0;
}


/**
 * This function should initialise the server,
 * and it not invoked after a re-exec.
 * 
 * @return  Non-zero on error
 */
int
initialise_server(void)
{
	int stage = 0;
	fail_if (server_initialised());
	fail_if (mds_message_initialise(&received)); stage++;

	return 0;

fail:
	xperror(*argv);
	if (stage >= 1) mds_message_destroy(&received);
	return 1;
}


/**
 * This function will be invoked after `initialise_server` (if not re-exec:ing)
 * or after `unmarshal_server` (if re-exec:ing)
 * 
 * @return  Non-zero on error
 */
int
postinitialise_server(void)
{
	int stage = 0;
	fail_if (initialise_libinput());
	fail_if (pthread_mutex_init(&dev_mutex, NULL)); stage++;

	if (connected)
		return 0;

	fail_if (reconnect_to_display());
	connected = 1;
	return 0;
fail:
	terminate_libinput();
	mds_message_destroy(&received);
	if (stage >= 1) pthread_mutex_destroy(&dev_mutex);
	return 1;
}


/**
 * Calculate the number of bytes that will be stored by `marshal_server`
 * 
 * On failure the program should `abort()` or exit by other means.
 * However it should not be possible for this function to fail.
 * 
 * @return  The number of bytes that will be stored by `marshal_server`
 */
size_t
marshal_server_size(void)
{
	size_t rc = 2 * sizeof(int) + sizeof(uint32_t);
	rc += mds_message_marshal_size(&received);
	return rc;
}


/**
 * Marshal server implementation specific data into a buffer
 * 
 * @param   state_buf  The buffer for the marshalled data
 * @return             Non-zero on error
 */
int
marshal_server(char *state_buf)
{
	buf_set_next(state_buf, int, MDS_LIBINPUT_VARS_VERSION);
	buf_set_next(state_buf, int, connected);
	buf_set_next(state_buf, uint32_t, message_id);
	mds_message_marshal(&received, state_buf);

	mds_message_destroy(&received);
	return 0;
}


/**
 * Unmarshal server implementation specific data and update the servers state accordingly
 * 
 * On critical failure the program should `abort()` or exit by other means.
 * That is, do not let `reexec_failure_recover` run successfully, if it unrecoverable
 * error has occurred or one severe enough that it is better to simply respawn.
 * 
 * @param   state_buf  The marshalled data that as not been read already
 * @return             Non-zero on error
 */
int
unmarshal_server(char *state_buf)
{
	/* buf_get_next(state_buf, int, MDS_LIBINPUT_VARS_VERSION); */
	buf_next(state_buf, int, 1);
	buf_get_next(state_buf, int, connected);
	buf_get_next(state_buf, uint32_t, message_id);
	fail_if (mds_message_unmarshal(&received, state_buf));

	return 0;
fail:
	xperror(*argv);
	mds_message_destroy(&received);
	return -1;
}


/**
 * Attempt to recover from a re-exec failure that has been
 * detected after the server successfully updated it execution image
 * 
 * @return  Non-zero on error
 */
int __attribute__((const))
reexec_failure_recover(void)
{
	return -1;
}


/**
 * Send a singal to all threads except the current thread
 * 
 * @param  signo  The signal
 */
void
signal_all(int signo)
{
	pthread_t current_thread = pthread_self();
  
	if (!pthread_equal(current_thread, master_thread))
		pthread_kill(master_thread, signo);

	if (ev_thread_started)
		if (!pthread_equal(current_thread, ev_thread))
			pthread_kill(ev_thread, signo);
}


/**
 * This function is called when a signal that
 * signals that the system is running out of memory
 * has been received
 * 
 * @param  signo  The signal that has been received
 */
void
received_danger(int signo)
{
	SIGHANDLER_START;
	if (!danger || !ev_danger) {
		danger = 1;
		ev_danger = 1;
		eprint("danger signal received.");
	}
	SIGHANDLER_END;
	(void) signo;
}


/**
 * Perform the server's mission
 * 
 * @return  Non-zero on error
 */
int
master_loop(void)
{
	int rc = 1, joined = 0, r;
	void *ev_ret;
  
	/* Start thread that reads input events. */
	fail_if ((errno = pthread_create(&ev_thread, NULL, event_loop, NULL)));

	/* Listen for messages. */
	while (!reexecing && !terminating) {
		if (info)
			dump_info();
		if (danger) {
			danger = 0;
			free(resp_send_buffer), resp_send_buffer = NULL;
			resp_send_buffer_size = 0;
			pack_devices();
		}

		if (!(r = mds_message_read(&received, socket_fd)))
			if (!(r = handle_message()))
				continue;

		if (r == -2) {
			eprint("corrupt message received, aborting.");
			goto done;
		} else if (errno == EINTR) {
			continue;
		} else {
			fail_if (errno != ECONNRESET);
		}

		eprint("lost connection to server.");
		mds_message_destroy(&received);
		mds_message_initialise(&received);
		connected = 0;
		fail_if (reconnect_to_display());
		connected = 1;
	}

	joined = 1;
	fail_if ((errno = pthread_join(ev_thread, &ev_ret)));
	rc = ev_ret == NULL ? 0 : 1;
	goto done;
fail:
	xperror(*argv);
done:
	free(resp_send_buffer);
	if (!joined && (errno = pthread_join(ev_thread, NULL)))
		xperror(*argv);
	if (!rc && reexecing)
		return 0;
	mds_message_destroy(&received);
	terminate_libinput();
	return rc;
}


/**
 * The event listener thread's main function
 * 
 * @param   data  Input data
 * @return        Output data
 */
void *event_loop(void *data)
{
	ev_thread_started = 1;

	if (handle_event() < 0)
		fail_if (errno != EINTR);
	while (!reexecing && !terminating) {
		if (ev_danger) {
			ev_danger = 0;
			free(anno_send_buffer);
			anno_send_buffer = NULL;
			anno_send_buffer_size = 0;
		}

		FD_SET(event_fd, &event_fd_set);
		if (select(event_fd + 1, &event_fd_set, NULL, NULL, NULL) < 0) {
			fail_if (errno != EINTR);
			continue;
		}
		if (handle_event() < 0)
			fail_if (errno != EINTR);
	}

	return NULL;

fail:
	xperror(*argv);
	raise(SIGTERM);
	return (void*)1024;
	(void) data;
}


/**
 * Handle an event from libinput
 * 
 * @return  Zero on success, -1 on error
 */
int
handle_event(void)
{
	struct libinput_event* ev;
	if ((errno = -libinput_dispatch(li)))
		return -1;
	while ((ev = libinput_get_event(li))) {
		switch (libinput_event_get_type(ev)) {
			/* TODO */
		default:
			break;
		}
		libinput_event_destroy(ev);
		if ((errno = -libinput_dispatch(li)))
			return -1;
	}
	return 0;
}


/**
 * Handle the received message
 * 
 * @return  Zero on success, -1 on error
 */
int
handle_message(void)
{
	/* TODO */
	return 0;
}


/**
 * Used by libinput to open a device
 * 
 * @param   path      The filename of the device
 * @param   flags     The flags to open(3)
 * @param   userdata  Not used
 * @return            The file descriptor, or `-errno` on error
 */
int
open_restricted(const char *path, int flags, void *userdata)
{
	int fd = open(path, flags);
	if (fd < 0)
		return perror(*argv), -errno;
	return fd;
	(void) userdata;
}


/**
 * Used by libinput to close device
 * 
 * @param  fd        The file descriptor of the device
 * @param  userdata  Not used
 */
void
close_restricted(int fd, void *userdata)
{
	close(fd);
	(void) userdata;
}


/**
 * Acquire access of input devices
 * 
 * @return  Zero on success, -1 on error
 */
int
initialise_libinput(void)
{
	const struct libinput_interface interface = {
		.open_restricted  = open_restricted,
		.close_restricted = close_restricted
	};

	if (!(udev = udev_new()))
		return eprint("failed to initialize udev."), errno = 0, -1;
	if (!(li = libinput_udev_create_context(&interface, NULL, udev)))
		return eprint("failed to initialize context from udev."), errno = 0, -1;
	if (libinput_udev_assign_seat(li, seat))
		return eprintf("failed to set seat: %s", seat), errno = 0, -1;

	event_fd = libinput_get_fd(li);
	FD_ZERO(&event_fd_set);

	return 0;
}


/**
 * Release access of input devices
 */
void
terminate_libinput(void)
{
	while (devices_used--)
		if (devices[devices_used])
			libinput_device_unref(devices[devices_used]);
	if (li)
		libinput_unref(li);
	if (udev)
		udev_unref(udev);
}


/**
 * Add a device to the device list
 * 
 * @param   dev  The device
 * @return       Zero on success, -1 on error
 */
int
add_device(struct libinput_device *dev)
{
	struct libinput_device **tmp;
	if (devices_ptr == devices_size) {
		if (yrealloc(tmp, devices, devices_size + 10, struct libinput_device*))
			return -1;
		devices_size += 10;
	}

	devices[devices_ptr++] = libinput_device_ref(dev);
	while (devices_ptr < devices_used && devices[devices_ptr])
		devices_ptr++;

	return 0;
}


/**
 * Remove a device from the device list
 * 
 * @param  dev  The device
 */
void
remove_device(struct libinput_device *dev)
{
	size_t i;
	for (i = 0; i < devices_used; i++) {
		if (devices[i] == dev) {
			libinput_device_unref(dev);
			devices[i] = NULL;
			if (i < devices_ptr)
				devices_ptr = i;
			if (i + 1 == devices_used)
				devices_used -= 1;
			break;
		}
	}
}


/**
 * Pack the device list
 */
void
pack_devices(void)
{
	struct libinput_device **tmp;
	size_t i;
  
	for (i = devices_ptr = 0; i < devices_used; i++)
		if (devices[i])
			devices[devices_ptr++] = devices[i];
	devices_used = devices_ptr;

	if (devices_used) {
		if (yrealloc(tmp, devices, devices_used, struct libinput_device *))
			return;
		devices_size = devices_used;
	} else {
		free(devices);
		devices = NULL;
		devices_size = 0;
	}
}


/**
 * This function is called when a signal that
 * signals that the system to dump state information
 * and statistics has been received
 * 
 * @param  signo  The signal that has been received
 */
void
received_info(int signo)
{
	SIGHANDLER_START;
	info = 1;
	SIGHANDLER_END;
	(void) signo;
}


/**
 * The the state of the server
 */
void
dump_info(void)
{
	info = 1;
	iprintf("next message ID: %" PRIu32, message_id);
	iprintf("connected: %s", connected ? "yes" : "no");
	iprintf("libinput seat: %s", seat);
	iprintf("sigdanger pending (main): %s", danger ? "yes" : "no");
	iprintf("sigdanger pending (event): %s", ev_danger ? "yes" : "no");
	iprintf("response send buffer size: %zu bytes", resp_send_buffer_size);
	iprintf("announce send buffer size: %zu bytes", anno_send_buffer_size);
	iprintf("event file descriptor: %i", event_fd);
	iprintf("event thread started: %s", ev_thread_started ? "yes" : "no");
	/* TODO list devices -- with_mutex(dev_mutex, ); */
}