Skip to content

Commit

Permalink
Viewer: dead key support
Browse files Browse the repository at this point in the history
Dead keys and AltGr-modified symbols should now work properly with the
Windows (native) TurboVNC Viewer.  Dead key support in the Java viewer
is still spotty for the following reasons:

* Java 6 doesn't use the proper key code when a "modified" dead key is
pressed (such as Shift-accent to produce a dead grave accent on a
Swedish keyboard, or Shift-accent to produce a dead tilde on a U.S.
International keyboard.)  Instead, the key code of the unmodified dead
key is communicated to the application.  No way to work around this.
Use Java 8.

* On Windows (under Java 8), Java uses the unmodified dead key code for
the 2nd and subsequent even-numbered presses/releases of a modified dead
key.  Thus, pressing a modified dead key twice doesn't produce the
expected behavior.  For instance, pressing dead tilde twice should
produce a tilde, but instead it does nothing.  No easy way to work
around this in the code, but pressing the modified dead key + space
works properly as an end-user workaround, since that also produces the
"live" key symbol corresponding to the dead key.

* On OS X, Java doesn't generally communicate the same key code when a
dead key is released as it does when the key is pressed.  On Linux, Java
doesn't generally communicate a KeyPress event for dead keys at all (it
only communicates a KeyRelease event.)  No known way to work around
these issues at the moment.
  • Loading branch information
dcommander committed Feb 1, 2016
1 parent 7de6f7a commit 00c436d
Show file tree
Hide file tree
Showing 8 changed files with 1,112 additions and 81 deletions.
14 changes: 14 additions & 0 deletions ChangeLog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,20 @@ the feature may benefit other use cases as well.
Added a new parameter to the Java TurboVNC Viewer (NoReconnect) that can
optionally be used to revert the behavior introduced in 2.0 beta1[17].
-------------------------------------------------------------------------------
[10]
"Dead" keys, which are used to add accents and other diacritics to subsequent
alphabetic keystrokes, should now work properly when using the Windows TurboVNC
Viewer. Additionally, certain AltGr-modified symbols (such as the Euro sign on
European keyboards) did not previously work with the Windows TurboVNC Viewer.
That has been fixed.

Furthermore, dead keys should now mostly work when using the Java TurboVNC
Viewer on Windows under Java 8 and later. Due to irregularities in how Java
translates dead keys into events on other platforms (for instance, on OS X,
Java uses a different key code for the press and release events of a particular
dead key, and on Linux, Java only communicates release events for dead keys),
dead key support on non-Windows platforms is still incomplete.
-------------------------------------------------------------------------------


