Beispiel #1
0
    def execute(self, data):
        """Run an executable file."""
        args = []
        if isinstance(data, dict):
            self._check_required_params("file", data, "execute")
            file_ref = data["file"]
            args_string = data.get("args", "")
            for arg in shlex.split(args_string):
                args.append(self._substitute(arg))
        else:
            file_ref = data
        # Determine whether 'file' value is a file id or a path
        exec_path = self._get_file(file_ref) or self._substitute(file_ref)
        if not exec_path:
            raise ScriptingError("Unable to find file %s" % file_ref, file_ref)
        if not os.path.exists(exec_path):
            raise ScriptingError("Unable to find required executable", exec_path)
        self.chmodx(exec_path)

        terminal = data.get("terminal")
        if terminal:
            terminal = system.get_default_terminal()

        command = [exec_path] + args
        logger.debug("Executing %s" % command)
        thread = LutrisThread(command, env=runtime.get_env(), term=terminal, cwd=self.target_path)
        self.abort_current_task = thread.killall
        thread.run()
        self.abort_current_task = None
Beispiel #2
0
 def xboxdrv(self, config):
     command = ("pkexec xboxdrv --daemon --detach-kernel-driver "
                "--dbus session --silent %s"
                % config)
     logger.debug("xboxdrv command: %s", command)
     thread = LutrisThread(command)
     thread.start()
Beispiel #3
0
    def execute(self, data):
        """Run an executable file"""
        args = []
        if isinstance(data, dict):
            self._check_required_params('file', data, 'execute')
            file_ref = data['file']
            args_string = data.get('args', '')
            for arg in shlex.split(args_string):
                args.append(self._substitute(arg))
        else:
            file_ref = data
        # Determine whether 'file' value is a file id or a path
        exec_path = self._get_file(file_ref) or self._substitute(file_ref)
        if not exec_path:
            raise ScriptingError("Unable to find file %s" % file_ref,
                                 file_ref)
        if not os.path.exists(exec_path):
            raise ScriptingError("Unable to find required executable",
                                 exec_path)
        self.chmodx(exec_path)

        terminal = data.get('terminal')
        if terminal:
            terminal = system.get_default_terminal()

        command = [exec_path] + args
        logger.debug("Executing %s" % command)
        thread = LutrisThread(command, env=get_runtime_env(), term=terminal)
        thread.run()
Beispiel #4
0
 def xboxdrv_start(self, config):
     command = ("pkexec xboxdrv --daemon --detach-kernel-driver "
                "--dbus session --silent %s" % config)
     logger.debug("xboxdrv command: %s", command)
     self.xboxdrv_thread = LutrisThread(command)
     self.xboxdrv_thread.set_stop_command(self.xboxdrv_stop)
     self.xboxdrv_thread.start()
Beispiel #5
0
    def play(self):
        """Launch the game."""
        if not self.runner:
            dialogs.ErrorDialog("Invalid game configuration: Missing runner")
            return False
        if not self.prelaunch():
            return False

        system_config = self.runner.system_config
        self.original_outputs = display.get_outputs()
        gameplay_info = self.runner.play()
        logger.debug("Launching %s: %s" % (self.name, gameplay_info))
        if 'error' in gameplay_info:
            show_error_message(gameplay_info)
            return False
        launch_arguments = gameplay_info['command']

        restrict_to_display = system_config.get('display')
        if restrict_to_display:
            display.turn_off_except(restrict_to_display)
            self.resolution_changed = True

        resolution = system_config.get('resolution')
        if resolution:
            display.change_resolution(resolution)
            self.resolution_changed = True

        if system_config.get('reset_pulse'):
            audio.reset_pulse()

        oss_wrapper = system_config.get("oss_wrapper")
        if audio.get_oss_wrapper(oss_wrapper):
            launch_arguments.insert(0, audio.get_oss_wrapper(oss_wrapper))

        ld_preload = gameplay_info.get('ld_preload')
        if ld_preload:
            launch_arguments.insert(0, 'LD_PRELOAD="{}"'.format(ld_preload))

        ld_library_path = gameplay_info.get('ld_library_path')
        if ld_library_path:
            launch_arguments.insert(
                0, 'LD_LIBRARY_PATH="{}"'.format(ld_library_path))

        killswitch = system_config.get('killswitch')
        self.game_thread = LutrisThread(" ".join(launch_arguments),
                                        path=self.runner.working_dir,
                                        killswitch=killswitch)
        if hasattr(self.runner, 'stop'):
            self.game_thread.set_stop_command(self.runner.stop)
        self.game_thread.start()
        if 'joy2key' in gameplay_info:
            self.joy2key(gameplay_info['joy2key'])
        xboxdrv_config = system_config.get('xboxdrv')
        if xboxdrv_config:
            self.xboxdrv(xboxdrv_config)
        if self.runner.is_watchable:
            # Create heartbeat every
            self.heartbeat = GLib.timeout_add(5000, self.poke_process)
Beispiel #6
0
 def xboxdrv_start(self, config):
     command = [
         "pkexec", "xboxdrv", "--daemon", "--detach-kernel-driver",
         "--dbus", "session", "--silent"
     ] + config.split()
     logger.debug("[xboxdrv] %s", ' '.join(command))
     self.xboxdrv_thread = LutrisThread(command)
     self.xboxdrv_thread.set_stop_command(self.xboxdrv_stop)
     self.xboxdrv_thread.start()
Beispiel #7
0
 def remove_game_data(self, **kwargs):
     if not self.is_installed():
         installed = self.install_dialog()
         if not installed:
             return False
     appid = self.game_config.get('appid')
     logger.debug("Launching Wine Steam uninstall of game %s" % appid)
     command = '"%s" steam://uninstall/%s' % (self.steam_exe, appid)
     thread = LutrisThread(command, path=self.working_dir)
     thread.start()
Beispiel #8
0
 def remove_game_data(self, **kwargs):
     if not self.is_installed():
         installed = self.install_dialog()
         if not installed:
             return False
     appid = self.game_config.get('appid')
     logger.debug("Launching Wine Steam uninstall of game %s" % appid)
     command = '"%s" steam://uninstall/%s' % (self.steam_exe, appid)
     thread = LutrisThread(command, path=self.working_dir)
     thread.start()
Beispiel #9
0
 def remove_game_data(self, **kwargs):
     if not self.is_installed():
         installed = self.install_dialog()
         if not installed:
             return False
     appid = self.game_config.get("appid")
     logger.debug("Launching Wine Steam uninstall of game %s" % appid)
     command = [self.steam_exe, "steam://uninstall/%s" % appid]
     thread = LutrisThread(command, runner=self)
     thread.start()
Beispiel #10
0
    def remove_game_data(self, appid=None, **kwargs):
        if not self.is_installed():
            installed = self.install_dialog()
            if not installed:
                return False
        appid = appid if appid else self.appid

        env = self.get_env(full=False)
        command = self.launch_args + ['steam://uninstall/%s' % appid]
        self.prelaunch()
        thread = LutrisThread(command, runner=self, env=env, watch=False)
        thread.start()
Beispiel #11
0
def wineexec(executable,
             args="",
             wine_path=None,
             prefix=None,
             arch=None,
             working_dir=None,
             winetricks_wine='',
             blocking=False,
             config=None):
    """Execute a Wine command."""
    detected_arch = detect_prefix_arch(prefix)
    executable = str(executable) if executable else ''
    if arch not in ('win32', 'win64'):
        arch = detected_arch or 'win32'
    if not wine_path:
        wine_path = wine().get_executable()
    if not working_dir:
        if os.path.isfile(executable):
            working_dir = os.path.dirname(executable)

    if executable.endswith(".msi"):
        executable = 'msiexec /i "%s"' % executable
    elif executable:
        executable = '%s' % executable

    # Create prefix if necessary
    if not detected_arch:
        wine_bin = winetricks_wine if winetricks_wine else wine_path
        create_prefix(prefix, wine_path=wine_bin, arch=arch)

    env = {'WINEARCH': arch}
    if winetricks_wine:
        env['WINE'] = winetricks_wine
    else:
        env['WINE'] = wine_path
    if prefix:
        env['WINEPREFIX'] = prefix

    wine_config = config or LutrisConfig(runner_slug='wine')
    if not wine_config.system_config[
            'disable_runtime'] and not runtime.is_disabled():
        env['LD_LIBRARY_PATH'] = ':'.join(runtime.get_paths())

    command = [wine_path]
    if executable:
        command.append(executable)
    command += shlex.split(args)
    if blocking:
        return system.execute(command, env=env, cwd=working_dir)
    else:
        thread = LutrisThread(command, runner=wine(), env=env, cwd=working_dir)
        thread.start()
        return thread
Beispiel #12
0
    def remove_game_data(self, appid=None, **kwargs):
        if not self.is_installed():
            installed = self.install_dialog()
            if not installed:
                return False
        appid = appid if appid else self.appid

        env = self.get_env(os_env=False)
        command = self.launch_args + ['steam://uninstall/%s' % appid]
        self.prelaunch()
        thread = LutrisThread(command, runner=self, env=env, watch=False)
        thread.start()
Beispiel #13
0
    def remove_game_data(self, **kwargs):
        if not self.is_installed():
            installed = self.install_dialog()
            if not installed:
                return False
        appid = self.game_config.get('appid')

        env = self.get_env()
        command = self.launch_args + ['steam://uninstall/%s' % appid]
        self.prepare_launch()
        thread = LutrisThread(' '.join(command), runner=self, env=env)
        thread.start()
Beispiel #14
0
 def remove_game_data(self, appid=None, **kwargs):
     if not self.is_installed():
         installed = self.install_dialog()
         if not installed:
             return False
     appid = appid if appid else self.game_config.get('appid')
     if appid is None:
         raise RuntimeError('No appid given for uninstallation '
                            '(game config=%s)' % self.game_config)
     logger.debug("Launching Steam uninstall of game %s" % appid)
     command = [self.get_executable(), 'steam://uninstall/%s' % appid]
     thread = LutrisThread(command, runner=self)
     thread.start()
Beispiel #15
0
 def remove_game_data(self, appid=None, **kwargs):
     if not self.is_installed():
         installed = self.install_dialog()
         if not installed:
             return False
     appid = appid if appid else self.appid
     if appid is None:
         raise RuntimeError('No appid given for uninstallation '
                            '(game config=%s)' % self.game_config)
     logger.debug("Launching Steam uninstall of game %s" % appid)
     command = [self.get_executable(), 'steam://uninstall/%s' % appid]
     thread = LutrisThread(command, runner=self, env=self.get_env(), watch=False)
     thread.start()
Beispiel #16
0
    def on_game_quit(self):
        """Restore some settings and cleanup after game quit."""

        self.timer.end_t()
        self.playtime = self.timer.increment(self.playtime)

        if self.prelaunch_thread:
            logger.info("Stopping prelaunch script")
            self.prelaunch_thread.stop()

        self.heartbeat = None
        if self.state != self.STATE_STOPPED:
            logger.debug("Game thread still running, stopping it (state: %s)", self.state)
            self.stop()
        
        # Check for post game script
        postexit_command = self.runner.system_config.get("postexit_command")
        if system.path_exists(postexit_command):
            logger.info("Running post-exit command: %s", postexit_command)
            postexit_thread = LutrisThread(
                [postexit_command],
                include_processes=[os.path.basename(postexit_command)],
                cwd=self.directory
            )
            postexit_thread.start()

        quit_time = time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime())
        logger.debug("%s stopped at %s", self.name, quit_time)
        self.lastplayed = int(time.time())
        self.save(metadata_only=True)

        os.chdir(os.path.expanduser('~'))

        if self.resolution_changed\
           or self.runner.system_config.get('reset_desktop'):
            display.change_resolution(self.original_outputs)

        if self.compositor_disabled:
            self.set_desktop_compositing(True)

        if self.runner.system_config.get('use_us_layout'):
            subprocess.Popen(['setxkbmap'], env=os.environ).communicate()

        if self.runner.system_config.get('restore_gamma'):
            display.restore_gamma()

        self.process_return_codes()

        if self.exit_main_loop:
            exit()
Beispiel #17
0
    def run(self, *args):
        """Run the runner alone."""
        if not self.runnable_alone:
            return
        if not self.is_installed():
            installed = self.install_dialog()
            if not installed:
                return

        command_data = self.get_run_data()
        command = command_data.get('command')
        env = (command_data.get('env') or {}).copy()

        thread = LutrisThread(command, runner=self, env=env, watch=False)
        thread.start()
Beispiel #18
0
    def run(self, *args):
        """Run the runner alone."""
        if not self.runnable_alone:
            return
        if not self.is_installed():
            installed = self.install_dialog()
            if not installed:
                return

        command_data = self.get_run_data()
        command = command_data.get('command')
        env = (command_data.get('env') or {}).copy()

        thread = LutrisThread(command, runner=self, env=env, watch=False)
        thread.start()
Beispiel #19
0
 def joy2key(self, config):
     """ Run a joy2key thread. """
     win = "grep %s" % config['window']
     if 'notwindow' in config:
         win = win + ' | grep -v %s' % config['notwindow']
     wid = "xwininfo -root -tree | %s | awk '{print $1}'" % win
     buttons = config['buttons']
     axis = "Left Right Up Down"
     rcfile = "~/.joy2keyrc"
     command = "sleep 5 "
     command += "&& joy2key $(%s) -X -rcfile %s -buttons %s -axis %s" % (
         wid, rcfile, buttons, axis
     )
     joy2key_thread = LutrisThread(command)
     self.game_thread.attach_thread(joy2key_thread)
     joy2key_thread.start()
Beispiel #20
0
def wineexec(executable, args="", wine_path=None, prefix=None, arch=None,
             working_dir=None, winetricks_wine='', blocking=False):
    """Execute a Wine command."""
    detected_arch = detect_prefix_arch(prefix)
    executable = str(executable) if executable else ''
    if arch not in ('win32', 'win64'):
        arch = detected_arch or 'win32'
    if not wine_path:
        wine_path = wine().get_executable()
    if not working_dir:
        if os.path.isfile(executable):
            working_dir = os.path.dirname(executable)

    if executable.endswith(".msi"):
        executable = 'msiexec /i "%s"' % executable
    elif executable:
        executable = '%s' % executable

    # Create prefix if necessary
    if not detected_arch:
        wine_bin = winetricks_wine if winetricks_wine else wine_path
        create_prefix(prefix, wine_path=wine_bin, arch=arch)

    env = {
        'WINEARCH': arch
    }
    if winetricks_wine:
        env['WINE'] = winetricks_wine
    else:
        env['WINE'] = wine_path
    if prefix:
        env['WINEPREFIX'] = prefix

    wine_config = LutrisConfig(runner_slug='wine')
    if not wine_config.system_config['disable_runtime'] and not runtime.is_disabled():
        env['LD_LIBRARY_PATH'] = ':'.join(runtime.get_paths())

    command = [wine_path]
    if executable:
        command.append(executable)
    command += shlex.split(args)
    if blocking:
        return system.execute(command, env=env, cwd=working_dir)
    else:
        thread = LutrisThread(command, runner=wine(), env=env, cwd=working_dir)
        thread.start()
        return thread
Beispiel #21
0
    def play(self):
        """Launch the game."""
        if not self.runner:
            dialogs.ErrorDialog("Invalid game configuration: Missing runner")
            return False
        if not self.prelaunch():
            return False

        system_config = self.runner.system_config
        self.original_outputs = display.get_outputs()
        gameplay_info = self.runner.play()
        logger.debug("Launching %s: %s" % (self.name, gameplay_info))
        if 'error' in gameplay_info:
            show_error_message(gameplay_info)
            return False
        launch_arguments = gameplay_info['command']

        restrict_to_display = system_config.get('display')
        if restrict_to_display:
            display.turn_off_except(restrict_to_display)
            self.resolution_changed = True

        resolution = system_config.get('resolution')
        if resolution:
            display.change_resolution(resolution)
            self.resolution_changed = True

        if system_config.get('reset_pulse'):
            audio.reset_pulse()

        oss_wrapper = system_config.get("oss_wrapper")
        if audio.get_oss_wrapper(oss_wrapper):
            launch_arguments.insert(0, audio.get_oss_wrapper(oss_wrapper))

        ld_preload = gameplay_info.get('ld_preload')
        if ld_preload:
            launch_arguments.insert(0, 'LD_PRELOAD="{}"'.format(ld_preload))

        ld_library_path = gameplay_info.get('ld_library_path')
        if ld_library_path:
            launch_arguments.insert(
                0, 'LD_LIBRARY_PATH="{}"'.format(ld_library_path)
            )

        killswitch = system_config.get('killswitch')
        self.game_thread = LutrisThread(" ".join(launch_arguments),
                                        path=self.runner.working_dir,
                                        killswitch=killswitch)
        if hasattr(self.runner, 'stop'):
            self.game_thread.set_stop_command(self.runner.stop)
        self.game_thread.start()
        if 'joy2key' in gameplay_info:
            self.joy2key(gameplay_info['joy2key'])
        xboxdrv_config = system_config.get('xboxdrv')
        if xboxdrv_config:
            self.xboxdrv(xboxdrv_config)
        if self.runner.is_watchable:
            # Create heartbeat every
            self.heartbeat = GLib.timeout_add(5000, self.poke_process)
Beispiel #22
0
    def remove_game_data(self, **kwargs):
        if not self.is_installed():
            installed = self.install_dialog()
            if not installed:
                return False
        appid = self.game_config.get('appid')
        prefix = self.game_config.get('prefix')

        command = []
        command.append('WINEARCH=%s ' % self.wine_arch)
        command.append('WINEPREFIX="%s" ' % self.prefix_path)
        command += self.launch_args
        command += ['steam://uninstall/%s' % appid]

        self.prepare_launch()
        thread = LutrisThread(' '.join(command), path=self.working_dir)
        thread.start()
Beispiel #23
0
    def play(self):
        """ Launch the game. """
        if not self.prelaunch():
            return False
        gameplay_info = self.runner.play()
        logger.debug("Launching %s: %s" % (self.name, gameplay_info))
        if isinstance(gameplay_info, dict):
            if 'error' in gameplay_info:
                show_error_message(gameplay_info)
                return False
            launch_arguments = gameplay_info['command']
        else:
            logger.error("Old method used for returning gameplay infos")
            launch_arguments = gameplay_info

        restrict_to_display = self.game_config.get_system('display')
        if restrict_to_display:
            display.turn_off_except(restrict_to_display)
            self.resolution_changed = True

        resolution = self.game_config.get_system('resolution')
        if resolution:
            display.change_resolution(resolution)
            self.resolution_changed = True

        if self.game_config.get_system('reset_pulse'):
            audio.reset_pulse()

        if self.game_config.get_system('hide_panels'):
            self.desktop.hide_panels()

        oss_wrapper = self.game_config.get_system("oss_wrapper")
        if oss_wrapper:
            launch_arguments.insert(0, audio.get_oss_wrapper(oss_wrapper))

        ld_preload = gameplay_info.get('ld_preload')
        if ld_preload:
            launch_arguments.insert(0, 'LD_PRELOAD="{}"'.format(ld_preload))

        ld_library_path = gameplay_info.get('ld_library_path')
        if ld_library_path:
            launch_arguments.insert(
                0, 'LD_LIBRARY_PATH="{}"'.format(ld_library_path)
            )

        killswitch = self.game_config.get_system('killswitch')
        self.heartbeat = GLib.timeout_add(5000, self.poke_process)
        self.game_thread = LutrisThread(" ".join(launch_arguments),
                                        path=self.runner.get_game_path(),
                                        killswitch=killswitch)
        if hasattr(self.runner, 'stop'):
            self.game_thread.set_stop_command(self.runner.stop)
        self.game_thread.start()
        if 'joy2key' in gameplay_info:
            self.joy2key(gameplay_info['joy2key'])
        xboxdrv_config = self.game_config.get_system('xboxdrv')
        if xboxdrv_config:
            self.xboxdrv(xboxdrv_config)
