Merge from next

This commit is contained in:
Mathieu Comandon 2016-01-24 16:15:29 -08:00
commit c8acbd805d
43 changed files with 727 additions and 288 deletions

View file

@ -5,3 +5,8 @@ Contributors:
Mathieu Comandon <strider@strycore.com>
Ludovic Soulié <contact@ludal.net>
Pascal Reinhard (Xodetaetl) <dev@xod.me>
Rob Loach <robloach@gmail.com>
Rémi Verschelde <rverschelde@gmail.com>
Ivan <malkavi@users.noreply.github.com>
mikeyd <mdeguzis@gmail.com>
Travis Nickles <nickles.travis@gmail.com>

31
INSTALL
View file

@ -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.

View file

@ -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
-----------
@ -72,4 +79,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

View file

@ -1,4 +1,4 @@
VERSION="0.3.7"
VERSION="0.3.7.2"
cover:
rm tests/fixtures/pga.db -f
@ -9,12 +9,12 @@ test:
rm tests/fixtures/pga.db -f
nosetests3
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

View file

@ -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
@ -70,16 +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("--reinstall", action="store_true", help="Reinstall game")
(options, args) = parser.parse_args()
@ -90,13 +95,29 @@ 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'))
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 game_list:
games.append({
'id': game['id'],
'slug': game['slug'],
'name': game['name'],
'runner': game['runner'],
'directory': game['directory'] or '-'
})
print json.dumps(games, indent=2).encode('utf-8')
else:
for game in game_list:
print u"{:4} | {:<40} | {:<40} | {:<15} | {:<64}".format(
game['id'],
game['name'][:40],
game['slug'][:40],
game['runner'],
game['directory'] or '-'
).encode('utf-8')
exit()
if options.list_steam:
from lutris.runners import winesteam

23
debian/changelog vendored
View file

@ -1,3 +1,26 @@
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 <strycore@gmail.com> Tue, 05 Jan 2016 08:41:23 -0800
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 <strycore@gmail.com> Tue, 29 Dec 2015 18:47:05 -0800
lutris (0.3.7) wily; urgency=medium
* Global:

4
debian/control vendored
View file

@ -4,16 +4,16 @@ Priority: optional
Build-Depends: cdbs,
debhelper,
python,
python-support,
dh-python,
gir1.2-gtk-3.0,
gir1.2-glib-2.0,
python-gi,
libgirepository1.0-dev,
libsdl2-2.0-0:i386
Maintainer: Mathieu Comandon <strider@strycore.com>
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

4
debian/copyright vendored
View file

@ -1,10 +1,10 @@
Format-Specification: http://wiki.debian.org/Proposals/CopyrightFormat
Upstream-Name: lutris
Upstream-Maintainer: Mathieu Comandon <strycore@gmail.com>
Upstream-Source: https://launchpad.net/lutris
Upstream-Source: https://github.com/lutris
Files: *
Copyright: (C) 2009, 2010 Mathieu Comandon <strycore@gmail.com>
Copyright: (C) 2009, 2015 Mathieu Comandon <strycore@gmail.com>
License: GPL-3
The full text of the GPL is distributed in
/usr/share/common-licenses/GPL-3 on Debian systems.

1
debian/pyversions vendored
View file

@ -1 +0,0 @@
2.7

2
debian/rules vendored
View file

@ -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

View file

@ -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

View file

@ -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.2
Release: 2%{?dist}
Summary: Install and play any video game easily
@ -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

View file

@ -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
)
@ -215,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')
@ -308,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)

View file

