
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <X11/Xlib.h>
#include <X11/extensions/xf86vmode.h>
#include "vidmode.h"

/* Why aren't these in the damn header??? */

/* Mode flags -- ignore flags not in V_FLAG_MASK */
#define V_FLAG_MASK     0x1FF;
#define V_PHSYNC        0x001 
#define V_NHSYNC        0x002
#define V_PVSYNC        0x004
#define V_NVSYNC        0x008
#define V_INTERLACE     0x010 
#define V_DBLSCAN       0x020
#define V_CSYNC         0x040
#define V_PCSYNC        0x080
#define V_NCSYNC        0x100

struct vidmode_s
{
    Display *display;
    int screen;
};

vidmode_t *vidmode_new( void )
{
    vidmode_t *vm = (vidmode_t *) malloc( sizeof( vidmode_t ) );
    if( !vm ) return 0;

    if( getenv( "DISPLAY" ) ) {
        vm->display = XOpenDisplay( getenv( "DISPLAY" ) );
    } else {
        free( vm );
        return 0;
    }
    vm->screen = DefaultScreen( vm->display );

    return vm;
}

void vidmode_delete( vidmode_t *vm )
{
    XCloseDisplay( vm->display );
    free( vm );
}

void vidmode_print_modeline_details( XF86VidModeModeInfo *info )
{
    fprintf( stderr, "vidmode: dot clock  %d\n", info->dotclock );
    fprintf( stderr, "vidmode: hdisplay   %d\n", info->hdisplay );
    fprintf( stderr, "vidmode: hsyncstart %d\n", info->hsyncstart );
    fprintf( stderr, "vidmode: hsyncend   %d\n", info->hsyncend );
    fprintf( stderr, "vidmode: htotal     %d\n", info->htotal );
    fprintf( stderr, "vidmode: hskew      %d\n", info->hskew );
    fprintf( stderr, "vidmode: vdisplay   %d\n", info->vdisplay );
    fprintf( stderr, "vidmode: vsyncstart %d\n", info->vsyncstart );
    fprintf( stderr, "vidmode: vsyncend   %d\n", info->vsyncend );
    fprintf( stderr, "vidmode: vtotal     %d\n", info->vtotal );
    fprintf( stderr, "vidmode: flags      %x\n", info->flags );
    fprintf( stderr, "vidmode: privsize   %d\n", info->privsize );
    fprintf( stderr, "vidmode: refresh    %5.2fhz.\n",
             (double) ( (double) info->dotclock * 1000.0 ) /
             (double) ( info->htotal * info->vtotal ) );
}

