diff --git a/include/GL/freeglut_ext.h b/include/GL/freeglut_ext.h index 0c22c4f..f12324a 100644 --- a/include/GL/freeglut_ext.h +++ b/include/GL/freeglut_ext.h @@ -145,6 +145,7 @@ FGAPI void FGAPIENTRY glutSetMenuFont( int menuID, void* font ); /* * Window-specific callback functions, see fg_callbacks.c */ +FGAPI void FGAPIENTRY glutKeyboardExtFunc( void (* callback)( int, int, int ) ); FGAPI void FGAPIENTRY glutMouseWheelFunc( void (* callback)( int, int, int, int ) ); FGAPI void FGAPIENTRY glutPositionFunc( void (* callback)( int, int ) ); FGAPI void FGAPIENTRY glutCloseFunc( void (* callback)( void ) ); @@ -163,6 +164,15 @@ FGAPI void FGAPIENTRY glutSetWindowData(void* data); FGAPI void* FGAPIENTRY glutGetMenuData( void ); FGAPI void FGAPIENTRY glutSetMenuData(void* data); +/* + * Clipboard access + */ +#define GLUT_CLIPBOARD 0 +#define GLUT_PRIMARY 1 +#define GLUT_SECONDARY 2 +FGAPI void FGAPIENTRY glutSetClipboard(int selection, const char *string); +FGAPI const char * FGAPIENTRY glutGetClipboard(int selection); + /* * Font stuff, see fg_font.c */ diff --git a/include/GL/freeglut_std.h b/include/GL/freeglut_std.h index a658c7c..01b4611 100644 --- a/include/GL/freeglut_std.h +++ b/include/GL/freeglut_std.h @@ -120,7 +120,7 @@ * The freeglut and GLUT API versions */ #define FREEGLUT 1 -#define GLUT_API_VERSION 4 +#define GLUT_API_VERSION 6 #define GLUT_XLIB_IMPLEMENTATION 13 /* Deprecated: cf. http://sourceforge.net/mailarchive/forum.php?thread_name=CABcAi1hw7cr4xtigckaGXB5X8wddLfMcbA_rZ3NAuwMrX_zmsw%40mail.gmail.com&forum_name=freeglut-developer */ @@ -498,6 +498,7 @@ FGAPI void FGAPIENTRY glutMotionFunc( void (* callback)( int, int ) ); FGAPI void FGAPIENTRY glutPassiveMotionFunc( void (* callback)( int, int ) ); FGAPI void FGAPIENTRY glutEntryFunc( void (* callback)( int ) ); +FGAPI void FGAPIENTRY glutKeyboardDownFunc( void (* callback)( unsigned char, int, int ) ); FGAPI void FGAPIENTRY glutKeyboardUpFunc( void (* callback)( unsigned char, int, int ) ); FGAPI void FGAPIENTRY glutSpecialUpFunc( void (* callback)( int, int, int ) ); FGAPI void FGAPIENTRY glutJoystickFunc( void (* callback)( unsigned int, int, int, int ), int pollInterval ); diff --git a/src/fg_callbacks.c b/src/fg_callbacks.c index 11924b3..bd5c21a 100644 --- a/src/fg_callbacks.c +++ b/src/fg_callbacks.c @@ -124,6 +124,8 @@ void FGAPIENTRY glut##a##Func( FGCB##b callback ) \ /* Implement all these callback setter functions... */ IMPLEMENT_CALLBACK_FUNC(Position) IMPLEMENT_CALLBACK_FUNC(Keyboard) +IMPLEMENT_CALLBACK_FUNC(KeyboardExt) +IMPLEMENT_CALLBACK_FUNC(KeyboardDown) IMPLEMENT_CALLBACK_FUNC(KeyboardUp) IMPLEMENT_CALLBACK_FUNC(Special) IMPLEMENT_CALLBACK_FUNC(SpecialUp) diff --git a/src/fg_ext.c b/src/fg_ext.c index d96849a..0c79052 100644 --- a/src/fg_ext.c +++ b/src/fg_ext.c @@ -111,6 +111,7 @@ static GLUTproc fghGetGLUTProcAddress( const char* procName ) CHECK_NAME(glutMenuStatusFunc); CHECK_NAME(glutOverlayDisplayFunc); CHECK_NAME(glutWindowStatusFunc); + CHECK_NAME(glutKeyboardDownFunc); CHECK_NAME(glutKeyboardUpFunc); CHECK_NAME(glutSpecialUpFunc); CHECK_NAME(glutSetColor); @@ -186,7 +187,10 @@ static GLUTproc fghGetGLUTProcAddress( const char* procName ) CHECK_NAME(glutBitmapString); CHECK_NAME(glutStrokeString); CHECK_NAME(glutGetProcAddress); + CHECK_NAME(glutKeyboardExtFunc); CHECK_NAME(glutMouseWheelFunc); + CHECK_NAME(glutGetClipboard); + CHECK_NAME(glutSetClipboard); CHECK_NAME(glutJoystickGetNumAxes); CHECK_NAME(glutJoystickGetNumButtons); CHECK_NAME(glutJoystickNotWorking); diff --git a/src/fg_internal.h b/src/fg_internal.h index 3f0dc42..2b18f62 100644 --- a/src/fg_internal.h +++ b/src/fg_internal.h @@ -210,6 +210,8 @@ typedef void (* FGCBReshape )( int, int ); typedef void (* FGCBPosition )( int, int ); typedef void (* FGCBVisibility )( int ); typedef void (* FGCBKeyboard )( unsigned char, int, int ); +typedef void (* FGCBKeyboardExt )( int, int, int ); +typedef void (* FGCBKeyboardDown )( unsigned char, int, int ); typedef void (* FGCBKeyboardUp )( unsigned char, int, int ); typedef void (* FGCBSpecial )( int, int, int ); typedef void (* FGCBSpecialUp )( int, int, int ); @@ -595,6 +597,8 @@ enum WCB_Reshape, WCB_Position, WCB_Keyboard, + WCB_KeyboardExt, + WCB_KeyboardDown, WCB_KeyboardUp, WCB_Special, WCB_SpecialUp, @@ -1061,6 +1065,9 @@ void fgWarning( const char *fmt, ... ); SFG_Proc fgPlatformGetProcAddress( const char *procName ); +void fgPlatformSetClipboard(int selection, const char *text); +const char *fgPlatformGetClipboard(int selection); + /* pushing attribute/value pairs into an array */ #define ATTRIB(a) attributes[where++]=(a) #define ATTRIB_VAL(a,v) {ATTRIB(a); ATTRIB(v);} diff --git a/src/fg_window.c b/src/fg_window.c index 09fcddf..3e24ffd 100644 --- a/src/fg_window.c +++ b/src/fg_window.c @@ -357,6 +357,29 @@ void FGAPIENTRY glutSetIconTitle( const char* title ) } } +/* + * This function sets the clipboard content to the UTF-8 encoded text. + */ +void FGAPIENTRY glutSetClipboard(int selection, const char *text) +{ + FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutSetClipboard" ); + FREEGLUT_EXIT_IF_NO_WINDOW ( "glutSetClipboard" ); + + fgPlatformSetClipboard(selection, text); +} + +/* + * This function returns the clipboard content as UTF-8 encoded text, + * or NULL if no content was available. + */ +const char* FGAPIENTRY glutGetClipboard(int selection) +{ + FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutGetClipboard" ); + FREEGLUT_EXIT_IF_NO_WINDOW ( "glutGetClipboard" ); + + return fgPlatformGetClipboard(selection); +} + /* * Change the current window's size */ diff --git a/src/freeglutdll.def.in b/src/freeglutdll.def.in index 988d7d3..a0b80c6 100644 --- a/src/freeglutdll.def.in +++ b/src/freeglutdll.def.in @@ -65,6 +65,8 @@ EXPORTS glutEntryFunc glutCloseFunc glutWMCloseFunc + glutKeyboardExtFunc + glutKeyboardDownFunc glutKeyboardUpFunc glutSpecialUpFunc glutJoystickFunc diff --git a/src/mswin/fg_init_mswin.c b/src/mswin/fg_init_mswin.c index b1c51da..d68a2a4 100644 --- a/src/mswin/fg_init_mswin.c +++ b/src/mswin/fg_init_mswin.c @@ -36,6 +36,8 @@ extern LRESULT CALLBACK fgPlatformWindowProc( HWND hWnd, UINT uMsg, extern void fgPlatformInitSystemTime(); extern void fghCloseInputDevices(void); +char *fgClipboardBuffer[3] = { NULL, NULL, NULL }; + /* * A call to this function should initialize all the display stuff... @@ -150,12 +152,20 @@ void fgPlatformDeinitialiseInputDevices ( void ) void fgPlatformCloseDisplay ( void ) { + int i; + if( fgDisplay.pDisplay.DisplayName ) { free( fgDisplay.pDisplay.DisplayName ); fgDisplay.pDisplay.DisplayName = NULL; } + for (i = 0; i < 3; ++i) + { + free(fgClipboardBuffer[i]); + fgClipboardBuffer[i] = NULL; + } + /* Reset the timer granularity */ timeEndPeriod ( 1 ); } diff --git a/src/mswin/fg_main_mswin.c b/src/mswin/fg_main_mswin.c index 54ec12c..f9fbdab 100644 --- a/src/mswin/fg_main_mswin.c +++ b/src/mswin/fg_main_mswin.c @@ -28,6 +28,10 @@ #include #include "../fg_internal.h" +#ifndef MAPVK_VK_TO_CHAR +#define MAPVK_VK_TO_CHAR 2 +#endif + extern void fghRedrawWindow ( SFG_Window *window ); extern void fghRedrawWindowAndChildren ( SFG_Window *window ); extern void fghOnReshapeNotify(SFG_Window *window, int width, int height, GLboolean forceNotify); @@ -480,6 +484,81 @@ fg_time_t fgPlatformSystemTime ( void ) return currTime32 | timeEpoch << 32; } +extern char *fgClipboardBuffer[3]; + +void fgPlatformSetClipboard(int selection, const char *text) +{ + if (selection == GLUT_PRIMARY) + { + free(fgClipboardBuffer[GLUT_PRIMARY]); + fgClipboardBuffer[GLUT_PRIMARY] = strdup(text); + } + else if (selection == GLUT_SECONDARY) + { + free(fgClipboardBuffer[GLUT_SECONDARY]); + fgClipboardBuffer[GLUT_SECONDARY] = strdup(text); + } + else if (selection == GLUT_CLIPBOARD && text) + { + int n = MultiByteToWideChar(CP_UTF8, 0, text, -1, NULL, 0); + if (n > 0) + { + HANDLE object = GlobalAlloc(0, n * sizeof(WCHAR)); + if (object) + { + WCHAR *wtext = GlobalLock(object); + if (wtext) + { + MultiByteToWideChar(CP_UTF8, 0, text, -1, wtext, n); + GlobalUnlock(object); + if (OpenClipboard(NULL)) + { + EmptyClipboard(); + SetClipboardData(CF_UNICODETEXT, object); + CloseClipboard(); + } + } + GlobalFree(object); + } + } + } +} + +const char *fgPlatformGetClipboard(int selection) +{ + if (selection == GLUT_PRIMARY) + return fgClipboardBuffer[GLUT_PRIMARY]; + if (selection == GLUT_SECONDARY) + return fgClipboardBuffer[GLUT_SECONDARY]; + if (selection == GLUT_CLIPBOARD) + { + free(fgClipboardBuffer[GLUT_CLIPBOARD]); + fgClipboardBuffer[GLUT_CLIPBOARD] = NULL; + if (OpenClipboard(NULL)) + { + HANDLE object = GetClipboardData(CF_UNICODETEXT); + if (object) + { + WCHAR *wtext = GlobalLock(object); + if (wtext) + { + int n = WideCharToMultiByte(CP_UTF8, 0, wtext, -1, NULL, 0, NULL, NULL); + if (n > 0) + { + char *text = malloc(n); + fgClipboardBuffer[GLUT_CLIPBOARD] = text; + WideCharToMultiByte(CP_UTF8, 0, wtext, -1, text, n, NULL, NULL); + } + GlobalUnlock(object); + } + GlobalFree(object); + } + CloseClipboard(); + } + return fgClipboardBuffer[GLUT_CLIPBOARD]; + } + return NULL; +} void fgPlatformSleepForEvents( fg_time_t msec ) { @@ -664,9 +743,17 @@ static LRESULT fghWindowProcKeyPress(SFG_Window *window, UINT uMsg, GLboolean ke case VK_DELETE: /* The delete key should be treated as an ASCII keypress: */ if (keydown) + { + INVOKE_WCB( *window, KeyboardDown, + ( 127, window->State.MouseX, window->State.MouseY ) + ); + INVOKE_WCB( *window, KeyboardExt, + ( 127, window->State.MouseX, window->State.MouseY ) + ); INVOKE_WCB( *window, Keyboard, ( 127, window->State.MouseX, window->State.MouseY ) ); + } else INVOKE_WCB( *window, KeyboardUp, ( 127, window->State.MouseX, window->State.MouseY ) @@ -675,21 +762,19 @@ static LRESULT fghWindowProcKeyPress(SFG_Window *window, UINT uMsg, GLboolean ke #if !defined(_WIN32_WCE) default: - /* keydown displayable characters are handled with WM_CHAR message, but no corresponding up is generated. So get that here. */ - if (!keydown) + /* Mapped characters are handled with the WM_CHAR message. Handle low-level ASCII press/release callbacks here. */ { - BYTE state[ 256 ]; - WORD code[ 2 ]; - - GetKeyboardState( state ); - - if( ToAscii( (UINT)wParam, 0, state, code, 0 ) == 1 ) - wParam=code[ 0 ]; - - INVOKE_WCB( *window, KeyboardUp, - ( (char)(wParam & 0xFF), /* and with 0xFF to indicate to runtime that we want to strip out higher bits - otherwise we get a runtime error when "Smaller Type Checks" is enabled */ - window->State.MouseX, window->State.MouseY ) - ); + UINT ascii = (UINT)MapVirtualKey((UINT)wParam, MAPVK_VK_TO_CHAR); + if (ascii >= 32 && ascii < 256) + { + /* Always send lowercase (unshifted) values */ + if (ascii >= 'A' && ascii <= 'Z') + ascii = ascii - 'A' + 'a'; + if (keydown) + INVOKE_WCB(*window, KeyboardDown, ((unsigned char)ascii, window->State.MouseX, window->State.MouseY) ); + else + INVOKE_WCB(*window, KeyboardUp, ((unsigned char)ascii, window->State.MouseX, window->State.MouseY) ); + } } #endif } @@ -1375,10 +1460,13 @@ LRESULT CALLBACK fgPlatformWindowProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPAR break; fgState.Modifiers = fgPlatformGetModifiers( ); - INVOKE_WCB( *window, Keyboard, - ( (char)wParam, - window->State.MouseX, window->State.MouseY ) + INVOKE_WCB( *window, KeyboardExt, + ( (int)wParam, window->State.MouseX, window->State.MouseY ) ); + if (wParam < 256) + INVOKE_WCB( *window, Keyboard, + ( (unsigned char)wParam, window->State.MouseX, window->State.MouseY ) + ); fgState.Modifiers = INVALID_MODIFIERS; } break; diff --git a/src/x11/fg_init_x11.c b/src/x11/fg_init_x11.c index d2907c9..62b64fd 100644 --- a/src/x11/fg_init_x11.c +++ b/src/x11/fg_init_x11.c @@ -33,12 +33,16 @@ #include "fg_init.h" #include "egl/fg_init_egl.h" +#include + /* Return the atom associated with "name". */ static Atom fghGetAtom(const char * name) { return XInternAtom(fgDisplay.pDisplay.Display, name, False); } +char *fgClipboardBuffer[3] = { NULL, NULL, NULL }; + /* * Check if "property" is set on "window". The property's values are returned * through "data". If the property is set and is of type "type", return the @@ -256,10 +260,46 @@ void fgPlatformInitialize( const char* displayName ) fgDisplay.pDisplay.ClientMachine = fghGetAtom("WM_CLIENT_MACHINE"); } + /* Open an input method */ + setlocale(LC_ALL, ""); /* ugh! but we can't force the client to do it for us... */ + if (!XSupportsLocale()) + fgWarning("X doesn't support the current locale."); + if (!XSetLocaleModifiers("")) + fgWarning("Couldn't set X locale modifiers."); + fgDisplay.pDisplay.IM = XOpenIM(fgDisplay.pDisplay.Display, NULL, NULL, NULL); + if (!fgDisplay.pDisplay.IM) + fgWarning("Couldn't open X input method."); + else + { + XIMStyles *styles; + XIMStyle supported = XIMPreeditNothing | XIMStatusNothing; + XIMStyle best = 0; + unsigned int i; + char *res = XGetIMValues(fgDisplay.pDisplay.IM, XNQueryInputStyle, &styles, NULL); + if (res) + fgWarning("Couldn't get input method style: %s.", res); + else + { + for (i = 0; i < styles->count_styles; ++i) + { + XIMStyle style = styles->supported_styles[i]; + if ((style & supported) == style) + best = style; + } + fgDisplay.pDisplay.InputStyle = best; + } + XFree(styles); + if (best == 0) + { + fgWarning("Couldn't find a usable input method style."); + XCloseIM(fgDisplay.pDisplay.IM); + fgDisplay.pDisplay.IM = NULL; + } + } + /* Get start time */ fgState.Time = fgSystemTime(); - fgState.Initialised = GL_TRUE; atexit(fgDeinitialize); @@ -279,17 +319,28 @@ void fgPlatformDeinitialiseInputDevices ( void ) void fgPlatformCloseDisplay ( void ) { + int i; + /* * Make sure all X-client data we have created will be destroyed on * display closing */ XSetCloseDownMode( fgDisplay.pDisplay.Display, DestroyAll ); + if (fgDisplay.pDisplay.IM) + XCloseIM(fgDisplay.pDisplay.IM); + /* * Close the display connection, destroying all windows we have * created so far */ XCloseDisplay( fgDisplay.pDisplay.Display ); + + for (i = 0; i < 3; ++i) + { + free(fgClipboardBuffer[i]); + fgClipboardBuffer[i] = NULL; + } } diff --git a/src/x11/fg_internal_x11.h b/src/x11/fg_internal_x11.h index 09a238a..4b15584 100644 --- a/src/x11/fg_internal_x11.h +++ b/src/x11/fg_internal_x11.h @@ -63,6 +63,8 @@ struct tagSFG_PlatformDisplay int NetWMSupported; /* Flag for EWMH Window Managers */ Atom NetWMPid; /* The _NET_WM_PID atom */ Atom ClientMachine; /* The client machine name atom */ + XIM IM; /* The input method */ + XIMStyle InputStyle; /* The input method style */ #ifdef HAVE_X11_EXTENSIONS_XRANDR_H int prev_xsz, prev_ysz; @@ -107,6 +109,7 @@ struct tagSFG_PlatformContext #else GLXFBConfig FBConfig; /* The window's FBConfig */ #endif + XIC IC; /* The window's input context */ }; diff --git a/src/x11/fg_main_x11.c b/src/x11/fg_main_x11.c index 0e54253..bfb4f8f 100644 --- a/src/x11/fg_main_x11.c +++ b/src/x11/fg_main_x11.c @@ -29,8 +29,9 @@ #include #include "../fg_internal.h" #include +#include #include - +#include /* * Try to get the maximum value allowed for ints, falling back to the minimum @@ -579,6 +580,285 @@ __fg_unused static void fghPrintEvent( XEvent *event ) } } +/* UTF-8 decoding routine */ +enum +{ + Runeerror = 0xFFFD, /* decoding error in UTF */ + + Bit1 = 7, + Bitx = 6, + Bit2 = 5, + Bit3 = 4, + Bit4 = 3, + Bit5 = 2, + + T1 = ((1<<(Bit1+1))-1) ^ 0xFF, /* 0000 0000 */ + Tx = ((1<<(Bitx+1))-1) ^ 0xFF, /* 1000 0000 */ + T2 = ((1<<(Bit2+1))-1) ^ 0xFF, /* 1100 0000 */ + T3 = ((1<<(Bit3+1))-1) ^ 0xFF, /* 1110 0000 */ + T4 = ((1<<(Bit4+1))-1) ^ 0xFF, /* 1111 0000 */ + T5 = ((1<<(Bit5+1))-1) ^ 0xFF, /* 1111 1000 */ + + Rune1 = (1<<(Bit1+0*Bitx))-1, /* 0000 0000 0111 1111 */ + Rune2 = (1<<(Bit2+1*Bitx))-1, /* 0000 0111 1111 1111 */ + Rune3 = (1<<(Bit3+2*Bitx))-1, /* 1111 1111 1111 1111 */ + Rune4 = (1<<(Bit4+3*Bitx))-1, /* 0001 1111 1111 1111 1111 1111 */ + + Maskx = (1< T1 + */ + c = *(const unsigned char*)str; + if(c < Tx) { + *rune = c; + return 1; + } + + /* + * two character sequence + * 0080-07FF => T2 Tx + */ + c1 = *(const unsigned char*)(str+1) ^ Tx; + if(c1 & Testx) + goto bad; + if(c < T3) { + if(c < T2) + goto bad; + l = ((c << Bitx) | c1) & Rune2; + if(l <= Rune1) + goto bad; + *rune = l; + return 2; + } + + /* + * three character sequence + * 0800-FFFF => T3 Tx Tx + */ + c2 = *(const unsigned char*)(str+2) ^ Tx; + if(c2 & Testx) + goto bad; + if(c < T4) { + l = ((((c << Bitx) | c1) << Bitx) | c2) & Rune3; + if(l <= Rune2) + goto bad; + *rune = l; + return 3; + } + + /* + * four character sequence (21-bit value) + * 10000-1FFFFF => T4 Tx Tx Tx + */ + c3 = *(const unsigned char*)(str+3) ^ Tx; + if (c3 & Testx) + goto bad; + if (c < T5) { + l = ((((((c << Bitx) | c1) << Bitx) | c2) << Bitx) | c3) & Rune4; + if (l <= Rune3) + goto bad; + *rune = l; + return 4; + } + /* + * Support for 5-byte or longer UTF-8 would go here, but + * since we don't have that, we'll just fall through to bad. + */ + + /* + * bad decoding + */ +bad: + *rune = Bad; + return 1; +} + +extern char *fgClipboardBuffer[3]; + +static Atom fghGetAtom(const char *name) +{ + return XInternAtom(fgDisplay.pDisplay.Display, name, False); +} + +static void fgHandleSelectionNotify(XEvent *event) +{ + Display *dpy = fgDisplay.pDisplay.Display; + Atom actual_type; + int actual_format; + unsigned long item_count; + unsigned long bytes_after; + unsigned char *prop; + + if (event->xselection.property == None) + { + fgWarning("Couldn't convert selection to UTF-8 string."); + return; + } + + XGetWindowProperty(dpy, event->xselection.requestor, event->xselection.property, + 0, LONG_MAX, True, AnyPropertyType, + &actual_type, &actual_format, &item_count, &bytes_after, &prop); + + if (actual_type == fghGetAtom("UTF8_STRING")) + { + if (event->xselection.selection == fghGetAtom("CLIPBOARD")) + { + free(fgClipboardBuffer[GLUT_CLIPBOARD]); + fgClipboardBuffer[GLUT_CLIPBOARD] = strdup((char*)prop); + } + if (event->xselection.selection == XA_PRIMARY) + { + free(fgClipboardBuffer[GLUT_PRIMARY]); + fgClipboardBuffer[GLUT_PRIMARY] = strdup((char*)prop); + } + if (event->xselection.selection == XA_SECONDARY) + { + free(fgClipboardBuffer[GLUT_SECONDARY]); + fgClipboardBuffer[GLUT_SECONDARY] = strdup((char*)prop); + } + } + + XFree(prop); +} + +static void fgHandleSelectionClear(XEvent *event) +{ + if (event->xselectionclear.selection == fghGetAtom("CLIPBOARD")) + { + free(fgClipboardBuffer[GLUT_CLIPBOARD]); + fgClipboardBuffer[GLUT_CLIPBOARD] = NULL; + } + else if (event->xselectionclear.selection == XA_PRIMARY) + { + free(fgClipboardBuffer[GLUT_PRIMARY]); + fgClipboardBuffer[GLUT_PRIMARY] = NULL; + } + else if (event->xselectionclear.selection == XA_SECONDARY) + { + free(fgClipboardBuffer[GLUT_SECONDARY]); + fgClipboardBuffer[GLUT_SECONDARY] = NULL; + } +} + +static void fgHandleSelectionRequest(XEvent *event) +{ + Display *dpy = fgDisplay.pDisplay.Display; + Window requestor = event->xselectionrequest.requestor; + Atom selection = event->xselectionrequest.selection; + Atom target = event->xselectionrequest.target; + Atom property = event->xselectionrequest.property; + Atom time = event->xselectionrequest.time; + XEvent response; + char *text; + + if (property == None) + property = target; + + response.xselection.type = SelectionNotify; + response.xselection.send_event = True; + response.xselection.display = dpy; + response.xselection.requestor = requestor; + response.xselection.selection = selection; + response.xselection.target = target; + response.xselection.property = property; + response.xselection.time = time; + + if (selection == fghGetAtom("CLIPBOARD")) + text = fgClipboardBuffer[GLUT_CLIPBOARD]; + else if (selection == XA_PRIMARY) + text = fgClipboardBuffer[GLUT_PRIMARY]; + else if (selection == XA_SECONDARY) + text = fgClipboardBuffer[GLUT_SECONDARY]; + else + return; + if (!text) + return; + + if (target == fghGetAtom("TARGETS")) + { + Atom list[4] = { + fghGetAtom("TARGETS"), + fghGetAtom("TIMESTAMP"), + XA_STRING, + fghGetAtom("UTF8_STRING") + }; + XChangeProperty(dpy, requestor, property, target, + 32, PropModeReplace, (unsigned char *)list, sizeof(list)/sizeof(Atom)); + } + else if (target == XA_STRING || target == fghGetAtom("UTF8_STRING")) + { + XChangeProperty(dpy, requestor, property, target, + 8, PropModeReplace, (unsigned char *)text, strlen(text)); + } + + XSendEvent(dpy, requestor, False, 0, &response); +} + +void fgPlatformSetClipboard(int selection, const char *text) +{ + Display *dpy = fgDisplay.pDisplay.Display; + Window window = fgStructure.CurrentWindow->Window.Handle; + Atom xselection; + if (selection == GLUT_CLIPBOARD) + xselection = fghGetAtom("CLIPBOARD"); + else if (selection == GLUT_PRIMARY) + xselection = XA_PRIMARY; + else if (selection == GLUT_SECONDARY) + xselection = XA_SECONDARY; + else + return; + + free(fgClipboardBuffer[selection]); + fgClipboardBuffer[selection] = strdup(text); + + XSetSelectionOwner(dpy, xselection, window, CurrentTime); +} + +static Bool isSelectionNotify(Display *dpi, XEvent *event, XPointer arg) +{ + return (event->type == SelectionNotify); +} + +const char *fgPlatformGetClipboard(int selection) +{ + Display *dpy = fgDisplay.pDisplay.Display; + Window window = fgStructure.CurrentWindow->Window.Handle; + Atom xselection; + Window owner; + XEvent event; + + if (selection == GLUT_CLIPBOARD) + xselection = fghGetAtom("CLIPBOARD"); + else if (selection == GLUT_PRIMARY) + xselection = XA_PRIMARY; + else if (selection == GLUT_SECONDARY) + xselection = XA_SECONDARY; + else + return NULL; + + owner = XGetSelectionOwner(dpy, xselection); + if (!owner) + return NULL; + if (owner != window) + { + XConvertSelection(dpy, xselection, fghGetAtom("UTF8_STRING"), xselection, window, CurrentTime); + XIfEvent(dpy, &event, isSelectionNotify, NULL); + fgHandleSelectionNotify(&event); + } + + return fgClipboardBuffer[selection]; +} void fgPlatformProcessSingleEvent ( void ) { @@ -603,6 +883,8 @@ void fgPlatformProcessSingleEvent ( void ) #if _DEBUG fghPrintEvent( &event ); #endif + if (XFilterEvent(&event, None)) + continue; switch( event.type ) { @@ -631,6 +913,16 @@ void fgPlatformProcessSingleEvent ( void ) } break; + case SelectionClear: + fgHandleSelectionClear(&event); + break; + case SelectionRequest: + fgHandleSelectionRequest(&event); + break; + case SelectionNotify: + fgHandleSelectionNotify(&event); + break; + /* * CreateNotify causes a configure-event so that sub-windows are * handled compatibly with GLUT. Otherwise, your sub-windows @@ -890,8 +1182,10 @@ void fgPlatformProcessSingleEvent ( void ) case KeyRelease: case KeyPress: { - FGCBKeyboard keyboard_cb; + FGCBKeyboardExt keyboard_ext_cb; + FGCBKeyboard keyboard_cb, keyboard_low_cb; FGCBSpecial special_cb; + int did_keyboard_cb = 0; GETWINDOW( xkey ); GETMOUSE( xkey ); @@ -932,108 +1226,154 @@ void fgPlatformProcessSingleEvent ( void ) if( event.type == KeyPress ) { + keyboard_ext_cb = (FGCBKeyboardExt)( FETCH_WCB( *window, KeyboardExt )); + keyboard_low_cb = (FGCBKeyboard)( FETCH_WCB( *window, KeyboardDown )); keyboard_cb = (FGCBKeyboard)( FETCH_WCB( *window, Keyboard )); special_cb = (FGCBSpecial) ( FETCH_WCB( *window, Special )); } else { - keyboard_cb = (FGCBKeyboard)( FETCH_WCB( *window, KeyboardUp )); + keyboard_ext_cb = NULL; + keyboard_low_cb = (FGCBKeyboard)( FETCH_WCB( *window, KeyboardUp )); + keyboard_cb = NULL; special_cb = (FGCBSpecial) ( FETCH_WCB( *window, SpecialUp )); } - /* Is there a keyboard/special callback hooked for this window? */ - if( keyboard_cb || special_cb ) + /* Is there a character keyboard callback hooked for this window? */ + if (keyboard_ext_cb || keyboard_cb) { - XComposeStatus composeStatus; - char asciiCode[ 32 ]; + static XComposeStatus composeStatus = { 0 }; /* keep state across invocations */ + XIC ic = window->Window.pContext.IC; + Status status; + char buf[32], *utf8 = buf; KeySym keySym; - int len; + int i, c, len; - /* Check for the ASCII/KeySym codes associated with the event: */ - len = XLookupString( &event.xkey, asciiCode, sizeof(asciiCode), - &keySym, &composeStatus - ); - - /* GLUT API tells us to have two separate callbacks... */ - if( len > 0 ) + /* Check for the Unicode text associated with the event: */ + if (ic) { - /* ...one for the ASCII translateable keypresses... */ - if( keyboard_cb ) + len = Xutf8LookupString(ic, &event.xkey, buf, sizeof buf, &keySym, &status); + if (status == XBufferOverflow) { - fgSetWindow( window ); - fgState.Modifiers = fgPlatformGetModifiers( event.xkey.state ); - keyboard_cb( asciiCode[ 0 ], - event.xkey.x, event.xkey.y - ); - fgState.Modifiers = INVALID_MODIFIERS; + utf8 = malloc(len); + len = Xutf8LookupString(ic, &event.xkey, utf8, len, &keySym, &status); } } else { - int special = -1; + len = XLookupString(&event.xkey, buf, sizeof buf, &keySym, &composeStatus); + } - /* - * ...and one for all the others, which need to be - * translated to GLUT_KEY_Xs... - */ - switch( keySym ) - { - case XK_F1: special = GLUT_KEY_F1; break; - case XK_F2: special = GLUT_KEY_F2; break; - case XK_F3: special = GLUT_KEY_F3; break; - case XK_F4: special = GLUT_KEY_F4; break; - case XK_F5: special = GLUT_KEY_F5; break; - case XK_F6: special = GLUT_KEY_F6; break; - case XK_F7: special = GLUT_KEY_F7; break; - case XK_F8: special = GLUT_KEY_F8; break; - case XK_F9: special = GLUT_KEY_F9; break; - case XK_F10: special = GLUT_KEY_F10; break; - case XK_F11: special = GLUT_KEY_F11; break; - case XK_F12: special = GLUT_KEY_F12; break; - - case XK_KP_Left: - case XK_Left: special = GLUT_KEY_LEFT; break; - case XK_KP_Right: - case XK_Right: special = GLUT_KEY_RIGHT; break; - case XK_KP_Up: - case XK_Up: special = GLUT_KEY_UP; break; - case XK_KP_Down: - case XK_Down: special = GLUT_KEY_DOWN; break; - - case XK_KP_Prior: - case XK_Prior: special = GLUT_KEY_PAGE_UP; break; - case XK_KP_Next: - case XK_Next: special = GLUT_KEY_PAGE_DOWN; break; - case XK_KP_Home: - case XK_Home: special = GLUT_KEY_HOME; break; - case XK_KP_End: - case XK_End: special = GLUT_KEY_END; break; - case XK_KP_Insert: - case XK_Insert: special = GLUT_KEY_INSERT; break; - - case XK_Num_Lock : special = GLUT_KEY_NUM_LOCK; break; - case XK_KP_Begin : special = GLUT_KEY_BEGIN; break; - case XK_KP_Delete: special = GLUT_KEY_DELETE; break; - - case XK_Shift_L: special = GLUT_KEY_SHIFT_L; break; - case XK_Shift_R: special = GLUT_KEY_SHIFT_R; break; - case XK_Control_L: special = GLUT_KEY_CTRL_L; break; - case XK_Control_R: special = GLUT_KEY_CTRL_R; break; - case XK_Alt_L: special = GLUT_KEY_ALT_L; break; - case XK_Alt_R: special = GLUT_KEY_ALT_R; break; - } + if (len > 0) + { + fgSetWindow(window); + fgState.Modifiers = fgPlatformGetModifiers(event.xkey.state); - /* - * Execute the callback (if one has been specified), - * given that the special code seems to be valid... - */ - if( special_cb && (special != -1) ) + i = 0; + while (i < len) { - fgSetWindow( window ); - fgState.Modifiers = fgPlatformGetModifiers( event.xkey.state ); - special_cb( special, event.xkey.x, event.xkey.y ); - fgState.Modifiers = INVALID_MODIFIERS; + i += chartorune(&c, utf8 + i); + + /* ...for the Unicode translateable keypresses... */ + if (keyboard_ext_cb) + keyboard_ext_cb(c, event.xkey.x, event.xkey.y); + + /* ...for the Latin-1 translateable keypresses... */ + if (keyboard_cb) + if (c < 256) + keyboard_cb(c, event.xkey.x, event.xkey.y); } + + fgState.Modifiers = INVALID_MODIFIERS; + + did_keyboard_cb = 1; + } + + if (utf8 != buf) + free(utf8); + } + + /* Is there a low-level keyboard callback hooked for this window? */ + if (keyboard_low_cb || special_cb) + { + int special = -1; + int ascii = 0; + + KeySym keySym = XLookupKeysym(&event.xkey, 0); + + /* ...for low-level keys, which need to be + * translated to GLUT_KEY_Xs or ASCII values... + */ + switch( keySym ) + { + case XK_F1: special = GLUT_KEY_F1; break; + case XK_F2: special = GLUT_KEY_F2; break; + case XK_F3: special = GLUT_KEY_F3; break; + case XK_F4: special = GLUT_KEY_F4; break; + case XK_F5: special = GLUT_KEY_F5; break; + case XK_F6: special = GLUT_KEY_F6; break; + case XK_F7: special = GLUT_KEY_F7; break; + case XK_F8: special = GLUT_KEY_F8; break; + case XK_F9: special = GLUT_KEY_F9; break; + case XK_F10: special = GLUT_KEY_F10; break; + case XK_F11: special = GLUT_KEY_F11; break; + case XK_F12: special = GLUT_KEY_F12; break; + + case XK_KP_Left: + case XK_Left: special = GLUT_KEY_LEFT; break; + case XK_KP_Right: + case XK_Right: special = GLUT_KEY_RIGHT; break; + case XK_KP_Up: + case XK_Up: special = GLUT_KEY_UP; break; + case XK_KP_Down: + case XK_Down: special = GLUT_KEY_DOWN; break; + + case XK_KP_Prior: + case XK_Prior: special = GLUT_KEY_PAGE_UP; break; + case XK_KP_Next: + case XK_Next: special = GLUT_KEY_PAGE_DOWN; break; + case XK_KP_Home: + case XK_Home: special = GLUT_KEY_HOME; break; + case XK_KP_End: + case XK_End: special = GLUT_KEY_END; break; + case XK_KP_Insert: + case XK_Insert: special = GLUT_KEY_INSERT; break; + + case XK_Num_Lock : special = GLUT_KEY_NUM_LOCK; break; + case XK_KP_Begin : special = GLUT_KEY_BEGIN; break; + case XK_KP_Delete: special = GLUT_KEY_DELETE; break; + + case XK_Shift_L: special = GLUT_KEY_SHIFT_L; break; + case XK_Shift_R: special = GLUT_KEY_SHIFT_R; break; + case XK_Control_L: special = GLUT_KEY_CTRL_L; break; + case XK_Control_R: special = GLUT_KEY_CTRL_R; break; + case XK_Alt_L: special = GLUT_KEY_ALT_L; break; + case XK_Alt_R: special = GLUT_KEY_ALT_R; break; + default: + if( keySym >= XK_space && keySym <= XK_ydiaeresis ) + ascii = keySym; + break; + } + + /* + * Execute the callback (if one has been specified), + * given that the special code seems to be valid... + * But only if we haven't already sent translated text for it, + * such as numeric keypad keys with numlock on. + */ + if( special_cb && (special != -1) && !did_keyboard_cb ) + { + fgSetWindow( window ); + fgState.Modifiers = fgPlatformGetModifiers( event.xkey.state ); + special_cb( special, event.xkey.x, event.xkey.y ); + fgState.Modifiers = INVALID_MODIFIERS; + } + else if( keyboard_low_cb && (ascii >= 32 && ascii < 256) ) + { + fgSetWindow( window ); + fgState.Modifiers = fgPlatformGetModifiers( event.xkey.state ); + keyboard_low_cb( ascii, event.xkey.x, event.xkey.y ); + fgState.Modifiers = INVALID_MODIFIERS; } } } diff --git a/src/x11/fg_window_x11.c b/src/x11/fg_window_x11.c index 90f1966..f097d39 100644 --- a/src/x11/fg_window_x11.c +++ b/src/x11/fg_window_x11.c @@ -154,6 +154,7 @@ void fgPlatformOpenWindow( SFG_Window* window, const char* title, unsigned long mask; unsigned int current_DisplayMode = fgState.DisplayMode ; XEvent fakeEvent = {0}; + long event_mask; /* Save the display mode if we are creating a menu window */ if( window->IsMenu && ( ! fgStructure.MenuContext ) ) @@ -223,11 +224,6 @@ void fgPlatformOpenWindow( SFG_Window* window, const char* title, * XXX more pleasant to trace. (Think mouse-motion! Tons of * XXX ``bonus'' GUI events stream in.) */ - winAttr.event_mask = - StructureNotifyMask | SubstructureNotifyMask | ExposureMask | - ButtonPressMask | ButtonReleaseMask | KeyPressMask | KeyReleaseMask | - VisibilityChangeMask | EnterWindowMask | LeaveWindowMask | - PointerMotionMask | ButtonMotionMask; winAttr.background_pixmap = None; winAttr.background_pixel = 0; winAttr.border_pixel = 0; @@ -237,7 +233,7 @@ void fgPlatformOpenWindow( SFG_Window* window, const char* title, visualInfo->visual, AllocNone ); - mask = CWBackPixmap | CWBorderPixel | CWColormap | CWEventMask; + mask = CWBackPixmap | CWBorderPixel | CWColormap; if( window->IsMenu || ( gameMode == GL_TRUE ) ) { @@ -260,6 +256,29 @@ void fgPlatformOpenWindow( SFG_Window* window, const char* title, &winAttr ); + event_mask = + StructureNotifyMask | SubstructureNotifyMask | ExposureMask | + ButtonPressMask | ButtonReleaseMask | KeyPressMask | KeyReleaseMask | + VisibilityChangeMask | EnterWindowMask | LeaveWindowMask | + PointerMotionMask | ButtonMotionMask; + + /* Create input context */ + window->Window.pContext.IC = NULL; + if (fgDisplay.pDisplay.IM) + { + long im_event_mask; + window->Window.pContext.IC = + XCreateIC(fgDisplay.pDisplay.IM, + XNInputStyle, fgDisplay.pDisplay.InputStyle, + XNClientWindow, window->Window.Handle, + XNFocusWindow, window->Window.Handle, + NULL); + XGetICValues(window->Window.pContext.IC, XNFilterEvents, &im_event_mask, NULL); + event_mask |= im_event_mask; + XSetICFocus(window->Window.pContext.IC); + } + XSelectInput(fgDisplay.pDisplay.Display, window->Window.Handle, event_mask); + /* Fake configure event to force viewport setup * even with no window manager. */ @@ -466,6 +485,10 @@ void fgPlatformCloseWindow( SFG_Window* window ) window->Window.pContext.FBConfig = NULL; #endif + if (window->Window.pContext.IC) { + XDestroyIC(window->Window.pContext.IC); + } + if( window->Window.Handle ) { XDestroyWindow( fgDisplay.pDisplay.Display, window->Window.Handle ); }