* /\ _ \ /\_ \ /\_ \
* \ \ \L\ \\//\ \ \//\ \ __ __ _ __ ___
* \ \ __ \ \ \ \ \ \ \ /'__`\ /'_ `\/\`'__\/ __`\
* \ \ \/\ \ \_\ \_ \_\ \_/\ __//\ \L\ \ \ \//\ \L\ \
* \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/
* \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/
* /\____/
* \_/__/
*
* Linux haptic (force-feedback) device driver.
*
* By Beoran.
*
* See LICENSE.txt for copyright information.
*/
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <math.h>
#include <stdio.h>
#include <sys/time.h>
#include "allegro5/allegro.h"
#include "allegro5/internal/aintern_haptic.h"
#include "allegro5/internal/aintern_ljoynu.h"
#include "allegro5/platform/aintunix.h"
#ifdef ALLEGRO_HAVE_LINUX_INPUT_H
#include <linux/input.h>
ALLEGRO_DEBUG_CHANNEL("lhaptic")
#ifndef input_event_sec
#define input_event_sec time.tv_sec
#define input_event_usec time.tv_usec
#endif
#define HAPTICS_MAX 32
#define HAPTICS_EFFECTS_MAX 16
typedef struct
{
struct ALLEGRO_HAPTIC parent;
bool active;
int fd;
int flags;
int effects[HAPTICS_EFFECTS_MAX];
} ALLEGRO_HAPTIC_LINUX;
#define LONG_BITS (sizeof(long) * 8)
#define NLONGS(x) (((x) + LONG_BITS - 1) / LONG_BITS)
#define TEST_BIT(nr, addr) \
((1UL << ((nr) % LONG_BITS)) & (addr)[(nr) / LONG_BITS])
static bool lhap_init_haptic(void);
static void lhap_exit_haptic(void);
static bool lhap_is_mouse_haptic(ALLEGRO_MOUSE *dev);
static bool lhap_is_joystick_haptic(ALLEGRO_JOYSTICK *);
static bool lhap_is_keyboard_haptic(ALLEGRO_KEYBOARD *dev);
static bool lhap_is_display_haptic(ALLEGRO_DISPLAY *dev);
static bool lhap_is_touch_input_haptic(ALLEGRO_TOUCH_INPUT *dev);
static ALLEGRO_HAPTIC *lhap_get_from_mouse(ALLEGRO_MOUSE *dev);
static ALLEGRO_HAPTIC *lhap_get_from_joystick(ALLEGRO_JOYSTICK *dev);
static ALLEGRO_HAPTIC *lhap_get_from_keyboard(ALLEGRO_KEYBOARD *dev);
static ALLEGRO_HAPTIC *lhap_get_from_display(ALLEGRO_DISPLAY *dev);
static ALLEGRO_HAPTIC *lhap_get_from_touch_input(ALLEGRO_TOUCH_INPUT *dev);
static bool lhap_release(ALLEGRO_HAPTIC *haptic);
static bool lhap_get_active(ALLEGRO_HAPTIC *hap);
static int lhap_get_capabilities(ALLEGRO_HAPTIC *dev);
static double lhap_get_gain(ALLEGRO_HAPTIC *dev);
static bool lhap_set_gain(ALLEGRO_HAPTIC *dev, double);
static int lhap_get_max_effects(ALLEGRO_HAPTIC *dev);
static bool lhap_is_effect_ok(ALLEGRO_HAPTIC *dev, ALLEGRO_HAPTIC_EFFECT *eff);
static bool lhap_upload_effect(ALLEGRO_HAPTIC *dev,
ALLEGRO_HAPTIC_EFFECT *eff,
ALLEGRO_HAPTIC_EFFECT_ID *id);
static bool lhap_play_effect(ALLEGRO_HAPTIC_EFFECT_ID *id, int loop);
static bool lhap_stop_effect(ALLEGRO_HAPTIC_EFFECT_ID *id);
static bool lhap_is_effect_playing(ALLEGRO_HAPTIC_EFFECT_ID *id);
static bool lhap_release_effect(ALLEGRO_HAPTIC_EFFECT_ID *id);
static double lhap_get_autocenter(ALLEGRO_HAPTIC *dev);
static bool lhap_set_autocenter(ALLEGRO_HAPTIC *dev, double);
static void lhap_timerclear(struct input_event *evt);
ALLEGRO_HAPTIC_DRIVER _al_hapdrv_linux =
{
_ALLEGRO_HAPDRV_LINUX,
"",
"",
"Linux haptic(s)",
lhap_init_haptic,
lhap_exit_haptic,
lhap_is_mouse_haptic,
lhap_is_joystick_haptic,
lhap_is_keyboard_haptic,
lhap_is_display_haptic,
lhap_is_touch_input_haptic,
lhap_get_from_mouse,
lhap_get_from_joystick,
lhap_get_from_keyboard,
lhap_get_from_display,
lhap_get_from_touch_input,
lhap_get_active,
lhap_get_capabilities,
lhap_get_gain,
lhap_set_gain,
lhap_get_max_effects,
lhap_is_effect_ok,
lhap_upload_effect,
lhap_play_effect,
lhap_stop_effect,
lhap_is_effect_playing,
lhap_release_effect,
lhap_release,
lhap_get_autocenter,
lhap_set_autocenter
};
static ALLEGRO_HAPTIC_LINUX haptics[HAPTICS_MAX];
static ALLEGRO_MUTEX *haptic_mutex = NULL;
struct CAP_MAP {
int linux_bit;
int allegro_bit;
};
static const struct CAP_MAP cap_map[] = {
{ FF_PERIODIC, ALLEGRO_HAPTIC_PERIODIC },
{ FF_RUMBLE, ALLEGRO_HAPTIC_RUMBLE },
{ FF_CONSTANT, ALLEGRO_HAPTIC_CONSTANT },
{ FF_SPRING, ALLEGRO_HAPTIC_SPRING },
{ FF_FRICTION, ALLEGRO_HAPTIC_FRICTION },
{ FF_DAMPER, ALLEGRO_HAPTIC_DAMPER },
{ FF_INERTIA, ALLEGRO_HAPTIC_INERTIA },
{ FF_RAMP, ALLEGRO_HAPTIC_RAMP },
{ FF_SQUARE, ALLEGRO_HAPTIC_SQUARE },
{ FF_TRIANGLE, ALLEGRO_HAPTIC_TRIANGLE },
{ FF_SINE, ALLEGRO_HAPTIC_SINE },
{ FF_SAW_UP, ALLEGRO_HAPTIC_SAW_UP },
{ FF_SAW_DOWN, ALLEGRO_HAPTIC_SAW_DOWN },
{ FF_CUSTOM, ALLEGRO_HAPTIC_CUSTOM },
{ FF_GAIN, ALLEGRO_HAPTIC_GAIN },
{ FF_AUTOCENTER, ALLEGRO_HAPTIC_AUTOCENTER },
{ -1, -1 }
};
static bool lhap_init_haptic(void)
{
int i;
ASSERT(haptic_mutex == NULL);
haptic_mutex = al_create_mutex();
if (!haptic_mutex)
return false;
for (i = 0; i < HAPTICS_MAX; i++) {
haptics[i].active = false;
}
return true;
}
static ALLEGRO_HAPTIC_LINUX *lhap_get_available_haptic(void)
{
int i;
for (i = 0; i < HAPTICS_MAX; i++) {
if (!haptics[i].active) {
haptics[i].active = true;
return &haptics[i];
}
}
return NULL;
}
static ALLEGRO_HAPTIC_LINUX *lhap_from_al(ALLEGRO_HAPTIC *hap)
{
return (ALLEGRO_HAPTIC_LINUX *)hap;
}
static void lhap_exit_haptic(void)
{
ASSERT(haptic_mutex);
al_destroy_mutex(haptic_mutex);
haptic_mutex = NULL;
}
static bool lhap_type2lin(__u16 *res, int type)
{
ASSERT(res);
switch (type) {
case ALLEGRO_HAPTIC_RUMBLE:
(*res) = FF_RUMBLE;
break;
case ALLEGRO_HAPTIC_PERIODIC:
(*res) = FF_PERIODIC;
break;
case ALLEGRO_HAPTIC_CONSTANT:
(*res) = FF_CONSTANT;
break;
case ALLEGRO_HAPTIC_SPRING:
(*res) = FF_SPRING;
break;
case ALLEGRO_HAPTIC_FRICTION:
(*res) = FF_FRICTION;
break;
case ALLEGRO_HAPTIC_DAMPER:
(*res) = FF_DAMPER;
break;
case ALLEGRO_HAPTIC_INERTIA:
(*res) = FF_INERTIA;
break;
case ALLEGRO_HAPTIC_RAMP:
(*res) = FF_RAMP;
break;
default:
return false;
}
return true;
}
static bool lhap_wave2lin(__u16 *res, int type)
{
ASSERT(res);
switch (type) {
case ALLEGRO_HAPTIC_SQUARE:
(*res) = FF_SQUARE;
break;
case ALLEGRO_HAPTIC_TRIANGLE:
(*res) = FF_TRIANGLE;
break;
case ALLEGRO_HAPTIC_SINE:
(*res) = FF_SINE;
break;
case ALLEGRO_HAPTIC_SAW_UP:
(*res) = FF_SAW_UP;
break;
case ALLEGRO_HAPTIC_SAW_DOWN:
(*res) = FF_SAW_DOWN;
break;
case ALLEGRO_HAPTIC_CUSTOM:
(*res) = FF_CUSTOM;
break;
default:
return false;
}
return true;
}
* Return false if out of bounds.
*/
static bool lhap_time2lin(__u16 *res, double sec)
{
ASSERT(res);
if (sec < 0.0 || sec > 32.767)
return false;
(*res) = (__u16) round(sec * 1000.0);
return true;
}
* Return false if out of bounds. This one allows negative times.
*/
static bool lhap_stime2lin(__s16 *res, double sec)
{
ASSERT(res);
if (sec < -32.767 || sec > 32.767)
return false;
(*res) = (__s16) round(sec * 1000.0);
return true;
}
static bool lhap_replay2lin(struct ff_replay *lin,
struct ALLEGRO_HAPTIC_REPLAY *al)
{
return lhap_time2lin(&lin->delay, al->delay)
&& lhap_time2lin(&lin->length, al->length);
}
* Returns false if out of bounds.
*/
static bool lhap_level2lin(__u16 *res, double level)
{
ASSERT(res);
if (level < 0.0 || level > 1.0)
return false;
*res = (__u16) round(level * (double)0x7fff);
return true;
}
* Returns false if out of bounds.
*/
static bool lhap_slevel2lin(__s16 *res, double level)
{
ASSERT(res);
if (level < -1.0 || level > 1.0)
return false;
*res = (__s16) round(level * (double)0x7ffe);
return true;
}
static bool lhap_envelope2lin(struct ff_envelope *lin,
struct ALLEGRO_HAPTIC_ENVELOPE *al)
{
return lhap_time2lin(&lin->attack_length, al->attack_length)
&& lhap_time2lin(&lin->fade_length, al->fade_length)
&& lhap_level2lin(&lin->attack_level, al->attack_level)
&& lhap_level2lin(&lin->fade_level, al->fade_level);
}
static bool lhap_rumble2lin(struct ff_rumble_effect *lin,
struct ALLEGRO_HAPTIC_RUMBLE_EFFECT *al)
{
return lhap_level2lin(&lin->strong_magnitude, al->strong_magnitude)
&& lhap_level2lin(&lin->weak_magnitude, al->weak_magnitude);
}
static bool lhap_constant2lin(struct ff_constant_effect *lin,
struct ALLEGRO_HAPTIC_CONSTANT_EFFECT *al)
{
return lhap_envelope2lin(&lin->envelope, &al->envelope)
&& lhap_slevel2lin(&lin->level, al->level);
}
static bool lhap_ramp2lin(struct ff_ramp_effect *lin,
struct ALLEGRO_HAPTIC_RAMP_EFFECT *al)
{
return lhap_envelope2lin(&lin->envelope, &al->envelope)
&& lhap_slevel2lin(&lin->start_level, al->start_level)
&& lhap_slevel2lin(&lin->end_level, al->end_level);
}
static bool lhap_condition2lin(struct ff_condition_effect *lin,
struct ALLEGRO_HAPTIC_CONDITION_EFFECT *al)
{
return lhap_slevel2lin(&lin->center, al->center)
&& lhap_level2lin(&lin->deadband, al->deadband)
&& lhap_slevel2lin(&lin->right_coeff, al->right_coeff)
&& lhap_level2lin(&lin->right_saturation, al->right_saturation)
&& lhap_slevel2lin(&lin->left_coeff, al->left_coeff)
&& lhap_level2lin(&lin->left_saturation, al->left_saturation);
}
static bool lhap_periodic2lin(struct ff_periodic_effect *lin,
struct ALLEGRO_HAPTIC_PERIODIC_EFFECT *al)
{
* haptic driver supports it.
*/
if (al->custom_data)
return false;
return lhap_slevel2lin(&lin->magnitude, al->magnitude)
&& lhap_stime2lin(&lin->offset, al->offset)
&& lhap_time2lin(&lin->period, al->period)
&& lhap_time2lin(&lin->phase, al->phase)
&& lhap_wave2lin(&lin->waveform, al->waveform)
&& lhap_envelope2lin(&lin->envelope, &al->envelope);
}
static bool lhap_effect2lin(struct ff_effect *lin, ALLEGRO_HAPTIC_EFFECT *al)
{
memset(lin, 0, sizeof(*lin));
if (!lhap_type2lin(&lin->type, al->type))
return false;
lin->direction = (__u16)
round(((double)0xC000 * al->direction.angle) / (2 * M_PI));
lin->id = -1;
if (!lhap_replay2lin(&lin->replay, &al->replay))
return false;
switch (lin->type) {
case FF_RUMBLE:
return lhap_rumble2lin(&lin->u.rumble, &al->data.rumble);
case FF_PERIODIC:
return lhap_periodic2lin(&lin->u.periodic, &al->data.periodic);
case FF_CONSTANT:
return lhap_constant2lin(&lin->u.constant, &al->data.constant);
case FF_RAMP:
return lhap_ramp2lin(&lin->u.ramp, &al->data.ramp);
case FF_SPRING:
case FF_FRICTION:
case FF_DAMPER:
case FF_INERTIA:
return lhap_condition2lin(&lin->u.condition[0], &al->data.condition);
default:
return false;
}
}
static bool lhap_get_active(ALLEGRO_HAPTIC *haptic)
{
ALLEGRO_HAPTIC_LINUX *lhap = lhap_from_al(haptic);
return lhap->active;
}
static bool lhap_is_mouse_haptic(ALLEGRO_MOUSE *mouse)
{
(void)mouse;
return false;
}
static bool lhap_fd_can_ff(int fd)
{
long bitmask[NLONGS(EV_CNT)] = { 0 };
if (ioctl(fd, EVIOCGBIT(0, sizeof(bitmask)), bitmask) < 0) {
return false;
}
if (TEST_BIT(EV_FF, bitmask)) {
return true;
}
return false;
}
static bool lhap_is_joystick_haptic(ALLEGRO_JOYSTICK *joy)
{
ALLEGRO_JOYSTICK_LINUX *ljoy = (ALLEGRO_JOYSTICK_LINUX *) joy;
if (!al_is_joystick_installed())
return false;
if (!al_get_joystick_active(joy))
return false;
if (ljoy->fd <= 0)
return false;
return lhap_fd_can_ff(ljoy->fd);
}
static bool lhap_is_display_haptic(ALLEGRO_DISPLAY *dev)
{
(void)dev;
return false;
}
static bool lhap_is_keyboard_haptic(ALLEGRO_KEYBOARD *dev)
{
(void)dev;
return false;
}
static bool lhap_is_touch_input_haptic(ALLEGRO_TOUCH_INPUT *dev)
{
(void)dev;
return false;
}
static ALLEGRO_HAPTIC *lhap_get_from_mouse(ALLEGRO_MOUSE *mouse)
{
(void)mouse;
return NULL;
}
static bool get_haptic_capabilities(int fd, int *capabilities)
{
unsigned long bitmask[NLONGS(FF_CNT)] = { 0 };
int caps;
int i;
if (ioctl(fd, EVIOCGBIT(EV_FF, sizeof(bitmask)), bitmask) < 0) {
ALLEGRO_ERROR("EVIOCGBIT failed for fd %d", fd);
return false;
}
caps = 0;
for (i = 0; cap_map[i].allegro_bit >= 0; i++) {
if (TEST_BIT(cap_map[i].linux_bit, bitmask)) {
caps |= cap_map[i].allegro_bit;
}
}
(*capabilities) = caps;
ALLEGRO_INFO("Capabilities: 0x%x\n", caps);
return true;
}
static ALLEGRO_HAPTIC *lhap_get_from_joystick(ALLEGRO_JOYSTICK *joy)
{
ALLEGRO_JOYSTICK_LINUX *ljoy = (ALLEGRO_JOYSTICK_LINUX *) joy;
ALLEGRO_HAPTIC_LINUX *lhap;
int i;
if (!al_is_joystick_haptic(joy))
return NULL;
al_lock_mutex(haptic_mutex);
lhap = lhap_get_available_haptic();
if (!lhap) {
al_unlock_mutex(haptic_mutex);
return NULL;
}
lhap->parent.device = joy;
lhap->parent.from = _AL_HAPTIC_FROM_JOYSTICK;
lhap->fd = ljoy->fd;
lhap->active = true;
for (i = 0; i < HAPTICS_EFFECTS_MAX; i++) {
lhap->effects[i] = -1;
}
lhap->parent.gain = 1.0;
get_haptic_capabilities(lhap->fd, &lhap->flags);
al_unlock_mutex(haptic_mutex);
return &lhap->parent;
}
static ALLEGRO_HAPTIC *lhap_get_from_display(ALLEGRO_DISPLAY *dev)
{
(void)dev;
return NULL;
}
static ALLEGRO_HAPTIC *lhap_get_from_keyboard(ALLEGRO_KEYBOARD *dev)
{
(void)dev;
return NULL;
}
static ALLEGRO_HAPTIC *lhap_get_from_touch_input(ALLEGRO_TOUCH_INPUT *dev)
{
(void)dev;
return NULL;
}
static int lhap_get_capabilities(ALLEGRO_HAPTIC *dev)
{
ALLEGRO_HAPTIC_LINUX *lhap = lhap_from_al(dev);
return lhap->flags;
}
static double lhap_get_gain(ALLEGRO_HAPTIC *dev)
{
ALLEGRO_HAPTIC_LINUX *lhap = lhap_from_al(dev);
(void)dev;
if(!al_is_haptic_capable(dev, ALLEGRO_HAPTIC_GAIN)) {
return 0.0;
}
* So, return the stored gain.
*/
return lhap->parent.gain;
}
static bool lhap_set_gain(ALLEGRO_HAPTIC *dev, double gain)
{
ALLEGRO_HAPTIC_LINUX *lhap = lhap_from_al(dev);
struct input_event ie;
lhap->parent.gain = gain;
lhap_timerclear(&ie);
ie.type = EV_FF;
ie.code = FF_GAIN;
ie.value = (__s32) ((double)0xFFFF * gain);
if (write(lhap->fd, &ie, sizeof(ie)) < 0) {
return false;
}
return true;
}
static bool lhap_set_autocenter(ALLEGRO_HAPTIC *dev, double autocenter)
{
ALLEGRO_HAPTIC_LINUX *lhap = lhap_from_al(dev);
struct input_event ie;
lhap->parent.autocenter = autocenter;
lhap_timerclear(&ie);
ie.type = EV_FF;
ie.code = FF_AUTOCENTER;
ie.value = (__s32) ((double)0xFFFF * autocenter);
if (write(lhap->fd, &ie, sizeof(ie)) < 0) {
return false;
}
return true;
}
static double lhap_get_autocenter(ALLEGRO_HAPTIC *dev)
{
ALLEGRO_HAPTIC_LINUX *lhap = lhap_from_al(dev);
(void)dev;
if(!al_is_haptic_capable(dev, ALLEGRO_HAPTIC_AUTOCENTER)) {
return 0.0;
}
* So, return the stored autocenter.
*/
return lhap->parent.autocenter;
}
int lhap_get_max_effects(ALLEGRO_HAPTIC *dev)
{
ALLEGRO_HAPTIC_LINUX *lhap = lhap_from_al(dev);
int n_effects;
if (ioctl(lhap->fd, EVIOCGEFFECTS, &n_effects) < 0) {
ALLEGRO_WARN("EVIOCGEFFECTS failed on fd %d\n", lhap->fd);
n_effects = HAPTICS_EFFECTS_MAX;
}
if (n_effects < HAPTICS_EFFECTS_MAX)
return n_effects;
else
return HAPTICS_EFFECTS_MAX;
}
static bool lhap_is_effect_ok(ALLEGRO_HAPTIC *haptic,
ALLEGRO_HAPTIC_EFFECT *effect)
{
int caps;
struct ff_effect leff;
caps = al_get_haptic_capabilities(haptic);
if (caps & effect->type) {
return lhap_effect2lin(&leff, effect);
}
return false;
}
static bool lhap_upload_effect(ALLEGRO_HAPTIC *dev,
ALLEGRO_HAPTIC_EFFECT *effect, ALLEGRO_HAPTIC_EFFECT_ID *id)
{
ALLEGRO_HAPTIC_LINUX *lhap = lhap_from_al(dev);
struct ff_effect leff;
int found;
int i;
ASSERT(dev);
ASSERT(id);
ASSERT(effect);
id->_haptic = NULL;
id->_id = -1;
id->_handle = -1;
if (!lhap_effect2lin(&leff, effect)) {
ALLEGRO_WARN("lhap_effect2lin failed");
return false;
}
found = -1;
for (i = 0; i < al_get_max_haptic_effects(dev); i++) {
if (lhap->effects[i] < 0) {
found = i;
break;
}
}
if (found < 0) {
ALLEGRO_WARN("No free effect slot.");
return false;
}
leff.id = -1;
if (ioctl(lhap->fd, EVIOCSFF, &leff) < 0) {
ALLEGRO_ERROR("EVIOCSFF failed for fd %d\n", lhap->fd);
return false;
}
id->_haptic = dev;
id->_id = found;
id->_handle = leff.id;
id->_effect_duration = al_get_haptic_effect_duration(effect);
id->_playing = false;
lhap->effects[i] = found;
return true;
}
static bool lhap_play_effect(ALLEGRO_HAPTIC_EFFECT_ID *id, int loops)
{
ALLEGRO_HAPTIC_LINUX *lhap = (ALLEGRO_HAPTIC_LINUX *) id->_haptic;
struct input_event play;
int fd;
double now;
double duration;
if (!lhap)
return false;
fd = lhap->fd;
lhap_timerclear(&play);
play.type = EV_FF;
play.code = id->_handle;
loops = (loops < 0) ? 1 : loops;
play.value = loops;
if (write(fd, (const void *)&play, sizeof(play)) < 0) {
ALLEGRO_ERROR("Effect play failed.\n");
return false;
}
now = al_get_time();
duration = loops * id->_effect_duration;
id->_playing = true;
id->_start_time = now;
id->_end_time = now + duration;
return true;
}
static bool lhap_stop_effect(ALLEGRO_HAPTIC_EFFECT_ID *id)
{
ALLEGRO_HAPTIC_LINUX *lhap = (ALLEGRO_HAPTIC_LINUX *) id->_haptic;
struct input_event play;
if (!lhap)
return false;
memset(&play, 0, sizeof(play));
play.type = EV_FF;
play.code = id->_handle;
play.value = 0;
if (write(lhap->fd, (const void *)&play, sizeof(play)) < 0) {
ALLEGRO_ERROR("Stop effect failed.\n");
return false;
}
id->_playing = false;
return true;
}
static bool lhap_is_effect_playing(ALLEGRO_HAPTIC_EFFECT_ID *id)
{
ASSERT(id);
* if the effect has been playing long enough to be finished or not.
*/
return (id->_playing && al_get_time() < id->_end_time);
}
static bool lhap_release_effect(ALLEGRO_HAPTIC_EFFECT_ID *id)
{
ALLEGRO_HAPTIC_LINUX *lhap = (ALLEGRO_HAPTIC_LINUX *)id->_haptic;
lhap_stop_effect(id);
if (ioctl(lhap->fd, EVIOCRMFF, id->_handle) < 0) {
ALLEGRO_ERROR("EVIOCRMFF failed.\n");
return false;
}
lhap->effects[id->_id] = -1;
return true;
}
static bool lhap_release(ALLEGRO_HAPTIC *haptic)
{
ALLEGRO_HAPTIC_LINUX *lhap = lhap_from_al(haptic);
ASSERT(haptic);
if (!lhap->active)
return false;
lhap->active = false;
lhap->fd = -1;
return true;
}
void lhap_timerclear(struct input_event* evt)
{
evt->input_event_sec = 0;
evt->input_event_usec = 0;
}
#endif