#!/usr/bin/python3
# -*- coding: utf-8 -*-
# vim: set et ts=4:

# Filename:      grml-x
# Purpose:       wrapper for startx on grml [providing new xconfiguration tool]
# Authors:       grml-team (grml.org), (c) Christian Hofstaedtler <ch@grml.org>
# Bug-Reports:   see http://grml.org/bugs/
# License:       This file is licensed under the GPL v2.
###############################################################################

import os
import subprocess
import sys
import tempfile
import time
import traceback
from optparse import OptionParser


class Section(object):
    def __init__(self, name, identifier, data):
        self.name = name
        self.identifer = identifier
        self.data = data
        self.subsect = ""

    def __str__(self):
        s = 'Section "%s"\n\tIdentifier "%s"\n' % (self.name, self.identifer)
        for k in self.data:
            v = self.data[k]
            if isinstance(v, list):
                v = '" "'.join(v)
            elif not isinstance(v, str):  # int, others
                v = str(v)
            elif "-" in v:  # sync range
                pass
            else:
                v = '"%s"' % v
            s += "\t%s %s\n" % (k, v)
        s += self.subsect
        s += "EndSection\n"
        return s


def get_monitor_section(options, force):
    if not options.hsync and not options.vsync and not force:
        return None
    d = {}
    d["HorizSync"] = options.hsync or "28.0 - 96.0"
    d["VertRefresh"] = options.vsync or "50.0 - 60.0"
    return Section("Monitor", "Monitor0", d)


def get_device_section(options):
    if not options.module:
        return None
    d = {}
    d["Driver"] = options.module
    d["VendorName"] = "All"
    d["BoardName"] = "All"
    return Section("Device", "Card0", d)


def build_bootparams():
    lines = []

    def walk_bootparams_path(p):
        try:
            if not os.path.exists(p):
                return
            for root, dirs, files in os.walk(p):
                for name in files:
                    f = open(os.path.join(root, name))
                    lines.extend(f.readlines())
                    f.close()
        except Exception:
            print("W: Error while getting bootparams from %s" % p)

    f = open("/proc/cmdline")
    lines.append(f.readline())
    f.close()
    walk_bootparams_path("/lib/live/mount/medium/bootparams")
    walk_bootparams_path("/run/live/medium/bootparams")
    params = {}
    for p in " ".join(lines).split(" "):
        if "=" in p:
            (k, v) = p.split("=", 1)
            params[k] = v
        else:
            params[p] = True
    return params


def detect_qemu():
    f = open("/proc/cpuinfo")
    x = "".join(f.readlines())
    f.close()
    if "QEMU" in x:
        return True
    return False


def get_program_output(args):
    p = subprocess.Popen(
        args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True
    )
    return p.communicate()[0]


def run_program(args):
    subprocess.Popen(args, close_fds=True).wait()


def which(program):
    def is_exe(fpath):
        return os.path.exists(fpath) and os.access(fpath, os.X_OK)

    fpath, fname = os.path.split(program)
    if fpath:
        if is_exe(program):
            return program
    else:
        for path in os.environ["PATH"].split(os.pathsep):
            exe_file = os.path.join(path, program)
            if is_exe(exe_file):
                return exe_file

    return None


XORG_CONF_HEADER = "# Automatically generated by grml-x.\n"


def check_old_xorg_conf(filename, overwrite):
    # True: no problem, we can create/overwrite the config file
    # False: pre-existing config file, and we are not to overwrite it
    if overwrite:
        return True
    if not os.path.exists(filename):
        return True
    try:
        f = open(filename, "r")
        lines = f.readlines()
        f.close()
        return XORG_CONF_HEADER not in lines
    except IOError:
        return False


