/* * Copyright (C) 2020 Purism SPC * SPDX-License-Identifier: LGPL-2.1+ * Author: Guido Günther */ #include "lfb-event.h" #include "lfb-enums.h" #include gboolean success = TRUE; guint interval_timeout_ms = 50; enum { PROP_0, PROP_EVENT, PROP_TIMEOUT, PROP_STATE, PROP_END_REASON, PROP_FEEDBACK_PROFILE, PROP_LAST_PROP, }; static GParamSpec *props[PROP_LAST_PROP]; enum { SIGNAL_FEEDBACK_ENDED, N_SIGNALS, }; static guint signals[N_SIGNALS]; typedef struct _LfbEvent { GObject parent; char *event; gint timeout; gchar *profile; guint id; LfbEventState state; gint end_reason; gulong handler_id; } LfbEvent; G_DEFINE_TYPE (LfbEvent, lfb_event, G_TYPE_OBJECT); typedef struct _LpfAsyncData { LfbEvent *event; GTask *task; } LpfAsyncData; static void lfb_event_set_state (LfbEvent *self, LfbEventState state) { if (self->state == state) return; self->state = state; g_object_notify_by_pspec (G_OBJECT (self), props[PROP_STATE]); } static void lfb_event_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { LfbEvent *self = LFB_EVENT (object); switch (property_id) { case PROP_EVENT: g_free (self->event); self->event = g_value_dup_string (value); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_EVENT]); break; case PROP_TIMEOUT: lfb_event_set_timeout (self, g_value_get_int (value)); break; case PROP_FEEDBACK_PROFILE: lfb_event_set_feedback_profile (self, g_value_get_string (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void lfb_event_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { LfbEvent *self = LFB_EVENT (object); switch (property_id) { case PROP_EVENT: g_value_set_string (value, self->event); break; case PROP_TIMEOUT: g_value_set_int (value, self->timeout); break; case PROP_FEEDBACK_PROFILE: g_value_set_string (value, self->profile); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void lfb_event_finalize (GObject *object) { LfbEvent *self = LFB_EVENT (object); /* Signal handler is disconnected automatically due to g_signal_connect_object */ self->handler_id = 0; g_clear_pointer (&self->event, g_free); g_clear_pointer (&self->profile, g_free); G_OBJECT_CLASS (lfb_event_parent_class)->finalize (object); } static void lfb_event_class_init (LfbEventClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->set_property = lfb_event_set_property; object_class->get_property = lfb_event_get_property; object_class->finalize = lfb_event_finalize; props[PROP_EVENT] = g_param_spec_string ( "event", "Event", "The name of the event triggering the feedback", NULL, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); props[PROP_TIMEOUT] = g_param_spec_int ( "timeout", "Timeout", "When the event should timeout", -1, G_MAXINT, -1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); props[PROP_STATE] = g_param_spec_enum ( "state", "State", "The event's state", LFB_TYPE_EVENT_STATE, LFB_EVENT_END_REASON_NATURAL, G_PARAM_READABLE | 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 ended", LFB_TYPE_EVENT_END_REASON, LFB_EVENT_END_REASON_NATURAL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); props[PROP_FEEDBACK_PROFILE] = g_param_spec_string ( "feedback-profile", "Feedback profile", "Feedback profile to use for this event", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); g_object_class_install_properties (object_class, PROP_LAST_PROP, props); signals[SIGNAL_FEEDBACK_ENDED] = g_signal_new ("feedback-ended", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); } static void lfb_event_init (LfbEvent *self) { self->timeout = -1; self->state = LFB_EVENT_STATE_NONE; self->end_reason = LFB_EVENT_END_REASON_NATURAL; } LfbEvent * lfb_event_new (const char *event) { return g_object_new (LFB_TYPE_EVENT, "event", event, NULL); } /* mock libfeedback functions */ static gboolean emit_ended (LfbEvent *self) { g_assert (LFB_IS_EVENT (self)); g_signal_emit (self, signals[SIGNAL_FEEDBACK_ENDED], 0); return G_SOURCE_REMOVE; } static gboolean on_mock_timeout (gpointer user_data) { GTask *task; GCancellable *cancellable; if (!G_IS_TASK (user_data)) return G_SOURCE_REMOVE; task = G_TASK (user_data); cancellable = g_task_get_cancellable (task); if (!g_cancellable_is_cancelled (cancellable)) { LfbEvent *event = g_task_get_source_object (task); LfbEventState state = GPOINTER_TO_INT (g_task_get_task_data (task)); g_task_return_boolean (task, TRUE); if (state == LFB_EVENT_STATE_ENDED) g_idle_add (G_SOURCE_FUNC (emit_ended), event); } return G_SOURCE_REMOVE; } static gboolean on_check_task_cancelled (gpointer user_data) { GTask *task; LfbEvent *event; LfbEventState state; if (!G_IS_TASK (user_data)) return G_SOURCE_REMOVE; task = G_TASK (user_data); event = g_task_get_source_object (task); state = GPOINTER_TO_INT (g_task_get_task_data (task)); lfb_event_set_state (event, success ? state : LFB_EVENT_STATE_ERRORED); if (g_task_get_completed (task) || g_task_return_error_if_cancelled (task)) { g_object_unref (task); return G_SOURCE_REMOVE; } return G_SOURCE_CONTINUE; } void lfb_event_trigger_feedback_async (LfbEvent *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; g_return_if_fail (LFB_IS_EVENT (self)); task = g_task_new (self, cancellable, callback, user_data); g_task_set_task_data (task, GINT_TO_POINTER (LFB_EVENT_STATE_RUNNING), NULL); g_timeout_add (interval_timeout_ms, on_mock_timeout, task); g_idle_add (G_SOURCE_FUNC (on_check_task_cancelled), task); } gboolean lfb_event_trigger_feedback_finish (LfbEvent *self, GAsyncResult *res, GError **error) { g_return_val_if_fail (g_task_is_valid (res, self), FALSE); return success; } gboolean lfb_event_end_feedback_finish (LfbEvent *self, GAsyncResult *res, GError **error) { g_return_val_if_fail (g_task_is_valid (res, self), FALSE); if (success) g_idle_add (G_SOURCE_FUNC (emit_ended), self); return success; } void lfb_event_end_feedback_async (LfbEvent *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; g_return_if_fail (LFB_IS_EVENT (self)); task = g_task_new (self, cancellable, callback, user_data); g_task_set_task_data (task, GINT_TO_POINTER (LFB_EVENT_STATE_ENDED), NULL); g_timeout_add (interval_timeout_ms, on_mock_timeout, task); g_idle_add (G_SOURCE_FUNC (on_check_task_cancelled), task); } void lfb_event_set_timeout (LfbEvent *self, gint timeout) { g_return_if_fail (LFB_IS_EVENT (self)); if (self->timeout == timeout) return; self->timeout = timeout; g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TIMEOUT]); } gint lfb_event_get_timeout (LfbEvent *self) { g_return_val_if_fail (LFB_IS_EVENT (self), -1); return self->timeout; } LfbEventState lfb_event_get_state (LfbEvent *self) { g_return_val_if_fail (LFB_IS_EVENT (self), LFB_EVENT_STATE_NONE); return self->state; } void lfb_event_set_feedback_profile (LfbEvent *self, const gchar *profile) { g_return_if_fail (LFB_IS_EVENT (self)); if (!g_strcmp0 (self->profile, profile)) return; g_free (self->profile); self->profile = g_strdup (profile); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_FEEDBACK_PROFILE]); } char * lfb_event_get_feedback_profile (LfbEvent *self) { g_return_val_if_fail (LFB_IS_EVENT (self), NULL); return g_strdup (self->profile); }