gs-utils.c 21.8 KB
Newer Older
1
2
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
 *
3
 * Copyright (C) 2013-2015 Richard Hughes <richard@hughsie.com>
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 *
 * Licensed under the GNU General Public License Version 2
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include "config.h"

24
25
#include <glib/gi18n.h>
#include <gio/gdesktopappinfo.h>
26
#include <errno.h>
27

28
29
30
31
#ifdef HAVE_POLKIT
#include <polkit/polkit.h>
#endif

32
33
34
35
#ifdef HAVE_FIRMWARE
#include <fwupd.h>
#endif

36
#include "gs-app.h"
37
#include "gs-utils.h"
38
#include "gs-plugin.h"
39
40
41
42
43
44

#define SPINNER_DELAY 500

static gboolean
fade_in (gpointer data)
{
45
46
	GtkWidget *spinner = data;
	gdouble opacity;
47

48
49
50
	opacity = gtk_widget_get_opacity (spinner);
	opacity = opacity + 0.1;
	gtk_widget_set_opacity (spinner, opacity);
51

52
53
	if (opacity >= 1.0) {
		g_object_steal_data (G_OBJECT (spinner), "fade-timeout");
54
		return G_SOURCE_REMOVE;
55
	}
56
	return G_SOURCE_CONTINUE;
57
58
59
60
61
}

static void
remove_source (gpointer data)
{
62
	g_source_remove (GPOINTER_TO_UINT (data));
63
64
65
66
67
}

static gboolean
start_spinning (gpointer data)
{
68
69
	GtkWidget *spinner = data;
	guint id;
70

71
72
73
74
75
	gtk_widget_set_opacity (spinner, 0);
	gtk_spinner_start (GTK_SPINNER (spinner));
	id = g_timeout_add (100, fade_in, spinner);
	g_object_set_data_full (G_OBJECT (spinner), "fade-timeout",
				GUINT_TO_POINTER (id), remove_source);
76

77
78
	/* don't try to remove this source in the future */
	g_object_steal_data (G_OBJECT (spinner), "start-timeout");
79
	return G_SOURCE_REMOVE;
80
81
82
83
84
}

void
gs_stop_spinner (GtkSpinner *spinner)
{
85
	g_object_set_data (G_OBJECT (spinner), "start-timeout", NULL);
86
	gtk_spinner_stop (spinner);
87
88
89
90
91
}

void
gs_start_spinner (GtkSpinner *spinner)
{
92
	gboolean active;
93
	guint id;
94

95
96
97
98
99
	/* Don't do anything if it's already spinning */
	g_object_get (spinner, "active", &active, NULL);
	if (active || g_object_get_data (G_OBJECT (spinner), "start-timeout") != NULL)
		return;

100
101
102
103
	gtk_widget_set_opacity (GTK_WIDGET (spinner), 0);
	id = g_timeout_add (SPINNER_DELAY, start_spinning, spinner);
	g_object_set_data_full (G_OBJECT (spinner), "start-timeout",
				GUINT_TO_POINTER (id), remove_source);
104
105
}

106
107
108
static void
remove_all_cb (GtkWidget *widget, gpointer user_data)
{
109
110
	GtkContainer *container = GTK_CONTAINER (user_data);
	gtk_container_remove (container, widget);
111
112
113
114
115
}

void
gs_container_remove_all (GtkContainer *container)
{
116
	gtk_container_foreach (container, remove_all_cb, container);
117
118
}

119
120
121
static void
grab_focus (GtkWidget *widget)
{
122
123
	g_signal_handlers_disconnect_by_func (widget, grab_focus, NULL);
	gtk_widget_grab_focus (widget);
124
125
126
127
128
}

void
gs_grab_focus_when_mapped (GtkWidget *widget)
{
129
130
131
132
133
	if (gtk_widget_get_mapped (widget))
		gtk_widget_grab_focus (widget);
	else
		g_signal_connect_after (widget, "map",
					G_CALLBACK (grab_focus), NULL);
134
}
135
136
137
138

