summaryrefslogblamecommitdiff
path: root/src/lib/elput/elput_evdev.c
blob: 91bbbb2a811b2b6d3b221a5b1172d67838e8567b (plain) (tree)
1
2
3
4
5
6
7
8
9
10


                          
                                   

                         
            


           

                                   








                                                 
                
 
                                                                      

 








                                                  

                                                                       

 
           














                                                        


                                                                 
                       






















                                                               

                                                  






                                                 



















                                                                         

 
                            
                                                

                             












                                                                         

                                                             






                                                                         






                                                                     
               


           
                                                 


                                

                                      


              








                                                                      














                                                                    
                                             














                                           
 


              



                                           
 



                                        
 


                                                                               
                                                                       
                          



                                                                                

                               

 



                                                         








                                                           

                                  






                                


                                                                        









                                                     
                                                              
 

                               







                           
                                                    


























                                                
           





                                





                                                                   
           
















                                                                                                                                                                          



                                        
                              
                                 
 


                                                 

                                           
                                                                     
       
                                                                   


           














                                                               















                                                                                                                    

                                                                 








                                        


                                         
                                    
                                  
                               


                                   

                              
 





                                                                           

                                                             
      

                                         
       


                                                               
                           

              
                                               


               




                                          
 
                               
 
                                         


















                                                                                 

                                         






                                       













                                                        

                                                                                                



























                                                                                           
                         
 
                                         

                                             































                                                                                                    
                                                                                                              
                       
                    


                                                                                                
                          
                                  
                                          
                                                                     
                          
                                       
                          







                                                   
      

       
                                                                     



                                                       


            

























                                                                                    


                                                                              
           

















                                                                                       










                                                                       
           




                                                                                     
                                                 



                            
                                                               







                                                
                                                       
                                                        
                                                                              





                                                                 
                                                                             


                                                    

                                                       
                                                                               




                                                          





                                                                        

                                                     
                                                                   
 

                                           
 


                                                                        

                                                                    
 
                                                       


                                                            
                                                                    






                                                                       
                                                                      

                                              
                                                                                      
                                                                                   

                                                                             

                                                                          
                                             








                                           








                                          
                              












                                 

                                  






                               
                       

























                                          
                         












                                   

                                  























                                 













                                                  
                                  
 

                            


                     

                                                                 


                     

                                                                 
 

                            
 


                                                  

                                  
                               
                                 
 



                                     











                                                        
                                      
                         



                                           
 
                                                                     

 
           
















                                                                                         















                                                                       




                                                                                   
                      






                                              













                                             


                                                           
                                   








                                                                                       
               






                                              

                  
                                                                        



                                     
                                                                        

                                     




                                                                    
                                                                        

















                                                                          


                                                  

                                  
                               
                                 
 



                                     





                                             
                                      
                         



                                           











                                                 
                                                                              
       
                                                                            

 





























                                                                    































                                                                                   
                                              
                                                              


















                                                               


                                                 

                                  
                              
                                 
 



                                     








                                                       
                                                                      











                                                                                          
                                                                          




                                                                 

              



















                                                                                    




















                                                                              







                                                           
                                              










                                                    


                                                 
                                    

                       



                                       






                                        
                                        






                             





                                                                             





                                         















                                                  
                              
                                 
 

                                          

                      






                                        
                                        
                         



                                             
 
                                                                     


           



                                                                                






                                                
                                                      
                                                           
 



                                                            


                                                                    

                                                                                                   
 
                                     
      

                                               

      

                   
                           
                                                         


                          
                                     

                                               
                                                 















                                                                              
                                                      
                                                           
 
                           
                                                       


           










                                                                                  



                                                            


                                                                    

                                                                                                   
 
                                                      
                                                           
 
                           

 
    














                                                                       
                                                                                      

























                                                                                
           



                                                  
                                   




                  




                                                                                         
                               
                                       
                  




                                                     



                                                                  
 


                                                       
                                             




                                                       
                                             

              
                                               



                                                                   

                                                              







































                                                                            

                                                               
 




                                                                    











                                                         
                              
                                 






                                                                        








                                                                                        



                                                                        







                                                               

                                                   


                                           
           








                                                       






                                                                                 
                      


                                                                                 
                                                                     

 










                                                  
                                                                      

                                         

                                                                         

                                                  

                                                                             

                                         

                                                                         

                                       

                                                                       

                                     
                                                                 

                                       
                                                                   

                                   
                                                               
              








                                                                             


                                                                     






                
                           
 
                                                   
                                               








                                                                      
                     



                                          
                  

                         

                                      
 
                                                   

                                              

                                                       

                                                                          

                                                                                



                                                                            

                                                                              
                                             
                                                                               
                                                                             
                                                                              
                                                
                                              
                         

                                                                         
                                           
                                                                           

                                                                       
                       











                                                                              






                                         

                          
 
                                              
                                  
                                               
                                   
                                            



                                           


                                                                  











                                            


                                                                       
                                               
                                                                 
                                                    

                                   
















                                                              
 





                                              






















                                             
#include "elput_private.h"

static void
_seat_event_free(void *d, void *ev)
{
   _udev_seat_destroy(d);
   free(ev);
}

static void
_seat_caps_update(Elput_Seat *seat)
{
   Elput_Event_Seat_Caps *ev;

   ev = calloc(1, sizeof(Elput_Event_Seat_Caps));
   if (!ev) return;

   ev->pointer_count = seat->count.ptr;
   ev->keyboard_count = seat->count.kbd;
   ev->touch_count = seat->count.touch;
   ev->seat = seat;
   seat->refs++;

   ecore_event_add(ELPUT_EVENT_SEAT_CAPS, ev, _seat_event_free, seat);
}

static void
_seat_frame_send(Elput_Seat *seat)
{
   Elput_Event_Seat_Frame *ev;

   ev = calloc(1, sizeof(Elput_Event_Seat_Frame));
   if (!ev) return;

   ev->seat = seat;
   seat->refs++;
   ecore_event_add(ELPUT_EVENT_SEAT_FRAME, ev, _seat_event_free, seat);
}

static void
_evdev_leds_update(Elput_Device *edev, Elput_Leds leds)
{
   enum libinput_led input_leds = 0;

   if (leds & ELPUT_LED_NUM)
     input_leds |= LIBINPUT_LED_NUM_LOCK;
   if (leds & ELPUT_LED_CAPS)
     input_leds |= LIBINPUT_LED_CAPS_LOCK;
   if (leds & ELPUT_LED_SCROLL)
     input_leds |= LIBINPUT_LED_SCROLL_LOCK;

   libinput_device_led_update(edev->device, input_leds);
}

static void
_keyboard_modifiers_update(Elput_Keyboard *kbd, Elput_Seat *seat)
{
   xkb_mod_mask_t mask;
   Elput_Leds leds = 0;

   kbd->mods.depressed =
     xkb_state_serialize_mods(kbd->state, XKB_STATE_DEPRESSED);
   kbd->mods.latched =
     xkb_state_serialize_mods(kbd->state, XKB_STATE_LATCHED);
   kbd->mods.locked =
     xkb_state_serialize_mods(kbd->state, XKB_STATE_LOCKED);
   kbd->mods.group =
     xkb_state_serialize_mods(kbd->state, XKB_STATE_EFFECTIVE);

   mask = (kbd->mods.depressed | kbd->mods.latched);

   seat->modifiers = 0;
   if (mask & kbd->info->mods.ctrl)
     seat->modifiers |= ECORE_EVENT_MODIFIER_CTRL;
   if (mask & kbd->info->mods.alt)
     seat->modifiers |= ECORE_EVENT_MODIFIER_ALT;
   if (mask & kbd->info->mods.shift)
     seat->modifiers |= ECORE_EVENT_MODIFIER_SHIFT;
   if (mask & kbd->info->mods.super)
     seat->modifiers |= ECORE_EVENT_MODIFIER_WIN;
   if (mask & kbd->info->mods.altgr)
     seat->modifiers |= ECORE_EVENT_MODIFIER_ALTGR;
   if (mask & kbd->info->mods.caps)
     seat->modifiers |= ECORE_EVENT_MODIFIER_CAPS;
   if (mask & kbd->info->mods.num)
     seat->modifiers |= ECORE_EVENT_MODIFIER_NUM;

   if (kbd->mods.locked & kbd->info->mods.caps)
     seat->modifiers |= ECORE_EVENT_LOCK_CAPS;
   if (kbd->mods.locked & kbd->info->mods.num)
     seat->modifiers |= ECORE_EVENT_LOCK_NUM;

   if (xkb_state_led_index_is_active(kbd->state, kbd->info->leds.num))
     leds |= ELPUT_LED_NUM;

   if (xkb_state_led_index_is_active(kbd->state, kbd->info->leds.caps))
     leds |= ELPUT_LED_CAPS;

   if (xkb_state_led_index_is_active(kbd->state, kbd->info->leds.scroll))
     leds |= ELPUT_LED_SCROLL;

   if (kbd->leds != leds)
     {
        Eina_List *l;
        Elput_Device *edev;

        EINA_LIST_FOREACH(seat->devices, l, edev)
          _evdev_leds_update(edev, leds);

        kbd->leds = leds;
     }
}