===============================================================================
Expand Down
20 changes: 19 additions & 1 deletion java/com/turbovnc/rfb/Keysyms.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
* Copyright (C) 2012-2013 D. R. Commander. All Rights Reserved.
* Copyright (C) 2012-2013, 2016 D. R. Commander. All Rights Reserved.
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -28,6 +28,24 @@
public class Keysyms {

public static final int ISO_Level3_Shift = 0xFE03;

public static final int Dead_Grave = 0xFE50;
public static final int Dead_Acute = 0xFE51;
public static final int Dead_Circumflex = 0xFE52;
public static final int Dead_Tilde = 0xFE53;
public static final int Dead_Macron = 0xFE54;
public static final int Dead_Breve = 0xFE55;
public static final int Dead_AboveDot = 0xFE56;
public static final int Dead_Diaeresis = 0xFE57;
public static final int Dead_AboveRing = 0xFE58;
public static final int Dead_DoubleAcute = 0xFE59;
public static final int Dead_Caron = 0xFE5A;
public static final int Dead_Cedilla = 0xFE5B;
public static final int Dead_Ogonek = 0xFE5C;
public static final int Dead_Iota = 0xFE5D;
public static final int Dead_Voiced_Sound = 0xFE5E;
public static final int Dead_Semivoiced_Sound = 0xFE5F;

public static final int BackSpace = 0xFF08;
public static final int Tab = 0xFF09;
public static final int Linefeed = 0xFF0A;
Expand Down
57 changes: 55 additions & 2 deletions java/com/turbovnc/vncviewer/CConn.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
* Copyright 2009-2011 Pierre Ossman <ossman@cendio.se> for Cendio AB
* Copyright (C) 2011-2015 D. R. Commander. All Rights Reserved.
* Copyright (C) 2011-2016 D. R. Commander. All Rights Reserved.
* Copyright (C) 2011-2013, 2015 Brian P. Hinz
*
* This is free software; you can redistribute it and/or modify
Expand Down Expand Up @@ -1623,6 +1623,8 @@ public void writeKeyEvent(int keysym, boolean down) {
if (state() != RFBSTATE_NORMAL || shuttingDown || benchmark)
return;
try {
vlog.debug("writeKeyEvent " + String.format("0x%04x", keysym) + " " +
(down ? "PRESS" : "release"));
writer().writeKeyEvent(keysym, down);
} catch (Exception e) {
if (!shuttingDown) {
Expand Down Expand Up @@ -1851,7 +1853,58 @@ else if (keycode == KeyEvent.VK_QUOTE)
else if (keycode >= 32 && keycode <= 126)
key = keycode;
}
keysym = UnicodeToKeysym.translate(key);
switch (keycode) {
case KeyEvent.VK_DEAD_ABOVEDOT:
keysym = Keysyms.Dead_AboveDot;
break;
case KeyEvent.VK_DEAD_ABOVERING:
keysym = Keysyms.Dead_AboveRing;
break;
case KeyEvent.VK_DEAD_ACUTE:
keysym = Keysyms.Dead_Acute;
break;
case KeyEvent.VK_DEAD_BREVE:
keysym = Keysyms.Dead_Breve;
break;
case KeyEvent.VK_DEAD_CARON:
keysym = Keysyms.Dead_Caron;
break;
case KeyEvent.VK_DEAD_CEDILLA:
keysym = Keysyms.Dead_Cedilla;
break;
case KeyEvent.VK_DEAD_CIRCUMFLEX:
keysym = Keysyms.Dead_Circumflex;
break;
case KeyEvent.VK_DEAD_DIAERESIS:
keysym = Keysyms.Dead_Diaeresis;
break;
case KeyEvent.VK_DEAD_DOUBLEACUTE:
keysym = Keysyms.Dead_DoubleAcute;
break;
case KeyEvent.VK_DEAD_GRAVE:
keysym = Keysyms.Dead_Grave;
break;
case KeyEvent.VK_DEAD_IOTA:
keysym = Keysyms.Dead_Iota;
break;
case KeyEvent.VK_DEAD_MACRON:
keysym = Keysyms.Dead_Macron;
break;
case KeyEvent.VK_DEAD_OGONEK:
keysym = Keysyms.Dead_Ogonek;
break;
case KeyEvent.VK_DEAD_SEMIVOICED_SOUND:
keysym = Keysyms.Dead_Semivoiced_Sound;
break;
case KeyEvent.VK_DEAD_TILDE:
keysym = Keysyms.Dead_Tilde;
break;
case KeyEvent.VK_DEAD_VOICED_SOUND:
keysym = Keysyms.Dead_Voiced_Sound;
break;
default:
keysym = UnicodeToKeysym.translate(key);
}
if (keysym == -1)
return;
}
Expand Down
1 change: 1 addition & 0 deletions win/vncviewer/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ set(VNCVIEWER_SOURCES
FileTransferItemInfo.cpp
HotKeys.cpp
KeyMap.cpp
keysym2ucs.c
Log.cpp
LoginAuthDialog.cpp
LowLevelHook.cpp
Expand Down
144 changes: 67 additions & 77 deletions win/vncviewer/KeyMap.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
// Copyright (C) 2013 D. R. Commander. All Rights Reserved.
// Copyright (C) 2013, 2016 D. R. Commander. All Rights Reserved.
// Copyright 2014 Pierre Ossman <ossman@cendio.se> for Cendio AB
//
// This file is part of the VNC system.
//
Expand All @@ -26,6 +27,7 @@
#include "stdhdrs.h"
#include "KeyMap.h"
#include "vncviewer.h"
#include "keysym2ucs.h"


typedef struct vncKeymapping_struct {
Expand Down Expand Up @@ -151,10 +153,10 @@ KeyActionSpec KeyMap::PCtoX(UINT virtkey, DWORD keyData)
kas.releaseModifiers = 0;

bool extended = ((keyData & 0x1000000) != 0);
vnclog.Print(8, " keyData %04x ", keyData);
vnclog.Print(8, "VK %.2x (%.8x)", virtkey, keyData);

if (extended) {
vnclog.Print(8, " (extended) ");
vnclog.Print(8, " [ext]");
switch (virtkey) {
case VK_MENU :
virtkey = VK_RMENU; break;
Expand Down Expand Up @@ -207,105 +209,93 @@ KeyActionSpec KeyMap::PCtoX(UINT virtkey, DWORD keyData)
if ((key == XK_Alt_L || key == XK_Alt_R) &&
GetKeyState(VK_SCROLL)) {
if (key == XK_Alt_L)
kas.keycodes[numkeys-1] = XK_Meta_L;
kas.keycodes[numkeys - 1] = XK_Meta_L;
else
kas.keycodes[numkeys-1] = XK_Meta_R;
kas.keycodes[numkeys - 1] = XK_Meta_R;
}
vnclog.Print(8, "keymap gives %u (%x) ", key, key);
vnclog.Print(8, ": keymap gives %.4x", key);

} else {
// not found in table
vnclog.Print(8, "not in special keymap, ");
vnclog.Print(8, ": not in keymap");

// Try a simple conversion to ASCII, using the current keyboard mapping
GetKeyboardState(keystate);

int ret = ToAscii(virtkey, 0, keystate, (WORD *) buf, 0);

// If Left Ctrl & Alt are both pressed and ToAscii() gives a valid keysym.
// (This is for AltGr on international keyboards (= LCtrl-Alt).
// e.g. Ctrl-Alt-Q gives @ on German keyboards.)
if (((keystate[VK_MENU] & 0x80) != 0) &&
((keystate[VK_CONTROL] & 0x80) != 0)) {

// If the key means anything in this keyboard layout
if ((ret >= 1) && (((*buf >= 32) && (*buf <= 126)) ||
((*buf >= 160) && (*buf <= 255)))) {

// Release the modifiers, then the keystroke, then press the modifiers.
// We don't release Right Ctrl; this allows German users to use it for
// doing Ctrl-@ etc.

if (GetKeyState(VK_LCONTROL) & 0x8000)
kas.releaseModifiers |= KEYMAP_LCONTROL;
if (GetKeyState(VK_LMENU) & 0x8000)
kas.releaseModifiers |= KEYMAP_LALT;
if (GetKeyState(VK_RMENU) & 0x8000)
kas.releaseModifiers |= KEYMAP_RALT;
if (((keystate[VK_RMENU] & 0x80) != 0) &&
((keystate[VK_LCONTROL] & 0x80) != 0)) {

// This is for Windows '95, and possibly other systems. The above
// GetKeyState() calls don't work in '95 - they always return 0.
// But if we're at this point in the code, we know that Ctrl and Alt
// are pressed, so let's release all Ctrl and Alt keys if we haven't
// registered them as already having been released.
if (kas.releaseModifiers == 0)
kas.releaseModifiers = KEYMAP_LCONTROL | KEYMAP_LALT | KEYMAP_RALT;
// Windows doesn't have a proper AltGr key event. It instead handles
// AltGr with fake left Ctrl and right Alt key events. Xvnc will
// automatically insert an AltGr (ISO Level 3 Shift) event if necessary
// (if it receives a key symbol that could not have been generated any
// other way), and unfortunately X11 doesn't generally like the sequence
// Ctrl + Alt + AltGr. Thus, for Windows viewers, we have to temporarily
// release Ctrl and Alt, then process the AltGr-modified symbol, then
// press Ctrl and Alt again.

vnclog.Print(8, "Ctrl-Alt pressed: ToAscii (without modifiers) returns %d byte(s): ",
ret);
for (int i = 0; i < ret; i++) {
kas.keycodes[numkeys++] = *(buf + i);
vnclog.Print(8, "%02x (%c) ", *(buf + i) , *(buf + i));
}
vnclog.Print(8, "\n");
}
}

// If not a Ctrl-Alt key
if (numkeys == 0) {
if (GetKeyState(VK_LCONTROL) & 0x8000)
kas.releaseModifiers |= KEYMAP_LCONTROL;
if (GetKeyState(VK_RMENU) & 0x8000)
kas.releaseModifiers |= KEYMAP_RALT;

// This is for Windows '95, and possibly other systems. The above
// GetKeyState() calls don't work in '95 - they always return 0.
// But if we're at this point in the code, we know that left Ctrl and
// right Alt are pressed, so let's release those keys if we haven't
// registered them as already having been released.
if (kas.releaseModifiers == 0)
kas.releaseModifiers = KEYMAP_LCONTROL | KEYMAP_RALT;
} else {
// There are no keysyms corresponding to control characters, e.g. Ctrl-F.
// The server already knows whether the Ctrl key is pressed, so we are
// interested in the key that would be sent if the Ctrl key were not
// pressed.
keystate[VK_CONTROL] = keystate[VK_LCONTROL] = keystate[VK_RCONTROL] = 0;
}

int ret = ToAscii(virtkey, 0, keystate, (WORD *) buf, 0);
if (ret < 0) {
switch (*buf) {
case '`' :
kas.keycodes[numkeys++] = XK_dead_grave; break;
case '\'' :
kas.keycodes[numkeys++] = XK_dead_acute; break;
case '~' :
kas.keycodes[numkeys++] = XK_dead_tilde; break;
case '^':
kas.keycodes[numkeys++] = XK_dead_circumflex; break;
case 168:
// dead_tilde / dead_diaeresis
if ((GetKeyState(VK_CONTROL) & 0x8000) &&
(GetKeyState(VK_MENU) & 0x8000)) {
// AltGr is pressed
kas.keycodes[numkeys++] = XK_dead_tilde;
} else {
kas.keycodes[numkeys++] = XK_dead_diaeresis;
}
break;
}
}
// If this works, and it's a regular printable character, we just send
// that
if (ret >= 1) {
vnclog.Print(8, "ToAscii (without ctrl) returns %d byte(s): ",
ret);
for (int i = 0; i < ret; i++) {
kas.keycodes[numkeys++] = *(buf + i);
vnclog.Print(8, "%02x (%c) ", *(buf + i) , *(buf + i));
}
int ret = ToUnicode(virtkey, 0, keystate, buf, 10, 0);

if (ret == 0) {
// Most Ctrl+Alt combinations will fail to produce a symbol, so
// try it again with Ctrl unconditionally disabled.
keystate[VK_CONTROL] = keystate[VK_LCONTROL] = keystate[VK_RCONTROL] = 0;
ret = ToUnicode(virtkey, 0, keystate, buf, 10, 0);
}

unsigned keysym = ucs2keysym(buf[0]);

if (ret == 1 && keysym != 0) {
// If the key means anything in this keyboard layout
if (((keystate[VK_RMENU] & 0x80) != 0) &&
((keystate[VK_LCONTROL] & 0x80) != 0))
vnclog.Print(8, "\n Ctrl-Alt: UCS->KS (w/o mods): ");
else
vnclog.Print(8, "\n UCS->KS (w/o ctrl): ");
kas.keycodes[numkeys++] = keysym;
vnclog.Print(8, " %.2x->%.2x", buf[0], keysym);
}

if (ret < 0 || ret > 1) {
// NOTE: For some reason, TigerVNC never gets a return value > 1 from
// ToUnicode(), but we do, even though we're using basically the same
// keyboard handling logic. Not sure why.
WCHAR dead_char = buf[0];
while (ret < 0) {
// Need to clear out the state that the dead key has caused.
// This is the recommended method by Microsoft's engineers:
// http://blogs.msdn.com/b/michkap/archive/2007/10/27/5717859.aspx
ret = ToUnicode(virtkey, 0, keystate, buf, 10, 0);
}
kas.keycodes[numkeys++] = ucs2keysym(ucs2combining(dead_char));
vnclog.Print(8, " %.2x->%.2x", dead_char, kas.keycodes[numkeys - 1]);
}
}

vnclog.Print(8, "\n");
kas.keycodes[numkeys] = VoidKeyCode;
return kas;
};
3 changes: 2 additions & 1 deletion win/vncviewer/KeyMap.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
// Copyright (C) 2016 D. R. Commander. All Rights Reserved.
//
// This file is part of the VNC system.
//
Expand Down Expand Up @@ -58,7 +59,7 @@ class KeyMap {
KeyActionSpec PCtoX(UINT virtkey, DWORD keyData);
private:
// CARD32 keymap[256];
unsigned char buf[4]; // lots of space for now
WCHAR buf[10]; // lots of space for now
BYTE keystate[256];
};

Expand Down

0 comments on commit 00c436d

Please sign in to comment.