void vidmode_switch_tv( vidmode_t *vm )
{
    XF86VidModeModeInfo newline;
    XF86VidModeModeInfo info;
    XF86VidModeModeInfo **modelist;
    int nummodes, i;

    /* help!  I can't figure this out at all. */

/*
    info.dotclock = 57350;
    info.hdisplay = 1024;
    info.hsyncstart = 1040;
    info.hsyncend = 1104;
    info.htotal = 1208;
    info.hskew = 0;
    info.vdisplay = 768;
    info.vsyncstart = 770;
    info.vsyncend = 772;
    info.vtotal = 792;
    info.flags = 0; // V_PHSYNC|V_PVSYNC;
    info.privsize = 0;
*/

/*
    info.dotclock = 105020;
    info.hdisplay = 1440;
    info.hsyncstart = 1472;
    info.hsyncend = 1584;
    info.htotal = 1768;
    info.hskew = 0;
    info.vdisplay = 960;
    info.vsyncstart = 963;
    info.vsyncend = 966;
    info.vtotal = 991;
    info.flags = 0; // V_PHSYNC|V_PVSYNC;
    info.privsize = 0;
*/

    info.dotclock = 94500;
    info.hdisplay =  1025;
    info.hsyncstart= 1072;
    info.hsyncend =  1168;
    info.htotal  =   1376;
    info.hskew   =   0;
    info.vdisplay =  768;
    info.vsyncstart= 769;
    info.vsyncend =  772;
    info.vtotal   =  808;
    info.flags    =  5;
    info.privsize =  0;

    newline.dotclock = 0;
    newline.hdisplay = 0;
    newline.hsyncstart = 0;
    newline.hsyncend = 0;
    newline.htotal = 0;
    newline.hskew = 0;
    newline.vdisplay = 0;
    newline.vsyncstart = 0;
    newline.vsyncend = 0;
    newline.vtotal = 0;
    newline.flags = 0;
    newline.privsize = 0;

/*
        "1024x768 @ 60Hz (VESA) hsync: 48.4kHz",
        {
            65000, 1024, 1048, 1184, 1344, 0, 768, 771, 777, 806,
            V_NHSYNC | V_NVSYNC
        }
*/
    info.dotclock = 65000;
    info.hdisplay =  1024; info.hsyncstart= 1048;
    info.hsyncend =  1184; info.htotal  =   1344; info.hskew   =   0;
    info.vdisplay =  768;  info.vsyncstart= 771;
    info.vsyncend =  777;  info.vtotal   =  806;
    info.flags    =  V_NHSYNC | V_NVSYNC;
    info.privsize =  0;

    fprintf( stderr, "vidmode: Getting all mode lines.\n" );
    if( !XF86VidModeGetAllModeLines( vm->display, vm->screen, &nummodes, &modelist ) ) {
        fprintf( stderr, "vidmode: can't get mode list.\n" );
    }

    for( i = 0; i < nummodes; i++ ) {
        fprintf( stderr, "\nvidmode: Mode %d:\n", i );
        vidmode_print_modeline_details( modelist[ i ] );
    }

    fprintf( stderr, "vidmode: Adding mode:\n" );
    vidmode_print_modeline_details( &info );

    fprintf( stderr, "vidmode: Validating new mode line.\n" );
    if( XF86VidModeValidateModeLine( vm->display, vm->screen, &info ) != Success ) {
        fprintf( stderr, "vidmode: Failed to validate mode.\n" );
        return;
    }

    fprintf( stderr, "vidmode: Adding new mode line.\n" );
    if( !XF86VidModeAddModeLine( vm->display, vm->screen, &info, &newline ) ) {
        fprintf( stderr, "vidmode: Failed to add modeline.\n" );
        return;
    }

    XSync( vm->display, False );
    XFlush( vm->display );

    fprintf( stderr, "vidmode: Getting all mode lines.\n" );
    if( !XF86VidModeGetAllModeLines( vm->display, vm->screen, &nummodes, &modelist ) ) {
        fprintf( stderr, "vidmode: can't get mode list.\n" );
    }

    for( i = 0; i < nummodes; i++ ) {
        fprintf( stderr, "\nvidmode: Mode %d:\n", i );
        vidmode_print_modeline_details( modelist[ i ] );
    }
/*
    fprintf( stderr, "vidmode: Locking mode switch.\n" );
    if( !XF86VidModeLockModeSwitch( vm->display, vm->screen, False ) ) {
        fprintf( stderr, "vidmode: Failed to lock mode.\n" );
        return;
    }
    XFlush( vm->display );
*/

    fprintf( stderr, "vidmode: Switching to new mode line.\n" );
    if( !XF86VidModeSwitchToMode( vm->display, vm->screen, &info ) ) {
        fprintf( stderr, "vidmode: Failed to switch mode.\n" );
        return;
    }
    XFlush( vm->display );

    sleep( 2 );
    fprintf( stderr, "vidmode: Switching to old mode line.\n" );
    if( !XF86VidModeSwitchToMode( vm->display, vm->screen, modelist[ 0 ] ) ) {
        fprintf( stderr, "vidmode: Failed to switch mode.\n" );
        return;
    }
    XFlush( vm->display );

    sleep( 2 );
    fprintf( stderr, "vidmode: Switching to new mode line.\n" );
    if( !XF86VidModeSwitchToMode( vm->display, vm->screen, &info ) ) {
        fprintf( stderr, "vidmode: Failed to switch mode.\n" );
        return;
    }
    XFlush( vm->display );

    sleep( 2 );
    fprintf( stderr, "vidmode: Switching to old mode line.\n" );
    if( !XF86VidModeSwitchToMode( vm->display, vm->screen, modelist[ 0 ] ) ) {
        fprintf( stderr, "vidmode: Failed to switch mode.\n" );
        return;
    }
    XFlush( vm->display );

    sleep( 2 );
    fprintf( stderr, "vidmode: Switching to new mode line.\n" );
    if( !XF86VidModeSwitchToMode( vm->display, vm->screen, &info ) ) {
        fprintf( stderr, "vidmode: Failed to switch mode.\n" );
        return;
    }
    XFlush( vm->display );
/*
    fprintf( stderr, "vidmode: Unlocking mode switch.\n" );
    if( !XF86VidModeLockModeSwitch( vm->display, vm->screen, True ) ) {
        fprintf( stderr, "vidmode: Failed to unlock mode.\n" );
        return;
    }
*/

    XFlush( vm->display );
}

