@@ -82,3 +82,8 @@ void CMsgHandler::framebufferUpdateStart() | |||
void CMsgHandler::framebufferUpdateEnd() | |||
{ | |||
} | |||
void CMsgHandler::setLEDState(unsigned int state) | |||
{ | |||
cp.setLEDState(state); | |||
} |
@@ -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; | |||
}; | |||
} |
@@ -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); | |||
} |
@@ -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; |
@@ -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; |
@@ -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; | |||
} |
@@ -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 |
@@ -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; |
@@ -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 |
@@ -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() |
@@ -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 ////////////////////// | |||
@@ -74,6 +74,8 @@ public: | |||
void fence(rdr::U32 flags, unsigned len, const char data[]); | |||
void setLEDState(unsigned int state); | |||
private: | |||
void resizeFramebuffer(); |
@@ -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}) |
@@ -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; |
@@ -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); |
@@ -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) |
@@ -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); |
@@ -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 |
@@ -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); | |||
} |