OpenCPN Partial API docs
displays.cpp
1 /***************************************************************************
2  *
3  * Project: OpenCPN
4  * Purpose: OpenCPN Display utilities
5  * Author: David Register
6  *
7  ***************************************************************************
8  * Copyright (C) 2010 by David S. Register *
9  * *
10  * This program is free software; you can redistribute it and/or modify *
11  * it under the terms of the GNU General Public License as published by *
12  * the Free Software Foundation; either version 2 of the License, or *
13  * (at your option) any later version. *
14  * *
15  * This program is distributed in the hope that it will be useful, *
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
18  * GNU General Public License for more details. *
19  * *
20  * You should have received a copy of the GNU General Public License *
21  * along with this program; if not, write to the *
22  * Free Software Foundation, Inc., *
23  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
24  **************************************************************************/
25 
26 #include <iostream>
27 #include "config.h"
28 #include "displays.h"
29 #include "model/logger.h"
30 
31 // Platform-specific includes
32 #if __linux__
33 #ifdef OCPN_HAVE_X11
34 #include <X11/Xlib.h>
35 #include <X11/extensions/Xrandr.h>
36 #include <gdk/gdk.h>
37 #endif
38 #elif _WIN32
39 #include <Windows.h>
40 #include <ShellScalingApi.h>
41 #include <locale>
42 #include <codecvt>
43 #elif __APPLE__
44 #include <CoreGraphics/CoreGraphics.h>
45 #endif
46 
47 size_t g_num_monitors = 0;
48 size_t g_current_monitor = 0;
49 double g_current_monitor_dip_px_ratio = 1.0;
50 std::vector<OCPN_MonitorInfo> g_monitor_info;
51 
52 #if _WIN32
53 
54 BOOL CALLBACK MonitorEnumProc(HMONITOR hMonitor, HDC hdcMonitor,
55  LPRECT lprcMonitor, LPARAM dwData) {
56  MONITORINFOEX monitorInfo;
57  monitorInfo.cbSize = sizeof(MONITORINFOEX);
58  if (GetMonitorInfo(hMonitor, &monitorInfo)) {
59  UINT rawdpiX, rawdpiY;
60  UINT effectivedpiX, effectivedpiY;
61  /* In newer SDKs (Windows 8.1+) it is much simpler to get DPI for each
62  * monitor as GetDpiForMonitor actually is exposed */
63  HRESULT hr = GetDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &effectivedpiX, &effectivedpiY);
64  if (!SUCCEEDED(hr)) {
65  WARNING_LOG << "GetDpiForMonitor MDT_EFFECTIVE_DPI failed, falling back to 96 DPI";
66  effectivedpiX = 96;
67  effectivedpiY = 96;
68  }
69  hr = GetDpiForMonitor(hMonitor, MDT_RAW_DPI, &rawdpiX, &rawdpiY);
70  if (!SUCCEEDED(hr) || rawdpiX == 0 || rawdpiY == 0) {
71  WARNING_LOG << "GetDpiForMonitor MDT_RAW_DPI failed, falling back to " << effectivedpiX <<" DPI"; // This happens in virtualized environments
72  rawdpiX = effectivedpiX;
73  rawdpiY = effectivedpiY;
74  }
75  DEBUG_LOG << "Raw DPI " << rawdpiX << "x" << rawdpiY;
76  DEBUG_LOG << "Effective DPI " << effectivedpiX << "x" << effectivedpiY;
77  DEVICE_SCALE_FACTOR scaleFactor;
78  hr = GetScaleFactorForMonitor(hMonitor, &scaleFactor);
79  if (!SUCCEEDED(hr) || scaleFactor == DEVICE_SCALE_FACTOR_INVALID) {
80  WARNING_LOG << "GetScaleFactorForMonitor failed, falling back to 100";
81  scaleFactor = SCALE_100_PERCENT;
82  }
83 
84  auto width = monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left;
85  auto height = monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top;
86  auto mmx = width * 25.4 / rawdpiX;
87  auto mmy = height * 25.4 / rawdpiY;
88  std::wstring ws(monitorInfo.szDevice);
89  DEBUG_LOG << "Display " << hMonitor << ":";
90  DEBUG_LOG << " Monitor Name: " << std::string(ws.begin(), ws.end());
91  DEBUG_LOG << " Resolution: " << width << "x" << height;
92  DEBUG_LOG << " Physical Size: " << mmx << " mm x " << mmy << " mm";
93  DEBUG_LOG << " Scale factor:" << scaleFactor;
94  g_monitor_info.push_back(
95  {std::string(ws.begin(), ws.end()), static_cast<size_t>(mmx),
96  static_cast<size_t>(mmy), static_cast<size_t>(width), static_cast<size_t>(height),
97  static_cast<size_t>(width), static_cast<size_t>(height),
98  static_cast<size_t>(scaleFactor)});
99  } else {
100  DEBUG_LOG << "GetMonitorInfo failed";
101  }
102  return TRUE;
103 }
104 #endif
105 
106 // Function to enumerate monitors and retrieve screen size
107 void EnumerateMonitors() {
108  g_monitor_info.clear();
109 #if __linux__
110 #ifdef OCPN_HAVE_X11
111  Display* display = XOpenDisplay(nullptr);
112  if (!display) {
113  std::cerr << "Error opening X display." << std::endl;
114  return;
115  }
116 
117  int screen = DefaultScreen(display);
118  Window root = RootWindow(display, screen);
119 
120  XRRScreenResources* resources = XRRGetScreenResources(display, root);
121  if (!resources) {
122  ERROR_LOG << "Error getting screen resources.";
123  XCloseDisplay(display);
124  return;
125  }
126 
127  GdkDisplay* gdk_display = gdk_display_get_default();
128  int gdk_num_monitors = gdk_display_get_n_monitors(gdk_display);
129  DEBUG_LOG << "GDK Monitors: " << gdk_num_monitors;
130  size_t scale;
131  for (int i = 0; i < resources->noutput; ++i) {
132  XRROutputInfo* outputInfo =
133  XRRGetOutputInfo(display, resources, resources->outputs[i]);
134  XRRCrtcInfo* crtcInfo =
135  XRRGetCrtcInfo(display, resources, resources->crtcs[i]);
136  scale = 100;
137  if (i < gdk_num_monitors) {
138  GdkMonitor* monitor = gdk_display_get_monitor(gdk_display, i);
139  scale = gdk_monitor_get_scale_factor(monitor) * 100;
140  }
141  if (outputInfo && crtcInfo) {
142  // Physical size can be unknown and reported as zero (For example over VNC, assume a "standard" DPI of 96 in that case to guess it)
143  size_t mm_width = outputInfo->mm_width > 0 ? outputInfo->mm_width : crtcInfo->width * 25.4 / 96.0;
144  size_t mm_height = outputInfo->mm_height > 0 ? outputInfo->mm_height : crtcInfo->height * 25.4 / 96.0;
145  DEBUG_LOG << "Monitor " << i + 1 << ":";
146  DEBUG_LOG << " Name: " << outputInfo->name;
147  DEBUG_LOG << " Physical Size (mm): " << mm_width << " x "
148  << mm_height;
149  DEBUG_LOG << " Resolution: " << crtcInfo->width << " x "
150  << crtcInfo->height;
151  DEBUG_LOG << " Scale: " << scale;
152  g_monitor_info.push_back({outputInfo->name, mm_width,
153  mm_height, crtcInfo->width,
154  crtcInfo->height, crtcInfo->width,
155  crtcInfo->height, scale});
156  }
157  XRRFreeOutputInfo(outputInfo);
158  XRRFreeCrtcInfo(crtcInfo);
159  }
160 
161  XRRFreeScreenResources(resources);
162  XCloseDisplay(display);
163 #endif
164 #elif _WIN32
165  EnumDisplayMonitors(NULL, NULL, MonitorEnumProc, 0);
166  // Get the names of the monitors
167  std::vector<DISPLAYCONFIG_PATH_INFO> paths;
168  std::vector<DISPLAYCONFIG_MODE_INFO> modes;
169  UINT32 flags = QDC_ONLY_ACTIVE_PATHS;
170  LONG isError = ERROR_INSUFFICIENT_BUFFER;
171 
172  UINT32 pathCount, modeCount;
173  isError = GetDisplayConfigBufferSizes(flags, &pathCount, &modeCount);
174 
175  if (!isError) {
176  // Allocate the path and mode arrays
177  paths.resize(pathCount);
178  modes.resize(modeCount);
179 
180  // Get all active paths and their modes
181  isError = QueryDisplayConfig(flags, &pathCount, paths.data(), &modeCount,
182  modes.data(), nullptr);
183 
184  // The function may have returned fewer paths/modes than estimated
185  paths.resize(pathCount);
186  modes.resize(modeCount);
187 
188  if (!isError) {
189  // For each active path
190  for (int i = 0; i < paths.size(); i++) {
191  // Find the target (monitor) friendly name
192  DISPLAYCONFIG_TARGET_DEVICE_NAME targetName = {};
193  targetName.header.adapterId = paths[i].targetInfo.adapterId;
194  targetName.header.id = paths[i].targetInfo.id;
195  targetName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME;
196  targetName.header.size = sizeof(targetName);
197  isError = DisplayConfigGetDeviceInfo(&targetName.header);
198 
199  if (!isError) {
200  if (targetName.flags.friendlyNameFromEdid) {
201  std::wstring ws(targetName.monitorFriendlyDeviceName);
202  std::wstring ws1(targetName.monitorDevicePath);
203  DEBUG_LOG << "Monitor found: " << std::string(ws.begin(), ws.end())
204  << " - " << std::string(ws1.begin(), ws1.end());
205  if (i < g_monitor_info.size()) {
206  g_monitor_info[i].name = std::string(ws.begin(), ws.end());
207  }
208  }
209  }
210  }
211  }
212  }
213 #elif __APPLE__
214  CGDirectDisplayID displayArray[32];
215  uint32_t displayCount;
216  CGGetOnlineDisplayList(32, displayArray, &displayCount);
217 
218  for (uint32_t i = 0; i < displayCount; ++i) {
219  CGDirectDisplayID displayID = displayArray[i];
220  CGSize displayPhysicalSize = CGDisplayScreenSize(displayID);
221  int width = CGDisplayModeGetWidth(CGDisplayCopyDisplayMode(displayID));
222  int height = CGDisplayModeGetHeight(CGDisplayCopyDisplayMode(displayID));
223  int pixel_width = CGDisplayModeGetPixelWidth(CGDisplayCopyDisplayMode(displayID));
224  int pixel_height = CGDisplayModeGetPixelHeight(CGDisplayCopyDisplayMode(displayID));
225  DEBUG_LOG << "Display " << i + 1 << ":";
226  DEBUG_LOG << " Physical Size: " << displayPhysicalSize.width << "x"
227  << displayPhysicalSize.height << " mm";
228  DEBUG_LOG << " Resolution: " << width << "x" << height << " pixels";
229  DEBUG_LOG << " Pixel resolution: " << pixel_width << "x" << pixel_height << " pixels";
230  g_monitor_info.push_back(
231  {std::to_string(i + 1), static_cast<size_t>(displayPhysicalSize.width),
232  static_cast<size_t>(displayPhysicalSize.height),
233  static_cast<size_t>(width), static_cast<size_t>(height), static_cast<size_t>(pixel_width), static_cast<size_t>(pixel_height), 100});
234  }
235 #endif
236  if (g_monitor_info.size() == 0) {
237  // This should never happen, but just in case...
238  // If we didn't find any monitors at all, add some dummy default that makes at least some sense (15.6 inch full HD)
239  // We might also use wxDisplaySize and wxDisplaySizeMM here, but what the heck would they report?
240  g_monitor_info.push_back({"Dummy monitor", 340, 190, 1920, 1080, 1920, 1080, 100});
241  }
242  g_num_monitors = g_monitor_info.size();
243  DEBUG_LOG << "Number of monitors: " << g_num_monitors;
244  DEBUG_LOG << "Monitor info:";
245  for (const auto &monitor : g_monitor_info) {
246  DEBUG_LOG << "Monitor: " << monitor.name << " " << monitor.width_mm << "x" << monitor.height_mm << "mm " << monitor.width << "x" << monitor.height << "DIP " << monitor.width_px << "x" << monitor.height_px << "px " << monitor.scale << "%";
247  }
248 }
Definition: Quilt.cpp:867