void vidmode_set_clock( vidmode_t *vm, double rate )
{
    XF86VidModeModeInfo newline;
    XF86VidModeModeInfo info;
    XF86VidModeModeInfo **modelist;
    int nummodes, i;
    double newclock;

    memset( &newline, 0, sizeof( XF86VidModeModeInfo ) );
/*
    newline.dotclock = 0; newline.hdisplay = 0; newline.hsyncstart = 0;
    newline.hsyncend = 0; newline.htotal = 0; newline.hskew = 0;
    newline.vdisplay = 0; newline.vsyncstart = 0; newline.vsyncend = 0;
    newline.vtotal = 0; newline.flags = 0; newline.privsize = 0;
*/

/*
        "1024x768 @ 60Hz (VESA) hsync: 48.4kHz",
        {
            65000, 1024, 1048, 1184, 1344, 0, 768, 771, 777, 806,
            V_NHSYNC | V_NVSYNC
        }
*/
    info.dotclock = 65000;
    info.hdisplay =  1024; info.hsyncstart= 1048;
    info.hsyncend =  1184; info.htotal  =   1344; info.hskew   =   0;
    info.vdisplay =  768;  info.vsyncstart= 771;
    info.vsyncend =  777;  info.vtotal   =  806;
    info.flags    =  V_NHSYNC | V_NVSYNC;
    info.privsize =  0;

    newclock = ( (double) ( info.vtotal * info.htotal ) * rate ) / 1000.0;
    fprintf( stderr, "old clock %d, new clock %d\n", 65000, (int) newclock );
    info.dotclock = (int) newclock;
    // return;

/*
    info.dotclock = 57350;
    info.hdisplay = 1024;
    info.hsyncstart = 1040;
    info.hsyncend = 1104;
    info.htotal = 1208;
    info.hskew = 0;
    info.vdisplay = 768;
    info.vsyncstart = 770;
    info.vsyncend = 772;
    info.vtotal = 792;
    info.flags = V_PHSYNC|V_PVSYNC;
    info.privsize = 0;
*/

    info.dotclock = 37641;
    info.hdisplay = 800;
    info.hsyncstart = 816;
    info.hsyncend = 880;
    info.htotal = 992;
    info.hskew = 0;
    info.vdisplay = 600;
    info.vsyncstart = 603;
    info.vsyncend = 606;
    info.vtotal = 633;
    info.flags = V_PHSYNC;//|V_PVSYNC;
    info.privsize = 0;

    newclock = ( (double) ( info.vtotal * info.htotal ) * rate ) / 1000.0;
    fprintf( stderr, "old clock %d, new clock %d\n", 65000, (int) newclock );
    info.dotclock = (int) newclock;

    fprintf( stderr, "vidmode: Getting all mode lines.\n" );
    if( !XF86VidModeGetAllModeLines( vm->display, vm->screen, &nummodes, &modelist ) ) {
        fprintf( stderr, "vidmode: can't get mode list.\n" );
    }

    for( i = 0; i < nummodes; i++ ) {
        fprintf( stderr, "\nvidmode: Mode %d:\n", i );
        vidmode_print_modeline_details( modelist[ i ] );
    }

    fprintf( stderr, "vidmode: Adding mode:\n" );
    vidmode_print_modeline_details( &info );

/*
    fprintf( stderr, "vidmode: Validating new mode line.\n" );
    if( XF86VidModeValidateModeLine( vm->display, vm->screen, &info ) != Success ) {
        fprintf( stderr, "vidmode: Failed to validate mode.\n" );
        return;
    }
*/

    fprintf( stderr, "vidmode: Adding new mode line.\n" );
    if( !XF86VidModeAddModeLine( vm->display, vm->screen, &info, &newline ) ) {
        fprintf( stderr, "vidmode: Failed to add modeline.\n" );
        return;
    }

    XSync( vm->display, False );
    XFlush( vm->display );

    fprintf( stderr, "vidmode: Getting all mode lines.\n" );
    if( !XF86VidModeGetAllModeLines( vm->display, vm->screen, &nummodes, &modelist ) ) {
        fprintf( stderr, "vidmode: can't get mode list.\n" );
    }

    for( i = 0; i < nummodes; i++ ) {
        fprintf( stderr, "\nvidmode: Mode %d:\n", i );
        vidmode_print_modeline_details( modelist[ i ] );
    }
/*
    fprintf( stderr, "vidmode: Locking mode switch.\n" );
    if( !XF86VidModeLockModeSwitch( vm->display, vm->screen, False ) ) {
        fprintf( stderr, "vidmode: Failed to lock mode.\n" );
        return;
    }
    XFlush( vm->display );
*/

    fprintf( stderr, "vidmode: Switching to new mode line.\n" );
    if( !XF86VidModeSwitchToMode( vm->display, vm->screen, &info ) ) {
        fprintf( stderr, "vidmode: Failed to switch mode.\n" );
        return;
    }
    XSync( vm->display, True );
    XFlush( vm->display );

/*
    fprintf( stderr, "vidmode: Unlocking mode switch.\n" );
    if( !XF86VidModeLockModeSwitch( vm->display, vm->screen, True ) ) {
        fprintf( stderr, "vidmode: Failed to unlock mode.\n" );
        return;
    }
    XFlush( vm->display );
*/
}

