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";

  wallpaperName = "wallpaper.jpg";
  # In case it ever shows up in the VM, we could OCR for it instead
  wallpaperText = "Lorem ipsum";

  # setup to make OCRing on terminal output more reliable
  terminalTextColour = {
    r = 91;
    g = 113;
    b = 102;
  };
  terminalTextColourString =
    lib:
    let
      toHex = component: lib.optionalString (component < 16) "0" + lib.trivial.toHexString component;
    in
    "#${toHex terminalTextColour.r}${toHex terminalTextColour.g}${toHex terminalTextColour.b}";
  terminalOcrTmpfilesSetup =
    {
      pkgs,
      lib,
      config,
    }:
    let
      white = "255, 255, 255";
      foreground = "${toString terminalTextColour.r}, ${toString terminalTextColour.g}, ${toString terminalTextColour.b}";
      colorSection = color: {
        Color = color;
        Bold = true;
        Transparency = false;
      };
      terminalColors = pkgs.writeText "customized.colorscheme" (
        lib.generators.toINI { } {
          Background = colorSection white;
          Foreground = colorSection foreground;
          Color2 = colorSection foreground;
          Color2Intense = colorSection foreground;
        }
      );
      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
    {
      "${confBase}".d = userDirArgs;
      "${confBase}/terminal.ubports".d = userDirArgs;
      "${confBase}/terminal.ubports/customized.colorscheme".L.argument = "${terminalColors}";
      "${confBase}/terminal.ubports/terminal.ubports.conf".L.argument = "${terminalConfig}";
    };

  wallpaperFile =
    pkgs:
    pkgs.runCommand wallpaperName
      {
        nativeBuildInputs = with pkgs; [
          (imagemagick.override { ghostscriptSupport = true; }) # produce OCR-able image
        ];
      }
      ''
        magick -size 640x480 canvas:black -pointsize 30 -fill white -annotate +100+100 '${wallpaperText}' $out
      '';

  lomiriWallpaperDconfSettings = pkgs: {
    settings = {
      "org/gnome/desktop/background" = {
        picture-uri = "file://${wallpaperFile pkgs}";
      };
    };
  };

  sharedTestFunctions = lib: ''
    from collections.abc import Callable
    import tempfile
    import subprocess

    # Based on terminal-emulators.nix' check_for_pink
    def check_for_color(color: str) -> Callable[[bool], bool]:
      def check_for_color_retry(final=False) -> bool:
        with tempfile.NamedTemporaryFile() as tmpin:
          machine.send_monitor_command("screendump {}".format(tmpin.name))

          cmd = 'convert {} -define histogram:unique-colors=true -format "%c" histogram:info:'.format(
            tmpin.name
          )
          ret = subprocess.run(cmd, shell=True, capture_output=True)
          if ret.returncode != 0:
            raise Exception(
              "image analysis failed with exit code {}".format(ret.returncode)
            )

          text = ret.stdout.decode("utf-8")
          return color in text

      return check_for_color_retry

    def check_for_color_continued_presence(color: str) -> Callable[[bool], bool]:
      colorFunc: Callable[[bool], bool] = check_for_color(color)
      def check_for_color_continued_presence_retry(final=False) -> bool:
        colorPresent: bool = colorFunc(final)

        if final:
          # If it fails now, retry handles the exception raising.
          # Otherwise, we passed.
          return colorPresent
        else:
          if colorPresent:
            # We want retry to continue running us until the timeout, so signal failure.
            return False
          else:
            # Color disappeared
            raise Exception(
              "color {} has disappeared from the screen!".format(color)
            )
      return check_for_color_continued_presence_retry

    def ensure_terminal_running() -> None:
      """
      Ensure that lomiri-terminal-app has finished starting up.
      """

      terminalTextColor: str = "${terminalTextColourString lib}"
      with machine.nested("Waiting for the screen to have terminalTextColor {} on it:".format(terminalTextColor)):
        retry(check_for_color(terminalTextColor))
      with machine.nested("Ensuring terminalTextColor {} stays present on the screen:".format(terminalTextColor)):
        retry(fn=check_for_color_continued_presence(terminalTextColor), timeout_seconds=5)

    def change_tty_back_forth(ttynumMain: int, ttynumDiff: int) -> None:
      """
      A qtmir bump made the image get stuck, a tty switch back and forth fixes it.
      """

      machine.send_key(f"ctrl-alt-f{ttynumDiff}")
      machine.sleep(10)
      machine.send_key(f"ctrl-alt-f{ttynumMain}")
      machine.sleep(10)

    def ensure_greeter_launched() -> None:
      """
      Ensure that Lomiri (in greeter mode) has started up and is responsive.
      Execution will stop at the user selection.
      """

      machine.wait_for_unit("display-manager.service")
      machine.wait_until_succeeds("pgrep -u lightdm -f 'lomiri --mode=greeter'")

      # Start page shows current time
      wait_for_text(r"(AM|PM)")

      # Display "hangs" since qtmir bump? Not sure why. Switch to a different tty and back, and ensure that time is still shown
      # Greeter runs on: tty1
      change_tty_back_forth(1, 2)
      wait_for_text(r"(AM|PM)")
      machine.screenshot("lomiri_greeter_launched")

      # Advance to user selection, to make sure display really isn't stuck anymore
      machine.send_key("ret")
      wait_for_text("${description}")
      machine.screenshot("lomiri_greeter_login")

    def ensure_lomiri_running(ttynumMain: int = 1, ttynumDiff: int = 2) -> None:
      """
      Ensure that Lomiri has finished starting up.
      """

      # Process runs
      machine.wait_until_succeeds("pgrep -u ${user} -f 'lomiri --mode=full-shell'")

      # One of the last UI elements that loads is the clock. In the past, we could OCR for AM/PM to ensure it's there. That is now flaky.
      # The next best thing is to look for the launcher button, and ensure it stays around for awhile (DE doesn't crash).
      launcherColor: str = "#5277C3"
      with machine.nested("Waiting for the screen to have launcherColor {} on it:".format(launcherColor)):
        retry(check_for_color(launcherColor))
      with machine.nested("Ensuring launcherColor {} stays present on the screen:".format(launcherColor)):
        retry(fn=check_for_color_continued_presence(launcherColor), timeout_seconds=30)

      # Display "hangs" since qtmir bump? Not sure why. Switch to a different tty and back, and ensure that launcher button is still shown
      change_tty_back_forth(ttynumMain, ttynumDiff)
      with machine.nested("Waiting for the screen to have launcherColor {} on it:".format(launcherColor)):
        retry(check_for_color(launcherColor))

      # First input seems to get dropped while Mir registers the new input device. Send a key that does nothing, to get that out of the way, and sleep a tiny bit for registration to finish.
      machine.send_key("left")
      machine.sleep(3)

      machine.screenshot("lomiri_launched")

    def wait_for_text(text) -> None:
      """
      Wait for on-screen text, and try to optimise retry count for slow hardware.
      """

      machine.sleep(30)
      machine.wait_for_text(text)

    def toggle_maximise() -> None:
      """
      Maximise the current window.
      """

      machine.send_key("ctrl-meta_l-up")

      # For some reason, Lomiri in these VM tests very frequently opens the starter menu a few seconds after sending the above.
      # Because this isn't 100% reproducible all the time, and there is no command to await when OCR doesn't pick up some text,
      # the best we can do is send some Escape input after waiting some arbitrary time and hope that it works out fine.
      machine.sleep(5)
      machine.send_key("esc")
      machine.sleep(5)

    def mouse_click(xpos, ypos) -> None:
      """
      Move the mouse to a screen location and hit left-click.
      """

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

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

    def open_starter() -> None:
      """
      Open the starter, and ensure it's opened.
      """

      # Using the keybind has a chance of instantly closing the menu again? Just click the button
      mouse_click(15, 15)
  '';

  makeIndicatorTest =
    {
      name,
      left,
      ocr,
      extraCheck ? null,

      titleOcr,
    }:

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

        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";

            # Not setting wallpaper, as it breaks indicator OCR(?)
          };

        enableOCR = true;

        testScript =
          { nodes, ... }:
          (sharedTestFunctions lib)
          + ''
            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"):
                ensure_lomiri_running()

            # 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.
            mouse_click(735, 0) # the cog in the top-right, for the session indicator
            wait_for_text(${titleOcr})
            machine.screenshot("indicators_open")

            # Indicator order within the menus *should* be fixed based on per-indicator order setting
            # Session is the one we clicked, but it might not be the one we want to test right now.
            # Go as far left as necessary.
            ${lib.strings.replicate left "machine.send_key(\"left\")\n"}

            with subtest("ayatana indicator session works"):
                wait_for_text(r"(${lib.strings.concatStringsSep "|" ocr})")
                machine.screenshot("indicator_${name}")
          ''
          + lib.optionalString (extraCheck != null) extraCheck;
      }
    );

  makeIndicatorTests =
    {
      titles,
      details,
    }:
    let
      titleOcr = "r\"(${builtins.concatStringsSep "|" titles})\"";
    in
    builtins.listToAttrs (
      map (
        {
          name,
          left,
          ocr,
          extraCheck ? null,
        }:
        {
          name = "desktop-ayatana-indicator-${name}";
          value = makeIndicatorTest {
            inherit
              name
              left
              ocr
              extraCheck
              titleOcr
              ;
          };
        }
      ) details
    );
