Source

fsgamer / fsgamer / fsgamer_app.py

Full commit
import json
import os
import time
import subprocess

import optparse
import signal

import sys
import re

import utils
import xorgutils

from xdg_wrapper import *

DEFAULT_ICON = 'gtk-file'
DESKTOP_TEMPLATE = """
    [Desktop Entry]
    Comment=%(comment)s
    Name=%(name)s
    Exec=%(exec)s
    Terminal=false
    Version=
    Type=Application
    Categories=Game;
    Icon=%(icon)s
""".replace("    ", "").strip()


def _load_de(fn):
    try:
        new_entry = d.DesktopEntry(filename=fn)
    except:
        # Catch all exception just to keep things rolling incase one of
        # these is invalid in a way I cannot even imagine
        warning("Uh oh, could not load ", fn)
    return new_entry


def get_xdg_applications():
    lst = []

    def add_menu_to_list(menu, depth = 0):
        for entry in menu.getEntries():
            if isinstance(entry, xdg.Menu.Menu):
                add_menu_to_list(entry, depth)
            elif isinstance(entry, xdg.Menu.MenuEntry):
                lst.append((menu.getPath(), entry.DesktopFileID, entry.DesktopEntry.getFileName()))
    add_menu_to_list(xdg.Menu.parse())

    desktop_entries = []
    for menu_path, fileid, fn in lst:
        if fn.endswith(".fsgamer.desktop"):
            # One of our own, skip
            continue

        new_entry = _load_de(fn)
        if new_entry.getName().lower().strip() == 'fsgamer':
            # It's probably me!
            continue

        new_entry.filename = fn
        desktop_entries.append(new_entry)
    return desktop_entries


class DesktopEntryList(list):
    def __init__(self, initial=None):
        entries = initial or get_xdg_applications()
        list.__init__(self, entries)

    def filter_games(self):
        def game_filter(item):
            l = map(str.lower, item.getCategories())
            return any(map(lambda s: 'game' in s,  l))
        return DesktopEntryList(filter(game_filter, self))

    def __repr__(self):
        s = u''
        for item in self:
            s += u'%s - Icon(%s) - Categories(%s)\n' % (repr(item.getName()), repr(item.getIcon()), repr(item.getCategories()))
        return s


def _config_path():
    path = os.path.join(xdg.BaseDirectory.xdg_config_home, 'fsgamer')
    if not os.path.exists(path):
        os.makedirs(path)
    return os.path.abspath(path)


def _local_share_applications_path():
    p = os.path.join(utils.HOMEDIR, ".local", "share", "applications")
    if not os.path.exists(p):
        os.makedirs(p)
    return os.path.abspath(p)


class ApplicationList(list):
    def __init__(self, path=None):
        # Defaults to home directory
        list.__init__([])

        if not path:
            path = _config_path()

        for dirpath, dirnames, filenames in os.walk(path):
            for filename in filenames:
                if filename.endswith('.desktop'):
                    path = os.path.join(dirpath, filename)
                    app = Application(desktop=_load_de(path), filepath=path)
                    self.append(app)

TITLE_TRANS = ''.join(chr(c) if chr(c).isupper() or chr(c).islower() else '_' for c in range(256))


def _to_filename(s):
    return s.translate(TITLE_TRANS).strip('_')


