diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index 24e3ac55ed3..133b6ad92a9 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -12,7 +12,7 @@ jobs: with: fetch-depth: 0 - name: Set up Python 3.10 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.10" - name: Install Ubuntu dependencies diff --git a/.github/workflows/pylint-checker.yml b/.github/workflows/pylint-checker.yml index 8ac323ad61a..b76c5840fc7 100644 --- a/.github/workflows/pylint-checker.yml +++ b/.github/workflows/pylint-checker.yml @@ -9,7 +9,7 @@ jobs: with: fetch-depth: 0 - name: Set up Python 3.10 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.10" - name: Install Ubuntu dependencies @@ -31,7 +31,7 @@ jobs: python3 utils/pylint-parser.py > output/pylint-result cat output/pylint-result echo ${{ github.event.number }} > output/pr-number - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: pylint-result path: output/ diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 89c61e8c44c..043f0b57b58 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -9,7 +9,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.x' - name: Install Ubuntu dependencies diff --git a/.github/workflows/update-manifest.yml b/.github/workflows/update-manifest.yml index f3aceaac90a..44d88339843 100644 --- a/.github/workflows/update-manifest.yml +++ b/.github/workflows/update-manifest.yml @@ -10,6 +10,7 @@ on: env: PR_BRANCH: pr/ci-manifest/${{ github.ref_name }} + FEDC_ARGS: --update --require-important-update --commit-only --never-fork "bottles-repository/com.usebottles.bottles.yml" jobs: update-manifest: @@ -20,7 +21,7 @@ jobs: path: "bottles-repository" ref: ${{ github.ref_name }} - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.x' cache: 'pip' @@ -35,7 +36,7 @@ jobs: git config user.name "github-actions[bot]" pur -r requirements.txt pur -r requirements.dev.txt - req2flatpak --requirements-file requirements.txt --yaml --target-platforms 310-x86_64 -o com.usebottles.bottles.pypi-deps.yaml + req2flatpak --requirements-file requirements.txt --yaml --target-platforms 311-x86_64 -o com.usebottles.bottles.pypi-deps.yaml git diff ${{ github.ref_name }} --exit-code requirements.txt requirements.dev.txt com.usebottles.bottles.pypi-deps.yaml updated=$? if [ $updated -ne 0 ]; then @@ -43,6 +44,12 @@ jobs: git commit -m "Update PyPI dependencies" fi + - name: Update arguments + if: github.event_name == 'workflow_dispatch' + run: | + remove_important_update_only=$(sed 's/--require-important-update//g' <<< '${{ env.FEDC_ARGS }}') + echo "FEDC_ARGS=$remove_important_update_only" >> $GITHUB_ENV + - uses: docker://ghcr.io/flathub/flatpak-external-data-checker:latest env: GIT_AUTHOR_NAME: github-actions[bot] @@ -52,7 +59,7 @@ jobs: EMAIL: github-actions[bot]@users.noreply.github.com GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - args: --update --require-important-update --commit-only --never-fork "bottles-repository/com.usebottles.bottles.yml" + args: ${{ env.FEDC_ARGS }} - name: Create PR if necessary working-directory: "bottles-repository" diff --git a/README.md b/README.md index 6c9f6d4d601..6f02e2d0b5b 100644 --- a/README.md +++ b/README.md @@ -20,9 +20,6 @@
- - - diff --git a/VERSION b/VERSION index c7b934fb6e3..1c4fafa0cf3 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -51.9 +51.11 diff --git a/bottles/backend/cabextract.py b/bottles/backend/cabextract.py index 313b8fdbaef..4fcf3702361 100644 --- a/bottles/backend/cabextract.py +++ b/bottles/backend/cabextract.py @@ -33,6 +33,7 @@ class CabExtract: extracts the file in a new directory with the input name under the Bottles' temp directory. """ + requirements: bool = False path: str name: str @@ -42,7 +43,13 @@ class CabExtract: def __init__(self): self.cabextract_bin = shutil.which("cabextract") - def run(self, path: str, name: str = "", files: Optional[list] = None, destination: str = ""): + def run( + self, + path: str, + name: str = "", + files: Optional[list] = None, + destination: str = "", + ): if files is None: files = [] @@ -70,10 +77,10 @@ def __extract(self) -> bool: try: if len(self.files) > 0: for file in self.files: - ''' + """ if file already exists as a symlink, remove it preventing broken symlinks - ''' + """ if os.path.exists(os.path.join(self.destination, file)): if os.path.islink(os.path.join(self.destination, file)): os.unlink(os.path.join(self.destination, file)) @@ -82,27 +89,27 @@ def __extract(self) -> bool: self.cabextract_bin, f"-F '*{file}*'", f"-d {self.destination}", - f"-q {self.path}" + f"-q {self.path}", ] command = " ".join(command) - subprocess.Popen( - command, - shell=True - ).communicate() + subprocess.Popen(command, shell=True).communicate() if len(file.split("/")) > 1: _file = file.split("/")[-1] _dir = file.replace(_file, "") if not os.path.exists(f"{self.destination}/{_file}"): - shutil.move(f"{self.destination}/{_dir}/{_file}", f"{self.destination}/{_file}") + shutil.move( + f"{self.destination}/{_dir}/{_file}", + f"{self.destination}/{_file}", + ) else: command_list = [ self.cabextract_bin, f"-d {self.destination}", - f"-q {self.path}" + f"-q {self.path}", ] command = " ".join(command_list) - subprocess.Popen(command, shell=True).communicate() + subprocess.Popen(command, shell=True).communicate() logging.info(f"Cabinet {self.name} extracted successfully") return True diff --git a/bottles/backend/diff.py b/bottles/backend/diff.py index ef977717fd0..1952c31771c 100644 --- a/bottles/backend/diff.py +++ b/bottles/backend/diff.py @@ -7,12 +7,8 @@ class Diff: This class is no more used by the application, it's just a reference for future implementations. """ - __ignored = [ - "dosdevices", - "users", - "bottle.yml", - "storage" - ] + + __ignored = ["dosdevices", "users", "bottle.yml", "storage"] @staticmethod def hashify(path: str) -> dict: @@ -26,10 +22,10 @@ def hashify(path: str) -> dict: _files = {} if path[-1] != os.sep: - ''' + """ Be sure to add a trailing slash at the end of the path to prevent the correct path name in the result. - ''' + """ path += os.sep for root, dirs, files in os.walk(path): @@ -71,8 +67,4 @@ def compare(parent: dict, child: dict) -> dict: elif parent[f] != child[f]: changed.append(f) - return { - "added": added, - "removed": removed, - "changed": changed - } + return {"added": added, "removed": removed, "changed": changed} diff --git a/bottles/backend/dlls/dll.py b/bottles/backend/dlls/dll.py index ab7563f2eab..c88f478def5 100644 --- a/bottles/backend/dlls/dll.py +++ b/bottles/backend/dlls/dll.py @@ -45,11 +45,18 @@ def __init__(self, version: str): def get_base_path(version: str) -> str: pass + @staticmethod + @abstractmethod + def get_override_keys() -> str: + pass + def check(self) -> bool: found = deepcopy(self.dlls) if None in self.dlls: - logging.error(f"DLL(s) \"{self.dlls[None]}\" path haven't been found, ignoring...") + logging.error( + f'DLL(s) "{self.dlls[None]}" path haven\'t been found, ignoring...' + ) return for path in self.dlls: @@ -77,13 +84,15 @@ def install(self, config: BottleConfig, overrides_only: bool = False, exclude=No exclude = [] if None in self.checked_dlls: - logging.error(f"DLL(s) \"{self.checked_dlls[None]}\" path haven't been found, ignoring...") + logging.error( + f'DLL(s) "{self.checked_dlls[None]}" path haven\'t been found, ignoring...' + ) return for path in self.checked_dlls: for dll in self.checked_dlls[path]: if dll not in exclude: - dll_name = dll.split('/')[-1].split('.')[0] + dll_name = dll.split("/")[-1].split(".")[0] if overrides_only: dll_in.append(dll_name) else: @@ -91,10 +100,9 @@ def install(self, config: BottleConfig, overrides_only: bool = False, exclude=No dll_in.append(dll_name) for dll in dll_in: - bundle["HKEY_CURRENT_USER\\Software\\Wine\\DllOverrides"].append({ - "value": dll, - "data": "native,builtin" - }) + bundle["HKEY_CURRENT_USER\\Software\\Wine\\DllOverrides"].append( + {"value": dll, "data": "native,builtin"} + ) reg.import_bundle(bundle) @@ -107,21 +115,22 @@ def uninstall(self, config: BottleConfig, exclude=None): exclude = [] if None in self.dlls: - logging.error(f"DLL(s) \"{self.dlls[None]}\" path haven't been found, ignoring...") + logging.error( + f'DLL(s) "{self.dlls[None]}" path haven\'t been found, ignoring...' + ) return for path in self.dlls: for dll in self.dlls[path]: if dll not in exclude: - dll_name = dll.split('/')[-1].split('.')[0] + dll_name = dll.split("/")[-1].split(".")[0] if self.__uninstall_dll(config, path, dll): dll_in.append(dll_name) for dll in dll_in: - bundle["HKEY_CURRENT_USER\\Software\\Wine\\DllOverrides"].append({ - "value": dll, - "data": "-" - }) + bundle["HKEY_CURRENT_USER\\Software\\Wine\\DllOverrides"].append( + {"value": dll, "data": "-"} + ) reg.import_bundle(bundle) @@ -131,14 +140,18 @@ def __get_sys_path(config: BottleConfig, path: str): if path in ["x32", "x86"]: return "system32" if config.Arch == Arch.WIN64: - if path in ["x64"] or any(arch in path for arch in ("x86_64", "lib64", "lib/")): + if path in ["x64"] or any( + arch in path for arch in ("x86_64", "lib64", "lib/") + ): return "system32" if path in ["x32", "x86"]: return "syswow64" return None - def __install_dll(self, config: BottleConfig, path: str, dll: str, remove: bool = False): - dll_name = dll.split('/')[-1] + def __install_dll( + self, config: BottleConfig, path: str, dll: str, remove: bool = False + ): + dll_name = dll.split("/")[-1] bottle = ManagerUtils.get_bottle_path(config) bottle = os.path.join(bottle, "drive_c", "windows") source = os.path.join(self.base_path, path, dll) @@ -158,27 +171,29 @@ def __install_dll(self, config: BottleConfig, path: str, dll: str, remove: bool try: shutil.copyfile(source, target) except FileNotFoundError: - logging.warning(f"{source} not found") # TODO: should not be ok but just ignore it for now + logging.warning( + f"{source} not found" + ) # TODO: should not be ok but just ignore it for now return False - ''' + """ reg.add( key="HKEY_CURRENT_USER\\Software\\Wine\\DllOverrides", value=dll_name.split('.')[0], data="native,builtin" ) - ''' + """ return True if os.path.exists(f"{target}.bck"): shutil.move(f"{target}.bck", target) elif os.path.exists(target): os.remove(target) - ''' + """ reg.remove( key="HKEY_CURRENT_USER\\Software\\Wine\\DllOverrides", value=dll_name.split('.')[0] ) - ''' + """ return True def __uninstall_dll(self, config, path: str, dll: str): diff --git a/bottles/backend/dlls/dxvk.py b/bottles/backend/dlls/dxvk.py index b4beb54b23c..53077d75f9c 100644 --- a/bottles/backend/dlls/dxvk.py +++ b/bottles/backend/dlls/dxvk.py @@ -21,20 +21,14 @@ class DXVKComponent(DLLComponent): dlls = { - "x32": [ - "d3d9.dll", - "d3d10core.dll", - "d3d11.dll", - "dxgi.dll" - ], - "x64": [ - "d3d9.dll", - "d3d10core.dll", - "d3d11.dll", - "dxgi.dll" - ] + "x32": ["d3d9.dll", "d3d10core.dll", "d3d11.dll", "dxgi.dll"], + "x64": ["d3d9.dll", "d3d10core.dll", "d3d11.dll", "dxgi.dll"], } + @staticmethod + def get_override_keys() -> str: + return "d3d9,d3d10core,d3d11,dxgi" + @staticmethod def get_base_path(version: str) -> str: return ManagerUtils.get_dxvk_path(version) diff --git a/bottles/backend/dlls/latencyflex.py b/bottles/backend/dlls/latencyflex.py index 87c5fb0f938..447b2da6f6c 100644 --- a/bottles/backend/dlls/latencyflex.py +++ b/bottles/backend/dlls/latencyflex.py @@ -27,6 +27,10 @@ class LatencyFleXComponent(DLLComponent): ] } + @staticmethod + def get_override_keys() -> str: + return "latencyflex_layer,latencyflex_wine" + @staticmethod def get_base_path(version: str) -> str: return ManagerUtils.get_latencyflex_path(version) diff --git a/bottles/backend/dlls/nvapi.py b/bottles/backend/dlls/nvapi.py index af630f001e4..fffca0ba42f 100644 --- a/bottles/backend/dlls/nvapi.py +++ b/bottles/backend/dlls/nvapi.py @@ -30,18 +30,16 @@ class NVAPIComponent(DLLComponent): dlls = { - "x32": [ - "nvapi.dll" - ], - "x64": [ - "nvapi64.dll" - ], - get_nvidia_dll_path(): [ - "nvngx.dll", - "_nvngx.dll" - ] + "x32": ["nvapi.dll"], + "x64": ["nvapi64.dll"], + get_nvidia_dll_path(): ["nvngx.dll", "_nvngx.dll"], } + @staticmethod + def get_override_keys() -> str: + # Bottles does not override (_)nvngx + return "nvapi,nvapi64" + @staticmethod def get_base_path(version: str) -> str: return ManagerUtils.get_nvapi_path(version) @@ -49,6 +47,7 @@ def get_base_path(version: str) -> str: @staticmethod def check_bottle_nvngx(bottle_path: str, bottle_config: BottleConfig): """Checks for the presence of the DLLs provided by the Nvidia driver, and if they're up to date.""" + def md5sum(file): hash_md5 = hashlib.md5() with open(file, "rb") as f: @@ -60,7 +59,9 @@ def md5sum(file): nvngx_path_system = get_nvidia_dll_path() if nvngx_path_system is None: - logging.error("Nvidia driver libraries haven't been found. DLSS might not work!") + logging.error( + "Nvidia driver libraries haven't been found. DLSS might not work!" + ) return # Reinstall nvngx if not present (acts as migration for this new patch) @@ -74,10 +75,14 @@ def md5sum(file): # If the system dll is different than the one in the bottle, reinstall them # Nvidia driver updates can change this DLL, so this should be checked at each startup - if md5sum(os.path.join(nvngx_path_bottle, "nvngx.dll")) != md5sum(os.path.join(get_nvidia_dll_path(), "nvngx.dll")): + if md5sum(os.path.join(nvngx_path_bottle, "nvngx.dll")) != md5sum( + os.path.join(get_nvidia_dll_path(), "nvngx.dll") + ): NVAPIComponent(bottle_config.NVAPI).install(bottle_config) return - - if md5sum(os.path.join(nvngx_path_bottle, "_nvngx.dll")) != md5sum(os.path.join(get_nvidia_dll_path(), "_nvngx.dll")): + + if md5sum(os.path.join(nvngx_path_bottle, "_nvngx.dll")) != md5sum( + os.path.join(get_nvidia_dll_path(), "_nvngx.dll") + ): NVAPIComponent(bottle_config.NVAPI).install(bottle_config) return diff --git a/bottles/backend/dlls/vkd3d.py b/bottles/backend/dlls/vkd3d.py index 4f545b1160c..7c0729be2ac 100644 --- a/bottles/backend/dlls/vkd3d.py +++ b/bottles/backend/dlls/vkd3d.py @@ -21,16 +21,14 @@ class VKD3DComponent(DLLComponent): dlls = { - "x86": [ - "d3d12.dll", - "d3d12core.dll" - ], - "x64": [ - "d3d12.dll", - "d3d12core.dll" - ] + "x86": ["d3d12.dll", "d3d12core.dll"], + "x64": ["d3d12.dll", "d3d12core.dll"], } + @staticmethod + def get_override_keys() -> str: + return "d3d12,d3d12core" + @staticmethod def get_base_path(version: str) -> str: return ManagerUtils.get_vkd3d_path(version) diff --git a/bottles/backend/downloader.py b/bottles/backend/downloader.py index 83c4e392d44..9772be16389 100644 --- a/bottles/backend/downloader.py +++ b/bottles/backend/downloader.py @@ -37,7 +37,9 @@ class Downloader: bars using the func parameter. """ - def __init__(self, url: str, file: str, update_func: Optional[TaskStreamUpdateHandler] = None): + def __init__( + self, url: str, file: str, update_func: Optional[TaskStreamUpdateHandler] = None + ): self.start_time = None self.url = url self.file = file @@ -48,7 +50,9 @@ def download(self) -> Result: try: with open(self.file, "wb") as file: self.start_time = time.time() - headers = {"User-Agent": "curl/7.79.1"} # we fake the user-agent to avoid 403 errors on some servers + headers = { + "User-Agent": "curl/7.79.1" + } # we fake the user-agent to avoid 403 errors on some servers response = requests.get(self.url, stream=True, headers=headers) total_size = int(response.headers.get("content-length", 0)) received_size = 0 @@ -67,12 +71,16 @@ def download(self) -> Result: self.update_func(1, 1) self.__progress(1, 1) except requests.exceptions.SSLError: - logging.error("Download failed due to a SSL error. " - "Your system may have a wrong date/time or wrong certificates.") + logging.error( + "Download failed due to a SSL error. " + "Your system may have a wrong date/time or wrong certificates." + ) return Result(False, message="Download failed due to a SSL error.") except (requests.exceptions.RequestException, OSError): logging.error("Download failed! Check your internet connection.") - return Result(False, message="Download failed! Check your internet connection.") + return Result( + False, message="Download failed! Check your internet connection." + ) return Result(True) @@ -81,24 +89,32 @@ def __progress(self, received_size, total_size): percent = int(received_size * 100 / total_size) done_str = FileUtils.get_human_size(received_size) total_str = FileUtils.get_human_size(total_size) - speed_str = FileUtils.get_human_size(received_size / (time.time() - self.start_time)) + speed_str = FileUtils.get_human_size( + received_size / (time.time() - self.start_time) + ) name = self.file.split("/")[-1] c_close, c_complete, c_incomplete = "\033[0m", "\033[92m", "\033[90m" divider = 2 - full_text_size = len(f"\r{c_complete}{name} (100%) " - f"{'━' * int(100 / divider)} " - f"({total_str}/{total_str} - 100MB)") + full_text_size = len( + f"\r{c_complete}{name} (100%) " + f"{'━' * int(100 / divider)} " + f"({total_str}/{total_str} - 100MB)" + ) while shutil.get_terminal_size().columns < full_text_size: divider = divider + 1 - full_text_size = len(f"\r{c_complete}{name} (100%) " - f"{'━' * int(100 / divider)} " - f"({total_str}/{total_str} - 100MB)") + full_text_size = len( + f"\r{c_complete}{name} (100%) " + f"{'━' * int(100 / divider)} " + f"({total_str}/{total_str} - 100MB)" + ) if divider > 10: break - text = f"\r{c_incomplete if percent < 100 else c_complete}{name} ({percent}%) " \ - f"{'━' * int(percent / divider)} " \ - f"({done_str}/{total_str} - {speed_str})" + text = ( + f"\r{c_incomplete if percent < 100 else c_complete}{name} ({percent}%) " + f"{'━' * int(percent / divider)} " + f"({done_str}/{total_str} - {speed_str})" + ) if sys.stdout.encoding == "utf-8": print(text, end="") diff --git a/bottles/backend/globals.py b/bottles/backend/globals.py index 152afd41941..ec315fd5cb1 100644 --- a/bottles/backend/globals.py +++ b/bottles/backend/globals.py @@ -20,11 +20,14 @@ from functools import lru_cache from pathlib import Path from typing import Dict +from bottles.backend.utils import yaml, json @lru_cache class Paths: - xdg_data_home = os.environ.get("XDG_DATA_HOME", os.path.join(Path.home(), ".local/share")) + xdg_data_home = os.environ.get( + "XDG_DATA_HOME", os.path.join(Path.home(), ".local/share") + ) # Icon paths icons_user = f"{xdg_data_home}/icons" @@ -79,9 +82,13 @@ class TrdyPaths: mangohud_available = shutil.which("mangohud") or False obs_vkc_available = shutil.which("obs-vkcapture") or False vmtouch_available = shutil.which("vmtouch") or False +base_version = "" +if os.path.isfile("/app/manifest.json"): + base_version = ( + json.load(open("/app/manifest.json")) + .get("base-version", "") + .removeprefix("stable-") + ) # encoding detection correction, following windows defaults -locale_encodings: Dict[str, str] = { - 'ja_JP': 'cp932', - 'zh_CN': 'gbk' -} +locale_encodings: Dict[str, str] = {"ja_JP": "cp932", "zh_CN": "gbk"} diff --git a/bottles/backend/health.py b/bottles/backend/health.py index b24be391bec..ced2604bfc7 100644 --- a/bottles/backend/health.py +++ b/bottles/backend/health.py @@ -63,10 +63,7 @@ def __init__(self): self.bottles_envs = self.get_bottles_envs() self.check_system_info() self.disk = self.get_disk_data() - self.ram = { - "MemTotal": "n/a", - "MemAvailable": "n/a" - } + self.ram = {"MemTotal": "n/a", "MemAvailable": "n/a"} self.get_ram_data() if "FLATPAK_ID" not in os.environ: @@ -141,6 +138,7 @@ def check_patool(): def check_icoextract(): try: import icoextract + return True except ModuleNotFoundError: return False @@ -149,6 +147,7 @@ def check_icoextract(): def check_pefile(): try: import pefile + return True except ModuleNotFoundError: return False @@ -157,6 +156,7 @@ def check_pefile(): def check_markdown(): try: import markdown + return True except ModuleNotFoundError: return False @@ -165,6 +165,7 @@ def check_markdown(): def check_orjson(): try: import orjson + return True except ModuleNotFoundError: return False @@ -187,6 +188,7 @@ def check_ImageMagick(): def check_FVS(): try: from fvs.repo import FVSRepo + return True except ModuleNotFoundError: return False @@ -197,14 +199,12 @@ def get_bottles_envs(): "TESTING_REPOS", "LOCAL_INSTALLERS", "LOCAL_COMPONENTS", - "LOCAL_DEPENDENCIES" + "LOCAL_DEPENDENCIES", ] for _look in look: if _look in os.environ: - return { - _look: os.environ[_look] - } + return {_look: os.environ[_look]} def check_system_info(self): self.kernel = os.uname().sysname @@ -212,19 +212,22 @@ def check_system_info(self): def get_disk_data(self): disk_data = self.file_utils.get_disk_size(False) - return { - "Total": disk_data["total"], - "Free": disk_data["free"] - } + return {"Total": disk_data["total"], "Free": disk_data["free"]} def get_ram_data(self): with contextlib.suppress(FileNotFoundError, PermissionError): - with open('/proc/meminfo') as file: + with open("/proc/meminfo") as file: for line in file: - if 'MemTotal' in line: - self.ram["MemTotal"] = self.file_utils.get_human_size_legacy(float(line.split()[1])*1024.0) - if 'MemAvailable' in line: - self.ram["MemAvailable"] = self.file_utils.get_human_size_legacy(float(line.split()[1])*1024.0) + if "MemTotal" in line: + self.ram["MemTotal"] = self.file_utils.get_human_size_legacy( + float(line.split()[1]) * 1024.0 + ) + if "MemAvailable" in line: + self.ram["MemAvailable"] = ( + self.file_utils.get_human_size_legacy( + float(line.split()[1]) * 1024.0 + ) + ) def get_results(self, plain: bool = False): results = { @@ -237,13 +240,10 @@ def get_results(self, plain: bool = False): "Wayland": self.wayland, }, "Graphics": self.gpus, - "Kernel": { - "Type": self.kernel, - "Version": self.kernel_version - }, + "Kernel": {"Type": self.kernel, "Version": self.kernel_version}, "Disk": self.disk, "RAM": self.ram, - "Bottles_envs": self.bottles_envs + "Bottles_envs": self.bottles_envs, } if "FLATPAK_ID" not in os.environ: @@ -258,7 +258,7 @@ def get_results(self, plain: bool = False): "markdown": self.markdown, "ImageMagick": self.ImageMagick, "FVS": self.FVS, - "xdpyinfo": self.xdpyinfo + "xdpyinfo": self.xdpyinfo, } if plain: @@ -273,7 +273,9 @@ def has_core_deps(self): for k, v in self.get_results()["Tools and Libraries"].items(): if v is False: - logging.error(f"Core dependency {k} not found, Bottles can't be started.") + logging.error( + f"Core dependency {k} not found, Bottles can't be started." + ) result = False return result diff --git a/bottles/backend/logger.py b/bottles/backend/logger.py index 15258a7613b..05bba87f5c0 100644 --- a/bottles/backend/logger.py +++ b/bottles/backend/logger.py @@ -30,16 +30,11 @@ class Logger(logging.getLoggerClass()): This class is a wrapper for the logging module. It provides custom formats for the log messages. """ - __color_map = { - "debug": 37, - "info": 36, - "warning": 33, - "error": 31, - "critical": 41 - } + + __color_map = {"debug": 37, "info": 36, "warning": 33, "error": 31, "critical": 41} __format_log = { - 'fmt': '\033[80m%(asctime)s \033[1m(%(levelname)s)\033[0m %(message)s \033[0m', - 'datefmt': '%H:%M:%S', + "fmt": "\033[80m%(asctime)s \033[1m(%(levelname)s)\033[0m %(message)s \033[0m", + "datefmt": "%H:%M:%S", } def __color(self, level, message: str): @@ -61,25 +56,35 @@ def __init__(self, formatter=None): self.root.addHandler(handler) def debug(self, message, **kwargs): - self.root.debug(self.__color("debug", message), ) + self.root.debug( + self.__color("debug", message), + ) def info(self, message, jn=False, **kwargs): - self.root.info(self.__color("info", message), ) + self.root.info( + self.__color("info", message), + ) if jn: JournalManager.write(JournalSeverity.INFO, message) def warning(self, message, jn=True, **kwargs): - self.root.warning(self.__color("warning", message), ) + self.root.warning( + self.__color("warning", message), + ) if jn: JournalManager.write(JournalSeverity.WARNING, message) def error(self, message, jn=True, **kwargs): - self.root.error(self.__color("error", message), ) + self.root.error( + self.__color("error", message), + ) if jn: JournalManager.write(JournalSeverity.ERROR, message) def critical(self, message, jn=True, **kwargs): - self.root.critical(self.__color("critical", message), ) + self.root.critical( + self.__color("critical", message), + ) if jn: JournalManager.write(JournalSeverity.CRITICAL, message) @@ -101,8 +106,7 @@ def write_log(data: list): # we write the same to the journal for convenience JournalManager.write( - severity=JournalSeverity.CRASH, - message="A crash has been detected." + severity=JournalSeverity.CRASH, message="A crash has been detected." ) def set_silent(self): diff --git a/bottles/backend/managers/backup.py b/bottles/backend/managers/backup.py index 428a4916583..63c5c121364 100644 --- a/bottles/backend/managers/backup.py +++ b/bottles/backend/managers/backup.py @@ -18,9 +18,8 @@ import os import shutil import tarfile -from gettext import gettext as _ - import pathvalidate +from gettext import gettext as _ from bottles.backend.globals import Paths from bottles.backend.logger import Logger @@ -35,6 +34,48 @@ class BackupManager: + @staticmethod + def _validate_path(path: str) -> bool: + """Validate if the path is not None or empty.""" + if not path: + logging.error(_("No path specified")) + return False + return True + + @staticmethod + def _create_tarfile( + source_path: str, destination_path: str, exclude_filter=None + ) -> bool: + """Helper function to create a tar.gz file from a source path.""" + try: + with tarfile.open(destination_path, "w:gz") as tar: + os.chdir(os.path.dirname(source_path)) + tar.add(os.path.basename(source_path), filter=exclude_filter) + return True + except (FileNotFoundError, PermissionError, tarfile.TarError, ValueError) as e: + logging.error(f"Error creating backup: {e}") + return False + + @staticmethod + def _safe_extract_tarfile(tar_path: str, extract_path: str) -> bool: + """ + Safely extract a tar.gz file to avoid directory traversal + vulnerabilities. + """ + try: + with tarfile.open(tar_path, "r:gz") as tar: + # Validate each member + for member in tar.getmembers(): + member_path = os.path.abspath( + os.path.join(extract_path, member.name) + ) + if not member_path.startswith(os.path.abspath(extract_path)): + raise Exception("Detected path traversal attempt in tar file") + tar.extractall(path=extract_path) + return True + except (tarfile.TarError, Exception) as e: + logging.error(f"Error extracting backup: {e}") + return False @staticmethod def export_backup(config: BottleConfig, scope: str, path: str) -> Result: @@ -44,43 +85,35 @@ def export_backup(config: BottleConfig, scope: str, path: str) -> Result: Config will only export the bottle configuration, full will export the full bottle in tar.gz format. """ - if path in [None, ""]: - logging.error(_("No path specified")) + if not BackupManager._validate_path(path): return Result(status=False) - logging.info(f"New {scope} backup for [{config.Name}] in [{path}]") + logging.info(f"Exporting {scope} backup for [{config.Name}] to [{path}]") if scope == "config": backup_created = config.dump(path).status else: task_id = TaskManager.add(Task(title=_("Backup {0}").format(config.Name))) bottle_path = ManagerUtils.get_bottle_path(config) - try: - with tarfile.open(path, "w:gz") as tar: - parent = os.path.dirname(bottle_path) - folder = os.path.basename(bottle_path) - os.chdir(parent) - tar.add(folder, filter=BackupManager.exclude_filter) - backup_created = True - except (FileNotFoundError, PermissionError, tarfile.TarError, ValueError): - logging.error(f"Error creating backup for [{config.Name}]") - backup_created = False - finally: - TaskManager.remove(task_id) + backup_created = BackupManager._create_tarfile( + bottle_path, path, exclude_filter=BackupManager.exclude_filter + ) + TaskManager.remove(task_id) if backup_created: - logging.info(f"New backup saved in path: {path}.", jn=True) + logging.info(f"Backup successfully saved to: {path}.") return Result(status=True) - - logging.error(f"Failed to save backup in path: {path}.") - return Result(status=False) + else: + logging.error("Failed to save backup.") + return Result(status=False) @staticmethod - def exclude_filter(tarinfo): - """Filter which excludes some unwanted files from the backup.""" + def exclude_filter(tarinfo: tarfile.TarInfo) -> tarfile.TarInfo: + """ + Filter which excludes some unwanted files from the backup. + """ if "dosdevices" in tarinfo.name: return None - return tarinfo @staticmethod @@ -91,126 +124,95 @@ def import_backup(scope: str, path: str) -> Result: Config will make a new bottle reproducing the configuration, full will import the full bottle from a tar.gz file. """ - if not path: - logging.error(_("No path specified")) + if not BackupManager._validate_path(path): return Result(status=False) - manager = Manager() - - backup_name = os.path.basename(path) - import_status = False - - task_id = TaskManager.add(Task(title=_("Importing backup: {0}").format(backup_name))) - logging.info(f"Importing backup: {backup_name}") + logging.info(f"Importing backup from: {path}") if scope == "config": - ''' - If the backup type is "config", the backup will be used - to replicate the bottle configuration, else the backup - will be used to extract the bottle's directory. - ''' - if backup_name.endswith(".yml"): - backup_name = backup_name[:-4] - - config_load = BottleConfig.load(path) - if config_load.status and manager.create_bottle_from_config(config_load.data): - import_status = True - else: - import_status = False + return BackupManager._import_config_backup(path) else: - if backup_name.endswith(".tar.gz"): - backup_name = backup_name[:-7] + return BackupManager._import_full_backup(path) - if backup_name.lower().startswith("backup_"): - # remove the "backup_" prefix if it exists - backup_name = backup_name[7:] - - try: - with tarfile.open(path, "r:gz") as tar: - def is_within_directory(directory, target): - - abs_directory = os.path.abspath(directory) - abs_target = os.path.abspath(target) - - prefix = os.path.commonprefix([abs_directory, abs_target]) - - return prefix == abs_directory - - def safe_extract(_tar, _path=".", members=None, *, numeric_owner=False): - - for member in _tar.getmembers(): - member_path = os.path.join(_path, member.name) - if not is_within_directory(_path, member_path): - raise Exception("Attempted Path Traversal in Tar File") - - _tar.extractall(_path, members, numeric_owner=numeric_owner) - - safe_extract(tar, Paths.bottles) - import_status = True - except (FileNotFoundError, PermissionError, tarfile.TarError): - import_status = False - - TaskManager.remove(task_id) - - if import_status: - manager.update_bottles() - logging.info(f"Backup imported: {path}", jn=True) + @staticmethod + def _import_config_backup(path: str) -> Result: + task_id = TaskManager.add(Task(title=_("Importing config backup"))) + config_load = BottleConfig.load(path) + manager = Manager() + if config_load.status and manager.create_bottle_from_config(config_load.data): + TaskManager.remove(task_id) + logging.info("Config backup imported successfully.") return Result(status=True) - - logging.error(f"Failed importing backup: {backup_name}") - return Result(status=False) + else: + TaskManager.remove(task_id) + logging.error("Failed to import config backup.") + return Result(status=False) @staticmethod - def duplicate_bottle(config, name) -> Result: - """Duplicates the bottle with the specified new name.""" - logging.info(f"Duplicating bottle: {config.Name} to {name}") - - path = pathvalidate.sanitize_filename(name, platform="universal") - source = ManagerUtils.get_bottle_path(config) - dest = os.path.join(Paths.bottles, path) - - source_drive = os.path.join(source, "drive_c") - dest_drive = os.path.join(dest, "drive_c") + def _import_full_backup(path: str) -> Result: + task_id = TaskManager.add(Task(title=_("Importing full backup"))) + if BackupManager._safe_extract_tarfile(path, Paths.bottles): + Manager().update_bottles() + TaskManager.remove(task_id) + logging.info("Full backup imported successfully.") + return Result(status=True) + else: + TaskManager.remove(task_id) + logging.error("Failed to import full backup.") + return Result(status=False) - source_config = os.path.join(source, "bottle.yml") - dest_config = os.path.join(dest, "bottle.yml") + @staticmethod + def duplicate_bottle(config: BottleConfig, name: str) -> Result: + """ + Duplicates the bottle with the specified new name. + """ + logging.info(f"Duplicating bottle: {config.Name} as {name}") - if not os.path.exists(dest): - os.makedirs(dest) + sanitized_name = pathvalidate.sanitize_filename(name, platform="universal") + source_path = ManagerUtils.get_bottle_path(config) + destination_path = os.path.join(Paths.bottles, sanitized_name) - regs = [ - "system.reg", - "user.reg", - "userdef.reg" - ] + return BackupManager._duplicate_bottle_directory( + config, source_path, destination_path, name + ) + @staticmethod + def _duplicate_bottle_directory( + config: BottleConfig, source_path: str, destination_path: str, new_name: str + ) -> Result: try: - for reg in regs: - source_reg = os.path.join(source, reg) - dest_reg = os.path.join(dest, reg) - if os.path.exists(source_reg): - shutil.copyfile(source_reg, dest_reg) - - shutil.copyfile(source_config, dest_config) - - with open(dest_config, "r") as config_file: - config = yaml.load(config_file) - config["Name"] = name - config["Path"] = path - - with open(dest_config, "w") as config_file: - yaml.dump(config, config_file, indent=4) - - shutil.copytree( - src=source_drive, - dst=dest_drive, - ignore=shutil.ignore_patterns(".*"), - symlinks=True, - ignore_dangling_symlinks=True - ) - except (FileNotFoundError, PermissionError, OSError): - logging.error(f"Failed duplicate bottle: {name}") + if not os.path.exists(destination_path): + os.makedirs(destination_path) + for item in [ + "drive_c", + "system.reg", + "user.reg", + "userdef.reg", + "bottle.yml", + ]: + source_item = os.path.join(source_path, item) + destination_item = os.path.join(destination_path, item) + if os.path.isdir(source_item): + shutil.copytree( + source_item, + destination_item, + ignore=shutil.ignore_patterns(".*"), + symlinks=True, + ) + elif os.path.isfile(source_item): + shutil.copy(source_item, destination_item) + + # Update the bottle configuration + config_path = os.path.join(destination_path, "bottle.yml") + with open(config_path) as config_file: + config_data = yaml.load(config_file) + config_data["Name"] = new_name + config_data["Path"] = destination_path + with open(config_path, "w") as config_file: + yaml.dump(config_data, config_file, indent=4) + + logging.info(f"Bottle duplicated successfully as {new_name}.") + return Result(status=True) + except (FileNotFoundError, PermissionError, OSError) as e: + logging.error(f"Error duplicating bottle: {e}") return Result(status=False) - - logging.info(f"Bottle {name} duplicated.", jn=True) - return Result(status=True) diff --git a/bottles/backend/managers/component.py b/bottles/backend/managers/component.py index 9100cfcab60..0ea911c71de 100644 --- a/bottles/backend/managers/component.py +++ b/bottles/backend/managers/component.py @@ -28,7 +28,14 @@ from bottles.backend.globals import Paths from bottles.backend.logger import Logger from bottles.backend.models.result import Result -from bottles.backend.state import Locks, Task, TaskStreamUpdateHandler, Status, TaskManager, LockManager +from bottles.backend.state import ( + Locks, + Task, + TaskStreamUpdateHandler, + Status, + TaskManager, + LockManager, +) from bottles.backend.utils.file import FileUtils from bottles.backend.utils.generic import is_glibc_min_available from bottles.backend.utils.manager import ManagerUtils @@ -64,7 +71,7 @@ def fetch_catalog(self) -> dict: "vkd3d": {}, "nvapi": {}, "latencyflex": {}, - "winebridge": {} + "winebridge": {}, } components_available = { "runtimes": self.__manager.runtimes_available, @@ -74,26 +81,28 @@ def fetch_catalog(self) -> dict: "vkd3d": self.__manager.vkd3d_available, "nvapi": self.__manager.nvapi_available, "latencyflex": self.__manager.latencyflex_available, - "winebridge": self.__manager.winebridge_available + "winebridge": self.__manager.winebridge_available, } index = self.__repo.catalog for component in index.items(): - ''' + """ For each component, append it to the corresponding catalog and mark it as installed if it is. - ''' + """ if component[1]["Category"] == "runners": if "soda" in component[0].lower() or "caffe" in component[0].lower(): if not is_glibc_min_available(): - logging.warning(f"{component[0]} was found but it requires " - "glibc >= 2.32 and your system is running an older " - "version. Use the Flatpak instead if you can't " - "upgrade your system. This runner will be ignored, " - "please keep in mind that Bottles and all our " - "installers are only tested with Soda and Caffe runners.") + logging.warning( + f"{component[0]} was found but it requires " + "glibc >= 2.32 and your system is running an older " + "version. Use the Flatpak instead if you can't " + "upgrade your system. This runner will be ignored, " + "please keep in mind that Bottles and all our " + "installers are only tested with Soda and Caffe runners." + ) continue sub_category = component[1]["Sub-category"] @@ -118,12 +127,12 @@ def fetch_catalog(self) -> dict: return catalog def download( - self, - download_url: str, - file: str, - rename: str = "", - checksum: str = "", - func: Optional[TaskStreamUpdateHandler] = None + self, + download_url: str, + file: str, + rename: str = "", + checksum: str = "", + func: Optional[TaskStreamUpdateHandler] = None, ) -> bool: """Download a component from the Bottles repository.""" @@ -136,10 +145,10 @@ def download( update_func = task.stream_update if not func else func if download_url.startswith("temp/"): - ''' + """ The caller is explicitly requesting a component from the /temp directory. Nothing should be downloaded. - ''' + """ return True existing_file = rename if rename else file @@ -147,18 +156,18 @@ def download( just_downloaded = False if os.path.isfile(os.path.join(Paths.temp, existing_file)): - ''' + """ Check if the file already exists in the /temp directory. If so, then skip the download process and set the update_func to completed. - ''' + """ logging.warning(f"File [{existing_file}] already exists in temp, skipping.") else: - ''' + """ As some urls can be redirect, we need to take care of this and make sure to use the final url. This check should be skipped for large files (e.g. runners). - ''' + """ c = pycurl.Curl() try: c.setopt(c.URL, download_url) @@ -183,9 +192,7 @@ def download( False and the download is removed from the download manager. """ res = Downloader( - url=download_url, - file=temp_dest, - update_func=update_func + url=download_url, file=temp_dest, update_func=update_func ).download() if not res.ok: @@ -199,7 +206,9 @@ def download( just_downloaded = True else: - logging.warning(f"Failed to download [{download_url}] with code: {req_code} != 200") + logging.warning( + f"Failed to download [{download_url}] with code: {req_code} != 200" + ) TaskManager.remove(task_id) return False @@ -210,7 +219,7 @@ def download( file_path = os.path.join(Paths.temp, rename) os.rename(temp_dest, file_path) - if checksum: + if checksum and not os.environ.get("BOTTLES_SKIP_CHECKSUM"): """ Compare the checksum of the downloaded file with the one provided by the caller. If they don't match, remove the @@ -222,7 +231,9 @@ def download( if local_checksum and local_checksum != checksum: logging.error(f"Downloaded file [{file}] looks corrupted.") - logging.error(f"Source cksum: [{checksum}] downloaded: [{local_checksum}]") + logging.error( + f"Source cksum: [{checksum}] downloaded: [{local_checksum}]" + ) logging.error(f"Removing corrupted file [{file}].") os.remove(file_path) TaskManager.remove(task_id) @@ -254,12 +265,12 @@ def extract(name: str, component: str, archive: str) -> True: return False try: - ''' + """ Try to extract the archive in the /temp directory. If the extraction fails, remove the archive from the /temp - directory and return False. The common cause of a failed + directory and return False. The common cause of a failed extraction is that the archive is corrupted. - ''' + """ tar = tarfile.open(f"{Paths.temp}/{archive}") root_dir = tar.getnames()[0] tar.extractall(path) @@ -275,15 +286,12 @@ def extract(name: str, component: str, archive: str) -> True: if root_dir.endswith("x86_64"): try: - ''' + """ If the folder ends with x86_64, remove this from its name. Return False if an folder with the same name already exists. - ''' + """ root_dir = os.path.join(path, root_dir) - shutil.move( - src=root_dir, - dst=root_dir[:-7] - ) + shutil.move(src=root_dir, dst=root_dir[:-7]) except (FileExistsError, shutil.Error): logging.error("Extraction failed! Component already exists.") return False @@ -291,10 +299,10 @@ def extract(name: str, component: str, archive: str) -> True: @LockManager.lock(Locks.ComponentsInstall) # avoid high resource usage def install( - self, - component_type: str, - component_name: str, - func: Optional[TaskStreamUpdateHandler] = None, + self, + component_type: str, + component_name: str, + func: Optional[TaskStreamUpdateHandler] = None, ): """ This function is used to install a component. It automatically @@ -314,14 +322,14 @@ def install( file=file["file_name"], rename=file["rename"], checksum=file["file_checksum"], - func=func + func=func, ) if not res: - ''' + """ If the download fails, execute the given func passing failed=True as a parameter. - ''' + """ if func: func(status=Status.FAILED) return Result(False) @@ -329,18 +337,18 @@ def install( archive = manifest["File"][0]["file_name"] if manifest["File"][0]["rename"]: - ''' + """ If the component has a rename, rename the downloaded file to the required name. - ''' + """ archive = manifest["File"][0]["rename"] self.extract(component_name, component_type, archive) - ''' + """ Execute Post Install if the component has it defined in the manifest. - ''' + """ if "Post" in manifest: print(f"Executing post install for [{component_name}].") @@ -348,11 +356,11 @@ def install( if post["action"] == "rename": self.__post_rename(component_type, post) - ''' + """ Ask the manager to re-organize its components. Note: I know that this is not the most efficient way to do this, please give feedback if you know a better way to avoid this. - ''' + """ if component_type in ["runtime", "winebridge"]: with contextlib.suppress(FileNotFoundError): os.remove(os.path.join(Paths.temp, archive)) @@ -398,10 +406,7 @@ def __post_rename(component_type: str, post: dict): return if not os.path.isdir(os.path.join(path, dest)): - shutil.move( - src=os.path.join(path, source), - dst=os.path.join(path, dest) - ) + shutil.move(src=os.path.join(path, source), dst=os.path.join(path, dest)) def is_in_use(self, component_type: str, component_name: str): bottles = self.__manager.local_bottles @@ -422,7 +427,12 @@ def is_in_use(self, component_type: str, component_name: str): def uninstall(self, component_type: str, component_name: str): if self.is_in_use(component_type, component_name): - return Result(False, data={"message": f"Component in use and cannot be removed: {component_name}"}) + return Result( + False, + data={ + "message": f"Component in use and cannot be removed: {component_name}" + }, + ) if component_type in ["runner", "runner:proton"]: path = ManagerUtils.get_runner_path(component_name) @@ -452,11 +462,11 @@ def uninstall(self, component_type: str, component_name: str): logging.error(f"Failed to uninstall component: {component_name}, {e}") return Result(False, data={"message": "Failed to uninstall component."}) - ''' + """ Ask the manager to re-organize its components. Note: I know that this is not the most efficient way to do this, please give feedback if you know a better way to avoid this. - ''' + """ if component_type in ["runner", "runner:proton"]: self.__manager.check_runners() diff --git a/bottles/backend/managers/conf.py b/bottles/backend/managers/conf.py index fbb87752330..bb0fc471cea 100644 --- a/bottles/backend/managers/conf.py +++ b/bottles/backend/managers/conf.py @@ -7,7 +7,12 @@ class ConfigManager(object): - def __init__(self, config_file: Optional[str] = None, config_type: str = 'ini', config_string: Optional[str] = None): + def __init__( + self, + config_file: Optional[str] = None, + config_type: str = "ini", + config_string: Optional[str] = None, + ): self.config_file = config_file self.config_string = config_string self.config_type = config_type @@ -18,7 +23,9 @@ def __init__(self, config_file: Optional[str] = None, config_type: str = 'ini', self.config_dict = self.read() if self.config_file is not None and self.config_string is not None: - raise ValueError('Passing both config_file and config_string is not allowed') + raise ValueError( + "Passing both config_file and config_string is not allowed" + ) def checks(self): """Checks if the configuration file exists, if not, create it.""" @@ -26,36 +33,36 @@ def checks(self): base_path = os.path.dirname(self.config_file) os.makedirs(base_path, exist_ok=True) - with open(self.config_file, 'w') as f: - f.write('') + with open(self.config_file, "w") as f: + f.write("") def read(self): if self.config_file is not None: """Reads the configuration file and returns it as a dictionary""" - if self.config_type == 'ini': + if self.config_type == "ini": config = ConfigParser() config.read(self.config_file) # noinspection PyProtectedMember res = config._sections - elif self.config_type == 'json': - with open(self.config_file, 'r') as f: + elif self.config_type == "json": + with open(self.config_file, "r") as f: res = json.load(f) - elif self.config_type == 'yaml' or self.config_type == 'yml': - with open(self.config_file, 'r') as f: + elif self.config_type == "yaml" or self.config_type == "yml": + with open(self.config_file, "r") as f: res = yaml.load(f) else: - raise ValueError('Invalid configuration type') + raise ValueError("Invalid configuration type") elif self.config_string is not None: - if self.config_type == 'ini': + if self.config_type == "ini": config = ConfigParser() config.read_string(self.config_string) res = config._sections - elif self.config_type == 'json': + elif self.config_type == "json": res = json.loads(self.config_string) - elif self.config_type == 'yaml' or self.config_type == 'yml': + elif self.config_type == "yaml" or self.config_type == "yml": res = yaml.load(self.config_string) else: - raise ValueError('Invalid configuration type') + raise ValueError("Invalid configuration type") else: res = None @@ -67,12 +74,12 @@ def get_dict(self): def write_json(self): """Writes the configuration to a JSON file""" - with open(self.config_file, 'w') as f: + with open(self.config_file, "w") as f: json.dump(self.config_dict, f, indent=4) def write_yaml(self): """Writes the configuration to a YAML file""" - with open(self.config_file, 'w') as f: + with open(self.config_file, "w") as f: yaml.dump(self.config_dict, f) def write_ini(self): @@ -85,24 +92,24 @@ def write_ini(self): for key, value in self.config_dict[section].items(): config.set(section, key, value) - with open(self.config_file, 'w') as f: + with open(self.config_file, "w") as f: config.write(f) def write_dict(self, config_file: Optional[str] = None): if self.config_file is None and config_file is None: - raise ValueError('No config path specified') + raise ValueError("No config path specified") elif self.config_file is None and config_file is not None: self.config_file = config_file """Writes the configuration to the file""" - if self.config_type == 'ini': + if self.config_type == "ini": self.write_ini() - elif self.config_type == 'json': + elif self.config_type == "json": self.write_json() - elif self.config_type == 'yaml': + elif self.config_type == "yaml": self.write_yaml() else: - raise ValueError('Invalid configuration type') + raise ValueError("Invalid configuration type") def merge_dict(self, changes: dict): """Merges a dictionary into the configuration""" diff --git a/bottles/backend/managers/data.py b/bottles/backend/managers/data.py index 6cd8fed650e..1c769d185ac 100644 --- a/bottles/backend/managers/data.py +++ b/bottles/backend/managers/data.py @@ -45,21 +45,25 @@ def __init__(self): def __get_data(self): try: - with open(self.__p_data, 'r') as s: + with open(self.__p_data, "r") as s: self.__data = yaml.load(s) if self.__data is None: raise AttributeError except FileNotFoundError: - logging.error('Data file not found. Creating new one.', ) + logging.error( + "Data file not found. Creating new one.", + ) self.__create_data_file() except AttributeError: - logging.error('Data file is empty. Creating new one.', ) + logging.error( + "Data file is empty. Creating new one.", + ) self.__create_data_file() def __create_data_file(self): if not os.path.exists(Paths.base): os.makedirs(Paths.base) - with open(self.__p_data, 'w') as s: + with open(self.__p_data, "w") as s: yaml.dump(Samples.data, s) self.__get_data() @@ -81,7 +85,7 @@ def set(self, key, value, of_type=None): self.__data[key] = value with contextlib.suppress(FileNotFoundError): - with open(self.__p_data, 'w') as s: + with open(self.__p_data, "w") as s: yaml.dump(self.__data, s) def remove(self, key): @@ -89,7 +93,7 @@ def remove(self, key): if self.__data.get(key): del self.__data[key] with contextlib.suppress(FileNotFoundError): - with open(self.__p_data, 'w') as s: + with open(self.__p_data, "w") as s: yaml.dump(self.__data, s) def get(self, key): diff --git a/bottles/backend/managers/dependency.py b/bottles/backend/managers/dependency.py index fb1f0981f37..7ae28f34315 100644 --- a/bottles/backend/managers/dependency.py +++ b/bottles/backend/managers/dependency.py @@ -87,16 +87,14 @@ def install(self, config: BottleConfig, dependency: list) -> Result: the dependency. """ self.__manager.versioning_manager.create_state( - config=config, - message=f"Before installing {dependency[0]}" + config=config, message=f"Before installing {dependency[0]}" ) task_id = TaskManager.add(Task(title=dependency[0])) - logging.info("Installing dependency [%s] in bottle [%s]." % ( - dependency[0], - config.Name - ), ) + logging.info( + "Installing dependency [%s] in bottle [%s]." % (dependency[0], config.Name), + ) manifest = self.get_dependency(dependency[0]) if not manifest: """ @@ -105,8 +103,7 @@ def install(self, config: BottleConfig, dependency: list) -> Result: """ TaskManager.remove(task_id) return Result( - status=False, - message=f"Cannot find manifest for {dependency[0]}." + status=False, message=f"Cannot find manifest for {dependency[0]}." ) if manifest.get("Dependencies"): @@ -128,12 +125,16 @@ def install(self, config: BottleConfig, dependency: list) -> Result: Here we execute all steps in the manifest. Steps are the actions performed to install the dependency. """ + arch = step.get("for", "win64_win32") + if config.Arch not in arch: + continue + res = self.__perform_steps(config, step) if not res.ok: TaskManager.remove(task_id) return Result( status=False, - message=f"One or more steps failed for {dependency[0]}." + message=f"One or more steps failed for {dependency[0]}.", ) if not res.data.get("uninstaller"): uninstaller = False @@ -146,13 +147,10 @@ def install(self, config: BottleConfig, dependency: list) -> Result: dependencies = [dependency[0]] if config.Installed_Dependencies: - dependencies = config.Installed_Dependencies + \ - [dependency[0]] + dependencies = config.Installed_Dependencies + [dependency[0]] self.__manager.update_config( - config=config, - key="Installed_Dependencies", - value=dependencies + config=config, key="Installed_Dependencies", value=dependencies ) if manifest.get("Uninstaller"): @@ -165,10 +163,7 @@ def install(self, config: BottleConfig, dependency: list) -> Result: if dependency[0] not in config.Installed_Dependencies: self.__manager.update_config( - config, - dependency[0], - uninstaller, - "Uninstallers" + config, dependency[0], uninstaller, "Uninstallers" ) # Remove entry from task manager @@ -177,20 +172,10 @@ def install(self, config: BottleConfig, dependency: list) -> Result: # Hide installation button and show remove button logging.info(f"Dependency installed: {dependency[0]} in {config.Name}", jn=True) if not uninstaller: - return Result( - status=True, - data={"uninstaller": False} - ) - return Result( - status=True, - data={"uninstaller": True} - ) + return Result(status=True, data={"uninstaller": False}) + return Result(status=True, data={"uninstaller": True}) - def __perform_steps( - self, - config: BottleConfig, - step: dict - ) -> Result: + def __perform_steps(self, config: BottleConfig, step: dict) -> Result: """ This method execute a step in the bottle (e.g. changing the Windows version, installing fonts, etc.) @@ -239,51 +224,27 @@ def __perform_steps( return Result(status=False) if step["action"] == "register_dll": - self.__step_register_dll( - config=config, - step=step - ) + self.__step_register_dll(config=config, step=step) if step["action"] == "override_dll": - self.__step_override_dll( - config=config, - step=step - ) + self.__step_override_dll(config=config, step=step) if step["action"] == "set_register_key": - self.__step_set_register_key( - config=config, - step=step - ) + self.__step_set_register_key(config=config, step=step) if step["action"] == "register_font": - self.__step_register_font( - config=config, - step=step - ) + self.__step_register_font(config=config, step=step) if step["action"] == "replace_font": - self.__step_replace_font( - config=config, - step=step - ) + self.__step_replace_font(config=config, step=step) if step["action"] == "set_windows": - self.__step_set_windows( - config=config, - step=step - ) + self.__step_set_windows(config=config, step=step) if step["action"] == "use_windows": - self.__step_use_windows( - config=config, - step=step - ) + self.__step_use_windows(config=config, step=step) - return Result( - status=True, - data={"uninstaller": uninstaller} - ) + return Result(status=True, data={"uninstaller": uninstaller}) @staticmethod def __get_real_dest(config: BottleConfig, dest: str) -> Union[str, bool]: @@ -323,7 +284,7 @@ def __step_download_archive(self, step: dict): download_url=step.get("url"), file=step.get("file_name"), rename=step.get("rename"), - checksum=step.get("file_checksum") + checksum=step.get("file_checksum"), ) return download @@ -338,7 +299,7 @@ def __step_install_exe_msi(self, config: BottleConfig, step: dict) -> bool: download_url=step.get("url"), file=step.get("file_name"), rename=step.get("rename"), - checksum=step.get("file_checksum") + checksum=step.get("file_checksum"), ) file = step.get("file_name") if step.get("rename"): @@ -354,7 +315,7 @@ def __step_install_exe_msi(self, config: BottleConfig, step: dict) -> bool: config, exec_path=file, args=step.get("arguments"), - environment=step.get("environment") + environment=step.get("environment"), ) executor.run() winedbg.wait_for_process(file) @@ -388,7 +349,7 @@ def __step_cab_extract(self, step: dict): download_url=step.get("url"), file=step.get("file_name"), rename=step.get("rename"), - checksum=step.get("file_checksum") + checksum=step.get("file_checksum"), ) if download: @@ -398,9 +359,7 @@ def __step_cab_extract(self, step: dict): file = step.get("file_name") if not CabExtract().run( - path=os.path.join(Paths.temp, file), - name=file, - destination=dest + path=os.path.join(Paths.temp, file), name=file, destination=dest ): return False else: @@ -411,16 +370,12 @@ def __step_cab_extract(self, step: dict): path = path.replace("temp/", f"{Paths.temp}/") if step.get("rename"): - file_path = os.path.splitext( - f"{step.get('rename')}")[0] + file_path = os.path.splitext(f"{step.get('rename')}")[0] else: - file_path = os.path.splitext( - f"{step.get('file_name')}")[0] + file_path = os.path.splitext(f"{step.get('file_name')}")[0] if not CabExtract().run( - f"{path}/{step.get('file_name')}", - file_path, - destination=dest + f"{path}/{step.get('file_name')}", file_path, destination=dest ): return False @@ -448,9 +403,7 @@ def __step_get_from_cab(self, config: BottleConfig, step: dict): return dest res = CabExtract().run( - path=os.path.join(Paths.temp, source), - files=[file_name], - destination=dest + path=os.path.join(Paths.temp, source), files=[file_name], destination=dest ) if rename: @@ -459,10 +412,7 @@ def __step_get_from_cab(self, config: BottleConfig, step: dict): if os.path.exists(os.path.join(dest, rename)): os.remove(os.path.join(dest, rename)) - shutil.move( - os.path.join(dest, _file_name), - os.path.join(dest, rename) - ) + shutil.move(os.path.join(dest, _file_name), os.path.join(dest, rename)) if not res: return False @@ -474,7 +424,7 @@ def __step_archive_extract(self, step: dict): download_url=step.get("url"), file=step.get("file_name"), rename=step.get("rename"), - checksum=step.get("file_checksum") + checksum=step.get("file_checksum"), ) if not download: @@ -492,8 +442,12 @@ def __step_archive_extract(self, step: dict): os.makedirs(archive_path) try: - patoolib.extract_archive(os.path.join(Paths.temp, file), outdir=archive_path) - if archive_path.endswith(".tar") and os.path.isfile(os.path.join(archive_path, os.path.basename(archive_path))): + patoolib.extract_archive( + os.path.join(Paths.temp, file), outdir=archive_path + ) + if archive_path.endswith(".tar") and os.path.isfile( + os.path.join(archive_path, os.path.basename(archive_path)) + ): tar_path = os.path.join(archive_path, os.path.basename(archive_path)) patoolib.extract_archive(tar_path, outdir=archive_path) except Exception as e: @@ -559,7 +513,9 @@ def __step_copy_dll(self, config: BottleConfig, step: dict): try: shutil.copyfile(_path, _dest) except shutil.SameFileError: - logging.info(f"{_name} already exists at the same version, skipping.") + logging.info( + f"{_name} already exists at the same version, skipping." + ) else: _name = step.get("file_name") _dest = os.path.join(dest, _name) @@ -571,7 +527,9 @@ def __step_copy_dll(self, config: BottleConfig, step: dict): try: shutil.copyfile(os.path.join(path, _name), _dest) except shutil.SameFileError: - logging.info(f"{_name} already exists at the same version, skipping.") + logging.info( + f"{_name} already exists at the same version, skipping." + ) except Exception as e: print(e) @@ -603,23 +561,24 @@ def __step_override_dll(config: BottleConfig, step: dict): for dll in dlls: dll_name = os.path.splitext(os.path.basename(dll))[0] - bundle["HKEY_CURRENT_USER\\Software\\Wine\\DllOverrides"].append({ - "value": dll_name, - "data": step.get("type") - }) + bundle["HKEY_CURRENT_USER\\Software\\Wine\\DllOverrides"].append( + {"value": dll_name, "data": step.get("type")} + ) reg.import_bundle(bundle) return True if step.get("bundle"): - _bundle = {"HKEY_CURRENT_USER\\Software\\Wine\\DllOverrides": step.get("bundle")} + _bundle = { + "HKEY_CURRENT_USER\\Software\\Wine\\DllOverrides": step.get("bundle") + } reg.import_bundle(_bundle) return True reg.add( key="HKEY_CURRENT_USER\\Software\\Wine\\DllOverrides", value=step.get("dll"), - data=step.get("type") + data=step.get("type"), ) return True @@ -631,7 +590,7 @@ def __step_set_register_key(config: BottleConfig, step: dict): key=step.get("key"), value=step.get("value"), data=step.get("data"), - value_type=step.get("type") + value_type=step.get("type"), ) return True @@ -642,7 +601,7 @@ def __step_register_font(config: BottleConfig, step: dict): reg.add( key="HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Fonts", value=step.get("name"), - data=step.get("file") + data=step.get("file"), ) return True @@ -662,7 +621,7 @@ def __step_replace_font(config: BottleConfig, step: dict): key="HKEY_CURRENT_USER\\Software\\Wine\\Fonts\\Replacements", value=r, value_type="", - data=target_font + data=target_font, ) for r in replaces ] diff --git a/bottles/backend/managers/epicgamesstore.py b/bottles/backend/managers/epicgamesstore.py index 1a4a6a17a6f..8a2989592a0 100644 --- a/bottles/backend/managers/epicgamesstore.py +++ b/bottles/backend/managers/epicgamesstore.py @@ -34,7 +34,8 @@ def find_dat_path(config: BottleConfig) -> Union[str, None]: paths = [ os.path.join( ManagerUtils.get_bottle_path(config), - "drive_c/ProgramData/Epic/UnrealEngineLauncher/LauncherInstalled.dat") + "drive_c/ProgramData/Epic/UnrealEngineLauncher/LauncherInstalled.dat", + ) ] for path in paths: @@ -67,23 +68,21 @@ def get_installed_games(config: BottleConfig) -> list: _uri = f"-com.epicgames.launcher://apps/{game['AppName']}?action=launch&silent=true" _args = f"-opengl -SkipBuildPatchPrereq {_uri}" _name = game["InstallLocation"].split("\\")[-1] - _path = "C:\\Program Files (x86)\\Epic Games\\Launcher\\Portal\\Binaries\\Win32\\" \ - "EpicGamesLauncher.exe" + _path = ( + "C:\\Program Files (x86)\\Epic Games\\Launcher\\Portal\\Binaries\\Win32\\" + "EpicGamesLauncher.exe" + ) _executable = _path.split("\\")[-1] _folder = ManagerUtils.get_exe_parent_dir(config, _path) - games.append({ - "executable": _path, - "arguments": _args, - "name": _name, - "path": _path, - "folder": _folder, - "icon": "com.usebottles.bottles-program", - "dxvk": config.Parameters.dxvk, - "vkd3d": config.Parameters.vkd3d, - "dxvk_nvapi": config.Parameters.dxvk_nvapi, - "fsr": config.Parameters.fsr, - "virtual_desktop": config.Parameters.virtual_desktop, - "pulseaudio_latency": config.Parameters.pulseaudio_latency, - "id": str(uuid.uuid4()), - }) + games.append( + { + "executable": _path, + "arguments": _args, + "name": _name, + "path": _path, + "folder": _folder, + "icon": "com.usebottles.bottles-program", + "id": str(uuid.uuid4()), + } + ) return games diff --git a/bottles/backend/managers/importer.py b/bottles/backend/managers/importer.py index 85adbd9a98d..7500f911f2a 100644 --- a/bottles/backend/managers/importer.py +++ b/bottles/backend/managers/importer.py @@ -45,7 +45,9 @@ def search_wineprefixes() -> Result: playonlinux_results = glob(f"{TrdyPaths.playonlinux}/*/") bottlesv1_results = glob(f"{TrdyPaths.bottlesv1}/*/") - results = wine_standard + lutris_results + playonlinux_results + bottlesv1_results + results = ( + wine_standard + lutris_results + playonlinux_results + bottlesv1_results + ) # count results is_wine = len(wine_standard) @@ -68,24 +70,22 @@ def search_wineprefixes() -> Result: # check the drive_c path exists if os.path.isdir(os.path.join(wineprefix, "drive_c")): - wineprefix_lock = os.path.isfile(os.path.join(wineprefix, "bottle.lock")) + wineprefix_lock = os.path.isfile( + os.path.join(wineprefix, "bottle.lock") + ) importer_wineprefixes.append( { "Name": wineprefix_name, "Manager": wineprefix_manager, "Path": wineprefix, - "Lock": wineprefix_lock - }) + "Lock": wineprefix_lock, + } + ) i += 1 logging.info(f"Found {len(importer_wineprefixes)} wine prefixes…") - return Result( - status=True, - data={ - "wineprefixes": importer_wineprefixes - } - ) + return Result(status=True, data={"wineprefixes": importer_wineprefixes}) def import_wineprefix(self, wineprefix: dict) -> Result: """Import wineprefix from external manager and convert in a bottle""" @@ -103,7 +103,7 @@ def import_wineprefix(self, wineprefix: dict) -> Result: # create lockfile in source path logging.info(f"Creating lock file in {wineprefix['Path']}…") - open(f'{wineprefix.get("Path")}/bottle.lock', 'a').close() + open(f'{wineprefix.get("Path")}/bottle.lock', "a").close() # copy wineprefix files in the new bottle command = f"cp -a {wineprefix.get('Path')}/* {bottle_complete_path}/" diff --git a/bottles/backend/managers/installer.py b/bottles/backend/managers/installer.py index 2d5cb6c7341..a000f21d2bd 100644 --- a/bottles/backend/managers/installer.py +++ b/bottles/backend/managers/installer.py @@ -57,9 +57,7 @@ def get_review(self, installer_name, parse: bool = True) -> str: @lru_cache def get_installer( - self, - installer_name: str, - plain: bool = False + self, installer_name: str, plain: bool = False ) -> Union[str, dict, bool]: """ Return an installer manifest from the repository. Use the plain @@ -116,11 +114,11 @@ def __process_local_resources(self, exe_msi_steps, installer): return True def __install_dependencies( - self, - config: BottleConfig, - dependencies: list, - step_fn: callable, - is_final: bool = False + self, + config: BottleConfig, + dependencies: list, + step_fn: callable, + is_final: bool = False, ): """Install a list of dependencies""" _config = config @@ -153,7 +151,9 @@ def __perform_checks(config, checks: dict): _f = os.path.join(bottle_path, "drive_c", f) if not os.path.exists(_f): - logging.error(f"During checks, file {_f} was not found, assuming it is not installed. Aborting.") + logging.error( + f"During checks, file {_f} was not found, assuming it is not installed. Aborting." + ) return False return True @@ -180,7 +180,7 @@ def __perform_steps(self, config: BottleConfig, steps: list): st.get("url"), st.get("file_name"), st.get("rename"), - checksum=st.get("file_checksum") + checksum=st.get("file_checksum"), ) else: download = True @@ -204,7 +204,9 @@ def __perform_steps(self, config: BottleConfig, steps: list): ) executor.run() else: - logging.error(f"Failed to download {st.get('file_name')}, or checksum failed.") + logging.error( + f"Failed to download {st.get('file_name')}, or checksum failed." + ) return False return True @@ -221,7 +223,7 @@ def __step_run_winecommand(config: BottleConfig, step: dict): config, command=command.get("command"), arguments=command.get("arguments"), - minimal=command.get("minimal") + minimal=command.get("minimal"), ) _winecommand.run() @@ -231,11 +233,9 @@ def __step_run_script(config: BottleConfig, step: dict): "!bottle_path": ManagerUtils.get_bottle_path(config), "!bottle_drive": f"{ManagerUtils.get_bottle_path(config)}/drive_c", "!bottle_name": config.Name, - "!bottle_arch": config.Arch - } - preventions = { - "bottle.yml": "Bottle configuration cannot be modified." + "!bottle_arch": config.Arch, } + preventions = {"bottle.yml": "Bottle configuration cannot be modified."} script = step.get("script") for key, value in placeholders.items(): @@ -243,7 +243,9 @@ def __step_run_script(config: BottleConfig, step: dict): for key, value in preventions.items(): if script.find(key) != -1: - logging.error(value, ) + logging.error( + value, + ) return False logging.info(f"Executing installer script…") @@ -252,7 +254,7 @@ def __step_run_script(config: BottleConfig, step: dict): shell=True, cwd=ManagerUtils.get_bottle_path(config), stdout=subprocess.PIPE, - stderr=subprocess.PIPE + stderr=subprocess.PIPE, ).communicate() logging.info(f"Finished executing installer script.") @@ -281,19 +283,27 @@ def __set_parameters(self, config: BottleConfig, new_params: dict): if "dxvk" in new_params and isinstance(new_params["dxvk"], bool): if new_params["dxvk"] != config.Parameters.dxvk: - self.__manager.install_dll_component(_config, "dxvk", remove=not new_params["dxvk"]) + self.__manager.install_dll_component( + _config, "dxvk", remove=not new_params["dxvk"] + ) if "vkd3d" in new_params and isinstance(new_params["vkd3d"], bool): if new_params["vkd3d"] != config.Parameters.vkd3d: - self.__manager.install_dll_component(_config, "vkd3d", remove=not new_params["vkd3d"]) + self.__manager.install_dll_component( + _config, "vkd3d", remove=not new_params["vkd3d"] + ) if "dxvk_nvapi" in new_params and isinstance(new_params["dxvk_nvapi"], bool): if new_params["dxvk_nvapi"] != config.Parameters.dxvk_nvapi: - self.__manager.install_dll_component(_config, "nvapi", remove=not new_params["dxvk_nvapi"]) + self.__manager.install_dll_component( + _config, "nvapi", remove=not new_params["dxvk_nvapi"] + ) if "latencyflex" in new_params and isinstance(new_params["latencyflex"], bool): if new_params["latencyflex"] != config.Parameters.latencyflex: - self.__manager.install_dll_component(_config, "latencyflex", remove=not new_params["latencyflex"]) + self.__manager.install_dll_component( + _config, "latencyflex", remove=not new_params["latencyflex"] + ) # avoid sync type change if not set to "wine" if "sync" in new_params and config.Parameters.sync != "wine": @@ -301,10 +311,7 @@ def __set_parameters(self, config: BottleConfig, new_params: dict): for k, v in new_params.items(): self.__manager.update_config( - config=config, - key=k, - value=v, - scope="Parameters" + config=config, key=k, value=v, scope="Parameters" ) def count_steps(self, installer) -> dict: @@ -333,9 +340,12 @@ def count_steps(self, installer) -> dict: def has_local_resources(self, installer): manifest = self.get_installer(installer[0]) steps = manifest.get("Steps", []) - exe_msi_steps = [s for s in steps - if s.get("action", "") in ["install_exe", "install_msi"] - and s.get("url", "") == "local"] + exe_msi_steps = [ + s + for s in steps + if s.get("action", "") in ["install_exe", "install_msi"] + and s.get("url", "") == "local" + ] if len(exe_msi_steps) == 0: return [] @@ -343,8 +353,14 @@ def has_local_resources(self, installer): files = [s.get("file_name", "") for s in exe_msi_steps] return files - def install(self, config: BottleConfig, installer: dict, step_fn: callable, is_final: bool = True, - local_resources: Optional[dict] = None): + def install( + self, + config: BottleConfig, + installer: dict, + step_fn: callable, + is_final: bool = True, + local_resources: Optional[dict] = None, + ): manifest = self.get_installer(installer[0]) _config = config @@ -366,18 +382,27 @@ def install(self, config: BottleConfig, installer: dict, step_fn: callable, is_f for i in installers: if not self.install(config, i, step_fn, False): logging.error("Failed to install dependent installer(s)") - return Result(False, data={"message": "Failed to install dependent installer(s)"}) + return Result( + False, + data={"message": "Failed to install dependent installer(s)"}, + ) # ask for local resources if local_resources: if not self.__process_local_resources(local_resources, installer): - return Result(False, data={"message": "Local resources not found or invalid"}) + return Result( + False, data={"message": "Local resources not found or invalid"} + ) # install dependencies if dependencies: logging.info("Installing dependencies") - if not self.__install_dependencies(_config, dependencies, step_fn, is_final): - return Result(False, data={"message": "Dependencies installation failed."}) + if not self.__install_dependencies( + _config, dependencies, step_fn, is_final + ): + return Result( + False, data={"message": "Dependencies installation failed."} + ) # set parameters if parameters: @@ -394,7 +419,9 @@ def install(self, config: BottleConfig, installer: dict, step_fn: callable, is_f step_fn() if not self.__perform_steps(_config, steps): - return Result(False, data={"message": "Installer is not well configured."}) + return Result( + False, data={"message": "Installer is not well configured."} + ) # execute checks if checks: @@ -402,12 +429,19 @@ def install(self, config: BottleConfig, installer: dict, step_fn: callable, is_f if is_final: step_fn() if not self.__perform_checks(_config, checks): - return Result(False, data={"message": "Checks failed, the program is not installed."}) + return Result( + False, + data={ + "message": "Checks failed, the program is not installed." + }, + ) # register executable - if executable['path'].startswith("userdir/"): + if executable["path"].startswith("userdir/"): _userdir = WineUtils.get_user_dir(bottle) - executable['path'] = executable['path'].replace("userdir/", f"/users/{_userdir}/") + executable["path"] = executable["path"].replace( + "userdir/", f"/users/{_userdir}/" + ) _path = f'C:\\{executable["path"]}'.replace("/", "\\") _uuid = str(uuid.uuid4()) @@ -416,7 +450,7 @@ def install(self, config: BottleConfig, installer: dict, step_fn: callable, is_f "arguments": executable.get("arguments", ""), "name": executable["name"], "path": _path, - "id": _uuid + "id": _uuid, } if "dxvk" in executable: @@ -426,7 +460,9 @@ def install(self, config: BottleConfig, installer: dict, step_fn: callable, is_f if "dxvk_nvapi" in executable: _program["dxvk_nvapi"] = executable["dxvk_nvapi"] - duplicates = [k for k, v in config.External_Programs.items() if v["path"] == _path] + duplicates = [ + k for k, v in config.External_Programs.items() if v["path"] == _path + ] ext = config.External_Programs if duplicates: @@ -434,25 +470,22 @@ def install(self, config: BottleConfig, installer: dict, step_fn: callable, is_f del ext[d] ext[_uuid] = _program self.__manager.update_config( - config=config, - key="External_Programs", - value=ext + config=config, key="External_Programs", value=ext ) else: self.__manager.update_config( - config=config, - key=_uuid, - value=_program, - scope="External_Programs" + config=config, key=_uuid, value=_program, scope="External_Programs" ) # create Desktop entry bottles_icons_path = os.path.join(ManagerUtils.get_bottle_path(config), "icons") - icon_path = os.path.join(bottles_icons_path, executable.get('icon')) + icon_path = os.path.join(bottles_icons_path, executable.get("icon")) ManagerUtils.create_desktop_entry(_config, _program, False, icon_path) if is_final: step_fn() - logging.info(f"Program installed: {manifest['Name']} in {config.Name}.", jn=True) + logging.info( + f"Program installed: {manifest['Name']} in {config.Name}.", jn=True + ) return Result(True) diff --git a/bottles/backend/managers/journal.py b/bottles/backend/managers/journal.py index 3fdcf49298b..d993f763226 100644 --- a/bottles/backend/managers/journal.py +++ b/bottles/backend/managers/journal.py @@ -28,6 +28,7 @@ class JournalSeverity: """Represents the severity of a journal entry.""" + DEBUG = "debug" INFO = "info" WARNING = "warning" @@ -63,7 +64,12 @@ def __get_journal() -> dict: return {} try: - journal = {k: v for k, v in sorted(journal.items(), key=lambda item: item[1]["timestamp"], reverse=True)} + journal = { + k: v + for k, v in sorted( + journal.items(), key=lambda item: item[1]["timestamp"], reverse=True + ) + } except (KeyError, TypeError): journal = {} @@ -80,7 +86,9 @@ def __clean_old(): if event.get("timestamp", None) is None: latest_datetime = datetime.strptime(latest, "%Y-%m-%d %H:%M:%S") else: - latest_datetime = datetime.strptime(event["timestamp"], "%Y-%m-%d %H:%M:%S") + latest_datetime = datetime.strptime( + event["timestamp"], "%Y-%m-%d %H:%M:%S" + ) latest = event["timestamp"] if latest_datetime < datetime.now() - timedelta(days=30): @@ -149,7 +157,9 @@ def __filter_by_date(journal: dict, period: str): end = start + timedelta(days=1) for event_id, event in journal.items(): - timestamp = datetime.strptime(event["timestamp"], "%Y-%m-%d %H:%M:%S").date() + timestamp = datetime.strptime( + event["timestamp"], "%Y-%m-%d %H:%M:%S" + ).date() if start <= timestamp <= end: _journal[event_id] = event @@ -175,7 +185,7 @@ def write(severity: JournalSeverity, message: str): journal[event_id] = { "severity": severity, "message": message, - "timestamp": now.strftime("%Y-%m-%d %H:%M:%S") + "timestamp": now.strftime("%Y-%m-%d %H:%M:%S"), } JournalManager.__save_journal(journal) JournalManager.__clean_old() diff --git a/bottles/backend/managers/library.py b/bottles/backend/managers/library.py index 90a3f68c23d..95f28fff53e 100644 --- a/bottles/backend/managers/library.py +++ b/bottles/backend/managers/library.py @@ -45,11 +45,11 @@ def load_library(self, silent=False): Loads data from the library.yml file. """ if not os.path.exists(self.library_path): - logging.warning('Library file not found, creating new one') + logging.warning("Library file not found, creating new one") self.__library = {} self.save_library() else: - with open(self.library_path, 'r') as library_file: + with open(self.library_path, "r") as library_file: self.__library = yaml.load(library_file) if self.__library is None: @@ -67,30 +67,32 @@ def add_to_library(self, data: dict, config: BottleConfig): Adds a new entry to the library.yml file. """ if self.__already_in_library(data): - logging.warning(f'Entry already in library, nothing to add: {data}') + logging.warning(f"Entry already in library, nothing to add: {data}") return _uuid = str(uuid.uuid4()) - logging.info(f'Adding new entry to library: {_uuid}') + logging.info(f"Adding new entry to library: {_uuid}") if not data.get("thumbnail"): - data['thumbnail'] = SteamGridDBManager.get_game_grid(data['name'], config) + data["thumbnail"] = SteamGridDBManager.get_game_grid(data["name"], config) self.__library[_uuid] = data self.save_library() def download_thumbnail(self, _uuid: str, config: BottleConfig): if not self.__library.get(_uuid): - logging.warning(f'Entry not found in library, can\'t download thumbnail: {_uuid}') + logging.warning( + f"Entry not found in library, can't download thumbnail: {_uuid}" + ) return False data = self.__library.get(_uuid) - value = SteamGridDBManager.get_game_grid(data['name'], config) + value = SteamGridDBManager.get_game_grid(data["name"], config) if not value: return False - self.__library[_uuid]['thumbnail'] = value + self.__library[_uuid]["thumbnail"] = value self.save_library() return True @@ -99,7 +101,7 @@ def __already_in_library(self, data: dict): Checks if the entry UUID is already in the library.yml file. """ for k, v in self.__library.items(): - if v['id'] == data['id']: + if v["id"] == data["id"]: return True return False @@ -109,21 +111,21 @@ def remove_from_library(self, _uuid: str): Removes an entry from the library.yml file. """ if self.__library.get(_uuid): - logging.info(f'Removing entry from library: {_uuid}') + logging.info(f"Removing entry from library: {_uuid}") del self.__library[_uuid] self.save_library() return - logging.warning(f'Entry not found in library, nothing to remove: {_uuid}') + logging.warning(f"Entry not found in library, nothing to remove: {_uuid}") def save_library(self, silent=False): """ Saves the library.yml file. """ - with open(self.library_path, 'w') as library_file: + with open(self.library_path, "w") as library_file: yaml.dump(self.__library, library_file) - + if not silent: - logging.info(f'Library saved') + logging.info(f"Library saved") def get_library(self): """ diff --git a/bottles/backend/managers/manager.py b/bottles/backend/managers/manager.py index 97e0174c501..5f99fb67827 100644 --- a/bottles/backend/managers/manager.py +++ b/bottles/backend/managers/manager.py @@ -103,7 +103,13 @@ class Manager(metaclass=Singleton): supported_dependencies = {} supported_installers = {} - def __init__(self, g_settings: Any = None, check_connection: bool = True, is_cli: bool = False, **kwargs): + def __init__( + self, + g_settings: Any = None, + check_connection: bool = True, + is_cli: bool = False, + **kwargs, + ): super().__init__(**kwargs) times = {"start": time.time()} @@ -111,13 +117,15 @@ def __init__(self, g_settings: Any = None, check_connection: bool = True, is_cli # common variables self.is_cli = is_cli self.settings = g_settings or GSettingsStub - self.utils_conn = ConnectionUtils(force_offline=self.is_cli or self.settings.get_boolean("force-offline")) + self.utils_conn = ConnectionUtils( + force_offline=self.is_cli or self.settings.get_boolean("force-offline") + ) self.data_mgr = DataManager() _offline = True - + if check_connection: _offline = not self.utils_conn.check_connection() - + # validating user-defined Paths.bottles if user_bottles_path := self.data_mgr.get(UserDataKeys.CustomBottlesPath): if os.path.exists(user_bottles_path): @@ -129,11 +137,11 @@ def __init__(self, g_settings: Any = None, check_connection: bool = True, is_cli ) # sub-managers - self.repository_manager = RepositoryManager(get_index= not _offline) + self.repository_manager = RepositoryManager(get_index=not _offline) if self.repository_manager.aborted_connections > 0: self.utils_conn.status = False _offline = True - + times["RepositoryManager"] = time.time() self.versioning_manager = VersioningManager(self) times["VersioningManager"] = time.time() @@ -145,7 +153,6 @@ def __init__(self, g_settings: Any = None, check_connection: bool = True, is_cli self.steam_manager = SteamManager() times["SteamManager"] = time.time() - if not self.is_cli: times.update(self.checks(install_latest=False, first_run=True).data) else: @@ -244,7 +251,10 @@ def check_app_dirs(self): logging.info("Bottles path doesn't exist, creating now.") os.makedirs(Paths.bottles, exist_ok=True) - if self.settings.get_boolean("steam-proton-support") and self.steam_manager.is_steam_supported: + if ( + self.settings.get_boolean("steam-proton-support") + and self.steam_manager.is_steam_supported + ): if not os.path.isdir(Paths.steam): logging.info("Steam path doesn't exist, creating now.") os.makedirs(Paths.steam, exist_ok=True) @@ -335,14 +345,9 @@ def remove_dependency(self, config: BottleConfig, dependency: list): config.Installed_Dependencies.remove(dependency) self.update_config( - config, - key="Installed_Dependencies", - value=config.Installed_Dependencies - ) - return Result( - status=True, - data={"removed": True} + config, key="Installed_Dependencies", value=config.Installed_Dependencies ) + return Result(status=True, data={"removed": True}) def check_runners(self, install_latest: bool = True) -> bool: """ @@ -368,15 +373,15 @@ def check_runners(self, install_latest: bool = True) -> bool: # check system wine if shutil.which("wine") is not None: - ''' + """ If the Wine command is available, get the runner version and add it to the runners_available list. - ''' - version = subprocess.Popen( - "wine --version", - stdout=subprocess.PIPE, - shell=True - ).communicate()[0].decode("utf-8") + """ + version = ( + subprocess.Popen("wine --version", stdout=subprocess.PIPE, shell=True) + .communicate()[0] + .decode("utf-8") + ) version = "sys-" + version.split("\n")[0].split(" ")[0] runners_available.append(version) @@ -393,7 +398,7 @@ def check_runners(self, install_latest: bool = True) -> bool: "vaniglia": [], "lutris": [], "others": [], - "sys-": [] + "sys-": [], } for i in runners_available: @@ -407,9 +412,11 @@ def check_runners(self, install_latest: bool = True) -> bool: self.runners_available = [x for l in list(runners_order.values()) for x in l] if len(self.runners_available) > 0: - logging.info("Runners found:\n - {0}".format("\n - ".join(self.runners_available))) + logging.info( + "Runners found:\n - {0}".format("\n - ".join(self.runners_available)) + ) - tmp_runners = [x for x in self.runners_available if not x.startswith('sys-')] + tmp_runners = [x for x in self.runners_available if not x.startswith("sys-")] if len(tmp_runners) == 0 and install_latest: logging.warning("No managed runners found.") @@ -466,7 +473,9 @@ def check_runtimes(self, install_latest: bool = True) -> bool: return True return False - def check_winebridge(self, install_latest: bool = True, update: bool = False) -> bool: + def check_winebridge( + self, install_latest: bool = True, update: bool = False + ) -> bool: self.winebridge_available = [] winebridge = os.listdir(Paths.winebridge) @@ -515,7 +524,9 @@ def check_latencyflex(self, install_latest: bool = True) -> bool: self.latencyflex_available = res return res is not False - def get_offline_components(self, component_type: str, extra_name_check: str = "") -> list: + def get_offline_components( + self, component_type: str, extra_name_check: str = "" + ) -> list: components = { "dxvk": { "available": self.dxvk_available, @@ -540,25 +551,38 @@ def get_offline_components(self, component_type: str, extra_name_check: str = "" "runner:proton": { "available": self.runners_available, "supported": self.supported_proton_runners, - } + }, } if component_type not in components: logging.warning(f"Unknown component type found: {component_type}") raise ValueError("Component type not supported.") component_list = components[component_type] - offline_components = list(set(component_list["available"]).difference(component_list["supported"].keys())) + offline_components = list( + set(component_list["available"]).difference( + component_list["supported"].keys() + ) + ) if component_type == "runner": - offline_components = [ runner for runner in offline_components \ - if not runner.startswith("sys-") and \ - not SteamUtils.is_proton(ManagerUtils.get_runner_path(runner)) ] + offline_components = [ + runner + for runner in offline_components + if not runner.startswith("sys-") + and not SteamUtils.is_proton(ManagerUtils.get_runner_path(runner)) + ] elif component_type == "runner:proton": - offline_components = [ runner for runner in offline_components \ - if SteamUtils.is_proton(ManagerUtils.get_runner_path(runner)) ] - - if extra_name_check and extra_name_check not in component_list["available"] \ - and extra_name_check not in component_list["supported"]: + offline_components = [ + runner + for runner in offline_components + if SteamUtils.is_proton(ManagerUtils.get_runner_path(runner)) + ] + + if ( + extra_name_check + and extra_name_check not in component_list["available"] + and extra_name_check not in component_list["supported"] + ): offline_components.append(extra_name_check) try: @@ -566,33 +590,35 @@ def get_offline_components(self, component_type: str, extra_name_check: str = "" except ValueError: return sorted(offline_components, reverse=True) - def __check_component(self, component_type: str, install_latest: bool = True) -> Union[bool, list]: + def __check_component( + self, component_type: str, install_latest: bool = True + ) -> Union[bool, list]: components = { "dxvk": { "available": self.dxvk_available, "supported": self.supported_dxvk, - "path": Paths.dxvk + "path": Paths.dxvk, }, "vkd3d": { "available": self.vkd3d_available, "supported": self.supported_vkd3d, - "path": Paths.vkd3d + "path": Paths.vkd3d, }, "nvapi": { "available": self.nvapi_available, "supported": self.supported_nvapi, - "path": Paths.nvapi + "path": Paths.nvapi, }, "latencyflex": { "available": self.latencyflex_available, "supported": self.supported_latencyflex, - "path": Paths.latencyflex + "path": Paths.latencyflex, }, "runtime": { "available": self.runtimes_available, "supported": self.supported_runtimes, - "path": Paths.runtimes - } + "path": Paths.runtimes, + }, } if component_type not in components: @@ -603,10 +629,11 @@ def __check_component(self, component_type: str, install_latest: bool = True) -> component["available"] = os.listdir(component["path"]) if len(component["available"]) > 0: - logging.info("{0}s found:\n - {1}".format( - component_type.capitalize(), - "\n - ".join(component["available"]) - )) + logging.info( + "{0}s found:\n - {1}".format( + component_type.capitalize(), "\n - ".join(component["available"]) + ) + ) if len(component["available"]) == 0 and install_latest: logging.warning(f"No {component_type} found.") @@ -646,21 +673,17 @@ def get_programs(self, config: BottleConfig) -> List[dict]: bottle = ManagerUtils.get_bottle_path(config) winepath = WinePath(config) - results = glob( - f"{bottle}/drive_c/users/*/Desktop/*.lnk", - recursive=True - ) + results = glob(f"{bottle}/drive_c/users/*/Desktop/*.lnk", recursive=True) results += glob( - f"{bottle}/drive_c/users/*/Start Menu/Programs/**/*.lnk", - recursive=True + f"{bottle}/drive_c/users/*/Start Menu/Programs/**/*.lnk", recursive=True ) results += glob( f"{bottle}/drive_c/ProgramData/Microsoft/Windows/Start Menu/Programs/**/*.lnk", - recursive=True + recursive=True, ) results += glob( f"{bottle}/drive_c/users/*/AppData/Roaming/Microsoft/Windows/Start Menu/Programs/**/*.lnk", - recursive=True + recursive=True, ) installed_programs = [] ignored_patterns = [ @@ -676,44 +699,48 @@ def get_programs(self, config: BottleConfig) -> List[dict]: "OriginEr", "*website*", "*web site*", - "*user_manual*" + "*user_manual*", ] found = [] ext_programs = config.External_Programs - ''' + """ Process External_Programs - ''' + """ for _, _program in ext_programs.items(): found.append(_program["executable"]) if winepath.is_windows(_program["path"]): - program_folder = ManagerUtils.get_exe_parent_dir(config, _program["path"]) + program_folder = ManagerUtils.get_exe_parent_dir( + config, _program["path"] + ) else: program_folder = os.path.dirname(_program["path"]) - installed_programs.append({ - "executable": _program["executable"], - "arguments": _program.get("arguments", ""), - "name": _program["name"], - "path": _program["path"], - "folder": _program.get("folder", program_folder), - "icon": "com.usebottles.bottles-program", - "script": _program.get("script"), - "dxvk": _program.get("dxvk", config.Parameters.dxvk), - "vkd3d": _program.get("vkd3d", config.Parameters.vkd3d), - "dxvk_nvapi": _program.get("dxvk_nvapi", config.Parameters.dxvk_nvapi), - "fsr": _program.get("fsr", config.Parameters.fsr), - "pulseaudio_latency": _program.get("pulseaudio_latency", config.Parameters.pulseaudio_latency), - "virtual_desktop": _program.get("virtual_desktop", config.Parameters.virtual_desktop), - "removed": _program.get("removed"), - "id": _program.get("id") - }) + installed_programs.append( + { + "executable": _program.get("executable"), + "arguments": _program.get("arguments"), + "name": _program.get("name"), + "path": _program.get("path"), + "folder": _program.get("folder", program_folder), + "icon": "com.usebottles.bottles-program", + "script": _program.get("script"), + "dxvk": _program.get("dxvk"), + "vkd3d": _program.get("vkd3d"), + "dxvk_nvapi": _program.get("dxvk_nvapi"), + "fsr": _program.get("fsr"), + "pulseaudio_latency": _program.get("pulseaudio_latency"), + "virtual_desktop": _program.get("virtual_desktop"), + "removed": _program.get("removed"), + "id": _program.get("id"), + } + ) for program in results: - ''' + """ for each .lnk file, try to get the executable path and - append it to the installed_programs list with its icon, + append it to the installed_programs list with its icon, skip if the path contains the "Uninstall" word. - ''' + """ executable_path = LnkUtils.get_data(program) if executable_path in [None, ""]: continue @@ -732,48 +759,46 @@ def get_programs(self, config: BottleConfig) -> List[dict]: continue path_check = os.path.join( - bottle, - executable_path.replace("C:\\", "drive_c\\").replace("\\", "/") + bottle, executable_path.replace("C:\\", "drive_c\\").replace("\\", "/") ) if os.path.exists(path_check): if executable_name not in found: - installed_programs.append({ - "executable": executable_name, - "arguments": "", - "name": executable_name.rsplit('.',1)[0], - "path": executable_path, - "folder": program_folder, - "icon": "com.usebottles.bottles-program", - "id": str(uuid.uuid4()), - "script": "", - "dxvk": config.Parameters.dxvk, - "vkd3d": config.Parameters.vkd3d, - "dxvk_nvapi": config.Parameters.dxvk_nvapi, - "fsr": config.Parameters.fsr, - "pulseaudio_latency": config.Parameters.pulseaudio_latency, - "virtual_desktop": config.Parameters.virtual_desktop, - "auto_discovered": True - }) + installed_programs.append( + { + "executable": executable_name, + "arguments": "", + "name": executable_name.rsplit(".", 1)[0], + "path": executable_path, + "folder": program_folder, + "icon": "com.usebottles.bottles-program", + "id": str(uuid.uuid4()), + "auto_discovered": True, + } + ) found.append(executable_name) win_steam_manager = SteamManager(config, is_windows=True) - if self.settings.get_boolean("steam-programs") \ - and win_steam_manager.is_steam_supported: + if ( + self.settings.get_boolean("steam-programs") + and win_steam_manager.is_steam_supported + ): programs_names = [p.get("name", "") for p in installed_programs] for app in win_steam_manager.get_installed_apps_as_programs(): if app["name"] not in programs_names: installed_programs.append(app) - if self.settings.get_boolean("epic-games") \ - and EpicGamesStoreManager.is_epic_supported(config): + if self.settings.get_boolean( + "epic-games" + ) and EpicGamesStoreManager.is_epic_supported(config): programs_names = [p.get("name", "") for p in installed_programs] for app in EpicGamesStoreManager.get_installed_games(config): if app["name"] not in programs_names: installed_programs.append(app) - if self.settings.get_boolean("ubisoft-connect") \ - and UbisoftConnectManager.is_uconnect_supported(config): + if self.settings.get_boolean( + "ubisoft-connect" + ) and UbisoftConnectManager.is_uconnect_supported(config): programs_names = [p.get("name", "") for p in installed_programs] for app in UbisoftConnectManager.get_installed_games(config): if app["name"] not in programs_names: @@ -802,7 +827,9 @@ def process_bottle(bottle): try: placeholder_yaml = yaml.load(f) if placeholder_yaml.get("Path"): - _config = os.path.join(placeholder_yaml.get("Path"), "bottle.yml") + _config = os.path.join( + placeholder_yaml.get("Path"), "bottle.yml" + ) else: raise ValueError("Missing Path in placeholder.yml") except (yaml.YAMLError, ValueError): @@ -827,10 +854,12 @@ def process_bottle(bottle): # if the folder name is "illegal" accross all platforms, rename the folder # "universal" platform works for all filesystem/OSes - sane_name = pathvalidate.sanitize_filepath(_name, platform='universal') + sane_name = pathvalidate.sanitize_filepath(_name, platform="universal") if config.Custom_Path is False: # There shouldn't be problems with this if config.Path != _name or sane_name != _name: - logging.warning("Illegal bottle folder or mismatch between config \"Path\" and folder name") + logging.warning( + 'Illegal bottle folder or mismatch between config "Path" and folder name' + ) if sane_name != _name: # This hopefully doesn't happen, but it's managed logging.warning(f"Broken path in bottle {_name}, fixing...") @@ -840,35 +869,29 @@ def process_bottle(bottle): return config.Path = sane_name - self.update_config( - config=config, - key="Path", - value=sane_name - ) + self.update_config(config=config, key="Path", value=sane_name) sample = BottleConfig() miss_keys = sample.keys() - config.keys() for key in miss_keys: logging.warning(f"Key {key} is missing for bottle {_name}, updating…") - self.update_config( - config=config, - key=key, - value=sample[key] - ) + self.update_config(config=config, key=key, value=sample[key]) miss_params_keys = sample.Parameters.keys() - config.Parameters.keys() for key in miss_params_keys: - ''' + """ For each missing key in the bottle configuration, set it to the default value. - ''' - logging.warning(f"Parameters key {key} is missing for bottle {_name}, updating…") + """ + logging.warning( + f"Parameters key {key} is missing for bottle {_name}, updating…" + ) self.update_config( config=config, key=key, value=sample.Parameters[key], - scope="Parameters" + scope="Parameters", ) self.local_bottles[config.Name] = config @@ -888,17 +911,26 @@ def process_bottle(bottle): # if one or more already exist, it will fail silently as there # is no need to create them again. try: - shutil.move(os.path.join(_bottle, c), os.path.join(_bottle, "cache", "dxvk_state")) + shutil.move( + os.path.join(_bottle, c), + os.path.join(_bottle, "cache", "dxvk_state"), + ) except shutil.Error: pass elif "vkd3d-proton.cache" in c: try: - shutil.move(os.path.join(_bottle, c), os.path.join(_bottle, "cache", "vkd3d_shader")) + shutil.move( + os.path.join(_bottle, c), + os.path.join(_bottle, "cache", "vkd3d_shader"), + ) except shutil.Error: pass elif c == "GLCache": try: - shutil.move(os.path.join(_bottle, c), os.path.join(_bottle, "cache", "gl_shader")) + shutil.move( + os.path.join(_bottle, c), + os.path.join(_bottle, "cache", "gl_shader"), + ) except shutil.Error: pass @@ -906,30 +938,34 @@ def process_bottle(bottle): NVAPIComponent.check_bottle_nvngx(_bottle, config) for b in bottles: - ''' + """ For each bottle add the path name to the `local_bottles` variable and append the config. - ''' + """ process_bottle(b) if len(self.local_bottles) > 0 and not silent: - logging.info("Bottles found:\n - {0}".format("\n - ".join(self.local_bottles))) + logging.info( + "Bottles found:\n - {0}".format("\n - ".join(self.local_bottles)) + ) - if self.settings.get_boolean("steam-proton-support") \ - and self.steam_manager.is_steam_supported \ - and not self.is_cli: + if ( + self.settings.get_boolean("steam-proton-support") + and self.steam_manager.is_steam_supported + and not self.is_cli + ): self.steam_manager.update_bottles() self.local_bottles.update(self.steam_manager.list_prefixes()) # Update parameters in bottle config def update_config( - self, - config: BottleConfig, - key: str, - value: Any, - scope: str = "", - remove: bool = False, - fallback: bool = False + self, + config: BottleConfig, + key: str, + value: Any, + scope: str = "", + remove: bool = False, + fallback: bool = False, ) -> Result[dict]: """ Update parameters in bottle config. Use the scope argument to @@ -947,11 +983,11 @@ def update_config( bottle_path = ManagerUtils.get_bottle_path(config) if key == "sync": - ''' + """ Workaround Sync type change requires wineserver restart or wine will fail to execute any command. - ''' + """ wineboot.kill() wineserver.wait() @@ -985,54 +1021,50 @@ def create_bottle_from_config(self, config: BottleConfig) -> bool: sample = BottleConfig() for key in sample.keys(): - ''' + """ If the key is not in the configuration sample, set it to the default value. - ''' + """ if key not in config.keys(): - self.update_config( - config=config, - key=key, - value=sample[key] - ) + self.update_config(config=config, key=key, value=sample[key]) if config.Runner not in self.runners_available: - ''' + """ If the runner is not in the list of available runners, set it to latest Soda. If there is no Soda, set it to the first one. - ''' + """ config.Runner = self.get_latest_runner() if config.DXVK not in self.dxvk_available: - ''' + """ If the DXVK is not in the list of available DXVKs, set it to highest version which is the first in the list. - ''' + """ config.DXVK = self.dxvk_available[0] if config.VKD3D not in self.vkd3d_available: - ''' + """ If the VKD3D is not in the list of available VKD3Ds, set it to highest version which is the first in the list. - ''' + """ config.VKD3D = self.vkd3d_available[0] if config.NVAPI not in self.nvapi_available: - ''' + """ If the NVAPI is not in the list of available NVAPIs, set it to highest version which is the first in the list. - ''' + """ config.NVAPI = self.nvapi_available[0] # create the bottle path bottle_path = os.path.join(Paths.bottles, config.Name) if not os.path.exists(bottle_path): - ''' + """ If the bottle does not exist, create it, else append a random number to the name. - ''' + """ os.makedirs(bottle_path) else: rnd = random.randint(100, 200) @@ -1041,59 +1073,68 @@ def create_bottle_from_config(self, config: BottleConfig) -> bool: config.Path = f"{config.Path}__{rnd}" os.makedirs(bottle_path) + # Pre-create drive_c directory and set the case-fold flag + bottle_drive_c = os.path.join(bottle_path, "drive_c") + os.makedirs(bottle_drive_c) + FileUtils.chattr_f(bottle_drive_c) + # write the bottle config file saved = config.dump(os.path.join(bottle_path, "bottle.yml")) if not saved.status: return False if config.Parameters.dxvk: - ''' + """ If DXVK is enabled, execute the installation script. - ''' + """ self.install_dll_component(config, "dxvk") if config.Parameters.dxvk_nvapi: - ''' + """ If NVAPI is enabled, execute the substitution of DLLs. - ''' + """ self.install_dll_component(config, "nvapi") if config.Parameters.vkd3d: - ''' + """ If the VKD3D parameter is set to True, install it in the new bottle. - ''' + """ self.install_dll_component(config, "vkd3d") for dependency in config.Installed_Dependencies: - ''' + """ Install each declared dependency in the new bottle. - ''' + """ if dependency in self.supported_dependencies.keys(): dep = [dependency, self.supported_dependencies[dependency]] res = self.dependency_manager.install(config, dep) if not res.ok: - logging.error(_("Failed to install dependency: %s") % dep.get("Description", "n/a"), jn=True) + logging.error( + _("Failed to install dependency: %s") + % dep.get("Description", "n/a"), + jn=True, + ) return False logging.info(f"New bottle from config created: {config.Path}") self.update_bottles(silent=True) return True def create_bottle( - self, - name, - environment: str, - path: str = "", - runner: str = False, - dxvk: bool = False, - vkd3d: bool = False, - nvapi: bool = False, - latencyflex: bool = False, - versioning: bool = False, - sandbox: bool = False, - fn_logger: callable = None, - arch: str = "win64", - custom_environment: Optional[str] = None + self, + name, + environment: str, + path: str = "", + runner: str = False, + dxvk: bool = False, + vkd3d: bool = False, + nvapi: bool = False, + latencyflex: bool = False, + versioning: bool = False, + sandbox: bool = False, + fn_logger: callable = None, + arch: str = "win64", + custom_environment: Optional[str] = None, ) -> Result[dict]: """ Create a new bottle from the given arguments. @@ -1120,7 +1161,7 @@ def components_check(): len(self.dxvk_available), len(self.vkd3d_available), len(self.nvapi_available), - len(self.latencyflex_available) + len(self.latencyflex_available), ]: logging.error("Missing essential components. Installing…") log_update(_("Missing essential components. Installing…")) @@ -1168,7 +1209,9 @@ def components_check(): # define bottle parameters bottle_name = name bottle_name_path = bottle_name.replace(" ", "-") - bottle_name_path = pathvalidate.sanitize_filename(bottle_name_path, platform="universal") + bottle_name_path = pathvalidate.sanitize_filename( + bottle_name_path, platform="universal" + ) # get bottle path if path == "": @@ -1181,10 +1224,10 @@ def components_check(): # if another bottle with same path exists, append a random number if os.path.exists(bottle_complete_path): - ''' + """ if bottle path already exists, create a new one using the name and a random number. - ''' + """ rnd = random.randint(100, 200) bottle_name_path = f"{bottle_name_path}__{rnd}" bottle_complete_path = f"{bottle_complete_path}__{rnd}" @@ -1198,8 +1241,14 @@ def components_check(): # create the bottle directory try: os.makedirs(bottle_complete_path) + # Pre-create drive_c directory and set the case-fold flag + bottle_drive_c = os.path.join(bottle_complete_path, "drive_c") + os.makedirs(bottle_drive_c) + FileUtils.chattr_f(bottle_drive_c) except: - logging.error(f"Failed to create bottle directory: {bottle_complete_path}", jn=True) + logging.error( + f"Failed to create bottle directory: {bottle_complete_path}", jn=True + ) log_update(_("Failed to create bottle directory.")) return Result(False) @@ -1211,7 +1260,10 @@ def components_check(): placeholder = {"Path": bottle_complete_path} f.write(yaml.dump(placeholder)) except: - logging.error(f"Failed to create placeholder directory/file at: {placeholder_dir}", jn=True) + logging.error( + f"Failed to create placeholder directory/file at: {placeholder_dir}", + jn=True, + ) log_update(_("Failed to create placeholder directory/file.")) return Result(False) @@ -1257,10 +1309,10 @@ def components_check(): log_update(_("Wine config updated!")) if "FLATPAK_ID" in os.environ or sandbox: - ''' - If running as Flatpak, or sandbox flag is set to True, unlink home + """ + If running as Flatpak, or sandbox flag is set to True, unlink home directories and make them as folders. - ''' + """ if "FLATPAK_ID": log_update(_("Running as Flatpak, sandboxing userdir…")) if sandbox: @@ -1287,7 +1339,9 @@ def components_check(): if os.path.islink(_dir_path): links.append(_dir_path) - _win_dir = os.path.join(_user_dir, "AppData", "Roaming", "Microsoft", "Windows") + _win_dir = os.path.join( + _user_dir, "AppData", "Roaming", "Microsoft", "Windows" + ) if os.path.isdir(_win_dir): for _dir in os.listdir(_win_dir): _dir_path = os.path.join(_win_dir, _dir) @@ -1306,8 +1360,9 @@ def components_check(): if not template and not custom_environment: logging.info("Setting Windows version…") log_update(_("Setting Windows version…")) - if "soda" not in runner_name.lower() \ - and "caffe" not in runner_name.lower(): # Caffe/Soda came with win10 by default + if ( + "soda" not in runner_name.lower() and "caffe" not in runner_name.lower() + ): # Caffe/Soda came with win10 by default rk.set_windows(config.Windows) wineboot.update() @@ -1329,7 +1384,7 @@ def components_check(): reg.add( key="HKEY_CURRENT_USER\\Software\\Wine\\DllOverrides", value=_dll, - data="" + data="", ) # apply environment configuration @@ -1360,24 +1415,31 @@ def components_check(): if prm in env.get("Parameters", {}): config.Parameters[prm] = env["Parameters"][prm] - if (not template and config.Parameters.dxvk) \ - or (template and template["config"]["DXVK"] != dxvk): + if (not template and config.Parameters.dxvk) or ( + template and template["config"]["DXVK"] != dxvk + ): # perform dxvk installation if configured logging.info("Installing DXVK…") log_update(_("Installing DXVK…")) self.install_dll_component(config, "dxvk", version=dxvk_name) template_updated = True - if not template and config.Parameters.vkd3d \ - or (template and template["config"]["VKD3D"] != vkd3d): + if ( + not template + and config.Parameters.vkd3d + or (template and template["config"]["VKD3D"] != vkd3d) + ): # perform vkd3d installation if configured logging.info("Installing VKD3D…") log_update(_("Installing VKD3D…")) self.install_dll_component(config, "vkd3d", version=vkd3d_name) template_updated = True - if not template and config.Parameters.dxvk_nvapi \ - or (template and template["config"]["NVAPI"] != nvapi): + if ( + not template + and config.Parameters.dxvk_nvapi + or (template and template["config"]["NVAPI"] != nvapi) + ): if GPUUtils.is_gpu(GPUVendors.NVIDIA): # perform nvapi installation if configured logging.info("Installing DXVK-NVAPI…") @@ -1390,11 +1452,21 @@ def components_check(): continue if dep in self.supported_dependencies: _dep = self.supported_dependencies[dep] - log_update(_("Installing dependency: %s …") % _dep.get("Description", "n/a")) + log_update( + _("Installing dependency: %s …") + % _dep.get("Description", "n/a") + ) res = self.dependency_manager.install(config, [dep, _dep]) if not res.ok: - logging.error(_("Failed to install dependency: %s") % _dep.get("Description", "n/a"), jn=True) - log_update(_("Failed to install dependency: %s") % _dep.get("Description", "n/a")) + logging.error( + _("Failed to install dependency: %s") + % _dep.get("Description", "n/a"), + jn=True, + ) + log_update( + _("Failed to install dependency: %s") + % _dep.get("Description", "n/a") + ) return Result(False) template_updated = True @@ -1405,10 +1477,7 @@ def components_check(): # create first state if versioning enabled logging.info("Creating versioning state 0…") log_update(_("Creating versioning state 0…")) - self.versioning_manager.create_state( - config=config, - message="First boot" - ) + self.versioning_manager.create_state(config=config, message="First boot") # set status created and UI usability logging.info(f"New bottle created: {bottle_name}", jn=True) @@ -1426,10 +1495,7 @@ def components_check(): log_update(_("Caching template…")) TemplateManager.new(environment, config) - return Result( - status=True, - data={"config": config} - ) + return Result(status=True, data={"config": config}) @staticmethod def __sort_runners(runner_list: list, prefix: str) -> sorted: @@ -1437,16 +1503,12 @@ def __sort_runners(runner_list: list, prefix: str) -> sorted: Return a sorted list of runners for a given prefix. Fallback to the first available if fallback argument is True. """ - runners = [ runner for runner in runner_list if runner.startswith(prefix) ] + runners = [runner for runner in runner_list if runner.startswith(prefix)] try: runners = sort_by_version(runners, "") except ValueError: - runners = sorted( - runners, - key=lambda x: x.split("-")[1], - reverse=True - ) + runners = sorted(runners, key=lambda x: x.split("-")[1], reverse=True) return runners @@ -1481,17 +1543,17 @@ def delete_bottle(self, config: BottleConfig) -> bool: library_manager = LibraryManager() entries = library_manager.get_library().copy() for _uuid, entry in entries.items(): - if entry.get('bottle').get('name') == config.Name: + if entry.get("bottle").get("name") == config.Name: library_manager.remove_from_library(_uuid) if config.Custom_Path: logging.info(f"Removing placeholder…") with contextlib.suppress(FileNotFoundError): - os.remove(os.path.join( - Paths.bottles, - os.path.basename(config.Path), - "placeholder.yml" - )) + os.remove( + os.path.join( + Paths.bottles, os.path.basename(config.Path), "placeholder.yml" + ) + ) logging.info(f"Removing the bottle…") path = ManagerUtils.get_bottle_path(config) @@ -1535,13 +1597,13 @@ def repair_bottle(self, config: BottleConfig) -> bool: return True def install_dll_component( - self, - config: BottleConfig, - component: str, - remove: bool = False, - version: str = False, - overrides_only: bool = False, - exclude: list = None + self, + config: BottleConfig, + component: str, + remove: bool = False, + version: str = False, + overrides_only: bool = False, + exclude: list = None, ) -> Result: if exclude is None: exclude = [] @@ -1564,8 +1626,7 @@ def install_dll_component( manager = LatencyFleXComponent(_version) else: return Result( - status=False, - data={"message": f"Invalid component: {component}"} + status=False, data={"message": f"Invalid component: {component}"} ) if remove: diff --git a/bottles/backend/managers/origin.py b/bottles/backend/managers/origin.py index 2ec572334c4..80eaf935039 100644 --- a/bottles/backend/managers/origin.py +++ b/bottles/backend/managers/origin.py @@ -32,7 +32,8 @@ def find_manifests_path(config: BottleConfig) -> Union[str, None]: paths = [ os.path.join( ManagerUtils.get_bottle_path(config), - "drive_c/ProgramData/Origin/LocalContent") + "drive_c/ProgramData/Origin/LocalContent", + ) ] for path in paths: diff --git a/bottles/backend/managers/repository.py b/bottles/backend/managers/repository.py index 30a6b4e4a36..7a683341881 100644 --- a/bottles/backend/managers/repository.py +++ b/bottles/backend/managers/repository.py @@ -36,25 +36,25 @@ class RepositoryManager: "components": { "url": "https://proxy.usebottles.com/repo/components/", "index": "", - "cls": ComponentRepo + "cls": ComponentRepo, }, "dependencies": { "url": "https://proxy.usebottles.com/repo/dependencies/", "index": "", - "cls": DependencyRepo + "cls": DependencyRepo, }, "installers": { "url": "https://proxy.usebottles.com/repo/programs/", "index": "", - "cls": InstallerRepo - } + "cls": InstallerRepo, + }, } def __init__(self, get_index=True): self.do_get_index = True self.aborted_connections = 0 SignalManager.connect(Signals.ForceStopNetworking, self.__stop_index) - + self.__check_locals() if get_index: self.__get_index() @@ -93,24 +93,24 @@ def __check_locals(self): else: logging.error(f"Local {repo} path does not exist: {_path}") - def __curl_progress(self, _download_t, _download_d, _upload_t, _upload_d): if self.do_get_index: return pycurl.E_OK else: - self.aborted_connections+=1 + self.aborted_connections += 1 return pycurl.E_ABORTED_BY_CALLBACK - + def __stop_index(self, res: Result): if res.status: self.do_get_index = False - + def __get_index(self): total = len(self.__repositories) threads = [] for repo, data in self.__repositories.items(): + def query(_repo, _data): __index = os.path.join(_data["url"], f"{APP_VERSION}.yml") __fallback = os.path.join(_data["url"], "index.yml") @@ -122,23 +122,29 @@ def query(_repo, _data): c.setopt(c.FOLLOWLOCATION, True) c.setopt(c.TIMEOUT, 10) c.setopt(c.NOPROGRESS, False) - c.setopt(c.XFERINFOFUNCTION, self.__curl_progress) + c.setopt(c.XFERINFOFUNCTION, self.__curl_progress) try: c.perform() except pycurl.error as e: if url is not __index: - logging.error(f"Could not get index for {_repo} repository: {e}") + logging.error( + f"Could not get index for {_repo} repository: {e}" + ) continue if url.startswith("file://") or c.getinfo(c.RESPONSE_CODE) == 200: _data["index"] = url - SignalManager.send(Signals.RepositoryFetched, Result(True, data=total)) + SignalManager.send( + Signals.RepositoryFetched, Result(True, data=total) + ) break c.close() else: - SignalManager.send(Signals.RepositoryFetched, Result(False, data=total)) + SignalManager.send( + Signals.RepositoryFetched, Result(False, data=total) + ) logging.error(f"Could not get index for {_repo} repository") thread = RunAsync(query, _repo=repo, _data=data) diff --git a/bottles/backend/managers/runtime.py b/bottles/backend/managers/runtime.py index bc4bfe85a4a..d2640f1cf0c 100644 --- a/bottles/backend/managers/runtime.py +++ b/bottles/backend/managers/runtime.py @@ -28,7 +28,7 @@ class RuntimeManager: def get_runtimes(_filter: str = "bottles"): runtimes = { "bottles": RuntimeManager.__get_bottles_runtime(), - "steam": RuntimeManager.__get_steam_runtime() + "steam": RuntimeManager.__get_steam_runtime(), } if _filter == "steam": @@ -47,11 +47,11 @@ def get_runtime_env(_filter: str = "bottles"): if "EasyAntiCheatRuntime" in p or "BattlEyeRuntime" in p: continue env += f":{p}" - + else: return False - ld = os.environ.get('LD_LIBRARY_PATH') + ld = os.environ.get("LD_LIBRARY_PATH") if ld: env += f":{ld}" @@ -115,10 +115,7 @@ def check_structure(found, expected): @staticmethod def __get_bottles_runtime(): - paths = [ - "/app/etc/runtime", - Paths.runtimes - ] + paths = ["/app/etc/runtime", Paths.runtimes] structure = ["lib", "lib32"] return RuntimeManager.__get_runtime(paths, structure) @@ -126,6 +123,7 @@ def __get_bottles_runtime(): @staticmethod def __get_steam_runtime(): from bottles.backend.managers.steam import SteamManager + available_runtimes = {} steam_manager = SteamManager(check_only=True) @@ -135,16 +133,24 @@ def __get_steam_runtime(): lookup = { "sniper": { "name": "sniper", - "entry_point": os.path.join(steam_manager.steam_path, "steamapps/common/SteamLinuxRuntime_sniper/_v2-entry-point"), + "entry_point": os.path.join( + steam_manager.steam_path, + "steamapps/common/SteamLinuxRuntime_sniper/_v2-entry-point", + ), }, "soldier": { "name": "soldier", - "entry_point": os.path.join(steam_manager.steam_path, "steamapps/common/SteamLinuxRuntime_soldier/_v2-entry-point"), + "entry_point": os.path.join( + steam_manager.steam_path, + "steamapps/common/SteamLinuxRuntime_soldier/_v2-entry-point", + ), }, "scout": { "name": "scout", - "entry_point": os.path.join(steam_manager.steam_path, "ubuntu12_32/steam-runtime/run.sh"), - } + "entry_point": os.path.join( + steam_manager.steam_path, "ubuntu12_32/steam-runtime/run.sh" + ), + }, } for name, data in lookup.items(): diff --git a/bottles/backend/managers/sandbox.py b/bottles/backend/managers/sandbox.py index 97d4281557a..a17799f8a6c 100644 --- a/bottles/backend/managers/sandbox.py +++ b/bottles/backend/managers/sandbox.py @@ -25,18 +25,18 @@ class SandboxManager: def __init__( - self, - envs: Optional[dict] = None, - chdir: Optional[str] = None, - clear_env: bool = False, - share_paths_ro: Optional[list] = None, - share_paths_rw: Optional[list] = None, - share_net: bool = False, - share_user: bool = False, - share_host_ro: bool = True, - share_display: bool = True, - share_sound: bool = True, - share_gpu: bool = True, + self, + envs: Optional[dict] = None, + chdir: Optional[str] = None, + clear_env: bool = False, + share_paths_ro: Optional[list] = None, + share_paths_rw: Optional[list] = None, + share_net: bool = False, + share_user: bool = False, + share_host_ro: bool = True, + share_display: bool = True, + share_sound: bool = True, + share_gpu: bool = True, ): self.envs = envs self.chdir = chdir @@ -68,13 +68,20 @@ def __get_bwrap(self, cmd: str): _cmd.append("--clearenv") if self.share_paths_ro: - _cmd += [f"--ro-bind {shlex.quote(p)} {shlex.quote(p)}" for p in self.share_paths_ro] + _cmd += [ + f"--ro-bind {shlex.quote(p)} {shlex.quote(p)}" + for p in self.share_paths_ro + ] if self.share_paths_rw: - _cmd += [f"--bind {shlex.quote(p)} {shlex.quote(p)}" for p in self.share_paths_ro] + _cmd += [ + f"--bind {shlex.quote(p)} {shlex.quote(p)}" for p in self.share_paths_ro + ] if self.share_sound: - _cmd.append(f"--ro-bind /run/user/{self.__uid}/pulse /run/user/{self.__uid}/pulse") + _cmd.append( + f"--ro-bind /run/user/{self.__uid}/pulse /run/user/{self.__uid}/pulse" + ) if self.share_gpu: pass # not implemented yet @@ -106,10 +113,15 @@ def __get_flatpak_spawn(self, cmd: str): _cmd.append("--clear-env") if self.share_paths_ro: - _cmd += [f"--sandbox-expose-path-ro={shlex.quote(p)}" for p in self.share_paths_ro] + _cmd += [ + f"--sandbox-expose-path-ro={shlex.quote(p)}" + for p in self.share_paths_ro + ] if self.share_paths_rw: - _cmd += [f"--sandbox-expose-path={shlex.quote(p)}" for p in self.share_paths_rw] + _cmd += [ + f"--sandbox-expose-path={shlex.quote(p)}" for p in self.share_paths_rw + ] if not self.share_net: _cmd.append("--no-network") @@ -136,4 +148,9 @@ def get_cmd(self, cmd: str): return " ".join(_cmd) def run(self, cmd: str) -> subprocess.Popen[bytes]: - return subprocess.Popen(self.get_cmd(cmd), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + return subprocess.Popen( + self.get_cmd(cmd), + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) diff --git a/bottles/backend/managers/steam.py b/bottles/backend/managers/steam.py index 28ea0f704e3..68048e6fb15 100644 --- a/bottles/backend/managers/steam.py +++ b/bottles/backend/managers/steam.py @@ -49,7 +49,12 @@ class SteamManager: localconfig = {} library_folders = [] - def __init__(self, config: Optional[BottleConfig] = None, is_windows: bool = False, check_only: bool = False): + def __init__( + self, + config: Optional[BottleConfig] = None, + is_windows: bool = False, + check_only: bool = False, + ): self.config = config self.is_windows = is_windows self.steam_path = self.__find_steam_path() @@ -63,10 +68,17 @@ def __init__(self, config: Optional[BottleConfig] = None, is_windows: bool = Fal def __find_steam_path(self) -> Union[str, None]: if self.is_windows and self.config: - paths = [os.path.join(ManagerUtils.get_bottle_path(self.config), "drive_c/Program Files (x86)/Steam")] + paths = [ + os.path.join( + ManagerUtils.get_bottle_path(self.config), + "drive_c/Program Files (x86)/Steam", + ) + ] else: paths = [ - os.path.join(Path.home(), ".var/app/com.valvesoftware.Steam/data/Steam"), + os.path.join( + Path.home(), ".var/app/com.valvesoftware.Steam/data/Steam" + ), os.path.join(Path.home(), ".local/share/Steam"), os.path.join(Path.home(), ".steam/debian-installation"), os.path.join(Path.home(), ".steam/steam"), @@ -129,9 +141,11 @@ def __get_library_folders(self) -> Union[list, None]: return None for _, folder in _library_folders["libraryfolders"].items(): - if not isinstance(folder, dict) \ - or not folder.get("path") \ - or not folder.get("apps"): + if ( + not isinstance(folder, dict) + or not folder.get("path") + or not folder.get("apps") + ): continue library_folders.append(folder) @@ -188,7 +202,9 @@ def get_runner_path(pfx_path: str) -> Union[tuple, None]: with open(config_info, "r") as f: lines = f.readlines() if len(lines) < 10: - logging.error(f"{config_info} is not valid, cannot get Steam Proton path") + logging.error( + f"{config_info} is not valid, cannot get Steam Proton path" + ) return None proton_path = lines[1].strip().removesuffix("/share/fonts/") @@ -206,10 +222,12 @@ def get_runner_path(pfx_path: str) -> Union[tuple, None]: def list_apps_ids(self) -> dict: """List all apps in Steam""" - apps = self.localconfig.get("UserLocalConfigStore", {}) \ - .get("Software", {}) \ - .get("Valve", {}) \ + apps = ( + self.localconfig.get("UserLocalConfigStore", {}) + .get("Software", {}) + .get("Valve", {}) .get("Steam", {}) + ) if "apps" in apps: apps = apps.get("apps") elif "Apps" in apps: @@ -221,7 +239,9 @@ def list_apps_ids(self) -> dict: def get_installed_apps_as_programs(self) -> list: """This is a Steam for Windows only function""" if not self.is_windows: - raise NotImplementedError("This function is only implemented for Windows versions of Steam") + raise NotImplementedError( + "This function is only implemented for Windows versions of Steam" + ) apps_ids = self.list_apps_ids() apps = [] @@ -234,24 +254,22 @@ def get_installed_apps_as_programs(self) -> list: if _acf is None: continue - _path = _acf["AppState"].get("LauncherPath", "C:\\Program Files (x86)\\Steam\\steam.exe") + _path = _acf["AppState"].get( + "LauncherPath", "C:\\Program Files (x86)\\Steam\\steam.exe" + ) _executable = _path.split("\\")[-1] _folder = ManagerUtils.get_exe_parent_dir(self.config, _path) - apps.append({ - "executable": _executable, - "arguments": f"steam://run/{app_id}", - "name": _acf["AppState"]["name"], - "path": _path, - "folder": _folder, - "icon": "com.usebottles.bottles-program", - "dxvk": self.config.Parameters.dxvk, - "vkd3d": self.config.Parameters.vkd3d, - "dxvk_nvapi": self.config.Parameters.dxvk_nvapi, - "fsr": self.config.Parameters.fsr, - "virtual_desktop": self.config.Parameters.virtual_desktop, - "pulseaudio_latency": self.config.Parameters.pulseaudio_latency, - "id": str(uuid.uuid4()), - }) + apps.append( + { + "executable": _executable, + "arguments": f"steam://run/{app_id}", + "name": _acf["AppState"]["name"], + "path": _path, + "folder": _folder, + "icon": "com.usebottles.bottles-program", + "id": str(uuid.uuid4()), + } + ) return apps @@ -277,24 +295,37 @@ def list_prefixes(self) -> Dict[str, BottleConfig]: _dir_name = os.path.basename(_path) _acf = self.get_acf_data(_library_path, _dir_name) _runner_path = self.get_runner_path(_path) - _creation_date = datetime.fromtimestamp(os.path.getctime(_path)) \ - .strftime("%Y-%m-%d %H:%M:%S.%f") + _creation_date = datetime.fromtimestamp(os.path.getctime(_path)).strftime( + "%Y-%m-%d %H:%M:%S.%f" + ) if not isinstance(_acf, dict): # WORKAROUND: for corrupted acf files, this is not at our fault continue if _acf is None or not _acf.get("AppState"): - logging.warning(f"A Steam prefix was found, but there is no ACF for it: {_dir_name}, skipping…") + logging.warning( + f"A Steam prefix was found, but there is no ACF for it: {_dir_name}, skipping…" + ) continue - if SteamUtils.is_proton(os.path.join(_library_path, "steamapps/common", _acf["AppState"].get("installdir", ""))): + if SteamUtils.is_proton( + os.path.join( + _library_path, + "steamapps/common", + _acf["AppState"].get("installdir", ""), + ) + ): # skip Proton default prefix - logging.warning(f"A Steam prefix was found, but it is a Proton one: {_dir_name}, skipping…") + logging.warning( + f"A Steam prefix was found, but it is a Proton one: {_dir_name}, skipping…" + ) continue if _runner_path is None: - logging.warning(f"A Steam prefix was found, but there is no Proton for it: {_dir_name}, skipping…") + logging.warning( + f"A Steam prefix was found, but there is no Proton for it: {_dir_name}, skipping…" + ) continue _conf = BottleConfig() @@ -311,13 +342,12 @@ def list_prefixes(self) -> Dict[str, BottleConfig]: ).strftime("%Y-%m-%d %H:%M:%S.%f") # Launch options - _conf.Parameters.mangohud = ("mangohud" in _launch_options.get("command", "")) - _conf.Parameters.gamemode = ("gamemode" in _launch_options.get("command", "")) + _conf.Parameters.mangohud = "mangohud" in _launch_options.get("command", "") + _conf.Parameters.gamemode = "gamemode" in _launch_options.get("command", "") _conf.Environment_Variables = _launch_options.get("env_vars", {}) for p in _launch_options.get("env_params", {}): _conf.Parameters[p] = _launch_options["env_params"].get(p, "") - prefixes[_dir_name] = _conf return prefixes @@ -342,10 +372,12 @@ def get_app_config(self, prefix: str) -> dict: logging.warning(_fail_msg) return {} - apps = self.localconfig.get("UserLocalConfigStore", {}) \ - .get("Software", {}) \ - .get("Valve", {}) \ + apps = ( + self.localconfig.get("UserLocalConfigStore", {}) + .get("Software", {}) + .get("Valve", {}) .get("Steam", {}) + ) if "apps" in apps: apps = apps.get("apps") elif "Apps" in apps: @@ -365,45 +397,14 @@ def get_launch_options(self, prefix: str, app_conf: Optional[dict] = None) -> {} launch_options = app_conf.get("LaunchOptions", "") _fail_msg = f"Fail to get launch options from Steam for: {prefix}" - prefix, args = "", "" - env_vars = {} - res = { - "command": "", - "args": "", - "env_vars": {}, - "env_params": {} - } + res = {"command": "", "args": "", "env_vars": {}, "env_params": {}} if len(launch_options) == 0: logging.debug(_fail_msg) return res - if "%command%" in launch_options: - _c = launch_options.split("%command%") - prefix = _c[0] if len(_c) > 0 else "" - args = _c[1] if len(_c) > 1 else "" - else: - args = launch_options - - try: - prefix_list = shlex.split(prefix.strip()) - except ValueError: - prefix_list = prefix.split(shlex.quote(prefix.strip())) - - for p in prefix_list.copy(): - if "=" in p: - k, v = p.split("=", 1) - v = shlex.quote(v) if " " in v else v - env_vars[k] = v - prefix_list.remove(p) - - command = " ".join(prefix_list) - res = { - "command": command, - "args": args, - "env_vars": env_vars, - "env_params": {} - } + command, args, env_vars = SteamUtils.handle_launch_options(launch_options) + res = {"command": command, "args": args, "env_vars": env_vars, "env_params": {}} tmp_env_vars = res["env_vars"].copy() for e in tmp_env_vars: @@ -440,11 +441,13 @@ def set_launch_options(self, prefix: str, options: dict): launch_options += f"{command} %command% {original_launch_options['args']}" try: - self.localconfig["UserLocalConfigStore"]["Software"]["Valve"]["Steam"]["apps"][prefix]["LaunchOptions"] \ - = launch_options + self.localconfig["UserLocalConfigStore"]["Software"]["Valve"]["Steam"][ + "apps" + ][prefix]["LaunchOptions"] = launch_options except (KeyError, TypeError): - self.localconfig["UserLocalConfigStore"]["Software"]["Valve"]["Steam"]["Apps"][prefix]["LaunchOptions"] \ - = launch_options + self.localconfig["UserLocalConfigStore"]["Software"]["Valve"]["Steam"][ + "Apps" + ][prefix]["LaunchOptions"] = launch_options self.save_local_config(self.localconfig) @@ -467,7 +470,9 @@ def del_launch_option(self, prefix: str, key_type: str, key: str): del original_launch_options["env_vars"][key] elif key_type == "command": if key in original_launch_options["command"]: - original_launch_options["command"] = original_launch_options["command"].replace(key, "") + original_launch_options["command"] = original_launch_options[ + "command" + ].replace(key, "") launch_options = "" @@ -476,11 +481,13 @@ def del_launch_option(self, prefix: str, key_type: str, key: str): launch_options += f"{original_launch_options['command']} %command% {original_launch_options['args']}" try: - self.localconfig["UserLocalConfigStore"]["Software"]["Valve"]["Steam"]["apps"][prefix]["LaunchOptions"] \ - = launch_options + self.localconfig["UserLocalConfigStore"]["Software"]["Valve"]["Steam"][ + "apps" + ][prefix]["LaunchOptions"] = launch_options except (KeyError, TypeError): - self.localconfig["UserLocalConfigStore"]["Software"]["Valve"]["Steam"]["Apps"][prefix]["LaunchOptions"] \ - = launch_options + self.localconfig["UserLocalConfigStore"]["Software"]["Valve"]["Steam"][ + "Apps" + ][prefix]["LaunchOptions"] = launch_options self.save_local_config(self.localconfig) @@ -501,11 +508,7 @@ def update_bottle(self, config: BottleConfig) -> BottleConfig: command, _args = command.split("%command%") args = args + " " + _args - options = { - "command": command, - "args": args, - "env_vars": env_vars - } + options = {"command": command, "args": args, "env_vars": env_vars} self.set_launch_options(pfx, options) self.config = config return config @@ -541,7 +544,7 @@ def add_shortcut(self, program_name: str, program_path: str): "DevkitGameID": "", "DevkitOverrideAppID": "", "LastPlayTime": 0, - "tags": {"0": "Bottles"} + "tags": {"0": "Bottles"}, } for c in confs: diff --git a/bottles/backend/managers/steamgriddb.py b/bottles/backend/managers/steamgriddb.py index 2818bdeb36b..925e9ceee77 100644 --- a/bottles/backend/managers/steamgriddb.py +++ b/bottles/backend/managers/steamgriddb.py @@ -27,30 +27,30 @@ class SteamGridDBManager: - + @staticmethod def get_game_grid(name: str, config: BottleConfig): try: res = requests.get(f"https://steamgrid.usebottles.com/api/search/{name}") except: return - + if res.status_code == 200: return SteamGridDBManager.__save_grid(res.json(), config) @staticmethod def __save_grid(url: str, config: BottleConfig): - grids_path = os.path.join(ManagerUtils.get_bottle_path(config), 'grids') + grids_path = os.path.join(ManagerUtils.get_bottle_path(config), "grids") if not os.path.exists(grids_path): os.makedirs(grids_path) - - ext = url.split('.')[-1] - filename = str(uuid.uuid4()) + '.' + ext + + ext = url.split(".")[-1] + filename = str(uuid.uuid4()) + "." + ext path = os.path.join(grids_path, filename) try: r = requests.get(url) - with open(path, 'wb') as f: + with open(path, "wb") as f: f.write(r.content) except Exception: return diff --git a/bottles/backend/managers/template.py b/bottles/backend/managers/template.py index de732ae9de1..6e832240100 100644 --- a/bottles/backend/managers/template.py +++ b/bottles/backend/managers/template.py @@ -54,25 +54,21 @@ def new(env: str, config: BottleConfig): delattr(config, "Creation_Date") delattr(config, "Update_Date") - ignored = [ - "dosdevices", - "states", - ".fvs", - "*.yml" - ".*" - ] + ignored = ["dosdevices", "states", ".fvs", "*.yml" ".*"] _path = os.path.join(Paths.templates, _uuid) logging.info("Copying files …") with contextlib.suppress(FileNotFoundError): - shutil.copytree(bottle, _path, symlinks=True, ignore=shutil.ignore_patterns(*ignored)) + shutil.copytree( + bottle, _path, symlinks=True, ignore=shutil.ignore_patterns(*ignored) + ) template = { "uuid": _uuid, "env": env, "created": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), - "config": config + "config": config, } with open(os.path.join(_path, "template.yml"), "w") as f: @@ -99,14 +95,16 @@ def __validate_template(template_uuid: str): for essential in essentials: if not os.path.exists(os.path.join(template_path, essential)): - logging.error(f"Template {template_uuid} is missing essential path: {essential}") + logging.error( + f"Template {template_uuid} is missing essential path: {essential}" + ) result = False - path_size = sum(file.stat().st_size for file in Path(template_path).rglob('*')) + path_size = sum(file.stat().st_size for file in Path(template_path).rglob("*")) if path_size < 300000000: logging.error(f"Template {template_uuid} is too small!") result = False - + with open(os.path.join(template_path, "template.yml"), "r") as f: template = yaml.load(f) if template["uuid"] != template_uuid: @@ -190,7 +188,14 @@ def copy_func(source: str, dest: str): logging.info(f"Unpacking template: {template['uuid']}") bottle = ManagerUtils.get_bottle_path(config) - _path = os.path.join(Paths.templates, template['uuid']) - - shutil.copytree(_path, bottle, symlinks=True, dirs_exist_ok=True, ignore=shutil.ignore_patterns('.*'), ignore_dangling_symlinks=True) + _path = os.path.join(Paths.templates, template["uuid"]) + + shutil.copytree( + _path, + bottle, + symlinks=True, + dirs_exist_ok=True, + ignore=shutil.ignore_patterns(".*"), + ignore_dangling_symlinks=True, + ) logging.info("Template unpacked successfully!") diff --git a/bottles/backend/managers/thumbnail.py b/bottles/backend/managers/thumbnail.py index e0744b9a9d7..96bd981fd90 100644 --- a/bottles/backend/managers/thumbnail.py +++ b/bottles/backend/managers/thumbnail.py @@ -25,7 +25,7 @@ class ThumbnailManager: - + @staticmethod def get_path(config: BottleConfig, uri: str): if uri.startswith("grid:"): @@ -36,15 +36,15 @@ def get_path(config: BottleConfig, uri: str): # return ThumbnailManager.__load_origin(config, uri) logging.error("Unknown URI: " + uri) return None - + @staticmethod def __load_grid(config: BottleConfig, uri: str): bottle_path = ManagerUtils.get_bottle_path(config) file_name = uri[5:] - path = os.path.join(bottle_path, 'grids', file_name) + path = os.path.join(bottle_path, "grids", file_name) if not os.path.exists(path): logging.error("Grid not found: " + path) return None - + return path diff --git a/bottles/backend/managers/ubisoftconnect.py b/bottles/backend/managers/ubisoftconnect.py index f9310ae635f..74484a87d46 100644 --- a/bottles/backend/managers/ubisoftconnect.py +++ b/bottles/backend/managers/ubisoftconnect.py @@ -33,7 +33,8 @@ def find_conf_path(config: BottleConfig) -> Union[str, None]: paths = [ os.path.join( ManagerUtils.get_bottle_path(config), - "drive_c/Program Files (x86)/Ubisoft/Ubisoft Game Launcher/cache/configuration/configurations") + "drive_c/Program Files (x86)/Ubisoft/Ubisoft Game Launcher/cache/configuration/configurations", + ) ] for path in paths: @@ -59,11 +60,14 @@ def get_installed_games(config: BottleConfig) -> list: key: Optional[str] = None appid: Optional[str] = None thumb: Optional[str] = None - reg_key = "register: HKEY_LOCAL_MACHINE\\SOFTWARE\\Ubisoft\\Launcher\\Installs\\" + reg_key = ( + "register: HKEY_LOCAL_MACHINE\\SOFTWARE\\Ubisoft\\Launcher\\Installs\\" + ) conf_path = UbisoftConnectManager.find_conf_path(config) games_path = os.path.join( ManagerUtils.get_bottle_path(config), - "drive_c/Program Files (x86)/Ubisoft/Ubisoft Game Launcher/games") + "drive_c/Program Files (x86)/Ubisoft/Ubisoft Game Launcher/games", + ) if conf_path is None: return [] @@ -76,11 +80,7 @@ def get_installed_games(config: BottleConfig) -> list: _key = r.replace("name:", "").strip() if _key != "" and _key not in found.keys(): key = _key - found[key] = { - "name": None, - "appid": None, - "thumb_image": None - } + found[key] = {"name": None, "appid": None, "thumb_image": None} elif key and r.startswith("- shortcut_name:"): _name = r.replace("- shortcut_name:", "").strip() @@ -88,7 +88,9 @@ def get_installed_games(config: BottleConfig) -> list: name = _name found[key]["name"] = name - elif key and found[key]["name"] is None and r.startswith("display_name:"): + elif ( + key and found[key]["name"] is None and r.startswith("display_name:") + ): name = r.replace("display_name:", "").strip() found[key]["name"] = name @@ -104,28 +106,28 @@ def get_installed_games(config: BottleConfig) -> list: key, name, appid, thumb = None, None, None, None for k, v in found.items(): - if v["name"] is None or not os.path.exists(os.path.join(games_path, v["name"])): + if v["name"] is None or not os.path.exists( + os.path.join(games_path, v["name"]) + ): continue _args = f"uplay://launch/{v['appid']}/0" _path = "C:\\Program Files (x86)\\Ubisoft\\Ubisoft Game Launcher\\UbisoftConnect.exe" _executable = _path.split("\\")[-1] _folder = ManagerUtils.get_exe_parent_dir(config, _path) - _thumb = "" if v['thumb_image'] is None else f"ubisoft:{v['thumb_image']}" - games.append({ - "executable": _path, - "arguments": _args, - "name": v["name"], - "thumb": _thumb, - "path": _path, - "folder": _folder, - "icon": "com.usebottles.bottles-program", - "dxvk": config.Parameters.dxvk, - "vkd3d": config.Parameters.vkd3d, - "dxvk_nvapi": config.Parameters.dxvk_nvapi, - "fsr": config.Parameters.fsr, - "virtual_desktop": config.Parameters.virtual_desktop, - "pulseaudio_latency": config.Parameters.pulseaudio_latency, - "id": str(uuid.uuid4()), - }) + _thumb = ( + "" if v["thumb_image"] is None else f"ubisoft:{v['thumb_image']}" + ) + games.append( + { + "executable": _path, + "arguments": _args, + "name": v["name"], + "thumb": _thumb, + "path": _path, + "folder": _folder, + "icon": "com.usebottles.bottles-program", + "id": str(uuid.uuid4()), + } + ) return games diff --git a/bottles/backend/managers/versioning.py b/bottles/backend/managers/versioning.py index 9f8255bb1b8..1d88ecd41ca 100644 --- a/bottles/backend/managers/versioning.py +++ b/bottles/backend/managers/versioning.py @@ -21,7 +21,12 @@ from gettext import gettext as _ from glob import glob -from fvs.exceptions import FVSNothingToCommit, FVSStateNotFound, FVSNothingToRestore, FVSStateZeroNotDeletable +from fvs.exceptions import ( + FVSNothingToCommit, + FVSStateNotFound, + FVSNothingToRestore, + FVSStateZeroNotDeletable, +) from fvs.repo import FVSRepo from bottles.backend.logger import Logger @@ -42,10 +47,7 @@ def __init__(self, manager): @staticmethod def __get_patterns(config: BottleConfig): - patterns = [ - "*dosdevices*", - "*cache*" - ] + patterns = ["*dosdevices*", "*cache*"] if config.Parameters.versioning_exclusion_patterns: patterns += config.Versioning_Exclusion_Patterns return patterns @@ -56,7 +58,7 @@ def is_initialized(config: BottleConfig): repo = FVSRepo( repo_path=ManagerUtils.get_bottle_path(config), use_compression=config.Parameters.versioning_compression, - no_init=True + no_init=True, ) except FileNotFoundError: return False @@ -78,26 +80,22 @@ def create_state(self, config: BottleConfig, message: str = "No message"): patterns = self.__get_patterns(config) repo = FVSRepo( repo_path=ManagerUtils.get_bottle_path(config), - use_compression=config.Parameters.versioning_compression + use_compression=config.Parameters.versioning_compression, ) task_id = TaskManager.add(Task(title=_("Committing state …"))) try: repo.commit(message, ignore=patterns) except FVSNothingToCommit: TaskManager.remove(task_id) - return Result( - status=False, - message=_("Nothing to commit") - ) + return Result(status=False, message=_("Nothing to commit")) TaskManager.remove(task_id) return Result( status=True, - message=_("New state [{0}] created successfully!").format(repo.active_state_id), - data={ - "state_id": repo.active_state_id, - "states": repo.states - } + message=_("New state [{0}] created successfully!").format( + repo.active_state_id + ), + data={"state_id": repo.active_state_id, "states": repo.states}, ) def list_states(self, config: BottleConfig) -> Result: @@ -109,29 +107,28 @@ def list_states(self, config: BottleConfig) -> Result: try: repo = FVSRepo( repo_path=ManagerUtils.get_bottle_path(config), - use_compression=config.Parameters.versioning_compression + use_compression=config.Parameters.versioning_compression, ) except FVSStateNotFound: - logging.warning("The FVS repository may be corrupted, trying to re-initialize it") + logging.warning( + "The FVS repository may be corrupted, trying to re-initialize it" + ) self.re_initialize(config) repo = FVSRepo( repo_path=ManagerUtils.get_bottle_path(config), - use_compression=config.Parameters.versioning_compression + use_compression=config.Parameters.versioning_compression, ) return Result( status=True, message=_("States list retrieved successfully!"), - data={ - "state_id": repo.active_state_id, - "states": repo.states - } + data={"state_id": repo.active_state_id, "states": repo.states}, ) bottle_path = ManagerUtils.get_bottle_path(config) states = {} try: - states_file = open('%s/states/states.yml' % bottle_path) + states_file = open("%s/states/states.yml" % bottle_path) states_file_yaml = yaml.load(states_file) states_file.close() states = states_file_yaml.get("States") @@ -141,31 +138,32 @@ def list_states(self, config: BottleConfig) -> Result: return states - def set_state(self, config: BottleConfig, state_id: int, after: callable = None) -> Result: + def set_state( + self, config: BottleConfig, state_id: int, after: callable = None + ) -> Result: if not config.Versioning: patterns = self.__get_patterns(config) repo = FVSRepo( repo_path=ManagerUtils.get_bottle_path(config), - use_compression=config.Parameters.versioning_compression + use_compression=config.Parameters.versioning_compression, ) res = Result( status=True, - message=_("State {0} restored successfully!").format(state_id) + message=_("State {0} restored successfully!").format(state_id), + ) + task_id = TaskManager.add( + Task(title=_("Restoring state {} …".format(state_id))) ) - task_id = TaskManager.add(Task(title=_("Restoring state {} …".format(state_id)))) try: repo.restore_state(state_id, ignore=patterns) except FVSStateNotFound: logging.error(f"State {state_id} not found.") - res = Result( - status=False, - message=_("State not found") - ) + res = Result(status=False, message=_("State not found")) except (FVSNothingToRestore, FVSStateZeroNotDeletable): logging.error(f"State {state_id} is the active state.") res = Result( status=False, - message=_("State {} is already the active state").format(state_id) + message=_("State {} is already the active state").format(state_id), ) TaskManager.remove(task_id) return res @@ -186,7 +184,9 @@ def set_state(self, config: BottleConfig, state_id: int, after: callable = None) for file in bottle_index.get("Files"): if file["file"] not in [f["file"] for f in state_index.get("Files")]: remove_files.append(file) - elif file["checksum"] not in [f["checksum"] for f in state_index.get("Files")]: + elif file["checksum"] not in [ + f["checksum"] for f in state_index.get("Files") + ]: edit_files.append(file) logging.info(f"[{len(remove_files)}] files to remove.") logging.info(f"[{len(edit_files)}] files to replace.") @@ -203,14 +203,17 @@ def set_state(self, config: BottleConfig, state_id: int, after: callable = None) os.remove("%s/drive_c/%s" % (bottle_path, file["file"])) for file in add_files: - source = "%s/states/%s/drive_c/%s" % (bottle_path, str(state_id), file["file"]) + source = "%s/states/%s/drive_c/%s" % ( + bottle_path, + str(state_id), + file["file"], + ) target = "%s/drive_c/%s" % (bottle_path, file["file"]) shutil.copy2(source, target) for file in edit_files: for i in search_sources: - source = "%s/states/%s/drive_c/%s" % ( - bottle_path, str(i), file["file"]) + source = "%s/states/%s/drive_c/%s" % (bottle_path, str(i), file["file"]) if os.path.isfile(source): checksum = FileUtils().get_checksum(source) if file["checksum"] == checksum: @@ -228,13 +231,18 @@ def set_state(self, config: BottleConfig, state_id: int, after: callable = None) return Result(True) @staticmethod - def get_state_files(config: BottleConfig, state_id: int, plain: bool = False) -> dict: + def get_state_files( + config: BottleConfig, state_id: int, plain: bool = False + ) -> dict: """ Return the files.yml content of the state. Use the plain argument to return the content as plain text. """ try: - file = open('%s/states/%s/files.yml' % (ManagerUtils.get_bottle_path(config), state_id)) + file = open( + "%s/states/%s/files.yml" + % (ManagerUtils.get_bottle_path(config), state_id) + ) files = file.read() if plain else yaml.load(file.read()) file.close() return files @@ -246,10 +254,7 @@ def get_state_files(config: BottleConfig, state_id: int, plain: bool = False) -> def get_index(config: BottleConfig): """List all files in a bottle and return as dict.""" bottle_path = ManagerUtils.get_bottle_path(config) - cur_index = { - "Update_Date": str(datetime.now()), - "Files": [] - } + cur_index = {"Update_Date": str(datetime.now()), "Files": []} for file in glob("%s/drive_c/**" % bottle_path, recursive=True): if not os.path.isfile(file): continue @@ -257,11 +262,13 @@ def get_index(config: BottleConfig): if os.path.islink(os.path.dirname(file)): continue - if file[len(bottle_path) + 9:].split("/")[0] in ["users"]: + if file[len(bottle_path) + 9 :].split("/")[0] in ["users"]: continue - cur_index["Files"].append({ - "file": file[len(bottle_path) + 9:], - "checksum": FileUtils().get_checksum(file) - }) + cur_index["Files"].append( + { + "file": file[len(bottle_path) + 9 :], + "checksum": FileUtils().get_checksum(file), + } + ) return cur_index diff --git a/bottles/backend/models/config.py b/bottles/backend/models/config.py index 695a28e6b39..6e85493ad71 100644 --- a/bottles/backend/models/config.py +++ b/bottles/backend/models/config.py @@ -148,7 +148,7 @@ class BottleConfig(DictCompatMixIn): data: dict = field(default_factory=dict) # possible keys: "config", ... RunnerPath: str = "" - def dump(self, file: Union[str, IO], mode='w', encoding=None, indent=4) -> Result: + def dump(self, file: Union[str, IO], mode="w", encoding=None, indent=4) -> Result: """ Dump config to file @@ -169,7 +169,7 @@ def dump(self, file: Union[str, IO], mode='w', encoding=None, indent=4) -> Resul f.close() @classmethod - def load(cls, file: Union[str, IO], mode='r') -> Result[Optional['BottleConfig']]: + def load(cls, file: Union[str, IO], mode="r") -> Result[Optional["BottleConfig"]]: """ Load config from file @@ -186,7 +186,9 @@ def load(cls, file: Union[str, IO], mode='r') -> Result[Optional['BottleConfig'] data = yaml.load(f) if not isinstance(data, dict): - raise TypeError("Config data should be dict type, but it was %s" % type(data)) + raise TypeError( + "Config data should be dict type, but it was %s" % type(data) + ) filled = cls._fill_with(data) if not filled.status: @@ -197,10 +199,11 @@ def load(cls, file: Union[str, IO], mode='r') -> Result[Optional['BottleConfig'] logging.exception(e) return Result(False, message=str(e)) finally: - if f: f.close() + if f: + f.close() @classmethod - def _fill_with(cls, data: dict) -> Result[Optional['BottleConfig']]: + def _fill_with(cls, data: dict) -> Result[Optional["BottleConfig"]]: """fill with dict""" try: data = data.copy() @@ -209,11 +212,10 @@ def _fill_with(cls, data: dict) -> Result[Optional['BottleConfig']]: params = BottleParams(**data.pop("Parameters", {})) sandbox_param = BottleSandboxParams(**data.pop("Sandbox", {})) - return Result(True, data=BottleConfig( - Parameters=params, - Sandbox=sandbox_param, - **data - )) + return Result( + True, + data=BottleConfig(Parameters=params, Sandbox=sandbox_param, **data), + ) except Exception as e: logging.exception(e) return Result(False, message=repr(e)) @@ -232,8 +234,12 @@ def _fix(cls, data: dict) -> dict: # migrate old fsr_level key to fsr_sharpening_strength # TODO: remove after some time if "fsr_level" in data["Parameters"]: - logging.warning("Migrating config key 'fsr_level' to 'fsr_sharpening_strength'") - data["Parameters"]["fsr_sharpening_strength"] = data["Parameters"].pop("fsr_level") + logging.warning( + "Migrating config key 'fsr_level' to 'fsr_sharpening_strength'" + ) + data["Parameters"]["fsr_sharpening_strength"] = data["Parameters"].pop( + "fsr_level" + ) # migrate typo fields if "DXVK_NVAPI" in data: @@ -266,6 +272,8 @@ def _filter(cls, data: dict, clazz: object = None) -> dict: v = cls._filter(v, field_type) new_data[k] = v else: - logging.warning("Skipping unexpected config '%s' in %s" % (k, clazz.__name__)) + logging.warning( + "Skipping unexpected config '%s' in %s" % (k, clazz.__name__) + ) return new_data diff --git a/bottles/backend/models/result.py b/bottles/backend/models/result.py index 647621ff8b7..ead3cd89222 100644 --- a/bottles/backend/models/result.py +++ b/bottles/backend/models/result.py @@ -16,7 +16,7 @@ # from typing import TypeVar, Generic -T = TypeVar('T') +T = TypeVar("T") class Result(Generic[T]): @@ -30,12 +30,7 @@ class Result(Generic[T]): data: T = None message: str = "" - def __init__( - self, - status: bool = False, - data: T = None, - message: str = "" - ): + def __init__(self, status: bool = False, data: T = None, message: str = ""): self.status = status self.data = data self.message = message diff --git a/bottles/backend/models/samples.py b/bottles/backend/models/samples.py index 21b3a2d85bb..654427b8943 100644 --- a/bottles/backend/models/samples.py +++ b/bottles/backend/models/samples.py @@ -10,7 +10,7 @@ class Samples: "sync": "fsync", "fsr": False, "discrete_gpu": True, - "pulseaudio_latency": False + "pulseaudio_latency": False, }, "Installed_Dependencies": [ "d3dx9", @@ -22,26 +22,21 @@ class Samples: "d3dcompiler_47", "mono", "gecko", - "faudio", - ] + ], }, "application": { "Runner": "wine", - "Parameters": { - "dxvk": True, - "vkd3d": True, - "pulseaudio_latency": False - }, + "Parameters": {"dxvk": True, "vkd3d": True, "pulseaudio_latency": False}, "Installed_Dependencies": [ "arial32", "times32", "courie32", "mono", - "gecko" - # "dotnet40", - # "dotnet48" - ] - } + "gecko", + # "dotnet40", + # "dotnet48" + ], + }, } bottles_to_steam_relations = { "MANGOHUD": ("mangohud", True), @@ -56,5 +51,5 @@ class Samples: "__NV_PRIME_RENDER_OFFLOAD": ("discrete_gpu", True), "PULSE_LATENCY_MSEC": ("pulseaudio_latency", True), "PROTON_EAC_RUNTIME": ("use_eac_runtime", True), - "PROTON_BATTLEYE_RUNTIME": ("use_be_runtime", True) + "PROTON_BATTLEYE_RUNTIME": ("use_be_runtime", True), } diff --git a/bottles/backend/models/vdict.py b/bottles/backend/models/vdict.py index 2f5d396f61e..cc795c074b3 100644 --- a/bottles/backend/models/vdict.py +++ b/bottles/backend/models/vdict.py @@ -23,7 +23,7 @@ from collections import Counter import collections.abc as _c -_iter_values = 'values' +_iter_values = "values" _range = range _string_type = str @@ -61,7 +61,9 @@ def __init__(self, data=None): if data is not None: if not isinstance(data, (list, dict)): - raise ValueError("Expected data to be list of pairs or dict, got %s" % type(data)) + raise ValueError( + "Expected data to be list of pairs or dict, got %s" % type(data) + ) self.update(data) def __repr__(self): @@ -204,13 +206,13 @@ def items(self): return _iView(self) def get_all_for(self, key): - """ Returns all values of the given key """ + """Returns all values of the given key""" if not isinstance(key, _string_type): raise TypeError("Key needs to be a string.") return [self[(idx, key)] for idx in _range(self.__kcount[key])] def remove_all_for(self, key): - """ Removes all items with the given key """ + """Removes all items with the given key""" if not isinstance(key, _string_type): raise TypeError("Key need to be a string.") diff --git a/bottles/backend/runner.py b/bottles/backend/runner.py index d6c1d5203f9..270916674a8 100644 --- a/bottles/backend/runner.py +++ b/bottles/backend/runner.py @@ -41,7 +41,9 @@ class Runner: """ @staticmethod - def runner_update(config: BottleConfig, manager: 'Manager', runner: str) -> Result[dict]: + def runner_update( + config: BottleConfig, manager: "Manager", runner: str + ) -> Result[dict]: """ This method should be executed after changing the runner for a bottle. It does a prefix update and re-initialize the @@ -56,19 +58,14 @@ def runner_update(config: BottleConfig, manager: 'Manager', runner: str) -> Resu if not os.path.exists(runner_path): logging.error(f"Runner {runner} not found in {runner_path}") - return Result( - status=False, - data={"config": config} - ) + return Result(status=False, data={"config": config}) # kill wineserver after update wineboot.kill(force_if_stalled=True) # update bottle config up_config = manager.update_config( - config=config, - key="Runner", - value=runner + config=config, key="Runner", value=runner ).data["config"] # perform a prefix update @@ -90,10 +87,9 @@ def runner_update(config: BottleConfig, manager: 'Manager', runner: str) -> Resu the host system. There are some exceptions, like the Soda and Wine-GE runners, which are built to work without the Steam Runtime. """ - if SteamUtils.is_proton(ManagerUtils.get_runner_path(runner)) and RuntimeManager.get_runtimes("steam"): + if SteamUtils.is_proton( + ManagerUtils.get_runner_path(runner) + ) and RuntimeManager.get_runtimes("steam"): manager.update_config(config, "use_steam_runtime", True, "Parameters") - return Result( - status=True, - data={"config": up_config} - ) + return Result(status=True, data={"config": up_config}) diff --git a/bottles/backend/state.py b/bottles/backend/state.py index 2e22e4bd7d6..d07150b5189 100644 --- a/bottles/backend/state.py +++ b/bottles/backend/state.py @@ -26,13 +26,20 @@ class Events(Enum): class Signals(Enum): """Signals backend support""" + ManagerLocalBottlesLoaded = "Manager.local_bottles_loaded" # no extra data - ForceStopNetworking = "LoadingView.stop_networking" # status(bool): Force Stop network operations + ForceStopNetworking = ( + "LoadingView.stop_networking" # status(bool): Force Stop network operations + ) RepositoryFetched = "RepositoryManager.repo_fetched" # status: fetch success or not, data(int): total repositories - NetworkStatusChanged = "ConnectionUtils.status_changed" # status(bool): network ready or not + NetworkStatusChanged = ( + "ConnectionUtils.status_changed" # status(bool): network ready or not + ) - GNotification = "G.send_notification" # data(Notification): data for Gio notification + GNotification = ( + "G.send_notification" # data(Notification): data for Gio notification + ) GShowUri = "G.show_uri" # data(str): the URI # data(UUID): the UUID of task @@ -48,7 +55,12 @@ class Status(Enum): class TaskStreamUpdateHandler(Protocol): - def __call__(self, received_size: int = 0, total_size: int = 0, status: Optional[Status] = None) -> None: ... + def __call__( + self, + received_size: int = 0, + total_size: int = 0, + status: Optional[Status] = None, + ) -> None: ... class SignalHandler(Protocol): @@ -70,7 +82,13 @@ class Task: hidden: bool = False # hide from UI cancellable: bool = False - def __init__(self, title: str = "Task", subtitle: str = "", hidden: bool = False, cancellable: bool = False): + def __init__( + self, + title: str = "Task", + subtitle: str = "", + hidden: bool = False, + cancellable: bool = False, + ): self.title = title self.subtitle = subtitle self.hidden = hidden @@ -83,7 +101,9 @@ def task_id(self) -> UUID: @task_id.setter def task_id(self, value: UUID): if self._task_id is not None: - raise NotImplementedError("Invalid usage, Task.task_id should only set once") + raise NotImplementedError( + "Invalid usage, Task.task_id should only set once" + ) self._task_id = value @property @@ -95,7 +115,12 @@ def subtitle(self, value: str): self._subtitle = value SignalManager.send(Signals.TaskUpdated, Result(True, self.task_id)) - def stream_update(self, received_size: int = 0, total_size: int = 0, status: Optional[Status] = None): + def stream_update( + self, + received_size: int = 0, + total_size: int = 0, + status: Optional[Status] = None, + ): """This is a default subtitle updating handler for streaming downloading progress""" match status: case Status.DONE | Status.FAILED: @@ -142,6 +167,7 @@ class EventManager: You can wait for the event to occur, or set it when the associated operations are finished. Wait for an event that has already been set, will immediately return. """ + _EVENTS: Dict[Events, PyEvent] = {} @classmethod @@ -167,6 +193,7 @@ def reset(cls, event: Events): class TaskManager: """Long-running tasks are registered here, for tracking and display them on UI""" + _TASKS: Dict[UUID, Task] = {} # {UUID4: Task} @classmethod @@ -192,6 +219,7 @@ def remove(cls, task: Union[UUID, Task]): class SignalManager: """sync backend state to frontend via registered signal handlers""" + _SIGNALS: Dict[Signals, List[SignalHandler]] = {} @classmethod diff --git a/bottles/backend/utils/connection.py b/bottles/backend/utils/connection.py index d1c4a3c2095..600c57bd6dd 100644 --- a/bottles/backend/utils/connection.py +++ b/bottles/backend/utils/connection.py @@ -35,6 +35,7 @@ class ConnectionUtils: Bottle's website. If the connection is offline, the user will be notified and False will be returned, otherwise True. """ + _status: Optional[bool] = None last_check = None @@ -61,7 +62,7 @@ def __curl_progress(self, _download_t, _download_d, _upload_t, _upload_d): if self.do_check_connection: return pycurl.E_OK else: - self.aborted_connections+=1 + self.aborted_connections += 1 return pycurl.E_ABORTED_BY_CALLBACK def stop_check(self, res: Result): @@ -77,11 +78,11 @@ def check_connection(self, show_notification=False) -> bool: try: c = pycurl.Curl() - c.setopt(c.URL, 'https://ping.usebottles.com') + c.setopt(c.URL, "https://ping.usebottles.com") c.setopt(c.FOLLOWLOCATION, True) c.setopt(c.NOBODY, True) c.setopt(c.NOPROGRESS, False) - c.setopt(c.XFERINFOFUNCTION, self.__curl_progress) + c.setopt(c.XFERINFOFUNCTION, self.__curl_progress) c.perform() if c.getinfo(pycurl.HTTP_CODE) != 200: @@ -92,11 +93,17 @@ def check_connection(self, show_notification=False) -> bool: except Exception: logging.warning("Connection status: offline …") if show_notification: - SignalManager.send(Signals.GNotification, Result(True, Notification( - title="Bottles", - text=_("You are offline, unable to download."), - image="network-wireless-disabled-symbolic" - ))) + SignalManager.send( + Signals.GNotification, + Result( + True, + Notification( + title="Bottles", + text=_("You are offline, unable to download."), + image="network-wireless-disabled-symbolic", + ), + ), + ) self.last_check = datetime.now() self.status = False finally: diff --git a/bottles/backend/utils/decorators.py b/bottles/backend/utils/decorators.py index 277d552a0d4..bad638cc586 100644 --- a/bottles/backend/utils/decorators.py +++ b/bottles/backend/utils/decorators.py @@ -33,7 +33,7 @@ def cache(_func=None, *, seconds: int = 600, maxsize: int = 128, typed: bool = F def wrapper_cache(f): f = lru_cache(maxsize=maxsize, typed=typed)(f) - f.delta = seconds * 10 ** 9 + f.delta = seconds * 10**9 f.expiration = monotonic_ns() + f.delta @wraps(f) diff --git a/bottles/backend/utils/display.py b/bottles/backend/utils/display.py index 21f286f5c56..a970e1aee93 100644 --- a/bottles/backend/utils/display.py +++ b/bottles/backend/utils/display.py @@ -17,12 +17,17 @@ def get_x_display(): for i in ports_range: _port = f":{i}" - _proc = subprocess.Popen( - f"xdpyinfo -display :{i}", - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - shell=True - ).communicate()[0].decode("utf-8").lower() + _proc = ( + subprocess.Popen( + f"xdpyinfo -display :{i}", + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + shell=True, + ) + .communicate()[0] + .decode("utf-8") + .lower() + ) if "x.org" in _proc: return _port @@ -32,12 +37,17 @@ def get_x_display(): def check_nvidia_device(): """Check if there is an nvidia device connected""" _query = "NVIDIA Corporation".lower() - _proc = subprocess.Popen( - "lspci | grep 'VGA'", - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - shell=True - ).communicate()[0].decode("utf-8").lower() + _proc = ( + subprocess.Popen( + "lspci | grep 'VGA'", + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + shell=True, + ) + .communicate()[0] + .decode("utf-8") + .lower() + ) if _query in _proc: return True @@ -47,4 +57,3 @@ def check_nvidia_device(): def display_server_type(): """Return the display server type""" return os.environ.get("XDG_SESSION_TYPE", "x11").lower() - diff --git a/bottles/backend/utils/file.py b/bottles/backend/utils/file.py index 09ab00e1a7e..4efa6bb2db9 100644 --- a/bottles/backend/utils/file.py +++ b/bottles/backend/utils/file.py @@ -19,9 +19,10 @@ import os import shutil import time +import fcntl from pathlib import Path from typing import Union - +from array import array class FileUtils: """ @@ -47,9 +48,9 @@ def get_checksum(file): @staticmethod def use_insensitive_ext(string): """Converts a glob pattern into a case-insensitive glob pattern""" - ext = string.split('.')[1] + ext = string.split(".")[1] globlist = ["[%s%s]" % (c.lower(), c.upper()) for c in ext] - return '*.%s' % ''.join(globlist) + return "*.%s" % "".join(globlist) @staticmethod def get_human_size(size: float) -> str: @@ -63,12 +64,12 @@ def get_human_size(size: float) -> str: @staticmethod def get_human_size_legacy(size: float) -> str: """Returns a human readable size from a given float size""" - for unit in ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi']: + for unit in ["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"]: if abs(size) < 1024.0: - return "%3.1f%s%s" % (size, unit, 'B') + return "%3.1f%s%s" % (size, unit, "B") size /= 1024.0 - return "%.1f%s%s" % (size, 'Yi', 'B') + return "%.1f%s%s" % (size, "Yi", "B") def get_path_size(self, path: str, human: bool = True) -> Union[str, float]: """ @@ -76,7 +77,7 @@ def get_path_size(self, path: str, human: bool = True) -> Union[str, float]: human-readable size. """ p = Path(path) - size = sum(f.stat().st_size for f in p.glob('**/*') if f.is_file()) + size = sum(f.stat().st_size for f in p.glob("**/*") if f.is_file()) if human: return self.get_human_size(size) @@ -88,7 +89,7 @@ def get_disk_size(self, human: bool = True) -> dict: Returns the size of the disk. If human is True, returns as a human-readable size. """ - disk_total, disk_used, disk_free = shutil.disk_usage('/') + disk_total, disk_used, disk_free = shutil.disk_usage("/") if human: disk_total = self.get_human_size(disk_total) @@ -102,7 +103,7 @@ def get_disk_size(self, human: bool = True) -> dict: } @staticmethod - def wait_for_files(files: list, timeout: int = .5) -> bool: + def wait_for_files(files: list, timeout: int = 0.5) -> bool: """Wait for a file to be created or modified.""" for file in files: if not os.path.isfile(file): @@ -112,3 +113,25 @@ def wait_for_files(files: list, timeout: int = .5) -> bool: time.sleep(timeout) return True + + @staticmethod + def chattr_f(directory: str) -> bool: + FS_IOC_GETFLAGS = 0x80086601 + FS_IOC_SETFLAGS = 0x40086602 + FS_CASEFOLD_FL = 0x40000000 + + success = True + if os.path.isdir(directory) and len(os.listdir(directory)) == 0: + fd = os.open(directory, os.O_RDONLY) + try: + arg = array('L', [0]) + fcntl.ioctl(fd, FS_IOC_GETFLAGS, arg, True) + arg[0] |= FS_CASEFOLD_FL + fcntl.ioctl(fd, FS_IOC_SETFLAGS, arg, True) + except OSError: + success = False + os.close(fd) + else: + success = False + + return success diff --git a/bottles/backend/utils/generic.py b/bottles/backend/utils/generic.py index f23196c4a4a..a6b607cbd05 100644 --- a/bottles/backend/utils/generic.py +++ b/bottles/backend/utils/generic.py @@ -30,13 +30,13 @@ def validate_url(url: str): """Validate a URL.""" regex = re.compile( - r'^(?:http|ftp)s?://' - r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' - r'localhost|' - r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' - r'(?::\d+)?' - r'(?:/?|[/?]\S+)$', - re.IGNORECASE + r"^(?:http|ftp)s?://" + r"(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|" + r"localhost|" + r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})" + r"(?::\d+)?" + r"(?:/?|[/?]\S+)$", + re.IGNORECASE, ) return re.match(regex, url) is not None @@ -48,9 +48,9 @@ def detect_encoding(text: bytes, locale_hint: str = None) -> Optional[str]: can't be detected. """ if not text: # when empty - return 'utf-8' + return "utf-8" if locale_hint: # when hint available - hint = locale_hint.split('.') + hint = locale_hint.split(".") match len(hint): case 1: loc = hint[0] @@ -67,8 +67,8 @@ def detect_encoding(text: bytes, locale_hint: str = None) -> Optional[str]: case _: pass result = chardet.detect(text) - encoding = result['encoding'] - confidence = result['confidence'] + encoding = result["encoding"] + confidence = result["confidence"] if confidence < 0.5: return None return encoding @@ -78,11 +78,12 @@ def is_glibc_min_available(): """Check if the glibc minimum version is available.""" try: import ctypes + process_namespace = ctypes.CDLL(None) gnu_get_libc_version = process_namespace.gnu_get_libc_version gnu_get_libc_version.restype = ctypes.c_char_p - version = gnu_get_libc_version().decode('ascii') - if version >= '2.32': + version = gnu_get_libc_version().decode("ascii") + if version >= "2.32": return version except: pass @@ -92,7 +93,9 @@ def is_glibc_min_available(): def sort_by_version(_list: list, extra_check: str = "async"): def natural_keys(text): result = [int(re.search(extra_check, text) is None)] - result.extend([int(t) if t.isdigit() else t.lower() for t in re.split('(\d+)', text)]) + result.extend( + [int(t) if t.isdigit() else t.lower() for t in re.split(r"(\d+)", text)] + ) return result _list.sort(key=natural_keys, reverse=True) @@ -104,9 +107,11 @@ def get_mime(path: str): with contextlib.suppress(FileNotFoundError): res = subprocess.check_output(["file", "--mime-type", path]) if res: - return res.decode('utf-8').split(':')[1].strip() + return res.decode("utf-8").split(":")[1].strip() return None def random_string(length: int): - return ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(length)) + return "".join( + random.choice(string.ascii_uppercase + string.digits) for _ in range(length) + ) diff --git a/bottles/backend/utils/gpu.py b/bottles/backend/utils/gpu.py index 321837c464b..00207971d92 100644 --- a/bottles/backend/utils/gpu.py +++ b/bottles/backend/utils/gpu.py @@ -32,12 +32,13 @@ class GPUVendors(Enum): NVIDIA = "nvidia" INTEL = "intel" + # noinspection PyTypeChecker class GPUUtils: __vendors = { "nvidia": "NVIDIA Corporation", "amd": "Advanced Micro Devices, Inc.", - "intel": "Intel Corporation" + "intel": "Intel Corporation", } def __init__(self): @@ -50,7 +51,7 @@ def list_all(self): f"lspci | grep '{self.__vendors[_vendor]}'", stdout=subprocess.PIPE, stderr=subprocess.PIPE, - shell=True + shell=True, ) stdout, stderr = _proc.communicate() @@ -75,7 +76,7 @@ def is_nouveau(): "lsmod | grep nouveau", stdout=subprocess.PIPE, stderr=subprocess.PIPE, - shell=True + shell=True, ) stdout, stderr = _proc.communicate() if len(stdout) > 0: @@ -85,15 +86,9 @@ def is_nouveau(): def get_gpu(self): checks = { - "nvidia": { - "query": "(VGA|3D).*NVIDIA" - }, - "amd": { - "query": "(VGA|3D).*AMD/ATI" - }, - "intel": { - "query": "(VGA|3D).*Intel" - } + "nvidia": {"query": "(VGA|3D).*NVIDIA"}, + "amd": {"query": "(VGA|3D).*AMD/ATI"}, + "intel": {"query": "(VGA|3D).*Intel"}, } gpus = { "nvidia": { @@ -101,34 +96,24 @@ def get_gpu(self): "envs": { "__NV_PRIME_RENDER_OFFLOAD": "1", "__GLX_VENDOR_LIBRARY_NAME": "nvidia", - "__VK_LAYER_NV_optimus": "NVIDIA_only" + "__VK_LAYER_NV_optimus": "NVIDIA_only", }, "icd": self.vk.get_vk_icd("nvidia", as_string=True), - "nvngx_path": get_nvidia_dll_path() + "nvngx_path": get_nvidia_dll_path(), }, "amd": { "vendor": "amd", - "envs": { - "DRI_PRIME": "1" - }, - "icd": self.vk.get_vk_icd("amd", as_string=True) + "envs": {"DRI_PRIME": "1"}, + "icd": self.vk.get_vk_icd("amd", as_string=True), }, "intel": { "vendor": "intel", - "envs": { - "DRI_PRIME": "1" - }, - "icd": self.vk.get_vk_icd("intel", as_string=True) - } + "envs": {"DRI_PRIME": "1"}, + "icd": self.vk.get_vk_icd("intel", as_string=True), + }, } found = [] - result = { - "vendors": {}, - "prime": { - "integrated": None, - "discrete": None - } - } + result = {"vendors": {}, "prime": {"integrated": None, "discrete": None}} if self.is_nouveau(): gpus["nvidia"]["envs"] = {"DRI_PRIME": "1"} @@ -140,7 +125,7 @@ def get_gpu(self): f"lspci | grep -iP '{_query}'", stdout=subprocess.PIPE, stderr=subprocess.PIPE, - shell=True + shell=True, ) stdout, stderr = _proc.communicate() if len(stdout) > 0: @@ -164,7 +149,7 @@ def is_gpu(vendor: GPUVendors) -> bool: f"lspci | grep -iP '{vendor.value}'", stdout=subprocess.PIPE, stderr=subprocess.PIPE, - shell=True + shell=True, ) stdout, stderr = _proc.communicate() return len(stdout) > 0 diff --git a/bottles/backend/utils/imagemagick.py b/bottles/backend/utils/imagemagick.py index cf3c9f78fc3..c83e560158d 100644 --- a/bottles/backend/utils/imagemagick.py +++ b/bottles/backend/utils/imagemagick.py @@ -35,7 +35,7 @@ def list_assets(self): cmd = f"identify '{self.path}'" try: - res = subprocess.check_output(['bash', "-c", cmd]) + res = subprocess.check_output(["bash", "-c", cmd]) except: return [] @@ -50,7 +50,15 @@ def list_assets(self): continue return assets - def convert(self, dest: str, asset_size: int = 256, resize: int = 256, flatten: bool = True, alpha: bool = True, fallback: bool = True): + def convert( + self, + dest: str, + asset_size: int = 256, + resize: int = 256, + flatten: bool = True, + alpha: bool = True, + fallback: bool = True, + ): if not self.__validate_path(dest): raise FileExistsError("Destination path already exists") diff --git a/bottles/backend/utils/json.py b/bottles/backend/utils/json.py index e38a8769c3b..6f38c97ac43 100644 --- a/bottles/backend/utils/json.py +++ b/bottles/backend/utils/json.py @@ -1,4 +1,5 @@ """This should be a drop-in replacement for the json module built in CPython""" + import json import json as _json from typing import Optional, IO, Any, Type @@ -26,8 +27,11 @@ def loads(s: str | bytes) -> Any: def dump( - obj: Any, fp: IO[str], *, - indent: Optional[str | int] = None, cls: Optional[Type[_json.JSONEncoder]] = None + obj: Any, + fp: IO[str], + *, + indent: Optional[str | int] = None, + cls: Optional[Type[_json.JSONEncoder]] = None ) -> None: """ Serialize obj as a JSON formatted stream to fp (a .write()-supporting file-like object). @@ -42,7 +46,12 @@ def dump( return _json.dump(obj, fp, indent=indent, cls=cls) -def dumps(obj: Any, *, indent: Optional[str | int] = None, cls: Optional[Type[_json.JSONEncoder]] = None) -> str: +def dumps( + obj: Any, + *, + indent: Optional[str | int] = None, + cls: Optional[Type[_json.JSONEncoder]] = None +) -> str: """ Serialize obj to a JSON formatted str. diff --git a/bottles/backend/utils/lnk.py b/bottles/backend/utils/lnk.py index 36131f9582c..d6a660afcb1 100644 --- a/bottles/backend/utils/lnk.py +++ b/bottles/backend/utils/lnk.py @@ -30,45 +30,45 @@ def get_data(path): Thanks to @Winand and @Jared for the code. """ - with open(path, 'rb') as stream: + with open(path, "rb") as stream: content = stream.read() - ''' + """ Skip first 20 bytes (HeaderSize and LinkCLSID) read the LinkFlags structure (4 bytes) - ''' - lflags = struct.unpack('I', content[0x14:0x18])[0] + """ + lflags = struct.unpack("I", content[0x14:0x18])[0] position = 0x18 if (lflags & 0x01) == 1: - ''' - If the HasLinkTargetIDList bit is set then skip the stored IDList + """ + If the HasLinkTargetIDList bit is set then skip the stored IDList structure and header - ''' - position = struct.unpack('H', content[0x4C:0x4E])[0] + 0x4E + """ + position = struct.unpack("H", content[0x4C:0x4E])[0] + 0x4E last_pos = position position += 0x04 # get how long the file information is (LinkInfoSize) - length = struct.unpack('I', content[last_pos:position])[0] + length = struct.unpack("I", content[last_pos:position])[0] - ''' + """ Skip 12 bytes (LinkInfoHeaderSize, LinkInfoFlags and VolumeIDOffset) - ''' + """ position += 0x0C # go to the LocalBasePath position - lbpos = struct.unpack('I', content[position:position + 0x04])[0] + lbpos = struct.unpack("I", content[position : position + 0x04])[0] position = last_pos + lbpos # read the string at the given position of the determined length size = (length + last_pos) - position - 0x02 - content = content[position:position + size].split(b'\x00', 1) + content = content[position : position + size].split(b"\x00", 1) decode = locale.getdefaultlocale()[1] if len(content) > 1 or decode is None: - decode = 'utf-16' + decode = "utf-16" try: return content[-1].decode(decode) diff --git a/bottles/backend/utils/nvidia.py b/bottles/backend/utils/nvidia.py index abf4bde596c..6a289bd53e5 100644 --- a/bottles/backend/utils/nvidia.py +++ b/bottles/backend/utils/nvidia.py @@ -1,4 +1,5 @@ """This file originated from Lutris (https://github.com/lutris/lutris/blob/master/lutris/util/nvidia.py)""" + """Nvidia library detection from Proton""" import os @@ -28,6 +29,7 @@ class LinkMap(Structure): /* Plus additional fields private to the implementation */ }; """ + _fields_ = [("l_addr", c_void_p), ("l_name", c_char_p), ("l_ld", c_void_p)] @@ -60,7 +62,8 @@ def get_nvidia_glx_path(): if ( dlinfo_func( libglx_nvidia._handle, RTLD_DI_LINKMAP, addressof(glx_nvidia_info_ptr) - ) != 0 + ) + != 0 ): logging.error("Unable to read Nvidia information") return None @@ -89,7 +92,9 @@ def get_nvidia_dll_path(): background on the chosen method of DLL discovery. """ from bottles.backend.utils.gpu import GPUUtils, GPUVendors - if not GPUUtils.is_gpu(GPUVendors.NVIDIA): return None + + if not GPUUtils.is_gpu(GPUVendors.NVIDIA): + return None libglx_path = get_nvidia_glx_path() if not libglx_path: diff --git a/bottles/backend/utils/proc.py b/bottles/backend/utils/proc.py index 8587757c46a..dbebadc410a 100644 --- a/bottles/backend/utils/proc.py +++ b/bottles/backend/utils/proc.py @@ -25,25 +25,29 @@ def __init__(self, pid): def __get_data(self, data): try: - with open(os.path.join('/proc', str(self.pid), data), 'rb') as f: - return f.read().decode('utf-8') + with open(os.path.join("/proc", str(self.pid), data), "rb") as f: + return f.read().decode("utf-8") except (FileNotFoundError, PermissionError): return "" def get_cmdline(self): - return self.__get_data('cmdline') + return self.__get_data("cmdline") def get_env(self): - return self.__get_data('environ') + return self.__get_data("environ") def get_cwd(self): - return self.__get_data('cwd') + return self.__get_data("cwd") def get_name(self): - return self.__get_data('stat') + return self.__get_data("stat") def kill(self): - subprocess.Popen(['kill', str(self.pid)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + subprocess.Popen( + ["kill", str(self.pid)], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) class ProcUtils: @@ -51,7 +55,7 @@ class ProcUtils: @staticmethod def get_procs(): procs = [] - for pid in os.listdir('/proc'): + for pid in os.listdir("/proc"): if pid.isdigit(): procs.append(Proc(pid)) return procs diff --git a/bottles/backend/utils/steam.py b/bottles/backend/utils/steam.py index bafad3b9125..b6436c2bb53 100644 --- a/bottles/backend/utils/steam.py +++ b/bottles/backend/utils/steam.py @@ -15,7 +15,7 @@ # along with this program. If not, see . # -import os, subprocess +import os, subprocess, shlex from typing import Union, TextIO from typing import TextIO @@ -25,6 +25,7 @@ logging = Logger() + class SteamUtils: @staticmethod @@ -59,11 +60,9 @@ def is_proton(path: str) -> bool: f = open(toolmanifest, "r", errors="replace") data = SteamUtils.parse_vdf(f.read()) - compat_layer_name = data.get("manifest", {}) \ - .get("compatmanager_layer_name", {}) + compat_layer_name = data.get("manifest", {}).get("compatmanager_layer_name", {}) - commandline = data.get("manifest", {}) \ - .get("commandline", {}) + commandline = data.get("manifest", {}).get("commandline", {}) return "proton" in compat_layer_name or "proton" in commandline @@ -80,8 +79,7 @@ def get_associated_runtime(path: str) -> str: runtime = "scout" f = open(toolmanifest, "r", errors="replace") data = SteamUtils.parse_vdf(f.read()) - tool_appid = data.get("manifest", {}) \ - .get("require_tool_appid", {}) + tool_appid = data.get("manifest", {}).get("require_tool_appid", {}) if "1628350" in tool_appid: runtime = "sniper" @@ -101,6 +99,38 @@ def get_dist_directory(path: str) -> str: elif os.path.isdir(os.path.join(path, f"files")): dist_directory = os.path.join(path, f"files") else: - logging.warning(f"No /dist or /files sub-directory was found under this Proton directory: {path}") + logging.warning( + f"No /dist or /files sub-directory was found under this Proton directory: {path}" + ) return dist_directory + + @staticmethod + def handle_launch_options(launch_options: str) -> tuple[str, str, str]: + """ + Handle launch options. Supports the %command% pattern. + Return prefix, arguments, and environment variables. + """ + env_vars = {} + prefix, args = "", "" + if "%command%" in launch_options: + _c = launch_options.split("%command%") + prefix = _c[0] if len(_c) > 0 else "" + args = _c[1] if len(_c) > 1 else "" + else: + args = launch_options + + try: + prefix_list = shlex.split(prefix.strip()) + except ValueError: + prefix_list = prefix.split(shlex.quote(prefix.strip())) + + for p in prefix_list.copy(): + if "=" in p: + k, v = p.split("=", 1) + v = shlex.quote(v) if " " in v else v + env_vars[k] = v + prefix_list.remove(p) + + prefix = " ".join(prefix_list) + return prefix, args, env_vars diff --git a/bottles/backend/utils/terminal.py b/bottles/backend/utils/terminal.py index 103b61de3b2..4acd1a5c115 100644 --- a/bottles/backend/utils/terminal.py +++ b/bottles/backend/utils/terminal.py @@ -17,6 +17,7 @@ import os import subprocess +import shlex from bottles.backend.logger import Logger @@ -29,6 +30,7 @@ class TerminalUtils: It will loop all the "supported" terminals to find the one that is available, so it will be used to launch the command. """ + colors = { "default": "#00ffff #2b2d2e", "debug": "#ff9800 #2e2c2b", @@ -37,21 +39,21 @@ class TerminalUtils: terminals = [ # Part of Flatpak package - ['easyterm.py', '-d -p "%s" -c %s'], + ["easyterm.py", '-d -p "%s" -c %s'], # Third party - ['foot', '%s'], - ['kitty', '%s'], - ['tilix', '-- %s'], + ["foot", "%s"], + ["kitty", "%s"], + ["tilix", "-- %s"], # Desktop environments - ['xfce4-terminal', '-e %s'], - ['konsole', '--noclose -e %s'], - ['gnome-terminal', '-- %s'], - ['kgx', '-e %s'], - ['mate-terminal', '--command %s'], - ['qterminal', '--execute %s'], - ['lxterminal', '-e %s'], + ["xfce4-terminal", "-e %s"], + ["konsole", "--noclose -e %s"], + ["gnome-terminal", "-- %s"], + ["kgx", "-e %s"], + ["mate-terminal", "--command %s"], + ["qterminal", "--execute %s"], + ["lxterminal", "-e %s"], # Fallback - ['xterm', '-e %s'], + ["xterm", "-e %s"], ] def __init__(self): @@ -63,11 +65,15 @@ def check_support(self): return True for terminal in self.terminals: - terminal_check = subprocess.Popen( - f"command -v {terminal[0]} > /dev/null && echo 1 || echo 0", - shell=True, - stdout=subprocess.PIPE - ).communicate()[0].decode("utf-8") + terminal_check = ( + subprocess.Popen( + f"command -v {terminal[0]} > /dev/null && echo 1 || echo 0", + shell=True, + stdout=subprocess.PIPE, + ) + .communicate()[0] + .decode("utf-8") + ) if "1" in terminal_check: self.terminal = terminal @@ -87,24 +93,26 @@ def execute(self, command, env=None, colors="default", cwd=None): colors = "default" colors = self.colors[colors] + command = shlex.quote(command) - if self.terminal[0] == 'easyterm.py': - command = ' '.join(self.terminal) % (colors, f'bash -c "{command}"') + if self.terminal[0] == "easyterm.py": + command = " ".join(self.terminal) % ( + colors, + shlex.quote(f"bash -c {command}"), + ) if "ENABLE_BASH" in os.environ: - command = ' '.join(self.terminal) % (colors, f"bash") - elif self.terminal[0] in ['kgx', 'xfce4-terminal']: - command = ' '.join(self.terminal) % "'sh -c %s'" % f'"{command}"' - elif self.terminal[0] in ['kitty', 'foot', 'konsole', 'gnome-terminal']: - command = ' '.join(self.terminal) % "sh -c %s" % f'"{command}"' + command = " ".join(self.terminal) % (colors, f"bash") + elif self.terminal[0] in ["xfce4-terminal"]: + command = " ".join(self.terminal) % "'sh -c %s'" % f"{command}" + elif self.terminal[0] in ["kitty", "foot", "konsole", "gnome-terminal"]: + command = " ".join(self.terminal) % "sh -c %s" % f"{command}" else: - command = ' '.join(self.terminal) % "bash -c %s" % f'"{command}"' + command = " ".join(self.terminal) % "bash -c %s" % f"{command}" + + logging.info(f"Command: {command}") subprocess.Popen( - command, - shell=True, - env=env, - stdout=subprocess.PIPE, - cwd=cwd + command, shell=True, env=env, stdout=subprocess.PIPE, cwd=cwd ).communicate()[0].decode("utf-8") return True @@ -112,7 +120,4 @@ def execute(self, command, env=None, colors="default", cwd=None): def launch_snake(self): snake_path = os.path.dirname(os.path.realpath(__file__)) snake_path = os.path.join(snake_path, "snake.py") - self.execute( - command="python %s" % snake_path, - colors="easter" - ) + self.execute(command="python %s" % snake_path, colors="easter") diff --git a/bottles/backend/utils/threading.py b/bottles/backend/utils/threading.py index c7b48476821..57beb6d1ec0 100644 --- a/bottles/backend/utils/threading.py +++ b/bottles/backend/utils/threading.py @@ -32,9 +32,12 @@ class RunAsync(threading.Thread): It takes a function, a callback and a list of arguments as input. """ - def __init__(self, task_func, callback=None, daemon=True, *args: Any, **kwargs: Any): + def __init__( + self, task_func, callback=None, daemon=True, *args: Any, **kwargs: Any + ): if "DEBUG_MODE" in os.environ: import faulthandler + faulthandler.enable() logging.debug( @@ -58,13 +61,15 @@ def __target(self, *args, **kwargs): try: result = self.task_func(*args, **kwargs) except Exception as exception: - logging.error(f"Error while running async job: {self.task_func}\n" - f"Exception: {exception}") + logging.error( + f"Error while running async job: {self.task_func}\n" + f"Exception: {exception}" + ) error = exception _ex_type, _ex_value, trace = sys.exc_info() traceback.print_tb(trace) - traceback_info = '\n'.join(traceback.format_tb(trace)) + traceback_info = "\n".join(traceback.format_tb(trace)) logging.write_log([str(exception), traceback_info]) self.callback(result, error) @@ -74,6 +79,16 @@ def run_async(func): def inner(*args, **kwargs): # Here we add None in the arguments so that callback=None, # but we still pass all the required argument to the function called - RunAsync(func, *((None, True,) + args), **kwargs) + RunAsync( + func, + *( + ( + None, + True, + ) + + args + ), + **kwargs, + ) return inner diff --git a/bottles/backend/utils/yaml.py b/bottles/backend/utils/yaml.py index 85959b5baf4..37007d96e79 100644 --- a/bottles/backend/utils/yaml.py +++ b/bottles/backend/utils/yaml.py @@ -26,8 +26,8 @@ def dump(data, stream=None, **kwargs): def load(stream, Loader=SafeLoader): """ Load a YAML stream. - Note: This function is a replacement for PyYAML's safe_load() function, - using the CLoader class instead of the default Loader, to achieve + Note: This function is a replacement for PyYAML's safe_load() function, + using the CLoader class instead of the default Loader, to achieve the best performance. """ return _yaml.load(stream, Loader=Loader) diff --git a/bottles/backend/wine/catalogs.py b/bottles/backend/wine/catalogs.py index 4dd4c64091a..bf1fdca110d 100644 --- a/bottles/backend/wine/catalogs.py +++ b/bottles/backend/wine/catalogs.py @@ -8,7 +8,7 @@ "CurrentMinorVersionNumber": "0", "CurrentMajorVersionNumber": "a", # 10 "ProductType": "WinNT", - "ProductName": "Microsoft Windows 10" + "ProductName": "Microsoft Windows 10", }, "win81": { "CSDVersion": "", @@ -19,7 +19,7 @@ "CurrentMinorVersionNumber": "3", "CurrentMajorVersionNumber": "6", "ProductType": "WinNT", - "ProductName": "Microsoft Windows 8.1" + "ProductName": "Microsoft Windows 8.1", }, "win8": { "CSDVersion": "", @@ -30,7 +30,7 @@ "CurrentMinorVersionNumber": "2", "CurrentMajorVersionNumber": "6", "ProductType": "WinNT", - "ProductName": "Microsoft Windows 8" + "ProductName": "Microsoft Windows 8", }, "win7": { "CSDVersion": "Service Pack 1", @@ -41,7 +41,7 @@ "CurrentMinorVersionNumber": "1", "CurrentMajorVersionNumber": "6", "ProductType": "WinNT", - "ProductName": "Microsoft Windows 7" + "ProductName": "Microsoft Windows 7", }, "win2008r2": { "CSDVersion": "Service Pack 1", @@ -52,7 +52,7 @@ "CurrentMinorVersionNumber": "1", "CurrentMajorVersionNumber": "6", "ProductType": "ServerNT", - "ProductName": "Microsoft Windows 2008 R2" + "ProductName": "Microsoft Windows 2008 R2", }, "win2008": { "CSDVersion": "Service Pack 2", @@ -63,7 +63,7 @@ "CurrentMinorVersionNumber": "0", "CurrentMajorVersionNumber": "6", "ProductType": "ServerNT", - "ProductName": "Microsoft Windows 2008" + "ProductName": "Microsoft Windows 2008", }, "win2003": { "CSDVersion": "Service Pack 2", @@ -74,7 +74,7 @@ "CurrentMinorVersionNumber": "2", "CurrentMajorVersionNumber": "5", "ProductType": "ServerNT", - "ProductName": "Microsoft Windows 2003" + "ProductName": "Microsoft Windows 2003", }, "winxp": { "CSDVersion": "Service Pack 3", @@ -84,7 +84,7 @@ "CurrentVersion": "5.1", "CurrentMinorVersionNumber": "2", "CurrentMajorVersionNumber": "5", - "ProductName": "Microsoft Windows XP" + "ProductName": "Microsoft Windows XP", }, "winxp64": { "CSDVersion": "Service Pack 2", @@ -95,20 +95,20 @@ "CurrentMinorVersionNumber": "2", "CurrentMajorVersionNumber": "5", "ProductType": "WinNT", - "ProductName": "Microsoft Windows XP" + "ProductName": "Microsoft Windows XP", }, "win98": { "CSDVersion": "", "CSDVersionHex": "00000000", "VersionNumber": "4.10.2222", "SubVersionNumber": " A ", - "ProductName": "Microsoft Windows 98" + "ProductName": "Microsoft Windows 98", }, "win95": { "CSDVersion": "", "CSDVersionHex": "00000000", "VersionNumber": "4.0.950", "SubVersionNumber": "", - "ProductName": "Microsoft Windows 95" + "ProductName": "Microsoft Windows 95", }, } diff --git a/bottles/backend/wine/cmd.py b/bottles/backend/wine/cmd.py index e00d4371735..2b1e9852d85 100644 --- a/bottles/backend/wine/cmd.py +++ b/bottles/backend/wine/cmd.py @@ -11,12 +11,12 @@ class CMD(WineProgram): command = "cmd" def run_batch( - self, - batch: str, - terminal: bool = True, - args: str = "", - environment: Optional[dict] = None, - cwd: Optional[str] = None + self, + batch: str, + terminal: bool = True, + args: str = "", + environment: Optional[dict] = None, + cwd: Optional[str] = None, ): args = f"/c {batch} {args}" @@ -26,5 +26,5 @@ def run_batch( terminal=terminal, environment=environment, cwd=cwd, - action_name="run_batch" + action_name="run_batch", ) diff --git a/bottles/backend/wine/control.py b/bottles/backend/wine/control.py index 89f086ef723..c9b2800f5a9 100644 --- a/bottles/backend/wine/control.py +++ b/bottles/backend/wine/control.py @@ -1,4 +1,3 @@ - from bottles.backend.logger import Logger from bottles.backend.wine.wineprogram import WineProgram diff --git a/bottles/backend/wine/eject.py b/bottles/backend/wine/eject.py index fd74798912e..703c047a1da 100644 --- a/bottles/backend/wine/eject.py +++ b/bottles/backend/wine/eject.py @@ -1,4 +1,3 @@ - from bottles.backend.logger import Logger from bottles.backend.wine.wineprogram import WineProgram diff --git a/bottles/backend/wine/executor.py b/bottles/backend/wine/executor.py index 2a734eb1438..a4c5eb61e11 100644 --- a/bottles/backend/wine/executor.py +++ b/bottles/backend/wine/executor.py @@ -3,6 +3,9 @@ import uuid from typing import Union, Optional +from bottles.backend.dlls.dxvk import DXVKComponent +from bottles.backend.dlls.nvapi import NVAPIComponent +from bottles.backend.dlls.vkd3d import VKD3DComponent from bottles.backend.logger import Logger from bottles.backend.models.config import BottleConfig from bottles.backend.models.result import Result @@ -21,22 +24,22 @@ class WineExecutor: def __init__( - self, - config: BottleConfig, - exec_path: str, - args: str = "", - terminal: bool = False, - cwd: Optional[str] = None, - environment: Optional[dict] = None, - move_file: bool = False, - move_upd_fn: callable = None, - post_script: Optional[str] = None, - monitoring: Optional[list] = None, - override_dxvk: Optional[bool] = None, - override_vkd3d: Optional[bool] = None, - override_nvapi: Optional[bool] = None, - override_fsr: Optional[bool] = None, - override_virt_desktop: Optional[bool] = None + self, + config: BottleConfig, + exec_path: str, + args: str = "", + terminal: bool = False, + cwd: Optional[str] = None, + environment: Optional[dict] = None, + move_file: bool = False, + move_upd_fn: callable = None, + post_script: Optional[str] = None, + monitoring: Optional[list] = None, + program_dxvk: Optional[bool] = None, + program_vkd3d: Optional[bool] = None, + program_nvapi: Optional[bool] = None, + program_fsr: Optional[bool] = None, + program_virt_desktop: Optional[bool] = None, ): logging.info("Launching an executable…") self.config = config @@ -59,67 +62,63 @@ def __init__( self.environment = environment self.post_script = post_script self.monitoring = monitoring - self.use_virt_desktop = override_virt_desktop + self.use_virt_desktop = program_virt_desktop env_dll_overrides = [] - if override_dxvk is not None \ - and not override_dxvk \ - and self.config.Parameters.dxvk: - env_dll_overrides.append("d3d9,d3d11,d3d10core,dxgi=n") - - if override_vkd3d is not None \ - and not override_vkd3d \ - and self.config.Parameters.vkd3d: - env_dll_overrides.append("d3d12,d3d12core=n") - - if override_nvapi is not None \ - and not override_nvapi \ - and self.config.Parameters.dxvk_nvapi: - env_dll_overrides.append("nvapi,nvapi64=n") - - if override_fsr is not None and override_fsr: - self.environment["WINE_FULLSCREEN_FSR"] = "1" - self.environment["WINE_FULLSCREEN_FSR_STRENGTH"] = str(self.config.Parameters.fsr_sharpening_strength) - - if "WINEDLLOVERRIDES" in self.environment: - self.environment["WINEDLLOVERRIDES"] += "," + ",".join(env_dll_overrides) - else: - self.environment["WINEDLLOVERRIDES"] = ",".join(env_dll_overrides) + + # None = use global DXVK value + if program_dxvk is not None: + # DXVK is globally activated, but disabled for the program + if not program_dxvk and self.config.Parameters.dxvk: + # Disable DXVK for the program + override_dxvk = DXVKComponent.get_override_keys() + "=b" + env_dll_overrides.append(override_dxvk) + + if program_vkd3d is not None: + if not program_vkd3d and self.config.Parameters.vkd3d: + override_vkd3d = VKD3DComponent.get_override_keys() + "=b" + env_dll_overrides.append(override_vkd3d) + + if program_nvapi is not None: + if not program_nvapi and self.config.Parameters.dxvk_nvapi: + override_nvapi = NVAPIComponent.get_override_keys() + "=b" + env_dll_overrides.append(override_nvapi) + + if program_fsr is not None and program_fsr != self.config.Parameters.fsr: + self.environment["WINE_FULLSCREEN_FSR"] = "1" if program_fsr else "0" + self.environment["WINE_FULLSCREEN_FSR_STRENGTH"] = str( + self.config.Parameters.fsr_sharpening_strength + ) + if self.config.Parameters.fsr_quality_mode: + self.environment["WINE_FULLSCREEN_FSR_MODE"] = str( + self.config.Parameters.fsr_quality_mode + ) + + if env_dll_overrides: + if "WINEDLLOVERRIDES" in self.environment: + self.environment["WINEDLLOVERRIDES"] += ";" + ";".join( + env_dll_overrides + ) + else: + self.environment["WINEDLLOVERRIDES"] = ";".join(env_dll_overrides) @classmethod def run_program(cls, config: BottleConfig, program: dict, terminal: bool = False): if program is None: logging.warning("The program entry is not well formatted.") - dxvk = config.Parameters.dxvk - vkd3d = config.Parameters.vkd3d - nvapi = config.Parameters.dxvk_nvapi - fsr = config.Parameters.fsr - virt_desktop = config.Parameters.virtual_desktop - - if program.get("dxvk") != dxvk: - dxvk = program.get("dxvk") - if program.get("vkd3d") != vkd3d: - vkd3d = program.get("vkd3d") - if program.get("dxvk_nvapi") != nvapi: - nvapi = program.get("dxvk_nvapi") - if program.get("fsr") != fsr: - fsr = program.get("fsr") - if program.get("virtual_desktop") != virt_desktop: - virt_desktop = program.get("virtual_desktop") - return cls( config=config, - exec_path=program["path"], - args=program.get("arguments", ""), - cwd=program.get("folder", None), - post_script=program.get("script", None), + exec_path=program.get("path"), + args=program.get("arguments"), + cwd=program.get("folder"), + post_script=program.get("script"), terminal=terminal, - override_dxvk=dxvk, - override_vkd3d=vkd3d, - override_nvapi=nvapi, - override_fsr=fsr, - override_virt_desktop=virt_desktop + program_dxvk=program.get("dxvk"), + program_vkd3d=program.get("vkd3d"), + program_nvapi=program.get("dxvk_nvapi"), + program_fsr=program.get("fsr"), + program_virt_desktop=program.get("virtual_desktop"), ).run() def __get_cwd(self, cwd: str) -> Union[str, None]: @@ -150,14 +149,14 @@ def __validate_path(exec_path): _msg = f"Executable file path does not exist: {exec_path}" if "FLATPAK_ID" in os.environ: _msg = f"Executable file path does not exist or is not accessible by the Flatpak: {exec_path}" - logging.error(_msg, ) + logging.error( + _msg, + ) return False def __move_file(self, exec_path, move_upd_fn): new_path = ManagerUtils.move_file_to_bottle( - file_path=exec_path, - config=self.config, - fn_update=move_upd_fn + file_path=exec_path, config=self.config, fn_update=move_upd_fn ) if new_path: exec_path = new_path @@ -200,12 +199,9 @@ def run_cli(self): terminal=self.terminal, args=self.args, environment=self.environment, - cwd=self.cwd - ) - return Result( - status=True, - data={"output": res} + cwd=self.cwd, ) + return Result(status=True, data={"output": res}) def run(self) -> Result: match self.exec_type: @@ -219,8 +215,7 @@ def run(self) -> Result: return self.__launch_dll() case _: return Result( - status=False, - data={"message": "Unknown executable type."} + status=False, data={"message": "Unknown executable type."} ) def __launch_with_bridge(self): @@ -247,10 +242,9 @@ def __launch_with_bridge(self): case "batch": return self.__launch_batch() case _: - logging.error(f'exec_type {self.exec_type} is not valid') + logging.error(f"exec_type {self.exec_type} is not valid") return Result( - status=False, - data={"message": "Unknown executable type."} + status=False, data={"message": "Unknown executable type."} ) def __launch_exe(self): @@ -270,14 +264,11 @@ def __launch_exe(self): cwd=self.cwd, environment=self.environment, communicate=True, - post_script=self.post_script + post_script=self.post_script, ) res = winecmd.run() self.__set_monitors() - return Result( - status=True, - data={"output": res} - ) + return Result(status=True, data={"output": res}) def __launch_msi(self): msiexec = MsiExec(self.config) @@ -286,7 +277,7 @@ def __launch_msi(self): args=self.args, terminal=self.terminal, cwd=self.cwd, - environment=self.environment + environment=self.environment, ) self.__set_monitors() return Result(True) @@ -298,12 +289,9 @@ def __launch_batch(self): terminal=self.terminal, args=self.args, environment=self.environment, - cwd=self.cwd - ) - return Result( - status=True, - data={"output": res} + cwd=self.cwd, ) + return Result(status=True, data={"output": res}) def __launch_with_starter(self): start = Start(self.config) @@ -312,13 +300,10 @@ def __launch_with_starter(self): terminal=self.terminal, args=self.args, environment=self.environment, - cwd=self.cwd + cwd=self.cwd, ) self.__set_monitors() - return Result( - status=True, - data={"output": res} - ) + return Result(status=True, data={"output": res}) def __launch_with_explorer(self): w, h = self.config.Parameters.virtual_desktop_res.split("x") @@ -330,21 +315,15 @@ def __launch_with_explorer(self): program=self.exec_path, args=self.args, environment=self.environment, - cwd=self.cwd + cwd=self.cwd, ) self.__set_monitors() - return Result( - status=res.status, - data={"output": res.data} - ) + return Result(status=res.status, data={"output": res.data}) @staticmethod def __launch_dll(): logging.warning("DLLs are not supported yet.") - return Result( - status=False, - data={"error": "DLLs are not supported yet."} - ) + return Result(status=False, data={"error": "DLLs are not supported yet."}) def __set_monitors(self): if not self.monitoring: diff --git a/bottles/backend/wine/expand.py b/bottles/backend/wine/expand.py index 904f2d59b28..7832169c12f 100644 --- a/bottles/backend/wine/expand.py +++ b/bottles/backend/wine/expand.py @@ -1,4 +1,3 @@ - from bottles.backend.logger import Logger from bottles.backend.wine.wineprogram import WineProgram diff --git a/bottles/backend/wine/explorer.py b/bottles/backend/wine/explorer.py index af4c0f3b180..34212b92730 100644 --- a/bottles/backend/wine/explorer.py +++ b/bottles/backend/wine/explorer.py @@ -11,14 +11,14 @@ class Explorer(WineProgram): command = "explorer" def launch_desktop( - self, - desktop: str = "shell", - width: int = 0, - height: int = 0, - program: Optional[str] = None, - args: Optional[str] = None, - environment: Optional[dict] = None, - cwd: Optional[str] = None + self, + desktop: str = "shell", + width: int = 0, + height: int = 0, + program: Optional[str] = None, + args: Optional[str] = None, + environment: Optional[dict] = None, + cwd: Optional[str] = None, ): _args = f"/desktop={desktop}" @@ -29,4 +29,10 @@ def launch_desktop( if args: _args += args - return self.launch(args=_args, communicate=True, action_name="launch_desktop", environment=environment, cwd=cwd) + return self.launch( + args=_args, + communicate=True, + action_name="launch_desktop", + environment=environment, + cwd=cwd, + ) diff --git a/bottles/backend/wine/hh.py b/bottles/backend/wine/hh.py index 8f438ab17ee..d294be0d731 100644 --- a/bottles/backend/wine/hh.py +++ b/bottles/backend/wine/hh.py @@ -1,4 +1,3 @@ - from bottles.backend.logger import Logger from bottles.backend.wine.wineprogram import WineProgram diff --git a/bottles/backend/wine/icinfo.py b/bottles/backend/wine/icinfo.py index 5d8e0ca51bf..9cc1fd3a813 100644 --- a/bottles/backend/wine/icinfo.py +++ b/bottles/backend/wine/icinfo.py @@ -1,4 +1,3 @@ - from bottles.backend.logger import Logger from bottles.backend.wine.wineprogram import WineProgram @@ -17,23 +16,20 @@ def get_dict(self): if not res.ready: return {} - res = [r.strip() for r in res.split('\n')[1:]] + res = [r.strip() for r in res.split("\n")[1:]] _res = {} _latest = None for r in res: if not r: continue - k, v = r.split(':') - if r.startswith('vidc.'): + k, v = r.split(":") + if r.startswith("vidc."): _latest = k _res[k] = {} - _res[k]['name'] = k - _res[k]['description'] = v + _res[k]["name"] = k + _res[k]["description"] = v else: _res[_latest][k] = v return _res - - - \ No newline at end of file diff --git a/bottles/backend/wine/msiexec.py b/bottles/backend/wine/msiexec.py index bc94b229c8c..3edfa8d0c1e 100644 --- a/bottles/backend/wine/msiexec.py +++ b/bottles/backend/wine/msiexec.py @@ -11,12 +11,12 @@ class MsiExec(WineProgram): command = "msiexec" def install( - self, - pkg_path: str, # or product code - args: str = "", - terminal: bool = False, - cwd: Optional[str] = None, - environment: Optional[dict] = None + self, + pkg_path: str, # or product code + args: str = "", + terminal: bool = False, + cwd: Optional[str] = None, + environment: Optional[dict] = None, ): args = f"/i {pkg_path} {args}" @@ -27,23 +27,23 @@ def install( environment=environment, terminal=terminal, cwd=cwd, - action_name="install" + action_name="install", ) def repair( - self, - pkg_path: str, - if_missing: bool = False, - if_missing_or_outdated: bool = False, - if_missing_or_outdated_or_same: bool = False, - if_missing_or_different: bool = False, - if_missing_or_hash_fail: bool = False, - force_all: bool = False, - all_user_registry_keys: bool = False, - all_computer_registry_keys: bool = False, - all_shortcuts: bool = False, - recache: bool = False, - cwd: Optional[str] = None + self, + pkg_path: str, + if_missing: bool = False, + if_missing_or_outdated: bool = False, + if_missing_or_outdated_or_same: bool = False, + if_missing_or_different: bool = False, + if_missing_or_hash_fail: bool = False, + force_all: bool = False, + all_user_registry_keys: bool = False, + all_computer_registry_keys: bool = False, + all_shortcuts: bool = False, + recache: bool = False, + cwd: Optional[str] = None, ): """ NOTICE: I have not been able to use the repair in any way, it seems to show @@ -75,36 +75,55 @@ def repair( args += f" {pkg_path}" self.launch( - args=args, - communicate=True, - minimal=True, - cwd=cwd, - action_name="repair" + args=args, communicate=True, minimal=True, cwd=cwd, action_name="repair" ) - def uninstall(self, pkg_path: str, cwd: Optional[str] = None): + def uninstall(self, pkg_path: str, cwd: Optional[str] = None): args = f"/x {pkg_path}" - self.launch(args=args, communicate=True, minimal=True, cwd=cwd, action_name="uninstall") + self.launch( + args=args, communicate=True, minimal=True, cwd=cwd, action_name="uninstall" + ) def apply_patch(self, patch: str, update: bool = False, cwd: Optional[str] = None): args = f"/p {patch}" if update: args = f" /update {patch}" - self.launch(args=args, communicate=True, minimal=True, cwd=cwd, action_name="apply_path") + self.launch( + args=args, communicate=True, minimal=True, cwd=cwd, action_name="apply_path" + ) - def uninstall_patch(self, patch: str, product: Optional[str] = None, cwd: Optional[str] = None): + def uninstall_patch( + self, patch: str, product: Optional[str] = None, cwd: Optional[str] = None + ): args = f"/uninstall {patch}" if product: args += f" /package {product}" - self.launch(args=args, communicate=True, minimal=True, cwd=cwd, action_name="uninstall_patch") + self.launch( + args=args, + communicate=True, + minimal=True, + cwd=cwd, + action_name="uninstall_patch", + ) def register_module(self, module: str, cwd: Optional[str] = None): args = f"/y {module}" - self.launch(args=args, communicate=True, minimal=True, cwd=cwd, action_name="register_module") + self.launch( + args=args, + communicate=True, + minimal=True, + cwd=cwd, + action_name="register_module", + ) def unregister_module(self, module: str, cwd: Optional[str] = None): args = f"/z {module}" - self.launch(args=args, communicate=True, minimal=True, cwd=cwd, action_name="unregister_module") - + self.launch( + args=args, + communicate=True, + minimal=True, + cwd=cwd, + action_name="unregister_module", + ) diff --git a/bottles/backend/wine/notepad.py b/bottles/backend/wine/notepad.py index fb86910fe3c..e70b6406442 100644 --- a/bottles/backend/wine/notepad.py +++ b/bottles/backend/wine/notepad.py @@ -23,4 +23,3 @@ def print(self, path: str, printer_name: Optional[str] = None): if printer_name: args = f"/pt {path} {printer_name}" return self.launch(args=args, communicate=True, action_name="print") - diff --git a/bottles/backend/wine/oleview.py b/bottles/backend/wine/oleview.py index 45d0cd9e20e..93da9a76b55 100644 --- a/bottles/backend/wine/oleview.py +++ b/bottles/backend/wine/oleview.py @@ -1,4 +1,3 @@ - from bottles.backend.logger import Logger from bottles.backend.wine.wineprogram import WineProgram @@ -8,4 +7,3 @@ class Oleview(WineProgram): program = "OLE/COM object viewer" command = "oleview" - diff --git a/bottles/backend/wine/progman.py b/bottles/backend/wine/progman.py index 9c8f085fdc9..8996a0e3016 100644 --- a/bottles/backend/wine/progman.py +++ b/bottles/backend/wine/progman.py @@ -1,4 +1,3 @@ - from bottles.backend.logger import Logger from bottles.backend.wine.wineprogram import WineProgram @@ -8,4 +7,3 @@ class Progman(WineProgram): program = "Wine Program Manager" command = "progman" - diff --git a/bottles/backend/wine/reg.py b/bottles/backend/wine/reg.py index d6c6f70c3ac..f7737edb679 100644 --- a/bottles/backend/wine/reg.py +++ b/bottles/backend/wine/reg.py @@ -34,7 +34,9 @@ def bulk_add(self, regs: List[RegItem]): logging.info(f"Importing {len(regs)} Key(s) to {config.Name} registry") winedbg = WineDbg(config) - mapping: Dict[str, List[RegItem]] = {k: list(v) for k, v in groupby(regs, lambda x: x.key)} + mapping: Dict[str, List[RegItem]] = { + k: list(v) for k, v in groupby(regs, lambda x: x.key) + } reg_file_header = "Windows Registry Editor Version 5.00\n\n" reg_key_header = "[%s]\n" reg_item_fmt = '"%s"="%s:%s"\n' @@ -45,33 +47,44 @@ def bulk_add(self, regs: List[RegItem]): file_content += reg_key_header % key for item in items: if item.value_type: - file_content += reg_item_fmt % (item.value, item.value_type, item.data) + file_content += reg_item_fmt % ( + item.value, + item.value_type, + item.data, + ) else: file_content += reg_item_def_fmt % (item.value, item.data) file_content += "\n" - tmp_reg_filepath = os.path.join(Paths.temp, f"bulk_{int(datetime.now().timestamp())}_{random_string(8)}.reg") - with open(tmp_reg_filepath, 'wb') as f: + tmp_reg_filepath = os.path.join( + Paths.temp, f"bulk_{int(datetime.now().timestamp())}_{random_string(8)}.reg" + ) + with open(tmp_reg_filepath, "wb") as f: f.write(codecs.BOM_UTF16_LE) - f.write(file_content.encode('utf-16le')) + f.write(file_content.encode("utf-16le")) # avoid conflicts when executing async winedbg.wait_for_process("reg.exe") - res = self.launch(("import", tmp_reg_filepath), communicate=True, minimal=True, action_name="bulk_add") + res = self.launch( + ("import", tmp_reg_filepath), + communicate=True, + minimal=True, + action_name="bulk_add", + ) logging.info(res.data) def add(self, key: str, value: str, data: str, value_type: Optional[str] = None): config = self.config - logging.info(f"Adding Key: [{key}] with Value: [{value}] and " - f"Data: [{data}] in {config.Name} registry") + logging.info( + f"Adding Key: [{key}] with Value: [{value}] and " + f"Data: [{data}] in {config.Name} registry" + ) winedbg = WineDbg(config) args = "add '%s' /v '%s' /d '%s' /f" % (key, value, data) if value_type is not None: - args = "add '%s' /v '%s' /t %s /d '%s' /f" % ( - key, value, value_type, data - ) + args = "add '%s' /v '%s' /t %s /d '%s' /f" % (key, value, value_type, data) # avoid conflicts when executing async winedbg.wait_for_process("reg.exe") @@ -82,8 +95,9 @@ def add(self, key: str, value: str, data: str, value_type: Optional[str] = None) def remove(self, key: str, value: str): """Remove a key from the registry""" config = self.config - logging.info(f"Removing Value: [{key}] from Key: [{value}] in " - f"{config.Name} registry") + logging.info( + f"Removing Value: [{key}] from Key: [{value}] in " f"{config.Name} registry" + ) winedbg = WineDbg(config) args = "delete '%s' /v %s /f" % (key, value) @@ -111,7 +125,9 @@ def import_bundle(self, bundle: dict): if value["data"] == "-": f.write(f'"{value["value"]}"=-\n') elif "key_type" in value: - f.write(f'"{value["value"]}"={value["key_type"]}:{value["data"]}\n') + f.write( + f'"{value["value"]}"={value["key_type"]}:{value["data"]}\n' + ) else: f.write(f'"{value["value"]}"="{value["data"]}"\n') @@ -122,7 +138,9 @@ def import_bundle(self, bundle: dict): # avoid conflicts when executing async winedbg.wait_for_process("reg.exe") - res = self.launch(args, communicate=True, minimal=True, action_name="import_bundle") + res = self.launch( + args, communicate=True, minimal=True, action_name="import_bundle" + ) logging.info(f"Import bundle result: '{res.data}'") # remove reg file diff --git a/bottles/backend/wine/regedit.py b/bottles/backend/wine/regedit.py index 9735bf9fbb3..25ffa665339 100644 --- a/bottles/backend/wine/regedit.py +++ b/bottles/backend/wine/regedit.py @@ -1,4 +1,3 @@ - from bottles.backend.logger import Logger from bottles.backend.wine.wineprogram import WineProgram diff --git a/bottles/backend/wine/register.py b/bottles/backend/wine/register.py index f7e1d5515f2..c4b1ede3cc9 100644 --- a/bottles/backend/wine/register.py +++ b/bottles/backend/wine/register.py @@ -63,24 +63,24 @@ def __parse_dict(path: str): for reg in regs: if cur_line <= 2: - ''' + """ Skip the first 4 lines which are the register header. - ''' + """ cur_line += 1 continue for line in reg.split("\n"): - ''' + """ Following checks will check the line format, when one check succeed, continue to the next line. - ''' + """ if line.startswith("["): - ''' + """ Check if line format corresponds to a key, if true, create a new key in the dictionary. - ''' + """ key = line.strip("[]") if any(key.startswith(ex) for ex in exclude): key = None @@ -89,15 +89,15 @@ def __parse_dict(path: str): _dict[key] = {} continue elif line not in ["", "\n"]: - ''' + """ Check if line format corresponds to a value, if true get key and value and append to last key. - ''' + """ if key is None: continue _key = line.split("=")[0] - _value = line[len(_key) + 1:] + _value = line[len(_key) + 1 :] _dict[key][_key] = _value continue @@ -114,7 +114,7 @@ def compare(self, path: Optional[str] = None, register: object = None): self.diff = diff return diff - def __get_diff(self, register: 'WinRegister'): + def __get_diff(self, register: "WinRegister"): """Return the difference between the current register and the given one.""" diff = {} other_reg = register.reg_dict @@ -146,9 +146,9 @@ def update(self, diff: Optional[dict] = None): self.reg_dict[key] = diff[key] if os.path.exists(self.path): - ''' + """ Make a backup before overwriting the register. - ''' + """ os.rename(self.path, f"{self.path}.{uuid.uuid4()}.bak") with open(self.path, "w") as reg: diff --git a/bottles/backend/wine/regkeys.py b/bottles/backend/wine/regkeys.py index f815ffcf8ac..740f64215c4 100644 --- a/bottles/backend/wine/regkeys.py +++ b/bottles/backend/wine/regkeys.py @@ -1,4 +1,3 @@ - from bottles.backend.logger import Logger from bottles.backend.models.config import BottleConfig from bottles.backend.models.enum import Arch @@ -43,15 +42,18 @@ def set_windows(self, version: str): wineboot = WineBoot(self.config) del_keys = { "HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows\\CurrentVersion": [ - "SubVersionNumber", "VersionNumber" + "SubVersionNumber", + "VersionNumber", ], "HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows NT\\CurrentVersion": [ - "CSDVersion", "CurrentBuildNumber", "CurrentVersion" + "CSDVersion", + "CurrentBuildNumber", + "CurrentVersion", ], "HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Control\\ProductOptions": "ProductType", "HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Control\\ServiceCurrent": "OS", "HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Control\\Windows": "CSDVersion", - "HKEY_CURRENT_USER\\Software\\Wine": "Version" + "HKEY_CURRENT_USER\\Software\\Wine": "Version", } for d in del_keys: _val = del_keys.get(d) @@ -64,105 +66,74 @@ def set_windows(self, version: str): if version not in ["win98", "win95"]: bundle = { "HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows NT\\CurrentVersion": [ - { - "value": "CSDVersion", - "data": win_version["CSDVersion"] - }, - { - "value": "CurrentBuild", - "data": win_version["CurrentBuild"] - }, + {"value": "CSDVersion", "data": win_version["CSDVersion"]}, + {"value": "CurrentBuild", "data": win_version["CurrentBuild"]}, { "value": "CurrentBuildNumber", - "data": win_version["CurrentBuildNumber"] - }, - { - "value": "CurrentVersion", - "data": win_version["CurrentVersion"] - }, - { - "value": "ProductName", - "data": win_version["ProductName"] + "data": win_version["CurrentBuildNumber"], }, + {"value": "CurrentVersion", "data": win_version["CurrentVersion"]}, + {"value": "ProductName", "data": win_version["ProductName"]}, { "value": "CurrentMinorVersionNumber", "data": win_version["CurrentMinorVersionNumber"], - "key_type": "dword" + "key_type": "dword", }, { "value": "CurrentMajorVersionNumber", "data": win_version["CurrentMajorVersionNumber"], - "key_type": "dword" + "key_type": "dword", }, ], "HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Control\\Windows": [ { "value": "CSDVersion", "data": win_version["CSDVersionHex"], - "key_type": "dword" + "key_type": "dword", } - ] + ], } else: bundle = { "HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows\\CurrentVersion": [ - { - "value": "ProductName", - "data": win_version["ProductName"] - }, + {"value": "ProductName", "data": win_version["ProductName"]}, { "value": "SubVersionNumber", - "data": win_version["SubVersionNumber"] + "data": win_version["SubVersionNumber"], }, - { - "value": "VersionNumber", - "data": win_version["VersionNumber"] - } + {"value": "VersionNumber", "data": win_version["VersionNumber"]}, ] } if self.config.Arch == Arch.WIN64: - bundle["HKEY_LOCAL_MACHINE\\Software\\Wow6432Node\\Microsoft\\Windows NT\\CurrentVersion"] = [ - { - "value": "CSDVersion", - "data": win_version["CSDVersion"] - }, - { - "value": "CurrentBuild", - "data": win_version["CurrentBuild"] - }, + bundle[ + "HKEY_LOCAL_MACHINE\\Software\\Wow6432Node\\Microsoft\\Windows NT\\CurrentVersion" + ] = [ + {"value": "CSDVersion", "data": win_version["CSDVersion"]}, + {"value": "CurrentBuild", "data": win_version["CurrentBuild"]}, { "value": "CurrentBuildNumber", - "data": win_version["CurrentBuildNumber"] - }, - { - "value": "CurrentVersion", - "data": win_version["CurrentVersion"] - }, - { - "value": "ProductName", - "data": win_version["ProductName"] + "data": win_version["CurrentBuildNumber"], }, + {"value": "CurrentVersion", "data": win_version["CurrentVersion"]}, + {"value": "ProductName", "data": win_version["ProductName"]}, { "value": "CurrentMinorVersionNumber", "data": win_version["CurrentMinorVersionNumber"], - "key_type": "dword" + "key_type": "dword", }, { "value": "CurrentMajorVersionNumber", "data": win_version["CurrentMajorVersionNumber"], - "key_type": "dword" - } + "key_type": "dword", + }, ] if "ProductType" in win_version: - '''windows xp 32 doesn't have ProductOptions/ProductType key''' - bundle["HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Control\\ProductOptions"] = [ - { - "value": "ProductType", - "data": win_version["ProductType"] - } - ] + """windows xp 32 doesn't have ProductOptions/ProductType key""" + bundle[ + "HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Control\\ProductOptions" + ] = [{"value": "ProductType", "data": win_version["ProductType"]}] self.reg.import_bundle(bundle) @@ -183,7 +154,7 @@ def set_app_default(self, version: str, executable: str): self.reg.add( key=f"HKEY_CURRENT_USER\\Software\\Wine\\AppDefaults\\{executable}", value="Version", - data=version + data=version, ) def toggle_virtual_desktop(self, state: bool, resolution: str = "800x600"): @@ -197,17 +168,16 @@ def toggle_virtual_desktop(self, state: bool, resolution: str = "800x600"): self.reg.add( key="HKEY_CURRENT_USER\\Software\\Wine\\Explorer", value="Desktop", - data="Default" + data="Default", ) self.reg.add( key="HKEY_CURRENT_USER\\Software\\Wine\\Explorer\\Desktops", value="Default", - data=resolution + data=resolution, ) else: self.reg.remove( - key="HKEY_CURRENT_USER\\Software\\Wine\\Explorer", - value="Desktop" + key="HKEY_CURRENT_USER\\Software\\Wine\\Explorer", value="Desktop" ) wineboot.update() @@ -220,28 +190,29 @@ def apply_cmd_settings(self, scheme=None): """ if scheme is None: scheme = {} - self.reg.import_bundle({ - "HKEY_CURRENT_USER\\Console\\C:_windows_system32_wineconsole.exe": [ - {"value": "ColorTable00", "data": "2368548"}, - {"value": "CursorSize", "data": "25"}, - {"value": "CursorVisible", "data": "1"}, - {"value": "EditionMode", "data": "0"}, - {"value": "FaceName", "data": "Monospace", "key_type": "dword"}, - {"value": "FontPitchFamily", "data": "1"}, - {"value": "FontSize", "data": "1248584"}, - {"value": "FontWeight", "data": "400"}, - {"value": "HistoryBufferSize", "data": "50"}, - {"value": "HistoryNoDup", "data": "0"}, - {"value": "InsertMode", "data": "1"}, - {"value": "MenuMask", "data": "0"}, - {"value": "PopupColors", "data": "245"}, - {"value": "QuickEdit", "data": "1"}, - {"value": "ScreenBufferSize", "data": "9830480"}, - {"value": "ScreenColors", "data": "11"}, - {"value": "WindowSize", "data": "1638480" - } - ] - }) + self.reg.import_bundle( + { + "HKEY_CURRENT_USER\\Console\\C:_windows_system32_wineconsole.exe": [ + {"value": "ColorTable00", "data": "2368548"}, + {"value": "CursorSize", "data": "25"}, + {"value": "CursorVisible", "data": "1"}, + {"value": "EditionMode", "data": "0"}, + {"value": "FaceName", "data": "Monospace", "key_type": "dword"}, + {"value": "FontPitchFamily", "data": "1"}, + {"value": "FontSize", "data": "1248584"}, + {"value": "FontWeight", "data": "400"}, + {"value": "HistoryBufferSize", "data": "50"}, + {"value": "HistoryNoDup", "data": "0"}, + {"value": "InsertMode", "data": "1"}, + {"value": "MenuMask", "data": "0"}, + {"value": "PopupColors", "data": "245"}, + {"value": "QuickEdit", "data": "1"}, + {"value": "ScreenBufferSize", "data": "9830480"}, + {"value": "ScreenColors", "data": "11"}, + {"value": "WindowSize", "data": "1638480"}, + ] + } + ) def set_renderer(self, value: str): """ @@ -254,7 +225,7 @@ def set_renderer(self, value: str): key="HKEY_CURRENT_USER\\Software\\Wine\\Direct3D", value="renderer", data=value, - value_type="REG_SZ" + value_type="REG_SZ", ) def set_dpi(self, value: int): @@ -265,7 +236,7 @@ def set_dpi(self, value: int): key="HKEY_CURRENT_USER\\Control Panel\\Desktop", value="LogPixels", data=str(value), - value_type="REG_DWORD" + value_type="REG_DWORD", ) def set_grab_fullscreen(self, state: bool): @@ -276,7 +247,7 @@ def set_grab_fullscreen(self, state: bool): self.reg.add( key="HKEY_CURRENT_USER\\Software\\Wine\\X11 Driver", value="GrabFullscreen", - data=value + data=value, ) def set_take_focus(self, state: bool): @@ -287,7 +258,7 @@ def set_take_focus(self, state: bool): self.reg.add( key="HKEY_CURRENT_USER\\Software\\Wine\\X11 Driver", value="UseTakeFocus", - data=value + data=value, ) def set_decorated(self, state: bool): @@ -298,7 +269,7 @@ def set_decorated(self, state: bool): self.reg.add( key="HKEY_CURRENT_USER\\Software\\Wine\\X11 Driver", value="Decorated", - data=value + data=value, ) def set_mouse_warp(self, state: int, executable: str = ""): @@ -309,11 +280,7 @@ def set_mouse_warp(self, state: int, executable: str = ""): 1: Enabled 2: Forced """ - values = { - 0: "disable", - 1: "enable", - 2: "force" - } + values = {0: "disable", 1: "enable", 2: "force"} if state not in values.keys(): raise ValueError(f"{state} is not a valid mouse warp setting (0, 1, 2)") @@ -321,8 +288,4 @@ def set_mouse_warp(self, state: int, executable: str = ""): if executable: key = f"HKEY_CURRENT_USER\\Software\\Wine\\AppDefaults\\{executable}\\DirectInput" - self.reg.add( - key=key, - value="MouseWarpOverride", - data=values[state] - ) + self.reg.add(key=key, value="MouseWarpOverride", data=values[state]) diff --git a/bottles/backend/wine/regsvr32.py b/bottles/backend/wine/regsvr32.py index d118c0f1307..420c829865d 100644 --- a/bottles/backend/wine/regsvr32.py +++ b/bottles/backend/wine/regsvr32.py @@ -1,4 +1,3 @@ - from bottles.backend.logger import Logger from bottles.backend.wine.wineprogram import WineProgram diff --git a/bottles/backend/wine/rundll32.py b/bottles/backend/wine/rundll32.py index b68a0bc32bc..c4ed2f9de3e 100644 --- a/bottles/backend/wine/rundll32.py +++ b/bottles/backend/wine/rundll32.py @@ -1,4 +1,3 @@ - from bottles.backend.logger import Logger from bottles.backend.wine.wineprogram import WineProgram diff --git a/bottles/backend/wine/start.py b/bottles/backend/wine/start.py index a5e4b68910e..a2c272c2e41 100644 --- a/bottles/backend/wine/start.py +++ b/bottles/backend/wine/start.py @@ -12,12 +12,12 @@ class Start(WineProgram): command = "start" def run( - self, - file: str, - terminal: bool = True, - args: str = "", - environment: Optional[dict] = None, - cwd: Optional[str] = None + self, + file: str, + terminal: bool = True, + args: str = "", + environment: Optional[dict] = None, + cwd: Optional[str] = None, ): winepath = WinePath(self.config) @@ -39,5 +39,5 @@ def run( environment=environment, cwd=cwd, minimal=False, - action_name="run" + action_name="run", ) diff --git a/bottles/backend/wine/taskmgr.py b/bottles/backend/wine/taskmgr.py index e18ecde778b..7f27f415f76 100644 --- a/bottles/backend/wine/taskmgr.py +++ b/bottles/backend/wine/taskmgr.py @@ -1,4 +1,3 @@ - from bottles.backend.logger import Logger from bottles.backend.wine.wineprogram import WineProgram diff --git a/bottles/backend/wine/uninstaller.py b/bottles/backend/wine/uninstaller.py index 84d00e08e6d..5821251afb7 100644 --- a/bottles/backend/wine/uninstaller.py +++ b/bottles/backend/wine/uninstaller.py @@ -29,11 +29,11 @@ def from_uuid(self, uuid: Optional[str] = None): def from_name(self, name: str): res = self.get_uuid(name) if not res.ready: - ''' + """ No UUID found, at this point it is safe to assume that the program is not installed ref: - ''' + """ return uuid = res.data.strip() for _uuid in uuid.splitlines(): diff --git a/bottles/backend/wine/wineboot.py b/bottles/backend/wine/wineboot.py index 9e5dfe6b865..65e90d8a560 100644 --- a/bottles/backend/wine/wineboot.py +++ b/bottles/backend/wine/wineboot.py @@ -1,4 +1,3 @@ - from bottles.backend.logger import Logger from bottles.backend.wine.wineprogram import WineProgram from bottles.backend.wine.wineserver import WineServer @@ -11,15 +10,12 @@ class WineBoot(WineProgram): command = "wineboot" def send_status(self, status: int): - states = { - -1: "force", - 0: "-k", - 1: "-r", - 2: "-s", - 3: "-u", - 4: "-i" + states = {-1: "force", 0: "-k", 1: "-r", 2: "-s", 3: "-u", 4: "-i"} + envs = { + "WINEDEBUG": "-all", + "DISPLAY": ":3.0", + "WINEDLLOVERRIDES": "winemenubuilder=d", } - envs = {"WINEDEBUG": "-all", "DISPLAY": ":3.0", "WINEDLLOVERRIDES": "winemenubuilder=d"} if status == 0 and not WineServer(self.config).is_alive(): logging.info("There is no running wineserver.") @@ -31,7 +27,7 @@ def send_status(self, status: int): args=args, environment=envs, communicate=True, - action_name=f"send_status({states[status]})" + action_name=f"send_status({states[status]})", ) else: raise ValueError(f"[{status}] is not a valid status for wineboot!") @@ -41,7 +37,7 @@ def force(self): def kill(self, force_if_stalled: bool = False): self.send_status(0) - + if force_if_stalled: wineserver = WineServer(self.config) if wineserver.is_alive(): diff --git a/bottles/backend/wine/winebridge.py b/bottles/backend/wine/winebridge.py index d8c28363a87..8bd1351c4d3 100644 --- a/bottles/backend/wine/winebridge.py +++ b/bottles/backend/wine/winebridge.py @@ -29,11 +29,7 @@ def get_procs(self): if not self.__wineserver_status: return processes - res = self.launch( - args=args, - communicate=True, - action_name="get_procs" - ) + res = self.launch(args=args, communicate=True, action_name="get_procs") if not res.ready: return processes @@ -47,35 +43,25 @@ def get_procs(self): if len(r) < 3: continue - processes.append({ - "pid": r[1], - "threads": r[2], - "name": r[0], - # "parent": r[3] - }) + processes.append( + { + "pid": r[1], + "threads": r[2], + "name": r[0], + # "parent": r[3] + } + ) return processes def kill_proc(self, pid: str): args = f"killProc {pid}" - return self.launch( - args=args, - communicate=True, - action_name="kill_proc" - ) + return self.launch(args=args, communicate=True, action_name="kill_proc") def kill_proc_by_name(self, name: str): args = f"killProcByName {name}" - return self.launch( - args=args, - communicate=True, - action_name="kill_proc_by_name" - ) + return self.launch(args=args, communicate=True, action_name="kill_proc_by_name") def run_exe(self, exec_path: str): args = f"runExe {exec_path}" - return self.launch( - args=args, - communicate=True, - action_name="run_exe" - ) + return self.launch(args=args, communicate=True, action_name="run_exe") diff --git a/bottles/backend/wine/winecfg.py b/bottles/backend/wine/winecfg.py index d961e2d5dc8..1644992dae5 100644 --- a/bottles/backend/wine/winecfg.py +++ b/bottles/backend/wine/winecfg.py @@ -1,4 +1,3 @@ - from bottles.backend.logger import Logger from bottles.backend.wine.wineprogram import WineProgram diff --git a/bottles/backend/wine/winecommand.py b/bottles/backend/wine/winecommand.py index bdad95d628f..3bce1899e2b 100644 --- a/bottles/backend/wine/winecommand.py +++ b/bottles/backend/wine/winecommand.py @@ -3,10 +3,17 @@ import stat import subprocess import tempfile +import shlex from typing import Optional -from bottles.backend.globals import Paths, gamemode_available, gamescope_available, mangohud_available, \ - obs_vkc_available, vmtouch_available +from bottles.backend.globals import ( + Paths, + gamemode_available, + gamescope_available, + mangohud_available, + obs_vkc_available, + vmtouch_available, +) from bottles.backend.logger import Logger from bottles.backend.managers.runtime import RuntimeManager from bottles.backend.managers.sandbox import SandboxManager @@ -26,11 +33,9 @@ class WineEnv: """ This class is used to store and return a command environment. """ + __env: dict = {} - __result: dict = { - "envs": {}, - "overrides": [] - } + __result: dict = {"envs": {}, "overrides": []} def __init__(self, clean: bool = False): self.__env = {} @@ -83,26 +88,27 @@ class WineCommand: """ def __init__( - self, - config: BottleConfig, - command: str, - terminal: bool = False, - arguments: str = False, - environment: dict = False, - communicate: bool = False, - cwd: Optional[str] = None, - colors: str = "default", - minimal: bool = False, # avoid gamemode/gamescope usage - post_script: Optional[str] = None + self, + config: BottleConfig, + command: str, + terminal: bool = False, + arguments: str = False, + environment: dict = {}, + communicate: bool = False, + cwd: Optional[str] = None, + colors: str = "default", + minimal: bool = False, # avoid gamemode/gamescope usage + post_script: Optional[str] = None, ): + _environment = environment.copy() self.config = self._get_config(config) self.minimal = minimal self.arguments = arguments self.cwd = self._get_cwd(cwd) self.runner, self.runner_runtime = self._get_runner_info() - self.command = self.get_cmd(command, post_script) + self.command = self.get_cmd(command, post_script, environment=_environment) self.terminal = terminal - self.env = self.get_env(environment) + self.env = self.get_env(_environment) self.communicate = communicate self.colors = colors self.vmtouch_files = None @@ -126,21 +132,26 @@ def _get_cwd(self, cwd) -> str: bottle = ManagerUtils.get_bottle_path(config) if not cwd: - ''' + """ If no cwd is given, use the WorkingDir from the bottle configuration. - ''' + """ cwd = config.WorkingDir if cwd == "" or not os.path.exists(cwd): - ''' + """ If the WorkingDir is empty, use the bottle path as working directory. - ''' + """ cwd = bottle return cwd - def get_env(self, environment: Optional[dict] = None, return_steam_env: bool = False, return_clean_env: bool = False) -> dict: + def get_env( + self, + environment: Optional[dict] = None, + return_steam_env: bool = False, + return_clean_env: bool = False, + ) -> dict: env = WineEnv(clean=return_steam_env or return_clean_env) config = self.config arch = config.Arch @@ -175,8 +186,6 @@ def get_env(self, environment: Optional[dict] = None, return_steam_env: bool = F if config.Environment_Variables: for key, value in config.Environment_Variables.items(): env.add(key, value, override=True) - if (key == "WINEDLLOVERRIDES") and value: - dll_overrides.extend(value.split(";")) # Environment variables from argument if environment: @@ -204,8 +213,11 @@ def get_env(self, environment: Optional[dict] = None, return_steam_env: bool = F dll_overrides.append("winemenubuilder=''") # Get Runtime libraries - if (params.use_runtime or params.use_eac_runtime or params.use_be_runtime) \ - and not self.terminal and not return_steam_env: + if ( + (params.use_runtime or params.use_eac_runtime or params.use_be_runtime) + and not self.terminal + and not return_steam_env + ): _rb = RuntimeManager.get_runtime_env("bottles") if _rb: _eac = RuntimeManager.get_eac() @@ -215,12 +227,16 @@ def get_env(self, environment: Optional[dict] = None, return_steam_env: bool = F logging.info("Using Bottles runtime") ld += _rb - if _eac and not self.minimal: # NOTE: should check for runner compatibility with "eac" (?) + if ( + _eac and not self.minimal + ): # NOTE: should check for runner compatibility with "eac" (?) logging.info("Using EasyAntiCheat runtime") env.add("PROTON_EAC_RUNTIME", _eac) dll_overrides.append("easyanticheat_x86,easyanticheat_x64=b,n") - if _be and not self.minimal: # NOTE: should check for runner compatibility with "be" (?) + if ( + _be and not self.minimal + ): # NOTE: should check for runner compatibility with "be" (?) logging.info("Using BattlEye runtime") env.add("PROTON_BATTLEYE_RUNTIME", _be) dll_overrides.append("beclient,beclient_x64=b,n") @@ -237,24 +253,21 @@ def get_env(self, environment: Optional[dict] = None, return_steam_env: bool = F "lib64/wine/x86_64-unix", "lib/wine/i386-unix", "lib32/wine/i386-unix", - "lib64/wine/i386-unix" + "lib64/wine/i386-unix", ] gst_libs = [ "lib64/gstreamer-1.0", "lib/gstreamer-1.0", - "lib32/gstreamer-1.0" + "lib32/gstreamer-1.0", ] else: runner_libs = [ "lib", "lib/wine/i386-unix", "lib32/wine/i386-unix", - "lib64/wine/i386-unix" - ] - gst_libs = [ - "lib/gstreamer-1.0", - "lib32/gstreamer-1.0" + "lib64/wine/i386-unix", ] + gst_libs = ["lib/gstreamer-1.0", "lib32/gstreamer-1.0"] for lib in runner_libs: _path = os.path.join(runner_path, lib) @@ -262,7 +275,7 @@ def get_env(self, environment: Optional[dict] = None, return_steam_env: bool = F ld.append(_path) # Embedded GStreamer environment variables - if not env.has('BOTTLES_USE_SYSTEM_GSTREAMER') and not return_steam_env: + if not env.has("BOTTLES_USE_SYSTEM_GSTREAMER") and not return_steam_env: gst_env_path = [] for lib in gst_libs: if os.path.exists(os.path.join(runner_path, lib)): @@ -273,21 +286,34 @@ def get_env(self, environment: Optional[dict] = None, return_steam_env: bool = F # DXVK environment variables if params.dxvk and not return_steam_env: env.add("WINE_LARGE_ADDRESS_AWARE", "1") - env.add("DXVK_STATE_CACHE_PATH", os.path.join(bottle, "cache", "dxvk_state")) + env.add( + "DXVK_STATE_CACHE_PATH", os.path.join(bottle, "cache", "dxvk_state") + ) env.add("STAGING_SHARED_MEMORY", "1") env.add("__GL_SHADER_DISK_CACHE", "1") - env.add("__GL_SHADER_DISK_CACHE_SKIP_CLEANUP", "1") # should not be needed anymore - env.add("__GL_SHADER_DISK_CACHE_PATH", os.path.join(bottle, "cache", "gl_shader")) - env.add("MESA_SHADER_CACHE_DIR", os.path.join(bottle, "cache", "mesa_shader")) + env.add( + "__GL_SHADER_DISK_CACHE_SKIP_CLEANUP", "1" + ) # should not be needed anymore + env.add( + "__GL_SHADER_DISK_CACHE_PATH", + os.path.join(bottle, "cache", "gl_shader"), + ) + env.add( + "MESA_SHADER_CACHE_DIR", os.path.join(bottle, "cache", "mesa_shader") + ) # VKD3D environment variables if params.vkd3d and not return_steam_env: - env.add("VKD3D_SHADER_CACHE_PATH", os.path.join(bottle, "cache", "vkd3d_shader")) + env.add( + "VKD3D_SHADER_CACHE_PATH", os.path.join(bottle, "cache", "vkd3d_shader") + ) # LatencyFleX environment variables if params.latencyflex and not return_steam_env: _lf_path = ManagerUtils.get_latencyflex_path(config.LatencyFleX) - _lf_layer_path = os.path.join(_lf_path, "layer/usr/share/vulkan/implicit_layer.d") + _lf_layer_path = os.path.join( + _lf_path, "layer/usr/share/vulkan/implicit_layer.d" + ) env.concat("VK_ADD_LAYER_PATH", _lf_layer_path) env.add("LFX", "1") ld.append(os.path.join(_lf_path, "layer/usr/lib/x86_64-linux-gnu")) @@ -295,13 +321,19 @@ def get_env(self, environment: Optional[dict] = None, return_steam_env: bool = F env.add("DISABLE_LFX", "1") # Mangohud environment variables - if params.mangohud and not self.minimal and not (gamescope_available and params.gamescope): + if ( + params.mangohud + and not self.minimal + and not (gamescope_available and params.gamescope) + ): env.add("MANGOHUD", "1") env.add("MANGOHUD_DLSYM", "1") # vkBasalt environment variables if params.vkbasalt and not self.minimal: - vkbasalt_conf_path = os.path.join(ManagerUtils.get_bottle_path(config), "vkBasalt.conf") + vkbasalt_conf_path = os.path.join( + ManagerUtils.get_bottle_path(config), "vkBasalt.conf" + ) if os.path.isfile(vkbasalt_conf_path): env.add("VKBASALT_CONFIG_FILE", vkbasalt_conf_path) env.add("ENABLE_VKBASALT", "1") @@ -314,8 +346,6 @@ def get_env(self, environment: Optional[dict] = None, return_steam_env: bool = F # DXVK-Nvapi environment variables if params.dxvk_nvapi and not return_steam_env: - conf = self.__set_dxvk_nvapi_conf(bottle) - env.add("DXVK_CONFIG_FILE", conf) # NOTE: users reported that DXVK_ENABLE_NVAPI and DXVK_NVAPIHACK must be set to make # DLSS works. I don't have a GPU compatible with this tech, so I'll trust them env.add("DXVK_NVAPIHACK", "0") @@ -364,21 +394,23 @@ def get_env(self, environment: Optional[dict] = None, return_steam_env: bool = F # VK_ICD if not env.has("VK_ICD_FILENAMES"): if gpu["prime"]["integrated"] is not None: - ''' + """ System support PRIME but user disabled the discrete GPU setting (previus check skipped), so using the integrated one. - ''' + """ env.concat("VK_ICD_FILENAMES", gpu["prime"]["integrated"]["icd"]) else: - ''' + """ System doesn't support PRIME, so using the first result from the gpu vendors list. - ''' + """ if "vendors" in gpu and len(gpu["vendors"]) > 0: _first = list(gpu["vendors"].keys())[0] env.concat("VK_ICD_FILENAMES", gpu["vendors"][_first]["icd"]) else: - logging.warning("No GPU vendor found, keep going without setting VK_ICD_FILENAMES…") + logging.warning( + "No GPU vendor found, keep going without setting VK_ICD_FILENAMES…" + ) # Add ld to LD_LIBRARY_PATH if ld: @@ -414,19 +446,19 @@ def _get_runner_info(self) -> tuple[str, str]: return "", "" if SteamUtils.is_proton(runner): - ''' - If the runner is Proton, set the path to /dist or /files + """ + If the runner is Proton, set the path to /dist or /files based on check if files exists. Additionally, check for its corresponding runtime. - ''' + """ runner_runtime = SteamUtils.get_associated_runtime(runner) runner = os.path.join(SteamUtils.get_dist_directory(runner), f"bin/wine") elif runner.startswith("sys-"): - ''' + """ If the runner type is system, set the runner binary path to the system command. Else set it to the full path. - ''' + """ runner = shutil.which("wine") else: @@ -440,16 +472,20 @@ def _get_runner_info(self) -> tuple[str, str]: return runner, runner_runtime def get_cmd( - self, - command, - post_script: Optional[str] = None, - return_steam_cmd: bool = False, - return_clean_cmd: bool = False + self, + command, + post_script: Optional[str] = None, + return_steam_cmd: bool = False, + return_clean_cmd: bool = False, + environment: Optional[dict] = None, ) -> str: config = self.config params = config.Parameters runner = self.runner + if environment is None: + environment = {} + if return_clean_cmd: return_steam_cmd = True @@ -470,7 +506,7 @@ def get_cmd( command = f"mangohud {command}" if gamescope_available and params.gamescope: - gamescope_run = tempfile.NamedTemporaryFile(mode='w', suffix='.sh').name + gamescope_run = tempfile.NamedTemporaryFile(mode="w", suffix=".sh").name # Create temporary sh script in /tmp where Gamescope will execute it file = [f"#!/usr/bin/env sh\n"] @@ -483,7 +519,9 @@ def get_cmd( f.write("".join(file)) # Update command - command = f"{self._get_gamescope_cmd(return_steam_cmd)} -- {gamescope_run}" + command = ( + f"{self._get_gamescope_cmd(return_steam_cmd)} -- {gamescope_run}" + ) logging.info(f"Running Gamescope command: '{command}'") logging.info(f"{gamescope_run} contains:") with open(gamescope_run, "r") as f: @@ -502,21 +540,21 @@ def get_cmd( if _rs: if "sniper" in _rs.keys() and "sniper" in self.runner_runtime: - ''' + """ Sniper is the default runtime used by Proton version >= 8.0 - ''' + """ _picked = _rs["sniper"] elif "soldier" in _rs.keys() and "soldier" in self.runner_runtime: - ''' + """ Sniper is the default runtime used by Proton version >= 5.13 and < 8.0 - ''' + """ _picked = _rs["soldier"] elif "scout" in _rs.keys(): - ''' + """ For Wine runners, we cannot make assumption about which runtime would suits them the best, as it would depend on their build environment. Sniper/Soldier are not backward-compatible, defaulting to Scout should maximize compatibility. - ''' + """ _picked = _rs["scout"] else: logging.warning("Steam runtime was requested but not found") @@ -525,15 +563,27 @@ def get_cmd( logging.info(f"Using Steam runtime {_picked['name']}") command = f"{_picked['entry_point']} {command}" else: - logging.warning("Steam runtime was requested and found but there are no valid combinations") + logging.warning( + "Steam runtime was requested and found but there are no valid combinations" + ) if self.arguments: - if "%command%" in self.arguments: - prefix = self.arguments.split("%command%")[0] - suffix = self.arguments.split("%command%")[1] - command = f"{prefix} {command} {suffix}" - else: - command = f"{command} {self.arguments}" + prefix, suffix, extracted_env = SteamUtils.handle_launch_options( + self.arguments + ) + if prefix: + command = f"{prefix} {command}" + if suffix: + command = f"{command} {suffix}" + if extracted_env: + if extracted_env.get("WINEDLLOVERRIDES") and environment.get( + "WINEDLLOVERRIDES" + ): + environment["WINEDLLOVERRIDES"] += ";" + extracted_env.get( + "WINEDLLOVERRIDES" + ) + del extracted_env["WINEDLLOVERRIDES"] + environment.update(extracted_env) if post_script is not None: command = f"{command} ; sh '{post_script}'" @@ -554,12 +604,14 @@ def _get_gamescope_cmd(self, return_steam_cmd: bool = False) -> str: if params.gamescope_borderless: gamescope_cmd.append("-b") if params.gamescope_scaling: - gamescope_cmd.append("-n") + gamescope_cmd.append("-S integer") if params.fsr: - gamescope_cmd.append("-U") + gamescope_cmd.append("-F fsr") # Upscaling sharpness is from 0 to 20. There are 5 FSR upscaling levels, # so multiply by 4 to reach 20 - gamescope_cmd.append(f"--fsr-sharpness {params.fsr_sharpening_strength * 4}") + gamescope_cmd.append( + f"--fsr-sharpness {params.fsr_sharpening_strength * 4}" + ) if params.gamescope_fps > 0: gamescope_cmd.append(f"-r {params.gamescope_fps}") if params.gamescope_fps_no_focus > 0: @@ -579,10 +631,12 @@ def _vmtouch_preload(self): vmtouch_flags = "-t -v -l -d" vmtouch_file_size = " -m 1024M" if self.command.find("C:\\") > 0: - s = (self.cwd + "/" + (self.command.split(" ")[-1].split('\\')[-1])).replace('\'', "") + s = ( + self.cwd + "/" + (self.command.split(" ")[-1].split("\\")[-1]) + ).replace("'", "") else: s = self.command.split(" ")[-1] - self.vmtouch_files = f"'{s}'" + self.vmtouch_files = shlex.quote(s) # if self.config.Parameters.vmtouch_cache_cwd: # self.vmtouch_files = "'"+self.vmtouch_files+"' '"+self.cwd+"/'" Commented out as fix for #1941 @@ -612,10 +666,7 @@ def _get_sandbox_manager(self) -> SandboxManager: envs=self.env, chdir=self.cwd, share_paths_rw=[ManagerUtils.get_bottle_path(self.config)], - share_paths_ro=[ - Paths.runners, - Paths.temp - ], + share_paths_ro=[Paths.runners, Paths.temp], share_net=self.config.Sandbox.share_net, share_sound=self.config.Sandbox.share_sound, ) @@ -628,22 +679,30 @@ def run(self) -> Result[Optional[str]]: `data` may be available even if `status` is False. """ if None in [self.runner, self.env]: - return Result(False, message="runner or env is not ready, Wine command terminated.") + return Result( + False, message="runner or env is not ready, Wine command terminated." + ) if vmtouch_available and self.config.Parameters.vmtouch and not self.terminal: self._vmtouch_preload() - sandbox = self._get_sandbox_manager() if self.config.Parameters.sandbox else None + sandbox = ( + self._get_sandbox_manager() if self.config.Parameters.sandbox else None + ) # run command in external terminal if terminal is True if self.terminal: if sandbox: return Result( - status=TerminalUtils().execute(sandbox.get_cmd(self.command), self.env, self.colors, self.cwd) + status=TerminalUtils().execute( + sandbox.get_cmd(self.command), self.env, self.colors, self.cwd + ) ) else: return Result( - status=TerminalUtils().execute(self.command, self.env, self.colors, self.cwd) + status=TerminalUtils().execute( + self.command, self.env, self.colors, self.cwd + ) ) # prepare proc if we are going to execute command internally @@ -659,7 +718,7 @@ def run(self) -> Result[Optional[str]]: stdout=subprocess.PIPE, shell=True, env=self.env, - cwd=self.cwd + cwd=self.cwd, ) except FileNotFoundError: return Result(False, message="File not found") @@ -690,30 +749,8 @@ def run(self) -> Result[Optional[str]]: # to fix it, which is removed since it may lead to unexpected behavior if "ShellExecuteEx" in rv: logging.warning("ShellExecuteEx exception seems occurred.") - return Result(False, data=rv, message="ShellExecuteEx exception seems occurred.") + return Result( + False, data=rv, message="ShellExecuteEx exception seems occurred." + ) return Result(True, data=rv) - - @staticmethod - def __set_dxvk_nvapi_conf(bottle: str): - """ - TODO: This should be moved to a dedicated DXVKConf class when - we will provide a way to set the DXVK configuration. - """ - dxvk_conf = f"{bottle}/dxvk.conf" - if not os.path.exists(dxvk_conf): - # create dxvk.conf if it doesn't exist - with open(dxvk_conf, "w") as f: - f.write("dxgi.nvapiHack = False") - else: - # check if dxvk.conf has the nvapiHack option, if not add it - with open(dxvk_conf, "r") as f: - lines = f.readlines() - with open(dxvk_conf, "w") as f: - for line in lines: - if "dxgi.nvapiHack" in line: - f.write("dxgi.nvapiHack = False\n") - else: - f.write(line) - - return dxvk_conf diff --git a/bottles/backend/wine/winedbg.py b/bottles/backend/wine/winedbg.py index 064b935ba69..43601a219ac 100644 --- a/bottles/backend/wine/winedbg.py +++ b/bottles/backend/wine/winedbg.py @@ -30,9 +30,7 @@ def get_processes(self): return processes res = self.launch( - args='--command "info proc"', - communicate=True, - action_name="get_processes" + args='--command "info proc"', communicate=True, action_name="get_processes" ) if not res.ready: return processes @@ -62,13 +60,13 @@ def get_processes(self): "pid": w_pid, "threads": w_threads, "name": w_name, - "parent": w_parent + "parent": w_parent, } processes.append(w) return processes - def wait_for_process(self, name: str, timeout: float = .5): + def wait_for_process(self, name: str, timeout: float = 0.5): """Wait for a process to exit.""" if not self.__wineserver_status(): return True @@ -91,24 +89,16 @@ def kill_process(self, pid: Optional[str] = None, name: Optional[str] = None): return if pid: - args = "\n".join([ - "<< END_OF_INPUTS", - f"attach 0x{pid}", - "kill", - "quit", - "END_OF_INPUTS" - ]) - res = self.launch( - args=args, - communicate=True, - action_name="kill_process" + args = "\n".join( + ["<< END_OF_INPUTS", f"attach 0x{pid}", "kill", "quit", "END_OF_INPUTS"] ) + res = self.launch(args=args, communicate=True, action_name="kill_process") if res.has_data and "error 5" in res.data and name: subprocess.Popen( f"kill $(pgrep {name[:15]})", stdout=subprocess.PIPE, stderr=subprocess.PIPE, - shell=True + shell=True, ) return wineboot.kill() diff --git a/bottles/backend/wine/winefile.py b/bottles/backend/wine/winefile.py index 890d9d601a4..28152f8e1ac 100644 --- a/bottles/backend/wine/winefile.py +++ b/bottles/backend/wine/winefile.py @@ -1,4 +1,3 @@ - from bottles.backend.logger import Logger from bottles.backend.wine.wineprogram import WineProgram diff --git a/bottles/backend/wine/winepath.py b/bottles/backend/wine/winepath.py index c1fb5db2673..aaff7ba266b 100644 --- a/bottles/backend/wine/winepath.py +++ b/bottles/backend/wine/winepath.py @@ -33,8 +33,7 @@ def to_unix(self, path: str, native: bool = False): bottle_path = ManagerUtils.get_bottle_path(self.config) path = path.replace("\\", "/") path = path.replace( - path[0:2], - f"{bottle_path}/dosdevices/{path[0:2].lower()}" + path[0:2], f"{bottle_path}/dosdevices/{path[0:2].lower()}" ) return self.__clean_path(path) args = f"--unix '{path}'" @@ -48,14 +47,12 @@ def to_windows(self, path: str, native: bool = False): if "/drive_" in path: drive = re.search(r"drive_([a-z])/", path.lower()).group(1) path = path.replace( - f"{bottle_path}/drive_{drive.lower()}", - f"{drive.upper()}:" + f"{bottle_path}/drive_{drive.lower()}", f"{drive.upper()}:" ) elif "/dosdevices" in path: drive = re.search(r"dosdevices/([a-z]):", path.lower()).group(1) path = path.replace( - f"{bottle_path}/dosdevices/{drive.lower()}", - f"{drive.upper()}:" + f"{bottle_path}/dosdevices/{drive.lower()}", f"{drive.upper()}:" ) path = path.replace("/", "\\") return self.__clean_path(path) diff --git a/bottles/backend/wine/wineprogram.py b/bottles/backend/wine/wineprogram.py index d3f5bb1edba..7f2b13c10f3 100644 --- a/bottles/backend/wine/wineprogram.py +++ b/bottles/backend/wine/wineprogram.py @@ -19,7 +19,9 @@ class WineProgram: def __init__(self, config: BottleConfig, silent=False): if not isinstance(config, BottleConfig): - raise TypeError("config should be BottleConfig type, but it was %s" % type(config)) + raise TypeError( + "config should be BottleConfig type, but it was %s" % type(config) + ) self.config = config self.silent = silent @@ -35,14 +37,14 @@ def get_command(self, args: Optional[str] = None): return command def launch( - self, - args: Union[tuple, str] | None = None, - terminal: bool = False, - minimal: bool = True, - communicate: bool = False, - environment: Optional[dict] = None, - cwd: Optional[str] = None, - action_name: str = "launch" + self, + args: Union[tuple, str] | None = None, + terminal: bool = False, + minimal: bool = True, + communicate: bool = False, + environment: Optional[dict] = None, + cwd: Optional[str] = None, + action_name: str = "launch", ): if environment is None: environment = {} @@ -67,7 +69,7 @@ def launch( colors=self.colors, environment=environment, cwd=cwd, - arguments=program_args + arguments=program_args, ).run() return res diff --git a/bottles/backend/wine/wineserver.py b/bottles/backend/wine/wineserver.py index f092ac9d787..60709b1279c 100644 --- a/bottles/backend/wine/wineserver.py +++ b/bottles/backend/wine/wineserver.py @@ -47,9 +47,9 @@ def is_alive(self): stderr=subprocess.PIPE, shell=True, cwd=bottle, - env=env + env=env, ) - time.sleep(.5) + time.sleep(0.5) if res.poll() is None: res.kill() # kill the process to avoid zombie incursion return True @@ -77,7 +77,7 @@ def wait(self): stderr=subprocess.PIPE, shell=True, cwd=bottle, - env=env + env=env, ).wait() def kill(self, signal: int = -1): @@ -86,9 +86,7 @@ def kill(self, signal: int = -1): args += str(signal) self.launch( - args=args, - communicate=True, - action_name="sending signal to the wine server" + args=args, communicate=True, action_name="sending signal to the wine server" ) def force_kill(self): diff --git a/bottles/backend/wine/winhelp.py b/bottles/backend/wine/winhelp.py index 69d3807e74c..b09e940fa24 100644 --- a/bottles/backend/wine/winhelp.py +++ b/bottles/backend/wine/winhelp.py @@ -1,4 +1,3 @@ - from bottles.backend.logger import Logger from bottles.backend.wine.wineprogram import WineProgram diff --git a/bottles/backend/wine/xcopy.py b/bottles/backend/wine/xcopy.py index 561e2058dd3..104852ae743 100644 --- a/bottles/backend/wine/xcopy.py +++ b/bottles/backend/wine/xcopy.py @@ -12,24 +12,24 @@ class Xcopy(WineProgram): command = "xcopy" def copy( - self, - source: str, - dest: str, - dir_and_subs: bool = False, - keep_empty_dirs: bool = False, - quiet: bool = False, - full_log: bool = False, - simulate: bool = False, - ask_confirm: bool = False, - only_struct: bool = False, - no_overwrite_notify: bool = False, - use_short_names: bool = False, - only_existing_in_dest: bool = False, - overwrite_read_only_files: bool = False, - include_hidden_and_sys_files: bool = False, - continue_if_error: bool = False, - copy_attributes: bool = False, - after_date: Optional[datetime] = None, + self, + source: str, + dest: str, + dir_and_subs: bool = False, + keep_empty_dirs: bool = False, + quiet: bool = False, + full_log: bool = False, + simulate: bool = False, + ask_confirm: bool = False, + only_struct: bool = False, + no_overwrite_notify: bool = False, + use_short_names: bool = False, + only_existing_in_dest: bool = False, + overwrite_read_only_files: bool = False, + include_hidden_and_sys_files: bool = False, + continue_if_error: bool = False, + copy_attributes: bool = False, + after_date: Optional[datetime] = None, ): args = f"{source} {dest} /i" diff --git a/bottles/frontend/bottles.py b/bottles/frontend/bottles.py index e18522ddafe..2d973c9a4b6 100644 --- a/bottles/frontend/bottles.py +++ b/bottles/frontend/bottles.py @@ -33,13 +33,15 @@ os.unsetenv("GTK_THEME") signal.signal(signal.SIGINT, signal.SIG_DFL) -gettext.install('bottles', localedir) +gettext.install("bottles", localedir) -if __name__ == '__main__': +if __name__ == "__main__": from gi.repository import Gio + resource = Gio.Resource.load(gresource_path) # noinspection PyProtectedMember resource._register() from bottles.frontend import main + sys.exit(main.main(APP_VERSION)) diff --git a/bottles/frontend/cli/cli.py b/bottles/frontend/cli/cli.py index 8a785946aae..ce27c615fed 100644 --- a/bottles/frontend/cli/cli.py +++ b/bottles/frontend/cli/cli.py @@ -543,6 +543,12 @@ def run_program(self): _executable = self.args.executable _cwd = None _script = None + _program_dxvk = None + _program_vkd3d = None + _program_dxvk_nvapi = None + _program_fsr = None + _program_virt_desktop = None + mng = Manager(g_settings=self.settings, is_cli=True) mng.checks() @@ -552,12 +558,6 @@ def run_program(self): bottle = mng.local_bottles[_bottle] programs = mng.get_programs(bottle) - _dxvk = bottle.Parameters.dxvk - _vkd3d = bottle.Parameters.vkd3d - _nvapi = bottle.Parameters.dxvk_nvapi - _fsr = bottle.Parameters.fsr - _pulse = bottle.Parameters.pulseaudio_latency - _virt_desktop = bottle.Parameters.virtual_desktop if _program is not None: if _executable is not None: @@ -570,23 +570,17 @@ def run_program(self): program = [p for p in programs if p["name"] == _program][0] _executable = program.get("path", "") - if _keep: - _args = program.get("arguments", "") + " " + _args + _program_args = program.get("arguments") + if _keep and _program_args: + _args = _program_args + " " + _args _cwd = program.get("folder", "") _script = program.get("script", None) - if program.get("dxvk") != _dxvk: - _dxvk = program.get("dxvk") - if program.get("vkd3d") != _vkd3d: - _vkd3d = program.get("vkd3d") - if program.get("dxvk_nvapi") != _nvapi: - _nvapi = program.get("dxvk_nvapi") - if program.get("fsr") != _fsr: - _fsr = program.get("fsr") - if program.get("pulseaudio_latency") != _pulse: - _pulse = program.get("pulseaudio_latency") - if program.get("virtual_desktop") != _virt_desktop: - _virt_desktop = program.get("virtual_desktop") + _program_dxvk = program.get("dxvk") + _program_vkd3d = program.get("vkd3d") + _program_dxvk_nvapi = program.get("dxvk_nvapi") + _program_fsr = program.get("fsr") + _program_virt_desktop = program.get("virtual_desktop") WineExecutor( bottle, @@ -594,11 +588,11 @@ def run_program(self): args=_args, cwd=_cwd, post_script=_script, - override_dxvk=_dxvk, - override_vkd3d=_vkd3d, - override_nvapi=_nvapi, - override_fsr=_fsr, - override_virt_desktop=_virt_desktop + program_dxvk=_program_dxvk, + program_vkd3d=_program_vkd3d, + program_nvapi=_program_dxvk_nvapi, + program_fsr=_program_fsr, + program_virt_desktop=_program_virt_desktop ).run_cli() # endregion diff --git a/bottles/frontend/const.py b/bottles/frontend/const.py index fb4f9ab1847..6f405b93f90 100644 --- a/bottles/frontend/const.py +++ b/bottles/frontend/const.py @@ -1 +1 @@ -BUILD_TYPE = "prod" \ No newline at end of file +BUILD_TYPE = "prod" diff --git a/bottles/frontend/devel/const.py b/bottles/frontend/devel/const.py index 3fccda2762b..2c23ecbf188 100644 --- a/bottles/frontend/devel/const.py +++ b/bottles/frontend/devel/const.py @@ -1 +1 @@ -BUILD_TYPE = "devel" \ No newline at end of file +BUILD_TYPE = "devel" diff --git a/bottles/frontend/main.py b/bottles/frontend/main.py index 11dc1d42482..8222cb40237 100644 --- a/bottles/frontend/main.py +++ b/bottles/frontend/main.py @@ -23,11 +23,11 @@ import webbrowser from os import path -gi.require_version('Gtk', '4.0') -gi.require_version('Adw', '1') -gi.require_version('GtkSource', '5') -#gi.require_version("Xdp", "1.0") -#gi.require_version("XdpGtk4", "1.0") +gi.require_version("Gtk", "4.0") +gi.require_version("Adw", "1") +gi.require_version("GtkSource", "5") +# gi.require_version("Xdp", "1.0") +# gi.require_version("XdpGtk4", "1.0") from gi.repository import Gtk, Gio, GLib, GObject, Adw @@ -40,29 +40,29 @@ logging = Logger() # region Translations -''' +""" This code snippet searches for and uploads translations to different directories, depending on your production or development environment. The function _() can be used to create and retrieve translations. -''' -share_dir = path.join(sys.prefix, 'share') -base_dir = '.' +""" +share_dir = path.join(sys.prefix, "share") +base_dir = "." -if getattr(sys, 'frozen', False): +if getattr(sys, "frozen", False): base_dir = path.dirname(sys.executable) - share_dir = path.join(base_dir, 'share') + share_dir = path.join(base_dir, "share") elif sys.argv[0]: exec_dir = path.dirname(path.realpath(sys.argv[0])) base_dir = path.dirname(exec_dir) - share_dir = path.join(base_dir, 'share') + share_dir = path.join(base_dir, "share") if not path.exists(share_dir): share_dir = base_dir -locale_dir = path.join(share_dir, 'locale') +locale_dir = path.join(share_dir, "locale") if not path.exists(locale_dir): # development - locale_dir = path.join(base_dir, 'build', 'mo') + locale_dir = path.join(base_dir, "build", "mo") locale.bindtextdomain("bottles", locale_dir) locale.textdomain("bottles") @@ -81,15 +81,15 @@ class Bottles(Adw.Application): def __init__(self): super().__init__( - application_id='com.usebottles.bottles', + application_id="com.usebottles.bottles", flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE, - register_session=True + register_session=True, ) - self.__create_action('quit', self.__quit, ['q', 'w']) - self.__create_action('about', self.__show_about_window) - self.__create_action('import', self.__show_importer_view, ['i']) - self.__create_action('preferences', self.__show_preferences, ['comma']) - self.__create_action('help', self.__help, ['F1']) + self.__create_action("quit", self.__quit, ["q", "w"]) + self.__create_action("about", self.__show_about_window) + self.__create_action("import", self.__show_importer_view, ["i"]) + self.__create_action("preferences", self.__show_preferences, ["comma"]) + self.__create_action("help", self.__help, ["F1"]) self.__register_arguments() @@ -109,7 +109,7 @@ def __register_arguments(self): GLib.OptionFlags.NONE, GLib.OptionArg.NONE, _("Show version"), - None + None, ) self.add_main_option( "executable", @@ -117,7 +117,7 @@ def __register_arguments(self): GLib.OptionFlags.NONE, GLib.OptionArg.STRING, _("Executable path"), - None + None, ) self.add_main_option( "lnk", @@ -125,7 +125,7 @@ def __register_arguments(self): GLib.OptionFlags.NONE, GLib.OptionArg.STRING, _("lnk path"), - None + None, ) self.add_main_option( "bottle", @@ -133,7 +133,7 @@ def __register_arguments(self): GLib.OptionFlags.NONE, GLib.OptionArg.STRING, _("Bottle name"), - None + None, ) self.add_main_option( "arguments", @@ -141,7 +141,7 @@ def __register_arguments(self): GLib.OptionFlags.NONE, GLib.OptionArg.STRING, _("Pass arguments"), - None + None, ) self.add_main_option( GLib.OPTION_REMAINING, @@ -149,7 +149,7 @@ def __register_arguments(self): GLib.OptionFlags.NONE, GLib.OptionArg.STRING_ARRAY, "URI", - None + None, ) def do_command_line(self, command): @@ -172,12 +172,12 @@ def do_command_line(self, command): self.arg_bottle = commands.lookup_value("bottle").get_string() if not self.arg_exe: - ''' + """ If no executable is specified, look if it was passed without the --executable argument. - ''' + """ for a in sys.argv: - if a.endswith(('.exe', '.msi', '.bat', '.lnk')): + if a.endswith((".exe", ".msi", ".bat", ".lnk")): self.arg_exe = a uri = commands.lookup_value(GLib.OPTION_REMAINING) @@ -195,20 +195,26 @@ def __process_uri(self, uri): uri = uri[0] if os.path.exists(uri): from bottles.frontend.windows.bottlepicker import BottlePickerDialog + dialog = BottlePickerDialog(application=self, arg_exe=uri) dialog.present() return 0 _wrong_uri_error = _("Invalid URI (syntax: bottles:run//)") - if not len(uri) > 0 or not uri.startswith('bottles:run/') or len(uri.split('/')) != 3: + if ( + not len(uri) > 0 + or not uri.startswith("bottles:run/") + or len(uri.split("/")) != 3 + ): print(_wrong_uri_error) return False - uri = uri.replace('bottles:run/', '') - bottle, program = uri.split('/') + uri = uri.replace("bottles:run/", "") + bottle, program = uri.split("/") import subprocess - subprocess.Popen(['bottles-cli', 'run', '-b', bottle, '-p', program]) + + subprocess.Popen(["bottles-cli", "run", "-b", bottle, "-p", program]) def do_startup(self): """ @@ -227,10 +233,7 @@ def do_activate(self): Adw.Application.do_activate(self) win = self.props.active_window if not win: - win = MainWindow( - application=self, - arg_bottle=self.arg_bottle - ) + win = MainWindow(application=self, arg_bottle=self.arg_bottle) self.win = win win.present() @@ -239,7 +242,9 @@ def __quit(self, *args): This function close the application. It is used by the [Ctrl+Q] shortcut. """ - logging.info(_("[Quit] request received."), ) + logging.info( + _("[Quit] request received."), + ) self.win.on_close_request() quit() @@ -249,7 +254,9 @@ def __help(action=None, param=None): This function open the documentation in the user's default browser. It is used by the [F1] shortcut. """ - logging.info(_("[Help] request received."), ) + logging.info( + _("[Help] request received."), + ) webbrowser.open_new_tab("https://docs.usebottles.com") def __refresh(self, action=None, param=None): @@ -257,7 +264,9 @@ def __refresh(self, action=None, param=None): This function refresh the user bottle list. It is used by the [Ctrl+R] shortcut. """ - logging.info(_("[Refresh] request received."), ) + logging.info( + _("[Refresh] request received."), + ) self.win.manager.update_bottles() def __show_preferences(self, *args): @@ -317,8 +326,8 @@ def __show_about_window(self, *_args): "icoextract https://github.com/jlu5/icoextract", "vmtouch https://github.com/hoytech/vmtouch", "FVS https://github.com/mirkobrombin/FVS", - "pathvalidate https://github.com/thombashi/pathvalidate" - ] + "pathvalidate https://github.com/thombashi/pathvalidate", + ], ) about_window.add_acknowledgement_section( _("Sponsored and Funded by"), @@ -327,8 +336,8 @@ def __show_about_window(self, *_args): "GitBook https://www.gitbook.com/?ref=bottles", "Linode https://www.linode.com/?from=bottles", "Appwrite https://appwrite.io/?from=bottles", - "Community ❤️ https://usebottles.com/funding" - ] + "Community ❤️ https://usebottles.com/funding", + ], ) about_window.set_release_notes(release_notes) about_window.set_release_notes_version("51.0") @@ -351,6 +360,7 @@ def __create_action(self, name, callback, shortcuts=None, param=None): if shortcuts: self.set_accels_for_action(f"app.{name}", shortcuts) + GObject.threads_init() diff --git a/bottles/frontend/operation.py b/bottles/frontend/operation.py index 16bd559869f..83773ee8fcd 100644 --- a/bottles/frontend/operation.py +++ b/bottles/frontend/operation.py @@ -22,13 +22,13 @@ from bottles.backend.models.result import Result from bottles.backend.state import TaskManager -gi.require_version('Adw', '1') +gi.require_version("Adw", "1") from gi.repository import Gtk, Adw -@Gtk.Template(resource_path='/com/usebottles/bottles/task-entry.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/task-entry.ui") class TaskEntry(Adw.ActionRow): - __gtype_name__ = 'TaskEntry' + __gtype_name__ = "TaskEntry" # region Widgets btn_cancel = Gtk.Template.Child() @@ -54,6 +54,7 @@ def update(self, subtitle: str): class TaskSyncer: """Keep task list updated with backend TaskManager""" + _TASK_WIDGETS: Dict[UUID, TaskEntry] = {} def __init__(self, window): diff --git a/bottles/frontend/params.py b/bottles/frontend/params.py index 4dd30c6962c..f80b147dd47 100644 --- a/bottles/frontend/params.py +++ b/bottles/frontend/params.py @@ -10,7 +10,7 @@ ANIM_DURATION = 120 # General purpose definitions -EXECUTABLE_EXTS = ('.exe', '.msi', '.bat', '.lnk') +EXECUTABLE_EXTS = (".exe", ".msi", ".bat", ".lnk") # URLs DOC_URL = "https://docs.usebottles.com" diff --git a/bottles/frontend/ui/details-bottle.blp b/bottles/frontend/ui/details-bottle.blp index 567c9e2e43c..c25f51c4334 100644 --- a/bottles/frontend/ui/details-bottle.blp +++ b/bottles/frontend/ui/details-bottle.blp @@ -163,6 +163,7 @@ template DetailsBottle : .AdwPreferencesPage { max-width-chars: 30; label: _("My bottle"); wrap-mode: word_char; + use-markup: false; styles [ "title-1", diff --git a/bottles/frontend/ui/dialog-dll-overrides.blp b/bottles/frontend/ui/dialog-dll-overrides.blp index d387deff54c..526503f20ee 100644 --- a/bottles/frontend/ui/dialog-dll-overrides.blp +++ b/bottles/frontend/ui/dialog-dll-overrides.blp @@ -14,6 +14,19 @@ template DLLOverridesDialog : .AdwPreferencesWindow { .AdwEntryRow entry_row { title: _("New Override"); show-apply-button: true; + + [suffix] + MenuButton menu_invalid_override { + valign: center; + tooltip-text: _("Show Information"); + icon-name: "warning-symbolic"; + popover: popover_invalid_override; + visible: false; + + styles [ + "flat", + ] + } } } @@ -22,3 +35,20 @@ template DLLOverridesDialog : .AdwPreferencesWindow { } } } + +Popover popover_invalid_override { + Label { + margin-start: 6; + margin-end: 6; + margin-top: 6; + margin-bottom: 6; + xalign: 0; + wrap: true; + wrap-mode: word_char; + ellipsize: none; + lines: 4; + use-markup: true; + max-width-chars: 40; + label: _("This override is already managed by Bottles."); + } +} diff --git a/bottles/frontend/ui/list-entry.blp b/bottles/frontend/ui/list-entry.blp index 5b3ed260d06..84d48872a23 100644 --- a/bottles/frontend/ui/list-entry.blp +++ b/bottles/frontend/ui/list-entry.blp @@ -3,6 +3,7 @@ using Gtk 4.0; template BottleViewEntry : .AdwActionRow { activatable: true; title: _("Bottle name"); + use-markup: false; Box list_labels { spacing: 1; diff --git a/bottles/frontend/ui/new.blp b/bottles/frontend/ui/new.blp index 3dd42c292c0..9bd8024df15 100644 --- a/bottles/frontend/ui/new.blp +++ b/bottles/frontend/ui/new.blp @@ -50,6 +50,7 @@ template NewView : .AdwWindow { .AdwPreferencesGroup { .AdwEntryRow entry_name { + use-markup: false; title: _("Name"); [suffix] @@ -280,4 +281,4 @@ Popover popover_duplicate { max-width-chars: 40; label: _("This name is unavailable, please try another."); } -} \ No newline at end of file +} diff --git a/bottles/frontend/utils/filters.py b/bottles/frontend/utils/filters.py index 7c8b3c973fa..e99b19579e9 100644 --- a/bottles/frontend/utils/filters.py +++ b/bottles/frontend/utils/filters.py @@ -16,6 +16,7 @@ from gi.repository import Gtk + def add_executable_filters(dialog): filter = Gtk.FileFilter() filter.set_name(_("Supported Executables")) @@ -24,6 +25,7 @@ def add_executable_filters(dialog): dialog.add_filter(filter) + def add_yaml_filters(dialog): filter = Gtk.FileFilter() filter.set_name("YAML") @@ -31,6 +33,7 @@ def add_yaml_filters(dialog): dialog.add_filter(filter) + def add_all_filters(dialog): filter = Gtk.FileFilter() filter.set_name(_("All Files")) diff --git a/bottles/frontend/utils/gtk.py b/bottles/frontend/utils/gtk.py index 350e9dddf6d..c70f57336fb 100644 --- a/bottles/frontend/utils/gtk.py +++ b/bottles/frontend/utils/gtk.py @@ -27,7 +27,11 @@ class GtkUtils: @staticmethod def validate_entry(entry, extend=None) -> bool: text = entry.get_text() - if re.search("[@!#$%^&*()<>?/|}{~:.;,'\"]", text) or len(text) == 0 or text.isspace(): + if ( + re.search("[@!#$%^&*()<>?/|}{~:.;,'\"]", text) + or len(text) == 0 + or text.isspace() + ): entry.add_css_class("error") return False @@ -45,8 +49,10 @@ def run_in_main_loop(func): def wrapper(*args, **kwargs): _tmp = [] if kwargs: - for _, param in list(signature(func).parameters.items())[len(args):]: - _tmp.append(kwargs[param.name] if param.name in kwargs else param.default) + for _, param in list(signature(func).parameters.items())[len(args) :]: + _tmp.append( + kwargs[param.name] if param.name in kwargs else param.default + ) args = args + tuple(_tmp) return GLib.idle_add(func, *args) diff --git a/bottles/frontend/views/bottle_dependencies.py b/bottles/frontend/views/bottle_dependencies.py index 8cdb5158b2c..f96b3bf29d2 100644 --- a/bottles/frontend/views/bottle_dependencies.py +++ b/bottles/frontend/views/bottle_dependencies.py @@ -28,9 +28,9 @@ from bottles.frontend.widgets.dependency import DependencyEntry -@Gtk.Template(resource_path='/com/usebottles/bottles/details-dependencies.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/details-dependencies.ui") class DependenciesView(Adw.Bin): - __gtype_name__ = 'DetailsDependencies' + __gtype_name__ = "DetailsDependencies" __registry = [] # region Widgets @@ -60,10 +60,12 @@ def __init__(self, details, config: BottleConfig, **kwargs): self.entry_search.add_controller(self.ev_controller) self.search_bar.set_key_capture_widget(self.window) - self.btn_report.connect("clicked", open_doc_url, "contribute/missing-dependencies") + self.btn_report.connect( + "clicked", open_doc_url, "contribute/missing-dependencies" + ) self.btn_help.connect("clicked", open_doc_url, "bottles/dependencies") - if self.manager.utils_conn.status == False: + if not self.manager.utils_conn.status: self.stack.set_visible_child_name("page_offline") self.spinner_loading.start() @@ -89,7 +91,7 @@ def empty_list(self): r.get_parent().remove(r) self.__registry = [] - def update(self, widget=False, config: Optional[BottleConfig] = None): + def update(self, _widget=False, config: Optional[BottleConfig] = None): """ This function update the dependencies list with the supported by the manager. @@ -99,7 +101,7 @@ def update(self, widget=False, config: Optional[BottleConfig] = None): self.config = config # Not sure if it's the best place to make this check - if self.manager.utils_conn.status == False: + if not self.manager.utils_conn.status: return self.stack.set_visible_child_name("page_loading") @@ -109,17 +111,17 @@ def new_dependency(dependency, plain=False): window=self.window, config=self.config, dependency=dependency, - plain=plain + plain=plain, ) self.__registry.append(entry) self.list_dependencies.append(entry) @GtkUtils.run_in_main_loop - def callback(result, error=False): + def callback(_result, _error=False): self.stack.set_visible_child_name("page_deps") def process_dependencies(): - time.sleep(.3) # workaround for freezing bug on bottle load + time.sleep(0.3) # workaround for freezing bug on bottle load EventManager.wait(Events.DependenciesOrganizing) dependencies = self.manager.supported_dependencies @@ -128,11 +130,7 @@ def process_dependencies(): if len(dependencies.keys()) > 0: for dep in dependencies.items(): if dep[0] in self.config.Installed_Dependencies: - continue # Do not list already installed dependencies' - - if dep[1].get("Arch", "win64") != self.config.Arch: - # NOTE: avoid listing dependencies not supported by the bottle arch - continue + continue # Do not list already installed dependencies GLib.idle_add(new_dependency, dep) diff --git a/bottles/frontend/views/bottle_details.py b/bottles/frontend/views/bottle_details.py index 4986e4732f4..c8b7973cb00 100644 --- a/bottles/frontend/views/bottle_details.py +++ b/bottles/frontend/views/bottle_details.py @@ -47,9 +47,9 @@ from bottles.frontend.windows.upgradeversioning import UpgradeVersioningDialog -@Gtk.Template(resource_path='/com/usebottles/bottles/details-bottle.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/details-bottle.ui") class BottleView(Adw.PreferencesPage): - __gtype_name__ = 'DetailsBottle' + __gtype_name__ = "DetailsBottle" __registry = [] # region Widgets @@ -113,10 +113,10 @@ def __init__(self, details, config, **kwargs): self.config = config self.show_hidden = False - self.target.connect('drop', self.on_drop) + self.target.connect("drop", self.on_drop) self.add_controller(self.target) - self.target.connect('enter', self.on_enter) - self.target.connect('leave', self.on_leave) + self.target.connect("enter", self.on_enter) + self.target.connect("leave", self.on_leave) self.add_shortcuts.connect("clicked", self.add) self.install_programs.connect("clicked", self.__change_page, "installers") @@ -145,16 +145,14 @@ def __init__(self, details, config, **kwargs): self.btn_backup_full.connect("clicked", self.__backup, "full") self.btn_duplicate.connect("clicked", self.__duplicate) self.btn_flatpak_doc.connect( - "clicked", - open_doc_url, - "flatpak/black-screen-or-silent-crash" + "clicked", open_doc_url, "flatpak/black-screen-or-silent-crash" ) if "FLATPAK_ID" in os.environ: - ''' + """ If Flatpak, show the btn_flatpak_doc widget to reach the documentation on how to expose directories - ''' + """ self.btn_flatpak_doc.set_visible(True) def __change_page(self, _widget, page_name): @@ -175,7 +173,10 @@ def on_drop(self, drop_target, value: Gdk.FileList, x, y, user_data=None): files: List[Gio.File] = value.get_files() args = "" file = files[0] - if ".exe" in file.get_basename().split("/")[-1] or ".msi" in file.get_basename().split("/")[-1]: + if ( + ".exe" in file.get_basename().split("/")[-1] + or ".msi" in file.get_basename().split("/")[-1] + ): executor = WineExecutor( self.config, exec_path=file.get_path(), @@ -190,7 +191,10 @@ def callback(a, b): else: self.window.show_toast( - _("File \"{0}\" is not a .exe or .msi file").format(file.get_basename().split("/")[-1])) + _('File "{0}" is not a .exe or .msi file').format( + file.get_basename().split("/")[-1] + ) + ) def on_enter(self, drop_target, x, y): self.drop_overlay.set_visible(True) @@ -229,8 +233,10 @@ def set_config(self, config: BottleConfig): if config.Versioning: self.__upgrade_versioning() - if config.Runner not in self.manager.runners_available \ - and not self.config.Environment == "Steam": + if ( + config.Runner not in self.manager.runners_available + and not self.config.Environment == "Steam" + ): self.__alert_missing_runner() # update programs list @@ -257,54 +263,62 @@ def set_path(_dialog, response): "name": basename[:-4], "path": path, "id": _uuid, - "folder": ManagerUtils.get_exe_parent_dir(self.config, path) + "folder": ManagerUtils.get_exe_parent_dir(self.config, path), } self.config = self.manager.update_config( config=self.config, key=_uuid, value=_program, scope="External_Programs", - fallback=True + fallback=True, ).data["config"] self.update_programs(config=self.config, force_add=_program) - self.window.show_toast(_("\"{0}\" added").format(basename[:-4])) + self.window.show_toast(_('"{0}" added').format(basename[:-4])) dialog = Gtk.FileChooserNative.new( title=_("Select Executable"), action=Gtk.FileChooserAction.OPEN, parent=self.window, - accept_label=_("Add") + accept_label=_("Add"), ) add_executable_filters(dialog) + add_all_filters(dialog) dialog.set_modal(True) dialog.connect("response", set_path) dialog.show() - def update_programs(self, config: Optional[BottleConfig] = None, force_add: dict = None): + def update_programs( + self, config: Optional[BottleConfig] = None, force_add: dict = None + ): """ This function update the programs lists. """ if config: if not isinstance(config, BottleConfig): - raise TypeError("config param need BottleConfig type, but it was %s" % type(config)) + raise TypeError( + "config param need BottleConfig type, but it was %s" % type(config) + ) self.config = config if not force_add: GLib.idle_add(self.empty_list) - def new_program(_program, check_boot=None, is_steam=False, - wineserver_status=False): + def new_program( + _program, check_boot=None, is_steam=False, wineserver_status=False + ): if check_boot is None: check_boot = wineserver_status - self.add_program(ProgramEntry( - self.window, - self.config, - _program, - is_steam=is_steam, - check_boot=check_boot, - )) + self.add_program( + ProgramEntry( + self.window, + self.config, + _program, + is_steam=is_steam, + check_boot=check_boot, + ) + ) if force_add: wineserver_status = WineServer(self.config).is_alive() @@ -323,7 +337,9 @@ def process_programs(): for program in programs: if program.get("removed"): if self.show_hidden: - GLib.idle_add(new_program, program, None, False, wineserver_status) + GLib.idle_add( + new_program, program, None, False, wineserver_status + ) handled += 1 continue GLib.idle_add(new_program, program, None, False, wineserver_status) @@ -337,16 +353,18 @@ def add_program(self, widget): self.__registry.append(widget) self.group_programs.remove(self.bottom_bar) # Remove the bottom_bar self.group_programs.add(widget) - self.group_programs.add(self.bottom_bar) # Add the bottom_bar back to the bottom + self.group_programs.add( + self.bottom_bar + ) # Add the bottom_bar back to the bottom def __toggle_removed(self, widget=False): """ This function toggle the show_hidden variable. """ if self.show_hidden: - self.btn_toggle_removed.set_property('text', _("Show Hidden Programs")) + self.btn_toggle_removed.set_property("text", _("Show Hidden Programs")) else: - self.btn_toggle_removed.set_property('text', _("Hide Hidden Programs")) + self.btn_toggle_removed.set_property("text", _("Hide Hidden Programs")) self.show_hidden = not self.show_hidden self.update_programs(config=self.config) @@ -383,7 +401,9 @@ def execute(_dialog, response): if response != Gtk.ResponseType.ACCEPT: return - self.window.show_toast(_("Launching \"{0}\"…").format(dialog.get_file().get_basename())) + self.window.show_toast( + _('Launching "{0}"…').format(dialog.get_file().get_basename()) + ) executor = WineExecutor( self.config, @@ -401,7 +421,7 @@ def callback(a, b): title=_("Select Executable"), action=Gtk.FileChooserAction.OPEN, parent=self.window, - accept_label=_("Run") + accept_label=_("Run"), ) add_executable_filters(dialog) @@ -410,11 +430,15 @@ def callback(a, b): dialog.connect("response", execute) dialog.show() - if "FLATPAK_ID" in os.environ and self.window.settings.get_boolean("show-sandbox-warning"): + if "FLATPAK_ID" in os.environ and self.window.settings.get_boolean( + "show-sandbox-warning" + ): dialog = Adw.MessageDialog.new( self.window, _("Be Aware of Sandbox"), - _("Bottles is running in a sandbox, a restricted permission environment needed to keep you safe. If the program won't run, consider moving inside the bottle (3 dots icon on the top), then launch from there.") + _( + "Bottles is running in a sandbox, a restricted permission environment needed to keep you safe. If the program won't run, consider moving inside the bottle (3 dots icon on the top), then launch from there." + ), ) dialog.add_response("ok", _("_Dismiss")) dialog.connect("response", show_chooser) @@ -440,9 +464,13 @@ def __backup(self, widget, backup_type): @GtkUtils.run_in_main_loop def finish(result, error=False): if result.ok: - self.window.show_toast(_("Backup created for \"{0}\"").format(self.config.Name)) + self.window.show_toast( + _('Backup created for "{0}"').format(self.config.Name) + ) else: - self.window.show_toast(_("Backup failed for \"{0}\"").format(self.config.Name)) + self.window.show_toast( + _('Backup failed for "{0}"').format(self.config.Name) + ) def set_path(_dialog, response): if response != Gtk.ResponseType.ACCEPT: @@ -455,14 +483,14 @@ def set_path(_dialog, response): callback=finish, config=self.config, scope=backup_type, - path=path + path=path, ) dialog = Gtk.FileChooserNative.new( title=title, action=Gtk.FileChooserAction.SAVE, parent=self.window, - accept_label=accept_label + accept_label=accept_label, ) dialog.set_modal(True) @@ -501,8 +529,14 @@ def handle_response(_widget, response_id): dialog = Adw.MessageDialog.new( self.window, - _("Are you sure you want to permanently delete \"{}\"?".format(self.config['Name'])), - _("This will permanently delete all programs and settings associated with it.") + _( + 'Are you sure you want to permanently delete "{}"?'.format( + self.config["Name"] + ) + ), + _( + "This will permanently delete all programs and settings associated with it." + ), ) dialog.add_response("cancel", _("_Cancel")) dialog.add_response("ok", _("_Delete")) @@ -522,8 +556,10 @@ def handle_response(_widget, response_id): dialog = Adw.MessageDialog.new( self.window, _("Missing Runner"), - _("The runner requested by this bottle is missing. Install it through \ -the Bottles preferences or choose a new one to run applications.") + _( + "The runner requested by this bottle is missing. Install it through \ +the Bottles preferences or choose a new one to run applications." + ), ) dialog.add_response("ok", _("_Dismiss")) dialog.connect("response", handle_response) @@ -534,10 +570,10 @@ def __update_by_env(self): for widget in widgets: widget.set_visible(True) - ''' + """ The following functions are used like wrappers for the runner utilities. - ''' + """ def run_winecfg(self, widget): program = WineCfg(self.config) @@ -598,7 +634,7 @@ def handle_response(_widget, response_id): dialog = Adw.MessageDialog.new( self.window, _("Are you sure you want to force stop all processes?"), - _("This can cause data loss, corruption, and programs to malfunction.") + _("This can cause data loss, corruption, and programs to malfunction."), ) dialog.add_response("cancel", _("_Cancel")) dialog.add_response("ok", _("Force _Stop")) diff --git a/bottles/frontend/views/bottle_installers.py b/bottles/frontend/views/bottle_installers.py index 4f57c5051ef..8d83ca00ab3 100644 --- a/bottles/frontend/views/bottle_installers.py +++ b/bottles/frontend/views/bottle_installers.py @@ -26,9 +26,9 @@ from bottles.frontend.widgets.installer import InstallerEntry -@Gtk.Template(resource_path='/com/usebottles/bottles/details-installers.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/details-installers.ui") class InstallersView(Adw.Bin): - __gtype_name__ = 'DetailsInstallers' + __gtype_name__ = "DetailsInstallers" __registry = [] # region Widgets @@ -57,7 +57,7 @@ def __init__(self, details, config, **kwargs): self.search_bar.set_key_capture_widget(self.window) self.btn_help.connect("clicked", open_doc_url, "bottles/installers") - self.entry_search.connect('changed', self.__search_installers) + self.entry_search.connect("changed", self.__search_installers) def __search_installers(self, *_args): """ @@ -65,10 +65,7 @@ def __search_installers(self, *_args): text written in the search entry. """ terms = self.entry_search.get_text() - self.list_installers.set_filter_func( - self.__filter_installers, - terms - ) + self.list_installers.set_filter_func(self.__filter_installers, terms) @staticmethod def __filter_installers(row, terms=None): @@ -97,9 +94,7 @@ def update(self, widget=False, config=None): def new_installer(_installer): entry = InstallerEntry( - window=self.window, - config=self.config, - installer=_installer + window=self.window, config=self.config, installer=_installer ) self.list_installers.append(entry) self.__registry.append(entry) @@ -111,7 +106,7 @@ def callback(result, error=False): self.list_installers.set_sensitive(result.status) def process_installers(): - time.sleep(.5) # workaround for freezing bug on bottle load + time.sleep(0.5) # workaround for freezing bug on bottle load GLib.idle_add(self.empty_list) if len(installers) == 0: diff --git a/bottles/frontend/views/bottle_preferences.py b/bottles/frontend/views/bottle_preferences.py index 7f5e9cb96f6..726a6aaf6c7 100644 --- a/bottles/frontend/views/bottle_preferences.py +++ b/bottles/frontend/views/bottle_preferences.py @@ -22,8 +22,15 @@ from gi.repository import Gtk, Adw -from bottles.backend.globals import gamemode_available, vkbasalt_available, mangohud_available, obs_vkc_available, \ - vmtouch_available, gamescope_available +from bottles.backend.globals import ( + gamemode_available, + vkbasalt_available, + mangohud_available, + obs_vkc_available, + vmtouch_available, + gamescope_available, + base_version, +) from bottles.backend.logger import Logger from bottles.backend.managers.library import LibraryManager from bottles.backend.managers.runtime import RuntimeManager @@ -52,9 +59,9 @@ # noinspection PyUnusedLocal -@Gtk.Template(resource_path='/com/usebottles/bottles/details-preferences.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/details-preferences.ui") class PreferencesView(Adw.PreferencesPage): - __gtype_name__ = 'DetailsPreferences' + __gtype_name__ = "DetailsPreferences" # region Widgets btn_manage_gamescope = Gtk.Template.Child() @@ -139,34 +146,40 @@ def __init__(self, details, config, **kwargs): self.btn_manage_vkbasalt.connect("clicked", self.__show_vkbasalt_settings) self.btn_manage_fsr.connect("clicked", self.__show_fsr_settings) self.btn_manage_sandbox.connect("clicked", self.__show_sandbox_settings) - self.btn_manage_versioning_patterns.connect("clicked", self.__show_exclusionpatterns_settings) + self.btn_manage_versioning_patterns.connect( + "clicked", self.__show_exclusionpatterns_settings + ) self.btn_manage_vmtouch.connect("clicked", self.__show_vmtouch_settings) 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_mangohud) - self.switch_obsvkc.connect('state-set', self.__toggle_obsvkc) - self.switch_vkbasalt.connect('state-set', self.__toggle_vkbasalt) - self.switch_fsr.connect('state-set', self.__toggle_fsr) - self.switch_nvapi.connect('state-set', self.__toggle_nvapi) + self.switch_mangohud.connect("state-set", self.__toggle_mangohud) + self.switch_obsvkc.connect("state-set", self.__toggle_obsvkc) + self.switch_vkbasalt.connect("state-set", self.__toggle_vkbasalt) + self.switch_fsr.connect("state-set", self.__toggle_fsr) + self.switch_nvapi.connect("state-set", self.__toggle_nvapi) # self.switch_latencyflex.connect('state-set', self.__toggle_latencyflex) - self.switch_gamemode.connect('state-set', self.__toggle_gamemode) - self.switch_gamescope.connect('state-set', self.__toggle_gamescope) - self.switch_sandbox.connect('state-set', self.__toggle_sandbox) - self.switch_discrete.connect('state-set', self.__toggle_discrete_gpu) - self.switch_versioning_compression.connect('state-set', self.__toggle_versioning_compression) - self.switch_auto_versioning.connect('state-set', self.__toggle_auto_versioning) - self.switch_versioning_patterns.connect('state-set', self.__toggle_versioning_patterns) - self.switch_vmtouch.connect('state-set', self.__toggle_vmtouch) - self.combo_runner.connect('notify::selected', self.__set_runner) - 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.entry_name.connect('changed', self.__check_entry_name) - self.entry_name.connect('apply', self.__save_name) + self.switch_gamemode.connect("state-set", self.__toggle_gamemode) + self.switch_gamescope.connect("state-set", self.__toggle_gamescope) + self.switch_sandbox.connect("state-set", self.__toggle_sandbox) + self.switch_discrete.connect("state-set", self.__toggle_discrete_gpu) + self.switch_versioning_compression.connect( + "state-set", self.__toggle_versioning_compression + ) + self.switch_auto_versioning.connect("state-set", self.__toggle_auto_versioning) + self.switch_versioning_patterns.connect( + "state-set", self.__toggle_versioning_patterns + ) + self.switch_vmtouch.connect("state-set", self.__toggle_vmtouch) + self.combo_runner.connect("notify::selected", self.__set_runner) + 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.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)""" @@ -177,13 +190,13 @@ def __init__(self, details, config, **kwargs): """Set Bottles Runtime row to visible when Bottles is not running inside Flatpak""" if "FLATPAK_ID" not in os.environ and RuntimeManager.get_runtimes("bottles"): self.row_runtime.set_visible(True) - self.switch_runtime.connect('state-set', self.__toggle_runtime) + self.switch_runtime.connect("state-set", self.__toggle_runtime) if RuntimeManager.get_runtimes("steam"): self.row_steam_runtime.set_visible(True) - self.switch_steam_runtime.connect('state-set', self.__toggle_steam_runtime) + self.switch_steam_runtime.connect("state-set", self.__toggle_steam_runtime) - '''Toggle some utilities according to its availability''' + """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) @@ -193,12 +206,30 @@ def __init__(self, details, config, **kwargs): self.switch_obsvkc.set_sensitive(obs_vkc_available) self.switch_vmtouch.set_sensitive(vmtouch_available) _not_available = _("This feature is unavailable on your system.") - _flatpak_not_available = _("{} To add this feature, please run flatpak install").format(_not_available) + _flatpak_not_available = _( + "{} To add this feature, please run flatpak install" + ).format(_not_available) + _gamescope_pkg_name = "org.freedesktop.Platform.VulkanLayer.gamescope" + _vkbasalt_pkg_name = "org.freedesktop.Platform.VulkanLayer.vkBasalt" + _mangohud_pkg_name = "org.freedesktop.Platform.VulkanLayer.MangoHud" + _obsvkc_pkg_name = "com.obsproject.Studio.Plugin.OBSVkCapture" _flatpak_pkg_name = { - "gamescope": "org.freedesktop.Platform.VulkanLayer.gamescope", - "vkbasalt": "org.freedesktop.Platform.VulkanLayer.vkBasalt//22.08", - "mangohud": "org.freedesktop.Platform.VulkanLayer.MangoHud//22.08", - "obsvkc": "com.obsproject.Studio.Plugin.OBSVkCapture" + "gamescope": ( + f"{_gamescope_pkg_name}//{base_version}" + if base_version + else _gamescope_pkg_name + ), + "vkbasalt": ( + f"{_vkbasalt_pkg_name}//{base_version}" + if base_version + else _vkbasalt_pkg_name + ), + "mangohud": ( + f"{_mangohud_pkg_name}//{base_version}" + if base_version + else _mangohud_pkg_name + ), + "obsvkc": _obsvkc_pkg_name, } if not gamemode_available: @@ -206,7 +237,9 @@ def __init__(self, details, config, **kwargs): if not gamescope_available: if "FLATPAK_ID" in os.environ: - _gamescope_not_available = f"{_flatpak_not_available} {_flatpak_pkg_name['gamescope']}" + _gamescope_not_available = ( + f"{_flatpak_not_available} {_flatpak_pkg_name['gamescope']}" + ) self.switch_gamescope.set_tooltip_text(_gamescope_not_available) self.btn_manage_gamescope.set_tooltip_text(_gamescope_not_available) else: @@ -215,7 +248,9 @@ def __init__(self, details, config, **kwargs): if not vkbasalt_available: if "FLATPAK_ID" in os.environ: - _vkbasalt_not_available = f"{_flatpak_not_available} {_flatpak_pkg_name['vkbasalt']}" + _vkbasalt_not_available = ( + f"{_flatpak_not_available} {_flatpak_pkg_name['vkbasalt']}" + ) self.switch_vkbasalt.set_tooltip_text(_vkbasalt_not_available) self.btn_manage_vkbasalt.set_tooltip_text(_vkbasalt_not_available) else: @@ -224,14 +259,18 @@ def __init__(self, details, config, **kwargs): if not mangohud_available: if "FLATPAK_ID" in os.environ: - _mangohud_not_available = f"{_flatpak_not_available} {_flatpak_pkg_name['mangohud']}" + _mangohud_not_available = ( + f"{_flatpak_not_available} {_flatpak_pkg_name['mangohud']}" + ) self.switch_mangohud.set_tooltip_text(_mangohud_not_available) else: self.switch_mangohud.set_tooltip_text(_not_available) if not obs_vkc_available: if "FLATPAK_ID" in os.environ: - _obsvkc_not_available = f"{_flatpak_not_available} {_flatpak_pkg_name['obsvkc']}" + _obsvkc_not_available = ( + f"{_flatpak_not_available} {_flatpak_pkg_name['obsvkc']}" + ) self.switch_obsvkc.set_tooltip_text(_obsvkc_not_available) else: self.switch_obsvkc.set_tooltip_text(_not_available) @@ -272,11 +311,7 @@ def __save_name(self, *_args): library_manager.__library = entries library_manager.save_library() - self.manager.update_config( - config=self.config, - key="Name", - value=new_name - ) + 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() @@ -290,9 +325,7 @@ def set_path(_dialog, response): path = dialog.get_file().get_path() self.manager.update_config( - config=self.config, - key="WorkingDir", - value=dialog.get_file().get_path() + config=self.config, key="WorkingDir", value=dialog.get_file().get_path() ) self.label_cwd.set_label(os.path.basename(path)) self.btn_cwd_reset.set_visible(True) @@ -300,7 +333,7 @@ def set_path(_dialog, response): dialog = Gtk.FileChooserNative.new( title=_("Select Working Directory"), action=Gtk.FileChooserAction.SELECT_FOLDER, - parent=self.window + parent=self.window, ) dialog.set_modal(True) @@ -381,9 +414,13 @@ def set_config(self, config: BottleConfig): self.switch_gamescope.handler_block_by_func(self.__toggle_gamescope) self.switch_sandbox.handler_block_by_func(self.__toggle_sandbox) self.switch_discrete.handler_block_by_func(self.__toggle_discrete_gpu) - self.switch_versioning_compression.handler_block_by_func(self.__toggle_versioning_compression) + self.switch_versioning_compression.handler_block_by_func( + self.__toggle_versioning_compression + ) self.switch_auto_versioning.handler_block_by_func(self.__toggle_auto_versioning) - self.switch_versioning_patterns.handler_block_by_func(self.__toggle_versioning_patterns) + self.switch_versioning_patterns.handler_block_by_func( + self.__toggle_versioning_patterns + ) with contextlib.suppress(TypeError): self.switch_runtime.handler_block_by_func(self.__toggle_runtime) self.switch_steam_runtime.handler_block_by_func(self.__toggle_steam_runtime) @@ -404,7 +441,9 @@ def set_config(self, config: BottleConfig): self.switch_sandbox.set_active(parameters.sandbox) self.switch_versioning_compression.set_active(parameters.versioning_compression) self.switch_auto_versioning.set_active(parameters.versioning_automatic) - self.switch_versioning_patterns.set_active(parameters.versioning_exclusion_patterns) + self.switch_versioning_patterns.set_active( + parameters.versioning_exclusion_patterns + ) self.switch_runtime.set_active(parameters.use_runtime) self.switch_steam_runtime.set_active(parameters.use_steam_runtime) self.switch_vmtouch.set_active(parameters.vmtouch) @@ -419,12 +458,13 @@ def set_config(self, config: BottleConfig): self.entry_name.set_text(config.Name) - self.row_cwd.set_subtitle(_("Directory that contains the data of \"{}\".".format(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 - )) + 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 @@ -438,7 +478,7 @@ def set_config(self, config: BottleConfig): "win2008r2": "Windows 2008 R2", "win2008": "Windows 2008", # "vista": "Windows Vista", # TODO: implement this in the backend - "winxp": "Windows XP" + "winxp": "Windows XP", } if self.config.Arch == Arch.WIN32: @@ -477,7 +517,12 @@ def set_config(self, config: BottleConfig): _latencyflex = self.config.LatencyFleX if parameters.latencyflex: if _latencyflex in self.manager.latencyflex_available: - if _i_latencyflex := self.manager.latencyflex_available.index(_latencyflex) + 1: + if ( + _i_latencyflex := self.manager.latencyflex_available.index( + _latencyflex + ) + + 1 + ): self.combo_latencyflex.set_selected(_i_latencyflex) else: self.combo_latencyflex.set_selected(0) @@ -506,12 +551,20 @@ def set_config(self, config: BottleConfig): self.switch_gamescope.handler_unblock_by_func(self.__toggle_gamescope) self.switch_sandbox.handler_unblock_by_func(self.__toggle_sandbox) self.switch_discrete.handler_unblock_by_func(self.__toggle_discrete_gpu) - self.switch_versioning_compression.handler_unblock_by_func(self.__toggle_versioning_compression) - self.switch_auto_versioning.handler_unblock_by_func(self.__toggle_auto_versioning) - self.switch_versioning_patterns.handler_unblock_by_func(self.__toggle_versioning_patterns) + self.switch_versioning_compression.handler_unblock_by_func( + self.__toggle_versioning_compression + ) + self.switch_auto_versioning.handler_unblock_by_func( + self.__toggle_auto_versioning + ) + self.switch_versioning_patterns.handler_unblock_by_func( + self.__toggle_versioning_patterns + ) with contextlib.suppress(TypeError): self.switch_runtime.handler_unblock_by_func(self.__toggle_runtime) - self.switch_steam_runtime.handler_unblock_by_func(self.__toggle_steam_runtime) + self.switch_steam_runtime.handler_unblock_by_func( + self.__toggle_steam_runtime + ) self.combo_runner.handler_unblock_by_func(self.__set_runner) self.combo_dxvk.handler_unblock_by_func(self.__set_dxvk) self.combo_vkd3d.handler_unblock_by_func(self.__set_vkd3d) @@ -523,24 +576,15 @@ def set_config(self, config: BottleConfig): self.__set_steam_rules() def __show_gamescope_settings(self, widget): - new_window = GamescopeDialog( - window=self.window, - config=self.config - ) + new_window = GamescopeDialog(window=self.window, config=self.config) new_window.present() def __show_vkbasalt_settings(self, widget): - new_window = VkBasaltDialog( - parent_window=self.window, - config=self.config - ) + new_window = VkBasaltDialog(parent_window=self.window, config=self.config) new_window.present() def __show_fsr_settings(self, widget): - new_window = FsrDialog( - parent_window=self.window, - config=self.config - ) + new_window = FsrDialog(parent_window=self.window, config=self.config) new_window.present() def __show_display_settings(self, widget): @@ -550,44 +594,29 @@ def __show_display_settings(self, widget): details=self.details, queue=self.queue, widget=widget, - spinner_display=self.spinner_display + spinner_display=self.spinner_display, ) new_window.present() def __show_exclusionpatterns_settings(self, widget): - new_window = ExclusionPatternsDialog( - window=self.window, - config=self.config - ) + new_window = ExclusionPatternsDialog(window=self.window, config=self.config) new_window.present() def __show_sandbox_settings(self, widget): - new_window = SandboxDialog( - window=self.window, - config=self.config - ) + new_window = SandboxDialog(window=self.window, config=self.config) new_window.present() def __show_drives(self, widget): - new_window = DrivesDialog( - window=self.window, - config=self.config - ) + new_window = DrivesDialog(window=self.window, config=self.config) new_window.present() def __show_environment_variables(self, widget=False): """Show the environment variables dialog""" - new_window = EnvVarsDialog( - window=self.window, - config=self.config - ) + new_window = EnvVarsDialog(window=self.window, config=self.config) new_window.present() def __show_vmtouch_settings(self, widget): - new_window = VmtouchDialog( - window=self.window, - config=self.config - ) + new_window = VmtouchDialog(window=self.window, config=self.config) new_window.present() def __set_sync_type(self, *_args): @@ -606,7 +635,7 @@ def __set_sync_type(self, *_args): config=self.config, key="sync", value=sync_types[self.combo_sync.get_selected()], - scope="Parameters" + scope="Parameters", ) self.combo_sync.set_sensitive(True) self.queue.end_task() @@ -614,37 +643,25 @@ def __set_sync_type(self, *_args): def __toggle_mangohud(self, widget, state): """Toggle the Mangohud for current bottle""" self.config = self.manager.update_config( - config=self.config, - key="mangohud", - value=state, - scope="Parameters" + config=self.config, key="mangohud", value=state, scope="Parameters" ).data["config"] def __toggle_obsvkc(self, widget, state): """Toggle the OBS Vulkan capture for current bottle""" self.config = self.manager.update_config( - config=self.config, - key="obsvkc", - value=state, - scope="Parameters" + config=self.config, key="obsvkc", value=state, scope="Parameters" ).data["config"] def __toggle_vkbasalt(self, widget, state): """Toggle the vkBasalt for current bottle""" self.config = self.manager.update_config( - config=self.config, - key="vkbasalt", - value=state, - scope="Parameters" + config=self.config, key="vkbasalt", value=state, scope="Parameters" ).data["config"] def __toggle_fsr(self, widget, state): """Toggle the FSR for current bottle""" self.config = self.manager.update_config( - config=self.config, - key="fsr", - value=state, - scope="Parameters" + config=self.config, key="fsr", value=state, scope="Parameters" ).data["config"] def __toggle_nvapi(self, widget=False, state=False): @@ -657,68 +674,47 @@ def __toggle_nvapi(self, widget=False, state=False): callback=self.set_nvapi_status, config=self.config, component="nvapi", - remove=not state + remove=not state, ) self.config = self.manager.update_config( - config=self.config, - key="dxvk_nvapi", - value=state, - scope="Parameters" + config=self.config, key="dxvk_nvapi", value=state, scope="Parameters" ).data["config"] def __toggle_gamemode(self, widget=False, state=False): """Toggle the gamemode for current bottle""" self.config = self.manager.update_config( - config=self.config, - key="gamemode", - value=state, - scope="Parameters" + config=self.config, key="gamemode", value=state, scope="Parameters" ).data["config"] def __toggle_gamescope(self, widget=False, state=False): """Toggle the gamescope for current bottle""" self.config = self.manager.update_config( - config=self.config, - key="gamescope", - value=state, - scope="Parameters" + config=self.config, key="gamescope", value=state, scope="Parameters" ).data["config"] def __toggle_sandbox(self, widget=False, state=False): """Toggle the sandbox for current bottle""" self.config = self.manager.update_config( - config=self.config, - key="sandbox", - value=state, - scope="Parameters" + config=self.config, key="sandbox", value=state, scope="Parameters" ).data["config"] def __toggle_runtime(self, widget, state): """Toggle the Bottles runtime for current bottle""" self.config = self.manager.update_config( - config=self.config, - key="use_runtime", - value=state, - scope="Parameters" + config=self.config, key="use_runtime", value=state, scope="Parameters" ).data["config"] def __toggle_steam_runtime(self, widget, state): """Toggle the Steam runtime for current bottle""" self.config = self.manager.update_config( - config=self.config, - key="use_steam_runtime", - value=state, - scope="Parameters" + config=self.config, key="use_steam_runtime", value=state, scope="Parameters" ).data["config"] def __toggle_discrete_gpu(self, widget, state): """Toggle the discrete GPU for current bottle""" self.config = self.manager.update_config( - config=self.config, - key="discrete_gpu", - value=state, - scope="Parameters" + config=self.config, key="discrete_gpu", value=state, scope="Parameters" ).data["config"] def __toggle_versioning_compression(self, widget, state): @@ -729,12 +725,14 @@ def update(): config=self.config, key="versioning_compression", value=state, - scope="Parameters" + scope="Parameters", ).data["config"] def handle_response(_widget, response_id): if response_id == "ok": - RunAsync(self.manager.versioning_manager.re_initialize, config=self.config) + RunAsync( + self.manager.versioning_manager.re_initialize, config=self.config + ) _widget.destroy() if self.manager.versioning_manager.is_initialized(self.config): @@ -757,7 +755,7 @@ def __toggle_auto_versioning(self, widget, state): config=self.config, key="versioning_automatic", value=state, - scope="Parameters" + scope="Parameters", ).data["config"] def __toggle_versioning_patterns(self, widget, state): @@ -766,16 +764,13 @@ def __toggle_versioning_patterns(self, widget, state): config=self.config, key="versioning_exclusion_patterns", value=state, - scope="Parameters" + scope="Parameters", ).data["config"] def __toggle_vmtouch(self, widget=False, state=False): """Toggle vmtouch for current bottle""" self.config = self.manager.update_config( - config=self.config, - key="vmtouch", - value=state, - scope="Parameters" + config=self.config, key="vmtouch", value=state, scope="Parameters" ).data["config"] def __set_runner(self, *_args): @@ -787,7 +782,7 @@ def set_widgets_status(status=True): self.switch_nvapi, self.combo_dxvk, self.combo_nvapi, - self.combo_vkd3d + self.combo_vkd3d, ]: w.set_sensitive(status) if status: @@ -799,15 +794,21 @@ def set_widgets_status(status=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"] + 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"] if self.config.Parameters.use_steam_runtime: - self.switch_steam_runtime.handler_block_by_func(self.__toggle_steam_runtime) + self.switch_steam_runtime.handler_block_by_func( + self.__toggle_steam_runtime + ) self.switch_steam_runtime.set_active(True) - self.switch_steam_runtime.handler_unblock_by_func(self.__toggle_steam_runtime) + self.switch_steam_runtime.handler_unblock_by_func( + self.__toggle_steam_runtime + ) set_widgets_status(True) self.queue.end_task() @@ -828,7 +829,7 @@ def run_task(status=True): callback=update, config=self.config, manager=self.manager, - runner=runner + runner=runner, ) if re.search("^(GE-)?Proton", runner): @@ -839,9 +840,13 @@ def run_task(status=True): def __dll_component_task_func(self, *args, **kwargs): # Remove old version - self.manager.install_dll_component(config=kwargs["config"], component=kwargs["component"], remove=True) + 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"]) + self.manager.install_dll_component( + config=kwargs["config"], component=kwargs["component"] + ) def __set_dxvk(self, *_args): """Set the DXVK version to use for the bottle""" @@ -860,35 +865,27 @@ def __set_dxvk(self, *_args): callback=self.set_dxvk_status, config=self.config, component="dxvk", - remove=True + remove=True, ) self.config = self.manager.update_config( - config=self.config, - key="dxvk", - value=False, - scope="Parameters" + 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 + 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" + component="dxvk", ) self.config = self.manager.update_config( - config=self.config, - key="dxvk", - value=True, - scope="Parameters" + config=self.config, key="dxvk", value=True, scope="Parameters" ).data["config"] def __set_vkd3d(self, *_args): @@ -904,14 +901,11 @@ def __set_vkd3d(self, *_args): callback=self.set_vkd3d_status, config=self.config, component="vkd3d", - remove=True + remove=True, ) self.config = self.manager.update_config( - config=self.config, - key="vkd3d", - value=False, - scope="Parameters" + config=self.config, key="vkd3d", value=False, scope="Parameters" ).data["config"] else: if self.combo_dxvk.get_selected() == 0: @@ -920,23 +914,18 @@ def __set_vkd3d(self, *_args): vkd3d = self.manager.vkd3d_available[self.combo_vkd3d.get_selected() - 1] self.config = self.manager.update_config( - config=self.config, - key="VKD3D", - value=vkd3d + 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" + component="vkd3d", ) self.config = self.manager.update_config( - config=self.config, - key="vkd3d", - value=True, - scope="Parameters" + config=self.config, key="vkd3d", value=True, scope="Parameters" ).data["config"] def __set_nvapi(self, *_args): @@ -948,23 +937,18 @@ def __set_nvapi(self, *_args): nvapi = self.manager.nvapi_available[self.combo_nvapi.get_selected()] self.config = self.manager.update_config( - config=self.config, - key="NVAPI", - value=nvapi + 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" + component="nvapi", ) self.config = self.manager.update_config( - config=self.config, - key="dxvk_nvapi", - value=True, - scope="Parameters" + config=self.config, key="dxvk_nvapi", value=True, scope="Parameters" ).data["config"] def __set_latencyflex(self, *_args): @@ -976,34 +960,28 @@ def __set_latencyflex(self, *_args): callback=self.set_latencyflex_status, config=self.config, component="latencyflex", - remove=True + remove=True, ) self.config = self.manager.update_config( - config=self.config, - key="latencyflex", - value=False, - scope="Parameters" + config=self.config, key="latencyflex", value=False, scope="Parameters" ).data["config"] else: - latencyflex = self.manager.latencyflex_available[self.combo_latencyflex.get_selected() - 1] + latencyflex = self.manager.latencyflex_available[ + self.combo_latencyflex.get_selected() - 1 + ] self.config = self.manager.update_config( - config=self.config, - key="LatencyFleX", - value=latencyflex + 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" + component="latencyflex", ) self.config = self.manager.update_config( - config=self.config, - key="latencyflex", - value=True, - scope="Parameters" + config=self.config, key="latencyflex", value=True, scope="Parameters" ).data["config"] def __set_windows(self, *_args): @@ -1026,16 +1004,10 @@ def update(result, error=False): 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 + config=self.config, key="Windows", value=windows_version ).data["config"] - RunAsync( - rk.set_windows, - callback=update, - version=windows_version - ) + RunAsync(rk.set_windows, callback=update, version=windows_version) break def __set_language(self, *_args): @@ -1050,10 +1022,7 @@ def __set_language(self, *_args): def __show_dll_overrides_view(self, widget=False): """Show the DLL overrides view""" - new_window = DLLOverridesDialog( - window=self.window, - config=self.config - ) + new_window = DLLOverridesDialog(window=self.window, config=self.config) new_window.present() @GtkUtils.run_in_main_loop @@ -1122,4 +1091,6 @@ def __set_steam_rules(self): w.set_visible(status) w.set_sensitive(status) - self.row_sandbox.set_visible(self.window.settings.get_boolean("experiments-sandbox")) + self.row_sandbox.set_visible( + self.window.settings.get_boolean("experiments-sandbox") + ) diff --git a/bottles/frontend/views/bottle_taskmanager.py b/bottles/frontend/views/bottle_taskmanager.py index 105d138b7ba..012ab11c6ba 100644 --- a/bottles/frontend/views/bottle_taskmanager.py +++ b/bottles/frontend/views/bottle_taskmanager.py @@ -26,9 +26,9 @@ from bottles.frontend.utils.gtk import GtkUtils -@Gtk.Template(resource_path='/com/usebottles/bottles/details-taskmanager.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/details-taskmanager.ui") class TaskManagerView(Gtk.ScrolledWindow): - __gtype_name__ = 'TaskManagerView' + __gtype_name__ = "TaskManagerView" # region Widgets treeview_processes = Gtk.Template.Child() @@ -63,9 +63,9 @@ def __init__(self, details, config, **kwargs): "Threads", # "Parent" ]: - ''' + """ For each column, add it to the treeview_processes - ''' + """ column = Gtk.TreeViewColumn(column, cell_renderer, text=i) self.treeview_processes.append_column(column) i += 1 @@ -109,18 +109,16 @@ def fetch_processes(config: Optional[BottleConfig] = None): def update_processes(processes: list, *_args): if len(processes) > 0: for process in processes: - self.liststore_processes.append([ - process.get("pid"), - process.get("name", "n/a"), - process.get("threads", "0"), - # process.get("parent", "0") - ]) + self.liststore_processes.append( + [ + process.get("pid"), + process.get("name", "n/a"), + process.get("threads", "0"), + # process.get("parent", "0") + ] + ) - RunAsync( - task_func=fetch_processes, - callback=update_processes, - config=config - ) + RunAsync(task_func=fetch_processes, callback=update_processes, config=config) def sensitive_update(self, widget): @GtkUtils.run_in_main_loop @@ -129,10 +127,7 @@ def reset(result, error): self.btn_update.set_sensitive(False) RunAsync( - task_func=self.update, - callback=reset, - widget=False, - config=self.config + task_func=self.update, callback=reset, widget=False, config=self.config ) def kill_process(self, widget): @@ -152,15 +147,7 @@ def reset(result, error): self.liststore_processes.remove(treeiter) if winebridge.is_available(): - RunAsync( - task_func=winebridge.kill_proc, - callback=reset, - pid=pid - ) + RunAsync(task_func=winebridge.kill_proc, callback=reset, pid=pid) else: winedbg = WineDbg(self.config) - RunAsync( - task_func=winedbg.kill_process, - callback=reset, - pid=pid - ) + RunAsync(task_func=winedbg.kill_process, callback=reset, pid=pid) diff --git a/bottles/frontend/views/bottle_versioning.py b/bottles/frontend/views/bottle_versioning.py index c361f15d577..4c2539a1760 100644 --- a/bottles/frontend/views/bottle_versioning.py +++ b/bottles/frontend/views/bottle_versioning.py @@ -27,9 +27,9 @@ from bottles.frontend.widgets.state import StateEntry -@Gtk.Template(resource_path='/com/usebottles/bottles/details-versioning.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/details-versioning.ui") class VersioningView(Adw.PreferencesPage): - __gtype_name__ = 'DetailsVersioning' + __gtype_name__ = "DetailsVersioning" __registry = [] # region Widgets @@ -79,22 +79,21 @@ def update(self, widget=None, config=None, states=None, active=0): if states is None: states = self.versioning_manager.list_states(config) if not config.Versioning: - active = states.data.get('state_id') - states = states.data.get('states') + active = states.data.get("state_id") + states = states.data.get("states") self.config = config self.list_states.set_sensitive(False) if self.config.Versioning: self.btn_add.set_sensitive(False) - self.btn_add.set_tooltip_text(_("Please migrate to the new Versioning system to create new states.")) + self.btn_add.set_tooltip_text( + _("Please migrate to the new Versioning system to create new states.") + ) def new_state(_state, active): entry = StateEntry( - parent=self, - config=self.config, - state=_state, - active=active + parent=self, config=self.config, state=_state, active=active ) self.__registry.append(entry) self.list_states.append(entry) @@ -131,7 +130,7 @@ def check_entry_state_message(self, *_args): self.btn_save.set_sensitive(check) self.entry_state_message.set_icon_from_icon_name( - 1, '' if check else 'dialog-warning-symbolic"' + 1, "" if check else 'dialog-warning-symbolic"' ) def add_state(self, widget): @@ -146,7 +145,9 @@ def add_state(self, widget): def update(result, error): self.window.show_toast(result.message) if result.ok: - self.update(states=result.data.get('states'), active=result.data.get('state_id')) + self.update( + states=result.data.get("states"), active=result.data.get("state_id") + ) message = self.entry_state_message.get_text() if message != "": @@ -154,7 +155,7 @@ def update(result, error): task_func=self.versioning_manager.create_state, callback=update, config=self.config, - message=message + message=message, ) self.entry_state_message.set_text("") self.pop_state.popdown() diff --git a/bottles/frontend/views/details.py b/bottles/frontend/views/details.py index 0a2cfaa736e..b2bf6091c85 100644 --- a/bottles/frontend/views/details.py +++ b/bottles/frontend/views/details.py @@ -34,14 +34,14 @@ from bottles.frontend.views.bottle_taskmanager import TaskManagerView -@Gtk.Template(resource_path='/com/usebottles/bottles/details.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/details.ui") class DetailsView(Adw.Bin): """ This class is the starting point for all the pages concerning the bottle (details, preferences, dependencies ..). """ - __gtype_name__ = 'Details' + __gtype_name__ = "Details" __pages = {} # region Widgets @@ -84,14 +84,14 @@ def __init__(self, window, config: Optional[BottleConfig] = None, **kwargs): self.btn_back.connect("clicked", self.go_back) self.btn_back_sidebar.connect("clicked", self.go_back_sidebar) - self.window.main_leaf.connect('notify::visible-child', self.unload_view) + self.window.main_leaf.connect("notify::visible-child", self.unload_view) self.default_actions.append(self.view_bottle.actions) # region signals - self.stack_bottle.connect('notify::visible-child', self.__on_page_change) - self.btn_operations.connect('activate', self.__on_operations_toggled) - self.btn_operations.connect('notify::visible', self.__spin_tasks_toggle) - self.leaflet.connect('notify::folded', self.__on_leaflet_folded) + self.stack_bottle.connect("notify::visible-child", self.__on_page_change) + self.btn_operations.connect("activate", self.__on_operations_toggled) + self.btn_operations.connect("notify::visible", self.__spin_tasks_toggle) + self.leaflet.connect("notify::folded", self.__on_leaflet_folded) # endregion RunAsync(self.build_pages) @@ -117,7 +117,7 @@ def __on_page_change(self, *_args): self.window.toggle_selection_mode(False) page = self.stack_bottle.get_visible_child_name() - self.set_title(self.__pages[page]['title'], self.__pages[page]['description']) + self.set_title(self.__pages[page]["title"], self.__pages[page]["description"]) if page == "dependencies": self.set_actions(self.view_dependencies.actions) self.view_dependencies.update(config=self.config) @@ -156,7 +156,7 @@ def build_pages(self): "taskmanager": { "title": _("Task Manager"), "description": "", - } + }, } if self.config.Environment == "Steam": @@ -179,8 +179,8 @@ def ui_update(): def set_actions(self, widget: Gtk.Widget = None): """ - This function is used to set the actions buttons in the headerbar. - """ + This function is used to set the actions buttons in the headerbar. + """ while self.box_actions.get_first_child(): self.box_actions.remove(self.box_actions.get_first_child()) diff --git a/bottles/frontend/views/importer.py b/bottles/frontend/views/importer.py index 3ccd04f6fe5..4e026cd9986 100644 --- a/bottles/frontend/views/importer.py +++ b/bottles/frontend/views/importer.py @@ -26,9 +26,9 @@ from bottles.frontend.widgets.importer import ImporterEntry -@Gtk.Template(resource_path='/com/usebottles/bottles/importer.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/importer.ui") class ImporterView(Adw.Bin): - __gtype_name__ = 'ImporterView' + __gtype_name__ = "ImporterView" # region Widgets list_prefixes = Gtk.Template.Child() @@ -81,10 +81,7 @@ def update(result, error=False): widget.set_sensitive(False) - RunAsync( - self.import_manager.search_wineprefixes, - callback=update - ) + RunAsync(self.import_manager.search_wineprefixes, callback=update) @GtkUtils.run_in_main_loop def __finish(self, result, error=False): @@ -117,7 +114,7 @@ def set_path(_dialog, response): title=_("Select a Backup Archive"), action=Gtk.FileChooserAction.OPEN, parent=self.window, - accept_label=_("Import") + accept_label=_("Import"), ) filter = Gtk.FileFilter() @@ -154,7 +151,7 @@ def set_path(_dialog, response): title=_("Select a Configuration File"), action=Gtk.FileChooserAction.OPEN, parent=self.window, - accept_label=_("Import") + accept_label=_("Import"), ) add_yaml_filters(dialog) diff --git a/bottles/frontend/views/library.py b/bottles/frontend/views/library.py index 9f96eadc48d..1013e8c969b 100644 --- a/bottles/frontend/views/library.py +++ b/bottles/frontend/views/library.py @@ -25,9 +25,9 @@ from bottles.frontend.widgets.library import LibraryEntry -@Gtk.Template(resource_path='/com/usebottles/bottles/library.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/library.ui") class LibraryView(Adw.Bin): - __gtype_name__ = 'LibraryView' + __gtype_name__ = "LibraryView" # region Widgets scroll_window = Gtk.Template.Child() @@ -75,11 +75,11 @@ def dismissed_callback(*args): entry.hide() self.items_per_line -= 1 self.window.show_toast( - message=_("\"{0}\" removed from the library.").format(entry.name), + message=_('"{0}" removed from the library.').format(entry.name), timeout=5, action_label=_("Undo"), action_callback=undo_callback, - dismissed_callback=dismissed_callback + dismissed_callback=dismissed_callback, ) def __delete_entry(self, entry): diff --git a/bottles/frontend/views/list.py b/bottles/frontend/views/list.py index 7c91f89f4f6..9ddbdfa384c 100644 --- a/bottles/frontend/views/list.py +++ b/bottles/frontend/views/list.py @@ -28,9 +28,9 @@ from bottles.frontend.utils.filters import add_executable_filters, add_all_filters -@Gtk.Template(resource_path='/com/usebottles/bottles/list-entry.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/list-entry.ui") class BottleViewEntry(Adw.ActionRow): - __gtype_name__ = 'BottleViewEntry' + __gtype_name__ = "BottleViewEntry" Adw.init() @@ -56,23 +56,25 @@ def __init__(self, window, config: BottleConfig, **kwargs): self.config = config self.label_env_context = self.label_env.get_style_context() - '''Format update date''' + """Format update date""" update_date = _("N/A") if self.config.Update_Date: try: - temp_date = datetime.strptime(self.config.Update_Date, "%Y-%m-%d %H:%M:%S.%f") + temp_date = datetime.strptime( + self.config.Update_Date, "%Y-%m-%d %H:%M:%S.%f" + ) update_date = temp_date.strftime("%d %B, %Y %H:%M:%S") except ValueError: update_date = _("N/A") - '''Check runner type by name''' + """Check runner type by name""" if self.config.Runner.startswith("lutris"): self.runner_type = "wine" else: self.runner_type = "proton" # connect signals - activate_handler = self.connect('activated', self.show_details) + activate_handler = self.connect("activated", self.show_details) self.btn_run.connect("clicked", self.run_executable) self.btn_repair.connect("clicked", self.repair) self.btn_run_executable.connect("clicked", self.run_executable) @@ -84,13 +86,12 @@ def __init__(self, window, config: BottleConfig, **kwargs): if self.window.settings.get_boolean("update-date"): self.set_subtitle(update_date) self.label_env.set_text(_(self.config.Environment)) - self.label_env_context.add_class( - "tag-%s" % self.config.Environment.lower()) + self.label_env_context.add_class("tag-%s" % self.config.Environment.lower()) # Set tooltip text - self.btn_run.set_tooltip_text(_(f"Run executable in \"{self.config.Name}\"")) + self.btn_run.set_tooltip_text(_(f'Run executable in "{self.config.Name}"')) - '''If config is broken''' + """If config is broken""" if self.config.get("Broken"): for w in [self.btn_repair, self.icon_damaged]: w.set_visible(True) @@ -99,26 +100,24 @@ def __init__(self, window, config: BottleConfig, **kwargs): self.btn_run.set_sensitive(False) self.handler_block_by_func(self.show_details) - '''Repair bottle''' + """Repair bottle""" def repair(self, widget): self.disable() - RunAsync( - task_func=self.manager.repair_bottle, - config=self.config - ) + RunAsync(task_func=self.manager.repair_bottle, config=self.config) - '''Display file dialog for executable''' + """Display file dialog for executable""" def run_executable(self, *_args): def set_path(_dialog, response): if response != Gtk.ResponseType.ACCEPT: return - self.window.show_toast(_("Launching \"{0}\" in \"{1}\"…").format( - dialog.get_file().get_basename(), - self.config.Name) + self.window.show_toast( + _('Launching "{0}" in "{1}"…').format( + dialog.get_file().get_basename(), self.config.Name ) + ) path = dialog.get_file().get_path() _executor = WineExecutor(self.config, exec_path=path) @@ -128,7 +127,7 @@ def set_path(_dialog, response): title=_("Select Executable"), action=Gtk.FileChooserAction.OPEN, parent=self.window, - accept_label=_("Run") + accept_label=_("Run"), ) add_executable_filters(dialog) @@ -148,9 +147,9 @@ def disable(self): self.set_visible(False) -@Gtk.Template(resource_path='/com/usebottles/bottles/list.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/list.ui") class BottleView(Adw.Bin): - __gtype_name__ = 'BottleView' + __gtype_name__ = "BottleView" __bottles = {} # region Widgets @@ -176,10 +175,12 @@ def __init__(self, window, arg_bottle=None, **kwargs): # connect signals self.btn_create.connect("clicked", self.window.show_add_view) - self.entry_search.connect('changed', self.__search_bottles) + self.entry_search.connect("changed", self.__search_bottles) # backend signals - SignalManager.connect(Signals.ManagerLocalBottlesLoaded, self.backend_local_bottle_loaded) + SignalManager.connect( + Signals.ManagerLocalBottlesLoaded, self.backend_local_bottle_loaded + ) self.update_bottles() @@ -189,10 +190,7 @@ def __search_bottles(self, widget, event=None, data=None): text written in the search entry. """ terms = widget.get_text() - self.list_bottles.set_filter_func( - self.__filter_bottles, - terms - ) + self.list_bottles.set_filter_func(self.__filter_bottles, terms) @staticmethod def __filter_bottles(row, terms=None): @@ -234,8 +232,9 @@ def idle_update_bottles(self, show=False): self.group_steam.set_visible(True) self.group_bottles.set_title(_("Your Bottles")) - if (self.arg_bottle is not None and self.arg_bottle in local_bottles.keys()) \ - or (show is not None and show in local_bottles.keys()): + if ( + self.arg_bottle is not None and self.arg_bottle in local_bottles.keys() + ) or (show is not None and show in local_bottles.keys()): _config = None if self.arg_bottle: _config = local_bottles[self.arg_bottle] diff --git a/bottles/frontend/views/loading.py b/bottles/frontend/views/loading.py index 44418a66ea6..3bed43a015c 100644 --- a/bottles/frontend/views/loading.py +++ b/bottles/frontend/views/loading.py @@ -24,9 +24,9 @@ from bottles.frontend.utils.gtk import GtkUtils -@Gtk.Template(resource_path='/com/usebottles/bottles/loading.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/loading.ui") class LoadingView(Adw.Bin): - __gtype_name__ = 'LoadingView' + __gtype_name__ = "LoadingView" __fetched = 0 # region widgets @@ -34,8 +34,8 @@ class LoadingView(Adw.Bin): label_downloading = Gtk.Template.Child() btn_go_offline = Gtk.Template.Child() # endregion - - def __init__(self,**kwargs): + + def __init__(self, **kwargs): super().__init__(**kwargs) self.btn_go_offline.connect("clicked", self.go_offline) @@ -43,8 +43,12 @@ def __init__(self,**kwargs): def add_fetched(self, res: Result): total: int = res.data self.__fetched += 1 - self.label_downloading.set_text(_("Downloading ~{0} of packages…").format("20kb")) - self.label_fetched.set_text(_("Fetched {0} of {1} packages").format(self.__fetched, total)) + self.label_downloading.set_text( + _("Downloading ~{0} of packages…").format("20kb") + ) + self.label_fetched.set_text( + _("Fetched {0} of {1} packages").format(self.__fetched, total) + ) def go_offline(self, _widget): - SignalManager.send(Signals.ForceStopNetworking, Result(status=True)) \ No newline at end of file + SignalManager.send(Signals.ForceStopNetworking, Result(status=True)) diff --git a/bottles/frontend/views/new.py b/bottles/frontend/views/new.py index 3434e52010c..31990e0767c 100644 --- a/bottles/frontend/views/new.py +++ b/bottles/frontend/views/new.py @@ -77,10 +77,7 @@ def __init__(self, window, **kwargs): self.runner = None self.default_string = _("(Default)") - self.arch = { - "win64": "64-bit", - "win32": "32-bit" - } + self.arch = {"win64": "64-bit", "win32": "32-bit"} # connect signals self.check_custom.connect("toggled", self.__set_group) @@ -106,7 +103,7 @@ def __init__(self, window, **kwargs): self.entry_name.grab_focus() def __set_group(self, *_args) -> None: - """ Checks the state of combo_environment and updates group_custom accordingly. """ + """Checks the state of combo_environment and updates group_custom accordingly.""" self.group_custom.set_sensitive(self.check_custom.get_active()) def __check_entry_name(self, *_args) -> None: @@ -125,6 +122,7 @@ def __choose_env_recipe(self, *_args) -> None: Opens a file chooser dialog to select the configuration file in yaml format. """ + def set_path(_dialog, response: Gtk.ResponseType): if response == Gtk.ResponseType.ACCEPT: self.btn_choose_env_reset.set_visible(True) @@ -145,7 +143,8 @@ def set_path(_dialog, response: Gtk.ResponseType): dialog.show() def __choose_path(self, *_args) -> None: - """ Opens a file chooser dialog to select the directory. """ + """Opens a file chooser dialog to select the directory.""" + def set_path(_dialog, response: Gtk.ResponseType) -> None: if response == Gtk.ResponseType.ACCEPT: self.btn_choose_path_reset.set_visible(True) @@ -156,7 +155,7 @@ def set_path(_dialog, response: Gtk.ResponseType) -> None: dialog = Gtk.FileChooserNative.new( title=_("Select Bottle Directory"), action=Gtk.FileChooserAction.SELECT_FOLDER, - parent=self.window + parent=self.window, ) dialog.set_modal(True) @@ -164,7 +163,7 @@ def set_path(_dialog, response: Gtk.ResponseType) -> None: dialog.show() def create_bottle(self, *_args) -> None: - """ Starts creating the bottle. """ + """Starts creating the bottle.""" # set widgets states self.is_closable = False self.btn_cancel.set_visible(False) @@ -177,7 +176,9 @@ def create_bottle(self, *_args) -> None: self.status_statuses.set_description(_("This could take a while.")) if self.check_custom.get_active(): - self.runner = self.manager.runners_available[self.combo_runner.get_selected()] + self.runner = self.manager.runners_available[ + self.combo_runner.get_selected() + ] RunAsync( task_func=self.manager.create_bottle, @@ -190,7 +191,7 @@ def create_bottle(self, *_args) -> None: dxvk=self.manager.dxvk_available[0], sandbox=self.switch_sandbox.get_state(), fn_logger=self.update_output, - custom_environment=self.env_recipe_path + custom_environment=self.env_recipe_path, ) @GtkUtils.run_in_main_loop @@ -205,10 +206,10 @@ def update_output(self, text: str) -> None: @GtkUtils.run_in_main_loop def finish(self, result, error=None) -> None: - """ Updates widgets based on whether it succeeded or failed. """ + """Updates widgets based on whether it succeeded or failed.""" def send_notification(notification: Gio.Notification) -> None: - """ Sends notification if out of focus. """ + """Sends notification if out of focus.""" if not self.is_active(): self.app.send_notification(None, notification) @@ -230,9 +231,9 @@ def send_notification(notification: Gio.Notification) -> None: # Show success title = _("Bottle Created") - description = _("\"{0}\" was created successfully.").format( - self.entry_name.get_text() - ) + description = _('"{0}" was created successfully.').format( + self.entry_name.get_text() + ) notification.set_title(title) notification.set_body(description) @@ -271,7 +272,7 @@ def __reset_path(self, _widget: Gtk.Button) -> None: self.label_choose_path.set_label(self.default_string) def do_close_request(self, *_args) -> bool: - """ Close window if a new bottle is not being created """ + """Close window if a new bottle is not being created""" if self.is_closable is False: # TODO: Implement AdwMessageDialog to prompt the user if they are # SURE they want to cancel creation. For now, the window will not diff --git a/bottles/frontend/views/preferences.py b/bottles/frontend/views/preferences.py index 01142ee43fc..ce7ec958054 100644 --- a/bottles/frontend/views/preferences.py +++ b/bottles/frontend/views/preferences.py @@ -29,9 +29,9 @@ from bottles.frontend.widgets.component import ComponentEntry, ComponentExpander -@Gtk.Template(resource_path='/com/usebottles/bottles/preferences.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/preferences.ui") class PreferencesWindow(Adw.PreferencesWindow): - __gtype_name__ = 'PreferencesWindow' + __gtype_name__ = "PreferencesWindow" __registry = [] # region Widgets @@ -86,23 +86,79 @@ def __init__(self, window, **kwargs): self.current_bottles_path = self.data.get(UserDataKeys.CustomBottlesPath) if self.current_bottles_path: - self.label_bottles_path.set_label(os.path.basename(self.current_bottles_path)) + self.label_bottles_path.set_label( + os.path.basename(self.current_bottles_path) + ) self.btn_bottles_path_reset.set_visible(True) # bind widgets - self.settings.bind("dark-theme", self.switch_theme, "active", Gio.SettingsBindFlags.DEFAULT) - self.settings.bind("notifications", self.switch_notifications, "active", Gio.SettingsBindFlags.DEFAULT) - self.settings.bind("force-offline", self.switch_force_offline, "active", Gio.SettingsBindFlags.DEFAULT) - self.settings.bind("temp", self.switch_temp, "active", Gio.SettingsBindFlags.DEFAULT) + self.settings.bind( + "dark-theme", self.switch_theme, "active", Gio.SettingsBindFlags.DEFAULT + ) + self.settings.bind( + "notifications", + self.switch_notifications, + "active", + Gio.SettingsBindFlags.DEFAULT, + ) + self.settings.bind( + "force-offline", + self.switch_force_offline, + "active", + Gio.SettingsBindFlags.DEFAULT, + ) + self.settings.bind( + "temp", self.switch_temp, "active", Gio.SettingsBindFlags.DEFAULT + ) # Connect RC signal to another func - self.settings.bind("release-candidate", self.switch_release_candidate, "active", Gio.SettingsBindFlags.DEFAULT) - self.settings.bind("steam-proton-support", self.switch_steam, "active", Gio.SettingsBindFlags.DEFAULT) - self.settings.bind("experiments-sandbox", self.switch_sandbox, "active", Gio.SettingsBindFlags.DEFAULT) - self.settings.bind("auto-close-bottles", self.switch_auto_close, "active", Gio.SettingsBindFlags.DEFAULT) - self.settings.bind("update-date", self.switch_update_date, "active", Gio.SettingsBindFlags.DEFAULT) - self.settings.bind("steam-programs", self.switch_steam_programs, "active", Gio.SettingsBindFlags.DEFAULT) - self.settings.bind("epic-games", self.switch_epic_games, "active", Gio.SettingsBindFlags.DEFAULT) - self.settings.bind("ubisoft-connect", self.switch_ubisoft_connect, "active", Gio.SettingsBindFlags.DEFAULT) + self.settings.bind( + "release-candidate", + self.switch_release_candidate, + "active", + Gio.SettingsBindFlags.DEFAULT, + ) + self.settings.bind( + "steam-proton-support", + self.switch_steam, + "active", + Gio.SettingsBindFlags.DEFAULT, + ) + self.settings.bind( + "experiments-sandbox", + self.switch_sandbox, + "active", + Gio.SettingsBindFlags.DEFAULT, + ) + self.settings.bind( + "auto-close-bottles", + self.switch_auto_close, + "active", + Gio.SettingsBindFlags.DEFAULT, + ) + self.settings.bind( + "update-date", + self.switch_update_date, + "active", + Gio.SettingsBindFlags.DEFAULT, + ) + self.settings.bind( + "steam-programs", + self.switch_steam_programs, + "active", + Gio.SettingsBindFlags.DEFAULT, + ) + self.settings.bind( + "epic-games", + self.switch_epic_games, + "active", + Gio.SettingsBindFlags.DEFAULT, + ) + self.settings.bind( + "ubisoft-connect", + self.switch_ubisoft_connect, + "active", + Gio.SettingsBindFlags.DEFAULT, + ) # setup loading screens self.installers_stack.set_visible_child_name("installers_loading") @@ -121,17 +177,18 @@ def __init__(self, window, **kwargs): RunAsync(self.ui_update) # connect signals - self.settings.connect('changed::dark-theme', self.__toggle_night) - self.settings.connect('changed::release-candidate', self.__toggle_rc) - self.settings.connect('changed::update-date', self.__toggle_update_date) - self.btn_bottles_path.connect('clicked', self.__choose_bottles_path) - self.btn_bottles_path_reset.connect('clicked', self.__reset_bottles_path) - self.btn_steam_proton_doc.connect('clicked', self.__open_steam_proton_doc) + self.settings.connect("changed::dark-theme", self.__toggle_night) + self.settings.connect("changed::release-candidate", self.__toggle_rc) + self.settings.connect("changed::update-date", self.__toggle_update_date) + self.btn_bottles_path.connect("clicked", self.__choose_bottles_path) + self.btn_bottles_path_reset.connect("clicked", self.__reset_bottles_path) + self.btn_steam_proton_doc.connect("clicked", self.__open_steam_proton_doc) if not self.manager.steam_manager.is_steam_supported: self.switch_steam.set_sensitive(False) self.action_steam_proton.set_tooltip_text( - _("Steam was not found or Bottles does not have enough permissions.")) + _("Steam was not found or Bottles does not have enough permissions.") + ) self.btn_steam_proton_doc.set_visible(True) if not self.style_manager.get_system_supports_color_schemes(): @@ -169,7 +226,9 @@ def __toggle_rc(self, widget, state): self.ui_update() def __open_steam_proton_doc(self, widget): - webbrowser.open("https://docs.usebottles.com/flatpak/cant-enable-steam-proton-manager") + webbrowser.open( + "https://docs.usebottles.com/flatpak/cant-enable-steam-proton-manager" + ) def __choose_bottles_path(self, widget): def set_path(_dialog, response): @@ -186,7 +245,7 @@ def set_path(_dialog, response): dialog = Gtk.FileChooserNative.new( title=_("Select Bottles Path"), action=Gtk.FileChooserAction.SELECT_FOLDER, - parent=self.window + parent=self.window, ) dialog.set_modal(True) @@ -195,10 +254,7 @@ def set_path(_dialog, response): def handle_restart(self, widget, response_id): if response_id == "restart": - subprocess.Popen( - "sleep 1 && bottles & disown", - shell=True - ) + subprocess.Popen("sleep 1 && bottles & disown", shell=True) self.window.proper_close() widget.destroy() @@ -207,11 +263,15 @@ def prompt_restart(self): dialog = Adw.MessageDialog.new( self.window, _("Relaunch Bottles?"), - _("Bottles will need to be relaunched to use this directory.\n\nBe sure to close every program launched from Bottles before relaunching Bottles, as not doing so can cause data loss, corruption and programs to malfunction.") + _( + "Bottles will need to be relaunched to use this directory.\n\nBe sure to close every program launched from Bottles before relaunching Bottles, as not doing so can cause data loss, corruption and programs to malfunction." + ), ) dialog.add_response("dismiss", _("_Cancel")) dialog.add_response("restart", _("_Relaunch")) - dialog.set_response_appearance("restart", Adw.ResponseAppearance.DESTRUCTIVE) + dialog.set_response_appearance( + "restart", Adw.ResponseAppearance.DESTRUCTIVE + ) dialog.connect("response", self.handle_restart) dialog.present() @@ -221,20 +281,34 @@ def __reset_bottles_path(self, widget): self.label_bottles_path.set_label(_("(Default)")) self.prompt_restart() - def __display_unstable_candidate(self, component = [ "", { "Channel": "unstable" } ]): - return (self.window.settings.get_boolean("release-candidate") - or component[1]["Channel"] not in ["rc", "unstable"]) + def __display_unstable_candidate(self, component=["", {"Channel": "unstable"}]): + return self.window.settings.get_boolean("release-candidate") or component[1][ + "Channel" + ] not in ["rc", "unstable"] - def __populate_component_list(self, component_type, supported_components, list_component): + def __populate_component_list( + self, component_type, supported_components, list_component + ): offline_components = self.manager.get_offline_components(component_type) supported_component_items = list(supported_components.items()) if self.__display_unstable_candidate(): i, j = 0, 0 while i <= len(supported_component_items): - while j < len(offline_components) and \ - (i == len(supported_component_items) or \ - sort_by_version([offline_components[j], supported_component_items[i][0]])[0] == offline_components[j]): - offline_entry = [ offline_components[j], { "Installed": True, "Channel": "unstable", "Category": component_type } ] + while j < len(offline_components) and ( + i == len(supported_component_items) + or sort_by_version( + [offline_components[j], supported_component_items[i][0]] + )[0] + == offline_components[j] + ): + offline_entry = [ + offline_components[j], + { + "Installed": True, + "Channel": "unstable", + "Category": component_type, + }, + ] supported_component_items.insert(i, offline_entry) j += 1 i += 1 @@ -247,29 +321,51 @@ def __populate_component_list(self, component_type, supported_components, list_c def populate_runtimes_list(self): for runtime in self.manager.supported_runtimes.items(): - self.list_runtimes.add(ComponentEntry(self.window, runtime, "runtime", is_upgradable=True)) + self.list_runtimes.add( + ComponentEntry(self.window, runtime, "runtime", is_upgradable=True) + ) def populate_winebridge_list(self): for bridge in self.manager.supported_winebridge.items(): - self.list_winebridge.add(ComponentEntry(self.window, bridge, "winebridge", is_upgradable=True)) + self.list_winebridge.add( + ComponentEntry(self.window, bridge, "winebridge", is_upgradable=True) + ) def populate_dxvk_list(self): - self.__populate_component_list("dxvk", self.manager.supported_dxvk, self.list_dxvk) + self.__populate_component_list( + "dxvk", self.manager.supported_dxvk, self.list_dxvk + ) def populate_vkd3d_list(self): - self.__populate_component_list("vkd3d", self.manager.supported_vkd3d, self.list_vkd3d) + self.__populate_component_list( + "vkd3d", self.manager.supported_vkd3d, self.list_vkd3d + ) def populate_nvapi_list(self): - self.__populate_component_list("nvapi", self.manager.supported_nvapi, self.list_nvapi) + self.__populate_component_list( + "nvapi", self.manager.supported_nvapi, self.list_nvapi + ) def populate_latencyflex_list(self): - self.__populate_component_list("latencyflex", self.manager.supported_latencyflex, self.list_latencyflex) + self.__populate_component_list( + "latencyflex", self.manager.supported_latencyflex, self.list_latencyflex + ) - def __populate_runners_helper(self, runner_type, supported_runners_dict, identifiable_runners_struct): + def __populate_runners_helper( + self, runner_type, supported_runners_dict, identifiable_runners_struct + ): offline_runners_list = self.manager.get_offline_components(runner_type) if self.__display_unstable_candidate(): for offline_runner_name in offline_runners_list: - offline_runner = [ offline_runner_name, { "Installed": True, "Channel": "unstable", "Category": "runners", "Sub-category": "wine" if runner_type == "runner" else "proton" } ] + offline_runner = [ + offline_runner_name, + { + "Installed": True, + "Channel": "unstable", + "Category": "runners", + "Sub-category": "wine" if runner_type == "runner" else "proton", + }, + ] _runner_name = offline_runner_name.lower() for identifiable_runner in identifiable_runners_struct: if _runner_name.startswith(identifiable_runner["prefix"]): @@ -284,10 +380,20 @@ def __populate_runners_helper(self, runner_type, supported_runners_dict, identif _entry = ComponentEntry(self.window, supported_runner, runner_type) for identifiable_runner in identifiable_runners_struct: if _runner_name.startswith(identifiable_runner["prefix"]): - while identifiable_runner["offline_runners"] and \ - sort_by_version([identifiable_runner["offline_runners"][0][0], supported_runner[0]])[0] == identifiable_runner["offline_runners"][0][0]: + while ( + identifiable_runner["offline_runners"] + and sort_by_version( + [ + identifiable_runner["offline_runners"][0][0], + supported_runner[0], + ] + )[0] + == identifiable_runner["offline_runners"][0][0] + ): offline_runner = identifiable_runner["offline_runners"].pop(0) - _offline_entry = ComponentEntry(self.window, offline_runner, runner_type) + _offline_entry = ComponentEntry( + self.window, offline_runner, runner_type + ) identifiable_runner["expander"].add_row(_offline_entry) identifiable_runner["count"] += 1 identifiable_runner["expander"].add_row(_entry) @@ -298,49 +404,123 @@ def __populate_runners_helper(self, runner_type, supported_runners_dict, identif for identifiable_runner in identifiable_runners_struct: while identifiable_runner["offline_runners"]: offline_runner = identifiable_runner["offline_runners"].pop(0) - _offline_entry = ComponentEntry(self.window, offline_runner, runner_type) + _offline_entry = ComponentEntry( + self.window, offline_runner, runner_type + ) identifiable_runner["expander"].add_row(_offline_entry) identifiable_runner["count"] += 1 def populate_runners_list(self): - exp_soda = ComponentExpander("Soda", _("Based on Valve's Wine, includes Staging and Proton patches.")) - exp_caffe = ComponentExpander("Caffe", _("Based on Wine upstream, includes Staging and Proton patches.")) - exp_wine_ge = ComponentExpander("Wine GE", _("Based on the most recent bleeding-edge Valve's Proton Experimental Wine, " - "includes Staging and custom patches. " - "This is meant to be used with non-steam games outside of Steam.")) - exp_kron4ek = ComponentExpander("Kron4ek", _("Based on Wine upstream, Staging, Staging-TkG and Proton patchset optionally available.")) + exp_soda = ComponentExpander( + "Soda", _("Based on Valve's Wine, includes Staging and Proton patches.") + ) + exp_caffe = ComponentExpander( + "Caffe", _("Based on Wine upstream, includes Staging and Proton patches.") + ) + exp_wine_ge = ComponentExpander( + "Wine GE", + _( + "Based on the most recent bleeding-edge Valve's Proton Experimental Wine, " + "includes Staging and custom patches. " + "This is meant to be used with non-steam games outside of Steam." + ), + ) + exp_kron4ek = ComponentExpander( + "Kron4ek", + _( + "Based on Wine upstream, Staging, Staging-TkG and Proton patchset optionally available." + ), + ) exp_lutris = ComponentExpander("Lutris") - exp_vaniglia = ComponentExpander("Vaniglia", _("Based on Wine upstream, includes Staging patches.")) - exp_proton_ge = ComponentExpander("Proton GE", _("Based on most recent bleeding-edge Valve's Proton Experimental, " - "includes Staging and custom patches. " - "Requires the Steam Runtime turned on.")) + exp_vaniglia = ComponentExpander( + "Vaniglia", _("Based on Wine upstream, includes Staging patches.") + ) + exp_proton_ge = ComponentExpander( + "Proton GE", + _( + "Based on most recent bleeding-edge Valve's Proton Experimental, " + "includes Staging and custom patches. " + "Requires the Steam Runtime turned on." + ), + ) exp_other_wine = ComponentExpander(_("Other Wine runners")) exp_other_proton = ComponentExpander(_("Other Proton runners")) identifiable_wine_runners = [ - { "prefix": "soda", "count": 0, "expander": exp_soda, "offline_runners": [] }, - { "prefix": "caffe", "count": 0, "expander": exp_caffe, "offline_runners": [] }, - { "prefix": "vaniglia", "count": 0, "expander": exp_vaniglia, "offline_runners": [] }, - { "prefix": "wine-ge", "count": 0, "expander": exp_wine_ge, "offline_runners": [] }, - { "prefix": "kron4ek", "count": 0, "expander": exp_kron4ek, "offline_runners": [] }, - { "prefix": "lutris", "count": 0, "expander": exp_lutris, "offline_runners": [] }, + {"prefix": "soda", "count": 0, "expander": exp_soda, "offline_runners": []}, + { + "prefix": "caffe", + "count": 0, + "expander": exp_caffe, + "offline_runners": [], + }, + { + "prefix": "vaniglia", + "count": 0, + "expander": exp_vaniglia, + "offline_runners": [], + }, + { + "prefix": "wine-ge", + "count": 0, + "expander": exp_wine_ge, + "offline_runners": [], + }, + { + "prefix": "kron4ek", + "count": 0, + "expander": exp_kron4ek, + "offline_runners": [], + }, + { + "prefix": "lutris", + "count": 0, + "expander": exp_lutris, + "offline_runners": [], + }, ] identifiable_proton_runners = [ - { "prefix": "ge-proton", "count": 0, "expander": exp_proton_ge, "offline_runners": [] } + { + "prefix": "ge-proton", + "count": 0, + "expander": exp_proton_ge, + "offline_runners": [], + } ] other_wine_runners = [ - { "prefix": "", "count": 0, "expander": exp_other_wine, "offline_runners": [] }, + { + "prefix": "", + "count": 0, + "expander": exp_other_wine, + "offline_runners": [], + }, ] other_proton_runners = [ - { "prefix": "", "count": 0, "expander": exp_other_proton, "offline_runners": [] }, + { + "prefix": "", + "count": 0, + "expander": exp_other_proton, + "offline_runners": [], + }, ] - self.__populate_runners_helper("runner", \ - self.manager.supported_wine_runners, identifiable_wine_runners + other_wine_runners) - self.__populate_runners_helper("runner:proton", \ - self.manager.supported_proton_runners, identifiable_proton_runners + other_proton_runners) + self.__populate_runners_helper( + "runner", + self.manager.supported_wine_runners, + identifiable_wine_runners + other_wine_runners, + ) + self.__populate_runners_helper( + "runner:proton", + self.manager.supported_proton_runners, + identifiable_proton_runners + other_proton_runners, + ) - for runner in identifiable_wine_runners + identifiable_proton_runners + other_wine_runners + other_proton_runners: + for runner in ( + identifiable_wine_runners + + identifiable_proton_runners + + other_wine_runners + + other_proton_runners + ): if runner["count"] > 0: self.list_runners.add(runner["expander"]) self.__registry.append(runner["expander"]) diff --git a/bottles/frontend/widgets/component.py b/bottles/frontend/widgets/component.py index 3502ed71e54..89fe89dd0db 100644 --- a/bottles/frontend/widgets/component.py +++ b/bottles/frontend/widgets/component.py @@ -29,12 +29,12 @@ logging = Logger() -@Gtk.Template(resource_path='/com/usebottles/bottles/component-entry.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/component-entry.ui") class ComponentEntry(Adw.ActionRow): - __gtype_name__ = 'ComponentEntry' + __gtype_name__ = "ComponentEntry" __gsignals__ = { - 'component-installed': (GObject.SIGNAL_RUN_FIRST, None, ()), - 'component-error': (GObject.SIGNAL_RUN_FIRST, None, ()) + "component-installed": (GObject.SIGNAL_RUN_FIRST, None, ()), + "component-error": (GObject.SIGNAL_RUN_FIRST, None, ()), } # region Widgets @@ -48,7 +48,9 @@ class ComponentEntry(Adw.ActionRow): # endregion - def __init__(self, window, component, component_type, is_upgradable=False, **kwargs): + def __init__( + self, window, component, component_type, is_upgradable=False, **kwargs + ): super().__init__(**kwargs) # common variables and references @@ -65,14 +67,16 @@ def __init__(self, window, component, component_type, is_upgradable=False, **kwa if component[1].get("Installed"): self.btn_browse.set_visible(True) - if not self.manager.component_manager.is_in_use(self.component_type, self.name): + if not self.manager.component_manager.is_in_use( + self.component_type, self.name + ): self.btn_remove.set_visible(True) else: self.btn_download.set_visible(True) self.btn_browse.set_visible(False) if is_upgradable: - self.btn_download.set_icon_name('software-update-available-symbolic') + self.btn_download.set_icon_name("software-update-available-symbolic") self.btn_download.set_tooltip_text(_("Upgrade")) # connect signals @@ -98,7 +102,7 @@ def async_callback(result, error=False): callback=async_callback, component_type=self.component_type, component_name=self.name, - func=self.update_progress + func=self.update_progress, ) def uninstall(self, widget): @@ -116,18 +120,22 @@ def update(result, error=False): task_func=self.component_manager.uninstall, callback=update, component_type=self.component_type, - component_name=self.name + component_name=self.name, ) def run_browse(self, widget): self.btn_download.set_visible(False) ManagerUtils.open_filemanager( - path_type=self.component_type, - component=self.name + path_type=self.component_type, component=self.name ) - def update_progress(self, received_size: int = 0, total_size: int = 0, status: Optional[Status] = None): + def update_progress( + self, + received_size: int = 0, + total_size: int = 0, + status: Optional[Status] = None, + ): if status == Status.FAILED: logging.error(f"Component installation failed") self.set_err() @@ -136,7 +144,7 @@ def update_progress(self, received_size: int = 0, total_size: int = 0, status: O self.box_download_status.set_visible(True) percent = int(received_size * 100 / total_size) - self.label_task_status.set_text(f'{percent}%') + self.label_task_status.set_text(f"{percent}%") if percent >= 100: self.label_task_status.set_text(_("Installing…")) @@ -164,9 +172,12 @@ def set_uninstalled(self): self.btn_browse.set_visible(False) self.btn_err.set_visible(False) self.btn_download.set_visible(True) - if self.name in self.manager.get_offline_components(self.component_type, self.name): + if self.name in self.manager.get_offline_components( + self.component_type, self.name + ): self.set_visible(False) + class ComponentExpander(Adw.ExpanderRow): def __init__(self, title, subtitle=None, **kwargs): diff --git a/bottles/frontend/widgets/dependency.py b/bottles/frontend/widgets/dependency.py index d50932be489..226e915a5e1 100644 --- a/bottles/frontend/widgets/dependency.py +++ b/bottles/frontend/widgets/dependency.py @@ -28,9 +28,9 @@ from bottles.frontend.windows.generic import SourceDialog -@Gtk.Template(resource_path='/com/usebottles/bottles/dependency-entry.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/dependency-entry.ui") class DependencyEntry(Adw.ActionRow): - __gtype_name__ = 'DependencyEntry' + __gtype_name__ = "DependencyEntry" # region Widgets label_category = Gtk.Template.Child() @@ -56,10 +56,10 @@ def __init__(self, window, config: BottleConfig, dependency, plain=False, **kwar self.queue = window.page_details.queue if plain: - ''' + """ If the dependency is plain, treat it as a placeholder, it can be used to display "fake" elements on the list - ''' + """ self.set_title(dependency) self.set_subtitle("") self.btn_install.set_visible(False) @@ -67,6 +67,15 @@ def __init__(self, window, config: BottleConfig, dependency, plain=False, **kwar self.btn_reinstall.set_visible(True) return + if self.config.Arch not in dependency[1].get("Arch", "win64_win32"): + self.btn_install.set_visible(False) + self.btn_remove.set_visible(False) + self.btn_reinstall.set_visible(False) + self.btn_err.set_visible(True) + self.btn_err.set_tooltip_text( + _("This dependency is not compatible with this bottle architecture.") + ) + # populate widgets self.set_title(dependency[0]) self.set_subtitle(dependency[1].get("Description")) @@ -80,24 +89,24 @@ def __init__(self, window, config: BottleConfig, dependency, plain=False, **kwar self.btn_license.connect("clicked", self.open_license) if dependency[0] in self.config.Installed_Dependencies: - ''' + """ If the dependency is installed, hide the btn_install button and show the btn_remove button - ''' + """ self.btn_install.set_visible(False) self.btn_remove.set_visible(True) self.btn_reinstall.set_visible(True) if dependency[0] in self.config.Uninstallers.keys(): - ''' + """ If the dependency has no uninstaller, disable the btn_remove button - ''' + """ uninstaller = self.config.Uninstallers[dependency[0]] if uninstaller in [False, "NO_UNINSTALLER"]: self.btn_remove.set_sensitive(False) - def open_manifest(self, widget): + def open_manifest(self, _widget): """ This function pop up a dialog with the manifest of the dependency @@ -106,12 +115,11 @@ def open_manifest(self, widget): parent=self.window, title=_("Manifest for {0}").format(self.dependency[0]), message=self.manager.dependency_manager.get_dependency( - name=self.dependency[0], - plain=True - ) + name=self.dependency[0], plain=True + ), ).present() - def open_license(self, widget): + def open_license(self, _widget): """ This function pop up a dialog with the license of the dependency @@ -121,7 +129,7 @@ def open_license(self, widget): ) webbrowser.open(manifest["License_url"]) - def install_dependency(self, widget): + def install_dependency(self, _widget): """ This function install the dependency in the bottle, it will also prevent user from installing other dependencies @@ -142,12 +150,12 @@ def install_dependency(self, widget): dependency=self.dependency, ) - def remove_dependency(self, widget): + def remove_dependency(self, _widget): """ This function remove the dependency from the bottle configuration """ - widget.set_sensitive(False) + _widget.set_sensitive(False) RunAsync( task_func=self.manager.remove_dependency, callback=self.set_install_status, @@ -169,10 +177,13 @@ def set_install_status(self, result: Result, error=None): uninstaller = result.data.get("uninstaller") removed = result.data.get("removed") or False if removed: - self.window.show_toast(_("\"{0}\" uninstalled").format(self.dependency[0])) + self.window.show_toast( + _('"{0}" uninstalled').format(self.dependency[0]) + ) else: - self.window.show_toast(_("\"{0}\" installed").format(self.dependency[0])) - return self.set_installed(uninstaller, removed) + self.window.show_toast(_('"{0}" installed').format(self.dependency[0])) + self.set_installed(uninstaller, removed) + return self.set_err() def set_err(self): @@ -185,7 +196,7 @@ def set_err(self): self.btn_remove.set_visible(False) self.btn_err.set_visible(True) self.get_parent().set_sensitive(True) - self.window.show_toast(_("\"{0}\" failed to install").format(self.dependency[0])) + self.window.show_toast(_('"{0}" failed to install').format(self.dependency[0])) def set_installed(self, installer=True, removed=False): """ diff --git a/bottles/frontend/widgets/executable.py b/bottles/frontend/widgets/executable.py index c469b3335d2..a4e00413a02 100644 --- a/bottles/frontend/widgets/executable.py +++ b/bottles/frontend/widgets/executable.py @@ -29,14 +29,12 @@ def __init__(self, parent, data, config, **kwargs): self.config = config self.data = data - self.set_label(data.get('name')) - self.connect('clicked', self.on_clicked) + self.set_label(data.get("name")) + self.connect("clicked", self.on_clicked) def on_clicked(self, widget): executor = WineExecutor( - self.config, - exec_path=self.data.get("file"), - args=self.data.get("args") + self.config, exec_path=self.data.get("file"), args=self.data.get("args") ) RunAsync(executor.run) self.parent.pop_run.popdown() # workaround #1640 diff --git a/bottles/frontend/widgets/importer.py b/bottles/frontend/widgets/importer.py index 7ed75e6be62..19c06694b69 100644 --- a/bottles/frontend/widgets/importer.py +++ b/bottles/frontend/widgets/importer.py @@ -22,9 +22,9 @@ from bottles.frontend.utils.gtk import GtkUtils -@Gtk.Template(resource_path='/com/usebottles/bottles/importer-entry.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/importer-entry.ui") class ImporterEntry(Adw.ActionRow): - __gtype_name__ = 'ImporterEntry' + __gtype_name__ = "ImporterEntry" # region Widgets label_manager = Gtk.Template.Child() @@ -65,7 +65,9 @@ def set_imported(result, error=False): self.img_lock.set_visible(result.ok) if result.ok: - self.window.show_toast(_("\"{0}\" imported").format(self.prefix.get("Name"))) + self.window.show_toast( + _('"{0}" imported').format(self.prefix.get("Name")) + ) self.set_sensitive(True) @@ -74,5 +76,5 @@ def set_imported(result, error=False): RunAsync( self.import_manager.import_wineprefix, callback=set_imported, - wineprefix=self.prefix + wineprefix=self.prefix, ) diff --git a/bottles/frontend/widgets/installer.py b/bottles/frontend/widgets/installer.py index ff366b67461..cc36452cfd7 100644 --- a/bottles/frontend/widgets/installer.py +++ b/bottles/frontend/widgets/installer.py @@ -23,9 +23,9 @@ from bottles.frontend.windows.installer import InstallerDialog -@Gtk.Template(resource_path='/com/usebottles/bottles/installer-entry.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/installer-entry.ui") class InstallerEntry(Adw.ActionRow): - __gtype_name__ = 'InstallerEntry' + __gtype_name__ = "InstallerEntry" # region Widgets btn_install = Gtk.Template.Child() @@ -46,15 +46,19 @@ def __init__(self, window, config, installer, **kwargs): self.installer = installer grade_descriptions = { - "Bronze": _("This application may work poorly. The installer was configured to provide the best possible experience, but expect glitches, instability and lack of working features."), - "Silver": _("This program works with noticeable glitches, but these glitches do not affect the application's functionality."), + "Bronze": _( + "This application may work poorly. The installer was configured to provide the best possible experience, but expect glitches, instability and lack of working features." + ), + "Silver": _( + "This program works with noticeable glitches, but these glitches do not affect the application's functionality." + ), "Gold": _("This program works with minor glitches."), "Platinum": _("This program works perfectly."), } name = installer[1].get("Name") description = installer[1].get("Description") - grade = installer[1].get('Grade') + grade = installer[1].get("Grade") grade_description = grade_descriptions[grade] # populate widgets @@ -73,23 +77,24 @@ def __init__(self, window, config, installer, **kwargs): def __open_manifest(self, widget): """Open installer manifest""" plain_manifest = self.manager.installer_manager.get_installer( - installer_name=self.installer[0], - plain=True + installer_name=self.installer[0], plain=True ) SourceDialog( parent=self.window, title=_("Manifest for {0}").format(self.installer[0]), - message=plain_manifest + message=plain_manifest, ).present() def __open_review(self, widget): """Open review""" - plain_text = self.manager.installer_manager.get_review(self.installer[0], parse=False) + plain_text = self.manager.installer_manager.get_review( + self.installer[0], parse=False + ) SourceDialog( parent=self.window, title=_("Review for {0}").format(self.installer[0]), message=plain_text, - lang="markdown" + lang="markdown", ).present() @staticmethod diff --git a/bottles/frontend/widgets/library.py b/bottles/frontend/widgets/library.py index 0ddbe01ffa0..88fe68eb240 100644 --- a/bottles/frontend/widgets/library.py +++ b/bottles/frontend/widgets/library.py @@ -31,9 +31,9 @@ logging = Logger() -@Gtk.Template(resource_path='/com/usebottles/bottles/library-entry.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/library-entry.ui") class LibraryEntry(Gtk.Box): - __gtype_name__ = 'LibraryEntry' + __gtype_name__ = "LibraryEntry" # region Widgets btn_run = Gtk.Template.Child() @@ -55,7 +55,7 @@ def __init__(self, library, uuid, entry, *args, **kwargs): self.library = library self.window = library.window self.manager = library.window.manager - self.name = entry['name'] + self.name = entry["name"] self.uuid = uuid self.entry = entry self.config = self.__get_config() @@ -68,17 +68,17 @@ def __init__(self, library, uuid, entry, *args, **kwargs): self.program = self.__get_program() - if len(entry['name']) >= 15: - name = entry['name'][:13] + "…" + if len(entry["name"]) >= 15: + name = entry["name"][:13] + "…" else: - name = entry['name'] + name = entry["name"] self.label_name.set_text(name) - self.label_bottle.set_text(entry['bottle']['name']) + self.label_bottle.set_text(entry["bottle"]["name"]) self.label_no_cover.set_label(self.name) - if entry.get('thumbnail'): - path = ThumbnailManager.get_path(self.config, entry['thumbnail']) + if entry.get("thumbnail"): + path = ThumbnailManager.get_path(self.config, entry["thumbnail"]) if path is None: # redownloading *should* never fail as it was successfully downloaded before @@ -87,9 +87,9 @@ def __init__(self, library, uuid, entry, *args, **kwargs): result = library_manager.download_thumbnail(self.uuid, self.config) if result: entry = library_manager.get_library().get(uuid) - path = ThumbnailManager.get_path(self.config, entry['thumbnail']) + path = ThumbnailManager.get_path(self.config, entry["thumbnail"]) - if path is not None: + if path is not None: # Gtk.Picture.set_pixbuf deprecated in GTK 4.12 texture = Gdk.Texture.new_from_filename(path) self.img_cover.set_paintable(texture) @@ -107,15 +107,19 @@ def __init__(self, library, uuid, entry, *args, **kwargs): def __get_config(self): bottles = self.manager.local_bottles - if self.entry['bottle']['name'] in bottles: - return bottles[self.entry['bottle']['name']] + if self.entry["bottle"]["name"] in bottles: + return bottles[self.entry["bottle"]["name"]] parent = self.get_parent() if parent: parent.remove(self) # TODO: Remove from list def __get_program(self): programs = self.manager.get_programs(self.config) - programs = [p for p in programs if p["id"] == self.entry["id"] or p["name"] == self.entry["name"]] + programs = [ + p + for p in programs + if p["id"] == self.entry["id"] or p["name"] == self.entry["name"] + ] if len(programs) == 0: return None # TODO: remove entry from library return programs[0] @@ -128,7 +132,9 @@ def __reset_buttons(self, result: Result | bool = None, error=False): case bool(): status = result case _: - logging.error(f"result should be Result or bool, but it was {type(result)}") + logging.error( + f"result should be Result or bool, but it was {type(result)}" + ) status = False self.btn_remove.set_visible(status) @@ -147,25 +153,25 @@ def set_watcher(result=False, error=False): winedbg.wait_for_process, callback=self.__reset_buttons, name=self.program["executable"], - timeout=5 + timeout=5, ) RunAsync( winedbg.is_process_alive, callback=set_watcher, - name=self.program["executable"] + name=self.program["executable"], ) def __remove_entry(self, *args): self.library.remove_entry(self) def run_executable(self, widget, with_terminal=False): - self.window.show_toast(_("Launching \"{0}\"…").format(self.program["name"])) + self.window.show_toast(_('Launching "{0}"…').format(self.program["name"])) RunAsync( WineExecutor.run_program, callback=self.__reset_buttons, config=self.config, - program=self.program + program=self.program, ) self.__reset_buttons() @@ -173,7 +179,7 @@ def run_steam(self, widget): self.manager.steam_manager.launch_app(self.config.CompatData) def stop_process(self, widget): - self.window.show_toast(_("Stopping \"{0}\"…").format(self.program["name"])) + self.window.show_toast(_('Stopping "{0}"…').format(self.program["name"])) winedbg = WineDbg(self.config) winedbg.kill_process(name=self.program["executable"]) self.__reset_buttons(True) diff --git a/bottles/frontend/widgets/program.py b/bottles/frontend/widgets/program.py index 263157e64c4..95f1ca3a2a8 100644 --- a/bottles/frontend/widgets/program.py +++ b/bottles/frontend/widgets/program.py @@ -59,7 +59,9 @@ class ProgramEntry(Adw.ActionRow): # endregion - def __init__(self, window, config, program, is_steam=False, check_boot=True, **kwargs): + def __init__( + self, window, config, program, is_steam=False, check_boot=True, **kwargs + ): super().__init__(**kwargs) # common variables and references @@ -73,11 +75,7 @@ def __init__(self, window, config, program, is_steam=False, check_boot=True, **k if is_steam: self.set_subtitle("Steam") - for w in [ - self.btn_run, - self.btn_stop, - self.btn_menu - ]: + for w in [self.btn_run, self.btn_stop, self.btn_menu]: w.set_visible(False) w.set_sensitive(False) self.btn_launch_steam.set_visible(True) @@ -145,7 +143,9 @@ def __reset_buttons(self, result: Union[bool, Result] = False, _error=False): if not isinstance(result, bool): status = result.status else: - raise NotImplementedError("Invalid data type, expect bool or Result, but it was %s" % type(result)) + raise NotImplementedError( + "Invalid data type, expect bool or Result, but it was %s" % type(result) + ) self.btn_run.set_visible(status) self.btn_stop.set_visible(not status) @@ -164,14 +164,10 @@ def set_watcher(_result=False, _error=False): winedbg.wait_for_process, callback=self.__reset_buttons, name=self.executable, - timeout=5 + timeout=5, ) - RunAsync( - winedbg.is_process_alive, - callback=set_watcher, - name=self.executable - ) + RunAsync(winedbg.is_process_alive, callback=set_watcher, name=self.executable) def run_executable(self, _widget, with_terminal=False): self.pop_actions.popdown() # workaround #1640 @@ -181,17 +177,19 @@ def _run(): self.pop_actions.popdown() # workaround #1640 return True - self.window.show_toast(_("Launching \"{0}\"…").format(self.program["name"])) + self.window.show_toast(_('Launching "{0}"…').format(self.program["name"])) RunAsync(_run, callback=self.__reset_buttons) self.__reset_buttons() def run_steam(self, _widget): self.manager.steam_manager.launch_app(self.config.CompatData) - self.window.show_toast(_("Launching \"{0}\" with Steam…").format(self.program["name"])) + self.window.show_toast( + _('Launching "{0}" with Steam…').format(self.program["name"]) + ) self.pop_actions.popdown() # workaround #1640 def stop_process(self, widget): - self.window.show_toast(_("Stopping \"{0}\"…").format(self.program["name"])) + self.window.show_toast(_('Stopping "{0}"…').format(self.program["name"])) winedbg = WineDbg(self.config) widget.set_sensitive(False) winedbg.kill_process(self.executable) @@ -206,14 +204,14 @@ def uninstall_program(self, _widget): RunAsync( task_func=uninstaller.from_name, callback=self.update_programs, - name=self.program["name"] + name=self.program["name"], ) def hide_program(self, _widget=None, update=True): status = not self.program.get("removed") - msg = _("\"{0}\" hidden").format(self.program["name"]) + msg = _('"{0}" hidden').format(self.program["name"]) if not status: - msg = _("\"{0}\" showed").format(self.program["name"]) + msg = _('"{0}" showed').format(self.program["name"]) self.program["removed"] = status self.save_program() @@ -228,7 +226,7 @@ def save_program(self): config=self.config, key=self.program["id"], value=self.program, - scope="External_Programs" + scope="External_Programs", ).data["config"] def remove_program(self, _widget=None): @@ -237,9 +235,9 @@ def remove_program(self, _widget=None): key=self.program["id"], scope="External_Programs", value=None, - remove=True + remove=True, ).data["config"] - self.window.show_toast(_("\"{0}\" removed").format(self.program["name"])) + self.window.show_toast(_('"{0}" removed').format(self.program["name"])) self.update_programs() def rename_program(self, _widget): @@ -252,7 +250,7 @@ def func(new_name): config=self.config, key=self.program["id"], value=self.program, - scope="External_Programs" + scope="External_Programs", ) def async_work(): @@ -271,7 +269,9 @@ def async_work(): @GtkUtils.run_in_main_loop def ui_update(_result, _error): self.window.page_library.update() - self.window.show_toast(_("\"{0}\" renamed to \"{1}\"").format(old_name, new_name)) + self.window.show_toast( + _('"{0}" renamed to "{1}"').format(old_name, new_name) + ) self.update_programs() RunAsync(async_work, callback=ui_update) @@ -281,9 +281,7 @@ def ui_update(_result, _error): def browse_program_folder(self, _widget): ManagerUtils.open_filemanager( - config=self.config, - path_type="custom", - custom_path=self.program["folder"] + config=self.config, path_type="custom", custom_path=self.program["folder"] ) self.pop_actions.popdown() # workaround #1640 @@ -294,7 +292,9 @@ def update(result, _error=False): webbrowser.open("https://docs.usebottles.com/bottles/programs#flatpak") return - self.window.show_toast(_("Desktop Entry created for \"{0}\"").format(self.program["name"])) + self.window.show_toast( + _('Desktop Entry created for "{0}"').format(self.program["name"]) + ) RunAsync( ManagerUtils.create_desktop_entry, @@ -304,23 +304,30 @@ def update(result, _error=False): "name": self.program["name"], "executable": self.program["executable"], "path": self.program["path"], - } + }, ) def add_to_library(self, _widget): def update(_result, _error=False): self.window.update_library() - self.window.show_toast(_("\"{0}\" added to your library").format(self.program["name"])) + self.window.show_toast( + _('"{0}" added to your library').format(self.program["name"]) + ) def add_to_library(): self.save_program() # we need to store it in the bottle configuration to keep the reference library_manager = LibraryManager() - library_manager.add_to_library({ - "bottle": {"name": self.config.Name, "path": self.config.Path}, - "name": self.program["name"], - "id": str(self.program["id"]), - "icon": ManagerUtils.extract_icon(self.config, self.program["name"], self.program["path"]), - }, self.config) + library_manager.add_to_library( + { + "bottle": {"name": self.config.Name, "path": self.config.Path}, + "name": self.program["name"], + "id": str(self.program["id"]), + "icon": ManagerUtils.extract_icon( + self.config, self.program["name"], self.program["path"] + ), + }, + self.config, + ) self.btn_add_library.set_visible(False) RunAsync(add_to_library, update) @@ -328,12 +335,14 @@ def add_to_library(): def add_to_steam(self, _widget): def update(result, _error=False): if result.ok: - self.window.show_toast(_("\"{0}\" added to your Steam library").format(self.program["name"])) + self.window.show_toast( + _('"{0}" added to your Steam library').format(self.program["name"]) + ) steam_manager = SteamManager(self.config) RunAsync( steam_manager.add_shortcut, update, program_name=self.program["name"], - program_path=self.program["path"] + program_path=self.program["path"], ) diff --git a/bottles/frontend/widgets/state.py b/bottles/frontend/widgets/state.py index 56d486ee573..c7af5426b5a 100644 --- a/bottles/frontend/widgets/state.py +++ b/bottles/frontend/widgets/state.py @@ -23,9 +23,9 @@ from bottles.frontend.utils.gtk import GtkUtils -@Gtk.Template(resource_path='/com/usebottles/bottles/state-entry.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/state-entry.ui") class StateEntry(Adw.ActionRow): - __gtype_name__ = 'StateEntry' + __gtype_name__ = "StateEntry" # region Widgets btn_restore = Gtk.Template.Child() @@ -46,7 +46,9 @@ def __init__(self, parent, config, state, active, **kwargs): if config.Versioning: self.state_name = "#{} - {}".format( state[0], - datetime.strptime(state[1]["Creation_Date"], "%Y-%m-%d %H:%M:%S.%f").strftime("%d %B %Y, %H:%M") + datetime.strptime( + state[1]["Creation_Date"], "%Y-%m-%d %H:%M:%S.%f" + ).strftime("%d %B %Y, %H:%M"), ) self.set_subtitle(self.state[1]["Comment"]) @@ -55,7 +57,9 @@ def __init__(self, parent, config, state, active, **kwargs): else: self.state_name = "{} - {}".format( state[0], - datetime.fromtimestamp(state[1]["timestamp"]).strftime("%d %B %Y, %H:%M") + datetime.fromtimestamp(state[1]["timestamp"]).strftime( + "%d %B %Y, %H:%M" + ), ) self.set_subtitle(state[1]["message"]) if active: @@ -78,7 +82,9 @@ def set_state(self, widget): self.spinner.start() def _after(): - self.window.page_details.view_versioning.update(None, self.config) # update states + self.window.page_details.view_versioning.update( + None, self.config + ) # update states self.manager.update_bottles() # update bottles RunAsync( @@ -86,7 +92,7 @@ def _after(): callback=self.set_completed, config=self.config, state_id=self.state[0], - after=_after + after=_after, ) @GtkUtils.run_in_main_loop diff --git a/bottles/frontend/windows/bottlepicker.py b/bottles/frontend/windows/bottlepicker.py index 6b37c9cecbd..70633849d1a 100644 --- a/bottles/frontend/windows/bottlepicker.py +++ b/bottles/frontend/windows/bottlepicker.py @@ -31,10 +31,11 @@ def __init__(self, config: BottleConfig): self.set_title(config.Name) -@Gtk.Template(resource_path='/com/usebottles/bottles/dialog-bottle-picker.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/dialog-bottle-picker.ui") class BottlePickerDialog(Adw.ApplicationWindow): """This class should not be called from the application GUI, only from CLI.""" - __gtype_name__ = 'BottlePickerDialog' + + __gtype_name__ = "BottlePickerDialog" settings = Gio.Settings.new(APP_ID) Adw.init() @@ -57,9 +58,9 @@ def __init__(self, arg_exe, **kwargs): self.list_bottles.append(BottleEntry(config)) self.list_bottles.select_row(self.list_bottles.get_first_child()) - self.btn_cancel.connect('clicked', self.__close) - self.btn_select.connect('clicked', self.__select) - self.btn_open.connect('clicked', self.__open) + self.btn_cancel.connect("clicked", self.__close) + self.btn_select.connect("clicked", self.__select) + self.btn_open.connect("clicked", self.__open) @staticmethod def __close(*_args): @@ -69,7 +70,16 @@ def __select(self, *_args): row = self.list_bottles.get_selected_row() if row: self.destroy() - subprocess.Popen(["bottles-cli", "run", "-b", row.bottle, "-e", self.arg_exe]) + subprocess.Popen( + [ + "bottles-cli", + "run", + "-b", + f'"{row.bottle}"', + "-e", + f'"{self.arg_exe}"', + ] + ) def __open(self, *_args): self.destroy() diff --git a/bottles/frontend/windows/crash.py b/bottles/frontend/windows/crash.py index 3745ea12824..7b486a9d382 100644 --- a/bottles/frontend/windows/crash.py +++ b/bottles/frontend/windows/crash.py @@ -45,9 +45,9 @@ def __on_btn_report_clicked(button, report): webbrowser.open(report["html_url"]) -@Gtk.Template(resource_path='/com/usebottles/bottles/dialog-crash-report.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/dialog-crash-report.ui") class CrashReportDialog(Adw.Window): - __gtype_name__ = 'CrashReportDialog' + __gtype_name__ = "CrashReportDialog" # region Widgets btn_send = Gtk.Template.Child() @@ -68,28 +68,30 @@ def __init__(self, window, log, **kwargs): # connect signals self.btn_send.connect("clicked", self.__open_github, log) - self.check_unlock_send.connect('toggled', self.__on_unlock_send) + self.check_unlock_send.connect("toggled", self.__on_unlock_send) self.label_output.set_text(log) __similar_reports = self.__get_similar_issues(log) if len(__similar_reports) >= 5: - ''' + """ This issue was reported 5 times, preventing the user from sending it again. - ''' - prevent_text = _("""\ + """ + prevent_text = _( + """\ This issue was reported 5 times and cannot be sent again. - Report your feedback in one of the below existing reports.""") + Report your feedback in one of the below existing reports.""" + ) self.check_unlock_send.set_sensitive(False) self.btn_send.set_tooltip_text(prevent_text) self.label_notice.set_text(prevent_text) elif len(__similar_reports) > 0: - ''' + """ If there are similar reports, show the box_related and append them to list_reports. Otherwise, make the btn_send sensitive, so the user can send the report. - ''' + """ i = 0 for issue in __similar_reports: self.list_reports.add(SimilarReportEntry(issue)) @@ -140,7 +142,12 @@ def __get_similar_issues(log): """ similar_issues = [] api_url = "https://api.github.com/repos/bottlesdevs/Bottles/issues?filter=all&state=all" - with contextlib.suppress(urllib.error.HTTPError, urllib.error.URLError, json.JSONDecodeError, TypeError): + with contextlib.suppress( + urllib.error.HTTPError, + urllib.error.URLError, + json.JSONDecodeError, + TypeError, + ): with urllib.request.urlopen(api_url) as r: data = r.read().decode("utf-8") data = json.loads(data) @@ -152,7 +159,7 @@ def __get_similar_issues(log): return similar_issues - '''Run executable with args''' + """Run executable with args""" def __open_github(self, widget, log): """ diff --git a/bottles/frontend/windows/depscheck.py b/bottles/frontend/windows/depscheck.py index d83dfeb0bdf..9e4b7186bc3 100644 --- a/bottles/frontend/windows/depscheck.py +++ b/bottles/frontend/windows/depscheck.py @@ -18,9 +18,9 @@ from gi.repository import Gtk, Adw -@Gtk.Template(resource_path='/com/usebottles/bottles/dialog-deps-check.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/dialog-deps-check.ui") class DependenciesCheckDialog(Adw.Window): - __gtype_name__ = 'DependenciesCheckDialog' + __gtype_name__ = "DependenciesCheckDialog" # region widgets btn_quit = Gtk.Template.Child() diff --git a/bottles/frontend/windows/display.py b/bottles/frontend/windows/display.py index 231a0aca45d..893811d6124 100644 --- a/bottles/frontend/windows/display.py +++ b/bottles/frontend/windows/display.py @@ -30,9 +30,9 @@ renderers = ["gl", "gdi", "vulkan"] -@Gtk.Template(resource_path='/com/usebottles/bottles/dialog-display.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/dialog-display.ui") class DisplayDialog(Adw.Window): - __gtype_name__ = 'DisplayDialog' + __gtype_name__ = "DisplayDialog" # Region Widgets btn_save = Gtk.Template.Child() @@ -46,7 +46,9 @@ class DisplayDialog(Adw.Window): spin_dpi = Gtk.Template.Child() combo_renderer = Gtk.Template.Child() - def __init__(self, parent_window, config, details, queue, widget, spinner_display, **kwargs): + def __init__( + self, parent_window, config, details, queue, widget, spinner_display, **kwargs + ): super().__init__(**kwargs) self.set_transient_for(parent_window) @@ -66,7 +68,9 @@ def __init__(self, parent_window, config, details, queue, widget, spinner_displa def __update(self, config): self.parameters = config.Parameters - self.expander_virtual_desktop.set_enable_expansion(self.parameters.virtual_desktop) + self.expander_virtual_desktop.set_enable_expansion( + self.parameters.virtual_desktop + ) self.switch_mouse_capture.set_active(self.parameters.fullscreen_capture) self.switch_take_focus.set_active(self.parameters.take_focus) self.switch_mouse_warp.set_active(self.parameters.mouse_warp) @@ -114,7 +118,10 @@ def complete_queue(): self.window.show_toast(_("Display settings updated")) self.queue.end_task() - if self.expander_virtual_desktop.get_enable_expansion() != self.parameters.virtual_desktop: + if ( + self.expander_virtual_desktop.get_enable_expansion() + != self.parameters.virtual_desktop + ): """Toggle virtual desktop""" @GtkUtils.run_in_main_loop @@ -123,7 +130,7 @@ def update(result, error=False): config=self.config, key="virtual_desktop", value=self.expander_virtual_desktop.get_enable_expansion(), - scope="Parameters" + scope="Parameters", ).data["config"] complete_queue() @@ -133,10 +140,13 @@ def update(result, error=False): task_func=rk.toggle_virtual_desktop, callback=update, state=self.expander_virtual_desktop.get_enable_expansion(), - resolution=resolution + resolution=resolution, ) - if self.expander_virtual_desktop.get_enable_expansion() == True and resolution != self.parameters.virtual_desktop_res: + if ( + self.expander_virtual_desktop.get_enable_expansion() == True + and resolution != self.parameters.virtual_desktop_res + ): """Set virtual desktop resolution""" @GtkUtils.run_in_main_loop @@ -145,7 +155,7 @@ def update(result, error=False): config=self.config, key="virtual_desktop_res", value=resolution, - scope="Parameters" + scope="Parameters", ).data["config"] complete_queue() @@ -156,7 +166,7 @@ def update(result, error=False): task_func=rk.toggle_virtual_desktop, callback=update, state=True, - resolution=resolution + resolution=resolution, ) if self.switch_mouse_warp.get_active() != self.parameters.mouse_warp: @@ -168,7 +178,7 @@ def update(result, error=False): config=self.config, key="mouse_warp", value=self.switch_mouse_warp.get_active(), - scope="Parameters" + scope="Parameters", ).data["config"] complete_queue() @@ -178,7 +188,7 @@ def update(result, error=False): RunAsync( rk.set_mouse_warp, callback=update, - state=self.switch_mouse_warp.get_active() + state=self.switch_mouse_warp.get_active(), ) if self.spin_dpi.get_value() != self.parameters.custom_dpi: @@ -187,10 +197,7 @@ def update(result, error=False): @GtkUtils.run_in_main_loop def update(result, error=False): self.config = self.manager.update_config( - config=self.config, - key="custom_dpi", - value=dpi, - scope="Parameters" + config=self.config, key="custom_dpi", value=dpi, scope="Parameters" ).data["config"] complete_queue() @@ -198,11 +205,7 @@ def update(result, error=False): rk = RegKeys(self.config) dpi = int(self.spin_dpi.get_value()) - RunAsync( - rk.set_dpi, - callback=update, - value=dpi - ) + RunAsync(rk.set_dpi, callback=update, value=dpi) if renderers[self.combo_renderer.get_selected()] != self.parameters.renderer: """Set renderer""" @@ -213,7 +216,7 @@ def update(result, error=False): config=self.config, key="renderer", value=renderer, - scope="Parameters" + scope="Parameters", ).data["config"] complete_queue() @@ -221,11 +224,7 @@ def update(result, error=False): rk = RegKeys(self.config) renderer = renderers[self.combo_renderer.get_selected()] - RunAsync( - rk.set_renderer, - callback=update, - value=renderer - ) + RunAsync(rk.set_renderer, callback=update, value=renderer) def toggle_x11_reg_key(state, rkey, ckey): """Update x11 registry keys""" @@ -233,10 +232,7 @@ def toggle_x11_reg_key(state, rkey, ckey): @GtkUtils.run_in_main_loop def update(result, error=False): self.config = self.manager.update_config( - config=self.config, - key=ckey, - value=state, - scope="Parameters" + config=self.config, key=ckey, value=state, scope="Parameters" ).data["config"] complete_queue() @@ -249,15 +245,23 @@ def update(result, error=False): callback=update, key="HKEY_CURRENT_USER\\Software\\Wine\\X11 Driver", value=rkey, - data=_rule + data=_rule, ) if self.switch_mouse_capture.get_active() != self.parameters.fullscreen_capture: - toggle_x11_reg_key(self.switch_mouse_capture.get_active(), "GrabFullscreen", "fullscreen_capture") + toggle_x11_reg_key( + self.switch_mouse_capture.get_active(), + "GrabFullscreen", + "fullscreen_capture", + ) if self.switch_take_focus.get_active() != self.parameters.take_focus: - toggle_x11_reg_key(self.switch_take_focus.get_active(), "UseTakeFocus", "take_focus") + toggle_x11_reg_key( + self.switch_take_focus.get_active(), "UseTakeFocus", "take_focus" + ) if self.switch_decorated.get_active() != self.parameters.decorated: - toggle_x11_reg_key(self.switch_decorated.get_active(), "Decorated", "decorated") + toggle_x11_reg_key( + self.switch_decorated.get_active(), "Decorated", "decorated" + ) """Close window""" self.close() diff --git a/bottles/frontend/windows/dlloverrides.py b/bottles/frontend/windows/dlloverrides.py index 3db97747e3d..83eb55a4362 100644 --- a/bottles/frontend/windows/dlloverrides.py +++ b/bottles/frontend/windows/dlloverrides.py @@ -17,10 +17,12 @@ from gi.repository import Gtk, GLib, Adw +from bottles.backend.dlls.dll import DLLComponent -@Gtk.Template(resource_path='/com/usebottles/bottles/dll-override-entry.ui') + +@Gtk.Template(resource_path="/com/usebottles/bottles/dll-override-entry.ui") class DLLEntry(Adw.ComboRow): - __gtype_name__ = 'DLLEntry' + __gtype_name__ = "DLLEntry" # region Widgets btn_remove = Gtk.Template.Child() @@ -37,16 +39,16 @@ def __init__(self, window, config, override, **kwargs): self.override = override types = ("b", "n", "b,n", "n,b", "d") - ''' + """ Set the DLL name as ActionRow title and set the combo_type to the type of override - ''' + """ self.set_title(self.override[0]) self.set_selected(types.index(self.override[1])) # connect signals self.btn_remove.connect("clicked", self.__remove_override) - self.connect('notify::selected', self.__set_override_type) + self.connect("notify::selected", self.__set_override_type) def __set_override_type(self, *_args): """ @@ -59,7 +61,7 @@ def __set_override_type(self, *_args): config=self.config, key=self.override[0], value=types[selected], - scope="DLL_Overrides" + scope="DLL_Overrides", ) def __remove_override(self, *_args): @@ -72,18 +74,19 @@ def __remove_override(self, *_args): key=self.override[0], value=False, scope="DLL_Overrides", - remove=True + remove=True, ) self.get_parent().remove(self) -@Gtk.Template(resource_path='/com/usebottles/bottles/dialog-dll-overrides.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/dialog-dll-overrides.ui") class DLLOverridesDialog(Adw.PreferencesWindow): - __gtype_name__ = 'DLLOverridesDialog' + __gtype_name__ = "DLLOverridesDialog" # region Widgets entry_row = Gtk.Template.Child() group_overrides = Gtk.Template.Child() + menu_invalid_override = Gtk.Template.Child() # endregion @@ -99,8 +102,32 @@ def __init__(self, window, config, **kwargs): self.__populate_overrides_list() # connect signals + self.entry_row.connect("changed", self.__check_override) self.entry_row.connect("apply", self.__save_override) + def __check_override(self, *_args): + """ + This function check if the override name is valid + Overrides already managed by Bottles (e.g. DXVK, VKD3D...) are deemed invalid + """ + dll_name = self.entry_row.get_text() + invalid_dlls = [] + + for managed_component in DLLComponent.__subclasses__(): + invalid_dlls += managed_component.get_override_keys().split(",") + + is_invalid = dll_name in invalid_dlls + + self.__valid_name = not is_invalid + self.menu_invalid_override.set_visible(is_invalid) + if is_invalid: + self.entry_row.add_css_class("error") + self.entry_row.set_show_apply_button(False) + else: + self.entry_row.remove_css_class("error") + # Needs to be set to true immediately + self.entry_row.set_show_apply_button(True) + def __save_override(self, *_args): """ This function check if the override name is not empty, then @@ -109,17 +136,12 @@ def __save_override(self, *_args): """ dll_name = self.entry_row.get_text() - if dll_name != "": + if dll_name != "" and self.__valid_name: self.manager.update_config( - config=self.config, - key=dll_name, - value="n,b", - scope="DLL_Overrides" + config=self.config, key=dll_name, value="n,b", scope="DLL_Overrides" ) _entry = DLLEntry( - window=self.window, - config=self.config, - override=[dll_name, "n,b"] + window=self.window, config=self.config, override=[dll_name, "n,b"] ) GLib.idle_add(self.group_overrides.add, _entry) self.group_overrides.set_description("") @@ -138,9 +160,5 @@ def __populate_overrides_list(self): self.group_overrides.set_description("") for override in overrides: - _entry = DLLEntry( - window=self.window, - config=self.config, - override=override - ) + _entry = DLLEntry(window=self.window, config=self.config, override=override) GLib.idle_add(self.group_overrides.add, _entry) diff --git a/bottles/frontend/windows/drives.py b/bottles/frontend/windows/drives.py index 74825435452..03de4e66ef9 100644 --- a/bottles/frontend/windows/drives.py +++ b/bottles/frontend/windows/drives.py @@ -20,9 +20,9 @@ from bottles.backend.wine.drives import Drives -@Gtk.Template(resource_path='/com/usebottles/bottles/drive-entry.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/drive-entry.ui") class DriveEntry(Adw.ActionRow): - __gtype_name__ = 'DriveEntry' + __gtype_name__ = "DriveEntry" # region Widgets btn_remove = Gtk.Template.Child() @@ -39,10 +39,10 @@ def __init__(self, parent, drive, **kwargs): self.config = parent.config self.drive = drive - ''' + """ Set the env var name as ActionRow title and set the entry_value to its value - ''' + """ self.set_title(self.drive[0]) self.set_subtitle(self.drive[1]) @@ -59,6 +59,7 @@ def __choose_path(self, *_args): Open the file chooser dialog and set the path to the selected file """ + def set_path(_dialog, response): if response != Gtk.ResponseType.ACCEPT: return @@ -70,7 +71,7 @@ def set_path(_dialog, response): dialog = Gtk.FileChooserNative.new( title=_("Select Drive Path"), action=Gtk.FileChooserAction.SELECT_FOLDER, - parent=self.parent.window + parent=self.parent.window, ) dialog.set_modal(True) @@ -87,13 +88,36 @@ def __remove(self, *_args): self.parent.add_combo_letter(self.drive[0]) -@Gtk.Template(resource_path='/com/usebottles/bottles/dialog-drives.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/dialog-drives.ui") class DrivesDialog(Adw.Window): - __gtype_name__ = 'DrivesDialog' - __alphabet = ["A", "B", "D", "E", "F", "G", "H", - "I", "J", "K", "L", "M", "N", "O", - "P", "Q", "R", "S", "T", "U", "V", - "W", "X", "Y", "Z"] + __gtype_name__ = "DrivesDialog" + __alphabet = [ + "A", + "B", + "D", + "E", + "F", + "G", + "H", + "I", + "J", + "K", + "L", + "M", + "N", + "O", + "P", + "Q", + "R", + "S", + "T", + "U", + "V", + "W", + "X", + "Y", + "Z", + ] # region Widgets combo_letter = Gtk.Template.Child() @@ -162,4 +186,3 @@ def add_combo_letter(self, letter): self.str_list_letters.append(item) self.combo_letter.set_selected(0) - \ No newline at end of file diff --git a/bottles/frontend/windows/duplicate.py b/bottles/frontend/windows/duplicate.py index 3e9f0ac538e..9f69e9cd55e 100644 --- a/bottles/frontend/windows/duplicate.py +++ b/bottles/frontend/windows/duplicate.py @@ -24,9 +24,9 @@ from bottles.frontend.utils.gtk import GtkUtils -@Gtk.Template(resource_path='/com/usebottles/bottles/dialog-duplicate.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/dialog-duplicate.ui") class DuplicateDialog(Adw.Window): - __gtype_name__ = 'DuplicateDialog' + __gtype_name__ = "DuplicateDialog" # region Widgets entry_name = Gtk.Template.Child() @@ -76,7 +76,7 @@ def __duplicate_bottle(self, widget): task_func=BackupManager.duplicate_bottle, callback=self.finish, config=self.config, - name=name + name=name, ) @GtkUtils.run_in_main_loop @@ -88,5 +88,5 @@ def finish(self, result, error=None): def pulse(self): # This function update the progress bar every half second. while True: - time.sleep(.5) + time.sleep(0.5) self.progressbar.pulse() diff --git a/bottles/frontend/windows/envvars.py b/bottles/frontend/windows/envvars.py index 3ec5d6c656f..caca77d9907 100644 --- a/bottles/frontend/windows/envvars.py +++ b/bottles/frontend/windows/envvars.py @@ -20,9 +20,9 @@ from bottles.frontend.utils.gtk import GtkUtils -@Gtk.Template(resource_path='/com/usebottles/bottles/env-var-entry.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/env-var-entry.ui") class EnvVarEntry(Adw.EntryRow): - __gtype_name__ = 'EnvVarEntry' + __gtype_name__ = "EnvVarEntry" # region Widgets btn_remove = Gtk.Template.Child() @@ -53,7 +53,7 @@ def __save(self, *_args): config=self.config, key=self.env[0], value=self.get_text(), - scope="Environment_Variables" + scope="Environment_Variables", ) def __remove(self, *_args): @@ -66,14 +66,14 @@ def __remove(self, *_args): key=self.env[0], value=False, remove=True, - scope="Environment_Variables" + scope="Environment_Variables", ) self.parent.group_vars.remove(self) -@Gtk.Template(resource_path='/com/usebottles/bottles/dialog-env-vars.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/dialog-env-vars.ui") class EnvVarsDialog(Adw.Window): - __gtype_name__ = 'EnvVarsDialog' + __gtype_name__ = "EnvVarsDialog" # region Widgets entry_name = Gtk.Template.Child() @@ -96,7 +96,9 @@ def __init__(self, window, config, **kwargs): self.entry_name.connect("apply", self.__save_var) def __validate(self, *_args): - self.__valid_name = GtkUtils.validate_entry(self.entry_name) + self.__valid_name = GtkUtils.validate_entry( + self.entry_name, lambda envvar: envvar.startswith("WINEDLLOVERRIDES") + ) def __save_var(self, *_args): """ @@ -111,7 +113,7 @@ def __save_var(self, *_args): env_name = self.entry_name.get_text() env_value = "value" - split_value = env_name.rsplit('=', 1) + split_value = env_name.split("=", 1) if len(split_value) == 2: env_name = split_value[0] env_value = split_value[1] @@ -119,7 +121,7 @@ def __save_var(self, *_args): config=self.config, key=env_name, value=env_value, - scope="Environment_Variables" + scope="Environment_Variables", ) _entry = EnvVarEntry(parent=self, env=[env_name, env_value]) GLib.idle_add(self.group_vars.add, _entry) diff --git a/bottles/frontend/windows/exclusionpatterns.py b/bottles/frontend/windows/exclusionpatterns.py index 139d9c6c60f..80bc5fc4f75 100644 --- a/bottles/frontend/windows/exclusionpatterns.py +++ b/bottles/frontend/windows/exclusionpatterns.py @@ -18,9 +18,9 @@ from gi.repository import Gtk, GLib, Adw -@Gtk.Template(resource_path='/com/usebottles/bottles/exclusion-pattern-entry.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/exclusion-pattern-entry.ui") class ExclusionPatternEntry(Adw.ActionRow): - __gtype_name__ = 'ExclusionPatternEntry' + __gtype_name__ = "ExclusionPatternEntry" # region Widgets btn_remove = Gtk.Template.Child() @@ -50,16 +50,14 @@ def __remove(self, *_args): patterns.remove(self.pattern) self.manager.update_config( - config=self.config, - key="Versioning_Exclusion_Patterns", - value=patterns + config=self.config, key="Versioning_Exclusion_Patterns", value=patterns ) self.parent.group_patterns.remove(self) -@Gtk.Template(resource_path='/com/usebottles/bottles/dialog-exclusion-patterns.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/dialog-exclusion-patterns.ui") class ExclusionPatternsDialog(Adw.Window): - __gtype_name__ = 'ExclusionPatternsDialog' + __gtype_name__ = "ExclusionPatternsDialog" # region Widgets entry_name = Gtk.Template.Child() @@ -89,7 +87,7 @@ def __save_var(self, *_args): self.manager.update_config( config=self.config, key="Versioning_Exclusion_Patterns", - value=self.config.Versioning_Exclusion_Patterns + [pattern] + value=self.config.Versioning_Exclusion_Patterns + [pattern], ) _entry = ExclusionPatternEntry(self, pattern) GLib.idle_add(self.group_patterns.add, _entry) diff --git a/bottles/frontend/windows/fsr.py b/bottles/frontend/windows/fsr.py index 99838fb6b62..1d863be916c 100644 --- a/bottles/frontend/windows/fsr.py +++ b/bottles/frontend/windows/fsr.py @@ -21,9 +21,9 @@ logging = Logger() -@Gtk.Template(resource_path='/com/usebottles/bottles/dialog-fsr.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/dialog-fsr.ui") class FsrDialog(Adw.Window): - __gtype_name__ = 'FsrDialog' + __gtype_name__ = "FsrDialog" # Region Widgets btn_save = Gtk.Template.Child() @@ -40,11 +40,11 @@ def __init__(self, parent_window, config, **kwargs): self.manager = parent_window.manager self.config = config self.quality_mode = { - "none": _("None"), - "ultra": _("Ultra Quality"), - "quality": _("Quality"), - "balanced": _("Balanced"), - "performance": _("Performance"), + "none": _("None"), + "ultra": _("Ultra Quality"), + "quality": _("Quality"), + "balanced": _("Balanced"), + "performance": _("Performance"), } # Connect signals @@ -61,25 +61,30 @@ def __update(self, config): # Select right entry if parameters.fsr_quality_mode: - self.combo_quality_mode.set_selected(list(self.quality_mode.keys()).index(parameters.fsr_quality_mode)) + self.combo_quality_mode.set_selected( + list(self.quality_mode.keys()).index(parameters.fsr_quality_mode) + ) self.spin_sharpening_strength.set_value(parameters.fsr_sharpening_strength) def __idle_save(self, *_args): print(list(self.quality_mode.keys())[self.combo_quality_mode.get_selected()]) - settings = {"fsr_quality_mode": list(self.quality_mode.keys())[self.combo_quality_mode.get_selected()], - "fsr_sharpening_strength": int(self.spin_sharpening_strength.get_value())} + settings = { + "fsr_quality_mode": list(self.quality_mode.keys())[ + self.combo_quality_mode.get_selected() + ], + "fsr_sharpening_strength": int(self.spin_sharpening_strength.get_value()), + } for setting in settings.keys(): self.manager.update_config( config=self.config, key=setting, value=settings[setting], - scope="Parameters" + scope="Parameters", ) self.destroy() def __save(self, *_args): GLib.idle_add(self.__idle_save) - diff --git a/bottles/frontend/windows/gamescope.py b/bottles/frontend/windows/gamescope.py index b04203bb810..2e917411c69 100644 --- a/bottles/frontend/windows/gamescope.py +++ b/bottles/frontend/windows/gamescope.py @@ -18,9 +18,9 @@ from gi.repository import Gtk, GLib, Adw -@Gtk.Template(resource_path='/com/usebottles/bottles/dialog-gamescope.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/dialog-gamescope.ui") class GamescopeDialog(Adw.Window): - __gtype_name__ = 'GamescopeDialog' + __gtype_name__ = "GamescopeDialog" # region Widgets spin_width = Gtk.Template.Child() @@ -85,26 +85,27 @@ def __update(self, config): self.toggle_fullscreen.handler_unblock_by_func(self.__change_wtype) def __idle_save(self, *_args): - settings = {"gamescope_game_width": self.spin_width.get_value(), - "gamescope_game_height": self.spin_height.get_value(), - "gamescope_window_width": self.spin_gamescope_width.get_value(), - "gamescope_window_height": self.spin_gamescope_height.get_value(), - "gamescope_fps": self.spin_fps_limit.get_value(), - "gamescope_fps_no_focus": self.spin_fps_limit_no_focus.get_value(), - "gamescope_scaling": self.switch_scaling.get_active(), - "gamescope_borderless": self.toggle_borderless.get_active(), - "gamescope_fullscreen": self.toggle_fullscreen.get_active()} + settings = { + "gamescope_game_width": self.spin_width.get_value(), + "gamescope_game_height": self.spin_height.get_value(), + "gamescope_window_width": self.spin_gamescope_width.get_value(), + "gamescope_window_height": self.spin_gamescope_height.get_value(), + "gamescope_fps": self.spin_fps_limit.get_value(), + "gamescope_fps_no_focus": self.spin_fps_limit_no_focus.get_value(), + "gamescope_scaling": self.switch_scaling.get_active(), + "gamescope_borderless": self.toggle_borderless.get_active(), + "gamescope_fullscreen": self.toggle_fullscreen.get_active(), + } for setting in settings.keys(): self.manager.update_config( config=self.config, key=setting, value=settings[setting], - scope="Parameters" + scope="Parameters", ) self.destroy() def __save(self, *_args): GLib.idle_add(self.__idle_save) - diff --git a/bottles/frontend/windows/generic.py b/bottles/frontend/windows/generic.py index a3ab9bb6259..81e16355dc4 100644 --- a/bottles/frontend/windows/generic.py +++ b/bottles/frontend/windows/generic.py @@ -27,7 +27,7 @@ def __init__(self, window, message=_("An error has occurred."), log=False): destroy_with_parent=True, message_type=Gtk.MessageType.WARNING, buttons=Gtk.ButtonsType.OK_CANCEL, - text=message + text=message, ) self.set_transient_for(window) self.set_modal(True) @@ -73,14 +73,14 @@ def __build_ui(self): highlight_syntax=True, highlight_matching_brackets=True, style_scheme=style_scheme_manager.get_scheme("oblivion"), - language=lang_manager.get_language(self.lang) + language=lang_manager.get_language(self.lang), ) source_view = GtkSource.View( buffer=source_buffer, show_line_numbers=True, show_line_marks=True, tab_width=4, - monospace=True + monospace=True, ) source_buffer = source_view.get_buffer() @@ -192,4 +192,3 @@ def __build_ui(self): def __copy_text(self, widget): clipboard = Gdk.Display.get_clipboard(Gdk.Display.get_default()) clipboard.set_content(Gdk.ContentProvider.new_for_value(self.message)) - diff --git a/bottles/frontend/windows/generic_cli.py b/bottles/frontend/windows/generic_cli.py index e628f844d24..7cc76e9f1cb 100644 --- a/bottles/frontend/windows/generic_cli.py +++ b/bottles/frontend/windows/generic_cli.py @@ -1,10 +1,6 @@ class MessageDialog: def __init__( - self, - parent, - title="Warning", - message="An error has occurred.", - log=False + self, parent, title="Warning", message="An error has occurred.", log=False ): parent = _ title = _ diff --git a/bottles/frontend/windows/installer.py b/bottles/frontend/windows/installer.py index a948fb7eba1..43fe78daa96 100644 --- a/bottles/frontend/windows/installer.py +++ b/bottles/frontend/windows/installer.py @@ -23,9 +23,9 @@ from bottles.frontend.utils.gtk import GtkUtils -@Gtk.Template(resource_path='/com/usebottles/bottles/local-resource-entry.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/local-resource-entry.ui") class LocalResourceEntry(Adw.ActionRow): - __gtype_name__ = 'LocalResourceEntry' + __gtype_name__ = "LocalResourceEntry" # region Widgets btn_path = Gtk.Template.Child() @@ -69,9 +69,9 @@ def set_path(_dialog, response): dialog.show() -@Gtk.Template(resource_path='/com/usebottles/bottles/dialog-installer.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/dialog-installer.ui") class InstallerDialog(Adw.Window): - __gtype_name__ = 'InstallerDialog' + __gtype_name__ = "InstallerDialog" __sections = {} __steps = 0 __current_step = 0 @@ -110,13 +110,18 @@ def __init__(self, window, config, installer, **kwargs): "params": _("Configuring the bottle…"), "steps": _("Processing installer steps…"), "exe": _("Installing the {}…".format(installer[1].get("Name"))), - "checks": _("Performing final checks…") + "checks": _("Performing final checks…"), } self.status_init.set_title(installer[1].get("Name")) - self.install_status_page.set_title(_("Installing {0}…").format(installer[1].get("Name"))) + self.install_status_page.set_title( + _("Installing {0}…").format(installer[1].get("Name")) + ) self.status_installed.set_description( - _("{0} is now available in the programs view.").format(installer[1].get("Name"))) + _("{0} is now available in the programs view.").format( + installer[1].get("Name") + ) + ) self.__set_icon() self.btn_install.connect("clicked", self.__check_resources) @@ -143,7 +148,9 @@ def __set_icon(self): self.img_icon_install.set_visible(False) def __check_resources(self, *_args): - self.__local_resources = self.manager.installer_manager.has_local_resources(self.installer) + self.__local_resources = self.manager.installer_manager.has_local_resources( + self.installer + ) if len(self.__local_resources) == 0: self.__install() return @@ -174,7 +181,7 @@ def set_status(result, error=False): config=self.config, installer=self.installer, step_fn=self.next_step, - local_resources=self.__final_resources + local_resources=self.__final_resources, ) def __installed(self): diff --git a/bottles/frontend/windows/journal.py b/bottles/frontend/windows/journal.py index ca5ef6dc6bb..9199d1bae4a 100644 --- a/bottles/frontend/windows/journal.py +++ b/bottles/frontend/windows/journal.py @@ -20,9 +20,9 @@ from bottles.backend.managers.journal import JournalManager, JournalSeverity -@Gtk.Template(resource_path='/com/usebottles/bottles/dialog-journal.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/dialog-journal.ui") class JournalDialog(Adw.Window): - __gtype_name__ = 'JournalDialog' + __gtype_name__ = "JournalDialog" # region Widgets tree_view = Gtk.Template.Child() @@ -43,12 +43,16 @@ def __init__(self, **kwargs): self.store = Gtk.ListStore(str, str, str) # connect signals - self.search_entry.connect('search-changed', self.on_search_changed) - self.btn_all.connect('clicked', self.filter_results, "") - self.btn_critical.connect('clicked', self.filter_results, JournalSeverity.CRITICAL) - self.btn_error.connect('clicked', self.filter_results, JournalSeverity.ERROR) - self.btn_warning.connect('clicked', self.filter_results, JournalSeverity.WARNING) - self.btn_info.connect('clicked', self.filter_results, JournalSeverity.INFO) + self.search_entry.connect("search-changed", self.on_search_changed) + self.btn_all.connect("clicked", self.filter_results, "") + self.btn_critical.connect( + "clicked", self.filter_results, JournalSeverity.CRITICAL + ) + self.btn_error.connect("clicked", self.filter_results, JournalSeverity.ERROR) + self.btn_warning.connect( + "clicked", self.filter_results, JournalSeverity.WARNING + ) + self.btn_info.connect("clicked", self.filter_results, JournalSeverity.INFO) self.populate_tree_view() @@ -56,29 +60,39 @@ def populate_tree_view(self, query="", severity=""): self.store.clear() colors = { - JournalSeverity.CRITICAL: '#db1600', - JournalSeverity.ERROR: '#db6600', - JournalSeverity.WARNING: '#dba100', - JournalSeverity.INFO: '#3283a8', - JournalSeverity.CRASH: '#db1600', + JournalSeverity.CRITICAL: "#db1600", + JournalSeverity.ERROR: "#db6600", + JournalSeverity.WARNING: "#dba100", + JournalSeverity.INFO: "#3283a8", + JournalSeverity.CRASH: "#db1600", } for _, value in self.journal: - if query.lower() in value['message'].lower() \ - and (severity == "" or severity == value['severity']): - self.store.append([ - '{}'.format( - colors[value['severity']], value['severity'].capitalize()), - value['timestamp'], - value['message'] - ]) + if query.lower() in value["message"].lower() and ( + severity == "" or severity == value["severity"] + ): + self.store.append( + [ + '{}'.format( + colors[value["severity"]], value["severity"].capitalize() + ), + value["timestamp"], + value["message"], + ] + ) self.tree_view.set_model(self.store) self.tree_view.set_search_column(1) - self.tree_view.append_column(Gtk.TreeViewColumn('Severity', Gtk.CellRendererText(), markup=0)) - self.tree_view.append_column(Gtk.TreeViewColumn('Timestamp', Gtk.CellRendererText(), text=1)) - self.tree_view.append_column(Gtk.TreeViewColumn('Message', Gtk.CellRendererText(), text=2)) + self.tree_view.append_column( + Gtk.TreeViewColumn("Severity", Gtk.CellRendererText(), markup=0) + ) + self.tree_view.append_column( + Gtk.TreeViewColumn("Timestamp", Gtk.CellRendererText(), text=1) + ) + self.tree_view.append_column( + Gtk.TreeViewColumn("Message", Gtk.CellRendererText(), text=2) + ) def on_search_changed(self, entry): self.populate_tree_view(entry.get_text()) diff --git a/bottles/frontend/windows/launchoptions.py b/bottles/frontend/windows/launchoptions.py index 986cac9e8d5..816f1d425c2 100644 --- a/bottles/frontend/windows/launchoptions.py +++ b/bottles/frontend/windows/launchoptions.py @@ -21,9 +21,9 @@ from gettext import gettext as _ -@Gtk.Template(resource_path='/com/usebottles/bottles/dialog-launch-options.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/dialog-launch-options.ui") class LaunchOptionsDialog(Adw.Window): - __gtype_name__ = 'LaunchOptionsDialog' + __gtype_name__ = "LaunchOptionsDialog" __gsignals__ = { "options-saved": (GObject.SIGNAL_RUN_FIRST, None, (object,)), } @@ -52,8 +52,19 @@ class LaunchOptionsDialog(Adw.Window): __default_script_msg = _("Choose a script which should be executed after run.") __default_cwd_msg = _("Choose from where start the program.") - __msg_disabled = _("{0} is already disabled for this bottle.") - __msg_override = _("This setting is different from the bottle's default.") + __msg_disabled = _("{0} is disabled globally for this bottle.") + __msg_override = _("This setting overrides the bottle's global setting.") + + def __set_disabled_switches(self): + if not self.global_dxvk: + self.action_dxvk.set_subtitle(self.__msg_disabled.format("DXVK")) + self.switch_dxvk.set_sensitive(False) + if not self.global_vkd3d: + self.action_vkd3d.set_subtitle(self.__msg_disabled.format("VKD3D")) + self.switch_vkd3d.set_sensitive(False) + if not self.global_nvapi: + self.action_nvapi.set_subtitle(self.__msg_disabled.format("DXVK-NVAPI")) + self.switch_nvapi.set_sensitive(False) def __init__(self, parent, config, program, **kwargs): super().__init__(**kwargs) @@ -68,7 +79,16 @@ def __init__(self, parent, config, program, **kwargs): self.set_transient_for(self.window) # set widget defaults - self.entry_arguments.set_text(program.get("arguments", "")) + if program.get("arguments") not in ["", None]: + self.entry_arguments.set_text(program.get("arguments")) + + # keeps track of toggled switches + self.toggled = {} + self.toggled["dxvk"] = False + self.toggled["vkd3d"] = False + self.toggled["dxvk_nvapi"] = False + self.toggled["fsr"] = False + self.toggled["virtual_desktop"] = False # connect signals self.btn_save.connect("clicked", self.__save) @@ -78,118 +98,106 @@ def __init__(self, parent, config, program, **kwargs): self.btn_cwd_reset.connect("clicked", self.__reset_cwd) self.btn_reset_defaults.connect("clicked", self.__reset_defaults) self.entry_arguments.connect("activate", self.__save) + + # set overrides status + self.global_dxvk = program_dxvk = config.Parameters.dxvk + self.global_vkd3d = program_vkd3d = config.Parameters.vkd3d + self.global_nvapi = program_nvapi = config.Parameters.dxvk_nvapi + self.global_fsr = program_fsr = config.Parameters.fsr + self.global_virt_desktop = program_virt_desktop = ( + config.Parameters.virtual_desktop + ) + + if self.program.get("dxvk") is not None: + program_dxvk = self.program.get("dxvk") + self.action_dxvk.set_subtitle(self.__msg_override) + if self.program.get("vkd3d") is not None: + program_vkd3d = self.program.get("vkd3d") + self.action_vkd3d.set_subtitle(self.__msg_override) + if self.program.get("dxvk_nvapi") is not None: + program_nvapi = self.program.get("dxvk_nvapi") + self.action_nvapi.set_subtitle(self.__msg_override) + if self.program.get("fsr") is not None: + program_fsr = self.program.get("fsr") + self.action_fsr.set_subtitle(self.__msg_override) + if self.program.get("virtual_desktop") is not None: + program_virt_desktop = self.program.get("virtual_desktop") + self.action_virt_desktop.set_subtitle(self.__msg_override) + + self.switch_dxvk.set_active(program_dxvk) + self.switch_vkd3d.set_active(program_vkd3d) + self.switch_nvapi.set_active(program_nvapi) + self.switch_fsr.set_active(program_fsr) + self.switch_virt_desktop.set_active(program_virt_desktop) + self.switch_dxvk.connect( - "state-set", - self.__check_override, - config.Parameters.dxvk, - self.action_dxvk + "state-set", self.__check_override, self.action_dxvk, "dxvk" ) self.switch_vkd3d.connect( - "state-set", - self.__check_override, - config.Parameters.vkd3d, - self.action_vkd3d + "state-set", self.__check_override, self.action_vkd3d, "vkd3d" ) self.switch_nvapi.connect( - "state-set", - self.__check_override, - config.Parameters.dxvk_nvapi, - self.action_nvapi + "state-set", self.__check_override, self.action_nvapi, "dxvk_nvapi" ) self.switch_fsr.connect( - "state-set", - self.__check_override, - config.Parameters.fsr, - self.action_fsr + "state-set", self.__check_override, self.action_fsr, "fsr" ) self.switch_virt_desktop.connect( "state-set", self.__check_override, - config.Parameters.virtual_desktop, - self.action_virt_desktop + self.action_virt_desktop, + "virtual_desktop", ) if program.get("script") not in ["", None]: self.action_script.set_subtitle(program["script"]) self.btn_script_reset.set_visible(True) - if program.get("folder") not in ["", None, ManagerUtils.get_exe_parent_dir(self.config, self.program["path"])]: + if program.get("folder") not in [ + "", + None, + ManagerUtils.get_exe_parent_dir(self.config, self.program["path"]), + ]: self.action_cwd.set_subtitle(program["folder"]) self.btn_cwd_reset.set_visible(True) - # set overrides status - dxvk = config.Parameters.dxvk - vkd3d = config.Parameters.vkd3d - nvapi = config.Parameters.dxvk_nvapi - fsr = config.Parameters.fsr - virt_desktop = config.Parameters.virtual_desktop + self.__set_disabled_switches() - if not dxvk: - self.action_dxvk.set_subtitle(self.__msg_disabled.format("DXVK")) - self.switch_dxvk.set_sensitive(False) - if not vkd3d: - self.action_vkd3d.set_subtitle(self.__msg_disabled.format("VKD3D")) - self.switch_vkd3d.set_sensitive(False) - if not nvapi: - self.action_nvapi.set_subtitle(self.__msg_disabled.format("DXVK-Nvapi")) - self.switch_nvapi.set_sensitive(False) - - if dxvk != self.program.get("dxvk"): - self.action_dxvk.set_subtitle(self.__msg_override) - if vkd3d != self.program.get("vkd3d"): - self.action_vkd3d.set_subtitle(self.__msg_override) - if nvapi != self.program.get("dxvk_nvapi"): - self.action_nvapi.set_subtitle(self.__msg_override) - if fsr != self.program.get("fsr"): - self.action_fsr.set_subtitle(self.__msg_override) - if virt_desktop != self.program.get("virtual_desktop"): - self.action_virt_desktop.set_subtitle(self.__msg_override) - - if "dxvk" in self.program: - dxvk = self.program["dxvk"] - if "vkd3d" in self.program: - vkd3d = self.program["vkd3d"] - if "dxvk_nvapi" in self.program: - nvapi = self.program["dxvk_nvapi"] - if "fsr" in self.program: - fsr = self.program["fsr"] - if "virtual_desktop" in self.program: - virt_desktop = self.program["virtual_desktop"] - - self.switch_dxvk.set_active(dxvk) - self.switch_vkd3d.set_active(vkd3d) - self.switch_nvapi.set_active(nvapi) - self.switch_fsr.set_active(fsr) - self.switch_virt_desktop.set_active(virt_desktop) - - def __check_override(self, widget, state, value, action): - if state != value: - action.set_subtitle(self.__msg_override) - else: - action.set_subtitle("") + def __check_override(self, widget, state, action, name): + self.toggled[name] = True + action.set_subtitle(self.__msg_override) def get_config(self): return self.config + def __set_override(self, name, program_value, global_value): + # Special reset value + if self.toggled[name] is None and name in self.program: + del self.program[name] + if self.toggled[name]: + self.program[name] = program_value + def __idle_save(self, *_args): - dxvk = self.switch_dxvk.get_state() - vkd3d = self.switch_vkd3d.get_state() - nvapi = self.switch_nvapi.get_state() - fsr = self.switch_fsr.get_state() - virt_desktop = self.switch_virt_desktop.get_state() - - self.program["dxvk"] = dxvk - self.program["vkd3d"] = vkd3d - self.program["dxvk_nvapi"] = nvapi - self.program["fsr"] = fsr - self.program["virtual_desktop"] = virt_desktop + program_dxvk = self.switch_dxvk.get_state() + program_vkd3d = self.switch_vkd3d.get_state() + program_nvapi = self.switch_nvapi.get_state() + program_fsr = self.switch_fsr.get_state() + program_virt_desktop = self.switch_virt_desktop.get_state() + + self.__set_override("dxvk", program_dxvk, self.global_dxvk) + self.__set_override("vkd3d", program_vkd3d, self.global_vkd3d) + self.__set_override("dxvk_nvapi", program_nvapi, self.global_nvapi) + self.__set_override("fsr", program_fsr, self.global_fsr) + self.__set_override( + "virtual_desktop", program_virt_desktop, self.global_virt_desktop + ) self.program["arguments"] = self.entry_arguments.get_text() self.config = self.manager.update_config( config=self.config, key=self.program["id"], value=self.program, - scope="External_Programs" + scope="External_Programs", ).data["config"] self.emit("options-saved", self.config) @@ -213,7 +221,7 @@ def set_path(dialog, response): dialog = Gtk.FileChooserNative.new( title=_("Select Script"), parent=self.window, - action=Gtk.FileChooserAction.OPEN + action=Gtk.FileChooserAction.OPEN, ) dialog.set_modal(True) @@ -239,7 +247,7 @@ def set_path(dialog, response): dialog = Gtk.FileChooserNative.new( title=_("Select Working Directory"), parent=self.window, - action=Gtk.FileChooserAction.SELECT_FOLDER + action=Gtk.FileChooserAction.SELECT_FOLDER, ) dialog.set_modal(True) @@ -250,13 +258,23 @@ def __reset_cwd(self, *_args): """ This function reset the script path. """ - self.program["folder"] = ManagerUtils.get_exe_parent_dir(self.config, self.program["path"]) + self.program["folder"] = ManagerUtils.get_exe_parent_dir( + self.config, self.program["path"] + ) self.action_cwd.set_subtitle(self.__default_cwd_msg) self.btn_cwd_reset.set_visible(False) def __reset_defaults(self, *_args): - self.switch_dxvk.set_active(self.config.Parameters.dxvk) - self.switch_vkd3d.set_active(self.config.Parameters.vkd3d) - self.switch_nvapi.set_active(self.config.Parameters.dxvk_nvapi) - self.switch_fsr.set_active(self.config.Parameters.fsr) - self.switch_virt_desktop.set_active(self.config.Parameters.virtual_desktop) + self.switch_dxvk.set_active(self.global_dxvk) + self.switch_vkd3d.set_active(self.global_vkd3d) + self.switch_nvapi.set_active(self.global_nvapi) + self.switch_fsr.set_active(self.global_fsr) + self.switch_virt_desktop.set_active(self.global_virt_desktop) + self.action_dxvk.set_subtitle("") + self.action_vkd3d.set_subtitle("") + self.action_nvapi.set_subtitle("") + self.action_fsr.set_subtitle("") + self.action_virt_desktop.set_subtitle("") + self.__set_disabled_switches() + for name in self.toggled: + self.toggled[name] = None diff --git a/bottles/frontend/windows/main_window.py b/bottles/frontend/windows/main_window.py index 4f516bb3b46..5fb9a15c295 100644 --- a/bottles/frontend/windows/main_window.py +++ b/bottles/frontend/windows/main_window.py @@ -51,9 +51,9 @@ logging = Logger() -@Gtk.Template(resource_path='/com/usebottles/bottles/window.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/window.ui") class MainWindow(Adw.ApplicationWindow): - __gtype_name__ = 'MainWindow' + __gtype_name__ = "MainWindow" # region Widgets stack_main = Gtk.Template.Child() @@ -81,7 +81,9 @@ def __init__(self, arg_bottle, **kwargs): super().__init__(**kwargs, default_width=width, default_height=height) self.disable_onboard = False - self.utils_conn = ConnectionUtils(force_offline=self.settings.get_boolean("force-offline")) + self.utils_conn = ConnectionUtils( + force_offline=self.settings.get_boolean("force-offline") + ) self.manager = None self.arg_bottle = arg_bottle self.app = kwargs.get("application") @@ -99,8 +101,7 @@ def __init__(self, arg_bottle, **kwargs): # Populate stack self.stack_main.add_named( - child=self.page_loading, - name="page_loading" + child=self.page_loading, name="page_loading" ).set_visible(False) self.headerbar.add_css_class("flat") @@ -112,14 +113,22 @@ def __init__(self, arg_bottle, **kwargs): # backend signal handlers self.task_syncer = TaskSyncer(self) SignalManager.connect(Signals.TaskAdded, self.task_syncer.task_added_handler) - SignalManager.connect(Signals.TaskRemoved, self.task_syncer.task_removed_handler) - SignalManager.connect(Signals.TaskUpdated, self.task_syncer.task_updated_handler) - SignalManager.connect(Signals.NetworkStatusChanged, self.network_changed_handler) + SignalManager.connect( + Signals.TaskRemoved, self.task_syncer.task_removed_handler + ) + SignalManager.connect( + Signals.TaskUpdated, self.task_syncer.task_updated_handler + ) + SignalManager.connect( + Signals.NetworkStatusChanged, self.network_changed_handler + ) SignalManager.connect(Signals.GNotification, self.g_notification_handler) SignalManager.connect(Signals.GShowUri, self.g_show_uri_handler) self.__on_start() - logging.info("Bottles Started!", ) + logging.info( + "Bottles Started!", + ) @Gtk.Template.Callback() def on_close_request(self, *args): @@ -169,7 +178,9 @@ def __on_start(self): def set_manager(result: Manager, error=None): self.manager = result - tmp_runners = [x for x in self.manager.runners_available if not x.startswith('sys-')] + tmp_runners = [ + x for x in self.manager.runners_available if not x.startswith("sys-") + ] if len(tmp_runners) == 0: self.show_onboard_view() @@ -186,49 +197,63 @@ def set_manager(result: Manager, error=None): self.main_leaf.get_page(self.page_importer).set_navigatable(False) self.stack_main.add_titled( - child=self.page_list, - name="page_list", - title=_("Bottles") + child=self.page_list, name="page_list", title=_("Bottles") ).set_icon_name("com.usebottles.bottles-symbolic") self.stack_main.add_titled( - child=self.page_library, - name="page_library", - title=_("Library") + child=self.page_library, name="page_library", title=_("Library") ).set_icon_name("library-symbolic") self.page_list.search_bar.set_key_capture_widget(self) - self.btn_search.bind_property('active', self.page_list.search_bar, 'search-mode-enabled', - GObject.BindingFlags.BIDIRECTIONAL) + self.btn_search.bind_property( + "active", + self.page_list.search_bar, + "search-mode-enabled", + GObject.BindingFlags.BIDIRECTIONAL, + ) - if self.stack_main.get_child_by_name(self.settings.get_string("startup-view")) is None: + if ( + self.stack_main.get_child_by_name( + self.settings.get_string("startup-view") + ) + is None + ): self.stack_main.set_visible_child_name("page_list") self.settings.bind( "startup-view", self.stack_main, "visible-child-name", - Gio.SettingsBindFlags.DEFAULT + Gio.SettingsBindFlags.DEFAULT, ) self.lock_ui(False) self.headerbar.get_style_context().remove_class("flat") - user_defined_bottles_path = self.manager.data_mgr.get(UserDataKeys.CustomBottlesPath) + user_defined_bottles_path = self.manager.data_mgr.get( + UserDataKeys.CustomBottlesPath + ) if user_defined_bottles_path and Paths.bottles != user_defined_bottles_path: dialog = Adw.MessageDialog.new( self, _("Custom Bottles Path not Found"), - _("Falling back to default path. No bottles from the given path will be listed.") + _( + "Falling back to default path. No bottles from the given path will be listed." + ), ) dialog.add_response("cancel", _("_Dismiss")) dialog.present() def get_manager(): if self.utils_conn.check_connection(): - SignalManager.connect(Signals.RepositoryFetched, self.page_loading.add_fetched) - - # do not redo connection if aborted connection - mng = Manager(g_settings=self.settings, check_connection=self.utils_conn.aborted_connections == 0) + SignalManager.connect( + Signals.RepositoryFetched, self.page_loading.add_fetched + ) + + # do not redo connection if aborted connection + mng = Manager( + g_settings=self.settings, + check_connection=self.utils_conn.aborted_connections == 0, + ) return mng self.check_core_deps() @@ -320,13 +345,14 @@ def lock_ui(self, status: bool = True): for w in widgets: w.set_visible(not status) - def show_toast(self, - message, - timeout=3, - action_label=None, - action_callback=None, - dismissed_callback=None - ) -> Adw.Toast: + def show_toast( + self, + message, + timeout=3, + action_label=None, + action_callback=None, + dismissed_callback=None, + ) -> Adw.Toast: toast = Adw.Toast.new(message) toast.props.timeout = timeout diff --git a/bottles/frontend/windows/onboard.py b/bottles/frontend/windows/onboard.py index fa2950f52b4..7a56ef71dc7 100644 --- a/bottles/frontend/windows/onboard.py +++ b/bottles/frontend/windows/onboard.py @@ -24,9 +24,9 @@ from bottles.frontend.utils.gtk import GtkUtils -@Gtk.Template(resource_path='/com/usebottles/bottles/onboard.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/onboard.ui") class OnboardDialog(Adw.Window): - __gtype_name__ = 'OnboardDialog' + __gtype_name__ = "OnboardDialog" __installing = False __settings = Gtk.Settings.get_default() @@ -45,12 +45,7 @@ class OnboardDialog(Adw.Window): label_skip = Gtk.Template.Child() # endregion - carousel_pages = [ - "welcome", - "bottles", - "download", - "finish" - ] + carousel_pages = ["welcome", "bottles", "download", "finish"] images = [ "/com/usebottles/bottles/images/images/bottles-welcome.svg", "/com/usebottles/bottles/images/images/bottles-welcome-night.svg", @@ -66,12 +61,14 @@ def __init__(self, window, **kwargs): # connect signals self.connect("close-request", self.__quit) - self.carousel.connect('page-changed', self.__page_changed) + self.carousel.connect("page-changed", self.__page_changed) self.btn_close.connect("clicked", self.__close_window) self.btn_back.connect("clicked", self.__previous_page) self.btn_next.connect("clicked", self.__next_page) self.btn_install.connect("clicked", self.__install_runner) - self.__settings.connect("notify::gtk-application-prefer-dark-theme", self.__theme_changed) + self.__settings.connect( + "notify::gtk-application-prefer-dark-theme", self.__theme_changed + ) self.btn_close.set_sensitive(False) @@ -81,7 +78,9 @@ def __init__(self, window, **kwargs): self.__page_changed() def __theme_changed(self, settings, key): - self.img_welcome.set_from_resource(self.images[settings.get_property("gtk-application-prefer-dark-theme")]) + self.img_welcome.set_from_resource( + self.images[settings.get_property("gtk-application-prefer-dark-theme")] + ) def __get_page(self, index): return self.carousel_pages[index] @@ -139,7 +138,7 @@ def set_completed(result: Result, error=False): task_func=self.manager.checks, callback=set_completed, install_latest=True, - first_run=True + first_run=True, ) def __previous_page(self, widget=False): @@ -155,7 +154,7 @@ def __next_page(self, widget=False): def pulse(self): # This function update the progress bar every 1s. while True: - time.sleep(.5) + time.sleep(0.5) self.progressbar.pulse() def __close_window(self, widget): diff --git a/bottles/frontend/windows/protonalert.py b/bottles/frontend/windows/protonalert.py index 58837f35498..c13a09a9e20 100644 --- a/bottles/frontend/windows/protonalert.py +++ b/bottles/frontend/windows/protonalert.py @@ -18,9 +18,9 @@ from gi.repository import Gtk, Adw -@Gtk.Template(resource_path='/com/usebottles/bottles/dialog-proton-alert.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/dialog-proton-alert.ui") class ProtonAlertDialog(Adw.Window): - __gtype_name__ = 'ProtonAlertDialog' + __gtype_name__ = "ProtonAlertDialog" __resources = {} # region Widgets diff --git a/bottles/frontend/windows/rename.py b/bottles/frontend/windows/rename.py index a5f29dab698..e58535268e4 100644 --- a/bottles/frontend/windows/rename.py +++ b/bottles/frontend/windows/rename.py @@ -18,9 +18,9 @@ from gi.repository import Gtk, Adw -@Gtk.Template(resource_path='/com/usebottles/bottles/dialog-rename.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/dialog-rename.ui") class RenameDialog(Adw.Window): - __gtype_name__ = 'RenameDialog' + __gtype_name__ = "RenameDialog" # region Widgets entry_name = Gtk.Template.Child() diff --git a/bottles/frontend/windows/sandbox.py b/bottles/frontend/windows/sandbox.py index 3f47a98322f..84c7d98f06e 100644 --- a/bottles/frontend/windows/sandbox.py +++ b/bottles/frontend/windows/sandbox.py @@ -18,9 +18,9 @@ from gi.repository import Gtk, Adw -@Gtk.Template(resource_path='/com/usebottles/bottles/dialog-sandbox.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/dialog-sandbox.ui") class SandboxDialog(Adw.Window): - __gtype_name__ = 'SandboxDialog' + __gtype_name__ = "SandboxDialog" # region Widgets switch_net = Gtk.Template.Child() @@ -44,10 +44,7 @@ def __init__(self, window, config, **kwargs): def __set_flag(self, widget, state, flag): self.config = self.manager.update_config( - config=self.config, - key=flag, - value=state, - scope="Sandbox" + config=self.config, key=flag, value=state, scope="Sandbox" ).data["config"] def __update(self, config): diff --git a/bottles/frontend/windows/upgradeversioning.py b/bottles/frontend/windows/upgradeversioning.py index e8024597dea..f9ebe665a0e 100644 --- a/bottles/frontend/windows/upgradeversioning.py +++ b/bottles/frontend/windows/upgradeversioning.py @@ -21,9 +21,9 @@ from bottles.backend.utils.threading import RunAsync -@Gtk.Template(resource_path='/com/usebottles/bottles/dialog-upgrade-versioning.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/dialog-upgrade-versioning.ui") class UpgradeVersioningDialog(Adw.Window): - __gtype_name__ = 'UpgradeVersioningDialog' + __gtype_name__ = "UpgradeVersioningDialog" # region Widgets btn_cancel = Gtk.Template.Child() @@ -58,8 +58,12 @@ def __upgrade(self, widget): self.btn_cancel.set_label("Close") RunAsync(self.pulse) - RunAsync(self.parent.manager.versioning_manager.update_system, self.finish, self.config) - + RunAsync( + self.parent.manager.versioning_manager.update_system, + self.finish, + self.config, + ) + def __proceed(self, widget): self.stack_switcher.set_visible_child_name("page_info") self.btn_proceed.set_visible(False) @@ -73,6 +77,5 @@ def finish(self, result, error=False): def pulse(self): # This function update the progress bar every half second. while True: - time.sleep(.5) + time.sleep(0.5) self.progressbar.pulse() - diff --git a/bottles/frontend/windows/vkbasalt.py b/bottles/frontend/windows/vkbasalt.py index 0ebb1e99400..c6d2407c1c8 100644 --- a/bottles/frontend/windows/vkbasalt.py +++ b/bottles/frontend/windows/vkbasalt.py @@ -15,7 +15,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -''' +""" Terminologies: -------------- cas: Contrast Adaptive Sharpening @@ -23,7 +23,7 @@ fxaa: Fast Approximate Anti-Aliasing smaa: Subpixel Morphological Anti-Aliasing clut (or lut): Color LookUp Table -''' +""" import os from gi.repository import Gtk, GLib, Adw @@ -33,6 +33,7 @@ logging = Logger() + class VkBasaltSettings: default = False effects = False @@ -53,9 +54,10 @@ class VkBasaltSettings: lut_file_path = False exec = False -@Gtk.Template(resource_path='/com/usebottles/bottles/dialog-vkbasalt.ui') + +@Gtk.Template(resource_path="/com/usebottles/bottles/dialog-vkbasalt.ui") class VkBasaltDialog(Adw.Window): - __gtype_name__ = 'VkBasaltDialog' + __gtype_name__ = "VkBasaltDialog" # Region Widgets switch_default = Gtk.Template.Child() @@ -86,7 +88,9 @@ def __init__(self, parent_window, config, **kwargs): self.window = parent_window self.manager = parent_window.manager self.config = config - conf = os.path.join(ManagerUtils.get_bottle_path(self.config), "vkBasalt.conf") # Configuration file location + conf = os.path.join( + ManagerUtils.get_bottle_path(self.config), "vkBasalt.conf" + ) # Configuration file location self.effects = { "cas": self.expander_cas, @@ -220,11 +224,20 @@ def get_subeffects(self, VkBasaltSettings): [VkBasaltSettings.dls_sharpness, self.spin_dls_sharpness], [VkBasaltSettings.dls_denoise, self.spin_dls_denoise], [VkBasaltSettings.fxaa_subpixel_quality, self.spin_fxaa_subpixel_quality], - [VkBasaltSettings.fxaa_quality_edge_threshold, self.spin_fxaa_quality_edge_threshold], - [VkBasaltSettings.fxaa_quality_edge_threshold_min, self.spin_fxaa_quality_edge_threshold_min], + [ + VkBasaltSettings.fxaa_quality_edge_threshold, + self.spin_fxaa_quality_edge_threshold, + ], + [ + VkBasaltSettings.fxaa_quality_edge_threshold_min, + self.spin_fxaa_quality_edge_threshold_min, + ], [VkBasaltSettings.smaa_threshold, self.spin_smaa_threshold], [VkBasaltSettings.smaa_max_search_steps, self.spin_smaa_max_search_steps], - [VkBasaltSettings.smaa_max_search_steps_diagonal, self.spin_smaa_max_search_steps_diagonal], + [ + VkBasaltSettings.smaa_max_search_steps_diagonal, + self.spin_smaa_max_search_steps_diagonal, + ], [VkBasaltSettings.smaa_corner_rounding, self.spin_smaa_corner_rounding], ] return subeffects @@ -240,12 +253,23 @@ def set_effects(self): VkBasaltSettings.cas_sharpness = self.spin_cas_sharpness.get_value() VkBasaltSettings.dls_sharpness = self.spin_dls_sharpness.get_value() VkBasaltSettings.dls_denoise = self.spin_dls_denoise.get_value() - VkBasaltSettings.fxaa_subpixel_quality = self.spin_fxaa_subpixel_quality.get_value() - VkBasaltSettings.fxaa_quality_edge_threshold = self.spin_fxaa_quality_edge_threshold.get_value() - VkBasaltSettings.fxaa_quality_edge_threshold_min = self.spin_fxaa_quality_edge_threshold_min.get_value() + VkBasaltSettings.fxaa_subpixel_quality = ( + self.spin_fxaa_subpixel_quality.get_value() + ) + VkBasaltSettings.fxaa_quality_edge_threshold = ( + self.spin_fxaa_quality_edge_threshold.get_value() + ) + VkBasaltSettings.fxaa_quality_edge_threshold_min = ( + self.spin_fxaa_quality_edge_threshold_min.get_value() + ) VkBasaltSettings.smaa_threshold = self.spin_smaa_threshold.get_value() VkBasaltSettings.smaa_edge_detection = self.smaa_edge_detection - VkBasaltSettings.smaa_corner_rounding = self.spin_smaa_corner_rounding.get_value() - VkBasaltSettings.smaa_max_search_steps = self.spin_smaa_max_search_steps.get_value() - VkBasaltSettings.smaa_max_search_steps_diagonal = self.spin_smaa_max_search_steps_diagonal.get_value() - + VkBasaltSettings.smaa_corner_rounding = ( + self.spin_smaa_corner_rounding.get_value() + ) + VkBasaltSettings.smaa_max_search_steps = ( + self.spin_smaa_max_search_steps.get_value() + ) + VkBasaltSettings.smaa_max_search_steps_diagonal = ( + self.spin_smaa_max_search_steps_diagonal.get_value() + ) diff --git a/bottles/frontend/windows/vmtouch.py b/bottles/frontend/windows/vmtouch.py index 29d59ce137d..b613dc23413 100644 --- a/bottles/frontend/windows/vmtouch.py +++ b/bottles/frontend/windows/vmtouch.py @@ -20,9 +20,9 @@ from gi.repository import Gtk, GLib, Adw -@Gtk.Template(resource_path='/com/usebottles/bottles/dialog-vmtouch.ui') +@Gtk.Template(resource_path="/com/usebottles/bottles/dialog-vmtouch.ui") class VmtouchDialog(Adw.Window): - __gtype_name__ = 'VmtouchDialog' + __gtype_name__ = "VmtouchDialog" # region Widgets switch_cache_cwd = Gtk.Template.Child() @@ -56,11 +56,10 @@ def __idle_save(self, *_args): config=self.config, key=setting, value=settings[setting], - scope="Parameters" + scope="Parameters", ) self.destroy() def __save(self, *_args): GLib.idle_add(self.__idle_save) - diff --git a/bottles/tests/backend/manager/test_manager.py b/bottles/tests/backend/manager/test_manager.py index 1199956111b..7651141c78a 100644 --- a/bottles/tests/backend/manager/test_manager.py +++ b/bottles/tests/backend/manager/test_manager.py @@ -1,12 +1,16 @@ """Core Manager tests""" + from bottles.backend.managers.manager import Manager from bottles.backend.utils.gsettings_stub import GSettingsStub def test_manager_is_singleton(): - assert Manager(is_cli=True) is Manager(is_cli=True), "Manager should be singleton object" - assert Manager(is_cli=True) is Manager(g_settings=GSettingsStub(), is_cli=True), \ - "Manager should be singleton even with different argument" + assert Manager(is_cli=True) is Manager( + is_cli=True + ), "Manager should be singleton object" + assert Manager(is_cli=True) is Manager( + g_settings=GSettingsStub(), is_cli=True + ), "Manager should be singleton even with different argument" def test_manager_default_gsettings_stub(): diff --git a/bottles/tests/backend/state/test_events.py b/bottles/tests/backend/state/test_events.py index 3090a0651d7..c216d8ac20a 100644 --- a/bottles/tests/backend/state/test_events.py +++ b/bottles/tests/backend/state/test_events.py @@ -1,4 +1,5 @@ """EventManager tests""" + import time from enum import Enum from threading import Thread @@ -6,6 +7,7 @@ from bottles.backend.state import EventManager + class Events(Enum): SimpleEvent = "simple.event" WaitAfterDone = "wait_after_done.event" @@ -14,8 +16,9 @@ class Events(Enum): DoneSingleton = "done_singleton.event" CorrectFlagDone = "correct_flag_done.event" + def approx_time(start, target): - epsilon = 0.010 # 5 ms window + epsilon = 0.010 # 5 ms window variation = time.time() - start - target result = -epsilon / 2 <= variation <= epsilon / 2 if not result: @@ -24,8 +27,10 @@ def approx_time(start, target): print(f"Variation: {variation}") return result + def test_simple_event(): start_time = time.time() + def t1_func(): EventManager.wait(Events.SimpleEvent) @@ -46,9 +51,11 @@ def test_wait_after_done_event(): EventManager.wait(Events.WaitAfterDone) assert approx_time(start_time, 0) + @pytest.mark.filterwarnings("error") def test_set_reset(): start_time = time.time() + def t1_func(): start_time_t1 = time.time() EventManager.wait(Events.SetResetEvent) @@ -87,6 +94,7 @@ def t2_func(): t1.join() assert approx_time(start_time, 0.3) + def test_event_singleton_wait(): EventManager._EVENTS = {} @@ -112,6 +120,7 @@ def wait_thread_by_value(): t2.join() t3.join() + def test_event_singleton_done_reset(): EventManager._EVENTS = {} @@ -125,6 +134,7 @@ def test_event_singleton_done_reset(): EventManager.reset(Events.DoneSingleton) assert len(EventManager._EVENTS) == 1 + def test_correct_internal_flag(): EventManager.done(Events.CorrectFlagDone) diff --git a/bottles/tests/backend/utils/test_generic.py b/bottles/tests/backend/utils/test_generic.py index d8a99b39534..1fe1e0765d9 100644 --- a/bottles/tests/backend/utils/test_generic.py +++ b/bottles/tests/backend/utils/test_generic.py @@ -7,17 +7,20 @@ # CP932 is superset of Shift-JIS, which is default codec for Japanese in Windows # GBK is default codec for Chinese in Windows -@pytest.mark.parametrize("text, hint, codec", [ - ("Hello, world!", None, "ascii"), - (" ", None, "ascii"), - ("Привет, мир!", None, "windows-1251"), - ("こんにちは、世界!", "ja_JP", "cp932"), - ("こんにちは、世界!", "ja_JP.utf-8", "utf-8"), - ("你好,世界!", "zh_CN", "gbk"), - ("你好,世界!", "zh_CN.UTF-8", "utf-8"), - ("你好,世界!", "zh_CN.invalid_fallback", "gbk"), - ("", None, "utf-8"), -]) +@pytest.mark.parametrize( + "text, hint, codec", + [ + ("Hello, world!", None, "ascii"), + (" ", None, "ascii"), + ("Привет, мир!", None, "windows-1251"), + ("こんにちは、世界!", "ja_JP", "cp932"), + ("こんにちは、世界!", "ja_JP.utf-8", "utf-8"), + ("你好,世界!", "zh_CN", "gbk"), + ("你好,世界!", "zh_CN.UTF-8", "utf-8"), + ("你好,世界!", "zh_CN.invalid_fallback", "gbk"), + ("", None, "utf-8"), + ], +) def test_detect_encoding(text: str, hint: Optional[str], codec: Optional[str]): text_bytes = text.encode(codec) guess = detect_encoding(text_bytes, hint) diff --git a/build.sh b/build.sh index 4950a8d0796..5adaea6523e 100644 --- a/build.sh +++ b/build.sh @@ -1 +1,36 @@ -flatpak-spawn --host flatpak run org.flatpak.Builder build com.usebottles.bottles.yml --user --install --force-clean && flatpak-spawn --host flatpak run com.usebottles.bottles \ No newline at end of file +#!/bin/sh + +run_flatpak() { + flatpak-spawn --host flatpak run org.flatpak.Builder build com.usebottles.bottles.yml --user --install --force-clean && flatpak-spawn --host flatpak run com.usebottles.bottles +} + +run_host() { + flatpak run org.flatpak.Builder build com.usebottles.bottles.yml --user --install --force-clean && flatpak run com.usebottles.bottles +} + +run_container() { + host-spawn flatpak run org.flatpak.Builder build com.usebottles.bottles.yml --user --install --force-clean && host-spawn flatpak run com.usebottles.bottles +} + +if [ -x "$(command -v flatpak-spawn)" ]; then + run_flatpak + exit $? +fi + +if [ -f "/run/.containerenv" ]; then + if [ -x "$(command -v flatpak)" ]; then + run_host + exit $? + fi + + if [ -x "$(command -v host-spawn)" ]; then + run_container + exit $? + fi + + echo "Looks like you are running in a container, but you don't have flatpak or host-spawn installed." + echo "Nothing to do here." +fi + +run_host +exit $? diff --git a/com.usebottles.bottles.pypi-deps.yaml b/com.usebottles.bottles.pypi-deps.yaml index 073d0b8fc12..a94326cd1b2 100644 --- a/com.usebottles.bottles.pypi-deps.yaml +++ b/com.usebottles.bottles.pypi-deps.yaml @@ -1,4 +1,4 @@ -# Generated by req2flatpak.py --requirements-file requirements.txt --yaml --target-platforms 310-x86_64 -o com.usebottles.bottles.pypi-deps.yaml +# Generated by req2flatpak.py --requirements-file requirements.txt --yaml --target-platforms 311-x86_64 -o com.usebottles.bottles.pypi-deps.yaml name: python3-package-installation buildsystem: simple build-commands: @@ -14,11 +14,11 @@ sources: url: https://files.pythonhosted.org/packages/1a/b5/228c1cdcfe138f1a8e01ab1b54284c8b83735476cb22b6ba251656ed13ad/Markdown-3.4.4-py3-none-any.whl sha256: a4c1b65c0957b4bd9e7d86ddc7b3c9868fb9670660f6f99f6d1bca8954d5a941 - type: file - url: https://files.pythonhosted.org/packages/44/80/96d6317a15a13a4f80ffa61118dc144a70756135fbc6ace30f9b033f51f7/PyGObject-3.44.1.tar.gz - sha256: 665fbe980c91e8b31ad78ed3f66879946948200864002d193f67eccc1d7d5d83 + url: https://files.pythonhosted.org/packages/ac/4a/f24ddf1d20cc4b56affc7921e29928559a06c922eb60077448392792b914/PyGObject-3.46.0.tar.gz + sha256: 481437b05af0a66b7c366ea052710eb3aacbb979d22d30b797f7ec29347ab1e6 - type: file - url: https://files.pythonhosted.org/packages/29/61/bf33c6c85c55bc45a29eee3195848ff2d518d84735eb0e2d8cb42e0d285e/PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - sha256: ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515 + url: https://files.pythonhosted.org/packages/7b/5e/efd033ab7199a0b2044dab3b9f7a4f6670e6a52c089de572e928d2873b06/PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + sha256: d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673 only-arches: - x86_64 - type: file @@ -28,8 +28,8 @@ sources: url: https://files.pythonhosted.org/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl sha256: e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970 - type: file - url: https://files.pythonhosted.org/packages/a4/65/057bf29660aae6ade0816457f8db4e749e5c0bfa2366eb5f67db9912fa4c/charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - sha256: 193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad + url: https://files.pythonhosted.org/packages/ff/b6/9222090f396f33cd58aa5b08b9bbf8871416b746a0c7b412a41a973674a5/charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + sha256: f0d1e3732768fecb052d90d62b220af62ead5748ac51ef61e7b32c266cac9293 only-arches: - x86_64 - type: file @@ -39,13 +39,13 @@ sources: url: https://files.pythonhosted.org/packages/fc/34/3030de6f1370931b9dbb4dad48f6ab1015ab1d32447850b9fc94e60097be/idna-3.4-py3-none-any.whl sha256: 90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2 - type: file - url: https://files.pythonhosted.org/packages/34/7a/9ec6142fdab1749eacbab23c8a502cccb57f1985d764a5cfacad1d55a2de/orjson-3.9.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - sha256: f13d61c0c7414ddee1ef4d0f303e2222f8cced5a2e26d9774751aecd72324c9e + url: https://files.pythonhosted.org/packages/0b/e1/2c6e894de23c1bb5c76eff087c99fc3c7ce8f317a793f8c80767f4225733/orjson-3.9.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + sha256: e5205ec0dfab1887dd383597012199f5175035e782cdb013c542187d280ca443 only-arches: - x86_64 - type: file - url: https://files.pythonhosted.org/packages/c8/98/e6c836ea547236fffab67503ff691d302f2366fbe3a1e37bfe611e352b3c/pathvalidate-3.1.0-py3-none-any.whl - sha256: 912fd1d2e1a2a6a6f98da36a91f21ed86746473810ff625b9c34f3d06c0caa1d + url: https://files.pythonhosted.org/packages/0c/ab/673cce13ab635fd755d206b18c0a371ef6e28ddbe25fadba9ae6c59f22a5/pathvalidate-3.2.0-py3-none-any.whl + sha256: cc593caa6299b22b37f228148257997e2fa850eea2daf7e4cc9205cef6908dee - type: file url: https://files.pythonhosted.org/packages/43/94/52243ddff508780dd2d8110964320ab4851134a55ab102285b46e740f76a/patool-1.12-py2.py3-none-any.whl sha256: 3f642549c9a78f5b8bef1af92df385b521d360520d1f34e4dba3fd1dee2a21bc @@ -53,8 +53,8 @@ sources: url: https://files.pythonhosted.org/packages/55/26/d0ad8b448476d0a1e8d3ea5622dc77b916db84c6aa3cb1e1c0965af948fc/pefile-2023.2.7-py3-none-any.whl sha256: da185cd2af68c08a6cd4481f7325ed600a88f6a813bad9dea07ab3ef73d8d8d6 - type: file - url: https://files.pythonhosted.org/packages/a2/dd/bc2c9ee9485308a29c18b1241329e677917af25c60b127857f0fb23d0c6e/pycairo-1.24.0.tar.gz - sha256: 1444d52f1bb4cc79a4a0c0fe2ccec4bd78ff885ab01ebe1c0f637d8392bcafb6 + url: https://files.pythonhosted.org/packages/db/f1/45f288a45215e12dea5a107a2e686e33902701d5485219437b5d64d1080a/pycairo-1.25.0.tar.gz + sha256: 37842b9bfa6339c45a5025f752e1d78d5840b1a0f82303bdd5610846ad8b5c4f - type: file url: https://files.pythonhosted.org/packages/a8/af/24d3acfa76b867dbd8f1166853c18eefc890fc5da03a48672b38ea77ddae/pycurl-7.45.2.tar.gz sha256: 5730590be0271364a5bddd9e245c9cc0fb710c4cbacbdd95264a3122d23224ca @@ -62,8 +62,8 @@ sources: url: https://files.pythonhosted.org/packages/70/8e/0e2d847013cb52cd35b38c009bb167a1a26b2ce6cd6965bf26b47bc0bf44/requests-2.31.0-py3-none-any.whl sha256: 58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f - type: file - url: https://files.pythonhosted.org/packages/9b/81/62fd61001fa4b9d0df6e31d47ff49cfa9de4af03adecf339c7bc30656b37/urllib3-2.0.4-py3-none-any.whl - sha256: de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4 + url: https://files.pythonhosted.org/packages/26/40/9957270221b6d3e9a3b92fdfba80dd5c9661ff45a664b47edd5d00f707f5/urllib3-2.0.6-py3-none-any.whl + sha256: 7a7c7003b000adf9e7ca2a377c9688bbc54ed41b985789ed576570342a375cd2 - type: file - url: https://files.pythonhosted.org/packages/28/f5/6955d7b3a5d71ce6bac104f9cf98c1b0513ad656cdaca8ea7d579196f771/wheel-0.41.1-py3-none-any.whl - sha256: 473219bd4cbedc62cea0cb309089b593e47c15c4a2531015f94e4e3b9a0f6981 + url: https://files.pythonhosted.org/packages/b8/8b/31273bf66016be6ad22bb7345c37ff350276cfd46e389a0c2ac5da9d9073/wheel-0.41.2-py3-none-any.whl + sha256: 75909db2664838d015e3d9139004ee16711748a52c8f336b52882266540215d8 diff --git a/com.usebottles.bottles.yml b/com.usebottles.bottles.yml index 51221715d83..cb397a3bc57 100644 --- a/com.usebottles.bottles.yml +++ b/com.usebottles.bottles.yml @@ -2,8 +2,8 @@ id: com.usebottles.bottles sdk: org.gnome.Sdk runtime: org.gnome.Platform base: org.winehq.Wine -base-version: stable-22.08 -runtime-version: &runtime-version '44' +base-version: stable-23.08 +runtime-version: &runtime-version '45' command: bottles finish-args: @@ -16,7 +16,6 @@ finish-args: - --socket=wayland - --socket=pulseaudio - --device=all - - --filesystem=xdg-download - --system-talk-name=org.freedesktop.UDisks2 - --env=LD_LIBRARY_PATH=/app/lib:/app/lib32 - --env=PATH=/app/bin:/app/utils/bin:/usr/bin:/usr/lib/extensions/vulkan/MangoHud/bin/:/usr/bin:/usr/lib/extensions/vulkan/OBSVkCapture/bin/:/usr/lib/extensions/vulkan/gamescope/bin/ @@ -100,7 +99,7 @@ modules: sources: - type: git url: https://github.com/hoytech/vmtouch - commit: 8f6898e3c027f445962e223ca7a7b33d40395fc6 + commit: af86e27675843b3c7e4ddfee66ddbaf44eff43c4 x-checker-data: type: json url: https://api.github.com/repos/hoytech/vmtouch/commits @@ -118,8 +117,8 @@ modules: sources: - type: git url: https://github.com/KhronosGroup/Vulkan-Tools.git - tag: sdk-1.3.250.1 - commit: 695887a994ef9cc00a7aa3f9c00b31a56ea79534 + tag: sdk-1.3.261.1 + commit: a7da7027ca9fd0901639f02619c226da9c6036f1 x-checker-data: type: git tag-pattern: ^sdk-([\d.]+)$ @@ -129,8 +128,8 @@ modules: sources: - type: git url: https://github.com/KhronosGroup/Vulkan-Headers.git - tag: sdk-1.3.250.1 - commit: 9e61870ecbd32514113b467e0a0c46f60ed222c7 + tag: sdk-1.3.261.1 + commit: 85c2334e92e215cce34e8e0ed8b2dce4700f4a50 x-checker-data: type: git tag-pattern: ^sdk-([\d.]+)$ @@ -144,8 +143,8 @@ modules: sources: &gamemode_sources - type: git url: https://github.com/FeralInteractive/gamemode - tag: '1.7' - commit: 4dc99dff76218718763a6b07fc1900fa6d1dafd9 + tag: 1.8.1 + commit: 5180d89e66830d87f69687b95fb86f622552b94b x-checker-data: type: git is-important: true @@ -188,8 +187,8 @@ modules: sources: - type: git url: https://github.com/ImageMagick/ImageMagick - tag: 7.1.1-15 - commit: a0a5f3da4cd07919dd2d1bbae121e590b5f105e2 + tag: 7.1.1-23 + commit: 54b13e91d262b1083e27fc8c02532c89d3ff649c x-checker-data: type: git tag-pattern: ^([\d.]+-[\d]+)$ @@ -198,14 +197,15 @@ modules: buildsystem: meson config-opts: - -Ddocs=false - - -Dbackends=gtk4 + - -Dbackend-gtk4=enabled sources: - type: git url: https://github.com/flatpak/libportal - tag: '0.6' - commit: 13df0b887a7eb7b0f9b14069561a41f62e813155 + tag: 0.7.1 + commit: e9ed3a50cdde321eaf42361212480a66eb94a57a x-checker-data: type: git + is-important: true tag-pattern: ^([\d.]+)$ - name: blueprint-compiler @@ -246,6 +246,7 @@ modules: commit: 6fbeab0470df55aeab141a45763147440b2f0290 x-checker-data: type: git + is-important: true tag-pattern: ^([\d.]+)$ modules: - name: vte @@ -256,8 +257,8 @@ modules: sources: - type: git url: https://gitlab.gnome.org/GNOME/vte - tag: 0.73.93 - commit: f13f4d6cd83ba01315ac62755d70736705c40c99 + tag: 0.74.2 + commit: 3f66edbf598129bafde3baa91ccfb345056418c3 x-checker-data: type: git tag-pattern: ^([\d.]+)$ diff --git a/data/com.usebottles.bottles.metainfo.xml.in b/data/com.usebottles.bottles.metainfo.xml.in index 0a666a43a15..03dff3ce912 100644 --- a/data/com.usebottles.bottles.metainfo.xml.in +++ b/data/com.usebottles.bottles.metainfo.xml.in @@ -7,6 +7,9 @@ Bottles Run Windows Software Bottles Contributors + + Bottles Contributors +

Bottles lets you run Windows software on Linux, such as applications and games. It introduces a workflow that helps you organize by categorizing each software to your liking. Bottles provides several tools and integrations to help you manage and optimize your applications.

Features:

@@ -64,19 +67,37 @@ 768 + + +

Fix running programs via CLI

+

Fix handling of empty program arguments

+

Removed obsolete faudio dependency from the environment

+

Update translations

+

Various bug fixes and refactoring

+
+
+ + +

Clean FSR settings

+

Improve bad connections handling

+

Update Flatpak runtime

+

Update translations

+

Various bug fixes

+
+
- +

Fix runners and components from not showing when prereleases are off

Fix Steam runtime compatibility with Wine runners

- +

A few bug fixes

- +

Support for the double-DLL VKD3D

Updated Flatpak runtime

Minor improvement and fixes to the library

@@ -86,34 +107,34 @@
- +

Update metadata information

- +

Add more update information and correct release notes version

- +

Fixed "Add to Steam" button

Fixed BottleConfig being not serializable

Fixed Patool double extraction failing

- +

Correct version

- +

Fix crash when creating a bottle

- +

Major change: Redesign New Bottle interface

Quality of life improvements:

    @@ -131,14 +152,14 @@ - +
    • Fix error when downloading if Bottles isn't run from terminal
    - +
    • Correct version date
    • Hide NVIDIA-related critical errors on non-NVIDIA systems
    • @@ -146,7 +167,7 @@ - +
      • Gamescope improvements and fixes
      • Dependency installation is faster and more stable
      • @@ -167,4 +188,4 @@ - + \ No newline at end of file diff --git a/meson.build b/meson.build index ce7980b9348..8c7ce736196 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project( 'bottles', - version: '51.9', + version: '51.11', meson_version: '>= 0.59.0', default_options: [ 'warning_level=2', diff --git a/po/ar.po b/po/ar.po index 3869466449e..5e2b5ff3ff2 100644 --- a/po/ar.po +++ b/po/ar.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: bottles\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-03-27 13:50+0530\n" -"PO-Revision-Date: 2023-08-21 21:27+0000\n" +"PO-Revision-Date: 2024-01-12 18:06+0000\n" "Last-Translator: jonnysemon \n" "Language-Team: Arabic \n" @@ -18,7 +18,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " "&& n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n" -"X-Generator: Weblate 5.0-dev\n" +"X-Generator: Weblate 5.4-dev\n" #: bottles/backend/managers/backup.py:48 bottles/backend/managers/backup.py:95 msgid "No path specified" @@ -409,7 +409,7 @@ msgstr "مُكّن تعيين الإصدار لهذه القارورة" #: bottles/frontend/ui/details-bottle.blp:218 msgid "Versioning is active for this bottle." -msgstr "تعيين الإصدار نشط في هذه القارورة." +msgstr "تعيين الإصدار نشط لهذه القارورة." #: bottles/frontend/ui/details-bottle.blp:227 bottles/frontend/ui/list-entry.blp:31 msgid "0" diff --git a/po/de.po b/po/de.po index bdeee5b06ef..f291a0b6648 100644 --- a/po/de.po +++ b/po/de.po @@ -8,8 +8,8 @@ msgstr "" "Project-Id-Version: bottles\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-03-27 13:50+0530\n" -"PO-Revision-Date: 2023-04-24 18:47+0000\n" -"Last-Translator: manu \n" +"PO-Revision-Date: 2024-02-06 06:01+0000\n" +"Last-Translator: David \n" "Language-Team: German \n" "Language: de\n" @@ -17,7 +17,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.18-dev\n" +"X-Generator: Weblate 5.4-dev\n" #: bottles/backend/managers/backup.py:48 bottles/backend/managers/backup.py:95 msgid "No path specified" @@ -214,7 +214,7 @@ msgstr "Gefördert durch" # Translators: Bottles is a proper noun referring to the app #: bottles/frontend/ui/about.blp:5 msgid "Copyright © 2017 Bottles Developers" -msgstr "Urheberrecht © 2017-2022 - Bottles Entwickler" +msgstr "Urheberrecht © 2017-2024 - Bottles Entwickler" # Translators: Bottles is a proper noun referring to the app #: bottles/frontend/ui/about.blp:10 @@ -362,7 +362,7 @@ msgstr "Alle Prozesse beenden" #: bottles/frontend/ui/details-bottle.blp:94 msgid "Simulate a Windows system shutdown." -msgstr "Window-Herunterfahren simulieren." +msgstr "Simuliere das Herunterfahren von Windows." #: bottles/frontend/ui/details-bottle.blp:95 msgid "Shutdown" @@ -436,6 +436,10 @@ msgid "" "executable to the Programs list, or \"Install Programs…\" to install " "programs curated by the community." msgstr "" +"Klicke auf „Programmdatei ausführen...“, um eine Programmdatei auszuführen, " +"auf „Kurzbefehle hinzufügen...“, um eine Programmdatei der Programmliste " +"hinzuzufügen oder „Programme Installieren...“, um von der Community " +"gepflegte Programme zu installieren." #: bottles/frontend/ui/details-bottle.blp:298 msgid "Add Shortcuts…" @@ -446,18 +450,15 @@ msgid "Install Programs…" msgstr "Programme Installieren…" #: bottles/frontend/ui/details-bottle.blp:346 -#, fuzzy msgid "Options" msgstr "Optionen" #: bottles/frontend/ui/details-bottle.blp:350 #: bottles/frontend/views/details.py:141 -#, fuzzy msgid "Settings" msgstr "Einstellungen" #: bottles/frontend/ui/details-bottle.blp:351 -#, fuzzy msgid "Configure bottle settings." msgstr "Bottle-Einstellungen konfigurieren." @@ -467,21 +468,18 @@ msgid "Dependencies" msgstr "Abhängigkeiten" #: bottles/frontend/ui/details-bottle.blp:361 -#, fuzzy msgid "Install dependencies for programs." msgstr "Abhängigkeiten der Programme installieren." #: bottles/frontend/ui/details-bottle.blp:370 #: bottles/frontend/ui/details-preferences.blp:377 #: bottles/frontend/views/details.py:149 -#, fuzzy msgid "Snapshots" msgstr "Schnappschüsse" #: bottles/frontend/ui/details-bottle.blp:371 -#, fuzzy msgid "Create and manage bottle states." -msgstr "Speichere den Zustand der Bottle." +msgstr "Erstelle und verwalte Bottle zustände." #: bottles/frontend/ui/details-bottle.blp:380 #: bottles/frontend/ui/details-bottle.blp:426 @@ -506,19 +504,16 @@ msgid "Run commands inside the Bottle." msgstr "Befehle in der Bottle ausführen." #: bottles/frontend/ui/details-bottle.blp:404 -#, fuzzy msgid "Registry Editor" msgstr "Registry-Editor" #: bottles/frontend/ui/details-bottle.blp:405 -#, fuzzy msgid "Edit the internal registry." -msgstr "Internes Verzeichnis bearbeiten." +msgstr "Die interne Registrierung bearbeiten." #: bottles/frontend/ui/details-bottle.blp:413 -#, fuzzy msgid "Legacy Wine Tools" -msgstr "Legacy-Werkzeuge" +msgstr "Legacy-Winewerkzeuge" #: bottles/frontend/ui/details-bottle.blp:417 msgid "Explorer" @@ -549,11 +544,12 @@ msgstr "Abhängigkeiten suchen…" #: bottles/frontend/ui/preferences.blp:178 #: bottles/frontend/ui/preferences.blp:235 msgid "You're offline :(" -msgstr "" +msgstr "Du bist offline :(" #: bottles/frontend/ui/details-dependencies.blp:25 msgid "Bottles is running in offline mode, so dependencies are not available." msgstr "" +"Bottles läuft im Offlinemodus, daher sind keine Abhängigkeiten verfügbar." #: bottles/frontend/ui/details-dependencies.blp:47 msgid "" @@ -574,7 +570,6 @@ msgid "Report a problem or a missing dependency." msgstr "Ein Problem oder eine fehlende Abhängigkeit melden." #: bottles/frontend/ui/details-dependencies.blp:77 -#, fuzzy msgid "Report Missing Dependency" msgstr "Fehlende Abhängigkeit melden" @@ -599,19 +594,17 @@ msgid "Search for Programs…" msgstr "Nach Programmen suchen…" #: bottles/frontend/ui/details-installers.blp:15 -#, fuzzy msgid "" "Install programs curated by our community.\n" "\n" "Files on this page are provided by third parties under a proprietary " "license. By installing them, you agree with their respective licensing terms." msgstr "" -"Installieren Sie Programme, die von der Community bereitgestellt werden, " -"ohne manuell fortfahren zu müssen.\n" +"Installiere Programme, die von unserer Community kuratiert wurden.\n" "\n" -"Dateien auf dieser Seite werden von Dritten unter einer proprietären Lizenz " -"bereitgestellt. Indem Sie sie installieren, stimmen Sie den jeweiligen " -"Lizenzbedingungen zu." +"Die Dateien auf dieser Seite werden von Dritten unter einer proprietären " +"Lizenz bereitgestellt. Wenn du sie installierst, erklärst du dich mit den " +"jeweiligen Lizenzbedingungen einverstanden." #: bottles/frontend/ui/details-installers.blp:29 msgid "No Installers Found" @@ -676,7 +669,6 @@ msgid "Updating VKD3D, please wait…" msgstr "Aktualisiere VKD3D, bitte warten…" #: bottles/frontend/ui/details-preferences.blp:54 -#, fuzzy msgid "DXVK NVAPI" msgstr "DXVK-NVAPI" @@ -691,21 +683,18 @@ msgid "LatencyFleX" msgstr "LatencyFleX" #: bottles/frontend/ui/details-preferences.blp:69 -#, fuzzy msgid "Increase responsiveness. Can be detected by some anti-cheat software." msgstr "" -"Eine Alternative zu NVIDIA Reflex. Kann von einigen Anticheatprogrammen " +"Erhöhung der Reaktionsfähigkeit. Kann von einigen Anti-Cheat-Software " "erkannt werden." #: bottles/frontend/ui/details-preferences.blp:71 -#, fuzzy msgid "Updating LatencyFleX, please wait…" -msgstr "Aktualisiere DXVK, bitte warten…" +msgstr "Aktualisiere LatencyFleX, bitte warten…" #: bottles/frontend/ui/details-preferences.blp:84 -#, fuzzy msgid "Display" -msgstr "Anzeigeeinstellungen" +msgstr "Anzeige" #: bottles/frontend/ui/details-preferences.blp:88 msgid "Deep Learning Super Sampling" @@ -721,17 +710,16 @@ msgstr "" #: bottles/frontend/ui/details-preferences.blp:105 msgid "FidelityFX Super Resolution" -msgstr "" +msgstr "FidelityFX Super Resolution" #: bottles/frontend/ui/details-preferences.blp:106 msgid "Increase performance at the expense of visuals. Only works on Vulkan." msgstr "" -"Verbessert die Performance zu Lasten der Graphik. Funktioniert nur auf " -"Vulkan." +"Erhöht die Leistung auf Kosten der Grafik. Funktioniert nur unter Vulkan." #: bottles/frontend/ui/details-preferences.blp:108 msgid "Manage FidelityFX Super Resolution settings" -msgstr "" +msgstr "Verwalte die FidelityFX Super Resolution-Einstellungen" #: bottles/frontend/ui/details-preferences.blp:125 msgid "Discrete Graphics" @@ -746,12 +734,10 @@ msgstr "" "Stromverbrauchs." #: bottles/frontend/ui/details-preferences.blp:135 -#, fuzzy msgid "Post-Processing Effects" -msgstr "Nachträgliche Effekte" +msgstr "Nachbearbeitungseffekte" #: bottles/frontend/ui/details-preferences.blp:136 -#, fuzzy msgid "" "Add various post-processing effects using vkBasalt. Only works on Vulkan." msgstr "" @@ -759,13 +745,14 @@ msgstr "" "mit Vulkan." #: bottles/frontend/ui/details-preferences.blp:138 -#, fuzzy msgid "Manage Post-Processing Layer settings" -msgstr "Konfiguration der nachträglichen Effekte" +msgstr "Einstellungen der Nachbearbeitungsebene verwalten" #: bottles/frontend/ui/details-preferences.blp:154 msgid "Manage how games should be displayed on the screen using Gamescope." msgstr "" +"Verwalte mit Gamescope, wie Spiele auf dem Bildschirm angezeigt werden " +"sollen." #: bottles/frontend/ui/details-preferences.blp:157 msgid "Manage Gamescope settings" @@ -814,6 +801,8 @@ msgid "" "Display monitoring information such as framerate, temperatures, CPU/GPU load " "and more on OpenGL and Vulkan using MangoHud." msgstr "" +"Zeige mit MangoHud Überwachungsinformationen wie Framerate, Temperaturen, " +"CPU/GPU-Last und mehr zu OpenGL und Vulkan an." #: bottles/frontend/ui/details-preferences.blp:211 msgid "Feral GameMode" @@ -823,36 +812,36 @@ msgstr "Feral GameMode" msgid "" "Apply a set of optimizations to your device. Can improve game performance." msgstr "" +"Wende eine Reihe von Optimierungen auf deinem Gerät an. Kann die " +"Spielleistung verbessern." #: bottles/frontend/ui/details-preferences.blp:221 -#, fuzzy msgid "Preload Game Files" -msgstr "Vorladen von Spieldateien in den RAM" +msgstr "Spiel-Dateien vorladen" #: bottles/frontend/ui/details-preferences.blp:222 msgid "" "Improve loading time when launching the game multiple times. The game will " "take longer to start for the first time." msgstr "" +"Verbessert die Ladezeit beim mehrfachen Starten des Spiels. Das Spiel " +"braucht beim ersten Mal länger, um zu starten." #: bottles/frontend/ui/details-preferences.blp:226 msgid "Manage vmtouch settings" msgstr "Verwalten der vmtouch-Einstellungen" #: bottles/frontend/ui/details-preferences.blp:241 -#, fuzzy msgid "OBS Game Capture" -msgstr "OBS Vulkan Capture" +msgstr "OBS Spiele-Aufnahme" #: bottles/frontend/ui/details-preferences.blp:242 -#, fuzzy msgid "Toggle OBS Game Capture for all Vulkan and OpenGL programs." -msgstr "De(-aktiviere) OBS Game Capture für alle Programme." +msgstr "Umschalten der OBS-Spielaufnahme für alle Vulkan- und OpenGL-Programme." #: bottles/frontend/ui/details-preferences.blp:251 -#, fuzzy msgid "Compatibility" -msgstr "Kompatibilitätsgrad" +msgstr "Kompatibilität" #: bottles/frontend/ui/details-preferences.blp:254 msgid "Windows Version" @@ -876,11 +865,9 @@ msgstr "Dezidierte Sandbox" #: bottles/frontend/ui/details-preferences.blp:276 msgid "Use a restricted/managed environment for this bottle." -msgstr "" -"Verwenden Sie für diese Bottle eine eingeschränkte/verwaltete Umgebung." +msgstr "Verwende für diese Bottle eine eingeschränkte/verwaltete Umgebung." #: bottles/frontend/ui/details-preferences.blp:279 -#, fuzzy msgid "Manage the Sandbox Permissions" msgstr "Sandbox-Berechtigungen verwalten" @@ -925,9 +912,8 @@ msgstr "Auf Standard zurücksetzen" #: bottles/frontend/ui/details-preferences.blp:339 #: bottles/frontend/ui/preferences.blp:157 bottles/frontend/views/new.py:78 #: bottles/frontend/views/preferences.py:210 -#, fuzzy msgid "(Default)" -msgstr "Standard" +msgstr "(Standard)" #: bottles/frontend/ui/details-preferences.blp:347 #: bottles/frontend/ui/dialog-dll-overrides.blp:7 @@ -945,35 +931,36 @@ msgid "Manage Drives" msgstr "Laufwerke verwalten" #: bottles/frontend/ui/details-preferences.blp:381 -#, fuzzy msgid "Automatic Snapshots" -msgstr "Automatische Versionierung" +msgstr "Automatische Schnappschüsse" #: bottles/frontend/ui/details-preferences.blp:382 msgid "" "Automatically create snapshots before installing software or changing " "settings." msgstr "" +"Erstelle automatisch Schnappschüsse, bevor du Software installierst oder " +"Einstellungen änderst." #: bottles/frontend/ui/details-preferences.blp:391 -#, fuzzy msgid "Compression" -msgstr "Komponentenversion" +msgstr "Komprimierung" #: bottles/frontend/ui/details-preferences.blp:392 msgid "" "Compress snapshots to reduce space. This will slow down the creation of " "snapshots." msgstr "" +"Schnappschüsse komprimieren, um Platz zu sparen. Dies wird das Erzeugen von " +"Schnappschüssen verlangsamen." #: bottles/frontend/ui/details-preferences.blp:401 msgid "Use Exclusion Patterns" -msgstr "Verwenden Sie Ausschlusspattern" +msgstr "Ausschlussmuster verwenden" #: bottles/frontend/ui/details-preferences.blp:402 -#, fuzzy msgid "Exclude paths in snapshots." -msgstr "Pfade mit Hilfe von Mustern von der Versionierung ausschließen." +msgstr "Pfade in Schnappschüssen ausschließen." #: bottles/frontend/ui/details-preferences.blp:405 msgid "Manage Patterns" @@ -988,14 +975,14 @@ msgid "Stop process" msgstr "Prozess beenden" #: bottles/frontend/ui/details-versioning.blp:18 -#, fuzzy msgid "No Snapshots Found" -msgstr "Keine Zustände gefunden" +msgstr "Keine Schnappschüsse gefunden" #: bottles/frontend/ui/details-versioning.blp:19 -#, fuzzy msgid "Create your first snapshot to start saving states of your preferences." -msgstr "Erstelle den ersten Zustand, um mit der Versionierung zu beginnen." +msgstr "" +"Erstelle deinen ersten Schnappschuss, um mit dem Speichern des Status deiner " +"Einstellungen zu beginnen." #: bottles/frontend/ui/details-versioning.blp:54 msgid "A short comment" @@ -1006,18 +993,15 @@ msgid "Save the bottle state." msgstr "Speichere den Zustand der Bottle." #: bottles/frontend/ui/details-versioning.blp:78 -#, fuzzy msgid "Create new Snapshot" -msgstr "Neuen Zustand erstellen" +msgstr "Neuen Schnappschuss erstellen" #: bottles/frontend/ui/details.blp:16 -#, fuzzy msgid "Details" -msgstr "Bottle-Details" +msgstr "Einzelheiten" #: bottles/frontend/ui/details.blp:24 bottles/frontend/ui/details.blp:64 #: bottles/frontend/ui/importer.blp:15 -#, fuzzy msgid "Go Back" msgstr "Zurück" @@ -1048,7 +1032,6 @@ msgstr "Neue Bottle erstellen" # Translators: Bottles is a proper noun referring to the app #: bottles/frontend/ui/dialog-crash-report.blp:8 -#, fuzzy msgid "Bottles Crash Report" msgstr "Bottles Absturzbericht" @@ -1066,7 +1049,6 @@ msgid "_Cancel" msgstr "_Abbrechen" #: bottles/frontend/ui/dialog-crash-report.blp:25 -#, fuzzy msgid "Send Report" msgstr "Bericht senden" @@ -1164,7 +1146,7 @@ msgstr "Duplizieren" #: bottles/frontend/ui/dialog-duplicate.blp:49 msgid "Enter a name for the duplicate of the Bottle." -msgstr "Gebe einen Namen für die neue Bottle ein." +msgstr "Gebe einen Namen für das Duplikat der Bottle ein." #: bottles/frontend/ui/dialog-duplicate.blp:69 msgid "Duplicating…" @@ -1175,7 +1157,7 @@ msgstr "Dupliziere…" #: bottles/frontend/ui/dialog-upgrade-versioning.blp:112 #: bottles/frontend/views/new.py:177 msgid "This could take a while." -msgstr "" +msgstr "Das kann eine Weile dauern." #: bottles/frontend/ui/dialog-duplicate.blp:97 msgid "Bottle Duplicated" @@ -1206,7 +1188,7 @@ msgid "" "Define patterns that will be used to prevent some directories to being " "versioned." msgstr "" -"Definieren Sie Pattern, die verwendet werden, um zu verhindern, dass einige " +"Definiere Pattern, die verwendet werden, um zu verhindern, dass einige " "Verzeichnisse versioniert werden." #: bottles/frontend/ui/dialog-exclusion-patterns.blp:31 @@ -1230,16 +1212,15 @@ msgstr "Speichern" #: bottles/frontend/ui/dialog-gamescope.blp:40 msgid "Manage how games should be displayed." -msgstr "" +msgstr "Verwalte, wie Spiele angezeigt werden sollen." #: bottles/frontend/ui/dialog-gamescope.blp:44 msgid "Game Resolution" msgstr "Spielauflösung" #: bottles/frontend/ui/dialog-gamescope.blp:45 -#, fuzzy msgid "Uses the resolution of the video game as a reference in pixels." -msgstr "Verwendet die Auflösung des Videospiels als Referenz." +msgstr "Verwendet die Auflösung des Videospiels als Referenz in Pixel." #: bottles/frontend/ui/dialog-gamescope.blp:48 #: bottles/frontend/ui/dialog-gamescope.blp:85 @@ -1252,37 +1233,32 @@ msgid "Height" msgstr "Höhe" #: bottles/frontend/ui/dialog-gamescope.blp:81 -#, fuzzy msgid "Window Resolution" -msgstr "Windows Version" +msgstr "Fensterauflösung" #: bottles/frontend/ui/dialog-gamescope.blp:82 -#, fuzzy msgid "" "Upscales the resolution when using a resolution higher than the game " "resolution in pixels." msgstr "" "Erhöht die Auflösung, wenn eine Auflösung verwendet wird, die höher ist als " -"die Spielauflösung." +"die Spielauflösung in Pixel." #: bottles/frontend/ui/dialog-gamescope.blp:118 msgid "Miscellaneous" msgstr "Sonstiges" #: bottles/frontend/ui/dialog-gamescope.blp:121 -#, fuzzy msgid "Frame Rate Limit" -msgstr "Framerate-Limit (z. B. 60)" +msgstr "Bildratenbegrenzung" #: bottles/frontend/ui/dialog-gamescope.blp:137 -#, fuzzy msgid "Frame Rate Limit When Unfocused" -msgstr "Framerate-Limit (wenn nicht fokussiert)" +msgstr "Bildratenbegrenzung bei Unfokussierung" #: bottles/frontend/ui/dialog-gamescope.blp:153 -#, fuzzy msgid "Integer Scaling" -msgstr "Ganzzahlige Skalierung verwenden" +msgstr "Ganzzahlige Skalierung" #: bottles/frontend/ui/dialog-gamescope.blp:162 msgid "Window Type" @@ -1298,12 +1274,11 @@ msgstr "Vollbild" #: bottles/frontend/ui/dialog-installer.blp:40 msgid "Do you want to proceed with the installation?" -msgstr "" +msgstr "Möchtest du mit der Installation fortfahren?" #: bottles/frontend/ui/dialog-installer.blp:45 -#, fuzzy msgid "Start Installation" -msgstr "Übersetzungen" +msgstr "Installation starten" #: bottles/frontend/ui/dialog-installer.blp:64 msgid "" @@ -1318,14 +1293,12 @@ msgid "Proceed" msgstr "Fortfahren" #: bottles/frontend/ui/dialog-installer.blp:127 -#, fuzzy msgid "Completed!" msgstr "Fertig!" #: bottles/frontend/ui/dialog-installer.blp:130 -#, fuzzy msgid "Show Programs" -msgstr "Programm einblenden" +msgstr "Programme anzeigen" #: bottles/frontend/ui/dialog-installer.blp:148 msgid "Installation Failed!" @@ -1333,7 +1306,7 @@ msgstr "Installation Fehlgeschlagen!" #: bottles/frontend/ui/dialog-installer.blp:149 msgid "Something went wrong." -msgstr "" +msgstr "Etwas ist schief gelaufen." #: bottles/frontend/ui/dialog-journal.blp:9 msgid "All messages" @@ -1365,7 +1338,7 @@ msgstr "Journal-Browser" #: bottles/frontend/ui/dialog-journal.blp:53 msgid "Change Logging Level." -msgstr "" +msgstr "Protokollierungslevel ändern." #: bottles/frontend/ui/dialog-journal.blp:57 msgid "All" @@ -1400,7 +1373,6 @@ msgstr "" "Wähle ein Skript, das ausgeführt wird, nachdem das Programm gestartet wurde." #: bottles/frontend/ui/dialog-launch-options.blp:70 -#, fuzzy msgid "Choose a Script" msgstr "Wähle ein Skript aus" @@ -1512,7 +1484,7 @@ msgstr "Netzwerk freigeben" #: bottles/frontend/ui/dialog-sandbox.blp:34 msgid "Share Sound" -msgstr "Sound teilen" +msgstr "Klang teilen" #: bottles/frontend/ui/dialog-upgrade-versioning.blp:16 msgid "Upgrade Needed" @@ -1536,7 +1508,6 @@ msgid "The new bottle versioning system has landed." msgstr "Das neue System zur Versionierung von Bottles ist jetzt verfügbar." #: bottles/frontend/ui/dialog-upgrade-versioning.blp:83 -#, fuzzy msgid "" "Bottles has a whole new Versioning System that is not backwards compatible.\n" "\n" @@ -1555,27 +1526,26 @@ msgstr "" "\n" "Um die Versionierung weiterhin zu nutzen, müssen wir das Bottles-Repository " "neu initialisieren. Dabei werden keine Daten aus der Bottle gelöscht, " -"sondern alle bestehenden Zustände gelöscht und ein neuer erstellt. \n" +"sondern alle bestehenden Zustände gelöscht und ein neuer erstellt.\n" "\n" -"Wenn Sie zu einem früheren Zustand zurückkehren müssen, bevor Sie " -"fortfahren, schließen Sie dieses Fenster, stellen Sie den Zustand wieder her " -"und öffnen Sie die Bottle erneut, um dieses Fenster wieder anzuzeigen. \n" +"Wenn du zu einem früheren Schnappschuss zurückkehren musst, bevor du " +"fortfährst, schließe dieses Fenster, stelle den Schnappschuss wieder her und " +"öffne dann die Bottle erneut, um dieses Fenster wieder anzuzeigen.\n" "\n" "Das alte System wird in einer der nächsten Versionen abgeschafft werden." #: bottles/frontend/ui/dialog-upgrade-versioning.blp:103 -#, fuzzy msgid "Re-initializing Repository…" msgstr "Repository wird neu initialisiert…" #: bottles/frontend/ui/dialog-upgrade-versioning.blp:133 msgid "Done! Please restart Bottles." -msgstr "Fertig! Bitte starten Sie Bottles neu." +msgstr "Fertig! Bitte starte Bottles neu." #. Translators: vkBasalt is a Vulkan post processing layer for Linux #: bottles/frontend/ui/dialog-vkbasalt.blp:10 msgid "Post-Processing Effects Settings" -msgstr "" +msgstr "Einstellungen für Nachbearbeitungseffekte" #: bottles/frontend/ui/dialog-vkbasalt.blp:44 msgid "Default" @@ -1627,7 +1597,7 @@ msgstr "Entrauschen" #: bottles/frontend/ui/dialog-vkbasalt.blp:160 msgid "Fast Approximate Anti-Aliasing" -msgstr "Schnelles ungefähres Anti-Aliasing" +msgstr "Schnelles Annäherungs-Anti-Aliasing" #: bottles/frontend/ui/dialog-vkbasalt.blp:163 msgid "Subpixel Quality" @@ -1686,33 +1656,33 @@ msgid "" "DLS sharpness increases the sharpness of a frame. Higher values make the " "frame sharper." msgstr "" -"Die DLS-Schärfe erhöht die Schärfe eines Bildes. Höhere Werte machen das " -"Bild schärfer." +"DLS-Schärfe erhöht die Schärfe eines Bildes. Höhere Werte machen das Bild " +"schärfer." #: bottles/frontend/ui/dialog-vkbasalt.blp:445 msgid "" "DLS denoise decreases the noise of a frame. Higher values make the frame " "softer." msgstr "" -"Die DLS-Entrauschung verringert das Rauschen eines Bildes. Höhere Werte " -"machen das Bild weicher." +"DLS-Entrauschung verringert das Rauschen eines Bildes. Höhere Werte machen " +"das Bild weicher." #: bottles/frontend/ui/dialog-vkbasalt.blp:462 msgid "" "FXAA subpixel quality decreases aliasing at the subpixel level. Higher " "values make the frame softer." msgstr "" -"Die FXAA-Subpixelqualität verringert Aliasing auf Subpixel-Ebene. Höhere " -"Werte machen das Bild weicher." +"FXAA-Subpixelqualität verringert Aliasing auf Subpixel-Ebene. Höhere Werte " +"machen das Bild weicher." #: bottles/frontend/ui/dialog-vkbasalt.blp:479 msgid "" "FXAA edge threshold is the minimum amount of contrast required to apply the " "FXAA algorithm. Higher values make the frame have more contrast." msgstr "" -"Die FXAA-Kantenschwelle ist der Mindestkontrast, der für die Anwendung des " -"FXAA-Algorithmus erforderlich ist. Höhere Werte bewirken, dass das Bild mehr " -"Kontrast aufweist." +"FXAA-Kantenschwelle ist der Mindestkontrast, der für die Anwendung des FXAA-" +"Algorithmus erforderlich ist. Höhere Werte bewirken, dass das Bild mehr " +"Kontrast hat." #: bottles/frontend/ui/dialog-vkbasalt.blp:496 msgid "" @@ -1738,7 +1708,7 @@ msgid "" "SMAA threshold specifies the sensitivity of edge detection. Lower values " "detect more edges at the expense of performance." msgstr "" -"Der SMAA-Schwellenwert gibt die Empfindlichkeit der Kantenerkennung an. " +"SMAA-Schwellenwert gibt die Empfindlichkeit der Kantenerkennung an. " "Niedrigere Werte erkennen mehr Kanten auf Kosten der Leistung." #: bottles/frontend/ui/dialog-vkbasalt.blp:547 @@ -1779,7 +1749,7 @@ msgstr "Nativ, dann Integrierte" #: bottles/frontend/ui/dll-override-entry.blp:12 msgid "Disabled" -msgstr "deaktiviert" +msgstr "Deaktiviert" #: bottles/frontend/ui/dll-override-entry.blp:20 #: bottles/frontend/ui/drive-entry.blp:12 @@ -1801,23 +1771,20 @@ msgstr "Dateien durchsuchen" #. Translators: A Wine prefix is a separate environment (C:\ drive) for the Wine program #: bottles/frontend/ui/importer-entry.blp:21 -#, fuzzy msgid "Wine prefix name" -msgstr "Wineprefix Name" +msgstr "Wine-Präfixname" #: bottles/frontend/ui/importer-entry.blp:28 msgid "Manager" msgstr "Manager" #: bottles/frontend/ui/importer-entry.blp:38 -#, fuzzy msgid "This Wine prefix was already imported in Bottles." msgstr "Dieser Wine-Präfix wurde bereits in Bottles importiert." #: bottles/frontend/ui/importer.blp:22 -#, fuzzy msgid "Import a Bottle backup" -msgstr "Bottles-Sicherung importieren" +msgstr "Eine Bottles-Sicherung importieren" #: bottles/frontend/ui/importer.blp:28 msgid "Search again for prefixes" @@ -1828,13 +1795,12 @@ msgid "No Prefixes Found" msgstr "Keine Präfixe gefunden" #: bottles/frontend/ui/importer.blp:39 -#, fuzzy msgid "" "No external prefixes were found. Does Bottles have access to them?\n" "Use the icon on the top to import a bottle from a backup." msgstr "" -"Es wurden keine Präfixe von Lutris, PlayOnLinux, etc. gefunden.\n" -"Verwende das Symbol oben, um eine Bottle von einer Sicherung zu importieren." +"Es wurden keine externen Präfixe gefunden. Hat Bottles Zugriff darauf?\n" +"Verwende das Symbol oben, um eine Bottle aus einer Sicherung zu importieren." #: bottles/frontend/ui/importer.blp:74 msgid "Full Archive" @@ -1850,34 +1816,31 @@ msgstr "Rezension lesen…" #: bottles/frontend/ui/installer-entry.blp:34 msgid "Installer name" -msgstr "Name des Installers" +msgstr "Name des Installateurs" #: bottles/frontend/ui/installer-entry.blp:35 msgid "Installer description" -msgstr "Beschreibung des Installers" +msgstr "Beschreibung des Installateurs" #: bottles/frontend/ui/installer-entry.blp:42 msgid "Unknown" msgstr "Unbekannt" #: bottles/frontend/ui/installer-entry.blp:51 -#, fuzzy msgid "Install this Program" msgstr "Dieses Programm installieren" #: bottles/frontend/ui/installer-entry.blp:69 -#, fuzzy msgid "Program Menu" -msgstr "Programmname" +msgstr "Programm-Menü" #: bottles/frontend/ui/library-entry.blp:36 msgid "No Thumbnail" -msgstr "" +msgstr "Kein Vorschaubild" #: bottles/frontend/ui/library-entry.blp:57 -#, fuzzy msgid "Launch" -msgstr "Startoptionen" +msgstr "Starten" #: bottles/frontend/ui/library-entry.blp:70 #: bottles/frontend/ui/program-entry.blp:89 @@ -1894,7 +1857,7 @@ msgstr "Aus Bibliothek entfernen" #: bottles/frontend/ui/library-entry.blp:143 msgid "Stop" -msgstr "" +msgstr "Stopp" #: bottles/frontend/ui/library.blp:11 #: bottles/frontend/windows/main_window.py:196 @@ -1914,7 +1877,6 @@ msgid "This bottle looks damaged." msgstr "Diese Bottle sieht beschädigt aus." #: bottles/frontend/ui/list-entry.blp:55 -#, fuzzy msgid "Execute in this Bottle" msgstr "In dieser Bottle ausführen" @@ -1945,9 +1907,8 @@ msgid "Bottles" msgstr "Bottles" #: bottles/frontend/ui/list.blp:49 -#, fuzzy msgid "Create New Bottle…" -msgstr "Neue Bottle erstellen" +msgstr "Neue Bottle erstellen…" #: bottles/frontend/ui/list.blp:63 msgid "No Results Found" @@ -1970,9 +1931,8 @@ msgid "Browse" msgstr "Durchsuchen" #: bottles/frontend/ui/new.blp:32 -#, fuzzy msgid "C_reate" -msgstr "Erstellen" +msgstr "E_rstellen" #: bottles/frontend/ui/new.blp:53 #, fuzzy @@ -1980,28 +1940,24 @@ msgid "Bottle Name" msgstr "Bottle-Name" #: bottles/frontend/ui/new.blp:75 -#, fuzzy msgid "_Application" -msgstr "Anwendungen" +msgstr "_Anwendung" #: bottles/frontend/ui/new.blp:88 -#, fuzzy msgid "_Gaming" -msgstr "Spiele" +msgstr "_Spiele" #: bottles/frontend/ui/new.blp:101 -#, fuzzy msgid "C_ustom" -msgstr "Benutzerdefiniert" +msgstr "Ben_utzerdefiniert" #: bottles/frontend/ui/new.blp:114 msgid "Custom" msgstr "Benutzerdefiniert" #: bottles/frontend/ui/new.blp:118 -#, fuzzy msgid "Share User Directory" -msgstr "Wähle ein Verzeichnis" +msgstr "Benutzerverzeichnis freigeben" #: bottles/frontend/ui/new.blp:119 msgid "" @@ -2009,40 +1965,38 @@ msgid "" "sharing personal information to Windows software. This option cannot be " "changed after the bottle has been created." msgstr "" +"Dadurch wird das Benutzerverzeichnis in der Bottle auffindbar, auch auf die " +"Gefahr hin, dass persönliche Informationen an Windows-Software weitergegeben " +"werden. Diese Option kann nicht geändert werden, nachdem die Bottle erstellt " +"wurde." #: bottles/frontend/ui/new.blp:136 msgid "Architecture" msgstr "Architektur" #: bottles/frontend/ui/new.blp:137 -#, fuzzy msgid "32-bit should only be used if strictly necessary." -msgstr "" -"Wir empfehlen 32-bit nur dann zu nutzen, wenn es zwingend erforderlich ist." +msgstr "32-Bit sollte nur verwendet werden, wenn es unbedingt notwendig ist." #: bottles/frontend/ui/new.blp:146 -#, fuzzy msgid "Import a custom configuration." -msgstr "Konfiguration exportieren…" +msgstr "Eine benutzerdefinierte Konfiguration importieren." #: bottles/frontend/ui/new.blp:176 -#, fuzzy msgid "Bottle Directory" -msgstr "Wähle ein Verzeichnis" +msgstr "Bottleverzeichnis" #: bottles/frontend/ui/new.blp:177 msgid "Directory that will contain the data of this bottle." -msgstr "" +msgstr "Verzeichnis, das die Daten dieser Bottle enthalten wird." #: bottles/frontend/ui/new.blp:249 -#, fuzzy msgid "_Close" -msgstr "Schließen" +msgstr "S_chließen" #: bottles/frontend/ui/new.blp:281 -#, fuzzy msgid "This name is unavailable, please try another." -msgstr "Diese Funktion ist auf deinem System nicht verfügbar." +msgstr "Dieser Name ist nicht verfügbar, bitte versuche einen anderen." #: bottles/frontend/ui/onboard.blp:34 msgid "Previous" @@ -2080,13 +2034,12 @@ msgid "We need a few more minutes to set everything up…" msgstr "Wir brauchen noch ein paar Minuten, um alles einzurichten…" #: bottles/frontend/ui/onboard.blp:105 -#, fuzzy msgid "All Ready!" msgstr "Alles bereit!" #: bottles/frontend/ui/onboard.blp:114 msgid "Please Finish the setup first" -msgstr "Bitte beenden Sie zuerst die Einrichtung" +msgstr "Bitte beende zuerst die Einrichtung" # Translators: Bottles is a proper noun referring to the app #: bottles/frontend/ui/onboard.blp:120 @@ -2116,7 +2069,7 @@ msgstr "Dunkler Modus" #: bottles/frontend/ui/preferences.blp:18 msgid "Whether Bottles should use the dark color scheme." -msgstr "Ob Flaschen das dunkle Farbschema verwenden sollen." +msgstr "Ob Bottles das dunkle Farbschema verwenden sollen." #: bottles/frontend/ui/preferences.blp:28 msgid "Show Update Date" @@ -2170,7 +2123,7 @@ msgstr "Steam-Apps in der Programmliste auflisten" #: bottles/frontend/ui/preferences.blp:98 msgid "Requires Steam for Windows installed in the bottle." -msgstr "Erfordert Steam für Windows, welcher im Bottle installiert ist." +msgstr "Erfordert Steam für Windows, das in der Bottle installiert ist." #: bottles/frontend/ui/preferences.blp:107 msgid "List Epic Games in Programs List" @@ -2193,24 +2146,20 @@ msgid "Advanced" msgstr "Erweitert" #: bottles/frontend/ui/preferences.blp:131 -#, fuzzy msgid "Bottles Directory" -msgstr "Wähle ein Verzeichnis" +msgstr "Bottles-Verzeichnis" #: bottles/frontend/ui/preferences.blp:132 msgid "Directory that contains the data of your Bottles." -msgstr "" +msgstr "Verzeichnis, das die Daten Ihrer Bottles enthält." #: bottles/frontend/ui/preferences.blp:167 msgid "Runners" msgstr "Runner" #: bottles/frontend/ui/preferences.blp:181 -#, fuzzy msgid "Bottles is running in offline mode, so runners are not available." -msgstr "" -"Bottles daran hintern Ausführbare Dateien auszuführen, wenn der Runner nicht " -"verfügbar ist" +msgstr "Bottles läuft im Offline-Modus, so dass keine Runner verfügbar sind." #: bottles/frontend/ui/preferences.blp:208 msgid "Pre-Release" @@ -2226,7 +2175,7 @@ msgstr "DLL-Komponenten" #: bottles/frontend/ui/preferences.blp:238 msgid "Bottles is running in offline mode, so DLLs are not available." -msgstr "" +msgstr "Bottles läuft im Offlinemodus, daher sind die DLLs nicht verfügbar." #: bottles/frontend/ui/preferences.blp:270 msgid "DXVK-NVAPI" @@ -2266,12 +2215,10 @@ msgid "In early development." msgstr "Noch in früher Entwicklung." #: bottles/frontend/ui/program-entry.blp:19 -#, fuzzy msgid "Launch with Terminal" msgstr "In der Konsole ausführen" #: bottles/frontend/ui/program-entry.blp:25 -#, fuzzy msgid "Browse Path" msgstr "Pfad durchsuchen" @@ -2280,9 +2227,8 @@ msgid "Change Launch Options…" msgstr "Startoptionen ändern…" #: bottles/frontend/ui/program-entry.blp:43 -#, fuzzy msgid "Add to Library" -msgstr "Zu meiner Bibliothek hinzufügen" +msgstr "Zur Bibliothek hinzufügen" #: bottles/frontend/ui/program-entry.blp:47 msgid "Add Desktop Entry" @@ -2322,9 +2268,8 @@ msgid "State comment" msgstr "Status-Kommentar" #: bottles/frontend/ui/state-entry.blp:16 -#, fuzzy msgid "Restore this Snapshot" -msgstr "Diesen Zustand wiederherstellen" +msgstr "Diesen Schnappschuss wiederherstellen" #: bottles/frontend/ui/task-entry.blp:19 msgid "Delete message" @@ -2332,7 +2277,7 @@ msgstr "Nachricht löschen" #: bottles/frontend/ui/window.blp:40 msgid "Main Menu" -msgstr "" +msgstr "Hauptmenü" #: bottles/frontend/ui/window.blp:54 msgid "" @@ -2345,13 +2290,12 @@ msgstr "" "dieses Symbol, wenn du die Verbindung wiederhergestellt hast." #: bottles/frontend/ui/window.blp:79 -#, fuzzy msgid "Import…" msgstr "Importieren…" #: bottles/frontend/ui/window.blp:91 msgid "Help" -msgstr "" +msgstr "Hilfe" # Translators: Bottles is a proper noun referring to the app #: bottles/frontend/ui/window.blp:96 @@ -2361,7 +2305,7 @@ msgstr "Über Bottles" #: bottles/frontend/views/bottle_details.py:191 #, python-brace-format msgid "File \"{0}\" is not a .exe or .msi file" -msgstr "" +msgstr "Datei „{0}“ ist keine .exe- oder .msi-Datei" #: bottles/frontend/views/bottle_details.py:207 #, python-format @@ -2371,34 +2315,32 @@ msgstr "Aktualisiert: %s" #: bottles/frontend/views/bottle_details.py:267 #, python-brace-format msgid "\"{0}\" added" -msgstr "'{0}' hinzugefügt" +msgstr "„{0}“ hinzugefügt" #: bottles/frontend/views/bottle_details.py:270 #: bottles/frontend/views/bottle_details.py:398 #: bottles/frontend/views/list.py:128 -#, fuzzy msgid "Select Executable" -msgstr "Bottle auswählen" +msgstr "Ausführbare Datei auswählen" #: bottles/frontend/views/bottle_details.py:273 msgid "Add" msgstr "Hinzufügen" #: bottles/frontend/views/bottle_details.py:346 -#, fuzzy msgid "Hide Hidden Programs" -msgstr "Versteckte Programme ein-/ausblenden" +msgstr "Versteckte Programme ausblenden" #: bottles/frontend/views/bottle_details.py:383 #: bottles/frontend/widgets/library.py:156 #: bottles/frontend/widgets/program.py:184 -#, fuzzy, python-brace-format +#, python-brace-format msgid "Launching \"{0}\"…" -msgstr "Starten von '{0}'…" +msgstr "Starten von „{0}“…" #: bottles/frontend/views/bottle_details.py:413 msgid "Be Aware of Sandbox" -msgstr "Achten Sie auf die Sandbox" +msgstr "Sei dir der Sandbox bewusst" #: bottles/frontend/views/bottle_details.py:414 msgid "" @@ -2415,7 +2357,7 @@ msgstr "" #: bottles/frontend/views/bottle_details.py:525 #: bottles/frontend/windows/main_window.py:223 msgid "_Dismiss" -msgstr "" +msgstr "_Verwerfen" #: bottles/frontend/views/bottle_details.py:429 msgid "Select the location where to save the backup config" @@ -2430,107 +2372,101 @@ msgid "Select the location where to save the backup archive" msgstr "Wähle den Speicherort für das Sicherungsarchiv" #: bottles/frontend/views/bottle_details.py:435 -#, fuzzy msgid "Backup" -msgstr "Sicherung {0}" +msgstr "Sicherung" #: bottles/frontend/views/bottle_details.py:440 #, python-brace-format msgid "Backup created for \"{0}\"" -msgstr "Sicherung im Pfad gespeichert: {0}" +msgstr "Sicherung für „{0}“ erstellt" #: bottles/frontend/views/bottle_details.py:442 #, python-brace-format msgid "Backup failed for \"{0}\"" -msgstr "Sicherung fehlgeschlagen für Pfad {0}" +msgstr "Sicherung für „{0}“ fehlgeschlagen" #: bottles/frontend/views/bottle_details.py:501 -#, fuzzy msgid "Are you sure you want to permanently delete \"{}\"?" -msgstr "" -"Bist du dir sicher, dass du diese Bottle und alle Dateien darin löschen " -"möchtest?" +msgstr "Bist du sicher, dass du „{}“ dauerhaft löschen möchtest?" #: bottles/frontend/views/bottle_details.py:502 msgid "" "This will permanently delete all programs and settings associated with it." msgstr "" +"Dadurch werden alle damit verbundenen Programme und Einstellungen dauerhaft " +"gelöscht." #: bottles/frontend/views/bottle_details.py:505 #: bottles/frontend/views/bottle_preferences.py:750 msgid "_Delete" -msgstr "" +msgstr "_Löschen" #: bottles/frontend/views/bottle_details.py:521 -#, fuzzy msgid "Missing Runner" -msgstr "Wine Runner" +msgstr "Fehlender Runner" #: bottles/frontend/views/bottle_details.py:522 msgid "" "The runner requested by this bottle is missing. Install it through the " "Bottles preferences or choose a new one to run applications." msgstr "" +"Der für diese Bottle angeforderte Runner fehlt. Installiere ihn über die " +"Bottles-Einstellungen oder wähle einen neuen aus, um Anwendungen auszuführen." #: bottles/frontend/views/bottle_details.py:597 -#, fuzzy msgid "Are you sure you want to force stop all processes?" -msgstr "" -"Bist du dir sicher, dass du diese Bottle und alle Dateien darin löschen " -"möchtest?" +msgstr "Bist du sicher, dass du das Anhalten aller Prozesse erzwingen willst?" #: bottles/frontend/views/bottle_details.py:598 msgid "This can cause data loss, corruption, and programs to malfunction." msgstr "" +"Dies kann zu Datenverlust, Beschädigung und Fehlfunktionen von Programmen " +"führen." #: bottles/frontend/views/bottle_details.py:601 msgid "Force _Stop" -msgstr "" +msgstr "_Stopp erzwingen" #: bottles/frontend/views/bottle_preferences.py:195 -#, fuzzy msgid "This feature is unavailable on your system." msgstr "Diese Funktion ist auf deinem System nicht verfügbar." #: bottles/frontend/views/bottle_preferences.py:196 msgid "{} To add this feature, please run flatpak install" -msgstr "" +msgstr "{} Um diese Funktion hinzuzufügen, führe bitte „flatpak install“ aus" #: bottles/frontend/views/bottle_preferences.py:246 -#, fuzzy msgid "This bottle name is already in use." -msgstr "Der Name enthält Sonderzeichen oder wird bereits verwendet." +msgstr "Dieser Bottle name wird bereits verwendet." #: bottles/frontend/views/bottle_preferences.py:301 #: bottles/frontend/windows/launchoptions.py:241 -#, fuzzy msgid "Select Working Directory" -msgstr "Arbeitsverzeichnis" +msgstr "Arbeitsverzeichnis auswählen" #: bottles/frontend/views/bottle_preferences.py:423 msgid "Directory that contains the data of \"{}\"." -msgstr "" +msgstr "Verzeichnis, das die Daten von „{}“ enthält." #: bottles/frontend/views/bottle_preferences.py:746 -#, fuzzy msgid "Are you sure you want to delete all snapshots?" -msgstr "" -"Bist du dir sicher, dass du diese Bottle und alle Dateien darin löschen " -"möchtest?" +msgstr "Bist du sicher, dass du alle Schnappschüsse löschen möchtest?" #: bottles/frontend/views/bottle_preferences.py:747 msgid "This will delete all snapshots but keep your files." msgstr "" +"Dadurch werden alle Schnappschüsse gelöscht, deine Dateien bleiben jedoch " +"erhalten." #: bottles/frontend/views/bottle_versioning.py:90 msgid "Please migrate to the new Versioning system to create new states." msgstr "" -"Bitte migrieren Sie auf das neue Versionierungssystem, um einen neuen Status " -"zu erstellen." +"Bitte migriere auf das neue Versionierungssystem, um einen neuen Status zu " +"erstellen." #: bottles/frontend/views/details.py:153 msgid "Installers" -msgstr "Installer" +msgstr "Installateure" #: bottles/frontend/views/details.py:234 msgid "Operations in progress, please wait." @@ -2538,7 +2474,7 @@ msgstr "Vorgang läuft, bitte warten." #: bottles/frontend/views/details.py:239 msgid "Return to your bottles." -msgstr "Kehren Sie zu Ihren Bottles zurück." +msgstr "Kehre zu deinen Bottles zurück." #: bottles/frontend/views/importer.py:92 msgid "Backup imported successfully" @@ -2554,7 +2490,6 @@ msgid "Importing backup…" msgstr "Sicherung importieren…" #: bottles/frontend/views/importer.py:119 -#, fuzzy msgid "Select a Backup Archive" msgstr "Wähle ein Sicherungsarchiv" @@ -2564,9 +2499,8 @@ msgid "Import" msgstr "Importieren" #: bottles/frontend/views/importer.py:158 bottles/frontend/views/new.py:136 -#, fuzzy msgid "Select a Configuration File" -msgstr "Wähle eine Konfigurationsdatei" +msgstr "Eine Konfigurationsdatei auswählen" #: bottles/frontend/views/list.py:60 bottles/frontend/views/list.py:66 msgid "N/A" @@ -2576,16 +2510,16 @@ msgstr "nicht verfügbar" #: bottles/frontend/views/list.py:91 #, python-brace-format msgid "Run executable in \"{self.config.Name}\"" -msgstr "" +msgstr "Ausführbare Datei in „{self.config.Name}“ ausführen" #: bottles/frontend/views/list.py:118 -#, fuzzy, python-brace-format +#, python-brace-format msgid "Launching \"{0}\" in \"{1}\"…" -msgstr "Starten von '{0}'…" +msgstr "„{0}“ wird in „{1}“ gestartet…" #: bottles/frontend/views/list.py:235 msgid "Your Bottles" -msgstr "Über Bottles" +msgstr "Deine Bottles" #: bottles/frontend/views/loading.py:41 #, python-brace-format @@ -2595,37 +2529,34 @@ msgstr "Herunterladen von ~{0} Paketen…" #: bottles/frontend/views/loading.py:42 #, python-brace-format msgid "Fetched {0} of {1} packages" -msgstr "{0} von {1} Paketen abgerufen" +msgstr "Geholt {0} von {1} Paketen" #: bottles/frontend/views/new.py:157 -#, fuzzy msgid "Select Bottle Directory" -msgstr "Wähle ein Verzeichnis" +msgstr "Bottle-Verzeichnis wählen" #: bottles/frontend/views/new.py:176 -#, fuzzy msgid "Creating Bottle…" -msgstr "Eine Bottle erstellen…" +msgstr "Bottle wird erstellt…" #: bottles/frontend/views/new.py:221 -#, fuzzy msgid "Unable to Create Bottle" -msgstr "Neue Bottle erstellen" +msgstr "Kann Bottle nicht erstellen" #: bottles/frontend/views/new.py:225 msgid "Bottle failed to create with one or more errors." msgstr "" +"Die Erstellung der Bottle ist mit einem oder mehreren Fehlern fehlgeschlagen." #. Show success #: bottles/frontend/views/new.py:232 -#, fuzzy msgid "Bottle Created" msgstr "Bottle erstellt" #: bottles/frontend/views/new.py:233 #, python-brace-format msgid "\"{0}\" was created successfully." -msgstr "Neuen Zustand [{0}] erfolgreich erstellt." +msgstr "„{0}“ wurde erfolgreich erstellt." #: bottles/frontend/views/preferences.py:142 msgid "Steam was not found or Bottles does not have enough permissions." @@ -2633,9 +2564,8 @@ msgstr "" "Steam wurde nicht gefunden oder Bottles hat nicht genügend Berechtigungen." #: bottles/frontend/views/preferences.py:176 -#, fuzzy msgid "Select Bottles Path" -msgstr "Bottle auswählen" +msgstr "Bottle-Pfad auswählen" #: bottles/frontend/views/preferences.py:198 msgid "Relaunch Bottles?" @@ -2649,10 +2579,16 @@ msgid "" "Bottles, as not doing so can cause data loss, corruption and programs to " "malfunction." msgstr "" +"Bottles muss neu gestartet werden, um dieses Verzeichnis verwenden zu können." +"\n" +"\n" +"Stelle sicher, dass jedes Programm, das von Bottles gestartet wurde, " +"geschlossen ist, bevor Bottles wieder ausgeführt wird. Andernfalls kann es " +"zu Datenverlust, Beschädigung und Fehlfunktionen der Programme kommen." #: bottles/frontend/views/preferences.py:202 msgid "_Relaunch" -msgstr "" +msgstr "_Neustart" #: bottles/frontend/views/preferences.py:243 msgid "Based on Valve's Wine, includes staging and Proton patches." @@ -2694,24 +2630,24 @@ msgid "Manifest for {0}" msgstr "Manifest für {0}" #: bottles/frontend/widgets/dependency.py:172 -#, fuzzy, python-brace-format +#, python-brace-format msgid "\"{0}\" uninstalled" -msgstr "{0} installiert" +msgstr "„{0}“ deinstalliert" #: bottles/frontend/widgets/dependency.py:174 #, python-brace-format msgid "\"{0}\" installed" -msgstr "{0} installiert" +msgstr "„{0}“ installiert" #: bottles/frontend/widgets/dependency.py:188 #, python-brace-format msgid "\"{0}\" failed to install" -msgstr "'{0}' konnte nicht installiert werden" +msgstr "„{0}“ konnte nicht installiert werden" #: bottles/frontend/widgets/importer.py:68 #, python-brace-format msgid "\"{0}\" imported" -msgstr "'{0}' importiert" +msgstr "„{0}“ importiert" #: bottles/frontend/widgets/installer.py:49 msgid "" @@ -2719,71 +2655,77 @@ msgid "" "the best possible experience, but expect glitches, instability and lack of " "working features." msgstr "" +"Diese Anwendung funktioniert möglicherweise nicht ordnungsgemäß. Das " +"Installationsprogramm wurde so konfiguriert, dass es die bestmögliche " +"Benutzererfahrung bietet. Allerdings muss man mit Störungen, Instabilität " +"und dem Fehlen funktionierender Funktionen rechnen." #: bottles/frontend/widgets/installer.py:50 msgid "" "This program works with noticeable glitches, but these glitches do not " "affect the application's functionality." msgstr "" +"Dieses Programm funktioniert mit erkennbaren Störungen, diese " +"beeinträchtigen jedoch nicht die Funktionalität der Anwendung." #: bottles/frontend/widgets/installer.py:51 msgid "This program works with minor glitches." -msgstr "" +msgstr "Dieses Programm funktioniert mit kleineren Störungen." #: bottles/frontend/widgets/installer.py:52 msgid "This program works perfectly." -msgstr "" +msgstr "Dieses Programm funktioniert perfekt." #: bottles/frontend/widgets/installer.py:90 #, python-brace-format msgid "Review for {0}" -msgstr "Review für {0}" +msgstr "Rezension für {0}" #: bottles/frontend/widgets/library.py:169 #: bottles/frontend/widgets/program.py:194 -#, fuzzy, python-brace-format +#, python-brace-format msgid "Stopping \"{0}\"…" -msgstr "Starten von '{0}'…" +msgstr "Anhalten von „{0}“…" #: bottles/frontend/widgets/program.py:190 -#, fuzzy, python-brace-format +#, python-brace-format msgid "Launching \"{0}\" with Steam…" -msgstr "Starte {0} mit Steam…" +msgstr "Starte „{0}“ mit Steam…" #: bottles/frontend/widgets/program.py:214 #, python-brace-format msgid "\"{0}\" hidden" -msgstr "'{0}' versteckt" +msgstr "„{0}“ versteckt" #: bottles/frontend/widgets/program.py:216 #, python-brace-format msgid "\"{0}\" showed" -msgstr "'{0}' angezeigt" +msgstr "„{0}“ angezeigt" #: bottles/frontend/widgets/program.py:242 #, python-brace-format msgid "\"{0}\" removed" -msgstr "'{0}' entfernt" +msgstr "„{0}“ entfernt" #: bottles/frontend/widgets/program.py:274 #, python-brace-format msgid "\"{0}\" renamed to \"{1}\"" -msgstr "'{0}' umbenannt zu '{1}'" +msgstr "„{0}“ umbenannt zu „{1}“" #: bottles/frontend/widgets/program.py:297 -#, fuzzy, python-brace-format +#, python-brace-format msgid "Desktop Entry created for \"{0}\"" -msgstr "Desktop Eintrag für '{0}' erstellt" +msgstr "Desktop Eintrag für „{0}“ erstellt" #: bottles/frontend/widgets/program.py:313 -#, fuzzy, python-brace-format +#, python-brace-format msgid "\"{0}\" added to your library" -msgstr "'{0}' zu Ihrer Bibliothek hinzugefügt" +msgstr "„{0}“ wurde zur Bibliothek hinzugefügt" #: bottles/frontend/widgets/program.py:331 -#, fuzzy, python-brace-format +#, python-brace-format msgid "\"{0}\" added to your Steam library" -msgstr "'{0}' zur Steam-Bibliothek hinzugefügt" +msgstr "„{0}“ wurde zur Steam-Bibliothek hinzugefügt" #: bottles/frontend/windows/crash.py:33 msgid "Show report" @@ -2799,23 +2741,20 @@ msgstr "" " Schreibe dein Feedback in einen der bereits vorhandenen Berichte." #: bottles/frontend/windows/display.py:102 -#, fuzzy msgid "Updating display settings, please wait…" -msgstr "Windows Version wird geändert, bitte warten…" +msgstr "Anzeigeeinstellungen werden aktualisiert, bitte warten…" #: bottles/frontend/windows/display.py:114 -#, fuzzy msgid "Display settings updated" -msgstr "Anzeigeeinstellungen" +msgstr "Anzeigeeinstellungen aktualisiert" #: bottles/frontend/windows/dlloverrides.py:136 msgid "No overrides found." msgstr "Keinen Override gefunden." #: bottles/frontend/windows/drives.py:71 -#, fuzzy msgid "Select Drive Path" -msgstr "Wählen Sie eine Datei" +msgstr "Laufwerkspfad auswählen" #: bottles/frontend/windows/envvars.py:131 msgid "No environment variables defined." @@ -2836,9 +2775,8 @@ msgid "Copy to clipboard" msgstr "In Zwischenablage kopieren" #: bottles/frontend/windows/installer.py:62 -#, fuzzy msgid "Select Resource File" -msgstr "Wähle die Ressourcen Datei aus" +msgstr "Ressourcendatei auswählen" #: bottles/frontend/windows/installer.py:109 msgid "Installing Windows dependencies…" @@ -2877,16 +2815,15 @@ msgstr "Installationsprogramm fehlgeschlagen mit einem unbekannten Fehler" #: bottles/frontend/windows/launchoptions.py:56 #, python-brace-format msgid "{0} is already disabled for this bottle." -msgstr "{0} ist für diese Flasche bereits deaktiviert." +msgstr "{0} ist für diese Bottle bereits deaktiviert." #: bottles/frontend/windows/launchoptions.py:57 msgid "This setting is different from the bottle's default." msgstr "Diese Einstellung entspricht nicht Bottle's Standardeinstellung." #: bottles/frontend/windows/launchoptions.py:215 -#, fuzzy msgid "Select Script" -msgstr "Wählen Sie eine Datei" +msgstr "Skript auswählen" #: bottles/frontend/windows/main_window.py:220 msgid "Custom Bottles Path not Found" @@ -2901,11 +2838,10 @@ msgstr "" #: data/com.usebottles.bottles.desktop.in.in:3 msgid "@APP_NAME@" -msgstr "" +msgstr "@APP_NAME@" #: data/com.usebottles.bottles.desktop.in.in:4 #: data/com.usebottles.bottles.metainfo.xml.in:8 -#, fuzzy msgid "Run Windows Software" msgstr "Windows-Software ausführen" @@ -2991,7 +2927,7 @@ msgstr "Temporäre Dateien beim Systemstart löschen." #: data/com.usebottles.bottles.gschema.xml:56 msgid "Release Candidate" -msgstr "Vorabversionen" +msgstr "Release-Kandidat" #: data/com.usebottles.bottles.gschema.xml:57 msgid "Toggle release candidate for runners." @@ -3164,72 +3100,66 @@ msgid "... and much more that you can find by installing Bottles!" msgstr "… und vieles mehr. Einfach Bottles installieren und entdecken!" #: data/com.usebottles.bottles.metainfo.xml.in:84 -#, fuzzy msgid "Update metadata information" -msgstr "Bottleinformationen" +msgstr "Metadateninformationen aktualisieren" #: data/com.usebottles.bottles.metainfo.xml.in:89 msgid "Add more update information and correct release notes version" msgstr "" +"Füge weitere Update-Informationen hinzu und korrigiere die Versionshinweise" #: data/com.usebottles.bottles.metainfo.xml.in:94 -#, fuzzy msgid "Fixed \"Add to Steam\" button" -msgstr "Zu Steam hinzufügen" +msgstr "Die Schaltfläche „Zu Steam hinzufügen“ wurde korrigiert" #: data/com.usebottles.bottles.metainfo.xml.in:95 msgid "Fixed BottleConfig being not serializable" -msgstr "" +msgstr "Problem behoben, bei dem BottleConfig nicht serialisierbar war" #: data/com.usebottles.bottles.metainfo.xml.in:96 msgid "Fixed Patool double extraction failing" -msgstr "" +msgstr "Doppelte Patool-Extraktion wurde behoben" #: data/com.usebottles.bottles.metainfo.xml.in:101 -#, fuzzy msgid "Correct version" -msgstr "Komponentenversion" +msgstr "Korrekte Version" #: data/com.usebottles.bottles.metainfo.xml.in:106 -#, fuzzy msgid "Fix crash when creating a bottle" -msgstr "Beim Erstellen der Bottle trat ein Fehler auf." +msgstr "Absturz beim Erstellen einer Bottle behoben" #: data/com.usebottles.bottles.metainfo.xml.in:111 msgid "Major change: Redesign New Bottle interface" -msgstr "" +msgstr "Große Änderung: Neugestaltung der neuen Bottles oberfläche" #: data/com.usebottles.bottles.metainfo.xml.in:112 -#, fuzzy msgid "Quality of life improvements:" -msgstr "Kleinere Verbesserungen der Benutzeroberfläche" +msgstr "Verbesserung der Nutzererfahrung:" #: data/com.usebottles.bottles.metainfo.xml.in:114 msgid "Replace emote-love icon with library in library page" msgstr "" #: data/com.usebottles.bottles.metainfo.xml.in:115 -#, fuzzy msgid "Add toast for \"Run Executable\"" -msgstr "Benutzerdefinierten Pfad für eine ausführbare Datei hinzufügen" +msgstr "Toast für „Ausführbare Datei ausführen“ hinzugefügt" #: data/com.usebottles.bottles.metainfo.xml.in:117 -#, fuzzy msgid "Bug fixes:" -msgstr "Fehlerbehebungen" +msgstr "Fehlerbehebungen:" #: data/com.usebottles.bottles.metainfo.xml.in:119 msgid "Adding shortcut to Steam resulted an error" msgstr "" #: data/com.usebottles.bottles.metainfo.xml.in:120 -#, fuzzy msgid "Importing backups resulted an error" -msgstr "Sicherung importieren: {0}" +msgstr "Beim Importieren von Backups ist ein Fehler aufgetreten" #: data/com.usebottles.bottles.metainfo.xml.in:121 msgid "Steam Runtime automatically enabled when using wine-ge-custom" msgstr "" +"Steam Runtime wird bei Verwendung von wine-ge-custom automatisch aktiviert" #: data/com.usebottles.bottles.metainfo.xml.in:122 msgid "" @@ -3239,16 +3169,17 @@ msgstr "" #: data/com.usebottles.bottles.metainfo.xml.in:123 msgid "Fix various issues related to text encoding" -msgstr "" +msgstr "Behebung verschiedener Probleme im Zusammenhang mit der Textkodierung" #: data/com.usebottles.bottles.metainfo.xml.in:130 msgid "Fix error when downloading if Bottles isn't run from terminal" msgstr "" +"Fehler beim Herunterladen beheben, wenn Bottles nicht vom Terminal aus " +"gestartet wird" #: data/com.usebottles.bottles.metainfo.xml.in:137 -#, fuzzy msgid "Correct version date" -msgstr "Erstelldatum" +msgstr "Korrektes Versionsdatum" #: data/com.usebottles.bottles.metainfo.xml.in:138 msgid "Hide NVIDIA-related critical errors on non NVIDIA systems" @@ -3256,31 +3187,35 @@ msgstr "" #: data/com.usebottles.bottles.metainfo.xml.in:145 msgid "Gamescope improvements and fixes" -msgstr "" +msgstr "Gamescope-Verbesserungen und -Korrekturen" #: data/com.usebottles.bottles.metainfo.xml.in:146 msgid "Dependency installation is faster and more stable" -msgstr "" +msgstr "Die Abhängigkeitsinstallation ist schneller und stabiler" #: data/com.usebottles.bottles.metainfo.xml.in:147 msgid "The health check has more information for faster debugging" msgstr "" +"Der health check verfügt über mehr Informationen für ein schnelleres " +"Debugging" #: data/com.usebottles.bottles.metainfo.xml.in:148 msgid "NVAPI has a lot of fixes and is more stable, should now work properly" msgstr "" +"NVAPI hat viele Korrekturen und ist stabiler, sollte jetzt ordnungsgemäß " +"funktionieren" #: data/com.usebottles.bottles.metainfo.xml.in:149 msgid "Fix crash when downloading a component" -msgstr "" +msgstr "Absturz beim Herunterladen einer Komponente behoben" #: data/com.usebottles.bottles.metainfo.xml.in:150 msgid "Backend code improvement by avoiding spin-lock" -msgstr "" +msgstr "Verbesserung des Backend-Codes durch Vermeidung von Spin-Lock" #: data/com.usebottles.bottles.metainfo.xml.in:151 msgid "More variables for installer scripting" -msgstr "" +msgstr "Weitere Variablen für die Skripterstellung des Installationsprogramms" #: data/com.usebottles.bottles.metainfo.xml.in:152 msgid "Fix onboard dialog showing \"All ready\" while it was in fact not ready" @@ -3288,35 +3223,40 @@ msgstr "" #: data/com.usebottles.bottles.metainfo.xml.in:153 msgid "Improvement to build system" -msgstr "" +msgstr "Verbesserung des Build-Systems" #: data/com.usebottles.bottles.metainfo.xml.in:154 msgid "Enabling VKD3D by default when creating bottles for gaming" -msgstr "" +msgstr "VKD3D standardmäßig aktivieren, wenn Bottles für Spiele erstellt werden" #: data/com.usebottles.bottles.metainfo.xml.in:155 msgid "Fix crashes when reading Steam files with bad encodings" msgstr "" +"Abstürze beim Lesen von Steam-Dateien mit fehlerhaften Codierungen behoben" #: data/com.usebottles.bottles.metainfo.xml.in:156 msgid "" "Fix components not updated correctly in the UI after installation/" "uninstallation" msgstr "" +"Behebung von Komponenten, die nach der Installation/Deinstallation in der " +"Benutzeroberfläche nicht korrekt aktualisiert werden" #: data/com.usebottles.bottles.metainfo.xml.in:157 msgid "More FSR fixes" -msgstr "" +msgstr "Weitere FSR-Korrekturen" #: data/com.usebottles.bottles.metainfo.xml.in:158 msgid "" "Fix the issue when a program closes after it was launched from \"Run " "executable\"" msgstr "" +"Behebung des Problems, wenn ein Programm geschlossen wird, nachdem es über „" +"Ausführbare Datei ausführen“ gestartet wurde" #: data/com.usebottles.bottles.metainfo.xml.in:159 msgid "and many, many, many more!" -msgstr "" +msgstr "und viele, viele, viele mehr!" #~ msgid "Calculating…" #~ msgstr "Berechne…" diff --git a/po/el.po b/po/el.po index 5da7e35683c..1ef5b404cad 100644 --- a/po/el.po +++ b/po/el.po @@ -8,8 +8,8 @@ msgstr "" "Project-Id-Version: bottles\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-03-27 13:50+0530\n" -"PO-Revision-Date: 2022-09-10 07:36+0000\n" -"Last-Translator: Fotios Kolytoumpas \n" +"PO-Revision-Date: 2024-03-07 18:01+0000\n" +"Last-Translator: Giannis Vassilopoulos \n" "Language-Team: Greek \n" "Language: el\n" @@ -17,7 +17,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.14.1-dev\n" +"X-Generator: Weblate 5.5-dev\n" #: bottles/backend/managers/backup.py:48 bottles/backend/managers/backup.py:95 msgid "No path specified" @@ -155,21 +155,20 @@ msgid "Restoring state {} …" msgstr "" #: bottles/backend/managers/versioning.py:162 -#, fuzzy msgid "State not found" -msgstr "Πειράματα:εγκαταστάτες" +msgstr "Η κατάσταση δεν βρέθηκε" #: bottles/backend/managers/versioning.py:168 msgid "State {} is already the active state" -msgstr "" +msgstr "Η κατάσταση {} είναι ήδη ενεργή" #: bottles/frontend/main.py:112 msgid "Show version" -msgstr "" +msgstr "Εμφάνιση έκδοσης" #: bottles/frontend/main.py:120 msgid "Executable path" -msgstr "" +msgstr "Εκτελέσιμη διαδρομή" #: bottles/frontend/main.py:128 msgid "lnk path" @@ -186,47 +185,45 @@ msgstr "" #: bottles/frontend/main.py:203 msgid "Invalid URI (syntax: bottles:run//)" -msgstr "" +msgstr "Μη έγκυρος σύνδεσμος (σύνταξη: bottles:run//<πρόγραμμα>)" #: bottles/frontend/main.py:244 msgid "[Quit] request received." -msgstr "" +msgstr "[Έξοδος] το αίτημα ελήφθη." #: bottles/frontend/main.py:253 msgid "[Help] request received." -msgstr "" +msgstr "[Βοήθεια] Το αίτημα ελήφθη." #: bottles/frontend/main.py:261 msgid "[Refresh] request received." -msgstr "" +msgstr "[Ανανέωση] Το αίτημα ελήφθη." #: bottles/frontend/main.py:294 msgid "Donate" -msgstr "" +msgstr "Δωρεά" #: bottles/frontend/main.py:299 msgid "Third-Party Libraries and Special Thanks" -msgstr "" +msgstr "Βιβλιοθήκες τρίτων και Ειδικές ευχαριστίες" #: bottles/frontend/main.py:325 msgid "Sponsored and Funded by" -msgstr "" +msgstr "Χορηγούμενο και ιδρυθέν από" # Translators: Bottles is a proper noun referring to the app #: bottles/frontend/ui/about.blp:5 -#, fuzzy msgid "Copyright © 2017 Bottles Developers" -msgstr "© 2017-2021 - Προγραμματιστές του Bottles" +msgstr "Πνευματικά δικαιώματα © 2017 - Οι προγραμματιστές του Bottles" # Translators: Bottles is a proper noun referring to the app #: bottles/frontend/ui/about.blp:10 -#, fuzzy msgid "Bottles Developers" -msgstr "© 2017-2021 - Προγραμματιστές του Bottles" +msgstr "Προγραμματιστές του Bottles" #: bottles/frontend/ui/about.blp:12 msgid "translator_credits" -msgstr "" +msgstr "μεταφραστές" #: bottles/frontend/ui/component-entry.blp:4 msgid "Component version" @@ -239,20 +236,17 @@ msgid "Uninstall" msgstr "Απεγκατάσταση" #: bottles/frontend/ui/component-entry.blp:23 -#, fuzzy msgid "Browse Files" -msgstr "Εξερεύνηση αρχείων" +msgstr "Περιήγηση αρχείων" #: bottles/frontend/ui/component-entry.blp:34 -#, fuzzy msgid "" "The installation failed. This may be due to a repository error, partial " "download or checksum mismatch. Press to try again." msgstr "" -"Η εγκατάσταση απέτυχε. Αυτό μπορεί να οφείλεται σε σφάλμα του repository, " -"μερική λήψη ή αναντιστοιχία αθροίσματος ελέγχου.\n" -"\n" -"Πατήστε για να προσπαθήσετε ξανά." +"Η εγκατάσταση απέτυχε. Αυτό μπορεί να οφείλεται σε σφάλμα του αποθετηρίου, " +"μερική λήψη ή αναντιστοιχία αθροίσματος ελέγχου. Πατήστε για να προσπαθήσετε " +"ξανά." #: bottles/frontend/ui/component-entry.blp:45 msgid "Download & Install" @@ -278,24 +272,23 @@ msgstr "Επανεγκατάσταση" #: bottles/frontend/ui/dependency-entry.blp:36 #: bottles/frontend/ui/installer-entry.blp:27 msgid "Report a Bug…" -msgstr "" +msgstr "Αναφορά σφάλματος…" #: bottles/frontend/ui/dependency-entry.blp:42 msgid "Dependency name" -msgstr "" +msgstr "Όνομα εξάρτησης" #: bottles/frontend/ui/dependency-entry.blp:44 msgid "Dependency description" -msgstr "" +msgstr "Περιγραφή εξάρτησης" #: bottles/frontend/ui/dependency-entry.blp:51 msgid "Category" -msgstr "" +msgstr "Κατηγορία" #: bottles/frontend/ui/dependency-entry.blp:64 -#, fuzzy msgid "Download & Install this Dependency" -msgstr "Λήψη & εγκατάσταση αυτού του dependency" +msgstr "Λήψη & εγκατάσταση αυτής της εξάρτησης" #: bottles/frontend/ui/dependency-entry.blp:79 msgid "" @@ -303,38 +296,35 @@ msgid "" "run it via terminal to read the output." msgstr "" "Παρουσιάστηκε σφάλμα κατά την εγκατάσταση. Επανεκκινήστε το Bottles για να " -"διαβάσετε την αναφορά crash ή εκτελέστε το μέσω τερματικού για να διαβάσετε " -"την έξοδο." +"διαβάσετε την αναφορά τερματικού σφάλματος ή εκτελέστε το μέσω τερματικού " +"για να διαβάσετε την έξοδο." #: bottles/frontend/ui/dependency-entry.blp:93 msgid "Dependency Menu" -msgstr "" +msgstr "Επιλογές εξαρτήσεων" #: bottles/frontend/ui/details-bottle.blp:16 msgid "Troubleshooting" msgstr "Αντιμετώπιση προβλημάτων" #: bottles/frontend/ui/details-bottle.blp:24 -#, fuzzy msgid "Browse Files…" -msgstr "Εξερεύνηση αρχείων" +msgstr "Περιήγηση αρχείων…" #: bottles/frontend/ui/details-bottle.blp:28 -#, fuzzy msgid "Duplicate Bottle…" -msgstr "Διπλοτυπία αυτού του bottle" +msgstr "Διπλοτυπία αυτού του bottle…" #: bottles/frontend/ui/details-bottle.blp:32 #: bottles/frontend/ui/importer.blp:73 msgid "This is the complete archive of your bottle, including personal files." msgstr "" -"Αυτός είναι ο πλήρες φάκελος του bottle σας, συμπεριλαμβανομένων των " +"Αυτός είναι ο πλήρης φάκελος του bottle σας, συμπεριλαμβανομένων των " "προσωπικών αρχείων." #: bottles/frontend/ui/details-bottle.blp:33 -#, fuzzy msgid "Full Backup…" -msgstr "Πλήρες αντίγραφο ασφαλείας" +msgstr "Πλήρες αντίγραφο ασφαλείας…" #: bottles/frontend/ui/details-bottle.blp:37 #: bottles/frontend/ui/importer.blp:68 @@ -346,31 +336,27 @@ msgstr "" "ένα νέο αλλά χωρίς προσωπικά αρχεία." #: bottles/frontend/ui/details-bottle.blp:38 -#, fuzzy msgid "Export Configuration…" -msgstr "Εξαγωγή διαμόρφωσης" +msgstr "Εξαγωγή διαμόρφωσης…" #: bottles/frontend/ui/details-bottle.blp:45 #: bottles/frontend/views/bottle_details.py:344 -#, fuzzy msgid "Show Hidden Programs" -msgstr "Προγράμματα" +msgstr "Εμφάνιση κρυμμένων προγραμμάτων" #: bottles/frontend/ui/details-bottle.blp:49 -#, fuzzy msgid "Search for new programs" -msgstr "Προγράμματα" +msgstr "Αναζήτηση για νέα προγράμματα" #: bottles/frontend/ui/details-bottle.blp:56 -#, fuzzy msgid "Delete Bottle…" -msgstr "Διαγραφή bottle" +msgstr "Διαγραφή bottle…" #: bottles/frontend/ui/details-bottle.blp:73 #: bottles/frontend/ui/details-dependencies.blp:99 #: bottles/frontend/ui/details-installers.blp:68 msgid "Secondary Menu" -msgstr "" +msgstr "Δευτερεύουσες επιλογές" #: bottles/frontend/ui/details-bottle.blp:90 #, fuzzy @@ -396,7 +382,7 @@ msgstr "Επανεκκίνηση" #: bottles/frontend/ui/details-bottle.blp:118 #: bottles/frontend/ui/dialog-launch-options.blp:6 msgid "Launch Options" -msgstr "" +msgstr "Επιλογές εκκίνησης" #: bottles/frontend/ui/details-bottle.blp:135 #, fuzzy @@ -405,12 +391,11 @@ msgstr "Εκτέλεση μέσω τερματικού" #: bottles/frontend/ui/details-bottle.blp:148 msgid "Drop files to execute them" -msgstr "" +msgstr "Σύρσιμο αρχείων προς εκτέλεση" #: bottles/frontend/ui/details-bottle.blp:164 -#, fuzzy msgid "My bottle" -msgstr "Bottles" +msgstr "Το δικό μου bottle" #: bottles/frontend/ui/details-bottle.blp:177 msgid "Win64" @@ -425,7 +410,7 @@ msgstr "Περιβάλλον" #: bottles/frontend/ui/details-preferences.blp:14 #: bottles/frontend/ui/new.blp:128 msgid "Runner" -msgstr "Δρομέας" +msgstr "Εκτελεστής" #: bottles/frontend/ui/details-bottle.blp:213 #: bottles/frontend/ui/list-entry.blp:21 @@ -443,9 +428,8 @@ msgid "0" msgstr "0" #: bottles/frontend/ui/details-bottle.blp:247 -#, fuzzy msgid "Run Executable…" -msgstr "Εκτέλεση εφαρμογής" +msgstr "Εκτέλεση αρχείου…" #: bottles/frontend/ui/details-bottle.blp:272 msgid "Programs" @@ -457,62 +441,64 @@ msgid "" "executable to the Programs list, or \"Install Programs…\" to install " "programs curated by the community." msgstr "" +"Κάντε κλικ στο \"Εκτέλεση αρχείου\" για να εκτελέσετε ένα αρχείο, " +"\"Δημιουργία συντομεύσεων...\" για να προσθέσετε ένα εκτελέσιμο αρχείο στη " +"λίστα των Προγραμμάτων ή \"Εγκατάσταση προγραμμάτων\" για να εγκαταστήσετε " +"προγράμματα που θεραπεύονται από την κοινότητα." #: bottles/frontend/ui/details-bottle.blp:298 msgid "Add Shortcuts…" -msgstr "" +msgstr "Δημιουργία συντομεύσεων…" #: bottles/frontend/ui/details-bottle.blp:325 -#, fuzzy msgid "Install Programs…" -msgstr "Πειράματα:εγκαταστάτες" +msgstr "Εγκατάσταση προγραμμάτων" #: bottles/frontend/ui/details-bottle.blp:346 msgid "Options" -msgstr "" +msgstr "Επιλογές" #: bottles/frontend/ui/details-bottle.blp:350 #: bottles/frontend/views/details.py:141 msgid "Settings" -msgstr "" +msgstr "Ρυθμίσεις" #: bottles/frontend/ui/details-bottle.blp:351 msgid "Configure bottle settings." -msgstr "" +msgstr "Διαμόρφωση ρυθμίσεων του bottle." #: bottles/frontend/ui/details-bottle.blp:360 #: bottles/frontend/views/details.py:145 msgid "Dependencies" -msgstr "" +msgstr "Εξαρτήσεις" #: bottles/frontend/ui/details-bottle.blp:361 -#, fuzzy msgid "Install dependencies for programs." -msgstr "Εγκατάσταση εξάρτησης: %s…" +msgstr "Εγκατάσταση εξαρτήσεων προγραμμάτων." #: bottles/frontend/ui/details-bottle.blp:370 #: bottles/frontend/ui/details-preferences.blp:377 #: bottles/frontend/views/details.py:149 msgid "Snapshots" -msgstr "" +msgstr "Στιγμιότυπα" #: bottles/frontend/ui/details-bottle.blp:371 msgid "Create and manage bottle states." -msgstr "" +msgstr "Δημιουργία και διαχείριση κατάστασης του bottle." #: bottles/frontend/ui/details-bottle.blp:380 #: bottles/frontend/ui/details-bottle.blp:426 #: bottles/frontend/views/details.py:157 msgid "Task Manager" -msgstr "" +msgstr "Διαχείριση εργασιών" #: bottles/frontend/ui/details-bottle.blp:381 msgid "Manage running programs." -msgstr "" +msgstr "Διαχείριση εκτελούμενων προγραμμάτων." #: bottles/frontend/ui/details-bottle.blp:390 msgid "Tools" -msgstr "" +msgstr "Εργαλεία" #: bottles/frontend/ui/details-bottle.blp:394 #, fuzzy @@ -538,7 +524,7 @@ msgstr "" #: bottles/frontend/ui/details-bottle.blp:417 msgid "Explorer" -msgstr "" +msgstr "Εξερευνητής" #: bottles/frontend/ui/details-bottle.blp:435 #, fuzzy @@ -548,30 +534,31 @@ msgstr "Εντοπισμός σφαλμάτων" #: bottles/frontend/ui/details-bottle.blp:444 #: bottles/frontend/ui/importer.blp:69 bottles/frontend/ui/new.blp:145 msgid "Configuration" -msgstr "" +msgstr "Διαμόρφωση" #: bottles/frontend/ui/details-bottle.blp:453 -#, fuzzy msgid "Uninstaller" -msgstr "Πειράματα:εγκαταστάτες" +msgstr "Απεγκαταστάτης" #: bottles/frontend/ui/details-bottle.blp:462 msgid "Control Panel" -msgstr "" +msgstr "Πίνακας ελέγχου" #: bottles/frontend/ui/details-dependencies.blp:9 msgid "Search for dependencies…" -msgstr "" +msgstr "Εύρεση εξαρτήσεων…" #: bottles/frontend/ui/details-dependencies.blp:22 #: bottles/frontend/ui/preferences.blp:178 #: bottles/frontend/ui/preferences.blp:235 msgid "You're offline :(" -msgstr "" +msgstr "Βρίσκεστε εκτός σύνδεσης :(" #: bottles/frontend/ui/details-dependencies.blp:25 msgid "Bottles is running in offline mode, so dependencies are not available." msgstr "" +"Το Bottles εκτελείται χωρίς σύνδεση στο διαδίκτυο, οπότε οι εξαρτήσεις δεν " +"είναι διαθέσιμες." #: bottles/frontend/ui/details-dependencies.blp:47 msgid "" @@ -580,20 +567,24 @@ msgid "" "Files on this page are provided by third parties under a proprietary " "license. By installing them, you agree with their respective licensing terms." msgstr "" +"Οι εξαρτήσεις είναι πόροι που βελτιώσουν τη συμβατότητα του λογισμικού " +"Windows.\n" +"\n" +"Τα αρχεία σε αυτή τη σελίδα παρέχονται από τρίτους με άδεια ιδιοκτησίας. Με " +"την εγκατάσταση τους, συμφωνείτε με τους αντίστοιχους όρους αδειοδότησης " +"τους." #: bottles/frontend/ui/details-dependencies.blp:76 msgid "Report a problem or a missing dependency." msgstr "Αναφορά προβλήματος η έλειψης εξάρτησης." #: bottles/frontend/ui/details-dependencies.blp:77 -#, fuzzy msgid "Report Missing Dependency" -msgstr "Αναφορά προβλήματος η έλειψης εξάρτησης." +msgstr "Αναφορά έλλειψης εξάρτησης" #: bottles/frontend/ui/details-dependencies.blp:81 -#, fuzzy msgid "Read Documentation." -msgstr "Διαβάστε οδηγείες" +msgstr "Διαβάστε οδηγίες." #: bottles/frontend/ui/details-dependencies.blp:82 #: bottles/frontend/ui/details-installers.blp:51 @@ -605,11 +596,11 @@ msgstr "" #: bottles/frontend/ui/details-installers.blp:61 #: bottles/frontend/ui/window.blp:46 msgid "Search" -msgstr "" +msgstr "Εύρεση" #: bottles/frontend/ui/details-installers.blp:9 msgid "Search for Programs…" -msgstr "" +msgstr "Εύρεση προγραμμάτων…" #: bottles/frontend/ui/details-installers.blp:15 msgid "" @@ -620,36 +611,35 @@ msgid "" msgstr "" #: bottles/frontend/ui/details-installers.blp:29 -#, fuzzy msgid "No Installers Found" -msgstr "Πειράματα:εγκαταστάτες" +msgstr "Δε βρέθηκαν προγράμματα εγκατάστασης" #: bottles/frontend/ui/details-installers.blp:32 msgid "" "The repository is unreachable or no installer is compatible with this bottle." msgstr "" +"Το αποθετήριο δεν είναι προσβάσιμο ή κανένα πρόγραμμα εγκατάστασης δεν είναι " +"συμβατό με αυτό το bottle." #: bottles/frontend/ui/details-installers.blp:50 #: bottles/frontend/ui/details-versioning.blp:36 #: bottles/frontend/ui/preferences.blp:81 -#, fuzzy msgid "Read Documentation" -msgstr "Διαβάστε οδηγείες" +msgstr "Διαβάστε οδηγίες" #: bottles/frontend/ui/details-preferences.blp:6 #: bottles/frontend/ui/dialog-duplicate.blp:52 msgid "Name" -msgstr "" +msgstr "Όνομα" #: bottles/frontend/ui/details-preferences.blp:11 -#, fuzzy msgid "Components" -msgstr "Εκδοση Windows" +msgstr "Εξαρτήματα" #: bottles/frontend/ui/details-preferences.blp:15 #: bottles/frontend/ui/new.blp:129 msgid "The version of the Wine compatibility layer." -msgstr "" +msgstr "Η έκδοση του στρώματος συμβατότητας Wine." #: bottles/frontend/ui/details-preferences.blp:17 msgid "Updating Runner and components, please wait…" @@ -1147,9 +1137,8 @@ msgid "Duplicate" msgstr "" #: bottles/frontend/ui/dialog-duplicate.blp:49 -#, fuzzy msgid "Enter a name for the duplicate of the Bottle." -msgstr "Συμπληρώστε ένα όνομα για το bottle σας" +msgstr "Συμπληρώστε ένα όνομα για το bottle σας." #: bottles/frontend/ui/dialog-duplicate.blp:69 msgid "Duplicating…" @@ -1845,9 +1834,8 @@ msgid "This bottle looks damaged." msgstr "" #: bottles/frontend/ui/list-entry.blp:55 -#, fuzzy msgid "Execute in this Bottle" -msgstr "Το Versioning είναι ενεργό σε αυτό το bottle." +msgstr "Το Versioning είναι ενεργό σε αυτό το bottle" #: bottles/frontend/ui/list-entry.blp:69 msgid "Run Here" @@ -1946,9 +1934,8 @@ msgid "32-bit should only be used if strictly necessary." msgstr "" #: bottles/frontend/ui/new.blp:146 -#, fuzzy msgid "Import a custom configuration." -msgstr "Εξαγωγή διαμόρφωσης" +msgstr "Εισαγωγή διαμόρφωσης." #: bottles/frontend/ui/new.blp:176 #, fuzzy @@ -1977,9 +1964,8 @@ msgid "Welcome to Bottles" msgstr "" #: bottles/frontend/ui/onboard.blp:60 -#, fuzzy msgid "Run Windows Software on Linux." -msgstr "Τρέξτε λογισμικό των Windows σε Linux με το Bottles!" +msgstr "Εκτέλεση λογισμικού των Windows σε Linux." # Translators: Bottles is a generic noun here, referring to wine prefixes and is to be translated #: bottles/frontend/ui/onboard.blp:65 @@ -2067,10 +2053,8 @@ msgid "Clean temp files when Bottles launches?" msgstr "" #: bottles/frontend/ui/preferences.blp:62 -#, fuzzy msgid "Close Bottles After Starting a Program" -msgstr "" -"Kλείσιμο Bottles μετα την εκκίνηση προγραμμάτος απο τη διαχείρηση αρχείων." +msgstr "Κλείσιμο των Bottles μετά από εκκίνηση προγράμματος." #: bottles/frontend/ui/preferences.blp:63 #, fuzzy @@ -2709,9 +2693,8 @@ msgid "Display settings updated" msgstr "" #: bottles/frontend/windows/dlloverrides.py:136 -#, fuzzy msgid "No overrides found." -msgstr "Υπερισχύσεις DLL" +msgstr "Δε βρέθηκαν υπερισχύσεις." #: bottles/frontend/windows/drives.py:71 #, fuzzy @@ -2719,14 +2702,12 @@ msgid "Select Drive Path" msgstr "Διαγραφή bottle" #: bottles/frontend/windows/envvars.py:131 -#, fuzzy msgid "No environment variables defined." -msgstr "Προσθέστε μεταβλητές περιβάλλοντος γρήγορα" +msgstr "Δεν έχουν οριστεί μεταβλητές περιβάλλοντος." #: bottles/frontend/windows/exclusionpatterns.py:108 -#, fuzzy msgid "No exclusion patterns defined." -msgstr "Προσθέστε μεταβλητές περιβάλλοντος γρήγορα" +msgstr "Δεν έχουν προστεθεί κανόνες αποκλεισμού." #: bottles/frontend/windows/generic.py:24 msgid "An error has occurred." @@ -2778,9 +2759,9 @@ msgid "Installer failed with unknown error" msgstr "" #: bottles/frontend/windows/launchoptions.py:56 -#, fuzzy, python-brace-format +#, python-brace-format msgid "{0} is already disabled for this bottle." -msgstr "Το Versioning ενεργοποιήθηκε για αυτό το bottle" +msgstr "{0} είναι ήδη απενεργοποιημένο για αυτό το bottle." #: bottles/frontend/windows/launchoptions.py:57 msgid "This setting is different from the bottle's default." @@ -2845,27 +2826,24 @@ msgid "Steam apps listing" msgstr "" #: data/com.usebottles.bottles.gschema.xml:22 -#, fuzzy msgid "Toggle steam apps listing." -msgstr "Ενεργοποίηση/Απενεργοποίηση ημερομηνίας αναβάθμισης στη λίστα" +msgstr "Εμφάνιση λίστας εφαρμογών steam." #: data/com.usebottles.bottles.gschema.xml:26 msgid "Epic Games listing" msgstr "" #: data/com.usebottles.bottles.gschema.xml:27 -#, fuzzy msgid "Toggle epic games listing." -msgstr "Ενεργοποίηση/Απενεργοποίηση ημερομηνίας αναβάθμισης στη λίστα" +msgstr "Εμφάνιση λίστας παιχνιδιών epic." #: data/com.usebottles.bottles.gschema.xml:31 msgid "Ubisoft Connect listing" msgstr "" #: data/com.usebottles.bottles.gschema.xml:32 -#, fuzzy msgid "Toggle ubisoft connect listing." -msgstr "Ενεργοποίηση/Απενεργοποίηση ημερομηνίας αναβάθμισης στη λίστα" +msgstr "Εμφάνιση λίστας συνδέσεων ubisoft." #: data/com.usebottles.bottles.gschema.xml:36 msgid "Window width" diff --git a/po/hi.po b/po/hi.po index 904d98720e3..6e1d03a13fd 100644 --- a/po/hi.po +++ b/po/hi.po @@ -8,8 +8,8 @@ msgstr "" "Project-Id-Version: bottles\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-03-27 13:50+0530\n" -"PO-Revision-Date: 2023-07-28 03:08+0000\n" -"Last-Translator: Raveesh Agarwal \n" +"PO-Revision-Date: 2024-02-07 02:19+0000\n" +"Last-Translator: Incognitux \n" "Language-Team: Hindi \n" "Language: hi\n" @@ -17,7 +17,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" -"X-Generator: Weblate 5.0-dev\n" +"X-Generator: Weblate 5.4-dev\n" #: bottles/backend/managers/backup.py:48 bottles/backend/managers/backup.py:95 msgid "No path specified" @@ -112,7 +112,7 @@ msgstr "VKD3D इंस्टॉल किया जा रहा है…" #: bottles/backend/managers/manager.py:1326 msgid "Installing DXVK-NVAPI…" -msgstr "" +msgstr "डीएक्सवीके-एनवीएपीआई स्थापित किया जा रहा है…" #: bottles/backend/managers/manager.py:1335 #, python-format @@ -2842,9 +2842,8 @@ msgid "Clean the temp path when booting the system." msgstr "सिस्टम को बूट करते समय अस्थायी पथ को साफ करें।" #: data/com.usebottles.bottles.gschema.xml:56 -#, fuzzy msgid "Release Candidate" -msgstr "उम्मीदवार के रिहाई" +msgstr "रिलीज कैंडिडेट" #: data/com.usebottles.bottles.gschema.xml:57 msgid "Toggle release candidate for runners." diff --git a/po/hr.po b/po/hr.po index fa2cee65cfd..77179a18acb 100644 --- a/po/hr.po +++ b/po/hr.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: bottles\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-03-27 13:50+0530\n" -"PO-Revision-Date: 2023-07-20 12:07+0000\n" +"PO-Revision-Date: 2024-02-25 02:02+0000\n" "Last-Translator: Milo Ivir \n" "Language-Team: Croatian \n" @@ -18,7 +18,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" -"X-Generator: Weblate 5.0-dev\n" +"X-Generator: Weblate 5.5-dev\n" #: bottles/backend/managers/backup.py:48 bottles/backend/managers/backup.py:95 msgid "No path specified" @@ -140,16 +140,16 @@ msgstr "Nema promjena" #: bottles/backend/managers/versioning.py:96 #, python-brace-format msgid "New state [{0}] created successfully!" -msgstr "Uspješno je stvoreno novo stanje [{0}]!" +msgstr "Novo stanje [{0}] je uspješno stvoreno!" #: bottles/backend/managers/versioning.py:123 msgid "States list retrieved successfully!" -msgstr "Popis stanja je uspješno učitan!" +msgstr "Popis stanja je uspješno dohvaćen!" #: bottles/backend/managers/versioning.py:153 #, python-brace-format msgid "State {0} restored successfully!" -msgstr "Uspješno je obnovljeno novo stanje [{0}]!" +msgstr "Stanje [{0}] je uspješno obnovljeno!!" #: bottles/backend/managers/versioning.py:155 msgid "Restoring state {} …" @@ -689,13 +689,15 @@ msgstr "Prikaz" #: bottles/frontend/ui/details-preferences.blp:88 msgid "Deep Learning Super Sampling" -msgstr "" +msgstr "Deep Learning Super Sampling" #: bottles/frontend/ui/details-preferences.blp:89 msgid "" "Increase performance at the expense of visuals using DXVK-NVAPI. Only works " "on newer NVIDIA GPUs." msgstr "" +"Povećaj performancu nauštrb vizualnih prikaza koristeći DXVK-NVAPI. Radi " +"samo na novijim NVIDIA GPU-ovima." #: bottles/frontend/ui/details-preferences.blp:105 msgid "FidelityFX Super Resolution" @@ -703,40 +705,42 @@ msgstr "FidelityFX Super Resolution" #: bottles/frontend/ui/details-preferences.blp:106 msgid "Increase performance at the expense of visuals. Only works on Vulkan." -msgstr "Povećaj performansu na račun vizualnih prikaza. Radi samo na Vulkanu." +msgstr "Povećaj performancu nauštrb vizualnih prikaza. Radi samo za Vulkan." #: bottles/frontend/ui/details-preferences.blp:108 msgid "Manage FidelityFX Super Resolution settings" msgstr "Upravljaj FidelityFX Super Resolution postavkama" #: bottles/frontend/ui/details-preferences.blp:125 -#, fuzzy msgid "Discrete Graphics" -msgstr "Diskretni grafički procesor" +msgstr "Dodatna grafička kartica" #: bottles/frontend/ui/details-preferences.blp:126 msgid "" "Use the discrete graphics card to increase performance at the expense of " "power consumption." msgstr "" +"Koristi dodatnu grafičku karticu za povećanje performance nauštrb potrošnje " +"energije." #: bottles/frontend/ui/details-preferences.blp:135 msgid "Post-Processing Effects" -msgstr "" +msgstr "Efekti naknadne obrade" #: bottles/frontend/ui/details-preferences.blp:136 msgid "" "Add various post-processing effects using vkBasalt. Only works on Vulkan." msgstr "" +"Dodaj razne efekte naknadne obrade koristeći vkBasalt. Radi samo s Vulkan " +"API-em." #: bottles/frontend/ui/details-preferences.blp:138 -#, fuzzy msgid "Manage Post-Processing Layer settings" -msgstr "Upravljaj vmtouch postavkama" +msgstr "Upravljaj postavkama slojeva naknadne obrade" #: bottles/frontend/ui/details-preferences.blp:154 msgid "Manage how games should be displayed on the screen using Gamescope." -msgstr "" +msgstr "Upravljaj načinom prikaza igri na ekranu pomoću Gamescopea." #: bottles/frontend/ui/details-preferences.blp:157 msgid "Manage Gamescope settings" @@ -748,12 +752,12 @@ msgstr "Napredne postavke prikaza" #: bottles/frontend/ui/details-preferences.blp:184 msgid "Performance" -msgstr "Izvođenje" +msgstr "Performanca" #: bottles/frontend/ui/details-preferences.blp:188 msgid "Enable synchronization to increase performance of multicore processors." msgstr "" -"Aktiviraj sinkronizaciju za povećavanje performanse višejezgrenih procesora." +"Aktiviraj sinkronizaciju za povećavanje performance višejezgrenih procesora." #: bottles/frontend/ui/details-preferences.blp:189 msgid "Synchronization" @@ -776,25 +780,27 @@ msgid "Futex2" msgstr "Futex2" #: bottles/frontend/ui/details-preferences.blp:202 -#, fuzzy msgid "Monitor Performance" -msgstr "Izvođenje" +msgstr "Prati performancu" #: bottles/frontend/ui/details-preferences.blp:203 msgid "" "Display monitoring information such as framerate, temperatures, CPU/GPU load " "and more on OpenGL and Vulkan using MangoHud." msgstr "" +"Prikaži informacije praćenja kao što su broj sličica u sekundi, temperature, " +"CPU/GPU opterećenje i više na OpenGL-u i Vulkanu koristeći MangoHud." #: bottles/frontend/ui/details-preferences.blp:211 -#, fuzzy msgid "Feral GameMode" -msgstr "Modus igranja" +msgstr "Feral GameMode" #: bottles/frontend/ui/details-preferences.blp:212 msgid "" "Apply a set of optimizations to your device. Can improve game performance." msgstr "" +"Primijeni niz optimiziranja na tvom uređaju. Može poboljšati performancu " +"igri." #: bottles/frontend/ui/details-preferences.blp:221 msgid "Preload Game Files" @@ -805,20 +811,20 @@ msgid "" "Improve loading time when launching the game multiple times. The game will " "take longer to start for the first time." msgstr "" +"Poboljšaj vrijeme učitavanja pri višestrukom pokretanju igre. Pokretanje " +"igre će trajati duže pri prvom pokretanju." #: bottles/frontend/ui/details-preferences.blp:226 msgid "Manage vmtouch settings" msgstr "Upravljaj vmtouch postavkama" #: bottles/frontend/ui/details-preferences.blp:241 -#, fuzzy msgid "OBS Game Capture" -msgstr "OBS Vulkan snimanje" +msgstr "Snimanje OBS igre" #: bottles/frontend/ui/details-preferences.blp:242 -#, fuzzy msgid "Toggle OBS Game Capture for all Vulkan and OpenGL programs." -msgstr "Uključi/Isključi OBS snimanje igre za sve programe." +msgstr "Uključi/Isključi snimanje OBS igre za sve Vulkan and OpenGL programe." #: bottles/frontend/ui/details-preferences.blp:251 msgid "Compatibility" @@ -912,15 +918,15 @@ msgid "Manage Drives" msgstr "Upravljaj pogonima" #: bottles/frontend/ui/details-preferences.blp:381 -#, fuzzy msgid "Automatic Snapshots" -msgstr "Automatsko verzioniranje" +msgstr "Automatske snimke" #: bottles/frontend/ui/details-preferences.blp:382 msgid "" "Automatically create snapshots before installing software or changing " "settings." msgstr "" +"Automatski stvori snimke prije instaliranja softvera ili mijenjanja postavki." #: bottles/frontend/ui/details-preferences.blp:391 msgid "Compression" @@ -959,9 +965,8 @@ msgid "No Snapshots Found" msgstr "Nijedna snimka nije pronađena" #: bottles/frontend/ui/details-versioning.blp:19 -#, fuzzy msgid "Create your first snapshot to start saving states of your preferences." -msgstr "Stvori prvo stanje za početak korištenja verzioniranja." +msgstr "Stvori svoju prvu snimku za spremanje stanja tvojih postavki." #: bottles/frontend/ui/details-versioning.blp:54 msgid "A short comment" @@ -1234,9 +1239,8 @@ msgid "Frame Rate Limit When Unfocused" msgstr "Ograničenje frekvencije kad prozor nije aktivan" #: bottles/frontend/ui/dialog-gamescope.blp:153 -#, fuzzy msgid "Integer Scaling" -msgstr "Koristi cijele brojeve za skaliranje" +msgstr "Povećanje sa cijelim brojevima" #: bottles/frontend/ui/dialog-gamescope.blp:162 msgid "Window Type" @@ -1316,7 +1320,7 @@ msgstr "Preglednik dnevnika" #: bottles/frontend/ui/dialog-journal.blp:53 msgid "Change Logging Level." -msgstr "" +msgstr "Promijeni razinu zapisivanja." #: bottles/frontend/ui/dialog-journal.blp:57 msgid "All" @@ -1350,7 +1354,6 @@ msgid "Choose a script which should be executed after run." msgstr "Odaberi skripta koji se treba izvršiti nakon pokretanja." #: bottles/frontend/ui/dialog-launch-options.blp:70 -#, fuzzy msgid "Choose a Script" msgstr "Odaberi skripta" @@ -1483,7 +1486,6 @@ msgid "The new bottle versioning system has landed." msgstr "Uveden je novi sustav verzioniranja butelja." #: bottles/frontend/ui/dialog-upgrade-versioning.blp:83 -#, fuzzy msgid "" "Bottles has a whole new Versioning System that is not backwards compatible.\n" "\n" @@ -1502,16 +1504,15 @@ msgstr "" "\n" "Da bismo nastavili koristiti verzioniranje, moramo ponovo inicijalizirati " "repozitorij butelje. To neće izbrisati podatke iz tvoje butelje, ali će " -"izbrisati sva postojeća stanja i stvoriti novo.\n" +"izbrisati sve postojeće snimke i stvoriti novu butelju.\n" "\n" "Ako se moraš vratiti na jedno prethodno stanje prije nastavljanja, zatvori " -"ovaj prozor i obnovi stanje, a zatim ponovo otvori butelju za ponovno " +"ovaj prozor i obnovi snimku, a zatim ponovo otvori butelju za ponovno " "prikazivanje ovog prozora.\n" "\n" -"Stari sustav bit će ukinut u jednom od sljedećih izdanja." +"Stari sustav će se ukinuti u jednom od sljedećih izdanja." #: bottles/frontend/ui/dialog-upgrade-versioning.blp:103 -#, fuzzy msgid "Re-initializing Repository…" msgstr "Ponovno inicijaliziranje repozitorija …" @@ -1522,7 +1523,7 @@ msgstr "Gotovo! Pokreni Butelje ponovo." #. Translators: vkBasalt is a Vulkan post processing layer for Linux #: bottles/frontend/ui/dialog-vkbasalt.blp:10 msgid "Post-Processing Effects Settings" -msgstr "" +msgstr "Postavke efekata naknadne obrade" #: bottles/frontend/ui/dialog-vkbasalt.blp:44 msgid "Default" @@ -1668,7 +1669,7 @@ msgid "" msgstr "" "Minimalna FXAA kvaliteta praga rubova minimalna je vrijednost tamnih piksela " "koje FXAA algoritam zanemaruje. Korištenjem većih vrijednosti, FXAA će " -"zanemariti piksele ispod navedene vrijednosti i može povećati performansu." +"zanemariti piksele ispod navedene vrijednosti i može povećati performancu." #: bottles/frontend/ui/dialog-vkbasalt.blp:513 msgid "" @@ -1684,7 +1685,7 @@ msgid "" "detect more edges at the expense of performance." msgstr "" "SMAA prag određuje osjetljivost otkrivanja rubova. Manje vrijednosti " -"otkrivaju više rubova nauštrb performanci." +"otkrivaju više rubova nauštrb performance." #: bottles/frontend/ui/dialog-vkbasalt.blp:547 msgid "" @@ -1802,23 +1803,20 @@ msgid "Unknown" msgstr "Nepoznato" #: bottles/frontend/ui/installer-entry.blp:51 -#, fuzzy msgid "Install this Program" msgstr "Instaliraj ovaj program" #: bottles/frontend/ui/installer-entry.blp:69 -#, fuzzy msgid "Program Menu" -msgstr "Ime programa" +msgstr "Izbornik programa" #: bottles/frontend/ui/library-entry.blp:36 msgid "No Thumbnail" -msgstr "" +msgstr "Bez minijature" #: bottles/frontend/ui/library-entry.blp:57 -#, fuzzy msgid "Launch" -msgstr "Opcije pokretanja" +msgstr "Pokreni" #: bottles/frontend/ui/library-entry.blp:70 #: bottles/frontend/ui/program-entry.blp:89 @@ -1835,7 +1833,7 @@ msgstr "Ukloni iz biblioteke" #: bottles/frontend/ui/library-entry.blp:143 msgid "Stop" -msgstr "" +msgstr "Prekini" #: bottles/frontend/ui/library.blp:11 #: bottles/frontend/windows/main_window.py:196 @@ -1855,7 +1853,6 @@ msgid "This bottle looks damaged." msgstr "Čini se da je ova butelja oštećena." #: bottles/frontend/ui/list-entry.blp:55 -#, fuzzy msgid "Execute in this Bottle" msgstr "Izvrši u ovoj butelji" @@ -1910,9 +1907,8 @@ msgid "Browse" msgstr "Pregledaj" #: bottles/frontend/ui/new.blp:32 -#, fuzzy msgid "C_reate" -msgstr "Stvori" +msgstr "Stvo_ri" #: bottles/frontend/ui/new.blp:53 #, fuzzy @@ -1920,28 +1916,24 @@ msgid "Bottle Name" msgstr "Ime butelje" #: bottles/frontend/ui/new.blp:75 -#, fuzzy msgid "_Application" -msgstr "Program" +msgstr "_Program" #: bottles/frontend/ui/new.blp:88 -#, fuzzy msgid "_Gaming" -msgstr "Igre" +msgstr "_Igre" #: bottles/frontend/ui/new.blp:101 -#, fuzzy msgid "C_ustom" -msgstr "Prilagođeno" +msgstr "Pri_lagođeno" #: bottles/frontend/ui/new.blp:114 msgid "Custom" msgstr "Prilagođeno" #: bottles/frontend/ui/new.blp:118 -#, fuzzy msgid "Share User Directory" -msgstr "Odaberi jednu mapu" +msgstr "Dijeli korisničku mapu" #: bottles/frontend/ui/new.blp:119 msgid "" @@ -1955,33 +1947,28 @@ msgid "Architecture" msgstr "Arhitektura" #: bottles/frontend/ui/new.blp:137 -#, fuzzy msgid "32-bit should only be used if strictly necessary." -msgstr "" -"Preporučujemo koristiti 32-bitnu verziju samo ako je izričito potrebno." +msgstr "Koristi 32-bitnu verziju samo ako je izričito potrebno." #: bottles/frontend/ui/new.blp:146 msgid "Import a custom configuration." msgstr "Izvezi prilagođenu konfiguraciju." #: bottles/frontend/ui/new.blp:176 -#, fuzzy msgid "Bottle Directory" -msgstr "Odaberi jednu mapu" +msgstr "Direktorij butelje" #: bottles/frontend/ui/new.blp:177 msgid "Directory that will contain the data of this bottle." -msgstr "" +msgstr "Direktorij koji će sadržati podatke ove butelje." #: bottles/frontend/ui/new.blp:249 -#, fuzzy msgid "_Close" -msgstr "Zatvori" +msgstr "_Zatvori" #: bottles/frontend/ui/new.blp:281 -#, fuzzy msgid "This name is unavailable, please try another." -msgstr "Ova funkcija nije dostupna na tvom sustavu." +msgstr "Ovo ime nije dostupno. Pokušaj jedno drugo ime." #: bottles/frontend/ui/onboard.blp:34 msgid "Previous" @@ -2018,7 +2005,6 @@ msgid "We need a few more minutes to set everything up…" msgstr "Trebamo još par minuta kako bismo sve postavili …" #: bottles/frontend/ui/onboard.blp:105 -#, fuzzy msgid "All Ready!" msgstr "Sve je spremno!" @@ -2131,13 +2117,12 @@ msgid "Advanced" msgstr "Napredno" #: bottles/frontend/ui/preferences.blp:131 -#, fuzzy msgid "Bottles Directory" -msgstr "Odaberi jednu mapu" +msgstr "Direktorij za Butelje" #: bottles/frontend/ui/preferences.blp:132 msgid "Directory that contains the data of your Bottles." -msgstr "" +msgstr "Direktorij koji sadrži podatke tvoje Butelje." #: bottles/frontend/ui/preferences.blp:167 msgid "Runners" @@ -2146,6 +2131,7 @@ msgstr "Pokretači" #: bottles/frontend/ui/preferences.blp:181 msgid "Bottles is running in offline mode, so runners are not available." msgstr "" +"„Bottles” radi u izvanmrežnom načinu rada, stoga pokretači nisu dostupni." #: bottles/frontend/ui/preferences.blp:208 msgid "Pre-Release" @@ -2161,7 +2147,7 @@ msgstr "DLL komponente" #: bottles/frontend/ui/preferences.blp:238 msgid "Bottles is running in offline mode, so DLLs are not available." -msgstr "" +msgstr "„Bottles” radi u izvanmrežnom načinu rada, stoga DLL-ovi nisu dostupni." #: bottles/frontend/ui/preferences.blp:270 msgid "DXVK-NVAPI" @@ -2201,12 +2187,10 @@ msgid "In early development." msgstr "U ranoj fazi razvoja." #: bottles/frontend/ui/program-entry.blp:19 -#, fuzzy msgid "Launch with Terminal" msgstr "Pokreni s terminalom" #: bottles/frontend/ui/program-entry.blp:25 -#, fuzzy msgid "Browse Path" msgstr "Pregledaj stazu" @@ -2215,9 +2199,8 @@ msgid "Change Launch Options…" msgstr "Promijeni opcije pokretanja …" #: bottles/frontend/ui/program-entry.blp:43 -#, fuzzy msgid "Add to Library" -msgstr "Dodaj u moju biblioteku" +msgstr "Dodaj u biblioteku" #: bottles/frontend/ui/program-entry.blp:47 msgid "Add Desktop Entry" @@ -2308,9 +2291,8 @@ msgstr "„{0}” dodan" #: bottles/frontend/views/bottle_details.py:270 #: bottles/frontend/views/bottle_details.py:398 #: bottles/frontend/views/list.py:128 -#, fuzzy msgid "Select Executable" -msgstr "Odaberi butelju" +msgstr "Odaberi izvršnu datoteku" #: bottles/frontend/views/bottle_details.py:273 msgid "Add" @@ -2323,7 +2305,7 @@ msgstr "Sakrij skrivene programe" #: bottles/frontend/views/bottle_details.py:383 #: bottles/frontend/widgets/library.py:156 #: bottles/frontend/widgets/program.py:184 -#, fuzzy, python-brace-format +#, python-brace-format msgid "Launching \"{0}\"…" msgstr "Pokreće se „{0}” …" @@ -2360,19 +2342,18 @@ msgid "Select the location where to save the backup archive" msgstr "Odaberi mjesto za spremanje arhive sigurnosne kopije" #: bottles/frontend/views/bottle_details.py:435 -#, fuzzy msgid "Backup" -msgstr "Sigurnosna kopija {0}" +msgstr "Sigurnosna kopija" #: bottles/frontend/views/bottle_details.py:440 #, python-brace-format msgid "Backup created for \"{0}\"" -msgstr "Sigurnosna kopija spremljena za „{0}”" +msgstr "Sigurnosna kopija stvorena za „{0}”" #: bottles/frontend/views/bottle_details.py:442 #, python-brace-format msgid "Backup failed for \"{0}\"" -msgstr "Neuspjelo spremanje sigurnosne kopije za „{0}”" +msgstr "Sigurnosna kopija neuspjela za „{0}”" #: bottles/frontend/views/bottle_details.py:501 msgid "Are you sure you want to permanently delete \"{}\"?" @@ -2389,61 +2370,57 @@ msgid "_Delete" msgstr "_Izbriši" #: bottles/frontend/views/bottle_details.py:521 -#, fuzzy msgid "Missing Runner" -msgstr "Wine pokretači" +msgstr "Nedostaje pokretač" #: bottles/frontend/views/bottle_details.py:522 msgid "" "The runner requested by this bottle is missing. Install it through the " "Bottles preferences or choose a new one to run applications." msgstr "" +"Zatraženi pokretač ove butelje nedostaje. Instaliraj ga u postavkama ili " +"odaberi jedan novi za pokretanje aplikacija." #: bottles/frontend/views/bottle_details.py:597 -#, fuzzy msgid "Are you sure you want to force stop all processes?" -msgstr "Stvarno želiš izbrisati ovu butelju i sve datoteke?" +msgstr "Stvarno želiš prisilno prekinuti sve procese?" #: bottles/frontend/views/bottle_details.py:598 msgid "This can cause data loss, corruption, and programs to malfunction." -msgstr "" +msgstr "To može prouzročiti gubitak podataka, oštećenje i kvarove programa." #: bottles/frontend/views/bottle_details.py:601 msgid "Force _Stop" -msgstr "" +msgstr "_Prisili prekid" #: bottles/frontend/views/bottle_preferences.py:195 -#, fuzzy msgid "This feature is unavailable on your system." msgstr "Ova funkcija nije dostupna na tvom sustavu." #: bottles/frontend/views/bottle_preferences.py:196 msgid "{} To add this feature, please run flatpak install" -msgstr "" +msgstr "{} Za dodavanje ove funkcije pokreni flatpak instalaciju" #: bottles/frontend/views/bottle_preferences.py:246 -#, fuzzy msgid "This bottle name is already in use." -msgstr "Ime sadrži posebne znakove ili se već koristi." +msgstr "Ime butelje se već koristi." #: bottles/frontend/views/bottle_preferences.py:301 #: bottles/frontend/windows/launchoptions.py:241 -#, fuzzy msgid "Select Working Directory" -msgstr "Radna mapa" +msgstr "Odaberi radni direktorij" #: bottles/frontend/views/bottle_preferences.py:423 msgid "Directory that contains the data of \"{}\"." -msgstr "" +msgstr "Direktorij koji sadrži podatke od „{}”." #: bottles/frontend/views/bottle_preferences.py:746 -#, fuzzy msgid "Are you sure you want to delete all snapshots?" -msgstr "Stvarno želiš izbrisati ovu butelju i sve datoteke?" +msgstr "Stvarno želiš izbrisati sve snimke?" #: bottles/frontend/views/bottle_preferences.py:747 msgid "This will delete all snapshots but keep your files." -msgstr "" +msgstr "Ovo će izbrisati sve snimke, ali će zadržati tvoje datoteke." #: bottles/frontend/views/bottle_versioning.py:90 msgid "Please migrate to the new Versioning system to create new states." @@ -2495,7 +2472,7 @@ msgstr "--" #: bottles/frontend/views/list.py:91 #, python-brace-format msgid "Run executable in \"{self.config.Name}\"" -msgstr "" +msgstr "Pokreni izvršnu datoteku u „{self.config.Name}”" #: bottles/frontend/views/list.py:118 #, python-brace-format @@ -2517,23 +2494,20 @@ msgid "Fetched {0} of {1} packages" msgstr "Preuzeto {0} od {1} paketa" #: bottles/frontend/views/new.py:157 -#, fuzzy msgid "Select Bottle Directory" -msgstr "Odaberi jednu mapu" +msgstr "Odaberi direktorij butelje" #: bottles/frontend/views/new.py:176 -#, fuzzy msgid "Creating Bottle…" msgstr "Stvaranje butelje …" #: bottles/frontend/views/new.py:221 -#, fuzzy msgid "Unable to Create Bottle" -msgstr "Stvori novu butelju" +msgstr "Neuspjelo stvaranje butelje" #: bottles/frontend/views/new.py:225 msgid "Bottle failed to create with one or more errors." -msgstr "" +msgstr "Neuspjelo stvaranje butelje zbog jedne ili više grešaka." #. Show success #: bottles/frontend/views/new.py:232 @@ -2543,16 +2517,15 @@ msgstr "Butelja je stvorena" #: bottles/frontend/views/new.py:233 #, python-brace-format msgid "\"{0}\" was created successfully." -msgstr "„{0}” je uspješno stvoreno." +msgstr "„{0}” je uspješno stvoren." #: bottles/frontend/views/preferences.py:142 msgid "Steam was not found or Bottles does not have enough permissions." msgstr "Steam nije pronađen ili Butelje nema potrebne dozvole." #: bottles/frontend/views/preferences.py:176 -#, fuzzy msgid "Select Bottles Path" -msgstr "Odaberi butelju" +msgstr "Odaberi stazu za „Butelje”" #: bottles/frontend/views/preferences.py:198 msgid "Relaunch Bottles?" @@ -2611,9 +2584,9 @@ msgid "Manifest for {0}" msgstr "Manifest za {0}" #: bottles/frontend/widgets/dependency.py:172 -#, fuzzy, python-brace-format +#, python-brace-format msgid "\"{0}\" uninstalled" -msgstr "„{0}” instalirano" +msgstr "„{0}” deinstalirano" #: bottles/frontend/widgets/dependency.py:174 #, python-brace-format @@ -2642,14 +2615,16 @@ msgid "" "This program works with noticeable glitches, but these glitches do not " "affect the application's functionality." msgstr "" +"Ovaj program radi s primjetnim greškama, ali te greške ne utječu na " +"funkcionalnost aplikacije." #: bottles/frontend/widgets/installer.py:51 msgid "This program works with minor glitches." -msgstr "" +msgstr "Ovaj program radi s manjim greškama." #: bottles/frontend/widgets/installer.py:52 msgid "This program works perfectly." -msgstr "" +msgstr "Ovaj program radi savršeno." #: bottles/frontend/widgets/installer.py:90 #, python-brace-format @@ -2658,12 +2633,12 @@ msgstr "Pregled za {0}" #: bottles/frontend/widgets/library.py:169 #: bottles/frontend/widgets/program.py:194 -#, fuzzy, python-brace-format +#, python-brace-format msgid "Stopping \"{0}\"…" -msgstr "Pokreće se „{0}” …" +msgstr "Prekida se „{0}” …" #: bottles/frontend/widgets/program.py:190 -#, fuzzy, python-brace-format +#, python-brace-format msgid "Launching \"{0}\" with Steam…" msgstr "Pokreće se „{0}” sa Steamom …" @@ -2688,17 +2663,17 @@ msgid "\"{0}\" renamed to \"{1}\"" msgstr "„{0}” preimenovan u „{1}”" #: bottles/frontend/widgets/program.py:297 -#, fuzzy, python-brace-format +#, python-brace-format msgid "Desktop Entry created for \"{0}\"" -msgstr "Ikona programa na radnoj površini stvorena za „{0}”" +msgstr "Unos na radnoj površini stvoren za „{0}”" #: bottles/frontend/widgets/program.py:313 -#, fuzzy, python-brace-format +#, python-brace-format msgid "\"{0}\" added to your library" msgstr "„{0}” dodan u tvoju biblioteku" #: bottles/frontend/widgets/program.py:331 -#, fuzzy, python-brace-format +#, python-brace-format msgid "\"{0}\" added to your Steam library" msgstr "„{0}” dodan u tvoju Steam biblioteku" @@ -2716,14 +2691,12 @@ msgstr "" "izvještaja." #: bottles/frontend/windows/display.py:102 -#, fuzzy msgid "Updating display settings, please wait…" -msgstr "Aktualiziranje verzije Windowsa, pričekaj …" +msgstr "Aktualiziranje postavki prikaza. Pričekaj …" #: bottles/frontend/windows/display.py:114 -#, fuzzy msgid "Display settings updated" -msgstr "Postavke prikaza" +msgstr "Postavke prikaza su aktualizirana" #: bottles/frontend/windows/dlloverrides.py:136 msgid "No overrides found." @@ -2753,7 +2726,6 @@ msgid "Copy to clipboard" msgstr "Kopiraj u međuspremnik" #: bottles/frontend/windows/installer.py:62 -#, fuzzy msgid "Select Resource File" msgstr "Odaberi datoteku resursa" @@ -2801,9 +2773,8 @@ msgid "This setting is different from the bottle's default." msgstr "Ova se postavka razlikuje od standardne postavke butelje." #: bottles/frontend/windows/launchoptions.py:215 -#, fuzzy msgid "Select Script" -msgstr "Odaberi datoteku" +msgstr "Odaberi skripta" #: bottles/frontend/windows/main_window.py:220 msgid "Custom Bottles Path not Found" @@ -2818,11 +2789,10 @@ msgstr "" #: data/com.usebottles.bottles.desktop.in.in:3 msgid "@APP_NAME@" -msgstr "" +msgstr "@APP_NAME@" #: data/com.usebottles.bottles.desktop.in.in:4 #: data/com.usebottles.bottles.metainfo.xml.in:8 -#, fuzzy msgid "Run Windows Software" msgstr "Pokreći Windows softver" @@ -3075,16 +3045,15 @@ msgstr ".. i još mnogo toga što možeš pronaći instaliranjem programa Butelj #: data/com.usebottles.bottles.metainfo.xml.in:84 msgid "Update metadata information" -msgstr "" +msgstr "Aktualiziraj informacije metapodataka" #: data/com.usebottles.bottles.metainfo.xml.in:89 msgid "Add more update information and correct release notes version" msgstr "" #: data/com.usebottles.bottles.metainfo.xml.in:94 -#, fuzzy msgid "Fixed \"Add to Steam\" button" -msgstr "Dodaj u Steam" +msgstr "Ispravljen gumb „Dodaj u Steam”" #: data/com.usebottles.bottles.metainfo.xml.in:95 msgid "Fixed BottleConfig being not serializable" @@ -3095,9 +3064,8 @@ msgid "Fixed Patool double extraction failing" msgstr "" #: data/com.usebottles.bottles.metainfo.xml.in:101 -#, fuzzy msgid "Correct version" -msgstr "Verzija komponente" +msgstr "Isparvna verzija" #: data/com.usebottles.bottles.metainfo.xml.in:106 msgid "Fix crash when creating a bottle" @@ -3105,7 +3073,7 @@ msgstr "Ispravljena greška prilikom stvaranja butelje" #: data/com.usebottles.bottles.metainfo.xml.in:111 msgid "Major change: Redesign New Bottle interface" -msgstr "" +msgstr "Velika promjena: Redizajn New Bottle sučelja" #: data/com.usebottles.bottles.metainfo.xml.in:112 msgid "Quality of life improvements:" @@ -3129,9 +3097,8 @@ msgid "Adding shortcut to Steam resulted an error" msgstr "" #: data/com.usebottles.bottles.metainfo.xml.in:120 -#, fuzzy msgid "Importing backups resulted an error" -msgstr "Uvoz sigurnosne kopije: {0}" +msgstr "Uvoz sigurnosnih kopija koje su prouzročile grešku" #: data/com.usebottles.bottles.metainfo.xml.in:121 msgid "Steam Runtime automatically enabled when using wine-ge-custom" @@ -3146,16 +3113,17 @@ msgstr "" #: data/com.usebottles.bottles.metainfo.xml.in:123 msgid "Fix various issues related to text encoding" -msgstr "" +msgstr "Ispravljeni razni problemi u vezi s kodiranjem teksta" #: data/com.usebottles.bottles.metainfo.xml.in:130 msgid "Fix error when downloading if Bottles isn't run from terminal" msgstr "" +"Ispravljena greška prilikom preuzimanja ako Bottles nije pokrenut putem " +"terminala" #: data/com.usebottles.bottles.metainfo.xml.in:137 -#, fuzzy msgid "Correct version date" -msgstr "Datum stvaranja" +msgstr "Ispravljen datum verzije" #: data/com.usebottles.bottles.metainfo.xml.in:138 msgid "Hide NVIDIA-related critical errors on non NVIDIA systems" diff --git a/po/it.po b/po/it.po index 061ab5eb1f4..116deb4143f 100644 --- a/po/it.po +++ b/po/it.po @@ -8,8 +8,8 @@ msgstr "" "Project-Id-Version: bottles\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-03-27 13:50+0530\n" -"PO-Revision-Date: 2023-06-11 18:29+0000\n" -"Last-Translator: Kryotek \n" +"PO-Revision-Date: 2024-03-12 18:01+0000\n" +"Last-Translator: Andrea Andre \n" "Language-Team: Italian \n" "Language: it\n" @@ -17,7 +17,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.18-dev\n" +"X-Generator: Weblate 5.5-dev\n" #: bottles/backend/managers/backup.py:48 bottles/backend/managers/backup.py:95 msgid "No path specified" @@ -223,7 +223,7 @@ msgstr "Sviluppatori di Bottles" #: bottles/frontend/ui/about.blp:12 msgid "translator_credits" -msgstr "" +msgstr "crediti_traduttore" #: bottles/frontend/ui/component-entry.blp:4 msgid "Component version" @@ -405,7 +405,7 @@ msgstr "Ambiente" #: bottles/frontend/ui/details-preferences.blp:14 #: bottles/frontend/ui/new.blp:128 msgid "Runner" -msgstr "Runner" +msgstr "Avviatore" #: bottles/frontend/ui/details-bottle.blp:213 #: bottles/frontend/ui/list-entry.blp:21 @@ -474,7 +474,7 @@ msgstr "Installa dipendenze dei programmi." #: bottles/frontend/ui/details-preferences.blp:377 #: bottles/frontend/views/details.py:149 msgid "Snapshots" -msgstr "Istantanee" +msgstr "Snapshots" #: bottles/frontend/ui/details-bottle.blp:371 msgid "Create and manage bottle states." @@ -635,7 +635,7 @@ msgstr "La versione del layer di compatibilità Wine." #: bottles/frontend/ui/details-preferences.blp:17 msgid "Updating Runner and components, please wait…" -msgstr "Aggiornamento del runner e dei componenti, attendere…" +msgstr "Aggiornamento dell'avviatore e dei componenti, attendere…" #: bottles/frontend/ui/details-preferences.blp:27 #: bottles/frontend/ui/preferences.blp:262 @@ -742,7 +742,7 @@ msgstr "" #: bottles/frontend/ui/details-preferences.blp:138 msgid "Manage Post-Processing Layer settings" -msgstr "Gestisci le impostazioni del livello di Post-Processing" +msgstr "Gestisci le impostazioni del layer di post-processing" #: bottles/frontend/ui/details-preferences.blp:154 msgid "Manage how games should be displayed on the screen using Gamescope." @@ -1122,7 +1122,7 @@ msgid "" "devices by the runner (e.g. C: D:…)." msgstr "" "Questi sono percorsi dal tuo sistema host che sono mappati e riconosciuti " -"come dispositivi dal runner (ad es. C: D:…)." +"come dispositivi dall'avviatore (ad es. C: D:…)." #: bottles/frontend/ui/dialog-drives.blp:27 msgid "Letter" @@ -1216,7 +1216,7 @@ msgstr "Risoluzione del gioco" #: bottles/frontend/ui/dialog-gamescope.blp:45 msgid "Uses the resolution of the video game as a reference in pixels." -msgstr "Utilizza la risoluzione del videogioco come riferimento in pixel." +msgstr "Utilizza la risoluzione del videogioco in pixelcome riferimento." #: bottles/frontend/ui/dialog-gamescope.blp:48 #: bottles/frontend/ui/dialog-gamescope.blp:85 @@ -1237,8 +1237,8 @@ msgid "" "Upscales the resolution when using a resolution higher than the game " "resolution in pixels." msgstr "" -"Aumenta la risoluzione quando si usa una risoluzione superiore a quella del " -"gioco in pixel." +"Aumenta la risoluzione quando si usa una risoluzione in pixel superiore a " +"quella del gioco." #: bottles/frontend/ui/dialog-gamescope.blp:118 msgid "Miscellaneous" @@ -1333,9 +1333,8 @@ msgid "Journal Browser" msgstr "Registro" #: bottles/frontend/ui/dialog-journal.blp:53 -#, fuzzy msgid "Change Logging Level." -msgstr "Cambia il livello di logging." +msgstr "Cambia il livello dei log." #: bottles/frontend/ui/dialog-journal.blp:57 msgid "All" @@ -1366,7 +1365,7 @@ msgstr "Script post-esecuzione" #: bottles/frontend/ui/dialog-launch-options.blp:53 #: bottles/frontend/windows/launchoptions.py:54 msgid "Choose a script which should be executed after run." -msgstr "Scegli uno script da eseguire dopo l'esecuzione." +msgstr "Scegli uno script che dovrebbe essere eseguito dopo l'avvio." #: bottles/frontend/ui/dialog-launch-options.blp:70 msgid "Choose a Script" @@ -1473,12 +1472,10 @@ msgid "Sandbox Settings" msgstr "Impostazioni Sandbox" #: bottles/frontend/ui/dialog-sandbox.blp:25 -#, fuzzy msgid "Share Network" msgstr "Condividi Rete" #: bottles/frontend/ui/dialog-sandbox.blp:34 -#, fuzzy msgid "Share Sound" msgstr "Condividi Audio" @@ -1541,7 +1538,7 @@ msgstr "Fatto! Per favore riavvia Bottles." #. Translators: vkBasalt is a Vulkan post processing layer for Linux #: bottles/frontend/ui/dialog-vkbasalt.blp:10 msgid "Post-Processing Effects Settings" -msgstr "Impostazioni effetti di Post-Processing" +msgstr "Impostazioni degli effetti di post-elaborazione" #: bottles/frontend/ui/dialog-vkbasalt.blp:44 msgid "Default" @@ -1585,40 +1582,39 @@ msgstr "Mostra Informazioni" #. Translators: Luma is not translatable #: bottles/frontend/ui/dialog-vkbasalt.blp:99 msgid "Denoised Luma Sharpening" -msgstr "" +msgstr "Rimuovere effetto Luma" #: bottles/frontend/ui/dialog-vkbasalt.blp:130 msgid "Denoise" -msgstr "" +msgstr "Eliminazione del rumore" #: bottles/frontend/ui/dialog-vkbasalt.blp:160 msgid "Fast Approximate Anti-Aliasing" -msgstr "" +msgstr "Anti-aliasing veloce e approssimativo" #: bottles/frontend/ui/dialog-vkbasalt.blp:163 -#, fuzzy msgid "Subpixel Quality" -msgstr "Qualità ultra" +msgstr "Qualità subpixel" #: bottles/frontend/ui/dialog-vkbasalt.blp:191 msgid "Quality Edge Threshold" -msgstr "" +msgstr "Soglia qualità bordo" #: bottles/frontend/ui/dialog-vkbasalt.blp:219 msgid "Quality Edge Threshold Minimum" -msgstr "" +msgstr "Soglia minima qualità del bordo" #: bottles/frontend/ui/dialog-vkbasalt.blp:249 msgid "Subpixel Morphological Anti-Aliasing" -msgstr "" +msgstr "Anti-aliasing morfologico subpixel" #: bottles/frontend/ui/dialog-vkbasalt.blp:252 msgid "Edge Detection" -msgstr "" +msgstr "Rilevamento dei bordi" #: bottles/frontend/ui/dialog-vkbasalt.blp:267 msgid "Luma" -msgstr "" +msgstr "Luma" #: bottles/frontend/ui/dialog-vkbasalt.blp:273 msgid "Color" @@ -1630,45 +1626,57 @@ msgstr "Soglia" #: bottles/frontend/ui/dialog-vkbasalt.blp:312 msgid "Max Search Steps" -msgstr "" +msgstr "Passaggi di ricerca massimi" #: bottles/frontend/ui/dialog-vkbasalt.blp:339 msgid "Max Search Steps Diagonal" -msgstr "" +msgstr "Numero massimo di passaggi di ricerca diagonale" #: bottles/frontend/ui/dialog-vkbasalt.blp:366 msgid "Max Corner Rounding" -msgstr "" +msgstr "Arrotondamento massimo degli angoli" #: bottles/frontend/ui/dialog-vkbasalt.blp:411 msgid "" "CAS sharpness increases the sharpness of a frame. Higher values make the " "frame sharper, whereas values lower than 0 make the frame softer than native." msgstr "" +"La nitidezza CAS aumenta la nitidezza di un fotogramma. Valori più alti " +"rendono il fotogramma più nitido, mentre valori inferiori a 0 rendono il " +"fotogramma più morbido rispetto a quello nativo." #: bottles/frontend/ui/dialog-vkbasalt.blp:428 msgid "" "DLS sharpness increases the sharpness of a frame. Higher values make the " "frame sharper." msgstr "" +"La nitidezza DLS aumenta la nitidezza di un fotogramma. Valori più elevati " +"rendono il fotogramma più nitido." #: bottles/frontend/ui/dialog-vkbasalt.blp:445 msgid "" "DLS denoise decreases the noise of a frame. Higher values make the frame " "softer." msgstr "" +"Il denoise DLS diminuisce il rumore di un frame. Valori più alti rendono il " +"frame più morbido." #: bottles/frontend/ui/dialog-vkbasalt.blp:462 msgid "" "FXAA subpixel quality decreases aliasing at the subpixel level. Higher " "values make the frame softer." msgstr "" +"La qualità dei subpixel FXAA diminuisce l'aliasing a livello dei subpixel. " +"Valori più alti rendono il frame più morbido." #: bottles/frontend/ui/dialog-vkbasalt.blp:479 msgid "" "FXAA edge threshold is the minimum amount of contrast required to apply the " "FXAA algorithm. Higher values make the frame have more contrast." msgstr "" +"La soglia del bordo FXAA è la quantità minima di contrasto richiesta per " +"applicare l'algoritmo FXAA. Valori più alti fanno sì che il frame abbia più " +"contrasto." #: bottles/frontend/ui/dialog-vkbasalt.blp:496 msgid "" @@ -1676,34 +1684,48 @@ msgid "" "are ignored by the FXAA algorithm. Higher values make FXAA ignore pixels " "below the specified value and can lead to a performance increase." msgstr "" +"La soglia minima del bordo qualità FXAA è il valore minimo dei pixel scuri " +"che vengono ignorati dall'algoritmo FXAA. Valori più alti fanno sì che FXAA " +"ignori i pixel al di sotto del valore specificato e possono portare ad un " +"aumento delle prestazioni." #: bottles/frontend/ui/dialog-vkbasalt.blp:513 msgid "" "Luma detects edges from a monochrome perspective, whereas Color detects " "edges based on colors. Luma is more performant than Color." msgstr "" +"Luma rileva i contorni da una prospettiva monocromatica, mentre Colore " +"rileva i bordi in base ai colori. Luma è più performante del Colore." #: bottles/frontend/ui/dialog-vkbasalt.blp:530 msgid "" "SMAA threshold specifies the sensitivity of edge detection. Lower values " "detect more edges at the expense of performance." msgstr "" +"La soglia SMAA specifica la sensibilità del rilevamento dei bordi. Valori " +"più bassi rilevano più bordi a scapito delle prestazioni." #: bottles/frontend/ui/dialog-vkbasalt.blp:547 msgid "" "SMAA max search steps specifies how many horizontal and vertical search " "steps are performed when searching for edges." msgstr "" +"I passaggi di ricerca massimi SMAA specificano quanti passaggi di ricerca " +"orizzontali e verticali vengono eseguiti durante la ricerca dei bordi." #: bottles/frontend/ui/dialog-vkbasalt.blp:564 msgid "" "SMAA max diagonal search steps specifies how many diagonal search steps are " "performed when searching for edges." msgstr "" +"I passaggi di ricerca diagonale massimi SMAA specificano quanti passaggi di " +"ricerca diagonale vengono eseguiti durante la ricerca dei bordi." #: bottles/frontend/ui/dialog-vkbasalt.blp:581 msgid "SMAA corner rounding specifies the strength of rounding edge corners." msgstr "" +"L'arrotondamento degli angoli SMAA specifica la forza dell'arrotondamento " +"degli angoli dei bordi." #: bottles/frontend/ui/dll-override-entry.blp:8 msgid "Builtin (Wine)" @@ -1749,18 +1771,16 @@ msgid "Wine prefix name" msgstr "Nome del prefisso Wine" #: bottles/frontend/ui/importer-entry.blp:28 -#, fuzzy msgid "Manager" -msgstr "Gestisci runner" +msgstr "Gestore" #: bottles/frontend/ui/importer-entry.blp:38 msgid "This Wine prefix was already imported in Bottles." msgstr "Questo wineprefix è già stato importato in Bottles." #: bottles/frontend/ui/importer.blp:22 -#, fuzzy msgid "Import a Bottle backup" -msgstr "Importa da backup" +msgstr "Importa un backup della bottiglia" #: bottles/frontend/ui/importer.blp:28 msgid "Search again for prefixes" @@ -1771,13 +1791,12 @@ msgid "No Prefixes Found" msgstr "Nessun prefisso trovato" #: bottles/frontend/ui/importer.blp:39 -#, fuzzy msgid "" "No external prefixes were found. Does Bottles have access to them?\n" "Use the icon on the top to import a bottle from a backup." msgstr "" -"Non è stato trovato alcun prefisso di Lutris, PlayOnLinux, ecc.\n" -"Usare l'icona in alto per importare una bottiglia da un backup" +"Non è stato trovato alcun prefisso esterno. Bottles ha accesso ad essi?\n" +"Utilizza l'icona in alto per importare una bottiglia da un backup." #: bottles/frontend/ui/importer.blp:74 msgid "Full Archive" @@ -1792,14 +1811,12 @@ msgid "Read Review…" msgstr "Leggi rapporto…" #: bottles/frontend/ui/installer-entry.blp:34 -#, fuzzy msgid "Installer name" -msgstr "Installatori" +msgstr "Nome dell'installatore" #: bottles/frontend/ui/installer-entry.blp:35 -#, fuzzy msgid "Installer description" -msgstr "Installatori" +msgstr "Descrizione dell'installatore" #: bottles/frontend/ui/installer-entry.blp:42 msgid "Unknown" @@ -1810,9 +1827,8 @@ msgid "Install this Program" msgstr "Installa questo programma" #: bottles/frontend/ui/installer-entry.blp:69 -#, fuzzy msgid "Program Menu" -msgstr "Programmi" +msgstr "Menù del programma" #: bottles/frontend/ui/library-entry.blp:36 msgid "No Thumbnail" @@ -1828,9 +1844,8 @@ msgid "Launch with Steam" msgstr "Avvia con Steam" #: bottles/frontend/ui/library-entry.blp:108 -#, fuzzy msgid "Item name" -msgstr "Nome bottiglia" +msgstr "Nome dell'elemento" #: bottles/frontend/ui/library-entry.blp:132 msgid "Remove from Library" @@ -1888,9 +1903,8 @@ msgid "Bottles" msgstr "Bottles" #: bottles/frontend/ui/list.blp:49 -#, fuzzy msgid "Create New Bottle…" -msgstr "Crea una nuova bottiglia" +msgstr "Crea nuova bottiglia…" #: bottles/frontend/ui/list.blp:63 msgid "No Results Found" @@ -1913,9 +1927,8 @@ msgid "Browse" msgstr "Sfoglia" #: bottles/frontend/ui/new.blp:32 -#, fuzzy msgid "C_reate" -msgstr "Crea" +msgstr "C_rea" #: bottles/frontend/ui/new.blp:53 #, fuzzy @@ -1923,28 +1936,24 @@ msgid "Bottle Name" msgstr "Nome bottiglia" #: bottles/frontend/ui/new.blp:75 -#, fuzzy msgid "_Application" -msgstr "Applicazioni" +msgstr "_Applicazione" #: bottles/frontend/ui/new.blp:88 -#, fuzzy msgid "_Gaming" -msgstr "Giochi" +msgstr "_Giochi" #: bottles/frontend/ui/new.blp:101 -#, fuzzy msgid "C_ustom" -msgstr "Personalizzato" +msgstr "P_ersonalizzato" #: bottles/frontend/ui/new.blp:114 msgid "Custom" msgstr "Personalizzato" #: bottles/frontend/ui/new.blp:118 -#, fuzzy msgid "Share User Directory" -msgstr "Scegli una cartella" +msgstr "Condividi la directory utente" #: bottles/frontend/ui/new.blp:119 msgid "" @@ -1952,38 +1961,38 @@ msgid "" "sharing personal information to Windows software. This option cannot be " "changed after the bottle has been created." msgstr "" +"Ciò rende la directory dell'utente rilevabile nella bottiglia, con il " +"rischio di condividere informazioni personali con il software Windows. " +"Questa opzione non può essere modificata dopo la creazione della bottiglia." #: bottles/frontend/ui/new.blp:136 msgid "Architecture" msgstr "Architettura" #: bottles/frontend/ui/new.blp:137 -#, fuzzy msgid "32-bit should only be used if strictly necessary." -msgstr "Si consiglia di utilizzare 32 bit solo se strettamente necessario." +msgstr "" +"La versione a 32 bit deve essere utilizzata solo se strettamente necessaria." #: bottles/frontend/ui/new.blp:146 msgid "Import a custom configuration." msgstr "Importa una configurazione personalizzata." #: bottles/frontend/ui/new.blp:176 -#, fuzzy msgid "Bottle Directory" -msgstr "Scegli una cartella" +msgstr "Directory della bottiglia" #: bottles/frontend/ui/new.blp:177 msgid "Directory that will contain the data of this bottle." -msgstr "" +msgstr "Directory che conterrà i dati di questa bottiglia." #: bottles/frontend/ui/new.blp:249 -#, fuzzy msgid "_Close" -msgstr "Chiudi" +msgstr "_Chiudi" #: bottles/frontend/ui/new.blp:281 -#, fuzzy msgid "This name is unavailable, please try another." -msgstr "Questa funzione non è disponibile sul tuo sistema." +msgstr "Questo nome non è disponibile, provane un altro." #: bottles/frontend/ui/onboard.blp:34 msgid "Previous" @@ -1995,9 +2004,8 @@ msgid "Welcome to Bottles" msgstr "Benvenuto in Bottles" #: bottles/frontend/ui/onboard.blp:60 -#, fuzzy msgid "Run Windows Software on Linux." -msgstr "Esegui programmi per Windows su Linux con Bottles!" +msgstr "Esegui il software Windows su Linux." # Translators: Bottles is a generic noun here, referring to wine prefixes and is to be translated #: bottles/frontend/ui/onboard.blp:65 @@ -2009,7 +2017,7 @@ msgid "" "Bottles uses compatibility runners to provide isolated containerized Windows-" "like environments where programs run." msgstr "" -"Bottles usa dei runner di compatibilità per fornire ambienti di lavoro " +"Bottles usa degli avviatori di compatibilità per fornire ambienti di lavoro " "equivalenti a Windows isolati e containerizzati in cui vengono eseguiti i " "programmi." @@ -2022,13 +2030,12 @@ msgid "We need a few more minutes to set everything up…" msgstr "Serve ancora qualche minuto per preparare tutto…" #: bottles/frontend/ui/onboard.blp:105 -#, fuzzy msgid "All Ready!" msgstr "Tutto pronto!" #: bottles/frontend/ui/onboard.blp:114 msgid "Please Finish the setup first" -msgstr "" +msgstr "Per favore completa prima la configurazione" # Translators: Bottles is a proper noun referring to the app #: bottles/frontend/ui/onboard.blp:120 @@ -2122,35 +2129,33 @@ msgid "Requires Epic Games Store installed in the bottle." msgstr "Richiede l'installazione di Epic Games Store nella bottiglia." #: bottles/frontend/ui/preferences.blp:117 -#, fuzzy msgid "List Ubisoft Games in Programs List" -msgstr "Elenca Epic Games nell'elenco dei programmi" +msgstr "Elenca Ubisoft Games nell'elenco dei programmi" #: bottles/frontend/ui/preferences.blp:118 -#, fuzzy msgid "Requires Ubisoft Connect installed in the bottle." -msgstr "Richiede l'installazione di Epic Games Store nella bottiglia." +msgstr "Richiede l'installazione di Ubisoft Connect nella bottiglia." #: bottles/frontend/ui/preferences.blp:128 msgid "Advanced" msgstr "Avanzate" #: bottles/frontend/ui/preferences.blp:131 -#, fuzzy msgid "Bottles Directory" -msgstr "Scegli una cartella" +msgstr "Directory di Bottles" #: bottles/frontend/ui/preferences.blp:132 msgid "Directory that contains the data of your Bottles." -msgstr "" +msgstr "Directory che contiene i dati delle tue Bottiglie." #: bottles/frontend/ui/preferences.blp:167 msgid "Runners" -msgstr "Runner" +msgstr "Avviatori" #: bottles/frontend/ui/preferences.blp:181 msgid "Bottles is running in offline mode, so runners are not available." -msgstr "Bottles è in modalità offline, quindi i runner non sono disponibili." +msgstr "" +"Bottles è in modalità offline, quindi gli avviatori non sono disponibili." #: bottles/frontend/ui/preferences.blp:208 msgid "Pre-Release" @@ -2167,6 +2172,8 @@ msgstr "Componenti DLL" #: bottles/frontend/ui/preferences.blp:238 msgid "Bottles is running in offline mode, so DLLs are not available." msgstr "" +"Bottles è in esecuzione in modalità offline, quindi le DLL non sono " +"disponibili." #: bottles/frontend/ui/preferences.blp:270 msgid "DXVK-NVAPI" @@ -2194,6 +2201,8 @@ msgid "" "These features are under heavy development and may be unstable, expect bugs " "and breakage." msgstr "" +"Queste funzionalità sono in fase di intenso sviluppo e potrebbero essere " +"instabili, prevedere bug e rotture." #: bottles/frontend/ui/preferences.blp:303 msgid "Sandbox per bottle" @@ -2204,12 +2213,10 @@ msgid "In early development." msgstr "In fase di sviluppo iniziale." #: bottles/frontend/ui/program-entry.blp:19 -#, fuzzy msgid "Launch with Terminal" -msgstr "Avvia nel terminale" +msgstr "Avvia con il terminale" #: bottles/frontend/ui/program-entry.blp:25 -#, fuzzy msgid "Browse Path" msgstr "Sfoglia il percorso" @@ -2218,7 +2225,6 @@ msgid "Change Launch Options…" msgstr "Modifica opzioni di avvio…" #: bottles/frontend/ui/program-entry.blp:43 -#, fuzzy msgid "Add to Library" msgstr "Aggiungi alla mia libreria" @@ -2247,25 +2253,21 @@ msgid "Remove from List" msgstr "Rimuovi dalla lista" #: bottles/frontend/ui/program-entry.blp:83 -#, fuzzy msgid "Program name" -msgstr "Programmi" +msgstr "Nome del programma" #. Translators: id as identification #: bottles/frontend/ui/state-entry.blp:8 -#, fuzzy msgid "State id" -msgstr "Nessuno stato trovato" +msgstr "Id stato" #: bottles/frontend/ui/state-entry.blp:9 -#, fuzzy msgid "State comment" -msgstr "Un breve commento" +msgstr "Commento sullo stato" #: bottles/frontend/ui/state-entry.blp:16 -#, fuzzy msgid "Restore this Snapshot" -msgstr "Ripristina lo stato" +msgstr "Ripristina questo snapshot" #: bottles/frontend/ui/task-entry.blp:19 msgid "Delete message" @@ -2273,7 +2275,7 @@ msgstr "Cancellare il messaggio" #: bottles/frontend/ui/window.blp:40 msgid "Main Menu" -msgstr "" +msgstr "Menu principale" #: bottles/frontend/ui/window.blp:54 msgid "" @@ -2301,7 +2303,7 @@ msgstr "Informazioni su Bottles" #: bottles/frontend/views/bottle_details.py:191 #, python-brace-format msgid "File \"{0}\" is not a .exe or .msi file" -msgstr "" +msgstr "Il file \"{0}\" non è un file .exe o .msi" #: bottles/frontend/views/bottle_details.py:207 #, python-format @@ -2309,37 +2311,34 @@ msgid "Updated: %s" msgstr "Aggiornato: %s" #: bottles/frontend/views/bottle_details.py:267 -#, fuzzy, python-brace-format +#, python-brace-format msgid "\"{0}\" added" -msgstr "'{0}' aggiunto." +msgstr "\"{0}\" aggiunto" #: bottles/frontend/views/bottle_details.py:270 #: bottles/frontend/views/bottle_details.py:398 #: bottles/frontend/views/list.py:128 -#, fuzzy msgid "Select Executable" -msgstr "Seleziona Bottiglia" +msgstr "Seleziona eseguibile" #: bottles/frontend/views/bottle_details.py:273 msgid "Add" msgstr "Aggiungi" #: bottles/frontend/views/bottle_details.py:346 -#, fuzzy msgid "Hide Hidden Programs" -msgstr "Mostra/nascondi i programmi rimossi" +msgstr "Nascondere i programmi nascosti" #: bottles/frontend/views/bottle_details.py:383 #: bottles/frontend/widgets/library.py:156 #: bottles/frontend/widgets/program.py:184 -#, fuzzy, python-brace-format +#, python-brace-format msgid "Launching \"{0}\"…" -msgstr "Avvio \"{0}\"…" +msgstr "Avvio di \"{0}\"…" #: bottles/frontend/views/bottle_details.py:413 -#, fuzzy msgid "Be Aware of Sandbox" -msgstr "Sandbox dedicata" +msgstr "Fai attenzione alla sandbox" #: bottles/frontend/views/bottle_details.py:414 msgid "" @@ -2347,12 +2346,16 @@ msgid "" "to keep you safe. If the program won't run, consider moving inside the " "bottle (3 dots icon on the top), then launch from there." msgstr "" +"Bottles è in esecuzione in una sandbox, un ambiente con permessi limitati " +"necessario per mantenerti al sicuro. Se il programma non viene eseguito, " +"valuta la possibilità di spostarsi all'interno della bottiglia (icona a 3 " +"punti in alto), quindi avviarlo da lì." #: bottles/frontend/views/bottle_details.py:416 #: bottles/frontend/views/bottle_details.py:525 #: bottles/frontend/windows/main_window.py:223 msgid "_Dismiss" -msgstr "" +msgstr "_Abbandona" #: bottles/frontend/views/bottle_details.py:429 msgid "Select the location where to save the backup config" @@ -2367,109 +2370,107 @@ msgid "Select the location where to save the backup archive" msgstr "Seleziona la posizione dove salvare l'archivio di backup" #: bottles/frontend/views/bottle_details.py:435 -#, fuzzy msgid "Backup" -msgstr "Backup {0}" +msgstr "Backup" #: bottles/frontend/views/bottle_details.py:440 -#, fuzzy, python-brace-format +#, python-brace-format msgid "Backup created for \"{0}\"" -msgstr "Backup creato per '{0}'." +msgstr "Backup creato per \"{0}\"" #: bottles/frontend/views/bottle_details.py:442 -#, fuzzy, python-brace-format +#, python-brace-format msgid "Backup failed for \"{0}\"" -msgstr "Impossibile creare il backup per '{0}'." +msgstr "Impossibile creare il backup per \"{0}\"" #: bottles/frontend/views/bottle_details.py:501 -#, fuzzy msgid "Are you sure you want to permanently delete \"{}\"?" -msgstr "Sei sicuro di voler rimuovere questa Bottiglia e tutti i suoi file?" +msgstr "Sei sicuro di voler eliminare definitivamente \"{}\"?" #: bottles/frontend/views/bottle_details.py:502 msgid "" "This will permanently delete all programs and settings associated with it." msgstr "" +"Ciò eliminerà definitivamente tutti i programmi e le impostazioni ad esso " +"associati." #: bottles/frontend/views/bottle_details.py:505 #: bottles/frontend/views/bottle_preferences.py:750 msgid "_Delete" -msgstr "" +msgstr "_Elimina" #: bottles/frontend/views/bottle_details.py:521 -#, fuzzy msgid "Missing Runner" -msgstr "Wine Runners" +msgstr "Avviatore mancante" #: bottles/frontend/views/bottle_details.py:522 msgid "" "The runner requested by this bottle is missing. Install it through the " "Bottles preferences or choose a new one to run applications." msgstr "" +"Manca l'avviatore richiesto da questa bottiglia. Installalo tramite le " +"preferenze in Bottles o scegline uno nuovo per eseguire le applicazioni." #: bottles/frontend/views/bottle_details.py:597 -#, fuzzy msgid "Are you sure you want to force stop all processes?" -msgstr "Sei sicuro di voler rimuovere questa Bottiglia e tutti i suoi file?" +msgstr "Sei sicuro di voler forzare l'arresto di tutti i processi?" #: bottles/frontend/views/bottle_details.py:598 msgid "This can cause data loss, corruption, and programs to malfunction." msgstr "" +"Ciò può causare perdita di dati, danneggiamento e malfunzionamento dei " +"programmi." #: bottles/frontend/views/bottle_details.py:601 msgid "Force _Stop" -msgstr "" +msgstr "Forza_Arresto" #: bottles/frontend/views/bottle_preferences.py:195 -#, fuzzy msgid "This feature is unavailable on your system." msgstr "Questa funzione non è disponibile sul tuo sistema." #: bottles/frontend/views/bottle_preferences.py:196 msgid "{} To add this feature, please run flatpak install" msgstr "" +"{} Per aggiungere questa funzionalità, esegui l'installazione di flatpak" #: bottles/frontend/views/bottle_preferences.py:246 -#, fuzzy msgid "This bottle name is already in use." -msgstr "Il nome contiene caratteri speciali o è già in uso." +msgstr "Questo nome di bottiglia è già in uso." #: bottles/frontend/views/bottle_preferences.py:301 #: bottles/frontend/windows/launchoptions.py:241 -#, fuzzy msgid "Select Working Directory" -msgstr "Cartella di lavoro" +msgstr "Seleziona Directory di lavoro" #: bottles/frontend/views/bottle_preferences.py:423 msgid "Directory that contains the data of \"{}\"." msgstr "Cartella che contiene i dati di \"{}\"." #: bottles/frontend/views/bottle_preferences.py:746 -#, fuzzy msgid "Are you sure you want to delete all snapshots?" -msgstr "Sei sicuro di voler rimuovere questa Bottiglia e tutti i suoi file?" +msgstr "Vuoi eliminare tutti gli snapshot?" #: bottles/frontend/views/bottle_preferences.py:747 msgid "This will delete all snapshots but keep your files." -msgstr "" +msgstr "Ciò eliminerà tutte le istantanee ma manterrà i tuoi file." #: bottles/frontend/views/bottle_versioning.py:90 msgid "Please migrate to the new Versioning system to create new states." msgstr "" +"Migra al nuovo sistema di controllo delle versioni per creare nuovi stati." #: bottles/frontend/views/details.py:153 msgid "Installers" msgstr "Installatori" #: bottles/frontend/views/details.py:234 -#, fuzzy msgid "Operations in progress, please wait." -msgstr "Aggiornamento della versione di Windows, attendere…" +msgstr "Operazioni in corso, attendere." #: bottles/frontend/views/details.py:239 -#, fuzzy msgid "Return to your bottles." -msgstr "Cerca bottiglie…" +msgstr "Ritorna alle tue bottiglie." #: bottles/frontend/views/importer.py:92 msgid "Backup imported successfully" @@ -2485,9 +2486,8 @@ msgid "Importing backup…" msgstr "Importazione del backup…" #: bottles/frontend/views/importer.py:119 -#, fuzzy msgid "Select a Backup Archive" -msgstr "Scegli un archivio di backup" +msgstr "Seleziona un archivio di backup" #: bottles/frontend/views/importer.py:122 #: bottles/frontend/views/importer.py:161 @@ -2495,9 +2495,8 @@ msgid "Import" msgstr "Importa" #: bottles/frontend/views/importer.py:158 bottles/frontend/views/new.py:136 -#, fuzzy msgid "Select a Configuration File" -msgstr "Scegli un file di configurazione" +msgstr "Seleziona un file di configurazione" #: bottles/frontend/views/list.py:60 bottles/frontend/views/list.py:66 msgid "N/A" @@ -2507,7 +2506,7 @@ msgstr "N/A" #: bottles/frontend/views/list.py:91 #, python-brace-format msgid "Run executable in \"{self.config.Name}\"" -msgstr "" +msgstr "Esegui l'eseguibile in \"{self.config.Name}\"" #: bottles/frontend/views/list.py:118 #, python-brace-format @@ -2521,7 +2520,7 @@ msgstr "Le tue bottiglie" #: bottles/frontend/views/loading.py:41 #, python-brace-format msgid "Downloading ~{0} of packages…" -msgstr "" +msgstr "Download di ~{0} pacchetti in corso…" #: bottles/frontend/views/loading.py:42 #, python-brace-format @@ -2529,23 +2528,20 @@ msgid "Fetched {0} of {1} packages" msgstr "Recuperati {0} di {1} pacchetti" #: bottles/frontend/views/new.py:157 -#, fuzzy msgid "Select Bottle Directory" -msgstr "Scegli una cartella" +msgstr "Seleziona directory bottiglia" #: bottles/frontend/views/new.py:176 -#, fuzzy msgid "Creating Bottle…" -msgstr "Creando una bottiglia …" +msgstr "Creazione della bottiglia…" #: bottles/frontend/views/new.py:221 -#, fuzzy msgid "Unable to Create Bottle" -msgstr "Crea una nuova bottiglia" +msgstr "Impossibile creare una bottiglia" #: bottles/frontend/views/new.py:225 msgid "Bottle failed to create with one or more errors." -msgstr "" +msgstr "Creazione della bottiglia non riuscita con uno o più errori." #. Show success #: bottles/frontend/views/new.py:232 @@ -2562,9 +2558,8 @@ msgid "Steam was not found or Bottles does not have enough permissions." msgstr "Steam non è stato trovato o Bottles non ha autorizzazioni sufficienti." #: bottles/frontend/views/preferences.py:176 -#, fuzzy msgid "Select Bottles Path" -msgstr "Seleziona Bottiglia" +msgstr "Seleziona percorso bottiglie" #: bottles/frontend/views/preferences.py:198 msgid "Relaunch Bottles?" @@ -2586,7 +2581,7 @@ msgstr "" #: bottles/frontend/views/preferences.py:202 msgid "_Relaunch" -msgstr "" +msgstr "_Rilancia" #: bottles/frontend/views/preferences.py:243 msgid "Based on Valve's Wine, includes staging and Proton patches." @@ -2625,24 +2620,24 @@ msgid "Manifest for {0}" msgstr "Manifesto per {0}" #: bottles/frontend/widgets/dependency.py:172 -#, fuzzy, python-brace-format +#, python-brace-format msgid "\"{0}\" uninstalled" -msgstr "{0} installato." +msgstr "\"{0}\" disinstallato" #: bottles/frontend/widgets/dependency.py:174 -#, fuzzy, python-brace-format +#, python-brace-format msgid "\"{0}\" installed" -msgstr "{0} installato." +msgstr "\"{0}\" installato" #: bottles/frontend/widgets/dependency.py:188 -#, fuzzy, python-brace-format +#, python-brace-format msgid "\"{0}\" failed to install" -msgstr "'{0}' installato." +msgstr "\"{0}\" installazione fallita" #: bottles/frontend/widgets/importer.py:68 -#, fuzzy, python-brace-format +#, python-brace-format msgid "\"{0}\" imported" -msgstr "'{0}' importato." +msgstr "\"{0}\" importato" #: bottles/frontend/widgets/installer.py:49 msgid "" @@ -2650,20 +2645,25 @@ msgid "" "the best possible experience, but expect glitches, instability and lack of " "working features." msgstr "" +"Questa applicazione potrebbe funzionare male. Il programma di installazione " +"è stato configurato per fornire la migliore esperienza possibile, ma prevede " +"problemi, instabilità e mancanza di funzionalità ulteriori." #: bottles/frontend/widgets/installer.py:50 msgid "" "This program works with noticeable glitches, but these glitches do not " "affect the application's functionality." msgstr "" +"Questo programma funziona con notevoli problemi, ma questi problemi non " +"influiscono sulla funzionalità dell'applicazione." #: bottles/frontend/widgets/installer.py:51 msgid "This program works with minor glitches." -msgstr "" +msgstr "Questo programma funziona con piccoli problemi." #: bottles/frontend/widgets/installer.py:52 msgid "This program works perfectly." -msgstr "" +msgstr "Questo programma funziona perfettamente." #: bottles/frontend/widgets/installer.py:90 #, python-brace-format @@ -2679,42 +2679,42 @@ msgstr "Interrompo \"{0}\"…" #: bottles/frontend/widgets/program.py:190 #, python-brace-format msgid "Launching \"{0}\" with Steam…" -msgstr "Eseguo \"{0}\" con Steam" +msgstr "Eseguo \"{0}\" con Steam…" #: bottles/frontend/widgets/program.py:214 -#, fuzzy, python-brace-format +#, python-brace-format msgid "\"{0}\" hidden" -msgstr "'{0}' nascosto." +msgstr "\"{0}\" nascosto" #: bottles/frontend/widgets/program.py:216 -#, fuzzy, python-brace-format +#, python-brace-format msgid "\"{0}\" showed" -msgstr "'{0}' mostrato." +msgstr "\"{0}\" mostrato" #: bottles/frontend/widgets/program.py:242 -#, fuzzy, python-brace-format +#, python-brace-format msgid "\"{0}\" removed" -msgstr "'{0}' è stato rimosso." +msgstr "\"{0}\" rimosso" #: bottles/frontend/widgets/program.py:274 -#, fuzzy, python-brace-format +#, python-brace-format msgid "\"{0}\" renamed to \"{1}\"" -msgstr "'{0}' rinominato in '{1}'." +msgstr "\"{0}\"rinominato in \"{1}\"" #: bottles/frontend/widgets/program.py:297 -#, fuzzy, python-brace-format +#, python-brace-format msgid "Desktop Entry created for \"{0}\"" -msgstr "Icona sulla scrivania creata per '{0}'" +msgstr "Voce desktop creata per \"{0}\"" #: bottles/frontend/widgets/program.py:313 -#, fuzzy, python-brace-format +#, python-brace-format msgid "\"{0}\" added to your library" -msgstr "'{0}' aggiunto alla tua libreria" +msgstr "\"{0}\"aggiunto alla tua libreria" #: bottles/frontend/widgets/program.py:331 -#, fuzzy, python-brace-format +#, python-brace-format msgid "\"{0}\" added to your Steam library" -msgstr "'{0}' aggiunto alla tua libreria Steam" +msgstr "\"{0}\" aggiunto alla tua libreria Steam" #: bottles/frontend/windows/crash.py:33 msgid "Show report" @@ -2742,9 +2742,8 @@ msgid "No overrides found." msgstr "Nessuna sostituzione trovata." #: bottles/frontend/windows/drives.py:71 -#, fuzzy msgid "Select Drive Path" -msgstr "Elimina bottiglia…" +msgstr "Seleziona percorso del drive" #: bottles/frontend/windows/envvars.py:131 msgid "No environment variables defined." @@ -2765,9 +2764,8 @@ msgid "Copy to clipboard" msgstr "Copia negli appunti" #: bottles/frontend/windows/installer.py:62 -#, fuzzy msgid "Select Resource File" -msgstr "Seleziona il File della Risorsa" +msgstr "Seleziona file risorse" #: bottles/frontend/windows/installer.py:109 msgid "Installing Windows dependencies…" @@ -2813,29 +2811,28 @@ msgid "This setting is different from the bottle's default." msgstr "Questa impostazione è diversa da quella predefinita della bottiglia." #: bottles/frontend/windows/launchoptions.py:215 -#, fuzzy msgid "Select Script" -msgstr "Elimina bottiglia…" +msgstr "Seleziona script" #: bottles/frontend/windows/main_window.py:220 -#, fuzzy msgid "Custom Bottles Path not Found" -msgstr "Percorso bottiglie personalizzato (Riavvio richiesto)" +msgstr "Percorso bottiglie personalizzate non trovato" #: bottles/frontend/windows/main_window.py:221 msgid "" "Falling back to default path. No bottles from the given path will be listed." msgstr "" +"Ritorno al percorso predefinito. Non verrà elencata alcuna bottiglia dal " +"percorso indicato." #: data/com.usebottles.bottles.desktop.in.in:3 msgid "@APP_NAME@" -msgstr "" +msgstr "@APP_NAME@" #: data/com.usebottles.bottles.desktop.in.in:4 #: data/com.usebottles.bottles.metainfo.xml.in:8 -#, fuzzy msgid "Run Windows Software" -msgstr "Usa programmi per Windows" +msgstr "Esegui software per Windows" #: data/com.usebottles.bottles.desktop.in.in:13 msgid "wine;windows;" @@ -2883,12 +2880,11 @@ msgstr "Attiva/disattiva l'elenco di Epic Games." #: data/com.usebottles.bottles.gschema.xml:31 msgid "Ubisoft Connect listing" -msgstr "" +msgstr "Elenco Ubisoft Connect" #: data/com.usebottles.bottles.gschema.xml:32 -#, fuzzy msgid "Toggle ubisoft connect listing." -msgstr "Attiva/disattiva l'elenco delle app di Steam." +msgstr "Attiva/disattiva l'elenco di ubisoft connect." #: data/com.usebottles.bottles.gschema.xml:36 msgid "Window width" @@ -2924,7 +2920,7 @@ msgstr "Release candidate" #: data/com.usebottles.bottles.gschema.xml:57 msgid "Toggle release candidate for runners." -msgstr "Attiva/disattiva le release candidate per i runners." +msgstr "Attiva/disattiva le release candidate per gli avviatori." #: data/com.usebottles.bottles.gschema.xml:61 msgid "Startup view" @@ -2940,7 +2936,7 @@ msgid "" "candidate for runners." msgstr "" "Attiva/disattiva funzioni sperimentali come il versionamento, gli " -"installatori e i Release candidate per i runner." +"installatori e i Release candidate per gli avviatori." #: data/com.usebottles.bottles.gschema.xml:71 msgid "Steam Proton Support" @@ -2968,12 +2964,11 @@ msgstr "Chiudi Bottles dopo aver avviato l'eseguibile dal gestore di file." #: data/com.usebottles.bottles.gschema.xml:86 msgid "Show sandbox warning" -msgstr "" +msgstr "Mostra avviso sandbox" #: data/com.usebottles.bottles.gschema.xml:87 -#, fuzzy msgid "Toggle sandbox warning." -msgstr "Attiva/disattiva l'elenco delle app di Steam." +msgstr "Attiva/disattiva l'avviso sandbox." #: data/com.usebottles.bottles.metainfo.xml.in:11 msgid "Run Windows software on Linux with Bottles!" @@ -3088,71 +3083,69 @@ msgid "... and much more that you can find by installing Bottles!" msgstr "... e molto altro che puoi trovare installando Bottles!" #: data/com.usebottles.bottles.metainfo.xml.in:84 -#, fuzzy msgid "Update metadata information" -msgstr "Fix informazioni appdata" +msgstr "Aggiorna le informazioni sui metadati" #: data/com.usebottles.bottles.metainfo.xml.in:89 msgid "Add more update information and correct release notes version" msgstr "" +"Aggiunte ulteriori informazioni sull'aggiornamento e corretto la versione " +"delle note di rilascio" #: data/com.usebottles.bottles.metainfo.xml.in:94 -#, fuzzy msgid "Fixed \"Add to Steam\" button" -msgstr "Aggiungi a Steam" +msgstr "Risolto il problema con il pulsante \"Aggiungi a Steam\"" #: data/com.usebottles.bottles.metainfo.xml.in:95 msgid "Fixed BottleConfig being not serializable" -msgstr "" +msgstr "Risolto il problema con BottleConfig che non era serializzabile" #: data/com.usebottles.bottles.metainfo.xml.in:96 msgid "Fixed Patool double extraction failing" -msgstr "" +msgstr "Risolto il problema con la doppia estrazione di Patool" #: data/com.usebottles.bottles.metainfo.xml.in:101 -#, fuzzy msgid "Correct version" -msgstr "Versione componente" +msgstr "Versione corrente" #: data/com.usebottles.bottles.metainfo.xml.in:106 -#, fuzzy msgid "Fix crash when creating a bottle" -msgstr "Si è verificato un errore nella creazione della bottiglia." +msgstr "Risolto il crash durante la creazione di una bottiglia" #: data/com.usebottles.bottles.metainfo.xml.in:111 msgid "Major change: Redesign New Bottle interface" msgstr "" +"Cambiamento importante: riprogettazione dell'interfaccia della nuova " +"bottiglia" #: data/com.usebottles.bottles.metainfo.xml.in:112 msgid "Quality of life improvements:" -msgstr "" +msgstr "Miglioramenti della qualità di vita:" #: data/com.usebottles.bottles.metainfo.xml.in:114 msgid "Replace emote-love icon with library in library page" msgstr "" #: data/com.usebottles.bottles.metainfo.xml.in:115 -#, fuzzy msgid "Add toast for \"Run Executable\"" -msgstr "Aggiungi un percorso personalizzato all'eseguibile" +msgstr "Aggiungi toast per \"Esegui eseguibile\"" #: data/com.usebottles.bottles.metainfo.xml.in:117 -#, fuzzy msgid "Bug fixes:" -msgstr "Correzioni" +msgstr "Correzioni di bug:" #: data/com.usebottles.bottles.metainfo.xml.in:119 msgid "Adding shortcut to Steam resulted an error" msgstr "" #: data/com.usebottles.bottles.metainfo.xml.in:120 -#, fuzzy msgid "Importing backups resulted an error" -msgstr "Importa da backup o altro gestore" +msgstr "L'importazione dei backup ha prodotto un errore" #: data/com.usebottles.bottles.metainfo.xml.in:121 msgid "Steam Runtime automatically enabled when using wine-ge-custom" msgstr "" +"Steam Runtime abilitato automaticamente quando si utilizza wine-ge-custom" #: data/com.usebottles.bottles.metainfo.xml.in:122 msgid "" @@ -3162,16 +3155,17 @@ msgstr "" #: data/com.usebottles.bottles.metainfo.xml.in:123 msgid "Fix various issues related to text encoding" -msgstr "" +msgstr "Risolto vari problemi relativi alla codifica del testo" #: data/com.usebottles.bottles.metainfo.xml.in:130 msgid "Fix error when downloading if Bottles isn't run from terminal" msgstr "" +"Corretto l'errore durante il download se Bottles non viene eseguito dal " +"terminale" #: data/com.usebottles.bottles.metainfo.xml.in:137 -#, fuzzy msgid "Correct version date" -msgstr "Data di creazione" +msgstr "Data della versione corretta" #: data/com.usebottles.bottles.metainfo.xml.in:138 msgid "Hide NVIDIA-related critical errors on non NVIDIA systems" @@ -3179,32 +3173,34 @@ msgstr "" #: data/com.usebottles.bottles.metainfo.xml.in:145 msgid "Gamescope improvements and fixes" -msgstr "" +msgstr "Miglioramenti e correzioni di Gamescope" #: data/com.usebottles.bottles.metainfo.xml.in:146 msgid "Dependency installation is faster and more stable" -msgstr "" +msgstr "L'installazione delle dipendenze è più veloce e più stabile" #: data/com.usebottles.bottles.metainfo.xml.in:147 msgid "The health check has more information for faster debugging" msgstr "" +"Il controllo dello stato contiene più informazioni per un debug più rapido" #: data/com.usebottles.bottles.metainfo.xml.in:148 msgid "NVAPI has a lot of fixes and is more stable, should now work properly" msgstr "" +"NVAPI ha molte correzioni ed è più stabile, ora dovrebbe funzionare " +"correttamente" #: data/com.usebottles.bottles.metainfo.xml.in:149 -#, fuzzy msgid "Fix crash when downloading a component" -msgstr "Preferenze di download" +msgstr "Risolto il crash durante il download di un componente" #: data/com.usebottles.bottles.metainfo.xml.in:150 msgid "Backend code improvement by avoiding spin-lock" -msgstr "" +msgstr "Miglioramento del codice backend evitando lo spin-lock" #: data/com.usebottles.bottles.metainfo.xml.in:151 msgid "More variables for installer scripting" -msgstr "" +msgstr "Più variabili per lo scripting del programma di installazione" #: data/com.usebottles.bottles.metainfo.xml.in:152 msgid "Fix onboard dialog showing \"All ready\" while it was in fact not ready" @@ -3212,35 +3208,41 @@ msgstr "" #: data/com.usebottles.bottles.metainfo.xml.in:153 msgid "Improvement to build system" -msgstr "" +msgstr "Miglioramento del sistema di costruzione" #: data/com.usebottles.bottles.metainfo.xml.in:154 msgid "Enabling VKD3D by default when creating bottles for gaming" msgstr "" +"Abilitazione di VKD3D per impostazione predefinita durante la creazione di " +"bottiglie per i giochi" #: data/com.usebottles.bottles.metainfo.xml.in:155 msgid "Fix crashes when reading Steam files with bad encodings" -msgstr "" +msgstr "Risolti i crash durante la lettura di file Steam con codifiche errate" #: data/com.usebottles.bottles.metainfo.xml.in:156 msgid "" "Fix components not updated correctly in the UI after installation/" "uninstallation" msgstr "" +"Corretti i componenti non aggiornati correttamente nell'interfaccia utente " +"dopo l'installazione/disinstallazione" #: data/com.usebottles.bottles.metainfo.xml.in:157 msgid "More FSR fixes" -msgstr "" +msgstr "Ulteriori correzioni FSR" #: data/com.usebottles.bottles.metainfo.xml.in:158 msgid "" "Fix the issue when a program closes after it was launched from \"Run " "executable\"" msgstr "" +"Risolto il problema quando un programma si chiude dopo essere stato avviato " +"da \"Avvia eseguibile\"" #: data/com.usebottles.bottles.metainfo.xml.in:159 msgid "and many, many, many more!" -msgstr "" +msgstr "e tanti, tanti, tanti altri ancora!" #~ msgid "Calculating…" #~ msgstr "Calcolo…" diff --git a/po/ko.po b/po/ko.po index 55845479ba9..d0865457516 100644 --- a/po/ko.po +++ b/po/ko.po @@ -8,8 +8,8 @@ msgstr "" "Project-Id-Version: bottles\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-03-27 13:50+0530\n" -"PO-Revision-Date: 2023-09-10 06:41+0000\n" -"Last-Translator: HNL-J \n" +"PO-Revision-Date: 2024-03-05 16:00+0000\n" +"Last-Translator: whatthesamuel \n" "Language-Team: Korean \n" "Language: ko\n" @@ -17,7 +17,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 5.0.1-dev\n" +"X-Generator: Weblate 5.5-dev\n" #: bottles/backend/managers/backup.py:48 bottles/backend/managers/backup.py:95 msgid "No path specified" @@ -31,7 +31,7 @@ msgstr "백업 {0}" #: bottles/backend/managers/backup.py:101 #, python-brace-format msgid "Importing backup: {0}" -msgstr "" +msgstr "백업 가져오는 중:{0}" #: bottles/backend/managers/manager.py:1057 msgid "Fail to install components, tried 3 times." @@ -51,7 +51,7 @@ msgstr "임시 디렉토리/파일 생성에 실패했습니다." #: bottles/backend/managers/manager.py:1162 msgid "Generating bottle configuration…" -msgstr "" +msgstr "bottle 구성 생성중…" #: bottles/backend/managers/manager.py:1185 msgid "Template found, applying…" diff --git a/po/pl.po b/po/pl.po index 36cca29e959..315df67579f 100644 --- a/po/pl.po +++ b/po/pl.po @@ -8,9 +8,8 @@ msgstr "" "Project-Id-Version: bottles\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-03-27 13:50+0530\n" -"PO-Revision-Date: 2023-01-30 20:53+0000\n" -"Last-Translator: Mikołaj Nowak \n" +"PO-Revision-Date: 2024-03-10 16:47+0000\n" +"Last-Translator: kamczatek “Kamczatek” \n" "Language-Team: Polish \n" "Language: pl\n" @@ -19,7 +18,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " "|| n%100>=20) ? 1 : 2;\n" -"X-Generator: Weblate 4.16-dev\n" +"X-Generator: Weblate 5.5-dev\n" #: bottles/backend/managers/backup.py:48 bottles/backend/managers/backup.py:95 msgid "No path specified" @@ -549,7 +548,7 @@ msgstr "" #: bottles/frontend/ui/details-dependencies.blp:25 msgid "Bottles is running in offline mode, so dependencies are not available." -msgstr "" +msgstr "Butelki działają w trybie offline, więc zależności nie są dostępne." #: bottles/frontend/ui/details-dependencies.blp:47 msgid "" @@ -709,7 +708,7 @@ msgstr "" #: bottles/frontend/ui/details-preferences.blp:105 msgid "FidelityFX Super Resolution" -msgstr "" +msgstr "Super rozdzielczość FidelityFX" #: bottles/frontend/ui/details-preferences.blp:106 msgid "Increase performance at the expense of visuals. Only works on Vulkan." @@ -718,7 +717,7 @@ msgstr "" #: bottles/frontend/ui/details-preferences.blp:108 msgid "Manage FidelityFX Super Resolution settings" -msgstr "" +msgstr "Zarządzaj ustawieniami super rozdzielczości FidelityFX" #: bottles/frontend/ui/details-preferences.blp:125 msgid "Discrete Graphics" diff --git a/po/pt.po b/po/pt.po index 8277eb59f5d..a1cc826b629 100644 --- a/po/pt.po +++ b/po/pt.po @@ -8,8 +8,8 @@ msgstr "" "Project-Id-Version: bottles\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-03-27 13:50+0530\n" -"PO-Revision-Date: 2023-03-22 00:06+0000\n" -"Last-Translator: Felipe Nogueira \n" +"PO-Revision-Date: 2024-03-10 16:47+0000\n" +"Last-Translator: Daniel Martins \n" "Language-Team: Portuguese \n" "Language: pt\n" @@ -17,11 +17,11 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" -"X-Generator: Weblate 4.16.2-dev\n" +"X-Generator: Weblate 5.5-dev\n" #: bottles/backend/managers/backup.py:48 bottles/backend/managers/backup.py:95 msgid "No path specified" -msgstr "Nenhum caminho especificado" +msgstr "Caminho não especificiado" #: bottles/backend/managers/backup.py:56 #, python-brace-format @@ -43,7 +43,7 @@ msgstr "Componentes essenciais em falta. Instalando…" #: bottles/backend/managers/manager.py:1145 msgid "Failed to create bottle directory." -msgstr "Falha ao criar diretório para bottle." +msgstr "Falha ao criar pasta do bottle." #: bottles/backend/managers/manager.py:1157 msgid "Failed to create placeholder directory/file." @@ -55,7 +55,7 @@ msgstr "A gerar configuração da Bottle…" #: bottles/backend/managers/manager.py:1185 msgid "Template found, applying…" -msgstr "Templete encontrado, a aplicar…" +msgstr "modelo encontrado, aplicando…" #. execute wineboot on the bottle path #: bottles/backend/managers/manager.py:1197 @@ -201,15 +201,15 @@ msgstr "Solicitação [Atualizar] recebida." #: bottles/frontend/main.py:294 msgid "Donate" -msgstr "" +msgstr "Doar" #: bottles/frontend/main.py:299 msgid "Third-Party Libraries and Special Thanks" -msgstr "" +msgstr "Bibliotecas de Terceiros e Agradecimentos Especiais" #: bottles/frontend/main.py:325 msgid "Sponsored and Funded by" -msgstr "" +msgstr "Patrocinado e Financiado por" # Translators: Bottles is a proper noun referring to the app #: bottles/frontend/ui/about.blp:5 @@ -223,7 +223,7 @@ msgstr "Programadores do Bottles" #: bottles/frontend/ui/about.blp:12 msgid "translator_credits" -msgstr "" +msgstr "Kazevic https://github.com/Kazevic" #: bottles/frontend/ui/component-entry.blp:4 msgid "Component version" @@ -541,11 +541,13 @@ msgstr "Pesquisar dependências…" #: bottles/frontend/ui/preferences.blp:178 #: bottles/frontend/ui/preferences.blp:235 msgid "You're offline :(" -msgstr "" +msgstr "Está offline :(" #: bottles/frontend/ui/details-dependencies.blp:25 msgid "Bottles is running in offline mode, so dependencies are not available." msgstr "" +"O Bottles executa no modo offline, logo as dependências não estão " +"disponíveis." #: bottles/frontend/ui/details-dependencies.blp:47 msgid "" @@ -590,18 +592,16 @@ msgid "Search for Programs…" msgstr "Pesquisar programas…" #: bottles/frontend/ui/details-installers.blp:15 -#, fuzzy msgid "" "Install programs curated by our community.\n" "\n" "Files on this page are provided by third parties under a proprietary " "license. By installing them, you agree with their respective licensing terms." msgstr "" -"Instale programas com curadoria de nossa comunidade, sem ter que proceder " -"manualmente.\n" +"Instale programas selecionados pela nossa comunidade.\n" "\n" "Os ficheiros desta página são fornecidos por terceiros sob uma licença " -"proprietária. Ao instalá-los, concorda com os seus respectivos termos de " +"proprietária. Ao instalá-los, concorda com os respectivos termos de " "licenciamento." #: bottles/frontend/ui/details-installers.blp:29 @@ -708,7 +708,7 @@ msgstr "" #: bottles/frontend/ui/details-preferences.blp:105 msgid "FidelityFX Super Resolution" -msgstr "" +msgstr "FidelityFX Super Resolution" #: bottles/frontend/ui/details-preferences.blp:106 msgid "Increase performance at the expense of visuals. Only works on Vulkan." @@ -716,7 +716,7 @@ msgstr "Aumente o desempenho em detrimento do visual. Só funciona no Vulkan." #: bottles/frontend/ui/details-preferences.blp:108 msgid "Manage FidelityFX Super Resolution settings" -msgstr "" +msgstr "Gerir as configurações do FidelityFX Super Resolution" #: bottles/frontend/ui/details-preferences.blp:125 msgid "Discrete Graphics" @@ -799,9 +799,8 @@ msgstr "" "carga de CPU/GPU e muito mais em OpenGL e Vulkan usando o MangoHud." #: bottles/frontend/ui/details-preferences.blp:211 -#, fuzzy msgid "Feral GameMode" -msgstr "GameMode" +msgstr "Feral GameMode" #: bottles/frontend/ui/details-preferences.blp:212 msgid "" @@ -1252,9 +1251,8 @@ msgid "Frame Rate Limit When Unfocused" msgstr "Limite da taxa de quadros quando não está em foco" #: bottles/frontend/ui/dialog-gamescope.blp:153 -#, fuzzy msgid "Integer Scaling" -msgstr "Usar escala numérica" +msgstr "Escalonamento Íntegro" #: bottles/frontend/ui/dialog-gamescope.blp:162 msgid "Window Type" @@ -1582,7 +1580,7 @@ msgstr "Mostrar informação" #. Translators: Luma is not translatable #: bottles/frontend/ui/dialog-vkbasalt.blp:99 msgid "Denoised Luma Sharpening" -msgstr "" +msgstr "Nitidez de Luma sem ruído" #: bottles/frontend/ui/dialog-vkbasalt.blp:130 msgid "Denoise" @@ -1598,15 +1596,15 @@ msgstr "Qualidade Subpixel" #: bottles/frontend/ui/dialog-vkbasalt.blp:191 msgid "Quality Edge Threshold" -msgstr "" +msgstr "Limiar de Qualidade de Borda" #: bottles/frontend/ui/dialog-vkbasalt.blp:219 msgid "Quality Edge Threshold Minimum" -msgstr "" +msgstr "Limiar Mínimo de Qualidade de Borda" #: bottles/frontend/ui/dialog-vkbasalt.blp:249 msgid "Subpixel Morphological Anti-Aliasing" -msgstr "" +msgstr "Antisserrilhamento morfológico de subpixel" #: bottles/frontend/ui/dialog-vkbasalt.blp:252 msgid "Edge Detection" @@ -1614,7 +1612,7 @@ msgstr "Detecção de bordas" #: bottles/frontend/ui/dialog-vkbasalt.blp:267 msgid "Luma" -msgstr "" +msgstr "Luma" #: bottles/frontend/ui/dialog-vkbasalt.blp:273 msgid "Color" @@ -1630,11 +1628,11 @@ msgstr "Etapas de pesquisa máxima" #: bottles/frontend/ui/dialog-vkbasalt.blp:339 msgid "Max Search Steps Diagonal" -msgstr "" +msgstr "Etapas máximas de busca diagonal" #: bottles/frontend/ui/dialog-vkbasalt.blp:366 msgid "Max Corner Rounding" -msgstr "" +msgstr "Arredondamento Máximo de Cantos" #: bottles/frontend/ui/dialog-vkbasalt.blp:411 msgid "" @@ -1694,28 +1692,38 @@ msgid "" "Luma detects edges from a monochrome perspective, whereas Color detects " "edges based on colors. Luma is more performant than Color." msgstr "" +"Luma deteta bordas de uma perspectiva monocromática, enquanto Color deteta " +"bordas com base em cores. Luma é mais performante que Color." #: bottles/frontend/ui/dialog-vkbasalt.blp:530 msgid "" "SMAA threshold specifies the sensitivity of edge detection. Lower values " "detect more edges at the expense of performance." msgstr "" +"O limiar do SMAA especifica a sensibilidade da detecção de bordas. Valores " +"mais baixos detetam mais bordas à custa do desempenho." #: bottles/frontend/ui/dialog-vkbasalt.blp:547 msgid "" "SMAA max search steps specifies how many horizontal and vertical search " "steps are performed when searching for edges." msgstr "" +"As etapas máximas de busca do SMAA especificam quantas etapas de busca " +"horizontal e vertical são feitas ao buscar bordas." #: bottles/frontend/ui/dialog-vkbasalt.blp:564 msgid "" "SMAA max diagonal search steps specifies how many diagonal search steps are " "performed when searching for edges." msgstr "" +"As etapas máximas de busca diagonal do SMAA especificam quantas etapas de " +"busca diagonais são executadas ao buscar bordas." #: bottles/frontend/ui/dialog-vkbasalt.blp:581 msgid "SMAA corner rounding specifies the strength of rounding edge corners." msgstr "" +"O arredondamento dos cantos do SMAA especifica a intensidade do " +"arredondamento dos cantos." #: bottles/frontend/ui/dll-override-entry.blp:8 msgid "Builtin (Wine)" @@ -1765,9 +1773,8 @@ msgid "Manager" msgstr "Gestor" #: bottles/frontend/ui/importer-entry.blp:38 -#, fuzzy msgid "This Wine prefix was already imported in Bottles." -msgstr "Esta configuração (wineprefix) já foi importada para o Bottles." +msgstr "Este prefixo Wine já foi importado no Bottles." #: bottles/frontend/ui/importer.blp:22 msgid "Import a Bottle backup" @@ -1864,7 +1871,6 @@ msgid "This bottle looks damaged." msgstr "Este Bottle parece danificado." #: bottles/frontend/ui/list-entry.blp:55 -#, fuzzy msgid "Execute in this Bottle" msgstr "Executar nesta garrafa" @@ -1895,9 +1901,8 @@ msgid "Bottles" msgstr "Bottles" #: bottles/frontend/ui/list.blp:49 -#, fuzzy msgid "Create New Bottle…" -msgstr "Criar nova garrafa" +msgstr "Criar Nova Garrafa…" #: bottles/frontend/ui/list.blp:63 msgid "No Results Found" @@ -1920,9 +1925,8 @@ msgid "Browse" msgstr "Navegar" #: bottles/frontend/ui/new.blp:32 -#, fuzzy msgid "C_reate" -msgstr "Criar" +msgstr "C_riar" #: bottles/frontend/ui/new.blp:53 #, fuzzy @@ -1930,28 +1934,24 @@ msgid "Bottle Name" msgstr "Nome do Bottle" #: bottles/frontend/ui/new.blp:75 -#, fuzzy msgid "_Application" -msgstr "Aplicação" +msgstr "_Aplicativo" #: bottles/frontend/ui/new.blp:88 -#, fuzzy msgid "_Gaming" -msgstr "Jogos" +msgstr "_Jogos" #: bottles/frontend/ui/new.blp:101 -#, fuzzy msgid "C_ustom" -msgstr "Personalizado" +msgstr "C_ustomizado" #: bottles/frontend/ui/new.blp:114 msgid "Custom" msgstr "Personalizado" #: bottles/frontend/ui/new.blp:118 -#, fuzzy msgid "Share User Directory" -msgstr "Escolher um diretório" +msgstr "Partilhar Diretório de Utilizador" #: bottles/frontend/ui/new.blp:119 msgid "" @@ -1959,39 +1959,37 @@ msgid "" "sharing personal information to Windows software. This option cannot be " "changed after the bottle has been created." msgstr "" +"Isto torna o diretório do utilizador detectável na garrafa, com o risco de " +"partilhar informações pessoais com programas de Windows. Esta opção não pode " +"ser mudada após criar a garrafa." #: bottles/frontend/ui/new.blp:136 msgid "Architecture" msgstr "Arquitetura" #: bottles/frontend/ui/new.blp:137 -#, fuzzy msgid "32-bit should only be used if strictly necessary." -msgstr "Recomendamos usar 32 bits apenas se estritamente necessário." +msgstr "32 bits só deve ser usado se estritamente necessário." #: bottles/frontend/ui/new.blp:146 -#, fuzzy msgid "Import a custom configuration." -msgstr "Exportar configuração…" +msgstr "Importe uma configuração customizada." #: bottles/frontend/ui/new.blp:176 -#, fuzzy msgid "Bottle Directory" -msgstr "Escolher um diretório" +msgstr "Diretório da Garrafa" #: bottles/frontend/ui/new.blp:177 msgid "Directory that will contain the data of this bottle." -msgstr "" +msgstr "Diretório que contém os dados desta garrafa." #: bottles/frontend/ui/new.blp:249 -#, fuzzy msgid "_Close" -msgstr "Fechar" +msgstr "_Fechar" #: bottles/frontend/ui/new.blp:281 -#, fuzzy msgid "This name is unavailable, please try another." -msgstr "Esse recurso não está disponível no seu sistema." +msgstr "Este nome está indisponível, tente outro." #: bottles/frontend/ui/onboard.blp:34 msgid "Previous" @@ -2028,13 +2026,12 @@ msgid "We need a few more minutes to set everything up…" msgstr "Precisamos de mais alguns minutos para configurar tudo…" #: bottles/frontend/ui/onboard.blp:105 -#, fuzzy msgid "All Ready!" msgstr "Tudo pronto!" #: bottles/frontend/ui/onboard.blp:114 msgid "Please Finish the setup first" -msgstr "" +msgstr "Termine a configuração primeiro" # Translators: Bottles is a proper noun referring to the app #: bottles/frontend/ui/onboard.blp:120 @@ -2063,9 +2060,8 @@ msgid "Dark Mode" msgstr "Tema escuro" #: bottles/frontend/ui/preferences.blp:18 -#, fuzzy msgid "Whether Bottles should use the dark color scheme." -msgstr "Se os seus Bottles devem usar uma variante de tema escuro." +msgstr "Se o Bottles deve usar o esquema de cor escuro." #: bottles/frontend/ui/preferences.blp:28 msgid "Show Update Date" @@ -2130,25 +2126,23 @@ msgstr "Requer a Epic Games Store instalada na garrafa." #: bottles/frontend/ui/preferences.blp:117 msgid "List Ubisoft Games in Programs List" -msgstr "" +msgstr "Listar jogos da Ubisoft na lista de programas" #: bottles/frontend/ui/preferences.blp:118 -#, fuzzy msgid "Requires Ubisoft Connect installed in the bottle." -msgstr "O controlo da versão está ativo neste Bottle." +msgstr "Requer Ubisoft Connect instalado na garrafa." #: bottles/frontend/ui/preferences.blp:128 msgid "Advanced" msgstr "Avançado" #: bottles/frontend/ui/preferences.blp:131 -#, fuzzy msgid "Bottles Directory" -msgstr "Escolher um diretório" +msgstr "Diretório do Bottles" #: bottles/frontend/ui/preferences.blp:132 msgid "Directory that contains the data of your Bottles." -msgstr "" +msgstr "Diretório que contém os dados das suas garrafas." #: bottles/frontend/ui/preferences.blp:167 msgid "Runners" @@ -2157,6 +2151,7 @@ msgstr "Runners" #: bottles/frontend/ui/preferences.blp:181 msgid "Bottles is running in offline mode, so runners are not available." msgstr "" +"O Bottles executa no modo offline, logo os executores não estão disponíveis." #: bottles/frontend/ui/preferences.blp:208 msgid "Pre-Release" @@ -2172,7 +2167,7 @@ msgstr "Componentes DLL" #: bottles/frontend/ui/preferences.blp:238 msgid "Bottles is running in offline mode, so DLLs are not available." -msgstr "" +msgstr "O Bottles executa no modo offline, logo as DLLs não estão disponíveis." #: bottles/frontend/ui/preferences.blp:270 msgid "DXVK-NVAPI" @@ -2200,6 +2195,8 @@ msgid "" "These features are under heavy development and may be unstable, expect bugs " "and breakage." msgstr "" +"Estes recursos estão em desenvolvimento pesado e podem ser instáveis, espere " +"bugs e quebras." #: bottles/frontend/ui/preferences.blp:303 msgid "Sandbox per bottle" @@ -2214,9 +2211,8 @@ msgid "Launch with Terminal" msgstr "Iniciar com terminal" #: bottles/frontend/ui/program-entry.blp:25 -#, fuzzy msgid "Browse Path" -msgstr "Navegar no local" +msgstr "Navegar no caminho" #: bottles/frontend/ui/program-entry.blp:39 msgid "Change Launch Options…" @@ -2256,19 +2252,16 @@ msgstr "Nome do programa" #. Translators: id as identification #: bottles/frontend/ui/state-entry.blp:8 -#, fuzzy msgid "State id" -msgstr "Nenhum estado encontrado" +msgstr "Identificação do estado" #: bottles/frontend/ui/state-entry.blp:9 -#, fuzzy msgid "State comment" -msgstr "Um comentário bem curto" +msgstr "Comentário do estado" #: bottles/frontend/ui/state-entry.blp:16 -#, fuzzy msgid "Restore this Snapshot" -msgstr "Execute neste bottle" +msgstr "Voltar a este ponto de restauração" #: bottles/frontend/ui/task-entry.blp:19 msgid "Delete message" @@ -2276,7 +2269,7 @@ msgstr "Apagar mensagem" #: bottles/frontend/ui/window.blp:40 msgid "Main Menu" -msgstr "" +msgstr "Menu principal" #: bottles/frontend/ui/window.blp:54 msgid "" @@ -2303,7 +2296,7 @@ msgstr "Sobre o Bottles" #: bottles/frontend/views/bottle_details.py:191 #, python-brace-format msgid "File \"{0}\" is not a .exe or .msi file" -msgstr "" +msgstr "O ficheiro \"{0}\" não é um ficheiro .exe ou .msi" #: bottles/frontend/views/bottle_details.py:207 #, python-format @@ -2318,29 +2311,27 @@ msgstr "\"{0}\" adicionado" #: bottles/frontend/views/bottle_details.py:270 #: bottles/frontend/views/bottle_details.py:398 #: bottles/frontend/views/list.py:128 -#, fuzzy msgid "Select Executable" -msgstr "Selecionar garrafa" +msgstr "Selecionar Executável" #: bottles/frontend/views/bottle_details.py:273 msgid "Add" msgstr "Adicionar" #: bottles/frontend/views/bottle_details.py:346 -#, fuzzy msgid "Hide Hidden Programs" -msgstr "Ocultar programa" +msgstr "Ocultar Programas Ocultos" #: bottles/frontend/views/bottle_details.py:383 #: bottles/frontend/widgets/library.py:156 #: bottles/frontend/widgets/program.py:184 -#, fuzzy, python-brace-format +#, python-brace-format msgid "Launching \"{0}\"…" -msgstr "Instalando {0}…" +msgstr "A iniciar \"{0}\"…" #: bottles/frontend/views/bottle_details.py:413 msgid "Be Aware of Sandbox" -msgstr "" +msgstr "Esteja Atento ao Sandbox" #: bottles/frontend/views/bottle_details.py:414 msgid "" @@ -2348,12 +2339,16 @@ msgid "" "to keep you safe. If the program won't run, consider moving inside the " "bottle (3 dots icon on the top), then launch from there." msgstr "" +"O Bottles executa num sandbox, um ambiente de permissão restrita necessário " +"para mantê-lo seguro. Considere movê-lo para dentro da garrafa (ícone de 3 " +"pontos na parte superior) e iniciar a partir daí se o programa não for " +"executado." #: bottles/frontend/views/bottle_details.py:416 #: bottles/frontend/views/bottle_details.py:525 #: bottles/frontend/windows/main_window.py:223 msgid "_Dismiss" -msgstr "" +msgstr "_Dispensar" #: bottles/frontend/views/bottle_details.py:429 msgid "Select the location where to save the backup config" @@ -2368,9 +2363,8 @@ msgid "Select the location where to save the backup archive" msgstr "Selecione o local onde gravar o ficheiro da cópia de segurança" #: bottles/frontend/views/bottle_details.py:435 -#, fuzzy msgid "Backup" -msgstr "Cópia de segurança {0}" +msgstr "Backup" #: bottles/frontend/views/bottle_details.py:440 #, python-brace-format @@ -2383,80 +2377,78 @@ msgid "Backup failed for \"{0}\"" msgstr "Backup falhou para \"{0}\"" #: bottles/frontend/views/bottle_details.py:501 -#, fuzzy msgid "Are you sure you want to permanently delete \"{}\"?" -msgstr "Tem certeza de que quer eliminar este Bottle e todos os ficheiros?" +msgstr "Tem certeza de que quer apagar \"{}\" para sempre?" #: bottles/frontend/views/bottle_details.py:502 msgid "" "This will permanently delete all programs and settings associated with it." msgstr "" +"Isto apagará para sempre todos programas e a configuração lhes associadas." #: bottles/frontend/views/bottle_details.py:505 #: bottles/frontend/views/bottle_preferences.py:750 msgid "_Delete" -msgstr "" +msgstr "_Excluir" #: bottles/frontend/views/bottle_details.py:521 -#, fuzzy msgid "Missing Runner" -msgstr "Wine Runners" +msgstr "Executor ausente" #: bottles/frontend/views/bottle_details.py:522 msgid "" "The runner requested by this bottle is missing. Install it through the " "Bottles preferences or choose a new one to run applications." msgstr "" +"O executor solicitado por esta garrafa falta. Instale-o através das " +"preferências do Bottles ou escolha uma nova garrafa para executar aplicações." #: bottles/frontend/views/bottle_details.py:597 -#, fuzzy msgid "Are you sure you want to force stop all processes?" -msgstr "Tem certeza de que quer eliminar este Bottle e todos os ficheiros?" +msgstr "Tem certeza de que quer forçar a paragem de todos os processos?" #: bottles/frontend/views/bottle_details.py:598 msgid "This can cause data loss, corruption, and programs to malfunction." msgstr "" +"Isto pode causar perda de dados, corrupção e mau funcionamento de programas." #: bottles/frontend/views/bottle_details.py:601 msgid "Force _Stop" -msgstr "" +msgstr "Forçar _parada" #: bottles/frontend/views/bottle_preferences.py:195 -#, fuzzy msgid "This feature is unavailable on your system." -msgstr "Esse recurso não está disponível no seu sistema." +msgstr "Este recurso está indisponível no seu sistema." #: bottles/frontend/views/bottle_preferences.py:196 msgid "{} To add this feature, please run flatpak install" -msgstr "" +msgstr "{} Para adicionar este recurso, execute flatpak install" #: bottles/frontend/views/bottle_preferences.py:246 -#, fuzzy msgid "This bottle name is already in use." -msgstr "Caracteres especiais não são permitidos!" +msgstr "Este nome de garrafa já está em uso." #: bottles/frontend/views/bottle_preferences.py:301 #: bottles/frontend/windows/launchoptions.py:241 -#, fuzzy msgid "Select Working Directory" -msgstr "Diretório de trabalho" +msgstr "Selecione Diretório de Trabalho" #: bottles/frontend/views/bottle_preferences.py:423 msgid "Directory that contains the data of \"{}\"." -msgstr "" +msgstr "Diretório que contém os dados de \"{}\"." #: bottles/frontend/views/bottle_preferences.py:746 -#, fuzzy msgid "Are you sure you want to delete all snapshots?" -msgstr "Tem certeza de que quer eliminar este Bottle e todos os ficheiros?" +msgstr "Tem certeza de que deseja apagar todos os pontos de restauração?" #: bottles/frontend/views/bottle_preferences.py:747 msgid "This will delete all snapshots but keep your files." msgstr "" +"Isto apagará todos os pontos de restauração, mas manterá os seus ficheiros." #: bottles/frontend/views/bottle_versioning.py:90 msgid "Please migrate to the new Versioning system to create new states." -msgstr "" +msgstr "Migre para o novo sistema de controle de versão para criar estados." #: bottles/frontend/views/details.py:153 msgid "Installers" @@ -2484,9 +2476,8 @@ msgid "Importing backup…" msgstr "Importando backup…" #: bottles/frontend/views/importer.py:119 -#, fuzzy msgid "Select a Backup Archive" -msgstr "Escolha um ficheiro de cópia de segurança" +msgstr "Selecione um Arquivo de Backup" #: bottles/frontend/views/importer.py:122 #: bottles/frontend/views/importer.py:161 @@ -2494,9 +2485,8 @@ msgid "Import" msgstr "Importar" #: bottles/frontend/views/importer.py:158 bottles/frontend/views/new.py:136 -#, fuzzy msgid "Select a Configuration File" -msgstr "Escolha um ficheiro de configuração" +msgstr "Selecione um Ficheiro de Configuração" #: bottles/frontend/views/list.py:60 bottles/frontend/views/list.py:66 msgid "N/A" @@ -2506,12 +2496,12 @@ msgstr "N/A" #: bottles/frontend/views/list.py:91 #, python-brace-format msgid "Run executable in \"{self.config.Name}\"" -msgstr "" +msgstr "Executar executável em \"{self.config.Name}\"" #: bottles/frontend/views/list.py:118 -#, fuzzy, python-brace-format +#, python-brace-format msgid "Launching \"{0}\" in \"{1}\"…" -msgstr "Instalando {0}…" +msgstr "A iniciar \"{0}\" em \"{1}\"…" #: bottles/frontend/views/list.py:235 msgid "Your Bottles" @@ -2520,37 +2510,33 @@ msgstr "Os seus Bottles" #: bottles/frontend/views/loading.py:41 #, python-brace-format msgid "Downloading ~{0} of packages…" -msgstr "" +msgstr "A descarregar ~{0} de pacotes…" #: bottles/frontend/views/loading.py:42 #, python-brace-format msgid "Fetched {0} of {1} packages" -msgstr "" +msgstr "Obteve {0} de {1} pacotes" #: bottles/frontend/views/new.py:157 -#, fuzzy msgid "Select Bottle Directory" -msgstr "Escolher um diretório" +msgstr "Selecione o Diretório da Garrafa" #: bottles/frontend/views/new.py:176 -#, fuzzy msgid "Creating Bottle…" -msgstr "A apagar um bottle …" +msgstr "A criar Garrafa…" #: bottles/frontend/views/new.py:221 -#, fuzzy msgid "Unable to Create Bottle" -msgstr "Criar nova garrafa" +msgstr "Incapaz de Criar Garrafa" #: bottles/frontend/views/new.py:225 msgid "Bottle failed to create with one or more errors." -msgstr "" +msgstr "Falha na criação da garrafa com um ou mais erros." #. Show success #: bottles/frontend/views/new.py:232 -#, fuzzy msgid "Bottle Created" -msgstr "Bottle criada" +msgstr "Garrafa criada" #: bottles/frontend/views/new.py:233 #, python-brace-format @@ -2562,9 +2548,8 @@ msgid "Steam was not found or Bottles does not have enough permissions." msgstr "Steam não foi encontrada ou Bottles não tem permissões suficientes." #: bottles/frontend/views/preferences.py:176 -#, fuzzy msgid "Select Bottles Path" -msgstr "Selecionar garrafa" +msgstr "Selecione o Caminho das Garrafas" #: bottles/frontend/views/preferences.py:198 msgid "Relaunch Bottles?" @@ -2578,10 +2563,15 @@ msgid "" "Bottles, as not doing so can cause data loss, corruption and programs to " "malfunction." msgstr "" +"Bottles terá de ser reiniciado para usar este diretório.\n" +"\n" +"Certifique-se de fechar todo programa iniciado a partir do Bottles antes de " +"reiniciar o Bottles, pois não fazer isso pode causar perda de dados, " +"corrupção e mau funcionamento dos programas." #: bottles/frontend/views/preferences.py:202 msgid "_Relaunch" -msgstr "" +msgstr "_Reiniciar" #: bottles/frontend/views/preferences.py:243 msgid "Based on Valve's Wine, includes staging and Proton patches." @@ -2620,9 +2610,9 @@ msgid "Manifest for {0}" msgstr "Manifest para {0}" #: bottles/frontend/widgets/dependency.py:172 -#, fuzzy, python-brace-format +#, python-brace-format msgid "\"{0}\" uninstalled" -msgstr "\"{0}\" instalado" +msgstr "\"{0}\" desinstalado" #: bottles/frontend/widgets/dependency.py:174 #, python-brace-format @@ -2645,20 +2635,25 @@ msgid "" "the best possible experience, but expect glitches, instability and lack of " "working features." msgstr "" +"Esta aplicação pode funcionar mal. O instalador foi configurado para " +"fornecer a melhor experiência possível, mas espere falhas, instabilidade e " +"falta de recursos funcionais." #: bottles/frontend/widgets/installer.py:50 msgid "" "This program works with noticeable glitches, but these glitches do not " "affect the application's functionality." msgstr "" +"Este programa funciona com falhas notáveis, mas estas falhas não afetam a " +"funcionalidade da aplicação." #: bottles/frontend/widgets/installer.py:51 msgid "This program works with minor glitches." -msgstr "" +msgstr "Este programa funciona com pequenas falhas." #: bottles/frontend/widgets/installer.py:52 msgid "This program works perfectly." -msgstr "" +msgstr "Este programa funciona perfeitamente." #: bottles/frontend/widgets/installer.py:90 #, python-brace-format @@ -2667,14 +2662,14 @@ msgstr "Análise de {0}" #: bottles/frontend/widgets/library.py:169 #: bottles/frontend/widgets/program.py:194 -#, fuzzy, python-brace-format +#, python-brace-format msgid "Stopping \"{0}\"…" -msgstr "Instalando {0}…" +msgstr "A parar \"{0}\"…" #: bottles/frontend/widgets/program.py:190 -#, fuzzy, python-brace-format +#, python-brace-format msgid "Launching \"{0}\" with Steam…" -msgstr "Iniciar com Steam" +msgstr "A iniciar \"{0}\" com Steam…" #: bottles/frontend/widgets/program.py:214 #, python-brace-format @@ -2697,19 +2692,19 @@ msgid "\"{0}\" renamed to \"{1}\"" msgstr "\"{0}\" renomeado para \"{1}\"" #: bottles/frontend/widgets/program.py:297 -#, fuzzy, python-brace-format +#, python-brace-format msgid "Desktop Entry created for \"{0}\"" -msgstr "Entrada da área de trabalho criada para '{0}'" +msgstr "Entrada da área de trabalho criada para \"{0}\"" #: bottles/frontend/widgets/program.py:313 -#, fuzzy, python-brace-format +#, python-brace-format msgid "\"{0}\" added to your library" -msgstr "'{0}' adicionado a sua biblioteca" +msgstr "\"{0}\" adicionado à sua biblioteca" #: bottles/frontend/widgets/program.py:331 -#, fuzzy, python-brace-format +#, python-brace-format msgid "\"{0}\" added to your Steam library" -msgstr "'{0}' adicionado à sua biblioteca Steam" +msgstr "\"{0}\" adicionado à sua biblioteca Steam" #: bottles/frontend/windows/crash.py:33 msgid "Show report" @@ -2737,9 +2732,8 @@ msgid "No overrides found." msgstr "Nenhuma substituição encontrada." #: bottles/frontend/windows/drives.py:71 -#, fuzzy msgid "Select Drive Path" -msgstr "Criar bottle" +msgstr "Selecione o Caminho da Unidade" #: bottles/frontend/windows/envvars.py:131 msgid "No environment variables defined." @@ -2760,9 +2754,8 @@ msgid "Copy to clipboard" msgstr "Copiar para a área de transferência" #: bottles/frontend/windows/installer.py:62 -#, fuzzy msgid "Select Resource File" -msgstr "Selecionar ficheiro de recurso" +msgstr "Selecionar Ficheiro de Recurso" #: bottles/frontend/windows/installer.py:109 msgid "Installing Windows dependencies…" @@ -2808,9 +2801,8 @@ msgid "This setting is different from the bottle's default." msgstr "Esta configuração é diferente do padrão da garrafa." #: bottles/frontend/windows/launchoptions.py:215 -#, fuzzy msgid "Select Script" -msgstr "Criar bottle" +msgstr "Selecione um Script" #: bottles/frontend/windows/main_window.py:220 msgid "Custom Bottles Path not Found" @@ -2820,16 +2812,16 @@ msgstr "Caminho de Garrafas Personalizado não encontrado" msgid "" "Falling back to default path. No bottles from the given path will be listed." msgstr "" +"A voltar ao caminho padrão. Nenhuma garrafa do caminho dado será listada." #: data/com.usebottles.bottles.desktop.in.in:3 msgid "@APP_NAME@" -msgstr "" +msgstr "@APP_NAME@" #: data/com.usebottles.bottles.desktop.in.in:4 #: data/com.usebottles.bottles.metainfo.xml.in:8 -#, fuzzy msgid "Run Windows Software" -msgstr "Abrir aplicação Windows" +msgstr "Execute programas do Windows" #: data/com.usebottles.bottles.desktop.in.in:13 msgid "wine;windows;" @@ -2877,12 +2869,11 @@ msgstr "Alterne a listagem da Epic Games." #: data/com.usebottles.bottles.gschema.xml:31 msgid "Ubisoft Connect listing" -msgstr "" +msgstr "Listagem do Ubisoft Connect" #: data/com.usebottles.bottles.gschema.xml:32 -#, fuzzy msgid "Toggle ubisoft connect listing." -msgstr "Accionar a data de atualização na lista" +msgstr "Alternar a listagem do Ubisoft Connect." #: data/com.usebottles.bottles.gschema.xml:36 msgid "Window width" @@ -2965,12 +2956,11 @@ msgstr "" #: data/com.usebottles.bottles.gschema.xml:86 msgid "Show sandbox warning" -msgstr "" +msgstr "Exibir aviso de sandbox" #: data/com.usebottles.bottles.gschema.xml:87 -#, fuzzy msgid "Toggle sandbox warning." -msgstr "Accionar a data de atualização na lista" +msgstr "Alternar aviso de sandbox." #: data/com.usebottles.bottles.metainfo.xml.in:11 msgid "Run Windows software on Linux with Bottles!" @@ -3083,68 +3073,67 @@ msgstr ".. e muito mais que pode encontrar ao instalar o Bottles!" #: data/com.usebottles.bottles.metainfo.xml.in:84 msgid "Update metadata information" -msgstr "" +msgstr "Atualizar informações de metadados" #: data/com.usebottles.bottles.metainfo.xml.in:89 msgid "Add more update information and correct release notes version" msgstr "" +"Adicionadas mais informações de atualização e corrigida a versão das notas " +"de versão" #: data/com.usebottles.bottles.metainfo.xml.in:94 -#, fuzzy msgid "Fixed \"Add to Steam\" button" -msgstr "Adicionar à Steam" +msgstr "Corrigido o botão “Adicionar à Steam”" #: data/com.usebottles.bottles.metainfo.xml.in:95 msgid "Fixed BottleConfig being not serializable" -msgstr "" +msgstr "Corrigido o BottleConfig sendo não serializável" #: data/com.usebottles.bottles.metainfo.xml.in:96 msgid "Fixed Patool double extraction failing" -msgstr "" +msgstr "Corrigida uma falha na extração dupla do Patool" #: data/com.usebottles.bottles.metainfo.xml.in:101 -#, fuzzy msgid "Correct version" -msgstr "Versão de componente" +msgstr "Versão correta" #: data/com.usebottles.bottles.metainfo.xml.in:106 -#, fuzzy msgid "Fix crash when creating a bottle" -msgstr "Houve um erro ao criar a garrafa." +msgstr "Corrigido um erro ao criar uma garrafa" #: data/com.usebottles.bottles.metainfo.xml.in:111 msgid "Major change: Redesign New Bottle interface" -msgstr "" +msgstr "Grande mudança: Redesenho da Interface de Nova Garrafa" #: data/com.usebottles.bottles.metainfo.xml.in:112 msgid "Quality of life improvements:" -msgstr "" +msgstr "Melhorias na qualidade de vida:" #: data/com.usebottles.bottles.metainfo.xml.in:114 msgid "Replace emote-love icon with library in library page" msgstr "" #: data/com.usebottles.bottles.metainfo.xml.in:115 -#, fuzzy msgid "Add toast for \"Run Executable\"" -msgstr "Correr o executável" +msgstr "Adicionar notificação do sistema para “Executar Executável”" #: data/com.usebottles.bottles.metainfo.xml.in:117 msgid "Bug fixes:" -msgstr "" +msgstr "Correções de bugs:" #: data/com.usebottles.bottles.metainfo.xml.in:119 msgid "Adding shortcut to Steam resulted an error" msgstr "" #: data/com.usebottles.bottles.metainfo.xml.in:120 -#, fuzzy msgid "Importing backups resulted an error" -msgstr "A importar a cópia de segurança: {0}" +msgstr "Importar backups causou um erro" #: data/com.usebottles.bottles.metainfo.xml.in:121 msgid "Steam Runtime automatically enabled when using wine-ge-custom" msgstr "" +"Ambiente de Execução da Steam automaticamente ativado ao usar o wine-ge-" +"custom" #: data/com.usebottles.bottles.metainfo.xml.in:122 msgid "" @@ -3154,16 +3143,16 @@ msgstr "" #: data/com.usebottles.bottles.metainfo.xml.in:123 msgid "Fix various issues related to text encoding" -msgstr "" +msgstr "Corrigidos vários problemas relacionados à codificação de texto" #: data/com.usebottles.bottles.metainfo.xml.in:130 msgid "Fix error when downloading if Bottles isn't run from terminal" msgstr "" +"Corrigido o erro ao descarregar se o Bottles não for executado no terminal" #: data/com.usebottles.bottles.metainfo.xml.in:137 -#, fuzzy msgid "Correct version date" -msgstr "Data de criação" +msgstr "Data correta da versão" #: data/com.usebottles.bottles.metainfo.xml.in:138 msgid "Hide NVIDIA-related critical errors on non NVIDIA systems" @@ -3171,31 +3160,35 @@ msgstr "" #: data/com.usebottles.bottles.metainfo.xml.in:145 msgid "Gamescope improvements and fixes" -msgstr "" +msgstr "Melhorias e correções do Gamescope" #: data/com.usebottles.bottles.metainfo.xml.in:146 msgid "Dependency installation is faster and more stable" -msgstr "" +msgstr "A instalação de dependências é mais rápida e mais estável" #: data/com.usebottles.bottles.metainfo.xml.in:147 msgid "The health check has more information for faster debugging" msgstr "" +"A verificação de integridade tem mais informações para uma depuração mais " +"rápida" #: data/com.usebottles.bottles.metainfo.xml.in:148 msgid "NVAPI has a lot of fixes and is more stable, should now work properly" msgstr "" +"O NVAPI tem várias correções e está mais estável e agora deve funcionar " +"corretamente" #: data/com.usebottles.bottles.metainfo.xml.in:149 msgid "Fix crash when downloading a component" -msgstr "" +msgstr "Corrigido um erro ao descarregar um componente" #: data/com.usebottles.bottles.metainfo.xml.in:150 msgid "Backend code improvement by avoiding spin-lock" -msgstr "" +msgstr "Melhora do código de back-end ao evitar o spin-lock" #: data/com.usebottles.bottles.metainfo.xml.in:151 msgid "More variables for installer scripting" -msgstr "" +msgstr "Mais variáveis para o script do instalador" #: data/com.usebottles.bottles.metainfo.xml.in:152 msgid "Fix onboard dialog showing \"All ready\" while it was in fact not ready" @@ -3203,35 +3196,39 @@ msgstr "" #: data/com.usebottles.bottles.metainfo.xml.in:153 msgid "Improvement to build system" -msgstr "" +msgstr "Melhoria do sistema de builds" #: data/com.usebottles.bottles.metainfo.xml.in:154 msgid "Enabling VKD3D by default when creating bottles for gaming" -msgstr "" +msgstr "Ativação do VKD3D por padrão ao criar garrafas para jogos" #: data/com.usebottles.bottles.metainfo.xml.in:155 msgid "Fix crashes when reading Steam files with bad encodings" -msgstr "" +msgstr "Corrigidos erros ao ler ficheiros da Steam com má codificação" #: data/com.usebottles.bottles.metainfo.xml.in:156 msgid "" "Fix components not updated correctly in the UI after installation/" "uninstallation" msgstr "" +"Corrigidos componentes não atualizados corretamente na interface do " +"utilizador após a instalação/desinstalação" #: data/com.usebottles.bottles.metainfo.xml.in:157 msgid "More FSR fixes" -msgstr "" +msgstr "Mais correções de FSR" #: data/com.usebottles.bottles.metainfo.xml.in:158 msgid "" "Fix the issue when a program closes after it was launched from \"Run " "executable\"" msgstr "" +"Corrigido o problema quando um programa fecha após ser iniciado a partir de “" +"Executar executável”" #: data/com.usebottles.bottles.metainfo.xml.in:159 msgid "and many, many, many more!" -msgstr "" +msgstr "e muito, muito, muito mais!" #~ msgid "Calculating…" #~ msgstr "Calculando…" diff --git a/po/pt_BR.po b/po/pt_BR.po index 1c44710b469..ca30c66be8a 100644 --- a/po/pt_BR.po +++ b/po/pt_BR.po @@ -8,9 +8,8 @@ msgstr "" "Project-Id-Version: bottles\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-03-27 13:50+0530\n" -"PO-Revision-Date: 2023-07-15 15:52+0000\n" -"Last-Translator: Fernando Lopes <118869201+plasmus777@users.noreply.github." -"com>\n" +"PO-Revision-Date: 2024-02-25 02:02+0000\n" +"Last-Translator: Kazevic \n" "Language-Team: Portuguese (Brazil) \n" "Language: pt_BR\n" @@ -18,7 +17,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" -"X-Generator: Weblate 5.0-dev\n" +"X-Generator: Weblate 5.5-dev\n" #: bottles/backend/managers/backup.py:48 bottles/backend/managers/backup.py:95 msgid "No path specified" @@ -27,7 +26,7 @@ msgstr "Nenhum caminho especificado" #: bottles/backend/managers/backup.py:56 #, python-brace-format msgid "Backup {0}" -msgstr "Cópia de segurança {0}" +msgstr "Backup {0}" #: bottles/backend/managers/backup.py:101 #, python-brace-format @@ -414,7 +413,7 @@ msgstr "Controle de versão habilitado para essa garrafa" #: bottles/frontend/ui/details-bottle.blp:218 msgid "Versioning is active for this bottle." -msgstr "O controle de versão está ativo nessa garrafa." +msgstr "O controle de versão está ativo nesta garrafa." #: bottles/frontend/ui/details-bottle.blp:227 #: bottles/frontend/ui/list-entry.blp:31 @@ -1868,7 +1867,7 @@ msgstr "Adicione itens aqui da lista de programas da sua garrafa" #: bottles/frontend/ui/list-entry.blp:26 msgid "Versioning is active in this bottle." -msgstr "O controle de versão está ativo nessa garrafa." +msgstr "O controle de versão está ativo nesta garrafa." #: bottles/frontend/ui/list-entry.blp:42 msgid "This bottle looks damaged." @@ -2130,7 +2129,7 @@ msgstr "Requer a Epic Games Store instalada na garrafa." #: bottles/frontend/ui/preferences.blp:117 msgid "List Ubisoft Games in Programs List" -msgstr "Listar jogos da Ubisoft na lista de programas" +msgstr "Listar jogos da Ubisoft na Lista de Programas" #: bottles/frontend/ui/preferences.blp:118 msgid "Requires Ubisoft Connect installed in the bottle." diff --git a/po/ru.po b/po/ru.po index e94deeeb35f..03515a1bcd2 100644 --- a/po/ru.po +++ b/po/ru.po @@ -8,8 +8,8 @@ msgstr "" "Project-Id-Version: bottles\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-03-27 13:50+0530\n" -"PO-Revision-Date: 2023-05-06 20:51+0000\n" -"Last-Translator: lenemter \n" +"PO-Revision-Date: 2024-03-02 07:55+0000\n" +"Last-Translator: Hikeri \n" "Language-Team: Russian \n" "Language: ru\n" @@ -18,7 +18,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" -"X-Generator: Weblate 4.18-dev\n" +"X-Generator: Weblate 5.5-dev\n" #: bottles/backend/managers/backup.py:48 bottles/backend/managers/backup.py:95 msgid "No path specified" @@ -598,7 +598,7 @@ msgid "" "Files on this page are provided by third parties under a proprietary " "license. By installing them, you agree with their respective licensing terms." msgstr "" -"Устанавливаете прорраммы, курируемые нашим сообществом.\n" +"Устанавливаете программы, курируемые нашим сообществом.\n" "\n" "Файлы на этой странице предоставляются третьими лицами под проприетарной " "лицензией. Устанавливая их, вы соглашаетесь с соответствующими условиями " diff --git a/po/uk.po b/po/uk.po index c37fde27368..6a220897c00 100644 --- a/po/uk.po +++ b/po/uk.po @@ -8,8 +8,8 @@ msgstr "" "Project-Id-Version: bottles\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-03-27 13:50+0530\n" -"PO-Revision-Date: 2023-05-14 18:51+0000\n" -"Last-Translator: volkov \n" +"PO-Revision-Date: 2024-02-14 17:35+0000\n" +"Last-Translator: Сергій \n" "Language-Team: Ukrainian \n" "Language: uk\n" @@ -18,7 +18,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" -"X-Generator: Weblate 4.18-dev\n" +"X-Generator: Weblate 5.4-dev\n" #: bottles/backend/managers/backup.py:48 bottles/backend/managers/backup.py:95 msgid "No path specified" @@ -1992,7 +1992,7 @@ msgstr "Ласкаво просимо до Bottles" #: bottles/frontend/ui/onboard.blp:60 msgid "Run Windows Software on Linux." -msgstr "Запускайте програми Windows на Linux із Bottles!" +msgstr "Запускайте програми Windows на Linux." # Translators: Bottles is a generic noun here, referring to wine prefixes and is to be translated #: bottles/frontend/ui/onboard.blp:65 @@ -2080,7 +2080,7 @@ msgstr "Чи очищати тимчасові файли при запуску #: bottles/frontend/ui/preferences.blp:62 msgid "Close Bottles After Starting a Program" -msgstr "Закривати Bottles після запуску програми." +msgstr "Закривати Bottles після запуску програми" #: bottles/frontend/ui/preferences.blp:63 msgid "Close Bottles after starting a program from the file manager." @@ -2295,7 +2295,7 @@ msgstr "Оновлено: %s" #: bottles/frontend/views/bottle_details.py:267 #, python-brace-format msgid "\"{0}\" added" -msgstr "'{0}' додано." +msgstr "\"{0}\" додано" #: bottles/frontend/views/bottle_details.py:270 #: bottles/frontend/views/bottle_details.py:398 @@ -2615,7 +2615,7 @@ msgstr "\"{0}\" встановлено" #: bottles/frontend/widgets/dependency.py:188 #, python-brace-format msgid "\"{0}\" failed to install" -msgstr "Не вдалося встановити \"{0}\"." +msgstr "\"{0}\" не вдалося встановити" #: bottles/frontend/widgets/importer.py:68 #, python-brace-format @@ -2867,7 +2867,7 @@ msgstr "Показувати ігри Ubisoft Connect" #: data/com.usebottles.bottles.gschema.xml:32 msgid "Toggle ubisoft connect listing." -msgstr "Показ ігор від Ubisoft" +msgstr "Показ ігор від Ubisoft." #: data/com.usebottles.bottles.gschema.xml:36 msgid "Window width" diff --git a/po/zh_Hans.po b/po/zh_Hans.po index e610928b520..87fa132bb7b 100644 --- a/po/zh_Hans.po +++ b/po/zh_Hans.po @@ -8,8 +8,8 @@ msgstr "" "Project-Id-Version: bottles\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-03-27 13:50+0530\n" -"PO-Revision-Date: 2023-06-14 08:49+0000\n" -"Last-Translator: Eric \n" +"PO-Revision-Date: 2023-12-23 11:10+0000\n" +"Last-Translator: 刘韬 \n" "Language-Team: Chinese (Simplified) \n" "Language: zh_Hans\n" @@ -17,7 +17,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 4.18-dev\n" +"X-Generator: Weblate 5.4-dev\n" #: bottles/backend/managers/backup.py:48 bottles/backend/managers/backup.py:95 msgid "No path specified" @@ -772,9 +772,8 @@ msgstr "监视性能" msgid "" "Display monitoring information such as framerate, temperatures, CPU/GPU load " "and more on OpenGL and Vulkan using MangoHud." -msgstr "" -"使用 MangoHud 显示帧率,温度,CPU/GPU 负载和更多关于 OpenGL 和 Vulkan 的监控" -"信息." +msgstr "使用 MangoHud 显示帧率,温度,CPU/GPU 负载和更多关于 OpenGL 和 Vulkan " +"的监控信息。" #: bottles/frontend/ui/details-preferences.blp:211 msgid "Feral GameMode" @@ -892,7 +891,7 @@ msgstr "环境变量" #: bottles/frontend/ui/details-preferences.blp:367 msgid "Manage Drives" -msgstr "管理驱动" +msgstr "管理驱动器" #: bottles/frontend/ui/details-preferences.blp:381 msgid "Automatic Snapshots" @@ -1180,7 +1179,7 @@ msgstr "高度" #: bottles/frontend/ui/dialog-gamescope.blp:81 msgid "Window Resolution" -msgstr "Windows 分辨率" +msgstr "窗口分辨率" #: bottles/frontend/ui/dialog-gamescope.blp:82 msgid "" diff --git a/po/zh_Hant.po b/po/zh_Hant.po index 9a71b9c195a..d0c6203523c 100644 --- a/po/zh_Hant.po +++ b/po/zh_Hant.po @@ -8,8 +8,8 @@ msgstr "" "Project-Id-Version: bottles\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-03-27 13:50+0530\n" -"PO-Revision-Date: 2023-05-06 20:51+0000\n" -"Last-Translator: csc-chicken \n" +"PO-Revision-Date: 2024-02-08 06:09+0000\n" +"Last-Translator: shamo3qk \n" "Language-Team: Chinese (Traditional) \n" "Language: zh_Hant\n" @@ -17,7 +17,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 4.18-dev\n" +"X-Generator: Weblate 5.4-dev\n" #: bottles/backend/managers/backup.py:48 bottles/backend/managers/backup.py:95 msgid "No path specified" @@ -51,7 +51,7 @@ msgstr "無法建立暫時填充用的目錄/檔案。" #: bottles/backend/managers/manager.py:1162 msgid "Generating bottle configuration…" -msgstr "正在產生酒瓶設定檔…" +msgstr "正在產生bottle設定檔…" #: bottles/backend/managers/manager.py:1185 msgid "Template found, applying…" @@ -68,7 +68,7 @@ msgstr "WINE 設定檔已更新!" #: bottles/backend/managers/manager.py:1207 msgid "Running as Flatpak, sandboxing userdir…" -msgstr "在Flatpak環境執行,正將使用者目錄進行沙盒隔離…" +msgstr "在 Flatpak 環境執行,正將使用者目錄進行沙盒隔離…" #: bottles/backend/managers/manager.py:1209 msgid "Sandboxing userdir…" @@ -227,7 +227,7 @@ msgstr "" #: bottles/frontend/ui/component-entry.blp:4 msgid "Component version" -msgstr "版本" +msgstr "元件版本" #: bottles/frontend/ui/component-entry.blp:12 #: bottles/frontend/ui/dependency-entry.blp:29 @@ -418,7 +418,7 @@ msgstr "0" #: bottles/frontend/ui/details-bottle.blp:247 msgid "Run Executable…" -msgstr "執行檔案…" +msgstr "運行可執行檔…" #: bottles/frontend/ui/details-bottle.blp:272 msgid "Programs" @@ -457,11 +457,11 @@ msgstr "正在設定酒瓶。" #: bottles/frontend/ui/details-bottle.blp:360 #: bottles/frontend/views/details.py:145 msgid "Dependencies" -msgstr "相依項目" +msgstr "相依套件" #: bottles/frontend/ui/details-bottle.blp:361 msgid "Install dependencies for programs." -msgstr "安裝程式的相依套件。" +msgstr "為程式安裝相依套件。" #: bottles/frontend/ui/details-bottle.blp:370 #: bottles/frontend/ui/details-preferences.blp:377 @@ -481,7 +481,7 @@ msgstr "工作管理員" #: bottles/frontend/ui/details-bottle.blp:381 msgid "Manage running programs." -msgstr "管理正在執行的程式。" +msgstr "管理執行中的程式。" #: bottles/frontend/ui/details-bottle.blp:390 msgid "Tools" @@ -505,7 +505,7 @@ msgstr "編輯內部註冊表。" #: bottles/frontend/ui/details-bottle.blp:413 msgid "Legacy Wine Tools" -msgstr "舊版Wine工具" +msgstr "舊版 Wine 工具" #: bottles/frontend/ui/details-bottle.blp:417 msgid "Explorer" @@ -530,17 +530,17 @@ msgstr "控制台" #: bottles/frontend/ui/details-dependencies.blp:9 msgid "Search for dependencies…" -msgstr "搜尋相依項目…" +msgstr "搜尋相依套件…" #: bottles/frontend/ui/details-dependencies.blp:22 #: bottles/frontend/ui/preferences.blp:178 #: bottles/frontend/ui/preferences.blp:235 msgid "You're offline :(" -msgstr "" +msgstr "您已離線 :(" #: bottles/frontend/ui/details-dependencies.blp:25 msgid "Bottles is running in offline mode, so dependencies are not available." -msgstr "" +msgstr "Bottles 正在離線模式下執行,因此相依套件將無法存取。" #: bottles/frontend/ui/details-dependencies.blp:47 msgid "" @@ -555,7 +555,7 @@ msgstr "" #: bottles/frontend/ui/details-dependencies.blp:76 msgid "Report a problem or a missing dependency." -msgstr "回報問題或是遺失的相依項目。" +msgstr "回報問題或是遺失的相依套件。" #: bottles/frontend/ui/details-dependencies.blp:77 msgid "Report Missing Dependency" @@ -563,7 +563,7 @@ msgstr "回報遺失的相依套件" #: bottles/frontend/ui/details-dependencies.blp:81 msgid "Read Documentation." -msgstr "閱讀說明文件。" +msgstr "閱讀文檔。" #: bottles/frontend/ui/details-dependencies.blp:82 #: bottles/frontend/ui/details-installers.blp:51 @@ -595,7 +595,7 @@ msgstr "" #: bottles/frontend/ui/details-installers.blp:29 msgid "No Installers Found" -msgstr "沒有找到安裝檔" +msgstr "沒有找到安裝程式" #: bottles/frontend/ui/details-installers.blp:32 msgid "" @@ -620,7 +620,7 @@ msgstr "組件" #: bottles/frontend/ui/details-preferences.blp:15 #: bottles/frontend/ui/new.blp:129 msgid "The version of the Wine compatibility layer." -msgstr "Wine相容層的版本。" +msgstr "Wine 相容層的版本。" #: bottles/frontend/ui/details-preferences.blp:17 msgid "Updating Runner and components, please wait…" @@ -633,7 +633,7 @@ msgstr "DXVK" #: bottles/frontend/ui/details-preferences.blp:28 msgid "Improve Direct3D 9/10/11 compatibility by translating it to Vulkan." -msgstr "轉譯成Vulkan,改善對Direct3D 9/10/11的相容性。" +msgstr "將 Direct3D 9/10/11 轉譯成 Vulkan,以改善相容性。" #: bottles/frontend/ui/details-preferences.blp:30 msgid "Updating DXVK, please wait…" @@ -646,7 +646,7 @@ msgstr "VKD3D" #: bottles/frontend/ui/details-preferences.blp:41 msgid "Improve Direct3D 12 compatibility by translating it to Vulkan." -msgstr "轉譯成Vulkan以改善Direct3D 12的相容性。" +msgstr "將 Direct3D 12 轉譯成 Vulkan,以改善相容性。" #: bottles/frontend/ui/details-preferences.blp:43 msgid "Updating VKD3D, please wait…" @@ -672,7 +672,7 @@ msgstr "改進反應速度。可能會被某些反作弊程式偵測到。" #: bottles/frontend/ui/details-preferences.blp:71 msgid "Updating LatencyFleX, please wait…" -msgstr "正在更新LatencyFleX,請稍候…" +msgstr "正在更新 LatencyFleX,請稍候…" #: bottles/frontend/ui/details-preferences.blp:84 msgid "Display" @@ -686,19 +686,19 @@ msgstr "深度學習超級採樣" msgid "" "Increase performance at the expense of visuals using DXVK-NVAPI. Only works " "on newer NVIDIA GPUs." -msgstr "使用DXVK-NVAPI,犧牲視覺效果換取性能。儘支援新款Nvidia GPU。" +msgstr "使用 DXVK-NVAPI,犧牲視覺效果換取性能。僅支援 NVIDIA Turing+ GPUs。" #: bottles/frontend/ui/details-preferences.blp:105 msgid "FidelityFX Super Resolution" -msgstr "" +msgstr "FidelityFX Super Resolution" #: bottles/frontend/ui/details-preferences.blp:106 msgid "Increase performance at the expense of visuals. Only works on Vulkan." -msgstr "犧牲視覺效果換取性能。僅在Vulkan平台有效。" +msgstr "犧牲視覺效果換取性能。僅在 Vulkan 平台有效。" #: bottles/frontend/ui/details-preferences.blp:108 msgid "Manage FidelityFX Super Resolution settings" -msgstr "" +msgstr "管理 FidelityFX Super Resolution 設定" #: bottles/frontend/ui/details-preferences.blp:125 msgid "Discrete Graphics" @@ -708,28 +708,28 @@ msgstr "獨立顯示卡" msgid "" "Use the discrete graphics card to increase performance at the expense of " "power consumption." -msgstr "使用獨立顯示卡提升性能,增加耗電量。" +msgstr "以增加耗電量為代價,使用獨立顯示卡提升性能。" #: bottles/frontend/ui/details-preferences.blp:135 msgid "Post-Processing Effects" -msgstr "後製效果" +msgstr "後製特效" #: bottles/frontend/ui/details-preferences.blp:136 msgid "" "Add various post-processing effects using vkBasalt. Only works on Vulkan." -msgstr "使用vkBasalt加入多種後製特效。僅Vulkan平台有效。" +msgstr "使用 vkBasalt 加入多種後製特效。僅在 Vulkan 平台有效。" #: bottles/frontend/ui/details-preferences.blp:138 msgid "Manage Post-Processing Layer settings" -msgstr "管理後製特效層的設定" +msgstr "管理後製層的設定" #: bottles/frontend/ui/details-preferences.blp:154 msgid "Manage how games should be displayed on the screen using Gamescope." -msgstr "管理遊戲透過Gamescope的顯示方式。" +msgstr "管理遊戲透過 Gamescope 的顯示方式。" #: bottles/frontend/ui/details-preferences.blp:157 msgid "Manage Gamescope settings" -msgstr "管理Gamescope設定" +msgstr "管理 Gamescope 設定" #: bottles/frontend/ui/details-preferences.blp:171 msgid "Advanced Display Settings" @@ -771,8 +771,8 @@ msgstr "監測效能" msgid "" "Display monitoring information such as framerate, temperatures, CPU/GPU load " "and more on OpenGL and Vulkan using MangoHud." -msgstr "利用MangoHud,在OpenGL和Vulkan應用程式顯示影格率、溫度、CPU/" -"GPU負載等監測資訊。" +msgstr "利用 MangoHud,在 OpenGL 和 Vulkan 應用程式顯示影格率、溫度、CPU/" +"GPUMangoHud負載等監測資訊。" #: bottles/frontend/ui/details-preferences.blp:211 msgid "Feral GameMode" @@ -795,15 +795,15 @@ msgstr "改善多次啟動遊戲的載入速度。遊戲初次啟動時間會變 #: bottles/frontend/ui/details-preferences.blp:226 msgid "Manage vmtouch settings" -msgstr "管理vmtouch設定" +msgstr "管理 vmtouch 設定" #: bottles/frontend/ui/details-preferences.blp:241 msgid "OBS Game Capture" -msgstr "OBS遊戲擷取" +msgstr "OBS 遊戲擷取" #: bottles/frontend/ui/details-preferences.blp:242 msgid "Toggle OBS Game Capture for all Vulkan and OpenGL programs." -msgstr "為所有Vulkan和OpenGL程式切換 OBS 遊戲擷取。" +msgstr "切換所有 Vulkan 和 OpenGL 程式的 OBS 遊戲擷取。" #: bottles/frontend/ui/details-preferences.blp:251 msgid "Compatibility" @@ -840,7 +840,7 @@ msgstr "管理沙盒權限" # Translators: Bottles is a proper noun referring to the app #: bottles/frontend/ui/details-preferences.blp:295 msgid "Bottles Runtime" -msgstr "Bottles 執行環境" +msgstr "Bottles Runtime" #: bottles/frontend/ui/details-preferences.blp:296 msgid "" @@ -850,13 +850,13 @@ msgstr "新增額外函式庫以增加相容性。若遇到問題請將此選項 #: bottles/frontend/ui/details-preferences.blp:306 msgid "Steam Runtime" -msgstr "Steam執行時間" +msgstr "Steam Runtime" #: bottles/frontend/ui/details-preferences.blp:307 msgid "" "Provide a bundle of extra libraries for more compatibility with Steam games. " "Disable it if you run into issues." -msgstr "新增額外函式庫以增加Steam遊戲相容性。若遇到問題請將此選項關閉。" +msgstr "提供額外函式庫以增加 Steam 遊戲相容性。若遇到問題請將此選項關閉。" #: bottles/frontend/ui/details-preferences.blp:315 #: bottles/frontend/ui/dialog-launch-options.blp:83 @@ -918,7 +918,7 @@ msgstr "使用排除模式" #: bottles/frontend/ui/details-preferences.blp:402 msgid "Exclude paths in snapshots." -msgstr "排除快照中的路徑。" +msgstr "為快照排除路徑。" #: bottles/frontend/ui/details-preferences.blp:405 msgid "Manage Patterns" @@ -989,7 +989,7 @@ msgstr "鑄造新酒瓶" # Translators: Bottles is a proper noun referring to the app #: bottles/frontend/ui/dialog-crash-report.blp:8 msgid "Bottles Crash Report" -msgstr "回報酒瓶崩潰" +msgstr "Bottles 崩潰回報" #: bottles/frontend/ui/dialog-crash-report.blp:18 #: bottles/frontend/ui/dialog-duplicate.blp:22 @@ -1076,7 +1076,7 @@ msgstr "硬碟" msgid "" "These are paths from your host system that are mapped and recognized as " "devices by the runner (e.g. C: D:…)." -msgstr "這些是您主機系統映射到和給執行器辨識的路徑(例如 C: D:....)。" +msgstr "這些是由您的主機系統映射和被執行器辨識的路徑(例如 C: D:....)。" #: bottles/frontend/ui/dialog-drives.blp:27 msgid "Letter" @@ -1135,7 +1135,7 @@ msgstr "排除樣式" msgid "" "Define patterns that will be used to prevent some directories to being " "versioned." -msgstr "定義用來避免一些目錄被記錄到版本裡的樣式。" +msgstr "定義樣式,將目錄排除於版本控制。" #: bottles/frontend/ui/dialog-exclusion-patterns.blp:31 msgid "Pattern" @@ -1147,7 +1147,7 @@ msgstr "現存的樣式" #: bottles/frontend/ui/dialog-gamescope.blp:6 msgid "Gamescope Settings" -msgstr "Gamescope設定" +msgstr "Gamescope 設定" #: bottles/frontend/ui/dialog-gamescope.blp:30 #: bottles/frontend/ui/dialog-launch-options.blp:32 @@ -1180,7 +1180,7 @@ msgstr "高度" #: bottles/frontend/ui/dialog-gamescope.blp:81 msgid "Window Resolution" -msgstr "Windows解析度" +msgstr "視窗解析度" #: bottles/frontend/ui/dialog-gamescope.blp:82 msgid "" @@ -1201,9 +1201,8 @@ msgid "Frame Rate Limit When Unfocused" msgstr "非聚焦時的影格率" #: bottles/frontend/ui/dialog-gamescope.blp:153 -#, fuzzy msgid "Integer Scaling" -msgstr "使用整數縮放" +msgstr "整數縮放" #: bottles/frontend/ui/dialog-gamescope.blp:162 msgid "Window Type" @@ -1404,7 +1403,7 @@ msgstr "執行" #: bottles/frontend/ui/dialog-run-args.blp:44 msgid "Write below the arguments to be passed to the executable." -msgstr "請在下方輸入要傳遞給執行檔的引述。" +msgstr "請在下方輸入要傳遞給執行檔的引數。" #: bottles/frontend/ui/dialog-run-args.blp:47 msgid "e.g.: -opengl -SkipBuildPatchPrereq" @@ -1486,7 +1485,7 @@ msgstr "預設" #: bottles/frontend/ui/dialog-vkbasalt.blp:48 msgid "Default Settings" -msgstr "顯示設定" +msgstr "預設設定" #: bottles/frontend/ui/dialog-vkbasalt.blp:57 msgid "Effects are applied according to the list order." @@ -1703,7 +1702,7 @@ msgstr "匯入酒瓶備份" #: bottles/frontend/ui/importer.blp:28 msgid "Search again for prefixes" -msgstr "再次搜尋Wineprefix" +msgstr "再次搜尋 Wineprefixes" #: bottles/frontend/ui/importer.blp:38 msgid "No Prefixes Found" @@ -1714,7 +1713,7 @@ msgid "" "No external prefixes were found. Does Bottles have access to them?\n" "Use the icon on the top to import a bottle from a backup." msgstr "" -"找不到外部prefix。請確認Bottles是否有存取權限?\n" +"找不到外部 prefixes。請確認 Bottles 是否有存取權限?\n" "\n" "請使用上方圖示從備份匯入酒瓶。" @@ -1839,7 +1838,7 @@ msgstr "啟動中…" #: bottles/frontend/ui/local-resource-entry.blp:4 msgid "This resource is missing." -msgstr "遺失這項資源。" +msgstr "這項資源已遺失。" #: bottles/frontend/ui/local-resource-entry.blp:8 msgid "Browse" @@ -1884,7 +1883,8 @@ msgid "" "This makes the user directory discoverable in the bottle, at the risk of " "sharing personal information to Windows software. This option cannot be " "changed after the bottle has been created." -msgstr "" +msgstr "這使得使用者目錄可以在酒瓶中被發現,從而面臨與 Windows " +"軟體共享個人資訊的風險。建立酒瓶後此選項無法變更。" #: bottles/frontend/ui/new.blp:136 msgid "Architecture" @@ -1896,19 +1896,16 @@ msgid "32-bit should only be used if strictly necessary." msgstr "如果沒有特別的需求,我們建議不要使用 32 位元。" #: bottles/frontend/ui/new.blp:146 -#, fuzzy msgid "Import a custom configuration." -msgstr "匯出配置…" +msgstr "匯入自訂配置" #: bottles/frontend/ui/new.blp:176 -#, fuzzy msgid "Bottle Directory" -msgstr "Bottles的目錄" +msgstr "Bottle 目錄" #: bottles/frontend/ui/new.blp:177 -#, fuzzy msgid "Directory that will contain the data of this bottle." -msgstr "用於存放您Bottles資料的目錄。" +msgstr "用於存放此 bottle 資料的目錄。" #: bottles/frontend/ui/new.blp:249 #, fuzzy @@ -1916,9 +1913,8 @@ msgid "_Close" msgstr "關閉" #: bottles/frontend/ui/new.blp:281 -#, fuzzy msgid "This name is unavailable, please try another." -msgstr "您的系統無法使用這個功能。" +msgstr "此名稱無法使用,請重新命名。" #: bottles/frontend/ui/onboard.blp:34 msgid "Previous" @@ -1931,12 +1927,12 @@ msgstr "歡迎來到 Bottles" #: bottles/frontend/ui/onboard.blp:60 msgid "Run Windows Software on Linux." -msgstr "在Linux上執行Windows軟體。" +msgstr "在 Linux 上執行 Windows 軟體。" # Translators: Bottles is a generic noun here, referring to wine prefixes and is to be translated #: bottles/frontend/ui/onboard.blp:65 msgid "Windows in Bottles" -msgstr "Bottles裡的Windows" +msgstr "Bottles 中的 Windows" #: bottles/frontend/ui/onboard.blp:66 msgid "" @@ -1985,11 +1981,11 @@ msgstr "外觀" #: bottles/frontend/ui/preferences.blp:17 msgid "Dark Mode" -msgstr "黑暗模式" +msgstr "闇黑模式" #: bottles/frontend/ui/preferences.blp:18 msgid "Whether Bottles should use the dark color scheme." -msgstr "Bottles 是否要使用黑暗配色。" +msgstr "Bottles 是否要使用黯黑配色。" #: bottles/frontend/ui/preferences.blp:28 msgid "Show Update Date" @@ -1997,7 +1993,7 @@ msgstr "顯示更新日期" #: bottles/frontend/ui/preferences.blp:29 msgid "Whether to show the update date in the bottle list." -msgstr "是否應該在酒瓶列表中顯示更新日期。" +msgstr "是否在 bottle 列表中顯示更新日期。" #: bottles/frontend/ui/preferences.blp:42 #: data/com.usebottles.bottles.gschema.xml:46 @@ -2034,7 +2030,7 @@ msgstr "Steam Proton Prefixes" #: bottles/frontend/ui/preferences.blp:77 msgid "List and manage Steam Proton prefixes." -msgstr "列出和管理Steam Proton Prefixes。" +msgstr "列出和管理 Steam Proton Prefixes。" #: bottles/frontend/ui/preferences.blp:97 msgid "List Steam Apps in Programs List" @@ -2066,11 +2062,11 @@ msgstr "進階" #: bottles/frontend/ui/preferences.blp:131 msgid "Bottles Directory" -msgstr "Bottles的目錄" +msgstr "Bottles 的目錄" #: bottles/frontend/ui/preferences.blp:132 msgid "Directory that contains the data of your Bottles." -msgstr "用於存放您Bottles資料的目錄。" +msgstr "用於存放您 Bottles 資料的目錄。" #: bottles/frontend/ui/preferences.blp:167 msgid "Runners" @@ -2078,7 +2074,7 @@ msgstr "執行器" #: bottles/frontend/ui/preferences.blp:181 msgid "Bottles is running in offline mode, so runners are not available." -msgstr "" +msgstr "Bottles 正在離線模式下執行,因此無法使用執行器。" #: bottles/frontend/ui/preferences.blp:208 msgid "Pre-Release" @@ -2094,7 +2090,7 @@ msgstr "DLL 組件" #: bottles/frontend/ui/preferences.blp:238 msgid "Bottles is running in offline mode, so DLLs are not available." -msgstr "" +msgstr "Bottles 正在離線模式下執行,因此無法使用 DLLs。" #: bottles/frontend/ui/preferences.blp:270 msgid "DXVK-NVAPI" @@ -2121,7 +2117,7 @@ msgstr "實驗性功能" msgid "" "These features are under heavy development and may be unstable, expect bugs " "and breakage." -msgstr "此功能正在積極開發中,可能不穩定,含有許多臭蟲,可能造成毀損。" +msgstr "此功能正在積極開發中,可能不穩定,臭蟲和毀損是可預期的。" #: bottles/frontend/ui/preferences.blp:303 msgid "Sandbox per bottle" @@ -2235,9 +2231,8 @@ msgstr "「{0}」已新增" #: bottles/frontend/views/bottle_details.py:270 #: bottles/frontend/views/bottle_details.py:398 #: bottles/frontend/views/list.py:128 -#, fuzzy msgid "Select Executable" -msgstr "選取酒瓶" +msgstr "選取可執行檔" #: bottles/frontend/views/bottle_details.py:273 msgid "Add" @@ -2322,9 +2317,8 @@ msgstr "缺少執行器" msgid "" "The runner requested by this bottle is missing. Install it through the " "Bottles preferences or choose a new one to run applications." -msgstr "" -"此酒瓶所需的執行器已遺失。從Bottles的偏好設定重新安裝執行器,或是選擇新的執行" -"器用於執行應用程式。" +msgstr "此酒瓶所需的執行器已遺失。從 Bottles " +"的偏好設定重新安裝執行器,或是選擇新的執行器用於執行應用程式。" #: bottles/frontend/views/bottle_details.py:597 msgid "Are you sure you want to force stop all processes?" @@ -2353,9 +2347,8 @@ msgstr "名稱含有特殊字元,或是已經被使用。" #: bottles/frontend/views/bottle_preferences.py:301 #: bottles/frontend/windows/launchoptions.py:241 -#, fuzzy msgid "Select Working Directory" -msgstr "工作目錄" +msgstr "選擇工作目錄" #: bottles/frontend/views/bottle_preferences.py:423 msgid "Directory that contains the data of \"{}\"." @@ -2415,18 +2408,18 @@ msgstr "選擇配置文件" #: bottles/frontend/views/list.py:60 bottles/frontend/views/list.py:66 msgid "N/A" -msgstr "無/全" +msgstr "N/A" #. Set tooltip text #: bottles/frontend/views/list.py:91 #, python-brace-format msgid "Run executable in \"{self.config.Name}\"" -msgstr "" +msgstr "在 {self.config.Name} 中執行檔案" #: bottles/frontend/views/list.py:118 -#, fuzzy, python-brace-format +#, python-brace-format msgid "Launching \"{0}\" in \"{1}\"…" -msgstr "正在啟動「{0}」…" +msgstr "正在啟動「{1}」中的「{0}」…" #: bottles/frontend/views/list.py:235 msgid "Your Bottles" @@ -2443,9 +2436,8 @@ msgid "Fetched {0} of {1} packages" msgstr "已取得 {0} 個軟體包,共 {1} 個" #: bottles/frontend/views/new.py:157 -#, fuzzy msgid "Select Bottle Directory" -msgstr "Bottles的目錄" +msgstr "選取 Bottle 的目錄" #: bottles/frontend/views/new.py:176 #, fuzzy @@ -2453,13 +2445,12 @@ msgid "Creating Bottle…" msgstr "正在鑄造酒瓶…" #: bottles/frontend/views/new.py:221 -#, fuzzy msgid "Unable to Create Bottle" -msgstr "鑄造新酒瓶" +msgstr "鑄造酒瓶失敗" #: bottles/frontend/views/new.py:225 msgid "Bottle failed to create with one or more errors." -msgstr "" +msgstr "由於一個或多個錯誤,無法建立酒瓶。" #. Show success #: bottles/frontend/views/new.py:232 @@ -2721,20 +2712,19 @@ msgstr "安裝程式因不明錯誤安裝失敗" #: bottles/frontend/windows/launchoptions.py:56 #, python-brace-format msgid "{0} is already disabled for this bottle." -msgstr "{0} 原本就已經為此酒瓶停用了。" +msgstr "{0} 已在此酒瓶停用。" #: bottles/frontend/windows/launchoptions.py:57 msgid "This setting is different from the bottle's default." msgstr "此設定與酒瓶的預設設定不一致。" #: bottles/frontend/windows/launchoptions.py:215 -#, fuzzy msgid "Select Script" -msgstr "選取硬碟路徑" +msgstr "選取腳本" #: bottles/frontend/windows/main_window.py:220 msgid "Custom Bottles Path not Found" -msgstr "找不到自訂的酒瓶路徑" +msgstr "找不到自訂的 Bottles 路徑" #: bottles/frontend/windows/main_window.py:221 msgid "" @@ -2743,12 +2733,12 @@ msgstr "退回使用預設路徑。提供路徑中的酒瓶不會被列出。" #: data/com.usebottles.bottles.desktop.in.in:3 msgid "@APP_NAME@" -msgstr "" +msgstr "@APP_NAME@" #: data/com.usebottles.bottles.desktop.in.in:4 #: data/com.usebottles.bottles.metainfo.xml.in:8 msgid "Run Windows Software" -msgstr "執行 Windows軟體" +msgstr "執行 Windows 軟體" #: data/com.usebottles.bottles.desktop.in.in:13 msgid "wine;windows;" @@ -2764,11 +2754,11 @@ msgstr "觸發 Flatpak 遷移對話框。" #: data/com.usebottles.bottles.gschema.xml:11 msgid "Dark theme" -msgstr "黑暗主題" +msgstr "闇黑主題" #: data/com.usebottles.bottles.gschema.xml:12 msgid "Force the use of dark theme." -msgstr "強制使用黑暗主題。" +msgstr "強制使用闇黑主題。" #: data/com.usebottles.bottles.gschema.xml:16 msgid "Toggle update date in list" @@ -2776,7 +2766,7 @@ msgstr "在列表中切換更新日期" #: data/com.usebottles.bottles.gschema.xml:17 msgid "Toggle the update date in list of bottles." -msgstr "在容器列表中切換更新日期。" +msgstr "切換 bottles 的更新日期顯示。" #: data/com.usebottles.bottles.gschema.xml:21 msgid "Steam apps listing" @@ -2788,7 +2778,7 @@ msgstr "切換 Steam 應用程式列表。" #: data/com.usebottles.bottles.gschema.xml:26 msgid "Epic Games listing" -msgstr "Epic Games 遊戲列表" +msgstr "Epic Games 應用程式列表" #: data/com.usebottles.bottles.gschema.xml:27 msgid "Toggle epic games listing." @@ -2796,7 +2786,7 @@ msgstr "切換 Epic Games 應用程式列表。" #: data/com.usebottles.bottles.gschema.xml:31 msgid "Ubisoft Connect listing" -msgstr "Ubisoft Connect 列表" +msgstr "Ubisoft Connect 應用程式列表" #: data/com.usebottles.bottles.gschema.xml:32 msgid "Toggle ubisoft connect listing." @@ -2824,7 +2814,7 @@ msgstr "顯示通知。" #: data/com.usebottles.bottles.gschema.xml:51 msgid "Temp cleaning" -msgstr "暫存清理" +msgstr "暫存清理中" #: data/com.usebottles.bottles.gschema.xml:52 msgid "Clean the temp path when booting the system." @@ -2836,7 +2826,7 @@ msgstr "發佈" #: data/com.usebottles.bottles.gschema.xml:57 msgid "Toggle release candidate for runners." -msgstr "為執行器切換發佈。" +msgstr "切換候選版本執行器的顯示。" #: data/com.usebottles.bottles.gschema.xml:61 msgid "Startup view" @@ -2850,7 +2840,7 @@ msgstr "選擇該程式應在何種檢視下啟動。" msgid "" "Toggle experimental features such as versioning and installers. Release " "candidate for runners." -msgstr "切換實驗性功能,例如版本控制與安裝程序。為執行器發佈候選。" +msgstr "切換實驗性功能,例如版本控制、安裝程式、候選版本的執行器。" #: data/com.usebottles.bottles.gschema.xml:71 msgid "Steam Proton Support" @@ -2858,11 +2848,11 @@ msgstr "Steam Proton 支援" #: data/com.usebottles.bottles.gschema.xml:72 msgid "Toggle Steam Proton prefixes support." -msgstr "切換 Steam Proton 的 prefix 支援。" +msgstr "切換 Steam Proton prefixes 的支援。" #: data/com.usebottles.bottles.gschema.xml:76 msgid "Experiments:sandbox" -msgstr "實驗性:沙盒" +msgstr "實驗性功能:沙盒" #: data/com.usebottles.bottles.gschema.xml:77 msgid "Toggle experimental Sandbox per bottle." @@ -2984,11 +2974,11 @@ msgstr "……與更多待你安裝 Bottles 後發掘!" #: data/com.usebottles.bottles.metainfo.xml.in:84 msgid "Update metadata information" -msgstr "" +msgstr "更新詮釋資料資訊" #: data/com.usebottles.bottles.metainfo.xml.in:89 msgid "Add more update information and correct release notes version" -msgstr "" +msgstr "新增更多更新資訊和正確的發行說明版本" #: data/com.usebottles.bottles.metainfo.xml.in:94 #, fuzzy @@ -3032,7 +3022,7 @@ msgstr "新增自定義執行檔路徑" #: data/com.usebottles.bottles.metainfo.xml.in:117 msgid "Bug fixes:" -msgstr "" +msgstr "錯誤修正:" #: data/com.usebottles.bottles.metainfo.xml.in:119 msgid "Adding shortcut to Steam resulted an error" @@ -3045,7 +3035,7 @@ msgstr "匯入備份:{0}" #: data/com.usebottles.bottles.metainfo.xml.in:121 msgid "Steam Runtime automatically enabled when using wine-ge-custom" -msgstr "" +msgstr "當使用wine-ge-custom時, Steam執行時間將會自動啟用" #: data/com.usebottles.bottles.metainfo.xml.in:122 msgid "" diff --git a/requirements.dev.txt b/requirements.dev.txt index 1fe53caa01d..fa2997e8476 100644 --- a/requirements.dev.txt +++ b/requirements.dev.txt @@ -1,7 +1,7 @@ # Updated using pur -r requirements.txt -pytest==7.4.0 +pytest==7.4.3 requirements-parser==0.5.0 -mypy==1.5.1 +mypy==1.8.0 types_Markdown types-PyYAML types-Pygments diff --git a/requirements.txt b/requirements.txt index 9f8a64f6bae..5df06198d58 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,19 +1,19 @@ # Updated using pur -r requirements.txt -wheel==0.41.1 +wheel==0.42.0 PyYAML==6.0.1 pycurl==7.45.2 chardet==5.2.0 requests[use_chardet_on_py3]==2.31.0 -Markdown==3.4.4 +Markdown==3.5.1 icoextract==0.1.4 -patool==1.12 -pathvalidate==3.1.0 +patool==2.0.0 +pathvalidate==3.2.0 FVS==0.3.4 -orjson==3.9.5 -pycairo==1.24.0 -PyGObject==3.44.1 -charset-normalizer==3.2.0 -idna==3.4 -urllib3==2.0.4 -certifi==2023.7.22 +orjson==3.9.10 +pycairo==1.25.1 +PyGObject==3.46.0 +charset-normalizer==3.3.2 +idna==3.6 +urllib3==2.1.0 +certifi==2023.11.17 pefile==2023.2.7