The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── .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
    │   ├── close-stale-issues.yml
    │   ├── pre-commit.yml
    │   └── update-manifest.yml
├── .gitignore
├── .gitmodules
├── .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
    │   │   ├── epicgamesstore.py
    │   │   ├── importer.py
    │   │   ├── installer.py
    │   │   ├── journal.py
    │   │   ├── library.py
    │   │   ├── manager.py
    │   │   ├── meson.build
    │   │   ├── origin.py
    │   │   ├── queue.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
    │   │   ├── 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
    │   │   ├── midi.py
    │   │   ├── 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
    │   ├── bottle-details-page.blp
    │   ├── bottle-details-view.blp
    │   ├── bottle-picker-dialog.blp
    │   ├── bottle-row.blp
    │   ├── bottle_details_page.py
    │   ├── bottle_details_view.py
    │   ├── bottle_picker_dialog.py
    │   ├── bottles-list-view.blp
    │   ├── bottles.gresource.xml
    │   ├── bottles.py
    │   ├── bottles_list_view.py
    │   ├── check-row.blp
    │   ├── cli.py
    │   ├── common.py
    │   ├── component-entry-row.blp
    │   ├── component_entry_row.py
    │   ├── crash-report-dialog.blp
    │   ├── crash_report_dialog.py
    │   ├── dependencies-check-dialog.blp
    │   ├── dependencies_check_dialog.py
    │   ├── dependency-entry-row.blp
    │   ├── dependency_entry_row.py
    │   ├── details-dependencies-view.blp
    │   ├── details-installers-view.blp
    │   ├── details-preferences-page.blp
    │   ├── details-task-manager-view.blp
    │   ├── details-versioning-page.blp
    │   ├── details_dependencies_view.py
    │   ├── details_installers_view.py
    │   ├── details_preferences_page.py
    │   ├── details_task_manager_view.py
    │   ├── details_versioning_page.py
    │   ├── display-dialog.blp
    │   ├── display_dialog.py
    │   ├── dll-override-entry.blp
    │   ├── dll-overrides-dialog.blp
    │   ├── dll_overrides_dialog.py
    │   ├── drive-entry.blp
    │   ├── drives-dialog.blp
    │   ├── drives_dialog.py
    │   ├── duplicate-dialog.blp
    │   ├── duplicate_dialog.py
    │   ├── env-var-entry.blp
    │   ├── environment-variables-dialog.blp
    │   ├── environment_variables_dialog.py
    │   ├── exclusion-pattern-row.blp
    │   ├── exclusion-patterns-dialog.blp
    │   ├── exclusion_patterns_dialog.py
    │   ├── executable.py
    │   ├── filters.py
    │   ├── fsr-dialog.blp
    │   ├── fsr_dialog.py
    │   ├── gamescope-dialog.blp
    │   ├── gamescope_dialog.py
    │   ├── generic.py
    │   ├── generic_cli.py
    │   ├── gtk.py
    │   ├── help-overlay.blp
    │   ├── importer-row.blp
    │   ├── importer-view.blp
    │   ├── importer_row.py
    │   ├── importer_view.py
    │   ├── installer-dialog.blp
    │   ├── installer-row.blp
    │   ├── installer_dialog.py
    │   ├── installer_row.py
    │   ├── journal-dialog.blp
    │   ├── journal_dialog.py
    │   ├── launch-options-dialog.blp
    │   ├── launch_options_dialog.py
    │   ├── library-entry.blp
    │   ├── library-view.blp
    │   ├── library_entry.py
    │   ├── library_view.py
    │   ├── loading-view.blp
    │   ├── loading_view.py
    │   ├── local-resource-row.blp
    │   ├── main.py
    │   ├── mangohud-dialog.blp
    │   ├── mangohud_dialog.py
    │   ├── meson.build
    │   ├── new-bottle-dialog.blp
    │   ├── new_bottle_dialog.py
    │   ├── onboard-dialog.blp
    │   ├── onboard_dialog.py
    │   ├── operation.py
    │   ├── params.py
    │   ├── preferences.blp
    │   ├── preferences.py
    │   ├── program-row.blp
    │   ├── program_row.py
    │   ├── proton-alert-dialog.blp
    │   ├── proton_alert_dialog.py
    │   ├── rename-program-dialog.blp
    │   ├── rename_program_dialog.py
    │   ├── sandbox-dialog.blp
    │   ├── sandbox_dialog.py
    │   ├── sh.py
    │   ├── state-row.blp
    │   ├── state_row.py
    │   ├── style-dark.css
    │   ├── style.css
    │   ├── task-row.blp
    │   ├── upgrade-versioning-dialog.blp
    │   ├── upgrade_versioning_dialog.py
    │   ├── vkbasalt-dialog.blp
    │   ├── vkbasalt_dialog.py
    │   ├── vmtouch-dialog.blp
    │   ├── vmtouch_dialog.py
    │   ├── window.blp
    │   └── window.py
    ├── meson.build
    └── tests
    │   ├── __init__.py
    │   └── backend
    │       ├── __init__.py
    │       ├── manager
    │           ├── __init__.py
    │           └── test_manager.py
    │       ├── state
    │           ├── __init__.py
    │           └── test_events.py
    │       └── utils
    │           ├── __init__.py
    │           └── test_generic.py
├── build-aux
    ├── bottles-deps.yaml
    ├── com.usebottles.bottles.Devel.json
    ├── 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
    │   ├── hicolor
    │   │   ├── scalable
    │   │   │   └── apps
    │   │   │   │   ├── com.usebottles.bottles-program.svg
    │   │   │   │   ├── com.usebottles.bottles.Devel.svg
    │   │   │   │   ├── com.usebottles.bottles.svg
    │   │   │   │   └── com.usebottles.bottles_old.svg
    │   │   └── symbolic
    │   │   │   ├── actions
    │   │   │       ├── application-x-addon-symbolic.svg
    │   │   │       ├── applications-system-symbolic.svg
    │   │   │       ├── computer-symbolic.svg
    │   │   │       ├── document-save-symbolic.svg
    │   │   │       ├── external-link-symbolic.svg
    │   │   │       ├── go-next-symbolic.svg
    │   │   │       ├── go-previous-symbolic.svg
    │   │   │       ├── info-symbolic.svg
    │   │   │       ├── library-symbolic.svg
    │   │   │       ├── list-add-symbolic.svg
    │   │   │       ├── media-playback-start-symbolic.svg
    │   │   │       ├── media-playback-stop-symbolic.svg
    │   │   │       ├── open-menu-symbolic.svg
    │   │   │       ├── paper-symbolic.svg
    │   │   │       ├── preferences-desktop-apps-symbolic.svg
    │   │   │       ├── preferences-system-time-symbolic.svg
    │   │   │       ├── selection-mode-symbolic.svg
    │   │   │       ├── system-run-symbolic.svg
    │   │   │       ├── system-search-symbolic.svg
    │   │   │       ├── system-shutdown-symbolic.svg
    │   │   │       ├── system-software-install-symbolic.svg
    │   │   │       ├── view-more-symbolic.svg
    │   │   │       └── warning-symbolic.svg
    │   │   │   └── apps
    │   │   │       ├── bottle-symbolic.svg
    │   │   │       ├── bottles-steam-symbolic.svg
    │   │   │       ├── com.usebottles.bottles-symbolic.svg
    │   │   │       └── com.usebottles.bottles.Devel-symbolic.svg
    │   └── meson.build
    ├── images
    │   ├── bottles-welcome-night.svg
    │   ├── bottles-welcome.png
    │   └── bottles-welcome.svg
    ├── meson.build
    └── screenshots
    │   ├── 1.png
    │   ├── 2.png
    │   ├── 3.png
    │   ├── 4.png
    │   ├── 5.png
    │   └── 6.png
├── docs
    ├── screenshot-dark.png
    └── screenshot-light.png
├── 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
    ├── ga.po
    ├── gl.po
    ├── he.po
    ├── hi.po
    ├── hr.po
    ├── hu.po
    ├── id.po
    ├── ie.po
    ├── it.po
    ├── ja.po
    ├── ka.po
    ├── ko.po
    ├── lt.po
    ├── meson.build
    ├── ms.po
    ├── nb_NO.po
    ├── nl.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
    ├── zh_CN.po
    ├── zh_HK.po
    ├── zh_Hans.po
    ├── zh_Hant.po
    ├── zh_SG.po
    └── zh_TW.po
├── pyproject.toml
├── requirements.dev.txt
└── requirements.txt


/.gitattributes:
--------------------------------------------------------------------------------
1 | # Ref: https://git-scm.com/docs/gitattributes
2 | * text=auto eol=lf
3 | 


--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 | liberapay: Bottles
3 | github: ['bottlesdevs']
4 | custom: ['https://usebottles.com/funding']
5 | 


--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: true
2 | contact_links:
3 |   - name: Documentation
4 |     url: https://docs.usebottles.com/
5 |     about: Before posting, check if the topic has already been covered by our documentation.
6 | 


--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature-request.yml:
--------------------------------------------------------------------------------
 1 | name: Feature Request
 2 | description: Suggest an idea for this project
 3 | title: "[Request]: "
 4 | labels: ["Feature request"]
 5 | 
 6 | body:
 7 |   - type: textarea
 8 |     id: what-happened
 9 |     attributes:
10 |       label: Tell us the problem or your need
11 |       description: A clear and concise description of what the problem is.
12 |       placeholder: Ex. I'm always frustrated when [...]
13 |     validations:
14 |       required: true
15 | 
16 |   - type: textarea
17 |     id: your-solution
18 |     attributes:
19 |       label: Describe the solution you'd like
20 |       description: A clear and concise description of what you want to happen.
21 |       placeholder: To fix this, I would [...]
22 |     validations:
23 |       required: true
24 | 
25 |   - type: textarea
26 |     id: other-solutions
27 |     attributes:
28 |       label: Other solutions?
29 |       description: A clear and concise description of any alternative solutions or features you've considered.
30 |     validations:
31 |       required: false
32 | 
33 |   - type: textarea
34 |     id: additional-context
35 |     attributes:
36 |       label: Additional context and references
37 |       description: Add any other context or reference about the feature request here.
38 |     validations:
39 |       required: false
40 | 


--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feedback.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: General Feedback
3 | about: Send your feedback, start a discussion, or ask a question to the developers.
4 | labels: Feedback
5 | ---
6 | 
7 | !!! PLEASE DON'T OPEN ISSUES FOR PROGRAMS NOT RUNNING IN BOTTLES, USE THE programs REPOSITORY INSTEAD !!!
8 | 


--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/mirror.yml:
--------------------------------------------------------------------------------
 1 | name: Network issue report
 2 | description: Report Network/Mirror issue to sysadmin
 3 | labels: ["Network Issue"]
 4 | body:
 5 |   - type: markdown
 6 |     id: introduction
 7 |     attributes:
 8 |       value: |
 9 |         📝 Please use this template while reporting a Network/Mirror issue and provide as much info as possible.
