[gnome-initial-setup/wip/pwithnall/misc-fixes: 25/70] live-chooser: add the Live Chooser page




commit fad4d499fd86defa3576de86cc153eac71021b9e
Author: Georges Basile Stavracas Neto <georges stavracas gmail com>
Date:   Wed Jul 24 16:40:27 2019 -0700

    live-chooser: add the Live Chooser page
    
    When booting from live media, present the user with a choice between
    entering a temporary session, or launching the reformatter.
    
    Adds a script named eos-transient-setup to do some of the tasks that
    will be used for live mode as well as for store demo mode later in the
    downstream series of patches.
    
    (Rebase 3.38: Fix declaration of save_data() function, and use new
    gis_page_set_has_forward() API. Drop `--mode demo` support from
    `eos-transient-setup`.)
    
    Reference to related tickets:
        https://phabricator.endlessm.com/T12547
        https://phabricator.endlessm.com/T12548
        https://phabricator.endlessm.com/T13578
        https://phabricator.endlessm.com/T13654
        https://phabricator.endlessm.com/T13950
        https://phabricator.endlessm.com/T14616
        https://phabricator.endlessm.com/T15370
        https://phabricator.endlessm.com/T15438
        https://phabricator.endlessm.com/T15834
        https://phabricator.endlessm.com/T15938
        https://phabricator.endlessm.com/T16130
        https://phabricator.endlessm.com/T17082
        https://phabricator.endlessm.com/T17622
        https://phabricator.endlessm.com/T18063
        https://phabricator.endlessm.com/T18708
        https://phabricator.endlessm.com/T20103
        https://phabricator.endlessm.com/T20990
        https://phabricator.endlessm.com/T23552

 data/eos-transient-setup.service.in                |  13 +
 data/meson.build                                   |  12 +
 gnome-initial-setup/eos-transient-setup            | 286 +++++++++++++++++++++
 gnome-initial-setup/gnome-initial-setup.c          |   2 +
 gnome-initial-setup/meson.build                    |   5 +
 .../pages/live-chooser/gis-live-chooser-page.c     | 265 +++++++++++++++++++
 .../pages/live-chooser/gis-live-chooser-page.h     |  58 +++++
 .../pages/live-chooser/gis-live-chooser-page.ui    | 195 ++++++++++++++
 .../pages/live-chooser/install-endless.png         | Bin 0 -> 3293 bytes
 .../pages/live-chooser/live-chooser.css            |   4 +
 .../pages/live-chooser/live-chooser.gresource.xml  |   9 +
 gnome-initial-setup/pages/live-chooser/meson.build |   9 +
 .../pages/live-chooser/try-endless.png             | Bin 0 -> 3105 bytes
 gnome-initial-setup/pages/meson.build              |   1 +
 meson_options.txt                                  |   5 +
 po/POTFILES.in                                     |   2 +
 16 files changed, 866 insertions(+)
---
diff --git a/data/eos-transient-setup.service.in b/data/eos-transient-setup.service.in
new file mode 100644
index 00000000..b3e40867
--- /dev/null
+++ b/data/eos-transient-setup.service.in
@@ -0,0 +1,13 @@
+[Unit]
+Description=Adjust desktop settings for live boot
+After=eos-live-boot-overlayfs-setup.service
+Before=display-manager.service
+ConditionKernelCommandLine=endless.live_boot
+
+[Service]
+Type=oneshot
+ExecStart=@LIBEXECDIR@/eos-transient-setup --mode live
+RemainAfterExit=yes
+
+[Install]
+WantedBy=graphical.target
diff --git a/data/meson.build b/data/meson.build
index aa4f7983..8f73b8b3 100644
--- a/data/meson.build
+++ b/data/meson.build
@@ -90,7 +90,19 @@ configure_file(
     configuration: desktop_conf,
 )
 
+systemd_system_unit_dir = get_option('systemd-system-unit-dir')
 systemd_dep = dependency ('systemd')
