/** * FreeRDP: A Remote Desktop Protocol Implementation * X11 Monitor Handling * * Copyright 2011 Marc-Andre Moreau * Copyright 2017 David Fort * Copyright 2018 Kai Harms * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #define TAG CLIENT_TAG("x11") #ifdef WITH_XINERAMA #include #endif #ifdef WITH_XRANDR #include #include #if (RANDR_MAJOR * 100 + RANDR_MINOR) >= 105 #define USABLE_XRANDR #endif #endif #include "xf_monitor.h" /* See MSDN Section on Multiple Display Monitors: http://msdn.microsoft.com/en-us/library/dd145071 */ int xf_list_monitors(xfContext* xfc) { Display* display; int major, minor; int i, nmonitors = 0; display = XOpenDisplay(NULL); if (!display) { WLog_ERR(TAG, "failed to open X display"); return -1; } #if defined(USABLE_XRANDR) if (XRRQueryExtension(xfc->display, &major, &minor) && (XRRQueryVersion(xfc->display, &major, &minor) == True) && (major * 100 + minor >= 105)) { XRRMonitorInfo* monitors = XRRGetMonitors(xfc->display, DefaultRootWindow(xfc->display), 1, &nmonitors); for (i = 0; i < nmonitors; i++) { printf(" %s [%d] %dx%d\t+%d+%d\n", monitors[i].primary ? "*" : " ", i, monitors[i].width, monitors[i].height, monitors[i].x, monitors[i].y); } XRRFreeMonitors(monitors); } else #endif #ifdef WITH_XINERAMA if (XineramaQueryExtension(display, &major, &minor)) { if (XineramaIsActive(display)) { XineramaScreenInfo* screen = XineramaQueryScreens(display, &nmonitors); for (i = 0; i < nmonitors; i++) { printf(" %s [%d] %hdx%hd\t+%hd+%hd\n", (i == 0) ? "*" : " ", i, screen[i].width, screen[i].height, screen[i].x_org, screen[i].y_org); } XFree(screen); } } else #else { Screen* screen = ScreenOfDisplay(display, DefaultScreen(display)); printf(" * [0] %dx%d\t+0+0\n", WidthOfScreen(screen), HeightOfScreen(screen)); } #endif XCloseDisplay(display); return 0; } static BOOL xf_is_monitor_id_active(xfContext* xfc, UINT32 id) { UINT32 index; rdpSettings* settings = xfc->context.settings; if (!settings->NumMonitorIds) return TRUE; for (index = 0; index < settings->NumMonitorIds; index++) { if (settings->MonitorIds[index] == id) return TRUE; } return FALSE; } BOOL xf_detect_monitors(xfContext* xfc, UINT32* pMaxWidth, UINT32* pMaxHeight) { int nmonitors = 0; int monitor_index = 0; BOOL primaryMonitorFound = FALSE; VIRTUAL_SCREEN* vscreen; rdpSettings* settings; int mouse_x, mouse_y, _dummy_i; Window _dummy_w; int current_monitor = 0; Screen* screen; MONITOR_INFO* monitor; #if defined WITH_XINERAMA || defined WITH_XRANDR int major, minor; #endif #if defined(USABLE_XRANDR) XRRMonitorInfo* rrmonitors = NULL; BOOL useXRandr = FALSE; #endif if (!xfc || !pMaxWidth || !pMaxHeight || !xfc->context.settings) return FALSE; settings = xfc->context.settings; vscreen = &xfc->vscreen; *pMaxWidth = settings->DesktopWidth; *pMaxHeight = settings->DesktopHeight; /* get mouse location */ if (!XQueryPointer(xfc->display, DefaultRootWindow(xfc->display), &_dummy_w, &_dummy_w, &mouse_x, &mouse_y, &_dummy_i, &_dummy_i, (void*)&_dummy_i)) mouse_x = mouse_y = 0; #if defined(USABLE_XRANDR) if (XRRQueryExtension(xfc->display, &major, &minor) && (XRRQueryVersion(xfc->display, &major, &minor) == True) && (major * 100 + minor >= 105)) { XRRMonitorInfo* rrmonitors = XRRGetMonitors(xfc->display, DefaultRootWindow(xfc->display), 1, &vscreen->nmonitors); if (vscreen->nmonitors > 16) vscreen->nmonitors = 0; if (vscreen->nmonitors) { int i; for (i = 0; i < vscreen->nmonitors; i++) { vscreen->monitors[i].area.left = rrmonitors[i].x; vscreen->monitors[i].area.top = rrmonitors[i].y; vscreen->monitors[i].area.right = rrmonitors[i].x + rrmonitors[i].width - 1; vscreen->monitors[i].area.bottom = rrmonitors[i].y + rrmonitors[i].height - 1; vscreen->monitors[i].primary = rrmonitors[i].primary > 0; } } XRRFreeMonitors(rrmonitors); useXRandr = TRUE; } else #endif #ifdef WITH_XINERAMA if (XineramaQueryExtension(xfc->display, &major, &minor) && XineramaIsActive(xfc->display)) { XineramaScreenInfo* screenInfo = XineramaQueryScreens(xfc->display, &vscreen->nmonitors); if (vscreen->nmonitors > 16) vscreen->nmonitors = 0; if (vscreen->nmonitors) { int i; for (i = 0; i < vscreen->nmonitors; i++) { vscreen->monitors[i].area.left = screenInfo[i].x_org; vscreen->monitors[i].area.top = screenInfo[i].y_org; vscreen->monitors[i].area.right = screenInfo[i].x_org + screenInfo[i].width - 1; vscreen->monitors[i].area.bottom = screenInfo[i].y_org + screenInfo[i].height - 1; } } XFree(screenInfo); } #endif xfc->fullscreenMonitors.top = xfc->fullscreenMonitors.bottom = xfc->fullscreenMonitors.left = xfc->fullscreenMonitors.right = 0; /* Determine which monitor that the mouse cursor is on */ if (vscreen->monitors) { int i; for (i = 0; i < vscreen->nmonitors; i++) { if ((mouse_x >= vscreen->monitors[i].area.left) && (mouse_x <= vscreen->monitors[i].area.right) && (mouse_y >= vscreen->monitors[i].area.top) && (mouse_y <= vscreen->monitors[i].area.bottom)) { current_monitor = i; break; } } } /* Even for a single monitor, we need to calculate the virtual screen to support window managers that do not implement all X window state hints. If the user did not request multiple monitor or is using workarea without remote app, we force the number of monitors be 1 so later the rest of the client don't end up using more monitors than the user desires. */ if ((!settings->UseMultimon && !settings->SpanMonitors) || (settings->Workarea && !settings->RemoteApplicationMode)) { /* If no monitors were specified on the command-line then set the current monitor as active */ if (!settings->NumMonitorIds) { settings->MonitorIds[0] = current_monitor; } /* Always sets number of monitors from command-line to just 1. * If the monitor is invalid then we will default back to current monitor * later as a fallback. So, there is no need to validate command-line entry here. */ settings->NumMonitorIds = 1; } /* WORKAROUND: With Remote Application Mode - using NET_WM_WORKAREA * causes issues with the ability to fully size the window vertically * (the bottom of the window area is never updated). So, we just set * the workArea to match the full Screen width/height. */ if (settings->RemoteApplicationMode || !xf_GetWorkArea(xfc)) { /* if only 1 monitor is enabled, use monitor area this is required in case of a screen composed of more than one monitor but user did not enable multimonitor */ if ((settings->NumMonitorIds == 1) && (vscreen->nmonitors > current_monitor)) { monitor = vscreen->monitors + current_monitor; if (!monitor) return FALSE; xfc->workArea.x = monitor->area.left; xfc->workArea.y = monitor->area.top; xfc->workArea.width = monitor->area.right - monitor->area.left + 1; xfc->workArea.height = monitor->area.bottom - monitor->area.top + 1; } else { xfc->workArea.x = 0; xfc->workArea.y = 0; xfc->workArea.width = WidthOfScreen(xfc->screen); xfc->workArea.height = HeightOfScreen(xfc->screen); } } if (settings->Fullscreen) { *pMaxWidth = WidthOfScreen(xfc->screen); *pMaxHeight = HeightOfScreen(xfc->screen); } else if (settings->Workarea) { *pMaxWidth = xfc->workArea.width; *pMaxHeight = xfc->workArea.height; } else if (settings->PercentScreen) { /* If we have specific monitor information then limit the PercentScreen value * to only affect the current monitor vs. the entire desktop */ if (vscreen->nmonitors > 0) { if (!vscreen->monitors) return FALSE; *pMaxWidth = vscreen->monitors[current_monitor].area.right - vscreen->monitors[current_monitor].area.left + 1; *pMaxHeight = vscreen->monitors[current_monitor].area.bottom - vscreen->monitors[current_monitor].area.top + 1; if (settings->PercentScreenUseWidth) *pMaxWidth = ((vscreen->monitors[current_monitor].area.right - vscreen->monitors[current_monitor].area.left + 1) * settings->PercentScreen) / 100; if (settings->PercentScreenUseHeight) *pMaxHeight = ((vscreen->monitors[current_monitor].area.bottom - vscreen->monitors[current_monitor].area.top + 1) * settings->PercentScreen) / 100; } else { *pMaxWidth = xfc->workArea.width; *pMaxHeight = xfc->workArea.height; if (settings->PercentScreenUseWidth) *pMaxWidth = (xfc->workArea.width * settings->PercentScreen) / 100; if (settings->PercentScreenUseHeight) *pMaxHeight = (xfc->workArea.height * settings->PercentScreen) / 100; } } else if (settings->DesktopWidth && settings->DesktopHeight) { *pMaxWidth = settings->DesktopWidth; *pMaxHeight = settings->DesktopHeight; } /* Create array of all active monitors by taking into account monitors requested on the * command-line */ { int i; for (i = 0; i < vscreen->nmonitors; i++) { MONITOR_ATTRIBUTES* attrs; if (!xf_is_monitor_id_active(xfc, (UINT32)i)) continue; if (!vscreen->monitors) return FALSE; settings->MonitorDefArray[nmonitors].x = (vscreen->monitors[i].area.left * (settings->PercentScreenUseWidth ? settings->PercentScreen : 100)) / 100; settings->MonitorDefArray[nmonitors].y = (vscreen->monitors[i].area.top * (settings->PercentScreenUseHeight ? settings->PercentScreen : 100)) / 100; settings->MonitorDefArray[nmonitors].width = ((vscreen->monitors[i].area.right - vscreen->monitors[i].area.left + 1) * (settings->PercentScreenUseWidth ? settings->PercentScreen : 100)) / 100; settings->MonitorDefArray[nmonitors].height = ((vscreen->monitors[i].area.bottom - vscreen->monitors[i].area.top + 1) * (settings->PercentScreenUseWidth ? settings->PercentScreen : 100)) / 100; settings->MonitorDefArray[nmonitors].orig_screen = i; #ifdef USABLE_XRANDR if (useXRandr && rrmonitors) { Rotation rot, ret; attrs = &settings->MonitorDefArray[nmonitors].attributes; attrs->physicalWidth = rrmonitors[i].mwidth; attrs->physicalHeight = rrmonitors[i].mheight; ret = XRRRotations(xfc->display, i, &rot); attrs->orientation = rot; } #endif if ((UINT32)i == settings->MonitorIds[0]) { settings->MonitorDefArray[nmonitors].is_primary = TRUE; settings->MonitorLocalShiftX = settings->MonitorDefArray[nmonitors].x; settings->MonitorLocalShiftY = settings->MonitorDefArray[nmonitors].y; primaryMonitorFound = TRUE; } nmonitors++; } } /* If no monitor is active(bogus command-line monitor specification) - then lets try to fallback * to go fullscreen on the current monitor only */ if (nmonitors == 0 && vscreen->nmonitors > 0) { INT32 width, height; if (!vscreen->monitors) return FALSE; width = vscreen->monitors[current_monitor].area.right - vscreen->monitors[current_monitor].area.left + 1L; height = vscreen->monitors[current_monitor].area.bottom - vscreen->monitors[current_monitor].area.top + 1L; settings->MonitorDefArray[0].x = vscreen->monitors[current_monitor].area.left; settings->MonitorDefArray[0].y = vscreen->monitors[current_monitor].area.top; settings->MonitorDefArray[0].width = MIN(width, (INT64)(*pMaxWidth)); settings->MonitorDefArray[0].height = MIN(height, (INT64)(*pMaxHeight)); settings->MonitorDefArray[0].orig_screen = current_monitor; nmonitors = 1; } settings->MonitorCount = nmonitors; /* If we have specific monitor information */ if (settings->MonitorCount) { UINT32 i; /* Initialize bounding rectangle for all monitors */ int vX = settings->MonitorDefArray[0].x; int vY = settings->MonitorDefArray[0].y; int vR = vX + settings->MonitorDefArray[0].width; int vB = vY + settings->MonitorDefArray[0].height; xfc->fullscreenMonitors.top = xfc->fullscreenMonitors.bottom = xfc->fullscreenMonitors.left = xfc->fullscreenMonitors.right = settings->MonitorDefArray[0].orig_screen; /* Calculate bounding rectangle around all monitors to be used AND * also set the Xinerama indices which define left/top/right/bottom monitors. */ for (i = 1; i < settings->MonitorCount; i++) { /* does the same as gdk_rectangle_union */ int destX = MIN(vX, settings->MonitorDefArray[i].x); int destY = MIN(vY, settings->MonitorDefArray[i].y); int destR = MAX(vR, settings->MonitorDefArray[i].x + settings->MonitorDefArray[i].width); int destB = MAX(vB, settings->MonitorDefArray[i].y + settings->MonitorDefArray[i].height); if (vX != destX) xfc->fullscreenMonitors.left = settings->MonitorDefArray[i].orig_screen; if (vY != destY) xfc->fullscreenMonitors.top = settings->MonitorDefArray[i].orig_screen; if (vR != destR) xfc->fullscreenMonitors.right = settings->MonitorDefArray[i].orig_screen; if (vB != destB) xfc->fullscreenMonitors.bottom = settings->MonitorDefArray[i].orig_screen; vX = destX / ((settings->PercentScreenUseWidth ? settings->PercentScreen : 100) / 100.); vY = destY / ((settings->PercentScreenUseHeight ? settings->PercentScreen : 100) / 100.); vR = destR / ((settings->PercentScreenUseWidth ? settings->PercentScreen : 100) / 100.); vB = destB / ((settings->PercentScreenUseHeight ? settings->PercentScreen : 100) / 100.); } vscreen->area.left = 0; vscreen->area.right = vR - vX - 1; vscreen->area.top = 0; vscreen->area.bottom = vB - vY - 1; if (settings->Workarea) { vscreen->area.top = xfc->workArea.y; vscreen->area.bottom = xfc->workArea.height + xfc->workArea.y - 1; } if (!primaryMonitorFound) { /* If we have a command line setting we should use it */ if (settings->NumMonitorIds) { /* The first monitor is the first in the setting which should be used */ monitor_index = settings->MonitorIds[0]; } else { /* This is the same as when we would trust the Xinerama results.. and set the monitor index to zero. The monitor listed with /monitor-list on index zero is always the primary */ screen = DefaultScreenOfDisplay(xfc->display); monitor_index = XScreenNumberOfScreen(screen); } int j = monitor_index; /* If the "default" monitor is not 0,0 use it */ if (settings->MonitorDefArray[j].x != 0 || settings->MonitorDefArray[j].y != 0) { settings->MonitorDefArray[j].is_primary = TRUE; settings->MonitorLocalShiftX = settings->MonitorDefArray[j].x; settings->MonitorLocalShiftY = settings->MonitorDefArray[j].y; primaryMonitorFound = TRUE; } else { /* Lets try to see if there is a monitor with a 0,0 coordinate and use it as a * fallback*/ for (i = 0; i < settings->MonitorCount; i++) { if (!primaryMonitorFound && settings->MonitorDefArray[i].x == 0 && settings->MonitorDefArray[i].y == 0) { settings->MonitorDefArray[i].is_primary = TRUE; settings->MonitorLocalShiftX = settings->MonitorDefArray[i].x; settings->MonitorLocalShiftY = settings->MonitorDefArray[i].y; primaryMonitorFound = TRUE; } } } } /* Subtract monitor shift from monitor variables for server-side use. * We maintain monitor shift value as Window requires the primary monitor to have a * coordinate of 0,0 In some X configurations, no monitor may have a coordinate of 0,0. This * can also be happen if the user requests specific monitors from the command-line as well. * So, we make sure to translate our primary monitor's upper-left corner to 0,0 on the * server. */ for (i = 0; i < settings->MonitorCount; i++) { settings->MonitorDefArray[i].x = settings->MonitorDefArray[i].x - settings->MonitorLocalShiftX; settings->MonitorDefArray[i].y = settings->MonitorDefArray[i].y - settings->MonitorLocalShiftY; } /* Set the desktop width and height according to the bounding rectangle around the active * monitors */ *pMaxWidth = MIN(*pMaxWidth, (UINT32)vscreen->area.right - vscreen->area.left + 1); *pMaxHeight = MIN(*pMaxHeight, (UINT32)vscreen->area.bottom - vscreen->area.top + 1); } /* some 2008 server freeze at logon if we announce support for monitor layout PDU with * #monitors < 2. So let's announce it only if we have more than 1 monitor. */ if (settings->MonitorCount) settings->SupportMonitorLayoutPdu = TRUE; #ifdef USABLE_XRANDR if (rrmonitors) XRRFreeMonitors(rrmonitors); #endif return TRUE; }