Skip to content

Commit

Permalink
frontend: runExe and arguments in uri
Browse files Browse the repository at this point in the history
  • Loading branch information
tancop committed Aug 4, 2023
1 parent 38a61ea commit 5480ce5
Show file tree
Hide file tree
Showing 6 changed files with 222 additions and 7 deletions.
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()

0 comments on commit 5480ce5

Please sign in to comment.