10 | 
11 |   - type: checkboxes
12 |     id: prerequisites
13 |     attributes:
14 |       label: Prerequisites
15 |       options:
16 |         - label: |
17 |             I am sure that this problem has NEVER been discussed in [other issues](https://github.com/bottlesdevs/Bottles/issues).
18 |           required: true
19 | 
20 |   - type: textarea
21 |     id: what_happened
22 |     attributes:
23 |       label: What happened
24 |     validations:
25 |       required: true
26 | 
27 |   - type: textarea
28 |     id: expected_behavior
29 |     attributes:
30 |       label: What you expected to happen
31 |     validations:
32 |       required: true
33 | 
34 |   - type: textarea
35 |     id: how_to_reproduce
36 |     attributes:
37 |       label: How to reproduce it
38 |     validations:
39 |       required: true
40 | 
41 |   - type: textarea
42 |     id: ping_result
43 |     attributes:
44 |       label: Ping proxy.usebottles.com
45 |       description: Please run `ping proxy.usebottles.com` in a terminal and send us the output.
46 |       render: log
47 |     validations:
48 |       required: true
49 | 
50 |   - type: textarea
51 |     id: dig_result
52 |     attributes:
53 |       label: Dig proxy.usebottles.com
54 |       description: Please run `dig proxy.usebottles.com` in a terminal and send us the output.
55 |       render: log
56 |     validations:
57 |       required: true
58 | 
59 |   - type: input
60 |     id: isp
61 |     attributes:
62 |       label: Your Internet Service Provider (ISP) name
63 | 
64 |   - type: input
65 |     id: area
66 |     attributes:
67 |       label: Your Country/Area
68 | 
69 |   - type: textarea
70 |     id: others
71 |     attributes:
72 |       label: Anything else we need to know
73 | 


--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
 1 | # To get started with Dependabot version updates, you'll need to specify which
 2 | # package ecosystems to update and where the package manifests are located.
 3 | # Please see the documentation for all configuration options:
 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
 5 | 
 6 | version: 2
 7 | updates:
 8 | - package-ecosystem: github-actions
 9 |   directory: "/"
10 |   schedule:
11 |     interval: daily
12 | 


--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
 1 | # Description
 2 | Please include a summary of the change and which issue is fixed (if available).
 3 | Please also include relevant motivation and context.
 4 | 
 5 | Fixes #(issue)
 6 | 
 7 | ## Type of change
 8 | - [ ] Bug fix (non-breaking change which fixes an issue)
 9 | - [ ] New feature (non-breaking change which adds functionality)
10 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
11 | - [ ] This change requires a documentation update
12 | 
13 | # How Has This Been Tested?
14 | Please describe the tests that you ran to verify your changes.
15 | Provide instructions so we can reproduce.
16 | - [ ] Test A
17 | - [ ] Test B
18 | 


--------------------------------------------------------------------------------
/.github/workflows/build_flatpak.yml:
--------------------------------------------------------------------------------
 1 | on:
 2 |   push:
 3 |     branches: [main]
 4 |   pull_request:
 5 | name: Build Flatpak
 6 | jobs:
 7 |   flatpak:
 8 |     name: "build-packages"
 9 |     runs-on: ubuntu-latest
10 |     container:
11 |       image: ghcr.io/flathub-infra/flatpak-github-actions:gnome-48
12 |       options: --privileged
13 |     steps:
14 |     - uses: actions/checkout@v4
15 |     - uses: flathub-infra/flatpak-github-actions/flatpak-builder@master
16 |       with:
17 |         bundle: bottles.flatpak
18 |         manifest-path: build-aux/com.usebottles.bottles.Devel.json
19 |         cache-key: flatpak-builder-${{ github.sha }}
20 | 


--------------------------------------------------------------------------------
/.github/workflows/close-issues.yml:
--------------------------------------------------------------------------------
 1 | name: close-issues
 2 | 
 3 | on:
 4 |   issues:
 5 |     types: [opened]
 6 | 
 7 | jobs:
 8 |   comment:
 9 |     runs-on: ubuntu-latest
10 |     steps:
11 |       - uses: actions-ecosystem/action-regex-match@v2
12 |         id: regex-match
13 |         with:
14 |           text: ${{ github.event.issue.body }}
15 |           regex: '[Vv]ersion.*:.*202\d.\d\d?.\d\d?'
16 |       - if: ${{ steps.regex-match.outputs.match != '' }}
17 |         name: Close Issue
18 |         uses: peter-evans/close-issue@v3
19 |         with:
20 |           close-reason: not_planned
21 |           comment: |
22 |             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.
23 | 


--------------------------------------------------------------------------------
/.github/workflows/pre-commit.yml:
--------------------------------------------------------------------------------
 1 | name: pre-commit
 2 | 
 3 | on:
 4 |   pull_request:
 5 |   push:
 6 |     branches: [main]
 7 | 
 8 | jobs:
 9 |   pre-commit:
10 |     runs-on: ubuntu-latest
11 |     steps:
12 |     - uses: actions/checkout@v3
13 |     - uses: actions/setup-python@v3
14 |     - uses: pre-commit/action@v3.0.1
15 | 


--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
 1 | .vscode/
 2 | .mypy_cache/
 3 | .pytest_cache/
 4 | /.project
 5 | /.pydevproject
 6 | /.settings
 7 | /.cproject
 8 | /.idea
 9 | .flatpak-builder/
10 | /build
11 | /build-dir
12 | /mesonbuild
13 | 
14 | __pycache__
15 | .coverage
16 | /install dir
17 | /work area
18 | 
19 | /meson-test-run.txt
20 | /meson-test-run.xml
21 | /meson-cross-test-run.txt
22 | /meson-cross-test-run.xml
23 | 
24 | /.flatpak
25 | /builddir
26 | 
27 | .DS_Store
28 | *~
29 | *.swp
30 | packagecache
31 | /MANIFEST
32 | /dist
33 | /meson.egg-info
34 | 
35 | /docs/built_docs
36 | /docs/hotdoc-private*
37 | 
38 | *.pyc
39 | /*venv*
40 | 
41 | .buildconfig
42 | 
43 | # Ignore AppImage build dirs
44 | /AppDir
45 | /appimage-builder-cache
46 | 
47 | # Ignore generated files
48 | *.deb
49 | *.dsc
50 | *.changes
51 | .build
52 | *.buildinfo
53 | *.tar.gz
54 | 
55 | 
56 | # Ignore files generated during build
57 | *debian/files
58 | *debian/.*
59 | *debian/com.usebottles.bottles*
60 | *obj-x86_64-linux-gnu
61 | 
62 | # Ignore flatpak build dirs
63 | /repo/
64 | /flatpak/
65 | .vscode/*
66 | /.vscode/*
67 | 
68 | /build-flatpak.sh
69 | 


--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "build-aux/req2flatpak"]
2 | 	path = build-aux/req2flatpak
3 | 	url = https://github.com/johannesjh/req2flatpak.git
4 | 


--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
 1 | # See https://pre-commit.com for more information
 2 | # See https://pre-commit.com/hooks.html for more hooks
 3 | repos:
 4 | -   repo: https://github.com/pre-commit/pre-commit-hooks
 5 |     rev: v5.0.0
 6 |     hooks:
 7 |     -   id: trailing-whitespace
 8 |     -   id: end-of-file-fixer
 9 |     -   id: check-yaml
10 |     -   id: check-xml
11 |     -   id: check-json
12 |     -   id: pretty-format-json
13 |         args: ["--autofix", "--no-sort-keys", "--indent", "4"]
14 |     -   id: check-added-large-files
15 | 
16 | -   repo: https://github.com/asottile/pyupgrade
17 |     rev: v3.19.1
18 |     hooks:
19 |     -   id: pyupgrade
20 |         args: ["--py312-plus"]
21 | 
22 | -   repo: https://github.com/astral-sh/ruff-pre-commit
23 |     rev: v0.8.2
24 |     hooks:
25 |     -   id: ruff
26 |         args: [ "--fix" ]
27 |     -   id: ruff-format
28 | 
29 | -   repo: https://github.com/PyCQA/autoflake
30 |     rev: v2.3.1
31 |     hooks:
32 |     -   id: autoflake
33 | 
34 | -   repo: https://github.com/pre-commit/mirrors-mypy
35 |     rev: v1.13.0
36 |     hooks:
37 |     -   id: mypy
38 |         args: ["--pretty"]
39 |         additional_dependencies: ["pygobject-stubs", "types-PyYAML", "types-Markdown", "types-requests", "types-pycurl", "types-chardet", "pytest-stub", "types-orjson", "pathvalidate", "requirements-parser", "icoextract", "fvs", "patool", "pyfluidsynth", "git+https://gitlab.com/TheEvilSkeleton/vkbasalt-cli.git@main"]
40 | 


--------------------------------------------------------------------------------
/CODING_GUIDE.md:
--------------------------------------------------------------------------------
 1 | ## Build & Run locally
 2 | 
 3 | ### use flatpak
 4 | 
 5 | #### Build & install
 6 | 
 7 | ```bash
 8 | flatpak-builder --install --user --force-clean ./.flatpak-builder/out ./build-aux/com.usebottles.bottles.Devel.json
 9 | ```
10 | 
11 | #### Run
12 | 
13 | ```bash
14 | flatpak run com.usebottles.bottles.Devel
15 | ```
16 | 
17 | #### Uninstall devel version
18 | 
19 | ```bash
20 | flatpak uninstall com.usebottles.bottles.Devel
21 | ```
22 | 
23 | ## Unit Test
24 | 
25 | ### run all tests
26 | 
27 | ```bash
28 | pytest .
29 | ```
30 | 
31 | ## Dependencies
32 | 
33 | Regenerate PYPI dependency manifest when requirements.txt changed
34 | 
35 | ```bash
36 | python ./build-aux/req2flatpak/req2flatpak.py --requirements-file requirements.txt --yaml --target-platforms 312-x86_64 -o build-aux/pypi-deps.yaml
37 | ```
38 | 
39 | ## I18n files
40 | 
41 | ### `po/POTFILES`
42 | 
43 | List of source files containing translatable strings.
44 | Regenerate this file when you added/moved/removed/renamed files
45 | that contains translatable strings.
46 | 
47 | ```bash
48 | cat > po/POTFILES <<EOF
49 | # List of source files containing translatable strings.
50 | # Please keep this file sorted alphabetically.
51 | EOF
52 | grep -rlP "_\(['\"]" bottles | sort >> po/POTFILES
53 | cat >> po/POTFILES <<EOF
54 | data/com.usebottles.bottles.desktop.in.in
55 | data/com.usebottles.bottles.gschema.xml
56 | data/com.usebottles.bottles.metainfo.xml.in.in
57 | EOF
58 | ```
59 | 
60 | ### `po/bottles.pot` and `po/*.po`
61 | 
62 | We have a main pot file, which is template for other `.po` files
63 | And for each language listed in `po/LINGUAS` we have a corresponding `.po` file
64 | Regenerate these files when any translatable string added/changed/removed
65 | 
66 | ```bash
67 | # make sure you have `meson` and `blueprint-compiler` installed
68 | meson setup /tmp/i18n-build
69 | meson compile -C /tmp/i18n-build/ bottles-pot
70 | meson compile -C /tmp/i18n-build/ bottles-update-po
71 | ```
72 | 


--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
 1 | # Contributing to Bottles
 2 | First off, thanks for taking the time to contribute :heart:!
 3 | 
 4 | ## Found a Problem?
 5 | Before reporting a problem, be it a bug, design or others, we assume you have made sure that:
 6 | 1. the [Bottles wiki](https://github.com/bottlesdevs/Bottles/wiki) does not cover your problem
 7 | 1. the problem has not been reported in the [issue tracker](https://github.com/bottlesdevs/Bottles/issues)
 8 | 1. the problem is reproducible with [Bottles from Flathub](https://flathub.org/apps/details/com.usebottles.bottles)
 9 | 
10 | If all apply, then please consider opening a [new issue](https://github.com/bottlesdevs/Bottles/issues/new/choose).
11 | 
12 | ## Want to Submit Code?
13 | 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).
14 | 
15 | To build Bottles, refer to the [Building](README.md#Building) instructions.
16 | 
17 | ### Having Trouble Understanding the Source Code?
18 | 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!
19 | 
20 | ## Want to Translate Bottles?
21 | You can help Bottles speak your language by translating on [Weblate](https://hosted.weblate.org/projects/bottles).
22 | 
23 | ## Want to Donate or Sponsor Bottles?
24 | You can financially support Bottles through [donations and sponsorships](https://usebottles.com/funding).
25 | 


--------------------------------------------------------------------------------
/VERSION:
--------------------------------------------------------------------------------
1 | 51.18
2 | 


--------------------------------------------------------------------------------
/VERSION_UPDATE.md:
--------------------------------------------------------------------------------
1 | ### Paths to be updated
2 | - VERSION
3 | - data/com.usebottles.metainfo.xml.in
4 | - meson.build
5 | 


--------------------------------------------------------------------------------
/bottles/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bottlesdevs/Bottles/5020d62c2e086de3f407d8667b3d8b4b5ae644f5/bottles/__init__.py


--------------------------------------------------------------------------------
/bottles/backend/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bottlesdevs/Bottles/5020d62c2e086de3f407d8667b3d8b4b5ae644f5/bottles/backend/__init__.py


--------------------------------------------------------------------------------
/bottles/backend/diff.py:
--------------------------------------------------------------------------------
 1 | import os
 2 | import hashlib
 3 | 
 4 | 
 5 | class Diff:
 6 |     """
 7 |     This class is no more used by the application, it's just a
 8 |     reference for future implementations.
 9 |     """
10 | 
11 |     __ignored = ["dosdevices", "users", "bottle.yml", "storage"]
12 | 
13 |     @staticmethod
14 |     def hashify(path: str) -> dict:
15 |         """
16 |         Hash (SHA-1) all files in a directory and return
17 |         them in a dictionary. Here we use SHA-1 instead of
18 |         better ones like SHA-256 because we only need to
19 |         compare the file hashes, it's faster, and it's
20 |         not a security risk.
21 |         """
22 |         _files = {}
23 | 
24 |         if path[-1] != os.sep:
25 |             """
26 |             Be sure to add a trailing slash at the end of the path to
27 |             prevent the correct path name in the result.
28 |             """
29 |             path += os.sep
30 | 
31 |         for root, dirs, files in os.walk(path):
32 |             dirs[:] = [d for d in dirs if d not in Diff.__ignored]
33 |             for f in files:
34 |                 if f in Diff.__ignored:
35 |                     continue
36 |                 with open(os.path.join(root, f), "rb") as fr:
37 |                     _hash = hashlib.sha1(fr.read()).hexdigest()
38 | 
39 |                 _key = os.path.join(root, f)
40 |                 _key = _key.replace(path, "")
41 |                 _files[_key] = _hash
42 | 
43 |         return _files
44 | 
45 |     @staticmethod
46 |     def file_hashify(path: str) -> str:
47 |         """Hash (SHA-1) a file and return it."""
48 |         with open(path, "rb") as fr:
49 |             _hash = hashlib.sha1(fr.read()).hexdigest()
50 | 
51 |         return _hash
52 | 
53 |     @staticmethod
54 |     def compare(parent: dict, child: dict) -> dict:
55 |         """
56 |         Compare two hashes dictionaries and return the
57 |         differences (added, removed, changed).
58 |         """
59 | 
60 |         added = []
61 |         changed = []
62 |         removed = [f for f in parent if f not in child]
63 | 
64 |         for f in child:
65 |             if f not in parent:
66 |                 added.append(f)
67 |             elif parent[f] != child[f]:
68 |                 changed.append(f)
69 | 
70 |         return {"added": added, "removed": removed, "changed": changed}
71 | 


--------------------------------------------------------------------------------
/bottles/backend/dlls/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bottlesdevs/Bottles/5020d62c2e086de3f407d8667b3d8b4b5ae644f5/bottles/backend/dlls/__init__.py


--------------------------------------------------------------------------------
/bottles/backend/dlls/dxvk.py:
--------------------------------------------------------------------------------
 1 | # dxvk.py
 2 | #
 3 | # Copyright 2022 brombinmirko <send@mirko.pm>
 4 | #
 5 | # This program is free software: you can redistribute it and/or modify
 6 | # it under the terms of the GNU General Public License as published by
 7 | # the Free Software Foundation, in version 3 of the License.
 8 | #
 9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 | #
17 | 
18 | from bottles.backend.dlls.dll import DLLComponent
19 | from bottles.backend.utils.manager import ManagerUtils
20 | 
21 | 
22 | class DXVKComponent(DLLComponent):
23 |     dlls = {
24 |         "x32": ["d3d8.dll", "d3d9.dll", "d3d10core.dll", "d3d11.dll", "dxgi.dll"],
25 |         "x64": ["d3d8.dll", "d3d9.dll", "d3d10core.dll", "d3d11.dll", "dxgi.dll"],
26 |     }
27 | 
28 |     @staticmethod
29 |     def get_override_keys() -> str:
30 |         return "d3d8,d3d9,d3d10core,d3d11,dxgi"
31 | 
32 |     @staticmethod
33 |     def get_base_path(version: str) -> str:
34 |         return ManagerUtils.get_dxvk_path(version)
35 | 


--------------------------------------------------------------------------------
/bottles/backend/dlls/latencyflex.py:
--------------------------------------------------------------------------------
 1 | # dxvk.py
 2 | #
 3 | # Copyright 2022 brombinmirko <send@mirko.pm>
 4 | #
 5 | # This program is free software: you can redistribute it and/or modify
 6 | # it under the terms of the GNU General Public License as published by
 7 | # the Free Software Foundation, in version 3 of the License.
 8 | #
 9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 | #
17 | 
18 | from bottles.backend.dlls.dll import DLLComponent
19 | from bottles.backend.utils.manager import ManagerUtils
20 | 
21 | 
22 | class LatencyFleXComponent(DLLComponent):
23 |     dlls = {
24 |         "wine/usr/lib/wine/x86_64-windows": [
25 |             "latencyflex_layer.dll",
26 |             "latencyflex_wine.dll",
27 |         ]
28 |     }
29 | 
30 |     @staticmethod
31 |     def get_override_keys() -> str:
32 |         return "latencyflex_layer,latencyflex_wine"
33 | 
34 |     @staticmethod
35 |     def get_base_path(version: str) -> str:
36 |         return ManagerUtils.get_latencyflex_path(version)
37 | 


--------------------------------------------------------------------------------
/bottles/backend/dlls/meson.build:
--------------------------------------------------------------------------------
 1 | pkgdatadir = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name())
 2 | dllsdir = join_paths(pkgdatadir, 'bottles/backend/dlls')
 3 | 
 4 | bottles_sources = [
 5 |   '__init__.py',
 6 |   'dll.py',
 7 |   'dxvk.py',
 8 |   'vkd3d.py',
 9 |   'nvapi.py',
10 |   'latencyflex.py',
11 | ]
12 | 
13 | install_data(bottles_sources, install_dir: dllsdir)
14 | 


--------------------------------------------------------------------------------
/bottles/backend/dlls/vkd3d.py:
--------------------------------------------------------------------------------
 1 | # vkd3d.py
 2 | #
 3 | # Copyright 2022 brombinmirko <send@mirko.pm>
 4 | #
 5 | # This program is free software: you can redistribute it and/or modify
 6 | # it under the terms of the GNU General Public License as published by
 7 | # the Free Software Foundation, in version 3 of the License.
 8 | #
 9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 | #
17 | 
18 | from bottles.backend.dlls.dll import DLLComponent
19 | from bottles.backend.utils.manager import ManagerUtils
20 | 
21 | 
22 | class VKD3DComponent(DLLComponent):
23 |     dlls = {
24 |         "x86": ["d3d12.dll", "d3d12core.dll"],
25 |         "x64": ["d3d12.dll", "d3d12core.dll"],
26 |     }
27 | 
28 |     @staticmethod
29 |     def get_override_keys() -> str:
30 |         return "d3d12,d3d12core"
31 | 
32 |     @staticmethod
33 |     def get_base_path(version: str) -> str:
34 |         return ManagerUtils.get_vkd3d_path(version)
35 | 


--------------------------------------------------------------------------------
/bottles/backend/managers/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bottlesdevs/Bottles/5020d62c2e086de3f407d8667b3d8b4b5ae644f5/bottles/backend/managers/__init__.py


--------------------------------------------------------------------------------
/bottles/backend/managers/meson.build:
--------------------------------------------------------------------------------
 1 | pkgdatadir = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name())
 2 | managersdir = join_paths(pkgdatadir, 'bottles/backend/managers')
 3 | 
 4 | bottles_sources = [
 5 |   '__init__.py',
 6 |   'backup.py',
 7 |   'component.py',
 8 |   'dependency.py',
 9 |   'installer.py',
10 |   'library.py',
11 |   'manager.py',
12 |   'versioning.py',
13 |   'data.py',
14 |   'runtime.py',
15 |   'importer.py',
16 |   'conf.py',
17 |   'journal.py',
18 |   'repository.py',
19 |   'template.py',
20 |   'sandbox.py',
21 |   'steam.py',
22 |   'epicgamesstore.py',
23 |   'ubisoftconnect.py',
24 |   'origin.py',
25 |   'queue.py',
26 |   'steamgriddb.py',
27 |   'thumbnail.py'
28 | ]
29 | 
30 | install_data(bottles_sources, install_dir: managersdir)
31 | 


--------------------------------------------------------------------------------
/bottles/backend/managers/origin.py:
--------------------------------------------------------------------------------
 1 | # origin.py
 2 | #
 3 | # Copyright 2022 brombinmirko <send@mirko.pm>
 4 | #
 5 | # This program is free software: you can redistribute it and/or modify
 6 | # it under the terms of the GNU General Public License as published by
 7 | # the Free Software Foundation, in version 3 of the License.
 8 | #
 9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 | #
17 | 
18 | import os
19 | 
20 | from bottles.backend.models.config import BottleConfig
21 | from bottles.backend.utils.manager import ManagerUtils
22 | 
23 | 
24 | class OriginManager:
25 |     @staticmethod
26 |     def find_manifests_path(config: BottleConfig) -> str | None:
27 |         """
28 |         Finds the Origin manifests path.
29 |         """
30 |         paths = [
31 |             os.path.join(
32 |                 ManagerUtils.get_bottle_path(config),
33 |                 "drive_c/ProgramData/Origin/LocalContent",
34 |             )
35 |         ]
36 | 
37 |         for path in paths:
38 |             if os.path.exists(path):
39 |                 return path
40 |         return None
41 | 
42 |     @staticmethod
43 |     def is_origin_supported(config: BottleConfig) -> bool:
44 |         """
45 |         Checks if Origin is supported.
46 |         """
47 |         return OriginManager.find_manifests_path(config) is not None
48 | 
49 |     @staticmethod
50 |     def get_installed_games(config: BottleConfig) -> list:
51 |         """
52 |         Gets the games.
53 |         """
54 |         games = []
55 |         return games
56 | 


--------------------------------------------------------------------------------
/bottles/backend/managers/queue.py:
--------------------------------------------------------------------------------
 1 | # queue.py
 2 | #
 3 | # Copyright 2022 brombinmirko <send@mirko.pm>
 4 | #
 5 | # This program is free software: you can redistribute it and/or modify
 6 | # it under the terms of the GNU General Public License as published by
 7 | # the Free Software Foundation, in version 3 of the License.
 8 | #
 9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 | #
17 | 
18 | 
19 | class QueueManager:
20 |     __queue = 0
21 | 
22 |     def __init__(self, end_fn, add_fn=None):
23 |         self.__add_fn = add_fn
24 |         self.__end_fn = end_fn
25 | 
26 |     def add_task(self):
27 |         self.__queue += 1
28 |         if self.__add_fn and self.__queue == 1:
29 |             self.__add_fn()
30 | 
31 |     def end_task(self):
32 |         self.__queue -= 1
33 |         if self.__queue <= 0:
34 |             self.__end_fn()
35 | 


--------------------------------------------------------------------------------
/bottles/backend/managers/steamgriddb.py:
--------------------------------------------------------------------------------
 1 | # steamgriddb.py
 2 | #
 3 | # Copyright 2022 brombinmirko <send@mirko.pm>
 4 | #
 5 | # This program is free software: you can redistribute it and/or modify
 6 | # it under the terms of the GNU General Public License as published by
 7 | # the Free Software Foundation, in version 3 of the License.
 8 | #
 9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 | #
17 | 
18 | import os
19 | import uuid
20 | import requests
21 | 
22 | from bottles.backend.logger import Logger
23 | from bottles.backend.models.config import BottleConfig
24 | from bottles.backend.utils.manager import ManagerUtils
25 | 
26 | logging = Logger()
27 | 
28 | 
29 | class SteamGridDBManager:
30 |     @staticmethod
31 |     def get_game_grid(name: str, config: BottleConfig):
32 |         try:
33 |             res = requests.get(f"https://steamgrid.usebottles.com/api/search/{name}")
34 |         except:
35 |             return
36 | 
37 |         if res.status_code == 200:
38 |             return SteamGridDBManager.__save_grid(res.json(), config)
39 | 
40 |     @staticmethod
41 |     def __save_grid(url: str, config: BottleConfig):
42 |         grids_path = os.path.join(ManagerUtils.get_bottle_path(config), "grids")
43 |         if not os.path.exists(grids_path):
44 |             os.makedirs(grids_path)
45 | 
46 |         ext = url.split(".")[-1]
47 |         filename = str(uuid.uuid4()) + "." + ext
48 |         path = os.path.join(grids_path, filename)
49 | 
50 |         try:
51 |             r = requests.get(url)
52 |             with open(path, "wb") as f:
53 |                 f.write(r.content)
54 |         except Exception:
55 |             return
56 | 
57 |         return f"grid:{filename}"
58 | 


--------------------------------------------------------------------------------
/bottles/backend/managers/thumbnail.py:
--------------------------------------------------------------------------------
 1 | # thumbnail.py
 2 | #
 3 | # Copyright 2022 brombinmirko <send@mirko.pm>
 4 | #
 5 | # This program is free software: you can redistribute it and/or modify
 6 | # it under the terms of the GNU General Public License as published by
 7 | # the Free Software Foundation, in version 3 of the License.
 8 | #
 9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 | #
17 | 
18 | import os
19 | 
20 | from bottles.backend.logger import Logger
21 | from bottles.backend.models.config import BottleConfig
22 | from bottles.backend.utils.manager import ManagerUtils
23 | 
24 | logging = Logger()
25 | 
26 | 
27 | class ThumbnailManager:
28 |     @staticmethod
29 |     def get_path(config: BottleConfig, uri: str):
30 |         if uri.startswith("grid:"):
31 |             return ThumbnailManager.__load_grid(config, uri)
32 |         # elif uri.startswith("epic:"):
33 |         #     return ThumbnailManager.__load_epic(config, uri)
34 |         # elif uri.startswith("origin:"):
35 |         #     return ThumbnailManager.__load_origin(config, uri)
36 |         logging.error("Unknown URI: " + uri)
37 |         return None
38 | 
39 |     @staticmethod
40 |     def __load_grid(config: BottleConfig, uri: str):
41 |         bottle_path = ManagerUtils.get_bottle_path(config)
42 |         file_name = uri[5:]
43 |         path = os.path.join(bottle_path, "grids", file_name)
44 | 
45 |         if not os.path.exists(path):
46 |             logging.error("Grid not found: " + path)
47 |             return None
48 | 
49 |         return path
50 | 


--------------------------------------------------------------------------------
/bottles/backend/meson.build:
--------------------------------------------------------------------------------
 1 | pkgdatadir = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name())
 2 | backenddir = join_paths(pkgdatadir, 'bottles/backend')
 3 | 
 4 | params_file = configure_file(
 5 |     input:  'params.py',
 6 |     output:  'params.py',
 7 |     configuration: conf
 8 | )
 9 | 
10 | subdir('wine')
11 | subdir('models')
12 | subdir('utils')
13 | subdir('dlls')
14 | subdir('repos')
15 | subdir('managers')
16 | 
17 | bottles_sources = [
18 |   '__init__.py',
19 |   'globals.py',
20 |   'runner.py',
21 |   'diff.py',
22 |   'health.py',
23 |   'downloader.py',
24 |   'logger.py',
25 |   'cabextract.py',
26 |   'state.py',
27 |   params_file
28 | ]
29 | 
30 | install_data(bottles_sources, install_dir: backenddir)
31 | 


--------------------------------------------------------------------------------
/bottles/backend/models/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bottlesdevs/Bottles/5020d62c2e086de3f407d8667b3d8b4b5ae644f5/bottles/backend/models/__init__.py


--------------------------------------------------------------------------------
/bottles/backend/models/enum.py:
--------------------------------------------------------------------------------
1 | class Arch:
2 |     WIN32 = "win32"
3 |     WIN64 = "win64"
4 | 


--------------------------------------------------------------------------------
/bottles/backend/models/meson.build:
--------------------------------------------------------------------------------
 1 | pkgdatadir = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name())
 2 | modelsdir = join_paths(pkgdatadir, 'bottles/backend/models')
 3 | 
 4 | bottles_sources = [
 5 |   '__init__.py',
 6 |   'result.py',
 7 |   'samples.py',
 8 |   'vdict.py',
 9 |   'config.py',
10 |   'enum.py',
11 | ]
12 | 
13 | install_data(bottles_sources, install_dir: modelsdir)
14 | 


--------------------------------------------------------------------------------
/bottles/backend/models/result.py:
--------------------------------------------------------------------------------
 1 | # result.py
 2 | #
 3 | # Copyright 2022 brombinmirko <send@mirko.pm>
 4 | #
 5 | # This program is free software: you can redistribute it and/or modify
 6 | # it under the terms of the GNU General Public License as published by
 7 | # the Free Software Foundation, in version 3 of the License.
 8 | #
 9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 | #
17 | from typing import TypeVar, Generic
18 | 
19 | T = TypeVar("T")
20 | 
21 | 
22 | class Result(Generic[T]):
23 |     """
24 |     The Result object is the standard return object for every
25 |     method in the backend. It is important to use this object
26 |     to keep the code clean and consistent.
27 |     """
28 | 
29 |     status: bool = False
30 |     data: T = None
31 |     message: str = ""
32 | 
33 |     def __init__(self, status: bool = False, data: T = None, message: str = ""):
34 |         self.status = status
35 |         self.data = data
36 |         self.message = message
37 | 
38 |     def set_status(self, v: bool):
39 |         self.status = v
40 | 
41 |     @property
42 |     def ok(self):
43 |         return self.status
44 | 
45 |     @property
46 |     def has_data(self):
47 |         return bool(self.data)
48 | 
49 |     @property
50 |     def ready(self):
51 |         return self.ok and self.has_data
52 | 


--------------------------------------------------------------------------------
/bottles/backend/models/samples.py:
--------------------------------------------------------------------------------
 1 | class Samples:
 2 |     data = {}
 3 |     environments = {
 4 |         "gaming": {
 5 |             "Runner": "wine",
 6 |             "Parameters": {
 7 |                 "dxvk": True,
 8 |                 # "nvapi": True,
 9 |                 "vkd3d": True,
10 |                 "sync": "fsync",
11 |                 "fsr": False,
12 |                 "discrete_gpu": True,
13 |                 "pulseaudio_latency": False,
14 |             },
15 |             "Installed_Dependencies": [
16 |                 "d3dx9",
17 |                 "msls31",
18 |                 "arial32",
19 |                 "times32",
20 |                 "courie32",
21 |                 "d3dcompiler_43",
22 |                 "d3dcompiler_47",
23 |                 "mono",
24 |                 "gecko",
25 |             ],
26 |         },
27 |         "application": {
28 |             "Runner": "wine",
29 |             "Parameters": {"dxvk": True, "vkd3d": True, "pulseaudio_latency": False},
30 |             "Installed_Dependencies": [
31 |                 "arial32",
32 |                 "times32",
33 |                 "courie32",
34 |                 "mono",
35 |                 "gecko",
36 |                 # "dotnet40",
37 |                 # "dotnet48"
38 |             ],
39 |         },
40 |     }
41 |     bottles_to_steam_relations = {
42 |         "MANGOHUD": ("mangohud", True),
43 |         "OBS_VKCAPTURE": ("obsvkc", True),
44 |         "ENABLE_VKBASALT": ("vkbasalt", True),
45 |         "WINEESYNC": ("sync", "esync"),
46 |         "WINEFSYNC": ("sync", "fsync"),
47 |         "WINE_FULLSCREEN_FSR": ("fsr", True),
48 |         "WINE_FULLSCREEN_FSR_STRENGTH": ("fsr_sharpening_strength", 2),
49 |         "WINE_FULLSCREEN_FSR_MODE": ("fsr_quality_mode", "none"),
50 |         "GAMESCOPE": ("gamescope", False),
51 |         "DRI_PRIME": ("discrete_gpu", True),
52 |         "__NV_PRIME_RENDER_OFFLOAD": ("discrete_gpu", True),
53 |         "PULSE_LATENCY_MSEC": ("pulseaudio_latency", True),
54 |         "PROTON_EAC_RUNTIME": ("use_eac_runtime", True),
55 |         "PROTON_BATTLEYE_RUNTIME": ("use_be_runtime", True),
56 |     }
57 | 


--------------------------------------------------------------------------------
/bottles/backend/params.py:
--------------------------------------------------------------------------------
1 | APP_VERSION = "@APP_VERSION@"
2 | BASE_ID = "@BASE_ID@"
3 | APP_ID = "@APP_ID@"
4 | 


--------------------------------------------------------------------------------
/bottles/backend/repos/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bottlesdevs/Bottles/5020d62c2e086de3f407d8667b3d8b4b5ae644f5/bottles/backend/repos/__init__.py


--------------------------------------------------------------------------------
/bottles/backend/repos/component.py:
--------------------------------------------------------------------------------
 1 | # component.py
 2 | #
 3 | # Copyright 2022 brombinmirko <send@mirko.pm>
 4 | #
 5 | # This program is free software: you can redistribute it and/or modify
 6 | # it under the terms of the GNU General Public License as published by
 7 | # the Free Software Foundation, in version 3 of the License.
 8 | #
 9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 | #
17 | 
18 | from bottles.backend.repos.repo import Repo
19 | 
20 | 
21 | class ComponentRepo(Repo):
22 |     name = "components"
23 | 
24 |     def get(self, name: str, plain: bool = False) -> str | dict | bool:
25 |         if name in self.catalog:
26 |             entry = self.catalog[name]
27 |             category = entry["Category"]
28 |             subcategory = entry.get("Sub-category")
29 | 
30 |             if subcategory:
31 |                 url = f"{self.url}/{category}/{subcategory}/{name}.yml"
32 |             else:
33 |                 url = f"{self.url}/{category}/{name}.yml"
34 | 
35 |             return self.get_manifest(url, plain)
36 |         return False
37 | 


--------------------------------------------------------------------------------
/bottles/backend/repos/dependency.py:
--------------------------------------------------------------------------------
 1 | # dependency.py
 2 | #
 3 | # Copyright 2022 brombinmirko <send@mirko.pm>
 4 | #
 5 | # This program is free software: you can redistribute it and/or modify
 6 | # it under the terms of the GNU General Public License as published by
 7 | # the Free Software Foundation, in version 3 of the License.
 8 | #
 9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 | #
17 | 
18 | from bottles.backend.repos.repo import Repo
19 | 
20 | 
21 | class DependencyRepo(Repo):
22 |     name = "dependencies"
23 | 
24 |     def get(self, name: str, plain: bool = False) -> str | dict | bool:
25 |         if name in self.catalog:
26 |             entry = self.catalog[name]
27 |             url = f"{self.url}/{entry['Category']}/{name}.yml"
28 |             return self.get_manifest(url, plain)
29 |         return False
30 | 


--------------------------------------------------------------------------------
/bottles/backend/repos/installer.py:
--------------------------------------------------------------------------------
 1 | # installer.py
 2 | #
 3 | # Copyright 2022 brombinmirko <send@mirko.pm>
 4 | #
 5 | # This program is free software: you can redistribute it and/or modify
 6 | # it under the terms of the GNU General Public License as published by
 7 | # the Free Software Foundation, in version 3 of the License.
 8 | #
 9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 | #
17 | 
18 | from bottles.backend.repos.repo import Repo
19 | 
20 | 
21 | class InstallerRepo(Repo):
22 |     name = "installers"
23 | 
24 |     def get(self, name: str, plain: bool = False) -> str | dict | bool:
25 |         if name in self.catalog:
26 |             entry = self.catalog[name]
27 |             url = f"{self.url}/{entry['Category']}/{name}.yml"
28 |             return self.get_manifest(url, plain)
29 |         return False
30 | 
31 |     def get_review(self, name: str) -> str | dict | bool:
32 |         if name in self.catalog:
33 |             return self.get_manifest(f"{self.url}/Reviews/{name}.md", plain=True)
34 |         return False
35 | 
36 |     def get_icon(self, name: str) -> str | bytes | None:
37 |         if name in self.catalog:
38 |             entry = self.catalog[name]
39 |             icon = entry.get("Icon")
40 |             if icon:
41 |                 return f"{self.url}/data/{name}/{icon}"
42 |         return None
43 | 


--------------------------------------------------------------------------------
/bottles/backend/repos/meson.build:
--------------------------------------------------------------------------------
 1 | pkgdatadir = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name())
 2 | reposdir = join_paths(pkgdatadir, 'bottles/backend/repos')
 3 | 
 4 | bottles_sources = [
 5 |   '__init__.py',
 6 |   'repo.py',
 7 |   'dependency.py',
 8 |   'component.py',
 9 |   'installer.py',
10 | ]
11 | 
12 | install_data(bottles_sources, install_dir: reposdir)
13 | 


--------------------------------------------------------------------------------
/bottles/backend/repos/repo.py:
--------------------------------------------------------------------------------
 1 | # repo.py
 2 | #
 3 | # Copyright 2022 brombinmirko <send@mirko.pm>
 4 | #
 5 | # This program is free software: you can redistribute it and/or modify
 6 | # it under the terms of the GNU General Public License as published by
 7 | # the Free Software Foundation, in version 3 of the License.
 8 | #
 9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 | #
17 | 
18 | from io import BytesIO
19 | 
20 | import pycurl
21 | 
22 | from bottles.backend.logger import Logger
23 | from bottles.backend.state import EventManager, Events
24 | from bottles.backend.utils import yaml
25 | from bottles.backend.utils.threading import RunAsync
26 | 
27 | logging = Logger()
28 | 
29 | 
30 | class Repo:
31 |     name: str = ""
32 | 
33 |     def __init__(self, url: str, index: str, offline: bool = False):
34 |         self.url = url
35 |         self.catalog = None
36 | 
37 |         def set_catalog(result, error=None):
38 |             self.catalog = result
39 |             EventManager.done(Events(self.name + ".fetching"))
40 | 
41 |         RunAsync(self.__get_catalog, callback=set_catalog, index=index, offline=offline)
42 | 
43 |     def __get_catalog(self, index: str, offline: bool = False):
44 |         if index in ["", None] or offline:
45 |             return {}
46 | 
47 |         try:
48 |             buffer = BytesIO()
49 | 
50 |             c = pycurl.Curl()
51 |             c.setopt(c.URL, index)
52 |             c.setopt(c.FOLLOWLOCATION, True)
53 |             c.setopt(c.WRITEDATA, buffer)
54 |             c.perform()
55 |             c.close()
56 | 
57 |             index = yaml.load(buffer.getvalue())
58 |             logging.info(f"Catalog {self.name} loaded")
59 | 
60 |             return index
61 |         except (pycurl.error, yaml.YAMLError):
62 |             logging.error(f"Cannot fetch {self.name} repository index.")
63 |             return {}
64 | 
65 |     def get_manifest(self, url: str, plain: bool = False) -> str | dict | bool:
66 |         try:
67 |             buffer = BytesIO()
68 | 
69 |             c = pycurl.Curl()
70 |             c.setopt(c.URL, url)
71 |             c.setopt(c.FOLLOWLOCATION, True)
72 |             c.setopt(c.WRITEDATA, buffer)
73 |             c.perform()
74 |             c.close()
75 | 
76 |             res = buffer.getvalue()
77 | 
78 |             if plain:
79 |                 return res.decode("utf-8")
80 | 
81 |             return yaml.load(res)
82 |         except (pycurl.error, yaml.YAMLError):
83 |             logging.error(f"Cannot fetch {self.name} manifest.")
84 |             return False
85 | 


--------------------------------------------------------------------------------
/bottles/backend/utils/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bottlesdevs/Bottles/5020d62c2e086de3f407d8667b3d8b4b5ae644f5/bottles/backend/utils/__init__.py


--------------------------------------------------------------------------------
/bottles/backend/utils/decorators.py:
--------------------------------------------------------------------------------
 1 | # decorators.py
 2 | #
 3 | # Copyright 2022 brombinmirko <send@mirko.pm>
 4 | #
 5 | # This program is free software: you can redistribute it and/or modify
 6 | # it under the terms of the GNU General Public License as published by
 7 | # the Free Software Foundation, in version 3 of the License.
 8 | #
 9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 | #
17 | 
18 | from functools import lru_cache, wraps
19 | from time import monotonic_ns
20 | 
21 | 
22 | def cache(_func=None, *, seconds: int = 600, maxsize: int = 128, typed: bool = False):
23 |     """
24 |     Extension of functools lru_cache with a timeout
25 | 
26 |     Parameters:
27 |     seconds (int): Timeout in seconds to clear the WHOLE cache, default = 10 minutes
28 |     maxsize (int): Maximum Size of the Cache
29 |     typed (bool): Same value of different type will be a different entry
30 | 
31 |     Source: <https://gist.github.com/Morreski/c1d08a3afa4040815eafd3891e16b945>
32 |     """
33 | 
34 |     def wrapper_cache(f):
35 |         f = lru_cache(maxsize=maxsize, typed=typed)(f)
36 |         f.delta = seconds * 10**9
37 |         f.expiration = monotonic_ns() + f.delta
38 | 
39 |         @wraps(f)
40 |         def wrapped_f(*args, **kwargs):
41 |             if monotonic_ns() >= f.expiration:
42 |                 f.cache_clear()
43 |                 f.expiration = monotonic_ns() + f.delta
44 |             return f(*args, **kwargs)
45 | 
46 |         wrapped_f.cache_info = f.cache_info
47 |         wrapped_f.cache_clear = f.cache_clear
48 |         return wrapped_f
49 | 
50 |     # To allow decorator to be used without arguments
51 |     if _func is None:
52 |         return wrapper_cache
53 |     else:
54 |         return wrapper_cache(_func)
55 | 


--------------------------------------------------------------------------------
/bottles/backend/utils/display.py:
--------------------------------------------------------------------------------
 1 | import os
 2 | import subprocess
 3 | from functools import lru_cache
 4 | 
 5 | 
 6 | class DisplayUtils:
 7 |     @staticmethod
 8 |     @lru_cache
 9 |     def get_x_display():
10 |         """Get the X display port."""
11 |         env_var = "DISPLAY"
12 |         ports_range = range(3)
13 | 
14 |         if os.environ.get(env_var):
15 |             return os.environ.get(env_var)
16 | 
17 |         for i in ports_range:
18 |             _port = f":{i}"
19 |             _proc = (
20 |                 subprocess.Popen(
21 |                     f"xdpyinfo -display :{i}",
22 |                     stdout=subprocess.PIPE,
23 |                     stderr=subprocess.PIPE,
24 |                     shell=True,
25 |                 )
26 |                 .communicate()[0]
27 |                 .decode("utf-8")
28 |                 .lower()
29 |             )
30 |             if "x.org" in _proc:
31 |                 return _port
32 | 
33 |         return False
34 | 
35 |     @staticmethod
36 |     def check_nvidia_device():
37 |         """Check if there is an nvidia device connected"""
38 |         _query = "NVIDIA Corporation".lower()
39 |         _proc = (
40 |             subprocess.Popen(
41 |                 "lspci | grep 'VGA'",
42 |                 stdout=subprocess.PIPE,
43 |                 stderr=subprocess.PIPE,
44 |                 shell=True,
45 |             )
46 |             .communicate()[0]
47 |             .decode("utf-8")
48 |             .lower()
49 |         )
50 | 
51 |         if _query in _proc:
52 |             return True
53 |         return False
54 | 
55 |     @staticmethod
56 |     def display_server_type():
57 |         """Return the display server type"""
58 |         return os.environ.get("XDG_SESSION_TYPE", "x11").lower()
59 | 


--------------------------------------------------------------------------------
/bottles/backend/utils/gsettings_stub.py:
--------------------------------------------------------------------------------
 1 | from bottles.backend.logger import Logger
 2 | 
 3 | logging = Logger()
 4 | 
 5 | 
 6 | class GSettingsStub:
 7 |     @staticmethod
 8 |     def get_boolean(key: str) -> bool:
 9 |         logging.warning(f"Stub GSettings key {key}=False")
10 |         return False
11 | 


--------------------------------------------------------------------------------
/bottles/backend/utils/imagemagick.py:
--------------------------------------------------------------------------------
 1 | # imagemagick.py
 2 | #
 3 | # Copyright 2022 brombinmirko <send@mirko.pm>
 4 | #
 5 | # This program is free software: you can redistribute it and/or modify
 6 | # it under the terms of the GNU General Public License as published by
 7 | # the Free Software Foundation, in version 3 of the License.
 8 | #
 9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 | #
17 | 
18 | import os
19 | import subprocess
20 | 
21 | 
22 | class ImageMagickUtils:
23 |     def __init__(self, path: str):
24 |         self.path = path
25 | 
26 |     @staticmethod
27 |     def __validate_path(path: str):
28 |         if os.path.exists(path):
29 |             return False
30 |         if os.path.isdir(path):
31 |             return False
32 |         return True
33 | 
34 |     def list_assets(self):
35 |         cmd = f"identify '{self.path}'"
36 | 
37 |         try:
38 |             res = subprocess.check_output(["bash", "-c", cmd])
39 |         except:
40 |             return []
41 | 
42 |         assets = []
43 |         for r in res.decode().split("\n"):
44 |             _r = r.replace(self.path, "").split()
45 |             if len(_r) < 3:
46 |                 continue
47 |             try:
48 |                 assets.append(int(_r[2].split("x")[0]))
49 |             except ValueError:
50 |                 continue
51 |         return assets
52 | 
53 |     def convert(
54 |         self,
55 |         dest: str,
56 |         asset_size: int = 256,
57 |         resize: int = 256,
58 |         flatten: bool = True,
59 |         alpha: bool = True,
60 |         fallback: bool = True,
61 |     ):
62 |         if not self.__validate_path(dest):
63 |             raise FileExistsError("Destination path already exists")
64 | 
65 |         assets = self.list_assets()
66 |         asset_index = -1
67 |         cmd = f"convert '{self.path}'"
68 | 
69 |         if asset_size not in assets:
70 |             if not fallback:
71 |                 raise ValueError("Asset size not available")
72 |             if len(assets) > 0:
73 |                 asset_size = max(assets)
74 |                 asset_index = assets.index(asset_size)
75 |         else:
76 |             asset_index = assets.index(asset_size)
77 | 
78 |         if asset_index != -1:
79 |             cmd = f"convert '{self.path}[{asset_index}]'"
80 |         if resize > 0:
81 |             cmd += f" -thumbnail {resize}x{resize}"
82 |         if alpha:
83 |             cmd += " -alpha on -background none"
84 |         if flatten:
85 |             cmd += " -flatten"
86 | 
87 |         cmd += f" '{dest}'"
88 |         subprocess.Popen(["bash", "-c", cmd])
89 | 


--------------------------------------------------------------------------------
/bottles/backend/utils/json.py:
--------------------------------------------------------------------------------
 1 | """This should be a drop-in replacement for the json module built in CPython"""
 2 | 
 3 | import json
 4 | import json as _json
 5 | from typing import IO, Any
 6 | 
 7 | from bottles.backend.models.config import DictCompatMixIn
 8 | 
 9 | JSONDecodeError = json.JSONDecodeError
10 | 
11 | 
12 | class ExtJSONEncoder(_json.JSONEncoder):
13 |     def default(self, o):
14 |         if isinstance(o, DictCompatMixIn):
15 |             return o.json_serialize_handler(o)
16 |         return super().default(o)
17 | 
18 | 
19 | def load(fp: IO[str]) -> Any:
20 |     """Deserialize fp (a .read()-supporting file-like object containing a JSON document) to a Python object."""
21 |     return _json.load(fp)
22 | 
23 | 
24 | def loads(s: str | bytes) -> Any:
25 |     """Deserialize s (a str, bytes or bytearray instance containing a JSON document) to a Python object."""
26 |     return _json.loads(s)
27 | 
28 | 
29 | def dump(
30 |     obj: Any,
31 |     fp: IO[str],
32 |     *,
33 |     indent: str | int | None = None,
34 |     cls: type[_json.JSONEncoder] | None = None,
35 | ) -> None:
36 |     """
37 |     Serialize obj as a JSON formatted stream to fp (a .write()-supporting file-like object).
38 | 
39 |     :param obj: the object you want to serialize
40 |     :param fp: the file-like object you want to write
41 |     :param indent: `None` for compact output, `0` for newline only, non-negative integer for indent level
42 |     :param cls: Custom JsonEncoder subclass, use ExtJsonEncoder if not provided
43 |     """
44 |     if cls is None:  # replace default JsonEncoder
45 |         cls = ExtJSONEncoder
46 |     return _json.dump(obj, fp, indent=indent, cls=cls)
47 | 
48 | 
49 | def dumps(
50 |     obj: Any,
51 |     *,
52 |     indent: str | int | None = None,
53 |     cls: type[_json.JSONEncoder] | None = None,
54 | ) -> str:
55 |     """
56 |     Serialize obj to a JSON formatted str.
57 | 
58 |     :param obj: the object you want to serialize
59 |     :param indent: `None` for compact output, `0` for newline only, non-negative integer for indent level
60 |     :param cls: Custom JsonEncoder subclass, use ExtJsonEncoder if not provided
61 |     :return: serialized result
62 |     """
63 |     if cls is None:  # replace default JsonEncoder
64 |         cls = ExtJSONEncoder
65 |     return _json.dumps(obj, indent=indent, cls=cls)
66 | 


--------------------------------------------------------------------------------
/bottles/backend/utils/lnk.py:
--------------------------------------------------------------------------------
 1 | # lnk.py
 2 | #
 3 | # Copyright 2022 brombinmirko <send@mirko.pm>
 4 | #
 5 | # This program is free software: you can redistribute it and/or modify
 6 | # it under the terms of the GNU General Public License as published by
 7 | # the Free Software Foundation, in version 3 of the License.
 8 | #
 9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 | #
17 | 
18 | import locale
19 | import struct
20 | from functools import lru_cache
21 | 
22 | 
23 | class LnkUtils:
24 |     @staticmethod
25 |     @lru_cache
26 |     def get_data(path):
27 |         """
28 |         Gets data from a .lnk file, and returns them in a dictionary.
29 |         Thanks to @Winand and @Jared for the code.
30 |         <https://gist.github.com/Winand/997ed38269e899eb561991a0c663fa49>
31 |         """
32 |         with open(path, "rb") as stream:
33 |             content = stream.read()
34 |             """
35 |             Skip first 20 bytes (HeaderSize and LinkCLSID)
36 |             read the LinkFlags structure (4 bytes)
37 |             """
38 |             lflags = struct.unpack("I", content[0x14:0x18])[0]
39 |             position = 0x18
40 | 
41 |             if (lflags & 0x01) == 1:
42 |                 """
43 |                 If the HasLinkTargetIDList bit is set then skip the stored IDList
44 |                 structure and header
45 |                 """
46 |                 position = struct.unpack("H", content[0x4C:0x4E])[0] + 0x4E
47 | 
48 |             last_pos = position
49 |             position += 0x04
50 | 
51 |             # get how long the file information is (LinkInfoSize)
52 |             length = struct.unpack("I", content[last_pos:position])[0]
53 | 
54 |             """
55 |             Skip 12 bytes (LinkInfoHeaderSize, LinkInfoFlags and
56 |             VolumeIDOffset)
57 |             """
58 |             position += 0x0C
59 | 
60 |             # go to the LocalBasePath position
61 |             lbpos = struct.unpack("I", content[position : position + 0x04])[0]
62 |             position = last_pos + lbpos
63 | 
64 |             # read the string at the given position of the determined length
65 |             size = (length + last_pos) - position - 0x02
66 |             content = content[position : position + size].split(b"\x00", 1)
67 | 
68 |             decode = locale.getdefaultlocale()[1]
69 |             if len(content) > 1 or decode is None:
70 |                 decode = "utf-16"
71 | 
72 |             try:
73 |                 return content[-1].decode(decode)
74 |             except UnicodeDecodeError:
75 |                 return None
76 | 


--------------------------------------------------------------------------------
/bottles/backend/utils/meson.build:
--------------------------------------------------------------------------------
 1 | pkgdatadir = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name())
 2 | utilsdir = join_paths(pkgdatadir, 'bottles/backend/utils')
 3 | 
 4 | bottles_sources = [
 5 |   '__init__.py',
 6 |   'display.py',
 7 |   'gpu.py',
 8 |   'manager.py',
 9 |   'midi.py',
10 |   'vulkan.py',
11 |   'terminal.py',
12 |   'file.py',
13 |   'generic.py',
14 |   'wine.py',
15 |   'steam.py',
16 |   'lnk.py',
17 |   'decorators.py',
18 |   'snake.py',
19 |   'vdf.py',
20 |   'imagemagick.py',
21 |   'proc.py',
22 |   'yaml.py',
23 |   'nvidia.py',
24 |   'threading.py',
25 |   'connection.py',
26 |   'gsettings_stub.py',
27 |   'json.py',
28 |   'singleton.py'
29 | ]
30 | 
31 | install_data(bottles_sources, install_dir: utilsdir)
32 | 


--------------------------------------------------------------------------------
/bottles/backend/utils/proc.py:
--------------------------------------------------------------------------------
 1 | # proc.py
 2 | #
 3 | # Copyright 2022 brombinmirko <send@mirko.pm>
 4 | #
 5 | # This program is free software: you can redistribute it and/or modify
 6 | # it under the terms of the GNU General Public License as published by
 7 | # the Free Software Foundation, in version 3 of the License.
 8 | #
 9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 | #
17 | 
18 | import os
19 | import subprocess
20 | 
21 | 
22 | class Proc:
23 |     def __init__(self, pid):
24 |         self.pid = pid
25 | 
26 |     def __get_data(self, data):
27 |         try:
28 |             with open(os.path.join("/proc", str(self.pid), data), "rb") as f:
29 |                 return f.read().decode("utf-8")
30 |         except (FileNotFoundError, PermissionError):
31 |             return ""
32 | 
33 |     def get_cmdline(self):
34 |         return self.__get_data("cmdline")
35 | 
36 |     def get_env(self):
37 |         return self.__get_data("environ")
38 | 
39 |     def get_cwd(self):
40 |         return self.__get_data("cwd")
41 | 
42 |     def get_name(self):
43 |         return self.__get_data("stat")
44 | 
45 |     def kill(self):
46 |         subprocess.Popen(
47 |             ["kill", str(self.pid)],
48 |             stdout=subprocess.DEVNULL,
49 |             stderr=subprocess.DEVNULL,
50 |         )
51 | 
52 | 
53 | class ProcUtils:
54 |     @staticmethod
55 |     def get_procs():
56 |         procs = []
57 |         for pid in os.listdir("/proc"):
58 |             if pid.isdigit():
59 |                 procs.append(Proc(pid))
60 |         return procs
61 | 
62 |     @staticmethod
63 |     def get_by_cmdline(cmdline):
64 |         _procs = ProcUtils.get_procs()
65 |         return [proc for proc in _procs if cmdline in proc.get_cmdline()]
66 | 
67 |     @staticmethod
68 |     def get_by_env(env):
69 |         _procs = ProcUtils.get_procs()
70 |         return [proc for proc in _procs if env in proc.get_env()]
71 | 
72 |     @staticmethod
73 |     def get_by_cwd(cwd):
74 |         _procs = ProcUtils.get_procs()
75 |         return [proc for proc in _procs if cwd in proc.get_cwd()]
76 | 
77 |     @staticmethod
78 |     def get_by_name(name):
79 |         _procs = ProcUtils.get_procs()
80 |         return [proc for proc in _procs if name in proc.get_name()]
81 | 
82 |     @staticmethod
83 |     def get_by_pid(pid):
84 |         return Proc(pid)
85 | 


--------------------------------------------------------------------------------
/bottles/backend/utils/singleton.py:
--------------------------------------------------------------------------------
1 | class Singleton(type):
2 |     _instances = {}
3 | 
4 |     def __call__(cls, *args, **kwargs):
5 |         if cls not in cls._instances:
6 |             cls._instances[cls] = super().__call__(*args, **kwargs)
7 |         return cls._instances[cls]
8 | 


--------------------------------------------------------------------------------
/bottles/backend/utils/vulkan.py:
--------------------------------------------------------------------------------
 1 | # vulkan.py
 2 | #
 3 | # Copyright 2022 brombinmirko <send@mirko.pm>
 4 | #
 5 | # This program is free software: you can redistribute it and/or modify
 6 | # it under the terms of the GNU General Public License as published by
 7 | # the Free Software Foundation, in version 3 of the License.
 8 | #
 9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 | #
17 | 
18 | import os
19 | from glob import glob
20 | import shutil
21 | import subprocess
22 | 
23 | 
24 | class VulkanUtils:
25 |     __vk_icd_dirs = [
26 |         "/usr/share/vulkan",
27 |         "/etc/vulkan",
28 |         "/usr/local/share/vulkan",
29 |         "/usr/local/etc/vulkan",
30 |     ]
31 |     if "FLATPAK_ID" in os.environ:
32 |         __vk_icd_dirs += [
33 |             "/usr/lib/x86_64-linux-gnu/GL/vulkan",
34 |             "/usr/lib/i386-linux-gnu/GL/vulkan",
35 |         ]
36 | 
37 |     def __init__(self):
38 |         self.loaders = self.__get_vk_icd_loaders()
39 | 
40 |     def __get_vk_icd_loaders(self):
41 |         loaders = {"nvidia": [], "amd": [], "intel": []}
42 | 
43 |         for _dir in self.__vk_icd_dirs:
44 |             _files = glob(f"{_dir}/icd.d/*.json", recursive=True)
45 | 
46 |             for file in _files:
47 |                 if "nvidia" in file.lower():
48 |                     loaders["nvidia"] += [file]
49 |                 elif "amd" in file.lower() or "radeon" in file.lower():
50 |                     loaders["amd"] += [file]
51 |                 elif "intel" in file.lower():
52 |                     loaders["intel"] += [file]
53 | 
54 |         return loaders
55 | 
56 |     def get_vk_icd(self, vendor: str, as_string=False):
57 |         vendors = ["nvidia", "amd", "intel"]
58 |         icd = []
59 | 
60 |         if vendor in vendors:
61 |             icd = self.loaders[vendor]
62 | 
63 |         if as_string:
64 |             icd = ":".join(icd)
65 | 
66 |         return icd
67 | 
68 |     @staticmethod
69 |     def check_support():
70 |         return True
71 | 
72 |     @staticmethod
73 |     def test_vulkan():
74 |         if shutil.which("vulkaninfo") is None:
75 |             return "vulkaninfo tool not found"
76 | 
77 |         res = (
78 |             subprocess.Popen(
79 |                 "vulkaninfo", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True
80 |             )
81 |             .communicate()[0]
82 |             .decode("utf-8")
83 |         )
84 | 
85 |         return res
86 | 


--------------------------------------------------------------------------------
/bottles/backend/utils/wine.py:
--------------------------------------------------------------------------------
 1 | import os
 2 | 
 3 | 
 4 | class WineUtils:
 5 |     @staticmethod
 6 |     def get_user_dir(prefix_path: str):
 7 |         ignored = ["Public"]
 8 |         usersdir = os.path.join(prefix_path, "drive_c", "users")
 9 |         found = []
10 | 
11 |         for user_dir in os.listdir(usersdir):
12 |             if user_dir in ignored:
13 |                 continue
14 |             found.append(user_dir)
15 | 
16 |         if len(found) == 0:
17 |             raise Exception("No user directories found.")
18 | 
19 |         return found[0]
20 | 


--------------------------------------------------------------------------------
/bottles/backend/utils/yaml.py:
--------------------------------------------------------------------------------
 1 | import yaml as _yaml
 2 | 
 3 | from bottles.backend.models.config import BottleConfig
 4 | 
 5 | try:
 6 |     from yaml import CSafeLoader as SafeLoader, CSafeDumper as SafeDumper
 7 | except ImportError:
 8 |     from yaml import SafeLoader, SafeDumper
 9 | 
10 | YAMLError = _yaml.YAMLError
11 | SafeDumper.add_representer(BottleConfig, BottleConfig.yaml_serialize_handler)
12 | 
13 | 
14 | def dump(data, stream=None, **kwargs):
15 |     """
16 |     Serialize a Python object into a YAML stream.
17 |     If stream is None, return the produced string instead.
18 |     Note: This function is a replacement for PyYAML's dump() function,
19 |           using the CDumper class instead of the default Dumper, to achieve
20 |           the best performance.
21 |     """
22 |     return _yaml.dump(data, stream, Dumper=SafeDumper, **kwargs)
23 | 
24 | 
25 | # noinspection PyPep8Naming
26 | def load(stream, Loader=SafeLoader):
27 |     """
28 |     Load a YAML stream.
29 |     Note: This function is a replacement for PyYAML's safe_load() function,
30 |           using the CLoader class instead of the default Loader, to achieve
31 |           the best performance.
32 |     """
33 |     return _yaml.load(stream, Loader=Loader)
34 | 


--------------------------------------------------------------------------------
/bottles/backend/wine/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bottlesdevs/Bottles/5020d62c2e086de3f407d8667b3d8b4b5ae644f5/bottles/backend/wine/__init__.py


--------------------------------------------------------------------------------
/bottles/backend/wine/cmd.py:
--------------------------------------------------------------------------------
 1 | from bottles.backend.logger import Logger
 2 | from bottles.backend.wine.wineprogram import WineProgram
 3 | 
 4 | logging = Logger()
 5 | 
 6 | 
 7 | class CMD(WineProgram):
 8 |     program = "Wine Command Line"
 9 |     command = "cmd"
10 | 
11 |     def run_batch(
12 |         self,
13 |         batch: str,
14 |         terminal: bool = True,
15 |         args: str = "",
16 |         environment: dict | None = None,
17 |         cwd: str | None = None,
18 |     ):
19 |         args = f"/c {batch} {args}"
20 | 
21 |         self.launch(
22 |             args=args,
23 |             communicate=True,
24 |             terminal=terminal,
25 |             environment=environment,
26 |             cwd=cwd,
27 |             action_name="run_batch",
28 |         )
29 | 


--------------------------------------------------------------------------------
/bottles/backend/wine/control.py:
--------------------------------------------------------------------------------
 1 | from bottles.backend.logger import Logger
 2 | from bottles.backend.wine.wineprogram import WineProgram
 3 | 
 4 | logging = Logger()
 5 | 
 6 | 
 7 | class Control(WineProgram):
 8 |     program = "Wine Control Panel"
 9 |     command = "control"
10 | 
11 |     def load_applet(self, name: str):
12 |         args = name
13 |         return self.launch(args=args, communicate=True, action_name="load_applet")
14 | 
15 |     def load_joystick(self):
16 |         return self.load_applet("joy.cpl")
17 | 
18 |     def load_appwiz(self):
19 |         return self.load_applet("appwiz.cpl")
20 | 
21 |     def load_inetcpl(self):
22 |         return self.load_applet("inetcpl.cpl")
23 | 


--------------------------------------------------------------------------------
/bottles/backend/wine/drives.py:
--------------------------------------------------------------------------------
 1 | import os
 2 | 
 3 | from bottles.backend.logger import Logger
 4 | from bottles.backend.models.config import BottleConfig
 5 | from bottles.backend.utils.manager import ManagerUtils
 6 | 
 7 | logging = Logger()
 8 | 
 9 | 
10 | class Drives:
11 |     def __init__(self, config: BottleConfig):
12 |         self.config = config
13 |         bottle = ManagerUtils.get_bottle_path(self.config)
14 |         self.dosdevices_path = os.path.join(bottle, "dosdevices")
15 | 
16 |     def get_all(self):
17 |         """Get all the drives from the bottle"""
18 |         drives = {}
19 |         if os.path.exists(self.dosdevices_path):
20 |             for drive in os.listdir(self.dosdevices_path):
21 |                 if os.path.islink(f"{self.dosdevices_path}/{drive}"):
22 |                     letter = os.path.basename(drive).replace(":", "").upper()
23 |                     if len(letter) == 1 and letter.isalpha():
24 |                         path = os.readlink(f"{self.dosdevices_path}/{drive}")
25 |                         drives[letter] = path
26 |         return drives
27 | 
28 |     def get_drive(self, letter: str):
29 |         """Get a drive from the bottle"""
30 |         if letter in self.get_all():
31 |             return self.get_all().get(letter)
32 |         return None
33 | 
34 |     def set_drive_path(self, letter: str, path: str):
35 |         """Change a drives path in the bottle"""
36 |         letter = f"{letter}:".lower()
37 |         drive_sym_path = os.path.join(self.dosdevices_path, letter)
38 |         if not os.path.exists(self.dosdevices_path):
39 |             os.makedirs(self.dosdevices_path)
40 |         if not os.path.exists(drive_sym_path):
41 |             os.symlink(path, drive_sym_path)
42 |             logging.info(f"New drive {letter} added to the bottle")
43 |         else:
44 |             os.remove(drive_sym_path)
45 |             os.symlink(path, drive_sym_path)
46 |             logging.info(f"Drive {letter} path changed to {path}")
47 | 
48 |     def remove_drive(self, letter: str):
49 |         """Remove a drive from the bottle"""
50 |         if letter.upper() in self.get_all():
51 |             os.remove(f"{self.dosdevices_path}/{letter.lower()}:")
52 |             logging.info(f"Drive {letter} removed from the bottle")
53 | 


--------------------------------------------------------------------------------
/bottles/backend/wine/eject.py:
--------------------------------------------------------------------------------
 1 | from bottles.backend.logger import Logger
 2 | from bottles.backend.wine.wineprogram import WineProgram
 3 | 
 4 | logging = Logger()
 5 | 
 6 | 
 7 | class Eject(WineProgram):
 8 |     program = "Wine Eject CLI"
 9 |     command = "eject"
10 | 
11 |     def cdrom(self, drive: str, unmount_only: bool = False):
12 |         args = drive
13 |         if unmount_only:
14 |             args += " -u"
15 |         return self.launch(args=args, communicate=True, action_name="cdrom")
16 | 
17 |     def all(self):
18 |         args = "-a"
19 |         return self.launch(args=args, communicate=True, action_name="all")
20 | 


--------------------------------------------------------------------------------
/bottles/backend/wine/expand.py:
--------------------------------------------------------------------------------
 1 | from bottles.backend.logger import Logger
 2 | from bottles.backend.wine.wineprogram import WineProgram
 3 | 
 4 | logging = Logger()
 5 | 
 6 | 
 7 | class Expand(WineProgram):
 8 |     program = "Wine cabinet expander"
 9 |     command = "expand"
10 | 
11 |     def extract(self, cabinet: str, filename: str):
12 |         args = f"{cabinet} {filename}"
13 |         return self.launch(args=args, communicate=True, action_name="extract")
14 | 
15 |     def extract_all(self, cabinet: str, filenames: list):
16 |         for filename in filenames:
17 |             self.extract(cabinet, filename)
18 | 


--------------------------------------------------------------------------------
/bottles/backend/wine/explorer.py:
--------------------------------------------------------------------------------
 1 | from bottles.backend.logger import Logger
 2 | from bottles.backend.wine.wineprogram import WineProgram
 3 | 
 4 | logging = Logger()
 5 | 
 6 | 
 7 | class Explorer(WineProgram):
 8 |     program = "Wine Explorer"
 9 |     command = "explorer"
10 | 
11 |     def launch_desktop(
12 |         self,
13 |         desktop: str = "shell",
14 |         width: int = 0,
15 |         height: int = 0,
16 |         program: str | None = None,
17 |         args: str | None = None,
18 |         environment: dict | None = None,
19 |         cwd: str | None = None,
20 |     ):
21 |         _args = f"/desktop={desktop}"
22 | 
23 |         if width and height:
24 |             _args += f",{width}x{height}"
25 |         if program:
26 |             _args += f" {program}"
27 |         if args:
28 |             _args += args
29 | 
30 |         return self.launch(
31 |             args=_args,
32 |             communicate=True,
33 |             action_name="launch_desktop",
34 |             environment=environment,
35 |             cwd=cwd,
36 |         )
37 | 


--------------------------------------------------------------------------------
/bottles/backend/wine/hh.py:
--------------------------------------------------------------------------------
 1 | from bottles.backend.logger import Logger
 2 | from bottles.backend.wine.wineprogram import WineProgram
 3 | 
 4 | logging = Logger()
 5 | 
 6 | 
 7 | class Hh(WineProgram):
 8 |     program = "Wine HTML help viewer"
 9 |     command = "hh"
10 | 


--------------------------------------------------------------------------------
/bottles/backend/wine/icinfo.py:
--------------------------------------------------------------------------------
 1 | from bottles.backend.logger import Logger
 2 | from bottles.backend.wine.wineprogram import WineProgram
 3 | 
 4 | logging = Logger()
 5 | 
 6 | 
 7 | class Icinfo(WineProgram):
 8 |     program = "List installed video compressors"
 9 |     command = "icinfo"
10 | 
11 |     def get_output(self):
12 |         return self.launch(communicate=True, action_name="get_output")
13 | 
14 |     def get_dict(self):
15 |         res = self.launch(communicate=True, action_name="get_dict")
16 |         if not res.ready:
17 |             return {}
18 | 
19 |         res = [r.strip() for r in res.split("\n")[1:]]
20 |         _res = {}
21 |         _latest = None
22 | 
23 |         for r in res:
24 |             if not r:
25 |                 continue
26 |             k, v = r.split(":")
27 |             if r.startswith("vidc."):
28 |                 _latest = k
29 |                 _res[k] = {}
30 |                 _res[k]["name"] = k
31 |                 _res[k]["description"] = v
32 |             else:
33 |                 _res[_latest][k] = v
34 | 
35 |         return _res
36 | 


--------------------------------------------------------------------------------
/bottles/backend/wine/meson.build:
--------------------------------------------------------------------------------
 1 | pkgdatadir = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name())
 2 | winedir = join_paths(pkgdatadir, 'bottles/backend/wine')
 3 | 
 4 | bottles_sources = [
 5 |   '__init__.py',
 6 |   'catalogs.py',
 7 |   'winecommand.py',
 8 |   'wineprogram.py',
 9 |   'uninstaller.py',
10 |   'winecfg.py',
11 |   'winedbg.py',
12 |   'wineserver.py',
13 |   'wineboot.py',
14 |   'winepath.py',
15 |   'cmd.py',
16 |   'taskmgr.py',
17 |   'control.py',
18 |   'regedit.py',
19 |   'reg.py',
20 |   'regkeys.py',
21 |   'net.py',
22 |   'msiexec.py',
23 |   'executor.py',
24 |   'start.py',
25 |   'register.py',
26 |   'regsvr32.py',
27 |   'winebridge.py',
28 |   'explorer.py',
29 |   'drives.py',
30 |   'eject.py',
31 |   'expand.py',
32 |   'hh.py',
33 |   'icinfo.py',
34 |   'notepad.py',
35 |   'oleview.py',
36 |   'progman.py',
37 |   'rundll32.py',
38 |   'winefile.py',
39 |   'winhelp.py',
40 |   'xcopy.py',
41 | ]
42 | 
43 | install_data(bottles_sources, install_dir: winedir)
44 | 


--------------------------------------------------------------------------------
/bottles/backend/wine/net.py:
--------------------------------------------------------------------------------
 1 | from bottles.backend.logger import Logger
 2 | from bottles.backend.wine.wineprogram import WineProgram
 3 | 
 4 | logging = Logger()
 5 | 
 6 | 
 7 | class Net(WineProgram):
 8 |     program = "Wine Services manager"
 9 |     command = "net"
10 | 
11 |     def start(self, name: str | None = None):
12 |         args = "start"
13 | 
14 |         if name is not None:
15 |             args = f"start '{name}'"
16 | 
17 |         return self.launch(args=args, communicate=True, action_name="start")
18 | 
19 |     def stop(self, name: str | None = None):
20 |         args = "stop"
21 | 
22 |         if name is not None:
23 |             args = f"stop '{name}'"
24 | 
25 |         return self.launch(args=args, communicate=True, action_name="stop")
26 | 
27 |     def use(self, name: str | None = None):
28 |         # this command has no documentation, not tested yet
29 |         args = "use"
30 | 
31 |         if name is not None:
32 |             args = f"use '{name}'"
33 | 
34 |         return self.launch(args=args, communicate=True, action_name="use")
35 | 
36 |     def list(self):
37 |         services = []
38 |         res = self.start()
39 | 
40 |         if not res.ready:
41 |             return services
42 | 
43 |         lines = res.data.strip().splitlines()
44 |         for r in lines[1:]:
45 |             r = r[4:]
46 |             services.append(r)
47 | 
48 |         return services
49 | 


--------------------------------------------------------------------------------
/bottles/backend/wine/notepad.py:
--------------------------------------------------------------------------------
 1 | from bottles.backend.logger import Logger
 2 | from bottles.backend.wine.wineprogram import WineProgram
 3 | 
 4 | logging = Logger()
 5 | 
 6 | 
 7 | class Notepad(WineProgram):
 8 |     program = "Wine Notepad"
 9 |     command = "notepad"
10 | 
11 |     def open(self, path: str, as_ansi: bool = False, as_utf16: bool = False):
12 |         args = path
13 |         if as_ansi:
14 |             args = f"/a {path}"
15 |         elif as_utf16:
16 |             args = f"/w {path}"
17 |         return self.launch(args=args, communicate=True, action_name="open")
18 | 
19 |     def print(self, path: str, printer_name: str | None = None):
20 |         args = f"/p {path}"
21 |         if printer_name:
22 |             args = f"/pt {path} {printer_name}"
23 |         return self.launch(args=args, communicate=True, action_name="print")
24 | 


--------------------------------------------------------------------------------
/bottles/backend/wine/oleview.py:
--------------------------------------------------------------------------------
 1 | from bottles.backend.logger import Logger
 2 | from bottles.backend.wine.wineprogram import WineProgram
 3 | 
 4 | logging = Logger()
 5 | 
 6 | 
 7 | class Oleview(WineProgram):
 8 |     program = "OLE/COM object viewer"
 9 |     command = "oleview"
10 | 


--------------------------------------------------------------------------------
/bottles/backend/wine/progman.py:
--------------------------------------------------------------------------------
 1 | from bottles.backend.logger import Logger
 2 | from bottles.backend.wine.wineprogram import WineProgram
 3 | 
 4 | logging = Logger()
 5 | 
 6 | 
 7 | class Progman(WineProgram):
 8 |     program = "Wine Program Manager"
 9 |     command = "progman"
10 | 


--------------------------------------------------------------------------------
/bottles/backend/wine/regedit.py:
--------------------------------------------------------------------------------
 1 | from bottles.backend.logger import Logger
 2 | from bottles.backend.wine.wineprogram import WineProgram
 3 | 
 4 | logging = Logger()
 5 | 
 6 | 
 7 | class Regedit(WineProgram):
 8 |     program = "Wine Registry Editor"
 9 |     command = "regedit"
10 | 


--------------------------------------------------------------------------------
/bottles/backend/wine/regsvr32.py:
--------------------------------------------------------------------------------
 1 | from bottles.backend.logger import Logger
 2 | from bottles.backend.wine.wineprogram import WineProgram
 3 | 
 4 | logging = Logger()
 5 | 
 6 | 
 7 | class Regsvr32(WineProgram):
 8 |     program = "Wine DLL Registration Server"
 9 |     command = "regsvr32"
10 | 
11 |     def register(self, dll: str):
12 |         args = f"/s {dll}"
13 |         return self.launch(args=args, communicate=True, action_name="register")
14 | 
15 |     def unregister(self, dll: str):
16 |         args = f"/s /u {dll}"
17 |         return self.launch(args=args, communicate=True, action_name="unregister")
18 | 
19 |     def register_all(self, dlls: list):
20 |         for dll in dlls:
21 |             self.register(dll)
22 | 
23 |     def unregister_all(self, dlls: list):
24 |         for dll in dlls:
25 |             self.unregister(dll)
26 | 


--------------------------------------------------------------------------------
/bottles/backend/wine/rundll32.py:
--------------------------------------------------------------------------------
 1 | from bottles.backend.logger import Logger
 2 | from bottles.backend.wine.wineprogram import WineProgram
 3 | 
 4 | logging = Logger()
 5 | 
 6 | 
 7 | class RunDLL32(WineProgram):
 8 |     program = "32-bit DLLs loader and runner"
 9 |     command = "rundll32"
10 | 


--------------------------------------------------------------------------------
/bottles/backend/wine/start.py:
--------------------------------------------------------------------------------
 1 | from bottles.backend.logger import Logger
 2 | from bottles.backend.wine.wineprogram import WineProgram
 3 | from bottles.backend.wine.winepath import WinePath
 4 | 
 5 | logging = Logger()
 6 | 
 7 | 
 8 | class Start(WineProgram):
 9 |     program = "Wine Starter"
10 |     command = "start"
11 | 
12 |     def run(
13 |         self,
14 |         file: str,
15 |         terminal: bool = True,
16 |         args: str = "",
17 |         environment: dict | None = None,
18 |         pre_script: str | None = None,
19 |         post_script: str | None = None,
20 |         cwd: str | None = None,
21 |         midi_soundfont: str | None = None,
22 |     ):
23 |         winepath = WinePath(self.config)
24 | 
25 |         if winepath.is_unix(file):
26 |             # running unix paths with start is not recommended
27 |             # as it can miss important files due to the wrong
28 |             # current working directory
29 |             _args = f"/unix /wait {file}"
30 |         else:
31 |             if cwd not in [None, ""] and winepath.is_windows(cwd):
32 |                 _args = f"/wait /dir {cwd} {file}"
33 |             else:
34 |                 _args = f"/wait {file}"
35 | 
36 |         self.launch(
37 |             args=(_args, args),
38 |             communicate=True,
39 |             terminal=terminal,
40 |             environment=environment,
41 |             pre_script=pre_script,
42 |             post_script=post_script,
43 |             cwd=cwd,
44 |             midi_soundfont=midi_soundfont,
45 |             minimal=False,
46 |             action_name="run",
47 |         )
48 | 


--------------------------------------------------------------------------------
/bottles/backend/wine/taskmgr.py:
--------------------------------------------------------------------------------
 1 | from bottles.backend.logger import Logger
 2 | from bottles.backend.wine.wineprogram import WineProgram
 3 | 
 4 | logging = Logger()
 5 | 
 6 | 
 7 | class Taskmgr(WineProgram):
 8 |     program = "Wine Task Manager"
 9 |     command = "taskmgr"
10 | 


--------------------------------------------------------------------------------
/bottles/backend/wine/uninstaller.py:
--------------------------------------------------------------------------------
 1 | from bottles.backend.logger import Logger
 2 | from bottles.backend.wine.wineprogram import WineProgram
 3 | 
 4 | logging = Logger()
 5 | 
 6 | 
 7 | class Uninstaller(WineProgram):
 8 |     program = "Wine Uninstaller"
 9 |     command = "uninstaller"
10 | 
11 |     def get_uuid(self, name: str | None = None):
12 |         args = " --list"
13 | 
14 |         if name is not None:
15 |             args = f"--list | grep -i '{name}' | cut -f1 -d\\|"
16 | 
17 |         return self.launch(args=args, communicate=True, action_name="get_uuid")
18 | 
19 |     def from_uuid(self, uuid: str | None = None):
20 |         args = ""
21 | 
22 |         if uuid not in [None, ""]:
23 |             args = f"--remove {uuid}"
24 | 
25 |         return self.launch(args=args, action_name="from_uuid")
26 | 
27 |     def from_name(self, name: str):
28 |         res = self.get_uuid(name)
29 |         if not res.ready:
30 |             """
31 |             No UUID found, at this point it is safe to assume that the
32 |             program is not installed
33 |             ref: <https://github.com/bottlesdevs/Bottles/issues/2237>
34 |             """
35 |             return
36 |         uuid = res.data.strip()
37 |         for _uuid in uuid.splitlines():
38 |             self.from_uuid(_uuid)
39 | 


--------------------------------------------------------------------------------
/bottles/backend/wine/wineboot.py:
--------------------------------------------------------------------------------
 1 | from bottles.backend.logger import Logger
 2 | from bottles.backend.wine.wineprogram import WineProgram
 3 | from bottles.backend.wine.wineserver import WineServer
 4 | 
 5 | import os
 6 | import signal
 7 | 
 8 | logging = Logger()
 9 | 
10 | 
11 | class WineBoot(WineProgram):
12 |     program = "Wine Runtime tool"
13 |     command = "wineboot"
14 | 
15 |     def send_status(self, status: int):
16 |         if status == -2:
17 |             return self.nv_stop_all_processes()
18 | 
19 |         states = {-1: "force", 0: "-k", 1: "-r", 2: "-s", 3: "-u", 4: "-i"}
20 |         envs = {
21 |             "WINEDEBUG": "-all",
22 |             "DISPLAY": ":3.0",
23 |             "WINEDLLOVERRIDES": "winemenubuilder=d",
24 |         }
25 | 
26 |         if status == 0 and not WineServer(self.config).is_alive():
27 |             logging.info("There is no running wineserver.")
28 |             return
29 | 
30 |         if status in states:
31 |             args = f"{states[status]} /nogui"
32 |             self.launch(
33 |                 args=args,
34 |                 environment=envs,
35 |                 communicate=True,
36 |                 action_name=f"send_status({states[status]})",
37 |             )
38 |         else:
39 |             raise ValueError(f"[{status}] is not a valid status for wineboot!")
40 | 
41 |     def force(self):
42 |         return self.send_status(-1)
43 | 
44 |     def kill(self, force_if_stalled: bool = False):
45 |         self.send_status(0)
46 | 
47 |         if force_if_stalled:
48 |             wineserver = WineServer(self.config)
49 |             if wineserver.is_alive():
50 |                 wineserver.force_kill()
51 |                 wineserver.wait()
52 | 
53 |     def restart(self):
54 |         return self.send_status(1)
55 | 
56 |     def shutdown(self):
57 |         return self.send_status(2)
58 | 
59 |     def update(self):
60 |         return self.send_status(3)
61 | 
62 |     def init(self):
63 |         return self.send_status(4)
64 | 
65 |     def nv_stop_all_processes(self):
66 |         try:
67 |             for pid in os.listdir("/proc"):
68 |                 if pid.isdigit():
69 |                     try:
70 |                         with open(f"/proc/{pid}/environ") as env_file:
71 |                             env_vars = env_file.read()
72 |                             if f"BOTTLE={self.config.Path}" in env_vars:
73 |                                 os.kill(int(pid), signal.SIGTERM)
74 |                                 logging.info(f"Killed process with PID {pid}.")
75 |                     except (FileNotFoundError, ProcessLookupError):
76 |                         continue
77 |         except Exception as e:
78 |             logging.error(f"Error stopping processes: {e}")
79 | 


--------------------------------------------------------------------------------
/bottles/backend/wine/winebridge.py:
--------------------------------------------------------------------------------
 1 | import os
 2 | 
 3 | from bottles.backend.logger import Logger
 4 | from bottles.backend.wine.wineprogram import WineProgram
 5 | from bottles.backend.wine.wineserver import WineServer
 6 | 
 7 | logging = Logger()
 8 | 
 9 | 
10 | class WineBridge(WineProgram):
11 |     program = "Wine Bridge"
12 |     command = "WineBridge.exe"
13 |     is_internal = True
14 |     internal_path = "winebridge"
15 | 
16 |     def __wineserver_status(self):
17 |         return WineServer(self.config).is_alive()
18 | 
19 |     def is_available(self):
20 |         if os.path.isfile(self.get_command()):
21 |             logging.info(f"{self.program} is available.")
22 |             return True
23 |         return False
24 | 
25 |     def get_procs(self):
26 |         args = "getProcs"
27 |         processes = []
28 | 
29 |         if not self.__wineserver_status:
30 |             return processes
31 | 
32 |         res = self.launch(args=args, communicate=True, action_name="get_procs")
33 |         if not res.ready:
34 |             return processes
35 | 
36 |         lines = res.data.split("\n")
37 |         for r in lines:
38 |             if r in ["", "\r"]:
39 |                 continue
40 | 
41 |             r = r.split("|")
42 | 
43 |             if len(r) < 3:
44 |                 continue
45 | 
46 |             processes.append(
47 |                 {
48 |                     "pid": r[1],
49 |                     "threads": r[2],
50 |                     "name": r[0],
51 |                     # "parent": r[3]
52 |                 }
53 |             )
54 | 
55 |         return processes
56 | 
57 |     def kill_proc(self, pid: str):
58 |         args = f"killProc {pid}"
59 |         return self.launch(args=args, communicate=True, action_name="kill_proc")
60 | 
61 |     def kill_proc_by_name(self, name: str):
62 |         args = f"killProcByName {name}"
63 |         return self.launch(args=args, communicate=True, action_name="kill_proc_by_name")
64 | 
65 |     def run_exe(self, exec_path: str):
66 |         args = f"runExe {exec_path}"
67 |         return self.launch(args=args, communicate=True, action_name="run_exe")
68 | 


--------------------------------------------------------------------------------
/bottles/backend/wine/winecfg.py:
--------------------------------------------------------------------------------
 1 | import os
 2 | 
 3 | from bottles.backend.logger import Logger
 4 | from bottles.backend.wine.wineprogram import WineProgram
 5 | from bottles.backend.wine.winedbg import WineDbg
 6 | from bottles.backend.wine.wineboot import WineBoot
 7 | 
 8 | logging = Logger()
 9 | 
10 | 
11 | class WineCfg(WineProgram):
12 |     program = "Wine Configuration"
13 |     command = "winecfg"
14 | 
15 |     def set_windows_version(self, version):
16 |         logging.info(f"Setting Windows version to {version}")
17 | 
18 |         winedbg = WineDbg(self.config)
19 |         wineboot = WineBoot(self.config)
20 | 
21 |         wineboot.kill()
22 | 
23 |         res = self.launch(
24 |             args=f"-v {version}",
25 |             communicate=True,
26 |             environment={
27 |                 "DISPLAY": os.environ.get("DISPLAY", ":0"),
28 |                 "WAYLAND_DISPLAY": os.environ.get("WAYLAND_DISPLAY", ""),
29 |             },
30 |             action_name="set_windows_version",
31 |         )
32 | 
33 |         winedbg.wait_for_process("winecfg")
34 |         wineboot.restart()
35 | 
36 |         return res
37 | 


--------------------------------------------------------------------------------
/bottles/backend/wine/winefile.py:
--------------------------------------------------------------------------------
 1 | from bottles.backend.logger import Logger
 2 | from bottles.backend.wine.wineprogram import WineProgram
 3 | 
 4 | logging = Logger()
 5 | 
 6 | 
 7 | class WineFile(WineProgram):
 8 |     program = "Wine File Explorer"
 9 |     command = "winefile"
10 | 
11 |     def open_path(self, path: str = "C:\\\\"):
12 |         args = path
13 |         return self.launch(args=args, communicate=True, action_name="open_path")
14 | 


--------------------------------------------------------------------------------
/bottles/backend/wine/winepath.py:
--------------------------------------------------------------------------------
 1 | import re
 2 | from functools import lru_cache
 3 | 
 4 | from bottles.backend.logger import Logger
 5 | from bottles.backend.wine.wineprogram import WineProgram
 6 | from bottles.backend.utils.manager import ManagerUtils
 7 | 
 8 | logging = Logger()
 9 | 
10 | 
11 | class WinePath(WineProgram):
12 |     program = "Wine path converter"
13 |     command = "winepath"
14 | 
15 |     @staticmethod
16 |     @lru_cache
17 |     def is_windows(path: str):
18 |         return ":" in path or "\\" in path
19 | 
20 |     @staticmethod
21 |     @lru_cache
22 |     def is_unix(path: str):
23 |         return not WinePath.is_windows(path)
24 | 
25 |     @staticmethod
26 |     @lru_cache
27 |     def __clean_path(path):
28 |         return path.replace("\n", " ").replace("\r", " ").replace("\t", " ").strip()
29 | 
30 |     @lru_cache
31 |     def to_unix(self, path: str, native: bool = False):
32 |         if native:
33 |             bottle_path = ManagerUtils.get_bottle_path(self.config)
34 |             path = path.replace("\\", "/")
35 |             path = path.replace(
36 |                 path[0:2], f"{bottle_path}/dosdevices/{path[0:2].lower()}"
37 |             )
38 |             return self.__clean_path(path)
39 |         args = f"--unix '{path}'"
40 |         res = self.launch(args=args, communicate=True, action_name="--unix")
41 |         return self.__clean_path(res.data)
42 | 
43 |     @lru_cache
44 |     def to_windows(self, path: str, native: bool = False):
45 |         if native:
46 |             bottle_path = ManagerUtils.get_bottle_path(self.config)
47 |             if "/drive_" in path:
48 |                 drive = re.search(r"drive_([a-z])/", path.lower()).group(1)
49 |                 path = path.replace(
50 |                     f"{bottle_path}/drive_{drive.lower()}", f"{drive.upper()}:"
51 |                 )
52 |             elif "/dosdevices" in path:
53 |                 drive = re.search(r"dosdevices/([a-z]):", path.lower()).group(1)
54 |                 path = path.replace(
55 |                     f"{bottle_path}/dosdevices/{drive.lower()}", f"{drive.upper()}:"
56 |                 )
57 |             path = path.replace("/", "\\")
58 |             return self.__clean_path(path)
59 | 
60 |         args = f"--windows '{path}'"
61 |         res = self.launch(args=args, communicate=True, action_name="--windows")
62 |         return self.__clean_path(res.data)
63 | 
64 |     @lru_cache
65 |     def to_long(self, path: str):
66 |         args = f"--long '{path}'"
67 |         res = self.launch(args=args, communicate=True, action_name="--long")
68 |         return self.__clean_path(res.data)
69 | 
70 |     @lru_cache
71 |     def to_short(self, path: str):
72 |         args = f"--short '{path}'"
73 |         res = self.launch(args=args, communicate=True, action_name="--short")
74 |         return self.__clean_path(res.data)
75 | 


--------------------------------------------------------------------------------
/bottles/backend/wine/wineprogram.py:
--------------------------------------------------------------------------------
 1 | import os
 2 | 
 3 | from bottles.backend.logger import Logger
 4 | from bottles.backend.globals import Paths
 5 | from bottles.backend.models.config import BottleConfig
 6 | from bottles.backend.wine.winecommand import WineCommand
 7 | 
 8 | logging = Logger()
 9 | 
10 | 
11 | class WineProgram:
12 |     program: str = "unknown"
13 |     command: str = ""
14 |     config: BottleConfig
15 |     colors: str = "default"
16 |     is_internal: bool = False
17 |     internal_path: str = ""
18 | 
19 |     def __init__(self, config: BottleConfig, silent=False):
20 |         if not isinstance(config, BottleConfig):
21 |             raise TypeError(
22 |                 "config should be BottleConfig type, but it was %s" % type(config)
23 |             )
24 |         self.config = config
25 |         self.silent = silent
26 | 
27 |     def get_command(self, args: str | None = None):
28 |         command = self.command
29 | 
30 |         if self.is_internal:
31 |             command = os.path.join(Paths.base, self.internal_path, command)
32 | 
33 |         if args is not None:
34 |             command += f" {args}"
35 | 
36 |         return command
37 | 
38 |     def launch(
39 |         self,
40 |         args: tuple | str | None = None,
41 |         terminal: bool = False,
42 |         minimal: bool = True,
43 |         communicate: bool = False,
44 |         environment: dict | None = None,
45 |         pre_script: str | None = None,
46 |         post_script: str | None = None,
47 |         cwd: str | None = None,
48 |         midi_soundfont: str | None = None,
49 |         action_name: str = "launch",
50 |     ):
51 |         if environment is None:
52 |             environment = {}
53 | 
54 |         if not self.silent:
55 |             logging.info(f"Using {self.program} -- {action_name}")
56 | 
57 |         if isinstance(args, tuple):
58 |             wineprogram_args = args[0]
59 |             program_args = args[1]
60 |         else:
61 |             wineprogram_args = args
62 |             program_args = None
63 | 
64 |         command = self.get_command(wineprogram_args)
65 |         res = WineCommand(
66 |             self.config,
67 |             command=command,
68 |             terminal=terminal,
69 |             minimal=minimal,
70 |             communicate=communicate,
71 |             colors=self.colors,
72 |             environment=environment,
73 |             pre_script=pre_script,
74 |             post_script=post_script,
75 |             cwd=cwd,
76 |             midi_soundfont=midi_soundfont,
77 |             arguments=program_args,
78 |         )
79 | 
80 |         # logging.info("Executing command:", res.command)
81 |         res = res.run()
82 |         return res
83 | 
84 |     def launch_terminal(self, args: str | None = None):
85 |         self.launch(args=args, terminal=True, action_name="launch_terminal")
86 | 
87 |     def launch_minimal(self, args: str | None = None):
88 |         self.launch(args=args, minimal=True, action_name="launch_minimal")
89 | 


--------------------------------------------------------------------------------
/bottles/backend/wine/winhelp.py:
--------------------------------------------------------------------------------
 1 | from bottles.backend.logger import Logger
 2 | from bottles.backend.wine.wineprogram import WineProgram
 3 | 
 4 | logging = Logger()
 5 | 
 6 | 
 7 | class WinHelp(WineProgram):
 8 |     program = "Microsoft help file viewer"
 9 |     command = "winhelp"
10 | 


--------------------------------------------------------------------------------
/bottles/backend/wine/xcopy.py:
--------------------------------------------------------------------------------
 1 | from datetime import datetime
 2 | 
 3 | from bottles.backend.logger import Logger
 4 | from bottles.backend.wine.wineprogram import WineProgram
 5 | 
 6 | logging = Logger()
 7 | 
 8 | 
 9 | class Xcopy(WineProgram):
10 |     program = "Wine Xcopy implementation"
11 |     command = "xcopy"
12 | 
13 |     def copy(
14 |         self,
15 |         source: str,
16 |         dest: str,
17 |         dir_and_subs: bool = False,
18 |         keep_empty_dirs: bool = False,
19 |         quiet: bool = False,
20 |         full_log: bool = False,
21 |         simulate: bool = False,
22 |         ask_confirm: bool = False,
23 |         only_struct: bool = False,
24 |         no_overwrite_notify: bool = False,
25 |         use_short_names: bool = False,
26 |         only_existing_in_dest: bool = False,
27 |         overwrite_read_only_files: bool = False,
28 |         include_hidden_and_sys_files: bool = False,
29 |         continue_if_error: bool = False,
30 |         copy_attributes: bool = False,
31 |         after_date: datetime | None = None,
32 |     ):
33 |         args = f"{source} {dest} /i"
34 | 
35 |         if dir_and_subs:
36 |             args += "/s"
37 |         if keep_empty_dirs:
38 |             args += "/e"
39 |         if quiet:
40 |             args += "/q"
41 |         if full_log:
42 |             args += "/f"
43 |         if simulate:
44 |             args += "/l"
45 |         if ask_confirm:
46 |             args += "/w"
47 |         if only_struct:
48 |             args += "/t"
49 |         if no_overwrite_notify:
50 |             args += "/y"
51 |         if use_short_names:
52 |             args += "/n"
53 |         if only_existing_in_dest:
54 |             args += "/u"
55 |         if overwrite_read_only_files:
56 |             args += "/r"
57 |         if include_hidden_and_sys_files:
58 |             args += "/h"
59 |         if continue_if_error:
60 |             args += "/c"
61 |         if copy_attributes:
62 |             args += "/a"
63 |         if after_date:
64 |             if isinstance(after_date, datetime):
65 |                 args += f"/d:{after_date.strftime('%m-%d-%Y')}"
66 | 
67 |         return self.launch(args=args, communicate=True, action_name="start")
68 | 


--------------------------------------------------------------------------------
/bottles/frontend/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bottlesdevs/Bottles/5020d62c2e086de3f407d8667b3d8b4b5ae644f5/bottles/frontend/__init__.py


--------------------------------------------------------------------------------
/bottles/frontend/bottle-details-view.blp:
--------------------------------------------------------------------------------
  1 | using Gtk 4.0;
  2 | using Adw 1;
  3 | 
  4 | template $BottleDetailsView: Adw.Bin {
  5 |   Adw.Leaflet leaflet {
  6 |     can-navigate-back: true;
  7 |     can-unfold: false;
  8 |     hexpand: true;
  9 | 
 10 |     Box {
 11 |       orientation: vertical;
 12 | 
 13 |       Adw.HeaderBar sidebar_headerbar {
 14 |         show-end-title-buttons: false;
 15 | 
 16 |         title-widget: Adw.WindowTitle sidebar_title {
 17 |           title: _("Details");
 18 |         };
 19 | 
 20 |         [start]
 21 |         Button btn_back {
 22 |           icon-name: "go-previous-symbolic";
 23 |           tooltip-text: _("Go Back");
 24 |         }
 25 | 
 26 |         [end]
 27 |         Box default_actions {}
 28 |       }
 29 | 
 30 |       Box default_view {}
 31 |     }
 32 | 
 33 |     Adw.LeafletPage {
 34 |       navigatable: false;
 35 | 
 36 |       child: Separator panel_separator {
 37 |         orientation: vertical;
 38 | 
 39 |         styles [
 40 |           "sidebar",
 41 |         ]
 42 |       };
 43 |     }
 44 | 
 45 |     Box content {
 46 |       orientation: vertical;
 47 | 
 48 |       Adw.HeaderBar content_headerbar {
 49 |         show-start-title-buttons: false;
 50 | 
 51 |         title-widget: Adw.WindowTitle content_title {};
 52 | 
 53 |         [start]
 54 |         Button btn_back_sidebar {
 55 |           icon-name: "go-previous-symbolic";
 56 |           tooltip-text: _("Go Back");
 57 |           visible: false;
 58 |         }
 59 | 
 60 |         [end]
 61 |         Box box_actions {}
 62 | 
 63 |         [end]
 64 |         MenuButton btn_operations {
 65 |           visible: false;
 66 |           tooltip-text: _("Operations");
 67 |           popover: pop_tasks;
 68 | 
 69 |           Spinner spinner_tasks {}
 70 | 
 71 |           styles [
 72 |             "flat",
 73 |           ]
 74 |         }
 75 |       }
 76 | 
 77 |       Stack stack_bottle {
 78 |         transition-type: crossfade;
 79 |         hexpand: true;
 80 |         vexpand: true;
 81 |       }
 82 |     }
 83 |   }
 84 | }
 85 | 
 86 | Popover pop_tasks {
 87 |   Box {
 88 |     orientation: vertical;
 89 |     spacing: 3;
 90 | 
 91 |     Box {
 92 |       orientation: vertical;
 93 | 
 94 |       ListBox list_tasks {
 95 |         selection-mode: none;
 96 | 
 97 |         styles [
 98 |           "content",
 99 |         ]
100 |       }
101 |     }
102 |   }
103 | }
104 | 


--------------------------------------------------------------------------------
/bottles/frontend/bottle-picker-dialog.blp:
--------------------------------------------------------------------------------
 1 | using Gtk 4.0;
 2 | using Adw 1;
 3 | 
 4 | template $BottlePickerDialog: Adw.ApplicationWindow {
 5 |   title: _("Select Bottle");
 6 |   default-width: 450;
 7 |   default-height: 450;
 8 | 
 9 |   Box {
10 |     orientation: vertical;
11 | 
12 |     Adw.HeaderBar {
13 |       show-end-title-buttons: false;
14 | 
15 |       [start]
16 |       Button btn_cancel {
17 |         label: _("Cancel");
18 |       }
19 | 
20 |       [end]
21 |       Button btn_select {
22 |         label: _("Select");
23 | 
24 |         styles [
25 |           "suggested-action",
26 |         ]
27 |       }
28 |     }
29 | 
30 |     ScrolledWindow {
31 |       hexpand: true;
32 |       vexpand: true;
33 | 
34 |       ListBox list_bottles {}
35 |     }
36 | 
37 |     Button btn_open {
38 |       label: _("Create New Bottle");
39 |       margin-top: 6;
40 |       margin-start: 6;
41 |       margin-bottom: 6;
42 |       margin-end: 6;
43 |     }
44 |   }
45 | }
46 | 


--------------------------------------------------------------------------------
/bottles/frontend/bottle-row.blp:
--------------------------------------------------------------------------------
 1 | using Gtk 4.0;
 2 | using Adw 1;
 3 | 
 4 | template $BottleRow: Adw.ActionRow {
 5 |   activatable: true;
 6 |   use-markup: false;
 7 | 
 8 |   Adw.WrapBox wrap_box {
 9 |     valign: center;
10 | 
11 |     styles [
12 |       "tag",
13 |       "caption",
14 |     ]
15 |   }
16 | 
17 |   Button button_run {
18 |     halign: center;
19 |     valign: center;
20 |     icon-name: "system-run-symbolic";
21 | 
22 |     styles [
23 |       "flat",
24 |     ]
25 |   }
26 | 
27 |   Image {
28 |     icon-name: "go-next-symbolic";
29 |   }
30 | }
31 | 


--------------------------------------------------------------------------------
/bottles/frontend/bottles-list-view.blp:
--------------------------------------------------------------------------------
 1 | using Gtk 4.0;
 2 | using Adw 1;
 3 | 
 4 | template $BottlesListView: Adw.Bin {
 5 |   ScrolledWindow {
 6 |     Box {
 7 |       hexpand: true;
 8 |       vexpand: true;
 9 |       orientation: vertical;
10 | 
11 |       SearchBar search_bar {
12 |         SearchEntry entry_search {
13 |           placeholder-text: _("Search your bottles…");
14 |         }
15 |       }
16 | 
17 |       Adw.PreferencesPage pref_page {
18 |         Adw.PreferencesGroup group_bottles {
19 |           ListBox list_bottles {
20 |             selection-mode: none;
21 | 
22 |             styles [
23 |               "boxed-list",
24 |             ]
25 |           }
26 |         }
27 | 
28 |         Adw.PreferencesGroup group_steam {
29 |           title: _("Steam Proton");
30 | 
31 |           ListBox list_steam {
32 |             selection-mode: none;
33 | 
34 |             styles [
35 |               "boxed-list",
36 |             ]
37 |           }
38 |         }
39 |       }
40 | 
41 |       Adw.StatusPage bottle_status {
42 |         title: _("Bottles");
43 |         hexpand: true;
44 |         vexpand: true;
45 | 
46 |         child: Button btn_create {
47 |           valign: center;
48 |           halign: center;
49 |           label: _("Create New Bottle…");
50 | 
51 |           styles [
52 |             "suggested-action",
53 |             "pill",
54 |           ]
55 |         };
56 |       }
57 | 
58 |       Adw.StatusPage no_bottles_found {
59 |         visible: false;
60 |         icon-name: "system-search-symbolic";
61 |         title: _("No Results Found");
62 |         description: _("Try a different search.");
63 |         hexpand: true;
64 |         vexpand: true;
65 |       }
66 |     }
67 |   }
68 | }
69 | 


--------------------------------------------------------------------------------
/bottles/frontend/bottles.py:
--------------------------------------------------------------------------------
 1 | #!@PYTHON@
 2 | 
 3 | # bottles.in
 4 | #
 5 | # Copyright 2020 brombinmirko <send@mirko.pm>
 6 | #
 7 | # This program is free software: you can redistribute it and/or modify
 8 | # it under the terms of the GNU General Public License as published by
 9 | # the Free Software Foundation, either version 3 of the License, or
10 | # (at your option) any later version.
11 | #
12 | # This program is distributed in the hope that it will be useful,
13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 | # GNU General Public License for more details.
16 | #
17 | # You should have received a copy of the GNU General Public License
18 | # along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 | 
20 | import os
21 | import sys
22 | import signal
23 | import gettext
24 | 
25 | APP_VERSION = "@APP_VERSION@"
26 | pkgdatadir = "@pkgdatadir@"
27 | localedir = "@localedir@"
28 | # noinspection DuplicatedCode
29 | data_gresource_path = f"{pkgdatadir}/data.gresource"
30 | bottles_gresource_path = f"{pkgdatadir}/bottles.gresource"
31 | sys.path.insert(1, pkgdatadir)
32 | 
33 | # Remove GTK_THEME variable to prevent breakages
34 | # REF: https://github.com/bottlesdevs/Bottles/pull/2886
35 | os.unsetenv("GTK_THEME")
36 | 
37 | signal.signal(signal.SIGINT, signal.SIG_DFL)
38 | gettext.install("bottles", localedir)
39 | 
40 | if __name__ == "__main__":
41 |     from gi.repository import Gio
42 | 
43 |     data_resource = Gio.Resource.load(data_gresource_path)
44 |     bottles_resource = Gio.Resource.load(bottles_gresource_path)
45 |     # noinspection PyProtectedMember
46 |     data_resource._register()
47 |     bottles_resource._register()
48 | 
49 |     from bottles.frontend import main
50 | 
51 |     sys.exit(main.main(APP_VERSION))
52 | 


--------------------------------------------------------------------------------
/bottles/frontend/check-row.blp:
--------------------------------------------------------------------------------
 1 | using Gtk 4.0;
 2 | using Adw 1;
 3 | 
 4 | template $CheckRow: Adw.ActionRow {
 5 |   activatable-widget: check_button;
 6 |   active: bind check_button.active bidirectional;
 7 | 
 8 |   [prefix]
 9 |   CheckButton check_button {
10 |     valign: center;
11 |     can-focus: false;
12 |     can-target: false;
13 |   }
14 | }
15 | 


--------------------------------------------------------------------------------
/bottles/frontend/common.py:
--------------------------------------------------------------------------------
 1 | # utils.py
 2 | #
 3 | # Copyright 2022 brombinmirko <send@mirko.pm>
 4 | #
 5 | # This program is free software: you can redistribute it and/or modify
 6 | # it under the terms of the GNU General Public License as published by
 7 | # the Free Software Foundation, in version 3 of the License.
 8 | #
 9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 | #
17 | 
18 | import webbrowser
19 | 
20 | 
21 | def open_doc_url(widget, page):
22 |     webbrowser.open_new_tab(f"https://docs.usebottles.com/{page}")
23 | 


--------------------------------------------------------------------------------
/bottles/frontend/component-entry-row.blp:
--------------------------------------------------------------------------------
 1 | using Gtk 4.0;
 2 | using Adw 1;
 3 | 
 4 | template $ComponentEntryRow: Adw.ActionRow {
 5 |   title: _("Component version");
 6 | 
 7 |   Spinner spinner {
 8 |     visible: false;
 9 |   }
10 | 
11 |   Button btn_remove {
12 |     visible: false;
13 |     tooltip-text: _("Uninstall");
14 |     valign: center;
15 |     icon-name: "user-trash-symbolic";
16 | 
17 |     styles [
18 |       "flat",
19 |     ]
20 |   }
21 | 
22 |   Button btn_browse {
23 |     visible: false;
24 |     tooltip-text: _("Browse Files");
25 |     valign: center;
26 |     icon-name: "folder-open-symbolic";
27 | 
28 |     styles [
29 |       "flat",
30 |     ]
31 |   }
32 | 
33 |   Button btn_err {
34 |     visible: false;
35 |     tooltip-text: _("The installation failed. This may be due to a repository error, partial download or checksum mismatch. Press to try again.");
36 |     valign: center;
37 |     icon-name: "emblem-important-symbolic";
38 | 
39 |     styles [
40 |       "flat",
41 |     ]
42 |   }
43 | 
44 |   Button btn_download {
45 |     visible: false;
46 |     tooltip-text: _("Download & Install");
47 |     valign: center;
48 |     icon-name: "document-save-symbolic";
49 | 
50 |     styles [
51 |       "flat",
52 |     ]
53 |   }
54 | 
55 |   Box box_download_status {
56 |     visible: false;
57 | 
58 |     Label label_task_status {
59 |       label: _("0%");
60 |     }
61 | 
62 |     Image {
63 |       icon-name: "document-save-symbolic";
64 |     }
65 |   }
66 | 
67 |   Button btn_cancel {
68 |     visible: false;
69 |     valign: center;
70 |     icon-name: "edit-delete-symbolic";
71 | 
72 |     styles [
73 |       "flat",
74 |     ]
75 |   }
76 | }
77 | 


--------------------------------------------------------------------------------
/bottles/frontend/crash-report-dialog.blp:
--------------------------------------------------------------------------------
  1 | using Gtk 4.0;
  2 | using Adw 1;
  3 | 
  4 | template $CrashReportDialog: Adw.Window {
  5 |   resizable: false;
  6 |   deletable: true;
  7 |   modal: true;
  8 |   default-width: 550;
  9 |   title: _("Bottles Crash Report");
 10 | 
 11 |   Box {
 12 |     orientation: vertical;
 13 | 
 14 |     Adw.HeaderBar {
 15 |       show-start-title-buttons: false;
 16 |       show-end-title-buttons: false;
 17 | 
 18 |       Button btn_cancel {
 19 |         label: _("_Cancel");
 20 |         use-underline: true;
 21 |         action-name: "window.close";
 22 |       }
 23 | 
 24 |       [end]
 25 |       Button btn_send {
 26 |         label: _("Send Report");
 27 |         sensitive: false;
 28 | 
 29 |         styles [
 30 |           "suggested-action",
 31 |         ]
 32 |       }
 33 |     }
 34 | 
 35 |     Box {
 36 |       spacing: 10;
 37 |       margin-top: 10;
 38 |       margin-start: 10;
 39 |       margin-bottom: 10;
 40 |       margin-end: 10;
 41 |       orientation: vertical;
 42 | 
 43 |       Label {
 44 |         halign: start;
 45 |         label: _("Bottles crashed last time. Please fill out a report attaching the following traceback to help us identify the problem preventing it from happening again.");
 46 |         wrap: true;
 47 |       }
 48 | 
 49 |       Label label_output {
 50 |         hexpand: true;
 51 |         wrap: true;
 52 |         selectable: true;
 53 |         max-width-chars: 78;
 54 |         xalign: 0;
 55 | 
 56 |         styles [
 57 |           "monospace",
 58 |           "terminal",
 59 |           "card",
 60 |         ]
 61 |       }
 62 | 
 63 |       Box box_related {
 64 |         visible: false;
 65 |         spacing: 10;
 66 |         orientation: vertical;
 67 | 
 68 |         Box {
 69 |           Image {
 70 |             icon-name: "dialog-warning";
 71 |           }
 72 | 
 73 |           Label label_notice {
 74 |             halign: start;
 75 |             label: _("We found one or more similar (or identical) reports. Please make sure to check carefully that it has not already been reported before submitting a new one. Each report requires effort on the part of the developers to diagnose, please respect their work and make sure you don\'t post duplicates.");
 76 |             wrap: true;
 77 |             max-width-chars: 78;
 78 |           }
 79 | 
 80 |           styles [
 81 |             "custom_warning",
 82 |           ]
 83 |         }
 84 | 
 85 |         Adw.PreferencesGroup list_reports {}
 86 | 
 87 |         Expander {
 88 |           CheckButton check_unlock_send {
 89 |             label: _("I still want to report.");
 90 |             halign: start;
 91 |           }
 92 | 
 93 |           [label]
 94 |           Label {
 95 |             label: _("Advanced options");
 96 |           }
 97 |         }
 98 |       }
 99 |     }
100 |   }
101 | }
102 | 


--------------------------------------------------------------------------------
/bottles/frontend/dependencies-check-dialog.blp:
--------------------------------------------------------------------------------
 1 | using Gtk 4.0;
 2 | using Adw 1;
 3 | 
 4 | template $DependenciesCheckDialog: Adw.Window {
 5 |   modal: true;
 6 |   deletable: true;
 7 |   resizable: false;
 8 |   default-width: 550;
 9 |   default-height: 500;
10 | 
11 |   Adw.Clamp {
12 |     Adw.StatusPage {
13 |       icon-name: "dialog-warning-symbolic";
14 |       title: _("Incomplete package");
15 |       description: _("This version of Bottles does not seem to provide all the necessary core dependencies, please contact the package maintainer or use an official version.");
16 | 
17 |       Button btn_quit {
18 |         halign: center;
19 |         label: _("Quit");
20 | 
21 |         styles [
22 |           "pill",
23 |           "suggested-action",
24 |         ]
25 |       }
26 |     }
27 |   }
28 | }
29 | 


--------------------------------------------------------------------------------
/bottles/frontend/dependencies_check_dialog.py:
--------------------------------------------------------------------------------
 1 | # dependencies_check_dialog.py
 2 | #
 3 | # Copyright 2025 The Bottles Contributors
 4 | #
 5 | # This program is free software: you can redistribute it and/or modify
 6 | # it under the terms of the GNU General Public License as published by
 7 | # the Free Software Foundation, in version 3 of the License.
 8 | #
 9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 | #
17 | 
18 | from gi.repository import Gtk, Adw
19 | 
20 | 
21 | @Gtk.Template(resource_path="/com/usebottles/bottles/dependencies-check-dialog.ui")
22 | class DependenciesCheckDialog(Adw.Window):
23 |     __gtype_name__ = "DependenciesCheckDialog"
24 | 
25 |     # region widgets
26 |     btn_quit = Gtk.Template.Child()
27 | 
28 |     # endregion
29 | 
30 |     def __init__(self, window, **kwargs):
31 |         super().__init__(**kwargs)
32 |         self.set_transient_for(window)
33 |         self.window = window
34 | 
35 |         self.btn_quit.connect("clicked", self.__quit)
36 | 
37 |     def __quit(self, *_args):
38 |         self.window.proper_close()
39 | 


--------------------------------------------------------------------------------
/bottles/frontend/dependency-entry-row.blp:
--------------------------------------------------------------------------------
  1 | using Gtk 4.0;
  2 | using Adw 1;
  3 | 
  4 | Popover pop_actions {
  5 |   styles [
  6 |     "menu",
  7 |   ]
  8 | 
  9 |   Box {
 10 |     margin-top: 6;
 11 |     margin-bottom: 6;
 12 |     margin-start: 6;
 13 |     margin-end: 6;
 14 |     orientation: vertical;
 15 | 
 16 |     $GtkModelButton btn_manifest {
 17 |       text: _("Show Manifest");
 18 |     }
 19 | 
 20 |     $GtkModelButton btn_license {
 21 |       text: _("License");
 22 |     }
 23 | 
 24 |     $GtkModelButton btn_reinstall {
 25 |       text: _("Reinstall");
 26 |       visible: false;
 27 |     }
 28 | 
 29 |     $GtkModelButton btn_remove {
 30 |       text: _("Uninstall");
 31 |     }
 32 | 
 33 |     Separator {}
 34 | 
 35 |     $GtkModelButton btn_report {
 36 |       text: _("Report a Bug…");
 37 |     }
 38 |   }
 39 | }
 40 | 
 41 | template $DependencyEntryRow: Adw.ActionRow {
 42 |   title: _("Dependency name");
 43 |   activatable-widget: btn_install;
 44 |   subtitle: _("Dependency description");
 45 | 
 46 |   Box box_actions {
 47 |     spacing: 6;
 48 | 
 49 |     Label label_category {
 50 |       valign: center;
 51 |       label: _("Category");
 52 | 
 53 |       styles [
 54 |         "tag",
 55 |         "caption",
 56 |       ]
 57 |     }
 58 | 
 59 |     Spinner spinner {
 60 |       visible: false;
 61 |     }
 62 | 
 63 |     Button btn_install {
 64 |       tooltip-text: _("Download & Install this Dependency");
 65 |       valign: center;
 66 | 
 67 |       Image {
 68 |         icon-name: "document-save-symbolic";
 69 |       }
 70 | 
 71 |       styles [
 72 |         "flat",
 73 |       ]
 74 |     }
 75 | 
 76 |     Button btn_err {
 77 |       visible: false;
 78 |       sensitive: false;
 79 |       tooltip-text: _("An installation error occurred. Restart Bottles to read the Crash Report or run it via terminal to read the output.");
 80 |       valign: center;
 81 |       icon-name: "emblem-important-symbolic";
 82 |     }
 83 | 
 84 |     Separator {
 85 |       margin-top: 12;
 86 |       margin-bottom: 12;
 87 |     }
 88 | 
 89 |     MenuButton {
 90 |       valign: center;
 91 |       popover: pop_actions;
 92 |       icon-name: "view-more-symbolic";
 93 |       tooltip-text: _("Dependency Menu");
 94 | 
 95 |       styles [
 96 |         "flat",
 97 |       ]
 98 |     }
 99 |   }
100 | }
101 | 


--------------------------------------------------------------------------------
/bottles/frontend/details-dependencies-view.blp:
--------------------------------------------------------------------------------
  1 | using Gtk 4.0;
  2 | using Adw 1;
  3 | 
  4 | template $DetailsDependenciesView: Adw.Bin {
  5 |   Box {
  6 |     orientation: vertical;
  7 | 
  8 |     SearchBar search_bar {
  9 |       SearchEntry entry_search {
 10 |         placeholder-text: _("Search for dependencies…");
 11 |       }
 12 |     }
 13 | 
 14 |     Stack stack {
 15 |       transition-type: crossfade;
 16 | 
 17 |       StackPage {
 18 |         name: "page_offline";
 19 | 
 20 |         child: Adw.StatusPage {
 21 |           icon-name: "network-error-symbolic";
 22 |           title: _("You're offline :(");
 23 |           vexpand: true;
 24 |           hexpand: true;
 25 |           description: _("Bottles is running in offline mode, so dependencies are not available.");
 26 |         };
 27 |       }
 28 | 
 29 |       StackPage {
 30 |         name: "page_loading";
 31 | 
 32 |         child: Adw.StatusPage {
 33 |           vexpand: true;
 34 |           hexpand: true;
 35 | 
 36 |           Spinner spinner_loading {
 37 |             valign: center;
 38 |           }
 39 |         };
 40 |       }
 41 | 
 42 |       StackPage {
 43 |         name: "page_deps";
 44 | 
 45 |         child: Adw.PreferencesPage {
 46 |           Adw.PreferencesGroup {
 47 |             description: _("Dependencies are resources that improve compatibility of Windows software.\n\nFiles on this page are provided by third parties under a proprietary license. By installing them, you agree with their respective licensing terms.");
 48 | 
 49 |             ListBox list_dependencies {
 50 |               selection-mode: none;
 51 | 
 52 |               styles [
 53 |                 "boxed-list",
 54 |               ]
 55 |             }
 56 |           }
 57 |         };
 58 |       }
 59 |     }
 60 |   }
 61 | }
 62 | 
 63 | Popover pop_context {
 64 |   styles [
 65 |     "menu",
 66 |   ]
 67 | 
 68 |   Box {
 69 |     orientation: vertical;
 70 |     margin-top: 6;
 71 |     margin-bottom: 6;
 72 |     margin-start: 6;
 73 |     margin-end: 6;
 74 | 
 75 |     $GtkModelButton btn_report {
 76 |       tooltip-text: _("Report a problem or a missing dependency.");
 77 |       text: _("Report Missing Dependency");
 78 |     }
 79 | 
 80 |     $GtkModelButton btn_help {
 81 |       tooltip-text: _("Read Documentation.");
 82 |       text: _("Documentation");
 83 |     }
 84 |   }
 85 | }
 86 | 
 87 | Box actions {
 88 |   spacing: 6;
 89 | 
 90 |   ToggleButton btn_search {
 91 |     active: bind search_bar.search-mode-enabled no-sync-create bidirectional;
 92 |     tooltip-text: _("Search");
 93 |     icon-name: "system-search-symbolic";
 94 |   }
 95 | 
 96 |   MenuButton {
 97 |     popover: pop_context;
 98 |     icon-name: "view-more-symbolic";
 99 |     tooltip-text: _("Secondary Menu");
100 |   }
101 | }
102 | 


--------------------------------------------------------------------------------
/bottles/frontend/details-installers-view.blp:
--------------------------------------------------------------------------------
 1 | using Gtk 4.0;
 2 | using Adw 1;
 3 | 
 4 | template $DetailsInstallersView: Adw.Bin {
 5 |   Box {
 6 |     orientation: vertical;
 7 | 
 8 |     SearchBar search_bar {
 9 |       SearchEntry entry_search {
10 |         placeholder-text: _("Search for Programs…");
11 |       }
12 |     }
13 | 
14 |     Adw.PreferencesPage pref_page {
15 |       Adw.PreferencesGroup {
16 |         description: _("Install programs curated by our community.\n\nFiles on this page are provided by third parties under a proprietary license. By installing them, you agree with their respective licensing terms.");
17 | 
18 |         ListBox list_installers {
19 |           selection-mode: none;
20 | 
21 |           styles [
22 |             "boxed-list",
23 |           ]
24 |         }
25 |       }
26 |     }
27 | 
28 |     Adw.StatusPage status_page {
29 |       icon-name: "system-software-install-symbolic";
30 |       title: _("No Installers Found");
31 |       vexpand: true;
32 |       hexpand: true;
33 |       description: _("The repository is unreachable or no installer is compatible with this bottle.");
34 |     }
35 |   }
36 | }
37 | 
38 | Popover pop_context {
39 |   styles [
40 |     "menu",
41 |   ]
42 | 
43 |   Box {
44 |     orientation: vertical;
45 |     margin-top: 6;
46 |     margin-bottom: 6;
47 |     margin-start: 6;
48 |     margin-end: 6;
49 | 
50 |     $GtkModelButton btn_help {
51 |       tooltip-text: _("Read Documentation");
52 |       text: _("Documentation");
53 |     }
54 |   }
55 | }
56 | 
57 | Box actions {
58 |   spacing: 6;
59 | 
60 |   ToggleButton btn_toggle_search {
61 |     active: bind search_bar.search-mode-enabled no-sync-create bidirectional;
62 |     tooltip-text: _("Search");
63 |     icon-name: "system-search-symbolic";
64 |   }
65 | 
66 |   MenuButton {
67 |     popover: pop_context;
68 |     icon-name: "view-more-symbolic";
69 |     tooltip-text: _("Secondary Menu");
70 |   }
71 | }
72 | 


--------------------------------------------------------------------------------
/bottles/frontend/details-task-manager-view.blp:
--------------------------------------------------------------------------------
 1 | using Gtk 4.0;
 2 | 
 3 | template $DetailsTaskManagerView: ScrolledWindow {
 4 |   TreeView treeview_processes {
 5 |     enable-grid-lines: horizontal;
 6 | 
 7 |     [internal-child selection]
 8 |     TreeSelection {}
 9 |   }
10 | }
11 | 
12 | Box actions {
13 |   spacing: 6;
14 | 
15 |   Button btn_update {
16 |     tooltip-text: _("Refresh");
17 |     icon-name: "view-refresh-symbolic";
18 |   }
19 | 
20 |   Button btn_kill {
21 |     tooltip-text: _("Stop process");
22 |     icon-name: "process-stop-symbolic";
23 |   }
24 | }
25 | 


--------------------------------------------------------------------------------
/bottles/frontend/details-versioning-page.blp:
--------------------------------------------------------------------------------
 1 | using Gtk 4.0;
 2 | using Adw 1;
 3 | 
 4 | template $DetailsVersioningPage: Adw.PreferencesPage {
 5 |   Adw.PreferencesPage pref_page {
 6 |     Adw.PreferencesGroup {
 7 |       ListBox list_states {
 8 |         selection-mode: none;
 9 | 
10 |         styles [
11 |           "boxed-list",
12 |         ]
13 |       }
14 |     }
15 |   }
16 | 
17 |   Adw.StatusPage status_page {
18 |     icon-name: "preferences-system-time-symbolic";
19 |     title: _("No Snapshots Found");
20 |     description: _("Create your first snapshot to start saving states of your preferences.");
21 |   }
22 | }
23 | 
24 | Popover pop_context {
25 |   styles [
26 |     "menu",
27 |   ]
28 | 
29 |   Box {
30 |     orientation: vertical;
31 |     margin-top: 6;
32 |     margin-bottom: 6;
33 |     margin-start: 6;
34 |     margin-end: 6;
35 | 
36 |     $GtkModelButton btn_help {
37 |       tooltip-text: _("Read Documentation");
38 |       text: _("Documentation");
39 |     }
40 |   }
41 | }
42 | 
43 | Popover pop_state {
44 |   Box {
45 |     orientation: vertical;
46 |     spacing: 6;
47 | 
48 |     styles [
49 |       "menu",
50 |     ]
51 | 
52 |     Box {
53 |       Entry entry_state_message {
54 |         hexpand: true;
55 |         placeholder-text: _("A short comment");
56 |       }
57 | 
58 |       Button btn_save {
59 |         tooltip-text: _("Save the bottle state.");
60 |         halign: end;
61 |         icon-name: "object-select-symbolic";
62 | 
63 |         styles [
64 |           "suggested-action",
65 |         ]
66 |       }
67 | 
68 |       styles [
69 |         "linked",
70 |       ]
71 |     }
72 |   }
73 | }
74 | 
75 | Box actions {
76 |   spacing: 6;
77 | 
78 |   MenuButton btn_add {
79 |     tooltip-text: _("Create new Snapshot");
80 |     popover: pop_state;
81 |     icon-name: "list-add-symbolic";
82 |   }
83 | 
84 |   MenuButton {
85 |     popover: pop_context;
86 |     icon-name: "view-more-symbolic";
87 |   }
88 | }
89 | 


--------------------------------------------------------------------------------
/bottles/frontend/dll-override-entry.blp:
--------------------------------------------------------------------------------
 1 | using Gtk 4.0;
 2 | using Adw 1;
 3 | 
 4 | template $DLLEntry: Adw.ComboRow {
 5 |   title: "DLL Name";
 6 | 
 7 |   model: StringList {
 8 |     strings [
 9 |       _("Builtin (Wine)"),
10 |       _("Native (Windows)"),
11 |       _("Builtin, then Native"),
12 |       _("Native, then Builtin"),
13 |       _("Disabled")
14 |     ]
15 |   };
16 | 
17 |   Button btn_remove {
18 |     valign: center;
19 |     icon-name: "user-trash-symbolic";
20 |     tooltip-text: _("Remove");
21 | 
22 |     styles [
23 |       "flat",
24 |     ]
25 |   }
26 | }
27 | 


--------------------------------------------------------------------------------
/bottles/frontend/dll-overrides-dialog.blp:
--------------------------------------------------------------------------------
 1 | using Gtk 4.0;
 2 | using Adw 1;
 3 | 
 4 | template $DLLOverridesDialog: Adw.PreferencesWindow {
 5 |   modal: true;
 6 |   default-width: 500;
 7 |   search-enabled: false;
 8 |   title: _("DLL Overrides");
 9 | 
10 |   Adw.PreferencesPage {
11 |     Adw.PreferencesGroup {
12 |       description: _("Dynamic Link Libraries can be specified to be builtin (provided by Wine) or native (provided by the program).");
13 |       title: _("DLL Overrides");
14 | 
15 |       Adw.EntryRow entry_row {
16 |         title: _("New Override");
17 |         show-apply-button: true;
18 | 
19 |         [suffix]
20 |         MenuButton menu_invalid_override {
21 |           valign: center;
22 |           tooltip-text: _("Show Information");
23 |           icon-name: "warning-symbolic";
24 |           popover: popover_invalid_override;
25 |           visible: false;
26 | 
27 |           styles [
28 |             "flat",
29 |           ]
30 |         }
31 |       }
32 |     }
33 | 
34 |     Adw.PreferencesGroup group_overrides {
35 |       title: _("Overrides");
36 |     }
37 |   }
38 | }
39 | 
40 | Popover popover_invalid_override {
41 |   Label {
42 |     margin-start: 6;
43 |     margin-end: 6;
44 |     margin-top: 6;
45 |     margin-bottom: 6;
46 |     xalign: 0;
47 |     wrap: true;
48 |     wrap-mode: word_char;
49 |     ellipsize: none;
50 |     lines: 4;
51 |     use-markup: true;
52 |     max-width-chars: 40;
53 |     label: _("This override is already managed by Bottles.");
54 |   }
55 | }
56 | 


--------------------------------------------------------------------------------
/bottles/frontend/drive-entry.blp:
--------------------------------------------------------------------------------
 1 | using Gtk 4.0;
 2 | using Adw 1;
 3 | 
 4 | template $DriveEntry: Adw.ActionRow {
 5 |   title: "C:";
 6 |   subtitle: _("/point/to/path");
 7 | 
 8 |   Box {
 9 |     spacing: 6;
10 | 
11 |     Button btn_remove {
12 |       valign: center;
13 |       tooltip-text: _("Remove");
14 |       icon-name: "user-trash-symbolic";
15 | 
16 |       styles [
17 |         "flat",
18 |       ]
19 |     }
20 | 
21 |     Button btn_path {
22 |       valign: center;
23 |       tooltip-text: _("Choose a Directory");
24 |       icon-name: "document-open-symbolic";
25 | 
26 |       styles [
27 |         "flat",
28 |       ]
29 |     }
30 |   }
31 | }
32 | 


--------------------------------------------------------------------------------
/bottles/frontend/drives-dialog.blp:
--------------------------------------------------------------------------------
 1 | using Gtk 4.0;
 2 | using Adw 1;
 3 | 
 4 | template $DrivesDialog: Adw.Window {
 5 |   modal: true;
 6 |   default-width: 500;
 7 |   default-height: 500;
 8 |   title: _("Drives");
 9 | 
10 |   ShortcutController {
11 |     Shortcut {
12 |       trigger: "Escape";
13 |       action: "action(window.close)";
14 |     }
15 |   }
16 | 
17 |   Box {
18 |     orientation: vertical;
19 | 
20 |     Adw.HeaderBar {}
21 | 
22 |     Adw.PreferencesPage {
23 |       Adw.PreferencesGroup {
24 |         description: _("These are paths from your host system that are mapped and recognized as devices by the runner (e.g. C: D:…).");
25 | 
26 |         Adw.ComboRow combo_letter {
27 |           title: _("Letter");
28 | 
29 |           model: StringList str_list_letters {};
30 | 
31 |           Button btn_save {
32 |             valign: center;
33 | 
34 |             Image {
35 |               icon-name: "object-select-symbolic";
36 |             }
37 | 
38 |             styles [
39 |               "flat",
40 |             ]
41 |           }
42 |         }
43 |       }
44 | 
45 |       Adw.PreferencesGroup list_drives {
46 |         title: _("Existing Drives");
47 |       }
48 |     }
49 |   }
50 | }
51 | 


--------------------------------------------------------------------------------
/bottles/frontend/duplicate-dialog.blp:
--------------------------------------------------------------------------------
  1 | using Gtk 4.0;
  2 | using Adw 1;
  3 | 
  4 | template $DuplicateDialog: Adw.Window {
  5 |   modal: true;
  6 |   default-width: 400;
  7 |   default-height: 400;
  8 |   destroy-with-parent: true;
  9 | 
 10 |   Box {
 11 |     orientation: vertical;
 12 | 
 13 |     Adw.HeaderBar {
 14 |       show-end-title-buttons: false;
 15 | 
 16 |       title-widget: Adw.WindowTitle {
 17 |         title: _("Duplicate Bottle");
 18 |       };
 19 | 
 20 |       Button btn_cancel {
 21 |         label: _("_Cancel");
 22 |         use-underline: true;
 23 |         action-name: "window.close";
 24 |       }
 25 | 
 26 |       ShortcutController {
 27 |         scope: managed;
 28 | 
 29 |         Shortcut {
 30 |           trigger: "Escape";
 31 |           action: "action(window.close)";
 32 |         }
 33 |       }
 34 | 
 35 |       [end]
 36 |       Button btn_duplicate {
 37 |         label: _("Duplicate");
 38 | 
 39 |         styles [
 40 |           "suggested-action",
 41 |         ]
 42 |       }
 43 |     }
 44 | 
 45 |     Stack stack_switcher {
 46 |       Adw.PreferencesPage page_name {
 47 |         Adw.PreferencesGroup {
 48 |           description: _("Enter a name for the duplicate of the Bottle.");
 49 | 
 50 |           Adw.EntryRow entry_name {
 51 |             title: _("Name");
 52 |           }
 53 |         }
 54 |       }
 55 | 
 56 |       StackPage {
 57 |         name: "page_duplicating";
 58 | 
 59 |         child: Box page_duplicating {
 60 |           margin-top: 24;
 61 |           margin-bottom: 24;
 62 |           orientation: vertical;
 63 | 
 64 |           Label {
 65 |             halign: center;
 66 |             margin-top: 12;
 67 |             margin-bottom: 12;
 68 |             label: _("Duplicating…");
 69 | 
 70 |             styles [
 71 |               "title-1",
 72 |             ]
 73 |           }
 74 | 
 75 |           Label {
 76 |             margin-bottom: 6;
 77 |             label: _("This could take a while.");
 78 |           }
 79 | 
 80 |           ProgressBar progressbar {
 81 |             width-request: 300;
 82 |             halign: center;
 83 |             margin-top: 24;
 84 |             margin-bottom: 12;
 85 |           }
 86 |         };
 87 |       }
 88 | 
 89 |       StackPage {
 90 |         name: "page_duplicated";
 91 | 
 92 |         child: Adw.StatusPage page_duplicated {
 93 |           icon-name: "object-select-symbolic";
 94 |           title: _("Bottle Duplicated");
 95 |         };
 96 |       }
 97 |     }
 98 |   }
 99 | }
100 | 


--------------------------------------------------------------------------------
/bottles/frontend/env-var-entry.blp:
--------------------------------------------------------------------------------
 1 | using Gtk 4.0;
 2 | using Adw 1;
 3 | 
 4 | template $EnvironmentVariableEntryRow: Adw.EntryRow {
 5 |   show-apply-button: true;
 6 | 
 7 |   Button btn_remove {
 8 |     valign: center;
 9 |     icon-name: "user-trash-symbolic";
10 |     tooltip-text: _("Remove");
11 | 
12 |     styles [
13 |       "flat",
14 |     ]
15 |   }
16 | }
17 | 


--------------------------------------------------------------------------------
/bottles/frontend/environment-variables-dialog.blp:
--------------------------------------------------------------------------------
 1 | using Gtk 4.0;
 2 | using Adw 1;
 3 | 
 4 | template $EnvironmentVariablesDialog: Adw.Dialog {
 5 |   content-width: 600;
 6 |   content-height: 800;
 7 |   title: _("Environment Variables");
 8 | 
 9 |   Box {
10 |     orientation: vertical;
11 | 
12 |     Adw.HeaderBar {
13 |       styles [
14 |         "flat",
15 |       ]
16 |     }
17 | 
18 |     Adw.PreferencesPage {
19 |       Adw.PreferencesGroup {
20 |         description: _("Environment variables are dynamic-named values that can affect the way running processes will behave in your bottle");
21 | 
22 |         Adw.EntryRow entry_new_var {
23 |           title: _("New Variable");
24 |           show-apply-button: true;
25 |         }
26 |       }
27 | 
28 |       Adw.PreferencesGroup group_vars {}
29 |     }
30 |   }
31 | }
32 | 


--------------------------------------------------------------------------------
/bottles/frontend/exclusion-pattern-row.blp:
--------------------------------------------------------------------------------
 1 | using Gtk 4.0;
 2 | using Adw 1;
 3 | 
 4 | template $ExclusionPatternRow: Adw.ActionRow {
 5 |   title: _("Value");
 6 | 
 7 |   Button btn_remove {
 8 |     valign: center;
 9 |     icon-name: "user-trash-symbolic";
10 | 
11 |     styles [
12 |       "flat",
13 |     ]
14 |   }
15 | }
16 | 


--------------------------------------------------------------------------------
/bottles/frontend/exclusion-patterns-dialog.blp:
--------------------------------------------------------------------------------
 1 | using Gtk 4.0;
 2 | using Adw 1;
 3 | 
 4 | template $ExclusionPatternsDialog: Adw.Window {
 5 |   modal: true;
 6 |   default-width: 500;
 7 |   default-height: 500;
 8 | 
 9 |   ShortcutController {
10 |     Shortcut {
11 |       trigger: "Escape";
12 |       action: "action(window.close)";
13 |     }
14 |   }
15 | 
16 |   Box {
17 |     orientation: vertical;
18 | 
19 |     Adw.HeaderBar {
20 |       title-widget: Adw.WindowTitle {
21 |         title: _("Exclusion Patterns");
22 |       };
23 |     }
24 | 
25 |     Adw.PreferencesPage {
26 |       Adw.PreferencesGroup {
27 |         description: _("Define patterns that will be used to prevent some directories to being versioned.");
28 | 
29 |         Adw.EntryRow entry_name {
30 |           title: _("Pattern");
31 |           show-apply-button: true;
32 |         }
33 |       }
34 | 
35 |       Adw.PreferencesGroup group_patterns {
36 |         title: _("Existing Patterns");
37 |       }
38 |     }
39 |   }
40 | }
41 | 


--------------------------------------------------------------------------------
/bottles/frontend/executable.py:
--------------------------------------------------------------------------------
 1 | # executable.py
 2 | #
 3 | # Copyright 2022 brombinmirko <send@mirko.pm>
 4 | #
 5 | # This program is free software: you can redistribute it and/or modify
 6 | # it under the terms of the GNU General Public License as published by
 7 | # the Free Software Foundation, in version 3 of the License.
 8 | #
 9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 | #
17 | 
18 | from gi.repository import Gtk
19 | 
20 | from bottles.backend.utils.threading import RunAsync
21 | from bottles.backend.wine.executor import WineExecutor
22 | 
23 | 
24 | class ExecButton(Gtk.Button):
25 |     def __init__(self, parent, data, config, **kwargs):
26 |         super().__init__(**kwargs)
27 |         self.parent = parent
28 |         self.config = config
29 |         self.data = data
30 | 
31 |         self.set_label(data.get("name"))
32 |         self.connect("clicked", self.on_clicked)
33 | 
34 |     def on_clicked(self, widget):
35 |         executor = WineExecutor(
36 |             self.config, exec_path=self.data.get("file"), args=self.data.get("args")
37 |         )
38 |         RunAsync(executor.run)
39 |         self.parent.pop_run.popdown()  # workaround #1640
40 | 


--------------------------------------------------------------------------------
/bottles/frontend/filters.py:
--------------------------------------------------------------------------------
 1 | # filters.py: File for providing common GtkFileFilters
 2 | #
 3 | # Copyright 2023 Bottles Contributors
 4 | #
 5 | # This program is free software: you can redistribute it and/or modify
 6 | # it under the terms of the GNU General Public License as published by
 7 | # the Free Software Foundation, in version 3 of the License.
 8 | #
 9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 | 
17 | from gettext import gettext as _
18 | 
19 | from gi.repository import Gio, GObject, Gtk
20 | 
21 | 
22 | def add_executable_filters(dialog):
23 |     # TODO: Investigate why `filter.add_mime_type(...)` does not show filter in all distributions.
24 |     # Intended MIME types are:
25 |     #   - `application/x-ms-dos-executable`
26 |     #   - `application/x-msi`
27 |     __set_filter(dialog, _("Supported Executables"), ["*.exe", "*.msi"])
28 | 
29 | 
30 | def add_soundfont_filters(dialog):
31 |     __set_filter(dialog, _("Supported SoundFonts"), ["*.sf2", "*.sf3"])
32 | 
33 | 
34 | def add_yaml_filters(dialog):
35 |     # TODO: Investigate why `filter.add_mime_type(...)` does not show filter in all distributions.
36 |     # Intended MIME types are:
37 |     #   - `application/yaml`
38 |     __set_filter(dialog, "YAML", ["*.yaml", "*.yml"])
39 | 
40 | 
41 | def add_all_filters(dialog):
42 |     __set_filter(dialog, _("All Files"), ["*"])
43 | 
44 | 
45 | def __set_filter(dialog: GObject.Object, name: str, patterns: list[str]):
46 |     """Set dialog named file filter from list of extension patterns."""
47 | 
48 |     filter = Gtk.FileFilter()
49 |     filter.set_name(name)
50 |     for pattern in patterns:
51 |         filter.add_pattern(pattern)
52 | 
53 |     if isinstance(dialog, Gtk.FileDialog):
54 |         filters = dialog.get_filters() or Gio.ListStore.new(Gtk.FileFilter)
55 |         filters.append(filter)
56 |         dialog.set_filters(filters)
57 |     elif isinstance(dialog, Gtk.FileChooserNative):
58 |         dialog.add_filter(filter)
59 |     else:
60 |         raise TypeError
61 | 


--------------------------------------------------------------------------------
/bottles/frontend/fsr-dialog.blp:
--------------------------------------------------------------------------------
 1 | using Gtk 4.0;
 2 | using Adw 1;
 3 | 
 4 | template $FsrDialog: Adw.Window {
 5 |   default-width: 500;
 6 |   modal: true;
 7 |   title: _("FidelityFX Super Resolution Settings");
 8 | 
 9 |   ShortcutController {
10 |     Shortcut {
11 |       trigger: "Escape";
12 |       action: "action(window.close)";
13 |     }
14 |   }
15 | 
16 |   Box {
17 |     orientation: vertical;
18 | 
19 |     Adw.HeaderBar {
20 |       show-start-title-buttons: false;
21 |       show-end-title-buttons: false;
22 | 
23 |       Button {
24 |         label: _("_Cancel");
25 |         use-underline: true;
26 |         action-name: "window.close";
27 |       }
28 | 
29 |       [end]
30 |       Button btn_save {
31 |         label: _("Save");
32 | 
33 |         styles [
34 |           "suggested-action",
35 |         ]
36 |       }
37 |     }
38 | 
39 |     Adw.PreferencesPage {
40 |       Adw.PreferencesGroup {
41 |         Adw.ComboRow combo_quality_mode {
42 |           title: "Quality Mode";
43 | 
44 |           model: StringList str_list_quality_mode {};
45 |         }
46 | 
47 |         Adw.ActionRow {
48 |           title: _("Sharpening Strength");
49 | 
50 |           SpinButton {
51 |             numeric: true;
52 |             valign: center;
53 | 
54 |             adjustment: Adjustment spin_sharpening_strength {
55 |               step-increment: 1;
56 |               upper: 5;
57 |             };
58 |           }
59 |         }
60 |       }
61 |     }
62 |   }
63 | }
64 | 


--------------------------------------------------------------------------------
/bottles/frontend/generic_cli.py:
--------------------------------------------------------------------------------
1 | class MessageDialog:
2 |     def __init__(
3 |         self, parent, title="Warning", message="An error has occurred.", log=False
4 |     ):
5 |         print(message)
6 | 


--------------------------------------------------------------------------------
/bottles/frontend/help-overlay.blp:
--------------------------------------------------------------------------------
 1 | using Gtk 4.0;
 2 | 
 3 | ShortcutsWindow help_overlay {
 4 |   modal: true;
 5 | 
 6 |   ShortcutsSection {
 7 |     section-name: "shortcuts";
 8 |     max-height: 10;
 9 | 
10 |     ShortcutsGroup {
11 |       title: C_("shortcut window", "General");
12 | 
13 |       ShortcutsShortcut {
14 |         title: C_("shortcut window", "New Bottle");
15 |         action-name: "app.new";
16 |       }
17 | 
18 |       ShortcutsShortcut {
19 |         title: C_("shortcut window", "Import Bottle");
20 |         action-name: "app.import";
21 |       }
22 | 
23 |       ShortcutsShortcut {
24 |         title: C_("shortcut window", "Preferences");
25 |         action-name: "app.preferences";
26 |       }
27 | 
28 |       ShortcutsShortcut {
29 |         title: C_("shortcut window", "Documentation");
30 |         action-name: "app.help";
31 |       }
32 | 
33 |       ShortcutsShortcut {
34 |         title: C_("shortcut window", "Show Shortcuts");
35 |         action-name: "win.show-help-overlay";
36 |       }
37 | 
38 |       ShortcutsShortcut {
39 |         title: C_("shortcut window", "Quit");
40 |         action-name: "app.quit";
41 |       }
42 | 
43 |     }
44 |   }
45 | }
46 | 


--------------------------------------------------------------------------------
/bottles/frontend/importer-row.blp:
--------------------------------------------------------------------------------
 1 | using Gtk 4.0;
 2 | using Adw 1;
 3 | 
 4 | Popover pop_actions {
 5 |   styles [
 6 |     "menu",
 7 |   ]
 8 | 
 9 |   Box {
10 |     orientation: vertical;
11 |     spacing: 3;
12 | 
13 |     $GtkModelButton btn_browse {
14 |       text: _("Browse Files");
15 |     }
16 |   }
17 | }
18 | 
19 | template $ImporterRow: Adw.ActionRow {
20 |   /* Translators: A Wine prefix is a separate environment (C:\ drive) for the Wine program */
21 |   title: _("Wine prefix name");
22 | 
23 |   Box {
24 |     spacing: 6;
25 | 
26 |     Label label_manager {
27 |       valign: center;
28 |       label: _("Manager");
29 | 
30 |       styles [
31 |         "tag",
32 |         "caption",
33 |       ]
34 |     }
35 | 
36 |     Image img_lock {
37 |       visible: false;
38 |       tooltip-text: _("This Wine prefix was already imported in Bottles.");
39 |       valign: center;
40 |       icon-name: "channel-secure-symbolic";
41 | 
42 |       styles [
43 |         "tag",
44 |         "caption",
45 |       ]
46 |     }
47 | 
48 |     Button btn_import {
49 |       valign: center;
50 | 
51 |       Image {
52 |         icon-name: "document-save-symbolic";
53 |       }
54 | 
55 |       styles [
56 |         "flat",
57 |       ]
58 |     }
59 | 
60 |     Separator {
61 |       margin-top: 12;
62 |       margin-bottom: 12;
63 |     }
64 | 
65 |     MenuButton {
66 |       valign: center;
67 |       popover: pop_actions;
68 |       icon-name: "view-more-symbolic";
69 | 
70 |       styles [
71 |         "flat",
72 |       ]
73 |     }
74 |   }
75 | }
76 | 


