#!/usr/bin/python

import os
import sys
import subprocess
import time
import signal
import errno
import imp
import grp

sys.path.insert(0, '/usr/lib/ubiquity')

import ubiquity.frontend
import pwd

from ubiquity import osextras

background = '/usr/share/backgrounds/warty-final-ubuntu.png'

class XStartupError(EnvironmentError):
    pass

class MissingProgramError(EnvironmentError):
    pass

class DM:
    def __init__(self, vt, display):
        self.vt = vt
        self.display = display
        self.server_started = False

        fp = open('/etc/casper.conf', 'r')
        try:
            for line in fp:
                if line.startswith('#'):
                    continue
                line = line.split()
                if not line:
                    continue
                if line[0] == 'export':
                    w = line[1].split('=')
                    if w[0] == 'USERNAME':
                        self.username = w[1].strip('"')
        except IOError:
            import syslog
            syslog.syslog('Unable to determine the username from /etc/casper.conf, using the default.')
            self.username = 'ubuntu'
        finally:
            fp.close()
        self.uid, self.gid = pwd.getpwnam(self.username)[2:4]
        self.homedir = pwd.getpwnam(self.username)[5]
        self.uid = int(self.uid)
        self.gid = int(self.gid)
        self.groups = []
        for g in grp.getgrall():
            if self.username in g[3] or g[0] == self.username:
                self.groups.append(g[2])

        # Look for a frontend module; we won't actually use it (yet), but
        # this lets us find out which window manager etc. to launch. Be
        # careful that importing this here will cause the underlying library
        # to try to talk to the X server, which won't go well.
        frontend_names = ['mythbuntu_ui', 'gtk_ui', 'kde_ui']
        self.frontend = None
        for f in frontend_names:
            try:
                imp.find_module(f, ubiquity.frontend.__path__)
                self.frontend = f
                break
            except ImportError:
                pass
        else:
            raise AttributeError, ('No frontend available; tried %s' %
                                   ', '.join(frontend_names))

    def sigusr1_handler(self, signum, frame):
        self.server_started = True

    def drop_privileges(self):
        os.setgroups(self.groups)
        os.setgid(self.gid)
        os.setuid(self.uid)

    def server_preexec(self):
        signal.signal(signal.SIGUSR1, signal.SIG_IGN)

    def run(self, *program):
        null = open('/dev/null', 'w')
        if not os.path.exists('/var/log/installer'):
            os.makedirs('/var/log/installer')
        logfile = open('/var/log/installer/dm', 'w')

        signal.signal(signal.SIGUSR1, self.sigusr1_handler)
        signal.signal(signal.SIGTTIN, signal.SIG_IGN)
        signal.signal(signal.SIGTTOU, signal.SIG_IGN)

        servercommand = ['X', '-br', '-ac', '-noreset', self.vt, self.display]
        if self.frontend == 'mythbuntu_ui':
            servercommand.append('-dpi')
            servercommand.append('100')

        server = subprocess.Popen(servercommand, stdin=null, stdout=logfile, stderr=logfile, preexec_fn=self.server_preexec)

        os.environ['DISPLAY'] = self.display
        os.environ['HOME'] = self.homedir

        # Really we should select on a pipe or something, but it's not worth
        # the effort for now.
        timeout = 60
        while not self.server_started:
            if timeout == 0:
                raise XStartupError, "X server failed to start after 60 seconds"
            time.sleep(1)
            timeout -= 1

        extras = []
        if self.frontend == 'gtk_ui' or self.frontend == 'mythbuntu_ui':
            # Accessibility infrastructure
            gconf_dir = ('xml:readwrite:%s' %
                '/root/.gconf')
            accessibility = 'false'
            if osextras.find_on_path('gconftool-2'):
                subp = subprocess.Popen(
                    ['gconftool-2', '--config-source', gconf_dir,
                     '--get', '/desktop/gnome/interface/accessibility'],
                    stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                accessibility = subp.communicate()[0].rstrip('\n')
            if accessibility == 'true':
                if os.path.exists('/usr/lib/at-spi/at-spi-registryd'):
                    extras.append(subprocess.Popen(['/usr/lib/at-spi/at-spi-registryd'],
                        stdin=null, stdout=logfile, stderr=logfile))
                    os.environ['GTK_MODULES'] = 'gail:atk-bridge'

            maybe_drop_privileges = {}
            if accessibility != 'true':
                maybe_drop_privileges['preexec_fn'] = self.drop_privileges

            # Don't show the background image in v1 accessibility profile. It
            # is not shown in the GNOME desktop after all.
            fp = open('/proc/cmdline', 'r')
            if os.access(background, os.R_OK) and not 'access=v1' in fp.readline():
                import gtk
                pixbuf = gtk.gdk.pixbuf_new_from_file(background)
                root = gtk.gdk.get_default_root_window()
                (root_width, root_height) = root.get_size()
                scaled = pixbuf.scale_simple(root_width, root_height, gtk.gdk.INTERP_BILINEAR)
                (pixmap, mask) = scaled.render_pixmap_and_mask(0)
                root.set_back_pixmap(pixmap, False)
                root.clear()
                gtk.gdk.flush()
            fp.close()

        if self.frontend in ('gtk_ui', 'mythbuntu_ui'):
            if osextras.find_on_path('metacity'):
                wm = subprocess.Popen(['metacity', '--sm-disable'],
                    stdin=null, stdout=logfile, stderr=logfile,
                    **maybe_drop_privileges)
            elif osextras.find_on_path('xfwm4'):
                wm = subprocess.Popen('xfwm4',
                    stdin=null, stdout=logfile, stderr=logfile,
                    **maybe_drop_privileges)
            else:
                raise MissingProgramError, \
                    "No window manager found (tried metacity, xfwm4)"

            if os.path.exists('/usr/lib/gnome-settings-daemon/gnome-settings-daemon'):
                extras.append(subprocess.Popen(['/usr/lib/gnome-settings-daemon/gnome-settings-daemon'],
                    stdin=null, stdout=logfile, stderr=logfile, **maybe_drop_privileges))

            # Accessibility tools
            if accessibility == 'true':
                fp = open('/proc/cmdline', 'r')
                if 'access=m2' in fp.readline():
                    if osextras.find_on_path('onboard'):
                        extras.append(subprocess.Popen(['onboard'],
                            stdin=null, stdout=logfile, stderr=logfile))
                else:
                    if osextras.find_on_path('orca'):
                        time.sleep(15)
                        extras.append(subprocess.Popen(['orca', '-n'],
                            stdin=null, stdout=logfile, stderr=logfile))
                fp.close()
        elif self.frontend == 'kde_ui':
            wm = subprocess.Popen('kwin', stdin=null, stdout=logfile,
                stderr=logfile, preexec_fn=self.drop_privileges)
            extras.append(subprocess.Popen(['dcopserver', '--nosid'],
                stdin=null, stdout=logfile, stderr=logfile,
                preexec_fn=self.drop_privileges))

        greeter = subprocess.Popen(program, stdin=null, stdout=logfile, stderr=logfile)
        ret = greeter.wait()

        def sigalrm_handler(signum, frame):
            os.kill(wm.pid, signal.SIGKILL)
            for extra in extras:
                os.kill(extra.pid, signal.SIGKILL)

        os.kill(wm.pid, signal.SIGTERM)
        for extra in extras:
            os.kill(extra.pid, signal.SIGTERM)
        signal.signal(signal.SIGALRM, sigalrm_handler)
        signal.alarm(1) # low patience with WMs failing to exit on demand
        processes = set(extras)
        processes.add(wm)
        while processes:
            done = set()
            for process in processes:
                try:
                    process.wait()
                    done.add(process)
                except OSError, e:
                    if e.errno == errno.EINTR:
                        continue
                    raise
            processes -= done
        signal.alarm(0)
        os.kill(server.pid, signal.SIGTERM)
        server.wait()

        if ret is not None and ret >= 0:
            return ret
        else:
            return 1

if len(sys.argv) < 3:
    sys.stderr.write('Usage: ubiquity-dm <vt[1-N]> <:[0-N]> <program> [<arguments>]\n')
    sys.exit(1)

vt, display = sys.argv[1:3]
dm = DM(vt, display)
proc = None
if dm.frontend == 'gtk_ui' or dm.frontend == 'mythbuntu_ui':
    proc = ['/etc/init.d/gdm', 'start']
elif dm.frontend == 'kde_ui':
    proc = ['/etc/init.d/kdm', 'start']
ret = dm.run(*sys.argv[3:])
if proc:
    subprocess.Popen(proc)
sys.exit(ret)
