mirror of
https://github.com/lutris/lutris
synced 2024-11-05 18:10:49 +00:00
Better steam data handling + client login
This commit is contained in:
parent
397e7deafb
commit
c8accf0293
9 changed files with 162 additions and 49 deletions
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<!-- interface-requires gtk+ 3.0 -->
|
||||
<object class="GtkDialog" id="dialog1">
|
||||
<object class="GtkDialog" id="lutris-login">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="border_width">5</property>
|
||||
<property name="type_hint">dialog</property>
|
||||
|
@ -15,11 +15,12 @@
|
|||
<property name="can_focus">False</property>
|
||||
<property name="layout_style">end</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="button1">
|
||||
<property name="label" translatable="yes">button</property>
|
||||
<object class="GtkButton" id="cancel_button">
|
||||
<property name="label">gtk-cancel</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
|
@ -28,11 +29,12 @@
|
|||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="button2">
|
||||
<property name="label" translatable="yes">button</property>
|
||||
<object class="GtkButton" id="connect_button">
|
||||
<property name="label">gtk-connect</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
|
@ -98,11 +100,14 @@
|
|||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEntry" id="entry1">
|
||||
<object class="GtkEntry" id="username_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="has_focus">True</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="invisible_char">•</property>
|
||||
<property name="width_chars">32</property>
|
||||
<property name="shadow_type">none</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
|
@ -112,10 +117,12 @@
|
|||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEntry" id="entry2">
|
||||
<object class="GtkEntry" id="password_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="max_length">26</property>
|
||||
<property name="visibility">False</property>
|
||||
<property name="invisible_char">•</property>
|
||||
<property name="invisible_char_set">True</property>
|
||||
</object>
|
||||
|
@ -148,8 +155,8 @@
|
|||
</object>
|
||||
</child>
|
||||
<action-widgets>
|
||||
<action-widget response="0">button1</action-widget>
|
||||
<action-widget response="0">button2</action-widget>
|
||||
<action-widget response="0">cancel_button</action-widget>
|
||||
<action-widget response="4">connect_button</action-widget>
|
||||
</action-widgets>
|
||||
</object>
|
||||
</interface>
|
||||
|
|
20
lutris/api.py
Normal file
20
lutris/api.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
import os
|
||||
import json
|
||||
import urllib
|
||||
import urllib2
|
||||
from lutris import settings
|
||||
|
||||
|
||||
def connect(username, password):
|
||||
credentials = urllib.urlencode({'username': username,
|
||||
'password': password})
|
||||
login_url = settings.SITE_URL + "user/auth/"
|
||||
request = urllib2.urlopen(login_url, credentials, 3)
|
||||
response = json.loads(request.read())
|
||||
if 'token' in response:
|
||||
token = response['token']
|
||||
token_file_path = os.path.join(settings.CACHE_DIR, 'auth-token')
|
||||
with open(token_file_path, "w") as token_file:
|
||||
token_file.write(token)
|
||||
return response['token']
|
||||
return False
|
|
@ -141,7 +141,7 @@ class LutrisGame(object):
|
|||
return False
|
||||
logger.debug("get ready for %s " % self.get_real_name())
|
||||
gameplay_info = self.runner.play()
|
||||
|
||||
logger.debug("gameplay_info: %s" % gameplay_info)
|
||||
if isinstance(gameplay_info, dict):
|
||||
if 'error' in gameplay_info:
|
||||
show_error_message(gameplay_info)
|
||||
|
|
|
@ -7,11 +7,13 @@ from lutris.gui.widgets import DownloadProgressBox
|
|||
|
||||
from lutris import settings
|
||||
from lutris import pga
|
||||
from lutris import api
|
||||
|
||||
|
||||
class GtkBuilderDialog(object):
|
||||
class GtkBuilderDialog(GObject.Object):
|
||||
|
||||
def __init__(self):
|
||||
super(GtkBuilderDialog, self).__init__()
|
||||
ui_filename = os.path.join(settings.get_data_path(), 'ui',
|
||||
self.glade_file)
|
||||
if not os.path.exists(ui_filename):
|
||||
|
@ -36,6 +38,7 @@ class NoticeDialog(Gtk.MessageDialog):
|
|||
""" Displays a message to the user. """
|
||||
def __init__(self, message):
|
||||
super(NoticeDialog, self).__init__(buttons=Gtk.ButtonsType.OK)
|
||||
from lutris import api
|
||||
self.set_markup(message)
|
||||
self.run()
|
||||
self.destroy()
|
||||
|
@ -130,7 +133,9 @@ class PgaSourceDialog(GtkBuilderDialog):
|
|||
# GtkBuilder Objects
|
||||
self.sources_selection = self.builder.get_object("sources_selection")
|
||||
self.sources_treeview = self.builder.get_object("sources_treeview")
|
||||
self.remove_source_button = self.builder.get_object("remove_source_button")
|
||||
self.remove_source_button = self.builder.get_object(
|
||||
"remove_source_button"
|
||||
)
|
||||
|
||||
# Treeview setup
|
||||
self.sources_liststore = Gtk.ListStore(str)
|
||||
|
@ -180,3 +185,32 @@ class PgaSourceDialog(GtkBuilderDialog):
|
|||
""" Set sentivity of remove source button """
|
||||
(model, treeiter) = self.sources_selection.get_selected()
|
||||
self.remove_source_button.set_sensitive(treeiter is not None)
|
||||
|
||||
|
||||
class ClientLoginDialog(GtkBuilderDialog):
|
||||
glade_file = 'dialog-lutris-login.ui'
|
||||
dialog_object = 'lutris-login'
|
||||
__gsignals__ = {
|
||||
"connected": (GObject.SIGNAL_RUN_FIRST, None, (str, )),
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
super(ClientLoginDialog, self).__init__()
|
||||
|
||||
cancel_button = self.builder.get_object('cancel_button')
|
||||
cancel_button.connect('clicked', self.on_cancel)
|
||||
connect_button = self.builder.get_object('connect_button')
|
||||
connect_button.connect('clicked', self.on_connect)
|
||||
|
||||
def on_cancel(self, widget):
|
||||
self.dialog.destroy()
|
||||
|
||||
def on_connect(self, widget):
|
||||
username = self.builder.get_object('username_entry').get_text()
|
||||
password = self.builder.get_object('password_entry').get_text()
|
||||
token = api.connect(username, password)
|
||||
if not token:
|
||||
NoticeDialog("Login failed")
|
||||
else:
|
||||
self.emit('connected', token)
|
||||
self.dialog.destroy()
|
||||
|
|
|
@ -152,7 +152,11 @@ class LutrisWindow(object):
|
|||
# Callbacks
|
||||
def on_connect(self, *args):
|
||||
"""Callback when a user connects to his account"""
|
||||
dialogs.NoticeDialog("This functionnality is not yet implemented.")
|
||||
login_dialog = dialogs.ClientLoginDialog()
|
||||
login_dialog.connect('connected', self.on_connect_success)
|
||||
|
||||
def on_connect_success(self, dialog, token):
|
||||
self.status_label.set_text("Connected")
|
||||
|
||||
def on_destroy(self, *args):
|
||||
"""Signal for window close"""
|
||||
|
|
|
@ -123,9 +123,15 @@ class ScriptInterpreter(object):
|
|||
else:
|
||||
self._iter_commands()
|
||||
|
||||
def install_steam_game(self, appid):
|
||||
def get_steam_game_path(self, appid, file_id, steam_rel_path):
|
||||
steam_runner = steam()
|
||||
steam_runner.install_game(appid)
|
||||
data_path = steam_runner.get_game_data_path(appid)
|
||||
if not data_path:
|
||||
steam_runner.install_game(appid)
|
||||
data_path = steam_runner.get_game_data_path(appid)
|
||||
logger.debug("got data path: %s" % data_path)
|
||||
self.game_files[file_id] = os.path.join(data_path, steam_rel_path)
|
||||
self.iter_game_files()
|
||||
|
||||
def _download_file(self, game_file):
|
||||
"""Download a file referenced in the installer script
|
||||
|
@ -137,14 +143,6 @@ class ScriptInterpreter(object):
|
|||
- filename : force destination filename when url is present or path
|
||||
of local file
|
||||
"""
|
||||
def on_steam_installed(appid):
|
||||
game_path = steam_runner.get_game_data_path(appid)
|
||||
if not game_path:
|
||||
steam_runner.install_game(appid)
|
||||
game_path = steam_runner.get_game_data_path(appid)
|
||||
self.game_files[file_id] = os.path.join(game_path, parts[2])
|
||||
self.iter_game_files()
|
||||
|
||||
# Setup file_id, file_uri and local filename
|
||||
file_id = game_file.keys()[0]
|
||||
if isinstance(game_file[file_id], dict):
|
||||
|
@ -158,6 +156,7 @@ class ScriptInterpreter(object):
|
|||
elif file_uri.startswith("$WINESTEAM"):
|
||||
parts = file_uri.split(":", 2)
|
||||
appid = parts[1]
|
||||
steam_rel_path = parts[2]
|
||||
steam_runner = steam()
|
||||
if not steam_runner.is_installed():
|
||||
steam_installer_path = os.path.join(
|
||||
|
@ -170,7 +169,8 @@ class ScriptInterpreter(object):
|
|||
appid
|
||||
)
|
||||
else:
|
||||
self.install_steam_game(appid)
|
||||
logger.debug("Steam already installed, installing game")
|
||||
self.get_steam_game_path(appid, file_id, steam_rel_path)
|
||||
return
|
||||
logger.debug("Fetching [%s]: %s" % (file_id, file_uri))
|
||||
|
||||
|
@ -225,13 +225,21 @@ class ScriptInterpreter(object):
|
|||
"""Write the game configuration as a Lutris launcher."""
|
||||
config_filename = join(settings.CONFIG_DIR,
|
||||
"games/%s.yml" % self.game_slug)
|
||||
runner_name = self.script['runner']
|
||||
config_data = {
|
||||
'game': {},
|
||||
'realname': self.script['name'],
|
||||
'runner': self.script['runner']
|
||||
'runner': runner_name
|
||||
}
|
||||
if 'system' in self.script:
|
||||
config_data['system'] = self.script['system']
|
||||
if runner_name in self.script:
|
||||
config_data[runner_name] = self.script[runner_name]
|
||||
if 'game' in self.script:
|
||||
for key in self.script['game']:
|
||||
value = self._substitute(self.script['game'][key])
|
||||
config_data['game'][key] = value
|
||||
|
||||
is_64bit = platform.machine() == "x86_64"
|
||||
exe = 'exe64' if 'exe64' in self.script and is_64bit else 'exe'
|
||||
for launcher in [exe, 'iso', 'rom', 'disk', 'main_file']:
|
||||
|
@ -257,6 +265,7 @@ class ScriptInterpreter(object):
|
|||
config_data['game'][key] = game_resource
|
||||
|
||||
yaml_config = yaml.safe_dump(config_data, default_flow_style=False)
|
||||
logger.debug(yaml_config)
|
||||
with open(config_filename, "w") as config_file:
|
||||
config_file.write(yaml_config)
|
||||
|
||||
|
@ -330,8 +339,20 @@ class ScriptInterpreter(object):
|
|||
raise ScriptingError("MD5 checksum mismatch", data)
|
||||
|
||||
def mergecopy(self, params):
|
||||
logger.debug("Merging %s" % str(params))
|
||||
src, dst = self._get_move_paths(params)
|
||||
raise ScriptingError("%s, %s" % (str(src), str(dst)))
|
||||
if not os.path.exists(dst):
|
||||
raise ValueError(dst)
|
||||
for (dirpath, dirnames, filenames) in os.walk(src):
|
||||
src_relpath = dirpath[len(src) + 1:]
|
||||
dst_abspath = os.path.join(dst, src_relpath)
|
||||
for dirname in dirnames:
|
||||
new_dir = os.path.join(dst_abspath, dirname)
|
||||
logger.debug("creating dir: %s" % new_dir)
|
||||
os.mkdir(new_dir)
|
||||
for filename in filenames:
|
||||
shutil.copy(os.path.join(dirpath, filename),
|
||||
os.path.join(dst_abspath, filename))
|
||||
|
||||
def move(self, params):
|
||||
""" Move a file or directory """
|
||||
|
@ -353,9 +374,11 @@ class ScriptInterpreter(object):
|
|||
|
||||
def extract(self, data):
|
||||
""" Extracts a file, guessing the compression method """
|
||||
logger.debug("extracting file %s" % str(data))
|
||||
filename = self.game_files.get(data['file'])
|
||||
if not filename:
|
||||
raise ScriptingError("No file for '%s' in game files %s " % (data, self.game_files))
|
||||
raise ScriptingError("No file for '%s' in game files %s "
|
||||
% (data, self.game_files))
|
||||
return False
|
||||
if not os.path.exists(filename):
|
||||
logger.error("%s does not exists" % filename)
|
||||
|
@ -373,6 +396,8 @@ class ScriptInterpreter(object):
|
|||
else:
|
||||
logger.error("unrecognised file extension %s" % extension)
|
||||
return False
|
||||
import time
|
||||
time.sleep(1)
|
||||
|
||||
def runner_task(self, data):
|
||||
""" This action triggers a task within a runner.
|
||||
|
@ -461,6 +486,7 @@ class InstallerDialog(Gtk.Dialog):
|
|||
self.interpreter.target_path = text_entry.get_text()
|
||||
|
||||
def on_install_clicked(self, button):
|
||||
button.set_sensitive(False)
|
||||
self.interpreter._start_install()
|
||||
|
||||
def ask_user_for_file(self):
|
||||
|
@ -491,6 +517,7 @@ class InstallerDialog(Gtk.Dialog):
|
|||
self.interpreter.iter_game_files()
|
||||
|
||||
def on_steam_downloaded(self, widget, data, steam_info):
|
||||
logger.debug("Steam downloaded")
|
||||
steam_runner = steam()
|
||||
steam_runner.install(steam_info[0])
|
||||
steam_runner.install_game(steam_info[1])
|
||||
|
|
|
@ -21,6 +21,8 @@
|
|||
|
||||
""" Runner for MS Dos games """
|
||||
|
||||
import os
|
||||
from lutris.util.log import logger
|
||||
from lutris.runners.runner import Runner
|
||||
|
||||
|
||||
|
@ -34,18 +36,34 @@ class dosbox(Runner):
|
|||
self.executable = "dosbox"
|
||||
self.platform = "MS DOS"
|
||||
self.description = "DOS Emulator"
|
||||
self.game_options = [{
|
||||
"option": "main_file",
|
||||
"type": "file_chooser",
|
||||
"label": "EXE File"
|
||||
}]
|
||||
self.game_options = [
|
||||
{
|
||||
"option": "main_file",
|
||||
"type": "file_chooser",
|
||||
"label": "EXE File"
|
||||
},
|
||||
{
|
||||
"option": "config_file",
|
||||
"type": "file_chooser",
|
||||
"label": "Configuration file"
|
||||
}
|
||||
]
|
||||
self.runner_options = []
|
||||
if settings:
|
||||
self.exe = settings["game"]["main_file"]
|
||||
self.settings = settings
|
||||
|
||||
def play(self):
|
||||
""" Run the game """
|
||||
if not self.is_installed():
|
||||
return {'error': 'RUNNER_NOT_INSTALLED'}
|
||||
command = [self.executable, "\"%s\"" % self.exe]
|
||||
return command
|
||||
logger.debug(self.settings)
|
||||
self.exe = self.settings["game"]["main_file"]
|
||||
|
||||
if not os.path.exists(self.exe):
|
||||
return {'error': "FILE_NOT_FOUND", 'file': self.exe}
|
||||
if self.exe.endswith(".conf"):
|
||||
exe = ["-conf", self.exe]
|
||||
else:
|
||||
exe = [self.exe]
|
||||
if "config_file" in self.settings["game"]:
|
||||
params = ["-conf", self.settings["game"]["config_file"]]
|
||||
else:
|
||||
params = []
|
||||
return [self.executable] + params + exe
|
||||
|
|
|
@ -24,8 +24,8 @@
|
|||
import os
|
||||
import stat
|
||||
import os.path
|
||||
import logging
|
||||
|
||||
from lutris.util.log import logger
|
||||
from lutris.runners.runner import Runner
|
||||
|
||||
|
||||
|
@ -61,7 +61,7 @@ class linux(Runner):
|
|||
|
||||
#Check if script is executable and make it executable if not
|
||||
if not os.access(installer_path, os.X_OK):
|
||||
logging.debug("%s is not executable, setting it executable")
|
||||
logger.debug("%s is not executable, setting it executable")
|
||||
os.chmod(installer_path,
|
||||
stat.S_IXUSR | stat.S_IRUSR | stat.S_IWUSR)
|
||||
|
||||
|
@ -73,6 +73,7 @@ class linux(Runner):
|
|||
|
||||
def play(self):
|
||||
"""Run native game."""
|
||||
logger.debug("Launching Linux game")
|
||||
game_config = self.config.get('game')
|
||||
if not game_config:
|
||||
return {'error': 'INVALID_CONFIG'}
|
||||
|
@ -89,4 +90,5 @@ class linux(Runner):
|
|||
command.append("./%s" % os.path.basename(executable))
|
||||
for arg in args.split():
|
||||
command.append(arg)
|
||||
logger.debug("Linux runner args: %s" % command)
|
||||
return {'command': command}
|
||||
|
|
|
@ -23,12 +23,12 @@
|
|||
import os
|
||||
import subprocess
|
||||
|
||||
from lutris.gui.dialogs import QuestionDialog, DirectoryDialog
|
||||
from lutris.gui.dialogs import DirectoryDialog
|
||||
from lutris.runners.wine import wine
|
||||
from lutris.util.log import logger
|
||||
from lutris.config import LutrisConfig
|
||||
|
||||
|
||||
|
||||
def get_name(steam_file):
|
||||
"""Get game name from some weird steam file"""
|
||||
data = steam_file.read(1000)
|
||||
|
@ -74,7 +74,8 @@ def vdf_parse(steam_config_file, config):
|
|||
# pylint: disable=C0103
|
||||
class steam(wine):
|
||||
|
||||
installer_url = "http://cdn.steampowered.com/download/SteamInstall.msi"
|
||||
#installer_url = "http://cdn.steampowered.com/download/SteamInstall.msi"
|
||||
installer_url = "http://lutris.net/files/runners/SteamInstall.msi"
|
||||
|
||||
"""Runs Steam games with Wine"""
|
||||
def __init__(self, settings=None):
|
||||
|
@ -134,10 +135,10 @@ class steam(wine):
|
|||
def install_game(self, appid):
|
||||
#print "Q2", apps["2320"]
|
||||
#print "Shadow", apps["238070"]
|
||||
subprocess.Popen(
|
||||
["wine", '%s' % os.path.join(self.game_path, self.executable),
|
||||
"-no-drwite", "steam://install/%s" % appid]
|
||||
)
|
||||
args = ["wine", '%s' % os.path.join(self.game_path, self.executable),
|
||||
"-no-dwrite", "steam://install/%s" % appid]
|
||||
logger.debug(args)
|
||||
subprocess.Popen(args)
|
||||
|
||||
def play(self):
|
||||
appid = self.settings['game']['appid']
|
||||
|
|
Loading…
Reference in a new issue