--------------------------------------------------------------------------------
/bottles/frontend/importer-view.blp:
--------------------------------------------------------------------------------
 1 | using Gtk 4.0;
 2 | using Adw 1;
 3 | 
 4 | template $ImporterView: Adw.Bin {
 5 |   Box {
 6 |     orientation: vertical;
 7 | 
 8 |     HeaderBar headerbar {
 9 |       title-widget: Adw.WindowTitle window_title {};
10 | 
11 |       [start]
12 |       Button btn_back {
13 |         tooltip-text: _("Go Back");
14 |         icon-name: "go-previous-symbolic";
15 |       }
16 | 
17 |       [end]
18 |       Box box_actions {
19 |         MenuButton btn_import_backup {
20 |           tooltip-text: _("Import a Bottle backup");
21 |           popover: pop_backup;
22 |           icon-name: "document-send-symbolic";
23 |         }
24 | 
25 |         Button btn_find_prefixes {
26 |           tooltip-text: _("Search again for prefixes");
27 |           icon-name: "view-refresh-symbolic";
28 |         }
29 |       }
30 |     }
31 | 
32 |     Adw.PreferencesPage {
33 |       Adw.StatusPage status_page {
34 |         vexpand: true;
35 |         icon-name: "document-save-symbolic";
36 |         title: _("No Prefixes Found");
37 |         description: _("No external prefixes were found. Does Bottles have access to them?\nUse the icon on the top to import a bottle from a backup.");
38 |       }
39 | 
40 |       Adw.PreferencesGroup group_prefixes {
41 |         visible: false;
42 | 
43 |         ListBox list_prefixes {
44 |           styles [
45 |             "boxed-list",
46 |           ]
47 |         }
48 |       }
49 |     }
50 |   }
51 | }
52 | 
53 | Popover pop_backup {
54 |   styles [
55 |     "menu",
56 |   ]
57 | 
58 |   Box {
59 |     orientation: vertical;
60 |     margin-top: 6;
61 |     margin-bottom: 6;
62 |     margin-start: 6;
63 |     margin-end: 6;
64 | 
65 |     $GtkModelButton btn_import_config {
66 |       tooltip-text: _("This is just the bottle configuration, it\'s perfect if you want to create a new one but without personal files.");
67 |       text: _("Configuration");
68 |     }
69 | 
70 |     $GtkModelButton btn_import_full {
71 |       tooltip-text: _("This is the complete archive of your bottle, including personal files.");
72 |       text: _("Full Archive");
73 |     }
74 |   }
75 | }
76 | 