void vidmode_set_refresh( vidmode_t *vm, double rate )
{
    XF86VidModeModeLine mode_line;
    int dot_clock;
    double newtotal;

    if( !XF86VidModeGetModeLine( vm->display, vm->screen,
                                 &dot_clock, &mode_line ) ) {
        fprintf( stderr, "vidmode: Can't get modeline.\n" );
        return;
    }
    fprintf( stderr, "vidmode: Old refresh    %5.2fhz.\n",
             (double) ( (double) dot_clock * 1000.0 ) /
             (double) ( mode_line.htotal * mode_line.vtotal ) );
    newtotal = ( (double) dot_clock * 1000.0 ) /
                      ( (double) mode_line.htotal * rate );
    fprintf( stderr, "vtotal was %d, will be %d\n", mode_line.vtotal, (int) newtotal );
    mode_line.vtotal = (int) newtotal;
    if( !XF86VidModeModModeLine( vm->display, vm->screen, &mode_line ) ) {
        fprintf( stderr, "vidmode: Can't mod modeline.\n" );
        return;
    }
    XFlush( vm->display );
    fprintf( stderr, "vidmode: Theoretical refresh    %7.4fhz.\n",
             (double) ( (double) dot_clock * 1000.0 ) /
             (double) ( mode_line.htotal * mode_line.vtotal ) );
    if( !XF86VidModeGetModeLine( vm->display, vm->screen,
                                 &dot_clock, &mode_line ) ) {
        fprintf( stderr, "vidmode: Can't get modeline.\n" );
        return;
    }
    fprintf( stderr, "vidmode: New refresh    %7.4fhz.\n",
             (double) ( (double) dot_clock * 1000.0 ) /
             (double) ( mode_line.htotal * mode_line.vtotal ) );
}

