Windows: Dual Monitor Support -monitor2 cmdline param

Post tutorials on how to do certain tasks within game or engine code here.
Post Reply
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Windows: Dual Monitor Support -monitor2 cmdline param

Post by Baker »

Requires: Microsoft Visual Studio.NET 2003 or later. Won't work in MSVC6 without an SDK update (Windows 2003 SP1 SDK) that is no longer available via internet from Microsoft.

This will enable an engine to run on the secondary monitor of a dual monitor system by adding -monitor2 to the command line. What this actually does is switch the primary monitor to the secondary in Vid_Init and restore it back in Vid_Shutdown (which is called by Host_Error). So this is achieves dual monitor support for a client by cheating a little, but it isn't much different than how Quake sets everything to fullscreen and resets it back on shutdown/exit. Reason: Windows and OpenGL on dual monitors may or may not have hardware acceleration on the 2nd monitor. This method works around the issue.

Instructions

1. In the beginning of Vid_Init do something like this (in gl_vidnt.c or vid_wgl.c depending on your engine):

Code: Select all

if (COM_CheckParm("-monitor2"))
{
   CollectMonitorInformation ();
   if (num_monitors == 2)  // This is written in a 2-monitor way, don't have a third monitor to test triple monitor 
        SwapPrimaryMonitor (); // Set monitors_swapped to 1
}
2. At the end of Vid_Shutdown do something like this (in gl_vidnt.c or vid_wgl.c depending on your engine):

Code: Select all

    if (monitors_swapped)
		RestorePrimaryMonitor ();
3. Create monitors.c

Code: Select all

#define POINTER_64 __ptr64 //Baker
#include "quakedef.h" // Almost optional!
#include <windows.h>
#define MSGBOX(x) MessageBox(NULL, x, "Info", MB_OK);		// Developer message

typedef struct
{
	char DeviceName[32]; // wingdi.h --> CCHFORMNAME which is 32
	int original_x;
	int original_y;
	int original_width;
	int original_height;
} monitor_def_t;
#define MAX_NUM_MONITORS 2

monitor_def_t monitor_info[MAX_NUM_MONITORS];
int original_primary = -1;
int original_secondary = -1;
int num_monitors;
int monitors_swapped;	

DISPLAY_DEVICE DisplayDevice;

char monitorstring[2048];

DEVMODE deviceMode[MAX_NUM_MONITORS];

void CollectMonitorInformation (void)
{
	int i;
	{
		// Reset monitor info
		num_monitors = 0; // Just in case
		original_primary = -1; // Just in case
		ZeroMemory(&DisplayDevice, sizeof(DISPLAY_DEVICE));
		DisplayDevice.cb = sizeof(DisplayDevice);
	}

	for(i = 0; EnumDisplayDevices(NULL, i, &DisplayDevice, 0) && num_monitors < MAX_NUM_MONITORS; i++)
	{
		if(DisplayDevice.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER || !(DisplayDevice.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP))
			continue; // Non-monitor

		// Ok we found a monitor
			
		deviceMode[num_monitors].dmSize = sizeof(DEVMODE);
		deviceMode[num_monitors].dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL | DM_POSITION | DM_DISPLAYFREQUENCY | DM_DISPLAYFLAGS;
			
		EnumDisplaySettings(DisplayDevice.DeviceName, ENUM_CURRENT_SETTINGS, &deviceMode[num_monitors]);
	
		// Populate fields
		strcpy (monitor_info[num_monitors].DeviceName, DisplayDevice.DeviceName);	
		monitor_info[num_monitors].original_x		=	deviceMode[num_monitors].dmPosition.x;
		monitor_info[num_monitors].original_y		=	deviceMode[num_monitors].dmPosition.y;
		monitor_info[num_monitors].original_width	=	deviceMode[num_monitors].dmPelsWidth;
		monitor_info[num_monitors].original_height	=	deviceMode[num_monitors].dmPelsHeight;

		if (deviceMode[num_monitors].dmPosition.x == 0 && deviceMode[num_monitors].dmPosition.y ==0)
			original_primary = num_monitors;  // Primary monitor is one with 0,0 xy position
		else
			original_secondary = num_monitors;

		strcat (monitorstring, va("Monitor #%i %s %s x y w h %i %i %i %i\n", 
		num_monitors+1,
		monitor_info[num_monitors].DeviceName,
		num_monitors == original_primary ? "(Primary)" : num_monitors == original_secondary ? "(Secondary)" : "(Neither)",
		monitor_info[num_monitors].original_x,
		monitor_info[num_monitors].original_y,
		monitor_info[num_monitors].original_width,
		monitor_info[num_monitors].original_height));

		num_monitors ++;
		
	}
	MSGBOX (va("Num monitors is %i", num_monitors));
	MSGBOX (va("Monitor infos \n\n%s", monitorstring));
	MSGBOX (va("Primary Monitor is %i and secondary is %i", original_primary, original_secondary));

}

