Page 1 of 1

Windows: Dual Monitor Support -monitor2 cmdline param

Posted: Tue Apr 12, 2011 11:21 pm
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 ...

Posted: Thu Apr 21, 2011 4:08 am
by r00k
Works like a champ!