void vidmode_change_refresh( vidmode_t *vm )
{
    XF86VidModeModeLine mode_line;
    int dot_clock;

    if( !XF86VidModeGetModeLine( vm->display, vm->screen,
                                 &dot_clock, &mode_line ) ) {
        fprintf( stderr, "vidmode: Can't get modeline.\n" );
        return;
    }
    fprintf( stderr, "vidmode: refresh    %5.2fhz.\n",
             (double) ( (double) dot_clock * 1000.0 ) /
             (double) ( mode_line.htotal * mode_line.vtotal ) );
    sleep( 2 );

    mode_line.htotal += 1;
    if( !XF86VidModeModModeLine( vm->display, vm->screen, &mode_line ) ) {
        fprintf( stderr, "vidmode: Can't mod modeline.\n" );
        return;
    }
    fprintf( stderr, "vidmode: refresh    %7.4fhz.\n",
             (double) ( (double) dot_clock * 1000.0 ) /
             (double) ( mode_line.htotal * mode_line.vtotal ) );
    if( !XF86VidModeGetModeLine( vm->display, vm->screen,
                                 &dot_clock, &mode_line ) ) {
        fprintf( stderr, "vidmode: Can't get modeline.\n" );
        return;
    }
    sleep( 2 );

    mode_line.htotal -= 1;
    if( !XF86VidModeModModeLine( vm->display, vm->screen, &mode_line ) ) {
        fprintf( stderr, "vidmode: Can't mod modeline.\n" );
        return;
    }
    fprintf( stderr, "vidmode: refresh    %7.4fhz.\n",
             (double) ( (double) dot_clock * 1000.0 ) /
             (double) ( mode_line.htotal * mode_line.vtotal ) );
    if( !XF86VidModeGetModeLine( vm->display, vm->screen,
                                 &dot_clock, &mode_line ) ) {
        fprintf( stderr, "vidmode: Can't get modeline.\n" );
        return;
    }
    sleep( 2 );

    mode_line.htotal += 1;
    if( !XF86VidModeModModeLine( vm->display, vm->screen, &mode_line ) ) {
        fprintf( stderr, "vidmode: Can't mod modeline.\n" );
        return;
    }
    fprintf( stderr, "vidmode: refresh    %7.4fhz.\n",
             (double) ( (double) dot_clock * 1000.0 ) /
             (double) ( mode_line.htotal * mode_line.vtotal ) );
    if( !XF86VidModeGetModeLine( vm->display, vm->screen,
                                 &dot_clock, &mode_line ) ) {
        fprintf( stderr, "vidmode: Can't get modeline.\n" );
        return;
    }
    sleep( 2 );

    mode_line.htotal -= 1;
    if( !XF86VidModeModModeLine( vm->display, vm->screen, &mode_line ) ) {
        fprintf( stderr, "vidmode: Can't mod modeline.\n" );
        return;
    }
    fprintf( stderr, "vidmode: refresh    %7.4fhz.\n",
             (double) ( (double) dot_clock * 1000.0 ) /
             (double) ( mode_line.htotal * mode_line.vtotal ) );
    if( !XF86VidModeGetModeLine( vm->display, vm->screen,
                                 &dot_clock, &mode_line ) ) {
        fprintf( stderr, "vidmode: Can't get modeline.\n" );
        return;
    }
    fprintf( stderr, "vidmode: done.\n" );
}