in
{
  greeter = makeTest (
    { pkgs, lib, ... }:
    {
      name = "lomiri-greeter";

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

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

          virtualisation.memorySize = 2047;

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

          services.xserver.enable = true;
          services.xserver.windowManager.icewm.enable = true;
          services.xserver.displayManager.lightdm = {
            enable = true;
            greeters.lomiri.enable = true;
          };
          services.displayManager.defaultSession = lib.mkForce "none+icewm";
        };

      enableOCR = true;

      testScript =
        { nodes, ... }:
        (sharedTestFunctions lib)
        + ''
          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"):
              ensure_greeter_launched()

              # Login
              machine.send_chars("${password}\n")
              machine.wait_for_x()
              machine.screenshot("session_launched")
        '';
    }
  );

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

      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 ];

          environment = {
            # Help with OCR
            etc."xdg/alacritty/alacritty.toml".source = (pkgs.formats.toml { }).generate "alacritty.toml" {
              font = rec {
                normal.family = "Inconsolata";
                bold.family = normal.family;
                italic.family = normal.family;
                bold_italic.family = normal.family;
                size = 16;
              };
              colors = rec {
                primary = {
                  foreground = "0x000000";
                  background = "0xffffff";
                };
                normal = {
                  green = primary.foreground;
                };
              };
            };

            etc."${wallpaperName}".source = wallpaperFile pkgs;

            systemPackages = with pkgs; [
              # Forcing alacritty to run as an X11 app when opened from the starter menu
              (symlinkJoin {
                name = "x11-${alacritty.name}";

                paths = [ alacritty ];

                nativeBuildInputs = [ makeWrapper ];

                postBuild = ''
                  wrapProgram $out/bin/alacritty \
                    --set WINIT_UNIX_BACKEND x11 \
                    --set WAYLAND_DISPLAY ""
                '';

                inherit (alacritty) meta;
              })
            ];
          };

          programs.dconf.profiles.user.databases = [
            (lomiriWallpaperDconfSettings pkgs)
          ];

          # Help with OCR
          systemd.tmpfiles.settings = {
            "10-lomiri-test-setup" = terminalOcrTmpfilesSetup { inherit pkgs lib config; };
          };
        };

      enableOCR = true;

      testScript =
        { nodes, ... }:
        (sharedTestFunctions lib)
        + ''
          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"):
              ensure_lomiri_running()

          # Working terminal keybind is good
          with subtest("terminal keybind works"):
              machine.send_key("ctrl-alt-t")
              ensure_terminal_running()
              machine.screenshot("terminal_opens")
              machine.send_key("alt-f4")

          # We want the ability to launch applications
          with subtest("starter menu works"):
              open_starter()
              machine.screenshot("starter_opens")

              # Just try the terminal again, we know that it should work
              machine.send_chars("Terminal\n")
              ensure_terminal_running()
              machine.send_key("alt-f4")

          # We want support for X11 apps
          with subtest("xwayland support works"):
              open_starter()
              machine.send_chars("Alacritty\n")
              wait_for_text(r"(${user}|machine)")
              machine.screenshot("alacritty_opens")
              machine.send_key("alt-f4")

          # Morph is how we go online
          with subtest("morph browser works"):
              open_starter()
              machine.send_chars("Morph\n")
              wait_for_text(r"(Bookmarks|address|site|visited any)")
              machine.screenshot("morph_open")

              # morph-browser has a separate VM test to test its basic functionalities

              machine.send_key("alt-f4")

          # LSS provides DE settings
          with subtest("system settings open"):
              open_starter()
              machine.send_chars("System Settings\n")
              wait_for_text("Rotation Lock")
              machine.screenshot("settings_open")

              # lomiri-system-settings has a separate VM test to test its basic functionalities

              machine.send_key("alt-f4")
        '';
    }
  );

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

      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
          programs.ydotool.enable = true;

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

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

          environment = {
            # Help with OCR
            etc."xdg/alacritty/alacritty.yml".text = lib.generators.toYAML { } {
              font = rec {
                normal.family = "Inconsolata";
                bold.family = normal.family;
                italic.family = normal.family;
                bold_italic.family = normal.family;
                size = 16;
              };
              colors = rec {
                primary = {
                  foreground = "0x000000";
                  background = "0xffffff";
                };
                normal = {
                  green = primary.foreground;
                };
              };
            };

            etc."${wallpaperName}".source = wallpaperFile pkgs;

            variables = {
              # So we can test what lomiri-content-hub is working behind the scenes
              LOMIRI_CONTENT_HUB_LOGGING_LEVEL = "2";
            };

            systemPackages = with pkgs; [
              # For a convenient way of kicking off lomiri-content-hub peer collection
              lomiri.lomiri-content-hub.examples
            ];
          };

          programs.dconf.profiles.user.databases = [
            (lomiriWallpaperDconfSettings pkgs)
          ];

          # Help with OCR
          systemd.tmpfiles.settings = {
            "10-lomiri-test-setup" = terminalOcrTmpfilesSetup { inherit pkgs lib config; };
          };
        };

      enableOCR = true;

      testScript =
        { nodes, ... }:
        (sharedTestFunctions lib)
        + ''
          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"):
              ensure_lomiri_running()

          # Working terminal keybind is good
          with subtest("terminal keybind works"):
              machine.send_key("ctrl-alt-t")
              ensure_terminal_running()
              machine.screenshot("terminal_opens")

              # for the LSS lomiri-content-hub test to work reliably, we need to kick off peer collecting
              machine.send_chars("lomiri-content-hub-test-importer\n")
              wait_for_text(r"(/build/source|hub.cpp|handler.cpp|void|virtual|const)") # awaiting log messages from lomiri-content-hub
              machine.send_key("ctrl-c")

              # 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("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.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)

              machine.send_key("alt-f4")

          # LSS provides DE settings
          with subtest("system settings open"):
              open_starter()
              machine.send_chars("System Settings\n")
              wait_for_text("Rotation Lock")
              machine.screenshot("settings_open")

              # lomiri-system-settings has a separate VM test, only test Lomiri-specific lomiri-content-hub functionalities here

              # Make fullscreen, can't navigate to Background plugin via keyboard unless window has non-phone-like aspect ratio
              toggle_maximise()

              # Load Background plugin
              machine.send_key("tab")
              machine.send_key("tab")
              machine.send_key("tab")
              machine.send_key("tab")
              machine.send_key("tab")
              machine.send_key("tab")
              machine.send_key("ret")
              wait_for_text("Background image")

              # Try to load custom background
              machine.send_key("shift-tab")
              machine.send_key("shift-tab")
              machine.send_key("shift-tab")
              machine.send_key("shift-tab")
              machine.send_key("shift-tab")
              machine.send_key("shift-tab")
              machine.send_key("ret")

              # Peers should be loaded
              wait_for_text("Gallery")
              machine.screenshot("settings_lomiri-content-hub_peers")

              # Select Gallery as content source
              mouse_click(540, 80)

              # Expect Gallery to be brought into the foreground, with its sharing page open
              wait_for_text("Photos")

              # If lomiri-content-hub encounters a problem, it may have crashed the original application issuing the request.
              # Check that it's still alive
              machine.succeed("pgrep -u ${user} -f lomiri-system-settings")

              machine.screenshot("lomiri-content-hub_exchange")

              # Testing any more would require more applications & setup, the fact that it's already being attempted is a good sign
              machine.send_key("tab")
              machine.send_key("ret")

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

  keymap =
    let
      pwInput = "qwerty";
      pwOutput = "qwertz";
    in
    makeTest (
      { pkgs, lib, ... }:
      {
        name = "lomiri-keymap";

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

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

            virtualisation.memorySize = 2047;

            users.users.${user} = {
              inherit description;
              password = lib.mkForce pwOutput;
            };

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

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

            services.xserver.xkb.layout = lib.strings.concatStringsSep "," [
              # Start with a non-QWERTY keymap to test keymap patch
              "de"
              # Then a QWERTY one to test switching
              "us"
            ];

            environment.etc."${wallpaperName}".source = wallpaperFile pkgs;

            programs.dconf.profiles.user.databases = [
              (lomiriWallpaperDconfSettings pkgs)
            ];

            # Help with OCR
            systemd.tmpfiles.settings = {
              "10-lomiri-test-setup" = terminalOcrTmpfilesSetup { inherit pkgs lib config; };
            };
          };

        enableOCR = true;

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

            # Lomiri in greeter mode should use the correct keymap
            with subtest("lomiri greeter keymap works"):
                ensure_greeter_launched()

                # Login
                machine.send_chars("${pwInput}\n")

                # And the desktop doesn't render the wallpaper anymore. Grumble grumble...
                # When going lomiri(greeter) -> lomiri(desktop), we run on tty2
                ensure_lomiri_running(2, 1)

            # Lomiri in desktop mode should use the correct keymap
            with subtest("lomiri session keymap works"):
                machine.send_key("ctrl-alt-t")
                ensure_terminal_running()
                machine.screenshot("terminal_opens")

                machine.send_chars("touch ${pwInput}\n")
                machine.wait_for_file("/home/alice/${pwOutput}", 90)

                # Issues with this keybind: input leaks to focused surface, may open launcher
                # Don't have the keyboard indicator to handle this better
                machine.send_key("meta_l-spc")
                machine.wait_for_console_text('SET KEYMAP "us"')

                # Handle keybind fallout
                machine.sleep(10) # wait for everything to settle
                machine.send_key("esc") # close launcher in case it was opened
                machine.sleep(2) # wait for animation to finish
                # Make sure input leaks are gone
                machine.send_key("backspace")
                machine.send_key("backspace")
                machine.send_key("backspace")
                machine.send_key("backspace")
                machine.send_key("backspace")
                machine.send_key("backspace")
                machine.send_key("backspace")
                machine.send_key("backspace")
                machine.send_key("backspace")
                machine.send_key("backspace")

                machine.send_chars("touch ${pwInput}\n")
                machine.wait_for_file("/home/alice/${pwInput}", 90)

                machine.send_key("alt-f4")
          '';
      }
    );
}
// makeIndicatorTests {
  titles = [
    "Notifications" # messages
    "Rotation" # display
    "Battery" # power
    "Sound" # sound
    "Time" # datetime
    "Date" # datetime
    "System" # session
  ];
  details = [
    # messages normally has no contents
    {
      name = "display";
      left = 6;
      ocr = [ "Lock" ];
    }
    {
      name = "bluetooth";
      left = 5;
      ocr = [ "Bluetooth" ];
    }
    {
      name = "network";
      left = 4;
      ocr = [
        "Flight"
        "Wi-Fi"
      ];
    }
    {
      name = "sound";
      left = 3;
      ocr = [
        "Silent"
        "Volume"
      ];
    }
    {
      name = "power";
      left = 2;
      ocr = [
        "Charge"
        "Battery"
      ];
    }
    {
      name = "datetime";
      left = 1;
      ocr = [
        "Time"
        "Date"
      ];
    }
    {
      name = "session";
      left = 0;
      ocr = [ "Log Out" ];
      extraCheck = ''
        # We should be able to log out and return to the greeter
        mouse_click(600, 250) # "Log Out"
        mouse_click(340, 220) # confirm logout
        machine.wait_until_fails("pgrep -u ${user} -f 'lomiri --mode=full-shell'")
      '';
    }
  ];
}
