#!/opt/thinlinc/libexec/python3
# -*- mode: python; coding: utf-8 -*-
#
# Copyright 2014-2018 Cendio AB.
# For more information, see http://www.cendio.com

import sys
import os
import re
import subprocess
import locale
import shlex
from shutil import which

from thinlinc import hiveconf

from thinlinc import prefix
from thinlinc.profiles import find_x11_session_desktop_file
from thinlinc.xdg.DesktopEntry import DesktopEntry

def argv_to_shell(argv):
    escaped_argv = []
    for arg in argv:
        escaped_argv.append(shlex.quote(arg))
    return " ".join(escaped_argv)

def update_dbus_environ(environ):
    if which("dbus-update-activation-environment") is None:
        return

    print('Updating D-Bus and systemd environment...')

    # We need to modify it, so get a local copy
    environ = environ.copy()

    # These are session specific and shouldn't be pushed to a common
    # daemon (only XDG_SESSION_ID is generally set in ThinLinc)
    for name in ["XDG_SEAT", "XDG_SESSION_ID", "XDG_VTNR"]:
        if name in environ:
            del environ[name]

    # systemd is really fussy about environment variables so we need to
    # filter out anything that might upset it
    for name in list(environ.keys()):
        # surrogates means something with an invalid encoding was found
        if re.search(r'[\udc80-\udcff]', name) is not None or \
           re.search(r'[\udc80-\udcff]', environ[name]) is not None:
            print("Ignoring malformed environment variable: %r=%r" % \
                  (name, environ[name]))
            del environ[name]
            continue

        # dbus/systemd only supports UTF-8 right now
        if sys.getfilesystemencoding() != 'utf-8':
            try:
                name.encode("ascii")
                environ[name].encode("ascii")
            except UnicodeEncodeError:
                print("Ignoring non-ASCII environment variable on non-UTF-8 system: %r=%r" % \
                      (name, environ[name]))
                del environ[name]
                continue

        # (regexps taken from gnome-session)
        if not re.match("^[a-zA-Z_][a-zA-Z0-9_]*$", name):
            print("Ignoring invalid environment variable: %r" % name)
            del environ[name]
            continue
        if not re.match("^([ \t\r\n\v\f]|[^\x00-\x1F\x7F])*$", environ[name]):
            print("Ignoring environment variable with invalid value: %r=%r" % \
                  (name, environ[name]))
            del environ[name]
            continue

    sys.stdout.flush()

    p = subprocess.Popen(["dbus-update-activation-environment",
                          "--systemd", "--all"],
                         env=environ)
    p.wait()
    if p.returncode != 0:
        print("tl-run-profile: Failed to update D-Bus and systemd environment", file=sys.stderr)