void
gs_app_notify_installed (GsApp *app)
{
139
140
	g_autofree gchar *summary = NULL;
	g_autoptr(GNotification) n = NULL;
141

142
143
	/* TRANSLATORS: this is the summary of a notification that an application
	 * has been successfully installed */
144
	summary = g_strdup_printf (_("%s is now installed"), gs_app_get_name (app));
Matthias Clasen's avatar
Matthias Clasen committed
145
	n = g_notification_new (summary);
146
	if (gs_app_get_kind (app) == AS_APP_KIND_DESKTOP) {
147
148
149
		/* TRANSLATORS: this is button that opens the newly installed application */
		g_notification_add_button_with_target (n, _("Launch"),
						       "app.launch", "s",
150
						       gs_app_get_id (app));
151
152
	}
	g_notification_set_default_action_and_target  (n, "app.details", "(ss)",
153
						       gs_app_get_id (app), "");
Matthias Clasen's avatar
Matthias Clasen committed
154
	g_application_send_notification (g_application_get_default (), "installed", n);
155
156
}

157
158
159
160
/**
 * gs_app_notify_failed_modal:
 **/
void
161
162
gs_app_notify_failed_modal (GsApp *app,
			    GtkWindow *parent_window,
163
164
165
			    GsPluginLoaderAction action,
			    const GError *error)
{
166
	const gchar *title;
167
	g_autoptr(GString) msg = NULL;
168

169
	/* TRANSLATORS: install or removed failed */
170
	title = _("Sorry, this did not work");
171
172
173

	/* say what we tried to do */
	msg = g_string_new ("");
174
175
	switch (action) {
	case GS_PLUGIN_LOADER_ACTION_INSTALL:
176
	case GS_PLUGIN_LOADER_ACTION_UPGRADE_DOWNLOAD:
177
		/* TRANSLATORS: this is when the install fails */
178
179
		g_string_append_printf (msg, _("Installation of %s failed."),
					gs_app_get_name (app));
180
181
182
		break;
	case GS_PLUGIN_LOADER_ACTION_REMOVE:
		/* TRANSLATORS: this is when the remove fails */
183
184
		g_string_append_printf (msg, _("Removal of %s failed."),
					gs_app_get_name (app));
185
186
187
188
189
		break;
	default:
		g_assert_not_reached ();
		break;
	}
190

191
	gs_utils_show_error_dialog (parent_window, title, msg->str, error->message);
192
193
}

194
typedef enum {
195
196
197
198
	GS_APP_LICENSE_FREE		= 0,
	GS_APP_LICENSE_NONFREE		= 1,
	GS_APP_LICENSE_PATENT_CONCERN	= 2
} GsAppLicenseHint;
199

200
/**
201
 * gs_app_notify_unavailable:
202
 **/