Beispiel #24
0
 def xboxdrv_start(self, config):
     command = [
         "pkexec", "xboxdrv", "--daemon", "--detach-kernel-driver",
         "--dbus", "session", "--silent"
     ] + config.split()
     logger.debug("xboxdrv command: %s", command)
     self.xboxdrv_thread = LutrisThread(command)
     self.xboxdrv_thread.set_stop_command(self.xboxdrv_stop)
     self.xboxdrv_thread.start()
Beispiel #25
0
 def xboxdrv_start(self, config):
     command = [
         "pkexec", "xboxdrv", "--daemon", "--detach-kernel-driver",
         "--dbus", "session", "--silent"
     ] + config.split()
     logger.debug("[xboxdrv] %s", ' '.join(command))
     self.xboxdrv_thread = LutrisThread(command, include_processes=['xboxdrv'])
     self.xboxdrv_thread.set_stop_command(self.xboxdrv_stop)
     self.xboxdrv_thread.start()
Beispiel #26
0
 def joy2key(self, config):
     """Run a joy2key thread."""
     if not system.find_executable('joy2key'):
         logger.error("joy2key is not installed")
         return
     win = "grep %s" % config['window']
     if 'notwindow' in config:
         win += ' | grep -v %s' % config['notwindow']
     wid = "xwininfo -root -tree | %s | awk '{print $1}'" % win
     buttons = config['buttons']
     axis = "Left Right Up Down"
     rcfile = os.path.expanduser("~/.joy2keyrc")
     rc_option = '-rcfile %s' % rcfile if os.path.exists(rcfile) else ''
     command = "sleep 5 "
     command += "&& joy2key $(%s) -X %s -buttons %s -axis %s" % (
         wid, rc_option, buttons, axis)
     joy2key_thread = LutrisThread(command)
     self.game_thread.attach_thread(joy2key_thread)
     joy2key_thread.start()
Beispiel #27
0
 def on_execute_script_clicked(self, _widget):
     """Execute the game's associated script"""
     game = Game(self.view.selected_game)
     ondemand_command = game.runner.system_config.get("ondemand_command")
     if path_exists(ondemand_command):
         LutrisThread(
             [ondemand_command],
             include_processes=[os.path.basename(ondemand_command)],
             cwd=game.directory).start()
         logger.info("Running %s in the background", ondemand_command)
Beispiel #28
0
    def execute(self, data):
        """Run an executable file."""
        args = []
        terminal = None
        working_dir = None
        if isinstance(data, dict):
            self._check_required_params('file', data, 'execute')
            file_ref = data['file']
            args_string = data.get('args', '')
            for arg in shlex.split(args_string):
                args.append(self._substitute(arg))
            terminal = data.get('terminal')
            working_dir = data.get('working_dir')
        else:
            file_ref = data

        # Determine whether 'file' value is a file id or a path
        exec_path = self._get_file(file_ref) or self._substitute(file_ref)
        if not exec_path:
            raise ScriptingError("Unable to find file %s" % file_ref, file_ref)
        if not os.path.exists(exec_path):
            raise ScriptingError("Unable to find required executable",
                                 exec_path)
        if not os.access(exec_path, os.X_OK):
            self.chmodx(exec_path)

        if terminal:
            terminal = system.get_default_terminal()

        if not working_dir or not os.path.exists(working_dir):
            working_dir = self.target_path

        command = [exec_path] + args
        logger.debug("Executing %s" % command)
        thread = LutrisThread(command,
                              env=runtime.get_env(),
                              term=terminal,
                              cwd=self.target_path,
                              watch=False)
        self.abort_current_task = thread.killall
        thread.run()
        self.abort_current_task = None
Beispiel #29
0
 def joy2key(self, config):
     """Run a joy2key thread."""
     if not system.find_executable('joy2key'):
         logger.error("joy2key is not installed")
         return
     win = "grep %s" % config['window']
     if 'notwindow' in config:
         win += ' | grep -v %s' % config['notwindow']
     wid = "xwininfo -root -tree | %s | awk '{print $1}'" % win
     buttons = config['buttons']
     axis = "Left Right Up Down"
     rcfile = os.path.expanduser("~/.joy2keyrc")
     rc_option = '-rcfile %s' % rcfile if os.path.exists(rcfile) else ''
     command = "sleep 5 "
     command += "&& joy2key $(%s) -X %s -buttons %s -axis %s" % (
         wid, rc_option, buttons, axis
     )
     joy2key_thread = LutrisThread(command)
     self.game_thread.attach_thread(joy2key_thread)
     joy2key_thread.start()
Beispiel #30
0
    def execute(self, data):
        """Run an executable file."""
        args = []
        terminal = None
        working_dir = None
        if isinstance(data, dict):
            self._check_required_params('file', data, 'execute')
            file_ref = data['file']
            args_string = data.get('args', '')
            for arg in shlex.split(args_string):
                args.append(self._substitute(arg))
            terminal = data.get('terminal')
            working_dir = data.get('working_dir')
        else:
            file_ref = data

        # Determine whether 'file' value is a file id or a path
        exec_path = self._get_file(file_ref) or self._substitute(file_ref)
        if not exec_path:
            raise ScriptingError("Unable to find file %s" % file_ref,
                                 file_ref)
        if not os.path.exists(exec_path):
            raise ScriptingError("Unable to find required executable",
                                 exec_path)
        if not os.access(exec_path, os.X_OK):
            self.chmodx(exec_path)

        if terminal:
            terminal = system.get_default_terminal()

        if not working_dir or not os.path.exists(working_dir):
            working_dir = self.target_path

        command = [exec_path] + args
        logger.debug("Executing %s" % command)
        thread = LutrisThread(command, env=runtime.get_env(), term=terminal,
                              cwd=self.target_path, watch=False)
        self.abort_current_task = thread.killall
        thread.run()
        self.abort_current_task = None
Beispiel #31
0
    def run(self, *args):
        """Run the runner alone."""
        if not self.runnable_alone:
            return
        if not self.is_installed():
            installed = self.install_dialog()
            if not installed:
                return

        if self.use_runtime():
            if runtime.is_outdated() or runtime.is_updating():
                result = dialogs.RuntimeUpdateDialog().run()
                if not result == Gtk.ResponseType.OK:
                    return

        command_data = self.get_run_data()
        command = command_data.get('command')
        env = (command_data.get('env') or {}).copy()
        if self.use_runtime():
            env.update(runtime.get_env())

        thread = LutrisThread(command, runner=self, env=env, watch=False)
        thread.start()
Beispiel #32
0
    def do_play(self, prelaunched, _error=None):
        if not prelaunched:
            self.state = self.STATE_STOPPED
            return
        system_config = self.runner.system_config
        self.original_outputs = display.get_outputs()
        gameplay_info = self.runner.play()

        env = {}

        logger.debug("Launching %s: %s" % (self.name, gameplay_info))
        if 'error' in gameplay_info:
            show_error_message(gameplay_info)
            self.state = self.STATE_STOPPED
            return

        restrict_to_display = system_config.get('display')
        if restrict_to_display != 'off':
            display.turn_off_except(restrict_to_display)
            time.sleep(3)
            self.resolution_changed = True

        resolution = system_config.get('resolution')
        if resolution != 'off':
            display.change_resolution(resolution)
            time.sleep(3)
            self.resolution_changed = True

        if system_config.get('reset_pulse'):
            audio.reset_pulse()

        self.killswitch = system_config.get('killswitch')
        if self.killswitch and not os.path.exists(self.killswitch):
            # Prevent setting a killswitch to a file that doesn't exists
            self.killswitch = None

        # Command
        launch_arguments = gameplay_info['command']

        primusrun = system_config.get('primusrun')
        if primusrun and system.find_executable('primusrun'):
            launch_arguments.insert(0, 'primusrun')

        xephyr = system_config.get('xephyr') or 'off'
        if xephyr != 'off':
            if xephyr == '8bpp':
                xephyr_depth = '8'
            else:
                xephyr_depth = '16'
            xephyr_resolution = system_config.get('xephyr_resolution') or '640x480'
            xephyr_command = ['Xephyr', ':2', '-ac', '-screen',
                              xephyr_resolution + 'x' + xephyr_depth, '-glamor',
                              '-reset', '-terminate', '-fullscreen']
            xephyr_thread = LutrisThread(xephyr_command)
            xephyr_thread.start()
            time.sleep(3)
            env['DISPLAY'] = ':2'

        pulse_latency = system_config.get('pulse_latency')
        if pulse_latency:
            env['PULSE_LATENCY_MSEC'] = '60'

        prefix_command = system_config.get("prefix_command") or ''
        if prefix_command.strip():
            launch_arguments.insert(0, prefix_command)

        terminal = system_config.get('terminal')
        if terminal:
            terminal = system_config.get("terminal_app",
                                         system.get_default_terminal())
            if terminal and not system.find_executable(terminal):
                dialogs.ErrorDialog("The selected terminal application "
                                    "could not be launched:\n"
                                    "%s" % terminal)
                self.state = self.STATE_STOPPED
                return
        # Env vars
        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')
            ld_library_path += runtime.get_paths()

        game_ld_libary_path = gameplay_info.get('ld_library_path')
        if game_ld_libary_path:
            ld_library_path.append(game_ld_libary_path)

        if ld_library_path:
            ld_full = ':'.join(ld_library_path)
            env["LD_LIBRARY_PATH"] = "{}:$LD_LIBRARY_PATH".format(ld_full)
        # /Env vars

        self.game_thread = LutrisThread(launch_arguments,
                                        runner=self.runner, env=env,
                                        rootpid=gameplay_info.get('rootpid'),
                                        term=terminal)
        if hasattr(self.runner, 'stop'):
            self.game_thread.set_stop_command(self.runner.stop)
        self.game_thread.start()
        self.state = self.STATE_RUNNING
        if 'joy2key' in gameplay_info:
            self.joy2key(gameplay_info['joy2key'])
        xboxdrv_config = system_config.get('xboxdrv')
        if xboxdrv_config:
            self.xboxdrv_start(xboxdrv_config)
        self.heartbeat = GLib.timeout_add(HEARTBEAT_DELAY, self.beat)
Beispiel #33
0
def wineexec(executable, args="", wine_path=None, prefix=None, arch=None,
             working_dir=None, winetricks_wine='', blocking=False,
             config=None, include_processes=[], exclude_processes=[],
             disable_runtime=False, env={}, overrides=None):
    """
    Execute a Wine command.

    Args:
        executable (str): wine program to run, pass None to run wine itself
        args (str): program arguments
        wine_path (str): path to the wine version to use
        prefix (str): path to the wine prefix to use
        arch (str): wine architecture of the prefix
        working_dir (str): path to the working dir for the process
        winetricks_wine (str): path to the wine version used by winetricks
        blocking (bool): if true, do not run the process in a thread
        config (LutrisConfig): LutrisConfig object for the process context
        watch (list): list of process names to monitor (even when in a ignore list)

    Returns:
        Process results if the process is running in blocking mode or
        LutrisThread instance otherwise.
    """
    executable = str(executable) if executable else ''
    if not wine_path:
        wine_path = wine().get_executable()
    if not working_dir:
        if os.path.isfile(executable):
            working_dir = os.path.dirname(executable)

    executable, _args, working_dir = get_real_executable(executable, working_dir)
    if _args:
        args = '{} "{}"'.format(_args[0], _args[1])

    # Create prefix if necessary
    detected_arch = detect_prefix_arch(prefix)
    if arch not in ('win32', 'win64'):
        arch = detected_arch or 'win32'
    if not detected_arch:
        wine_bin = winetricks_wine if winetricks_wine else wine_path
        create_prefix(prefix, wine_path=wine_bin, arch=arch)

    wineenv = {
        'WINEARCH': arch
    }
    if winetricks_wine:
        wineenv['WINE'] = winetricks_wine
    else:
        wineenv['WINE'] = wine_path
    if prefix:
        wineenv['WINEPREFIX'] = prefix

    wine_config = config or LutrisConfig(runner_slug='wine')
    if (
            not runtime.RUNTIME_DISABLED and
            not disable_runtime and
            not wine_config.system_config['disable_runtime']
    ):
        wineenv['LD_LIBRARY_PATH'] = ':'.join(runtime.get_paths())

    if overrides:
        wineenv['WINEDLLOVERRIDES'] = get_overrides_env(overrides)

    wineenv.update(env)

    command = [wine_path]
    if executable:
        command.append(executable)
    command += shlex.split(args)
    if blocking:
        return system.execute(command, env=wineenv, cwd=working_dir)
    else:
        thread = LutrisThread(command, runner=wine(), env=wineenv, cwd=working_dir,
                              include_processes=include_processes,
                              exclude_processes=exclude_processes)
        thread.start()
        return thread
Beispiel #34
0
class Game(object):
    """" This class takes cares about loading the configuration for a game
         and running it.
    """
    def __init__(self, slug):
        self.slug = slug
        self.game_thread = None
        self.heartbeat = None
        self.game_config = None

        game_data = pga.get_game_by_slug(slug)
        self.runner_name = game_data['runner']
        self.directory = game_data['directory']
        self.name = game_data['name']
        self.is_installed = bool(game_data['installed'])

        self.load_config()

    def get_runner(self):
        """ Return the runner's name """
        return self.game_config['runner']

    def load_config(self):
        """ Load the game's configuration. """
        self.game_config = LutrisConfig(game=self.slug)
        if self.is_installed:
            if not self.game_config.is_valid():
                logger.error("Invalid game config for %s" % self.slug)
            else:
                runner_class = import_runner(self.runner_name)
                self.runner = runner_class(self.game_config)

    def remove(self, from_library=False, from_disk=False):
        if from_disk:
            if os.path.exists(self.directory):
                shutil.rmtree(self.directory)
        if from_library:
            pga.delete_game(self.slug)
        else:
            pga.set_uninstalled(self.slug)
        self.game_config.remove()

    def prelaunch(self):
        """ Verify that the current game can be launched. """
        if not self.runner.is_installed():
            question = ("The required runner is not installed.\n"
                        "Do you wish to install it now ?")
            install_runner_dialog = dialogs.QuestionDialog(
                {'question': question,
                 'title': "Required runner unavailable"})
            if Gtk.ResponseType.YES == install_runner_dialog.result:
                self.runner.install()
            return False

        if hasattr(self.runner, 'prelaunch'):
            success = self.runner.prelaunch()
            return success
        return True

    def play(self):
        """ Launch the game. """
        if not self.prelaunch():
            return False
        gameplay_info = self.runner.play()
        logger.debug("Launching %s: %s" % (self.name, gameplay_info))
        if isinstance(gameplay_info, dict):
            if 'error' in gameplay_info:
                show_error_message(gameplay_info)
                return False
            launch_arguments = gameplay_info['command']
        else:
            logger.error("Old method used for returning gameplay infos")
            launch_arguments = gameplay_info

        resolution = self.game_config.get_system('resolution')
        if resolution:
            desktop_control.change_resolution(resolution)

        if self.game_config.get_system('reset_pulse'):
            desktop_control.reset_pulse()

        if self.game_config.get_system('hide_panels'):
            self.desktop.hide_panels()

        oss_wrapper = self.game_config.get_system("oss_wrapper")
        if oss_wrapper:
            launch_arguments.insert(0, audio.get_oss_wrapper(oss_wrapper))

        ld_preload = gameplay_info.get('ld_preload')
        if ld_preload:
            launch_arguments.insert(0, 'LD_PRELOAD="{}"'.format(ld_preload))

        ld_library_path = gameplay_info.get('ld_library_path')
        if ld_library_path:
            launch_arguments.insert(
                0, 'LD_LIBRARY_PATH="{}"'.format(ld_library_path)
            )

        killswitch = self.game_config.get_system('killswitch')
        self.heartbeat = GLib.timeout_add(5000, self.poke_process)
        self.game_thread = LutrisThread(" ".join(launch_arguments),
                                        path=self.runner.get_game_path(),
                                        killswitch=killswitch)
        if hasattr(self.runner, 'stop'):
            self.game_thread.set_stop_command(self.runner.stop)
        self.game_thread.start()
        if 'joy2key' in gameplay_info:
            self.joy2key(gameplay_info['joy2key'])
        xboxdrv_config = self.game_config.get_system('xboxdrv')
        if xboxdrv_config:
            self.xboxdrv(xboxdrv_config)

    def joy2key(self, config):
        """ Run a joy2key thread. """
        win = "grep %s" % config['window']
        if 'notwindow' in config:
            win = win + ' | grep -v %s' % config['notwindow']
        wid = "xwininfo -root -tree | %s | awk '{print $1}'" % win
        buttons = config['buttons']
        axis = "Left Right Up Down"
        rcfile = "~/.joy2keyrc"
        command = "sleep 5 "
        command += "&& joy2key $(%s) -X -rcfile %s -buttons %s -axis %s" % (
            wid, rcfile, buttons, axis
        )
        joy2key_thread = LutrisThread(command)
        self.game_thread.attach_thread(joy2key_thread)
        joy2key_thread.start()

    def xboxdrv(self, config):
        command = ("pkexec xboxdrv --daemon --detach-kernel-driver "
                   "--dbus session --silent %s"
                   % config)
        logger.debug("xboxdrv command: %s", command)
        thread = LutrisThread(command)
        thread.start()

    def poke_process(self):
        """ Watch game's process. """
        if not self.game_thread.pid:
            self.quit_game()
            return False
        return True

    def quit_game(self):
        """ Quit the game and cleanup. """
        self.heartbeat = None
        quit_time = time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime())
        logger.debug("game has quit at %s" % quit_time)

        if self.game_config.get_system('resolution'):
            desktop_control.reset_desktop()

        if self.game_config.get_system('xboxdrv'):
            logger.debug("Shutting down xboxdrv")
            os.system("pkexec xboxdrvctl --shutdown")

        if self.game_thread:
            self.game_thread.stop()
Beispiel #35
0
def wineexec(executable, args="", wine_path=None, prefix=None, arch=None,
             working_dir=None, winetricks_wine='', blocking=False,
             config=None, include_processes=[]):
    """
    Execute a Wine command.

    Args:
        executable (str): wine program to run, pass None to run wine itself
        args (str): program arguments
        wine_path (str): path to the wine version to use
        prefix (str): path to the wine prefix to use
        arch (str): wine architecture of the prefix
        working_dir (str): path to the working dir for the process
        winetricks_wine (str): path to the wine version used by winetricks
        blocking (bool): if true, do not run the process in a thread
        config (LutrisConfig): LutrisConfig object for the process context
        watch (list): list of process names to monitor (even when in a ignore list)

    Returns:
        Process results if the process is running in blocking mode or
        LutrisThread instance otherwise.
    """
    detected_arch = detect_prefix_arch(prefix)
    executable = str(executable) if executable else ''
    if arch not in ('win32', 'win64'):
        arch = detected_arch or 'win32'
    if not wine_path:
        wine_path = wine().get_executable()
    if not working_dir:
        if os.path.isfile(executable):
            working_dir = os.path.dirname(executable)

    executable, _args = get_real_executable(executable)
    if _args:
        args = '{} "{}"'.format(_args[0], _args[1])

    # Create prefix if necessary
    if not detected_arch:
        wine_bin = winetricks_wine if winetricks_wine else wine_path
        create_prefix(prefix, wine_path=wine_bin, arch=arch)

    env = {
        'WINEARCH': arch
    }
    if winetricks_wine:
        env['WINE'] = winetricks_wine
    else:
        env['WINE'] = wine_path
    if prefix:
        env['WINEPREFIX'] = prefix

    wine_config = config or LutrisConfig(runner_slug='wine')
    if not wine_config.system_config['disable_runtime'] and not runtime.is_disabled():
        env['LD_LIBRARY_PATH'] = ':'.join(runtime.get_paths())

    command = [wine_path]
    if executable:
        command.append(executable)
    command += shlex.split(args)
    if blocking:
        return system.execute(command, env=env, cwd=working_dir)
    else:
        thread = LutrisThread(command, runner=wine(), env=env, cwd=working_dir,
                              include_processes=include_processes)
        thread.start()
        return thread
