aboutsummaryrefslogblamecommitdiffstats
path: root/src/mds-kkbd.c
blob: 3319c87bf36182306c900310023a2e857ee19389 (plain) (tree)
1
2
3

                                 
                                                                     














                                                                        

                                                                         












                                     
                    
                   
                                 



























                                



                                                                   
                                                   
 
   
                                                    






                                              




                                                       
 












                                                                     

                         






                                                            
                               






























                                                   


















                                                  
 


                                
                                                                             



                                     
                                




                                   










                                   




                                                 




                                                           
























                                         










                                                             

















































                                                                                            

























                                                                                
                   


                                  
                                  
                            




                          
        
                                    






                                     
  





                                                               



                                              
      
                 
                



                                     


                                                         














                                                                               
                                   

                


                                 







                                                                
                                                        


















                                                                        
                                                                          
                                                    














                                                            
                                                



                                                      









                                                             


                                            
                


















                                                                                         
                                                



                                                      






                                                
                                                               

                                                             


                                                        
      

                                 
                

























                                                                    
                            

                
                                                        
                                                                             
  
                            

                                    







                                                             

                                         



                                                        
                    


                              

                                      




                                           
                                       

                    
  


                                                         
            
      
                 
      
                                     
                                        
                    

                                                            

                       
                                 
                




            












                                               
                               
  

              
      
                 





                     












                                        
                                 

           

                                                         










                                                            
                                                        




                              
                                                                                             


                                                                      

                                                           
                                                     
                                                                                    
                                                      
                                                    




                                                                                        
                                                   
                                                                        
                                                   
                                                                                 

                                                   
        

                                                                    

            



   











                                                  

                                              

           


                    



   


                                                       

                                                                                  





                                                                                       
                 
           
        
  
                             
                                                           
  
  


                                                                                                       


                                     
                                                                               

                    
                                                                             


                               
                                         




                                                                  
                                                 
                    

                                             

     




                                                                          
  

                                                                                 
                                               


                           
                                     




                                           
                         
                                     


                                
                                                                                 


                                                              
                                             
                



                                         









                                                                                  
                                                           
 

                                                                        
                 

                      
                             
                                                           
  



                                             

                                             
                                               
                             


                                                           
                             
  
  




                                                                





                                                                             
  



                                                                  
                                               
  
                                            




                                                                          




                                                             







                                                      
                                  
                                                                      



                                       


                                                                    

         





                                                                                   


                                                                

                                                                                  


                                                                                       
                                                                        

           



                                             

                           
                                     














                                                                        


                                                  
                                             

                



                                         














                                                                                





                    



                                                                        
                                                                                      

                        
                                                                                    
  











                                                                                 


                                                                  
                  
                                                                






                                             


                                                                
                  
                                                              





                      
                                                           




                                     








                                                     

                                                                                  





                                                                                     
                 

              
  



                                                                        
                                                                                          

                                       
                                                                                         
  





                                                         
                                 

     



                                                                          
  
                                    
                                                        
                                               


                                
                                     

                                       
               
                                                 


                                                    
                  
                                                






                                                              
                                             

                




                                         



   






































                                                                         



                                                                     










                                                        



                                                                        





























































































                                                                                

                                







                                                                                                  





                                                       


               
                        
                                  













                                                                    

           

            




















                                                   
                                   






                                                  

            




















                                                                                          

                                                      
                                                          







                                                               
                                         












                                                                                                  

            



   





                                                                 
                                                                                 
 
                                                      

                      
                 
  


                                                   





                                             










                                                   
                                                     
  
                                            




                                                                          

                                              
                
                                                       

                                    


                                                                    

                                
                                                         


                                
                                     
                         





                                                           

                                                              


                         

                                                        
                                             

                



                                         



   


                                                




                                                                                  
   

                                                                               
 
        



                                                                        

                                                                             

                                     
                                                                                        


                                                                     
                                                 
                    
                                             






                                           
     
                                           
     
                                           
                                                                                             
      
                                                               
     

                                                                          
  


            



   

















                                                         






                                         
                                                    

                                             
                    
                  


           
                                               
      


            










                                            
                











                                     
                                             



                

            










                                              



                                        















                                                    
                                                          


                                                              
                                                              
           


                 














                                                          








                                                                             
                                                          
                 








                                           



                                                 
  




                                                                          
           




                                  
                                         
                                       

                                                           
                                            
      




                                 
                                         
                                       

                                 
                                            
  

                                                                      
                                            
                



                                         































                                                                 
                                             

                             
                        




































                                            



                  

 










                                                                                  
                                                                                     

        
                                               





                                            
                                                 





                                                                          
                                             
                



                                         

 



































                                                                      








                                                    
                   















                                                                                            
                 

 
/**
 * mds — A micro-display server
 * Copyright © 2014, 2015  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-kkbd.h"
/* TODO: This server should wait for `Command: get-vt` to be available,
         query the active VT and connect to that TTY instead of stdin. */

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

#include <inttypes.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/kd.h>
#include <pthread.h>
#include <alloca.h>
#define reconnect_to_display() -1



#ifdef __sparc__
# define GET_LED  KIOCGLED
# define SET_LED  KIOCSLED
#else
# define GET_LED  KDGETLED
# define SET_LED  KDSETLED
#endif


#ifdef __sparc__
# define LED_NUM_LOCK   1
# define LED_CAPS_LOCK  8
# define LED_SCRL_LOCK  4
# define LED_COMPOSE    2
#else
# define LED_NUM_LOCK   LED_NUM
# define LED_CAPS_LOCK  LED_CAP
# define LED_SCRL_LOCK  LED_SCR
#endif



#define MDS_KKBD_VARS_VERSION  0


/**
 * The name of the keyboard for which its server implements control
 */
#define KEYBOARD_ID  "kernel"
/* NOTE: length hardcoded in `initialise_server` */

/**
 * LED:s that we believe are present on the keyboard
 */
#ifdef LED_COMPOSE
# define PRESENT_LEDS  "num caps scrl compose"
#else
# define PRESENT_LEDS  "num caps scrl"
#endif

/**
 * Measure a string literal in compile time
 */
#define lengthof(str)  (sizeof(str) / sizeof(char) - 1)



/**
 * 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 = 0,
    .require_display = 1,
    .require_respawn_info = 0,
    .sanity_check_argc = 1,
    .fork_for_safety = 1,
    .danger_is_deadly = 0
  };



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

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

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

/**
 * File descriptor for accessing the keyboard LED:s
 */