203
204
GtkResponseType
gs_app_notify_unavailable (GsApp *app, GtkWindow *parent)
205
{
206
	GsAppLicenseHint hint = GS_APP_LICENSE_FREE;
207
208
	GtkResponseType response;
	GtkWidget *dialog;
209
	const gchar *license;
210
211
212
213
	gboolean already_enabled = FALSE;	/* FIXME */
	guint i;
	struct {
		const gchar	*str;
214
		GsAppLicenseHint hint;
215
	} keywords[] = {
216
217
218
		{ "NonFree",		GS_APP_LICENSE_NONFREE },
		{ "PatentConcern",	GS_APP_LICENSE_PATENT_CONCERN },
		{ "Proprietary",	GS_APP_LICENSE_NONFREE },
219
220
		{ NULL, 0 }
	};
221
222
	g_autofree gchar *origin_url = NULL;
	g_autoptr(GSettings) settings = NULL;
Kalev Lember's avatar
Kalev Lember committed
223
224
	g_autoptr(GString) body = NULL;
	g_autoptr(GString) title = NULL;
225

226
	/* this is very crude */
227
228
	license = gs_app_get_license (app);
	if (license != NULL) {
229
		for (i = 0; keywords[i].str != NULL; i++) {
230
			if (g_strstr_len (license, -1, keywords[i].str) != NULL)
231
232
233
234
				hint |= keywords[i].hint;
		}
	} else {
		/* use the worst-case assumption */
235
		hint = GS_APP_LICENSE_NONFREE | GS_APP_LICENSE_PATENT_CONCERN;
236
237
	}

238
239
240
241
242
243
	/* check if the user has already dismissed */
	settings = g_settings_new ("org.gnome.software");
	if (!g_settings_get_boolean (settings, "prompt-for-nonfree"))
		return GTK_RESPONSE_OK;

	title = g_string_new ("");
244
245
246
247
248
249
250
251
252
	if (already_enabled) {
		g_string_append_printf (title, "<b>%s</b>",
					/* TRANSLATORS: window title */
					_("Install Third-Party Software?"));
	} else {
		g_string_append_printf (title, "<b>%s</b>",
					/* TRANSLATORS: window title */
					_("Enable Third-Party Software Source?"));
	}
253
254
255
256
257
258
259
	dialog = gtk_message_dialog_new (parent,
					 GTK_DIALOG_MODAL,
					 GTK_MESSAGE_QUESTION,
					 GTK_BUTTONS_CANCEL,
					 NULL);
	gtk_message_dialog_set_markup (GTK_MESSAGE_DIALOG (dialog), title->str);

260
	/* FIXME: get the URL somehow... */
261
	origin_url = g_strdup_printf ("<a href=\"\">%s</a>", gs_app_get_origin (app));
262
	body = g_string_new ("");
263
	if (hint & GS_APP_LICENSE_NONFREE) {
264
265
266
		g_string_append_printf (body,
					/* TRANSLATORS: the replacements are as follows:
					 * 1. Application name, e.g. "Firefox"
267
					 * 2. Software source name, e.g. fedora-optional
268
					 */
269
270
271
					_("%s is not <a href=\"https://en.wikipedia.org/wiki/Free_and_open-source_software\">"
					  "free and open source software</a>, "
					  "and is provided by “%s”."),
272
					gs_app_get_name (app),
273
					origin_url);
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
	} else {
		g_string_append_printf (body,
					/* TRANSLATORS: the replacements are as follows:
					 * 1. Application name, e.g. "Firefox"
					 * 2. Software source name, e.g. fedora-optional */
					_("%s is provided by “%s”."),
					gs_app_get_name (app),
					origin_url);
	}

	/* tell the use what needs to be done */
	if (!already_enabled) {
		g_string_append (body, " ");
		g_string_append (body,
				/* TRANSLATORS: a software source is a repo */
				_("This software source must be "
				  "enabled to continue installation."));
	}

	/* be aware of patent clauses */
294
	if (hint & GS_APP_LICENSE_PATENT_CONCERN) {
295
		g_string_append (body, "\n\n");
296
		if (gs_app_get_kind (app) != AS_APP_KIND_CODEC) {
297
298
299
300
301
302
303
304
305
306
307
308
			g_string_append_printf (body,
						/* TRANSLATORS: Laws are geographical, urgh... */
						_("It may be illegal to install "
						  "or use %s in some countries."),
						gs_app_get_name (app));
		} else {
			g_string_append (body,
					/* TRANSLATORS: Laws are geographical, urgh... */
					_("It may be illegal to install or use "
					  "this codec in some countries."));
		}
	}
309
310
311

	gtk_message_dialog_format_secondary_markup (GTK_MESSAGE_DIALOG (dialog), "%s", body->str);
	/* TRANSLATORS: this is button text to not ask about non-free content again */
312
313
314
315
316
317
318
319
320
321
322
323
	if (0) gtk_dialog_add_button (GTK_DIALOG (dialog), _("Don't Warn Again"), GTK_RESPONSE_YES);
	if (already_enabled) {
		gtk_dialog_add_button (GTK_DIALOG (dialog),
				       /* TRANSLATORS: button text */
				       _("Install"),
				       GTK_RESPONSE_OK);
	} else {
		gtk_dialog_add_button (GTK_DIALOG (dialog),
				       /* TRANSLATORS: button text */
				       _("Enable and Install"),
				       GTK_RESPONSE_OK);
	}
324
325
326
327
328
329
330
331
332
	response = gtk_dialog_run (GTK_DIALOG (dialog));
	if (response == GTK_RESPONSE_YES) {
		response = GTK_RESPONSE_OK;
		g_settings_set_boolean (settings, "prompt-for-nonfree", FALSE);
	}
	gtk_widget_destroy (dialog);
	return response;
}