--------------------------------------------------------------------------------
/bottles/frontend/importer_row.py:
--------------------------------------------------------------------------------
 1 | # importer_row.py
 2 | #
 3 | # Copyright 2025 The Bottles Contributors
 4 | #
 5 | # This program is free software: you can redistribute it and/or modify
 6 | # it under the terms of the GNU General Public License as published by
 7 | # the Free Software Foundation, in version 3 of the License.
 8 | #
 9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 | #
17 | 
18 | from gettext import gettext as _
19 | 
20 | from gi.repository import Gtk, Adw
21 | 
22 | from bottles.backend.utils.manager import ManagerUtils
23 | from bottles.backend.utils.threading import RunAsync
24 | from bottles.frontend.gtk import GtkUtils
25 | 
26 | 
27 | @Gtk.Template(resource_path="/com/usebottles/bottles/importer-row.ui")
28 | class ImporterRow(Adw.ActionRow):
29 |     __gtype_name__ = "ImporterRow"
30 | 
31 |     # region Widgets
32 |     label_manager = Gtk.Template.Child()
33 |     btn_import = Gtk.Template.Child()
34 |     btn_browse = Gtk.Template.Child()
35 |     img_lock = Gtk.Template.Child()
36 | 
37 |     # endregion
38 | 
39 |     def __init__(self, im_manager, prefix, **kwargs):
40 |         super().__init__(**kwargs)
41 | 
42 |         # common variables and references
43 |         self.window = im_manager.window
44 |         self.import_manager = im_manager.import_manager
45 |         self.prefix = prefix
46 | 
47 |         # populate widgets
48 |         self.set_title(prefix.get("Name"))
49 |         self.label_manager.set_text(prefix.get("Manager"))
50 | 
51 |         if prefix.get("Lock"):
52 |             self.img_lock.set_visible(True)
53 | 
54 |         self.label_manager.add_css_class("tag-%s" % prefix.get("Manager").lower())
55 | 
56 |         # connect signals
57 |         self.btn_browse.connect("clicked", self.browse_wineprefix)
58 |         self.btn_import.connect("clicked", self.import_wineprefix)
59 | 
60 |     def browse_wineprefix(self, widget):
61 |         ManagerUtils.browse_wineprefix(self.prefix)
62 | 
63 |     def import_wineprefix(self, widget):
64 |         @GtkUtils.run_in_main_loop
65 |         def set_imported(result, error=False):
66 |             self.btn_import.set_visible(result.ok)
67 |             self.img_lock.set_visible(result.ok)
68 | 
69 |             if result.ok:
70 |                 self.window.show_toast(
71 |                     _('"{0}" imported').format(self.prefix.get("Name"))
72 |                 )
73 | 
74 |             self.set_sensitive(True)
75 | 
76 |         self.set_sensitive(False)
77 | 
78 |         RunAsync(
79 |             self.import_manager.import_wineprefix,
80 |             callback=set_imported,
81 |             wineprefix=self.prefix,
82 |         )
83 | 


