Properly use Gtk.Application, kill custom DBus service

This commit is contained in:
Patrick Griffis 2016-12-04 07:54:38 -05:00
parent 483a669f36
commit d48d2410ff
9 changed files with 212 additions and 222 deletions

View file

@ -13,26 +13,8 @@
# You should have received a copy of the GNU General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
import dbus
import dbus.service
import os
import sys
import logging
import optparse
import signal
import time
import json
# pylint: disable=E0611
import gi
gi.require_version('Gdk', '3.0')
gi.require_version('Gtk', '3.0')
from dbus.mainloop.glib import DBusGMainLoop
DBusGMainLoop(set_as_default=True)
from gi.repository import Gdk, Gtk, GObject, GLib
from os.path import realpath, dirname, normpath
LAUNCH_PATH = dirname(realpath(__file__))
@ -40,132 +22,11 @@ if LAUNCH_PATH != "/usr/bin":
SOURCE_PATH = normpath(os.path.join(LAUNCH_PATH, '..'))
sys.path.insert(0, SOURCE_PATH)
from lutris.migrations import migrate
from lutris.gui.application import Application
from lutris import pga
from lutris.runtime import RuntimeUpdater
from lutris.config import check_config # , register_handler
from lutris.util.log import logger
from lutris.game import Game
from lutris.gui.installgamedialog import InstallerDialog
from lutris.settings import VERSION
from lutris.util import service
from lutris.util.steam import get_steamapps_paths, AppManifest, get_appmanifests
app = Application()
sys.exit(app.run(sys.argv))
# Support for command line options.
parser = optparse.OptionParser(version="%prog " + VERSION)
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", 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("--list-steam-games", action="store_true",
help="List available Steam games")
parser.add_option("--list-steam-folders", action="store_true",
help="List all known Steam library folders")
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()
if options.debug:
logger.setLevel(logging.DEBUG)
if options.list_games:
game_list = pga.get_games()
if options.list_installed:
game_list = [game for game in game_list if game['installed']]
if options.json:
games = []
for game in game_list:
games.append({
'id': game['id'],
'slug': game['slug'],
'name': game['name'],
'runner': game['runner'],
'directory': game['directory']
})
print(json.dumps(games, indent=2))
else:
for game in game_list:
print("{:4} | {:<40} | {:<40} | {:<15} | {:<64}".format(
game['id'],
game['name'][:40],
game['slug'][:40],
game['runner'] or '-',
game['directory'] or '-'
))
exit()
if options.list_steam_games:
steamapps_paths = get_steamapps_paths()
for platform in ('linux', 'windows'):
for path in steamapps_paths[platform]:
appmanifest_files = get_appmanifests(path)
for appmanifest_file in appmanifest_files:
appmanifest = AppManifest(os.path.join(path, appmanifest_file))
print(" {:8} | {:<60} | {:10} | {}".format(
appmanifest.steamid,
appmanifest.name or '-',
platform,
", ".join(appmanifest.states)
))
exit()
if options.list_steam_folders:
steamapps_paths = get_steamapps_paths()
for platform in ('linux', 'windows'):
for path in steamapps_paths[platform]:
print(path)
exit()
check_config(force_wipe=False)
installer = False
game = None
signal.signal(signal.SIGINT, signal.SIG_DFL)
# D-Bus init
bus = dbus.SessionBus()
lutris = service.get_service(bus)
# Make sure the existing process is not frozen
if type(lutris) is dbus.Interface:
try:
lutris.is_running()
except dbus.exceptions.DBusException as e:
logger.error(e)
try:
# Get existing process' PID
dbus_proxy = bus.get_object('org.freedesktop.DBus',
'/org/freedesktop/DBus/Bus')
dbus_interface = dbus.Interface(dbus_proxy, 'org.freedesktop.DBus')
pid = dbus_interface.GetConnectionUnixProcessID(lutris.bus_name)
os.kill(pid, signal.SIGKILL)
except (OSError, dbus.exceptions.DBusException) as ex:
logger.error("Lutris was non responsive, we tried, we failed and failed "
"some more. Please now try to restart Lutris")
logger.error(ex)
exit()
else:
time.sleep(1) # Wait for bus name to be available again
lutris = service.LutrisService(bus, '/', service.DBUS_INTERFACE)
migrate()
game_slug = ""
for arg in args:
if arg.startswith('lutris:'):
game_slug = arg[7:]
break
installer = options.installer_file
if game_slug or installer:
if not game_slug and not os.path.isfile(installer):
@ -200,10 +61,5 @@ if game_slug or installer:
else:
runtime_updater.update()
InstallerDialog(installer or game_slug)
GObject.threads_init()
Gtk.main()
exit()
lutris.run(int(time.time()))
if lutris.is_running():
Gdk.notify_startup_complete()