Kalev Lember's avatar
Kalev Lember committed
333
334
335
336
void
gs_app_show_url (GsApp *app, AsUrlKind kind)
{
	const gchar *url;
337
	g_autoptr(GError) error = NULL;
Kalev Lember's avatar
Kalev Lember committed
338
339
340
341
342
343

	url = gs_app_get_url (app, kind);
	if (!gtk_show_uri (NULL, url, GDK_CURRENT_TIME, &error))
		g_warning ("spawn of '%s' failed", url);
}

344
345
346
347
348
349
/**
 * gs_mkdir_parent:
 **/
gboolean
gs_mkdir_parent (const gchar *path, GError **error)
{
350
	g_autofree gchar *parent = NULL;
351
352
353
354
355
356
357
358

	parent = g_path_get_dirname (path);
	if (g_mkdir_with_parents (parent, 0755) == -1) {
		g_set_error (error,
			     GS_PLUGIN_ERROR,
			     GS_PLUGIN_ERROR_FAILED,
			     "Failed to create '%s': %s",
			     parent, g_strerror (errno));
359
		return FALSE;
360
	}
361
	return TRUE;
362
363
}

364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
/**
 * gs_image_set_from_pixbuf_with_scale:
 **/
void
gs_image_set_from_pixbuf_with_scale (GtkImage *image, const GdkPixbuf *pixbuf, gint scale)
{
	cairo_surface_t *surface;
	surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, scale, NULL);
	gtk_image_set_from_surface (image, surface);
	cairo_surface_destroy (surface);
}

/**
 * gs_image_set_from_pixbuf:
 **/
void
gs_image_set_from_pixbuf (GtkImage *image, const GdkPixbuf *pixbuf)
{
	gint scale;
	scale = gdk_pixbuf_get_width (pixbuf) / 64;
	gs_image_set_from_pixbuf_with_scale (image, pixbuf, scale);
}

387
388
389
390
391
392
/**
 * gs_utils_get_file_age:
 *
 * Returns: The time in seconds since the file was modified
 */
guint
Richard Hughes's avatar
Richard Hughes committed
393
gs_utils_get_file_age (GFile *file)
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
{
	guint64 now;
	guint64 mtime;
	g_autoptr(GFileInfo) info = NULL;

	info = g_file_query_info (file,
				  G_FILE_ATTRIBUTE_TIME_MODIFIED,
				  G_FILE_QUERY_INFO_NONE,
				  NULL,
				  NULL);
	if (info == NULL)
		return G_MAXUINT;
	mtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
	now = (guint64) g_get_real_time () / G_USEC_PER_SEC;
	if (mtime > now)
		return G_MAXUINT;
	return now - mtime;
}

413
414
415
const gchar *
gs_user_agent (void)
{
416
417
418
419
420
421
422
423
424
425
426
427
#ifdef HAVE_FIRMWARE
	static gchar *user_agent = NULL;
	if (user_agent == NULL) {
		user_agent = g_strdup_printf ("%s/%s fwupd/%i.%i.%i",
					      PACKAGE_NAME,
					      PACKAGE_VERSION,
					      FWUPD_MAJOR_VERSION,
					      FWUPD_MINOR_VERSION,
					      FWUPD_MICRO_VERSION);
	}
	return user_agent;
#else
428
	return PACKAGE_NAME "/" PACKAGE_VERSION;
429
#endif
430
431
}

