Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Frontend: Add arguments and custom executables to URI #3016

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bottles/backend/globals.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class Paths:
latencyflex = f"{base}/latencyflex"
templates = f"{base}/templates"
library = f"{base}/library.yml"
exe_paths = f"{base}/exe_paths.json"

@staticmethod
def is_vkbasalt_available():
Expand Down
108 changes: 108 additions & 0 deletions bottles/backend/managers/exepath.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# library.py
#
# Copyright 2022 brombinmirko <[email protected]>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, in version 3 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#

import os

from bottles.backend.utils import json

from bottles.backend.logger import Logger
from bottles.backend.globals import Paths

logging = Logger()


class ExePathManager:
"""
The ExePathManager class is used to read and write the mapping
between real and mangled paths from the user exe_paths.json file.
"""

json_path: str = Paths.exe_paths
__paths: dict = {}

def __init__(self):
self.load_paths(silent=True)

def load_paths(self, silent=False):
"""
Loads data from the exe_paths file.
"""
if not os.path.exists(self.json_path):
logging.warning('exe_paths file not found, creating new one')
self.__paths = {}
self.save_paths()
else:
with open(self.json_path, 'r') as paths_file:
self.__paths = json.load(paths_file)

if self.__paths is None:
self.__paths = {}

self.save_paths(silent=silent)

def add_path(self, real_path: str, mangled_path: str):
"""
Adds a new entry to the exe_paths.json file.
"""
if self.__already_exists(real_path):
logging.warning(f'Exe path already exists, nothing to add: {real_path}, {mangled_path}')
return

logging.info(f'Adding new entry to exe paths: {real_path}')

self.__paths[real_path] = mangled_path
self.save_paths()

def __already_exists(self, real_path: str):
"""
Checks if the real path is already in the exe_paths.json file.
"""
for k, _ in self.__paths.items():
if k == real_path:
return True

return False

def remove_path(self, _real_path: str):
"""
Removes an entry from the exe_paths.json file.
"""
if self.__paths.get(_real_path):
logging.info(f'Removing entry from exe paths: {_real_path}')
del self.__paths[_real_path]
self.save_paths()
return
logging.warning(f'Entry not found in exe paths, nothing to remove: {_real_path}')

def save_paths(self, silent=False):
"""
Saves the exe_paths.json file.
"""
with open(self.json_path, 'w') as json_file:
json.dump(self.__paths, json_file)

if not silent:
logging.info(f'Exe paths saved')

def get_mangled_path(self, real_path: str):
return self.__paths.get(real_path)

def get_paths(self):
"""
Returns the exe_paths.json file.
"""
return self.__paths
1 change: 1 addition & 0 deletions bottles/backend/managers/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ bottles_sources = [
'backup.py',
'component.py',
'dependency.py',
'exepath.py',
'installer.py',
'library.py',
'manager.py',
Expand Down
54 changes: 47 additions & 7 deletions bottles/frontend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,22 +193,62 @@ def __process_uri(self, uri):
e.g. xdg-open bottles:run/<bottle>/<program>
"""
uri = uri[0]

import urllib.parse
uri = urllib.parse.unquote(uri)
if os.path.exists(uri):
from bottles.frontend.windows.bottlepicker import BottlePickerDialog
dialog = BottlePickerDialog(application=self, arg_exe=uri)
dialog.present()
return 0

_wrong_uri_error = _("Invalid URI (syntax: bottles:run/<bottle>/<program>)")
if not len(uri) > 0 or not uri.startswith('bottles:run/') or len(uri.split('/')) != 3:
_wrong_uri_error = _("Invalid URI (syntax: bottles:run/<bottle>/<program>/<arguments?> or bottles:runExe/<bottle>//<executable>//<arguments?>)")
if not len(uri) > 0 or not (uri.startswith('bottles:run/') or uri.startswith('bottles:runExe/')) or len(uri.split('/')) < 3:
print(_wrong_uri_error)
return False

uri = uri.replace('bottles:run/', '')
bottle, program = uri.split('/')

import subprocess
subprocess.Popen(['bottles-cli', 'run', '-b', bottle, '-p', program])
if uri.startswith('bottles:run/'):
uri = uri.replace('bottles:run/', '')
if len(uri.split('/')) == 2:
bottle, program = uri.split('/')

import subprocess
subprocess.Popen(['bottles-cli', 'run', '-b', bottle, '-p', program])
else:
split = uri.split('/')
bottle, program, arguments = split[0], split[1], split[2:]

import subprocess
subprocess.Popen(['bottles-cli', 'run', '-b', bottle, '-p', program, '--', '/'.join(arguments)])
elif uri.startswith('bottles:runExe/'):
uri = uri.replace('bottles:runExe/', '')
if len(uri.split('//')) == 2:
bottle, exe = uri.split('//')
can_access_exe = True

from bottles.backend.managers.exepath import ExePathManager
exe_path_manager = ExePathManager()
mangled_path = exe_path_manager.get_mangled_path(exe)

if not mangled_path or not os.path.exists(mangled_path):
can_access_exe = False

if can_access_exe:
import subprocess
subprocess.Popen(['bottles-cli', 'run', '-b', bottle, '-e', mangled_path])
else:
from bottles.frontend.windows.runfiledialog import RunFileDialog
dialog = RunFileDialog(bottle, exe, application=self)
dialog.present()
dialog.hide()
return 0

else:
split = uri.split('//')
bottle, exe, arguments = split[0], split[1], split[2:]

import subprocess
subprocess.Popen(['bottles-cli', 'run', '-b', bottle, '-e', exe, '--', '//'.join(arguments)])

def do_startup(self):
"""
Expand Down
1 change: 1 addition & 0 deletions bottles/frontend/windows/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ bottles_sources = [
'upgradeversioning.py',
'vmtouch.py',
'main_window.py',
'runfiledialog.py',
]

install_data(bottles_sources, install_dir: dialogsdir)
64 changes: 64 additions & 0 deletions bottles/frontend/windows/runfiledialog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# runfiledialog.py
#
# Copyright 2022 brombinmirko <[email protected]>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, in version 3 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#

from gettext import gettext as _

from gi.repository import Gtk, Adw, Gio
from bottles.backend.managers.exepath import ExePathManager

from bottles.frontend.utils.filters import add_all_filters, add_executable_filters
from bottles.frontend.params import APP_ID

class RunFileDialog(Adw.ApplicationWindow):
"""This class should not be called from the application GUI, only from CLI."""
__gtype_name__ = 'RunFileDialog'
settings = Gio.Settings.new(APP_ID)
Adw.init()

def __init__(self, bottle: str, exe: str, **kwargs):
super().__init__(**kwargs)
def execute(_dialog, response):
if response != Gtk.ResponseType.ACCEPT:
self._exit()
return

exe_path_manager = ExePathManager()
# map the flatpak mangled path to `exe`
exe_path_manager.add_path(exe, dialog.get_file().get_path())
import subprocess
subprocess.Popen(['bottles-cli', 'run', '-b', bottle, '-e', dialog.get_file().get_path()])
self._exit()

fd = Gio.File.new_for_path(exe)

dialog = Gtk.FileChooserNative.new(
_("Select Executable"),
self,
Gtk.FileChooserAction.OPEN,
_("Run"),
None
)
import os.path
if os.path.exists(fd.get_path()):
dialog.set_file(fd)
add_executable_filters(dialog)
add_all_filters(dialog)
dialog.connect("response", execute)
dialog.show()

def _exit(self):
self.close()