static int ledfd = 0;

/**
 * Saved LED states
 */
static int saved_leds;

/**
 * Saved TTY settings
 */
static struct termios saved_stty;

/**
 * Save keyboard mode
 */
static int saved_kbd_mode;

/**
 * Keycode remapping table
 */
static int* restrict mapping = NULL;

/**
 * The size of `mapping`
 */
static size_t mapping_size = 0;

/**
 * Scancode buffer
 */
static int scancode_buf[3] = { 0, 0, 0 };

/**
 * The number of elements stored in `scancode_buf`
 */
static int scancode_ptr = 0;

/**
 * Message buffer for `send_key`
 */
static char key_send_buffer[80 + 3 * 3 + 5 + lengthof(KEYBOARD_ID) + 10 + 1];

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

/**
 * The size of `send_buffer`
 */
static size_t send_buffer_size = 0;

/**
 * The keyboard listener thread
 */
static pthread_t kbd_thread;

/**
 * Whether `kbd_thread` has started
 */
static int kbd_thread_started = 0;

/**
 * Mutex that should be used when sending message
 */
static pthread_mutex_t send_mutex;

/**
 * Mutex that should be used when accessing the keycode map
 */
static pthread_mutex_t mapping_mutex;

/**
 * The value Num Lock's LED is mapped
 */
static int led_num_lock = LED_NUM_LOCK;

/**
 * The value Caps Lock's LED is mapped
 */
static int led_caps_lock = LED_CAPS_LOCK;

/**
 * The value Scroll Lock's LED is mapped
 */
static int led_scrl_lock = LED_SCRL_LOCK;

#ifdef LED_COMPOSE
/**
 * The value Compose's LED is mapped
 */
static int led_compose = LED_COMPOSE;
#endif



/**
 * 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, "--led=")) /* Remap LED:s. */
	{
	  if (remap_led_cmdline(arg + strlen("--led=")) < 0)
	    return -1;
	}
    }
  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;
  const char* const message =
    "Command: intercept\n"
    "Message ID: 0\n"
    "Length: 102\n"
    "\n"
    "Command: set-keyboard-leds\n"
    "Command: get-keyboard-leds\n"
    "Command: map-keyboard-leds\n"
    "Command: keycode-map\n"
    /* NEXT MESSAGE */
    "Command: intercept\n"
    "Message ID: 1\n"
    "Modifying: yes\n"
    "Length: 59\n"
    "\n"
    "Command: enumerate-keyboards\n"
    "Command: keyboard-enumeration\n"
    /* NEXT MESSAGE */
    "Command: new-keyboard\n"
    "Message ID: 2\n"
    "Length: 7\n"
    "\n"
    KEYBOARD_ID "\n";
  
  fail_if (open_leds() < 0);  stage++;
  fail_if (open_input() < 0);  stage++;
  fail_if (pthread_mutex_init(&send_mutex, NULL));  stage++;
  fail_if (pthread_mutex_init(&mapping_mutex, NULL));  stage++;
  fail_if (full_send(message, strlen(message)));
  fail_if (server_initialised());  stage++;
  fail_if (mds_message_initialise(&received));
  
  return 0;
  
 fail:
  xperror(*argv);
  if (stage < 5)
    {
      if (stage >= 2)  close_input();
      if (stage >= 1)  close_leds();
    }
  if (stage >= 3)  pthread_mutex_destroy(&send_mutex);
  if (stage >= 4)  pthread_mutex_destroy(&mapping_mutex);
  if (stage >= 5)  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)
{
  if (connected)
    return 0;
  
  fail_if (reconnect_to_display());
  connected = 1;
  return 0;
 fail:
  mds_message_destroy(&received);
  return 1;
}


/**
 * This function is called by the parent server process when the
 * child server process exits, if the server has completed its
 * initialisation
 * 
 * @param  status  The status witch which the child died
 */
void fork_cleanup(int status)
{
  (void) status;
  close_input();
  close_leds();
}


/**
 * 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 = 9 * sizeof(int) + sizeof(uint32_t) + sizeof(struct termios);
  rc += sizeof(size_t) + mapping_size * sizeof(int);
  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_KKBD_VARS_VERSION);
  buf_set_next(state_buf, int, connected);
  buf_set_next(state_buf, uint32_t, message_id);
  buf_set_next(state_buf, int, ledfd);
  buf_set_next(state_buf, int, saved_leds);
  buf_set_next(state_buf, struct termios, saved_stty);
  buf_set_next(state_buf, int, saved_kbd_mode);
  buf_set_next(state_buf, int, scancode_ptr);
  buf_set_next(state_buf, int, scancode_buf[0]);
  buf_set_next(state_buf, int, scancode_buf[1]);
  buf_set_next(state_buf, int, scancode_buf[2]);
  buf_set_next(state_buf, size_t, mapping_size);
  if (mapping_size > 0)
    {
      memcpy(state_buf, mapping, mapping_size * sizeof(int));
      state_buf += mapping_size * sizeof(int) / sizeof(char);
    }
  mds_message_marshal(&received, state_buf);
  
  mds_message_destroy(&received);
  free(mapping);
  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_KKBDOARD_VARS_VERSION); */
  buf_next(state_buf, int, 1);
  buf_get_next(state_buf, int, connected);
  buf_get_next(state_buf, uint32_t, message_id);
  buf_get_next(state_buf, int, ledfd);
  buf_get_next(state_buf, int, saved_leds);
  buf_get_next(state_buf, struct termios, saved_stty);
  buf_get_next(state_buf, int, saved_kbd_mode);
  buf_get_next(state_buf, int, scancode_ptr);
  buf_get_next(state_buf, int, scancode_buf[0]);
  buf_get_next(state_buf, int, scancode_buf[1]);
  buf_get_next(state_buf, int, scancode_buf[2]);
  buf_get_next(state_buf, size_t, mapping_size);
  if (mapping_size > 0)
    {
      fail_if (xmemdup(mapping, state_buf, mapping_size, int));
      state_buf += mapping_size * sizeof(int) / sizeof(char);
    }
  fail_if (mds_message_unmarshal(&received, state_buf));
  
  return 0;
 fail:
  xperror(*argv);
  mds_message_destroy(&received);
  free(mapping);
  abort(); /* We must abort on failure to not risk the keyboard
	      getting stuck and freeze up the computer until
	      someone ssh:es into it and kill the server. */
  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;
}


