Forum

Windows: Dual Monitor Support -monitor2 cmdline param

Post tutorials on how to do certain tasks within game or engine code here.

Moderator: InsideQC Admins

Windows: Dual Monitor Support -monitor2 cmdline param

Postby Baker » Tue Apr 12, 2011 11:21 pm

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 ..
User avatar
Baker
 
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Postby r00k » Thu Apr 21, 2011 4:08 am

Works like a champ!
r00k
 
Posts: 1110
Joined: Sat Nov 13, 2004 10:39 pm


Return to Programming Tutorials

Who is online

Users browsing this forum: No registered users and 1 guest