static Elput_Keyboard_Info *
_keyboard_info_create(struct xkb_keymap *keymap)
{
   Elput_Keyboard_Info *info;

   info = calloc(1, sizeof(Elput_Keyboard_Info));
   if (!info) return NULL;

   info->keymap.map = xkb_keymap_ref(keymap);
   info->refs = 1;

   info->mods.super =
     1 << xkb_keymap_mod_get_index(info->keymap.map, XKB_MOD_NAME_LOGO);
   info->mods.shift =
     1 << xkb_keymap_mod_get_index(info->keymap.map, XKB_MOD_NAME_SHIFT);
   info->mods.caps =
     1 << xkb_keymap_mod_get_index(info->keymap.map, XKB_MOD_NAME_CAPS);
   info->mods.num =
     1 << xkb_keymap_mod_get_index(info->keymap.map, "Mod2");
   info->mods.ctrl =
     1 << xkb_keymap_mod_get_index(info->keymap.map, XKB_MOD_NAME_CTRL);
   info->mods.alt =
     1 << xkb_keymap_mod_get_index(info->keymap.map, XKB_MOD_NAME_ALT);
   info->mods.altgr =
     1 << xkb_keymap_mod_get_index(info->keymap.map, "ISO_Level3_Shift");

   info->leds.num =
     xkb_keymap_led_get_index(info->keymap.map, XKB_LED_NAME_NUM);
   info->leds.caps =
     xkb_keymap_led_get_index(info->keymap.map, XKB_LED_NAME_CAPS);
   info->leds.scroll =
     xkb_keymap_led_get_index(info->keymap.map, XKB_LED_NAME_SCROLL);

   return info;
}

static void
_keyboard_info_destroy(Elput_Keyboard_Info *info)
{
   if (--info->refs > 0) return;

   xkb_keymap_unref(info->keymap.map);

   free(info);
}

/**
 * @brief Creates the XKB context and keymap.
 *
 * @param kbd The keyboard config to be set up for XKB.
 * @return EINA_TRUE if build successful, EINA_FALSE otherwise.
 *
 * Sets default settings for the keyboard if not set by the system.
 * Assumes evdev rules, with a pc105 model keyboard and US key layout.
 */
static Eina_Bool
_keyboard_global_build(Elput_Keyboard *kbd)
{
   struct xkb_keymap *keymap;

   kbd->context = xkb_context_new(0);
   if (!kbd->context) return EINA_FALSE;

   if (!kbd->names.rules) kbd->names.rules = strdup("evdev");
   if (!kbd->names.model) kbd->names.model = strdup("pc105");
   if (!kbd->names.layout) kbd->names.layout = strdup("us");

   keymap = xkb_keymap_new_from_names(kbd->context, &kbd->names, 0);
   if (!keymap) return EINA_FALSE;

   kbd->info = _keyboard_info_create(keymap);
   xkb_keymap_unref(keymap);

   if (!kbd->info) return EINA_FALSE;
   return EINA_TRUE;
}

static Elput_Keyboard *
_keyboard_create(Elput_Seat *seat)
{
   Elput_Keyboard *kbd;

   kbd = calloc(1, sizeof(Elput_Keyboard));
   if (!kbd) return NULL;

   kbd->seat = seat;

   return kbd;
}

static void
_keyboard_compose_init(Elput_Keyboard *kbd)
{
   const char *locale;

   if (!(locale = getenv("LC_ALL")))
     if (!(locale = getenv("LC_CTYPE")))
       if (!(locale = getenv("LANG")))
         locale = "C";

   if (kbd->compose_table) xkb_compose_table_unref(kbd->compose_table);
   kbd->compose_table = xkb_compose_table_new_from_locale(kbd->context, locale,
     XKB_COMPOSE_COMPILE_NO_FLAGS);
   if (kbd->compose_state) xkb_compose_state_unref(kbd->compose_state);
   if (kbd->compose_table)
     {
        kbd->compose_state =
          xkb_compose_state_new(kbd->compose_table, XKB_COMPOSE_STATE_NO_FLAGS);
     }
   else
     kbd->compose_state = NULL;
}

/**
 * Create a new keyboard object for the seat, initialized
 * to the given keymap.
 */
static Eina_Bool
_keyboard_init(Elput_Seat *seat, struct xkb_keymap *keymap)
{
   Elput_Keyboard *kbd;

   if (seat->kbd)
     {
        seat->count.kbd += 1;
        if (seat->count.kbd == 1)
          _seat_caps_update(seat);
        return EINA_TRUE;
     }

   kbd = _keyboard_create(seat);
   if (!kbd) return EINA_FALSE;

   if (keymap)
     {
        if (seat->manager->cached.keymap == keymap)
          kbd->context = xkb_context_ref(seat->manager->cached.context);
        kbd->info = _keyboard_info_create(keymap);
        if (!kbd->info) goto err;
     }
   else
     {
        if (!_keyboard_global_build(kbd)) goto err;
        kbd->info->refs++;
     }

   kbd->state = xkb_state_new(kbd->info->keymap.map);
   if (!kbd->state) goto err;
   kbd->maskless_state = xkb_state_new(kbd->info->keymap.map);

   _keyboard_compose_init(kbd);

   seat->kbd = kbd;
   seat->count.kbd = 1;

   _seat_caps_update(seat);

   return EINA_TRUE;

err:
   if (kbd->info) _keyboard_info_destroy(kbd->info);
   free(kbd);
   return EINA_FALSE;
}

static void
_keyboard_state_reset(Elput_Keyboard *kbd)
{
   struct xkb_state *state;

   state = xkb_state_new(kbd->info->keymap.map);
   if (!state) return;

   xkb_state_unref(kbd->state);
   kbd->state = state;
}

static void
_keyboard_release(Elput_Seat *seat)
{
   seat->count.kbd--;
   if (seat->count.kbd == 0)
     {
        _keyboard_state_reset(seat->kbd);
        _seat_caps_update(seat);
     }
}

static void
_event_free(void *dev, void *ev)
{
   if (dev) efl_unref(dev);
   free(ev);
}

/**
 * Creates an event key object and generates a corresponding event.
 *
 * An ECORE_EVENT_KEY_DOWN event is generated on state
 * LIBINPUT_KEY_STATE_PRESSED, or ECORE_EVENT_KEY_DOWN otherwise.
 */
static void
_keyboard_key_send(Elput_Device *dev, enum libinput_key_state state, const char *keyname, const char *key, const char *compose, unsigned int code, unsigned int timestamp)
{
   Ecore_Event_Key *ev;

   ev = calloc(1, sizeof(Ecore_Event_Key) + strlen(key) + strlen(keyname) + 
               ((compose[0] != '\0') ? strlen(compose) : 0) + 3);
   if (!ev) return;

   ev->keyname = (char *)(ev + 1);
   ev->key = ev->keyname + strlen(keyname) + 1;
   ev->compose = strlen(compose) ? ev->key + strlen(key) + 1 : NULL;
   ev->string = ev->compose;

   strcpy((char *)ev->keyname, keyname);
   strcpy((char *)ev->key, key);
   if (strlen(compose)) strcpy((char *)ev->compose, compose);

   ev->keycode = code;
   ev->modifiers = dev->seat->modifiers;
   ev->timestamp = timestamp;
   ev->same_screen = 1;
   ev->dev = dev->evas_device;
   if (ev->dev) efl_ref(ev->dev);

   ev->window = dev->seat->manager->window;
   ev->event_window = dev->seat->manager->window;
   ev->root_window = dev->seat->manager->window;

   if (state == LIBINPUT_KEY_STATE_PRESSED)
     ecore_event_add(ECORE_EVENT_KEY_DOWN, ev, _event_free, ev->dev);
   else
     ecore_event_add(ECORE_EVENT_KEY_UP, ev, _event_free, ev->dev);
}

