The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
# chres.pl - change the resolution of the screen
#
# This script was written by Aldo Calpini (dada@perl.it) in 2001.
# It is released into the public domain.  You may use it freely, however,
# if you make any modifications and redistribute, please list your name
# and describe the changes. This script is distributed without any warranty,
# express or implied.

use Win32::API 0.20;
use Getopt::Mixed;

my $VERSION = '0.51';

# the required APIs
my $EnumDisplaySettings =   new Win32::API( 
    "user32", "EnumDisplaySettings",   "PNP",  "N" 
);
my $ChangeDisplaySettings = new Win32::API( 
    "user32", "ChangeDisplaySettings", "PN",   "N" 
);
my $CreateDC =              new Win32::API( 
    "gdi32",  "CreateDC",              "PPPP", "N" 
);
my $GetDeviceCaps =         new Win32::API( 
    "gdi32",  "GetDeviceCaps",         "NN",   "N" 
);
my $DeleteDC =              new Win32::API( 
    "gdi32",  "DeleteDC",              "N",    "V" 
);

# process command line options
Getopt::Mixed::getOptions qw(
    help h>help ?>help
    quiet q>quiet
    test t>test
    list l>list
    info i>info
    permanent p>permanent
    global g>global
    reset r>reset
    x=i
    y=i
    colordepth=s c>colordepth
    frequency=i f>frequency
);

# beautify list for BPPs
my %colors = (
    4 => "16 Colors",
    8 => "256 Colors",
    16 => "High Color",
    24 => "True Color",
    32 => "True Color",
);

$opt_colordepth = color2bpp($opt_colordepth) if $opt_colordepth;

# process non-options on command line
$doing = "x";
while(@ARGV) {
    $argv = shift @ARGV;
    if($doing eq "x") {
        if($argv =~ /(\d+)x(\d+)/i) {
            $opt_x = $1;
            $opt_y = $2;
            $doing = "colordepth";
        } else {
            $opt_x = $argv;
            $doing = "y";
        }
    } elsif($doing eq "y") {
        $opt_y = $argv;
        $doing = "colordepth";
    } elsif($doing eq "colordepth") {
        $opt_colordepth = color2bpp($argv);
        $doing = "frequency";
    } elsif($doing eq "frequency") {
        $opt_frequency = $argv;
        $doing = "skip";
    }
}

if($opt_test) {
    $flag = 0x02;
} elsif($opt_permanent) {
    $flag = 0x01;
} elsif($opt_global) {
    $flag = 0x08;
} else {
    $flag = 0;
}

if($opt_help) {
    display_help();
    exit();
}

if($opt_reset) {
    $res = $ChangeDisplaySettings->Call( 0, 0 );
    print "Default mode restored.\n" unless $opt_quiet;
    exit($res);
}

if($opt_info) {
    ($X, $Y, $BPP, $HZ) = getres();
    printf "%dx%d %s (%d Bit) %dHz\n", 
        $X, $Y, $colors{$BPP}, $BPP, $HZ unless $opt_quiet;
    exit();
}

if($opt_list) {
    exit( list_modes($opt_x, $opt_y, $opt_colordepth, $opt_frequency) );
}

if(
    not defined $opt_x 
    and not defined $opt_y 
    and not defined $opt_colordepth 
    and not defined $opt_frequency
) {
    if(not $opt_quiet) {
        print "Nothing to do.\n";
        print "Type $0 --help for more information.\n";
    }
    exit();
}

$res = chres($opt_x, $opt_y, $opt_colordepth, $opt_frequency, $flag);

unless( $opt_quiet ) {
    if($res ==  0) { 
        if($opt_test) { print "Test successful.\n"; }
        else          { print "Mode changed.\n"; }
    }
    if($res ==  1) { print "The computer must be restarted.\n"; }
    if($res == -1) { 
        print "The display driver failed the specified graphics mode.\n"; 
    }
    if($res == -2) { print "The graphics mode is not supported.\n"; }
    if($res == -3) { print "Unable to write settings to the registry.\n"; }
    if($res == -4) { print "Invalid parameters.\n"; }
    if($res == -5) { print "Invalid parameters.\n"; }
}
exit($res);

sub chres {
    my($wanted_X, $wanted_Y, $wanted_BPP, $wanted_HZ, $flags) = @_;

    $flags = 0 unless defined $flags;

    my($actual_X, $actual_Y, $actual_BPP, $actual_HZ) = @_;
    if(not defined $wanted_X
    or not defined $wanted_X
    or not defined $wanted_BPP
    or not defined $wanted_HZ) {
        ($actual_X, $actual_Y, $actual_BPP, $actual_HZ) = getres();
    }

    my $wanted;
    $wanted =        ((defined $wanted_X)   ? $wanted_X   : $actual_X);
    $wanted .= "," . ((defined $wanted_Y)   ? $wanted_Y   : $actual_Y);
    $wanted .= "," . ((defined $wanted_BPP) ? $wanted_BPP : $actual_BPP);
    $wanted .= "," . ((defined $wanted_HZ)  ? $wanted_HZ  : $actual_HZ);

    my $devmode = init_devmode();
    my $newmode = undef;
    my $i = 0;
    my $res = $EnumDisplaySettings->Call( 0, $i, $devmode );
    while( $res != 0) {
        ($BPP, $X, $Y, undef, $HZ) = unpack("x104 LLLLL", $devmode);
        $mode = "$X,$Y,$BPP,$HZ";
        if($mode eq $wanted) {
            $newmode = $devmode;
            last;
        }
        $res = $EnumDisplaySettings->Call( 0, ++$i, $devmode );
    }

    if(defined $newmode) {
        $res = $ChangeDisplaySettings->Call( $newmode, $flags );
    } else {
        $res = -2;
    }
    return $res;
}
    
