1 /*
2  * Copyright (C) 2020 Purism SPC
3  * SPDX-License-Identifier: GPL-3.0+
4  * Author: Guido Günther <agx@sigxcpu.org>
5  */
6 
7 #include "libfeedback.h"
8 #include "lfb-priv.h"
9 
10 #include <gio/gio.h>
11 
12 /**
13  * SECTION:lfb-event
14  * @Short_description: An event triggering feedback to the user
15  * @Title: LfbEvent
16  *
17  * #LfbEvent represents an event that should trigger
18  * audio, haptic and/or visual feedback to the user by triggering
19  * feedback on a feedback daemon. Valid event names are specified
20  * in the
21  * [Event naming specification](https://source.puri.sm/Librem5/feedbackd/-/blob/master/Event-naming-spec-0.0.0.md).
22  *
23  * One event can trigger multiple feedbacks at once (e.g. audio and
24  * haptic feedback). This is determined by the feedback theme in
25  * use (which is not under the appliction's control) and the active
26  * feedback profile (see #lfb_set_feedback_profile()).
27  *
28  * After initializing the library via #lfb_init() feedback can be
29  * triggered like:
30  *
31  * |[
32  *    g_autoptr (GError) err = NULL;
33  *    LpfEvent *event = lfb_event_new ("message-new-instant");
34  *    lfb_event_set_timeout (event, 0);
35  *    if (!lfb_event_trigger_feedback (event, &err))
36  *      g_warning ("Failed to trigger feedback: %s", err->message);
37  * ]|
38  *
39  * When all feedback for this event has ended the #LfbEvent::feedback-ended
40  * signal is emitted. If you want to end the feedback ahead of time use
41  * #lfb_event_end_feedback ():
42  *
43  * |[
44  *    if (!lfb_event_end_feedback (event, &err))
45  *      g_warning ("Failed to end feedback: %s", err->message);
46  * ]|
47  *
48  * Since these methods involve DBus calls there are asynchronous variants
49  * available, e.g. #lfb_event_trigger_feedback_async():
50  *
51  * |[
52  *    static void
53  *    on_feedback_triggered (LfbEvent      *event,
54  *                           GAsyncResult  *res,
55  *                           gpointer      unused)
56  *    {
57  *       g_autoptr (GError) err = NULL;
58  *       if (!lfb_event_trigger_feedback_finish (event, res, &err)) {
59  *          g_warning ("Failed to trigger feedback for %s: %s",
60  *                     lfb_event_get_event (event), err->message);
61  *       }
62  *    }
63  *
64  *    static void
65  *    my_function ()
66  *    {
67  *      LfbEvent *event = lfb_event_new ("message-new-instant");
68  *      lfb_event_trigger_feedback_async (event, NULL,
69  *                                       (GAsyncReadyCallback)on_feedback_triggered,
70  *                                       NULL);
71  *    }
72  * ]|
73  */
74 
75 enum {
76   PROP_0,
77   PROP_EVENT,
78   PROP_TIMEOUT,
79   PROP_STATE,
80   PROP_END_REASON,
81   PROP_FEEDBACK_PROFILE,
82   PROP_LAST_PROP,
83 };
84 static GParamSpec *props[PROP_LAST_PROP];
85 
86 enum {
87       SIGNAL_FEEDBACK_ENDED,
88       N_SIGNALS,
89 };
90 static guint signals[N_SIGNALS];
91 
92 typedef struct _LfbEvent {
93   GObject        parent;
94 
95   char          *event;
96   gint           timeout;
97   gchar         *profile;
98 
99   guint          id;
100   LfbEventState  state;
101   gint           end_reason;
102   gulong         handler_id;
103 } LfbEvent;
104 
105 G_DEFINE_TYPE (LfbEvent, lfb_event, G_TYPE_OBJECT);
106 
107 typedef struct _LpfAsyncData {
108   LfbEvent *event;
109   GTask    *task;
110 } LpfAsyncData;
111 
112 static void
lfb_event_set_state(LfbEvent * self,LfbEventState state)113 lfb_event_set_state (LfbEvent *self, LfbEventState state)
114 {
115   if (self->state == state)
116     return;
117 
118   self->state = state;
119   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_STATE]);
120 }
121 
122 static void
lfb_event_set_end_reason(LfbEvent * self,LfbEventEndReason reason)123 lfb_event_set_end_reason (LfbEvent *self, LfbEventEndReason reason)
124 {
125   if (self->end_reason == reason)
126     return;
127 
128   self->end_reason = reason;
129   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_END_REASON]);
130 }
131 
132 static GVariant *
build_hints(LfbEvent * self)133 build_hints (LfbEvent *self)
134 {
135   GVariantBuilder hints_builder;
136 
137   g_variant_builder_init (&hints_builder, G_VARIANT_TYPE ("a{sv}"));
138   if (self->profile)
139     g_variant_builder_add (&hints_builder, "{sv}", "profile", g_variant_new_string (self->profile));
140   return g_variant_builder_end (&hints_builder);
141 }
142 
143 static void
on_trigger_feedback_finished(LfbGdbusFeedback * proxy,GAsyncResult * res,LpfAsyncData * data)144 on_trigger_feedback_finished (LfbGdbusFeedback *proxy,
145                               GAsyncResult     *res,
146                               LpfAsyncData     *data)
147 
148 {
149   GTask *task = data->task;
150   LfbEvent *self = data->event;
151   g_autoptr (GError) err = NULL;
152   gboolean success;
153   LfbEventState state;
154 
155   g_return_if_fail (G_IS_TASK (task));
156   g_return_if_fail (LFB_GDBUS_IS_FEEDBACK (proxy));
157   g_return_if_fail (LFB_IS_EVENT (self));
158 
159   success = lfb_gdbus_feedback_call_trigger_feedback_finish (proxy,
160                                                              &self->id,
161                                                              res,
162                                                              &err);
163   if (!success) {
164     g_task_return_error (task, g_steal_pointer (&err));
165     state = LFB_EVENT_STATE_ERRORED;
166   } else {
167     g_task_return_boolean (task, TRUE);
168     state = LFB_EVENT_STATE_RUNNING;
169     _lfb_active_add_id (self->id);
170   }
171 
172   lfb_event_set_state (self, state);
173   g_free (data);
174   g_object_unref (task);
175   g_object_unref (self);
176 }
177 
178 static void
on_end_feedback_finished(LfbGdbusFeedback * proxy,GAsyncResult * res,LpfAsyncData * data)179 on_end_feedback_finished (LfbGdbusFeedback *proxy,
180                           GAsyncResult     *res,
181                           LpfAsyncData     *data)
182 
183 {
184   GTask *task = data->task;
185   LfbEvent *self = data->event;
186   g_autoptr (GError) err = NULL;
187   gboolean success;
188 
189   g_return_if_fail (G_IS_TASK (task));
190   g_return_if_fail (LFB_GDBUS_IS_FEEDBACK (proxy));
191   g_return_if_fail (LFB_IS_EVENT (self));
192 
193   success = lfb_gdbus_feedback_call_end_feedback_finish (proxy,
194 							 res,
195 							 &err);
196   if (!success) {
197     g_task_return_error (task, g_steal_pointer (&err));
198   } else
199     g_task_return_boolean (task, TRUE);
200 
201   g_free (data);
202   g_object_unref (task);
203   g_object_unref (self);
204 }
205 
206 static void
lfb_event_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)207 lfb_event_set_property (GObject      *object,
208                         guint         property_id,
209                         const GValue *value,
210                         GParamSpec   *pspec)
211 {
212   LfbEvent *self = LFB_EVENT (object);
213 
214   switch (property_id) {
215   case PROP_EVENT:
216     g_free (self->event);
217     self->event = g_value_dup_string (value);
218     g_object_notify_by_pspec (G_OBJECT (self), props[PROP_EVENT]);
219     break;
220   case PROP_TIMEOUT:
221     lfb_event_set_timeout (self, g_value_get_int (value));
222     break;
223   case PROP_FEEDBACK_PROFILE:
224     lfb_event_set_feedback_profile (self, g_value_get_string (value));
225     break;
226   default:
227     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
228     break;
229   }
230 }
231 
232 
233 static void
lfb_event_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)234 lfb_event_get_property (GObject    *object,
235                         guint       property_id,
236                         GValue     *value,
237                         GParamSpec *pspec)
238 {
239   LfbEvent *self = LFB_EVENT (object);
240 
241   switch (property_id) {
242   case PROP_EVENT:
243     g_value_set_string (value, self->event);
244     break;
245   case PROP_TIMEOUT:
246     g_value_set_int (value, self->timeout);
247     break;
248   case PROP_FEEDBACK_PROFILE:
249     g_value_set_string (value, self->profile);
250     break;
251   default:
252     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
253     break;
254   }
255 }
256 
257 static void
lfb_event_finalize(GObject * object)258 lfb_event_finalize (GObject *object)
259 {
260   LfbEvent *self = LFB_EVENT (object);
261 
262   /* Signal handler is disconnected automatically due to g_signal_connect_object */
263   self->handler_id = 0;
264 
265   g_clear_pointer (&self->event, g_free);
266   g_clear_pointer (&self->profile, g_free);
267 
268   G_OBJECT_CLASS (lfb_event_parent_class)->finalize (object);
269 }
270 
271 static void
lfb_event_class_init(LfbEventClass * klass)272 lfb_event_class_init (LfbEventClass *klass)
273 {
274   GObjectClass *object_class = G_OBJECT_CLASS (klass);
275 
276   object_class->set_property = lfb_event_set_property;
277   object_class->get_property = lfb_event_get_property;
278 
279   object_class->finalize = lfb_event_finalize;
280 
281   /**
282    * LfbEvent:event:
283    *
284    * The type of event from the Event naming spec, e.g. 'message-new-instant'.
285    */
286   props[PROP_EVENT] =
287     g_param_spec_string (
288       "event",
289       "Event",
290       "The name of the event triggering the feedback",
291       NULL,
292       G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
293 
294   /**
295    * LfbEvent:timeout:
296    *
297    * How long feedback should be provided in seconds. The special value
298    * %-1 uses the natural length of each feedback while %0 plays each feedback
299    * in a loop until ended explicitly via e.g. #lfb_event_end_feedback().
300    */
301   props[PROP_TIMEOUT] =
302     g_param_spec_int (
303       "timeout",
304       "Timeout",
305       "When the event should timeout",
306       -1, G_MAXINT, -1,
307       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
308 
309   props[PROP_STATE] =
310     g_param_spec_enum (
311       "state",
312       "State",
313       "The event's state",
314       LFB_TYPE_EVENT_STATE,
315       LFB_EVENT_END_REASON_NATURAL,
316       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
317 
318   props[PROP_END_REASON] =
319     g_param_spec_enum (
320       "end-reason",
321       "End reason",
322       "The reason why the feedbacks ended",
323       LFB_TYPE_EVENT_END_REASON,
324       LFB_EVENT_END_REASON_NATURAL,
325       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
326 
327   /**
328    * LfbEvent:feedback-profile:
329    *
330    * The name of the feedback profile to use for this event. See
331    * #lfb_event_set_feedback_profile() for details.
332    */
333   props[PROP_FEEDBACK_PROFILE] =
334     g_param_spec_string (
335       "feedback-profile",
336       "Feedback profile",
337       "Feedback profile to use for this event",
338       NULL,
339       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
340 
341   g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
342 
343   /**
344    * LfbEvent::feedback-ended:
345    *
346    * Emitted when all feedbacks triggered by the event have ended.
347    */
348   signals[SIGNAL_FEEDBACK_ENDED] = g_signal_new ("feedback-ended",
349                                                  G_TYPE_FROM_CLASS (klass),
350                                                  G_SIGNAL_RUN_LAST, 0, NULL, NULL,
351                                                  NULL,
352                                                  G_TYPE_NONE,
353                                                  0);
354 }
355 
356 static void
lfb_event_init(LfbEvent * self)357 lfb_event_init (LfbEvent *self)
358 {
359   self->timeout = -1;
360   self->state = LFB_EVENT_STATE_NONE;
361   self->end_reason = LFB_EVENT_END_REASON_NATURAL;
362 }
363 
364 /**
365  * lfb_event_new:
366  * @event: The event's name.
367  *
368  * Creates a new #LfbEvent based on the given event
369  * name. See #LfbEvent:event for details.
370  *
371  * Returns: The #LfbEvent.
372  */
373 LfbEvent *
lfb_event_new(const char * event)374 lfb_event_new (const char *event)
375 {
376   return g_object_new (LFB_TYPE_EVENT, "event", event, NULL);
377 }
378 
379 static void
on_feedback_ended(LfbEvent * self,guint event_id,guint reason,LfbGdbusFeedback * proxy)380 on_feedback_ended (LfbEvent         *self,
381                    guint             event_id,
382                    guint             reason,
383                    LfbGdbusFeedback *proxy)
384 {
385   g_return_if_fail (LFB_IS_EVENT (self));
386   g_return_if_fail (LFB_GDBUS_IS_FEEDBACK (proxy));
387 
388   if (event_id != self->id)
389     return;
390 
391   lfb_event_set_end_reason (self, reason);
392   lfb_event_set_state (self, LFB_EVENT_STATE_ENDED);
393   g_signal_emit (self, signals[SIGNAL_FEEDBACK_ENDED], 0);
394   _lfb_active_remove_id (self->id);
395   self->id = 0;
396   g_signal_handler_disconnect (proxy, self->handler_id);
397   self->handler_id = 0;
398 }
399 
400 /**
401  * lfb_event_trigger_feedback:
402  * @self: The event to trigger feedback for.
403  * @error: The returned error information.
404  *
405  * Tells the feedback server to provide proper feedback for the give
406  * event to the user.
407  *
408  * Returns: %TRUE if successful. On error, this will return %FALSE and set
409  *          @error.
410  */
411 gboolean
lfb_event_trigger_feedback(LfbEvent * self,GError ** error)412 lfb_event_trigger_feedback (LfbEvent *self, GError **error)
413 {
414   LfbGdbusFeedback *proxy;
415   gboolean success;
416 
417   g_return_val_if_fail (LFB_IS_EVENT (self), FALSE);
418   g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
419 
420    if (!lfb_is_initted ()) {
421      g_warning ("you must call lfb_init() before triggering events");
422      g_assert_not_reached ();
423    }
424 
425    proxy = _lfb_get_proxy ();
426    g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), FALSE);
427 
428    if (self->handler_id == 0) {
429      self->handler_id = g_signal_connect_object (proxy,
430                                                  "feedback-ended",
431                                                  G_CALLBACK (on_feedback_ended),
432                                                  self,
433                                                  G_CONNECT_SWAPPED);
434    }
435 
436    success =  lfb_gdbus_feedback_call_trigger_feedback_sync (proxy,
437                                                              lfb_get_app_id (),
438                                                              self->event,
439                                                              build_hints (self),
440                                                              self->timeout,
441                                                              &self->id,
442                                                              NULL,
443                                                              error);
444    if (success)
445      _lfb_active_add_id (self->id);
446    lfb_event_set_state (self, success ? LFB_EVENT_STATE_RUNNING : LFB_EVENT_STATE_ERRORED);
447    return success;
448 }
449 
450 /**
451  * lfb_event_trigger_feedback_async:
452  * @self: The event to trigger feedback for.
453  * @cancellable: (nullable): A #GCancellable or %NULL.
454  * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL.
455  * @user_data: User data to pass to @callback.
456  *
457  * Tells the feedback server to provide proper feedback for the give
458  * event to the user. This is the sync version of
459  * #lfb_event_trigger_feedback.
460  */
461 void
lfb_event_trigger_feedback_async(LfbEvent * self,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)462 lfb_event_trigger_feedback_async (LfbEvent            *self,
463                                   GCancellable        *cancellable,
464                                   GAsyncReadyCallback  callback,
465                                   gpointer             user_data)
466 {
467   LpfAsyncData *data;
468   LfbGdbusFeedback *proxy;
469 
470   g_return_if_fail (LFB_IS_EVENT (self));
471   if (!lfb_is_initted ()) {
472      g_warning ("you must call lfb_init() before triggering events");
473      g_assert_not_reached ();
474   }
475 
476   proxy = _lfb_get_proxy ();
477   g_return_if_fail (LFB_GDBUS_IS_FEEDBACK (proxy));
478 
479   if (self->handler_id == 0) {
480     self->handler_id = g_signal_connect_object (proxy,
481 						"feedback-ended",
482 						G_CALLBACK (on_feedback_ended),
483 						self,
484 						G_CONNECT_SWAPPED);
485   }
486 
487   data = g_new0 (LpfAsyncData, 1);
488   data->task = g_task_new (self, cancellable, callback, user_data);
489   data->event = g_object_ref (self);
490   lfb_gdbus_feedback_call_trigger_feedback (proxy,
491                                             lfb_get_app_id (),
492                                             self->event,
493                                             build_hints (self),
494                                             self->timeout,
495                                             cancellable,
496                                             (GAsyncReadyCallback)on_trigger_feedback_finished,
497                                             data);
498 }
499 
500 /**
501  * lfb_event_trigger_feedback_finish:
502  * @self: the event
503  * @res: Result object passed to the callback of
504  *  #lfb_event_trigger_feedback_async
505  * @error: Return location for error
506  *
507  * Finish an async operation started by lfb_event_trigger_feedback_async. You
508  * must call this function in the callback to free memory and receive any
509  * errors which occurred.
510  *
511  * Returns: %TRUE if triggering the feedbacks was successful
512  */
513 gboolean
lfb_event_trigger_feedback_finish(LfbEvent * self,GAsyncResult * res,GError ** error)514 lfb_event_trigger_feedback_finish (LfbEvent      *self,
515                                    GAsyncResult  *res,
516                                    GError       **error)
517 {
518   g_return_val_if_fail (g_task_is_valid (res, self), FALSE);
519 
520   return g_task_propagate_boolean (G_TASK (res), error);
521 }
522 
523 /**
524  * lfb_event_end_feedback:
525  * @self: The event to end feedback for.
526  * @error: The returned error information.
527  *
528  * Tells the feedback server to end all feedback for the given event as
529  * soon as possible.
530  *
531  * Returns: %TRUE if successful. On error, this will return %FALSE and set
532  *          @error.
533  */
534 gboolean
lfb_event_end_feedback(LfbEvent * self,GError ** error)535 lfb_event_end_feedback (LfbEvent *self, GError **error)
536 {
537   LfbGdbusFeedback *proxy;
538 
539   g_return_val_if_fail (LFB_IS_EVENT (self), FALSE);
540   g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
541 
542   if (!lfb_is_initted ()) {
543      g_warning ("you must call lfb_init() before ending events");
544      g_assert_not_reached ();
545   }
546 
547   proxy = _lfb_get_proxy ();
548   g_return_val_if_fail (LFB_GDBUS_IS_FEEDBACK (proxy), FALSE);
549   return lfb_gdbus_feedback_call_end_feedback_sync (proxy, self->id, NULL, error);
550 }
551 
552 /**
553  * lfb_event_end_feedback_finish:
554  * @self: the event
555  * @res: Result object passed to the callback of
556  *  #lfb_event_end_feedback_async
557  * @error: Return location for error
558  *
559  * Finish an async operation started by lfb_event_end_feedback_async. You
560  * must call this function in the callback to free memory and receive any
561  * errors which occurred.
562  *
563  * This does not mean that the feedbacks finished right away. Connect to the
564  * #LfbEvent::feedback-ended signal for this.
565  *
566  * Returns: %TRUE if ending the feedbacks was successful
567  */
568 gboolean
lfb_event_end_feedback_finish(LfbEvent * self,GAsyncResult * res,GError ** error)569 lfb_event_end_feedback_finish (LfbEvent      *self,
570 			       GAsyncResult  *res,
571 			       GError       **error)
572 {
573   g_return_val_if_fail (g_task_is_valid (res, self), FALSE);
574 
575   return g_task_propagate_boolean (G_TASK (res), error);
576 }
577 
578 /**
579  * lfb_event_end_feedback_async:
580  * @self: The event to end feedback for.
581  * @cancellable: (nullable): A #GCancellable or %NULL.
582  * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL.
583  * @user_data: User data to pass to @callback.
584  *
585  * Tells the feedback server to end all feedback for the given event as
586  * soon as possible.
587  */
588 void
lfb_event_end_feedback_async(LfbEvent * self,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)589 lfb_event_end_feedback_async (LfbEvent            *self,
590 			      GCancellable        *cancellable,
591 			      GAsyncReadyCallback  callback,
592 			      gpointer             user_data)
593 {
594   LpfAsyncData *data;
595   LfbGdbusFeedback *proxy;
596 
597   g_return_if_fail (LFB_IS_EVENT (self));
598   if (!lfb_is_initted ()) {
599      g_warning ("you must call lfb_init() before ending events");
600      g_assert_not_reached ();
601   }
602 
603   proxy = _lfb_get_proxy ();
604   g_return_if_fail (LFB_GDBUS_IS_FEEDBACK (proxy));
605 
606   data = g_new0 (LpfAsyncData, 1);
607   data->task = g_task_new (self, cancellable, callback, user_data);
608   data->event = g_object_ref (self);
609   lfb_gdbus_feedback_call_end_feedback (proxy,
610                                         self->id,
611                                         cancellable,
612                                         (GAsyncReadyCallback)on_end_feedback_finished,
613                                         data);
614 }
615 
616 /**
617  * lfb_event_set_timeout:
618  * @self: The event
619  * @timeout: The timeout
620  *
621  * Tells the feedback server to end feedack after #timeout seconds.
622  * The value -1 indicates to not set a timeout and let feedbacks stop
623  * on their own while 0 indicates to loop all feedbacks endlessly.
624  * They must be stopped via #lfb_event_end_feedback () in this case.
625  *
626  * It is an error to change the timeout after the feedback has been triggered
627  * via lfb_event_trigger.
628  */
629 void
lfb_event_set_timeout(LfbEvent * self,gint timeout)630 lfb_event_set_timeout (LfbEvent *self, gint timeout)
631 {
632   g_return_if_fail (LFB_IS_EVENT (self));
633 
634   if (self->timeout == timeout)
635     return;
636 
637   self->timeout = timeout;
638   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TIMEOUT]);
639 }
640 
641 /**
642  * lfb_event_get_event:
643  * @self: The event
644  *
645  * Get the event's name according to the event naming spec.
646  *
647  * Returns: The event name
648  */
649 const char *
lfb_event_get_event(LfbEvent * self)650 lfb_event_get_event (LfbEvent *self)
651 {
652   g_return_val_if_fail (LFB_IS_EVENT (self), NULL);
653   return self->event;
654 }
655 
656 /**
657  * lfb_event_get_timeout:
658  * @self: The event
659  *
660  * Get the currently set timeout.
661  *
662  * Returns: The event timeout in msecs
663  */
664 gint
lfb_event_get_timeout(LfbEvent * self)665 lfb_event_get_timeout (LfbEvent *self)
666 {
667   g_return_val_if_fail (LFB_IS_EVENT (self), -1);
668   return self->timeout;
669 }
670 
671 /**
672  * lfb_event_get_state:
673  * @self: The event
674  *
675  * Get the current event state (e.g. if triggered feeedback is
676  * currently running.
677  *
678  * Returns: The state of the feedback triggered by event.
679  */
680 LfbEventState
lfb_event_get_state(LfbEvent * self)681 lfb_event_get_state (LfbEvent *self)
682 {
683   g_return_val_if_fail (LFB_IS_EVENT (self), LFB_EVENT_STATE_NONE);
684   return self->state;
685 }
686 
687 /**
688  * lfb_event_get_end_reason:
689  * @self: The event
690  *
691  * Get the reason why the feadback ended.
692  *
693  * Returns: The reason why feedback ended.
694  */
695 LfbEventEndReason
lfb_event_get_end_reason(LfbEvent * self)696 lfb_event_get_end_reason (LfbEvent *self)
697 {
698   g_return_val_if_fail (LFB_IS_EVENT (self), LFB_EVENT_END_REASON_NATURAL);
699   return self->end_reason;
700 }
701 
702 /**
703  * lfb_event_set_feedback_profile:
704  * @self: The event
705  * @profile: The feedback profile to use
706  *
707  * Tells the feedback server to use the given feedback profile for
708  * this event when it is submitted. The server might ignore this
709  * request.  Valid profile names and their 'noisiness' are specified
710  * in the [Feedback theme specification](https://source.puri.sm/Librem5/feedbackd/-/blob/master/Feedback-theme-spec-0.0.0.md).
711  *
712  * A value of %NULL (the default) lets the server pick the profile.
713  */
714 void
lfb_event_set_feedback_profile(LfbEvent * self,const gchar * profile)715 lfb_event_set_feedback_profile (LfbEvent *self, const gchar *profile)
716 {
717   g_return_if_fail (LFB_IS_EVENT (self));
718 
719   if (!g_strcmp0 (self->profile, profile))
720     return;
721 
722   g_free (self->profile);
723   self->profile = g_strdup (profile);
724   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_FEEDBACK_PROFILE]);
725 }
726 
727 /**
728  * lfb_event_get_feedback_profile:
729  * @self: The event
730  *
731  * Returns:(transfer full): The set feedback profile to use for this
732  * event or %NULL.
733  */
734 char *
lfb_event_get_feedback_profile(LfbEvent * self)735 lfb_event_get_feedback_profile (LfbEvent *self)
736 {
737   g_return_val_if_fail (LFB_IS_EVENT (self), NULL);
738 
739   return g_strdup (self->profile);
740 }
741