mirror of
https://github.com/lutris/lutris
synced 2024-09-15 22:09:55 +00:00
Merge from next
This commit is contained in:
commit
c8acbd805d
5
AUTHORS
5
AUTHORS
|
@ -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
31
INSTALL
|
@ -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.
|
11
INSTALL.rst
11
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
|
||||
-----------
|
||||
|
||||
|
@ -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
|
||||
|
|
6
Makefile
6
Makefile
|
@ -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
|
||||
|
|
47
bin/lutris
47
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
|
||||
|
@ -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
23
debian/changelog
vendored
|
@ -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
4
debian/control
vendored
|
@ -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
4
debian/copyright
vendored
|
@ -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
1
debian/pyversions
vendored
|
@ -1 +0,0 @@
|
|||
2.7
|
2
debian/rules
vendored
2
debian/rules
vendored
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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': (
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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'])
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
16
lutris/migrations/update_runners.py
Normal file
16
lutris/migrations/update_runners.py
Normal 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)
|
|
@ -15,7 +15,7 @@ __all__ = (
|
|||
# Nintendo
|
||||
"snes9x", "mupen64plus", "dolphin",
|
||||
# Sony
|
||||
"pcsxr",
|
||||
"pcsxr", "ppsspp", "pcsx2",
|
||||
# Sega
|
||||
"osmose", "dgen", "reicast",
|
||||
# Misc legacy systems
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
41
lutris/runners/pcsx2.py
Normal 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
41
lutris/runners/ppsspp.py
Normal 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}
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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='',
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
183
po/sv.po
Normal 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"
|
BIN
share/lutris/media/runner_icons/pcsx2.png
Normal file
BIN
share/lutris/media/runner_icons/pcsx2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.2 KiB |
BIN
share/lutris/media/runner_icons/ppsspp.png
Normal file
BIN
share/lutris/media/runner_icons/ppsspp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.3 KiB |
|
@ -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)
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue