/*------------------------------------------------------------------------------------------
	SETRES SINGLE MONITOR
	Windows command line program to set display resolution / colour depth / refresh frequency
	© 2010 Ian Sharpe (www.iansharpe.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 3 of the License, or
	any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	The GNU General Public License can be viewed at http://www.gnu.org/licenses/

COMPATIBILITY
	Windows 98, Windows 2000 & later

COMPILATION
	This version compiles without alteration using GCC in conjunction with the
	Bloodshed Dev-C++ IDE. Modification to work with other compilers is usually
	just a matter of getting the correct #INCLUDEs.

VERSION HISTORY
	30 April 2010		v2.3	:  Initial GPL3 open source release
	Prior to v2.3				:  Closed source

MULTIPLE DISPLAYS
	SETRES SINGLE MONITOR works with the primary display only.
	A closed-source	multi-monitor edition is available at www.iansharpe.com

------------------------------------------------------------------------------------------*/

//----------------------------------------------------------------------------------------
// INCLUDES
// Compilers other than GCC may require different/additional include libraries.

#include <stdio.h>
#include <windows.h>
#include <conio.h>

//----------------------------------------------------------------------------------------
// FORWARD DECLARATIONS
//
void ExitWithHelp(char *);
void waitkey(void);

//----------------------------------------------------------------------------------------
// GLOBALS
//
const char* APP_VERSION = "v2.3 - 30 Apr 2010";	// Application version & date
BOOL bNoWait;   								// TRUE if user specifies not to wait for keypress on error

//----------------------------------------------------------------------------------------
// APP ENTRY POINT
//
int main( int argc, char* argv[] )
{

	DEVMODE DevMode;		// Device mode structure - see Windows API documentation

	DWORD	dwNewHres,		// User-specified new horizontal resolution
			dwNewVres,		// User-specified new vertical resolution
			dwNewBitDepth,	// User-specified new colour (bit) depth
			dwNewFreq;		// User-specified new frequency

	// Parse command line parameters

	dwNewHres = dwNewVres = dwNewBitDepth = dwNewFreq = 0;
	bNoWait = FALSE;

	for ( int i = 1; i < argc; i++ )
	{
		_strupr(argv[i]);

		switch(argv[i][0])
		{
			case 'H':		// Horizontal res
				dwNewHres = atoi(&argv[i][1]);
				break;

			case 'V':		// Vertical res
				dwNewVres = atoi(&argv[i][1]);
				break;

			case 'B':		// Bit depth
				dwNewBitDepth = atoi(&argv[i][1]);
				break;

			case 'F':		// Frequency
				dwNewFreq = atoi(&argv[i][1]);
				break;

			case 'N':		// No wait for keypress on error
				bNoWait = TRUE;
				break;

			default:
				ExitWithHelp("Unrecognised parameter supplied");
		}
	}

	// Parameter count check - after parsing in case user specified N = no wait parameter
	if ( argc < 3 || argc > 6 )
		ExitWithHelp("Wrong number of command line parameters supplied.");

	// Uncomment for debugging
	/* printf("dwNewHres=%d\ndwNewVres=%d\ndwNewBitDepth=%d\ndwNewFreq=%d", dwNewHres, dwNewVres, dwNewBitDepth, dwNewFreq);
	getchar(0);
	exit(0); */

	// Check we have sufficient information
	if( dwNewHres < 640 || dwNewVres < 480 )
		ExitWithHelp("Valid horizontal and/or vertical size not specified. 640x480 minimum.");

	// Obtain current display settings
	if( ! EnumDisplaySettings(NULL,ENUM_CURRENT_SETTINGS, &DevMode) )
	{
		puts("FATAL ERROR: EnumDisplaySettings function failed.\nSomething went badly wrong. I don't know what. Sorry.");
		puts("PRESS A KEY");
		waitkey();
		exit(EXIT_FAILURE);
	}

	// Overwrite with user-specified settings

	DevMode.dmPelsWidth = dwNewHres;
	DevMode.dmPelsHeight = dwNewVres;
	DevMode.dmFields = DM_PELSWIDTH+DM_PELSHEIGHT;

	if( dwNewBitDepth)
	{
		DevMode.dmBitsPerPel = dwNewBitDepth;
		DevMode.dmFields += DM_BITSPERPEL;
	}

	if (dwNewFreq)
	{
		DevMode.dmDisplayFrequency = dwNewFreq;
		DevMode.dmFields += DM_DISPLAYFREQUENCY;
	}

	// Execute resolution change, interpret API return code & exit

	puts ("Display mode change requested and Windows replied...\t");

	switch (ChangeDisplaySettings(&DevMode, CDS_UPDATEREGISTRY))
	{
		case DISP_CHANGE_SUCCESSFUL:
			puts("The settings change was successful");
			exit(EXIT_SUCCESS);

		case DISP_CHANGE_RESTART:
			puts("The computer must be restarted in order for the graphics mode to work");
			puts("PRESS A KEY");
			waitkey();
			exit(EXIT_SUCCESS);

		case DISP_CHANGE_BADFLAGS:
			puts("An invalid set of flags was passed in");
			puts("PRESS A KEY");
			waitkey();
			exit(EXIT_FAILURE);

		case DISP_CHANGE_BADPARAM:
			puts("An invalid parameter was passed in. This can include an invalid flag or combination of flags");
			puts("PRESS A KEY");
			waitkey();
			exit(EXIT_FAILURE);

		case DISP_CHANGE_FAILED:
			puts("The display driver failed the specified graphics mode");
			puts("PRESS A KEY");
			waitkey();
			exit(EXIT_FAILURE);

		case DISP_CHANGE_BADMODE:
			puts("The graphics mode is not supported");
			puts("PRESS A KEY");
			waitkey();
			exit(EXIT_FAILURE);

		case DISP_CHANGE_NOTUPDATED:
			puts("Windows NT: Unable to write settings to the registry");
			puts("PRESS A KEY");
			waitkey();
			exit(EXIT_FAILURE);

		default:
			puts("with an unknown return code (possibly an error)");
			puts("PRESS A KEY");
			waitkey();
			exit(EXIT_FAILURE);
	}
}