/**
 * Perform the server's mission
 * 
 * @return  Non-zero on error
 */
int master_loop(void)
{
  int rc = 1, joined = 0, r;
  void* kbd_ret;
  
  /* Start thread that reads input from the keyboard. */
  fail_if ((errno = pthread_create(&kbd_thread, NULL, keyboard_loop, NULL)));
  
  /* Listen for messages. */
  while (!reexecing && !terminating)
    {
      if (danger)
	{
	  danger = 0;
	  free(send_buffer), send_buffer = NULL;
	  send_buffer_size = 0;
	}
      
      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(kbd_thread, &kbd_ret)));
  rc = kbd_ret == NULL ? 0 : 1;
  goto done;
 fail:
  xperror(*argv);
 done:
  pthread_mutex_destroy(&send_mutex);
  pthread_mutex_destroy(&mapping_mutex);
  free(send_buffer);
  if ((!joined) && (errno = pthread_join(kbd_thread, NULL)))
    xperror(*argv);
  if (!rc && reexecing)
    return 0;
  mds_message_destroy(&received);
  free(mapping);
  return rc;
}


/**
 * The keyboard listener thread's main function
 * 
 * @param   data  Input data
 * @return        Output data
 */
void* keyboard_loop(void* data)
{
  (void) data;
  
  kbd_thread_started = 1;
  
  while (!reexecing && !terminating)
    if (fetch_keys() < 0)
      fail_if (errno != EINTR);
  
  return NULL;
  
 fail:
  xperror(*argv);
  raise(SIGTERM);
  return (void*)1024;
}


/**
 * Handle the received message
 * 
 * @return  Zero on success, -1 on error
 */
int handle_message(void)
{
  const char* recv_command = NULL;
  const char* recv_client_id = "0:0";
  const char* recv_message_id = NULL;
  const char* recv_modify_id = NULL;
  const char* recv_active = NULL;
  const char* recv_mask = NULL;
  const char* recv_keyboard = NULL;
  const char* recv_action = NULL;
  size_t i;
  
#define __get_header(storage, header)			\
  (startswith(received.headers[i], header))		\
    storage = received.headers[i] + strlen(header)
  
  for (i = 0; i < received.header_count; i++)
    {
      if      __get_header(recv_command,    "Command: ");
      else if __get_header(recv_client_id,  "Client ID: ");
      else if __get_header(recv_message_id, "Message ID: ");
      else if __get_header(recv_modify_id,  "Modify ID: ");
      else if __get_header(recv_active,     "Active: ");
      else if __get_header(recv_mask,       "Mask: ");
      else if __get_header(recv_keyboard,   "Keyboard: ");
      else if __get_header(recv_action,     "Action: ");
    }
  
#undef __get_header
  
  if (recv_message_id == NULL)
    return eprint("received message without ID, ignoring, master server is misbehaving."), 0;
  
  if (recv_command == NULL)
    return 0; /* How did that get here, not matter, just ignore it? */

#define t(expr)  do { fail_if (expr); return 0; } while (0)
  if (strequals(recv_command, "enumerate-keyboards"))
    t (handle_enumerate_keyboards(recv_client_id, recv_message_id, recv_modify_id));
  if (strequals(recv_command, "keyboard-enumeration"))
    t (handle_keyboard_enumeration(recv_modify_id));
  if (strequals(recv_command, "keycode-map"))
    t (handle_keycode_map(recv_client_id, recv_message_id, recv_action, recv_keyboard));
  /* The following do not need to be inside a mutex, because this server
     only interprets on message at the time, thus there can not be any
     conflicts and access to LED:s are automatically atomic. */
  if (strequals(recv_command, "set-keyboard-leds"))
    t (handle_set_keyboard_leds(recv_active, recv_mask, recv_keyboard));
  if (strequals(recv_command, "get-keyboard-leds"))
    t (handle_get_keyboard_leds(recv_client_id, recv_message_id, recv_keyboard));
  if (strequals(recv_command, "map-keyboard-leds"))
    t (handle_map_keyboard_leds(recv_keyboard));
#undef t
  
  return 0; /* How did that get here, not matter, just ignore it? */
 fail:
  return -1;
}


/**
 * Make sure `send_buffer` is large enough
 * 
 * @param   size  The size required for the buffer
 * @return        Zero on success, -1 on error
 */
static int ensure_send_buffer_size(size_t size)
{
  char* old = send_buffer;
  
  if (send_buffer_size >= size)
    return 0;
  
  fail_if (xrealloc(send_buffer, size, char));
  send_buffer_size = size;
  
  return 0;
 fail:
  send_buffer = old;
  return -1;
}


/**
 * Handle the received message after it has been
 * identified to contain `Command: enumerate-keyboards`
 * 
 * @param   recv_client_id   The value of the `Client ID`-header, "0:0" if omitted
 * @param   recv_message_id  The value of the `Message ID`-header
 * @param   recv_modify_id   The value of the `Modify ID`-header, `NULL` if omitted
 * @return                   Zero on success, -1 on error
 */