@ -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
@ -17,44 +18,6 @@ DIALOG_HEIGHT = 550
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_entry_box(self, entry, label_text=None):
box = Gtk.HBox()
if label_text:
label = Gtk.Label(label=label_text)
box.pack_start(label, False, False, 20)
box.pack_start(entry, True, True, 20)
return box
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_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)
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
@staticmethod
def build_scrolled_window(widget):
scrolled_window = Gtk.ScrolledWindow()
@ -67,36 +30,101 @@ class GameDialogCommon(object):
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_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):
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")
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")
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)
box.pack_start(label, False, False, 20)
box.pack_start(entry, True, True, 20)
return box
def _get_runner_box(self):
runner_box = Gtk.HBox()
label = Gtk.Label("Runner")
label.set_alignment(0.5, 0.5)
runner_dropdown = self.get_runner_dropdown()
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(runner_dropdown, False, False, 20)
info_box.pack_start(runner_box, False, False, 5)
runner_box.pack_start(self.runner_dropdown, False, False, 20)
runner_box.pack_start(install_runners_btn, False, False, 0)
return runner_box
info_sw = self.build_scrolled_window(info_box)
self.add_notebook_tab(info_sw, "Game info")
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
if self.game:
for runner in runner_liststore:
if self.runner_name == str(runner[1]):
break
runner_index += 1
runner_dropdown.set_active(runner_index)
runner_dropdown.connect("changed", self.on_runner_changed)
cell = Gtk.CellRendererText()
cell.props.ellipsize = Pango.EllipsizeMode.END
runner_dropdown.pack_start(cell, True)
runner_dropdown.add_attribute(cell, 'text', 0)
return runner_dropdown
def build_game_tab(self):
@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 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
try:
@ -112,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)
@ -167,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:
@ -182,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()
@ -204,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()
@ -245,6 +269,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)
@ -255,8 +281,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
@ -275,7 +300,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()

View file

@ -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()

View file

@ -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': (

View file

@ -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
@ -73,28 +73,24 @@ 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.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.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.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
# ---------------------------
@ -124,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()
@ -131,11 +128,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'])
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)
@ -155,7 +153,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)
@ -178,11 +175,11 @@ class InstallerDialog(Gtk.Window):
self.installer_choice_box.pack_start(label, True, True, padding)
return label
self.description_label = _create_label(10,
"<i><b>{}</b></i>".format(self.scripts[0]['description'])
self.description_label = _create_label(
10, "<i><b>{}</b></i>".format(self.scripts[0]['description'])
)
self.notes_label = _create_label(5,
"<i>{}</i>".format(self.scripts[0]['notes'])
self.notes_label = _create_label(
5, "<i>{}</i>".format(self.scripts[0]['notes'])
)
self.widget_box.pack_start(self.installer_choice_box, False, False, 10)
@ -197,7 +194,7 @@ class InstallerDialog(Gtk.Window):
def on_installer_toggled(self, btn, script_index):
description = self.scripts[script_index]['description']
self.description_label.set_markup(
"<i><b>{}</b></i>".format(self.scripts[script_index]['description'])
"<i><b>{}</b></i>".format(description)
)
self.notes_label.set_markup(
"<i>{}</i>".format(self.scripts[script_index]['notes'])
@ -348,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)
@ -405,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()
@ -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)

View file

@ -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)
@ -158,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:
@ -208,7 +211,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')
@ -296,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)
@ -513,13 +515,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 +534,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:

View file

@ -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)

View file

@ -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)

View file

@ -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
@ -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:
@ -90,7 +94,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 +110,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 +121,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 +136,10 @@ class Commands(object):
"containing the following file or folder:\n"
"<i>%s</i>" % requires
)
self.parent.wait_for_user_action(message, self._find_matching_disc,
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'
def _find_matching_disc(self, widget, requires):
@ -263,7 +269,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,21 +280,17 @@ 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()
# 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):
@ -306,13 +308,30 @@ 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)
self.parent.cancel_button.set_sensitive(True)
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'],
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'])

View file

@ -147,7 +147,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
@ -653,3 +653,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)

View file

@ -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,

View file

@ -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 ['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']:
shutil.rmtree(path)

View file

