#!/usr/bin/python
###########################################################################
# displayconfig-restore.py - description                                  #
# ------------------------------                                          #
# begin     : Wed Dec 15 2004                                             #
# copyright : (C) 2004-2006 by Simon Edwards                              #
# email     : simon@simonzone.com                                         #
#                                                                         #
###########################################################################
#                                                                         #
#   This program is free software; you can redistribute it and/or modify  #
#   it under the terms of the GNU General Public License as published by  #
#   the Free Software Foundation; either version 2 of the License, or     #
#   (at your option) any later version.                                   #
#                                                                         #
###########################################################################
import os
import os.path
import subprocess
import ixf86misc
import xf86misc

from execwithcapture import *

############################################################################
def FindXorgConfig(self):
    # Lookup location of X configfile
    for line in ExecWithCapture("xset", ["xset","q"],True).split('\n'):
        if line.strip().startswith("Config file"):
            return line.split(":")[1].strip()
    # Sometimes, xset doesn't know about the configfile location, hence ...
    if os.path.isfile("/etc/X11/xorg.conf"):
            return "/etc/X11/xorg.conf"
    return None

############################################################################
# FixXorgDPI
# ==========
# The idea here is to ensure that applications use a sensible DPI setting
# for fonts. When Xorg starts up it tries to detect the size of the attached
# monitor and calculate the real DPI from there and use that. Problems are:
#
#   * if the monitor size can not be detect then Xorg uses 75dpi. This is
#     usually far too low.
#
#   * if the monitor size is not accurately detected then you get bad a DPI.
#
#   * most fonts are optimised to work at a handful of standard DPIs. 96dpi,
#     120dpi and printer resolution 300dpi and 600dpi. Fonts rendered in
#     non-standard DPIs often look bad and jagged. This is a real problem
#     when rendering fonts on low resolution devices. (i.e. a computer
#     monitor).
#
# Although it is desirable in theory to use the real DPI of the monitor, in
# practice it is more important to ensure that fonts are well rendered even
# if the DPI in use is not correct.
#
# What this function does is read the display size from the X server and
# if it is lower than 140dpi then 'round' it to either 96dpi or 120dpi.
# (A dpi greater or equal to 140 is assumed to be high enough to render fonts
# well.) The new dpi is then loaded with the xrdb command into the X server
# resource database. Most X applications (Qt and GTK apps at least) will then
# use this DPI for font rendering.
#
def FixXorgDPI(desiredDPI):
    # dpi is:
    #       None - round the DPI.
    #       xserver - Use the X server's DPI.
    #       <number> - DPI to use.
    if desiredDPI=="xserver":
        return

    dpi = 96
    try:
        if desiredDPI is not None:
            dpi = int(desiredDPI)
    except ValueError:
        desiredDPI = None

    if desiredDPI is None:
        xserver = xf86misc.XF86Server()
        if len(xserver.getScreens())!=0:
            (width,height,width_mm,height_mm) = xserver.getScreens()[0].getDimensions()
            if not float(width_mm) == 0:
                w_dpi = float(width)/(float(width_mm)/25.4)
            else:
                w_dpi = 96
            if not float(height_mm) == 0:
                h_dpi = float(height)/(float(height_mm)/25.4)
            else:
                h_dpi = 96
            dpi = (w_dpi+h_dpi)/2.0   # Average the two possible DPIs.

            if dpi >= 140:   # Anything above 140 is ok.
                dpi = int(dpi)
            else:
                if abs(96-dpi) < abs(120-dpi):  # Rounding to 96 is best.
                    dpi = 96
                else:
                    dpi = 120

            # work around for LP beastie 151311
            if ((w_dpi < 200) and (h_dpi > 900)):
                dpi = 96

    try:
        xrdb = subprocess.Popen(["xrdb","-nocpp","-merge"],stdin=subprocess.PIPE)
        xrdb.communicate("Xft.dpi: %i\n" % dpi)
        xrdb.wait()
    except OSError:
        pass

    # Other common, but now used settingsfor xrdb:
    # Xft.antialias: 
    # Xft.hinting:
    # Xft.hintstyle: 
    # Xft.rgba: 

