/**
* @file event-input.c
* /dev/input event provider for the Mode Control Entity
*
* Copyright (c) 2004 - 2011 Nokia Corporation and/or its subsidiary(-ies).
* Copyright (c) 2013 - 2020 Jolla Ltd.
* Copyright (c) 2020 Open Mobile Platform LLC.
*
* @author David Weinehall
* @author Ismo Laitinen
* @author Tapio Rantala
* @author Santtu Lakkala
* @author Jukka Turunen
* @author Mika Laitio
* @author Robin Burchell
* @author Simo Piiroinen
*
* mce is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*
* mce 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 mce. If not, see .
*/
#include "event-input.h"
#include "mce.h"
#include "mce-log.h"
#include "mce-io.h"
#include "mce-lib.h"
#include "mce-conf.h"
#include "mce-dbus.h"
#ifdef ENABLE_DOUBLETAP_EMULATION
# include "mce-setting.h"
#endif
#include "mce-sensorfw.h"
#include "multitouch.h"
#include "evdev.h"
#include
#include
#include
#include
#include
#include
#include
#include
/* ========================================================================= *
* CONSTANTS
* ========================================================================= */
#ifndef SW_CAMERA_LENS_COVER
/** Input layer code for the camera lens cover switch */
# define SW_CAMERA_LENS_COVER 0x09
#endif
#ifndef SW_KEYPAD_SLIDE
/** Input layer code for the keypad slide switch */
# define SW_KEYPAD_SLIDE 0x0a
#endif
#ifndef SW_FRONT_PROXIMITY
/** Input layer code for the front proximity sensor switch */
# define SW_FRONT_PROXIMITY 0x0b
#endif
#ifndef KEY_CAMERA_FOCUS
/** Input layer code for the camera focus button */
# define KEY_CAMERA_FOCUS 0x0210
#endif
#ifndef FF_STATUS_CNT
# ifdef FF_STATUS_MAX
# define FF_STATUS_CNT (FF_STATUS_MAX+1)
# else
# define FF_STATUS_CNT 0
# endif
#endif
#ifndef PWR_CNT
# ifdef PWR_MAX
# define PWR_CNT (PWR_MAX+1)
# else
# define PWR_CNT 0
# endif
#endif
/* ========================================================================= *
* DATA TYPES AND FUNCTION PROTOTYPES
* ========================================================================= */
/* ------------------------------------------------------------------------- *
* GPIO_KEYS -- N900 camera focus key enable/disable policy
* ------------------------------------------------------------------------- */
static void evin_gpio_init (void);
static void evin_gpio_key_enable (unsigned key);
static void evin_gpio_key_disable (unsigned key);
/* ------------------------------------------------------------------------- *
* EVENT_MAPPING -- translate EV_SW events kernel sends to what mce expects
* ------------------------------------------------------------------------- */
/** Structure for holding evdev event translation data
*/
typedef struct
{
/** Event that kernel is emitting */
struct input_event em_kernel_emits;
/** Event mce is expecting to see */
struct input_event em_mce_expects;
} evin_event_mapping_t;
static int evin_event_mapping_guess_event_type (const char *event_code_name);
static bool evin_event_mapping_parse_event (struct input_event *ev, const char *event_code_name);
static bool evin_event_mapping_parse_config (evin_event_mapping_t *self, const char *kernel_emits, const char *mce_expects);
static bool evin_event_mapping_apply (const evin_event_mapping_t *self, struct input_event *ev);
static int evin_event_mapper_rlookup_switch (int expected_by_mce);
static void evin_event_mapper_translate_event (struct input_event *ev);
static void evin_event_mapper_init (void);
static void evin_event_mapper_quit (void);
/* ------------------------------------------------------------------------- *
* EVDEVBITS
* ------------------------------------------------------------------------- */
/** Calculate how many elements an array of longs bitmap needs to
* have enough space for bc items */
#define EVIN_EVDEVBITS_LEN(bc) (((bc)+LONG_BIT-1)/LONG_BIT)
/** Supported codes for one evdev event type
*/
typedef struct
{
/** event type */
int type;
/** event code count for this type */
int cnt;
/** bitmask of supported event codes */
unsigned long bit[0];
} evin_evdevbits_t;
static evin_evdevbits_t *evin_evdevbits_create (int type);
static void evin_evdevbits_delete (evin_evdevbits_t *self);
static int evin_evdevbits_probe (evin_evdevbits_t *self, int fd);
static void evin_evdevbits_clear (evin_evdevbits_t *self);
static int evin_evdevbits_test (const evin_evdevbits_t *self, int bit);
/* ------------------------------------------------------------------------- *
* EVDEVINFO
* ------------------------------------------------------------------------- */
/** Supported event types and codes for an evdev device node
*/
typedef struct
{
/** Array of bitmasks for supported event types */
evin_evdevbits_t *mask[EV_CNT];
} evin_evdevinfo_t;
static int evin_evdevinfo_list_has_entry (const int *list, int entry);
static evin_evdevinfo_t *evin_evdevinfo_create (void);
static void evin_evdevinfo_delete (evin_evdevinfo_t *self);
static int evin_evdevinfo_probe (evin_evdevinfo_t *self, int fd);
static int evin_evdevinfo_has_type (const evin_evdevinfo_t *self, int type);
static int evin_evdevinfo_has_types (const evin_evdevinfo_t *self, const int *types);
static int evin_evdevinfo_has_code (const evin_evdevinfo_t *self, int type, int code);
static int evin_evdevinfo_has_codes (const evin_evdevinfo_t *self, int type, const int *codes);
static int evin_evdevinfo_match_types_ex (const evin_evdevinfo_t *self, const int *types_req, const int *types_ign);
static int evin_evdevinfo_match_types (const evin_evdevinfo_t *self, const int *types);
static int evin_evdevinfo_match_codes_ex (const evin_evdevinfo_t *self, int type, const int *codes, const int *codes_ign);
static int evin_evdevinfo_match_codes (const evin_evdevinfo_t *self, int type, const int *codes);
static bool evin_evdevinfo_is_volumekey_default (const evin_evdevinfo_t *self);
static bool evin_evdevinfo_is_volumekey_hammerhead (const evin_evdevinfo_t *self);
static bool evin_evdevinfo_is_volumekey (const evin_evdevinfo_t *self);
static bool evin_evdevinfo_is_keyboard (const evin_evdevinfo_t *self);
/* ------------------------------------------------------------------------- *
* EVDEVTYPE
* ------------------------------------------------------------------------- */
/** Types of use MCE can have for evdev input devices
*/
typedef enum {
/** Sensors that might look like touch but should be ignored */
EVDEV_REJECT,
/** Touch screen to be tracked and processed */
EVDEV_TOUCH,
/** Mouse to be tracked and processed */
EVDEV_MOUSE,
/** Keys etc that mce needs to track and process */
EVDEV_INPUT,
/** Keys etc that mce itself does not need, tracked only for
* detecting user activity */
EVDEV_ACTIVITY,
/** The rest, mce does not track these */
EVDEV_IGNORE,
/** Button like touch device */
EVDEV_DBLTAP,
/** Proximity sensor */
EVDEV_PS,
/** Ambient light sensor */
EVDEV_ALS,
/** Volume key device */
EVDEV_VOLKEY,
/** Keyboard device */
EVDEV_KEYBOARD,
/** Device type was not explicitly set in configuration */
EVDEV_UNKNOWN,
} evin_evdevtype_t;
static const char *evin_evdevtype_repr (evin_evdevtype_t type);
static evin_evdevtype_t evin_evdevtype_parse (const char *name);
static evin_evdevtype_t evin_evdevtype_from_info (evin_evdevinfo_t *info);
/* ------------------------------------------------------------------------- *
* DOUBLETAP_EMULATION
* ------------------------------------------------------------------------- */
#ifdef ENABLE_DOUBLETAP_EMULATION
static void evin_doubletap_setting_cb (GConfClient *const client, const guint id, GConfEntry *const entry, gpointer const data);
#endif // ENABLE_DOUBLETAP_EMULATION
/* ------------------------------------------------------------------------- *
* INI_FILE_HELPERS
* ------------------------------------------------------------------------- */
static bool evio_is_valid_key_char(int ch);
static char *evio_sanitize_key_name(const char *name);
/* ------------------------------------------------------------------------- *
* EVDEV_IO_MONITORING
* ------------------------------------------------------------------------- */
/** Cached capabilities and type of monitored evdev input device */
typedef struct
{
/** Device name as reported by the driver */
char *ex_name;
/** Cached device node capabilities */
evin_evdevinfo_t *ex_info;
/** Device type from mce point of view */
evin_evdevtype_t ex_type;
/** Name of device that provides keypad slide state*/
gchar *ex_sw_keypad_slide;
/** State data for multitouch/mouse input devices */
mt_state_t *ex_mt_state;
} evin_iomon_extra_t;
static void evin_iomon_extra_delete_cb (void *aptr);
static evin_iomon_extra_t *evin_iomon_extra_create (int fd, const char *name);
// common rate limited activity generation
static void evin_iomon_generate_activity (struct input_event *ev, bool cooked, bool raw);
// event handling by device type
static bool evin_iomon_sw_gestures_allowed (void);
static gboolean evin_iomon_touchscreen_cb (mce_io_mon_t *iomon, gpointer data, gsize bytes_read);
static gboolean evin_iomon_evin_doubletap_cb (mce_io_mon_t *iomon, gpointer data, gsize bytes_read);
static gboolean evin_iomon_keypress_cb (mce_io_mon_t *iomon, gpointer data, gsize bytes_read);
static gboolean evin_iomon_activity_cb (mce_io_mon_t *iomon, gpointer data, gsize bytes_read);
// add/remove devices
static void evin_iomon_device_delete_cb (mce_io_mon_t *iomon);
static void evin_iomon_device_rem_all (void);
static void evin_iomon_device_add (const gchar *path);
static void evin_iomon_device_update (const gchar *path, gboolean add);
static mce_io_mon_t*evin_iomon_lookup_device (const char *name);
static void evin_iomon_device_iterate (evin_evdevtype_t type, GFunc func, gpointer data);
// check initial switch event states
static void evin_iomon_switch_states_update_iter_cb (gpointer io_monitor, gpointer user_data);
static void evin_iomon_switch_states_update (void);
static void evin_iomon_keyboard_state_update_iter_cb (gpointer io_monitor, gpointer user_data);
static void evin_iomon_keyboard_state_update (void);
static void evin_iomon_mouse_state_update_iter_cb (gpointer io_monitor, gpointer user_data);
static void evin_iomon_mouse_state_update (void);
// start/stop io monitoring
static bool evin_iomon_init (void);
static void evin_iomon_quit (void);
/* ------------------------------------------------------------------------- *
* EVDEV_DIRECTORY_MONITORING
* ------------------------------------------------------------------------- */
static void evin_devdir_monitor_changed_cb (GFileMonitor *monitor, GFile *file, GFile *other_file, GFileMonitorEvent event_type, gpointer user_data);
static bool evin_devdir_monitor_init (void);
static void evin_devdir_monitor_quit (void);
/* ------------------------------------------------------------------------- *
* TOUCHSTATE_MONITORING
* ------------------------------------------------------------------------- */
static void evin_touchstate_iomon_iter_cb (gpointer data, gpointer user_data);
static gboolean evin_touchstate_update_cb (gpointer aptr);
static void evin_touchstate_cancel_update (void);
static void evin_touchstate_schedule_update (void);
/* ------------------------------------------------------------------------- *
* INPUT_GRAB -- GENERIC EVDEV INPUT GRAB STATE MACHINE
* ------------------------------------------------------------------------- */
/** Event input policy state */
typedef enum
{
/** Initial value */
EVIN_STATE_UNKNOWN = 0,
/** Input events can be processed normally */
EVIN_STATE_ENABLED = 1,
/** Input events should be ignored */
EVIN_STATE_DISABLED = 2,
} evin_state_t;
static const char *evin_state_repr(evin_state_t state);
static const char *evin_state_to_dbus(evin_state_t state);
typedef struct evin_input_grab_t evin_input_grab_t;
/** State information for generic input grabbing state machine */
struct evin_input_grab_t
{
/** State machine instance name */
const char *ig_name;
/** Current policy decision */
evin_state_t ig_state;
/** Currently touched/down */
bool ig_touching;
/** Was touched/down, delaying release */
bool ig_touched;
/** Input grab is wanted */
bool ig_want_grab;
/** Input grab is allowed */
bool ig_allow_grab;
/** Input grab should be active */
bool ig_have_grab;
/** Input grab is active */
bool ig_real_grab;
/** Delayed release timer */
guint ig_release_id;
/** Delayed release delay */
int ig_release_ms;
/** Callback for notifying grab status changes */
void (*ig_grab_changed_cb)(evin_input_grab_t *self, bool have_grab);
/** Callback for additional release polling */
bool (*ig_release_verify_cb)(evin_input_grab_t *self);
/** Callback for broadcasting policy changes */
void (*ig_state_changed_cb)(evin_input_grab_t *self);
};
static void evin_input_grab_reset (evin_input_grab_t *self);
static gboolean evin_input_grab_release_cb (gpointer aptr);
static void evin_input_grab_start_release_timer (evin_input_grab_t *self);
static void evin_input_grab_cancel_release_timer (evin_input_grab_t *self);
static void evin_input_grab_rethink (evin_input_grab_t *self);
static void evin_input_grab_set_touching (evin_input_grab_t *self, bool touching);
static void evin_input_grab_request_grab (evin_input_grab_t *self, bool want_grab);
static void evin_input_grab_allow_grab (evin_input_grab_t *self, bool allow_grab);
static void evin_input_grab_iomon_cb (gpointer data, gpointer user_data);
/* ------------------------------------------------------------------------- *
* TS_GRAB -- TOUCHSCREEN EVDEV INPUT GRAB STATE MACHINE
* ------------------------------------------------------------------------- */
static void evin_ts_grab_set_led_raw (bool enabled);
static gboolean evin_ts_grab_set_led_cb (gpointer aptr);
static void evin_ts_grab_set_led (bool enabled);
static void evin_ts_grab_rethink_led (void);
static void evin_ts_grab_set_active (gboolean grab);
static bool evin_ts_grab_poll_palm_detect (evin_input_grab_t *ctrl);
static void evin_ts_grab_changed (evin_input_grab_t *ctrl, bool grab);
static void evin_ts_policy_changed (evin_input_grab_t *ctrl);
static void evin_ts_grab_setting_cb (GConfClient *const client, const guint id, GConfEntry *const entry, gpointer const data);
static void evin_ts_grab_init (void);
static void evin_ts_grab_quit (void);
/* ------------------------------------------------------------------------- *
* KP_GRAB -- KEYPAD EVDEV INPUT GRAB STATE MACHINE
* ------------------------------------------------------------------------- */
static void evin_kp_grab_set_active (gboolean grab);
static void evin_kp_grab_changed (evin_input_grab_t *ctrl, bool grab);
static void evin_kp_policy_changed (evin_input_grab_t *ctrl);
static void evin_kp_grab_event_filter_cb (struct input_event *ev);
/* ------------------------------------------------------------------------- *
* DYNAMIC_SETTINGS
* ------------------------------------------------------------------------- */
static void evin_setting_input_grab_rethink (void);
static void evin_setting_cb (GConfClient *const gcc, const guint id, GConfEntry *const entry, gpointer const data);
static void evin_setting_init (void);
static void evin_setting_quit (void);
/* ------------------------------------------------------------------------- *
* DBUS_HOOKS
* ------------------------------------------------------------------------- */
static void evin_dbus_send_keypad_input_policy (DBusMessage *const req);
static gboolean evin_dbus_keypad_input_policy_get_req_cb(DBusMessage *const msg);
static void evin_dbus_send_touch_input_policy (DBusMessage *const req);
static gboolean evin_dbus_touch_input_policy_get_req_cb (DBusMessage *const msg);
static void evin_dbus_init (void);
static void evin_dbus_quit (void);
/* ------------------------------------------------------------------------- *
* EVIN_DATAPIPE
* ------------------------------------------------------------------------- */
static void evin_datapipe_submode_cb (gconstpointer data);
static void evin_datapipe_touch_grab_wanted_cb (gconstpointer data);
static void evin_datapipe_touch_detected_cb (gconstpointer data);
static void evin_datapipe_display_state_curr_cb (gconstpointer data);
static void evin_datapipe_keypad_grab_wanted_cb (gconstpointer data);
static void evin_datapipe_display_state_next_cb (gconstpointer data);
static void evin_datapipe_proximity_sensor_actual_cb(gconstpointer data);
static void evin_datapipe_lid_sensor_filtered_cb (gconstpointer data);
static void evin_datapipe_topmost_window_pid_cb (gconstpointer data);
static void evin_datapipe_call_state_cb (gconstpointer data);
static void evin_datapipe_interaction_expected_cb (gconstpointer data);
static void evin_datapipe_alarm_ui_state_cb (gconstpointer data);
static void evin_datapipe_init (void);
static void evin_datapipe_quit (void);
/* ------------------------------------------------------------------------- *
* MODULE_INIT
* ------------------------------------------------------------------------- */
gboolean mce_input_init (void);
void mce_input_exit (void);
/* ========================================================================= *
* EVIN_DATAPIPE
* ========================================================================= */
/** Cached submode: Initialized to invalid placeholder value */
static submode_t submode = MCE_SUBMODE_INVALID;
/** Cached current display state */
static display_state_t display_state_curr = MCE_DISPLAY_UNDEF;
/** Cached target display state */
static display_state_t display_state_next = MCE_DISPLAY_UNDEF;
/** Cached touch input policy state */
static bool touch_grab_wanted = false;
/** Cached keypad input policy state */
static bool keypad_grab_wanted = false;
/** Cached finger on touchscreen state */
static bool touch_detected = false;
/** Cached (raw) proximity sensor state */
static cover_state_t proximity_sensor_actual = COVER_UNDEF;
/** Cached (filtered) lid sensor state */
static cover_state_t lid_sensor_filtered = COVER_UNDEF;
/** Cached PID of process owning the topmost window on UI */
static int topmost_window_pid = -1;
/** Cached alarm state; assume no active alarms */
static alarm_ui_state_t alarm_ui_state = MCE_ALARM_UI_OFF_INT32;
/** Cached call state */
static call_state_t call_state = CALL_STATE_INVALID;
/** Cached Interaction expected state */
static bool interaction_expected = false;
/* ========================================================================= *
* GPIO_KEYS
* ========================================================================= */
/** Can GPIO key interrupts be disabled? */
static gboolean evin_gpio_key_disable_exists = FALSE;
/** Check if enable/disable controls for gpio keys exist
*/
static void
evin_gpio_init(void)
{
evin_gpio_key_disable_exists = (g_access(GPIO_KEY_DISABLE_PATH, W_OK) == 0);
}
/** Enable the specified GPIO key
*
* Non-existing or already enabled keys are silently ignored
*
* @param key The key to enable
*/
static void
evin_gpio_key_enable(unsigned key)
{
gchar *disabled_keys_old = NULL;
gchar *disabled_keys_new = NULL;
gulong *bitmask = NULL;
gsize bitmasklen;
if( !mce_read_string_from_file(GPIO_KEY_DISABLE_PATH, &disabled_keys_old) )
goto EXIT;
bitmasklen = (KEY_CNT / bitsize_of(*bitmask)) +
((KEY_CNT % bitsize_of(*bitmask)) ? 1 : 0);
bitmask = g_malloc0(bitmasklen * sizeof (*bitmask));
if( !string_to_bitfield(disabled_keys_old, &bitmask, bitmasklen) )
goto EXIT;
clear_bit(key, &bitmask);
if( !(disabled_keys_new = bitfield_to_string(bitmask, bitmasklen)) )
goto EXIT;
mce_write_string_to_file(GPIO_KEY_DISABLE_PATH, disabled_keys_new);
EXIT:
g_free(disabled_keys_old);
g_free(bitmask);
g_free(disabled_keys_new);
return;
}
/** Disable the specified GPIO key/switch
*
* non-existing or already disabled keys/switches are silently ignored
*
* @param key The key/switch to disable
*/
static void
evin_gpio_key_disable(unsigned key)
{
gchar *disabled_keys_old = NULL;
gchar *disabled_keys_new = NULL;
gulong *bitmask = NULL;
gsize bitmasklen;
if( !mce_read_string_from_file(GPIO_KEY_DISABLE_PATH, &disabled_keys_old) )
goto EXIT;
bitmasklen = (KEY_CNT / bitsize_of(*bitmask)) +
((KEY_CNT % bitsize_of(*bitmask)) ? 1 : 0);
bitmask = g_malloc0(bitmasklen * sizeof (*bitmask));
if( !string_to_bitfield(disabled_keys_old, &bitmask, bitmasklen) )
goto EXIT;
set_bit(key, &bitmask);
if( !(disabled_keys_new = bitfield_to_string(bitmask, bitmasklen)) )
goto EXIT;
mce_write_string_to_file(GPIO_KEY_DISABLE_PATH, disabled_keys_new);
EXIT:
g_free(disabled_keys_old);
g_free(bitmask);
g_free(disabled_keys_new);
return;
}
/** Disable/enable gpio keys based on submode changes
*
* @param data The submode stored in a pointer
*/
static void
evin_datapipe_submode_cb(gconstpointer data)
{
submode_t prev = submode;
submode = GPOINTER_TO_INT(data);
if( prev != submode )
mce_log(LL_DEBUG, "submode: %s", submode_change_repr(prev, submode));
/* If the tklock is enabled, disable the camera focus interrupts,
* since we don't use them anyway
*/
if( evin_gpio_key_disable_exists ) {
submode_t tklock_prev = (prev & MCE_SUBMODE_TKLOCK);
submode_t tklock_curr = (submode & MCE_SUBMODE_TKLOCK);
if( tklock_prev != tklock_curr ) {
if( tklock_curr )
evin_gpio_key_disable(KEY_CAMERA_FOCUS);
else
evin_gpio_key_enable(KEY_CAMERA_FOCUS);
}
}
}
/* ========================================================================= *
* EVENT_MAPPING
* ========================================================================= */
/** Guess event type from name of the event code
*
* @param event_code_name name of the event e.g. "SW_KEYPAD_SLIDE"
*
* @return event type id e.g. EV_SW, or -1 if unknown
*/
static int
evin_event_mapping_guess_event_type(const char *event_code_name)
{
int etype = -1;
if( !event_code_name )
goto EXIT;
/* We are interested only in EV_KEY and EV_SW events */
if( !strncmp(event_code_name, "KEY_", 4) )
etype = EV_KEY;
else if( !strncmp(event_code_name, "BTN_", 4) )
etype = EV_KEY;
else if( !strncmp(event_code_name, "SW_", 3) )
etype = EV_SW;
EXIT:
return etype;
}
/** Fill in event type and code based on name of the event code
*
* @param event_code_name name of the event e.g. "SW_KEYPAD_SLIDE"
*
* @return true on success, or false on failure
*/
static bool
evin_event_mapping_parse_event(struct input_event *ev, const char *event_code_name)
{
bool success = false;
int etype = evin_event_mapping_guess_event_type(event_code_name);
if( etype < 0 )
goto EXIT;
int ecode = evdev_lookup_event_code(etype, event_code_name);
if( ecode < 0 )
goto EXIT;
ev->type = etype;
ev->code = ecode;
success = true;
EXIT:
return success;
}
/** Fill in event mapping structure from source and target event code names
*
* @param self pointer to a mapping structure to fill in
* @param kernel_emits name of the event kernel will be emitting
* @param mce_expects mame of the event mce is expecting instead
*
* @return true on success, or false on failure
*/
static bool
evin_event_mapping_parse_config(evin_event_mapping_t *self,
const char *kernel_emits,
const char *mce_expects)
{
bool success = false;
if( !evin_event_mapping_parse_event(&self->em_kernel_emits, kernel_emits) )
goto EXIT;
if( !evin_event_mapping_parse_event(&self->em_mce_expects, mce_expects) )
goto EXIT;
success = true;
EXIT:
return success;
}
/** Translate event if applicable
*
* @param self pointer to a mapping structure to use
* @param ev input event to modify
*
* @param true if event was modified, false otherwise
*/
static bool
evin_event_mapping_apply(const evin_event_mapping_t *self, struct input_event *ev)
{
bool applied = false;
if( self->em_kernel_emits.type != ev->type )
goto EXIT;
if( self->em_kernel_emits.code != ev->code )
goto EXIT;
mce_log(LL_DEBUG, "map: %s:%s -> %s:%s",
evdev_get_event_type_name(self->em_kernel_emits.type),
evdev_get_event_code_name(self->em_kernel_emits.type,
self->em_kernel_emits.code),
evdev_get_event_type_name(self->em_mce_expects.type),
evdev_get_event_code_name(self->em_mce_expects.type,
self->em_mce_expects.code));
ev->type = self->em_mce_expects.type;
ev->code = self->em_mce_expects.code;
applied = true;
EXIT:
return applied;
}
/** Lookup table for translatable events */
static evin_event_mapping_t *evin_event_mapper_lut = 0;
/** Number of entries in evin_event_mapper_lut */
static size_t evin_event_mapper_cnt = 0;
/** Reverse lookup switch kernel is emitting from switch mce is expecting
*
* Note: For use from event switch initial state evaluation only.
*
* @param expected_by_mce event code of SW_xxx kind mce expect to see
*
* @return event code of SW_xxx kind kernel might be sending
*/
static int
evin_event_mapper_rlookup_switch(int expected_by_mce)
{
/* Assume kernel emits events mce is expecting to see */
int emitted_by_kernel = expected_by_mce;
/* If emitted_by_kernel -> expected_by_mce mapping exist, use it */
for( size_t i = 0; i < evin_event_mapper_cnt; ++i ) {
evin_event_mapping_t *map = evin_event_mapper_lut + i;
if( map->em_kernel_emits.type != EV_SW )
continue;
if( map->em_mce_expects.type != EV_SW )
continue;
if( map->em_mce_expects.code != expected_by_mce )
continue;
emitted_by_kernel = map->em_kernel_emits.code;
goto EXIT;
}
/* But if there is rule for mapping the event for something
* else, it should be ignored instead of used as is */
for( size_t i = 0; i < evin_event_mapper_cnt; ++i ) {
evin_event_mapping_t *map = evin_event_mapper_lut + i;
if( map->em_kernel_emits.type != EV_SW )
continue;
if( map->em_mce_expects.type != EV_SW )
continue;
if( map->em_kernel_emits.code != expected_by_mce )
continue;
/* Assumption: SW_MAX is valid index for ioctl() probing,
* but is not an alias for anything that kernel
* would report.
*/
emitted_by_kernel = SW_MAX;
goto EXIT;
}
EXIT:
return emitted_by_kernel;
}
/** Translate event emitted by kernel to something mce is expecting to see
*
* @param ev Input event to translate
*/
static void
evin_event_mapper_translate_event(struct input_event *ev)
{
if( !ev )
goto EXIT;
/* Skip if there is no translation lookup table */
if( !evin_event_mapper_lut )
goto EXIT;
/* We want to process key and switch events, but under all
* potentially high frequency events should be skipped */
switch( ev->type ) {
case EV_KEY:
case EV_SW:
break;
default:
goto EXIT;
}
/* Try to find suitable mapping from lookup table */
for( size_t i = 0; i < evin_event_mapper_cnt; ++i ) {
evin_event_mapping_t *map = evin_event_mapper_lut + i;
if( evin_event_mapping_apply(map, ev) )
break;
}
EXIT:
return;
}
/** Initialize event translation lookup table
*/
static void
evin_event_mapper_init(void)
{
static const char grp[] = MCE_CONF_EVDEV_GROUP;
gchar **keys = 0;
gsize count = 0;
gsize valid = 0;
if( !mce_conf_has_group(grp) )
goto EXIT;
keys = mce_conf_get_keys(grp, &count);
if( !keys || !count )
goto EXIT;
evin_event_mapper_lut = calloc(count, sizeof *evin_event_mapper_lut);
for( gsize i = 0; i < count; ++i ) {
evin_event_mapping_t *map = evin_event_mapper_lut + valid;
const gchar *key = keys[i];
gchar *val = mce_conf_get_string(grp, key, 0);
if( val && evin_event_mapping_parse_config(map, key, val) )
++valid;
g_free(val);
}
evin_event_mapper_cnt = valid;
EXIT:
/* Remove also lookup table pointer if there are no entries */
if( !evin_event_mapper_cnt )
evin_event_mapper_quit();
g_strfreev(keys);
mce_log(LL_DEBUG, "EVDEV MAPS: %zd", evin_event_mapper_cnt);
return;
}
/** Release event translation lookup table
*/
static void
evin_event_mapper_quit(void)
{
free(evin_event_mapper_lut),
evin_event_mapper_lut = 0,
evin_event_mapper_cnt = 0;
}
/* ------------------------------------------------------------------------- *
* EVDEVBITS
* ------------------------------------------------------------------------- */
/** Create empty event code bitmap for one evdev event type
*
* @param type evdev event type
*
* @return evin_evdevbits_t object, or NULL for types not needed by mce
*/
static evin_evdevbits_t *
evin_evdevbits_create(int type)
{
evin_evdevbits_t *self = 0;
int cnt = 0;
switch( type ) {
case EV_SYN: cnt = EV_CNT; break;
case EV_KEY: cnt = KEY_CNT; break;
case EV_REL: cnt = REL_CNT; break;
case EV_ABS: cnt = ABS_CNT; break;
case EV_MSC: cnt = MSC_CNT; break;
case EV_SW: cnt = SW_CNT; break;
#if 0
case EV_LED: cnt = LED_CNT; break;
case EV_SND: cnt = SND_CNT; break;
case EV_REP: cnt = REP_CNT; break;
case EV_FF: cnt = FF_CNT; break;
case EV_PWR: cnt = PWR_CNT; break;
case EV_FF_STATUS: cnt = FF_STATUS_CNT; break;
#endif
default: break;
}
if( cnt > 0 ) {
int len = EVIN_EVDEVBITS_LEN(cnt);
self = g_malloc0(sizeof *self + len * sizeof *self->bit);
self->type = type;
self->cnt = cnt;
}
return self;
}
/** Delete evdev event code bitmap
*
* @param self evin_evdevbits_t object, or NULL
*/
static void
evin_evdevbits_delete(evin_evdevbits_t *self)
{
g_free(self);
}
/** Clear bits in evdev event code bitmap
*
* @param self evin_evdevbits_t object, or NULL
*/
static void
evin_evdevbits_clear(evin_evdevbits_t *self)
{
if( self ) {
int len = EVIN_EVDEVBITS_LEN(self->cnt);
memset(self->bit, 0, len * sizeof *self->bit);
}
}
/** Read supported codes from file descriptor
*
* @param self evin_evdevbits_t object, or NULL
* @param fd file descriptor to probe data from
*
* @return 0 on success, -1 on errors
*/
static int
evin_evdevbits_probe(evin_evdevbits_t *self, int fd)
{
int res = 0;
if( self && ioctl(fd, EVIOCGBIT(self->type, self->cnt), self->bit) == -1 ) {
mce_log(LL_WARN, "EVIOCGBIT(%s, %d): %m",
evdev_get_event_type_name(self->type), self->cnt);
evin_evdevbits_clear(self);
res = -1;
}
return res;
}
/** Test if evdev event code is set in bitmap
*
* @param self evin_evdevbits_t object, or NULL
* @param bit event code to check
*
* @return 1 if code is supported, 0 otherwise
*/
static int
evin_evdevbits_test(const evin_evdevbits_t *self, int bit)
{
int res = 0;
if( self && (unsigned)bit < (unsigned)self->cnt ) {
int i = bit / LONG_BIT;
unsigned long m = 1ul << (bit % LONG_BIT);
if( self->bit[i] & m ) res = 1;
}
return res;
}
/* ------------------------------------------------------------------------- *
* EVDEVINFO
* ------------------------------------------------------------------------- */
/** Helper for checking if array of integers contains a particular value
*
* @param list array of ints, terminated with -1
* @param entry value to check
*
* @return 1 if value is present in the list, 0 if not
*/
static int
evin_evdevinfo_list_has_entry(const int *list, int entry)
{
if( !list )
return 0;
for( int i = 0; list[i] != -1; ++i ) {
if( list[i] == entry )
return 1;
}
return 0;
}
/** Create evdev information object
*
* @return evin_evdevinfo_t object
*/
static evin_evdevinfo_t *
evin_evdevinfo_create(void)
{
evin_evdevinfo_t *self = g_malloc0(sizeof *self);
for( int i = 0; i < EV_CNT; ++i )
self->mask[i] = evin_evdevbits_create(i);
return self;
}
/** Delete evdev information object
*
* @param self evin_evdevinfo_t object
*/
static void
evin_evdevinfo_delete(evin_evdevinfo_t *self)
{
if( self ) {
for( int i = 0; i < EV_CNT; ++i )
evin_evdevbits_delete(self->mask[i]);
g_free(self);
}
}
/** Check if event type is supported
*
* @param self evin_evdevinfo_t object
* @param type evdev event type
*
* @return 1 if event type is supported, 0 otherwise
*/
static int
evin_evdevinfo_has_type(const evin_evdevinfo_t *self, int type)
{
int res = 0;
if( (unsigned)type < EV_CNT )
res = evin_evdevbits_test(self->mask[0], type);
return res;
}
/** Check if any of given event types are supported
*
* @param self evin_evdevinfo_t object
* @param types array of evdev event types
*
* @return 1 if at least on of the types is supported, 0 otherwise
*/
static int
evin_evdevinfo_has_types(const evin_evdevinfo_t *self, const int *types)
{
int res = 0;
for( size_t i = 0; types[i] >= 0; ++i ) {
if( (res = evin_evdevinfo_has_type(self, types[i])) )
break;
}
return res;
}
/** Check if event code is supported
*
* @param self evin_evdevinfo_t object
* @param type evdev event type
* @param code evdev event code
*
* @return 1 if event code for type is supported, 0 otherwise
*/
static int
evin_evdevinfo_has_code(const evin_evdevinfo_t *self, int type, int code)
{
int res = 0;
if( evin_evdevinfo_has_type(self, type) )
res = evin_evdevbits_test(self->mask[type], code);
return res;
}
/** Check if any of given event codes are supported
*
* @param self evin_evdevinfo_t object
* @param type evdev event type
* @param code array of evdev event codes
*
* @return 1 if at least on of the event codes for type is supported, 0 otherwise
*/
static int
evin_evdevinfo_has_codes(const evin_evdevinfo_t *self, int type, const int *codes)
{
int res = 0;
if( evin_evdevinfo_has_type(self, type) ) {
for( size_t i = 0; codes[i] != -1; ++i ) {
if( (res = evin_evdevbits_test(self->mask[type], codes[i])) )
break;
}
}
return res;
}
/** Check if all of the listed types and only the listed types are supported
*
* @param self evin_evdevinfo_t object
* @param types_req array of required evdev event types, terminated with -1
* @param types_ign array event types to ignore, terminated with -1; or null
*
* @return 1 if all of types and only types are supported, 0 otherwise
*/
static int
evin_evdevinfo_match_types_ex(const evin_evdevinfo_t *self,
const int *types_req, const int *types_ign)
{
for( int etype = 1; etype < EV_CNT; ++etype ) {
if( evin_evdevinfo_list_has_entry(types_ign, etype) )
continue;
int have = evin_evdevinfo_has_type(self, etype);
int want = evin_evdevinfo_list_has_entry(types_req, etype);
if( have != want )
return 0;
}
return 1;
}
/** Check if all of the listed types and only the listed types are supported
*
* @param self evin_evdevinfo_t object
* @param types array of evdev event types, terminated with -1
*
* @return 1 if all of types and only types are supported, 0 otherwise
*/
static int
evin_evdevinfo_match_types(const evin_evdevinfo_t *self, const int *types)
{
return evin_evdevinfo_match_types_ex(self, types, 0);
}
/** Check if all of the listed codes and only the listed codes are supported
*
* @param self evin_evdevinfo_t object
* @param types evdev event type
* @param codes array of evdev event codes, terminated with -1
* @param codes_ign array of dontcare evdev event codes, terminated with -1
*
* @return 1 if all of codes and only codes are supported, 0 otherwise
*/
static int
evin_evdevinfo_match_codes_ex(const evin_evdevinfo_t *self, int type,
const int *codes, const int *codes_ign)
{
for( int ecode = 0; ecode < KEY_CNT; ++ecode ) {
if( evin_evdevinfo_list_has_entry(codes_ign, ecode) )
continue;
int have = evin_evdevinfo_has_code(self, type, ecode);
int want = evin_evdevinfo_list_has_entry(codes, ecode);
if( have != want )
return 0;
}
return 1;
}
/** Check if all of the listed codes and only the listed codes are supported
*
* @param self evin_evdevinfo_t object
* @param types evdev event type
* @param codes array of evdev event codes, terminated with -1
*
* @return 1 if all of codes and only codes are supported, 0 otherwise
*/
static int
evin_evdevinfo_match_codes(const evin_evdevinfo_t *self, int type, const int *codes)
{
return evin_evdevinfo_match_codes_ex(self, type, codes, 0);
}
#ifndef KEY_CAMERA_SNAPSHOT
# define KEY_CAMERA_SNAPSHOT 0x02fe
#endif
/** Test if input device sends only volume key events
*
* @param self evin_evdevinfo_t object
*
* @return true if info matches volume keys only device, false otherwise
*/
static bool
evin_evdevinfo_is_volumekey_default(const evin_evdevinfo_t *self)
{
/* Emits volume key events, and only volume key events */
static const int wanted_types[] = {
EV_KEY,
-1
};
static const int wanted_key_codes[] = {
KEY_VOLUMEDOWN,
KEY_VOLUMEUP,
-1
};
static const int ignored_key_codes[] = {
/* Getting some key blocked/unblocked based on
* volume key policy is less harmful than leaving
* the volume keys active all the time. */
KEY_CAMERA_FOCUS,
KEY_CAMERA_SNAPSHOT,
KEY_CAMERA,
/* Home key should be handled by mce and can be
* ignored as well. */
KEY_HOME,
-1
};
/* Except we do not care if autorepeat controls are there or not */
static const int ignored_types[] = {
EV_REP,
-1
};
return (evin_evdevinfo_match_types_ex(self, wanted_types, ignored_types) &&
evin_evdevinfo_match_codes_ex(self, EV_KEY, wanted_key_codes,
ignored_key_codes));
}
/** Test if input device is like volume key device in Nexus 5
*
* In addition to volume keys, the input device also reports
* lid sensor state.
*
* @param self evin_evdevinfo_t object
*
* @return true if info matches Nexus 5 volume key device, false otherwise
*/
static bool
evin_evdevinfo_is_volumekey_hammerhead(const evin_evdevinfo_t *self)
{
static const int wanted_types[] = {
EV_KEY,
EV_SW,
-1
};
static const int wanted_key_codes[] = {
KEY_VOLUMEDOWN,
KEY_VOLUMEUP,
-1
};
static const int ignored_key_codes[] = {
/* Getting camera focus blocked/unblocked based on
* volume key policy is less harmful than leaving
* the volume keys active all the time. */
KEY_CAMERA_FOCUS,
-1
};
static const int wanted_sw_codes[] = {
SW_LID, // magnetic lid cover sensor
-1
};
return (evin_evdevinfo_match_types(self, wanted_types) &&
evin_evdevinfo_match_codes_ex(self, EV_KEY, wanted_key_codes,
ignored_key_codes) &&
evin_evdevinfo_match_codes(self, EV_SW, wanted_sw_codes));
}
/** Test if input device is grabbable volume key device
*
* @param self evin_evdevinfo_t object
*
* @return true if info matches grabbable volume key device, false otherwise
*/
static bool
evin_evdevinfo_is_volumekey(const evin_evdevinfo_t *self)
{
/* Note: If device node - in addition to volume keys - serves
* events that should always be made available to other
* processes too (KEY_POWER, SW_HEADPHONE_INSERT, etc),
* it should not be detected as grabbable volume key.
*/
return (evin_evdevinfo_is_volumekey_default(self) ||
evin_evdevinfo_is_volumekey_hammerhead(self));
}
static bool
evin_evdevinfo_is_keyboard(const evin_evdevinfo_t *self)
{
return (evin_evdevinfo_has_type(self, EV_KEY) &&
evin_evdevinfo_has_code(self, EV_KEY, KEY_Q) &&
evin_evdevinfo_has_code(self, EV_KEY, KEY_P));
}
/** Fill in evdev data by probing file descriptor
*
* @param self evin_evdevinfo_t object
* @param fd file descriptor to probe data from
*
* @return 0 on success, -1 on errors
*/
static int
evin_evdevinfo_probe(evin_evdevinfo_t *self, int fd)
{
int res = evin_evdevbits_probe(self->mask[0], fd);
for( int i = 1; i < EV_CNT; ++i ) {
if( evin_evdevbits_test(self->mask[0], i) )
evin_evdevbits_probe(self->mask[i], fd);
else
evin_evdevbits_clear(self->mask[i]);
}
return res;
}
/* ========================================================================= *
* EVDEVTYPE
* ========================================================================= */
/** Get Human readable evdev classifications for debugging purposes
*/
static const char *
evin_evdevtype_repr(evin_evdevtype_t type)
{
static const char * const lut[] =
{
[EVDEV_REJECT] = "REJECT",
[EVDEV_TOUCH] = "TOUCHSCREEN",
[EVDEV_MOUSE] = "MOUSE",
[EVDEV_INPUT] = "KEY, BUTTON or SWITCH",
[EVDEV_ACTIVITY] = "USER ACTIVITY ONLY",
[EVDEV_IGNORE] = "IGNORE",
[EVDEV_DBLTAP] = "DOUBLE TAP",
[EVDEV_PS] = "PROXIMITY SENSOR",
[EVDEV_ALS] = "AMBIENT LIGHT SENSOR",
[EVDEV_VOLKEY] = "VOLUME KEYS",
[EVDEV_KEYBOARD] = "KEYBOARD",
[EVDEV_UNKNOWN] = "UNKNOWN",
};
return lut[type];
}
/** Convert textual evdev classification from config file to enum value
*
* @param name evdev device type from configuration file
*
* @return Return corresponding device type id, or EVDEV_UNKNOWN
*/
static evin_evdevtype_t
evin_evdevtype_parse(const char *name)
{
static const struct
{
const char *key;
int val;
} lut[] =
{
{ "REJECT", EVDEV_REJECT, },
{ "TOUCH", EVDEV_TOUCH, },
{ "MOUSE", EVDEV_MOUSE, },
{ "INPUT", EVDEV_INPUT, },
{ "ACTIVITY", EVDEV_ACTIVITY, },
{ "IGNORE", EVDEV_IGNORE, },
{ "DOUBLE_TAP", EVDEV_DBLTAP, },
{ "DBLTAP", EVDEV_DBLTAP, },
{ "PS", EVDEV_PS, },
{ "PROXIMITY_SENSOR", EVDEV_PS, },
{ "ALS", EVDEV_ALS, },
{ "LIGHT_SENSOR", EVDEV_ALS, },
{ "VOLKEY", EVDEV_VOLKEY, },
{ "VOLUME_KEYS", EVDEV_VOLKEY, },
{ "KEYBOARD", EVDEV_KEYBOARD, },
/* Note: EVDEV_UNKNOWN is left out on purpose as it
* signifies parsing error and thus is not
* meant to be used in configuration files. */
};
evin_evdevtype_t type = EVDEV_UNKNOWN;
if( !name )
goto EXIT;
for( size_t i = 0; i < G_N_ELEMENTS(lut); ++i ) {
if( strcmp(lut[i].key, name) )
continue;
type = lut[i].val;
break;
}
EXIT:
return type;
}
/** Use heuristics to determine what mce should do with an evdev device node
*
* @param info Event types and codes emitted by a evdev device
*
* @return one of EVDEV_TOUCH, EVDEV_INPUT, ...
*/
static evin_evdevtype_t
evin_evdevtype_from_info(evin_evdevinfo_t *info)
{
/* EV_ABS probing arrays for ALS/PS detection */
static const int abs_only[] = { EV_ABS, -1 };
static const int misc_only[] = { ABS_MISC, -1 };
static const int dist_only[] = { ABS_DISTANCE, -1 };
/* EV_KEY probing arrays for detecting input devices
* that report double tap gestures as power key events */
static const int key_only[] = { EV_KEY, -1 };
static const int dbltap_lut[] = {
KEY_POWER,
KEY_MENU,
KEY_BACK,
KEY_HOMEPAGE,
-1
};
/* Key events mce is interested in */
static const int keypad_lut[] = {
KEY_CAMERA,
KEY_CAMERA_FOCUS,
KEY_POWER,
KEY_SCREENLOCK,
KEY_VOLUMEDOWN,
KEY_VOLUMEUP,
-1
};
/* Switch events mce is interested in */
static const int switch_lut[] = {
SW_CAMERA_LENS_COVER,
SW_FRONT_PROXIMITY,
SW_HEADPHONE_INSERT,
SW_KEYPAD_SLIDE,
SW_LID,
SW_LINEOUT_INSERT,
SW_MICROPHONE_INSERT,
SW_VIDEOOUT_INSERT,
-1
};
/* Event classes that could be due to "user activity" */
static const int misc_lut[] = {
EV_KEY,
EV_REL,
EV_ABS,
EV_MSC,
EV_SW,
-1
};
/* All event classes except EV_ABS */
static const int all_but_abs_lut[] = {
EV_KEY,
EV_REL,
EV_MSC,
EV_SW,
EV_LED,
EV_SND,
EV_REP,
EV_FF,
EV_PWR,
EV_FF_STATUS,
-1
};
int res = EVDEV_IGNORE;
/* Ambient light and proximity sensor inputs */
if( evin_evdevinfo_match_types(info, abs_only) ) {
if( evin_evdevinfo_match_codes(info, EV_ABS, misc_only) ) {
// only EV_ABS:ABS_MISC -> ALS
res = EVDEV_ALS;
goto cleanup;
}
if( evin_evdevinfo_match_codes(info, EV_ABS, dist_only) ) {
// only EV_ABS:ABS_DISTANCE -> PS
res = EVDEV_PS;
goto cleanup;
}
}
/* MCE has no use for accelerometers etc */
if( evin_evdevinfo_has_code(info, EV_KEY, BTN_Z) ||
evin_evdevinfo_has_code(info, EV_REL, REL_Z) ||
evin_evdevinfo_has_code(info, EV_ABS, ABS_Z) ) {
// 3d sensor like accelorometer/magnetometer
res = EVDEV_REJECT;
goto cleanup;
}
/* While MCE mostly uses touchscreen inputs only for
* "user activity" monitoring, the touch devices
* generate a lot of events and mce has mechanism in
* place to avoid processing all of them */
if( evin_evdevinfo_has_code(info, EV_KEY, BTN_TOUCH) &&
evin_evdevinfo_has_code(info, EV_ABS, ABS_X) &&
evin_evdevinfo_has_code(info, EV_ABS, ABS_Y) ) {
// singletouch protocol
res = EVDEV_TOUCH;
goto cleanup;
}
if( evin_evdevinfo_has_code(info, EV_ABS, ABS_MT_POSITION_X) &&
evin_evdevinfo_has_code(info, EV_ABS, ABS_MT_POSITION_Y) ) {
// multitouch protocol
res = EVDEV_TOUCH;
goto cleanup;
}
/* In SDK we might bump into mouse devices, track them
* as if they were touch screen devices */
if( evin_evdevinfo_has_code(info, EV_KEY, BTN_MOUSE) &&
evin_evdevinfo_has_code(info, EV_REL, REL_X) &&
evin_evdevinfo_has_code(info, EV_REL, REL_Y) ) {
// mouse
res = EVDEV_MOUSE;
goto cleanup;
}
/* Touchscreen that emits power key events on double tap */
if( evin_evdevinfo_match_types(info, key_only) &&
evin_evdevinfo_match_codes(info, EV_KEY, dbltap_lut) ) {
res = EVDEV_DBLTAP;
goto cleanup;
}
/* Presense of keyboard devices needs to be signaled */
if( evin_evdevinfo_is_keyboard(info) ) {
res = EVDEV_KEYBOARD;
goto cleanup;
}
/* Volume keys only input devices can be grabbed */
if( evin_evdevinfo_is_volumekey(info) ) {
res = EVDEV_VOLKEY;
goto cleanup;
}
/* Some keys and swithes are processed at mce level */
if( evin_evdevinfo_has_codes(info, EV_KEY, keypad_lut ) ||
evin_evdevinfo_has_codes(info, EV_SW, switch_lut ) ) {
res = EVDEV_INPUT;
goto cleanup;
}
/* Also gesture events from an input device that does not
* emit touch events need to be handled as double taps etc. */
if( evin_evdevinfo_has_code(info, EV_MSC, MSC_GESTURE) ) {
res = EVDEV_DBLTAP;
goto cleanup;
}
/* Assume that: devices that support only ABS_DISTANCE are
* proximity sensors and devices that support only ABS_MISC
* are ambient light sensors that are handled via libhybris
* in more appropriate place and should not be used for
* "user activity" tracking. */
if( evin_evdevinfo_has_type(info, EV_ABS) &&
!evin_evdevinfo_has_types(info, all_but_abs_lut) ) {
int maybe_als = evin_evdevinfo_has_code(info, EV_ABS, ABS_MISC);
int maybe_ps = evin_evdevinfo_has_code(info, EV_ABS, ABS_DISTANCE);
// supports one of the two, but not both ...
if( maybe_als != maybe_ps ) {
for( int code = 0; ; ++code ) {
switch( code ) {
case ABS_CNT:
// ... and no other events supported
res = EVDEV_REJECT;
goto cleanup;
case ABS_DISTANCE:
case ABS_MISC:
continue;
default:
break;
}
if( evin_evdevinfo_has_code(info, EV_ABS, code) )
break;
}
}
}
/* Ignore devices that emit only X or Y values */
if( (evin_evdevinfo_has_code(info, EV_KEY, BTN_X) !=
evin_evdevinfo_has_code(info, EV_KEY, BTN_Y)) ||
(evin_evdevinfo_has_code(info, EV_REL, REL_X) !=
evin_evdevinfo_has_code(info, EV_REL, REL_Y)) ||
(evin_evdevinfo_has_code(info, EV_ABS, ABS_X) !=
evin_evdevinfo_has_code(info, EV_ABS, ABS_Y)) ) {
// assume unknown 1d sensor like als/proximity
res = EVDEV_REJECT;
goto cleanup;
}
/* Track events that can be considered as "user activity" */
if( evin_evdevinfo_has_types(info, misc_lut) ) {
res = EVDEV_ACTIVITY;
goto cleanup;
}
cleanup:
return res;
}
/* ------------------------------------------------------------------------- *
* DOUBLETAP_EMULATION
* ------------------------------------------------------------------------- */
#ifdef ENABLE_DOUBLETAP_EMULATION
/** Fake doubletap policy */
static gboolean evin_doubletap_emulation_enabled = MCE_DEFAULT_USE_FAKE_DOUBLETAP;
static guint evin_doubletap_emulation_enabled_setting_id = 0;
/** Callback for handling changes to fake doubletap configuration
*
* @param client (not used)
* @param id (not used)
* @param entry GConf entry that changed
* @param data (not used)
*/
static void
evin_doubletap_setting_cb(GConfClient *const client, const guint id,
GConfEntry *const entry, gpointer const data)
{
(void)client; (void)id; (void)data;
gboolean enabled = evin_doubletap_emulation_enabled;
const GConfValue *value = 0;
if( entry && (value = gconf_entry_get_value(entry)) ) {
if( value->type == GCONF_VALUE_BOOL )
enabled = gconf_value_get_bool(value);
}
if( evin_doubletap_emulation_enabled != enabled ) {
mce_log(LL_NOTICE, "use fake doubletap change: %d -> %d",
evin_doubletap_emulation_enabled, enabled);
evin_doubletap_emulation_enabled = enabled;
}
}
#endif /* ENABLE_DOUBLETAP_EMULATION */
/* ========================================================================= *
* INI_FILE_HELPERS
* ========================================================================= */
/** Predicate for: character can be used in glib keyfile key keyname
*
* @param ch character
*
* @return true if character can be used as is, false otherwise
*/
static bool
evio_is_valid_key_char(int ch)
{
/* Skip negatives, ascii control chars / white space */
if( ch <= 0x20 )
return false;
/* Keys must be utf-8 and we do not control what kernel
* returns -> skip stuff that is not ascii-7 pure */
if( ch >= 0x80 )
return false;
/* Square brackets are used for keyfile groups or
* specifying language specific variant values */
if( ch == '[' || ch == ']' )
return false;
/* And '=' is used for separating keys from values */
if( ch == '=' )
return false;
/* Assume everything else is ok */
return true;
}
/** Sanitize c-string to "usable as ini file key" form
*
* Dynamically obtained strings - such as evdev device names queried
* from kernel - might contain characters that are not allowed in
* glib keyfile keys. This function performs one way transformation
* that allows turning any c-string into a form that can be used
* as key name.
*
* Leading and trailing illegal characters are skipped altogether.
*
* Sequences of mid-string illegal characters are squeezed into
* single underscores.
*
* Caller must release the returned string via free().
*
* Examples:
* "gpio-keys" -> "gpio-keys" (no change)
* " some thing [x=7] " -> "some_thing_x_7"
*
* @param name Device name
*
* @return Device name without illegal characters, or NULL
*/
static char *
evio_sanitize_key_name(const char *name)
{
char *key = 0;
if( !name )
goto EXIT;
if( !(key = strdup(name)) )
goto EXIT;
char *src = key;
char *dst = key;
while( *src && !evio_is_valid_key_char(*src) )
++src;
for( ;; ) {
while( *src && evio_is_valid_key_char(*src) )
*dst++ = *src++;
while( *src && !evio_is_valid_key_char(*src) )
++src;
if( !*src )
break;
*dst++ = '_';
}
*dst = 0;
EXIT:
return key;
}
/* ========================================================================= *
* EVDEV_IO_MONITORING
* ========================================================================= */
static void
evin_iomon_extra_delete_cb(void *aptr)
{
evin_iomon_extra_t *self = aptr;
if( self ) {
mt_state_delete(self->ex_mt_state),
self->ex_mt_state = 0;
evin_evdevinfo_delete(self->ex_info);
g_free(self->ex_sw_keypad_slide);
free(self->ex_name);
free(self);
}
}
static evin_iomon_extra_t *
evin_iomon_extra_create(int fd, const char *name)
{
evin_iomon_extra_t *self = calloc(1, sizeof *self);
gchar *config = 0;
char *key = 0;
char *id = 0;
struct input_id info = {};
/* Initialize extra info to sane defaults */
self->ex_name = strdup(name);
self->ex_info = evin_evdevinfo_create();
self->ex_type = EVDEV_UNKNOWN;
self->ex_sw_keypad_slide = 0;
self->ex_mt_state = 0;
evin_evdevinfo_probe(self->ex_info, fd);
/* Check if evdev device type has been set in the configuration
*
* First lookup using bus-vendor-product based name,
* then as a fallback lookup using sanitized device name.
*/
if( ioctl(fd, EVIOCGID, &info) < 0 ) {
mce_log(LL_WARN, "EVIOCGID: N/A (%m)");
}
else {
id = g_strdup_printf("b%04xv%04xp%04x",
info.bustype,
info.vendor,
info.product);
}
if( id ) {
config = mce_conf_get_string(MCE_CONF_EVDEV_TYPE_GROUP, id, 0);
}
if( !config ) {
key = evio_sanitize_key_name(name);
config = mce_conf_get_string(MCE_CONF_EVDEV_TYPE_GROUP, key, 0);
}
/* Heuristics based type detection */
evin_evdevtype_t probed = evin_evdevtype_from_info(self->ex_info);
/* Override based on configuration */
if( config ) {
/* RULE := [':'[':']]
* RULES := [';']...
*/
char *rules = config;
for( char *rule; *(rule = mce_slice_token(rules, &rules, ";")); ) {
const char *arg1 = mce_slice_token(rule, &rule, ":");
const char *arg2 = mce_slice_token(rule, &rule, ":");
evin_evdevtype_t configured = EVDEV_UNKNOWN;
evin_evdevtype_t replaces = EVDEV_UNKNOWN;
if( *arg1 ) {
if( (configured = evin_evdevtype_parse(arg1)) == EVDEV_UNKNOWN )
mce_log(LL_WARN, "unknown evdev device type '%s'", arg1);
}
if( *arg2 ) {
if( (replaces = evin_evdevtype_parse(arg2)) == EVDEV_UNKNOWN )
mce_log(LL_WARN, "unknown evdev device type '%s'", arg2);
}
if( replaces == EVDEV_UNKNOWN || replaces == probed ) {
/* Unconditional / condition matched
* -> use configured / keep probed type
*/
if( configured != EVDEV_UNKNOWN )
probed = configured;
break;
}
}
}
self->ex_type = probed;
/* Initialize type specific tracking data */
if( self->ex_type == EVDEV_KEYBOARD ) {
self->ex_sw_keypad_slide = mce_conf_get_string("SW_KEYPAD_SLIDE",
self->ex_name, 0);
}
if( self->ex_type == EVDEV_TOUCH ||
self->ex_type == EVDEV_MOUSE ||
self->ex_type == EVDEV_DBLTAP ) {
bool protocol_b = evin_evdevinfo_has_code(self->ex_info,
EV_ABS, ABS_MT_SLOT);
self->ex_mt_state = mt_state_create(protocol_b);
}
g_free(config);
free(key);
g_free(id);
return self;
}
/** List of monitored evdev input devices */
static GSList *evin_iomon_device_list = NULL;
/** Handle touch device iomon delete notification
*
* @param iomon I/O monitor that is about to get deleted
*/
static void
evin_iomon_device_delete_cb(mce_io_mon_t *iomon)
{
evin_iomon_device_list = g_slist_remove(evin_iomon_device_list, iomon);
}
/** Locate I/O monitor object by device name
*
* @param name Name of the device
*
* @return iomon object or NULL if not found
*/
static mce_io_mon_t *
evin_iomon_lookup_device(const char *name)
{
mce_io_mon_t *res = 0;
if( !name )
goto EXIT;
for( GSList *item = evin_iomon_device_list; item; item = item->next ) {
mce_io_mon_t *iomon = item->data;
if( !iomon )
continue;
evin_iomon_extra_t *extra = mce_io_mon_get_user_data(iomon);
if( !extra )
continue;
if( strcmp(extra->ex_name, name) )
continue;
res = iomon;
break;
}
EXIT:
return res;
}
static void
evin_iomon_device_iterate(evin_evdevtype_t type, GFunc func, gpointer data)
{
GSList *item;
for( item = evin_iomon_device_list; item; item = item->next ) {
mce_io_mon_t *iomon = item->data;
if( !iomon )
continue;
evin_iomon_extra_t *extra = mce_io_mon_get_user_data(iomon);
if( !extra )
continue;
if( extra->ex_type == type )
func(iomon, data);
}
}
/** Remove all touch device I/O monitors
*/
static void
evin_iomon_device_rem_all(void)
{
mce_io_mon_unregister_list(evin_iomon_device_list),
evin_iomon_device_list = 0;
}
/** Handle emitting of generic and/or genuine user activity
*
* To avoid excessive timer reprogramming the activity signaling is
* rate limited to occur once / second.
*
* @param ev Input event that caused activity reporting
* @param cooked True, if generic activity should be sent
* @param raw True, if non-synthetized activity should be sent
*/
static void
evin_iomon_generate_activity(struct input_event *ev, bool cooked, bool raw)
{
static time_t t_cooked = 0;
static time_t t_raw = 0;
if( !ev )
goto EXIT;
time_t t = ev->input_event_sec;
/* Actual, never synthetized user activity */
if( raw ) {
if( t_raw != t ) {
t_raw = t;
datapipe_exec_full(&user_activity_event_pipe, ev);
}
}
/* Generic, possibly synthetized user activity */
if( cooked ) {
if( t_cooked != t || (submode & MCE_SUBMODE_EVEATER) ) {
t_cooked = t;
mce_datapipe_generate_activity();
}
}
EXIT:
return;
}
/** Predicate for using touch input for sw gestures is allowed
*
* @returns true if gesture events can be injected, false otherwise
*/
static bool
evin_iomon_sw_gestures_allowed(void)
{
bool gestures_allowed = false;
/* No simulated gestures unless only mce is supposed to
* handle touch input */
bool grabbed = touch_grab_wanted;
if( !grabbed )
goto EXIT;
/* The setting must be enabled */
if( !evin_doubletap_emulation_enabled )
goto EXIT;
/* And the display must be firmly in logically off state */
switch( display_state_next ) {
case MCE_DISPLAY_OFF:
case MCE_DISPLAY_LPM_OFF:
case MCE_DISPLAY_LPM_ON:
break;
default:
goto EXIT;
}
switch( display_state_curr ) {
case MCE_DISPLAY_OFF:
case MCE_DISPLAY_LPM_OFF:
case MCE_DISPLAY_LPM_ON:
break;
default:
goto EXIT;
}
gestures_allowed = true;
EXIT:
return gestures_allowed;
}
/** I/O monitor callback for handling touchscreen events
*
* @param data The new data
* @param bytes_read The number of bytes read
*
* @return FALSE to return remaining chunks (if any),
* TRUE to flush all remaining chunks
*/
static gboolean
evin_iomon_touchscreen_cb(mce_io_mon_t *iomon, gpointer data, gsize bytes_read)
{
(void)iomon;
gboolean flush = FALSE;
struct input_event *ev = data;
if( ev == 0 || bytes_read != sizeof *ev )
goto EXIT;
/* Map event before processing */
evin_event_mapper_translate_event(ev);
mce_log(LL_DEBUG, "type: %s, code: %s, value: %d",
evdev_get_event_type_name(ev->type),
evdev_get_event_code_name(ev->type, ev->code),
ev->value);
bool grabbed = touch_grab_wanted;
bool doubletap = false;
evin_iomon_extra_t *extra = mce_io_mon_get_user_data(iomon);
if( extra && extra->ex_mt_state ) {
bool touching_prev = mt_state_touching(extra->ex_mt_state);
doubletap = mt_state_handle_event(extra->ex_mt_state, ev);
bool touching_curr = mt_state_touching(extra->ex_mt_state);
if( touching_prev != touching_curr )
evin_touchstate_schedule_update();
}
#ifdef ENABLE_DOUBLETAP_EMULATION
if( doubletap && evin_iomon_sw_gestures_allowed() ) {
mce_log(LL_DEVEL, "[doubletap] emulated from touch input");
ev->type = EV_MSC;
ev->code = MSC_GESTURE;
ev->value = GESTURE_DOUBLETAP | GESTURE_SYNTHESIZED;
}
#endif
/* Power key up event from touch screen -> double tap gesture event */
if( ev->type == EV_KEY && ev->code == KEY_POWER && ev->value == 0 ) {
mce_log(LL_DEVEL, "[doubletap] as power key event; "
"proximity=%s, lid=%s",
proximity_state_repr(proximity_sensor_actual),
proximity_state_repr(lid_sensor_filtered));
/* Mimic N9 style gesture event for which we
* already have logic in place. Possible filtering
* due to proximity state etc happens at tklock.c
*/
ev->type = EV_MSC;
ev->code = MSC_GESTURE;
ev->value = GESTURE_DOUBLETAP;
}
/* Ignore unwanted events */
if( ev->type != EV_ABS &&
ev->type != EV_KEY &&
ev->type != EV_MSC )
goto EXIT;
/* Do not generate activity if ts input is grabbed */
if( !grabbed )
evin_iomon_generate_activity(ev, true, true);
/* If the event eater is active, don't send anything */
if( submode & MCE_SUBMODE_EVEATER )
goto EXIT;
if( ev->type == EV_MSC && ev->code == MSC_GESTURE ) {
/* Gesture events count as actual non-synthetized
* user activity. */
evin_iomon_generate_activity(ev, false, true);
/* But otherwise are handled in powerkey.c. */
datapipe_exec_full(&keypress_event_pipe, &ev);
}
else if( (ev->type == EV_ABS && ev->code == ABS_PRESSURE) ||
(ev->type == EV_KEY && ev->code == BTN_TOUCH ) ) {
/* Only send pressure events */
datapipe_exec_full(&touchscreen_event_pipe, &ev);
}
EXIT:
return flush;
}
/** I/O monitor callback for handling powerkey is doubletap events
*
* @param data The new data
* @param bytes_read The number of bytes read
*
* @return Always returns FALSE to return remaining chunks (if any)
*/
static gboolean
evin_iomon_evin_doubletap_cb(mce_io_mon_t *iomon, gpointer data, gsize bytes_read)
{
struct input_event *ev = data;
gboolean flush = FALSE;
/* Don't process invalid reads */
if( bytes_read != sizeof (*ev) )
goto EXIT;
if( ev->type == EV_MSC && ev->code == MSC_GESTURE ) {
/* Feed gesture events to touchscreen handler as-is */
evin_iomon_touchscreen_cb(iomon, ev, sizeof *ev);
}
else if( ev->type == EV_KEY && ev->code == KEY_POWER ) {
/* Feed power key events to touchscreen handler for
* possible double tap gesture event conversion */
evin_iomon_touchscreen_cb(iomon, ev, sizeof *ev);
}
EXIT:
return flush;
}
/** I/O monitor callback for handling keypress events
*
* @param data The new data
* @param bytes_read The number of bytes read
*
* @return Always returns FALSE to return remaining chunks (if any)
*/
static gboolean
evin_iomon_keypress_cb(mce_io_mon_t *iomon, gpointer data, gsize bytes_read)
{
(void)iomon;
static bool key_fn_down = false;
static bool key_esc_down = false;
struct input_event *ev;
ev = data;
/* Don't process invalid reads */
if( bytes_read != sizeof (*ev) )
goto EXIT;
/* Map event before processing */
evin_event_mapper_translate_event(ev);
mce_log((ev->type == EV_SW && ev->code == SW_LID) ? LL_DEVEL : LL_DEBUG,
"type: %s, code: %s, value: %d",
evdev_get_event_type_name(ev->type),
evdev_get_event_code_name(ev->type, ev->code),
ev->value);
evin_kp_grab_event_filter_cb(ev);
/* Ignore non-keypress events */
if ((ev->type != EV_KEY) && (ev->type != EV_SW)) {
goto EXIT;
}
if (ev->type == EV_KEY) {
if ((ev->code == KEY_SCREENLOCK) && (ev->value != 2)) {
key_state_t key_state = ev->value ?
KEY_STATE_PRESSED : KEY_STATE_RELEASED;
datapipe_exec_full(&lockkey_state_pipe,
GINT_TO_POINTER(key_state));
}
else if( ev->code == KEY_FN || ev->code == KEY_LEFTMETA ) {
key_fn_down = (ev->value != 0);
}
else if( ev->code == KEY_ESC ) {
bool alarm_ringing = (alarm_ui_state == MCE_ALARM_UI_RINGING_INT32 ||
alarm_ui_state == MCE_ALARM_UI_VISIBLE_INT32);
bool incoming_call = (call_state == CALL_STATE_RINGING);
/* Trapping ESC key should be harmless when display
* is off / when there is no active application that
* might have input focus.
*
* While there is a slight chance of hiccups, also use
* escape key for silencing alarms / calls without need
* for pressing the meta key.
*/
bool allow_trap = (key_fn_down || !interaction_expected ||
incoming_call || alarm_ringing);
if( ev->value != 0 && allow_trap ) {
/* Press / repeat event while trapping allowed */
key_esc_down = true;
ev->code = KEY_POWER;
}
else if( key_esc_down ) {
/* Repeat / release event while already trapped */
ev->code = KEY_POWER;
key_esc_down = (ev->value != 0);
}
if( ev->code == KEY_POWER )
mce_log(LL_DEBUG, "esc key -> power key %s",
key_esc_down ? "press" : "release");
}
/* For now there's no reason to cache the keypress
*
* If the event eater is active, and this is the press,
* don't send anything; never eat releases, otherwise
* the release event for a [power] press might get lost
* and the device shut down... Not good(tm)
*
* Also, don't send repeat events, and don't send
* keypress events for the focus and screenlock keys
*
* Additionally ignore all key events if proximity locked
* during a call or alarm.
*/
if (((ev->code != KEY_CAMERA_FOCUS) &&
(ev->code != KEY_SCREENLOCK) &&
((((submode & MCE_SUBMODE_EVEATER) == 0) &&
(ev->value == 1)) || (ev->value == 0))) &&
((submode & MCE_SUBMODE_PROXIMITY_TKLOCK) == 0)) {
datapipe_exec_full(&keypress_event_pipe, &ev);
}
}
if (ev->type == EV_SW) {
switch (ev->code) {
case SW_CAMERA_LENS_COVER:
if (ev->value != 2) {
cover_state_t cover_state = ev->value ?
COVER_CLOSED : COVER_OPEN;
datapipe_exec_full(&lens_cover_state_pipe,
GINT_TO_POINTER(cover_state));
}
/* Don't generate activity on COVER_CLOSED */
if (ev->value == 1)
goto EXIT;
break;
case SW_KEYPAD_SLIDE:
if (ev->value != 2) {
cover_state_t cover_state = ev->value ?
COVER_CLOSED : COVER_OPEN;
datapipe_exec_full(&keyboard_slide_state_pipe,
GINT_TO_POINTER(cover_state));
evin_iomon_keyboard_state_update();
}
/* Don't generate activity on COVER_CLOSED */
if (ev->value == 1)
goto EXIT;
break;
case SW_FRONT_PROXIMITY:
if (ev->value != 2) {
cover_state_t cover_state = ev->value ?
COVER_CLOSED : COVER_OPEN;
datapipe_exec_full(&proximity_sensor_actual_pipe,
GINT_TO_POINTER(cover_state));
}
break;
case SW_HEADPHONE_INSERT:
case SW_MICROPHONE_INSERT:
case SW_LINEOUT_INSERT:
case SW_VIDEOOUT_INSERT:
if (ev->value != 2) {
cover_state_t cover_state = ev->value ?
COVER_CLOSED : COVER_OPEN;
datapipe_exec_full(&jack_sense_state_pipe,
GINT_TO_POINTER(cover_state));
}
break;
case SW_LID:
/* hammerhead magnetic lid sensor; Feed in to the
* same datapipe as N770 sliding cover uses */
if( ev->value ) {
datapipe_exec_full(&lid_sensor_actual_pipe,
GINT_TO_POINTER(COVER_CLOSED));
}
else {
datapipe_exec_full(&lid_sensor_actual_pipe,
GINT_TO_POINTER(COVER_OPEN));
}
break;
/* Other switches do not have custom actions */
default:
break;
}
}
/* Power key press and release events count as actual non-synthetized
* user activity, but otherwise are handled in the powerkey module.
*/
if( ev->type == EV_KEY && ev->code == KEY_POWER ) {
if( ev->value != 2 )
evin_iomon_generate_activity(ev, false, true);
goto EXIT;
}
/* Generate activity - rate limited to once/second */
evin_iomon_generate_activity(ev, true, false);
EXIT:
return FALSE;
}
/** I/O monitor callback generatic activity from misc evdev events
*
* @param data The new data
* @param bytes_read The number of bytes read
*
* @return Always returns FALSE to return remaining chunks (if any)
*/
static gboolean
evin_iomon_activity_cb(mce_io_mon_t *iomon, gpointer data, gsize bytes_read)
{
(void)iomon;
struct input_event *ev = data;
if( !ev || bytes_read != sizeof (*ev) )
goto EXIT;
/* Ignore synchronisation, force feedback, LED,
* and force feedback status
*/
switch (ev->type) {
case EV_SYN:
case EV_LED:
case EV_SND:
case EV_FF:
case EV_FF_STATUS:
goto EXIT;
default:
break;
}
mce_log(LL_DEBUG, "type: %s, code: %s, value: %d",
evdev_get_event_type_name(ev->type),
evdev_get_event_code_name(ev->type, ev->code),
ev->value);
/* Generate activity - rate limited to once/second */
evin_iomon_generate_activity(ev, true, false);
EXIT:
return FALSE;
}
/** Match and register I/O monitor
*
* @param path Path to the device to add
*/
static void
evin_iomon_device_add(const gchar *path)
{
int fd = -1;
mce_io_mon_notify_cb notify = 0;
evin_iomon_extra_t *extra = 0;
mce_io_mon_t *iomon = 0;
char name[256];
const gchar * const *black;
/* If we cannot open the file, abort */
if( (fd = open(path, O_NONBLOCK | O_RDONLY)) == -1 ) {
mce_log(LL_WARN, "Failed to open `%s', skipping", path);
goto EXIT;
}
/* Get name of the evdev node */
if( ioctl(fd, EVIOCGNAME(sizeof name), name) < 0 ) {
mce_log(LL_WARN, "ioctl(EVIOCGNAME) failed on `%s'", path);
goto EXIT;
}
/* Check if the device is blacklisted by name in the config files */
if( (black = mce_conf_get_blacklisted_event_drivers()) ) {
for( size_t i = 0; black[i]; i++ ) {
if( strcmp(name, black[i]) )
continue;
mce_log(LL_NOTICE, "%s: \"%s\", is blacklisted", path, name);
goto EXIT;
}
}
/* Probe device type */
extra = evin_iomon_extra_create(fd, name);
mce_log(LL_NOTICE, "%s: name='%s' type=%s", path, name,
evin_evdevtype_repr(extra->ex_type));
/* Choose notification callback function based on device type */
switch( extra->ex_type ) {
case EVDEV_TOUCH:
case EVDEV_MOUSE:
notify = evin_iomon_touchscreen_cb;
break;
case EVDEV_DBLTAP:
notify = evin_iomon_evin_doubletap_cb;
break;
case EVDEV_INPUT:
case EVDEV_KEYBOARD:
notify = evin_iomon_keypress_cb;
break;
case EVDEV_VOLKEY:
notify = evin_iomon_keypress_cb;
break;
case EVDEV_ACTIVITY:
notify = evin_iomon_activity_cb;
break;
case EVDEV_ALS:
/* Hook wakelockable ALS input source */
mce_sensorfw_als_attach(fd), fd = -1;
goto EXIT;
case EVDEV_PS:
/* Hook wakelockable PS input source */
mce_sensorfw_ps_attach(fd), fd = -1;
goto EXIT;
case EVDEV_REJECT:
case EVDEV_IGNORE:
case EVDEV_UNKNOWN:
goto EXIT;
default:
break;
}
if( !notify ) {
mce_log(LL_ERR, "%s: no iomon notify callback assigned", path);
goto EXIT;
}
/* Create io monitor for the device file descriptor */
iomon = mce_io_mon_register_chunk(fd, path, MCE_IO_ERROR_POLICY_WARN,
FALSE, notify,
evin_iomon_device_delete_cb,
sizeof (struct input_event));
/* After mce_io_mon_register_chunk() returns the fd is either
* attached to iomon or closed. */
fd = -1;
if( !iomon )
goto EXIT;
/* Attach device type information to the io monitor */
mce_io_mon_set_user_data(iomon, extra, evin_iomon_extra_delete_cb),
extra = 0;
/* Add to list of evdev io monitors */
evin_iomon_device_list = g_slist_prepend(evin_iomon_device_list, iomon);
EXIT:
/* Release type data if it was not attached to io monitor */
if( extra )
evin_iomon_extra_delete_cb(extra);
/* Close unmonitored file descriptors */
if( fd != -1 && TEMP_FAILURE_RETRY(close(fd)) )
mce_log(LL_ERR, "Failed to close `%s'; %m", path);
}
/** Update list of input devices
*
* Remove the I/O monitor for the specified device (if existing)
* and (re)open it if available
*
* @param path Path to the device to add/remove
* @param add TRUE to add device, FALSE to remove it
*/
static void
evin_iomon_device_update(const gchar *path, gboolean add)
{
/* Try unregistering by device path; if io monitor exists
* the delete callback is used to unlink it from device type
* specific lists in this module. */
mce_io_mon_unregister_at_path(path);
/* add new io monitor if so requested */
if( add )
evin_iomon_device_add(path);
evin_iomon_switch_states_update();
evin_iomon_keyboard_state_update();
evin_iomon_mouse_state_update();
}
/** Check whether the fd in question supports the switches
* we want information about -- if so, update their state
*/
static void
evin_iomon_switch_states_update_iter_cb(gpointer io_monitor, gpointer user_data)
{
(void)user_data;
const mce_io_mon_t *iomon = io_monitor;
const gchar *filename = mce_io_mon_get_path(iomon);
int fd = mce_io_mon_get_fd(iomon);
gulong *featurelist = NULL;
gulong *statelist = NULL;
gsize featurelistlen;
gint state;
int ecode;
featurelistlen = (KEY_CNT / bitsize_of(*featurelist)) +
((KEY_CNT % bitsize_of(*featurelist)) ? 1 : 0);
featurelist = g_malloc0(featurelistlen * sizeof (*featurelist));
statelist = g_malloc0(featurelistlen * sizeof (*statelist));
if (ioctl(fd, EVIOCGBIT(EV_SW, SW_MAX), featurelist) == -1) {
mce_log(LL_ERR, "%s: EVIOCGBIT(EV_SW, SW_MAX) failed: %m",
filename);
goto EXIT;
}
if (ioctl(fd, EVIOCGSW(SW_MAX), statelist) == -1) {
mce_log(LL_ERR, "%s: EVIOCGSW(SW_MAX) failed: %m",
filename);
goto EXIT;
}
/* Check initial camera lens cover state */
ecode = evin_event_mapper_rlookup_switch(SW_CAMERA_LENS_COVER);
if( test_bit(ecode, featurelist) ) {
state = test_bit(ecode, statelist) ? COVER_CLOSED : COVER_OPEN;
datapipe_exec_full(&lens_cover_state_pipe, GINT_TO_POINTER(state));
}
/* Check initial keypad slide state */
ecode = evin_event_mapper_rlookup_switch(SW_KEYPAD_SLIDE);
if( test_bit(ecode, featurelist) ) {
state = test_bit(ecode, statelist) ? COVER_CLOSED : COVER_OPEN;
datapipe_exec_full(&keyboard_slide_state_pipe, GINT_TO_POINTER(state));
}
/* Check initial front proximity state */
ecode = evin_event_mapper_rlookup_switch(SW_FRONT_PROXIMITY);
if( test_bit(ecode, featurelist) ) {
state = test_bit(ecode, statelist) ? COVER_CLOSED : COVER_OPEN;
datapipe_exec_full(&proximity_sensor_actual_pipe, GINT_TO_POINTER(state));
}
/* Check initial lid sensor state */
ecode = evin_event_mapper_rlookup_switch(SW_LID);
if( test_bit(ecode, featurelist) ) {
state = test_bit(ecode, statelist) ? COVER_CLOSED : COVER_OPEN;
mce_log(LL_DEVEL, "SW_LID initial state = %s",
cover_state_repr(state));
datapipe_exec_full(&lid_sensor_actual_pipe, GINT_TO_POINTER(state));
}
/* Need to consider more than one switch state when setting the
* initial value of the jack_sense_state_pipe */
bool have = false;
int value = 0;
ecode = evin_event_mapper_rlookup_switch(SW_HEADPHONE_INSERT);
if( test_bit(ecode, featurelist) )
have = true, value |= test_bit(ecode, statelist);
ecode = evin_event_mapper_rlookup_switch(SW_MICROPHONE_INSERT);
if( test_bit(ecode, featurelist) )
have = true, value |= test_bit(ecode, statelist);
ecode = evin_event_mapper_rlookup_switch(SW_LINEOUT_INSERT);
if( test_bit(ecode, featurelist) )
have = true, value |= test_bit(ecode, statelist);
ecode = evin_event_mapper_rlookup_switch(SW_VIDEOOUT_INSERT);
if( test_bit(ecode, featurelist) )
have = true, value |= test_bit(ecode, statelist);
if( have ) {
state = value ? COVER_CLOSED : COVER_OPEN;
datapipe_exec_full(&jack_sense_state_pipe, GINT_TO_POINTER(state));
}
EXIT:
g_free(statelist);
g_free(featurelist);
return;
}
/** Update switch states
*
* Go through monitored input devices and get current state of
* switches mce has interest in.
*/
static void
evin_iomon_switch_states_update(void)
{
evin_iomon_device_iterate(EVDEV_INPUT,
evin_iomon_switch_states_update_iter_cb,
0);
evin_iomon_device_iterate(EVDEV_VOLKEY,
evin_iomon_switch_states_update_iter_cb,
0);
}
/** Iterator callback for evaluation availability of keyboard input devices
*
* Note: The iteration is peforming a logical OR operation, so the
* result variable must be modified only to set it true.
*
* @param io_monitor io monitor as void pointer
* @param user_data pointer to bool available flag
*/
static void
evin_iomon_keyboard_state_update_iter_cb(gpointer io_monitor, gpointer user_data)
{
const mce_io_mon_t *iomon = io_monitor;
const mce_io_mon_t *slide = 0;
evin_iomon_extra_t *extra = mce_io_mon_get_user_data(iomon);
bool *avail = (bool *)user_data;
const char *name = extra->ex_name;
/* Whether keypad slide state switch is SW_KEYPAD_SLIDE or something
* else depends on configuration. */
int ecode = evin_event_mapper_rlookup_switch(SW_KEYPAD_SLIDE);
/** Check if another device node is supposed to provide slide status */
if( (slide = evin_iomon_lookup_device(extra->ex_sw_keypad_slide)) ) {
iomon = slide;
extra = mce_io_mon_get_user_data(iomon);
mce_log(LL_DEBUG, "'%s' gets slide state from '%s'",
name, extra->ex_name);
}
/* Keyboard devices that do not have keypad slide switch are
* considered to be always available. */
if( !evin_evdevinfo_has_code(extra->ex_info, EV_SW, ecode) ) {
*avail = true;
mce_log(LL_DEBUG, "'%s' is non-sliding keyboard", name);
goto EXIT;
}
/* Keyboard devices that have keypad slide are considered available
* only when the slider is in open state */
int fd = mce_io_mon_get_fd(iomon);
unsigned long bits[EVIN_EVDEVBITS_LEN(SW_MAX)];
memset(bits, 0, sizeof bits);
if( ioctl(fd, EVIOCGSW(SW_MAX), bits) == -1 ) {
mce_log(LL_WARN, "%s: EVIOCGSW(SW_MAX) failed: %m",
mce_io_mon_get_path(iomon));
goto EXIT;
}
bool is_open = !test_bit(ecode, bits);
if( is_open )
*avail = true;
mce_log(LL_DEBUG, "'%s' is sliding keyboard in %s position",
name, is_open ? "open" : "closed");
EXIT:
return;
}
/** Check if at least one keyboard device in usable state exists
*
* Iterate over monitored input devices to find normal keyboards
* or slide in keyboards in open position.
*
* Update keyboard availablity state based on the scanning result.
*
* This function should be called when new devices are detected,
* old ones disappear or SW_KEYPAD_SLIDE events are seen.
*/
static void
evin_iomon_keyboard_state_update(void)
{
bool available = false;
evin_iomon_device_iterate(EVDEV_KEYBOARD,
evin_iomon_keyboard_state_update_iter_cb,
&available);
mce_log(LL_DEBUG, "available = %s", available ? "true" : "false");
cover_state_t state = available ? COVER_OPEN : COVER_CLOSED;
datapipe_exec_full(&keyboard_available_state_pipe,
GINT_TO_POINTER(state));
}
/** Iterator callback for evaluation availability of mouse input devices
*
* Note: The iteration is peforming a logical OR operation, so the
* result variable must be modified only to set it true.
*
* @param io_monitor io monitor as void pointer
* @param user_data pointer to bool available flag
*/
static void
evin_iomon_mouse_state_update_iter_cb(gpointer io_monitor, gpointer user_data)
{
(void)io_monitor;
bool *available = user_data;
/* As long as we are iterating devices of EVDEV_MOUSE
* type, it is enough that we got here */
*available = true;
return;
}
/** Check if at least one mouse device in usable state exists
*
* Iterate over monitored input devices to find mouses.
*
* Update mouse availablity state based on the scanning result.
*
* This function should be called when new devices are detected,
* or old ones disappear.
*/
static void
evin_iomon_mouse_state_update(void)
{
bool available = false;
evin_iomon_device_iterate(EVDEV_MOUSE,
evin_iomon_mouse_state_update_iter_cb,
&available);
mce_log(LL_DEBUG, "available = %s", available ? "true" : "false");
cover_state_t state = available ? COVER_OPEN : COVER_CLOSED;
datapipe_exec_full(&mouse_available_state_pipe,
GINT_TO_POINTER(state));
}
/** Scan /dev/input for input event devices
*
* @return TRUE on success, FALSE on failure
*/
static bool
evin_iomon_init(void)
{
static const char pfix[] = EVENT_FILE_PREFIX;
bool res = false;
DIR *dir = NULL;
if( !(dir = opendir(DEV_INPUT_PATH)) ) {
mce_log(LL_ERR, "opendir() failed; %m");
goto EXIT;
}
struct dirent *de;
while( (de = readdir(dir)) != 0 ) {
if( strncmp(de->d_name, pfix, sizeof pfix - 1) ) {
mce_log(LL_DEBUG, "`%s/%s' skipped", DEV_INPUT_PATH, de->d_name);
continue;
}
gchar *path = g_strdup_printf("%s/%s", DEV_INPUT_PATH, de->d_name);
evin_iomon_device_add(path);
g_free(path);
}
res = true;
EXIT:
if( dir && closedir(dir) == -1 )
mce_log(LL_ERR, "closedir() failed; %m");
return res;
}
/** Unregister io monitors for all input devices
*/
static void
evin_iomon_quit(void)
{
evin_iomon_device_rem_all();
}
/* ========================================================================= *
* EVDEV_DIRECTORY_MONITORING
* ========================================================================= */
/** GFile pointer for /dev/input */
static GFile *evin_devdir_directory = NULL;
/** GFileMonitor for evin_devdir_directory */
static GFileMonitor *evin_devdir_monitor = NULL;
/** Handler ID for the evin_devdir_monitor "changed" signal */
static gulong evin_devdir_monitor_changed_id = 0;
/** Callback for /dev/input directory changes
*
* @param monitor Unused
* @param file The file that changed
* @param other_file Unused
* @param event_type The event that occured
* @param user_data Unused
*/
static void
evin_devdir_monitor_changed_cb(GFileMonitor *monitor,
GFile *file, GFile *other_file,
GFileMonitorEvent event_type, gpointer user_data)
{
(void)monitor;
(void)other_file;
(void)user_data;
char *filename = g_file_get_basename(file);
char *filepath = g_file_get_path(file);
if( !filename || !filepath )
goto EXIT;
if( strncmp(filename, EVENT_FILE_PREFIX, strlen(EVENT_FILE_PREFIX)) )
goto EXIT;
switch (event_type) {
case G_FILE_MONITOR_EVENT_CREATED:
evin_iomon_device_update(filepath, TRUE);
break;
case G_FILE_MONITOR_EVENT_DELETED:
evin_iomon_device_update(filepath, FALSE);
break;
default:
break;
}
EXIT:
g_free(filepath);
g_free(filename);
return;
}
/** Start tracking changes in /dev/input directory
*
* @return TRUE if monitoring was started, FALSE otherwise
*/
static bool
evin_devdir_monitor_init(void)
{
bool success = false;
GError *error = NULL;
/* Retrieve a GFile pointer to the directory to monitor */
if( !(evin_devdir_directory = g_file_new_for_path(DEV_INPUT_PATH)) )
goto EXIT;
/* Monitor the directory */
evin_devdir_monitor = g_file_monitor_directory(evin_devdir_directory,
G_FILE_MONITOR_NONE,
NULL, &error);
if( !evin_devdir_monitor ) {
mce_log(LL_ERR,
"Failed to add monitor for directory `%s'; %s",
DEV_INPUT_PATH, error->message);
goto EXIT;
}
/* Connect "changed" signal for the directory monitor */
evin_devdir_monitor_changed_id =
g_signal_connect(G_OBJECT(evin_devdir_monitor), "changed",
G_CALLBACK(evin_devdir_monitor_changed_cb), NULL);
if( !evin_devdir_monitor_changed_id ) {
mce_log(LL_ERR, "Failed to connect to 'changed' signal"
" for directory `%s'", DEV_INPUT_PATH);
goto EXIT;
}
success = true;
EXIT:
/* All or nothing */
if( !success )
evin_devdir_monitor_quit();
g_clear_error(&error);
return success;
}
/** Stop tracking changes in /dev/input directory
*/
static void
evin_devdir_monitor_quit(void)
{
/* Remove directory monitor */
if( evin_devdir_monitor ) {
if( evin_devdir_monitor_changed_id ) {
g_signal_handler_disconnect(G_OBJECT(evin_devdir_monitor),
evin_devdir_monitor_changed_id);
evin_devdir_monitor_changed_id = 0;
}
g_object_unref(evin_devdir_monitor),
evin_devdir_monitor = 0;
}
/* Release directory file object */
if( evin_devdir_directory ) {
g_object_unref(evin_devdir_directory),
evin_devdir_directory = 0;
}
}
/* ========================================================================= *
* TOUCHSTATE_MONITORING
* ========================================================================= */
/** Iterator callback for finding touch devices in finger-on-screen state
*
* @param data iomon object
* @param user_data pointer to bool flag to set if tracked device is touched
*/
static void
evin_touchstate_iomon_iter_cb(gpointer data, gpointer user_data)
{
mce_io_mon_t *iomon = data;
bool *touching = user_data;
evin_iomon_extra_t *extra = mce_io_mon_get_user_data(iomon);
if( extra && mt_state_touching(extra->ex_mt_state) )
*touching = true;
}
/** Idle ID for delayed update of finger-on-screen state */
static guint evin_touchstate_update_id = 0;
/** Idle cb function for delayed update of finger-on-screen state
*
* @param aptr User data pointer (unused)
*
* @return FALSE to keep idle callback from repeating
*/
static gboolean
evin_touchstate_update_cb(gpointer aptr)
{
(void)aptr;
if( !evin_touchstate_update_id )
goto EXIT;
evin_touchstate_update_id = 0;
bool touching = false;
evin_iomon_device_iterate(EVDEV_TOUCH,
evin_touchstate_iomon_iter_cb,
&touching);
evin_iomon_device_iterate(EVDEV_MOUSE,
evin_touchstate_iomon_iter_cb,
&touching);
if( touching == touch_detected )
goto EXIT;
mce_log(LL_DEBUG, "touch_detected=%s", touching ? "true" : "false");
datapipe_exec_full(&touch_detected_pipe,
GINT_TO_POINTER(touching));
EXIT:
return FALSE;
}
/** Cancel delayed update of finger-on-screen state
*/
static void
evin_touchstate_cancel_update(void)
{
if( evin_touchstate_update_id ) {
g_source_remove(evin_touchstate_update_id),
evin_touchstate_update_id = 0;
}
}
/** Schedule delayed update of finger-on-screen state
*/
static void
evin_touchstate_schedule_update(void)
{
if( !evin_touchstate_update_id ) {
evin_touchstate_update_id = g_idle_add(evin_touchstate_update_cb, 0);
}
}
/* ========================================================================= *
* INPUT_GRAB -- GENERIC EVDEV INPUT GRAB STATE MACHINE
* ========================================================================= */
/** Convert state enum to human readable string for purposes
*/
static const char *
evin_state_repr(evin_state_t state)
{
const char *str = "EVIN_STATE_INVALID";
switch( state ) {
case EVIN_STATE_UNKNOWN: str = "EVIN_STATE_UNKNOWN"; break;
case EVIN_STATE_ENABLED: str = "EVIN_STATE_ENABLED"; break;
case EVIN_STATE_DISABLED: str = "EVIN_STATE_DISABLED"; break;
default: break;
}
return str;
}
/** Convert state enum to string for use on dbus
*/
static const char *
evin_state_to_dbus(evin_state_t state)
{
if( state == EVIN_STATE_DISABLED )
return MCE_INPUT_POLICY_DISABLED;
return MCE_INPUT_POLICY_ENABLED;
}
/** Reset input grab state machine
*
* Releases any dynamic resources held by the state machine
*/
static void
evin_input_grab_reset(evin_input_grab_t *self)
{
self->ig_touching = false;
self->ig_touched = false;
if( self->ig_release_id )
g_source_remove(self->ig_release_id),
self->ig_release_id = 0;
}
/** Delayed release timeout callback
*
* Grab/ungrab happens from this function when touch/press ends
*/
static gboolean
evin_input_grab_release_cb(gpointer aptr)
{
evin_input_grab_t *self = aptr;
gboolean repeat = FALSE;
if( !self->ig_release_id )
goto EXIT;
if( self->ig_release_verify_cb && !self->ig_release_verify_cb(self) ) {
mce_log(LL_DEBUG, "touching(%s) = holding", self->ig_name);
repeat = TRUE;
goto EXIT;
}
// timer no longer active
self->ig_release_id = 0;
// touch release delay has ended
self->ig_touched = false;
mce_log(LL_DEBUG, "touching(%s) = released", self->ig_name);
// evaluate next state
evin_input_grab_rethink(self);
EXIT:
return repeat;
}
/** Start delayed release timer if not already running
*/
static void
evin_input_grab_start_release_timer(evin_input_grab_t *self)
{
if( !self->ig_release_id )
self->ig_release_id = g_timeout_add(self->ig_release_ms,
evin_input_grab_release_cb,
self);
}
/** Cancel delayed release timer
*/
static void
evin_input_grab_cancel_release_timer(evin_input_grab_t *self)
{
if( self->ig_release_id )
g_source_remove(self->ig_release_id),
self->ig_release_id = 0;
}
/** Re-evaluate input grab state
*/
static void
evin_input_grab_rethink(evin_input_grab_t *self)
{
// no changes while active touch
if( self->ig_touching ) {
evin_input_grab_cancel_release_timer(self);
goto EXIT;
}
// delay after touch release
if( self->ig_touched ) {
evin_input_grab_start_release_timer(self);
goto EXIT;
}
// do the transition
self->ig_have_grab = self->ig_want_grab;
EXIT:
;
// evaluate actual grab
bool real = self->ig_have_grab && self->ig_allow_grab;
if( self->ig_real_grab != real ) {
self->ig_real_grab = real;
if( self->ig_grab_changed_cb )
self->ig_grab_changed_cb(self, self->ig_real_grab);
}
// evaluate policy change
evin_state_t state = EVIN_STATE_ENABLED;
if( self->ig_want_grab || self->ig_have_grab )
state = EVIN_STATE_DISABLED;
if( self->ig_state != state ) {
mce_log(LL_DEBUG, "state(%s): %s -> %s", self->ig_name,
evin_state_repr(self->ig_state),
evin_state_repr(state));
self->ig_state = state;
if( self->ig_state_changed_cb )
self->ig_state_changed_cb(self);
}
return;
}
/** Feed touching/pressed state to input grab state machine
*/
static void
evin_input_grab_set_touching(evin_input_grab_t *self, bool touching)
{
if( self->ig_touching == touching )
goto EXIT;
mce_log(LL_DEBUG, "touching(%s) = %s", self->ig_name, touching ? "yes" : "no");
if( (self->ig_touching = touching) )
self->ig_touched = true;
evin_input_grab_rethink(self);
EXIT:
return;
}
/** Feed desire to grab to input grab state machine
*/
static void
evin_input_grab_request_grab(evin_input_grab_t *self, bool want_grab)
{
if( self->ig_want_grab == want_grab )
goto EXIT;
self->ig_want_grab = want_grab;
evin_input_grab_rethink(self);
EXIT:
return;
}
/** Feed allow/deny grab control to input grab state machine
*/
static void
evin_input_grab_allow_grab(evin_input_grab_t *self, bool allow_grab)
{
if( self->ig_allow_grab == allow_grab )
goto EXIT;
self->ig_allow_grab = allow_grab;
evin_input_grab_rethink(self);
EXIT:
return;
}
/** Callback for changing iomonitor input grab state
*/
static void
evin_input_grab_iomon_cb(gpointer data, gpointer user_data)
{
gpointer iomon = data;
int grab = GPOINTER_TO_INT(user_data);
int fd = mce_io_mon_get_fd(iomon);
if( fd == -1 )
goto EXIT;
const char *path = mce_io_mon_get_path(iomon) ?: "unknown";
if( ioctl(fd, EVIOCGRAB, grab) == -1 ) {
mce_log(LL_ERR, "EVIOCGRAB(%s, %d): %m", path, grab);
goto EXIT;
}
mce_log(LL_DEBUG, "%sGRABBED fd=%d path=%s",
grab ? "" : "UN", fd, path);
EXIT:
return;
}
/* ------------------------------------------------------------------------- *
* TS_GRAB
* ------------------------------------------------------------------------- */
/** State data for touch input grab state machine */
static evin_input_grab_t evin_ts_grab_state =
{
.ig_name = "ts",
.ig_state = EVIN_STATE_UNKNOWN,
.ig_touching = false,
.ig_touched = false,
.ig_want_grab = false,
.ig_have_grab = false,
.ig_real_grab = false,
.ig_allow_grab = false,
.ig_release_id = 0,
.ig_release_ms = MCE_DEFAULT_TOUCH_UNBLOCK_DELAY,
.ig_grab_changed_cb = evin_ts_grab_changed,
.ig_release_verify_cb = evin_ts_grab_poll_palm_detect,
.ig_state_changed_cb = evin_ts_policy_changed,
};
/* Touch unblock delay from settings [ms] */
static gint evin_ts_grab_release_delay = MCE_DEFAULT_TOUCH_UNBLOCK_DELAY;
static guint evin_ts_grab_release_delay_setting_id = 0;
/** Low level helper for input grab debug led pattern activate/deactivate
*/
static void
evin_ts_grab_set_led_raw(bool enabled)
{
datapipe_exec_full(enabled ?
&led_pattern_activate_pipe :
&led_pattern_deactivate_pipe,
MCE_LED_PATTERN_TOUCH_INPUT_BLOCKED);
}
/** Handle delayed input grab led pattern activation
*/
static gboolean
evin_ts_grab_set_led_cb(gpointer aptr)
{
guint *id = aptr;
if( !*id )
goto EXIT;
*id = 0;
evin_ts_grab_set_led_raw(true);
EXIT:
return FALSE;
}
/** Handle grab led pattern activation/deactivation
*
* Deactivation happens immediately.
* Activation after brief delay
*/
static void
evin_ts_grab_set_led(bool enabled)
{
static guint id = 0;
static bool prev = false;
if( prev == enabled )
goto EXIT;
if( id )
g_source_remove(id), id = 0;
if( enabled )
id = g_timeout_add(200, evin_ts_grab_set_led_cb, &id);
else
evin_ts_grab_set_led_raw(false);
prev = enabled;
EXIT:
return;
}
/** Evaluate need for grab active led notification
*
* This should be called when display state or
* touch screen grab state changes.
*/
static void
evin_ts_grab_rethink_led(void)
{
bool enable = false;
switch( display_state_curr )
{
case MCE_DISPLAY_ON:
case MCE_DISPLAY_DIM:
if( evin_ts_grab_state.ig_state == EVIN_STATE_DISABLED )
enable = true;
break;
default:
break;
}
evin_ts_grab_set_led(enable);
}
/** Grab/ungrab all monitored touch input devices
*/
static void
evin_ts_grab_set_active(gboolean grab)
{
static gboolean old_grab = FALSE;
if( old_grab == grab )
goto EXIT;
old_grab = grab;
evin_iomon_device_iterate(EVDEV_TOUCH,
evin_input_grab_iomon_cb,
GINT_TO_POINTER(grab));
evin_iomon_device_iterate(EVDEV_MOUSE,
evin_input_grab_iomon_cb,
GINT_TO_POINTER(grab));
// STATE MACHINE -> OUTPUT DATAPIPE
datapipe_exec_full(&touch_grab_active_pipe,
GINT_TO_POINTER(grab));
EXIT:
return;
}
/** Query palm detection state
*
* Used to keep touch input in unreleased state even if finger touch
* events are not coming in.
*/
static bool
evin_ts_grab_poll_palm_detect(evin_input_grab_t *ctrl)
{
(void)ctrl;
static const char path[] = "/sys/devices/i2c-3/3-0020/palm_status";
bool released = true;
int fd = -1;
char buf[32];
if( (fd = open(path, O_RDONLY)) == -1 ) {
if( errno != ENOENT )
mce_log(LL_ERR, "can't open %s: %m", path);
goto EXIT;
}
int rc = read(fd, buf, sizeof buf - 1);
if( rc < 0 ) {
mce_log(LL_ERR, "can't read %s: %m", path);
goto EXIT;
}
buf[rc] = 0;
released = (strtol(buf, 0, 0) == 0);
EXIT:
if( fd != -1 && close(fd) == -1 )
mce_log(LL_WARN, "can't close %s: %m", path);
return released;
}
/** Handle grab state notifications from generic input grab state machine
*/
static void
evin_ts_grab_changed(evin_input_grab_t *ctrl, bool grab)
{
(void)ctrl;
evin_ts_grab_set_active(grab);
}
static void
evin_ts_policy_changed(evin_input_grab_t *ctrl)
{
(void)ctrl;
evin_ts_grab_rethink_led();
evin_dbus_send_touch_input_policy(0);
}
enum
{
TS_RELEASE_DELAY_BLANK = 100,
TS_RELEASE_DELAY_UNBLANK = 600,
};
/** Gconf notification callback for touch unblock delay
*
* @param client (not used)
* @param id (not used)
* @param entry GConf entry that changed
* @param data (not used)
*/
static void
evin_ts_grab_setting_cb(GConfClient *const client,
const guint id,
GConfEntry *const entry,
gpointer const data)
{
(void)client; (void)id; (void)data;
gint delay = evin_ts_grab_release_delay;
const GConfValue *value = 0;
if( !entry )
goto EXIT;
if( !(value = gconf_entry_get_value(entry)) )
goto EXIT;
if( value->type == GCONF_VALUE_INT )
delay = gconf_value_get_int(value);
if( evin_ts_grab_release_delay == delay )
goto EXIT;
mce_log(LL_NOTICE, "touch unblock delay changed: %d -> %d",
evin_ts_grab_release_delay, delay);
evin_ts_grab_release_delay = delay;
// NB: currently active timer is not reprogrammed, change
// will take effect on the next unblank
EXIT:
return;
}
/** Feed desired touch grab state from datapipe to state machine
*
* @param data The grab wanted boolean as a pointer
*/
static void
evin_datapipe_touch_grab_wanted_cb(gconstpointer data)
{
bool prev = touch_grab_wanted;
touch_grab_wanted = GPOINTER_TO_INT(data);
if( prev != touch_grab_wanted )
mce_log(LL_DEBUG, "touch_grab_wanted: %d -> %d",
prev, touch_grab_wanted);
// INPUT DATAPIPE -> STATE MACHINE
evin_input_grab_request_grab(&evin_ts_grab_state, touch_grab_wanted);
}
/** Feed detected finger-on-screen state from datapipe to state machine
*
* @param data The touch detected boolean as a pointer
*/
static void
evin_datapipe_touch_detected_cb(gconstpointer data)
{
bool prev = touch_detected;
touch_detected = GPOINTER_TO_INT(data);
if( prev != touch_detected )
mce_log(LL_DEBUG, "touch_detected = %s",
touch_detected ? "true" : "false");
evin_input_grab_set_touching(&evin_ts_grab_state, touch_detected);
}
/** Take display state changes in account for touch grab state
*
* @param data Display state as void pointer
*/
static void
evin_datapipe_display_state_curr_cb(gconstpointer data)
{
display_state_t prev = display_state_curr;
display_state_curr = GPOINTER_TO_INT(data);
if( display_state_curr == prev )
goto EXIT;
mce_log(LL_DEBUG, "display_state_curr=%s", display_state_repr(display_state_curr));
switch( display_state_curr ) {
case MCE_DISPLAY_POWER_DOWN:
case MCE_DISPLAY_OFF:
case MCE_DISPLAY_LPM_ON:
case MCE_DISPLAY_LPM_OFF:
/* Assume UI can deal with losing touch input mid gesture
* and grab touch input already when we just start to power
* down the display. */
evin_input_grab_reset(&evin_ts_grab_state);
evin_input_grab_rethink(&evin_ts_grab_state);
break;
case MCE_DISPLAY_POWER_UP:
/* Fake a touch to keep statemachine from releasing
* the input grab before we have a change to get
* actual input from the touch panel. */
evin_ts_grab_state.ig_release_ms = TS_RELEASE_DELAY_UNBLANK;
if( !touch_detected ) {
evin_input_grab_set_touching(&evin_ts_grab_state, true);
evin_input_grab_set_touching(&evin_ts_grab_state, false);
}
/* Fall through */
case MCE_DISPLAY_ON:
case MCE_DISPLAY_DIM:
evin_ts_grab_state.ig_release_ms = evin_ts_grab_release_delay;
if( prev != MCE_DISPLAY_ON && prev != MCE_DISPLAY_DIM ) {
/* End the faked touch once the display is
* fully on. If there is a finger on the
* screen we will get more input events
* before the delay from artificial touch
* release ends. */
evin_input_grab_set_touching(&evin_ts_grab_state, touch_detected);
}
break;
default:
case MCE_DISPLAY_UNDEF:
break;
}
evin_ts_grab_rethink_led();
EXIT:
return;
}
/** Initialize touch screen grabbing state machine
*/
static void
evin_ts_grab_init(void)
{
/* Get touch unblock delay */
mce_setting_notifier_add(MCE_SETTING_EVENT_INPUT_PATH,
MCE_SETTING_TOUCH_UNBLOCK_DELAY,
evin_ts_grab_setting_cb,
&evin_ts_grab_release_delay_setting_id);
mce_setting_get_int(MCE_SETTING_TOUCH_UNBLOCK_DELAY,
&evin_ts_grab_release_delay);
mce_log(LL_INFO, "touch unblock delay config: %d",
evin_ts_grab_release_delay);
evin_ts_grab_state.ig_release_ms = evin_ts_grab_release_delay;
}
/** De-initialize touch screen grabbing state machine
*/
static void
evin_ts_grab_quit(void)
{
mce_setting_notifier_remove(evin_ts_grab_release_delay_setting_id),
evin_ts_grab_release_delay_setting_id = 0;
evin_input_grab_reset(&evin_ts_grab_state);
}
/* ------------------------------------------------------------------------- *
* KP_GRAB
* ------------------------------------------------------------------------- */
/** Grab/ungrab all monitored volumekey input devices
*/
static void
evin_kp_grab_set_active(gboolean grab)
{
static gboolean old_grab = FALSE;
if( old_grab == grab )
goto EXIT;
old_grab = grab;
evin_iomon_device_iterate(EVDEV_VOLKEY,
evin_input_grab_iomon_cb,
GINT_TO_POINTER(grab));
// STATE MACHINE -> OUTPUT DATAPIPE
datapipe_exec_full(&keypad_grab_active_pipe,
GINT_TO_POINTER(grab));
EXIT:
return;
}
/** Handle grab state notifications from generic input grab state machine
*/
static void
evin_kp_grab_changed(evin_input_grab_t *ctrl, bool grab)
{
(void)ctrl;
evin_kp_grab_set_active(grab);
}
static void
evin_kp_policy_changed(evin_input_grab_t *ctrl)
{
(void)ctrl;
evin_dbus_send_keypad_input_policy(0);
}
/** State data for volumekey input grab state machine */
static evin_input_grab_t evin_kp_grab_state =
{
.ig_name = "kp",
.ig_state = EVIN_STATE_UNKNOWN,
.ig_touching = false,
.ig_touched = false,
.ig_want_grab = false,
.ig_have_grab = false,
.ig_real_grab = false,
.ig_allow_grab = false,
.ig_release_id = 0,
.ig_release_ms = 200,
.ig_grab_changed_cb = evin_kp_grab_changed,
.ig_state_changed_cb = evin_kp_policy_changed,
};
/** Event filter for determining volume key pressed state
*/
static void
evin_kp_grab_event_filter_cb(struct input_event *ev)
{
static bool vol_up = false;
static bool vol_dn = false;
switch( ev->type ) {
case EV_KEY:
switch( ev->code ) {
case KEY_VOLUMEUP:
vol_up = (ev->value != 0);
break;
case KEY_VOLUMEDOWN:
vol_dn = (ev->value != 0);
break;
default:
break;
}
break;
default:
break;
}
evin_input_grab_set_touching(&evin_kp_grab_state, vol_up || vol_dn);
}
/** Feed desired volumekey grab state from datapipe to state machine
*
* @param data The grab wanted boolean as a pointer
*/
static void
evin_datapipe_keypad_grab_wanted_cb(gconstpointer data)
{
bool prev = keypad_grab_wanted;
keypad_grab_wanted = GPOINTER_TO_INT(data);
if( prev != keypad_grab_wanted )
mce_log(LL_DEBUG, "keypad_grab_wanted: %d -> %d",
prev, keypad_grab_wanted);
// INPUT DATAPIPE -> STATE MACHINE
evin_input_grab_request_grab(&evin_kp_grab_state, keypad_grab_wanted);
}
/* ========================================================================= *
* DYNAMIC_SETTINGS
* ========================================================================= */
/** Flag: Input device types that can be grabbed */
static gint evin_setting_input_grab_allowed = MCE_DEFAULT_INPUT_GRAB_ALLOWED;
static guint evin_setting_input_grab_allowed_setting_id = 0;
/** Handle changes to the list of grabbable input devices
*/
static void evin_setting_input_grab_rethink(void)
{
bool ts = (evin_setting_input_grab_allowed & MCE_INPUT_GRAB_ALLOW_TS) != 0;
bool kp = (evin_setting_input_grab_allowed & MCE_INPUT_GRAB_ALLOW_KP) != 0;
evin_input_grab_allow_grab(&evin_ts_grab_state, ts);
evin_input_grab_allow_grab(&evin_kp_grab_state, kp);
}
/** GConf callback for event input related settings
*
* @param gcc Unused
* @param id Connection ID from gconf_client_notify_add()
* @param entry The modified GConf entry
* @param data Unused
*/
static void evin_setting_cb(GConfClient *const gcc, const guint id,
GConfEntry *const entry, gpointer const data)
{
(void)gcc;
(void)data;
(void)id;
const GConfValue *gcv = gconf_entry_get_value(entry);
if( !gcv ) {
mce_log(LL_DEBUG, "GConf Key `%s' has been unset",
gconf_entry_get_key(entry));
goto EXIT;
}
if( id == evin_setting_input_grab_allowed_setting_id ) {
gint old = evin_setting_input_grab_allowed;
evin_setting_input_grab_allowed = gconf_value_get_int(gcv);
mce_log(LL_NOTICE, "evin_setting_input_grab_allowed: %d -> %d",
old, evin_setting_input_grab_allowed);
evin_setting_input_grab_rethink();
}
else {
mce_log(LL_WARN, "Spurious GConf value received; confused!");
}
EXIT:
return;
}
/** Get intial setting values and start tracking changes
*/
static void evin_setting_init(void)
{
/* Bitmask of input devices that can be grabbed */
mce_setting_track_int(MCE_SETTING_INPUT_GRAB_ALLOWED,
&evin_setting_input_grab_allowed,
MCE_DEFAULT_INPUT_GRAB_ALLOWED,
evin_setting_cb,
&evin_setting_input_grab_allowed_setting_id);
evin_setting_input_grab_rethink();
}
/** Stop tracking setting changes
*/
static void evin_setting_quit(void)
{
mce_setting_notifier_remove(evin_setting_input_grab_allowed_setting_id),
evin_setting_input_grab_allowed_setting_id = 0;
}
/* ========================================================================= *
* DBUS_HOOKS
* ========================================================================= */
/** Send the keypad input policy
*
* @param req A method call message to be replied, or
* NULL to broadcast a keypad input policy signal
*/
static void
evin_dbus_send_keypad_input_policy(DBusMessage *const req)
{
DBusMessage *rsp = 0;
if( req )
rsp = dbus_new_method_reply(req);
else
rsp = dbus_new_signal(MCE_SIGNAL_PATH, MCE_SIGNAL_IF,
MCE_VOLKEY_INPUT_POLICY_SIG);
if( !rsp )
goto EXIT;
const char *arg = evin_state_to_dbus(evin_kp_grab_state.ig_state);
mce_log(LL_DEBUG, "send keypad input policy %s: %s",
req ? "reply" : "signal", arg);
if( !dbus_message_append_args(rsp,
DBUS_TYPE_STRING, &arg,
DBUS_TYPE_INVALID) )
goto EXIT;
dbus_send_message(rsp), rsp = 0;
EXIT:
if( rsp ) dbus_message_unref(rsp);
}
/** D-Bus callback for the get keypad input policy method call
*
* @param msg The D-Bus message
*
* @return TRUE
*/
static gboolean
evin_dbus_keypad_input_policy_get_req_cb(DBusMessage *const msg)
{
mce_log(LL_DEVEL, "Received keypad input policy get request from %s",
mce_dbus_get_message_sender_ident(msg));
evin_dbus_send_keypad_input_policy(msg);
return TRUE;
}
/** Send the touch input policy
*
* @param req A method call message to be replied, or
* NULL to broadcast a touch input policy signal
*/
static void
evin_dbus_send_touch_input_policy(DBusMessage *const req)
{
DBusMessage *rsp = 0;
if( req )
rsp = dbus_new_method_reply(req);
else
rsp = dbus_new_signal(MCE_SIGNAL_PATH, MCE_SIGNAL_IF,
MCE_TOUCH_INPUT_POLICY_SIG);
if( !rsp )
goto EXIT;
const char *arg = evin_state_to_dbus(evin_ts_grab_state.ig_state);
mce_log(LL_DEBUG, "send touch input policy %s: %s",
req ? "reply" : "signal", arg);
if( !dbus_message_append_args(rsp,
DBUS_TYPE_STRING, &arg,
DBUS_TYPE_INVALID) )
goto EXIT;
dbus_send_message(rsp), rsp = 0;
EXIT:
if( rsp ) dbus_message_unref(rsp);
}
/** D-Bus callback for the get touch input policy method call
*
* @param msg The D-Bus message
*
* @return TRUE
*/
static gboolean
evin_dbus_touch_input_policy_get_req_cb(DBusMessage *const msg)
{
mce_log(LL_DEVEL, "Received keypad input policy get request from %s",
mce_dbus_get_message_sender_ident(msg));
evin_dbus_send_touch_input_policy(msg);
return TRUE;
}
/** Array of dbus message handlers */
static mce_dbus_handler_t evin_dbus_handlers[] =
{
/* signals - outbound (for Introspect purposes only) */
{
.interface = MCE_SIGNAL_IF,
.name = MCE_VOLKEY_INPUT_POLICY_SIG,
.type = DBUS_MESSAGE_TYPE_SIGNAL,
.args =
" \n"
},
{
.interface = MCE_SIGNAL_IF,
.name = MCE_TOUCH_INPUT_POLICY_SIG,
.type = DBUS_MESSAGE_TYPE_SIGNAL,
.args =
" \n"
},
/* method calls */
{
.interface = MCE_REQUEST_IF,
.name = MCE_VOLKEY_INPUT_POLICY_GET,
.type = DBUS_MESSAGE_TYPE_METHOD_CALL,
.callback = evin_dbus_keypad_input_policy_get_req_cb,
.args =
" \n"
},
{
.interface = MCE_REQUEST_IF,
.name = MCE_TOUCH_INPUT_POLICY_GET,
.type = DBUS_MESSAGE_TYPE_METHOD_CALL,
.callback = evin_dbus_touch_input_policy_get_req_cb,
.args =
" \n"
},
/* sentinel */
{
.interface = 0
}
};
/** Add dbus handlers
*/
static void evin_dbus_init(void)
{
mce_dbus_handler_register_array(evin_dbus_handlers);
}
/** Remove dbus handlers
*/
static void evin_dbus_quit(void)
{
mce_dbus_handler_unregister_array(evin_dbus_handlers);
}
/* ========================================================================= *
* EVIN_DATAPIPE
* ========================================================================= */
/** Pre-change notifications for display_state_curr
*/
static void evin_datapipe_display_state_next_cb(gconstpointer data)
{
display_state_t prev = display_state_next;
display_state_next = GPOINTER_TO_INT(data);
if( prev == display_state_next )
goto EXIT;
mce_log(LL_DEBUG, "display_state_next = %s -> %s",
display_state_repr(prev),
display_state_repr(display_state_next));
EXIT:
return;
}
/** Change notifications for proximity_sensor_actual
*/
static void evin_datapipe_proximity_sensor_actual_cb(gconstpointer data)
{
cover_state_t prev = proximity_sensor_actual;
proximity_sensor_actual = GPOINTER_TO_INT(data);
if( proximity_sensor_actual == prev )
goto EXIT;
mce_log(LL_DEBUG, "proximity_sensor_actual = %s -> %s",
proximity_state_repr(prev),
proximity_state_repr(proximity_sensor_actual));
EXIT:
return;
}
/** Change notifications from lid_sensor_filtered_pipe
*/
static void evin_datapipe_lid_sensor_filtered_cb(gconstpointer data)
{
cover_state_t prev = lid_sensor_filtered;
lid_sensor_filtered = GPOINTER_TO_INT(data);
if( lid_sensor_filtered == prev )
goto EXIT;
mce_log(LL_DEBUG, "lid_sensor_filtered = %s -> %s",
cover_state_repr(prev),
cover_state_repr(lid_sensor_filtered));
EXIT:
return;
}
/** Change notifications for topmost_window_pid_pipe
*/
static void
evin_datapipe_topmost_window_pid_cb(gconstpointer data)
{
int prev = topmost_window_pid;
topmost_window_pid = GPOINTER_TO_INT(data);
if( prev == topmost_window_pid )
goto EXIT;
mce_log(LL_DEBUG, "topmost_window_pid: %d -> %d",
prev, topmost_window_pid);
EXIT:
return;
}
/** Change notifications for alarm_ui_state
*/
static void evin_datapipe_alarm_ui_state_cb(gconstpointer data)
{
alarm_ui_state_t prev = alarm_ui_state;
alarm_ui_state = GPOINTER_TO_INT(data);
if( alarm_ui_state == MCE_ALARM_UI_INVALID_INT32 )
alarm_ui_state = MCE_ALARM_UI_OFF_INT32;
if( alarm_ui_state == prev )
goto EXIT;
mce_log(LL_DEBUG, "alarm_ui_state = %s -> %s",
alarm_state_repr(prev),
alarm_state_repr(alarm_ui_state));
EXIT:
return;
}
/** Change notifications for call_state
*/
static void evin_datapipe_call_state_cb(gconstpointer data)
{
call_state_t prev = call_state;
call_state = GPOINTER_TO_INT(data);
if( call_state == CALL_STATE_INVALID )
call_state = CALL_STATE_NONE;
if( call_state == prev )
goto EXIT;
mce_log(LL_DEBUG, "call_state = %s -> %s",
call_state_repr(prev),
call_state_repr(call_state));
EXIT:
return;
}
/** Change notifications for interaction_expected_pipe
*/
static void evin_datapipe_interaction_expected_cb(gconstpointer data)
{
bool prev = interaction_expected;
interaction_expected = GPOINTER_TO_INT(data);
if( prev == interaction_expected )
goto EXIT;
mce_log(LL_DEBUG, "interaction_expected: %d -> %d",
prev, interaction_expected);
EXIT:
return;
}
/** Array of datapipe handlers */
static datapipe_handler_t evin_datapipe_handlers[] =
{
// output triggers
{
.datapipe = &submode_pipe,
.output_cb = evin_datapipe_submode_cb,
},
{
.datapipe = &display_state_curr_pipe,
.output_cb = evin_datapipe_display_state_curr_cb,
},
{
.datapipe = &touch_detected_pipe,
.output_cb = evin_datapipe_touch_detected_cb,
},
{
.datapipe = &touch_grab_wanted_pipe,
.output_cb = evin_datapipe_touch_grab_wanted_cb,
},
{
.datapipe = &keypad_grab_wanted_pipe,
.output_cb = evin_datapipe_keypad_grab_wanted_cb,
},
{
.datapipe = &display_state_next_pipe,
.output_cb = evin_datapipe_display_state_next_cb,
},
{
.datapipe = &proximity_sensor_actual_pipe,
.output_cb = evin_datapipe_proximity_sensor_actual_cb,
},
{
.datapipe = &lid_sensor_filtered_pipe,
.output_cb = evin_datapipe_lid_sensor_filtered_cb,
},
{
.datapipe = &topmost_window_pid_pipe,
.output_cb = evin_datapipe_topmost_window_pid_cb,
},
{
.datapipe = &alarm_ui_state_pipe,
.output_cb = evin_datapipe_alarm_ui_state_cb,
},
{
.datapipe = &call_state_pipe,
.output_cb = evin_datapipe_call_state_cb,
},
{
.datapipe = &interaction_expected_pipe,
.output_cb = evin_datapipe_interaction_expected_cb,
},
// sentinel
{
.datapipe = 0,
}
};
static datapipe_bindings_t evin_datapipe_bindings =
{
.module = "mce_input",
.handlers = evin_datapipe_handlers,
};
/** Append triggers/filters to datapipes
*/
static void evin_datapipe_init(void)
{
mce_datapipe_init_bindings(&evin_datapipe_bindings);
}
/** Remove triggers/filters from datapipes
*/
static void evin_datapipe_quit(void)
{
mce_datapipe_quit_bindings(&evin_datapipe_bindings);
}
/* ========================================================================= *
* MODULE_INIT
* ========================================================================= */
/** Init function for the /dev/input event component
*
* @return TRUE on success, FALSE on failure
*/
gboolean
mce_input_init(void)
{
gboolean status = FALSE;
evin_gpio_init();
evin_event_mapper_init();
evin_dbus_init();
evin_ts_grab_init();
evin_setting_init();
#ifdef ENABLE_DOUBLETAP_EMULATION
/* Get fake doubletap policy configuration & track changes */
mce_setting_notifier_add(MCE_SETTING_EVENT_INPUT_PATH,
MCE_SETTING_USE_FAKE_DOUBLETAP,
evin_doubletap_setting_cb,
&evin_doubletap_emulation_enabled_setting_id);
mce_setting_get_bool(MCE_SETTING_USE_FAKE_DOUBLETAP,
&evin_doubletap_emulation_enabled);
#endif
/* Append triggers/filters to datapipes */
evin_datapipe_init();
/* Register input device directory monitor */
if( !evin_devdir_monitor_init() )
goto EXIT;
/* Find the initial set of input devices */
if( !evin_iomon_init() )
goto EXIT;
evin_iomon_switch_states_update();
evin_iomon_keyboard_state_update();
evin_iomon_mouse_state_update();
status = TRUE;
EXIT:
return status;
}
/** Exit function for the /dev/input event component
*/
void
mce_input_exit(void)
{
#ifdef ENABLE_DOUBLETAP_EMULATION
/* Remove fake doubletap policy change notifier */
mce_setting_notifier_remove(evin_doubletap_emulation_enabled_setting_id),
evin_doubletap_emulation_enabled_setting_id = 0;
#endif
/* Remove triggers/filters from datapipes */
evin_datapipe_quit();
/* Remove input device directory monitor */
evin_devdir_monitor_quit();
evin_setting_quit();
evin_iomon_quit();
/* Reset input grab state machines */
evin_ts_grab_quit();
evin_input_grab_reset(&evin_kp_grab_state);
/* Release event mapping lookup tables */
evin_event_mapper_quit();
evin_touchstate_cancel_update();
evin_dbus_quit();
return;
}