int handle_enumerate_keyboards(const char* recv_client_id, const char* recv_message_id,
			       const char* recv_modify_id)
{
  uint32_t msgid;
  size_t n;
  int r;
  
  if (recv_modify_id == NULL)
    return eprint("did not get a modify ID, ignoring."), 0;
  
  
  if (strequals(recv_client_id, "0:0"))
    {
      eprint("received information request from an anonymous client, sending non-modifying response.");
      
      with_mutex (send_mutex,
		  msgid = message_id;
		  message_id = message_id == UINT32_MAX ? 0 : (message_id + 1);
		  );
      
      fail_if (ensure_send_buffer_size(47 + strlen(recv_modify_id) + 1) < 0);
      sprintf(send_buffer,
	      "Modify: no\n"
	      "Modify ID: %s\n"
	      "Message ID: %" PRIu32 "\n"
	      "\n",
	      recv_modify_id, msgid);
      
      with_mutex (send_mutex,
		  r = full_send(send_buffer, strlen(send_buffer));
		  if (r)  r = errno ? errno : -1;
		  );
      fail_if (errno = (r == -1 ? 0 : r), r);
      return 0;
    }
  
  with_mutex (send_mutex,
	      msgid = message_id;
	      message_id = message_id == INT32_MAX ? 0 : (message_id + 1);
	      message_id = message_id == INT32_MAX ? 0 : (message_id + 1);
	      );
  
  n = 134 + 3 * sizeof(size_t) + lengthof(KEYBOARD_ID);
  n += strlen(recv_client_id) + strlen(recv_modify_id) + strlen(recv_message_id);
  fail_if (ensure_send_buffer_size(n + 1) < 0);
  sprintf(send_buffer,
	  "Modify: yes\n"
	  "Modify ID: %s\n"
	  "Message ID: %" PRIu32 "\n"
	  "\n"
	  /* NEXT MESSAGE */
	  "Command: keyboard-enumeration\n"
	  "To: %s\n"
	  "In response to: %s\n"
	  "Length: %zu\n"
	  "Message ID: %" PRIu32 "\n"
	  "\n"
	  KEYBOARD_ID "\n",
	  recv_modify_id, msgid,
	  recv_client_id, recv_message_id, lengthof(KEYBOARD_ID) + 1, msgid + 1);
  
  with_mutex (send_mutex,
	      r = full_send(send_buffer, strlen(send_buffer));
	      if (r)  r = errno ? errno : -1;
	      );
  fail_if (errno = (r == -1 ? 0 : r), r);
  return 0;
 fail:
  return -1;
}


/**
 * Handle the received message after it has been
 * identified to contain `Command: keyboard-enumeration`
 * 
 * @param   recv_modify_id  The value of the `Modify ID`-header, `NULL` if omitted
 * @return                  Zero on success, -1 on error
 */
int handle_keyboard_enumeration(const char* recv_modify_id)
{
  size_t i, off, m, n = lengthof(KEYBOARD_ID "\n") + 3 * sizeof(size_t);
  ssize_t top;
  uint32_t msgid;
  int r, have_len = 0;
  
  if (recv_modify_id == NULL)
    return eprint("did not get a modify ID, ignoring."), 0;
  
  
  /* Calculate length of received message. */
  
  /* Measure the length of the headers. */
  for (i = 0; i < received.header_count; i++)
    n += strlen(received.headers[i]);
  /* Count the line feeds after the headers. */
  n += received.header_count;
  /* There is an empty line between headers and payload. */
  n += 1;
  /* Add the length of the payload. */
  n += received.payload_size;
  
  
  /* Calculate upper bound for the outbound message's payload */
  n += lengthof("Length: \n") + 3 * sizeof(size_t);
  n += lengthof(KEYBOARD_ID "\n");
  
  
  /* Calculate an upper bound for the outbound message.
     `off` is an upper bound for the outbound message's
     headers plus empty line and thus where we should
     write the payload to the buffer. */
  off = sizeof("Modify ID: \nMessage ID: \nLength: \n\n") / sizeof(char) - 1;
  n += off += strlen(recv_modify_id) + 10 + 3 * sizeof(size_t);
  
  
  /* Make sure the buffer fits the message, add one additional
     character to the allocation so sprintf does not write outside
     the buffer. */
  fail_if (ensure_send_buffer_size(n + 1) < 0);
  
  /* Fetch and increase local message ID. */
  with_mutex (send_mutex,
	      msgid = message_id;
	      message_id = message_id == INT32_MAX ? 0 : (message_id + 1);
	      );
  
  
  /* Write outbound message payload. */
  
  /* Write inbound message's headers to the outbound message,
     `n` keeps track of were we are. */
  n = off;
  for (i = 0; i < received.header_count; i++)
    {
      const char* header = received.headers[i];
      if (!have_len && startswith(header, "Length: "))
	{
	  have_len = 1;
	  sprintf(send_buffer + n,
		  "Length: %zu\n",
		  received.payload_size + lengthof(KEYBOARD_ID "\n"));
	  n += strlen(send_buffer + n);
	}
      else
	{
	  m = strlen(header);
	  memcpy(send_buffer + n, header, m * sizeof(char)), n += m;
	  send_buffer[n++] = '\n';
	}
    }
  /* If we did not `Length`-header we must add it as we will extend the payload. */
  if (!have_len)
    sprintf(send_buffer + n,
	    "Length: %zu\n",
	    lengthof(KEYBOARD_ID "\n")),
      n += strlen(send_buffer + n);
  /* Mark end of inbound headers. */
  send_buffer[n++] = '\n';
  /* Write inbound message's payload to the outbound message. */
  memcpy(send_buffer + n, received.payload, received.payload_size * sizeof(char));
  n += received.payload_size;
  /* Add our keyboard to the payload. */
  memcpy(send_buffer + n, KEYBOARD_ID "\n", lengthof(KEYBOARD_ID "\n") * sizeof(char));
  n += lengthof(KEYBOARD_ID "\n");
  /* Change `n` to tell the length of the outbound message's payload. */
  n -= off;
  
  
  /* Complete outbound message. */
  
  /* Write the outbound message's headers. */
  sprintf(send_buffer,
	  "Modify ID: %s\n"
	  "Message ID: %" PRIu32 "\n"
	  "Length: %zu\n%zn",
	  recv_modify_id, msgid, n, &top);
  /* Write the empty line. */
  send_buffer[(size_t)(top++)] = '\n';
  /* Calculate what the offset in the entire buffer for the
     message should be, and move the message to that offset.
     Only the headers and the empty line should be moved, so
     that it is juxtaposed with the payload. */
  off -= (size_t)top;
  memmove(send_buffer + off, send_buffer, ((size_t)top) * sizeof(char));
  /* Calculate the final length of the message. */
  n += (size_t)top;
  
  
  /* Send message. */
  
  with_mutex (send_mutex,
	      r = full_send(send_buffer + off, n);
	      if (r)  r = errno ? errno : -1;
	      );
  
  fail_if (errno = (r == -1 ? 0 : r), r);
  return 0;
 fail:
  return -1;
}


/**
 * Handle the received message after it has been
 * identified to contain `Command: set-keyboard-leds`
 * 
 * @param   recv_active    The value of the `Active`-header, `NULL` if omitted
 * @param   recv_mask      The value of the `Mask`-header, `NULL` if omitted
 * @param   recv_keyboard  The value of the `Keyboard`-header, `NULL` if omitted
 * @return                 Zero on success, -1 on error
 */
