diffstat of debian/ for gnome-software_3.30.6-2 gnome-software_3.30.6-2ubuntu6 README.source | 9 changelog | 61 control | 25 control.in | 25 gbp.conf | 3 icons/hicolor/128x128/apps/ubuntusoftware.svg | 2293 ++++++++++ icons/hicolor/16x16/apps/ubuntusoftware.svg | 564 ++ icons/hicolor/24x24/apps/ubuntusoftware.svg | 678 ++ icons/hicolor/48x48/apps/ubuntusoftware.svg | 666 ++ icons/hicolor/scalable/apps/ubuntusoftware.svg | 2293 ++++++++++ patches/0001-Construct-the-Software-Sources-menu-item-dynamically.patch | 83 patches/0001-details-page-Add-support-for-verified-developers.patch | 89 patches/0002-Download-changelog-information-on-demand-this-stops-.patch | 109 patches/0002-snap-Set-verified-developer-flag.patch | 35 patches/0003-Sort-snaps-before-other-apps.patch | 38 patches/0004-Hide-Kudo-details-since-we-don-t-have-good-data.patch | 39 patches/0005-details-Show-an-in-app-notification-when-passed-an-i.patch | 189 patches/0006-packagekit-Disable-updates.patch | 53 patches/0007-snap-Only-feature-snaps.patch | 61 patches/0008-Don-t-randomize-editors-picks.patch | 25 patches/0009-Display-a-warning-for-non-sandboxed-snaps.patch | 117 patches/0010-Sort-category-snaps-before-other-packages.patch | 37 patches/0011-Support-snap-channels.patch | 1696 +++++++ patches/0012-Don-t-use-colour-to-differentiate-between-free-and-p.patch | 34 patches/0013-overview-page-Rotate-featured-apps.patch | 20 patches/0014-Add-a-basic-permissions-system.patch | 1984 ++++++++ patches/0015-build-Translate-Ubuntu-s-.desktop-file.patch | 60 patches/0016-snap-Use-default-icon-if-none-provided.patch | 93 patches/0017-snap-Make-snaps-purchasable.patch | 121 patches/0018-Disable-paid-snap-support-unless-env-variable-GNOME_.patch | 29 patches/0019-Delay-startup-of-GNOME-Software-to-allow-the-Shell-t.patch | 21 patches/0020-details-page-Don-t-show-missing-screenshot-placehold.patch | 31 patches/0021-details-Use-custom-icon-for-verified-developers.patch | 54 patches/0022-snap-Use-wide-scope-when-searching.patch | 66 patches/0023-snap-Don-t-treat-auth-cancellation-as-an-error.patch | 41 patches/0024-shell-search-provider-implement-XUbuntuCancel.patch | 172 patches/0025-snap-Use-new-media-API.patch | 221 patches/0026-odrs-Only-show-reviews-from-the-same-distribution.patch | 36 patches/0027-shell-Don-t-progate-CTRL-F-key-pressed-event.patch | 29 patches/series | 32 ubuntu-software.install | 2 ubuntu-software.links | 2 42 files changed, 12227 insertions(+), 9 deletions(-) diff -Nru gnome-software-3.30.6/debian/README.source gnome-software-3.30.6/debian/README.source --- gnome-software-3.30.6/debian/README.source 1970-01-01 00:00:00.000000000 +0000 +++ gnome-software-3.30.6/debian/README.source 2019-04-16 22:49:03.000000000 +0000 @@ -0,0 +1,9 @@ +The Ubuntu packaging is maintained at +https://code.launchpad.net/~ubuntu-desktop/gnome-software/ubuntu + +Patches are maintained at https://gitlab.gnome.org/ubuntu/gnome-software +in ubuntu-* branches. These branches are regularly rebased. + +To inspect these changes individually, please issue + + $ git clone -b ubuntu-3-26 https://gitlab.gnome.org/ubuntu/gnome-software.git diff -Nru gnome-software-3.30.6/debian/changelog gnome-software-3.30.6/debian/changelog --- gnome-software-3.30.6/debian/changelog 2019-01-05 22:26:30.000000000 +0000 +++ gnome-software-3.30.6/debian/changelog 2019-06-10 00:02:12.000000000 +0000 @@ -1,3 +1,51 @@ +gnome-software (3.30.6-2ubuntu6) eoan; urgency=medium + + * debian/patches/0016-snap-Use-default-icon-if-none-provided.patch: + - Update default snap icon (LP: #1818920) + + -- Robert Ancell Mon, 10 Jun 2019 12:02:12 +1200 + +gnome-software (3.30.6-2ubuntu5) eoan; urgency=medium + + * debian/patches/0014-Add-a-basic-permissions-system.patch: + * debian/patches/0028-Added-u2f-devices-to-interfaces-UI.patch + - Merge u2f changes into original patch + * debian/patches/0027-shell-Don-t-progate-CTRL-F-key-pressed-event.patch: + - Fix Ctrl-F not opening search bar (LP: #1735071) + + -- Robert Ancell Tue, 14 May 2019 11:04:29 +1200 + +gnome-software (3.30.6-2ubuntu4) disco; urgency=mediumd + + * debian/patches/0026-odrs-Only-show-reviews-from-the-same-distribution.patch: + - Show only Ubuntu reviews (LP: #1825064) + + -- Robert Ancell Wed, 17 Apr 2019 10:49:03 +1200 + +gnome-software (3.30.6-2ubuntu3) disco; urgency=medium + + * debian/patches/0028-Added-u2f-devices-to-interfaces-UI.patch + - Allow connections on the u2f-devices interface (LP: #1738164) + (the patch has been SRUed to bionic but was missing from Disco) + + -- Sebastien Bacher Thu, 04 Apr 2019 13:45:29 +0200 + +gnome-software (3.30.6-2ubuntu2) disco; urgency=medium + + * debian/patches/0025-snap-Use-new-media-API.patch: + - Use new snapd media API (LP: #1799614) + + -- Robert Ancell Fri, 18 Jan 2019 15:26:44 +1300 + +gnome-software (3.30.6-2ubuntu1) disco; urgency=medium + + * Sync with Debian. Remaining changes: + + debian/patches/*.patch: Various Ubuntu changes from ubuntu-master + branch - see patch headers for more information. + + Add an "ubuntu-software" package with some branding for Ubuntu. + + -- Jeremy Bicha Sat, 05 Jan 2019 17:30:35 -0500 + gnome-software (3.30.6-2) unstable; urgency=medium * Recommend instead of Suggest fwupd (Closes: #916036) @@ -10,6 +58,19 @@ -- Jeremy Bicha Sat, 05 Jan 2019 17:26:30 -0500 +gnome-software (3.30.6-1ubuntu1) disco; urgency=medium + + * Sync with Debian. Remaining changes: + + debian/patches/*.patch: Various Ubuntu changes from ubuntu-master + branch - see patch headers for more information. + + Add an "ubuntu-software" package with some branding for Ubuntu. + + debian/control.in: + - GNOME Software provides the PackageKit session interface. + Sessioninstaller wants to do that too - Conflict with it to get it + off user systems. + + -- Jeremy Bicha Fri, 21 Dec 2018 11:03:20 -0500 + gnome-software (3.30.6-1) unstable; urgency=medium * New upstream release diff -Nru gnome-software-3.30.6/debian/control gnome-software-3.30.6/debian/control --- gnome-software-3.30.6/debian/control 2019-01-05 22:26:30.000000000 +0000 +++ gnome-software-3.30.6/debian/control 2019-06-10 00:02:12.000000000 +0000 @@ -5,7 +5,8 @@ Source: gnome-software Section: gnome Priority: optional -Maintainer: Debian GNOME Maintainers +Maintainer: Ubuntu Developers +XSBC-Original-Maintainer: Debian GNOME Maintainers Uploaders: Jeremy Bicha , Laurent Bigonville Build-Depends: appstream, appstream-util, @@ -26,7 +27,7 @@ libpackagekit-glib2-dev (>= 1.1.11), libpolkit-gobject-1-dev, libsecret-1-dev, - libsnapd-glib-dev (>= 1.41) [amd64 arm64 armel armhf i386 ppc64el s390x], + libsnapd-glib-dev (>= 1.45) [amd64 arm64 armel armhf i386 ppc64el s390x], libsoup2.4-dev (>= 2.52.0), libxml2-utils, meson (>= 0.40), @@ -35,8 +36,10 @@ xsltproc Rules-Requires-Root: no Standards-Version: 4.3.0 -Vcs-Browser: https://salsa.debian.org/gnome-team/gnome-software -Vcs-Git: https://salsa.debian.org/gnome-team/gnome-software.git +XS-Debian-Vcs-Browser: https://salsa.debian.org/gnome-team/gnome-software +XS-Debian-Vcs-Git: https://salsa.debian.org/gnome-team/gnome-software.git +Vcs-Browser: https://git.launchpad.net/~ubuntu-desktop/ubuntu/+source/gnome-software +Vcs-Git: https://git.launchpad.net/~ubuntu-desktop/ubuntu/+source/gnome-software Homepage: https://wiki.gnome.org/Apps/Software Package: gnome-software @@ -157,3 +160,17 @@ . This package contains documentation for use when developing plugins for Software. + +Package: ubuntu-software +Architecture: all +Depends: gnome-software (>= ${source:Version}), + ${misc:Depends}, + ${shlibs:Depends} +Description: Utility for browsing, installing, and removing software + Ubuntu Software lets you browse and install the applications available for + Ubuntu. You can view available software by category, or search by name + or description. You can also examine the software already installed, and + remove items you no longer need. + . + To install or remove software using Ubuntu Software, you need administrator + access on the computer. diff -Nru gnome-software-3.30.6/debian/control.in gnome-software-3.30.6/debian/control.in --- gnome-software-3.30.6/debian/control.in 2019-01-05 22:26:30.000000000 +0000 +++ gnome-software-3.30.6/debian/control.in 2019-04-16 22:49:03.000000000 +0000 @@ -1,7 +1,8 @@ Source: gnome-software Section: gnome Priority: optional -Maintainer: Debian GNOME Maintainers +Maintainer: Ubuntu Developers +XSBC-Original-Maintainer: Debian GNOME Maintainers Uploaders: @GNOME_TEAM@ Build-Depends: appstream, appstream-util, @@ -22,7 +23,7 @@ libpackagekit-glib2-dev (>= 1.1.11), libpolkit-gobject-1-dev, libsecret-1-dev, - libsnapd-glib-dev (>= 1.41) [amd64 arm64 armel armhf i386 ppc64el s390x], + libsnapd-glib-dev (>= 1.45) [amd64 arm64 armel armhf i386 ppc64el s390x], libsoup2.4-dev (>= 2.52.0), libxml2-utils, meson (>= 0.40), @@ -31,8 +32,10 @@ xsltproc Rules-Requires-Root: no Standards-Version: 4.3.0 -Vcs-Browser: https://salsa.debian.org/gnome-team/gnome-software -Vcs-Git: https://salsa.debian.org/gnome-team/gnome-software.git +XS-Debian-Vcs-Browser: https://salsa.debian.org/gnome-team/gnome-software +XS-Debian-Vcs-Git: https://salsa.debian.org/gnome-team/gnome-software.git +Vcs-Browser: https://git.launchpad.net/~ubuntu-desktop/ubuntu/+source/gnome-software +Vcs-Git: https://git.launchpad.net/~ubuntu-desktop/ubuntu/+source/gnome-software Homepage: https://wiki.gnome.org/Apps/Software Package: gnome-software @@ -153,3 +156,17 @@ . This package contains documentation for use when developing plugins for Software. + +Package: ubuntu-software +Architecture: all +Depends: gnome-software (>= ${source:Version}), + ${misc:Depends}, + ${shlibs:Depends} +Description: Utility for browsing, installing, and removing software + Ubuntu Software lets you browse and install the applications available for + Ubuntu. You can view available software by category, or search by name + or description. You can also examine the software already installed, and + remove items you no longer need. + . + To install or remove software using Ubuntu Software, you need administrator + access on the computer. diff -Nru gnome-software-3.30.6/debian/gbp.conf gnome-software-3.30.6/debian/gbp.conf --- gnome-software-3.30.6/debian/gbp.conf 2019-01-05 22:26:30.000000000 +0000 +++ gnome-software-3.30.6/debian/gbp.conf 2019-04-16 22:49:03.000000000 +0000 @@ -1,6 +1,7 @@ [DEFAULT] pristine-tar = True -debian-branch = debian/master +debian-branch = ubuntu/master +debian-tag=ubuntu/%(version)s upstream-branch = upstream/latest [buildpackage] diff -Nru gnome-software-3.30.6/debian/icons/hicolor/128x128/apps/ubuntusoftware.svg gnome-software-3.30.6/debian/icons/hicolor/128x128/apps/ubuntusoftware.svg --- gnome-software-3.30.6/debian/icons/hicolor/128x128/apps/ubuntusoftware.svg 1970-01-01 00:00:00.000000000 +0000 +++ gnome-software-3.30.6/debian/icons/hicolor/128x128/apps/ubuntusoftware.svg 2019-04-16 22:49:03.000000000 +0000 @@ -0,0 +1,2293 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -Nru gnome-software-3.30.6/debian/icons/hicolor/16x16/apps/ubuntusoftware.svg gnome-software-3.30.6/debian/icons/hicolor/16x16/apps/ubuntusoftware.svg --- gnome-software-3.30.6/debian/icons/hicolor/16x16/apps/ubuntusoftware.svg 1970-01-01 00:00:00.000000000 +0000 +++ gnome-software-3.30.6/debian/icons/hicolor/16x16/apps/ubuntusoftware.svg 2019-04-16 22:49:03.000000000 +0000 @@ -0,0 +1,564 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -Nru gnome-software-3.30.6/debian/icons/hicolor/24x24/apps/ubuntusoftware.svg gnome-software-3.30.6/debian/icons/hicolor/24x24/apps/ubuntusoftware.svg --- gnome-software-3.30.6/debian/icons/hicolor/24x24/apps/ubuntusoftware.svg 1970-01-01 00:00:00.000000000 +0000 +++ gnome-software-3.30.6/debian/icons/hicolor/24x24/apps/ubuntusoftware.svg 2019-04-16 22:49:03.000000000 +0000 @@ -0,0 +1,678 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -Nru gnome-software-3.30.6/debian/icons/hicolor/48x48/apps/ubuntusoftware.svg gnome-software-3.30.6/debian/icons/hicolor/48x48/apps/ubuntusoftware.svg --- gnome-software-3.30.6/debian/icons/hicolor/48x48/apps/ubuntusoftware.svg 1970-01-01 00:00:00.000000000 +0000 +++ gnome-software-3.30.6/debian/icons/hicolor/48x48/apps/ubuntusoftware.svg 2019-04-16 22:49:03.000000000 +0000 @@ -0,0 +1,666 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -Nru gnome-software-3.30.6/debian/icons/hicolor/scalable/apps/ubuntusoftware.svg gnome-software-3.30.6/debian/icons/hicolor/scalable/apps/ubuntusoftware.svg --- gnome-software-3.30.6/debian/icons/hicolor/scalable/apps/ubuntusoftware.svg 1970-01-01 00:00:00.000000000 +0000 +++ gnome-software-3.30.6/debian/icons/hicolor/scalable/apps/ubuntusoftware.svg 2019-04-16 22:49:03.000000000 +0000 @@ -0,0 +1,2293 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -Nru gnome-software-3.30.6/debian/patches/0001-Construct-the-Software-Sources-menu-item-dynamically.patch gnome-software-3.30.6/debian/patches/0001-Construct-the-Software-Sources-menu-item-dynamically.patch --- gnome-software-3.30.6/debian/patches/0001-Construct-the-Software-Sources-menu-item-dynamically.patch 1970-01-01 00:00:00.000000000 +0000 +++ gnome-software-3.30.6/debian/patches/0001-Construct-the-Software-Sources-menu-item-dynamically.patch 2019-04-16 22:49:03.000000000 +0000 @@ -0,0 +1,83 @@ +From: Iain Lane +Date: Mon, 23 Jan 2017 16:22:09 +0000 +Subject: [PATCH 01/24] Construct the "Software Sources" menu item dynamically + +If we have software-properties-gtk, we'll launch that. In that case, +call the menu item "Software & Updates", since that more accurately +reflects what it does. +--- + src/gs-application.c | 26 ++++++++++++++++++++++++++ + src/gs-menus.ui | 12 +----------- + 2 files changed, 27 insertions(+), 11 deletions(-) + +diff --git a/src/gs-application.c b/src/gs-application.c +index eeabb72..5a75d41 100644 +--- a/src/gs-application.c ++++ b/src/gs-application.c +@@ -904,10 +904,31 @@ gs_application_add_wrapper_actions (GApplication *application) + static void + gs_application_startup (GApplication *application) + { ++ g_autofree gchar *software_properties = NULL; ++ const gchar *label = NULL; ++ g_autoptr(GMenu) menu = NULL; ++ g_autoptr(GMenu) new = NULL; ++ + GSettings *settings; + GsApplication *app = GS_APPLICATION (application); + G_APPLICATION_CLASS (gs_application_parent_class)->startup (application); + ++ /* This follows the behaviour in src/gs-shell.c; when we have s-p-gtk, ++ * we will launch it. It provides a UI to manage update behaviour too. ++ */ ++ software_properties = g_find_program_in_path ("software-properties-gtk"); ++ ++ if (!software_properties) ++ label = _("Software Sources"); ++ else ++ label = _("Software & Updates"); ++ ++ menu = gtk_application_get_menu_by_id (GTK_APPLICATION (application), ++ "app-menu"); ++ new = g_menu_new (); ++ g_menu_append (new, label, "app.sources"); ++ g_menu_prepend_section (menu, NULL, G_MENU_MODEL (new)); ++ + gs_application_add_wrapper_actions (application); + + g_action_map_add_action_entries (G_ACTION_MAP (application), +@@ -927,6 +948,11 @@ gs_application_startup (GApplication *application) + + gs_application_initialize_ui (app); + ++ // If the flatpak plugin is running, show update preferences ++ if (gs_plugin_loader_get_enabled (app->plugin_loader, "flatpak")) { ++ g_menu_append (new, _("_Update Preferences"), "app.prefs"); ++ } ++ + GS_APPLICATION (application)->update_monitor = + gs_update_monitor_new (GS_APPLICATION (application)); + gs_folders_convert (); +diff --git a/src/gs-menus.ui b/src/gs-menus.ui +index b9bebb3..59e0b1e 100644 +--- a/src/gs-menus.ui ++++ b/src/gs-menus.ui +@@ -2,17 +2,7 @@ + + + +-
+- +- _Software Repositories +- app.sources +- action-disabled +- +- +- _Update Preferences +- app.prefs +- +-
++ +
+ + _About diff -Nru gnome-software-3.30.6/debian/patches/0001-details-page-Add-support-for-verified-developers.patch gnome-software-3.30.6/debian/patches/0001-details-page-Add-support-for-verified-developers.patch --- gnome-software-3.30.6/debian/patches/0001-details-page-Add-support-for-verified-developers.patch 1970-01-01 00:00:00.000000000 +0000 +++ gnome-software-3.30.6/debian/patches/0001-details-page-Add-support-for-verified-developers.patch 2019-04-16 22:49:03.000000000 +0000 @@ -0,0 +1,89 @@ +From: Robert Ancell +Date: Tue, 10 Jul 2018 11:47:44 +0200 +Subject: [PATCH 01/28] details page: Add support for verified developers + +--- + src/gs-details-page.c | 3 +++ + src/gs-details-page.ui | 31 +++++++++++++++++++++++-------- + 2 files changed, 26 insertions(+), 8 deletions(-) + +diff --git a/src/gs-details-page.c b/src/gs-details-page.c +index 3e7d319..68cde5a 100644 +--- a/src/gs-details-page.c ++++ b/src/gs-details-page.c +@@ -101,6 +101,7 @@ struct _GsDetailsPage + GtkWidget *label_details_category_value; + GtkWidget *label_details_developer_title; + GtkWidget *label_details_developer_value; ++ GtkWidget *image_details_developer_verified; + GtkWidget *button_details_license_free; + GtkWidget *button_details_license_nonfree; + GtkWidget *button_details_license_unknown; +@@ -951,6 +952,7 @@ gs_details_page_refresh_all (GsDetailsPage *self) + gtk_label_set_label (GTK_LABEL (self->label_details_developer_value), tmp); + gtk_widget_set_visible (self->label_details_developer_value, TRUE); + } ++ gtk_widget_set_visible (self->image_details_developer_verified, gs_app_has_quirk (self->app, AS_APP_QUIRK_DEVELOPER_VERIFIED)); + + /* set the license buttons */ + tmp = gs_app_get_license (self->app); +@@ -2441,6 +2443,7 @@ gs_details_page_class_init (GsDetailsPageClass *klass) + gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, label_details_category_value); + gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, label_details_developer_title); + gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, label_details_developer_value); ++ gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, image_details_developer_verified); + gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, button_details_license_free); + gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, button_details_license_nonfree); + gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, button_details_license_unknown); +diff --git a/src/gs-details-page.ui b/src/gs-details-page.ui +index 9836384..206d99f 100644 +--- a/src/gs-details-page.ui ++++ b/src/gs-details-page.ui +@@ -1041,16 +1041,31 @@ + + + +- ++ + True + False + True +- Yorba +- True +- True +- 10 +- 0 +- 0.5 ++ 3 ++ ++ ++ True ++ False ++ Yorba ++ True ++ True ++ 10 ++ 0 ++ 0.5 ++ ++ ++ ++ ++ True ++ False ++ 16 ++ emblem-ok-symbolic ++ ++ + + + 1 +@@ -1363,7 +1378,7 @@ + + + +- ++ + + + diff -Nru gnome-software-3.30.6/debian/patches/0002-Download-changelog-information-on-demand-this-stops-.patch gnome-software-3.30.6/debian/patches/0002-Download-changelog-information-on-demand-this-stops-.patch --- gnome-software-3.30.6/debian/patches/0002-Download-changelog-information-on-demand-this-stops-.patch 1970-01-01 00:00:00.000000000 +0000 +++ gnome-software-3.30.6/debian/patches/0002-Download-changelog-information-on-demand-this-stops-.patch 2019-04-16 22:49:03.000000000 +0000 @@ -0,0 +1,109 @@ +From: Robert Ancell +Date: Mon, 7 Nov 2016 16:55:18 +1300 +Subject: [PATCH 02/24] Download changelog information on demand - this stops + the UI blocking on startup + +--- + lib/gs-plugin-types.h | 2 ++ + src/gs-update-dialog.c | 40 +++++++++++++++++++++++++++++++++++++--- + 2 files changed, 39 insertions(+), 3 deletions(-) + +diff --git a/lib/gs-plugin-types.h b/lib/gs-plugin-types.h +index 14dfdff..6cdd28a 100644 +--- a/lib/gs-plugin-types.h ++++ b/lib/gs-plugin-types.h +@@ -153,6 +153,7 @@ typedef enum { + * @GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN_UI: Require the origin for UI + * @GS_PLUGIN_REFINE_FLAGS_REQUIRE_RUNTIME: Require the runtime + * @GS_PLUGIN_REFINE_FLAGS_REQUIRE_SCREENSHOTS: Require screenshot information ++ * @GS_PLUGIN_REFINE_FLAGS_REQUIRE_CHANGELOG: Require the changelog + * + * The refine flags. + **/ +@@ -184,6 +185,7 @@ typedef enum { + #define GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN_UI ((guint64) 1 << 24) + #define GS_PLUGIN_REFINE_FLAGS_REQUIRE_RUNTIME ((guint64) 1 << 25) + #define GS_PLUGIN_REFINE_FLAGS_REQUIRE_SCREENSHOTS ((guint64) 1 << 26) ++#define GS_PLUGIN_REFINE_FLAGS_REQUIRE_CHANGELOG ((guint64) 1 << 27) + typedef guint64 GsPluginRefineFlags; + + /** +diff --git a/src/gs-update-dialog.c b/src/gs-update-dialog.c +index b4c1b11..2667b2b 100644 +--- a/src/gs-update-dialog.c ++++ b/src/gs-update-dialog.c +@@ -50,6 +50,7 @@ struct _GsUpdateDialog + GQueue *back_entry_stack; + GCancellable *cancellable; + GsPluginLoader *plugin_loader; ++ GsApp *app; + GtkWidget *box_header; + GtkWidget *button_back; + GtkWidget *image_icon; +@@ -96,6 +97,25 @@ back_entry_free (BackEntry *entry) + g_slice_free (BackEntry, entry); + } + ++static void ++refine_cb (GsPluginLoader *plugin_loader, GAsyncResult *res, GsUpdateDialog *dialog) ++{ ++ const gchar *update_details; ++ g_autoptr(GError) error = NULL; ++ ++ if (!gs_plugin_loader_job_action_finish (plugin_loader, res, &error)) ++ g_warning ("Failed to get changelog information: %s", error->message); ++ ++ update_details = gs_app_get_update_details (dialog->app); ++ if (update_details == NULL) { ++ /* TRANSLATORS: this is where the packager did not write ++ * a description for the update */ ++ update_details = _("No update description available."); ++ } ++ ++ gtk_label_set_label (GTK_LABEL (dialog->label_details), update_details); ++} ++ + static void + set_updates_description_ui (GsUpdateDialog *dialog, GsApp *app) + { +@@ -103,6 +123,8 @@ set_updates_description_ui (GsUpdateDialog *dialog, GsApp *app) + const GdkPixbuf *pixbuf; + const gchar *update_details; + ++ g_set_object (&dialog->app, app); ++ + /* set window title */ + kind = gs_app_get_kind (app); + if (kind == AS_APP_KIND_OS_UPDATE) { +@@ -126,9 +148,20 @@ set_updates_description_ui (GsUpdateDialog *dialog, GsApp *app) + gtk_widget_set_visible (dialog->box_header, kind == AS_APP_KIND_DESKTOP); + update_details = gs_app_get_update_details (app); + if (update_details == NULL) { +- /* TRANSLATORS: this is where the packager did not write +- * a description for the update */ +- update_details = _("No update description available."); ++ g_autoptr(GsPluginJob) plugin_job = NULL; ++ ++ /* TRANSLATORS: this is displayed while the changelog is being downloaded */ ++ update_details = _("Downloading change information…"); ++ ++ plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REFINE, ++ "app", app, ++ "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_CHANGELOG, ++ NULL); ++ gs_plugin_loader_job_process_async (dialog->plugin_loader, ++ plugin_job, ++ dialog->cancellable, ++ (GAsyncReadyCallback) refine_cb, ++ dialog); + } + gtk_label_set_label (GTK_LABEL (dialog->label_details), update_details); + gtk_label_set_label (GTK_LABEL (dialog->label_name), gs_app_get_name (app)); +@@ -702,6 +735,7 @@ gs_update_dialog_dispose (GObject *object) + } + + g_clear_object (&dialog->plugin_loader); ++ g_clear_object (&dialog->app); + + G_OBJECT_CLASS (gs_update_dialog_parent_class)->dispose (object); + } diff -Nru gnome-software-3.30.6/debian/patches/0002-snap-Set-verified-developer-flag.patch gnome-software-3.30.6/debian/patches/0002-snap-Set-verified-developer-flag.patch --- gnome-software-3.30.6/debian/patches/0002-snap-Set-verified-developer-flag.patch 1970-01-01 00:00:00.000000000 +0000 +++ gnome-software-3.30.6/debian/patches/0002-snap-Set-verified-developer-flag.patch 2019-04-16 22:49:03.000000000 +0000 @@ -0,0 +1,35 @@ +From: Robert Ancell +Date: Tue, 10 Jul 2018 11:48:04 +0200 +Subject: [PATCH 02/26] snap: Set verified developer flag + +--- + meson.build | 2 +- + plugins/snap/gs-plugin-snap.c | 2 ++ + 2 files changed, 3 insertions(+), 1 deletion(-) + +diff --git a/meson.build b/meson.build +index 0e09225..f5a80ce 100644 +--- a/meson.build ++++ b/meson.build +@@ -171,7 +171,7 @@ if get_option('gudev') + endif + + if get_option('snap') +- snap = dependency('snapd-glib', version : '>= 1.41') ++ snap = dependency('snapd-glib', version : '>= 1.42') + endif + + gnome = import('gnome') +diff --git a/plugins/snap/gs-plugin-snap.c b/plugins/snap/gs-plugin-snap.c +index 8658aaf..d34948f 100644 +--- a/plugins/snap/gs-plugin-snap.c ++++ b/plugins/snap/gs-plugin-snap.c +@@ -820,6 +820,8 @@ gs_plugin_refine_app (GsPlugin *plugin, + if (developer_name == NULL) + developer_name = snapd_snap_get_publisher_username (snap); + gs_app_set_developer_name (app, developer_name); ++ if (snapd_snap_get_publisher_validation (snap) == SNAPD_PUBLISHER_VALIDATION_VERIFIED) ++ gs_app_add_quirk (app, AS_APP_QUIRK_DEVELOPER_VERIFIED); + + snap = local_snap != NULL ? local_snap : store_snap; + gs_app_set_version (app, snapd_snap_get_version (snap)); diff -Nru gnome-software-3.30.6/debian/patches/0003-Sort-snaps-before-other-apps.patch gnome-software-3.30.6/debian/patches/0003-Sort-snaps-before-other-apps.patch --- gnome-software-3.30.6/debian/patches/0003-Sort-snaps-before-other-apps.patch 1970-01-01 00:00:00.000000000 +0000 +++ gnome-software-3.30.6/debian/patches/0003-Sort-snaps-before-other-apps.patch 2019-04-16 22:49:03.000000000 +0000 @@ -0,0 +1,38 @@ +From: Robert Ancell +Date: Wed, 30 Mar 2016 15:55:47 +1300 +Subject: [PATCH 03/26] Sort snaps before other apps + +--- + plugins/snap/gs-plugin-snap.c | 1 + + src/gs-search-page.c | 6 ++++++ + 2 files changed, 7 insertions(+) + +diff --git a/plugins/snap/gs-plugin-snap.c b/plugins/snap/gs-plugin-snap.c +index d34948f..f9c6a34 100644 +--- a/plugins/snap/gs-plugin-snap.c ++++ b/plugins/snap/gs-plugin-snap.c +@@ -585,6 +585,7 @@ gs_plugin_add_search (GsPlugin *plugin, + + for (i = 0; i < snaps->len; i++) { + g_autoptr(GsApp) app = snap_to_app (plugin, g_ptr_array_index (snaps, i)); ++ gs_app_set_match_value (app, snaps->len - i); + gs_app_list_add (list, app); + } + +diff --git a/src/gs-search-page.c b/src/gs-search-page.c +index cdb2eee..2e7b379 100644 +--- a/src/gs-search-page.c ++++ b/src/gs-search-page.c +@@ -203,6 +203,12 @@ gs_search_page_get_app_sort_key (GsApp *app) + { + GString *key = g_string_sized_new (64); + ++ /* sort snaps before other apps */ ++ if (g_strcmp0 (gs_app_get_management_plugin (app), "snap") == 0) ++ g_string_append (key, "9:"); ++ else ++ g_string_append (key, "1:"); ++ + /* sort apps before runtimes and extensions */ + switch (gs_app_get_kind (app)) { + case AS_APP_KIND_DESKTOP: diff -Nru gnome-software-3.30.6/debian/patches/0004-Hide-Kudo-details-since-we-don-t-have-good-data.patch gnome-software-3.30.6/debian/patches/0004-Hide-Kudo-details-since-we-don-t-have-good-data.patch --- gnome-software-3.30.6/debian/patches/0004-Hide-Kudo-details-since-we-don-t-have-good-data.patch 1970-01-01 00:00:00.000000000 +0000 +++ gnome-software-3.30.6/debian/patches/0004-Hide-Kudo-details-since-we-don-t-have-good-data.patch 2019-04-16 22:49:03.000000000 +0000 @@ -0,0 +1,39 @@ +From: Robert Ancell +Date: Tue, 8 Nov 2016 09:58:05 +1300 +Subject: [PATCH 04/24] Hide Kudo details since we don't have good data + +--- + src/gs-details-page.c | 5 +++-- + src/gs-details-page.ui | 2 +- + 2 files changed, 4 insertions(+), 3 deletions(-) + +diff --git a/src/gs-details-page.c b/src/gs-details-page.c +index 68cde5a..0ff1a3f 100644 +--- a/src/gs-details-page.c ++++ b/src/gs-details-page.c +@@ -1074,9 +1074,10 @@ gs_details_page_refresh_all (GsDetailsPage *self) + + /* hide the kudo details for non-desktop software */ + switch (gs_app_get_kind (self->app)) { +- case AS_APP_KIND_DESKTOP: ++ // Hidden on Ubuntu since don't have appropriate information ++ /*case AS_APP_KIND_DESKTOP: + gtk_widget_set_visible (self->grid_details_kudo, TRUE); +- break; ++ break;*/ + default: + gtk_widget_set_visible (self->grid_details_kudo, FALSE); + break; +diff --git a/src/gs-details-page.ui b/src/gs-details-page.ui +index 206d99f..47bb99a 100644 +--- a/src/gs-details-page.ui ++++ b/src/gs-details-page.ui +@@ -620,7 +620,7 @@ + 30 + + +- True ++ False + False + 9 + 12 diff -Nru gnome-software-3.30.6/debian/patches/0005-details-Show-an-in-app-notification-when-passed-an-i.patch gnome-software-3.30.6/debian/patches/0005-details-Show-an-in-app-notification-when-passed-an-i.patch --- gnome-software-3.30.6/debian/patches/0005-details-Show-an-in-app-notification-when-passed-an-i.patch 1970-01-01 00:00:00.000000000 +0000 +++ gnome-software-3.30.6/debian/patches/0005-details-Show-an-in-app-notification-when-passed-an-i.patch 2019-04-16 22:49:03.000000000 +0000 @@ -0,0 +1,189 @@ +From: Iain Lane +Date: Tue, 4 Apr 2017 13:25:33 +0100 +Subject: [PATCH 05/24] details: Show an in-app notification when passed an + invalid file or URL + +Previously there was no feedback when gnome-software was fed a bad URL +on the commandline - show a message. +--- + src/gs-details-page.c | 44 ++++++++++++++++++++++++++++++++++++++++---- + src/gs-shell.c | 12 +----------- + src/gs-shell.h | 13 +++++++++++++ + 3 files changed, 54 insertions(+), 15 deletions(-) + +diff --git a/src/gs-details-page.c b/src/gs-details-page.c +index 0ff1a3f..08f450a 100644 +--- a/src/gs-details-page.c ++++ b/src/gs-details-page.c +@@ -1645,23 +1645,47 @@ set_app (GsDetailsPage *self, GsApp *app) + gs_details_page_app_refine2 (self); + } + ++typedef struct { ++ GsDetailsPage *page; ++ gchar *url; ++} GsDetailsFileHelper; ++ ++static void ++gs_details_page_file_helper_free (GsDetailsFileHelper *helper) ++{ ++ g_object_unref (helper->page); ++ g_free (helper->url); ++ g_free (helper); ++} ++ ++G_DEFINE_AUTOPTR_CLEANUP_FUNC(GsDetailsFileHelper, gs_details_page_file_helper_free); ++ + static void + gs_details_page_file_to_app_cb (GObject *source, + GAsyncResult *res, + gpointer user_data) + { + GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source); +- GsDetailsPage *self = GS_DETAILS_PAGE (user_data); ++ g_autoptr(GsDetailsFileHelper) helper = user_data; ++ GsDetailsPage *self = helper->page; + g_autoptr(GsAppList) list = NULL; + g_autoptr(GError) error = NULL; ++ g_autofree gchar *msg = NULL; + + list = gs_plugin_loader_job_process_finish (plugin_loader, + res, + &error); + if (list == NULL) { ++ /* TRANSLATORS: This message is shown when opening ++ * gnome-software with a path to an unhandled app, e.g. ++ * /no/such/file */ ++ msg = g_strdup_printf (_("Don't know how to handle ‘%s’"), helper->url); + g_warning ("failed to convert file to GsApp: %s", error->message); + /* go back to the overview */ + gs_shell_change_mode (self->shell, GS_SHELL_MODE_OVERVIEW, NULL, FALSE); ++ gs_shell_show_event_app_notify (self->shell, ++ msg, ++ GS_SHELL_EVENT_BUTTON_NONE); + } else { + GsApp *app = gs_app_list_index (list, 0); + set_app (self, app); +@@ -1674,17 +1698,23 @@ gs_details_page_url_to_app_cb (GObject *source, + gpointer user_data) + { + GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source); +- GsDetailsPage *self = GS_DETAILS_PAGE (user_data); ++ g_autoptr(GsDetailsFileHelper) helper = user_data; ++ GsDetailsPage *self = helper->page; + g_autoptr(GsAppList) list = NULL; + g_autoptr(GError) error = NULL; ++ g_autofree gchar *msg = NULL; + + list = gs_plugin_loader_job_process_finish (plugin_loader, + res, + &error); + if (list == NULL) { ++ msg = g_strdup_printf (_("Don't know how to handle ‘%s’"), helper->url); + g_warning ("failed to convert URL to GsApp: %s", error->message); + /* go back to the overview */ + gs_shell_change_mode (self->shell, GS_SHELL_MODE_OVERVIEW, NULL, FALSE); ++ gs_shell_show_event_app_notify (self->shell, ++ msg, ++ GS_SHELL_EVENT_BUTTON_NONE); + } else { + GsApp *app = gs_app_list_index (list, 0); + set_app (self, app); +@@ -1695,6 +1725,9 @@ void + gs_details_page_set_local_file (GsDetailsPage *self, GFile *file) + { + g_autoptr(GsPluginJob) plugin_job = NULL; ++ GsDetailsFileHelper *helper = g_new0 (GsDetailsFileHelper, 1); ++ helper->page = g_object_ref (self); ++ helper->url = g_file_get_uri (file); + gs_details_page_set_state (self, GS_DETAILS_PAGE_STATE_LOADING); + plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_FILE_TO_APP, + "file", file, +@@ -1716,13 +1749,16 @@ gs_details_page_set_local_file (GsDetailsPage *self, GFile *file) + gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job, + self->cancellable, + gs_details_page_file_to_app_cb, +- self); ++ helper); + } + + void + gs_details_page_set_url (GsDetailsPage *self, const gchar *url) + { + g_autoptr(GsPluginJob) plugin_job = NULL; ++ GsDetailsFileHelper *helper = g_new0 (GsDetailsFileHelper, 1); ++ helper->page = g_object_ref (self); ++ helper->url = g_strdup (url); + gs_details_page_set_state (self, GS_DETAILS_PAGE_STATE_LOADING); + plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_URL_TO_APP, + "search", url, +@@ -1745,7 +1781,7 @@ gs_details_page_set_url (GsDetailsPage *self, const gchar *url) + gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job, + self->cancellable, + gs_details_page_url_to_app_cb, +- self); ++ helper); + } + + static void +diff --git a/src/gs-shell.c b/src/gs-shell.c +index 6f1cda8..a03ceaf 100644 +--- a/src/gs-shell.c ++++ b/src/gs-shell.c +@@ -895,16 +895,6 @@ gs_shell_allow_updates_notify_cb (GsPluginLoader *plugin_loader, + priv->mode == GS_SHELL_MODE_UPDATES); + } + +-typedef enum { +- GS_SHELL_EVENT_BUTTON_NONE = 0, +- GS_SHELL_EVENT_BUTTON_SOURCES = 1 << 0, +- GS_SHELL_EVENT_BUTTON_NO_SPACE = 1 << 1, +- GS_SHELL_EVENT_BUTTON_NETWORK_SETTINGS = 1 << 2, +- GS_SHELL_EVENT_BUTTON_MORE_INFO = 1 << 3, +- GS_SHELL_EVENT_BUTTON_RESTART_REQUIRED = 1 << 4, +- GS_SHELL_EVENT_BUTTON_LAST +-} GsShellEventButtons; +- + static gboolean + gs_shell_has_disk_examination_app (void) + { +@@ -912,7 +902,7 @@ gs_shell_has_disk_examination_app (void) + return (baobab != NULL); + } + +-static void ++void + gs_shell_show_event_app_notify (GsShell *shell, + const gchar *title, + GsShellEventButtons buttons) +diff --git a/src/gs-shell.h b/src/gs-shell.h +index a08d46e..ce73391 100644 +--- a/src/gs-shell.h ++++ b/src/gs-shell.h +@@ -40,6 +40,16 @@ struct _GsShellClass + void (* loaded) (GsShell *shell); + }; + ++typedef enum { ++ GS_SHELL_EVENT_BUTTON_NONE = 0, ++ GS_SHELL_EVENT_BUTTON_SOURCES = 1 << 0, ++ GS_SHELL_EVENT_BUTTON_NO_SPACE = 1 << 1, ++ GS_SHELL_EVENT_BUTTON_NETWORK_SETTINGS = 1 << 2, ++ GS_SHELL_EVENT_BUTTON_MORE_INFO = 1 << 3, ++ GS_SHELL_EVENT_BUTTON_RESTART_REQUIRED = 1 << 4, ++ GS_SHELL_EVENT_BUTTON_LAST ++} GsShellEventButtons; ++ + typedef enum { + GS_SHELL_MODE_UNKNOWN, + GS_SHELL_MODE_OVERVIEW, +@@ -97,6 +107,9 @@ void gs_shell_show_extras_search (GsShell *shell, + gchar **resources); + void gs_shell_show_uri (GsShell *shell, + const gchar *url); ++void gs_shell_show_event_app_notify (GsShell *shell, ++ const gchar *title, ++ GsShellEventButtons buttons); + void gs_shell_setup (GsShell *shell, + GsPluginLoader *plugin_loader, + GCancellable *cancellable); diff -Nru gnome-software-3.30.6/debian/patches/0006-packagekit-Disable-updates.patch gnome-software-3.30.6/debian/patches/0006-packagekit-Disable-updates.patch --- gnome-software-3.30.6/debian/patches/0006-packagekit-Disable-updates.patch 1970-01-01 00:00:00.000000000 +0000 +++ gnome-software-3.30.6/debian/patches/0006-packagekit-Disable-updates.patch 2019-04-16 22:49:03.000000000 +0000 @@ -0,0 +1,53 @@ +From: Olivier Tilloy +Date: Tue, 7 Aug 2018 19:03:23 +0200 +Subject: [PATCH 06/26] packagekit: Disable updates + +In Ubuntu we use Update Manager for this currently. The long term plan is to use Software, but we're not there yet. +--- + plugins/packagekit/gs-plugin-packagekit-refresh.c | 3 +++ + plugins/packagekit/gs-plugin-packagekit.c | 3 +++ + plugins/packagekit/gs-plugin-systemd-updates.c | 3 +++ + 3 files changed, 9 insertions(+) + +diff --git a/plugins/packagekit/gs-plugin-packagekit-refresh.c b/plugins/packagekit/gs-plugin-packagekit-refresh.c +index ff893da..61263d2 100644 +--- a/plugins/packagekit/gs-plugin-packagekit-refresh.c ++++ b/plugins/packagekit/gs-plugin-packagekit-refresh.c +@@ -112,6 +112,9 @@ gs_plugin_download (GsPlugin *plugin, + GCancellable *cancellable, + GError **error) + { ++ /* In Ubuntu we're using Update Manager for this, for now (LP: #1775226) */ ++ return TRUE; ++ + g_autoptr(GsAppList) list_tmp = gs_app_list_new (); + + /* add any packages */ +diff --git a/plugins/packagekit/gs-plugin-packagekit.c b/plugins/packagekit/gs-plugin-packagekit.c +index fb240fd..f7e0c18 100644 +--- a/plugins/packagekit/gs-plugin-packagekit.c ++++ b/plugins/packagekit/gs-plugin-packagekit.c +@@ -569,6 +569,9 @@ gs_plugin_add_updates (GsPlugin *plugin, + g_autoptr(PkResults) results = NULL; + g_autoptr(GPtrArray) array = NULL; + ++ /* In Ubuntu we're using Update Manager for this, for now */ ++ return TRUE; ++ + /* do sync call */ + gs_plugin_status_update (plugin, NULL, GS_PLUGIN_STATUS_WAITING); + results = pk_client_get_updates (PK_CLIENT (priv->task), +diff --git a/plugins/packagekit/gs-plugin-systemd-updates.c b/plugins/packagekit/gs-plugin-systemd-updates.c +index 0d2cba2..e655a38 100644 +--- a/plugins/packagekit/gs-plugin-systemd-updates.c ++++ b/plugins/packagekit/gs-plugin-systemd-updates.c +@@ -279,6 +279,9 @@ gs_plugin_update (GsPlugin *plugin, + GCancellable *cancellable, + GError **error) + { ++ /* In Ubuntu we're using Update Manager for this, for now. */ ++ return TRUE; ++ + /* any are us? */ + for (guint i = 0; i < gs_app_list_length (list); i++) { + GsApp *app = gs_app_list_index (list, i); diff -Nru gnome-software-3.30.6/debian/patches/0007-snap-Only-feature-snaps.patch gnome-software-3.30.6/debian/patches/0007-snap-Only-feature-snaps.patch --- gnome-software-3.30.6/debian/patches/0007-snap-Only-feature-snaps.patch 1970-01-01 00:00:00.000000000 +0000 +++ gnome-software-3.30.6/debian/patches/0007-snap-Only-feature-snaps.patch 2019-04-16 22:49:03.000000000 +0000 @@ -0,0 +1,61 @@ +From: Robert Ancell +Date: Wed, 9 Aug 2017 15:43:02 +1200 +Subject: [PATCH 07/24] snap: Only feature snaps + +Run after the other plugins that populate featured/popular apps and remove +them when we set ours. +--- + plugins/snap/gs-plugin-snap.c | 15 ++++++++++++++- + 1 file changed, 14 insertions(+), 1 deletion(-) + +diff --git a/plugins/snap/gs-plugin-snap.c b/plugins/snap/gs-plugin-snap.c +index f9c6a34..3ceb50e 100644 +--- a/plugins/snap/gs-plugin-snap.c ++++ b/plugins/snap/gs-plugin-snap.c +@@ -79,11 +79,13 @@ gs_plugin_initialize (GsPlugin *plugin) + + gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_RUN_AFTER, "desktop-categories"); + gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_RUN_AFTER, "ubuntu-reviews"); ++ gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_RUN_AFTER, "appstream"); + gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_BETTER_THAN, "packagekit"); + gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_RUN_BEFORE, "icons"); + + /* Override hardcoded popular apps */ +- gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_RUN_BEFORE, "hardcoded-popular"); ++ gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_RUN_AFTER, "hardcoded-popular"); ++ gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_RUN_AFTER, "hardcoded-featured"); + + /* set name of MetaInfo file */ + gs_plugin_set_appstream_id (plugin, "org.gnome.Software.Plugin.Snap"); +@@ -387,6 +389,12 @@ is_banner_icon_image (const gchar *filename) + return g_regex_match_simple ("^banner-icon(?:_[a-zA-Z0-9]{7})?\\.(?:png|jpg)$", filename, 0, 0); + } + ++static gboolean ++remove_cb (GsApp *app, gpointer user_data) ++{ ++ return FALSE; ++} ++ + gboolean + gs_plugin_add_featured (GsPlugin *plugin, + GsAppList *list, +@@ -455,6 +463,8 @@ gs_plugin_add_featured (GsPlugin *plugin, + background_css->str); + gs_app_set_metadata (app, "GnomeSoftware::FeatureTile-css", css); + ++ /* replace any other featured apps with our one */ ++ gs_app_list_filter (list, remove_cb, NULL); + gs_app_list_add (list, app); + + return TRUE; +@@ -473,6 +483,9 @@ gs_plugin_add_popular (GsPlugin *plugin, + if (snaps == NULL) + return FALSE; + ++ /* replace any other popular apps with our one */ ++ gs_app_list_filter (list, remove_cb, NULL); ++ + /* skip first snap - it is used as the featured app */ + for (i = 1; i < snaps->len; i++) { + g_autoptr(GsApp) app = snap_to_app (plugin, g_ptr_array_index (snaps, i)); diff -Nru gnome-software-3.30.6/debian/patches/0008-Don-t-randomize-editors-picks.patch gnome-software-3.30.6/debian/patches/0008-Don-t-randomize-editors-picks.patch --- gnome-software-3.30.6/debian/patches/0008-Don-t-randomize-editors-picks.patch 1970-01-01 00:00:00.000000000 +0000 +++ gnome-software-3.30.6/debian/patches/0008-Don-t-randomize-editors-picks.patch 2019-04-16 22:49:03.000000000 +0000 @@ -0,0 +1,25 @@ +From: Robert Ancell +Date: Fri, 18 Aug 2017 16:27:26 +1200 +Subject: [PATCH 08/24] Don't randomize editors picks. + +The featured snaps are provided in a significant order. +https://bugs.launchpad.net/bugs/1705953 +--- + src/gs-overview-page.c | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/src/gs-overview-page.c b/src/gs-overview-page.c +index 82a0238..d345b2e 100644 +--- a/src/gs-overview-page.c ++++ b/src/gs-overview-page.c +@@ -189,7 +189,9 @@ gs_overview_page_get_popular_cb (GObject *source_object, + + /* Don't show apps from the category that's currently featured as the category of the day */ + gs_app_list_filter (list, filter_category, priv->category_of_day); +- gs_app_list_randomize (list); ++ /* Disabled on Ubuntu as we only show snaps and the order is significant ++ * https://bugs.launchpad.net/bugs/1705953 */ ++ //gs_app_list_randomize (list); + + gs_container_remove_all (GTK_CONTAINER (priv->box_popular)); + diff -Nru gnome-software-3.30.6/debian/patches/0009-Display-a-warning-for-non-sandboxed-snaps.patch gnome-software-3.30.6/debian/patches/0009-Display-a-warning-for-non-sandboxed-snaps.patch --- gnome-software-3.30.6/debian/patches/0009-Display-a-warning-for-non-sandboxed-snaps.patch 1970-01-01 00:00:00.000000000 +0000 +++ gnome-software-3.30.6/debian/patches/0009-Display-a-warning-for-non-sandboxed-snaps.patch 2019-04-16 22:49:03.000000000 +0000 @@ -0,0 +1,117 @@ +From: James Henstridge +Date: Fri, 23 Jun 2017 11:22:36 +0800 +Subject: [PATCH 09/24] Display a warning for non-sandboxed snaps. + +--- + src/gs-details-page.c | 14 ++++++++++++++ + src/gs-details-page.ui | 44 ++++++++++++++++++++++++++++++++++++++++++-- + 2 files changed, 56 insertions(+), 2 deletions(-) + +diff --git a/src/gs-details-page.c b/src/gs-details-page.c +index 08f450a..3eafb32 100644 +--- a/src/gs-details-page.c ++++ b/src/gs-details-page.c +@@ -148,6 +148,7 @@ struct _GsDetailsPage + GtkWidget *label_content_rating_none; + GtkWidget *button_details_rating_value; + GtkWidget *label_details_rating_title; ++ GtkWidget *box_not_sandboxed_warning; + }; + + G_DEFINE_TYPE (GsDetailsPage, gs_details_page, GS_TYPE_PAGE) +@@ -1083,6 +1084,18 @@ gs_details_page_refresh_all (GsDetailsPage *self) + break; + } + ++ /* Display a warning about non-sandboxed apps that may come ++ * from third party sources. Currently only checking snaps. */ ++ ret = FALSE; ++ switch (gs_app_get_bundle_kind (self->app)) { ++ case AS_BUNDLE_KIND_SNAP: ++ ret |= (kudos & GS_APP_KUDO_SANDBOXED) == 0; ++ break; ++ default: ++ break; ++ } ++ gtk_widget_set_visible (self->box_not_sandboxed_warning, ret); ++ + /* are we trying to replace something in the baseos */ + gtk_widget_set_visible (self->infobar_details_package_baseos, + gs_app_has_quirk (self->app, AS_APP_QUIRK_COMPULSORY) && +@@ -2527,6 +2540,7 @@ gs_details_page_class_init (GsDetailsPageClass *klass) + gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, label_content_rating_none); + gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, button_details_rating_value); + gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, label_details_rating_title); ++ gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, box_not_sandboxed_warning); + } + + static void +diff --git a/src/gs-details-page.ui b/src/gs-details-page.ui +index 47bb99a..9d95c1f 100644 +--- a/src/gs-details-page.ui ++++ b/src/gs-details-page.ui +@@ -612,6 +612,46 @@ + 11 + + ++ ++ ++ False ++ False ++ 30 ++ ++ ++ True ++ False ++ 16 ++ dialog-warning ++ 6 ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ True ++ False ++ This application is unconfined. It can access all personal files and system resources. ++ 0 ++ 0.5 ++ ++ ++ True ++ True ++ 1 ++ ++ ++ ++ ++ True ++ True ++ 12 ++ ++ + + + True +@@ -1162,7 +1202,7 @@ + + False + True +- 12 ++ 13 + + + +@@ -1225,7 +1265,7 @@ + + False + True +- 13 ++ 14 + + + diff -Nru gnome-software-3.30.6/debian/patches/0010-Sort-category-snaps-before-other-packages.patch gnome-software-3.30.6/debian/patches/0010-Sort-category-snaps-before-other-packages.patch --- gnome-software-3.30.6/debian/patches/0010-Sort-category-snaps-before-other-packages.patch 1970-01-01 00:00:00.000000000 +0000 +++ gnome-software-3.30.6/debian/patches/0010-Sort-category-snaps-before-other-packages.patch 2019-04-16 22:49:03.000000000 +0000 @@ -0,0 +1,37 @@ +From: Robert Ancell +Date: Mon, 22 Jan 2018 16:50:29 +1300 +Subject: [PATCH 10/24] Sort category snaps before other packages + +--- + src/gs-category-page.c | 11 +++++++++-- + 1 file changed, 9 insertions(+), 2 deletions(-) + +diff --git a/src/gs-category-page.c b/src/gs-category-page.c +index e0ef772..fe2e2d3 100644 +--- a/src/gs-category-page.c ++++ b/src/gs-category-page.c +@@ -188,6 +188,8 @@ gs_category_page_get_apps_cb (GObject *source_object, + static gboolean + _max_results_sort_cb (GsApp *app1, GsApp *app2, gpointer user_data) + { ++ if (gs_app_get_bundle_kind (app1) == AS_BUNDLE_KIND_SNAP || gs_app_get_bundle_kind (app2) == AS_BUNDLE_KIND_SNAP) ++ return gs_app_get_bundle_kind (app1) == AS_BUNDLE_KIND_SNAP ? -1 : 1; + return gs_app_get_rating (app1) < gs_app_get_rating (app2); + } + +@@ -208,8 +210,13 @@ gs_category_page_sort_flow_box_sort_func (GtkFlowBoxChild *child1, + sort_type = GS_CATEGORY_PAGE (data)->sort_type; + + if (sort_type == SUBCATEGORY_SORT_TYPE_RATING) { +- gint rating_app1 = gs_app_get_rating (app1); +- gint rating_app2 = gs_app_get_rating (app2); ++ gint rating_app1, rating_app2; ++ ++ if (gs_app_get_bundle_kind (app1) == AS_BUNDLE_KIND_SNAP || gs_app_get_bundle_kind (app2) == AS_BUNDLE_KIND_SNAP) ++ return gs_app_get_bundle_kind (app1) == AS_BUNDLE_KIND_SNAP ? -1 : 1; ++ ++ rating_app1 = gs_app_get_rating (app1); ++ rating_app2 = gs_app_get_rating (app2); + if (rating_app1 > rating_app2) + return -1; + if (rating_app1 < rating_app2) diff -Nru gnome-software-3.30.6/debian/patches/0011-Support-snap-channels.patch gnome-software-3.30.6/debian/patches/0011-Support-snap-channels.patch --- gnome-software-3.30.6/debian/patches/0011-Support-snap-channels.patch 1970-01-01 00:00:00.000000000 +0000 +++ gnome-software-3.30.6/debian/patches/0011-Support-snap-channels.patch 2019-04-16 22:49:03.000000000 +0000 @@ -0,0 +1,1696 @@ +From: Robert Ancell +Date: Thu, 23 Nov 2017 10:54:20 +1300 +Subject: [PATCH 11/24] Support snap channels + +--- + lib/gs-app.c | 91 ++++++++++++++ + lib/gs-app.h | 7 ++ + lib/gs-channel.c | 116 ++++++++++++++++++ + lib/gs-channel.h | 44 +++++++ + lib/gs-plugin-job-private.h | 1 + + lib/gs-plugin-job.c | 31 +++++ + lib/gs-plugin-job.h | 2 + + lib/gs-plugin-loader.c | 13 ++ + lib/gs-plugin-types.h | 4 + + lib/gs-plugin-vfuncs.h | 18 +++ + lib/gs-plugin.c | 6 + + lib/gs-self-test.c | 16 +++ + lib/gs-utils.c | 35 ++++++ + lib/gs-utils.h | 2 + + lib/meson.build | 2 + + plugins/dummy/gs-plugin-dummy.c | 11 ++ + plugins/snap/gs-plugin-snap.c | 254 +++++++++++++++++++++++++++++++++++++--- + src/gs-details-page.c | 199 +++++++++++++++++++++++++++++-- + src/gs-details-page.ui | 104 +++++++++++++--- + src/gtk-style.css | 31 +++++ + 20 files changed, 944 insertions(+), 43 deletions(-) + create mode 100644 lib/gs-channel.c + create mode 100644 lib/gs-channel.h + +diff --git a/lib/gs-app.c b/lib/gs-app.c +index 6fbfeac..c2b486f 100644 +--- a/lib/gs-app.c ++++ b/lib/gs-app.c +@@ -126,6 +126,8 @@ typedef struct + AsContentRating *content_rating; + GdkPixbuf *pixbuf; + GsPrice *price; ++ GPtrArray *channels; ++ GsChannel *active_channel; + GCancellable *cancellable; + GsPluginAction pending_action; + } GsAppPrivate; +@@ -595,6 +597,18 @@ gs_app_to_string_append (GsApp *app, GString *str) + gs_app_kv_lpad (str, "keyword", tmp); + } + } ++ for (i = 0; i < priv->channels->len; i++) { ++ GsChannel *channel = g_ptr_array_index (priv->channels, i); ++ g_autofree gchar *key = NULL; ++ key = g_strdup_printf ("channel-%02u", i); ++ gs_app_kv_printf (str, key, "%s [%s]", ++ gs_channel_get_name (channel), ++ gs_channel_get_version (channel)); ++ } ++ if (priv->active_channel != NULL) { ++ gs_app_kv_printf (str, "active-channel", "%s", ++ gs_channel_get_name (priv->active_channel)); ++ } + keys = g_hash_table_get_keys (priv->metadata); + for (GList *l = keys; l != NULL; l = l->next) { + GVariant *val; +@@ -3989,6 +4003,80 @@ gs_app_get_priority (GsApp *app) + return priv->priority; + } + ++/** ++ * gs_app_add_channel: ++ * @app: a #GsApp ++ * @channel: a #GsChannel ++ * ++ * Adds a channel to the application. ++ * ++ * Since: 3.28 ++ **/ ++void ++gs_app_add_channel (GsApp *app, GsChannel *channel) ++{ ++ GsAppPrivate *priv = gs_app_get_instance_private (app); ++ g_return_if_fail (GS_IS_APP (app)); ++ g_return_if_fail (GS_IS_CHANNEL (channel)); ++ g_ptr_array_add (priv->channels, g_object_ref (channel)); ++ if (priv->active_channel == NULL && gs_channel_get_version (channel) != NULL) ++ priv->active_channel = g_object_ref (channel); ++} ++ ++/** ++ * gs_app_get_channels: ++ * @app: a #GsApp ++ * ++ * Gets the list of channels. ++ * ++ * Returns: (element-type GsChannel) (transfer none): a list ++ * ++ * Since: 3.28 ++ **/ ++GPtrArray * ++gs_app_get_channels (GsApp *app) ++{ ++ GsAppPrivate *priv = gs_app_get_instance_private (app); ++ g_return_val_if_fail (GS_IS_APP (app), NULL); ++ return priv->channels; ++} ++ ++/** ++ * gs_app_set_active_channel: ++ * @app: a #GsApp ++ * @channel: a #GsChannel ++ * ++ * Set the currently active channel. ++ * ++ * Since: 3.28 ++ **/ ++void ++gs_app_set_active_channel (GsApp *app, GsChannel *channel) ++{ ++ GsAppPrivate *priv = gs_app_get_instance_private (app); ++ g_return_if_fail (GS_IS_APP (app)); ++ g_return_if_fail (GS_IS_CHANNEL (channel)); ++ g_set_object (&priv->active_channel, channel); ++} ++ ++/** ++ * gs_app_get_active_channel: ++ * @app: a #GsApp ++ * ++ * Gets the currently active channel. ++ * ++ * Returns: a #GsChannel or %NULL. ++ * ++ * Since: 3.28 ++ **/ ++GsChannel * ++gs_app_get_active_channel (GsApp *app) ++{ ++ GsAppPrivate *priv = gs_app_get_instance_private (app); ++ g_return_val_if_fail (GS_IS_APP (app), NULL); ++ return priv->active_channel; ++} ++ + /** + * gs_app_get_cancellable: + * @app: a #GsApp +@@ -4170,6 +4258,8 @@ gs_app_dispose (GObject *object) + g_clear_pointer (&priv->reviews, g_ptr_array_unref); + g_clear_pointer (&priv->provides, g_ptr_array_unref); + g_clear_pointer (&priv->icons, g_ptr_array_unref); ++ g_clear_pointer (&priv->channels, g_ptr_array_unref); ++ g_clear_object (&priv->active_channel); + + G_OBJECT_CLASS (gs_app_parent_class)->dispose (object); + } +@@ -4357,6 +4447,7 @@ gs_app_init (GsApp *app) + priv->reviews = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); + priv->provides = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); + priv->icons = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); ++ priv->channels = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); + priv->metadata = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, +diff --git a/lib/gs-app.h b/lib/gs-app.h +index f05779a..225d870 100644 +--- a/lib/gs-app.h ++++ b/lib/gs-app.h +@@ -28,6 +28,7 @@ + #include + #include + ++#include "gs-channel.h" + #include "gs-price.h" + + G_BEGIN_DECLS +@@ -325,6 +326,12 @@ void gs_app_remove_quirk (GsApp *app, + AsAppQuirk quirk); + gboolean gs_app_is_installed (GsApp *app); + gboolean gs_app_is_updatable (GsApp *app); ++GPtrArray *gs_app_get_channels (GsApp *app); ++void gs_app_add_channel (GsApp *app, ++ GsChannel *channel); ++void gs_app_set_active_channel (GsApp *app, ++ GsChannel *channel); ++GsChannel *gs_app_get_active_channel (GsApp *app); + G_END_DECLS + + #endif /* __GS_APP_H */ +diff --git a/lib/gs-channel.c b/lib/gs-channel.c +new file mode 100644 +index 0000000..658a33b +--- /dev/null ++++ b/lib/gs-channel.c +@@ -0,0 +1,116 @@ ++/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- ++ * ++ * Copyright (C) 2017 Canonical Ltd. ++ * ++ * 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" ++ ++#include ++ ++#include "gs-channel.h" ++ ++struct _GsChannel ++{ ++ GObject parent_instance; ++ ++ gchar *name; ++ gchar *version; ++}; ++ ++G_DEFINE_TYPE (GsChannel, gs_channel, G_TYPE_OBJECT) ++ ++/** ++ * gs_channel_get_name: ++ * @channel: a #GsChannel ++ * ++ * Get the channel name. ++ * ++ * Returns: a channel name. ++ * ++ * Since: 3.28 ++ */ ++const gchar * ++gs_channel_get_name (GsChannel *channel) ++{ ++ g_return_val_if_fail (GS_IS_CHANNEL (channel), NULL); ++ return channel->name; ++} ++ ++/** ++ * gs_channel_get_version: ++ * @channel: a #GsChannel ++ * ++ * Get the channel version. ++ * ++ * Returns: a channel version. ++ * ++ * Since: 3.28 ++ */ ++const gchar * ++gs_channel_get_version (GsChannel *channel) ++{ ++ g_return_val_if_fail (GS_IS_CHANNEL (channel), NULL); ++ return channel->version; ++} ++ ++static void ++gs_channel_finalize (GObject *object) ++{ ++ GsChannel *channel = GS_CHANNEL (object); ++ ++ g_free (channel->name); ++ g_free (channel->version); ++ ++ G_OBJECT_CLASS (gs_channel_parent_class)->finalize (object); ++} ++ ++static void ++gs_channel_class_init (GsChannelClass *klass) ++{ ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ object_class->finalize = gs_channel_finalize; ++} ++ ++static void ++gs_channel_init (GsChannel *channel) ++{ ++} ++ ++/** ++ * gs_channel_new: ++ * @name: the name of the channel. ++ * @version: the version this channel is providing. ++ * ++ * Creates a new channel object. ++ * ++ * Return value: a new #GsChannel object. ++ * ++ * Since: 3.28 ++ **/ ++GsChannel * ++gs_channel_new (const gchar *name, const gchar *version) ++{ ++ GsChannel *channel; ++ channel = g_object_new (GS_TYPE_CHANNEL, NULL); ++ channel->name = g_strdup (name); ++ channel->version = g_strdup (version); ++ return GS_CHANNEL (channel); ++} ++ ++/* vim: set noexpandtab: */ +diff --git a/lib/gs-channel.h b/lib/gs-channel.h +new file mode 100644 +index 0000000..64610ab +--- /dev/null ++++ b/lib/gs-channel.h +@@ -0,0 +1,44 @@ ++ /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- ++ * ++ * Copyright (C) 2017 Canonical Ltd. ++ * ++ * 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. ++ */ ++ ++#ifndef __GS_CHANNEL_H ++#define __GS_CHANNEL_H ++ ++#include ++ ++G_BEGIN_DECLS ++ ++#define GS_TYPE_CHANNEL (gs_channel_get_type ()) ++ ++G_DECLARE_FINAL_TYPE (GsChannel, gs_channel, GS, CHANNEL, GObject) ++ ++GsChannel *gs_channel_new (const gchar *name, ++ const gchar *version); ++ ++const gchar *gs_channel_get_name (GsChannel *channel); ++ ++const gchar *gs_channel_get_version (GsChannel *channel); ++ ++G_END_DECLS ++ ++#endif /* __GS_CHANNEL_H */ ++ ++/* vim: set noexpandtab: */ +diff --git a/lib/gs-plugin-job-private.h b/lib/gs-plugin-job-private.h +index 6daadd0..7a85910 100644 +--- a/lib/gs-plugin-job-private.h ++++ b/lib/gs-plugin-job-private.h +@@ -52,6 +52,7 @@ GsPlugin *gs_plugin_job_get_plugin (GsPluginJob *self); + GsCategory *gs_plugin_job_get_category (GsPluginJob *self); + AsReview *gs_plugin_job_get_review (GsPluginJob *self); + GsPrice *gs_plugin_job_get_price (GsPluginJob *self); ++GsChannel *gs_plugin_job_get_channel (GsPluginJob *self); + gchar *gs_plugin_job_to_string (GsPluginJob *self); + void gs_plugin_job_set_action (GsPluginJob *self, + GsPluginAction action); +diff --git a/lib/gs-plugin-job.c b/lib/gs-plugin-job.c +index 74cec3c..d9c0722 100644 +--- a/lib/gs-plugin-job.c ++++ b/lib/gs-plugin-job.c +@@ -48,6 +48,7 @@ struct _GsPluginJob + GsCategory *category; + AsReview *review; + GsPrice *price; ++ GsChannel *channel; + gint64 time_created; + }; + +@@ -67,6 +68,7 @@ enum { + PROP_REVIEW, + PROP_MAX_RESULTS, + PROP_PRICE, ++ PROP_CHANNEL, + PROP_TIMEOUT, + PROP_LAST + }; +@@ -125,6 +127,9 @@ gs_plugin_job_to_string (GsPluginJob *self) + g_autofree gchar *price_string = gs_price_to_string (self->price); + g_string_append_printf (str, " with price=%s", price_string); + } ++ if (self->channel != NULL) { ++ g_string_append_printf (str, " with channel=%s", gs_channel_get_name (self->channel)); ++ } + if (self->auth != NULL) { + g_string_append_printf (str, " with auth=%s", + gs_auth_get_provider_id (self->auth)); +@@ -435,6 +440,20 @@ gs_plugin_job_get_price (GsPluginJob *self) + return self->price; + } + ++void ++gs_plugin_job_set_channel (GsPluginJob *self, GsChannel *channel) ++{ ++ g_return_if_fail (GS_IS_PLUGIN_JOB (self)); ++ g_set_object (&self->channel, channel); ++} ++ ++GsChannel * ++gs_plugin_job_get_channel (GsPluginJob *self) ++{ ++ g_return_val_if_fail (GS_IS_PLUGIN_JOB (self), NULL); ++ return self->channel; ++} ++ + static void + gs_plugin_job_get_property (GObject *obj, guint prop_id, GValue *value, GParamSpec *pspec) + { +@@ -480,6 +499,9 @@ gs_plugin_job_get_property (GObject *obj, guint prop_id, GValue *value, GParamSp + case PROP_PRICE: + g_value_set_object (value, self->price); + break; ++ case PROP_CHANNEL: ++ g_value_set_object (value, self->channel); ++ break; + case PROP_MAX_RESULTS: + g_value_set_uint (value, self->max_results); + break; +@@ -543,6 +565,9 @@ gs_plugin_job_set_property (GObject *obj, guint prop_id, const GValue *value, GP + case PROP_PRICE: + gs_plugin_job_set_price (self, g_value_get_object (value)); + break; ++ case PROP_CHANNEL: ++ gs_plugin_job_set_channel (self, g_value_get_object (value)); ++ break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec); + break; +@@ -562,6 +587,7 @@ gs_plugin_job_finalize (GObject *obj) + g_clear_object (&self->category); + g_clear_object (&self->review); + g_clear_object (&self->price); ++ g_clear_object (&self->channel); + G_OBJECT_CLASS (gs_plugin_job_parent_class)->finalize (obj); + } + +@@ -651,6 +677,11 @@ gs_plugin_job_class_init (GsPluginJobClass *klass) + GS_TYPE_PRICE, + G_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_PRICE, pspec); ++ ++ pspec = g_param_spec_object ("channel", NULL, NULL, ++ GS_TYPE_CHANNEL, ++ G_PARAM_READWRITE); ++ g_object_class_install_property (object_class, PROP_CHANNEL, pspec); + } + + static void +diff --git a/lib/gs-plugin-job.h b/lib/gs-plugin-job.h +index fdca686..cfd9af7 100644 +--- a/lib/gs-plugin-job.h ++++ b/lib/gs-plugin-job.h +@@ -70,6 +70,8 @@ void gs_plugin_job_set_review (GsPluginJob *self, + AsReview *review); + void gs_plugin_job_set_price (GsPluginJob *self, + GsPrice *price); ++void gs_plugin_job_set_channel (GsPluginJob *self, ++ GsChannel *channel); + + #define gs_plugin_job_newv(a,...) GS_PLUGIN_JOB(g_object_new(GS_TYPE_PLUGIN_JOB, "action", a, __VA_ARGS__)) + +diff --git a/lib/gs-plugin-loader.c b/lib/gs-plugin-loader.c +index a6bf67c..884533c 100644 +--- a/lib/gs-plugin-loader.c ++++ b/lib/gs-plugin-loader.c +@@ -135,6 +135,11 @@ typedef gboolean (*GsPluginPurchaseFunc) (GsPlugin *plugin, + GsPrice *price, + GCancellable *cancellable, + GError **error); ++typedef gboolean (*GsPluginSwitchChannelFunc) (GsPlugin *plugin, ++ GsApp *app, ++ GsChannel *channel, ++ GCancellable *cancellable, ++ GError **error); + typedef gboolean (*GsPluginReviewFunc) (GsPlugin *plugin, + GsApp *app, + AsReview *review, +@@ -657,6 +662,14 @@ gs_plugin_loader_call_vfunc (GsPluginLoaderHelper *helper, + cancellable, &error_local); + } + break; ++ case GS_PLUGIN_ACTION_SWITCH_CHANNEL: ++ { ++ GsPluginSwitchChannelFunc plugin_func = func; ++ ret = plugin_func (plugin, app, ++ gs_plugin_job_get_channel (helper->plugin_job), ++ cancellable, &error_local); ++ } ++ break; + case GS_PLUGIN_ACTION_REVIEW_SUBMIT: + case GS_PLUGIN_ACTION_REVIEW_UPVOTE: + case GS_PLUGIN_ACTION_REVIEW_DOWNVOTE: +diff --git a/lib/gs-plugin-types.h b/lib/gs-plugin-types.h +index 6cdd28a..2c968b5 100644 +--- a/lib/gs-plugin-types.h ++++ b/lib/gs-plugin-types.h +@@ -154,6 +154,7 @@ typedef enum { + * @GS_PLUGIN_REFINE_FLAGS_REQUIRE_RUNTIME: Require the runtime + * @GS_PLUGIN_REFINE_FLAGS_REQUIRE_SCREENSHOTS: Require screenshot information + * @GS_PLUGIN_REFINE_FLAGS_REQUIRE_CHANGELOG: Require the changelog ++ * @GS_PLUGIN_REFINE_FLAGS_REQUIRE_CHANNELS: Require channel information + * + * The refine flags. + **/ +@@ -186,6 +187,7 @@ typedef enum { + #define GS_PLUGIN_REFINE_FLAGS_REQUIRE_RUNTIME ((guint64) 1 << 25) + #define GS_PLUGIN_REFINE_FLAGS_REQUIRE_SCREENSHOTS ((guint64) 1 << 26) + #define GS_PLUGIN_REFINE_FLAGS_REQUIRE_CHANGELOG ((guint64) 1 << 27) ++#define GS_PLUGIN_REFINE_FLAGS_REQUIRE_CHANNELS ((guint64) 1 << 28) + typedef guint64 GsPluginRefineFlags; + + /** +@@ -253,6 +255,7 @@ typedef enum { + * @GS_PLUGIN_ACTION_DESTROY: Destroy the plugin + * @GS_PLUGIN_ACTION_PURCHASE: Purchase an app + * @GS_PLUGIN_ACTION_DOWNLOAD: Download an application ++ * @GS_PLUGIN_ACTION_SWITCH_CHANNEL: Switch app channel + * + * The plugin action. + **/ +@@ -301,6 +304,7 @@ typedef enum { + GS_PLUGIN_ACTION_DESTROY, + GS_PLUGIN_ACTION_PURCHASE, + GS_PLUGIN_ACTION_DOWNLOAD, ++ GS_PLUGIN_ACTION_SWITCH_CHANNEL, + /*< private >*/ + GS_PLUGIN_ACTION_LAST + } GsPluginAction; +diff --git a/lib/gs-plugin-vfuncs.h b/lib/gs-plugin-vfuncs.h +index 121dc51..61609b6 100644 +--- a/lib/gs-plugin-vfuncs.h ++++ b/lib/gs-plugin-vfuncs.h +@@ -591,6 +591,24 @@ gboolean gs_plugin_app_install (GsPlugin *plugin, + GCancellable *cancellable, + GError **error); + ++/** ++ * gs_plugin_app_switch_channel: ++ * @plugin: a #GsPlugin ++ * @app: a #GsApp ++ * @channel: a #GsChannel ++ * @cancellable: a #GCancellable, or %NULL ++ * @error: a #GError, or %NULL ++ * ++ * Set the app chanel. ++ * ++ * Returns: %TRUE for success or if not relevant ++ **/ ++gboolean gs_plugin_app_switch_channel (GsPlugin *plugin, ++ GsApp *app, ++ GsChannel *channel, ++ GCancellable *cancellable, ++ GError **error); ++ + /** + * gs_plugin_app_remove: + * @plugin: a #GsPlugin +diff --git a/lib/gs-plugin.c b/lib/gs-plugin.c +index 6d7518a..31bb08b 100644 +--- a/lib/gs-plugin.c ++++ b/lib/gs-plugin.c +@@ -1763,6 +1763,8 @@ gs_plugin_action_to_function_name (GsPluginAction action) + return "gs_plugin_destroy"; + if (action == GS_PLUGIN_ACTION_PURCHASE) + return "gs_plugin_app_purchase"; ++ if (action == GS_PLUGIN_ACTION_SWITCH_CHANNEL) ++ return "gs_plugin_app_switch_channel"; + return NULL; + } + +@@ -1865,6 +1867,8 @@ gs_plugin_action_to_string (GsPluginAction action) + return "destroy"; + if (action == GS_PLUGIN_ACTION_PURCHASE) + return "purchase"; ++ if (action == GS_PLUGIN_ACTION_SWITCH_CHANNEL) ++ return "switch-channel"; + return NULL; + } + +@@ -1967,6 +1971,8 @@ gs_plugin_action_from_string (const gchar *action) + return GS_PLUGIN_ACTION_DESTROY; + if (g_strcmp0 (action, "purchase") == 0) + return GS_PLUGIN_ACTION_PURCHASE; ++ if (g_strcmp0 (action, "switch-channel") == 0) ++ return GS_PLUGIN_ACTION_SWITCH_CHANNEL; + return GS_PLUGIN_ACTION_UNKNOWN; + } + +diff --git a/lib/gs-self-test.c b/lib/gs-self-test.c +index e8f5230..751b33b 100644 +--- a/lib/gs-self-test.c ++++ b/lib/gs-self-test.c +@@ -44,6 +44,11 @@ gs_utils_url_func (void) + g_autofree gchar *path3 = NULL; + g_autofree gchar *scheme1 = NULL; + g_autofree gchar *scheme2 = NULL; ++ g_autofree gchar *value1 = NULL; ++ g_autofree gchar *value2 = NULL; ++ g_autofree gchar *value3 = NULL; ++ g_autofree gchar *value4 = NULL; ++ g_autofree gchar *value5 = NULL; + + scheme1 = gs_utils_get_url_scheme ("appstream://gimp.desktop"); + g_assert_cmpstr (scheme1, ==, "appstream"); +@@ -56,6 +61,17 @@ gs_utils_url_func (void) + g_assert_cmpstr (path2, ==, "gimp.desktop"); + path3 = gs_utils_get_url_path ("apt:/gimp"); + g_assert_cmpstr (path3, ==, "gimp"); ++ ++ value1 = gs_utils_get_url_query_param ("snap://moon-buggy", "channel"); ++ g_assert_null (value1); ++ value2 = gs_utils_get_url_query_param ("snap://moon-buggy?", "channel"); ++ g_assert_null (value2); ++ value3 = gs_utils_get_url_query_param ("snap://moon-buggy?channel=beta", "channel"); ++ g_assert_cmpstr (value3, ==, "beta"); ++ value4 = gs_utils_get_url_query_param ("snap://moon-buggy?channel=beta&foo=bar", "channel"); ++ g_assert_cmpstr (value4, ==, "beta"); ++ value5 = gs_utils_get_url_query_param ("snap://moon-buggy?foo=bar&channel=beta", "channel"); ++ g_assert_cmpstr (value5, ==, "beta"); + } + + static void +diff --git a/lib/gs-utils.c b/lib/gs-utils.c +index f95e7fb..978e256 100644 +--- a/lib/gs-utils.c ++++ b/lib/gs-utils.c +@@ -1048,6 +1048,41 @@ gs_utils_get_url_path (const gchar *url) + return g_strdup (path); + } + ++/** ++ * gs_utils_get_url_query: ++ * @url: A URL, e.g. "snap://moon-buggy?channel=beta" ++ * @url: A parameter name, e.g. "channel" ++ * ++ * Gets a query parameter from the URL string. ++ * ++ * Returns: the URL query parameter, e.g. "beta" ++ */ ++gchar * ++gs_utils_get_url_query_param (const gchar *url, const gchar *name) ++{ ++ g_autoptr(SoupURI) uri = NULL; ++ const gchar *query; ++ g_autofree gchar *prefix = NULL; ++ g_auto(GStrv) params = NULL; ++ int i; ++ ++ uri = soup_uri_new (url); ++ if (!SOUP_URI_IS_VALID (uri)) ++ return NULL; ++ ++ query = soup_uri_get_query (uri); ++ if (query == NULL) ++ return NULL; ++ params = g_strsplit (query, "&", -1); ++ prefix = g_strdup_printf ("%s=", name); ++ for (i = 0; params[i] != NULL; i++) { ++ if (g_str_has_prefix (params[i], prefix)) ++ return g_strdup (params[i] + strlen (prefix)); ++ } ++ ++ return NULL; ++} ++ + /** + * gs_user_agent: + * +diff --git a/lib/gs-utils.h b/lib/gs-utils.h +index 792a815..d5819f0 100644 +--- a/lib/gs-utils.h ++++ b/lib/gs-utils.h +@@ -93,6 +93,8 @@ gboolean gs_utils_is_low_resolution (GtkWidget *toplevel); + + gchar *gs_utils_get_url_scheme (const gchar *url); + gchar *gs_utils_get_url_path (const gchar *url); ++gchar *gs_utils_get_url_query_param (const gchar *url, ++ const gchar *name); + const gchar *gs_user_agent (void); + void gs_utils_append_key_value (GString *str, + gsize align_len, +diff --git a/lib/meson.build b/lib/meson.build +index 7b11e72..ae5ad87 100644 +--- a/lib/meson.build ++++ b/lib/meson.build +@@ -42,6 +42,7 @@ install_headers([ + 'gs-app-list.h', + 'gs-auth.h', + 'gs-category.h', ++ 'gs-channel.h', + 'gs-os-release.h', + 'gs-plugin.h', + 'gs-plugin-event.h', +@@ -76,6 +77,7 @@ libgnomesoftware = static_library( + 'gs-app-list.c', + 'gs-auth.c', + 'gs-category.c', ++ 'gs-channel.c', + 'gs-debug.c', + 'gs-ioprio.c', + 'gs-ioprio.h', +diff --git a/plugins/dummy/gs-plugin-dummy.c b/plugins/dummy/gs-plugin-dummy.c +index 623092c..2d81c5a 100644 +--- a/plugins/dummy/gs-plugin-dummy.c ++++ b/plugins/dummy/gs-plugin-dummy.c +@@ -834,6 +834,17 @@ gs_plugin_refresh (GsPlugin *plugin, + return gs_plugin_dummy_delay (plugin, app, 3100, cancellable, error); + } + ++gboolean ++gs_plugin_app_switch_channel (GsPlugin *plugin, ++ GsApp *app, ++ GsChannel *channel, ++ GCancellable *cancellable, ++ GError **error) ++{ ++ g_debug ("Switching channel to %s", gs_channel_get_name (channel)); ++ return TRUE; ++} ++ + gboolean + gs_plugin_app_upgrade_download (GsPlugin *plugin, GsApp *app, + GCancellable *cancellable, GError **error) +diff --git a/plugins/snap/gs-plugin-snap.c b/plugins/snap/gs-plugin-snap.c +index 3ceb50e..c943c64 100644 +--- a/plugins/snap/gs-plugin-snap.c ++++ b/plugins/snap/gs-plugin-snap.c +@@ -36,6 +36,27 @@ struct GsPluginData { + GHashTable *store_snaps; + }; + ++typedef struct { ++ SnapdSnap *snap; ++ gboolean full_details; ++} CacheEntry; ++ ++static CacheEntry * ++cache_entry_new (SnapdSnap *snap, gboolean full_details) ++{ ++ CacheEntry *entry = g_slice_new (CacheEntry); ++ entry->snap = g_object_ref (snap); ++ entry->full_details = full_details; ++ return entry; ++} ++ ++static void ++cache_entry_free (CacheEntry *entry) ++{ ++ g_object_unref (entry->snap); ++ g_slice_free (CacheEntry, entry); ++} ++ + static SnapdClient * + get_client (GsPlugin *plugin, GError **error) + { +@@ -70,7 +91,7 @@ gs_plugin_initialize (GsPlugin *plugin) + } + + priv->store_snaps = g_hash_table_new_full (g_str_hash, g_str_equal, +- g_free, (GDestroyNotify) g_object_unref); ++ g_free, (GDestroyNotify) cache_entry_free); + + priv->auth = gs_auth_new ("snapd"); + gs_auth_set_provider_name (priv->auth, "Snap Store"); +@@ -222,21 +243,24 @@ gs_plugin_setup (GsPlugin *plugin, GCancellable *cancellable, GError **error) + } + + static SnapdSnap * +-store_snap_cache_lookup (GsPlugin *plugin, const gchar *name) ++store_snap_cache_lookup (GsPlugin *plugin, const gchar *name, gboolean need_details) + { + GsPluginData *priv = gs_plugin_get_data (plugin); ++ CacheEntry *entry; + g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&priv->store_snaps_lock); +- SnapdSnap *snap; + +- snap = g_hash_table_lookup (priv->store_snaps, name); +- if (snap == NULL) ++ entry = g_hash_table_lookup (priv->store_snaps, name); ++ if (entry == NULL) + return NULL; + +- return g_object_ref (snap); ++ if (need_details && !entry->full_details) ++ return NULL; ++ ++ return g_object_ref (entry->snap); + } + + static void +-store_snap_cache_update (GsPlugin *plugin, GPtrArray *snaps) ++store_snap_cache_update (GsPlugin *plugin, GPtrArray *snaps, gboolean full_details) + { + GsPluginData *priv = gs_plugin_get_data (plugin); + g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&priv->store_snaps_lock); +@@ -244,7 +268,7 @@ store_snap_cache_update (GsPlugin *plugin, GPtrArray *snaps) + + for (i = 0; i < snaps->len; i++) { + SnapdSnap *snap = snaps->pdata[i]; +- g_hash_table_insert (priv->store_snaps, g_strdup (snapd_snap_get_name (snap)), g_object_ref (snap)); ++ g_hash_table_insert (priv->store_snaps, g_strdup (snapd_snap_get_name (snap)), cache_entry_new (snap, full_details)); + } + } + +@@ -264,7 +288,7 @@ find_snaps (GsPlugin *plugin, SnapdFindFlags flags, const gchar *section, const + return NULL; + } + +- store_snap_cache_update (plugin, snaps); ++ store_snap_cache_update (plugin, snaps, flags & SNAPD_FIND_FLAGS_MATCH_NAME); + + return g_steal_pointer (&snaps); + } +@@ -324,6 +348,8 @@ snap_to_app (GsPlugin *plugin, SnapdSnap *snap) + + if (priv->system_confinement == SNAPD_SYSTEM_CONFINEMENT_STRICT && confinement == SNAPD_CONFINEMENT_STRICT) + gs_app_add_kudo (app, GS_APP_KUDO_SANDBOXED); ++ else ++ gs_app_remove_kudo (app, GS_APP_KUDO_SANDBOXED); + + return g_steal_pointer (&app); + } +@@ -339,6 +365,7 @@ gs_plugin_url_to_app (GsPlugin *plugin, + g_autofree gchar *path = NULL; + g_autoptr(GPtrArray) snaps = NULL; + g_autoptr(GsApp) app = NULL; ++ g_autofree gchar *channel_name = NULL; + + /* not us */ + scheme = gs_utils_get_url_scheme (url); +@@ -352,6 +379,9 @@ gs_plugin_url_to_app (GsPlugin *plugin, + return TRUE; + + app = snap_to_app (plugin, g_ptr_array_index (snaps, 0)); ++ channel_name = gs_utils_get_url_query_param (url, "channel"); ++ if (channel_name != NULL) ++ gs_app_set_metadata (app, "snap::channel", channel_name); + gs_app_list_add (list, app); + + return TRUE; +@@ -606,13 +636,13 @@ gs_plugin_add_search (GsPlugin *plugin, + } + + static SnapdSnap * +-get_store_snap (GsPlugin *plugin, const gchar *name, GCancellable *cancellable, GError **error) ++get_store_snap (GsPlugin *plugin, const gchar *name, gboolean need_details, GCancellable *cancellable, GError **error) + { + SnapdSnap *snap = NULL; + g_autoptr(GPtrArray) snaps = NULL; + + /* use cached version if available */ +- snap = store_snap_cache_lookup (plugin, name); ++ snap = store_snap_cache_lookup (plugin, name, need_details); + if (snap != NULL) + return g_object_ref (snap); + +@@ -767,7 +797,7 @@ load_icon (GsPlugin *plugin, SnapdClient *client, GsApp *app, const gchar *id, S + } + + if (store_snap == NULL) +- store_snap = get_store_snap (plugin, gs_app_get_metadata_item (app, "snap::name"), cancellable, NULL); ++ store_snap = get_store_snap (plugin, gs_app_get_metadata_item (app, "snap::name"), FALSE, cancellable, NULL); + if (store_snap != NULL) + return load_store_icon (app, store_snap); + +@@ -783,6 +813,102 @@ gs_plugin_snap_get_description_safe (SnapdSnap *snap) + return g_string_free (str, FALSE); + } + ++static void ++add_channel (GsApp *app, const gchar *name, const gchar *version) ++{ ++ g_autoptr(GsChannel) c = NULL; ++ ++ c = gs_channel_new (name, version); ++ gs_app_add_channel (app, c); ++} ++ ++static int ++compare_branch_names (gconstpointer a, gconstpointer b) ++{ ++ SnapdChannel *channel_a = *((SnapdChannel **) a); ++ SnapdChannel *channel_b = *((SnapdChannel **) b); ++ return g_strcmp0 (snapd_channel_get_name (channel_a), snapd_channel_get_name (channel_b)); ++} ++ ++static void ++refine_channels (GsApp *app, SnapdSnap *snap) ++{ ++ gchar **tracks; ++ guint i; ++ ++ /* already refined... */ ++ if (gs_app_get_channels (app)->len > 0) ++ return; ++ ++ tracks = snapd_snap_get_tracks (snap); ++ for (i = 0; tracks[i] != NULL; i++) { ++ const gchar *track = tracks[i]; ++ const gchar *risks[] = {"stable", "candidate", "beta", "edge", NULL}; ++ const gchar *last_version = NULL; ++ guint j; ++ ++ for (j = 0; risks[j] != NULL; j++) { ++ const gchar *risk = risks[j]; ++ GPtrArray *channels; ++ g_autofree gchar *name = NULL; ++ const gchar *version = NULL; ++ guint k; ++ g_autoptr(GPtrArray) branches = NULL; ++ ++ channels = snapd_snap_get_channels (snap); ++ ++ if (strcmp (track, "latest") == 0) ++ name = g_strdup (risk); ++ else ++ name = g_strdup_printf ("%s/%s", track, risk); ++ for (k = 0; k < channels->len; k++) { ++ SnapdChannel *channel = channels->pdata[k]; ++ if (strcmp (snapd_channel_get_name (channel), name) == 0) { ++ version = snapd_channel_get_version (channel); ++ break; ++ } ++ } ++ if (version == NULL) ++ version = last_version; ++ add_channel (app, name, version); ++ ++ /* add any branches for this track/risk */ ++ branches = g_ptr_array_new (); ++ for (k = 0; k < channels->len; k++) { ++ SnapdChannel *c = channels->pdata[k]; ++ if (snapd_channel_get_branch (c) != NULL && ++ g_strcmp0 (snapd_channel_get_track (c), track) == 0 && ++ g_strcmp0 (snapd_channel_get_risk (c), risk) == 0) ++ g_ptr_array_add (branches, c); ++ } ++ g_ptr_array_sort (branches, compare_branch_names); ++ for (k = 0; k < branches->len; k++) { ++ SnapdChannel *c = branches->pdata[k]; ++ add_channel (app, snapd_channel_get_name (c), snapd_channel_get_version (c)); ++ } ++ ++ last_version = version; ++ } ++ } ++} ++ ++static gboolean ++set_active_channel (GsApp *app, SnapdChannel *channel) ++{ ++ GPtrArray *channels = gs_app_get_channels (app); ++ guint i; ++ ++ for (i = 0; i < channels->len; i++) { ++ GsChannel *c = g_ptr_array_index (channels, i); ++ if (g_strcmp0 (gs_channel_get_name (c), snapd_channel_get_name (channel)) == 0) { ++ gs_app_set_active_channel (app, c); ++ return TRUE; ++ } ++ } ++ ++ return FALSE; ++} ++ + gboolean + gs_plugin_refine_app (GsPlugin *plugin, + GsApp *app, +@@ -792,7 +918,7 @@ gs_plugin_refine_app (GsPlugin *plugin, + { + GsPluginData *priv = gs_plugin_get_data (plugin); + g_autoptr(SnapdClient) client = NULL; +- const gchar *name; ++ const gchar *name, *tracking_channel = NULL, *store_version = NULL; + g_autoptr(SnapdSnap) local_snap = NULL; + g_autoptr(SnapdSnap) store_snap = NULL; + SnapdSnap *snap; +@@ -809,13 +935,52 @@ gs_plugin_refine_app (GsPlugin *plugin, + + /* get information from local snaps and store */ + local_snap = snapd_client_get_snap_sync (client, gs_app_get_metadata_item (app, "snap::name"), cancellable, NULL); +- if (local_snap == NULL || (flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_SCREENSHOTS) != 0) +- store_snap = get_store_snap (plugin, gs_app_get_metadata_item (app, "snap::name"), cancellable, NULL); ++ /* Need to do full lookup when channel information required ++ * https://forum.snapcraft.io/t/channel-maps-list-is-empty-when-using-v1-snaps-search-as-opposed-to-using-v2-snaps-details */ ++ if (local_snap == NULL || (flags & (GS_PLUGIN_REFINE_FLAGS_REQUIRE_SCREENSHOTS | GS_PLUGIN_REFINE_FLAGS_REQUIRE_CHANNELS)) != 0) ++ store_snap = get_store_snap (plugin, gs_app_get_metadata_item (app, "snap::name"), (flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_CHANNELS) != 0, cancellable, NULL); + if (local_snap == NULL && store_snap == NULL) + return TRUE; + ++ /* get channel information */ ++ if (store_snap != NULL && flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_CHANNELS) ++ refine_channels (app, store_snap); ++ ++ /* set channel being tracked */ + if (local_snap != NULL) +- gs_app_set_state (app, AS_APP_STATE_INSTALLED); ++ tracking_channel = snapd_snap_get_tracking_channel (local_snap); ++ else ++ tracking_channel = gs_app_get_metadata_item (app, "snap::channel"); ++ if (store_snap != NULL && tracking_channel != NULL) { ++ SnapdChannel *c = NULL; ++ ++ c = snapd_snap_match_channel (store_snap, tracking_channel); ++ if (c != NULL) ++ set_active_channel (app, c); ++ } ++ ++ /* get latest upstream version */ ++ if (store_snap != NULL) { ++ GsChannel *channel = gs_app_get_active_channel (app); ++ if (channel != NULL) ++ store_version = gs_channel_get_version (channel); ++ else ++ store_version = snapd_snap_get_version (store_snap); ++ } ++ ++ gs_app_set_update_version (app, NULL); ++ if (local_snap != NULL) { ++ if (store_version != NULL && g_strcmp0 (store_version, snapd_snap_get_version (local_snap)) != 0) { ++ gs_app_set_update_version (app, store_version); ++ gs_app_set_state (app, AS_APP_STATE_UPDATABLE_LIVE); ++ } ++ else { ++ // Workaround it not being valid to switch from updatable to installed (e.g. if you switch channels) ++ if (gs_app_get_state (app) == AS_APP_STATE_UPDATABLE_LIVE) ++ gs_app_set_state (app, AS_APP_STATE_UNKNOWN); ++ gs_app_set_state (app, AS_APP_STATE_INSTALLED); ++ } ++ } + else + gs_app_set_state (app, AS_APP_STATE_AVAILABLE); + +@@ -927,6 +1092,7 @@ gs_plugin_app_install (GsPlugin *plugin, + { + g_autoptr(SnapdClient) client = NULL; + SnapdInstallFlags flags = SNAPD_INSTALL_FLAGS_NONE; ++ const gchar *channel = NULL; + + /* We can only install apps we know of */ + if (g_strcmp0 (gs_app_get_management_plugin (app), "snap") != 0) +@@ -936,10 +1102,39 @@ gs_plugin_app_install (GsPlugin *plugin, + if (client == NULL) + return FALSE; + ++ if (gs_app_get_active_channel (app) != NULL) ++ channel = gs_channel_get_name (gs_app_get_active_channel (app)); ++ + gs_app_set_state (app, AS_APP_STATE_INSTALLING); + if (g_strcmp0 (gs_app_get_metadata_item (app, "snap::confinement"), "classic") == 0) + flags |= SNAPD_INSTALL_FLAGS_CLASSIC; +- if (!snapd_client_install2_sync (client, flags, gs_app_get_metadata_item (app, "snap::name"), NULL, NULL, progress_cb, app, cancellable, error)) { ++ if (!snapd_client_install2_sync (client, flags, gs_app_get_metadata_item (app, "snap::name"), channel, NULL, progress_cb, app, cancellable, error)) { ++ gs_app_set_state_recover (app); ++ snapd_error_convert (error); ++ return FALSE; ++ } ++ gs_app_set_state (app, AS_APP_STATE_INSTALLED); ++ return TRUE; ++} ++ ++gboolean ++gs_plugin_update_app (GsPlugin *plugin, ++ GsApp *app, ++ GCancellable *cancellable, ++ GError **error) ++{ ++ g_autoptr(SnapdClient) client = NULL; ++ ++ /* We can only install apps we know of */ ++ if (g_strcmp0 (gs_app_get_management_plugin (app), "snap") != 0) ++ return TRUE; ++ ++ client = get_client (plugin, error); ++ if (client == NULL) ++ return FALSE; ++ ++ gs_app_set_state (app, AS_APP_STATE_INSTALLING); ++ if (!snapd_client_refresh_sync (client, gs_app_get_metadata_item (app, "snap::name"), NULL, progress_cb, app, cancellable, error)) { + gs_app_set_state_recover (app); + snapd_error_convert (error); + return FALSE; +@@ -1028,6 +1223,31 @@ gs_plugin_launch (GsPlugin *plugin, + return g_app_info_launch (info, NULL, NULL, error); + } + ++gboolean ++gs_plugin_app_switch_channel (GsPlugin *plugin, ++ GsApp *app, ++ GsChannel *channel, ++ GCancellable *cancellable, ++ GError **error) ++{ ++ g_autoptr(SnapdClient) client = NULL; ++ ++ /* We can only modify apps we know of */ ++ if (g_strcmp0 (gs_app_get_management_plugin (app), "snap") != 0) ++ return TRUE; ++ ++ client = get_client (plugin, error); ++ if (client == NULL) ++ return FALSE; ++ ++ if (!snapd_client_switch_sync (client, gs_app_get_metadata_item (app, "snap::name"), gs_channel_get_name (channel), progress_cb, app, cancellable, error)) { ++ snapd_error_convert (error); ++ return FALSE; ++ } ++ ++ return TRUE; ++} ++ + gboolean + gs_plugin_app_remove (GsPlugin *plugin, + GsApp *app, +diff --git a/src/gs-details-page.c b/src/gs-details-page.c +index 3eafb32..3d57a8a 100644 +--- a/src/gs-details-page.c ++++ b/src/gs-details-page.c +@@ -113,6 +113,8 @@ struct _GsDetailsPage + GtkWidget *label_details_size_download_value; + GtkWidget *label_details_updated_title; + GtkWidget *label_details_updated_value; ++ GtkWidget *label_details_channel_title; ++ GtkWidget *button_details_channel; + GtkWidget *label_details_version_value; + GtkWidget *label_failed; + GtkWidget *label_license_nonfree_details; +@@ -128,6 +130,7 @@ struct _GsDetailsPage + GtkWidget *spinner_remove; + GtkWidget *stack_details; + GtkWidget *grid_details_kudo; ++ GtkWidget *grid_popover_channel; + GtkWidget *image_details_kudo_docs; + GtkWidget *image_details_kudo_sandboxed; + GtkWidget *image_details_kudo_integration; +@@ -139,6 +142,7 @@ struct _GsDetailsPage + GtkWidget *label_details_kudo_translated; + GtkWidget *label_details_kudo_updated; + GtkWidget *progressbar_top; ++ GtkWidget *popover_channel; + GtkWidget *popover_license_free; + GtkWidget *popover_license_nonfree; + GtkWidget *popover_license_unknown; +@@ -891,6 +895,7 @@ gs_details_page_refresh_all (GsDetailsPage *self) + guint64 updated; + guint64 user_integration_bf; + gboolean show_support_box = FALSE; ++ GsChannel *channel; + g_autoptr(GError) error = NULL; + + /* change widgets */ +@@ -971,13 +976,22 @@ gs_details_page_refresh_all (GsDetailsPage *self) + gtk_widget_set_visible (self->button_details_license_unknown, FALSE); + } + +- /* set version */ +- tmp = gs_app_get_version (self->app); +- if (tmp != NULL){ +- gtk_label_set_label (GTK_LABEL (self->label_details_version_value), tmp); ++ /* set channel */ ++ channel = gs_app_get_active_channel (self->app); ++ gtk_widget_set_visible (self->label_details_channel_title, channel != NULL); ++ gtk_widget_set_visible (self->button_details_channel, channel != NULL); ++ if (channel != NULL) { ++ gtk_button_set_label (GTK_BUTTON (self->button_details_channel), gs_channel_get_name (channel)); ++ gtk_label_set_label (GTK_LABEL (self->label_details_version_value), gs_channel_get_version (channel)); + } else { +- /* TRANSLATORS: this is where the version is not known */ +- gtk_label_set_label (GTK_LABEL (self->label_details_version_value), C_("version", "Unknown")); ++ /* set version */ ++ tmp = gs_app_get_version (self->app); ++ if (tmp != NULL){ ++ gtk_label_set_label (GTK_LABEL (self->label_details_version_value), tmp); ++ } else { ++ /* TRANSLATORS: this is where the version is not known */ ++ gtk_label_set_label (GTK_LABEL (self->label_details_version_value), C_("version", "Unknown")); ++ } + } + + /* refresh size information */ +@@ -1757,7 +1771,8 @@ gs_details_page_set_local_file (GsDetailsPage *self, GFile *file) + GS_PLUGIN_REFINE_FLAGS_REQUIRE_RELATED | + GS_PLUGIN_REFINE_FLAGS_REQUIRE_RUNTIME | + GS_PLUGIN_REFINE_FLAGS_REQUIRE_PERMISSIONS | +- GS_PLUGIN_REFINE_FLAGS_REQUIRE_SCREENSHOTS, ++ GS_PLUGIN_REFINE_FLAGS_REQUIRE_SCREENSHOTS | ++ GS_PLUGIN_REFINE_FLAGS_REQUIRE_CHANNELS, + NULL); + gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job, + self->cancellable, +@@ -1789,6 +1804,7 @@ gs_details_page_set_url (GsDetailsPage *self, const gchar *url) + GS_PLUGIN_REFINE_FLAGS_REQUIRE_RUNTIME | + GS_PLUGIN_REFINE_FLAGS_REQUIRE_PERMISSIONS | + GS_PLUGIN_REFINE_FLAGS_REQUIRE_SCREENSHOTS | ++ GS_PLUGIN_REFINE_FLAGS_REQUIRE_CHANNELS | + GS_PLUGIN_REFINE_FLAGS_ALLOW_PACKAGES, + NULL); + gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job, +@@ -1816,7 +1832,8 @@ gs_details_page_load (GsDetailsPage *self) + GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROVENANCE | + GS_PLUGIN_REFINE_FLAGS_REQUIRE_RUNTIME | + GS_PLUGIN_REFINE_FLAGS_REQUIRE_ADDONS | +- GS_PLUGIN_REFINE_FLAGS_REQUIRE_SCREENSHOTS, ++ GS_PLUGIN_REFINE_FLAGS_REQUIRE_SCREENSHOTS | ++ GS_PLUGIN_REFINE_FLAGS_REQUIRE_CHANNELS, + NULL); + gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job, + self->cancellable, +@@ -1932,6 +1949,165 @@ gs_details_page_app_cancel_button_cb (GtkWidget *widget, GsDetailsPage *self) + gs_details_page_remove_app (self); + } + ++typedef struct { ++ GsDetailsPage *self; ++ GsChannel *channel; ++} GsDetailsPageChannelHelper; ++ ++static void ++gs_details_page_channel_helper_free (GsDetailsPageChannelHelper *helper) ++{ ++ g_object_unref (helper->self); ++ g_object_unref (helper->channel); ++ g_free (helper); ++} ++ ++G_DEFINE_AUTOPTR_CLEANUP_FUNC(GsDetailsPageChannelHelper, gs_details_page_channel_helper_free); ++ ++static void ++gs_page_channel_switch_refine_cb (GObject *source, ++ GAsyncResult *res, ++ gpointer user_data) ++{ ++ g_autoptr(GsDetailsPageChannelHelper) helper = (GsDetailsPageChannelHelper *) user_data; ++ GsDetailsPage *self = helper->self; ++ GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source); ++ gboolean ret; ++ g_autoptr(GError) error = NULL; ++ ++ ret = gs_plugin_loader_job_action_finish (plugin_loader, ++ res, ++ &error); ++ if (g_error_matches (error, ++ GS_PLUGIN_ERROR, ++ GS_PLUGIN_ERROR_CANCELLED)) { ++ g_debug ("%s", error->message); ++ return; ++ } ++ if (!ret) { ++ g_warning ("failed to refine %s: %s", ++ gs_app_get_id (self->app), ++ error->message); ++ return; ++ } ++ ++ gs_details_page_refresh_all (self); ++} ++ ++static void ++gs_page_channel_switched_cb (GObject *source, ++ GAsyncResult *res, ++ gpointer user_data) ++{ ++ g_autoptr(GsDetailsPageChannelHelper) helper = (GsDetailsPageChannelHelper *) user_data; ++ GsDetailsPage *self = helper->self; ++ GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source); ++ gboolean ret; ++ g_autoptr(GsPluginJob) plugin_job = NULL; ++ g_autoptr(GError) error = NULL; ++ ++ ret = gs_plugin_loader_job_action_finish (plugin_loader, ++ res, ++ &error); ++ if (g_error_matches (error, ++ GS_PLUGIN_ERROR, ++ GS_PLUGIN_ERROR_CANCELLED)) { ++ g_debug ("%s", error->message); ++ return; ++ } ++ if (!ret) { ++ g_warning ("failed to switch channel %s: %s", ++ gs_app_get_id (self->app), ++ error->message); ++ return; ++ } ++ ++ plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REFINE, ++ "app", self->app, ++ "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION, ++ NULL); ++ gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job, ++ self->app_cancellable, ++ gs_page_channel_switch_refine_cb, ++ g_steal_pointer (&helper)); ++} ++ ++static void ++gs_details_page_switch_channel_cb (GtkWidget *widget, gpointer user_data) ++{ ++ g_autoptr(GsDetailsPageChannelHelper) helper = (GsDetailsPageChannelHelper *) user_data; ++ GsDetailsPage *self = helper->self; ++ g_autoptr(GsPluginJob) plugin_job = NULL; ++ ++ gtk_widget_hide (self->popover_channel); ++ ++ gs_app_set_active_channel (self->app, helper->channel); ++ ++ switch (gs_app_get_state (self->app)) { ++ case AS_APP_STATE_INSTALLED: ++ case AS_APP_STATE_UPDATABLE: ++ case AS_APP_STATE_UPDATABLE_LIVE: ++ plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_SWITCH_CHANNEL, ++ "app", self->app, ++ "channel", helper->channel, ++ NULL); ++ gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job, ++ self->app_cancellable, ++ gs_page_channel_switched_cb, ++ g_steal_pointer (&helper)); ++ break; ++ default: ++ break; ++ } ++ ++ gs_details_page_refresh_all (self); ++} ++ ++static void ++gs_details_page_channel_cb (GtkWidget *widget, GsDetailsPage *self) ++{ ++ GPtrArray *channels; ++ guint i; ++ ++ gs_container_remove_all (GTK_CONTAINER (self->grid_popover_channel)); ++ channels = gs_app_get_channels (self->app); ++ for (i = 0; i < channels->len; i++) { ++ GsChannel *channel = g_ptr_array_index (channels, i); ++ const gchar *version; ++ GtkWidget *label; ++ GtkWidget *button; ++ ++ version = gs_channel_get_version (channel); ++ ++ label = gtk_label_new (gs_channel_get_name (channel)); ++ gtk_label_set_xalign (GTK_LABEL (label), 0.0); ++ gs_details_page_set_sensitive (label, version != NULL); ++ gtk_widget_show (label); ++ gtk_grid_attach (GTK_GRID (self->grid_popover_channel), label, 0, i, 1, 1); ++ ++ label = gtk_label_new (version == NULL ? "—" : version); ++ gtk_label_set_xalign (GTK_LABEL (label), 0.0); ++ gs_details_page_set_sensitive (label, version != NULL); ++ gtk_widget_show (label); ++ gtk_grid_attach (GTK_GRID (self->grid_popover_channel), label, 1, i, 1, 1); ++ ++ if (version != NULL && channel != gs_app_get_active_channel (self->app)) { ++ GsDetailsPageChannelHelper *helper = g_new0 (GsDetailsPageChannelHelper, 1); ++ ++ button = gtk_button_new_with_label (_("Switch")); ++ gtk_widget_show (button); ++ gtk_grid_attach (GTK_GRID (self->grid_popover_channel), button, 2, i, 1, 1); ++ helper->self = g_object_ref (self); ++ helper->channel = g_object_ref (channel); ++ g_signal_connect (button, "clicked", ++ G_CALLBACK (gs_details_page_switch_channel_cb), ++ helper); ++ } ++ } ++ ++ gtk_widget_show (self->popover_channel); ++} ++ + static void + gs_details_page_app_install_button_cb (GtkWidget *widget, GsDetailsPage *self) + { +@@ -2405,6 +2581,9 @@ gs_details_page_setup (GsPage *page, + g_signal_connect (self->button_donate, "clicked", + G_CALLBACK (gs_details_page_donate_cb), + self); ++ g_signal_connect (self->button_details_channel, "clicked", ++ G_CALLBACK (gs_details_page_channel_cb), ++ self); + g_signal_connect (self->button_details_license_free, "clicked", + G_CALLBACK (gs_details_page_license_free_cb), + self); +@@ -2505,6 +2684,10 @@ gs_details_page_class_init (GsDetailsPageClass *klass) + gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, label_details_size_installed_value); + gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, label_details_updated_title); + gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, label_details_updated_value); ++ gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, label_details_channel_title); ++ gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, button_details_channel); ++ gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, popover_channel); ++ gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, grid_popover_channel); + gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, label_details_version_value); + gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, label_failed); + gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, list_box_addons); +diff --git a/src/gs-details-page.ui b/src/gs-details-page.ui +index 9d95c1f..154ce19 100644 +--- a/src/gs-details-page.ui ++++ b/src/gs-details-page.ui +@@ -816,6 +816,51 @@ + False + 9 + 24 ++ ++ ++ True ++ False ++ Channel ++ 0 ++ 0.5 ++ True ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ vertical ++ ++ ++ stable ++ True ++ True ++ True ++ start ++ ++ ++ ++ False ++ False ++ 0 ++ ++ ++ ++ ++ 1 ++ 0 ++ ++ + + + True +@@ -830,7 +875,7 @@ + + + 0 +- 0 ++ 1 + + + +@@ -849,7 +894,7 @@ + + + 1 +- 0 ++ 1 + + + +@@ -867,7 +912,7 @@ + + + 0 +- 8 ++ 9 + + + +@@ -887,7 +932,7 @@ + + + 1 +- 8 ++ 9 + + + +@@ -905,7 +950,7 @@ + + + 0 +- 1 ++ 2 + + + +@@ -923,7 +968,7 @@ + + + 1 +- 1 ++ 2 + + + +@@ -940,7 +985,7 @@ + + + 0 +- 2 ++ 3 + + + +@@ -960,7 +1005,7 @@ + + + 1 +- 2 ++ 3 + + + +@@ -978,7 +1023,7 @@ + + + 0 +- 6 ++ 7 + + + +@@ -993,7 +1038,7 @@ + + + 1 +- 6 ++ 7 + + + +@@ -1011,7 +1056,7 @@ + + + 0 +- 7 ++ 8 + + + +@@ -1026,7 +1071,7 @@ + + + 1 +- 7 ++ 8 + + + +@@ -1044,7 +1089,7 @@ + + + 0 +- 5 ++ 6 + + + +@@ -1060,7 +1105,7 @@ + + + 1 +- 5 ++ 6 + + + +@@ -1077,7 +1122,7 @@ + + + 0 +- 4 ++ 5 + + + +@@ -1109,7 +1154,7 @@ + + + 1 +- 4 ++ 5 + + + +@@ -1126,7 +1171,7 @@ + + + 0 +- 3 ++ 4 + + + +@@ -1188,7 +1233,7 @@ + + + 1 +- 3 ++ 4 + + + +@@ -1429,6 +1474,29 @@ + + + ++ ++ False ++ 21 ++ button_details_channel ++ ++ ++ True ++ True ++ never ++ automatic ++ 300 ++ True ++ ++ ++ True ++ False ++ 9 ++ 12 ++ ++ ++ ++ ++ + + False + 21 +diff --git a/src/gtk-style.css b/src/gtk-style.css +index e37447a..5376dfa 100644 +--- a/src/gtk-style.css ++++ b/src/gtk-style.css +@@ -53,6 +53,37 @@ + border-radius: 16px; + } + ++.details-channel, ++.details-channel:backdrop { ++ outline-offset: 0; ++ background-image: none; ++ border-image: none; ++ border-radius: 4px; ++ border-width: 0 0 2px 0; ++ padding: 1px 9px; ++ box-shadow: none; ++ text-shadow: none; ++ color: #ffffff; ++} ++ ++.details-channel label, ++.details-channel:backdrop label, ++.details-channel:hover label { ++ color: #fff; ++} ++ ++.details-channel { ++ background-color: #4e9a06; ++ border-color: #3e7905; ++} ++.details-channel:hover { ++ background-color: #5db807; ++ border-color: #4d9606; ++} ++.details-channel:backdrop { ++ border-color: #4e9a06; ++} ++ + .details-license-free, + .details-license-nonfree, + .details-license-unknown, diff -Nru gnome-software-3.30.6/debian/patches/0012-Don-t-use-colour-to-differentiate-between-free-and-p.patch gnome-software-3.30.6/debian/patches/0012-Don-t-use-colour-to-differentiate-between-free-and-p.patch --- gnome-software-3.30.6/debian/patches/0012-Don-t-use-colour-to-differentiate-between-free-and-p.patch 1970-01-01 00:00:00.000000000 +0000 +++ gnome-software-3.30.6/debian/patches/0012-Don-t-use-colour-to-differentiate-between-free-and-p.patch 2019-04-16 22:49:03.000000000 +0000 @@ -0,0 +1,34 @@ +From: Robert Ancell +Date: Fri, 2 Mar 2018 12:22:11 +1300 +Subject: [PATCH 12/24] Don't use colour to differentiate between free and + proprietary licenses + +--- + src/gtk-style.css | 10 +++++----- + 1 file changed, 5 insertions(+), 5 deletions(-) + +diff --git a/src/gtk-style.css b/src/gtk-style.css +index 5376dfa..353ff9e 100644 +--- a/src/gtk-style.css ++++ b/src/gtk-style.css +@@ -141,15 +141,15 @@ + } + + .details-license-nonfree { +- background-color: #ee2222; +- border-color: #c20f0f; ++ background-color: #4e9a06; ++ border-color: #3e7905; + } + .details-license-nonfree:hover { +- background-color: #f25959; +- border-color: #ed1b1b; ++ background-color: #5db807; ++ border-color: #4d9606; + } + .details-license-nonfree:backdrop { +- border-color: #ee2222; ++ border-color: #4e9a06; + } + + .details-license-unknown { diff -Nru gnome-software-3.30.6/debian/patches/0013-overview-page-Rotate-featured-apps.patch gnome-software-3.30.6/debian/patches/0013-overview-page-Rotate-featured-apps.patch --- gnome-software-3.30.6/debian/patches/0013-overview-page-Rotate-featured-apps.patch 1970-01-01 00:00:00.000000000 +0000 +++ gnome-software-3.30.6/debian/patches/0013-overview-page-Rotate-featured-apps.patch 2019-04-16 22:49:03.000000000 +0000 @@ -0,0 +1,20 @@ +From: Robert Ancell +Date: Wed, 31 Jan 2018 10:20:11 +1300 +Subject: [PATCH 13/24] overview page: Rotate featured apps + +--- + src/gtk-style.css | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/src/gtk-style.css b/src/gtk-style.css +index 353ff9e..de2844b 100644 +--- a/src/gtk-style.css ++++ b/src/gtk-style.css +@@ -553,3 +553,7 @@ flowboxchild { + .switcher-label { + opacity: 0.5; + } ++ ++.switcher-label { ++ opacity: 0.5; ++} diff -Nru gnome-software-3.30.6/debian/patches/0014-Add-a-basic-permissions-system.patch gnome-software-3.30.6/debian/patches/0014-Add-a-basic-permissions-system.patch --- gnome-software-3.30.6/debian/patches/0014-Add-a-basic-permissions-system.patch 1970-01-01 00:00:00.000000000 +0000 +++ gnome-software-3.30.6/debian/patches/0014-Add-a-basic-permissions-system.patch 2019-05-13 23:04:16.000000000 +0000 @@ -0,0 +1,1984 @@ +From 04ae5465cfe174355b3e6938f027e90cb579d843 Mon Sep 17 00:00:00 2001 +From: Robert Ancell +Date: Fri, 26 May 2017 16:30:56 +1200 +Subject: [PATCH 14/29] Add a basic permissions system + +--- + lib/gs-app.c | 55 ++++++++ + lib/gs-app.h | 4 + + lib/gs-permission-value.c | 123 ++++++++++++++++++ + lib/gs-permission-value.h | 47 +++++++ + lib/gs-permission.c | 196 ++++++++++++++++++++++++++++ + lib/gs-permission.h | 57 ++++++++ + lib/gs-plugin-job-private.h | 2 + + lib/gs-plugin-job.c | 61 +++++++++ + lib/gs-plugin-job.h | 6 + + lib/gs-plugin-loader.c | 15 +++ + lib/gs-plugin-types.h | 2 + + lib/gs-plugin-vfuncs.h | 21 +++ + lib/gs-plugin.c | 6 + + lib/meson.build | 4 + + plugins/snap/gs-plugin-snap.c | 215 +++++++++++++++++++++++++++++++ + po/POTFILES.in | 1 + + src/gnome-software.gresource.xml | 1 + + src/gs-details-page.c | 43 +++++++ + src/gs-details-page.ui | 18 ++- + src/gs-page.c | 29 +++++ + src/gs-page.h | 5 + + src/gs-permission-combo-box.c | 125 ++++++++++++++++++ + src/gs-permission-combo-box.h | 41 ++++++ + src/gs-permission-dialog.c | 153 ++++++++++++++++++++++ + src/gs-permission-dialog.h | 39 ++++++ + src/gs-permission-dialog.ui | 67 ++++++++++ + src/gs-permission-switch.c | 100 ++++++++++++++ + src/gs-permission-switch.h | 41 ++++++ + src/meson.build | 3 + + 29 files changed, 1479 insertions(+), 1 deletion(-) + create mode 100644 lib/gs-permission-value.c + create mode 100644 lib/gs-permission-value.h + create mode 100644 lib/gs-permission.c + create mode 100644 lib/gs-permission.h + create mode 100644 src/gs-permission-combo-box.c + create mode 100644 src/gs-permission-combo-box.h + create mode 100644 src/gs-permission-dialog.c + create mode 100644 src/gs-permission-dialog.h + create mode 100644 src/gs-permission-dialog.ui + create mode 100644 src/gs-permission-switch.c + create mode 100644 src/gs-permission-switch.h + +diff --git a/lib/gs-app.c b/lib/gs-app.c +index c2b486f3..c5cc0dd2 100644 +--- a/lib/gs-app.c ++++ b/lib/gs-app.c +@@ -92,6 +92,7 @@ typedef struct + gchar *origin; + gchar *origin_appstream; + gchar *origin_hostname; ++ GPtrArray *permissions; + gchar *update_version; + gchar *update_version_ui; + gchar *update_details; +@@ -448,6 +449,18 @@ gs_app_to_string_append (GsApp *app, GString *str) + gs_app_kv_lpad (str, "icon-filename", + as_icon_get_filename (icon)); + } ++ for (i = 0; i < priv->permissions->len; i++) { ++ GsPermission *permission; ++ GsPermissionValue *value; ++ g_autofree gchar *key = NULL; ++ ++ permission = g_ptr_array_index (priv->permissions, i); ++ value = gs_permission_get_value (permission); ++ key = g_strdup_printf ("permission-%02u", i); ++ gs_app_kv_printf (str, key, "[%s] %s", ++ gs_permission_get_label (permission), ++ value ? gs_permission_value_get_label (value) : "(unset)"); ++ } + if (priv->match_value != 0) + gs_app_kv_printf (str, "match-value", "%05x", priv->match_value); + if (priv->priority != 0) +@@ -2599,6 +2612,46 @@ gs_app_set_origin_hostname (GsApp *app, const gchar *origin_hostname) + priv->origin_hostname = g_strdup (origin_hostname); + } + ++/** ++ * gs_app_add_permission: ++ * @app: a #GsApp ++ * @permission: a #GsPermission ++ * ++ * Adds a permission to the applicaton. ++ * ++ * Since: 3.26 ++ **/ ++void ++gs_app_add_permission (GsApp *app, GsPermission *permission) ++{ ++ GsAppPrivate *priv = gs_app_get_instance_private (app); ++ ++ g_return_if_fail (GS_IS_APP (app)); ++ g_return_if_fail (GS_IS_PERMISSION (permission)); ++ ++ g_ptr_array_add (priv->permissions, g_object_ref (permission)); ++} ++ ++/** ++ * gs_app_get_permissions: ++ * @app: a #GsApp ++ * ++ * Gets the list of permissions. ++ * ++ * Returns: (element-type GsPermission) (transfer none): a list ++ * ++ * Since: 3.26 ++ **/ ++GPtrArray * ++gs_app_get_permissions (GsApp *app) ++{ ++ GsAppPrivate *priv = gs_app_get_instance_private (app); ++ ++ g_return_val_if_fail (GS_IS_APP (app), NULL); ++ ++ return priv->permissions; ++} ++ + /** + * gs_app_add_screenshot: + * @app: a #GsApp +@@ -4260,6 +4313,7 @@ gs_app_dispose (GObject *object) + g_clear_pointer (&priv->icons, g_ptr_array_unref); + g_clear_pointer (&priv->channels, g_ptr_array_unref); + g_clear_object (&priv->active_channel); ++ g_clear_pointer (&priv->permissions, g_ptr_array_unref); + + G_OBJECT_CLASS (gs_app_parent_class)->dispose (object); + } +@@ -4448,6 +4502,7 @@ gs_app_init (GsApp *app) + priv->provides = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); + priv->icons = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); + priv->channels = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); ++ priv->permissions = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); + priv->metadata = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, +diff --git a/lib/gs-app.h b/lib/gs-app.h +index 225d8708..72636aa1 100644 +--- a/lib/gs-app.h ++++ b/lib/gs-app.h +@@ -30,6 +30,7 @@ + + #include "gs-channel.h" + #include "gs-price.h" ++#include "gs-permission.h" + + G_BEGIN_DECLS + +@@ -212,6 +213,9 @@ void gs_app_set_origin_appstream (GsApp *app, + const gchar *gs_app_get_origin_hostname (GsApp *app); + void gs_app_set_origin_hostname (GsApp *app, + const gchar *origin_hostname); ++GPtrArray *gs_app_get_permissions (GsApp *app); ++void gs_app_add_permission (GsApp *app, ++ GsPermission *permission); + GPtrArray *gs_app_get_screenshots (GsApp *app); + void gs_app_add_screenshot (GsApp *app, + AsScreenshot *screenshot); +diff --git a/lib/gs-permission-value.c b/lib/gs-permission-value.c +new file mode 100644 +index 00000000..a9143e34 +--- /dev/null ++++ b/lib/gs-permission-value.c +@@ -0,0 +1,123 @@ ++/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- ++ * ++ * Copyright (C) 2017 Canonical Ltd. ++ * ++ * 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" ++ ++#include ++ ++#include "gs-permission-value.h" ++ ++struct _GsPermissionValue ++{ ++ GObject parent_instance; ++ ++ gchar *label; ++ GHashTable *metadata; /* utf8: utf8 */ ++}; ++ ++G_DEFINE_TYPE (GsPermissionValue, gs_permission_value, G_TYPE_OBJECT) ++ ++/** ++ * gs_permission_value_get_metadata_item: ++ * @value: a #GsPermissionValue ++ * @key: a string ++ * ++ * Gets some metadata from a permission value object. ++ * It is left for the the plugin to use this method as required, but a ++ * typical use would be to retrieve an ID for this permission value. ++ * ++ * Returns: A string value, or %NULL for not found ++ */ ++const gchar * ++gs_permission_value_get_metadata_item (GsPermissionValue *value, const gchar *key) ++{ ++ g_return_val_if_fail (GS_IS_PERMISSION_VALUE (value), NULL); ++ g_return_val_if_fail (key != NULL, NULL); ++ return g_hash_table_lookup (value->metadata, key); ++} ++ ++/** ++ * gs_permission_value_add_metadata: ++ * @value: a #GsPermissionValue ++ * @key: a string ++ * @value: a string ++ * ++ * Adds metadata to the permission object. ++ * It is left for the the plugin to use this method as required, but a ++ * typical use would be to store an ID for this permission. ++ */ ++void ++gs_permission_value_add_metadata (GsPermissionValue *value, const gchar *key, const gchar *val) ++{ ++ g_return_if_fail (GS_IS_PERMISSION_VALUE (value)); ++ g_hash_table_insert (value->metadata, g_strdup (key), g_strdup (val)); ++} ++ ++/** ++ * gs_permission_value_get_label: ++ * @permission: a #GsPermissionValue ++ * ++ * Get the label for this permission. ++ * ++ * Returns: a label string. ++ */ ++const gchar * ++gs_permission_value_get_label (GsPermissionValue *value) ++{ ++ g_return_val_if_fail (GS_IS_PERMISSION_VALUE (value), NULL); ++ return value->label; ++} ++ ++static void ++gs_permission_value_finalize (GObject *object) ++{ ++ GsPermissionValue *value = GS_PERMISSION_VALUE (object); ++ ++ g_free (value->label); ++ g_hash_table_unref (value->metadata); ++ ++ G_OBJECT_CLASS (gs_permission_value_parent_class)->finalize (object); ++} ++ ++static void ++gs_permission_value_class_init (GsPermissionValueClass *klass) ++{ ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ object_class->finalize = gs_permission_value_finalize; ++} ++ ++static void ++gs_permission_value_init (GsPermissionValue *value) ++{ ++ value->metadata = g_hash_table_new_full (g_str_hash, g_str_equal, ++ g_free, g_free); ++} ++ ++GsPermissionValue * ++gs_permission_value_new (const gchar *label) ++{ ++ GsPermissionValue *value; ++ value = g_object_new (GS_TYPE_PERMISSION_VALUE, NULL); ++ value->label = g_strdup (label); ++ return GS_PERMISSION_VALUE (value); ++} ++ ++/* vim: set noexpandtab: */ +diff --git a/lib/gs-permission-value.h b/lib/gs-permission-value.h +new file mode 100644 +index 00000000..b2ee3b40 +--- /dev/null ++++ b/lib/gs-permission-value.h +@@ -0,0 +1,47 @@ ++/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- ++ * ++ * Copyright (C) 2017 Canonical Ltd. ++ * ++ * 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. ++ */ ++ ++#ifndef __GS_PERMISSION_VALUE_H ++#define __GS_PERMISSION_VALUE_H ++ ++#include ++ ++G_BEGIN_DECLS ++ ++#define GS_TYPE_PERMISSION_VALUE (gs_permission_value_get_type ()) ++ ++G_DECLARE_FINAL_TYPE (GsPermissionValue, gs_permission_value, GS, PERMISSION_VALUE, GObject) ++ ++GsPermissionValue *gs_permission_value_new (const gchar *label); ++ ++const gchar *gs_permission_value_get_metadata_item (GsPermissionValue *value, ++ const gchar *key); ++void gs_permission_value_add_metadata (GsPermissionValue *value, ++ const gchar *key, ++ const gchar *val); ++ ++const gchar *gs_permission_value_get_label (GsPermissionValue *value); ++ ++G_END_DECLS ++ ++#endif /* __GS_PERMISSION_VALUE_H */ ++ ++/* vim: set noexpandtab: */ +diff --git a/lib/gs-permission.c b/lib/gs-permission.c +new file mode 100644 +index 00000000..4ab5e370 +--- /dev/null ++++ b/lib/gs-permission.c +@@ -0,0 +1,196 @@ ++/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- ++ * ++ * Copyright (C) 2017 Canonical Ltd. ++ * ++ * 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" ++ ++#include ++ ++#include "gs-permission.h" ++ ++struct _GsPermission ++{ ++ GObject parent_instance; ++ ++ gchar *label; ++ GPtrArray *values; ++ GsPermissionValue *value; ++ GHashTable *metadata; /* utf8: utf8 */ ++}; ++ ++G_DEFINE_TYPE (GsPermission, gs_permission, G_TYPE_OBJECT) ++ ++/** ++ * gs_permission_get_metadata_item: ++ * @permission: a #GsPermission ++ * @key: a string ++ * ++ * Gets some metadata from a permission object. ++ * It is left for the the plugin to use this method as required, but a ++ * typical use would be to retrieve an ID for this permission. ++ * ++ * Returns: A string value, or %NULL for not found ++ */ ++const gchar * ++gs_permission_get_metadata_item (GsPermission *permission, const gchar *key) ++{ ++ g_return_val_if_fail (GS_IS_PERMISSION (permission), NULL); ++ g_return_val_if_fail (key != NULL, NULL); ++ return g_hash_table_lookup (permission->metadata, key); ++} ++ ++/** ++ * gs_permission_add_metadata: ++ * @permission: a #GsPermission ++ * @key: a string ++ * @value: a string ++ * ++ * Adds metadata to the permission object. ++ * It is left for the the plugin to use this method as required, but a ++ * typical use would be to store an ID for this permission. ++ */ ++void ++gs_permission_add_metadata (GsPermission *permission, const gchar *key, const gchar *value) ++{ ++ g_return_if_fail (GS_IS_PERMISSION (permission)); ++ g_hash_table_insert (permission->metadata, g_strdup (key), g_strdup (value)); ++} ++ ++/** ++ * gs_permission_get_label: ++ * @permission: a #GsPermission ++ * ++ * Get the label for this permission. ++ * ++ * Returns: a label string. ++ */ ++const gchar * ++gs_permission_get_label (GsPermission *permission) ++{ ++ g_return_val_if_fail (GS_IS_PERMISSION (permission), NULL); ++ return permission->label; ++} ++ ++/** ++ * gs_permission_add_value: ++ * @permission: a #GsPermission ++ * @value: a #GsPermissionValue ++ * ++ * Add a possible values for this permission. ++ */ ++void ++gs_permission_add_value (GsPermission *permission, GsPermissionValue *value) ++{ ++ g_return_if_fail (GS_IS_PERMISSION (permission)); ++ g_ptr_array_add (permission->values, g_object_ref (value)); ++} ++ ++/** ++ * gs_permission_get_values: ++ * @permission: a #GsPermission ++ * ++ * Get the possible values for this permission. ++ * ++ * Returns: (element-type GsPermissionValue) (transfer none): a list ++ */ ++GPtrArray * ++gs_permission_get_values (GsPermission *permission) ++{ ++ g_return_val_if_fail (GS_IS_PERMISSION (permission), NULL); ++ return permission->values; ++} ++ ++/** ++ * gs_permission_get_value: ++ * @permission: a #GsPermission ++ * ++ * Get the value for this permission. ++ * ++ * Returns: a %GsPermissionValue or %NULL. ++ */ ++GsPermissionValue * ++gs_permission_get_value (GsPermission *permission) ++{ ++ g_return_val_if_fail (GS_IS_PERMISSION (permission), NULL); ++ return permission->value; ++} ++ ++/** ++ * gs_permission_set_value: ++ * @permission: a #GsPermission ++ * @value: a #GsPermissionValue to set for this permission ++ * ++ * Set the value of this permission. ++ */ ++void ++gs_permission_set_value (GsPermission *permission, GsPermissionValue *value) ++{ ++ g_return_if_fail (GS_IS_PERMISSION (permission)); ++ g_set_object (&permission->value, value); ++} ++ ++static void ++gs_permission_dispose (GObject *object) ++{ ++ GsPermission *permission = GS_PERMISSION (object); ++ ++ g_clear_pointer (&permission->values, g_ptr_array_unref); ++ g_clear_object (&permission->value); ++ ++ G_OBJECT_CLASS (gs_permission_parent_class)->dispose (object); ++} ++ ++static void ++gs_permission_finalize (GObject *object) ++{ ++ GsPermission *permission = GS_PERMISSION (object); ++ ++ g_free (permission->label); ++ g_hash_table_unref (permission->metadata); ++ ++ G_OBJECT_CLASS (gs_permission_parent_class)->finalize (object); ++} ++ ++static void ++gs_permission_class_init (GsPermissionClass *klass) ++{ ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ object_class->dispose = gs_permission_dispose; ++ object_class->finalize = gs_permission_finalize; ++} ++ ++static void ++gs_permission_init (GsPermission *permission) ++{ ++ permission->metadata = g_hash_table_new_full (g_str_hash, g_str_equal, ++ g_free, g_free); ++ permission->values = g_ptr_array_new_with_free_func (g_object_unref); ++} ++ ++GsPermission * ++gs_permission_new (const gchar *label) ++{ ++ GsPermission *permission; ++ permission = g_object_new (GS_TYPE_PERMISSION, NULL); ++ permission->label = g_strdup (label); ++ return GS_PERMISSION (permission); ++} ++ ++/* vim: set noexpandtab: */ +diff --git a/lib/gs-permission.h b/lib/gs-permission.h +new file mode 100644 +index 00000000..08a24366 +--- /dev/null ++++ b/lib/gs-permission.h +@@ -0,0 +1,57 @@ ++/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- ++ * ++ * Copyright (C) 2017 Canonical Ltd. ++ * ++ * 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. ++ */ ++ ++#ifndef __GS_PERMISSION_H ++#define __GS_PERMISSION_H ++ ++#include ++ ++#include "gs-permission-value.h" ++ ++G_BEGIN_DECLS ++ ++#define GS_TYPE_PERMISSION (gs_permission_get_type ()) ++ ++G_DECLARE_FINAL_TYPE (GsPermission, gs_permission, GS, PERMISSION, GObject) ++ ++GsPermission *gs_permission_new (const gchar *label); ++ ++const gchar *gs_permission_get_metadata_item (GsPermission *permission, ++ const gchar *key); ++void gs_permission_add_metadata (GsPermission *permission, ++ const gchar *key, ++ const gchar *value); ++ ++const gchar *gs_permission_get_label (GsPermission *permission); ++ ++void gs_permission_add_value (GsPermission *permission, ++ GsPermissionValue *value); ++GPtrArray *gs_permission_get_values (GsPermission *permission); ++ ++GsPermissionValue *gs_permission_get_value (GsPermission *permission); ++void gs_permission_set_value (GsPermission *permission, ++ GsPermissionValue *value); ++ ++G_END_DECLS ++ ++#endif /* __GS_PERMISSION_H */ ++ ++/* vim: set noexpandtab: */ +diff --git a/lib/gs-plugin-job-private.h b/lib/gs-plugin-job-private.h +index 7a85910f..d6890831 100644 +--- a/lib/gs-plugin-job-private.h ++++ b/lib/gs-plugin-job-private.h +@@ -53,6 +53,8 @@ GsCategory *gs_plugin_job_get_category (GsPluginJob *self); + AsReview *gs_plugin_job_get_review (GsPluginJob *self); + GsPrice *gs_plugin_job_get_price (GsPluginJob *self); + GsChannel *gs_plugin_job_get_channel (GsPluginJob *self); ++GsPermission *gs_plugin_job_get_permission (GsPluginJob *self); ++GsPermissionValue *gs_plugin_job_get_permission_value (GsPluginJob *self); + gchar *gs_plugin_job_to_string (GsPluginJob *self); + void gs_plugin_job_set_action (GsPluginJob *self, + GsPluginAction action); +diff --git a/lib/gs-plugin-job.c b/lib/gs-plugin-job.c +index d9c07225..2f3150e6 100644 +--- a/lib/gs-plugin-job.c ++++ b/lib/gs-plugin-job.c +@@ -49,6 +49,8 @@ struct _GsPluginJob + AsReview *review; + GsPrice *price; + GsChannel *channel; ++ GsPermission *permission; ++ GsPermissionValue *permission_value; + gint64 time_created; + }; + +@@ -70,6 +72,8 @@ enum { + PROP_PRICE, + PROP_CHANNEL, + PROP_TIMEOUT, ++ PROP_PERMISSION, ++ PROP_PERMISSION_VALUE, + PROP_LAST + }; + +@@ -138,6 +142,12 @@ gs_plugin_job_to_string (GsPluginJob *self) + g_autofree gchar *path = g_file_get_path (self->file); + g_string_append_printf (str, " with file=%s", path); + } ++ if (self->permission != NULL) { ++ g_string_append_printf (str, " with permission=%s", gs_permission_get_label (self->permission)); ++ } ++ if (self->permission_value != NULL) { ++ g_string_append_printf (str, " with permission-value=%s", gs_permission_value_get_label (self->permission_value)); ++ } + if (self->plugin != NULL) { + g_string_append_printf (str, " on plugin=%s", + gs_plugin_get_name (self->plugin)); +@@ -454,6 +464,34 @@ gs_plugin_job_get_channel (GsPluginJob *self) + return self->channel; + } + ++void ++gs_plugin_job_set_permission (GsPluginJob *self, GsPermission *permission) ++{ ++ g_return_if_fail (GS_IS_PLUGIN_JOB (self)); ++ g_set_object (&self->permission, permission); ++} ++ ++GsPermission * ++gs_plugin_job_get_permission (GsPluginJob *self) ++{ ++ g_return_val_if_fail (GS_IS_PLUGIN_JOB (self), NULL); ++ return self->permission; ++} ++ ++void ++gs_plugin_job_set_permission_value (GsPluginJob *self, GsPermissionValue *value) ++{ ++ g_return_if_fail (GS_IS_PLUGIN_JOB (self)); ++ g_set_object (&self->permission_value, value); ++} ++ ++GsPermissionValue * ++gs_plugin_job_get_permission_value (GsPluginJob *self) ++{ ++ g_return_val_if_fail (GS_IS_PLUGIN_JOB (self), NULL); ++ return self->permission_value; ++} ++ + static void + gs_plugin_job_get_property (GObject *obj, guint prop_id, GValue *value, GParamSpec *pspec) + { +@@ -508,6 +546,12 @@ gs_plugin_job_get_property (GObject *obj, guint prop_id, GValue *value, GParamSp + case PROP_TIMEOUT: + g_value_set_uint (value, self->timeout); + break; ++ case PROP_PERMISSION: ++ g_value_set_object (value, self->permission); ++ break; ++ case PROP_PERMISSION_VALUE: ++ g_value_set_object (value, self->permission_value); ++ break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec); + break; +@@ -568,6 +612,12 @@ gs_plugin_job_set_property (GObject *obj, guint prop_id, const GValue *value, GP + case PROP_CHANNEL: + gs_plugin_job_set_channel (self, g_value_get_object (value)); + break; ++ case PROP_PERMISSION: ++ gs_plugin_job_set_permission (self, g_value_get_object (value)); ++ break; ++ case PROP_PERMISSION_VALUE: ++ gs_plugin_job_set_permission_value (self, g_value_get_object (value)); ++ break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec); + break; +@@ -588,6 +638,7 @@ gs_plugin_job_finalize (GObject *obj) + g_clear_object (&self->review); + g_clear_object (&self->price); + g_clear_object (&self->channel); ++ g_clear_object (&self->permission); + G_OBJECT_CLASS (gs_plugin_job_parent_class)->finalize (obj); + } + +@@ -682,6 +733,16 @@ gs_plugin_job_class_init (GsPluginJobClass *klass) + GS_TYPE_CHANNEL, + G_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_CHANNEL, pspec); ++ ++ pspec = g_param_spec_object ("permission", NULL, NULL, ++ GS_TYPE_PERMISSION, ++ G_PARAM_READWRITE); ++ g_object_class_install_property (object_class, PROP_PERMISSION, pspec); ++ ++ pspec = g_param_spec_object ("permission-value", NULL, NULL, ++ GS_TYPE_PERMISSION_VALUE, ++ G_PARAM_READWRITE); ++ g_object_class_install_property (object_class, PROP_PERMISSION_VALUE, pspec); + } + + static void +diff --git a/lib/gs-plugin-job.h b/lib/gs-plugin-job.h +index cfd9af78..e4c662aa 100644 +--- a/lib/gs-plugin-job.h ++++ b/lib/gs-plugin-job.h +@@ -29,6 +29,8 @@ + #include "gs-category.h" + #include "gs-plugin-types.h" + #include "gs-price.h" ++#include "gs-permission.h" ++#include "gs-permission-value.h" + + G_BEGIN_DECLS + +@@ -72,6 +74,10 @@ void gs_plugin_job_set_price (GsPluginJob *self, + GsPrice *price); + void gs_plugin_job_set_channel (GsPluginJob *self, + GsChannel *channel); ++void gs_plugin_job_set_permission (GsPluginJob *self, ++ GsPermission *permission); ++void gs_plugin_job_set_permission_value (GsPluginJob *self, ++ GsPermissionValue *value); + + #define gs_plugin_job_newv(a,...) GS_PLUGIN_JOB(g_object_new(GS_TYPE_PLUGIN_JOB, "action", a, __VA_ARGS__)) + +diff --git a/lib/gs-plugin-loader.c b/lib/gs-plugin-loader.c +index 884533cf..e393c5f6 100644 +--- a/lib/gs-plugin-loader.c ++++ b/lib/gs-plugin-loader.c +@@ -140,6 +140,12 @@ typedef gboolean (*GsPluginSwitchChannelFunc) (GsPlugin *plugin, + GsChannel *channel, + GCancellable *cancellable, + GError **error); ++typedef gboolean (*GsPluginSetPermissionFunc) (GsPlugin *plugin, ++ GsApp *app, ++ GsPermission *permission, ++ GsPermissionValue *value, ++ GCancellable *cancellable, ++ GError **error); + typedef gboolean (*GsPluginReviewFunc) (GsPlugin *plugin, + GsApp *app, + AsReview *review, +@@ -670,6 +676,15 @@ gs_plugin_loader_call_vfunc (GsPluginLoaderHelper *helper, + cancellable, &error_local); + } + break; ++ case GS_PLUGIN_ACTION_SET_PERMISSION: ++ { ++ GsPluginSetPermissionFunc plugin_func = func; ++ ret = plugin_func (plugin, app, ++ gs_plugin_job_get_permission (helper->plugin_job), ++ gs_plugin_job_get_permission_value (helper->plugin_job), ++ cancellable, &error_local); ++ } ++ break; + case GS_PLUGIN_ACTION_REVIEW_SUBMIT: + case GS_PLUGIN_ACTION_REVIEW_UPVOTE: + case GS_PLUGIN_ACTION_REVIEW_DOWNVOTE: +diff --git a/lib/gs-plugin-types.h b/lib/gs-plugin-types.h +index 2c968b5d..71195a49 100644 +--- a/lib/gs-plugin-types.h ++++ b/lib/gs-plugin-types.h +@@ -256,6 +256,7 @@ typedef enum { + * @GS_PLUGIN_ACTION_PURCHASE: Purchase an app + * @GS_PLUGIN_ACTION_DOWNLOAD: Download an application + * @GS_PLUGIN_ACTION_SWITCH_CHANNEL: Switch app channel ++ * @GS_PLUGIN_ACTION_SET_PERMISSION: Set app permission + * + * The plugin action. + **/ +@@ -305,6 +306,7 @@ typedef enum { + GS_PLUGIN_ACTION_PURCHASE, + GS_PLUGIN_ACTION_DOWNLOAD, + GS_PLUGIN_ACTION_SWITCH_CHANNEL, ++ GS_PLUGIN_ACTION_SET_PERMISSION, + /*< private >*/ + GS_PLUGIN_ACTION_LAST + } GsPluginAction; +diff --git a/lib/gs-plugin-vfuncs.h b/lib/gs-plugin-vfuncs.h +index 61609b60..efdc1405 100644 +--- a/lib/gs-plugin-vfuncs.h ++++ b/lib/gs-plugin-vfuncs.h +@@ -41,6 +41,7 @@ + #include "gs-app-list.h" + #include "gs-category.h" + #include "gs-price.h" ++#include "gs-permission.h" + + G_BEGIN_DECLS + +@@ -770,6 +771,26 @@ gboolean gs_plugin_app_upgrade_trigger (GsPlugin *plugin, + GCancellable *cancellable, + GError **error); + ++/** ++ * gs_plugin_app_set_permission: ++ * @plugin: a #GsPlugin ++ * @app: a #GsApp ++ * @permission: a #GsPermission to set ++ * @value: value to set for the permission ++ * @cancellable: a #GCancellable, or %NULL ++ * @error: a #GError, or %NULL ++ * ++ * Set an app permission. ++ * ++ * Returns: %TRUE for success or if not relevant ++ **/ ++gboolean gs_plugin_app_set_permission (GsPlugin *plugin, ++ GsApp *app, ++ GsPermission *permission, ++ GsPermissionValue *value, ++ GCancellable *cancellable, ++ GError **error); ++ + /** + * gs_plugin_review_submit: + * @plugin: a #GsPlugin +diff --git a/lib/gs-plugin.c b/lib/gs-plugin.c +index 31bb08be..032fc391 100644 +--- a/lib/gs-plugin.c ++++ b/lib/gs-plugin.c +@@ -1765,6 +1765,8 @@ gs_plugin_action_to_function_name (GsPluginAction action) + return "gs_plugin_app_purchase"; + if (action == GS_PLUGIN_ACTION_SWITCH_CHANNEL) + return "gs_plugin_app_switch_channel"; ++ if (action == GS_PLUGIN_ACTION_SET_PERMISSION) ++ return "gs_plugin_app_set_permission"; + return NULL; + } + +@@ -1869,6 +1871,8 @@ gs_plugin_action_to_string (GsPluginAction action) + return "purchase"; + if (action == GS_PLUGIN_ACTION_SWITCH_CHANNEL) + return "switch-channel"; ++ if (action == GS_PLUGIN_ACTION_SET_PERMISSION) ++ return "set-permission"; + return NULL; + } + +@@ -1973,6 +1977,8 @@ gs_plugin_action_from_string (const gchar *action) + return GS_PLUGIN_ACTION_PURCHASE; + if (g_strcmp0 (action, "switch-channel") == 0) + return GS_PLUGIN_ACTION_SWITCH_CHANNEL; ++ if (g_strcmp0 (action, "set-permission") == 0) ++ return GS_PLUGIN_ACTION_SET_PERMISSION; + return GS_PLUGIN_ACTION_UNKNOWN; + } + +diff --git a/lib/meson.build b/lib/meson.build +index ae5ad87b..ce019a47 100644 +--- a/lib/meson.build ++++ b/lib/meson.build +@@ -44,6 +44,8 @@ install_headers([ + 'gs-category.h', + 'gs-channel.h', + 'gs-os-release.h', ++ 'gs-permission.h', ++ 'gs-permission-value.h', + 'gs-plugin.h', + 'gs-plugin-event.h', + 'gs-plugin-types.h', +@@ -82,6 +84,8 @@ libgnomesoftware = static_library( + 'gs-ioprio.c', + 'gs-ioprio.h', + 'gs-os-release.c', ++ 'gs-permission.c', ++ 'gs-permission-value.c', + 'gs-plugin.c', + 'gs-plugin-event.c', + 'gs-plugin-job.c', +diff --git a/plugins/snap/gs-plugin-snap.c b/plugins/snap/gs-plugin-snap.c +index c943c64e..9451229b 100644 +--- a/plugins/snap/gs-plugin-snap.c ++++ b/plugins/snap/gs-plugin-snap.c +@@ -1063,6 +1063,172 @@ gs_plugin_refine_app (GsPlugin *plugin, + if (flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON && gs_app_get_pixbuf (app) == NULL) + load_icon (plugin, client, app, gs_app_get_metadata_item (app, "snap::name"), local_snap, store_snap, cancellable); + ++ if (gs_app_get_permissions (app)->len == 0) { ++ g_autoptr(GPtrArray) plugs = NULL; ++ g_autoptr(GPtrArray) slots = NULL; ++ guint i; ++ ++ if (!snapd_client_get_interfaces_sync (client, &plugs, &slots, cancellable, error)) ++ return FALSE; ++ for (i = 0; i < plugs->len; i++) { ++ SnapdPlug *plug = plugs->pdata[i]; ++ const gchar *interface_name, *label; ++ g_autoptr(GsPermission) permission = NULL; ++ SnapdConnection *connection = NULL; ++ guint j; ++ ++ /* skip if not relating to this snap */ ++ if (g_strcmp0 (snapd_plug_get_snap (plug), gs_app_get_metadata_item (app, "snap::name")) != 0) ++ continue; ++ ++ interface_name = snapd_plug_get_interface (plug); ++ if (strcmp (interface_name, "account-control") == 0) { ++ label = _("Add user accounts and change passwords"); ++ } else if (strcmp (interface_name, "alsa") == 0) { ++ label = _("Play and record sound"); ++ } else if (strcmp (interface_name, "avahi-observe") == 0) { ++ label = _("Detect network devices using mDNS/DNS-SD (Bonjour/zeroconf)"); ++ } else if (strcmp (interface_name, "bluetooth-control") == 0) { ++ label = _("Access bluetooth hardware directly"); ++ } else if (strcmp (interface_name, "bluez") == 0) { ++ label = _("Use bluetooth devices"); ++ } else if (strcmp (interface_name, "camera") == 0) { ++ label = _("Use your camera"); ++ } else if (strcmp (interface_name, "cups-control") == 0) { ++ label = _("Print documents"); ++ } else if (strcmp (interface_name, "joystick") == 0) { ++ label = _("Use any connected joystick"); ++ } else if (strcmp (interface_name, "docker") == 0) { ++ label = _("Allow connecting to the Docker service"); ++ } else if (strcmp (interface_name, "firewall-control") == 0) { ++ label = _("Configure network firewall"); ++ } else if (strcmp (interface_name, "fuse-support") == 0) { ++ label = _("Setup and use privileged FUSE filesystems"); ++ } else if (strcmp (interface_name, "fwupd") == 0) { ++ label = _("Update firmware on this device"); ++ } else if (strcmp (interface_name, "hardware-observe") == 0) { ++ label = _("Access hardware information"); ++ } else if (strcmp (interface_name, "hardware-random-control") == 0) { ++ label = _("Provide entropy to hardware random number generator"); ++ } else if (strcmp (interface_name, "hardware-random-observe") == 0) { ++ label = _("Use hardware-generated random numbers"); ++ } else if (strcmp (interface_name, "home") == 0) { ++ label = _("Access files in your home folder"); ++ } else if (strcmp (interface_name, "libvirt") == 0) { ++ label = _("Access libvirt service"); ++ } else if (strcmp (interface_name, "locale-control") == 0) { ++ label = _("Change system language and region settings"); ++ } else if (strcmp (interface_name, "location-control") == 0) { ++ label = _("Change location settings and providers"); ++ } else if (strcmp (interface_name, "location-observe") == 0) { ++ label = _("Access your location"); ++ } else if (strcmp (interface_name, "log-observe") == 0) { ++ label = _("Read system and application logs"); ++ } else if (strcmp (interface_name, "lxd") == 0) { ++ label = _("Access LXD service"); ++ //} else if (strcmp (interface_name, "media-hub") == 0) { ++ // label = _("access the media-hub service"); ++ } else if (strcmp (interface_name, "modem-manager") == 0) { ++ label = _("Use and configure modems"); ++ } else if (strcmp (interface_name, "mount-observe") == 0) { ++ label = _("Read system mount information and disk quotas"); ++ } else if (strcmp (interface_name, "mpris") == 0) { ++ label = _("Control music and video players"); ++ } else if (strcmp (interface_name, "network-control") == 0) { ++ label = _("Change low-level network settings"); ++ } else if (strcmp (interface_name, "network-manager") == 0) { ++ label = _("Access the NetworkManager service to read and change network settings"); ++ } else if (strcmp (interface_name, "network-observe") == 0) { ++ label = _("Read access to network settings"); ++ } else if (strcmp (interface_name, "network-setup-control") == 0) { ++ label = _("Change network settings"); ++ } else if (strcmp (interface_name, "network-setup-observe") == 0) { ++ label = _("Read network settings"); ++ } else if (strcmp (interface_name, "ofono") == 0) { ++ label = _("Access the ofono service to read and change network settings for mobile telephony"); ++ } else if (strcmp (interface_name, "openvswitch") == 0) { ++ label = _("Control Open vSwitch hardware"); ++ } else if (strcmp (interface_name, "optical-drive") == 0) { ++ label = _("Read from CD/DVD"); ++ } else if (strcmp (interface_name, "password-manager-service") == 0) { ++ label = _("Read, add, change, or remove saved passwords"); ++ } else if (strcmp (interface_name, "ppp") == 0) { ++ label = _("Access pppd and ppp devices for configuring Point-to-Point Protocol connections"); ++ } else if (strcmp (interface_name, "process-control") == 0) { ++ label = _("Pause or end any process on the system"); ++ } else if (strcmp (interface_name, "pulseaudio") == 0) { ++ label = _("Play and record sound"); ++ } else if (strcmp (interface_name, "raw-usb") == 0) { ++ label = _("Access USB hardware directly"); ++ } else if (strcmp (interface_name, "removable-media") == 0) { ++ label = _("Read/write files on removable storage devices"); ++ } else if (strcmp (interface_name, "screen-inhibit-control") == 0) { ++ label = _("Prevent screen sleep/lock"); ++ } else if (strcmp (interface_name, "serial-port") == 0) { ++ label = _("Access serial port hardware"); ++ } else if (strcmp (interface_name, "shutdown") == 0) { ++ label = _("Restart or power off the device"); ++ } else if (strcmp (interface_name, "snapd-control") == 0) { ++ label = _("Install, remove and configure software"); ++ } else if (strcmp (interface_name, "storage-framework-service") == 0) { ++ label = _("Access Storage Framework service"); ++ } else if (strcmp (interface_name, "system-observe") == 0) { ++ label = _("Read process and system information"); ++ } else if (strcmp (interface_name, "system-trace") == 0) { ++ label = _("Monitor and control any running program"); ++ } else if (strcmp (interface_name, "time-control") == 0) { ++ label = _("Change the date and time"); ++ } else if (strcmp (interface_name, "timeserver-control") == 0) { ++ label = _("Change time server settings"); ++ } else if (strcmp (interface_name, "timezone-control") == 0) { ++ label = _("Change the time zone"); ++ } else if (strcmp (interface_name, "udisks2") == 0) { ++ label = _("Access the UDisks2 service for configuring disks and removable media"); ++ } else if (strcmp (interface_name, "unity8-calendar") == 0) { ++ label = _("Read/change shared calendar events in Ubuntu Unity 8"); ++ } else if (strcmp (interface_name, "unity8-contacts") == 0) { ++ label = _("Read/change shared contacts in Ubuntu Unity 8"); ++ } else if (strcmp (interface_name, "upower-observe") == 0) { ++ label = _("Access energy usage data"); ++ } else if (strcmp (interface_name, "u2f-devices") == 0) { ++ label = _("Read/write access to U2F devices exposed"); ++ } else { ++ g_debug ("Skipping plug with interface %s", interface_name); ++ continue; ++ } ++ /* map interfaces to known permissions */ ++ permission = gs_permission_new (label); ++ gs_permission_add_metadata (permission, "snap::plug", snapd_plug_get_name (plug)); ++ ++ if (snapd_plug_get_connections (plug)->len > 0) ++ connection = g_ptr_array_index (snapd_plug_get_connections (plug), 0); ++ for (j = 0; j < slots->len; j++) { ++ SnapdSlot *slot = slots->pdata[j]; ++ g_autoptr(GsPermissionValue) value = NULL; ++ g_autofree gchar *value_label = NULL; ++ ++ /* skip slots we can't connect to */ ++ if (g_strcmp0 (snapd_plug_get_interface (plug), snapd_slot_get_interface (slot)) != 0) ++ continue; ++ ++ if (strcmp (snapd_slot_get_snap (slot), "core") == 0) ++ value_label = g_strdup_printf (":%s", snapd_slot_get_name (slot)); ++ else ++ value_label = g_strdup_printf ("%s:%s", snapd_slot_get_snap (slot), snapd_slot_get_name (slot)); ++ value = gs_permission_value_new (value_label); ++ gs_permission_value_add_metadata (value, "snap::snap", snapd_slot_get_snap (slot)); ++ gs_permission_value_add_metadata (value, "snap::slot", snapd_slot_get_name (slot)); ++ gs_permission_add_value (permission, value); ++ ++ if (connection != NULL && ++ g_strcmp0 (snapd_slot_get_snap (slot), snapd_connection_get_snap (connection)) == 0 && ++ g_strcmp0 (snapd_slot_get_name (slot), snapd_connection_get_name (connection)) == 0) ++ gs_permission_set_value (permission, value); ++ } ++ gs_app_add_permission (app, permission); ++ } ++ } ++ + return TRUE; + } + +@@ -1274,6 +1440,55 @@ gs_plugin_app_remove (GsPlugin *plugin, + return TRUE; + } + ++gboolean ++gs_plugin_app_set_permission (GsPlugin *plugin, ++ GsApp *app, ++ GsPermission *permission, ++ GsPermissionValue *value, ++ GCancellable *cancellable, ++ GError **error) ++{ ++ g_autoptr(SnapdClient) client = NULL; ++ const gchar *plug_snap, *plug_name; ++ ++ /* We can set permissions on apps we know of */ ++ if (g_strcmp0 (gs_app_get_management_plugin (app), "snap") != 0) ++ return TRUE; ++ ++ client = get_client (plugin, error); ++ if (client == NULL) ++ return FALSE; ++ ++ plug_snap = gs_app_get_metadata_item (app, "snap::name"); ++ plug_name = gs_permission_get_metadata_item (permission, "snap::plug"); ++ ++ if (value != NULL) { ++ const gchar *slot_snap, *slot_name; ++ ++ slot_snap = gs_permission_value_get_metadata_item (value, "snap::snap"); ++ slot_name = gs_permission_value_get_metadata_item (value, "snap::slot"); ++ if (!snapd_client_connect_interface_sync (client, ++ plug_snap, ++ plug_name, ++ slot_snap, ++ slot_name, ++ NULL, NULL, ++ cancellable, error)) ++ return FALSE; ++ } else { ++ if (!snapd_client_disconnect_interface_sync (client, ++ plug_snap, ++ plug_name, ++ "", ++ "", ++ NULL, NULL, ++ cancellable, error)) ++ return FALSE; ++ } ++ ++ return TRUE; ++} ++ + gboolean + gs_plugin_auth_login (GsPlugin *plugin, GsAuth *auth, + GCancellable *cancellable, GError **error) +diff --git a/po/POTFILES.in b/po/POTFILES.in +index 5f64e1d1..527f02f2 100644 +--- a/po/POTFILES.in ++++ b/po/POTFILES.in +@@ -41,6 +41,7 @@ src/gs-moderate-page.ui + src/gs-overview-page.c + src/gs-overview-page.ui + src/gs-page.c ++src/gs-permission-dialog.ui + lib/gs-plugin-loader.c + src/gs-popular-tile.c + src/gs-popular-tile.ui +diff --git a/src/gnome-software.gresource.xml b/src/gnome-software.gresource.xml +index 5ce90429..b6c9916d 100644 +--- a/src/gnome-software.gresource.xml ++++ b/src/gnome-software.gresource.xml +@@ -19,6 +19,7 @@ + gs-loading-page.ui + gs-moderate-page.ui + gs-overview-page.ui ++ gs-permission-dialog.ui + gs-popular-tile.ui + gs-prefs-dialog.ui + gs-removal-dialog.ui +diff --git a/src/gs-details-page.c b/src/gs-details-page.c +index 3d57a8af..071f91e6 100644 +--- a/src/gs-details-page.c ++++ b/src/gs-details-page.c +@@ -39,6 +39,7 @@ + #include "gs-review-histogram.h" + #include "gs-review-dialog.h" + #include "gs-review-row.h" ++#include "gs-permission-dialog.h" + + /* the number of reviews to show before clicking the 'More Reviews' button */ + #define SHOW_NR_REVIEWS_INITIAL 4 +@@ -89,6 +90,7 @@ struct _GsDetailsPage + GtkWidget *button_install; + GtkWidget *button_remove; + GtkWidget *button_cancel; ++ GtkWidget *button_permissions; + GtkWidget *button_more_reviews; + GtkWidget *infobar_details_app_norepo; + GtkWidget *infobar_details_app_repo; +@@ -266,6 +268,7 @@ gs_details_page_switch_to (GsPage *page, gboolean scroll_up) + g_autofree gchar *text = NULL; + GtkStyleContext *sc; + GtkAdjustment *adj; ++ GPtrArray *permissions; + + if (gs_shell_get_mode (self->shell) != GS_SHELL_MODE_DETAILS) { + g_warning ("Called switch_to(details) when in mode %s", +@@ -422,6 +425,19 @@ gs_details_page_switch_to (GsPage *page, gboolean scroll_up) + gtk_widget_set_visible (self->button_remove, FALSE); + } + ++ /* permissions button */ ++ switch (gs_app_get_state (self->app)) { ++ case AS_APP_STATE_INSTALLED: ++ case AS_APP_STATE_UPDATABLE: ++ case AS_APP_STATE_UPDATABLE_LIVE: ++ permissions = gs_app_get_permissions (self->app); ++ gtk_widget_set_visible (self->button_permissions, permissions->len > 0); ++ break; ++ default: ++ gtk_widget_set_visible (self->button_permissions, FALSE); ++ break; ++ } ++ + adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (self->scrolledwindow_details)); + gtk_adjustment_set_value (adj, gtk_adjustment_get_lower (adj)); + +@@ -2108,6 +2124,29 @@ gs_details_page_channel_cb (GtkWidget *widget, GsDetailsPage *self) + gtk_widget_show (self->popover_channel); + } + ++static void ++gs_details_page_permission_changed_cb (GsPermissionDialog *dialog, GsPermission *permission, GsPermissionValue *value, GsDetailsPage *self) ++{ ++ g_autoptr(GCancellable) cancellable = g_cancellable_new (); ++ g_set_object (&self->cancellable, cancellable); ++ gs_page_set_app_permission (GS_PAGE (self), self->app, permission, value, self->cancellable); ++} ++ ++static void ++gs_details_page_app_permissions_button_cb (GtkWidget *widget, GsDetailsPage *self) ++{ ++ GtkWidget *dialog; ++ ++ dialog = gs_permission_dialog_new (self->app); ++ g_signal_connect (dialog, "permission-changed", ++ G_CALLBACK (gs_details_page_permission_changed_cb), self); ++ gs_shell_modal_dialog_present (self->shell, GTK_DIALOG (dialog)); ++ ++ /* just destroy */ ++ g_signal_connect_swapped (dialog, "response", ++ G_CALLBACK (gtk_widget_destroy), dialog); ++} ++ + static void + gs_details_page_app_install_button_cb (GtkWidget *widget, GsDetailsPage *self) + { +@@ -2557,6 +2596,9 @@ gs_details_page_setup (GsPage *page, + g_signal_connect (self->button_cancel, "clicked", + G_CALLBACK (gs_details_page_app_cancel_button_cb), + self); ++ g_signal_connect (self->button_permissions, "clicked", ++ G_CALLBACK (gs_details_page_app_permissions_button_cb), ++ self); + g_signal_connect (self->button_more_reviews, "clicked", + G_CALLBACK (gs_details_page_more_reviews_button_cb), + self); +@@ -2660,6 +2702,7 @@ gs_details_page_class_init (GsDetailsPageClass *klass) + gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, button_install); + gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, button_remove); + gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, button_cancel); ++ gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, button_permissions); + gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, button_more_reviews); + gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, infobar_details_app_norepo); + gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, infobar_details_app_repo); +diff --git a/src/gs-details-page.ui b/src/gs-details-page.ui +index 154ce197..a3f2ccb5 100644 +--- a/src/gs-details-page.ui ++++ b/src/gs-details-page.ui +@@ -367,6 +367,22 @@ + end + + ++ ++ ++ True ++ _Permissions ++ 105 ++ True ++ True ++ start ++ start ++ ++ ++ False ++ False ++ 9 ++ ++ + + + False +@@ -568,7 +584,7 @@ + + False + False +- 0 ++ 1 + + + +diff --git a/src/gs-page.c b/src/gs-page.c +index 1432d63d..77f795fd 100644 +--- a/src/gs-page.c ++++ b/src/gs-page.c +@@ -787,6 +787,35 @@ gs_page_launch_app (GsPage *page, GsApp *app, GCancellable *cancellable) + NULL); + } + ++static void ++gs_page_app_permission_set_cb (GObject *source, ++ GAsyncResult *res, ++ gpointer user_data) ++{ ++ GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source); ++ g_autoptr(GError) error = NULL; ++ if (!gs_plugin_loader_job_action_finish (plugin_loader, res, &error)) { ++ g_warning ("failed to set permission on GsApp: %s", error->message); ++ return; ++ } ++} ++ ++void ++gs_page_set_app_permission (GsPage *page, GsApp *app, GsPermission *permission, GsPermissionValue *value, GCancellable *cancellable) ++{ ++ GsPagePrivate *priv = gs_page_get_instance_private (page); ++ g_autoptr(GsPluginJob) plugin_job = NULL; ++ plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_SET_PERMISSION, ++ "app", app, ++ "permission", permission, ++ "permission-value", value, ++ NULL); ++ gs_plugin_loader_job_process_async (priv->plugin_loader, plugin_job, ++ cancellable, ++ gs_page_app_permission_set_cb, ++ NULL); ++} ++ + static void + gs_page_app_shortcut_added_cb (GObject *source, + GAsyncResult *res, +diff --git a/src/gs-page.h b/src/gs-page.h +index 539a0b58..cbc193b7 100644 +--- a/src/gs-page.h ++++ b/src/gs-page.h +@@ -80,6 +80,11 @@ void gs_page_update_app (GsPage *page, + void gs_page_launch_app (GsPage *page, + GsApp *app, + GCancellable *cancellable); ++void gs_page_set_app_permission (GsPage *page, ++ GsApp *app, ++ GsPermission *permission, ++ GsPermissionValue *value, ++ GCancellable *cancellable); + void gs_page_shortcut_add (GsPage *page, + GsApp *app, + GCancellable *cancellable); +diff --git a/src/gs-permission-combo-box.c b/src/gs-permission-combo-box.c +new file mode 100644 +index 00000000..a3cfd86f +--- /dev/null ++++ b/src/gs-permission-combo-box.c +@@ -0,0 +1,125 @@ ++/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- ++ * ++ * Copyright (C) 2017 Canonical Ltd. ++ * ++ * 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 "gs-permission-combo-box.h" ++ ++struct _GsPermissionComboBox ++{ ++ GtkComboBox parent_instance; ++ ++ GsPermission *permission; ++}; ++ ++G_DEFINE_TYPE (GsPermissionComboBox, gs_permission_combo_box, GTK_TYPE_COMBO_BOX) ++ ++enum { ++ SIGNAL_VALUE_CHANGED, ++ SIGNAL_LAST ++}; ++ ++static guint signals [SIGNAL_LAST] = { 0 }; ++ ++GsPermission * ++gs_permission_combo_box_get_permission (GsPermissionComboBox *combo) ++{ ++ g_return_val_if_fail (GS_IS_PERMISSION_COMBO_BOX (combo), NULL); ++ return combo->permission; ++} ++ ++static void ++changed_cb (GsPermissionComboBox *combo) ++{ ++ GtkTreeIter iter; ++ GsPermissionValue *value = NULL; ++ ++ if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter)) ++ gtk_tree_model_get (gtk_combo_box_get_model (GTK_COMBO_BOX (combo)), &iter, 1, &value, -1); ++ ++ g_signal_emit (combo, signals[SIGNAL_VALUE_CHANGED], 0, value); ++} ++ ++static void ++gs_permission_combo_box_dispose (GObject *object) ++{ ++ GsPermissionComboBox *combo = GS_PERMISSION_COMBO_BOX (object); ++ ++ g_clear_object (&combo->permission); ++ ++ G_OBJECT_CLASS (gs_permission_combo_box_parent_class)->dispose (object); ++} ++ ++static void ++gs_permission_combo_box_class_init (GsPermissionComboBoxClass *klass) ++{ ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ ++ object_class->dispose = gs_permission_combo_box_dispose; ++ ++ signals [SIGNAL_VALUE_CHANGED] = ++ g_signal_new ("value-changed", ++ G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, ++ 0, ++ NULL, NULL, g_cclosure_marshal_generic, ++ G_TYPE_NONE, 1, GS_TYPE_PERMISSION_VALUE); ++} ++ ++static void ++gs_permission_combo_box_init (GsPermissionComboBox *combo) ++{ ++} ++ ++GsPermissionComboBox * ++gs_permission_combo_box_new (GsPermission *permission) ++{ ++ GsPermissionComboBox *combo; ++ GtkListStore *store; ++ GtkCellRenderer *renderer; ++ guint i; ++ GtkTreeIter iter; ++ GPtrArray *values; ++ ++ combo = g_object_new (GS_TYPE_PERMISSION_COMBO_BOX, NULL); ++ combo->permission = g_object_ref (permission); ++ ++ store = gtk_list_store_new (2, G_TYPE_STRING, GS_TYPE_PERMISSION_VALUE); ++ gtk_combo_box_set_model (GTK_COMBO_BOX (combo), GTK_TREE_MODEL (store)); ++ ++ renderer = gtk_cell_renderer_text_new (); ++ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), renderer, TRUE); ++ gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (combo), renderer, "text", 0); ++ ++ gtk_list_store_append (store, &iter); ++ gtk_list_store_set (store, &iter, 0, "(disconnected)", 1, NULL, -1); ++ values = gs_permission_get_values (permission); ++ for (i = 0; i < values->len; i++) { ++ GsPermissionValue *value = g_ptr_array_index (values, i); ++ ++ gtk_list_store_append (store, &iter); ++ gtk_list_store_set (store, &iter, 0, gs_permission_value_get_label (value), 1, value, -1); ++ ++ if (value == gs_permission_get_value (permission)) ++ gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo), &iter); ++ } ++ ++ g_signal_connect (combo, "changed", G_CALLBACK (changed_cb), NULL); ++ ++ return combo; ++} +diff --git a/src/gs-permission-combo-box.h b/src/gs-permission-combo-box.h +new file mode 100644 +index 00000000..5b1f6470 +--- /dev/null ++++ b/src/gs-permission-combo-box.h +@@ -0,0 +1,41 @@ ++/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- ++ * ++ * Copyright (C) 2017 Canonical Ltd. ++ * ++ * 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. ++ */ ++ ++#ifndef GS_PERMISSION_COMBO_BOX_H ++#define GS_PERMISSION_COMBO_BOX_H ++ ++#include ++ ++#include "gnome-software-private.h" ++ ++G_BEGIN_DECLS ++ ++#define GS_TYPE_PERMISSION_COMBO_BOX (gs_permission_combo_box_get_type ()) ++ ++G_DECLARE_FINAL_TYPE (GsPermissionComboBox, gs_permission_combo_box, GS, PERMISSION_COMBO_BOX, GtkComboBox) ++ ++GsPermissionComboBox *gs_permission_combo_box_new (GsPermission *permission); ++ ++GsPermission *gs_permission_combo_box_get_permission (GsPermissionComboBox *combo); ++ ++G_END_DECLS ++ ++#endif /* GS_PERMISSION_COMBO_BOX_H */ +diff --git a/src/gs-permission-dialog.c b/src/gs-permission-dialog.c +new file mode 100644 +index 00000000..708427c4 +--- /dev/null ++++ b/src/gs-permission-dialog.c +@@ -0,0 +1,153 @@ ++/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- ++ * ++ * Copyright (C) 2017 Canonical Ltd. ++ * ++ * 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" ++ ++#include "gs-permission-dialog.h" ++#include "gs-permission-switch.h" ++#include "gs-permission-combo-box.h" ++ ++struct _GsPermissionDialog ++{ ++ GtkDialog parent_instance; ++ ++ GsApp *app; ++ GtkWidget *permission_grid; ++ GtkWidget *close_button; ++}; ++ ++G_DEFINE_TYPE (GsPermissionDialog, gs_permission_dialog, GTK_TYPE_DIALOG) ++ ++enum { ++ SIGNAL_PERMISSION_CHANGED, ++ SIGNAL_LAST ++}; ++ ++static guint signals [SIGNAL_LAST] = { 0 }; ++ ++static void ++close_button_clicked (GtkWidget *widget, GsPermissionDialog *dialog) ++{ ++ gtk_widget_destroy (GTK_WIDGET (dialog)); ++} ++ ++static void ++gs_permission_dialog_init (GsPermissionDialog *dialog) ++{ ++ gtk_widget_init_template (GTK_WIDGET (dialog)); ++ ++ g_signal_connect (dialog->close_button, "clicked", ++ G_CALLBACK (close_button_clicked), dialog); ++} ++ ++static void ++gs_permission_dialog_dispose (GObject *object) ++{ ++ GsPermissionDialog *dialog = GS_PERMISSION_DIALOG (object); ++ ++ g_clear_object (&dialog->app); ++ ++ G_OBJECT_CLASS (gs_permission_dialog_parent_class)->dispose (object); ++} ++ ++static void ++gs_permission_dialog_class_init (GsPermissionDialogClass *klass) ++{ ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); ++ ++ object_class->dispose = gs_permission_dialog_dispose; ++ ++ signals [SIGNAL_PERMISSION_CHANGED] = ++ g_signal_new ("permission-changed", ++ G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, ++ 0, ++ NULL, NULL, g_cclosure_marshal_generic, ++ G_TYPE_NONE, 2, GS_TYPE_PERMISSION, GS_TYPE_PERMISSION_VALUE); ++ ++ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Software/gs-permission-dialog.ui"); ++ ++ gtk_widget_class_bind_template_child (widget_class, GsPermissionDialog, permission_grid); ++ gtk_widget_class_bind_template_child (widget_class, GsPermissionDialog, close_button); ++} ++ ++// FIXME: Make a GsPermissionControl interfaces that can be shared between GsPermissionSwitch and GsPermissionComboBox ++ ++static void ++permission_switch_changed_cb (GsPermissionSwitch *sw, GsPermissionValue *value, GsPermissionDialog *dialog) ++{ ++ g_signal_emit (dialog, signals[SIGNAL_PERMISSION_CHANGED], 0, ++ gs_permission_switch_get_permission (sw), ++ value); ++} ++ ++static void ++permission_combo_box_changed_cb (GsPermissionComboBox *combo, GsPermissionValue *value, GsPermissionDialog *dialog) ++{ ++ g_signal_emit (dialog, signals[SIGNAL_PERMISSION_CHANGED], 0, ++ gs_permission_combo_box_get_permission (combo), ++ value); ++} ++ ++static void ++set_row (GsPermissionDialog *dialog, int row, GsPermission *permission) ++{ ++ GtkWidget *label; ++ GtkWidget *control; ++ ++ label = gtk_label_new (gs_permission_get_label (permission)); ++ gtk_label_set_xalign (GTK_LABEL (label), 1.0); ++ gtk_widget_set_hexpand (label, TRUE); ++ gtk_widget_show (label); ++ gtk_grid_attach (GTK_GRID (dialog->permission_grid), label, 0, row, 1, 1); ++ ++ if (gs_permission_get_values (permission)->len == 1) { ++ control = GTK_WIDGET (gs_permission_switch_new (permission)); ++ g_signal_connect (control, "changed", G_CALLBACK (permission_switch_changed_cb), dialog); ++ } ++ else { ++ control = GTK_WIDGET (gs_permission_combo_box_new (permission)); ++ g_signal_connect (control, "value-changed", G_CALLBACK (permission_combo_box_changed_cb), dialog); ++ } ++ gtk_widget_show (control); ++ gtk_grid_attach (GTK_GRID (dialog->permission_grid), control, 1, row, 1, 1); ++} ++ ++GtkWidget * ++gs_permission_dialog_new (GsApp *app) ++{ ++ GsPermissionDialog *dialog; ++ GPtrArray *permissions; ++ guint i; ++ ++ dialog = g_object_new (GS_TYPE_PERMISSION_DIALOG, ++ "use-header-bar", TRUE, ++ NULL); ++ dialog->app = g_object_ref (app); ++ ++ permissions = gs_app_get_permissions (app); ++ for (i = 0; i < permissions->len; i++) { ++ GsPermission *permission = g_ptr_array_index (permissions, i); ++ set_row (dialog, i, permission); ++ } ++ ++ return GTK_WIDGET (dialog); ++} +diff --git a/src/gs-permission-dialog.h b/src/gs-permission-dialog.h +new file mode 100644 +index 00000000..e13114df +--- /dev/null ++++ b/src/gs-permission-dialog.h +@@ -0,0 +1,39 @@ ++/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- ++ * ++ * Copyright (C) 2017 Canonical Ltd. ++ * ++ * 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. ++ */ ++ ++#ifndef GS_PERMISSION_DIALOG_H ++#define GS_PERMISSION_DIALOG_H ++ ++#include ++ ++#include "gnome-software-private.h" ++ ++G_BEGIN_DECLS ++ ++#define GS_TYPE_PERMISSION_DIALOG (gs_permission_dialog_get_type ()) ++ ++G_DECLARE_FINAL_TYPE (GsPermissionDialog, gs_permission_dialog, GS, PERMISSION_DIALOG, GtkDialog) ++ ++GtkWidget *gs_permission_dialog_new (GsApp *app); ++ ++G_END_DECLS ++ ++#endif /* GS_PERMISSION_DIALOG_H */ +diff --git a/src/gs-permission-dialog.ui b/src/gs-permission-dialog.ui +new file mode 100644 +index 00000000..e4f2955d +--- /dev/null ++++ b/src/gs-permission-dialog.ui +@@ -0,0 +1,67 @@ ++ ++ ++ ++ ++ ++ +diff --git a/src/gs-permission-switch.c b/src/gs-permission-switch.c +new file mode 100644 +index 00000000..ea41969f +--- /dev/null ++++ b/src/gs-permission-switch.c +@@ -0,0 +1,100 @@ ++/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- ++ * ++ * Copyright (C) 2017 Canonical Ltd. ++ * ++ * 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" ++ ++#include "gs-permission-switch.h" ++ ++struct _GsPermissionSwitch ++{ ++ GtkSwitch parent_instance; ++ ++ GsPermission *permission; ++}; ++ ++G_DEFINE_TYPE (GsPermissionSwitch, gs_permission_switch, GTK_TYPE_SWITCH) ++ ++enum { ++ SIGNAL_CHANGED, ++ SIGNAL_LAST ++}; ++ ++static guint signals [SIGNAL_LAST] = { 0 }; ++ ++GsPermission * ++gs_permission_switch_get_permission (GsPermissionSwitch *sw) ++{ ++ g_return_val_if_fail (GS_IS_PERMISSION_SWITCH (sw), NULL); ++ return sw->permission; ++} ++ ++static void ++active_changed_cb (GsPermissionSwitch *sw) ++{ ++ GsPermissionValue *value; ++ ++ value = g_ptr_array_index (gs_permission_get_values (sw->permission), 0); ++ g_signal_emit (sw, signals[SIGNAL_CHANGED], 0, ++ gtk_switch_get_active (GTK_SWITCH (sw)) ? value : NULL); ++} ++ ++static void ++gs_permission_switch_dispose (GObject *object) ++{ ++ GsPermissionSwitch *sw = GS_PERMISSION_SWITCH (object); ++ ++ g_clear_object (&sw->permission); ++ ++ G_OBJECT_CLASS (gs_permission_switch_parent_class)->dispose (object); ++} ++ ++static void ++gs_permission_switch_class_init (GsPermissionSwitchClass *klass) ++{ ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ ++ object_class->dispose = gs_permission_switch_dispose; ++ ++ signals [SIGNAL_CHANGED] = ++ g_signal_new ("changed", ++ G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, ++ 0, ++ NULL, NULL, g_cclosure_marshal_generic, ++ G_TYPE_NONE, 1, GS_TYPE_PERMISSION_VALUE); ++} ++ ++static void ++gs_permission_switch_init (GsPermissionSwitch *sw) ++{ ++} ++ ++GsPermissionSwitch * ++gs_permission_switch_new (GsPermission *permission) ++{ ++ GsPermissionSwitch *sw; ++ ++ sw = g_object_new (GS_TYPE_PERMISSION_SWITCH, NULL); ++ sw->permission = g_object_ref (permission); ++ gtk_switch_set_active (GTK_SWITCH (sw), gs_permission_get_value (permission) != NULL); ++ g_signal_connect (sw, "notify::active", G_CALLBACK (active_changed_cb), NULL); ++ ++ return sw; ++} +diff --git a/src/gs-permission-switch.h b/src/gs-permission-switch.h +new file mode 100644 +index 00000000..72406863 +--- /dev/null ++++ b/src/gs-permission-switch.h +@@ -0,0 +1,41 @@ ++/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- ++ * ++ * Copyright (C) 2017 Canonical Ltd. ++ * ++ * 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. ++ */ ++ ++#ifndef GS_PERMISSION_SWITCH_H ++#define GS_PERMISSION_SWITCH_H ++ ++#include ++ ++#include "gnome-software-private.h" ++ ++G_BEGIN_DECLS ++ ++#define GS_TYPE_PERMISSION_SWITCH (gs_permission_switch_get_type ()) ++ ++G_DECLARE_FINAL_TYPE (GsPermissionSwitch, gs_permission_switch, GS, PERMISSION_SWITCH, GtkSwitch) ++ ++GsPermissionSwitch *gs_permission_switch_new (GsPermission *permission); ++ ++GsPermission *gs_permission_switch_get_permission (GsPermissionSwitch *sw); ++ ++G_END_DECLS ++ ++#endif /* GS_PERMISSION_SWITCH_H */ +diff --git a/src/meson.build b/src/meson.build +index 2ea8f755..ea2c229c 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -43,6 +43,9 @@ gnome_software_sources = [ + 'gs-moderate-page.c', + 'gs-overview-page.c', + 'gs-page.c', ++ 'gs-permission-combo-box.c', ++ 'gs-permission-dialog.c', ++ 'gs-permission-switch.c', + 'gs-popular-tile.c', + 'gs-prefs-dialog.c', + 'gs-progress-button.c', +-- +2.20.1 + diff -Nru gnome-software-3.30.6/debian/patches/0015-build-Translate-Ubuntu-s-.desktop-file.patch gnome-software-3.30.6/debian/patches/0015-build-Translate-Ubuntu-s-.desktop-file.patch --- gnome-software-3.30.6/debian/patches/0015-build-Translate-Ubuntu-s-.desktop-file.patch 1970-01-01 00:00:00.000000000 +0000 +++ gnome-software-3.30.6/debian/patches/0015-build-Translate-Ubuntu-s-.desktop-file.patch 2019-04-16 22:49:03.000000000 +0000 @@ -0,0 +1,60 @@ +From: Gunnar Hjalmarsson +Date: Thu, 22 Mar 2018 08:03:14 -0400 +Subject: [PATCH 15/24] build: Translate Ubuntu's .desktop file + +--- + data/meson.build | 3 +++ + data/org.gnome.Software.desktop | 19 +++++++++++++++++++ + po/POTFILES.in | 1 + + 3 files changed, 23 insertions(+) + create mode 100644 data/org.gnome.Software.desktop + +diff --git a/data/meson.build b/data/meson.build +index 8e5443a..5267120 100644 +--- a/data/meson.build ++++ b/data/meson.build +@@ -6,6 +6,9 @@ compiled_schemas = gnome.compile_schemas() + install_data('org.gnome.software.gschema.xml', + install_dir : 'share/glib-2.0/schemas') + ++install_data('org.gnome.Software.desktop', ++ install_dir : 'share/ubuntu/applications') ++ + if get_option('external_appstream') + # replace @libexecdir@ + conf_data = configuration_data() +diff --git a/data/org.gnome.Software.desktop b/data/org.gnome.Software.desktop +new file mode 100644 +index 0000000..663dc20 +--- /dev/null ++++ b/data/org.gnome.Software.desktop +@@ -0,0 +1,19 @@ ++[Desktop Entry] ++Name=Ubuntu Software ++Comment=Add, remove or update software on this computer ++# Translators: Do NOT translate or transliterate this text (this is an icon file name)! ++Icon=ubuntusoftware ++Exec=gnome-software %U ++Terminal=false ++Type=Application ++Categories=GNOME;GTK;System;PackageManager; ++# Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! ++Keywords=Updates;Upgrade;Sources;Repositories;Preferences;Install;Uninstall;Program;Software;App;Store; ++StartupNotify=true ++MimeType=x-scheme-handler/appstream;x-scheme-handler/apt;x-scheme-handler/snap; ++X-GNOME-Bugzilla-Bugzilla=GNOME ++X-GNOME-Bugzilla-Product=gnome-software ++X-GNOME-Bugzilla-Component=gnome-software ++X-GNOME-UsesNotifications=true ++DBusActivatable=true ++X-Ubuntu-Gettext-Domain=gnome-software +diff --git a/po/POTFILES.in b/po/POTFILES.in +index 527f02f..5c9d3f9 100644 +--- a/po/POTFILES.in ++++ b/po/POTFILES.in +@@ -1,4 +1,5 @@ + data/appdata/org.gnome.Software.appdata.xml.in ++data/org.gnome.Software.desktop + data/org.gnome.software.external-appstream.policy.in + data/org.gnome.software.gschema.xml + src/gnome-software-local-file.desktop.in diff -Nru gnome-software-3.30.6/debian/patches/0016-snap-Use-default-icon-if-none-provided.patch gnome-software-3.30.6/debian/patches/0016-snap-Use-default-icon-if-none-provided.patch --- gnome-software-3.30.6/debian/patches/0016-snap-Use-default-icon-if-none-provided.patch 1970-01-01 00:00:00.000000000 +0000 +++ gnome-software-3.30.6/debian/patches/0016-snap-Use-default-icon-if-none-provided.patch 2019-06-10 00:01:32.000000000 +0000 @@ -0,0 +1,93 @@ +From 8ba3c2d40f65b3a708d5f9c82682184a224f3f1c Mon Sep 17 00:00:00 2001 +From: Robert Ancell +Date: Mon, 16 Apr 2018 11:17:42 +1200 +Subject: [PATCH 16/31] snap: Use default icon if none provided + +--- + plugins/snap/default-snap-icon.svg | 1 + + plugins/snap/gs-plugin-snap.c | 17 +++++++++++++++-- + plugins/snap/gs-plugin-snap.gresource.xml | 6 ++++++ + plugins/snap/meson.build | 8 ++++++++ + 4 files changed, 30 insertions(+), 2 deletions(-) + create mode 100644 plugins/snap/default-snap-icon.svg + create mode 100644 plugins/snap/gs-plugin-snap.gresource.xml + +diff --git a/plugins/snap/default-snap-icon.svg b/plugins/snap/default-snap-icon.svg +new file mode 100644 +index 00000000..c2759591 +--- /dev/null ++++ b/plugins/snap/default-snap-icon.svg +@@ -0,0 +1 @@ ++ +\ No newline at end of file +diff --git a/plugins/snap/gs-plugin-snap.c b/plugins/snap/gs-plugin-snap.c +index 9451229b..dd1db74a 100644 +--- a/plugins/snap/gs-plugin-snap.c ++++ b/plugins/snap/gs-plugin-snap.c +@@ -789,6 +789,9 @@ load_store_icon (GsApp *app, SnapdSnap *snap) + static gboolean + load_icon (GsPlugin *plugin, SnapdClient *client, GsApp *app, const gchar *id, SnapdSnap *local_snap, SnapdSnap *store_snap, GCancellable *cancellable) + { ++ g_autoptr(GdkPixbuf) pixbuf = NULL; ++ g_autoptr(GError) error = NULL; ++ + if (local_snap != NULL) { + if (load_snap_icon (app, client, local_snap, cancellable)) + return TRUE; +@@ -798,9 +801,19 @@ load_icon (GsPlugin *plugin, SnapdClient *client, GsApp *app, const gchar *id, S + + if (store_snap == NULL) + store_snap = get_store_snap (plugin, gs_app_get_metadata_item (app, "snap::name"), FALSE, cancellable, NULL); +- if (store_snap != NULL) +- return load_store_icon (app, store_snap); ++ if (store_snap != NULL) { ++ if (load_store_icon (app, store_snap)) ++ return TRUE; ++ } ++ ++ /* Default to built-in icon */ ++ pixbuf = gdk_pixbuf_new_from_resource_at_scale ("/org/gnome/Software/Snap/default-snap-icon.svg", 64, 64, TRUE, &error); ++ if (pixbuf != NULL) { ++ gs_app_set_pixbuf (app, pixbuf); ++ return TRUE; ++ } + ++ g_warning ("Failed to load built-in icon: %s", error->message); + return FALSE; + } + +diff --git a/plugins/snap/gs-plugin-snap.gresource.xml b/plugins/snap/gs-plugin-snap.gresource.xml +new file mode 100644 +index 00000000..6a35050a +--- /dev/null ++++ b/plugins/snap/gs-plugin-snap.gresource.xml +@@ -0,0 +1,6 @@ ++ ++ ++ ++ default-snap-icon.svg ++ ++ +diff --git a/plugins/snap/meson.build b/plugins/snap/meson.build +index 41f2c7cb..2712f839 100644 +--- a/plugins/snap/meson.build ++++ b/plugins/snap/meson.build +@@ -1,7 +1,15 @@ + cargs = ['-DG_LOG_DOMAIN="GsPluginSnap"'] + ++resources_src = gnome.compile_resources( ++ 'gs-plugin-snap-resources', ++ 'gs-plugin-snap.gresource.xml', ++ source_dir : '.', ++ c_name : 'gs_plugin_snap' ++) ++ + shared_module( + 'gs_plugin_snap', ++ resources_src, + sources : [ + 'gs-plugin-snap.c' + ], +-- +2.20.1 + diff -Nru gnome-software-3.30.6/debian/patches/0017-snap-Make-snaps-purchasable.patch gnome-software-3.30.6/debian/patches/0017-snap-Make-snaps-purchasable.patch --- gnome-software-3.30.6/debian/patches/0017-snap-Make-snaps-purchasable.patch 1970-01-01 00:00:00.000000000 +0000 +++ gnome-software-3.30.6/debian/patches/0017-snap-Make-snaps-purchasable.patch 2019-04-16 22:49:03.000000000 +0000 @@ -0,0 +1,121 @@ +From: Robert Ancell +Date: Wed, 24 Jan 2018 15:53:56 +1300 +Subject: [PATCH 17/24] snap: Make snaps purchasable + +--- + plugins/snap/gs-plugin-snap.c | 66 ++++++++++++++++++++++++++++++++++++++++--- + 1 file changed, 62 insertions(+), 4 deletions(-) + +diff --git a/plugins/snap/gs-plugin-snap.c b/plugins/snap/gs-plugin-snap.c +index 88b2015..630170a 100644 +--- a/plugins/snap/gs-plugin-snap.c ++++ b/plugins/snap/gs-plugin-snap.c +@@ -147,6 +147,14 @@ snapd_error_convert (GError **perror) + case SNAPD_ERROR_TWO_FACTOR_INVALID: + error->code = GS_PLUGIN_ERROR_AUTH_INVALID; + break; ++ case SNAPD_ERROR_PAYMENT_NOT_SETUP: ++ error->code = GS_PLUGIN_ERROR_PURCHASE_NOT_SETUP; ++ g_free (error->message); ++ error->message = g_strdup ("do online using @https://my.ubuntu.com/payment/edit"); ++ break; ++ case SNAPD_ERROR_PAYMENT_DECLINED: ++ error->code = GS_PLUGIN_ERROR_PURCHASE_DECLINED; ++ break; + case SNAPD_ERROR_CONNECTION_FAILED: + case SNAPD_ERROR_WRITE_FAILED: + case SNAPD_ERROR_READ_FAILED: +@@ -155,8 +163,6 @@ snapd_error_convert (GError **perror) + case SNAPD_ERROR_PERMISSION_DENIED: + case SNAPD_ERROR_FAILED: + case SNAPD_ERROR_TERMS_NOT_ACCEPTED: +- case SNAPD_ERROR_PAYMENT_NOT_SETUP: +- case SNAPD_ERROR_PAYMENT_DECLINED: + case SNAPD_ERROR_ALREADY_INSTALLED: + case SNAPD_ERROR_NOT_INSTALLED: + case SNAPD_ERROR_NO_UPDATE_AVAILABLE: +@@ -333,6 +339,7 @@ snap_to_app (GsPlugin *plugin, SnapdSnap *snap) + gs_plugin_cache_add (plugin, unique_id, app); + } + ++ gs_app_set_metadata (app, "snap::id", snapd_snap_get_id (snap)); + gs_app_set_management_plugin (app, "snap"); + if (gs_app_get_kind (app) != AS_APP_KIND_DESKTOP) + gs_app_add_quirk (app, AS_APP_QUIRK_NOT_LAUNCHABLE); +@@ -994,8 +1001,12 @@ gs_plugin_refine_app (GsPlugin *plugin, + gs_app_set_state (app, AS_APP_STATE_INSTALLED); + } + } +- else +- gs_app_set_state (app, AS_APP_STATE_AVAILABLE); ++ else { ++ if (store_snap != NULL && snapd_snap_get_status (store_snap) == SNAPD_SNAP_STATUS_PRICED) ++ gs_app_set_state (app, AS_APP_STATE_PURCHASABLE); ++ else ++ gs_app_set_state (app, AS_APP_STATE_AVAILABLE); ++ } + + /* use store information for basic metadata over local information */ + snap = store_snap != NULL ? store_snap : local_snap; +@@ -1038,7 +1049,16 @@ gs_plugin_refine_app (GsPlugin *plugin, + + /* add information specific to store snaps */ + if (store_snap != NULL) { ++ GPtrArray *prices; ++ + gs_app_set_origin (app, priv->store_name); ++ ++ prices = snapd_snap_get_prices (store_snap); ++ if (prices->len > 0) { ++ SnapdPrice *price = prices->pdata[0]; ++ gs_app_set_price (app, snapd_price_get_amount (price), snapd_price_get_currency (price)); ++ } ++ + gs_app_set_size_download (app, snapd_snap_get_download_size (store_snap)); + + if (flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_SCREENSHOTS && gs_app_get_screenshots (app)->len == 0) { +@@ -1243,6 +1263,44 @@ gs_plugin_refine_app (GsPlugin *plugin, + return TRUE; + } + ++gboolean ++gs_plugin_app_purchase (GsPlugin *plugin, ++ GsApp *app, ++ GsPrice *price, ++ GCancellable *cancellable, ++ GError **error) ++{ ++ g_autoptr(SnapdClient) client = NULL; ++ const gchar *id; ++ ++ /* We can only purchase apps we know of */ ++ if (g_strcmp0 (gs_app_get_management_plugin (app), "snap") != 0) ++ return TRUE; ++ ++ client = get_client (plugin, error); ++ if (client == NULL) ++ return FALSE; ++ ++ gs_app_set_state (app, AS_APP_STATE_PURCHASING); ++ ++ if (!snapd_client_check_buy_sync (client, cancellable, error)) { ++ gs_app_set_state_recover (app); ++ snapd_error_convert (error); ++ return FALSE; ++ } ++ ++ id = gs_app_get_metadata_item (app, "snap::id"); ++ if (!snapd_client_buy_sync (client, id, gs_price_get_amount (price), gs_price_get_currency (price), cancellable, error)) { ++ gs_app_set_state_recover (app); ++ snapd_error_convert (error); ++ return FALSE; ++ } ++ ++ gs_app_set_state (app, AS_APP_STATE_AVAILABLE); ++ ++ return TRUE; ++} ++ + static void + progress_cb (SnapdClient *client, SnapdChange *change, gpointer deprecated, gpointer user_data) + { diff -Nru gnome-software-3.30.6/debian/patches/0018-Disable-paid-snap-support-unless-env-variable-GNOME_.patch gnome-software-3.30.6/debian/patches/0018-Disable-paid-snap-support-unless-env-variable-GNOME_.patch --- gnome-software-3.30.6/debian/patches/0018-Disable-paid-snap-support-unless-env-variable-GNOME_.patch 1970-01-01 00:00:00.000000000 +0000 +++ gnome-software-3.30.6/debian/patches/0018-Disable-paid-snap-support-unless-env-variable-GNOME_.patch 2019-04-16 22:49:03.000000000 +0000 @@ -0,0 +1,29 @@ +From: Robert Ancell +Date: Thu, 19 Apr 2018 15:33:42 +1200 +Subject: [PATCH 18/24] Disable paid snap support unless env variable + GNOME_SOFTWARE_SHOW_PAID is set + +--- + plugins/snap/gs-plugin-snap.c | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) + +diff --git a/plugins/snap/gs-plugin-snap.c b/plugins/snap/gs-plugin-snap.c +index 630170a..8339ac9 100644 +--- a/plugins/snap/gs-plugin-snap.c ++++ b/plugins/snap/gs-plugin-snap.c +@@ -1002,8 +1002,14 @@ gs_plugin_refine_app (GsPlugin *plugin, + } + } + else { +- if (store_snap != NULL && snapd_snap_get_status (store_snap) == SNAPD_SNAP_STATUS_PRICED) ++ if (store_snap != NULL && snapd_snap_get_status (store_snap) == SNAPD_SNAP_STATUS_PRICED) { ++ if (g_getenv ("GNOME_SOFTWARE_SHOW_PAID") == NULL) { ++ g_set_error (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_FAILED, "Paid snaps not supported"); ++ return FALSE; ++ } ++ + gs_app_set_state (app, AS_APP_STATE_PURCHASABLE); ++ } + else + gs_app_set_state (app, AS_APP_STATE_AVAILABLE); + } diff -Nru gnome-software-3.30.6/debian/patches/0019-Delay-startup-of-GNOME-Software-to-allow-the-Shell-t.patch gnome-software-3.30.6/debian/patches/0019-Delay-startup-of-GNOME-Software-to-allow-the-Shell-t.patch --- gnome-software-3.30.6/debian/patches/0019-Delay-startup-of-GNOME-Software-to-allow-the-Shell-t.patch 1970-01-01 00:00:00.000000000 +0000 +++ gnome-software-3.30.6/debian/patches/0019-Delay-startup-of-GNOME-Software-to-allow-the-Shell-t.patch 2019-04-16 22:49:03.000000000 +0000 @@ -0,0 +1,21 @@ +From: Jean-Baptiste Lallement +Date: Mon, 25 Jun 2018 16:14:38 +1200 +Subject: [PATCH 19/24] Delay startup of GNOME Software to allow the Shell to + load first. + +--- + src/gnome-software-service.desktop.in | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/src/gnome-software-service.desktop.in b/src/gnome-software-service.desktop.in +index dc4d4d9..fb47759 100644 +--- a/src/gnome-software-service.desktop.in ++++ b/src/gnome-software-service.desktop.in +@@ -1,6 +1,7 @@ + [Desktop Entry] + Type=Application + Name=GNOME Software ++X-GNOME-Autostart-Delay=60 + Exec=@bindir@/gnome-software --gapplication-service + OnlyShowIn=GNOME;Unity; + NoDisplay=true diff -Nru gnome-software-3.30.6/debian/patches/0020-details-page-Don-t-show-missing-screenshot-placehold.patch gnome-software-3.30.6/debian/patches/0020-details-page-Don-t-show-missing-screenshot-placehold.patch --- gnome-software-3.30.6/debian/patches/0020-details-page-Don-t-show-missing-screenshot-placehold.patch 1970-01-01 00:00:00.000000000 +0000 +++ gnome-software-3.30.6/debian/patches/0020-details-page-Don-t-show-missing-screenshot-placehold.patch 2019-04-16 22:49:03.000000000 +0000 @@ -0,0 +1,31 @@ +From: Robert Ancell +Date: Wed, 27 Jun 2018 16:33:28 +1200 +Subject: [PATCH 20/24] details page: Don't show missing screenshot + placeholder + +--- + src/gs-details-page.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/gs-details-page.c b/src/gs-details-page.c +index 071f91e..e59cf23 100644 +--- a/src/gs-details-page.c ++++ b/src/gs-details-page.c +@@ -690,7 +690,7 @@ gs_details_page_refresh_screenshots (GsDetailsPage *self) + gtk_widget_set_visible (self->box_details_screenshot, + screenshots->len > 0); + gtk_widget_set_visible (self->box_details_screenshot_fallback, +- screenshots->len == 0); ++ FALSE /*screenshots->len == 0*/); + return; + } + +@@ -710,7 +710,7 @@ gs_details_page_refresh_screenshots (GsDetailsPage *self) + break; + default: + gtk_widget_set_visible (self->box_details_screenshot_fallback, +- screenshots->len == 0); ++ FALSE /*screenshots->len == 0*/); + break; + } + diff -Nru gnome-software-3.30.6/debian/patches/0021-details-Use-custom-icon-for-verified-developers.patch gnome-software-3.30.6/debian/patches/0021-details-Use-custom-icon-for-verified-developers.patch --- gnome-software-3.30.6/debian/patches/0021-details-Use-custom-icon-for-verified-developers.patch 1970-01-01 00:00:00.000000000 +0000 +++ gnome-software-3.30.6/debian/patches/0021-details-Use-custom-icon-for-verified-developers.patch 2019-04-16 22:49:03.000000000 +0000 @@ -0,0 +1,54 @@ +From: Robert Ancell +Date: Tue, 28 Aug 2018 12:43:15 +1200 +Subject: [PATCH 21/24] details: Use custom icon for verified developers + +--- + src/developer-verified.svg | 12 ++++++++++++ + src/gnome-software.gresource.xml | 1 + + src/gs-details-page.ui | 2 +- + 3 files changed, 14 insertions(+), 1 deletion(-) + create mode 100644 src/developer-verified.svg + +diff --git a/src/developer-verified.svg b/src/developer-verified.svg +new file mode 100644 +index 0000000..57c0f11 +--- /dev/null ++++ b/src/developer-verified.svg +@@ -0,0 +1,12 @@ ++ ++ ++ path6490 ++ Created with Sketch. ++ ++ ++ ++ ++ ++ ++ ++ +\ No newline at end of file +diff --git a/src/gnome-software.gresource.xml b/src/gnome-software.gresource.xml +index b6c9916..7d3b9bc 100644 +--- a/src/gnome-software.gresource.xml ++++ b/src/gnome-software.gresource.xml +@@ -39,5 +39,6 @@ + org.freedesktop.PackageKit.xml + gtk-style.css + gtk-style-hc.css ++ developer-verified.svg + + +diff --git a/src/gs-details-page.ui b/src/gs-details-page.ui +index a3f2ccb..168f7a6 100644 +--- a/src/gs-details-page.ui ++++ b/src/gs-details-page.ui +@@ -1164,7 +1164,7 @@ + True + False + 16 +- emblem-ok-symbolic ++ /org/gnome/Software/developer-verified.svg + + + diff -Nru gnome-software-3.30.6/debian/patches/0022-snap-Use-wide-scope-when-searching.patch gnome-software-3.30.6/debian/patches/0022-snap-Use-wide-scope-when-searching.patch --- gnome-software-3.30.6/debian/patches/0022-snap-Use-wide-scope-when-searching.patch 1970-01-01 00:00:00.000000000 +0000 +++ gnome-software-3.30.6/debian/patches/0022-snap-Use-wide-scope-when-searching.patch 2019-04-16 22:49:03.000000000 +0000 @@ -0,0 +1,66 @@ +From: Robert Ancell +Date: Tue, 7 Aug 2018 16:49:35 +1200 +Subject: [PATCH 22/24] snap: Use wide scope when searching + +--- + plugins/snap/gs-plugin-snap.c | 12 ++++++------ + 1 file changed, 6 insertions(+), 6 deletions(-) + +diff --git a/plugins/snap/gs-plugin-snap.c b/plugins/snap/gs-plugin-snap.c +index 8339ac9..23a4fdf 100644 +--- a/plugins/snap/gs-plugin-snap.c ++++ b/plugins/snap/gs-plugin-snap.c +@@ -381,7 +381,7 @@ gs_plugin_url_to_app (GsPlugin *plugin, + + /* create app */ + path = gs_utils_get_url_path (url); +- snaps = find_snaps (plugin, SNAPD_FIND_FLAGS_MATCH_NAME, NULL, path, cancellable, NULL); ++ snaps = find_snaps (plugin, SNAPD_FIND_FLAGS_SCOPE_WIDE | SNAPD_FIND_FLAGS_MATCH_NAME, NULL, path, cancellable, NULL); + if (snaps == NULL || snaps->len < 1) + return TRUE; + +@@ -447,7 +447,7 @@ gs_plugin_add_featured (GsPlugin *plugin, + g_autoptr(GString) background_css = NULL; + g_autofree gchar *css = NULL; + +- snaps = find_snaps (plugin, SNAPD_FIND_FLAGS_NONE, "featured", NULL, cancellable, error); ++ snaps = find_snaps (plugin, SNAPD_FIND_FLAGS_SCOPE_WIDE, "featured", NULL, cancellable, error); + + if (snaps == NULL) + return FALSE; +@@ -516,7 +516,7 @@ gs_plugin_add_popular (GsPlugin *plugin, + g_autoptr(GPtrArray) snaps = NULL; + guint i; + +- snaps = find_snaps (plugin, SNAPD_FIND_FLAGS_NONE, "featured", NULL, cancellable, error); ++ snaps = find_snaps (plugin, SNAPD_FIND_FLAGS_SCOPE_WIDE, "featured", NULL, cancellable, error); + if (snaps == NULL) + return FALSE; + +@@ -574,7 +574,7 @@ gs_plugin_add_category_apps (GsPlugin *plugin, + g_autoptr(GPtrArray) snaps = NULL; + guint j; + +- snaps = find_snaps (plugin, SNAPD_FIND_FLAGS_NONE, tokens[i], NULL, cancellable, error); ++ snaps = find_snaps (plugin, SNAPD_FIND_FLAGS_SCOPE_WIDE, tokens[i], NULL, cancellable, error); + if (snaps == NULL) + return FALSE; + for (j = 0; j < snaps->len; j++) { +@@ -629,7 +629,7 @@ gs_plugin_add_search (GsPlugin *plugin, + guint i; + + query = g_strjoinv (" ", values); +- snaps = find_snaps (plugin, SNAPD_FIND_FLAGS_NONE, NULL, query, cancellable, error); ++ snaps = find_snaps (plugin, SNAPD_FIND_FLAGS_SCOPE_WIDE, NULL, query, cancellable, error); + if (snaps == NULL) + return FALSE; + +@@ -653,7 +653,7 @@ get_store_snap (GsPlugin *plugin, const gchar *name, gboolean need_details, GCan + if (snap != NULL) + return g_object_ref (snap); + +- snaps = find_snaps (plugin, SNAPD_FIND_FLAGS_MATCH_NAME, NULL, name, cancellable, error); ++ snaps = find_snaps (plugin, SNAPD_FIND_FLAGS_SCOPE_WIDE | SNAPD_FIND_FLAGS_MATCH_NAME, NULL, name, cancellable, error); + if (snaps == NULL || snaps->len < 1) + return NULL; + diff -Nru gnome-software-3.30.6/debian/patches/0023-snap-Don-t-treat-auth-cancellation-as-an-error.patch gnome-software-3.30.6/debian/patches/0023-snap-Don-t-treat-auth-cancellation-as-an-error.patch --- gnome-software-3.30.6/debian/patches/0023-snap-Don-t-treat-auth-cancellation-as-an-error.patch 1970-01-01 00:00:00.000000000 +0000 +++ gnome-software-3.30.6/debian/patches/0023-snap-Don-t-treat-auth-cancellation-as-an-error.patch 2019-04-16 22:49:03.000000000 +0000 @@ -0,0 +1,41 @@ +From: Andrea Azzarone +Date: Fri, 3 Aug 2018 15:43:01 +0300 +Subject: [PATCH 23/25] snap: Don't treat auth cancellation as an error + +SNAPD_ERROR_PERMISSION_DENIED is triggered when the user cancels the PolicyKit +authorization process. For this reasons treat this error in a special way to +avoid showing an error notification. + +Fixes: https://bugs.launchpad.net/bugs/1785240 +--- + meson.build | 2 +- + plugins/snap/gs-plugin-snap.c | 3 +++ + 2 files changed, 4 insertions(+), 1 deletion(-) + +diff --git a/meson.build b/meson.build +index f5a80ce..5471426 100644 +--- a/meson.build ++++ b/meson.build +@@ -171,7 +171,7 @@ if get_option('gudev') + endif + + if get_option('snap') +- snap = dependency('snapd-glib', version : '>= 1.42') ++ snap = dependency('snapd-glib', version : '>= 1.43') + endif + + gnome = import('gnome') +diff --git a/plugins/snap/gs-plugin-snap.c b/plugins/snap/gs-plugin-snap.c +index 23a4fdf..47148e5 100644 +--- a/plugins/snap/gs-plugin-snap.c ++++ b/plugins/snap/gs-plugin-snap.c +@@ -155,6 +155,9 @@ snapd_error_convert (GError **perror) + case SNAPD_ERROR_PAYMENT_DECLINED: + error->code = GS_PLUGIN_ERROR_PURCHASE_DECLINED; + break; ++ case SNAPD_ERROR_AUTH_CANCELLED: ++ error->code = GS_PLUGIN_ERROR_CANCELLED; ++ break; + case SNAPD_ERROR_CONNECTION_FAILED: + case SNAPD_ERROR_WRITE_FAILED: + case SNAPD_ERROR_READ_FAILED: diff -Nru gnome-software-3.30.6/debian/patches/0024-shell-search-provider-implement-XUbuntuCancel.patch gnome-software-3.30.6/debian/patches/0024-shell-search-provider-implement-XUbuntuCancel.patch --- gnome-software-3.30.6/debian/patches/0024-shell-search-provider-implement-XUbuntuCancel.patch 1970-01-01 00:00:00.000000000 +0000 +++ gnome-software-3.30.6/debian/patches/0024-shell-search-provider-implement-XUbuntuCancel.patch 2019-04-16 22:49:03.000000000 +0000 @@ -0,0 +1,172 @@ +From: Andrea Azzarone +Date: Fri, 7 Sep 2018 19:58:00 +0200 +Subject: [PATCH 24/26] shell-search-provider: implement XUbuntuCancel + +Implement XUbuntuCancel to request search cancellation. This is used by +gnome-shell to cancel the current search e.g. if the search overview is +closed. +--- + src/gs-shell-search-provider.c | 74 +++++++++++++++++++++------ + src/shell-search-provider-dbus-interfaces.xml | 1 + + 2 files changed, 60 insertions(+), 15 deletions(-) + +diff --git a/src/gs-shell-search-provider.c b/src/gs-shell-search-provider.c +index c01d72c..8c1e0fd 100644 +--- a/src/gs-shell-search-provider.c ++++ b/src/gs-shell-search-provider.c +@@ -45,6 +45,8 @@ struct _GsShellSearchProvider { + GsPluginLoader *plugin_loader; + GCancellable *cancellable; + ++ PendingSearch *current_search; ++ + GHashTable *metas_cache; + GsAppList *search_results; + }; +@@ -58,6 +60,17 @@ pending_search_free (PendingSearch *search) + g_slice_free (PendingSearch, search); + } + ++static void ++cancel_current_search (GsShellSearchProvider *self) ++{ ++ g_debug ("*** Cancel current search"); ++ ++ if (self->cancellable != NULL) { ++ g_cancellable_cancel (self->cancellable); ++ g_clear_object (&self->cancellable); ++ } ++} ++ + static gint + search_sort_by_kudo_cb (GsApp *app1, GsApp *app2, gpointer user_data) + { +@@ -71,6 +84,23 @@ search_sort_by_kudo_cb (GsApp *app1, GsApp *app2, gpointer user_data) + return 0; + } + ++static void ++pending_search_finish (PendingSearch *search, ++ GDBusMethodInvocation *invocation, ++ GVariant *result) ++{ ++ GsShellSearchProvider *self = search->provider; ++ ++ g_dbus_method_invocation_return_value (invocation, result); ++ ++ if (search == self->current_search) { ++ self->current_search = NULL; ++ } ++ ++ pending_search_free (search); ++ g_application_release (g_application_get_default ()); ++} ++ + static void + search_done_cb (GObject *source, + GAsyncResult *res, +@@ -87,10 +117,9 @@ search_done_cb (GObject *source, + + list = gs_plugin_loader_job_process_finish (self->plugin_loader, res, NULL); + if (list == NULL) { +- g_dbus_method_invocation_return_value (search->invocation, g_variant_new ("(as)", NULL)); +- pending_search_free (search); +- g_application_release (g_application_get_default ()); +- return; ++ pending_search_finish (search, search->invocation, ++ g_variant_new ("(as)", NULL)); ++ return; + } + + /* sort by kudos, as there is no ratings data by default */ +@@ -106,10 +135,8 @@ search_done_cb (GObject *source, + /* cache this in case we need the app in GetResultMetas */ + gs_app_list_add (self->search_results, app); + } +- g_dbus_method_invocation_return_value (search->invocation, g_variant_new ("(as)", &builder)); + +- pending_search_free (search); +- g_application_release (g_application_get_default ()); ++ pending_search_finish (search, search->invocation, g_variant_new ("(as)", &builder)); + } + + static gchar * +@@ -167,10 +194,7 @@ execute_search (GsShellSearchProvider *self, + + value = g_strjoinv (" ", terms); + +- if (self->cancellable != NULL) { +- g_cancellable_cancel (self->cancellable); +- g_clear_object (&self->cancellable); +- } ++ cancel_current_search (self); + + /* don't attempt searches for a single character */ + if (g_strv_length (terms) == 1 && +@@ -183,6 +207,7 @@ execute_search (GsShellSearchProvider *self, + pending_search->provider = self; + pending_search->invocation = g_object_ref (invocation); + ++ self->current_search = pending_search; + g_application_hold (g_application_get_default ()); + self->cancellable = g_cancellable_new (); + +@@ -334,6 +359,26 @@ handle_launch_search (GsShellSearchProvider2 *skeleton, + return TRUE; + } + ++static gboolean ++handle_xubuntu_cancel (GsShellSearchProvider2 *skeleton, ++ GDBusMethodInvocation *invocation, ++ gpointer user_data) ++{ ++ GsShellSearchProvider *self = GS_SHELL_SEARCH_PROVIDER (user_data); ++ ++ g_debug ("*** XUbuntuCancel called"); ++ ++ if (self->current_search != NULL && ++ g_strcmp0 (g_dbus_method_invocation_get_sender (self->current_search->invocation), ++ g_dbus_method_invocation_get_sender (invocation)) == 0) { ++ cancel_current_search (self); ++ } ++ ++ gs_shell_search_provider2_complete_xubuntu_cancel (skeleton, invocation); ++ ++ return TRUE; ++} ++ + gboolean + gs_shell_search_provider_register (GsShellSearchProvider *self, + GDBusConnection *connection, +@@ -355,10 +400,7 @@ search_provider_dispose (GObject *obj) + { + GsShellSearchProvider *self = GS_SHELL_SEARCH_PROVIDER (obj); + +- if (self->cancellable != NULL) { +- g_cancellable_cancel (self->cancellable); +- g_clear_object (&self->cancellable); +- } ++ cancel_current_search (self); + + if (self->metas_cache != NULL) { + g_hash_table_destroy (self->metas_cache); +@@ -393,6 +435,8 @@ gs_shell_search_provider_init (GsShellSearchProvider *self) + G_CALLBACK (handle_activate_result), self); + g_signal_connect (self->skeleton, "handle-launch-search", + G_CALLBACK (handle_launch_search), self); ++ g_signal_connect (self->skeleton, "handle-xubuntu-cancel", ++ G_CALLBACK (handle_xubuntu_cancel), self); + } + + static void +diff --git a/src/shell-search-provider-dbus-interfaces.xml b/src/shell-search-provider-dbus-interfaces.xml +index f6840e2..4529c1e 100644 +--- a/src/shell-search-provider-dbus-interfaces.xml ++++ b/src/shell-search-provider-dbus-interfaces.xml +@@ -40,5 +40,6 @@ + + + ++ + + diff -Nru gnome-software-3.30.6/debian/patches/0025-snap-Use-new-media-API.patch gnome-software-3.30.6/debian/patches/0025-snap-Use-new-media-API.patch --- gnome-software-3.30.6/debian/patches/0025-snap-Use-new-media-API.patch 1970-01-01 00:00:00.000000000 +0000 +++ gnome-software-3.30.6/debian/patches/0025-snap-Use-new-media-API.patch 2019-04-16 22:49:03.000000000 +0000 @@ -0,0 +1,221 @@ +From ade099bb49ed1e149153986888873c610517598a Mon Sep 17 00:00:00 2001 +From: Robert Ancell +Date: Wed, 24 Oct 2018 16:12:40 +1300 +Subject: [PATCH 25/27] snap: Use new media API + +--- + meson.build | 2 +- + plugins/snap/gs-plugin-snap.c | 156 ++++++++++++++++++++++++---------- + 2 files changed, 113 insertions(+), 45 deletions(-) + +diff --git a/meson.build b/meson.build +index a4d35dc1..545a9870 100644 +--- a/meson.build ++++ b/meson.build +@@ -171,7 +171,7 @@ if get_option('gudev') + endif + + if get_option('snap') +- snap = dependency('snapd-glib', version : '>= 1.43') ++ snap = dependency('snapd-glib', version : '>= 1.45') + endif + + gnome = import('gnome') +diff --git a/plugins/snap/gs-plugin-snap.c b/plugins/snap/gs-plugin-snap.c +index 47148e5c..159ee95a 100644 +--- a/plugins/snap/gs-plugin-snap.c ++++ b/plugins/snap/gs-plugin-snap.c +@@ -429,6 +429,48 @@ is_banner_icon_image (const gchar *filename) + return g_regex_match_simple ("^banner-icon(?:_[a-zA-Z0-9]{7})?\\.(?:png|jpg)$", filename, 0, 0); + } + ++static const gchar * ++get_media_url (SnapdSnap *snap, gboolean (*match_func)(const gchar *filename)) ++{ ++ GPtrArray *media, *screenshots; ++ guint i; ++ ++ media = snapd_snap_get_media (snap); ++ for (i = 0; i < media->len; i++) { ++ SnapdMedia *m = media->pdata[i]; ++ ++ /* FIXME: In the future there will be a media type for these */ ++ ++ /* Fall back to old specially named screenshots */ ++ if (g_strcmp0 (snapd_media_get_media_type (m), "screenshot") == 0) { ++ const gchar *url; ++ g_autofree gchar *filename = NULL; ++ ++ url = snapd_media_get_url (m); ++ filename = g_path_get_basename (url); ++ if (match_func (filename)) ++ return url; ++ } ++ } ++ ++ /* Fall back to old screenshots */ ++G_GNUC_BEGIN_IGNORE_DEPRECATIONS ++ screenshots = snapd_snap_get_screenshots (snap); ++G_GNUC_END_IGNORE_DEPRECATIONS ++ for (i = 0; i < screenshots->len; i++) { ++ SnapdScreenshot *screenshot = screenshots->pdata[i]; ++ const gchar *url; ++ g_autofree gchar *filename = NULL; ++ ++ url = snapd_screenshot_get_url (screenshot); ++ filename = g_path_get_basename (url); ++ if (match_func (filename)) ++ return url; ++ } ++ ++ return NULL; ++} ++ + static gboolean + remove_cb (GsApp *app, gpointer user_data) + { +@@ -444,8 +486,6 @@ gs_plugin_add_featured (GsPlugin *plugin, + g_autoptr(GPtrArray) snaps = NULL; + SnapdSnap *snap; + g_autoptr(GsApp) app = NULL; +- GPtrArray *screenshots; +- guint i; + const gchar *banner_url = NULL, *icon_url = NULL; + g_autoptr(GString) background_css = NULL; + g_autofree gchar *css = NULL; +@@ -463,19 +503,8 @@ gs_plugin_add_featured (GsPlugin *plugin, + app = snap_to_app (plugin, snap); + + /* if has a screenshot called 'banner.png' or 'banner-icon.png' then use them for the banner */ +- screenshots = snapd_snap_get_screenshots (snap); +- for (i = 0; i < screenshots->len; i++) { +- SnapdScreenshot *screenshot = screenshots->pdata[i]; +- const gchar *url; +- g_autofree gchar *filename = NULL; +- +- url = snapd_screenshot_get_url (screenshot); +- filename = g_path_get_basename (url); +- if (is_banner_image (filename)) +- banner_url = url; +- else if (is_banner_icon_image (filename)) +- icon_url = url; +- } ++ banner_url = get_media_url (snap, is_banner_image); ++ icon_url = get_media_url (snap, is_banner_icon_image); + + background_css = g_string_new (""); + if (icon_url != NULL) +@@ -932,6 +961,72 @@ set_active_channel (GsApp *app, SnapdChannel *channel) + return FALSE; + } + ++static void ++refine_screenshots (GsApp *app, SnapdSnap *snap) ++{ ++ GPtrArray *media, *screenshots; ++ guint i; ++ ++ media = snapd_snap_get_media (snap); ++ for (i = 0; i < media->len; i++) { ++ SnapdMedia *m = media->pdata[i]; ++ const gchar *url; ++ g_autofree gchar *filename = NULL; ++ g_autoptr(AsScreenshot) ss = NULL; ++ g_autoptr(AsImage) image = NULL; ++ ++ if (g_strcmp0 (snapd_media_get_media_type (m), "screenshot") != 0) ++ continue; ++ ++ /* skip screenshots used for banner when app is featured */ ++ url = snapd_media_get_url (m); ++ filename = g_path_get_basename (url); ++ if (is_banner_image (filename) || is_banner_icon_image (filename)) ++ continue; ++ ++ ss = as_screenshot_new (); ++ as_screenshot_set_kind (ss, AS_SCREENSHOT_KIND_NORMAL); ++ image = as_image_new (); ++ as_image_set_url (image, snapd_media_get_url (m)); ++ as_image_set_kind (image, AS_IMAGE_KIND_SOURCE); ++ as_image_set_width (image, snapd_media_get_width (m)); ++ as_image_set_height (image, snapd_media_get_height (m)); ++ as_screenshot_add_image (ss, image); ++ gs_app_add_screenshot (app, ss); ++ } ++ ++ if (gs_app_get_screenshots (app)->len > 0) ++ return; ++ ++ /* fallback to old screenshots data */ ++G_GNUC_BEGIN_IGNORE_DEPRECATIONS ++ screenshots = snapd_snap_get_screenshots (snap); ++G_GNUC_END_IGNORE_DEPRECATIONS ++ for (i = 0; i < screenshots->len; i++) { ++ SnapdScreenshot *screenshot = screenshots->pdata[i]; ++ const gchar *url; ++ g_autofree gchar *filename = NULL; ++ g_autoptr(AsScreenshot) ss = NULL; ++ g_autoptr(AsImage) image = NULL; ++ ++ /* skip screenshots used for banner when app is featured */ ++ url = snapd_screenshot_get_url (screenshot); ++ filename = g_path_get_basename (url); ++ if (is_banner_image (filename) || is_banner_icon_image (filename)) ++ continue; ++ ++ ss = as_screenshot_new (); ++ as_screenshot_set_kind (ss, AS_SCREENSHOT_KIND_NORMAL); ++ image = as_image_new (); ++ as_image_set_url (image, snapd_screenshot_get_url (screenshot)); ++ as_image_set_kind (image, AS_IMAGE_KIND_SOURCE); ++ as_image_set_width (image, snapd_screenshot_get_width (screenshot)); ++ as_image_set_height (image, snapd_screenshot_get_height (screenshot)); ++ as_screenshot_add_image (ss, image); ++ gs_app_add_screenshot (app, ss); ++ } ++} ++ + gboolean + gs_plugin_refine_app (GsPlugin *plugin, + GsApp *app, +@@ -1070,35 +1165,8 @@ gs_plugin_refine_app (GsPlugin *plugin, + + gs_app_set_size_download (app, snapd_snap_get_download_size (store_snap)); + +- if (flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_SCREENSHOTS && gs_app_get_screenshots (app)->len == 0) { +- GPtrArray *screenshots; +- guint i; +- +- screenshots = snapd_snap_get_screenshots (store_snap); +- for (i = 0; i < screenshots->len; i++) { +- SnapdScreenshot *screenshot = screenshots->pdata[i]; +- const gchar *url; +- g_autofree gchar *filename = NULL; +- g_autoptr(AsScreenshot) ss = NULL; +- g_autoptr(AsImage) image = NULL; +- +- /* skip screenshots used for banner when app is featured */ +- url = snapd_screenshot_get_url (screenshot); +- filename = g_path_get_basename (url); +- if (is_banner_image (filename) || is_banner_icon_image (filename)) +- continue; +- +- ss = as_screenshot_new (); +- as_screenshot_set_kind (ss, AS_SCREENSHOT_KIND_NORMAL); +- image = as_image_new (); +- as_image_set_url (image, snapd_screenshot_get_url (screenshot)); +- as_image_set_kind (image, AS_IMAGE_KIND_SOURCE); +- as_image_set_width (image, snapd_screenshot_get_width (screenshot)); +- as_image_set_height (image, snapd_screenshot_get_height (screenshot)); +- as_screenshot_add_image (ss, image); +- gs_app_add_screenshot (app, ss); +- } +- } ++ if (flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_SCREENSHOTS && gs_app_get_screenshots (app)->len == 0) ++ refine_screenshots (app, store_snap); + } + + /* load icon if requested */ +-- +2.19.1 + diff -Nru gnome-software-3.30.6/debian/patches/0026-odrs-Only-show-reviews-from-the-same-distribution.patch gnome-software-3.30.6/debian/patches/0026-odrs-Only-show-reviews-from-the-same-distribution.patch --- gnome-software-3.30.6/debian/patches/0026-odrs-Only-show-reviews-from-the-same-distribution.patch 1970-01-01 00:00:00.000000000 +0000 +++ gnome-software-3.30.6/debian/patches/0026-odrs-Only-show-reviews-from-the-same-distribution.patch 2019-04-16 22:49:03.000000000 +0000 @@ -0,0 +1,36 @@ +From 8f7898fa6da8e951fff9168be82e511f3b2b52c0 Mon Sep 17 00:00:00 2001 +From: Robert Ancell +Date: Wed, 17 Apr 2019 10:26:04 +1200 +Subject: [PATCH 26/28] odrs: Only show reviews from the same distribution + +--- + plugins/odrs/gs-plugin-odrs.c | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/plugins/odrs/gs-plugin-odrs.c b/plugins/odrs/gs-plugin-odrs.c +index c8fead61..2b3526ba 100644 +--- a/plugins/odrs/gs-plugin-odrs.c ++++ b/plugins/odrs/gs-plugin-odrs.c +@@ -312,6 +312,7 @@ gs_plugin_odrs_parse_reviews (GsPlugin *plugin, + gssize data_len, + GError **error) + { ++ GsPluginData *priv = gs_plugin_get_data (plugin); + JsonArray *json_reviews; + JsonNode *json_root; + guint i; +@@ -375,6 +376,11 @@ gs_plugin_odrs_parse_reviews (GsPlugin *plugin, + return NULL; + } + ++ if (g_strcmp0 (json_object_get_string_member (json_item, "distro"), priv->distro) != 0) { ++ g_debug ("Ignoring review from distro %s", json_object_get_string_member (json_item, "distro")); ++ continue; ++ } ++ + /* create review */ + review = gs_plugin_odrs_parse_review_object (plugin, + json_item); +-- +2.20.1 + diff -Nru gnome-software-3.30.6/debian/patches/0027-shell-Don-t-progate-CTRL-F-key-pressed-event.patch gnome-software-3.30.6/debian/patches/0027-shell-Don-t-progate-CTRL-F-key-pressed-event.patch --- gnome-software-3.30.6/debian/patches/0027-shell-Don-t-progate-CTRL-F-key-pressed-event.patch 1970-01-01 00:00:00.000000000 +0000 +++ gnome-software-3.30.6/debian/patches/0027-shell-Don-t-progate-CTRL-F-key-pressed-event.patch 2019-05-13 23:04:29.000000000 +0000 @@ -0,0 +1,29 @@ +From f395463521e19706112b24eb1adceeb123764822 Mon Sep 17 00:00:00 2001 +From: Andrea Azzarone +Date: Thu, 18 Apr 2019 16:35:11 +0100 +Subject: [PATCH 27/29] shell: Don't progate CTRL+F key pressed event + +If the search bar is toggled using CTRL+F key don't propagate the key press +event. + +Closes: https://gitlab.gnome.org/GNOME/gnome-software/issues/641 +--- + src/gs-shell.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/gs-shell.c b/src/gs-shell.c +index a03ceaf7..a411f6eb 100644 +--- a/src/gs-shell.c ++++ b/src/gs-shell.c +@@ -601,7 +601,7 @@ window_keypress_handler (GtkWidget *window, GdkEvent *event, GsShell *shell) + } else { + gtk_search_bar_set_search_mode (GTK_SEARCH_BAR (w), FALSE); + } +- return GDK_EVENT_PROPAGATE; ++ return GDK_EVENT_STOP; + } + } + +-- +2.20.1 + diff -Nru gnome-software-3.30.6/debian/patches/series gnome-software-3.30.6/debian/patches/series --- gnome-software-3.30.6/debian/patches/series 2019-01-05 22:26:30.000000000 +0000 +++ gnome-software-3.30.6/debian/patches/series 2019-05-13 23:04:29.000000000 +0000 @@ -0,0 +1,32 @@ +# Patches from upstream +0001-details-page-Add-support-for-verified-developers.patch +0002-snap-Set-verified-developer-flag.patch + +# Patches from the ubuntu* branch +0001-Construct-the-Software-Sources-menu-item-dynamically.patch +0002-Download-changelog-information-on-demand-this-stops-.patch +0003-Sort-snaps-before-other-apps.patch +0004-Hide-Kudo-details-since-we-don-t-have-good-data.patch +0005-details-Show-an-in-app-notification-when-passed-an-i.patch +0006-packagekit-Disable-updates.patch +0007-snap-Only-feature-snaps.patch +0008-Don-t-randomize-editors-picks.patch +0009-Display-a-warning-for-non-sandboxed-snaps.patch +0010-Sort-category-snaps-before-other-packages.patch +0011-Support-snap-channels.patch +0012-Don-t-use-colour-to-differentiate-between-free-and-p.patch +0013-overview-page-Rotate-featured-apps.patch +0014-Add-a-basic-permissions-system.patch +0015-build-Translate-Ubuntu-s-.desktop-file.patch +0016-snap-Use-default-icon-if-none-provided.patch +0017-snap-Make-snaps-purchasable.patch +0018-Disable-paid-snap-support-unless-env-variable-GNOME_.patch +0019-Delay-startup-of-GNOME-Software-to-allow-the-Shell-t.patch +0020-details-page-Don-t-show-missing-screenshot-placehold.patch +0021-details-Use-custom-icon-for-verified-developers.patch +0022-snap-Use-wide-scope-when-searching.patch +0023-snap-Don-t-treat-auth-cancellation-as-an-error.patch +0024-shell-search-provider-implement-XUbuntuCancel.patch +0025-snap-Use-new-media-API.patch +0026-odrs-Only-show-reviews-from-the-same-distribution.patch +0027-shell-Don-t-progate-CTRL-F-key-pressed-event.patch diff -Nru gnome-software-3.30.6/debian/ubuntu-software.install gnome-software-3.30.6/debian/ubuntu-software.install --- gnome-software-3.30.6/debian/ubuntu-software.install 1970-01-01 00:00:00.000000000 +0000 +++ gnome-software-3.30.6/debian/ubuntu-software.install 2019-04-16 22:49:03.000000000 +0000 @@ -0,0 +1,2 @@ +usr/share/ubuntu/applications/ +debian/icons/hicolor usr/share/icons/ diff -Nru gnome-software-3.30.6/debian/ubuntu-software.links gnome-software-3.30.6/debian/ubuntu-software.links --- gnome-software-3.30.6/debian/ubuntu-software.links 1970-01-01 00:00:00.000000000 +0000 +++ gnome-software-3.30.6/debian/ubuntu-software.links 2019-04-16 22:49:03.000000000 +0000 @@ -0,0 +1,2 @@ +usr/bin/gnome-software usr/bin/ubuntu-software +usr/share/ubuntu/applications/org.gnome.Software.desktop usr/share/ubuntu-wayland/applications/org.gnome.Software.desktop