############################################################################
def ReadDisplayConfigRC():
    screens = None
    dpi = None
    dpms_seconds = None
    dpms_enabled = None

    configpath = ExecWithCapture("tde-config",['tde-config','--path','config'],True)

    # Hunt down the user's displayconfigrc file and adjust the resolution
    # on the fly to match. (Non-root Users can independantly specify their own settings.)
    dirs = configpath.strip().split(":")
    for dir in dirs:
        if dir!="":
            configpath = os.path.join(dir,"displayconfigrc")
            if os.path.exists(configpath):
                # Parse the config file.
                fhandle = open(configpath)
                screens = []
                currentscreen = None
                for line in fhandle.readlines():
                    line = line.strip()
                    if line.startswith("[Screen"):
                        # Screen, width, height, refresh, reflectx, reflecty, rotate, redgamma, greengamma,bluegamma
                        currentscreen = [int(line[7:-1]), None, None, None, False, False, "0", None, None, None]
                        screens.append(currentscreen)
                    elif line.startswith("["):
                        currentscreen = None
                    elif line.startswith("dpi="):
                        dpi = line[4:]
                    elif currentscreen is not None:
                        if line.startswith("width="):
                            currentscreen[1] = int(line[6:])
                        elif line.startswith("height="):
                            currentscreen[2] = int(line[7:])
                        elif line.startswith("refresh="):
                            currentscreen[3] = int(line[8:])
                        elif line.startswith("reflectX="):
                            currentscreen[4] = line[9:]=="1"
                        elif line.startswith("reflectY="):
                            currentscreen[5] = line[9:]=="1"
                        elif line.startswith("rotate="):
                            currentscreen[6] = line[7:]
                        elif line.startswith("redgamma="):
                            currentscreen[7] = line[9:]
                        elif line.startswith("greengamma="):
                            currentscreen[8] = line[11:]
                        elif line.startswith("bluegamma="):
                            currentscreen[9] = line[10:]
                        elif line.startswith("dpmsEnabled"):
                            dpms_enabled = line.split("=")[1]
                        elif line.startswith("dpmsSeconds"):
                            dpms_seconds = int(line.split("=")[1])
                fhandle.close()
                break

    return (screens,dpi,dpms_enabled,dpms_seconds)