int handle_set_keyboard_leds(const char* recv_active, const char* recv_mask,
			     const char* recv_keyboard)
{
  int active = 0;
  int mask = 0;
  int current;
  const char* begin;
  const char* end;
  
  if ((recv_keyboard != NULL) && !strequals(recv_keyboard, KEYBOARD_ID))
    return 0;
  
  if (recv_active == NULL)
    return eprint("received LED writing request without active header, ignoring."), 0;
  
  if (recv_mask == NULL)
    return eprint("received LED writing request without mask header, ignoring."), 0;
  
  current = get_leds();
  if (current < 0)
    {
      xperror(*argv);
      return 0; /* Not fatal */
    }
  
#define __test(have, want)  (startswith(have, want " ") || strequals(have, want))
  
  for (begin = end = recv_active; end != NULL;)
    {
      end = strchr(begin, ' ');
      if      (__test(begin, "num"))      active |= led_num_lock;
      else if (__test(begin, "caps"))     active |= led_caps_lock;
      else if (__test(begin, "scrl"))     active |= led_scrl_lock;
#ifdef LED_COMPOSE
      else if (__test(begin, "compose"))  active |= led_compose;
#endif
      begin = end + 1;
    }
  
  for (begin = end = recv_mask; end != NULL;)
    {
      end = strchr(begin, ' ');
      if      (__test(begin, "num"))      mask |= led_num_lock;
      else if (__test(begin, "caps"))     mask |= led_caps_lock;
      else if (__test(begin, "scrl"))     mask |= led_scrl_lock;
#ifdef LED_COMPOSE
      else if (__test(begin, "compose"))  mask |= led_compose;
#endif
      begin = end + 1;
    }
  
#undef __test
  
  current = (active & mask) | ((current ^ active) & ~mask);
  if (set_leds(current) < 0)
    {
      xperror(*argv); /* Not fatal */
      return 0;
    }
  
  return 0;
}


/**
 * Handle the received message after it has been
 * identified to contain `Command: get-keyboard-leds`
 * 
 * @param   recv_client_id   The value of the `Client ID`-header, "0:0" if omitted
 * @param   recv_message_id  The value of the `Message ID`-header
 * @param   recv_keyboard    The value of the `Keyboard`-header, `NULL` if omitted
 * @return                   Zero on success, -1 on error
 */
int handle_get_keyboard_leds(const char* recv_client_id, const char* recv_message_id,
			     const char* recv_keyboard)
{
  uint32_t msgid;
  size_t n;
  int r, leds;
  
  if ((recv_keyboard != NULL) && !strequals(recv_keyboard, KEYBOARD_ID))
    return 0;
  
  if (recv_keyboard == NULL)
    return eprint("received LED reading request but no specified keyboard, ignoring."), 0;
  
  if (strequals(recv_client_id, "0:0"))
    return eprint("received information request from an anonymous client, ignoring."), 0;
  
  leds = get_leds();
  if (leds < 0)
    {
      int error = errno;
      xperror(*argv);
      send_errno(error, recv_client_id, recv_message_id);
      fail_if (errno = error, 1);
    }
  
  with_mutex (send_mutex,
	      msgid = message_id;
	      message_id = message_id == INT32_MAX ? 0 : (message_id + 1);
	      );
  
  n = 65 + 2 * strlen(PRESENT_LEDS);
  n += strlen(recv_client_id) + strlen(recv_message_id);
  fail_if (ensure_send_buffer_size(n + 1) < 0);
  sprintf(send_buffer,
	  "To: %s\n"
	  "In response to: %s\n"
	  "Message ID: %" PRIu32 "\n"
	  "Active:%s%s%s%s%s\n"
	  "Present: " PRESENT_LEDS "\n"
	  "\n",
	  recv_client_id, recv_message_id, msgid,
	  (leds & led_num_lock)  ? " num"      : "",
	  (leds & led_caps_lock) ? " caps"     : "",
	  (leds & led_scrl_lock) ? " scrl"     : "",
#ifdef LED_COMPOSE
	  (leds & led_compose)   ? " compose"  :
#endif
	  "",
	  leds == 0 ? " none" : ""
	  );
  
  with_mutex (send_mutex,
	      r = full_send(send_buffer, strlen(send_buffer));
	      if (r)  r = errno ? errno : -1;
	      );
  
  fail_if (errno = (r == -1 ? 0 : r), r);
  return 0;
 fail:
  xperror(*argv);
  return -1;
}


/**
 * Retrieve the value of a LED from its name
 * 
 * @param   name  The name of the LED
 * @return        The value of the LED
 */
static int parse_led(const char* name)
{
  if (strequals(name, "num"))      return LED_NUM_LOCK;
  if (strequals(name, "caps"))     return LED_CAPS_LOCK;
  if (strequals(name, "scrl"))     return LED_SCRL_LOCK;
#ifdef LED_COMPOSE
  if (strequals(name, "compose"))  return LED_COMPOSE;
#endif
  return -1;
}


/**
 * Remap a LED
 * 
 * @param  name      The name of the LED to remap
 * @param  position  The new position of the LED, either zero-based index
 *                   or name of the original LED with that position
 */
static void remap_led(const char* name, const char* position)
{
  int* leds[] = { [LED_NUM_LOCK]  = &led_num_lock,
		  [LED_CAPS_LOCK] = &led_caps_lock,
#ifdef LED_COMPOSE
		  [LED_COMPOSE]   = &led_compose,
#endif
		  [LED_SCRL_LOCK] = &led_scrl_lock};
  
  int led = parse_led(name);
  int pos = parse_led(position);
  size_t i, n;
  char c;
  
  if (led < 0)
    {
      eprintf("received invalid LED, %s, to remap, ignoring.", name);
      return;
    }
  
  if (pos >= 0)
    goto done;
  
  for (pos = 0, i = 0, n = strlen(position); i < n; i++)
    if (c = position[i], ('0' <= c) && (c <= '9'))
      pos = pos * 10 + (c & 15);
    else
      break;
  
  if (i < n)
    {
      eprintf("received invalid LED position, %s, ignoring.", position);
      return;
    }
  
  pos = 1 << pos;
  
 done:
  *(leds[led]) = pos;
}


/**
 * Remap a LED, from the command line
 * 
 * @param   arg  The name of the LED to remap, the new position of the LED,
 *               either zero-based index or name of the original LED with
 *               that position; delimited by an equals-sign
 * @return       Zero on success, -1 on error
 */
