Pierre Ossman 2 роки тому
джерело
коміт
51dfeecd8e

+ 7
- 0
vncviewer/CMakeLists.txt Переглянути файл

@@ -17,6 +17,8 @@ set(VNCVIEWER_SOURCES
parameters.cxx
keysym2ucs.c
touch.cxx
MonitorArrangement.cxx
MonitorIndicesParameter.cxx
vncviewer.cxx)

if(WIN32)
@@ -61,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})

+ 77
- 17
vncviewer/DesktopWindow.cxx Переглянути файл

@@ -465,9 +465,34 @@ void DesktopWindow::draw()

// Make sure it's properly seen by adjusting it relative to the
// primary screen rather than the entire window
if (fullscreen_active() && fullScreenAllMonitors) {
if (fullscreen_active()) {
assert(Fl::screen_count() >= 1);
Fl::screen_xywh(sx, sy, sw, sh, 0);

rfb::Rect window_rect, screen_rect;
window_rect.setXYWH(x(), y(), w(), h());

bool found_enclosed_screen = false;
for (int i = 0; i < Fl::screen_count(); i++) {
Fl::screen_xywh(sx, sy, sw, sh, i);

// The screen with the smallest index that are enclosed by
// the viewport will be used for showing the overlay.
screen_rect.setXYWH(sx, sy, sw, sh);
if (screen_rect.enclosed_by(window_rect)) {
found_enclosed_screen = true;
break;
}
}

// If no monitor inside the viewport was found,
// use the one primary instead.
if (!found_enclosed_screen)
Fl::screen_xywh(sx, sy, sw, sh, 0);

// Adjust the coordinates so they are relative to the viewport.
sx -= x();
sy -= y();

} else {
sx = 0;
sy = 0;
@@ -608,6 +633,11 @@ void DesktopWindow::resize(int x, int y, int w, int h)

repositionWidgets();
}

// Some systems require a grab after the window size has been changed.
// Otherwise they might hold on to displays, resulting in them being unusable.
if (fullscreen_active() && fullscreenSystemKeys)
grabKeyboard();
}


@@ -850,41 +880,71 @@ int DesktopWindow::fltkHandle(int event, Fl_Window *win)
return ret;
}