Beispiel #36
0
    def play(self):
        """Launch the game."""
        if not self.runner:
            dialogs.ErrorDialog("Invalid game configuration: Missing runner")
            return False
        if not self.prelaunch():
            return False

        system_config = self.runner.system_config
        self.original_outputs = display.get_outputs()
        gameplay_info = self.runner.play()
        logger.debug("Launching %s: %s" % (self.name, gameplay_info))
        if 'error' in gameplay_info:
            show_error_message(gameplay_info)
            return False

        launch_arguments = gameplay_info['command']

        restrict_to_display = system_config.get('display')
        if restrict_to_display:
            display.turn_off_except(restrict_to_display)
            time.sleep(3)
            self.resolution_changed = True

        resolution = system_config.get('resolution')
        if resolution:
            display.change_resolution(resolution)
            time.sleep(3)
            self.resolution_changed = True

        if system_config.get('reset_pulse'):
            audio.reset_pulse()

        primusrun = system_config.get('primusrun')
        if primusrun and system.find_executable('primusrun'):
            launch_arguments.insert(0, 'primusrun')

        prefix_command = system_config.get("prefix_command", '').strip()
        if prefix_command:
            launch_arguments.insert(0, prefix_command)

        ld_preload = gameplay_info.get('ld_preload')
        if ld_preload:
            launch_arguments.insert(0, 'LD_PRELOAD="{}"'.format(ld_preload))

        ld_library_path = []
        if self.use_runtime(system_config):
            runtime64_path = os.path.join(settings.RUNTIME_DIR, "lib64")
            if os.path.exists(runtime64_path):
                ld_library_path.append(runtime64_path)
            runtime32_path = os.path.join(settings.RUNTIME_DIR, "lib32")
            if os.path.exists(runtime32_path):
                ld_library_path.append(runtime32_path)

        game_ld_libary_path = gameplay_info.get('ld_library_path')
        if game_ld_libary_path:
            ld_library_path.append(game_ld_libary_path)

        if ld_library_path:
            ld_full = ':'.join(ld_library_path)
            ld_arg = 'LD_LIBRARY_PATH="{}:$LD_LIBRARY_PATH"'.format(ld_full)
            launch_arguments.insert(0, ld_arg)

        env = gameplay_info.get('env') or []
        for var in env:
            launch_arguments.insert(0, var)

        killswitch = system_config.get('killswitch')
        self.game_thread = LutrisThread(" ".join(launch_arguments),
                                        path=self.runner.working_dir,
                                        killswitch=killswitch)
        if hasattr(self.runner, 'stop'):
            self.game_thread.set_stop_command(self.runner.stop)
        self.game_thread.start()
        if 'joy2key' in gameplay_info:
            self.joy2key(gameplay_info['joy2key'])
        xboxdrv_config = system_config.get('xboxdrv')
        if xboxdrv_config:
            self.xboxdrv_start(xboxdrv_config)
        if self.runner.is_watchable:
            # Create heartbeat every
            self.heartbeat = GLib.timeout_add(5000, self.poke_process)
Beispiel #37
0
class Game(object):
    """This class takes cares of loading the configuration for a game
       and running it.
    """
    STATE_IDLE = 'idle'
    STATE_STOPPED = 'stopped'
    STATE_RUNNING = 'running'

    def __init__(self, id=None):
        self.id = id
        self.runner = None
        self.game_thread = None
        self.heartbeat = None
        self.config = None
        self.killswitch = None
        self.state = self.STATE_IDLE
        self.exit_main_loop = False

        game_data = pga.get_game_by_field(id, 'id')
        self.slug = game_data.get('slug') or ''
        self.runner_name = game_data.get('runner') or ''
        self.directory = game_data.get('directory') or ''
        self.name = game_data.get('name') or ''

        self.is_installed = bool(game_data.get('installed')) or False
        self.platform = game_data.get('platform') or ''
        self.year = game_data.get('year') or ''
        self.lastplayed = game_data.get('lastplayed') or 0
        self.game_config_id = game_data.get('configpath') or ''
        self.steamid = game_data.get('steamid') or ''
        self.has_custom_banner = bool(game_data.get('has_custom_banner')) or False
        self.has_custom_icon = bool(game_data.get('has_custom_icon')) or False

        self.load_config()
        self.resolution_changed = False
        self.compositor_disabled = False
        self.stop_compositor = self.start_compositor = ""
        self.original_outputs = None
        self.log_buffer = Gtk.TextBuffer()
        self.log_buffer.create_tag("warning", foreground="red")

    def __repr__(self):
        return self.__unicode__()

    def __unicode__(self):
        value = self.name
        if self.runner_name:
            value += " (%s)" % self.runner_name
        return value

    def show_error_message(self, message):
        """Display an error message based on the runner's output."""
        if "CUSTOM" == message['error']:
            message_text = message['text'].replace('&', '&')
            dialogs.ErrorDialog(message_text)
        elif "RUNNER_NOT_INSTALLED" == message['error']:
            dialogs.ErrorDialog('Error the runner is not installed')
        elif "NO_BIOS" == message['error']:
            dialogs.ErrorDialog("A bios file is required to run this game")
        elif "FILE_NOT_FOUND" == message['error']:
            filename = message['file']
            if filename:
                message_text = "The file {} could not be found".format(
                    filename.replace('&', '&')
                )
            else:
                message_text = "No file provided"
            dialogs.ErrorDialog(message_text)

        elif "NOT_EXECUTABLE" == message['error']:
            message_text = message['file'].replace('&', '&')
            dialogs.ErrorDialog("The file %s is not executable" % message_text)

    def get_browse_dir(self):
        """Return the path to open with the Browse Files action."""
        return self.runner.browse_dir

    def load_config(self):
        """Load the game's configuration."""
        self.config = LutrisConfig(runner_slug=self.runner_name,
                                   game_config_id=self.game_config_id)
        if not self.is_installed:
            return
        if not self.runner_name:
            logger.error('Incomplete data for %s', self.slug)
            return
        try:
            runner_class = import_runner(self.runner_name)
        except InvalidRunner:
            logger.error("Unable to import runner %s for %s",
                         self.runner_name, self.slug)
        else:
            self.runner = runner_class(self.config)

    def desktop_effects(self, enable):
        if enable:
            system.execute(self.start_compositor, shell=True)
        else:
            if os.environ.get('DESKTOP_SESSION') == "plasma":
                self.stop_compositor = "qdbus org.kde.KWin /Compositor org.kde.kwin.Compositing.suspend"
                self.start_compositor = "qdbus org.kde.KWin /Compositor org.kde.kwin.Compositing.resume"
            elif os.environ.get('DESKTOP_SESSION') == "mate" and system.execute("gsettings get org.mate.Marco.general compositing-manager", shell=True) == 'true':
                self.stop_compositor = "gsettings set org.mate.Marco.general compositing-manager false"
                self.start_compositor = "gsettings set org.mate.Marco.general compositing-manager true"
            elif os.environ.get('DESKTOP_SESSION') == "xfce" and system.execute("xfconf-query --channel=xfwm4 --property=/general/use_compositing", shell=True) == 'true':
                self.stop_compositor = "xfconf-query --channel=xfwm4 --property=/general/use_compositing --set=false"
                self.start_compositor = "xfconf-query --channel=xfwm4 --property=/general/use_compositing --set=true"

            if not (self.compositor_disabled or self.stop_compositor == ""):
                system.execute(self.stop_compositor, shell=True)
                self.compositor_disabled = True;

    def remove(self, from_library=False, from_disk=False):
        if from_disk and self.runner:
            logger.debug("Removing game %s from disk" % self.id)
            self.runner.remove_game_data(game_path=self.directory)

        # Do not keep multiple copies of the same game
        existing_games = pga.get_games_where(slug=self.slug)
        if len(existing_games) > 1:
            from_library = True

        if from_library:
            logger.debug("Removing game %s from library" % self.id)
            pga.delete_game(self.id)
        else:
            pga.set_uninstalled(self.id)
        self.config.remove()
        xdg.remove_launcher(self.slug, self.id, desktop=True, menu=True)
        return from_library

    def set_platform_from_runner(self):
        if not self.runner:
            return
        self.platform = self.runner.get_platform()

    def save(self, metadata_only=False):
        """
        Save the game's config and metadata, if `metadata_only` is set to True,
        do not save the config. This is useful when exiting the game since the
        config might have changed and we don't want to override the changes.
        """
        if not metadata_only:
            self.config.save()
        self.id = pga.add_or_update(
            name=self.name,
            runner=self.runner_name,
            slug=self.slug,
            platform=self.platform,
            year=self.year,
            lastplayed=self.lastplayed,
            directory=self.directory,
            installed=self.is_installed,
            configpath=self.config.game_config_id,
            steamid=self.steamid,
            id=self.id
        )

    def prelaunch(self):
        """Verify that the current game can be launched."""
        if not self.runner.is_installed():
            installed = self.runner.install_dialog()
            if not installed:
                return False

        if self.runner.use_runtime():
            runtime_updater = runtime.RuntimeUpdater()
            if runtime_updater.is_updating():
                logger.warning("Runtime updates: {}".format(
                    runtime_updater.current_updates)
                )
                dialogs.ErrorDialog("Runtime currently updating",
                                    "Game might not work as expected")
        return True

    def play(self):
        """Launch the game."""
        if not self.runner:
            dialogs.ErrorDialog("Invalid game configuration: Missing runner")
            self.state = self.STATE_STOPPED
            return

        if not self.prelaunch():
            self.state = self.STATE_STOPPED
            return

        if hasattr(self.runner, 'prelaunch'):
            jobs.AsyncCall(self.runner.prelaunch, self.do_play)
        else:
            self.do_play(True)

    def do_play(self, prelaunched, _error=None):
        if not prelaunched:
            self.state = self.STATE_STOPPED
            return
        system_config = self.runner.system_config
        self.original_outputs = sorted(
            display.get_outputs(),
            key=lambda e: e[0] == system_config.get('display')
        )

        gameplay_info = self.runner.play()
        logger.debug("Launching %s: %s" % (self.name, gameplay_info))
        if 'error' in gameplay_info:
            self.show_error_message(gameplay_info)
            self.state = self.STATE_STOPPED
            return

        env = {}
        sdl_gamecontrollerconfig = system_config.get('sdl_gamecontrollerconfig')
        if sdl_gamecontrollerconfig:
            path = os.path.expanduser(sdl_gamecontrollerconfig)
            if os.path.exists(path):
                with open(path, "r") as f:
                    sdl_gamecontrollerconfig = f.read()
            env['SDL_GAMECONTROLLERCONFIG'] = sdl_gamecontrollerconfig

        sdl_video_fullscreen = system_config.get('sdl_video_fullscreen') or ''
        env['SDL_VIDEO_FULLSCREEN_DISPLAY'] = sdl_video_fullscreen

        restrict_to_display = system_config.get('display')
        if restrict_to_display != 'off':
            display.turn_off_except(restrict_to_display)
            time.sleep(3)
            self.resolution_changed = True

        resolution = system_config.get('resolution')
        if resolution != 'off':
            display.change_resolution(resolution)
            time.sleep(3)
            self.resolution_changed = True

        if system_config.get('reset_pulse'):
            audio.reset_pulse()

        self.killswitch = system_config.get('killswitch')
        if self.killswitch and not os.path.exists(self.killswitch):
            # Prevent setting a killswitch to a file that doesn't exists
            self.killswitch = None

        # Command
        launch_arguments = gameplay_info['command']

        optimus = system_config.get('optimus')
        if optimus == 'primusrun' and system.find_executable('primusrun'):
            launch_arguments.insert(0, 'primusrun')
        elif optimus == 'optirun' and system.find_executable('optirun'):
            launch_arguments.insert(0, 'virtualgl')
            launch_arguments.insert(0, '-b')
            launch_arguments.insert(0, 'optirun')

        xephyr = system_config.get('xephyr') or 'off'
        if xephyr != 'off':
            if xephyr == '8bpp':
                xephyr_depth = '8'
            else:
                xephyr_depth = '16'
            xephyr_resolution = system_config.get('xephyr_resolution') or '640x480'
            xephyr_command = ['Xephyr', ':2', '-ac', '-screen',
                              xephyr_resolution + 'x' + xephyr_depth, '-glamor',
                              '-reset', '-terminate', '-fullscreen']
            xephyr_thread = LutrisThread(xephyr_command)
            xephyr_thread.start()
            time.sleep(3)
            env['DISPLAY'] = ':2'

        if system_config.get('use_us_layout'):
            setxkbmap_command = ['setxkbmap', '-model', 'pc101', 'us', '-print']
            xkbcomp_command = ['xkbcomp', '-', os.environ.get('DISPLAY', ':0')]
            xkbcomp = subprocess.Popen(xkbcomp_command, stdin=subprocess.PIPE)
            subprocess.Popen(setxkbmap_command,
                             env=os.environ,
                             stdout=xkbcomp.stdin).communicate()
            xkbcomp.communicate()

        pulse_latency = system_config.get('pulse_latency')
        if pulse_latency:
            env['PULSE_LATENCY_MSEC'] = '60'

        prefix_command = system_config.get("prefix_command") or ''
        if prefix_command:
            launch_arguments = shlex.split(os.path.expandvars(prefix_command)) + launch_arguments

        single_cpu = system_config.get('single_cpu') or False
        if single_cpu:
            logger.info('The game will run on a single CPU core')
            launch_arguments.insert(0, '0')
            launch_arguments.insert(0, '-c')
            launch_arguments.insert(0, 'taskset')

        terminal = system_config.get('terminal')
        if terminal:
            terminal = system_config.get("terminal_app",
                                         system.get_default_terminal())
            if terminal and not system.find_executable(terminal):
                dialogs.ErrorDialog("The selected terminal application "
                                    "could not be launched:\n"
                                    "%s" % terminal)
                self.state = self.STATE_STOPPED
                return

        # Env vars
        game_env = gameplay_info.get('env') or self.runner.get_env()
        env.update(game_env)

        ld_preload = gameplay_info.get('ld_preload')
        if ld_preload:
            env["LD_PRELOAD"] = ld_preload

        game_ld_libary_path = gameplay_info.get('ld_library_path')
        if game_ld_libary_path:
            ld_library_path = env.get("LD_LIBRARY_PATH")
            if not ld_library_path:
                ld_library_path = '$LD_LIBRARY_PATH'
            ld_library_path = ":".join([game_ld_libary_path, ld_library_path])
            env["LD_LIBRARY_PATH"] = ld_library_path
        # /Env vars

        include_processes = shlex.split(system_config.get('include_processes', ''))
        exclude_processes = shlex.split(system_config.get('exclude_processes', ''))

        monitoring_disabled = system_config.get('disable_monitoring')
        if monitoring_disabled:
            show_obnoxious_process_monitor_message()
        process_watch = not monitoring_disabled

        if self.runner.system_config.get('disable_compositor'):
            self.desktop_effects(False)

        self.game_thread = LutrisThread(launch_arguments,
                                        runner=self.runner,
                                        env=env,
                                        rootpid=gameplay_info.get('rootpid'),
                                        watch=process_watch,
                                        term=terminal,
                                        log_buffer=self.log_buffer,
                                        include_processes=include_processes,
                                        exclude_processes=exclude_processes)
        if hasattr(self.runner, 'stop'):
            self.game_thread.set_stop_command(self.runner.stop)
        self.game_thread.start()
        self.state = self.STATE_RUNNING

        # xboxdrv setup
        xboxdrv_config = system_config.get('xboxdrv')
        if xboxdrv_config:
            self.xboxdrv_start(xboxdrv_config)

        if monitoring_disabled:
            logger.info("Process monitoring disabled")
        else:
            self.heartbeat = GLib.timeout_add(HEARTBEAT_DELAY, self.beat)

    def xboxdrv_start(self, config):
        command = [
            "pkexec", "xboxdrv", "--daemon", "--detach-kernel-driver",
            "--dbus", "session", "--silent"
        ] + config.split()
        logger.debug("[xboxdrv] %s", ' '.join(command))
        self.xboxdrv_thread = LutrisThread(command, include_processes=['xboxdrv'])
        self.xboxdrv_thread.set_stop_command(self.xboxdrv_stop)
        self.xboxdrv_thread.start()

    def xboxdrv_stop(self):
        os.system("pkexec xboxdrvctl --shutdown")
        if os.path.exists("/usr/share/lutris/bin/resetxpad"):
            os.system("pkexec /usr/share/lutris/bin/resetxpad")

    def beat(self):
        """Watch the game's process(es)."""
        if self.game_thread.error:
            dialogs.ErrorDialog("<b>Error lauching the game:</b>\n" +
                                self.game_thread.error)
            self.on_game_quit()
            return False

        # The killswitch file should be set to a device (ie. /dev/input/js0)
        # When that device is unplugged, the game is forced to quit.
        killswitch_engage = (
            self.killswitch and not os.path.exists(self.killswitch)
        )
        if not self.game_thread.is_running or killswitch_engage:
            logger.debug("Game thread stopped")
            self.on_game_quit()
            return False
        return True

    def stop(self):
        if self.runner.system_config.get('xboxdrv'):
            logger.debug("Stopping xboxdrv")
            self.xboxdrv_thread.stop()
        if self.game_thread:
            jobs.AsyncCall(self.game_thread.stop, None, killall=self.runner.killall_on_exit())
        self.state = self.STATE_STOPPED

    def on_game_quit(self):
        """Restore some settings and cleanup after game quit."""
        self.heartbeat = None
        if self.state != self.STATE_STOPPED:
            logger.debug("Game thread still running, stopping it (state: %s)", self.state)
            self.stop()
        quit_time = time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime())
        logger.debug("%s stopped at %s", self.name, quit_time)
        self.lastplayed = int(time.time())
        self.save(metadata_only=True)

        os.chdir(os.path.expanduser('~'))

        if self.resolution_changed\
           or self.runner.system_config.get('reset_desktop'):
            display.change_resolution(self.original_outputs)

        if self.compositor_disabled:
            self.desktop_effects(True)

        if self.runner.system_config.get('use_us_layout'):
            subprocess.Popen(['setxkbmap'], env=os.environ).communicate()

        if self.runner.system_config.get('restore_gamma'):
            display.restore_gamma()

        self.process_return_codes()

        if self.exit_main_loop:
            exit()

    def process_return_codes(self):
        """Do things depending on how the game quitted."""
        if self.game_thread.return_code == 127:
            # Error missing shared lib
            error = "error while loading shared lib"
            error_line = strings.lookup_string_in_text(error,
                                                       self.game_thread.stdout)
            if error_line:
                dialogs.ErrorDialog("<b>Error: Missing shared library.</b>"
                                    "\n\n%s" % error_line)

        if self.game_thread.return_code == 1:
            # Error Wine version conflict
            error = "maybe the wrong wineserver"
            if strings.lookup_string_in_text(error, self.game_thread.stdout):
                dialogs.ErrorDialog("<b>Error: A different Wine version is "
                                    "already using the same Wine prefix.</b>")

    def notify_steam_game_changed(self, appmanifest):
        """Receive updates from Steam games and set the thread's ready state accordingly"""
        if 'Fully Installed' in appmanifest.states and not self.game_thread.ready_state:
            logger.info("Steam game %s is fully installed", appmanifest.steamid)
            self.game_thread.ready_state = True
        elif 'Update Required' in appmanifest.states and self.game_thread.ready_state:
            logger.info("Steam game %s updating, setting game thread as not ready",
                        appmanifest.steamid)
            self.game_thread.ready_state = False
