2014-08-27 20:34:55 +04:00
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd .
Copyright ( C ) 2014 David Herrmann < dh . herrmann @ gmail . com >
systemd is free software ; you can redistribute it and / or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation ; either version 2.1 of the License , or
( at your option ) any later version .
systemd 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
Lesser General Public License for more details .
You should have received a copy of the GNU Lesser General Public License
along with systemd ; If not , see < http : //www.gnu.org/licenses/>.
* * */
# include <inttypes.h>
# include <stdbool.h>
# include <stdlib.h>
# include <systemd/sd-bus.h>
# include <systemd/sd-event.h>
# include <xkbcommon/xkbcommon.h>
2014-11-24 18:32:54 +03:00
# include <xkbcommon/xkbcommon-compose.h>
2014-08-27 20:34:55 +04:00
# include "bus-util.h"
# include "hashmap.h"
# include "idev.h"
# include "idev-internal.h"
# include "macro.h"
2014-11-24 20:25:30 +03:00
# include "term-internal.h"
2014-08-27 20:34:55 +04:00
# include "util.h"
2014-11-24 18:32:54 +03:00
typedef struct kbdtbl kbdtbl ;
2014-08-27 20:34:55 +04:00
typedef struct kbdmap kbdmap ;
typedef struct kbdctx kbdctx ;
typedef struct idev_keyboard idev_keyboard ;
2014-11-24 18:32:54 +03:00
struct kbdtbl {
unsigned long ref ;
struct xkb_compose_table * xkb_compose_table ;
} ;
2014-08-27 20:34:55 +04:00
struct kbdmap {
unsigned long ref ;
struct xkb_keymap * xkb_keymap ;
xkb_mod_index_t modmap [ IDEV_KBDMOD_CNT ] ;
xkb_led_index_t ledmap [ IDEV_KBDLED_CNT ] ;
} ;
struct kbdctx {
unsigned long ref ;
idev_context * context ;
struct xkb_context * xkb_context ;
struct kbdmap * kbdmap ;
2014-11-24 18:32:54 +03:00
struct kbdtbl * kbdtbl ;
2014-08-27 20:34:55 +04:00
sd_bus_slot * slot_locale_props_changed ;
sd_bus_slot * slot_locale_get_all ;
2014-11-24 18:32:54 +03:00
char * locale_lang ;
2014-08-27 20:34:55 +04:00
char * locale_x11_model ;
char * locale_x11_layout ;
char * locale_x11_variant ;
char * locale_x11_options ;
char * last_x11_model ;
char * last_x11_layout ;
char * last_x11_variant ;
char * last_x11_options ;
} ;
struct idev_keyboard {
idev_device device ;
kbdctx * kbdctx ;
kbdmap * kbdmap ;
2014-11-24 18:32:54 +03:00
kbdtbl * kbdtbl ;
2014-08-27 20:34:55 +04:00
struct xkb_state * xkb_state ;
2014-11-24 18:32:54 +03:00
struct xkb_compose_state * xkb_compose ;
2014-08-27 20:34:55 +04:00
usec_t repeat_delay ;
usec_t repeat_rate ;
sd_event_source * repeat_timer ;
uint32_t n_syms ;
idev_data evdata ;
idev_data repdata ;
2014-11-24 20:25:30 +03:00
uint32_t * compose_res ;
2014-08-27 20:34:55 +04:00
bool repeating : 1 ;
} ;
# define keyboard_from_device(_d) container_of((_d), idev_keyboard, device)
# define KBDCTX_KEY "keyboard.context" /* hashmap key for global kbdctx */
# define KBDXKB_SHIFT (8) /* xkb shifts evdev key-codes by 8 */
# define KBDKEY_UP (0) /* KEY UP event value */
# define KBDKEY_DOWN (1) /* KEY DOWN event value */
# define KBDKEY_REPEAT (2) /* KEY REPEAT event value */
static const idev_device_vtable keyboard_vtable ;
static int keyboard_update_kbdmap ( idev_keyboard * k ) ;
2014-11-24 18:32:54 +03:00
static int keyboard_update_kbdtbl ( idev_keyboard * k ) ;
/*
* Keyboard Compose Tables
*/
static kbdtbl * kbdtbl_ref ( kbdtbl * kt ) {
if ( kt ) {
assert_return ( kt - > ref > 0 , NULL ) ;
+ + kt - > ref ;
}
return kt ;
}
static kbdtbl * kbdtbl_unref ( kbdtbl * kt ) {
if ( ! kt )
return NULL ;
assert_return ( kt - > ref > 0 , NULL ) ;
if ( - - kt - > ref > 0 )
return NULL ;
xkb_compose_table_unref ( kt - > xkb_compose_table ) ;
free ( kt ) ;
return 0 ;
}
DEFINE_TRIVIAL_CLEANUP_FUNC ( kbdtbl * , kbdtbl_unref ) ;
static int kbdtbl_new_from_locale ( kbdtbl * * out , kbdctx * kc , const char * locale ) {
_cleanup_ ( kbdtbl_unrefp ) kbdtbl * kt = NULL ;
assert_return ( out , - EINVAL ) ;
assert_return ( locale , - EINVAL ) ;
kt = new0 ( kbdtbl , 1 ) ;
if ( ! kt )
return - ENOMEM ;
kt - > ref = 1 ;
kt - > xkb_compose_table = xkb_compose_table_new_from_locale ( kc - > xkb_context ,
locale ,
XKB_COMPOSE_COMPILE_NO_FLAGS ) ;
if ( ! kt - > xkb_compose_table )
return errno > 0 ? - errno : - EFAULT ;
* out = kt ;
kt = NULL ;
return 0 ;
}
2014-08-27 20:34:55 +04:00
/*
* Keyboard Keymaps
*/
static const char * const kbdmap_modmap [ IDEV_KBDMOD_CNT ] = {
[ IDEV_KBDMOD_IDX_SHIFT ] = XKB_MOD_NAME_SHIFT ,
[ IDEV_KBDMOD_IDX_CTRL ] = XKB_MOD_NAME_CTRL ,
[ IDEV_KBDMOD_IDX_ALT ] = XKB_MOD_NAME_ALT ,
[ IDEV_KBDMOD_IDX_LINUX ] = XKB_MOD_NAME_LOGO ,
[ IDEV_KBDMOD_IDX_CAPS ] = XKB_MOD_NAME_CAPS ,
} ;
static const char * const kbdmap_ledmap [ IDEV_KBDLED_CNT ] = {
[ IDEV_KBDLED_IDX_NUM ] = XKB_LED_NAME_NUM ,
[ IDEV_KBDLED_IDX_CAPS ] = XKB_LED_NAME_CAPS ,
[ IDEV_KBDLED_IDX_SCROLL ] = XKB_LED_NAME_SCROLL ,
} ;
static kbdmap * kbdmap_ref ( kbdmap * km ) {
assert_return ( km , NULL ) ;
assert_return ( km - > ref > 0 , NULL ) ;
+ + km - > ref ;
return km ;
}
static kbdmap * kbdmap_unref ( kbdmap * km ) {
if ( ! km )
return NULL ;
assert_return ( km - > ref > 0 , NULL ) ;
if ( - - km - > ref > 0 )
return NULL ;
xkb_keymap_unref ( km - > xkb_keymap ) ;
free ( km ) ;
return 0 ;
}
DEFINE_TRIVIAL_CLEANUP_FUNC ( kbdmap * , kbdmap_unref ) ;
static int kbdmap_new_from_names ( kbdmap * * out ,
kbdctx * kc ,
const char * model ,
const char * layout ,
const char * variant ,
const char * options ) {
_cleanup_ ( kbdmap_unrefp ) kbdmap * km = NULL ;
struct xkb_rule_names rmlvo = { } ;
unsigned int i ;
assert_return ( out , - EINVAL ) ;
km = new0 ( kbdmap , 1 ) ;
if ( ! km )
return - ENOMEM ;
km - > ref = 1 ;
rmlvo . rules = " evdev " ;
rmlvo . model = model ;
rmlvo . layout = layout ;
rmlvo . variant = variant ;
rmlvo . options = options ;
errno = 0 ;
km - > xkb_keymap = xkb_keymap_new_from_names ( kc - > xkb_context , & rmlvo , 0 ) ;
if ( ! km - > xkb_keymap )
return errno > 0 ? - errno : - EFAULT ;
for ( i = 0 ; i < IDEV_KBDMOD_CNT ; + + i ) {
const char * t = kbdmap_modmap [ i ] ;
if ( t )
km - > modmap [ i ] = xkb_keymap_mod_get_index ( km - > xkb_keymap , t ) ;
else
km - > modmap [ i ] = XKB_MOD_INVALID ;
}
for ( i = 0 ; i < IDEV_KBDLED_CNT ; + + i ) {
const char * t = kbdmap_ledmap [ i ] ;
if ( t )
km - > ledmap [ i ] = xkb_keymap_led_get_index ( km - > xkb_keymap , t ) ;
else
km - > ledmap [ i ] = XKB_LED_INVALID ;
}
* out = km ;
km = NULL ;
return 0 ;
}
/*
* Keyboard Context
*/
2014-11-24 18:32:54 +03:00
static int kbdctx_refresh_compose_table ( kbdctx * kc , const char * lang ) {
kbdtbl * kt ;
idev_session * s ;
idev_device * d ;
Iterator i , j ;
int r ;
if ( ! lang )
lang = " C " ;
if ( streq_ptr ( kc - > locale_lang , lang ) )
return 0 ;
r = free_and_strdup ( & kc - > locale_lang , lang ) ;
if ( r < 0 )
return r ;
log_debug ( " idev-keyboard: new default compose table: [ %s ] " , lang ) ;
r = kbdtbl_new_from_locale ( & kt , kc , lang ) ;
if ( r < 0 ) {
/* TODO: We need to catch the case where no compose-file is
* available . xkb doesn ' t tell us so far . . so we must not treat
* it as a hard - failure but just continue . Preferably , we want
* xkb to tell us exactly whether compilation failed or whether
* there is no compose file available for this locale . */
2014-11-28 19:09:20 +03:00
log_debug_errno ( r , " idev-keyboard: cannot load compose-table for '%s': %m " ,
lang ) ;
2014-11-24 18:32:54 +03:00
r = 0 ;
kt = NULL ;
}
kbdtbl_unref ( kc - > kbdtbl ) ;
kc - > kbdtbl = kt ;
HASHMAP_FOREACH ( s , kc - > context - > session_map , i )
HASHMAP_FOREACH ( d , s - > device_map , j )
if ( idev_is_keyboard ( d ) )
keyboard_update_kbdtbl ( keyboard_from_device ( d ) ) ;
return 0 ;
}
2014-08-27 20:34:55 +04:00
static void move_str ( char * * dest , char * * src ) {
free ( * dest ) ;
* dest = * src ;
* src = NULL ;
}
static int kbdctx_refresh_keymap ( kbdctx * kc ) {
idev_session * s ;
idev_device * d ;
Iterator i , j ;
kbdmap * km ;
int r ;
if ( kc - > kbdmap & &
streq_ptr ( kc - > locale_x11_model , kc - > last_x11_model ) & &
streq_ptr ( kc - > locale_x11_layout , kc - > last_x11_layout ) & &
streq_ptr ( kc - > locale_x11_variant , kc - > last_x11_variant ) & &
streq_ptr ( kc - > locale_x11_options , kc - > last_x11_options ) )
return 0 ;
move_str ( & kc - > last_x11_model , & kc - > locale_x11_model ) ;
move_str ( & kc - > last_x11_layout , & kc - > locale_x11_layout ) ;
move_str ( & kc - > last_x11_variant , & kc - > locale_x11_variant ) ;
move_str ( & kc - > last_x11_options , & kc - > locale_x11_options ) ;
log_debug ( " idev-keyboard: new default keymap: [%s / %s / %s / %s] " ,
kc - > last_x11_model , kc - > last_x11_layout , kc - > last_x11_variant , kc - > last_x11_options ) ;
/* TODO: add a fallback keymap that's compiled-in */
r = kbdmap_new_from_names ( & km , kc , kc - > last_x11_model , kc - > last_x11_layout ,
kc - > last_x11_variant , kc - > last_x11_options ) ;
if ( r < 0 ) {
2014-11-28 19:09:20 +03:00
log_debug_errno ( r , " idev-keyboard: cannot create keymap from locale1: %m " ) ;
2014-08-27 20:34:55 +04:00
return r ;
}
kbdmap_unref ( kc - > kbdmap ) ;
kc - > kbdmap = km ;
HASHMAP_FOREACH ( s , kc - > context - > session_map , i )
HASHMAP_FOREACH ( d , s - > device_map , j )
if ( idev_is_keyboard ( d ) )
keyboard_update_kbdmap ( keyboard_from_device ( d ) ) ;
return 0 ;
}
2014-11-24 18:32:54 +03:00
static int kbdctx_set_locale ( sd_bus * bus , const char * member , sd_bus_message * m , sd_bus_error * error , void * userdata ) {
kbdctx * kc = userdata ;
const char * s , * ctype = NULL , * lang = NULL ;
int r ;
r = sd_bus_message_enter_container ( m , ' a ' , " s " ) ;
if ( r < 0 )
goto error ;
while ( ( r = sd_bus_message_read ( m , " s " , & s ) ) > 0 ) {
if ( ! ctype )
ctype = startswith ( s , " LC_CTYPE= " ) ;
if ( ! lang )
lang = startswith ( s , " LANG= " ) ;
}
if ( r < 0 )
goto error ;
r = sd_bus_message_exit_container ( m ) ;
if ( r < 0 )
goto error ;
kbdctx_refresh_compose_table ( kc , ctype ? : lang ) ;
r = 0 ;
error :
if ( r < 0 )
2014-11-28 15:19:16 +03:00
log_debug_errno ( r , " idev-keyboard: cannot parse locale property from locale1: %m " ) ;
2014-11-24 18:32:54 +03:00
return r ;
}
2014-08-27 20:34:55 +04:00
static const struct bus_properties_map kbdctx_locale_map [ ] = {
2014-11-24 18:32:54 +03:00
{ " Locale " , " as " , kbdctx_set_locale , 0 } ,
2014-08-27 20:34:55 +04:00
{ " X11Model " , " s " , NULL , offsetof ( kbdctx , locale_x11_model ) } ,
{ " X11Layout " , " s " , NULL , offsetof ( kbdctx , locale_x11_layout ) } ,
{ " X11Variant " , " s " , NULL , offsetof ( kbdctx , locale_x11_variant ) } ,
{ " X11Options " , " s " , NULL , offsetof ( kbdctx , locale_x11_options ) } ,
} ;
static int kbdctx_locale_get_all_fn ( sd_bus * bus ,
sd_bus_message * m ,
void * userdata ,
sd_bus_error * ret_err ) {
kbdctx * kc = userdata ;
int r ;
kc - > slot_locale_get_all = sd_bus_slot_unref ( kc - > slot_locale_get_all ) ;
if ( sd_bus_message_is_method_error ( m , NULL ) ) {
const sd_bus_error * error = sd_bus_message_get_error ( m ) ;
log_debug ( " idev-keyboard: GetAll() on locale1 failed: %s: %s " ,
error - > name , error - > message ) ;
return 0 ;
}
r = bus_message_map_all_properties ( bus , m , kbdctx_locale_map , kc ) ;
if ( r < 0 ) {
log_debug ( " idev-keyboard: erroneous GetAll() reply from locale1 " ) ;
return 0 ;
}
kbdctx_refresh_keymap ( kc ) ;
return 0 ;
}
static int kbdctx_query_locale ( kbdctx * kc ) {
_cleanup_bus_message_unref_ sd_bus_message * m = NULL ;
int r ;
kc - > slot_locale_get_all = sd_bus_slot_unref ( kc - > slot_locale_get_all ) ;
r = sd_bus_message_new_method_call ( kc - > context - > sysbus ,
& m ,
" org.freedesktop.locale1 " ,
" /org/freedesktop/locale1 " ,
" org.freedesktop.DBus.Properties " ,
" GetAll " ) ;
if ( r < 0 )
goto error ;
r = sd_bus_message_append ( m , " s " , " org.freedesktop.locale1 " ) ;
if ( r < 0 )
goto error ;
r = sd_bus_call_async ( kc - > context - > sysbus ,
& kc - > slot_locale_get_all ,
m ,
kbdctx_locale_get_all_fn ,
kc ,
0 ) ;
if ( r < 0 )
goto error ;
return 0 ;
error :
2014-11-28 15:19:16 +03:00
log_debug_errno ( r , " idev-keyboard: cannot send GetAll to locale1: %m " ) ;
2014-08-27 20:34:55 +04:00
return r ;
}
static int kbdctx_locale_props_changed_fn ( sd_bus * bus ,
sd_bus_message * signal ,
void * userdata ,
sd_bus_error * ret_err ) {
kbdctx * kc = userdata ;
int r ;
kc - > slot_locale_get_all = sd_bus_slot_unref ( kc - > slot_locale_get_all ) ;
2014-08-28 17:24:00 +04:00
/* skip interface name */
r = sd_bus_message_skip ( signal , " s " ) ;
if ( r < 0 )
goto error ;
2014-08-27 20:34:55 +04:00
r = bus_message_map_properties_changed ( bus , signal , kbdctx_locale_map , kc ) ;
2014-08-28 17:24:00 +04:00
if ( r < 0 )
goto error ;
2014-08-27 20:34:55 +04:00
if ( r > 0 ) {
r = kbdctx_query_locale ( kc ) ;
if ( r < 0 )
return r ;
}
kbdctx_refresh_keymap ( kc ) ;
return 0 ;
2014-08-28 17:24:00 +04:00
error :
2014-11-28 15:19:16 +03:00
log_debug_errno ( r , " idev-keyboard: cannot handle PropertiesChanged from locale1: %m " ) ;
2014-08-28 17:24:00 +04:00
return r ;
2014-08-27 20:34:55 +04:00
}
static int kbdctx_setup_bus ( kbdctx * kc ) {
int r ;
r = sd_bus_add_match ( kc - > context - > sysbus ,
& kc - > slot_locale_props_changed ,
" type='signal', "
" sender='org.freedesktop.locale1', "
" interface='org.freedesktop.DBus.Properties', "
" member='PropertiesChanged', "
" path='/org/freedesktop/locale1' " ,
kbdctx_locale_props_changed_fn ,
kc ) ;
if ( r < 0 ) {
2014-11-28 15:19:16 +03:00
log_debug_errno ( r , " idev-keyboard: cannot setup locale1 link: %m " ) ;
2014-08-27 20:34:55 +04:00
return r ;
}
return kbdctx_query_locale ( kc ) ;
}
2014-11-25 12:24:39 +03:00
static void kbdctx_log_fn ( struct xkb_context * ctx , enum xkb_log_level lvl , const char * format , va_list args ) {
char buf [ LINE_MAX ] ;
int sd_lvl ;
if ( lvl > = XKB_LOG_LEVEL_DEBUG )
sd_lvl = LOG_DEBUG ;
else if ( lvl > = XKB_LOG_LEVEL_INFO )
sd_lvl = LOG_INFO ;
else if ( lvl > = XKB_LOG_LEVEL_WARNING )
sd_lvl = LOG_INFO ; /* most XKB warnings really are informational */
else if ( lvl > = XKB_LOG_LEVEL_ERROR )
sd_lvl = LOG_ERR ;
else if ( lvl > = XKB_LOG_LEVEL_CRITICAL )
sd_lvl = LOG_CRIT ;
else
sd_lvl = LOG_CRIT ;
snprintf ( buf , sizeof ( buf ) , " idev-xkb: %s " , format ) ;
2014-11-28 01:15:54 +03:00
log_internalv ( sd_lvl , 0 , __FILE__ , __LINE__ , __func__ , buf , args ) ;
2014-11-25 12:24:39 +03:00
}
2014-08-27 20:34:55 +04:00
static kbdctx * kbdctx_ref ( kbdctx * kc ) {
assert_return ( kc , NULL ) ;
assert_return ( kc - > ref > 0 , NULL ) ;
+ + kc - > ref ;
return kc ;
}
static kbdctx * kbdctx_unref ( kbdctx * kc ) {
if ( ! kc )
return NULL ;
assert_return ( kc - > ref > 0 , NULL ) ;
if ( - - kc - > ref > 0 )
return NULL ;
free ( kc - > last_x11_options ) ;
free ( kc - > last_x11_variant ) ;
free ( kc - > last_x11_layout ) ;
free ( kc - > last_x11_model ) ;
free ( kc - > locale_x11_options ) ;
free ( kc - > locale_x11_variant ) ;
free ( kc - > locale_x11_layout ) ;
free ( kc - > locale_x11_model ) ;
2014-11-24 18:32:54 +03:00
free ( kc - > locale_lang ) ;
2014-08-27 20:34:55 +04:00
kc - > slot_locale_get_all = sd_bus_slot_unref ( kc - > slot_locale_get_all ) ;
kc - > slot_locale_props_changed = sd_bus_slot_unref ( kc - > slot_locale_props_changed ) ;
2014-11-24 18:32:54 +03:00
kc - > kbdtbl = kbdtbl_unref ( kc - > kbdtbl ) ;
2014-08-27 20:34:55 +04:00
kc - > kbdmap = kbdmap_unref ( kc - > kbdmap ) ;
xkb_context_unref ( kc - > xkb_context ) ;
hashmap_remove_value ( kc - > context - > data_map , KBDCTX_KEY , kc ) ;
free ( kc ) ;
return NULL ;
}
DEFINE_TRIVIAL_CLEANUP_FUNC ( kbdctx * , kbdctx_unref ) ;
static int kbdctx_new ( kbdctx * * out , idev_context * c ) {
_cleanup_ ( kbdctx_unrefp ) kbdctx * kc = NULL ;
int r ;
assert_return ( out , - EINVAL ) ;
assert_return ( c , - EINVAL ) ;
kc = new0 ( kbdctx , 1 ) ;
if ( ! kc )
return - ENOMEM ;
kc - > ref = 1 ;
kc - > context = c ;
errno = 0 ;
2014-11-25 12:24:08 +03:00
kc - > xkb_context = xkb_context_new ( XKB_CONTEXT_NO_FLAGS ) ;
2014-08-27 20:34:55 +04:00
if ( ! kc - > xkb_context )
return errno > 0 ? - errno : - EFAULT ;
2014-11-25 12:24:39 +03:00
xkb_context_set_log_fn ( kc - > xkb_context , kbdctx_log_fn ) ;
xkb_context_set_log_level ( kc - > xkb_context , XKB_LOG_LEVEL_DEBUG ) ;
2014-08-27 20:34:55 +04:00
r = kbdctx_refresh_keymap ( kc ) ;
if ( r < 0 )
return r ;
2014-11-24 18:32:54 +03:00
r = kbdctx_refresh_compose_table ( kc , NULL ) ;
if ( r < 0 )
return r ;
2014-08-27 20:34:55 +04:00
if ( c - > sysbus ) {
r = kbdctx_setup_bus ( kc ) ;
if ( r < 0 )
return r ;
}
r = hashmap_put ( c - > data_map , KBDCTX_KEY , kc ) ;
if ( r < 0 )
return r ;
* out = kc ;
kc = NULL ;
return 0 ;
}
static int get_kbdctx ( idev_context * c , kbdctx * * out ) {
kbdctx * kc ;
assert_return ( c , - EINVAL ) ;
assert_return ( out , - EINVAL ) ;
kc = hashmap_get ( c - > data_map , KBDCTX_KEY ) ;
if ( kc ) {
* out = kbdctx_ref ( kc ) ;
return 0 ;
}
return kbdctx_new ( out , c ) ;
}
/*
* Keyboard Devices
*/
bool idev_is_keyboard ( idev_device * d ) {
return d & & d - > vtable = = & keyboard_vtable ;
}
idev_device * idev_find_keyboard ( idev_session * s , const char * name ) {
char * kname ;
assert_return ( s , NULL ) ;
assert_return ( name , NULL ) ;
kname = strappenda ( " keyboard/ " , name ) ;
return hashmap_get ( s - > device_map , kname ) ;
}
static int keyboard_raise_data ( idev_keyboard * k , idev_data * data ) {
idev_device * d = & k - > device ;
int r ;
r = idev_session_raise_device_data ( d - > session , d , data ) ;
if ( r < 0 )
2014-11-28 19:09:20 +03:00
log_debug_errno ( r , " idev-keyboard: %s/%s: error while raising data event: %m " ,
d - > session - > name , d - > name ) ;
2014-08-27 20:34:55 +04:00
return r ;
}
2014-11-24 20:25:30 +03:00
static int keyboard_resize_bufs ( idev_keyboard * k , uint32_t n_syms ) {
uint32_t * t ;
if ( n_syms < = k - > n_syms )
return 0 ;
t = realloc ( k - > compose_res , sizeof ( * t ) * n_syms ) ;
if ( ! t )
return - ENOMEM ;
k - > compose_res = t ;
t = realloc ( k - > evdata . keyboard . keysyms , sizeof ( * t ) * n_syms ) ;
if ( ! t )
return - ENOMEM ;
k - > evdata . keyboard . keysyms = t ;
t = realloc ( k - > evdata . keyboard . codepoints , sizeof ( * t ) * n_syms ) ;
if ( ! t )
return - ENOMEM ;
k - > evdata . keyboard . codepoints = t ;
t = realloc ( k - > repdata . keyboard . keysyms , sizeof ( * t ) * n_syms ) ;
if ( ! t )
return - ENOMEM ;
k - > repdata . keyboard . keysyms = t ;
t = realloc ( k - > repdata . keyboard . codepoints , sizeof ( * t ) * n_syms ) ;
if ( ! t )
return - ENOMEM ;
k - > repdata . keyboard . codepoints = t ;
k - > n_syms = n_syms ;
return 0 ;
}
static unsigned int keyboard_read_compose ( idev_keyboard * k , const xkb_keysym_t * * out ) {
_cleanup_free_ char * t = NULL ;
term_utf8 u8 = { } ;
char buf [ 256 ] , * p ;
size_t flen = 0 ;
int i , r ;
r = xkb_compose_state_get_utf8 ( k - > xkb_compose , buf , sizeof ( buf ) ) ;
if ( r > = ( int ) sizeof ( buf ) ) {
t = malloc ( r + 1 ) ;
if ( ! t )
return 0 ;
xkb_compose_state_get_utf8 ( k - > xkb_compose , t , r + 1 ) ;
p = t ;
} else {
p = buf ;
}
for ( i = 0 ; i < r ; + + i ) {
uint32_t * ucs ;
size_t len , j ;
len = term_utf8_decode ( & u8 , & ucs , p [ i ] ) ;
if ( len > 0 ) {
r = keyboard_resize_bufs ( k , flen + len ) ;
if ( r < 0 )
return 0 ;
for ( j = 0 ; j < len ; + + j )
k - > compose_res [ flen + + ] = ucs [ j ] ;
}
}
* out = k - > compose_res ;
return flen ;
}
2014-08-27 20:34:55 +04:00
static void keyboard_arm ( idev_keyboard * k , usec_t usecs ) {
int r ;
if ( usecs ! = 0 ) {
usecs + = now ( CLOCK_MONOTONIC ) ;
r = sd_event_source_set_time ( k - > repeat_timer , usecs ) ;
if ( r > = 0 )
sd_event_source_set_enabled ( k - > repeat_timer , SD_EVENT_ONESHOT ) ;
} else {
sd_event_source_set_enabled ( k - > repeat_timer , SD_EVENT_OFF ) ;
}
}
static int keyboard_repeat_timer_fn ( sd_event_source * source , uint64_t usec , void * userdata ) {
idev_keyboard * k = userdata ;
2014-11-24 20:25:30 +03:00
/* never feed REPEAT keys into COMPOSE */
2014-08-27 20:34:55 +04:00
keyboard_arm ( k , k - > repeat_rate ) ;
return keyboard_raise_data ( k , & k - > repdata ) ;
}
int idev_keyboard_new ( idev_device * * out , idev_session * s , const char * name ) {
_cleanup_ ( idev_device_freep ) idev_device * d = NULL ;
idev_keyboard * k ;
char * kname ;
int r ;
assert_return ( out , - EINVAL ) ;
assert_return ( s , - EINVAL ) ;
assert_return ( name , - EINVAL ) ;
k = new0 ( idev_keyboard , 1 ) ;
if ( ! k )
return - ENOMEM ;
d = & k - > device ;
k - > device = IDEV_DEVICE_INIT ( & keyboard_vtable , s ) ;
k - > repeat_delay = 250 * USEC_PER_MSEC ;
k - > repeat_rate = 30 * USEC_PER_MSEC ;
/* TODO: add key-repeat configuration */
r = get_kbdctx ( s - > context , & k - > kbdctx ) ;
if ( r < 0 )
return r ;
r = keyboard_update_kbdmap ( k ) ;
if ( r < 0 )
return r ;
2014-11-24 18:32:54 +03:00
r = keyboard_update_kbdtbl ( k ) ;
if ( r < 0 )
return r ;
2014-11-24 20:25:30 +03:00
r = keyboard_resize_bufs ( k , 8 ) ;
if ( r < 0 )
return r ;
2014-08-27 20:34:55 +04:00
r = sd_event_add_time ( s - > context - > event ,
& k - > repeat_timer ,
CLOCK_MONOTONIC ,
0 ,
10 * USEC_PER_MSEC ,
keyboard_repeat_timer_fn ,
k ) ;
if ( r < 0 )
return r ;
r = sd_event_source_set_enabled ( k - > repeat_timer , SD_EVENT_OFF ) ;
if ( r < 0 )
return r ;
kname = strappenda ( " keyboard/ " , name ) ;
r = idev_device_add ( d , kname ) ;
if ( r < 0 )
return r ;
if ( out )
* out = d ;
d = NULL ;
return 0 ;
}
static void keyboard_free ( idev_device * d ) {
idev_keyboard * k = keyboard_from_device ( d ) ;
2014-11-24 18:32:54 +03:00
xkb_compose_state_unref ( k - > xkb_compose ) ;
2014-08-28 14:21:33 +04:00
xkb_state_unref ( k - > xkb_state ) ;
2014-08-27 20:34:55 +04:00
free ( k - > repdata . keyboard . codepoints ) ;
free ( k - > repdata . keyboard . keysyms ) ;
free ( k - > evdata . keyboard . codepoints ) ;
free ( k - > evdata . keyboard . keysyms ) ;
2014-11-24 20:25:30 +03:00
free ( k - > compose_res ) ;
2014-08-27 20:34:55 +04:00
k - > repeat_timer = sd_event_source_unref ( k - > repeat_timer ) ;
2014-11-24 18:32:54 +03:00
k - > kbdtbl = kbdtbl_unref ( k - > kbdtbl ) ;
2014-08-27 20:34:55 +04:00
k - > kbdmap = kbdmap_unref ( k - > kbdmap ) ;
k - > kbdctx = kbdctx_unref ( k - > kbdctx ) ;
free ( k ) ;
}
static int8_t guess_ascii ( struct xkb_state * state , uint32_t code , uint32_t n_syms , const uint32_t * syms ) {
xkb_layout_index_t n_lo , lo ;
xkb_level_index_t lv ;
struct xkb_keymap * keymap ;
const xkb_keysym_t * s ;
int num ;
2014-10-03 14:48:36 +04:00
if ( n_syms = = 1 & & syms [ 0 ] < 128 & & syms [ 0 ] > 0 )
2014-08-27 20:34:55 +04:00
return syms [ 0 ] ;
keymap = xkb_state_get_keymap ( state ) ;
n_lo = xkb_keymap_num_layouts_for_key ( keymap , code + KBDXKB_SHIFT ) ;
for ( lo = 0 ; lo < n_lo ; + + lo ) {
lv = xkb_state_key_get_level ( state , code + KBDXKB_SHIFT , lo ) ;
num = xkb_keymap_key_get_syms_by_level ( keymap , code + KBDXKB_SHIFT , lo , lv , & s ) ;
2014-10-03 14:48:36 +04:00
if ( num = = 1 & & s [ 0 ] < 128 & & s [ 0 ] > 0 )
2014-08-27 20:34:55 +04:00
return s [ 0 ] ;
}
return - 1 ;
}
static int keyboard_fill ( idev_keyboard * k ,
idev_data * dst ,
bool resync ,
uint16_t code ,
uint32_t value ,
uint32_t n_syms ,
const uint32_t * keysyms ) {
idev_data_keyboard * kev ;
uint32_t i ;
2014-11-24 20:25:30 +03:00
int r ;
2014-08-27 20:34:55 +04:00
assert ( dst = = & k - > evdata | | dst = = & k - > repdata ) ;
2014-11-24 20:25:30 +03:00
r = keyboard_resize_bufs ( k , n_syms ) ;
if ( r < 0 )
return r ;
2014-08-27 20:34:55 +04:00
dst - > type = IDEV_DATA_KEYBOARD ;
dst - > resync = resync ;
kev = & dst - > keyboard ;
kev - > ascii = guess_ascii ( k - > xkb_state , code , n_syms , keysyms ) ;
kev - > value = value ;
kev - > keycode = code ;
kev - > mods = 0 ;
kev - > consumed_mods = 0 ;
kev - > n_syms = n_syms ;
memcpy ( kev - > keysyms , keysyms , sizeof ( * keysyms ) * n_syms ) ;
for ( i = 0 ; i < n_syms ; + + i ) {
kev - > codepoints [ i ] = xkb_keysym_to_utf32 ( keysyms [ i ] ) ;
if ( ! kev - > codepoints [ i ] )
kev - > codepoints [ i ] = 0xffffffffUL ;
}
for ( i = 0 ; i < IDEV_KBDMOD_CNT ; + + i ) {
if ( k - > kbdmap - > modmap [ i ] = = XKB_MOD_INVALID )
continue ;
r = xkb_state_mod_index_is_active ( k - > xkb_state , k - > kbdmap - > modmap [ i ] , XKB_STATE_MODS_EFFECTIVE ) ;
if ( r > 0 )
kev - > mods | = 1 < < i ;
r = xkb_state_mod_index_is_consumed ( k - > xkb_state , code + KBDXKB_SHIFT , k - > kbdmap - > modmap [ i ] ) ;
if ( r > 0 )
kev - > consumed_mods | = 1 < < i ;
}
return 0 ;
}
static void keyboard_repeat ( idev_keyboard * k ) {
idev_data * evdata = & k - > evdata ;
idev_data * repdata = & k - > repdata ;
idev_data_keyboard * evkbd = & evdata - > keyboard ;
idev_data_keyboard * repkbd = & repdata - > keyboard ;
const xkb_keysym_t * keysyms ;
idev_device * d = & k - > device ;
bool repeats ;
int r , num ;
if ( evdata - > resync ) {
/*
* We received a re - sync event . During re - sync , any number of
* key - events may have been lost and sync - events may be
* re - ordered . Always disable key - repeat for those events . Any
* following event will trigger it again .
*/
k - > repeating = false ;
keyboard_arm ( k , 0 ) ;
return ;
}
repeats = xkb_keymap_key_repeats ( k - > kbdmap - > xkb_keymap , evkbd - > keycode + KBDXKB_SHIFT ) ;
if ( k - > repeating & & repkbd - > keycode = = evkbd - > keycode ) {
/*
* We received an event for the key we currently repeat . If it
* was released , stop key - repeat . Otherwise , ignore the event .
*/
if ( evkbd - > value = = KBDKEY_UP ) {
k - > repeating = false ;
keyboard_arm ( k , 0 ) ;
}
} else if ( evkbd - > value = = KBDKEY_DOWN & & repeats ) {
/*
* We received a key - down event for a key that repeats . The
* previous condition caught keys we already repeat , so we know
* this is a different key or no key - repeat is running . Start
* new key - repeat .
*/
errno = 0 ;
num = xkb_state_key_get_syms ( k - > xkb_state , evkbd - > keycode + KBDXKB_SHIFT , & keysyms ) ;
if ( num < 0 )
r = errno > 0 ? errno : - EFAULT ;
else
r = keyboard_fill ( k , repdata , false , evkbd - > keycode , KBDKEY_REPEAT , num , keysyms ) ;
if ( r < 0 ) {
2014-11-28 19:09:20 +03:00
log_debug_errno ( r , " idev-keyboard: %s/%s: cannot set key-repeat: %m " ,
d - > session - > name , d - > name ) ;
2014-08-27 20:34:55 +04:00
k - > repeating = false ;
keyboard_arm ( k , 0 ) ;
} else {
k - > repeating = true ;
keyboard_arm ( k , k - > repeat_delay ) ;
}
} else if ( k - > repeating & & ! repeats ) {
/*
* We received an event for a key that does not repeat , but we
* currently repeat a previously received key . The new key is
* usually a modifier , but might be any kind of key . In this
* case , we continue repeating the old key , but update the
* symbols according to the new state .
*/
errno = 0 ;
num = xkb_state_key_get_syms ( k - > xkb_state , repkbd - > keycode + KBDXKB_SHIFT , & keysyms ) ;
if ( num < 0 )
r = errno > 0 ? errno : - EFAULT ;
else
r = keyboard_fill ( k , repdata , false , repkbd - > keycode , KBDKEY_REPEAT , num , keysyms ) ;
if ( r < 0 ) {
2014-11-28 19:09:20 +03:00
log_debug_errno ( r , " idev-keyboard: %s/%s: cannot update key-repeat: %m " ,
d - > session - > name , d - > name ) ;
2014-08-27 20:34:55 +04:00
k - > repeating = false ;
keyboard_arm ( k , 0 ) ;
}
}
}
static int keyboard_feed_evdev ( idev_keyboard * k , idev_data * data ) {
struct input_event * ev = & data - > evdev . event ;
enum xkb_state_component compch ;
2014-11-24 20:25:30 +03:00
enum xkb_compose_status cstatus ;
2014-08-27 20:34:55 +04:00
const xkb_keysym_t * keysyms ;
idev_device * d = & k - > device ;
int num , r ;
if ( ev - > type ! = EV_KEY | | ev - > value > KBDKEY_DOWN )
return 0 ;
/* TODO: We should audit xkb-actions, whether they need @resync as
* flag . Most actions should just be executed , however , there might
* be actions that depend on modifier - orders . Those should be
* suppressed . */
num = xkb_state_key_get_syms ( k - > xkb_state , ev - > code + KBDXKB_SHIFT , & keysyms ) ;
compch = xkb_state_update_key ( k - > xkb_state , ev - > code + KBDXKB_SHIFT , ev - > value ) ;
if ( compch & XKB_STATE_LEDS ) {
/* TODO: update LEDs */
}
2014-09-11 19:20:11 +04:00
if ( num < 0 ) {
r = num ;
2014-08-27 20:34:55 +04:00
goto error ;
2014-09-11 19:20:11 +04:00
}
2014-08-27 20:34:55 +04:00
2014-11-24 20:25:30 +03:00
if ( k - > xkb_compose & & ev - > value = = KBDKEY_DOWN ) {
if ( num = = 1 & & ! data - > resync ) {
xkb_compose_state_feed ( k - > xkb_compose , keysyms [ 0 ] ) ;
cstatus = xkb_compose_state_get_status ( k - > xkb_compose ) ;
} else {
cstatus = XKB_COMPOSE_CANCELLED ;
}
switch ( cstatus ) {
case XKB_COMPOSE_NOTHING :
/* keep produced keysyms and forward unchanged */
break ;
case XKB_COMPOSE_COMPOSING :
/* consumed by compose-state, drop keysym */
keysyms = NULL ;
num = 0 ;
break ;
case XKB_COMPOSE_COMPOSED :
/* compose-state produced sth, replace keysym */
num = keyboard_read_compose ( k , & keysyms ) ;
xkb_compose_state_reset ( k - > xkb_compose ) ;
break ;
case XKB_COMPOSE_CANCELLED :
/* canceled compose, reset, forward cancellation sym */
xkb_compose_state_reset ( k - > xkb_compose ) ;
break ;
}
} else if ( k - > xkb_compose & &
num = = 1 & &
keysyms [ 0 ] = = XKB_KEY_Multi_key & &
! data - > resync & &
ev - > value = = KBDKEY_UP ) {
/* Reset compose state on Multi-Key UP events. This effectively
* requires you to hold the key during the whole sequence . I
* think it ' s pretty handy to avoid accidental
* Compose - sequences , but this may break Compose for disabled
* people . We really need to make this opional ! ( TODO ) */
xkb_compose_state_reset ( k - > xkb_compose ) ;
}
if ( ev - > value = = KBDKEY_UP ) {
/* never produce keysyms for UP */
keysyms = NULL ;
num = 0 ;
}
2014-08-27 20:34:55 +04:00
r = keyboard_fill ( k , & k - > evdata , data - > resync , ev - > code , ev - > value , num , keysyms ) ;
if ( r < 0 )
goto error ;
keyboard_repeat ( k ) ;
return keyboard_raise_data ( k , & k - > evdata ) ;
error :
2014-11-28 19:09:20 +03:00
log_debug_errno ( r , " idev-keyboard: %s/%s: cannot handle event: %m " ,
d - > session - > name , d - > name ) ;
2014-08-27 20:34:55 +04:00
k - > repeating = false ;
keyboard_arm ( k , 0 ) ;
return 0 ;
}
static int keyboard_feed ( idev_device * d , idev_data * data ) {
idev_keyboard * k = keyboard_from_device ( d ) ;
switch ( data - > type ) {
case IDEV_DATA_RESYNC :
/*
* If the underlying device is re - synced , key - events might be
* sent re - ordered . Thus , we don ' t know which key was pressed
* last . Key - repeat might get confused , hence , disable it
* during re - syncs . The first following event will enable it
* again .
*/
k - > repeating = false ;
keyboard_arm ( k , 0 ) ;
return 0 ;
case IDEV_DATA_EVDEV :
return keyboard_feed_evdev ( k , data ) ;
default :
return 0 ;
}
}
static int keyboard_update_kbdmap ( idev_keyboard * k ) {
idev_device * d = & k - > device ;
struct xkb_state * state ;
kbdmap * km ;
int r ;
assert ( k ) ;
km = k - > kbdctx - > kbdmap ;
if ( km = = k - > kbdmap )
return 0 ;
errno = 0 ;
state = xkb_state_new ( km - > xkb_keymap ) ;
if ( ! state ) {
r = errno > 0 ? - errno : - EFAULT ;
goto error ;
}
kbdmap_unref ( k - > kbdmap ) ;
k - > kbdmap = kbdmap_ref ( km ) ;
xkb_state_unref ( k - > xkb_state ) ;
k - > xkb_state = state ;
/* TODO: On state-change, we should trigger a resync so the whole
* event - state is flushed into the new xkb - state . libevdev currently
* does not support that , though . */
return 0 ;
error :
2014-11-28 19:09:20 +03:00
log_debug_errno ( r , " idev-keyboard: %s/%s: cannot adopt new keymap: %m " ,
d - > session - > name , d - > name ) ;
2014-08-27 20:34:55 +04:00
return r ;
}
2014-11-24 18:32:54 +03:00
static int keyboard_update_kbdtbl ( idev_keyboard * k ) {
idev_device * d = & k - > device ;
struct xkb_compose_state * compose = NULL ;
kbdtbl * kt ;
int r ;
assert ( k ) ;
kt = k - > kbdctx - > kbdtbl ;
if ( kt = = k - > kbdtbl )
return 0 ;
if ( kt ) {
errno = 0 ;
compose = xkb_compose_state_new ( kt - > xkb_compose_table , XKB_COMPOSE_STATE_NO_FLAGS ) ;
if ( ! compose ) {
r = errno > 0 ? - errno : - EFAULT ;
goto error ;
}
}
kbdtbl_unref ( k - > kbdtbl ) ;
k - > kbdtbl = kbdtbl_ref ( kt ) ;
xkb_compose_state_unref ( k - > xkb_compose ) ;
k - > xkb_compose = compose ;
return 0 ;
error :
2014-11-28 19:09:20 +03:00
log_debug_errno ( r , " idev-keyboard: %s/%s: cannot adopt new compose table: %m " ,
d - > session - > name , d - > name ) ;
2014-11-24 18:32:54 +03:00
return r ;
}
2014-08-27 20:34:55 +04:00
static const idev_device_vtable keyboard_vtable = {
. free = keyboard_free ,
. feed = keyboard_feed ,
} ;