Full Code of bottlesdevs/Bottles for AI

main 1920d2dbfb54 cached
404 files
7.7 MB
2.0M tokens
1649 symbols
1 requests
Download .txt
Showing preview only (8,151K chars total). Download the full file or copy to clipboard to get everything.
Repository: bottlesdevs/Bottles
Branch: main
Commit: 1920d2dbfb54
Files: 404
Total size: 7.7 MB

Directory structure:
gitextract_3jwfbjfe/

├── .gitattributes
├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug.yml
│   │   ├── config.yml
│   │   ├── feature-request.yml
│   │   ├── feedback.md
│   │   └── mirror.yml
│   ├── dependabot.yml
│   ├── pull_request_template.md
│   └── workflows/
│       ├── build_flatpak.yml
│       ├── close-issues.yml
│       ├── pre-commit.yml
│       └── update-manifest.yml
├── .gitignore
├── .gitmodules
├── .mailmap
├── .pre-commit-config.yaml
├── CODE_OF_CONDUCT.md
├── CODING_GUIDE.md
├── CONTRIBUTING.md
├── COPYING.md
├── README.md
├── VERSION
├── VERSION_UPDATE.md
├── bottles/
│   ├── __init__.py
│   ├── backend/
│   │   ├── __init__.py
│   │   ├── cabextract.py
│   │   ├── diff.py
│   │   ├── dlls/
│   │   │   ├── __init__.py
│   │   │   ├── dll.py
│   │   │   ├── dxvk.py
│   │   │   ├── latencyflex.py
│   │   │   ├── meson.build
│   │   │   ├── nvapi.py
│   │   │   └── vkd3d.py
│   │   ├── downloader.py
│   │   ├── globals.py
│   │   ├── health.py
│   │   ├── logger.py
│   │   ├── managers/
│   │   │   ├── __init__.py
│   │   │   ├── backup.py
│   │   │   ├── component.py
│   │   │   ├── conf.py
│   │   │   ├── data.py
│   │   │   ├── dependency.py
│   │   │   ├── eagle.py
│   │   │   ├── eagle.yar
│   │   │   ├── epicgamesstore.py
│   │   │   ├── importer.py
│   │   │   ├── installer.py
│   │   │   ├── journal.py
│   │   │   ├── library.py
│   │   │   ├── manager.py
│   │   │   ├── meson.build
│   │   │   ├── origin.py
│   │   │   ├── playtime.py
│   │   │   ├── queue.py
│   │   │   ├── registry_rule.py
│   │   │   ├── repository.py
│   │   │   ├── runtime.py
│   │   │   ├── sandbox.py
│   │   │   ├── steam.py
│   │   │   ├── steamgriddb.py
│   │   │   ├── template.py
│   │   │   ├── thumbnail.py
│   │   │   ├── ubisoftconnect.py
│   │   │   └── versioning.py
│   │   ├── meson.build
│   │   ├── models/
│   │   │   ├── __init__.py
│   │   │   ├── config.py
│   │   │   ├── enum.py
│   │   │   ├── meson.build
│   │   │   ├── process.py
│   │   │   ├── registry_rule.py
│   │   │   ├── result.py
│   │   │   ├── samples.py
│   │   │   └── vdict.py
│   │   ├── params.py
│   │   ├── repos/
│   │   │   ├── __init__.py
│   │   │   ├── component.py
│   │   │   ├── dependency.py
│   │   │   ├── installer.py
│   │   │   ├── meson.build
│   │   │   └── repo.py
│   │   ├── runner.py
│   │   ├── state.py
│   │   ├── utils/
│   │   │   ├── __init__.py
│   │   │   ├── connection.py
│   │   │   ├── decorators.py
│   │   │   ├── display.py
│   │   │   ├── file.py
│   │   │   ├── generic.py
│   │   │   ├── gpu.py
│   │   │   ├── gsettings_stub.py
│   │   │   ├── imagemagick.py
│   │   │   ├── json.py
│   │   │   ├── lnk.py
│   │   │   ├── manager.py
│   │   │   ├── meson.build
│   │   │   ├── nvidia.py
│   │   │   ├── proc.py
│   │   │   ├── singleton.py
│   │   │   ├── snake.py
│   │   │   ├── steam.py
│   │   │   ├── terminal.py
│   │   │   ├── threading.py
│   │   │   ├── vdf.py
│   │   │   ├── vulkan.py
│   │   │   ├── wine.py
│   │   │   └── yaml.py
│   │   └── wine/
│   │       ├── __init__.py
│   │       ├── catalogs.py
│   │       ├── cmd.py
│   │       ├── control.py
│   │       ├── drives.py
│   │       ├── eject.py
│   │       ├── executor.py
│   │       ├── expand.py
│   │       ├── explorer.py
│   │       ├── hh.py
│   │       ├── icinfo.py
│   │       ├── meson.build
│   │       ├── msiexec.py
│   │       ├── net.py
│   │       ├── notepad.py
│   │       ├── oleview.py
│   │       ├── progman.py
│   │       ├── reg.py
│   │       ├── regedit.py
│   │       ├── register.py
│   │       ├── regkeys.py
│   │       ├── regsvr32.py
│   │       ├── rundll32.py
│   │       ├── start.py
│   │       ├── taskmgr.py
│   │       ├── uninstaller.py
│   │       ├── wineboot.py
│   │       ├── winebridge.py
│   │       ├── winecfg.py
│   │       ├── winecommand.py
│   │       ├── winedbg.py
│   │       ├── winefile.py
│   │       ├── winepath.py
│   │       ├── wineprogram.py
│   │       ├── wineserver.py
│   │       ├── winhelp.py
│   │       └── xcopy.py
│   ├── frontend/
│   │   ├── __init__.py
│   │   ├── bottles.py
│   │   ├── cli/
│   │   │   ├── __init__.py
│   │   │   ├── cli.py
│   │   │   └── meson.build
│   │   ├── main.py
│   │   ├── meson.build
│   │   ├── operation.py
│   │   ├── params.py
│   │   ├── ui/
│   │   │   ├── bottle-row.blp
│   │   │   ├── bottles.gresource.xml
│   │   │   ├── check-row.blp
│   │   │   ├── component-entry.blp
│   │   │   ├── dependency-entry.blp
│   │   │   ├── details-bottle.blp
│   │   │   ├── details-dependencies.blp
│   │   │   ├── details-installers.blp
│   │   │   ├── details-preferences.blp
│   │   │   ├── details-registry-rules.blp
│   │   │   ├── details-taskmanager.blp
│   │   │   ├── details-versioning.blp
│   │   │   ├── details.blp
│   │   │   ├── dialog-bottle-picker.blp
│   │   │   ├── dialog-crash-report.blp
│   │   │   ├── dialog-dependency-install.blp
│   │   │   ├── dialog-deps-check.blp
│   │   │   ├── dialog-display.blp
│   │   │   ├── dialog-dll-overrides.blp
│   │   │   ├── dialog-drives.blp
│   │   │   ├── dialog-duplicate.blp
│   │   │   ├── dialog-env-vars.blp
│   │   │   ├── dialog-exclusion-patterns.blp
│   │   │   ├── dialog-gamescope.blp
│   │   │   ├── dialog-installer.blp
│   │   │   ├── dialog-journal.blp
│   │   │   ├── dialog-launch-options.blp
│   │   │   ├── dialog-mangohud.blp
│   │   │   ├── dialog-playtime-graph.blp
│   │   │   ├── dialog-proton-alert.blp
│   │   │   ├── dialog-registry-rules.blp
│   │   │   ├── dialog-rename.blp
│   │   │   ├── dialog-run-args.blp
│   │   │   ├── dialog-sandbox.blp
│   │   │   ├── dialog-upgrade-versioning.blp
│   │   │   ├── dialog-versioning-branch.blp
│   │   │   ├── dialog-versioning-commit.blp
│   │   │   ├── dialog-versioning-manage-branches.blp
│   │   │   ├── dialog-versioning-settings.blp
│   │   │   ├── dialog-vkbasalt.blp
│   │   │   ├── dialog-vmtouch.blp
│   │   │   ├── dialog-winebridge-update.blp
│   │   │   ├── dll-override-entry.blp
│   │   │   ├── drive-entry.blp
│   │   │   ├── eagle.blp
│   │   │   ├── env-var-entry.blp
│   │   │   ├── exclusion-pattern-entry.blp
│   │   │   ├── help-overlay.blp
│   │   │   ├── importer-entry.blp
│   │   │   ├── importer.blp
│   │   │   ├── inherited-env-entry.blp
│   │   │   ├── installer-entry.blp
│   │   │   ├── library-entry.blp
│   │   │   ├── library.blp
│   │   │   ├── list.blp
│   │   │   ├── loading.blp
│   │   │   ├── local-resource-entry.blp
│   │   │   ├── meson.build
│   │   │   ├── new-bottle-dialog.blp
│   │   │   ├── onboard.blp
│   │   │   ├── preferences.blp
│   │   │   ├── program-entry.blp
│   │   │   ├── registry-rule-entry.blp
│   │   │   ├── state-entry.blp
│   │   │   ├── style-dark.css
│   │   │   ├── style.css
│   │   │   ├── task-entry.blp
│   │   │   └── window.blp
│   │   ├── utils/
│   │   │   ├── __init__.py
│   │   │   ├── common.py
│   │   │   ├── filters.py
│   │   │   ├── gtk.py
│   │   │   ├── meson.build
│   │   │   ├── playtime.py
│   │   │   └── sh.py
│   │   ├── views/
│   │   │   ├── __init__.py
│   │   │   ├── bottle_dependencies.py
│   │   │   ├── bottle_details.py
│   │   │   ├── bottle_installers.py
│   │   │   ├── bottle_preferences.py
│   │   │   ├── bottle_registry_rules.py
│   │   │   ├── bottle_taskmanager.py
│   │   │   ├── bottle_versioning.py
│   │   │   ├── details.py
│   │   │   ├── eagle.py
│   │   │   ├── importer.py
│   │   │   ├── library.py
│   │   │   ├── list.py
│   │   │   ├── loading.py
│   │   │   ├── meson.build
│   │   │   ├── new_bottle_dialog.py
│   │   │   └── preferences.py
│   │   ├── widgets/
│   │   │   ├── __init__.py
│   │   │   ├── component.py
│   │   │   ├── dependency.py
│   │   │   ├── executable.py
│   │   │   ├── importer.py
│   │   │   ├── installer.py
│   │   │   ├── library.py
│   │   │   ├── meson.build
│   │   │   ├── playtimechart_hourly.py
│   │   │   ├── playtimechart_monthly.py
│   │   │   ├── playtimechart_weekly.py
│   │   │   ├── program.py
│   │   │   └── state.py
│   │   └── windows/
│   │       ├── __init__.py
│   │       ├── bottlepicker.py
│   │       ├── crash.py
│   │       ├── dependency_install.py
│   │       ├── depscheck.py
│   │       ├── display.py
│   │       ├── dlloverrides.py
│   │       ├── drives.py
│   │       ├── duplicate.py
│   │       ├── envvars.py
│   │       ├── exclusionpatterns.py
│   │       ├── funding.py
│   │       ├── gamescope.py
│   │       ├── generic.py
│   │       ├── generic_cli.py
│   │       ├── installer.py
│   │       ├── journal.py
│   │       ├── launchoptions.py
│   │       ├── mangohud.py
│   │       ├── meson.build
│   │       ├── onboard.py
│   │       ├── playtimegraph.py
│   │       ├── protonalert.py
│   │       ├── registry_rules.py
│   │       ├── rename.py
│   │       ├── sandbox.py
│   │       ├── upgradeversioning.py
│   │       ├── versioning_branch.py
│   │       ├── versioning_commit.py
│   │       ├── versioning_manage_branches.py
│   │       ├── versioning_settings.py
│   │       ├── vkbasalt.py
│   │       ├── vmtouch.py
│   │       ├── window.py
│   │       └── winebridgeupdate.py
│   ├── fvs/
│   │   ├── __init__.py
│   │   ├── exceptions.py
│   │   ├── meson.build
│   │   └── repo.py
│   ├── meson.build
│   └── tests/
│       ├── __init__.py
│       ├── backend/
│       │   ├── __init__.py
│       │   ├── integration/
│       │   │   └── playtime/
│       │   │       ├── conftest.py
│       │   │       ├── test_aggregation.py
│       │   │       ├── test_disabled_tracking.py
│       │   │       ├── test_failure_run.py
│       │   │       ├── test_playtime_signals.py
│       │   │       ├── test_recovery.py
│       │   │       ├── test_schema_meta.py
│       │   │       ├── test_successful_run.py
│       │   │       ├── test_uniqueness_retry.py
│       │   │       └── test_wine_executor_playtime.py
│       │   ├── manager/
│       │   │   ├── __init__.py
│       │   │   ├── test_manager.py
│       │   │   └── test_playtime.py
│       │   ├── state/
│       │   │   ├── __init__.py
│       │   │   └── test_events.py
│       │   ├── utils/
│       │   │   ├── __init__.py
│       │   │   └── test_generic.py
│       │   └── wine/
│       │       └── test_executor.py
│       ├── conftest.py
│       └── frontend/
│           └── test_playtime_service.py
├── build-aux/
│   ├── build.sh
│   ├── com.usebottles.bottles.Devel.json
│   ├── fvs2-modules.txt
│   ├── fvs2.yaml
│   ├── install.sh
│   └── pypi-deps.yaml
├── data/
│   ├── com.usebottles.bottles.desktop.in.in
│   ├── com.usebottles.bottles.gschema.xml
│   ├── com.usebottles.bottles.metainfo.xml.in.in
│   ├── data.gresource.xml.in
│   ├── icons/
│   │   └── meson.build
│   └── meson.build
├── meson.build
├── meson_options.txt
├── mypy.ini
├── po/
│   ├── LINGUAS
│   ├── POTFILES
│   ├── README.md
│   ├── ar.po
│   ├── az.po
│   ├── be.po
│   ├── bg.po
│   ├── bn.po
│   ├── bottles.pot
│   ├── bs.po
│   ├── ca.po
│   ├── ckb.po
│   ├── cs.po
│   ├── da.po
│   ├── de.po
│   ├── el.po
│   ├── eo.po
│   ├── es.po
│   ├── et.po
│   ├── eu.po
│   ├── fa.po
│   ├── fi.po
│   ├── fr.po
│   ├── gl.po
│   ├── he.po
│   ├── hi.po
│   ├── hr.po
│   ├── hu.po
│   ├── id.po
│   ├── ie.po
│   ├── it.po
│   ├── ja.po
│   ├── ka.po
│   ├── kab.po
│   ├── ko.po
│   ├── kw.po
│   ├── lt.po
│   ├── meson.build
│   ├── ms.po
│   ├── nb_NO.po
│   ├── nl.po
│   ├── oc.po
│   ├── pl.po
│   ├── pt.po
│   ├── pt_BR.po
│   ├── ro.po
│   ├── ru.po
│   ├── sk.po
│   ├── sl.po
│   ├── sr.po
│   ├── sv.po
│   ├── ta.po
│   ├── th.po
│   ├── tr.po
│   ├── uk.po
│   ├── vi.po
│   ├── yi.po
│   ├── zh_Hans.po
│   └── zh_Hant.po
├── pyproject.toml
├── pyrightconfig.json
├── requirements.dev.txt
├── requirements.txt
├── test_path_normalization.py
└── tests/
    ├── conftest.py
    └── test_fvs.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitattributes
================================================
# Ref: https://git-scm.com/docs/gitattributes
* text=auto eol=lf


================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
liberapay: Bottles
github: ['bottlesdevs']
custom: ['https://usebottles.com/funding']


================================================
FILE: .github/ISSUE_TEMPLATE/bug.yml
================================================
name: Bug Report
description: File a bug report
title: "[Bug]: "
labels: ["bug", "triage"]
body:
  - type: markdown
    attributes:
      value: |
        ⚠️ Do not open issues for Windows executables not working in Bottles, if not installed through our installers. Bottles is a Wine prefix manager. If a Windows application doesn't work, it could be a Wine problem or a misconfiguration of the Wine prefix. Please visit [WineHQ](https://www.winehq.org) and [ProtonDB](https://www.protondb.com) for more information on the software you are trying to run. For additional support, use the [Programs](https://github.com/bottlesdevs/programs) repository.
        ⚠️ Do not open issues for bugs outside of [Bottles on Flathub](https://flathub.org/apps/details/com.usebottles.bottles), as we do not support packages from third-party repositories. If you can reproduce the bug(s) using Bottles on Flathub, then we will provide support, otherwise we will close the issue.

  - type: textarea
    id: what-happened
    attributes:
      label: Describe the bug
      description: Also tell us, what did you expect to happen?
      placeholder: A clear and concise description of what the bug is.
    validations:
      required: true

  - type: textarea
    id: to-reproduce
    attributes:
      label: To Reproduce
      description: Steps to reproduce the behavior
      placeholder: |
        1. Go to '...'
        2. Click on '...'
        3. Scroll down to '...'
        4. See error
    validations:
      required: true

  - type: markdown
    attributes:
      value: |
        ## System information

  - type: dropdown
    id: package
    attributes:
      label: Package
      description: |
        How did you install Bottles?

        If you use Bottles from your distribution package manager, please install [Bottles from Flathub](https://flathub.org/apps/details/com.usebottles.bottles) and try to reproduce the bug there. You might/will not get help if you don't run Bottles from Flathub.
      options:
        - Flatpak from Flathub
        - Flatpak from Flathub Beta
        - Flatpak from GitHub Artifacts
    validations:
      required: true

  - type: input
    id: distro
    attributes:
      label: Distribution
      description: In which Linux distribution did you encounter the bug?
      placeholder: Fedora 35, Arch Linux, Ubuntu 21.10..
    validations:
      required: true

  - type: textarea
    id: health-check
    attributes:
      label: Debugging Information
      description: |
        In Bottles, press the hamburger menu (`☰`) at the top right. Then press "About Bottles". Select "Troubleshooting", then "Debugging Information". Lastly, copy text and paste here.

        If you are unable to run Bottles, then you can retrieve information by running `flatpak run --command=bottles-cli com.usebottles.bottles info health-check` in the terminal.
      render: shell
      placeholder: |
        Display:
            X.org: true
            X.org (port): :99.0
            Wayland: false
        Graphics:
            vendors:
                nvidia:
                    vendor: nvidia
                    envs:
                        __NV_PRIME_RENDER_OFFLOAD: '1'
                        __GLX_VENDOR_LIBRARY_NAME: nvidia
                        __VK_LAYER_NV_optimus: NVIDIA_only
                    icd: /usr/lib/x86_64-linux-gnu/GL/vulkan/icd.d/nvidia_icd.json:/usr/lib/i386-linux-gnu/GL/vulkan/icd.d/nvidia_icd.json
                amd:
                    vendor: amd
                    envs:
                        DRI_PRIME: '1'
                    icd: /usr/lib/x86_64-linux-gnu/GL/vulkan/icd.d/radeon_icd.x86_64.json:/usr/lib/i386-linux-gnu/GL/vulkan/icd.d/radeon_icd.i686.json
            prime:
                integrated: *id001
                discrete: *id002
        Kernel:
            Type: Linux
            Version: 5.16.9-200.fc35.x86_64
        Distro:
            Name: GNOME
            Version: '"41 (Flatpak runtime)"'
        Tools:
            cabextract: true
            p7zip: true
            patool: true
            glibc_min: '2.33'
        Bottles_envs: null
    validations:
      required: true

  - type: textarea
    id: log
    attributes:
      label: Troubleshooting Logs
      description: If applicable, run `flatpak run com.usebottles.bottles`, reproduce the issue and paste the logs here.
      render: shell
      placeholder: |
        [Errno 2] No such file or directory: '/home/USER/.var/app/com.usebottles.bottles/data/bottles/bottles/Opera-GX__185/bottle.yml'  File "/app/share/bottles/bottles/frontend/utils/threading.py", line 61, in __target
            result = self.task_func(*args, **kwargs)

          File "/app/share/bottles/bottles/frontend/windows/main_window.py", line 194, in get_manager
            mng = Manager(window=window, repo_fn_update=repo_fn_update)

          File "/app/share/bottles/bottles/backend/managers/manager.py", line 135, in __init__
            times.update(self.checks(install_latest=False, first_run=True).data)

          File "/app/share/bottles/bottles/backend/managers/manager.py", line 189, in checks
            self.check_bottles()

          File "/app/share/bottles/bottles/backend/managers/manager.py", line 804, in check_bottles
            process_bottle(b)

          File "/app/share/bottles/bottles/backend/managers/manager.py", line 758, in process_bottle
            self.update_config(

          File "/app/share/bottles/bottles/backend/managers/manager.py", line 874, in update_config
            with open(os.path.join(bottle_path, "bottle.yml"), "w") as conf_file

  - type: textarea
    id: additional-context
    attributes:
      label: Additional context
      description: Add any other context about the problem here.
    validations:
      required: false


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: true
contact_links:
  - name: Documentation
    url: https://docs.usebottles.com/
    about: Before posting, check if the topic has already been covered by our documentation.


================================================
FILE: .github/ISSUE_TEMPLATE/feature-request.yml
================================================
name: Feature Request
description: Suggest an idea for this project
title: "[Request]: "
labels: ["Feature request"]

body:
  - type: textarea
    id: what-happened
    attributes:
      label: Tell us the problem or your need
      description: A clear and concise description of what the problem is.
      placeholder: Ex. I'm always frustrated when [...]
    validations:
      required: true

  - type: textarea
    id: your-solution
    attributes:
      label: Describe the solution you'd like
      description: A clear and concise description of what you want to happen.
      placeholder: To fix this, I would [...]
    validations:
      required: true

  - type: textarea
    id: other-solutions
    attributes:
      label: Other solutions?
      description: A clear and concise description of any alternative solutions or features you've considered.
    validations:
      required: false

  - type: textarea
    id: additional-context
    attributes:
      label: Additional context and references
      description: Add any other context or reference about the feature request here.
    validations:
      required: false


================================================
FILE: .github/ISSUE_TEMPLATE/feedback.md
================================================
---
name: General Feedback
about: Send your feedback, start a discussion, or ask a question to the developers.
labels: Feedback
---

!!! PLEASE DON'T OPEN ISSUES FOR PROGRAMS NOT RUNNING IN BOTTLES, USE THE programs REPOSITORY INSTEAD !!!


================================================
FILE: .github/ISSUE_TEMPLATE/mirror.yml
================================================
name: Network issue report
description: Report Network/Mirror issue to sysadmin
labels: ["Network Issue"]
body:
  - type: markdown
    id: introduction
    attributes:
      value: |
        📝 Please use this template while reporting a Network/Mirror issue and provide as much info as possible.

  - type: checkboxes
    id: prerequisites
    attributes:
      label: Prerequisites
      options:
        - label: |
            I am sure that this problem has NEVER been discussed in [other issues](https://github.com/bottlesdevs/Bottles/issues).
          required: true

  - type: textarea
    id: what_happened
    attributes:
      label: What happened
    validations:
      required: true

  - type: textarea
    id: expected_behavior
    attributes:
      label: What you expected to happen
    validations:
      required: true

  - type: textarea
    id: how_to_reproduce
    attributes:
      label: How to reproduce it
    validations:
      required: true

  - type: textarea
    id: ping_result
    attributes:
      label: Ping proxy.usebottles.com
      description: Please run `ping proxy.usebottles.com` in a terminal and send us the output.
      render: log
    validations:
      required: true

  - type: textarea
    id: dig_result
    attributes:
      label: Dig proxy.usebottles.com
      description: Please run `dig proxy.usebottles.com` in a terminal and send us the output.
      render: log
    validations:
      required: true

  - type: input
    id: isp
    attributes:
      label: Your Internet Service Provider (ISP) name

  - type: input
    id: area
    attributes:
      label: Your Country/Area

  - type: textarea
    id: others
    attributes:
      label: Anything else we need to know


================================================
FILE: .github/dependabot.yml
================================================
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates

version: 2
updates:
- package-ecosystem: github-actions
  directory: "/"
  schedule:
    interval: daily


================================================
FILE: .github/pull_request_template.md
================================================
# Description
Please include a summary of the change and which issue is fixed (if available).
Please also include relevant motivation and context.

Fixes #(issue)

## Type of change
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update

# How Has This Been Tested?
Please describe the tests that you ran to verify your changes.
Provide instructions so we can reproduce.
- [ ] Test A
- [ ] Test B


================================================
FILE: .github/workflows/build_flatpak.yml
================================================
on:
  push:
    branches: [main]
  pull_request:
name: Build Flatpak
jobs:
  flatpak:
    name: "build-packages"
    runs-on: ubuntu-latest
    container:
      image: bilelmoussaoui/flatpak-github-actions:gnome-47
      options: --privileged
    steps:
    - uses: actions/checkout@v4
    - uses: bilelmoussaoui/flatpak-github-actions/flatpak-builder@v6
      with:
        bundle: bottles.flatpak
        manifest-path: build-aux/com.usebottles.bottles.Devel.json
        cache-key: flatpak-builder-${{ github.sha }}


================================================
FILE: .github/workflows/close-issues.yml
================================================
name: close-issues

on:
  issues:
    types: [opened]

jobs:
  comment:
    runs-on: ubuntu-latest
    steps:
      - uses: actions-ecosystem/action-regex-match@v2
        id: regex-match
        with:
          text: ${{ github.event.issue.body }}
          regex: '[Vv]ersion.*:.*202\d.\d\d?.\d\d?'
      - if: ${{ steps.regex-match.outputs.match != '' }}
        name: Close Issue
        uses: peter-evans/close-issue@v3
        with:
          close-reason: not_planned
          comment: |
            It seems like you're using an old version of Bottles. Please upgrade to the version from Flathub [here](https://flathub.org/apps/details/com.usebottles.bottles), and try to reproduce the bug.


================================================
FILE: .github/workflows/pre-commit.yml
================================================
name: pre-commit

on:
  pull_request:
  push:
    branches: [main]

jobs:
  pre-commit:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - uses: actions/setup-python@v3
    - uses: pre-commit/action@v3.0.1


================================================
FILE: .github/workflows/update-manifest.yml
================================================
name: Update manifest

on:
  schedule:
    # Check for update every day at 07:11
    - cron:  '11 7 * * *'
  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:


env:
  PR_BRANCH: pr/ci-manifest/${{ github.ref_name }}
  FEDC_ARGS: --update --require-important-update --commit-only --never-fork "bottles-repository/build-aux/com.usebottles.bottles.Devel.json"
  UPDATE_PYTHON: false

jobs:
  update-manifest:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          path: "bottles-repository"
          ref: ${{ github.ref_name }}

      - uses: actions/setup-python@v5
        with:
          python-version: '3.x'
          cache: 'pip'

      - run: pip install setuptools pur req2flatpak pyyaml

      - name: Update requirements
        working-directory: "bottles-repository"
        shell: bash {0}
        run: |
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git config user.name "github-actions[bot]"
          pur -r requirements.txt
          pur -r requirements.dev.txt
          req2flatpak --requirements-file requirements.txt --yaml --target-platforms 312-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
              git add requirements.txt requirements.dev.txt com.usebottles.bottles.pypi-deps.yaml
              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
          echo "UPDATE_PYTHON=true" >> $GITHUB_ENV

      - uses: docker://ghcr.io/flathub/flatpak-external-data-checker:latest
        env:
          GIT_AUTHOR_NAME: github-actions[bot]
          GIT_COMMITTER_NAME: github-actions[bot]
          GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com
          GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com
          EMAIL: github-actions[bot]@users.noreply.github.com
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          args: ${{ env.FEDC_ARGS }}

      - name: Create PR if necessary
        working-directory: "bottles-repository"
        shell: bash {0}
        run: |
          git checkout -B ${{ env.PR_BRANCH }}
          git push -f --set-upstream origin ${{ env.PR_BRANCH }}
          git diff ${{ github.ref_name }} --exit-code build-aux/com.usebottles.bottles.Devel.json
          updated=$?
          if [ $updated -ne 0 ] || [ "${{ env.UPDATE_PYTHON }}" = true ]; then
            gh pr create --title ":robot:: Update manifest (important)" --body ":wrench: One or more modules marked as 'important' have been updated." --head ${{ env.PR_BRANCH }} --base ${{ github.ref_name }}
            exit 0
          fi
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}


================================================
FILE: .gitignore
================================================
.vscode/
.mypy_cache/
.pytest_cache/
/.project
/.pydevproject
/.settings
/.cproject
/.idea
.flatpak-builder/
/build
/build-dir
/mesonbuild

__pycache__
.coverage
/install dir
/work area

/meson-test-run.txt
/meson-test-run.xml
/meson-cross-test-run.txt
/meson-cross-test-run.xml

/.flatpak
/builddir

.DS_Store
*~
*.swp
packagecache
/MANIFEST
/dist
/meson.egg-info

/docs/built_docs
/docs/hotdoc-private*

*.pyc
/*venv*

.buildconfig

# Ignore AppImage build dirs
/AppDir
/appimage-builder-cache

# Ignore generated files
*.deb
*.dsc
*.changes
.build
*.buildinfo
*.tar.gz


# Ignore files generated during build
*debian/files
*debian/.*
*debian/com.usebottles.bottles*
*obj-x86_64-linux-gnu

# Ignore flatpak build dirs
/repo/
/flatpak/
.vscode/*
/.vscode/*

/build-flatpak.sh

/FVS*/

================================================
FILE: .gitmodules
================================================
[submodule "build-aux/req2flatpak"]
	path = build-aux/req2flatpak
	url = https://github.com/johannesjh/req2flatpak.git


================================================
FILE: .mailmap
================================================
Mirko Brombin <brombin94@gmail.com> <send@mirko.pm>
Mirko Brombin <brombin94@gmail.com> <brombin@mirko.pm>
Mirko Brombin <brombin94@gmail.com> <brombinmirko@gmail.com>
Mirko Brombin <brombin94@gmail.com> <mirko@pop-os.localdomain>
Mirko Brombin <brombin94@gmail.com> <mirko@rog.station>
Mirko Brombin <brombin94@gmail.com> <mirkobrombin@users.noreply.github.com>
Hari Rana <theevilskeleton@riseup.net> TheEvilSkeleton <theevilskeleton@riseup.net>
Kinsteen <me@kinsteen.fr> <pitiqui69@gmail.com>
jannuary <27908024+jannuary@users.noreply.github.com>
axtloss <rose@pinkro.se> <axtlos@tar.black>
axtloss <rose@pinkro.se> <axtlos@getcryst.al>
EmoonX <emoon@emoon.dev> <gustavo.chicato@usp.br>

================================================
FILE: .pre-commit-config.yaml
================================================
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
-   repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v5.0.0
    hooks:
    -   id: trailing-whitespace
    -   id: end-of-file-fixer
    -   id: check-yaml
    -   id: check-xml
    -   id: check-json
    -   id: pretty-format-json
        args: ["--autofix", "--no-sort-keys", "--indent", "4"]
    -   id: check-added-large-files

-   repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.8.2
    hooks:
    -   id: ruff
        args: [ "--fix" ]
    -   id: ruff-format

-   repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.13.0
    hooks:
    -   id: mypy
        args: ["--pretty"]
        additional_dependencies: ["pygobject-stubs", "types-PyYAML", "types-Markdown", "types-requests", "types-pycurl", "types-chardet", "pytest-stub", "types-orjson", "pathvalidate", "requirements-parser", "icoextract", "fvs", "patool", "git+https://gitlab.com/TheEvilSkeleton/vkbasalt-cli.git@main"]

-   repo: https://github.com/PyCQA/autoflake
    rev: v2.3.1
    hooks:
    -   id: autoflake


================================================
FILE: CODE_OF_CONDUCT.md
================================================
# GNOME Code of Conduct

Thank you for being a part of the GNOME project. We value your participation and want everyone to have an enjoyable and fulfilling experience. Accordingly, all participants are expected to follow this Code of Conduct, and to show respect, understanding, and consideration to one another. Thank you for helping make this a welcoming, friendly community for everyone.

## Scope

This Code of Conduct applies to all online GNOME community spaces, including, but not limited to:

 * Issue tracking systems - bugzilla.gnome.org
 * Documentation and tutorials - developer.gnome.org
 * Code repositories - git.gnome.org and gitlab.gnome.org
 * Mailing lists - mail.gnome.org
 * Wikis - wiki.gnome.org
 * Chat and forums - irc.gnome.org, discourse.gnome.org, GNOME Telegram channels, and GNOME groups and channels on Matrix.org (including bridges to GNOME IRC channels)
 * Community spaces hosted on gnome.org infrastructure
 * Any other channels or groups which exist in order to discuss GNOME project activities

Communication channels and private conversations that are normally out of scope may be considered in scope if a GNOME participant is being stalked or harassed. Social media conversations may be considered in-scope if the incident occurred under a GNOME event hashtag, or when an official GNOME account on social media is tagged, or within any other discussion about GNOME. The GNOME Foundation reserves the right to take actions against behaviors that happen in any context, if they are deemed to be relevant to the GNOME project and its participants.

All participants in GNOME online community spaces are subject to the Code of Conduct. This includes GNOME Foundation board members, corporate sponsors, and paid employees. This also includes volunteers, maintainers, leaders, contributors, contribution reviewers, issue reporters, GNOME users, and anyone participating in discussion in GNOME online spaces.

## Reporting an Incident

If you believe that someone is violating the Code of Conduct, or have
any other concerns, please [contact the Code of Conduct committee](https://wiki.gnome.org/Foundation/CodeOfConduct/ReporterGuide).

## Our Standards

The GNOME online community is dedicated to providing a positive experience for everyone, regardless of:

 * age
 * body size
 * caste
 * citizenship
 * disability
 * education
 * ethnicity
 * familial status
 * gender expression
 * gender identity
 * genetic information
 * immigration status
 * level of experience
 * nationality
 * personal appearance
 * pregnancy
 * race
 * religion
 * sex characteristics
 * sexual orientation
 * sexual identity
 * socio-economic status
 * tribe
 * veteran status

### Community Guidelines

Examples of behavior that contributes to creating a positive environment include:

 * **Be friendly.** Use welcoming and inclusive language.
 * **Be empathetic.** Be respectful of differing viewpoints and experiences.
 * **Be respectful.** When we disagree, we do so in a polite and constructive manner.
 * **Be considerate.** Remember that decisions are often a difficult choice between competing priorities. Focus on what is best for the community. Keep discussions around technology choices constructive and respectful.
 * **Be patient and generous.** If someone asks for help it is because they need it. When documentation is available that answers the question, politely point them to it. If the question is off-topic, suggest a more appropriate online space to seek help.
 * **Try to be concise.** Read the discussion before commenting in order to not repeat a point that has been made.

### Inappropriate Behavior

Community members asked to stop any inappropriate behavior are expected to comply immediately.

We want all participants in the GNOME community have the best possible experience they can. In order to be clear what that means, we've provided a list of examples of behaviors that are inappropriate for GNOME community spaces:

 * **Deliberate intimidation, stalking, or following.**
 * **Sustained disruption of online discussion, talks, or other events.** Sustained disruption of events, online discussions, or meetings, including talks and presentations, will not be tolerated. This includes 'Talking over' or 'heckling' event speakers or influencing crowd actions that cause hostility in event sessions. Sustained disruption also includes drinking alcohol to excess or using recreational drugs to excess, or pushing others to do so.
 * **Harassment of people who don't drink alcohol.** We do not tolerate derogatory comments about those who abstain from alcohol or other substances. We do not tolerate pushing people to drink, talking about their abstinence or preferences to others, or pressuring them to drink - physically or through jeering.
 * **Sexist, racist, homophobic, transphobic, ableist language or otherwise exclusionary language.** This includes deliberately referring to someone by a gender that they do not identify with, and/or questioning the legitimacy of an individual's gender identity. If you're unsure if a word is derogatory, don't use it. This also includes repeated subtle and/or indirect discrimination.
 * **Unwelcome sexual attention or behavior that contributes to a sexualized environment.** This includes sexualized comments, jokes or imagery in interactions, communications or presentation materials, as well as inappropriate touching, groping, or sexual advances. Sponsors should not use sexualized images, activities, or other material. Meetup organizing staff and other volunteer organizers should not use sexualized clothing/uniforms/costumes, or otherwise create a sexualized environment.
 * **Unwelcome physical contact.** This includes touching a person without permission, including sensitive areas such as their hair, pregnant stomach, mobility device (wheelchair, scooter, etc) or tattoos. This also includes physically blocking or intimidating another person. Physical contact or simulated physical contact (such as emojis like "kiss") without affirmative consent is not acceptable. This includes sharing or distribution of sexualized images or text.
 * **Violence or threats of violence.** Violence and threats of violence are not acceptable - online or offline. This includes incitement of violence toward any individual, including encouraging a person to commit self-harm. This also includes posting or threatening to post other people's personally identifying information ("doxxing") online.
 * **Influencing or encouraging inappropriate behavior.** If you influence or encourage another person to violate the Code of Conduct, you may face the same consequences as if you had violated the Code of Conduct.
 * **Possession of an offensive weapon at a GNOME event.** This includes anything deemed to be a weapon by the event organizers.

The GNOME community prioritizes marginalized people's safety over privileged people's comfort. The committee will not act on complaints regarding:

 * "Reverse"-isms, including "reverse racism," "reverse sexism," and "cisphobia"
 * Reasonable communication of boundaries, such as "leave me alone," "go away," or "I'm not discussing this with you."
 * Criticizing racist, sexist, cissexist, or otherwise oppressive behavior or assumptions
 * Communicating boundaries or criticizing oppressive behavior in a "tone" you don't find congenial

The examples listed above are not against the Code of Conduct. If you have questions about the above statements, please [read this document](https://github.com/sagesharp/code-of-conduct-template/blob/master/code-of-conduct/example-reversisms.md#supporting-diversity).

If a participant engages in behavior that violates this code of conduct, the GNOME Code of Conduct committee may take any action they deem appropriate. Examples of consequences are outlined in the [Committee Procedures Guide](https://wiki.gnome.org/Foundation/CodeOfConduct/CommitteeProcedures).

## Procedure for Handling Incidents

 * [Reporter Guide](https://wiki.gnome.org/Foundation/CodeOfConduct/ReporterGuide)

 * [Moderator Procedures](https://wiki.gnome.org/Foundation/CodeOfConduct/ModeratorProcedures)

 * [Committee Procedures Guide](https://wiki.gnome.org/Foundation/CodeOfConduct/CommitteeProcedures)

## License

The GNOME Online Code of Conduct is licensed under a [Creative Commons Attribution Share-Alike 3.0 Unported License](http://creativecommons.org/licenses/by-sa/3.0/)

![Creative Commons License](http://i.creativecommons.org/l/by-sa/3.0/88x31.png)

## Attribution

The GNOME Online Code of Conduct was forked from the example policy from the [Geek Feminism wiki, created by the Ada Initiative and other volunteers](http://geekfeminism.wikia.com/wiki/Conference_anti-harassment/Policy), which is under a Creative Commons Zero license.

Additional language was incorporated and modified from the following Codes of Conduct:

 * [Citizen Code of Conduct](http://citizencodeofconduct.org/) is licensed [Creative Commons Attribution Share-Alike 3.0 Unported License](http://creativecommons.org/licenses/by-sa/3.0/).
 * [Code of Conduct template](https://github.com/sagesharp/code-of-conduct-template/) is licensed [Creative Commons Attribution Share-Alike 3.0 Unported License](http://creativecommons.org/licenses/by-sa/3.0/) by [Otter Tech](https://otter.technology/code-of-conduct-training)
 * [Contributor Covenant version 1.4](https://www.contributor-covenant.org/version/1/4/code-of-conduct) (licensed [CC BY 4.0](https://github.com/EthicalSource/contributor_covenant/blob/release/LICENSE.md))
 * [Data Carpentry Code of Conduct](https://docs.carpentries.org/topic_folders/policies/index_coc.html) is licensed [Creative Commons Attribution 4.0 License](https://creativecommons.org/licenses/by/4.0/)
 * [Django Project Code of Conduct](https://www.djangoproject.com/conduct/) is licensed under a [Creative Commons Attribution 3.0 Unported License](http://creativecommons.org/licenses/by/3.0/)
 * [Fedora Code of Conduct](http://fedoraproject.org/code-of-conduct)
 * [Geek Feminism Anti-harassment Policy](http://geekfeminism.wikia.com/wiki/Conference_anti-harassment/Policy) which is under a [Creative Commons Zero license](https://creativecommons.org/publicdomain/zero/1.0/)
 * [Previous GNOME Foundation Code of Conduct](https://wiki.gnome.org/action/recall/Foundation/CodeOfConduct/Old)
 * [LGBTQ in Technology Slack Code of Conduct](https://lgbtq.technology/coc.html) licensed [Creative Commons Zero](https://creativecommons.org/publicdomain/zero/1.0/)
 * [Mozilla Community Participation Guidelines](https://www.mozilla.org/en-US/about/governance/policies/participation/) is licensed [Creative Commons Attribution-ShareAlike 3.0 Unported License](https://creativecommons.org/licenses/by-sa/3.0/).
 * [Python Mentors Code of Conduct](http://pythonmentors.com/)
 * [Speak Up! Community Code of Conduct](http://web.archive.org/web/20141109123859/http://speakup.io/coc.html), licensed under a [Creative Commons Attribution 3.0 Unported License](http://creativecommons.org/licenses/by/3.0/)


================================================
FILE: CODING_GUIDE.md
================================================
## Build & Run locally

### use flatpak

#### Build & install

```bash
flatpak-builder --install --user --force-clean ./.flatpak-builder/out ./build-aux/com.usebottles.bottles.Devel.json
```

#### Run

```bash
flatpak run com.usebottles.bottles.Devel
```

#### Uninstall devel version

```bash
flatpak uninstall com.usebottles.bottles.Devel
```

## Unit Test

### run all tests

```bash
pytest .
```

## Dependencies

Regenerate PYPI dependency manifest when requirements.txt changed

```bash
python ./build-aux/flatpak-pip-generator.py --runtime org.gnome.Sdk -r requirements.txt -o com.usebottles.bottles.pypi-deps --yaml
```

## I18n files

### `po/POTFILES`

List of source files containing translatable strings.
Regenerate this file when you added/moved/removed/renamed files
that contains translatable strings.

```bash
cat > po/POTFILES <<EOF
# List of source files containing translatable strings.
# Please keep this file sorted alphabetically.
EOF
grep -rlP "_\(['\"]" bottles | sort >> po/POTFILES
cat >> po/POTFILES <<EOF
data/com.usebottles.bottles.desktop.in.in
data/com.usebottles.bottles.gschema.xml
data/com.usebottles.bottles.metainfo.xml.in.in
EOF
```

### `po/bottles.pot` and `po/*.po`

We have a main pot file, which is template for other `.po` files
And for each language listed in `po/LINGUAS` we have a corresponding `.po` file
Regenerate these files when any translatable string added/changed/removed

```bash
# make sure you have `meson` and `blueprint-compiler` installed
meson setup /tmp/i18n-build
meson compile -C /tmp/i18n-build/ bottles-pot
meson compile -C /tmp/i18n-build/ bottles-update-po
```


================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to Bottles
First off, thanks for taking the time to contribute :heart:!

## Found a Problem?
Before reporting a problem, be it a bug, design or others, we assume you have made sure that:
1. the [Bottles wiki](https://github.com/bottlesdevs/Bottles/wiki) does not cover your problem
1. the problem has not been reported in the [issue tracker](https://github.com/bottlesdevs/Bottles/issues)
1. the problem is reproducible with [Bottles from Flathub](https://flathub.org/apps/details/com.usebottles.bottles)

If all apply, then please consider opening a [new issue](https://github.com/bottlesdevs/Bottles/issues/new/choose).

## Want to Submit Code?
You can submit code by [forking](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/about-forks) this project, editing the desired code and finally submitting a [pull request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request).

To build Bottles, refer to the [Building](README.md#Building) instructions.

### Having Trouble Understanding the Source Code?
If you want to inquire to understand the code base, you can contact us via [Discord](https://discord.com/invite/wF4JAdYrTR). We'd love to help you out!

## Want to Translate Bottles?
You can help Bottles speak your language by translating on [Weblate](https://hosted.weblate.org/projects/bottles).

## Want to Donate or Sponsor Bottles?
You can financially support Bottles through [donations and sponsorships](https://usebottles.com/funding).


================================================
FILE: COPYING.md
================================================
### GNU GENERAL PUBLIC LICENSE

Version 3, 29 June 2007

Copyright (C) 2007 Free Software Foundation, Inc.
<https://fsf.org/>

Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.

### Preamble

The GNU General Public License is a free, copyleft license for
software and other kinds of works.

The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom
to share and change all versions of a program--to make sure it remains
free software for all its users. We, the Free Software Foundation, use
the GNU General Public License for most of our software; it applies
also to any other work released this way by its authors. You can apply
it to your programs, too.

When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.

To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you
have certain responsibilities if you distribute copies of the
software, or if you modify it: responsibilities to respect the freedom
of others.

For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.

Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.

For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.

Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the
manufacturer can do so. This is fundamentally incompatible with the
aim of protecting users' freedom to change the software. The
systematic pattern of such abuse occurs in the area of products for
individuals to use, which is precisely where it is most unacceptable.
Therefore, we have designed this version of the GPL to prohibit the
practice for those products. If such problems arise substantially in
other domains, we stand ready to extend this provision to those
domains in future versions of the GPL, as needed to protect the
freedom of users.

Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish
to avoid the special danger that patents applied to a free program
could make it effectively proprietary. To prevent this, the GPL
assures that patents cannot be used to render the program non-free.

The precise terms and conditions for copying, distribution and
modification follow.

### TERMS AND CONDITIONS

#### 0. Definitions.

"This License" refers to version 3 of the GNU General Public License.

"Copyright" also means copyright-like laws that apply to other kinds
of works, such as semiconductor masks.

"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.

To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of
an exact copy. The resulting work is called a "modified version" of
the earlier work or a work "based on" the earlier work.

A "covered work" means either the unmodified Program or a work based
on the Program.

To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.

To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user
through a computer network, with no transfer of a copy, is not
conveying.

An interactive user interface displays "Appropriate Legal Notices" to
the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.

#### 1. Source Code.

The "source code" for a work means the preferred form of the work for
making modifications to it. "Object code" means any non-source form of
a work.

A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.

The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.

The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.

The Corresponding Source need not include anything that users can
regenerate automatically from other parts of the Corresponding Source.

The Corresponding Source for a work in source code form is that same
work.

#### 2. Basic Permissions.

All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.

You may make, run and propagate covered works that you do not convey,
without conditions so long as your license otherwise remains in force.
You may convey covered works to others for the sole purpose of having
them make modifications exclusively for you, or provide you with
facilities for running those works, provided that you comply with the
terms of this License in conveying all material for which you do not
control copyright. Those thus making or running the covered works for
you must do so exclusively on your behalf, under your direction and
control, on terms that prohibit them from making any copies of your
copyrighted material outside their relationship with you.

Conveying under any other circumstances is permitted solely under the
conditions stated below. Sublicensing is not allowed; section 10 makes
it unnecessary.

#### 3. Protecting Users' Legal Rights From Anti-Circumvention Law.

No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.

When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such
circumvention is effected by exercising rights under this License with
respect to the covered work, and you disclaim any intention to limit
operation or modification of the work as a means of enforcing, against
the work's users, your or third parties' legal rights to forbid
circumvention of technological measures.

#### 4. Conveying Verbatim Copies.

You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.

You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.

#### 5. Conveying Modified Source Versions.

You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these
conditions:

-   a) The work must carry prominent notices stating that you modified
    it, and giving a relevant date.
-   b) The work must carry prominent notices stating that it is
    released under this License and any conditions added under
    section 7. This requirement modifies the requirement in section 4
    to "keep intact all notices".
-   c) You must license the entire work, as a whole, under this
    License to anyone who comes into possession of a copy. This
    License will therefore apply, along with any applicable section 7
    additional terms, to the whole of the work, and all its parts,
    regardless of how they are packaged. This License gives no
    permission to license the work in any other way, but it does not
    invalidate such permission if you have separately received it.
-   d) If the work has interactive user interfaces, each must display
    Appropriate Legal Notices; however, if the Program has interactive
    interfaces that do not display Appropriate Legal Notices, your
    work need not make them do so.

A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.

#### 6. Conveying Non-Source Forms.

You may convey a covered work in object code form under the terms of
sections 4 and 5, provided that you also convey the machine-readable
Corresponding Source under the terms of this License, in one of these
ways:

-   a) Convey the object code in, or embodied in, a physical product
    (including a physical distribution medium), accompanied by the
    Corresponding Source fixed on a durable physical medium
    customarily used for software interchange.
-   b) Convey the object code in, or embodied in, a physical product
    (including a physical distribution medium), accompanied by a
    written offer, valid for at least three years and valid for as
    long as you offer spare parts or customer support for that product
    model, to give anyone who possesses the object code either (1) a
    copy of the Corresponding Source for all the software in the
    product that is covered by this License, on a durable physical
    medium customarily used for software interchange, for a price no
    more than your reasonable cost of physically performing this
    conveying of source, or (2) access to copy the Corresponding
    Source from a network server at no charge.
-   c) Convey individual copies of the object code with a copy of the
    written offer to provide the Corresponding Source. This
    alternative is allowed only occasionally and noncommercially, and
    only if you received the object code with such an offer, in accord
    with subsection 6b.
-   d) Convey the object code by offering access from a designated
    place (gratis or for a charge), and offer equivalent access to the
    Corresponding Source in the same way through the same place at no
    further charge. You need not require recipients to copy the
    Corresponding Source along with the object code. If the place to
    copy the object code is a network server, the Corresponding Source
    may be on a different server (operated by you or a third party)
    that supports equivalent copying facilities, provided you maintain
    clear directions next to the object code saying where to find the
    Corresponding Source. Regardless of what server hosts the
    Corresponding Source, you remain obligated to ensure that it is
    available for as long as needed to satisfy these requirements.
-   e) Convey the object code using peer-to-peer transmission,
    provided you inform other peers where the object code and
    Corresponding Source of the work are being offered to the general
    public at no charge under subsection 6d.

A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.

A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal,
family, or household purposes, or (2) anything designed or sold for
incorporation into a dwelling. In determining whether a product is a
consumer product, doubtful cases shall be resolved in favor of
coverage. For a particular product received by a particular user,
"normally used" refers to a typical or common use of that class of
product, regardless of the status of the particular user or of the way
in which the particular user actually uses, or expects or is expected
to use, the product. A product is a consumer product regardless of
whether the product has substantial commercial, industrial or
non-consumer uses, unless such uses represent the only significant
mode of use of the product.

"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to
install and execute modified versions of a covered work in that User
Product from a modified version of its Corresponding Source. The
information must suffice to ensure that the continued functioning of
the modified object code is in no case prevented or interfered with
solely because modification has been made.

If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).

The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or
updates for a work that has been modified or installed by the
recipient, or for the User Product in which it has been modified or
installed. Access to a network may be denied when the modification
itself materially and adversely affects the operation of the network
or violates the rules and protocols for communication across the
network.

Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.

#### 7. Additional Terms.

"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.

When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.

Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders
of that material) supplement the terms of this License with terms:

-   a) Disclaiming warranty or limiting liability differently from the
    terms of sections 15 and 16 of this License; or
-   b) Requiring preservation of specified reasonable legal notices or
    author attributions in that material or in the Appropriate Legal
    Notices displayed by works containing it; or
-   c) Prohibiting misrepresentation of the origin of that material,
    or requiring that modified versions of such material be marked in
    reasonable ways as different from the original version; or
-   d) Limiting the use for publicity purposes of names of licensors
    or authors of the material; or
-   e) Declining to grant rights under trademark law for use of some
    trade names, trademarks, or service marks; or
-   f) Requiring indemnification of licensors and authors of that
    material by anyone who conveys the material (or modified versions
    of it) with contractual assumptions of liability to the recipient,
    for any liability that these contractual assumptions directly
    impose on those licensors and authors.

All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.

If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.

Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions; the
above requirements apply either way.

#### 8. Termination.

You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).

However, if you cease all violation of this License, then your license
from a particular copyright holder is reinstated (a) provisionally,
unless and until the copyright holder explicitly and finally
terminates your license, and (b) permanently, if the copyright holder
fails to notify you of the violation by some reasonable means prior to
60 days after the cessation.

Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.

Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.

#### 9. Acceptance Not Required for Having Copies.

You are not required to accept this License in order to receive or run
a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.

#### 10. Automatic Licensing of Downstream Recipients.

Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.

An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.

You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.

#### 11. Patents.

A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".

A contributor's "essential patent claims" are all patent claims owned
or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.

Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.

In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.

If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.

If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.

A patent license is "discriminatory" if it does not include within the
scope of its coverage, prohibits the exercise of, or is conditioned on
the non-exercise of one or more of the rights that are specifically
granted under this License. You may not convey a covered work if you
are a party to an arrangement with a third party that is in the
business of distributing software, under which you make payment to the
third party based on the extent of your activity of conveying the
work, and under which the third party grants, to any of the parties
who would receive the covered work from you, a discriminatory patent
license (a) in connection with copies of the covered work conveyed by
you (or copies made from those copies), or (b) primarily for and in
connection with specific products or compilations that contain the
covered work, unless you entered into that arrangement, or that patent
license was granted, prior to 28 March 2007.

Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.

#### 12. No Surrender of Others' Freedom.

If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under
this License and any other pertinent obligations, then as a
consequence you may not convey it at all. For example, if you agree to
terms that obligate you to collect a royalty for further conveying
from those to whom you convey the Program, the only way you could
satisfy both those terms and this License would be to refrain entirely
from conveying the Program.

#### 13. Use with the GNU Affero General Public License.

Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.

#### 14. Revised Versions of this License.

The Free Software Foundation may publish revised and/or new versions
of the GNU General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in
detail to address new problems or concerns.

Each version is given a distinguishing version number. If the Program
specifies that a certain numbered version of the GNU General Public
License "or any later version" applies to it, you have the option of
following the terms and conditions either of that numbered version or
of any later version published by the Free Software Foundation. If the
Program does not specify a version number of the GNU General Public
License, you may choose any version ever published by the Free
Software Foundation.

If the Program specifies that a proxy can decide which future versions
of the GNU General Public License can be used, that proxy's public
statement of acceptance of a version permanently authorizes you to
choose that version for the Program.

Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.

#### 15. Disclaimer of Warranty.

THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT
WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND
PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE
DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
CORRECTION.

#### 16. Limitation of Liability.

IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR
CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES
ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT
NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR
LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM
TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER
PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

#### 17. Interpretation of Sections 15 and 16.

If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.

END OF TERMS AND CONDITIONS

### How to Apply These Terms to Your New Programs

If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these
terms.

To do so, attach the following notices to the program. It is safest to
attach them to the start of each source file to most effectively state
the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.

        <one line to give the program's name and a brief idea of what it does.>
        Copyright (C) <year>  <name of author>

        This program is free software: you can redistribute it and/or modify
        it under the terms of the GNU General Public License as published by
        the Free Software Foundation, either version 3 of the License, or
        (at your option) any later version.

        This program is distributed in the hope that it will be useful,
        but WITHOUT ANY WARRANTY; without even the implied warranty of
        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
        GNU General Public License for more details.

        You should have received a copy of the GNU General Public License
        along with this program.  If not, see <https://www.gnu.org/licenses/>.

Also add information on how to contact you by electronic and paper
mail.

If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:

        <program>  Copyright (C) <year>  <name of author>
        This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
        This is free software, and you are welcome to redistribute it
        under certain conditions; type `show c' for details.

The hypothetical commands \`show w' and \`show c' should show the
appropriate parts of the General Public License. Of course, your
program's commands might be different; for a GUI interface, you would
use an "about box".

You should also get your employer (if you work as a programmer) or
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. For more information on this, and how to apply and follow
the GNU GPL, see <https://www.gnu.org/licenses/>.

The GNU General Public License does not permit incorporating your
program into proprietary programs. If your program is a subroutine
library, you may consider it more useful to permit linking proprietary
applications with the library. If this is what you want to do, use the
GNU Lesser General Public License instead of this License. But first,
please read <https://www.gnu.org/licenses/why-not-lgpl.html>.


================================================
FILE: README.md
================================================
<div align="center">
  <img src="https://raw.githubusercontent.com/bottlesdevs/Bottles/main/data/icons/hicolor/scalable/apps/com.usebottles.bottles.svg" width="64">
  <h1 align="center">Bottles</h1>
  <p align="center">Run Windows Software on Linux</p>
</div>

<br/>

<div align="center">
  <a href="https://flathub.org/apps/com.usebottles.bottles">
    <img alt="Flathub" src="https://img.shields.io/flathub/downloads/com.usebottles.bottles" />
  </a>
  <a href="https://hosted.weblate.org/engage/bottles">
    <img src="https://hosted.weblate.org/widgets/bottles/-/bottles/svg-badge.svg" />
  </a>
  <a href="https://www.codefactor.io/repository/github/bottlesdevs/bottles/overview/main">
    <img src="https://www.codefactor.io/repository/github/bottlesdevs/bottles/badge/main" />
  </a>
  <a href="https://github.com/bottlesdevs/Bottles/blob/main/LICENSE">
    <img src="https://img.shields.io/badge/License-GPL--3.0-blue.svg">
  </a>
  <br>
  <a href="https://stopthemingmy.app" title="Please do not theme this app">
    <img src="https://stopthemingmy.app/badge.svg">
  </a>

  <hr />

  <a href="https://docs.usebottles.com">Documentation</a> ·
  <a href="https://github.com/orgs/bottlesdevs/discussions">Forums</a> ·
  <a href="https://discord.gg/wF4JAdYrTR">Discord</a> ·
  <a href="https://usebottles.com/funding">Funding</a>
</div>

<br/>

![Bottles Dark](docs/screenshot-dark.png#gh-dark-mode-only)![Bottles Light](docs/screenshot-light.png#gh-light-mode-only)

## Installation

<a href='https://flathub.org/apps/com.usebottles.bottles'><img width='240' alt='Download on Flathub' src='https://flathub.org/assets/badges/flathub-badge-en.png'/></a>

## Contributing

Refer to the [Contributing](CONTRIBUTING.md) page.

## Building

⚠️ Be sure to backup all your data before testing experimental builds of Bottles!

There are two methods to build Bottles. The first and longer method is using `org.flatpak.Builder`, and the second but shorter method is building directly.

### org.flatpak.Builder

1. Install [`org.flatpak.Builder`](https://github.com/flathub/org.flatpak.Builder) from Flathub
1. Clone `https://github.com/bottlesdevs/Bottles.git` (or your fork)
1. Run `flatpak run org.flatpak.Builder --install --install-deps-from=flathub --default-branch=master --force-clean build-dir build-aux/com.usebottles.bottles.Devel.json` in the terminal from the root of the repository (use `--user` if necessary)
1. Run `flatpak run com.usebottles.bottles.Devel` to launch it

### Meson

Since Bottles is primarily and officially distributed as a Flatpak, we only provide instructions to directly build it inside a Flatpak environment:

1. Download and install the latest build of Bottles: [bottles-x86_64.zip](https://nightly.link/bottlesdevs/Bottles/workflows/build_flatpak/main/bottles-x86_64.zip). Unzip it, and run `flatpak install bottles.flatpak` (use `--user` if necessary)
2. Run `flatpak run -d --filesystem=$PWD --command=bash com.usebottles.bottles.Devel` from the root of the repository, followed by `./build-aux/install.sh`. This will build Bottles and install it under the `build/` directory.
3. Run `./build/bin/bottles` to launch Bottles

Due to GNOME Builder limitations, Builder cannot build Bottles for the time being; see [GNOME/gnome-builder#2061](https://gitlab.gnome.org/GNOME/gnome-builder/-/issues/2061) for more context. This is the best workaround we can provide.

## Code of Conduct
This project follows the [GNOME Code of Conduct](https://wiki.gnome.org/Foundation/CodeOfConduct). You are expected to follow it in all Bottles spaces, such as this repository, the project's social media, messenger chats and forums. Bigotry and harassment will not be tolerated.

## Sponsors
<a href="https://www.jetbrains.com/?from=bottles"><img height="55" src="https://unifiedban.solutions/static/images/jetbrains-logos/jetbrains.png" /></a>&nbsp;&nbsp;&nbsp;
<a href="https://www.gitbook.com/?ref=bottles"><img height="55" src="https://www.gitbook.com/cdn-cgi/image/height=55,fit=contain,dpr=1,format=auto/https%3A%2F%2F2775338190-files.gitbook.io%2F~%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FNkEGS7hzeqa35sMXQZ4X%252Flogo%252FTO5E3RjWKeaJmYYWMGWV%252Fspaces_gitbook_avatar-rectangle.png%3Falt%3Dmedia%26token%3Da34e957e-f044-4bee-abee-23946d2e9cfb" /></a>&nbsp;&nbsp;&nbsp;
<a href="https://www.linode.com/?from=bottles"><img height="48" src="https://usebottles.com/uploads/linode-brand.png" /></a>&nbsp;&nbsp;&nbsp;
<a href="https://appwrite.io?from=bottles"><img height="48" src="https://usebottles.com/uploads/built-with-appwrite.svg" /></a>
<a href="https://hyperbit.it?from=bottles"><img height="48" src="https://hyperbit.it-mil-1.linodeobjects.com/assets/full_dark_logo/HyperBit_Dark_Extended_Logo.png"/></a>


================================================
FILE: VERSION
================================================
62.0

================================================
FILE: VERSION_UPDATE.md
================================================
### Paths to be updated
- VERSION
- data/com.usebottles.metainfo.xml.in
- meson.build


================================================
FILE: bottles/__init__.py
================================================


================================================
FILE: bottles/backend/__init__.py
================================================


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

import os
import shlex
import shutil
import subprocess
from typing import Optional

from bottles.backend.logger import Logger

logging = Logger()


class CabExtract:
    """
    This class is used to extract a Windows cabinet file.
    It takes the cabinet file path and the destination name as input. Then it
    extracts the file in a new directory with the input name under the Bottles'
    temp directory.
    """

    requirements: bool = False
    path: str
    name: str
    files: list
    destination: str

    def __init__(self):
        self.cabextract_bin = shutil.which("cabextract")

    def run(
        self,
        path: str,
        name: str = "",
        files: Optional[list] = None,
        destination: str = "",
    ):
        if files is None:
            files = []

        self.path = path
        self.name = name
        self.files = files
        self.destination = shlex.quote(destination)
        self.name = self.name.replace(".", "_")

        if not self.__checks():
            return False
        return self.__extract()

    def __checks(self):
        if not os.path.exists(self.path) and "*" not in self.path:
            logging.error(f"Cab file {self.path} not found")
            return False

        return True

    def __extract(self) -> bool:
        if not os.path.exists(self.destination):
            os.makedirs(self.destination)

        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))

                    command = [
                        self.cabextract_bin,
                        f"-F '*{file}*'",
                        f"-d {self.destination}",
                        f"-q {self.path}",
                    ]
                    command = " ".join(command)
                    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}",
                            )
            else:
                command_list = [
                    self.cabextract_bin,
                    f"-d {self.destination}",
                    f"-q {self.path}",
                ]
                command = " ".join(command_list)
                subprocess.Popen(command, shell=True).communicate()

            logging.info(f"Cabinet {self.name} extracted successfully")
            return True
        except Exception as exception:
            logging.error(f"Error while extracting cab file {self.path}:\n{exception}")

        return False


================================================
FILE: bottles/backend/diff.py
================================================
import os
import hashlib


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

    @staticmethod
    def hashify(path: str) -> dict:
        """
        Hash (SHA-1) all files in a directory and return
        them in a dictionary. Here we use SHA-1 instead of
        better ones like SHA-256 because we only need to
        compare the file hashes, it's faster, and it's
        not a security risk.
        """
        _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):
            dirs[:] = [d for d in dirs if d not in Diff.__ignored]
            for f in files:
                if f in Diff.__ignored:
                    continue
                with open(os.path.join(root, f), "rb") as fr:
                    _hash = hashlib.sha1(fr.read()).hexdigest()

                _key = os.path.join(root, f)
                _key = _key.replace(path, "")
                _files[_key] = _hash

        return _files

    @staticmethod
    def file_hashify(path: str) -> str:
        """Hash (SHA-1) a file and return it."""
        with open(path, "rb") as fr:
            _hash = hashlib.sha1(fr.read()).hexdigest()

        return _hash

    @staticmethod
    def compare(parent: dict, child: dict) -> dict:
        """
        Compare two hashes dictionaries and return the
        differences (added, removed, changed).
        """

        added = []
        changed = []
        removed = [f for f in parent if f not in child]

        for f in child:
            if f not in parent:
                added.append(f)
            elif parent[f] != child[f]:
                changed.append(f)

        return {"added": added, "removed": removed, "changed": changed}


================================================
FILE: bottles/backend/dlls/__init__.py
================================================


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

import os
import shutil
from abc import abstractmethod
from copy import deepcopy

from bottles.backend.logger import Logger
from bottles.backend.models.config import BottleConfig
from bottles.backend.models.enum import Arch
from bottles.backend.utils.manager import ManagerUtils
from bottles.backend.wine.reg import Reg

logging = Logger()


class DLLComponent:
    base_path: str
    dlls: dict = {}
    checked_dlls: dict = {}
    version: str = ""

    def __init__(self, version: str):
        self.version = version
        self.base_path = self.get_base_path(version)
        self.check()

    @staticmethod
    @abstractmethod
    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...'
            )
            return False

        for path in self.dlls:
            _path = os.path.join(self.base_path, path)
            if not os.path.exists(_path):
                del found[path]
                continue
            for dll in self.dlls[path]:
                _dll = os.path.join(_path, dll)
                if not os.path.exists(_dll):
                    found[path].remove(dll)

        if len(found) == 0:
            return False

        self.checked_dlls = found
        return True

    def install(self, config: BottleConfig, overrides_only: bool = False, exclude=None):
        dll_in = []
        bundle = {"HKEY_CURRENT_USER\\Software\\Wine\\DllOverrides": []}
        reg = Reg(config)

        if exclude is None:
            exclude = []

        if None in self.checked_dlls:
            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]
                    if overrides_only:
                        dll_in.append(dll_name)
                    else:
                        if self.__install_dll(config, path, dll, False):
                            dll_in.append(dll_name)

        for dll in dll_in:
            bundle["HKEY_CURRENT_USER\\Software\\Wine\\DllOverrides"].append(
                {"value": dll, "data": "native,builtin"}
            )

        reg.import_bundle(bundle)

    def uninstall(self, config: BottleConfig, exclude=None):
        reg = Reg(config)
        dll_in = []
        bundle = {"HKEY_CURRENT_USER\\Software\\Wine\\DllOverrides": []}

        if exclude is None:
            exclude = []

        if None in self.dlls:
            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]
                    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": "-"}
            )

        reg.import_bundle(bundle)

    @staticmethod
    def __get_sys_path(config: BottleConfig, path: str) -> str:
        if config.Arch == Arch.WIN32:
            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/")
            ):
                return "system32"
            if path in ["x32", "x86"]:
                return "syswow64"
        return ""

    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)
        path = self.__get_sys_path(config, path)

        if path != "":
            target = os.path.join(bottle, path, dll_name)
        else:
            target = None

        print(f"{source} -> {target}")

        if target is not None:
            if not remove:
                if os.path.exists(target) and not os.path.exists(f"{target}.bck"):
                    shutil.copy(target, f"{target}.bck")
                try:
                    shutil.copyfile(source, target)
                except FileNotFoundError:
                    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):
        return self.__install_dll(config, path, dll, remove=True)


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

from bottles.backend.dlls.dll import DLLComponent
from bottles.backend.utils.manager import ManagerUtils


class DXVKComponent(DLLComponent):
    dlls = {
        "x32": ["d3d8.dll", "d3d9.dll", "d3d10core.dll", "d3d11.dll", "dxgi.dll"],
        "x64": ["d3d8.dll", "d3d9.dll", "d3d10core.dll", "d3d11.dll", "dxgi.dll"],
    }

    @staticmethod
    def get_override_keys() -> str:
        return "d3d8,d3d9,d3d10core,d3d11,dxgi"

    @staticmethod
    def get_base_path(version: str) -> str:
        return ManagerUtils.get_dxvk_path(version)


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

from bottles.backend.dlls.dll import DLLComponent
from bottles.backend.utils.manager import ManagerUtils


class LatencyFleXComponent(DLLComponent):
    dlls = {
        "wine/usr/lib/wine/x86_64-windows": [
            "latencyflex_layer.dll",
            "latencyflex_wine.dll",
        ]
    }

    @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)


================================================
FILE: bottles/backend/dlls/meson.build
================================================
pkgdatadir = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name())
dllsdir = join_paths(pkgdatadir, 'bottles/backend/dlls')

bottles_sources = [
  '__init__.py',
  'dll.py',
  'dxvk.py',
  'vkd3d.py',
  'nvapi.py',
  'latencyflex.py',
]

install_data(bottles_sources, install_dir: dllsdir)


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

import hashlib
import os

from bottles.backend.dlls.dll import DLLComponent
from bottles.backend.logger import Logger
from bottles.backend.models.config import BottleConfig
from bottles.backend.utils.manager import ManagerUtils
from bottles.backend.utils.nvidia import get_nvidia_dll_path

logging = Logger()


class NVAPIComponent(DLLComponent):
    dlls = {
        "x32": ["nvapi.dll"],
        "x64": ["nvapi64.dll"],
        get_nvidia_dll_path(): ["nvngx.dll", "_nvngx.dll"],
    }

    @staticmethod
    def get_override_keys() -> str:
        # NOTE: Bottles does not override (_)nvngx
        return "nvapi,nvapi64"

    @staticmethod
    def get_base_path(version: str) -> str:
        return ManagerUtils.get_nvapi_path(version)

    @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:
                for chunk in iter(lambda: f.read(4096), b""):
                    hash_md5.update(chunk)
            return hash_md5.hexdigest()

        nvngx_path_bottle = os.path.join(bottle_path, "drive_c", "windows", "system32")
        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!"
            )
            return

        # Reinstall nvngx if not present (acts as migration for this new patch)
        if not os.path.exists(os.path.join(nvngx_path_bottle, "nvngx.dll")):
            NVAPIComponent(bottle_config.NVAPI).install(bottle_config)
            return

        if not os.path.exists(os.path.join(nvngx_path_bottle, "_nvngx.dll")):
            NVAPIComponent(bottle_config.NVAPI).install(bottle_config)
            return

        # 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
        nvidia_dll_path = get_nvidia_dll_path()
        if nvidia_dll_path is not None:
            if md5sum(os.path.join(nvngx_path_bottle, "nvngx.dll")) != md5sum(
                os.path.join(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(nvidia_dll_path, "_nvngx.dll")
            ):
                NVAPIComponent(bottle_config.NVAPI).install(bottle_config)
                return


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

from bottles.backend.dlls.dll import DLLComponent
from bottles.backend.utils.manager import ManagerUtils


class VKD3DComponent(DLLComponent):
    dlls = {
        "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)


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

import os
import shutil
import sys
import time
from contextlib import suppress
from threading import Event
from typing import Optional

import requests

from bottles.backend.logger import Logger
from bottles.backend.models.result import Result
from bottles.backend.state import Status, TaskStreamUpdateHandler
from bottles.backend.utils.file import FileUtils

logging = Logger()


class DownloadCancelled(Exception):
    """Raised when a download operation is cancelled."""


class Downloader:
    """
    Download a resource from a given URL. It shows and update a progress
    bar while downloading but can also be used to update external progress
    bars using the func parameter.
    """

    def __init__(
        self,
        url: str,
        file: str,
        update_func: Optional[TaskStreamUpdateHandler] = None,
        cancel_event: Optional[Event] = None,
    ):
        self.start_time = None
        self.url = url
        self.file = file
        self.update_func = update_func
        self.cancel_event = cancel_event

    def download(self) -> Result:
        """Start the download."""
        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
                response = requests.get(self.url, stream=True, headers=headers)
                total_size = int(response.headers.get("content-length", 0))
                received_size = 0

                if total_size != 0:
                    for data in response.iter_content(1024 * 1024):  # 1MB buffer
                        if self.cancel_event and self.cancel_event.is_set():
                            raise DownloadCancelled
                        received_size += len(data)
                        file.write(data)
                        if not self.update_func:
                            continue
                        self.update_func(received_size, total_size)
                        self.__progress(received_size, total_size)
                else:
                    file.write(response.content)
                    if self.update_func:
                        self.update_func(1, 1)
                        self.__progress(1, 1)
        except DownloadCancelled:
            if self.update_func:
                self.update_func(status=Status.CANCELLED)
            with suppress(FileNotFoundError):
                os.remove(self.file)
            return Result(False, message="cancelled")
        except requests.exceptions.SSLError:
            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(True)

    def __progress(self, received_size, total_size):
        """Update the progress bar."""
        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)
        )
        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)"
        )
        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)"
            )
            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})"
        )

        if sys.stdout.encoding == "utf-8":
            print(text, end="")
        else:
            # usually means user is using legacy encoding
            # which cannot cover unicode codepoint,
            # so we need replace '━' with '-'
            print(text.replace("━", "-"), end="")

        if percent == 100:
            print(f"{c_close}\n")


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

import os
import shutil
from pathlib import Path
from typing import Dict

from bottles.backend.utils import json, yaml


class Paths:
    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"

    # Local paths
    base = f"{xdg_data_home}/bottles"

    # User applications path
    applications = f"{xdg_data_home}/applications/"

    temp = f"{base}/temp"
    runtimes = f"{base}/runtimes"
    winebridge = f"{base}/winebridge"
    runners = f"{base}/runners"
    bottles = f"{base}/bottles"
    steam = f"{base}/steam"
    dxvk = f"{base}/dxvk"
    vkd3d = f"{base}/vkd3d"
    nvapi = f"{base}/nvapi"
    latencyflex = f"{base}/latencyflex"
    templates = f"{base}/templates"
    library = f"{base}/library.yml"
    process_metrics = f"{base}/process_metrics.sqlite"

    @staticmethod
    def is_vkbasalt_available():
        vkbasalt_paths = [
            "/usr/lib/extensions/vulkan/vkBasalt/etc/vkBasalt",
            "/usr/local",
            "/usr/share/vkBasalt",
        ]
        for path in vkbasalt_paths:
            if os.path.exists(path):
                return True
        return False


class TrdyPaths:
    # External managers paths
    wine = f"{Path.home()}/.wine"
    lutris = f"{Path.home()}/Games"
    playonlinux = f"{Path.home()}/.PlayOnLinux/wineprefix"
    bottlesv1 = f"{Path.home()}/.Bottles"


# check if bottles exists in xdg data path
os.makedirs(Paths.base, exist_ok=True)

# Check if some tools are available
gamemode_available = shutil.which("gamemoderun") or False
gamescope_available = shutil.which("gamescope") or False
vkbasalt_available = Paths.is_vkbasalt_available()
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"):
    with open("/app/manifest.json", mode="r", encoding="utf-8") as file:
        base_version = (
            json.load(file)  # type: ignore
            .get("base-version", "")
            .removeprefix("stable-")
        )

# encoding detection correction, following windows defaults
locale_encodings: Dict[str, str] = {"ja_JP": "cp932", "zh_CN": "gbk"}


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

import contextlib
import os

from bottles.backend.logger import Logger
from bottles.backend.params import APP_VERSION
from bottles.backend.utils import yaml
from bottles.backend.utils.display import DisplayUtils
from bottles.backend.utils.file import FileUtils
from bottles.backend.utils.generic import is_glibc_min_available
from bottles.backend.utils.gpu import GPUUtils

logging = Logger()


class HealthChecker:
    x11: bool = False
    x11_port: str = ""
    wayland: bool = False
    xwayland: bool = False
    desktop: str = ""
    gpus: dict = {}
    cabextract: bool = False
    p7zip: bool = False
    patool: bool = False
    icoextract: bool = False
    pefile: bool = False
    orjson: bool = False
    markdown: bool = False
    xdpyinfo: bool = False
    ImageMagick: bool = False
    FVS: bool = False
    glibc_min: str = ""
    kernel: str = ""
    kernel_version: str = ""
    bottles_envs: dict = {}

    def __init__(self):
        self.file_utils = FileUtils()
        self.x11 = self.check_x11()
        self.wayland = self.check_wayland()
        self.xwayland = self.x11 and self.wayland
        self.desktop = self.check_desktop()
        self.gpus = GPUUtils().get_gpu()
        self.glibc_min = is_glibc_min_available()
        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.get_ram_data()

    def check_x11(self):
        port = DisplayUtils.get_x_display()
        if port:
            self.x11_port = port
            return True
        return False

    @staticmethod
    def check_wayland():
        return "WAYLAND_DISPLAY" in os.environ or "WAYLAND_SOCKET" in os.environ

    def check_desktop(self):
        return os.environ.get("DESKTOP_SESSION")

    @staticmethod
    def get_bottles_envs():
        look = [
            "TESTING_REPOS",
            "LOCAL_INSTALLERS",
            "LOCAL_COMPONENTS",
            "LOCAL_DEPENDENCIES",
        ]

        for _look in look:
            if _look in os.environ:
                return {_look: os.environ[_look]}

    def check_system_info(self):
        self.kernel = os.uname().sysname
        self.kernel_version = os.uname().release

    def get_disk_data(self):
        disk_data = self.file_utils.get_disk_size(False)
        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:
                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
                            )
                        )

    def get_results(self, plain: bool = False):
        results = {
            "Official Package": "FLATPAK_ID" in os.environ,
            "Version": APP_VERSION,
            "DE/WM": self.desktop,
            "Display": {
                "X.org": self.x11,
                "X.org (port)": self.x11_port,
                "Wayland": self.wayland,
            },
            "Graphics": self.gpus,
            "Kernel": {"Type": self.kernel, "Version": self.kernel_version},
            "Disk": self.disk,
            "RAM": self.ram,
            "Bottles_envs": self.bottles_envs,
        }

        if plain:
            _yaml = yaml.dump(results, sort_keys=False, indent=4)
            _yaml = _yaml.replace("&id", "&amp;id")
            return _yaml

        return results


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

from bottles.backend.globals import Paths
from bottles.backend.managers.journal import JournalManager, JournalSeverity

# Set default logging level
logging.basicConfig(level=logging.DEBUG)


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}
    __format_log = {
        "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):
        if message and "\n" in message:
            message = message.replace("\n", "\n\t") + "\n"
        color_id = self.__color_map[level]
        return "\033[%dm%s\033[0m" % (color_id, message)

    def __init__(self, formatter=None):
        if formatter is None:
            formatter = self.__format_log
        formatter = logging.Formatter(**formatter)

        self.root.setLevel(os.environ.get("LOG_LEVEL") or logging.INFO)
        self.root.handlers = []

        handler = logging.StreamHandler()
        handler.setFormatter(formatter)
        self.root.addHandler(handler)

    def debug(self, message, **kwargs):
        self.root.debug(
            self.__color("debug", message),
        )

    def info(self, message, jn=False, **kwargs):
        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),
        )
        if jn:
            JournalManager.write(JournalSeverity.WARNING, message)

    def error(self, message, jn=True, **kwargs):
        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),
        )
        if jn:
            JournalManager.write(JournalSeverity.CRITICAL, message)

    @staticmethod
    def write_log(data: list):
        """
        Writes a crash.log file. It finds and replace the user's home directory
        with "USER" as a proposed standard for crash reports.
        """
        log_path = f"{Paths.xdg_data_home}/bottles/crash.log"

        with open(log_path, "w") as crash_log:
            for d in data:
                # replace username with "USER" as standard
                if "/home/" in d:
                    d = re.sub(r"/home/([^/]*)/", r"/home/USER/", d)

                crash_log.write(d)

        # we write the same to the journal for convenience
        JournalManager.write(
            severity=JournalSeverity.CRASH, message="A crash has been detected."
        )

    def set_silent(self):
        self.root.handlers = []


================================================
FILE: bottles/backend/managers/__init__.py
================================================


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

import os
import shutil
import tarfile
from gettext import gettext as _
from typing import Callable, Optional

import pathvalidate

from bottles.backend.globals import Paths
from bottles.backend.logger import Logger
from bottles.backend.managers.manager import Manager
from bottles.backend.models.config import BottleConfig
from bottles.backend.models.result import Result
from bottles.backend.state import Task, TaskManager
from bottles.backend.utils import yaml
from bottles.backend.utils.manager import ManagerUtils

logging = Logger()


class ProgressTrackingFilter:
    """
    A filter wrapper that tracks uncompressed bytes being added to the tar
    and reports progress via a Task.
    """

    def __init__(
        self,
        total_size: int,
        task: Optional[Task] = None,
        base_filter: Optional[Callable] = None,
    ):
        self._total_size = total_size
        self._task = task
        self._base_filter = base_filter
        self._processed = 0
        self._last_percent = -1

    def __call__(self, tarinfo: tarfile.TarInfo) -> Optional[tarfile.TarInfo]:
        # Apply base filter first
        if self._base_filter:
            tarinfo = self._base_filter(tarinfo)
            if tarinfo is None:
                return None

        # Track progress based on file size being added
        if tarinfo.isfile():
            self._processed += tarinfo.size
            self._update_progress()

        return tarinfo

    def _update_progress(self):
        if self._task and self._total_size > 0:
            percent = min(int(self._processed * 100 / self._total_size), 99)
            if percent != self._last_percent:
                self._last_percent = percent
                self._task.subtitle = f"{percent}%"


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 _calculate_dir_size(
        path: str, exclude_filter: Optional[Callable] = None
    ) -> int:
        """
        Calculate the total size of a directory, respecting the exclude filter.
        """
        total_size = 0
        for dirpath, dirnames, filenames in os.walk(path):
            # Apply exclude filter logic to directories
            if exclude_filter:
                # Check if this directory should be excluded
                rel_path = os.path.relpath(dirpath, os.path.dirname(path))
                mock_info = type("TarInfo", (), {"name": rel_path})()
                if exclude_filter(mock_info) is None:
                    dirnames.clear()  # Don't descend into excluded directories
                    continue

            for filename in filenames:
                filepath = os.path.join(dirpath, filename)
                # Apply exclude filter to files
                if exclude_filter:
                    rel_path = os.path.relpath(filepath, os.path.dirname(path))
                    mock_info = type("TarInfo", (), {"name": rel_path})()
                    if exclude_filter(mock_info) is None:
                        continue
                try:
                    total_size += os.path.getsize(filepath)
                except (OSError, FileNotFoundError):
                    pass
        return total_size

    @staticmethod
    def _create_tarfile(
        source_path: str,
        destination_path: str,
        exclude_filter: Optional[Callable] = None,
        task: Optional[Task] = None,
    ) -> bool:
        """Helper function to create a tar.gz file from a source path."""
        try:
            # Calculate total size for progress tracking
            total_size = 0
            if task:
                task.subtitle = _("Calculating…")
                total_size = BackupManager._calculate_dir_size(
                    source_path, exclude_filter
                )

            os.chdir(os.path.dirname(source_path))

            # Create progress-tracking filter if task is provided
            if task and total_size > 0:
                progress_filter = ProgressTrackingFilter(
                    total_size, task, exclude_filter
                )
                active_filter = progress_filter
            else:
                active_filter = exclude_filter

            with tarfile.open(destination_path, "w:gz") as tar:
                tar.add(os.path.basename(source_path), filter=active_filter)

            if task:
                task.subtitle = "100%"

            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, task: Optional[Task] = None
    ) -> bool:
        """
        Safely extract a tar.gz file to avoid directory traversal
        vulnerabilities.
        """
        try:
            with tarfile.open(tar_path, "r:gz") as tar:
                members = tar.getmembers()

                # Validate all members first
                for member in members:
                    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")

                if task:
                    # Calculate total size for progress
                    total_size = sum(m.size for m in members if m.isfile())
                    extracted_size = 0
                    last_percent = -1

                    for member in members:
                        tar.extract(member, path=extract_path)
                        if member.isfile():
                            extracted_size += member.size
                            percent = (
                                min(int(extracted_size * 100 / total_size), 99)
                                if total_size > 0
                                else 0
                            )
                            if percent != last_percent:
                                last_percent = percent
                                task.subtitle = f"{percent}%"
                    task.subtitle = "100%"
                else:
                    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:
        """
        Exports a bottle backup to the specified path.
        Use the scope parameter to specify the backup type: config, full.
        Config will only export the bottle configuration, full will export
        the full bottle in tar.gz format.
        """
        if not BackupManager._validate_path(path):
            return Result(status=False)

        logging.info(f"Exporting {scope} backup for [{config.Name}] to [{path}]")

        if scope == "config":
            backup_created = config.dump(path).status
        else:
            task = Task(title=_("Backup {0}").format(config.Name))
            task_id = TaskManager.add(task)
            bottle_path = ManagerUtils.get_bottle_path(config)
            backup_created = BackupManager._create_tarfile(
                bottle_path,
                path,
                exclude_filter=BackupManager.exclude_filter,
                task=task,
            )
            TaskManager.remove(task_id)

        if backup_created:
            logging.info(f"Backup successfully saved to: {path}.")
            return Result(status=True)
        else:
            logging.error("Failed to save backup.")
            return Result(status=False)

    @staticmethod
    def exclude_filter(tarinfo: tarfile.TarInfo) -> tarfile.TarInfo | None:
        """
        Filter which excludes some unwanted files from the backup.
        """
        if "dosdevices" in tarinfo.name:
            return None
        return tarinfo

    @staticmethod
    def import_backup(scope: str, path: str) -> Result:
        """
        Imports a backup from the specified path.
        Use the scope parameter to specify the backup type: config, full.
        Config will make a new bottle reproducing the configuration, full will
        import the full bottle from a tar.gz file.
        """
        if not BackupManager._validate_path(path):
            return Result(status=False)

        logging.info(f"Importing backup from: {path}")

        if scope == "config":
            return BackupManager._import_config_backup(path)
        else:
            return BackupManager._import_full_backup(path)

    @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 config_load.data
            and manager.create_bottle_from_config(config_load.data)
        ):
            TaskManager.remove(task_id)
            logging.info("Config backup imported successfully.")
            return Result(status=True)
        else:
            TaskManager.remove(task_id)
            logging.error("Failed to import config backup.")
            return Result(status=False)

    @staticmethod
    def _import_full_backup(path: str) -> Result:
        task = Task(title=_("Importing full backup"))
        task_id = TaskManager.add(task)
        if BackupManager._safe_extract_tarfile(path, Paths.bottles, task=task):
            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)

    @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}")

        sanitized_name = pathvalidate.sanitize_filename(name, platform="universal")
        source_path = ManagerUtils.get_bottle_path(config)
        destination_path = os.path.join(Paths.bottles, sanitized_name)

        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:
            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)


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

import contextlib
import os
import shutil
import tarfile
from functools import lru_cache
from threading import Event
from typing import Optional

import pycurl

from bottles.backend.downloader import Downloader
from bottles.backend.globals import Paths
from bottles.backend.logger import Logger
from bottles.backend.models.result import Result
from bottles.backend.state import (
    LockManager,
    Locks,
    Status,
    Task,
    TaskManager,
    TaskStreamUpdateHandler,
)
from bottles.backend.utils.file import FileUtils
from bottles.backend.utils.generic import is_glibc_min_available
from bottles.backend.utils.manager import ManagerUtils

logging = Logger()


# noinspection PyTypeChecker
class ComponentManager:
    def __init__(self, manager, offline: bool = False):
        self.__manager = manager
        self.__repo = manager.repository_manager.get_repo("components", offline)
        self.__utils_conn = manager.utils_conn

    @lru_cache
    def get_component(self, name: str, plain: bool = False) -> dict:
        return self.__repo.get(name, plain)

    def fetch_catalog(self) -> dict:
        """
        Fetch all components from the Bottles repository, mark the installed
        ones and return a dict with the catalog.
        """
        if not self.__utils_conn.check_connection():
            return {}

        catalog = {
            "runtimes": {},
            "wine": {},
            "proton": {},
            "dxvk": {},
            "vkd3d": {},
            "nvapi": {},
            "latencyflex": {},
            "winebridge": {},
        }
        components_available = {
            "runtimes": self.__manager.runtimes_available,
            "wine": self.__manager.runners_available,
            "proton": self.__manager.runners_available,
            "dxvk": self.__manager.dxvk_available,
            "vkd3d": self.__manager.vkd3d_available,
            "nvapi": self.__manager.nvapi_available,
            "latencyflex": self.__manager.latencyflex_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."
                        )
                        continue

                sub_category = component[1]["Sub-category"]
                catalog[sub_category][component[0]] = component[1]
                if component[0] in components_available[sub_category]:
                    catalog[sub_category][component[0]]["Installed"] = True
                else:
                    catalog[sub_category][component[0]].pop("Installed", None)

                continue

            category = component[1]["Category"]
            if category not in catalog:
                continue

            catalog[category][component[0]] = component[1]
            if component[0] in components_available[category]:
                catalog[category][component[0]]["Installed"] = True
            else:
                catalog[category][component[0]].pop("Installed", None)

        return catalog

    def download(
        self,
        download_url: str,
        file: str,
        rename: str = "",
        checksum: str = "",
        func: Optional[TaskStreamUpdateHandler] = None,
        cancel_event: Optional[Event] = None,
        task: Optional[Task] = None,
    ) -> Result:
        """Download a component from the Bottles repository."""

        # Check for missing Bottles paths before download
        self.__manager.check_app_dirs()

        # Register this file download task to TaskManager unless an existing task
        # is provided by the caller.
        external_task = task is not None and task.task_id is not None
        if task is None:
            task = Task(title=file, cancellable=cancel_event is not None)

        if task.task_id is None:
            task_id = TaskManager.add(task)
        else:
            task_id = task.task_id

        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 Result(True)

        existing_file = rename if rename else file
        temp_dest = os.path.join(Paths.temp, file)
        just_downloaded = False

        file_path = os.path.join(Paths.temp, existing_file)
        if os.path.isfile(file_path):
            """
            Check if the file already exists in the /temp directory.
            If it's a 0-byte empty file, remove it to allow a fresh download.
            Otherwise, skip the download.
            """
            if os.path.getsize(file_path) == 0:
                logging.warning(f"File [{existing_file}] is a 0-byte empty file. Removing to force re-download.")
                os.remove(file_path)
            else:
                logging.warning(f"File [{existing_file}] already exists in temp, skipping.")
                return Result(True)
        
        if not os.path.isfile(file_path):
            """
            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)  # type: ignore
                c.setopt(c.FOLLOWLOCATION, True)  # type: ignore
                c.setopt(c.HTTPHEADER, ["User-Agent: curl/7.79.1"])  # type: ignore
                c.setopt(c.NOBODY, True)  # type: ignore
                c.perform()

                req_code = c.getinfo(c.RESPONSE_CODE)  # type: ignore
                download_url = c.getinfo(c.EFFECTIVE_URL)  # type: ignore
            except pycurl.error:
                logging.exception(f"Failed to download [{download_url}]")
                if not external_task:
                    TaskManager.remove(task_id)
                return Result(False)
            finally:
                c.close()

            if req_code == 200:
                """
                If the status code is 200, the resource should be available
                and the download should be started. Any exceptions return
                False and the download is removed from the download manager.
                """
                res = Downloader(
                    url=download_url,
                    file=temp_dest,
                    update_func=update_func,
                    cancel_event=cancel_event,
                ).download()

                if not res.ok:
                    if not external_task:
                        TaskManager.remove(task_id)
                    return res

                if not os.path.isfile(temp_dest):
                    """Fail if the file is not available in the /temp directory."""
                    if not external_task:
                        TaskManager.remove(task_id)
                    return Result(False)

                just_downloaded = True
            else:
                logging.warning(
                    f"Failed to download [{download_url}] with code: {req_code} != 200"
                )
                if not external_task:
                    TaskManager.remove(task_id)
                return Result(False)

        file_path = os.path.join(Paths.temp, existing_file)
        if rename and just_downloaded:
            """Renaming the downloaded file if requested."""
            logging.info(f"Renaming [{file}] to [{rename}].")
            file_path = os.path.join(Paths.temp, rename)
            os.rename(temp_dest, file_path)

        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
            file from the /temp directory, remove the entry from the
            task manager and return False.
            """
            checksum = checksum.lower()
            local_checksum = FileUtils().get_checksum(file_path)

            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"Removing corrupted file [{file}].")
                os.remove(file_path)
                if not external_task:
                    TaskManager.remove(task_id)
                return Result(False)

        if not external_task:
            TaskManager.remove(task_id)
        return Result(True)

    @staticmethod
    def extract(name: str, component: str, archive: str) -> bool:
        """Extract a component from an archive."""

        if component in ["runner", "runner:proton"]:
            path = Paths.runners
        elif component == "dxvk":
            path = Paths.dxvk
        elif component == "vkd3d":
            path = Paths.vkd3d
        elif component == "nvapi":
            path = Paths.nvapi
        elif component == "latencyflex":
            path = Paths.latencyflex
        elif component == "runtime":
            path = Paths.runtimes
        elif component == "winebridge":
            path = Paths.winebridge
        else:
            logging.error(f"Unknown component [{component}].")
            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
            extraction is that the archive is corrupted.
            """
            tar = tarfile.open(f"{Paths.temp}/{archive}")
            root_dir = tar.getnames()[0]
            tar.extractall(path)
            tar.close()
        except (tarfile.TarError, IOError, EOFError):
            with contextlib.suppress(FileNotFoundError):
                os.remove(os.path.join(Paths.temp, archive))
            with contextlib.suppress(FileNotFoundError):
                shutil.rmtree(os.path.join(path, archive[:-7]))

            logging.error("Extraction failed! Archive ends earlier than expected.")
            return False

        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])
            except (FileExistsError, shutil.Error):
                logging.error("Extraction failed! Component already exists.")
                return False
        return True

    @LockManager.lock(Locks.ComponentsInstall)  # avoid high resource usage
    def install(
        self,
        component_type: str,
        component_name: str,
        func: Optional[TaskStreamUpdateHandler] = None,
        cancel_event: Optional[Event] = None,
    ):
        """
        This function is used to install a component. It automatically
        gets the manifest from the given component and then calls the
        download and extract functions.
        """
        manifest = self.get_component(component_name)

        if not manifest:
            return Result(False)

        logging.info(f"Installing component: [{component_name}].")
        file = manifest["File"][0]

        res = self.download(
            download_url=file["url"],
            file=file["file_name"],
            rename=file["rename"],
            checksum=file["file_checksum"],
            func=func,
            cancel_event=cancel_event,
        )

        if not res.ok:
            """
            If the download fails, execute the given func passing
            failed=True as a parameter.
            """
            if func:
                if res.message == "cancelled":
                    func(status=Status.CANCELLED)
                else:
                    func(status=Status.FAILED)
            return Result(False, message=res.message)

        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}].")

            for post in manifest.get("Post", []):
                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))

        if component_type in ["runner", "runner:proton"]:
            self.__manager.check_runners()

        elif component_type == "dxvk":
            self.__manager.check_dxvk()

        elif component_type == "vkd3d":
            self.__manager.check_vkd3d()

        elif component_type == "nvapi":
            self.__manager.check_nvapi()

        elif component_type == "runtime":
            self.__manager.check_runtimes()

        elif component_type == "winebridge":
            self.__manager.check_winebridge()

        self.__manager.organize_components()
        logging.info(f"Component installed: {component_type} {component_name}", jn=True)

        return Result(True)

    @staticmethod
    def __post_rename(component_type: str, post: dict):
        source = post.get("source")
        dest = post.get("dest")

        if component_type in ["runner", "runner:proton"]:
            path = Paths.runners
        elif component_type == "dxvk":
            path = Paths.dxvk
        elif component_type == "vkd3d":
            path = Paths.vkd3d
        elif component_type == "nvapi":
            path = Paths.nvapi
        else:
            logging.error(f"Unknown component type: {component_type}")
            return

        if (
            source is not None
            and dest is not None
            and not os.path.isdir(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

        if component_type in ["runner", "runner:proton"]:
            return component_name in [b["Runner"] for _, b in bottles.items()]
        if component_type == "dxvk":
            return component_name in [b["DXVK"] for _, b in bottles.items()]
        if component_type == "vkd3d":
            return component_name in [b["VKD3D"] for _, b in bottles.items()]
        if component_type == "nvapi":
            return component_name in [b["NVAPI"] for _, b in bottles.items()]
        if component_type == "latencyflex":
            return component_name in [b["LatencyFleX"] for _, b in bottles.items()]
        if component_type in ["runtime", "winebridge"]:
            return True
        return False

    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}"
                },
            )

        if component_type in ["runner", "runner:proton"]:
            path = ManagerUtils.get_runner_path(component_name)

        elif component_type == "dxvk":
            path = ManagerUtils.get_dxvk_path(component_name)

        elif component_type == "vkd3d":
            path = ManagerUtils.get_vkd3d_path(component_name)

        elif component_type == "nvapi":
            path = ManagerUtils.get_nvapi_path(component_name)

        elif component_type == "latencyflex":
            path = ManagerUtils.get_latencyflex_path(component_name)

        else:
            logging.error(f"Unknown component type: {component_type}")
            return Result(False, data={"message": "Unknown component type."})

        if not os.path.isdir(path):
            return Result(False, data={"message": "Component not installed."})

        try:
            shutil.rmtree(path)
        except Exception as e:
            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()

        elif component_type == "dxvk":
            self.__manager.check_dxvk()

        elif component_type == "vkd3d":
            self.__manager.check_vkd3d()

        elif component_type == "nvapi":
            self.__manager.check_nvapi()

        elif component_type == "runtime":
            self.__manager.check_runtimes()

        elif component_type == "winebridge":
            self.__manager.check_winebridge()

        self.__manager.organize_components()
        logging.info(f"Component uninstalled: {component_type} {component_name}")

        return Result(True)


================================================
FILE: bottles/backend/managers/conf.py
================================================
import os
from configparser import ConfigParser
from typing import Optional

from bottles.backend.utils import yaml, json


class ConfigManager(object):
    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

        if self.config_file is not None:
            self.checks()

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

    def checks(self):
        """Checks if the configuration file exists, if not, create it."""
        if not os.path.exists(self.config_file):
            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("")

    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":
                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:
                    res = json.load(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")
        elif self.config_string is not None:
            if self.config_type == "ini":
                config = ConfigParser()
                config.read_string(self.config_string)
                res = config._sections
            elif self.config_type == "json":
                res = json.loads(self.config_string)
            elif self.config_type == "yaml" or self.config_type == "yml":
                res = yaml.load(self.config_string)
            else:
                raise ValueError("Invalid configuration type")
        else:
            res = None

        return res or {}

    def get_dict(self):
        """Returns the configuration as a dictionary"""
        return self.config_dict

    def write_json(self):
        """Writes the configuration to a JSON file"""
        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:
            yaml.dump(self.config_dict, f)

    def write_ini(self):
        """Writes the configuration to an INI file"""
        config = ConfigParser()

        for section in self.config_dict:
            config.add_section(section)

            for key, value in self.config_dict[section].items():
                config.set(section, key, value)

        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")
        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":
            self.write_ini()
        elif self.config_type == "json":
            self.write_json()
        elif self.config_type == "yaml":
            self.write_yaml()
        else:
            raise ValueError("Invalid configuration type")

    def merge_dict(self, changes: dict):
        """Merges a dictionary into the configuration"""
        for section in changes:
            if section in self.config_dict:
                for key, value in changes[section].items():
                    if isinstance(value, dict):
                        if key in self.config_dict[section]:
                            self.config_dict[section][key].update(value)
                        else:
                            self.config_dict[section][key] = value
                    else:
                        self.config_dict[section][key] = value
            else:
                self.config_dict[section] = changes[section]

        self.write_dict()

    def del_key(self, key_struct: dict):
        """Deletes a key from the configuration"""
        key = self.config_dict

        for i, k in enumerate(key_struct):
            if i == len(key_struct) - 1:
                del key[k]
                continue
            key = key[k]

        self.write_dict()


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

import contextlib
import os

from bottles.backend.globals import Paths
from bottles.backend.logger import Logger
from bottles.backend.models.samples import Samples
from bottles.backend.utils import yaml

logging = Logger()


class UserDataKeys:
    CustomBottlesPath = "custom_bottles_path"
    FundingDismissed = "funding_dialog_dismissed_next"
    LastFundingPrompt = "last_funding_prompt_date"
    FundingPromptCount = "funding_prompt_count"
    PersonalRepositories = "personal_repositories"


class DataManager:
    """
    The DataManager class is used to store and retrieve data
    from the user data.yml file. Should be stored only info
    and settings that should not be stored in gsettings.
    """

    __data: dict = {}
    __p_data = f"{Paths.base}/data.yml"

    def __init__(self):
        self.__get_data()

    def __get_data(self):
        try:
            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.",
            )
            self.__create_data_file()
        except AttributeError:
            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:
            yaml.dump(Samples.data, s)
        self.__get_data()

    def list(self):
        """Returns the whole data dictionary."""
        return self.__data

    def set(self, key, value, of_type=None):
        """Sets a value in the data dictionary."""
        if self.__data.get(key):
            if isinstance(self.__data[key], list):
                self.__data[key].append(value)
            else:
                self.__data[key] = value
        else:
            if of_type is list:
                self.__data[key] = [value]
            else:
                self.__data[key] = value

        with contextlib.suppress(FileNotFoundError):
            with open(self.__p_data, "w") as s:
                yaml.dump(self.__data, s)

    def remove(self, key):
        """Removes a key from the data dictionary."""
        if self.__data.get(key):
            del self.__data[key]
            with contextlib.suppress(FileNotFoundError):
                with open(self.__p_data, "w") as s:
                    yaml.dump(self.__data, s)

    def get(self, key, default=None):
        """Returns the value of a key in the data dictionary."""
        return self.__data.get(key, default)


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

import os
import shutil
import traceback
from functools import lru_cache
from glob import glob
from typing import Callable, Optional

from gettext import gettext as _

import patoolib  # type: ignore [import-untyped]

from bottles.backend.cabextract import CabExtract
from bottles.backend.globals import Paths
from bottles.backend.logger import Logger
from bottles.backend.managers.registry_rule import RegistryRuleManager
from bottles.backend.models.config import BottleConfig
from bottles.backend.models.enum import Arch
from bottles.backend.models.result import Result
from bottles.backend.state import Status, Task, TaskManager
from bottles.backend.utils.generic import validate_url
from bottles.backend.utils.manager import ManagerUtils
from bottles.backend.wine.executor import WineExecutor
from bottles.backend.wine.reg import Reg, RegItem
from bottles.backend.wine.regkeys import RegKeys
from bottles.backend.wine.regsvr32 import Regsvr32
from bottles.backend.wine.uninstaller import Uninstaller
from bottles.backend.wine.winedbg import WineDbg

logging = Logger()


class DependencyManager:
    def __init__(self, manager, offline: bool = False):
        self.__manager = manager
        self.__repo = manager.repository_manager.get_repo("dependencies", offline)
        self.__utils_conn = manager.utils_conn

    @lru_cache
    def get_dependency(self, name: str, plain: bool = False) -> str | dict | bool:
        return self.__repo.get(name, plain)

    @lru_cache
    def fetch_catalog(self) -> dict:
        """
        Fetch all dependencies from the Bottles repository
        and return these as a dictionary. It also returns an empty dictionary
        if there are no dependencies or fails to fetch them.
        """
        if not self.__utils_conn.check_connection():
            return {}

        catalog = {}
        index = self.__repo.catalog

        for dependency in index.items():
            catalog[dependency[0]] = dependency[1]

        catalog = dict(sorted(catalog.items()))
        return catalog

    @staticmethod
    def __notify_progress(
        callback: Optional[Callable[[str], None]], message: str, task: Optional[Task] = None
    ) -> None:
        if not message:
            return

        if task is not None:
            task.subtitle = message

        if callback:
            callback(message)

    @staticmethod
    def __notify_progress_fraction(
        callback: Optional[Callable[[Optional[float]], None]], fraction: Optional[float]
    ) -> None:
        if callback:
            callback(fraction)

    @staticmethod
    def __build_progress_handler(
        task: Optional[Task],
        progress_cb: Optional[Callable[[Optional[float]], None]],
    ) -> Callable[[int, int, Optional[Status]], None]:
        def handler(
            received_size: int = 0,
            total_size: int = 0,
            status: Optional[Status] = None,
        ) -> None:
            if task is not None:
                task.stream_update(received_size, total_size, status)

            if progress_cb is None:
                return

            if status in [Status.DONE, Status.FAILED, Status.CANCELLED]:
                progress_cb(None)
                return

            if total_size:
                progress_cb(received_size / total_size)

        return handler

    @staticmethod
    def __describe_step(step: dict) -> str:
        action = step.get("action", "step")
        file_name = step.get("rename") or step.get("file_name")

        descriptions = {
            "download_archive": _("Downloading {0}…").format(
                file_name or _("archive")
            ),
            "install_exe": _("Running {0}…").format(file_name or _("installer")),
            "install_msi": _("Running {0}…").format(file_name or _("installer")),
            "install_cab_fonts": _("Installing fonts…"),
            "install_fonts": _("Installing fonts…"),
            "cab_extract": _("Extracting {0}…").format(file_name or _("package")),
            "get_from_cab": _("Extracting {0} from cabinet…").format(
                file_name or _("files")
            ),
            "archive_extract": _("Unpacking archive…"),
            "copy_dll": _("Copying files…"),
            "copy_file": _("Copying files…"),
            "delete_dlls": _("Cleaning previous files…"),
            "register_dll": _("Registering libraries…"),
            "override_dll": _("Updating DLL overrides…"),
            "set_register_key": _("Writing registry keys…"),
            "register_font": _("Registering fonts…"),
            "replace_font": _("Replacing fonts…"),
            "set_windows": _("Adjusting Windows version…"),
            "use_windows": _("Applying Windows version…"),
            "uninstall": _("Running uninstaller…"),
        }

        return descriptions.get(action, _("Running {0}…").format(action.replace("_", " ")))

    def install(
        self,
        config: BottleConfig,
        dependency: list,
        progress_cb: Optional[Callable[[str], None]] = None,
        progress_progress_cb: Optional[Callable[[Optional[float]], None]] = None,
    ) -> Result:
        """
        Install a given dependency in a bottle. It will
        return True if the installation was successful.
        """
        uninstaller = True
        installed_new = False

        if config.Parameters.versioning_automatic:
            """
            If the bottle has the versioning system enabled, we need
            to create a new version of the bottle, before installing
            the dependency.
            """
            self.__manager.versioning_manager.create_state(
                config=config, message=f"Before installing {dependency[0]}"
            )

        task_id = TaskManager.add(Task(title=dependency[0]))
        task = TaskManager.get(task_id)

        self.__notify_progress(
            progress_cb, _("Preparing installation…"), task=task
        )

        logging.info(
            "Installing dependency [%s] in bottle [%s]." % (dependency[0], config.Name),
        )
        manifest = self.get_dependency(dependency[0])
        if not manifest:
            """
            If the manifest is not found, return a Result
            object with the error.
            """
            TaskManager.remove(task_id)
            return Result(
                status=False, message=f"Cannot find manifest for {dependency[0]}."
            )

        if manifest.get("Dependencies"):
            """
            If the manifest has dependencies, we need to install them
            before installing the current one.
            """
            for _ext_dep in manifest.get("Dependencies"):
                if _ext_dep in config.Installed_Dependencies:
                    continue
                if _ext_dep in self.__manager.supported_dependencies:
                    _dep = self.__manager.supported_dependencies[_ext_dep]
                    self.__notify_progress(
                        progress_cb,
                        _("Installing prerequisite “{0}”…").format(_ext_dep),
                        task=task,
                    )
                    _res = self.install(
                        config,
                        [_ext_dep, _dep],
                        progress_cb=progress_cb,
                        progress_progress_cb=progress_progress_cb,
                    )
                    if not _res.status:
                        return _res

        for step in manifest.get("Steps"):
            """
            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

            description = self.__describe_step(step)
            self.__notify_progress(progress_cb, description, task=task)
            self.__notify_progress_fraction(progress_progress_cb, None)

            res = self.__perform_steps(
                config,
                step,
                task=task,
                progress_cb=progress_cb,
                progress_progress_cb=progress_progress_cb,
            )
            if not res.ok:
                TaskManager.remove(task_id)
                return Result(
                    status=False,
                    message=f"One or more steps failed for {dependency[0]}.",
                )
            if not res.data.get("uninstaller"):
                uninstaller = False

        if dependency[0] not in config.Installed_Dependencies:
            """
            If the dependency is not already listed in the installed
            dependencies list of the bottle, add it.
            """
            dependencies = [dependency[0]]

            if config.Installed_Dependencies:
                dependencies = config.Installed_Dependencies + [dependency[0]]

            self.__manager.update_config(
                config=config, key="Installed_Dependencies", value=dependencies
            )
            installed_new = True

        if manifest.get("Uninstaller"):
            """
            If the manifest has an uninstaller, add it to the
            uninstaller list in the bottle config.
            Set it to NO_UNINSTALLER if the dependency cannot be uninstalled.
            """
            uninstaller = manifest.get("Uninstaller")

        if dependency[0] not in config.Installed_Dependencies:
            self.__manager.update_config(
                config, dependency[0], uninstaller, "Uninstallers"
            )
            installed_new = True

        # Remove entry from task manager
        TaskManager.remove(task_id)

        # Hide installation button and show remove button
        logging.info(f"Dependency installed: {dependency[0]} in {config.Name}", jn=True)

        if installed_new:
            RegistryRuleManager.apply_rules(config, trigger="dependencies")
        self.__notify_progress_fraction(progress_progress_cb, None)
        self.__notify_progress(
            progress_cb, _("Finalizing installation…"), task=task
        )
        if not uninstaller:
            return Result(status=True, data={"uninstaller": False})
        return Result(status=True, data={"uninstaller": True})

    def __perform_steps(
        self,
        config: BottleConfig,
        step: dict,
        task: Optional[Task] = None,
        progress_cb: Optional[Callable[[str], None]] = None,
        progress_progress_cb: Optional[Callable[[Optional[float]], None]] = None,
    ) -> Result:
        """
        This method execute a step in the bottle (e.g. changing the Windows
        version, installing fonts, etc.)
        ---
        Returns True if the dependency cannot be uninstalled.
        """
        uninstaller = True

        if step["action"] == "delete_dlls":
            self.__step_delete_dlls(config, step)

        if step["action"] == "download_archive":
            if not self.__step_download_archive(
                step, task=task, progress_progress_cb=progress_progress_cb
            ):
                return Result(status=False)

        if step["action"] in ["install_exe", "install_msi"]:
            if not self.__step_install_exe_msi(
                config=config,
                step=step,
                task=task,
                progress_cb=progress_cb,
                progress_progress_cb=progress_progress_cb,
            ):
                return Result(status=False)

        if step["action"] == "uninstall":
            self.__step_uninstall(config=config, file_name=step["file_name"])

        if step["action"] == "cab_extract":
            uninstaller = False
            if not self.__step_cab_extract(
                step=step, task=task, progress_progress_cb=progress_progress_cb
            ):
                return Result(status=False)

        if step["action"] == "get_from_cab":
            uninstaller = False
            if not self.__step_get_from_cab(config=config, step=step):
                return Result(status=False)

        if step["action"] == "archive_extract":
            uninstaller = False
            if not self.__step_archive_extract(
                step, task=task, progress_progress_cb=progress_progress_cb
            ):
                return Result(status=False)

        if step["action"] in ["install_cab_fonts", "install_fonts"]:
            uninstaller = False
            if not self.__step_install_fonts(config=config, step=step):
                return Result(status=False)

        if step["action"] in ["copy_dll", "copy_file"]:
            uninstaller = False
            if not self.__step_copy_dll(config=config, step=step):
                return Result(status=False)

        if step["action"] == "register_dll":
            self.__step_register_dll(config=config, step=step)

        if step["action"] == "override_dll":
            self.__step_override_dll(config=config, step=step)

        if step["action"] == "set_register_key":
            self.__step_set_register_key(config=config, step=step)

        if step["action"] == "register_font":
            self.__step_register_font(config=config, step=step)

        if step["action"] == "replace_font":
            self.__step_replace_font(config=config, step=step)

        if step["action"] == "set_windows":
            self.__step_set_windows(config=config, step=step)

        if step["action"] == "use_windows":
            self.__step_use_windows(config=config, step=step)

        return Result(status=True, data={"uninstaller": uninstaller})

    @staticmethod
    def __get_real_dest(config: BottleConfig, dest: str) -> str | bool:
        """This function return the real destination path."""
        bottle = ManagerUtils.get_bottle_path(config)
        _dest = dest

        if dest.startswith("temp/"):
            dest = dest.replace("temp/", f"{Paths.temp}/")
        elif dest.startswith("windows/"):
            dest = f"{bottle}/drive_c/{dest}"
        elif dest.startswith("win32"):
            dest = f"{bottle}/drive_c/windows/system32/"
            if config.Arch == Arch.WIN64:
                dest = f"{bottle}/drive_c/windows/syswow64/"
            dest = _dest.replace("win32", dest)
        elif dest.startswith("win64"):
            if config.Arch == Arch.WIN64:
                dest = f"{bottle}/drive_c/windows/system32/"
                dest = _dest.replace("win64", dest)
            else:
                return True
        else:
            logging.error("Destination path not supported!")
            return False

        return dest

    def __step_download_archive(
        self,
        step: dict,
        task: Optional[Task] = None,
        progress_progress_cb: Optional[Callable[[Optional[float]], None]] = None,
    ):
        """
        This function download an archive from the given step.
        Can be used for any file type (cab, zip, ...). Please don't
        use this method for exe/msi files as the install_exe already
        download the exe/msi file before installation.
        """
        progress_handler = self.__build_progress_handler(task, progress_progress_cb)
        download = self.__manager.component_manager.download(
            download_url=step.get("url"),
            file=step.get("file_name"),
            rename=step.get("rename"),
            checksum=step.get("file_checksum"),
            func=progress_handler,
            task=task,
        )

        return download

    def __step_install_exe_msi(
        self,
        config: BottleConfig,
        step: dict,
        task: Optional[Task] = None,
        progress_cb: Optional[Callable[[str], None]] = None,
        progress_progress_cb: Optional[Callable[[Optional[float]], None]] = None,
    ) -> bool:
        """
        Download and install the .exe or .msi file
        declared in the step, in a bottle.
        """
        winedbg = WineDbg(config)
        progress_handler = self.__build_progress_handler(task, progress_progress_cb)
        download = self.__manager.component_manager.download(
            download_url=step.get("url"),
            file=step.get("file_name"),
            rename=step.get("rename"),
            checksum=step.get("file_checksum"),
            func=progress_handler,
            task=task,
        )
        file = step.get("file_name")
        if step.get("rename"):
            file = step.get("rename")

        if download:
            if step.get("url").startswith("temp/"):
                _file = step.get("url").replace("temp/", f"{Paths.temp}/")
                file = f"{_file}/{file}"
            else:
                file = f"{Paths.temp}/{file}"
            executor = WineExecutor(
                config,
                exec_path=file,
                args=step.get("arguments"),
                environment=step.get("environment"),
            )
            executor.run()
            winedbg.wait_for_process(file)

            if progress_progress_cb:
                progress_progress_cb(None)

            if progress_cb:
                progress_cb(_("Installer completed."))
            return True

        return False

    @staticmethod
    def __step_uninstall(config: BottleConfig, file_name: str) -> bool:
        """
        This function find an uninstaller in the bottle by the given
        file name and execute it.
        """
        Uninstaller(config).from_name(file_name)
        return True

    def __step_cab_extract(
        self,
        step: dict,
        task: Optional[Task] = None,
        progress_progress_cb: Optional[Callable[[Optional[float]], None]] = None,
    ):
        """
        This function download and extract a Windows Cabinet to the
        temp folder.
        """
        dest = step.get("dest")
        if dest.startswith("temp/"):
            dest = dest.replace("temp/", f"{Paths.temp}/")
        else:
            logging.error("Destination path not supported!")
            return False

        if validate_url(step["url"]):
            progress_handler = self.__build_progress_handler(task, progress_progress_cb)
            download = self.__manager.component_manager.download(
                download_url=step.get("url"),
                file=step.get("file_name"),
                rename=step.get("rename"),
                checksum=step.get("file_checksum"),
                func=progress_handler,
                task=task,
            )

            if download:
                if step.get("rename"):
                    file = step.get("rename")
                else:
                    file = step.get("file_name")

                if not CabExtract().run(
                    path=os.path.join(Paths.temp, file), name=file, destination=dest
                ):
                    return False
            else:
                return False

        elif step["url"].startswith("temp/"):
            path = step["url"]
            path = path.replace("temp/", f"{Paths.temp}/")

            if step.get("rename"):
                file_path = os.path.splitext(f"{step.get('rename')}")[0]
            else:
                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
            ):
                return False

        return True

    def __step_delete_dlls(self, config: BottleConfig, step: dict):
        """Deletes the given dlls from the system32 or syswow64 paths"""
        dest = self.__get_real_dest(config, step.get("dest"))

        for d in step.get("dlls", []):
            _d = os.path.join(dest, d)
            if os.path.exists(_d):
                os.remove(_d)

        return True

    def __step_get_from_cab(self, config: BottleConfig, step: dict):
        """Take a file from a cabiner and extract to a path."""
        source = step.get("source")
        file_name = step.get("file_name")
        rename = step.get("rename")
        dest = self.__get_real_dest(config, step.get("dest"))

        if isinstance(dest, bool):
            return dest

        res = CabExtract().run(
            path=os.path.join(Paths.temp, source), files=[file_name], destination=dest
        )

        if rename:
            _file_name = file_name.split("/")[-1]

            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))

        if not res:
            return False
        return True

    def __step_archive_extract(
        self,
        step: dict,
        task: Optional[Task] = None,
        progress_progress_cb: Optional[Callable[[Optional[float]], None]] = None,
    ):
        """Download and extract an archive to the temp folder."""
        progress_handler = self.__build_progress_handler(task, progress_progress_cb)
        download = self.__manager.component_manager.download(
            download_url=step.get("url"),
            file=step.get("file_name"),
            rename=step.get("rename"),
            checksum=step.get("file_checksum"),
            func=progress_handler,
            task=task,
        )

        if not download:
            return False

        if step.get("rename"):
            file = step.get("rename")
        else:
            file = step.get("file_name")

        archive_path = os.path.join(Paths.temp, os.path.splitext(file)[0])

        if os.path.exists(archive_path):
            shutil.rmtree(archive_path)

        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))
            ):
                tar_path = os.path.join(archive_path, os.path.basename(archive_path))
                patoolib.extract_archive(tar_path, outdir=archive_path)
        except Exception as e:
            logging.error("Something wrong happened during extraction.")
            logging.error(f"{e}")
            logging.error(f"{traceback.format_exc()}")
            return False
        return True

    @staticmethod
    def __step_install_fonts(config: BottleConfig, step: dict):
        """Move fonts to the drive_c/windows/Fonts path."""
        path = step["url"]
        path = path.replace("temp/", f"{Paths.temp}/")
        bottle_path = ManagerUtils.get_bottle_path(config)

        for font in step.get("fonts"):
            font_path = f"{bottle_path}/drive_c/windows/Fonts/"
            if not os.path.exists(font_path):
                os.makedirs(font_path)

            try:
                shutil.copyfile(f"{path}/{font}", f"{font_path}/{font}")
            except (FileNotFoundError, FileExistsError):
                logging.warning(f"Font {font} already exists or is not found.")

            # print(f"Copying {font} to {bottle_path}/drive_c/windows/Fonts/")

        return True

    # noinspection PyTypeChecker
    def __step_copy_dll(self, config: BottleConfig, step: dict):
        """
        This function copy dlls from temp folder to a directory
        declared in the step. The bottle drive_c path will be used as
        root path.
        """
        path = step["url"]
        path = path.replace("temp/", f"{Paths.temp}/")
        dest = self.__get_real_dest(config, step.get("dest"))

        if isinstance(dest, bool):
            return dest

        if not os.path.exists(dest):
            os.makedirs(dest)

        try:
            if "*" in step.get("file_name"):
                files = glob(f"{path}/{step.get('file_name')}")
                if not files:
                    logging.info(f"File(s) not found in {path}")
                    return False
                for fg in files:
                    _name = fg.split("/")[-1]
                    _path = os.path.join(path, _name)
                    _dest = os.path.join(dest, _name)
                    logging.info(f"Copying {_name} to {_dest}")

                    if os.path.exists(_dest) and os.path.islink(_dest):
                        os.unlink(_dest)

                    try:
                        shutil.copyfile(_path, _dest)
                    except shutil.SameFileError:
                        logging.info(
                            f"{_name} already exists at the same version, skipping."
                        )
            else:
                _name = step.get("file_name")
                _dest = os.path.join(dest, _name)
                logging.info(f"Copying {_name} to {_dest}")

                if os.path.exists(_dest) and os.path.islink(_dest):
                    os.unlink(_dest)

                try:
                    shutil.copyfile(os.path.join(path, _name), _dest)
                except shutil.SameFileError:
                    logging.info(
                        f"{_name} already exists at the same version, skipping."
                    )

        except Exception as e:
            print(e)
            logging.warning("An error occurred while copying dlls.")
            return False

        return True

    @staticmethod
    def __step_register_dll(config: BottleConfig, step: dict):
        """Register one or more dll and ActiveX control"""
        regsvr32 = Regsvr32(config)

        for dll in step.get("dlls", []):
            regsvr32.register(dll)

        return True

    @staticmethod
    def __step_override_dll(config: BottleConfig, step: dict):
        """Register a new override for each dll."""
        reg = Reg(config)

        if step.get("url") and step.get("url").startswith("temp/"):
            path = step["url"].replace("temp/", f"{Paths.temp}/")
            dlls = glob(os.path.join(path, step.get("dll")))

            bundle = {"HKEY_CURRENT_USER\\Software\\Wine\\DllOverrides": []}

            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")}
                )

            reg.import_bundle(bundle)
            return True

        if 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"),
        )
        return True

    @staticmethod
    def __step_set_register_key(config: BottleConfig, step: dict):
        """Set a registry key."""
        reg = Reg(config)
        reg.add(
            key=step.get("key"),
            value=step.get("value"),
            data=step.get("data"),
            value_type=step.get("type"),
        )
        return True

    @staticmethod
    def __step_register_font(config: BottleConfig, step: dict):
        """Register a font in the registry."""
        reg = Reg(config)
        reg.add(
            key="HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Fonts",
            value=step.get("name"),
            data=step.get("file"),
        )
        return True

    @staticmethod
    def __step_replace_font(config: BottleConfig, step: dict):
        """Register a font replacement in the registry."""
        reg = Reg(config)
        target_font = step.get("font")
        replaces = step.get("replace")

        if not isinstance(replaces, list):
            logging.warning("Invalid replace_font, 'replace' field should be list.")
            return False

        regs = [
            RegItem(
                key="HKEY_CURRENT_USER\\Software\\Wine\\Fonts\\Replacements",
                value=r,
                value_type="",
                data=target_font,
            )
            for r in replaces
        ]
        reg.bulk_add(regs)
        return True

    @staticmethod
    def __step_set_windows(config: BottleConfig, step: dict):
        """Set the Windows version."""
        rk = RegKeys(config)
        rk.lg_set_windows(step.get("version"))
        return True

    @staticmethod
    def __step_use_windows(config: BottleConfig, step: dict):
        """Set a Windows version per program."""
        rk = RegKeys(config)
        rk.set_app_default(step.get("version"), step.get("executable"))
        return True


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

import os
import time
import shutil
import uuid
import datetime
from glob import glob
import pefile
import patoolib
import yara
import struct
import json
import subprocess

from bottles.backend.globals import Paths
from bottles.backend.models.config import BottleConfig
from bottles.backend.models.result import Result
from bottles.backend.state import SignalManager, Signals
from bottles.backend.logger import Logger
from bottles.backend.params import APP_ID

from gi.repository import Gio, GLib

logging = Logger()

STEP_DELAY = 0.12 # to allow seeing the analysis progress


class EagleManager:
    """Windows executables analysis engine using pefile and YARA."""

    VS_PRODUCTS = {
        0x0104: "VS 2017", 0x0105: "VS 2017", 0x0106: "VS 2017",
        0x010A: "VS 2019", 0x010B: "VS 2019", 0x010C: "VS 2019",
        0x010D: "VS 2019", 0x010E: "VS 2019", 0x010F: "VS 2019",
        0x0114: "VS 2022", 0x0115: "VS 2022", 0x0116: "VS 2022",
        0x0117: "VS 2022", 0x0118: "VS 2022", 0x0119: "VS 2022",
        0x00FF: "VS 2015", 0x00EB: "VS 2013", 0x00D9: "VS 2012",
        0x00C7: "VS 2010", 0x0083: "VS 2008", 0x006D: "VS 2005",
    }

    DLL_MAPPINGS = {
        "d3d8.dll": ("DirectX 8", "Graphics"), "d3d9.dll": ("DirectX 9", "Graphics"),
        "d3d10.dll": ("DirectX 10", "Graphics"), "d3d11.dll": ("DirectX 11", "Graphics"),
        "d3d12.dll": ("DirectX 12", "Graphics"), "dxgi.dll": ("DXGI", "Graphics"),
        "d3dcompiler_47.dll": ("D3D Compiler", "Graphics"),
        "opengl32.dll": ("OpenGL", "Graphics"), "vulkan-1.dll": ("Vulkan", "Graphics"),
        "nvapi.dll": ("NVAPI", "Graphics"), "nvapi64.dll": ("NVAPI", "Graphics"),
        "amd_ags_x64.dll": ("AMD AGS", "Graphics"),
        "physx_loader.dll": ("PhysX", "Graphics"), "physx3_x64.dll": ("PhysX", "Graphics"),
        "d3dx9_43.dll": ("D3DX9", "Graphics"), "d3dx11_43.dll": ("D3DX11", "Graphics"),
        "nvngx_dlss.dll": ("DLSS", "Upscaling"), "nvngx_dlssg.dll": ("DLSS 3 Frame Gen", "Upscaling"),
        "libxess.dll": ("Intel XeSS", "Upscaling"),
        "ffx_fsr2_api_x64.dll": ("AMD FSR 2", "Upscaling"), "amd_fidelityfx_dx12.dll": ("AMD FSR", "Upscaling"),
        "amd_fidelityfx_vk.dll": ("AMD FSR", "Upscaling"),
        "xaudio2_7.dll": ("XAudio 2.7", "Audio"), "xaudio2_9.dll": ("XAudio 2.9", "Audio"),
        "x3daudio1_7.dll": ("X3DAudio", "Audio"),
        "fmod.dll": ("FMOD", "Audio"), "fmod64.dll": ("FMOD", "Audio"),
        "fmodstudio.dll": ("FMOD Studio", "Audio"),
        "wwise.dll": ("Wwise", "Audio"),
        "binkw32.dll": ("Bink", "Audio"), "binkw64.dll": ("Bink", "Audio"),
        "openal32.dll": ("OpenAL", "Audio"), "dsound.dll": ("DirectSound", "Audio"),
        "miles32.dll": ("Miles", "Audio"), "mss32.dll": ("Miles", "Audio"),
        "mscoree.dll": (".NET Framework", "Runtimes"),
        "clr.dll": (".NET CLR", "Runtimes"),
        "hostfxr.dll": (".NET Core/5+", "Runtimes"),
        "coreclr.dll": (".NET Core CLR", "Runtimes"),
        "msvcp100.dll": ("VC++ 2010", "Runtimes"), "msvcr100.dll": ("VC++ 2010", "Runtimes"),
        "msvcp110.dll": ("VC++ 2012", "Runtimes"), "msvcr110.dll": ("VC++ 2012", "Runtimes"),
        "msvcp120.dll": ("VC++ 2013", "Runtimes"), "msvcr120.dll": ("VC++ 2013", "Runtimes"),
        "msvcp140.dll": ("VC++ 2015-22", "Runtimes"), "vcruntime140.dll": ("VC++ 2015-22", "Runtimes"),
        "vcruntime140_1.dll": ("VC++ 2015-22", "Runtimes"),
        "ucrtbase.dll": ("UCRT", "Runtimes"),
        "mono-2.0-bdwgc.dll": ("Mono", "Runtimes"),
        "jvm.dll": ("Java", "Runtimes"),
        "python3.dll": ("Python", "Runtimes"), "python311.dll": ("Python 3.11", "Runtimes"),
        "lua54.dll": ("Lua 5.4", "Runtimes"),
        "xinput1_3.dll": ("XInput 1.3", "Input"), "xinput1_4.dll": ("XInput 1.4", "Input"),
        "dinput8.dll": ("DirectInput", "Input"),
        "sdl2.dll": ("SDL2", "Input"),
        "steam_api.dll": ("Steamworks", "Social/DRM"), "steam_api64.dll": ("Steamworks", "Social/DRM"),
        "galaxy.dll": ("GOG Galaxy", "Social/DRM"), "galaxy64.dll": ("GOG Galaxy", "Social/DRM"),
        "eossdk-win64-shipping.dll": ("Epic", "Social/DRM"),
        "discord_game_sdk.dll": ("Discord", "Social/DRM"),
        "uplay_r1_loader64.dll": ("Ubisoft", "Social/DRM"),
        "unityplayer.dll": ("Unity", "Engines"), "gameassembly.dll": ("Unity IL2CPP", "Engines"),
        "libcef.dll": ("CEF", "Engines"),
        "tier0.dll": ("Source", "Engines"), "vstdlib.dll": ("Source", "Engines"),
        "cryrenderd3d11.dll": ("CryEngine", "Engines"),
        "qt5core.dll": ("Qt 5", "Frameworks"), "qt6core.dll": ("Qt 6", "Frameworks"),
        "electron.dll": ("Electron", "Frameworks"),
        "easyanticheat.dll": ("EAC", "Protection"), "easyanticheat_x64.dll": ("EAC", "Protection"),
        "battleye.dll": ("BattlEye", "Protection"), "beclient_x64.dll": ("BattlEye", "Protection"),
        "vmprotectsdk64.dll": ("VMProtect", "Protection"),
        "denuvo64.dll": ("Denuvo", "Protection"),
    }

    PACKER_SECTIONS = {
        "UPX0": "UPX", "UPX1": "UPX", ".upx": "UPX",
        ".vmp0": "VMProtect", ".vmp1": "VMProtect", "VMP0": "VMProtect",
        ".themida": "Themida",
        ".enigma": "Enigma",
    }

    _yara_rules = None

    def __init__(self, config: BottleConfig):
        self.config = config
        self._load_yara_rules()

    @classmethod
    def _load_yara_rules(cls) -> None:
        """Load YARA rules from the bundled file."""

        logging.info("[Eagle] Reloading YARA rules...")
        rules_path = os.path.join(os.path.dirname(__file__), "eagle.yar")
        if os.path.exists(rules_path):
            try:
                cls._yara_rules = yara.compile(filepath=rules_path)
                logging.info("[Eagle] YARA rules loaded")
            except Exception as e:
                logging.warning(f"[Eagle] Failed to load YARA rules: {e}")
                cls._yara_rules = None

    def _is_safe_neighbor_dir(self, directory: str) -> bool:
        """Check if directory is safe for neighbor scanning (not a common clutter folder)."""
        try:
            path = os.path.realpath(directory)
            
            if "/run/user/" in path and "/doc/" in path:
                logging.info(f"[Eagle] Blocking neighbor scan for portal path: {path}")
                return False

            unsafe_dirs = set()
            home = GLib.get_home_dir()
            if home:
                unsafe_dirs.add(os.path.realpath(home))
                for d in ["Downloads", "Desktop", "Documents", "Templates"]:
                    unsafe_dirs.add(os.path.realpath(os.path.join(home, d)))

            for xdg_type in [
                GLib.UserDirectory.DIRECTORY_DOWNLOAD,
                GLib.UserDirectory.DIRECTORY_DESKTOP,
                GLib.UserDirectory.DIRECTORY_DOCUMENTS,
                GLib.UserDirectory.DIRECTORY_TEMPLATES
            ]:
                xdg_path = GLib.get_user_special_dir(xdg_type)
                if xdg_path:
                    unsafe_dirs.add(os.path.realpath(xdg_path))

            if path in unsafe_dirs or path == os.path.realpath(os.path.expanduser("~")):
                logging.info(f"[Eagle] Neighbor scan blocked for unsafe path: {path}")
                return False
                
            return True
        except Exception as e:
            logging.warning(f"[Eagle] Error checking directory safety: {e}")
            return True

    def _send_step(self, msg: str, delay: bool = True) -> None:
        """Send a step update to the UI with optional delay."""
        SignalManager.send(Signals.EagleStep, Result(status=True, data=msg))
        logging.info(f"[Eagle] {msg}")
        if delay:
            time.sleep(STEP_DELAY)

    def _scan_yara(self, file_path: str, insights: dict, source: str = "Main Executable") -> list:
        """Run YARA scan on a file and update insights."""
        if self._yara_rules is None:
            return []

        matches = []
        try:
            results = self._yara_rules.match(file_path, timeout=30)
            for match in results:
                category = match.meta.get('category', 'Unknown')
                name = match.meta.get('name', match.rule)
                warning_desc = match.meta.get('description', '')
                severity = match.meta.get('severity', 'info')

                if category not in insights:
                    insights[category] = []
                
                existing_names = [i['name'] for i in insights[category]] if isinstance(insights[category], list) else []
                context = []
                for valid_match in match.strings:
                    ident = None
                    data = None
                    
                    if isinstance(valid_match, tuple):
                        _, ident, data = valid_match
                    else:
                         try:
                             ident = valid_match.identifier
                             if hasattr(valid_match, "instances") and valid_match.instances:
                                 data = valid_match.instances[0].matched_data
                         except:
                             continue
                    
                    rule_name = match.rule
                    should_capture = False
                    
                    if ident and data:
                        if ident == "$capture":
                            should_capture = True
                        elif "key" in ident or "reg" in ident:
                             should_capture = True
                        elif rule_name in ["Node_System_Commands", "Node_Native_Module", "Node_Kernel_Driver", "Driver_Service"]:
                            should_capture = True

                    if should_capture:
                        try:
                            decoded = data.decode('utf-16le') if b'\x00' in data else data.decode('utf-8')
                            clean = decoded.strip().replace('\x00', '')
                            
                            clean_upper = clean.upper()
                            roots = ["HKEY_LOCAL_MACHINE", "HKEY_CURRENT_USER", "HKLM", "HKCU", "HKLM\\", "HKCU\\"]
                            
                            if clean_upper not in roots and len(clean) > 2 and clean not in context:
                                context.append(clean)
                        except:
                            pass
                
                context_list = context[:20]

                if name not in existing_names:
                    if category == "Warning":
                        insights[category].append({
                            "name": name,
                            "description": warning_desc,
                            "severity": severity,
                            "source": source,
                            "context": context_list
                        })
                        self._send_step(f"[!] Warning: {name} ({source})")
                    else:
                        insights[category].append({
                            "name": name,
                            "source": source,
                            "context": context_list
                        })
                        self._send_step(f"[{category}] {name} ({source})", delay=False)
                matches.append({"rule": match.rule, "category": category, "name": name, "severity": severity, "source": source})
        except Exception as e:
            logging.error(f"[Eagle] YARA scan failed on {file_path}: {e}")

        return matches

    def _extract_asar(self, asar_path: str) -> tuple:
        """Extract ASAR archive using pure Python."""
        extract_dir = os.path.join(Paths.temp, f"eagle_asar_{uuid.uuid4().hex[:8]}")
        os.makedirs(extract_dir, exist_ok=True)
        extracted_files = []

        try:
            self._send_step(f"Extracting ASAR archive: {os.path.basename(asar_path)}...")
            with open(asar_path, 'rb') as f:
                data = f.read(16)
                if len(data) < 16:
                    return [], None
                
                header_json_len = struct.unpack('<I', data[12:16])[0]
                
                header_json_data = f.read(header_json_len)
                header = json.loads(header_json_data.decode('utf-8'))
                
                # Base offset for files
                base_offset = 16 + header_json_len
                
                def walk_files(dir_structure, current_path=""):
                    if "files" in dir_structure:
                        for name, info in dir_structure["files"].items():
                            new_path = os.path.join(current_path, name)
                            if "files" in info:
                                # Directory
                                os.makedirs(os.path.join(extract_dir, new_path), exist_ok=True)
                                walk_files(info, new_path)
                            elif "offset" in info and "size" in info:
                                # File
                                try:
                                    ext = os.path.splitext(name)[1].lower()
                                    if ext not in [".js", ".json", ".node", ".html", ".htm"]:
                                        continue

                                    offset = int(info["offset"]) + base_offset
                                    size = int(info["size"])
                                    
                                    out_path = os.path.join(extract_dir, new_path)
                                    os.makedirs(os.path.dirname(out_path), exist_ok=True)
                                    
                                    current_pos = f.tell()
                                    f.seek(offset)
                                    content = f.read(size)
                                    f.seek(current_pos)
                                    
                                    with open(out_path, 'wb') as out_f:
                                        out_f.write(content)
                                    
                                    extracted_files.append(out_path)
                                except Exception as e:
                                    logging.warning(f"[Eagle] Failed to extract {new_path}: {e}")

                walk_files(header)
                self._send_step(f"Extracted {len(extracted_files)} files from ASAR")
                return extracted_files, extract_dir

        except Exception as e:
            logging.error(f"[Eagle] ASAR extraction failed: {e}")
            return [], None

    def _extract_installer(self, installer_path: str, installer_type: str) -> tuple:
        """Extract installer and return list of extracted exe/dll paths."""
        extract_dir = os.path.join(Paths.temp, f"eagle_{uuid.uuid4().hex[:8]}")
        os.makedirs(extract_dir, exist_ok=True)
        extracted_files = []

        try:
            self._send_step(f"Extracting {installer_type} installer...")

            seven_z = shutil.which("7z") or shutil.which("7za") or shutil.which("7zr")
            if seven_z:
                cmd = [seven_z, "x"
Download .txt
gitextract_3jwfbjfe/

├── .gitattributes
├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug.yml
│   │   ├── config.yml
│   │   ├── feature-request.yml
│   │   ├── feedback.md
│   │   └── mirror.yml
│   ├── dependabot.yml
│   ├── pull_request_template.md
│   └── workflows/
│       ├── build_flatpak.yml
│       ├── close-issues.yml
│       ├── pre-commit.yml
│       └── update-manifest.yml
├── .gitignore
├── .gitmodules
├── .mailmap
├── .pre-commit-config.yaml
├── CODE_OF_CONDUCT.md
├── CODING_GUIDE.md
├── CONTRIBUTING.md
├── COPYING.md
├── README.md
├── VERSION
├── VERSION_UPDATE.md
├── bottles/
│   ├── __init__.py
│   ├── backend/
│   │   ├── __init__.py
│   │   ├── cabextract.py
│   │   ├── diff.py
│   │   ├── dlls/
│   │   │   ├── __init__.py
│   │   │   ├── dll.py
│   │   │   ├── dxvk.py
│   │   │   ├── latencyflex.py
│   │   │   ├── meson.build
│   │   │   ├── nvapi.py
│   │   │   └── vkd3d.py
│   │   ├── downloader.py
│   │   ├── globals.py
│   │   ├── health.py
│   │   ├── logger.py
│   │   ├── managers/
│   │   │   ├── __init__.py
│   │   │   ├── backup.py
│   │   │   ├── component.py
│   │   │   ├── conf.py
│   │   │   ├── data.py
│   │   │   ├── dependency.py
│   │   │   ├── eagle.py
│   │   │   ├── eagle.yar
│   │   │   ├── epicgamesstore.py
│   │   │   ├── importer.py
│   │   │   ├── installer.py
│   │   │   ├── journal.py
│   │   │   ├── library.py
│   │   │   ├── manager.py
│   │   │   ├── meson.build
│   │   │   ├── origin.py
│   │   │   ├── playtime.py
│   │   │   ├── queue.py
│   │   │   ├── registry_rule.py
│   │   │   ├── repository.py
│   │   │   ├── runtime.py
│   │   │   ├── sandbox.py
│   │   │   ├── steam.py
│   │   │   ├── steamgriddb.py
│   │   │   ├── template.py
│   │   │   ├── thumbnail.py
│   │   │   ├── ubisoftconnect.py
│   │   │   └── versioning.py
│   │   ├── meson.build
│   │   ├── models/
│   │   │   ├── __init__.py
│   │   │   ├── config.py
│   │   │   ├── enum.py
│   │   │   ├── meson.build
│   │   │   ├── process.py
│   │   │   ├── registry_rule.py
│   │   │   ├── result.py
│   │   │   ├── samples.py
│   │   │   └── vdict.py
│   │   ├── params.py
│   │   ├── repos/
│   │   │   ├── __init__.py
│   │   │   ├── component.py
│   │   │   ├── dependency.py
│   │   │   ├── installer.py
│   │   │   ├── meson.build
│   │   │   └── repo.py
│   │   ├── runner.py
│   │   ├── state.py
│   │   ├── utils/
│   │   │   ├── __init__.py
│   │   │   ├── connection.py
│   │   │   ├── decorators.py
│   │   │   ├── display.py
│   │   │   ├── file.py
│   │   │   ├── generic.py
│   │   │   ├── gpu.py
│   │   │   ├── gsettings_stub.py
│   │   │   ├── imagemagick.py
│   │   │   ├── json.py
│   │   │   ├── lnk.py
│   │   │   ├── manager.py
│   │   │   ├── meson.build
│   │   │   ├── nvidia.py
│   │   │   ├── proc.py
│   │   │   ├── singleton.py
│   │   │   ├── snake.py
│   │   │   ├── steam.py
│   │   │   ├── terminal.py
│   │   │   ├── threading.py
│   │   │   ├── vdf.py
│   │   │   ├── vulkan.py
│   │   │   ├── wine.py
│   │   │   └── yaml.py
│   │   └── wine/
│   │       ├── __init__.py
│   │       ├── catalogs.py
│   │       ├── cmd.py
│   │       ├── control.py
│   │       ├── drives.py
│   │       ├── eject.py
│   │       ├── executor.py
│   │       ├── expand.py
│   │       ├── explorer.py
│   │       ├── hh.py
│   │       ├── icinfo.py
│   │       ├── meson.build
│   │       ├── msiexec.py
│   │       ├── net.py
│   │       ├── notepad.py
│   │       ├── oleview.py
│   │       ├── progman.py
│   │       ├── reg.py
│   │       ├── regedit.py
│   │       ├── register.py
│   │       ├── regkeys.py
│   │       ├── regsvr32.py
│   │       ├── rundll32.py
│   │       ├── start.py
│   │       ├── taskmgr.py
│   │       ├── uninstaller.py
│   │       ├── wineboot.py
│   │       ├── winebridge.py
│   │       ├── winecfg.py
│   │       ├── winecommand.py
│   │       ├── winedbg.py
│   │       ├── winefile.py
│   │       ├── winepath.py
│   │       ├── wineprogram.py
│   │       ├── wineserver.py
│   │       ├── winhelp.py
│   │       └── xcopy.py
│   ├── frontend/
│   │   ├── __init__.py
│   │   ├── bottles.py
│   │   ├── cli/
│   │   │   ├── __init__.py
│   │   │   ├── cli.py
│   │   │   └── meson.build
│   │   ├── main.py
│   │   ├── meson.build
│   │   ├── operation.py
│   │   ├── params.py
│   │   ├── ui/
│   │   │   ├── bottle-row.blp
│   │   │   ├── bottles.gresource.xml
│   │   │   ├── check-row.blp
│   │   │   ├── component-entry.blp
│   │   │   ├── dependency-entry.blp
│   │   │   ├── details-bottle.blp
│   │   │   ├── details-dependencies.blp
│   │   │   ├── details-installers.blp
│   │   │   ├── details-preferences.blp
│   │   │   ├── details-registry-rules.blp
│   │   │   ├── details-taskmanager.blp
│   │   │   ├── details-versioning.blp
│   │   │   ├── details.blp
│   │   │   ├── dialog-bottle-picker.blp
│   │   │   ├── dialog-crash-report.blp
│   │   │   ├── dialog-dependency-install.blp
│   │   │   ├── dialog-deps-check.blp
│   │   │   ├── dialog-display.blp
│   │   │   ├── dialog-dll-overrides.blp
│   │   │   ├── dialog-drives.blp
│   │   │   ├── dialog-duplicate.blp
│   │   │   ├── dialog-env-vars.blp
│   │   │   ├── dialog-exclusion-patterns.blp
│   │   │   ├── dialog-gamescope.blp
│   │   │   ├── dialog-installer.blp
│   │   │   ├── dialog-journal.blp
│   │   │   ├── dialog-launch-options.blp
│   │   │   ├── dialog-mangohud.blp
│   │   │   ├── dialog-playtime-graph.blp
│   │   │   ├── dialog-proton-alert.blp
│   │   │   ├── dialog-registry-rules.blp
│   │   │   ├── dialog-rename.blp
│   │   │   ├── dialog-run-args.blp
│   │   │   ├── dialog-sandbox.blp
│   │   │   ├── dialog-upgrade-versioning.blp
│   │   │   ├── dialog-versioning-branch.blp
│   │   │   ├── dialog-versioning-commit.blp
│   │   │   ├── dialog-versioning-manage-branches.blp
│   │   │   ├── dialog-versioning-settings.blp
│   │   │   ├── dialog-vkbasalt.blp
│   │   │   ├── dialog-vmtouch.blp
│   │   │   ├── dialog-winebridge-update.blp
│   │   │   ├── dll-override-entry.blp
│   │   │   ├── drive-entry.blp
│   │   │   ├── eagle.blp
│   │   │   ├── env-var-entry.blp
│   │   │   ├── exclusion-pattern-entry.blp
│   │   │   ├── help-overlay.blp
│   │   │   ├── importer-entry.blp
│   │   │   ├── importer.blp
│   │   │   ├── inherited-env-entry.blp
│   │   │   ├── installer-entry.blp
│   │   │   ├── library-entry.blp
│   │   │   ├── library.blp
│   │   │   ├── list.blp
│   │   │   ├── loading.blp
│   │   │   ├── local-resource-entry.blp
│   │   │   ├── meson.build
│   │   │   ├── new-bottle-dialog.blp
│   │   │   ├── onboard.blp
│   │   │   ├── preferences.blp
│   │   │   ├── program-entry.blp
│   │   │   ├── registry-rule-entry.blp
│   │   │   ├── state-entry.blp
│   │   │   ├── style-dark.css
│   │   │   ├── style.css
│   │   │   ├── task-entry.blp
│   │   │   └── window.blp
│   │   ├── utils/
│   │   │   ├── __init__.py
│   │   │   ├── common.py
│   │   │   ├── filters.py
│   │   │   ├── gtk.py
│   │   │   ├── meson.build
│   │   │   ├── playtime.py
│   │   │   └── sh.py
│   │   ├── views/
│   │   │   ├── __init__.py
│   │   │   ├── bottle_dependencies.py
│   │   │   ├── bottle_details.py
│   │   │   ├── bottle_installers.py
│   │   │   ├── bottle_preferences.py
│   │   │   ├── bottle_registry_rules.py
│   │   │   ├── bottle_taskmanager.py
│   │   │   ├── bottle_versioning.py
│   │   │   ├── details.py
│   │   │   ├── eagle.py
│   │   │   ├── importer.py
│   │   │   ├── library.py
│   │   │   ├── list.py
│   │   │   ├── loading.py
│   │   │   ├── meson.build
│   │   │   ├── new_bottle_dialog.py
│   │   │   └── preferences.py
│   │   ├── widgets/
│   │   │   ├── __init__.py
│   │   │   ├── component.py
│   │   │   ├── dependency.py
│   │   │   ├── executable.py
│   │   │   ├── importer.py
│   │   │   ├── installer.py
│   │   │   ├── library.py
│   │   │   ├── meson.build
│   │   │   ├── playtimechart_hourly.py
│   │   │   ├── playtimechart_monthly.py
│   │   │   ├── playtimechart_weekly.py
│   │   │   ├── program.py
│   │   │   └── state.py
│   │   └── windows/
│   │       ├── __init__.py
│   │       ├── bottlepicker.py
│   │       ├── crash.py
│   │       ├── dependency_install.py
│   │       ├── depscheck.py
│   │       ├── display.py
│   │       ├── dlloverrides.py
│   │       ├── drives.py
│   │       ├── duplicate.py
│   │       ├── envvars.py
│   │       ├── exclusionpatterns.py
│   │       ├── funding.py
│   │       ├── gamescope.py
│   │       ├── generic.py
│   │       ├── generic_cli.py
│   │       ├── installer.py
│   │       ├── journal.py
│   │       ├── launchoptions.py
│   │       ├── mangohud.py
│   │       ├── meson.build
│   │       ├── onboard.py
│   │       ├── playtimegraph.py
│   │       ├── protonalert.py
│   │       ├── registry_rules.py
│   │       ├── rename.py
│   │       ├── sandbox.py
│   │       ├── upgradeversioning.py
│   │       ├── versioning_branch.py
│   │       ├── versioning_commit.py
│   │       ├── versioning_manage_branches.py
│   │       ├── versioning_settings.py
│   │       ├── vkbasalt.py
│   │       ├── vmtouch.py
│   │       ├── window.py
│   │       └── winebridgeupdate.py
│   ├── fvs/
│   │   ├── __init__.py
│   │   ├── exceptions.py
│   │   ├── meson.build
│   │   └── repo.py
│   ├── meson.build
│   └── tests/
│       ├── __init__.py
│       ├── backend/
│       │   ├── __init__.py
│       │   ├── integration/
│       │   │   └── playtime/
│       │   │       ├── conftest.py
│       │   │       ├── test_aggregation.py
│       │   │       ├── test_disabled_tracking.py
│       │   │       ├── test_failure_run.py
│       │   │       ├── test_playtime_signals.py
│       │   │       ├── test_recovery.py
│       │   │       ├── test_schema_meta.py
│       │   │       ├── test_successful_run.py
│       │   │       ├── test_uniqueness_retry.py
│       │   │       └── test_wine_executor_playtime.py
│       │   ├── manager/
│       │   │   ├── __init__.py
│       │   │   ├── test_manager.py
│       │   │   └── test_playtime.py
│       │   ├── state/
│       │   │   ├── __init__.py
│       │   │   └── test_events.py
│       │   ├── utils/
│       │   │   ├── __init__.py
│       │   │   └── test_generic.py
│       │   └── wine/
│       │       └── test_executor.py
│       ├── conftest.py
│       └── frontend/
│           └── test_playtime_service.py
├── build-aux/
│   ├── build.sh
│   ├── com.usebottles.bottles.Devel.json
│   ├── fvs2-modules.txt
│   ├── fvs2.yaml
│   ├── install.sh
│   └── pypi-deps.yaml
├── data/
│   ├── com.usebottles.bottles.desktop.in.in
│   ├── com.usebottles.bottles.gschema.xml
│   ├── com.usebottles.bottles.metainfo.xml.in.in
│   ├── data.gresource.xml.in
│   ├── icons/
│   │   └── meson.build
│   └── meson.build
├── meson.build
├── meson_options.txt
├── mypy.ini
├── po/
│   ├── LINGUAS
│   ├── POTFILES
│   ├── README.md
│   ├── ar.po
│   ├── az.po
│   ├── be.po
│   ├── bg.po
│   ├── bn.po
│   ├── bottles.pot
│   ├── bs.po
│   ├── ca.po
│   ├── ckb.po
│   ├── cs.po
│   ├── da.po
│   ├── de.po
│   ├── el.po
│   ├── eo.po
│   ├── es.po
│   ├── et.po
│   ├── eu.po
│   ├── fa.po
│   ├── fi.po
│   ├── fr.po
│   ├── gl.po
│   ├── he.po
│   ├── hi.po
│   ├── hr.po
│   ├── hu.po
│   ├── id.po
│   ├── ie.po
│   ├── it.po
│   ├── ja.po
│   ├── ka.po
│   ├── kab.po
│   ├── ko.po
│   ├── kw.po
│   ├── lt.po
│   ├── meson.build
│   ├── ms.po
│   ├── nb_NO.po
│   ├── nl.po
│   ├── oc.po
│   ├── pl.po
│   ├── pt.po
│   ├── pt_BR.po
│   ├── ro.po
│   ├── ru.po
│   ├── sk.po
│   ├── sl.po
│   ├── sr.po
│   ├── sv.po
│   ├── ta.po
│   ├── th.po
│   ├── tr.po
│   ├── uk.po
│   ├── vi.po
│   ├── yi.po
│   ├── zh_Hans.po
│   └── zh_Hant.po
├── pyproject.toml
├── pyrightconfig.json
├── requirements.dev.txt
├── requirements.txt
├── test_path_normalization.py
└── tests/
    ├── conftest.py
    └── test_fvs.py
Download .txt
SYMBOL INDEX (1649 symbols across 194 files)

FILE: bottles/backend/cabextract.py
  class CabExtract (line 29) | class CabExtract:
    method __init__ (line 43) | def __init__(self):
    method run (line 46) | def run(
    method __checks (line 66) | def __checks(self):
    method __extract (line 73) | def __extract(self) -> bool:

FILE: bottles/backend/diff.py
  class Diff (line 5) | class Diff:
    method hashify (line 14) | def hashify(path: str) -> dict:
    method file_hashify (line 46) | def file_hashify(path: str) -> str:
    method compare (line 54) | def compare(parent: dict, child: dict) -> dict:

FILE: bottles/backend/dlls/dll.py
  class DLLComponent (line 32) | class DLLComponent:
    method __init__ (line 38) | def __init__(self, version: str):
    method get_base_path (line 45) | def get_base_path(version: str) -> str:
    method get_override_keys (line 50) | def get_override_keys() -> str:
    method check (line 53) | def check(self) -> bool:
    method install (line 78) | def install(self, config: BottleConfig, overrides_only: bool = False, ...
    method uninstall (line 109) | def uninstall(self, config: BottleConfig, exclude=None):
    method __get_sys_path (line 138) | def __get_sys_path(config: BottleConfig, path: str) -> str:
    method __install_dll (line 151) | def __install_dll(
    method __uninstall_dll (line 199) | def __uninstall_dll(self, config, path: str, dll: str):

FILE: bottles/backend/dlls/dxvk.py
  class DXVKComponent (line 22) | class DXVKComponent(DLLComponent):
    method get_override_keys (line 29) | def get_override_keys() -> str:
    method get_base_path (line 33) | def get_base_path(version: str) -> str:

FILE: bottles/backend/dlls/latencyflex.py
  class LatencyFleXComponent (line 22) | class LatencyFleXComponent(DLLComponent):
    method get_override_keys (line 31) | def get_override_keys() -> str:
    method get_base_path (line 35) | def get_base_path(version: str) -> str:

FILE: bottles/backend/dlls/nvapi.py
  class NVAPIComponent (line 30) | class NVAPIComponent(DLLComponent):
    method get_override_keys (line 38) | def get_override_keys() -> str:
    method get_base_path (line 43) | def get_base_path(version: str) -> str:
    method check_bottle_nvngx (line 47) | def check_bottle_nvngx(bottle_path: str, bottle_config: BottleConfig):

FILE: bottles/backend/dlls/vkd3d.py
  class VKD3DComponent (line 22) | class VKD3DComponent(DLLComponent):
    method get_override_keys (line 29) | def get_override_keys() -> str:
    method get_base_path (line 33) | def get_base_path(version: str) -> str:

FILE: bottles/backend/downloader.py
  class DownloadCancelled (line 36) | class DownloadCancelled(Exception):
  class Downloader (line 40) | class Downloader:
    method __init__ (line 47) | def __init__(
    method download (line 60) | def download(self) -> Result:
    method __progress (line 107) | def __progress(self, received_size, total_size):

FILE: bottles/backend/globals.py
  class Paths (line 26) | class Paths:
    method is_vkbasalt_available (line 55) | def is_vkbasalt_available():
  class TrdyPaths (line 67) | class TrdyPaths:

FILE: bottles/backend/health.py
  class HealthChecker (line 32) | class HealthChecker:
    method __init__ (line 54) | def __init__(self):
    method check_x11 (line 68) | def check_x11(self):
    method check_wayland (line 76) | def check_wayland():
    method check_desktop (line 79) | def check_desktop(self):
    method get_bottles_envs (line 83) | def get_bottles_envs():
    method check_system_info (line 95) | def check_system_info(self):
    method get_disk_data (line 99) | def get_disk_data(self):
    method get_ram_data (line 103) | def get_ram_data(self):
    method get_results (line 118) | def get_results(self, plain: bool = False):

FILE: bottles/backend/logger.py
  class Logger (line 28) | class Logger(logging.getLoggerClass()):
    method __color (line 40) | def __color(self, level, message: str):
    method __init__ (line 46) | def __init__(self, formatter=None):
    method debug (line 58) | def debug(self, message, **kwargs):
    method info (line 63) | def info(self, message, jn=False, **kwargs):
    method warning (line 70) | def warning(self, message, jn=True, **kwargs):
    method error (line 77) | def error(self, message, jn=True, **kwargs):
    method critical (line 84) | def critical(self, message, jn=True, **kwargs):
    method write_log (line 92) | def write_log(data: list):
    method set_silent (line 112) | def set_silent(self):

FILE: bottles/backend/managers/backup.py
  class ProgressTrackingFilter (line 38) | class ProgressTrackingFilter:
    method __init__ (line 44) | def __init__(
    method __call__ (line 56) | def __call__(self, tarinfo: tarfile.TarInfo) -> Optional[tarfile.TarIn...
    method _update_progress (line 70) | def _update_progress(self):
  class BackupManager (line 78) | class BackupManager:
    method _validate_path (line 80) | def _validate_path(path: str) -> bool:
    method _calculate_dir_size (line 88) | def _calculate_dir_size(
    method _create_tarfile (line 120) | def _create_tarfile(
    method _safe_extract_tarfile (line 159) | def _safe_extract_tarfile(
    method export_backup (line 206) | def export_backup(config: BottleConfig, scope: str, path: str) -> Result:
    method exclude_filter (line 240) | def exclude_filter(tarinfo: tarfile.TarInfo) -> tarfile.TarInfo | None:
    method import_backup (line 249) | def import_backup(scope: str, path: str) -> Result:
    method _import_config_backup (line 267) | def _import_config_backup(path: str) -> Result:
    method _import_full_backup (line 285) | def _import_full_backup(path: str) -> Result:
    method duplicate_bottle (line 299) | def duplicate_bottle(config: BottleConfig, name: str) -> Result:
    method _duplicate_bottle_directory (line 314) | def _duplicate_bottle_directory(

FILE: bottles/backend/managers/component.py
  class ComponentManager (line 48) | class ComponentManager:
    method __init__ (line 49) | def __init__(self, manager, offline: bool = False):
    method get_component (line 55) | def get_component(self, name: str, plain: bool = False) -> dict:
    method fetch_catalog (line 58) | def fetch_catalog(self) -> dict:
    method download (line 129) | def download(
    method extract (line 272) | def extract(name: str, component: str, archive: str) -> bool:
    method install (line 327) | def install(
    method __post_rename (line 423) | def __post_rename(component_type: str, post: dict):
    method is_in_use (line 446) | def is_in_use(self, component_type: str, component_name: str):
    method uninstall (line 463) | def uninstall(self, component_type: str, component_name: str):

FILE: bottles/backend/managers/conf.py
  class ConfigManager (line 8) | class ConfigManager(object):
    method __init__ (line 9) | def __init__(
    method checks (line 29) | def checks(self):
    method read (line 38) | def read(self):
    method get_dict (line 70) | def get_dict(self):
    method write_json (line 74) | def write_json(self):
    method write_yaml (line 79) | def write_yaml(self):
    method write_ini (line 84) | def write_ini(self):
    method write_dict (line 97) | def write_dict(self, config_file: Optional[str] = None):
    method merge_dict (line 113) | def merge_dict(self, changes: dict):
    method del_key (line 130) | def del_key(self, key_struct: dict):

FILE: bottles/backend/managers/data.py
  class UserDataKeys (line 29) | class UserDataKeys:
  class DataManager (line 37) | class DataManager:
    method __init__ (line 47) | def __init__(self):
    method __get_data (line 50) | def __get_data(self):
    method __create_data_file (line 67) | def __create_data_file(self):
    method list (line 74) | def list(self):
    method set (line 78) | def set(self, key, value, of_type=None):
    method remove (line 95) | def remove(self, key):
    method get (line 103) | def get(self, key, default=None):

FILE: bottles/backend/managers/dependency.py
  class DependencyManager (line 49) | class DependencyManager:
    method __init__ (line 50) | def __init__(self, manager, offline: bool = False):
    method get_dependency (line 56) | def get_dependency(self, name: str, plain: bool = False) -> str | dict...
    method fetch_catalog (line 60) | def fetch_catalog(self) -> dict:
    method __notify_progress (line 79) | def __notify_progress(
    method __notify_progress_fraction (line 92) | def __notify_progress_fraction(
    method __build_progress_handler (line 99) | def __build_progress_handler(
    method __describe_step (line 124) | def __describe_step(step: dict) -> str:
    method install (line 156) | def install(
    method __perform_steps (line 299) | def __perform_steps(
    method __get_real_dest (line 390) | def __get_real_dest(config: BottleConfig, dest: str) -> str | bool:
    method __step_download_archive (line 416) | def __step_download_archive(
    method __step_install_exe_msi (line 440) | def __step_install_exe_msi(
    method __step_uninstall (line 491) | def __step_uninstall(config: BottleConfig, file_name: str) -> bool:
    method __step_cab_extract (line 499) | def __step_cab_extract(
    method __step_delete_dlls (line 556) | def __step_delete_dlls(self, config: BottleConfig, step: dict):
    method __step_get_from_cab (line 567) | def __step_get_from_cab(self, config: BottleConfig, step: dict):
    method __step_archive_extract (line 593) | def __step_archive_extract(
    method __step_install_fonts (line 641) | def __step_install_fonts(config: BottleConfig, step: dict):
    method __step_copy_dll (line 662) | def __step_copy_dll(self, config: BottleConfig, step: dict):
    method __step_register_dll (line 722) | def __step_register_dll(config: BottleConfig, step: dict):
    method __step_override_dll (line 732) | def __step_override_dll(config: BottleConfig, step: dict):
    method __step_set_register_key (line 766) | def __step_set_register_key(config: BottleConfig, step: dict):
    method __step_register_font (line 778) | def __step_register_font(config: BottleConfig, step: dict):
    method __step_replace_font (line 789) | def __step_replace_font(config: BottleConfig, step: dict):
    method __step_set_windows (line 812) | def __step_set_windows(config: BottleConfig, step: dict):
    method __step_use_windows (line 819) | def __step_use_windows(config: BottleConfig, step: dict):

FILE: bottles/backend/managers/eagle.py
  class EagleManager (line 44) | class EagleManager:
    method __init__ (line 122) | def __init__(self, config: BottleConfig):
    method _load_yara_rules (line 127) | def _load_yara_rules(cls) -> None:
    method _is_safe_neighbor_dir (line 140) | def _is_safe_neighbor_dir(self, directory: str) -> bool:
    method _send_step (line 175) | def _send_step(self, msg: str, delay: bool = True) -> None:
    method _scan_yara (line 182) | def _scan_yara(self, file_path: str, insights: dict, source: str = "Ma...
    method _extract_asar (line 264) | def _extract_asar(self, asar_path: str) -> tuple:
    method _extract_installer (line 326) | def _extract_installer(self, installer_path: str, installer_type: str)...
    method _cleanup_extraction (line 373) | def _cleanup_extraction(self, extract_dir: str) -> None:
    method analyze (line 381) | def analyze(self, executable_path: str) -> None:

FILE: bottles/backend/managers/epicgamesstore.py
  class EpicGamesStoreManager (line 26) | class EpicGamesStoreManager:
    method find_dat_path (line 28) | def find_dat_path(config: BottleConfig) -> str | None:
    method is_epic_supported (line 45) | def is_epic_supported(config: BottleConfig) -> bool:
    method get_installed_games (line 52) | def get_installed_games(config: BottleConfig) -> list:

FILE: bottles/backend/managers/importer.py
  class ImportManager (line 31) | class ImportManager:
    method __init__ (line 32) | def __init__(self, manager):
    method search_wineprefixes (line 36) | def search_wineprefixes() -> Result:
    method import_wineprefix (line 88) | def import_wineprefix(self, wineprefix: dict) -> Result:

FILE: bottles/backend/managers/installer.py
  class InstallerManager (line 39) | class InstallerManager:
    method __init__ (line 40) | def __init__(self, manager, offline: bool = False):
    method get_review (line 48) | def get_review(self, installer_name, parse: bool = True) -> str:
    method get_installer (line 58) | def get_installer(
    method fetch_catalog (line 68) | def fetch_catalog(self) -> dict:
    method get_icon_url (line 81) | def get_icon_url(self, installer):
    method __download_icon (line 85) | def __download_icon(self, config, executable: dict, manifest):
    method __process_local_resources (line 105) | def __process_local_resources(self, exe_msi_steps, installer):
    method __install_dependencies (line 115) | def __install_dependencies(
    method __perform_checks (line 141) | def __perform_checks(config, checks: dict):
    method __perform_steps (line 160) | def __perform_steps(self, config: BottleConfig, steps: list):
    method __step_run_winecommand (line 213) | def __step_run_winecommand(config: BottleConfig, step: dict):
    method __step_run_script (line 230) | def __step_run_script(config: BottleConfig, step: dict):
    method __step_update_config (line 261) | def __step_update_config(config: BottleConfig, step: dict):
    method __set_parameters (line 280) | def __set_parameters(self, config: BottleConfig, new_params: dict):
    method count_steps (line 316) | def count_steps(self, installer) -> dict:
    method has_local_resources (line 339) | def has_local_resources(self, installer):
    method install (line 355) | def install(

FILE: bottles/backend/managers/journal.py
  class JournalSeverity (line 29) | class JournalSeverity:
  class JournalManager (line 40) | class JournalManager:
    method __get_journal (line 49) | def __get_journal() -> dict:
    method __clean_old (line 79) | def __clean_old():
    method __save_journal (line 103) | def __save_journal(journal: Optional[dict] = None):
    method get (line 113) | def get(period: str = "today", plain: bool = False):
    method __filter_by_date (line 138) | def __filter_by_date(journal: dict, period: str):
    method get_event (line 170) | def get_event(event_id: str):
    method first_event_date (line 176) | def first_event_date():
    method write (line 193) | def write(severity: JournalSeverity, message: str):

FILE: bottles/backend/managers/library.py
  class LibraryManager (line 30) | class LibraryManager:
    method __init__ (line 39) | def __init__(self):
    method load_library (line 42) | def load_library(self, silent=False):
    method add_to_library (line 64) | def add_to_library(self, data: dict, config: BottleConfig):
    method download_thumbnail (line 81) | def download_thumbnail(self, _uuid: str, config: BottleConfig):
    method __already_in_library (line 98) | def __already_in_library(self, data: dict):
    method remove_from_library (line 108) | def remove_from_library(self, _uuid: str):
    method save_library (line 119) | def save_library(self, silent=False):
    method get_library (line 129) | def get_library(self):

FILE: bottles/backend/managers/manager.py
  class Manager (line 83) | class Manager(metaclass=Singleton):
    method __init__ (line 113) | def __init__(
    method checks (line 200) | def checks(
    method __del__ (line 316) | def __del__(self):
    method _initialize_playtime_tracker (line 324) | def _initialize_playtime_tracker(self) -> None:
    method _on_playtime_enabled_changed (line 334) | def _on_playtime_enabled_changed(self, _settings, _key) -> None:
    method playtime_start (line 354) | def playtime_start(
    method playtime_finish (line 376) | def playtime_finish(
    method _on_program_started (line 395) | def _on_program_started(self, data: Optional[Result] = None) -> None:
    method _on_program_finished (line 421) | def _on_program_finished(self, data: Optional[Result] = None) -> None:
    method _get_payload_config (line 441) | def _get_payload_config(self, payload) -> Optional[BottleConfig]:
    method __clear_temp (line 456) | def __clear_temp(self, force: bool = False):
    method get_cache_details (line 468) | def get_cache_details(self) -> dict:
    method clear_temp_cache (line 508) | def clear_temp_cache(self) -> Result[None]:
    method clear_template_cache (line 517) | def clear_template_cache(self, template_uuid: str) -> Result[None]:
    method clear_templates_cache (line 527) | def clear_templates_cache(self) -> Result[None]:
    method clear_all_caches (line 538) | def clear_all_caches(self) -> Result[None]:
    method update_bottles (line 549) | def update_bottles(self, silent: bool = False):
    method check_app_dirs (line 554) | def check_app_dirs(self):
    method organize_components (line 608) | def organize_components(self):
    method organize_dependencies (line 628) | def organize_dependencies(self):
    method organize_installers (line 641) | def organize_installers(self):
    method remove_dependency (line 653) | def remove_dependency(self, config: BottleConfig, dependency: list):
    method check_runners (line 673) | def check_runners(self, install_latest: bool = True) -> bool:
    method check_runtimes (line 766) | def check_runtimes(self, install_latest: bool = True) -> bool:
    method __winebridge_status (line 797) | def __winebridge_status(self) -> tuple[Optional[str], Optional[str], b...
    method winebridge_update_status (line 839) | def winebridge_update_status(self) -> dict:
    method check_winebridge (line 848) | def check_winebridge(
    method check_dxvk (line 869) | def check_dxvk(self, install_latest: bool = True) -> bool:
    method check_vkd3d (line 875) | def check_vkd3d(self, install_latest: bool = True) -> bool:
    method check_nvapi (line 881) | def check_nvapi(self, install_latest: bool = True) -> bool:
    method check_latencyflex (line 887) | def check_latencyflex(self, install_latest: bool = True) -> bool:
    method get_offline_components (line 893) | def get_offline_components(
    method __check_component (line 959) | def __check_component(
    method get_programs (line 1032) | def get_programs(self, config: BottleConfig) -> List[dict]:
    method check_bottles (line 1179) | def check_bottles(self, silent: bool = False):
    method update_config (line 1334) | def update_config(
    method apply_audio_driver (line 1403) | def apply_audio_driver(self, driver: str) -> Result[None]:
    method create_bottle_from_config (line 1418) | def create_bottle_from_config(self, config: BottleConfig) -> bool:
    method create_bottle (line 1522) | def create_bottle(
    method __sort_runners (line 2033) | def __sort_runners(runner_list: list, prefix: str) -> sorted:
    method get_latest_runner (line 2047) | def get_latest_runner(self, runner_prefix: str = "soda") -> list:
    method delete_bottle (line 2054) | def delete_bottle(self, config: BottleConfig) -> bool:
    method repair_bottle (line 2098) | def repair_bottle(self, config: BottleConfig) -> bool:
    method install_dll_component (line 2130) | def install_dll_component(

FILE: bottles/backend/managers/origin.py
  class OriginManager (line 24) | class OriginManager:
    method find_manifests_path (line 26) | def find_manifests_path(config: BottleConfig) -> str | None:
    method is_origin_supported (line 43) | def is_origin_supported(config: BottleConfig) -> bool:
    method get_installed_games (line 50) | def get_installed_games(config: BottleConfig) -> list:

FILE: bottles/backend/managers/playtime.py
  class PlaytimeTotalsDict (line 27) | class PlaytimeTotalsDict(TypedDict):
  class _TrackedSession (line 40) | class _TrackedSession:
  function _utc_now_seconds (line 49) | def _utc_now_seconds() -> int:
  function _normalize_path_to_windows (line 53) | def _normalize_path_to_windows(bottle_path: str, program_path: str) -> str:
  function _compute_program_id (line 100) | def _compute_program_id(bottle_id: str, bottle_path: str, program_path: ...
  class ProcessSessionTracker (line 120) | class ProcessSessionTracker:
    method __init__ (line 129) | def __init__(
    method _connect (line 160) | def _connect(self) -> sqlite3.Connection:
    method _ensure_schema (line 169) | def _ensure_schema(self) -> None:
    method disable_tracking (line 231) | def disable_tracking(self) -> None:
    method shutdown (line 235) | def shutdown(self) -> None:
    method _atexit_shutdown (line 257) | def _atexit_shutdown(self) -> None:
    method start_session (line 263) | def start_session(
    method mark_exit (line 373) | def mark_exit(
    method mark_failure (line 420) | def mark_failure(self, session_id: int, *, status: str) -> None:
    method recover_open_sessions (line 426) | def recover_open_sessions(self) -> None:
    method _heartbeat_loop (line 456) | def _heartbeat_loop(self) -> None:
    method _flush_heartbeats (line 463) | def _flush_heartbeats(self) -> None:
    method _update_totals (line 489) | def _update_totals(
    method get_totals (line 552) | def get_totals(
    method get_all_program_totals (line 600) | def get_all_program_totals(
    method get_weekly_playtime (line 653) | def get_weekly_playtime(
    method get_daily_playtime (line 733) | def get_daily_playtime(
    method get_monthly_playtime (line 835) | def get_monthly_playtime(
    method get_weekly_session_count (line 907) | def get_weekly_session_count(
    method get_daily_session_count (line 951) | def get_daily_session_count(
    method get_yearly_session_count (line 989) | def get_yearly_session_count(

FILE: bottles/backend/managers/queue.py
  class QueueManager (line 19) | class QueueManager:
    method __init__ (line 22) | def __init__(self, end_fn, add_fn=None):
    method add_task (line 26) | def add_task(self):
    method end_task (line 31) | def end_task(self):

FILE: bottles/backend/managers/registry_rule.py
  class RegistryRuleManager (line 17) | class RegistryRuleManager:
    method load_rules (line 21) | def load_rules(config: BottleConfig) -> dict[str, RegistryRule]:
    method list_rules (line 30) | def list_rules(cls, config: BottleConfig) -> List[RegistryRule]:
    method upsert_rule (line 34) | def upsert_rule(
    method delete_rule (line 43) | def delete_rule(cls, manager: "Manager", config: BottleConfig, name: s...
    method apply_rules (line 53) | def apply_rules(

FILE: bottles/backend/managers/repository.py
  class RepositoryManager (line 35) | class RepositoryManager:
    method __init__ (line 54) | def __init__(self, get_index=True):
    method get_repo (line 64) | def get_repo(self, name: str, offline: bool = False):
    method __check_personals (line 71) | def __check_personals(self):
    method __curl_progress (line 101) | def __curl_progress(self, _download_t, _download_d, _upload_t, _upload...
    method __stop_index (line 108) | def __stop_index(self, res: Result):
    method __get_index (line 112) | def __get_index(self):

FILE: bottles/backend/managers/runtime.py
  class RuntimeManager (line 24) | class RuntimeManager:
    method get_runtimes (line 27) | def get_runtimes(_filter: str = "bottles"):
    method get_runtime_env (line 40) | def get_runtime_env(_filter: str = "bottles"):
    method get_eac (line 60) | def get_eac():
    method get_be (line 71) | def get_be():
    method __get_runtime (line 82) | def __get_runtime(paths: list, structure: list):
    method __get_bottles_runtime (line 116) | def __get_bottles_runtime():
    method __get_steam_runtime (line 123) | def __get_steam_runtime():

FILE: bottles/backend/managers/sandbox.py
  class SandboxManager (line 25) | class SandboxManager:
    method __init__ (line 26) | def __init__(
    method __get_bwrap (line 53) | def __get_bwrap(self, cmd: str):
    method __get_flatpak_spawn (line 97) | def __get_flatpak_spawn(self, cmd: str):
    method get_cmd (line 141) | def get_cmd(self, cmd: str):
    method run (line 149) | def run(self, cmd: str) -> subprocess.Popen[bytes]:

FILE: bottles/backend/managers/steam.py
  class SteamManager (line 44) | class SteamManager:
    method __init__ (line 51) | def __init__(
    method __find_steam_path (line 68) | def __find_steam_path(self) -> str | None:
    method __get_scoped_path (line 92) | def __get_scoped_path(self, scope: str = "steamapps"):
    method get_acf_data (line 103) | def get_acf_data(libraryfolder: str, app_id: str) -> dict | None:
    method __get_local_config_path (line 113) | def __get_local_config_path(self) -> str | None:
    method __get_library_folders (line 124) | def __get_library_folders(self) -> list | None:
    method get_appid_library_path (line 155) | def get_appid_library_path(self, appid: str) -> str | None:
    method __get_local_config (line 166) | def __get_local_config(self) -> dict:
    method save_local_config (line 179) | def save_local_config(self, new_data: dict):
    method get_runner_path (line 194) | def get_runner_path(pfx_path: str) -> Optional[str]:
    method list_apps_ids (line 222) | def list_apps_ids(self) -> dict:
    method get_installed_apps_as_programs (line 238) | def get_installed_apps_as_programs(self) -> list:
    method list_prefixes (line 275) | def list_prefixes(self) -> Dict[str, BottleConfig]:
    method update_bottles (line 366) | def update_bottles(self):
    method get_app_config (line 379) | def get_app_config(self, prefix: str) -> dict:
    method get_launch_options (line 405) | def get_launch_options(self, prefix: str, app_conf: Optional[dict] = N...
    method set_launch_options (line 432) | def set_launch_options(self, prefix: str, options: dict):
    method del_launch_option (line 466) | def del_launch_option(self, prefix: str, key_type: str, key: str):
    method update_bottle (line 505) | def update_bottle(self, config: BottleConfig) -> BottleConfig:
    method launch_app (line 528) | def launch_app(prefix: str):
    method add_shortcut (line 533) | def add_shortcut(self, program_name: str, program_path: str):

FILE: bottles/backend/managers/steamgriddb.py
  class SteamGridDBManager (line 30) | class SteamGridDBManager:
    method get_game_grid (line 32) | def get_game_grid(name: str, config: BottleConfig):
    method __save_grid (line 42) | def __save_grid(url: str, config: BottleConfig):

FILE: bottles/backend/managers/template.py
  class TemplateManager (line 35) | class TemplateManager:
    method new (line 37) | def new(env: str, config: BottleConfig):
    method __validate_template (line 82) | def __validate_template(template_uuid: str):
    method get_template_manifest (line 115) | def get_template_manifest(template: str):
    method get_templates (line 120) | def get_templates():
    method delete_template (line 133) | def delete_template(template_uuid: str):
    method check_outdated (line 147) | def check_outdated(template: dict):
    method get_env_template (line 169) | def get_env_template(env: str):
    method unpack_template (line 180) | def unpack_template(template: dict, config: BottleConfig):

FILE: bottles/backend/managers/thumbnail.py
  class ThumbnailManager (line 27) | class ThumbnailManager:
    method get_path (line 29) | def get_path(config: BottleConfig, uri: str):
    method __load_grid (line 40) | def __load_grid(config: BottleConfig, uri: str):

FILE: bottles/backend/managers/ubisoftconnect.py
  class UbisoftConnectManager (line 26) | class UbisoftConnectManager:
    method find_conf_path (line 28) | def find_conf_path(config: BottleConfig) -> str | None:
    method is_uconnect_supported (line 45) | def is_uconnect_supported(config: BottleConfig) -> bool:
    method get_installed_games (line 53) | def get_installed_games(config: BottleConfig) -> list:

FILE: bottles/backend/managers/versioning.py
  class VersioningManager (line 45) | class VersioningManager:
    method __init__ (line 46) | def __init__(self, manager):
    method __get_patterns (line 50) | def __get_patterns(config: BottleConfig):
    method is_initialized (line 57) | def is_initialized(config: BottleConfig):
    method needs_migration (line 65) | def needs_migration(config: BottleConfig):
    method re_initialize (line 89) | def re_initialize(config: BottleConfig):
    method update_system (line 107) | def update_system(self, config: BottleConfig):
    method create_state (line 111) | def create_state(self, config: BottleConfig, message: str = "No messag...
    method list_states (line 134) | def list_states(
    method set_state (line 180) | def set_state(
    method get_state_files (line 279) | def get_state_files(
    method get_index (line 299) | def get_index(config: BottleConfig):
    method get_branches (line 321) | def get_branches(self, config: BottleConfig) -> list:
    method get_active_branch (line 332) | def get_active_branch(self, config: BottleConfig) -> str:
    method create_branch (line 343) | def create_branch(self, config: BottleConfig, branch_name: str) -> Res...
    method delete_branch (line 355) | def delete_branch(self, config: BottleConfig, branch_name: str) -> Res...
    method checkout_branch (line 367) | def checkout_branch(self, config: BottleConfig, branch_name: str) -> R...

FILE: bottles/backend/models/config.py
  class DictCompatMixIn (line 16) | class DictCompatMixIn:
    method yaml_serialize_handler (line 18) | def yaml_serialize_handler(dumper, data):
    method json_serialize_handler (line 23) | def json_serialize_handler(data):
    method keys (line 26) | def keys(self):
    method get (line 29) | def get(self, key, __default=None):
    method copy (line 32) | def copy(self):
    method to_dict (line 35) | def to_dict(self) -> dict:
    method items (line 38) | def items(self) -> ItemsView[str, Container]:
    method __iter__ (line 41) | def __iter__(self):
    method __getitem__ (line 45) | def __getitem__(self, item):
    method __delitem__ (line 49) | def __delitem__(self, key):
    method __setitem__ (line 53) | def __setitem__(self, key, value):
  class BottleSandboxParams (line 59) | class BottleSandboxParams(DictCompatMixIn):
  class BottleParams (line 69) | class BottleParams(DictCompatMixIn):
  class BottleConfig (line 119) | class BottleConfig(DictCompatMixIn):
    method dump (line 157) | def dump(self, file: str | IO, mode="w", encoding=None, indent=4) -> R...
    method load (line 179) | def load(cls, file: str | IO, mode="r") -> Result[Optional["BottleConf...
    method _fill_with (line 212) | def _fill_with(cls, data: dict) -> Result[Optional["BottleConfig"]]:
    method _fix (line 230) | def _fix(cls, data: dict) -> dict:
    method _filter (line 264) | def _filter(cls, data: dict, clazz: object = None) -> dict:

FILE: bottles/backend/models/enum.py
  class Arch (line 1) | class Arch:

FILE: bottles/backend/models/process.py
  class ProcessStartedPayload (line 6) | class ProcessStartedPayload:
  class ProcessFinishedPayload (line 16) | class ProcessFinishedPayload:

FILE: bottles/backend/models/registry_rule.py
  class RegistryRule (line 6) | class RegistryRule:
    method from_dict (line 20) | def from_dict(cls, data: dict) -> "RegistryRule":
    method to_dict (line 29) | def to_dict(self) -> dict:

FILE: bottles/backend/models/result.py
  class Result (line 22) | class Result(Generic[T]):
    method __init__ (line 33) | def __init__(self, status: bool = False, data: T = None, message: str ...
    method set_status (line 38) | def set_status(self, v: bool):
    method ok (line 42) | def ok(self):
    method has_data (line 46) | def has_data(self):
    method ready (line 50) | def ready(self):

FILE: bottles/backend/models/samples.py
  class Samples (line 1) | class Samples:

FILE: bottles/backend/models/vdict.py
  class _kView (line 31) | class _kView(_c.KeysView):
    method __iter__ (line 32) | def __iter__(self):
  class _vView (line 36) | class _vView(_c.ValuesView):
    method __iter__ (line 37) | def __iter__(self):
  class _iView (line 41) | class _iView(_c.ItemsView):
    method __iter__ (line 42) | def __iter__(self):
  class VDFDict (line 46) | class VDFDict(dict):
    method __init__ (line 47) | def __init__(self, data=None):
    method __repr__ (line 69) | def __repr__(self):
    method __len__ (line 74) | def __len__(self):
    method _verify_key_tuple (line 78) | def _verify_key_tuple(key):
    method _normalize_key (line 86) | def _normalize_key(self, key):
    method __setitem__ (line 95) | def __setitem__(self, key, value):
    method __getitem__ (line 108) | def __getitem__(self, key):
    method __delitem__ (line 111) | def __delitem__(self, key):
    method __iter__ (line 141) | def __iter__(self):
    method __contains__ (line 144) | def __contains__(self, key):
    method __eq__ (line 147) | def __eq__(self, other):
    method __ne__ (line 153) | def __ne__(self, other):
    method clear (line 156) | def clear(self):
    method get (line 161) | def get(self, key, *_args):
    method setdefault (line 164) | def setdefault(self, key, default=None):
    method pop (line 169) | def pop(self, key):
    method popitem (line 175) | def popitem(self):
    method update (line 181) | def update(self, data=None, **kwargs):
    method iterkeys (line 190) | def iterkeys(self):
    method keys (line 193) | def keys(self):
    method itervalues (line 196) | def itervalues(self):
    method values (line 199) | def values(self):
    method iteritems (line 202) | def iteritems(self):
    method items (line 205) | def items(self):
    method get_all_for (line 208) | def get_all_for(self, key):
    method remove_all_for (line 214) | def remove_all_for(self, key):
    method has_duplicates (line 226) | def has_duplicates(self):

FILE: bottles/backend/repos/component.py
  class ComponentRepo (line 21) | class ComponentRepo(Repo):
    method get (line 24) | def get(self, name: str, plain: bool = False) -> str | dict | bool:

FILE: bottles/backend/repos/dependency.py
  class DependencyRepo (line 21) | class DependencyRepo(Repo):
    method get (line 24) | def get(self, name: str, plain: bool = False) -> str | dict | bool:

FILE: bottles/backend/repos/installer.py
  class InstallerRepo (line 21) | class InstallerRepo(Repo):
    method get (line 24) | def get(self, name: str, plain: bool = False) -> str | dict | bool:
    method get_review (line 31) | def get_review(self, name: str) -> str | dict | bool:
    method get_icon (line 36) | def get_icon(self, name: str) -> str | bytes | None:

FILE: bottles/backend/repos/repo.py
  class Repo (line 30) | class Repo:
    method __init__ (line 33) | def __init__(self, url: str, index: str, offline: bool = False):
    method __get_catalog (line 43) | def __get_catalog(self, index: str, offline: bool = False):
    method get_manifest (line 65) | def get_manifest(self, url: str, plain: bool = False) -> str | dict | ...

FILE: bottles/backend/runner.py
  class Runner (line 35) | class Runner:
    method runner_update (line 44) | def runner_update(

FILE: bottles/backend/state.py
  class Locks (line 14) | class Locks(Enum):
  class Events (line 18) | class Events(Enum):
  class Signals (line 27) | class Signals(Enum):
  class Status (line 64) | class Status(Enum):
  class TaskStreamUpdateHandler (line 71) | class TaskStreamUpdateHandler(Protocol):
    method __call__ (line 72) | def __call__(
  class SignalHandler (line 80) | class SignalHandler(Protocol):
    method __call__ (line 81) | def __call__(self, data: Optional[Result] = None) -> None: ...
  class Notification (line 85) | class Notification:
  class Task (line 92) | class Task:
    method __init__ (line 99) | def __init__(
    method task_id (line 112) | def task_id(self) -> Optional[UUID]:
    method task_id (line 116) | def task_id(self, value: UUID):
    method subtitle (line 124) | def subtitle(self) -> str:
    method subtitle (line 128) | def subtitle(self, value: str):
    method stream_update (line 132) | def stream_update(
  class LockManager (line 154) | class LockManager:
    method lock (line 158) | def lock(cls, name: Locks):
    method get (line 174) | def get(cls, name: Locks) -> PyLock:
  class EventManager (line 178) | class EventManager:
    method wait (line 188) | def wait(cls, event: Events):
    method done (line 196) | def done(cls, event: Events):
    method reset (line 202) | def reset(cls, event: Events):
  class TaskManager (line 208) | class TaskManager:
    method get (line 214) | def get(cls, task_id: UUID) -> Optional[Task]:
    method add (line 218) | def add(cls, task: Task) -> UUID:
    method remove (line 227) | def remove(cls, task: UUID | Task):
  class SignalManager (line 234) | class SignalManager:
    method connect (line 240) | def connect(cls, signal: Signals, handler: SignalHandler) -> None:
    method send (line 245) | def send(cls, signal: Signals, data: Optional[Result] = None) -> None:

FILE: bottles/backend/utils/connection.py
  class ConnectionUtils (line 32) | class ConnectionUtils:
    method __init__ (line 42) | def __init__(self, force_offline=False, **kwargs):
    method status (line 50) | def status(self) -> Optional[bool]:
    method status (line 54) | def status(self, value: bool):
    method __curl_progress (line 61) | def __curl_progress(self, _download_t, _download_d, _upload_t, _upload...
    method stop_check (line 68) | def stop_check(self, res: Result):
    method check_connection (line 72) | def check_connection(self, show_notification=False) -> Optional[bool]:

FILE: bottles/backend/utils/decorators.py
  function cache (line 22) | def cache(_func=None, *, seconds: int = 600, maxsize: int = 128, typed: ...

FILE: bottles/backend/utils/display.py
  class DisplayUtils (line 6) | class DisplayUtils:
    method get_x_display (line 9) | def get_x_display():
    method check_nvidia_device (line 36) | def check_nvidia_device():
    method display_server_type (line 56) | def display_server_type():

FILE: bottles/backend/utils/file.py
  class FileUtils (line 27) | class FileUtils:
    method get_checksum (line 34) | def get_checksum(file):
    method use_insensitive_ext (line 49) | def use_insensitive_ext(string):
    method get_human_size (line 56) | def get_human_size(size: float) -> str:
    method get_human_size_legacy (line 65) | def get_human_size_legacy(size: float) -> str:
    method get_path_size (line 74) | def get_path_size(self, path: str, human: bool = True) -> str | float:
    method get_disk_size (line 87) | def get_disk_size(self, human: bool = True) -> dict:
    method wait_for_files (line 106) | def wait_for_files(files: list, timeout: int = 0.5) -> bool:
    method remove_path (line 118) | def remove_path(path: str) -> bool:
    method chattr_f (line 133) | def chattr_f(directory: str) -> bool:

FILE: bottles/backend/utils/generic.py
  function validate_url (line 30) | def validate_url(url: str):
  function detect_encoding (line 45) | def detect_encoding(text: bytes, locale_hint: str = None) -> Optional[str]:
  function is_glibc_min_available (line 77) | def is_glibc_min_available():
  function sort_by_version (line 93) | def sort_by_version(_list: list, extra_check: str = "async"):
  function get_mime (line 105) | def get_mime(path: str):
  function random_string (line 114) | def random_string(length: int):

FILE: bottles/backend/utils/gpu.py
  class GPUVendors (line 29) | class GPUVendors(Enum):
  class GPUUtils (line 36) | class GPUUtils:
    method __init__ (line 43) | def __init__(self):
    method list_all (line 46) | def list_all(self):
    method assume_discrete (line 63) | def assume_discrete(vendors: list):
    method is_nouveau (line 73) | def is_nouveau():
    method get_gpu (line 86) | def get_gpu(self):
    method is_gpu (line 146) | def is_gpu(vendor: GPUVendors) -> bool:

FILE: bottles/backend/utils/gsettings_stub.py
  class GSettingsStub (line 6) | class GSettingsStub:
    method get_boolean (line 8) | def get_boolean(key: str) -> bool:
    method get_string (line 13) | def get_string(key: str) -> str:
    method set_string (line 18) | def set_string(key: str, value: str) -> None:

FILE: bottles/backend/utils/imagemagick.py
  class ImageMagickUtils (line 22) | class ImageMagickUtils:
    method __init__ (line 23) | def __init__(self, path: str):
    method __validate_path (line 27) | def __validate_path(path: str):
    method list_assets (line 34) | def list_assets(self):
    method convert (line 53) | def convert(

FILE: bottles/backend/utils/json.py
  class ExtJSONEncoder (line 12) | class ExtJSONEncoder(_json.JSONEncoder):
    method default (line 13) | def default(self, o):
  function load (line 19) | def load(fp: IO[str]) -> Any:
  function loads (line 24) | def loads(s: str | bytes) -> Any:
  function dump (line 29) | def dump(
  function dumps (line 49) | def dumps(

FILE: bottles/backend/utils/lnk.py
  class LnkUtils (line 23) | class LnkUtils:
    method get_data (line 26) | def get_data(path):

FILE: bottles/backend/utils/manager.py
  class ManagerUtils (line 42) | class ManagerUtils:
    method open_filemanager (line 49) | def open_filemanager(
    method get_bottle_path (line 89) | def get_bottle_path(config: BottleConfig) -> str:
    method get_runner_path (line 99) | def get_runner_path(runner: str) -> str:
    method get_dxvk_path (line 105) | def get_dxvk_path(dxvk: str) -> str:
    method get_vkd3d_path (line 109) | def get_vkd3d_path(vkd3d: str) -> str:
    method get_nvapi_path (line 113) | def get_nvapi_path(nvapi: str) -> str:
    method get_latencyflex_path (line 117) | def get_latencyflex_path(latencyflex: str) -> str:
    method get_temp_path (line 121) | def get_temp_path(dest: str) -> str:
    method get_template_path (line 125) | def get_template_path(template: str) -> str:
    method move_file_to_bottle (line 129) | def move_file_to_bottle(
    method get_exe_parent_dir (line 177) | def get_exe_parent_dir(config, executable_path):
    method extract_icon (line 186) | def extract_icon(config: BottleConfig, program_name: str, program_path...
    method create_desktop_entry (line 227) | def create_desktop_entry(
    method browse_wineprefix (line 281) | def browse_wineprefix(wineprefix: dict):
    method get_languages (line 288) | def get_languages(

FILE: bottles/backend/utils/nvidia.py
  class LinkMap (line 15) | class LinkMap(Structure):
  function get_nvidia_glx_path (line 36) | def get_nvidia_glx_path():
  function get_nvidia_dll_path (line 88) | def get_nvidia_dll_path():

FILE: bottles/backend/utils/proc.py
  class Proc (line 22) | class Proc:
    method __init__ (line 23) | def __init__(self, pid):
    method __get_data (line 26) | def __get_data(self, data):
    method get_cmdline (line 33) | def get_cmdline(self):
    method get_env (line 36) | def get_env(self):
    method get_cwd (line 39) | def get_cwd(self):
    method get_name (line 42) | def get_name(self):
    method kill (line 45) | def kill(self):
  class ProcUtils (line 53) | class ProcUtils:
    method get_procs (line 55) | def get_procs():
    method get_by_cmdline (line 63) | def get_by_cmdline(cmdline):
    method get_by_env (line 68) | def get_by_env(env):
    method get_by_cwd (line 73) | def get_by_cwd(cwd):
    method get_by_name (line 78) | def get_by_name(name):
    method get_by_pid (line 83) | def get_by_pid(pid):

FILE: bottles/backend/utils/singleton.py
  class Singleton (line 1) | class Singleton(type):
    method __call__ (line 4) | def __call__(cls, *args, **kwargs):

FILE: bottles/backend/utils/snake.py
  class Snake (line 8) | class Snake:
    method __init__ (line 9) | def __init__(self, stdscr: curses.window):
    method generate_food (line 27) | def generate_food(self):
    method draw (line 36) | def draw(self):
    method move (line 55) | def move(self):
    method get_input (line 75) | def get_input(self):
    method get_result (line 88) | def get_result(self):
    method run (line 91) | def run(self):
  function main (line 103) | def main(stdscr):

FILE: bottles/backend/utils/steam.py
  class SteamUtils (line 29) | class SteamUtils:
    method parse_acf (line 31) | def parse_acf(data: str) -> VDFDict:
    method parse_vdf (line 38) | def parse_vdf(data: str) -> VDFDict:
    method to_vdf (line 45) | def to_vdf(data: VDFDict, fp: TextIO):
    method is_proton (line 52) | def is_proton(path: str) -> bool:
    method get_associated_runtime (line 69) | def get_associated_runtime(path: str) -> Optional[str]:
    method get_dist_directory (line 91) | def get_dist_directory(path: str) -> str:
    method handle_launch_options (line 108) | def handle_launch_options(launch_options: str) -> tuple[str, str, dict...

FILE: bottles/backend/utils/terminal.py
  class TerminalUtils (line 27) | class TerminalUtils:
    method __init__ (line 61) | def __init__(self):
    method check_support (line 64) | def check_support(self):
    method execute (line 86) | def execute(self, command, env=None, colors="default", cwd=None):
    method launch_snake (line 172) | def launch_snake(self):

FILE: bottles/backend/utils/threading.py
  class RunAsync (line 31) | class RunAsync(threading.Thread):
    method __init__ (line 37) | def __init__(
    method __target (line 68) | def __target(self, *args, **kwargs):
    method cancel (line 109) | def cancel(self):
    method run_async (line 115) | def run_async(func):

FILE: bottles/backend/utils/vdf.py
  function strip_bom (line 40) | def strip_bom(line):
  function _re_escape_match (line 61) | def _re_escape_match(m):
  function _re_unescape_match (line 65) | def _re_unescape_match(m):
  function _escape (line 69) | def _escape(text):
  function _unescape (line 73) | def _unescape(text):
  function parse (line 80) | def parse(fp, mapper=dict, merge_duplicate_keys=True, escaped=True):
  function loads (line 229) | def loads(s, **kwargs):
  function load (line 245) | def load(fp, **kwargs):
  function dumps (line 253) | def dumps(obj, pretty=False, escaped=True):
  function dump (line 267) | def dump(obj, fp, pretty=False, escaped=True):
  function _dump_gen (line 285) | def _dump_gen(data, pretty=False, escaped=True, level=0):
  class BASE_INT (line 309) | class BASE_INT(int_type):
    method __repr__ (line 310) | def __repr__(self):
  class UINT_64 (line 314) | class UINT_64(BASE_INT):
  class INT_64 (line 318) | class INT_64(BASE_INT):
  class POINTER (line 322) | class POINTER(BASE_INT):
  class COLOR (line 326) | class COLOR(BASE_INT):
  function binary_loads (line 343) | def binary_loads(
  function binary_load (line 364) | def binary_load(
  function binary_dumps (line 484) | def binary_dumps(obj, alt_format=False):
  function binary_dump (line 493) | def binary_dump(obj, fp, alt_format=False):
  function _binary_dump_gen (line 506) | def _binary_dump_gen(obj, level=0, alt_format=False):
  function vbkv_loads (line 554) | def vbkv_loads(s, mapper=dict, merge_duplicate_keys=True):
  function vbkv_dumps (line 575) | def vbkv_dumps(obj):

FILE: bottles/backend/utils/vulkan.py
  class VulkanUtils (line 25) | class VulkanUtils:
    method __init__ (line 38) | def __init__(self):
    method __get_vk_icd_loaders (line 41) | def __get_vk_icd_loaders(self):
    method get_vk_icd (line 67) | def get_vk_icd(self, vendor: str, as_string=False):
    method check_support (line 80) | def check_support():
    method test_vulkan (line 84) | def test_vulkan():

FILE: bottles/backend/utils/wine.py
  class WineUtils (line 4) | class WineUtils:
    method get_user_dir (line 6) | def get_user_dir(prefix_path: str):

FILE: bottles/backend/utils/yaml.py
  function register_serializer (line 11) | def register_serializer(cls):
  function dump (line 16) | def dump(data, stream=None, **kwargs):
  function load (line 28) | def load(stream, Loader=SafeLoader):

FILE: bottles/backend/wine/cmd.py
  class CMD (line 9) | class CMD(WineProgram):
    method run_batch (line 13) | def run_batch(

FILE: bottles/backend/wine/control.py
  class Control (line 7) | class Control(WineProgram):
    method load_applet (line 11) | def load_applet(self, name: str):
    method load_joystick (line 15) | def load_joystick(self):
    method load_appwiz (line 18) | def load_appwiz(self):
    method load_inetcpl (line 21) | def load_inetcpl(self):

FILE: bottles/backend/wine/drives.py
  class Drives (line 10) | class Drives:
    method __init__ (line 11) | def __init__(self, config: BottleConfig):
    method get_all (line 16) | def get_all(self):
    method get_drive (line 28) | def get_drive(self, letter: str):
    method set_drive_path (line 34) | def set_drive_path(self, letter: str, path: str):
    method remove_drive (line 48) | def remove_drive(self, letter: str):

FILE: bottles/backend/wine/eject.py
  class Eject (line 7) | class Eject(WineProgram):
    method cdrom (line 11) | def cdrom(self, drive: str, unmount_only: bool = False):
    method all (line 17) | def all(self):

FILE: bottles/backend/wine/executor.py
  class WineExecutor (line 32) | class WineExecutor:
    method __init__ (line 42) | def __init__(
    method run_program (line 133) | def run_program(cls, config: BottleConfig, program: dict, terminal: bo...
    method _build_placeholder_map (line 165) | def _build_placeholder_map(config: BottleConfig, program: dict) -> dic...
    method _replace_placeholders (line 196) | def _replace_placeholders(
    method __get_cwd (line 208) | def __get_cwd(self, cwd: str) -> str | None:
    method __validate_path (line 223) | def __validate_path(exec_path):
    method __move_file (line 241) | def __move_file(self, exec_path, move_upd_fn):
    method __get_exec_type (line 253) | def __get_exec_type(exec_path):
    method run_cli (line 269) | def run_cli(self):
    method run (line 294) | def run(self) -> Result:
    method __launch_with_bridge (line 371) | def __launch_with_bridge(self):
    method __launch_exe (line 404) | def __launch_exe(self):
    method __launch_msi (line 430) | def __launch_msi(self):
    method __launch_batch (line 442) | def __launch_batch(self):
    method __launch_with_starter (line 453) | def __launch_with_starter(self):
    method __launch_with_explorer (line 469) | def __launch_with_explorer(self):
    method __launch_dll (line 485) | def __launch_dll():
    method __set_monitors (line 489) | def __set_monitors(self):

FILE: bottles/backend/wine/expand.py
  class Expand (line 7) | class Expand(WineProgram):
    method extract (line 11) | def extract(self, cabinet: str, filename: str):
    method extract_all (line 15) | def extract_all(self, cabinet: str, filenames: list):

FILE: bottles/backend/wine/explorer.py
  class Explorer (line 9) | class Explorer(WineProgram):
    method launch_desktop (line 13) | def launch_desktop(

FILE: bottles/backend/wine/hh.py
  class Hh (line 7) | class Hh(WineProgram):

FILE: bottles/backend/wine/icinfo.py
  class Icinfo (line 7) | class Icinfo(WineProgram):
    method get_output (line 11) | def get_output(self):
    method get_dict (line 14) | def get_dict(self):

FILE: bottles/backend/wine/msiexec.py
  class MsiExec (line 9) | class MsiExec(WineProgram):
    method install (line 13) | def install(
    method repair (line 33) | def repair(
    method uninstall (line 81) | def uninstall(self, pkg_path: str, cwd: Optional[str] = None):
    method apply_patch (line 87) | def apply_patch(self, patch: str, update: bool = False, cwd: Optional[...
    method uninstall_patch (line 96) | def uninstall_patch(
    method register_module (line 111) | def register_module(self, module: str, cwd: Optional[str] = None):
    method unregister_module (line 121) | def unregister_module(self, module: str, cwd: Optional[str] = None):

FILE: bottles/backend/wine/net.py
  class Net (line 9) | class Net(WineProgram):
    method start (line 13) | def start(self, name: Optional[str] = None):
    method stop (line 21) | def stop(self, name: Optional[str] = None):
    method use (line 29) | def use(self, name: Optional[str] = None):
    method list (line 38) | def list(self):

FILE: bottles/backend/wine/notepad.py
  class Notepad (line 9) | class Notepad(WineProgram):
    method open (line 13) | def open(self, path: str, as_ansi: bool = False, as_utf16: bool = False):
    method print (line 21) | def print(self, path: str, printer_name: Optional[str] = None):

FILE: bottles/backend/wine/oleview.py
  class Oleview (line 7) | class Oleview(WineProgram):

FILE: bottles/backend/wine/progman.py
  class Progman (line 7) | class Progman(WineProgram):

FILE: bottles/backend/wine/reg.py
  class RegItem (line 20) | class RegItem:
  class Reg (line 27) | class Reg(WineProgram):
    method bulk_add (line 31) | def bulk_add(self, regs: List[RegItem]):
    method add (line 77) | def add(self, key: str, value: str, data: str, value_type: Optional[st...
    method remove (line 95) | def remove(self, key: str, value: str):
    method import_bundle (line 110) | def import_bundle(self, bundle: dict):

FILE: bottles/backend/wine/regedit.py
  class Regedit (line 7) | class Regedit(WineProgram):

FILE: bottles/backend/wine/register.py
  class WinRegister (line 25) | class WinRegister:
    method __init__ (line 26) | def __init__(self):
    method new (line 32) | def new(self, path: str):
    method __get_header (line 41) | def __get_header(self):
    method __parse_dict (line 48) | def __parse_dict(path: str):
    method compare (line 104) | def compare(self, path: Optional[str] = None, register: object = None):
    method __get_diff (line 115) | def __get_diff(self, register: "WinRegister"):
    method update (line 136) | def update(self, diff: Optional[dict] = None):
    method export_json (line 163) | def export_json(self, path: str):

FILE: bottles/backend/wine/regkeys.py
  class RegKeys (line 14) | class RegKeys:
    method __init__ (line 15) | def __init__(self, config: BottleConfig):
    method lg_set_windows (line 19) | def lg_set_windows(self, version: str):
    method set_windows (line 29) | def set_windows(self, version: str):
    method set_app_default (line 156) | def set_app_default(self, version: str, executable: str):
    method toggle_virtual_desktop (line 173) | def toggle_virtual_desktop(self, state: bool, resolution: str = "800x6...
    method toggle_wayland_driver (line 197) | def toggle_wayland_driver(self, state: bool):
    method apply_cmd_settings (line 205) | def apply_cmd_settings(self, scheme=None):
    method apply_font_smoothing (line 238) | def apply_font_smoothing(self, mode: str = "rgb"):
    method set_renderer (line 276) | def set_renderer(self, value: str):
    method set_audio_driver (line 290) | def set_audio_driver(self, driver: Optional[str]):
    method set_dpi (line 318) | def set_dpi(self, value: int):
    method set_grab_fullscreen (line 329) | def set_grab_fullscreen(self, state: bool):
    method set_take_focus (line 340) | def set_take_focus(self, state: bool):
    method set_decorated (line 351) | def set_decorated(self, state: bool):
    method set_mouse_warp (line 362) | def set_mouse_warp(self, state: int, executable: str = ""):

FILE: bottles/backend/wine/regsvr32.py
  class Regsvr32 (line 7) | class Regsvr32(WineProgram):
    method register (line 11) | def register(self, dll: str):
    method unregister (line 15) | def unregister(self, dll: str):
    method register_all (line 19) | def register_all(self, dlls: list):
    method unregister_all (line 23) | def unregister_all(self, dlls: list):

FILE: bottles/backend/wine/rundll32.py
  class RunDLL32 (line 7) | class RunDLL32(WineProgram):

FILE: bottles/backend/wine/start.py
  class Start (line 10) | class Start(WineProgram):
    method run (line 14) | def run(

FILE: bottles/backend/wine/taskmgr.py
  class Taskmgr (line 7) | class Taskmgr(WineProgram):

FILE: bottles/backend/wine/uninstaller.py
  class Uninstaller (line 9) | class Uninstaller(WineProgram):
    method get_uuid (line 13) | def get_uuid(self, name: Optional[str] = None):
    method from_uuid (line 21) | def from_uuid(self, uuid: Optional[str] = None):
    method from_name (line 29) | def from_name(self, name: str):

FILE: bottles/backend/wine/wineboot.py
  class WineBoot (line 11) | class WineBoot(WineProgram):
    method send_status (line 15) | def send_status(self, status: int):
    method force (line 41) | def force(self):
    method kill (line 44) | def kill(self, force_if_stalled: bool = False):
    method restart (line 53) | def restart(self):
    method shutdown (line 56) | def shutdown(self):
    method update (line 59) | def update(self):
    method init (line 62) | def init(self):
    method nv_stop_all_processes (line 65) | def nv_stop_all_processes(self):

FILE: bottles/backend/wine/winebridge.py
  class WineBridge (line 11) | class WineBridge(WineProgram):
    method __wineserver_status (line 17) | def __wineserver_status(self):
    method is_available (line 20) | def is_available(self):
    method get_procs (line 26) | def get_procs(self):
    method kill_proc (line 58) | def kill_proc(self, pid: str):
    method kill_proc_by_name (line 62) | def kill_proc_by_name(self, name: str):
    method run_exe (line 66) | def run_exe(self, exec_path: str):

FILE: bottles/backend/wine/winecfg.py
  class WineCfg (line 11) | class WineCfg(WineProgram):
    method set_windows_version (line 15) | def set_windows_version(self, version):

FILE: bottles/backend/wine/winecommand.py
  class WineEnv (line 33) | class WineEnv:
    method __init__ (line 41) | def __init__(self, clean: bool = False, allowed_keys: Optional[Iterabl...
    method add (line 54) | def add(self, key, value, override=False):
    method add_bundle (line 62) | def add_bundle(self, bundle, override=False):
    method get (line 66) | def get(self):
    method remove (line 73) | def remove(self, key):
    method is_empty (line 77) | def is_empty(self, key):
    method concat (line 80) | def concat(self, key, values, sep=":"):
    method has (line 89) | def has(self, key):
  function apply_wayland_preferences (line 93) | def apply_wayland_preferences(env: "WineEnv", params) -> None:
  function _needs_steam_virtual_gamepad_workaround (line 105) | def _needs_steam_virtual_gamepad_workaround(runner_name: Optional[str]) ...
  class WineCommand (line 129) | class WineCommand:
    method __init__ (line 135) | def __init__(
    method _get_config (line 176) | def _get_config(self, config: BottleConfig) -> BottleConfig:
    method _get_cwd (line 186) | def _get_cwd(self, cwd) -> str:
    method get_env (line 209) | def get_env(
    method _get_runner_info (line 507) | def _get_runner_info(self) -> tuple[str, str]:
    method get_cmd (line 545) | def get_cmd(
    method _get_gamescope_cmd (line 676) | def _get_gamescope_cmd(self, return_steam_cmd: bool = False) -> str:
    method _vmtouch_preload (line 713) | def _vmtouch_preload(self):
    method _vmtouch_free (line 728) | def _vmtouch_free(self):
    method _get_sandbox_manager (line 747) | def _get_sandbox_manager(self) -> SandboxManager:
    method run (line 757) | def run(self) -> Result[Optional[str]]:

FILE: bottles/backend/wine/winedbg.py
  class WineDbg (line 15) | class WineDbg(WineProgram):
    method __wineserver_status (line 20) | def __wineserver_status(self):
    method get_processes (line 24) | def get_processes(self):
    method wait_for_process (line 69) | def wait_for_process(self, name: str, timeout: float = 0.5):
    method kill_process (line 83) | def kill_process(self, pid: Optional[str] = None, name: Optional[str] ...
    method is_process_alive (line 112) | def is_process_alive(self, pid: Optional[str] = None, name: Optional[s...

FILE: bottles/backend/wine/winefile.py
  class WineFile (line 7) | class WineFile(WineProgram):
    method open_path (line 11) | def open_path(self, path: str = "C:\\\\"):

FILE: bottles/backend/wine/winepath.py
  class WinePath (line 11) | class WinePath(WineProgram):
    method is_windows (line 17) | def is_windows(path: str):
    method is_unix (line 22) | def is_unix(path: str):
    method __clean_path (line 27) | def __clean_path(path):
    method to_unix (line 31) | def to_unix(self, path: str, native: bool = False):
    method to_windows (line 44) | def to_windows(self, path: str, native: bool = False):
    method to_long (line 67) | def to_long(self, path: str):
    method to_short (line 73) | def to_short(self, path: str):

FILE: bottles/backend/wine/wineprogram.py
  class WineProgram (line 12) | class WineProgram:
    method __init__ (line 20) | def __init__(self, config: BottleConfig, silent=False):
    method get_command (line 28) | def get_command(self, args: Optional[str] = None):
    method launch (line 39) | def launch(
    method launch_terminal (line 87) | def launch_terminal(self, args: Optional[str] = None):
    method launch_minimal (line 90) | def launch_minimal(self, args: Optional[str] = None):

FILE: bottles/backend/wine/wineserver.py
  class WineServer (line 14) | class WineServer(WineProgram):
    method is_alive (line 18) | def is_alive(self):
    method wait (line 59) | def wait(self):
    method kill (line 84) | def kill(self, signal: int = -1):
    method force_kill (line 93) | def force_kill(self):

FILE: bottles/backend/wine/winhelp.py
  class WinHelp (line 7) | class WinHelp(WineProgram):

FILE: bottles/backend/wine/xcopy.py
  class Xcopy (line 10) | class Xcopy(WineProgram):
    method copy (line 14) | def copy(

FILE: bottles/frontend/cli/cli.py
  class CLI (line 69) | class CLI:
    method __init__ (line 72) | def __init__(self):
    method __clear (line 278) | def __clear():
    method __process_args (line 281) | def __process_args(self):
    method show_info (line 341) | def show_info(self):
    method list_bottles (line 357) | def list_bottles(self, c_filter=None):
    method list_components (line 379) | def list_components(self, c_filter=None):
    method list_programs (line 412) | def list_programs(self):
    method launch_tool (line 437) | def launch_tool(self):
    method add_program (line 467) | def add_program(self):
    method manage_reg (line 521) | def manage_reg(self):
    method manage_reg_rules (line 550) | def manage_reg_rules(self):
    method edit_bottle (line 628) | def edit_bottle(self):
    method new_bottle (line 722) | def new_bottle(self):
    method run_program (line 750) | def run_program(self):
    method run_shell (line 822) | def run_shell(self):
    method generate_standalone (line 842) | def generate_standalone(self):

FILE: bottles/frontend/main.py
  class Bottles (line 84) | class Bottles(Adw.Application):
    method __init__ (line 90) | def __init__(self):
    method __register_arguments (line 107) | def __register_arguments(self):
    method do_command_line (line 166) | def do_command_line(self, command):
    method __process_uri (line 207) | def __process_uri(self, uri):
    method do_startup (line 239) | def do_startup(self):
    method do_activate (line 247) | def do_activate(self):
    method __quit (line 265) | def __quit(self, *args):
    method __help (line 277) | def __help(action=None, param=None):
    method __refresh (line 287) | def __refresh(self, action=None, param=None):
    method __show_preferences (line 297) | def __show_preferences(self, *args):
    method __new_bottle (line 301) | def __new_bottle(self, *args):
    method __show_importer_view (line 304) | def __show_importer_view(self, widget=False, *args):
    method __show_journal (line 307) | def __show_journal(self, *args):
    method __on_journal_close (line 319) | def __on_journal_close(self, *_args):
    method __show_about_dialog (line 323) | def __show_about_dialog(self, *_args):
    method __create_action (line 396) | def __create_action(self, name, callback, shortcuts=None, param=None):
  function main (line 416) | def main(version):

FILE: bottles/frontend/operation.py
  class TaskEntry (line 27) | class TaskEntry(Adw.ActionRow):
    method __init__ (line 35) | def __init__(self, window, title, cancellable=True, **kwargs):
    method update (line 48) | def update(self, subtitle: str):
  class TaskSyncer (line 52) | class TaskSyncer:
    method __init__ (line 57) | def __init__(self, window):
    method _new_widget (line 60) | def _new_widget(self, title, cancellable=True) -> TaskEntry:
    method _set_task_btn_visible (line 66) | def _set_task_btn_visible(self, visible: bool):
    method task_added_handler (line 69) | def task_added_handler(self, res: Result):
    method task_updated_handler (line 76) | def task_updated_handler(self, res: Result):
    method task_removed_handler (line 84) | def task_removed_handler(self, res: Result):

FILE: bottles/frontend/utils/common.py
  function open_doc_url (line 21) | def open_doc_url(widget, page):

FILE: bottles/frontend/utils/filters.py
  function add_executable_filters (line 22) | def add_executable_filters(dialog):
  function add_yaml_filters (line 35) | def add_yaml_filters(dialog):
  function add_all_filters (line 47) | def add_all_filters(dialog):

FILE: bottles/frontend/utils/gtk.py
  class GtkUtils (line 27) | class GtkUtils:
    method validate_entry (line 29) | def validate_entry(entry, extend=None) -> bool:
    method validate_env_var_name (line 53) | def validate_env_var_name(entry, extend=None) -> bool:
    method reset_entry_apply_button (line 76) | def reset_entry_apply_button(entry) -> None:
    method run_in_main_loop (line 86) | def run_in_main_loop(func):
    method get_parent_window (line 101) | def get_parent_window() -> Optional[GObject.Object]:

FILE: bottles/frontend/utils/playtime.py
  class PlaytimeRecord (line 40) | class PlaytimeRecord:
  class PlaytimeCache (line 52) | class PlaytimeCache:
    method __init__ (line 55) | def __init__(self, ttl_seconds: int = 30):
    method get (line 59) | def get(self, bottle_id: str, program_id: str) -> Optional[PlaytimeRec...
    method set (line 75) | def set(self, bottle_id: str, program_id: str, record: PlaytimeRecord)...
    method invalidate (line 80) | def invalidate(self, bottle_id: str, program_id: str) -> None:
    method clear (line 88) | def clear(self) -> None:
  class PlaytimeService (line 93) | class PlaytimeService:
    method __init__ (line 100) | def __init__(self, manager):
    method is_enabled (line 110) | def is_enabled(self) -> bool:
    method get_program_playtime (line 117) | def get_program_playtime(
    method get_bottle_playtime (line 177) | def get_bottle_playtime(self, bottle_id: str) -> Optional[PlaytimeReco...
    method invalidate_program (line 228) | def invalidate_program(
    method invalidate_cache (line 242) | def invalidate_cache(self) -> None:
    method get_weekly_data (line 251) | def get_weekly_data(
    method get_hourly_data (line 294) | def get_hourly_data(
    method get_monthly_data (line 337) | def get_monthly_data(self, bottle_id: str, program_id: str, year: int)...
    method get_weekly_session_count (line 378) | def get_weekly_session_count(
    method get_daily_session_count (line 397) | def get_daily_session_count(
    method get_yearly_session_count (line 416) | def get_yearly_session_count(
    method format_playtime (line 436) | def format_playtime(total_seconds: int) -> str:
    method format_last_played (line 475) | def format_last_played(last_played: Optional[datetime]) -> str:
    method format_subtitle (line 515) | def format_subtitle(self, record: Optional[PlaytimeRecord]) -> str:

FILE: bottles/frontend/utils/sh.py
  class ShUtils (line 23) | class ShUtils:
    method is_name (line 25) | def is_name(text: str) -> bool:
    method split_assignment (line 29) | def split_assignment(text: str) -> tuple[str, str]:

FILE: bottles/frontend/views/bottle_dependencies.py
  class DependenciesView (line 32) | class DependenciesView(Adw.Bin):
    method __init__ (line 49) | def __init__(self, details, config: BottleConfig, **kwargs):
    method __search_dependencies (line 73) | def __search_dependencies(self, *_args):
    method __filter_dependencies (line 82) | def __filter_dependencies(row, terms=None):
    method empty_list (line 88) | def empty_list(self):
    method update (line 94) | def update(self, _widget=False, config: Optional[BottleConfig] = None):

FILE: bottles/frontend/views/bottle_details.py
  class BottleView (line 59) | class BottleView(Adw.PreferencesPage):
    method __init__ (line 122) | def __init__(self, details, config, **kwargs):
    method __change_page (line 201) | def __change_page(self, _widget, page_name):
    method __show_versioning_settings (line 214) | def __show_versioning_settings(self, widget):
    method __update_fvs2_badge (line 219) | def __update_fvs2_badge(self, bottle_path):
    method on_drop (line 236) | def on_drop(self, drop_target, value: Gdk.FileList, x, y, user_data=No...
    method on_enter (line 264) | def on_enter(self, drop_target, x, y):
    method on_leave (line 268) | def on_leave(self, drop_target):
    method set_config (line 271) | def set_config(self, config: BottleConfig):
    method add (line 320) | def add(self, widget=False):
    method update_programs (line 369) | def update_programs(
    method add_program (line 435) | def add_program(self, widget):
    method __toggle_removed (line 443) | def __toggle_removed(self, widget=False):
    method __scan_programs (line 454) | def __scan_programs(self, widget=False):
    method empty_list (line 457) | def empty_list(self):
    method _on_program_finished (line 465) | def _on_program_finished(self, data=None):
    method _on_task_added (line 498) | def _on_task_added(self, data=None):
    method _on_task_removed (line 514) | def _on_task_removed(self, data=None):
    method _on_task_updated (line 524) | def _on_task_updated(self, data=None):
    method _show_backup_progress (line 535) | def _show_backup_progress(self):
    method _hide_backup_progress (line 541) | def _hide_backup_progress(self):
    method _update_backup_progress (line 546) | def _update_backup_progress(self, text: str):
    method populate_updates (line 550) | def populate_updates(self):
    method __collect_component_updates (line 563) | def __collect_component_updates(self) -> List[Dict[str, object]]:
    method __collect_dll_component_update (line 608) | def __collect_dll_component_update(
    method __collect_runner_update (line 627) | def __collect_runner_update(self) -> Optional[Dict[str, object]]:
    method __collect_winebridge_update (line 653) | def __collect_winebridge_update(self) -> Optional[Dict[str, object]]:
    method __resolve_runner_catalog (line 676) | def __resolve_runner_catalog(self, runner: str) -> Tuple[List[str], str]:
    method __match_runner_candidates (line 691) | def __match_runner_candidates(self, runner: str, catalog: dict) -> Lis...
    method __runner_family (line 700) | def __runner_family(runner: str) -> str:
    method __get_latest_supported (line 712) | def __get_latest_supported(supported_dict: dict) -> Optional[str]:
    method __is_version_newer (line 721) | def __is_version_newer(self, latest: str, current: Optional[str]) -> b...
    method __build_update_row (line 733) | def __build_update_row(self, update: Dict[str, object]) -> Adw.ActionRow:
    method __run_update (line 755) | def __run_update(self, button, spinner, update):
    method __ensure_component_available (line 804) | def __ensure_component_available(self, component: str, version: str) -...
    method __update_dll_component (line 816) | def __update_dll_component(
    method __update_runner_component (line 850) | def __update_runner_component(
    method __update_winebridge_component (line 863) | def __update_winebridge_component(
    method __run_executable_with_args (line 870) | def __run_executable_with_args(self, widget):
    method run_executable (line 878) | def run_executable(self, widget, args=False):
    method run_eagle (line 941) | def run_eagle(self, _widget):
    method __backup (line 970) | def __backup(self, widget, backup_type):
    method __duplicate (line 1022) | def __duplicate(self, widget):
    method __upgrade_versioning (line 1030) | def __upgrade_versioning(self):
    method __confirm_delete (line 1038) | def __confirm_delete(self, widget):
    method __alert_missing_runner (line 1068) | def __alert_missing_runner(self):
    method __update_by_env (line 1089) | def __update_by_env(self):
    method run_winecfg (line 1099) | def run_winecfg(self, widget):
    method run_debug (line 1103) | def run_debug(self, widget):
    method run_browse (line 1107) | def run_browse(self, widget):
    method run_explorer (line 1110) | def run_explorer(self, widget):
    method run_cmd (line 1114) | def run_cmd(self, widget):
    method run_snake (line 1119) | def run_snake(widget, event):
    method run_taskmanager (line 1123) | def run_taskmanager(self, widget):
    method run_controlpanel (line 1127) | def run_controlpanel(self, widget):
    method run_uninstaller (line 1131) | def run_uninstaller(self, widget):
    method run_regedit (line 1135) | def run_regedit(self, widget):
    method wineboot (line 1139) | def wineboot(self, widget, status):
    method __set_steam_rules (line 1166) | def __set_steam_rules(self):

FILE: bottles/frontend/views/bottle_installers.py
  class InstallersView (line 30) | class InstallersView(Adw.Bin):
    method __init__ (line 47) | def __init__(self, details, config, **kwargs):
    method __search_installers (line 62) | def __search_installers(self, *_args):
    method __filter_installers (line 71) | def __filter_installers(row, terms=None):
    method empty_list (line 77) | def empty_list(self):
    method update (line 83) | def update(self, widget=False, config=None):

FILE: bottles/frontend/views/bottle_preferences.py
  class PreferencesView (line 62) | class PreferencesView(Adw.PreferencesPage):
    method __init__ (line 133) | def __init__(self, details, config, **kwargs):
    method __create_unavailable_popover (line 298) | def __create_unavailable_popover(self, command: str | None) -> Gtk.Pop...
    method __add_unavailable_indicator (line 343) | def __add_unavailable_indicator(self, row: Adw.ActionRow, command: str...
    method __copy_command_to_clipboard (line 357) | def __copy_command_to_clipboard(self, _widget, command: str):
    method __check_entry_name (line 366) | def __check_entry_name(self, *_args):
    method __save_name (line 377) | def __save_name(self, *_args):
    method choose_cwd (line 405) | def choose_cwd(self, widget):
    method reset_cwd (line 427) | def reset_cwd(self, *_args):
    method __update_working_directory_row (line 431) | def __update_working_directory_row(self, working_dir=None):
    method update_combo_components (line 447) | def update_combo_components(self):
    method set_config (line 502) | def set_config(self, config: BottleConfig):
    method __show_display_settings (line 655) | def __show_display_settings(self, widget):
    method __show_feature_dialog (line 666) | def __show_feature_dialog(self, _widget: Gtk.Widget, dialog: Adw.Windo...
    method __toggle_feature (line 671) | def __toggle_feature(self, state: bool, key: str) -> None:
    method __toggle_feature_cb (line 677) | def __toggle_feature_cb(self, _widget: Gtk.Widget, state: bool, key: s...
    method __toggle_wayland (line 680) | def __toggle_wayland(self, _widget: Gtk.Widget, state: bool) -> None:
    method __set_sync_type (line 685) | def __set_sync_type(self, *_args):
    method __toggle_nvapi (line 706) | def __toggle_nvapi(self, widget=False, state=False):
    method __set_runner (line 721) | def __set_runner(self, *_args):
    method __dll_component_task_func (line 786) | def __dll_component_task_func(self, *args, **kwargs):
    method __set_dxvk (line 796) | def __set_dxvk(self, *_args):
    method __set_vkd3d (line 836) | def __set_vkd3d(self, *_args):
    method __set_nvapi (line 876) | def __set_nvapi(self, *_args):
    method __set_latencyflex (line 899) | def __set_latencyflex(self, *_args):
    method __set_windows (line 932) | def __set_windows(self, *_args):
    method __set_language (line 958) | def __set_language(self, *_args):
    method set_dxvk_status (line 969) | def set_dxvk_status(self, status=None, error=None, pending=False):
    method set_vkd3d_status (line 981) | def set_vkd3d_status(self, status=None, error=None, pending=False):
    method set_nvapi_status (line 993) | def set_nvapi_status(self, status=None, error=None, pending=False):
    method set_latencyflex_status (line 1010) | def set_latencyflex_status(self, status=None, error=None, pending=False):
    method __set_steam_rules (line 1021) | def __set_steam_rules(self):

FILE: bottles/frontend/views/bottle_registry_rules.py
  class RegistryRulesView (line 29) | class RegistryRulesView(Adw.Bin):
    method __init__ (line 43) | def __init__(self, details, config: BottleConfig, **kwargs):
    method update (line 59) | def update(self, _widget=False, config: Optional[BottleConfig] = None):
    method __search_rules (line 66) | def __search_rules(self, *_args):
    method __filter_rules (line 71) | def __filter_rules(row, terms=None):
    method __load_rules (line 77) | def __load_rules(self):
    method __clear_rules (line 91) | def __clear_rules(self):
    method __open_rule_dialog (line 97) | def __open_rule_dialog(self, *_args, rule: Optional[RegistryRule] = No...
    method __on_rule_saved (line 104) | def __on_rule_saved(self, *_args):
    method populate_form (line 107) | def populate_form(self, rule: RegistryRule):
    method remove_entry (line 110) | def remove_entry(self, entry: RegistryRuleEntry):
  class RegistryRulesDialog (line 121) | class RegistryRulesDialog(Adw.Dialog):
    method __init__ (line 142) | def __init__(
    method present (line 161) | def present(self):
    method __populate_form (line 164) | def __populate_form(self, rule: RegistryRule):
    method __save_rule (line 175) | def __save_rule(self, *_args):

FILE: bottles/frontend/views/bottle_taskmanager.py
  class TaskManagerView (line 30) | class TaskManagerView(Gtk.ScrolledWindow):
    method __init__ (line 41) | def __init__(self, details, config, **kwargs):
    method set_config (line 75) | def set_config(self, config):
    method show_kill_btn (line 78) | def show_kill_btn(self, widget):
    method update (line 86) | def update(self, widget=False, config: Optional[BottleConfig] = None):
    method sensitive_update (line 123) | def sensitive_update(self, widget):
    method kill_process (line 133) | def kill_process(self, widget):

FILE: bottles/frontend/views/bottle_versioning.py
  class VersioningView (line 34) | class VersioningView(Adw.PreferencesPage):
    method __init__ (line 54) | def __init__(self, details, config, **kwargs):
    method _on_mapped (line 72) | def _on_mapped(self, widget):
    method _refresh_dirty_state (line 75) | def _refresh_dirty_state(self):
    method _set_busy (line 92) | def _set_busy(self, busy, label=""):
    method _refresh_details_badge (line 102) | def _refresh_details_badge(self):
    method on_branch_changed (line 110) | def on_branch_changed(self, widget, pspec):
    method show_add_branch_dialog (line 131) | def show_add_branch_dialog(self, widget):
    method show_manage_branches_dialog (line 135) | def show_manage_branches_dialog(self, widget):
    method create_branch (line 139) | def create_branch(self, branch_name: str):
    method empty_list (line 157) | def empty_list(self):
    method update (line 163) | def update(self, widget=None, config=None, states=None, active=0):
    method show_add_state_dialog (line 255) | def show_add_state_dialog(self, widget):
    method add_state (line 259) | def add_state(self, message: str):

FILE: bottles/frontend/views/details.py
  class DetailsView (line 39) | class DetailsView(Adw.Bin):
    method __init__ (line 66) | def __init__(self, window, config: Optional[BottleConfig] = None, **kw...
    method set_title (line 102) | def set_title(self, title, subtitle: str = ""):
    method __on_leaflet_folded (line 110) | def __on_leaflet_folded(self, widget, *_args):
    method __on_page_change (line 116) | def __on_page_change(self, *_args):
    method build_pages (line 139) | def build_pages(self):
    method set_actions (line 197) | def set_actions(self, widget: Gtk.Widget = None):
    method set_config (line 207) | def set_config(self, config: BottleConfig, rebuild_pages=True):
    method __on_operations_toggled (line 227) | def __on_operations_toggled(self, widget):
    method __spin_tasks_toggle (line 231) | def __spin_tasks_toggle(self, widget, *_args):
    method go_back (line 239) | def go_back(self, _widget=False):
    method go_back_sidebar (line 242) | def go_back_sidebar(self, *_args):
    method unload_view (line 245) | def unload_view(self, *_args):
    method lock_back (line 250) | def lock_back(self):
    method unlock_back (line 255) | def unlock_back(self):
    method update_runner_label (line 259) | def update_runner_label(self, runner: str):

FILE: bottles/frontend/views/eagle.py
  class EagleView (line 35) | class EagleView(Gtk.Box):
    method __init__ (line 53) | def __init__(self, details, config: BottleConfig, **kwargs):
    method analyze (line 70) | def analyze(self, executable_path: str) -> None:
    method __reset_steps (line 118) | def __reset_steps(self) -> None:
    method __create_step_row (line 129) | def __create_step_row(self, text: str) -> Gtk.Box:
    method __mark_last_step_completed (line 156) | def __mark_last_step_completed(self) -> None:
    method __set_step_completed (line 164) | def __set_step_completed(self, row: Adw.ActionRow) -> None:
    method __on_eagle_step (line 173) | def __on_eagle_step(self, res: Result) -> None:
    method __create_info_row (line 191) | def __create_info_row(self, title: str, subtitle: str, icon: str = Non...
    method __on_eagle_finished (line 200) | def __on_eagle_finished(self, res: Result) -> None:
    method __on_launch_clicked (line 497) | def __on_launch_clicked(self, _widget) -> None:
    method __on_report_clicked (line 550) | def __on_report_clicked(self, _widget) -> None:

FILE: bottles/frontend/views/importer.py
  class ImporterView (line 31) | class ImporterView(Adw.Bin):
    method __init__ (line 48) | def __init__(self, window, **kwargs):
    method __find_prefixes (line 68) | def __find_prefixes(self, widget):
    method __finish (line 97) | def __finish(self, result, error=False):
    method __import_full_bck (line 103) | def __import_full_bck(self, *_args):
    method __import_config_bck (line 143) | def __import_config_bck(self, *_args):
    method _on_task_added (line 176) | def _on_task_added(self, data=None):
    method _on_task_removed (line 191) | def _on_task_removed(self, data=None):
    method _on_task_updated (line 201) | def _on_task_updated(self, data=None):
    method _show_import_progress (line 212) | def _show_import_progress(self):
    method _hide_import_progress (line 218) | def _hide_import_progress(self):
    method _update_import_progress (line 223) | def _update_import_progress(self, text: str):
    method go_back (line 227) | def go_back(self, *_args):

FILE: bottles/frontend/views/library.py
  class LibraryView (line 29) | class LibraryView(Adw.Bin):
    method __init__ (line 41) | def __init__(self, window, **kwargs):
    method update (line 47) | def update(self):
    method remove_entry (line 65) | def remove_entry(self, entry):
    method __delete_entry (line 85) | def __delete_entry(self, entry):
    method go_back (line 89) | def go_back(self, widget=False):

FILE: bottles/frontend/views/list.py
  class BottlesBottleRow (line 33) | class BottlesBottleRow(Adw.ActionRow):
    method __init__ (line 44) | def __init__(self, window, config: BottleConfig, **kwargs):
    method run_executable (line 83) | def run_executable(self, *_args):
    method show_details (line 115) | def show_details(self, widget=None, config=None):
    method disable (line 121) | def disable(self):
  class BottleView (line 127) | class BottleView(Adw.Bin):
    method __init__ (line 145) | def __init__(self, window, arg_bottle=None, **kwargs):
    method __search_bottles (line 165) | def __search_bottles(self, widget, event=None, data=None):
    method __filter_bottles (line 175) | def __filter_bottles(row, terms=None):
    method update_bottles_list (line 179) | def update_bottles_list(self, *args) -> None:
    method show_page (line 209) | def show_page(self, page: str) -> None:
    method disable_bottle (line 213) | def disable_bottle(self, config):

FILE: bottles/frontend/views/loading.py
  class LoadingView (line 29) | class LoadingView(Adw.Bin):
    method __init__ (line 40) | def __init__(self, **kwargs):
    method add_fetched (line 46) | def add_fetched(self, res: Result):
    method go_offline (line 56) | def go_offline(self, _widget):

FILE: bottles/frontend/views/new_bottle_dialog.py
  class BottlesCheckRow (line 35) | class BottlesCheckRow(Adw.ActionRow):
  class BottlesNewBottleDialog (line 55) | class BottlesNewBottleDialog(Adw.Dialog):
    method __init__ (line 85) | def __init__(self, **kwargs: Any) -> None:
    method __check_validity (line 137) | def __check_validity(self, *_args: Any) -> tuple[bool, bool]:
    method __check_entry_name (line 142) | def __check_entry_name(self, *_args: Any) -> None:
    method __entry_activated (line 153) | def __entry_activated(self, *_args: Any) -> None:
    method __remove_notifications (line 157) | def __remove_notifications(self, *_args: Any) -> None:
    method __choose_env_recipe (line 160) | def __choose_env_recipe(self, *_args: Any) -> None:
    method __choose_path (line 196) | def __choose_path(self, *_args: Any) -> None:
    method create_bottle (line 217) | def create_bottle(self, *_args: Any) -> None:
    method update_output (line 252) | def update_output(self, text: str) -> None:
    method __create_step_row (line 281) | def __create_step_row(self, text: str) -> Adw.ActionRow:
    method __mark_last_step_completed (line 295) | def __mark_last_step_completed(self) -> None:
    method __set_step_completed (line 301) | def __set_step_completed(self, row: Adw.ActionRow) -> None:
    method finish (line 307) | def finish(self, result: Optional[Result], error=None) -> None:
    method __reset_env_recipe (line 375) | def __reset_env_recipe(self, *_args: Any) -> None:
    method __reset_path (line 380) | def __reset_path(self, *_args: Any) -> None:
    method __close_dialog (line 385) | def __close_dialog(self, *_args: Any) -> None:
    method __prompt_cancel_confirmation (line 397) | def __prompt_cancel_confirmation(
    method __cancel_creation (line 425) | def __cancel_creation(self) -> None:
    method __finalize_close (line 449) | def __finalize_close(self) -> None:
    method __clear_creation_task (line 453) | def __clear_creation_task(self) -> None:
    method __reset_creation_steps (line 464) | def __reset_creation_steps(self) -> None:
    method __build_cleanup_config (line 473) | def __build_cleanup_config(self) -> BottleConfig:
    method __start_cancellation_cleanup (line 489) | def __start_cancellation_cleanup(self) -> None:
    method __on_cleanup_finished (line 501) | def __on_cleanup_finished(self, success: Optional[bool], error=None) -...

FILE: bottles/frontend/views/preferences.py
  class PreferencesWindow (line 33) | class PreferencesWindow(Adw.PreferencesWindow):
    method __init__ (line 82) | def __init__(self, window, **kwargs):
    method empty_list (line 251) | def empty_list(self):
    method ui_update (line 258) | def ui_update(self):
    method __toggle_night (line 268) | def __toggle_night(self, widget, state):
    method __toggle_update_date (line 274) | def __toggle_update_date(self, widget, state):
    method __toggle_rc (line 277) | def __toggle_rc(self, widget, state):
    method __on_eagle_limit_changed (line 280) | def __on_eagle_limit_changed(self, spin_row, _pspec):
    method __open_steam_proton_doc (line 283) | def __open_steam_proton_doc(self, widget):
    method __choose_bottles_path (line 288) | def __choose_bottles_path(self, widget):
    method handle_restart (line 310) | def handle_restart(self, widget, response_id):
    method prompt_restart (line 316) | def prompt_restart(self, force=False):
    method __reset_bottles_path (line 337) | def __reset_bottles_path(self, widget):
    method __on_personal_repo_changed (line 343) | def __on_personal_repo_changed(self, row, repo_name):
    method __on_personal_repo_apply (line 349) | def __on_personal_repo_apply(self, row, repo_name):
    method __persist_personal_repositories (line 359) | def __persist_personal_repositories(self):
    method __on_audio_driver_setting_changed (line 371) | def __on_audio_driver_setting_changed(self, *_args):
    method __sync_audio_driver_selection (line 374) | def __sync_audio_driver_selection(self, *_args):
    method __on_audio_driver_selected (line 385) | def __on_audio_driver_selected(self, combo, _pspec):
    method __display_unstable_candidate (line 400) | def __display_unstable_candidate(self, component=["", {"Channel": "uns...
    method __populate_component_list (line 405) | def __populate_component_list(
    method populate_dlls_list (line 445) | def populate_dlls_list(self):
    method __populate_runners_helper (line 461) | def __populate_runners_helper(
    method populate_runners_list (line 520) | def populate_runners_list(self):
    method populate_cache_list (line 637) | def populate_cache_list(self):
    method __render_cache_details (line 651) | def __render_cache_details(self, cache_details: dict):
    method __populate_template_cache_rows (line 671) | def __populate_template_cache_rows(self, templates: list[dict]):
    method __format_template_title (line 715) | def __format_template_title(self, template: dict) -> str:
    method __format_template_subtitle (line 719) | def __format_template_subtitle(self, template: dict) -> str:
    method __format_env_label (line 731) | def __format_env_label(self, env: str) -> str:
    method __confirm_clear_all_caches (line 744) | def __confirm_clear_all_caches(self, widget):
    method __confirm_clear_temp_cache (line 755) | def __confirm_clear_temp_cache(self, widget):
    method __confirm_clear_templates_cache (line 766) | def __confirm_clear_templates_cache(self, widget):
    method __confirm_clear_template (line 777) | def __confirm_clear_template(self, widget, template: dict):
    method __confirm_cache_action (line 792) | def __confirm_cache_action(self, title: str, description: str, action,...
    method __cache_action_finished (line 819) | def __cache_action_finished(self, result, button=None):

FILE: bottles/frontend/widgets/component.py
  class ComponentEntry (line 34) | class ComponentEntry(Adw.ActionRow):
    method __init__ (line 53) | def __init__(
    method download (line 95) | def download(self, widget):
    method uninstall (line 150) | def uninstall(self, widget):
    method run_browse (line 168) | def run_browse(self, widget):
    method update_progress (line 175) | def update_progress(
    method set_err (line 219) | def set_err(self, msg=None, retry=True):
    method set_installed (line 231) | def set_installed(self):
    method set_uninstalled (line 240) | def set_uninstalled(self):
    method cancel_download (line 250) | def cancel_download(self, widget):
    method _restore_pre_download_visibility (line 261) | def _restore_pre_download_visibility(self):
    method _set_spinner_active (line 284) | def _set_spinner_active(self, active: bool):
    method _sanitize_progress_value (line 294) | def _sanitize_progress_value(value: Optional[int]) -> int:
    method _clear_download_context (line 307) | def _clear_download_context(self):
  class ComponentExpander (line 313) | class ComponentExpander(Adw.ExpanderRow):
    method __init__ (line 314) | def __init__(self, title, subtitle=None, **kwargs):

FILE: bottles/frontend/widgets/dependency.py
  class DependencyEntry (line 33) | class DependencyEntry(Adw.ActionRow):
    method __init__ (line 49) | def __init__(self, window, config: BottleConfig, dependency, plain=Fal...
    method open_manifest (line 111) | def open_manifest(self, _widget):
    method open_license (line 124) | def open_license(self, _widget):
    method install_dependency (line 134) | def install_dependency(self, _widget):
    method remove_dependency (line 160) | def remove_dependency(self, _widget):
    method set_install_status (line 174) | def set_install_status(self, result: Result, error=None):
    method set_err (line 201) | def set_err(self):
    method set_installed (line 213) | def set_installed(self, installer=True, removed=False):

FILE: bottles/frontend/widgets/executable.py
  class ExecButton (line 24) | class ExecButton(Gtk.Button):
    method __init__ (line 25) | def __init__(self, parent, data, config, **kwargs):
    method on_clicked (line 34) | def on_clicked(self, widget):

FILE: bottles/frontend/widgets/importer.py
  class ImporterEntry (line 28) | class ImporterEntry(Adw.ActionRow):
    method __init__ (line 39) | def __init__(self, im_manager, prefix, **kwargs):
    method browse_wineprefix (line 60) | def browse_wineprefix(self, widget):
    method import_wineprefix (line 63) | def import_wineprefix(self, widget):

FILE: bottles/frontend/widgets/installer.py
  class InstallerEntry (line 28) | class InstallerEntry(Adw.ActionRow):
    method __init__ (line 40) | def __init__(self, window, config, installer, **kwargs):
    method __open_manifest (line 78) | def __open_manifest(self, widget):
    method __open_review (line 89) | def __open_review(self, widget):
    method __open_bug_report (line 102) | def __open_bug_report(widget):
    method __execute_installer (line 106) | def __execute_installer(self, widget):

FILE: bottles/frontend/widgets/library.py
  class LibraryEntryInitializationError (line 34) | class LibraryEntryInitializationError(Exception):
  class LibraryEntry (line 41) | class LibraryEntry(Gtk.Box):
    method __init__ (line 59) | def __init__(self, library, uuid, entry, *args, **kwargs):
    method __get_config (line 127) | def __get_config(self):
    method __get_program (line 137) | def __get_program(self):
    method __remove_from_library (line 149) | def __remove_from_library(self):
    method __handle_initialization_failure (line 153) | def __handle_initialization_failure(self, message: str):
    method __reset_buttons (line 160) | def __reset_buttons(self, result: Result | bool = None, error=False):
    method __is_alive (line 176) | def __is_alive(self):
    method __remove_entry (line 197) | def __remove_entry(self, *args):
    method run_executable (line 200) | def run_executable(self, widget, with_terminal=False):
    method run_steam (line 210) | def run_steam(self, widget):
    method stop_process (line 213) | def stop_process(self, widget):
    method __on_motion_enter (line 219) | def __on_motion_enter(self, *args):
    method __on_motion_leave (line 223) | def __on_motion_leave(self, *args):
    method hide (line 229) | def hide(self):
    method show (line 232) | def show(self):

FILE: bottles/frontend/widgets/playtimechart_hourly.py
  class PlaytimeChartHourly (line 39) | class PlaytimeChartHourly(Gtk.Box):
    method __init__ (line 48) | def __init__(self, **kwargs: Any) -> None:
    method _get_font_scale (line 73) | def _get_font_scale(self) -> float:
    method _on_width_changed (line 82) | def _on_width_changed(self, *_args: Any) -> None:
    method _on_style_changed (line 88) | def _on_style_changed(self, *_args: Any) -> None:
    method _build_ui (line 98) | def _build_ui(self) -> None:
    method set_hourly_data (line 112) | def set_hourly_data(
    method _render_chart (line 125) | def _render_chart(self, max_hours_override: Optional[int] = None) -> N...
    method _draw_hour_labels (line 199) | def _draw_hour_labels(
    method _on_chart_motion (line 240) | def _on_chart_motion(
    method _on_chart_tooltip (line 247) | def _on_chart_tooltip(
    method _draw_chart (line 278) | def _draw_chart(
    method _format_time (line 466) | def _format_time(self, minutes: int) -> str:

FILE: bottles/frontend/widgets/playtimechart_monthly.py
  class PlaytimeChartMonthly (line 39) | class PlaytimeChartMonthly(Gtk.Box):
    method __init__ (line 48) | def __init__(self, **kwargs: Any) -> None:
    method _get_font_scale (line 72) | def _get_font_scale(self) -> float:
    method _on_width_changed (line 81) | def _on_width_changed(self, *_args: Any) -> None:
    method _on_style_changed (line 87) | def _on_style_changed(self, *_args: Any) -> None:
    method _build_ui (line 97) | def _build_ui(self) -> None:
    method set_monthly_data (line 112) | def set_monthly_data(
    method _render_chart (line 125) | def _render_chart(self, max_hours_override: Optional[int] = None) -> N...
    method _draw_month_labels (line 210) | def _draw_month_labels(
    method _on_chart_motion (line 263) | def _on_chart_motion(
    method _on_chart_tooltip (line 270) | def _on_chart_tooltip(
    method _draw_chart (line 302) | def _draw_chart(
    method _format_time (line 489) | def _format_time(self, minutes: int) -> str:

FILE: bottles/frontend/widgets/playtimechart_weekly.py
  class PlaytimeChartWeekly (line 38) | class PlaytimeChartWeekly(Gtk.Box):
    method __init__ (line 47) | def __init__(self, **kwargs: Any) -> None:
    method _get_font_scale (line 71) | def _get_font_scale(self) -> float:
    method _on_width_changed (line 80) | def _on_width_changed(self, *_args: Any) -> None:
    method _on_style_changed (line 86) | def _on_style_changed(self, *_args: Any) -> None:
    method _build_ui (line 96) | def _build_ui(self) -> None:
    method set_daily_data (line 111) | def set_daily_data(
    method _render_chart (line 124) | def _render_chart(self, max_hours_override: Optional[int] = None) -> N...
    method _draw_day_labels (line 207) | def _draw_day_labels(
    method _on_chart_motion (line 245) | def _on_chart_motion(
    method _on_chart_tooltip (line 252) | def _on_chart_tooltip(
    method _draw_chart (line 284) | def _draw_chart(
    method _format_time (line 468) | def _format_time(self, minutes: int) -> str:

FILE: bottles/frontend/widgets/program.py
  class ProgramEntry (line 42) | class ProgramEntry(Adw.ActionRow):
    method __init__ (line 66) | def __init__(
    method __update_subtitle (line 136) | def __update_subtitle(self):
    method show_launch_options_view (line 172) | def show_launch_options_view(self, _widget=False):
    method show_playtime_stats (line 181) | def show_playtime_stats(self, _widget=False):
    method __reset_buttons (line 201) | def __reset_buttons(self, result: bool | Result = False, _error=False):
    method __is_alive (line 219) | def __is_alive(self):
    method run_executable (line 236) | def run_executable(self, _widget, with_terminal=False):
    method run_steam (line 248) | def run_steam(self, _widget):
    method stop_process (line 255) | def stop_process(self, widget):
    method update_programs (line 263) | def update_programs(self, _result=False, _error=False):
    method uninstall_program (line 266) | def uninstall_program(self, _widget):
    method hide_program (line 274) | def hide_program(self, _widget=None, update=True):
    method save_program (line 288) | def save_program(self):
    method remove_program (line 296) | def remove_program(self, _widget=None):
    method rename_program (line 307) | def rename_program(self, _widget):
    method browse_program_folder (line 346) | def browse_program_folder(self, _widget):
    method add_entry (line 352) | def add_entry(self, _widget):
    method add_to_library (line 368) | def add_to_library(self, _widget):
    method add_to_steam (line 393) | def add_to_steam(self, _widget):
    method update_playtime (line 408) | def update_playtime(self, playtime_service):

FILE: bottles/frontend/widgets/state.py
  class StateEntry (line 31) | class StateEntry(Adw.ActionRow):
    method __init__ (line 43) | def __init__(self, parent, config, state, active, **kwargs):
    method set_state (line 86) | def set_state(self, widget):
    method set_completed (line 131) | def set_completed(self, result, error=False):

FILE: bottles/frontend/windows/bottlepicker.py
  class BottleEntry (line 27) | class BottleEntry(Adw.ActionRow):
    method __init__ (line 28) | def __init__(self, config: BottleConfig):
  class BottlePickerDialog (line 35) | class BottlePickerDialog(Adw.ApplicationWindow):
    method __init__ (line 50) | def __init__(self, arg_exe, **kwargs):
    method __close (line 66) | def __close(*_args):
    method __select (line 69) | def __select(self, *_args):
    method __open (line 84) | def __open(self, *_args):

FILE: bottles/frontend/windows/crash.py
  class SimilarReportEntry (line 28) | class SimilarReportEntry(Adw.ActionRow):
    method __init__ (line 29) | def __init__(self, report: dict):
    method __on_btn_report_clicked (line 40) | def __on_btn_report_clicked(button, report):
  class CrashReportDialog (line 49) | class CrashReportDialog(Adw.Window):
    method __init__ (line 62) | def __init__(self, window, log, **kwargs):
    method __on_unlock_send (line 105) | def __on_unlock_send(self, widget):
    method __get_similarity (line 113) | def __get_similarity(log: str, issue: dict) -> int:
    method __get_similar_issues (line 137) | def __get_similar_issues(log):
    method __open_github (line 164) | def __open_github(self, widget, log):

FILE: bottles/frontend/windows/dependency_install.py
  class DependencyInstallDialog (line 28) | class DependencyInstallDialog(Adw.Window):
    method __init__ (line 40) | def __init__(self, parent, dependency_name: str, **kwargs):
    method __create_step_row (line 55) | def __create_step_row(self, text: str) -> Adw.ActionRow:
    method __mark_last_step_completed (line 69) | def __mark_last_step_completed(self) -> None:
    method __scroll_to_bottom (line 77) | def __scroll_to_bottom(self) -> bool:
    method __on_close_clicked (line 82) | def __on_close_clicked(self, *_args):
    method add_step (line 86) | def add_step(self, text: str) -> None:
    method update_progress (line 100) | def update_progress(self, fraction: Optional[float]) -> None:
    method finish (line 115) | def finish(self, success: bool) -> None:

FILE: bottles/frontend/windows/depscheck.py
  class DependenciesCheckDialog (line 22) | class DependenciesCheckDialog(Adw.Window):
    method __init__ (line 30) | def __init__(self, window, **kwargs):
    method __quit (line 37) | def __quit(self, *_args):

FILE: bottles/frontend/windows/display.py
  class DisplayDialog (line 34) | class DisplayDialog(Adw.Window):
    method __init__ (line 49) | def __init__(
    method __update (line 68) | def __update(self, config):
    method __idle_save (line 93) | def __idle_save(self, *args):
    method __save (line 270) | def __save(self, *args):

FILE: bottles/frontend/windows/dlloverrides.py
  class DLLEntry (line 26) | class DLLEntry(Adw.ComboRow):
    method __init__ (line 34) | def __init__(self, window, config, override, **kwargs):
    method __set_override_type (line 55) | def __set_override_type(self, *_args):
    method __remove_override (line 69) | def __remove_override(self, *_args):
  class DLLOverridesDialog (line 85) | class DLLOverridesDialog(Adw.PreferencesWindow):
    method __init__ (line 95) | def __init__(self, window, config, **kwargs):
    method __check_override (line 110) | def __check_override(self, *_args):
    method __save_override (line 133) | def __save_override(self, *_args):
    method __populate_overrides_list (line 152) | def __populate_overrides_list(self):

FILE: bottles/frontend/windows/drives.py
  class DriveEntry (line 27) | class DriveEntry(Adw.ActionRow):
    method __init__ (line 36) | def __init__(self, parent, drive, **kwargs):
    method __choose_path (line 58) | def __choose_path(self, *_args):
    method __remove (line 79) | def __remove(self, *_args):
  class DrivesDialog (line 87) | class DrivesDialog(Adw.Window):
    method __init__ (line 99) | def __init__(self, window, config, **kwargs):
    method __save (line 113) | def __save(self, *_args):
    method __populate_combo_and_drives (line 122) | def __populate_combo_and_drives(self):
    method add_combo_letter (line 138) | def add_combo_letter(self, letter: str):

FILE: bottles/frontend/windows/duplicate.py
  class DuplicateDialog (line 28) | class DuplicateDialog(Adw.Window):
    method __init__ (line 40) | def __init__(self, parent, **kwargs):
    method __check_entry_name (line 53) | def __check_entry_name(self, *_args):
    method __duplicate_bottle (line 62) | def __duplicate_bottle(self, widget):
    method finish (line 83) | def finish(self, result, error=None):
    method pulse (line 88) | def pulse(self):

FILE: bottles/frontend/windows/envvars.py
  class EnvironmentVariableEntryRow (line 32) | class EnvironmentVariableEntryRow(Adw.EntryRow):
    method __init__ (line 39) | def __init__(self, parent, env, **kwargs):
    method __customize_layout (line 56) | def __customize_layout(self):
    method __save (line 80) | def __save(self, *_args):
    method __remove (line 100) | def __remove(self, *_args):
    method __remove_config (line 108) | def __remove_config(self, *_args):
    method __validate (line 118) | def __validate(self, *_args):
  class InheritedEnvironmentVariableRow (line 128) | class InheritedEnvironmentVariableRow(Adw.ActionRow):
    method __init__ (line 133) | def __init__(self, parent, name: str, **kwargs):
    method __remove (line 140) | def __remove(self, *_args):
  class EnvironmentVariablesDialog (line 145) | class EnvironmentVariablesDialog(Adw.Dialog):
    method __init__ (line 158) | def __init__(self, window, config, **kwargs):
    method present (line 181) | def present(self):
    method __validate (line 184) | def __validate(self, *_args):
    method __save_var (line 189) | def __save_var(self, *_args):
    method remove_entry (line 206) | def remove_entry(self, _entry):
    method __set_description (line 210) | def __set_description(self):
    method __populate_vars_list (line 214) | def __populate_vars_list(self):
    method __validate_inherited (line 226) | def __validate_inherited(self, *_args):
    method __toggle_inherited_limit (line 229) | def __toggle_inherited_limit(self, *_args):
    method __populate_inherited_list (line 246) | def __populate_inherited_list(self):
    method __update_inherited_placeholder (line 260) | def __update_inherited_placeholder(self):
    method __update_inherited_state (line 275) | def __update_inherited_state(self):
    method __save_inherited_var (line 281) | def __save_inherited_var(self, *_args):
    method remove_inherited_entry (line 303) | def remove_inherited_entry(self, entry: InheritedEnvironmentVariableRow):

FILE: bottles/frontend/windows/exclusionpatterns.py
  class ExclusionPatternEntry (line 24) | class ExclusionPatternEntry(Adw.ActionRow):
    method __init__ (line 31) | def __init__(self, parent, pattern, **kwargs):
    method __remove (line 45) | def __remove(self, *_args):
  class ExclusionPatternsDialog (line 61) | class ExclusionPatternsDialog(Adw.Window):
    method __init__ (line 69) | def __init__(self, window, config, **kwargs):
    method __save_var (line 83) | def __save_var(self, *_args):
    method __populate_patterns_list (line 98) | def __populate_patterns_list(self):

FILE: bottles/frontend/windows/funding.py
  class FundingDialog (line 24) | class FundingDialog(Adw.Window):
    method __init__ (line 29) | def __init__(self, parent, **kwargs):
    method on_donate_clicked (line 77) | def on_donate_clicked(self, btn):
    method on_dont_show_clicked (line 82) | def on_dont_show_clicked(self, btn):
    method __on_close_request (line 86) | def __on_close_request(self, *args):

FILE: bottles/frontend/windows/gamescope.py
  class GamescopeDialog (line 22) | class GamescopeDialog(Adw.Window):
    method __init__ (line 43) | def __init__(self, window, config, **kwargs):
    method __change_wtype (line 59) | def __change_wtype(self, widget, wtype):
    method __update (line 72) | def __update(self, config):
    method __idle_save (line 93) | def __idle_save(self, *_args):
    method __save (line 119) | def __save(self, *_args):

FILE: bottles/frontend/windows/generic.py
  class MessageDialog (line 23) | class MessageDialog(Gtk.MessageDialog):
    method __init__ (line 24) | def __init__(self, window, message=_("An error has occurred."), log=Fa...
  class SourceDialog (line 48) | class SourceDialog(Adw.Window):
    method __init__ (line 49) | def __init__(self, parent, title, message, buttons=None, lang="yaml", ...
    method __build_ui (line 64) | def __build_ui(self):
    method __copy_text (line 107) | def __copy_text(self, widget):
  class TextDialog (line 112) | class TextDialog(Adw.Window):
    method __init__ (line 113) | def __init__(self, parent, title, message, **kwargs):
    method __build_ui (line 123) | def __build_ui(self):
    method __copy_text (line 146) | def __copy_text(self, widget):
  class WebDialog (line 151) | class WebDialog(Adw.Window):
    method __init__ (line 157) | def __init__(self, parent, title, message):
    method __build_ui (line 169) | def __build_ui(self):
    method __copy_text (line 190) | def __copy_text(self, widget):

FILE: bottles/frontend/windows/generic_cli.py
  class MessageDialog (line 1) | class MessageDialog:
    method __init__ (line 2) | def __init__(

FILE: bottles/frontend/windows/installer.py
  class LocalResourceEntry (line 28) | class LocalResourceEntry(Adw.ActionRow):
    method __init__ (line 36) | def __init__(self, parent, resource, **kwargs):
    method __choose_path (line 48) | def __choose_path(self, *_args):
  class InstallerDialog (line 74) | class InstallerDialog(Adw.Window):
    method __init__ (line 100) | def __init__(self, window, config, installer, **kwargs):
    method __set_icon (line 132) | def __set_icon(self):
    method __check_resources (line 151) | def __check_resources(self, *_args):
    method __install (line 166) | def __install(self, *_args):
    method __installed (line 192) | def __installed(self):
    method __error (line 198) | def __error(self, error):
    method next_step (line 203) | def next_step(self, detail=None):
    method set_steps (line 216) | def set_steps(self, steps):
    method add_resource (line 221) | def add_resource(self, resource, path):
    method __close (line 226) | def __close(self, *_args):

FILE: bottles/frontend/windows/journal.py
  class JournalDialog (line 27) | class JournalDialog(Adw.Window):
    method __init__ (line 42) | def __init__(self, **kwargs):
    method __setup_tree_view (line 64) | def __setup_tree_view(self):
    method populate_tree_view (line 102) | def populate_tree_view(self, query="", severity=None):
    method on_search_changed (line 151) | def on_search_changed(self, entry):
    method filter_results (line 154) | def filter_results(self, _, severity):
    method __get_cell_data_func (line 169) | def __get_cell_data_func(self, column_index):
    method __populate_cell (line 175) | def __populate_cell(self, renderer, model, iter_, column_index):

FILE: bottles/frontend/windows/launchoptions.py
  class LaunchOptionsDialog (line 29) | class LaunchOptionsDialog(Adw.Window):
    method __set_disabled_switches (line 70) | def __set_disabled_switches(self):
    method __init__ (line 86) | def __init__(self, parent, config, program, **kwargs):
    method __check_override (line 206) | def __check_override(self, widget, state, action, name):
    method get_config (line 210) | def get_config(self):
    method __set_override (line 213) | def __set_override(self, name, program_value, global_value):
    method __idle_save (line 220) | def __idle_save(self, *_args):
    method __save (line 254) | def __save(self, *_args):
    method __choose_pre_script (line 257) | def __choose_pre_script(self, *_args):
    method __choose_post_script (line 290) | def __choose_post_script(self, *_args):
    method __reset_pre_script (line 321) | def __reset_pre_script(self, *_args):
    method __reset_post_script (line 329) | def __reset_post_script(self, *_args):
    method __choose_cwd (line 337) | def __choose_cwd(self, *_args):
    method __reset_cwd (line 366) | def __reset_cwd(self, *_args):
    method __reset_defaults (line 376) | def __reset_defaults(self, *_args):

FILE: bottles/frontend/windows/mangohud.py
  class MangoHudDialog (line 26) | class MangoHudDialog(Adw.Window):
    method __init__ (line 33) | def __init__(self, window, config, **kwargs):
    method __update (line 47) | def __update(self, config):
    method __idle_save (line 51) | def __idle_save(self, *_args):
    method __save (line 66) | def __save(self, *_args):

FILE: bottles/frontend/windows/onboard.py
  class OnboardDialog (line 28) | class OnboardDialog(Adw.Dialog):
    method __init__ (line 57) | def __init__(self, window, **kwargs):
    method __theme_changed (line 81) | def __theme_changed(self, settings, key):
    method __get_page (line 86) | def __get_page(self, index):
    method __page_changed (line 89) | def __page_changed(self, widget=False, index=0, *_args):
    method __quit (line 112) | def __quit(widget=False):
    method __install_runner (line 115) | def __install_runner(self, widget):
    method __previous_page (line 148) | def __previous_page(self, widget=False):
    method __next_page (line 153) | def __next_page(self, widget=False):
    method __handle_progress (line 158) | def __handle_progress(self, **kwargs):
    method __update_progress (line 162) | def __update_progress(
    method __update_progress (line 190) | def __update_progress(
    method __close_dialog (line 217) | def __close_dialog(self, widget):

FILE: bottles/frontend/windows/playtimegraph.py
  class PlaytimeGraphDialog (line 35) | class PlaytimeGraphDialog(Adw.Window):
    method __init__ (line 56) | def __init__(
    method __on_view_toggled (line 88) | def __on_view_toggled(self, button: Gtk.ToggleButton, view: str) -> None:
    method __on_prev_week (line 108) | def __on_prev_week(self, _widget: Gtk.Button) -> None:
    method __on_next_week (line 113) | def __on_next_week(self, _widget: Gtk.Button) -> None:
    method __load_data (line 120) | def __load_data(self) -> None:
    method __get_weekly_data (line 288) | def __get_weekly_data(self) -> List[int]:
    method __get_hourly_data (line 303) | def __get_hourly_data(self) -> List[int]:
    method __get_monthly_data (line 319) | def __get_monthly_data(self) -> List[int]:
    method __render_chart (line 334) | def __render_chart(self, data: List[int]) -> None:
    method __format_time (line 359) | def __format_time(self, minutes: int, allow_less_than_minute: bool = F...

FILE: bottles/frontend/windows/protonalert.py
  class ProtonAlertDialog (line 22) | class ProtonAlertDialog(Adw.Window):
    method __init__ (line 33) | def __init__(self, window, callback, **kwargs):
    method __callback (line 44) | def __callback(self, _, status):
    method __toggle_btn_use (line 49) | def __toggle_btn_use(self, widget, *_args):

FILE: bottles/frontend/windows/registry_rules.py
  class RegistryRuleEntry (line 25) | class RegistryRuleEntry(Adw.ActionRow):
    method __init__ (line 34) | def __init__(self, parent, rule: RegistryRule, config, manager, **kwar...
    method _set_subtitle (line 53) | def _set_subtitle(self):
    method __apply (line 65) | def __apply(self, *_args):
    method __delete (line 71) | def __delete(self, *_args):
    method __edit (line 76) | def __edit(self, *_args):

FILE: bottles/frontend/windows/rename.py
  class RenameDialog (line 22) | class RenameDialog(Adw.Window):
    method __init__ (line 33) | def __init__(self, window, name, on_save, **kwargs):
    method __on_save (line 52) | def __on_save(self, *_args):
    method __close_window (line 57) | def __close_window(self, *_args):
    method on_change (line 60) | def on_change(self, *_args):

FILE: bottles/frontend/windows/sandbox.py
  class SandboxDialog (line 22) | class SandboxDialog(Adw.Window):
    method __init__ (line 31) | def __init__(self, window, config, **kwargs):
    method __set_flag (line 45) | def __set_flag(self, widget, state, flag):
    method __update (line 50) | def __update(self, config):

FILE: bottles/frontend/windows/upgradeversioning.py
  class UpgradeVersioningDialog (line 26) | class UpgradeVersioningDialog(Adw.Window):
    method __init__ (line 38) | def __init__(self, parent, **kwargs):
    method __upgrade (line 50) | def __upgrade(self, widget):
    method __proceed (line 68) | def __proceed(self, widget):
    method finish (line 73) | def finish(self, result, error=False):
    method pulse (line 78) | def pulse(self):

FILE: bottles/frontend/windows/versioning_branch.py
  class VersioningBranchDialog (line 22) | class VersioningBranchDialog(Adw.PreferencesDialog):
    method __init__ (line 27) | def __init__(self, parent, callback, **kwargs):
    method on_apply (line 34) | def on_apply(self, widget):

FILE: bottles/frontend/windows/versioning_commit.py
  class VersioningCommitDialog (line 22) | class VersioningCommitDialog(Adw.PreferencesDialog):
    method __init__ (line 27) | def __init__(self, parent, callback, **kwargs):
    method on_apply (line 34) | def on_apply(self, widget):

FILE: bottles/frontend/windows/versioning_manage_branches.py
  class VersioningManageBranchesDialog (line 24) | class VersioningManageBranchesDialog(Adw.PreferencesDialog):
    method __init__ (line 29) | def __init__(self, parent, versioning_view, **kwargs):
    method refresh_branches (line 37) | def refresh_branches(self):
    method on_delete_branch (line 68) | def on_delete_branch(self, button, branch_name):

FILE: bottles/frontend/windows/versioning_settings.py
  class VersioningSettingsDialog (line 27) | class VersioningSettingsDialog(Adw.Window):
    method __init__ (line 34) | def __init__(self, window, config: BottleConfig, **kwargs):
    method __toggle_feature (line 51) | def __toggle_feature(self, state: bool, key: str) -> None:
    method __toggle_feature_cb (line 57) | def __toggle_feature_cb(self, _widget: Gtk.Widget, state: bool, key: s...

FILE: bottles/frontend/windows/vkbasalt.py
  class VkBasaltSettings (line 39) | class VkBasaltSettings:
  class VkBasaltDialog (line 61) | class VkBasaltDialog(Adw.Window):
    method __init__ (line 85) | def __init__(self, window, config, **kwargs):
    method __idle_save (line 150) | def __idle_save(self, *args):
    method __save (line 181) | def __save(self, *args):
    method __check_state (line 185) | def __check_state(self, *args):
    method __default (line 189) | def __default(self, widget, state):
    method __change_edge_detection_type (line 195) | def __change_edge_detection_type(self, widget, edge_detection_type):
    method effects_widgets (line 210) | def effects_widgets(self, status=True):
    method check_effects_states (line 215) | def check_effects_states(self):
    method get_subeffects (line 222) | def get_subeffects(self, VkBasaltSettings):
    method set_effects (line 247) | def set_effects(self):

FILE: bottles/frontend/windows/vmtouch.py
  class VmtouchDialog (line 24) | class VmtouchDialog(Adw.Window):
    method __init__ (line 34) | def __init__(self, window, config, **kwargs):
    method __update (line 48) | def __update(self, config):
    method __idle_save (line 51) | def __idle_save(self, *_args):
    method __save (line 64) | def __save(self, *_args):

FILE: bottles/frontend/windows/window.py
  class BottlesWindow (line 58) | class BottlesWindow(Adw.ApplicationWindow):
    method __init__ (line 81) | def __init__(self, arg_bottle, **kwargs):
    method __schedule_donate_icon_swap (line 196) | def __schedule_donate_icon_swap(self):
    method __on_donate_icon_timeout (line 199) | def __on_donate_icon_timeout(self):
    method __resolve_donate_icon_name (line 206) | def __resolve_donate_icon_name(self) -> Optional[str]:
    method on_close_request (line 231) | def on_close_request(self, *args):
    method network_changed_handler (line 236) | def network_changed_handler(self, res: Result):
    method g_notification_handler (line 239) | def g_notification_handler(self, res: Result):
    method g_show_uri_handler (line 244) | def g_show_uri_handler(self, res: Result):
    method update_library (line 251) | def update_library(self):
    method title (line 254) | def title(self, title, subtitle: str = ""):
    method check_for_connection (line 258) | def check_for_connection(self, status):
    method __maybe_prompt_winebridge_update (line 267) | def __maybe_prompt_winebridge_update(self):
    method __on_start (line 303) | def __on_start(self):
    method send_notification (line 400) | def send_notification(self, title, text, image="", ignore_user=False):
    method go_back (line 416) | def go_back(self, *_args):
    method show_details_view (line 419) | def show_details_view(self, widget=False, config: Optional[BottleConfi...
    method show_loading_view (line 423) | def show_loading_view(self, widget=False):
    method show_onboard_view (line 427) | def show_onboard_view(self, widget=False):
    method show_add_view (line 431) | def show_add_view(self, widget=False):
    method show_list_view (line 435) | def show_list_view(self, widget=False):
    method show_importer_view (line 438) | def show_importer_view(self, widget=False):
    method show_prefs_view (line 441) | def show_prefs_view(self, widget=False, view=0):
    method show_download_preferences_view (line 445) | def show_download_preferences_view(self, widget=False):
    method show_runners_preferences_view (line 448) | def show_runners_preferences_view(self, widget=False):
    method check_crash_log (line 451) | def check_crash_log(self):
    method __maybe_show_funding_dialog (line 463) | def __maybe_show_funding_dialog(self):
    method __funding_response (line 477) | def __funding_response(self, dialog, response):
    method toggle_selection_mode (line 484) | def toggle_selection_mode(self, status: bool = True):
    method lock_ui (line 491) | def lock_ui(self, status: bool = True):
    method show_toast (line 501) | def show_toast(
    method __on_page_changed (line 526) | def __on_page_changed(self, stack, *args):
    method proper_close (line 531) | def proper_close():
    method open_url (line 536) | def open_url(widget, url):

FILE: bottles/frontend/windows/winebridgeupdate.py
  class WineBridgeUpdateDialog (line 14) | class WineBridgeUpdateDialog(Adw.Window):
    method __init__ (line 28) | def __init__(
    method __populate_details (line 52) | def __populate_details(self):
    method __start_update (line 74) | def __start_update(self, *_args):
    method __on_cancel (line 98) | def __on_cancel(self, *_args):
    method __close (line 107) | def __close(self, *_args):
    method __show_error (line 110) | def __show_error(self):
    method __update_progress (line 117) | def __update_progress(
    method __on_install_complete (line 140) | def __on_install_complete(self, result: Result, error=False):

FILE: bottles/fvs/exceptions.py
  class FVSException (line 1) | class FVSException(Exception):
  class FVSNothingToCommit (line 4) | class FVSNothingToCommit(FVSException):
  class FVSNothingToRestore (line 7) | class FVSNothingToRestore(FVSException):
  class FVSStateNotFound (line 10) | class FVSStateNotFound(FVSException):
  class FVSStateZeroNotDeletable (line 13) | class FVSStateZeroNotDeletable(FVSException):
  class FVSStateAlreadyExists (line 16) | class FVSStateAlreadyExists(FVSException):
  class FVSMissingStateIndex (line 19) | class FVSMissingStateIndex(FVSException):
  class FVSEmptyStateIndex (line 22) | class FVSEmptyStateIndex(FVSException):

FILE: bottles/fvs/repo.py
  class FVSRepo (line 31) | class FVSRepo:
    method __init__ (line 32) | def __init__(self, repo_path: str, use_compression: bool = False, no_i...
    method _get_fvs2_bin (line 51) | def _get_fvs2_bin(self):
    method _run_cmd (line 54) | def _run_cmd(self, *args, check=True):
    method _init_repo (line 58) | def _init_repo(self):
    method commit (line 71) | def commit(self, message: str, ignore: list = None):
    method restore_state (line 81) | def restore_state(self, state_id: str, ignore: list = None, reset: boo...
    method _refresh (line 103) | def _refresh(self):
    method check_dirty (line 150) | def check_dirty(self):
    method has_no_states (line 168) | def has_no_states(self) -> bool:
    method states (line 172) | def states(self) -> dict:
    method active_state_id (line 176) | def active_state_id(self) -> str:
    method active_branch (line 180) | def active_branch(self) -> str:
    method dirty (line 184) | def dirty(self) -> bool:
    method changed_files (line 188) | def changed_files(self) -> int:
    method branches (line 192) | def branches(self) -> list:
    method create_branch (line 195) | def create_branch(self, branch_name: str):
    method delete_branch (line 202) | def delete_branch(self, branch_name: str):
    method checkout (line 209) | def checkout(self, target: str):

FILE: bottles/tests/backend/integration/playtime/conftest.py
  class _FVSRepoStub (line 18) | class _FVSRepoStub:
    method __init__ (line 19) | def __init__(self, *args, **kwargs):
    method commit (line 24) | def commit(self, *_args, **_kwargs):
    method restore_state (line 27) | def restore_state(self, *_args, **_kwargs):
  class _FVSError (line 31) | class _FVSError(Exception):
  function temp_xdg_home (line 51) | def temp_xdg_home(monkeypatch):
  function test_settings_stub (line 58) | def test_settings_stub():
  function manager (line 70) | def manager(temp_xdg_home, test_settings_stub):
  function open_db (line 83) | def open_db(m: Manager) -> sqlite3.Connection:

FILE: bottles/tests/backend/integration/playtime/test_aggregation.py
  function test_same_program_aggregates_into_one_row (line 8) | def test_same_program_aggregates_into_one_row(manager):
  function test_different_programs_separate_rows (line 49) | def test_different_programs_separate_rows(manager):

FILE: bottles/tests/backend/integration/playtime/test_disabled_tracking.py
  function test_disabled_tracking_smoke (line 9) | def test_disabled_tracking_smoke(temp_xdg_home):

FILE: bottles/tests/backend/integration/playtime/test_failure_run.py
  function test_failure_run_marks_unknown_and_updates_totals (line 8) | def test_failure_run_marks_unknown_and_updates_totals(manager):
  function test_multiple_failures_different_bottles (line 37) | def test_multiple_failures_different_bottles(manager):
  function test_multiple_failures_same_bottle_different_programs (line 73) | def test_multiple_failures_same_bottle_different_programs(manager):

FILE: bottles/tests/backend/integration/playtime/test_playtime_signals.py
  class _Settings (line 15) | class _Settings:
    method get_boolean (line 16) | def get_boolean(self, key: str) -> bool:
    method get_int (line 19) | def get_int(self, key: str) -> int:
  function _new_manager (line 23) | def _new_manager(tmpdir: str) -> Manager:
  function test_signals_flow_success (line 39) | def test_signals_flow_success():
  function test_signals_flow_unknown_failure (line 71) | def test_signals_flow_unknown_failure():

FILE: bottles/tests/backend/integration/playtime/test_recovery.py
  function test_recovery_smoke (line 10) | def test_recovery_smoke(manager):
  function test_recovery_different_bottles (line 46) | def test_recovery_different_bottles(manager):
  function test_recovery_same_bottle_different_programs (line 87) | def test_recovery_same_bottle_different_programs(manager):

FILE: bottles/tests/backend/integration/playtime/test_schema_meta.py
  function test_schema_meta_smoke (line 7) | def test_schema_meta_smoke(manager):

FILE: bottles/tests/backend/integration/playtime/test_successful_run.py
  function test_successful_run_finalizes_and_updates_totals (line 8) | def test_successful_run_finalizes_and_updates_totals(manager):
  function test_success_multiple_apps_two_same_bottle_one_other (line 39) | def test_success_multiple_apps_two_same_bottle_one_other(manager):

FILE: bottles/tests/backend/integration/playtime/test_uniqueness_retry.py
  function test_uniqueness_retry_smoke (line 8) | def test_uniqueness_retry_smoke(manager):

FILE: bottles/tests/backend/integration/playtime/test_wine_executor_playtime.py
  class _Settings (line 13) | class _Settings:
    method get_boolean (line 14) | def get_boolean(self, key: str) -> bool:
    method get_int (line 17) | def get_int(self, key: str) -> int:
  function _new_manager (line 21) | def _new_manager(tmpdir: str) -> Manager:
  function _config (line 35) | def _config(name: str) -> BottleConfig:
  function test_wine_executor_emits_and_updates_totals (line 42) | def test_wine_executor_emits_and_updates_totals(mocker):

FILE: bottles/tests/backend/manager/test_manager.py
  function test_manager_is_singleton (line 7) | def test_manager_is_singleton():
  function test_manager_default_gsettings_stub (line 16) | def test_manager_default_gsettings_stub():

FILE: bottles/tests/backend/manager/test_playtime.py
  function _new_tracker (line 9) | def _new_tracker(tmpdir, enabled=True, heartbeat_interval=5):
  function test_schema_created_and_wal (line 16) | def test_schema_created_and_wal():
  function test_start_and_heartbeat_and_exit_updates_totals (line 31) | def test_start_and_heartbeat_and_exit_updates_totals():
  function test_recovery_finalizes_running_sessions (line 65) | def test_recovery_finalizes_running_sessions():
  function test_disabled_tracker_is_noop (line 97) | def test_disabled_tracker_is_noop():
  function test_mark_failure (line 111) | def test_mark_failure():
  function test_multiple_sessions_aggregate (line 131) | def test_multiple_sessions_aggregate():
  function test_different_programs_separate_totals (line 168) | def test_different_programs_separate_totals():
  function test_program_id_consistency (line 196) | def test_program_id_consistency():
  function test_indices_exist (line 226) | def test_indices_exist():
  function test_user_version_set (line 239) | def test_user_version_set():
  function test_unique_constraint (line 249) | def test_unique_constraint():
  function test_disable_tracking_method (line 281) | def test_disable_tracking_method():
  function test_start_session_collapses_duplicate_running_session (line 305) | def test_start_session_collapses_duplicate_running_session():
  function test_get_totals_returns_program_stats (line 335) | def test_get_totals_returns_program_stats():
  function test_get_totals_returns_none_when_not_found (line 364) | def test_get_totals_returns_none_when_not_found():
  function test_get_totals_returns_none_when_disabled (line 373) | def test_get_totals_returns_none_when_disabled():
  function test_get_all_program_totals_returns_all_programs (line 382) | def test_get_all_program_totals_returns_all_programs():
  function test_get_all_program_totals_returns_empty_when_disabled (line 416) | def test_get_all_program_totals_returns_empty_when_disabled():
  function test_normalize_path_to_windows (line 425) | def test_normalize_path_to_windows():
  function test_database_stores_normalized_paths (line 446) | def test_database_stores_normalized_paths():

FILE: bottles/tests/backend/state/test_events.py
  class Events (line 11) | class Events(Enum):
  function approx_time (line 20) | def approx_time(start, target):
  function test_simple_event (line 31) | def test_simple_event():
  function test_wait_after_done_event (line 47) | def test_wait_after_done_event():
  function test_set_reset (line 56) | def test_set_reset():
  function test_event_singleton_wait (line 98) | def test_event_singleton_wait():
  function test_event_singleton_done_reset (line 124) | def test_event_singleton_done_reset():
  function test_correct_internal_flag (line 138) | def test_correct_internal_flag():

FILE: bottles/tests/backend/utils/test_generic.py
  function test_detect_encoding (line 24) | def test_detect_encoding(text: str, hint: Optional[str], codec: Optional...

FILE: bottles/tests/backend/wine/test_executor.py
  function _make_config (line 10) | def _make_config(name: str = "TestBottle", path: str = "TestBottlePath")...
  function test_build_placeholder_map_uses_program_values (line 14) | def test_build_placeholder_map_uses_program_values():
  function test_replace_placeholders_handles_unknown_tokens (line 31) | def test_replace_placeholders_handles_unknown_tokens():
  function test_run_program_substitutes_placeholders (line 42) | def test_run_program_substitutes_placeholders(monkeypatch):
  function test_wine_env_respects_allowed_keys (line 111) | def test_wine_env_respects_allowed_keys(monkeypatch):
  function test_winecommand_filters_host_environment (line 122) | def test_winecommand_filters_host_environment(monkeypatch, tmp_path):

FILE: bottles/tests/conftest.py
  function _add_repo_root_to_syspath (line 2) | def _add_repo_root_to_syspath() -> None:

FILE: bottles/tests/frontend/test_playtime_service.py
  class MockManager (line 10) | class MockManager:
    method __init__ (line 13) | def __init__(self, tracker):
  function test_format_playtime (line 17) | def test_format_playtime():
  function test_format_last_played (line 31) | def test_format_last_played():
  function test_format_subtitle_with_data (line 58) | def test_format_subtitle_with_data():
  function test_format_subtitle_never_played (line 80) | def test_format_subtitle_never_played():
  function test_service_disabled_returns_none (line 88) | def test_service_disabled_returns_none():

FILE: test_path_normalization.py
  function test_normalize_path (line 5) | def test_normalize_path():

FILE: tests/conftest.py
  function _add_repo_root_to_syspath (line 11) | def _add_repo_root_to_syspath() -> None:

FILE: tests/test_fvs.py
  function main (line 10) | def main():
Condensed preview — 404 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (8,669K chars).
[
  {
    "path": ".gitattributes",
    "chars": 65,
    "preview": "# Ref: https://git-scm.com/docs/gitattributes\n* text=auto eol=lf\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 132,
    "preview": "# These are supported funding model platforms\nliberapay: Bottles\ngithub: ['bottlesdevs']\ncustom: ['https://usebottles.co"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug.yml",
    "chars": 5816,
    "preview": "name: Bug Report\ndescription: File a bug report\ntitle: \"[Bug]: \"\nlabels: [\"bug\", \"triage\"]\nbody:\n  - type: markdown\n    "
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 197,
    "preview": "blank_issues_enabled: true\ncontact_links:\n  - name: Documentation\n    url: https://docs.usebottles.com/\n    about: Befor"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature-request.yml",
    "chars": 1138,
    "preview": "name: Feature Request\ndescription: Suggest an idea for this project\ntitle: \"[Request]: \"\nlabels: [\"Feature request\"]\n\nbo"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feedback.md",
    "chars": 239,
    "preview": "---\nname: General Feedback\nabout: Send your feedback, start a discussion, or ask a question to the developers.\nlabels: F"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/mirror.yml",
    "chars": 1730,
    "preview": "name: Network issue report\ndescription: Report Network/Mirror issue to sysadmin\nlabels: [\"Network Issue\"]\nbody:\n  - type"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 428,
    "preview": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where "
  },
  {
    "path": ".github/pull_request_template.md",
    "chars": 617,
    "preview": "# Description\nPlease include a summary of the change and which issue is fixed (if available).\nPlease also include releva"
  },
  {
    "path": ".github/workflows/build_flatpak.yml",
    "chars": 519,
    "preview": "on:\n  push:\n    branches: [main]\n  pull_request:\nname: Build Flatpak\njobs:\n  flatpak:\n    name: \"build-packages\"\n    run"
  },
  {
    "path": ".github/workflows/close-issues.yml",
    "chars": 700,
    "preview": "name: close-issues\n\non:\n  issues:\n    types: [opened]\n\njobs:\n  comment:\n    runs-on: ubuntu-latest\n    steps:\n      - us"
  },
  {
    "path": ".github/workflows/pre-commit.yml",
    "chars": 231,
    "preview": "name: pre-commit\n\non:\n  pull_request:\n  push:\n    branches: [main]\n\njobs:\n  pre-commit:\n    runs-on: ubuntu-latest\n    s"
  },
  {
    "path": ".github/workflows/update-manifest.yml",
    "chars": 3161,
    "preview": "name: Update manifest\n\non:\n  schedule:\n    # Check for update every day at 07:11\n    - cron:  '11 7 * * *'\n  # Allows yo"
  },
  {
    "path": ".gitignore",
    "chars": 784,
    "preview": ".vscode/\n.mypy_cache/\n.pytest_cache/\n/.project\n/.pydevproject\n/.settings\n/.cproject\n/.idea\n.flatpak-builder/\n/build\n/bui"
  },
  {
    "path": ".gitmodules",
    "chars": 119,
    "preview": "[submodule \"build-aux/req2flatpak\"]\n\tpath = build-aux/req2flatpak\n\turl = https://github.com/johannesjh/req2flatpak.git\n"
  },
  {
    "path": ".mailmap",
    "chars": 688,
    "preview": "Mirko Brombin <brombin94@gmail.com> <send@mirko.pm>\nMirko Brombin <brombin94@gmail.com> <brombin@mirko.pm>\nMirko Brombin"
  },
  {
    "path": ".pre-commit-config.yaml",
    "chars": 1130,
    "preview": "# See https://pre-commit.com for more information\n# See https://pre-commit.com/hooks.html for more hooks\nrepos:\n-   repo"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "chars": 11049,
    "preview": "# GNOME Code of Conduct\n\nThank you for being a part of the GNOME project. We value your participation and want everyone "
  },
  {
    "path": "CODING_GUIDE.md",
    "chars": 1629,
    "preview": "## Build & Run locally\n\n### use flatpak\n\n#### Build & install\n\n```bash\nflatpak-builder --install --user --force-clean ./"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 1601,
    "preview": "# Contributing to Bottles\nFirst off, thanks for taking the time to contribute :heart:!\n\n## Found a Problem?\nBefore repor"
  },
  {
    "path": "COPYING.md",
    "chars": 34916,
    "preview": "### GNU GENERAL PUBLIC LICENSE\n\nVersion 3, 29 June 2007\n\nCopyright (C) 2007 Free Software Foundation, Inc.\n<https://fsf."
  },
  {
    "path": "README.md",
    "chars": 4760,
    "preview": "<div align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/bottlesdevs/Bottles/main/data/icons/hicolor/scalable/"
  },
  {
    "path": "VERSION",
    "chars": 4,
    "preview": "62.0"
  },
  {
    "path": "VERSION_UPDATE.md",
    "chars": 86,
    "preview": "### Paths to be updated\n- VERSION\n- data/com.usebottles.metainfo.xml.in\n- meson.build\n"
  },
  {
    "path": "bottles/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "bottles/backend/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "bottles/backend/cabextract.py",
    "chars": 3887,
    "preview": "# cabextract.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistr"
  },
  {
    "path": "bottles/backend/diff.py",
    "chars": 2044,
    "preview": "import os\nimport hashlib\n\n\nclass Diff:\n    \"\"\"\n    This class is no more used by the application, it's just a\n    refere"
  },
  {
    "path": "bottles/backend/dlls/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "bottles/backend/dlls/dll.py",
    "chars": 6452,
    "preview": "# dll.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistribute i"
  },
  {
    "path": "bottles/backend/dlls/dxvk.py",
    "chars": 1202,
    "preview": "# dxvk.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistribute "
  },
  {
    "path": "bottles/backend/dlls/latencyflex.py",
    "chars": 1183,
    "preview": "# dxvk.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistribute "
  },
  {
    "path": "bottles/backend/dlls/meson.build",
    "chars": 314,
    "preview": "pkgdatadir = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name())\ndllsdir = join_paths(pkgdatad"
  },
  {
    "path": "bottles/backend/dlls/nvapi.py",
    "chars": 3361,
    "preview": "# nvapi.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistribute"
  },
  {
    "path": "bottles/backend/dlls/vkd3d.py",
    "chars": 1118,
    "preview": "# vkd3d.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistribute"
  },
  {
    "path": "bottles/backend/downloader.py",
    "chars": 5461,
    "preview": "# component.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistri"
  },
  {
    "path": "bottles/backend/globals.py",
    "chars": 2969,
    "preview": "# globals.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistribu"
  },
  {
    "path": "bottles/backend/health.py",
    "chars": 4553,
    "preview": "# health.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistribut"
  },
  {
    "path": "bottles/backend/logger.py",
    "chars": 3680,
    "preview": "# logger.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistribut"
  },
  {
    "path": "bottles/backend/managers/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "bottles/backend/managers/backup.py",
    "chars": 13098,
    "preview": "# backup.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistribut"
  },
  {
    "path": "bottles/backend/managers/component.py",
    "chars": 19440,
    "preview": "# component.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistri"
  },
  {
    "path": "bottles/backend/managers/conf.py",
    "chars": 4902,
    "preview": "import os\nfrom configparser import ConfigParser\nfrom typing import Optional\n\nfrom bottles.backend.utils import yaml, jso"
  },
  {
    "path": "bottles/backend/managers/data.py",
    "chars": 3384,
    "preview": "# data.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistribute "
  },
  {
    "path": "bottles/backend/managers/dependency.py",
    "chars": 29209,
    "preview": "# dependency.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistr"
  },
  {
    "path": "bottles/backend/managers/eagle.py",
    "chars": 43757,
    "preview": "# eagle.py\n#\n# Copyright 2026 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistribute"
  },
  {
    "path": "bottles/backend/managers/eagle.yar",
    "chars": 21167,
    "preview": "// Eagle YARA Rules Database\n// Copyright 2026 mirkobrombin <brombin94@gmail.com>\n// SPDX-License-Identifier: GPL-3.0-on"
  },
  {
    "path": "bottles/backend/managers/epicgamesstore.py",
    "chars": 2914,
    "preview": "# epicgamesstore.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can red"
  },
  {
    "path": "bottles/backend/managers/importer.py",
    "chars": 4716,
    "preview": "# importer.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistrib"
  },
  {
    "path": "bottles/backend/managers/installer.py",
    "chars": 17292,
    "preview": "# installer_manager.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can "
  },
  {
    "path": "bottles/backend/managers/journal.py",
    "chars": 6604,
    "preview": "# journal.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistribu"
  },
  {
    "path": "bottles/backend/managers/library.py",
    "chars": 4068,
    "preview": "# library.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistribu"
  },
  {
    "path": "bottles/backend/managers/manager.py",
    "chars": 82009,
    "preview": "# manager.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistribu"
  },
  {
    "path": "bottles/backend/managers/meson.build",
    "chars": 732,
    "preview": "pkgdatadir = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name())\nmanagersdir = join_paths(pkgd"
  },
  {
    "path": "bottles/backend/managers/origin.py",
    "chars": 1622,
    "preview": "# origin.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistribut"
  },
  {
    "path": "bottles/backend/managers/playtime.py",
    "chars": 37153,
    "preview": "# playtime.py\n#\n# Core playtime tracking manager: session lifecycle, heartbeats, recovery, and totals.\n\nfrom __future__ "
  },
  {
    "path": "bottles/backend/managers/queue.py",
    "chars": 1040,
    "preview": "# queue.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistribute"
  },
  {
    "path": "bottles/backend/managers/registry_rule.py",
    "chars": 3145,
    "preview": "import os\nimport uuid\nfrom typing import TYPE_CHECKING, Iterable, List, Optional\n\nfrom bottles.backend.logger import Log"
  },
  {
    "path": "bottles/backend/managers/repository.py",
    "chars": 5519,
    "preview": "# repository.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistr"
  },
  {
    "path": "bottles/backend/managers/runtime.py",
    "chars": 4616,
    "preview": "# runtime.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistribu"
  },
  {
    "path": "bottles/backend/managers/sandbox.py",
    "chars": 4692,
    "preview": "# steam.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistribute"
  },
  {
    "path": "bottles/backend/managers/steam.py",
    "chars": 20737,
    "preview": "# steam.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistribute"
  },
  {
    "path": "bottles/backend/managers/steamgriddb.py",
    "chars": 1787,
    "preview": "# steamgriddb.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redist"
  },
  {
    "path": "bottles/backend/managers/template.py",
    "chars": 6977,
    "preview": "# template.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistrib"
  },
  {
    "path": "bottles/backend/managers/thumbnail.py",
    "chars": 1668,
    "preview": "# thumbnail.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistri"
  },
  {
    "path": "bottles/backend/managers/ubisoftconnect.py",
    "chars": 4815,
    "preview": "# ubisoftconnect.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can red"
  },
  {
    "path": "bottles/backend/managers/versioning.py",
    "chars": 14828,
    "preview": "# versioning.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistr"
  },
  {
    "path": "bottles/backend/meson.build",
    "chars": 589,
    "preview": "pkgdatadir = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name())\nbackenddir = join_paths(pkgda"
  },
  {
    "path": "bottles/backend/models/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "bottles/backend/models/config.py",
    "chars": 9783,
    "preview": "import inspect\nimport logging\nimport os\nfrom dataclasses import asdict, dataclass, field, is_dataclass, replace\nfrom io "
  },
  {
    "path": "bottles/backend/models/enum.py",
    "chars": 52,
    "preview": "class Arch:\n    WIN32 = \"win32\"\n    WIN64 = \"win64\"\n"
  },
  {
    "path": "bottles/backend/models/meson.build",
    "chars": 357,
    "preview": "pkgdatadir = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name())\nmodelsdir = join_paths(pkgdat"
  },
  {
    "path": "bottles/backend/models/process.py",
    "chars": 394,
    "preview": "from dataclasses import dataclass\nfrom typing import Literal\n\n\n@dataclass(frozen=True)\nclass ProcessStartedPayload:\n    "
  },
  {
    "path": "bottles/backend/models/registry_rule.py",
    "chars": 998,
    "preview": "from dataclasses import dataclass, field\nfrom typing import List\n\n\n@dataclass\nclass RegistryRule:\n    \"\"\"Reusable regist"
  },
  {
    "path": "bottles/backend/models/result.py",
    "chars": 1425,
    "preview": "# result.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistribut"
  },
  {
    "path": "bottles/backend/models/samples.py",
    "chars": 2119,
    "preview": "class Samples:\n    data = {\n        \"funding_dialog_dismissed\": False,\n        \"personal_repositories\": {},\n    }\n    de"
  },
  {
    "path": "bottles/backend/models/vdict.py",
    "chars": 8024,
    "preview": "# Copyright (c) 2015 Rossen Georgiev <rossen@rgp.io>\n#\n# Permission is hereby granted, free of charge, to any person obt"
  },
  {
    "path": "bottles/backend/params.py",
    "chars": 72,
    "preview": "APP_VERSION = \"@APP_VERSION@\"\nBASE_ID = \"@BASE_ID@\"\nAPP_ID = \"@APP_ID@\"\n"
  },
  {
    "path": "bottles/backend/repos/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "bottles/backend/repos/component.py",
    "chars": 1246,
    "preview": "# component.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistri"
  },
  {
    "path": "bottles/backend/repos/dependency.py",
    "chars": 1042,
    "preview": "# dependency.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistr"
  },
  {
    "path": "bottles/backend/repos/installer.py",
    "chars": 1497,
    "preview": "# installer.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistri"
  },
  {
    "path": "bottles/backend/repos/meson.build",
    "chars": 312,
    "preview": "pkgdatadir = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name())\nreposdir = join_paths(pkgdata"
  },
  {
    "path": "bottles/backend/repos/repo.py",
    "chars": 2538,
    "preview": "# repo.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistribute "
  },
  {
    "path": "bottles/backend/runner.py",
    "chars": 3654,
    "preview": "# runner.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistribut"
  },
  {
    "path": "bottles/backend/state.py",
    "chars": 7295,
    "preview": "import dataclasses\nfrom enum import Enum\nfrom gettext import gettext as _\nfrom threading import Lock as PyLock, Event as"
  },
  {
    "path": "bottles/backend/utils/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "bottles/backend/utils/connection.py",
    "chars": 3813,
    "preview": "# connection.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistr"
  },
  {
    "path": "bottles/backend/utils/decorators.py",
    "chars": 1855,
    "preview": "# decorators.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistr"
  },
  {
    "path": "bottles/backend/utils/display.py",
    "chars": 1494,
    "preview": "import os\nimport subprocess\nfrom functools import lru_cache\n\n\nclass DisplayUtils:\n    @staticmethod\n    @lru_cache\n    d"
  },
  {
    "path": "bottles/backend/utils/file.py",
    "chars": 4676,
    "preview": "# file.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistribute "
  },
  {
    "path": "bottles/backend/utils/generic.py",
    "chars": 3490,
    "preview": "# generic.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistribu"
  },
  {
    "path": "bottles/backend/utils/gpu.py",
    "chars": 4959,
    "preview": "# gpu.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistribute i"
  },
  {
    "path": "bottles/backend/utils/gsettings_stub.py",
    "chars": 498,
    "preview": "from bottles.backend.logger import Logger\n\nlogging = Logger()\n\n\nclass GSettingsStub:\n    @staticmethod\n    def get_boole"
  },
  {
    "path": "bottles/backend/utils/imagemagick.py",
    "chars": 2564,
    "preview": "# imagemagick.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redist"
  },
  {
    "path": "bottles/backend/utils/json.py",
    "chars": 2068,
    "preview": "\"\"\"This should be a drop-in replacement for the json module built in CPython\"\"\"\n\nimport json\nimport json as _json\nfrom t"
  },
  {
    "path": "bottles/backend/utils/lnk.py",
    "chars": 2598,
    "preview": "# lnk.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistribute i"
  },
  {
    "path": "bottles/backend/utils/manager.py",
    "chars": 12834,
    "preview": "# manager.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistribu"
  },
  {
    "path": "bottles/backend/utils/meson.build",
    "chars": 584,
    "preview": "pkgdatadir = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name())\nutilsdir = join_paths(pkgdata"
  },
  {
    "path": "bottles/backend/utils/nvidia.py",
    "chars": 3444,
    "preview": "\"\"\"This file originated from Lutris (https://github.com/lutris/lutris/blob/master/lutris/util/nvidia.py)\"\"\"\n\n\"\"\"Nvidia l"
  },
  {
    "path": "bottles/backend/utils/proc.py",
    "chars": 2326,
    "preview": "# proc.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistribute "
  },
  {
    "path": "bottles/backend/utils/singleton.py",
    "chars": 239,
    "preview": "class Singleton(type):\n    _instances = {}\n\n    def __call__(cls, *args, **kwargs):\n        if cls not in cls._instances"
  },
  {
    "path": "bottles/backend/utils/snake.py",
    "chars": 3912,
    "preview": "import curses\nimport random\nimport time\nimport sys\nimport os\n\n\nclass Snake:\n    def __init__(self, stdscr: curses.window"
  },
  {
    "path": "bottles/backend/utils/steam.py",
    "chars": 4371,
    "preview": "# steam.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistribute"
  },
  {
    "path": "bottles/backend/utils/terminal.py",
    "chars": 5505,
    "preview": "# terminal.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistrib"
  },
  {
    "path": "bottles/backend/utils/threading.py",
    "chars": 3985,
    "preview": "# threading.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistri"
  },
  {
    "path": "bottles/backend/utils/vdf.py",
    "chars": 18735,
    "preview": "# Copyright (c) 2015 Rossen Georgiev <rossen@rgp.io>\n#\n# Permission is hereby granted, free of charge, to any person obt"
  },
  {
    "path": "bottles/backend/utils/vulkan.py",
    "chars": 2945,
    "preview": "# vulkan.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistribut"
  },
  {
    "path": "bottles/backend/utils/wine.py",
    "chars": 455,
    "preview": "import os\n\n\nclass WineUtils:\n    @staticmethod\n    def get_user_dir(prefix_path: str):\n        ignored = [\"Public\"]\n    "
  },
  {
    "path": "bottles/backend/utils/yaml.py",
    "chars": 1091,
    "preview": "import yaml as _yaml\n\ntry:\n    from yaml import CSafeLoader as SafeLoader, CSafeDumper as SafeDumper\nexcept ImportError:"
  },
  {
    "path": "bottles/backend/wine/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "bottles/backend/wine/catalogs.py",
    "chars": 4339,
    "preview": "win_versions = {\n    \"win11\": {\n        \"CSDVersion\": \"\",\n        \"CSDVersionHex\": \"0\",\n        \"CurrentBuild\": \"22000\","
  },
  {
    "path": "bottles/backend/wine/cmd.py",
    "chars": 670,
    "preview": "from typing import Optional\n\nfrom bottles.backend.logger import Logger\nfrom bottles.backend.wine.wineprogram import Wine"
  },
  {
    "path": "bottles/backend/wine/control.py",
    "chars": 573,
    "preview": "from bottles.backend.logger import Logger\nfrom bottles.backend.wine.wineprogram import WineProgram\n\nlogging = Logger()\n\n"
  },
  {
    "path": "bottles/backend/wine/drives.py",
    "chars": 2040,
    "preview": "import os\n\nfrom bottles.backend.logger import Logger\nfrom bottles.backend.models.config import BottleConfig\nfrom bottles"
  },
  {
    "path": "bottles/backend/wine/eject.py",
    "chars": 526,
    "preview": "from bottles.backend.logger import Logger\nfrom bottles.backend.wine.wineprogram import WineProgram\n\nlogging = Logger()\n\n"
  },
  {
    "path": "bottles/backend/wine/executor.py",
    "chars": 17524,
    "preview": "import os\nimport re\nimport shlex\nimport time\nimport uuid\nfrom typing import Optional, Pattern\n\nfrom bottles.backend.dlls"
  },
  {
    "path": "bottles/backend/wine/expand.py",
    "chars": 518,
    "preview": "from bottles.backend.logger import Logger\nfrom bottles.backend.wine.wineprogram import WineProgram\n\nlogging = Logger()\n\n"
  },
  {
    "path": "bottles/backend/wine/explorer.py",
    "chars": 916,
    "preview": "from typing import Optional\n\nfrom bottles.backend.logger import Logger\nfrom bottles.backend.wine.wineprogram import Wine"
  },
  {
    "path": "bottles/backend/wine/hh.py",
    "chars": 201,
    "preview": "from bottles.backend.logger import Logger\nfrom bottles.backend.wine.wineprogram import WineProgram\n\nlogging = Logger()\n\n"
  },
  {
    "path": "bottles/backend/wine/icinfo.py",
    "chars": 908,
    "preview": "from bottles.backend.logger import Logger\nfrom bottles.backend.wine.wineprogram import WineProgram\n\nlogging = Logger()\n\n"
  },
  {
    "path": "bottles/backend/wine/meson.build",
    "chars": 802,
    "preview": "pkgdatadir = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name())\nwinedir = join_paths(pkgdatad"
  },
  {
    "path": "bottles/backend/wine/msiexec.py",
    "chars": 3667,
    "preview": "from typing import Optional\n\nfrom bottles.backend.logger import Logger\nfrom bottles.backend.wine.wineprogram import Wine"
  },
  {
    "path": "bottles/backend/wine/net.py",
    "chars": 1212,
    "preview": "from typing import Optional\n\nfrom bottles.backend.logger import Logger\nfrom bottles.backend.wine.wineprogram import Wine"
  },
  {
    "path": "bottles/backend/wine/notepad.py",
    "chars": 760,
    "preview": "from typing import Optional\n\nfrom bottles.backend.logger import Logger\nfrom bottles.backend.wine.wineprogram import Wine"
  },
  {
    "path": "bottles/backend/wine/oleview.py",
    "chars": 211,
    "preview": "from bottles.backend.logger import Logger\nfrom bottles.backend.wine.wineprogram import WineProgram\n\nlogging = Logger()\n\n"
  },
  {
    "path": "bottles/backend/wine/progman.py",
    "chars": 210,
    "preview": "from bottles.backend.logger import Logger\nfrom bottles.backend.wine.wineprogram import WineProgram\n\nlogging = Logger()\n\n"
  },
  {
    "path": "bottles/backend/wine/reg.py",
    "chars": 4891,
    "preview": "import codecs\nimport dataclasses\nimport os\nimport uuid\nfrom datetime import datetime\nfrom itertools import groupby\nfrom "
  },
  {
    "path": "bottles/backend/wine/regedit.py",
    "chars": 210,
    "preview": "from bottles.backend.logger import Logger\nfrom bottles.backend.wine.wineprogram import WineProgram\n\nlogging = Logger()\n\n"
  },
  {
    "path": "bottles/backend/wine/register.py",
    "chars": 5545,
    "preview": "# register.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistrib"
  },
  {
    "path": "bottles/backend/wine/regkeys.py",
    "chars": 14145,
    "preview": "from typing import Optional\n\nfrom bottles.backend.logger import Logger\nfrom bottles.backend.models.config import BottleC"
  },
  {
    "path": "bottles/backend/wine/regsvr32.py",
    "chars": 709,
    "preview": "from bottles.backend.logger import Logger\nfrom bottles.backend.wine.wineprogram import WineProgram\n\nlogging = Logger()\n\n"
  },
  {
    "path": "bottles/backend/wine/rundll32.py",
    "chars": 221,
    "preview": "from bottles.backend.logger import Logger\nfrom bottles.backend.wine.wineprogram import WineProgram\n\nlogging = Logger()\n\n"
  },
  {
    "path": "bottles/backend/wine/start.py",
    "chars": 1525,
    "preview": "from typing import Optional\n\nfrom bottles.backend.logger import Logger\nfrom bottles.backend.wine.wineprogram import Wine"
  },
  {
    "path": "bottles/backend/wine/taskmgr.py",
    "chars": 207,
    "preview": "from bottles.backend.logger import Logger\nfrom bottles.backend.wine.wineprogram import WineProgram\n\nlogging = Logger()\n\n"
  },
  {
    "path": "bottles/backend/wine/uninstaller.py",
    "chars": 1138,
    "preview": "from typing import Optional\n\nfrom bottles.backend.logger import Logger\nfrom bottles.backend.wine.wineprogram import Wine"
  },
  {
    "path": "bottles/backend/wine/wineboot.py",
    "chars": 2396,
    "preview": "from bottles.backend.logger import Logger\nfrom bottles.backend.wine.wineprogram import WineProgram\nfrom bottles.backend."
  },
  {
    "path": "bottles/backend/wine/winebridge.py",
    "chars": 1961,
    "preview": "import os\n\nfrom bottles.backend.logger import Logger\nfrom bottles.backend.wine.winepath import WinePath\nfrom bottles.bac"
  },
  {
    "path": "bottles/backend/wine/winecfg.py",
    "chars": 948,
    "preview": "import os\n\nfrom bottles.backend.logger import Logger\nfrom bottles.backend.wine.wineprogram import WineProgram\nfrom bottl"
  },
  {
    "path": "bottles/backend/wine/winecommand.py",
    "chars": 30404,
    "preview": "import os\nimport re\nimport shlex\nimport shutil\nimport stat\nimport subprocess\nimport tempfile\nfrom typing import Iterable"
  },
  {
    "path": "bottles/backend/wine/winedbg.py",
    "chars": 3707,
    "preview": "import re\nimport time\nimport subprocess\nfrom typing import Optional\n\nfrom bottles.backend.logger import Logger\nfrom bott"
  },
  {
    "path": "bottles/backend/wine/winefile.py",
    "chars": 359,
    "preview": "from bottles.backend.logger import Logger\nfrom bottles.backend.wine.wineprogram import WineProgram\n\nlogging = Logger()\n\n"
  },
  {
    "path": "bottles/backend/wine/winepath.py",
    "chars": 2568,
    "preview": "import re\nfrom functools import lru_cache\n\nfrom bottles.backend.logger import Logger\nfrom bottles.backend.utils.manager "
  },
  {
    "path": "bottles/backend/wine/wineprogram.py",
    "chars": 2740,
    "preview": "import os\nfrom typing import Optional\n\nfrom bottles.backend.logger import Logger\nfrom bottles.backend.globals import Pat"
  },
  {
    "path": "bottles/backend/wine/wineserver.py",
    "chars": 2893,
    "preview": "import os\nimport subprocess\nimport time\n\nfrom bottles.backend.logger import Logger\nfrom bottles.backend.utils.manager im"
  },
  {
    "path": "bottles/backend/wine/winhelp.py",
    "chars": 216,
    "preview": "from bottles.backend.logger import Logger\nfrom bottles.backend.wine.wineprogram import WineProgram\n\nlogging = Logger()\n\n"
  },
  {
    "path": "bottles/backend/wine/xcopy.py",
    "chars": 1933,
    "preview": "from typing import Optional\nfrom datetime import datetime\n\nfrom bottles.backend.logger import Logger\nfrom bottles.backen"
  },
  {
    "path": "bottles/frontend/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "bottles/frontend/bottles.py",
    "chars": 1603,
    "preview": "#!@PYTHON@\n\n# bottles.in\n#\n# Copyright 2020 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you ca"
  },
  {
    "path": "bottles/frontend/cli/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "bottles/frontend/cli/cli.py",
    "chars": 30119,
    "preview": "#!@PYTHON@\n\n# cli.in\n#\n# Copyright 2020 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can re"
  },
  {
    "path": "bottles/frontend/cli/meson.build",
    "chars": 409,
    "preview": "pkgdatadir = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name())\nclidir = join_paths(pkgdatadi"
  },
  {
    "path": "bottles/frontend/main.py",
    "chars": 14204,
    "preview": "# main.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistribute "
  },
  {
    "path": "bottles/frontend/meson.build",
    "chars": 763,
    "preview": "pkgdatadir = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name())\nfrontenddir = join_paths(pkgd"
  },
  {
    "path": "bottles/frontend/operation.py",
    "chars": 3013,
    "preview": "# operation.py\n#\n# Copyright 2025 mirkobrombin <brombin94@gmail.com>\n#\n# This program is free software: you can redistri"
  },
  {
    "path": "bottles/frontend/params.py",
    "chars": 469,
    "preview": "# Application details\nAPP_NAME = \"@APP_NAME@\"\nAPP_NAME_LOWER = APP_NAME.lower()\nBASE_ID = \"@BASE_ID@\"\nAPP_ID = \"@APP_ID@"
  },
  {
    "path": "bottles/frontend/ui/bottle-row.blp",
    "chars": 406,
    "preview": "using Gtk 4.0;\nusing Adw 1;\n\ntemplate $BottlesBottleRow: Adw.ActionRow {\n  activatable: true;\n  use-markup: false;\n\n  Ad"
  },
  {
    "path": "bottles/frontend/ui/bottles.gresource.xml",
    "chars": 4345,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<gresources>\n  <gresource prefix=\"/com/usebottles/bottles\">\n    <file>style.css</"
  },
  {
    "path": "bottles/frontend/ui/check-row.blp",
    "chars": 270,
    "preview": "using Gtk 4.0;\nusing Adw 1;\n\ntemplate $BottlesCheckRow: Adw.ActionRow {\n  activatable-widget: check_button;\n  active: bi"
  },
  {
    "path": "bottles/frontend/ui/component-entry.blp",
    "chars": 1291,
    "preview": "using Gtk 4.0;\nusing Adw 1;\n\ntemplate $ComponentEntry: Adw.ActionRow {\n  title: _(\"Component version\");\n\n  Spinner spinn"
  },
  {
    "path": "bottles/frontend/ui/dependency-entry.blp",
    "chars": 1752,
    "preview": "using Gtk 4.0;\nusing Adw 1;\n\nPopover pop_actions {\n  styles [\n    \"menu\",\n  ]\n\n  Box {\n    margin-top: 6;\n    margin-bot"
  },
  {
    "path": "bottles/frontend/ui/details-bottle.blp",
    "chars": 11339,
    "preview": "using Gtk 4.0;\nusing Adw 1;\n\nPopover pop_context {\n  styles [\n    \"menu\",\n  ]\n\n  Box {\n    margin-top: 6;\n    margin-bot"
  },
  {
    "path": "bottles/frontend/ui/details-dependencies.blp",
    "chars": 2211,
    "preview": "using Gtk 4.0;\nusing Adw 1;\n\ntemplate $DetailsDependencies: Adw.Bin {\n  Box {\n    orientation: vertical;\n\n    SearchBar "
  },
  {
    "path": "bottles/frontend/ui/details-installers.blp",
    "chars": 1580,
    "preview": "using Gtk 4.0;\nusing Adw 1;\n\ntemplate $DetailsInstallers: Adw.Bin {\n  Box {\n    orientation: vertical;\n\n    SearchBar se"
  },
  {
    "path": "bottles/frontend/ui/details-preferences.blp",
    "chars": 9591,
    "preview": "using Gtk 4.0;\nusing Adw 1;\n\ntemplate $DetailsPreferences: Adw.PreferencesPage {\n  Adw.PreferencesGroup group_details {\n"
  },
  {
    "path": "bottles/frontend/ui/details-registry-rules.blp",
    "chars": 1338,
    "preview": "using Gtk 4.0;\nusing Adw 1;\n\ntemplate $DetailsRegistryRules: Adw.Bin {\n  Box {\n    orientation: vertical;\n\n    SearchBar"
  },
  {
    "path": "bottles/frontend/ui/details-taskmanager.blp",
    "chars": 418,
    "preview": "using Gtk 4.0;\n\ntemplate $TaskManagerView: ScrolledWindow {\n  TreeView treeview_processes {\n    enable-grid-lines: horiz"
  },
  {
    "path": "bottles/frontend/ui/details-versioning.blp",
    "chars": 2270,
    "preview": "using Gtk 4.0;\nusing Adw 1;\n\ntemplate $DetailsVersioning: Adw.PreferencesPage {\n  Adw.PreferencesPage pref_page {\n    Ad"
  },
  {
    "path": "bottles/frontend/ui/details.blp",
    "chars": 1801,
    "preview": "using Gtk 4.0;\nusing Adw 1;\n\ntemplate $Details: Adw.Bin {\n  Adw.Leaflet leaflet {\n    can-navigate-back: true;\n    can-u"
  },
  {
    "path": "bottles/frontend/ui/dialog-bottle-picker.blp",
    "chars": 729,
    "preview": "using Gtk 4.0;\nusing Adw 1;\n\ntemplate $BottlePickerDialog: Adw.ApplicationWindow {\n  title: _(\"Select Bottle\");\n  defaul"
  },
  {
    "path": "bottles/frontend/ui/dialog-crash-report.blp",
    "chars": 2275,
    "preview": "using Gtk 4.0;\nusing Adw 1;\n\ntemplate $CrashReportDialog: Adw.Window {\n  resizable: false;\n  deletable: true;\n  modal: t"
  },
  {
    "path": "bottles/frontend/ui/dialog-dependency-install.blp",
    "chars": 1382,
    "preview": "using Gtk 4.0;\nusing Adw 1;\n\ntemplate $DependencyInstallDialog: Adw.Window {\n  modal: true;\n  deletable: false;\n  defaul"
  },
  {
    "path": "bottles/frontend/ui/dialog-deps-check.blp",
    "chars": 642,
    "preview": "using Gtk 4.0;\nusing Adw 1;\n\ntemplate $DependenciesCheckDialog: Adw.Window {\n  modal: true;\n  deletable: true;\n  resizab"
  },
  {
    "path": "bottles/frontend/ui/dialog-display.blp",
    "chars": 3386,
    "preview": "using Gtk 4.0;\nusing Adw 1;\n\ntemplate $DisplayDialog: Adw.Window {\n  default-width: 500;\n  default-height: 450;\n  modal:"
  },
  {
    "path": "bottles/frontend/ui/dialog-dll-overrides.blp",
    "chars": 1234,
    "preview": "using Gtk 4.0;\nusing Adw 1;\n\ntemplate $DLLOverridesDialog: Adw.PreferencesWindow {\n  modal: true;\n  default-width: 500;\n"
  },
  {
    "path": "bottles/frontend/ui/dialog-drives.blp",
    "chars": 957,
    "preview": "using Gtk 4.0;\nusing Adw 1;\n\ntemplate $DrivesDialog: Adw.Window {\n  modal: true;\n  default-width: 500;\n  default-height:"
  },
  {
    "path": "bottles/frontend/ui/dialog-duplicate.blp",
    "chars": 1958,
    "preview": "using Gtk 4.0;\nusing Adw 1;\n\ntemplate $DuplicateDialog: Adw.Window {\n  modal: true;\n  default-width: 400;\n  default-heig"
  },
  {
    "path": "bottles/frontend/ui/dialog-env-vars.blp",
    "chars": 1499,
    "preview": "using Gtk 4.0;\nusing Adw 1;\n\ntemplate $EnvironmentVariablesDialog: Adw.Dialog {\n  content-width: 600;\n  content-height: "
  },
  {
    "path": "bottles/frontend/ui/dialog-exclusion-patterns.blp",
    "chars": 787,
    "preview": "using Gtk 4.0;\nusing Adw 1;\n\ntemplate $ExclusionPatternsDialog: Adw.Window {\n  modal: true;\n  default-width: 500;\n  defa"
  },
  {
    "path": "bottles/frontend/ui/dialog-gamescope.blp",
    "chars": 4236,
    "preview": "using Gtk 4.0;\nusing Adw 1;\n\ntemplate $GamescopeDialog: Adw.Window {\n  modal: true;\n  default-width: 550;\n  title: _(\"Ga"
  },
  {
    "path": "bottles/frontend/ui/dialog-installer.blp",
    "chars": 3119,
    "preview": "using Gtk 4.0;\nusing Adw 1;\n\ntemplate $InstallerDialog: Adw.Window {\n  modal: true;\n  deletable: true;\n  default-width: "
  },
  {
    "path": "bottles/frontend/ui/dialog-journal.blp",
    "chars": 1338,
    "preview": "using Gtk 4.0;\nusing Adw 1;\n\nPopover pop_menu {\n  Box {\n    orientation: vertical;\n    spacing: 3;\n\n    $GtkModelButton "
  },
  {
    "path": "bottles/frontend/ui/dialog-launch-options.blp",
    "chars": 5459,
    "preview": "using Gtk 4.0;\nusing Adw 1;\n\ntemplate $LaunchOptionsDialog: Adw.Window {\n  modal: true;\n  default-width: 500;\n  title: _"
  },
  {
    "path": "bottles/frontend/ui/dialog-mangohud.blp",
    "chars": 1060,
    "preview": "using Gtk 4.0;\nusing Adw 1;\n\ntemplate $MangoHudDialog : Adw.Window {\n default-width: 500;\n  modal: true;\n\n  title:_(\"Man"
  },
  {
    "path": "bottles/frontend/ui/dialog-playtime-graph.blp",
    "chars": 6711,
    "preview": "using Gtk 4.0;\nusing Adw 1;\n\ntemplate $PlaytimeGraphDialog: Adw.Window {\n  modal: true;\n  default-height: 600;\n  destroy"
  },
  {
    "path": "bottles/frontend/ui/dialog-proton-alert.blp",
    "chars": 1315,
    "preview": "using Gtk 4.0;\nusing Adw 1;\n\ntemplate $ProtonAlertDialog: Adw.Window {\n  title: _(\"Proton Disclaimer\");\n  default-width:"
  },
  {
    "path": "bottles/frontend/ui/dialog-registry-rules.blp",
    "chars": 2174,
    "preview": "using Gtk 4.0;\nusing Adw 1;\n\ntemplate $RegistryRulesDialog: Adw.Dialog {\n  title: _(\"Registry Rule\");\n  content-width: 5"
  },
  {
    "path": "bottles/frontend/ui/dialog-rename.blp",
    "chars": 637,
    "preview": "using Gtk 4.0;\nusing Adw 1;\n\ntemplate $RenameDialog: Adw.Window {\n  modal: true;\n  deletable: false;\n  default-width: 55"
  },
  {
    "path": "bottles/frontend/ui/dialog-run-args.blp",
    "chars": 850,
    "preview": "using Gtk 4.0;\nusing Adw 1;\n\ntemplate $RunArgsDialog: Window {\n  modal: true;\n  default-width: 550;\n  destroy-with-paren"
  },
  {
    "path": "bottles/frontend/ui/dialog-sandbox.blp",
    "chars": 774,
    "preview": "using Gtk 4.0;\nusing Adw 1;\n\ntemplate $SandboxDialog: Adw.Window {\n  modal: true;\n  deletable: true;\n  default-width: 55"
  },
  {
    "path": "bottles/frontend/ui/dialog-upgrade-versioning.blp",
    "chars": 3082,
    "preview": "using Gtk 4.0;\nusing Adw 1;\n\ntemplate $UpgradeVersioningDialog: Adw.Window {\n  modal: true;\n  default-width: 500;\n  defa"
  },
  {
    "path": "bottles/frontend/ui/dialog-versioning-branch.blp",
    "chars": 413,
    "preview": "using Gtk 4.0;\nusing Adw 1;\n\ntemplate $VersioningBranchDialog: Adw.PreferencesDialog {\n  title: _(\"Create Branch\");\n\n  A"
  },
  {
    "path": "bottles/frontend/ui/dialog-versioning-commit.blp",
    "chars": 424,
    "preview": "using Gtk 4.0;\nusing Adw 1;\n\ntemplate $VersioningCommitDialog: Adw.PreferencesDialog {\n  title: _(\"Create Snapshot\");\n\n "
  },
  {
    "path": "bottles/frontend/ui/dialog-versioning-manage-branches.blp",
    "chars": 354,
    "preview": "using Gtk 4.0;\nusing Adw 1;\n\ntemplate $VersioningManageBranchesDialog: Adw.PreferencesDialog {\n  title: _(\"Manage Branch"
  },
  {
    "path": "bottles/frontend/ui/dialog-versioning-settings.blp",
    "chars": 733,
    "preview": "using Gtk 4.0;\nusing Adw 1;\n\ntemplate $VersioningSettingsDialog: Adw.Window {\n  modal: true;\n  deletable: true;\n  defaul"
  },
  {
    "path": "bottles/frontend/ui/dialog-vkbasalt.blp",
    "chars": 13419,
    "preview": "using Gtk 4.0;\nusing Adw 1;\n\ntemplate $VkBasaltDialog: Adw.Window {\n  default-width: 500;\n  default-height: 500;\n  modal"
  },
  {
    "path": "bottles/frontend/ui/dialog-vmtouch.blp",
    "chars": 1031,
    "preview": "using Gtk 4.0;\nusing Adw 1;\n\ntemplate $VmtouchDialog: Adw.Window {\n  modal: true;\n  default-width: 550;\n  title: _(\"Vmto"
  },
  {
    "path": "bottles/frontend/ui/dialog-winebridge-update.blp",
    "chars": 2576,
    "preview": "using Gtk 4.0;\nusing Adw 1;\n\ntemplate $WineBridgeUpdateDialog: Adw.Window {\n  modal: true;\n  default-width: 520;\n  defau"
  },
  {
    "path": "bottles/frontend/ui/dll-override-entry.blp",
    "chars": 427,
    "preview": "using Gtk 4.0;\nusing Adw 1;\n\ntemplate $DLLEntry: Adw.ComboRow {\n  title: \"DLL Name\";\n\n  model: StringList {\n    strings "
  },
  {
    "path": "bottles/frontend/ui/drive-entry.blp",
    "chars": 491,
    "preview": "using Gtk 4.0;\nusing Adw 1;\n\ntemplate $DriveEntry: Adw.ActionRow {\n  title: \"C:\";\n  subtitle: _(\"/point/to/path\");\n\n  Bo"
  }
]

// ... and 204 more files (download for full content)

About this extraction

This page contains the full source code of the bottlesdevs/Bottles GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 404 files (7.7 MB), approximately 2.0M tokens, and a symbol index with 1649 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!