aboutsummaryrefslogblamecommitdiffstats
path: root/src/mds-libinput.c
blob: 09cf26a4b56096e4d83935485ee9c6eaa21238f8 (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;
  
  /* Parse command line arguments. */
  for (i = 1; i < argc; i++)
    {
      char* arg = argv[i];
      int v;
      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) == 0)
    pthread_kill(master_thread, signo);
  
  if (ev_thread_started)
    if (pthread_equal(current_thread, ev_thread) == 0)
      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;
  (void) signo;
  if ((danger == 0) || (ev_danger == 0))
    {
      danger = 1;
      ev_danger = 1;
      eprint("danger signal received.");
    }
  SIGHANDLER_END;
}


/**
 * 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), r == 0)
	if (r = handle_message(), r == 0)
	  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)
{
  (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;
}


/**
 * 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)
{
  if (devices_ptr == devices_size)
    {
      struct libinput_device** tmp;
      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)
{
  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)
    {
      struct libinput_device** tmp;
      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;
  (void) signo;
  info = 1;
  SIGHANDLER_END;
}


/**
 * 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, ); */
}