Initial commit

Signed-off-by: Guido Günther <agx@sigxcpu.org>
This commit is contained in:
Guido Günther
2020-02-14 17:40:34 +01:00
commit 3663b7d149
83 changed files with 8059 additions and 0 deletions
+131
View File
@@ -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;
}
+26
View File
@@ -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
+367
View File
@@ -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;
}
+26
View File
@@ -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
View File
@@ -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;
}
+52
View File
@@ -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
+229
View File
@@ -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);
}
+31
View File
@@ -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
+128
View File
@@ -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)
{
}
+16
View File
@@ -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
+394
View File
@@ -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;
}
+24
View File
@@ -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
+341
View File
@@ -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";
}
}
+35
View File
@@ -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
+142
View File
@@ -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;
}
+18
View File
@@ -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
+319
View File
@@ -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;
}
+34
View File
@@ -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
+156
View File
@@ -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)
{
}
+18
View File
@@ -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
+175
View File
@@ -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;
}
+18
View File
@@ -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
+157
View File
@@ -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;
}
+28
View File
@@ -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
+119
View File
@@ -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);
}
+13
View File
@@ -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
+68
View File
@@ -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