Remove the FLTK simple keyboard system and reorganise things in preparation for a more direct approach.tags/v1.3.90
@@ -279,9 +279,6 @@ if(FLTK_FOUND) | |||
set(CMAKE_REQUIRED_INCLUDES ${FLTK_INCLUDE_DIR}) | |||
set(CMAKE_REQUIRED_LIBRARIES ${FLTK_LIBRARIES}) | |||
# FLTK STR #2599 | |||
check_cxx_source_compiles("#include <FL/Fl_Widget.H>\nint main(int c, char** v) { void (Fl_Widget::*foo)() = &Fl_Widget::set_simple_keyboard; return 0; }" HAVE_FLTK_DEAD_KEYS) | |||
# FLTK STR #2636 | |||
check_cxx_source_compiles("#include <FL/Fl.H>\nint main(int c, char** v) { Fl::add_clipboard_notify(NULL, NULL); return 0; }" HAVE_FLTK_CLIPBOARD) | |||
@@ -10,7 +10,6 @@ | |||
#cmakedefine HAVE_GNUTLS_DATUM_T | |||
#cmakedefine HAVE_GNUTLS_PK_ALGORITHM_T | |||
#cmakedefine HAVE_GNUTLS_SIGN_ALGORITHM_T | |||
#cmakedefine HAVE_FLTK_DEAD_KEYS | |||
#cmakedefine HAVE_FLTK_CLIPBOARD | |||
#cmakedefine HAVE_FLTK_MEDIAKEYS | |||
#cmakedefine HAVE_FLTK_FULLSCREEN |
@@ -1,286 +0,0 @@ | |||
diff -ur fltk-1.3.0r9619.org/FL/Fl_Widget.H fltk-1.3.0r9619/FL/Fl_Widget.H | |||
--- fltk-1.3.0r9619.org/FL/Fl_Widget.H 2012-04-23 22:12:06.000000000 +0200 | |||
+++ fltk-1.3.0r9619/FL/Fl_Widget.H 2012-06-18 13:46:07.302320825 +0200 | |||
@@ -171,6 +171,7 @@ | |||
GROUP_RELATIVE = 1<<16, ///< position this widget relative to the parent group, not to the window | |||
COPIED_TOOLTIP = 1<<17, ///< the widget tooltip is internally copied, its destruction is handled by the widget | |||
FULLSCREEN = 1<<18, ///< a fullscreen window (Fl_Window) | |||
+ SIMPLE_KEYBOARD = 1<<19, ///< the widget wants simple, consistent keypresses and not advanced input (like character composition and CJK input) | |||
// (space for more flags) | |||
USERFLAG3 = 1<<29, ///< reserved for 3rd party extensions | |||
USERFLAG2 = 1<<30, ///< reserved for 3rd party extensions | |||
@@ -776,6 +777,35 @@ | |||
*/ | |||
void clear_changed() {flags_ &= ~CHANGED;} | |||
+ /** | |||
+ Returns if the widget sees a simplified keyboard model or not. | |||
+ | |||
+ Normally widgets get a full-featured keyboard model that is geared | |||
+ towards text input. This includes support for compose sequences and | |||
+ advanced input methods, commonly used for asian writing system. This | |||
+ system however has downsides in that extra graphic can be presented | |||
+ to the user and that a physical key press doesn't correspond directly | |||
+ to a FLTK event. | |||
+ | |||
+ Widgets that need a direct correspondence between actual key events | |||
+ and those seen by the widget can swith to the simplified keyboard | |||
+ model. | |||
+ | |||
+ \retval 0 if the widget uses the normal keyboard model | |||
+ \see set_changed(), clear_changed() | |||
+ */ | |||
+ unsigned int simple_keyboard() const {return flags_&SIMPLE_KEYBOARD;} | |||
+ | |||
+ /** Marks a widget to use the simple keyboard model. | |||
+ \see changed(), clear_changed() | |||
+ */ | |||
+ void set_simple_keyboard() {flags_ |= SIMPLE_KEYBOARD;} | |||
+ | |||
+ /** Marks a widget to use the normal keyboard model. | |||
+ \see changed(), set_changed() | |||
+ */ | |||
+ void set_normal_keyboard() {flags_ &= ~SIMPLE_KEYBOARD;} | |||
+ | |||
/** Gives the widget the keyboard focus. | |||
Tries to make this widget be the Fl::focus() widget, by first sending | |||
it an FL_FOCUS event, and if it returns non-zero, setting | |||
diff -ur fltk-1.3.0r9619.org/src/Fl.cxx fltk-1.3.0r9619/src/Fl.cxx | |||
--- fltk-1.3.0r9619.org/src/Fl.cxx 2012-03-23 17:47:53.000000000 +0100 | |||
+++ fltk-1.3.0r9619/src/Fl.cxx 2012-06-18 13:46:07.303320877 +0200 | |||
@@ -70,6 +70,8 @@ | |||
extern double fl_mac_flush_and_wait(double time_to_wait, char in_idle); | |||
#endif // WIN32 | |||
+extern void fl_update_focus(void); | |||
+ | |||
// | |||
// Globals... | |||
// | |||
@@ -876,6 +878,8 @@ | |||
fl_oldfocus = p; | |||
} | |||
e_number = old_event; | |||
+ // let the platform code do what it needs | |||
+ fl_update_focus(); | |||
} | |||
} | |||
diff -ur fltk-1.3.0r9619.org/src/Fl_grab.cxx fltk-1.3.0r9619/src/Fl_grab.cxx | |||
--- fltk-1.3.0r9619.org/src/Fl_grab.cxx 2012-03-23 17:47:53.000000000 +0100 | |||
+++ fltk-1.3.0r9619/src/Fl_grab.cxx 2012-06-18 13:46:07.303320877 +0200 | |||
@@ -29,6 +29,7 @@ | |||
// override_redirect, it does similar things on WIN32. | |||
extern void fl_fix_focus(); // in Fl.cxx | |||
+void fl_update_focus(void); | |||
#ifdef WIN32 | |||
// We have to keep track of whether we have captured the mouse, since | |||
@@ -80,6 +81,7 @@ | |||
#endif | |||
} | |||
grab_ = win; | |||
+ fl_update_focus(); | |||
} else { | |||
if (grab_) { | |||
#ifdef WIN32 | |||
@@ -98,6 +100,7 @@ | |||
XFlush(fl_display); | |||
#endif | |||
grab_ = 0; | |||
+ fl_update_focus(); | |||
fl_fix_focus(); | |||
} | |||
} | |||
diff -ur fltk-1.3.0r9619.org/src/Fl_x.cxx fltk-1.3.0r9619/src/Fl_x.cxx | |||
--- fltk-1.3.0r9619.org/src/Fl_x.cxx 2012-06-18 13:46:07.205316173 +0200 | |||
+++ fltk-1.3.0r9619/src/Fl_x.cxx 2012-06-18 13:46:18.216844629 +0200 | |||
@@ -298,6 +298,7 @@ | |||
Colormap fl_colormap; | |||
XIM fl_xim_im = 0; | |||
XIC fl_xim_ic = 0; | |||
+Window fl_xim_win = 0; | |||
char fl_is_over_the_spot = 0; | |||
static XRectangle status_area; | |||
@@ -583,6 +584,65 @@ | |||
if(xim_styles) XFree(xim_styles); | |||
} | |||
+void fl_xim_deactivate(void); | |||
+ | |||
+void fl_xim_activate(Window xid) | |||
+{ | |||
+ if (!fl_xim_im) | |||
+ return; | |||
+ | |||
+ // If the focused window has changed, then use the brute force method | |||
+ // of completely recreating the input context. | |||
+ if (fl_xim_win != xid) { | |||
+ fl_xim_deactivate(); | |||
+ | |||
+ fl_new_ic(); | |||
+ fl_xim_win = xid; | |||
+ | |||
+ XSetICValues(fl_xim_ic, | |||
+ XNFocusWindow, fl_xim_win, | |||
+ XNClientWindow, fl_xim_win, | |||
+ NULL); | |||
+ } | |||
+ | |||
+ fl_set_spot(spotf, spots, spot.x, spot.y, spot.width, spot.height); | |||
+} | |||
+ | |||
+void fl_xim_deactivate(void) | |||
+{ | |||
+ if (!fl_xim_ic) | |||
+ return; | |||
+ | |||
+ XDestroyIC(fl_xim_ic); | |||
+ fl_xim_ic = NULL; | |||
+ | |||
+ fl_xim_win = 0; | |||
+} | |||
+ | |||
+extern Fl_Window *fl_xfocus; | |||
+ | |||
+void fl_update_focus(void) | |||
+{ | |||
+ Fl_Widget *focus; | |||
+ | |||
+ focus = Fl::grab(); | |||
+ if (!focus) | |||
+ focus = Fl::focus(); | |||
+ if (!focus) | |||
+ return; | |||
+ | |||
+ if (focus->simple_keyboard()) { | |||
+ fl_xim_deactivate(); | |||
+ } else { | |||
+ // fl_xfocus should always be set if something has focus, but let's | |||
+ // play it safe | |||
+ if (!fl_xfocus || !fl_xid(fl_xfocus)) | |||
+ return; | |||
+ | |||
+ fl_xim_activate(fl_xid(fl_xfocus)); | |||
+ } | |||
+} | |||
+ | |||
void fl_open_display() { | |||
if (fl_display) return; | |||
@@ -917,10 +977,9 @@ | |||
XEvent xevent = thisevent; | |||
fl_xevent = &thisevent; | |||
Window xid = xevent.xany.window; | |||
- static Window xim_win = 0; | |||
if (fl_xim_ic && xevent.type == DestroyNotify && | |||
- xid != xim_win && !fl_find(xid)) | |||
+ xid != fl_xim_win && !fl_find(xid)) | |||
{ | |||
XIM xim_im; | |||
xim_im = XOpenIM(fl_display, NULL, NULL, NULL); | |||
@@ -935,48 +994,10 @@ | |||
return 0; | |||
} | |||
- if (fl_xim_ic && (xevent.type == FocusIn)) | |||
- { | |||
-#define POOR_XIM | |||
-#ifdef POOR_XIM | |||
- if (xim_win != xid) | |||
- { | |||
- xim_win = xid; | |||
- XDestroyIC(fl_xim_ic); | |||
- fl_xim_ic = NULL; | |||
- fl_new_ic(); | |||
- XSetICValues(fl_xim_ic, | |||
- XNFocusWindow, xevent.xclient.window, | |||
- XNClientWindow, xid, | |||
- NULL); | |||
- } | |||
- fl_set_spot(spotf, spots, spot.x, spot.y, spot.width, spot.height); | |||
-#else | |||
- if (Fl::first_window() && Fl::first_window()->modal()) { | |||
- Window x = fl_xid(Fl::first_window()); | |||
- if (x != xim_win) { | |||
- xim_win = x; | |||
- XSetICValues(fl_xim_ic, | |||
- XNFocusWindow, xim_win, | |||
- XNClientWindow, xim_win, | |||
- NULL); | |||
- fl_set_spot(spotf, spots, spot.x, spot.y, spot.width, spot.height); | |||
- } | |||
- } else if (xim_win != xid && xid) { | |||
- xim_win = xid; | |||
- XSetICValues(fl_xim_ic, | |||
- XNFocusWindow, xevent.xclient.window, | |||
- XNClientWindow, xid, | |||
- //XNFocusWindow, xim_win, | |||
- //XNClientWindow, xim_win, | |||
- NULL); | |||
- fl_set_spot(spotf, spots, spot.x, spot.y, spot.width, spot.height); | |||
- } | |||
-#endif | |||
+ if (fl_xim_ic) { | |||
+ if (XFilterEvent((XEvent *)&xevent, 0)) | |||
+ return 1; | |||
} | |||
- | |||
- if ( XFilterEvent((XEvent *)&xevent, 0) ) | |||
- return(1); | |||
#if USE_XRANDR | |||
if( XRRUpdateConfiguration_f && xevent.type == randrEventBase + RRScreenChangeNotify) { | |||
@@ -1326,15 +1347,15 @@ | |||
//static XComposeStatus compose; | |||
len = XLookupString((XKeyEvent*)&(xevent.xkey), | |||
buffer, buffer_len, &keysym, 0/*&compose*/); | |||
- if (keysym && keysym < 0x400) { // a character in latin-1,2,3,4 sets | |||
- // force it to type a character (not sure if this ever is needed): | |||
- // if (!len) {buffer[0] = char(keysym); len = 1;} | |||
- len = fl_utf8encode(XKeysymToUcs(keysym), buffer); | |||
- if (len < 1) len = 1; | |||
- // ignore all effects of shift on the keysyms, which makes it a lot | |||
- // easier to program shortcuts and is Windoze-compatible: | |||
- keysym = XKeycodeToKeysym(fl_display, keycode, 0); | |||
- } | |||
+ // XLookupString() is only defined to return Latin-1 (although it | |||
+ // often gives you more). To be safe, use our own lookups based on | |||
+ // keysym. | |||
+ len = fl_utf8encode(XKeysymToUcs(keysym), buffer); | |||
+ if (len < 1) | |||
+ len = 1; | |||
+ // ignore all effects of shift on the keysyms, which makes it a lot | |||
+ // easier to program shortcuts and is Windoze-compatable: | |||
+ keysym = XKeycodeToKeysym(fl_display, keycode, 0); | |||
} | |||
// MRS: Can't use Fl::event_state(FL_CTRL) since the state is not | |||
// set until set_event_xy() is called later... | |||
diff -ur fltk-1.3.0r9619.org/src/xutf8/imKStoUCS.c fltk-1.3.0r9619/src/xutf8/imKStoUCS.c | |||
--- fltk-1.3.0r9619.org/src/xutf8/imKStoUCS.c 2009-03-13 23:43:43.000000000 +0100 | |||
+++ fltk-1.3.0r9619/src/xutf8/imKStoUCS.c 2012-06-18 13:46:07.304320930 +0200 | |||
@@ -266,6 +266,12 @@ | |||
0x20a8, 0x20a9, 0x20aa, 0x20ab, 0x20ac /* 0x20a8-0x20af */ | |||
}; | |||
+static unsigned short const keysym_to_unicode_fe50_fe60[] = { | |||
+ 0x0300, 0x0301, 0x0302, 0x0303, 0x0304, 0x0306, 0x0307, 0x0308, /* 0xfe50-0xfe57 */ | |||
+ 0x030a, 0x030b, 0x030c, 0x0327, 0x0328, 0x1da5, 0x3099, 0x309a, /* 0xfe58-0xfe5f */ | |||
+ 0x0323 /* 0xfe60-0xfe67 */ | |||
+}; | |||
+ | |||
unsigned int | |||
KeySymToUcs4(KeySym keysym) | |||
{ | |||
@@ -315,6 +321,8 @@ | |||
return keysym_to_unicode_1e9f_1eff[keysym - 0x1e9f]; | |||
else if (keysym > 0x209f && keysym < 0x20ad) | |||
return keysym_to_unicode_20a0_20ac[keysym - 0x20a0]; | |||
+ else if (keysym > 0xfe4f && keysym < 0xfe61) | |||
+ return keysym_to_unicode_fe50_fe60[keysym - 0xfe50]; | |||
else | |||
return 0; | |||
} |
@@ -1,256 +0,0 @@ | |||
diff -ur fltk-1.3.0r9293.org/src/Fl_win32.cxx fltk-1.3.0r9293/src/Fl_win32.cxx | |||
--- fltk-1.3.0r9293.org/src/Fl_win32.cxx 2012-06-18 09:07:56.522314557 +0200 | |||
+++ fltk-1.3.0r9293/src/Fl_win32.cxx 2012-06-18 09:08:07.392836285 +0200 | |||
@@ -87,6 +87,8 @@ | |||
static Fl_Display_Device fl_gdi_display(&fl_gdi_driver); | |||
Fl_Display_Device *Fl_Display_Device::_display = &fl_gdi_display; // the platform display | |||
+bool use_simple_keyboard = false; | |||
+ | |||
// dynamic wsock dll handling api: | |||
#if defined(__CYGWIN__) && !defined(SOCKET) | |||
# define SOCKET int | |||
@@ -120,6 +122,8 @@ | |||
* size and link dependencies. | |||
*/ | |||
static HMODULE s_imm_module = 0; | |||
+typedef BOOL (WINAPI* flTypeImmAssociateContextEx)(HWND, HIMC, DWORD); | |||
+static flTypeImmAssociateContextEx flImmAssociateContextEx = 0; | |||
typedef HIMC (WINAPI* flTypeImmGetContext)(HWND); | |||
static flTypeImmGetContext flImmGetContext = 0; | |||
typedef BOOL (WINAPI* flTypeImmSetCompositionWindow)(HIMC, LPCOMPOSITIONFORM); | |||
@@ -135,6 +139,7 @@ | |||
if (!s_imm_module) | |||
Fl::fatal("FLTK Lib Error: IMM32.DLL file not found!\n\n" | |||
"Please check your input method manager library accessibility."); | |||
+ flImmAssociateContextEx = (flTypeImmAssociateContextEx)GetProcAddress(s_imm_module, "ImmAssociateContextEx"); | |||
flImmGetContext = (flTypeImmGetContext)GetProcAddress(s_imm_module, "ImmGetContext"); | |||
flImmSetCompositionWindow = (flTypeImmSetCompositionWindow)GetProcAddress(s_imm_module, "ImmSetCompositionWindow"); | |||
flImmReleaseContext = (flTypeImmReleaseContext)GetProcAddress(s_imm_module, "ImmReleaseContext"); | |||
@@ -413,7 +418,12 @@ | |||
} | |||
} | |||
- TranslateMessage(&fl_msg); | |||
+ // Don't bother with key to character translation as we do | |||
+ // it manually for simpley keyboard widgets. In fact, calling | |||
+ // TranslateMessage() just makes it more difficult as it sets | |||
+ // a bunch of internal state. | |||
+ if (!use_simple_keyboard) | |||
+ TranslateMessage(&fl_msg); | |||
DispatchMessageW(&fl_msg); | |||
have_message = PeekMessageW(&fl_msg, NULL, 0, 0, PM_REMOVE); | |||
} | |||
@@ -638,6 +648,49 @@ | |||
} | |||
} | |||
+void fl_update_focus(void) | |||
+{ | |||
+ Fl_Widget *focus; | |||
+ Fl_Window *win; | |||
+ | |||
+ get_imm_module(); | |||
+ | |||
+ focus = Fl::grab(); | |||
+ if (!focus) | |||
+ focus = Fl::focus(); | |||
+ if (!focus) | |||
+ return; | |||
+ | |||
+ // Grabs are special in that events are sent to the first | |||
+ // available window | |||
+ if (focus == Fl::grab()) | |||
+ win = Fl::first_window(); | |||
+ else { | |||
+ win = focus->as_window(); | |||
+ if (!win) | |||
+ win = focus->window(); | |||
+ } | |||
+ | |||
+ if (!win) { | |||
+ Fl::warning("Cannot find window for widget receiving focus"); | |||
+ return; | |||
+ } | |||
+ | |||
+ // No Win32 window created yet | |||
+ if (!Fl_X::i(win) || !fl_xid(win)) | |||
+ return; | |||
+ | |||
+ if (focus->simple_keyboard()) { | |||
+ use_simple_keyboard = true; | |||
+ if (flImmGetContext(fl_xid(win)) != 0) | |||
+ flImmAssociateContextEx(fl_xid(win), 0, 0); | |||
+ } else { | |||
+ use_simple_keyboard = false; | |||
+ if (flImmGetContext(fl_xid(win)) == 0) | |||
+ flImmAssociateContextEx(fl_xid(win), 0, IACE_DEFAULT); | |||
+ } | |||
+} | |||
+ | |||
HWND fl_capture; | |||
static int mouse_event(Fl_Window *window, int what, int button, | |||
@@ -785,6 +838,27 @@ | |||
return extended ? extendedlut[vk] : vklut[vk]; | |||
} | |||
+static xchar msdead2fltk(xchar in) | |||
+{ | |||
+ switch (in) { | |||
+ case 0x0060: // GRAVE ACCENT | |||
+ return 0x0300; // COMBINING GRAVE ACCENT | |||
+ case 0x00b4: // ACUTE ACCENT | |||
+ return 0x0301; // COMBINING ACUTE ACCENT | |||
+ case 0x005e: // CIRCUMFLEX ACCENT | |||
+ return 0x0302; // COMBINING CIRCUMFLEX ACCENT | |||
+ case 0x007e: // TILDE | |||
+ return 0x0303; // COMBINING TILDE | |||
+ case 0x00a8: // DIAERESIS | |||
+ return 0x0308; // COMBINING DIAERESIS | |||
+ // FIXME: Windows dead key behaviour isn't documented and I don't have | |||
+ // any more keyboards to test with... | |||
+ } | |||
+ | |||
+ // hope that Windows gave us something proper to begin with | |||
+ return in; | |||
+} | |||
+ | |||
#if USE_COLORMAP | |||
extern HPALETTE fl_select_palette(void); // in fl_color_win32.cxx | |||
#endif | |||
@@ -846,6 +920,8 @@ | |||
//fl_msg.pt = ??? | |||
//fl_msg.lPrivate = ??? | |||
+ MSG fl_orig_msg = fl_msg; | |||
+ | |||
Fl_Window *window = fl_find(hWnd); | |||
if (window) switch (uMsg) { | |||
@@ -1025,23 +1101,82 @@ | |||
if (GetKeyState(VK_SCROLL)) state |= FL_SCROLL_LOCK; | |||
Fl::e_state = state; | |||
static char buffer[1024]; | |||
- if (uMsg == WM_CHAR || uMsg == WM_SYSCHAR) { | |||
+ if (use_simple_keyboard) { | |||
+ BYTE keystate[256]; | |||
+ WCHAR wbuf[8]; | |||
+ int ret; | |||
+ | |||
+ // I'm not sure if we ever get WM_CHAR (& friends) without an initial | |||
+ // WM_KEYDOWN (& friends), but if we do then we should not send such | |||
+ // side band events to simple keyboard widgets. | |||
+ if ((fl_orig_msg.message != WM_KEYDOWN) && | |||
+ (fl_orig_msg.message != WM_SYSKEYDOWN) && | |||
+ (fl_orig_msg.message != WM_KEYUP) && | |||
+ (fl_orig_msg.message != WM_SYSKEYUP)) | |||
+ break; | |||
+ | |||
+ GetKeyboardState(keystate); | |||
+ | |||
+ // Pressing Ctrl wreaks havoc with the symbol lookup, so turn that off. | |||
+ // But AltGr shows up as Ctrl+Alt in Windows, so keep Ctrl if Alt is | |||
+ // active. | |||
+ if (!(keystate[VK_MENU] & 0x80)) | |||
+ keystate[VK_CONTROL] = keystate[VK_LCONTROL] = keystate[VK_RCONTROL] = 0; | |||
+ | |||
+ // We cannot inspect or modify Windows' internal state of the keyboard | |||
+ // so we have to try to infer information from ToUnicode() and wedge | |||
+ // things into a known state. | |||
+ for (int i = 0;i < 2;i++) { | |||
+ ret = ToUnicode(fl_orig_msg.wParam, 0, keystate, wbuf, | |||
+ sizeof(wbuf)/sizeof(wbuf[0]), 0); | |||
+ | |||
+ // No symbol for this key (or unexpected length) | |||
+ if ((ret == 0) || (ret < -1)) { | |||
+ buffer[0] = 0; | |||
+ Fl::e_length = 0; | |||
+ break; | |||
+ } | |||
+ | |||
+ // A dead key. Convert this to a Unicode combining character so | |||
+ // that the application can tell the difference between dead and | |||
+ // normal keys. | |||
+ if (ret == -1) { | |||
+ xchar u = (xchar) msdead2fltk(wbuf[0]); | |||
+ Fl::e_length = fl_utf8fromwc(buffer, 1024, &u, 1); | |||
+ buffer[Fl::e_length] = 0; | |||
+ break; | |||
+ } | |||
+ | |||
+ // If we have two characters (or more) from ToUnicode(), that's | |||
+ // an invalid sequence. One character chould mean a proper symbol, | |||
+ // or it could mean a composed one. In both cases we need to call | |||
+ // ToUnicode() again to get something sane. | |||
+ if (i == 0) | |||
+ continue; | |||
+ | |||
+ // We should now have something sane. Give whatever we have to the | |||
+ // application. | |||
+ Fl::e_length = fl_utf8fromwc(buffer, 1024, wbuf, ret); | |||
+ buffer[Fl::e_length] = 0; | |||
+ } | |||
+ } else if (uMsg == WM_CHAR || uMsg == WM_SYSCHAR) { | |||
xchar u = (xchar) wParam; | |||
// Fl::e_length = fl_unicode2utf(&u, 1, buffer); | |||
Fl::e_length = fl_utf8fromwc(buffer, 1024, &u, 1); | |||
buffer[Fl::e_length] = 0; | |||
+ } else { | |||
+ buffer[0] = 0; | |||
+ Fl::e_length = 0; | |||
+ } | |||
- | |||
- } else if (Fl::e_keysym >= FL_KP && Fl::e_keysym <= FL_KP_Last) { | |||
- if (state & FL_NUM_LOCK) { | |||
- // Convert to regular keypress... | |||
- buffer[0] = Fl::e_keysym-FL_KP; | |||
- Fl::e_length = 1; | |||
- } else { | |||
- // Convert to special keypress... | |||
- buffer[0] = 0; | |||
- Fl::e_length = 0; | |||
+ // The keypad area is a bit odd in that we need to change the keysym | |||
+ // to properly indicate what the user meant, unlike other keys where | |||
+ // we normally change the text and keep keysym stable. | |||
+ if (Fl::e_keysym >= FL_KP && Fl::e_keysym <= FL_KP_Last) { | |||
+ // The initial mapping tables give us a keysym that corresponds to | |||
+ // numlock being on, so we only do something when it is off. | |||
+ if (!(state & FL_NUM_LOCK)) { | |||
switch (Fl::e_keysym) { | |||
case FL_KP + '0' : | |||
Fl::e_keysym = FL_Insert; | |||
@@ -1073,30 +1208,10 @@ | |||
case FL_KP + '.' : | |||
Fl::e_keysym = FL_Delete; | |||
break; | |||
- case FL_KP + '/' : | |||
- case FL_KP + '*' : | |||
- case FL_KP + '-' : | |||
- case FL_KP + '+' : | |||
- buffer[0] = Fl::e_keysym-FL_KP; | |||
- Fl::e_length = 1; | |||
- break; | |||
} | |||
} | |||
- } else if ((lParam & (1<<31))==0) { | |||
-#ifdef FLTK_PREVIEW_DEAD_KEYS | |||
- if ((lParam & (1<<24))==0) { // clear if dead key (always?) | |||
- xchar u = (xchar) wParam; | |||
- Fl::e_length = fl_utf8fromwc(buffer, 1024, &u, 1); | |||
- buffer[Fl::e_length] = 0; | |||
- } else { // set if "extended key" (never printable?) | |||
- buffer[0] = 0; | |||
- Fl::e_length = 0; | |||
- } | |||
-#else | |||
- buffer[0] = 0; | |||
- Fl::e_length = 0; | |||
-#endif | |||
} | |||
+ | |||
Fl::e_text = buffer; | |||
if (lParam & (1<<31)) { // key up events. | |||
if (Fl::handle(FL_KEYUP, window)) return 0; |
@@ -1,375 +0,0 @@ | |||
diff -ur fltk-1.3.0r9619.org/configure.in fltk-1.3.0r9619/configure.in | |||
--- fltk-1.3.0r9619.org/configure.in 2012-04-22 04:45:09.000000000 +0200 | |||
+++ fltk-1.3.0r9619/configure.in 2012-06-18 13:47:33.290447462 +0200 | |||
@@ -865,6 +865,8 @@ | |||
Darwin*) | |||
# MacOS X uses Cocoa for graphics. | |||
LIBS="$LIBS -framework Cocoa" | |||
+ # And some Carbon for keyboard handling | |||
+ LIBS="$LIBS -framework Carbon" | |||
if test x$have_pthread = xyes; then | |||
AC_DEFINE(HAVE_PTHREAD) | |||
diff -ur fltk-1.3.0r9619.org/src/Fl_cocoa.mm fltk-1.3.0r9619/src/Fl_cocoa.mm | |||
--- fltk-1.3.0r9619.org/src/Fl_cocoa.mm 2012-06-16 10:49:52.000000000 +0200 | |||
+++ fltk-1.3.0r9619/src/Fl_cocoa.mm 2012-06-18 13:47:42.944910782 +0200 | |||
@@ -53,6 +53,7 @@ | |||
#include <math.h> | |||
#import <Cocoa/Cocoa.h> | |||
+#import <Carbon/Carbon.h> | |||
#ifndef NSINTEGER_DEFINED // appears with 10.5 in NSObjCRuntime.h | |||
#if defined(__LP64__) && __LP64__ | |||
@@ -114,6 +115,8 @@ | |||
extern Fl_Window* fl_xmousewin; | |||
#endif | |||
+bool use_simple_keyboard = false; | |||
+ | |||
enum { FLTKTimerEvent = 1, FLTKDataReadyEvent }; | |||
@@ -130,6 +133,39 @@ | |||
{ | |||
} | |||
+// Undocumented voodoo. Taken from Mozilla. | |||
+#define ENABLE_ROMAN_KYBDS_ONLY -23 | |||
+ | |||
+void fl_update_focus(void) | |||
+{ | |||
+ Fl_Widget *focus; | |||
+ | |||
+ focus = Fl::grab(); | |||
+ if (!focus) | |||
+ focus = Fl::focus(); | |||
+ if (!focus) | |||
+ return; | |||
+ | |||
+ if (focus->simple_keyboard()) | |||
+ use_simple_keyboard = true; | |||
+ else | |||
+ use_simple_keyboard = false; | |||
+ | |||
+ // Force a "Roman" or "ASCII" keyboard, which both the Mozilla and | |||
+ // Safari people seem to think implies turning off advanced IME stuff | |||
+ // (see nsTSMManager::SyncKeyScript in Mozilla and enableSecureTextInput | |||
+ // in Safari/Webcore). Should be good enough for us then... | |||
+#if (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5) | |||
+ CFArrayRef inputSources = TISCreateASCIICapableInputSourceList(); | |||
+ TSMSetDocumentProperty(TSMGetActiveDocument(), | |||
+ kTSMDocumentEnabledInputSourcesPropertyTag, | |||
+ sizeof(CFArrayRef), &inputSources); | |||
+ CFRelease(inputSources); | |||
+#else | |||
+ KeyScript(use_simple_keyboard ? ENABLE_ROMAN_KYBDS_ONLY : smKeyEnableKybds); | |||
+#endif | |||
+} | |||
+ | |||
/* | |||
* Mac keyboard lookup table | |||
*/ | |||
@@ -908,6 +944,25 @@ | |||
} | |||
@end | |||
+static const char* cocoaDead2FLTK(const char *in) | |||
+{ | |||
+ if (strcmp(in, "\140") == 0) // GRAVE ACCENT | |||
+ return "\314\200"; // COMBINING GRAVE ACCENT | |||
+ if (strcmp(in, "\302\264") == 0) // ACUTE ACCENT | |||
+ return "\314\201"; // COMBINING ACUTE ACCENT | |||
+ if (strcmp(in, "\136") == 0) // CIRCUMFLEX ACCENT | |||
+ return "\314\202"; // COMBINING CIRCUMFLEX ACCENT | |||
+ if (strcmp(in, "\176") == 0) // TILDE | |||
+ return "\314\203"; // COMBINING TILDE | |||
+ if (strcmp(in, "\302\250") == 0) // DIAERESIS | |||
+ return "\314\210"; // COMBINING DIAERESIS | |||
+ // FIXME: OS X dead key behaviour isn't documented and I don't have | |||
+ // any more keyboards to test with... | |||
+ | |||
+ // hope that OS X gave us something proper to begin with | |||
+ return in; | |||
+} | |||
+ | |||
/* | |||
Handle cocoa keyboard events | |||
Events during a character composition sequence: | |||
@@ -1648,6 +1703,7 @@ | |||
- (void)rightMouseDragged:(NSEvent *)theEvent; | |||
- (void)otherMouseDragged:(NSEvent *)theEvent; | |||
- (void)scrollWheel:(NSEvent *)theEvent; | |||
++ (NSString *)keyTranslate:(UInt16)keyCode withModifierFlags:(UInt32)modifierFlags; | |||
- (BOOL)handleKeyDown:(NSEvent *)theEvent; | |||
- (void)keyDown:(NSEvent *)theEvent; | |||
- (void)keyUp:(NSEvent *)theEvent; | |||
@@ -1726,6 +1782,130 @@ | |||
- (void)scrollWheel:(NSEvent *)theEvent { | |||
cocoaMouseWheelHandler(theEvent); | |||
} | |||
++ (NSString *)keyTranslate:(UInt16)keyCode withModifierFlags:(UInt32)modifierFlags { | |||
+ const UCKeyboardLayout *layout; | |||
+ OSStatus err; | |||
+ | |||
+ layout = NULL; | |||
+ | |||
+#if (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5) | |||
+ TISInputSourceRef keyboard; | |||
+ CFDataRef uchr; | |||
+ | |||
+ keyboard = TISCopyCurrentKeyboardInputSource(); | |||
+ uchr = (CFDataRef)TISGetInputSourceProperty(keyboard, | |||
+ kTISPropertyUnicodeKeyLayoutData); | |||
+ if (uchr == NULL) | |||
+ return nil; | |||
+ | |||
+ layout = (const UCKeyboardLayout*)CFDataGetBytePtr(uchr); | |||
+#else | |||
+ KeyboardLayoutRef old_layout; | |||
+ int kind; | |||
+ | |||
+ err = KLGetCurrentKeyboardLayout(&old_layout); | |||
+ if (err != noErr) | |||
+ return nil; | |||
+ | |||
+ err = KLGetKeyboardLayoutProperty(old_layout, kKLKind, | |||
+ (const void**)&kind); | |||
+ if (err != noErr) | |||
+ return nil; | |||
+ | |||
+ // Old, crufty layout format? | |||
+ if (kind == kKLKCHRKind) { | |||
+ void *kchr_layout; | |||
+ | |||
+ UInt32 chars, state; | |||
+ char buf[3]; | |||
+ | |||
+ unichar result[16]; | |||
+ ByteCount in_len, out_len; | |||
+ | |||
+ err = KLGetKeyboardLayoutProperty(old_layout, kKLKCHRData, | |||
+ (const void**)&kchr_layout); | |||
+ if (err != noErr) | |||
+ return nil; | |||
+ | |||
+ state = 0; | |||
+ | |||
+ keyCode &= 0x7f; | |||
+ modifierFlags &= 0xff00; | |||
+ | |||
+ chars = KeyTranslate(kchr_layout, keyCode | modifierFlags, &state); | |||
+ | |||
+ buf[0] = (chars >> 16) & 0xff; | |||
+ buf[1] = chars & 0xff; | |||
+ buf[2] = '\0'; | |||
+ | |||
+ if (buf[0] == '\0') { | |||
+ buf[0] = buf[1]; | |||
+ buf[1] = '\0'; | |||
+ } | |||
+ | |||
+ // The data is now in some layout specific encoding. Need to convert | |||
+ // this to unicode. | |||
+ | |||
+ ScriptCode script; | |||
+ TextEncoding encoding; | |||
+ TECObjectRef converter; | |||
+ | |||
+ script = (ScriptCode)GetScriptManagerVariable(smKeyScript); | |||
+ | |||
+ err = UpgradeScriptInfoToTextEncoding(script, kTextLanguageDontCare, | |||
+ kTextRegionDontCare, NULL, | |||
+ &encoding); | |||
+ if (err != noErr) | |||
+ return nil; | |||
+ | |||
+ err = TECCreateConverter(&converter, encoding, kTextEncodingUnicodeV4_0); | |||
+ if (err != noErr) | |||
+ return nil; | |||
+ | |||
+ in_len = strlen(buf); | |||
+ out_len = sizeof(result); | |||
+ | |||
+ err = TECConvertText(converter, (ConstTextPtr)buf, in_len, &in_len, | |||
+ (TextPtr)result, out_len, &out_len); | |||
+ | |||
+ TECDisposeConverter(converter); | |||
+ | |||
+ if (err != noErr) | |||
+ return nil; | |||
+ | |||
+ return [NSString stringWithCharacters:result | |||
+ length:(out_len / sizeof(unichar))]; | |||
+ } | |||
+ | |||
+ if ((kind != kKLKCHRuchrKind) && (kind != kKLuchrKind)) | |||
+ return nil; | |||
+ | |||
+ err = KLGetKeyboardLayoutProperty(old_layout, kKLuchrData, | |||
+ (const void**)&layout); | |||
+ if (err != noErr) | |||
+ return nil; | |||
+#endif | |||
+ | |||
+ if (layout == NULL) | |||
+ return nil; | |||
+ | |||
+ UInt32 dead_state; | |||
+ UniCharCount max_len, actual_len; | |||
+ UniChar string[255]; | |||
+ | |||
+ dead_state = 0; | |||
+ max_len = sizeof(string)/sizeof(*string); | |||
+ | |||
+ modifierFlags = (modifierFlags >> 8) & 0xff; | |||
+ | |||
+ err = UCKeyTranslate(layout, keyCode, kUCKeyActionDown, modifierFlags, | |||
+ LMGetKbdType(), 0, &dead_state, max_len, &actual_len, | |||
+ string); | |||
+ if (err != noErr) | |||
+ return nil; | |||
+ | |||
+ return [NSString stringWithCharacters:string length:actual_len]; | |||
+} | |||
- (BOOL)handleKeyDown:(NSEvent *)theEvent { | |||
//NSLog(@"handleKeyDown"); | |||
fl_lock_function(); | |||
@@ -1752,14 +1932,47 @@ | |||
break; | |||
} | |||
} | |||
- if (!no_text_key && !(Fl::e_state & FL_META) ) { | |||
- // Don't send cmd-<key> to interpretKeyEvents because it beeps. | |||
+ if (!no_text_key) { | |||
+ // The simple keyboard model will ignore insertText, so we need to grab | |||
+ // the symbol directly from the event. Note that we still use setMarkedText. | |||
+ if (use_simple_keyboard) { | |||
+ NSString *simple_chars; | |||
+ UInt32 modifiers; | |||
+ | |||
+ // We want a "normal" symbol out of the event, which basically means | |||
+ // we only respect the shift and alt/altgr modifiers. Cocoa can help | |||
+ // us if we only wanted shift, but as we also want alt/altgr, we'll | |||
+ // have to do some lookup ourselves. This matches our behaviour on | |||
+ // other platforms. | |||
+ | |||
+ modifiers = 0; | |||
+ if ([theEvent modifierFlags] & NSAlphaShiftKeyMask) | |||
+ modifiers |= alphaLock; | |||
+ if ([theEvent modifierFlags] & NSShiftKeyMask) | |||
+ modifiers |= shiftKey; | |||
+ if ([theEvent modifierFlags] & NSAlternateKeyMask) | |||
+ modifiers |= optionKey; | |||
+ | |||
+ simple_chars = [FLView keyTranslate:[theEvent keyCode] | |||
+ withModifierFlags:modifiers]; | |||
+ if (simple_chars == nil) { | |||
+ // Something went wrong. Fall back to what Cocoa gave us... | |||
+ simple_chars = [theEvent charactersIgnoringModifiers]; | |||
+ } | |||
+ | |||
+ [FLView prepareEtext:simple_chars]; | |||
+ } | |||
+ | |||
// Then we can let the OS have a stab at it and see if it thinks it | |||
// should result in some text | |||
- NSText *edit = [[theEvent window] fieldEditor:YES forObject:nil]; | |||
- in_key_event = true; | |||
- [edit interpretKeyEvents:[NSArray arrayWithObject:theEvent]]; | |||
- in_key_event = false; | |||
+ | |||
+ // Don't send cmd-<key> to interpretKeyEvents because it beeps. | |||
+ if (!(Fl::e_state & FL_META)) { | |||
+ NSText *edit = [[theEvent window] fieldEditor:YES forObject:nil]; | |||
+ in_key_event = true; | |||
+ [edit interpretKeyEvents:[NSArray arrayWithObject:theEvent]]; | |||
+ in_key_event = false; | |||
+ } | |||
} | |||
//NSLog(@"to text=%@ l=%d", [NSString stringWithUTF8String:Fl::e_text], Fl::e_length); | |||
int handled = Fl::handle(FL_KEYDOWN, window); | |||
@@ -1937,21 +2150,30 @@ | |||
//NSLog(@"insertText: received=%@",received); | |||
if (!in_key_event) fl_lock_function(); | |||
+ | |||
+ // Simple keyboard widgets do not want these side channel inputs. | |||
+ if (use_simple_keyboard) | |||
+ goto end; | |||
+ | |||
[FLView prepareEtext:received]; | |||
+ | |||
// We can get called outside of key events (e.g. from the character | |||
- // palette). Transform such actions to FL_PASTE events. | |||
+ // palette). We need to fake our own key event at that point. | |||
if (!in_key_event) { | |||
Fl_Window *target = [(FLWindow*)[self window] getFl_Window]; | |||
- Fl::handle(FL_PASTE, target); | |||
+ Fl::e_keysym = Fl::e_original_keysym = 0; | |||
+ Fl::handle(FL_KEYDOWN, target); | |||
// for some reason, the window does not redraw until the next mouse move or button push | |||
// sending a 'redraw()' or 'awake()' does not solve the issue! | |||
Fl::flush(); | |||
} | |||
+ | |||
+end: | |||
if (!in_key_event) fl_unlock_function(); | |||
} | |||
- (void)setMarkedText:(id)aString selectedRange:(NSRange)newSelection { | |||
- NSString *received; | |||
+ NSString *received, *current, *aggregate; | |||
if (newSelection.location == 0) { | |||
[self unmarkText]; | |||
return; | |||
@@ -1962,11 +2184,47 @@ | |||
received = (NSString*)aString; | |||
} | |||
//NSLog(@"setMarkedText: %@ %d %d",received,newSelection.location,newSelection.length); | |||
+ | |||
+ fl_lock_function(); | |||
+ | |||
+ // Simple keyboard widgets generally do not want these side channel | |||
+ // inputs, but we have no other way of getting dead keys so we make | |||
+ // an exception in that case. | |||
+ if (use_simple_keyboard) { | |||
+ if (in_key_event && (Fl::e_length == 0)) { | |||
+ [FLView prepareEtext:received]; | |||
+ | |||
+ Fl::e_text = (char*)cocoaDead2FLTK(Fl::e_text); | |||
+ Fl::e_length = strlen(Fl::e_text); | |||
+ } | |||
+ goto end; | |||
+ } | |||
+ | |||
// This code creates the OS X behaviour of seeing dead keys as things | |||
// are being composed. | |||
+ // | |||
+ // Note: The concatenation thing is because of how OS X deals with | |||
+ // invalid sequences. At that point it will spit out one call | |||
+ // to insertText with the now aborted sequence, and one new | |||
+ // call to setMarkedText with the new sequence. Since we want | |||
+ // both to be visible, we need to concatenate. | |||
next_compose_length = newSelection.location; | |||
- [FLView prepareEtext:received]; | |||
- //NSLog(@"Fl::e_text=%@ Fl::e_length=%d next_compose_length=%d", received, Fl::e_length, next_compose_length); | |||
+ current = [NSString stringWithUTF8String:Fl::e_text]; | |||
+ aggregate = [current stringByAppendingString:received]; | |||
+ | |||
+ [FLView prepareEtext:aggregate]; | |||
+ //NSLog(@"Fl::e_text=%@ Fl::e_length=%d next_compose_length=%d", aggregate, Fl::e_length, next_compose_length); | |||
+ | |||
+ // We can get called outside of key events (e.g. from the character | |||
+ // palette). We need to fake our own key event at that point. | |||
+ if (!in_key_event) { | |||
+ Fl_Window *target = [(FLWindow*)[self window] getFl_Window]; | |||
+ Fl::e_keysym = Fl::e_original_keysym = 0; | |||
+ Fl::handle(FL_KEYDOWN, target); | |||
+ } | |||
+ | |||
+end: | |||
+ fl_unlock_function(); | |||
} | |||
- (void)unmarkText { |
@@ -86,16 +86,14 @@ enum { ID_EXIT, ID_FULLSCREEN, ID_RESIZE, | |||
ID_CTRL, ID_ALT, ID_MENUKEY, ID_CTRLALTDEL, | |||
ID_REFRESH, ID_OPTIONS, ID_INFO, ID_ABOUT, ID_DISMISS }; | |||
// Fake key presses use this value and above | |||
static const int fakeKeyBase = 0x200; | |||
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), | |||
cursor(NULL), menuCtrlKey(false), menuAltKey(false) | |||
{ | |||
// FLTK STR #2599 must be fixed for proper dead keys support | |||
#ifdef HAVE_FLTK_DEAD_KEYS | |||
set_simple_keyboard(); | |||
#endif | |||
// FLTK STR #2636 gives us the ability to monitor clipboard changes | |||
#ifdef HAVE_FLTK_CLIPBOARD | |||
Fl::add_clipboard_notify(handleClipboardChange, this); | |||
@@ -399,26 +397,15 @@ int Viewport::handle(int event) | |||
// Release all keys that were pressed as that generally makes most | |||
// sense (e.g. Alt+Tab where we only see the Alt press) | |||
while (!downKeySym.empty()) | |||
handleKeyEvent(downKeySym.begin()->first, downKeySym.begin()->first, | |||
"", false); | |||
handleKeyRelease(downKeySym.begin()->first); | |||
return 1; | |||
case FL_KEYDOWN: | |||
if (menuKeyCode && (Fl::event_key() == menuKeyCode)) { | |||
popupContextMenu(); | |||
return 1; | |||
} | |||
handleKeyEvent(Fl::event_key(), Fl::event_original_key(), | |||
Fl::event_text(), true); | |||
handleFLTKKeyPress(); | |||
return 1; | |||
case FL_KEYUP: | |||
if (menuKeyCode && (Fl::event_key() == menuKeyCode)) | |||
return 1; | |||
handleKeyEvent(Fl::event_key(), Fl::event_original_key(), | |||
Fl::event_text(), false); | |||
handleKeyRelease(Fl::event_original_key()); | |||
return 1; | |||
} | |||
@@ -495,9 +482,156 @@ void Viewport::handlePointerTimeout(void *data) | |||
} | |||
rdr::U32 Viewport::translateKeyEvent(int keyCode, int origKeyCode, const char *keyText) | |||
void Viewport::handleKeyPress(int keyCode, rdr::U32 keySym) | |||
{ | |||
static bool menuRecursion = false; | |||
// Prevent recursion if the menu wants to send its own | |||
// activation key. | |||
if (menuKeySym && (keySym == menuKeySym) && !menuRecursion) { | |||
menuRecursion = true; | |||
popupContextMenu(); | |||
menuRecursion = false; | |||
return; | |||
} | |||
if (viewOnly) | |||
return; | |||
#ifdef __APPLE__ | |||
// Alt on OS X behaves more like AltGr on other systems, and to get | |||
// sane behaviour we should translate things in that manner for the | |||
// remote VNC server. However that means we lose the ability to use | |||
// Alt as a shortcut modifier. Do what RealVNC does and hijack the | |||
// left command key as an Alt replacement. | |||
switch (keySym) { | |||
case XK_Super_L: | |||
keySym = XK_Alt_L; | |||
break; | |||
case XK_Super_R: | |||
keySym = XK_Super_L; | |||
break; | |||
case XK_Alt_L: | |||
case XK_Alt_R: | |||
keySym = XK_ISO_Level3_Shift; | |||
break; | |||
} | |||
#endif | |||
#ifdef WIN32 | |||
// Ugly hack alert! | |||
// | |||
// Windows doesn't have a proper AltGr, but handles it using fake | |||
// Ctrl+Alt. Unfortunately X11 doesn't generally like the combination | |||
// Ctrl+Alt+AltGr, which we usually end up with when Xvnc tries to | |||
// get everything in the correct state. Cheat and temporarily release | |||
// Ctrl and Alt when we send some other symbol. | |||
bool ctrlPressed, altPressed; | |||
DownMap::iterator iter; | |||
ctrlPressed = false; | |||
altPressed = false; | |||
for (iter = downKeySym.begin();iter != downKeySym.end();++iter) { | |||
if (iter->second == XK_Control_L) | |||
ctrlPressed = true; | |||
else if (iter->second == XK_Alt_R) | |||
altPressed = true; | |||
} | |||
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); | |||
} catch (rdr::Exception& e) { | |||
vlog.error("%s", e.str()); | |||
exit_vncviewer(e.str()); | |||
} | |||
} | |||
#endif | |||
// Because of the way keyboards work, we cannot expect to have the same | |||
// symbol on release as when pressed. This breaks the VNC protocol however, | |||
// so we need to keep track of what keysym a key _code_ generated on press | |||
// and send the same on release. | |||
downKeySym[keyCode] = keySym; | |||
#if defined(WIN32) || defined(__APPLE__) | |||
vlog.debug("Key pressed: 0x%04x => 0x%04x", keyCode, keySym); | |||
#else | |||
vlog.debug("Key pressed: 0x%04x => XK_%s (0x%04x)", | |||
keyCode, XKeysymToString(keySym), keySym); | |||
#endif | |||
try { | |||
cc->writer()->keyEvent(keySym, true); | |||
} catch (rdr::Exception& e) { | |||
vlog.error("%s", e.str()); | |||
exit_vncviewer(e.str()); | |||
} | |||
#ifdef WIN32 | |||
// Ugly hack continued... | |||
if (ctrlPressed && altPressed) { | |||
vlog.debug("Restoring AltGr state"); | |||
try { | |||
cc->writer()->keyEvent(XK_Control_L, true); | |||
cc->writer()->keyEvent(XK_Alt_R, true); | |||
} catch (rdr::Exception& e) { | |||
vlog.error("%s", e.str()); | |||
exit_vncviewer(e.str()); | |||
} | |||
} | |||
#endif | |||
} | |||
void Viewport::handleKeyRelease(int keyCode) | |||
{ | |||
DownMap::iterator iter; | |||
if (viewOnly) | |||
return; | |||
iter = downKeySym.find(keyCode); | |||
if (iter == downKeySym.end()) { | |||
// These occur somewhat frequently so let's not spam them unless | |||
// logging is turned up. | |||
vlog.debug("Unexpected release of key code %d", keyCode); | |||
return; | |||
} | |||
#if defined(WIN32) || defined(__APPLE__) | |||
vlog.debug("Key released: 0x%04x => 0x%04x", keyCode, iter->second); | |||
#else | |||
vlog.debug("Key released: 0x%04x => XK_%s (0x%04x)", | |||
keyCode, XKeysymToString(iter->second), iter->second); | |||
#endif | |||
try { | |||
cc->writer()->keyEvent(iter->second, false); | |||
} catch (rdr::Exception& e) { | |||
vlog.error("%s", e.str()); | |||
exit_vncviewer(e.str()); | |||
} | |||
downKeySym.erase(iter); | |||
} | |||
rdr::U32 Viewport::translateKeyEvent(void) | |||
{ | |||
unsigned ucs; | |||
int keyCode, origKeyCode; | |||
const char *keyText; | |||
int keyTextLen; | |||
keyCode = Fl::event_key(); | |||
origKeyCode = Fl::event_original_key(); | |||
keyText = Fl::event_text(); | |||
keyTextLen = Fl::event_length(); | |||
vlog.debug("FLTK key %d (%d) '%s'[%d]", origKeyCode, keyCode, keyText, keyTextLen); | |||
// First check for function keys | |||
if ((keyCode > FL_F) && (keyCode <= FL_F_Last)) | |||
@@ -541,23 +675,6 @@ rdr::U32 Viewport::translateKeyEvent(int keyCode, int origKeyCode, const char *k | |||
} | |||
} | |||
#ifdef __APPLE__ | |||
// Alt on OS X behaves more like AltGr on other systems, and to get | |||
// sane behaviour we should translate things in that manner for the | |||
// remote VNC server. However that means we lose the ability to use | |||
// Alt as a shortcut modifier. Do what RealVNC does and hijack the | |||
// left command key as an Alt replacement. | |||
switch (keyCode) { | |||
case FL_Meta_L: | |||
return XK_Alt_L; | |||
case FL_Meta_R: | |||
return XK_Super_L; | |||
case FL_Alt_L: | |||
case FL_Alt_R: | |||
return XK_ISO_Level3_Shift; | |||
} | |||
#endif | |||
#if defined(WIN32) || defined(__APPLE__) | |||
// X11 fairly consistently uses XK_KP_Separator for comma and | |||
// XK_KP_Decimal for period. Windows and OS X are a different matter | |||
@@ -722,113 +839,15 @@ rdr::U32 Viewport::translateKeyEvent(int keyCode, int origKeyCode, const char *k | |||
} | |||
void Viewport::handleKeyEvent(int keyCode, int origKeyCode, const char *keyText, bool down) | |||
void Viewport::handleFLTKKeyPress(void) | |||
{ | |||
rdr::U32 keySym; | |||
if (viewOnly) | |||
return; | |||
// Because of the way keyboards work, we cannot expect to have the same | |||
// symbol on release as when pressed. This breaks the VNC protocol however, | |||
// so we need to keep track of what keysym a key _code_ generated on press | |||
// and send the same on release. | |||
if (!down) { | |||
DownMap::iterator iter; | |||
iter = downKeySym.find(origKeyCode); | |||
if (iter == downKeySym.end()) { | |||
vlog.error(_("Unexpected release of FLTK key code %d (0x%04x)"), | |||
origKeyCode, origKeyCode); | |||
return; | |||
} | |||
vlog.debug("Key released: 0x%04x => 0x%04x", origKeyCode, iter->second); | |||
try { | |||
cc->writer()->keyEvent(iter->second, false); | |||
} catch (rdr::Exception& e) { | |||
vlog.error("%s", e.str()); | |||
exit_vncviewer(e.str()); | |||
} | |||
downKeySym.erase(iter); | |||
return; | |||
} | |||
keySym = translateKeyEvent(keyCode, origKeyCode, keyText); | |||
keySym = translateKeyEvent(); | |||
if (keySym == NoSymbol) | |||
return; | |||
#ifdef WIN32 | |||
// Ugly hack alert! | |||
// | |||
// Windows doesn't have a proper AltGr, but handles it using fake | |||
// Ctrl+Alt. Unfortunately X11 doesn't generally like the combination | |||
// Ctrl+Alt+AltGr, which we usually end up with when Xvnc tries to | |||
// get everything in the correct state. Cheat and temporarily release | |||
// Ctrl and Alt whenever we get a key with a symbol. | |||
bool need_cheat = true; | |||
if (keyText[0] == '\0') | |||
need_cheat = false; | |||
else if ((downKeySym.find(FL_Control_L) == downKeySym.end()) && | |||
(downKeySym.find(FL_Control_R) == downKeySym.end())) | |||
need_cheat = false; | |||
else if ((downKeySym.find(FL_Alt_L) == downKeySym.end()) && | |||
(downKeySym.find(FL_Alt_R) == downKeySym.end())) | |||
need_cheat = false; | |||
if (need_cheat) { | |||
vlog.debug("Faking release of AltGr (Ctrl+Alt)"); | |||
try { | |||
if (downKeySym.find(FL_Control_L) != downKeySym.end()) | |||
cc->writer()->keyEvent(XK_Control_L, false); | |||
if (downKeySym.find(FL_Control_R) != downKeySym.end()) | |||
cc->writer()->keyEvent(XK_Control_R, false); | |||
if (downKeySym.find(FL_Alt_L) != downKeySym.end()) | |||
cc->writer()->keyEvent(XK_Alt_L, false); | |||
if (downKeySym.find(FL_Alt_R) != downKeySym.end()) | |||
cc->writer()->keyEvent(XK_Alt_R, false); | |||
} catch (rdr::Exception& e) { | |||
vlog.error("%s", e.str()); | |||
exit_vncviewer(e.str()); | |||
} | |||
} | |||
#endif | |||
vlog.debug("Key pressed: 0x%04x (0x%04x) '%s' => 0x%04x", | |||
origKeyCode, keyCode, keyText, keySym); | |||
downKeySym[origKeyCode] = keySym; | |||
try { | |||
cc->writer()->keyEvent(keySym, down); | |||
} catch (rdr::Exception& e) { | |||
vlog.error("%s", e.str()); | |||
exit_vncviewer(e.str()); | |||
} | |||
#ifdef WIN32 | |||
// Ugly hack continued... | |||
if (need_cheat) { | |||
vlog.debug("Restoring AltGr state"); | |||
try { | |||
if (downKeySym.find(FL_Control_L) != downKeySym.end()) | |||
cc->writer()->keyEvent(XK_Control_L, true); | |||
if (downKeySym.find(FL_Control_R) != downKeySym.end()) | |||
cc->writer()->keyEvent(XK_Control_R, true); | |||
if (downKeySym.find(FL_Alt_L) != downKeySym.end()) | |||
cc->writer()->keyEvent(XK_Alt_L, true); | |||
if (downKeySym.find(FL_Alt_R) != downKeySym.end()) | |||
cc->writer()->keyEvent(XK_Alt_R, true); | |||
} catch (rdr::Exception& e) { | |||
vlog.error(e.str()); | |||
exit_vncviewer(e.str()); | |||
} | |||
} | |||
#endif | |||
handleKeyPress(Fl::event_original_key(), keySym); | |||
} | |||
@@ -853,7 +872,7 @@ void Viewport::initContextMenu() | |||
contextMenu->add(_("Alt"), 0, NULL, (void*)ID_ALT, | |||
FL_MENU_TOGGLE | (menuAltKey?FL_MENU_VALUE:0)); | |||
if (menuKeyCode) { | |||
if (menuKeySym) { | |||
char sendMenuKey[64]; | |||
snprintf(sendMenuKey, 64, _("Send %s"), (const char *)menuKey); | |||
contextMenu->add(sendMenuKey, 0, NULL, (void*)ID_MENUKEY, 0); | |||
@@ -922,25 +941,31 @@ void Viewport::popupContextMenu() | |||
window()->size(w(), h()); | |||
break; | |||
case ID_CTRL: | |||
handleKeyEvent(FL_Control_L, FL_Control_L, "", m->value()); | |||
if (m->value()) | |||
handleKeyPress(fakeKeyBase + 0, XK_Control_L); | |||
else | |||
handleKeyRelease(fakeKeyBase + 0); | |||
menuCtrlKey = !menuCtrlKey; | |||
break; | |||
case ID_ALT: | |||
handleKeyEvent(FL_Alt_L, FL_Alt_L, "", m->value()); | |||
if (m->value()) | |||
handleKeyPress(fakeKeyBase + 1, XK_Alt_L); | |||
else | |||
handleKeyRelease(fakeKeyBase + 1); | |||
menuAltKey = !menuAltKey; | |||
break; | |||
case ID_MENUKEY: | |||
handleKeyEvent(menuKeyCode, menuKeyCode, "", true); | |||
handleKeyEvent(menuKeyCode, menuKeyCode, "", false); | |||
handleKeyPress(fakeKeyBase + 2, menuKeySym); | |||
handleKeyRelease(fakeKeyBase + 2); | |||
break; | |||
case ID_CTRLALTDEL: | |||
handleKeyEvent(FL_Control_L, FL_Control_L, "", true); | |||
handleKeyEvent(FL_Alt_L, FL_Alt_L, "", true); | |||
handleKeyEvent(FL_Delete, FL_Delete, "", true); | |||
handleKeyPress(fakeKeyBase + 3, XK_Control_L); | |||
handleKeyPress(fakeKeyBase + 4, XK_Alt_L); | |||
handleKeyPress(fakeKeyBase + 5, XK_Delete); | |||
handleKeyEvent(FL_Delete, FL_Delete, "", false); | |||
handleKeyEvent(FL_Alt_L, FL_Alt_L, "", false); | |||
handleKeyEvent(FL_Control_L, FL_Control_L, "", false); | |||
handleKeyRelease(fakeKeyBase + 5); | |||
handleKeyRelease(fakeKeyBase + 4); | |||
handleKeyRelease(fakeKeyBase + 3); | |||
break; | |||
case ID_REFRESH: | |||
cc->refreshFramebuffer(); | |||
@@ -966,7 +991,7 @@ void Viewport::popupContextMenu() | |||
void Viewport::setMenuKey() | |||
{ | |||
menuKeyCode = getMenuKeyCode(); | |||
getMenuKey(&menuKeyCode, &menuKeySym); | |||
} | |||
@@ -70,8 +70,11 @@ private: | |||
void handlePointerEvent(const rfb::Point& pos, int buttonMask); | |||
static void handlePointerTimeout(void *data); | |||
rdr::U32 translateKeyEvent(int keyCode, int origKeyCode, const char *keyText); | |||
void handleKeyEvent(int keyCode, int origKeyCode, const char *keyText, bool down); | |||
void handleKeyPress(int keyCode, rdr::U32 keySym); | |||
void handleKeyRelease(int keyCode); | |||
rdr::U32 translateKeyEvent(void); | |||
void handleFLTKKeyPress(void); | |||
void initContextMenu(); | |||
void popupContextMenu(); | |||
@@ -91,6 +94,7 @@ private: | |||
typedef std::map<int, rdr::U32> DownMap; | |||
DownMap downKeySym; | |||
rdr::U32 menuKeySym; | |||
int menuKeyCode; | |||
Fl_Menu_Button *contextMenu; | |||
@@ -20,31 +20,37 @@ | |||
#include <string.h> | |||
#include <FL/Fl.H> | |||
// FLTK can pull in the X11 headers on some systems | |||
#ifndef XK_VoidSymbol | |||
#define XK_MISCELLANY | |||
#include <rfb/keysymdef.h> | |||
#endif | |||
#include "menukey.h" | |||
#include "parameters.h" | |||
static const MenuKeySymbol menuSymbols[] = { | |||
{"F1", FL_F + 1}, | |||
{"F2", FL_F + 2}, | |||
{"F3", FL_F + 3}, | |||
{"F4", FL_F + 4}, | |||
{"F5", FL_F + 5}, | |||
{"F6", FL_F + 6}, | |||
{"F7", FL_F + 7}, | |||
{"F8", FL_F + 8}, | |||
{"F9", FL_F + 9}, | |||
{"F10", FL_F + 10}, | |||
{"F11", FL_F + 11}, | |||
{"F12", FL_F + 12}, | |||
{"Pause", FL_Pause}, | |||
{"Print", FL_Print}, | |||
{"Scroll_Lock", FL_Scroll_Lock}, | |||
{"Escape", FL_Escape}, | |||
{"Insert", FL_Insert}, | |||
{"Delete", FL_Delete}, | |||
{"Home", FL_Home}, | |||
{"Page_Up", FL_Page_Up}, | |||
{"Page_Down", FL_Page_Down}, | |||
{"F1", FL_F + 1, XK_F1}, | |||
{"F2", FL_F + 2, XK_F2}, | |||
{"F3", FL_F + 3, XK_F3}, | |||
{"F4", FL_F + 4, XK_F4}, | |||
{"F5", FL_F + 5, XK_F5}, | |||
{"F6", FL_F + 6, XK_F6}, | |||
{"F7", FL_F + 7, XK_F7}, | |||
{"F8", FL_F + 8, XK_F8}, | |||
{"F9", FL_F + 9, XK_F9}, | |||
{"F10", FL_F + 10, XK_F10}, | |||
{"F11", FL_F + 11, XK_F11}, | |||
{"F12", FL_F + 12, XK_F12}, | |||
{"Pause", FL_Pause, XK_Pause}, | |||
{"Print", FL_Print, XK_Print}, | |||
{"Scroll_Lock", FL_Scroll_Lock, XK_Scroll_Lock}, | |||
{"Escape", FL_Escape, XK_Escape}, | |||
{"Insert", FL_Insert, XK_Insert}, | |||
{"Delete", FL_Delete, XK_Delete}, | |||
{"Home", FL_Home, XK_Home}, | |||
{"Page_Up", FL_Page_Up, XK_Page_Up}, | |||
{"Page_Down", FL_Page_Down, XK_Page_Down}, | |||
}; | |||
int getMenuKeySymbolCount() | |||
@@ -57,15 +63,19 @@ const MenuKeySymbol* getMenuKeySymbols() | |||
return menuSymbols; | |||
} | |||
int getMenuKeyCode() | |||
void getMenuKey(int *keycode, rdr::U32 *keysym) | |||
{ | |||
const char *menuKeyStr; | |||
int menuKeyCode = 0; | |||
const char *menuKeyStr; | |||
menuKeyStr = menuKey; | |||
for(int i = 0; i < getMenuKeySymbolCount(); i++) | |||
if (!strcmp(menuSymbols[i].name, menuKeyStr)) | |||
menuKeyCode = menuSymbols[i].keycode; | |||
menuKeyStr = menuKey; | |||
for(int i = 0; i < getMenuKeySymbolCount(); i++) { | |||
if (!strcmp(menuSymbols[i].name, menuKeyStr)) { | |||
*keycode = menuSymbols[i].keycode; | |||
*keysym = menuSymbols[i].keysym; | |||
return; | |||
} | |||
} | |||
return menuKeyCode; | |||
*keycode = 0; | |||
*keysym = 0; | |||
} |
@@ -18,12 +18,15 @@ | |||
#ifndef __KEYSYM_H__ | |||
#define __KEYSYM_H__ | |||
#include <rdr/types.h> | |||
typedef struct { | |||
const char* name; | |||
int keycode; | |||
rdr::U32 keysym; | |||
} MenuKeySymbol; | |||
int getMenuKeyCode(); | |||
void getMenuKey(int *keycode, rdr::U32 *keysym); | |||
int getMenuKeySymbolCount(); | |||
const MenuKeySymbol* getMenuKeySymbols(); | |||