--------------------------------------------------------------------------------
/bottles/frontend/installer-row.blp:
--------------------------------------------------------------------------------
 1 | using Gtk 4.0;
 2 | using Adw 1;
 3 | 
 4 | Popover pop_actions {
 5 |   styles [
 6 |     "menu",
 7 |   ]
 8 | 
 9 |   Box {
10 |     orientation: vertical;
11 |     margin-top: 6;
12 |     margin-bottom: 6;
13 |     margin-start: 6;
14 |     margin-end: 6;
15 | 
16 |     $GtkModelButton btn_manifest {
17 |       text: _("Show Manifest…");
18 |     }
19 | 
20 |     $GtkModelButton btn_review {
21 |       text: _("Read Review…");
22 |     }
23 | 
24 |     Separator {}
25 | 
26 |     $GtkModelButton btn_report {
27 |       text: _("Report a Bug…");
28 |     }
29 |   }
30 | }
31 | 
32 | template $InstallerRow: Adw.ActionRow {
33 |   activatable-widget: btn_install;
34 |   title: _("Installer name");
35 |   subtitle: _("Installer description");
36 | 
37 |   Box {
38 |     spacing: 6;
39 | 
40 |     Label label_grade {
41 |       valign: center;
42 |       label: _("Unknown");
43 | 
44 |       styles [
45 |         "tag",
46 |         "caption",
47 |       ]
48 |     }
49 | 
50 |     Button btn_install {
51 |       tooltip-text: _("Install this Program");
52 |       valign: center;
53 |       icon-name: "document-save-symbolic";
54 | 
55 |       styles [
56 |         "flat",
57 |       ]
58 |     }
59 | 
60 |     Separator {
61 |       margin-top: 12;
62 |       margin-bottom: 12;
63 |     }
64 | 
65 |     MenuButton {
66 |       valign: center;
67 |       popover: pop_actions;
68 |       icon-name: "view-more-symbolic";
69 |       tooltip-text: _("Program Menu");
70 | 
71 |       styles [
72 |         "flat",
73 |       ]
74 |     }
75 |   }
76 | }
77 | 


