aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPierre Ossman <ossman@cendio.se>2016-12-05 15:26:21 +0100
committerPierre Ossman <ossman@cendio.se>2017-08-24 12:33:03 +0200
commit2fa63f8576e5d1c632efeeb2c185f11e943899d8 (patch)
tree9a4e68d8be1ff669c2c07b95380ba4a0a71a3e6d
parent82e753d4e1928342c82cc7b82e854aa2666d1145 (diff)
downloadtigervnc-2fa63f8576e5d1c632efeeb2c185f11e943899d8.tar.gz
tigervnc-2fa63f8576e5d1c632efeeb2c185f11e943899d8.zip
Add client support for LED state sync
-rw-r--r--common/rfb/CMsgHandler.cxx5
-rw-r--r--common/rfb/CMsgHandler.h2
-rw-r--r--common/rfb/CMsgReader.cxx12
-rw-r--r--common/rfb/CMsgReader.h1
-rw-r--r--common/rfb/CMsgWriter.cxx2
-rw-r--r--common/rfb/ConnParams.cxx16
-rw-r--r--common/rfb/ConnParams.h5
-rw-r--r--common/rfb/encodings.h1
-rw-r--r--common/rfb/ledStates.h30
-rw-r--r--tests/CMakeLists.txt4
-rw-r--r--vncviewer/CConn.cxx9
-rw-r--r--vncviewer/CConn.h2
-rw-r--r--vncviewer/CMakeLists.txt4
-rw-r--r--vncviewer/DesktopWindow.cxx6
-rw-r--r--vncviewer/DesktopWindow.h3
-rw-r--r--vncviewer/Viewport.cxx172
-rw-r--r--vncviewer/Viewport.h5
-rw-r--r--vncviewer/cocoa.h3
-rw-r--r--vncviewer/cocoa.mm50
19 files changed, 327 insertions, 5 deletions
diff --git a/common/rfb/CMsgHandler.cxx b/common/rfb/CMsgHandler.cxx
index 11c979a3..74c7bf92 100644
--- a/common/rfb/CMsgHandler.cxx
+++ b/common/rfb/CMsgHandler.cxx
@@ -82,3 +82,8 @@ void CMsgHandler::framebufferUpdateStart()
void CMsgHandler::framebufferUpdateEnd()
{
}
+
+void CMsgHandler::setLEDState(unsigned int state)
+{
+ cp.setLEDState(state);
+}
diff --git a/common/rfb/CMsgHandler.h b/common/rfb/CMsgHandler.h
index 993276ed..ef2cda20 100644
--- a/common/rfb/CMsgHandler.h
+++ b/common/rfb/CMsgHandler.h
@@ -69,6 +69,8 @@ namespace rfb {
virtual void bell() = 0;
virtual void serverCutText(const char* str, rdr::U32 len) = 0;
+ virtual void setLEDState(unsigned int state);
+
ConnParams cp;
};
}
diff --git a/common/rfb/CMsgReader.cxx b/common/rfb/CMsgReader.cxx
index 9abe3f24..0aaf71fa 100644
--- a/common/rfb/CMsgReader.cxx
+++ b/common/rfb/CMsgReader.cxx
@@ -109,6 +109,9 @@ void CMsgReader::readMsg()
case pseudoEncodingExtendedDesktopSize:
readExtendedDesktopSize(x, y, w, h);
break;
+ case pseudoEncodingLEDState:
+ readLEDState();
+ break;
default:
readRect(Rect(x, y, x+w, y+h), encoding);
break;
@@ -382,3 +385,12 @@ void CMsgReader::readExtendedDesktopSize(int x, int y, int w, int h)
handler->setExtendedDesktopSize(x, y, w, h, layout);
}
+
+void CMsgReader::readLEDState()
+{
+ rdr::U8 state;
+
+ state = is->readU8();
+
+ handler->setLEDState(state);
+}
diff --git a/common/rfb/CMsgReader.h b/common/rfb/CMsgReader.h
index 7b52033f..99638276 100644
--- a/common/rfb/CMsgReader.h
+++ b/common/rfb/CMsgReader.h
@@ -65,6 +65,7 @@ namespace rfb {
void readSetCursorWithAlpha(int width, int height, const Point& hotspot);
void readSetDesktopName(int x, int y, int w, int h);
void readExtendedDesktopSize(int x, int y, int w, int h);
+ void readLEDState();
CMsgHandler* handler;
rdr::InStream* is;
diff --git a/common/rfb/CMsgWriter.cxx b/common/rfb/CMsgWriter.cxx
index fa784048..7a89a934 100644
--- a/common/rfb/CMsgWriter.cxx
+++ b/common/rfb/CMsgWriter.cxx
@@ -82,6 +82,8 @@ void CMsgWriter::writeSetEncodings(int preferredEncoding, bool useCopyRect)
encodings[nEncodings++] = pseudoEncodingExtendedDesktopSize;
if (cp->supportsDesktopRename)
encodings[nEncodings++] = pseudoEncodingDesktopName;
+ if (cp->supportsLEDState)
+ encodings[nEncodings++] = pseudoEncodingLEDState;
encodings[nEncodings++] = pseudoEncodingLastRect;
encodings[nEncodings++] = pseudoEncodingContinuousUpdates;
diff --git a/common/rfb/ConnParams.cxx b/common/rfb/ConnParams.cxx
index 9ee1d9c0..f0b69327 100644
--- a/common/rfb/ConnParams.cxx
+++ b/common/rfb/ConnParams.cxx
@@ -22,6 +22,7 @@
#include <rdr/OutStream.h>
#include <rfb/Exception.h>
#include <rfb/encodings.h>
+#include <rfb/ledStates.h>
#include <rfb/ConnParams.h>
#include <rfb/util.h>
@@ -34,10 +35,11 @@ ConnParams::ConnParams()
supportsLocalCursorWithAlpha(false),
supportsDesktopResize(false), supportsExtendedDesktopSize(false),
supportsDesktopRename(false), supportsLastRect(false),
- supportsSetDesktopSize(false), supportsFence(false),
- supportsContinuousUpdates(false),
+ supportsLEDState(false), supportsSetDesktopSize(false),
+ supportsFence(false), supportsContinuousUpdates(false),
compressLevel(2), qualityLevel(-1), fineQualityLevel(-1),
- subsampling(subsampleUndefined), name_(0), verStrPos(0)
+ subsampling(subsampleUndefined), name_(0), verStrPos(0),
+ ledState_(ledUnknown)
{
setName("");
cursor_ = new Cursor(0, 0, Point(), NULL);
@@ -141,6 +143,9 @@ void ConnParams::setEncodings(int nEncodings, const rdr::S32* encodings)
case pseudoEncodingLastRect:
supportsLastRect = true;
break;
+ case pseudoEncodingLEDState:
+ supportsLEDState = true;
+ break;
case pseudoEncodingFence:
supportsFence = true;
break;
@@ -183,3 +188,8 @@ void ConnParams::setEncodings(int nEncodings, const rdr::S32* encodings)
encodings_.insert(encodings[i]);
}
}
+
+void ConnParams::setLEDState(unsigned int state)
+{
+ ledState_ = state;
+}
diff --git a/common/rfb/ConnParams.h b/common/rfb/ConnParams.h
index 5e538933..d99d142c 100644
--- a/common/rfb/ConnParams.h
+++ b/common/rfb/ConnParams.h
@@ -84,6 +84,9 @@ namespace rfb {
void setEncodings(int nEncodings, const rdr::S32* encodings);
+ unsigned int ledState() { return ledState_; }
+ void setLEDState(unsigned int state);
+
bool useCopyRect;
bool supportsLocalCursor;
@@ -93,6 +96,7 @@ namespace rfb {
bool supportsExtendedDesktopSize;
bool supportsDesktopRename;
bool supportsLastRect;
+ bool supportsLEDState;
bool supportsSetDesktopSize;
bool supportsFence;
@@ -111,6 +115,7 @@ namespace rfb {
std::set<rdr::S32> encodings_;
char verStr[13];
int verStrPos;
+ unsigned int ledState_;
};
}
#endif
diff --git a/common/rfb/encodings.h b/common/rfb/encodings.h
index a65d863b..adeecaa9 100644
--- a/common/rfb/encodings.h
+++ b/common/rfb/encodings.h
@@ -34,6 +34,7 @@ namespace rfb {
const int pseudoEncodingXCursor = -240;
const int pseudoEncodingCursor = -239;
const int pseudoEncodingDesktopSize = -223;
+ const int pseudoEncodingLEDState = -261;
const int pseudoEncodingExtendedDesktopSize = -308;
const int pseudoEncodingDesktopName = -307;
const int pseudoEncodingFence = -312;
diff --git a/common/rfb/ledStates.h b/common/rfb/ledStates.h
new file mode 100644
index 00000000..ef146828
--- /dev/null
+++ b/common/rfb/ledStates.h
@@ -0,0 +1,30 @@
+/* Copyright 2016 Pierre Ossman 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 __RFB_LEDSTATES_H__
+#define __RFB_LEDSTATES_H__
+
+namespace rfb {
+
+ const unsigned int ledScrollLock = 1 << 0;
+ const unsigned int ledNumLock = 1 << 1;
+ const unsigned int ledCapsLock = 1 << 2;
+
+ const unsigned int ledUnknown = (unsigned int)-1;
+}
+
+#endif
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 60edb013..7e006815 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -40,5 +40,7 @@ if(WIN32)
target_link_libraries(fbperf msimg32)
endif()
if(APPLE)
- target_link_libraries(fbperf "-framework Cocoa" "-framework Carbon")
+ target_link_libraries(fbperf "-framework Cocoa")
+ target_link_libraries(fbperf "-framework Carbon")
+ target_link_libraries(fbperf "-framework IOKit")
endif()
diff --git a/vncviewer/CConn.cxx b/vncviewer/CConn.cxx
index 2e97ec28..07e7841c 100644
--- a/vncviewer/CConn.cxx
+++ b/vncviewer/CConn.cxx
@@ -92,6 +92,8 @@ CConn::CConn(const char* vncServerName, network::Socket* socket=NULL)
cp.supportsExtendedDesktopSize = true;
cp.supportsDesktopRename = true;
+ cp.supportsLEDState = true;
+
if (customCompressLevel)
cp.compressLevel = compressLevel;
else
@@ -503,6 +505,13 @@ void CConn::fence(rdr::U32 flags, unsigned len, const char data[])
}
}
+void CConn::setLEDState(unsigned int state)
+{
+ CConnection::setLEDState(state);
+
+ desktop->setLEDState(state);
+}
+
////////////////////// Internal methods //////////////////////
diff --git a/vncviewer/CConn.h b/vncviewer/CConn.h
index 93cc278f..426bd1e2 100644
--- a/vncviewer/CConn.h
+++ b/vncviewer/CConn.h
@@ -74,6 +74,8 @@ public:
void fence(rdr::U32 flags, unsigned len, const char data[]);
+ void setLEDState(unsigned int state);
+
private:
void resizeFramebuffer();
diff --git a/vncviewer/CMakeLists.txt b/vncviewer/CMakeLists.txt
index 2aecda23..56313e93 100644
--- a/vncviewer/CMakeLists.txt
+++ b/vncviewer/CMakeLists.txt
@@ -53,7 +53,9 @@ if(WIN32)
endif()
if(APPLE)
- target_link_libraries(vncviewer "-framework Cocoa" "-framework Carbon")
+ target_link_libraries(vncviewer "-framework Cocoa")
+ target_link_libraries(vncviewer "-framework Carbon")
+ target_link_libraries(vncviewer "-framework IOKit")
endif()
install(TARGETS vncviewer DESTINATION ${BIN_DIR})
diff --git a/vncviewer/DesktopWindow.cxx b/vncviewer/DesktopWindow.cxx
index 408efd19..3973cd66 100644
--- a/vncviewer/DesktopWindow.cxx
+++ b/vncviewer/DesktopWindow.cxx
@@ -395,6 +395,12 @@ void DesktopWindow::draw()
}
+void DesktopWindow::setLEDState(unsigned int state)
+{
+ viewport->setLEDState(state);
+}
+
+
void DesktopWindow::resize(int x, int y, int w, int h)
{
bool resizing;
diff --git a/vncviewer/DesktopWindow.h b/vncviewer/DesktopWindow.h
index 4224699c..f1bf312f 100644
--- a/vncviewer/DesktopWindow.h
+++ b/vncviewer/DesktopWindow.h
@@ -66,6 +66,9 @@ public:
void setCursor(int width, int height, const rfb::Point& hotspot,
const rdr::U8* data);
+ // Change client LED state
+ void setLEDState(unsigned int state);
+
// Fl_Window callback methods
void draw();
void resize(int x, int y, int w, int h);
diff --git a/vncviewer/Viewport.cxx b/vncviewer/Viewport.cxx
index 6a23526a..c0bbd0dd 100644
--- a/vncviewer/Viewport.cxx
+++ b/vncviewer/Viewport.cxx
@@ -28,6 +28,7 @@
#include <rfb/CMsgWriter.h>
#include <rfb/LogWriter.h>
#include <rfb/Exception.h>
+#include <rfb/ledStates.h>
// FLTK can pull in the X11 headers on some systems
#ifndef XK_VoidSymbol
@@ -41,6 +42,10 @@
#include <rfb/XF86keysym.h>
#endif
+#if ! (defined(WIN32) || defined(__APPLE__))
+#include <X11/XKBlib.h>
+#endif
+
#ifndef NoSymbol
#define NoSymbol 0
#endif
@@ -92,6 +97,11 @@ enum { ID_EXIT, ID_FULLSCREEN, ID_MINIMIZE, ID_RESIZE,
// Fake key presses use this value and above
static const int fakeKeyBase = 0x200;
+// Used to detect fake input (0xaa is not a real key)
+#ifdef WIN32
+static const WORD SCAN_FAKE = 0xaa;
+#endif
+
Viewport::Viewport(int w, int h, const rfb::PixelFormat& serverPF, CConn* cc_)
: Fl_Widget(0, 0, w, h), cc(cc_), frameBuffer(NULL),
lastPointerPos(0, 0), lastButtonMask(0),
@@ -218,6 +228,108 @@ void Viewport::setCursor(int width, int height, const Point& hotspot,
}
+void Viewport::setLEDState(unsigned int state)
+{
+ Fl_Widget *focus;
+
+ vlog.debug("Got server LED state: 0x%08x", state);
+
+ focus = Fl::grab();
+ if (!focus)
+ focus = Fl::focus();
+ if (!focus)
+ return;
+
+ if (focus != this)
+ return;
+
+#if defined(WIN32)
+ INPUT input[6];
+ UINT count;
+ UINT ret;
+
+ memset(input, 0, sizeof(input));
+ count = 0;
+
+ if (!!(state & ledCapsLock) != !!(GetKeyState(VK_CAPITAL) & 0x1)) {
+ input[count].type = input[count+1].type = INPUT_KEYBOARD;
+ input[count].ki.wVk = input[count+1].ki.wVk = VK_CAPITAL;
+ input[count].ki.wScan = input[count+1].ki.wScan = SCAN_FAKE;
+ input[count].ki.dwFlags = 0;
+ input[count+1].ki.dwFlags = KEYEVENTF_KEYUP;
+ count += 2;
+ }
+
+ if (!!(state & ledNumLock) != !!(GetKeyState(VK_NUMLOCK) & 0x1)) {
+ input[count].type = input[count+1].type = INPUT_KEYBOARD;
+ input[count].ki.wVk = input[count+1].ki.wVk = VK_NUMLOCK;
+ input[count].ki.wScan = input[count+1].ki.wScan = SCAN_FAKE;
+ input[count].ki.dwFlags = KEYEVENTF_EXTENDEDKEY;
+ input[count+1].ki.dwFlags = KEYEVENTF_KEYUP | KEYEVENTF_EXTENDEDKEY;
+ count += 2;
+ }
+
+ if (!!(state & ledScrollLock) != !!(GetKeyState(VK_SCROLL) & 0x1)) {
+ input[count].type = input[count+1].type = INPUT_KEYBOARD;
+ input[count].ki.wVk = input[count+1].ki.wVk = VK_SCROLL;
+ input[count].ki.wScan = input[count+1].ki.wScan = SCAN_FAKE;
+ input[count].ki.dwFlags = 0;
+ input[count+1].ki.dwFlags = KEYEVENTF_KEYUP;
+ count += 2;
+ }
+
+ if (count == 0)
+ return;
+
+ ret = SendInput(count, input, sizeof(*input));
+ if (ret < count)
+ vlog.error(_("Failed to update keyboard LED state: %lu"), GetLastError());
+#elif defined(__APPLE__)
+ int ret;
+
+ ret = cocoa_set_caps_lock_state(state & ledCapsLock);
+ if (ret != 0) {
+ vlog.error(_("Failed to update keyboard LED state: %d"), ret);
+ return;
+ }
+
+ ret = cocoa_set_num_lock_state(state & ledNumLock);
+ if (ret != 0) {
+ vlog.error(_("Failed to update keyboard LED state: %d"), ret);
+ return;
+ }
+
+ // No support for Scroll Lock //
+
+#else
+ unsigned int affect, values;
+ unsigned int mask;
+
+ Bool ret;
+
+ affect = values = 0;
+
+ affect |= LockMask;
+ if (state & ledCapsLock)
+ values |= LockMask;
+
+ mask = getModifierMask(XK_Num_Lock);
+ affect |= mask;
+ if (state & ledNumLock)
+ values |= mask;
+
+ mask = getModifierMask(XK_Scroll_Lock);
+ affect |= mask;
+ if (state & ledScrollLock)
+ values |= mask;
+
+ ret = XkbLockModifiers(fl_display, XkbUseCoreKbd, affect, values);
+ if (!ret)
+ vlog.error(_("Failed to update keyboard LED state"));
+#endif
+}
+
+
void Viewport::draw(Surface* dst)
{
int X, Y, W, H;
@@ -352,6 +464,55 @@ int Viewport::handle(int event)
return Fl_Widget::handle(event);
}
+
+#if ! (defined(WIN32) || defined(__APPLE__))
+unsigned int Viewport::getModifierMask(unsigned int keysym)
+{
+ XkbDescPtr xkb;
+ unsigned int mask, keycode;
+ XkbAction *act;
+
+ mask = 0;
+
+ xkb = XkbGetMap(fl_display, XkbAllComponentsMask, XkbUseCoreKbd);
+ if (xkb == NULL)
+ return 0;
+
+ for (keycode = xkb->min_key_code; keycode <= xkb->max_key_code; keycode++) {
+ unsigned int state_out;
+ KeySym ks;
+
+ XkbTranslateKeyCode(xkb, keycode, 0, &state_out, &ks);
+ if (ks == NoSymbol)
+ continue;
+
+ if (ks == keysym)
+ break;
+ }
+
+ // KeySym not mapped?
+ if (keycode > xkb->max_key_code)
+ goto out;
+
+ act = XkbKeyAction(xkb, keycode, 0);
+ if (act == NULL)
+ goto out;
+ if (act->type != XkbSA_LockMods)
+ goto out;
+
+ if (act->mods.flags & XkbSA_UseModMapMods)
+ mask = xkb->map->modmap[keycode];
+ else
+ mask = act->mods.mask;
+
+out:
+ XkbFreeKeyboard(xkb, XkbAllComponentsMask, True);
+
+ return mask;
+}
+#endif
+
+
void Viewport::handleClipboardChange(int source, void *data)
{
Viewport *self = (Viewport *)data;
@@ -577,6 +738,11 @@ int Viewport::handleSystemEvent(void *event, void *data)
keyCode = ((msg->lParam >> 16) & 0xff);
+ if (keyCode == SCAN_FAKE) {
+ vlog.debug("Ignoring fake key press (virtual key 0x%02x)", vKey);
+ return 1;
+ }
+
// Windows sets the scan code to 0x00 for multimedia keys, so we
// have to do a reverse lookup based on the vKey.
if (keyCode == 0x00) {
@@ -620,6 +786,12 @@ int Viewport::handleSystemEvent(void *event, void *data)
isExtended = (msg->lParam & (1 << 24)) != 0;
keyCode = ((msg->lParam >> 16) & 0xff);
+
+ if (keyCode == SCAN_FAKE) {
+ vlog.debug("Ignoring fake key release (virtual key 0x%02x)", vKey);
+ return 1;
+ }
+
if (keyCode == 0x00)
keyCode = MapVirtualKey(vKey, MAPVK_VK_TO_VSC);
if (isExtended)
diff --git a/vncviewer/Viewport.h b/vncviewer/Viewport.h
index 6f0710d3..652feb45 100644
--- a/vncviewer/Viewport.h
+++ b/vncviewer/Viewport.h
@@ -47,6 +47,9 @@ public:
void setCursor(int width, int height, const rfb::Point& hotspot,
const rdr::U8* data);
+ // Change client LED state
+ void setLEDState(unsigned int state);
+
void draw(Surface* dst);
// Fl_Widget callback methods
@@ -59,6 +62,8 @@ public:
private:
+ unsigned int getModifierMask(unsigned int keysym);
+
static void handleClipboardChange(int source, void *data);
void handlePointerEvent(const rfb::Point& pos, int buttonMask);
diff --git a/vncviewer/cocoa.h b/vncviewer/cocoa.h
index 0c3ac82f..b5d5f763 100644
--- a/vncviewer/cocoa.h
+++ b/vncviewer/cocoa.h
@@ -33,4 +33,7 @@ int cocoa_is_key_press(const void *event);
int cocoa_event_keycode(const void *event);
int cocoa_event_keysym(const void *event);
+int cocoa_set_caps_lock_state(bool on);
+int cocoa_set_num_lock_state(bool on);
+
#endif
diff --git a/vncviewer/cocoa.mm b/vncviewer/cocoa.mm
index 6e464fa4..6483291c 100644
--- a/vncviewer/cocoa.mm
+++ b/vncviewer/cocoa.mm
@@ -27,6 +27,9 @@
#import <Cocoa/Cocoa.h>
#import <Carbon/Carbon.h>
+#include <IOKit/hidsystem/IOHIDLib.h>
+#include <IOKit/hidsystem/IOHIDParameter.h>
+
#define XK_LATIN1
#define XK_MISCELLANY
#define XK_XKB_KEYS
@@ -406,3 +409,50 @@ int cocoa_event_keysym(const void *event)
return ucs2keysym([chars characterAtIndex:0]);
}
+
+static int cocoa_open_hid(io_connect_t *ioc)
+{
+ kern_return_t ret;
+ io_service_t ios;
+ CFMutableDictionaryRef mdict;
+
+ mdict = IOServiceMatching(kIOHIDSystemClass);
+ ios = IOServiceGetMatchingService(kIOMasterPortDefault,
+ (CFDictionaryRef) mdict);
+ if (!ios)
+ return KERN_FAILURE;
+
+ ret = IOServiceOpen(ios, mach_task_self(), kIOHIDParamConnectType, ioc);
+ IOObjectRelease(ios);
+ if (ret != KERN_SUCCESS)
+ return ret;
+
+ return KERN_SUCCESS;
+}
+
+static int cocoa_set_modifier_lock_state(int modifier, bool on)
+{
+ kern_return_t ret;
+ io_connect_t ioc;
+
+ ret = cocoa_open_hid(&ioc);
+ if (ret != KERN_SUCCESS)
+ return ret;
+
+ ret = IOHIDSetModifierLockState(ioc, modifier, on);
+ IOServiceClose(ioc);
+ if (ret != KERN_SUCCESS)
+ return ret;
+
+ return KERN_SUCCESS;
+}
+
+int cocoa_set_caps_lock_state(bool on)
+{
+ return cocoa_set_modifier_lock_state(kIOHIDCapsLockState, on);
+}
+
+int cocoa_set_num_lock_state(bool on)
+{
+ return cocoa_set_modifier_lock_state(kIOHIDNumLockState, on);
+}