#!/usr/bin/env python3
"""
QECore "headless" script that takes care of starting the session in wanted configuration.
"""

import os
import errno
import subprocess
import traceback
from subprocess import check_output, STDOUT, CalledProcessError
import time
import argparse
import configparser
import shutil
import sys
import re


ENVIRONMENT_VARIABLES_TO_PRESERVE = [
    "PYTHONPATH",
    "TEST",
    "TERM",
    "QECORE_DEBUG",
    "QECORE_EMBED_ALL",
    "LOGGING",
    "GNOME_ACCESSIBILITY",
]


def run(command) -> str:
    """
    Utility function to execute given command and return its output.
    """

    try:
        output = check_output(command, shell=True, env=os.environ, stderr=STDOUT, encoding="utf-8")
        return output.strip("\n")
    except CalledProcessError as error:
        return error.output


def run_verbose(command) -> tuple:
    """
    Utility function to execute given command and return its output.
    """

    try:
        output = check_output(command, shell=True, env=os.environ, stderr=STDOUT, encoding="utf-8")
        return (output.strip("\n"), 0, None)
    except CalledProcessError as error:
        return (error.output, error.returncode, error)


def get_environment_dictionary() -> dict:
    """
    Targetting the gnome-session-binary process to get all environment variables.
    Returns environment as dictionary.
    """

    environment_dictionary = {}

    user_id = str(os.geteuid())
    gnome_session_binary_pid = None

    try:
        gnome_session_binary_pid = run(f"pgrep -u {user_id} gnome-session").split("\n")[0]
    except CalledProcessError:
        traceback_message = traceback.format_exc()
        print(f"headless: Failed to retrieve gnome-session-binary pid from pgrep:\n{traceback_message}")

    # Use the gnome-session-binary pid to get its environment variables.
    if gnome_session_binary_pid:
        environment_process_path = f"/proc/{gnome_session_binary_pid}/environ"

        # Verify that the environ file can be opened and load environment variables to dictionary.
        try:
            with open(environment_process_path, "r", encoding="utf-8") as environ_file:
                for item in environ_file.read().split("\x00"):
                    if "=" in item:
                        key, value = item.split("=", 1)
                        environment_dictionary[key] = value
        except IOError as error:
            print(f"headless: Environment file manipulation failed on: '{error}'")

        # Preserving wanted environment variables.
        for environment_variable in ENVIRONMENT_VARIABLES_TO_PRESERVE:
            if environment_variable in os.environ:
                environment_dictionary[environment_variable] = os.environ[environment_variable]

        # Set TERM as xterm.
        print("headless: Setting environment variable TERM as 'xterm'")
        environment_dictionary["TERM"] = "xterm"

        return environment_dictionary

    print("headless: Environment file not found, most likely caused by gdm not running.")
    return environment_dictionary


def is_binary_existing_and_executable(path) -> bool:
    """
    Test if given binary file exists.
    """

    if (
        path.startswith(os.path.sep)
        or path.startswith(os.path.join(".", ""))
        or path.startswith(os.path.join("..", ""))
    ):
        if not os.path.exists(path):
            raise IOError(errno.ENOENT, "No such file", path)

        if not os.access(path, os.X_OK):
            raise IOError(errno.ENOEXEC, "Permission denied", path)

    return True