sub getres {
    my $hdc = $CreateDC->Call("DISPLAY", 0, 0, 0);
    if(!$hdc) {
        return undef;
    }
    my $HORZRES = 8;
    my $VERTRES = 10;
    my $BITSPIXEL = 12;
    my $VREFRESH = 116;

    my $X = $GetDeviceCaps->Call($hdc, $HORZRES);
    my $Y = $GetDeviceCaps->Call($hdc, $VERTRES);
    my $BPP = $GetDeviceCaps->Call($hdc, $BITSPIXEL);
    my $HZ = $GetDeviceCaps->Call($hdc, $VREFRESH);

    $DeleteDC->Call($hdc);
    return ($X, $Y, $BPP, $HZ);
}

sub list_modes {
    my($wanted_X, $wanted_Y, $wanted_BPP, $wanted_HZ) = @_;

    my $modes = 0;
    my $devmode = init_devmode();
    my $i = 0;
    my $res = $EnumDisplaySettings->Call( 0, $i, $devmode );
    while( $res != 0) {
        ($BPP, $X, $Y, undef, $HZ) = unpack("x104 LLLLL", $devmode);
        
        if( (not defined $wanted_X   or $wanted_X   == $X)
        and (not defined $wanted_Y   or $wanted_Y   == $Y)
        and (not defined $wanted_BPP or $wanted_BPP == $BPP)
        and (not defined $wanted_HZ  or $wanted_HZ  == $HZ)
        ) {
            printf "%dx%d %s (%d Bit) %dHz\n", 
                $X, $Y, $colors{$BPP}, $BPP, $HZ unless $opt_quiet;
            $modes++;
        }
        $res = $EnumDisplaySettings->Call( 0, ++$i, $devmode );
    }
    print "No matching graphics modes.\n" if not $opt_quiet and $modes == 0;
    return $modes;
}

sub init_devmode {
    return pack(
        "B" x 32 . "SSSSLsssssssssssss" . "B" x 32 . "SLLLLL",
        (0 x 32),       # dmDeviceName
        0,              # dmSpecVersion
        0,              # dmDriverVersion
        124,            # dmSize
        0,              # dmDriverExtra
        0,              # dmFields
        0,              # dmOrientation
        0,              # dmPaperSize
        0,              # dmPaperLength
        0,              # dmPaperWidth
        0,              # dmScale
        0,              # dmCopies
        0,              # dmDefaultSource
        0,              # dmPrintQuality
        0,              # dmColor
        0,              # dmDuplex
        0,              # dmYResolution
        0,              # dmTTOption
        0,              # dmCollate
        (0 x 32),       # dmFormName
        0,              # dmLogPixels
        0,              # dmBitsPerPel
        0,              # dmPelsWidth
        0,              # dmPelsHeight
        0,              # dmDisplayFlags
        0,              # dmDisplayFrequency
    );
}

sub color2bpp {
    my($arg) = shift;
    $arg = lc $arg;
    my %table = (
        1 => 1,
        2 => 2,
        16 => 4,
        256 => 8,
        65000 => 16,
        '64k' => 16,
        '65k' => 16,
        high => 16,
        '16m' => 24,
        true => 32,
    );      
    if($arg =~ /^(\d+)b$/) {
        return $1;
    } elsif(exists $table{$arg}) {
        return $table{$arg};
    }
}

sub display_help {

print qq(
$0 version $VERSION, (c) 2001 Aldo Calpini <dada\@perl.it>

usage: $0 [OPTIONS] [NNNxNNN] [COLORS] [FREQ]

OPTIONS:
    --help              shows this help
    --x NNN             width, in pixels
    --y NNN             height, in pixels
    --colordepth COLORS color depth (see below)
    --frequency NNN     vertical refresh rate, in Hz (only WinNT/2000)
    --quiet             does not display information on STDOUT
    --reset             reset the screen to the default mode
    --info              isplay the current mode and exit
    --list              list the available modes. can be combined with
                        --x, --y, --colordepth and --frequency, for 
                        example: '--x 1024 --list' lists all the
                        modes with width of 1024 pixels
    --test              test the given mode without making changes
    --permanent         make change permanent (for the current user)
    --global            make change permanent and global (for all
                        users) (only WinNT/2000)

all options can be given in the short form too (eg. -t for --test,
-h for --help, -x for --x and so on).

COLORS:
    recognized values are:
        1, 2, 1b              >   2 colors (1 bpp)
        16, 4b                =>  16 colors (4 bpp)
        256, 8b               =>  256 colors (8 bpp)
        65000, 64k, high, 16b =>  65536 colors (16 bpp)
        16m, true, 32b        =>  16 millions colors (32 bpp)

the --x, --y, --colordepth and --frequency options can also be
given on command line without introduction, but in this case
order must be respected. for example, to change to a 800x600
screen resolution:
    $0 800x600
or  $0 800 600

to change to 800x600, 32bpp, 85Hz:
    $0 800 600 32 85
    
);
}

=head1 NAME

chres - CHange RESolution

=head1 DESCRIPTION

Change the resolution of the screen on a Windows machine.
Launch the script with the --help option for a detailed
description of the usage.

=head1 README

Change the resolution of the screen on a Windows machine.

=head1 PREREQUISITES

This script requires C<Win32::API 0.20> and C<Getopt::Mixed>.

=pod OSNAMES

MSWin32

=pod SCRIPT CATEGORIES

Win32
Win32/Utilities

=head1 AUTHOR

Aldo Calpini (dada@perl.it).

=cut