#!/usr/bin/python
descr = """
dogtail-run-headless-next

Unlike the original headless script this will make use of an Display Manager
(DM - currently gdm) to handle starting the X server and user session. It's motivated
by  changes related to systemd - that disallows running a gnome session from an
environment spawned by 'su'. The original headless will not work in these cases
anymore on systemd systems

Instead this script uses the AutoLogin feature of the DM, so that when it starts DM's
service the session will login for particular user at once. It then uses the
environment properties from the new session and runs the target script inthere.

Will work with distros where 'service gdm/kdm start/stop' takes an effect, but
you can also specify an alternative like 'init 5/init 3' to use with systems that
i.e. make use of initscripts.

Even if you are still able to use dogtail-run-headless in your usecase, you might
consider switching to this scrpt - as making use of DM is likely to be more reliable
and less buggy compared to headless itself taking care of everything.

Please take a look at --help for usage info.

Limitations
--
Currently limited only to gnome-session, GDM and systemd (will extend soon).

"""

drop_overview = '''from dogtail.utils import absoluteMotion, keyPress
absoluteMotion(100,100)
keyPress('esc')'''

import argparse
import sys
import os
import glob
import subprocess
import time
import ConfigParser
import shutil
import re
from dogtail.sessions import Script

preserve_envs = ['PYTHONPATH', 'TEST']

def getSessionEnvironment(sessionBinary):

    def isSessionProcess(fileName):
        try:
            if os.path.realpath(fileName + 'exe') != ('/usr/bin/plasma-desktop' \
                        if sessionBinary.split('/')[-1] == 'startkde'
                        else sessionBinary):
                return False
        except OSError:
            return False
        pid = fileName.split('/')[2]
        if pid == 'self' or pid == str(os.getpid()): return False
        return True

    def getEnvDict(fileName):
        try: envString = open(fileName, 'r').read()
        except IOError: return {}
        envItems = envString.split('\x00')
        envDict = {}
        for item in envItems:
            if not '=' in item: continue
            k, v = item.split('=', 1)
            envDict[k] = v
        return envDict

    def copyVars(envDict):
        '''Copy a couple of old variables we want to preserve'''
        for env in preserve_envs:
            if os.environ.has_key(env):
                envDict[env] = os.environ[env]
        return envDict


    envDict = False
    for path in glob.glob('/proc/*/'):
        if not isSessionProcess(path): continue
        envFile = path + 'environ'
        envDict = getEnvDict(envFile)
    if not envDict:
        raise RuntimeError("Can't find our environment!")
    return copyVars(envDict)

def execCodeWithEnv(code, env=None):
    with open("/tmp/execcode.dogtail", "w") as text_file:
        text_file.write(code)
    subprocess.Popen('python /tmp/execcode.dogtail'.split(), env=(os.environ if env is None else env)).wait()

