Commits (30)
(
(c-mode . (
(c-file-style . "linux")
(indent-tabs-mode . nil)
(c-basic-offset . 2)
))
(setq auto-mode-alist (cons '("\\.ui$" . nxml-mode) auto-mode-alist))
(nxml-mode . (
(indent-tabs-mode . nil)
))
(css-mode . (
(css-indent-offset . 2)
))
)
......@@ -57,7 +57,12 @@ gdbus introspect --session --dest org.sigxcpu.Feedback --object-path /org/sigxcp
and to request feedback for an event
```sh
gdbus call --session --dest org.sigxcpu.Feedback --object-path /org/sigxcpu/Feedback --method org.sigxcpu.Feedback.Feedback 'my.app.id' 'phone-incoming-call' '[]' 0
gdbus call --session --dest org.sigxcpu.Feedback --object-path /org/sigxcpu/Feedback --method org.sigxcpu.Feedback.TriggerFeedback 'my.app.id' 'phone-incoming-call' '[]' 0
```
This will return an Event id which you should memorize if you ever want to end the ringtone with:
```sh
gdbus call --session --dest org.sigxcpu.Feedback --object-path /org/sigxcpu/Feedback --method org.sigxcpu.Feedback.EndFeedback EVENTID
```
See `examples/` for a simple python example using GObject introspection.
......@@ -74,8 +79,74 @@ the quiet and silent profile work out of the box.
Events are then mapped to a specific type of feedback (sound, led, vibra) via a
device specific theme (since devices have different capabilities).
There's currently only a single hard coded theme named `default`. The currently
available feedback types are:
Feedbackd is shipped with a default theme `default.json`.
You can add your own themes in one of two ways:
1. By exporting an environment variable `FEEDBACK_THEME` with a path to a
valid theme file (not recommended, use for testing only), or
2. By creating a theme file under `$XDG_CONFIG_HOME/feedbackd/themes/default.json`.
If `XDG_CONFIG_HOME` environment variable is not set or empty, it will
default to `$HOME/.config`, or
3. By adding your theme file to one of the folders in the `XDG_DATA_DIRS`
environment variable, appended with `feedbackd/themes/`. This folder isn't
created automatically, so you have to create it yourself. Here's an example:
```bash
# Check which folders are "valid"
$ echo $XDG_DATA_DIRS
[ ... ]:/usr/local/share:/usr/share
# Pick a folder that suits you. Note that you shouldn't place themes in
# /usr/share, because they would be overwritten by updates!
# Create missing directories
$ sudo mkdir -p /usr/local/share/feedbackd/themes
# Add your theme file!
$ sudo cp my_awesome_theme.json /usr/local/share/feedbackd/themes/
```
Check out the companion [feedbackd-device-themes][1] repository for a
selection of device-specific themes. In order for your theme to be recognized
it must be named properly. Currently, theme names are based on the `compatible`
device-tree attribute. You can run the following command to get a list of valid
filenames for your custom theme (**Note**: You must run this command on the
device you want to create the theme for!):
```bash
$ cat /sys/firmware/devicetree/base/compatible | tr '\0' "\n"
```
Example output (for a Pine64 PinePhone):
```bash
$ cat /sys/firmware/devicetree/base/compatible | tr '\0' "\n"
pine64,pinephone-1.2
pine64,pinephone
allwinner,sun50i-a64
```
Thus you could create a custom feedbackd theme for the Pinephone by placing a
modified theme file in
`/usr/local/share/feedbackd/themes/pine64,pinephone.json`
If multiple theme files exist, the selection logic follows these steps:
1. It picks an identifier from the devicetree, until none are left
2. It searches through the folders in `XDG_DATA_DIRS` in order of appearence,
until none are left
3. If a theme file is found in the current location with the current name,
**it will be chosen** and other themes are ignored.
If no theme file can be found this way (i.e. there are no identifiers and
folders left to check), `default.json` is chosen instead. Given the above
examples:
- `/usr/local/share/feedbackd/themes/pine64,pinephone-1.2.json` takes
precedence over `/usr/local/share/feedbackd/themes/pine64-pinephone.json`
- `/usr/local/share/feedbackd/themes/pine64-pinephone.json` takes precedence
over `/usr/share/feedbackd/themes/pine64-pinephone-1.2.json`
- etc...
The currently available feedback types are:
- Sound (an audible sound from the sound naming spec)
- VibraRumble: haptic motor rumbling
......@@ -143,3 +214,5 @@ GSETTINGS_SCHEMA_DIR=_build/data/ gsettings set org.sigxcpu.feedbackd.applicatio
- [Feedback-theme-spec draft](./Feedback-theme-spec-0.0.0.md)
[debian/control]: ./debian/control#L5
[1]: https://source.puri.sm/Librem5/feedbackd-device-themes)
feedbackd (0.0.0+git20210426) byzantium; urgency=medium
[ Martin Bürgmann ]
* implement fbd_dev_sound_stop
tracks the FdbFeedbackSounds in a GHashTable in the FdbDevSound
(Closes: #10)
[ Guido Günther ]
* fbd-dev-sound: Don't leak GError
* fbd-dev-sound: Don't warn when sound was cancelled.
This is not an error per se.
* lfb-event: Don't leak error.
Don't leak the error values in the success case
* tests: Run lfb_event_{get,set}_timeout
* test-lfb-integration: Test invocation error
* event: Make log-domain match source file name
* tests: Run event tests too
* event: Remove 'g' prefix from common C types.
This matchs phosh's style.
* fbd-event: Add sender property.
This allows us to store the DBus sender
* fbd-manager: Keep DBus sender around
* feedback-manager: Track DBus clients and end feedbacks if they go away.
When the client goes away we build up a list of events to end feedback
for to make sure the hash table isn't modified in place. tThe alternative
approach would be to disconnect the on_event_feedbacks_ended handler,
duplicate the DBus signal emission and use g_hash_table_foreach_remove
but that looks less robust than using the same code path. (Closes: #25)
* Help emacs to indent properly
* fbd-feedback-theme: Don't leak JSON node
* fbd-dev-leds: Free actual FbdDevLed as well.
So far we only unref'ed the device.
* fbd-dev-leds: Don't leak enum_name
g_enum_to_string() is transfer full.
* fbd-dev-leds: Don't leak color
g_ascii_strdown() creates a copy already.
* fbd-dev-leds: Use automatic deep cleanup for device list.
The current logic was fragile and failed e.g. when the
FEEDBACKD_UDEV_VAL_LED would not match leaking the dev since the
g_object_unref() at the end of the loop was never hit.
Prevent that with automatic cleanup and explicitly ref'ing the
devs we want to use.
* fbd-dev-sound: Use correct cleanup for hash table.
This went unnoticed since the manager forgot to cleanup
this at all (see follow up commit).
* fbd-feedback-manager: Make sure to dispose sound device as well.
Fixes another leak.
* fbd-feedback-manager: Don't leak config_path.
It's assigned multiple times.
* fbd-feedback-manager: Don't leak device list
[ Clayton Craft ]
* fbd-ledctrl: fix matching of trigger in list of triggers.
If the trigger is the last thing in sysfs_path/LED_TRIGGER_ATTR, then
the new-line at the end of the output causes g_strv_contains to fail to
find the trigger in the list. This strips the new-line before splitting
the output into a list for searching.
[ Sebastian Spaeth ]
* README: Fix gdbus test command.
It is TriggerFeedback and not Feedback (anymore). Also add a note how to
end the feedback in order to prevent loss of hearing :-).
-- Guido Günther <agx@sigxcpu.org> Mon, 26 Apr 2021 12:34:38 +0200
feedbackd (0.0.0+git20210125) amber-phone; urgency=medium
[ Dylan Van Assche ]
......
......@@ -148,7 +148,7 @@ on_trigger_feedback_finished (LfbGdbusFeedback *proxy,
{
GTask *task = data->task;
LfbEvent *self = data->event;
GError *err = NULL;
g_autoptr (GError) err = NULL;
gboolean success;
LfbEventState state;
......@@ -161,7 +161,7 @@ on_trigger_feedback_finished (LfbGdbusFeedback *proxy,
res,
&err);
if (!success) {
g_task_return_error (task, err);
g_task_return_error (task, g_steal_pointer (&err));
state = LFB_EVENT_STATE_ERRORED;
} else {
g_task_return_boolean (task, TRUE);
......@@ -183,7 +183,7 @@ on_end_feedback_finished (LfbGdbusFeedback *proxy,
{
GTask *task = data->task;
LfbEvent *self = data->event;
GError *err = NULL;
g_autoptr (GError) err = NULL;
gboolean success;
g_return_if_fail (G_IS_TASK (task));
......@@ -194,7 +194,7 @@ on_end_feedback_finished (LfbGdbusFeedback *proxy,
res,
&err);
if (!success) {
g_task_return_error (task, err);
g_task_return_error (task, g_steal_pointer (&err));
} else
g_task_return_boolean (task, TRUE);
......
......@@ -56,6 +56,7 @@ static void
fbd_dev_led_free (FbdDevLed *led)
{
g_object_unref (led->dev);
g_free (led);
}
static gboolean
......@@ -93,8 +94,7 @@ initable_init (GInitable *initable,
{
const gchar * const subsystems[] = { LED_SUBSYSTEM, NULL };
FbdDevLeds *self = FBD_DEV_LEDS (initable);
g_autoptr (GList) leds;
g_autolist (GUdevDevice) leds = NULL;
gboolean found = FALSE;
self->client = g_udev_client_new (subsystems);
......@@ -116,11 +116,13 @@ initable_init (GInitable *initable,
color LEDSs so go with fixed colors until the kernel gives us
enough information */
for (int i = 0; i <= FBD_FEEDBACK_LED_COLOR_LAST; i++) {
g_autofree gchar *color;
g_autofree char *color = NULL;
g_autofree char *enum_name = NULL;
gchar *c;
c = strrchr (g_enum_to_string (FBD_TYPE_FEEDBACK_LED_COLOR, i), '_');
color = g_strdup (g_ascii_strdown (c+1, -1));
enum_name = g_enum_to_string (FBD_TYPE_FEEDBACK_LED_COLOR, i);
c = strrchr (enum_name, '_');
color = g_ascii_strdown (c+1, -1);
if (g_strstr_len (name, -1, color)) {
g_autoptr (GError) err = NULL;
guint brightness = g_udev_device_get_sysfs_attr_as_int (dev, "max_brightness");
......@@ -129,7 +131,7 @@ initable_init (GInitable *initable,
continue;
led = g_malloc0 (sizeof(FbdDevLed));
led->dev = dev;
led->dev = g_object_ref (dev);
led->color = i;
led->max_brightness = brightness;
path = g_udev_device_get_sysfs_path (dev);
......@@ -139,9 +141,6 @@ initable_init (GInitable *initable,
break;
}
}
if (!led)
g_object_unref (dev);
}
/* TODO: listen for new leds via udev events */
......
......@@ -29,13 +29,16 @@
typedef struct _FbdAsyncData {
FbdDevSoundPlayedCallback callback;
FbdFeedbackSound *feedback;
FbdDevSound *dev;
GCancellable *playback;
} FbdAsyncData;
typedef struct _FbdDevSound {
GObject parent;
GSoundContext *ctx;
GSettings *sound_settings;
GSettings *sound_settings;
GHashTable *playbacks;
} FbdDevSound;
static void initable_iface_init (GInitableIface *iface);
......@@ -45,8 +48,8 @@ G_DEFINE_TYPE_WITH_CODE (FbdDevSound, fbd_dev_sound, G_TYPE_OBJECT,
static void
on_sound_theme_name_changed (FbdDevSound *self,
const gchar *key,
GSettings *settings)
const gchar *key,
GSettings *settings)
{
gboolean ok;
g_autoptr(GError) error = NULL;
......@@ -69,6 +72,29 @@ on_sound_theme_name_changed (FbdDevSound *self,
g_warning ("Failed to set sound theme name to %s: %s", key, error->message);
}
static FbdAsyncData*
fbd_async_data_new (FbdDevSound *dev, FbdFeedbackSound *feedback, FbdDevSoundPlayedCallback callback)
{
FbdAsyncData* data;
data = g_new0 (FbdAsyncData, 1);
data->callback = callback;
data->feedback = g_object_ref (feedback);
data->dev = g_object_ref (dev);
data->playback = g_cancellable_new ();
return data;
}
static void
fbd_async_data_dispose (FbdAsyncData *object)
{
g_object_unref (object->feedback);
g_object_unref (object->dev);
g_object_unref (object->playback);
g_free (object);
}
static void
fbd_dev_sound_dispose (GObject *object)
{
......@@ -76,24 +102,32 @@ fbd_dev_sound_dispose (GObject *object)
g_clear_object (&self->ctx);
g_clear_object (&self->sound_settings);
g_clear_pointer (&self->playbacks, g_hash_table_unref);
G_OBJECT_CLASS (fbd_dev_sound_parent_class)->dispose (object);
}
static gboolean
initable_init (GInitable *initable,
GCancellable *cancellable,
GError **error)
initable_init (GInitable *initable,
GCancellable *cancellable,
GError **error)
{
FbdDevSound *self = FBD_DEV_SOUND (initable);
const char *desktop;
gboolean gnome_session = FALSE;
self->ctx = gsound_context_new(NULL, error);
self->playbacks = g_hash_table_new (g_direct_hash, g_direct_equal);
self->ctx = gsound_context_new (NULL, error);
if (!self->ctx)
return FALSE;
desktop = g_getenv ("XDG_CURRENT_DESKTOP");
if (!g_strcmp0 (desktop, "GNOME")) {
if (desktop) {
g_auto (GStrv) components = g_strsplit (desktop, ":", -1);
gnome_session = g_strv_contains ((const char * const *)components, "GNOME");
}
if (gnome_session) {
self->sound_settings = g_settings_new (GNOME_SOUND_SCHEMA_ID);
g_signal_connect_object (self->sound_settings, "changed::" GNOME_SOUND_KEY_THEME_NAME,
......@@ -139,11 +173,13 @@ on_sound_play_finished_callback (GSoundContext *ctx,
GAsyncResult *res,
FbdAsyncData *data)
{
GError *err = NULL;
g_autoptr (GError) err = NULL;
if (!gsound_context_play_full_finish (ctx, res, &err)) {
if (err->domain == GSOUND_ERROR && err->code == GSOUND_ERROR_NOTFOUND) {
g_debug ("Failed to find sound '%s'", fbd_feedback_sound_get_effect (data->feedback));
} else if (g_error_matches (err, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
g_debug ("Sound '%s' cancelled", fbd_feedback_sound_get_effect (data->feedback));
} else {
g_warning ("Failed to play sound '%s': %s",
fbd_feedback_sound_get_effect (data->feedback),
......@@ -152,8 +188,9 @@ on_sound_play_finished_callback (GSoundContext *ctx,
}
(*data->callback)(data->feedback);
g_object_unref (data->feedback);
g_free (data);
g_hash_table_remove (data->dev->playbacks, data->feedback);
fbd_async_data_dispose (data);
}
......@@ -165,12 +202,12 @@ fbd_dev_sound_play (FbdDevSound *self, FbdFeedbackSound *feedback, FbdDevSoundPl
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);
data = fbd_async_data_new (self, feedback, callback);
gsound_context_play_full (self->ctx, NULL,
(GAsyncReadyCallback)on_sound_play_finished_callback,
g_hash_table_insert (self->playbacks, feedback, data);
gsound_context_play_full (self->ctx, data->playback,
(GAsyncReadyCallback) on_sound_play_finished_callback,
data,
GSOUND_ATTR_EVENT_ID, fbd_feedback_sound_get_effect (feedback),
GSOUND_ATTR_EVENT_DESCRIPTION, "Feedbackd sound feedback",
......@@ -178,3 +215,20 @@ fbd_dev_sound_play (FbdDevSound *self, FbdFeedbackSound *feedback, FbdDevSoundPl
NULL);
return TRUE;
}
gboolean
fbd_dev_sound_stop (FbdDevSound *self, FbdFeedbackSound *feedback)
{
FbdAsyncData *data;
g_return_val_if_fail (FBD_IS_DEV_SOUND (self), FALSE);
data = g_hash_table_lookup (self->playbacks, feedback);
if (data == NULL)
return FALSE;
g_cancellable_cancel (data->playback);
return TRUE;
}
......@@ -4,7 +4,7 @@
* Author: Guido Günther <agx@sigxcpu.org>
*/
#define G_LOG_DOMAIN "fbd-feedback-event"
#define G_LOG_DOMAIN "fbd-event"
#include "lfb-names.h"
#include "fbd.h"
......@@ -25,6 +25,7 @@ enum {
PROP_END_REASON,
PROP_FEEDBACKS_ENDED,
PROP_TIMEOUT,
PROP_SENDER,
PROP_LAST_PROP,
};
static GParamSpec *props[PROP_LAST_PROP];
......@@ -35,8 +36,9 @@ typedef struct _FbdEvent {
guint id;
char *app_id;
char *event;
char *sender;
gint timeout;
int timeout;
gboolean expired;
guint timeout_id;
......@@ -118,6 +120,10 @@ fbd_event_set_property (GObject *object,
case PROP_END_REASON:
fbd_event_set_end_reason (self, g_value_get_enum (value));
break;
case PROP_SENDER:
g_free (self->sender);
self->sender = g_value_dup_string (value);
break;
case PROP_FEEDBACKS_ENDED:
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
......@@ -152,6 +158,9 @@ fbd_event_get_property (GObject *object,
case PROP_FEEDBACKS_ENDED:
g_value_set_boolean (value, self->ended);
break;
case PROP_SENDER:
g_value_set_string (value, self->sender);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
......@@ -181,6 +190,7 @@ fbd_event_finalize (GObject *object)
g_clear_pointer (&self->app_id, g_free);
g_clear_pointer (&self->event, g_free);
g_clear_pointer (&self->sender, g_free);
G_OBJECT_CLASS (fbd_event_parent_class)->finalize (object);
}
......@@ -245,6 +255,19 @@ fbd_event_class_init (FbdEventClass *klass)
FBD_EVENT_END_REASON_NATURAL,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
/**
* FbdEvent:Sender:
*
* The DBus name of the sender
*/
props[PROP_SENDER] =
g_param_spec_string (
"sender",
"",
"",
NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
/**
......@@ -267,17 +290,18 @@ fbd_event_init (FbdEvent *self)
}
FbdEvent *
fbd_event_new (gint id, const gchar *app_id, const gchar *event, gint timeout)
fbd_event_new (int id, const char *app_id, const char *event, int timeout, const char *sender)
{
return FBD_EVENT (g_object_new (FBD_TYPE_EVENT,
"id", id,
"app_id", app_id,
"event", event,
"timeout", timeout,
"sender", sender,
NULL));
}
const gchar *
const char *
fbd_event_get_event (FbdEvent *self)
{
g_return_val_if_fail (FBD_IS_EVENT (self), NULL);
......@@ -285,7 +309,7 @@ fbd_event_get_event (FbdEvent *self)
return self->event;
}
const gchar *
const char *
fbd_event_get_app_id (FbdEvent *self)
{
g_return_val_if_fail (FBD_IS_EVENT (self), NULL);
......@@ -301,7 +325,7 @@ fbd_event_get_id (FbdEvent *self)
return self->id;
}
gint
int
fbd_event_get_timeout (FbdEvent *self)
{
g_return_val_if_fail (FBD_IS_EVENT (self), -1);
......@@ -334,7 +358,7 @@ fbd_event_get_feedbacks (FbdEvent *self)
return self->feedbacks;
}
gint
int
fbd_event_remove_feedback (FbdEvent *self, FbdFeedbackBase *feedback)
{
g_return_val_if_fail (FBD_IS_EVENT (self), 0);
......@@ -447,3 +471,17 @@ fbd_event_get_end_reason (FbdEvent *self)
return self->end_reason;
}
/**
* fbd_event_get_sender:
* @self: The Event
*
* Returns: The DBus sender that triggered the event.
*/
const char *
fbd_event_get_sender (FbdEvent *self)
{
g_return_val_if_fail (FBD_IS_EVENT (self), FBD_EVENT_END_REASON_NATURAL);
return self->sender;
}
......@@ -33,20 +33,21 @@ typedef enum _FbdEventTimeout {
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);
FbdEvent *fbd_event_new (gint id, const char *app_id, const char *event, int timeout, const char *sender);
const char *fbd_event_get_event (FbdEvent *event);
const char *fbd_event_get_app_id (FbdEvent *event);
guint fbd_event_get_id (FbdEvent *event);
gint fbd_event_get_timeout (FbdEvent *self);
int 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,
int 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);
const char *fbd_event_get_sender (FbdEvent *self);
G_END_DECLS
......@@ -47,7 +47,10 @@ typedef struct _FbdFeedbackManager {
FbdFeedbackTheme *theme;
guint next_id;
/* Key: event id, value: event */
GHashTable *events;
/* Key: DBus name, value: watch_id */
GHashTable *clients;
/* Hardware interaction */
GUdevClient *client;
......@@ -127,7 +130,8 @@ app_get_feedback_level (const gchar *app_id)
static void
init_devices (FbdFeedbackManager *self)
{
GList *devices, *l;
GList *l;
g_autolist (GUdevClient) devices = NULL;
g_autoptr(GError) err = NULL;
devices = g_udev_client_query_by_subsystem (self->client, "input");
......@@ -214,6 +218,72 @@ on_feedbackd_setting_changed (FbdFeedbackManager *self,
fbd_feedback_manager_set_profile (self, profile);
}
static void
on_client_vanished (GDBusConnection *connection,
const gchar *name,
gpointer user_data)
{
FbdFeedbackManager *self = FBD_FEEDBACK_MANAGER (user_data);
GHashTableIter iter;
gpointer key, value;
FbdEvent *event;
GSList *l;
g_autoptr (GSList) events = NULL;
g_return_if_fail (name);
g_debug ("Client %s vanished", name);
/*
* Prepare a list of events to end feedback for so we don't modify
* the hash table in place when 'feedbacks-ended' fires.
*/
g_hash_table_iter_init (&iter, self->events);
while (g_hash_table_iter_next (&iter, &key, &value)) {
event = FBD_EVENT (value);
if (!g_strcmp0 (fbd_event_get_sender (event), name))
events = g_slist_append (events, event);
}
for (l = events; l; l = l->next) {
event = l->data;
g_debug ("Ending event %s (%d) since %s vanished",
fbd_event_get_event (event),
fbd_event_get_id (event),
name);
fbd_event_end_feedbacks (event);
}
g_hash_table_remove (self->clients, name);
}
static void
watch_client (FbdFeedbackManager *self, GDBusMethodInvocation *invocation)
{
guint watch_id;
GDBusConnection *conn = g_dbus_method_invocation_get_connection (invocation);
const char *sender = g_dbus_method_invocation_get_sender (invocation);
watch_id = g_bus_watch_name_on_connection (conn,
sender,
G_BUS_NAME_WATCHER_FLAGS_NONE,
NULL,
on_client_vanished,
self,
NULL);
g_hash_table_insert (self->clients, g_strdup (sender), GUINT_TO_POINTER (watch_id));
}
static void
free_client_watch (gpointer data)
{
guint watch_id = GPOINTER_TO_UINT (data);
if (watch_id == 0)
return;
g_bus_unwatch_name (watch_id);
}
static FbdFeedbackProfileLevel
get_max_level (FbdFeedbackProfileLevel global_level,
FbdFeedbackProfileLevel app_level,
......@@ -254,10 +324,12 @@ fbd_feedback_manager_handle_trigger_feedback (LfbGdbusFeedback *object,
FbdEvent *event;
GSList *feedbacks, *l;
gint event_id;
const gchar *sender;
FbdFeedbackProfileLevel app_level, level, hint_level = FBD_FEEDBACK_PROFILE_LEVEL_FULL;
gboolean found_fb = FALSE;
g_debug ("Event '%s' for '%s'", arg_event, arg_app_id);
sender = g_dbus_method_invocation_get_sender (invocation);
g_debug ("Event '%s' for '%s' from %s", arg_event, arg_app_id, sender);
g_return_val_if_fail (FBD_IS_FEEDBACK_MANAGER (object), FALSE);
g_return_val_if_fail (arg_app_id, FALSE);
......@@ -290,7 +362,7 @@ fbd_feedback_manager_handle_trigger_feedback (LfbGdbusFeedback *object,
event_id = self->next_id++;
event = fbd_event_new (event_id, arg_app_id, arg_event, arg_timeout);
event = fbd_event_new (event_id, arg_app_id, arg_event, arg_timeout, sender);
g_hash_table_insert (self->events, GUINT_TO_POINTER (event_id), event);
app_level = app_get_feedback_level (arg_app_id);
......@@ -318,6 +390,7 @@ fbd_feedback_manager_handle_trigger_feedback (LfbGdbusFeedback *object,
self,
G_CONNECT_SWAPPED);
fbd_event_run_feedbacks (event);
watch_client (self, invocation);
} else {
g_hash_table_remove (self->events, GUINT_TO_POINTER (event_id));
lfb_gdbus_feedback_emit_feedback_ended (LFB_GDBUS_FEEDBACK (self), event_id,
......@@ -364,10 +437,18 @@ find_themefile (void)
const gchar *comp;
g_autoptr (GError) err = NULL;
g_autofree gchar *user_config_path = NULL;
gchar **xdg_data_dirs = (gchar **) g_get_system_data_dirs ();
g_autofree gchar *config_path = NULL;
g_autofree gchar *compatibles = NULL;
// First look for a default file under $XDG_DATA_HOME
user_config_path = g_build_filename (g_get_user_config_dir (), "feedbackd",
"themes", "default.json", NULL);
if (g_file_test (user_config_path, (G_FILE_TEST_EXISTS))) {
g_debug ("Found user themefile at: %s", user_config_path);
return g_steal_pointer (&user_config_path);
}
// Try to read the device name
if (g_file_test (DEVICE_TREE_PATH, (G_FILE_TEST_EXISTS))) {
g_debug ("Found device tree device compatible at %s", DEVICE_TREE_PATH);
......@@ -381,13 +462,19 @@ find_themefile (void)
// Iterate over $XDG_DATA_DIRS
for (i = 0; i < g_strv_length (xdg_data_dirs); i++) {
config_path = g_strconcat (xdg_data_dirs[i], "feedbackd/themes/", comp, ".json", NULL);
g_autofree gchar *config_path = NULL;
g_autofree gchar *theme_file_name = NULL;
// We leave it to g_build_filename to add/remove erroneous path separators
theme_file_name = g_strconcat (comp, ".json", NULL);
config_path = g_build_filename (xdg_data_dirs[i], "feedbackd", "themes",
theme_file_name, NULL);
g_debug ("Searching for device specific themefile in %s", config_path);
// Check if file exist
if (g_file_test (config_path, (G_FILE_TEST_EXISTS))) {
g_debug ("Found themefile for this device at: %s", config_path);
return g_strdup (config_path);
return g_steal_pointer (&config_path);
}
}
......@@ -445,10 +532,12 @@ fbd_feedback_manager_dispose (GObject *object)
g_clear_object (&self->settings);
g_clear_object (&self->theme);
g_clear_object (&self->sound);
g_clear_object (&self->vibra);
g_clear_object (&self->leds);
g_clear_object (&self->client);
g_clear_pointer (&self->events, g_hash_table_destroy);
g_clear_pointer (&self->clients, g_hash_table_destroy);
G_OBJECT_CLASS (fbd_feedback_manager_parent_class)->dispose (object);
}
......@@ -486,6 +575,10 @@ fbd_feedback_manager_init (FbdFeedbackManager *self)
g_direct_equal,
NULL,
(GDestroyNotify)g_object_unref);
self->clients = g_hash_table_new_full (g_str_hash,
g_str_equal,
g_free,
free_client_watch);
}
FbdFeedbackManager *
......
......@@ -154,10 +154,3 @@ 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;
}
......@@ -249,7 +249,7 @@ fbd_feedback_theme_new (const gchar *name)
FbdFeedbackTheme *
fbd_feedback_theme_new_from_data (const gchar *data, GError **error)
{
JsonNode *node = json_from_string(data, error);
g_autoptr (JsonNode) node = json_from_string(data, error);
if (!node)
return NULL;
......
......@@ -102,6 +102,7 @@ set_trigger (const char *sysfs_path, const char *trigger)
* same trigger over and over again in a udev rule.
*/
val = read_sysfs_attr (sysfs_path, LED_TRIGGER_ATTR);
g_strstrip(val);
triggers = g_strsplit (val, " ", 0);
/*
......
......@@ -67,6 +67,7 @@ test_fbd_deps = [
fbd_tests = [
'fbd-feedback-profile',
'fbd-feedback-theme',
'fbd-event',
]
foreach test : fbd_tests
......
......@@ -15,10 +15,11 @@ test_fbd_event (void)
g_autoptr(FbdEvent) event = NULL;
g_autofree gchar *appid = NULL;
g_autofree gchar *name = NULL;
g_autofree gchar *sender = NULL;
FbdEventEndReason reason;
gint timeout;
event = fbd_event_new (1, TEST_APP_ID, TEST_EVENT, 2);
event = fbd_event_new (1, TEST_APP_ID, TEST_EVENT, 2, "sender-id");
g_assert_true (FBD_IS_EVENT (event));
g_object_get (event,
......@@ -26,6 +27,7 @@ test_fbd_event (void)
"event", &name,
"app-id", &appid,
"timeout", &timeout,
"sender", &sender,
NULL);
g_assert_cmpstr (fbd_event_get_event (event), ==, TEST_EVENT);
......@@ -37,8 +39,11 @@ test_fbd_event (void)
g_assert_cmpint (fbd_event_get_timeout (event), ==, timeout);
g_assert_cmpint (timeout, ==, 2);
g_assert_cmpint (fbd_event_get_end_reason (event), ==, FBD_EVENT_END_REASON_NORMAL);
g_assert_cmpint (reason, ==, FBD_EVENT_END_REASON_NORMAL);
g_assert_cmpstr (fbd_event_get_sender (event), ==, sender);
g_assert_cmpstr (sender, ==, "sender-id");
g_assert_cmpint (fbd_event_get_end_reason (event), ==, FBD_EVENT_END_REASON_NATURAL);
g_assert_cmpint (reason, ==, FBD_EVENT_END_REASON_NATURAL);
}
static void
......@@ -49,7 +54,7 @@ test_fbd_event_feedback (void)
g_autoptr(FbdFeedbackDummy) feedback1 = NULL;
g_autoptr(FbdFeedbackDummy) feedback2 = NULL;
event = fbd_event_new (1, TEST_APP_ID, TEST_EVENT, -1);
event = fbd_event_new (1, TEST_APP_ID, TEST_EVENT, -1, NULL);
feedback1 = g_object_new (FBD_TYPE_FEEDBACK_DUMMY, NULL);
feedback2 = g_object_new (FBD_TYPE_FEEDBACK_DUMMY, NULL);
......@@ -96,7 +101,7 @@ test_fbd_event_feedback_ended (void)
g_autoptr(FbdFeedbackDummy) feedback2 = NULL;
gboolean ended = FALSE;
event = fbd_event_new (1, TEST_APP_ID, TEST_EVENT, FBD_EVENT_TIMEOUT_ONESHOT);
event = fbd_event_new (1, TEST_APP_ID, TEST_EVENT, FBD_EVENT_TIMEOUT_ONESHOT, NULL);
feedback1 = g_object_new (FBD_TYPE_FEEDBACK_DUMMY, NULL);
fbd_event_add_feedback (event, FBD_FEEDBACK_BASE(feedback1));
feedback2 = g_object_new (FBD_TYPE_FEEDBACK_DUMMY, NULL);
......@@ -117,7 +122,7 @@ test_fbd_event_feedback_loop (void)
g_autoptr(FbdFeedbackDummy) feedback2 = NULL;
gboolean ended = FALSE;
event = fbd_event_new (1, TEST_APP_ID, TEST_EVENT, FBD_EVENT_TIMEOUT_LOOP);
event = fbd_event_new (1, TEST_APP_ID, TEST_EVENT, FBD_EVENT_TIMEOUT_LOOP, NULL);
feedback1 = g_object_new (FBD_TYPE_FEEDBACK_DUMMY, NULL);
fbd_event_add_feedback (event, FBD_FEEDBACK_BASE(feedback1));
feedback2 = g_object_new (FBD_TYPE_FEEDBACK_DUMMY, NULL);
......@@ -139,7 +144,7 @@ test_fbd_event_feedback_timeout (void)
g_autoptr(FbdFeedbackDummy) feedback2 = NULL;
gboolean ended = FALSE;
event = fbd_event_new (1, TEST_APP_ID, TEST_EVENT, 1);
event = fbd_event_new (1, TEST_APP_ID, TEST_EVENT, 1, NULL);
feedback1 = g_object_new (FBD_TYPE_FEEDBACK_DUMMY, NULL);
fbd_event_add_feedback (event, FBD_FEEDBACK_BASE(feedback1));
feedback2 = g_object_new (FBD_TYPE_FEEDBACK_DUMMY, NULL);
......
......@@ -185,6 +185,8 @@ test_lfb_integration_event_async (void)
cmp1 = NULL;
event10 = lfb_event_new ("test-dummy-10");
lfb_event_set_timeout (event10, 1);
g_assert_cmpint (lfb_event_get_timeout (event10), ==, 1);
lfb_event_set_feedback_profile (event10, "quiet");
g_signal_connect (event10, "feedback-ended", (GCallback)on_feedback_ended, &cmp1);
......@@ -210,6 +212,47 @@ test_lfb_integration_event_async (void)
g_assert_cmpint (lfb_event_get_end_reason (event10), ==, LFB_EVENT_END_REASON_EXPLICIT);
}
static void
on_event_with_error_triggered (LfbEvent *event,
GAsyncResult *res,
LfbEvent **cmp)
{
g_autoptr (GError) err = NULL;
gboolean success;
g_debug ("%s: %p, %s", __func__, event, lfb_event_get_event (event));
g_assert_true (LFB_IS_EVENT (event));
g_assert_null (*cmp);
success = lfb_event_end_feedback_finish (event, res, &err);
g_assert_false (success);
g_assert_error (err, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS);
/* "Return" event */
*cmp = event;
g_main_loop_quit (mainloop);
}
static void
test_lfb_integration_event_async_error (void)
{
g_autoptr(LfbEvent) event0 = NULL;
LfbEvent *cmp1 = NULL;
/* Empty event names are invalid */
event0 = lfb_event_new ("");
lfb_event_trigger_feedback_async (event0,
NULL,
(GAsyncReadyCallback)on_event_with_error_triggered,
&cmp1);
g_main_loop_run (mainloop);
/* The async finish callback saw the right event */
g_assert_true (event0 == cmp1);
}
static void
on_profile_changed (LfbGdbusFeedback *proxy, GParamSpec *psepc, const gchar **profile)
{
......@@ -248,11 +291,16 @@ main (gint argc, gchar *argv[])
(gpointer)test_lfb_integration_event_sync,
(gpointer)fixture_teardown);
g_test_add("/feedbackd/lfb-integration/event_async", TestFixture, NULL,
g_test_add("/feedbackd/lfb-integration/event_async/success", TestFixture, NULL,
(gpointer)fixture_setup,
(gpointer)test_lfb_integration_event_async,
(gpointer)fixture_teardown);
g_test_add("/feedbackd/lfb-integration/event_async/error", TestFixture, NULL,
(gpointer)fixture_setup,
(gpointer)test_lfb_integration_event_async_error,
(gpointer)fixture_teardown);
g_test_add("/feedbackd/lfb-integration/event_not_found", TestFixture, NULL,
(gpointer)fixture_setup,
(gpointer)test_lfb_integration_event_not_found,
......