int remap_led_cmdline(char* arg)
{
  char* pos = strchr(arg, '=');
  
  if ((pos == NULL) || (strlen(pos) == 1))
    {
      eprintf("received invalid argument for --led: %s", arg);
      errno = 0;
      return -1;
    }
  
  *pos++ = '\0';
  remap_led(arg, pos);
  return 0;
}


/**
 * Handle the received message after it has been
 * identified to contain `Command: map-keyboard-leds`
 * 
 * @param   recv_keyboard  The value of the `Keyboard`-header, `NULL` if omitted
 * @return                 Zero on success, -1 on error
 */
int handle_map_keyboard_leds(const char* recv_keyboard)
{
  char* led = NULL;
  char* pos = NULL;
  int stage = 0;
  size_t i;
  
  if ((recv_keyboard != NULL) && !strequals(recv_keyboard, KEYBOARD_ID))
    return 0;
  
  /* Parse the payload. `led` and `pos` are set just when thier first
     character is iterated over, rather than at the delimiter. This way
     we can easily check for message corruption at the end and give a
     better error message. */
  for (i = 0; i < received.payload_size; i++)
    if (stage == 0)
      {
	if (led == NULL)
	  led = received.payload + i;
	if (received.payload[i] == ' ')
	  received.payload[i] = '\0', stage = 1;
      }
    else if (stage == 1)
      {
	if (pos == NULL)
	  pos = received.payload + i;
	if (received.payload[i] == '\n')
	  {
	    received.payload[i] = '\0';
	    remap_led(led, pos);
	    led = pos = NULL;
	    stage = 0;
	  }
      }
  if ((stage == 1) && (pos != NULL))
    {
      /* Copy over the position value to a new allocation on the stack
         and NUL-terminate it. We are at the of the payload and cannot
         besure sure that we can put an extra NUL at the end of it
	 without reallocating the payload which is much ore complicated. */
      size_t len = (size_t)(pos - received.payload);
      char* pos_ = alloca((len + 1) * sizeof(char));
      memcpy(pos_, pos, len * sizeof(char));
      pos_[len] = '\0';
      remap_led(led, pos_);
    }
  else if (led != NULL)
    eprint("received incomplete LED remapping instruction, ignoring.");
  
  return 0;
}


/**
 * Parse a keycode rampping line
 * 
 * @param   begin  The beginning of the line
 * @param   end    The end of the line, `NULL` if it is not terminated by a new line
 * @param   n      The size of the table from the position of `begin`
 * @param   in     Output parameter for the keycode that should be remapped
 * @param   out    Output parameter for the keycode's new mapping
 * @return         -1 on error, 1 if parsed, 0 if the line is empty
 */
static int parse_remap_line(char* begin, char* end, size_t n, int* restrict in, int* restrict out)
{
  static char buf[3 * sizeof(int) + 1];
  
  size_t len = end == NULL ? n : (size_t)(end - begin);
  char* delimiter = memchr(begin, ' ', len);
  
  if (len == 0)
    return 0;
  
  if (delimiter == NULL)
    fail_if (*in = -1, *out = -1);
  
  *delimiter++ = '\0';
  *in = atoi(begin);
  if (end == NULL)
    {
      snprintf(buf, sizeof(buf) / sizeof(char), "%.*s",
	       (int)(len - (size_t)(delimiter - begin)), delimiter);
      *out = atoi(buf);
    }
  else
    {
      *end = '\0';
      *out = atoi(delimiter);
    }
  
  return 1;
 fail:
  return -1;
}


/**
 * Add a mapping to the keycode mapping table
 * 
 * @param   in   The keycode to remap
 * @parma   out  The keycode's new mapping
 * @return       Zero on success, -1 on error
 */
static int add_mapping(int in, int out)
{
  size_t n = ((size_t)in) + 1;
  int* old;
  
  if (n > mapping_size)
    {
      if (in == out)
	return 0;
      
      if (old = mapping, xrealloc(mapping, n, int))
	fail_if (mapping = old, 1);
      
      for (; mapping_size < n; mapping_size++)
	mapping[mapping_size] = (int)mapping_size;
    }
  
  mapping[in] = out;
  return 0;
 fail:
  return -1;
}


/**
 * Change the keycode mapping
 * 
 * @param   table  The remapping table as described by the `Command: keycode-map` protocol
 * @param   n      The size of `table`
 * @return         Zero on success, -1 on error
 */
static int remap(char* table, size_t n)
{
  char* begin = table;
  int greatest_remap = -1;
  int greatest_reset = -1;
  
  for (;;)
    {
      char* end = memchr(begin, '\n', n);
      int in, out;
      
      if (!parse_remap_line(begin, end, n, &in, &out))
	goto next;
      if ((in < 0) || (out < 0) || ((in | out) >= 0x4000))
	{
	  eprint("received malformated remapping table.");
	  goto next;
	}
      
      if (in != out)  greatest_remap = max(greatest_remap, in);
      else            greatest_reset = max(greatest_reset, in);
      
      fail_if (add_mapping(in, out) < 0);
      
    next:
      if (end == NULL)
	break;
      end++;
      n -= (size_t)(end - begin);
      begin = end;
    }
  
  if ((greatest_reset > greatest_remap) && (((((size_t)greatest_remap) + 1) >> 1) < mapping_size))
    shrink_map();
  
  return 0;
 fail:
  return -1;
}


/**
 * Responde to a keycode mapping query
 * 
 * @param   recv_client_id   The value of the `Client ID`-header
 * @param   recv_message_id  The value of the `Message ID`-header
 * @return                   Zero on success, -1 on error
 */