void DesktopWindow::fullscreen_on()
{
if (not fullScreenAllMonitors)
fullscreen_screens(-1, -1, -1, -1);
else {
bool all_monitors = !strcasecmp(fullScreenMode, "all");
bool selected_monitors = !strcasecmp(fullScreenMode, "selected");

if (not selected_monitors and not all_monitors) {
int n = Fl::screen_num(x(), y(), w(), h());
fullscreen_screens(n, n, n, n);
} else {
int top, bottom, left, right;
int top_y, bottom_y, left_x, right_x;

int sx, sy, sw, sh;

top = bottom = left = right = 0;
std::set<int> monitors;

if (selected_monitors and not all_monitors) {
std::set<int> selected = fullScreenSelectedMonitors.getParam();
monitors.insert(selected.begin(), selected.end());
} else {
for (int i = 0; i < Fl::screen_count(); i++) {
monitors.insert(i);
}
}

// If no monitors were found in the selected monitors case, we want
// to explicitly use the window's current monitor.
if (monitors.size() == 0) {
monitors.insert(Fl::screen_num(x(), y(), w(), h()));
}

// If there are monitors selected, calculate the dimensions
// of the frame buffer, expressed in the monitor indices that
// limits it.
std::set<int>::iterator it = monitors.begin();

Fl::screen_xywh(sx, sy, sw, sh, 0);
// Get first monitor dimensions.
Fl::screen_xywh(sx, sy, sw, sh, *it);
top = bottom = left = right = *it;
top_y = sy;
bottom_y = sy + sh;
left_x = sx;
right_x = sx + sw;

for (int i = 1;i < Fl::screen_count();i++) {
Fl::screen_xywh(sx, sy, sw, sh, i);
// Keep going through the rest of the monitors.
for (; it != monitors.end(); it++) {
Fl::screen_xywh(sx, sy, sw, sh, *it);

if (sy < top_y) {
top = i;
top = *it;
top_y = sy;
}

if ((sy + sh) > bottom_y) {
bottom = i;
bottom = *it;
bottom_y = sy + sh;
}

if (sx < left_x) {
left = i;
left = *it;
left_x = sx;
}

if ((sx + sw) > right_x) {
right = i;
right = *it;
right_x = sx + sw;
}
}
@@ -932,7 +992,7 @@ void DesktopWindow::grabKeyboard()
#elif defined(__APPLE__)
int ret;
ret = cocoa_capture_display(this, fullScreenAllMonitors);
ret = cocoa_capture_displays(this);
if (ret != 0) {
vlog.error(_("Failure grabbing keyboard"));
return;
@@ -988,7 +1048,7 @@ void DesktopWindow::ungrabKeyboard()
#if defined(WIN32)
win32_disable_lowlevel_keyboard(fl_xid(this));
#elif defined(__APPLE__)
cocoa_release_display(this);
cocoa_release_displays(this);
#else
// FLTK has a grab so lets not mess with it
if (Fl::grab())
@@ -1347,7 +1407,7 @@ void DesktopWindow::handleOptions(void *data)
self->ungrabKeyboard();

// Call fullscreen_on even if active since it handles
// fullScreenAllMonitors
// fullScreenMode
if (fullScreen)
self->fullscreen_on();
else if (!fullScreen && self->fullscreen_active())

+ 467
- 0
vncviewer/MonitorArrangement.cxx Переглянути файл

@@ -0,0 +1,467 @@
/* Copyright 2021 Hugo Lundin <huglu@cendio.se> for Cendio AB.
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/

#include <set>
#include <vector>
#include <string>
#include <utility>
#include <sstream>
#include <assert.h>
#include <algorithm>

#include <FL/Fl.H>
#include <FL/fl_draw.H>
#include <FL/Fl_Button.H>

#if defined(HAVE_XRANDR) && !defined(__APPLE__)
#include <X11/extensions/Xrandr.h>
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#endif

#ifdef __APPLE__
#include <Carbon/Carbon.h>
#include <IOKit/graphics/IOGraphicsLib.h>
#include <IOKit/hidsystem/IOHIDLib.h>
#include <IOKit/hidsystem/IOHIDParameter.h>
#endif

#include <rfb/Rect.h>
#include <rfb/LogWriter.h>

#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(
int x, int y, int w, int h)
: Fl_Group(x, y, w, h),
SELECTION_COLOR(fl_lighter(FL_BLUE)),
AVAILABLE_COLOR(fl_lighter(fl_lighter(fl_lighter(FL_BACKGROUND_COLOR)))),
m_monitors()
{
// Used for required monitors.
Fl::set_boxtype(FL_CHECKERED_BOX, checkered_pattern_draw, 0, 0, 0, 0);

box(FL_DOWN_BOX);
color(fl_lighter(FL_BACKGROUND_COLOR));
layout();
end();
}

MonitorArrangement::~MonitorArrangement()
{

}

std::set<int> MonitorArrangement::get()
{
std::set<int> indices;

for (int i = 0; i < (int) m_monitors.size(); i++) {
if (m_monitors[i]->value() == 1)
indices.insert(i);
}

return indices;
}

void MonitorArrangement::set(std::set<int> indices)
{
for (int i = 0; i < (int) m_monitors.size(); i++) {
bool selected = std::find(indices.begin(), indices.end(), i) != indices.end();
m_monitors[i]->value(selected ? 1 : 0);
}
}

void MonitorArrangement::draw()
{
for (int i = 0; i < (int) m_monitors.size(); i++) {
Fl_Button * monitor = m_monitors[i];

if (is_required(i)) {
monitor->box(FL_CHECKERED_BOX);
monitor->color(SELECTION_COLOR);
} else {
monitor->box(FL_BORDER_BOX);
monitor->color(AVAILABLE_COLOR);
monitor->selection_color(SELECTION_COLOR);
}
}

Fl_Group::draw();
}

void MonitorArrangement::layout()
{
int x, y, w, h;
double scale = this->scale();
const double MARGIN_SCALE_FACTOR = 0.99;
std::pair<int, int> offset = this->offset();

for (int i = 0; i < Fl::screen_count(); i++) {
Fl::screen_xywh(x, y, w, h, i);

Fl_Button *monitor = new Fl_Button(
/* x = */ this->x() + offset.first + x*scale + (1 - MARGIN_SCALE_FACTOR)*x*scale,
/* y = */ this->y() + offset.second + y*scale + (1 - MARGIN_SCALE_FACTOR)*y*scale,
/* w = */ w*scale*MARGIN_SCALE_FACTOR,
/* h = */ h*scale*MARGIN_SCALE_FACTOR
);

monitor->clear_visible_focus();
monitor->callback(monitor_pressed, this);
monitor->type(FL_TOGGLE_BUTTON);
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)
{
// A selected monitor is never required.
if (m_monitors[m]->value() == 1)
return false;

// If no monitors are selected, none are required.
std::set<int> selected = get();
if (selected.size() <= 0)
return false;


// Go through all selected monitors and find the monitor
// indices that bounds the fullscreen frame buffer. If
// the given monitor's coordinates are inside the bounds,
// while not being selected, it is instead required.

int x, y, w, h;
int top_y, bottom_y, left_x, right_x;
std::set<int>::iterator it = selected.begin();

// Base the rest of the calculations on the dimensions
// obtained for the first monitor.
Fl::screen_xywh(x, y, w, h, *it);
top_y = y;
bottom_y = y + h;
left_x = x;
right_x = x + w;

// Go through the rest of the monitors,
// exhausting the rest of the iterator.
for (; it != selected.end(); it++) {
Fl::screen_xywh(x, y, w, h, *it);

if (y < top_y) {
top_y = y;
}

if ((y + h) > bottom_y) {
bottom_y = y + h;
}

if (x < left_x) {
left_x = x;
}

if ((x + w) > right_x) {
right_x = x + w;
}
}

rfb::Rect viewport, monitor;
viewport.setXYWH(left_x, top_y, right_x - left_x, bottom_y - top_y);

Fl::screen_xywh(x, y, w, h, m);
monitor.setXYWH(x, y, w, h);

return monitor.enclosed_by(viewport);
}

double MonitorArrangement::scale()
{
const int MARGIN = 20;
std::pair<int, int> size = this->size();

double s_w = static_cast<double>(this->w()-MARGIN) / static_cast<double>(size.first);
double s_h = static_cast<double>(this->h()-MARGIN) / static_cast<double>(size.second);

// Choose the one that scales the least, in order to
// maximize our use of the given bounding area.
if (s_w > s_h)
return s_h;
else
return s_w;
}

std::pair<int, int> MonitorArrangement::size()
{
int x, y, w, h;
int top, bottom, left, right;
int x_min, x_max, y_min, y_max;
x_min = x_max = y_min = y_max = 0;

for (int i = 0; i < Fl::screen_count(); i++) {
Fl::screen_xywh(x, y, w, h, i);

top = y;
bottom = y + h;
left = x;
right = x + w;

if (top < y_min)
y_min = top;

if (bottom > y_max)
y_max = bottom;

if (left < x_min)
x_min = left;

if (right > x_max)
x_max = right;
}

return std::make_pair(x_max - x_min, y_max - y_min);
}

std::pair<int, int> MonitorArrangement::offset()
{
double scale = this->scale();
std::pair<int, int> size = this->size();
std::pair<int, int> origin = this->origin();

int offset_x = (this->w()/2) - (size.first/2 * scale);
int offset_y = (this->h()/2) - (size.second/2 * scale);

return std::make_pair(offset_x + abs(origin.first)*scale, offset_y + abs(origin.second)*scale);
}

std::pair<int, int> MonitorArrangement::origin()
{
int x, y, w, h, ox, oy;
ox = oy = 0;

for (int i = 0; i < Fl::screen_count(); i++) {
Fl::screen_xywh(x, y, w, h, i);

if (x < ox)
ox = x;

if (y < oy)
oy = y;
}

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;

// When a monitor is selected, FLTK changes the state of it for us.
// However, selecting a monitor might implicitly change the state of
// others (if they become required). FLTK only redraws the selected
// monitor. Therefore, we must trigger a redraw of the whole widget
// manually.
self->redraw();
}

void MonitorArrangement::checkered_pattern_draw(
int x, int y, int width, int height, Fl_Color color)
{
bool draw_checker = false;
const int CHECKER_SIZE = 8;

fl_color(fl_lighter(fl_lighter(fl_lighter(color))));
fl_rectf(x, y, width, height);

fl_color(Fl::draw_box_active() ? color : fl_inactive(color));

// Round up the square count. Later on, we remove square area that are
// outside the given bounding area.
const int count = (width + CHECKER_SIZE - 1) / CHECKER_SIZE;

for (int i = 0; i < count; i++) {
for (int j = 0; j < count; j++) {

draw_checker = (i + j) % 2 == 0;

if (draw_checker) {
fl_rectf(
/* x = */ x + i * CHECKER_SIZE,
/* y = */ y + j * CHECKER_SIZE,
/* w = */ CHECKER_SIZE - std::max(0, ((i + 1) * CHECKER_SIZE) - width),
/* h = */ CHECKER_SIZE - std::max(0, ((j + 1) * CHECKER_SIZE) - height)
);
}
}
}

fl_color(Fl::draw_box_active() ? FL_BLACK : fl_inactive(FL_BLACK));
fl_rect(x, y, width, height);
}

+ 76
- 0
vncviewer/MonitorArrangement.h Переглянути файл

@@ -0,0 +1,76 @@
/* Copyright 2021 Hugo Lundin <huglu@cendio.se> for Cendio AB.
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/

#ifndef __MONITOR_ARRANGEMENT_H__
#define __MONITOR_ARRANGEMENT_H__

#include <string>

class Fl_Group;
class Fl_Button;

class MonitorArrangement: public Fl_Group {
public:
MonitorArrangement(int x, int y, int w, int h);
~MonitorArrangement();

// Get selected indices.
std::set<int> get();

// Set selected indices.
void set(std::set<int> indices);

protected:
virtual void draw();

private:
const Fl_Color SELECTION_COLOR;
const Fl_Color AVAILABLE_COLOR;
std::vector<Fl_Button *> m_monitors;

// Layout the monitor arrangement.
void layout();

// Return true if the given monitor is required to be part of the configuration
// for it to be valid. A configuration is only valid if the framebuffer created
// from is rectangular.
bool is_required(int m);

// Calculate the scale of the monitor arrangement.
double scale();

// Return the size of the monitor arrangement.
std::pair<int, int> size();

// Return the offset required for centering the monitor
// arrangement in the given bounding area.
std::pair<int, int> offset();

// Return the origin of the monitor arrangement (top left corner).
std::pair<int, int> 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);
};

#endif

+ 229
- 0
vncviewer/MonitorIndicesParameter.cxx Переглянути файл

@@ -0,0 +1,229 @@
/* Copyright 2021 Hugo Lundin <huglu@cendio.se> for Cendio AB.
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/

#include <algorithm>
#include <vector>
#include <string>
#include <limits>
#include <set>
#include <stdlib.h>
#include <stdexcept>

#include "i18n.h"
#include <FL/Fl.H>
#include <rfb/LogWriter.h>

#include "MonitorIndicesParameter.h"

using namespace rfb;
static LogWriter vlog("MonitorIndicesParameter");

MonitorIndicesParameter::MonitorIndicesParameter(const char* name_, const char* desc_, const char* v)
: StringParameter(name_, desc_, v) {}

std::set<int> MonitorIndicesParameter::getParam()
{
bool valid = false;
std::set<int> indices;
std::set<int> config_indices;
std::vector<MonitorIndicesParameter::Monitor> monitors = this->monitors();

if (monitors.size() <= 0) {
vlog.error(_("Failed to get monitors."));
return indices;
}

valid = parse_indices(value, &config_indices);
if (!valid) {
return indices;
}

if (config_indices.size() <= 0) {
return indices;
}

// Go through the monitors and see what indices are present in the config.
for (int i = 0; i < ((int) monitors.size()); i++) {
if (std::find(config_indices.begin(), config_indices.end(), i) != config_indices.end())
indices.insert(monitors[i].fltk_index);
}

return indices;
}

bool MonitorIndicesParameter::setParam(const char* value)
{
int index;
std::set<int> indices;

if (strlen(value) < 0)
return false;

if (!parse_indices(value, &indices)) {
vlog.error(_("Invalid FullScreenSelectedMonitors configuration."));
return false;
}

for (std::set<int>::iterator it = indices.begin(); it != indices.end(); it++) {
index = *it + 1;

if (index <= 0 || index > Fl::screen_count())
vlog.error(_("Monitor index %d does not exist."), index);
}

return StringParameter::setParam(value);
}

bool MonitorIndicesParameter::setParam(std::set<int> indices)
{
static const int BUF_MAX_LEN = 1024;
char buf[BUF_MAX_LEN] = {0};
std::set<int> config_indices;
std::vector<MonitorIndicesParameter::Monitor> monitors = this->monitors();

if (monitors.size() <= 0) {
vlog.error(_("Failed to get monitors."));
// Don't return, store the configuration anyways.
}

for (int i = 0; i < ((int) monitors.size()); i++) {
if (std::find(indices.begin(), indices.end(), monitors[i].fltk_index) != indices.end())
config_indices.insert(i);
}

int bytes_written = 0;
char const * separator = "";

for (std::set<int>::iterator index = config_indices.begin();
index != config_indices.end();
index++)
{
bytes_written += snprintf(
buf+bytes_written,
BUF_MAX_LEN-bytes_written,
"%s%u",
separator,
(*index)+1
);

separator = ",";
}

return setParam(buf);
}

static bool parse_number(std::string number, std::set<int> *indices)
{
if (number.size() <= 0)
return false;

int v = strtol(number.c_str(), NULL, 0);

if (v <= 0) {
vlog.error(_("The given monitor index(%s) is too small to be valid."), number.c_str());
return false;
}

if (v > INT_MAX) {
vlog.error(_("The given monitor index (%s) is too large to be valid."), number.c_str());
return false;
}

indices->insert(v-1);
return true;
}

bool MonitorIndicesParameter::parse_indices(const char* value, std::set<int> *indices)
{
char d;
std::string current;

for (size_t i = 0; i < strlen(value); i++) {
d = value[i];

if (d == ' ')
continue;
else if (d >= '0' && d <= '9')
current.push_back(d);
else if (d == ',') {
if (!parse_number(current, indices)) {
vlog.error(_("Invalid monitor index '%s' in FullScreenSelectedMonitors"), current.c_str());
return false;
}

current.clear();
} else {
vlog.error(_("Unexpected character '%c' in FullScreenSelectedMonitors"), d);
return false;
}
}

// If we have nothing left to parse we are in a valid state.
if (current.size() == 0)
return true;

// Parsing anything we have left.
if (!parse_number(current, indices))
return false;

return true;
}

std::vector<MonitorIndicesParameter::Monitor> MonitorIndicesParameter::monitors()
{
std::vector<Monitor> monitors;

// Start by creating a struct for every monitor.
for (int i = 0; i < Fl::screen_count(); i++) {
Monitor monitor = {0};

// Get the properties of the monitor at the current index;
Fl::screen_xywh(
monitor.x,
monitor.y,
monitor.w,
monitor.h,
i
);

monitor.fltk_index = i;
monitors.push_back(monitor);
}

// Sort the monitors according to the specification in the vncviewer manual.
qsort(&monitors[0], monitors.size(), sizeof(*(&monitors[0])), sort_cb);
return monitors;
}

int MonitorIndicesParameter::sort_cb(const void *a, const void *b)
{
MonitorIndicesParameter::Monitor * monitor1 = (MonitorIndicesParameter::Monitor *) a;
MonitorIndicesParameter::Monitor * monitor2 = (MonitorIndicesParameter::Monitor *) b;

if (monitor1->x < monitor2->x)
return -1;

if (monitor1->y < monitor2->y)
return -1;

if (monitor1->x == monitor2->x)
if (monitor1->y == monitor2->y)
return 0;

return 1;
}

+ 44
- 0
vncviewer/MonitorIndicesParameter.h Переглянути файл

@@ -0,0 +1,44 @@
/* Copyright 2021 Hugo Lundin <huglu@cendio.se> for Cendio AB.
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/

#ifndef __MONITOR_INDEX_PARAMETER_H
#define __MONITOR_INDEX_PARAMETER_H

#include <set>
#include <vector>

#include <rfb/Configuration.h>

class MonitorIndicesParameter: public rfb::StringParameter {
public:
MonitorIndicesParameter(const char* name_, const char* desc_, const char* v);
std::set<int> getParam();
bool setParam(std::set<int> indices);
bool setParam(const char* value);
private:
typedef struct {
int x, y, w, h;
int fltk_index;
} Monitor;

bool parse_indices(const char* value, std::set<int> *indices);
std::vector<MonitorIndicesParameter::Monitor> monitors();
static int sort_cb(const void*, const void*);
};

#endif // __MONITOR_INDEX_PARAMETER_H

+ 84
- 9
vncviewer/OptionsDialog.cxx Переглянути файл

@@ -38,6 +38,7 @@
#include "i18n.h"
#include "menukey.h"
#include "parameters.h"
#include "MonitorArrangement.h"

#include <FL/Fl_Tabs.H>
#include <FL/Fl_Button.H>
@@ -297,9 +298,19 @@ void OptionsDialog::loadOptions(void)
}
remoteResizeCheckbox->value(remoteResize);
fullScreenCheckbox->value(fullScreen);
fullScreenAllMonitorsCheckbox->value(fullScreenAllMonitors);

if (!strcasecmp(fullScreenMode, "all")) {
allMonitorsButton->setonly();
} else if (!strcasecmp(fullScreenMode, "selected")) {
selectedMonitorsButton->setonly();
} else {
currentMonitorButton->setonly();
}

monitorArrangement->set(fullScreenSelectedMonitors.getParam());

handleDesktopSize(desktopSizeCheckbox, this);
handleFullScreenMode(selectedMonitorsButton, this);

/* Misc. */
sharedCheckbox->value(shared);
@@ -407,7 +418,16 @@ void OptionsDialog::storeOptions(void)
}
remoteResize.setParam(remoteResizeCheckbox->value());
fullScreen.setParam(fullScreenCheckbox->value());
fullScreenAllMonitors.setParam(fullScreenAllMonitorsCheckbox->value());

if (allMonitorsButton->value()) {
fullScreenMode.setParam("All");
} else if (selectedMonitorsButton->value()) {
fullScreenMode.setParam("Selected");
} else {
fullScreenMode.setParam("Current");
}

fullScreenSelectedMonitors.setParam(monitorArrangement->get());

/* Misc. */
shared.setParam(sharedCheckbox->value());
@@ -755,6 +775,7 @@ void OptionsDialog::createInputPage(int tx, int ty, int tw, int th)
void OptionsDialog::createScreenPage(int tx, int ty, int tw, int th)
{
int x;
int width, height;

Fl_Group *group = new Fl_Group(tx, ty, tw, th, _("Screen"));

@@ -783,15 +804,58 @@ void OptionsDialog::createScreenPage(int tx, int ty, int tw, int th)
fullScreenCheckbox = new Fl_Check_Button(LBLRIGHT(tx, ty,
CHECK_MIN_WIDTH,
CHECK_HEIGHT,
_("Full-screen mode")));
ty += CHECK_HEIGHT + TIGHT_MARGIN;
_("Enable full-screen")));
ty += CHECK_HEIGHT + INNER_MARGIN;

fullScreenAllMonitorsCheckbox = new Fl_Check_Button(LBLRIGHT(tx + INDENT, ty,
CHECK_MIN_WIDTH,
CHECK_HEIGHT,
_("Enable full-screen mode over all monitors")));
ty += CHECK_HEIGHT + TIGHT_MARGIN;
width = tw - OUTER_MARGIN * 2;
height = th - ty + OUTER_MARGIN * 3;
Fl_Group *fullScreenModeGroup = new Fl_Group(tx,
ty,
width,
height);

{
tx += INDENT;

currentMonitorButton = new Fl_Round_Button(LBLRIGHT(tx, ty,
RADIO_MIN_WIDTH,
RADIO_HEIGHT,
_("Use current monitor")));
currentMonitorButton->type(FL_RADIO_BUTTON);
currentMonitorButton->callback(handleFullScreenMode, this);
ty += RADIO_HEIGHT + TIGHT_MARGIN;

allMonitorsButton = new Fl_Round_Button(LBLRIGHT(tx, ty,
RADIO_MIN_WIDTH,
RADIO_HEIGHT,
_("Use all monitors")));
allMonitorsButton->type(FL_RADIO_BUTTON);
allMonitorsButton->callback(handleFullScreenMode, this);
ty += RADIO_HEIGHT + TIGHT_MARGIN;

selectedMonitorsButton = new Fl_Round_Button(LBLRIGHT(tx, ty,
RADIO_MIN_WIDTH,
RADIO_HEIGHT,
_("Use selected monitor(s)")));
selectedMonitorsButton->type(FL_RADIO_BUTTON);
selectedMonitorsButton->callback(handleFullScreenMode, this);
ty += RADIO_HEIGHT + TIGHT_MARGIN;

int full_width = tw - OUTER_MARGIN * 2;
int margin_width = full_width - INDENT - INNER_MARGIN*2;
int full_height = th;
int margin_height = full_height - ty + INNER_MARGIN*3;

monitorArrangement = new MonitorArrangement(
tx + INDENT,
ty,
margin_width,
margin_height);

ty += CHECK_HEIGHT + margin_height;
}

fullScreenModeGroup->end();
group->end();
}

