# bottle_preferences.py
#
# Copyright 2025 mirkobrombin <brombin94@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, in version 3 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

import contextlib
import os
import re
from gettext import gettext as _

from gi.repository import Adw, Gdk, Gtk, Xdp

from bottles.backend.globals import (
    gamemode_available,
    gamescope_available,
    hdr_wsi_available,
    is_cpak,
    lsfg_vk_available,
    mangohud_available,
    obs_vkc_available,
    vkbasalt_available,
    vmtouch_available,
)
from bottles.backend.logger import Logger
from bottles.backend.managers.library import LibraryManager
from bottles.backend.managers.runtime import RuntimeManager
from bottles.backend.models.config import BottleConfig
from bottles.backend.models.enum import Arch
from bottles.backend.models.result import Result
from bottles.backend.runner import Runner
from bottles.backend.utils.display import DisplayUtils
from bottles.backend.utils.gpu import GPUUtils, GPUVendors
from bottles.backend.utils.manager import ManagerUtils
from bottles.backend.utils.threading import RunAsync
from bottles.backend.utils.vulkan import VulkanUtils
from bottles.backend.wine.adaptive import is_supported_runner
from bottles.backend.wine.regkeys import RegKeys
from bottles.frontend.utils.common import format_runner_name
from bottles.frontend.utils.gtk import GtkUtils
from bottles.frontend.windows.display import DisplayDialog
from bottles.frontend.windows.dlloverrides import DLLOverridesDialog
from bottles.frontend.windows.drives import DrivesDialog
from bottles.frontend.windows.envvars import EnvironmentVariablesDialog
from bottles.frontend.windows.gamescope import GamescopeDialog
from bottles.frontend.windows.lsfgvk import LsfgVkDialog
from bottles.frontend.windows.mangohud import MangoHudDialog
from bottles.frontend.windows.protonalert import ProtonAlertDialog
from bottles.frontend.windows.sandbox import SandboxDialog
from bottles.frontend.windows.vkbasalt import VkBasaltDialog
from bottles.frontend.windows.vmtouch import VmtouchDialog

logging = Logger()

FLATPAK_INSTALL_COMMANDS = {
    "gamescope": "flatpak install flathub org.freedesktop.Platform.VulkanLayer.gamescope//25.08",
    "hdr": "flatpak install flathub org.freedesktop.Platform.VulkanLayer.HdrWsi//25.08",
    "vkbasalt": "flatpak install flathub org.freedesktop.Platform.VulkanLayer.vkBasalt//25.08",
    "lsfg_vk": "flatpak install flathub org.freedesktop.Platform.VulkanLayer.lsfgvk//25.08",
    "mangohud": "flatpak install flathub org.freedesktop.Platform.VulkanLayer.MangoHud//25.08",
    "obsvkc": "flatpak install flathub org.freedesktop.Platform.VulkanLayer.OBSVkCapture//25.08",
}