1
debian/control vendored
View file

@ -20,7 +20,6 @@ Architecture: any
Depends: ${misc:Depends},
${python3:Depends},
python3-yaml,
python3-dbus,
python3-gi,
python3-pyinotify,
gir1.2-gtk-3.0,

View file

@ -1 +0,0 @@
dbus_python python3-dbus

View file

@ -21,11 +21,11 @@ BuildRequires: python3-devel
%if 0%{?fedora_version}
BuildRequires: python3-gobject, python3-wheel, python3-setuptools, python3-gobject
Requires: python3-gobject, python3-PyYAML, python3-dbus
Requires: python3-gobject, python3-PyYAML
%endif
%if 0%{?rhel_version} || 0%{?centos_version}
BuildRequires: python3-gobject
Requires: python3-gobject, python3-PyYAML, python3-dbus
Requires: python3-gobject, python3-PyYAML
%endif
%if 0%{?suse_version}
BuildRequires: python3-gobject
@ -34,7 +34,7 @@ BuildRequires: update-desktop-files
BuildRequires: hicolor-icon-theme
BuildRequires: polkit
BuildRequires: python3-setuptools
Requires: python3-gobject, python3-PyYAML, dbus-1-python3
Requires: python3-gobject, python3-PyYAML
%endif
%if 0%{?fedora_version} || 0%{?suse_version}
BuildRequires: fdupes

203
lutris/gui/application.py Normal file
View file