class DisplayManagerSession(object):

    gdm_config = '/etc/gdm/custom.conf'
    kdm_config = '/etc/kde/kdm/kdmrc'
    gdm_options = {'section':'daemon', 'enable':'AutomaticLoginEnable', 'user':'AutomaticLogin'}
    kdm_options = {'section':'X-:0-Core', 'enable':'AutoLoginEnable', 'user':'AutoLoginUser'}
    scriptDelay = 20
    user = 'test'

    def isProcessRunning(self, process):
        doc='''Gives true if process can be greped out of full ps dump '''
        s = subprocess.Popen(["ps", "axw"],stdout=subprocess.PIPE)
        for x in s.stdout:
            if re.search(process, x):
                return True
        return False

    def waitForProcess(self, process, invert=False):
        doc='''Waits until a process appears'''
        while self.isProcessRunning(process) is invert:
            time.sleep(1)

    def __init__(self, dm = 'gdm', user = None):
        if user is not None:
            self.user = user
        if dm == 'gdm':
            self.tmp_file = '/tmp/%s' % os.path.basename(self.gdm_config)
            self.options = self.gdm_options
            self.config = self.gdm_config
        elif dm == 'kdm':
            self.tmp_file = '/tmp/%s' % os.path.basename(self.kdm_config)
            self.options = self.kdm_options
            self.config = self.kdm_config
        self.dm = dm

    def setup(self, restore=False):
        shutil.copy(self.config, self.tmp_file)
        config = ConfigParser.SafeConfigParser()
        config.optionxform = str
        config.read(self.tmp_file)
        if not restore:
            config.set(self.options['section'], self.options['enable'], 'true')
            config.set(self.options['section'], self.options['user'], self.user)
        else:
            config.remove_option(self.options['section'], self.options['enable'])
            config.remove_option(self.options['section'], self.options['user'])
        output = open(self.tmp_file, 'w')
        config.write(output)
        output.flush()
        subprocess.Popen('sudo mv -f %s %s' % (self.tmp_file , self.config), shell=True).wait()
        if self.dm == 'kdm':
            try:
                os.makedirs(os.getenv('HOME') + '/.kde/env/')
            except:
                pass
            subprocess.Popen('echo "export QT_ACCESSIBILITY=1" > ~/.kde/env/qt-at-spi.sh', shell=True).wait()
            subprocess.Popen('echo "[Desktop]\nSession=kde-plasma" > ~/.dmrc', shell=True).wait()

    def start(self, session_binary):
        subprocess.Popen(('sudo service %s start' % (self.dm)).split())
        self.session_binary = session_binary
        self.waitForProcess(session_binary)
        # some extra time for an environment (shell/kwin) to load all resources etc.
        if self.dm == 'kdm':
            time.sleep(10) # KDE keeps loading screen on untill all is loaded
        else:
            time.sleep(4) # GNOME shows stuff as it appears

    def setA11y(self, enable):
        subprocess.Popen('/usr/bin/gsettings set org.gnome.desktop.interface toolkit-accessibility %s' \
                         % ('true' if enable else 'false'), shell=True, env=os.environ)
#       if enable: # mouse is at 0x0 at the start - which brings in the overview
#            execCodeWithEnv(drop_overview, env = os.environ)
#            time.sleep(2) # time for the overview to go away

    def stop(self):
        subprocess.Popen(('sudo service %s stop' % (self.dm)).split()).wait()
        self.waitForProcess('/usr/bin/%s' % self.dm, invert=True)
        time.sleep(3) # extra safe time
        # did i.e. gnome-shell get stuck running?
        if self.isProcessRunning(self.session_binary):
            print('dogtail-run-headless-next: WARNING: %s still running, proceeding with kill -9' % self.session_binary)
            subprocess.Popen(('sudo pkill --signal 9 %s' % (self.session_binary)).split()).wait()
            time.sleep(1)


def parse():
    import argparse
    parser = argparse.ArgumentParser(prog='$ dogtail-run-headless-next', description=descr)
    parser.add_argument('script', help="""Command to execute the script""")
    parser.add_argument('--session', required=False, help="""What session to use, 'kde' or 'gnome' (default).""")
    parser.add_argument('--dont-start', action='store_true', help="""Use already running session (doesn't have to be under Display Manager)""")
    parser.add_argument('--dont-kill', action='store_true', help="""Do not kill session when script exits.""")
    parser.add_argument('--disable-a11y', action='store_true', help="""Disable accessibility technologies on script(not session) exit.""")
    parser.add_argument('--remove-overview', action='store_true', help="""Just moves mouse from 0,0 and turns off the overview.""")
    return parser.parse_args()

def main():
    args = parse()
    scriptCmdList = args.script.split()

    if args.session == 'gnome' or args.session is None:
        dm_name = 'gdm'
        session_binary = '/usr/bin/gnome-shell'
    elif args.session == 'kde':
        dm_name = 'kdm'
        session_binary = '/usr/bin/kwin'
    else:
        print('dogtail-run-headless-next: Do not recognize the session!')
        sys.exit(-1)

    dm = DisplayManagerSession(dm_name)

    if args.dont_start is not True:
        dm.setup()
        dm.start(session_binary.split('/')[-1])

    os.environ = getSessionEnvironment(session_binary)

    if dm_name == 'gdm':
        dm.setA11y(True)

    script = Script(scriptCmdList)
    scriptPid = script.start()
    exitCode = script.wait()

    if args.disable_a11y is True:
        dm.setA11y(False)

    if args.dont_kill is False:
        dm.stop()
        dm.setup(restore=True)

    sys.exit(exitCode)

if __name__ == "__main__": main()