static void
_keyboard_modifiers_send(Elput_Keyboard *kbd)
{
   Elput_Event_Modifiers_Send *ev;

   ev = calloc(1, sizeof(Elput_Event_Modifiers_Send));
   if (!ev) return;

   ev->depressed = kbd->mods.depressed;
   ev->latched = kbd->mods.latched;
   ev->locked = kbd->mods.locked;
   ev->group = kbd->mods.group;

   ecore_event_add(ELPUT_EVENT_MODIFIERS_SEND, ev, NULL, NULL);
}

static Eina_Bool
_keyboard_state_update(Elput_Keyboard *kbd, struct xkb_keymap *map, xkb_mod_mask_t *latched, xkb_mod_mask_t *locked)
{
   struct xkb_state *state, *maskless_state;

   state = xkb_state_new(map);
   if (!state) return EINA_FALSE;
   maskless_state = xkb_state_new(map);
   if (!maskless_state)
     {
        xkb_state_unref(state);
        return EINA_FALSE;
     }

   *latched = xkb_state_serialize_mods(kbd->state, XKB_STATE_MODS_LATCHED);
   *locked = xkb_state_serialize_mods(kbd->state, XKB_STATE_MODS_LOCKED);
   xkb_state_update_mask(state, 0, *latched, *locked,
                         kbd->seat->manager->cached.group, 0, 0);

   xkb_state_unref(kbd->state);
   kbd->state = state;
   xkb_state_unref(kbd->maskless_state);
   kbd->maskless_state = maskless_state;
   return EINA_TRUE;
}

void
_keyboard_keymap_update(Elput_Seat *seat)
{
   Elput_Keyboard *kbd;
   Elput_Keyboard_Info *info = NULL;
   xkb_mod_mask_t latched, locked;
   Eina_Bool state = EINA_TRUE;

   kbd = _evdev_keyboard_get(seat);
   if (!kbd) return;
   kbd->pending_keymap = 1;
   if (kbd->key_count) return;

   if (kbd->seat->manager->cached.keymap)
     {
        if (kbd->context) xkb_context_unref(kbd->context);
        kbd->context = xkb_context_ref(kbd->seat->manager->cached.context);
        info = _keyboard_info_create(kbd->seat->manager->cached.keymap);
        if (!info) return;
        state = _keyboard_state_update(kbd, info->keymap.map,
                                       &latched, &locked);
     }
   else if (!_keyboard_global_build(kbd))
     return;
   else
     state = _keyboard_state_update(kbd, kbd->info->keymap.map,
                                    &latched, &locked);

   kbd->pending_keymap = 0;
   if (!state)
     {
        if (info) _keyboard_info_destroy(info);
        return;
     }

   if (info)
     {
        _keyboard_info_destroy(kbd->info);
        kbd->info = info;
     }

   _keyboard_compose_init(kbd);

   _keyboard_modifiers_update(kbd, seat);

   if ((!latched) && (!locked)) return;

   _keyboard_modifiers_send(kbd);
}

void
_keyboard_group_update(Elput_Seat *seat)
{
   Elput_Keyboard *kbd;
   xkb_mod_mask_t latched, locked;
   Eina_Bool state;

   kbd = _evdev_keyboard_get(seat);
   if (!kbd) return;

   state = _keyboard_state_update(kbd, kbd->info->keymap.map, &latched, &locked);
   if (!state) return;
   _keyboard_compose_init(kbd);

   _keyboard_modifiers_update(kbd, seat);

   if ((!latched) && (!locked)) return;

   _keyboard_modifiers_send(kbd);
}

static int
_keyboard_remapped_key_get(Elput_Device *edev, int code)
{
   void *ret = NULL;

   if (!edev) return code;
   if (!edev->key_remap) return code;
   if (!edev->key_remap_hash) return code;

   ret = eina_hash_find(edev->key_remap_hash, &code);
   if (ret) code = (int)(intptr_t)ret;
   return code;
}

static int
_keyboard_keysym_translate(xkb_keysym_t keysym, unsigned int modifiers, char *buffer, int bytes)
{
/* this function is copied, with slight changes in variable names, from KeyBind.c in libX11
 * the license from that file can be found below:
 */
/*

Copyright 1985, 1987, 1998  The Open Group

Permission to use, copy, modify, distribute, and sell this software and its
documentation for any purpose is hereby granted without fee, provided that
the above copyright notice appear in all copies and that both that
copyright notice and this permission notice appear in supporting
documentation.

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Except as contained in this notice, the name of The Open Group shall not be
used in advertising or otherwise to promote the sale, use or other dealings
in this Software without prior written authorization from The Open Group.

*/
   if (!keysym) return 0;

   /* check for possible control codes */
   if (modifiers & ECORE_EVENT_MODIFIER_CTRL)
     {
        Eina_Bool valid_control_code = EINA_TRUE;
        unsigned long hbytes = 0;
        unsigned char c;

        hbytes = (keysym >> 8);
        if (!(bytes &&
        ((hbytes == 0) ||
        ((hbytes == 0xFF) &&
        (((keysym >= XKB_KEY_BackSpace) && (keysym <= XKB_KEY_Clear)) ||
        (keysym == XKB_KEY_Return) ||
        (keysym == XKB_KEY_Escape) ||
        (keysym == XKB_KEY_KP_Space) ||
        (keysym == XKB_KEY_KP_Tab) ||
        (keysym == XKB_KEY_KP_Enter) ||
        ((keysym >= XKB_KEY_KP_Multiply) && (keysym <= XKB_KEY_KP_9)) ||
        (keysym == XKB_KEY_KP_Equal) ||
        (keysym == XKB_KEY_Delete))))))
          return 0;

        if (keysym == XKB_KEY_KP_Space)
          c = (XKB_KEY_space & 0x7F);
        else if (hbytes == 0xFF)
          c = (keysym & 0x7F);
        else
          c = (keysym & 0xFF);

        /* We are building here a control code
           for more details, read:
           https://en.wikipedia.org/wiki/C0_and_C1_control_codes#C0_.28ASCII_and_derivatives.29
         */

        if (((c >= '@') && (c <= '_')) || /* those are the one defined in C0 with capital letters */
             ((c >= 'a') && (c <= 'z')) ||  /* the lowercase symbols (not part of the standard, but useful) */
              c == ' ')
          c &= 0x1F;
        else if (c == '\x7f')
          c = '\177';
        /* following codes are alternatives, they are longer here, i dont want to change them */
        else if (c == '2')
          c = '\000'; /* 0 code */
        else if ((c >= '3') && (c <= '7'))
          c -= ('3' - '\033'); /* from escape to unitseperator code*/
        else if (c == '8')
          c = '\177'; /* delete code */
        else if (c == '/')
          c = '_' & 0x1F; /* unit seperator code */
        else
          valid_control_code = EINA_FALSE;

        if (valid_control_code)
          buffer[0] = c;
        else
          return 0;
     }
   else
     {
        /* if its not a control code, try to produce useful output */
        if (!xkb_keysym_to_utf8(keysym, buffer, bytes))
          return 0;
     }

   return 1;
}

/* from weston/clients/window.c */
/* Translate symbols appropriately if a compose sequence is being entered */
static xkb_keysym_t
process_key_press(xkb_keysym_t sym, Elput_Keyboard *kbd)
{
   if (!kbd->compose_state)
     return sym;
   if (sym == XKB_KEY_NoSymbol)
     return sym;
   if (xkb_compose_state_feed(kbd->compose_state, sym) != XKB_COMPOSE_FEED_ACCEPTED)
     return sym;

   switch (xkb_compose_state_get_status(kbd->compose_state))
     {
      case XKB_COMPOSE_COMPOSING:
        return XKB_KEY_NoSymbol;
      case XKB_COMPOSE_COMPOSED:
        return xkb_compose_state_get_one_sym(kbd->compose_state);
      case XKB_COMPOSE_CANCELLED:
        return XKB_KEY_NoSymbol;
      case XKB_COMPOSE_NOTHING:
      default: break;
     }
   return sym;
}

