From 94f52f9edf11bcd30df76b2da35f614c23a81c2f Mon Sep 17 00:00:00 2001 From: Hugo Lundin Date: Tue, 13 Jul 2021 15:55:54 +0200 Subject: [PATCH] Add monitor description to tooltip It might be useful to have more information about a monitor when configuring its settings in the Options menu. Therefore, this commit adds support for showing additional information about a monitor (resolution and platform-specific name). --- vncviewer/CMakeLists.txt | 5 + vncviewer/MonitorArrangement.cxx | 164 +++++++++++++++++++++++++++++++ vncviewer/MonitorArrangement.h | 4 + vncviewer/win32.c | 52 ++++++++++ vncviewer/win32.h | 1 + 5 files changed, 226 insertions(+) diff --git a/vncviewer/CMakeLists.txt b/vncviewer/CMakeLists.txt index 9d11ba79..9aa985af 100644 --- a/vncviewer/CMakeLists.txt +++ b/vncviewer/CMakeLists.txt @@ -63,6 +63,11 @@ elseif(APPLE) target_link_libraries(vncviewer "-framework IOKit") else() target_link_libraries(vncviewer ${X11_Xi_LIB}) + + if(X11_Xrandr_LIB) + add_definitions(-DHAVE_XRANDR) + target_link_libraries(vncviewer ${X11_Xrandr_LIB}) + endif() endif() install(TARGETS vncviewer DESTINATION ${CMAKE_INSTALL_FULL_BINDIR}) diff --git a/vncviewer/MonitorArrangement.cxx b/vncviewer/MonitorArrangement.cxx index e5ff3652..83a53d38 100644 --- a/vncviewer/MonitorArrangement.cxx +++ b/vncviewer/MonitorArrangement.cxx @@ -28,10 +28,31 @@ #include #include +#if defined(HAVE_XRANDR) && !defined(__APPLE__) +#include +#include +#include +#include +#endif + +#ifdef __APPLE__ +#include +#include +#include +#include +#endif + #include +#include + +#ifdef WIN32 +#include "win32.h" +#endif +#include "i18n.h" #include "MonitorArrangement.h" +static rfb::LogWriter vlog("MonitorArrangement"); static const Fl_Boxtype FL_CHECKERED_BOX = FL_FREE_BOXTYPE; MonitorArrangement::MonitorArrangement( @@ -116,6 +137,9 @@ void MonitorArrangement::layout() monitor->when(FL_WHEN_CHANGED); m_monitors.push_back(monitor); } + + for (int i = 0; i < (int) m_monitors.size(); i++) + m_monitors[i]->copy_tooltip(description(i).c_str()); } bool MonitorArrangement::is_required(int m) @@ -255,6 +279,146 @@ std::pair MonitorArrangement::origin() return std::make_pair(ox, oy); } +std::string MonitorArrangement::description(int m) +{ + assert(m < (int) m_monitors.size()); + const size_t name_len = 1024; + char name[name_len] = {}; + int bytes_written = get_monitor_name(m, name, name_len); + + int x, y, w, h; + Fl::screen_xywh(x, y, w, h, m); + std::stringstream ss; + + if (bytes_written > 0) + ss << name << " (" << w << "x" << h << ")"; + else + ss << w << "x" << h; + + return ss.str(); +} + +int MonitorArrangement::get_monitor_name(int m, char name[], size_t name_len) +{ +#if defined(WIN32) + int x, y, w, h; + Fl::screen_xywh(x, y, w, h, m); + return win32_get_monitor_name(x, y, w, h, name, name_len); + +#elif defined(__APPLE__) + CGDisplayCount count; + int bytes_written = 0; + CGDirectDisplayID displays[16]; + + if (CGGetActiveDisplayList(16, displays, &count) != kCGErrorSuccess) + return -1; + + if (count != (unsigned)Fl::screen_count()) + return -1; + + if (m >= (int)count) + return -1; + + // Notice: Here we assume indices to be ordered the same as in FLTK (we rely on that in cocoa.mm as well). + CGDirectDisplayID displayID = displays[m]; + + CFDictionaryRef info = IODisplayCreateInfoDictionary( + /* display = */ CGDisplayIOServicePort(displayID), + /* options = */ kIODisplayOnlyPreferredName); + + CFDictionaryRef dict = (CFDictionaryRef) CFDictionaryGetValue(info, CFSTR(kDisplayProductName)); + CFIndex dict_len = CFDictionaryGetCount(dict); + + if (dict_len > 0) { + CFTypeRef * names = new CFTypeRef[dict_len]; + CFDictionaryGetKeysAndValues(dict, NULL, (const void **) names); + + if (names[0]) { + + // Because of `kIODisplayOnlyPreferredName` names *should* only contain the name with + // the current system localization. + CFStringRef localized_name = (CFStringRef) names[0]; + CFIndex localized_name_len = CFStringGetLength(localized_name); + + // Even though we already have the length of `localized_name` above, we know that we will format it + // as UTF-8 when we put it in the destination buffer. Therefore we need to check whether the name + // with that encoding will fit. + CFIndex localized_name_max_size = CFStringGetMaximumSizeForEncoding(localized_name_len, kCFStringEncodingUTF8) + 1; + + if (name_len > (size_t)localized_name_max_size) { + if (CFStringGetCString( + /* ref = */ localized_name, + /* dest = */ name, + /* dest_len = */ name_len, + /* encoding = */ kCFStringEncodingUTF8)) + { + bytes_written = strlen(name); + } + } + } + + delete[] names; + } + + CFRelease(info); + return bytes_written; + +#else +#if defined (HAVE_XRANDR) + int x, y, w, h; + int ev, err, xi_major; + + fl_open_display(); + assert(fl_display != NULL); + Fl::screen_xywh(x, y, w, h, m); + + if (!XQueryExtension(fl_display, "RANDR", &xi_major, &ev, &err)) { + vlog.info(_("Failed to get monitor name because X11 RandR could not be found.")); + return -1; + } + + XRRScreenResources *res = XRRGetScreenResources(fl_display, DefaultRootWindow(fl_display)); + if (!res) { + vlog.error(_("Failed to get XRRScreenResources for root window.")); + return -1; + } + + for (int i = 0; i < res->ncrtc; i++) { + XRRCrtcInfo *crtc = XRRGetCrtcInfo(fl_display, res, res->crtcs[i]); + + if (!crtc) { + vlog.error(_("Failed to get XRRCrtcInfo for crtc %d"), i); + continue; + } + + for (int j = 0; j < crtc->noutput; j++) { + bool monitor_found = (crtc->x == x) && + (crtc->y == y) && + (crtc->width == ((unsigned int) w)) && + (crtc->height == ((unsigned int) h)); + + if (monitor_found) { + XRROutputInfo *output = XRRGetOutputInfo(fl_display, res, crtc->outputs[j]); + if (!output) { + vlog.error(_("Failed to get XRROutputInfo for crtc %d, output %d."), i, j); + continue; + } + + if (strlen(output->name) >= name_len) + return -1; + + return snprintf(name, name_len, "%.*s", (int)name_len, output->name); + } + } + } + + return -1; + +#endif // !HAVE_XRANDR + return 0; +#endif +} + void MonitorArrangement::monitor_pressed(Fl_Widget *widget, void *user_data) { MonitorArrangement *self = (MonitorArrangement *) user_data; diff --git a/vncviewer/MonitorArrangement.h b/vncviewer/MonitorArrangement.h index c29b6ff6..ca1c4896 100644 --- a/vncviewer/MonitorArrangement.h +++ b/vncviewer/MonitorArrangement.h @@ -64,6 +64,10 @@ private: // Return the origin of the monitor arrangement (top left corner). std::pair origin(); + // Get a textual description of the given monitor. + std::string description(int m); + int get_monitor_name(int m, char name[], size_t name_len); + static void monitor_pressed(Fl_Widget *widget, void *user_data); static void checkered_pattern_draw( int x, int y, int width, int height, Fl_Color color); diff --git a/vncviewer/win32.c b/vncviewer/win32.c index d116ecc3..dc8cb602 100644 --- a/vncviewer/win32.c +++ b/vncviewer/win32.c @@ -18,6 +18,7 @@ */ #include +#include #define XK_MISCELLANY #define XK_XKB_KEYS @@ -418,3 +419,54 @@ int win32_has_altgr(void) return has_altgr; } + +typedef struct { + int x, y, w, h; + char* name; + size_t name_len; + int bytes_written; +} EnumCallbackData; + +static BOOL CALLBACK EnumDisplayMonitorsCallback( + HMONITOR monitor, HDC deviceContext, LPRECT rect, LPARAM userData) +{ + EnumCallbackData *data = (EnumCallbackData *)userData; + MONITORINFOEX info; + info.cbSize = sizeof(info); + GetMonitorInfo(monitor, (LPMONITORINFO)&info); + + int x = info.rcMonitor.left; + int y = info.rcMonitor.top; + int w = info.rcMonitor.right - info.rcMonitor.left; + int h = info.rcMonitor.bottom - info.rcMonitor.top; + + if ((data->x == x) && (data->y == y) && (data->w == w) && (data->h == h)) { + data->bytes_written = snprintf(data->name, data->name_len, + "%.*s", (int)(data->name_len - 1), info.szDevice); + + if (data->bytes_written < 0) + return FALSE; + + // Stop the iteration. + return FALSE; + } + + // Keep iterating. + return TRUE; +} + +int win32_get_monitor_name(int x, int y, int w, int h, char name[], size_t name_len) +{ + EnumCallbackData data = { + .x = x, + .y = y, + .w = w, + .h = h, + .name = name, + .name_len = name_len, + .bytes_written = -1 + }; + + EnumDisplayMonitors(NULL, NULL, EnumDisplayMonitorsCallback, (LPARAM) &data); + return data.bytes_written; +} diff --git a/vncviewer/win32.h b/vncviewer/win32.h index 9d4704bc..2dae04b9 100644 --- a/vncviewer/win32.h +++ b/vncviewer/win32.h @@ -34,6 +34,7 @@ int win32_vkey_to_keysym(UINT vkey, int extended); int win32_has_altgr(void); +int win32_get_monitor_name(int x, int y, int w, int h, char name[], size_t name_len); }; #endif -- 2.39.5