432
433
434
435
/**
 * gs_utils_get_cachedir:
 **/
gchar *
436
gs_utils_get_cachedir (const gchar *kind, GError **error)
437
438
{
	g_autofree gchar *vername = NULL;
439
	g_autofree gchar *cachedir = NULL;
440
	g_auto(GStrv) version = g_strsplit (VERSION, ".", 3);
441
442
443
444
	g_autoptr(GFile) cachedir_file = NULL;

	/* create the cachedir in a per-release location, creating
	 * if it does not already exist */
445
	vername = g_strdup_printf ("%s.%s", version[0], version[1]);
446
447
448
449
450
451
452
453
	cachedir = g_build_filename (g_get_user_cache_dir (),
				      "gnome-software", vername, kind, NULL);
	cachedir_file = g_file_new_for_path (cachedir);
	if (!g_file_query_exists (cachedir_file, NULL) &&
	    !g_file_make_directory_with_parents (cachedir_file, NULL, error))
		return NULL;

	return g_steal_pointer (&cachedir);
454
455
}

456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
/**
 * gs_utils_get_user_hash:
 *
 * This SHA1 hash is composed of the contents of machine-id and your
 * usename and is also salted with a hardcoded value.
 *
 * This provides an identifier that can be used to identify a specific
 * user on a machine, allowing them to cast only one vote or perform
 * one review on each application.
 *
 * There is no known way to calculate the machine ID or username from
 * the machine hash and there should be no privacy issue.
 */
gchar *
gs_utils_get_user_hash (GError **error)
{
	g_autofree gchar *data = NULL;
	g_autofree gchar *salted = NULL;

	if (!g_file_get_contents ("/etc/machine-id", &data, NULL, error))
		return NULL;

478
	salted = g_strdup_printf ("gnome-software[%s:%s]",
479
480
481
482
				  g_get_user_name (), data);
	return g_compute_checksum_for_string (G_CHECKSUM_SHA1, salted, -1);
}

483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
/**
 * gs_utils_get_permission:
 **/
GPermission *
gs_utils_get_permission (const gchar *id)
{
#ifdef HAVE_POLKIT
	g_autoptr(GPermission) permission = NULL;
	g_autoptr(GError) error = NULL;

	permission = polkit_permission_new_sync (id, NULL, NULL, &error);
	if (permission == NULL) {
		g_warning ("Failed to create permission %s: %s", id, error->message);
		return NULL;
	}
	return g_steal_pointer (&permission);
#else
	g_debug ("no PolicyKit, so can't return GPermission for %s", id);
	return NULL;
#endif
}

505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
/**
 * gs_utils_get_content_type:
 */
gchar *
gs_utils_get_content_type (const gchar *filename,
			   GCancellable *cancellable,
			   GError **error)
{
	const gchar *tmp;
	g_autoptr(GFile) file = NULL;
	g_autoptr(GFileInfo) info = NULL;

	/* get content type */
	file = g_file_new_for_path (filename);
	info = g_file_query_info (file,
				  G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
				  G_FILE_QUERY_INFO_NONE,
				  cancellable,
				  error);
	if (info == NULL)
		return NULL;
	tmp = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE);
	if (tmp == NULL)
		return NULL;
	return g_strdup (tmp);
}

532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
/**
 * gs_utils_is_current_desktop:
 */
gboolean
gs_utils_is_current_desktop (const gchar *name)
{
	const gchar *tmp;
	g_auto(GStrv) names = NULL;
	tmp = g_getenv ("XDG_CURRENT_DESKTOP");
	if (tmp == NULL)
		return FALSE;
	names = g_strsplit (tmp, ":", -1);
	return g_strv_contains ((const gchar * const *) names, name);
}

547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
/**
 * gs_utils_widget_css_parsing_error_cb:
 */