def troubleshoot() -> None:
    """
    Test parts of the system for correct configuration.
    """

    # Troubleshooting gnome-session-binary upon failure.
    user_id = str(os.geteuid())
    gnome_session_binary_pid = run(f"pgrep -u {user_id} gnome-session").split("\n")[0]
    ps_command = "ps ax -o uid,gid,pid,tname,command | grep gnome-session"
    ps_command_result = run(ps_command)
    error_message = "".join(
        (
            "Target file for environment is '/usr/libexec/gnome-session-binary'",
            f"\nSearch was done with 'pgrep -u {user_id} gnome-session'. ",
            f"With result '{str(gnome_session_binary_pid)}'",
            "\nActual running processes:",
            f"\n$ {ps_command}",
            f"\n{str(ps_command_result)}",
        )
    )
    print(f"headless: Troubleshooting gnome-session-binary:\n{error_message}\n")

    # Troubleshooting dbus upon failure.
    dbus_processes_command = "ps ax -o uid,gid,pid,tname,command | grep dbus"
    dbus_processes_result = run(dbus_processes_command)
    print(f"headless: Troubleshooting dbus:\n$ {dbus_processes_command}\n{dbus_processes_result}\n")

    # Troubleshooting at-spi processes that are required.
    print("headless: Troubleshooting AT-SPI processes:")
    at_spi_command = "ps ax -o uid,gid,pid,tname,command | grep at-spi"
    at_spi_result = run(at_spi_command)
    print(
        "".join(
            (
                "Expecting running processes to be:\n",
                "'/usr/libexec/at-spi-bus-launcher'\n",
                "'/usr/libexec/at-spi2-registryd'\n",
                "\nActual running processes:",
                f"\n$ {at_spi_command}",
                f"\n{at_spi_result}\n",
            )
        )
    )

    # Troubleshooting important environment variables.
    print("headless: Troubleshooting Environment variables:")
    display = os.getenv("DISPLAY")
    xauthority = os.getenv("XAUTHORITY")
    dbus_session_bus_address = os.getenv("DBUS_SESSION_BUS_ADDRESS")
    print(
        "\n".join(
            (
                f"DISPLAY = '{display}'",
                f"XAUTHORITY = '{xauthority}'",
                f"DBUS_SESSION_BUS_ADDRESS = '{dbus_session_bus_address}'\n",
            )
        )
    )


def verify_file_ownership() -> None:
    """
    Verify ownership of the dconf file.
    """

    user_id = os.geteuid()
    file_owner_command = f"sudo stat -c '%U %G' /run/user/{user_id}/dconf/user"

    if not os.path.isfile(f"/run/user/{user_id}/dconf/user"):
        return

    file_owner_command_result = run_verbose(file_owner_command)

    if file_owner_command_result[1] == 0 and "root" in file_owner_command_result[0]:
        print("headless: Attempting to restore dconf file ownership.")
        run(f"sudo rm -rf /run/user/{user_id}/dconf/user")
    elif file_owner_command_result[1] != 0:
        print(f"headless: Issue was detected and might need attention - {file_owner_command_result}")


def parse():
    """
    Parser for arguments given to the script.

    :return: _description_
    :rtype: _type_
    """

    parser = argparse.ArgumentParser(
        prog="$ qecore-headless",
        description="Adjusted headless script.",
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )
    parser.add_argument(
        "script",
        nargs="?",
        default="bash",
        help="Script to be executed, if not provided 'bash' will be used.",
    )
    parser.add_argument(
        "--session-type",
        required=False,
        choices=("xorg", "wayland"),
        help="Choose which session type will be used.",
    )
    parser.add_argument(
        "--session-desktop",
        required=False,
        choices=(
            "gnome",
            "gnome-classic",
            "gnome-classic-wayland",
            "gnome-kiosk-script",
        ),
        help="Choose which session desktop will be used.",
    )
    parser.add_argument(
        "--display",
        required=False,
        help="Number of the DISPLAY to connect to - default value is ':0'",
    )
    parser.add_argument(
        "--dont-start",
        required=False,
        action="store_true",
        help="Use the system as is. Does not have to be under display manager",
    )
    parser.add_argument(
        "--dont-kill",
        required=False,
        action="store_true",
        help="Do not kill the session when script exits.",
    )
    parser.add_argument(
        "--restart",
        required=False,
        action="store_true",
        help="Restart previously running display manager session before script execution.",
    )
    parser.add_argument(
        "--keep", required=False, help="Number of tests to keep gdm running"
    )
    parser.add_argument(
        "--keep-max",
        required=False,
        action="store_true",
        help="Keep gdm running 'maximum number of tests' times. This equals --dont-kill parameter.",
    )
    parser.add_argument(
        "--disable-a11y",
        required=False,
        action="store_true",
        help="Disable accessibility technologies on script (not session) exit.",
    )
    parser.add_argument(
        "--force",
        required=False,
        action="store_true",
        help="Will check if the wanted protocol was used. Exit upon fail.",
    )
    parser.add_argument(
        "--debug",
        required=False,
        action="store_true",
        help="Will print debug messages to the file.",
    )
    return parser.parse_args()


