Introduction
In December 2013, it was time for me to buy a new laptop. I looked around and ended up with one of the best laptops I could find... a DELL XPS 15 (2013 model). I really like the laptop but it has one disadvantage. The desktop resolution is insanely high. It is a whopping 3200 x 1800. Most programs look good on this resolution when you have at least Windows 8.1. But games that are played in a lower resolution all had black borders around them. For some reason, the NVIDIA 750 graphics drivers don't seem to scale the games right. So, since I'm a developer, I decided to fix this. After testing, I found out that the games did work without black borders when I first switched back the desktop resolution to a lower setting. Since I didn't want to do that every time, I decided to write a simple console program for it.
The program had to do a few things for me:
- Change the resolution to a lower setting
- Start a program (game)
- Wait until the program (game) is closed
- Change the resolution back to the old settings
Using the Code
I decided to call the program
ResChanger
(resolution changer). First, I created a new C# console app in Visual Studio 2013 as you can see in the code below:
using System;
using System.IO;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace ResChanger
{
internal class Program
{
#region DllImport
[DllImport("kernel32.dll")]
static extern bool FreeConsole();
#endregion
private static void Main(string[] args)
{
FreeConsole();
if (args.Length != 2)
{
ShowMessage("Not enough command line arguments given.");
return;
}
var newResolution = args[0].Split('x');
int newWidth;
int newHeight;
if (newResolution.Length != 2)
{
ShowMessage("Invalid resolution format given.");
return;
}
if (!int.TryParse(newResolution[0], out newWidth) || !int.TryParse(newResolution[1], out newHeight))
{
ShowMessage("Invalid resolution given, could not parse to integer.");
return;
}
var resolution = new Resolution();
string supportedModes;
if (!resolution.IsDisplayModeSupported(newWidth, newHeight, out supportedModes))
{
ShowMessage("The mode '" + args[0] + "' is not supported,
supported modes are: " + Environment.NewLine + supportedModes +
Environment.NewLine);
return;
}
if (!File.Exists(args[1]))
{
ShowMessage("The file '" + args[1] + "' does not exists.");
return;
}
if (!resolution.ChangeDisplaySettings(newWidth, newHeight))
return;
var runningProgram = Process.Start(args[1]);
if (runningProgram != null) runningProgram.WaitForExit();
resolution.RestoreDisplaySettings();
}
#region ShowMessage
private static void ShowMessage(string errorMessage)
{
MessageBox.Show("Invalid command line arguments given:" +
Environment.NewLine + Environment.NewLine +
"Error: " + errorMessage + Environment.NewLine + Environment.NewLine +
"Expected \"<resolution>\" \"<program to start,
with full path\">" + Environment.NewLine +
Environment.NewLine +
"For example: " + Path.GetFileName(Environment.GetCommandLineArgs()[0]) +
" \"1920x1080\"
\"C:\\Program Files (x86)\\SQUARE ENIX\\Tombraider\\TombRaider.exe\"" +
Environment.NewLine + Environment.NewLine +
"This will first switch the resolution to 1920x1080 and then start Tombraider." +
Environment.NewLine +
"After you exit the game the resolution will be switched back to the old configuration.");
}
#endregion
}
}
I also needed a class to change the resolution itself. Since it is not
possible to do this with standard C# .NET, I did have to switch to
DLLImports. I found some nice articles on CodeProject that I could use
for this. I ended up with the following class called Resolution:
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace ResChanger
{
#region Struct Pointl
[StructLayout(LayoutKind.Sequential)]
public struct Pointl
{
[MarshalAs(UnmanagedType.I4)]
public int x;
[MarshalAs(UnmanagedType.I4)]
public int y;
}
#endregion
#region Struct Devmode
[StructLayout(LayoutKind.Sequential,
CharSet = CharSet.Ansi)]
public struct Devmode
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string dmDeviceName;
[MarshalAs(UnmanagedType.U2)]
public UInt16 dmSpecVersion;
[MarshalAs(UnmanagedType.U2)]
public UInt16 dmDriverVersion;
[MarshalAs(UnmanagedType.U2)]
public UInt16 dmSize;
[MarshalAs(UnmanagedType.U2)]
public UInt16 dmDriverExtra;
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmFields;
public Pointl dmPosition;
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmDisplayOrientation;
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmDisplayFixedOutput;
[MarshalAs(UnmanagedType.I2)]
public Int16 dmColor;
[MarshalAs(UnmanagedType.I2)]
public Int16 dmDuplex;
[MarshalAs(UnmanagedType.I2)]
public Int16 dmYResolution;
[MarshalAs(UnmanagedType.I2)]
public Int16 dmTTOption;
[MarshalAs(UnmanagedType.I2)]
public Int16 dmCollate;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string dmFormName;
[MarshalAs(UnmanagedType.U2)]
public UInt16 dmLogPixels;
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmBitsPerPel;
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmPelsWidth;
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmPelsHeight;
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmDisplayFlags;
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmDisplayFrequency;
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmICMMethod;
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmICMIntent;
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmMediaType;
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmDitherType;
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmReserved1;
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmReserved2;
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmPanningWidth;
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmPanningHeight;
}
#endregion
internal class Resolution
{
#region Fields
private static Devmode _oldDevmode;
#endregion
#region Consts
private const int EnumCurrentSettings = -1;
private const int DispChangeSuccessful = 0;
private const int DispChangeBadmode = -2;
private const int DispChangeRestart = 1;
#endregion
#region DllImport
[DllImport("User32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern Boolean EnumDisplaySettings(
[param: MarshalAs(UnmanagedType.LPTStr)] string lpszDeviceName,
[param: MarshalAs(UnmanagedType.U4)] int iModeNum,
[In, Out] ref Devmode lpDevMode);
[DllImport("User32.dll")]
[return: MarshalAs(UnmanagedType.I4)]
private static extern int ChangeDisplaySettings(
[In, Out] ref Devmode lpDevMode,
[param: MarshalAs(UnmanagedType.U4)] uint dwflags);
#endregion
#region IsDisplayModeSupported
public bool IsDisplayModeSupported(int width, int height, out string supportedModes)
{
var mode = new Devmode();
mode.dmSize = (ushort) Marshal.SizeOf(mode);
var modeIndex = 0;
supportedModes = string.Empty;
var previousSupportedMode = string.Empty;
while (EnumDisplaySettings(null,
modeIndex,
ref mode))
{
if (mode.dmPelsWidth == (uint) width && mode.dmPelsHeight == (uint) height)
return true;
var newSupportedMode = mode.dmPelsWidth + "x" + mode.dmPelsHeight;
if (newSupportedMode != previousSupportedMode)
{
if (supportedModes == string.Empty)
supportedModes += newSupportedMode;
else
supportedModes += ", " + newSupportedMode;
previousSupportedMode = newSupportedMode;
}
modeIndex++;
}
return false;
}
#endregion
#region ChangeDisplaySettings
public bool ChangeDisplaySettings(int width, int height)
{
_oldDevmode = new Devmode();
_oldDevmode.dmSize = (ushort) Marshal.SizeOf(_oldDevmode);
EnumDisplaySettings(null,
EnumCurrentSettings,
ref _oldDevmode);
var newMode = _oldDevmode;
newMode.dmPelsWidth = (uint)width;
newMode.dmPelsHeight = (uint)height;
var result =
ChangeDisplaySettings(ref newMode, 1);
switch (result)
{
case DispChangeSuccessful:
return true;
case DispChangeBadmode:
MessageBox.Show("Mode not supported.");
return false;
case DispChangeRestart:
MessageBox.Show("Restart required.");
return false;
default:
MessageBox.Show("Failed. Error code = " + result);
return false;
}
}
#endregion
#region RestoreDisplaySettings
public void RestoreDisplaySettings()
{
ChangeDisplaySettings(ref _oldDevmode, 1);
}
#endregion
}
}
After putting everything together, we end up with a very simple console program that can be called like this: Reschanger.exe "resolution" "<program to start, including path" for example Reschanger.exe "1920x1080" "c:\Program Files\totalcmd\totalcmd.exe"
This will switch the desktop resolution to 1920 x 1080 and then start totalcmd.exe. You can make a shortcut on the Windows desktop to make everything easier.
And that's it... a very simple solution to an annoying problem.
History
- 2013-12-27 - First version
- 2014-01-03 - Version 1.1, cleaned up the code somewhat and added EXE file to ZIP file