static int mapping_query(const char* recv_client_id, const char* recv_message_id)
{
  size_t top = 64 + 3 * sizeof(size_t), n = 0, off, i;
  ssize_t len;
  int greatest = 0, r;
  uint32_t msgid;
  
  /* Count the number of non-identity mappings, and
     figure out the value of non-identity mapping
     with the highest value. */
  for (i = 0; i < mapping_size; i++)
    if (mapping[i] != (int)i)
      {
	greatest = max(greatest, mapping[i]);
	n++;
      }
  /* If the highest source is larger than the
     highest targt, that source value will
     be highest integer that will be included
     the mapping-table. */
  greatest = max(greatest, (int)mapping_size);
  /* Calculate an upper bound for the payload. */
  n *= 2 + 2 * (size_t)(greatest > 0x00FF ? 5 : 3);
  
  /* Ensure that the buffer is large enough for
     the message and that `sprintf` will not
     write outside it. */
  fail_if (ensure_send_buffer_size(top + n + 2) < 0);
  
  /* Fetch and increase local message ID. */
  with_mutex (send_mutex,
	      msgid = message_id;
	      message_id = message_id == INT32_MAX ? 0 : (message_id + 1);
	      );
  
  /* The offset for the payload should fit all
     headers and an empty line. */
  off = top + 1;
  /* Write all non-identity mappings to the payload. */
  for (i = 0; i < mapping_size; i++)
    if (mapping[i] != (int)i)
      sprintf(send_buffer + off, "%i %i\n%zn", i, mapping[i], &len),
	off += (size_t)len;
  /* Calculate the length of the payload. */
  n = (size_t)(off - (top + 1));
  
  /* Write the headers, and the empty line after them. */
  sprintf(send_buffer,
	  "To: %s\n"
	  "In response to: %s\n"
	  "Message ID: %" PRIu32 "\n"
	  "Length: %zu\n"
	  "\n%zn",
	  recv_client_id, recv_message_id, msgid, n, &len);
  top = (size_t)len;
  /* Move the headers and the empty line so that they are
     juxtaposed with the payload. */
  off -= top;
  memmove(send_buffer + off, send_buffer, top * sizeof(char));
  
  
  /* Send the message. */
  
  with_mutex (send_mutex,
	      r = full_send(send_buffer + off, top + n);
	      if (r)  r = errno ? errno : -1;
	      );
  
  fail_if (errno = (r == -1 ? 0 : r), r);
  return 0;
 fail:
  return -1;
}


/**
 * Handle the received message after it has been
 * identified to contain `Command: keycode-map`
 * 
 * @param   recv_client_id   The value of the `Client ID`-header, "0:0" if omitted
 * @param   recv_message_id  The value of the `Message ID`-header
 * @param   recv_action      The value of the `Action`-header, `NULL` if omitted
 * @param   recv_keyboard    The value of the `Keyboard`-header, `NULL` if omitted
 * @return                   Zero on success, -1 on error
 */
int handle_keycode_map(const char* recv_client_id, const char* recv_message_id,
		       const char* recv_action, const char* recv_keyboard)
{
  int r;
  if ((recv_keyboard != NULL) && !strequals(recv_keyboard, KEYBOARD_ID))
    return 0;
  
  if (recv_action == NULL)
    eprint("received keycode map request but without any action, ignoring.");
  else if (strequals(recv_action, "remap"))
    {
      if (received.payload_size == 0)
	return eprint("received keycode remap request without a payload, ignoring."), 0;
      
      with_mutex (mapping_mutex,
		  r = remap(received.payload, received.payload_size);
		  if (r)  r = errno ? errno : -1;
		  );
      fail_if (errno = (r == -1 ? 0 : r), r);
    }
  else if (strequals(recv_action, "reset"))
    {
      with_mutex (mapping_mutex,
		  free(mapping);
		  mapping_size = 0;
		  );
    }
  else if (strequals(recv_action, "query"))
    {
      if (strequals(recv_client_id, "0:0"))
	return eprint("received information request from an anonymous client, ignoring."), 0;
      
      fail_if (mapping_query(recv_client_id, recv_message_id));
    }
  else
    eprint("received keycode map request with invalid action, ignoring.");
  
  return 0;
 fail:
  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 (kbd_thread_started)
    if (pthread_equal(current_thread, kbd_thread) == 0)
      pthread_kill(kbd_thread, signo);
}


/**
 * Acquire access of the keyboard's LED:s
 * 
 * @return  Zero on success, -1 on error
 */
int open_leds(void)
{
#ifdef __sparc__
  fail_if ((ledfd = open(SPARC_KBD, O_RDONLY)) < 0);
  if (ioctl(ledfd, GET_LED, &saved_leds) < 0)
    {
      xclose(ledfd);
      fail_if (1);
    }
  return 0;
#else
  fail_if (ioctl(ledfd, GET_LED, &saved_leds));
#endif
  return 0;
 fail:
  return -1;
}


/**
 * Release access of the keyboard's LED:s
 */
void close_leds(void)
{
  if (ioctl(ledfd, SET_LED, saved_leds) < 0)
    xperror(*argv);
#ifdef __sparc__
  xclose(ledfd);
#endif
}


/**
 * Get active LED:s on the keyboard
 * 
 * @return  Active LED:s, -1 on error
 */
int get_leds(void)
{
  int leds;
  fail_if (ioctl(ledfd, GET_LED, &leds) < 0);
#ifdef __sparc__
  leds &= 15;
#endif
  return leds;
 fail:
  return -1;
}


/**
 * Set active LED:s on the keyboard
 * 
 * @param   leds  Active LED:s
 * @return        Zero on success, -1 on error
 */
int set_leds(int leds)
{
  fail_if (ioctl(ledfd, SET_LED, leds));
  return 0;
 fail:
  return -1;
}


/**
 * Acquire access of keyboard input
 * 
 * @return  Zero on success, -1 on error
 */
int open_input(void)
{
  struct termios stty;
  if (tcgetattr(STDIN_FILENO, &saved_stty) < 0)
    return -1;
  stty = saved_stty;
  stty.c_lflag &= (tcflag_t)~(ECHO | ICANON | ISIG);
  stty.c_iflag = 0;
  fail_if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &stty) < 0);
  /* K_MEDIUMRAW: utilise keyboard drivers, but not layout */
  if ((ioctl(STDIN_FILENO, KDGKBMODE, &saved_kbd_mode) < 0) ||
      (ioctl(STDIN_FILENO, KDSKBMODE, K_MEDIUMRAW) < 0))
    fail_if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_stty));
  return 0;
 fail:
  xperror(*argv);
  return -1;
}


/**
 * Release access of keyboard input
 */
void close_input(void)
{
  if (ioctl(STDIN_FILENO, KDSKBMODE, saved_kbd_mode) < 0)
    xperror(*argv);
  if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_stty) < 0)
    xperror(*argv);
}


/**
 * Broadcast a keyboard input event
 * 
 * @param   scancode  The scancode
 * @param   trio      Whether the scancode has three integers rather than one
 * @return            Zero on success, -1 on error
 */