/**
 * Retrieve the string name ('q', 'bracketleft', etc.) for a given key symbol.
 */
static void
_elput_symbol_rep_find(xkb_keysym_t keysym, char *buffer, int size, unsigned int code)
{
    int n = 0;

    n = xkb_keysym_to_utf8(keysym, buffer, size);

    /* check if we are a control code */
    if (n > 0 && !(
        (buffer[0] > 0x0 && buffer[0] < 0x20) || /* others 0x0 to 0x1F control codes */
        buffer[0] == 0x7F)) /*delete control code */
      return;

    if (xkb_keysym_get_name(keysym, buffer, size) != 0)
      return;

    snprintf(buffer, size, "Keycode-%u", code);
}

/**
 * Handle keyboard events emitted by libinput.
 *
 * Processes a single key pressed / released event from libinput.  The
 * key code will be remapped to another code if one has been registered
 * (see elput_input_key_remap_enable()), and then XKB notified of the
 * keyboard status change.  XKB translates the key code into the
 * appropriate symbol for the current keyboard layout.  Compose
 * translation is performed, if appropriate.  An up or down event is
 * then generated for the processed key via _keyboard_key_send().
 */
static void
_keyboard_key(struct libinput_device *idevice, struct libinput_event_keyboard *event)
{
   Elput_Device *dev;
   Elput_Keyboard *kbd;
   enum libinput_key_state state;
   xkb_keysym_t sym_name, sym = XKB_KEY_NoSymbol;
   const xkb_keysym_t *syms;
   unsigned int code = 0;
   unsigned int nsyms;
   unsigned int timestamp;
   char key[256] = {0}, keyname[256] = {0}, compose[256] = {0};
   int count;

   dev = libinput_device_get_user_data(idevice);
   if (!dev) return;

   kbd = _evdev_keyboard_get(dev->seat);
   if (!kbd) return;

   /* Retrieve details about the event from libinput */
   state = libinput_event_keyboard_get_key_state(event);
   kbd->key_count = count = libinput_event_keyboard_get_seat_key_count(event);

   /* Ignore key events that are not seat wide state changes. */
   if (((state == LIBINPUT_KEY_STATE_PRESSED) && (count != 1)) ||
       ((state == LIBINPUT_KEY_STATE_RELEASED) && (count != 0)))
     return;

   /* Retrieve the code and remap it if a remap for it has been registered */
   code = libinput_event_keyboard_get_key(event);
   code = _keyboard_remapped_key_get(dev, code) + 8;

   timestamp = libinput_event_keyboard_get_time(event);

   /* Update the XKB keyboard state for the key that was pressed or released */
   if (state == LIBINPUT_KEY_STATE_PRESSED)
     xkb_state_update_key(kbd->state, code, XKB_KEY_DOWN);
   else
     xkb_state_update_key(kbd->state, code, XKB_KEY_UP);

   /* Apply the current keyboard state to translate the code for the key
    * that was struck into its effective symbol (after applying
    * modifiers like CAPSLOCK and so on).  We also use the maskless
    * keyboard state to lookup the underlying symbol name (i.e. without
    * applying modifiers).
    */
   nsyms = xkb_key_get_syms(kbd->state, code, &syms);
   if (nsyms == 1) sym = syms[0];
   sym_name = xkb_state_key_get_one_sym(kbd->maskless_state, code);

   if (state == LIBINPUT_KEY_STATE_PRESSED)
     sym = process_key_press(sym, kbd);

   /* Lookup the textual name ('q', 'space', 'bracketleft', etc.) of the
    * symbol and of the symbol name.
    */
   _elput_symbol_rep_find(sym, key, sizeof(key), code);
   _elput_symbol_rep_find(sym_name, keyname, sizeof(keyname), code);

   /* If no keyname was found, name it "Keycode-NNN" */
   if (keyname[0] == '\0')
     snprintf(keyname, sizeof(keyname), "Keycode-%u", code);

   /* If Shift key is active, downcase the keyname's first letter */
   if (xkb_state_mod_index_is_active(kbd->state, kbd->info->mods.shift,
                                     XKB_STATE_MODS_EFFECTIVE))
     {
        if (keyname[0] != '\0')
          keyname[0] = tolower(keyname[0]);
     }

   /* Update the seat's modifiers to match what's active in the kbd */
   _keyboard_modifiers_update(kbd, dev->seat);

   /* Translate the key symbol into a printable character in Unicode (UTF-8) format */
   _keyboard_keysym_translate(sym, dev->seat->modifiers, compose, sizeof(compose));

   /* Issue the appropriate key up or down event with all related key data */
   _keyboard_key_send(dev, state, keyname, key, compose, code, timestamp);

   if ((kbd->pending_keymap) && (count == 0))
     _keyboard_keymap_update(dev->seat);

   if (state == LIBINPUT_KEY_STATE_PRESSED)
     {
        kbd->grab.key = code;
        kbd->grab.timestamp = timestamp;
     }
}

static Elput_Pointer *
_pointer_create(Elput_Seat *seat)
{
   Elput_Pointer *ptr;

   ptr = calloc(1, sizeof(Elput_Pointer));
   if (!ptr) return NULL;

   ptr->seat = seat;
   ptr->mouse.threshold = 250;

   return ptr;
}

static Eina_Bool
_pointer_init(Elput_Seat *seat)
{
   Elput_Pointer *ptr;

   if (seat->ptr)
     {
        seat->count.ptr += 1;
        if (seat->count.ptr == 1)
          _seat_caps_update(seat);
        return EINA_TRUE;
     }

   ptr = _pointer_create(seat);
   if (!ptr) return EINA_FALSE;

   seat->ptr = ptr;
   seat->count.ptr = 1;
   ptr->pressure = 1.0;

   _seat_caps_update(seat);

   return EINA_TRUE;
}

static void
_pointer_release(Elput_Seat *seat)
{
   seat->count.ptr--;
   if (seat->count.ptr == 0)
     {
        seat->ptr->buttons = 0;
        _seat_caps_update(seat);
     }
}

static Elput_Touch *
_touch_create(Elput_Seat *seat)
{
   Elput_Touch *touch;

   touch = calloc(1, sizeof(Elput_Touch));
   if (!touch) return NULL;

   touch->seat = seat;
   touch->pressure = 1.0;

   return touch;
}

static Eina_Bool
_touch_init(Elput_Seat *seat)
{
   Elput_Touch *touch;

   if (seat->touch)
     {
        seat->count.touch += 1;
        if (seat->count.touch == 1)
          _seat_caps_update(seat);
        return EINA_TRUE;
     }

   touch = _touch_create(seat);
   if (!touch) return EINA_FALSE;

   seat->touch = touch;
   seat->count.touch = 1;

   _seat_caps_update(seat);

   return EINA_TRUE;
}

static void
_touch_release(Elput_Seat *seat)
{
   seat->count.touch--;
   if (seat->count.touch == 0)
     {
        seat->touch->points = 0;
        _seat_caps_update(seat);
     }
}