--------------------------------------------------------------------------------
/bottles/frontend/journal-dialog.blp:
--------------------------------------------------------------------------------
 1 | using Gtk 4.0;
 2 | using Adw 1;
 3 | 
 4 | Popover pop_menu {
 5 |   Box {
 6 |     orientation: vertical;
 7 |     spacing: 3;
 8 | 
 9 |     $GtkModelButton btn_all {
10 |       text: _("All messages");
11 |     }
12 | 
13 |     $GtkModelButton btn_critical {
14 |       text: _("Critical");
15 |     }
16 | 
17 |     $GtkModelButton btn_error {
18 |       text: _("Errors");
19 |     }
20 | 
21 |     $GtkModelButton btn_warning {
22 |       text: _("Warnings");
23 |     }
24 | 
25 |     $GtkModelButton btn_info {
26 |       text: _("Info");
27 |     }
28 |   }
29 | }
30 | 
31 | template $JournalDialog: Adw.Window {
32 |   default-width: 800;
33 |   default-height: 600;
34 |   destroy-with-parent: true;
35 | 
36 |   Box {
37 |     orientation: vertical;
38 | 
39 |     Adw.HeaderBar {
40 |       title-widget: Adw.WindowTitle {
41 |         title: _("Journal Browser");
42 |       };
43 | 
44 |       [title]
45 |       Box {
46 |         SearchEntry search_entry {
47 |           placeholder-text: _("Journal Browser");
48 |         }
49 | 
50 |         MenuButton {
51 |           focus-on-click: false;
52 |           tooltip-text: _("Change Logging Level.");
53 |           popover: pop_menu;
54 | 
55 |           Label label_filter {
56 |             label: _("All");
57 |           }
58 |         }
59 | 
60 |         styles [
61 |           "linked",
62 |         ]
63 |       }
64 |     }
65 | 
66 |     ScrolledWindow {
67 |       hexpand: true;
68 |       vexpand: true;
69 | 
70 |       TreeView tree_view {
71 |         reorderable: true;
72 |         hexpand: true;
73 |         vexpand: true;
74 | 
75 |         [internal-child selection]
76 |         TreeSelection {}
77 |       }
78 |     }
79 |   }
80 | }
81 | 


--------------------------------------------------------------------------------
/bottles/frontend/library-view.blp:
--------------------------------------------------------------------------------
 1 | using Gtk 4.0;
 2 | using Adw 1;
 3 | 
 4 | template $LibraryView: Adw.Bin {
 5 |   Box {
 6 |     orientation: vertical;
 7 | 
 8 |     Adw.StatusPage status_page {
 9 |       vexpand: true;
10 |       hexpand: true;
11 |       icon-name: "library-symbolic";
12 |       title: _("Library");
13 |       description: _("Add items here from your bottle\'s program list");
14 |     }
15 | 
16 |     ScrolledWindow scroll_window {
17 |       hexpand: true;
18 |       vexpand: true;
19 | 
20 |       FlowBox main_flow {
21 |         max-children-per-line: bind template.items_per_line;
22 |         row-spacing: 5;
23 |         column-spacing: 5;
24 |         halign: center;
25 |         valign: start;
26 |         margin-top: 5;
27 |         margin-start: 5;
28 |         margin-bottom: 5;
29 |         margin-end: 5;
30 |         homogeneous: true;
31 |         selection-mode: none;
32 |         activate-on-single-click: false;
33 |       }
34 |     }
35 |   }
36 | }
37 | 


--------------------------------------------------------------------------------
/bottles/frontend/loading-view.blp:
--------------------------------------------------------------------------------
 1 | using Gtk 4.0;
 2 | using Adw 1;
 3 | 
 4 | template $LoadingView: Adw.Bin {
 5 |   WindowHandle {
 6 |     hexpand: true;
 7 |     vexpand: true;
 8 | 
 9 |     Box {
10 |       orientation: vertical;
11 | 
12 |       Adw.StatusPage loading_status_page {
13 |         title: _("Starting up…");
14 |         hexpand: true;
15 |         vexpand: true;
16 |       }
17 | 
18 |       Button btn_go_offline {
19 |         margin-bottom: 20;
20 |         valign: center;
21 |         halign: center;
22 |         label: _("Continue Offline");
23 | 
24 |         styles [
25 |           "destructive-action",
26 |           "pill",
27 |         ]
28 |       }
29 | 
30 |       Label label_fetched {
31 |         styles [
32 |           "dim-label",
33 |         ]
34 |       }
35 | 
36 |       Label label_downloading {
37 |         margin-bottom: 20;
38 | 
39 |         styles [
40 |           "dim-label",
41 |         ]
42 |       }
43 |     }
44 |   }
45 | }
46 | 


--------------------------------------------------------------------------------
/bottles/frontend/loading_view.py:
--------------------------------------------------------------------------------
 1 | # loading_view.py
 2 | #
 3 | # Copyright 2025 The Bottles Contributors
 4 | #
 5 | # This program is free software: you can redistribute it and/or modify
 6 | # it under the terms of the GNU General Public License as published by
 7 | # the Free Software Foundation, in version 3 of the License.
 8 | #
 9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 | #
17 | 
18 | from gettext import gettext as _
19 | 
20 | from gi.repository import Gtk, Adw
21 | 
22 | from bottles.backend.models.result import Result
23 | from bottles.backend.state import SignalManager, Signals
24 | from bottles.frontend.gtk import GtkUtils
25 | from bottles.frontend.params import APP_ID
26 | 
27 | 
28 | @Gtk.Template(resource_path="/com/usebottles/bottles/loading-view.ui")
29 | class LoadingView(Adw.Bin):
30 |     __gtype_name__ = "LoadingView"
31 |     __fetched = 0
32 | 
33 |     # region widgets
34 |     label_fetched = Gtk.Template.Child()
35 |     label_downloading = Gtk.Template.Child()
36 |     btn_go_offline = Gtk.Template.Child()
37 |     loading_status_page = Gtk.Template.Child()
38 |     # endregion
39 | 
40 |     def __init__(self, **kwargs):
41 |         super().__init__(**kwargs)
42 |         self.loading_status_page.set_icon_name(APP_ID)
43 |         self.btn_go_offline.connect("clicked", self.go_offline)
44 | 
45 |     @GtkUtils.run_in_main_loop
46 |     def add_fetched(self, res: Result):
47 |         total: int = res.data
48 |         self.__fetched += 1
49 |         self.label_downloading.set_text(
50 |             _("Downloading ~{0} of packages…").format("20kb")
51 |         )
52 |         self.label_fetched.set_text(
53 |             _("Fetched {0} of {1} packages").format(self.__fetched, total)
54 |         )
55 | 
56 |     def go_offline(self, _widget):
57 |         SignalManager.send(Signals.ForceStopNetworking, Result(status=True))
58 | 


--------------------------------------------------------------------------------
/bottles/frontend/local-resource-row.blp:
--------------------------------------------------------------------------------
 1 | using Gtk 4.0;
 2 | using Adw 1;
 3 | 
 4 | template $LocalResourceRow: Adw.ActionRow {
 5 |   subtitle: _("This resource is missing.");
 6 | 
 7 |   Button btn_path {
 8 |     valign: center;
 9 |     tooltip-text: _("Browse");
10 |     icon-name: "document-open-symbolic";
11 | 
12 |     styles [
13 |       "flat",
14 |     ]
15 |   }
16 | }
17 | 


--------------------------------------------------------------------------------
/bottles/frontend/mangohud-dialog.blp:
--------------------------------------------------------------------------------
 1 | using Gtk 4.0;
 2 | using Adw 1;
 3 | 
 4 | template $MangoHudDialog : Adw.Window {
 5 |  default-width: 500;
 6 |   modal: true;
 7 | 
 8 |   title:_("MangoHud Settings");
 9 | 
10 |   ShortcutController {
11 |     Shortcut {
12 |       trigger: "Escape";
13 |       action: "action(window.close)";
14 |     }
15 |   }
16 | 
17 |   Box {
18 |     orientation: vertical;
19 | 
20 |     Adw.HeaderBar {
21 |       show-start-title-buttons: false;
22 |       show-end-title-buttons: false;
23 | 
24 |       Button {
25 |         label: _("_Cancel");
26 |         use-underline: true;
27 |         action-name: "window.close";
28 |       }
29 | 
30 |       [end]
31 |       Button btn_save {
32 |         label: _("Save");
33 | 
34 |         styles [
35 |           "suggested-action",
36 |         ]
37 |       }
38 |     }
39 | 
40 |     Adw.PreferencesPage {
41 |       Adw.PreferencesGroup {
42 |         Adw.ActionRow {
43 |           title: _("Display On Game Start");
44 |           subtitle: _("Display HUD as soon as the game starts. Can be toggled in-game (default keybind: [⇧ Right Shift] + [F12]).");
45 |           activatable-widget: display_on_game_start;
46 | 
47 |           Switch display_on_game_start {
48 |             valign: center;
49 |           }
50 |         }
51 |       }
52 |     }
53 |   }
54 | }
55 | 


--------------------------------------------------------------------------------
/bottles/frontend/mangohud_dialog.py:
--------------------------------------------------------------------------------
 1 | # mangohud_dialog.py
 2 | #
 3 | # Copyright 2025 The Bottles Contributors
 4 | #
 5 | # This program is free software: you can redistribute it and/or modify
 6 | # it under the terms of the GNU General Public License as published by
 7 | # the Free Software Foundation, either version 3 of the License, or
 8 | # (at your option) any later version.
 9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 | 
18 | from gi.repository import Gtk, GLib, Adw
19 | from bottles.backend.logger import Logger
20 | 
21 | logging = Logger()
22 | 
23 | 
24 | @Gtk.Template(resource_path="/com/usebottles/bottles/mangohud-dialog.ui")
25 | class MangoHudDialog(Adw.Window):
26 |     __gtype_name__ = "MangoHudDialog"
27 | 
28 |     # Region Widgets
29 |     btn_save = Gtk.Template.Child()
30 |     display_on_game_start = Gtk.Template.Child()
31 | 
32 |     def __init__(self, window, config, **kwargs):
33 |         super().__init__(**kwargs)
34 |         self.set_transient_for(window)
35 | 
36 |         # Common variables and references
37 |         self.window = window
38 |         self.manager = window.manager
39 |         self.config = config
40 | 
41 |         # Connect signals
42 |         self.btn_save.connect("clicked", self.__save)
43 | 
44 |         self.__update(config)
45 | 
46 |     def __update(self, config):
47 |         parameters = config.Parameters
48 |         self.display_on_game_start.set_active(parameters.mangohud_display_on_game_start)
49 | 
50 |     def __idle_save(self, *_args):
51 |         settings = {
52 |             "mangohud_display_on_game_start": self.display_on_game_start.get_active(),
53 |         }
54 | 
55 |         for setting in settings.keys():
56 |             self.manager.update_config(
57 |                 config=self.config,
58 |                 key=setting,
59 |                 value=settings[setting],
60 |                 scope="Parameters",
61 |             )
62 | 
63 |             self.destroy()
64 | 
65 |     def __save(self, *_args):
66 |         GLib.idle_add(self.__idle_save)
67 | 


--------------------------------------------------------------------------------
/bottles/frontend/params.py:
--------------------------------------------------------------------------------
 1 | # Application details
 2 | APP_NAME = "@APP_NAME@"
 3 | APP_NAME_LOWER = APP_NAME.lower()
 4 | BASE_ID = "@BASE_ID@"
 5 | APP_ID = "@APP_ID@"
 6 | APP_VERSION = "@APP_VERSION@"
 7 | APP_MAJOR_VERSION = "@APP_MAJOR_VERSION@"
 8 | APP_MINOR_VERSION = "@APP_MINOR_VERSION@"
 9 | APP_ICON = "@APP_ID@"
10 | PROFILE = "@PROFILE@"
11 | 
12 | # Internal settings not user editable
13 | ANIM_DURATION = 120
14 | 
15 | # General purpose definitions
16 | EXECUTABLE_EXTS = (".exe", ".msi", ".bat", ".lnk")
17 | 
18 | # URLs
19 | DOC_URL = "https://docs.usebottles.com"
20 | 


--------------------------------------------------------------------------------
/bottles/frontend/program-row.blp:
--------------------------------------------------------------------------------
  1 | using Gtk 4.0;
  2 | using Adw 1;
  3 | 
  4 | Popover pop_actions {
  5 |   styles [
  6 |     "menu",
  7 |   ]
  8 | 
  9 |   Box {
 10 |     orientation: vertical;
 11 |     margin-top: 6;
 12 |     margin-bottom: 6;
 13 |     margin-start: 6;
 14 |     margin-end: 6;
 15 | 
 16 |     Box {
 17 |       homogeneous: true;
 18 | 
 19 |       Button btn_launch_terminal {
 20 |         tooltip-text: _("Launch with Terminal");
 21 |         icon-name: "utilities-terminal-symbolic";
 22 |         valign: center;
 23 |       }
 24 | 
 25 |       Button btn_browse {
 26 |         tooltip-text: _("Browse Path");
 27 |         icon-name: "document-open-symbolic";
 28 |         valign: center;
 29 |       }
 30 | 
 31 |       styles [
 32 |         "linked",
 33 |       ]
 34 |     }
 35 | 
 36 |     Separator {}
 37 | 
 38 |     $GtkModelButton btn_launch_options {
 39 |       text: _("Change Launch Options…");
 40 |     }
 41 | 
 42 |     $GtkModelButton btn_add_library {
 43 |       text: _("Add to Library");
 44 |     }
 45 | 
 46 |     $GtkModelButton btn_add_entry {
 47 |       text: _("Add Desktop Entry");
 48 |     }
 49 | 
 50 |     $GtkModelButton btn_add_steam {
 51 |       text: _("Add to Steam");
 52 |     }
 53 | 
 54 |     $GtkModelButton btn_rename {
 55 |       text: _("Rename…");
 56 |     }
 57 | 
 58 |     Separator {}
 59 | 
 60 |     $GtkModelButton btn_hide {
 61 |       text: _("Hide Program");
 62 |     }
 63 | 
 64 |     $GtkModelButton btn_unhide {
 65 |       text: _("Show Program");
 66 |     }
 67 | 
 68 |     $GtkModelButton btn_remove {
 69 |       text: _("Remove from List");
 70 |     }
 71 | 
 72 |     Separator {}
 73 | 
 74 |     $GtkModelButton btn_uninstall {
 75 |       text: _("Uninstall");
 76 |     }
 77 |   }
 78 | }
 79 | 
 80 | template $ProgramRow: Adw.ActionRow {
 81 |   title: _("Program name");
 82 | 
 83 |   Box {
 84 |     spacing: 6;
 85 | 
 86 |     Button btn_launch_steam {
 87 |       tooltip-text: _("Launch with Steam");
 88 |       valign: center;
 89 |       visible: false;
 90 |       icon-name: "bottles-steam-symbolic";
 91 | 
 92 |       styles [
 93 |         "flat",
 94 |       ]
 95 |     }
 96 | 
 97 |     Button btn_run {
 98 |       valign: center;
 99 |       icon-name: "media-playback-start-symbolic";
100 | 
101 |       styles [
102 |         "flat",
103 |       ]
104 |     }
105 | 
106 |     Button btn_stop {
107 |       valign: center;
108 |       visible: false;
109 |       icon-name: "media-playback-stop-symbolic";
110 | 
111 |       styles [
112 |         "flat",
113 |       ]
114 |     }
115 | 
116 |     MenuButton btn_menu {
117 |       valign: center;
118 |       popover: pop_actions;
119 |       icon-name: "view-more-symbolic";
120 | 
121 |       styles [
122 |         "flat",
123 |       ]
124 |     }
125 |   }
126 | }
127 | 


--------------------------------------------------------------------------------
/bottles/frontend/proton-alert-dialog.blp:
--------------------------------------------------------------------------------
 1 | using Gtk 4.0;
 2 | using Adw 1;
 3 | 
 4 | template $ProtonAlertDialog: Adw.Window {
 5 |   title: _("Proton Disclaimer");
 6 |   default-width: 500;
 7 |   default-height: 380;
 8 | 
 9 |   Box {
10 |     orientation: vertical;
11 | 
12 |     Adw.HeaderBar {
13 |       show-end-title-buttons: false;
14 | 
15 |       [start]
16 |       Button btn_cancel {
17 |         label: _("Cancel");
18 |       }
19 | 
20 |       [end]
21 |       Button btn_use {
22 |         label: _("Use Proton");
23 |         sensitive: false;
24 | 
25 |         styles [
26 |           "suggested-action",
27 |         ]
28 |       }
29 |     }
30 | 
31 |     Label {
32 |       margin-top: 10;
33 |       margin-start: 10;
34 |       margin-end: 10;
35 |       wrap: true;
36 |       label: _("Beware, using Proton-based runners in non-Steam bottles can cause problems and prevent them from behaving correctly.\n\nWe recommend using Wine-GE rather, a version of Proton meant to run outside of Steam.\n\nProceeding will automatically enable the Steam runtime (if present in the system and detected by Bottles) in order to allow it to access the necessary libraries and limit compatibility problems. Be aware that GloriousEggroll, the runner\'s provider, is not responsible for any problems and we ask that you do not report to them.");
37 |     }
38 | 
39 |     CheckButton check_confirm {
40 |       margin-top: 10;
41 |       margin-bottom: 10;
42 |       margin-start: 10;
43 |       margin-end: 10;
44 |       label: _("I got it.");
45 |     }
46 |   }
47 | }
48 | 


--------------------------------------------------------------------------------
/bottles/frontend/proton_alert_dialog.py:
--------------------------------------------------------------------------------
 1 | # proton_alert_dialog.py
 2 | #
 3 | # Copyright 2025 The Bottles Contributors
 4 | #
 5 | # This program is free software: you can redistribute it and/or modify
 6 | # it under the terms of the GNU General Public License as published by
 7 | # the Free Software Foundation, in version 3 of the License.
 8 | #
 9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 | #
17 | 
18 | from gi.repository import Gtk, Adw
19 | 
20 | 
21 | @Gtk.Template(resource_path="/com/usebottles/bottles/proton-alert-dialog.ui")
22 | class ProtonAlertDialog(Adw.Window):
23 |     __gtype_name__ = "ProtonAlertDialog"
24 |     __resources = {}
25 | 
26 |     # region Widgets
27 |     btn_use = Gtk.Template.Child()
28 |     btn_cancel = Gtk.Template.Child()
29 |     check_confirm = Gtk.Template.Child()
30 | 
31 |     # endregion
32 | 
33 |     def __init__(self, window, callback, **kwargs):
34 |         super().__init__(**kwargs)
35 |         self.set_transient_for(window)
36 | 
37 |         self.callback = callback
38 | 
39 |         # connect signals
40 |         self.btn_use.connect("clicked", self.__callback, True)
41 |         self.btn_cancel.connect("clicked", self.__callback, False)
42 |         self.check_confirm.connect("toggled", self.__toggle_btn_use)
43 | 
44 |     def __callback(self, _, status):
45 |         self.destroy()
46 |         self.callback(status)
47 |         self.close()
48 | 
49 |     def __toggle_btn_use(self, widget, *_args):
50 |         self.btn_use.set_sensitive(widget.get_active())
51 | 


--------------------------------------------------------------------------------
/bottles/frontend/rename-program-dialog.blp:
--------------------------------------------------------------------------------
 1 | using Gtk 4.0;
 2 | using Adw 1;
 3 | 
 4 | template $RenameProgramDialog: Adw.Window {
 5 |   modal: true;
 6 |   deletable: false;
 7 |   default-width: 550;
 8 |   title: _("Rename");
 9 | 
10 |   Box {
11 |     orientation: vertical;
12 | 
13 |     Adw.HeaderBar {
14 |       [start]
15 |       Button btn_cancel {
16 |         label: _("Cancel");
17 |       }
18 | 
19 |       [end]
20 |       Button btn_save {
21 |         label: _("Save");
22 | 
23 |         styles [
24 |           "suggested-action",
25 |         ]
26 |       }
27 |     }
28 | 
29 |     Adw.PreferencesPage {
30 |       Adw.PreferencesGroup {
31 |         description: _("Choose a new name for the selected program.");
32 | 
33 |         Adw.EntryRow entry_name {
34 |           title: _("New Name");
35 |         }
36 |       }
37 |     }
38 |   }
39 | }
40 | 


--------------------------------------------------------------------------------
/bottles/frontend/rename_program_dialog.py:
--------------------------------------------------------------------------------
 1 | # rename_program_dialog.py
 2 | #
 3 | # Copyright 2025 The Bottles Contributors
 4 | #
 5 | # This program is free software: you can redistribute it and/or modify
 6 | # it under the terms of the GNU General Public License as published by
 7 | # the Free Software Foundation, in version 3 of the License.
 8 | #
 9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 | #
17 | 
18 | from gi.repository import Gtk, Adw
19 | 
20 | 
21 | @Gtk.Template(resource_path="/com/usebottles/bottles/rename-program-dialog.ui")
22 | class RenameProgramDialog(Adw.Window):
23 |     __gtype_name__ = "RenameProgramDialog"
24 | 
25 |     # region Widgets
26 |     entry_name = Gtk.Template.Child()
27 |     btn_cancel = Gtk.Template.Child()
28 |     btn_save = Gtk.Template.Child()
29 |     ev_controller = Gtk.EventControllerKey.new()
30 | 
31 |     # endregion
32 | 
33 |     def __init__(self, window, name, on_save, **kwargs):
34 |         super().__init__(**kwargs)
35 | 
36 |         self.set_transient_for(window)
37 | 
38 |         # common variables and references
39 |         self.window = window
40 |         self.manager = window.manager
41 |         self.on_save = on_save
42 | 
43 |         # set widget defaults
44 |         self.entry_name.set_text(name)
45 |         self.entry_name.add_controller(self.ev_controller)
46 | 
47 |         # connect signals
48 |         self.ev_controller.connect("key-released", self.on_change)
49 |         self.btn_cancel.connect("clicked", self.__close_window)
50 |         self.btn_save.connect("clicked", self.__on_save)
51 | 
52 |     def __on_save(self, *_args):
53 |         text = self.entry_name.get_text()
54 |         self.on_save(new_name=text)
55 |         self.destroy()
56 | 
57 |     def __close_window(self, *_args):
58 |         self.destroy()
59 | 
60 |     def on_change(self, *_args):
61 |         self.btn_save.set_sensitive(len(self.entry_name.get_text()) > 0)
62 | 