@ -0,0 +1,203 @@
# application.py
#
# Copyright (C) 2016 Patrick Griffis <tingping@tingping.se>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import sys
import logging
import signal
import json
from gettext import gettext as _
# pylint: disable=E0611
import gi
gi.require_version('Gdk', '3.0')
gi.require_version('Gtk', '3.0')
from gi.repository import GLib, Gio, Gtk
from lutris.migrations import migrate
from lutris import pga
from lutris.runtime import RuntimeUpdater
from lutris.config import check_config # , register_handler
from lutris.util.log import logger
from lutris.game import Game
from lutris.gui.installgamedialog import InstallerDialog
from lutris.settings import VERSION
from lutris.util.steam import get_steamapps_paths, AppManifest, get_appmanifests
from .lutriswindow import LutrisWindow
class Application(Gtk.Application):
def __init__(self):
Gtk.Application.__init__(self, application_id='net.lutris.Lutris',
flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE)
GLib.set_application_name(_('Lutris'))
self.window = None
self.add_main_option('debug', ord('d'), GLib.OptionFlags.NONE, GLib.OptionArg.NONE,
_('Show debug messages'), None)
self.add_main_option('install', ord('i'), GLib.OptionFlags.NONE, GLib.OptionArg.STRING,
_('Install a game from a yml file'), None)
self.add_main_option('list-games', ord('l'), GLib.OptionFlags.NONE, GLib.OptionArg.NONE,
_('List all games in database'), None)
self.add_main_option('installed', ord('o'), GLib.OptionFlags.NONE, GLib.OptionArg.NONE,
_('Only list installed games'), None)
self.add_main_option('list-steam-games', ord('s'), GLib.OptionFlags.NONE, GLib.OptionArg.NONE,
_('List available Steam games'), None)
self.add_main_option('list-steam-folders', 0, GLib.OptionFlags.NONE, GLib.OptionArg.NONE,
_('List all known Steam library folders'), None)
self.add_main_option('json', ord('j'), GLib.OptionFlags.NONE, GLib.OptionArg.NONE,
_('Display the list of games in JSON format'), None)
self.add_main_option('reinstall', 0, GLib.OptionFlags.NONE, GLib.OptionArg.NONE,
_('Reinstall game'), None)
self.add_main_option(GLib.OPTION_REMAINING, 0, GLib.OptionFlags.NONE, GLib.OptionArg.STRING_ARRAY,
_('uri to open'), 'URI')
def do_startup(self):
Gtk.Application.do_startup(self)
signal.signal(signal.SIGINT, signal.SIG_DFL)
def do_activate(self):
if not self.window:
self.window = LutrisWindow()
self.add_window(self.window.window)
self.window.window.present()
@staticmethod
def _print(command_line, string):
# Workaround broken pygobject bindings
command_line.do_print_literal(command_line, string + '\n')
def do_command_line(self, command_line):
options = command_line.get_options_dict()
if options.contains('debug'):
logger.setLevel(logging.DEBUG)
if options.contains('list-games'):
game_list = pga.get_games()
if options.contains('installed'):
game_list = [game for game in game_list if game['installed']]
if options.contains('json'):
games = []
for game in game_list:
games.append({
'id': game['id'],
'slug': game['slug'],
'name': game['name'],
'runner': game['runner'],
'directory': game['directory']
})
self._print(command_line, json.dumps(games, indent=2))
else:
for game in game_list:
self._print(command_line, "{:4} | {:<40} | {:<40} | {:<15} | {:<64}".format(
game['id'],
game['name'][:40],
game['slug'][:40],
game['runner'] or '-',
game['directory'] or '-'
))
return 0
if options.contains('list-steam-games'):
steamapps_paths = get_steamapps_paths()
for platform in ('linux', 'windows'):
for path in steamapps_paths[platform]:
appmanifest_files = get_appmanifests(path)
for appmanifest_file in appmanifest_files:
appmanifest = AppManifest(os.path.join(path, appmanifest_file))
self._print(command_line, " {:8} | {:<60} | {:10} | {}".format(
appmanifest.steamid,
appmanifest.name or '-',
platform,
", ".join(appmanifest.states)
))
return 0
if options.contains('list-steam-folders'):
steamapps_paths = get_steamapps_paths()
for platform in ('linux', 'windows'):
for path in steamapps_paths[platform]:
self._print(command_line, path)
return 0
check_config(force_wipe=False)
migrate()
game = None
game_slug = ''
uri = options.lookup_value(GLib.OPTION_REMAINING)
if uri and len(uri):
uri = uri[0] # TODO: Support multiple
if not uri.startswith('lutris:'):
self._print(command_line, '%s is not a valid URI' %uri)
return 1
game_slug = uri[7:]
if game_slug or options.contains('install'):
installer = options.lookup_value('install')
if not game_slug and not os.path.isfile(installer):
self._print(command_line, "No such file: %s" % installer)
return 1
db_game = None
if game_slug:
db_game = (pga.get_game_by_field(game_slug, 'id')
or pga.get_game_by_field(game_slug, 'slug')
or pga.get_game_by_field(game_slug, 'installer_slug'))
if db_game and db_game['installed'] and not options.contains('reinstall'):
self._print(command_line, "Launching %s", db_game['name'])
if self.window:
self.run_game(db_game['id'])
else:
lutris_game = Game(db_game['id'])
# FIXME: This is awful
lutris_game.exit_main_loop = True
lutris_game.play()
try:
GLib.MainLoop().run()
except KeyboardInterrupt:
lutris_game.stop()
return 0
else:
self._print(command_line, "Installing %s", game_slug)
if self.window:
self.install_game(installer or game_slug)
else:
runtime_updater = RuntimeUpdater()
runtime_updater.update()
# FIXME: This should be a Gtk.Dialog child of LutrisWindow
dialog = InstallerDialog(installer or game_slug)
self.add_window(dialog)
return 0
self.activate()
return 0
def do_shutdown(self):
Gtk.Application.do_shutdown(self)
if self.window:
self.window.window.destroy()
def install_game(self, game_ref):
self.window.on_install_clicked(game_ref=game_ref)
def run_game(self, game_id):
self.window.on_game_run(game_id=game_id)

