cp.supportsContinuousUpdates = true;
}
+void CMsgHandler::supportsQEMUKeyEvent()
+{
+ cp.supportsQEMUKeyEvent = true;
+}
+
void CMsgHandler::framebufferUpdateStart()
{
}
virtual void setName(const char* name);
virtual void fence(rdr::U32 flags, unsigned len, const char data[]);
virtual void endOfContinuousUpdates();
+ virtual void supportsQEMUKeyEvent();
virtual void serverInit() = 0;
virtual void readAndDecodeRect(const Rect& r, int encoding,
break;
case pseudoEncodingLEDState:
readLEDState();
+ case pseudoEncodingQEMUKeyEvent:
+ handler->supportsQEMUKeyEvent();
break;
default:
readRect(Rect(x, y, x+w, y+h), encoding);
#include <rfb/msgTypes.h>
#include <rfb/fenceTypes.h>
#include <rfb/encodings.h>
+#include <rfb/qemuTypes.h>
#include <rfb/Exception.h>
#include <rfb/PixelFormat.h>
#include <rfb/Rect.h>
encodings[nEncodings++] = pseudoEncodingLastRect;
encodings[nEncodings++] = pseudoEncodingContinuousUpdates;
encodings[nEncodings++] = pseudoEncodingFence;
+ encodings[nEncodings++] = pseudoEncodingQEMUKeyEvent;
if (Decoder::supported(preferredEncoding)) {
encodings[nEncodings++] = preferredEncoding;
endMsg();
}
-void CMsgWriter::keyEvent(rdr::U32 key, bool down)
+void CMsgWriter::keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down)
{
- startMsg(msgTypeKeyEvent);
- os->writeU8(down);
- os->pad(2);
- os->writeU32(key);
- endMsg();
+ if (!cp->supportsQEMUKeyEvent || !keycode) {
+ /* This event isn't meaningful without a valid keysym */
+ if (!keysym)
+ return;
+
+ startMsg(msgTypeKeyEvent);
+ os->writeU8(down);
+ os->pad(2);
+ os->writeU32(keysym);
+ endMsg();
+ } else {
+ startMsg(msgTypeQEMUClientMessage);
+ os->writeU8(qemuExtendedKeyEvent);
+ os->writeU16(down);
+ os->writeU32(keysym);
+ os->writeU32(keycode);
+ endMsg();
+ }
}
// InputHandler implementation
- virtual void keyEvent(rdr::U32 key, bool down);
+ virtual void keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down);
virtual void pointerEvent(const Point& pos, int buttonMask);
virtual void clientCutText(const char* str, rdr::U32 len);
supportsLocalCursorWithAlpha(false),
supportsDesktopResize(false), supportsExtendedDesktopSize(false),
supportsDesktopRename(false), supportsLastRect(false),
- supportsLEDState(false), supportsSetDesktopSize(false),
- supportsFence(false), supportsContinuousUpdates(false),
+ supportsLEDState(false), supportsQEMUKeyEvent(false),
+ supportsSetDesktopSize(false), supportsFence(false),
+ supportsContinuousUpdates(false),
compressLevel(2), qualityLevel(-1), fineQualityLevel(-1),
subsampling(subsampleUndefined), name_(0), verStrPos(0),
ledState_(ledUnknown)
supportsExtendedDesktopSize = false;
supportsLocalXCursor = false;
supportsLastRect = false;
+ supportsQEMUKeyEvent = false;
compressLevel = -1;
qualityLevel = -1;
fineQualityLevel = -1;
break;
case pseudoEncodingLEDState:
supportsLEDState = true;
+ case pseudoEncodingQEMUKeyEvent:
+ supportsQEMUKeyEvent = true;
break;
case pseudoEncodingFence:
supportsFence = true;
bool supportsDesktopRename;
bool supportsLastRect;
bool supportsLEDState;
+ bool supportsQEMUKeyEvent;
bool supportsSetDesktopSize;
bool supportsFence;
class InputHandler {
public:
virtual ~InputHandler() {}
- virtual void keyEvent(rdr::U32 key, bool down) {}
+ virtual void keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down) {}
virtual void pointerEvent(const Point& pos, int buttonMask) {}
virtual void clientCutText(const char* str, int len) {}
};
SMsgHandler::setEncodings(nEncodings, encodings);
}
+void SConnection::supportsQEMUKeyEvent()
+{
+ writer()->writeQEMUKeyEvent();
+}
+
void SConnection::versionReceived()
{
}
virtual void setEncodings(int nEncodings, const rdr::S32* encodings);
+ virtual void supportsQEMUKeyEvent();
// Methods to be overridden in a derived class
void SMsgHandler::setEncodings(int nEncodings, const rdr::S32* encodings)
{
- bool firstFence, firstContinuousUpdates, firstLEDState;
+ bool firstFence, firstContinuousUpdates, firstLEDState,
+ firstQEMUKeyEvent;
firstFence = !cp.supportsFence;
firstContinuousUpdates = !cp.supportsContinuousUpdates;
firstLEDState = !cp.supportsLEDState;
+ firstQEMUKeyEvent = !cp.supportsQEMUKeyEvent;
cp.setEncodings(nEncodings, encodings);
supportsContinuousUpdates();
if (cp.supportsLEDState && firstLEDState)
supportsLEDState();
+ if (cp.supportsQEMUKeyEvent && firstQEMUKeyEvent)
+ supportsQEMUKeyEvent();
}
void SMsgHandler::supportsLocalCursor()
{
}
+void SMsgHandler::supportsQEMUKeyEvent()
+{
+}
+
void SMsgHandler::setDesktopSize(int fb_width, int fb_height,
const ScreenSet& layout)
{
// server state.
virtual void supportsLEDState();
+ // supportsQEMUKeyEvent() is called the first time we detect that the
+ // client wants the QEMU Extended Key Event extension. The default
+ // handler will send a pseudo-rect back, signalling server support.
+ virtual void supportsQEMUKeyEvent();
+
ConnParams cp;
};
}
#include <stdio.h>
#include <rdr/InStream.h>
#include <rfb/msgTypes.h>
+#include <rfb/qemuTypes.h>
#include <rfb/Exception.h>
#include <rfb/util.h>
#include <rfb/SMsgHandler.h>
case msgTypeClientCutText:
readClientCutText();
break;
+ case msgTypeQEMUClientMessage:
+ readQEMUMessage();
+ break;
default:
fprintf(stderr, "unknown message type %d\n", msgType);
throw Exception("unknown message type");
bool down = is->readU8();
is->skip(2);
rdr::U32 key = is->readU32();
- handler->keyEvent(key, down);
+ handler->keyEvent(key, 0, down);
}
void SMsgReader::readPointerEvent()
handler->clientCutText(ca.buf, len);
}
+void SMsgReader::readQEMUMessage()
+{
+ int subType = is->readU8();
+ switch (subType) {
+ case qemuExtendedKeyEvent:
+ readQEMUKeyEvent();
+ break;
+ default:
+ throw Exception("unknown QEMU submessage type %d", subType);
+ }
+}
+
+void SMsgReader::readQEMUKeyEvent()
+{
+ bool down = is->readU16();
+ rdr::U32 keysym = is->readU32();
+ rdr::U32 keycode = is->readU32();
+ if (!keycode) {
+ vlog.error("Key event without keycode - ignoring");
+ return;
+ }
+ handler->keyEvent(keysym, keycode, down);
+}
void readPointerEvent();
void readClientCutText();
+ void readQEMUMessage();
+ void readQEMUKeyEvent();
+
SMsgHandler* handler;
rdr::InStream* is;
};
needSetDesktopSize(false), needExtendedDesktopSize(false),
needSetDesktopName(false), needSetCursor(false),
needSetXCursor(false), needSetCursorWithAlpha(false),
- needLEDState(false)
+ needLEDState(false), needQEMUKeyEvent(false)
{
}
return true;
}
+bool SMsgWriter::writeQEMUKeyEvent()
+{
+ if (!cp->supportsQEMUKeyEvent)
+ return false;
+
+ needQEMUKeyEvent = true;
+
+ return true;
+}
+
bool SMsgWriter::needFakeUpdate()
{
if (needSetDesktopName)
return true;
if (needLEDState)
return true;
+ if (needQEMUKeyEvent)
+ return true;
if (needNoDataUpdate())
return true;
nRects++;
if (needLEDState)
nRects++;
+ if (needQEMUKeyEvent)
+ nRects++;
}
os->writeU16(nRects);
writeLEDStateRect(cp->ledState());
needLEDState = false;
}
+
+ if (needQEMUKeyEvent) {
+ writeQEMUKeyEventRect();
+ needQEMUKeyEvent = false;
+ }
}
void SMsgWriter::writeNoDataRects()
os->writeU32(pseudoEncodingLEDState);
os->writeU8(state);
}
+
+void SMsgWriter::writeQEMUKeyEventRect()
+{
+ if (!cp->supportsQEMUKeyEvent)
+ throw Exception("Client does not support QEMU extended key events");
+ if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader)
+ throw Exception("SMsgWriter::writeQEMUKeyEventRect: nRects out of sync");
+
+ os->writeS16(0);
+ os->writeS16(0);
+ os->writeU16(0);
+ os->writeU16(0);
+ os->writeU32(pseudoEncodingQEMUKeyEvent);
+}
// Same for LED state message
bool writeLEDState();
+ // And QEMU keyboard event handshake
+ bool writeQEMUKeyEvent();
+
// needFakeUpdate() returns true when an immediate update is needed in
// order to flush out pseudo-rectangles to the client.
bool needFakeUpdate();
int hotspotX, int hotspotY,
const rdr::U8* data);
void writeLEDStateRect(rdr::U8 state);
+ void writeQEMUKeyEventRect();
ConnParams* cp;
rdr::OutStream* os;
bool needSetXCursor;
bool needSetCursorWithAlpha;
bool needLEDState;
+ bool needQEMUKeyEvent;
typedef struct {
rdr::U16 reason, result;
std::set<rdr::U32>::iterator i;
for (i=pressedKeys.begin(); i!=pressedKeys.end(); i++) {
vlog.debug("Releasing key 0x%x on client disconnect", *i);
- server->desktop->keyEvent(*i, false);
+ server->desktop->keyEvent(*i, 0, false);
}
if (server->pointerClient == this)
server->pointerClient = 0;
~VNCSConnectionSTShiftPresser() {
if (pressed) {
vlog.debug("Releasing fake Shift_L");
- desktop->keyEvent(XK_Shift_L, false);
+ desktop->keyEvent(XK_Shift_L, 0, false);
}
}
void press() {
vlog.debug("Pressing fake Shift_L");
- desktop->keyEvent(XK_Shift_L, true);
+ desktop->keyEvent(XK_Shift_L, 0, true);
pressed = true;
}
SDesktop* desktop;
// keyEvent() - record in the pressedKeys which keys were pressed. Allow
// multiple down events (for autorepeat), but only allow a single up event.
-void VNCSConnectionST::keyEvent(rdr::U32 key, bool down) {
+void VNCSConnectionST::keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down) {
lastEventTime = time(0);
server->lastUserInputTime = lastEventTime;
if (!(accessRights & AccessKeyEvents)) return;
if (!rfb::Server::acceptKeyEvents) return;
if (down)
- vlog.debug("Key pressed: 0x%x", key);
+ vlog.debug("Key pressed: 0x%x / 0x%x", keysym, keycode);
else
- vlog.debug("Key released: 0x%x", key);
+ vlog.debug("Key released: 0x%x / 0x%x", keysym, keycode);
// Remap the key if required
if (server->keyRemapper) {
rdr::U32 newkey;
- newkey = server->keyRemapper->remapKey(key);
- if (newkey != key) {
+ newkey = server->keyRemapper->remapKey(keysym);
+ if (newkey != keysym) {
vlog.debug("Key remapped to 0x%x", newkey);
- key = newkey;
+ keysym = newkey;
}
}
// Avoid lock keys if we don't know the server state
if ((server->ledState == ledUnknown) &&
- ((key == XK_Caps_Lock) ||
- (key == XK_Num_Lock) ||
- (key == XK_Scroll_Lock))) {
+ ((keysym == XK_Caps_Lock) ||
+ (keysym == XK_Num_Lock) ||
+ (keysym == XK_Scroll_Lock))) {
vlog.debug("Ignoring lock key (e.g. caps lock)");
return;
}
if (!cp.supportsLEDState) {
// Always ignore ScrollLock as we don't have a heuristic
// for that
- if (key == XK_Scroll_Lock) {
+ if (keysym == XK_Scroll_Lock) {
vlog.debug("Ignoring lock key (e.g. caps lock)");
return;
}
// CapsLock synchronisation heuristic
// (this assumes standard interaction between CapsLock the Shift
// keys and normal characters)
- if (((key >= XK_A) && (key <= XK_Z)) ||
- ((key >= XK_a) && (key <= XK_z))) {
+ if (((keysym >= XK_A) && (keysym <= XK_Z)) ||
+ ((keysym >= XK_a) && (keysym <= XK_z))) {
bool uppercase, shift, lock;
- uppercase = (key >= XK_A) && (key <= XK_Z);
+ uppercase = (keysym >= XK_A) && (keysym <= XK_Z);
shift = pressedKeys.find(XK_Shift_L) != pressedKeys.end() ||
pressedKeys.find(XK_Shift_R) != pressedKeys.end();
lock = server->ledState & ledCapsLock;
if (lock == (uppercase == shift)) {
vlog.debug("Inserting fake CapsLock to get in sync with client");
- server->desktop->keyEvent(XK_Caps_Lock, true);
- server->desktop->keyEvent(XK_Caps_Lock, false);
+ server->desktop->keyEvent(XK_Caps_Lock, 0, true);
+ server->desktop->keyEvent(XK_Caps_Lock, 0, false);
}
}
// NumLock synchronisation heuristic
// (this is more cautious because of the differences between Unix,
// Windows and macOS)
- if (((key >= XK_KP_Home) && (key <= XK_KP_Delete)) ||
- ((key >= XK_KP_0) && (key <= XK_KP_9)) ||
- (key == XK_KP_Separator) || (key == XK_KP_Decimal)) {
+ if (((keysym >= XK_KP_Home) && (keysym <= XK_KP_Delete)) ||
+ ((keysym >= XK_KP_0) && (keysym <= XK_KP_9)) ||
+ (keysym == XK_KP_Separator) || (keysym == XK_KP_Decimal)) {
bool number, shift, lock;
- number = ((key >= XK_KP_0) && (key <= XK_KP_9)) ||
- (key == XK_KP_Separator) || (key == XK_KP_Decimal);
+ number = ((keysym >= XK_KP_0) && (keysym <= XK_KP_9)) ||
+ (keysym == XK_KP_Separator) || (keysym == XK_KP_Decimal);
shift = pressedKeys.find(XK_Shift_L) != pressedKeys.end() ||
pressedKeys.find(XK_Shift_R) != pressedKeys.end();
lock = server->ledState & ledNumLock;
//
} else if (lock == (number == shift)) {
vlog.debug("Inserting fake NumLock to get in sync with client");
- server->desktop->keyEvent(XK_Num_Lock, true);
- server->desktop->keyEvent(XK_Num_Lock, false);
+ server->desktop->keyEvent(XK_Num_Lock, 0, true);
+ server->desktop->keyEvent(XK_Num_Lock, 0, false);
}
}
}
// Turn ISO_Left_Tab into shifted Tab.
VNCSConnectionSTShiftPresser shiftPresser(server->desktop);
- if (key == XK_ISO_Left_Tab) {
+ if (keysym == XK_ISO_Left_Tab) {
if (pressedKeys.find(XK_Shift_L) == pressedKeys.end() &&
pressedKeys.find(XK_Shift_R) == pressedKeys.end())
shiftPresser.press();
- key = XK_Tab;
+ keysym = XK_Tab;
}
if (down) {
- pressedKeys.insert(key);
+ pressedKeys.insert(keysym);
} else {
- if (!pressedKeys.erase(key)) return;
+ if (!pressedKeys.erase(keysym))
+ return;
}
- server->desktop->keyEvent(key, down);
+ server->desktop->keyEvent(keysym, keycode, down);
}
void VNCSConnectionST::clientCutText(const char* str, int len)
virtual void clientInit(bool shared);
virtual void setPixelFormat(const PixelFormat& pf);
virtual void pointerEvent(const Point& pos, int buttonMask);
- virtual void keyEvent(rdr::U32 key, bool down);
+ virtual void keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down);
virtual void clientCutText(const char* str, int len);
virtual void framebufferUpdateRequest(const Rect& r, bool incremental);
virtual void setDesktopSize(int fb_width, int fb_height,
const int pseudoEncodingFence = -312;
const int pseudoEncodingContinuousUpdates = -313;
const int pseudoEncodingCursorWithAlpha = -314;
+ const int pseudoEncodingQEMUKeyEvent = -258;
// TightVNC-specific
const int pseudoEncodingLastRect = -224;
const int msgTypeClientFence = 248;
const int msgTypeSetDesktopSize = 251;
+
+ const int msgTypeQEMUClientMessage = 255;
}
#endif
--- /dev/null
+/* Copyright 2017 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_QEMUTYPES_H__
+#define __RFB_QEMUTYPES_H__
+
+namespace rfb {
+ const int qemuExtendedKeyEvent = 0;
+ const int qemuAudio = 1;
+}
+#endif
#endif
}
- virtual void keyEvent(rdr::U32 key, bool down) {
+ virtual void keyEvent(rdr::U32 keysym, rdr::U32 xtcode, bool down) {
#ifdef HAVE_XTEST
if (!haveXtest) return;
- int keycode = XKeysymToKeycode(dpy, key);
+ int keycode = XKeysymToKeycode(dpy, keysym);
if (keycode)
XTestFakeKeyEvent(dpy, keycode, down, CurrentTime);
#endif
}
}
-void XserverDesktop::keyEvent(rdr::U32 keysym, bool down)
+void XserverDesktop::keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down)
{
vncKeyboardEvent(keysym, down);
}
// rfb::SDesktop callbacks
virtual void pointerEvent(const rfb::Point& pos, int buttonMask);
- virtual void keyEvent(rdr::U32 key, bool down);
+ virtual void keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down);
virtual void clientCutText(const char* str, int len);
virtual rfb::Point getFbSize() { return rfb::Point(width(), height()); }
virtual unsigned int setScreenLayout(int fb_width, int fb_height,
if (ctrlPressed && altPressed) {
vlog.debug("Faking release of AltGr (Ctrl_L+Alt_R)");
try {
- cc->writer()->keyEvent(XK_Control_L, false);
- cc->writer()->keyEvent(XK_Alt_R, false);
+ cc->writer()->keyEvent(XK_Control_L, 0, false);
+ cc->writer()->keyEvent(XK_Alt_R, 0, false);
} catch (rdr::Exception& e) {
vlog.error("%s", e.str());
exit_vncviewer(e.str());
#endif
try {
- cc->writer()->keyEvent(keySym, true);
+ cc->writer()->keyEvent(keySym, 0, true);
} catch (rdr::Exception& e) {
vlog.error("%s", e.str());
exit_vncviewer(e.str());
if (ctrlPressed && altPressed) {
vlog.debug("Restoring AltGr state");
try {
- cc->writer()->keyEvent(XK_Control_L, true);
- cc->writer()->keyEvent(XK_Alt_R, true);
+ cc->writer()->keyEvent(XK_Control_L, 0, true);
+ cc->writer()->keyEvent(XK_Alt_R, 0, true);
} catch (rdr::Exception& e) {
vlog.error("%s", e.str());
exit_vncviewer(e.str());
#endif
try {
- cc->writer()->keyEvent(iter->second, false);
+ cc->writer()->keyEvent(iter->second, 0, false);
} catch (rdr::Exception& e) {
vlog.error("%s", e.str());
exit_vncviewer(e.str());
}
}
-void SDisplay::keyEvent(rdr::U32 key, bool down) {
+void SDisplay::keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down) {
// - Check that the SDesktop doesn't need restarting
if (isRestartRequired())
restartCore();
if (kbd)
- kbd->keyEvent(key, down);
+ kbd->keyEvent(keysym, keycode, down);
}
bool SDisplay::checkLedState() {
virtual void start(VNCServer* vs);
virtual void stop();
virtual void pointerEvent(const Point& pos, int buttonmask);
- virtual void keyEvent(rdr::U32 key, bool down);
+ virtual void keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down);
virtual void clientCutText(const char* str, int len);
virtual Point getFbSize();
}
-void win32::SKeyboard::keyEvent(rdr::U32 keysym, bool down)
+void win32::SKeyboard::keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down)
{
for (unsigned int i = 0; i < sizeof(keysymToAscii) / sizeof(keysymToAscii_t); i++) {
if (keysymToAscii[i].keysym == keysym) {
class SKeyboard {
public:
SKeyboard();
- void keyEvent(rdr::U32 key, bool down);
+ void keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down);
static BoolParameter deadKeyAware;
private:
std::map<rdr::U32,rdr::U8> vkMap;