static void
gs_utils_widget_css_parsing_error_cb (GtkCssProvider *provider,
				      GtkCssSection *section,
				      GError *error,
				      gpointer user_data)
{
	g_warning ("CSS parse error %i:%i: %s",
		   gtk_css_section_get_start_line (section),
		   gtk_css_section_get_start_position (section),
		   error->message);
}

/**
 * gs_utils_widget_set_custom_css:
 **/
void
gs_utils_widget_set_custom_css (GtkWidget *widget, const gchar *css)
{
	GString *str = g_string_sized_new (1024);
	GtkStyleContext *context;
	g_autofree gchar *class_name = NULL;
	g_autoptr(GtkCssProvider) provider = NULL;

	/* invalid */
	if (css == NULL)
		return;

	/* make into a proper CSS class */
	class_name = g_strdup_printf ("themed-widget_%p", widget);
	g_string_append_printf (str, ".%s {\n", class_name);
	g_string_append_printf (str, "%s\n", css);
	g_string_append (str, "}");

	g_string_append_printf (str, ".%s:hover {\n", class_name);
	g_string_append (str, "  opacity: 0.9;\n");
	g_string_append (str, "}\n");

	g_debug ("using custom CSS %s", str->str);

	/* set the custom CSS class */
	context = gtk_widget_get_style_context (widget);
	gtk_style_context_add_class (context, class_name);

	/* set up custom provider and store on the widget */
	provider = gtk_css_provider_new ();
	g_signal_connect (provider, "parsing-error",
			  G_CALLBACK (gs_utils_widget_css_parsing_error_cb), NULL);
	gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
						   GTK_STYLE_PROVIDER (provider),
						   GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
	gtk_css_provider_load_from_data (provider, str->str, -1, NULL);
	g_object_set_data_full (G_OBJECT (widget),
				"GnomeSoftware::provider",
				g_object_ref (provider),
				g_object_unref);
}

607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
static void
do_not_expand (GtkWidget *child, gpointer data)
{
	gtk_container_child_set (GTK_CONTAINER (gtk_widget_get_parent (child)),
				 child, "expand", FALSE, "fill", FALSE, NULL);
}

static gboolean
unset_focus (GtkWidget *widget, GdkEvent *event, gpointer data)
{
	if (GTK_IS_WINDOW (widget))
		gtk_window_set_focus (GTK_WINDOW (widget), NULL);
	return FALSE;
}

/**
 * insert_details_widget:
 * @dialog: the message dialog where the widget will be inserted
 * @details: the detailed message text to display
 *
 * Inserts a widget displaying the detailed message into the message dialog.
 */