View file

@ -33,21 +33,16 @@ from lutris.gui.gameviews import (
)
class LutrisWindow(Gtk.Application):
class LutrisWindow:
"""Handler class for main window signals."""
def __init__(self, service=None):
def __init__(self):
Gtk.Application.__init__(
self, application_id="net.lutris.main",
flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE
)
ui_filename = os.path.join(
datapath.get(), 'ui', 'lutris-window.ui'
)
if not os.path.exists(ui_filename):
raise IOError('File %s not found' % ui_filename)
self.service = service
self.runtime_updater = RuntimeUpdater()
self.running_game = None
self.threads_stoppers = []
@ -468,16 +463,11 @@ class LutrisWindow(Gtk.Application):
logger.info("%s is still running, stopping it", self.running_game.name)
self.running_game.stop()
if self.service:
self.service.stop()
# Save settings
width, height = self.window_size
settings.write_setting('width', width)
settings.write_setting('height', height)
Gtk.main_quit(*args)
def on_runners_activate(self, _widget, _data=None):
"""Callback when manage runners is activated."""
RunnersDialog()

View file

@ -1,56 +0,0 @@
import dbus
from gi.repository import Gtk, GObject
from lutris.gui.lutriswindow import LutrisWindow
from lutris.util.log import logger
DBUS_INTERFACE = 'net.lutris.main'
class LutrisService(dbus.service.Object):
"""Main D-Bus Lutris service."""
def __init__(self, bus_name, object_path, name):
dbus.service.Object.__init__(self, bus_name, object_path, name)
self.running = False
self.lutris_window = None
def stop(self):
""" stop the dbus controller and remove from the bus """
self.remove_from_connection()
@dbus.service.method(DBUS_INTERFACE, out_signature='b')
def is_running(self):
return self.running
@dbus.service.method(DBUS_INTERFACE, in_signature='i')
def run(self, timestamp):
if self.is_running():
self.lutris_window.window.present_with_time(timestamp)
else:
logger.info("Welcome to Lutris")
self.running = True
self.lutris_window = LutrisWindow(service=self)
GObject.threads_init()
Gtk.main()
self.running = False
@dbus.service.method(DBUS_INTERFACE, in_signature='s')
def install_game(self, game_ref):
self.lutris_window.on_install_clicked(game_ref=game_ref)
@dbus.service.method(DBUS_INTERFACE, in_signature='i')
def run_game(self, game_id):
self.lutris_window.on_game_run(game_id=game_id)
def get_bus():
return dbus.SessionBus()
def get_service(bus):
request = bus.request_name(DBUS_INTERFACE, dbus.bus.NAME_FLAG_DO_NOT_QUEUE)
if request != dbus.bus.REQUEST_NAME_REPLY_EXISTS:
service = LutrisService(bus, '/', DBUS_INTERFACE)
else:
proxy = bus.get_object(DBUS_INTERFACE, "/")
service = dbus.Interface(proxy, DBUS_INTERFACE)
return service

View file

@ -32,7 +32,6 @@ setup(
install_requires=[
'PyYAML',
'PyGObject',
'dbus-python',
'pyinotify',
'evdev'
],

View file

@ -20,7 +20,7 @@
<property name="pixel_size">16</property>
<property name="icon_name">view-list-symbolic</property>
</object>
<object class="GtkWindow" id="window">
<object class="GtkApplicationWindow" id="window">
<property name="can_focus">False</property>
<property name="title" translatable="yes">Lutris</property>
<property name="window_position">center</property>