class DisplayManager:
    """
    Display Manager class.
    """

    def __init__(
        self,
        session_type=None,
        session_desktop=None,
        enable_start=True,
        enable_stop=True,
        gdm_restart=False,
    ) -> None:
        self.enable_start = enable_start
        self.enable_stop = enable_stop
        self.gdm_restart = gdm_restart

        self.display_manager = "gdm"
        # Session type is xorg, wayland, None -> respect setting of the system.
        self.session_type = session_type
        # Session desktop is gnome gnome-classic, None -> respect setting of the system.
        self.session_desktop = session_desktop
        self.session_started_indicator = "/usr/libexec/gnome-session-binary"

        self.user = run("whoami")
        self.user_id = run(f"id -u {self.user}")

        self.config_file = "/etc/gdm/custom.conf"
        self.temporary_config_file = f"/tmp/{os.path.basename(self.config_file)}"

        # Debugging a11y randomly turning off.
        self.debug_a11y_file = "/usr/lib/systemd/user/org.gnome.SettingsDaemon.A11ySettings.service"
        self.temporary_debug_a11y_file = f"/tmp/{os.path.basename(self.debug_a11y_file)}"

        self.unsafe_mode_config_file = "/usr/lib/systemd/user/org.gnome.Shell@wayland.service"
        self.temporary_unsafe_mode_config_file = f"/tmp/{os.path.basename(self.unsafe_mode_config_file)}"

        self.restart_needed = False

    def restore_config(self) -> None:
        """
        Restore configuration file.
        Not used, but implemented if needed.
        """

        shutil.copy(self.config_file, self.temporary_config_file)

        config_parser = configparser.ConfigParser()
        config_parser.optionxform = str
        config_parser.read(self.temporary_config_file)

        config_parser.remove_option("daemon", "AutomaticLoginEnable")
        config_parser.remove_option("daemon", "AutomaticLogin")
        config_parser.remove_option("daemon", "WaylandEnable")

        with open(self.temporary_config_file, "w", encoding="utf-8") as _file:
            config_parser.write(_file)

    def handling_config_setup(self) -> None:
        """
        Handling config setup
        """

        run(f"cp -f {self.config_file} {self.temporary_config_file}")

        config_parser = configparser.ConfigParser()
        # Default option returns lower-case, setting this will make it case sensitive.
        config_parser.optionxform = str
        try:
            config_parser.read(self.temporary_config_file)
        except configparser.Error:
            error_message = "headless: Unable to parse '/etc/gdm/custom.conf'."
            error_message += "\n\n" + str(traceback.format_exc())
            error_message += "\nContent of '/etc/gdm/custom.conf' file:\n"
            error_message += run(f"cat {self.temporary_config_file}")
            error_message += "\n\nheadless: Exiting headless."
            print(error_message)
            sys.exit(1)

        if not config_parser.has_section("daemon"):  # Section does not exist.
            config_parser.add_section("daemon")

        config_parser.set("daemon", "AutomaticLoginEnable", "true")
        config_parser.set("daemon", "AutomaticLogin", self.user)

        if self.session_type == "xorg":
            config_parser.set("daemon", "WaylandEnable", "false")

        elif self.session_type == "wayland":
            config_parser.set("daemon", "WaylandEnable", "true")

        # Respecting system setting, get the session that is to be started.
        elif self.session_type is None:
            if "WaylandEnable" in config_parser.options("daemon"):
                # Set xorg only if there is WaylandEnable=false.
                # Wayland should be default everywhere.
                self.session_type = (
                    "xorg"
                    if config_parser.get("daemon", "WaylandEnable") == "false"
                    else "wayland"
                )
            # "WaylandEnable" not in config_parser.options("daemon").
            else:
                self.session_type = "wayland"
                config_parser.set("daemon", "WaylandEnable", "true")

        else:
            print("headless: This is not acceptable session type. Fallback to the 'xorg' session type")
            print("headless: Acceptable names for --session-type: ['xorg', 'wayland']")
            self.session_type = "xorg"
            config_parser.set("daemon", "WaylandEnable", "false")

        with open(self.temporary_config_file, "w", encoding="utf-8") as _file:
            config_parser.write(_file)

        if not os.path.isfile(self.temporary_config_file):
            print("headless: Temporary config file was not found, waiting a bit...")
            time.sleep(1)

        run(f"sudo mv -f {self.temporary_config_file} {self.config_file}")
        run(f"sudo rm -f {self.temporary_config_file}")

    def handling_account_setup(self) -> None:
        """
        Handling account setup
        """

        # Get all defined desktop file name for xorg and wayland.
        acceptable_x_desktop_names = run("ls /usr/share/xsessions").split("\n")
        acceptable_wayland_desktop_names = run("ls /usr/share/wayland-sessions").split("\n")
        acceptable_desktop_file_names = None

        # Get acceptable desktop file names for xorg.
        if self.session_type == "xorg":
            acceptable_desktop_file_names = [
                x.strip("desktop").strip(".") for x in acceptable_x_desktop_names if x
            ]

        # Get aceptable desktop file names for wayland.
        elif self.session_type == "wayland":
            acceptable_desktop_file_names = [
                x.strip("desktop").strip(".")
                for x in acceptable_wayland_desktop_names
                if x
            ]

        # Get initial values that we work with.
        interface = "".join((
            "org.freedesktop.Accounts ",
            f"/org/freedesktop/Accounts/User{self.user_id} ",
            "org.freedesktop.Accounts.User",
        ))
        saved_session_desktop = run(f"busctl get-property {interface} Session")
        saved_xsession_desktop = run(f"busctl get-property {interface} XSession")

        # Handling result from get-property. Making sure they are equal.
        saved_session_desktop = saved_session_desktop[3:-1]
        saved_xsession_desktop = saved_xsession_desktop[3:-1]
        if saved_session_desktop == "":
            run(f"busctl call {interface} SetSession 's' '{saved_xsession_desktop}'")
            saved_session_desktop = saved_xsession_desktop
        elif saved_xsession_desktop == "":
            run(f"busctl call {interface} SetXSession 's' '{saved_session_desktop}'")
            saved_xsession_desktop = saved_session_desktop

        # Chosen desktop differs from current one.
        if (
            self.session_desktop not in (saved_session_desktop, None)
            and self.session_desktop in acceptable_desktop_file_names
        ):
            print(f"headless: Changing desktop '{saved_session_desktop}' -> '{self.session_desktop}'")
            run(f"busctl call {interface} SetSession 's' '{self.session_desktop}'")
            run(f"busctl call {interface} SetXSession 's' '{self.session_desktop}'")
            self.restart_needed = True

        # Chosing desktop not found in acceptable desktop file names.
        elif (
            self.session_desktop is not None
            and self.session_desktop not in acceptable_desktop_file_names
        ):
            print("headless: This is not acceptable session desktop name. Fallback to the 'gnome' session desktop")
            print(f"headless: Acceptable names for '{self.session_type}': {acceptable_desktop_file_names}")
            run(f"busctl call {interface} SetSession 's' 'gnome'")
            run(f"busctl call {interface} SetXSession 's' 'gnome'")
            self.restart_needed = True

        if self.restart_needed:
            print("headless: Restart required")
            run("sudo systemctl restart accounts-daemon")
            run("sudo systemctl restart systemd-logind")

    def handling_debug_accessibility_setup(self) -> None:
        """
        Handling debug accessibility setup.
        """

        # If there is no debug file do not attempt to set it.
        if not os.path.isfile(self.debug_a11y_file):
            return

        run(f"cp -f {self.debug_a11y_file} {self.temporary_debug_a11y_file}")

        config_parser = configparser.ConfigParser()
        # Default option returns lower-case, setting this will make it case sensitive.
        config_parser.optionxform = str
        config_parser.read(self.temporary_debug_a11y_file)

        if not config_parser.has_section("Service"):
            config_parser.add_section("Service")

        config_parser.set("Service", "Environment", '"G_MESSAGES_DEBUG=a11y-settings-plugin"')

        # Write the data to temporary file.
        with open(self.temporary_debug_a11y_file, "w", encoding="utf-8") as _file:
            config_parser.write(_file)

        if not os.path.isfile(self.temporary_debug_a11y_file):
            print("headless: Temporary config file was not found, waiting a bit...")
            time.sleep(1)

        # Moving the file to its destination and removing the temporary one.
        run(f"sudo mv -f {self.temporary_debug_a11y_file} {self.debug_a11y_file}")
        run(f"sudo rm -f {self.temporary_debug_a11y_file}")

        print("headless: Enabling G_MESSAGES_DEBUG for accessibility.")

    def handling_unsafe_mode_setup(self, use_unsafe_mode=True) -> None:
        """
        Handling unsafe mode setup.

        :param use_unsafe_mode: Using unsafe mode, defaults to True.
        :type use_unsafe_mode: bool, optional
        """

        # Handling unsafe mode setup.
        if not os.path.isfile(self.unsafe_mode_config_file):
            # File is not present on rhel-8 so it will not be executed.
            return

        run(f"cp -f {self.unsafe_mode_config_file} {self.temporary_unsafe_mode_config_file}")

        config_parser = configparser.ConfigParser()
        config_parser.optionxform = str
        config_parser.read(self.temporary_unsafe_mode_config_file)

        if (
            use_unsafe_mode
            and config_parser.get("Service", "ExecStart")
            != "/usr/bin/gnome-shell --unsafe-mode"
        ):
            print("headless: Using gnome-shell --unsafe-mode under Wayland.")
            config_parser.set("Service", "ExecStart", "/usr/bin/gnome-shell --unsafe-mode")

        elif (
            not use_unsafe_mode
            and config_parser.get("Service", "ExecStart") != "/usr/bin/gnome-shell"
        ):
            config_parser.set("Service", "ExecStart", "/usr/bin/gnome-shell")

        # No change required.
        else:
            run(f"sudo rm -f {self.temporary_unsafe_mode_config_file}")
            return

        with open(self.temporary_unsafe_mode_config_file, "w", encoding="utf-8") as _file:
            config_parser.write(_file)

        run(f"sudo mv -f {self.temporary_unsafe_mode_config_file} {self.unsafe_mode_config_file}")
        run(f"sudo rm -f {self.temporary_unsafe_mode_config_file}")

    def start_display_manager(self) -> None:
        """
        Starting the display manager - gdm.
        """

        # Stop gdm only if requested by user or required by config change, continue
        # using active one othewise.
        if self.gdm_restart or self.restart_needed:
            self.stop_display_manager()

        list_of_systemd_processes = (
            run(f"pgrep -u {os.geteuid()} -f '/usr/lib/systemd/systemd --user'")
            .strip("\n")
            .split("\n")
        )
        number_of_systemd_processes = len(list_of_systemd_processes)

        if number_of_systemd_processes > 1:
            print("".join((
                "headless: multiple instances of '/usr/lib/systemd/systemd --user' ",
                "detected - trying to recover...",
            )))

            # Stop the running session first - important for 'keep' option.
            self.stop_display_manager()

            for pid in list_of_systemd_processes:
                run(f"sudo kill -9 {pid}")

            # Prevent any race when cleaning systemd processes and starting gdm.
            time.sleep(1)

        # Start gdm if gdm is not active already.
        is_display_manager_active = run("systemctl is-active gdm").strip("\n")
        if is_display_manager_active != "active":
            print("headless: Starting Display Manager")
            run("sudo systemctl start gdm")
            time.sleep(4)

        # But the session must be running.
        if not self.wait_until_process_is_running(self.session_started_indicator):
            print("headless: Running session indicator not detected running - restart required")
            print("headless: Attempt to restore headless - stopping gdm")
            self.stop_display_manager()
            print("headless: Attempt to restore headless - starting gdm")
            run("sudo systemctl start gdm")
            time.sleep(4)

    def stop_display_manager(self) -> None:
        """
        Stopping the display manager - gdm.
        """

        print("headless: Stopping Display Manager")
        run("sudo systemctl stop gdm")

        # Dumb sleep to prevent any kind of races.
        time.sleep(3)

        self.wait_until_process_is_not_running(self.session_started_indicator)

        # Failsave, the session should end, but if not, send SIGTERM and give it time to close.
        still_open_session = run_verbose(f"sudo loginctl | grep {self.user} | grep seat0")
        # Check return code.
        if still_open_session[1] == 0:
            os.system("sudo loginctl")

            still_open_session_number = (
                run(f"sudo loginctl | grep {self.user} | grep seat0")
                .strip(" ")
                .split(" ")[0]
            )
            print("".join((
                "headless: session did not end after 'systemctl stop gdm', ",
                f"sending SIGTERM to '{still_open_session_number}'",
            )))
            run(f"sudo loginctl kill-session --signal=15 {still_open_session_number}")
            time.sleep(1)

            # If something goes wrong the system might hang up to 90 seconds waiting
            # for the service to die.
            for counter in range(100):
                user_still_logged_in = run_verbose(f"loginctl show-user {self.user} --property=State")
                if user_still_logged_in[0].strip("\n") == "State=closing":
                    time.sleep(1)

                elif user_still_logged_in[0].strip("\n") == "State=active":
                    print("headless: session did not end after sending SIGTERM and is still active")
                    break

                # Failed to get user: User ID 1000 is not logged in or lingering.
                elif user_still_logged_in[1] == 1:
                    print(f"headless: session ended after SIGTERM in '{counter}' seconds.")
                    break

        # Leftover gdm process that seems to mess everything up.
        still_open_gdm_session = run_verbose("sudo loginctl | grep gdm")

        # Check return code.
        if still_open_gdm_session[1] == 0:
            os.system("sudo loginctl")

            still_open_gdm_session_number = run("sudo loginctl | grep gdm").strip(" ").split(" ")[0]
            print(f"headless: session still open, sending SIGTERM to gdm login '{still_open_gdm_session_number}'")
            run(f"sudo loginctl kill-session --signal=15 {still_open_gdm_session_number}")
            time.sleep(1)

    @staticmethod
    def is_process_running(process_to_find) -> bool:
        """
        Is process running helper function.

        :param process_to_find: Process to find.
        :type process_to_find: str

        :return: Process is running.
        :rtype: bool
        """

        active_processes = run("ps axw").split("\n")
        for active_process in active_processes:
            if re.search(process_to_find, str(active_process).lower()):
                return True
        return False

    def wait_until_process_is_running(self, process_to_find) -> bool:
        """
        Waiting until the process is running function.

        :param process_to_find: Process to find.
        :type process_to_find: str

        :return: The process was found before the 30 second timeout.
        :rtype: bool
        """

        for _ in range(60):
            if not self.is_process_running(process_to_find):
                time.sleep(0.5)
            else:
                return True
        return False

    def wait_until_process_is_not_running(self, process_to_find) -> None:
        """
        Waiting until the process is NOT running function.

        :param process_to_find: Process to NOT find.
        :type process_to_find: str

        :return: The process was NOT found in the 30 second timeout.
        :rtype: bool
        """

        for _ in range(60):
            if self.is_process_running(process_to_find):
                time.sleep(0.5)
            else:
                break


