From e36db6a41bddc3fe4fb1ec7aace54a00a3e513e4 Mon Sep 17 00:00:00 2001 From: Xodetaetl Date: Wed, 16 Dec 2015 18:40:08 +0100 Subject: [PATCH 01/68] Change "Install" label for installed games --- lutris/gui/gameviews.py | 2 ++ lutris/gui/lutriswindow.py | 1 + 2 files changed, 3 insertions(+) diff --git a/lutris/gui/gameviews.py b/lutris/gui/gameviews.py index 5d457aacf..03c19f579 100644 --- a/lutris/gui/gameviews.py +++ b/lutris/gui/gameviews.py @@ -467,6 +467,8 @@ class ContextualMenu(Gtk.Menu): is_installed = game_row[COL_INSTALLED] hiding_condition = { 'add': is_installed, + 'install': is_installed, + 'install_more': not is_installed, 'play': not is_installed, 'configure': not is_installed, 'desktop-shortcut': ( diff --git a/lutris/gui/lutriswindow.py b/lutris/gui/lutriswindow.py index 15d2ceab9..4dcc0a2b5 100644 --- a/lutris/gui/lutriswindow.py +++ b/lutris/gui/lutriswindow.py @@ -130,6 +130,7 @@ class LutrisWindow(object): ('rm-desktop-shortcut', "Delete desktop shortcut", self.remove_desktop_shortcut), ('menu-shortcut', "Create application menu shortcut", self.create_menu_shortcut), ('rm-menu-shortcut', "Delete application menu shortcut", self.remove_menu_shortcut), + ('install_more', "Install (add) another version", self.on_install_clicked), ('remove', "Remove", self.on_remove_game), ] self.menu = ContextualMenu(main_entries) From d4d82d55622d74c5fbc161281bfc0cbcfa5558d9 Mon Sep 17 00:00:00 2001 From: Mathieu Comandon Date: Thu, 17 Dec 2015 02:54:03 -0800 Subject: [PATCH 02/68] Fix creation of 32bit prefixes when win64 is set --- lutris/runners/wine.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/lutris/runners/wine.py b/lutris/runners/wine.py index 909e16982..b6fb64133 100644 --- a/lutris/runners/wine.py +++ b/lutris/runners/wine.py @@ -14,7 +14,7 @@ WINE_DIR = os.path.join(settings.RUNNER_DIR, "wine") def set_regedit(path, key, value='', type_='REG_SZ', - wine_path=None, prefix=None): + wine_path=None, prefix=None, arch='win32'): """Add keys to the windows registry. Path is something like HKEY_CURRENT_USER\Software\Wine\Direct3D @@ -38,23 +38,23 @@ def set_regedit(path, key, value='', type_='REG_SZ', "%s"=%s """ % (path, key, formatted_value[type_]))) reg_file.close() - set_regedit_file(reg_path, wine_path=wine_path, prefix=prefix) + set_regedit_file(reg_path, wine_path=wine_path, prefix=prefix, arch=arch) os.remove(reg_path) -def set_regedit_file(filename, wine_path=None, prefix=None): +def set_regedit_file(filename, wine_path=None, prefix=None, arch='win32'): """Apply a regedit file to the Windows registry.""" - wineexec('regedit', args=filename, wine_path=wine_path, prefix=prefix) + wineexec('regedit', args=filename, wine_path=wine_path, prefix=prefix, arch=arch) -def delete_registry_key(key, wine_path=None, prefix=None): +def delete_registry_key(key, wine_path=None, prefix=None, arch='win32'): wineexec('regedit', args='/D "%s"' % key, wine_path=wine_path, - prefix=prefix) + prefix=prefix, arch=arch) def create_prefix(prefix, wine_dir=None, arch='win32'): """Create a new Wine prefix.""" - logger.debug("Creating a Wine prefix in %s", prefix) + logger.debug("Creating a %s prefix in %s", arch, prefix) if not wine_dir: wine_dir = os.path.dirname(wine().get_executable()) wineboot_path = os.path.join(wine_dir, 'wineboot') @@ -127,7 +127,7 @@ def winetricks(app, prefix=None, winetricks_env=None, silent=True, wine_path=path, arch=arch, args=args, blocking=blocking) -def winecfg(wine_path=None, prefix=None, blocking=True): +def winecfg(wine_path=None, prefix=None, arch='win32', blocking=True): """Execute winecfg.""" if not wine_path: logger.debug("winecfg: Reverting to default wine") @@ -139,6 +139,7 @@ def winecfg(wine_path=None, prefix=None, blocking=True): env = [] if prefix: env.append('WINEPREFIX="%s" ' % prefix) + env.append('WINEARCH="%s" ' % arch) if settings.RUNNER_DIR in wine_path: runtime32_path = os.path.join(settings.RUNTIME_DIR, "lib32") @@ -499,6 +500,7 @@ class wine(Runner): Get it from the config or detect it from the prefix""" arch = self.game_config.get('arch') or 'auto' if arch not in ('win32', 'win64'): + logger.debug('Arch not provided, auto-detecting') arch = detect_prefix_arch(self.prefix_path) or 'win32' return arch @@ -561,7 +563,7 @@ class wine(Runner): def run_winecfg(self, *args): winecfg(wine_path=self.get_executable(), prefix=self.prefix_path, - blocking=False) + arch=self.wine_arch, blocking=False) def run_regedit(self, *args): wineexec("regedit", wine_path=self.get_executable(), @@ -581,17 +583,19 @@ class wine(Runner): value = self.runner_config.get(key) or 'auto' if not value or value == 'auto': delete_registry_key(path, wine_path=self.get_executable(), - prefix=prefix) + prefix=prefix, arch=self.wine_arch) elif key in self.runner_config: if key == 'Desktop' and value is True: value = 'WineDesktop' set_regedit(path, key, value, - wine_path=self.get_executable(), prefix=prefix) + wine_path=self.get_executable(), prefix=prefix, + arch=self.wine_arch) overrides = self.runner_config.get('overrides') or {} overrides_path = "%s\DllOverrides" % self.reg_prefix for dll, value in overrides.iteritems(): set_regedit(overrides_path, dll, value, - wine_path=self.get_executable(), prefix=prefix) + wine_path=self.get_executable(), + prefix=prefix, arch=self.wine_arch) def prelaunch(self): self.set_regedit_keys() From 22a919c595f787937a8ef7d08eb0692be30fb6bd Mon Sep 17 00:00:00 2001 From: Mathieu Comandon Date: Thu, 17 Dec 2015 17:39:43 -0800 Subject: [PATCH 03/68] Get pids of processes using wine64 when running 64bit Windows programs --- lutris/runners/wine.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lutris/runners/wine.py b/lutris/runners/wine.py index b6fb64133..160c3c5a8 100644 --- a/lutris/runners/wine.py +++ b/lutris/runners/wine.py @@ -617,6 +617,8 @@ class wine(Runner): exe = self.get_executable() if not exe.startswith('/'): exe = system.find_executable(exe) + if self.wine_arch == 'win64': + exe += '64' return system.get_pids_using_file(exe) def get_xinput_path(self): From a5da27bfe62b5ddc7ba59c1caae994033e864e1a Mon Sep 17 00:00:00 2001 From: Mathieu Comandon Date: Sat, 19 Dec 2015 17:05:19 -0800 Subject: [PATCH 04/68] Save steamid in database for manually added games --- lutris/game.py | 2 ++ lutris/gui/config_dialogs.py | 2 ++ lutris/sync.py | 3 ++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lutris/game.py b/lutris/game.py index eb16eb962..c8b8188fa 100644 --- a/lutris/game.py +++ b/lutris/game.py @@ -60,6 +60,7 @@ class Game(object): self.is_installed = bool(game_data.get('installed')) or False self.year = game_data.get('year') or '' self.game_config_id = game_data.get('configpath') or '' + self.steamid = game_data.get('steamid') or '' self.load_config() self.resolution_changed = False @@ -123,6 +124,7 @@ class Game(object): directory=self.directory, installed=self.is_installed, configpath=self.config.game_config_id, + steamid=self.steamid, id=self.id ) diff --git a/lutris/gui/config_dialogs.py b/lutris/gui/config_dialogs.py index 0acfe469a..53f5d1251 100644 --- a/lutris/gui/config_dialogs.py +++ b/lutris/gui/config_dialogs.py @@ -245,6 +245,8 @@ class GameDialogCommon(object): self.game.config = self.lutris_config self.game.directory = runner.game_path self.game.is_installed = True + if self.runner_name in ('steam', 'winesteam'): + self.game.steamid = self.lutris_config.game_config['appid'] self.game.save() self.destroy() logger.debug("Saved %s", name) diff --git a/lutris/sync.py b/lutris/sync.py index bb139c085..e03b7a0ec 100644 --- a/lutris/sync.py +++ b/lutris/sync.py @@ -184,7 +184,8 @@ class Sync(object): continue if runner == 'winesteam' and not winesteamrunner.is_installed(): continue - logger.debug("Setting %s as uninstalled" % game_info['name']) + logger.debug("Setting %(name)s (%(steamid)s) as uninstalled", game_info) + game_id = pga.add_or_update( name=game_info['name'], runner='', From 042d23dcdb65c27fca77fa9015984db6eb9f350a Mon Sep 17 00:00:00 2001 From: Xodetaetl Date: Mon, 21 Dec 2015 14:47:03 +0100 Subject: [PATCH 05/68] Fix Configure Manually from NoInstallerDialog + make dialog non-blocking --- lutris/gui/dialogs.py | 2 +- lutris/gui/installgamedialog.py | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/lutris/gui/dialogs.py b/lutris/gui/dialogs.py index 03a29e203..1aa07e0a0 100644 --- a/lutris/gui/dialogs.py +++ b/lutris/gui/dialogs.py @@ -301,6 +301,6 @@ class NoInstallerDialog(Gtk.MessageDialog): self.format_secondary_text("No installer is available for this game") self.add_buttons("Configure manually", self.MANUAL_CONF, "Write installer", self.NEW_INSTALLER, - "Exit", self.EXIT) + "Close", self.EXIT) self.result = self.run() self.destroy() diff --git a/lutris/gui/installgamedialog.py b/lutris/gui/installgamedialog.py index a651df6af..8215d14b0 100644 --- a/lutris/gui/installgamedialog.py +++ b/lutris/gui/installgamedialog.py @@ -4,7 +4,7 @@ from gi.repository import Gtk, Pango import webbrowser import yaml -from lutris import settings, shortcuts +from lutris import pga, settings, shortcuts from lutris.installer import interpreter from lutris.game import Game from lutris.gui.config_dialogs import AddGameDialog @@ -131,11 +131,12 @@ class InstallerDialog(Gtk.Window): """Open dialog for 'no script available' situation.""" dlg = NoInstallerDialog(self) if dlg.result == dlg.MANUAL_CONF: - game = Game(self.game_ref) - game_dialog = AddGameDialog(self, game) - game_dialog.run() - if game_dialog.saved: - self.notify_install_success() + game_data = pga.get_game_by_field(self.game_ref, 'slug') + game = Game(game_data['id']) + game_dialog = AddGameDialog( + self.parent.window, game, + callback=lambda: self.notify_install_success(game_data['id']) + ) elif dlg.result == dlg.NEW_INSTALLER: installer_url = settings.SITE_URL + "games/%s/" % self.game_ref webbrowser.open(installer_url) @@ -416,9 +417,10 @@ class InstallerDialog(Gtk.Window): self.set_urgency_hint(True) # Blink in taskbar self.connect('focus-in-event', self.on_window_focus) - def notify_install_success(self): + def notify_install_success(self, game_id=None): + game_id = game_id or self.interpreter.game_id if self.parent: - self.parent.view.emit('game-installed', self.interpreter.game_id) + self.parent.view.emit('game-installed', game_id) def on_window_focus(self, widget, *args): self.set_urgency_hint(False) From c0ed2e4e816097b408b5cb0a87fa10bb07196f21 Mon Sep 17 00:00:00 2001 From: Xodetaetl Date: Mon, 21 Dec 2015 15:20:03 +0100 Subject: [PATCH 06/68] Make other instantiations of AddGameDialog non-blocking --- lutris/gui/lutriswindow.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lutris/gui/lutriswindow.py b/lutris/gui/lutriswindow.py index cdfb08aba..22b1c6069 100644 --- a/lutris/gui/lutriswindow.py +++ b/lutris/gui/lutriswindow.py @@ -208,7 +208,7 @@ class LutrisWindow(object): import http # Move me AsyncCall(http.download_content, on_version_received, - 'https://lutris.net/version') + 'https://lutris.net/version') def get_view_type(self): view_type = settings.read_setting('view_type') @@ -513,13 +513,13 @@ class LutrisWindow(object): self.view.update_image(game_id, is_installed) def add_manually(self, *args): - game = Game(self.view.selected_game) - add_game_dialog = AddGameDialog(self.window, game) - add_game_dialog.run() - if add_game_dialog.saved: + def on_game_added(game): self.view.set_installed(game) self.sidebar_treeview.update() + game = Game(self.view.selected_game) + AddGameDialog(self.window, game, callback=lambda: on_game_added(game)) + def on_view_game_log_activate(self, widget): if not self.running_game: dialogs.ErrorDialog('No game log available') @@ -532,10 +532,10 @@ class LutrisWindow(object): def add_game(self, _widget, _data=None): """Add a new game.""" - add_game_dialog = AddGameDialog(self.window) - add_game_dialog.run() - if add_game_dialog.saved: - self.add_game_to_view(add_game_dialog.game.id) + dialog = AddGameDialog( + self.window, + callback=lambda: self.add_game_to_view(dialog.game.id) + ) def add_game_to_view(self, game_id, async=True): if not game_id: From 9fb177557a6fceccd277554726124b078206e817 Mon Sep 17 00:00:00 2001 From: Xodetaetl Date: Mon, 21 Dec 2015 19:54:55 +0100 Subject: [PATCH 07/68] Add "Install runners" button next to Runners menu in AddGameDialog --- lutris/gui/config_dialogs.py | 58 +++++++++++++++++++++++++----------- lutris/gui/runnersdialog.py | 5 ++++ 2 files changed, 45 insertions(+), 18 deletions(-) diff --git a/lutris/gui/config_dialogs.py b/lutris/gui/config_dialogs.py index 53f5d1251..65e904dd3 100644 --- a/lutris/gui/config_dialogs.py +++ b/lutris/gui/config_dialogs.py @@ -4,9 +4,10 @@ from gi.repository import Gtk, Pango from lutris import runners, settings from lutris.config import LutrisConfig, TEMP_CONFIG, make_game_config_id from lutris.game import Game +from lutris import gui +from lutris.gui.config_boxes import GameBox, RunnerBox, SystemBox from lutris.gui.dialogs import ErrorDialog from lutris.gui.widgets import VBox, Dialog -from lutris.gui.config_boxes import GameBox, RunnerBox, SystemBox from lutris.util.log import logger from lutris.util.strings import slugify @@ -39,21 +40,21 @@ class GameDialogCommon(object): def get_runner_dropdown(self): runner_liststore = self.get_runner_liststore() - self.runner_dropdown = Gtk.ComboBox.new_with_model(runner_liststore) - self.runner_dropdown.set_id_column(1) + runner_dropdown = Gtk.ComboBox.new_with_model(runner_liststore) + runner_dropdown.set_id_column(1) runner_index = 0 if self.game: for runner in runner_liststore: if self.runner_name == str(runner[1]): break runner_index += 1 - self.runner_dropdown.set_active(runner_index) - self.runner_dropdown.connect("changed", self.on_runner_changed) + runner_dropdown.set_active(runner_index) + runner_dropdown.connect("changed", self.on_runner_changed) cell = Gtk.CellRendererText() cell.props.ellipsize = Pango.EllipsizeMode.END - self.runner_dropdown.pack_start(cell, True) - self.runner_dropdown.add_attribute(cell, 'text', 0) - return self.runner_dropdown + runner_dropdown.pack_start(cell, True) + runner_dropdown.add_attribute(cell, 'text', 0) + return runner_dropdown @staticmethod def build_scrolled_window(widget): @@ -72,12 +73,15 @@ class GameDialogCommon(object): def build_info_tab(self): info_box = VBox() + + # Game name self.name_entry = Gtk.Entry() if self.game: self.name_entry.set_text(self.game.name) name_box = self.build_entry_box(self.name_entry, "Name") info_box.pack_start(name_box, False, False, 5) + # Game slug if self.game: self.slug_entry = Gtk.Entry() self.slug_entry.set_text(self.game.slug) @@ -85,17 +89,36 @@ class GameDialogCommon(object): slug_box = self.build_entry_box(self.slug_entry, "Identifier") info_box.pack_start(slug_box, False, False, 5) - runner_box = Gtk.HBox() - label = Gtk.Label("Runner") - label.set_alignment(0.5, 0.5) - runner_dropdown = self.get_runner_dropdown() - runner_box.pack_start(label, False, False, 20) - runner_box.pack_start(runner_dropdown, False, False, 20) - info_box.pack_start(runner_box, False, False, 5) + # Runner + self.runner_box = self.get_runner_box() + info_box.pack_start(self.runner_box, False, False, 5) info_sw = self.build_scrolled_window(info_box) self.add_notebook_tab(info_sw, "Game info") + def get_runner_box(self): + runner_box = Gtk.HBox() + label = Gtk.Label("Runner") + label.set_alignment(0.5, 0.5) + self.runner_dropdown = self.get_runner_dropdown() + install_runners_btn = Gtk.Button(label="Install runners") + install_runners_btn.connect('clicked', self.on_install_runners_clicked) + install_runners_btn.set_margin_right(20) + + runner_box.pack_start(label, False, False, 20) + runner_box.pack_start(self.runner_dropdown, False, False, 20) + runner_box.pack_start(install_runners_btn, False, False, 0) + return runner_box + + def on_install_runners_clicked(self, _button): + runners_dialog = gui.runnersdialog.RunnersDialog() + runners_dialog.connect("runner-installed", self.update_runner_dropdown) + + def update_runner_dropdown(self, _widget): + active_id = self.runner_dropdown.get_active_id() + self.runner_dropdown.set_model(self.get_runner_liststore()) + self.runner_dropdown.set_active_id(active_id) + def build_game_tab(self): if self.game and self.runner_name: self.game.runner_name = self.runner_name @@ -257,8 +280,7 @@ class GameDialogCommon(object): class AddGameDialog(Dialog, GameDialogCommon): """Add game dialog class.""" - - def __init__(self, parent, game=None): + def __init__(self, parent, game=None, callback=None): super(AddGameDialog, self).__init__("Add a new game", parent=parent) self.game = game self.saved = False @@ -277,7 +299,7 @@ class AddGameDialog(Dialog, GameDialogCommon): level='game') self.build_notebook() self.build_tabs('game') - self.build_action_area("Add", self.on_save) + self.build_action_area("Add", self.on_save, callback) self.name_entry.grab_focus() self.show_all() diff --git a/lutris/gui/runnersdialog.py b/lutris/gui/runnersdialog.py index b972d02ea..a7e58015b 100644 --- a/lutris/gui/runnersdialog.py +++ b/lutris/gui/runnersdialog.py @@ -11,6 +11,9 @@ from lutris.gui.runnerinstalldialog import RunnerInstallDialog class RunnersDialog(Gtk.Window): """Dialog to manage the runners.""" + __gsignals__ = { + "runner-installed": (GObject.SIGNAL_RUN_FIRST, None, ()), + } def __init__(self): GObject.GObject.__init__(self) @@ -140,6 +143,7 @@ class RunnersDialog(Gtk.Window): """Install a runner.""" runner.install() if runner.is_installed(): + self.emit('runner-installed') widget.hide() runner_label.set_sensitive(True) @@ -154,6 +158,7 @@ class RunnersDialog(Gtk.Window): def set_install_state(self, widget, runner, runner_label): if runner.is_installed(): runner_label.set_sensitive(True) + self.emit('runner-installed') else: runner_label.set_sensitive(False) From ab1a9522f6a43485adfbc7ac955dbf3db74b0282 Mon Sep 17 00:00:00 2001 From: Xodetaetl Date: Mon, 21 Dec 2015 19:57:06 +0100 Subject: [PATCH 08/68] Refactor : some cleanup + methods reordering in AddGameDialog --- lutris/gui/config_dialogs.py | 193 ++++++++++++++++---------------- lutris/gui/installgamedialog.py | 13 +-- 2 files changed, 103 insertions(+), 103 deletions(-) diff --git a/lutris/gui/config_dialogs.py b/lutris/gui/config_dialogs.py index 65e904dd3..bdab42d7c 100644 --- a/lutris/gui/config_dialogs.py +++ b/lutris/gui/config_dialogs.py @@ -19,18 +19,50 @@ class GameDialogCommon(object): no_runner_label = "Select a runner in the Game Info tab" @staticmethod - def get_runner_liststore(): - """Build a ListStore with available runners.""" - runner_liststore = Gtk.ListStore(str, str) - runner_liststore.append(("Select a runner from the list", "")) - for runner in runners.get_installed(): - description = runner.description - runner_liststore.append( - ("%s (%s)" % (runner.name, description), runner.name) - ) - return runner_liststore + def build_scrolled_window(widget): + scrolled_window = Gtk.ScrolledWindow() + scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, + Gtk.PolicyType.AUTOMATIC) + scrolled_window.add_with_viewport(widget) + return scrolled_window - def build_entry_box(self, entry, label_text=None): + def build_notebook(self): + self.notebook = Gtk.Notebook() + self.vbox.pack_start(self.notebook, True, True, 10) + + def build_tabs(self, config_level): + if config_level == 'game': + self._build_info_tab() + self._build_game_tab() + self._build_runner_tab(config_level) + self._build_system_tab(config_level) + + def _build_info_tab(self): + info_box = VBox() + + # Game name + self.name_entry = Gtk.Entry() + if self.game: + self.name_entry.set_text(self.game.name) + name_box = self._build_entry_box(self.name_entry, "Name") + info_box.pack_start(name_box, False, False, 5) + + # Game slug + if self.game: + self.slug_entry = Gtk.Entry() + self.slug_entry.set_text(self.game.slug) + self.slug_entry.set_sensitive(False) + slug_box = self._build_entry_box(self.slug_entry, "Identifier") + info_box.pack_start(slug_box, False, False, 5) + + # Runner + self.runner_box = self._get_runner_box() + info_box.pack_start(self.runner_box, False, False, 5) + + info_sw = self.build_scrolled_window(info_box) + self._add_notebook_tab(info_sw, "Game info") + + def _build_entry_box(self, entry, label_text=None): box = Gtk.HBox() if label_text: label = Gtk.Label(label=label_text) @@ -38,8 +70,22 @@ class GameDialogCommon(object): box.pack_start(entry, True, True, 20) return box - def get_runner_dropdown(self): - runner_liststore = self.get_runner_liststore() + def _get_runner_box(self): + runner_box = Gtk.HBox() + label = Gtk.Label("Runner") + label.set_alignment(0.5, 0.5) + self.runner_dropdown = self._get_runner_dropdown() + install_runners_btn = Gtk.Button(label="Install runners") + install_runners_btn.connect('clicked', self.on_install_runners_clicked) + install_runners_btn.set_margin_right(20) + + runner_box.pack_start(label, False, False, 20) + runner_box.pack_start(self.runner_dropdown, False, False, 20) + runner_box.pack_start(install_runners_btn, False, False, 0) + return runner_box + + def _get_runner_dropdown(self): + runner_liststore = self._get_runner_liststore() runner_dropdown = Gtk.ComboBox.new_with_model(runner_liststore) runner_dropdown.set_id_column(1) runner_index = 0 @@ -57,69 +103,28 @@ class GameDialogCommon(object): return runner_dropdown @staticmethod - def build_scrolled_window(widget): - scrolled_window = Gtk.ScrolledWindow() - scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, - Gtk.PolicyType.AUTOMATIC) - scrolled_window.add_with_viewport(widget) - return scrolled_window - - def build_notebook(self): - self.notebook = Gtk.Notebook() - self.vbox.pack_start(self.notebook, True, True, 10) - - def add_notebook_tab(self, widget, label): - self.notebook.append_page(widget, Gtk.Label(label=label)) - - def build_info_tab(self): - info_box = VBox() - - # Game name - self.name_entry = Gtk.Entry() - if self.game: - self.name_entry.set_text(self.game.name) - name_box = self.build_entry_box(self.name_entry, "Name") - info_box.pack_start(name_box, False, False, 5) - - # Game slug - if self.game: - self.slug_entry = Gtk.Entry() - self.slug_entry.set_text(self.game.slug) - self.slug_entry.set_sensitive(False) - slug_box = self.build_entry_box(self.slug_entry, "Identifier") - info_box.pack_start(slug_box, False, False, 5) - - # Runner - self.runner_box = self.get_runner_box() - info_box.pack_start(self.runner_box, False, False, 5) - - info_sw = self.build_scrolled_window(info_box) - self.add_notebook_tab(info_sw, "Game info") - - def get_runner_box(self): - runner_box = Gtk.HBox() - label = Gtk.Label("Runner") - label.set_alignment(0.5, 0.5) - self.runner_dropdown = self.get_runner_dropdown() - install_runners_btn = Gtk.Button(label="Install runners") - install_runners_btn.connect('clicked', self.on_install_runners_clicked) - install_runners_btn.set_margin_right(20) - - runner_box.pack_start(label, False, False, 20) - runner_box.pack_start(self.runner_dropdown, False, False, 20) - runner_box.pack_start(install_runners_btn, False, False, 0) - return runner_box + def _get_runner_liststore(): + """Build a ListStore with available runners.""" + runner_liststore = Gtk.ListStore(str, str) + runner_liststore.append(("Select a runner from the list", "")) + for runner in runners.get_installed(): + description = runner.description + runner_liststore.append( + ("%s (%s)" % (runner.name, description), runner.name) + ) + return runner_liststore def on_install_runners_clicked(self, _button): runners_dialog = gui.runnersdialog.RunnersDialog() - runners_dialog.connect("runner-installed", self.update_runner_dropdown) + runners_dialog.connect("runner-installed", + self._update_runner_dropdown) - def update_runner_dropdown(self, _widget): + def _update_runner_dropdown(self, _widget): active_id = self.runner_dropdown.get_active_id() - self.runner_dropdown.set_model(self.get_runner_liststore()) + self.runner_dropdown.set_model(self._get_runner_liststore()) self.runner_dropdown.set_active_id(active_id) - def build_game_tab(self): + def _build_game_tab(self): if self.game and self.runner_name: self.game.runner_name = self.runner_name try: @@ -135,35 +140,23 @@ class GameDialogCommon(object): game_sw = self.build_scrolled_window(self.game_box) else: game_sw = Gtk.Label(label=self.no_runner_label) - self.add_notebook_tab(game_sw, "Game options") + self._add_notebook_tab(game_sw, "Game options") - def build_runner_tab(self, config_level): + def _build_runner_tab(self, config_level): if self.runner_name: self.runner_box = RunnerBox(self.lutris_config) runner_sw = self.build_scrolled_window(self.runner_box) else: runner_sw = Gtk.Label(label=self.no_runner_label) - self.add_notebook_tab(runner_sw, "Runner options") + self._add_notebook_tab(runner_sw, "Runner options") - def build_system_tab(self, config_level): + def _build_system_tab(self, config_level): self.system_box = SystemBox(self.lutris_config) self.system_sw = self.build_scrolled_window(self.system_box) - self.add_notebook_tab(self.system_sw, "System options") + self._add_notebook_tab(self.system_sw, "System options") - def build_tabs(self, config_level): - if config_level == 'game': - self.build_info_tab() - self.build_game_tab() - self.build_runner_tab(config_level) - self.build_system_tab(config_level) - - def rebuild_tabs(self): - for i in range(self.notebook.get_n_pages(), 1, -1): - self.notebook.remove_page(i - 1) - self.build_game_tab() - self.build_runner_tab('game') - self.build_system_tab('game') - self.show_all() + def _add_notebook_tab(self, widget, label): + self.notebook.append_page(widget, Gtk.Label(label=label)) def build_action_area(self, label, button_callback, callback2=None): self.action_area.set_layout(Gtk.ButtonBoxStyle.EDGE) @@ -190,7 +183,13 @@ class GameDialogCommon(object): hbox.pack_start(button, True, True, 0) self.action_area.pack_start(hbox, True, True, 0) - def set_advanced_options_visible(self, value): + def on_show_advanced_options_toggled(self, checkbox): + value = True if checkbox.get_active() else False + settings.write_setting('show_advanced_options', value) + + self._set_advanced_options_visible(value) + + def _set_advanced_options_visible(self, value): """Change visibility of advanced options across all config tabs.""" widgets = self.system_box.get_children() if self.runner_name: @@ -205,12 +204,6 @@ class GameDialogCommon(object): widget.set_no_show_all(not value) widget.show_all() - def on_show_advanced_options_toggled(self, checkbox): - value = True if checkbox.get_active() else False - settings.write_setting('show_advanced_options', value) - - self.set_advanced_options_visible(value) - def on_runner_changed(self, widget): """Action called when runner drop down is changed.""" runner_index = widget.get_active() @@ -227,9 +220,17 @@ class GameDialogCommon(object): level='game' ) - self.rebuild_tabs() + self._rebuild_tabs() self.notebook.set_current_page(current_page) + def _rebuild_tabs(self): + for i in range(self.notebook.get_n_pages(), 1, -1): + self.notebook.remove_page(i - 1) + self._build_game_tab() + self._build_runner_tab('game') + self._build_system_tab('game') + self.show_all() + def on_cancel_clicked(self, widget=None): """Dialog destroy callback.""" self.destroy() diff --git a/lutris/gui/installgamedialog.py b/lutris/gui/installgamedialog.py index 8215d14b0..7e78fd232 100644 --- a/lutris/gui/installgamedialog.py +++ b/lutris/gui/installgamedialog.py @@ -133,7 +133,7 @@ class InstallerDialog(Gtk.Window): if dlg.result == dlg.MANUAL_CONF: game_data = pga.get_game_by_field(self.game_ref, 'slug') game = Game(game_data['id']) - game_dialog = AddGameDialog( + AddGameDialog( self.parent.window, game, callback=lambda: self.notify_install_success(game_data['id']) ) @@ -156,7 +156,6 @@ class InstallerDialog(Gtk.Window): for index, script in enumerate(self.scripts): for item in ['description', 'notes']: script[item] = (script.get(item) or '').encode('utf-8') - description = script['description'] runner = script['runner'] version = script['version'] label = "{} ({})".format(version, runner) @@ -179,11 +178,11 @@ class InstallerDialog(Gtk.Window): self.installer_choice_box.pack_start(label, True, True, padding) return label - self.description_label = _create_label(10, - "{}".format(self.scripts[0]['description']) + self.description_label = _create_label( + 10, "{}".format(self.scripts[0]['description']) ) - self.notes_label = _create_label(5, - "{}".format(self.scripts[0]['notes']) + self.notes_label = _create_label( + 5, "{}".format(self.scripts[0]['notes']) ) self.widget_box.pack_start(self.installer_choice_box, False, False, 10) @@ -198,7 +197,7 @@ class InstallerDialog(Gtk.Window): def on_installer_toggled(self, btn, script_index): description = self.scripts[script_index]['description'] self.description_label.set_markup( - "{}".format(self.scripts[script_index]['description']) + "{}".format(description) ) self.notes_label.set_markup( "{}".format(self.scripts[script_index]['notes']) From 228e23586fae1ef27d31e15697fcaa5ad59ea14e Mon Sep 17 00:00:00 2001 From: Xodetaetl Date: Mon, 21 Dec 2015 20:57:07 +0100 Subject: [PATCH 09/68] Update test --- tests/test_dialogs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_dialogs.py b/tests/test_dialogs.py index 1564b7202..b1ff800a6 100644 --- a/tests/test_dialogs.py +++ b/tests/test_dialogs.py @@ -15,7 +15,7 @@ TEST_PGA_PATH = os.path.join(os.path.dirname(__file__), 'pga.db') class TestGameDialogCommon(TestCase): def test_get_runner_liststore(self): dlg = config_dialogs.GameDialogCommon() - list_store = dlg.get_runner_liststore() + list_store = dlg._get_runner_liststore() self.assertTrue( list_store[1][0].startswith(runners.get_installed()[0].name) ) From 2c0d504d0f2d88fcc1874bfbd3a61b9e106a5d3b Mon Sep 17 00:00:00 2001 From: Xodetaetl Date: Mon, 21 Dec 2015 21:24:35 +0100 Subject: [PATCH 10/68] Move runtime updating before library sync --- lutris/gui/lutriswindow.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lutris/gui/lutriswindow.py b/lutris/gui/lutriswindow.py index 108ab86a7..ef96f7851 100644 --- a/lutris/gui/lutriswindow.py +++ b/lutris/gui/lutriswindow.py @@ -159,6 +159,8 @@ class LutrisWindow(object): self.init_game_store() + self.update_runtime() + # Connect account and/or sync credentials = api.read_api_key() if credentials: @@ -297,7 +299,6 @@ class LutrisWindow(object): icons_sync = AsyncCall(self.sync_icons, None, stoppable=True) self.threads_stoppers.append(icons_sync.stop_request.set) self.set_status("Library synced") - self.update_runtime() def update_runtime(self): cancellables = runtime.update(self.set_status) From e4f71f96d683230ae41a4c47e529099554ef04dd Mon Sep 17 00:00:00 2001 From: Xodetaetl Date: Tue, 22 Dec 2015 17:44:35 +0100 Subject: [PATCH 11/68] Add Runner.uninstall() --- lutris/runners/runner.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lutris/runners/runner.py b/lutris/runners/runner.py index 18246a641..f2b53160d 100644 --- a/lutris/runners/runner.py +++ b/lutris/runners/runner.py @@ -2,6 +2,7 @@ """Generic runner.""" import os import platform +import shutil from gi.repository import Gtk @@ -29,7 +30,6 @@ def get_arch(): class Runner(object): """Generic runner (base class for other runners).""" - multiple_versions = False platform = NotImplemented runnable_alone = False @@ -282,7 +282,8 @@ class Runner(object): """GObject callback received by downloader""" self.extract(**user_data) - def extract(self, archive=None, dest=None, merge_single=None, callback=None): + def extract(self, archive=None, dest=None, merge_single=None, + callback=None): if not os.path.exists(archive): logger.error("Can't find %s, aborting install", archive) return False @@ -295,3 +296,8 @@ class Runner(object): def remove_game_data(self, game_path=None): system.remove_folder(game_path) + + def uninstall(self): + runner_path = os.path.join(settings.RUNNER_DIR, self.name) + if os.path.isdir(runner_path): + shutil.rmtree(runner_path) From 501a9b8338a24d2f4593b926f87ce4ed425b6117 Mon Sep 17 00:00:00 2001 From: Xodetaetl Date: Tue, 22 Dec 2015 18:23:08 +0100 Subject: [PATCH 12/68] Add migration to update runners --- lutris/migrations/__init__.py | 6 +++++- lutris/migrations/update_runners.py | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 lutris/migrations/update_runners.py diff --git a/lutris/migrations/__init__.py b/lutris/migrations/__init__.py index 3a275513a..cf4c13f78 100644 --- a/lutris/migrations/__init__.py +++ b/lutris/migrations/__init__.py @@ -1,7 +1,7 @@ from lutris import settings from lutris.util.log import logger -MIGRATION_VERSION = 2 +MIGRATION_VERSION = 3 MIGRATIONS = [] @@ -14,6 +14,10 @@ MIGRATIONS.append([ 'fix_missing_steam_appids', ]) +MIGRATIONS.append([ + 'update_runners', +]) + def get_migration_module(migration_name): return __import__('lutris.migrations.%s' % migration_name, diff --git a/lutris/migrations/update_runners.py b/lutris/migrations/update_runners.py new file mode 100644 index 000000000..8e5d2ad57 --- /dev/null +++ b/lutris/migrations/update_runners.py @@ -0,0 +1,16 @@ +import os +import shutil +from lutris.settings import RUNNER_DIR + + +def migrate(): + for dirname in os.listdir(RUNNER_DIR): + path = os.path.join(RUNNER_DIR, dirname) + if not os.path.isdir(path): + return + if dirname in ['dgen', 'dolphin', 'dosbox', 'frotz', 'fs-uae', + 'hatari', 'jzintv', 'mame', 'mednafen', 'mess', + 'mupen64plus', 'nulldc', 'o2em', 'osmose', 'pcsxr', + 'reicast', 'ResidualVM', 'residualvm', 'scummvm', + 'snes9x', 'stella', 'vice', 'virtualjaguar']: + shutil.rmtree(path) From bd32e9553280d97685b3aac915d5b0387a06434a Mon Sep 17 00:00:00 2001 From: Xodetaetl Date: Tue, 22 Dec 2015 19:04:51 +0100 Subject: [PATCH 13/68] Add atari800 and gens to runners uninstalled by migration --- lutris/migrations/update_runners.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lutris/migrations/update_runners.py b/lutris/migrations/update_runners.py index 8e5d2ad57..3b4ae999d 100644 --- a/lutris/migrations/update_runners.py +++ b/lutris/migrations/update_runners.py @@ -8,8 +8,8 @@ def migrate(): path = os.path.join(RUNNER_DIR, dirname) if not os.path.isdir(path): return - if dirname in ['dgen', 'dolphin', 'dosbox', 'frotz', 'fs-uae', - 'hatari', 'jzintv', 'mame', 'mednafen', 'mess', + if dirname in ['atari800', 'dgen', 'dolphin', 'dosbox', 'frotz', 'fs-uae', + 'gens', 'hatari', 'jzintv', 'mame', 'mednafen', 'mess', 'mupen64plus', 'nulldc', 'o2em', 'osmose', 'pcsxr', 'reicast', 'ResidualVM', 'residualvm', 'scummvm', 'snes9x', 'stella', 'vice', 'virtualjaguar']: From 8c49cd24fb88c987da7cf880349d4313c942fc81 Mon Sep 17 00:00:00 2001 From: Rob Loach Date: Tue, 22 Dec 2015 18:31:52 -0500 Subject: [PATCH 14/68] Add JSON formatting to --list-games --- bin/lutris | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/bin/lutris b/bin/lutris index 463a2072f..654b4992d 100755 --- a/bin/lutris +++ b/bin/lutris @@ -23,6 +23,7 @@ import logging import optparse import signal import time +import json # pylint: disable=E0611 from gi.repository import Gdk, Gtk, GObject, GLib @@ -80,6 +81,8 @@ parser.add_option("-l", "--list-games", action="store_true", help="List all games in database") parser.add_option("-s", "--list-steam", action="store_true", help="List Steam (Windows) games") +parser.add_option("-j", "--json", action="store_true", + dest="json", help="Display the list of games in JSON format") parser.add_option("--reinstall", action="store_true", help="Reinstall game") (options, args) = parser.parse_args() @@ -90,13 +93,24 @@ if options.debug: logger.setLevel(logging.DEBUG) if options.list_games: - for game in pga.get_games(): - print u"{:<40} | {:<40} | {:<15} | {:<64}".format( - game['name'][:40], - game['slug'][:40], - game['runner'], - game['directory'] or '-' - ).encode('utf-8') + if options.json: + games = [] + for game in pga.get_games(): + games.append({ + 'slug': game['slug'], + 'name': game['name'], + 'runner': game['runner'], + 'directory': game['directory'] or '-' + }) + print json.dumps(games).encode('utf-8') + else: + for game in pga.get_games(): + print u"{:<40} | {:<40} | {:<15} | {:<64}".format( + game['name'][:40], + game['slug'][:40], + game['runner'], + game['directory'] or '-' + ).encode('utf-8') exit() if options.list_steam: from lutris.runners import winesteam From 67f0cae6a3b57aa4e48a1cd97609377dad8d9f25 Mon Sep 17 00:00:00 2001 From: Xodetaetl Date: Wed, 23 Dec 2015 12:10:12 +0100 Subject: [PATCH 15/68] Fix#236, steam installers with "files:" trying to create target path --- lutris/installer/interpreter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lutris/installer/interpreter.py b/lutris/installer/interpreter.py index a9d63aae4..6ec3bac21 100644 --- a/lutris/installer/interpreter.py +++ b/lutris/installer/interpreter.py @@ -146,7 +146,7 @@ class ScriptInterpreter(Commands): if not os.path.exists(self.download_cache_path): os.mkdir(self.download_cache_path) - if self.should_create_target: + if self.target_path and self.should_create_target: os.makedirs(self.target_path) self.reversion_data['created_main_dir'] = True From 8be9f19d122bc08af5d7fdd758268592467a30e3 Mon Sep 17 00:00:00 2001 From: Xodetaetl Date: Sat, 26 Dec 2015 23:33:38 +0100 Subject: [PATCH 16/68] Fix forgotten inherited functions in winesteam from wine --- docs/installers.rst | 2 +- lutris/runners/winesteam.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/installers.rst b/docs/installers.rst index 255d00f86..97a88d5ab 100644 --- a/docs/installers.rst +++ b/docs/installers.rst @@ -306,7 +306,7 @@ Currently, the following tasks are implemented: prefix: $GAMEDIR path: HKEY_CURRENT_USER\Software\Valve\Steam key: SuppressAutoRun - value: 00000000 + value: '00000000' type: REG_DWORD * wine / winesteam: ``set_regedit_file`` Apply a regedit file to the diff --git a/lutris/runners/winesteam.py b/lutris/runners/winesteam.py index 6ca8c627c..38b429c16 100644 --- a/lutris/runners/winesteam.py +++ b/lutris/runners/winesteam.py @@ -17,9 +17,12 @@ from lutris.util.wineregistry import WineRegistry # Redefine wine installer tasks set_regedit = wine.set_regedit +set_regedit_file = wine.set_regedit_file +delete_registry_key = wine.delete_registry_key create_prefix = wine.create_prefix wineexec = wine.wineexec winetricks = wine.winetricks +winecfg = wine.winecfg STEAM_INSTALLER_URL = "http://lutris.net/files/runners/SteamInstall.msi" From 72a2327af329e45b09c8712d3b7013d454c7ff9b Mon Sep 17 00:00:00 2001 From: Xodetaetl Date: Sat, 26 Dec 2015 23:41:12 +0100 Subject: [PATCH 17/68] Fix "type_" arg in wine.set_regedit not corresponding to installer doc The doc says "type", not "type_". It may not be cool at all to override the "type" built-in function but I think it's worth it here, for the sake of an intuitive and clean installer syntax. --- lutris/runners/wine.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lutris/runners/wine.py b/lutris/runners/wine.py index 160c3c5a8..11a8cc2b7 100644 --- a/lutris/runners/wine.py +++ b/lutris/runners/wine.py @@ -13,8 +13,8 @@ from lutris.runners.runner import Runner WINE_DIR = os.path.join(settings.RUNNER_DIR, "wine") -def set_regedit(path, key, value='', type_='REG_SZ', - wine_path=None, prefix=None, arch='win32'): +def set_regedit(path, key, value='', type='REG_SZ', wine_path=None, + prefix=None, arch='win32'): """Add keys to the windows registry. Path is something like HKEY_CURRENT_USER\Software\Wine\Direct3D @@ -36,7 +36,7 @@ def set_regedit(path, key, value='', type_='REG_SZ', REGEDIT4 [%s] "%s"=%s - """ % (path, key, formatted_value[type_]))) + """ % (path, key, formatted_value[type]))) reg_file.close() set_regedit_file(reg_path, wine_path=wine_path, prefix=prefix, arch=arch) os.remove(reg_path) From 60ca17a20934f840309aee8ae6eb90160da9c049 Mon Sep 17 00:00:00 2001 From: Xodetaetl Date: Sun, 27 Dec 2015 01:48:59 +0100 Subject: [PATCH 18/68] Revert "Fix forgotten inherited functions in winesteam from wine" This reverts commit 8be9f19d122bc08af5d7fdd758268592467a30e3. --- docs/installers.rst | 2 +- lutris/runners/winesteam.py | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/docs/installers.rst b/docs/installers.rst index 97a88d5ab..255d00f86 100644 --- a/docs/installers.rst +++ b/docs/installers.rst @@ -306,7 +306,7 @@ Currently, the following tasks are implemented: prefix: $GAMEDIR path: HKEY_CURRENT_USER\Software\Valve\Steam key: SuppressAutoRun - value: '00000000' + value: 00000000 type: REG_DWORD * wine / winesteam: ``set_regedit_file`` Apply a regedit file to the diff --git a/lutris/runners/winesteam.py b/lutris/runners/winesteam.py index 38b429c16..6ca8c627c 100644 --- a/lutris/runners/winesteam.py +++ b/lutris/runners/winesteam.py @@ -17,12 +17,9 @@ from lutris.util.wineregistry import WineRegistry # Redefine wine installer tasks set_regedit = wine.set_regedit -set_regedit_file = wine.set_regedit_file -delete_registry_key = wine.delete_registry_key create_prefix = wine.create_prefix wineexec = wine.wineexec winetricks = wine.winetricks -winecfg = wine.winecfg STEAM_INSTALLER_URL = "http://lutris.net/files/runners/SteamInstall.msi" From 7704b7c6502967a502c79e414c6041f209b55e83 Mon Sep 17 00:00:00 2001 From: Xodetaetl Date: Tue, 29 Dec 2015 15:59:44 +0100 Subject: [PATCH 19/68] idle_add'ify all Gtk stuff from installer commands I don't know why we didn't get crashes sooner, with all this Gtk stuff happening outside of the main thread. --- lutris/installer/commands.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lutris/installer/commands.py b/lutris/installer/commands.py index 778a05bb9..0bed24b34 100644 --- a/lutris/installer/commands.py +++ b/lutris/installer/commands.py @@ -3,7 +3,7 @@ import os import shutil import shlex -from gi.repository import Gdk +from gi.repository import Gdk, GLib from .errors import ScriptingError @@ -90,7 +90,7 @@ class Commands(object): dest_path = self.target_path msg = "Extracting %s" % os.path.basename(filename) logger.debug(msg) - self.parent.set_status(msg) + GLib.idle_add(self.parent.set_status, msg) merge_single = 'nomerge' not in data extractor = data.get('format') logger.debug("extracting file %s to %s", filename, dest_path) @@ -106,8 +106,8 @@ class Commands(object): has_entry = data.get('entry') options = data['options'] preselect = self._substitute(data.get('preselect', '')) - self.parent.input_menu(alias, options, preselect, has_entry, - self._on_input_menu_validated) + GLib.idle_add(self.parent.input_menu, alias, options, preselect, + has_entry, self._on_input_menu_validated) return 'STOP' def _on_input_menu_validated(self, widget, *args): @@ -117,7 +117,7 @@ class Commands(object): if choosen_option: self.user_inputs.append({'alias': alias, 'value': choosen_option}) - self.parent.continue_button.hide() + GLib.idle_add(self.parent.continue_button.hide) self._iter_commands() def insert_disc(self, data): @@ -132,8 +132,8 @@ class Commands(object): "containing the following file or folder:\n" "%s" % requires ) - self.parent.wait_for_user_action(message, self._find_matching_disc, - requires) + GLib.idle_add(self.parent.wait_for_user_action, message, + self._find_matching_disc, requires) return 'STOP' def _find_matching_disc(self, widget, requires): @@ -263,7 +263,7 @@ class Commands(object): passed to the runner task. """ self._check_required_params('name', data, 'task') - self.parent.cancel_button.set_sensitive(False) + GLib.idle_add(self.parent.cancel_button.set_sensitive, False) task_name = data.pop('name') if '.' in task_name: # Run a task from a different runner @@ -274,7 +274,7 @@ class Commands(object): try: runner_class = import_runner(runner_name) except InvalidRunner: - self.parent.cancel_button.set_sensitive(True) + GLib.idle_add(self.parent.cancel_button.set_sensitive, True) raise ScriptingError('Invalid runner provided %s', runner_name) runner = runner_class() @@ -308,7 +308,7 @@ class Commands(object): data[key] = self._substitute(data[key]) task = import_task(runner_name, task_name) task(**data) - self.parent.cancel_button.set_sensitive(True) + GLib.idle_add(self.parent.cancel_button.set_sensitive, True) def write_config(self, params): self._check_required_params(['file', 'section', 'key', 'value'], From 512e7d9d3bceb607ba1d3bd7faa51fa0b81ea4bd Mon Sep 17 00:00:00 2001 From: Xodetaetl Date: Tue, 29 Dec 2015 16:00:54 +0100 Subject: [PATCH 20/68] Exclude more processes from watch (regedit, winetricks, winecfg, joystick CP) --- lutris/thread.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lutris/thread.py b/lutris/thread.py index f9da245c1..382c5f2d5 100644 --- a/lutris/thread.py +++ b/lutris/thread.py @@ -161,9 +161,11 @@ class LutrisThread(threading.Thread): num_children += 1 # Exclude other wrapper processes - if child.name in ('steamwebhelper', 'steam', 'sh', 'tee', 'bash', - 'Steam.exe', 'steamwebhelper.', 'PnkBstrA.exe', 'steamer', - 'SteamService.ex', 'steamerrorrepor', 'lutris'): + if child.name in ('bash', 'control', 'lutris', 'PnkBstrA.exe', + 'regedit', 'sh', 'steam', 'Steam.exe', 'steamer', + 'steamerrorrepor', 'SteamService.ex', + 'steamwebhelper', 'steamwebhelper.', 'tee', 'tr', + 'winecfg.exe', 'winetricks', 'zenity', ): continue num_watched_children += 1 logger.debug("{}\t{}\t{}".format(child.pid, From e2fcaf66f0a9ac8c170263dd9f2ddb99e6a9ce75 Mon Sep 17 00:00:00 2001 From: Xodetaetl Date: Tue, 29 Dec 2015 16:13:06 +0100 Subject: [PATCH 21/68] Exclude python from watched processes --- lutris/thread.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lutris/thread.py b/lutris/thread.py index 382c5f2d5..d9a27661f 100644 --- a/lutris/thread.py +++ b/lutris/thread.py @@ -161,11 +161,13 @@ class LutrisThread(threading.Thread): num_children += 1 # Exclude other wrapper processes - if child.name in ('bash', 'control', 'lutris', 'PnkBstrA.exe', - 'regedit', 'sh', 'steam', 'Steam.exe', 'steamer', - 'steamerrorrepor', 'SteamService.ex', - 'steamwebhelper', 'steamwebhelper.', 'tee', 'tr', - 'winecfg.exe', 'winetricks', 'zenity', ): + excluded = ( + 'bash', 'control', 'lutris', 'PnkBstrA.exe', 'python', 'regedit', + 'sh', 'steam', 'Steam.exe', 'steamer', 'steamerrorrepor', + 'SteamService.ex', 'steamwebhelper', 'steamwebhelper.', 'tee', + 'tr', 'winecfg.exe', 'winetricks', 'zenity', + ) + if child.name in excluded: continue num_watched_children += 1 logger.debug("{}\t{}\t{}".format(child.pid, From 744778bddc4023feef37f7b04d3c564fdc8f7f2e Mon Sep 17 00:00:00 2001 From: Mathieu Comandon Date: Tue, 29 Dec 2015 15:18:50 -0800 Subject: [PATCH 22/68] Add id to the list of installed games (Fixes #239) --- bin/lutris | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bin/lutris b/bin/lutris index 654b4992d..87b68337c 100755 --- a/bin/lutris +++ b/bin/lutris @@ -97,6 +97,7 @@ if options.list_games: games = [] for game in pga.get_games(): games.append({ + 'id': game['id'], 'slug': game['slug'], 'name': game['name'], 'runner': game['runner'], @@ -105,7 +106,8 @@ if options.list_games: print json.dumps(games).encode('utf-8') else: for game in pga.get_games(): - print u"{:<40} | {:<40} | {:<15} | {:<64}".format( + print u"{:4} | {:<40} | {:<40} | {:<15} | {:<64}".format( + game['id'], game['name'][:40], game['slug'][:40], game['runner'], From 6615f9b9eb09e3dcfdb57fa6e094d447c3bf5a3f Mon Sep 17 00:00:00 2001 From: Mathieu Comandon Date: Tue, 29 Dec 2015 15:19:07 -0800 Subject: [PATCH 23/68] Format json output --- bin/lutris | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/lutris b/bin/lutris index 87b68337c..7831f3ba2 100755 --- a/bin/lutris +++ b/bin/lutris @@ -103,7 +103,7 @@ if options.list_games: 'runner': game['runner'], 'directory': game['directory'] or '-' }) - print json.dumps(games).encode('utf-8') + print json.dumps(games, indent=2).encode('utf-8') else: for game in pga.get_games(): print u"{:4} | {:<40} | {:<40} | {:<15} | {:<64}".format( From 6b710bf06f00a59b611d8f15ecb9b53f5e7b3eda Mon Sep 17 00:00:00 2001 From: Mathieu Comandon Date: Tue, 29 Dec 2015 18:42:33 -0800 Subject: [PATCH 24/68] Add option to list installed games (Fixes #238) --- bin/lutris | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/bin/lutris b/bin/lutris index 7831f3ba2..6e2787ddc 100755 --- a/bin/lutris +++ b/bin/lutris @@ -71,18 +71,20 @@ logger.setLevel(logging.ERROR) # Support for command line options. parser = optparse.OptionParser(version="%prog " + VERSION) -parser.add_option("-v", "--verbose", action="store_true", - dest="verbose", help="Verbose output") -parser.add_option("-d", "--debug", action="store_true", - dest="debug", help="Show debug messages") +parser.add_option("-v", "--verbose", action="store_true", dest="verbose", + help="Verbose output") +parser.add_option("-d", "--debug", action="store_true", dest="debug", + help="Show debug messages") parser.add_option("-i", "--install", dest="installer_file", help="Install a game from a yml file") -parser.add_option("-l", "--list-games", action="store_true", - help="List all games in database") +parser.add_option("-l", "--list-games", action="store_true", dest="list_games", + help="List games in database") +parser.add_option("-o", "--installed", action="store_true", dest="list_installed", + help="Only list installed games") parser.add_option("-s", "--list-steam", action="store_true", help="List Steam (Windows) games") -parser.add_option("-j", "--json", action="store_true", - dest="json", help="Display the list of games in JSON format") +parser.add_option("-j", "--json", action="store_true", dest="json", + help="Display the list of games in JSON format") parser.add_option("--reinstall", action="store_true", help="Reinstall game") (options, args) = parser.parse_args() @@ -93,9 +95,12 @@ if options.debug: logger.setLevel(logging.DEBUG) if options.list_games: + game_list = pga.get_games() + if options.list_installed: + game_list = [game for game in game_list if game['installed']] if options.json: games = [] - for game in pga.get_games(): + for game in game_list: games.append({ 'id': game['id'], 'slug': game['slug'], @@ -105,7 +110,7 @@ if options.list_games: }) print json.dumps(games, indent=2).encode('utf-8') else: - for game in pga.get_games(): + for game in game_list: print u"{:4} | {:<40} | {:<40} | {:<15} | {:<64}".format( game['id'], game['name'][:40], From ed702a9cbb05bef40e234663d715e4810dbe17d5 Mon Sep 17 00:00:00 2001 From: Mathieu Comandon Date: Tue, 29 Dec 2015 19:01:27 -0800 Subject: [PATCH 25/68] Add contributors to AUTHORS --- AUTHORS | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/AUTHORS b/AUTHORS index f1d6b658d..c7553848a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -5,3 +5,8 @@ Contributors: Mathieu Comandon Ludovic Soulié Pascal Reinhard (Xodetaetl) + Rob Loach + Rémi Verschelde + Ivan + mikeyd + Travis Nickles From 7b361d9ed382a5fdc17989fceea938bd8b5e7d1c Mon Sep 17 00:00:00 2001 From: Mathieu Comandon Date: Tue, 29 Dec 2015 19:06:48 -0800 Subject: [PATCH 26/68] Bump to version 0.3.7.1 --- INSTALL.rst | 2 +- Makefile | 2 +- debian/changelog | 12 ++++++++++++ lutris.spec | 2 +- lutris/settings.py | 2 +- 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/INSTALL.rst b/INSTALL.rst index 7c6b84c2e..c88b977d0 100644 --- a/INSTALL.rst +++ b/INSTALL.rst @@ -72,4 +72,4 @@ You can now build the RPM:: rpmbuild -ba lutris.spec The resulting package will be available at -~/rpmbuild/RPMS/noarch/lutris-0.3.6-3.fc20.noarch.rpm +~/rpmbuild/RPMS/noarch/lutris-0.3.7-3.fc20.noarch.rpm diff --git a/Makefile b/Makefile index 3aa1c1ef7..48542ec43 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -VERSION="0.3.7" +VERSION="0.3.7.1" cover: rm tests/fixtures/pga.db -f diff --git a/debian/changelog b/debian/changelog index 454d0ec66..b328cfa71 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,15 @@ +lutris (0.3.7.1) wily; urgency=medium + + * Improved command line option to list games + * Force update of runners + * Add support of 64bit wine + * Improve MESS runner + * Fix Vice runner for non Commodore 64 machines + * Fix RPM packaging + * Various bugfixes + + -- Mathieu Comandon Tue, 29 Dec 2015 18:47:05 -0800 + lutris (0.3.7) wily; urgency=medium * Global: diff --git a/lutris.spec b/lutris.spec index 141a9629c..d176fb43a 100644 --- a/lutris.spec +++ b/lutris.spec @@ -1,7 +1,7 @@ %{!?python_sitelib: %global python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")} Name: lutris -Version: 0.3.7 +Version: 0.3.7.1 Release: 2%{?dist} Summary: Install and play any video game easily diff --git a/lutris/settings.py b/lutris/settings.py index d39daa1bc..d52b4fe45 100644 --- a/lutris/settings.py +++ b/lutris/settings.py @@ -5,7 +5,7 @@ from gi.repository import GLib from lutris.util.settings import SettingsIO PROJECT = "Lutris" -VERSION = "0.3.7" +VERSION = "0.3.7.1" COPYRIGHT = "(c) 2010-2015 Lutris Gaming Platform" AUTHORS = ["Mathieu Comandon ", "Pascal Reinhard (Xodetaetl) Date: Tue, 29 Dec 2015 19:22:54 -0800 Subject: [PATCH 27/68] Remove python-support and libsdl2-2.0-0:i386 deps --- debian/control | 2 -- 1 file changed, 2 deletions(-) diff --git a/debian/control b/debian/control index 1838d264a..a978f2e4a 100644 --- a/debian/control +++ b/debian/control @@ -4,12 +4,10 @@ Priority: optional Build-Depends: cdbs, debhelper, python, - python-support, gir1.2-gtk-3.0, gir1.2-glib-2.0, python-gi, libgirepository1.0-dev, - libsdl2-2.0-0:i386 Maintainer: Mathieu Comandon Standards-Version: 3.9.5 Vcs-Git: https://github.com/lutris/lutris From 8e38878dcbb5f1f10fef134ad162af1943f17fa9 Mon Sep 17 00:00:00 2001 From: Mathieu Comandon Date: Tue, 29 Dec 2015 19:43:07 -0800 Subject: [PATCH 28/68] Migrate to dh-python for deb packaging --- debian/control | 2 ++ debian/copyright | 4 ++-- debian/pyversions | 1 - debian/rules | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) delete mode 100644 debian/pyversions diff --git a/debian/control b/debian/control index a978f2e4a..91c67f851 100644 --- a/debian/control +++ b/debian/control @@ -4,6 +4,7 @@ Priority: optional Build-Depends: cdbs, debhelper, python, + dh-python, gir1.2-gtk-3.0, gir1.2-glib-2.0, python-gi, @@ -12,6 +13,7 @@ Maintainer: Mathieu Comandon Standards-Version: 3.9.5 Vcs-Git: https://github.com/lutris/lutris Homepage: https://lutris.net +X-Python-Version: >= 2.7 Package: lutris Architecture: any diff --git a/debian/copyright b/debian/copyright index fde557294..7758df831 100644 --- a/debian/copyright +++ b/debian/copyright @@ -1,10 +1,10 @@ Format-Specification: http://wiki.debian.org/Proposals/CopyrightFormat Upstream-Name: lutris Upstream-Maintainer: Mathieu Comandon -Upstream-Source: https://launchpad.net/lutris +Upstream-Source: https://github.com/lutris Files: * -Copyright: (C) 2009, 2010 Mathieu Comandon +Copyright: (C) 2009, 2015 Mathieu Comandon License: GPL-3 The full text of the GPL is distributed in /usr/share/common-licenses/GPL-3 on Debian systems. diff --git a/debian/pyversions b/debian/pyversions deleted file mode 100644 index 1effb0034..000000000 --- a/debian/pyversions +++ /dev/null @@ -1 +0,0 @@ -2.7 diff --git a/debian/rules b/debian/rules index 8b0ec471b..a97fc775f 100755 --- a/debian/rules +++ b/debian/rules @@ -1,6 +1,6 @@ #!/usr/bin/make -f -DEB_PYTHON_SYSTEM=pysupport +DEB_PYTHON2_MODULE_PACKAGES=lutris include /usr/share/cdbs/1/rules/debhelper.mk include /usr/share/cdbs/1/class/python-distutils.mk From fd19b26301e32fa350009020c4d5d48a45ca74cd Mon Sep 17 00:00:00 2001 From: Mathieu Comandon Date: Wed, 30 Dec 2015 00:14:44 -0800 Subject: [PATCH 29/68] Refactor buttons on install dialogs --- lutris/gui/installgamedialog.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/lutris/gui/installgamedialog.py b/lutris/gui/installgamedialog.py index 7e78fd232..6365cda1f 100644 --- a/lutris/gui/installgamedialog.py +++ b/lutris/gui/installgamedialog.py @@ -73,10 +73,10 @@ class InstallerDialog(Gtk.Window): self.cancel_button.connect('clicked', self.on_cancel_clicked) self.action_buttons.add(self.cancel_button) - self.install_button = Gtk.Button.new_with_mnemonic("_Install") - self.install_button.set_margin_left(20) - self.install_button.connect('clicked', self.on_install_clicked) - self.action_buttons.add(self.install_button) + self.install_button = self.add_button("_Install", self.on_install_clicked) + self.continue_button = self.add_button("_Continue") + self.play_button = self.add_button("_Launch game", self.launch_game) + self.close_button = self.add_button("_Close", self.close) self.continue_button = Gtk.Button.new_with_mnemonic("_Continue") self.continue_button.set_margin_left(20) @@ -88,13 +88,18 @@ class InstallerDialog(Gtk.Window): self.play_button.connect('clicked', self.launch_game) self.action_buttons.add(self.play_button) - self.close_button = Gtk.Button.new_with_mnemonic("_Close") - self.close_button.set_margin_left(20) - self.close_button.connect('clicked', self.close) - self.action_buttons.add(self.close_button) + self.continue_handler = None self.get_scripts() + def add_button(self, label, handler=None): + button = Gtk.Button.new_with_mnemonic(label) + button.set_margin_left(20) + if handler: + button.connect('clicked', handler) + self.action_buttons.add(button) + return button + # --------------------------- # "Get installer" stage # --------------------------- From 36e8e2de2ae225a4af93b164d17f5d4da18d2261 Mon Sep 17 00:00:00 2001 From: Mathieu Comandon Date: Wed, 30 Dec 2015 02:18:25 -0800 Subject: [PATCH 30/68] Allow loading extra environment variables from system config --- lutris/game.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lutris/game.py b/lutris/game.py index c8b8188fa..7edcae9ea 100644 --- a/lutris/game.py +++ b/lutris/game.py @@ -217,11 +217,12 @@ class Game(object): env = {} game_env = gameplay_info.get('env') or {} env.update(game_env) + system_env = system_config.get('env') or {} + env.update(system_env) ld_preload = gameplay_info.get('ld_preload') if ld_preload: env["LD_PRELOAD"] = ld_preload - ld_library_path = [] if self.runner.use_runtime(): env['STEAM_RUNTIME'] = os.path.join(settings.RUNTIME_DIR, 'steam') From 17c487a8b4574f3b7418b56dd928bd668df8843f Mon Sep 17 00:00:00 2001 From: Mathieu Comandon Date: Wed, 30 Dec 2015 02:21:31 -0800 Subject: [PATCH 31/68] Add eject button for wine games using cdroms --- lutris/gui/installgamedialog.py | 16 ++++++---------- lutris/installer/commands.py | 18 ++++++++++-------- lutris/installer/interpreter.py | 5 +++++ lutris/runners/wine.py | 8 ++++++++ 4 files changed, 29 insertions(+), 18 deletions(-) diff --git a/lutris/gui/installgamedialog.py b/lutris/gui/installgamedialog.py index 6365cda1f..e7fa9dab4 100644 --- a/lutris/gui/installgamedialog.py +++ b/lutris/gui/installgamedialog.py @@ -73,21 +73,12 @@ class InstallerDialog(Gtk.Window): self.cancel_button.connect('clicked', self.on_cancel_clicked) self.action_buttons.add(self.cancel_button) + self.eject_button = self.add_button("_Eject", self.on_eject_clicked) self.install_button = self.add_button("_Install", self.on_install_clicked) self.continue_button = self.add_button("_Continue") self.play_button = self.add_button("_Launch game", self.launch_game) self.close_button = self.add_button("_Close", self.close) - self.continue_button = Gtk.Button.new_with_mnemonic("_Continue") - self.continue_button.set_margin_left(20) - self.continue_handler = None - self.action_buttons.add(self.continue_button) - - self.play_button = Gtk.Button.new_with_mnemonic("_Launch game") - self.play_button.set_margin_left(20) - self.play_button.connect('clicked', self.launch_game) - self.action_buttons.add(self.play_button) - self.continue_handler = None self.get_scripts() @@ -129,6 +120,7 @@ class InstallerDialog(Gtk.Window): self.close_button.hide() self.play_button.hide() self.install_button.hide() + self.eject_button.hide() self.choose_installer() @@ -353,6 +345,9 @@ class InstallerDialog(Gtk.Window): button.grab_focus() button.show() + def on_eject_clicked(self, widget, data=None): + self.interpreter.eject_wine_disc() + def input_menu(self, alias, options, preselect, has_entry, callback): """Display an input request as a dropdown menu with options.""" time.sleep(0.3) @@ -410,6 +405,7 @@ class InstallerDialog(Gtk.Window): self.connect('destroy', self.create_shortcuts) # Buttons + self.eject_button.hide() self.cancel_button.hide() self.continue_button.hide() self.install_button.hide() diff --git a/lutris/installer/commands.py b/lutris/installer/commands.py index 0bed24b34..6ed508d4d 100644 --- a/lutris/installer/commands.py +++ b/lutris/installer/commands.py @@ -19,6 +19,10 @@ from lutris.thread import LutrisThread class Commands(object): """The directives for the `installer:` part of the install script.""" + def _get_wine_version(self): + if self.script.get('wine'): + return wine.support_legacy_version(self.script['wine'].get('version')) + def _check_required_params(self, params, command_data, command_name): """Verify presence of a list of parameters required by a command.""" if type(params) is str: @@ -132,6 +136,8 @@ class Commands(object): "containing the following file or folder:\n" "%s" % requires ) + if self.runner == 'wine': + GLib.idle_add(self.parent.eject_button.show) GLib.idle_add(self.parent.wait_for_user_action, message, self._find_matching_disc, requires) return 'STOP' @@ -280,15 +286,11 @@ class Commands(object): runner = runner_class() # Check/install Wine runner at version specified in the script + # TODO : move this, the runner should be installed before the install + # starts wine_version = None - if runner_name == 'wine' and self.script.get('wine'): - wine_version = self.script.get('wine').get('version') - - # Old lutris versions used a version + arch tuple, we now include - # everything in the version. - # Before that change every wine runner was for i386 - if '-' not in wine_version: - wine_version += '-i386' + if runner_name == 'wine': + wine_version = self._get_wine_version() if wine_version and task_name == 'wineexec': if not wine.is_version_installed(wine_version): diff --git a/lutris/installer/interpreter.py b/lutris/installer/interpreter.py index 6ec3bac21..445e52e80 100644 --- a/lutris/installer/interpreter.py +++ b/lutris/installer/interpreter.py @@ -652,3 +652,8 @@ class ScriptInterpreter(Commands): callback_args = self.steam_data['callback_args'] self.parent.add_spinner() self.install_steam_game(*callback_args) + + def eject_wine_disc(self): + prefix = self.target_path + wine_path = wine.get_wine_version_exe(self._get_wine_version()) + wine.eject_disc(wine_path, prefix) diff --git a/lutris/runners/wine.py b/lutris/runners/wine.py index 11a8cc2b7..1bfba93ad 100644 --- a/lutris/runners/wine.py +++ b/lutris/runners/wine.py @@ -158,6 +158,10 @@ def joycpl(wine_path=None, prefix=None): wine_path=wine_path, arch=arch, args='joy.cpl', blocking=False) +def eject_disc(wine_path, prefix): + wineexec('eject', prefix=prefix, wine_path=wine_path, args='-a') + + def detect_prefix_arch(directory=None): """Return the architecture of the prefix found in `directory`. @@ -218,6 +222,10 @@ def get_wine_versions(): def get_wine_version_exe(version): + if not version: + version = get_default_version() + if not version: + raise RuntimeError("Wine is not installed") return os.path.join(WINE_DIR, '{}/bin/wine'.format(version)) From a5848b4d14340a54e692fd16fec1b1a7a56f96a7 Mon Sep 17 00:00:00 2001 From: Xodetaetl Date: Tue, 29 Dec 2015 16:14:18 +0100 Subject: [PATCH 32/68] Revert "Revert "Fix forgotten inherited functions in winesteam from wine"" This reverts commit 60ca17a20934f840309aee8ae6eb90160da9c049. --- docs/installers.rst | 2 +- lutris/runners/winesteam.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/installers.rst b/docs/installers.rst index 255d00f86..97a88d5ab 100644 --- a/docs/installers.rst +++ b/docs/installers.rst @@ -306,7 +306,7 @@ Currently, the following tasks are implemented: prefix: $GAMEDIR path: HKEY_CURRENT_USER\Software\Valve\Steam key: SuppressAutoRun - value: 00000000 + value: '00000000' type: REG_DWORD * wine / winesteam: ``set_regedit_file`` Apply a regedit file to the diff --git a/lutris/runners/winesteam.py b/lutris/runners/winesteam.py index 6ca8c627c..38b429c16 100644 --- a/lutris/runners/winesteam.py +++ b/lutris/runners/winesteam.py @@ -17,9 +17,12 @@ from lutris.util.wineregistry import WineRegistry # Redefine wine installer tasks set_regedit = wine.set_regedit +set_regedit_file = wine.set_regedit_file +delete_registry_key = wine.delete_registry_key create_prefix = wine.create_prefix wineexec = wine.wineexec winetricks = wine.winetricks +winecfg = wine.winecfg STEAM_INSTALLER_URL = "http://lutris.net/files/runners/SteamInstall.msi" From 5ba8752f7e37f4b7e5c8cf06bd5151c6beafc2cb Mon Sep 17 00:00:00 2001 From: Xodetaetl Date: Thu, 31 Dec 2015 01:36:10 +0100 Subject: [PATCH 33/68] Fix winetricks run from installers --- lutris/runners/wine.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lutris/runners/wine.py b/lutris/runners/wine.py index 11a8cc2b7..333748478 100644 --- a/lutris/runners/wine.py +++ b/lutris/runners/wine.py @@ -90,7 +90,8 @@ def wineexec(executable, args="", wine_path=None, prefix=None, arch=None, # Create prefix if necessary if not detected_arch: - create_prefix(prefix, wine_dir=os.path.dirname(wine_path), arch=arch) + wine_dir = winetricks_env if winetricks_env else wine_path + create_prefix(prefix, wine_dir=os.path.dirname(wine_dir), arch=arch) env = ['WINEARCH=%s' % arch] if winetricks_env: @@ -112,7 +113,7 @@ def wineexec(executable, args="", wine_path=None, prefix=None, arch=None, def winetricks(app, prefix=None, winetricks_env=None, silent=True, - blocking=False): + blocking=True): """Execute winetricks.""" path = (system.find_executable('winetricks') or os.path.join(datapath.get(), 'bin/winetricks')) From cf296c475f85d053414d6fef6149a68db7a1630d Mon Sep 17 00:00:00 2001 From: Xodetaetl Date: Thu, 31 Dec 2015 01:37:03 +0100 Subject: [PATCH 34/68] Make joycpl non-blocking when run from context menu --- lutris/runners/wine.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lutris/runners/wine.py b/lutris/runners/wine.py index 333748478..b9af20eb1 100644 --- a/lutris/runners/wine.py +++ b/lutris/runners/wine.py @@ -152,11 +152,11 @@ def winecfg(wine_path=None, prefix=None, arch='win32', blocking=True): p.communicate() -def joycpl(wine_path=None, prefix=None): +def joycpl(wine_path=None, prefix=None, blocking=True): """Execute winetricks.""" arch = detect_prefix_arch(prefix) or 'win32' wineexec('control', prefix=prefix, - wine_path=wine_path, arch=arch, args='joy.cpl', blocking=False) + wine_path=wine_path, arch=arch, args='joy.cpl', blocking=blocking) def detect_prefix_arch(directory=None): @@ -575,7 +575,8 @@ class wine(Runner): winetricks_env=self.get_executable(), blocking=False) def run_joycpl(self, *args): - joycpl(prefix=self.prefix_path, wine_path=self.get_executable()) + joycpl(prefix=self.prefix_path, wine_path=self.get_executable(), + blocking=False) def set_regedit_keys(self): """Reset regedit keys according to config.""" From 5f9431427e291fdb1567e0cf24fd6469b3cfd088 Mon Sep 17 00:00:00 2001 From: Xodetaetl Date: Thu, 31 Dec 2015 01:39:14 +0100 Subject: [PATCH 35/68] Add prefix param if not present in wine/winesteam installer task --- lutris/installer/commands.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lutris/installer/commands.py b/lutris/installer/commands.py index 6ed508d4d..ce1cb1235 100644 --- a/lutris/installer/commands.py +++ b/lutris/installer/commands.py @@ -308,6 +308,10 @@ class Commands(object): for key in data: data[key] = self._substitute(data[key]) + + if runner_name in ['wine', 'winesteam'] and 'prefix' not in data: + data['prefix'] = self.target_path + task = import_task(runner_name, task_name) task(**data) GLib.idle_add(self.parent.cancel_button.set_sensitive, True) From 1fe727d3bb4a2c2776f588f6c52b70b736f94680 Mon Sep 17 00:00:00 2001 From: Mathieu Comandon Date: Wed, 30 Dec 2015 00:14:44 -0800 Subject: [PATCH 36/68] Refactor buttons on install dialogs --- lutris/gui/installgamedialog.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/lutris/gui/installgamedialog.py b/lutris/gui/installgamedialog.py index 7e78fd232..6365cda1f 100644 --- a/lutris/gui/installgamedialog.py +++ b/lutris/gui/installgamedialog.py @@ -73,10 +73,10 @@ class InstallerDialog(Gtk.Window): self.cancel_button.connect('clicked', self.on_cancel_clicked) self.action_buttons.add(self.cancel_button) - self.install_button = Gtk.Button.new_with_mnemonic("_Install") - self.install_button.set_margin_left(20) - self.install_button.connect('clicked', self.on_install_clicked) - self.action_buttons.add(self.install_button) + self.install_button = self.add_button("_Install", self.on_install_clicked) + self.continue_button = self.add_button("_Continue") + self.play_button = self.add_button("_Launch game", self.launch_game) + self.close_button = self.add_button("_Close", self.close) self.continue_button = Gtk.Button.new_with_mnemonic("_Continue") self.continue_button.set_margin_left(20) @@ -88,13 +88,18 @@ class InstallerDialog(Gtk.Window): self.play_button.connect('clicked', self.launch_game) self.action_buttons.add(self.play_button) - self.close_button = Gtk.Button.new_with_mnemonic("_Close") - self.close_button.set_margin_left(20) - self.close_button.connect('clicked', self.close) - self.action_buttons.add(self.close_button) + self.continue_handler = None self.get_scripts() + def add_button(self, label, handler=None): + button = Gtk.Button.new_with_mnemonic(label) + button.set_margin_left(20) + if handler: + button.connect('clicked', handler) + self.action_buttons.add(button) + return button + # --------------------------- # "Get installer" stage # --------------------------- From 98547719eb47c81482f6e3a74ab2511f4618f24e Mon Sep 17 00:00:00 2001 From: Mathieu Comandon Date: Wed, 30 Dec 2015 02:18:25 -0800 Subject: [PATCH 37/68] Allow loading extra environment variables from system config --- lutris/game.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lutris/game.py b/lutris/game.py index c8b8188fa..7edcae9ea 100644 --- a/lutris/game.py +++ b/lutris/game.py @@ -217,11 +217,12 @@ class Game(object): env = {} game_env = gameplay_info.get('env') or {} env.update(game_env) + system_env = system_config.get('env') or {} + env.update(system_env) ld_preload = gameplay_info.get('ld_preload') if ld_preload: env["LD_PRELOAD"] = ld_preload - ld_library_path = [] if self.runner.use_runtime(): env['STEAM_RUNTIME'] = os.path.join(settings.RUNTIME_DIR, 'steam') From d560dcef8e93499babb2e3e83394381083de646b Mon Sep 17 00:00:00 2001 From: Mathieu Comandon Date: Wed, 30 Dec 2015 02:21:31 -0800 Subject: [PATCH 38/68] Add eject button for wine games using cdroms --- lutris/gui/installgamedialog.py | 16 ++++++---------- lutris/installer/commands.py | 18 ++++++++++-------- lutris/installer/interpreter.py | 5 +++++ lutris/runners/wine.py | 8 ++++++++ 4 files changed, 29 insertions(+), 18 deletions(-) diff --git a/lutris/gui/installgamedialog.py b/lutris/gui/installgamedialog.py index 6365cda1f..e7fa9dab4 100644 --- a/lutris/gui/installgamedialog.py +++ b/lutris/gui/installgamedialog.py @@ -73,21 +73,12 @@ class InstallerDialog(Gtk.Window): self.cancel_button.connect('clicked', self.on_cancel_clicked) self.action_buttons.add(self.cancel_button) + self.eject_button = self.add_button("_Eject", self.on_eject_clicked) self.install_button = self.add_button("_Install", self.on_install_clicked) self.continue_button = self.add_button("_Continue") self.play_button = self.add_button("_Launch game", self.launch_game) self.close_button = self.add_button("_Close", self.close) - self.continue_button = Gtk.Button.new_with_mnemonic("_Continue") - self.continue_button.set_margin_left(20) - self.continue_handler = None - self.action_buttons.add(self.continue_button) - - self.play_button = Gtk.Button.new_with_mnemonic("_Launch game") - self.play_button.set_margin_left(20) - self.play_button.connect('clicked', self.launch_game) - self.action_buttons.add(self.play_button) - self.continue_handler = None self.get_scripts() @@ -129,6 +120,7 @@ class InstallerDialog(Gtk.Window): self.close_button.hide() self.play_button.hide() self.install_button.hide() + self.eject_button.hide() self.choose_installer() @@ -353,6 +345,9 @@ class InstallerDialog(Gtk.Window): button.grab_focus() button.show() + def on_eject_clicked(self, widget, data=None): + self.interpreter.eject_wine_disc() + def input_menu(self, alias, options, preselect, has_entry, callback): """Display an input request as a dropdown menu with options.""" time.sleep(0.3) @@ -410,6 +405,7 @@ class InstallerDialog(Gtk.Window): self.connect('destroy', self.create_shortcuts) # Buttons + self.eject_button.hide() self.cancel_button.hide() self.continue_button.hide() self.install_button.hide() diff --git a/lutris/installer/commands.py b/lutris/installer/commands.py index 0bed24b34..6ed508d4d 100644 --- a/lutris/installer/commands.py +++ b/lutris/installer/commands.py @@ -19,6 +19,10 @@ from lutris.thread import LutrisThread class Commands(object): """The directives for the `installer:` part of the install script.""" + def _get_wine_version(self): + if self.script.get('wine'): + return wine.support_legacy_version(self.script['wine'].get('version')) + def _check_required_params(self, params, command_data, command_name): """Verify presence of a list of parameters required by a command.""" if type(params) is str: @@ -132,6 +136,8 @@ class Commands(object): "containing the following file or folder:\n" "%s" % requires ) + if self.runner == 'wine': + GLib.idle_add(self.parent.eject_button.show) GLib.idle_add(self.parent.wait_for_user_action, message, self._find_matching_disc, requires) return 'STOP' @@ -280,15 +286,11 @@ class Commands(object): runner = runner_class() # Check/install Wine runner at version specified in the script + # TODO : move this, the runner should be installed before the install + # starts wine_version = None - if runner_name == 'wine' and self.script.get('wine'): - wine_version = self.script.get('wine').get('version') - - # Old lutris versions used a version + arch tuple, we now include - # everything in the version. - # Before that change every wine runner was for i386 - if '-' not in wine_version: - wine_version += '-i386' + if runner_name == 'wine': + wine_version = self._get_wine_version() if wine_version and task_name == 'wineexec': if not wine.is_version_installed(wine_version): diff --git a/lutris/installer/interpreter.py b/lutris/installer/interpreter.py index 6ec3bac21..445e52e80 100644 --- a/lutris/installer/interpreter.py +++ b/lutris/installer/interpreter.py @@ -652,3 +652,8 @@ class ScriptInterpreter(Commands): callback_args = self.steam_data['callback_args'] self.parent.add_spinner() self.install_steam_game(*callback_args) + + def eject_wine_disc(self): + prefix = self.target_path + wine_path = wine.get_wine_version_exe(self._get_wine_version()) + wine.eject_disc(wine_path, prefix) diff --git a/lutris/runners/wine.py b/lutris/runners/wine.py index b9af20eb1..d5f6b00f2 100644 --- a/lutris/runners/wine.py +++ b/lutris/runners/wine.py @@ -159,6 +159,10 @@ def joycpl(wine_path=None, prefix=None, blocking=True): wine_path=wine_path, arch=arch, args='joy.cpl', blocking=blocking) +def eject_disc(wine_path, prefix): + wineexec('eject', prefix=prefix, wine_path=wine_path, args='-a') + + def detect_prefix_arch(directory=None): """Return the architecture of the prefix found in `directory`. @@ -219,6 +223,10 @@ def get_wine_versions(): def get_wine_version_exe(version): + if not version: + version = get_default_version() + if not version: + raise RuntimeError("Wine is not installed") return os.path.join(WINE_DIR, '{}/bin/wine'.format(version)) From e9d93f2fe209c7c1d4a20cc6845c1a32faf48c01 Mon Sep 17 00:00:00 2001 From: Mathieu Comandon Date: Wed, 30 Dec 2015 23:32:57 -0800 Subject: [PATCH 39/68] Use LutrisThread for winexec --- lutris/runners/wine.py | 54 ++++++++++++++++++------------------------ 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/lutris/runners/wine.py b/lutris/runners/wine.py index d5f6b00f2..0451c1e8f 100644 --- a/lutris/runners/wine.py +++ b/lutris/runners/wine.py @@ -9,6 +9,7 @@ from lutris import settings from lutris.util import datapath, display, system from lutris.util.log import logger from lutris.runners.runner import Runner +from lutris.thread import LutrisThread WINE_DIR = os.path.join(settings.RUNNER_DIR, "wine") @@ -71,7 +72,7 @@ def create_prefix(prefix, wine_dir=None, arch='win32'): def wineexec(executable, args="", wine_path=None, prefix=None, arch=None, - working_dir=None, winetricks_env='', blocking=True): + working_dir=None, winetricks_env=''): """Execute a Wine command.""" detected_arch = detect_prefix_arch(prefix) executable = str(executable) if executable else '' @@ -86,34 +87,31 @@ def wineexec(executable, args="", wine_path=None, prefix=None, arch=None, if executable.endswith(".msi"): executable = 'msiexec /i "%s"' % executable elif executable: - executable = '"%s"' % executable + executable = '%s' % executable # Create prefix if necessary if not detected_arch: wine_dir = winetricks_env if winetricks_env else wine_path create_prefix(prefix, wine_dir=os.path.dirname(wine_dir), arch=arch) - env = ['WINEARCH=%s' % arch] + env = { + 'WINEARCH': arch + } if winetricks_env: - env.append('WINE="%s"' % winetricks_env) + env['WINE'] = winetricks_env if prefix: - env.append('WINEPREFIX="%s" ' % prefix) + env['WINEPREFIX'] = prefix if settings.RUNNER_DIR in wine_path: - runtime_path = ':'.join(runtime.get_paths()) - env.append('LD_LIBRARY_PATH={}'.format(runtime_path)) + env['LD_LIBRARY_PATH'] = ':'.join(runtime.get_paths()) - command = '{0} "{1}" {2} {3}'.format( - " ".join(env), wine_path, executable, args - ) - p = subprocess.Popen(command, cwd=working_dir, shell=True, - stdout=subprocess.PIPE) - if blocking: - p.communicate() + command = [wine_path, executable] + args.split() + thread = LutrisThread(command, runner=wine(), env=env, watch=True, cwd=working_dir) + thread.start() + return thread -def winetricks(app, prefix=None, winetricks_env=None, silent=True, - blocking=True): +def winetricks(app, prefix=None, winetricks_env=None, silent=True): """Execute winetricks.""" path = (system.find_executable('winetricks') or os.path.join(datapath.get(), 'bin/winetricks')) @@ -124,8 +122,8 @@ def winetricks(app, prefix=None, winetricks_env=None, silent=True, args = "-q " + app else: args = app - wineexec(None, prefix=prefix, winetricks_env=winetricks_env, - wine_path=path, arch=arch, args=args, blocking=blocking) + return wineexec(None, prefix=prefix, winetricks_env=winetricks_env, + wine_path=path, arch=arch, args=args) def winecfg(wine_path=None, prefix=None, arch='win32', blocking=True): @@ -152,11 +150,11 @@ def winecfg(wine_path=None, prefix=None, arch='win32', blocking=True): p.communicate() -def joycpl(wine_path=None, prefix=None, blocking=True): - """Execute winetricks.""" +def joycpl(wine_path=None, prefix=None): + """Execute Joystick control panel.""" arch = detect_prefix_arch(prefix) or 'win32' wineexec('control', prefix=prefix, - wine_path=wine_path, arch=arch, args='joy.cpl', blocking=blocking) + wine_path=wine_path, arch=arch, args='joy.cpl') def eject_disc(wine_path, prefix): @@ -180,10 +178,8 @@ def detect_prefix_arch(directory=None): for i in range(5): line = registry.readline() if 'win64' in line: - logger.debug("Detected 64bit prefix in %s", directory) return 'win64' elif 'win32' in line: - logger.debug("Detected 32bit prefix in %s", directory) return 'win32' logger.debug("Can't detect prefix arch for %s", directory) @@ -509,7 +505,6 @@ class wine(Runner): Get it from the config or detect it from the prefix""" arch = self.game_config.get('arch') or 'auto' if arch not in ('win32', 'win64'): - logger.debug('Arch not provided, auto-detecting') arch = detect_prefix_arch(self.prefix_path) or 'win32' return arch @@ -572,19 +567,16 @@ class wine(Runner): def run_winecfg(self, *args): winecfg(wine_path=self.get_executable(), prefix=self.prefix_path, - arch=self.wine_arch, blocking=False) + arch=self.wine_arch) def run_regedit(self, *args): - wineexec("regedit", wine_path=self.get_executable(), - prefix=self.prefix_path, blocking=False) + wineexec("regedit", wine_path=self.get_executable(), prefix=self.prefix_path) def run_winetricks(self, *args): - winetricks('', prefix=self.prefix_path, - winetricks_env=self.get_executable(), blocking=False) + winetricks('', prefix=self.prefix_path, winetricks_env=self.get_executable()) def run_joycpl(self, *args): - joycpl(prefix=self.prefix_path, wine_path=self.get_executable(), - blocking=False) + joycpl(prefix=self.prefix_path, wine_path=self.get_executable()) def set_regedit_keys(self): """Reset regedit keys according to config.""" From e0d99895691bf5a22eccf234ca97a2d7d8c6d362 Mon Sep 17 00:00:00 2001 From: Mathieu Comandon Date: Thu, 31 Dec 2015 00:05:42 -0800 Subject: [PATCH 40/68] Monitor installer tasks if they return a LutrisThread --- lutris/installer/commands.py | 15 ++++++++++++++- lutris/thread.py | 9 +++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/lutris/installer/commands.py b/lutris/installer/commands.py index ce1cb1235..ef71d1b2d 100644 --- a/lutris/installer/commands.py +++ b/lutris/installer/commands.py @@ -313,8 +313,21 @@ class Commands(object): data['prefix'] = self.target_path task = import_task(runner_name, task_name) - task(**data) + thread = task(**data) GLib.idle_add(self.parent.cancel_button.set_sensitive, True) + if isinstance(thread, LutrisThread): + # Monitor thread and continue when task has executed + self.heartbeat = GLib.timeout_add(1000, self._monitor_task, thread) + return 'STOP' + + def _monitor_task(self, thread): + logger.debug("Monitoring %s", thread) + if not thread.is_running: + logger.debug("Thread QUIT") + self._iter_commands() + self.heartbeat = None + return False + return True def write_config(self, params): self._check_required_params(['file', 'section', 'key', 'value'], diff --git a/lutris/thread.py b/lutris/thread.py index d9a27661f..5f096917b 100644 --- a/lutris/thread.py +++ b/lutris/thread.py @@ -24,7 +24,7 @@ class LutrisThread(threading.Thread): debug_output = True def __init__(self, command, runner=None, env={}, rootpid=None, term=None, - watch=True, cwd='/tmp'): + watch=True, cwd=None): """Thread init""" threading.Thread.__init__(self) self.env = env @@ -43,7 +43,12 @@ class LutrisThread(threading.Thread): self.startup_time = time.time() self.monitoring_started = False - self.cwd = runner.working_dir if self.runner else cwd + if cwd: + self.cwd = cwd + elif self.runner: + self.cwd = runner.working_dir + else: + self.cwd = '/tmp' self.env_string = '' for (k, v) in self.env.iteritems(): From 430e7ce77efa2c7991271d9a8373eec81b00af62 Mon Sep 17 00:00:00 2001 From: Mathieu Comandon Date: Thu, 31 Dec 2015 21:25:35 -0800 Subject: [PATCH 41/68] Randomize temporary extraction directories to avoid a very nasty bug --- lutris/util/extract.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lutris/util/extract.py b/lutris/util/extract.py index cf034da28..59a244d51 100644 --- a/lutris/util/extract.py +++ b/lutris/util/extract.py @@ -1,4 +1,5 @@ import os +import uuid import shutil import tarfile import zipfile @@ -26,7 +27,8 @@ def extract_archive(path, to_directory='.', merge_single=True, extractor=None): raise RuntimeError( "Could not extract `%s` as no appropriate extractor is found" % path) - temp_path = temp_dir = os.path.join(to_directory, "temp_lutris_extract") + temp_name = ".extract-" + str(uuid.uuid4())[:8] + temp_path = temp_dir = os.path.join(to_directory, temp_name) handler = opener(path, mode) handler.extractall(temp_path) handler.close() From 94bc204f293a5bb999fd1cff06a1ae7fb55da037 Mon Sep 17 00:00:00 2001 From: Mathieu Comandon Date: Wed, 30 Dec 2015 02:21:31 -0800 Subject: [PATCH 42/68] Add eject button for wine games using cdroms --- lutris/runners/wine.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lutris/runners/wine.py b/lutris/runners/wine.py index d5f6b00f2..45c70efbd 100644 --- a/lutris/runners/wine.py +++ b/lutris/runners/wine.py @@ -163,6 +163,10 @@ def eject_disc(wine_path, prefix): wineexec('eject', prefix=prefix, wine_path=wine_path, args='-a') +def eject_disc(wine_path, prefix): + wineexec('eject', prefix=prefix, wine_path=wine_path, args='-a') + + def detect_prefix_arch(directory=None): """Return the architecture of the prefix found in `directory`. From 598fc61d5a22e880de903a8903a5bd08a17f3a00 Mon Sep 17 00:00:00 2001 From: Xodetaetl Date: Tue, 29 Dec 2015 16:14:18 +0100 Subject: [PATCH 43/68] Revert "Revert "Fix forgotten inherited functions in winesteam from wine"" This reverts commit 60ca17a20934f840309aee8ae6eb90160da9c049. --- docs/installers.rst | 2 +- lutris/runners/winesteam.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/installers.rst b/docs/installers.rst index 255d00f86..97a88d5ab 100644 --- a/docs/installers.rst +++ b/docs/installers.rst @@ -306,7 +306,7 @@ Currently, the following tasks are implemented: prefix: $GAMEDIR path: HKEY_CURRENT_USER\Software\Valve\Steam key: SuppressAutoRun - value: 00000000 + value: '00000000' type: REG_DWORD * wine / winesteam: ``set_regedit_file`` Apply a regedit file to the diff --git a/lutris/runners/winesteam.py b/lutris/runners/winesteam.py index 6ca8c627c..38b429c16 100644 --- a/lutris/runners/winesteam.py +++ b/lutris/runners/winesteam.py @@ -17,9 +17,12 @@ from lutris.util.wineregistry import WineRegistry # Redefine wine installer tasks set_regedit = wine.set_regedit +set_regedit_file = wine.set_regedit_file +delete_registry_key = wine.delete_registry_key create_prefix = wine.create_prefix wineexec = wine.wineexec winetricks = wine.winetricks +winecfg = wine.winecfg STEAM_INSTALLER_URL = "http://lutris.net/files/runners/SteamInstall.msi" From ff1e93f0550535ab3bfaa5c070529b4215e0b4bc Mon Sep 17 00:00:00 2001 From: Xodetaetl Date: Thu, 31 Dec 2015 01:39:14 +0100 Subject: [PATCH 44/68] Add prefix param if not present in wine/winesteam installer task --- lutris/installer/commands.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lutris/installer/commands.py b/lutris/installer/commands.py index 6ed508d4d..ce1cb1235 100644 --- a/lutris/installer/commands.py +++ b/lutris/installer/commands.py @@ -308,6 +308,10 @@ class Commands(object): for key in data: data[key] = self._substitute(data[key]) + + if runner_name in ['wine', 'winesteam'] and 'prefix' not in data: + data['prefix'] = self.target_path + task = import_task(runner_name, task_name) task(**data) GLib.idle_add(self.parent.cancel_button.set_sensitive, True) From ded16e8f83b43cb691c57da06daaff9c6ca68168 Mon Sep 17 00:00:00 2001 From: Mathieu Comandon Date: Wed, 30 Dec 2015 23:32:57 -0800 Subject: [PATCH 45/68] Use LutrisThread for winexec --- lutris/runners/wine.py | 54 ++++++++++++++++++------------------------ 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/lutris/runners/wine.py b/lutris/runners/wine.py index 45c70efbd..78889204b 100644 --- a/lutris/runners/wine.py +++ b/lutris/runners/wine.py @@ -9,6 +9,7 @@ from lutris import settings from lutris.util import datapath, display, system from lutris.util.log import logger from lutris.runners.runner import Runner +from lutris.thread import LutrisThread WINE_DIR = os.path.join(settings.RUNNER_DIR, "wine") @@ -71,7 +72,7 @@ def create_prefix(prefix, wine_dir=None, arch='win32'): def wineexec(executable, args="", wine_path=None, prefix=None, arch=None, - working_dir=None, winetricks_env='', blocking=True): + working_dir=None, winetricks_env=''): """Execute a Wine command.""" detected_arch = detect_prefix_arch(prefix) executable = str(executable) if executable else '' @@ -86,34 +87,31 @@ def wineexec(executable, args="", wine_path=None, prefix=None, arch=None, if executable.endswith(".msi"): executable = 'msiexec /i "%s"' % executable elif executable: - executable = '"%s"' % executable + executable = '%s' % executable # Create prefix if necessary if not detected_arch: wine_dir = winetricks_env if winetricks_env else wine_path create_prefix(prefix, wine_dir=os.path.dirname(wine_dir), arch=arch) - env = ['WINEARCH=%s' % arch] + env = { + 'WINEARCH': arch + } if winetricks_env: - env.append('WINE="%s"' % winetricks_env) + env['WINE'] = winetricks_env if prefix: - env.append('WINEPREFIX="%s" ' % prefix) + env['WINEPREFIX'] = prefix if settings.RUNNER_DIR in wine_path: - runtime_path = ':'.join(runtime.get_paths()) - env.append('LD_LIBRARY_PATH={}'.format(runtime_path)) + env['LD_LIBRARY_PATH'] = ':'.join(runtime.get_paths()) - command = '{0} "{1}" {2} {3}'.format( - " ".join(env), wine_path, executable, args - ) - p = subprocess.Popen(command, cwd=working_dir, shell=True, - stdout=subprocess.PIPE) - if blocking: - p.communicate() + command = [wine_path, executable] + args.split() + thread = LutrisThread(command, runner=wine(), env=env, watch=True, cwd=working_dir) + thread.start() + return thread -def winetricks(app, prefix=None, winetricks_env=None, silent=True, - blocking=True): +def winetricks(app, prefix=None, winetricks_env=None, silent=True): """Execute winetricks.""" path = (system.find_executable('winetricks') or os.path.join(datapath.get(), 'bin/winetricks')) @@ -124,8 +122,8 @@ def winetricks(app, prefix=None, winetricks_env=None, silent=True, args = "-q " + app else: args = app - wineexec(None, prefix=prefix, winetricks_env=winetricks_env, - wine_path=path, arch=arch, args=args, blocking=blocking) + return wineexec(None, prefix=prefix, winetricks_env=winetricks_env, + wine_path=path, arch=arch, args=args) def winecfg(wine_path=None, prefix=None, arch='win32', blocking=True): @@ -152,11 +150,11 @@ def winecfg(wine_path=None, prefix=None, arch='win32', blocking=True): p.communicate() -def joycpl(wine_path=None, prefix=None, blocking=True): - """Execute winetricks.""" +def joycpl(wine_path=None, prefix=None): + """Execute Joystick control panel.""" arch = detect_prefix_arch(prefix) or 'win32' wineexec('control', prefix=prefix, - wine_path=wine_path, arch=arch, args='joy.cpl', blocking=blocking) + wine_path=wine_path, arch=arch, args='joy.cpl') def eject_disc(wine_path, prefix): @@ -184,10 +182,8 @@ def detect_prefix_arch(directory=None): for i in range(5): line = registry.readline() if 'win64' in line: - logger.debug("Detected 64bit prefix in %s", directory) return 'win64' elif 'win32' in line: - logger.debug("Detected 32bit prefix in %s", directory) return 'win32' logger.debug("Can't detect prefix arch for %s", directory) @@ -513,7 +509,6 @@ class wine(Runner): Get it from the config or detect it from the prefix""" arch = self.game_config.get('arch') or 'auto' if arch not in ('win32', 'win64'): - logger.debug('Arch not provided, auto-detecting') arch = detect_prefix_arch(self.prefix_path) or 'win32' return arch @@ -576,19 +571,16 @@ class wine(Runner): def run_winecfg(self, *args): winecfg(wine_path=self.get_executable(), prefix=self.prefix_path, - arch=self.wine_arch, blocking=False) + arch=self.wine_arch) def run_regedit(self, *args): - wineexec("regedit", wine_path=self.get_executable(), - prefix=self.prefix_path, blocking=False) + wineexec("regedit", wine_path=self.get_executable(), prefix=self.prefix_path) def run_winetricks(self, *args): - winetricks('', prefix=self.prefix_path, - winetricks_env=self.get_executable(), blocking=False) + winetricks('', prefix=self.prefix_path, winetricks_env=self.get_executable()) def run_joycpl(self, *args): - joycpl(prefix=self.prefix_path, wine_path=self.get_executable(), - blocking=False) + joycpl(prefix=self.prefix_path, wine_path=self.get_executable()) def set_regedit_keys(self): """Reset regedit keys according to config.""" From f763e580fb4c085194e564cd277297785f9bb52a Mon Sep 17 00:00:00 2001 From: Mathieu Comandon Date: Thu, 31 Dec 2015 00:05:42 -0800 Subject: [PATCH 46/68] Monitor installer tasks if they return a LutrisThread --- lutris/installer/commands.py | 15 ++++++++++++++- lutris/thread.py | 9 +++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/lutris/installer/commands.py b/lutris/installer/commands.py index ce1cb1235..ef71d1b2d 100644 --- a/lutris/installer/commands.py +++ b/lutris/installer/commands.py @@ -313,8 +313,21 @@ class Commands(object): data['prefix'] = self.target_path task = import_task(runner_name, task_name) - task(**data) + thread = task(**data) GLib.idle_add(self.parent.cancel_button.set_sensitive, True) + if isinstance(thread, LutrisThread): + # Monitor thread and continue when task has executed + self.heartbeat = GLib.timeout_add(1000, self._monitor_task, thread) + return 'STOP' + + def _monitor_task(self, thread): + logger.debug("Monitoring %s", thread) + if not thread.is_running: + logger.debug("Thread QUIT") + self._iter_commands() + self.heartbeat = None + return False + return True def write_config(self, params): self._check_required_params(['file', 'section', 'key', 'value'], diff --git a/lutris/thread.py b/lutris/thread.py index d9a27661f..5f096917b 100644 --- a/lutris/thread.py +++ b/lutris/thread.py @@ -24,7 +24,7 @@ class LutrisThread(threading.Thread): debug_output = True def __init__(self, command, runner=None, env={}, rootpid=None, term=None, - watch=True, cwd='/tmp'): + watch=True, cwd=None): """Thread init""" threading.Thread.__init__(self) self.env = env @@ -43,7 +43,12 @@ class LutrisThread(threading.Thread): self.startup_time = time.time() self.monitoring_started = False - self.cwd = runner.working_dir if self.runner else cwd + if cwd: + self.cwd = cwd + elif self.runner: + self.cwd = runner.working_dir + else: + self.cwd = '/tmp' self.env_string = '' for (k, v) in self.env.iteritems(): From 4a7c52461f87ba69d2d1fedeb0e4b6c25c129de0 Mon Sep 17 00:00:00 2001 From: Mathieu Comandon Date: Thu, 31 Dec 2015 21:25:35 -0800 Subject: [PATCH 47/68] Randomize temporary extraction directories to avoid a very nasty bug --- lutris/util/extract.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lutris/util/extract.py b/lutris/util/extract.py index cf034da28..59a244d51 100644 --- a/lutris/util/extract.py +++ b/lutris/util/extract.py @@ -1,4 +1,5 @@ import os +import uuid import shutil import tarfile import zipfile @@ -26,7 +27,8 @@ def extract_archive(path, to_directory='.', merge_single=True, extractor=None): raise RuntimeError( "Could not extract `%s` as no appropriate extractor is found" % path) - temp_path = temp_dir = os.path.join(to_directory, "temp_lutris_extract") + temp_name = ".extract-" + str(uuid.uuid4())[:8] + temp_path = temp_dir = os.path.join(to_directory, temp_name) handler = opener(path, mode) handler.extractall(temp_path) handler.close() From b7d854899768bb610d86061e579b6bda55bc9509 Mon Sep 17 00:00:00 2001 From: Mathieu Comandon Date: Wed, 30 Dec 2015 02:21:31 -0800 Subject: [PATCH 48/68] Add eject button for wine games using cdroms --- lutris/runners/wine.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lutris/runners/wine.py b/lutris/runners/wine.py index 78889204b..488d3390d 100644 --- a/lutris/runners/wine.py +++ b/lutris/runners/wine.py @@ -165,6 +165,10 @@ def eject_disc(wine_path, prefix): wineexec('eject', prefix=prefix, wine_path=wine_path, args='-a') +def eject_disc(wine_path, prefix): + wineexec('eject', prefix=prefix, wine_path=wine_path, args='-a') + + def detect_prefix_arch(directory=None): """Return the architecture of the prefix found in `directory`. From 79a0a99fd415733e3da422573f736013e76adcfe Mon Sep 17 00:00:00 2001 From: Mathieu Comandon Date: Mon, 4 Jan 2016 16:43:03 -0800 Subject: [PATCH 49/68] Don't cd into user directory on game launch --- lutris/thread.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/lutris/thread.py b/lutris/thread.py index 5f096917b..b03bf5c2f 100644 --- a/lutris/thread.py +++ b/lutris/thread.py @@ -79,8 +79,6 @@ class LutrisThread(threading.Thread): stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=self.cwd, env=env) - os.chdir(os.path.expanduser('~')) - for line in iter(self.game_process.stdout.readline, ''): self.stdout += line if self.debug_output: From 650fa9deb1b84bc11e94463de49748ed93efb3a9 Mon Sep 17 00:00:00 2001 From: Mathieu Comandon Date: Mon, 4 Jan 2016 16:51:42 -0800 Subject: [PATCH 50/68] Just cd back in home on game quit --- lutris/game.py | 3 +++ lutris/thread.py | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lutris/game.py b/lutris/game.py index 7edcae9ea..f709ab8df 100644 --- a/lutris/game.py +++ b/lutris/game.py @@ -311,6 +311,9 @@ class Game(object): quit_time = time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime()) logger.debug("%s stopped at %s", self.name, quit_time.decode("utf-8")) self.state = self.STATE_STOPPED + + os.chdir(os.path.expanduser('~')) + if self.resolution_changed\ or self.runner.system_config.get('reset_desktop'): display.change_resolution(self.original_outputs) diff --git a/lutris/thread.py b/lutris/thread.py index b03bf5c2f..d7774d4e3 100644 --- a/lutris/thread.py +++ b/lutris/thread.py @@ -109,7 +109,6 @@ class LutrisThread(threading.Thread): stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=self.cwd) - os.chdir(os.path.expanduser('~')) def iter_children(self, process, topdown=True, first=True): if self.runner and self.runner.name.startswith('wine') and first: From ce8ddfe75c8bede35f3af6e7b271de96b925f525 Mon Sep 17 00:00:00 2001 From: Mathieu Comandon Date: Mon, 4 Jan 2016 16:52:18 -0800 Subject: [PATCH 51/68] Make MAME config creation part of prelaunch --- lutris/runners/mame.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/lutris/runners/mame.py b/lutris/runners/mame.py index 66031e022..8a09cdd34 100644 --- a/lutris/runners/mame.py +++ b/lutris/runners/mame.py @@ -29,24 +29,28 @@ class mame(Runner): def get_executable(self): return os.path.join(settings.RUNNER_DIR, "mame/mame") + @property + def config_dir(self): + return os.path.join(os.path.expanduser("~"), ".mame") + + def prelaunch(self): + if not os.path.exists(os.path.join(self.config_dir, "mame.ini")): + try: + os.makedirs(self.config_dir) + except OSError: + pass + subprocess.Popen([self.get_executable(), "-createconfig"], + stdout=subprocess.PIPE) + return True + def play(self): options = [] rompath = os.path.dirname(self.game_config.get('main_file')) rom = os.path.basename(self.game_config.get('main_file')) - mameconfigdir = os.path.join(os.path.expanduser("~"), ".mame") if not self.runner_config.get('fullscreen'): options.append("-window") - if not os.path.exists(os.path.join(mameconfigdir, "mame.ini")): - try: - os.makedirs(mameconfigdir) - except OSError: - pass - os.chdir(mameconfigdir) - subprocess.Popen([self.get_executable(), "-createconfig"], - stdout=subprocess.PIPE) - os.chdir(rompath) return {'command': [self.get_executable(), - "-inipath", mameconfigdir, + "-inipath", self.config_dir, "-video", "opengl", "-skip_gameinfo", "-rompath", rompath, From 3b4b6be25bdcc98ffd88c5bbd8dd0a51cf5d649d Mon Sep 17 00:00:00 2001 From: Mathieu Comandon Date: Mon, 4 Jan 2016 16:53:24 -0800 Subject: [PATCH 52/68] Set MAME's working dir to its config dir --- lutris/runners/mame.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lutris/runners/mame.py b/lutris/runners/mame.py index 8a09cdd34..12c4085e0 100644 --- a/lutris/runners/mame.py +++ b/lutris/runners/mame.py @@ -33,6 +33,10 @@ class mame(Runner): def config_dir(self): return os.path.join(os.path.expanduser("~"), ".mame") + @property + def working_dir(self): + return self.config_dir + def prelaunch(self): if not os.path.exists(os.path.join(self.config_dir, "mame.ini")): try: From deaaae6c4dd2dbdea94f39aec0dd2ec68df1aa28 Mon Sep 17 00:00:00 2001 From: Mathieu Comandon Date: Mon, 4 Jan 2016 17:00:07 -0800 Subject: [PATCH 53/68] Also use MAME's config dir as MESS's working dir --- lutris/runners/mess.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lutris/runners/mess.py b/lutris/runners/mess.py index 1690101a9..d42a31ff0 100644 --- a/lutris/runners/mess.py +++ b/lutris/runners/mess.py @@ -57,6 +57,10 @@ class mess(Runner): def get_executable(self): return os.path.join(settings.RUNNER_DIR, "mess/mess") + @property + def working_dir(self): + return os.path.join(os.path.expanduser("~"), ".mame") + def play(self): rompath = self.runner_config.get('rompath') or '' if not os.path.exists(rompath): From 1f6cc1837ff8fd5a3be3951b8133454100e6175d Mon Sep 17 00:00:00 2001 From: Mathieu Comandon Date: Wed, 30 Dec 2015 02:21:31 -0800 Subject: [PATCH 54/68] Add eject button for wine games using cdroms --- lutris/runners/wine.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lutris/runners/wine.py b/lutris/runners/wine.py index 488d3390d..57fba6bd9 100644 --- a/lutris/runners/wine.py +++ b/lutris/runners/wine.py @@ -169,6 +169,10 @@ def eject_disc(wine_path, prefix): wineexec('eject', prefix=prefix, wine_path=wine_path, args='-a') +def eject_disc(wine_path, prefix): + wineexec('eject', prefix=prefix, wine_path=wine_path, args='-a') + + def detect_prefix_arch(directory=None): """Return the architecture of the prefix found in `directory`. From 658f6c7088ace721d5eddca23f37b0786db8fa7a Mon Sep 17 00:00:00 2001 From: Mathieu Comandon Date: Tue, 5 Jan 2016 08:51:00 -0800 Subject: [PATCH 55/68] Changelog for version 0.3.7.2 --- Makefile | 2 +- debian/changelog | 11 +++++++++++ lutris.spec | 2 +- lutris/settings.py | 2 +- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 48542ec43..7d582a64a 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -VERSION="0.3.7.1" +VERSION="0.3.7.2" cover: rm tests/fixtures/pga.db -f diff --git a/debian/changelog b/debian/changelog index b328cfa71..040efc30c 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,14 @@ +lutris (0.3.7.2) wily; urgency=medium + + * Add button to eject CD-ROMs during installation of Wine games + * Prevent MAME and MESS to save config files in home directory + * Monitor installation tasks so installers can respawn processes + * Randomize extractions folder names to prevent a bug occuring when + extracting several archives concurrently + * Allow loading environment variables from system config + + -- Mathieu Comandon Tue, 05 Jan 2016 08:41:23 -0800 + lutris (0.3.7.1) wily; urgency=medium * Improved command line option to list games diff --git a/lutris.spec b/lutris.spec index d176fb43a..89e015d89 100644 --- a/lutris.spec +++ b/lutris.spec @@ -1,7 +1,7 @@ %{!?python_sitelib: %global python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")} Name: lutris -Version: 0.3.7.1 +Version: 0.3.7.2 Release: 2%{?dist} Summary: Install and play any video game easily diff --git a/lutris/settings.py b/lutris/settings.py index d52b4fe45..efc906525 100644 --- a/lutris/settings.py +++ b/lutris/settings.py @@ -5,7 +5,7 @@ from gi.repository import GLib from lutris.util.settings import SettingsIO PROJECT = "Lutris" -VERSION = "0.3.7.1" +VERSION = "0.3.7.2" COPYRIGHT = "(c) 2010-2015 Lutris Gaming Platform" AUTHORS = ["Mathieu Comandon ", "Pascal Reinhard (Xodetaetl) Date: Tue, 5 Jan 2016 08:54:37 -0800 Subject: [PATCH 56/68] Run clean before building packages --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 7d582a64a..cf4f9ea97 100644 --- a/Makefile +++ b/Makefile @@ -9,12 +9,12 @@ test: rm tests/fixtures/pga.db -f nosetests -deb-source: +deb-source: clean gbp buildpackage -S mkdir -p build mv ../lutris_0* build -deb: +deb: clean gbp buildpackage mkdir -p build mv ../lutris_0* build From 89731d01246630359bb7c4efc6930ce564448b26 Mon Sep 17 00:00:00 2001 From: Mathieu Comandon Date: Tue, 5 Jan 2016 16:48:13 -0800 Subject: [PATCH 57/68] Prevent a crash when no resolution is provided to change_resolution --- lutris/util/display.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lutris/util/display.py b/lutris/util/display.py index 61671de42..1e36bbe40 100644 --- a/lutris/util/display.py +++ b/lutris/util/display.py @@ -79,6 +79,9 @@ def change_resolution(resolution): Takes a string for single monitors or a list of displays as returned by get_outputs(). """ + if not resolution: + logger.warning("No resolution provided") + return if isinstance(resolution, basestring): logger.debug("Switching resolution to %s", resolution) From d1230de6eaaf4e525970356cbe7d96c186ab0ea2 Mon Sep 17 00:00:00 2001 From: Mathieu Comandon Date: Wed, 6 Jan 2016 15:29:33 -0800 Subject: [PATCH 58/68] Add PPSSPP runner --- lutris/gui/widgets.py | 4 ++ lutris/runners/__init__.py | 2 +- lutris/runners/ppsspp.py | 41 +++++++++++++++++++++ share/lutris/media/runner_icons/ppsspp.png | Bin 0 -> 5405 bytes 4 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 lutris/runners/ppsspp.py create mode 100644 share/lutris/media/runner_icons/ppsspp.png diff --git a/lutris/gui/widgets.py b/lutris/gui/widgets.py index 6c9fc57ac..581e90547 100644 --- a/lutris/gui/widgets.py +++ b/lutris/gui/widgets.py @@ -4,6 +4,7 @@ import os from gi.repository import Gtk, GObject, GdkPixbuf, GLib, Pango +from lutris.util.log import logger from lutris.downloader import Downloader from lutris.util import datapath from lutris.util.system import reverse_expanduser @@ -14,6 +15,9 @@ PADDING = 5 def get_runner_icon(runner_name, format='image', size=None): icon_path = os.path.join(datapath.get(), 'media/runner_icons', runner_name + '.png') + if not os.path.exists(icon_path): + logger.error("Unable to find icon '%s'", icon_path) + return if format == 'image': icon = Gtk.Image() icon.set_from_file(icon_path) diff --git a/lutris/runners/__init__.py b/lutris/runners/__init__.py index c7fa6aa5b..e9a3be312 100644 --- a/lutris/runners/__init__.py +++ b/lutris/runners/__init__.py @@ -15,7 +15,7 @@ __all__ = ( # Nintendo "snes9x", "mupen64plus", "dolphin", # Sony - "pcsxr", + "pcsxr", "ppsspp", # Sega "osmose", "dgen", "reicast", # Misc legacy systems diff --git a/lutris/runners/ppsspp.py b/lutris/runners/ppsspp.py new file mode 100644 index 000000000..a81bf9b10 --- /dev/null +++ b/lutris/runners/ppsspp.py @@ -0,0 +1,41 @@ +import os +from lutris import settings +from lutris.runners.runner import Runner + + +class ppsspp(Runner): + human_name = "PPSSPP" + description = "Sony PSP emulator" + platform = "Sony PSP" + game_options = [ + { + 'option': 'main_file', + 'type': 'file', + 'label': 'ISO file', + 'default_path': 'game_path' + } + ] + + runner_options = [ + { + 'option': 'fullscreen', + 'type': 'bool', + 'label': 'Fullscreen', + 'default': False, + } + ] + + def get_executable(self): + return os.path.join(settings.RUNNER_DIR, 'ppsspp/PPSSPPSDL') + + def play(self): + arguments = [self.get_executable()] + + if self.runner_config.get('fullscreen'): + arguments.append('--fullscreen') + + iso = self.game_config.get('main_file') or '' + if not os.path.exists(iso): + return {'error': 'FILE_NOT_FOUND', 'file': iso} + arguments.append(iso) + return {'command': arguments} diff --git a/share/lutris/media/runner_icons/ppsspp.png b/share/lutris/media/runner_icons/ppsspp.png new file mode 100644 index 0000000000000000000000000000000000000000..617ef485587a2da101c90526de3e7357acab035f GIT binary patch literal 5405 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 Hu0mjfqgXxm literal 0 HcmV?d00001 From e1b5cf760311dda389f0b4070e5073ffebc4a6d9 Mon Sep 17 00:00:00 2001 From: Mathieu Comandon Date: Wed, 6 Jan 2016 23:41:11 -0800 Subject: [PATCH 59/68] Add PCSX2 runner --- lutris/runners/__init__.py | 2 +- lutris/runners/pcsx2.py | 41 ++++++++++++++++++++++ share/lutris/media/runner_icons/pcsx2.png | Bin 0 -> 5314 bytes 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 lutris/runners/pcsx2.py create mode 100644 share/lutris/media/runner_icons/pcsx2.png diff --git a/lutris/runners/__init__.py b/lutris/runners/__init__.py index e9a3be312..82903db70 100644 --- a/lutris/runners/__init__.py +++ b/lutris/runners/__init__.py @@ -15,7 +15,7 @@ __all__ = ( # Nintendo "snes9x", "mupen64plus", "dolphin", # Sony - "pcsxr", "ppsspp", + "pcsxr", "ppsspp", "pcsx2", # Sega "osmose", "dgen", "reicast", # Misc legacy systems diff --git a/lutris/runners/pcsx2.py b/lutris/runners/pcsx2.py new file mode 100644 index 000000000..fb33c0474 --- /dev/null +++ b/lutris/runners/pcsx2.py @@ -0,0 +1,41 @@ +import os +from lutris import settings +from lutris.runners.runner import Runner + + +class pcsx2(Runner): + human_name = "PCSX2" + description = "Playstation 2 emulator" + platform = "Sony Playstation 2" + game_options = [ + { + 'option': 'main_file', + 'type': 'file', + 'label': 'ISO file', + 'default_path': 'game_path' + } + ] + + runner_options = [ + { + 'option': 'fullscreen', + 'type': 'bool', + 'label': 'Fullscreen', + 'default': False, + } + ] + + def get_executable(self): + return os.path.join(settings.RUNNER_DIR, 'pcsx2/PCSX2') + + def play(self): + arguments = [self.get_executable()] + + if self.runner_config.get('fullscreen'): + arguments.append('--fullscreen') + + iso = self.game_config.get('main_file') or '' + if not os.path.exists(iso): + return {'error': 'FILE_NOT_FOUND', 'file': iso} + arguments.append(iso) + return {'command': arguments} diff --git a/share/lutris/media/runner_icons/pcsx2.png b/share/lutris/media/runner_icons/pcsx2.png new file mode 100644 index 0000000000000000000000000000000000000000..65eef2bd0afe35e421b3f206cdd0a370c15b5105 GIT binary patch literal 5314 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 UGFoOQNdN!<07*qoM6N<$f;gTYfdBvi literal 0 HcmV?d00001 From 5f34089226e99ce17be1d86759e9732f084284fe Mon Sep 17 00:00:00 2001 From: djazz Date: Thu, 7 Jan 2016 20:03:31 +0100 Subject: [PATCH 60/68] wrong command in warning message --- lutris/installer/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lutris/installer/commands.py b/lutris/installer/commands.py index ef71d1b2d..e075df539 100644 --- a/lutris/installer/commands.py +++ b/lutris/installer/commands.py @@ -331,7 +331,7 @@ class Commands(object): def write_config(self, params): self._check_required_params(['file', 'section', 'key', 'value'], - params, 'move') + params, 'write_config') """Write a key-value pair into an INI type config file.""" # Get file config_file = self._get_file(params['file']) From 76514e8c92d8474e3f0c0b2f0e1420ef75243fe7 Mon Sep 17 00:00:00 2001 From: Mathieu Comandon Date: Thu, 7 Jan 2016 18:56:14 -0800 Subject: [PATCH 61/68] Check for availability of xdg-user-dir --- lutris/shortcuts.py | 20 +++++++++++++------- lutris/util/system.py | 6 ++++++ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/lutris/shortcuts.py b/lutris/shortcuts.py index b6a7702ee..1a028d6ee 100644 --- a/lutris/shortcuts.py +++ b/lutris/shortcuts.py @@ -8,6 +8,8 @@ from textwrap import dedent from xdg import BaseDirectory from gi.repository import GLib +from lutris.util import system +from lutris.util.log import logger from lutris.settings import CACHE_DIR @@ -57,7 +59,11 @@ def get_launcher_path(game_slug, game_id): When legacy is set, it will return the old path with only the slug, otherwise it will return the path with slug + id """ - desktop_dir = subprocess.Popen(['xdg-user-dir', 'DESKTOP'], + xdg_executable = 'xdg-user-dir' + if not system.find_executable(xdg_executable): + logger.error("%s not found", xdg_executable) + return + desktop_dir = subprocess.Popen([xdg_executable, 'DESKTOP'], stdout=subprocess.PIPE).communicate()[0] desktop_dir = desktop_dir.strip() @@ -65,7 +71,7 @@ def get_launcher_path(game_slug, game_id): desktop_dir, get_xdg_basename(game_slug, game_id, legacy=True) ) # First check if legacy path exists, for backward compatibility - if os.path.exists(legacy_launcher_path): + if system.path_exists(legacy_launcher_path): return legacy_launcher_path # Otherwise return new path, whether it exists or not return os.path.join( @@ -81,7 +87,7 @@ def get_menu_launcher_path(game_slug, game_id): menu_path = os.path.join( menu_dir, get_xdg_basename(game_slug, game_id, legacy=True) ) - if os.path.exists(menu_path): + if system.path_exists(menu_path): return menu_path return os.path.join( menu_dir, get_xdg_basename(game_slug, game_id, legacy=False) @@ -89,21 +95,21 @@ def get_menu_launcher_path(game_slug, game_id): def desktop_launcher_exists(game_slug, game_id): - return os.path.exists(get_launcher_path(game_slug, game_id)) + return system.path_exists(get_launcher_path(game_slug, game_id)) def menu_launcher_exists(game_slug, game_id): - return os.path.exists(get_menu_launcher_path(game_slug, game_id)) + return system.path_exists(get_menu_launcher_path(game_slug, game_id)) def remove_launcher(game_slug, game_id, desktop=False, menu=False): """Remove existing .desktop file.""" if desktop: launcher_path = get_launcher_path(game_slug, game_id) - if os.path.exists(launcher_path): + if system.path_exists(launcher_path): os.remove(launcher_path) if menu: menu_path = get_menu_launcher_path(game_slug, game_id) - if os.path.exists(menu_path): + if system.path_exists(menu_path): os.remove(menu_path) diff --git a/lutris/util/system.py b/lutris/util/system.py index 6dd999449..cc2906dd2 100644 --- a/lutris/util/system.py +++ b/lutris/util/system.py @@ -223,3 +223,9 @@ def reverse_expanduser(path): path = path[len(user_path):].strip('/') return '~/' + path return path + + +def path_exists(path): + if not path: + return False + return os.path.exists(path) From 66e05945036e56bdfa9ab3ed2fc44ea043cda81a Mon Sep 17 00:00:00 2001 From: Mathieu Comandon Date: Sat, 16 Jan 2016 01:21:33 -0800 Subject: [PATCH 62/68] Check that wine64 exists when getting pids using it --- lutris/runners/wine.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lutris/runners/wine.py b/lutris/runners/wine.py index 0451c1e8f..a4d200f75 100644 --- a/lutris/runners/wine.py +++ b/lutris/runners/wine.py @@ -620,7 +620,9 @@ class wine(Runner): if not exe.startswith('/'): exe = system.find_executable(exe) if self.wine_arch == 'win64': - exe += '64' + wine64 = system.find_executable(exe + '64') + if wine64: + exe = wine64 return system.get_pids_using_file(exe) def get_xinput_path(self): From 0c9e6e5596c94783921078d654f34a4e2483691e Mon Sep 17 00:00:00 2001 From: Mathieu Comandon Date: Sat, 16 Jan 2016 01:52:59 -0800 Subject: [PATCH 63/68] Remove duplicated Popen in thread --- lutris/thread.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/lutris/thread.py b/lutris/thread.py index d7774d4e3..e2c4081c6 100644 --- a/lutris/thread.py +++ b/lutris/thread.py @@ -75,10 +75,7 @@ class LutrisThread(threading.Thread): self.terminal = False env = os.environ.copy() env.update(self.env) - self.game_process = subprocess.Popen(self.command, bufsize=1, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - cwd=self.cwd, env=env) + self.game_process = self.execute_process(self.command, env) for line in iter(self.game_process.stdout.readline, ''): self.stdout += line if self.debug_output: @@ -104,11 +101,12 @@ class LutrisThread(threading.Thread): )) os.chmod(file_path, 0744) - term_command = [self.terminal, '-e', file_path] - self.game_process = subprocess.Popen(term_command, bufsize=1, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - cwd=self.cwd) + self.game_process = self.execute_process([self.terminal, '-e', file_path]) + + def execute_process(self, command, env=None): + return subprocess.Popen(command, bufsize=1, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + cwd=self.cwd, env=env) def iter_children(self, process, topdown=True, first=True): if self.runner and self.runner.name.startswith('wine') and first: From 64b8018cd86484294375409f10f4092ec574d348 Mon Sep 17 00:00:00 2001 From: Mathieu Comandon Date: Sun, 17 Jan 2016 10:01:47 -0800 Subject: [PATCH 64/68] Make calls to regedit blocking --- lutris/runners/wine.py | 16 ++++++++++------ lutris/util/system.py | 7 +++++-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/lutris/runners/wine.py b/lutris/runners/wine.py index a4d200f75..4f9bf3563 100644 --- a/lutris/runners/wine.py +++ b/lutris/runners/wine.py @@ -45,12 +45,13 @@ def set_regedit(path, key, value='', type='REG_SZ', wine_path=None, def set_regedit_file(filename, wine_path=None, prefix=None, arch='win32'): """Apply a regedit file to the Windows registry.""" - wineexec('regedit', args=filename, wine_path=wine_path, prefix=prefix, arch=arch) + wineexec('regedit', args=filename, wine_path=wine_path, prefix=prefix, arch=arch, + blocking=True) def delete_registry_key(key, wine_path=None, prefix=None, arch='win32'): wineexec('regedit', args='/D "%s"' % key, wine_path=wine_path, - prefix=prefix, arch=arch) + prefix=prefix, arch=arch, blocking=True) def create_prefix(prefix, wine_dir=None, arch='win32'): @@ -72,7 +73,7 @@ def create_prefix(prefix, wine_dir=None, arch='win32'): def wineexec(executable, args="", wine_path=None, prefix=None, arch=None, - working_dir=None, winetricks_env=''): + working_dir=None, winetricks_env='', blocking=False): """Execute a Wine command.""" detected_arch = detect_prefix_arch(prefix) executable = str(executable) if executable else '' @@ -106,9 +107,12 @@ def wineexec(executable, args="", wine_path=None, prefix=None, arch=None, env['LD_LIBRARY_PATH'] = ':'.join(runtime.get_paths()) command = [wine_path, executable] + args.split() - thread = LutrisThread(command, runner=wine(), env=env, watch=True, cwd=working_dir) - thread.start() - return thread + if blocking: + return system.execute(command, env=env, cwd=working_dir) + else: + thread = LutrisThread(command, runner=wine(), env=env, cwd=working_dir) + thread.start() + return thread def winetricks(app, prefix=None, winetricks_env=None, silent=True): diff --git a/lutris/util/system.py b/lutris/util/system.py index cc2906dd2..957b5b6b7 100644 --- a/lutris/util/system.py +++ b/lutris/util/system.py @@ -12,13 +12,16 @@ from lutris.util.log import logger is_64bit = sys.maxsize > 2**32 -def execute(command, shell=False): +def execute(command, shell=False, env=None, cwd=None): """Execute a system command and return its results.""" + # logger.debug("Executing %s", ' '.join(command)) + # logger.debug("ENV: %s", env) try: stdout, stderr = subprocess.Popen(command, shell=shell, stdout=subprocess.PIPE, - stderr=subprocess.PIPE).communicate() + stderr=subprocess.PIPE, + env=env, cwd=cwd).communicate() except OSError as ex: logger.error('Could not run command %s: %s', command, ex) return From 26fd229081b519df56d6a54e9da3e780a42eff26 Mon Sep 17 00:00:00 2001 From: Mathieu Comandon Date: Sun, 17 Jan 2016 15:48:13 -0800 Subject: [PATCH 65/68] Add Swedish translations --- po/sv.po | 183 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 po/sv.po diff --git a/po/sv.po b/po/sv.po new file mode 100644 index 000000000..56651ead8 --- /dev/null +++ b/po/sv.po @@ -0,0 +1,183 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2015-05-03 16:49+0100\n" +"PO-Revision-Date: 2016-01-18 00:39+0100\n" +"Language: sv_SE\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Last-Translator: \n" +"Language-Team: \n" +"X-Generator: Poedit 1.8.6\n" + +#: ../data/ui/AboutDialog.ui.h:1 +msgid "About Lutris" +msgstr "Om lutris" + +#: ../data/ui/AboutDialog.ui.h:2 +msgid "© 2010, 2012 Mathieu Comandon" +msgstr "© 2010, 2012 Mathieu Comandon" + +#: ../data/ui/AboutDialog.ui.h:3 +msgid "Open Source Gaming Platform" +msgstr "Öppen källkod spelplattform" + +#: ../data/ui/AboutDialog.ui.h:4 +msgid "http://lutris.net" +msgstr "http://lutris.net" + +#: ../data/ui/AboutDialog.ui.h:5 +msgid "" +"This program is free software: you can redistribute it and/or modify\n" +"it under the terms of the GNU General Public License as published by\n" +"the Free Software Foundation, either version 3 of the License, or\n" +"(at your option) any later version.\n" +"\n" +"This program is distributed in the hope that it will be useful,\n" +"but WITHOUT ANY WARRANTY; without even the implied warranty of\n" +"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" +"GNU General Public License for more details.\n" +"\n" +"You should have received a copy of the GNU General Public License\n" +"along with this program. If not, see .\n" +msgstr "" + +#: ../data/ui/LutrisWindow3.ui.h:1 ../data/ui/LutrisWindow.ui.h:1 +msgid "Lutris" +msgstr "Lutris" + +#: ../data/ui/LutrisWindow3.ui.h:2 +msgid "_File" +msgstr "_Arkiv" + +#: ../data/ui/LutrisWindow3.ui.h:3 ../data/ui/LutrisWindow.ui.h:9 +msgid "_Edit" +msgstr "_Redigera" + +#: ../data/ui/LutrisWindow3.ui.h:4 ../data/ui/LutrisWindow.ui.h:11 +msgid "_View" +msgstr "_Granska" + +#: ../data/ui/LutrisWindow3.ui.h:5 ../data/ui/LutrisWindow.ui.h:16 +msgid "_Help" +msgstr "_Hjälp" + +#: ../data/ui/LutrisWindow3.ui.h:6 ../data/ui/LutrisWindow.ui.h:21 +msgid "Add game" +msgstr "Lägg till spel" + +#: ../data/ui/LutrisWindow3.ui.h:7 ../data/ui/LutrisWindow.ui.h:18 +msgid "Play" +msgstr "Spela" + +#: ../data/ui/LutrisWindow3.ui.h:8 ../data/ui/LutrisWindow.ui.h:20 +msgid "Stop" +msgstr "Sluta" + +#: ../data/ui/LutrisWindow3.ui.h:9 ../data/ui/LutrisWindow.ui.h:24 +msgid "Remove" +msgstr "Ta bort" + +#: ../data/ui/LutrisWindow.ui.h:2 +msgid "_Lutris" +msgstr "_Lutris" + +#: ../data/ui/LutrisWindow.ui.h:3 +msgid "Connect" +msgstr "Ansluta" + +#: ../data/ui/LutrisWindow.ui.h:4 +msgid "Install and configure runners" +msgstr "Installera och konfigurera löpare" + +#: ../data/ui/LutrisWindow.ui.h:5 +msgid "Manage runners" +msgstr "Hantera löpare" + +#: ../data/ui/LutrisWindow.ui.h:6 +msgid "Import" +msgstr "Importera" + +#: ../data/ui/LutrisWindow.ui.h:7 +msgid "Import games from ScummVM" +msgstr "Importera spel från ScummVM" + +#: ../data/ui/LutrisWindow.ui.h:8 +msgid "ScummVM" +msgstr "ScummVM" + +#: ../data/ui/LutrisWindow.ui.h:10 +msgid "configure the default options" +msgstr "konfigurera standardalternativen" + +#: ../data/ui/LutrisWindow.ui.h:12 +msgid "Icons" +msgstr "Ikoner" + +#: ../data/ui/LutrisWindow.ui.h:13 +msgid "List" +msgstr "Lista" + +#: ../data/ui/LutrisWindow.ui.h:14 +msgid "_Game" +msgstr "_Spel" + +#: ../data/ui/LutrisWindow.ui.h:15 +msgid "Start" +msgstr "Starta" + +#: ../data/ui/LutrisWindow.ui.h:17 +msgid "Play game" +msgstr "Spela spel" + +#: ../data/ui/LutrisWindow.ui.h:19 +msgid "Stop game" +msgstr "Stoppa spel" + +#: ../data/ui/LutrisWindow.ui.h:22 +msgid "Add Game" +msgstr "Lägg till spel" + +#: ../data/ui/LutrisWindow.ui.h:23 +msgid "Remove game from library" +msgstr "Ta bort spelet från biblioteket" + +#: ../data/ui/LutrisWindow.ui.h:25 +msgid "Filter the list of games" +msgstr "Filtrera listan över spel" + +#: ../data/ui/AboutLutrisDialog.ui.h:1 +msgid "" +"# Copyright (C) 2010 Mathieu Comandon \n" +"# This program is free software: you can redistribute it and/or modify it\n" +"# under the terms of the GNU General Public License version 3, as published\n" +"# by the Free Software Foundation.\n" +"#\n" +"# This program is distributed in the hope that it will be useful, but\n" +"# WITHOUT ANY WARRANTY; without even the implied warranties of\n" +"# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR\n" +"# PURPOSE. See the GNU General Public License for more details.\n" +"#\n" +"# You should have received a copy of the GNU General Public License along\n" +"# with this program. If not, see .\n" +msgstr "" + +#: ../data/ui/AboutLutrisDialog.ui.h:14 +msgid "Copyright (C) 2010 Mathieu Comandon " +msgstr "Copyright (C) 2010 Mathieu Comandon Date: Sat, 23 Jan 2016 12:50:02 -0800 Subject: [PATCH 66/68] Add Python XDG dependency for Suse and Fedora --- lutris.spec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lutris.spec b/lutris.spec index 89e015d89..65d6f20ae 100644 --- a/lutris.spec +++ b/lutris.spec @@ -18,7 +18,7 @@ BuildRequires: python-devel %if 0%{?fedora_version} BuildRequires: pygobject3, python3-gobject -Requires: pygobject3, PyYAML +Requires: pygobject3, PyYAML, python-xdg %endif %if 0%{?rhel_version} || 0%{?centos_version} BuildRequires: pygobject3 @@ -30,7 +30,7 @@ BuildRequires: update-desktop-files # Needed to workaround "directories not owned by a package" issue BuildRequires: hicolor-icon-theme BuildRequires: polkit -Requires: python-gobject, python-gtk, python-PyYAML +Requires: python-gobject, python-gtk, python-PyYAML, python-xdg %endif %if 0%{?fedora_version} || 0%{?suse_version} BuildRequires: fdupes From d7cc77fe9a71908cbc6f091cfe5449b325aea1da Mon Sep 17 00:00:00 2001 From: Mathieu Comandon Date: Sat, 23 Jan 2016 16:47:45 -0800 Subject: [PATCH 67/68] Remove duplicate INSTALL file --- INSTALL | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 INSTALL diff --git a/INSTALL b/INSTALL deleted file mode 100644 index 9d2fa6445..000000000 --- a/INSTALL +++ /dev/null @@ -1,31 +0,0 @@ -Installing Lutris -================= - -Requirements ------------- - -Lutris should work on any fairly recent GNU/Linux system, the following -dependencies are required: - - * python == 2.7 - * python-yaml - * PyGobject - * libsoup-gnome - * gvfs-backends - -Installation ------------- - -Lutris uses Python's distutils framework for installation. In order to -install Lutris, you will need root access. To install Lutris, perform -the following command as root: - - $ python setup.py install - -Run Lutris ------------ - -If you installed Lutris using the setup.py script, you can launch the -program by typing "lutris" at the command line. If you want to run -Lutris without installing it, start "bin/lutris" from within the -Lutris directory. From 8fafd808ba048faa4a3148e1b5da68b16fe9e823 Mon Sep 17 00:00:00 2001 From: Mathieu Comandon Date: Sat, 23 Jan 2016 17:03:28 -0800 Subject: [PATCH 68/68] Warn about no way to uninstall setup.py install --- INSTALL.rst | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/INSTALL.rst b/INSTALL.rst index c88b977d0..5203739e2 100644 --- a/INSTALL.rst +++ b/INSTALL.rst @@ -17,10 +17,17 @@ Installation Lutris uses Python's distutils framework for installation. In order to install Lutris, you will need root access. To install Lutris, perform -the following command as root: +the following command as root:: $ python setup.py install +Warning: there is no way to cleanly uninstall programs installed with setup.py +other than manuall deleting the created files. Prefer installing Lutris +through distribution packages or run it directly from the source directory:: + + cd /path/to/lutris/source + ./bin/lutris + Run Lutris -----------