Files
2026-07-28 03:21:34 +08:00

1371 lines
43 KiB
C++

/* Copyright 2011-2025 Pierre Ossman <ossman@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.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <assert.h>
#include <stdlib.h>
#include <list>
#include <core/string.h>
#include <rfb/encodings.h>
#if defined(HAVE_GNUTLS) || defined(HAVE_NETTLE)
#include <rfb/Security.h>
#include <rfb/SecurityClient.h>
#ifdef HAVE_GNUTLS
#include <rfb/CSecurityTLS.h>
#endif
#endif
#include "OptionsDialog.h"
#include "ShortcutHandler.h"
#include "i18n.h"
#include "parameters.h"
#include "fltk/layout.h"
#include "fltk/util.h"
#include "fltk/Fl_Monitor_Arrangement.h"
#include "fltk/Fl_Navigation.h"
#ifdef __APPLE__
#include "cocoa.h"
#endif
#include <FL/Fl.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Tabs.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Check_Button.H>
#include <FL/Fl_Return_Button.H>
#include <FL/Fl_Round_Button.H>
#include <FL/Fl_Toggle_Button.H>
#include <FL/Fl_Int_Input.H>
#include <FL/Fl_Choice.H>
std::map<OptionsCallback*, void*> OptionsDialog::callbacks;
static std::set<OptionsDialog *> instances;
OptionsDialog::OptionsDialog()
: Fl_Window(580, 420, _("T2SRFBVNC options"))
{
int x, y;
Fl_Navigation *navigation;
Fl_Button *button;
// Odd dimensions to get rid of extra borders
// FIXME: We need to retain the top border on Windows as they don't
// have any separator for the caption, which looks odd
#ifdef WIN32
navigation = new Fl_Navigation(-1, 0, w()+2,
h() - OUTER_MARGIN - BUTTON_HEIGHT - OUTER_MARGIN);
#else
navigation = new Fl_Navigation(-1, -1, w()+2,
h()+1 - OUTER_MARGIN - BUTTON_HEIGHT - OUTER_MARGIN);
#endif
{
int tx, ty, tw, th;
navigation->client_area(tx, ty, tw, th, 150);
createCompressionPage(tx, ty, tw, th);
createSecurityPage(tx, ty, tw, th);
createInputPage(tx, ty, tw, th);
createShortcutsPage(tx, ty, tw, th);
createDisplayPage(tx, ty, tw, th);
createMiscPage(tx, ty, tw, th);
}
navigation->end();
x = w() - BUTTON_WIDTH * 2 - INNER_MARGIN - OUTER_MARGIN;
y = h() - BUTTON_HEIGHT - OUTER_MARGIN;
button = new Fl_Button(x, y, BUTTON_WIDTH, BUTTON_HEIGHT, _("Cancel"));
button->callback(this->handleCancel, this);
x += BUTTON_WIDTH + INNER_MARGIN;
button = new Fl_Return_Button(x, y, BUTTON_WIDTH, BUTTON_HEIGHT, _("OK"));
button->callback(this->handleOK, this);
callback(this->handleCancel, this);
set_modal();
if (instances.size() == 0)
Fl::add_handler(fltk_event_handler);
instances.insert(this);
}
OptionsDialog::~OptionsDialog()
{
instances.erase(this);
if (instances.size() == 0)
Fl::remove_handler(fltk_event_handler);
}
void OptionsDialog::showDialog(void)
{
static OptionsDialog *dialog = nullptr;
if (!dialog)
dialog = new OptionsDialog();
if (dialog->shown())
return;
dialog->show();
}
void OptionsDialog::addCallback(OptionsCallback *cb, void *data)
{
callbacks[cb] = data;
}
void OptionsDialog::removeCallback(OptionsCallback *cb)
{
callbacks.erase(cb);
}
void OptionsDialog::show(void)
{
/* show() gets called for raise events as well */
if (!shown())
loadOptions();
Fl_Window::show();
}
void OptionsDialog::loadOptions(void)
{
/* Compression */
autoselectCheckbox->value(autoSelect);
if (preferredEncoding == "Tight")
tightButton->setonly();
else if (preferredEncoding == "ZRLE")
zrleButton->setonly();
else if (preferredEncoding == "Hextile")
hextileButton->setonly();
#ifdef HAVE_H264
else if (preferredEncoding == "H.264")
h264Button->setonly();
#endif
else if (preferredEncoding == "Raw")
rawButton->setonly();
if (fullColour)
fullcolorCheckbox->setonly();
else {
switch (lowColourLevel) {
case 0:
verylowcolorCheckbox->setonly();
break;
case 1:
lowcolorCheckbox->setonly();
break;
case 2:
mediumcolorCheckbox->setonly();
break;
}
}
char digit[2] = "0";
compressionCheckbox->value(customCompressLevel);
jpegCheckbox->value(!noJpeg);
digit[0] = '0' + compressLevel;
compressionInput->value(digit);
digit[0] = '0' + qualityLevel;
jpegInput->value(digit);
handleAutoselect(autoselectCheckbox, this);
handleCompression(compressionCheckbox, this);
handleJpeg(jpegCheckbox, this);
#if defined(HAVE_GNUTLS) || defined(HAVE_NETTLE)
/* Security */
rfb::Security security(rfb::SecurityClient::secTypes);
std::list<uint8_t> secTypes;
std::list<uint32_t> secTypesExt;
encNoneCheckbox->value(false);
#ifdef HAVE_GNUTLS
encTLSCheckbox->value(false);
encX509Checkbox->value(false);
#endif
#ifdef HAVE_NETTLE
encRSAAESCheckbox->value(false);
#endif
authNoneCheckbox->value(false);
authVncCheckbox->value(false);
authPlainCheckbox->value(false);
secTypes = security.GetEnabledSecTypes();
for (uint8_t type : secTypes) {
switch (type) {
case rfb::secTypeNone:
encNoneCheckbox->value(true);
authNoneCheckbox->value(true);
break;
case rfb::secTypeVncAuth:
encNoneCheckbox->value(true);
authVncCheckbox->value(true);
break;
}
}
secTypesExt = security.GetEnabledExtSecTypes();
for (uint32_t type : secTypesExt) {
switch (type) {
case rfb::secTypePlain:
encNoneCheckbox->value(true);
authPlainCheckbox->value(true);
break;
#ifdef HAVE_GNUTLS
case rfb::secTypeTLSNone:
encTLSCheckbox->value(true);
authNoneCheckbox->value(true);
break;
case rfb::secTypeTLSVnc:
encTLSCheckbox->value(true);
authVncCheckbox->value(true);
break;
case rfb::secTypeTLSPlain:
encTLSCheckbox->value(true);
authPlainCheckbox->value(true);
break;
case rfb::secTypeX509None:
encX509Checkbox->value(true);
authNoneCheckbox->value(true);
break;
case rfb::secTypeX509Vnc:
encX509Checkbox->value(true);
authVncCheckbox->value(true);
break;
case rfb::secTypeX509Plain:
encX509Checkbox->value(true);
authPlainCheckbox->value(true);
break;
#endif
#ifdef HAVE_NETTLE
case rfb::secTypeRA2:
case rfb::secTypeRA256:
encRSAAESCheckbox->value(true);
authVncCheckbox->value(true);
authPlainCheckbox->value(true);
break;
case rfb::secTypeRA2ne:
case rfb::secTypeRAne256:
authVncCheckbox->value(true);
/* fall through */
case rfb::secTypeDH:
case rfb::secTypeMSLogonII:
encNoneCheckbox->value(true);
authPlainCheckbox->value(true);
break;
#endif
}
}
#ifdef HAVE_GNUTLS
caInput->value(rfb::CSecurityTLS::X509CA);
crlInput->value(rfb::CSecurityTLS::X509CRL);
handleX509(encX509Checkbox, this);
#endif
#endif
/* Input */
viewOnlyCheckbox->value(viewOnly);
emulateMBCheckbox->value(emulateMiddleButton);
acceptClipboardCheckbox->value(acceptClipboard);
#if !defined(WIN32) && !defined(__APPLE__)
setPrimaryCheckbox->value(setPrimary);
#endif
sendClipboardCheckbox->value(sendClipboard);
#if !defined(WIN32) && !defined(__APPLE__)
sendPrimaryCheckbox->value(sendPrimary);
#endif
systemKeysCheckbox->value(fullscreenSystemKeys);
/* Keyboard shortcuts */
unsigned modifierMask;
modifierMask = 0;
for (core::EnumListEntry key : shortcutModifiers)
modifierMask |= ShortcutHandler::parseModifier(key.getValueStr().c_str());
ctrlButton->value(modifierMask & ShortcutHandler::Control);
shiftButton->value(modifierMask & ShortcutHandler::Shift);
altButton->value(modifierMask & ShortcutHandler::Alt);
superButton->value(modifierMask & ShortcutHandler::Super);
handleModifier(nullptr, this);
/* Display */
if (!fullScreen) {
windowedButton->setonly();
} else {
if (fullScreenMode == "all") {
allMonitorsButton->setonly();
} else if (fullScreenMode == "selected") {
selectedMonitorsButton->setonly();
} else {
currentMonitorButton->setonly();
}
}
monitorArrangement->value(fullScreenSelectedMonitors.getMonitors());
handleFullScreenMode(selectedMonitorsButton, this);
/* Misc. */
sharedCheckbox->value(shared);
reconnectCheckbox->value(reconnectOnError);
alwaysCursorCheckbox->value(alwaysCursor);
if (cursorType == "system") {
cursorTypeChoice->value(1);
} else {
// Default
cursorTypeChoice->value(0);
}
handleAlwaysCursor(alwaysCursorCheckbox, this);
}
void OptionsDialog::storeOptions(void)
{
/* Compression */
autoSelect.setParam(autoselectCheckbox->value());
if (tightButton->value())
preferredEncoding.setParam(rfb::encodingName(rfb::encodingTight));
else if (zrleButton->value())
preferredEncoding.setParam(rfb::encodingName(rfb::encodingZRLE));
else if (hextileButton->value())
preferredEncoding.setParam(rfb::encodingName(rfb::encodingHextile));
#ifdef HAVE_H264
else if (h264Button->value())
preferredEncoding.setParam(rfb::encodingName(rfb::encodingH264));
#endif
else if (rawButton->value())
preferredEncoding.setParam(rfb::encodingName(rfb::encodingRaw));
fullColour.setParam(fullcolorCheckbox->value());
if (verylowcolorCheckbox->value())
lowColourLevel.setParam(0);
else if (lowcolorCheckbox->value())
lowColourLevel.setParam(1);
else if (mediumcolorCheckbox->value())
lowColourLevel.setParam(2);
customCompressLevel.setParam(compressionCheckbox->value());
noJpeg.setParam(!jpegCheckbox->value());
compressLevel.setParam(atoi(compressionInput->value()));
qualityLevel.setParam(atoi(jpegInput->value()));
#if defined(HAVE_GNUTLS) || defined(HAVE_NETTLE)
/* Security */
rfb::Security security;
/* Process security types which don't use encryption */
if (encNoneCheckbox->value()) {
if (authNoneCheckbox->value())
security.EnableSecType(rfb::secTypeNone);
if (authVncCheckbox->value()) {
security.EnableSecType(rfb::secTypeVncAuth);
#ifdef HAVE_NETTLE
security.EnableSecType(rfb::secTypeRA2ne);
security.EnableSecType(rfb::secTypeRAne256);
#endif
}
if (authPlainCheckbox->value()) {
security.EnableSecType(rfb::secTypePlain);
#ifdef HAVE_NETTLE
security.EnableSecType(rfb::secTypeRA2ne);
security.EnableSecType(rfb::secTypeRAne256);
security.EnableSecType(rfb::secTypeDH);
security.EnableSecType(rfb::secTypeMSLogonII);
#endif
}
}
#ifdef HAVE_GNUTLS
/* Process security types which use TLS encryption */
if (encTLSCheckbox->value()) {
if (authNoneCheckbox->value())
security.EnableSecType(rfb::secTypeTLSNone);
if (authVncCheckbox->value())
security.EnableSecType(rfb::secTypeTLSVnc);
if (authPlainCheckbox->value())
security.EnableSecType(rfb::secTypeTLSPlain);
}
/* Process security types which use X509 encryption */
if (encX509Checkbox->value()) {
if (authNoneCheckbox->value())
security.EnableSecType(rfb::secTypeX509None);
if (authVncCheckbox->value())
security.EnableSecType(rfb::secTypeX509Vnc);
if (authPlainCheckbox->value())
security.EnableSecType(rfb::secTypeX509Plain);
}
rfb::CSecurityTLS::X509CA.setParam(caInput->value());
rfb::CSecurityTLS::X509CRL.setParam(crlInput->value());
#endif
#ifdef HAVE_NETTLE
if (encRSAAESCheckbox->value()) {
security.EnableSecType(rfb::secTypeRA2);
security.EnableSecType(rfb::secTypeRA256);
}
#endif
rfb::SecurityClient::secTypes.setParam(security.ToString());
#endif
/* Input */
viewOnly.setParam(viewOnlyCheckbox->value());
emulateMiddleButton.setParam(emulateMBCheckbox->value());
acceptClipboard.setParam(acceptClipboardCheckbox->value());
#if !defined(WIN32) && !defined(__APPLE__)
setPrimary.setParam(setPrimaryCheckbox->value());
#endif
sendClipboard.setParam(sendClipboardCheckbox->value());
#if !defined(WIN32) && !defined(__APPLE__)
sendPrimary.setParam(sendPrimaryCheckbox->value());
#endif
fullscreenSystemKeys.setParam(systemKeysCheckbox->value());
/* Keyboard shortcuts */
std::list<std::string> modifierList;
if (ctrlButton->value())
modifierList.push_back(
ShortcutHandler::modifierString(ShortcutHandler::Control));
if (shiftButton->value())
modifierList.push_back(
ShortcutHandler::modifierString(ShortcutHandler::Shift));
if (altButton->value())
modifierList.push_back(
ShortcutHandler::modifierString(ShortcutHandler::Alt));
if (superButton->value())
modifierList.push_back(
ShortcutHandler::modifierString(ShortcutHandler::Super));
shortcutModifiers.setParam(modifierList);
/* Display */
if (windowedButton->value()) {
fullScreen.setParam(false);
} else {
fullScreen.setParam(true);
if (allMonitorsButton->value()) {
fullScreenMode.setParam("All");
} else if (selectedMonitorsButton->value()) {
fullScreenMode.setParam("Selected");
} else {
fullScreenMode.setParam("Current");
}
}
fullScreenSelectedMonitors.setMonitors(monitorArrangement->value());
/* Misc. */
shared.setParam(sharedCheckbox->value());
reconnectOnError.setParam(reconnectCheckbox->value());
alwaysCursor.setParam(alwaysCursorCheckbox->value());
if (cursorTypeChoice->value() == 1) {
cursorType.setParam("System");
} else {
// Default
cursorType.setParam("Dot");
}
std::map<OptionsCallback*, void*>::const_iterator iter;
for (iter = callbacks.begin();iter != callbacks.end();++iter)
iter->first(iter->second);
}
void OptionsDialog::createCompressionPage(int tx, int ty, int tw, int th)
{
Fl_Group *group = new Fl_Group(tx, ty, tw, th, _("Compression"));
int orig_tx, orig_ty;
int col1_ty, col2_ty;
int half_width, full_width;
tx += OUTER_MARGIN;
ty += OUTER_MARGIN;
full_width = tw - OUTER_MARGIN * 2;
half_width = (full_width - INNER_MARGIN) / 2;
/* AutoSelect checkbox */
autoselectCheckbox = new Fl_Check_Button(LBLRIGHT(tx, ty,
CHECK_MIN_WIDTH,
CHECK_HEIGHT,
_("Auto select")));
autoselectCheckbox->callback(handleAutoselect, this);
ty += CHECK_HEIGHT + INNER_MARGIN;
/* Two columns */
orig_tx = tx;
orig_ty = ty;
/* VNC encoding box */
ty += GROUP_LABEL_OFFSET;
encodingGroup = new Fl_Group(tx, ty, half_width, 0,
_("Preferred encoding"));
encodingGroup->box(FL_FLAT_BOX);
encodingGroup->labelfont(FL_BOLD);
encodingGroup->align(FL_ALIGN_LEFT | FL_ALIGN_TOP);
{
tx += INDENT;
ty += TIGHT_MARGIN;
tightButton = new Fl_Round_Button(LBLRIGHT(tx, ty,
RADIO_MIN_WIDTH,
RADIO_HEIGHT,
"Tight"));
tightButton->type(FL_RADIO_BUTTON);
ty += RADIO_HEIGHT + TIGHT_MARGIN;
zrleButton = new Fl_Round_Button(LBLRIGHT(tx, ty,
RADIO_MIN_WIDTH,
RADIO_HEIGHT,
"ZRLE"));
zrleButton->type(FL_RADIO_BUTTON);
ty += RADIO_HEIGHT + TIGHT_MARGIN;
hextileButton = new Fl_Round_Button(LBLRIGHT(tx, ty,
RADIO_MIN_WIDTH,
RADIO_HEIGHT,
"Hextile"));
hextileButton->type(FL_RADIO_BUTTON);
ty += RADIO_HEIGHT + TIGHT_MARGIN;
#ifdef HAVE_H264
h264Button = new Fl_Round_Button(LBLRIGHT(tx, ty,
RADIO_MIN_WIDTH,
RADIO_HEIGHT,
"H.264"));
h264Button->type(FL_RADIO_BUTTON);
ty += RADIO_HEIGHT + TIGHT_MARGIN;
#endif
rawButton = new Fl_Round_Button(LBLRIGHT(tx, ty,
RADIO_MIN_WIDTH,
RADIO_HEIGHT,
"Raw"));
rawButton->type(FL_RADIO_BUTTON);
ty += RADIO_HEIGHT + TIGHT_MARGIN;
}
ty -= TIGHT_MARGIN;
encodingGroup->end();
/* Needed for resize to work sanely */
encodingGroup->resizable(nullptr);
encodingGroup->size(encodingGroup->w(), ty - encodingGroup->y());
col1_ty = ty;
/* Second column */
tx = orig_tx + half_width + INNER_MARGIN;
ty = orig_ty;
/* Color box */
ty += GROUP_LABEL_OFFSET;
colorlevelGroup = new Fl_Group(tx, ty, half_width, 0, _("Color level"));
colorlevelGroup->labelfont(FL_BOLD);
colorlevelGroup->box(FL_FLAT_BOX);
colorlevelGroup->align(FL_ALIGN_LEFT | FL_ALIGN_TOP);
{
tx += INDENT;
ty += TIGHT_MARGIN;
fullcolorCheckbox = new Fl_Round_Button(LBLRIGHT(tx, ty,
RADIO_MIN_WIDTH,
RADIO_HEIGHT,
_("Full")));
fullcolorCheckbox->type(FL_RADIO_BUTTON);
ty += RADIO_HEIGHT + TIGHT_MARGIN;
mediumcolorCheckbox = new Fl_Round_Button(LBLRIGHT(tx, ty,
RADIO_MIN_WIDTH,
RADIO_HEIGHT,
_("Medium")));
mediumcolorCheckbox->type(FL_RADIO_BUTTON);
ty += RADIO_HEIGHT + TIGHT_MARGIN;
lowcolorCheckbox = new Fl_Round_Button(LBLRIGHT(tx, ty,
RADIO_MIN_WIDTH,
RADIO_HEIGHT,
_("Low")));
lowcolorCheckbox->type(FL_RADIO_BUTTON);
ty += RADIO_HEIGHT + TIGHT_MARGIN;
verylowcolorCheckbox = new Fl_Round_Button(LBLRIGHT(tx, ty,
RADIO_MIN_WIDTH,
RADIO_HEIGHT,
_("Very low")));
verylowcolorCheckbox->type(FL_RADIO_BUTTON);
ty += RADIO_HEIGHT + TIGHT_MARGIN;
}
ty -= TIGHT_MARGIN;
colorlevelGroup->end();
/* Needed for resize to work sanely */
colorlevelGroup->resizable(nullptr);
colorlevelGroup->size(colorlevelGroup->w(),
ty - colorlevelGroup->y());
col2_ty = ty;
/* Back to normal */
tx = orig_tx;
ty = (col1_ty > col2_ty ? col1_ty : col2_ty) + INNER_MARGIN;
/* Checkboxes */
compressionCheckbox = new Fl_Check_Button(LBLRIGHT(tx, ty,
CHECK_MIN_WIDTH,
CHECK_HEIGHT,
_("Custom compression level:")));
compressionCheckbox->labelfont(FL_BOLD);
compressionCheckbox->callback(handleCompression, this);
ty += CHECK_HEIGHT + TIGHT_MARGIN;
compressionInput = new Fl_Int_Input(tx + INDENT, ty,
INPUT_HEIGHT, INPUT_HEIGHT,
_("level (0=fast, 9=best)"));
compressionInput->align(FL_ALIGN_RIGHT);
ty += INPUT_HEIGHT + INNER_MARGIN;
jpegCheckbox = new Fl_Check_Button(LBLRIGHT(tx, ty,
CHECK_MIN_WIDTH,
CHECK_HEIGHT,
_("Allow JPEG compression:")));
jpegCheckbox->labelfont(FL_BOLD);
jpegCheckbox->callback(handleJpeg, this);
ty += CHECK_HEIGHT + TIGHT_MARGIN;
jpegInput = new Fl_Int_Input(tx + INDENT, ty,
INPUT_HEIGHT, INPUT_HEIGHT,
_("quality (0=poor, 9=best)"));
jpegInput->align(FL_ALIGN_RIGHT);
ty += INPUT_HEIGHT + INNER_MARGIN;
group->end();
}
void OptionsDialog::createSecurityPage(int tx, int ty, int tw, int th)
{
#if defined(HAVE_GNUTLS) || defined(HAVE_NETTLE)
Fl_Group *group = new Fl_Group(tx, ty, tw, th, _("Security"));
int orig_tx;
int width;
tx += OUTER_MARGIN;
ty += OUTER_MARGIN;
width = tw - OUTER_MARGIN * 2;
orig_tx = tx;
/* Encryption */
ty += GROUP_LABEL_OFFSET;
encryptionGroup = new Fl_Group(tx, ty, width, 0, _("Encryption"));
encryptionGroup->labelfont(FL_BOLD);
encryptionGroup->box(FL_FLAT_BOX);
encryptionGroup->align(FL_ALIGN_LEFT | FL_ALIGN_TOP);
{
tx += INDENT;
ty += TIGHT_MARGIN;
encNoneCheckbox = new Fl_Check_Button(LBLRIGHT(tx, ty,
CHECK_MIN_WIDTH,
CHECK_HEIGHT,
_("None")));
ty += CHECK_HEIGHT + TIGHT_MARGIN;
#ifdef HAVE_GNUTLS
encTLSCheckbox = new Fl_Check_Button(LBLRIGHT(tx, ty,
CHECK_MIN_WIDTH,
CHECK_HEIGHT,
_("TLS with anonymous certificates")));
ty += CHECK_HEIGHT + TIGHT_MARGIN;
encX509Checkbox = new Fl_Check_Button(LBLRIGHT(tx, ty,
CHECK_MIN_WIDTH,
CHECK_HEIGHT,
_("TLS with X509 certificates")));
encX509Checkbox->callback(handleX509, this);
ty += CHECK_HEIGHT + TIGHT_MARGIN;
ty += INPUT_LABEL_OFFSET;
caInput = new Fl_Input(tx + INDENT, ty,
width - INDENT * 2, INPUT_HEIGHT,
_("Path to X509 CA certificate"));
caInput->align(FL_ALIGN_LEFT | FL_ALIGN_TOP);
ty += INPUT_HEIGHT + TIGHT_MARGIN;
ty += INPUT_LABEL_OFFSET;
crlInput = new Fl_Input(tx + INDENT, ty,
width - INDENT * 2, INPUT_HEIGHT,
_("Path to X509 CRL file"));
crlInput->align(FL_ALIGN_LEFT | FL_ALIGN_TOP);
ty += INPUT_HEIGHT + TIGHT_MARGIN;
#endif
#ifdef HAVE_NETTLE
encRSAAESCheckbox = new Fl_Check_Button(LBLRIGHT(tx, ty,
CHECK_MIN_WIDTH,
CHECK_HEIGHT,
"RSA-AES"));
encRSAAESCheckbox->callback(handleRSAAES, this);
ty += CHECK_HEIGHT + TIGHT_MARGIN;
#endif
}
ty -= TIGHT_MARGIN;
encryptionGroup->end();
/* Needed for resize to work sanely */
encryptionGroup->resizable(nullptr);
encryptionGroup->size(encryptionGroup->w(),
ty - encryptionGroup->y());
/* Back to normal */
tx = orig_tx;
ty += INNER_MARGIN;
/* Authentication */
ty += GROUP_LABEL_OFFSET;
authenticationGroup = new Fl_Group(tx, ty, width, 0, _("Authentication"));
authenticationGroup->labelfont(FL_BOLD);
authenticationGroup->box(FL_FLAT_BOX);
authenticationGroup->align(FL_ALIGN_LEFT | FL_ALIGN_TOP);
{
tx += INDENT;
ty += TIGHT_MARGIN;
authNoneCheckbox = new Fl_Check_Button(LBLRIGHT(tx, ty,
CHECK_MIN_WIDTH,
CHECK_HEIGHT,
_("None")));
ty += CHECK_HEIGHT + TIGHT_MARGIN;
authVncCheckbox = new Fl_Check_Button(LBLRIGHT(tx, ty,
CHECK_MIN_WIDTH,
CHECK_HEIGHT,
_("Standard VNC (insecure without encryption)")));
ty += CHECK_HEIGHT + TIGHT_MARGIN;
authPlainCheckbox = new Fl_Check_Button(LBLRIGHT(tx, ty,
CHECK_MIN_WIDTH,
CHECK_HEIGHT,
_("Username and password (insecure without encryption)")));
ty += CHECK_HEIGHT + TIGHT_MARGIN;
}
ty -= TIGHT_MARGIN;
authenticationGroup->end();
/* Needed for resize to work sanely */
authenticationGroup->resizable(nullptr);
authenticationGroup->size(authenticationGroup->w(),
ty - authenticationGroup->y());
/* Back to normal */
tx = orig_tx;
ty += INNER_MARGIN;
group->end();
#else
(void)tx;
(void)ty;
(void)tw;
(void)th;
#endif
}
void OptionsDialog::createInputPage(int tx, int ty, int tw, int th)
{
Fl_Group *group = new Fl_Group(tx, ty, tw, th, _("Input"));
int orig_tx;
int width;
tx += OUTER_MARGIN;
ty += OUTER_MARGIN;
width = tw - OUTER_MARGIN * 2;
viewOnlyCheckbox = new Fl_Check_Button(LBLRIGHT(tx, ty,
CHECK_MIN_WIDTH,
CHECK_HEIGHT,
_("View only (ignore mouse and keyboard)")));
ty += CHECK_HEIGHT + INNER_MARGIN;
orig_tx = tx;
/* Mouse */
ty += GROUP_LABEL_OFFSET;
mouseGroup = new Fl_Group(tx, ty, width, 0, _("Mouse"));
mouseGroup->labelfont(FL_BOLD);
mouseGroup->box(FL_FLAT_BOX);
mouseGroup->align(FL_ALIGN_LEFT | FL_ALIGN_TOP);
{
tx += INDENT;
ty += TIGHT_MARGIN;
emulateMBCheckbox = new Fl_Check_Button(LBLRIGHT(tx, ty,
CHECK_MIN_WIDTH,
CHECK_HEIGHT,
_("Emulate middle mouse button")));
ty += CHECK_HEIGHT + TIGHT_MARGIN;
alwaysCursorCheckbox = new Fl_Check_Button(LBLRIGHT(tx, ty,
CHECK_MIN_WIDTH,
CHECK_HEIGHT,
_("Show local cursor when not provided by server")));
alwaysCursorCheckbox->callback(handleAlwaysCursor, this);
ty += CHECK_HEIGHT + TIGHT_MARGIN;
/* Cursor type */
cursorTypeChoice = new Fl_Choice(LBLLEFT(tx, ty, 150, CHOICE_HEIGHT, _("Cursor type")));
fltk_menu_add(cursorTypeChoice, _("Dot"), 0, nullptr, nullptr, 0);
fltk_menu_add(cursorTypeChoice, _("System"), 0, nullptr, nullptr, 0);
fltk_adjust_choice(cursorTypeChoice);
ty += CHOICE_HEIGHT + TIGHT_MARGIN;
}
ty -= TIGHT_MARGIN;
mouseGroup->end();
/* Needed for resize to work sanely */
mouseGroup->resizable(nullptr);
mouseGroup->size(mouseGroup->w(), ty - mouseGroup->y());
/* Back to normal */
tx = orig_tx;
ty += INNER_MARGIN;
/* Keyboard */
ty += GROUP_LABEL_OFFSET;
keyboardGroup = new Fl_Group(tx, ty, width, 0, _("Keyboard"));
keyboardGroup->labelfont(FL_BOLD);
keyboardGroup->box(FL_FLAT_BOX);
keyboardGroup->align(FL_ALIGN_LEFT | FL_ALIGN_TOP);
{
tx += INDENT;
ty += TIGHT_MARGIN;
systemKeysCheckbox = new Fl_Check_Button(
LBLRIGHT(tx, ty, CHECK_MIN_WIDTH, CHECK_HEIGHT,
_("Always send all keyboard input in full screen")));
systemKeysCheckbox->callback(handleSystemKeys, this);
ty += CHECK_HEIGHT + TIGHT_MARGIN;
}
ty -= TIGHT_MARGIN;
keyboardGroup->end();
/* Needed for resize to work sanely */
keyboardGroup->resizable(nullptr);
keyboardGroup->size(keyboardGroup->w(), ty - keyboardGroup->y());
/* Back to normal */
tx = orig_tx;
ty += INNER_MARGIN;
/* Clipboard */
ty += GROUP_LABEL_OFFSET;
clipboardGroup = new Fl_Group(tx, ty, width, 0, _("Clipboard"));
clipboardGroup->labelfont(FL_BOLD);
clipboardGroup->box(FL_FLAT_BOX);
clipboardGroup->align(FL_ALIGN_LEFT | FL_ALIGN_TOP);
{
tx += INDENT;
ty += TIGHT_MARGIN;
acceptClipboardCheckbox = new Fl_Check_Button(LBLRIGHT(tx, ty,
CHECK_MIN_WIDTH,
CHECK_HEIGHT,
_("Accept clipboard from server")));
acceptClipboardCheckbox->callback(handleClipboard, this);
ty += CHECK_HEIGHT + TIGHT_MARGIN;
#if !defined(WIN32) && !defined(__APPLE__)
setPrimaryCheckbox = new Fl_Check_Button(LBLRIGHT(tx + INDENT, ty,
CHECK_MIN_WIDTH,
CHECK_HEIGHT,
_("Also set primary selection")));
ty += CHECK_HEIGHT + TIGHT_MARGIN;
#endif
sendClipboardCheckbox = new Fl_Check_Button(LBLRIGHT(tx, ty,
CHECK_MIN_WIDTH,
CHECK_HEIGHT,
_("Send clipboard to server")));
sendClipboardCheckbox->callback(handleClipboard, this);
ty += CHECK_HEIGHT + TIGHT_MARGIN;
#if !defined(WIN32) && !defined(__APPLE__)
sendPrimaryCheckbox = new Fl_Check_Button(LBLRIGHT(tx + INDENT, ty,
CHECK_MIN_WIDTH,
CHECK_HEIGHT,
_("Send primary selection as clipboard")));
ty += CHECK_HEIGHT + TIGHT_MARGIN;
#endif
}
ty -= TIGHT_MARGIN;
clipboardGroup->end();
/* Needed for resize to work sanely */
clipboardGroup->resizable(nullptr);
clipboardGroup->size(clipboardGroup->w(), ty - clipboardGroup->y());
/* Back to normal */
tx = orig_tx;
ty += INNER_MARGIN;
group->end();
}
void OptionsDialog::createShortcutsPage(int tx, int ty, int tw, int th)
{
Fl_Group *group = new Fl_Group(tx, ty, tw, th, _("Keyboard shortcuts"));
tx += OUTER_MARGIN;
ty += OUTER_MARGIN;
Fl_Box *intro = new Fl_Box(tx, ty, tw - OUTER_MARGIN * 2, INPUT_HEIGHT);
intro->align(FL_ALIGN_TOP_LEFT|FL_ALIGN_INSIDE);
intro->label(_("Modifier keys for keyboard shortcuts:"));
ty += INPUT_HEIGHT + INNER_MARGIN;
int width;
width = (tw - OUTER_MARGIN * 2 - INNER_MARGIN * 3) / 4;
ctrlButton = new Fl_Toggle_Button(tx, ty,
/*
* TRANSLATORS: This refers to the
* keyboard key
* */
width, BUTTON_HEIGHT, _("Ctrl"));
ctrlButton->selection_color(FL_SELECTION_COLOR);
ctrlButton->callback(handleModifier, this);
shiftButton = new Fl_Toggle_Button(tx + width + INNER_MARGIN, ty,
/*
* TRANSLATORS: This refers to the
* keyboard key
* */
width, BUTTON_HEIGHT, _("Shift"));
shiftButton->selection_color(FL_SELECTION_COLOR);
shiftButton->callback(handleModifier, this);
altButton = new Fl_Toggle_Button(tx + width * 2 + INNER_MARGIN * 2, ty,
/*
* TRANSLATORS: This refers to the
* keyboard key
* */
width, BUTTON_HEIGHT, _("Alt"));
altButton->selection_color(FL_SELECTION_COLOR);
altButton->callback(handleModifier, this);
superButton = new Fl_Toggle_Button(tx + width * 3 + INNER_MARGIN * 3, ty,
/*
* TRANSLATORS: This refers to the
* keyboard key
* */
width, BUTTON_HEIGHT, _("Win"));
superButton->selection_color(FL_SELECTION_COLOR);
superButton->callback(handleModifier, this);
#ifdef __APPLE__
/* TRANSLATORS: This refers to the keyboard key */
ctrlButton->label(_("⌃ Ctrl"));
/* TRANSLATORS: This refers to the keyboard key */
shiftButton->label(_("⇧ Shift"));
/* TRANSLATORS: This refers to the keyboard key */
altButton->label(_("⌥ Option"));
/* TRANSLATORS: This refers to the keyboard key */
superButton->label(_("⌘ Cmd"));
#endif
ty += BUTTON_HEIGHT + INNER_MARGIN;
shortcutsText = new Fl_Box(tx, ty, tw - OUTER_MARGIN * 2, th - ty - OUTER_MARGIN);
shortcutsText->align(FL_ALIGN_TOP_LEFT|FL_ALIGN_INSIDE|FL_ALIGN_WRAP);
group->end();
}
void OptionsDialog::createDisplayPage(int tx, int ty, int tw, int th)
{
Fl_Group *group = new Fl_Group(tx, ty, tw, th, _("Display"));
int orig_tx;
int width;
tx += OUTER_MARGIN;
ty += OUTER_MARGIN;
width = tw - OUTER_MARGIN * 2;
orig_tx = tx;
/* Display mode */
ty += GROUP_LABEL_OFFSET;
displayModeGroup = new Fl_Group(tx, ty, width, 0, _("Display mode"));
displayModeGroup->labelfont(FL_BOLD);
displayModeGroup->box(FL_FLAT_BOX);
displayModeGroup->align(FL_ALIGN_LEFT | FL_ALIGN_TOP);
{
tx += INDENT;
ty += TIGHT_MARGIN;
width -= INDENT;
windowedButton = new Fl_Round_Button(LBLRIGHT(tx, ty,
RADIO_MIN_WIDTH,
RADIO_HEIGHT,
_("Windowed")));
windowedButton->type(FL_RADIO_BUTTON);
windowedButton->callback(handleFullScreenMode, this);
ty += RADIO_HEIGHT + TIGHT_MARGIN;
currentMonitorButton = new Fl_Round_Button(LBLRIGHT(tx, ty,
RADIO_MIN_WIDTH,
RADIO_HEIGHT,
_("Full screen on 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,
_("Full screen on 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,
_("Full screen on selected monitor(s)")));
selectedMonitorsButton->type(FL_RADIO_BUTTON);
selectedMonitorsButton->callback(handleFullScreenMode, this);
ty += RADIO_HEIGHT + TIGHT_MARGIN;
monitorArrangement = new Fl_Monitor_Arrangement(
tx + INDENT, ty,
width - INDENT, 150);
ty += 150 + TIGHT_MARGIN;
}
ty -= TIGHT_MARGIN;
displayModeGroup->end();
/* Needed for resize to work sanely */
displayModeGroup->resizable(nullptr);
displayModeGroup->size(displayModeGroup->w(),
ty - displayModeGroup->y());
/* Back to normal */
tx = orig_tx;
ty += INNER_MARGIN;
width = tw - OUTER_MARGIN * 2;
group->end();
}
void OptionsDialog::createMiscPage(int tx, int ty, int tw, int th)
{
Fl_Group *group = new Fl_Group(tx, ty, tw, th, _("Miscellaneous"));
tx += OUTER_MARGIN;
ty += OUTER_MARGIN;
sharedCheckbox = new Fl_Check_Button(LBLRIGHT(tx, ty,
CHECK_MIN_WIDTH,
CHECK_HEIGHT,
_("Shared (don't disconnect other viewers)")));
ty += CHECK_HEIGHT + TIGHT_MARGIN;
reconnectCheckbox = new Fl_Check_Button(LBLRIGHT(tx, ty,
CHECK_MIN_WIDTH,
CHECK_HEIGHT,
_("Ask to reconnect on connection errors")));
ty += CHECK_HEIGHT + TIGHT_MARGIN;
group->end();
}
void OptionsDialog::handleAutoselect(Fl_Widget* /*widget*/, void *data)
{
OptionsDialog *dialog = (OptionsDialog*)data;
if (dialog->autoselectCheckbox->value()) {
dialog->encodingGroup->deactivate();
dialog->colorlevelGroup->deactivate();
} else {
dialog->encodingGroup->activate();
dialog->colorlevelGroup->activate();
}
// JPEG setting is also affected by autoselection
dialog->handleJpeg(dialog->jpegCheckbox, dialog);
}
void OptionsDialog::handleCompression(Fl_Widget* /*widget*/, void *data)
{
OptionsDialog *dialog = (OptionsDialog*)data;
if (dialog->compressionCheckbox->value())
dialog->compressionInput->activate();
else
dialog->compressionInput->deactivate();
}
void OptionsDialog::handleJpeg(Fl_Widget* /*widget*/, void *data)
{
OptionsDialog *dialog = (OptionsDialog*)data;
if (dialog->jpegCheckbox->value() &&
!dialog->autoselectCheckbox->value())
dialog->jpegInput->activate();
else
dialog->jpegInput->deactivate();
}
void OptionsDialog::handleX509(Fl_Widget* /*widget*/, void *data)
{
OptionsDialog *dialog = (OptionsDialog*)data;
if (dialog->encX509Checkbox->value()) {
dialog->caInput->activate();
dialog->crlInput->activate();
} else {
dialog->caInput->deactivate();
dialog->crlInput->deactivate();
}
}
void OptionsDialog::handleRSAAES(Fl_Widget* /*widget*/, void *data)
{
OptionsDialog *dialog = (OptionsDialog*)data;
if (dialog->encRSAAESCheckbox->value()) {
dialog->authVncCheckbox->value(true);
dialog->authPlainCheckbox->value(true);
}
}
void OptionsDialog::handleSystemKeys(Fl_Widget* /*widget*/, void* data)
{
#ifdef __APPLE__
OptionsDialog* dialog = (OptionsDialog*)data;
// Pop up the access dialog if needed
if (dialog->systemKeysCheckbox->value())
cocoa_is_trusted(true);
#else
(void)data;
#endif
}
void OptionsDialog::handleClipboard(Fl_Widget* /*widget*/, void *data)
{
(void)data;
#if !defined(WIN32) && !defined(__APPLE__)
OptionsDialog *dialog = (OptionsDialog*)data;
if (dialog->acceptClipboardCheckbox->value())
dialog->setPrimaryCheckbox->activate();
else
dialog->setPrimaryCheckbox->deactivate();
if (dialog->sendClipboardCheckbox->value())
dialog->sendPrimaryCheckbox->activate();
else
dialog->sendPrimaryCheckbox->deactivate();
#endif
}
void OptionsDialog::handleModifier(Fl_Widget* /*widget*/, void *data)
{
OptionsDialog *dialog = (OptionsDialog*)data;
unsigned mask;
mask = 0;
if (dialog->ctrlButton->value())
mask |= ShortcutHandler::Control;
if (dialog->shiftButton->value())
mask |= ShortcutHandler::Shift;
if (dialog->altButton->value())
mask |= ShortcutHandler::Alt;
if (dialog->superButton->value())
mask |= ShortcutHandler::Super;
if (mask == 0) {
dialog->shortcutsText->copy_label(
_("All keyboard shortcuts are disabled."));
} else {
char prefix[256];
char prefix_noplus[256];
std::string label;
strcpy(prefix, ShortcutHandler::modifierPrefix(mask));
strcpy(prefix_noplus, ShortcutHandler::modifierPrefix(mask, true));
label += core::format(
_("To release keyboard control from the session, press %s."),
prefix_noplus);
label += "\n\n";
label += core::format(
_("To pass all keyboard input to the session, press %sG."),
prefix);
label += "\n\n";
label += core::format(
_("To toggle full-screen mode, press %sEnter."), prefix);
label += "\n\n";
label += core::format(
_("To open the session context menu, press %sM."), prefix);
label += "\n\n";
label += core::format(
_("To send a key combination that includes %s directly to the "
"session, press %sSpace, release the space bar without "
"releasing %s, and press the desired key."),
prefix_noplus, prefix, prefix_noplus);
dialog->shortcutsText->copy_label(label.c_str());
}
}
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;
dialog->hide();
}
void OptionsDialog::handleOK(Fl_Widget* /*widget*/, void *data)
{
OptionsDialog *dialog = (OptionsDialog*)data;
dialog->hide();
dialog->storeOptions();
}
int OptionsDialog::fltk_event_handler(int event)
{
std::set<OptionsDialog *>::iterator iter;
if (event != FL_SCREEN_CONFIGURATION_CHANGED)
return 0;
// Refresh monitor arrangement widget to match the parameter settings after
// screen configuration has changed. The MonitorArrangement index doesn't work
// the same way as the FLTK screen index.
for (iter = instances.begin(); iter != instances.end(); iter++)
Fl::add_timeout(0, handleScreenConfigTimeout, (*iter));
return 0;
}
void OptionsDialog::handleScreenConfigTimeout(void *data)
{
OptionsDialog *self = (OptionsDialog *)data;
assert(self);
self->monitorArrangement->value(fullScreenSelectedMonitors.getMonitors());
}
void OptionsDialog::handleAlwaysCursor(Fl_Widget* /*widget*/, void *data)
{
OptionsDialog *dialog = (OptionsDialog*)data;
if (dialog->alwaysCursorCheckbox->value()) {
dialog->cursorTypeChoice->activate();
} else {
dialog->cursorTypeChoice->deactivate();
}
}