void vidmode_print_modeline( vidmode_t *vm )
{
    XF86VidModeMonitor monitor;
    XF86VidModeModeLine mode_line;
    int dot_clock;
    int flags, numclocks, maxclock;
    int *clocks;
    int i;

    if( !XF86VidModeGetModeLine( vm->display, vm->screen,
                                 &dot_clock, &mode_line ) ) {
        fprintf( stderr, "vidmode: Can't get modeline.\n" );
        return;
    }

    fprintf( stderr, "vidmode: dot clock  %d\n", dot_clock );
    fprintf( stderr, "vidmode: hdisplay   %d\n", mode_line.hdisplay );
    fprintf( stderr, "vidmode: hsyncstart %d\n", mode_line.hsyncstart );
    fprintf( stderr, "vidmode: hsyncend   %d\n", mode_line.hsyncend );
    fprintf( stderr, "vidmode: htotal     %d\n", mode_line.htotal );
    fprintf( stderr, "vidmode: hskew      %d\n", mode_line.hskew );
    fprintf( stderr, "vidmode: vdisplay   %d\n", mode_line.vdisplay );
    fprintf( stderr, "vidmode: vsyncstart %d\n", mode_line.vsyncstart );
    fprintf( stderr, "vidmode: vsyncend   %d\n", mode_line.vsyncend );
    fprintf( stderr, "vidmode: vtotal     %d\n", mode_line.vtotal );
    fprintf( stderr, "vidmode: flags      %x\n", mode_line.flags );
    fprintf( stderr, "vidmode: privsize   %d\n", mode_line.privsize );
    fprintf( stderr, "vidmode: refresh    %5.2fhz.\n",
             (double) ( (double) dot_clock * 1000.0 ) /
             (double) ( mode_line.htotal * mode_line.vtotal ) );

    XF86VidModeGetDotClocks( vm->display, vm->screen, &flags,
                             &numclocks, &maxclock, &clocks );

    fprintf( stderr, "vidmode: Dotclock flags: %x, numclocks: %d, maxclock: %d\n",
             flags, numclocks, maxclock );
    for( i = 0; i < numclocks; i++ ) {
        fprintf( stderr, "vidmode: \tclock %d: %d\n", i, clocks[ i ] );
    }

    XF86VidModeGetMonitor( vm->display, vm->screen, &monitor );
    fprintf( stderr, "vidmode: hsync hi %f lo %f\n",
             monitor.hsync->hi, monitor.hsync->lo );
    fprintf( stderr, "vidmode: vsync hi %f lo %f\n",
             monitor.vsync->hi, monitor.vsync->lo );
}

void vidmode_mod_mode_for_refresh_dotclock( vidmode_t *vm, double rate )
{
    XF86VidModeModeLine mode_line;
    int dot_clock;

    if( !XF86VidModeGetModeLine( vm->display, vm->screen,
                                 &dot_clock, &mode_line ) ) {
        fprintf( stderr, "vidmode: Can't get modeline.\n" );
        return;
    }

    fprintf( stderr, "dotclock should become: %f\n", (((double) (mode_line.htotal * mode_line.vtotal)) * rate)/(1000.0*1000.0) );
}

/*
# 1440x960 @ 59Hz,  59.40 kHz hsync
                          Mode "1440x960"
                          DotClock    105.02
                          HTimings  1440 1472 1584 1768
                          VTimings  960 963 966 991
                      EndMode

# 720x480 @ 59Hz,  29.79 kHz hsync
                          Mode "720x480"
                          DotClock     23.83
                          HTimings  720 728 752 800
                          VTimings  480 482 484 497
                      EndMode    

# 800x600 @ 59Hz,  37.16 kHz hsync
                          Mode "800x600"
                          DotClock     34.19
                          HTimings  800 816 856 920
                          VTimings  600 602 604 620
                      EndMode    

# 1024x768 @ 59Hz,  47.47 kHz hsync
                          Mode "1024x768"
                          DotClock     57.35
                          HTimings  1024 1040 1104 1208
                          VTimings  768 770 772 792
                      EndMode    
*/