# noinspection PyUnusedLocal
@Gtk.Template(resource_path="/com/usebottles/bottles/details-preferences.ui")
class PreferencesView(Adw.PreferencesPage):
    __gtype_name__ = "DetailsPreferences"

    # region Widgets
    btn_manage_gamescope = Gtk.Template.Child()
    btn_manage_vkbasalt = Gtk.Template.Child()
    btn_manage_lsfg_vk = Gtk.Template.Child()
    btn_manage_mangohud = Gtk.Template.Child()
    btn_manage_sandbox = Gtk.Template.Child()
    btn_manage_vmtouch = Gtk.Template.Child()
    btn_cwd_reset = Gtk.Template.Child()
    btn_cwd = Gtk.Template.Child()
    row_nvapi = Gtk.Template.Child()
    row_discrete = Gtk.Template.Child()
    row_vkbasalt = Gtk.Template.Child()
    row_lsfg_vk = Gtk.Template.Child()
    row_gamescope = Gtk.Template.Child()
    row_mangohud = Gtk.Template.Child()
    row_gamemode = Gtk.Template.Child()
    row_vmtouch = Gtk.Template.Child()
    row_adaptive_launch = Gtk.Template.Child()
    row_obsvkc = Gtk.Template.Child()
    row_wayland = Gtk.Template.Child()
    row_hdr = Gtk.Template.Child()
    row_winebridge = Gtk.Template.Child()
    row_manage_display = Gtk.Template.Child()
    row_runtime = Gtk.Template.Child()
    row_steam_runtime = Gtk.Template.Child()
    row_cwd = Gtk.Template.Child()
    label_cwd = Gtk.Template.Child()
    row_env_variables = Gtk.Template.Child()
    row_overrides = Gtk.Template.Child()
    row_drives = Gtk.Template.Child()
    row_sandbox = Gtk.Template.Child()
    entry_name = Gtk.Template.Child()
    switch_mangohud = Gtk.Template.Child()
    switch_obsvkc = Gtk.Template.Child()
    switch_vkbasalt = Gtk.Template.Child()
    switch_lsfg_vk = Gtk.Template.Child()
    switch_wayland = Gtk.Template.Child()
    switch_hdr = Gtk.Template.Child()
    switch_winebridge = Gtk.Template.Child()
    switch_nvapi = Gtk.Template.Child()
    switch_gamemode = Gtk.Template.Child()
    switch_gamescope = Gtk.Template.Child()
    switch_discrete = Gtk.Template.Child()
    switch_runtime = Gtk.Template.Child()
    switch_steam_runtime = Gtk.Template.Child()
    switch_sandbox = Gtk.Template.Child()
    switch_vmtouch = Gtk.Template.Child()
    switch_adaptive_launch = Gtk.Template.Child()
    combo_runner = Gtk.Template.Child()
    combo_d7vk = Gtk.Template.Child()
    combo_dxvk = Gtk.Template.Child()
    combo_vkd3d = Gtk.Template.Child()
    combo_nvapi = Gtk.Template.Child()
    combo_latencyflex = Gtk.Template.Child()
    combo_windows = Gtk.Template.Child()
    combo_language = Gtk.Template.Child()
    combo_sync = Gtk.Template.Child()
    spin_frame_rate_limit = Gtk.Template.Child()
    spinner_d7vk = Gtk.Template.Child()
    spinner_dxvk = Gtk.Template.Child()
    spinner_vkd3d = Gtk.Template.Child()
    spinner_nvapi = Gtk.Template.Child()
    spinner_nvapibool = Gtk.Template.Child()
    spinner_latencyflex = Gtk.Template.Child()
    spinner_runner = Gtk.Template.Child()
    spinner_windows = Gtk.Template.Child()
    spinner_display = Gtk.Template.Child()
    group_details = Gtk.Template.Child()
    str_list_languages = Gtk.Template.Child()
    str_list_runner = Gtk.Template.Child()
    str_list_d7vk = Gtk.Template.Child()
    str_list_dxvk = Gtk.Template.Child()
    str_list_vkd3d = Gtk.Template.Child()
    str_list_nvapi = Gtk.Template.Child()
    str_list_latencyflex = Gtk.Template.Child()
    str_list_windows = Gtk.Template.Child()

    # endregion

    def __init__(self, details, config, **kwargs):
        super().__init__(**kwargs)

        # common variables and references
        self.window = details.window
        self.manager = details.window.manager
        self.config = config
        self.queue = details.queue
        self.details = details
        self.combo_runner.set_list_factory(
            GtkUtils.create_full_width_string_list_factory()
        )

        steam_runtimes = RuntimeManager.get_runtimes("steam")
        self.row_steam_runtime.set_visible(True)
        self.switch_steam_runtime.set_sensitive(bool(steam_runtimes))
        if steam_runtimes:
            self.switch_steam_runtime.connect(
                "state-set", self.__toggle_feature_cb, "use_steam_runtime"
            )
        else:
            self.row_steam_runtime.set_subtitle(
                _(
                    "Steam Runtime was not detected. Install it or grant Bottles "
                    "access to Steam files, then restart Bottles."
                )
            )

        if not is_cpak() and not Xdp.Portal.running_under_sandbox():
            return

        _not_available = _("This feature is unavailable on your system.")
        _flatpak_not_available = _("{} To add this feature, please run").format(
            _not_available
        )
        self._install_commands = FLATPAK_INSTALL_COMMANDS

        is_flatpak = "FLATPAK_ID" in os.environ

        if not gamemode_available:
            self.switch_gamemode.set_tooltip_text(_not_available)
            self.__add_unavailable_indicator(self.row_gamemode, None)

        if not gamescope_available:
            _gamescope_command = self._install_commands.get("gamescope")
            _gamescope_not_available = (
                f"{_flatpak_not_available} {_gamescope_command}"
                if is_flatpak
                else _not_available
            )
            self.switch_gamescope.set_tooltip_text(_gamescope_not_available)
            self.btn_manage_gamescope.set_tooltip_text(_gamescope_not_available)
            self.__add_unavailable_indicator(
                self.row_gamescope, _gamescope_command if is_flatpak else None
            )

        if not vkbasalt_available:
            _vkbasalt_command = self._install_commands.get("vkbasalt")
            _vkbasalt_not_available = (
                f"{_flatpak_not_available} {_vkbasalt_command}"
                if is_flatpak
                else _not_available
            )
            self.switch_vkbasalt.set_tooltip_text(_vkbasalt_not_available)
            self.btn_manage_vkbasalt.set_tooltip_text(_vkbasalt_not_available)
            self.__add_unavailable_indicator(
                self.row_vkbasalt, _vkbasalt_command if is_flatpak else None
            )

        if not lsfg_vk_available:
            _lsfg_vk_command = self._install_commands.get("lsfg_vk")
            _lsfg_vk_not_available = (
                f"{_flatpak_not_available} {_lsfg_vk_command}"
                if is_flatpak
                else _not_available
            )
            self.switch_lsfg_vk.set_tooltip_text(_lsfg_vk_not_available)
            self.btn_manage_lsfg_vk.set_tooltip_text(_lsfg_vk_not_available)
            self.__add_unavailable_indicator(
                self.row_lsfg_vk, _lsfg_vk_command if is_flatpak else None
            )

        if not mangohud_available:
            _mangohud_command = self._install_commands.get("mangohud")
            _mangohud_not_available = (
                f"{_flatpak_not_available} {_mangohud_command}"
                if is_flatpak
                else _not_available
            )
            self.switch_mangohud.set_tooltip_text(_mangohud_not_available)
            self.btn_manage_mangohud.set_tooltip_text(_mangohud_not_available)
            self.__add_unavailable_indicator(
                self.row_mangohud, _mangohud_command if is_flatpak else None
            )

        if not obs_vkc_available:
            _obsvkc_command = self._install_commands.get("obsvkc")
            _obsvkc_not_available = (
                f"{_flatpak_not_available} {_obsvkc_command}"
                if is_flatpak
                else _not_available
            )
            self.switch_obsvkc.set_tooltip_text(_obsvkc_not_available)
            self.__add_unavailable_indicator(
                self.row_obsvkc, _obsvkc_command if is_flatpak else None
            )

        if not vmtouch_available:
            self.switch_vmtouch.set_tooltip_text(_not_available)
            self.__add_unavailable_indicator(self.row_vmtouch, None)

        is_nvidia_gpu = GPUUtils.is_gpu(GPUVendors.NVIDIA)
        if is_flatpak and is_nvidia_gpu and not hdr_wsi_available:
            _hdr_command = self._install_commands.get("hdr")
            _hdr_message = _(
                "Older NVIDIA drivers require HdrWsi and ENABLE_HDR_WSI=1 for HDR."
            )
            self.switch_hdr.set_tooltip_text(_hdr_message)
            self.__add_unavailable_indicator(
                self.row_hdr, _hdr_command, message=_hdr_message
            )

        # region signals
        self.row_manage_display.connect("activated", self.__show_display_settings)
        self.row_overrides.connect(
            "activated", self.__show_feature_dialog, DLLOverridesDialog
        )
        self.row_env_variables.connect(
            "activated", self.__show_feature_dialog, EnvironmentVariablesDialog
        )
        self.row_drives.connect("activated", self.__show_feature_dialog, DrivesDialog)
        self.btn_manage_gamescope.connect(
            "clicked", self.__show_feature_dialog, GamescopeDialog
        )
        self.btn_manage_vkbasalt.connect(
            "clicked", self.__show_feature_dialog, VkBasaltDialog
        )
        self.btn_manage_lsfg_vk.connect(
            "clicked", self.__show_feature_dialog, LsfgVkDialog
        )
        self.btn_manage_mangohud.connect(
            "clicked", self.__show_feature_dialog, MangoHudDialog
        )
        self.btn_manage_sandbox.connect(
            "clicked", self.__show_feature_dialog, SandboxDialog
        )
        self.btn_manage_vmtouch.connect(
            "clicked", self.__show_feature_dialog, VmtouchDialog
        )
        self.btn_cwd.connect("clicked", self.choose_cwd)
        self.btn_cwd_reset.connect("clicked", self.reset_cwd, True)
        self.switch_mangohud.connect("state-set", self.__toggle_feature_cb, "mangohud")
        self.switch_obsvkc.connect("state-set", self.__toggle_feature_cb, "obsvkc")
        self.switch_vkbasalt.connect("state-set", self.__toggle_feature_cb, "vkbasalt")
        self.switch_lsfg_vk.connect("state-set", self.__toggle_feature_cb, "lsfg_vk")
        self.switch_wayland.connect("state-set", self.__toggle_wayland)
        self.switch_hdr.connect("state-set", self.__toggle_hdr)
        self.switch_winebridge.connect(
            "state-set", self.__toggle_feature_cb, "winebridge"
        )
        self.switch_nvapi.connect("state-set", self.__toggle_nvapi)
        self.switch_gamemode.connect("state-set", self.__toggle_feature_cb, "gamemode")
        self.switch_gamescope.connect("state-set", self.__toggle_gamescope)
        self.switch_sandbox.connect("state-set", self.__toggle_feature_cb, "sandbox")
        self.switch_discrete.connect(
            "state-set", self.__toggle_feature_cb, "discrete_gpu"
        )
        self.switch_vmtouch.connect("state-set", self.__toggle_feature_cb, "vmtouch")
        self.switch_adaptive_launch.connect(
            "state-set", self.__toggle_feature_cb, "adaptive_launch"
        )
        self.combo_runner.connect("notify::selected", self.__set_runner)
        self.combo_d7vk.connect("notify::selected", self.__set_d7vk)
        self.combo_dxvk.connect("notify::selected", self.__set_dxvk)
        self.combo_vkd3d.connect("notify::selected", self.__set_vkd3d)
        self.combo_nvapi.connect("notify::selected", self.__set_nvapi)
        self.combo_latencyflex.connect("notify::selected", self.__set_latencyflex)
        self.combo_windows.connect("notify::selected", self.__set_windows)
        self.combo_language.connect("notify::selected-item", self.__set_language)
        self.combo_sync.connect("notify::selected", self.__set_sync_type)
        self.spin_frame_rate_limit.connect(
            "notify::value", self.__set_frame_rate_limit
        )
        self.entry_name.connect("changed", self.__check_entry_name)
        self.entry_name.connect("apply", self.__save_name)
        # endregion

        """Set DXVK_NVAPI related rows to visible when an NVIDIA GPU is detected (invisible by default)"""
        self.row_nvapi.set_visible(is_nvidia_gpu)
        self.combo_nvapi.set_visible(is_nvidia_gpu)

        """Toggle some utilities according to its availability"""
        self.switch_gamemode.set_sensitive(gamemode_available)
        self.switch_gamescope.set_sensitive(gamescope_available)
        self.btn_manage_gamescope.set_sensitive(gamescope_available)
        self.switch_vkbasalt.set_sensitive(vkbasalt_available)
        self.btn_manage_vkbasalt.set_sensitive(vkbasalt_available)
        _lsfg_vk_supported = lsfg_vk_available and config.Arch != Arch.WIN32
        self.switch_lsfg_vk.set_sensitive(_lsfg_vk_supported)
        self.btn_manage_lsfg_vk.set_sensitive(_lsfg_vk_supported)
        if config.Arch == Arch.WIN32:
            _lsfg_vk_64_bit_only = _(
                "lsfg-vk only supports 64-bit Vulkan applications."
            )
            self.switch_lsfg_vk.set_tooltip_text(_lsfg_vk_64_bit_only)
            self.btn_manage_lsfg_vk.set_tooltip_text(_lsfg_vk_64_bit_only)
        self.switch_mangohud.set_sensitive(mangohud_available)
        self.btn_manage_mangohud.set_sensitive(mangohud_available)
        self.switch_obsvkc.set_sensitive(obs_vkc_available)
        self.switch_vmtouch.set_sensitive(vmtouch_available)
        self.__update_adaptive_launch_support()

        vulkan_supported = VulkanUtils.check_support()
        if not vulkan_supported:
            self.combo_d7vk.set_sensitive(False)
            self.combo_dxvk.set_sensitive(False)
            self.combo_vkd3d.set_sensitive(False)
            self.combo_nvapi.set_sensitive(False)
            self.switch_nvapi.set_sensitive(False)
            self.combo_latencyflex.set_sensitive(False)
            self.switch_vkbasalt.set_sensitive(False)
            self.btn_manage_vkbasalt.set_sensitive(False)
            self.switch_lsfg_vk.set_sensitive(False)
            self.btn_manage_lsfg_vk.set_sensitive(False)
            self.switch_obsvkc.set_sensitive(False)

        is_wayland_session = DisplayUtils.display_server_type() == "wayland"
        self.switch_wayland.set_sensitive(is_wayland_session)
        self.__update_hdr_sensitivity()

    def __create_unavailable_popover(
        self, command: str | None, message: str | None = None
    ) -> Gtk.Popover:
        popover = Gtk.Popover()
        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
        box.set_margin_top(6)
        box.set_margin_bottom(6)
        box.set_margin_start(6)
        box.set_margin_end(6)

        unavailable_label = Gtk.Label(
            label=message or _("This feature is unavailable on your system."),
            xalign=0,
            wrap=True,
        )
        box.append(unavailable_label)

        if command:
            command_label = Gtk.Label(label=_("To add it, please run:"))
            command_label.set_xalign(0)
            box.append(command_label)

            command_box = Gtk.Box(spacing=6)
            command_box.set_hexpand(True)

            command_entry = Gtk.Entry()
            command_entry.set_editable(False)
            command_entry.set_hexpand(True)
            command_entry.set_text(command)
            command_entry.add_css_class("monospace")
            command_entry.set_width_chars(len(command))
            command_entry.set_focusable(False)

            btn_copy_command = Gtk.Button()
            btn_copy_command.set_icon_name("edit-copy-symbolic")
            btn_copy_command.set_tooltip_text(_("Copy command"))
            btn_copy_command.connect(
                "clicked", self.__copy_command_to_clipboard, command
            )

            command_box.append(command_entry)
            command_box.append(btn_copy_command)
            box.append(command_box)

        popover.set_child(box)
        return popover

    def __add_unavailable_indicator(
        self,
        row: Adw.ActionRow,
        command: str | None,
        message: str | None = None,
    ):
        if not row:
            return

        popover = self.__create_unavailable_popover(command, message)
        menu_button = Gtk.MenuButton()
        menu_button.set_valign(Gtk.Align.CENTER)
        menu_button.set_icon_name("dialog-warning-symbolic")
        menu_button.set_has_frame(False)
        menu_button.set_popover(popover)
        menu_button.set_tooltip_text(
            message or _("This feature is unavailable on your system.")
        )

        row.add_suffix(menu_button)
        return menu_button

    def __copy_command_to_clipboard(self, _widget, command: str):
        display = Gdk.Display.get_default()
        if not display:
            return

        clipboard = Gdk.Display.get_clipboard(display)
        clipboard.set_content(Gdk.ContentProvider.new_for_value(command))
        self.window.show_toast(_("Copied to clipboard"))

    def __check_entry_name(self, *_args):
        if self.entry_name.get_text() != self.config.Name:
            is_duplicate = self.entry_name.get_text() in self.manager.local_bottles
            if is_duplicate:
                self.window.show_toast(_("This bottle name is already in use."))
                self.__valid_name = False
                self.entry_name.add_css_class("error")
                return
        self.__valid_name = True
        self.entry_name.remove_css_class("error")

    def __save_name(self, *_args):
        if not self.__valid_name:
            self.entry_name.set_text(self.config.Name)
            self.__valid_name = True
            return

        new_name = self.entry_name.get_text()
        old_name = self.config.Name

        library_manager = LibraryManager()
        entries = library_manager.get_library()

        for uuid, entry in entries.items():
            bottle = entry.get("bottle") or {}
            if bottle.get("name") == old_name:
                logging.info(f"Updating library entry for {entry.get('name')}")
                entries[uuid]["bottle"]["name"] = new_name
                break

        library_manager.__library = entries
        library_manager.save_library()

        self.manager.update_config(config=self.config, key="Name", value=new_name)

        self.manager.update_bottles(silent=True)  # Updates backend bottles list and UI
        self.window.page_library.update()
        self.details.view_bottle.label_name.set_text(self.config.Name)

    def choose_cwd(self, widget):
        def set_path(_dialog, response):
            if response != Gtk.ResponseType.ACCEPT:
                return

            path = dialog.get_file().get_path()

            self.manager.update_config(
                config=self.config, key="WorkingDir", value=dialog.get_file().get_path()
            )
            self.__update_working_directory_row(path)

        dialog = Gtk.FileChooserNative.new(
            title=_("Select Working Directory"),
            action=Gtk.FileChooserAction.SELECT_FOLDER,
            parent=self.window,
        )

        dialog.set_modal(True)
        dialog.connect("response", set_path)
        dialog.show()

    def reset_cwd(self, *_args):
        self.manager.update_config(config=self.config, key="WorkingDir", value="")
        self.__update_working_directory_row()

    def __update_working_directory_row(self, working_dir=None):
        """Update the working directory."""

        working_dir = working_dir if working_dir is not None else self.config.WorkingDir
        has_custom_dir = bool(working_dir)

        if has_custom_dir:
            basename = os.path.basename(os.path.normpath(working_dir)) or working_dir
            self.label_cwd.set_label(basename)
            self.label_cwd.set_tooltip_text(working_dir)
        else:
            self.label_cwd.set_label(_("(Default)"))
            self.label_cwd.set_tooltip_text(None)

        self.btn_cwd_reset.set_visible(has_custom_dir)

    def update_combo_components(self):
        """
        This function update the components' combo boxes with the
        items in the manager catalogs. It also temporarily disable
        the functions connected to the combo boxes to avoid the
        bottle configuration to be updated during the process.
        """
        self.combo_runner.handler_block_by_func(self.__set_runner)
        self.combo_d7vk.handler_block_by_func(self.__set_d7vk)
        self.combo_dxvk.handler_block_by_func(self.__set_dxvk)
        self.combo_vkd3d.handler_block_by_func(self.__set_vkd3d)
        self.combo_nvapi.handler_block_by_func(self.__set_nvapi)
        self.combo_latencyflex.handler_block_by_func(self.__set_latencyflex)
        self.combo_language.handler_block_by_func(self.__set_language)
        self.combo_windows.handler_block_by_func(self.__set_windows)

        for string_list in [
            self.str_list_runner,
            self.str_list_d7vk,
            self.str_list_dxvk,
            self.str_list_vkd3d,
            self.str_list_nvapi,
            self.str_list_latencyflex,
            self.str_list_languages,
            self.str_list_windows,
        ]:
            string_list.splice(0, string_list.get_n_items())

        self.str_list_d7vk.append(_("Disabled"))
        self.str_list_dxvk.append(_("Disabled"))
        self.str_list_vkd3d.append(_("Disabled"))
        self.str_list_latencyflex.append(_("Disabled"))
        for d7vk in self.manager.d7vk_available:
            self.str_list_d7vk.append(d7vk)

        for index, dxvk in enumerate(self.manager.dxvk_available):
            self.str_list_dxvk.append(dxvk)

        for index, vkd3d in enumerate(self.manager.vkd3d_available):
            self.str_list_vkd3d.append(vkd3d)

        for index, runner in enumerate(self.manager.runners_available):
            self.str_list_runner.append(format_runner_name(runner))

        for index, nvapi in enumerate(self.manager.nvapi_available):
            self.str_list_nvapi.append(nvapi)

        for index, latencyflex in enumerate(self.manager.latencyflex_available):
            self.str_list_latencyflex.append(latencyflex)

        for lang in ManagerUtils.get_languages():
            self.str_list_languages.append(lang)

        self.combo_runner.handler_unblock_by_func(self.__set_runner)
        self.combo_d7vk.handler_unblock_by_func(self.__set_d7vk)
        self.combo_dxvk.handler_unblock_by_func(self.__set_dxvk)
        self.combo_vkd3d.handler_unblock_by_func(self.__set_vkd3d)
        self.combo_nvapi.handler_unblock_by_func(self.__set_nvapi)
        self.combo_latencyflex.handler_unblock_by_func(self.__set_latencyflex)
        self.combo_language.handler_unblock_by_func(self.__set_language)
        self.combo_windows.handler_unblock_by_func(self.__set_windows)

    def set_config(self, config: BottleConfig):
        self.config = config
        parameters = self.config.Parameters

        # temporary lock functions connected to the widgets
        self.switch_mangohud.handler_block_by_func(self.__toggle_feature_cb)
        self.switch_nvapi.handler_block_by_func(self.__toggle_nvapi)
        self.switch_vkbasalt.handler_block_by_func(self.__toggle_feature_cb)
        self.switch_lsfg_vk.handler_block_by_func(self.__toggle_feature_cb)
        self.switch_wayland.handler_block_by_func(self.__toggle_wayland)
        self.switch_hdr.handler_block_by_func(self.__toggle_hdr)
        self.switch_winebridge.handler_block_by_func(self.__toggle_feature_cb)
        self.switch_obsvkc.handler_block_by_func(self.__toggle_feature_cb)
        self.switch_gamemode.handler_block_by_func(self.__toggle_feature_cb)
        self.switch_adaptive_launch.handler_block_by_func(self.__toggle_feature_cb)
        self.switch_gamescope.handler_block_by_func(self.__toggle_gamescope)
        self.switch_sandbox.handler_block_by_func(self.__toggle_feature_cb)
        self.switch_discrete.handler_block_by_func(self.__toggle_feature_cb)
        with contextlib.suppress(TypeError):
            self.switch_steam_runtime.handler_block_by_func(self.__toggle_feature_cb)
        self.combo_runner.handler_block_by_func(self.__set_runner)
        self.combo_d7vk.handler_block_by_func(self.__set_d7vk)
        self.combo_dxvk.handler_block_by_func(self.__set_dxvk)
        self.combo_vkd3d.handler_block_by_func(self.__set_vkd3d)
        self.combo_nvapi.handler_block_by_func(self.__set_nvapi)
        self.combo_latencyflex.handler_block_by_func(self.__set_latencyflex)
        self.combo_windows.handler_block_by_func(self.__set_windows)
        self.combo_language.handler_block_by_func(self.__set_language)
        self.spin_frame_rate_limit.handler_block_by_func(self.__set_frame_rate_limit)
        self.switch_mangohud.set_active(parameters.mangohud)
        self.switch_obsvkc.set_active(parameters.obsvkc)
        self.switch_vkbasalt.set_active(parameters.vkbasalt)
        self.switch_lsfg_vk.set_active(parameters.lsfg_vk)
        _lsfg_vk_supported = (
            lsfg_vk_available
            and config.Arch != Arch.WIN32
            and VulkanUtils.check_support()
        )
        self.switch_lsfg_vk.set_sensitive(_lsfg_vk_supported)
        self.btn_manage_lsfg_vk.set_sensitive(_lsfg_vk_supported)
        self.switch_wayland.set_active(parameters.wayland)
        self.switch_hdr.set_active(parameters.hdr)
        self.__update_hdr_sensitivity(parameters.hdr)
        self.switch_winebridge.set_active(parameters.winebridge)
        self.switch_nvapi.set_active(parameters.dxvk_nvapi)
        self.switch_gamemode.set_active(parameters.gamemode)
        self.switch_gamescope.set_active(parameters.gamescope)
        self.switch_sandbox.set_active(parameters.sandbox)
        self.switch_steam_runtime.set_active(parameters.use_steam_runtime)
        self.switch_vmtouch.set_active(parameters.vmtouch)
        self.switch_adaptive_launch.set_active(parameters.adaptive_launch)
        self.spin_frame_rate_limit.set_value(parameters.frame_rate_limit)

        # self.toggle_sync.set_active(parameters["sync"] == "wine")
        # self.toggle_esync.set_active(parameters["sync"] == "esync")
        # self.toggle_fsync.set_active(parameters["sync"] == "fsync")

        self.switch_discrete.set_active(parameters.discrete_gpu)

        self.__update_working_directory_row()

        self.entry_name.set_text(config.Name)

        self.row_cwd.set_subtitle(
            _('Directory that contains the data of "{}".'.format(config.Name))
        )

        self.combo_language.set_selected(
            ManagerUtils.get_languages(from_locale=self.config.Language, get_index=True)
        )

        # region Windows Versions
        # NOTE: this should not be here but it's the only way to handle windows
        # versions in the current structure, we will fix this in the future
        # with the new Bottles Backend.
        self.windows_versions = {
            "win11": "Windows 11",
            "win10": "Windows 10",
            "win81": "Windows 8.1",
            "win8": "Windows 8",
            "win7": "Windows 7",
            "win2008r2": "Windows 2008 R2",
            "win2008": "Windows 2008",
            "vista": "Windows Vista",
            "winxp": "Windows XP",
        }

        if self.config.Arch == Arch.WIN32:
            self.windows_versions["win98"] = "Windows 98"
            self.windows_versions["win95"] = "Windows 95"

        for index, windows_version in enumerate(self.windows_versions):
            self.str_list_windows.append(self.windows_versions[windows_version])
            if windows_version == self.config.Windows:
                self.combo_windows.set_selected(index)
        # endregion

        parameters = self.config.Parameters

        _d7vk = self.config.D7VK
        if parameters.d7vk:
            if _d7vk in self.manager.d7vk_available:
                self.combo_d7vk.set_selected(
                    self.manager.d7vk_available.index(_d7vk) + 1
                )
        else:
            self.combo_d7vk.set_selected(0)

        _dxvk = self.config.DXVK
        if parameters.dxvk:
            if _dxvk in self.manager.dxvk_available:
                if _i_dxvk := self.manager.dxvk_available.index(_dxvk) + 1:
                    self.combo_dxvk.set_selected(_i_dxvk)
        else:
            self.combo_dxvk.set_selected(0)

        _vkd3d = self.config.VKD3D
        if parameters.vkd3d:
            if _vkd3d in self.manager.vkd3d_available:
                if _i_vkd3d := self.manager.vkd3d_available.index(_vkd3d) + 1:
                    self.combo_vkd3d.set_selected(_i_vkd3d)
        else:
            self.combo_vkd3d.set_selected(0)

        _nvapi = self.config.NVAPI
        if _nvapi in self.manager.nvapi_available:
            if _i_nvapi := self.manager.nvapi_available.index(_nvapi):
                self.combo_nvapi.set_selected(_i_nvapi)

        _latencyflex = self.config.LatencyFleX
        if parameters.latencyflex:
            if _latencyflex in self.manager.latencyflex_available:
                if (
                    _i_latencyflex := self.manager.latencyflex_available.index(
                        _latencyflex
                    )
                    + 1
                ):
                    self.combo_latencyflex.set_selected(_i_latencyflex)
        else:
            self.combo_latencyflex.set_selected(0)

        _runner = self.config.Runner
        if _runner in self.manager.runners_available:
            _i_runner = self.manager.runners_available.index(_runner)
            self.combo_runner.set_selected(_i_runner)
        elif self.manager.runners_available:
            _fallback_runner = self.manager.runners_available[0]
            self.combo_runner.set_selected(0)
            self.config = self.manager.update_config(
                config=self.config, key="Runner", value=_fallback_runner
            ).data["config"]

        sync_types = [
            "wine",
            "esync",
            "fsync",
            "ntsync",
        ]
        for sync in sync_types:
            if sync == parameters.sync:
                self.combo_sync.set_selected(sync_types.index(sync))

        # unlock functions connected to the widgets
        self.switch_mangohud.handler_unblock_by_func(self.__toggle_feature_cb)
        self.switch_nvapi.handler_unblock_by_func(self.__toggle_nvapi)
        self.switch_vkbasalt.handler_unblock_by_func(self.__toggle_feature_cb)
        self.switch_lsfg_vk.handler_unblock_by_func(self.__toggle_feature_cb)
        self.switch_wayland.handler_unblock_by_func(self.__toggle_wayland)
        self.switch_hdr.handler_unblock_by_func(self.__toggle_hdr)
        self.switch_winebridge.handler_unblock_by_func(self.__toggle_feature_cb)
        self.switch_obsvkc.handler_unblock_by_func(self.__toggle_feature_cb)
        self.switch_gamemode.handler_unblock_by_func(self.__toggle_feature_cb)
        self.switch_adaptive_launch.handler_unblock_by_func(self.__toggle_feature_cb)
        self.switch_gamescope.handler_unblock_by_func(self.__toggle_gamescope)
        self.switch_sandbox.handler_unblock_by_func(self.__toggle_feature_cb)
        self.switch_discrete.handler_unblock_by_func(self.__toggle_feature_cb)
        with contextlib.suppress(TypeError):
            self.switch_steam_runtime.handler_unblock_by_func(self.__toggle_feature_cb)
        self.combo_runner.handler_unblock_by_func(self.__set_runner)
        self.combo_d7vk.handler_unblock_by_func(self.__set_d7vk)
        self.combo_dxvk.handler_unblock_by_func(self.__set_dxvk)
        self.combo_vkd3d.handler_unblock_by_func(self.__set_vkd3d)
        self.combo_nvapi.handler_unblock_by_func(self.__set_nvapi)
        self.combo_latencyflex.handler_unblock_by_func(self.__set_latencyflex)
        self.combo_windows.handler_unblock_by_func(self.__set_windows)
        self.combo_language.handler_unblock_by_func(self.__set_language)
        self.spin_frame_rate_limit.handler_unblock_by_func(self.__set_frame_rate_limit)

        self.__set_steam_rules()
        self.__update_adaptive_launch_support()

    def __update_adaptive_launch_support(self) -> None:
        supported = is_supported_runner(self.config.Runner)
        message = (
            ""
            if supported
            else _("Soda 11.0-5 or newer is required to enable Adaptive Launch.")
        )
        if not hasattr(self, "_adaptive_launch_warning"):
            self._adaptive_launch_warning = self.__add_unavailable_indicator(
                self.row_adaptive_launch,
                None,
                _("Soda 11.0-5 or newer is required to enable Adaptive Launch."),
            )
        self._adaptive_launch_warning.set_visible(not supported)
        self.switch_adaptive_launch.set_sensitive(supported)
        self.switch_adaptive_launch.set_tooltip_text(message)
        self.row_adaptive_launch.set_tooltip_text(message)

    def __show_display_settings(self, widget):
        new_window = DisplayDialog(
            parent_window=self.window,
            config=self.config,
            details=self.details,
            queue=self.queue,
            widget=widget,
            spinner_display=self.spinner_display,
        )
        new_window.present()

    def __show_feature_dialog(self, _widget: Gtk.Widget, dialog: Adw.Window) -> None:
        """Present dialog of a specific feature."""
        window = dialog(window=self.window, config=self.config)
        window.present()

    def __toggle_feature(self, state: bool, key: str) -> None:
        """Toggle a specific feature."""
        self.config = self.manager.update_config(
            config=self.config, key=key, value=state, scope="Parameters"
        ).data["config"]

    def __toggle_feature_cb(self, _widget: Gtk.Widget, state: bool, key: str) -> None:
        self.__toggle_feature(state=state, key=key)

    def __toggle_wayland(self, _widget: Gtk.Widget, state: bool) -> None:
        self.__toggle_feature(state=state, key="wayland")
        self.__update_hdr_sensitivity()
        rk = RegKeys(self.config)
        RunAsync(rk.toggle_wayland_driver, state=state)

    def __toggle_hdr(self, _widget: Gtk.Widget, state: bool) -> None:
        self.__toggle_feature(state=state, key="hdr")
        self.__update_hdr_sensitivity(state)

    def __toggle_gamescope(self, _widget: Gtk.Widget, state: bool) -> None:
        self.__toggle_feature(state=state, key="gamescope")
        self.__update_hdr_sensitivity()

    def __update_hdr_sensitivity(self, active: bool | None = None) -> None:
        if active is None:
            active = self.config.Parameters.hdr
        self.switch_hdr.set_sensitive(
            active
            or (
                VulkanUtils.check_support()
                and (
                    (
                        DisplayUtils.display_server_type() == "wayland"
                        and self.config.Parameters.wayland
                    )
                    or (gamescope_available and self.config.Parameters.gamescope)
                )
            )
        )

    def __set_sync_type(self, *_args):
        """
        Set the sync type (wine, esync, fsync, ntsync)
        """
        sync_types = [
            "wine",
            "esync",
            "fsync",
            "ntsync",
        ]
        self.queue.add_task()
        self.combo_sync.set_sensitive(False)
        RunAsync(
            self.manager.update_config,
            config=self.config,
            key="sync",
            value=sync_types[self.combo_sync.get_selected()],
            scope="Parameters",
        )
        self.combo_sync.set_sensitive(True)
        self.queue.end_task()

    def __set_frame_rate_limit(self, spin_row, _pspec):
        value = int(spin_row.get_value())
        if value == self.config.Parameters.frame_rate_limit:
            return

        self.config = self.manager.update_config(
            config=self.config,
            key="frame_rate_limit",
            value=value,
            scope="Parameters",
        ).data["config"]

    def __toggle_nvapi(self, widget=False, state=False):
        """Install/Uninstall NVAPI from the bottle"""
        self.queue.add_task()
        self.set_nvapi_status(pending=True)

        RunAsync(
            task_func=self.manager.install_dll_component,
            callback=self.set_nvapi_status,
            config=self.config,
            component="nvapi",
            remove=not state,
        )

        self.__toggle_feature(state=state, key="dxvk_nvapi")

    def __set_runner(self, *_args):
        """Set the runner to use for the bottle"""

        def set_widgets_status(status=True):
            for w in [
                self.combo_runner,
                self.switch_nvapi,
                self.combo_d7vk,
                self.combo_dxvk,
                self.combo_nvapi,
                self.combo_vkd3d,
            ]:
                w.set_sensitive(status)
            if status:
                self.spinner_runner.stop()
                self.spinner_runner.set_visible(False)
            else:
                self.spinner_runner.start()
                self.spinner_runner.set_visible(True)

        @GtkUtils.run_in_main_loop
        def update(result: Result[dict], error=False):
            if isinstance(result, Result) and isinstance(
                result.data, dict
            ):  # expecting Result[dict].data["config"]
                self.details.update_runner_label(runner)

                if "config" in result.data:
                    self.config = result.data["config"]
                self.__update_adaptive_launch_support()
                if self.config.Parameters.use_steam_runtime:
                    self.switch_steam_runtime.handler_block_by_func(
                        self.__toggle_feature_cb
                    )
                    self.switch_steam_runtime.set_active(True)
                    self.switch_steam_runtime.handler_unblock_by_func(
                        self.__toggle_feature_cb
                    )

            set_widgets_status(True)
            self.queue.end_task()

        runner = self.manager.runners_available[self.combo_runner.get_selected()]

        def run_task(status=True):
            if not status:
                update(Result(True))
                self.combo_runner.handler_block_by_func(self.__set_runner)
                self.combo_runner.handler_unblock_by_func(self.__set_runner)
                return

            self.queue.add_task()
            RunAsync(
                Runner.runner_update,
                callback=update,
                config=self.config,
                manager=self.manager,
                runner=runner,
            )

        def apply_runner():
            set_widgets_status(False)
            if re.search("^(GE-)?Proton", runner):
                dialog = ProtonAlertDialog(self.window, run_task)
                dialog.show()
            else:
                run_task()

        if self.config.Parameters.hidraw_devices and "soda" not in runner.lower():
            dialog = Adw.MessageDialog.new(
                self.window,
                _("HIDRAW support is not guaranteed"),
                _(
                    "Selective HIDRAW access is tested with Soda. The new "
                    "runner may ignore it or behave differently. Continue anyway?"
                ),
            )
            dialog.add_response("cancel", _("_Cancel"))
            dialog.add_response("continue", _("_Continue"))
            dialog.set_response_appearance(
                "continue", Adw.ResponseAppearance.SUGGESTED
            )
            dialog.set_default_response("cancel")
            dialog.set_close_response("cancel")

            def on_response(_dialog, response):
                if response == "continue":
                    apply_runner()
                    return

                self.combo_runner.handler_block_by_func(self.__set_runner)
                if self.config.Runner in self.manager.runners_available:
                    self.combo_runner.set_selected(
                        self.manager.runners_available.index(self.config.Runner)
                    )
                self.combo_runner.handler_unblock_by_func(self.__set_runner)

            dialog.connect("response", on_response)
            dialog.present()
            return

        apply_runner()

    def __dll_component_task_func(self, *args, **kwargs):
        # Remove old version
        self.manager.install_dll_component(
            config=kwargs["config"], component=kwargs["component"], remove=True
        )
        # Install new version
        self.manager.install_dll_component(
            config=kwargs["config"], component=kwargs["component"]
        )

    def __set_d7vk(self, *_args):
        """Set the D7VK version to use for the bottle"""
        self.set_d7vk_status(pending=True)
        self.queue.add_task()
        selected = self.combo_d7vk.get_selected()
        version = self.manager.d7vk_available[selected - 1] if selected else None
        RunAsync(
            task_func=self.manager.set_d7vk,
            callback=self.set_d7vk_status,
            config=self.config,
            enabled=selected != 0,
            version=version,
        )

    def __set_dxvk(self, *_args):
        """Set the DXVK version to use for the bottle"""
        self.set_dxvk_status(pending=True)
        self.queue.add_task()

        if (self.combo_dxvk.get_selected()) == 0:
            self.set_dxvk_status(pending=True)

            if self.combo_vkd3d.get_selected() != 0:
                logging.info("VKD3D is enabled, disabling")
                self.combo_vkd3d.set_selected(0)

            RunAsync(
                task_func=self.manager.install_dll_component,
                callback=self.set_dxvk_status,
                config=self.config,
                component="dxvk",
                remove=True,
            )

            self.config = self.manager.update_config(
                config=self.config, key="dxvk", value=False, scope="Parameters"
            ).data["config"]
        else:
            dxvk = self.manager.dxvk_available[self.combo_dxvk.get_selected() - 1]
            self.config = self.manager.update_config(
                config=self.config, key="DXVK", value=dxvk
            ).data["config"]

            RunAsync(
                task_func=self.__dll_component_task_func,
                callback=self.set_dxvk_status,
                config=self.config,
                component="dxvk",
            )

            self.config = self.manager.update_config(
                config=self.config, key="dxvk", value=True, scope="Parameters"
            ).data["config"]

    def __set_vkd3d(self, *_args):
        """Set the VKD3D version to use for the bottle"""
        self.set_vkd3d_status(pending=True)
        self.queue.add_task()

        if (self.combo_vkd3d.get_selected()) == 0:
            self.set_vkd3d_status(pending=True)

            RunAsync(
                task_func=self.manager.install_dll_component,
                callback=self.set_vkd3d_status,
                config=self.config,
                component="vkd3d",
                remove=True,
            )

            self.config = self.manager.update_config(
                config=self.config, key="vkd3d", value=False, scope="Parameters"
            ).data["config"]
        else:
            if self.combo_dxvk.get_selected() == 0:
                logging.info("DXVK is disabled, reenabling")
                self.combo_dxvk.set_selected(1)

            vkd3d = self.manager.vkd3d_available[self.combo_vkd3d.get_selected() - 1]
            self.config = self.manager.update_config(
                config=self.config, key="VKD3D", value=vkd3d
            ).data["config"]

            RunAsync(
                task_func=self.__dll_component_task_func,
                callback=self.set_vkd3d_status,
                config=self.config,
                component="vkd3d",
            )

            self.config = self.manager.update_config(
                config=self.config, key="vkd3d", value=True, scope="Parameters"
            ).data["config"]

    def __set_nvapi(self, *_args):
        """Set the NVAPI version to use for the bottle"""
        self.set_nvapi_status(pending=True)
        self.queue.add_task()

        self.switch_nvapi.set_active(True)

        nvapi = self.manager.nvapi_available[self.combo_nvapi.get_selected()]
        self.config = self.manager.update_config(
            config=self.config, key="NVAPI", value=nvapi
        ).data["config"]

        RunAsync(
            task_func=self.__dll_component_task_func,
            callback=self.set_nvapi_status,
            config=self.config,
            component="nvapi",
        )

        self.config = self.manager.update_config(
            config=self.config, key="dxvk_nvapi", value=True, scope="Parameters"
        ).data["config"]

    def __set_latencyflex(self, *_args):
        """Set the latency flex value"""
        self.queue.add_task()
        if self.combo_latencyflex.get_selected() == 0:
            RunAsync(
                task_func=self.manager.install_dll_component,
                callback=self.set_latencyflex_status,
                config=self.config,
                component="latencyflex",
                remove=True,
            )

            self.config = self.manager.update_config(
                config=self.config, key="latencyflex", value=False, scope="Parameters"
            ).data["config"]
        else:
            latencyflex = self.manager.latencyflex_available[
                self.combo_latencyflex.get_selected() - 1
            ]
            self.config = self.manager.update_config(
                config=self.config, key="LatencyFleX", value=latencyflex
            ).data["config"]

            RunAsync(
                task_func=self.__dll_component_task_func,
                callback=self.set_latencyflex_status,
                config=self.config,
                component="latencyflex",
            )
            self.config = self.manager.update_config(
                config=self.config, key="latencyflex", value=True, scope="Parameters"
            ).data["config"]

    def __set_windows(self, *_args):
        """Set the Windows version to use for the bottle"""

        # self.manager.dxvk_available[self.combo_dxvk.get_selected()]
        @GtkUtils.run_in_main_loop
        def update(result, error=False):
            self.spinner_windows.stop()
            self.spinner_windows.set_visible(False)
            self.combo_windows.set_sensitive(True)
            self.queue.end_task()

        self.queue.add_task()
        self.spinner_windows.start()
        self.spinner_windows.set_visible(True)
        self.combo_windows.set_sensitive(False)
        rk = RegKeys(self.config)

        for index, windows_version in enumerate(self.windows_versions):
            if self.combo_windows.get_selected() == index:
                self.config = self.manager.update_config(
                    config=self.config, key="Windows", value=windows_version
                ).data["config"]

                RunAsync(rk.lg_set_windows, callback=update, version=windows_version)
                break

    def __set_language(self, *_args):
        """Set the language to use for the bottle"""
        index = self.combo_language.get_selected()
        language = ManagerUtils.get_languages(from_index=index)
        self.config = self.manager.update_config(
            config=self.config,
            key="Language",
            value=language[0],
        ).data["config"]

    @GtkUtils.run_in_main_loop
    def set_dxvk_status(self, status=None, error=None, pending=False):
        """Set the dxvk status"""
        self.combo_dxvk.set_sensitive(not pending)
        if pending:
            self.spinner_dxvk.start()
            self.spinner_dxvk.set_visible(True)
        else:
            self.spinner_dxvk.stop()
            self.spinner_dxvk.set_visible(False)
            self.queue.end_task()

    @GtkUtils.run_in_main_loop
    def set_d7vk_status(self, status=None, error=None, pending=False):
        """Set the D7VK status"""
        self.combo_d7vk.set_sensitive(not pending)
        if pending:
            self.spinner_d7vk.start()
            self.spinner_d7vk.set_visible(True)
        else:
            if isinstance(status, Result) and status.ok and status.data:
                self.config = status.data["config"]
            elif error or not isinstance(status, Result) or not status.ok:
                self.combo_d7vk.handler_block_by_func(self.__set_d7vk)
                selected = 0
                if (
                    self.config.Parameters.d7vk
                    and self.config.D7VK in self.manager.d7vk_available
                ):
                    selected = self.manager.d7vk_available.index(self.config.D7VK) + 1
                self.combo_d7vk.set_selected(selected)
                self.combo_d7vk.handler_unblock_by_func(self.__set_d7vk)
            self.spinner_d7vk.stop()
            self.spinner_d7vk.set_visible(False)
            self.queue.end_task()

    @GtkUtils.run_in_main_loop
    def set_vkd3d_status(self, status=None, error=None, pending=False):
        """Set the vkd3d status"""
        self.combo_vkd3d.set_sensitive(not pending)
        if pending:
            self.spinner_vkd3d.start()
            self.spinner_vkd3d.set_visible(True)
        else:
            self.spinner_vkd3d.stop()
            self.spinner_vkd3d.set_visible(False)
            self.queue.end_task()

    @GtkUtils.run_in_main_loop
    def set_nvapi_status(self, status=None, error=None, pending=False):
        """Set the nvapi status"""
        self.switch_nvapi.set_sensitive(not pending)
        self.combo_nvapi.set_sensitive(not pending)
        if pending:
            self.spinner_nvapi.start()
            self.spinner_nvapibool.start()
            self.spinner_nvapi.set_visible(True)
            self.spinner_nvapibool.set_visible(True)
        else:
            self.spinner_nvapi.stop()
            self.spinner_nvapibool.stop()
            self.spinner_nvapi.set_visible(False)
            self.spinner_nvapibool.set_visible(False)
            self.queue.end_task()

    @GtkUtils.run_in_main_loop
    def set_latencyflex_status(self, status=None, error=None, pending=False):
        """Set the latencyflex status"""
        self.combo_latencyflex.set_sensitive(not pending)
        if pending:
            self.spinner_latencyflex.start()
            self.spinner_latencyflex.set_visible(True)
        else:
            self.spinner_latencyflex.stop()
            self.spinner_latencyflex.set_visible(False)
            self.queue.end_task()

    def __set_steam_rules(self):
        """Set the Steam Environment specific rules"""
        status = self.config.Environment != "Steam"

        for w in [
            self.row_discrete,
            self.combo_dxvk,
            self.row_sandbox,
            self.group_details,
        ]:
            w.set_visible(status)
            w.set_sensitive(status)