static void
_pointer_motion_send(Elput_Device *edev)
{
   Elput_Pointer *ptr;
   Elput_Keyboard *kbd;
   Elput_Touch *touch;
   Ecore_Event_Mouse_Move *ev;
   double x, y;

   ptr = _evdev_pointer_get(edev->seat);
   if (!ptr) return;

   ev = calloc(1, sizeof(Ecore_Event_Mouse_Move));
   if (!ev) return;
   edev->seat->pending_motion = 0;

   x = ptr->seat->pointer.x;
   y = ptr->seat->pointer.y;

   if (x < ptr->minx)
     x = ptr->minx;
   else if (x >= ptr->minx + ptr->seat->manager->input.pointer_w)
     x = ptr->minx + ptr->seat->manager->input.pointer_w - 1;

   if (y < ptr->miny)
     y = ptr->miny;
   else if (y >= ptr->miny + ptr->seat->manager->input.pointer_h)
     y = ptr->miny + ptr->seat->manager->input.pointer_h - 1;

   ptr->seat->pointer.x = x;
   ptr->seat->pointer.y = y;

   ev->window = edev->seat->manager->window;
   ev->event_window = edev->seat->manager->window;
   ev->root_window = edev->seat->manager->window;
   ev->timestamp = ptr->timestamp;
   ev->same_screen = 1;
   ev->dev = edev->evas_device;
   if (ev->dev) efl_ref(ev->dev);

   ev->x = ptr->seat->pointer.x;
   ev->y = ptr->seat->pointer.y;
   ev->root.x = ptr->seat->pointer.x;
   ev->root.y = ptr->seat->pointer.y;

   kbd = _evdev_keyboard_get(edev->seat);
   if (kbd) _keyboard_modifiers_update(kbd, edev->seat);

   ev->modifiers = edev->seat->modifiers;

   touch = _evdev_touch_get(edev->seat);
   if (touch) ev->multi.device = touch->slot;

   ev->multi.radius = 1;
   ev->multi.radius_x = 1;
   ev->multi.radius_y = 1;
   ev->multi.pressure = ptr->pressure;
   ev->multi.angle = 0.0;
   ev->multi.x = ptr->seat->pointer.x;
   ev->multi.y = ptr->seat->pointer.y;
   ev->multi.root.x = ptr->seat->pointer.x;
   ev->multi.root.y = ptr->seat->pointer.y;

   ecore_event_add(ECORE_EVENT_MOUSE_MOVE, ev, _event_free, ev->dev);
}

static void
_pointer_motion_relative_fake(struct libinput_event_pointer *event, double dx, double dy)
{
   Elput_Event_Pointer_Motion *ev;

   ev = calloc(1, sizeof(Elput_Event_Pointer_Motion));
   EINA_SAFETY_ON_NULL_RETURN(ev);

   ev->time_usec = libinput_event_pointer_get_time_usec(event);
   ev->dx = dx;
   ev->dy = dy;
   ev->dx_unaccel = dx;
   ev->dy_unaccel = dy;

   ecore_event_add(ELPUT_EVENT_POINTER_MOTION, ev, NULL, NULL);
}

static void
_pointer_motion_relative(struct libinput_event_pointer *event)
{
   Elput_Event_Pointer_Motion *ev;

   ev = calloc(1, sizeof(Elput_Event_Pointer_Motion));
   EINA_SAFETY_ON_NULL_RETURN(ev);

   ev->time_usec = libinput_event_pointer_get_time_usec(event);
   ev->dx = libinput_event_pointer_get_dx(event);
   ev->dy = libinput_event_pointer_get_dy(event);
   ev->dx_unaccel = libinput_event_pointer_get_dx_unaccelerated(event);
   ev->dy_unaccel = libinput_event_pointer_get_dy_unaccelerated(event);

   ecore_event_add(ELPUT_EVENT_POINTER_MOTION, ev, NULL, NULL);
}

static Eina_Bool
_pointer_motion(struct libinput_device *idev, struct libinput_event_pointer *event)
{
   Elput_Device *edev;
   Elput_Pointer *ptr;
   double dx, dy, tmp;

   edev = libinput_device_get_user_data(idev);
   if (!edev) return EINA_FALSE;

   ptr = _evdev_pointer_get(edev->seat);
   if (!ptr) return EINA_FALSE;

   dx = libinput_event_pointer_get_dx(event);
   dy = libinput_event_pointer_get_dy(event);

   if (edev->swap)
     {
        tmp = dx;
        dx = dy;
        dy = tmp;
     }
   if (edev->invert_x) dx *= -1;
   if (edev->invert_y) dy *= -1;

   ptr->seat->pointer.x += dx;
   ptr->seat->pointer.y += dy;
   ptr->timestamp = libinput_event_pointer_get_time(event);

   _pointer_motion_send(edev);
   _pointer_motion_relative(event);

   return EINA_TRUE;
}

static Eina_Bool
_pointer_motion_abs(struct libinput_device *idev, struct libinput_event_pointer *event)
{
   Elput_Device *edev;
   Elput_Pointer *ptr;
   double x, y;

   edev = libinput_device_get_user_data(idev);
   if (!edev) return EINA_FALSE;

   ptr = _evdev_pointer_get(edev->seat);
   if (!ptr) return EINA_FALSE;

   x = edev->absx;
   edev->absx =
     libinput_event_pointer_get_absolute_x_transformed(event, edev->ow);
   ptr->seat->pointer.x = edev->absx;

   y = edev->absy;
   edev->absy =
     libinput_event_pointer_get_absolute_y_transformed(event, edev->oh);
   ptr->seat->pointer.y = edev->absy;

   ptr->timestamp = libinput_event_pointer_get_time(event);

   /* TODO: these needs to run a matrix transform based on output */

   _pointer_motion_send(edev);
   _pointer_motion_relative_fake(event, edev->absx - x, edev->absy - y);

   return EINA_TRUE;
}

static void
_pointer_button_send(Elput_Device *edev, enum libinput_button_state state)
{
   Elput_Pointer *ptr;
   Elput_Keyboard *kbd;
   Elput_Touch *touch;
   Ecore_Event_Mouse_Button *ev;

   ptr = _evdev_pointer_get(edev->seat);
   if (!ptr) return;

   ev = calloc(1, sizeof(Ecore_Event_Mouse_Button));
   if (!ev) return;

   ev->window = edev->seat->manager->window;
   ev->event_window = edev->seat->manager->window;
   ev->root_window = edev->seat->manager->window;
   ev->timestamp = ptr->timestamp;
   ev->same_screen = 1;
   ev->dev = edev->evas_device;
   if (ev->dev) efl_ref(ev->dev);

   ev->x = ptr->seat->pointer.x;
   ev->y = ptr->seat->pointer.y;
   ev->root.x = ptr->seat->pointer.x;
   ev->root.y = ptr->seat->pointer.y;

   touch = _evdev_touch_get(edev->seat);
   if (touch) ev->multi.device = touch->slot;
   ev->multi.radius = 1;
   ev->multi.radius_x = 1;
   ev->multi.radius_y = 1;
   ev->multi.pressure = ptr->pressure;
   ev->multi.angle = 0.0;
   ev->multi.x = ptr->seat->pointer.x;
   ev->multi.y = ptr->seat->pointer.y;
   ev->multi.root.x = ptr->seat->pointer.x;
   ev->multi.root.y = ptr->seat->pointer.y;

   ev->buttons = ptr->buttons;

   ev->double_click = ptr->mouse.double_click;
   ev->triple_click = ptr->mouse.triple_click;

   kbd = _evdev_keyboard_get(edev->seat);
   if (kbd)
     _keyboard_modifiers_update(kbd, edev->seat);
   ev->modifiers = edev->seat->modifiers;

   if (state)
     ecore_event_add(ECORE_EVENT_MOUSE_BUTTON_DOWN, ev, _event_free, ev->dev);
   else
     ecore_event_add(ECORE_EVENT_MOUSE_BUTTON_UP, ev, _event_free, ev->dev);
}

static void
_pointer_click_update(Elput_Pointer *ptr, unsigned int btn)
{
   unsigned int current;

   current = ptr->timestamp;
   ptr->mouse.double_click = EINA_FALSE;
   ptr->mouse.triple_click = EINA_FALSE;

   if (((current - ptr->mouse.prev_time) <= ptr->mouse.threshold) &&
       (btn == ptr->mouse.prev_button))
     {
        ptr->mouse.double_click = EINA_TRUE;
        if (((current - ptr->mouse.last_time) <=
             (2 * ptr->mouse.threshold)) &&
            (btn == ptr->mouse.last_button))
          {
             ptr->mouse.triple_click = EINA_TRUE;
             ptr->mouse.prev_time = 0;
             ptr->mouse.last_time = 0;
             current = 0;
          }
     }

   ptr->mouse.last_time = ptr->mouse.prev_time;
   ptr->mouse.prev_time = current;
   ptr->mouse.last_button = ptr->mouse.prev_button;
   ptr->mouse.prev_button = ptr->buttons;
}

