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