@ -15,7 +15,7 @@ __all__ = (
# Nintendo
"snes9x", "mupen64plus", "dolphin",
# Sony
"pcsxr",
"pcsxr", "ppsspp", "pcsx2",
# Sega
"osmose", "dgen", "reicast",
# Misc legacy systems

View file

@ -29,24 +29,32 @@ 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")
@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:
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,

View file

@ -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):

41
lutris/runners/pcsx2.py Normal file
View file

@ -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}

41
lutris/runners/ppsspp.py Normal file
View file

@ -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}

View file

@ -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
@ -285,7 +285,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
@ -298,3 +299,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)

View file

@ -9,12 +9,13 @@ 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")
def set_regedit(path, key, value='', type_='REG_SZ',
wine_path=None, prefix=None):
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,25 +37,26 @@ 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)
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,
blocking=True)
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, blocking=True)
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')
@ -71,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='', blocking=True):
working_dir=None, winetricks_env='', blocking=False):
"""Execute a Wine command."""
detected_arch = detect_prefix_arch(prefix)
executable = str(executable) if executable else ''
@ -86,33 +88,34 @@ 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:
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]
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)
command = [wine_path, executable] + args.split()
if blocking:
p.communicate()
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,
blocking=False):
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'))
@ -123,11 +126,11 @@ 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, 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 +142,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")
@ -151,10 +155,14 @@ def winecfg(wine_path=None, prefix=None, blocking=True):
def joycpl(wine_path=None, prefix=None):
"""Execute winetricks."""
"""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=False)
wine_path=wine_path, arch=arch, args='joy.cpl')
def eject_disc(wine_path, prefix):
wineexec('eject', prefix=prefix, wine_path=wine_path, args='-a')
def detect_prefix_arch(directory=None):
@ -174,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)
@ -217,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))
@ -561,15 +571,13 @@ class wine(Runner):
def run_winecfg(self, *args):
winecfg(wine_path=self.get_executable(), prefix=self.prefix_path,
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())
@ -581,17 +589,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()
@ -613,6 +623,10 @@ class wine(Runner):
exe = self.get_executable()
if not exe.startswith('/'):
exe = system.find_executable(exe)
if self.wine_arch == 'win64':
wine64 = system.find_executable(exe + '64')
if wine64:
exe = wine64
return system.get_pids_using_file(exe)
def get_xinput_path(self):

View file

@ -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"

View file

@ -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.2"
COPYRIGHT = "(c) 2010-2015 Lutris Gaming Platform"
AUTHORS = ["Mathieu Comandon <strycore@gmail.com>",
"Pascal Reinhard (Xodetaetl) <dev@xod.me"]

View file

@ -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)

View file

@ -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='',

View file

@ -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.items():
@ -70,12 +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)
os.chdir(os.path.expanduser('~'))
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:
@ -101,12 +101,12 @@ class LutrisThread(threading.Thread):
))
os.chmod(file_path, 0o744)
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)
os.chdir(os.path.expanduser('~'))
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:
@ -161,9 +161,13 @@ 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'):
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,

View file

@ -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)

View file

@ -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()

View file

@ -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
@ -223,3 +226,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)

183
po/sv.po Normal file
View file

@ -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 <EMAIL@ADDRESS>, 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 <http://www.gnu.org/licenses/>.\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 <strycore@gmail.com>\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 <http://www.gnu.org/licenses/>.\n"
msgstr ""
#: ../data/ui/AboutLutrisDialog.ui.h:14
msgid "Copyright (C) 2010 Mathieu Comandon <strycore@gmail.com>"
msgstr "Copyright (C) 2010 Mathieu Comandon <strycore@gmail.com"
#: ../data/ui/PreferencesLutrisDialog.ui.h:1
msgid "gtk-cancel"
msgstr "gtk-cancel"
#: ../data/ui/PreferencesLutrisDialog.ui.h:2
msgid "gtk-ok"
msgstr "gtk-ok"

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View file

@ -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)
)