Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-3.0-or-later
2 : : // SPDX-FileCopyrightText: Andy Holmes <andrew.g.r.holmes@gmail.com>
3 : :
4 : : #define G_LOG_DOMAIN "valent-window"
5 : :
6 : : #include "config.h"
7 : :
8 : : #include <adwaita.h>
9 : : #include <glib/gi18n-lib.h>
10 : : #include <gtk/gtk.h>
11 : : #include <libportal/portal.h>
12 : : #include <libvalent-core.h>
13 : : #include <libvalent-device.h>
14 : :
15 : : #include "valent-version-vcs.h"
16 : :
17 : : #include "valent-application-credits.h"
18 : : #include "valent-device-page.h"
19 : : #include "valent-preferences-dialog.h"
20 : : #include "valent-ui-utils-private.h"
21 : : #include "valent-window.h"
22 : :
23 : :
24 : : struct _ValentWindow
25 : : {
26 : : AdwApplicationWindow parent_instance;
27 : : ValentDeviceManager *manager;
28 : :
29 : : AdwAnimation *scan;
30 : : AdwAnimation *fade;
31 : :
32 : : /* template */
33 : : AdwNavigationView *view;
34 : : GtkProgressBar *progress_bar;
35 : : GtkListBox *device_list;
36 : : AdwDialog *preferences;
37 : : };
38 : :
39 [ + + + - ]: 85 : G_DEFINE_FINAL_TYPE (ValentWindow, valent_window, ADW_TYPE_APPLICATION_WINDOW)
40 : :
41 : : enum {
42 : : PROP_0,
43 : : PROP_DEVICE_MANAGER,
44 : : N_PROPERTIES
45 : : };
46 : :
47 : : static GParamSpec *properties[N_PROPERTIES] = { NULL, };
48 : :
49 : :
50 : : static JsonNode *
51 : 0 : valent_get_debug_info (void)
52 : : {
53 : 0 : g_autoptr (JsonBuilder) builder = NULL;
54 [ # # ]: 0 : g_autofree char *os_name = NULL;
55 : 0 : const char *desktop = NULL;
56 : 0 : const char *session = NULL;
57 : 0 : const char *environment = NULL;
58 : 0 : PeasEngine *engine = NULL;
59 : 0 : unsigned int n_plugins = 0;
60 : :
61 : 0 : os_name = g_get_os_info (G_OS_INFO_KEY_PRETTY_NAME);
62 : 0 : desktop = g_getenv ("XDG_CURRENT_DESKTOP");
63 : 0 : session = g_getenv ("XDG_SESSION_TYPE");
64 [ # # # # ]: 0 : environment = xdp_portal_running_under_flatpak () ? "flatpak" :
65 : 0 : (xdp_portal_running_under_snap (NULL) ? "snap" : "host");
66 : :
67 : 0 : builder = json_builder_new ();
68 : 0 : json_builder_begin_object (builder);
69 : :
70 : : /* Application */
71 : 0 : json_builder_set_member_name (builder, "application");
72 : 0 : json_builder_begin_object (builder);
73 : 0 : json_builder_set_member_name (builder, "id");
74 : 0 : json_builder_add_string_value (builder, APPLICATION_ID);
75 : 0 : json_builder_set_member_name (builder, "version");
76 : 0 : json_builder_add_string_value (builder, VALENT_VERSION);
77 : 0 : json_builder_set_member_name (builder, "commit");
78 : 0 : json_builder_add_string_value (builder, VALENT_VCS_TAG);
79 : 0 : json_builder_end_object (builder);
80 : :
81 : : /* Runtime */
82 : 0 : json_builder_set_member_name (builder, "runtime");
83 : 0 : json_builder_begin_object (builder);
84 : 0 : json_builder_set_member_name (builder, "os");
85 [ # # ]: 0 : json_builder_add_string_value (builder, os_name != NULL ? os_name : "unknown");
86 : 0 : json_builder_set_member_name (builder, "desktop");
87 [ # # ]: 0 : json_builder_add_string_value (builder, desktop != NULL ? desktop : "unknown");
88 : 0 : json_builder_set_member_name (builder, "session");
89 [ # # ]: 0 : json_builder_add_string_value (builder, session != NULL ? session : "unknown");
90 : 0 : json_builder_set_member_name (builder, "environment");
91 : 0 : json_builder_add_string_value (builder, environment);
92 : 0 : json_builder_end_object (builder);
93 : :
94 : : /* Plugins */
95 : 0 : json_builder_set_member_name (builder, "plugins");
96 : 0 : json_builder_begin_object (builder);
97 : :
98 : 0 : engine = valent_get_plugin_engine ();
99 : 0 : n_plugins = g_list_model_get_n_items (G_LIST_MODEL (engine));
100 : :
101 [ # # ]: 0 : for (unsigned int i = 0; i < n_plugins; i++)
102 : : {
103 : 0 : g_autoptr (PeasPluginInfo) info = NULL;
104 : 0 : const char *name = NULL;
105 : 0 : gboolean loaded = FALSE;
106 : :
107 : 0 : info = g_list_model_get_item (G_LIST_MODEL (engine), i);
108 : 0 : name = peas_plugin_info_get_module_name (info);
109 : 0 : loaded = peas_plugin_info_is_loaded (info);
110 : :
111 : 0 : json_builder_set_member_name (builder, name);
112 [ # # ]: 0 : json_builder_add_boolean_value (builder, loaded);
113 : : }
114 : 0 : json_builder_end_object (builder);
115 : :
116 : 0 : json_builder_end_object (builder);
117 : :
118 : 0 : return json_builder_get_root (builder);
119 : : }
120 : :
121 : : /*
122 : : * AdwAnimation Callbacks
123 : : */
124 : : static gboolean
125 : 0 : refresh_cb (gpointer data)
126 : : {
127 [ # # ]: 0 : g_assert (VALENT_IS_DEVICE_MANAGER (data));
128 : :
129 : 0 : valent_device_manager_refresh (VALENT_DEVICE_MANAGER (data));
130 : :
131 : 0 : return G_SOURCE_CONTINUE;
132 : : }
133 : :
134 : : static void
135 : 0 : on_animation_state_changed (AdwAnimation *animation,
136 : : GParamSpec *pspec,
137 : : ValentWindow *self)
138 : : {
139 : 0 : AdwAnimationState state = adw_animation_get_state (animation);
140 : 0 : static unsigned int refresh_id = 0;
141 : :
142 [ # # ]: 0 : g_clear_handle_id (&refresh_id, g_source_remove);
143 : :
144 [ # # # ]: 0 : switch (state)
145 : : {
146 : 0 : case ADW_ANIMATION_PLAYING:
147 [ # # ]: 0 : if (self->scan == animation)
148 : : {
149 : 0 : valent_device_manager_refresh (self->manager);
150 : 0 : refresh_id = g_timeout_add_seconds_full (G_PRIORITY_DEFAULT,
151 : : 2,
152 : : refresh_cb,
153 : 0 : g_object_ref (self->manager),
154 : : g_object_unref);
155 : 0 : gtk_widget_action_set_enabled (GTK_WIDGET (self), "win.refresh", FALSE);
156 : : }
157 : : break;
158 : :
159 : 0 : case ADW_ANIMATION_FINISHED:
160 [ # # ]: 0 : if (self->scan == animation)
161 : : {
162 : 0 : adw_animation_play (self->fade);
163 : 0 : gtk_widget_action_set_enabled (GTK_WIDGET (self), "win.refresh", TRUE);
164 : : }
165 : : break;
166 : :
167 : 0 : default:
168 : 0 : gtk_progress_bar_set_fraction (self->progress_bar, 0.0);
169 : 0 : gtk_widget_set_opacity (GTK_WIDGET (self->progress_bar), 1.0);
170 : 0 : gtk_widget_action_set_enabled (GTK_WIDGET (self), "win.refresh", TRUE);
171 : 0 : break;
172 : : }
173 : 0 : }
174 : :
175 : : /*
176 : : * ValentDevice Callbacks
177 : : */
178 : : static void
179 : 6 : on_device_changed (ValentDevice *device,
180 : : GParamSpec *pspec,
181 : : GtkWidget *status)
182 : : {
183 : 6 : ValentDeviceState state;
184 : :
185 [ + - ]: 6 : g_assert (VALENT_IS_DEVICE (device));
186 [ + - + - : 6 : g_assert (GTK_IS_LABEL (status));
- + - - ]
187 : :
188 : 6 : state = valent_device_get_state (device);
189 : :
190 [ + + ]: 6 : if ((state & VALENT_DEVICE_STATE_PAIRED) == 0)
191 : : {
192 : 2 : gtk_label_set_label (GTK_LABEL (status), _("Unpaired"));
193 : 2 : gtk_widget_remove_css_class (status, "dim-label");
194 : : }
195 [ + + ]: 4 : else if ((state & VALENT_DEVICE_STATE_CONNECTED) == 0)
196 : : {
197 : 2 : gtk_label_set_label (GTK_LABEL (status), _("Disconnected"));
198 : 2 : gtk_widget_add_css_class (status, "dim-label");
199 : : }
200 : : else
201 : : {
202 : 2 : gtk_label_set_label (GTK_LABEL (status), _("Connected"));
203 : 2 : gtk_widget_remove_css_class (status, "dim-label");
204 : : }
205 : 6 : }
206 : :
207 : : static GtkWidget *
208 : 2 : valent_window_create_row_func (gpointer item,
209 : : gpointer user_data)
210 : : {
211 : 2 : ValentWindow *self = VALENT_WINDOW (user_data);
212 : 2 : ValentDevice *device = VALENT_DEVICE (item);
213 : 2 : const char *device_id;
214 : 2 : const char *icon_name;
215 : 2 : GtkWidget *row;
216 : 2 : GtkWidget *box;
217 : 2 : GtkWidget *icon;
218 : 2 : GtkWidget *status;
219 : 2 : GtkWidget *arrow;
220 : :
221 [ + - ]: 2 : g_assert (VALENT_IS_WINDOW (self));
222 [ - + ]: 2 : g_assert (VALENT_IS_DEVICE (item));
223 : :
224 : : /* A device was added, so stop the refresh */
225 [ - + ]: 2 : if (self->scan != NULL)
226 : 0 : adw_animation_skip (self->scan);
227 : :
228 : 2 : device_id = valent_device_get_id (device);
229 : 2 : icon_name = valent_device_get_icon_name (device);
230 : :
231 : : /* Row */
232 : 2 : row = g_object_new (ADW_TYPE_ACTION_ROW,
233 : : "action-name", "win.page",
234 : : "action-target", g_variant_new_string (device_id),
235 : : "activatable", TRUE,
236 : : "selectable", FALSE,
237 : : NULL);
238 : :
239 : 2 : icon = g_object_new (GTK_TYPE_IMAGE,
240 : : "accessible-role", GTK_ACCESSIBLE_ROLE_PRESENTATION,
241 : : "icon-name", icon_name,
242 : : NULL);
243 : 2 : adw_action_row_add_prefix (ADW_ACTION_ROW (row), icon);
244 : :
245 : 2 : box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
246 : 2 : adw_action_row_add_suffix (ADW_ACTION_ROW (row), box);
247 : :
248 : 2 : status = g_object_new (GTK_TYPE_LABEL, NULL);
249 : 2 : gtk_box_append (GTK_BOX (box), status);
250 : :
251 : 4 : arrow = g_object_new (GTK_TYPE_IMAGE,
252 : : "accessible-role", GTK_ACCESSIBLE_ROLE_PRESENTATION,
253 : 2 : "css-classes", VALENT_STRV_INIT ("dim-label"),
254 : : "icon-name", "go-next-symbolic",
255 : : NULL);
256 : 2 : gtk_box_append (GTK_BOX (box), arrow);
257 : :
258 : 2 : g_object_bind_property (device, "name",
259 : : row, "title",
260 : : G_BINDING_SYNC_CREATE);
261 : :
262 : 2 : g_signal_connect_object (device,
263 : : "notify::state",
264 : : G_CALLBACK (on_device_changed),
265 : : status, 0);
266 : 2 : on_device_changed (device, NULL, status);
267 : :
268 : 2 : gtk_accessible_update_relation (GTK_ACCESSIBLE (row),
269 : : GTK_ACCESSIBLE_RELATION_DESCRIBED_BY, status, NULL,
270 : : -1);
271 : :
272 : 2 : return row;
273 : : }
274 : :
275 : : /*
276 : : * GActions
277 : : */
278 : : static void
279 : 0 : about_action (GtkWidget *widget,
280 : : const char *action_name,
281 : : GVariant *parameter)
282 : : {
283 : 0 : GtkWindow *window = GTK_WINDOW (widget);
284 : 0 : AdwDialog *dialog = NULL;
285 : 0 : g_autoptr (JsonNode) debug_json = NULL;
286 [ # # ]: 0 : g_autofree char *debug_info = NULL;
287 : :
288 [ # # # # : 0 : g_assert (GTK_IS_WINDOW (window));
# # # # ]
289 : :
290 : 0 : debug_json = valent_get_debug_info ();
291 : 0 : debug_info = json_to_string (debug_json, TRUE);
292 : :
293 : 0 : dialog = g_object_new (ADW_TYPE_ABOUT_DIALOG,
294 : : "application-icon", APPLICATION_ID,
295 : 0 : "application-name", _("Valent"),
296 : : "copyright", "© Andy Holmes",
297 : : "issue-url", PACKAGE_BUGREPORT,
298 : : "license-type", GTK_LICENSE_GPL_3_0,
299 : : "debug-info", debug_info,
300 : : "debug-info-filename", "valent-debug.json",
301 : : "artists", valent_application_credits_artists,
302 : : "designers", valent_application_credits_designers,
303 : : "developers", valent_application_credits_developers,
304 : : "documenters", valent_application_credits_documenters,
305 : 0 : "translator-credits", _("translator-credits"),
306 : : "version", PACKAGE_VERSION,
307 : : "website", PACKAGE_URL,
308 : : NULL);
309 : 0 : adw_about_dialog_add_acknowledgement_section (ADW_ABOUT_DIALOG (dialog),
310 : 0 : _("Sponsors"),
311 : : valent_application_credits_sponsors);
312 : :
313 : 0 : adw_dialog_present (dialog, GTK_WIDGET (window));
314 : 0 : }
315 : :
316 : : static void
317 : 4 : page_action (GtkWidget *widget,
318 : : const char *action_name,
319 : : GVariant *parameter)
320 : : {
321 : 4 : ValentWindow *self = VALENT_WINDOW (widget);
322 : 4 : unsigned int n_devices = 0;
323 : 4 : const char *tag;
324 : :
325 [ + - ]: 4 : g_assert (VALENT_IS_WINDOW (self));
326 : :
327 : 4 : tag = g_variant_get_string (parameter, NULL);
328 : :
329 [ + - + + ]: 4 : if (*tag == '\0' || g_strcmp0 (tag, "main") == 0)
330 : : {
331 : 2 : adw_navigation_view_pop (self->view);
332 : 2 : return;
333 : : }
334 : :
335 : :
336 [ - + ]: 2 : g_clear_pointer (&self->preferences, adw_dialog_force_close);
337 : :
338 : 2 : n_devices = g_list_model_get_n_items (G_LIST_MODEL (self->manager));
339 [ + - ]: 2 : for (unsigned int i = 0; i < n_devices; i++)
340 : : {
341 : 0 : g_autoptr (ValentDevice) device = NULL;
342 : 2 : AdwNavigationPage *page;
343 : :
344 : 2 : device = g_list_model_get_item (G_LIST_MODEL (self->manager), i);
345 : :
346 [ + - ]: 2 : if (g_strcmp0 (valent_device_get_id (device), tag) == 0)
347 : : {
348 : 2 : page = g_object_new (VALENT_TYPE_DEVICE_PAGE,
349 : : "device", device,
350 : : NULL);
351 : 2 : adw_navigation_view_push (self->view, page);
352 [ + - ]: 2 : break;
353 : : }
354 : : }
355 : : }
356 : :
357 : : static void
358 : 2 : preferences_action (GtkWidget *widget,
359 : : const char *action_name,
360 : : GVariant *parameter)
361 : : {
362 : 2 : ValentWindow *self = VALENT_WINDOW (widget);
363 : :
364 [ + - ]: 2 : g_assert (VALENT_IS_WINDOW (self));
365 : :
366 [ + + ]: 2 : if (self->preferences == NULL)
367 : : {
368 : 1 : self->preferences = g_object_new (VALENT_TYPE_PREFERENCES_DIALOG, NULL);
369 : 1 : g_object_add_weak_pointer (G_OBJECT (self->preferences),
370 : 1 : (gpointer)&self->preferences);
371 : : }
372 : :
373 : 2 : adw_dialog_present (ADW_DIALOG (self->preferences), GTK_WIDGET (self));
374 : 2 : }
375 : :
376 : : static void
377 : 2 : refresh_action (GtkWidget *widget,
378 : : const char *action_name,
379 : : GVariant *parameter)
380 : : {
381 : 2 : ValentWindow *self = VALENT_WINDOW (widget);
382 : :
383 [ + - ]: 2 : g_assert (VALENT_IS_WINDOW (self));
384 : :
385 [ + - ]: 2 : if (!adw_get_enable_animations (widget))
386 : : {
387 : 2 : valent_device_manager_refresh (self->manager);
388 : 2 : return;
389 : : }
390 : :
391 [ # # # # ]: 0 : if (self->scan == NULL && self->fade == NULL)
392 : 0 : {
393 : 0 : AdwAnimationTarget *target = NULL;
394 : :
395 : 0 : target = adw_property_animation_target_new (G_OBJECT (self->progress_bar),
396 : : "fraction");
397 : 0 : self->scan = adw_timed_animation_new (widget, 0.0, 1.0, 5000, target);
398 : 0 : g_signal_connect_object (self->scan,
399 : : "notify::state",
400 : : G_CALLBACK (on_animation_state_changed),
401 : : self, 0);
402 : :
403 : 0 : target = adw_property_animation_target_new (G_OBJECT (self->progress_bar),
404 : : "opacity");
405 : 0 : self->fade = adw_timed_animation_new (widget, 1.0, 0.0, 500, target);
406 : 0 : g_signal_connect_object (self->fade,
407 : : "notify::state",
408 : : G_CALLBACK (on_animation_state_changed),
409 : : self, 0);
410 : : }
411 : : else
412 : : {
413 : 0 : adw_animation_reset (self->fade);
414 : 0 : adw_animation_reset (self->scan);
415 : : }
416 : :
417 : 0 : adw_animation_play (self->scan);
418 : : }
419 : :
420 : : /*
421 : : * GObject
422 : : */
423 : : static void
424 : 4 : valent_window_constructed (GObject *object)
425 : : {
426 : 4 : ValentWindow *self = VALENT_WINDOW (object);
427 : :
428 [ + - ]: 4 : g_assert (self->manager != NULL);
429 : :
430 : 4 : gtk_list_box_bind_model (self->device_list,
431 : : G_LIST_MODEL (self->manager),
432 : : valent_window_create_row_func,
433 : : self, NULL);
434 : :
435 : 4 : G_OBJECT_CLASS (valent_window_parent_class)->constructed (object);
436 : 4 : }
437 : :
438 : : static void
439 : 4 : valent_window_dispose (GObject *object)
440 : : {
441 : 4 : ValentWindow *self = VALENT_WINDOW (object);
442 : :
443 [ - + ]: 4 : if (self->scan != NULL)
444 : 0 : adw_animation_reset (self->scan);
445 [ - + ]: 4 : g_clear_object (&self->scan);
446 : :
447 [ - + ]: 4 : if (self->fade != NULL)
448 : 0 : adw_animation_reset (self->fade);
449 [ - + ]: 4 : g_clear_object (&self->fade);
450 : :
451 [ + + ]: 4 : g_clear_pointer (&self->preferences, adw_dialog_force_close);
452 : 4 : gtk_widget_dispose_template (GTK_WIDGET (object), VALENT_TYPE_WINDOW);
453 : :
454 : 4 : G_OBJECT_CLASS (valent_window_parent_class)->dispose (object);
455 : 4 : }
456 : :
457 : : static void
458 : 4 : valent_window_finalize (GObject *object)
459 : : {
460 : 4 : ValentWindow *self = VALENT_WINDOW (object);
461 : :
462 [ + - ]: 4 : g_clear_object (&self->manager);
463 : :
464 : 4 : G_OBJECT_CLASS (valent_window_parent_class)->finalize (object);
465 : 4 : }
466 : :
467 : : static void
468 : 1 : valent_window_get_property (GObject *object,
469 : : guint prop_id,
470 : : GValue *value,
471 : : GParamSpec *pspec)
472 : : {
473 : 1 : ValentWindow *self = VALENT_WINDOW (object);
474 : :
475 [ + - ]: 1 : switch (prop_id)
476 : : {
477 : 1 : case PROP_DEVICE_MANAGER:
478 : 1 : g_value_set_object (value, self->manager);
479 : 1 : break;
480 : :
481 : 0 : default:
482 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
483 : : }
484 : 1 : }
485 : :
486 : : static void
487 : 4 : valent_window_set_property (GObject *object,
488 : : guint prop_id,
489 : : const GValue *value,
490 : : GParamSpec *pspec)
491 : : {
492 : 4 : ValentWindow *self = VALENT_WINDOW (object);
493 : :
494 [ + - ]: 4 : switch (prop_id)
495 : : {
496 : 4 : case PROP_DEVICE_MANAGER:
497 : 4 : self->manager = g_value_dup_object (value);
498 : 4 : break;
499 : :
500 : 0 : default:
501 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
502 : : }
503 : 4 : }
504 : :
505 : : static void
506 : 1 : valent_window_class_init (ValentWindowClass *klass)
507 : : {
508 : 1 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
509 : 1 : GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
510 : :
511 : 1 : object_class->constructed = valent_window_constructed;
512 : 1 : object_class->dispose = valent_window_dispose;
513 : 1 : object_class->finalize = valent_window_finalize;
514 : 1 : object_class->get_property = valent_window_get_property;
515 : 1 : object_class->set_property = valent_window_set_property;
516 : :
517 : 1 : gtk_widget_class_set_template_from_resource (widget_class, "/ca/andyholmes/Valent/ui/valent-window.ui");
518 : 1 : gtk_widget_class_bind_template_child (widget_class, ValentWindow, view);
519 : 1 : gtk_widget_class_bind_template_child (widget_class, ValentWindow, progress_bar);
520 : 1 : gtk_widget_class_bind_template_child (widget_class, ValentWindow, device_list);
521 : :
522 : 1 : gtk_widget_class_install_action (widget_class, "win.about", NULL, about_action);
523 : 1 : gtk_widget_class_install_action (widget_class, "win.page", "s", page_action);
524 : 1 : gtk_widget_class_install_action (widget_class, "win.preferences", NULL, preferences_action);
525 : 1 : gtk_widget_class_install_action (widget_class, "win.refresh", NULL, refresh_action);
526 : :
527 : : /**
528 : : * ValentWindow:device-manager:
529 : : *
530 : : * The [class@Valent.DeviceManager] that the window represents.
531 : : */
532 : 2 : properties [PROP_DEVICE_MANAGER] =
533 : 1 : g_param_spec_object ("device-manager", NULL, NULL,
534 : : VALENT_TYPE_DEVICE_MANAGER,
535 : : (G_PARAM_READWRITE |
536 : : G_PARAM_CONSTRUCT_ONLY |
537 : : G_PARAM_EXPLICIT_NOTIFY |
538 : : G_PARAM_STATIC_STRINGS));
539 : :
540 : 1 : g_object_class_install_properties (object_class, N_PROPERTIES, properties);
541 : 1 : }
542 : :
543 : : static void
544 : 4 : valent_window_init (ValentWindow *self)
545 : : {
546 : 4 : gtk_widget_init_template (GTK_WIDGET (self));
547 : 4 : }
548 : :
|