Beispiel #38
0
class Game(object):
    """This class takes cares of loading the configuration for a game
       and running it.
    """
    STATE_IDLE = 'idle'
    STATE_STOPPED = 'stopped'
    STATE_RUNNING = 'running'

    def __init__(self, id=None):
        self.id = id
        self.runner = None
        self.game_thread = None
        self.heartbeat = None
        self.config = None
        self.killswitch = None
        self.state = self.STATE_IDLE
        self.game_log = ''
        self.exit_main_loop = False

        game_data = pga.get_game_by_field(id, 'id')
        self.slug = game_data.get('slug') or ''
        self.runner_name = game_data.get('runner') or ''
        self.directory = game_data.get('directory') or ''
        self.name = game_data.get('name') or ''
        self.is_installed = bool(game_data.get('installed')) or False
        self.platform = game_data.get('platform') or ''
        self.year = game_data.get('year') or ''
        self.game_config_id = game_data.get('configpath') or ''
        self.steamid = game_data.get('steamid') or ''
        self.has_custom_banner = bool(game_data.get('has_custom_banner')) or False
        self.has_custom_icon = bool(game_data.get('has_custom_icon')) or False

        self.load_config()
        self.resolution_changed = False
        self.original_outputs = None

    def __repr__(self):
        return self.__unicode__()

    def __unicode__(self):
        value = self.name
        if self.runner_name:
            value += " (%s)" % self.runner_name
        return value

    def show_error_message(self, message):
        """Display an error message based on the runner's output."""
        if "CUSTOM" == message['error']:
            message_text = message['text'].replace('&', '&amp;')
            dialogs.ErrorDialog(message_text)
        elif "RUNNER_NOT_INSTALLED" == message['error']:
            dialogs.ErrorDialog('Error the runner is not installed')
        elif "NO_BIOS" == message['error']:
            dialogs.ErrorDialog("A bios file is required to run this game")
        elif "FILE_NOT_FOUND" == message['error']:
            filename = message['file']
            if filename:
                message_text = "The file {} could not be found".format(
                    filename.replace('&', '&amp;')
                )
            else:
                message_text = "No file provided"
            dialogs.ErrorDialog(message_text)

        elif "NOT_EXECUTABLE" == message['error']:
            message_text = message['file'].replace('&', '&amp;')
            dialogs.ErrorDialog("The file %s is not executable" % message_text)

    def get_browse_dir(self):
        """Return the path to open with the Browse Files action."""
        return self.runner.browse_dir

    def load_config(self):
        """Load the game's configuration."""
        self.config = LutrisConfig(runner_slug=self.runner_name,
                                   game_config_id=self.game_config_id)
        if not self.is_installed:
            return
        if not self.runner_name:
            logger.error('Incomplete data for %s', self.slug)
            return
        try:
            runner_class = import_runner(self.runner_name)
        except InvalidRunner:
            logger.error("Unable to import runner %s for %s",
                         self.runner_name, self.slug)
        else:
            self.runner = runner_class(self.config)

    def remove(self, from_library=False, from_disk=False):
        if from_disk and self.runner:
            logger.debug("Removing game %s from disk" % self.id)
            self.runner.remove_game_data(game_path=self.directory)

        # Do not keep multiple copies of the same game
        existing_games = pga.get_game_by_field(self.slug, 'slug', all=True)
        if len(existing_games) > 1:
            from_library = True

        if from_library:
            logger.debug("Removing game %s from library" % self.id)
            pga.delete_game(self.id)
        else:
            pga.set_uninstalled(self.id)
        self.config.remove()
        shortcuts.remove_launcher(self.slug, self.id, desktop=True, menu=True)
        return from_library

    def save(self):
        self.config.save()
        self.id = pga.add_or_update(
            name=self.name,
            runner=self.runner_name,
            slug=self.slug,
            platform=self.platform,
            year=self.year,
            directory=self.directory,
            installed=self.is_installed,
            configpath=self.config.game_config_id,
            steamid=self.steamid,
            id=self.id
        )

    def prelaunch(self):
        """Verify that the current game can be launched."""
        if not self.runner.is_installed():
            installed = self.runner.install_dialog()
            if not installed:
                return False

        if self.runner.use_runtime():
            runtime_updater = runtime.RuntimeUpdater()
            if runtime_updater.is_updating():
                logger.warning("Runtime updates: {}".format(
                    runtime_updater.current_updates)
                )
                dialogs.ErrorDialog("Runtime currently updating",
                                    "Game might not work as expected")
        return True

    def play(self):
        """Launch the game."""
        if not self.runner:
            dialogs.ErrorDialog("Invalid game configuration: Missing runner")
            self.state = self.STATE_STOPPED
            return

        if not self.prelaunch():
            self.state = self.STATE_STOPPED
            return

        if hasattr(self.runner, 'prelaunch'):
            jobs.AsyncCall(self.runner.prelaunch, self.do_play)
        else:
            self.do_play(True)

    def do_play(self, prelaunched, _error=None):
        if not prelaunched:
            self.state = self.STATE_STOPPED
            return
        system_config = self.runner.system_config
        self.original_outputs = display.get_outputs()
        gameplay_info = self.runner.play()

        env = {}

        logger.debug("Launching %s: %s" % (self.name, gameplay_info))
        if 'error' in gameplay_info:
            self.show_error_message(gameplay_info)
            self.state = self.STATE_STOPPED
            return

        sdl_video_fullscreen = system_config.get('sdl_video_fullscreen') or ''
        env['SDL_VIDEO_FULLSCREEN_DISPLAY'] = sdl_video_fullscreen

        restrict_to_display = system_config.get('display')
        if restrict_to_display != 'off':
            display.turn_off_except(restrict_to_display)
            time.sleep(3)
            self.resolution_changed = True

        resolution = system_config.get('resolution')
        if resolution != 'off':
            display.change_resolution(resolution)
            time.sleep(3)
            self.resolution_changed = True

        if system_config.get('reset_pulse'):
            audio.reset_pulse()

        self.killswitch = system_config.get('killswitch')
        if self.killswitch and not os.path.exists(self.killswitch):
            # Prevent setting a killswitch to a file that doesn't exists
            self.killswitch = None

        # Command
        launch_arguments = gameplay_info['command']

        primusrun = system_config.get('primusrun')
        if primusrun and system.find_executable('primusrun'):
            launch_arguments.insert(0, 'primusrun')

        xephyr = system_config.get('xephyr') or 'off'
        if xephyr != 'off':
            if xephyr == '8bpp':
                xephyr_depth = '8'
            else:
                xephyr_depth = '16'
            xephyr_resolution = system_config.get('xephyr_resolution') or '640x480'
            xephyr_command = ['Xephyr', ':2', '-ac', '-screen',
                              xephyr_resolution + 'x' + xephyr_depth, '-glamor',
                              '-reset', '-terminate', '-fullscreen']
            xephyr_thread = LutrisThread(xephyr_command)
            xephyr_thread.start()
            time.sleep(3)
            env['DISPLAY'] = ':2'

        if system_config.get('use_us_layout'):
            setxkbmap_command = ['setxkbmap', '-model', 'pc101', 'us', '-print']
            xkbcomp_command = ['xkbcomp', '-', os.environ.get('DISPLAY', ':0')]
            xkbcomp = subprocess.Popen(xkbcomp_command, stdin=subprocess.PIPE)
            subprocess.Popen(setxkbmap_command,
                             env=os.environ,
                             stdout=xkbcomp.stdin).communicate()
            xkbcomp.communicate()

        pulse_latency = system_config.get('pulse_latency')
        if pulse_latency:
            env['PULSE_LATENCY_MSEC'] = '60'

        prefix_command = system_config.get("prefix_command") or ''
        if prefix_command.strip():
            launch_arguments.insert(0, prefix_command)

        single_cpu = system_config.get('single_cpu') or False
        if single_cpu:
            logger.info('The game will run on a single CPU core')
            launch_arguments.insert(0, '0')
            launch_arguments.insert(0, '-c')
            launch_arguments.insert(0, 'taskset')

        terminal = system_config.get('terminal')
        if terminal:
            terminal = system_config.get("terminal_app",
                                         system.get_default_terminal())
            if terminal and not system.find_executable(terminal):
                dialogs.ErrorDialog("The selected terminal application "
                                    "could not be launched:\n"
                                    "%s" % terminal)
                self.state = self.STATE_STOPPED
                return
        # Env vars
        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') or ''
        env["LD_PRELOAD"] = ld_preload

        # Runtime management
        ld_library_path = ""
        if self.runner.use_runtime():
            runtime_env = runtime.get_env()
            if 'STEAM_RUNTIME' in runtime_env and 'STEAM_RUNTIME' not in env:
                env['STEAM_RUNTIME'] = runtime_env['STEAM_RUNTIME']
            if 'LD_LIBRARY_PATH' in runtime_env:
                ld_library_path = runtime_env['LD_LIBRARY_PATH']
        game_ld_libary_path = gameplay_info.get('ld_library_path')
        if game_ld_libary_path:
            if not ld_library_path:
                ld_library_path = '$LD_LIBRARY_PATH'
            ld_library_path = ":".join([game_ld_libary_path, ld_library_path])
        env["LD_LIBRARY_PATH"] = ld_library_path

        # /Env vars

        self.game_thread = LutrisThread(launch_arguments,
                                        runner=self.runner, env=env,
                                        rootpid=gameplay_info.get('rootpid'),
                                        term=terminal)
        if hasattr(self.runner, 'stop'):
            self.game_thread.set_stop_command(self.runner.stop)
        self.game_thread.start()
        self.state = self.STATE_RUNNING
        if 'joy2key' in gameplay_info:
            self.joy2key(gameplay_info['joy2key'])
        xboxdrv_config = system_config.get('xboxdrv')
        if xboxdrv_config:
            self.xboxdrv_start(xboxdrv_config)
        self.heartbeat = GLib.timeout_add(HEARTBEAT_DELAY, self.beat)

    def joy2key(self, config):
        """Run a joy2key thread."""
        if not system.find_executable('joy2key'):
            logger.error("joy2key is not installed")
            return
        win = "grep %s" % config['window']
        if 'notwindow' in config:
            win += ' | grep -v %s' % config['notwindow']
        wid = "xwininfo -root -tree | %s | awk '{print $1}'" % win
        buttons = config['buttons']
        axis = "Left Right Up Down"
        rcfile = os.path.expanduser("~/.joy2keyrc")
        rc_option = '-rcfile %s' % rcfile if os.path.exists(rcfile) else ''
        command = "sleep 5 "
        command += "&& joy2key $(%s) -X %s -buttons %s -axis %s" % (
            wid, rc_option, buttons, axis
        )
        joy2key_thread = LutrisThread(command)
        self.game_thread.attach_thread(joy2key_thread)
        joy2key_thread.start()

    def xboxdrv_start(self, config):
        command = [
            "pkexec", "xboxdrv", "--daemon", "--detach-kernel-driver",
            "--dbus", "session", "--silent"
        ] + config.split()
        logger.debug("[xboxdrv] %s", ' '.join(command))
        self.xboxdrv_thread = LutrisThread(command)
        self.xboxdrv_thread.set_stop_command(self.xboxdrv_stop)
        self.xboxdrv_thread.start()

    def xboxdrv_stop(self):
        os.system("pkexec xboxdrvctl --shutdown")
        if os.path.exists("/usr/share/lutris/bin/resetxpad"):
            os.system("pkexec /usr/share/lutris/bin/resetxpad")

    def beat(self):
        """Watch the game's process(es)."""
        if self.game_thread.error:
            dialogs.ErrorDialog("<b>Error lauching the game:</b>\n" +
                                self.game_thread.error)
            self.on_game_quit()
            return False
        self.game_log = self.game_thread.stdout
        killswitch_engage = self.killswitch and \
            not os.path.exists(self.killswitch)
        if not self.game_thread.is_running or killswitch_engage:
            logger.debug("Game thread stopped")
            self.on_game_quit()
            return False
        return True

    def stop(self):
        self.state = self.STATE_STOPPED
        if self.runner.system_config.get('xboxdrv'):
            self.xboxdrv_thread.stop()
        if self.game_thread:
            jobs.AsyncCall(self.game_thread.stop, None, killall=True)

    def on_game_quit(self):
        """Restore some settings and cleanup after game quit."""
        self.heartbeat = None
        quit_time = time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime())
        logger.debug("%s stopped at %s", self.name, quit_time)
        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)

        if self.runner.system_config.get('use_us_layout'):
            subprocess.Popen(['setxkbmap'], env=os.environ).communicate()

        if self.runner.system_config.get('restore_gamma'):
            display.restore_gamma()

        if self.runner.system_config.get('xboxdrv') \
           and self.xboxdrv_thread.is_running:
            self.xboxdrv_thread.stop()

        if self.game_thread:
            self.game_thread.stop()
        self.process_return_codes()

        if self.exit_main_loop:
            exit()

    def process_return_codes(self):
        """Do things depending on how the game quitted."""
        if self.game_thread.return_code == 127:
            # Error missing shared lib
            error = "error while loading shared lib"
            error_line = strings.lookup_string_in_text(error,
                                                       self.game_thread.stdout)
            if error_line:
                dialogs.ErrorDialog("<b>Error: Missing shared library.</b>"
                                    "\n\n%s" % error_line)

        if self.game_thread.return_code == 1:
            # Error Wine version conflict
            error = "maybe the wrong wineserver"
            if strings.lookup_string_in_text(error, self.game_thread.stdout):
                dialogs.ErrorDialog("<b>Error: A different Wine version is "
                                    "already using the same Wine prefix.</b>")
Beispiel #39
0
def wineexec(executable, args="", wine_path=None, prefix=None, arch=None,
             working_dir=None, winetricks_wine='', blocking=False,
             config=None, include_processes=[]):
    """
    Execute a Wine command.

    Args:
        executable (str): wine program to run, pass None to run wine itself
        args (str): program arguments
        wine_path (str): path to the wine version to use
        prefix (str): path to the wine prefix to use
        arch (str): wine architecture of the prefix
        working_dir (str): path to the working dir for the process
        winetricks_wine (str): path to the wine version used by winetricks
        blocking (bool): if true, do not run the process in a thread
        config (LutrisConfig): LutrisConfig object for the process context
        watch (list): list of process names to monitor (even when in a ignore list)

    Returns:
        Process results if the process is running in blocking mode or
        LutrisThread instance otherwise.
    """
    detected_arch = detect_prefix_arch(prefix)
    executable = str(executable) if executable else ''
    if arch not in ('win32', 'win64'):
        arch = detected_arch or 'win32'
    if not wine_path:
        wine_path = wine().get_executable()
    if not working_dir:
        if os.path.isfile(executable):
            working_dir = os.path.dirname(executable)

    if executable.endswith(".msi"):
        args = '/i "%s"' % executable
        executable = 'msiexec'

    # Create prefix if necessary
    if not detected_arch:
        wine_bin = winetricks_wine if winetricks_wine else wine_path
        create_prefix(prefix, wine_path=wine_bin, arch=arch)

    env = {
        'WINEARCH': arch
    }
    if winetricks_wine:
        env['WINE'] = winetricks_wine
    else:
        env['WINE'] = wine_path
    if prefix:
        env['WINEPREFIX'] = prefix

    wine_config = config or LutrisConfig(runner_slug='wine')
    if not wine_config.system_config['disable_runtime'] and not runtime.is_disabled():
        env['LD_LIBRARY_PATH'] = ':'.join(runtime.get_paths())

    command = [wine_path]
    if executable:
        command.append(executable)
    command += shlex.split(args)
    if blocking:
        return system.execute(command, env=env, cwd=working_dir)
    else:
        thread = LutrisThread(command, runner=wine(), env=env, cwd=working_dir,
                              include_processes=include_processes)
        thread.start()
        return thread
Beispiel #40
0
    def execute(self, data):
        """Run an executable file."""
        args = []
        terminal = None
        working_dir = None
        env = {}
        if isinstance(data, dict):
            self._check_required_params([('file', 'command')], data, 'execute')
            if 'command' in data and 'file' in data:
                raise ScriptingError(
                    'Parameters file and command can\'t be used '
                    'at the same time for the execute command', data)
            file_ref = data.get('file', '')
            command = data.get('command', '')
            args_string = data.get('args', '')
            for arg in shlex.split(args_string):
                args.append(self._substitute(arg))
            terminal = data.get('terminal')
            working_dir = data.get('working_dir')
            if not data.get('disable_runtime', False):
                # Possibly need to handle prefer_system_libs here
                env.update(runtime.get_env())
            userenv = data.get('env') or {}
            for key in userenv:
                env_value = userenv[key]
                userenv[key] = self._get_file(env_value)
            env.update(userenv)
            include_processes = shlex.split(data.get('include_processes', ''))
            exclude_processes = shlex.split(data.get('exclude_processes', ''))
        elif isinstance(data, str):
            command = data
            include_processes = []
            exclude_processes = []
        else:
            raise ScriptingError('No parameters supplied to execute command.',
                                 data)

        if command:
            file_ref = 'bash'
            args = ['-c', self._get_file(command.strip())]
            include_processes.append('bash')
        else:
            # Determine whether 'file' value is a file id or a path
            file_ref = self._get_file(file_ref)

        exec_path = system.find_executable(file_ref)
        if not exec_path:
            raise ScriptingError("Unable to find executable %s" % file_ref)
        if not os.access(exec_path, os.X_OK):
            logger.warning("Making %s executable", exec_path)
            self.chmodx(exec_path)

        if terminal:
            terminal = system.get_default_terminal()

        if not working_dir or not os.path.exists(working_dir):
            working_dir = self.target_path

        command = [exec_path] + args
        logger.debug("Executing %s", command)
        thread = LutrisThread(command,
                              env=env,
                              term=terminal,
                              cwd=working_dir,
                              include_processes=include_processes,
                              exclude_processes=exclude_processes)
        thread.start()
        GLib.idle_add(self.parent.attach_logger, thread)
        self.heartbeat = GLib.timeout_add(1000, self._monitor_task, thread)
        return 'STOP'