class Application(object):
    def __init__(self, desktop=None, command=None, filepath=None, options={}):
        self.filepath = filepath
        self.desktop = desktop
        self.command = command
        self.opts = {}

        # If the application is running, this is set to the
        # xorgutils.XorgProcess for the new X server
        self.xorg_process = None

        if self.desktop:
            e = self.desktop.getExec().strip()
            if e.startswith(utils.BINPATH) or 'fsgamer' in e:
                # Strip away our options for its internal representation
                options, args = utils.parse_options(e)
                self.desktop.set('Exec', options.exe)
                self.opts = options.opts

    def get_xdg_desktop(self):
        if self.desktop:
            return self.desktop

        command = self.command

        # Allow ~
        if command.startswith('~'):
            command = os.path.expanduser(command)

        # If its a path...
        if os.path.exists(command):
            command = os.path.realpath(command)

        # Otherwise, it is a command
        s = DESKTOP_TEMPLATE % {
            'comment': self.command,
            'name': self.get_name(),
            'exec': command,
            'icon': DEFAULT_ICON,
        }

        tmp_path = utils.write_tmp_file('desktop/%s' % self.get_filename(), s)
        return d.DesktopEntry(tmp_path)

    @staticmethod
    def from_cli(options, args):
        opts = {}

        if options.opts:
            opt = options.opts

        if options.desktop:
            a = Application.from_filepath(options.desktop, opts)
        else:
            a = Application.from_command(options.exe, opts)

        return a

    @staticmethod
    def from_filepath(filepath, opts={}):
        desktop = d.DesktopEntry(filepath)
        a = Application(desktop=desktop, filepath=filepath)
        a.opts.update(opts)
        return a

    @staticmethod
    def from_command(command, opts={}):
        a = Application(command=command)
        a.opts.update(opts)
        return a

    def get_name(self):
        if self.command:
            # figure out
            name = self.command
            try: name = name.split(' ')[0].split('/')[-1]
            except: pass
            name = name.capitalize()
            name = name.replace('_', ' ')
        else:
            name = self.desktop.getName()
        return name


    def write_desktop(self, path=None, name_suffix=""):
        old_name = self.get_name()

        # Get or create a xdg desktop object
        desktop = self.get_xdg_desktop()
        if name_suffix:
            desktop.set('Name', desktop.getName() + name_suffix)

        if not path:
            base_path = _config_path()
            filename = self.get_filename()
            path = os.path.join(base_path, filename)

        # Set exec
        old_exec = desktop.getExec()
        exe = self.build_exec()
        desktop.set('Exec', exe)

        # Use xdg's built in writer
        desktop.write(path)

        # Reset properties that we overwrote
        desktop.set('Name', old_name)
        desktop.set('Exec', old_exec)

    def build_exec(self):
        d = {
            'opts': self.opts,
            'exec': self.get_exec(),
        }

        if self.filepath:
            d['desktop'] = self.filepath

        return utils.unparse_options(d)

    def get_exec(self):
        if self.command:
            return self.command
        else:
            return self.desktop.getExec()

    def get_pixbuf(self, icon_theme, size=64):
        if self.desktop:
            try:
                pixbuf = icon_theme.load_icon(self.desktop.getIcon(), size, 0)
            except:
                pixbuf = icon_theme.load_icon(DEFAULT_ICON, size, 0)
        else:
            pixbuf = icon_theme.load_icon(DEFAULT_ICON, size, 0)
        return pixbuf

    def run_outer(self, replace=True):
        """
        Runs application, with wrapper (ie, to display GUI on this desktop, and
        then run_inner on another xsession)
        """
        e = self.build_exec()

        # Replace this process with outer shell:
        utils.shell_run(e, replace=True)

    def get_exec_inner_script(self):
        if self.command:
            e = self.command
        else:
            e = self.desktop.getExec()

        inner_script = ''
        if self.opts.get('xserver_script', '').strip():
            inner_script += "%s &\n" % self.opts['xserver_script'].strip()
        inner_script += "exec %s" % e
        return inner_script

    def get_xinit_fsgwm(self, fake_shell=False, display=1):
        script = ''

        if self.opts.get('before_script', '').strip():
            script += "%s &\n" % self.opts['before_script'].strip()

        # Get contents of inner script
        inner_script = self.get_exec_inner_script()

        # Wrap it in a sh and get the path to that shell
        inner_script_sh = utils.shell_run.sh_from_script(inner_script, fake_shell)

        # Build an xinit openbox type command that runs that inner script
        script += utils.shellbuilder.xinit_fsgwm(inner_script_sh, display, self.opts)

        return script


    def run(self):
        """
        Runs application on the other desktop (without GUI)
        """
        if self.xorg_process:
            utils.notice("Trying to run while already running.")
            return

        # This will check what Xorgs are running, and which displays are
        # locked, and from that determine what is the next display
        display, xorg_list = xorgutils.before_launching_xinit()

        # This is the key function that actually runs this command in a
        script = self.get_xinit_fsgwm(display=display)

        # This is the key function that actually runs this command in a
        # separate xsession
        utils.shell_run(script)
        
        # Here we use the xorg_list we got previously along with our display to
        # generate a nice helper XorgProcess helper object to help us manage
        # the new X server we spawned.
        time.sleep(5) # sleep for 5 seconds just to give time for Xorg to switch vt
        self.xorg_process = xorgutils.after_launching_xinit(display, xorg_list)

    def kill(self):
        """
        Kills application if running
        """
        if not self.xorg_process:
            utils.notice("How do you kill that which is already dead?")
            return

        # Will kindly kill, then brutally kill process
        self.xorg_process.kill()
        self.xorg_process = None

    def delete(self):
        if self.filepath:
            os.remove(self.filepath)
        self.remove_menu_icon()

    def get_filename(self):
        if self.filepath and os.path.basename(self.filepath):
            base_filename = os.path.splitext(os.path.basename(self.filepath))[0]
        else:
            base_filename = re.sub(r'\W+', '_', self.get_name().lower()).strip('_')

        # re-saving a .fsgamer.desktop file ends up doubling fsgamer
        # extensions, this prevents it ------v
        base_filename = base_filename.replace('.fsgamer', '')

        return "%s.fsgamer.desktop" % base_filename

    def get_menu_icon_path(self):
        """
        Gets the path for where this application would go if it were a menu
        icon
        """
        base_path = _local_share_applications_path()
        filename = self.get_filename()
        path = os.path.join(base_path, filename)
        return path

    def menu_icon_exists(self):
        path = self.get_menu_icon_path()
        return os.path.exists(path)

    def write_menu_icon(self):
        d_path = self.get_menu_icon_path()
        self.write_desktop(d_path, " - FSGamer")

    def remove_menu_icon(self):
        path = self.get_menu_icon_path()
        if os.path.exists(path):
            os.remove(path)

    def toggle_menu_icon(self):
        path = self.get_menu_icon_path()
        if os.path.exists(path):
            os.remove(path)
        else:
            self.write_menu_icon()


def main():
    a = ApplicationList()

if __name__ == "__main__":
    main()