void SetPrimaryMonitorOnDeviceName (const char *NewPrimaryMonitor, const char *NewSecondaryMonitor, int SecondaryX, int SecondaryY)
{
	DEVMODE deviceMode_newSecondary;
	DEVMODE deviceMode_newPrimary;

	if (num_monitors == 0)
	{
		MSGBOX ("No monitors ... need to call CollectMonitorInformation first")
		return;
	}

	MSGBOX (va("Setting monitor %s to primary monitor", NewPrimaryMonitor))

	// Get primary infos
	deviceMode_newPrimary.dmSize = sizeof(DEVMODE);
	deviceMode_newPrimary.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL | DM_POSITION | DM_DISPLAYFREQUENCY | DM_DISPLAYFLAGS;
	EnumDisplaySettings(NewPrimaryMonitor, ENUM_CURRENT_SETTINGS, &deviceMode_newPrimary);

	// Get secondary infos
	deviceMode_newSecondary.dmSize = sizeof(DEVMODE);
	deviceMode_newSecondary.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL | DM_POSITION | DM_DISPLAYFREQUENCY | DM_DISPLAYFLAGS;
	EnumDisplaySettings(NewSecondaryMonitor, ENUM_CURRENT_SETTINGS, &deviceMode_newSecondary);

	// move old primary display to new position
	deviceMode_newSecondary.dmFields = DM_POSITION;
	deviceMode_newSecondary.dmPosition.x = deviceMode_newPrimary.dmPelsWidth;
	deviceMode_newSecondary.dmPosition.y = 0;

	ChangeDisplaySettingsEx(NewSecondaryMonitor, &deviceMode_newSecondary, NULL, CDS_UPDATEREGISTRY | CDS_NORESET, NULL);

	// move old secondary display to 0,0
	deviceMode_newPrimary.dmFields = DM_POSITION;
	deviceMode_newPrimary.dmPosition.x = 0;
	deviceMode_newPrimary.dmPosition.y = 0;

	// CDS_UPDATEREGISTRY | CDS_NORESET = Have settings updated in registry, but CDS_NORESET means it won't take effect yet
	ChangeDisplaySettingsEx(NewPrimaryMonitor, &deviceMode_newPrimary, NULL, CDS_SET_PRIMARY | CDS_UPDATEREGISTRY | CDS_NORESET , NULL);
	ChangeDisplaySettingsEx (NULL, NULL, NULL, 0, NULL);  // Force it to take effect

//	Sleep (500); // Let it think a minute
}

void SwapPrimaryMonitor (void)  // In a dual monitor situation, use 0 = first monitor or 1 = second monitor, etc.
{
	// Set secondary monitor to primary
	SetPrimaryMonitorOnDeviceName (monitor_info[original_secondary].DeviceName, monitor_info[original_primary].DeviceName, monitor_info[original_secondary].original_width, monitor_info[original_secondary].original_height);
	monitors_swapped = 1;
}

void RestorePrimaryMonitor (void)
{
	// Set original primary monitor back to primary
	SetPrimaryMonitorOnDeviceName (monitor_info[original_primary].DeviceName, monitor_info[original_secondary].DeviceName, monitor_info[original_primary].original_width, monitor_info[original_primary].original_height);
	monitors_swapped = 0;
}
In Closing

This code is only trivially locked to Quake. In fact, I think the above only uses va (...) from Quake. So it could actually be used in an Windows project pretty much unchanged.

There IS a way to do this "naturally" and just create a Window on the 2nd display, but since you will likely lose hardware acceleration I've found it isn't particularly useful (2 fps sucks). See thread ...
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
r00k
Posts: 1111
Joined: Sat Nov 13, 2004 10:39 pm

Post by r00k »

Works like a champ!
Post Reply