Linux multi-monitor fullscreen support

develop
Nimetu 3 years ago
parent 2efc16ce52
commit d8deff3f23

@ -999,6 +999,9 @@ private:
bool createWindow(const GfxMode& mode);
bool destroyWindow();
// Return monitor info and positon in multi monitor setup or false if monitor not found.
bool getMonitorByName(const std::string &name, sint32 &x, sint32 &y, uint32 &w, uint32 &h) const;
enum EWindowStyle { EWSWindowed, EWSFullscreen };
void setWindowSize(uint32 width, uint32 height);

@ -650,8 +650,6 @@ bool CDriverGL::setDisplay(nlWindow wnd, const GfxMode &mode, bool show, bool re
_win = EmptyWindow;
_CurrentMode = mode;
_WindowVisible = false;
_Resizable = resizeable;
_DestroyWindow = false;
@ -1194,6 +1192,8 @@ bool CDriverGL::saveScreenMode()
#ifdef HAVE_XRANDR
// TODO: if using mode switching, save current xrandr mode to _OldSizeID
res = true;
if (!res && _xrandr_version > 0)
{
XRRScreenConfiguration *screen_config = XRRGetScreenInfo(_dpy, RootWindow(_dpy, screen));
@ -1254,6 +1254,8 @@ bool CDriverGL::restoreScreenMode()
#ifdef HAVE_XRANDR
// TODO: if using mode switching, then restore mode from _OldSizeID
res = true;
if (!res && _xrandr_version > 0)
{
Window root = RootWindow(_dpy, screen);
@ -1348,7 +1350,8 @@ bool CDriverGL::setScreenMode(const GfxMode &mode)
&& mode.Width == previousMode.Width
&& mode.Height == previousMode.Height
&& mode.Depth == previousMode.Depth
&& mode.Frequency == previousMode.Frequency)
&& mode.Frequency == previousMode.Frequency
&& mode.DisplayDevice == previousMode.DisplayDevice)
return true;
#if defined(NL_OS_WINDOWS)
@ -1388,6 +1391,8 @@ bool CDriverGL::setScreenMode(const GfxMode &mode)
bool found = false;
#ifdef HAVE_XRANDR
// TODO: implement mode switching using xrandr crts
found = true;
if (!found && _xrandr_version > 0)
{
@ -1911,8 +1916,92 @@ bool CDriverGL::setWindowStyle(EWindowStyle windowStyle)
return true;
}
bool CDriverGL::getMonitorByName(const std::string &name, sint32 &x, sint32 &y, uint32 &w, uint32 &h) const
{
bool found = false;
#if HAVE_XRANDR
int screen = DefaultScreen(_dpy);
// xrandr 1.5+
if (_xrandr_version >= 105)
{
int nmonitors = 0;
XRRMonitorInfo *monitor = XRRGetMonitors(_dpy, RootWindow(_dpy, screen), 1, &nmonitors);
if (!monitor)
return false;
for(sint i = 0; i< nmonitors; ++i)
{
char* pname = XGetAtomName(_dpy, monitor[i].name);
found = (nlstricmp(pname, name) == 0);
XFree(pname);
if (found)
{
x = monitor[i].x;
y = monitor[i].y;
w = monitor[i].width;
h = monitor[i].height;
break;
}
}
XRRFreeMonitors(monitor);
}
else
{
XRRScreenResources *resources = XRRGetScreenResourcesCurrent(_dpy, RootWindow(_dpy, screen));
if (!resources)
resources = XRRGetScreenResources(_dpy, RootWindow(_dpy, screen));
for(uint i = 0; i < resources->noutput; ++i)
{
XRROutputInfo *output = XRRGetOutputInfo(_dpy, resources, resources->outputs[i]);
if (!output)
continue;
if (output->crtc && output->connection == RR_Connected && nlstricmp(name, output->name) == 0)
{
// physical monitor
XRRCrtcInfo *crtc = XRRGetCrtcInfo(_dpy, resources, output->crtc);
if (crtc)
{
found = true;
x = crtc->x;
y = crtc->y;
// TODO: test rotation
if (crtc->rotation == RR_Rotate_0 || crtc->rotation == RR_Rotate_180)
{
w = crtc->width;
h = crtc->height;
}
else
{
w = crtc->height;
h = crtc->width;
}
XRRFreeCrtcInfo(crtc);
}
}
XRRFreeOutputInfo(output);
if (found)
break;
}
XRRFreeScreenResources(resources);
}
#endif
return found;
}
// --------------------------------------------------
bool CDriverGL::setMode(const GfxMode& mode)
bool CDriverGL::setMode(const GfxMode& amode)
{
H_AUTO_OGL(CDriverGL_setMode);
@ -1920,6 +2009,10 @@ bool CDriverGL::setMode(const GfxMode& mode)
if (!_DestroyWindow)
return true;
#if !(HAVE_XRANDR)
const GfxMode &mode = amode;
#endif
#if defined(NL_OS_WINDOWS)
// save relative cursor
POINT cursorPos;
@ -1929,21 +2022,89 @@ bool CDriverGL::setMode(const GfxMode& mode)
BOOL cursorPosOk = isSystemCursorInClientArea()
&& GetCursorPos(&cursorPos)
&& ScreenToClient(_win, &cursorPos);
// FIXME: this probably needs to use _CurrentMode instead of mode
sint curX = (sint)cursorPos.x * (sint)mode.Width;
sint curY = (sint)cursorPos.y * (sint)mode.Height;
#endif
#if HAVE_XRANDR
GfxMode mode = amode;
if (!mode.Windowed)
{
GfxMode current;
if (!getCurrentScreenMode(current))
nlinfo("3D: XrandR: Reading active monitor info failed");
sint newX = _WindowX;
sint newY = _WindowY;
// make sure resolution matches requested or currently active monitor's resolution
mode.Width = current.Width;
mode.Height = current.Height;
if (!mode.DisplayDevice.empty())
{
uint newW = current.Width;
uint newH = current.Height;
if (getMonitorByName(mode.DisplayDevice, newX, newY, newW, newH))
{
mode.Width = newW;
mode.Height = newH;
}
else
{
nlinfo("3D: XrandR: Reading requested monitor '%s' info failed, using '%s'", mode.DisplayDevice.c_str(), current.DisplayDevice.c_str());
mode.DisplayDevice = current.DisplayDevice;
}
}
// switching monitors.
// first move mouse pointer to target monitor and then move window.
// if window is visible, then also restore mouse relative position.
if (!mode.DisplayDevice.empty() && mode.DisplayDevice != current.DisplayDevice)
{
int screen = DefaultScreen(_dpy);
Window root = RootWindow(_dpy, screen);
uint mouseX = mode.Width / 2;
uint mouseY = mode.Height / 2;
XWindowAttributes xwa;
XGetWindowAttributes(_dpy, _win, &xwa);
if (xwa.map_state != IsUnmapped)
{
Window root_win;
Window child_win;
sint root_x, root_y, win_x, win_y;
uint mask;
Bool res = XQueryPointer(_dpy, _win, &root_win, &child_win, &root_x, &root_y, &win_x, &win_y, &mask);
if (res)
{
mouseX = (uint)((float)win_x * mode.Width / current.Width);
mouseY = (uint)((float)win_y * mode.Height / current.Height);
}
}
XWarpPointer(_dpy, None, root, None, None, None, None, newX + mouseX, newY + mouseY);
XMoveWindow(_dpy, _win, newX, newY);
_WindowX = newX;
_WindowY = newY;
}
}
#endif
if (!setScreenMode(mode))
return false;
_CurrentMode.Depth = mode.Depth;
_CurrentMode.Frequency = mode.Frequency;
_CurrentMode.DisplayDevice = mode.DisplayDevice;
// when changing window style, it's possible system change window size too
setWindowStyle(mode.Windowed ? EWSWindowed : EWSFullscreen);
if (!mode.Windowed)
_CurrentMode.Depth = mode.Depth;
setWindowSize(mode.Width, mode.Height);
setWindowPos(_WindowX, _WindowY);
setWindowSize(mode.Width, mode.Height);
switch (_CurrentMode.Depth)
{
@ -2120,6 +2281,66 @@ bool CDriverGL::getModes(std::vector<GfxMode> &modes)
int screen = DefaultScreen(_dpy);
#if defined(HAVE_XRANDR)
if (_xrandr_version >= 105)
{
int nmonitors = 0;
// virtual monitors
XRRMonitorInfo *monitor = XRRGetMonitors(_dpy, RootWindow(_dpy, screen), 1, &nmonitors);
for(sint i = 0; i < nmonitors; ++i)
{
char * name = XGetAtomName(_dpy, monitor[i].name);
GfxMode mode;
mode.DisplayDevice = name;
mode.Width = monitor[i].width;
mode.Height = monitor[i].height;
mode.Frequency = 0;
modes.push_back(mode);
XFree(name);
}
XRRFreeMonitors(monitor);
}
else
{
XRRScreenResources *resources = XRRGetScreenResourcesCurrent(_dpy, RootWindow(_dpy, screen));
if (!resources)
resources = XRRGetScreenResources(_dpy, RootWindow(_dpy, screen));
std::map<int, int> resourceModeMap;
for(sint i = 0; i< resources->nmode; ++i)
resourceModeMap.insert(std::make_pair(resources->modes[i].id, i));
for(sint i = 0; i < resources->noutput; ++i)
{
XRROutputInfo *output = XRRGetOutputInfo(_dpy, resources, resources->outputs[i]);
if (!output)
continue;
if (output->crtc && output->connection == RR_Connected)
{
// physical monitor
XRRCrtcInfo *crtc = XRRGetCrtcInfo(_dpy, resources, output->crtc);
if (crtc)
{
std::map<int,int>::const_iterator it = resourceModeMap.find(crtc->mode);
if (it != resourceModeMap.end())
{
GfxMode mode;
mode.DisplayDevice = output->name;
mode.Width = resources->modes[it->second].width;
mode.Height = resources->modes[it->second].height;
mode.Frequency = 0;
modes.push_back(mode);
}
XRRFreeCrtcInfo(crtc);
}
}
XRRFreeOutputInfo(output);
}
XRRFreeScreenResources(resources);
}
found = modes.size() > 0;
if (!found && _xrandr_version >= 100)
{
XRRScreenConfiguration *screen_config = XRRGetScreenInfo(_dpy, RootWindow(_dpy, screen));
@ -2255,6 +2476,120 @@ bool CDriverGL::getCurrentScreenMode(GfxMode &mode)
int screen = DefaultScreen(_dpy);
#ifdef HAVE_XRANDR
int x = 0;
int y = 0;
Window child;
// get window position so we can compare monitors (or mouse position if window not visible yet)
XWindowAttributes xwa;
XGetWindowAttributes(_dpy, _win, &xwa);
if (xwa.map_state != IsUnmapped)
{
XTranslateCoordinates(_dpy, _win, xwa.root, xwa.x, xwa.y, &x, &y, &child);
}
else
{
sint rx, ry, wx, wy;
uint mask;
Bool res = XQueryPointer(_dpy, RootWindow(_dpy, screen), &child, &child, &rx, &ry, &wx, &wy, &mask);
if (res)
{
x = rx;
y = ry;
}
}
if (_xrandr_version >= 105)
{
int nmonitors = 0;
XRRMonitorInfo *monitor = XRRGetMonitors(_dpy, RootWindow(_dpy, screen), 1, &nmonitors);
if (monitor)
{
sint bestMatch = -1;
for(sint i = 0; i< nmonitors; ++i)
{
if ((x >= monitor[i].x && x < (monitor[i].x + monitor[i].width) &&
y >= monitor[i].y && y < (monitor[i].y + monitor[i].height)) ||
(monitor[i].primary && bestMatch == -1))
{
bestMatch = i;
}
}
// best match or primary monitor
if (bestMatch != -1)
{
found = true;
char* pname = XGetAtomName(_dpy, monitor[bestMatch].name);
mode.DisplayDevice = pname;
mode.Width = monitor[bestMatch].width;
mode.Height = monitor[bestMatch].height;
mode.Windowed = _CurrentMode.Windowed;
mode.OffScreen = false;
mode.Depth = (uint) DefaultDepth(_dpy, screen);
mode.Frequency = 0;
XFree(pname);
}
XRRFreeMonitors(monitor);
}
}
else
{
XRRScreenResources *resources = XRRGetScreenResourcesCurrent(_dpy, RootWindow(_dpy, screen));
if (!resources)
resources = XRRGetScreenResources(_dpy, RootWindow(_dpy, screen));
for(uint i = 0; i < resources->noutput; ++i)
{
XRROutputInfo *output = XRRGetOutputInfo(_dpy, resources, resources->outputs[i]);
if (!output)
continue;
if (output->crtc && output->connection == RR_Connected)
{
XRRCrtcInfo *crtc = XRRGetCrtcInfo(_dpy, resources, output->crtc);
if (crtc)
{
sint width, height;
bool match = false;
// TODO: test rotation
if (crtc->rotation == RR_Rotate_0 || crtc->rotation == RR_Rotate_180)
{
width = crtc->width;
height = crtc->height;
}
else
{
width = crtc->height;
height = crtc->width;
}
if (x >= crtc->x && y >= crtc->y && x < (crtc->x + width) && y < (crtc->y + height))
{
found = true;
mode.DisplayDevice = output->name;
mode.Width = width;
mode.Height = height;
mode.Windowed = _CurrentMode.Windowed;
mode.OffScreen = false;
mode.Depth = (uint) DefaultDepth(_dpy, screen);
mode.Frequency = 0;
}
XRRFreeCrtcInfo(crtc);
}
}
XRRFreeOutputInfo(output);
if (found)
break;
}
XRRFreeScreenResources(resources);
}
if (!found && _xrandr_version > 0)
{
@ -2432,7 +2767,6 @@ void CDriverGL::setWindowPos(sint32 x, sint32 y)
_DecorationWidth = -1;
_DecorationHeight = -1;
}
XMoveWindow(_dpy, _win, x, y);
}

@ -331,6 +331,7 @@ bool CDriverUser::getCurrentScreenMode(CMode &mode)
GfxMode gfxMode;
bool res= _Driver->getCurrentScreenMode(gfxMode);
mode.Windowed= gfxMode.Windowed;
mode.DisplayDevice= gfxMode.DisplayDevice;
mode.Width= gfxMode.Width;
mode.Height= gfxMode.Height;
mode.Depth= gfxMode.Depth;

@ -302,6 +302,7 @@ CClientConfig::CClientConfig()
SelectedSlot = 0; // Default is slot 0
Windowed = false; // Default is windowed mode.
MonitorName = "";
Width = 0; // Default Width for the window (0 = current screen resolution).
Height = 0; // Default Height for the window (0 = current screen resolution).
Depth = 32; // Default Bit per Pixel.
@ -847,6 +848,8 @@ void CClientConfig::setValues()
}
else
cfgWarning("Default value used for 'Fullscreen'");
READ_STRING_FV(MonitorName);
// Width
READ_INT_FV(Width)
// Height
@ -1941,6 +1944,11 @@ void CClientConfig::serial(NLMISC::IStream &f)
f.serial(Light);
f.xmlPop();
f.xmlPushBegin("MonitorName");
f.xmlPushEnd();
f.serial(MonitorName);
f.xmlPop();
f.xmlPushBegin("Windowed");
f.xmlPushEnd();
f.serial(Windowed);

@ -137,6 +137,8 @@ struct CClientConfig
TDriver3D Driver3D;
/// Application start in a window or in fullscreen.
bool Windowed;
/// Monitor to use for fullscreen
std::string MonitorName;
/// Width for the Application.
uint16 Width;
/// Height for the Application.

@ -1070,6 +1070,7 @@ void prelogInit()
// fullscreen, using monitor resolution
mode.Windowed = false;
ClientCfg.MonitorName = mode.DisplayDevice;
ClientCfg.Windowed = mode.Windowed;
ClientCfg.Width = mode.Width;
ClientCfg.Height = mode.Height;
@ -1086,6 +1087,7 @@ void prelogInit()
// update client.cfg with detected resolution
ClientCfg.writeBool("FullScreen", !ClientCfg.Windowed, true);
ClientCfg.writeString("MonitorName", ClientCfg.MonitorName, true);
ClientCfg.writeInt("Width", ClientCfg.Width, true);
ClientCfg.writeInt("Height", ClientCfg.Height, true);
ClientCfg.writeInt("Depth", ClientCfg.Depth, true);
@ -1095,6 +1097,7 @@ void prelogInit()
}
else
{
mode.DisplayDevice = ClientCfg.MonitorName;
mode.Windowed = ClientCfg.Windowed;
mode.Width = ClientCfg.Width;
mode.Height = ClientCfg.Height;

@ -3458,6 +3458,7 @@ class CHandlerGameConfigApply : public IActionHandler
{
// Get W, H
sint w = 1024, h = 768;
string name;
{
CDBGroupComboBox *pCB = dynamic_cast<CDBGroupComboBox*>(CWidgetManager::getInstance()->getElementFromId( GAME_CONFIG_VIDEO_MODES_COMBO ));
if( pCB != NULL )
@ -3467,6 +3468,11 @@ class CHandlerGameConfigApply : public IActionHandler
fromString(tmp, w);
tmp = vidModeStr.substr(vidModeStr.find('x')+2,vidModeStr.size());
fromString(tmp, h);
// extract monitor "1024x768 (VGA-1)"
string::size_type pos = vidModeStr.find('(');
if (pos != std::string::npos)
name = vidModeStr.substr(pos + 1, vidModeStr.find(")") - pos - 1);
}
}
@ -3509,6 +3515,7 @@ class CHandlerGameConfigApply : public IActionHandler
ClientCfg.Width = w;
ClientCfg.Height = h;
ClientCfg.MonitorName = name;
// Write the modified client.cfg
ClientCfg.writeBool("FullScreen", bFullscreen);
@ -3517,6 +3524,7 @@ class CHandlerGameConfigApply : public IActionHandler
if (bFullscreen)
{
ClientCfg.writeString("MonitorName", name, true);
ClientCfg.writeInt("Depth", screenMode.Depth);
ClientCfg.writeInt("Frequency", freq);
}

@ -2208,6 +2208,7 @@ class CAHLessLod : public IActionHandler
REGISTER_ACTION_HANDLER (CAHLessLod, "less_lod");
// ***************************************************************************
// TODO: remove resolution change from login screen
class CAHUninitResLod : public IActionHandler
{
virtual void execute (CCtrlBase * /* pCaller */, const string &/* sParams */)

@ -76,7 +76,7 @@ void updateFromClientCfg()
// set latest config display mode if not attached
if (!StereoDisplayAttached)
setVideoMode(UDriver::CMode(ClientCfg.Width, ClientCfg.Height, (uint8)ClientCfg.Depth,
ClientCfg.Windowed, ClientCfg.Frequency));
ClientCfg.Windowed, ClientCfg.Frequency, -1, ClientCfg.MonitorName));
// force software cursor when attached
InitMouseWithCursor(ClientCfg.HardwareCursor && !StereoDisplayAttached);
}
@ -87,12 +87,13 @@ void updateFromClientCfg()
(ClientCfg.Width != LastClientCfg.Width) ||
(ClientCfg.Height != LastClientCfg.Height) ||
(ClientCfg.Depth != LastClientCfg.Depth) ||
(ClientCfg.Frequency != LastClientCfg.Frequency))
(ClientCfg.Frequency != LastClientCfg.Frequency)||
(ClientCfg.MonitorName != LastClientCfg.MonitorName))
{
if (!StereoDisplayAttached)
{
setVideoMode(UDriver::CMode(ClientCfg.Width, ClientCfg.Height, (uint8)ClientCfg.Depth,
ClientCfg.Windowed, ClientCfg.Frequency));
ClientCfg.Windowed, ClientCfg.Frequency, -1, ClientCfg.MonitorName));
}
}

@ -1408,6 +1408,7 @@ bool getRyzomModes(std::vector<NL3D::UDriver::CMode> &videoModes, std::vector<st
// **** Init Video Modes
Driver->getModes(videoModes);
// TODO: for resolutions below 1024x768, could use automatic UI scaling like in login/outgame
// Remove modes under 1024x768 (outgame ui limitation) and get the unique strings
sint i, j;
for (i = 0; i < (sint)videoModes.size(); ++i)
@ -1424,6 +1425,9 @@ bool getRyzomModes(std::vector<NL3D::UDriver::CMode> &videoModes, std::vector<st
// create string format with width and height
string res = toString(videoModes[i].Width)+" x "+toString(videoModes[i].Height);
if (!videoModes[i].DisplayDevice.empty())
res += toString(" (%s)", videoModes[i].DisplayDevice.c_str());
// check if video mode already found in list
for (j = 0; j < (sint)stringModeList.size(); ++j)
{

Loading…
Cancel
Save