Files
feedbackd/src/fbd-dev-sound.c
T
Guido Günther c88c6cfff5 fbd-dev-sound: Delay sending ended signal
Remove the feedback and the associated callback data before invoking the
callback (which then triggers FbdFeebackBased:ended) since we otherwise
remove the data from the currently playing sound from the hash table
instead of removing the data from the sound that just finished playing.

Call flow for a repeating sound before:

  on_sound_play_finished_callback()
    data->callback()
      fbd_feedback_get_ended()
        on_fb_ended()
          fbd_feedback_run()
            g_hash_table_insert(..., feedback, data)
            fbd_dev_sound_play()
              gsound_context_play_full() [async]
    g_hash_table_remove (..., data->feedback) <- removes wrong feedback

So (since the feedback is still the same but only the callback data
changes) we end up removing the data from the currently playing sound
from the hash table and any calls to fbd_dev_sound_stop() would fail
to find data and hence not cancel the playing sound.

Call flow for a repeating sound now:

  on_sound_play_finished_callback()
    g_hash_table_remove (..., data->feedback)
    data->callback()
      fbd_feedback_get_ended()
        on_fb_ended()
          fbd_feedback_run()
            g_hash_table_insert(..., feedback, data)
            fbd_dev_sound_play()
              gsound_context_play_full() [async]

So we remove the data of the currently playing sound before adding
the feedback to the hash table again. No ownership is transfered to
the hash table so no cleanusp need to be changed.

Closes: https://source.puri.sm/Librem5/feedbackd/-/issues/46
2021-10-15 15:12:55 +02:00

238 lines
6.4 KiB
C

/*
* 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>
#define GNOME_SOUND_SCHEMA_ID "org.gnome.desktop.sound"
#define GNOME_SOUND_KEY_THEME_NAME "theme-name"
/**
* 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;
FbdDevSound *dev;
GCancellable *cancel;
} FbdAsyncData;
typedef struct _FbdDevSound {
GObject parent;
GSoundContext *ctx;
GSettings *sound_settings;
GHashTable *playbacks;
} 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
on_sound_theme_name_changed (FbdDevSound *self,
const gchar *key,
GSettings *settings)
{
gboolean ok;
g_autoptr(GError) error = NULL;
g_autofree gchar *name = NULL;
g_return_if_fail (FBD_IS_DEV_SOUND (self));
g_return_if_fail (G_IS_SETTINGS (settings));
g_return_if_fail (!g_strcmp0 (key, GNOME_SOUND_KEY_THEME_NAME));
g_return_if_fail (self->ctx);
name = g_settings_get_string (settings, key);
g_debug ("Setting sound theme to %s", name);
ok = gsound_context_set_attributes (self->ctx,
&error,
GSOUND_ATTR_CANBERRA_XDG_THEME_NAME,
name,
NULL);
if (!ok)
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->cancel = g_cancellable_new ();
return data;
}
static void
fbd_async_data_dispose (FbdAsyncData *data)
{
g_object_unref (data->feedback);
g_object_unref (data->dev);
g_object_unref (data->cancel);
g_free (data);
}
static void
fbd_dev_sound_dispose (GObject *object)
{
FbdDevSound *self = FBD_DEV_SOUND (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)
{
FbdDevSound *self = FBD_DEV_SOUND (initable);
const char *desktop;
gboolean gnome_session = FALSE;
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 (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,
G_CALLBACK (on_sound_theme_name_changed), self,
G_CONNECT_SWAPPED);
on_sound_theme_name_changed (self, GNOME_SOUND_KEY_THEME_NAME, self->sound_settings);
}
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)
{
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),
err->message);
}
}
/* Order matters here. We need to remove the feedback from the hash table before
invoking the callback. */
g_hash_table_remove (data->dev->playbacks, data->feedback);
(*data->callback)(data->feedback);
fbd_async_data_dispose (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 = fbd_async_data_new (self, feedback, callback);
if (!g_hash_table_insert (self->playbacks, feedback, data))
g_warning ("Feedback %p already present", feedback);
gsound_context_play_full (self->ctx, data->cancel,
(GAsyncReadyCallback) on_sound_play_finished_callback,
data,
GSOUND_ATTR_EVENT_ID, fbd_feedback_sound_get_effect (feedback),
GSOUND_ATTR_EVENT_DESCRIPTION, "Feedbackd sound feedback",
GSOUND_ATTR_MEDIA_ROLE, "event",
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->cancel);
return TRUE;
}