//----------------------------------------------------------------------------------------
// Exit app with help message
//
// ARGUMENTS
//  char* pszSpecialMessage    :   Pointer to special message string
// RETURN
//  Does not return - exits app with failure code
//
void ExitWithHelp(char* pszSpecialMessage)
{
	printf("\nSETRES SINGLE MONITOR - %s (c) Ian Sharpe - www.iansharpe.com \n", APP_VERSION);
	puts("Open source software released under GPLv3 or greater (www.gnu.org/licenses/)");
	puts("===============================================================================");
	puts("Change screen resolution, colour depth and refresh frequency in Windows\n");
	puts("\tSETRES hXXXX vXXXX [bXX] [fXX] [n]\n");
	puts("hXXXX = Horizontal size of screen in pixels          Not optional. 640 minimum");
	puts("vXXXX = Vertical size of screen in pixels            Not optional. 480 minimum");
	puts("  bXX = Bit (colour) depth such as 8, 16 24, 32      Optional");
	puts("  fXX = Refresh frequncy in Hertz, e.g. 60, 75, 85   Optional");
	puts("    n = No 'Press a key' wait in error report        Optional");
	puts("\nEXAMPLES:");
	puts("\tSETRES h1024 v768");
	puts("\tSETRES h800 v600 b24");
	puts("\tSETRES h1280 v1024 b32 f75");
	puts("\tSETRES h1024 v768 n\n");
	puts("\nWARNING: SETRES does not check the capabilities of your hardware. Windows");
	puts("\tis supposed to reject unsupported settings but do not rely on this.");
	puts("\tIf you specify unsupported settings, I WILL NOT ACCEPT RESPONSIBILITY.\n");

	printf("***  ERROR: %s  ***\n", pszSpecialMessage);

	Beep(300, 200);
	waitkey();
	exit(EXIT_FAILURE);
}

//----------------------------------------------------------------------------------------
// Wait for keypress
//
void waitkey(void)
{
	if ( bNoWait )			// Return immediately if bNoWait flag set
		return;

	int iCountDown;      	// Remaining seconds to timeout
	
	for( iCountDown = 15 ; ! kbhit() && iCountDown ; iCountDown-- ) // 15 and Sleep(1000) = 15 seconds
	{
		printf("  PRESS A KEY - TIMEOUT IN %2d SECONDS - SEE SETRES.TXT FOR HELP\r", iCountDown);
		Sleep(1000);		// Delay in loop cuts processor usage
	}

	if (iCountDown)
		getch();			// Throw character away

	Beep(300, 200);
}

//----------------------------------------------------------------------------------------
// END
//----------------------------------------------------------------------------------------