############################################################################
def main():
    (screens,dpi,dpms_enabled,dpms_seconds) = ReadDisplayConfigRC()

    if dpms_enabled:
        if dpms_enabled == "on":
            if not dpms_seconds:
                dpms_seconds = 900
            cmd = "xset dpms %i %i %i" % (dpms_seconds,dpms_seconds,dpms_seconds)
            os.system(cmd)
        else:
            cmd = "xset -dpms"
            os.system(cmd)

    if screens is not None:
        # Set the X server.
        try:
            xserver = xf86misc.XF86Server()
            if len(screens)!=0:

                for screen in screens:
                    (id,width,height,refresh,reflectx,reflecty,rotate,redgamma,greengamma,bluegamma) = screen

                    # Convert the stuff into RandR's rotation bitfield thingy.
                    if rotate=="0":
                        rotation = xf86misc.XF86Screen.RR_Rotate_0
                    elif rotate=="90":
                        rotation = xf86misc.XF86Screen.RR_Rotate_90
                    elif rotate=="180":
                        rotation = xf86misc.XF86Screen.RR_Rotate_180
                    elif rotate=="270":
                        rotation = xf86misc.XF86Screen.RR_Rotate_270
                    if reflectx:
                        rotation |= xf86misc.XF86Screen.RR_Reflect_X
                    if reflecty:
                        rotation |= xf86misc.XF86Screen.RR_Reflect_Y

                    if id<len(xserver.getScreens()):
                        xscreen = xserver.getScreens()[id]

                        if xscreen.resolutionSupportAvailable():
                            available_sizes = xscreen.getAvailableSizes()

                            # Find the closest matching resolution
                            best_score = 1000000
                            best_size_id = 0
                            for size_id in range(len(available_sizes)):
                                size = available_sizes[size_id]
                                score = abs(size[0]-width)+abs(size[1]-height)
                                if score < best_score:
                                    best_size_id = size_id
                                    best_score = score

                            # Now find the best refresh for this resolution
                            best_score = 1000000
                            best_refresh = 50
                            for available_refresh in xscreen.getAvailableRefreshRates(best_size_id):
                                score = abs(refresh-available_refresh)
                                if score < best_score:
                                    best_refresh = available_refresh
                                    best_score = score

                            # Mask out any unsupported rotations.
                            rotation &= xscreen.getAvailableRotations()
                            xscreen.setScreenConfigAndRate(best_size_id, rotation, best_refresh)
                    
                    # Restore the gamma settings.
                    if redgamma is not None and greengamma is not None and bluegamma is not None:
                        try:
                            xscreen.setGamma( (float(redgamma), float(greengamma), float(bluegamma)) )
                        except ValueError,e:
                            pass
                            
            FixXorgDPI(dpi)
        except xf86misc.XF86Error,err:
            print err

        return

    else:
        # Ensure that the xorgs virtual screen size matches the default resolution
        # of the server. Why does this matter? When Xorg starts up it reads its
        # config file chooses the first mode in the "modes" line of the active
        # Screen section and uses it as the virtual screen size and as the
        # screen resolution (ie 1024x768 resolution screen showing a 1024x768 gfx
        # buffer). But, this means that you can't use RandR to get to any higher
        # screen resolutions (ie 1280x1024) because Xorg requires that the virtual
        # screen size 'cover' the screen resolution being displayed.
        #
        # So, to get around this problem and make it possible for people to select
        # a lower resolution screen *and* still have the option later to use
        # RandR/displayconfig to switch to higher resolution, displayconfig
        # explicitly sets the virtual screen size in xorg.conf to the largest
        # resoluution that the monitor/gfx card can support. The down side to
        # this is that the X server and tdm get the correct resolution but the
        # wrong (virtual) screen size. The user can now scroll around on the
        # greater virtual screen. Kind of annoying for tdm, unacceptable once
        # the user has logged in.
        #
        # What we do now as the user's KDE session is being started up is check
        # what the real virtual screen size is meant to be (=same as the real
        # resolution being used) and then use the RandR extension to explicitly
        # set the correct resolution. This has the effect of changing the virtual
        # screeen size to what we really want. (RandR can change the virtual
        # screen size, thankfully)
        import displayconfigabstraction

        try:
            xserver = xf86misc.XF86Server()

            for xscreen in xserver.getScreens():
                if xscreen.resolutionSupportAvailable():
                    mode_line = ixf86misc.XF86VidModeGetModeLine(xserver.getDisplay(),xscreen.getScreenId())

                    hdisplay = mode_line[1]
                    vdisplay = mode_line[5]

                    live_refresh_rate = xscreen.getRefreshRate()
                    try:
                        (live_width,live_height,x,x) = xscreen.getAvailableSizes()[xscreen.getSizeID()]
                    except IndexError, errmsg:
                        print "IndexError:", errmsg, "in displayconfig-restore getting live screen size - trying screen 0."
                        (live_width,live_height,x,x) = xscreen.getAvailableSizes()[0]

                    if (hdisplay,vdisplay) != (live_width,live_height):
                        # The screen resolution doesn't match the virtual screen size.
                        screen_sizes = xscreen.getAvailableSizes()
                        for size_id in range(len(screen_sizes)):
                            screen_size = screen_sizes[size_id]
                            if screen_size[0]==hdisplay and screen_size[1]==vdisplay:

                                # Find the closest matching refresh rate.
                                best_refresh = 0
                                best_score = 1000000
                                for rate in xscreen.getAvailableRefreshRates(size_id):
                                    score = abs(rate-live_refresh_rate)
                                    if score < best_score:
                                        best_refresh = rate
                                        best_score = score

                                # Reset the screen mode and virtual screen size.
                                xscreen.setScreenConfigAndRate(size_id,xscreen.getRotation(),best_refresh)
                                break
            FixXorgDPI(dpi)
        except (xf86misc.XF86Error,TypeError),err:
            print err

main()