--------------------------------------------------------------------------------
/bottles/frontend/sandbox-dialog.blp:
--------------------------------------------------------------------------------
 1 | using Gtk 4.0;
 2 | using Adw 1;
 3 | 
 4 | template $SandboxDialog: Adw.Window {
 5 |   modal: true;
 6 |   deletable: true;
 7 |   default-width: 550;
 8 |   title: _("Sandbox Settings");
 9 | 
10 |   ShortcutController {
11 |     Shortcut {
12 |       trigger: "Escape";
13 |       action: "action(window.close)";
14 |     }
15 |   }
16 | 
17 |   Box {
18 |     orientation: vertical;
19 | 
20 |     Adw.HeaderBar {}
21 | 
22 |     Adw.PreferencesPage {
23 |       Adw.PreferencesGroup {
24 |         Adw.ActionRow {
25 |           title: _("Share Network");
26 |           activatable-widget: switch_net;
27 | 
28 |           Switch switch_net {
29 |             valign: center;
30 |           }
31 |         }
32 | 
33 |         Adw.ActionRow {
34 |           title: _("Share Sound");
35 |           activatable-widget: switch_sound;
36 | 
37 |           Switch switch_sound {
38 |             valign: center;
39 |           }
40 |         }
41 |       }
42 |     }
43 |   }
44 | }
45 | 


--------------------------------------------------------------------------------
/bottles/frontend/sandbox_dialog.py:
--------------------------------------------------------------------------------
 1 | # sandbox_dialog.py
 2 | #
 3 | # Copyright 2025 The Bottles Contributors
 4 | #
 5 | # This program is free software: you can redistribute it and/or modify
 6 | # it under the terms of the GNU General Public License as published by
 7 | # the Free Software Foundation, in version 3 of the License.
 8 | #
 9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 | #
17 | 
18 | from gi.repository import Gtk, Adw
19 | 
20 | 
21 | @Gtk.Template(resource_path="/com/usebottles/bottles/sandbox-dialog.ui")
22 | class SandboxDialog(Adw.Window):
23 |     __gtype_name__ = "SandboxDialog"
24 | 
25 |     # region Widgets
26 |     switch_net = Gtk.Template.Child()
27 |     switch_sound = Gtk.Template.Child()
28 | 
29 |     # endregion
30 | 
31 |     def __init__(self, window, config, **kwargs):
32 |         super().__init__(**kwargs)
33 |         self.set_transient_for(window)
34 | 
35 |         # common variables and references
36 |         self.window = window
37 |         self.manager = window.manager
38 |         self.config = config
39 |         self.__update(config)
40 | 
41 |         # connect signals
42 |         self.switch_net.connect("state-set", self.__set_flag, "share_net")
43 |         self.switch_sound.connect("state-set", self.__set_flag, "share_sound")
44 | 
45 |     def __set_flag(self, widget, state, flag):
46 |         self.config = self.manager.update_config(
47 |             config=self.config, key=flag, value=state, scope="Sandbox"
48 |         ).data["config"]
49 | 
50 |     def __update(self, config):
51 |         self.switch_net.set_active(config.Sandbox.share_net)
52 |         self.switch_sound.set_active(config.Sandbox.share_sound)
53 | 


--------------------------------------------------------------------------------
/bottles/frontend/sh.py:
--------------------------------------------------------------------------------
 1 | # sh.py
 2 | #
 3 | # Copyright 2025 The Bottles Contributors
 4 | #
 5 | # This program is free software: you can redistribute it and/or modify
 6 | # it under the terms of the GNU General Public License as published by
 7 | # the Free Software Foundation, in version 3 of the License.
 8 | #
 9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 | #
17 | 
18 | import re
19 | 
20 | _is_name = re.compile(r"""[_a-zA-Z][_a-zA-Z0-9]*""")
21 | 
22 | 
23 | class ShUtils:
24 |     @staticmethod
25 |     def is_name(text: str) -> bool:
26 |         return bool(_is_name.fullmatch(text))
27 | 
28 |     @staticmethod
29 |     def split_assignment(text: str) -> tuple[str, str]:
30 |         name, _, value = text.partition("=")
31 |         return (name, value)
32 | 


--------------------------------------------------------------------------------
/bottles/frontend/state-row.blp:
--------------------------------------------------------------------------------
 1 | using Gtk 4.0;
 2 | using Adw 1;
 3 | 
 4 | template $StateRow: Adw.ActionRow {
 5 |   activatable-widget: btn_restore;
 6 | 
 7 |   /* Translators: id as identification */
 8 |   title: _("State id");
 9 |   subtitle: _("State comment");
10 | 
11 |   Spinner spinner {
12 |     visible: false;
13 |   }
14 | 
15 |   Button btn_restore {
16 |     tooltip-text: _("Restore this Snapshot");
17 |     valign: center;
18 |     icon-name: "document-open-recent-symbolic";
19 | 
20 |     styles [
21 |       "flat",
22 |     ]
23 |   }
24 | }
25 | 


--------------------------------------------------------------------------------
/bottles/frontend/style-dark.css:
--------------------------------------------------------------------------------
 1 | .grade-Platinum {
 2 |   background-color: #d8d8d8;
 3 |   color: #000000;
 4 | }
 5 | 
 6 | .grade-Gold {
 7 |   background-color: #8d7637;
 8 |   color: #f2edde;
 9 | }
10 | 
11 | .grade-Silver {
12 |   background-color: #6b6b6b;
13 |   color: #e8e8e8;
14 | }
15 | 
16 | .grade-Bronze {
17 |   background-color: #5a3f1a;
18 |   color: #dbbfa3;
19 | }
20 | 
21 | /* Donate button */
22 | .donate {
23 |   color: #d65790;
24 | }
25 | 


--------------------------------------------------------------------------------
/bottles/frontend/task-row.blp:
--------------------------------------------------------------------------------
 1 | using Gtk 4.0;
 2 | using Adw 1;
 3 | 
 4 | template $TaskRow: Adw.ActionRow {
 5 |   Box {
 6 |     spacing: 10;
 7 | 
 8 |     Spinner spinner_task {
 9 |       halign: center;
10 |       valign: center;
11 |     }
12 | 
13 |     Label label_task_status {
14 |       visible: false;
15 |       label: "n/a";
16 |       width-chars: 5;
17 |     }
18 | 
19 |     Button btn_cancel {
20 |       tooltip-text: _("Delete message");
21 |       halign: center;
22 |       valign: center;
23 | 
24 |       Image {
25 |         icon-name: "edit-delete-symbolic";
26 |       }
27 | 
28 |       styles [
29 |         "circular",
30 |         "image-button",
31 |       ]
32 |     }
33 |   }
34 | 
35 |   styles [
36 |     "message-entry",
37 |   ]
38 | }
39 | 


--------------------------------------------------------------------------------
/bottles/frontend/vmtouch-dialog.blp:
--------------------------------------------------------------------------------
 1 | using Gtk 4.0;
 2 | using Adw 1;
 3 | 
 4 | template $VmtouchDialog: Adw.Window {
 5 |   modal: true;
 6 |   default-width: 550;
 7 |   title: _("Vmtouch Settings");
 8 | 
 9 |   ShortcutController {
10 |     Shortcut {
11 |       trigger: "Escape";
12 |       action: "action(window.close)";
13 |     }
14 |   }
15 | 
16 |   Box {
17 |     orientation: vertical;
18 | 
19 |     Adw.HeaderBar {
20 |       show-end-title-buttons: false;
21 | 
22 |       [start]
23 |       Button btn_cancel {
24 |         label: _("_Cancel");
25 |         use-underline: true;
26 |         action-name: "window.close";
27 |       }
28 | 
29 |       [end]
30 |       Button btn_save {
31 |         label: _("Save");
32 | 
33 |         styles [
34 |           "suggested-action",
35 |         ]
36 |       }
37 |     }
38 | 
39 |     Adw.PreferencesPage {
40 |       Adw.PreferencesGroup {
41 |         title: _("Files to cache");
42 |         description: _("Select which files should be cached alongside the main executable.");
43 | 
44 |         Adw.ActionRow {
45 |           title: _("Cache work directory");
46 |           activatable-widget: switch_cache_cwd;
47 | 
48 |           Switch switch_cache_cwd {
49 |             valign: center;
50 |           }
51 |         }
52 |       }
53 |     }
54 |   }
55 | }
56 | 


--------------------------------------------------------------------------------
/bottles/frontend/vmtouch_dialog.py:
--------------------------------------------------------------------------------
 1 | # vmtouch_dialog.py
 2 | #
 3 | # Copyright 2025 The Bottles Contributors
 4 | #
 5 | # This program is free software: you can redistribute it and/or modify
 6 | # it under the terms of the GNU General Public License as published by
 7 | # the Free Software Foundation, either version 3 of the License, or
 8 | # (at your option) any later version.
 9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 | #
18 | # SPDX-License-Identifier: GPL-3.0-only
19 | 
20 | from gi.repository import Gtk, GLib, Adw
21 | 
22 | 
23 | @Gtk.Template(resource_path="/com/usebottles/bottles/vmtouch-dialog.ui")
24 | class VmtouchDialog(Adw.Window):
25 |     __gtype_name__ = "VmtouchDialog"
26 | 
27 |     # region Widgets
28 |     switch_cache_cwd = Gtk.Template.Child()
29 |     btn_save = Gtk.Template.Child()
30 |     btn_cancel = Gtk.Template.Child()
31 | 
32 |     # endregion
33 | 
34 |     def __init__(self, window, config, **kwargs):
35 |         super().__init__(**kwargs)
36 |         self.set_transient_for(window)
37 | 
38 |         # common variables and references
39 |         self.window = window
40 |         self.manager = window.manager
41 |         self.config = config
42 | 
43 |         # connect signals
44 |         self.btn_save.connect("clicked", self.__save)
45 | 
46 |         self.__update(config)
47 | 
48 |     def __update(self, config):
49 |         self.switch_cache_cwd.set_active(config.Parameters.vmtouch_cache_cwd)
50 | 
51 |     def __idle_save(self, *_args):
52 |         settings = {"vmtouch_cache_cwd": self.switch_cache_cwd.get_active()}
53 | 
54 |         for setting in settings.keys():
55 |             self.manager.update_config(
56 |                 config=self.config,
57 |                 key=setting,
58 |                 value=settings[setting],
59 |                 scope="Parameters",
60 |             )
61 | 
62 |         self.destroy()
63 | 
64 |     def __save(self, *_args):
65 |         GLib.idle_add(self.__idle_save)
66 | 


--------------------------------------------------------------------------------
/bottles/frontend/window.blp:
--------------------------------------------------------------------------------
  1 | using Gtk 4.0;
  2 | using Adw 1;
  3 | 
  4 | template $BottlesWindow: Adw.ApplicationWindow {
  5 |   title: "Bottles";
  6 |   close-request => $on_close_request();
  7 | 
  8 |   Adw.ToastOverlay toasts {
  9 |     Adw.Leaflet main_leaf {
 10 |       can-unfold: false;
 11 |       can-navigate-back: false;
 12 | 
 13 |       Box {
 14 |         orientation: vertical;
 15 | 
 16 |         HeaderBar headerbar {
 17 |           title-widget: Adw.ViewSwitcherTitle view_switcher_title {
 18 |             title: "Bottles";
 19 |             stack: stack_main;
 20 |           };
 21 | 
 22 |           Button btn_add {
 23 |             tooltip-text: _("Create New Bottle");
 24 |             icon-name: "list-add-symbolic";
 25 |           }
 26 | 
 27 |           Box box_actions {}
 28 | 
 29 |           styles [
 30 |             "titlebar",
 31 |           ]
 32 | 
 33 |           [end]
 34 |           MenuButton {
 35 |             icon-name: "open-menu-symbolic";
 36 |             menu-model: primary_menu;
 37 |             tooltip-text: _("Main Menu");
 38 |             primary: true;
 39 |           }
 40 | 
 41 |           [end]
 42 |           ToggleButton btn_search {
 43 |             tooltip-text: _("Search");
 44 |             icon-name: "system-search-symbolic";
 45 |             visible: false;
 46 |           }
 47 | 
 48 |           [end]
 49 |           Button btn_donate {
 50 |             tooltip-text: _("Donate");
 51 |             icon-name: "emblem-favorite-symbolic";
 52 |           }
 53 | 
 54 |           [end]
 55 |           Button btn_noconnection {
 56 |             visible: false;
 57 |             tooltip-text: _("You don\'t seem connected to the internet. Without it you will not be able to download essential components. Click this icon when you have reestablished the connection.");
 58 |             icon-name: "network-error-symbolic";
 59 |           }
 60 |         }
 61 | 
 62 |         SearchBar searchbar {}
 63 | 
 64 |         Adw.ViewStack stack_main {
 65 |           vexpand: true;
 66 |         }
 67 | 
 68 |         Adw.ViewSwitcherBar view_switcher_bar {
 69 |           stack: stack_main;
 70 |           reveal: bind view_switcher_title.title-visible;
 71 |         }
 72 |       }
 73 |     }
 74 |   }
 75 | }
 76 | 
 77 | menu primary_menu {
 78 |   section {
 79 |     item {
 80 |       label: _("_Import…");
 81 |       action: "app.import";
 82 |     }
 83 |   }
 84 | 
 85 |   section {
 86 |     item {
 87 |       label: _("_Preferences");
 88 |       action: "app.preferences";
 89 |     }
 90 | 
 91 |     item {
 92 |       label: _("_Keyboard Shortcuts");
 93 |       action: "win.show-help-overlay";
 94 |     }
 95 | 
 96 |     item {
 97 |       label: _("_Help");
 98 |       action: "app.help";
 99 |     }
100 | 
101 |     item {
102 |       label: _("_About Bottles");
103 |       action: "app.about";
104 |     }
105 |   }
106 | }
107 | 


--------------------------------------------------------------------------------
/bottles/meson.build:
--------------------------------------------------------------------------------
 1 | pkgdatadir = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name())
 2 | moduledir = join_paths(pkgdatadir, 'bottles')
 3 | 
 4 | python = import('python')
 5 | 
 6 | conf = configuration_data()
 7 | conf.set('PYTHON', python.find_installation('python3').full_path())
 8 | conf.set('BASE_ID', BASE_ID)
 9 | conf.set('APP_ID', APP_ID)
10 | conf.set('APP_NAME', APP_NAME)
11 | conf.set('APP_VERSION', APP_VERSION)
12 | conf.set('APP_MAJOR_VERSION', APP_MAJOR_VERSION)
13 | conf.set('APP_MINOR_VERSION', APP_MINOR_VERSION)
14 | conf.set('PROFILE', PROFILE)
15 | conf.set('localedir', join_paths(get_option('prefix'), get_option('localedir')))
16 | conf.set('pkgdatadir', pkgdatadir)
17 | 
18 | subdir('backend')
19 | subdir('frontend')
20 | 
21 | bottles_sources = [
22 |   '__init__.py',
23 | ]
24 | 
25 | install_data(bottles_sources, install_dir: moduledir)
26 | 


--------------------------------------------------------------------------------
/bottles/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bottlesdevs/Bottles/5020d62c2e086de3f407d8667b3d8b4b5ae644f5/bottles/tests/__init__.py


--------------------------------------------------------------------------------
/bottles/tests/backend/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bottlesdevs/Bottles/5020d62c2e086de3f407d8667b3d8b4b5ae644f5/bottles/tests/backend/__init__.py


--------------------------------------------------------------------------------
/bottles/tests/backend/manager/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bottlesdevs/Bottles/5020d62c2e086de3f407d8667b3d8b4b5ae644f5/bottles/tests/backend/manager/__init__.py


--------------------------------------------------------------------------------
/bottles/tests/backend/manager/test_manager.py:
--------------------------------------------------------------------------------
 1 | """Core Manager tests"""
 2 | 
 3 | from bottles.backend.managers.manager import Manager
 4 | from bottles.backend.utils.gsettings_stub import GSettingsStub
 5 | 
 6 | 
 7 | def test_manager_is_singleton():
 8 |     assert Manager(is_cli=True) is Manager(
 9 |         is_cli=True
10 |     ), "Manager should be singleton object"
11 |     assert Manager(is_cli=True) is Manager(
12 |         g_settings=GSettingsStub(), is_cli=True
13 |     ), "Manager should be singleton even with different argument"
14 | 
15 | 
16 | def test_manager_default_gsettings_stub():
17 |     assert Manager().settings.get_boolean("anything") is False
18 | 


--------------------------------------------------------------------------------
/bottles/tests/backend/state/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bottlesdevs/Bottles/5020d62c2e086de3f407d8667b3d8b4b5ae644f5/bottles/tests/backend/state/__init__.py


--------------------------------------------------------------------------------
/bottles/tests/backend/utils/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bottlesdevs/Bottles/5020d62c2e086de3f407d8667b3d8b4b5ae644f5/bottles/tests/backend/utils/__init__.py


--------------------------------------------------------------------------------
/bottles/tests/backend/utils/test_generic.py:
--------------------------------------------------------------------------------
 1 | import pytest
 2 | 
 3 | from bottles.backend.utils.generic import detect_encoding
 4 | 
 5 | 
 6 | # CP932 is superset of Shift-JIS, which is default codec for Japanese in Windows
 7 | # GBK is default codec for Chinese in Windows
 8 | @pytest.mark.parametrize(
 9 |     "text, hint, codec",
10 |     [
11 |         ("Hello, world!", None, "ascii"),
12 |         ("   ", None, "ascii"),
13 |         ("Привет, мир!", None, "windows-1251"),
14 |         ("こんにちは、世界!", "ja_JP", "cp932"),
15 |         ("こんにちは、世界!", "ja_JP.utf-8", "utf-8"),
16 |         ("你好,世界!", "zh_CN", "gbk"),
17 |         ("你好,世界!", "zh_CN.UTF-8", "utf-8"),
18 |         ("你好,世界!", "zh_CN.invalid_fallback", "gbk"),
19 |         ("", None, "utf-8"),
20 |     ],
21 | )
22 | def test_detect_encoding(text: str, hint: str | None, codec: str | None):
23 |     text_bytes = text.encode(codec)
24 |     guess = detect_encoding(text_bytes, hint)
25 |     assert guess.lower() == codec.lower()
26 | 


--------------------------------------------------------------------------------
/build-aux/install.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | BUILD_DIR="build/"
3 | if [ -d "$BUILD_DIR" ]; then
4 | 	rm -r build
5 | fi
6 | mkdir build
7 | meson --prefix=$PWD/build build
8 | ninja -j$(nproc) -C build install
9 | 


--------------------------------------------------------------------------------
/data/com.usebottles.bottles.desktop.in.in:
--------------------------------------------------------------------------------
 1 | [Desktop Entry]
 2 | Name=@APP_NAME@
 3 | Comment=Run Windows software
 4 | Icon=@APP_ID@
 5 | Exec=bottles %u
 6 | TryExec=bottles
 7 | Terminal=false
 8 | Type=Application
 9 | Categories=Utility;Game;Graphics;3DGraphics;Emulator;GNOME;GTK;
10 | StartupNotify=true
11 | StartupWMClass=bottles
12 | MimeType=x-scheme-handler/bottles;application/x-ms-dos-executable;application/x-msi;application/x-ms-shortcut;application/x-wine-extension-msp;
13 | Keywords=wine;windows;gaming;emulate;emulator;game;
14 | X-GNOME-UsesNotifications=true
15 | 


--------------------------------------------------------------------------------
/data/icons/hicolor/symbolic/actions/application-x-addon-symbolic.svg:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="UTF-8"?>
2 | <svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
3 |     <path d="m 7.015625 0 c -1.109375 0 -2 0.890625 -2 2 v 1 h -3.015625 c -1.109375 0 -2 0.890625 -2 2 v 2 h 1.015625 c 1.105469 0 2 0.890625 2 2 s -0.894531 2 -2 2 h -1.015625 v 2.988281 c 0 1.105469 0.890625 2 2 2 h 3.015625 v -0.988281 c 0 -1.109375 0.890625 -2 2 -2 c 1.105469 0 2 0.890625 2 2 v 0.988281 h 2 c 1.105469 0 2 -0.894531 2 -2 v -2.988281 h 1 c 1.105469 0 2 -0.890625 2 -2 s -0.894531 -2 -2 -2 h -1 v -2 c 0 -1.109375 -0.894531 -2 -2 -2 h -2 v -1 c 0 -1.109375 -0.894531 -2 -2 -2 z m 0 0" fill="#2e3436"/>
4 | </svg>
5 | 


--------------------------------------------------------------------------------
/data/icons/hicolor/symbolic/actions/computer-symbolic.svg:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="UTF-8"?>
2 | <svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
3 |     <path d="m 3 0 c -1.660156 0 -3 1.339844 -3 3 v 7 c 0 1.660156 1.339844 3 3 3 h 10 c 1.660156 0 3 -1.339844 3 -3 v -7 c 0 -1.660156 -1.339844 -3 -3 -3 z m 0 2 h 10 c 0.554688 0 1 0.445312 1 1 v 7 c 0 0.554688 -0.445312 1 -1 1 h -10 c -0.554688 0 -1 -0.445312 -1 -1 v -7 c 0 -0.554688 0.445312 -1 1 -1 z m 2 12 c -1.105469 0 -2 0.894531 -2 2 h 10 c 0 -1.105469 -0.894531 -2 -2 -2 z m 0 0" fill="#2e3436"/>
4 | </svg>
5 | 


--------------------------------------------------------------------------------
/data/icons/hicolor/symbolic/actions/document-save-symbolic.svg:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="UTF-8"?>
2 | <svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
3 |     <path d="m 8 0 c -0.550781 0 -1 0.449219 -1 1 v 8.585938 l -1.292969 -1.292969 c -0.1875 -0.1875 -0.441406 -0.292969 -0.707031 -0.292969 s -0.519531 0.105469 -0.707031 0.292969 c -0.390625 0.390625 -0.390625 1.023437 0 1.414062 l 3 3 c 0.390625 0.390625 1.023437 0.390625 1.414062 0 l 3 -3 c 0.390625 -0.390625 0.390625 -1.023437 0 -1.414062 s -1.023437 -0.390625 -1.414062 0 l -1.292969 1.292969 v -8.585938 c 0 -0.550781 -0.449219 -1 -1 -1 z m -7 14 v 2 h 14 v -2 z m 0 0" fill="#2e3436"/>
4 | </svg>
5 | 


--------------------------------------------------------------------------------
/data/icons/hicolor/symbolic/actions/external-link-symbolic.svg:
--------------------------------------------------------------------------------
1 | <svg height="16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M10 0a1 1 0 0 0 0 2h2.586L7.293 7.293a1 1 0 0 0 1.414 1.414L14 3.414V6a1 1 0 1 0 2 0V0h-1ZM4 1C2.355 1 1 2.355 1 4v8c0 1.645 1.355 3 3 3h8c1.645 0 3-1.355 3-3V9c0-.554-.446-1-1-1s-1 .446-1 1v3c0 .571-.429 1-1 1H4c-.571 0-1-.429-1-1V4c0-.571.429-1 1-1h3c.554 0 1-.446 1-1s-.446-1-1-1Z" style="fill:#3d3846"/></svg>
2 | 


--------------------------------------------------------------------------------
/data/icons/hicolor/symbolic/actions/go-next-symbolic.svg:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="UTF-8"?>
2 | <svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
3 |     <path d="m 4 2 c 0 -0.265625 0.105469 -0.519531 0.292969 -0.707031 c 0.390625 -0.390625 1.023437 -0.390625 1.414062 0 l 6 6 c 0.1875 0.1875 0.292969 0.441406 0.292969 0.707031 s -0.105469 0.519531 -0.292969 0.707031 l -6 6 c -0.390625 0.390625 -1.023437 0.390625 -1.414062 0 c -0.1875 -0.1875 -0.292969 -0.441406 -0.292969 -0.707031 s 0.105469 -0.519531 0.292969 -0.707031 l 5.292969 -5.292969 l -5.292969 -5.292969 c -0.1875 -0.1875 -0.292969 -0.441406 -0.292969 -0.707031 z m 0 0" fill="#2e3436"/>
4 | </svg>
5 | 


--------------------------------------------------------------------------------
/data/icons/hicolor/symbolic/actions/go-previous-symbolic.svg:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="UTF-8"?>
2 | <svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
3 |     <path d="m 12 2 c 0 -0.265625 -0.105469 -0.519531 -0.292969 -0.707031 c -0.390625 -0.390625 -1.023437 -0.390625 -1.414062 0 l -6 6 c -0.1875 0.1875 -0.292969 0.441406 -0.292969 0.707031 s 0.105469 0.519531 0.292969 0.707031 l 6 6 c 0.390625 0.390625 1.023437 0.390625 1.414062 0 c 0.1875 -0.1875 0.292969 -0.441406 0.292969 -0.707031 s -0.105469 -0.519531 -0.292969 -0.707031 l -5.292969 -5.292969 l 5.292969 -5.292969 c 0.1875 -0.1875 0.292969 -0.441406 0.292969 -0.707031 z m 0 0" fill="#2e3436"/>
4 | </svg>
5 | 


--------------------------------------------------------------------------------
/data/icons/hicolor/symbolic/actions/info-symbolic.svg:
--------------------------------------------------------------------------------
1 | <svg height="16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M7.906 1A7.002 7.002 0 0 0 1 8c0 3.867 3.133 7 7 7s7-3.133 7-7-3.133-7-7-7h-.094zM7.5 4h1c.277 0 .5.223.5.5v1c0 .277-.223.5-.5.5h-1a.498.498 0 0 1-.5-.5v-1c0-.277.223-.5.5-.5zM7 7h2v5H7zm0 0" fill="#2e3436"/></svg>
2 | 


--------------------------------------------------------------------------------
/data/icons/hicolor/symbolic/actions/library-symbolic.svg:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="UTF-8"?>
2 | <svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><g fill="#222222"><path d="m 1.5 2 h 2 c 0.277344 0 0.5 0.222656 0.5 0.5 v 12 c 0 0.277344 -0.222656 0.5 -0.5 0.5 h -2 c -0.277344 0 -0.5 -0.222656 -0.5 -0.5 v -12 c 0 -0.277344 0.222656 -0.5 0.5 -0.5 z m 0 0"/><path d="m 5.5 4 h 1 c 0.277344 0 0.5 0.222656 0.5 0.5 v 10 c 0 0.277344 -0.222656 0.5 -0.5 0.5 h -1 c -0.277344 0 -0.5 -0.222656 -0.5 -0.5 v -10 c 0 -0.277344 0.222656 -0.5 0.5 -0.5 z m 0 0"/><path d="m 8.5 3 h 1 c 0.277344 0 0.5 0.222656 0.5 0.5 v 11 c 0 0.277344 -0.222656 0.5 -0.5 0.5 h -1 c -0.277344 0 -0.5 -0.222656 -0.5 -0.5 v -11 c 0 -0.277344 0.222656 -0.5 0.5 -0.5 z m 0 0"/><path d="m 10.707031 1.460938 l 0.964844 -0.261719 c 0.265625 -0.070313 0.539063 0.089843 0.613281 0.355469 l 3.363282 12.558593 c 0.070312 0.265625 -0.085938 0.539063 -0.351563 0.609375 l -0.96875 0.261719 c -0.265625 0.070313 -0.539063 -0.089844 -0.613281 -0.355469 l -3.363282 -12.554687 c -0.070312 -0.269531 0.085938 -0.542969 0.355469 -0.613281 z m 0 0"/></g></svg>
3 | 


--------------------------------------------------------------------------------
/data/icons/hicolor/symbolic/actions/list-add-symbolic.svg:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="UTF-8"?>
2 | <svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
3 |     <path d="m 7 1 v 6 h -6 v 2 h 6 v 6 h 2 v -6 h 6 v -2 h -6 v -6 z m 0 0" fill="#2e3436"/>
4 | </svg>
5 | 


--------------------------------------------------------------------------------
/data/icons/hicolor/symbolic/actions/media-playback-start-symbolic.svg:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="UTF-8"?>
2 | <svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
3 |     <path d="m 2 2.5 v 11 c 0 1.5 1.269531 1.492188 1.269531 1.492188 h 0.128907 c 0.246093 0.003906 0.488281 -0.050782 0.699218 -0.171876 l 9.796875 -5.597656 c 0.433594 -0.242187 0.65625 -0.734375 0.65625 -1.226562 c 0 -0.492188 -0.222656 -0.984375 -0.65625 -1.222656 l -9.796875 -5.597657 c -0.210937 -0.121093 -0.453125 -0.175781 -0.699218 -0.175781 h -0.128907 s -1.269531 0 -1.269531 1.5 z m 0 0" fill="#2e3436"/>
4 | </svg>
5 | 


--------------------------------------------------------------------------------
/data/icons/hicolor/symbolic/actions/media-playback-stop-symbolic.svg:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="UTF-8"?>
2 | <svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
3 |     <path d="m 3.5 2 h 9 c 0.828125 0 1.5 0.671875 1.5 1.5 v 9 c 0 0.828125 -0.671875 1.5 -1.5 1.5 h -9 c -0.828125 0 -1.5 -0.671875 -1.5 -1.5 v -9 c 0 -0.828125 0.671875 -1.5 1.5 -1.5 z m 0 0" fill="#2e3436"/>
4 | </svg>
5 | 


--------------------------------------------------------------------------------
/data/icons/hicolor/symbolic/actions/open-menu-symbolic.svg:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="UTF-8"?>
2 | <svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
3 |     <g fill="#2e3436">
4 |         <path d="m 1 2 h 14 v 2 h -14 z m 0 0"/>
5 |         <path d="m 1 7 h 14 v 2 h -14 z m 0 0"/>
6 |         <path d="m 1 12 h 14 v 2 h -14 z m 0 0"/>
7 |     </g>
8 | </svg>
9 | 


--------------------------------------------------------------------------------
/data/icons/hicolor/symbolic/actions/paper-symbolic.svg:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="UTF-8"?>
2 | <svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><g fill="#222222"><path d="m 3 1 c -0.550781 0 -1 0.449219 -1 1 v 13 c 0 0.550781 0.449219 1 1 1 h 10 c 0.550781 0 1 -0.449219 1 -1 v -9.5 c 0 -0.265625 -0.105469 -0.519531 -0.292969 -0.707031 l -3.5 -3.5 c -0.1875 -0.1875 -0.441406 -0.292969 -0.707031 -0.292969 z m 1 2 h 5.085938 l 2.914062 2.914062 v 8.085938 h -8 z m 0 0"/><path d="m 9 2 v 4 h 4 z m 0 0"/></g></svg>
3 | 