+if systemd_system_unit_dir == ''
+    systemd_system_unit_dir = systemd_dep.get_pkgconfig_variable('systemdsystemunitdir')
+endif
+
+configure_file(
+    input: 'eos-transient-setup.service.in',
+    output: 'eos-transient-setup.service',
+    install: true,
+    install_dir: systemd_system_unit_dir,
+    configuration: desktop_conf
+)
 
 install_data(
     'product_serial.conf',
diff --git a/gnome-initial-setup/eos-transient-setup b/gnome-initial-setup/eos-transient-setup
new file mode 100755
index 00000000..002dc586
--- /dev/null
+++ b/gnome-initial-setup/eos-transient-setup
@@ -0,0 +1,286 @@
+#!/usr/bin/env python3
+# eos-transient-setup – configures the system for transient sessions
+# Copyright (C) 2016-2018 Endless Mobile, Ltd.
+#
+# 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.
+
+import argparse
+import enum
+import glob
+import json
+import logging
+import os
+import subprocess
+import sys
+import tempfile
+
+import gi
+gi.require_version('OSTree', '1.0')
+from gi.repository import GLib, Gio, OSTree  # noqa: E402
+
+log = logging.getLogger(sys.argv[0])
+
+ICON_GRID_DIR = '/var/lib/eos-image-defaults/icon-grid'
+
+DESKTOP_GRID_ID = 'desktop'
+
+EOS_INSTALLER = 'com.endlessm.Installer.desktop'
+EOS_INSTALLER_PATH = os.path.join('/usr/share/applications', EOS_INSTALLER)
+LOCAL_APPS_DIR = '/usr/local/share/applications'
+LOCAL_DESKTOP_PATH = os.path.join(LOCAL_APPS_DIR, EOS_INSTALLER)
+
+GOOGLE_CHROME = 'google-chrome.desktop'
+CHROMIUM_BROWSER = 'chromium-browser.desktop'
+
+LIVE_SETTINGS_DB = '/var/lib/eos-image-defaults/settings.live'
+USER_PROFILE_PATH = '/usr/local/share/dconf/profile/user'
+USER_PROFILE = '''user-db:user
+file-db:/var/lib/eos-image-defaults/settings.live
+file-db:/var/lib/eos-image-defaults/settings
+file-db:/usr/share/eos-default-settings/settings
+'''
+
+SHELL_SCHEMA = 'org.gnome.shell'
+FAVORITE_APPS_KEY = 'favorite-apps'
+
+GS_SCHEMA = 'org.gnome.software'
+ALLOW_UPDATES = 'allow-updates'
+
+GSD_POWER_SCHEMA = "org.gnome.settings-daemon.plugins.power"
+GSD_SLEEP_INACTIVE_AC_TYPE = "sleep-inactive-ac-type"
+GSD_SLEEP_INACTIVE_AC_TIMEOUT = "sleep-inactive-ac-timeout"
+GSD_SLEEP_INACTIVE_BATTERY_TYPE = "sleep-inactive-battery-type"
+GSD_SLEEP_INACTIVE_BATTERY_TIMEOUT = "sleep-inactive-battery-timeout"
+
+ONE_MINUTE = 60
+
+
+# The other mode here (demo mode) was removed with the rebase to 3.38. The
+# --mode argument has been kept around to maintain CLI API compatibility.
+class Mode(enum.Enum):
+    live = 1
+
+
+class AdjustGSettings(object):
+    def __init__(self):
+        self.keyfile = GLib.KeyFile()
+
+    def update(self, schema, key, variant):
+        """Stages 'variant' as the new value for 'key' in 'schema'."""
+        value = variant.print_(False)
+        log.info('Updating %s: %s to %s', schema, key, value)
+        self.keyfile.set_string(schema.replace('.', '/'), key, value)
+
+    def prepare(self, mode):
+        """Stage all settings to be overridden."""
+        self.update_favorite_apps(mode)
+        self.disallow_app_center_updates()
+
+    def write_dconf_compile(self):
+        """Write dconf database with overridden settings.
+
+        We also adjust the 'user' profile to use it.
+        """
+        with tempfile.TemporaryDirectory(suffix='.d') as d:
+            keyfile_path = os.path.join(d, '00-live')
+            log.info('writing keyfile to %s', keyfile_path)
+            self.keyfile.save_to_file(keyfile_path)
+
+            os.makedirs(os.path.dirname(LIVE_SETTINGS_DB), exist_ok=True)
+
+            args = ['dconf', 'compile', LIVE_SETTINGS_DB, d]
+            log.info('$ %s', ' '.join(args))
+            subprocess.check_call(args)
+
+        log.info('Installing new DConf profile to %s', USER_PROFILE_PATH)
+        os.makedirs(os.path.dirname(USER_PROFILE_PATH), exist_ok=True)
+        with open(USER_PROFILE_PATH, 'w') as f:
+            f.write(USER_PROFILE)
+
+    def write_stdout(self):
+        """Write keyfile with overridden settings to stdout, for debugging."""
+        data, length = self.keyfile.to_data()
+        print(data)
+
+    def adjust_power_settings(self):
+        """Configure session to log out after a short period of inactivity."""
+        for key in (GSD_SLEEP_INACTIVE_AC_TYPE,
+                    GSD_SLEEP_INACTIVE_BATTERY_TYPE):
+            self.update(GSD_POWER_SCHEMA, key,
+                        GLib.Variant('s', 'logout'))
+
+        for key in (GSD_SLEEP_INACTIVE_AC_TIMEOUT,
+                    GSD_SLEEP_INACTIVE_BATTERY_TIMEOUT):
+            self.update(GSD_POWER_SCHEMA, key,
+                        GLib.Variant('i', ONE_MINUTE))
+
+    def update_favorite_apps(self, mode):
+        """Adjust default favourite apps, which are shown on the taskbar."""
+        settings = Gio.Settings(schema=SHELL_SCHEMA)
+        favorite_apps = settings.get_strv(FAVORITE_APPS_KEY)
+        changed = False
+
+        if mode == Mode.live:
+            # Prepend installer icon
+            if EOS_INSTALLER not in favorite_apps:
+                favorite_apps.insert(0, EOS_INSTALLER)
+                changed = True
+
+        # Replace Chrome (downloaded on demand) with Chromium (pre-installed).
+        # This effectively undoes a step taken in the image builder,
+        # pre-seeding /var/eos-image-defaults/settings with the opposite
+        # change.
+        if replace_chrome_with_chromium(favorite_apps):
+            changed = True
+
+        if changed:
+            self.update(SHELL_SCHEMA, FAVORITE_APPS_KEY,
+                        GLib.Variant('as', favorite_apps))
+
+    def disallow_app_center_updates(self):
+        """Forbid installing app updates."""
+        self.update(GS_SCHEMA, ALLOW_UPDATES, GLib.Variant('b', False))
+
+
+def install_installer_desktop_file():
+    """Make eos-installer visible in user sessions.
+
+    eos-installer is shipped in all images, but its desktop file contains
+    NoDisplay=true. Make a copy with this setting removed so it can be added to
+    the desktop and taskbar and found via search.
+    """
+    log.info('Copying %s to %s with NoDisplay removed',
+             EOS_INSTALLER_PATH, LOCAL_DESKTOP_PATH)
+    os.makedirs(LOCAL_APPS_DIR, exist_ok=True)
+
+    kf = GLib.KeyFile()
+    kf.load_from_file(EOS_INSTALLER_PATH,
+                      GLib.KeyFileFlags.KEEP_COMMENTS |
+                      GLib.KeyFileFlags.KEEP_TRANSLATIONS)
+    kf.remove_key('Desktop Entry', 'NoDisplay')
+    kf.save_to_file(LOCAL_DESKTOP_PATH)
+
+
+def disable_chrome_auto_download():
+    """Prevent Chrome auto-downloader from running."""
+    cmd = os.path.join('/usr/share/eos-google-chrome-helper',
+                       'eos-google-chrome-system-helper.py')
+    log.info('$ %s', cmd)
+    subprocess.check_call([cmd])
+
+
+def replace_chrome_with_chromium(apps):
+    """Replace Chrome with Chromium in a list of .desktop file names."""
+    if GOOGLE_CHROME in apps:
+        apps[apps.index(GOOGLE_CHROME)] = CHROMIUM_BROWSER
+        return True
+    else:
+        return False
+
+
+def remove_chrome_from_icon_grids():
+    """Replace Chrome with Chromium in icon grids, if needed."""
+    pattern = os.path.join(ICON_GRID_DIR, 'icon-grid-*.json')
+    for path in glob.glob(pattern):
+        log.info("Checking %s", path)
+        try:
+            with open(path, 'r') as f:
+                grid = json.load(fp=f)
+
+            if replace_chrome_with_chromium(grid[DESKTOP_GRID_ID]):
+                log.info("Updating %s", path)
+
+                with open(path, 'w') as f:
+                    json.dump(grid, fp=f)
+        except Exception:
+            log.exception("while processing %s", path)
+
+
+def prepend_installer_to_icon_grid():
+    """Prepend installer to icon grid."""
+    pattern = os.path.join(ICON_GRID_DIR, 'icon-grid-prepend-*.json')
+    c_path = os.path.join(ICON_GRID_DIR, 'icon-grid-prepend-C.json')
+    paths = glob.glob(pattern)
+    if c_path not in paths:
+        paths.append(c_path)
+
+    for path in paths:
+        try:
+            try:
+                with open(path, 'r') as f:
+                    log.info("reading existing file %s", path)
+                    grid = json.load(fp=f)
+            except FileNotFoundError:
+                grid = {}
+
+            desktop = grid.setdefault(DESKTOP_GRID_ID, [])
+            if EOS_INSTALLER not in desktop:
+                desktop.insert(0, EOS_INSTALLER)
+
+                log.info("Writing %s: %s", path, json.dumps(obj=grid))
+                with open(path, 'w') as f:
+                    json.dump(obj=grid, fp=f)
+        except Exception:
+            log.exception("while processing %s", path)
+
+
+def reduce_ostree_min_free_space():
+    '''Don't require any free space on disk when installing apps. On live
+    systems, free space is at most half of physical RAM, and running out is not
+    a big deal.'''
+    repo = OSTree.Repo.new_default()
+    repo.open()
+    config = repo.copy_config()
+    # -size alone takes precedence but set both for clarity.
+    config.set_string('core', 'min-free-space-size', '0MB')
+    config.set_integer('core', 'min-free-space-percent', 0)
+    repo.write_config(config)
+
+
+def main():
+    """Configures system settings for live sessions."""
+    p = argparse.ArgumentParser(description=main.__doc__)
+    p.add_argument('--mode', choices=[m.name for m in Mode], required=True,
+                   default='live',
+                   help='Whether to adjust settings for live mode')
+    p.add_argument('--dry-run', action='store_true',
+                   help='Just print the DConf keyfile to stdout')
+    a = p.parse_args()
+    mode = Mode[a.mode]
+
+    logging.basicConfig(
+        level=logging.INFO,
+        format='%(name)s:%(lineno)-3s %(funcName)20s %(levelname)7s: '
+               '%(message)s')
+
+    setup = AdjustGSettings()
+    setup.prepare(mode=mode)
+
+    if a.dry_run:
+        setup.write_stdout()
+    else:
+        setup.write_dconf_compile()
+
+        disable_chrome_auto_download()
+        remove_chrome_from_icon_grids()
+        reduce_ostree_min_free_space()
+
+        if mode == Mode.live:
+            install_installer_desktop_file()
+            prepend_installer_to_icon_grid()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/gnome-initial-setup/gnome-initial-setup.c b/gnome-initial-setup/gnome-initial-setup.c
index 5414f5ee..3b22076c 100644
--- a/gnome-initial-setup/gnome-initial-setup.c
+++ b/gnome-initial-setup/gnome-initial-setup.c
@@ -41,6 +41,7 @@
 #include "pages/network/gis-network-page.h"
 #include "pages/timezone/gis-timezone-page.h"
 #include "pages/privacy/gis-privacy-page.h"
+#include "pages/live-chooser/gis-live-chooser-page.h"
 #include "pages/goa/gis-goa-page.h"
 #include "pages/account/gis-account-pages.h"
 #include "pages/parental-controls/gis-parental-controls-page.h"
@@ -70,6 +71,7 @@ typedef struct {
 static PageData page_table[] = {
   PAGE (welcome, FALSE),
   PAGE (language, FALSE),
+  PAGE (live_chooser, TRUE),
   PAGE (keyboard, FALSE),
   PAGE (display, TRUE),
   PAGE (endless_eula, TRUE),
diff --git a/gnome-initial-setup/meson.build b/gnome-initial-setup/meson.build
index 427d61ff..8d31a936 100644
--- a/gnome-initial-setup/meson.build
+++ b/gnome-initial-setup/meson.build
@@ -64,6 +64,11 @@ dependencies = [
     libmalcontent_ui_dep,
 ]
 
+install_data(
+    'eos-transient-setup',
+    install_dir: libexec_dir
+)
+
 executable(
     'gnome-initial-setup',
     sources,
diff --git a/gnome-initial-setup/pages/live-chooser/gis-live-chooser-page.c 
b/gnome-initial-setup/pages/live-chooser/gis-live-chooser-page.c
new file mode 100644
index 00000000..240d4b99
--- /dev/null
+++ b/gnome-initial-setup/pages/live-chooser/gis-live-chooser-page.c
@@ -0,0 +1,265 @@
+/* gis-live-chooser-page.c
+ *
+ * Copyright (C) 2016 Endless Mobile, Inc
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Written by:
+ *     Georges Basile Stavracas Neto <georges stavracas gmail com>
+ */
+
+#define _GNU_SOURCE 1  /* for NL_LOCALE_NAME */
+
+#include "gis-live-chooser-page.h"
+#include "live-chooser-resources.h"
+
+#include <act/act-user-manager.h>
+#include <gio/gio.h>
+#include <glib/gi18n.h>
+#include <polkit/polkit.h>
+
+#include <langinfo.h>
+#include <locale.h>
+
+#include "pages/language/cc-language-chooser.h"
+
+#define LIVE_ACCOUNT_AVATAR "/usr/share/pixmaps/faces/sunflower.jpg"
+#define LIVE_ACCOUNT_USERNAME "live"
+#define LIVE_ACCOUNT_FULLNAME "Endless OS"
+
+struct _GisLiveChooserPagePrivate
+{
+  GtkWidget *try_label;
+  GtkWidget *reformat_label;
+  GtkWidget *try_button;
+  GtkWidget *reformat_button;
+
+  ActUserManager   *act_client;
+};
+
+typedef struct _GisLiveChooserPagePrivate GisLiveChooserPagePrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (GisLiveChooserPage, gis_live_chooser_page, GIS_TYPE_PAGE);
+
+static gboolean
+gis_live_chooser_page_save_data (GisPage  *page,
+                                 GError  **error)
+{
+  GisLiveChooserPage *self = GIS_LIVE_CHOOSER_PAGE (page);
+  GisLiveChooserPagePrivate *priv = gis_live_chooser_page_get_instance_private (self);
+  g_autoptr(ActUser) user = NULL;
+  const gchar *language;
+
+  user = act_user_manager_create_user (priv->act_client,
+                                       LIVE_ACCOUNT_USERNAME,
+                                       LIVE_ACCOUNT_FULLNAME,
+                                       ACT_USER_ACCOUNT_TYPE_ADMINISTRATOR,
+                                       error);
+  if (user == NULL)
+    {
+      g_prefix_error (error, "Failed to create live user: ");
+      return FALSE;
+    }
+
+  act_user_set_password_mode (user, ACT_USER_PASSWORD_MODE_NONE);
+  act_user_set_automatic_login (user, FALSE);
+  act_user_set_icon_file (user, LIVE_ACCOUNT_AVATAR);
+
+  language = gis_driver_get_user_language (page->driver);
+
+  if (language)
+    act_user_set_language (user, language);
+
+  gis_driver_set_user_permissions (page->driver, user, NULL);
+
+  gis_update_login_keyring_password ("");
+
+  return TRUE;
+}
+
+static void
+load_css_overrides (GisLiveChooserPage *page)
+{
+  GtkCssProvider *provider;
+  GError *error;
+
+  error = NULL;
+  provider = gtk_css_provider_new ();
+  gtk_css_provider_load_from_resource (provider, "/org/gnome/initial-setup/live-chooser.css");
+
+  if (error != NULL)
+    {
+      g_warning ("Unable to load CSS overrides for the live-chooser page: %s", error->message);
+      g_clear_error (&error);
+    }
+  else
+    {
+      gtk_style_context_add_provider_for_screen (gtk_widget_get_screen (GTK_WIDGET (page)),
+                                                 GTK_STYLE_PROVIDER (provider),
+                                                 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+    }
+
+  g_object_unref (provider);
+}
+
+static void
+try_button_clicked (GisLiveChooserPage *page)
+{
+  GisAssistant *assistant = gis_driver_get_assistant (GIS_PAGE (page)->driver);
+
+  gis_assistant_next_page (assistant);
+}
+
+static void
+on_reformatter_exited (GisLiveChooserPage *page,
+                       const GError       *error)
+{
+  GisDriver *driver = GIS_PAGE (page)->driver;
+
+  gis_driver_show_window (driver);
+
+  if (error != NULL)
+    {
+      GtkWindow *toplevel = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (page)));
+      GtkWidget *message_dialog;
+
+      g_critical ("Error running the reformatter: %s", error->message);
+
+      message_dialog = gtk_message_dialog_new (toplevel,
+                                               GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
+                                               GTK_MESSAGE_ERROR,
+                                               GTK_BUTTONS_CLOSE,
+                                               _("Error running the reformatter: %s"), error->message);
+
+      gtk_dialog_run (GTK_DIALOG (message_dialog));
+      gtk_widget_destroy (message_dialog);
+    }
+}
+
+static void
+reformatter_exited_cb (GObject      *source,
+                       GAsyncResult *result,
+                       gpointer      user_data)
+{
+  GisLiveChooserPage *page = GIS_LIVE_CHOOSER_PAGE (user_data);
+  g_autoptr(GError) error = NULL;
+
+  g_subprocess_wait_check_finish (G_SUBPROCESS (source), result, &error);
+  on_reformatter_exited (page, error);
+}
+
+static void
+reformat_button_clicked (GisLiveChooserPage *page)
+{
+  g_autoptr(GSubprocessLauncher) launcher = NULL;
+  g_autoptr(GSubprocess) subprocess = NULL;
+  const gchar *locale = nl_langinfo (NL_LOCALE_NAME (LC_MESSAGES));
+  const gchar *command = "/usr/lib/eos-installer/gnome-image-installer";
+  g_autoptr(GError) error = NULL;
+
+  launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE);
+  g_subprocess_launcher_setenv (launcher, "LANG", locale, TRUE);
+  subprocess = g_subprocess_launcher_spawn (launcher, &error, command, NULL);
+  if (error)
+    {
+      on_reformatter_exited (page, error);
+    }
+  else
+    {
+      gis_driver_hide_window (GIS_PAGE (page)->driver);
+      g_subprocess_wait_check_async (subprocess, NULL,
+                                     reformatter_exited_cb, page);
+    }
+}
+
+static void
+gis_live_chooser_page_constructed (GObject *object)
+{
+  GisLiveChooserPage *page = GIS_LIVE_CHOOSER_PAGE (object);
+  GisLiveChooserPagePrivate *priv = gis_live_chooser_page_get_instance_private (page);
+
+  G_OBJECT_CLASS (gis_live_chooser_page_parent_class)->constructed (object);
+
+  gis_page_set_has_forward (GIS_PAGE (page), FALSE);
+
+  priv->act_client = act_user_manager_get_default ();
+
+  g_signal_connect_swapped (priv->try_button,
+                            "clicked",
+                            G_CALLBACK (try_button_clicked),
+                            page);
+
+  g_signal_connect_swapped (priv->reformat_button,
+                            "clicked",
+                            G_CALLBACK (reformat_button_clicked),
+                            page);
+
+
+  load_css_overrides (page);
+
+  gtk_widget_show (GTK_WIDGET (page));
+}
+
+static void
+gis_live_chooser_page_locale_changed (GisPage *page)
+{
+  GisLiveChooserPagePrivate *priv = gis_live_chooser_page_get_instance_private (GIS_LIVE_CHOOSER_PAGE 
(page));
+
+  gtk_label_set_label (GTK_LABEL (priv->try_label), _("Try Endless OS by running it from the USB Stick."));
+  gtk_label_set_label (GTK_LABEL (priv->reformat_label), _("Reformat this computer with Endless OS."));
+  gtk_button_set_label (GTK_BUTTON (priv->try_button), _("Try It"));
+  gtk_button_set_label (GTK_BUTTON (priv->reformat_button), _("Reformat"));
+
+  gis_page_set_title (page, _("Try or Reformat"));
+}
+
+static void
+gis_live_chooser_page_class_init (GisLiveChooserPageClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GisPageClass *page_class = GIS_PAGE_CLASS (klass);
+
+  page_class->page_id = "live-chooser";
+  page_class->locale_changed = gis_live_chooser_page_locale_changed;
+  page_class->save_data = gis_live_chooser_page_save_data;
+
+  gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (klass), 
"/org/gnome/initial-setup/gis-live-chooser-page.ui");
+
+  gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisLiveChooserPage, try_label);
+  gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisLiveChooserPage, 
reformat_label);
+  gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisLiveChooserPage, try_button);
+  gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisLiveChooserPage, 
reformat_button);
+
+  object_class->constructed = gis_live_chooser_page_constructed;
+}
+
+static void
+gis_live_chooser_page_init (GisLiveChooserPage *page)
+{
+  g_resources_register (live_chooser_get_resource ());
+
+  gtk_widget_init_template (GTK_WIDGET (page));
+}
+
+GisPage *
+gis_prepare_live_chooser_page (GisDriver *driver)
+{
+  /* Only show this page when running on a live boot session */
+  if (!gis_driver_is_live_session (driver))
+    return NULL;
+
+  return g_object_new (GIS_TYPE_LIVE_CHOOSER_PAGE,
+                       "driver", driver,
+                       NULL);
+}
diff --git a/gnome-initial-setup/pages/live-chooser/gis-live-chooser-page.h 
b/gnome-initial-setup/pages/live-chooser/gis-live-chooser-page.h
new file mode 100644
index 00000000..b386264f
--- /dev/null
+++ b/gnome-initial-setup/pages/live-chooser/gis-live-chooser-page.h
@@ -0,0 +1,58 @@
+/* gis-live-chooser-page.h
+ *
+ * Copyright (C) 2016 Endless Mobile, Inc
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Written by:
+ *     Georges Basile Stavracas Neto <georges stavracas gmail com>
+ */
+
+#ifndef GIS_LIVE_CHOOSER_PAGE_H
+#define GIS_LIVE_CHOOSER_PAGE_H
+
+#include "gnome-initial-setup.h"
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GIS_TYPE_LIVE_CHOOSER_PAGE (gis_live_chooser_page_get_type())
+#define GIS_LIVE_CHOOSER_PAGE(obj)                           (G_TYPE_CHECK_INSTANCE_CAST ((obj), 
GIS_TYPE_LIVE_CHOOSER_PAGE, GisLiveChooserPage))
+#define GIS_LIVE_CHOOSER_PAGE_CLASS(klass)                   (G_TYPE_CHECK_CLASS_CAST ((klass),  
GIS_TYPE_LIVE_CHOOSER_PAGE, GisLiveChooserPageClass))
+#define GIS_IS_LIVE_CHOOSER_PAGE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), 
GIS_TYPE_LIVE_CHOOSER_PAGE))
+#define GIS_IS_LIVE_CHOOSER_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  
GIS_TYPE_LIVE_CHOOSER_PAGE))
+#define GIS_LIVE_CHOOSER_PAGE_GET_CLASS(obj)                 (G_TYPE_INSTANCE_GET_CLASS ((obj),  
GIS_TYPE_LIVE_CHOOSER_PAGE, GisLiveChooserPageClass))
+
+typedef struct _GisLiveChooserPage        GisLiveChooserPage;
+typedef struct _GisLiveChooserPageClass   GisLiveChooserPageClass;
+
+struct _GisLiveChooserPage
+{
+    GisPage parent;
+};
+
+struct _GisLiveChooserPageClass
+{
+    GisPageClass parent_class;
+};
+
+GType gis_live_chooser_page_get_type (void);
+
+GisPage *gis_prepare_live_chooser_page (GisDriver *driver);
+
+G_END_DECLS
+
+#endif /* GIS_LIVE_CHOOSER_PAGE_H */
+
diff --git a/gnome-initial-setup/pages/live-chooser/gis-live-chooser-page.ui 
b/gnome-initial-setup/pages/live-chooser/gis-live-chooser-page.ui
new file mode 100644
index 00000000..e429166e
--- /dev/null
+++ b/gnome-initial-setup/pages/live-chooser/gis-live-chooser-page.ui
@@ -0,0 +1,195 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.0 -->
+  <template class="GisLiveChooserPage" parent="GisPage">
+    <child>
+      <object class="GtkBox" id="main_box">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="hexpand">True</property>
+        <property name="vexpand">True</property>
+        <property name="margin_start">24</property>
+        <property name="margin_end">24</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">24</property>
+        <child type="center">
+          <object class="GtkBox">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="hexpand">True</property>
+            <property name="vexpand">True</property>
+            <property name="spacing">24</property>
+            <property name="homogeneous">True</property>
+            <child>
+              <object class="GtkFrame">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="hexpand">True</property>
+                <property name="label_xalign">0</property>
+                <property name="shadow_type">none</property>
+                <child>
+                  <object class="GtkBox">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="hexpand">True</property>
+                    <property name="border_width">18</property>
+                    <property name="orientation">vertical</property>
+                    <property name="spacing">24</property>
+                    <child>
+                      <object class="GtkLabel" id="try_label">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="label" translatable="yes">Try Endless OS by running it from the USB 
Stick.</property>
+                        <property name="justify">center</property>
+                        <property name="wrap">True</property>
+                        <property name="yalign">0</property>
+                        <property name="max_width_chars">30</property>
+                        <attributes>
+                          <attribute name="scale" value="1.4"/>
+                        </attributes>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkImage" id="try_icon">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="resource">/org/gnome/initial-setup/try-endless.png</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkButton" id="try_button">
+                        <property name="label" translatable="yes">Try It</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">True</property>
+                        <style>
+                          <class name="suggested-action"/>
+                        </style>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">2</property>
+                      </packing>
+                    </child>
+                  </object>
+                </child>
+                <style>
+                  <class name="rounded-frame"/>
+                </style>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkFrame">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="hexpand">True</property>
+                <property name="label_xalign">0</property>
+                <property name="shadow_type">none</property>
+                <child>
+                  <object class="GtkBox">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="hexpand">True</property>
+                    <property name="border_width">18</property>
+                    <property name="orientation">vertical</property>
+                    <property name="spacing">24</property>
+                    <child>
+                      <object class="GtkLabel" id="reformat_label">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="label" translatable="yes">Reformat this computer with Endless 
OS.</property>
+                        <property name="justify">center</property>
+                        <property name="wrap">True</property>
+                        <property name="yalign">0</property>
+                        <property name="max_width_chars">30</property>
+                        <attributes>
+                          <attribute name="scale" value="1.4"/>
+                        </attributes>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkImage" id="reformat_icon">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="resource">/org/gnome/initial-setup/install-endless.png</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkButton" id="reformat_button">
+                        <property name="label" translatable="yes">Reformat</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">True</property>
+                        <style>
+                          <class name="suggested-action"/>
+                        </style>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">2</property>
+                      </packing>
+                    </child>
+                  </object>
+                </child>
+                <style>
+                  <class name="rounded-frame"/>
+                </style>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </template>
+  <object class="GtkSizeGroup">
+    <property name="mode">vertical</property>
+    <widgets>
+      <widget name="reformat_label"/>
+      <widget name="try_label"/>
+    </widgets>
+  </object>
+  <object class="GtkSizeGroup">
+    <property name="mode">vertical</property>
+    <widgets>
+      <widget name="reformat_icon"/>
+      <widget name="try_icon"/>
+    </widgets>
+  </object>
+</interface>
diff --git a/gnome-initial-setup/pages/live-chooser/install-endless.png 
b/gnome-initial-setup/pages/live-chooser/install-endless.png
new file mode 100644
index 00000000..74307a28
Binary files /dev/null and b/gnome-initial-setup/pages/live-chooser/install-endless.png differ
diff --git a/gnome-initial-setup/pages/live-chooser/live-chooser.css 
b/gnome-initial-setup/pages/live-chooser/live-chooser.css
new file mode 100644
index 00000000..b2bc76e1
--- /dev/null
+++ b/gnome-initial-setup/pages/live-chooser/live-chooser.css
@@ -0,0 +1,4 @@
+frame.rounded-frame {
+    border: solid 1px @borders;
+    border-radius: 4px;
+}
diff --git a/gnome-initial-setup/pages/live-chooser/live-chooser.gresource.xml 
b/gnome-initial-setup/pages/live-chooser/live-chooser.gresource.xml
new file mode 100644
index 00000000..9e309981
--- /dev/null
+++ b/gnome-initial-setup/pages/live-chooser/live-chooser.gresource.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+  <gresource prefix="/org/gnome/initial-setup">
+    <file alias="live-chooser.css">live-chooser.css</file>
+    <file preprocess="xml-stripblanks" alias="gis-live-chooser-page.ui">gis-live-chooser-page.ui</file>
+    <file>install-endless.png</file>
+    <file>try-endless.png</file>
+  </gresource>
+</gresources>
diff --git a/gnome-initial-setup/pages/live-chooser/meson.build 
b/gnome-initial-setup/pages/live-chooser/meson.build
new file mode 100644
index 00000000..08900a77
--- /dev/null
+++ b/gnome-initial-setup/pages/live-chooser/meson.build
@@ -0,0 +1,9 @@
+sources += gnome.compile_resources(
+    'live-chooser-resources',
+    files('live-chooser.gresource.xml'),
+    c_name: 'live_chooser'
+)
+
+sources += files(
+    'gis-live-chooser-page.c',
+)
diff --git a/gnome-initial-setup/pages/live-chooser/try-endless.png 
b/gnome-initial-setup/pages/live-chooser/try-endless.png
new file mode 100644
index 00000000..eb55491f
Binary files /dev/null and b/gnome-initial-setup/pages/live-chooser/try-endless.png differ
diff --git a/gnome-initial-setup/pages/meson.build b/gnome-initial-setup/pages/meson.build
index 6c14d5f6..0898d2ac 100644
--- a/gnome-initial-setup/pages/meson.build
+++ b/gnome-initial-setup/pages/meson.build
@@ -2,6 +2,7 @@ pages = [
    'account',
    'display',
    'language',
+   'live-chooser',
    'keyboard',
    'endless-eula',
    'network',
diff --git a/meson_options.txt b/meson_options.txt
index be386b59..7a71d278 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -21,6 +21,11 @@ option('systemd',
   description: 'Enable systemd integration'
 )
 
+option('systemd-system-unit-dir',
+       description: 'the directory to install systemd system units',
+       type: 'string'
+)
+
 option('parental_controls',
   type: 'feature',
   value: 'auto',
diff --git a/po/POTFILES.in b/po/POTFILES.in
index b40ed056..65ee030a 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -30,6 +30,8 @@ gnome-initial-setup/pages/language/cc-language-chooser.c
 gnome-initial-setup/pages/language/gis-language-page.c
 gnome-initial-setup/pages/language/gis-language-page.ui
 gnome-initial-setup/pages/language/gis-welcome-widget.c
+gnome-initial-setup/pages/live-chooser/gis-live-chooser-page.c
+gnome-initial-setup/pages/live-chooser/gis-live-chooser-page.ui
 gnome-initial-setup/pages/network/gis-network-page.c
 gnome-initial-setup/pages/network/gis-network-page.ui
 gnome-initial-setup/pages/parental-controls/gis-parental-controls-page.c



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]