parser = OptionParser(usage="usage: %prog [options] [window-manager]")
parser.add_option(
    "--nostart",
    action="store_false",
    dest="start_server",
    default=True,
    help="Don't start X server",
)
parser.add_option(
    "--display",
    action="store",
    type="string",
    dest="display",
    help="Start X server on display DISPLAY",
)
parser.add_option(
    "--hsync",
    action="store",
    type="string",
    dest="hsync",
    help="Force writing a HorizSync range",
)
parser.add_option(
    "--vsync",
    action="store",
    type="string",
    dest="vsync",
    help="Force writing a VertRefresh range",
)
parser.add_option(
    "--mode",
    action="store",
    type="string",
    dest="mode",
    help="Force a specific resolution",
)
parser.add_option(
    "--module",
    action="store",
    type="string",
    dest="module",
    help="Force driver MODULE instead of Xorg autodetection",
)
parser.add_option(
    "-o",
    action="store",
    type="string",
    dest="xorg_conf",
    default="/etc/X11/xorg.conf",
    help="Specify alternate xorg.conf file [default: %default]",
)
parser.add_option(
    "-f",
    "--force",
    action="store_true",
    dest="overwrite",
    default=False,
    help="Overwrite xorg.conf if it exists [default: %default]",
)


def main():
    (options, args) = parser.parse_args()
    bootparams = build_bootparams()

    if os.getuid() == 0 and options.start_server:
        print("W: running as root is unsupported and may not work.")
        time.sleep(1)

    if not check_old_xorg_conf(options.xorg_conf, options.overwrite):
        print("E: Not overwriting existing %r without --force." % options.xorg_conf)
        print("I: If you previously ran grml-x, use startx /usr/bin/x-window-manager")
        return 1

    if "xmode" in bootparams and not options.mode:
        options.mode = bootparams["xmode"]
    if "xmodule" in bootparams and not options.module:
        options.module = bootparams["xmodule"]

    force_monitor = False
    # cirrus driver for QEMU doesn't do 1024x768 without HorizSync set
    if detect_qemu():
        force_monitor = True

    monitor = get_monitor_section(options, force_monitor)
    device = get_device_section(options)

    # build Screen section ourselves
    d = {}
    if monitor:
        d["Monitor"] = monitor.identifer
    if device:
        d["Device"] = device.identifer
    screen = Section("Screen", "Screen0", d)
    if options.mode:
        d["DefaultColorDepth"] = 16
        for depth in [8, 15, 16, 24, 32]:
            screen.subsect += (
                'SubSection "Display"\n\tDepth %d\n\tModes "%s"\t\nEndSubSection\n'
                % (depth, options.mode)
            )

    xinitrc = "~/.xinitrc"
    if "XINITRC" in os.environ:
        xinitrc = os.environ["XINITRC"]
    xinitrc = os.path.expanduser(xinitrc)

    window_manager = "x-window-manager"
    if len(args) == 1:
        window_manager = args[0]
    window_manager_path = which(window_manager)
    if not window_manager_path:
        print("E: Cannot find window manager %r, aborting." % window_manager)
        return 2

    wm_exec = "exec %s\n" % window_manager_path
    if not os.path.exists(xinitrc):
        f = open(xinitrc, "w")
        f.write("#!/bin/sh\n")
        f.write(wm_exec)
        f.close()
    else:
        f = open(xinitrc, "r")
        lines = f.readlines()
        f.close()
        f = open(xinitrc, "w")
        for line in lines:
            if line.strip().startswith("exec "):
                line = wm_exec
            f.write(line)
        os.fchmod(f.fileno(), 0o750)
        f.close()

    # write new config
    if monitor or device or len(screen.data) > 0 or screen.subsect != "":
        try:
            f = tempfile.NamedTemporaryFile(mode="w+", delete=False)
            f.write(XORG_CONF_HEADER)
            f.write(
                "# DO NOT MODIFY, YOUR CHANGES WILL BE LOST - OR REMOVE ALL HEADER LINES\n"
            )
            f.write("# See man xorg.conf or /etc/X11/xorg.conf.example for more\n")
            if monitor:
                f.write(str(monitor))
            if device:
                f.write(str(device))
            f.write(str(screen))
            f.flush()
            os.fchmod(f.fileno(), 0o644)
            run_program(["sudo", "mv", "-f", f.name, options.xorg_conf])
        finally:
            f.close()

    if options.start_server:
        startx = ["startx", xinitrc, "--"]
        if options.display:
            startx.append(":" + options.display)
        print("Starting X: %r" % startx)
        run_program(startx)

    return 0


if __name__ == "__main__":
    rc = 1
    try:
        rc = main()
    except Exception:
        print("E: Exception: ", end=" ")
        traceback.print_exc()
    sys.exit(1)