static void
insert_details_widget (GtkMessageDialog *dialog, const gchar *details)
{
	GtkWidget *message_area, *sw, *label;
	GtkWidget *box, *tv;
	GtkTextBuffer *buffer;
	GList *children;
	g_autoptr(GString) msg = NULL;

	g_assert (GTK_IS_MESSAGE_DIALOG (dialog));
	g_assert (details != NULL);

	gtk_window_set_resizable (GTK_WINDOW (dialog), TRUE);

	msg = g_string_new ("");
	g_string_append_printf (msg, "%s\n\n%s",
	                        /* TRANSLATORS: these are show_detailed_error messages from the
	                         * package manager no mortal is supposed to understand,
	                         * but google might know what they mean */
	                        _("Detailed errors from the package manager follow:"),
	                        details);

	message_area = gtk_message_dialog_get_message_area (dialog);
	g_assert (GTK_IS_BOX (message_area));
	/* make the hbox expand */
	box = gtk_widget_get_parent (message_area);
	gtk_container_child_set (GTK_CONTAINER (gtk_widget_get_parent (box)), box,
	                         "expand", TRUE, "fill", TRUE, NULL);
	/* make the labels not expand */
	gtk_container_foreach (GTK_CONTAINER (message_area), do_not_expand, NULL);

	/* Find the secondary label and set its width_chars.   */
	/* Otherwise the label will tend to expand vertically. */
	children = gtk_container_get_children (GTK_CONTAINER (message_area));
	if (children && children->next && GTK_IS_LABEL (children->next->data)) {
		gtk_label_set_width_chars (GTK_LABEL (children->next->data), 40);
	}

	label = gtk_label_new (_("Details"));
	gtk_widget_set_halign (label, GTK_ALIGN_START);
	gtk_widget_set_visible (label, TRUE);
	gtk_box_pack_start (GTK_BOX (message_area), label, FALSE, FALSE, 0);

	sw = gtk_scrolled_window_new (NULL, NULL);
	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw),
	                                     GTK_SHADOW_IN);
	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
	                                GTK_POLICY_NEVER,
	                                GTK_POLICY_AUTOMATIC);
	gtk_scrolled_window_set_min_content_height (GTK_SCROLLED_WINDOW (sw), 150);
	gtk_widget_set_visible (sw, TRUE);

	tv = gtk_text_view_new ();
	buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
	gtk_text_view_set_editable (GTK_TEXT_VIEW (tv), FALSE);
	gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD);
	gtk_style_context_add_class (gtk_widget_get_style_context (tv),
	                             "update-failed-details");
	gtk_text_buffer_set_text (buffer, msg->str, -1);
	gtk_widget_set_visible (tv, TRUE);

	gtk_container_add (GTK_CONTAINER (sw), tv);
	gtk_box_pack_end (GTK_BOX (message_area), sw, TRUE, TRUE, 0);

	g_signal_connect (dialog, "map-event", G_CALLBACK (unset_focus), NULL);
}

Richard Hughes's avatar
Richard Hughes committed
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
/**
 * gs_utils_get_error_value:
 * @error: A GError
 *
 * Gets the machine-readable value stored in the error message.
 * The machine readable string is after the first "@", e.g.
 * message = "Requires authentication with @aaa"
 *
 * Returns: a string, or %NULL
 */
const gchar *
gs_utils_get_error_value (const GError *error)
{
	gchar *str;
	if (error == NULL)
		return NULL;
	str = g_strstr_len (error->message, -1, "@");
	if (str == NULL)
		return NULL;
	return (const gchar *) str + 1;
}

718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
/**
 * gs_utils_show_error_dialog:
 * @parent: transient parent, or NULL for none
 * @title: the title for the dialog
 * @msg: the message for the dialog
 * @details: (allow-none): the detailed error message, or NULL for none
 *
 * Shows a message dialog for displaying error messages.
 */
void
gs_utils_show_error_dialog (GtkWindow *parent,
                            const gchar *title,
                            const gchar *msg,
                            const gchar *details)
{
	GtkWidget *dialog;

	dialog = gtk_message_dialog_new_with_markup (parent,
	                                             0,
	                                             GTK_MESSAGE_INFO,
	                                             GTK_BUTTONS_CLOSE,
	                                             "<big><b>%s</b></big>", title);
	gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
	                                          "%s", msg);
	if (details != NULL)
		insert_details_widget (GTK_MESSAGE_DIALOG (dialog), details);

	g_signal_connect_swapped (dialog, "response",
	                          G_CALLBACK (gtk_widget_destroy),
	                          dialog);
	gtk_widget_show (dialog);
}

751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
/**
 * gs_utils_get_desktop_app_info:
 */
GDesktopAppInfo *
gs_utils_get_desktop_app_info (const gchar *id)
{
	GDesktopAppInfo *app_info;

	/* try to get the standard app-id */
	app_info = g_desktop_app_info_new (id);

	/* KDE is a special project because it believes /usr/share/applications
	 * isn't KDE enough. For this reason we support falling back to the
	 * "kde4-" prefixed ID to avoid educating various self-righteous
	 * upstreams about the correct ID to use in the AppData file. */
	if (app_info == NULL) {
		g_autofree gchar *kde_id = NULL;
		kde_id = g_strdup_printf ("%s-%s", "kde4", id);
		app_info = g_desktop_app_info_new (kde_id);
	}

	return app_info;
}

775
/* vim: set noexpandtab: */