@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Purism SPC
|
||||
* SPDX-License-Identifier: GPL-3.0+
|
||||
* Author: Guido Günther <agx@sigxcpu.org>
|
||||
*
|
||||
* See https://www.kernel.org/doc/html/latest/input/ff.html
|
||||
* and fftest.c from the joystick package.
|
||||
*/
|
||||
|
||||
#define G_LOG_DOMAIN "fbd-dev-sound"
|
||||
|
||||
#include "fbd-dev-sound.h"
|
||||
#include "fbd-feedback-sound.h"
|
||||
|
||||
#include <gsound.h>
|
||||
|
||||
/**
|
||||
* SECTION:fbd-dev-sound
|
||||
* @short_description: Sound interface
|
||||
* @Title: FbdDevSound
|
||||
*
|
||||
* The #FbdDevSound is used to play sounds via the systems audio
|
||||
* system.
|
||||
*/
|
||||
|
||||
typedef struct _FbdAsyncData {
|
||||
FbdDevSoundPlayedCallback callback;
|
||||
FbdFeedbackSound *feedback;
|
||||
} FbdAsyncData;
|
||||
|
||||
typedef struct _FbdDevSound {
|
||||
GObject parent;
|
||||
|
||||
GSoundContext *ctx;
|
||||
} FbdDevSound;
|
||||
|
||||
static void initable_iface_init (GInitableIface *iface);
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE (FbdDevSound, fbd_dev_sound, G_TYPE_OBJECT,
|
||||
G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init));
|
||||
|
||||
static void
|
||||
fbd_dev_sound_dispose (GObject *object)
|
||||
{
|
||||
FbdDevSound *self = FBD_DEV_SOUND (object);
|
||||
|
||||
g_clear_object (&self->ctx);
|
||||
|
||||
G_OBJECT_CLASS (fbd_dev_sound_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
initable_init (GInitable *initable,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
FbdDevSound *self = FBD_DEV_SOUND (initable);
|
||||
|
||||
self->ctx = gsound_context_new(NULL, error);
|
||||
if (!self->ctx)
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
initable_iface_init (GInitableIface *iface)
|
||||
{
|
||||
iface->init = initable_init;
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_dev_sound_class_init (FbdDevSoundClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->dispose = fbd_dev_sound_dispose;
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_dev_sound_init (FbdDevSound *self)
|
||||
{
|
||||
}
|
||||
|
||||
FbdDevSound *
|
||||
fbd_dev_sound_new (GError **error)
|
||||
{
|
||||
return FBD_DEV_SOUND (g_initable_new (FBD_TYPE_DEV_SOUND,
|
||||
NULL,
|
||||
error,
|
||||
NULL));
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
on_sound_play_finished_callback (GSoundContext *ctx,
|
||||
GAsyncResult *res,
|
||||
FbdAsyncData *data)
|
||||
{
|
||||
GError *err = NULL;
|
||||
|
||||
if (!gsound_context_play_full_finish (ctx, res, &err)) {
|
||||
g_warning ("Failed to play sound: %s", err->message);
|
||||
}
|
||||
|
||||
(*data->callback)(data->feedback);
|
||||
g_object_unref (data->feedback);
|
||||
g_free (data);
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
fbd_dev_sound_play (FbdDevSound *self, FbdFeedbackSound *feedback, FbdDevSoundPlayedCallback callback)
|
||||
{
|
||||
FbdAsyncData *data;
|
||||
|
||||
g_return_val_if_fail (FBD_IS_DEV_SOUND (self), FALSE);
|
||||
g_return_val_if_fail (GSOUND_IS_CONTEXT (self->ctx), FALSE);
|
||||
|
||||
data = g_new0 (FbdAsyncData, 1);
|
||||
data->callback = callback;
|
||||
data->feedback = g_object_ref (feedback);
|
||||
|
||||
gsound_context_play_full (self->ctx, NULL,
|
||||
(GAsyncReadyCallback)on_sound_play_finished_callback,
|
||||
data,
|
||||
GSOUND_ATTR_EVENT_ID, fbd_feedback_sound_get_effect (feedback),
|
||||
GSOUND_ATTR_EVENT_DESCRIPTION, "Feedbackd sound feedback",
|
||||
NULL);
|
||||
return TRUE;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Purism SPC
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0+
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <glib-object.h>
|
||||
|
||||
#include "fbd-feedback-sound.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define FBD_TYPE_DEV_SOUND (fbd_dev_sound_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (FbdDevSound, fbd_dev_sound, FBD, DEV_SOUND, GObject);
|
||||
|
||||
typedef void (*FbdDevSoundPlayedCallback)(FbdFeedbackSound *feedback);
|
||||
|
||||
FbdDevSound *fbd_dev_sound_new (GError **error);
|
||||
gboolean fbd_dev_sound_play (FbdDevSound *self,
|
||||
FbdFeedbackSound *feedback,
|
||||
FbdDevSoundPlayedCallback callback);
|
||||
gboolean fbd_dev_sound_stop (FbdDevSound *self, FbdFeedbackSound *feedback);
|
||||
|
||||
G_END_DECLS
|
||||
@@ -0,0 +1,367 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Purism SPC
|
||||
* SPDX-License-Identifier: GPL-3.0+
|
||||
* Author: Guido Günther <agx@sigxcpu.org>
|
||||
*
|
||||
* See https://www.kernel.org/doc/html/latest/input/ff.html
|
||||
* and fftest.c from the joystick package.
|
||||
*/
|
||||
|
||||
#define G_LOG_DOMAIN "fbd-dev-vibra"
|
||||
|
||||
#include "fbd-dev-vibra.h"
|
||||
|
||||
#include <gio/gio.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <linux/input.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
/**
|
||||
* SECTION:fbd-dev-vibra
|
||||
* @short_description: Haptic motor device interface
|
||||
* @Title: FbdDevVibra
|
||||
*
|
||||
* The #FbdDevVibra is used to interface with haptic motor via the force
|
||||
* feedback interface. It currently only supports one id at a time.
|
||||
*/
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_DEVICE,
|
||||
PROP_LAST_PROP,
|
||||
};
|
||||
static GParamSpec *props[PROP_LAST_PROP];
|
||||
|
||||
typedef enum {
|
||||
FBD_DEV_VIBRA_FEATURE_RUMBLE,
|
||||
FBD_DEV_VIBRA_FEATURE_PERIODIC,
|
||||
FBD_DEV_VIBRA_FEATURE_GAIN,
|
||||
} FbdDevVibraFeatureFlags;
|
||||
|
||||
typedef struct _FbdDevVibra {
|
||||
GObject parent;
|
||||
|
||||
GUdevDevice *device;
|
||||
gint fd;
|
||||
gint id; /* currently used id */
|
||||
|
||||
FbdDevVibraFeatureFlags features;
|
||||
} FbdDevVibra;
|
||||
|
||||
static void initable_iface_init (GInitableIface *iface);
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE (FbdDevVibra, fbd_dev_vibra, G_TYPE_OBJECT,
|
||||
G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init));
|
||||
|
||||
static void
|
||||
fbd_dev_vibra_set_property (GObject *object,
|
||||
guint property_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
FbdDevVibra *self = FBD_DEV_VIBRA (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_DEVICE:
|
||||
g_clear_object (&self->device);
|
||||
self->device = g_value_dup_object (value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
fbd_dev_vibra_get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
switch (property_id) {
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#define BITS_PER_LONG (8 * sizeof (long))
|
||||
|
||||
#define HAS_FEATURE(fbit, a) \
|
||||
(a[(fbit/BITS_PER_LONG)] >> ((fbit % BITS_PER_LONG)) & 1)
|
||||
|
||||
static gboolean
|
||||
initable_init (GInitable *initable,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
FbdDevVibra *self = FBD_DEV_VIBRA (initable);
|
||||
const char *filename = g_udev_device_get_device_file (self->device);
|
||||
gulong features[1 + FF_MAX/BITS_PER_LONG];
|
||||
struct input_event gain = { 0 };
|
||||
|
||||
self->fd = open (filename, O_RDWR | O_NONBLOCK, O_RDWR);
|
||||
if (self->fd < 0) {
|
||||
g_set_error (error,
|
||||
G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
||||
"Unable to open '%s': %s",
|
||||
filename, g_strerror (errno));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (ioctl (self->fd, EVIOCGBIT(EV_FF, sizeof (features)), features) == -1) {
|
||||
g_set_error (error,
|
||||
G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
||||
"Unable to probe features of '%s': %s",
|
||||
filename, g_strerror (errno));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (HAS_FEATURE(FF_RUMBLE, features))
|
||||
self->features |= FBD_DEV_VIBRA_FEATURE_RUMBLE;
|
||||
else {
|
||||
g_set_error (error,
|
||||
G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
||||
"No rumble capable vibra device “%s”: %s",
|
||||
filename, g_strerror (errno));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (HAS_FEATURE(FF_PERIODIC, features))
|
||||
self->features |= FBD_DEV_VIBRA_FEATURE_PERIODIC;
|
||||
else {
|
||||
g_set_error (error,
|
||||
G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
||||
"No rumble capable vibra device “%s”: %s",
|
||||
filename, g_strerror (errno));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Set gain to 75% if supported */
|
||||
if (HAS_FEATURE(FF_GAIN, features)) {
|
||||
self->features |= FBD_DEV_VIBRA_FEATURE_GAIN;
|
||||
memset(&gain, 0, sizeof(gain));
|
||||
gain.type = EV_FF;
|
||||
gain.code = FF_GAIN;
|
||||
gain.value = 0xC000; /* [0, 0xFFFF]) */
|
||||
|
||||
g_debug("Setting master gain to 75%%");
|
||||
if (write(self->fd, &gain, sizeof(gain)) != sizeof(gain)) {
|
||||
g_set_error (error,
|
||||
G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
||||
"Unable to set gain of '%s': %s",
|
||||
filename, g_strerror (errno));
|
||||
}
|
||||
} else {
|
||||
g_debug ("Gain unsupported");
|
||||
}
|
||||
|
||||
g_debug ("Vibra device at '%s' usable", filename);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
initable_iface_init (GInitableIface *iface)
|
||||
{
|
||||
iface->init = initable_init;
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_dev_vibra_dispose (GObject *object)
|
||||
{
|
||||
FbdDevVibra *self = FBD_DEV_VIBRA (object);
|
||||
|
||||
g_clear_object (&self->device);
|
||||
|
||||
G_OBJECT_CLASS (fbd_dev_vibra_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_dev_vibra_finalize (GObject *object)
|
||||
{
|
||||
FbdDevVibra *self = FBD_DEV_VIBRA (object);
|
||||
|
||||
if (self->fd >= 0) {
|
||||
close (self->fd);
|
||||
self->fd = -1;
|
||||
}
|
||||
|
||||
G_OBJECT_CLASS (fbd_dev_vibra_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_dev_vibra_class_init (FbdDevVibraClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->set_property = fbd_dev_vibra_set_property;
|
||||
object_class->get_property = fbd_dev_vibra_get_property;
|
||||
|
||||
object_class->dispose = fbd_dev_vibra_dispose;
|
||||
object_class->finalize = fbd_dev_vibra_finalize;
|
||||
|
||||
props[PROP_DEVICE] =
|
||||
g_param_spec_object (
|
||||
"device",
|
||||
"Device",
|
||||
"The udev device",
|
||||
G_UDEV_TYPE_DEVICE,
|
||||
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_dev_vibra_init (FbdDevVibra *self)
|
||||
{
|
||||
}
|
||||
|
||||
FbdDevVibra *
|
||||
fbd_dev_vibra_new (GUdevDevice *device, GError **error)
|
||||
{
|
||||
return FBD_DEV_VIBRA (g_initable_new (FBD_TYPE_DEV_VIBRA,
|
||||
NULL,
|
||||
error,
|
||||
"device", device,
|
||||
NULL));
|
||||
}
|
||||
|
||||
gboolean
|
||||
fbd_dev_vibra_rumble (FbdDevVibra *self, guint duration, gboolean upload)
|
||||
{
|
||||
struct input_event event = { 0 };
|
||||
struct ff_effect effect = { 0 };
|
||||
|
||||
g_return_val_if_fail (FBD_IS_DEV_VIBRA (self), FALSE);
|
||||
|
||||
memset(&effect, 0, sizeof(effect));
|
||||
effect.type = FF_RUMBLE;
|
||||
effect.id = -1;
|
||||
effect.u.rumble.strong_magnitude = 0x8000;
|
||||
effect.u.rumble.weak_magnitude = 0;
|
||||
effect.replay.length = duration;
|
||||
effect.replay.delay = 0;
|
||||
|
||||
if (upload) {
|
||||
g_debug("Uploading rumbling vibra effect (%d)", self->fd);
|
||||
if (ioctl(self->fd, EVIOCSFF, &effect) == -1) {
|
||||
g_warning ("Failed to upload rumbling vibra effect: %s", g_strerror (errno));
|
||||
return FALSE;
|
||||
}
|
||||
self->id = effect.id;
|
||||
}
|
||||
|
||||
g_debug("Playing rumbling vibra effect id %d", effect.id);
|
||||
event.type = EV_FF;
|
||||
event.value = 1;
|
||||
event.code = self->id;
|
||||
|
||||
if (write (self->fd, (const void*) &event, sizeof (event)) < 0) {
|
||||
g_warning ("Failed to play rumbling vibra effect.");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* TODO: fall back to multiple rumbles when sine not supported */
|
||||
gboolean
|
||||
fbd_dev_vibra_periodic (FbdDevVibra *self, guint duration, guint magnitude,
|
||||
guint fade_in_level, guint fade_in_time)
|
||||
{
|
||||
struct input_event event;
|
||||
struct ff_effect effect = { 0 };
|
||||
|
||||
g_return_val_if_fail (FBD_IS_DEV_VIBRA (self), FALSE);
|
||||
|
||||
if (!magnitude)
|
||||
magnitude = 0x7FFF;
|
||||
|
||||
if (!fade_in_level)
|
||||
fade_in_level = magnitude;
|
||||
|
||||
if (!fade_in_time)
|
||||
fade_in_time = duration;
|
||||
|
||||
effect.type = FF_PERIODIC;
|
||||
effect.id = -1;
|
||||
effect.u.periodic.waveform = FF_SINE;
|
||||
effect.u.periodic.period = 10;
|
||||
effect.u.periodic.magnitude = magnitude;
|
||||
effect.u.periodic.offset = 0;
|
||||
effect.u.periodic.phase = 0;
|
||||
effect.direction = 0x4000;
|
||||
effect.u.periodic.envelope.attack_length = fade_in_time;
|
||||
effect.u.periodic.envelope.attack_level = fade_in_level;;
|
||||
effect.u.periodic.envelope.fade_length = 0;
|
||||
effect.u.periodic.envelope.fade_level = 0;
|
||||
effect.trigger.button = 0;
|
||||
effect.trigger.interval = 0;
|
||||
effect.replay.length = duration;
|
||||
effect.replay.delay = 200;
|
||||
|
||||
g_debug("Uploading periodic effect (%d)", self->fd);
|
||||
if (ioctl(self->fd, EVIOCSFF, &effect) == -1) {
|
||||
g_warning ("Failed to upload periodic vibra effect: %s", g_strerror (errno));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
g_debug("Playing periodic vibra effect id %d", effect.id);
|
||||
event.type = EV_FF;
|
||||
self->id = event.code = effect.id;
|
||||
event.value = 1;
|
||||
|
||||
if (write (self->fd, (const void*) &event, sizeof (event)) < 0) {
|
||||
g_warning ("Failed to play rumbling effect.");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
fbd_dev_vibra_remove_effect (FbdDevVibra *self)
|
||||
{
|
||||
g_return_val_if_fail (FBD_IS_DEV_VIBRA (self), FALSE);
|
||||
|
||||
g_debug("Erasing vibra effect (%d)", self->fd);
|
||||
if (ioctl(self->fd, EVIOCRMFF, self->id) == -1) {
|
||||
g_warning ("Failed to erase vibra effect with id %d: %s", self->id, strerror(errno));
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
fbd_dev_vibra_stop(FbdDevVibra *self)
|
||||
{
|
||||
struct input_event stop = { 0 };
|
||||
|
||||
g_return_val_if_fail (FBD_IS_DEV_VIBRA (self), FALSE);
|
||||
|
||||
stop.type = EV_FF;
|
||||
stop.code = self->id;
|
||||
stop.value = 0;
|
||||
|
||||
if (write(self->fd, (const void*) &stop, sizeof(stop)) < 0) {
|
||||
g_warning ("Failed to stop vibra effect with id %d: %s", self->id, strerror(errno));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return fbd_dev_vibra_remove_effect (self);
|
||||
}
|
||||
|
||||
GUdevDevice *
|
||||
fbd_dev_vibra_get_device(FbdDevVibra *self)
|
||||
{
|
||||
g_return_val_if_fail (FBD_IS_DEV_VIBRA (self), FALSE);
|
||||
|
||||
return self->device;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Purism SPC
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0+
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <glib-object.h>
|
||||
#include <gudev/gudev.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define FBD_TYPE_DEV_VIBRA (fbd_dev_vibra_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (FbdDevVibra, fbd_dev_vibra, FBD, DEV_VIBRA, GObject);
|
||||
|
||||
FbdDevVibra *fbd_dev_vibra_new (GUdevDevice *device, GError **error);
|
||||
gboolean fbd_dev_vibra_rumble (FbdDevVibra *device, guint duration, gboolean upload);
|
||||
gboolean fbd_dev_vibra_periodic (FbdDevVibra *self, guint duration, guint magnitude,
|
||||
guint fade_in_level, guint fade_in_time);
|
||||
gboolean fbd_dev_vibra_stop (FbdDevVibra *self);
|
||||
gboolean fbd_dev_vibra_remove_effect (FbdDevVibra *self);
|
||||
GUdevDevice *fbd_dev_vibra_get_device(FbdDevVibra *self);
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
+449
@@ -0,0 +1,449 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Purism SPC
|
||||
* SPDX-License-Identifier: GPL-3.0+
|
||||
* Author: Guido Günther <agx@sigxcpu.org>
|
||||
*/
|
||||
|
||||
#define G_LOG_DOMAIN "fbd-feedback-event"
|
||||
|
||||
#include "lfb-names.h"
|
||||
#include "fbd.h"
|
||||
#include "fbd-enums.h"
|
||||
#include "fbd-event.h"
|
||||
|
||||
enum {
|
||||
SIGNAL_FEEDBACKS_ENDED,
|
||||
N_SIGNALS
|
||||
};
|
||||
static guint signals[N_SIGNALS];
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_ID,
|
||||
PROP_APP_ID,
|
||||
PROP_EVENT,
|
||||
PROP_END_REASON,
|
||||
PROP_FEEDBACKS_ENDED,
|
||||
PROP_TIMEOUT,
|
||||
PROP_LAST_PROP,
|
||||
};
|
||||
static GParamSpec *props[PROP_LAST_PROP];
|
||||
|
||||
typedef struct _FbdEvent {
|
||||
GObject parent;
|
||||
|
||||
guint id;
|
||||
char *app_id;
|
||||
char *event;
|
||||
|
||||
gint timeout;
|
||||
gboolean expired;
|
||||
guint timeout_id;
|
||||
|
||||
gboolean ended;
|
||||
FbdEventEndReason end_reason;
|
||||
|
||||
GSList *feedbacks;
|
||||
} FbdEvent;
|
||||
|
||||
G_DEFINE_TYPE (FbdEvent, fbd_event, G_TYPE_OBJECT);
|
||||
|
||||
static gboolean
|
||||
on_timeout_expired (FbdEvent *self)
|
||||
{
|
||||
g_return_val_if_fail (FBD_IS_EVENT (self), G_SOURCE_REMOVE);
|
||||
|
||||
self->expired = TRUE;
|
||||
self->timeout_id = 0;
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
check_ended (FbdEvent *self)
|
||||
{
|
||||
if (!fbd_event_get_feedbacks_ended (self))
|
||||
return FALSE;
|
||||
|
||||
self->ended = TRUE;
|
||||
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_FEEDBACKS_ENDED]);
|
||||
g_signal_emit (self, signals[SIGNAL_FEEDBACKS_ENDED], 0);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
on_fb_ended (FbdEvent *self, FbdFeedbackBase *fb)
|
||||
{
|
||||
switch (self->timeout) {
|
||||
case FBD_EVENT_TIMEOUT_ONESHOT:
|
||||
check_ended (self);
|
||||
break;
|
||||
case FBD_EVENT_TIMEOUT_LOOP:
|
||||
if (self->end_reason != FBD_EVENT_END_REASON_NATURAL)
|
||||
check_ended (self);
|
||||
else
|
||||
fbd_feedback_run (fb);
|
||||
break;
|
||||
default:
|
||||
if (!self->expired && self->end_reason == FBD_EVENT_END_REASON_NATURAL)
|
||||
fbd_feedback_run (fb);
|
||||
else
|
||||
check_ended (self);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_event_set_property (GObject *object,
|
||||
guint property_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
FbdEvent *self = FBD_EVENT (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_ID:
|
||||
self->id = g_value_get_int (value);
|
||||
break;
|
||||
case PROP_APP_ID:
|
||||
g_free (self->app_id);
|
||||
self->app_id = g_value_dup_string (value);
|
||||
break;
|
||||
case PROP_EVENT:
|
||||
g_free (self->event);
|
||||
self->event = g_value_dup_string (value);
|
||||
break;
|
||||
case PROP_TIMEOUT:
|
||||
self->timeout = g_value_get_int (value);
|
||||
break;
|
||||
case PROP_END_REASON:
|
||||
fbd_event_set_end_reason (self, g_value_get_enum (value));
|
||||
break;
|
||||
case PROP_FEEDBACKS_ENDED:
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_event_get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
FbdEvent *self = FBD_EVENT (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_ID:
|
||||
g_value_set_int (value, self->id);
|
||||
break;
|
||||
case PROP_APP_ID:
|
||||
g_value_set_string (value, self->app_id);
|
||||
break;
|
||||
case PROP_EVENT:
|
||||
g_value_set_string (value, self->event);
|
||||
break;
|
||||
case PROP_TIMEOUT:
|
||||
g_value_set_int (value, self->timeout);
|
||||
break;
|
||||
case PROP_END_REASON:
|
||||
g_value_set_enum (value, fbd_event_get_end_reason (self));
|
||||
break;
|
||||
case PROP_FEEDBACKS_ENDED:
|
||||
g_value_set_boolean (value, self->ended);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_event_dispose (GObject *object)
|
||||
{
|
||||
FbdEvent *self = FBD_EVENT (object);
|
||||
|
||||
g_clear_handle_id (&self->timeout_id, g_source_remove);
|
||||
|
||||
if (self->feedbacks) {
|
||||
/* Feedbacks end themselves when unrefed */
|
||||
g_slist_free_full (self->feedbacks, g_object_unref);
|
||||
self->feedbacks = NULL;
|
||||
}
|
||||
|
||||
G_OBJECT_CLASS (fbd_event_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_event_finalize (GObject *object)
|
||||
{
|
||||
FbdEvent *self = FBD_EVENT (object);
|
||||
|
||||
g_clear_pointer (&self->app_id, g_free);
|
||||
g_clear_pointer (&self->event, g_free);
|
||||
|
||||
G_OBJECT_CLASS (fbd_event_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_event_class_init (FbdEventClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->set_property = fbd_event_set_property;
|
||||
object_class->get_property = fbd_event_get_property;
|
||||
|
||||
object_class->dispose = fbd_event_dispose;
|
||||
object_class->finalize = fbd_event_finalize;
|
||||
|
||||
props[PROP_ID] =
|
||||
g_param_spec_int (
|
||||
"id",
|
||||
"Id",
|
||||
"The event id",
|
||||
0, G_MAXINT, 0,
|
||||
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
props[PROP_APP_ID] =
|
||||
g_param_spec_string (
|
||||
"app-id",
|
||||
"App id",
|
||||
"The application id",
|
||||
NULL,
|
||||
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
props[PROP_EVENT] =
|
||||
g_param_spec_string (
|
||||
"event",
|
||||
"Event",
|
||||
"The event name",
|
||||
NULL,
|
||||
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
props[PROP_TIMEOUT] =
|
||||
g_param_spec_int (
|
||||
"timeout",
|
||||
"Timeout",
|
||||
"Timeout after which feedback for event should end",
|
||||
-1, G_MAXINT, -1,
|
||||
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
props[PROP_FEEDBACKS_ENDED] =
|
||||
g_param_spec_boolean (
|
||||
"feedbacks-ended",
|
||||
"Feedbacks ended",
|
||||
"Whether all feedbacks have ended playing",
|
||||
FALSE,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
|
||||
|
||||
props[PROP_END_REASON] =
|
||||
g_param_spec_enum (
|
||||
"end-reason",
|
||||
"End reason",
|
||||
"The reason why the feedbacks ends/ended",
|
||||
FBD_TYPE_EVENT_END_REASON,
|
||||
FBD_EVENT_END_REASON_NATURAL,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
|
||||
|
||||
g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
|
||||
|
||||
/**
|
||||
* FbdEvent::feedbacks-ended:
|
||||
*
|
||||
* Emitted when all feedbacks associated with this event ended.
|
||||
*/
|
||||
signals[SIGNAL_FEEDBACKS_ENDED] = g_signal_new ("feedbacks-ended",
|
||||
G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_LAST, 0, NULL, NULL,
|
||||
NULL,
|
||||
G_TYPE_NONE,
|
||||
0);
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_event_init (FbdEvent *self)
|
||||
{
|
||||
self->timeout = -1;
|
||||
}
|
||||
|
||||
FbdEvent *
|
||||
fbd_event_new (gint id, const gchar *app_id, const gchar *event, gint timeout)
|
||||
{
|
||||
return FBD_EVENT (g_object_new (FBD_TYPE_EVENT,
|
||||
"id", id,
|
||||
"app_id", app_id,
|
||||
"event", event,
|
||||
"timeout", timeout,
|
||||
NULL));
|
||||
}
|
||||
|
||||
const gchar *
|
||||
fbd_event_get_event (FbdEvent *self)
|
||||
{
|
||||
g_return_val_if_fail (FBD_IS_EVENT (self), NULL);
|
||||
|
||||
return self->event;
|
||||
}
|
||||
|
||||
const gchar *
|
||||
fbd_event_get_app_id (FbdEvent *self)
|
||||
{
|
||||
g_return_val_if_fail (FBD_IS_EVENT (self), NULL);
|
||||
|
||||
return self->app_id;
|
||||
}
|
||||
|
||||
guint
|
||||
fbd_event_get_id (FbdEvent *self)
|
||||
{
|
||||
g_return_val_if_fail (FBD_IS_EVENT (self), 0);
|
||||
|
||||
return self->id;
|
||||
}
|
||||
|
||||
gint
|
||||
fbd_event_get_timeout (FbdEvent *self)
|
||||
{
|
||||
g_return_val_if_fail (FBD_IS_EVENT (self), -1);
|
||||
|
||||
return self->timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* fbd_event_add_feedback:
|
||||
* @self: The event that gets a feedback added
|
||||
* @feedback: (transfer-none): The feedback to add
|
||||
*
|
||||
* Add a feedback to the list of feedbacks triggered by event
|
||||
*/
|
||||
void
|
||||
fbd_event_add_feedback (FbdEvent *self, FbdFeedbackBase *feedback)
|
||||
{
|
||||
self->feedbacks = g_slist_prepend (self->feedbacks, g_object_ref(feedback));
|
||||
g_object_set_data (G_OBJECT (feedback), "event-id", GUINT_TO_POINTER(self->id));
|
||||
g_signal_connect_object (feedback,
|
||||
"ended",
|
||||
(GCallback) on_fb_ended,
|
||||
self,
|
||||
G_CONNECT_SWAPPED);
|
||||
}
|
||||
|
||||
GSList *
|
||||
fbd_event_get_feedbacks (FbdEvent *self)
|
||||
{
|
||||
return self->feedbacks;
|
||||
}
|
||||
|
||||
gint
|
||||
fbd_event_remove_feedback (FbdEvent *self, FbdFeedbackBase *feedback)
|
||||
{
|
||||
g_return_val_if_fail (FBD_IS_EVENT (self), 0);
|
||||
|
||||
self->feedbacks = g_slist_remove (self->feedbacks, feedback);
|
||||
return g_slist_length (self->feedbacks);
|
||||
}
|
||||
|
||||
/**
|
||||
* fbd_event_run_feedbacks:
|
||||
* @self: The Event
|
||||
*
|
||||
* Run all feedbacks associated for an event.
|
||||
*/
|
||||
void
|
||||
fbd_event_run_feedbacks (FbdEvent *self)
|
||||
{
|
||||
GSList *l;
|
||||
|
||||
g_return_if_fail (FBD_IS_EVENT (self));
|
||||
|
||||
g_debug ("Running %d feedbacks for event %d", g_slist_length (self->feedbacks), self->id);
|
||||
|
||||
if (!self->feedbacks)
|
||||
return;
|
||||
|
||||
if (self->timeout > 0) {
|
||||
self->timeout_id = g_timeout_add_seconds (self->timeout,
|
||||
(GSourceFunc)on_timeout_expired,
|
||||
self);
|
||||
g_source_set_name_by_id (self->timeout_id, "event timeout source");
|
||||
}
|
||||
|
||||
for (l = self->feedbacks; l; l = l->next) {
|
||||
FbdFeedbackBase *fb = FBD_FEEDBACK_BASE (l->data);
|
||||
fbd_feedback_run (fb);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* fbd_event_end_feedbacks:
|
||||
* @self: The Event
|
||||
*
|
||||
* End all running feedbacks as early as possible.
|
||||
*/
|
||||
void
|
||||
fbd_event_end_feedbacks (FbdEvent *self)
|
||||
{
|
||||
g_return_if_fail (FBD_IS_EVENT (self));
|
||||
|
||||
fbd_event_set_end_reason (self, FBD_EVENT_END_REASON_EXPLICIT);
|
||||
g_debug ("Ending %d feedbacks for event %d", g_slist_length (self->feedbacks), self->id);
|
||||
g_slist_foreach (self->feedbacks, (GFunc)fbd_feedback_end, NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* fbd_event_get_feedbacks_ended:
|
||||
* @self: The Event
|
||||
*
|
||||
* Whether all feedbacks have finished running.
|
||||
*
|
||||
* Returns: %TRUE if all feedbacks have finished, otherwise %FALSE.
|
||||
*/
|
||||
gboolean
|
||||
fbd_event_get_feedbacks_ended (FbdEvent *self)
|
||||
{
|
||||
GSList *l;
|
||||
|
||||
g_return_val_if_fail (FBD_IS_EVENT (self), FALSE);
|
||||
|
||||
if (!self->feedbacks)
|
||||
return TRUE;
|
||||
|
||||
for (l = self->feedbacks; l ; l = l->next) {
|
||||
if (!fbd_feedback_get_ended (FBD_FEEDBACK_BASE (l->data)))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* fbd_event_set_end_reason:
|
||||
* @self: The Event
|
||||
* @reason: The reason why feedback for the event ended
|
||||
*
|
||||
* Sets the reason why feedback for the event ends/has ended.
|
||||
*/
|
||||
void
|
||||
fbd_event_set_end_reason (FbdEvent *self, FbdEventEndReason reason)
|
||||
{
|
||||
g_return_if_fail (FBD_IS_EVENT (self));
|
||||
|
||||
if (self->end_reason == reason)
|
||||
return;
|
||||
self->end_reason = reason;
|
||||
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_END_REASON]);
|
||||
}
|
||||
|
||||
/**
|
||||
* fbd_event_get_end_reason:
|
||||
* @self: The Event
|
||||
*
|
||||
* Returns: The reason why feedback for the event ended.
|
||||
*/
|
||||
FbdEventEndReason
|
||||
fbd_event_get_end_reason (FbdEvent *self)
|
||||
{
|
||||
g_return_val_if_fail (FBD_IS_EVENT (self), FBD_EVENT_END_REASON_NATURAL);
|
||||
|
||||
return self->end_reason;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Purism SPC
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0+
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "fbd-feedback-base.h"
|
||||
#include "fbd-feedback-manager.h"
|
||||
|
||||
#include <glib-object.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef enum _FbdEventEndReason {
|
||||
/* No feedback in current theme for this event */
|
||||
FBD_EVENT_END_REASON_NOT_FOUND = -1,
|
||||
/* all feedbacks finished playing their natural length */
|
||||
FBD_EVENT_END_REASON_NATURAL = 0,
|
||||
/* The timer expired */
|
||||
FBD_EVENT_END_REASON_EXPIRED = 1,
|
||||
/* Application wanted to end feedbacks explicitly */
|
||||
FBD_EVENT_END_REASON_EXPLICIT = 2,
|
||||
} FbdEventEndReason;
|
||||
|
||||
typedef enum _FbdEventTimeout {
|
||||
/* Run each feedback once */
|
||||
FBD_EVENT_TIMEOUT_ONESHOT = -1,
|
||||
FBD_EVENT_TIMEOUT_LOOP = 0,
|
||||
} FbdEventTimeout;
|
||||
|
||||
#define FBD_TYPE_EVENT (fbd_event_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (FbdEvent, fbd_event, FBD, EVENT, GObject);
|
||||
|
||||
FbdEvent *fbd_event_new (gint id, const gchar *app_id, const gchar *event, gint timeout);
|
||||
const gchar *fbd_event_get_event (FbdEvent *event);
|
||||
const gchar *fbd_event_get_app_id (FbdEvent *event);
|
||||
guint fbd_event_get_id (FbdEvent *event);
|
||||
gint fbd_event_get_timeout (FbdEvent *self);
|
||||
void fbd_event_set_end_reason (FbdEvent *self, FbdEventEndReason reason);
|
||||
FbdEventEndReason fbd_event_get_end_reason (FbdEvent *self);
|
||||
GSList * fbd_event_get_feedbacks (FbdEvent *self);
|
||||
void fbd_event_add_feedback (FbdEvent *self,
|
||||
FbdFeedbackBase *feedback);
|
||||
gint fbd_event_remove_feedback (FbdEvent *self,
|
||||
FbdFeedbackBase *feedback);
|
||||
void fbd_event_run_feedbacks (FbdEvent *self);
|
||||
void fbd_event_end_feedbacks (FbdEvent *self);
|
||||
gboolean fbd_event_get_feedbacks_ended (FbdEvent *self);
|
||||
|
||||
G_END_DECLS
|
||||
@@ -0,0 +1,229 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Purism SPC
|
||||
* SPDX-License-Identifier: GPL-3.0+
|
||||
* Author: Guido Günther <agx@sigxcpu.org>
|
||||
*/
|
||||
|
||||
#define G_LOG_DOMAIN "fbd-feedback-base"
|
||||
|
||||
#include "fbd-feedback-base.h"
|
||||
|
||||
/**
|
||||
* SECTION:fbd-feedback-base
|
||||
* @short_description: Base class for different feedback types
|
||||
* @Title: FbdFeedbackManager
|
||||
*
|
||||
* You usually don't want to create objects of this type. It just
|
||||
* serves as a base class for other feedback types.
|
||||
*/
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_EVENT_NAME,
|
||||
PROP_LAST_PROP,
|
||||
};
|
||||
static GParamSpec *props[PROP_LAST_PROP];
|
||||
|
||||
enum {
|
||||
SIGNAL_ENDED,
|
||||
N_SIGNALS
|
||||
};
|
||||
static guint signals[N_SIGNALS];
|
||||
|
||||
typedef struct _FbdFeedbackBasePrivate {
|
||||
gchar *event_name;
|
||||
gboolean ended;
|
||||
} FbdFeedbackBasePrivate;
|
||||
|
||||
G_DEFINE_TYPE_WITH_PRIVATE (FbdFeedbackBase, fbd_feedback_base, G_TYPE_OBJECT);
|
||||
|
||||
static void
|
||||
fbd_feedback_base_set_property (GObject *object,
|
||||
guint property_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
FbdFeedbackBase *self = FBD_FEEDBACK_BASE (object);
|
||||
FbdFeedbackBasePrivate *priv = fbd_feedback_base_get_instance_private (self);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_EVENT_NAME:
|
||||
g_free (priv->event_name);
|
||||
priv->event_name = g_value_dup_string (value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
fbd_feedback_base_get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
FbdFeedbackBase *self = FBD_FEEDBACK_BASE (object);
|
||||
FbdFeedbackBasePrivate *priv = fbd_feedback_base_get_instance_private (self);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_EVENT_NAME:
|
||||
g_value_set_string (value, priv->event_name);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_feedback_base_dispose (GObject *object)
|
||||
{
|
||||
FbdFeedbackBase *self = FBD_FEEDBACK_BASE (object);
|
||||
|
||||
/* end feedback if running */
|
||||
if (!fbd_feedback_get_ended (self))
|
||||
fbd_feedback_end (self);
|
||||
|
||||
G_OBJECT_CLASS (fbd_feedback_base_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_feedback_base_finalize (GObject *object)
|
||||
{
|
||||
FbdFeedbackBase *self = FBD_FEEDBACK_BASE (object);
|
||||
FbdFeedbackBasePrivate *priv = fbd_feedback_base_get_instance_private (self);
|
||||
|
||||
g_clear_pointer (&priv->event_name, g_free);
|
||||
|
||||
G_OBJECT_CLASS (fbd_feedback_base_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_feedback_base_class_init (FbdFeedbackBaseClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->set_property = fbd_feedback_base_set_property;
|
||||
object_class->get_property = fbd_feedback_base_get_property;
|
||||
|
||||
object_class->dispose = fbd_feedback_base_dispose;
|
||||
object_class->finalize = fbd_feedback_base_finalize;
|
||||
|
||||
props[PROP_EVENT_NAME] =
|
||||
g_param_spec_string (
|
||||
"event-name",
|
||||
"Event Name",
|
||||
"The event this feedback is associated with",
|
||||
NULL,
|
||||
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
|
||||
|
||||
/**
|
||||
* FbdFeedbackBase::ended:
|
||||
*
|
||||
* Emitted when the feedback has ended
|
||||
*/
|
||||
signals[SIGNAL_ENDED] = g_signal_new ("ended",
|
||||
G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_LAST, 0, NULL, NULL,
|
||||
NULL,
|
||||
G_TYPE_NONE,
|
||||
0);
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_feedback_base_init (FbdFeedbackBase *self)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* fbd_feedback_get_event_name:
|
||||
* @self: The feedback
|
||||
*
|
||||
* Returns: the name from the event naming spec this feedback is associated with.
|
||||
*/
|
||||
const gchar *
|
||||
fbd_feedback_get_event_name (FbdFeedbackBase *self)
|
||||
{
|
||||
FbdFeedbackBasePrivate *priv;
|
||||
|
||||
g_return_val_if_fail (FBD_IS_FEEDBACK_BASE (self), NULL);
|
||||
priv = fbd_feedback_base_get_instance_private (self);
|
||||
|
||||
return priv->event_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* fbd_feedback_run:
|
||||
* @self: The feedback to run
|
||||
*
|
||||
* Emit the feedback.
|
||||
*/
|
||||
void
|
||||
fbd_feedback_run (FbdFeedbackBase *self)
|
||||
{
|
||||
FbdFeedbackBaseClass *klass;
|
||||
FbdFeedbackBasePrivate *priv;
|
||||
|
||||
g_return_if_fail (FBD_IS_FEEDBACK_BASE (self));
|
||||
priv = fbd_feedback_base_get_instance_private (self);
|
||||
|
||||
priv->ended = FALSE;
|
||||
klass = FBD_FEEDBACK_BASE_GET_CLASS (self);
|
||||
g_return_if_fail (klass->run);
|
||||
klass->run (self);
|
||||
}
|
||||
|
||||
/**
|
||||
* fbd_feedback_end:
|
||||
* @self: The feedback to end
|
||||
*
|
||||
* End the feedback immediately.
|
||||
*/
|
||||
void
|
||||
fbd_feedback_end (FbdFeedbackBase *self)
|
||||
{
|
||||
FbdFeedbackBaseClass *klass;
|
||||
|
||||
g_return_if_fail (FBD_IS_FEEDBACK_BASE (self));
|
||||
|
||||
klass = FBD_FEEDBACK_BASE_GET_CLASS (self);
|
||||
g_return_if_fail (klass->end);
|
||||
klass->end (self);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* fbd_feedback_get_ended:
|
||||
* @self: The feedback
|
||||
*
|
||||
* Whether the feedback is ended.
|
||||
*
|
||||
* Returns: %TRUE if feedback has ended, otherwise %FALSE.
|
||||
*/
|
||||
gboolean
|
||||
fbd_feedback_get_ended (FbdFeedbackBase *self)
|
||||
{
|
||||
FbdFeedbackBasePrivate *priv = fbd_feedback_base_get_instance_private (self);
|
||||
|
||||
return priv->ended;
|
||||
}
|
||||
|
||||
/**
|
||||
* fbd_feedback_base_done:
|
||||
* @self: The feedback
|
||||
*
|
||||
* Invoked by a derived classes to notify that it's done emitting feedback,
|
||||
* e.g. when the vibra motor stopped or a sound finished playing.
|
||||
*/
|
||||
void
|
||||
fbd_feedback_base_done (FbdFeedbackBase *self)
|
||||
{
|
||||
FbdFeedbackBasePrivate *priv = fbd_feedback_base_get_instance_private (self);
|
||||
|
||||
priv->ended = TRUE;
|
||||
g_signal_emit (self, signals[SIGNAL_ENDED], 0);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Purism SPC
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0+
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <glib-object.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define FBD_TYPE_FEEDBACK_BASE (fbd_feedback_base_get_type())
|
||||
|
||||
G_DECLARE_DERIVABLE_TYPE (FbdFeedbackBase, fbd_feedback_base, FBD, FEEDBACK_BASE, GObject);
|
||||
|
||||
struct _FbdFeedbackBaseClass
|
||||
{
|
||||
GObjectClass parent_class;
|
||||
|
||||
void (*run) (FbdFeedbackBase *self);
|
||||
void (*end) (FbdFeedbackBase *self);
|
||||
};
|
||||
|
||||
|
||||
const gchar *fbd_feedback_get_event_name (FbdFeedbackBase *self);
|
||||
void fbd_feedback_run (FbdFeedbackBase *self);
|
||||
void fbd_feedback_end (FbdFeedbackBase *self);
|
||||
gboolean fbd_feedback_get_ended (FbdFeedbackBase *self);
|
||||
void fbd_feedback_base_done (FbdFeedbackBase *self);
|
||||
|
||||
G_END_DECLS
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Purism SPC
|
||||
* SPDX-License-Identifier: GPL-3.0+
|
||||
* Author: Guido Günther <agx@sigxcpu.org>
|
||||
*/
|
||||
|
||||
#define G_LOG_DOMAIN "fbd-feedback-dummy"
|
||||
|
||||
#include "fbd-enums.h"
|
||||
#include "fbd-feedback-dummy.h"
|
||||
#include "fbd-feedback-manager.h"
|
||||
|
||||
/**
|
||||
* SECTION:fbd-feedback-dummy
|
||||
* @short_description: Describes a dummy feedback event that does nothing
|
||||
* @Title: FbdFeedbackDummy
|
||||
*/
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_DURATION,
|
||||
PROP_LAST_PROP,
|
||||
};
|
||||
static GParamSpec *props[PROP_LAST_PROP];
|
||||
|
||||
typedef struct _FbdFeedbackDummy {
|
||||
FbdFeedbackBase parent;
|
||||
|
||||
guint duration;
|
||||
guint timer_id;
|
||||
} FbdFeedbackDummy;
|
||||
|
||||
G_DEFINE_TYPE (FbdFeedbackDummy, fbd_feedback_dummy, FBD_TYPE_FEEDBACK_BASE);
|
||||
|
||||
static void
|
||||
fbd_feedback_dummy_set_property (GObject *object,
|
||||
guint property_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
FbdFeedbackDummy *self = FBD_FEEDBACK_DUMMY (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_DURATION:
|
||||
self->duration = g_value_get_uint (value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_feedback_dummy_get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
FbdFeedbackDummy *self = FBD_FEEDBACK_DUMMY (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_DURATION:
|
||||
g_value_set_uint (value, self->duration);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
on_timeout_expired (FbdFeedbackDummy *self)
|
||||
{
|
||||
fbd_feedback_base_done (FBD_FEEDBACK_BASE(self));
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_feedback_dummy_run (FbdFeedbackBase *base)
|
||||
{
|
||||
FbdFeedbackDummy *self = FBD_FEEDBACK_DUMMY (base);
|
||||
|
||||
if (self->duration) {
|
||||
self->timer_id = g_timeout_add (self->duration,
|
||||
(GSourceFunc)on_timeout_expired,
|
||||
self);
|
||||
g_source_set_name_by_id (self->timer_id, "feedback-dummy-timer");
|
||||
} else {
|
||||
fbd_feedback_base_done (base);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_feedback_dummy_end (FbdFeedbackBase *base)
|
||||
{
|
||||
FbdFeedbackDummy *self = FBD_FEEDBACK_DUMMY (base);
|
||||
|
||||
g_clear_handle_id(&self->timer_id, g_source_remove);
|
||||
fbd_feedback_base_done (base);
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_feedback_dummy_class_init (FbdFeedbackDummyClass *klass)
|
||||
{
|
||||
FbdFeedbackBaseClass *base_class = FBD_FEEDBACK_BASE_CLASS (klass);
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->set_property = fbd_feedback_dummy_set_property;
|
||||
object_class->get_property = fbd_feedback_dummy_get_property;
|
||||
|
||||
base_class->run = fbd_feedback_dummy_run;
|
||||
base_class->end = fbd_feedback_dummy_end;
|
||||
|
||||
props[PROP_DURATION] =
|
||||
g_param_spec_uint (
|
||||
"duration",
|
||||
"Duration",
|
||||
"Dummy event duration in msecs",
|
||||
0, G_MAXUINT, 0,
|
||||
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_feedback_dummy_init (FbdFeedbackDummy *self)
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Purism SPC
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0+
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "fbd-feedback-base.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define FBD_TYPE_FEEDBACK_DUMMY (fbd_feedback_dummy_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (FbdFeedbackDummy, fbd_feedback_dummy, FBD, FEEDBACK_DUMMY, FbdFeedbackBase);
|
||||
|
||||
G_END_DECLS
|
||||
@@ -0,0 +1,394 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Purism SPC
|
||||
* SPDX-License-Identifier: GPL-3.0+
|
||||
* Author: Guido Günther <agx@sigxcpu.org>
|
||||
*/
|
||||
|
||||
#define G_LOG_DOMAIN "fbd-feedback-manager"
|
||||
|
||||
#include "lfb-names.h"
|
||||
#include "fbd.h"
|
||||
#include "fbd-dev-vibra.h"
|
||||
#include "fbd-event.h"
|
||||
#include "fbd-feedback-vibra.h"
|
||||
#include "fbd-feedback-manager.h"
|
||||
#include "fbd-feedback-theme.h"
|
||||
|
||||
#include <gio/gio.h>
|
||||
#include <glib-unix.h>
|
||||
#include <gudev/gudev.h>
|
||||
|
||||
#define FEEDBACKD_SCHEMA_ID "org.sigxcpu.feedbackd"
|
||||
#define FEEDBACKD_KEY_PROFILE "profile"
|
||||
|
||||
/**
|
||||
* SECTION:fbd-feedback-manager
|
||||
* @short_description: The manager processing incoming events
|
||||
* @Title: FbdFeedbackManager
|
||||
*
|
||||
* The #FbdFeedbackManager listens for DBus messages and triggers feedbacks
|
||||
* based on the incoming events.
|
||||
*/
|
||||
|
||||
typedef struct _FbdFeedbackManager {
|
||||
LfbGdbusFeedbackSkeleton parent;
|
||||
|
||||
GSettings *settings;
|
||||
FbdFeedbackProfileLevel level;
|
||||
FbdFeedbackTheme *theme;
|
||||
guint next_id;
|
||||
|
||||
GHashTable *events;
|
||||
|
||||
/* Hardware interaction */
|
||||
GUdevClient *client;
|
||||
FbdDevVibra *vibra;
|
||||
FbdDevSound *sound;
|
||||
} FbdFeedbackManager;
|
||||
|
||||
static void fbd_feedback_manager_feedback_iface_init (LfbGdbusFeedbackIface *iface);
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE (FbdFeedbackManager,
|
||||
fbd_feedback_manager,
|
||||
LFB_GDBUS_TYPE_FEEDBACK_SKELETON,
|
||||
G_IMPLEMENT_INTERFACE (
|
||||
LFB_GDBUS_TYPE_FEEDBACK,
|
||||
fbd_feedback_manager_feedback_iface_init));
|
||||
|
||||
static void
|
||||
device_changes (FbdFeedbackManager *self, gchar *action, GUdevDevice *device,
|
||||
GUdevClient *client)
|
||||
{
|
||||
g_debug ("Device changes: action = %s, device = %s",
|
||||
action, g_udev_device_get_sysfs_path (device));
|
||||
|
||||
if (g_strcmp0 (action, "remove") == 0 && self->vibra) {
|
||||
GUdevDevice *dev = fbd_dev_vibra_get_device (self->vibra);
|
||||
|
||||
if (g_strcmp0 (g_udev_device_get_sysfs_path(dev),
|
||||
g_udev_device_get_sysfs_path (device)) == 0) {
|
||||
g_debug ("Vibra device %s got remove", g_udev_device_get_sysfs_path (dev));
|
||||
g_clear_object (&self->vibra);
|
||||
}
|
||||
} else if (g_strcmp0 (action, "add") == 0 || !self->vibra) {
|
||||
if (!g_strcmp0 (g_udev_device_get_property (device, "FEEDBACKD_TYPE"), "vibra")) {
|
||||
g_autoptr (GError) err = NULL;
|
||||
|
||||
g_debug ("Found hotplugged vibra device at %s", g_udev_device_get_sysfs_path (device));
|
||||
self->vibra = fbd_dev_vibra_new (device, &err);
|
||||
if (!self->vibra) {
|
||||
g_warning ("Failed to init vibra device: %s", err->message);
|
||||
g_clear_error (&err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
init_devices (FbdFeedbackManager *self)
|
||||
{
|
||||
GList *devices, *l;
|
||||
g_autoptr(GError) err = NULL;
|
||||
|
||||
devices = g_udev_client_query_by_subsystem (self->client, "input");
|
||||
|
||||
for (l = devices; l != NULL; l = l->next) {
|
||||
GUdevDevice *dev = l->data;
|
||||
|
||||
if (!g_strcmp0 (g_udev_device_get_property (dev, "FEEDBACKD_TYPE"), "vibra")) {
|
||||
g_debug ("Found vibra device");
|
||||
self->vibra = fbd_dev_vibra_new (dev, &err);
|
||||
if (!self->vibra) {
|
||||
g_warning ("Failed to init vibra device: %s", err->message);
|
||||
g_clear_error (&err);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!self->vibra)
|
||||
g_debug ("No vibra capable device found");
|
||||
|
||||
self->sound = fbd_dev_sound_new (&err);
|
||||
if (!self->sound) {
|
||||
g_warning ("Failed to init sound device: %s", err->message);
|
||||
g_clear_error (&err);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
on_event_feedbacks_ended (FbdFeedbackManager *self, FbdEvent *event)
|
||||
{
|
||||
guint event_id;
|
||||
|
||||
g_return_if_fail (FBD_IS_FEEDBACK_MANAGER (self));
|
||||
g_return_if_fail (FBD_IS_EVENT (event));
|
||||
|
||||
event_id = fbd_event_get_id (event);
|
||||
event = g_hash_table_lookup (self->events, GUINT_TO_POINTER (event_id));
|
||||
if (!event) {
|
||||
g_warning ("Feedback ended for unknown event %d", event_id);
|
||||
return;
|
||||
}
|
||||
|
||||
g_return_if_fail (fbd_event_get_feedbacks_ended (event));
|
||||
|
||||
lfb_gdbus_feedback_emit_feedback_ended
|
||||
(LFB_GDBUS_FEEDBACK(self), event_id,
|
||||
fbd_event_get_end_reason (event));
|
||||
|
||||
g_debug ("All feedbacks for event %d finished", event_id);
|
||||
g_hash_table_remove (self->events, GUINT_TO_POINTER (event_id));
|
||||
}
|
||||
|
||||
static void
|
||||
on_profile_changed (FbdFeedbackManager *self, GParamSpec *psepc, gpointer unused)
|
||||
{
|
||||
const gchar *pname;
|
||||
|
||||
g_return_if_fail (FBD_IS_FEEDBACK_MANAGER (self));
|
||||
|
||||
pname = lfb_gdbus_feedback_get_profile (LFB_GDBUS_FEEDBACK (self));
|
||||
|
||||
if (!fbd_feedback_manager_set_profile (self, pname))
|
||||
g_warning ("Invalid profile '%s'", pname);
|
||||
|
||||
/* TODO: end running feedbacks that aren't allowed in new profile immediately */
|
||||
}
|
||||
|
||||
static void
|
||||
on_feedbackd_setting_changed (FbdFeedbackManager *self,
|
||||
const gchar *key,
|
||||
GSettings *settings)
|
||||
{
|
||||
g_autofree gchar *profile = NULL;
|
||||
|
||||
g_return_if_fail (FBD_IS_FEEDBACK_MANAGER (self));
|
||||
g_return_if_fail (G_IS_SETTINGS (settings));
|
||||
g_return_if_fail (!g_strcmp0 (key, FEEDBACKD_KEY_PROFILE));
|
||||
|
||||
profile = g_settings_get_string (settings, key);
|
||||
fbd_feedback_manager_set_profile (self, profile);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fbd_feedback_manager_handle_trigger_feedback (LfbGdbusFeedback *object,
|
||||
GDBusMethodInvocation *invocation,
|
||||
const gchar *arg_app_id,
|
||||
const gchar *arg_event,
|
||||
GVariant *arg_hints,
|
||||
gint arg_timeout)
|
||||
{
|
||||
FbdFeedbackManager *self;
|
||||
FbdEvent *event;
|
||||
GSList *feedbacks, *l;
|
||||
gint event_id;
|
||||
|
||||
g_debug ("Event '%s' for '%s'", arg_event, arg_app_id);
|
||||
|
||||
g_return_val_if_fail (FBD_IS_FEEDBACK_MANAGER (object), FALSE);
|
||||
g_return_val_if_fail (arg_app_id, FALSE);
|
||||
g_return_val_if_fail (arg_event, FALSE);
|
||||
|
||||
self = FBD_FEEDBACK_MANAGER (object);
|
||||
if (!strlen (arg_app_id)) {
|
||||
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
|
||||
G_DBUS_ERROR_INVALID_ARGS,
|
||||
"Invalid app id %s", arg_app_id);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (!strlen (arg_event)) {
|
||||
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
|
||||
G_DBUS_ERROR_INVALID_ARGS,
|
||||
"Invalid event %s", arg_event);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (arg_timeout < -1)
|
||||
arg_timeout = -1;
|
||||
|
||||
event_id = self->next_id++;
|
||||
|
||||
event = fbd_event_new (event_id, arg_app_id, arg_event, arg_timeout);
|
||||
g_hash_table_insert (self->events, GUINT_TO_POINTER (event_id), event);
|
||||
|
||||
feedbacks = fbd_feedback_theme_lookup_feedback (self->theme, self->level, event);
|
||||
|
||||
if (feedbacks) {
|
||||
for (l = feedbacks; l; l = l->next) {
|
||||
FbdFeedbackBase *fb = l->data;
|
||||
fbd_event_add_feedback (event, fb);
|
||||
g_object_unref (fb);
|
||||
}
|
||||
g_slist_free (feedbacks);
|
||||
|
||||
g_signal_connect_object (event, "feedbacks-ended",
|
||||
(GCallback) on_event_feedbacks_ended,
|
||||
self,
|
||||
G_CONNECT_SWAPPED);
|
||||
|
||||
fbd_event_run_feedbacks (event);
|
||||
} else {
|
||||
/* No feedbacks found, so we're done */
|
||||
lfb_gdbus_feedback_emit_feedback_ended
|
||||
(LFB_GDBUS_FEEDBACK(self), event_id,
|
||||
FBD_EVENT_END_REASON_NOT_FOUND);
|
||||
}
|
||||
|
||||
lfb_gdbus_feedback_complete_trigger_feedback (object, invocation, event_id);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fbd_feedback_manager_handle_end_feedback (LfbGdbusFeedback *object,
|
||||
GDBusMethodInvocation *invocation,
|
||||
guint event_id)
|
||||
{
|
||||
FbdFeedbackManager *self;
|
||||
FbdEvent *event;
|
||||
|
||||
g_debug ("Ending feedback for event '%d'", event_id);
|
||||
|
||||
g_return_val_if_fail (FBD_IS_FEEDBACK_MANAGER (object), FALSE);
|
||||
g_return_val_if_fail (event_id, FALSE);
|
||||
|
||||
self = FBD_FEEDBACK_MANAGER (object);
|
||||
|
||||
event = g_hash_table_lookup (self->events, GUINT_TO_POINTER (event_id));
|
||||
if (event) {
|
||||
/* The last feedback ending will trigger event disposal via
|
||||
`on_fb_ended` */
|
||||
fbd_event_end_feedbacks (event);
|
||||
} else {
|
||||
g_warning ("Tried to end non-existing event %d", event_id);
|
||||
}
|
||||
|
||||
lfb_gdbus_feedback_complete_end_feedback (object, invocation);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_feedback_manager_constructed (GObject *object)
|
||||
{
|
||||
g_autoptr (GError) err = NULL;
|
||||
FbdFeedbackManager *self = FBD_FEEDBACK_MANAGER (object);
|
||||
const gchar *themefile;
|
||||
|
||||
G_OBJECT_CLASS (fbd_feedback_manager_parent_class)->constructed (object);
|
||||
|
||||
themefile = g_getenv ("FEEDBACK_THEME");
|
||||
if (!themefile)
|
||||
themefile = FEEDBACKD_THEME_DIR "/default.json";
|
||||
|
||||
self->theme = fbd_feedback_theme_new_from_file (themefile, &err);
|
||||
if (!self->theme) {
|
||||
/* No point to carry on */
|
||||
g_error ("Failed to load default theme: %s", err->message);
|
||||
}
|
||||
|
||||
g_signal_connect (self, "notify::profile", (GCallback)on_profile_changed, NULL);
|
||||
lfb_gdbus_feedback_set_profile (LFB_GDBUS_FEEDBACK (self),
|
||||
fbd_feedback_profile_level_to_string(self->level));
|
||||
|
||||
self->settings = g_settings_new (FEEDBACKD_SCHEMA_ID);
|
||||
g_signal_connect_swapped (self->settings, "changed::" FEEDBACKD_KEY_PROFILE,
|
||||
G_CALLBACK (on_feedbackd_setting_changed), self);
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_feedback_manager_dispose (GObject *object)
|
||||
{
|
||||
FbdFeedbackManager *self = FBD_FEEDBACK_MANAGER (object);
|
||||
|
||||
g_clear_object (&self->settings);
|
||||
g_clear_object (&self->theme);
|
||||
g_clear_object (&self->vibra);
|
||||
g_clear_object (&self->client);
|
||||
g_clear_pointer (&self->events, g_hash_table_destroy);
|
||||
|
||||
G_OBJECT_CLASS (fbd_feedback_manager_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_feedback_manager_feedback_iface_init (LfbGdbusFeedbackIface *iface)
|
||||
{
|
||||
iface->handle_trigger_feedback = fbd_feedback_manager_handle_trigger_feedback;
|
||||
iface->handle_end_feedback = fbd_feedback_manager_handle_end_feedback;
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_feedback_manager_class_init (FbdFeedbackManagerClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->constructed = fbd_feedback_manager_constructed;
|
||||
object_class->dispose = fbd_feedback_manager_dispose;
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_feedback_manager_init (FbdFeedbackManager *self)
|
||||
{
|
||||
const gchar * const subsystems[] = { "input", NULL };
|
||||
|
||||
self->next_id = 1;
|
||||
self->level = FBD_FEEDBACK_PROFILE_LEVEL_FULL;
|
||||
|
||||
self->client = g_udev_client_new (subsystems);
|
||||
g_signal_connect_swapped (G_OBJECT (self->client), "uevent",
|
||||
G_CALLBACK (device_changes), self);
|
||||
init_devices (self);
|
||||
|
||||
self->events = g_hash_table_new_full (g_direct_hash,
|
||||
g_direct_equal,
|
||||
NULL,
|
||||
(GDestroyNotify)g_object_unref);
|
||||
}
|
||||
|
||||
FbdFeedbackManager *
|
||||
fbd_feedback_manager_get_default (void)
|
||||
{
|
||||
static FbdFeedbackManager *instance;
|
||||
|
||||
if (instance == NULL) {
|
||||
instance = g_object_new (FBD_TYPE_FEEDBACK_MANAGER, NULL);
|
||||
g_object_add_weak_pointer (G_OBJECT (instance), (gpointer *)&instance);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
FbdDevVibra *
|
||||
fbd_feedback_manager_get_dev_vibra (FbdFeedbackManager *self)
|
||||
{
|
||||
g_return_val_if_fail (FBD_IS_FEEDBACK_MANAGER (self), NULL);
|
||||
|
||||
return self->vibra;
|
||||
}
|
||||
|
||||
FbdDevSound *
|
||||
fbd_feedback_manager_get_dev_sound (FbdFeedbackManager *self)
|
||||
{
|
||||
g_return_val_if_fail (FBD_IS_FEEDBACK_MANAGER (self), NULL);
|
||||
|
||||
return self->sound;
|
||||
}
|
||||
|
||||
gboolean
|
||||
fbd_feedback_manager_set_profile (FbdFeedbackManager *self, const gchar *profile)
|
||||
{
|
||||
FbdFeedbackProfileLevel level;
|
||||
|
||||
g_return_val_if_fail (FBD_IS_FEEDBACK_MANAGER (self), FALSE);
|
||||
|
||||
level = fbd_feedback_profile_level (profile);
|
||||
|
||||
if (level == FBD_FEEDBACK_PROFILE_LEVEL_UNKNOWN)
|
||||
return FALSE;
|
||||
|
||||
if (level == self->level)
|
||||
return TRUE;
|
||||
|
||||
g_debug ("Switching profile to '%s'", profile);
|
||||
self->level = level;
|
||||
lfb_gdbus_feedback_set_profile (LFB_GDBUS_FEEDBACK (self), profile);
|
||||
g_settings_set_string (self->settings, FEEDBACKD_KEY_PROFILE, profile);
|
||||
return TRUE;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Purism SPC
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0+
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "fbd-dev-vibra.h"
|
||||
#include "fbd-dev-sound.h"
|
||||
#include "lfb-gdbus.h"
|
||||
#include <glib-object.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define FBD_TYPE_FEEDBACK_MANAGER (fbd_feedback_manager_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (FbdFeedbackManager, fbd_feedback_manager, FBD, FEEDBACK_MANAGER, LfbGdbusFeedbackSkeleton);
|
||||
|
||||
FbdFeedbackManager *fbd_feedback_manager_get_default (void);
|
||||
FbdDevVibra *fbd_feedback_manager_get_dev_vibra (FbdFeedbackManager *self);
|
||||
FbdDevSound *fbd_feedback_manager_get_dev_sound (FbdFeedbackManager *self);
|
||||
gboolean fbd_feedback_manager_set_profile (FbdFeedbackManager *self, const gchar *profile);
|
||||
|
||||
G_END_DECLS
|
||||
@@ -0,0 +1,341 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Purism SPC
|
||||
* SPDX-License-Identifier: GPL-3.0+
|
||||
* Author: Guido Günther <agx@sigxcpu.org>
|
||||
*/
|
||||
|
||||
#define G_LOG_DOMAIN "fbd-feedback-profile"
|
||||
|
||||
#include "fbd-feedback-dummy.h"
|
||||
#include "fbd-feedback-profile.h"
|
||||
#include "fbd-feedback-sound.h"
|
||||
#include "fbd-feedback-vibra-periodic.h"
|
||||
#include "fbd-feedback-vibra-rumble.h"
|
||||
|
||||
#include <json-glib/json-glib.h>
|
||||
|
||||
#define FBD_FEEDBACK_CLS_PREFIX "FbdFeedback"
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_NAME,
|
||||
PROP_FEEDBACKS,
|
||||
PROP_LAST_PROP,
|
||||
};
|
||||
static GParamSpec *props[PROP_LAST_PROP];
|
||||
|
||||
/**
|
||||
* SECTION:fbd-feedback-profile
|
||||
* @short_description: A single profile in a #FbdFeedback
|
||||
* @Title: FbdFeedbackProfile
|
||||
*/
|
||||
|
||||
typedef struct _FbdFeedbackProfile {
|
||||
GObject parent;
|
||||
|
||||
gchar *name;
|
||||
GHashTable *feedbacks;
|
||||
} FbdFeedbackProfile;
|
||||
|
||||
static void json_serializable_iface_init (JsonSerializableIface *iface);
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE (FbdFeedbackProfile, fbd_feedback_profile, G_TYPE_OBJECT,
|
||||
G_IMPLEMENT_INTERFACE (JSON_TYPE_SERIALIZABLE,
|
||||
json_serializable_iface_init));
|
||||
|
||||
|
||||
static JsonNode *
|
||||
fbd_feedback_profile_serializable_serialize_property (JsonSerializable *serializable,
|
||||
const gchar *property_name,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
FbdFeedbackProfile *self = FBD_FEEDBACK_PROFILE (serializable);
|
||||
JsonNode *node = NULL;
|
||||
|
||||
if (g_strcmp0 (property_name, "feedbacks") == 0) {
|
||||
GHashTableIter iter;
|
||||
gpointer key;
|
||||
FbdFeedbackProfile *profile;
|
||||
JsonArray *array = json_array_sized_new (FBD_FEEDBACK_PROFILE_N_PROFILES);
|
||||
|
||||
g_hash_table_iter_init (&iter, self->feedbacks);
|
||||
while (g_hash_table_iter_next (&iter, (gpointer *)&key, (gpointer *) &profile)) {
|
||||
json_array_add_element (array, json_gobject_serialize (G_OBJECT(profile)));
|
||||
}
|
||||
node = json_node_init_array (json_node_alloc (), array);
|
||||
} else {
|
||||
node = json_serializable_default_serialize_property (serializable,
|
||||
property_name,
|
||||
value,
|
||||
pspec);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
static GType
|
||||
feedback_get_type (JsonNode *feedback_node)
|
||||
{
|
||||
g_autofree gchar *type_name = NULL;
|
||||
JsonObject *obj = json_node_get_object (feedback_node);
|
||||
JsonNode *type_node = json_object_get_member (obj, "type");
|
||||
GType gtype = 0;
|
||||
|
||||
/* Ensure all feedback types so the json parsing can use them */
|
||||
g_type_ensure (FBD_TYPE_FEEDBACK_DUMMY);
|
||||
g_type_ensure (FBD_TYPE_FEEDBACK_VIBRA_PERIODIC);
|
||||
g_type_ensure (FBD_TYPE_FEEDBACK_VIBRA_RUMBLE);
|
||||
g_type_ensure (FBD_TYPE_FEEDBACK_SOUND);
|
||||
|
||||
g_return_val_if_fail (type_node, FBD_TYPE_FEEDBACK_DUMMY);
|
||||
|
||||
type_name = g_strdup_printf ("FbdFeedback%s",
|
||||
json_node_get_string (type_node));
|
||||
type_name[strlen(FBD_FEEDBACK_CLS_PREFIX)] =
|
||||
g_ascii_toupper (type_name[strlen(FBD_FEEDBACK_CLS_PREFIX)]);
|
||||
gtype = g_type_from_name (type_name);
|
||||
|
||||
g_debug ("Feedback %s, type %lu", type_name, gtype);
|
||||
g_return_val_if_fail (gtype, FBD_TYPE_FEEDBACK_DUMMY);
|
||||
return gtype;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fbd_feedback_profile_serializable_deserialize_property (JsonSerializable *serializable,
|
||||
const gchar *property_name,
|
||||
GValue *value,
|
||||
GParamSpec *pspec,
|
||||
JsonNode *property_node)
|
||||
{
|
||||
if (g_strcmp0 (property_name, "feedbacks") == 0) {
|
||||
if (JSON_NODE_TYPE (property_node) == JSON_NODE_NULL) {
|
||||
g_value_set_pointer (value, NULL);
|
||||
return TRUE;
|
||||
} else if (JSON_NODE_TYPE (property_node) == JSON_NODE_ARRAY) {
|
||||
JsonArray *array = json_node_get_array (property_node);
|
||||
guint i, array_len = json_array_get_length (array);
|
||||
GHashTable *feedbacks = g_hash_table_new_full (g_str_hash,
|
||||
g_str_equal,
|
||||
g_free,
|
||||
(GDestroyNotify)g_object_unref);
|
||||
for (i = 0; i < array_len; i++) {
|
||||
JsonNode *element_node = json_array_get_element (array, i);
|
||||
|
||||
if (JSON_NODE_HOLDS_OBJECT (element_node)) {
|
||||
gchar *event_name;
|
||||
FbdFeedbackBase *feedback;
|
||||
GType gtype = feedback_get_type (element_node);
|
||||
|
||||
feedback = FBD_FEEDBACK_BASE (json_gobject_deserialize (gtype, element_node));
|
||||
event_name = g_strdup (fbd_feedback_get_event_name (FBD_FEEDBACK_BASE(feedback)));
|
||||
g_hash_table_insert (feedbacks, event_name, feedback);
|
||||
} else {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
g_value_set_boxed (value, feedbacks);
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
} else {
|
||||
return json_serializable_default_deserialize_property (serializable,
|
||||
property_name,
|
||||
value,
|
||||
pspec,
|
||||
property_node);
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_feedback_profile_set_property (GObject *object,
|
||||
guint property_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
FbdFeedbackProfile *self = FBD_FEEDBACK_PROFILE (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_NAME:
|
||||
g_free (self->name);
|
||||
self->name = g_value_dup_string (value);
|
||||
break;
|
||||
case PROP_FEEDBACKS:
|
||||
if (self->feedbacks)
|
||||
g_hash_table_unref (self->feedbacks);
|
||||
self->feedbacks = g_value_get_boxed (value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
fbd_feedback_profile_get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
FbdFeedbackProfile *self = FBD_FEEDBACK_PROFILE (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_NAME:
|
||||
g_value_set_string (value, self->name);
|
||||
break;
|
||||
case PROP_FEEDBACKS:
|
||||
g_value_set_boxed (value, self->feedbacks);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_feedback_profile_dispose (GObject *object)
|
||||
{
|
||||
FbdFeedbackProfile *self = FBD_FEEDBACK_PROFILE (object);
|
||||
|
||||
g_clear_pointer (&self->feedbacks, g_hash_table_unref);
|
||||
|
||||
G_OBJECT_CLASS (fbd_feedback_profile_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_feedback_profile_finalize (GObject *object)
|
||||
{
|
||||
FbdFeedbackProfile *self = FBD_FEEDBACK_PROFILE (object);
|
||||
|
||||
g_clear_pointer (&self->name, g_free);
|
||||
|
||||
G_OBJECT_CLASS (fbd_feedback_profile_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
json_serializable_iface_init (JsonSerializableIface *iface)
|
||||
{
|
||||
iface->serialize_property = fbd_feedback_profile_serializable_serialize_property;
|
||||
iface->deserialize_property = fbd_feedback_profile_serializable_deserialize_property;
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_feedback_profile_constructed (GObject *object)
|
||||
{
|
||||
FbdFeedbackProfile *self = FBD_FEEDBACK_PROFILE (object);
|
||||
|
||||
G_OBJECT_CLASS (fbd_feedback_profile_parent_class)->constructed (object);
|
||||
|
||||
if (!self->feedbacks) {
|
||||
self->feedbacks = g_hash_table_new_full (g_str_hash,
|
||||
g_str_equal,
|
||||
g_free,
|
||||
(GDestroyNotify)g_object_unref);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
fbd_feedback_profile_class_init (FbdFeedbackProfileClass *klass)
|
||||
{
|
||||
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->set_property = fbd_feedback_profile_set_property;
|
||||
object_class->get_property = fbd_feedback_profile_get_property;
|
||||
|
||||
object_class->constructed = fbd_feedback_profile_constructed;
|
||||
object_class->dispose = fbd_feedback_profile_dispose;
|
||||
object_class->finalize = fbd_feedback_profile_finalize;
|
||||
|
||||
props[PROP_NAME] =
|
||||
g_param_spec_string (
|
||||
"name",
|
||||
"Name",
|
||||
"The feedback name",
|
||||
NULL,
|
||||
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
props[PROP_FEEDBACKS] =
|
||||
g_param_spec_boxed (
|
||||
"feedbacks",
|
||||
"Feedbacks",
|
||||
"The feedbacks for this profile",
|
||||
G_TYPE_HASH_TABLE,
|
||||
/* Can't be CONSTRUCT_ONLY since json-glib can't handle it */
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_feedback_profile_init (FbdFeedbackProfile *self)
|
||||
{
|
||||
}
|
||||
|
||||
FbdFeedbackProfile *
|
||||
fbd_feedback_profile_new (const gchar *name)
|
||||
{
|
||||
return g_object_new(FBD_TYPE_FEEDBACK_PROFILE,
|
||||
"name", name,
|
||||
NULL);
|
||||
}
|
||||
|
||||
const char *
|
||||
fbd_feedback_profile_get_name (FbdFeedbackProfile *self)
|
||||
{
|
||||
g_return_val_if_fail (FBD_IS_FEEDBACK_PROFILE (self), "");
|
||||
|
||||
return self->name;
|
||||
}
|
||||
|
||||
void
|
||||
fbd_feedback_profile_add_feedback (FbdFeedbackProfile *self, FbdFeedbackBase *feedback)
|
||||
{
|
||||
g_return_if_fail (FBD_IS_FEEDBACK_PROFILE (self));
|
||||
gchar *name = g_strdup (fbd_feedback_get_event_name (feedback));
|
||||
|
||||
/* TODO: allow for more than one feedback per event and profile */
|
||||
g_hash_table_insert (self->feedbacks, name, g_object_ref (feedback));
|
||||
}
|
||||
|
||||
FbdFeedbackBase *
|
||||
fbd_feedback_profile_get_feedback (FbdFeedbackProfile *self, const char *event_name)
|
||||
{
|
||||
g_return_val_if_fail (FBD_IS_FEEDBACK_PROFILE (self), NULL);
|
||||
|
||||
return g_hash_table_lookup (self->feedbacks, event_name);
|
||||
}
|
||||
|
||||
FbdFeedbackProfileLevel
|
||||
fbd_feedback_profile_level (const char *name)
|
||||
{
|
||||
FbdFeedbackProfileLevel profile = FBD_FEEDBACK_PROFILE_LEVEL_UNKNOWN;
|
||||
|
||||
if (!g_strcmp0("silent", name)) {
|
||||
profile = FBD_FEEDBACK_PROFILE_LEVEL_SILENT;
|
||||
} else if (!g_strcmp0("quiet", name)) {
|
||||
profile = FBD_FEEDBACK_PROFILE_LEVEL_QUIET;
|
||||
} else if (!g_strcmp0("full", name)) {
|
||||
profile = FBD_FEEDBACK_PROFILE_LEVEL_FULL;
|
||||
}
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
const char*
|
||||
fbd_feedback_profile_level_to_string (FbdFeedbackProfileLevel level)
|
||||
{
|
||||
switch (level) {
|
||||
case FBD_FEEDBACK_PROFILE_LEVEL_SILENT:
|
||||
return "silent";
|
||||
case FBD_FEEDBACK_PROFILE_LEVEL_QUIET:
|
||||
return "quiet";
|
||||
case FBD_FEEDBACK_PROFILE_LEVEL_FULL:
|
||||
return "full";
|
||||
case FBD_FEEDBACK_PROFILE_LEVEL_UNKNOWN:
|
||||
default:
|
||||
return "full";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Purism SPC
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0+
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <fbd-feedback-base.h>
|
||||
|
||||
#include <glib-object.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef enum _FbdFeedbackProfileLevel {
|
||||
FBD_FEEDBACK_PROFILE_LEVEL_UNKNOWN = -1,
|
||||
FBD_FEEDBACK_PROFILE_LEVEL_SILENT = 0,
|
||||
FBD_FEEDBACK_PROFILE_LEVEL_QUIET,
|
||||
FBD_FEEDBACK_PROFILE_LEVEL_FULL,
|
||||
FBD_FEEDBACK_PROFILE_N_PROFILES,
|
||||
} FbdFeedbackProfileLevel;
|
||||
|
||||
#define FBD_TYPE_FEEDBACK_PROFILE (fbd_feedback_profile_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (FbdFeedbackProfile, fbd_feedback_profile, FBD, FEEDBACK_PROFILE, GObject);
|
||||
|
||||
FbdFeedbackProfile *fbd_feedback_profile_new (const gchar *name);
|
||||
const gchar *fbd_feedback_profile_get_name (FbdFeedbackProfile *self);
|
||||
void fbd_feedback_profile_add_feedback (FbdFeedbackProfile *self,
|
||||
FbdFeedbackBase *feedback);
|
||||
FbdFeedbackBase *fbd_feedback_profile_get_feedback (FbdFeedbackProfile *self,
|
||||
const char *event_name);
|
||||
FbdFeedbackProfileLevel fbd_feedback_profile_level (const char *name);
|
||||
const char* fbd_feedback_profile_level_to_string (FbdFeedbackProfileLevel level);
|
||||
|
||||
G_END_DECLS
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Purism SPC
|
||||
* SPDX-License-Identifier: GPL-3.0+
|
||||
* Author: Guido Günther <agx@sigxcpu.org>
|
||||
*/
|
||||
|
||||
#define G_LOG_DOMAIN "fbd-feedback-sound"
|
||||
|
||||
#include "fbd-enums.h"
|
||||
#include "fbd-feedback-sound.h"
|
||||
#include "fbd-feedback-manager.h"
|
||||
|
||||
/**
|
||||
* SECTION:fbd-feedback-sound
|
||||
* @short_description: Describes a feedback via a haptic motor
|
||||
* @Title: FbdFeedbackSound
|
||||
*
|
||||
* The #FbdSoundSound describes the properties of a sound feedback
|
||||
* event. It knows nothing about how to play sound itself but calls
|
||||
* #FbdDevSound for that.
|
||||
*/
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_EFFECT,
|
||||
PROP_LAST_PROP,
|
||||
};
|
||||
static GParamSpec *props[PROP_LAST_PROP];
|
||||
|
||||
typedef struct _FbdFeedbackSound {
|
||||
FbdFeedbackBase parent;
|
||||
|
||||
gchar *effect;
|
||||
} FbdFeedbackSound;
|
||||
|
||||
G_DEFINE_TYPE (FbdFeedbackSound, fbd_feedback_sound, FBD_TYPE_FEEDBACK_BASE);
|
||||
|
||||
static void
|
||||
on_effect_finished (FbdFeedbackSound *self)
|
||||
|
||||
{
|
||||
fbd_feedback_base_done (FBD_FEEDBACK_BASE(self));
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_feedback_sound_run (FbdFeedbackBase *base)
|
||||
{
|
||||
FbdFeedbackSound *self = FBD_FEEDBACK_SOUND (base);
|
||||
FbdFeedbackManager *manager = fbd_feedback_manager_get_default ();
|
||||
FbdDevSound *sound = fbd_feedback_manager_get_dev_sound (manager);
|
||||
|
||||
g_debug ("Sound event %s", self->effect);
|
||||
fbd_dev_sound_play (sound, self, on_effect_finished);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
fbd_feedback_sound_end (FbdFeedbackBase *base)
|
||||
{
|
||||
FbdFeedbackSound *self = FBD_FEEDBACK_SOUND (base);
|
||||
FbdFeedbackManager *manager = fbd_feedback_manager_get_default ();
|
||||
FbdDevSound *sound = fbd_feedback_manager_get_dev_sound (manager);
|
||||
|
||||
fbd_dev_sound_stop (sound, self);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
fbd_feedback_sound_set_property (GObject *object,
|
||||
guint property_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
FbdFeedbackSound *self = FBD_FEEDBACK_SOUND (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_EFFECT:
|
||||
self->effect = g_value_dup_string (value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_feedback_sound_get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
FbdFeedbackSound *self = FBD_FEEDBACK_SOUND (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_EFFECT:
|
||||
g_value_set_string (value, fbd_feedback_sound_get_effect (self));
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_feedback_sound_class_init (FbdFeedbackSoundClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
FbdFeedbackBaseClass *base_class = FBD_FEEDBACK_BASE_CLASS (klass);
|
||||
|
||||
object_class->set_property = fbd_feedback_sound_set_property;
|
||||
object_class->get_property = fbd_feedback_sound_get_property;
|
||||
|
||||
base_class->run = fbd_feedback_sound_run;
|
||||
base_class->end = fbd_feedback_sound_end;
|
||||
|
||||
props[PROP_EFFECT] =
|
||||
g_param_spec_string (
|
||||
"effect",
|
||||
"Effect",
|
||||
"The sound effects name",
|
||||
"",
|
||||
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_feedback_sound_init (FbdFeedbackSound *self)
|
||||
{
|
||||
}
|
||||
|
||||
const char *fbd_feedback_sound_get_effect (FbdFeedbackSound *self)
|
||||
{
|
||||
return self->effect;
|
||||
}
|
||||
|
||||
gboolean
|
||||
fbd_dev_sound_stop(FbdDevSound *self, FbdFeedbackSound *feedback)
|
||||
{
|
||||
/* TODO: can we use cancellable to actually end playback? */
|
||||
return TRUE;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Purism SPC
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0+
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "fbd-feedback-base.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define FBD_TYPE_FEEDBACK_SOUND (fbd_feedback_sound_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (FbdFeedbackSound, fbd_feedback_sound, FBD, FEEDBACK_SOUND, FbdFeedbackBase);
|
||||
|
||||
const char *fbd_feedback_sound_get_effect (FbdFeedbackSound *self);
|
||||
|
||||
G_END_DECLS
|
||||
@@ -0,0 +1,319 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Purism SPC
|
||||
* SPDX-License-Identifier: GPL-3.0+
|
||||
* Author: Guido Günther <agx@sigxcpu.org>
|
||||
*/
|
||||
|
||||
#define G_LOG_DOMAIN "fbd-feedback-theme"
|
||||
|
||||
#include "fbd-feedback-base.h"
|
||||
#include "fbd-feedback-dummy.h"
|
||||
#include "fbd-feedback-sound.h"
|
||||
#include "fbd-feedback-theme.h"
|
||||
#include "fbd-feedback-vibra.h"
|
||||
#include "fbd-feedback-profile.h"
|
||||
|
||||
#include <json-glib/json-glib.h>
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_NAME,
|
||||
PROP_PROFILES,
|
||||
PROP_LAST_PROP,
|
||||
};
|
||||
static GParamSpec *props[PROP_LAST_PROP];
|
||||
|
||||
typedef struct _FbdFeedbackTheme {
|
||||
GObject parent;
|
||||
|
||||
char *name;
|
||||
GHashTable *profiles;
|
||||
} FbdFeedbackTheme;
|
||||
|
||||
static void json_serializable_iface_init (JsonSerializableIface *iface);
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE (FbdFeedbackTheme, fbd_feedback_theme, G_TYPE_OBJECT,
|
||||
G_IMPLEMENT_INTERFACE (JSON_TYPE_SERIALIZABLE,
|
||||
json_serializable_iface_init));
|
||||
|
||||
|
||||
static JsonNode *
|
||||
fbd_theme_serializable_serialize_property (JsonSerializable *serializable,
|
||||
const gchar *property_name,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
FbdFeedbackTheme *self = FBD_FEEDBACK_THEME (serializable);
|
||||
JsonNode *node = NULL;
|
||||
|
||||
if (g_strcmp0 (property_name, "profiles") == 0) {
|
||||
GHashTableIter iter;
|
||||
gpointer key;
|
||||
FbdFeedbackProfile *profile;
|
||||
JsonArray *array = json_array_sized_new (FBD_FEEDBACK_PROFILE_N_PROFILES);
|
||||
|
||||
g_hash_table_iter_init (&iter, self->profiles);
|
||||
while (g_hash_table_iter_next (&iter, (gpointer *)&key, (gpointer *) &profile)) {
|
||||
json_array_add_element (array, json_gobject_serialize (G_OBJECT(profile)));
|
||||
}
|
||||
node = json_node_init_array (json_node_alloc (), array);
|
||||
} else {
|
||||
node = json_serializable_default_serialize_property (serializable,
|
||||
property_name,
|
||||
value,
|
||||
pspec);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fbd_theme_serializable_deserialize_property (JsonSerializable *serializable,
|
||||
const gchar *property_name,
|
||||
GValue *value,
|
||||
GParamSpec *pspec,
|
||||
JsonNode *property_node)
|
||||
{
|
||||
if (g_strcmp0 (property_name, "profiles") == 0) {
|
||||
if (JSON_NODE_TYPE (property_node) == JSON_NODE_NULL) {
|
||||
g_value_set_pointer (value, NULL);
|
||||
return TRUE;
|
||||
} else if (JSON_NODE_TYPE (property_node) == JSON_NODE_ARRAY) {
|
||||
JsonArray *array = json_node_get_array (property_node);
|
||||
guint i, array_len = json_array_get_length (array);
|
||||
GHashTable *profiles = g_hash_table_new_full (g_str_hash,
|
||||
g_str_equal,
|
||||
g_free,
|
||||
(GDestroyNotify)g_object_unref);
|
||||
|
||||
for (i = 0; i < array_len; i++) {
|
||||
JsonNode *element_node = json_array_get_element (array, i);
|
||||
FbdFeedbackProfile *profile;
|
||||
gchar *name;
|
||||
|
||||
if (JSON_NODE_HOLDS_OBJECT (element_node)) {
|
||||
profile = FBD_FEEDBACK_PROFILE (json_gobject_deserialize (FBD_TYPE_FEEDBACK_PROFILE,
|
||||
element_node));
|
||||
name = g_strdup (fbd_feedback_profile_get_name (profile));
|
||||
g_hash_table_insert (profiles, name, profile);
|
||||
} else {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
g_value_set_boxed (value, profiles);
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
} else {
|
||||
return json_serializable_default_deserialize_property (serializable,
|
||||
property_name,
|
||||
value,
|
||||
pspec,
|
||||
property_node);
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_feedback_theme_set_property (GObject *object,
|
||||
guint property_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
FbdFeedbackTheme *self = FBD_FEEDBACK_THEME (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_NAME:
|
||||
g_free (self->name);
|
||||
self->name = g_value_dup_string (value);
|
||||
break;
|
||||
case PROP_PROFILES:
|
||||
if (self->profiles)
|
||||
g_hash_table_unref (self->profiles);
|
||||
self->profiles = g_value_get_boxed (value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
fbd_feedback_theme_get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
FbdFeedbackTheme *self = FBD_FEEDBACK_THEME (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_NAME:
|
||||
g_value_set_string (value, self->name);
|
||||
break;
|
||||
case PROP_PROFILES:
|
||||
g_value_set_boxed (value, self->profiles);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_feedback_theme_dispose (GObject *object)
|
||||
{
|
||||
FbdFeedbackTheme *self = FBD_FEEDBACK_THEME (object);
|
||||
|
||||
g_clear_pointer (&self->profiles, g_hash_table_unref);
|
||||
|
||||
G_OBJECT_CLASS (fbd_feedback_theme_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_feedback_theme_finalize (GObject *object)
|
||||
{
|
||||
FbdFeedbackTheme *self = FBD_FEEDBACK_THEME (object);
|
||||
|
||||
g_clear_pointer (&self->name, g_free);
|
||||
|
||||
G_OBJECT_CLASS (fbd_feedback_theme_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
json_serializable_iface_init (JsonSerializableIface *iface)
|
||||
{
|
||||
iface->serialize_property = fbd_theme_serializable_serialize_property;
|
||||
iface->deserialize_property = fbd_theme_serializable_deserialize_property;
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_feedback_theme_constructed (GObject *object)
|
||||
{
|
||||
FbdFeedbackTheme *self = FBD_FEEDBACK_THEME (object);
|
||||
|
||||
G_OBJECT_CLASS (fbd_feedback_theme_parent_class)->constructed (object);
|
||||
|
||||
self->profiles = g_hash_table_new_full (g_str_hash,
|
||||
g_str_equal,
|
||||
g_free,
|
||||
(GDestroyNotify)g_object_unref);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
fbd_feedback_theme_class_init (FbdFeedbackThemeClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->set_property = fbd_feedback_theme_set_property;
|
||||
object_class->get_property = fbd_feedback_theme_get_property;
|
||||
|
||||
object_class->constructed = fbd_feedback_theme_constructed;
|
||||
object_class->dispose = fbd_feedback_theme_dispose;
|
||||
object_class->finalize = fbd_feedback_theme_finalize;
|
||||
|
||||
props[PROP_NAME] =
|
||||
g_param_spec_string (
|
||||
"name",
|
||||
"Name",
|
||||
"The theme name",
|
||||
NULL,
|
||||
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
props[PROP_PROFILES] =
|
||||
g_param_spec_boxed (
|
||||
"profiles",
|
||||
"Profiles",
|
||||
"The feedback profiles",
|
||||
G_TYPE_HASH_TABLE,
|
||||
/* Can't be CONSTRUCT_ONLY since json-glib can't handle it */
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_feedback_theme_init (FbdFeedbackTheme *self)
|
||||
{
|
||||
}
|
||||
|
||||
FbdFeedbackTheme *
|
||||
fbd_feedback_theme_new (const gchar *name)
|
||||
{
|
||||
return FBD_FEEDBACK_THEME (g_object_new (FBD_TYPE_FEEDBACK_THEME,
|
||||
"name", name,
|
||||
NULL));
|
||||
}
|
||||
|
||||
|
||||
FbdFeedbackTheme *
|
||||
fbd_feedback_theme_new_from_data (const gchar *data, GError **error)
|
||||
{
|
||||
JsonNode *node = json_from_string(data, error);
|
||||
if (!node)
|
||||
return NULL;
|
||||
|
||||
return FBD_FEEDBACK_THEME (json_gobject_deserialize (FBD_TYPE_FEEDBACK_THEME, node));
|
||||
}
|
||||
|
||||
|
||||
FbdFeedbackTheme *
|
||||
fbd_feedback_theme_new_from_file (const gchar *filename, GError **error)
|
||||
{
|
||||
g_autofree char *data = NULL;
|
||||
|
||||
if (!g_file_get_contents (filename, &data, NULL, error))
|
||||
return NULL;
|
||||
|
||||
return fbd_feedback_theme_new_from_data (data, error);
|
||||
}
|
||||
|
||||
const char *
|
||||
fbd_feedback_theme_get_name (FbdFeedbackTheme *self)
|
||||
{
|
||||
g_return_val_if_fail (FBD_IS_FEEDBACK_THEME (self), "");
|
||||
|
||||
return self->name;
|
||||
}
|
||||
|
||||
void
|
||||
fbd_feedback_theme_add_profile (FbdFeedbackTheme *self, FbdFeedbackProfile *profile)
|
||||
{
|
||||
gchar *name;
|
||||
|
||||
g_return_if_fail (FBD_IS_FEEDBACK_THEME (self));
|
||||
g_return_if_fail (FBD_IS_FEEDBACK_PROFILE (profile));
|
||||
name = g_strdup (fbd_feedback_profile_get_name (profile));
|
||||
|
||||
g_hash_table_insert (self->profiles, name, g_object_ref (profile));
|
||||
}
|
||||
|
||||
FbdFeedbackProfile *
|
||||
fbd_feedback_theme_get_profile (FbdFeedbackTheme *self, const char *name)
|
||||
{
|
||||
g_return_val_if_fail (FBD_IS_FEEDBACK_THEME (self), NULL);
|
||||
|
||||
return g_hash_table_lookup (self->profiles, name);
|
||||
}
|
||||
|
||||
GSList *
|
||||
fbd_feedback_theme_lookup_feedback (FbdFeedbackTheme *self,
|
||||
FbdFeedbackProfileLevel level,
|
||||
FbdEvent *event)
|
||||
{
|
||||
GSList *feedbacks = NULL;
|
||||
|
||||
for (int i = level; i >= FBD_FEEDBACK_PROFILE_LEVEL_SILENT; i--) {
|
||||
const char *profile_name = fbd_feedback_profile_level_to_string (i);
|
||||
FbdFeedbackProfile *profile = fbd_feedback_theme_get_profile (self, profile_name);
|
||||
FbdFeedbackBase *feedback = fbd_feedback_profile_get_feedback (profile,
|
||||
fbd_event_get_event (event));
|
||||
if (feedback) {
|
||||
feedbacks = g_slist_prepend (feedbacks, g_object_ref(feedback));
|
||||
}
|
||||
}
|
||||
|
||||
if (!g_slist_length (feedbacks))
|
||||
g_warning ("No feedback for event %s", fbd_event_get_event (event));
|
||||
return feedbacks;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Purism SPC
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0+
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "fbd-event.h"
|
||||
#include "fbd-feedback-base.h"
|
||||
#include "fbd-feedback-manager.h"
|
||||
#include "fbd-feedback-profile.h"
|
||||
|
||||
#include <glib-object.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define FBD_TYPE_FEEDBACK_THEME (fbd_feedback_theme_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (FbdFeedbackTheme, fbd_feedback_theme, FBD, FEEDBACK_THEME, GObject);
|
||||
|
||||
FbdFeedbackTheme *fbd_feedback_theme_new (const char *name);
|
||||
FbdFeedbackTheme *fbd_feedback_theme_new_from_data (const gchar *data, GError **error);
|
||||
FbdFeedbackTheme *fbd_feedback_theme_new_from_file (const gchar *filename, GError **error);
|
||||
|
||||
const char *fbd_feedback_theme_get_name (FbdFeedbackTheme *self);
|
||||
void fbd_feedback_theme_add_profile (FbdFeedbackTheme *self,
|
||||
FbdFeedbackProfile *profile);
|
||||
FbdFeedbackProfile *fbd_feedback_theme_get_profile (FbdFeedbackTheme *self, const char *name);
|
||||
|
||||
GSList *fbd_feedback_theme_lookup_feedback (FbdFeedbackTheme *self,
|
||||
FbdFeedbackProfileLevel profile,
|
||||
FbdEvent *event);
|
||||
|
||||
G_END_DECLS
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Purism SPC
|
||||
* SPDX-License-Identifier: GPL-3.0+
|
||||
* Author: Guido Günther <agx@sigxcpu.org>
|
||||
*/
|
||||
|
||||
#define G_LOG_DOMAIN "fbd-feedback-vibra-periodic"
|
||||
|
||||
#include "fbd-enums.h"
|
||||
#include "fbd-feedback-vibra-periodic.h"
|
||||
#include "fbd-feedback-manager.h"
|
||||
|
||||
/**
|
||||
* SECTION:fbd-feedback-vibra
|
||||
* @short_description: Describes a feedback via a haptic motor
|
||||
* @Title: FbdFeedbackVibra
|
||||
*
|
||||
* The #FbdVibraVibra describes the properties of a haptic feedback
|
||||
* event. It knows nothing about the hardware itself but calls
|
||||
* #FbdDevVibra for that.
|
||||
*/
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_MAGNITUDE,
|
||||
PROP_FADE_IN_LEVEL,
|
||||
PROP_FADE_IN_TIME,
|
||||
PROP_LAST_PROP,
|
||||
};
|
||||
static GParamSpec *props[PROP_LAST_PROP];
|
||||
|
||||
typedef struct _FbdFeedbackVibraPeriodic {
|
||||
FbdFeedbackVibra parent;
|
||||
|
||||
guint magnitude;
|
||||
guint fade_in_level;
|
||||
guint fade_in_time;
|
||||
} FbdFeedbackVibraPeriodic;
|
||||
|
||||
G_DEFINE_TYPE (FbdFeedbackVibraPeriodic, fbd_feedback_vibra_periodic, FBD_TYPE_FEEDBACK_VIBRA);
|
||||
|
||||
static void
|
||||
fbd_feedback_vibra_periodic_set_property (GObject *object,
|
||||
guint property_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
FbdFeedbackVibraPeriodic *self = FBD_FEEDBACK_VIBRA_PERIODIC (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_MAGNITUDE:
|
||||
self->magnitude = g_value_get_uint (value);
|
||||
break;
|
||||
case PROP_FADE_IN_LEVEL:
|
||||
self->fade_in_level = g_value_get_uint (value);
|
||||
break;
|
||||
case PROP_FADE_IN_TIME:
|
||||
self->fade_in_time = g_value_get_uint (value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_feedback_vibra_periodic_get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
FbdFeedbackVibraPeriodic *self = FBD_FEEDBACK_VIBRA_PERIODIC (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_MAGNITUDE:
|
||||
g_value_set_uint (value, self->magnitude);
|
||||
break;
|
||||
case PROP_FADE_IN_LEVEL:
|
||||
g_value_set_uint (value, self->fade_in_level);
|
||||
break;
|
||||
case PROP_FADE_IN_TIME:
|
||||
g_value_set_uint (value, self->fade_in_time);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_feedback_vibra_periodic_end_vibra (FbdFeedbackVibra *vibra)
|
||||
{
|
||||
FbdFeedbackManager *manager = fbd_feedback_manager_get_default ();
|
||||
FbdDevVibra *dev = fbd_feedback_manager_get_dev_vibra (manager);
|
||||
|
||||
fbd_dev_vibra_stop (dev);
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_feedback_vibra_periodic_start_vibra (FbdFeedbackVibra *vibra)
|
||||
{
|
||||
FbdFeedbackVibraPeriodic *self = FBD_FEEDBACK_VIBRA_PERIODIC (vibra);
|
||||
FbdFeedbackManager *manager = fbd_feedback_manager_get_default ();
|
||||
FbdDevVibra *dev = fbd_feedback_manager_get_dev_vibra (manager);
|
||||
guint duration = fbd_feedback_vibra_get_duration (vibra);
|
||||
|
||||
g_debug ("Periodic Vibra: %d %d %d %d",
|
||||
duration, self->magnitude, self->fade_in_level, self->fade_in_time);
|
||||
|
||||
fbd_dev_vibra_periodic (dev, duration, self->magnitude, self->fade_in_level,
|
||||
self->fade_in_time);
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_feedback_vibra_periodic_class_init (FbdFeedbackVibraPeriodicClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
FbdFeedbackVibraClass *vibra_class = FBD_FEEDBACK_VIBRA_CLASS (klass);
|
||||
|
||||
object_class->set_property = fbd_feedback_vibra_periodic_set_property;
|
||||
object_class->get_property = fbd_feedback_vibra_periodic_get_property;
|
||||
|
||||
vibra_class->start_vibra = fbd_feedback_vibra_periodic_start_vibra;
|
||||
vibra_class->end_vibra = fbd_feedback_vibra_periodic_end_vibra;
|
||||
|
||||
props[PROP_MAGNITUDE] =
|
||||
g_param_spec_uint (
|
||||
"magnitude",
|
||||
"Magnitude",
|
||||
"total magnitude",
|
||||
0, 0xFFFF, 0x7FFF,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
props[PROP_FADE_IN_TIME] =
|
||||
g_param_spec_uint (
|
||||
"fade-in-time",
|
||||
"Fade in time",
|
||||
"Fade in time until full strength",
|
||||
0, G_MAXINT, 0,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
props[PROP_FADE_IN_LEVEL] =
|
||||
g_param_spec_uint (
|
||||
"fade-in-level",
|
||||
"Fade in level",
|
||||
"Fade in start level",
|
||||
0, 0xFFFF, 0x7FFF,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_feedback_vibra_periodic_init (FbdFeedbackVibraPeriodic *self)
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Purism SPC
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0+
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "fbd-feedback-vibra.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define FBD_TYPE_FEEDBACK_VIBRA_PERIODIC (fbd_feedback_vibra_periodic_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (FbdFeedbackVibraPeriodic, fbd_feedback_vibra_periodic, FBD,
|
||||
FEEDBACK_VIBRA_PERIODIC,
|
||||
FbdFeedbackVibra);
|
||||
|
||||
G_END_DECLS
|
||||
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Purism SPC
|
||||
* SPDX-License-Identifier: GPL-3.0+
|
||||
* Author: Guido Günther <agx@sigxcpu.org>
|
||||
*/
|
||||
|
||||
#define G_LOG_DOMAIN "fbd-feedback-vibra-rumble"
|
||||
|
||||
#include "fbd-enums.h"
|
||||
#include "fbd-feedback-vibra-rumble.h"
|
||||
#include "fbd-feedback-manager.h"
|
||||
|
||||
/**
|
||||
* SECTION:fbd-feedback-vibra
|
||||
* @short_description: Describes a feedback via a haptic motor
|
||||
* @Title: FbdFeedbackVibra
|
||||
*
|
||||
* The #FbdVibraVibra describes the properties of a haptic feedback
|
||||
* event. It knows nothing about the hardware itself but calls
|
||||
* #FbdDevVibra for that.
|
||||
*/
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_COUNT,
|
||||
PROP_PAUSE,
|
||||
PROP_LAST_PROP,
|
||||
};
|
||||
static GParamSpec *props[PROP_LAST_PROP];
|
||||
|
||||
typedef struct _FbdFeedbackVibraRumble {
|
||||
FbdFeedbackVibra parent;
|
||||
|
||||
guint count; /* number of rumbles */
|
||||
guint pause; /* pause in msecs */
|
||||
|
||||
guint rumble; /* rumble in msecs */
|
||||
guint periods; /* number of periods to play */
|
||||
guint timer_id;
|
||||
} FbdFeedbackVibraRumble;
|
||||
|
||||
G_DEFINE_TYPE (FbdFeedbackVibraRumble, fbd_feedback_vibra_rumble, FBD_TYPE_FEEDBACK_VIBRA);
|
||||
|
||||
static void
|
||||
fbd_feedback_vibra_rumble_set_property (GObject *object,
|
||||
guint property_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
FbdFeedbackVibraRumble *self = FBD_FEEDBACK_VIBRA_RUMBLE (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_COUNT:
|
||||
self->count = g_value_get_uint (value);
|
||||
break;
|
||||
case PROP_PAUSE:
|
||||
self->pause = g_value_get_uint (value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_feedback_vibra_rumble_get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
FbdFeedbackVibraRumble *self = FBD_FEEDBACK_VIBRA_RUMBLE (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_COUNT:
|
||||
g_value_set_uint (value, self->count);
|
||||
break;
|
||||
case PROP_PAUSE:
|
||||
g_value_set_uint (value, self->pause);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
on_period_ended (FbdFeedbackVibraRumble *self)
|
||||
{
|
||||
FbdFeedbackManager *manager = fbd_feedback_manager_get_default ();
|
||||
FbdDevVibra *dev = fbd_feedback_manager_get_dev_vibra (manager);
|
||||
|
||||
g_return_val_if_fail (FBD_IS_FEEDBACK_VIBRA_RUMBLE (self), G_SOURCE_REMOVE);
|
||||
|
||||
if (self->periods) {
|
||||
fbd_dev_vibra_rumble (dev, self->rumble, FALSE);
|
||||
self->periods--;
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_feedback_vibra_rumble_end_vibra (FbdFeedbackVibra *vibra)
|
||||
{
|
||||
FbdFeedbackVibraRumble *self = FBD_FEEDBACK_VIBRA_RUMBLE (vibra);
|
||||
FbdFeedbackManager *manager = fbd_feedback_manager_get_default ();
|
||||
FbdDevVibra *dev = fbd_feedback_manager_get_dev_vibra (manager);
|
||||
|
||||
fbd_dev_vibra_stop (dev);
|
||||
g_clear_handle_id(&self->timer_id, g_source_remove);
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_feedback_vibra_rumble_start_vibra (FbdFeedbackVibra *vibra)
|
||||
{
|
||||
FbdFeedbackVibraRumble *self = FBD_FEEDBACK_VIBRA_RUMBLE (vibra);
|
||||
FbdFeedbackManager *manager = fbd_feedback_manager_get_default ();
|
||||
FbdDevVibra *dev = fbd_feedback_manager_get_dev_vibra (manager);
|
||||
guint duration = fbd_feedback_vibra_get_duration (vibra);
|
||||
guint period;
|
||||
|
||||
self->rumble = (duration / self->count) - self->pause;
|
||||
if (self->rumble <= 0) {
|
||||
self->rumble = FBD_FEEDBACK_VIBRA_DEFAULT_DURATION;
|
||||
self->pause = 0;
|
||||
self->count = 1;
|
||||
}
|
||||
period = self->rumble + self->pause;
|
||||
self->periods = self->count;
|
||||
|
||||
g_debug ("Rumble Vibra event: duration %d, rumble: %d, pause: %d, period: %d",
|
||||
duration, self->rumble, self->pause, period);
|
||||
fbd_dev_vibra_rumble (dev, self->rumble, TRUE);
|
||||
self->periods--;
|
||||
if (self->periods) {
|
||||
self->timer_id = g_timeout_add (period, (GSourceFunc) on_period_ended, self);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_feedback_vibra_rumble_class_init (FbdFeedbackVibraRumbleClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
FbdFeedbackVibraClass *vibra_class = FBD_FEEDBACK_VIBRA_CLASS (klass);
|
||||
|
||||
object_class->set_property = fbd_feedback_vibra_rumble_set_property;
|
||||
object_class->get_property = fbd_feedback_vibra_rumble_get_property;
|
||||
|
||||
vibra_class->start_vibra = fbd_feedback_vibra_rumble_start_vibra;
|
||||
vibra_class->end_vibra = fbd_feedback_vibra_rumble_end_vibra;
|
||||
|
||||
props[PROP_COUNT] =
|
||||
g_param_spec_uint (
|
||||
"count",
|
||||
"Count",
|
||||
"The number of rumbles",
|
||||
0, G_MAXINT, 1,
|
||||
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
props[PROP_PAUSE] =
|
||||
g_param_spec_uint (
|
||||
"pause",
|
||||
"Pause",
|
||||
"The pause in msecs between rumbles",
|
||||
0, G_MAXINT, 0,
|
||||
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_feedback_vibra_rumble_init (FbdFeedbackVibraRumble *self)
|
||||
{
|
||||
self->count = 1;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Purism SPC
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0+
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "fbd-feedback-vibra.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define FBD_TYPE_FEEDBACK_VIBRA_RUMBLE (fbd_feedback_vibra_rumble_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (FbdFeedbackVibraRumble, fbd_feedback_vibra_rumble, FBD,
|
||||
FEEDBACK_VIBRA_RUMBLE,
|
||||
FbdFeedbackVibra);
|
||||
|
||||
G_END_DECLS
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Purism SPC
|
||||
* SPDX-License-Identifier: GPL-3.0+
|
||||
* Author: Guido Günther <agx@sigxcpu.org>
|
||||
*/
|
||||
|
||||
#define G_LOG_DOMAIN "fbd-feedback-vibra"
|
||||
|
||||
#include "fbd-enums.h"
|
||||
#include "fbd-feedback-vibra.h"
|
||||
#include "fbd-feedback-manager.h"
|
||||
|
||||
/**
|
||||
* SECTION:fbd-feedback-vibra
|
||||
* @short_description: Describes a feedback via a haptic motor
|
||||
* @Title: FbdFeedbackVibra
|
||||
*
|
||||
* The #FbdVibraVibra describes the properties of a haptic feedback
|
||||
* event. It knows nothing about the hardware itself but calls
|
||||
* #FbdDevVibra for that.
|
||||
*/
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_DURATION,
|
||||
PROP_LAST_PROP,
|
||||
};
|
||||
static GParamSpec *props[PROP_LAST_PROP];
|
||||
|
||||
typedef struct _FbdFeedbackVibraPrivate {
|
||||
guint duration;
|
||||
guint timer_id;
|
||||
} FbdFeedbackVibraPrivate;
|
||||
|
||||
G_DEFINE_TYPE_WITH_PRIVATE (FbdFeedbackVibra, fbd_feedback_vibra, FBD_TYPE_FEEDBACK_BASE);
|
||||
|
||||
|
||||
static gboolean
|
||||
on_timeout_expired (FbdFeedbackVibra *self)
|
||||
{
|
||||
FbdFeedbackManager *manager = fbd_feedback_manager_get_default ();
|
||||
FbdDevVibra *dev = fbd_feedback_manager_get_dev_vibra (manager);
|
||||
|
||||
fbd_dev_vibra_remove_effect (dev);
|
||||
fbd_feedback_base_done (FBD_FEEDBACK_BASE(self));
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_feedback_vibra_run (FbdFeedbackBase *base)
|
||||
{
|
||||
FbdFeedbackVibra *self = FBD_FEEDBACK_VIBRA (base);
|
||||
FbdFeedbackVibraPrivate *priv = fbd_feedback_vibra_get_instance_private (self);
|
||||
FbdFeedbackVibraClass *klass;
|
||||
|
||||
klass = FBD_FEEDBACK_VIBRA_GET_CLASS (self);
|
||||
g_return_if_fail (klass->start_vibra);
|
||||
klass->start_vibra (self);
|
||||
|
||||
priv->timer_id = g_timeout_add (priv->duration,
|
||||
(GSourceFunc)on_timeout_expired,
|
||||
self);
|
||||
g_source_set_name_by_id (priv->timer_id, "feedback-vibra-timer");
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
fbd_feedback_vibra_end (FbdFeedbackBase *base)
|
||||
{
|
||||
FbdFeedbackVibra *self = FBD_FEEDBACK_VIBRA (base);
|
||||
FbdFeedbackVibraPrivate *priv = fbd_feedback_vibra_get_instance_private (self);
|
||||
FbdFeedbackVibraClass *klass = FBD_FEEDBACK_VIBRA_GET_CLASS (self);
|
||||
|
||||
if (!priv->timer_id)
|
||||
return;
|
||||
|
||||
g_return_if_fail (klass->end_vibra);
|
||||
klass->end_vibra(self);
|
||||
g_clear_handle_id(&priv->timer_id, g_source_remove);
|
||||
fbd_feedback_base_done (FBD_FEEDBACK_BASE(self));
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
fbd_feedback_vibra_set_property (GObject *object,
|
||||
guint property_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
FbdFeedbackVibra *self = FBD_FEEDBACK_VIBRA (object);
|
||||
FbdFeedbackVibraPrivate *priv = fbd_feedback_vibra_get_instance_private (self);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_DURATION:
|
||||
priv->duration = g_value_get_uint (value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_feedback_vibra_get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
FbdFeedbackVibra *self = FBD_FEEDBACK_VIBRA (object);
|
||||
FbdFeedbackVibraPrivate *priv = fbd_feedback_vibra_get_instance_private (self);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_DURATION:
|
||||
g_value_set_uint (value, priv->duration);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_feedback_vibra_class_init (FbdFeedbackVibraClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
FbdFeedbackBaseClass *base_class = FBD_FEEDBACK_BASE_CLASS (klass);
|
||||
|
||||
object_class->set_property = fbd_feedback_vibra_set_property;
|
||||
object_class->get_property = fbd_feedback_vibra_get_property;
|
||||
|
||||
base_class->run = fbd_feedback_vibra_run;
|
||||
base_class->end = fbd_feedback_vibra_end;
|
||||
|
||||
props[PROP_DURATION] =
|
||||
g_param_spec_uint (
|
||||
"duration",
|
||||
"Duration",
|
||||
"Vibra event duration in msecs",
|
||||
0, G_MAXUINT, FBD_FEEDBACK_VIBRA_DEFAULT_DURATION,
|
||||
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
|
||||
}
|
||||
|
||||
static void
|
||||
fbd_feedback_vibra_init (FbdFeedbackVibra *self)
|
||||
{
|
||||
}
|
||||
|
||||
guint fbd_feedback_vibra_get_duration (FbdFeedbackVibra *self)
|
||||
{
|
||||
FbdFeedbackVibraPrivate *priv;
|
||||
|
||||
g_return_val_if_fail (FBD_IS_FEEDBACK_VIBRA (self), 0);
|
||||
priv = fbd_feedback_vibra_get_instance_private (self);
|
||||
return priv->duration;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Purism SPC
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0+
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "fbd-feedback-base.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define FBD_TYPE_FEEDBACK_VIBRA (fbd_feedback_vibra_get_type())
|
||||
|
||||
#define FBD_FEEDBACK_VIBRA_DEFAULT_DURATION 1000
|
||||
|
||||
G_DECLARE_DERIVABLE_TYPE (FbdFeedbackVibra, fbd_feedback_vibra, FBD, FEEDBACK_VIBRA, FbdFeedbackBase);
|
||||
|
||||
struct _FbdFeedbackVibraClass
|
||||
{
|
||||
FbdFeedbackBaseClass parent_class;
|
||||
|
||||
void (*start_vibra) (FbdFeedbackVibra *self);
|
||||
void (*end_vibra) (FbdFeedbackVibra *self);
|
||||
};
|
||||
|
||||
guint fbd_feedback_vibra_get_duration (FbdFeedbackVibra *self);
|
||||
|
||||
G_END_DECLS
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Purism SPC
|
||||
* SPDX-License-Identifier: GPL-3.0+
|
||||
* Author: Guido Günther <agx@sigxcpu.org>
|
||||
*/
|
||||
|
||||
#define G_LOG_DOMAIN "fbd"
|
||||
|
||||
#include "fbd.h"
|
||||
#include "fbd-feedback-manager.h"
|
||||
#include "lfb-names.h"
|
||||
#include "lfb-gdbus.h"
|
||||
|
||||
#include <gio/gio.h>
|
||||
#include <glib-unix.h>
|
||||
|
||||
|
||||
static GMainLoop *loop;
|
||||
|
||||
|
||||
GQuark fbd_error_quark(void)
|
||||
{
|
||||
static GQuark quark = 0;
|
||||
|
||||
if (!quark)
|
||||
quark = g_quark_from_static_string("fbd");
|
||||
|
||||
return quark;
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
quit_cb (gpointer user_data)
|
||||
{
|
||||
g_info ("Caught signal, shutting down...");
|
||||
|
||||
if (loop)
|
||||
g_idle_add ((GSourceFunc) g_main_loop_quit, loop);
|
||||
else
|
||||
exit (0);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
bus_acquired_cb (GDBusConnection *connection,
|
||||
const gchar *name,
|
||||
gpointer user_data)
|
||||
{
|
||||
FbdFeedbackManager *manager = fbd_feedback_manager_get_default ();
|
||||
|
||||
g_debug ("Bus acquired, creating manager...");
|
||||
|
||||
g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (manager),
|
||||
connection,
|
||||
FB_DBUS_PATH,
|
||||
NULL);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
name_acquired_cb (GDBusConnection *connection,
|
||||
const gchar *name,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_debug ("Service name '%s' was acquired", name);
|
||||
}
|
||||
|
||||
static void
|
||||
name_lost_cb (GDBusConnection *connection,
|
||||
const gchar *name,
|
||||
gpointer user_data)
|
||||
{
|
||||
/* Note that we're not allowing replacement, so once the name acquired, the
|
||||
* process won't lose it. */
|
||||
if (!name) {
|
||||
g_warning ("Could not get the session bus. Make sure "
|
||||
"the message bus daemon is running!");
|
||||
} else {
|
||||
if (connection)
|
||||
g_warning ("Could not acquire the '%s' service name", name);
|
||||
else
|
||||
g_debug ("DBus connection close");
|
||||
}
|
||||
|
||||
g_main_loop_quit (loop);
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
g_autoptr(GError) err = NULL;
|
||||
g_autoptr(GOptionContext) opt_context = NULL;
|
||||
|
||||
opt_context = g_option_context_new ("- A daemon to trigger event feedback");
|
||||
if (!g_option_context_parse (opt_context, &argc, &argv, &err)) {
|
||||
g_warning ("%s", err->message);
|
||||
g_clear_error (&err);
|
||||
return 1;
|
||||
}
|
||||
|
||||
g_unix_signal_add (SIGTERM, quit_cb, NULL);
|
||||
g_unix_signal_add (SIGINT, quit_cb, NULL);
|
||||
|
||||
loop = g_main_loop_new (NULL, FALSE);
|
||||
|
||||
g_bus_own_name (FB_DBUS_TYPE,
|
||||
FB_DBUS_NAME,
|
||||
G_BUS_NAME_OWNER_FLAGS_NONE,
|
||||
bus_acquired_cb,
|
||||
name_acquired_cb,
|
||||
name_lost_cb,
|
||||
NULL,
|
||||
NULL);
|
||||
|
||||
g_main_loop_run (loop);
|
||||
g_main_loop_unref (loop);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <glib-object.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef enum {
|
||||
FBD_ERROR_FAILED = 0,
|
||||
} FbdError;
|
||||
|
||||
GQuark fbd_error_quark (void);
|
||||
|
||||
G_END_DECLS
|
||||
@@ -0,0 +1,68 @@
|
||||
# Feedback daemon
|
||||
if get_option('daemon')
|
||||
|
||||
fbd_enum_headers = files([
|
||||
'fbd-event.h',
|
||||
'fbd-feedback-vibra.h',
|
||||
])
|
||||
fbd_enum_sources = gnome.mkenums_simple('fbd-enums',
|
||||
sources : fbd_enum_headers)
|
||||
|
||||
sources = [
|
||||
generated_dbus_sources,
|
||||
fbd_enum_sources,
|
||||
'fbd.c',
|
||||
'fbd-dev-vibra.c',
|
||||
'fbd-dev-sound.c',
|
||||
'fbd-event.c',
|
||||
'fbd-feedback-base.c',
|
||||
'fbd-feedback-dummy.c',
|
||||
'fbd-feedback-manager.c',
|
||||
'fbd-feedback-profile.c',
|
||||
'fbd-feedback-sound.c',
|
||||
'fbd-feedback-theme.c',
|
||||
'fbd-feedback-vibra.c',
|
||||
'fbd-feedback-vibra-periodic.c',
|
||||
'fbd-feedback-vibra-rumble.c',
|
||||
]
|
||||
|
||||
fbd_deps = [
|
||||
gio,
|
||||
gio_unix,
|
||||
glib,
|
||||
gobject,
|
||||
gsound,
|
||||
gudev,
|
||||
json_glib,
|
||||
]
|
||||
|
||||
fbd_inc = [
|
||||
include_directories('.'),
|
||||
libfeedback_inc,
|
||||
dbus_inc,
|
||||
]
|
||||
|
||||
fbd_lib = static_library(
|
||||
'fbd',
|
||||
sources,
|
||||
include_directories : fbd_inc,
|
||||
dependencies : fbd_deps,
|
||||
install : false,
|
||||
)
|
||||
|
||||
fbd_dep = declare_dependency(
|
||||
include_directories : fbd_inc,
|
||||
link_with : fbd_lib,
|
||||
dependencies : fbd_deps,
|
||||
)
|
||||
|
||||
executable(
|
||||
'feedbackd',
|
||||
sources : 'fbd.c',
|
||||
include_directories : fbd_inc,
|
||||
dependencies : fbd_dep,
|
||||
install : true,
|
||||
install_dir: libexecdir,
|
||||
)
|
||||
|
||||
endif
|
||||
Reference in New Issue
Block a user