Unverified Commit 5244f79d authored by Cosima Neidahl's avatar Cosima Neidahl Committed by GitHub
Browse files

Merge pull request #332220 from OPNA2608/update/lomiri/OTA-5

lomiri.*: OTA-5
parents bea821b6 39ca1beb
Loading
Loading
Loading
Loading
+21 −19
Original line number Diff line number Diff line
{ config
, pkgs
, lib
, ...
{
  config,
  pkgs,
  lib,
  ...
}:

let
@@ -32,25 +33,26 @@ in
    environment = {
      systemPackages = cfg.packages;

      pathsToLink = [
        "/share/ayatana"
      ];
      pathsToLink = [ "/share/ayatana" ];
    };

    # libayatana-common's ayatana-indicators.target with explicit Wants & Before to bring up requested indicator services
    systemd.user.targets."ayatana-indicators" =
    systemd.user.targets =
      let
        indicatorServices = lib.lists.flatten
          (map
            (pkg:
              (map (ind: "${ind}.service") pkg.passthru.ayatana-indicators))
            cfg.packages);
        indicatorServices = lib.lists.flatten (
          map (pkg: (map (ind: "${ind}.service") pkg.passthru.ayatana-indicators)) cfg.packages
        );
      in
      {
        description = "Target representing the lifecycle of the Ayatana Indicators. Each indicator should be bound to it in its individual service file";
      lib.attrsets.mapAttrs
        (_: desc: {
          description = "Target representing the lifecycle of the ${desc}. Each indicator should be bound to it in its individual service file";
          partOf = [ "graphical-session.target" ];
          wants = indicatorServices;
          before = indicatorServices;
        })
        {
          ayatana-indicators = "Ayatana Indicators";
          lomiri-indicators = "Ayatana/Lomiri Indicators that shall be run in Lomiri";
        };
  };

+1 −1
Original line number Diff line number Diff line
@@ -537,7 +537,7 @@ in {
  lxd = pkgs.recurseIntoAttrs (handleTest ./lxd { inherit handleTestOn; });
  lxd-image-server = handleTest ./lxd-image-server.nix {};
  #logstash = handleTest ./logstash.nix {};
  lomiri = handleTest ./lomiri.nix {};
  lomiri = discoverTests (import ./lomiri.nix);
  lomiri-calculator-app = runTest ./lomiri-calculator-app.nix;
  lomiri-camera-app = runTest ./lomiri-camera-app.nix;
  lomiri-clock-app = runTest ./lomiri-clock-app.nix;
+484 −351
Original line number Diff line number Diff line
import ./make-test-python.nix ({ pkgs, lib, ... }: let
let
  makeTest = import ./make-test-python.nix;
  # Just to make sure everything is the same, need it for OCR & navigating greeter
  user = "alice";
  description = "Alice Foobar";
  password = "foobar";
in {
  name = "lomiri";
in
{
  greeter = makeTest (
    { pkgs, lib, ... }:
    {
      name = "lomiri-greeter";

      meta = {
        maintainers = lib.teams.lomiri.members;
      };

  nodes.machine = { config, ... }: {
      nodes.machine =
        { config, ... }:
        {
          imports = [ ./common/user-account.nix ];

          virtualisation.memorySize = 2047;

          users.users.${user} = {
            inherit description password;
          };

          services.desktopManager.lomiri.enable = lib.mkForce true;
          services.displayManager.defaultSession = lib.mkForce "lomiri";

          # Help with OCR
          fonts.packages = [ pkgs.inconsolata ];
        };

      enableOCR = true;

      testScript =
        { nodes, ... }:
        ''
          start_all()
          machine.wait_for_unit("multi-user.target")

          # Lomiri in greeter mode should work & be able to start a session
          with subtest("lomiri greeter works"):
              machine.wait_for_unit("display-manager.service")
              machine.wait_until_succeeds("pgrep -u lightdm -f 'lomiri --mode=greeter'")

              # Start page shows current time
              machine.wait_for_text(r"(AM|PM)")
              machine.screenshot("lomiri_greeter_launched")

              # Advance to login part
              machine.send_key("ret")
              machine.wait_for_text("${description}")
              machine.screenshot("lomiri_greeter_login")

              # Login
              machine.send_chars("${password}\n")
              machine.wait_until_succeeds("pgrep -u ${user} -f 'lomiri --mode=full-shell'")

              # Output rendering from Lomiri has started when it starts printing performance diagnostics
              machine.wait_for_console_text("Last frame took")
              # Look for datetime's clock, one of the last elements to load
              machine.wait_for_text(r"(AM|PM)")
              machine.screenshot("lomiri_launched")
        '';
    }
  );

  desktop = makeTest (
    { pkgs, lib, ... }:
    {
      name = "lomiri-desktop";

      meta = {
        maintainers = lib.teams.lomiri.members;
      };

      nodes.machine =
        { config, ... }:
        {
          imports = [
            ./common/auto.nix
            ./common/user-account.nix
          ];

          virtualisation.memorySize = 2047;

          users.users.${user} = {
            inherit description password;
            # polkit agent test
            extraGroups = [ "wheel" ];
          };

          test-support.displayManager.auto = {
            enable = true;
            inherit user;
          };

          # To control mouse via scripting
@@ -74,29 +153,12 @@ in {

                inherit (alacritty) meta;
              })

        # Polkit requests eventually time out.
        # Keep triggering them until we signal detection success
        (writeShellApplication {
          name = "lpa-check";
          text = ''
            while [ ! -f /tmp/lpa-checked ]; do
              pkexec echo a
            done
          '';
        })
        # Signal detection success
        (writeShellApplication {
          name = "lpa-signal";
          text = ''
            touch /tmp/lpa-checked
          '';
        })
            ];
          };

          # Help with OCR
    systemd.tmpfiles.settings = let
          systemd.tmpfiles.settings =
            let
              white = "255, 255, 255";
              black = "0, 0, 0";
              colorSection = color: {
@@ -104,26 +166,31 @@ in {
                Bold = true;
                Transparency = false;
              };
      terminalColors = pkgs.writeText "customized.colorscheme" (lib.generators.toINI {} {
              terminalColors = pkgs.writeText "customized.colorscheme" (
                lib.generators.toINI { } {
                  Background = colorSection white;
                  Foreground = colorSection black;
                  Color2 = colorSection black;
                  Color2Intense = colorSection black;
      });
      terminalConfig = pkgs.writeText "terminal.ubports.conf" (lib.generators.toINI {} {
                }
              );
              terminalConfig = pkgs.writeText "terminal.ubports.conf" (
                lib.generators.toINI { } {
                  General = {
                    colorScheme = "customized";
                    fontSize = "16";
                    fontStyle = "Inconsolata";
                  };
      });
                }
              );
              confBase = "${config.users.users.${user}.home}/.config";
              userDirArgs = {
                mode = "0700";
                user = user;
                group = "users";
              };
    in {
            in
            {
              "10-lomiri-test-setup" = {
                "${confBase}".d = userDirArgs;
                "${confBase}/terminal.ubports".d = userDirArgs;
@@ -135,7 +202,9 @@ in {

      enableOCR = true;

  testScript = { nodes, ... }: ''
      testScript =
        { nodes, ... }:
        ''
          def toggle_maximise():
              """
              Maximise the current window.
@@ -177,26 +246,9 @@ in {
          start_all()
          machine.wait_for_unit("multi-user.target")

    # Lomiri in greeter mode should work & be able to start a session
    with subtest("lomiri greeter works"):
        machine.wait_for_unit("display-manager.service")
        machine.wait_until_succeeds("pgrep -u lightdm -f 'lomiri --mode=greeter'")

        # Start page shows current time
        machine.wait_for_text(r"(AM|PM)")
        machine.screenshot("lomiri_greeter_launched")

        # Advance to login part
        machine.send_key("ret")
        machine.wait_for_text("${description}")
        machine.screenshot("lomiri_greeter_login")

        # Login
        machine.send_chars("${password}\n")
        machine.wait_until_succeeds("pgrep -u ${user} -f 'lomiri --mode=full-shell'")

          # The session should start, and not be stuck in i.e. a crash loop
          with subtest("lomiri starts"):
              machine.wait_until_succeeds("pgrep -u ${user} -f 'lomiri --mode=full-shell'")
              # Output rendering from Lomiri has started when it starts printing performance diagnostics
              machine.wait_for_console_text("Last frame took")
              # Look for datetime's clock, one of the last elements to load
@@ -218,13 +270,17 @@ in {

              # Doing this here, since we need an in-session shell & separately starting a terminal again wastes time
              with subtest("polkit agent works"):
            machine.send_chars("exec lpa-check\n")
            machine.wait_for_text(r"(Elevated permissions|Login)")
                  machine.send_chars("pkexec touch /tmp/polkit-test\n")
                  # There's an authentication notification here that gains focus, but we struggle with OCRing it
                  # Just hope that it's up after a short wait
                  machine.sleep(10)
                  machine.screenshot("polkit_agent")
            machine.execute("lpa-signal")
                  machine.send_chars("${password}")
                  machine.sleep(2) # Hopefully enough delay to make sure all the password characters have been registered? Maybe just placebo
                  machine.send_chars("\n")
                  machine.wait_for_file("/tmp/polkit-test", 10)

        # polkit test will quit terminal when agent request times out after OCR success
        machine.wait_until_fails("pgrep -u ${user} -f lomiri-terminal-app")
              machine.send_key("alt-f4")

          # We want the ability to launch applications
          with subtest("starter menu works"):
@@ -307,6 +363,81 @@ in {

              machine.sleep(2) # sleep a tiny bit so morph can close & the focus can return to LSS
              machine.send_key("alt-f4")
        '';
    }
  );

  desktop-ayatana-indicators = makeTest (
    { pkgs, lib, ... }:
    {
      name = "lomiri-desktop-ayatana-indicators";

      meta = {
        maintainers = lib.teams.lomiri.members;
      };

      nodes.machine =
        { config, ... }:
        {
          imports = [
            ./common/auto.nix
            ./common/user-account.nix
          ];

          virtualisation.memorySize = 2047;

          users.users.${user} = {
            inherit description password;
          };

          test-support.displayManager.auto = {
            enable = true;
            inherit user;
          };

          # To control mouse via scripting
          programs.ydotool.enable = true;

          services.desktopManager.lomiri.enable = lib.mkForce true;
          services.displayManager.defaultSession = lib.mkForce "lomiri";

          # Help with OCR
          fonts.packages = [ pkgs.inconsolata ];
        };

      enableOCR = true;

      testScript =
        { nodes, ... }:
        ''
          def mouse_click(xpos, ypos):
              """
              Move the mouse to a screen location and hit left-click.
              """

              # Need to reset to top-left, --absolute doesn't work?
              machine.execute("ydotool mousemove -- -10000 -10000")
              machine.sleep(2)

              # Move
              machine.execute(f"ydotool mousemove -- {xpos} {ypos}")
              machine.sleep(2)

              # Click (C0 - left button: down & up)
              machine.execute("ydotool click 0xC0")
              machine.sleep(2)

          start_all()
          machine.wait_for_unit("multi-user.target")

          # The session should start, and not be stuck in i.e. a crash loop
          with subtest("lomiri starts"):
              machine.wait_until_succeeds("pgrep -u ${user} -f 'lomiri --mode=full-shell'")
              # Output rendering from Lomiri has started when it starts printing performance diagnostics
              machine.wait_for_console_text("Last frame took")
              # Look for datetime's clock, one of the last elements to load
              machine.wait_for_text(r"(AM|PM)")
              machine.screenshot("lomiri_launched")

          # The ayatana indicators are an important part of the experience, and they hold the only graphical way of exiting the session.
          # There's a test app we could use that also displays their contents, but it's abit inconsistent.
@@ -358,6 +489,8 @@ in {
                  mouse_click(720, 280) # "Log Out"
                  mouse_click(400, 240) # confirm logout
                  machine.wait_until_fails("pgrep -u ${user} -f 'lomiri --mode=full-shell'")
            machine.wait_until_succeeds("pgrep -u lightdm -f 'lomiri --mode=greeter'")
        '';
})
    }
  );

}
+5 −17
Original line number Diff line number Diff line
@@ -27,26 +27,18 @@

stdenv.mkDerivation (finalAttrs: {
  pname = "lomiri-clock-app";
  version = "4.0.3";
  version = "4.0.4";

  src = fetchFromGitLab {
    owner = "ubports";
    repo = "development/apps/lomiri-clock-app";
    rev = "v${finalAttrs.version}";
    hash = "sha256-q/hdnwHO97bhL0W3VsdHwjPvGs6GhWbDiVLCx4NiR50=";
    hash = "sha256-IWNLMYrebYQe5otNwZtRUs4YGPo/5OFic3Nh2pWxROs=";
  };

  patches = [
    # Fix dispatching to clock app via LUD
    # Remove when version > 4.0.3
    (fetchpatch {
      name = "0001-lomiri-clock-app-Fix-non-click-lomiri-url-dispatcher-support.patch";
      url = "https://gitlab.com/ubports/development/apps/lomiri-clock-app/-/commit/493888b41489b360415d1a0d0e431754afdee2b0.patch";
      hash = "sha256-sI7YDrWjV0bSAq0vdSvNcWCLhmEEb10T7jd2kYfSfZU=";
    })

    # Fix GNUInstallDirs variables usage
    # Remove when https://gitlab.com/ubports/development/apps/lomiri-clock-app/-/merge_requests/215 merged & in release
    # Remove when version > 4.0.4
    (fetchpatch {
      name = "0002-lomiri-clock-app-Fix-GNUInstallDirs-variable-concatenations-in-CMake.patch";
      url = "https://gitlab.com/ubports/development/apps/lomiri-clock-app/-/commit/33c62d0382f69462de0567628d7a6ef162944e12.patch";
@@ -54,7 +46,7 @@ stdenv.mkDerivation (finalAttrs: {
    })

    # Fix installation of splash icon
    # Remove when https://gitlab.com/ubports/development/apps/lomiri-clock-app/-/merge_requests/216 merged & in release
    # Remove when version > 4.0.4
    (fetchpatch {
      name = "0003-lomiri-clock-app-Fix-splash-file-installation-in-non-clock-mode.patch";
      url = "https://gitlab.com/ubports/development/apps/lomiri-clock-app/-/commit/97fd6fd91ee787dfe107bd36bc895f2ff234b5e3.patch";
@@ -85,7 +77,7 @@ stdenv.mkDerivation (finalAttrs: {
    })

    # Don't ignore PATH when looking for qmltestrunner, saves us a patch for hardcoded fallback
    # Remove when https://gitlab.com/ubports/development/apps/lomiri-clock-app/-/merge_requests/218 merged & in release
    # Remove when version > 4.0.4
    (fetchpatch {
      name = "0008-lomiri-clock-app-tests-Drop-NO_DEFAULT_PATH.patch";
      url = "https://gitlab.com/ubports/development/apps/lomiri-clock-app/-/commit/190ef47e2efaaf139920d0556e0522f95479ea95.patch";
@@ -102,10 +94,6 @@ stdenv.mkDerivation (finalAttrs: {
  ];

  postPatch = ''
    # Part of 493888b41489b360415d1a0d0e431754afdee2b0 patch, but neither fetchpatch nor fetchpatch2 can handle a rename-only change to a file
    # Remove when version > 4.0.3
    mv clock.ubports_clock.url-dispatcher lomiri-clock-app.url-dispatcher

    # QT_IMPORTS_DIR returned by qmake -query is broken
    substituteInPlace CMakeLists.txt \
      --replace-fail 'qmake -query QT_INSTALL_QML' 'echo ''${CMAKE_INSTALL_PREFIX}/${qtbase.qtQmlPrefix}' \
+76 −110
Original line number Diff line number Diff line
{ stdenv
, lib
, fetchFromGitLab
, fetchpatch
, fetchpatch2
, gitUpdater
, testers
, accountsservice
, ayatana-indicator-datetime
, cmake
, cmake-extras
, content-hub
, dbus
, deviceinfo
, geonames
, gettext
, glib
, gnome-desktop
, gsettings-qt
, gtk3
, icu
, intltool
, json-glib
, libqofono
, libqtdbustest
, libqtdbusmock
, lomiri-indicator-network
, lomiri-schemas
, lomiri-settings-components
, lomiri-ui-toolkit
, maliit-keyboard
, pkg-config
, python3
, qmenumodel
, qtbase
, qtdeclarative
, qtmultimedia
, ubports-click
, upower
, validatePkgConfig
, wrapGAppsHook3
, wrapQtAppsHook
, xvfb-run
{
  stdenv,
  lib,
  fetchFromGitLab,
  gitUpdater,
  testers,
  accountsservice,
  ayatana-indicator-datetime,
  biometryd,
  cmake,
  cmake-extras,
  content-hub,
  dbus,
  deviceinfo,
  geonames,
  gettext,
  glib,
  gnome-desktop,
  gsettings-qt,
  gtk3,
  icu,
  intltool,
  json-glib,
  libqofono,
  libqtdbustest,
  libqtdbusmock,
  lomiri-indicator-network,
  lomiri-schemas,
  lomiri-settings-components,
  lomiri-ui-toolkit,
  maliit-keyboard,
  pkg-config,
  polkit,
  python3,
  qmenumodel,
  qtbase,
  qtdeclarative,
  qtmultimedia,
  trust-store,
  ubports-click,
  upower,
  validatePkgConfig,
  wrapGAppsHook3,
  wrapQtAppsHook,
  xvfb-run,
}:

stdenv.mkDerivation (finalAttrs: {
  pname = "lomiri-system-settings-unwrapped";
  version = "1.1.0";
  version = "1.2.0";

  src = fetchFromGitLab {
    owner = "ubports";
    repo = "development/core/lomiri-system-settings";
    rev = finalAttrs.version;
    hash = "sha256-Po5eArO7zyaGatTf6kqci3DdzFDJSZakeglbiMx9kR8=";
    hash = "sha256-dWaXPr9Z5jz5SbwLSd3jVqjK0E5BdcKVeF15p8j47uM=";
  };

  outputs = [
@@ -59,55 +61,9 @@ stdenv.mkDerivation (finalAttrs: {
    "dev"
  ];

  patches = [
    # Remove when https://gitlab.com/ubports/development/core/lomiri-system-settings/-/merge_requests/433 merged & in release
    (fetchpatch {
      name = "0001-lomiri-system-settings-plugins-language-Fix-linking-against-accountsservice.patch";
      url = "https://gitlab.com/ubports/development/core/lomiri-system-settings/-/commit/75763ae2f9669f5f7f29aec3566606e6f6cb7478.patch";
      hash = "sha256-2CE0yizkaz93kK82DhaaFjKmGnMoaikrwFj4k7RN534=";
    })

    # Remove when https://gitlab.com/ubports/development/core/lomiri-system-settings/-/merge_requests/434 merged & in release
    (fetchpatch {
      name = "0002-lomiri-system-settings-GNUInstallDirs-and-fix-absolute-path-handling.patch";
      url = "https://gitlab.com/ubports/development/core/lomiri-system-settings/-/commit/93ee84423f3677a608ef73addcd3ddcbe7dc1d32.patch";
      hash = "sha256-lSKAhtE3oSSv7USvDbbcfBZWAtWMmuKneWawKQABIiM=";
    })

    # Fixes tests with very-recent python-dbusmock
    # Remove when version > 1.1.0
    (fetchpatch {
      name = "0003-lomiri-system-settings-Revert-Pass-missing-parameter-to-dbusmock-bluez-PairDevice-function.patch";
      url = "https://gitlab.com/ubports/development/core/lomiri-system-settings/-/commit/67d9e28ebab8bdb9473d5bf8da2b7573e6848fa2.patch";
      hash = "sha256-pFWNne2UH3R5Fz9ayHvIpDXDQbXPs0k4b/oRg0fzi+s=";
    })

    (fetchpatch2 {
      name = "0004-lomiri-system-settings-QOfono-namespace-change.patch";
      url = "https://gitlab.com/ubports/development/core/lomiri-system-settings/-/commit/c0b5b007d77993fabdd95be5ccbbba5151f0f165.patch";
      hash = "sha256-HB7qdlbY0AVG6X3hL3IHf0Z7rm1G0wfdqo5MXtY7bfE=";
    })
  ] ++ [

    ./2000-Support-wrapping-for-Nixpkgs.patch

    # Make it work with regular accountsservice
    # https://gitlab.com/ubports/development/core/lomiri-system-settings/-/issues/341
    (fetchpatch {
      name = "2001-lomiri-system-settings-disable-current-language-switching.patch";
      url = "https://sources.debian.org/data/main/l/lomiri-system-settings/1.0.1-2/debian/patches/2001_disable-current-language-switching.patch";
      hash = "sha256-ZOFYwxS8s6+qMFw8xDCBv3nLBOBm86m9d/VhbpOjamY=";
    })
  ];
  patches = [ ./2000-Support-wrapping-for-Nixpkgs.patch ];

  postPatch = ''
    # Part of 0004-lomiri-system-settings-QOfono-namespace-change.patch, fetchpatch2 cannot handle rename-only changes
    for unmovedThing in tests/mocks/MeeGo/QOfono/*; do
      mv "$unmovedThing" "tests/mocks/QOfono/$(basename "$unmovedThing")"
    done
    rmdir tests/mocks/MeeGo/QOfono
    rmdir tests/mocks/MeeGo

    substituteInPlace CMakeLists.txt \
      --replace-fail "\''${CMAKE_INSTALL_LIBDIR}/qt5/qml" "\''${CMAKE_INSTALL_PREFIX}/${qtbase.qtQmlPrefix}" \

@@ -155,7 +111,9 @@ stdenv.mkDerivation (finalAttrs: {
    gtk3
    icu
    json-glib
    polkit
    qtbase
    trust-store
    ubports-click
    upower
  ];
@@ -163,6 +121,7 @@ stdenv.mkDerivation (finalAttrs: {
  # QML components and schemas the wrapper needs
  propagatedBuildInputs = [
    ayatana-indicator-datetime
    biometryd
    content-hub
    libqofono
    lomiri-indicator-network
@@ -177,9 +136,7 @@ stdenv.mkDerivation (finalAttrs: {

  nativeCheckInputs = [
    dbus
    (python3.withPackages (ps: with ps; [
      python-dbusmock
    ]))
    (python3.withPackages (ps: with ps; [ python-dbusmock ]))
    xvfb-run
  ];

@@ -194,19 +151,21 @@ stdenv.mkDerivation (finalAttrs: {
  cmakeFlags = [
    (lib.cmakeBool "ENABLE_LIBDEVICEINFO" true)
    (lib.cmakeBool "ENABLE_TESTS" finalAttrs.finalPackage.doCheck)
    (lib.cmakeFeature "CMAKE_CTEST_ARGUMENTS" (lib.concatStringsSep ";" [
    (lib.cmakeFeature "CMAKE_CTEST_ARGUMENTS" (
      lib.concatStringsSep ";" [
        # Exclude tests
      "-E" (lib.strings.escapeShellArg "(${lib.concatStringsSep "|" [
        "-E"
        (lib.strings.escapeShellArg "(${
          lib.concatStringsSep "|" [
            # Hits OpenGL context issue inside lomiri-ui-toolkit, see derivation of that on details
            "^testmouse"
            "^tst_notifications"
      ]})")
    ]))
          ]
        })")
      ]
    ))
  ];

  # CMake option had to be excluded from earlier patchset
  env.NIX_CFLAGS_COMPILE = lib.optionalString (lib.strings.versionOlder python3.pkgs.python-dbusmock.version "0.30.1") "-DMODERN_PYTHON_DBUSMOCK";

  # The linking for this normally ignores missing symbols, which is inconvenient for figuring out why subpages may be
  # failing to load their library modules. Force it to report them at linktime instead of runtime.
  env.NIX_LDFLAGS = "--unresolved-symbols=report-all";
@@ -218,7 +177,16 @@ stdenv.mkDerivation (finalAttrs: {

  preCheck = ''
    export QT_PLUGIN_PATH=${lib.getBin qtbase}/${qtbase.qtPluginPrefix}
    export QML2_IMPORT_PATH=${lib.makeSearchPathOutput "bin" qtbase.qtQmlPrefix ([ qtdeclarative lomiri-ui-toolkit lomiri-settings-components ] ++ lomiri-ui-toolkit.propagatedBuildInputs)}
    export QML2_IMPORT_PATH=${
      lib.makeSearchPathOutput "bin" qtbase.qtQmlPrefix (
        [
          qtdeclarative
          lomiri-ui-toolkit
          lomiri-settings-components
        ]
        ++ lomiri-ui-toolkit.propagatedBuildInputs
      )
    }
  '';

  postInstall = ''
@@ -244,8 +212,6 @@ stdenv.mkDerivation (finalAttrs: {
    mainProgram = "lomiri-system-settings";
    maintainers = teams.lomiri.members;
    platforms = platforms.linux;
    pkgConfigModules = [
      "LomiriSystemSettings"
    ];
    pkgConfigModules = [ "LomiriSystemSettings" ];
  };
})
Loading