Beispiel #41
0
    def execute(self, data):
        """Run an executable file."""
        args = []
        terminal = None
        working_dir = None
        env = {}
        if isinstance(data, dict):
            if 'command' not in data and 'file' not in data:
                raise ScriptingError('Parameter file or command is mandatory '
                                     'for the execute command', data)
            elif 'command' in data and 'file' in data:
                raise ScriptingError('Parameters file and command can\'t be '
                                     'used at the same time for the execute '
                                     'command', data)
            file_ref = data.get('file', '')
            command = data.get('command', '')
            args_string = data.get('args', '')
            for arg in shlex.split(args_string):
                args.append(self._substitute(arg))
            terminal = data.get('terminal')
            working_dir = data.get('working_dir')
            if not data.get('disable_runtime', False):
                env.update(runtime.get_env())
            userenv = data.get('env', {})
            for key in userenv:
                v = userenv[key]
                userenv[key] = self._get_file(v) or self._substitute(v)
            env.update(userenv)
            include_processes = shlex.split(data.get('include_processes', ''))
            exclude_processes = shlex.split(data.get('exclude_processes', ''))
        elif isinstance(data, str):
            command = data
            include_processes = []
            exclude_processes = []
        else:
            raise ScriptingError('No parameters supplied to execute command.', data)

        if command:
            command = command.strip()
            command = self._get_file(command) or self._substitute(command)
            file_ref = 'bash'
            args = ['-c', command]
            include_processes.append('bash')
        else:
            # Determine whether 'file' value is a file id or a path
            file_ref = self._get_file(file_ref) or self._substitute(file_ref)

        exec_path = system.find_executable(file_ref)
        if not exec_path:
            raise ScriptingError("Unable to find executable %s" % file_ref)
        if not os.access(exec_path, os.X_OK):
            self.chmodx(exec_path)

        if terminal:
            terminal = system.get_default_terminal()

        if not working_dir or not os.path.exists(working_dir):
            working_dir = self.target_path

        command = [exec_path] + args
        logger.debug("Executing %s" % command)
        thread = LutrisThread(command, env=env, term=terminal, cwd=working_dir,
                              include_processes=include_processes,
                              exclude_processes=exclude_processes)
        thread.start()
        GLib.idle_add(self.parent.attach_logger, thread)
        self.heartbeat = GLib.timeout_add(1000, self._monitor_task, thread)
        return 'STOP'
Beispiel #42
0
    def do_play(self, prelaunched, _error=None):
        if not prelaunched:
            self.state = self.STATE_STOPPED
            return
        system_config = self.runner.system_config
        self.original_outputs = display.get_outputs()
        gameplay_info = self.runner.play()

        env = {}

        logger.debug("Launching %s: %s" % (self.name, gameplay_info))
        if 'error' in gameplay_info:
            show_error_message(gameplay_info)
            self.state = self.STATE_STOPPED
            return

        sdl_video_fullscreen = system_config.get('sdl_video_fullscreen') or ''
        env['SDL_VIDEO_FULLSCREEN_DISPLAY'] = sdl_video_fullscreen

        restrict_to_display = system_config.get('display')
        if restrict_to_display != 'off':
            display.turn_off_except(restrict_to_display)
            time.sleep(3)
            self.resolution_changed = True

        resolution = system_config.get('resolution')
        if resolution != 'off':
            display.change_resolution(resolution)
            time.sleep(3)
            self.resolution_changed = True

        if system_config.get('reset_pulse'):
            audio.reset_pulse()

        self.killswitch = system_config.get('killswitch')
        if self.killswitch and not os.path.exists(self.killswitch):
            # Prevent setting a killswitch to a file that doesn't exists
            self.killswitch = None

        # Command
        launch_arguments = gameplay_info['command']

        primusrun = system_config.get('primusrun')
        if primusrun and system.find_executable('primusrun'):
            launch_arguments.insert(0, 'primusrun')

        xephyr = system_config.get('xephyr') or 'off'
        if xephyr != 'off':
            if xephyr == '8bpp':
                xephyr_depth = '8'
            else:
                xephyr_depth = '16'
            xephyr_resolution = system_config.get('xephyr_resolution') or '640x480'
            xephyr_command = ['Xephyr', ':2', '-ac', '-screen',
                              xephyr_resolution + 'x' + xephyr_depth, '-glamor',
                              '-reset', '-terminate', '-fullscreen']
            xephyr_thread = LutrisThread(xephyr_command)
            xephyr_thread.start()
            time.sleep(3)
            env['DISPLAY'] = ':2'

        if system_config.get('use_us_layout'):
            setxkbmap_command = ['setxkbmap', '-model', 'pc101', 'us', '-print']
            xkbcomp_command = ['xkbcomp', '-', os.environ.get('DISPLAY', ':0')]
            xkbcomp = subprocess.Popen(xkbcomp_command, stdin=subprocess.PIPE)
            subprocess.Popen(setxkbmap_command,
                             env=os.environ,
                             stdout=xkbcomp.stdin).communicate()
            xkbcomp.communicate()

        pulse_latency = system_config.get('pulse_latency')
        if pulse_latency:
            env['PULSE_LATENCY_MSEC'] = '60'

        prefix_command = system_config.get("prefix_command") or ''
        if prefix_command.strip():
            launch_arguments.insert(0, prefix_command)

        single_cpu = system_config.get('single_cpu') or False
        if single_cpu:
            logger.info('The game will run on a single CPU core')
            launch_arguments.insert(0, '0')
            launch_arguments.insert(0, '-c')
            launch_arguments.insert(0, 'taskset')

        terminal = system_config.get('terminal')
        if terminal:
            terminal = system_config.get("terminal_app",
                                         system.get_default_terminal())
            if terminal and not system.find_executable(terminal):
                dialogs.ErrorDialog("The selected terminal application "
                                    "could not be launched:\n"
                                    "%s" % terminal)
                self.state = self.STATE_STOPPED
                return
        # Env vars
        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') or ''
        env["LD_PRELOAD"] = ld_preload

        # Runtime management
        ld_library_path = ""
        if self.runner.use_runtime():
            runtime_env = runtime.get_env()
            if 'STEAM_RUNTIME' in runtime_env and 'STEAM_RUNTIME' not in env:
                env['STEAM_RUNTIME'] = runtime_env['STEAM_RUNTIME']
            if 'LD_LIBRARY_PATH' in runtime_env:
                ld_library_path = runtime_env['LD_LIBRARY_PATH']
        game_ld_libary_path = gameplay_info.get('ld_library_path')
        if game_ld_libary_path:
            if not ld_library_path:
                ld_library_path = '$LD_LIBRARY_PATH'
            ld_library_path = ":".join(game_ld_libary_path, ld_library_path)
        env["LD_LIBRARY_PATH"] = ld_library_path

        # /Env vars

        self.game_thread = LutrisThread(launch_arguments,
                                        runner=self.runner, env=env,
                                        rootpid=gameplay_info.get('rootpid'),
                                        term=terminal)
        if hasattr(self.runner, 'stop'):
            self.game_thread.set_stop_command(self.runner.stop)
        self.game_thread.start()
        self.state = self.STATE_RUNNING
        if 'joy2key' in gameplay_info:
            self.joy2key(gameplay_info['joy2key'])
        xboxdrv_config = system_config.get('xboxdrv')
        if xboxdrv_config:
            self.xboxdrv_start(xboxdrv_config)
        self.heartbeat = GLib.timeout_add(HEARTBEAT_DELAY, self.beat)
Beispiel #43
0
class Game(object):
    """This class takes cares of loading the configuration for a game
       and running it.
    """
    STATE_IDLE = 'idle'
    STATE_STOPPED = 'stopped'
    STATE_RUNNING = 'running'

    def __init__(self, id=None):
        self.id = id
        self.runner = None
        self.game_thread = None
        self.heartbeat = None
        self.config = None
        self.killswitch = None
        self.state = self.STATE_IDLE
        self.game_log = ''
        self.exit_main_loop = False

        game_data = pga.get_game_by_field(id, 'id')
        self.slug = game_data.get('slug') or ''
        self.runner_name = game_data.get('runner') or ''
        self.directory = game_data.get('directory') or ''
        self.name = game_data.get('name') or ''
        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.has_custom_banner = bool(game_data.get('has_custom_banner')) or False
        self.has_custom_icon = bool(game_data.get('has_custom_icon')) or False

        self.load_config()
        self.resolution_changed = False
        self.original_outputs = None

    def __repr__(self):
        return self.__unicode__()

    def __unicode__(self):
        value = self.name
        if self.runner_name:
            value += " (%s)" % self.runner_name
        return value

    def get_browse_dir(self):
        """Return the path to open with the Browse Files action."""
        return self.runner.browse_dir

    def load_config(self):
        """Load the game's configuration."""
        self.config = LutrisConfig(runner_slug=self.runner_name,
                                   game_config_id=self.game_config_id)
        if not self.is_installed:
            return
        if not self.runner_name:
            logger.error('Incomplete data for %s', self.slug)
            return
        try:
            runner_class = import_runner(self.runner_name)
        except InvalidRunner:
            logger.error("Unable to import runner %s for %s",
                         self.runner_name, self.slug)
        else:
            self.runner = runner_class(self.config)

    def remove(self, from_library=False, from_disk=False):
        if from_disk and self.runner:
            logger.debug("Removing game %s from disk" % self.id)
            self.runner.remove_game_data(game_path=self.directory)

        # Do not keep multiple copies of the same game
        existing_games = pga.get_game_by_field(self.slug, 'slug', all=True)
        if len(existing_games) > 1:
            from_library = True

        if from_library:
            logger.debug("Removing game %s from library" % self.id)
            pga.delete_game(self.id)
        else:
            pga.set_uninstalled(self.id)
        self.config.remove()
        shortcuts.remove_launcher(self.slug, self.id, desktop=True, menu=True)
        return from_library

    def save(self):
        self.config.save()
        self.id = pga.add_or_update(
            name=self.name,
            runner=self.runner_name,
            slug=self.slug,
            directory=self.directory,
            installed=self.is_installed,
            configpath=self.config.game_config_id,
            steamid=self.steamid,
            id=self.id
        )

    def prelaunch(self):
        """Verify that the current game can be launched."""
        if not self.runner.is_installed():
            installed = self.runner.install_dialog()
            if not installed:
                return False

        if self.runner.use_runtime():
            runtime_updater = runtime.RuntimeUpdater()
            if runtime_updater.is_updating():
                logger.warning("Runtime updates: {}".format(
                    runtime_updater.current_updates)
                )
                dialogs.ErrorDialog("Runtime currently updating",
                                    "Game might not work as expected")
        return True

    def play(self):
        """Launch the game."""
        if not self.runner:
            dialogs.ErrorDialog("Invalid game configuration: Missing runner")
            self.state = self.STATE_STOPPED
            return

        if not self.prelaunch():
            self.state = self.STATE_STOPPED
            return

        if hasattr(self.runner, 'prelaunch'):
            jobs.AsyncCall(self.runner.prelaunch, self.do_play)
        else:
            self.do_play(True)

    def do_play(self, prelaunched, _error=None):
        if not prelaunched:
            self.state = self.STATE_STOPPED
            return
        system_config = self.runner.system_config
        self.original_outputs = display.get_outputs()
        gameplay_info = self.runner.play()

        env = {}

        logger.debug("Launching %s: %s" % (self.name, gameplay_info))
        if 'error' in gameplay_info:
            show_error_message(gameplay_info)
            self.state = self.STATE_STOPPED
            return

        sdl_video_fullscreen = system_config.get('sdl_video_fullscreen') or ''
        env['SDL_VIDEO_FULLSCREEN_DISPLAY'] = sdl_video_fullscreen

        restrict_to_display = system_config.get('display')
        if restrict_to_display != 'off':
            display.turn_off_except(restrict_to_display)
            time.sleep(3)
            self.resolution_changed = True

        resolution = system_config.get('resolution')
        if resolution != 'off':
            display.change_resolution(resolution)
            time.sleep(3)
            self.resolution_changed = True

        if system_config.get('reset_pulse'):
            audio.reset_pulse()

        self.killswitch = system_config.get('killswitch')
        if self.killswitch and not os.path.exists(self.killswitch):
            # Prevent setting a killswitch to a file that doesn't exists
            self.killswitch = None

        # Command
        launch_arguments = gameplay_info['command']

        primusrun = system_config.get('primusrun')
        if primusrun and system.find_executable('primusrun'):
            launch_arguments.insert(0, 'primusrun')

        xephyr = system_config.get('xephyr') or 'off'
        if xephyr != 'off':
            if xephyr == '8bpp':
                xephyr_depth = '8'
            else:
                xephyr_depth = '16'
            xephyr_resolution = system_config.get('xephyr_resolution') or '640x480'
            xephyr_command = ['Xephyr', ':2', '-ac', '-screen',
                              xephyr_resolution + 'x' + xephyr_depth, '-glamor',
                              '-reset', '-terminate', '-fullscreen']
            xephyr_thread = LutrisThread(xephyr_command)
            xephyr_thread.start()
            time.sleep(3)
            env['DISPLAY'] = ':2'

        if system_config.get('use_us_layout'):
            setxkbmap_command = ['setxkbmap', '-model', 'pc101', 'us', '-print']
            xkbcomp_command = ['xkbcomp', '-', os.environ.get('DISPLAY', ':0')]
            xkbcomp = subprocess.Popen(xkbcomp_command, stdin=subprocess.PIPE)
            subprocess.Popen(setxkbmap_command,
                             env=os.environ,
                             stdout=xkbcomp.stdin).communicate()
            xkbcomp.communicate()

        pulse_latency = system_config.get('pulse_latency')
        if pulse_latency:
            env['PULSE_LATENCY_MSEC'] = '60'

        prefix_command = system_config.get("prefix_command") or ''
        if prefix_command.strip():
            launch_arguments.insert(0, prefix_command)

        single_cpu = system_config.get('single_cpu') or False
        if single_cpu:
            logger.info('The game will run on a single CPU core')
            launch_arguments.insert(0, '0')
            launch_arguments.insert(0, '-c')
            launch_arguments.insert(0, 'taskset')

        terminal = system_config.get('terminal')
        if terminal:
            terminal = system_config.get("terminal_app",
                                         system.get_default_terminal())
            if terminal and not system.find_executable(terminal):
                dialogs.ErrorDialog("The selected terminal application "
                                    "could not be launched:\n"
                                    "%s" % terminal)
                self.state = self.STATE_STOPPED
                return
        # Env vars
        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') or ''
        env["LD_PRELOAD"] = ld_preload

        # Runtime management
        ld_library_path = ""
        if self.runner.use_runtime():
            runtime_env = runtime.get_env()
            if 'STEAM_RUNTIME' in runtime_env and 'STEAM_RUNTIME' not in env:
                env['STEAM_RUNTIME'] = runtime_env['STEAM_RUNTIME']
            if 'LD_LIBRARY_PATH' in runtime_env:
                ld_library_path = runtime_env['LD_LIBRARY_PATH']
        game_ld_libary_path = gameplay_info.get('ld_library_path')
        if game_ld_libary_path:
            if not ld_library_path:
                ld_library_path = '$LD_LIBRARY_PATH'
            ld_library_path = ":".join(game_ld_libary_path, ld_library_path)
        env["LD_LIBRARY_PATH"] = ld_library_path

        # /Env vars

        self.game_thread = LutrisThread(launch_arguments,
                                        runner=self.runner, env=env,
                                        rootpid=gameplay_info.get('rootpid'),
                                        term=terminal)
        if hasattr(self.runner, 'stop'):
            self.game_thread.set_stop_command(self.runner.stop)
        self.game_thread.start()
        self.state = self.STATE_RUNNING
        if 'joy2key' in gameplay_info:
            self.joy2key(gameplay_info['joy2key'])
        xboxdrv_config = system_config.get('xboxdrv')
        if xboxdrv_config:
            self.xboxdrv_start(xboxdrv_config)
        self.heartbeat = GLib.timeout_add(HEARTBEAT_DELAY, self.beat)

    def joy2key(self, config):
        """Run a joy2key thread."""
        if not system.find_executable('joy2key'):
            logger.error("joy2key is not installed")
            return
        win = "grep %s" % config['window']
        if 'notwindow' in config:
            win += ' | grep -v %s' % config['notwindow']
        wid = "xwininfo -root -tree | %s | awk '{print $1}'" % win
        buttons = config['buttons']
        axis = "Left Right Up Down"
        rcfile = os.path.expanduser("~/.joy2keyrc")
        rc_option = '-rcfile %s' % rcfile if os.path.exists(rcfile) else ''
        command = "sleep 5 "
        command += "&& joy2key $(%s) -X %s -buttons %s -axis %s" % (
            wid, rc_option, buttons, axis
        )
        joy2key_thread = LutrisThread(command)
        self.game_thread.attach_thread(joy2key_thread)
        joy2key_thread.start()

    def xboxdrv_start(self, config):
        command = [
            "pkexec", "xboxdrv", "--daemon", "--detach-kernel-driver",
            "--dbus", "session", "--silent"
        ] + config.split()
        logger.debug("[xboxdrv] %s", ' '.join(command))
        self.xboxdrv_thread = LutrisThread(command)
        self.xboxdrv_thread.set_stop_command(self.xboxdrv_stop)
        self.xboxdrv_thread.start()

    def xboxdrv_stop(self):
        os.system("pkexec xboxdrvctl --shutdown")
        if os.path.exists("/usr/share/lutris/bin/resetxpad"):
            os.system("pkexec /usr/share/lutris/bin/resetxpad")

    def beat(self):
        """Watch the game's process(es)."""
        if self.game_thread.error:
            dialogs.ErrorDialog("<b>Error lauching the game:</b>\n"
                                + self.game_thread.error)
            self.on_game_quit()
            return False
        self.game_log = self.game_thread.stdout
        killswitch_engage = self.killswitch and \
            not os.path.exists(self.killswitch)
        if not self.game_thread.is_running or killswitch_engage:
            logger.debug("Game thread stopped")
            self.on_game_quit()
            return False
        return True

    def stop(self):
        self.state = self.STATE_STOPPED
        if self.runner.system_config.get('xboxdrv'):
            self.xboxdrv_thread.stop()
        if self.game_thread:
            jobs.AsyncCall(self.game_thread.stop, None, killall=True)

    def on_game_quit(self):
        """Restore some settings and cleanup after game quit."""
        self.heartbeat = None
        quit_time = time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime())
        logger.debug("%s stopped at %s", self.name, quit_time)
        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)

        if self.runner.system_config.get('use_us_layout'):
            subprocess.Popen(['setxkbmap'], env=os.environ).communicate()

        if self.runner.system_config.get('restore_gamma'):
            display.restore_gamma()

        if self.runner.system_config.get('xboxdrv') \
           and self.xboxdrv_thread.is_running:
            self.xboxdrv_thread.stop()

        if self.game_thread:
            self.game_thread.stop()
        self.process_return_codes()

        if self.exit_main_loop:
            exit()

    def process_return_codes(self):
        """Do things depending on how the game quitted."""
        if self.game_thread.return_code == 127:
            # Error missing shared lib
            error = "error while loading shared lib"
            error_line = strings.lookup_string_in_text(error,
                                                       self.game_thread.stdout)
            if error_line:
                dialogs.ErrorDialog("<b>Error: Missing shared library.</b>"
                                    "\n\n%s" % error_line)

        if self.game_thread.return_code == 1:
            # Error Wine version conflict
            error = "maybe the wrong wineserver"
            if strings.lookup_string_in_text(error, self.game_thread.stdout):
                dialogs.ErrorDialog("<b>Error: A different Wine version is "
                                    "already using the same Wine prefix.</b>")