@@ -902,6 +966,17 @@ void OptionsDialog::handleClipboard(Fl_Widget *widget, void *data)
#endif
}

void OptionsDialog::handleFullScreenMode(Fl_Widget *widget, void *data)
{
OptionsDialog *dialog = (OptionsDialog*)data;

if (dialog->selectedMonitorsButton->value()) {
dialog->monitorArrangement->activate();
} else {
dialog->monitorArrangement->deactivate();
}
}

void OptionsDialog::handleCancel(Fl_Widget *widget, void *data)
{
OptionsDialog *dialog = (OptionsDialog*)data;

+ 8
- 1
vncviewer/OptionsDialog.h Переглянути файл

@@ -30,6 +30,7 @@ class Fl_Round_Button;
class Fl_Input;
class Fl_Int_Input;
class Fl_Choice;
class MonitorArrangement;

typedef void (OptionsCallback)(void*);

@@ -66,6 +67,8 @@ protected:

static void handleClipboard(Fl_Widget *widget, void *data);

static void handleFullScreenMode(Fl_Widget *widget, void *data);

static void handleCancel(Fl_Widget *widget, void *data);
static void handleOK(Fl_Widget *widget, void *data);

@@ -125,7 +128,11 @@ protected:
Fl_Int_Input *desktopHeightInput;
Fl_Check_Button *remoteResizeCheckbox;
Fl_Check_Button *fullScreenCheckbox;
Fl_Check_Button *fullScreenAllMonitorsCheckbox;

Fl_Round_Button *currentMonitorButton;
Fl_Round_Button *allMonitorsButton;
Fl_Round_Button *selectedMonitorsButton;
MonitorArrangement *monitorArrangement;

/* Misc. */
Fl_Check_Button *sharedCheckbox;

+ 4
- 2
vncviewer/cocoa.h Переглянути файл

@@ -19,8 +19,10 @@
#ifndef __VNCVIEWER_COCOA_H__
#define __VNCVIEWER_COCOA_H__

int cocoa_capture_display(Fl_Window *win, bool all_displays);
void cocoa_release_display(Fl_Window *win);
class Fl_Window;

int cocoa_capture_displays(Fl_Window *win);
void cocoa_release_displays(Fl_Window *win);

typedef struct CGColorSpace *CGColorSpaceRef;


+ 27
- 18
vncviewer/cocoa.mm Переглянути файл

@@ -35,6 +35,7 @@
#define XK_XKB_KEYS
#include <rfb/keysymdef.h>
#include <rfb/XF86keysym.h>
#include <rfb/Rect.h>

#include "keysym2ucs.h"

@@ -49,36 +50,44 @@ const int kVK_Menu = 0x6E;

static bool captured = false;

int cocoa_capture_display(Fl_Window *win, bool all_displays)
int cocoa_capture_displays(Fl_Window *win)
{
NSWindow *nsw;

nsw = (NSWindow*)fl_xid(win);

if (!captured) {
if (all_displays) {
if (CGCaptureAllDisplays() != kCGErrorSuccess)
return 1;
} else {
CGDirectDisplayID displays[16];
CGDisplayCount count;
int index;
CGDisplayCount count;
CGDirectDisplayID displays[16];

if (CGGetActiveDisplayList(16, displays, &count) != kCGErrorSuccess)
return 1;
int sx, sy, sw, sh;
rfb::Rect windows_rect, screen_rect;

if (count != (unsigned)Fl::screen_count())
return 1;
windows_rect.setXYWH(win->x(), win->y(), win->w(), win->h());

if (CGGetActiveDisplayList(16, displays, &count) != kCGErrorSuccess)
return 1;

index = Fl::screen_num(win->x(), win->y(), win->w(), win->h());
if (count != (unsigned)Fl::screen_count())
return 1;

for (int i = 0; i < Fl::screen_count(); i++) {
Fl::screen_xywh(sx, sy, sw, sh, i);

if (CGDisplayCapture(displays[index]) != kCGErrorSuccess)
screen_rect.setXYWH(sx, sy, sw, sh);
if (screen_rect.enclosed_by(windows_rect)) {
if (CGDisplayCapture(displays[i]) != kCGErrorSuccess)
return 1;
}

captured = true;
} else {
// A display might have been captured with the previous
// monitor selection. In that case we don't want to keep
// it when its no longer inside the window_rect.
CGDisplayRelease(displays[i]);
}
}

captured = true;

if ([nsw level] == CGShieldingWindowLevel())
return 0;

@@ -87,7 +96,7 @@ int cocoa_capture_display(Fl_Window *win, bool all_displays)
return 0;
}

void cocoa_release_display(Fl_Window *win)
void cocoa_release_displays(Fl_Window *win)
{
NSWindow *nsw;
int newlevel;

+ 118
- 53
vncviewer/parameters.cxx Переглянути файл

@@ -101,10 +101,17 @@ IntParameter qualityLevel("QualityLevel",
8);

BoolParameter maximize("Maximize", "Maximize viewer window", false);
BoolParameter fullScreen("FullScreen", "Full screen mode", false);
BoolParameter fullScreen("FullScreen", "Enable full screen", false);
StringParameter fullScreenMode("FullScreenMode", "Specify which monitors to use when in full screen. "
"Should be either Current, Selected or All",
"Current");
BoolParameter fullScreenAllMonitors("FullScreenAllMonitors",
"Enable full screen over all monitors",
true);
"[DEPRECATED] Enable full screen over all monitors",
false);
MonitorIndicesParameter fullScreenSelectedMonitors("FullScreenSelectedMonitors",
"Use the given list of monitors in full screen"
" when -FullScreenMode=Selected.",
"1");
StringParameter desktopSize("DesktopSize",
"Reconfigure desktop size on the server on "
"connect (if possible)", "");
@@ -175,7 +182,8 @@ static VoidParameter* parameterArray[] = {
&noJpeg,
&qualityLevel,
&fullScreen,
&fullScreenAllMonitors,
&fullScreenMode,
&fullScreenSelectedMonitors,
&desktopSize,
&remoteResize,
&viewOnly,
@@ -190,6 +198,10 @@ static VoidParameter* parameterArray[] = {
&fullscreenSystemKeys
};

static VoidParameter* readOnlyParameterArray[] = {
&fullScreenAllMonitors
};

// Encoding Table
static struct {
const char first;
@@ -399,6 +411,30 @@ static bool getKeyInt(const char* _name, int* dest, HKEY* hKey) {
return true;
}

static bool removeValue(const char* _name, HKEY* hKey) {
const DWORD buffersize = 256;
wchar_t name[buffersize];

unsigned size = fl_utf8towc(_name, strlen(_name)+1, name, buffersize);
if (size >= buffersize) {
vlog.error(_("The name of the parameter %s was too large to remove from the registry"), _name);
return false;
}

LONG res = RegDeleteValueW(*hKey, name);
if (res != ERROR_SUCCESS) {
if (res == ERROR_FILE_NOT_FOUND) {
// The value does not exist, no need to remove it.
return true;
} else {
vlog.error(_("Failed to remove parameter %s from the registry: %ld"),
_name, res);
return false;
}
}

return true;
}

void saveHistoryToRegKey(const vector<string>& serverHistory) {
HKEY hKey;
@@ -456,6 +492,12 @@ static void saveToReg(const char* servername) {
}
}

// Remove read-only parameters to replicate the behaviour of Linux/macOS when they
// store a config to disk. If the parameter hasn't been migrated at this point it
// will be lost.
for (size_t i = 0; i < sizeof(readOnlyParameterArray)/sizeof(VoidParameter*); i++)
removeValue(readOnlyParameterArray[i]->getName(), &hKey);

res = RegCloseKey(hKey);
if (res != ERROR_SUCCESS) {
vlog.error(_("Failed to close registry key: %ld"), res);
@@ -500,6 +542,29 @@ void loadHistoryFromRegKey(vector<string>& serverHistory) {
}
}

static void findAndSetViewerParametersFromReg(VoidParameter* parameters[], size_t parameters_len, HKEY* hKey) {

const size_t buffersize = 256;
int intValue = 0;
char stringValue[buffersize];

for (size_t i = 0; i < parameters_len/sizeof(VoidParameter*); i++) {
if (dynamic_cast<StringParameter*>(parameters[i]) != NULL) {
if (getKeyString(parameters[i]->getName(), stringValue, buffersize, hKey))
parameters[i]->setParam(stringValue);
} else if (dynamic_cast<IntParameter*>(parameters[i]) != NULL) {
if (getKeyInt(parameters[i]->getName(), &intValue, hKey))
((IntParameter*)parameters[i])->setParam(intValue);
} else if (dynamic_cast<BoolParameter*>(parameters[i]) != NULL) {
if (getKeyInt(parameters[i]->getName(), &intValue, hKey))
((BoolParameter*)parameters[i])->setParam(intValue);
} else {
vlog.error(_("Unknown parameter type for parameter %s"),
parameters[i]->getName());
}
}
}

static char* loadFromReg() {

HKEY hKey;
@@ -523,24 +588,8 @@ static char* loadFromReg() {
if (getKeyString("ServerName", servernameBuffer, buffersize, &hKey))
snprintf(servername, buffersize, "%s", servernameBuffer);
int intValue = 0;
char stringValue[buffersize];
for (size_t i = 0; i < sizeof(parameterArray)/sizeof(VoidParameter*); i++) {
if (dynamic_cast<StringParameter*>(parameterArray[i]) != NULL) {
if (getKeyString(parameterArray[i]->getName(), stringValue, buffersize, &hKey))
parameterArray[i]->setParam(stringValue);
} else if (dynamic_cast<IntParameter*>(parameterArray[i]) != NULL) {
if (getKeyInt(parameterArray[i]->getName(), &intValue, &hKey))
((IntParameter*)parameterArray[i])->setParam(intValue);
} else if (dynamic_cast<BoolParameter*>(parameterArray[i]) != NULL) {
if (getKeyInt(parameterArray[i]->getName(), &intValue, &hKey))
((BoolParameter*)parameterArray[i])->setParam(intValue);
} else {
vlog.error(_("Unknown parameter type for parameter %s"),
parameterArray[i]->getName());
}
}
findAndSetViewerParametersFromReg(parameterArray, sizeof(parameterArray), &hKey);
findAndSetViewerParametersFromReg(readOnlyParameterArray, sizeof(readOnlyParameterArray), &hKey);

res = RegCloseKey(hKey);
if (res != ERROR_SUCCESS){
@@ -607,6 +656,48 @@ void saveViewerParameters(const char *filename, const char *servername) {
fclose(f);
}

static bool findAndSetViewerParameterFromValue(
VoidParameter* parameters[], size_t parameters_len,
char* value, char* line, int lineNr, char* filepath)
{
const size_t buffersize = 256;
char decodingBuffer[buffersize];

// Find and set the correct parameter
for (size_t i = 0; i < parameters_len/sizeof(VoidParameter*); i++) {

if (dynamic_cast<StringParameter*>(parameters[i]) != NULL) {
if (strcasecmp(line, ((StringParameter*)parameters[i])->getName()) == 0) {

if(!decodeValue(value, decodingBuffer, sizeof(decodingBuffer))) {
vlog.error(_("Failed to read line %d in file %s: %s"),
lineNr, filepath, _("Invalid format or too large value"));
continue;
}
((StringParameter*)parameters[i])->setParam(decodingBuffer);
return false;
}

} else if (dynamic_cast<IntParameter*>(parameters[i]) != NULL) {
if (strcasecmp(line, ((IntParameter*)parameters[i])->getName()) == 0) {
((IntParameter*)parameters[i])->setParam(atoi(value));
return false;
}

} else if (dynamic_cast<BoolParameter*>(parameters[i]) != NULL) {
if (strcasecmp(line, ((BoolParameter*)parameters[i])->getName()) == 0) {
((BoolParameter*)parameters[i])->setParam(atoi(value));
return false;
}

} else {
vlog.error(_("Unknown parameter type for parameter %s"),
parameters[i]->getName());
}
}

return true;
}

char* loadViewerParameters(const char *filename) {

@@ -705,38 +796,12 @@ char* loadViewerParameters(const char *filename) {
invalidParameterName = false;

} else {
invalidParameterName = findAndSetViewerParameterFromValue(parameterArray, sizeof(parameterArray),
value, line, lineNr, filepath);

// Find and set the correct parameter
for (size_t i = 0; i < sizeof(parameterArray)/sizeof(VoidParameter*); i++) {

if (dynamic_cast<StringParameter*>(parameterArray[i]) != NULL) {
if (strcasecmp(line, ((StringParameter*)parameterArray[i])->getName()) == 0) {

if(!decodeValue(value, decodingBuffer, sizeof(decodingBuffer))) {
vlog.error(_("Failed to read line %d in file %s: %s"),
lineNr, filepath, _("Invalid format or too large value"));
continue;
}
((StringParameter*)parameterArray[i])->setParam(decodingBuffer);
invalidParameterName = false;
}

} else if (dynamic_cast<IntParameter*>(parameterArray[i]) != NULL) {
if (strcasecmp(line, ((IntParameter*)parameterArray[i])->getName()) == 0) {
((IntParameter*)parameterArray[i])->setParam(atoi(value));
invalidParameterName = false;
}

} else if (dynamic_cast<BoolParameter*>(parameterArray[i]) != NULL) {
if (strcasecmp(line, ((BoolParameter*)parameterArray[i])->getName()) == 0) {
((BoolParameter*)parameterArray[i])->setParam(atoi(value));
invalidParameterName = false;
}

} else {
vlog.error(_("Unknown parameter type for parameter %s"),
parameterArray[i]->getName());
}
if (invalidParameterName) {
invalidParameterName = findAndSetViewerParameterFromValue(readOnlyParameterArray, sizeof(readOnlyParameterArray),
value, line, lineNr, filepath);
}
}


+ 4
- 1
vncviewer/parameters.h Переглянути файл

@@ -21,6 +21,7 @@
#define __PARAMETERS_H__

#include <rfb/Configuration.h>
#include "MonitorIndicesParameter.h"

#ifdef _WIN32
#include <vector>
@@ -49,7 +50,9 @@ extern rfb::IntParameter qualityLevel;

extern rfb::BoolParameter maximize;
extern rfb::BoolParameter fullScreen;
extern rfb::BoolParameter fullScreenAllMonitors;
extern rfb::StringParameter fullScreenMode;
extern rfb::BoolParameter fullScreenAllMonitors; // deprecated
extern MonitorIndicesParameter fullScreenSelectedMonitors;
extern rfb::StringParameter desktopSize;
extern rfb::StringParameter geometry;
extern rfb::BoolParameter remoteResize;

+ 12
- 0
vncviewer/vncviewer.cxx Переглянути файл

@@ -435,6 +435,16 @@ potentiallyLoadConfigurationFile(char *vncServerName)
}
}

static void
migrateDeprecatedOptions()
{
if (fullScreenAllMonitors) {
vlog.info(_("FullScreenAllMonitors is deprecated, set FullScreenMode to 'all' instead."));

fullScreenMode.setParam("all");
}
}

#ifndef WIN32
static int
interpretViaParam(char *remoteHost, int *remotePort, int localPort)
@@ -613,6 +623,8 @@ int main(int argc, char** argv)
// Check if the server name in reality is a configuration file
potentiallyLoadConfigurationFile(vncServerName);

migrateDeprecatedOptions();

mkvnchomedir();

CSecurity::upg = &dlg;

+ 16
- 2
vncviewer/vncviewer.man Переглянути файл

@@ -205,9 +205,23 @@ Maximize viewer window.
Start in full-screen mode.
.
.TP
.B \-FullScreenAllMonitors
.B \-FullScreenAllMonitors (DEPRECATED)
Use all local monitors and not just the current one when switching to
full-screen mode.
full-screen mode. Replaced by \fB-FullScreenMode=all\fP
.
.TP
.B \-FullScreenMode \fImode\fP
Specify which monitors to use when in full screen. It should be either "Current",
"Selected" (specified by \fB-FullScreenSelectedMonitors\fP) or "All".
The default is "Current".
.
.TP
.B \-FullScreenSelectedMonitors \fImonitors\fP
This option specifies the monitors to use with \fB-FullScreenMode=selected\fP.
Monitors are ordered according to the system configuration from left to right,
and in case of a conflict, from top to bottom. So, for example, "1,2,3" means
that the first, second and third monitor counting from the left should be used.
The default is "1".
.
.TP
.B \-FullscreenSystemKeys

+ 52
- 0
vncviewer/win32.c Переглянути файл

@@ -18,6 +18,7 @@
*/

#include <windows.h>
#include <stdio.h>

#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;
}

+ 1
- 0
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

Завантаження…
Відмінити
Зберегти