class Headless:
    """
    Headless class.
    """

    def __init__(self) -> None:
        self.display_manager_control = None
        self.environment_control = None
        self.script_control = None

        self.arguments = None
        self.script_as_list_of_arguments = ""

        self.enable_start = True
        self.enable_stop = True

        self.gdm_restart = False

        self.disable_accessibility_on_script_exit = None

        self.force = None
        self.session_type = None
        self.session_desktop = None

        self.user_script_process = None
        self.user_script_exit_code = None

        self.display_number = ":0"

    @staticmethod
    def set_accessibility_to(enable_accessibility) -> None:
        """
        Using simple gsettings command to enable or disable toolkit-accessibility.
        """

        set_accessibility_value = "true" if enable_accessibility else "false"

        gsetting_get_command = " ".join(
            (
                "dbus-run-session gsettings get",
                "org.gnome.desktop.interface",
                "toolkit-accessibility",
            )
        )

        gsetting_set_command = " ".join(
            (
                "dbus-run-session gsettings set",
                "org.gnome.desktop.interface",
                f"toolkit-accessibility {set_accessibility_value}",
            )
        )

        accessibility_value = run(gsetting_get_command)
        if accessibility_value != set_accessibility_value:
            print(f"headless: Changing a11y value from '{accessibility_value}' to '{set_accessibility_value}'")
            run(gsetting_set_command)

    @staticmethod
    def adjust_gsettings_values() -> None:
        """
        Using simple gsettings command to adjust values of delay, repeat and repeat-interval.
        """

        user = run("whoami")
        scheme = "org.gnome.desktop.peripherals.keyboard"

        gsetting_get_delay_command = f"sudo -Hu {user} gsettings get {scheme} delay"
        gsetting_set_delay_command = f"sudo -Hu {user} gsettings set {scheme} delay 'uint32 500'"

        gsetting_get_repeat_command = f"sudo -Hu {user} gsettings get {scheme} repeat"
        gsetting_set_repeat_command = f"sudo -Hu {user} gsettings set {scheme} repeat true"

        gsetting_get_repeat_interval_command = f"sudo -Hu {user} gsettings get {scheme} repeat-interval"
        gsetting_set_repeat_interval_command = f"sudo -Hu {user} gsettings set {scheme} repeat-interval 'uint32 30'"

        delay_result = run(gsetting_get_delay_command)
        repeat_result = run(gsetting_get_repeat_command)
        repeat_interval_result = run(gsetting_get_repeat_interval_command)

        if delay_result != "uint32 500":
            run(gsetting_set_delay_command)
            print(f"Value of gsettings delay was '{delay_result}' - changing to 'uint32 500'")

        if repeat_result != "true":
            run(gsetting_set_repeat_command)
            print(f"Value of gsettings repeat was '{repeat_result}' - changing to 'true'")

        if repeat_interval_result != "uint32 30":
            run(gsetting_set_repeat_interval_command)
            print(f"Value of gsettings repeat-interval was '{repeat_interval_result}' - changing to 'uint32 30'")

    def handle_keep_logic(self, keep_value) -> None:
        """
        Handling keep logic.

        :param keep_value: String form of an integer expressing how many test will be
            run in a single session before restart.
        :type keep_value: str
        """

        keep_file = "/tmp/qecore_keep"
        keep_from_argument = int(keep_value)

        try:
            with open(keep_file, "r", encoding="utf-8") as _file:
                keep_from_file = int(_file.read())
        except OSError:
            keep_from_file = 1

        self.enable_stop = False

        if keep_from_file == 1:
            self.gdm_restart = True

        if keep_from_file >= keep_from_argument:
            self.enable_stop = True
            keep_from_file = 0

        with open(keep_file, "w", encoding="utf-8") as _file:
            _file.write(f"{keep_from_file + 1}")

    def handle_arguments(self) -> None:
        """
        Makes all neccessary steps for arguments passed along the headless script.
        """

        # Workaround for firefox with bugged accessibility which will sometimes turn off.
        os.environ["GNOME_ACCESSIBILITY"] = "1"

        # Parse arguments of headless.
        self.arguments = parse()

        # Parse arguments of given script.
        self.script_as_list_of_arguments = self.arguments.script.split()

        # Handle headless debug variable.
        if self.arguments.debug:
            os.environ["DOGTAIL_DEBUG"] = "true"

        # Handle keep argument, check value of /tmp/qecore_keep.
        if self.arguments.keep:
            self.handle_keep_logic(self.arguments.keep)

        # Handle display number.
        if self.arguments.display:
            self.display_number = self.arguments.display

        # Handle headless don't start variable.
        if self.arguments.dont_start:
            self.enable_start = False

        # Handle headless don't kill variable.
        # Handle headless keep max variable, which is just do not kill variable.
        if self.arguments.dont_kill or self.arguments.keep_max:
            self.enable_stop = False

        # Handle headless restart variable.
        if self.arguments.restart:
            self.gdm_restart = True

        # Handle headless disable a11y variable.
        if self.arguments.disable_a11y:
            self.disable_accessibility_on_script_exit = True

        # Handle headless force variable.
        if self.arguments.force:
            self.force = True

        # Handle session type variable.
        if self.arguments.session_type:
            self.session_type = self.arguments.session_type

        # Handle session desktop variable.
        if self.arguments.session_desktop:
            self.session_desktop = self.arguments.session_desktop

    def set_display_number(self) -> None:
        """
        Retrieve information about running process and prints it before user script start.
        """

        os.environ["DISPLAY"] = self.display_number
        print(f"headless: Setting DISPLAY variable to '{self.display_number}'.")

    def check_what_desktop_and_type_is_running(self) -> None:
        """
        Retrieve information about running process and prints it before user script start.
        """

        if not self.enable_start:
            return

        error_list = []
        try:
            current_type = (
                "xorg"
                if os.environ["XDG_SESSION_TYPE"] == "x11"
                else os.environ["XDG_SESSION_TYPE"]
            )
        except KeyError as error:
            error_list.append(error)
            current_type = "__unavailable__"

        try:
            current_desktop = os.environ["XDG_SESSION_DESKTOP"]
        except KeyError as error:
            error_list.append(error)
            current_desktop = "__unavailable__"

        print(f"headless: Running '{current_type}' with desktop '{current_desktop}'")

        if error_list:
            print(f"headless: Error detected when loading environment variables: '{error_list}'")

    def verify_that_correct_session_was_started(self) -> None:
        """
        Verifies that correct session type as started, terminate on mismatch.
        """

        if not self.enable_start:
            return

        current_type = (
            "xorg"
            if os.environ["XDG_SESSION_TYPE"] == "x11"
            else os.environ["XDG_SESSION_TYPE"]
        )
        if (
            self.display_manager_control.session_type
            and self.display_manager_control.session_type != current_type
        ):
            print(
                "".join(
                    (
                        "headless: Script requires session of type: ",
                        f"'{self.display_manager_control.session_type}'\n",
                        "headless: Script was started under session of type: ",
                        f"'{current_type}'\n",
                    )
                )
            )
            print("Exitting the headless script.")
            sys.exit(1)

        current_desktop = os.environ["XDG_SESSION_DESKTOP"]
        if (
            self.display_manager_control.session_desktop
            and self.display_manager_control.session_desktop != current_desktop
        ):
            print(
                "".join(
                    (
                        "headless: Script requires session with desktop: ",
                        f"'{self.display_manager_control.session_desktop}'\n",
                        "headless: Script was started under session with desktop: ",
                        f"'{current_desktop}'\n",
                    )
                )
            )
            print("Exitting the headless script.")
            sys.exit(1)

    def execute(self) -> None:
        """
        Makes all neccessary preparations for the system to start gdm and execute user script.
        """

        # Arguments handling.
        self.handle_arguments()

        # Display manager setup and handling.
        self.display_manager_control = DisplayManager(
            session_type=self.session_type,
            session_desktop=self.session_desktop,
            enable_start=self.enable_start,
            enable_stop=self.enable_stop,
            gdm_restart=self.gdm_restart,
        )
        self.display_manager_control.handling_config_setup()
        self.display_manager_control.handling_account_setup()

        # Debugging a11y randomly turning off.
        self.display_manager_control.handling_debug_accessibility_setup()

        if self.display_manager_control.session_type == "wayland":
            self.display_manager_control.handling_unsafe_mode_setup(
                use_unsafe_mode=True
            )
        else:  # self.display_manager_control.session_type == "xorg":
            self.display_manager_control.handling_unsafe_mode_setup(
                use_unsafe_mode=False
            )

        # Required setup that needs to go through in its entirety or it will fail.
        if self.enable_start or self.display_manager_control.restart_needed:
            self.display_manager_control.start_display_manager()

        # Environment handling.
        environment_dictionary = get_environment_dictionary()
        if environment_dictionary:
            os.environ = environment_dictionary
        else:
            print(f"headless: Display Manager Status is: '{run('systemctl status gdm')}'")
            troubleshoot()

            self.display_manager_control.stop_display_manager()
            sys.exit(1)

        # Force xorg/wayland setting - terminate upon error.
        if self.force:
            self.verify_that_correct_session_was_started()

        # Check xorg/wayland setting - print what the test will run under.
        self.check_what_desktop_and_type_is_running()

        # Set DISPLAY number.
        self.set_display_number()

        # Accessibility - has to be started after gdm/dbus is running.
        self.set_accessibility_to(True)

        # Make a gsettings that sometimes get set too high and is uncomfortable to work with.
        self.adjust_gsettings_values()

        # User script handling.
        if is_binary_existing_and_executable(self.script_as_list_of_arguments[0]):
            self.user_script_process = subprocess.Popen(
                self.script_as_list_of_arguments, env=os.environ
            )
            print(f"headless: Started the script with PID {self.user_script_process.pid}")

            self.user_script_exit_code = self.user_script_process.wait()
            print(f"headless: The user script finished with return code {self.user_script_exit_code}")

        # Disable accessibility upon script exit.
        if self.disable_accessibility_on_script_exit:
            self.set_accessibility_to(False)

        # Stop display manager unless user specifies otherwise.
        if self.enable_stop:
            self.display_manager_control.stop_display_manager()


def main():
    """
    Main function.
    """

    starting_point = time.time()
    print("headless: Starting the timer for the headless script.")

    # Headless is not supposed to be run under user root.
    if os.geteuid() == 0:
        print("headless: Script is not meant to be run under the root user.")
        print("Exitting the headless script.")
        sys.exit(1)

    # There is a bug in which the root takes ownership of dconf file. So far we did not
    # discover the cause. We can easily fix this by deleting the file, upon session
    # start new file with correct owner is created.
    verify_file_ownership()

    headless = Headless()
    headless.execute()

    ending_point = time.time()
    print(f"headless: The headless script ran for {(ending_point - starting_point):.2f} seconds.")
    sys.exit(headless.user_script_exit_code)


if __name__ == "__main__":
    main()