Beispiel #44
0
    def play(self):
        """Launch the game."""
        if not self.runner:
            dialogs.ErrorDialog("Invalid game configuration: Missing runner")
            return False
        if not self.prelaunch():
            return False

        system_config = self.runner.system_config
        self.original_outputs = display.get_outputs()
        gameplay_info = self.runner.play()
        logger.debug("Launching %s: %s" % (self.name, gameplay_info))
        if 'error' in gameplay_info:
            show_error_message(gameplay_info)
            return False

        launch_arguments = gameplay_info['command']

        restrict_to_display = system_config.get('display')
        if restrict_to_display:
            display.turn_off_except(restrict_to_display)
            time.sleep(3)
            self.resolution_changed = True

        resolution = system_config.get('resolution')
        if resolution:
            display.change_resolution(resolution)
            time.sleep(3)
            self.resolution_changed = True

        if system_config.get('reset_pulse'):
            audio.reset_pulse()

        primusrun = system_config.get('primusrun')
        if primusrun and system.find_executable('primusrun'):
            launch_arguments.insert(0, 'primusrun')

        prefix_command = system_config.get("prefix_command", '').strip()
        if prefix_command:
            launch_arguments.insert(0, prefix_command)
        env = os.environ.copy()
        game_env = gameplay_info.get('env') or {}
        env.update(game_env)

        ld_preload = gameplay_info.get('ld_preload')
        if ld_preload:
            env["LD_PRELOAD"] = ld_preload

        ld_library_path = []
        if self.use_runtime(system_config):
            runtime64_path = os.path.join(settings.RUNTIME_DIR, "lib64")
            if os.path.exists(runtime64_path):
                ld_library_path.append(runtime64_path)
            runtime32_path = os.path.join(settings.RUNTIME_DIR, "lib32")
            if os.path.exists(runtime32_path):
                ld_library_path.append(runtime32_path)

        game_ld_libary_path = gameplay_info.get('ld_library_path')
        if game_ld_libary_path:
            ld_library_path.append(game_ld_libary_path)

        if ld_library_path:
            ld_full = ':'.join(ld_library_path)
            env["LD_LIBRARY_PATH"] = "{}:$LD_LIBRARY_PATH".format(ld_full)

        self.killswitch = system_config.get('killswitch')
        if self.killswitch and not os.path.exists(self.killswitch):
            # Prevent setting a killswitch to a file that doesn't exists
            self.killswitch = None

        self.game_thread = LutrisThread(launch_arguments,
                                        runner=self.runner,
                                        env=env,
                                        rootpid=gameplay_info.get('rootpid'))
        self.state = self.STATE_RUNNING
        if hasattr(self.runner, 'stop'):
            self.game_thread.set_stop_command(self.runner.stop)
        self.game_thread.start()
        if 'joy2key' in gameplay_info:
            self.joy2key(gameplay_info['joy2key'])
        xboxdrv_config = system_config.get('xboxdrv')
        if xboxdrv_config:
            self.xboxdrv_start(xboxdrv_config)
        if self.runner.is_watchable:
            # Create heartbeat every
            self.heartbeat = GLib.timeout_add(HEARTBEAT_DELAY, self.beat)
Beispiel #45
0
    def do_play(self, prelaunched, _error=None):
        if not prelaunched:
            self.state = self.STATE_STOPPED
            return
        system_config = self.runner.system_config
        self.original_outputs = sorted(
            display.get_outputs(),
            key=lambda e: e[0] == system_config.get('display')
        )

        gameplay_info = self.runner.play()
        logger.debug("Launching %s: %s" % (self.name, gameplay_info))
        if 'error' in gameplay_info:
            self.show_error_message(gameplay_info)
            self.state = self.STATE_STOPPED
            return

        env = {}
        sdl_gamecontrollerconfig = system_config.get('sdl_gamecontrollerconfig')
        if sdl_gamecontrollerconfig:
            path = os.path.expanduser(sdl_gamecontrollerconfig)
            if os.path.exists(path):
                with open(path, "r") as f:
                    sdl_gamecontrollerconfig = f.read()
            env['SDL_GAMECONTROLLERCONFIG'] = sdl_gamecontrollerconfig

        sdl_video_fullscreen = system_config.get('sdl_video_fullscreen') or ''
        env['SDL_VIDEO_FULLSCREEN_DISPLAY'] = sdl_video_fullscreen

        restrict_to_display = system_config.get('display')
        if restrict_to_display != 'off':
            display.turn_off_except(restrict_to_display)
            time.sleep(3)
            self.resolution_changed = True

        resolution = system_config.get('resolution')
        if resolution != 'off':
            display.change_resolution(resolution)
            time.sleep(3)
            self.resolution_changed = True

        if system_config.get('reset_pulse'):
            audio.reset_pulse()

        self.killswitch = system_config.get('killswitch')
        if self.killswitch and not os.path.exists(self.killswitch):
            # Prevent setting a killswitch to a file that doesn't exists
            self.killswitch = None

        # Command
        launch_arguments = gameplay_info['command']

        optimus = system_config.get('optimus')
        if optimus == 'primusrun' and system.find_executable('primusrun'):
            launch_arguments.insert(0, 'primusrun')
        elif optimus == 'optirun' and system.find_executable('optirun'):
            launch_arguments.insert(0, 'virtualgl')
            launch_arguments.insert(0, '-b')
            launch_arguments.insert(0, 'optirun')

        xephyr = system_config.get('xephyr') or 'off'
        if xephyr != 'off':
            if xephyr == '8bpp':
                xephyr_depth = '8'
            else:
                xephyr_depth = '16'
            xephyr_resolution = system_config.get('xephyr_resolution') or '640x480'
            xephyr_command = ['Xephyr', ':2', '-ac', '-screen',
                              xephyr_resolution + 'x' + xephyr_depth, '-glamor',
                              '-reset', '-terminate', '-fullscreen']
            xephyr_thread = LutrisThread(xephyr_command)
            xephyr_thread.start()
            time.sleep(3)
            env['DISPLAY'] = ':2'

        if system_config.get('use_us_layout'):
            setxkbmap_command = ['setxkbmap', '-model', 'pc101', 'us', '-print']
            xkbcomp_command = ['xkbcomp', '-', os.environ.get('DISPLAY', ':0')]
            xkbcomp = subprocess.Popen(xkbcomp_command, stdin=subprocess.PIPE)
            subprocess.Popen(setxkbmap_command,
                             env=os.environ,
                             stdout=xkbcomp.stdin).communicate()
            xkbcomp.communicate()

        pulse_latency = system_config.get('pulse_latency')
        if pulse_latency:
            env['PULSE_LATENCY_MSEC'] = '60'

        prefix_command = system_config.get("prefix_command") or ''
        if prefix_command:
            launch_arguments = shlex.split(os.path.expandvars(prefix_command)) + launch_arguments

        single_cpu = system_config.get('single_cpu') or False
        if single_cpu:
            logger.info('The game will run on a single CPU core')
            launch_arguments.insert(0, '0')
            launch_arguments.insert(0, '-c')
            launch_arguments.insert(0, 'taskset')

        terminal = system_config.get('terminal')
        if terminal:
            terminal = system_config.get("terminal_app",
                                         system.get_default_terminal())
            if terminal and not system.find_executable(terminal):
                dialogs.ErrorDialog("The selected terminal application "
                                    "could not be launched:\n"
                                    "%s" % terminal)
                self.state = self.STATE_STOPPED
                return

        # Env vars
        game_env = gameplay_info.get('env') or self.runner.get_env()
        env.update(game_env)

        ld_preload = gameplay_info.get('ld_preload')
        if ld_preload:
            env["LD_PRELOAD"] = ld_preload

        game_ld_libary_path = gameplay_info.get('ld_library_path')
        if game_ld_libary_path:
            ld_library_path = env.get("LD_LIBRARY_PATH")
            if not ld_library_path:
                ld_library_path = '$LD_LIBRARY_PATH'
            ld_library_path = ":".join([game_ld_libary_path, ld_library_path])
            env["LD_LIBRARY_PATH"] = ld_library_path
        # /Env vars

        include_processes = shlex.split(system_config.get('include_processes', ''))
        exclude_processes = shlex.split(system_config.get('exclude_processes', ''))

        monitoring_disabled = system_config.get('disable_monitoring')
        if monitoring_disabled:
            show_obnoxious_process_monitor_message()
        process_watch = not monitoring_disabled

        if self.runner.system_config.get('disable_compositor'):
            self.desktop_effects(False)

        self.game_thread = LutrisThread(launch_arguments,
                                        runner=self.runner,
                                        env=env,
                                        rootpid=gameplay_info.get('rootpid'),
                                        watch=process_watch,
                                        term=terminal,
                                        log_buffer=self.log_buffer,
                                        include_processes=include_processes,
                                        exclude_processes=exclude_processes)
        if hasattr(self.runner, 'stop'):
            self.game_thread.set_stop_command(self.runner.stop)
        self.game_thread.start()
        self.state = self.STATE_RUNNING

        # xboxdrv setup
        xboxdrv_config = system_config.get('xboxdrv')
        if xboxdrv_config:
            self.xboxdrv_start(xboxdrv_config)

        if monitoring_disabled:
            logger.info("Process monitoring disabled")
        else:
            self.heartbeat = GLib.timeout_add(HEARTBEAT_DELAY, self.beat)
Beispiel #46
0
    def do_play(self, prelaunched, _error=None):
        if not prelaunched:
            self.state = self.STATE_STOPPED
            return
        system_config = self.runner.system_config
        self.original_outputs = display.get_outputs()
        gameplay_info = self.runner.play()

        env = {}

        logger.debug("Launching %s: %s" % (self.name, gameplay_info))
        if 'error' in gameplay_info:
            self.show_error_message(gameplay_info)
            self.state = self.STATE_STOPPED
            return

        sdl_video_fullscreen = system_config.get('sdl_video_fullscreen') or ''
        env['SDL_VIDEO_FULLSCREEN_DISPLAY'] = sdl_video_fullscreen

        restrict_to_display = system_config.get('display')
        if restrict_to_display != 'off':
            display.turn_off_except(restrict_to_display)
            time.sleep(3)
            self.resolution_changed = True

        resolution = system_config.get('resolution')
        if resolution != 'off':
            display.change_resolution(resolution)
            time.sleep(3)
            self.resolution_changed = True

        if system_config.get('reset_pulse'):
            audio.reset_pulse()

        self.killswitch = system_config.get('killswitch')
        if self.killswitch and not os.path.exists(self.killswitch):
            # Prevent setting a killswitch to a file that doesn't exists
            self.killswitch = None

        # Command
        launch_arguments = gameplay_info['command']

        primusrun = system_config.get('primusrun')
        if primusrun and system.find_executable('primusrun'):
            launch_arguments.insert(0, 'primusrun')

        xephyr = system_config.get('xephyr') or 'off'
        if xephyr != 'off':
            if xephyr == '8bpp':
                xephyr_depth = '8'
            else:
                xephyr_depth = '16'
            xephyr_resolution = system_config.get('xephyr_resolution') or '640x480'
            xephyr_command = ['Xephyr', ':2', '-ac', '-screen',
                              xephyr_resolution + 'x' + xephyr_depth, '-glamor',
                              '-reset', '-terminate', '-fullscreen']
            xephyr_thread = LutrisThread(xephyr_command)
            xephyr_thread.start()
            time.sleep(3)
            env['DISPLAY'] = ':2'

        if system_config.get('use_us_layout'):
            setxkbmap_command = ['setxkbmap', '-model', 'pc101', 'us', '-print']
            xkbcomp_command = ['xkbcomp', '-', os.environ.get('DISPLAY', ':0')]
            xkbcomp = subprocess.Popen(xkbcomp_command, stdin=subprocess.PIPE)
            subprocess.Popen(setxkbmap_command,
                             env=os.environ,
                             stdout=xkbcomp.stdin).communicate()
            xkbcomp.communicate()

        pulse_latency = system_config.get('pulse_latency')
        if pulse_latency:
            env['PULSE_LATENCY_MSEC'] = '60'

        prefix_command = system_config.get("prefix_command") or ''
        if prefix_command.strip():
            launch_arguments.insert(0, prefix_command)

        single_cpu = system_config.get('single_cpu') or False
        if single_cpu:
            logger.info('The game will run on a single CPU core')
            launch_arguments.insert(0, '0')
            launch_arguments.insert(0, '-c')
            launch_arguments.insert(0, 'taskset')

        terminal = system_config.get('terminal')
        if terminal:
            terminal = system_config.get("terminal_app",
                                         system.get_default_terminal())
            if terminal and not system.find_executable(terminal):
                dialogs.ErrorDialog("The selected terminal application "
                                    "could not be launched:\n"
                                    "%s" % terminal)
                self.state = self.STATE_STOPPED
                return
        # Env vars
        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') or ''
        env["LD_PRELOAD"] = ld_preload

        # Runtime management
        ld_library_path = ""
        if self.runner.use_runtime():
            runtime_env = runtime.get_env()
            if 'STEAM_RUNTIME' in runtime_env and 'STEAM_RUNTIME' not in env:
                env['STEAM_RUNTIME'] = runtime_env['STEAM_RUNTIME']
            if 'LD_LIBRARY_PATH' in runtime_env:
                ld_library_path = runtime_env['LD_LIBRARY_PATH']
        game_ld_libary_path = gameplay_info.get('ld_library_path')
        if game_ld_libary_path:
            if not ld_library_path:
                ld_library_path = '$LD_LIBRARY_PATH'
            ld_library_path = ":".join([game_ld_libary_path, ld_library_path])
        env["LD_LIBRARY_PATH"] = ld_library_path

        # /Env vars

        self.game_thread = LutrisThread(launch_arguments,
                                        runner=self.runner, env=env,
                                        rootpid=gameplay_info.get('rootpid'),
                                        term=terminal)
        if hasattr(self.runner, 'stop'):
            self.game_thread.set_stop_command(self.runner.stop)
        self.game_thread.start()
        self.state = self.STATE_RUNNING
        if 'joy2key' in gameplay_info:
            self.joy2key(gameplay_info['joy2key'])
        xboxdrv_config = system_config.get('xboxdrv')
        if xboxdrv_config:
            self.xboxdrv_start(xboxdrv_config)
        self.heartbeat = GLib.timeout_add(HEARTBEAT_DELAY, self.beat)
Beispiel #47
0
def wineexec(
        executable,
        args="",
        wine_path=None,
        prefix=None,
        arch=None,  # pylint: disable=too-many-locals
        working_dir=None,
        winetricks_wine='',
        blocking=False,
        config=None,
        include_processes=[],
        exclude_processes=[],
        disable_runtime=False,
        env={},
        overrides=None):
    """
    Execute a Wine command.

    Args:
        executable (str): wine program to run, pass None to run wine itself
        args (str): program arguments
        wine_path (str): path to the wine version to use
        prefix (str): path to the wine prefix to use
        arch (str): wine architecture of the prefix
        working_dir (str): path to the working dir for the process
        winetricks_wine (str): path to the wine version used by winetricks
        blocking (bool): if true, do not run the process in a thread
        config (LutrisConfig): LutrisConfig object for the process context
        watch (list): list of process names to monitor (even when in a ignore list)

    Returns:
        Process results if the process is running in blocking mode or
        LutrisThread instance otherwise.
    """
    executable = str(executable) if executable else ''
    if not wine_path:
        wine = import_runner('wine')
        wine_path = wine().get_executable()
    if not wine_path:
        raise RuntimeError("Wine is not installed")

    if not working_dir:
        if os.path.isfile(executable):
            working_dir = os.path.dirname(executable)

    executable, _args, working_dir = get_real_executable(
        executable, working_dir)
    if _args:
        args = '{} "{}"'.format(_args[0], _args[1])

    # Create prefix if necessary
    if arch not in ('win32', 'win64'):
        arch = detect_arch(prefix, wine_path)
    if not detect_prefix_arch(prefix):
        wine_bin = winetricks_wine if winetricks_wine else wine_path
        create_prefix(prefix, wine_path=wine_bin, arch=arch)

    wineenv = {'WINEARCH': arch}
    if winetricks_wine:
        wineenv['WINE'] = winetricks_wine
    else:
        wineenv['WINE'] = wine_path

    if prefix:
        wineenv['WINEPREFIX'] = prefix

    wine_config = config or LutrisConfig(runner_slug='wine')
    disable_runtime = disable_runtime or wine_config.system_config[
        'disable_runtime']
    if use_lutris_runtime(wine_path=wineenv['WINE'],
                          force_disable=disable_runtime):
        if WINE_DIR in wine_path:
            wine_root_path = os.path.dirname(os.path.dirname(wine_path))
        elif WINE_DIR in winetricks_wine:
            wine_root_path = os.path.dirname(os.path.dirname(winetricks_wine))
        else:
            wine_root_path = None
        wineenv['LD_LIBRARY_PATH'] = ':'.join(
            runtime.get_paths(prefer_system_libs=wine_config.
                              system_config['prefer_system_libs'],
                              wine_path=wine_root_path))

    if overrides:
        wineenv['WINEDLLOVERRIDES'] = get_overrides_env(overrides)

    wineenv.update(env)

    command = [wine_path]
    if executable:
        command.append(executable)
    command += shlex.split(args)
    if blocking:
        return system.execute(command, env=wineenv, cwd=working_dir)
    wine = import_runner('wine')
    thread = LutrisThread(command,
                          runner=wine(),
                          env=wineenv,
                          cwd=working_dir,
                          include_processes=include_processes,
                          exclude_processes=exclude_processes)
    thread.start()
    return thread