static Eina_Bool
_pointer_button(struct libinput_device *idev, struct libinput_event_pointer *event)
{
   Elput_Device *edev;
   Elput_Pointer *ptr;
   int count;
   enum libinput_button_state state;
   unsigned int btn;

   edev = libinput_device_get_user_data(idev);
   if (!edev) return EINA_FALSE;

   ptr = _evdev_pointer_get(edev->seat);
   if (!ptr) return EINA_FALSE;

   state = libinput_event_pointer_get_button_state(event);
   count = libinput_event_pointer_get_seat_button_count(event);

   /* Ignore button events that are not seat wide state changes. */
   if (((state == LIBINPUT_BUTTON_STATE_PRESSED) && (count != 1)) ||
       ((state == LIBINPUT_BUTTON_STATE_RELEASED) && (count != 0)))
     return EINA_FALSE;

   btn = libinput_event_pointer_get_button(event);

   btn = ((btn & 0x00F) + 1);
   if (btn == 3) btn = 2;
   else if (btn == 2) btn = 3;

   ptr->buttons = btn;
   ptr->timestamp = libinput_event_pointer_get_time(event);

   if (state) _pointer_click_update(ptr, btn);
   if (edev->seat->pending_motion) _pointer_motion_send(edev);

   _pointer_button_send(edev, state);

   return EINA_TRUE;
}

static void
_pointer_axis_send(Elput_Device *dev, int direction, int value)
{
   Elput_Pointer *ptr;
   Elput_Keyboard *kbd;
   Ecore_Event_Mouse_Wheel *ev;

   ptr = _evdev_pointer_get(dev->seat);
   if (!ptr) return;

   ev = calloc(1, sizeof(Ecore_Event_Mouse_Wheel));
   if (!ev) return;

   ev->window = dev->seat->manager->window;
   ev->event_window = dev->seat->manager->window;
   ev->root_window = dev->seat->manager->window;
   ev->timestamp = ptr->timestamp;
   ev->same_screen = 1;
   ev->dev = dev->evas_device;
   if (ev->dev) efl_ref(ev->dev);

   ev->x = ptr->seat->pointer.x;
   ev->y = ptr->seat->pointer.y;
   ev->root.x = ptr->seat->pointer.x;
   ev->root.y = ptr->seat->pointer.y;

   ev->z = value;
   ev->direction = direction;

   kbd = _evdev_keyboard_get(dev->seat);
   if (kbd) _keyboard_modifiers_update(kbd, dev->seat);

   ev->modifiers = dev->seat->modifiers;

   ecore_event_add(ECORE_EVENT_MOUSE_WHEEL, ev, _event_free, ev->dev);
}

static double
_pointer_axis_value(struct libinput_event_pointer *event, enum libinput_pointer_axis axis)
{
   enum libinput_pointer_axis_source source;
   double val = 0.0;

   source = libinput_event_pointer_get_axis_source(event);
   switch (source)
     {
      case LIBINPUT_POINTER_AXIS_SOURCE_WHEEL:
        val = libinput_event_pointer_get_axis_value_discrete(event, axis);
        break;
      case LIBINPUT_POINTER_AXIS_SOURCE_FINGER:
      case LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS:
        val = libinput_event_pointer_get_axis_value(event, axis);
        break;
      default:
        break;
     }

   return val;
}

static Eina_Bool
_pointer_axis(struct libinput_device *idevice, struct libinput_event_pointer *event)
{
   Elput_Device *dev;
   Elput_Pointer *ptr;
   enum libinput_pointer_axis axis;
   Eina_Bool vert = EINA_FALSE, horiz = EINA_FALSE;
   int dir = 0, val = 0;

   dev = libinput_device_get_user_data(idevice);
   if (!dev) return EINA_FALSE;

   ptr = _evdev_pointer_get(dev->seat);
   if (!ptr) return EINA_FALSE;

   vert =
     libinput_event_pointer_has_axis(event,
                                     LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL);
   horiz =
     libinput_event_pointer_has_axis(event,
                                     LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL);
   if ((!vert) && (!horiz)) return EINA_FALSE;

   if (vert)
     {
        axis = LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL;
        val = _pointer_axis_value(event, axis);
     }

   if (horiz)
     {
        axis = LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL;
        val = _pointer_axis_value(event, axis);
        dir = 1;
     }

   ptr->timestamp = libinput_event_pointer_get_time(event);

   _pointer_axis_send(dev, dir, val);

   return EINA_TRUE;
}

static void
_touch_event_send(Elput_Device *dev, int type)
{
   Elput_Touch *touch;
   Ecore_Event_Mouse_Button *ev;
   unsigned int btn = 0;

   touch = _evdev_touch_get(dev->seat);
   if (!touch) return;

   ev = calloc(1, sizeof(Ecore_Event_Mouse_Button));
   if (!ev) return;

   ev->window = dev->seat->manager->window;
   ev->event_window = dev->seat->manager->window;
   ev->root_window = dev->seat->manager->window;
   ev->timestamp = touch->timestamp;
   ev->same_screen = 1;

   ev->x = touch->seat->pointer.x;
   ev->y = touch->seat->pointer.y;
   ev->root.x = touch->seat->pointer.x;
   ev->root.y = touch->seat->pointer.y;

   ev->modifiers = dev->seat->modifiers;

   ev->multi.device = touch->slot;
   ev->multi.radius = 1;
   ev->multi.radius_x = 1;
   ev->multi.radius_y = 1;
   ev->multi.pressure = touch->pressure;
   ev->multi.angle = 0.0;
   ev->multi.x = ev->x;
   ev->multi.y = ev->y;
   ev->multi.root.x = ev->x;
   ev->multi.root.y = ev->y;

   btn = ((btn & 0x00F) + 1);
// XXX: this code is useless. above btn is set to 0 at declaration time, then
// no code changes it until the above like effectively makes it 1. it can
// only ever be 1 so the below lines are pointless. this is probably a bug
// lurking...
//   if (btn == 3) btn = 2;
//   else if (btn == 2) btn = 3;
   ev->buttons = btn;

   ecore_event_add(type, ev, NULL, NULL);
}

static void
_touch_motion_send(Elput_Device *dev)
{
   Elput_Touch *touch;
   Ecore_Event_Mouse_Move *ev;

   touch = _evdev_touch_get(dev->seat);
   if (!touch) return;

   ev = calloc(1, sizeof(Ecore_Event_Mouse_Move));
   if (!ev) return;

   ev->window = dev->seat->manager->window;
   ev->event_window = dev->seat->manager->window;
   ev->root_window = dev->seat->manager->window;
   ev->timestamp = touch->timestamp;
   ev->same_screen = 1;
   ev->dev = dev->evas_device;
   if (ev->dev) efl_ref(ev->dev);

   ev->x = lround(touch->seat->pointer.x);
   ev->y = lround(touch->seat->pointer.y);
   ev->root.x = ev->x;
   ev->root.y = ev->y;

   ev->modifiers = dev->seat->modifiers;

   ev->multi.device = touch->slot;
   ev->multi.radius = 1;
   ev->multi.radius_x = 1;
   ev->multi.radius_y = 1;
   ev->multi.pressure = touch->pressure;
   ev->multi.angle = 0.0;
   ev->multi.x = touch->seat->pointer.x;
   ev->multi.y = touch->seat->pointer.y;
   ev->multi.root.x = touch->seat->pointer.x;
   ev->multi.root.y = touch->seat->pointer.y;

   ecore_event_add(ECORE_EVENT_MOUSE_MOVE, ev, _event_free, ev->dev);
}

static void
_touch_down(struct libinput_device *idevice, struct libinput_event_touch *event)
{
   Elput_Device *dev;
   Elput_Touch *touch;

   dev = libinput_device_get_user_data(idevice);
   if (!dev) return;

   touch = _evdev_touch_get(dev->seat);
   if (!touch) return;

   touch->slot = libinput_event_touch_get_slot(event);
   touch->timestamp = libinput_event_touch_get_time(event);

   touch->seat->pointer.x =
     libinput_event_touch_get_x_transformed(event, dev->ow);
   touch->seat->pointer.y =
     libinput_event_touch_get_y_transformed(event, dev->oh);

   /* TODO: these needs to run a matrix transform based on output */
   /* _ecore_drm2_output_coordinate_transform(dev->output, */
   /*                                         touch->seat->pointer.x, touch->seat->pointer.y, */
   /*                                         &touch->seat->pointer.x, &touch->seat->pointer.y); */

   if (touch->slot == touch->grab.id)
     {
        touch->grab.x = touch->seat->pointer.x;
        touch->grab.y = touch->seat->pointer.y;
     }

   touch->points++;

   _touch_motion_send(dev);
   _touch_event_send(dev, ECORE_EVENT_MOUSE_BUTTON_DOWN);

   if (touch->points == 1)
     {
        touch->grab.id = touch->slot;
        touch->grab.x = touch->seat->pointer.x;
        touch->grab.y = touch->seat->pointer.y;
        touch->grab.timestamp = touch->timestamp;
     }
}