--------------------------------------------------------------------------------
/data/icons/hicolor/symbolic/actions/preferences-desktop-apps-symbolic.svg:
--------------------------------------------------------------------------------
1 | <svg xmlns="http://www.w3.org/2000/svg" width="16.006" height="16"><path d="M1 0C.446 0 0 .446 0 1v5c0 .554.446 1 1 1h5c.554 0 1-.446 1-1V1c0-.554-.446-1-1-1zm1 3h3v2H2zm7-3c-.554 0-1 .446-1 1v5c0 .554.446 1 1 1h5c.554 0 1-.446 1-1V1c0-.554-.446-1-1-1zm1 3h3v2h-3zM1 8c-.554 0-1 .446-1 1v5c0 .554.446 1 1 1h5c.554 0 1-.446 1-1V9c0-.554-.446-1-1-1zm1 3h3v2H2zm7-3c-.554 0-1 .446-1 1v5c0 .554.446 1 1 1h5c.554 0 1-.446 1-1V9c0-.554-.446-1-1-1zm1 3h3v2h-3z" fill="#2e3436"/></svg>
2 | 


--------------------------------------------------------------------------------
/data/icons/hicolor/symbolic/actions/preferences-system-time-symbolic.svg:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="UTF-8"?>
2 | <svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
3 |     <g fill="#2e3436">
4 |         <path d="m 8 0 c -4.40625 0 -8 3.59375 -8 8 s 3.59375 8 8 8 s 8 -3.59375 8 -8 s -3.59375 -8 -8 -8 z m 0 2 c 3.324219 0 6 2.671875 6 6 c 0 3.324219 -2.675781 6 -6 6 s -6 -2.675781 -6 -6 c 0 -3.328125 2.675781 -6 6 -6 z m 0 0"/>
5 |         <path d="m 4.929688 4.953125 c -0.128907 0.003906 -0.257813 0.058594 -0.351563 0.152344 c -0.191406 0.195312 -0.1875 0.511719 0.007813 0.707031 l 3.113281 3.042969 c 0.105469 0.097656 0.246093 0.144531 0.386719 0.128906 h 2.914062 c 0.277344 0 0.5 -0.222656 0.5 -0.5 c 0 -0.273437 -0.222656 -0.5 -0.5 -0.5 h -2.761719 l -2.953125 -2.886719 c -0.09375 -0.09375 -0.222656 -0.144531 -0.355468 -0.144531 z m 0 0"/>
6 |     </g>
7 | </svg>
8 | 


--------------------------------------------------------------------------------
/data/icons/hicolor/symbolic/actions/selection-mode-symbolic.svg:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="UTF-8"?>
2 | <svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
3 |     <g fill="#2e3436">
4 |         <path d="m 9.492188 0.140625 c -0.851563 -0.1601562 -1.722657 -0.1796875 -2.574219 -0.0664062 c -1.703125 0.2343752 -3.328125 1.0078122 -4.601563 2.2929692 c -2.546875 2.566406 -3.050781 6.542968 -1.230468 9.664062 c 1.824218 3.125 5.527343 4.636719 9.015624 3.6875 c 3.492188 -0.953125 5.910157 -4.136719 5.894532 -7.753906 c -0.003906 -0.550782 -0.453125 -0.996094 -1.003906 -0.996094 c -0.550782 0.003906 -0.996094 0.453125 -0.996094 1.007812 c 0.011718 2.71875 -1.792969 5.097657 -4.417969 5.8125 c -2.625 0.714844 -5.394531 -0.414062 -6.761719 -2.765624 c -1.371094 -2.351563 -0.996094 -5.316407 0.917969 -7.246094 c 1.917969 -1.933594 4.878906 -2.335938 7.238281 -0.988282 c 0.480469 0.277344 1.09375 0.109376 1.367188 -0.371093 c 0.273437 -0.480469 0.105468 -1.089844 -0.375 -1.363281 c -0.785156 -0.449219 -1.621094 -0.753907 -2.472656 -0.914063 z m 0 0"/>
5 |         <path d="m 15.753906 3.660156 c 0.175782 -0.199218 0.261719 -0.460937 0.246094 -0.726562 c -0.019531 -0.265625 -0.140625 -0.511719 -0.339844 -0.6875 c -0.199218 -0.175782 -0.460937 -0.261719 -0.726562 -0.246094 c -0.265625 0.019531 -0.511719 0.140625 -0.6875 0.339844 l -6.296875 7.195312 l -2.242188 -2.242187 c -0.390625 -0.390625 -1.023437 -0.390625 -1.414062 0 c -0.1875 0.1875 -0.292969 0.441406 -0.292969 0.707031 s 0.105469 0.519531 0.292969 0.707031 l 3 3 c 0.195312 0.195313 0.464843 0.304688 0.742187 0.292969 c 0.273438 -0.007812 0.535156 -0.132812 0.71875 -0.339844 z m 0 0"/>
6 |     </g>
7 | </svg>
8 | 


--------------------------------------------------------------------------------
/data/icons/hicolor/symbolic/actions/system-search-symbolic.svg:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="UTF-8"?>
2 | <svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
3 |     <path d="m 6.5 0 c -3.578125 0 -6.5 2.921875 -6.5 6.5 s 2.921875 6.5 6.5 6.5 c 1.429688 0 2.753906 -0.46875 3.828125 -1.257812 l 3.945313 3.945312 c 0.957031 0.9375 2.363281 -0.5 1.40625 -1.4375 l -3.929688 -3.929688 c 0.785156 -1.074218 1.25 -2.394531 1.25 -3.820312 c 0 -3.578125 -2.921875 -6.5 -6.5 -6.5 z m 0 2 c 2.496094 0 4.5 2.003906 4.5 4.5 s -2.003906 4.5 -4.5 4.5 s -4.5 -2.003906 -4.5 -4.5 s 2.003906 -4.5 4.5 -4.5 z m 0 0" fill="#2e3436"/>
4 | </svg>
5 | 


--------------------------------------------------------------------------------
/data/icons/hicolor/symbolic/actions/system-shutdown-symbolic.svg:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="UTF-8"?>
2 | <svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
3 |     <path d="m 8 0 c -0.550781 0 -1 0.449219 -1 1 v 5 c 0 0.550781 0.449219 1 1 1 s 1 -0.449219 1 -1 v -5 c 0 -0.550781 -0.449219 -1 -1 -1 z m -3.136719 1.816406 c -0.128906 0.015625 -0.253906 0.058594 -0.367187 0.125 c -2.734375 1.582032 -4.074219 4.816406 -3.257813 7.871094 c 0.820313 3.050781 3.59375 5.183594 6.75 5.1875 c 3.160157 0.003906 5.941407 -2.121094 6.765625 -5.167969 c 0.828125 -3.050781 -0.5 -6.289062 -3.230468 -7.878906 c -0.476563 -0.28125 -1.089844 -0.121094 -1.367188 0.359375 c -0.132812 0.226562 -0.171875 0.5 -0.105469 0.757812 c 0.070313 0.257813 0.234375 0.476563 0.464844 0.609376 c 1.957031 1.140624 2.902344 3.441406 2.3125 5.628906 c -0.59375 2.183594 -2.570313 3.695312 -4.832031 3.691406 c -2.265625 -0.003906 -4.238282 -1.519531 -4.824219 -3.707031 s 0.363281 -4.488281 2.324219 -5.621094 c 0.476562 -0.277344 0.640625 -0.886719 0.363281 -1.363281 c -0.132813 -0.230469 -0.347656 -0.398438 -0.605469 -0.464844 c -0.125 -0.035156 -0.257812 -0.042969 -0.390625 -0.027344 z m 0 0" fill="#2e3436"/>
4 | </svg>
5 | 


--------------------------------------------------------------------------------
/data/icons/hicolor/symbolic/actions/system-software-install-symbolic.svg:
--------------------------------------------------------------------------------
1 | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="#474747"><path d="M3 8h10v7.059c0 .492-.472.937-.996.937H4c-.539 0-1-.43-1-1z" style="marker:none" color="#bebebe" overflow="visible"/><path d="M6.793 2.969a1 1 0 00-.752.379L3.225 6.807a1 1 0 00-.225.63v1a1 1 0 102 0v-.644L7.592 4.61a1 1 0 00-.799-1.642zm2.213 3.004a1 1 0 00-.144 1.988l2.17.379v.16a1 1 0 102 0v-1a1 1 0 00-.828-.984L9.205 5.99a1 1 0 00-.199-.017z" style="line-height:normal;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000;text-transform:none;text-orientation:mixed;shape-padding:0;isolation:auto;mix-blend-mode:normal" color="#000" font-weight="400" font-family="sans-serif" overflow="visible"/></g></svg>
2 | 


--------------------------------------------------------------------------------
/data/icons/hicolor/symbolic/actions/view-more-symbolic.svg:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="UTF-8"?>
2 | <svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
3 |     <path d="m 7.996094 0 c -1.105469 0 -2 0.894531 -2 2 s 0.894531 2 2 2 c 1.101562 0 2 -0.894531 2 -2 s -0.898438 -2 -2 -2 z m 0 6 c -1.105469 0 -2 0.894531 -2 2 s 0.894531 2 2 2 c 1.101562 0 2 -0.894531 2 -2 s -0.898438 -2 -2 -2 z m 0 6 c -1.105469 0 -2 0.894531 -2 2 s 0.894531 2 2 2 c 1.101562 0 2 -0.894531 2 -2 s -0.898438 -2 -2 -2 z m 0 0" fill="#2e3436"/>
4 | </svg>
5 | 


--------------------------------------------------------------------------------
/data/icons/hicolor/symbolic/actions/warning-symbolic.svg:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="UTF-8"?>
2 | <svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><path d="m 7.90625 0.105469 c -0.527344 -0.027344 -1.039062 0.28125 -1.4375 0.96875 l -6.25 11.59375 c -0.535156 0.964843 0.046875 2.34375 1.09375 2.34375 h 13.15625 c 0.980469 0 1.902344 -1.160157 1.21875 -2.34375 l -6.3125 -11.53125 c -0.398438 -0.648438 -0.941406 -1.003907 -1.46875 -1.03125 z m 0.0625 3.9375 c 0.542969 -0.019531 1.046875 0.488281 1.03125 1.03125 v 3.9375 c 0.007812 0.527343 -0.472656 1 -1 1 s -1.007812 -0.472657 -1 -1 v -3.9375 c -0.007812 -0.46875 0.355469 -0.914063 0.8125 -1 c 0.050781 -0.015625 0.101562 -0.023438 0.15625 -0.03125 z m 0.03125 6.96875 c 0.550781 0 1 0.449219 1 1 s -0.449219 1 -1 1 s -1 -0.449219 -1 -1 s 0.449219 -1 1 -1 z m 0 0" fill="#222222"/></svg>
3 | 


--------------------------------------------------------------------------------
/data/icons/hicolor/symbolic/apps/bottle-symbolic.svg:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="isolation:isolate" viewBox="0 0 16 16" width="16pt" height="16pt"><defs><clipPath id="_clipPath_m2sEYZWACb6jZ197dFMA3rFv3A6Imlec"><rect width="16" height="16"/></clipPath></defs><g clip-path="url(#_clipPath_m2sEYZWACb6jZ197dFMA3rFv3A6Imlec)"><path d=" M 7 0 L 7 0.616 C 6.685 2.294 5.368 3.781 5.163 5.288 L 5.154 16 L 10.846 16 L 10.838 5.3 C 10.517 3.485 9.453 2.22 9.001 0.616 L 9.001 0 L 7 0 Z " fill="rgb(36,31,49)"/></g></svg>
2 | 


--------------------------------------------------------------------------------
/data/icons/hicolor/symbolic/apps/bottles-steam-symbolic.svg:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?><!-- Generator: Gravit.io --><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="isolation:isolate" viewBox="0 0 40 40" width="40pt" height="40pt"><defs><clipPath id="_clipPath_2nwcEKlK5YA6Uj64adKs0OoQJpLwmHyg"><rect width="40" height="40"/></clipPath></defs><g clip-path="url(#_clipPath_2nwcEKlK5YA6Uj64adKs0OoQJpLwmHyg)"><path d=" M 38.986 20.312 C 38.991 29.723 32.103 37.718 22.795 39.105 C 13.487 40.493 4.567 34.855 1.826 25.852 L 9.011 28.811 C 9.494 31.065 11.389 32.741 13.685 32.945 C 15.982 33.148 18.142 31.831 19.014 29.697 C 19.299 29.011 19.439 28.273 19.425 27.529 L 26.072 22.781 L 26.23 22.781 C 29.158 22.788 31.802 21.028 32.925 18.324 C 34.049 15.62 33.431 12.505 31.36 10.435 C 29.29 8.364 26.175 7.746 23.471 8.87 C 20.767 9.993 19.007 12.636 19.014 15.565 L 19.014 15.66 L 14.376 22.417 C 13.259 22.369 12.153 22.668 11.211 23.272 L 1.003 19.062 L 1.003 19.062 C 1.349 8.573 10.131 0.351 20.62 0.696 C 31.109 1.041 39.331 9.824 38.986 20.312 Z  M 12.841 30.41 L 10.499 29.428 C 11.185 30.877 12.662 31.785 14.264 31.744 C 15.867 31.703 17.294 30.72 17.906 29.238 L 17.906 29.238 C 18.329 28.226 18.329 27.086 17.906 26.073 C 17.501 25.077 16.715 24.285 15.722 23.873 C 14.758 23.478 13.678 23.478 12.715 23.873 L 15.152 24.87 C 16.574 25.573 17.206 27.26 16.595 28.724 C 15.985 30.188 14.341 30.926 12.841 30.41 Z  M 26.23 20.312 C 23.621 20.26 21.543 18.112 21.578 15.503 C 21.612 12.893 23.747 10.801 26.356 10.818 C 28.966 10.835 31.073 12.955 31.073 15.565 C 31.03 18.206 28.872 20.321 26.23 20.312 L 26.23 20.312 Z  M 26.23 19.094 C 27.706 19.1 29.038 18.21 29.598 16.845 C 30.158 15.479 29.834 13.911 28.779 12.879 C 27.724 11.847 26.148 11.558 24.796 12.148 C 23.443 12.738 22.583 14.089 22.622 15.565 C 22.639 17.548 24.247 19.149 26.23 19.157 L 26.23 19.094 Z " fill="rgb(0,0,0)"/></g></svg>
2 | 


--------------------------------------------------------------------------------
/data/icons/hicolor/symbolic/apps/com.usebottles.bottles-symbolic.svg:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 2 | <svg
 3 |    height="16px"
 4 |    viewBox="0 0 16 16"
 5 |    width="16px"
 6 |    version="1.1"
 7 |    id="svg10"
 8 |    xmlns="http://www.w3.org/2000/svg"
 9 |    xmlns:svg="http://www.w3.org/2000/svg">
10 |   <defs
11 |      id="defs14" />
12 |   <path
13 |      id="path40913"
14 |      style="display:inline;fill:#241f31;fill-opacity:1;stroke-width:0.182362;enable-background:new"
15 |      d="m 2.9996528,0 v 0.6155537 c 0,0.37168686 -0.1697087,0.7866069 -0.4048074,1.2390098 C 1.9641554,2.951035 1.315954,4.020961 1.1626522,5.2879648 L 1.1529511,16 h 2.0003298 l 0.0097,-10.7120352 C 3.3162838,4.0209609 3.9644863,2.951035 4.5951752,1.8545635 4.8302749,1.4021606 4.9999836,0.98724056 4.9999836,0.6155537 V 0 Z" />
16 |   <path
17 |      id="path40913-3-6"
18 |      style="display:inline;fill:#241f31;fill-opacity:1;stroke-width:0.182362;enable-background:new"
19 |      d="m 7.0003121,0 v 0.6155537 c 0,0.37168686 -0.1697085,0.7866069 -0.4048083,1.2390098 C 5.9648148,2.951035 5.3166123,4.0209609 5.1633105,5.2879648 L 5.1536095,16 h 2.0003297 l 0.0097,-10.7120352 C 7.316942,4.0209609 7.9651434,2.951035 8.5958335,1.8545635 8.8309322,1.4021606 9.0006408,0.98724056 9.0006408,0.6155537 V 0 Z" />
20 |   <path
21 |      id="path3329"
22 |      style="display:inline;fill:#241f31;fill-opacity:1;stroke-width:0.182362;enable-background:new"
23 |      d="M 11.000978,0 V 0.6155537 C 10.685906,2.2935285 9.3691101,3.7814851 9.1639765,5.2879648 L 9.1542767,16 H 14.847049 L 14.839149,5.3002583 C 14.518062,3.4845031 13.453927,2.2202426 13.001231,0.6155537 V 0 Z" />
24 | </svg>
25 | 


--------------------------------------------------------------------------------
/data/icons/hicolor/symbolic/apps/com.usebottles.bottles.Devel-symbolic.svg:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 2 | <svg
 3 |    height="16px"
 4 |    viewBox="0 0 16 16"
 5 |    width="16px"
 6 |    version="1.1"
 7 |    id="svg10"
 8 |    xmlns="http://www.w3.org/2000/svg"
 9 |    xmlns:svg="http://www.w3.org/2000/svg">
10 |   <defs
11 |      id="defs14" />
12 |   <path
13 |      id="path40913"
14 |      style="display:inline;fill:#241f31;fill-opacity:1;stroke-width:0.182362;enable-background:new"
15 |      d="m 2.9996528,0 v 0.6155537 c 0,0.37168686 -0.1697087,0.7866069 -0.4048074,1.2390098 C 1.9641554,2.951035 1.315954,4.020961 1.1626522,5.2879648 L 1.1529511,16 h 2.0003298 l 0.0097,-10.7120352 C 3.3162838,4.0209609 3.9644863,2.951035 4.5951752,1.8545635 4.8302749,1.4021606 4.9999836,0.98724056 4.9999836,0.6155537 V 0 Z" />
16 |   <path
17 |      id="path40913-3-6"
18 |      style="display:inline;fill:#241f31;fill-opacity:1;stroke-width:0.182362;enable-background:new"
19 |      d="m 7.0003121,0 v 0.6155537 c 0,0.37168686 -0.1697085,0.7866069 -0.4048083,1.2390098 C 5.9648148,2.951035 5.3166123,4.0209609 5.1633105,5.2879648 L 5.1536095,16 h 2.0003297 l 0.0097,-10.7120352 C 7.316942,4.0209609 7.9651434,2.951035 8.5958335,1.8545635 8.8309322,1.4021606 9.0006408,0.98724056 9.0006408,0.6155537 V 0 Z" />
20 |   <path
21 |      id="path3329"
22 |      style="display:inline;fill:#241f31;fill-opacity:1;stroke-width:0.182362;enable-background:new"
23 |      d="M 11.000978,0 V 0.6155537 C 10.685906,2.2935285 9.3691101,3.7814851 9.1639765,5.2879648 L 9.1542767,16 H 14.847049 L 14.839149,5.3002583 C 14.518062,3.4845031 13.453927,2.2202426 13.001231,0.6155537 V 0 Z" />
24 | </svg>
25 | 


--------------------------------------------------------------------------------
/data/icons/meson.build:
--------------------------------------------------------------------------------
 1 | scalable_dir = 'hicolor' / 'scalable' / 'apps'
 2 | install_data(
 3 |   scalable_dir / ('@0@.svg').format(APP_ID),
 4 |   install_dir: get_option('datadir') / 'icons' / scalable_dir
 5 | )
 6 | 
 7 | symbolic_dir = 'hicolor' / 'symbolic' / 'apps'
 8 | install_data(
 9 |   symbolic_dir / ('@0@-symbolic.svg').format(APP_ID),
10 |   install_dir: get_option('datadir') / 'icons' / symbolic_dir
11 | )
12 | 


--------------------------------------------------------------------------------
/data/images/bottles-welcome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bottlesdevs/Bottles/5020d62c2e086de3f407d8667b3d8b4b5ae644f5/data/images/bottles-welcome.png


--------------------------------------------------------------------------------
/data/meson.build:
--------------------------------------------------------------------------------
 1 | conf = configuration_data()
 2 | conf.set('APP_ID', APP_ID)
 3 | conf.set('BASE_ID', BASE_ID)
 4 | conf.set('APP_NAME', APP_NAME)
 5 | conf.set('DEVELOPER_ID', DEVELOPER_ID)
 6 | 
 7 | desktop = i18n.merge_file(
 8 |   input: configure_file(
 9 |     input:  BASE_ID + '.desktop.in.in',
10 |     output:  BASE_ID + '.desktop.in',
11 |     configuration: conf
12 |   ),
13 |   output: APP_ID + '.desktop',
14 |   type: 'desktop',
15 |   po_dir: '../po',
16 |   install: true,
17 |   install_dir: join_paths(get_option('datadir'), 'applications')
18 | )
19 | 
20 | desktop_utils = find_program('desktop-file-validate', required: false)
21 | if desktop_utils.found()
22 |   test('Validate desktop file', desktop_utils,
23 |     args: [desktop]
24 |   )
25 | endif
26 | 
27 | appstream_file = i18n.merge_file(
28 |   input: configure_file(
29 |     input: BASE_ID + '.' + 'metainfo.xml.in.in',
30 |     output: BASE_ID + '.' + 'metainfo.xml.in',
31 |     configuration: conf
32 |   ),
33 |   output: APP_ID + '.' + 'metainfo.xml',
34 |   po_dir: '../po',
35 |   install: true,
36 |   install_dir: get_option('datadir') / 'metainfo'
37 | )
38 | 
39 | gnome.compile_resources('data',
40 |   configure_file(
41 |       input: 'data.gresource.xml.in',
42 |       output: 'data.gresource.xml',
43 |       configuration: conf,
44 |   ),
45 |   gresource_bundle: true,
46 |   install: true,
47 |   install_dir: pkgdatadir,
48 |   dependencies: [appstream_file]
49 | )
50 | 
51 | appstream_util = find_program('appstream-util', required: false)
52 | if appstream_util.found()
53 |   test('Validate appstream file', appstream_util,
54 |     args: ['validate', appstream_file]
55 |   )
56 | endif
57 | 
58 | install_data('com.usebottles.bottles.gschema.xml',
59 |   install_dir: join_paths(get_option('datadir'), 'glib-2.0/schemas')
60 | )
61 | 
62 | compile_schemas = find_program('glib-compile-schemas', required: false)
63 | if compile_schemas.found()
64 |   test('Validate schema file', compile_schemas,
65 |     args: ['--strict', '--dry-run', meson.current_source_dir()]
66 |   )
67 | endif
68 | 
69 | subdir('icons')
70 | 


--------------------------------------------------------------------------------
/data/screenshots/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bottlesdevs/Bottles/5020d62c2e086de3f407d8667b3d8b4b5ae644f5/data/screenshots/1.png


--------------------------------------------------------------------------------
/data/screenshots/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bottlesdevs/Bottles/5020d62c2e086de3f407d8667b3d8b4b5ae644f5/data/screenshots/2.png


--------------------------------------------------------------------------------
/data/screenshots/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bottlesdevs/Bottles/5020d62c2e086de3f407d8667b3d8b4b5ae644f5/data/screenshots/3.png


--------------------------------------------------------------------------------
/data/screenshots/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bottlesdevs/Bottles/5020d62c2e086de3f407d8667b3d8b4b5ae644f5/data/screenshots/4.png


--------------------------------------------------------------------------------
/data/screenshots/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bottlesdevs/Bottles/5020d62c2e086de3f407d8667b3d8b4b5ae644f5/data/screenshots/5.png


--------------------------------------------------------------------------------
/data/screenshots/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bottlesdevs/Bottles/5020d62c2e086de3f407d8667b3d8b4b5ae644f5/data/screenshots/6.png


--------------------------------------------------------------------------------
/docs/screenshot-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bottlesdevs/Bottles/5020d62c2e086de3f407d8667b3d8b4b5ae644f5/docs/screenshot-dark.png


--------------------------------------------------------------------------------
/docs/screenshot-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bottlesdevs/Bottles/5020d62c2e086de3f407d8667b3d8b4b5ae644f5/docs/screenshot-light.png


--------------------------------------------------------------------------------
/meson.build:
--------------------------------------------------------------------------------
 1 | project(
 2 | 	'bottles',
 3 | 	version: '51.18',
 4 | 	meson_version: '>= 1.5.0',
 5 | 	default_options: [
 6 | 		'warning_level=2',
 7 | 	],
 8 | 	license: 'GPL-3.0-only'
 9 | )
10 | 
11 | 
12 | APP_NAME = 'Bottles'
13 | DEVELOPER_ID = 'com.usebottles'
14 | BASE_ID = DEVELOPER_ID + '.' + meson.project_name()
15 | APP_ID = BASE_ID
16 | APP_VERSION = meson.project_version()
17 | _version_array = APP_VERSION.split('.')
18 | APP_MAJOR_VERSION = _version_array[0]
19 | APP_MINOR_VERSION = _version_array[1]
20 | PROFILE = get_option('profile')
21 | 
22 | if PROFILE == 'development'
23 | 	APP_VERSION += '-' + run_command(
24 | 		'git', 'rev-parse', '--short', 'HEAD',
25 | 		check: true
26 | 	).stdout().strip()
27 | 	APP_NAME += ' (Development)'
28 | 	APP_ID += '.' + 'Devel'
29 | endif
30 | 
31 | 
32 | gnome = import('gnome')
33 | i18n = import('i18n')
34 | localedir = get_option('localedir')
35 | 
36 | subdir('po')
37 | subdir('bottles')
38 | subdir('data')
39 | 
40 | gnome.post_install(
41 | 	glib_compile_schemas: true,
42 | 	gtk_update_icon_cache: true,
43 | 	update_desktop_database: true,
44 | )
45 | 


--------------------------------------------------------------------------------
/meson_options.txt:
--------------------------------------------------------------------------------
 1 | option (
 2 |     'profile',
 3 |     type: 'combo',
 4 |     choices: [
 5 |         'default',
 6 |         'development'
 7 |     ],
 8 |     value: 'default'
 9 | )
10 | 


--------------------------------------------------------------------------------
/mypy.ini:
--------------------------------------------------------------------------------
1 | [mypy]
2 | # TODO This errors needs to be fixed one by one
3 | disable_error_code = assignment, index, valid-type, misc, union-attr, arg-type, operator, call-overload, var-annotated, attr-defined
4 | 


--------------------------------------------------------------------------------
/po/LINGUAS:
--------------------------------------------------------------------------------
 1 | it
 2 | fr
 3 | de
 4 | hi
 5 | pt
 6 | es
 7 | nb_NO
 8 | pt_BR
 9 | id
10 | da
11 | nl
12 | tr
13 | sv
14 | ru
15 | eo
16 | zh_Hans
17 | fi
18 | ja
19 | hr
20 | cs
21 | uk
22 | hu
23 | pl
24 | zh_Hant
25 | ko
26 | vi
27 | eu
28 | bg
29 | el
30 | gl
31 | sk
32 | ro
33 | ms
34 | ckb
35 | fa
36 | th
37 | ar
38 | bn
39 | sl
40 | ca
41 | lt
42 | sr
43 | et
44 | ta
45 | he
46 | be
47 | ie
48 | az
49 | bs
50 | ga
51 | ka
52 | 


--------------------------------------------------------------------------------
/po/README.md:
--------------------------------------------------------------------------------
 1 | # Help translating Bottles :rocket:
 2 | Help Bottles get translated in your language!
 3 | 
 4 | ## Improve a translation :raising_hand:
 5 | If you've found typos or just think you can improve a translation, contribute
 6 | using the [Weblate](https://hosted.weblate.org/engage/bottles/) platform.
 7 | 
 8 | Please, this is an open source, free, free project. Don't vandalize the
 9 | translations, it's not funny, it's idiotic.
10 | 
11 | ## Thanks! :two_hearts: :tada:
12 | A heartfelt thanks to anyone who wants to help us get Bottles to speak any language!
13 | 


--------------------------------------------------------------------------------
/po/meson.build:
--------------------------------------------------------------------------------
 1 | i18n = import('i18n')
 2 | fs = import('fs')
 3 | 
 4 | # Add legacy language code alias
 5 | # Fix missing locale for Chinese
 6 | # See also: https://docs.weblate.org/en/latest/faq.html#why-does-weblate-use-language-codes-such-sr-latn-or-zh-hant
 7 | # And: https://github.com/bottlesdevs/Bottles/issues/1692
 8 | language_list = fs.read('LINGUAS').strip().split('\n')
 9 | language_list += ['zh_CN', 'zh_HK', 'zh_SG', 'zh_TW']
10 | 
11 | i18n.gettext(
12 | 	'bottles',
13 | 	install_dir: localedir,
14 | 	preset: 'glib',
15 | 	args: '--from-code=UTF-8',
16 | 	languages: language_list
17 | )
18 | 


--------------------------------------------------------------------------------
/po/zh_CN.po:
--------------------------------------------------------------------------------
1 | zh_Hans.po


--------------------------------------------------------------------------------
/po/zh_HK.po:
--------------------------------------------------------------------------------
1 | zh_Hant.po


--------------------------------------------------------------------------------
/po/zh_SG.po:
--------------------------------------------------------------------------------
1 | zh_Hans.po


--------------------------------------------------------------------------------
/po/zh_TW.po:
--------------------------------------------------------------------------------
1 | zh_Hant.po


--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
 1 | [tool.pytest.ini_options]
 2 | log_cli = true
 3 | log_cli_level = "INFO"
 4 | log_cli_format = "%(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)"
 5 | 
 6 | [tool.ruff]
 7 | exclude = [
 8 | 	"bottles/backend/utils/nvidia.py",
 9 | 	"bottles/backend/utils/vdf.py",
10 | ]
11 | 
12 | [tool.ruff.lint]
13 | ignore = ["F401", "F402", "E722", "E741"]
14 | 


--------------------------------------------------------------------------------
/requirements.dev.txt:
--------------------------------------------------------------------------------
 1 | # Updated using pur -r requirements.txt
 2 | pytest==8.3.4
 3 | requirements-parser==0.11.0
 4 | mypy==1.15.0
 5 | types_Markdown
 6 | types-PyYAML
 7 | types-Pygments
 8 | types_Pygments
 9 | types_colorama
10 | types_pycurl
11 | types_requests
12 | types_docutils
13 | pylint
14 | 


--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
 1 | # Updated using pur -r requirements.txt
 2 | wheel==0.45.1
 3 | PyYAML==6.0.2
 4 | pycurl==7.45.4
 5 | chardet==5.2.0
 6 | requests[use_chardet_on_py3]==2.32.3
 7 | Markdown==3.7
 8 | icoextract==0.1.5
 9 | patool==3.1.3
10 | pathvalidate==3.2.3
11 | FVS==0.3.4
12 | orjson==3.10.15
13 | pycairo==1.27.0
14 | PyGObject==3.50.0
15 | charset-normalizer==3.4.1
16 | numpy==2.2.3
17 | pyfluidsynth==1.3.4
18 | idna==3.10
19 | urllib3==2.3.0
20 | certifi==2025.1.31
21 | pefile==2024.8.26
22 | 


--------------------------------------------------------------------------------