def main():
    locale.setlocale(locale.LC_ALL, "")

    if len(sys.argv) != 1:
        print("Usage:", file=sys.stderr)
        print("    %s" % sys.argv[0], file=sys.stderr)
        sys.exit(1)

    # $TLCOMMAND overrides any set profile
    if "TLCOMMAND" in os.environ:
        # A TLCOMMAND environment variable may be set but have no
        # value. We want to catch that and print a warning message.
        if not os.getenv("TLCOMMAND").strip():
            print("tl-run-profile: Ignoring existing but empty TLCOMMAND.", file=sys.stderr)
        else:
            update_dbus_environ(os.environ)
            print("Executing start program command: %s" % os.environ["TLCOMMAND"])
            sys.stdout.flush()
            try:
                os.execv("/bin/bash", ["/bin/bash", "-c", os.environ["TLCOMMAND"]])
            except OSError as e:
                print("tl-run-profile: Failed to execute command:", e, file=sys.stderr)
                sys.exit(1)

    if "TLPROFILE" not in os.environ:
        print("tl-run-profile: Neither a command nor profile specified. Nothing to execute.", file=sys.stderr)
        sys.exit(1)

    profile = os.environ["TLPROFILE"]

    print("Executing profile: %s" % profile)

    hive = hiveconf.open_hive(os.path.join(prefix.get_tl_prefix(),
                                           "etc", "thinlinc.hconf"))

    if hive.lookup("/profiles/%s" % profile) is None:
        print('tl-run-profile: Profile "%s" does not exist' % profile, file=sys.stderr)
        sys.exit(1)

    # Find and parse the designated system session file
    xdg_session = hive.get_string_list("/profiles/%s/xdg_session" % profile)

    if (xdg_session is None) or (xdg_session == []):
        xdg_argv = None
        xdg_session = None
        xdg_desktop_names = None
    else:
        fn = find_x11_session_desktop_file(xdg_session)
        if fn is None:
            print('tl-run-profile: No XDG session file matching "%s" could be found' % xdg_session, file=sys.stderr)
            sys.exit(1)

        xdg_session = os.path.basename(fn)
        assert xdg_session.endswith(".desktop")
        xdg_session = xdg_session[:-len(".desktop")]

        print('Using XDG session: %s' % xdg_session)

        try:
            xdg_entry = DesktopEntry(fn)
        except Exception as e:
            print('tl-run-profile: Could not open XDG session file:', e, file=sys.stderr)
            sys.exit(1)

        if not xdg_entry.hasKey("Exec"):
            xdg_argv = None
        else:
            xdg_argv = xdg_entry.getExecArgv()
            # Clean up % markers
            _argv = []
            for arg in xdg_argv:
                if "%" not in arg:
                    _argv.append(arg)
                    continue

                new_arg = []
                perc = False
                for c in arg:
                    if perc:
                        if c == "%":
                            new_arg.append("%")
                        perc = False
                        continue

                    if c == "%":
                        perc = True
                        continue

                    new_arg.append(c)
                _argv.append("".join(new_arg))
            xdg_argv = _argv

        if xdg_entry.hasKey("DesktopNames"):
            xdg_desktop_names = xdg_entry.get("DesktopNames", list=True)
        elif xdg_entry.hasKey("X-LightDM-DesktopName"):
            # For Ubuntu
            name = xdg_entry.get("X-LightDM-DesktopName")
            xdg_desktop_names = []
            if len(name) > 0:
                xdg_desktop_names.append(name)
        else:
            xdg_desktop_names = None

    cmdline = hive.get_string("/profiles/%s/cmdline" % profile)

    # Prepare environment
    environ = os.environ.copy()

    if xdg_session is not None:
        environ["DESKTOP_SESSION"] = xdg_session
        environ["XDG_SESSION_DESKTOP"] = xdg_session
    if xdg_desktop_names is not None:
        environ["XDG_CURRENT_DESKTOP"] = ":".join(xdg_desktop_names)

    if xdg_session == "ubuntu":
        # Unity requires this to start correctly
        environ["COMPIZ_CONFIG_PROFILE"] = "ubuntu"

    # Ubuntu 13.10 and later wants most sessions to start via upstart
    if os.path.exists("/etc/upstart-xsessions"):
        with open("/etc/upstart-xsessions") as f:
            lines = f.readlines()
        for line in lines:
            if line.strip() == xdg_session:
                if (xdg_argv is not None) and (xdg_argv != []):
                    # Upstart needs this to start the correct jobs
                    if "gnome-session" in xdg_argv[0] or \
                       "gnome-flashback" in xdg_argv[0]:
                        environ["SESSIONTYPE"] = "gnome-session"
                    elif "lxsession" in xdg_argv[0]:
                        environ["SESSIONTYPE"] = "lxsession"

                # 14.10 and later uses the command "upstart" rather
                # than "init" as the init system might be systemd
                if which("upstart") is not None:
                    xdg_argv = ["upstart", "--user"]
                else:
                    xdg_argv = ["init", "--user"]

                break

    # We've set up all the special environment variables so we
    # are now ready to push them to dbus (and systemd)
    update_dbus_environ(environ)

    if (cmdline is not None) and (cmdline != ""):
        # Explicit command line
        print('Executing profile command: %s' % cmdline)
        argv = ["/bin/bash", "-c", cmdline]
        if (xdg_argv is not None) and (xdg_argv != []):
            environ["XDG_EXEC"] = argv_to_shell(xdg_argv)
    elif (xdg_argv is not None) and (xdg_argv != []):
        # Command from system session file
        print('Executing XDG session command: %s' % argv_to_shell(xdg_argv))
        argv = xdg_argv
    else:
        print('tl-run-profile: Profile "%s" has no command specified' % profile, file=sys.stderr)
        sys.exit(1)

    sys.stdout.flush()

    try:
        os.execvpe(argv[0], argv, environ)
    except OSError as e:
        print("tl-run-profile: Failed to execute command:", e, file=sys.stderr)
        sys.exit(1)

if __name__ == "__main__":
    main()