Beispiel #48
0
class Game(object):
    """This class takes cares of loading the configuration for a game
       and running it.
    """
    STATE_IDLE = 'idle'
    STATE_STOPPED = 'stopped'
    STATE_RUNNING = 'running'

    def __init__(self, id=None):
        self.id = id
        self.runner = None
        self.game_thread = None
        self.heartbeat = None
        self.config = None
        self.killswitch = None
        self.state = self.STATE_IDLE
        self.exit_main_loop = False

        game_data = pga.get_game_by_field(id, 'id')
        self.slug = game_data.get('slug') or ''
        self.runner_name = game_data.get('runner') or ''
        self.directory = game_data.get('directory') or ''
        self.name = game_data.get('name') or ''

        self.is_installed = bool(game_data.get('installed')) or False
        self.platform = game_data.get('platform') or ''
        self.year = game_data.get('year') or ''
        self.lastplayed = game_data.get('lastplayed') or 0
        self.game_config_id = game_data.get('configpath') or ''
        self.steamid = game_data.get('steamid') or ''
        self.has_custom_banner = bool(game_data.get('has_custom_banner')) or False
        self.has_custom_icon = bool(game_data.get('has_custom_icon')) or False

        self.load_config()
        self.resolution_changed = False
        self.original_outputs = None
        self.log_buffer = Gtk.TextBuffer()
        self.log_buffer.create_tag("warning", foreground="red")

    def __repr__(self):
        return self.__unicode__()

    def __unicode__(self):
        value = self.name
        if self.runner_name:
            value += " (%s)" % self.runner_name
        return value

    def show_error_message(self, message):
        """Display an error message based on the runner's output."""
        if "CUSTOM" == message['error']:
            message_text = message['text'].replace('&', '&amp;')
            dialogs.ErrorDialog(message_text)
        elif "RUNNER_NOT_INSTALLED" == message['error']:
            dialogs.ErrorDialog('Error the runner is not installed')
        elif "NO_BIOS" == message['error']:
            dialogs.ErrorDialog("A bios file is required to run this game")
        elif "FILE_NOT_FOUND" == message['error']:
            filename = message['file']
            if filename:
                message_text = "The file {} could not be found".format(
                    filename.replace('&', '&amp;')
                )
            else:
                message_text = "No file provided"
            dialogs.ErrorDialog(message_text)

        elif "NOT_EXECUTABLE" == message['error']:
            message_text = message['file'].replace('&', '&amp;')
            dialogs.ErrorDialog("The file %s is not executable" % message_text)

    def get_browse_dir(self):
        """Return the path to open with the Browse Files action."""
        return self.runner.browse_dir

    def load_config(self):
        """Load the game's configuration."""
        self.config = LutrisConfig(runner_slug=self.runner_name,
                                   game_config_id=self.game_config_id)
        if not self.is_installed:
            return
        if not self.runner_name:
            logger.error('Incomplete data for %s', self.slug)
            return
        try:
            runner_class = import_runner(self.runner_name)
        except InvalidRunner:
            logger.error("Unable to import runner %s for %s",
                         self.runner_name, self.slug)
        else:
            self.runner = runner_class(self.config)

    def remove(self, from_library=False, from_disk=False):
        if from_disk and self.runner:
            logger.debug("Removing game %s from disk" % self.id)
            self.runner.remove_game_data(game_path=self.directory)

        # Do not keep multiple copies of the same game
        existing_games = pga.get_games_where(slug=self.slug)
        if len(existing_games) > 1:
            from_library = True

        if from_library:
            logger.debug("Removing game %s from library" % self.id)
            pga.delete_game(self.id)
        else:
            pga.set_uninstalled(self.id)
        self.config.remove()
        xdg.remove_launcher(self.slug, self.id, desktop=True, menu=True)
        return from_library

    def set_platform_from_runner(self):
        if not self.runner:
            return
        self.platform = self.runner.get_platform()

    def save(self, metadata_only=False):
        """
        Save the game's config and metadata, if `metadata_only` is set to True,
        do not save the config. This is useful when exiting the game since the
        config might have changed and we don't want to override the changes.
        """
        if not metadata_only:
            self.config.save()
        self.id = pga.add_or_update(
            name=self.name,
            runner=self.runner_name,
            slug=self.slug,
            platform=self.platform,
            year=self.year,
            lastplayed=self.lastplayed,
            directory=self.directory,
            installed=self.is_installed,
            configpath=self.config.game_config_id,
            steamid=self.steamid,
            id=self.id
        )

    def prelaunch(self):
        """Verify that the current game can be launched."""
        if not self.runner.is_installed():
            installed = self.runner.install_dialog()
            if not installed:
                return False

        if self.runner.use_runtime():
            runtime_updater = runtime.RuntimeUpdater()
            if runtime_updater.is_updating():
                logger.warning("Runtime updates: {}".format(
                    runtime_updater.current_updates)
                )
                dialogs.ErrorDialog("Runtime currently updating",
                                    "Game might not work as expected")
        return True

    def play(self):
        """Launch the game."""
        if not self.runner:
            dialogs.ErrorDialog("Invalid game configuration: Missing runner")
            self.state = self.STATE_STOPPED
            return

        if not self.prelaunch():
            self.state = self.STATE_STOPPED
            return

        if hasattr(self.runner, 'prelaunch'):
            jobs.AsyncCall(self.runner.prelaunch, self.do_play)
        else:
            self.do_play(True)

    def do_play(self, prelaunched, _error=None):
        if not prelaunched:
            self.state = self.STATE_STOPPED
            return
        system_config = self.runner.system_config
        self.original_outputs = sorted(
            display.get_outputs(),
            key=lambda e: e[0] == system_config.get('display')
        )
        gameplay_info = self.runner.play()

        env = {}

        logger.debug("Launching %s: %s" % (self.name, gameplay_info))
        if 'error' in gameplay_info:
            self.show_error_message(gameplay_info)
            self.state = self.STATE_STOPPED
            return

        sdl_gamecontrollerconfig = system_config.get('sdl_gamecontrollerconfig')
        if sdl_gamecontrollerconfig:
            path = os.path.expanduser(sdl_gamecontrollerconfig)
            if os.path.exists(path):
                with open(path, "r") as f:
                    sdl_gamecontrollerconfig = f.read()
            env['SDL_GAMECONTROLLERCONFIG'] = sdl_gamecontrollerconfig

        sdl_video_fullscreen = system_config.get('sdl_video_fullscreen') or ''
        env['SDL_VIDEO_FULLSCREEN_DISPLAY'] = sdl_video_fullscreen

        restrict_to_display = system_config.get('display')
        if restrict_to_display != 'off':
            display.turn_off_except(restrict_to_display)
            time.sleep(3)
            self.resolution_changed = True

        resolution = system_config.get('resolution')
        if resolution != 'off':
            display.change_resolution(resolution)
            time.sleep(3)
            self.resolution_changed = True

        if system_config.get('reset_pulse'):
            audio.reset_pulse()

        self.killswitch = system_config.get('killswitch')
        if self.killswitch and not os.path.exists(self.killswitch):
            # Prevent setting a killswitch to a file that doesn't exists
            self.killswitch = None

        # Command
        launch_arguments = gameplay_info['command']

        primusrun = system_config.get('primusrun')
        if primusrun and system.find_executable('primusrun'):
            launch_arguments.insert(0, 'primusrun')

        xephyr = system_config.get('xephyr') or 'off'
        if xephyr != 'off':
            if xephyr == '8bpp':
                xephyr_depth = '8'
            else:
                xephyr_depth = '16'
            xephyr_resolution = system_config.get('xephyr_resolution') or '640x480'
            xephyr_command = ['Xephyr', ':2', '-ac', '-screen',
                              xephyr_resolution + 'x' + xephyr_depth, '-glamor',
                              '-reset', '-terminate', '-fullscreen']
            xephyr_thread = LutrisThread(xephyr_command)
            xephyr_thread.start()
            time.sleep(3)
            env['DISPLAY'] = ':2'

        if system_config.get('use_us_layout'):
            setxkbmap_command = ['setxkbmap', '-model', 'pc101', 'us', '-print']
            xkbcomp_command = ['xkbcomp', '-', os.environ.get('DISPLAY', ':0')]
            xkbcomp = subprocess.Popen(xkbcomp_command, stdin=subprocess.PIPE)
            subprocess.Popen(setxkbmap_command,
                             env=os.environ,
                             stdout=xkbcomp.stdin).communicate()
            xkbcomp.communicate()

        pulse_latency = system_config.get('pulse_latency')
        if pulse_latency:
            env['PULSE_LATENCY_MSEC'] = '60'

        prefix_command = system_config.get("prefix_command") or ''
        if prefix_command:
            launch_arguments = shlex.split(os.path.expandvars(prefix_command)) + launch_arguments

        single_cpu = system_config.get('single_cpu') or False
        if single_cpu:
            logger.info('The game will run on a single CPU core')
            launch_arguments.insert(0, '0')
            launch_arguments.insert(0, '-c')
            launch_arguments.insert(0, 'taskset')

        terminal = system_config.get('terminal')
        if terminal:
            terminal = system_config.get("terminal_app",
                                         system.get_default_terminal())
            if terminal and not system.find_executable(terminal):
                dialogs.ErrorDialog("The selected terminal application "
                                    "could not be launched:\n"
                                    "%s" % terminal)
                self.state = self.STATE_STOPPED
                return

        # Env vars
        game_env = gameplay_info.get('env') or {}
        env.update(game_env)

        ld_preload = gameplay_info.get('ld_preload')
        if (ld_preload):
            env["LD_PRELOAD"] = ld_preload

        game_ld_libary_path = gameplay_info.get('ld_library_path')
        if game_ld_libary_path:
            ld_library_path = env.get("LD_LIBRARY_PATH")
            if not ld_library_path:
                ld_library_path = '$LD_LIBRARY_PATH'
            ld_library_path = ":".join([game_ld_libary_path, ld_library_path])
            env["LD_LIBRARY_PATH"] = ld_library_path
        # /Env vars

        include_processes = shlex.split(system_config.get('include_processes', ''))
        exclude_processes = shlex.split(system_config.get('exclude_processes', ''))

        monitoring_disabled = system_config.get('disable_monitoring')
        process_watch = not monitoring_disabled

        self.game_thread = LutrisThread(launch_arguments,
                                        runner=self.runner,
                                        env=env,
                                        rootpid=gameplay_info.get('rootpid'),
                                        watch=process_watch,
                                        term=terminal,
                                        log_buffer=self.log_buffer,
                                        include_processes=include_processes,
                                        exclude_processes=exclude_processes)
        if hasattr(self.runner, 'stop'):
            self.game_thread.set_stop_command(self.runner.stop)
        self.game_thread.start()
        self.state = self.STATE_RUNNING

        # xboxdrv setup
        xboxdrv_config = system_config.get('xboxdrv')
        if xboxdrv_config:
            self.xboxdrv_start(xboxdrv_config)

        if monitoring_disabled:
            logger.info("Process monitoring disabled")
        else:
            self.heartbeat = GLib.timeout_add(HEARTBEAT_DELAY, self.beat)

    def xboxdrv_start(self, config):
        command = [
            "pkexec", "xboxdrv", "--daemon", "--detach-kernel-driver",
            "--dbus", "session", "--silent"
        ] + config.split()
        logger.debug("[xboxdrv] %s", ' '.join(command))
        self.xboxdrv_thread = LutrisThread(command, include_processes=['xboxdrv'])
        self.xboxdrv_thread.set_stop_command(self.xboxdrv_stop)
        self.xboxdrv_thread.start()

    def xboxdrv_stop(self):
        os.system("pkexec xboxdrvctl --shutdown")
        if os.path.exists("/usr/share/lutris/bin/resetxpad"):
            os.system("pkexec /usr/share/lutris/bin/resetxpad")

    def beat(self):
        """Watch the game's process(es)."""
        if self.game_thread.error:
            dialogs.ErrorDialog("<b>Error lauching the game:</b>\n" +
                                self.game_thread.error)
            self.on_game_quit()
            return False

        # The killswitch file should be set to a device (ie. /dev/input/js0)
        # When that device is unplugged, the game is forced to quit.
        killswitch_engage = (
            self.killswitch and not os.path.exists(self.killswitch)
        )
        if not self.game_thread.is_running or killswitch_engage:
            logger.debug("Game thread stopped")
            self.on_game_quit()
            return False
        return True

    def stop(self):
        if self.runner.system_config.get('xboxdrv'):
            logger.debug("Stopping xboxdrv")
            self.xboxdrv_thread.stop()
        if self.game_thread:
            jobs.AsyncCall(self.game_thread.stop, None, killall=self.runner.killall_on_exit())
        self.state = self.STATE_STOPPED

    def on_game_quit(self):
        """Restore some settings and cleanup after game quit."""
        self.heartbeat = None
        if self.state != self.STATE_STOPPED:
            logger.debug("Game thread still running, stopping it (state: %s)", self.state)
            self.stop()
        quit_time = time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime())
        logger.debug("%s stopped at %s", self.name, quit_time)
        self.lastplayed = int(time.time())
        self.save(metadata_only=True)

        os.chdir(os.path.expanduser('~'))

        if self.resolution_changed\
           or self.runner.system_config.get('reset_desktop'):
            display.change_resolution(self.original_outputs)

        if self.runner.system_config.get('use_us_layout'):
            subprocess.Popen(['setxkbmap'], env=os.environ).communicate()

        if self.runner.system_config.get('restore_gamma'):
            display.restore_gamma()

        self.process_return_codes()

        if self.exit_main_loop:
            exit()

    def process_return_codes(self):
        """Do things depending on how the game quitted."""
        if self.game_thread.return_code == 127:
            # Error missing shared lib
            error = "error while loading shared lib"
            error_line = strings.lookup_string_in_text(error,
                                                       self.game_thread.stdout)
            if error_line:
                dialogs.ErrorDialog("<b>Error: Missing shared library.</b>"
                                    "\n\n%s" % error_line)

        if self.game_thread.return_code == 1:
            # Error Wine version conflict
            error = "maybe the wrong wineserver"
            if strings.lookup_string_in_text(error, self.game_thread.stdout):
                dialogs.ErrorDialog("<b>Error: A different Wine version is "
                                    "already using the same Wine prefix.</b>")

    def notify_steam_game_changed(self, appmanifest):
        logger.debug("Steam game %s state has changed, new states: %s",
                     appmanifest.steamid, ', '.join(appmanifest.states))
        if 'Fully Installed' in appmanifest.states:
            self.game_thread.ready_state = True
        elif 'Update Required' in appmanifest.states:
            self.game_thread.ready_state = False
Beispiel #49
0
class Game(object):
    """This class takes cares about loading the configuration for a game
       and running it.
    """
    def __init__(self, slug):
        self.slug = slug
        self.runner = None
        self.game_thread = None
        self.heartbeat = None
        self.config = None

        game_data = pga.get_game_by_slug(slug)
        self.runner_name = game_data.get('runner') or ''
        self.directory = game_data.get('directory') or ''
        self.name = game_data.get('name') or ''
        self.is_installed = bool(game_data.get('installed')) or False
        self.year = game_data.get('year') or ''

        self.load_config()
        self.resolution_changed = False
        self.original_outputs = None

    def __repr__(self):
        return self.__unicode__()

    def __unicode__(self):
        value = self.name
        if self.runner_name:
            value += " (%s)" % self.runner_name
        return value

    def get_browse_dir(self):
        """Return the path to open with the Browse Files action."""
        return self.runner.browse_dir

    def load_config(self):
        """Load the game's configuration."""
        self.config = LutrisConfig(game=self.slug)
        if self.is_installed:
            runner_class = import_runner(self.runner_name)
            if runner_class:
                self.runner = runner_class(self.config)
            else:
                logger.error("Unable to import runner %s for %s",
                             self.runner_name, self.slug)

    def remove(self, from_library=False, from_disk=False):
        if from_disk:
            self.runner.remove_game_data(game_path=self.directory)
        if from_library:
            pga.delete_game(self.slug)
            self.config.remove()
        else:
            pga.set_uninstalled(self.slug)

    def prelaunch(self):
        """Verify that the current game can be launched."""
        if not self.runner.is_installed():
            installed = self.runner.install_dialog()
            if not installed:
                return False

        if hasattr(self.runner, 'prelaunch'):
            return self.runner.prelaunch()
        return True

    def play(self):
        """Launch the game."""
        if not self.runner:
            dialogs.ErrorDialog("Invalid game configuration: Missing runner")
            return False
        if not self.prelaunch():
            return False

        system_config = self.runner.system_config
        self.original_outputs = display.get_outputs()
        gameplay_info = self.runner.play()
        logger.debug("Launching %s: %s" % (self.name, gameplay_info))
        if 'error' in gameplay_info:
            show_error_message(gameplay_info)
            return False

        launch_arguments = gameplay_info['command']

        restrict_to_display = system_config.get('display')
        if restrict_to_display:
            display.turn_off_except(restrict_to_display)
            self.resolution_changed = True

        resolution = system_config.get('resolution')
        if resolution:
            display.change_resolution(resolution)
            self.resolution_changed = True

        if system_config.get('reset_pulse'):
            audio.reset_pulse()

        prefix_command = system_config.get("prefix_command", '').strip()
        if prefix_command and system.find_executable(prefix_command):
            launch_arguments.insert(0, prefix_command)

        ld_preload = gameplay_info.get('ld_preload')
        if ld_preload:
            launch_arguments.insert(0, 'LD_PRELOAD="{}"'.format(ld_preload))

        ld_library_path = []
        runtime64_path = os.path.join(settings.RUNTIME_DIR, "lib64")
        if os.path.exists(runtime64_path):
            ld_library_path.append(runtime64_path)
        runtime32_path = os.path.join(settings.RUNTIME_DIR, "lib32")
        if os.path.exists(runtime32_path):
            ld_library_path.append(runtime32_path)
        game_ld_libary_path = gameplay_info.get('ld_library_path')
        if game_ld_libary_path:
            ld_library_path.append(game_ld_libary_path)

        if ld_library_path:
            ld_full = ':'.join(ld_library_path)
            ld_arg = 'LD_LIBRARY_PATH="{}:$LD_LIBRARY_PATH"'.format(ld_full)
            launch_arguments.insert(0, ld_arg)

        env = gameplay_info.get('env') or []
        for var in env:
            launch_arguments.insert(0, var)

        killswitch = system_config.get('killswitch')
        self.game_thread = LutrisThread(" ".join(launch_arguments),
                                        path=self.runner.working_dir,
                                        killswitch=killswitch)
        if hasattr(self.runner, 'stop'):
            self.game_thread.set_stop_command(self.runner.stop)
        self.game_thread.start()
        if 'joy2key' in gameplay_info:
            self.joy2key(gameplay_info['joy2key'])
        xboxdrv_config = system_config.get('xboxdrv')
        if xboxdrv_config:
            self.xboxdrv_start(xboxdrv_config)
        if self.runner.is_watchable:
            # Create heartbeat every
            self.heartbeat = GLib.timeout_add(5000, self.poke_process)

    def joy2key(self, config):
        """Run a joy2key thread."""
        if not system.find_executable('joy2key'):
            logger.error("joy2key is not installed")
            return
        win = "grep %s" % config['window']
        if 'notwindow' in config:
            win += ' | grep -v %s' % config['notwindow']
        wid = "xwininfo -root -tree | %s | awk '{print $1}'" % win
        buttons = config['buttons']
        axis = "Left Right Up Down"
        rcfile = os.path.expanduser("~/.joy2keyrc")
        rc_option = '-rcfile %s' % rcfile if os.path.exists(rcfile) else ''
        command = "sleep 5 "
        command += "&& joy2key $(%s) -X %s -buttons %s -axis %s" % (
            wid, rc_option, buttons, axis
        )
        joy2key_thread = LutrisThread(command)
        self.game_thread.attach_thread(joy2key_thread)
        joy2key_thread.start()

    def xboxdrv_start(self, config):
        command = ("pkexec xboxdrv --daemon --detach-kernel-driver "
                   "--dbus session --silent %s"
                   % config)
        logger.debug("xboxdrv command: %s", command)
        self.xboxdrv_thread = LutrisThread(command)
        self.xboxdrv_thread.set_stop_command(self.xboxdrv_stop)
        self.xboxdrv_thread.start()

    def xboxdrv_stop(self):
        os.system("pkexec xboxdrvctl --shutdown")

    def poke_process(self):
        """Watch game's process."""
        if not self.game_thread.pid:
            self.quit_game()
            return False
        return True

    def quit_game(self):
        """Quit the game and cleanup."""
        self.heartbeat = None
        quit_time = time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime())
        logger.debug("game has quit at %s" % quit_time)

        if self.resolution_changed\
           or self.runner.system_config.get('reset_desktop'):
            display.change_resolution(self.original_outputs)

        if self.runner.system_config.get('restore_gamma'):
            display.restore_gamma()

        if self.runner.system_config.get('xboxdrv'):
            self.xboxdrv_thread.stop()

        if self.game_thread:
            self.game_thread.stop()