int send_key(int* restrict scancode, int trio)
{
  int r, keycode, released = (scancode[0] & 0x80) == 0x80;
  uint32_t msgid;
  scancode[0] &= 0x7F;
  if (trio)
    {
      keycode = (scancode[1] &= 0x7F) << 7;
      keycode |= (scancode[2] &= 0x7F);
    }
  else
    keycode = scancode[0];
  
  with_mutex (mapping_mutex,
	      if ((size_t)keycode < mapping_size)
		keycode = mapping[keycode];
	      );
  
  with_mutex (send_mutex,
	      msgid = message_id;
	      message_id = message_id == INT32_MAX ? 0 : (message_id + 1);
	      );
  
  if (trio)
    sprintf(key_send_buffer,
	    "Command: key-sent\n"
	    "Scancode: %i %i %i\n"
	    "Keycode: %i\n"
	    "Released: %s\n"
	    "Keyboard: " KEYBOARD_ID "\n"
	    "Message ID: %" PRIu32 "\n"
	    "\n",
	    scancode[0], scancode[1], scancode[2], keycode,
	    released ? "yes" : "no", msgid);
  else
    sprintf(key_send_buffer,
	    "Command: key-sent\n"
	    "Scancode: %i\n"
	    "Keycode: %i\n"
	    "Released: %s\n"
	    "Keyboard: " KEYBOARD_ID "\n"
	    "Message ID: %" PRIu32 "\n"
	    "\n",
	    scancode[0], keycode,
	    released ? "yes" : "no", msgid);
  
  with_mutex (send_mutex,
	      r = full_send(key_send_buffer, strlen(key_send_buffer));
	      if (r)  r = errno ? errno : 0;
	      );
  fail_if (errno = (r == -1 ? 0 : r), r);
  return 0;
 fail:
  return -1;
}


/**
 * Fetch and broadcast keys until interrupted
 * 
 * @return  Zero on success, -1 on error
 */
int fetch_keys(void)
{
#ifdef DEBUG
  int consecutive_escapes = 0;
#endif
  int c;
  ssize_t r;
  
  for (;;)
    {
      r = read(STDIN_FILENO, &c, sizeof(int));
      if (r <= 0)
	{
	  if (r == 0)
	    {
	      raise(SIGTERM);
	      errno = 0;
	    }
	  break;
	}
      
#ifdef DEBUG
      if ((c & 0x7F) == 1) /* Exit with ESCAPE, ESCAPE, ESCAPE */
	{
	  if (++consecutive_escapes >= 2 * 3)
	    {
	      raise(SIGTERM);
	      errno = 0;
	      break;
	    }
	}
      else
	consecutive_escapes = 0;
#endif
      
    redo:
      scancode_buf[scancode_ptr] = c;
      if (scancode_ptr == 0)
	{
	  if ((c & 0x7F) == 0)
	    scancode_ptr++;
	  else
	    send_key(scancode_buf, 0);
	}
      else if (scancode_ptr == 1)
	{
	  if ((c & 0x80) == 0)
	    {
	      scancode_ptr = 0;
	      goto redo;
	    }
	  scancode_ptr++;
	}
      else
	{
	  scancode_ptr = 0;
	  if ((c & 0x80) == 0)
	    {
	      send_key(scancode_buf + 1, 0);
	      goto redo;
	    }
	  send_key(scancode_buf, 1);
	}
    }
  
  fail_if (errno);
  return 0;
 fail:
  return -1;
}


/**
 * Send a response with an error number
 * 
 * @param   error            The error number
 * @param   recv_client_id   The client's ID
 * @param   recv_message_id  The message ID of the message the client sent
 * @return                   Zero on success, -1 on error
 */
int send_errno(int error, const char* recv_client_id, const char* recv_message_id)
{
  size_t n = 69 + strlen(recv_client_id) + strlen(recv_message_id) + 3 * sizeof(int);
  int r;
  
  fail_if (ensure_send_buffer_size(n + 1) < 0);
  
  with_mutex (send_mutex,
	      sprintf(send_buffer,
		      "Command: error\n"
		      "To: %s\n"
		      "In response to: %s\n"
		      "Message ID: %" PRIu32 "\n"
		      "Error: %i\n"
		      "\n",
		      recv_client_id, recv_message_id, message_id, error);
	      
	      message_id = message_id == INT32_MAX ? 0 : (message_id + 1);
	      r = full_send(send_buffer, strlen(send_buffer));
	      if (r)  r = errno ? errno : -1;
	      );
  fail_if (errno = (r == -1 ? 0 : r), r);
  return 0;
 fail:
  return -1;
}


/**
 * Attempt to shrink `mapping`
 */
void shrink_map(void)
{
  size_t i, greatest_mapping = 0;
  int* old;
  
  for (i = mapping_size; i > 0; i--)
    if (mapping[i] != (int)i)
      {
	greatest_mapping = i;
	break;
      }
  
  if (greatest_mapping == 0)
    {
      if (mapping[0] == 0)
	{
	  free(mapping);
	  mapping_size = 0;
	}
    }
  else if (greatest_mapping + 1 < mapping_size)
    {
      if (old = mapping, xrealloc(mapping, greatest_mapping + 1, int))
	{
	  mapping = old;
	  xperror(*argv);
	}
      else
	mapping_size = greatest_mapping + 1;
    }
}

/**
 * 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;
  size_t i;
  (void) signo;
  iprintf("next message ID: %" PRIu32, message_id);
  iprintf("connected: %s", connected ? "yes" : "no");
  iprintf("LED FD: %i", ledfd);
  iprintf("saved LED:s: %i", saved_leds);
  iprintf("scancode buffer: %i, %i, %i", scancode_buf[0], scancode_buf[1], scancode_buf[2]);
  iprintf("scancode buffer pointer: %i", scancode_ptr);
  iprintf("saved keyboard mode: %i", saved_kbd_mode);
  iprintf("send buffer size: %zu bytes", send_buffer_size);
  iprintf("keyboard thread started: %s", connected ? "yes" : "no");
  iprintf("keycode remapping tabel size: %zu", mapping_size);
  iprint("keycode remapping tabel:");
  for (i = 0; i < mapping_size; i++)
    if ((int)i != mapping[i])
      iprintf("  %zu -> %i", i, mapping[i]);
  SIGHANDLER_END;
}