mirror of
https://github.com/bottlesdevs/Bottles.git
synced 2026-10-29 07:17:00 +08:00
400 lines
13 KiB
Python
400 lines
13 KiB
Python
# main.py
|
|
#
|
|
# Copyright 2022 brombinmirko <send@mirko.pm>
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, in version 3 of the License.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
|
|
import sys
|
|
import gi
|
|
import gettext
|
|
import locale
|
|
import webbrowser
|
|
from os import path
|
|
|
|
from bottles.backend.logger import Logger
|
|
from bottles.backend.health import HealthChecker
|
|
from bottles.frontend.params import (
|
|
APP_ID,
|
|
APP_MAJOR_VERSION,
|
|
APP_MINOR_VERSION,
|
|
APP_VERSION,
|
|
)
|
|
|
|
gi.require_version("Gtk", "4.0")
|
|
gi.require_version("Adw", "1")
|
|
gi.require_version("GtkSource", "5")
|
|
gi.require_version("Xdp", "1.0")
|
|
# gi.require_version("XdpGtk4", "1.0")
|
|
|
|
# ruff: noqa: E402
|
|
from gi.repository import Gio, GLib, GObject, Adw # type: ignore
|
|
from bottles.frontend.window import BottlesWindow
|
|
from bottles.frontend.preferences import PreferencesWindow
|
|
|
|
|
|
logging = Logger()
|
|
|
|
# region Translations
|
|
"""
|
|
This code snippet searches for and uploads translations to different
|
|
directories, depending on your production or development environment.
|
|
The function _() can be used to create and retrieve translations.
|
|
"""
|
|
share_dir = path.join(sys.prefix, "share")
|
|
base_dir = "."
|
|
|
|
if getattr(sys, "frozen", False):
|
|
base_dir = path.dirname(sys.executable)
|
|
share_dir = path.join(base_dir, "share")
|
|
elif sys.argv[0]:
|
|
exec_dir = path.dirname(path.realpath(sys.argv[0]))
|
|
base_dir = path.dirname(exec_dir)
|
|
share_dir = path.join(base_dir, "share")
|
|
|
|
if not path.exists(share_dir):
|
|
share_dir = base_dir
|
|
|
|
locale_dir = path.join(share_dir, "locale")
|
|
|
|
if not path.exists(locale_dir): # development
|
|
locale_dir = path.join(base_dir, "build", "mo")
|
|
|
|
locale.bindtextdomain("bottles", locale_dir)
|
|
locale.textdomain("bottles")
|
|
gettext.bindtextdomain("bottles", locale_dir)
|
|
gettext.textdomain("bottles")
|
|
_ = gettext.gettext
|
|
|
|
|
|
# endregion
|
|
|
|
|
|
class Bottles(Adw.Application):
|
|
arg_exe = None
|
|
arg_bottle = None
|
|
dark_provider = None
|
|
|
|
def __init__(self):
|
|
super().__init__(
|
|
application_id=APP_ID,
|
|
resource_base_path="/com/usebottles/bottles",
|
|
flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE,
|
|
register_session=True,
|
|
)
|
|
self.__create_action("quit", self.__quit, ["<primary>q", "<primary>w"])
|
|
self.__create_action("about", self.__show_about_dialog)
|
|
self.__create_action("import", self.__show_importer_view, ["<primary>i"])
|
|
self.__create_action("preferences", self.__show_preferences, ["<primary>comma"])
|
|
self.__create_action("help", self.__help, ["F1"])
|
|
self.__create_action("new", self.__new_bottle, ["<primary>n"])
|
|
|
|
self.__register_arguments()
|
|
|
|
def __register_arguments(self):
|
|
"""
|
|
This function registers the command line arguments.
|
|
--version, -v: Prints the version of the application.
|
|
--executable, -e: The path of the executable to be launched.
|
|
--lnk, -l: The path of the shortcut to be launched.
|
|
--bottle, -b: The name of the bottle to be used.
|
|
--arguments, -a: The arguments to be passed to the executable.
|
|
--help, -h: Prints the help.
|
|
"""
|
|
self.add_main_option(
|
|
"version",
|
|
ord("v"),
|
|
GLib.OptionFlags.NONE,
|
|
GLib.OptionArg.NONE,
|
|
_("Show version"),
|
|
None,
|
|
)
|
|
self.add_main_option(
|
|
"executable",
|
|
ord("e"),
|
|
GLib.OptionFlags.NONE,
|
|
GLib.OptionArg.STRING,
|
|
_("Executable path"),
|
|
None,
|
|
)
|
|
self.add_main_option(
|
|
"lnk",
|
|
ord("l"),
|
|
GLib.OptionFlags.NONE,
|
|
GLib.OptionArg.STRING,
|
|
_("lnk path"),
|
|
None,
|
|
)
|
|
self.add_main_option(
|
|
"bottle",
|
|
ord("b"),
|
|
GLib.OptionFlags.NONE,
|
|
GLib.OptionArg.STRING,
|
|
_("Bottle name"),
|
|
None,
|
|
)
|
|
self.add_main_option(
|
|
"arguments",
|
|
ord("a"),
|
|
GLib.OptionFlags.NONE,
|
|
GLib.OptionArg.STRING,
|
|
_("Pass arguments"),
|
|
None,
|
|
)
|
|
self.add_main_option(
|
|
GLib.OPTION_REMAINING,
|
|
0,
|
|
GLib.OptionFlags.NONE,
|
|
GLib.OptionArg.STRING_ARRAY,
|
|
"URI",
|
|
None,
|
|
)
|
|
|
|
def do_command_line(self, command):
|
|
"""
|
|
This function is called when the application is launched from the
|
|
command line. It parses the command line arguments and calls the
|
|
corresponding functions.
|
|
See: __register_arguments()
|
|
"""
|
|
commands = command.get_options_dict()
|
|
|
|
if commands.contains("executable"):
|
|
self.arg_exe = commands.lookup_value("executable").get_string()
|
|
|
|
if commands.contains("version"):
|
|
print(APP_VERSION)
|
|
quit()
|
|
|
|
if commands.contains("bottle"):
|
|
self.arg_bottle = commands.lookup_value("bottle").get_string()
|
|
|
|
if not self.arg_exe:
|
|
"""
|
|
If no executable is specified, look if it was passed without
|
|
the --executable argument.
|
|
"""
|
|
for a in sys.argv:
|
|
if a.endswith((".exe", ".msi", ".bat", ".lnk")):
|
|
self.arg_exe = a
|
|
logging.info(
|
|
_("Launching with executable: {0}").format(a),
|
|
)
|
|
|
|
uri = commands.lookup_value(GLib.OPTION_REMAINING)
|
|
logging.info(
|
|
_("Launching with URI: {0}").format(uri),
|
|
)
|
|
if uri:
|
|
return self.__process_uri(uri)
|
|
|
|
self.do_activate()
|
|
return 0
|
|
|
|
def __process_uri(self, uri):
|
|
"""
|
|
This function processes the URI passed to the application.
|
|
e.g. xdg-open bottles:run/<bottle>/<program>
|
|
"""
|
|
uri = uri[0]
|
|
|
|
if uri.startswith("bottles:run/"):
|
|
if len(uri.split("/")) != 3:
|
|
logging.error(_("Invalid URI (syntax: bottles:run/<bottle>/<program>)"))
|
|
return False
|
|
|
|
uri = uri.replace("bottles:run/", "")
|
|
bottle, program = uri.split("/")
|
|
|
|
import subprocess
|
|
|
|
subprocess.Popen(["bottles-cli", "run", "-b", bottle, "-p", program])
|
|
return 0
|
|
|
|
try:
|
|
from bottles.frontend.bottle_picker_dialog import BottlePickerDialog
|
|
|
|
dialog = BottlePickerDialog(application=self, arg_exe=uri)
|
|
dialog.present()
|
|
return 0
|
|
except Exception as e:
|
|
logging.error(
|
|
_("Error while processing URI: {0}").format(e),
|
|
)
|
|
return False
|
|
|
|
def do_startup(self):
|
|
"""
|
|
This function is called when the application is started.
|
|
Here we register the application actions (shortcuts).
|
|
See: __register_actions()
|
|
"""
|
|
Adw.Application.do_startup(self)
|
|
|
|
def do_activate(self):
|
|
"""
|
|
This function is called when the application is activated.
|
|
"""
|
|
|
|
logging.info(
|
|
_("[Activate] request received."),
|
|
)
|
|
|
|
# create the main window
|
|
Adw.Application.do_activate(self)
|
|
win = self.props.active_window
|
|
if not win:
|
|
win = BottlesWindow(application=self, arg_bottle=self.arg_bottle)
|
|
self.win = win
|
|
|
|
win.present()
|
|
|
|
def __quit(self, *args):
|
|
"""
|
|
This function close the application.
|
|
It is used by the [Ctrl+Q] shortcut.
|
|
"""
|
|
logging.info(
|
|
_("[Quit] request received."),
|
|
)
|
|
self.win.on_close_request()
|
|
quit()
|
|
|
|
@staticmethod
|
|
def __help(action=None, param=None):
|
|
"""
|
|
This function open the documentation in the user's default browser.
|
|
It is used by the [F1] shortcut.
|
|
"""
|
|
logging.info(
|
|
_("[Help] request received."),
|
|
)
|
|
webbrowser.open_new_tab("https://docs.usebottles.com")
|
|
|
|
def __refresh(self, action=None, param=None):
|
|
"""
|
|
This function refresh the user bottle list.
|
|
It is used by the [Ctrl+R] shortcut.
|
|
"""
|
|
logging.info(
|
|
_("[Refresh] request received."),
|
|
)
|
|
self.win.manager.update_bottles()
|
|
|
|
def __show_preferences(self, *args):
|
|
preferences_window = PreferencesWindow(self.win)
|
|
preferences_window.present()
|
|
|
|
def __new_bottle(self, *args):
|
|
self.win.show_add_view()
|
|
|
|
def __show_importer_view(self, widget=False, *args):
|
|
self.win.main_leaf.set_visible_child(self.win.page_importer)
|
|
|
|
def __show_about_dialog(self, *_args):
|
|
developers = [
|
|
"Mirko Brombin https://github.com/mirkobrombin",
|
|
"hthre7 https://github.com/hthre7",
|
|
"Kekun https://github.com/Kekun",
|
|
"Sonny Piers https://github.com/sonnyp",
|
|
"BrainBlasted https://github.com/BrainBlasted",
|
|
"Francesco Masala <mail@francescomasala.me>",
|
|
"Hari Rana (TheEvilSkeleton) https://theevilskeleton.gitlab.io",
|
|
"axtlos https://axtloss.github.io",
|
|
"Oro https://github.com/orowith2os",
|
|
"gregorni https://gitlab.com/gregorni",
|
|
]
|
|
|
|
artists = [
|
|
"Marco Montini https://github.com/marckniack",
|
|
"Noëlle https://github.com/jannuary",
|
|
"Alvar Lagerlöf https://github.com/alvarlagerlof",
|
|
"Ezekiel Smith https://github.com/ZekeSmith",
|
|
]
|
|
|
|
about_dialog = Adw.AboutDialog.new_from_appdata(
|
|
"/com/usebottles/bottles/appdata",
|
|
f"{APP_MAJOR_VERSION}.{APP_MINOR_VERSION}",
|
|
)
|
|
about_dialog.set_developers(developers)
|
|
about_dialog.set_translator_credits(_("translator_credits"))
|
|
about_dialog.set_artists(artists)
|
|
about_dialog.set_debug_info(HealthChecker().get_results(plain=True))
|
|
about_dialog.add_link(_("Donate"), "https://usebottles.com/funding")
|
|
about_dialog.set_copyright(
|
|
_("Copyright © 2017 {developer_name}").format(
|
|
developer_name=about_dialog.get_developer_name()
|
|
)
|
|
)
|
|
about_dialog.add_acknowledgement_section(
|
|
_("Third-Party Libraries and Special Thanks"),
|
|
[
|
|
"DXVK https://github.com/doitsujin/dxvk",
|
|
"VKD3D https://github.com/HansKristian-Work/vkd3d-proton",
|
|
"DXVK-NVAPI https://github.com/jp7677/dxvk-nvapi",
|
|
"LatencyFleX https://github.com/ishitatsuyuki/LatencyFleX",
|
|
"MangoHud https://github.com/flightlessmango/MangoHud",
|
|
"AMD FidelityFX™ Super Resolution https://www.amd.com/en/technologies/fidelityfx-super-resolution",
|
|
"vkBasalt https://github.com/DadSchoorse/vkBasalt",
|
|
"vkbasalt-cli https://gitlab.com/TheEvilSkeleton/vkbasalt-cli",
|
|
"GameMode https://github.com/FeralInteractive/gamemode",
|
|
"Gamescope https://github.com/Plagman/gamescope",
|
|
"OBS Vulkan/OpenGL capture https://github.com/nowrep/obs-vkcapture",
|
|
"Wine-TKG https://github.com/Frogging-Family/wine-tkg-git",
|
|
"Proton https://github.com/ValveSoftware/proton",
|
|
"Wine-GE https://github.com/GloriousEggroll/wine-ge-custom",
|
|
"Wine https://www.winehq.org",
|
|
"orjson https://github.com/ijl/orjson",
|
|
"libadwaita https://gitlab.gnome.org/GNOME/libadwaita",
|
|
"icoextract https://github.com/jlu5/icoextract",
|
|
"vmtouch https://github.com/hoytech/vmtouch",
|
|
"FVS https://github.com/mirkobrombin/FVS",
|
|
"pathvalidate https://github.com/thombashi/pathvalidate",
|
|
],
|
|
)
|
|
about_dialog.add_acknowledgement_section(
|
|
_("Sponsored and Funded by"),
|
|
[
|
|
"JetBrains https://www.jetbrains.com/?from=bottles",
|
|
"GitBook https://www.gitbook.com/?ref=bottles",
|
|
"Linode https://www.linode.com/?from=bottles",
|
|
"Appwrite https://appwrite.io/?from=bottles",
|
|
"Community ❤️ https://usebottles.com/funding",
|
|
],
|
|
)
|
|
about_dialog.present(self.win)
|
|
|
|
def __create_action(self, name, callback, shortcuts=None, param=None):
|
|
"""Add an application action.
|
|
|
|
Args:
|
|
name: the name of the action
|
|
callback: the function to be called when the action is
|
|
activated
|
|
shortcuts: an optional list of accelerators
|
|
param: an optional list of parameters for the action
|
|
"""
|
|
action = Gio.SimpleAction.new(name, param)
|
|
action.connect("activate", callback)
|
|
self.add_action(action)
|
|
if shortcuts:
|
|
self.set_accels_for_action(f"app.{name}", shortcuts)
|
|
|
|
|
|
GObject.threads_init()
|
|
|
|
|
|
def main(version):
|
|
app = Bottles()
|
|
return app.run(sys.argv)
|