static void
_touch_up(struct libinput_device *idevice, struct libinput_event_touch *event)
{
   Elput_Device *dev;
   Elput_Touch *touch;

   dev = libinput_device_get_user_data(idevice);
   if (!dev) return;

   touch = _evdev_touch_get(dev->seat);
   if (!touch) return;

   touch->points--;
   touch->slot = libinput_event_touch_get_slot(event);
   touch->timestamp = libinput_event_touch_get_time(event);

   _touch_motion_send(dev);
   _touch_event_send(dev, ECORE_EVENT_MOUSE_BUTTON_UP);
}

static void
_touch_motion(struct libinput_device *idevice, struct libinput_event_touch *event)
{
   Elput_Device *dev;
   Elput_Touch *touch;

   dev = libinput_device_get_user_data(idevice);
   if (!dev) return;

   touch = _evdev_touch_get(dev->seat);
   if (!touch) return;

   touch->seat->pointer.x =
     libinput_event_touch_get_x_transformed(event, dev->ow);
   touch->seat->pointer.y =
     libinput_event_touch_get_y_transformed(event, dev->oh);

   /* TODO: these needs to run a matrix transform based on output */
   /* _ecore_drm2_output_coordinate_transform(dev->output, */
   /*                                         touch->seat->pointer.x, touch->seat->pointer.y, */
   /*                                         &touch->seat->pointer.x, &touch->seat->pointer.y); */

   touch->slot = libinput_event_touch_get_slot(event);
   touch->timestamp = libinput_event_touch_get_time(event);

   _touch_motion_send(dev);
}

void
_evdev_device_calibrate(Elput_Device *dev)
{
   float cal[6];
   const char *vals;
   const char *sysname;
   const char *device;
   Eina_List *devices;
   int w = 0, h = 0;
   enum libinput_config_status status;

   w = dev->ow;
   h = dev->oh;
   if ((w == 0) || (h == 0)) return;

   if ((!libinput_device_config_calibration_has_matrix(dev->device)) ||
       (libinput_device_config_calibration_get_default_matrix(dev->device, cal) != 0))
     return;

   sysname = libinput_device_get_sysname(dev->device);

   devices = eeze_udev_find_by_subsystem_sysname("input", sysname);
   EINA_LIST_FREE(devices, device)
     {
        vals = eeze_udev_syspath_get_property(device, "WL_CALIBRATION");
        if ((!vals) ||
            (sscanf(vals, "%f %f %f %f %f %f",
                    &cal[0], &cal[1], &cal[2], &cal[3], &cal[4], &cal[5]) != 6))
          goto cont;

        cal[2] /= w;
        cal[5] /= h;

        status =
          libinput_device_config_calibration_set_matrix(dev->device, cal);
        if (status != LIBINPUT_CONFIG_STATUS_SUCCESS)
          WRN("Failed to apply device calibration");

cont:
        eina_stringshare_del(device);
     }
}

static void
_axis_event_free(void *d EINA_UNUSED, void *event)
{
   Ecore_Event_Axis_Update *ev = event;

   if (ev->dev) efl_unref(ev->dev);
   free(ev->axis);
   free(ev);
}

static void
_tablet_tool_axis(struct libinput_device *idev, struct libinput_event_tablet_tool *event)
{
   Elput_Pointer *ptr;
   struct libinput_tablet_tool *tool;
   Elput_Device *dev = libinput_device_get_user_data(idev);
   Ecore_Event_Axis_Update *ev;
   Ecore_Axis ax[8] = {}, *axis = NULL;
   int i, num = 0;

   ptr = _evdev_pointer_get(dev->seat);
   EINA_SAFETY_ON_NULL_RETURN(ptr);
   tool = libinput_event_tablet_tool_get_tool(event);

   ptr->seat->pointer.x =
     libinput_event_tablet_tool_get_x_transformed(event, dev->ow);
   ptr->seat->pointer.y =
     libinput_event_tablet_tool_get_y_transformed(event, dev->oh);

   if (libinput_event_tablet_tool_x_has_changed(event))
     {
        ax[num].label = ECORE_AXIS_LABEL_X;
        ax[num].value = ptr->seat->pointer.x;
        num++;
     }
   if (libinput_event_tablet_tool_y_has_changed(event))
     {
        ax[num].label = ECORE_AXIS_LABEL_Y;
        ax[num].value = ptr->seat->pointer.y;
        num++;
     }
   if (libinput_tablet_tool_has_pressure(tool))
     {
        if (libinput_event_tablet_tool_pressure_has_changed(event))
          {
             ax[num].label = ECORE_AXIS_LABEL_PRESSURE;
             ax[num].value = ptr->pressure =
               libinput_event_tablet_tool_get_pressure(event);
             num++;
          }
     }
   if (libinput_tablet_tool_has_distance(tool))
     {
        if (libinput_event_tablet_tool_distance_has_changed(event))
          {
             ax[num].label = ECORE_AXIS_LABEL_DISTANCE;
             ax[num].value = libinput_event_tablet_tool_get_distance(event);
             num++;
          }
     }
   if (libinput_tablet_tool_has_tilt(tool))
     {
        if (libinput_event_tablet_tool_tilt_x_has_changed(event) ||
            libinput_event_tablet_tool_tilt_y_has_changed(event))
          {
             double x = sin(libinput_event_tablet_tool_get_tilt_x(event));
             double y = sin(-libinput_event_tablet_tool_get_tilt_y(event));

             ax[num].label = ECORE_AXIS_LABEL_TILT;
             ax[num].value = asin(sqrt((x * x) + (y * y)));
             num++;

             /* note: the value of atan2(0,0) is implementation-defined */
             ax[num].label = ECORE_AXIS_LABEL_AZIMUTH;
             ax[num].value = atan2(y, x);
             num++;
          }
     }
   if (libinput_tablet_tool_has_rotation(tool))
     {
        if (libinput_event_tablet_tool_rotation_has_changed(event))
          {
             ax[num].label = ECORE_AXIS_LABEL_TWIST;
             ax[num].value = libinput_event_tablet_tool_get_rotation(event);
             ax[num].value *= M_PI / 180;
             num++;
          }
     }

   ptr->timestamp = libinput_event_tablet_tool_get_time(event);

   /* FIXME: other properties which efl event structs don't support:
    * slider_position
    * wheel_delta
    */

   if (libinput_event_tablet_tool_x_has_changed(event) ||
       libinput_event_tablet_tool_y_has_changed(event))
     _pointer_motion_send(dev);

   if (!num) return;
   ev = calloc(1, sizeof(Ecore_Event_Axis_Update));

   ev->window = dev->seat->manager->window;
   ev->event_window = dev->seat->manager->window;
   ev->root_window = dev->seat->manager->window;
   ev->timestamp = ptr->timestamp;
   ev->naxis = num;
   ev->dev = dev->evas_device;
   if (ev->dev) efl_ref(ev->dev);
   ev->axis = axis = calloc(num, sizeof(Ecore_Axis));
   for (i = 0; i < num; i++)
     {
        axis[i].label = ax[i].label;
        axis[i].value = ax[i].value;
     }
   ecore_event_add(ECORE_EVENT_AXIS_UPDATE, ev, _axis_event_free, NULL);
}

static void
_tablet_tool_tip(struct libinput_device *idev, struct libinput_event_tablet_tool *event)
{
   Elput_Pointer *ptr;
   Elput_Device *dev = libinput_device_get_user_data(idev);
   int state;
   int press[] =
     {
        [LIBINPUT_TABLET_TOOL_TIP_DOWN] = LIBINPUT_BUTTON_STATE_PRESSED,
        [LIBINPUT_TABLET_TOOL_TIP_UP] = LIBINPUT_BUTTON_STATE_RELEASED,
     };

   ptr = _evdev_pointer_get(dev->seat);
   EINA_SAFETY_ON_NULL_RETURN(ptr);

   state = libinput_event_tablet_tool_get_tip_state(event);
   ptr->buttons = 1;
   ptr->timestamp = libinput_event_tablet_tool_get_time(event);

   if (press[state]) _pointer_click_update(ptr, 1);

   _pointer_button_send(dev, press[state]);
}