Beispiel #50
0
class Game(object):
    """This class takes cares about loading the configuration for a game
       and running it.
    """
    def __init__(self, slug):
        self.slug = slug
        self.runner = None
        self.game_thread = None
        self.heartbeat = None
        self.config = None

        game_data = pga.get_game_by_slug(slug)
        self.runner_name = game_data.get('runner') or ''
        self.directory = game_data.get('directory') or ''
        self.name = game_data.get('name') or ''
        self.is_installed = bool(game_data.get('installed')) or False
        self.year = game_data.get('year') or ''

        self.load_config()
        self.resolution_changed = False
        self.original_outputs = None

    def __repr__(self):
        return self.__unicode__()

    def __unicode__(self):
        value = self.name
        if self.runner_name:
            value += " (%s)" % self.runner_name
        return value

    def get_browse_dir(self):
        """Return the path to open with the Browse Files action."""
        return self.runner.browse_dir

    def load_config(self):
        """Load the game's configuration."""
        self.config = LutrisConfig(game=self.slug)
        if self.is_installed:
            runner_class = import_runner(self.runner_name)
            if runner_class:
                self.runner = runner_class(self.config)
            else:
                logger.error("Unable to import runner %s for %s",
                             self.runner_name, self.slug)

    def remove(self, from_library=False, from_disk=False):
        if from_disk:
            self.runner.remove_game_data(game_path=self.directory)
        if from_library:
            pga.delete_game(self.slug)
            self.config.remove()
        else:
            pga.set_uninstalled(self.slug)

    def prelaunch(self):
        """Verify that the current game can be launched."""
        if not self.runner.is_installed():
            installed = self.runner.install_dialog()
            if not installed:
                return False

        if hasattr(self.runner, 'prelaunch'):
            return self.runner.prelaunch()
        return True

    def use_runtime(self, system_config):
        disable_runtime = system_config.get('disable_runtime')
        env_runtime = os.getenv('LUTRIS_RUNTIME')
        if env_runtime and env_runtime.lower() in ('0', 'off'):
            disable_runtime = True
        return not disable_runtime

    def play(self):
        """Launch the game."""
        if not self.runner:
            dialogs.ErrorDialog("Invalid game configuration: Missing runner")
            return False
        if not self.prelaunch():
            return False

        system_config = self.runner.system_config
        self.original_outputs = display.get_outputs()
        gameplay_info = self.runner.play()
        logger.debug("Launching %s: %s" % (self.name, gameplay_info))
        if 'error' in gameplay_info:
            show_error_message(gameplay_info)
            return False

        launch_arguments = gameplay_info['command']

        restrict_to_display = system_config.get('display')
        if restrict_to_display:
            display.turn_off_except(restrict_to_display)
            time.sleep(3)
            self.resolution_changed = True

        resolution = system_config.get('resolution')
        if resolution:
            display.change_resolution(resolution)
            time.sleep(3)
            self.resolution_changed = True

        if system_config.get('reset_pulse'):
            audio.reset_pulse()

        primusrun = system_config.get('primusrun')
        if primusrun and system.find_executable('primusrun'):
            launch_arguments.insert(0, 'primusrun')

        prefix_command = system_config.get("prefix_command", '').strip()
        if prefix_command:
            launch_arguments.insert(0, prefix_command)

        ld_preload = gameplay_info.get('ld_preload')
        if ld_preload:
            launch_arguments.insert(0, 'LD_PRELOAD="{}"'.format(ld_preload))

        ld_library_path = []
        if self.use_runtime(system_config):
            runtime64_path = os.path.join(settings.RUNTIME_DIR, "lib64")
            if os.path.exists(runtime64_path):
                ld_library_path.append(runtime64_path)
            runtime32_path = os.path.join(settings.RUNTIME_DIR, "lib32")
            if os.path.exists(runtime32_path):
                ld_library_path.append(runtime32_path)

        game_ld_libary_path = gameplay_info.get('ld_library_path')
        if game_ld_libary_path:
            ld_library_path.append(game_ld_libary_path)

        if ld_library_path:
            ld_full = ':'.join(ld_library_path)
            ld_arg = 'LD_LIBRARY_PATH="{}:$LD_LIBRARY_PATH"'.format(ld_full)
            launch_arguments.insert(0, ld_arg)

        env = gameplay_info.get('env') or []
        for var in env:
            launch_arguments.insert(0, var)

        killswitch = system_config.get('killswitch')
        self.game_thread = LutrisThread(" ".join(launch_arguments),
                                        path=self.runner.working_dir,
                                        killswitch=killswitch)
        if hasattr(self.runner, 'stop'):
            self.game_thread.set_stop_command(self.runner.stop)
        self.game_thread.start()
        if 'joy2key' in gameplay_info:
            self.joy2key(gameplay_info['joy2key'])
        xboxdrv_config = system_config.get('xboxdrv')
        if xboxdrv_config:
            self.xboxdrv_start(xboxdrv_config)
        if self.runner.is_watchable:
            # Create heartbeat every
            self.heartbeat = GLib.timeout_add(5000, self.poke_process)

    def joy2key(self, config):
        """Run a joy2key thread."""
        if not system.find_executable('joy2key'):
            logger.error("joy2key is not installed")
            return
        win = "grep %s" % config['window']
        if 'notwindow' in config:
            win += ' | grep -v %s' % config['notwindow']
        wid = "xwininfo -root -tree | %s | awk '{print $1}'" % win
        buttons = config['buttons']
        axis = "Left Right Up Down"
        rcfile = os.path.expanduser("~/.joy2keyrc")
        rc_option = '-rcfile %s' % rcfile if os.path.exists(rcfile) else ''
        command = "sleep 5 "
        command += "&& joy2key $(%s) -X %s -buttons %s -axis %s" % (
            wid, rc_option, buttons, axis
        )
        joy2key_thread = LutrisThread(command)
        self.game_thread.attach_thread(joy2key_thread)
        joy2key_thread.start()

    def xboxdrv_start(self, config):
        command = ("pkexec xboxdrv --daemon --detach-kernel-driver "
                   "--dbus session --silent %s"
                   % config)
        logger.debug("xboxdrv command: %s", command)
        self.xboxdrv_thread = LutrisThread(command)
        self.xboxdrv_thread.set_stop_command(self.xboxdrv_stop)
        self.xboxdrv_thread.start()

    def xboxdrv_stop(self):
        os.system("pkexec xboxdrvctl --shutdown")

    def poke_process(self):
        """Watch game's process."""
        if not self.game_thread.pid:
            self.quit_game()
            return False
        return True

    def quit_game(self):
        """Quit the game and cleanup."""
        self.heartbeat = None
        quit_time = time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime())
        logger.debug("game has quit at %s" % quit_time)

        if self.resolution_changed\
           or self.runner.system_config.get('reset_desktop'):
            display.change_resolution(self.original_outputs)

        if self.runner.system_config.get('restore_gamma'):
            display.restore_gamma()

        if self.runner.system_config.get('xboxdrv'):
            self.xboxdrv_thread.stop()

        if self.game_thread:
            self.game_thread.stop()
Beispiel #51
0
    def play(self):
        """Launch the game."""
        if not self.runner:
            dialogs.ErrorDialog("Invalid game configuration: Missing runner")
            return False
        if not self.prelaunch():
            return False

        system_config = self.runner.system_config
        self.original_outputs = display.get_outputs()
        gameplay_info = self.runner.play()
        logger.debug("Launching %s: %s" % (self.name, gameplay_info))
        if 'error' in gameplay_info:
            show_error_message(gameplay_info)
            return False

        launch_arguments = gameplay_info['command']

        restrict_to_display = system_config.get('display')
        if restrict_to_display:
            display.turn_off_except(restrict_to_display)
            self.resolution_changed = True

        resolution = system_config.get('resolution')
        if resolution:
            display.change_resolution(resolution)
            self.resolution_changed = True

        if system_config.get('reset_pulse'):
            audio.reset_pulse()

        prefix_command = system_config.get("prefix_command", '').strip()
        if prefix_command and system.find_executable(prefix_command):
            launch_arguments.insert(0, prefix_command)

        ld_preload = gameplay_info.get('ld_preload')
        if ld_preload:
            launch_arguments.insert(0, 'LD_PRELOAD="{}"'.format(ld_preload))

        ld_library_path = []
        runtime64_path = os.path.join(settings.RUNTIME_DIR, "lib64")
        if os.path.exists(runtime64_path):
            ld_library_path.append(runtime64_path)
        runtime32_path = os.path.join(settings.RUNTIME_DIR, "lib32")
        if os.path.exists(runtime32_path):
            ld_library_path.append(runtime32_path)
        game_ld_libary_path = gameplay_info.get('ld_library_path')
        if game_ld_libary_path:
            ld_library_path.append(game_ld_libary_path)

        if ld_library_path:
            ld_full = ':'.join(ld_library_path)
            ld_arg = 'LD_LIBRARY_PATH="{}:$LD_LIBRARY_PATH"'.format(ld_full)
            launch_arguments.insert(0, ld_arg)

        env = gameplay_info.get('env') or []
        for var in env:
            launch_arguments.insert(0, var)

        killswitch = system_config.get('killswitch')
        self.game_thread = LutrisThread(" ".join(launch_arguments),
                                        path=self.runner.working_dir,
                                        killswitch=killswitch)
        if hasattr(self.runner, 'stop'):
            self.game_thread.set_stop_command(self.runner.stop)
        self.game_thread.start()
        if 'joy2key' in gameplay_info:
            self.joy2key(gameplay_info['joy2key'])
        xboxdrv_config = system_config.get('xboxdrv')
        if xboxdrv_config:
            self.xboxdrv_start(xboxdrv_config)
        if self.runner.is_watchable:
            # Create heartbeat every
            self.heartbeat = GLib.timeout_add(5000, self.poke_process)
Beispiel #52
0
    def play(self):
        """Launch the game."""
        if not self.runner:
            dialogs.ErrorDialog("Invalid game configuration: Missing runner")
            return False
        if not self.prelaunch():
            return False

        system_config = self.runner.system_config
        self.original_outputs = display.get_outputs()
        gameplay_info = self.runner.play()
        logger.debug("Launching %s: %s" % (self.name, gameplay_info))
        if 'error' in gameplay_info:
            show_error_message(gameplay_info)
            return False

        restrict_to_display = system_config.get('display')
        if restrict_to_display != 'off':
            display.turn_off_except(restrict_to_display)
            time.sleep(3)
            self.resolution_changed = True

        resolution = system_config.get('resolution')
        if resolution != 'off':
            display.change_resolution(resolution)
            time.sleep(3)
            self.resolution_changed = True

        if system_config.get('reset_pulse'):
            audio.reset_pulse()

        self.killswitch = system_config.get('killswitch')
        if self.killswitch and not os.path.exists(self.killswitch):
            # Prevent setting a killswitch to a file that doesn't exists
            self.killswitch = None

        # Command
        launch_arguments = gameplay_info['command']

        primusrun = system_config.get('primusrun')
        if primusrun and system.find_executable('primusrun'):
            launch_arguments.insert(0, 'primusrun')

        prefix_command = system_config.get("prefix_command") or ''
        if prefix_command.strip():
            launch_arguments.insert(0, prefix_command)

        terminal = system_config.get('terminal')
        if terminal:
            terminal = system_config.get("terminal_app",
                                         system.get_default_terminal())
            if terminal and not system.find_executable(terminal):
                dialogs.ErrorDialog("The selected terminal application "
                                    "could not be launched:\n"
                                    "%s" % terminal)
                return False
        # Env vars
        env = {}
        game_env = gameplay_info.get('env') or {}
        env.update(game_env)

        ld_preload = gameplay_info.get('ld_preload')
        if ld_preload:
            env["LD_PRELOAD"] = ld_preload

        ld_library_path = []
        if self.use_runtime(system_config):
            env['STEAM_RUNTIME'] = os.path.join(settings.RUNTIME_DIR, 'steam')
            ld_library_path += runtime.get_runtime_paths()

        game_ld_libary_path = gameplay_info.get('ld_library_path')
        if game_ld_libary_path:
            ld_library_path.append(game_ld_libary_path)

        if ld_library_path:
            ld_full = ':'.join(ld_library_path)
            env["LD_LIBRARY_PATH"] = "{}:$LD_LIBRARY_PATH".format(ld_full)
        # /Env vars

        self.game_thread = LutrisThread(launch_arguments,
                                        runner=self.runner, env=env,
                                        rootpid=gameplay_info.get('rootpid'),
                                        term=terminal)
        self.state = self.STATE_RUNNING
        if hasattr(self.runner, 'stop'):
            self.game_thread.set_stop_command(self.runner.stop)
        self.game_thread.start()
        if 'joy2key' in gameplay_info:
            self.joy2key(gameplay_info['joy2key'])
        xboxdrv_config = system_config.get('xboxdrv')
        if xboxdrv_config:
            self.xboxdrv_start(xboxdrv_config)
        if self.runner.is_watchable:
            # Create heartbeat every
            self.heartbeat = GLib.timeout_add(HEARTBEAT_DELAY, self.beat)
Beispiel #53
0
class Game(object):
    """This class takes cares about loading the configuration for a game
       and running it.
    """
    STATE_IDLE = 'idle'
    STATE_STOPPED = 'stopped'
    STATE_RUNNING = 'running'

    def __init__(self, slug):
        self.slug = slug
        self.runner = None
        self.game_thread = None
        self.heartbeat = None
        self.config = None
        self.killswitch = None
        self.state = self.STATE_IDLE
        self.game_log = ''

        game_data = pga.get_game_by_slug(slug)
        self.runner_name = game_data.get('runner') or ''
        self.directory = game_data.get('directory') or ''
        self.name = game_data.get('name') or ''
        self.is_installed = bool(game_data.get('installed')) or False
        self.year = game_data.get('year') or ''

        self.load_config()
        self.resolution_changed = False
        self.original_outputs = None

    def __repr__(self):
        return self.__unicode__()

    def __unicode__(self):
        value = self.name
        if self.runner_name:
            value += " (%s)" % self.runner_name
        return value

    def get_browse_dir(self):
        """Return the path to open with the Browse Files action."""
        return self.runner.browse_dir

    def load_config(self):
        """Load the game's configuration."""
        self.config = LutrisConfig(runner_slug=self.runner_name,
                                   game_slug=self.slug)
        if not self.is_installed:
            return
        if not self.runner_name:
            logger.error('Incomplete data for %s', self.slug)
            return
        try:
            runner_class = import_runner(self.runner_name)
        except InvalidRunner:
            logger.error("Unable to import runner %s for %s",
                         self.runner_name, self.slug)
        self.runner = runner_class(self.config)

    def remove(self, from_library=False, from_disk=False):
        if from_disk:
            self.runner.remove_game_data(game_path=self.directory)
        if from_library:
            pga.delete_game(self.slug)
            self.config.remove()
        else:
            pga.set_uninstalled(self.slug)

    def save(self):
        self.config.save()
        pga.add_or_update(
            name=self.name,
            runner=self.runner_name,
            slug=self.slug,
            directory=self.directory,
            installed=self.is_installed
        )

    def prelaunch(self):
        """Verify that the current game can be launched."""
        if not self.runner.is_installed():
            installed = self.runner.install_dialog()
            if not installed:
                return False

        if hasattr(self.runner, 'prelaunch'):
            return self.runner.prelaunch()
        return True

    def use_runtime(self, system_config):
        disable_runtime = system_config.get('disable_runtime')
        env_runtime = os.getenv('LUTRIS_RUNTIME')
        if env_runtime and env_runtime.lower() in ('0', 'off'):
            disable_runtime = True
        return not disable_runtime

    def play(self):
        """Launch the game."""
        if not self.runner:
            dialogs.ErrorDialog("Invalid game configuration: Missing runner")
            return False
        if not self.prelaunch():
            return False

        system_config = self.runner.system_config
        self.original_outputs = display.get_outputs()
        gameplay_info = self.runner.play()
        logger.debug("Launching %s: %s" % (self.name, gameplay_info))
        if 'error' in gameplay_info:
            show_error_message(gameplay_info)
            return False

        restrict_to_display = system_config.get('display')
        if restrict_to_display != 'off':
            display.turn_off_except(restrict_to_display)
            time.sleep(3)
            self.resolution_changed = True

        resolution = system_config.get('resolution')
        if resolution != 'off':
            display.change_resolution(resolution)
            time.sleep(3)
            self.resolution_changed = True

        if system_config.get('reset_pulse'):
            audio.reset_pulse()

        self.killswitch = system_config.get('killswitch')
        if self.killswitch and not os.path.exists(self.killswitch):
            # Prevent setting a killswitch to a file that doesn't exists
            self.killswitch = None

        # Command
        launch_arguments = gameplay_info['command']

        primusrun = system_config.get('primusrun')
        if primusrun and system.find_executable('primusrun'):
            launch_arguments.insert(0, 'primusrun')

        prefix_command = system_config.get("prefix_command") or ''
        if prefix_command.strip():
            launch_arguments.insert(0, prefix_command)

        terminal = system_config.get('terminal')
        if terminal:
            terminal = system_config.get("terminal_app",
                                         system.get_default_terminal())
            if terminal and not system.find_executable(terminal):
                dialogs.ErrorDialog("The selected terminal application "
                                    "could not be launched:\n"
                                    "%s" % terminal)
                return False
        # Env vars
        env = {}
        game_env = gameplay_info.get('env') or {}
        env.update(game_env)

        ld_preload = gameplay_info.get('ld_preload')
        if ld_preload:
            env["LD_PRELOAD"] = ld_preload

        ld_library_path = []
        if self.use_runtime(system_config):
            env['STEAM_RUNTIME'] = os.path.join(settings.RUNTIME_DIR, 'steam')
            ld_library_path += runtime.get_runtime_paths()

        game_ld_libary_path = gameplay_info.get('ld_library_path')
        if game_ld_libary_path:
            ld_library_path.append(game_ld_libary_path)

        if ld_library_path:
            ld_full = ':'.join(ld_library_path)
            env["LD_LIBRARY_PATH"] = "{}:$LD_LIBRARY_PATH".format(ld_full)
        # /Env vars

        self.game_thread = LutrisThread(launch_arguments,
                                        runner=self.runner, env=env,
                                        rootpid=gameplay_info.get('rootpid'),
                                        term=terminal)
        self.state = self.STATE_RUNNING
        if hasattr(self.runner, 'stop'):
            self.game_thread.set_stop_command(self.runner.stop)
        self.game_thread.start()
        if 'joy2key' in gameplay_info:
            self.joy2key(gameplay_info['joy2key'])
        xboxdrv_config = system_config.get('xboxdrv')
        if xboxdrv_config:
            self.xboxdrv_start(xboxdrv_config)
        if self.runner.is_watchable:
            # Create heartbeat every
            self.heartbeat = GLib.timeout_add(HEARTBEAT_DELAY, self.beat)

    def joy2key(self, config):
        """Run a joy2key thread."""
        if not system.find_executable('joy2key'):
            logger.error("joy2key is not installed")
            return
        win = "grep %s" % config['window']
        if 'notwindow' in config:
            win += ' | grep -v %s' % config['notwindow']
        wid = "xwininfo -root -tree | %s | awk '{print $1}'" % win
        buttons = config['buttons']
        axis = "Left Right Up Down"
        rcfile = os.path.expanduser("~/.joy2keyrc")
        rc_option = '-rcfile %s' % rcfile if os.path.exists(rcfile) else ''
        command = "sleep 5 "
        command += "&& joy2key $(%s) -X %s -buttons %s -axis %s" % (
            wid, rc_option, buttons, axis
        )
        joy2key_thread = LutrisThread(command)
        self.game_thread.attach_thread(joy2key_thread)
        joy2key_thread.start()

    def xboxdrv_start(self, config):
        command = [
            "pkexec", "xboxdrv", "--daemon", "--detach-kernel-driver",
            "--dbus", "session", "--silent"
        ] + config.split()
        logger.debug("xboxdrv command: %s", command)
        self.xboxdrv_thread = LutrisThread(command)
        self.xboxdrv_thread.set_stop_command(self.xboxdrv_stop)
        self.xboxdrv_thread.start()

    def xboxdrv_stop(self):
        os.system("pkexec xboxdrvctl --shutdown")

    def beat(self):
        """Watch game's process."""
        self.game_log = self.game_thread.stdout
        killswitch_engage = self.killswitch and \
            not os.path.exists(self.killswitch)
        if not self.game_thread.is_running or killswitch_engage:
            self.on_game_quit()
            return False
        return True

    def stop(self):
        self.game_thread.stop(killall=True)

    def on_game_quit(self):
        """Restore some settings and cleanup after game quit."""
        self.heartbeat = None
        quit_time = time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime())
        logger.debug("game has quit at %s" % quit_time)
        self.state = self.STATE_STOPPED
        if self.resolution_changed\
           or self.runner.system_config.get('reset_desktop'):
            display.change_resolution(self.original_outputs)

        if self.runner.system_config.get('restore_gamma'):
            display.restore_gamma()

        if self.runner.system_config.get('xboxdrv'):
            self.xboxdrv_thread.stop()

        if self.game_thread:
            self.game_thread.stop()