static void
_switch_event_free(void *data EINA_UNUSED, void *event)
{
   Elput_Event_Switch *ev = event;

   _evdev_device_destroy(ev->device);
   free(ev);
}

static void
_switch_toggle(struct libinput_device *idev, struct libinput_event_switch *event)
{
   Elput_Event_Switch *ev;

   ev = calloc(1, sizeof(Elput_Event_Switch));
   if (!ev) return;
   ev->device = libinput_device_get_user_data(idev);
   ev->device->refs++;
   ev->time_usec = libinput_event_switch_get_time_usec(event);
   ev->type = (Elput_Switch_Type)libinput_event_switch_get_switch(event);
   ev->state = (Elput_Switch_State)libinput_event_switch_get_switch_state(event);
   ecore_event_add(ELPUT_EVENT_SWITCH, ev, _switch_event_free, NULL);
}

int
_evdev_event_process(struct libinput_event *event)
{
   struct libinput_device *idev;
   int ret = 1;
   Eina_Bool frame = EINA_FALSE;

   idev = libinput_event_get_device(event);
   switch (libinput_event_get_type(event))
     {
      case LIBINPUT_EVENT_KEYBOARD_KEY:
        _keyboard_key(idev, libinput_event_get_keyboard_event(event));
        break;
      case LIBINPUT_EVENT_POINTER_MOTION:
        frame =
          _pointer_motion(idev, libinput_event_get_pointer_event(event));
        break;
      case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE:
        frame =
          _pointer_motion_abs(idev, libinput_event_get_pointer_event(event));
        break;
      case LIBINPUT_EVENT_POINTER_BUTTON:
        frame =
          _pointer_button(idev, libinput_event_get_pointer_event(event));
        break;
      case LIBINPUT_EVENT_POINTER_AXIS:
        frame =
          _pointer_axis(idev, libinput_event_get_pointer_event(event));
        break;
      case LIBINPUT_EVENT_TOUCH_DOWN:
        _touch_down(idev, libinput_event_get_touch_event(event));
        break;
      case LIBINPUT_EVENT_TOUCH_MOTION:
        _touch_motion(idev, libinput_event_get_touch_event(event));
        break;
      case LIBINPUT_EVENT_TOUCH_UP:
        _touch_up(idev, libinput_event_get_touch_event(event));
        break;
      case LIBINPUT_EVENT_TOUCH_FRAME: break;
      case LIBINPUT_EVENT_TABLET_TOOL_AXIS:
        _tablet_tool_axis(idev, libinput_event_get_tablet_tool_event(event));
        break;
      case LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY: /* is this useful? */
        break;
      case LIBINPUT_EVENT_TABLET_TOOL_TIP: /* is this useful? */
        _tablet_tool_tip(idev, libinput_event_get_tablet_tool_event(event));
        break;
      case LIBINPUT_EVENT_SWITCH_TOGGLE:
        _switch_toggle(idev, libinput_event_get_switch_event(event));
        break;
      default:
        ret = 0;
        break;
     }

   if (frame)
     {
        Elput_Device *edev;

        edev = libinput_device_get_user_data(idev);
        if (edev) _seat_frame_send(edev->seat);
     }

   return ret;
}

Elput_Device *
_evdev_device_create(Elput_Seat *seat, struct libinput_device *device)
{
   Elput_Device *edev;
   const char *oname;

   edev = calloc(1, sizeof(Elput_Device));
   if (!edev) return NULL;

   edev->refs = 1;
   edev->seat = seat;
   edev->device = device;
   edev->ow = seat->manager->output_w;
   edev->oh = seat->manager->output_h;

   oname = libinput_device_get_output_name(device);
   if (!oname)
     oname = libinput_device_get_name(device);
   eina_stringshare_replace(&edev->output_name, oname);

   if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_SWITCH))
     edev->caps |= ELPUT_DEVICE_CAPS_SWITCH;
   if ((libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_KEYBOARD)) &&
       (libinput_device_keyboard_has_key(device, KEY_ENTER)))
     edev->caps |= ELPUT_DEVICE_CAPS_KEYBOARD;
   if (edev->caps & (ELPUT_DEVICE_CAPS_SWITCH | ELPUT_DEVICE_CAPS_KEYBOARD))
     _keyboard_init(seat, seat->manager->cached.keymap);

   if ((libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_POINTER) &&
       (libinput_device_pointer_has_button(device, BTN_LEFT))))
     edev->caps |= ELPUT_DEVICE_CAPS_POINTER;
   if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_TABLET_TOOL))
     edev->caps |= ELPUT_DEVICE_CAPS_POINTER | ELPUT_DEVICE_CAPS_TABLET_TOOL;
   if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_TABLET_PAD))
     edev->caps |= ELPUT_DEVICE_CAPS_TABLET_PAD;
   if (edev->caps & ELPUT_DEVICE_CAPS_POINTER)
     _pointer_init(seat);

   if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_TOUCH))
     edev->caps |= ELPUT_DEVICE_CAPS_TOUCH;
   if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_GESTURE))
     edev->caps |= ELPUT_DEVICE_CAPS_TOUCH | ELPUT_DEVICE_CAPS_GESTURE;
   if (edev->caps & ELPUT_DEVICE_CAPS_TOUCH)
     _touch_init(seat);

   libinput_device_set_user_data(device, edev);
   libinput_device_ref(edev->device);

   if (libinput_device_config_tap_get_finger_count(edev->device) > 0)
     {
        Eina_Bool enable = EINA_FALSE;

        enable = libinput_device_config_tap_get_default_enabled(edev->device);
        libinput_device_config_tap_set_enabled(edev->device, enable);
     }

   return edev;
}

void
_evdev_device_destroy(Elput_Device *edev)
{
   if (!edev) return;
   edev->refs--;
   if (edev->refs) return;

   if (edev->caps & ELPUT_DEVICE_CAPS_POINTER)
     _pointer_release(edev->seat);
   if (edev->caps & ELPUT_DEVICE_CAPS_KEYBOARD)
     _keyboard_release(edev->seat);
   if (edev->caps & ELPUT_DEVICE_CAPS_TOUCH)
     _touch_release(edev->seat);

   libinput_device_unref(edev->device);
   eina_stringshare_del(edev->output_name);

   if (edev->key_remap_hash) eina_hash_free(edev->key_remap_hash);

   free(edev);
}

void
_evdev_keyboard_destroy(Elput_Keyboard *kbd)
{
   free((char *)kbd->names.rules);
   free((char *)kbd->names.model);
   free((char *)kbd->names.layout);
   free((char *)kbd->names.variant);
   free((char *)kbd->names.options);

   if (kbd->compose_table) xkb_compose_table_unref(kbd->compose_table);
   if (kbd->compose_state) xkb_compose_state_unref(kbd->compose_state);

   if (kbd->state) xkb_state_unref(kbd->state);
   if (kbd->maskless_state) xkb_state_unref(kbd->maskless_state);
   if (kbd->info) _keyboard_info_destroy(kbd->info);

   xkb_context_unref(kbd->context);

   free(kbd);
}

void
_evdev_pointer_destroy(Elput_Pointer *ptr)
{
   /* FIXME: destroy any resources inside pointer structure */
   free(ptr);
}

void
_evdev_touch_destroy(Elput_Touch *touch)
{
   /* FIXME: destroy any resources inside touch structure */
   free(touch);
}

void
_evdev_pointer_motion_send(Elput_Device *edev)
{
   _pointer_motion_send(edev);
}

Elput_Pointer *
_evdev_pointer_get(Elput_Seat *seat)
{
   if (!seat) return NULL;
   if (seat->count.ptr) return seat->ptr;
   return NULL;
}

Elput_Keyboard *
_evdev_keyboard_get(Elput_Seat *seat)
{
   if (!seat) return NULL;
   if (seat->count.kbd) return seat->kbd;
   return NULL;
}

Elput_Touch *
_evdev_touch_get(Elput_Seat *seat)
{
   if (!seat) return NULL;
   if (seat->count.touch) return seat->touch;
   return NULL;
}