Index: linden/indra/newview/linux_tools/wrapper.sh =================================================================== --- linden/indra/newview/linux_tools/wrapper.sh (.../tags/1.19.0.4) (revision 520) +++ linden/indra/newview/linux_tools/wrapper.sh (.../branches/1.19.0-GTK) (revision 520) @@ -55,6 +55,14 @@ export GTK_IM_MODULE=xim fi +## - Second Life requires C locale compatible numeric formatting, while GTK prefers to run under the user's locale. Find a compromise. +if [ "$LC_ALL" != "" ]; then + export LANG="$LC_ALL" + unset `env | sed -n "/^LC_/s/=.*//p"` +fi +if [ "$LANG" != "" -o "$LC_NUMERIC" != "" ]; then + export LC_NUMERIC="C" +fi ## Nothing worth editing below this line. ##------------------------------------------------------------------- Index: linden/indra/newview/llfilepicker.h =================================================================== --- linden/indra/newview/llfilepicker.h (.../tags/1.19.0.4) (revision 520) +++ linden/indra/newview/llfilepicker.h (.../branches/1.19.0-GTK) (revision 520) @@ -63,11 +63,6 @@ # include "gtk/gtk.h" #endif // LL_GTK -// also mostly for Linux, for some X11-specific filepicker usability tweaks -#if LL_X11 -#include "SDL/SDL_syswm.h" -#endif - #if LL_GTK // we use an aggregate structure so we can pass its pointer through a C callback typedef struct { Index: linden/indra/newview/llfilepicker.cpp =================================================================== --- linden/indra/newview/llfilepicker.cpp (.../tags/1.19.0.4) (revision 520) +++ linden/indra/newview/llfilepicker.cpp (.../branches/1.19.0-GTK) (revision 520) @@ -1049,23 +1049,8 @@ this_path->second.c_str()); } -# if LL_X11 - // Make GTK tell the window manager to associate this - // dialog with our non-GTK raw X11 window, which should try - // to keep it on top etc. - Window XWindowID = get_SDL_XWindowID(); - if (None != XWindowID) - { - gtk_widget_realize(GTK_WIDGET(win)); // so we can get its gdkwin - GdkWindow *gdkwin = gdk_window_foreign_new(XWindowID); - gdk_window_set_transient_for(GTK_WIDGET(win)->window, - gdkwin); - } - else - { - llwarns << "Hmm, couldn't get xwid to use for transient." << llendl; - } -# endif //LL_X11 + LLWindowSDL * const sdl_viewer_window = static_cast (gViewerWindow->getWindow()); + gtk_window_set_transient_for(GTK_WINDOW(win), GTK_WINDOW(sdl_viewer_window->getGtkWindow())); g_signal_connect (GTK_FILE_CHOOSER(win), "response", Index: linden/indra/llwindow/llkeyboardsdl.h =================================================================== --- linden/indra/llwindow/llkeyboardsdl.h (.../tags/1.19.0.4) (revision 520) +++ linden/indra/llwindow/llkeyboardsdl.h (.../branches/1.19.0-GTK) (revision 520) @@ -33,12 +33,14 @@ #define LL_LLKEYBOARDSDL_H #include "llkeyboard.h" -#include "SDL/SDL.h" +#include "gtk/gtk.h" +class LLWindowSDL; + class LLKeyboardSDL : public LLKeyboard { public: - LLKeyboardSDL(); + LLKeyboardSDL(LLWindowSDL *window); /*virtual*/ ~LLKeyboardSDL() {}; /*virtual*/ BOOL handleKeyUp(const U16 key, MASK mask); @@ -52,6 +54,8 @@ void setModifierKeyLevel( KEY key, BOOL new_state ); BOOL translateNumpadKey( const U16 os_key, KEY *translated_key ); U16 inverseTranslateNumpadKey(const KEY translated_key); +protected: + LLWindowSDL * const mWindow; private: std::map mTranslateNumpadMap; // special map for translating OS keys to numpad keys std::map mInvTranslateNumpadMap; // inverse of the above Index: linden/indra/llwindow/llwindowsdl.h =================================================================== --- linden/indra/llwindow/llwindowsdl.h (.../tags/1.19.0.4) (revision 520) +++ linden/indra/llwindow/llwindowsdl.h (.../branches/1.19.0-GTK) (revision 520) @@ -35,15 +35,9 @@ // Simple Directmedia Layer (http://libsdl.org/) implementation of LLWindow class #include "llwindow.h" +#include "llpreeditor.h" +#include "gtk/gtk.h" -#include "SDL/SDL.h" -#include "SDL/SDL_endian.h" - -#if LL_X11 -// get X11-specific headers for use in low-level stuff like copy-and-paste support -#include "SDL/SDL_syswm.h" -#endif - // AssertMacros.h does bad things. #undef verify #undef check @@ -60,7 +54,6 @@ /*virtual*/ BOOL getMinimized(); /*virtual*/ BOOL getMaximized(); /*virtual*/ BOOL maximize(); - /*virtual*/ BOOL getFullscreen(); /*virtual*/ BOOL getPosition(LLCoordScreen *position); /*virtual*/ BOOL getSize(LLCoordScreen *size); /*virtual*/ BOOL getSize(LLCoordWindow *size); @@ -118,16 +111,12 @@ /*virtual*/ void *getPlatformWindow(); /*virtual*/ void bringToFront(); - // Not great that these are public, but they have to be accessible - // by non-class code and it's better than making them global. -#if LL_X11 - // These are set up by the X11 clipboard initialization code - Window mSDL_XWindowID; - Display *mSDL_Display; -#endif - void (*Lock_Display)(void); - void (*Unlock_Display)(void); + /*virtual*/ void allowLanguageTextInput(LLPreeditor *preeditor, BOOL b); + /*virtual*/ void updateLanguageTextInputArea(); + /*virtual*/ void interruptLanguageTextInput(); + GtkWidget * getGtkWindow() const { return mWindow; } + protected: LLWindowSDL( char *title, int x, int y, int width, int height, U32 flags, @@ -140,72 +129,96 @@ BOOL isValid(); void moveWindow(const LLCoordScreen& position,const LLCoordScreen& size); - - // Changes display resolution. Returns true if successful - BOOL setDisplayResolution(S32 width, S32 height, S32 bits, S32 refresh); - - // Go back to last fullscreen display resolution. - BOOL setFullscreenResolution(); - void minimize(); void restore(); BOOL shouldPostQuit() { return mPostQuit; } - protected: // // Platform specific methods // - // create or re-create the GL context/window. Called from the constructor and switchContext(). - BOOL createContext(int x, int y, int width, int height, int bits, BOOL fullscreen, BOOL disable_vsync); + // createContext creates the GL context/window. Called from the constructor. + BOOL createContext(S32 x, S32 y, S32 width, S32 height, BOOL fullscreen, BOOL disable_vsync); void destroyContext(); void setupFailure(const char* text, const char* caption, U32 type); - void adjustCursorDecouple(bool warpingMouse = false); void fixWindowSize(void); - U32 SDLCheckGrabbyKeys(SDLKey keysym, BOOL gain); - BOOL SDLReallyCaptureInput(BOOL capture); + void setupX11Extensions(); + void initializeGamma(); + void getMonitorResolution(S32 *width, S32 *height); + void switchMonitorResolution(S32 width, S32 height); + void enterFullscreenResolution(); + void leaveFullscreenResolution(); + + U32 checkGrabbyKeys(guint keysym, BOOL gain); + void reallyCaptureInput(BOOL capture); + + // input method management methods. + void createIMContext(); + void destroyIMContext(); + void updateIMFocus(); + +public: + // GTK signal handlers. We need to make them public, since we + // want to call them from C functions (C-C++ thunks). We could + // make them private or protected otherwise. + + // GtkWindow event handlers: + gboolean handleGtkMotionNotifyEvent (GdkEventMotion *event); + gboolean handleGtkKeyPressEvent (GdkEventKey *event); + gboolean handleGtkKeyReleaseEvent (GdkEventKey *event); + gboolean handleGtkButtonPressEvent (GdkEventButton *event); + gboolean handleGtkButtonReleaseEvent (GdkEventButton *event); + gboolean handleGtkScrollEvent (GdkEventScroll *event); + gboolean handleGtkFocusInEvent (GdkEventFocus *event); + gboolean handleGtkFocusOutEvent (GdkEventFocus *event); + gboolean handleGtkExposeEvent (GdkEventExpose *event); + gboolean handleGtkConfigureEvent (GdkEventConfigure *event); + gboolean handleGtkWindowStateEvent (GdkEventWindowState *event); + gboolean handleGtkDeleteEvent (GdkEvent *event); + + // Input method (GtkIMContext) signal handlers: + void handleGtkCommitSignal (const gchar *committed_text); + void handleGtkPreeditChangedSignal (); + void handleGtkRetrieveSurroundingSignal (); + gboolean handleGtkDeleteSurroundingSignal (gint offset, gint n_chars); + +protected: // // Platform specific variables // - U32 mGrabbyKeyFlags; - int mReallyCapturedCount; - SDL_Surface * mWindow; - char * mWindowTitle; - double mOriginalAspectRatio; - BOOL mCursorDecoupled; - S32 mCursorLastEventDeltaX; - S32 mCursorLastEventDeltaY; - BOOL mCursorIgnoreNextDelta; - BOOL mNeedsResize; // Constructor figured out the window is too big, it needs a resize. - LLCoordScreen mNeedsResizeSize; + U32 mGrabbyKeyFlags; + std::string mWindowTitle; + GtkWidget * mWindow; + GtkWidget * mGLWidget; + S32 mOriginalMonitorWidth; + S32 mOriginalMonitorHeight; F32 mOverrideAspectRatio; - F32 mGamma; + F32 mGamma; + GdkCursor * mHiddenCursor; + GdkCursor * mCursors[UI_CURSOR_COUNT]; + S32 mXRRVersion; + S32 mXF86VMVersion; - int mSDLFlags; - - SDL_Cursor* mSDLCursors[UI_CURSOR_COUNT]; - int mHaveInputFocus; /* 0=no, 1=yes, else unknown */ - int mIsMinimized; /* 0=no, 1=yes, else unknown */ - friend class LLWindowManager; -#if LL_X11 -private: - // more X11 clipboard stuff - int init_x11clipboard(void); - void quit_x11clipboard(void); - int is_empty_x11clipboard(void); - void put_x11clipboard(int type, int srclen, const char *src); - void get_x11clipboard(int type, int *dstlen, char **dst); - void x11_set_urgent(BOOL urgent); +protected: BOOL mFlashing; LLTimer mFlashTimer; -#endif //LL_X11 - +protected: + // Member variables that keep track of input method states. + LLPreeditor *mPreeditor; + LLWString mPreeditText; + LLPreeditor::segment_lengths_t mPreeditSegmentLengths; + LLPreeditor::standouts_t mPreeditStandouts; + S32 mPreeditCaret; + GtkIMContext *mIMContext; + BOOL mWindowIsFocused; + BOOL mIMContextIsFocused; + BOOL mPrimaryClipboardNeedsReset; }; @@ -224,14 +237,7 @@ void load_url_external(const char* url); -#if LL_GTK // Lazily initialize and check the runtime GTK version for goodness. BOOL ll_try_gtk_init(void); -#endif // LL_GTK -#if LL_X11 -Window get_SDL_XWindowID(void); -Display* get_SDL_Display(void); -#endif // LL_X11 - #endif //LL_LLWINDOWSDL_H Index: linden/indra/llwindow/llkeyboardsdl.cpp =================================================================== --- linden/indra/llwindow/llkeyboardsdl.cpp (.../tags/1.19.0.4) (revision 520) +++ linden/indra/llwindow/llkeyboardsdl.cpp (.../branches/1.19.0-GTK) (revision 520) @@ -33,10 +33,12 @@ #include "linden_common.h" #include "llkeyboardsdl.h" -#include "llwindow.h" -#include "SDL/SDL.h" +#include "llwindowsdl.h" +#include "gtk/gtk.h" +#include "gdk/gdkkeysyms.h" -LLKeyboardSDL::LLKeyboardSDL() +LLKeyboardSDL::LLKeyboardSDL(LLWindowSDL *window) + : mWindow(window) { // Set up key mapping for SDL - eventually can read this from a file? // Anything not in the key map gets dropped @@ -44,7 +46,7 @@ // Virtual key mappings from SDL_keysym.h ... - // SDL maps the letter keys to the ASCII you'd expect, but it's lowercase... + // Gtk/Gdk maps the letter keys to the ASCII. U16 cur_char; for (cur_char = 'A'; cur_char <= 'Z'; cur_char++) { @@ -73,60 +75,60 @@ //mTranslateKeyMap[SDLK_KP3] = KEY_PAGE_DOWN; //mTranslateKeyMap[SDLK_KP0] = KEY_INSERT; - mTranslateKeyMap[SDLK_SPACE] = ' '; - mTranslateKeyMap[SDLK_RETURN] = KEY_RETURN; - mTranslateKeyMap[SDLK_LEFT] = KEY_LEFT; - mTranslateKeyMap[SDLK_RIGHT] = KEY_RIGHT; - mTranslateKeyMap[SDLK_UP] = KEY_UP; - mTranslateKeyMap[SDLK_DOWN] = KEY_DOWN; - mTranslateKeyMap[SDLK_ESCAPE] = KEY_ESCAPE; - mTranslateKeyMap[SDLK_KP_ENTER] = KEY_RETURN; - mTranslateKeyMap[SDLK_ESCAPE] = KEY_ESCAPE; - mTranslateKeyMap[SDLK_BACKSPACE] = KEY_BACKSPACE; - mTranslateKeyMap[SDLK_DELETE] = KEY_DELETE; - mTranslateKeyMap[SDLK_LSHIFT] = KEY_SHIFT; - mTranslateKeyMap[SDLK_RSHIFT] = KEY_SHIFT; - mTranslateKeyMap[SDLK_LCTRL] = KEY_CONTROL; - mTranslateKeyMap[SDLK_RCTRL] = KEY_CONTROL; - mTranslateKeyMap[SDLK_LALT] = KEY_ALT; - mTranslateKeyMap[SDLK_RALT] = KEY_ALT; - mTranslateKeyMap[SDLK_HOME] = KEY_HOME; - mTranslateKeyMap[SDLK_END] = KEY_END; - mTranslateKeyMap[SDLK_PAGEUP] = KEY_PAGE_UP; - mTranslateKeyMap[SDLK_PAGEDOWN] = KEY_PAGE_DOWN; - mTranslateKeyMap[SDLK_MINUS] = KEY_HYPHEN; - mTranslateKeyMap[SDLK_EQUALS] = KEY_EQUALS; - mTranslateKeyMap[SDLK_KP_EQUALS] = KEY_EQUALS; - mTranslateKeyMap[SDLK_INSERT] = KEY_INSERT; - mTranslateKeyMap[SDLK_CAPSLOCK] = KEY_CAPSLOCK; - mTranslateKeyMap[SDLK_TAB] = KEY_TAB; - mTranslateKeyMap[SDLK_KP_PLUS] = KEY_ADD; - mTranslateKeyMap[SDLK_KP_MINUS] = KEY_SUBTRACT; - mTranslateKeyMap[SDLK_KP_MULTIPLY] = KEY_MULTIPLY; - mTranslateKeyMap[SDLK_KP_DIVIDE] = KEY_DIVIDE; - mTranslateKeyMap[SDLK_F1] = KEY_F1; - mTranslateKeyMap[SDLK_F2] = KEY_F2; - mTranslateKeyMap[SDLK_F3] = KEY_F3; - mTranslateKeyMap[SDLK_F4] = KEY_F4; - mTranslateKeyMap[SDLK_F5] = KEY_F5; - mTranslateKeyMap[SDLK_F6] = KEY_F6; - mTranslateKeyMap[SDLK_F7] = KEY_F7; - mTranslateKeyMap[SDLK_F8] = KEY_F8; - mTranslateKeyMap[SDLK_F9] = KEY_F9; - mTranslateKeyMap[SDLK_F10] = KEY_F10; - mTranslateKeyMap[SDLK_F11] = KEY_F11; - mTranslateKeyMap[SDLK_F12] = KEY_F12; - mTranslateKeyMap[SDLK_PLUS] = '='; - mTranslateKeyMap[SDLK_COMMA] = ','; - mTranslateKeyMap[SDLK_MINUS] = '-'; - mTranslateKeyMap[SDLK_PERIOD] = '.'; - mTranslateKeyMap[SDLK_BACKQUOTE] = '`'; - mTranslateKeyMap[SDLK_SLASH] = '/'; - mTranslateKeyMap[SDLK_SEMICOLON] = ';'; - mTranslateKeyMap[SDLK_LEFTBRACKET] = '['; - mTranslateKeyMap[SDLK_BACKSLASH] = '\\'; - mTranslateKeyMap[SDLK_RIGHTBRACKET] = ']'; - mTranslateKeyMap[SDLK_QUOTE] = '\''; + mTranslateKeyMap[GDK_space] = ' '; + mTranslateKeyMap[GDK_Return] = KEY_RETURN; + mTranslateKeyMap[GDK_Left] = KEY_LEFT; + mTranslateKeyMap[GDK_Right] = KEY_RIGHT; + mTranslateKeyMap[GDK_Up] = KEY_UP; + mTranslateKeyMap[GDK_Down] = KEY_DOWN; + mTranslateKeyMap[GDK_Escape] = KEY_ESCAPE; + mTranslateKeyMap[GDK_KP_Enter] = KEY_RETURN; + mTranslateKeyMap[GDK_Escape] = KEY_ESCAPE; + mTranslateKeyMap[GDK_BackSpace] = KEY_BACKSPACE; + mTranslateKeyMap[GDK_Delete] = KEY_DELETE; + mTranslateKeyMap[GDK_Shift_L] = KEY_SHIFT; + mTranslateKeyMap[GDK_Shift_R] = KEY_SHIFT; + mTranslateKeyMap[GDK_Control_L] = KEY_CONTROL; + mTranslateKeyMap[GDK_Control_R] = KEY_CONTROL; + mTranslateKeyMap[GDK_Alt_L] = KEY_ALT; + mTranslateKeyMap[GDK_Alt_R] = KEY_ALT; + mTranslateKeyMap[GDK_Home] = KEY_HOME; + mTranslateKeyMap[GDK_End] = KEY_END; + mTranslateKeyMap[GDK_Page_Up] = KEY_PAGE_UP; + mTranslateKeyMap[GDK_Page_Down] = KEY_PAGE_DOWN; + mTranslateKeyMap[GDK_minus] = KEY_HYPHEN; + mTranslateKeyMap[GDK_equal] = KEY_EQUALS; + mTranslateKeyMap[GDK_KP_Equal] = KEY_EQUALS; + mTranslateKeyMap[GDK_Insert] = KEY_INSERT; + mTranslateKeyMap[GDK_Caps_Lock] = KEY_CAPSLOCK; + mTranslateKeyMap[GDK_Tab] = KEY_TAB; + mTranslateKeyMap[GDK_KP_Add] = KEY_ADD; + mTranslateKeyMap[GDK_KP_Subtract] = KEY_SUBTRACT; + mTranslateKeyMap[GDK_KP_Multiply] = KEY_MULTIPLY; + mTranslateKeyMap[GDK_KP_Divide] = KEY_DIVIDE; + mTranslateKeyMap[GDK_F1] = KEY_F1; + mTranslateKeyMap[GDK_F2] = KEY_F2; + mTranslateKeyMap[GDK_F3] = KEY_F3; + mTranslateKeyMap[GDK_F4] = KEY_F4; + mTranslateKeyMap[GDK_F5] = KEY_F5; + mTranslateKeyMap[GDK_F6] = KEY_F6; + mTranslateKeyMap[GDK_F7] = KEY_F7; + mTranslateKeyMap[GDK_F8] = KEY_F8; + mTranslateKeyMap[GDK_F9] = KEY_F9; + mTranslateKeyMap[GDK_F10] = KEY_F10; + mTranslateKeyMap[GDK_F11] = KEY_F11; + mTranslateKeyMap[GDK_F12] = KEY_F12; + mTranslateKeyMap[GDK_plus] = '='; + mTranslateKeyMap[GDK_comma] = ','; + mTranslateKeyMap[GDK_minus] = '-'; + mTranslateKeyMap[GDK_period] = '.'; + mTranslateKeyMap[GDK_quoteleft] = '`'; + mTranslateKeyMap[GDK_slash] = '/'; + mTranslateKeyMap[GDK_semicolon] = ';'; + mTranslateKeyMap[GDK_bracketleft] = '['; + mTranslateKeyMap[GDK_backslash] = '\\'; + mTranslateKeyMap[GDK_bracketright] = ']'; + mTranslateKeyMap[GDK_quoteright] = '\''; // Build inverse map std::map::iterator iter; @@ -136,17 +138,17 @@ } // numpad map - mTranslateNumpadMap[SDLK_KP0] = KEY_PAD_INS; - mTranslateNumpadMap[SDLK_KP1] = KEY_PAD_END; - mTranslateNumpadMap[SDLK_KP2] = KEY_PAD_DOWN; - mTranslateNumpadMap[SDLK_KP3] = KEY_PAD_PGDN; - mTranslateNumpadMap[SDLK_KP4] = KEY_PAD_LEFT; - mTranslateNumpadMap[SDLK_KP5] = KEY_PAD_CENTER; - mTranslateNumpadMap[SDLK_KP6] = KEY_PAD_RIGHT; - mTranslateNumpadMap[SDLK_KP7] = KEY_PAD_HOME; - mTranslateNumpadMap[SDLK_KP8] = KEY_PAD_UP; - mTranslateNumpadMap[SDLK_KP9] = KEY_PAD_PGUP; - mTranslateNumpadMap[SDLK_KP_PERIOD] = KEY_PAD_DEL; + mTranslateNumpadMap[GDK_KP_0] = KEY_PAD_INS; + mTranslateNumpadMap[GDK_KP_1] = KEY_PAD_END; + mTranslateNumpadMap[GDK_KP_2] = KEY_PAD_DOWN; + mTranslateNumpadMap[GDK_KP_3] = KEY_PAD_PGDN; + mTranslateNumpadMap[GDK_KP_4] = KEY_PAD_LEFT; + mTranslateNumpadMap[GDK_KP_5] = KEY_PAD_CENTER; + mTranslateNumpadMap[GDK_KP_6] = KEY_PAD_RIGHT; + mTranslateNumpadMap[GDK_KP_7] = KEY_PAD_HOME; + mTranslateNumpadMap[GDK_KP_8] = KEY_PAD_UP; + mTranslateNumpadMap[GDK_KP_9] = KEY_PAD_PGUP; + mTranslateNumpadMap[GDK_KP_Decimal] = KEY_PAD_DEL; // build inverse numpad map for (iter = mTranslateNumpadMap.begin(); @@ -159,45 +161,47 @@ void LLKeyboardSDL::resetMaskKeys() { - SDLMod mask = SDL_GetModState(); + GdkModifierType mask; + gdk_display_get_pointer(gtk_widget_get_display(mWindow->getGtkWindow()), NULL, NULL, NULL, &mask); // MBW -- XXX -- This mirrors the operation of the Windows version of resetMaskKeys(). // It looks a bit suspicious, as it won't correct for keys that have been released. // Is this the way it's supposed to work? - if(mask & KMOD_SHIFT) + if(mask & GDK_SHIFT_MASK) { mKeyLevel[KEY_SHIFT] = TRUE; } - if(mask & KMOD_CTRL) + if(mask & GDK_CONTROL_MASK) { mKeyLevel[KEY_CONTROL] = TRUE; } - if(mask & KMOD_ALT) + if(mask & GDK_MOD1_MASK) { mKeyLevel[KEY_ALT] = TRUE; } } -MASK LLKeyboardSDL::updateModifiers(const U32 mask) +// Translate the Gtk modifier mask into LL mask. +MASK LLKeyboardSDL::updateModifiers(const MASK mask) { - // translate the mask + const GdkModifierType in_mask = (GdkModifierType) mask; MASK out_mask = MASK_NONE; - if(mask & KMOD_SHIFT) + if(in_mask & GDK_SHIFT_MASK) { out_mask |= MASK_SHIFT; } - if(mask & KMOD_CTRL) + if(in_mask & GDK_CONTROL_MASK) { out_mask |= MASK_CONTROL; } - if(mask & KMOD_ALT) + if(in_mask & GDK_MOD1_MASK) { out_mask |= MASK_ALT; } @@ -206,8 +210,9 @@ } -static U16 adjustNativekeyFromUnhandledMask(const U16 key, const U32 mask) +static U16 adjustNativekeyFromUnhandledMask(const U16 key, const MASK mask) { +#if 0 // SDL doesn't automatically adjust the keysym according to // whether NUMLOCK is engaged, so we massage the keysym manually. U16 rtn = key; @@ -215,23 +220,27 @@ { switch (key) { - case SDLK_KP_PERIOD: rtn = SDLK_DELETE; break; - case SDLK_KP0: rtn = SDLK_INSERT; break; - case SDLK_KP1: rtn = SDLK_END; break; - case SDLK_KP2: rtn = SDLK_DOWN; break; - case SDLK_KP3: rtn = SDLK_PAGEDOWN; break; - case SDLK_KP4: rtn = SDLK_LEFT; break; - case SDLK_KP6: rtn = SDLK_RIGHT; break; - case SDLK_KP7: rtn = SDLK_HOME; break; - case SDLK_KP8: rtn = SDLK_UP; break; - case SDLK_KP9: rtn = SDLK_PAGEUP; break; + case GDK_KP_PERIOD: rtn = GDK_DELETE; break; + case GDK_KP0: rtn = GDK_INSERT; break; + case GDK_KP1: rtn = GDK_END; break; + case GDK_KP2: rtn = GDK_DOWN; break; + case GDK_KP3: rtn = GDK_PAGEDOWN; break; + case GDK_KP4: rtn = GDK_LEFT; break; + case GDK_KP6: rtn = GDK_RIGHT; break; + case GDK_KP7: rtn = GDK_HOME; break; + case GDK_KP8: rtn = GDK_UP; break; + case GDK_KP9: rtn = GDK_PAGEUP; break; } } return rtn; +#else + /* XXX */ + return key; +#endif } -BOOL LLKeyboardSDL::handleKeyDown(const U16 key, const U32 mask) +BOOL LLKeyboardSDL::handleKeyDown(const U16 key, const MASK mask) { U16 adjusted_nativekey; KEY translated_key = 0; @@ -251,7 +260,7 @@ } -BOOL LLKeyboardSDL::handleKeyUp(const U16 key, const U32 mask) +BOOL LLKeyboardSDL::handleKeyUp(const U16 key, const MASK mask) { U16 adjusted_nativekey; KEY translated_key = 0; @@ -273,16 +282,30 @@ MASK LLKeyboardSDL::currentMask(BOOL for_mouse_event) { MASK result = MASK_NONE; - SDLMod mask = SDL_GetModState(); + GdkModifierType modifiers; + gdk_display_get_pointer(gtk_widget_get_display(mWindow->getGtkWindow()), NULL, NULL, NULL, &modifiers); - if (mask & KMOD_SHIFT) result |= MASK_SHIFT; - if (mask & KMOD_CTRL) result |= MASK_CONTROL; - if (mask & KMOD_ALT) result |= MASK_ALT; + if (modifiers & GDK_SHIFT_MASK) + { + result |= MASK_SHIFT; + } + if (modifiers & GDK_CONTROL_MASK) + { + result |= MASK_CONTROL; + } + if (modifiers & GDK_MOD1_MASK) + { + result |= MASK_ALT; + } // For keyboard events, consider Meta keys equivalent to Control + // ... Fine, but which key in Microsoft 109 keyboard is the Meta (MOD2) key? if (!for_mouse_event) { - if (mask & KMOD_META) result |= MASK_CONTROL; + if (modifiers & GDK_MOD2_MASK) + { + result |= MASK_CONTROL; + } } return result; Index: linden/indra/llwindow/llwindowsdl.cpp =================================================================== --- linden/indra/llwindow/llwindowsdl.cpp (.../tags/1.19.0.4) (revision 520) +++ linden/indra/llwindow/llwindowsdl.cpp (.../branches/1.19.0-GTK) (revision 520) @@ -1,6 +1,6 @@ /** * @file llwindowsdl.cpp - * @brief SDL implementation of LLWindow class + * @brief GTK+/GtkGLExt implementation of LLWindow class * * $LicenseInfo:firstyear=2001&license=viewergpl$ * @@ -29,8 +29,6 @@ * $/LicenseInfo$ */ -#if LL_SDL - #include "linden_common.h" #include "llwindowsdl.h" @@ -44,139 +42,123 @@ #include "indra_constants.h" -#if LL_GTK -extern "C" { -# include "gtk/gtk.h" -} -#endif // LL_GTK +#include "llpreeditor.h" -#if LL_LINUX || LL_SOLARIS -// not necessarily available on random SDL platforms, so #if LL_LINUX -// for execv(), waitpid(), fork() -# include -# include -# include -#endif // LL_LINUX || LL_SOLARIS +#include "gdk/gdk.h" +#include "gdk/gdkkeysyms.h" +#include "gtk/gtk.h" +#include "gtk/gtkgl.h" -extern BOOL gDebugWindowProc; +#if LL_XRANDR +#include "gdk/gdkx.h" +#include +#include +#include +#endif -const S32 MAX_NUM_RESOLUTIONS = 32; +#if LL_XF86VM +#include "gdk/gdkx.h" +#include +#include +#define BOOL XBOOL // Typedef BOOL conflicts on my environment... +#include +#undef BOOL +#ifndef MODE_OK +#define MODE_OK 0 +#endif +#endif +#include +#include + // // LLWindowSDL // -#if LL_X11 -# include -#endif //LL_X11 +static const S32 RECOMMENDED_WINDOW_WIDTH = 800; +static const S32 RECOMMENDED_WINDOW_HEIGHT = 600; +static const S32 MINIMUM_FULLSCREEN_WIDTH = 640; +static const S32 MINIMUM_FULLSCREEN_HEIGHT = 480; + // TOFU HACK -- (*exactly* the same hack as LLWindowMacOSX for a similar // set of reasons): Stash a pointer to the LLWindowSDL object here and // maintain in the constructor and destructor. This assumes that there will // be only one object of this class at any time. Currently this is true. static LLWindowSDL *gWindowImplementation = NULL; -static BOOL was_fullscreen = FALSE; - - -void maybe_lock_display(void) +// Lazily initialize and check the runtime GTK version for goodness. +BOOL ll_try_gtk_init(void) { - if (gWindowImplementation) { - gWindowImplementation->Lock_Display(); - } -} + static BOOL tried_gtk_init = FALSE; + static BOOL gtk_is_good = FALSE; - -void maybe_unlock_display(void) -{ - if (gWindowImplementation) { - gWindowImplementation->Unlock_Display(); + if (tried_gtk_init) + { + return gtk_is_good; } -} + tried_gtk_init = TRUE; + llinfos << "Starting GTK Initialization." << llendl; -#if LL_GTK -// Lazily initialize and check the runtime GTK version for goodness. -BOOL ll_try_gtk_init(void) -{ - static BOOL done_gtk_diag = FALSE; - static BOOL gtk_is_good = FALSE; - static BOOL done_setlocale = FALSE; - static BOOL tried_gtk_init = FALSE; + // We *need* to issue setlocale to run immodules. + // gtk_disable_setlocale(); - if (!done_setlocale) + if (!g_thread_supported()) { - llinfos << "Starting GTK Initialization." << llendl; - maybe_lock_display(); - gtk_disable_setlocale(); - maybe_unlock_display(); - done_setlocale = TRUE; + g_thread_init(NULL); } - - if (!tried_gtk_init) + + if (!gtk_init_check(NULL, NULL)) { - tried_gtk_init = TRUE; - if (!g_thread_supported ()) g_thread_init (NULL); - maybe_lock_display(); - gtk_is_good = gtk_init_check(NULL, NULL); - maybe_unlock_display(); - if (!gtk_is_good) - llwarns << "GTK Initialization failed." << llendl; + llwarns << "GTK Initialization failed." << llendl; + // The message box may not appear, since GTK didn't initialize... + OSMessageBox("Gtk initialization failure.", "Error", OSMB_OK); + return gtk_is_good; } - if (gtk_is_good && !done_gtk_diag) - { - llinfos << "GTK Initialized." << llendl; - llinfos << "- Compiled against GTK version " + llinfos << "GTK Initialized." << llendl; + llinfos << "- Compiled against GTK version " << GTK_MAJOR_VERSION << "." << GTK_MINOR_VERSION << "." << GTK_MICRO_VERSION << llendl; - llinfos << "- Running against GTK version " + llinfos << "- Running against GTK version " << gtk_major_version << "." << gtk_minor_version << "." << gtk_micro_version << llendl; - gchar *gtk_warning; - maybe_lock_display(); - gtk_warning = gtk_check_version(GTK_MAJOR_VERSION, - GTK_MINOR_VERSION, - GTK_MICRO_VERSION); - maybe_unlock_display(); - if (gtk_warning) - { - llwarns << "- GTK COMPATIBILITY WARNING: " << - gtk_warning << llendl; - gtk_is_good = FALSE; - } + const gchar *gtk_warning; + gtk_warning = gtk_check_version(GTK_MAJOR_VERSION, + GTK_MINOR_VERSION, + GTK_MICRO_VERSION); + if (gtk_warning) + { + llwarns << "- GTK COMPATIBILITY WARNING: " << gtk_warning << llendl; + } - done_gtk_diag = TRUE; + if (!gtk_gl_init_check(NULL, NULL)) + { + llwarns << "GTK GL Initialization failed." << llendl; + OSMessageBox("Gtk GL initialization failure.", "Error", OSMB_OK); + return gtk_is_good; } + llinfos << "GTK GL Initialized." << llendl; + llinfos << "- Compiled against GTK GL version " + << GTKGLEXT_MAJOR_VERSION << "." + << GTKGLEXT_MINOR_VERSION << "." + << GTKGLEXT_MICRO_VERSION << llendl; + llinfos << "- Running against GTK version " + << gtkglext_major_version << "." + << gtkglext_minor_version << "." + << gtkglext_micro_version << llendl; + + gtk_is_good = TRUE; return gtk_is_good; } -#endif // LL_GTK - -#if LL_X11 -Window get_SDL_XWindowID(void) +#if 0 +static BOOL check_for_card(const char* RENDERER, const char* bad_card) { - if (gWindowImplementation) { - return gWindowImplementation->mSDL_XWindowID; - } - return None; -} - -Display* get_SDL_Display(void) -{ - if (gWindowImplementation) { - return gWindowImplementation->mSDL_Display; - } - return NULL; -} -#endif // LL_X11 - - -BOOL check_for_card(const char* RENDERER, const char* bad_card) -{ if (!strncasecmp(RENDERER, bad_card, strlen(bad_card))) { char buffer[1024]; /* Flawfinder: ignore */ @@ -207,10 +189,8 @@ return FALSE; } +#endif - - - LLWindowSDL::LLWindowSDL(char *title, S32 x, S32 y, S32 width, S32 height, U32 flags, BOOL fullscreen, BOOL clearBg, @@ -219,52 +199,54 @@ : LLWindow(fullscreen, flags), mGamma(1.0f) { // Initialize the keyboard - gKeyboard = new LLKeyboardSDL(); - // Note that we can't set up key-repeat until after SDL has init'd video + gKeyboard = new LLKeyboardSDL(this); // Ignore use_gl for now, only used for drones on PC mWindow = NULL; - mCursorDecoupled = FALSE; - mCursorLastEventDeltaX = 0; - mCursorLastEventDeltaY = 0; - mCursorIgnoreNextDelta = FALSE; - mNeedsResize = FALSE; + mGLWidget = NULL; mOverrideAspectRatio = 0.f; mGrabbyKeyFlags = 0; - mReallyCapturedCount = 0; - mHaveInputFocus = -1; - mIsMinimized = -1; + mPreeditor = NULL; + mFlashing = FALSE; + mWindowIsFocused = FALSE; + mIMContextIsFocused = FALSE; + mPrimaryClipboardNeedsReset = TRUE; + mSupportedResolutions = NULL; -#if LL_X11 - mSDL_XWindowID = None; - mSDL_Display = NULL; -#endif // LL_X11 - -#if LL_GTK // We MUST be the first to initialize GTK, i.e. we have to beat // our embedded Mozilla to the punch so that GTK doesn't get badly // initialized with a non-C locale and cause lots of serious random // weirdness. - ll_try_gtk_init(); -#endif // LL_GTK - - // Get the original aspect ratio of the main device. - mOriginalAspectRatio = 1024.0 / 768.0; // !!! *FIX: ? //(double)CGDisplayPixelsWide(mDisplay) / (double)CGDisplayPixelsHigh(mDisplay); - - if (!title) - title = "SDL Window"; // *FIX: (???) - - // Stash the window title - mWindowTitle = new char[strlen(title) + 1]; /* Flawfinder: ignore */ - if(mWindowTitle == NULL) + // + // Although the above comments (taken from SDL version) says + // "random weirdness", the issue is not random. The problem is + // that SL viewer code assumes a decimal point character is always + // a period, and passes the american notation of decimal fraction + // to/from locale sensitive number formatting functions. It's not + // a Mozilla problem, but an SL viewer problem. + // + // ... and, we need to run immodules under the user's locale. + // Else, it may not run properly. In this version, I simply + // ignore the above issue. As a result, this version does not run + // under a locale whose decimal point is not a period. I will + // come back this point in a future. + // + if (!ll_try_gtk_init()) { - llerrs << "Memory allocation failure" << llendl; return; } - strcpy(mWindowTitle, title); /* Flawfinder: ignore */ +#if LL_X11 + // See if some X11 extensions are available. Also does some + // initialization when they are. + setupX11Extensions(); +#endif + + // Save the title, since we need it everytime creating GL context. + mWindowTitle = (title ? title : "LLWindow"); + // Create the GL context and set it up for windowed or fullscreen, as appropriate. - if(createContext(x, y, width, height, 32, fullscreen, disable_vsync)) + if(createContext(x, y, width, height, fullscreen, disable_vsync)) { gGLManager.initGL(); @@ -272,31 +254,32 @@ initCursors(); setCursor( UI_CURSOR_ARROW ); } - stop_glerror(); - // Stash an object pointer for OSMessageBox() + // Initialize input method. + createIMContext(); + gWindowImplementation = this; - -#if LL_X11 - mFlashing = FALSE; -#endif // LL_X11 } -static SDL_Surface *Load_BMP_Resource(const char *basename) +// Load a bmp resource file. No matter what its name suggests, this +// function can load a lot of file formats other than BMP. +static GdkPixbuf *load_bmp_resource(const char *basename, const char * purpose) { - const int PATH_BUFFER_SIZE=1000; - char path_buffer[PATH_BUFFER_SIZE]; /* Flawfinder: ignore */ - - // Figure out where our BMP is living on the disk - snprintf(path_buffer, PATH_BUFFER_SIZE-1, "%s%sres-sdl%s%s", - gDirUtilp->getAppRODataDir().c_str(), - gDirUtilp->getDirDelimiter().c_str(), - gDirUtilp->getDirDelimiter().c_str(), - basename); - path_buffer[PATH_BUFFER_SIZE-1] = '\0'; - - return SDL_LoadBMP(path_buffer); + const std::string path = gDirUtilp->getAppRODataDir() + + gDirUtilp->getDirDelimiter() + + "res-sdl" + + gDirUtilp->getDirDelimiter() + + basename; + + GError *error = NULL; + GdkPixbuf * const pixbuf = gdk_pixbuf_new_from_file(path.c_str(), &error); + if (!pixbuf) + { + llwarns << "Loading of " << purpose << " BMP resource failed for " << basename << ": " << error->message << llendl; + g_error_free(error); + } + return pixbuf; } #if LL_X11 @@ -435,266 +418,189 @@ } #endif // LL_X11 -BOOL LLWindowSDL::createContext(int x, int y, int width, int height, int bits, BOOL fullscreen, BOOL disable_vsync) +// Bulk define member function wrappers (aka C-C++ callback thunk.) +#define EVENT_HANDLER_WRAPPER(HANDLER, EVENT) \ + static gboolean HANDLER##_cb(GtkWidget *widget, EVENT *event, gpointer user_data) \ + { return static_cast(user_data)->HANDLER(event); } +EVENT_HANDLER_WRAPPER(handleGtkMotionNotifyEvent, GdkEventMotion); +EVENT_HANDLER_WRAPPER(handleGtkKeyPressEvent, GdkEventKey); +EVENT_HANDLER_WRAPPER(handleGtkKeyReleaseEvent, GdkEventKey); +EVENT_HANDLER_WRAPPER(handleGtkButtonPressEvent, GdkEventButton); +EVENT_HANDLER_WRAPPER(handleGtkButtonReleaseEvent, GdkEventButton); +EVENT_HANDLER_WRAPPER(handleGtkScrollEvent, GdkEventScroll); +EVENT_HANDLER_WRAPPER(handleGtkFocusInEvent, GdkEventFocus); +EVENT_HANDLER_WRAPPER(handleGtkFocusOutEvent, GdkEventFocus); +EVENT_HANDLER_WRAPPER(handleGtkExposeEvent, GdkEventExpose); +EVENT_HANDLER_WRAPPER(handleGtkConfigureEvent, GdkEventConfigure); +EVENT_HANDLER_WRAPPER(handleGtkWindowStateEvent, GdkEventWindowState); +EVENT_HANDLER_WRAPPER(handleGtkDeleteEvent, GdkEvent); +#undef EVENT_HANDLER_WRAPPER + +BOOL LLWindowSDL::createContext(S32 x, S32 y, S32 width, S32 height, BOOL fullscreen, BOOL disable_vsync) { - //bool glneedsinit = false; -// const char *gllibname = null; + llinfos << "createContext, fullscreen=" << fullscreen << " size=" << width << "x" << height << llendl; - llinfos << "createContext, fullscreen=" << fullscreen << - " size=" << width << "x" << height << llendl; + // Create Window and GL widget. + mWindow = gtk_window_new(GTK_WINDOW_TOPLEVEL); + mGLWidget = gtk_drawing_area_new(); + gtk_container_add(GTK_CONTAINER(mWindow), mGLWidget); - // captures don't survive contexts - mGrabbyKeyFlags = 0; - mReallyCapturedCount = 0; - - if (SDL_Init(SDL_INIT_VIDEO) < 0) + initializeGamma(); + + // We can only run on a monitor of the default screen. It is + // because some of APIs we rely on implicitly assume default + // screen and monitor. Moreover, when using XRandR 1.1, we can't + // support "multiple monitors on a screen" configuration well, and + // a workaround is simply ignore any secondary or more monitors. + // Make sure our window will be realized there. As a side effect, + // we get the monitor geometory. We will use it later. + GdkScreen * screen = gdk_screen_get_default(); + gtk_window_set_screen(GTK_WINDOW(mWindow), screen); + gint monitor_number = gdk_screen_get_monitor_at_point(screen, x, y); + GdkRectangle monitor_rectangle; + gdk_screen_get_monitor_geometry(screen, monitor_number, &monitor_rectangle); + + // Find the geometory of the primary monitor on the default + // screen. Our window will always be there. At this moment, we + // can't support multiple monitor and/or multiple screen + // environment well. + + // Take care of initial window size. + mFullscreen = fullscreen; + if (!mFullscreen) { - llinfos << "sdl_init() failed! " << SDL_GetError() << llendl; - setupFailure("window creation error", "error", OSMB_OK); - return false; + // Supply some reasonable default. + if (width <= 0) + { + width = RECOMMENDED_WINDOW_WIDTH; + } + if (height <= 0) + { + height = RECOMMENDED_WINDOW_HEIGHT; + } + + // Let the window fit in the monitor we have examined. + width = llmin(width, monitor_rectangle.width); + height = llmin(height, monitor_rectangle.height); + x = llclamp(x, monitor_rectangle.x, monitor_rectangle.x + monitor_rectangle.width - width); + y = llclamp(y, monitor_rectangle.y, monitor_rectangle.y + monitor_rectangle.height - height); + + // allocate the window as calculated above. Size and position + // may further be changed by the Window Manager upon realize, + // however. + gtk_window_set_default_size(GTK_WINDOW(mWindow), width, height); + gtk_window_move(GTK_WINDOW(mWindow), x, y); } - SDL_version c_sdl_version; - SDL_VERSION(&c_sdl_version); - llinfos << "Compiled against SDL " - << int(c_sdl_version.major) << "." - << int(c_sdl_version.minor) << "." - << int(c_sdl_version.patch) << llendl; - const SDL_version *r_sdl_version; - r_sdl_version = SDL_Linked_Version(); - llinfos << " Running against SDL " - << int(r_sdl_version->major) << "." - << int(r_sdl_version->minor) << "." - << int(r_sdl_version->patch) << llendl; - - const SDL_VideoInfo *videoInfo = SDL_GetVideoInfo( ); - if (!videoInfo) + // Setup a GL context. + GdkGLConfig * gl_config + = gdk_gl_config_new_by_mode((GdkGLConfigMode)(GDK_GL_MODE_RGBA | GDK_GL_MODE_ALPHA | GDK_GL_MODE_DEPTH | GDK_GL_MODE_DOUBLE)); + if (!gl_config) { - llinfos << "SDL_GetVideoInfo() failed! " << SDL_GetError() << llendl; + llwarns << "gdk_gl_config_new_by_mode failed." << llendl; setupFailure("Window creation error", "Error", OSMB_OK); return FALSE; } - SDL_EnableUNICODE(1); - SDL_WM_SetCaption(mWindowTitle, mWindowTitle); - - // Set the application icon. - SDL_Surface *bmpsurface; - bmpsurface = Load_BMP_Resource("ll_icon.BMP"); - if (bmpsurface) + gboolean ok = gtk_widget_set_gl_capability(mGLWidget, gl_config, NULL, TRUE, GDK_GL_RGBA_TYPE); + if (!ok) { - // This attempts to give a black-keyed mask to the icon. - SDL_SetColorKey(bmpsurface, - SDL_SRCCOLORKEY, - SDL_MapRGB(bmpsurface->format, 0,0,0) ); - SDL_WM_SetIcon(bmpsurface, NULL); - // The SDL examples cheerfully avoid freeing the icon - // surface, but I'm betting that's leaky. - SDL_FreeSurface(bmpsurface); - bmpsurface = NULL; + llwarns << "gtk_widget_set_gl_capability failed." << llendl; + setupFailure("Window creation error", "Error", OSMB_OK); + return FALSE; } - // note: these SetAttributes make Tom's 9600-on-AMD64 fail to - // get a visual, but it's broken anyway when it does, and without - // these SetAttributes we might easily get an avoidable substandard - // visual to work with on most other machines. - SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); - SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE,8); - SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); -#if !LL_SOLARIS - SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, (bits <= 16) ? 16 : 24); -#else - // NOTE- use smaller Z-buffer to enable more graphics cards - // - This should not affect better GPUs and has been proven - // to provide 24-bit z-buffers when available. - // - // As the API states: - // - // GLX_DEPTH_SIZE Must be followed by a nonnegative - // minimum size specification. If this - // value is zero, visuals with no depth - // buffer are preferred. Otherwise, the - // largest available depth buffer of at - // least the minimum size is preferred. - - SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16); -#endif - SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, (bits <= 16) ? 1 : 8); - - // *FIX: try to toggle vsync here? - - mFullscreen = fullscreen; - was_fullscreen = fullscreen; - - int sdlflags = SDL_OPENGL | SDL_RESIZABLE | SDL_ANYFORMAT; - - SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); - - mSDLFlags = sdlflags; - - if (mFullscreen) + // Set the application icon and title. + GdkPixbuf *icon_pixbuf = load_bmp_resource("ll_icon.BMP", "icon"); + if (icon_pixbuf) { - llinfos << "createContext: setting up fullscreen " << width << "x" << height << llendl; - - // If the requested width or height is 0, find the best default for the monitor. - if((width == 0) || (height == 0)) + // Second Life icon is keyed with black. + GdkPixbuf * pixbuf = gdk_pixbuf_add_alpha(icon_pixbuf, TRUE, 0, 0, 0); + if (pixbuf) { - // Scan through the list of modes, looking for one which has: - // height between 700 and 800 - // aspect ratio closest to the user's original mode - S32 resolutionCount = 0; - LLWindowResolution *resolutionList = getSupportedResolutions(resolutionCount); - - if(resolutionList != NULL) - { - F32 closestAspect = 0; - U32 closestHeight = 0; - U32 closestWidth = 0; - int i; - - llinfos << "createContext: searching for a display mode, original aspect is " << mOriginalAspectRatio << llendl; - - for(i=0; i < resolutionCount; i++) - { - F32 aspect = (F32)resolutionList[i].mWidth / (F32)resolutionList[i].mHeight; - - llinfos << "createContext: width " << resolutionList[i].mWidth << " height " << resolutionList[i].mHeight << " aspect " << aspect << llendl; - - if( (resolutionList[i].mHeight >= 700) && (resolutionList[i].mHeight <= 800) && - (fabs(aspect - mOriginalAspectRatio) < fabs(closestAspect - mOriginalAspectRatio))) - { - llinfos << " (new closest mode) " << llendl; - - // This is the closest mode we've seen yet. - closestWidth = resolutionList[i].mWidth; - closestHeight = resolutionList[i].mHeight; - closestAspect = aspect; - } - } - - width = closestWidth; - height = closestHeight; - } + gtk_window_set_icon(GTK_WINDOW(mWindow), pixbuf); + g_object_unref(pixbuf); } + g_object_unref(icon_pixbuf); + } + gtk_window_set_title(GTK_WINDOW(mWindow), mWindowTitle.c_str()); - if((width == 0) || (height == 0)) - { - // Mode search failed for some reason. Use the old-school default. - width = 1024; - height = 768; - } + // An aesthetic workaround on window resizing and fullscreen + // switching. This setting makes newly exposed area in viewer + // window to be automatically erased with black (before viewer + // application redraws.) I think this behaviour looks more + // natural than leaving those area undrawn or erasing with the + // GTK's default background color. + static const GdkColor black = { 0, 0, 0, 0 }; + gtk_widget_modify_bg(mWindow, GTK_STATE_NORMAL, &black); - mWindow = SDL_SetVideoMode(width, height, bits, sdlflags | SDL_FULLSCREEN); + // We need to show (map/realize in fact) the GL widget before + // using GL functions. + gtk_widget_show_all(mWindow); - if (mWindow) - { - mFullscreen = TRUE; - was_fullscreen = TRUE; - mFullscreenWidth = mWindow->w; - mFullscreenHeight = mWindow->h; - mFullscreenBits = mWindow->format->BitsPerPixel; - mFullscreenRefresh = -1; + // Take care of full screen resolutions. - llinfos << "Running at " << mFullscreenWidth - << "x" << mFullscreenHeight - << "x" << mFullscreenBits - << " @ " << mFullscreenRefresh - << llendl; - } - else - { - llwarns << "createContext: fullscreen creation failure. SDL: " << SDL_GetError() << llendl; - // No fullscreen support - mFullscreen = FALSE; - was_fullscreen = FALSE; - mFullscreenWidth = -1; - mFullscreenHeight = -1; - mFullscreenBits = -1; - mFullscreenRefresh = -1; - - char error[256]; /* Flawfinder: ignore */ - snprintf(error, sizeof(error), "Unable to run fullscreen at %d x %d.\nRunning in window.", width, height); - OSMessageBox(error, "Error", OSMB_OK); - } + if (mFullscreen) + { + // XXX do something to support initial fullscreen... SL + // viewer currently doesn't need this feature, so just ignore + // the case. + llerrs << "Creating an initlally fullscreen window is not supported." << llendl; } - if(!mFullscreen && (mWindow == NULL)) - { - if (width == 0) - width = 1024; - if (height == 0) - width = 768; - - llinfos << "createContext: creating window " << width << "x" << height << "x" << bits << llendl; - mWindow = SDL_SetVideoMode(width, height, bits, sdlflags); - - if (!mWindow) - { - llwarns << "createContext: window creation failure. SDL: " << SDL_GetError() << llendl; - setupFailure("Window creation error", "Error", OSMB_OK); - return FALSE; - } - } else if (!mFullscreen && (mWindow != NULL)) - { - llinfos << "createContext: SKIPPING - !fullscreen, but +mWindow " << width << "x" << height << "x" << bits << llendl; - } - // Detect video memory size. -# if LL_X11 + // If VRAM is not detected, that is handled later +#if LL_X11 gGLManager.mVRAM = x11_detect_VRAM_kb() / 1024; if (gGLManager.mVRAM != 0) { llinfos << "X11 log-parser detected " << gGLManager.mVRAM << "MB VRAM." << llendl; - } else -# endif // LL_X11 - { - // fallback to letting SDL detect VRAM. - // note: I've not seen SDL's detection ever actually find - // VRAM != 0, but if SDL *does* detect it then that's a bonus. - gGLManager.mVRAM = videoInfo->video_mem / 1024; - if (gGLManager.mVRAM != 0) - { - llinfos << "SDL detected " << gGLManager.mVRAM << "MB VRAM." << llendl; - } } - // If VRAM is not detected, that is handled later +#else + gGLManager.mVRAM = 0; +#endif // LL_X11 // *TODO: Now would be an appropriate time to check for some // explicitly unsupported cards. //const char* RENDERER = (const char*) glGetString(GL_RENDERER); - GLint depthBits, stencilBits, redBits, greenBits, blueBits, alphaBits; + GLint depth_bits, stencil_bits, red_bits, green_bits, blue_bits, alpha_bits; - glGetIntegerv(GL_RED_BITS, &redBits); - glGetIntegerv(GL_GREEN_BITS, &greenBits); - glGetIntegerv(GL_BLUE_BITS, &blueBits); - glGetIntegerv(GL_ALPHA_BITS, &alphaBits); - glGetIntegerv(GL_DEPTH_BITS, &depthBits); - glGetIntegerv(GL_STENCIL_BITS, &stencilBits); + GdkGLContext * const gl_context = gtk_widget_get_gl_context(mGLWidget); + GdkGLDrawable * const gl_drawable = gtk_widget_get_gl_drawable(mGLWidget); + gdk_gl_drawable_make_current(gl_drawable, gl_context); + + glGetIntegerv(GL_RED_BITS, &red_bits); + glGetIntegerv(GL_GREEN_BITS, &green_bits); + glGetIntegerv(GL_BLUE_BITS, &blue_bits); + glGetIntegerv(GL_ALPHA_BITS, &alpha_bits); + glGetIntegerv(GL_DEPTH_BITS, &depth_bits); + glGetIntegerv(GL_STENCIL_BITS, &stencil_bits); - llinfos << "GL buffer:" << llendl - llinfos << " Red Bits " << S32(redBits) << llendl - llinfos << " Green Bits " << S32(greenBits) << llendl - llinfos << " Blue Bits " << S32(blueBits) << llendl - llinfos << " Alpha Bits " << S32(alphaBits) << llendl - llinfos << " Depth Bits " << S32(depthBits) << llendl - llinfos << " Stencil Bits " << S32(stencilBits) << llendl; + llinfos << "GL buffer:" << llendl; + llinfos << " Red Bits " << S32(red_bits) << llendl; + llinfos << " Green Bits " << S32(green_bits) << llendl; + llinfos << " Blue Bits " << S32(blue_bits) << llendl; + llinfos << " Alpha Bits " << S32(alpha_bits) << llendl; + llinfos << " Depth Bits " << S32(depth_bits) << llendl; + llinfos << " Stencil Bits " << S32(stencil_bits) << llendl; - GLint colorBits = redBits + greenBits + blueBits + alphaBits; + const GLint color_bits = red_bits + green_bits + blue_bits + alpha_bits; // fixme: actually, it's REALLY important for picking that we get at - // least 8 bits each of red,green,blue. Alpha we can be a bit more + // least 8 bits each of red, green, blue. Alpha we can be a bit more // relaxed about if we have to. -#if LL_SOLARIS -#error && defined(__sparc) - if(colorBits < 24) //HACK: on SPARC allow 24-bit color + +#if LL_SOLARIS && defined(__sparc) + const S32 minimum_color_bits = 24; //HACK: on SPARC allow 24-bit color // Why is SPARC CPU architecture special? -- Alissa #else - if (colorBits < 32) + const S32 minimum_color_bits = 32; #endif + + if (color_bits < minimum_color_bits) { close(); setupFailure( -#if LL_SOLARIS -#error && defined(__sparc) +#if LL_SOLARIS && defined(__sparc) "Second Life requires at least 24-bit color on SPARC to run in a window.\n" "Please use fbconfig to set your default color depth to 24 bits.\n" "You may also need to adjust the X11 setting in SMF. To do so use\n" @@ -712,7 +618,7 @@ } #if 0 // *FIX: we're going to brave it for now... - if (alphaBits < 8) + if (alpha_bits < 8) { close(); setupFailure( @@ -728,211 +634,576 @@ } #endif -#if LL_X11 - init_x11clipboard(); -#endif // LL_X11 - - // We need to do this here, once video is init'd - if (-1 == SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, - SDL_DEFAULT_REPEAT_INTERVAL)) - llwarns << "Couldn't enable key-repeat: " << SDL_GetError() < 9 ? 9 : minor); + } + else + { + llwarns << "XRandR Extension not available." << llendl; + } +#endif - //start with arrow cursor - initCursors(); - setCursor( UI_CURSOR_ARROW ); - } + mXF86VMVersion = 0; +#if LL_XF86VM + if (XF86VidModeQueryExtension(x11_display, &event_base, &error_base) && + XF86VidModeQueryVersion(x11_display, &major, &minor)) + { + llinfos << "XF86VideoMode Extension available. Version " + << major << "." << minor << llendl; + mXF86VMVersion = major * 10 + (minor > 9 ? 9 : minor); } + else + { + llwarns << "XF86VideoMode Extension not available." << llendl; + } +#endif +} - stop_glerror(); +#endif - return result; -} - void LLWindowSDL::destroyContext() { - llinfos << "destroyContext begins" << llendl; -#if LL_X11 - quit_x11clipboard(); -#endif // LL_X11 + if (mWindow) + { + restoreGamma(); + if (mFullscreen) + { + leaveFullscreenResolution(); + } - // Clean up remaining GL state before blowing away window - llinfos << "shutdownGL begins" << llendl; - gGLManager.shutdownGL(); - llinfos << "SDL_QuitSS/VID begins" << llendl; - SDL_QuitSubSystem(SDL_INIT_VIDEO); // *FIX: this might be risky... - //unload_all_glsyms(); + // Clean up remaining GL state before blowing away window + llinfos << "shutdownGL begins" << llendl; + gGLManager.shutdownGL(); + gdk_gl_drawable_gl_end(gtk_widget_get_gl_drawable(mGLWidget)); + // gtk_widget_destroy(mGLWidget); - mWindow = NULL; + gtk_widget_destroy(mWindow); + + mGLWidget = NULL; + mWindow = NULL; + } } LLWindowSDL::~LLWindowSDL() { quitCursors(); + destroyIMContext(); destroyContext(); - if(mSupportedResolutions != NULL) + delete [] mSupportedResolutions; + mSupportedResolutions = NULL; + + if (this == gWindowImplementation) { - delete []mSupportedResolutions; + gWindowImplementation = NULL; } +} - delete[] mWindowTitle; +// close() destroys all OS-specific code associated with a window. +// Usually called from LLWindowManager::destroyWindow() +void LLWindowSDL::close() +{ + // Is window is already closed? + // if (!mWindow) + // { + // return; + // } - gWindowImplementation = NULL; + // Make sure cursor is visible and we haven't mangled the clipping state. + setMouseClipping(FALSE); + showCursor(); + + destroyContext(); } +// Note on resolution handling: +// +// If we are running on X11 and XRandR is available, use it. We could +// use per CRTC resolution control of XRandR 1.2, but it is not +// implemented on many servers/drivers yet. We stick on the good old +// XRandR 1.0. As an alternative, we can use XF86VideoMode. -void LLWindowSDL::show() +// We need some support methods with LLWindow::LLWindowResolution so +// that STL works well with it. However, it is declared in the +// LLWindow and used in newview app. I have a feeling that I should +// not change it. As an alternative, we define our own subclass and +// use it internally. + +struct LLResolution : LLWindow::LLWindowResolution { - // *FIX: What to do with SDL? + inline LLResolution() + { + } + + inline LLResolution(const GdkRectangle &rectangle) + { + mWidth = rectangle.x; + mHeight = rectangle.y; + } + +#if LL_XRANDR + inline LLResolution(const XRRScreenSize &screen_size) + { + mWidth = screen_size.width; + mHeight = screen_size.height; + } +#endif + +#if LL_XF86VM + inline LLResolution(const XF86VidModeModeInfo *info) + { + mWidth = info->hdisplay; + mHeight = info->vdisplay; + } +#endif + + inline bool operator==(const LLResolution &another) const + { + return mWidth == another.mWidth && mHeight == another.mHeight; + } + + inline bool operator<(const LLResolution &another) const + { + return mWidth < another.mWidth || (mWidth == another.mWidth && mHeight < another.mHeight); + } + + static inline bool too_small(const LLResolution &a) + { + return a.mWidth < MINIMUM_FULLSCREEN_WIDTH || a.mHeight < MINIMUM_FULLSCREEN_HEIGHT; + } +}; + +// Given a list of resolutions (as a vector of LLReslution) and a pair +// of width and height, return the index in the array to the _best_ +// matching resolution. The best one is either: if there are some +// resolutions that entirely cover the given width-height pare, the +// smallest one in the covering resolutions is the best; otherwise the +// largest one is the best. In case of a tie, one having the smaller +// index is considered best in bests. +static int find_best_resolution(const std::vector &resolutions, + const S32 width, const S32 height) +{ + int winner; + S32 winner_area; + + winner = -1; + winner_area = S32_MAX; + for (size_t i = 0; i < resolutions.size(); i++) + { + const LLResolution r = resolutions[i]; + const S32 area = r.mWidth * r.mHeight; + if (r.mWidth >= width && r.mHeight >= height && area < winner_area) + { + winner = i; + winner_area = area; + } + } + if (winner >= 0) + { + return winner; + } + + winner = 0; + winner_area = 0; + for (size_t i = 0; i < resolutions.size(); i++) + { + const LLResolution r = resolutions[i]; + const S32 area = r.mWidth * r.mHeight; + if (area > winner_area) + { + winner = i; + winner_area = area; + } + } + return winner; } -void LLWindowSDL::hide() +// Get the current pixel width and height of the monitor that this +// window is on. The width and height are stored at the location +// where pointers width and height point to. +void LLWindowSDL::getMonitorResolution(S32 *width, S32 *height) { - // *FIX: What to do with SDL? + GdkScreen * screen = gtk_window_get_screen(GTK_WINDOW(mWindow)); + gint monitor_number = gdk_screen_get_monitor_at_window(screen, mWindow->window); + GdkRectangle monitor_rectangle; + gdk_screen_get_monitor_geometry(screen, monitor_number, &monitor_rectangle); + *width = monitor_rectangle.width; + *height = monitor_rectangle.height; } -void LLWindowSDL::minimize() +// Try to switch the monitor resolution. width and height are in +// pixels. The monitor the window is on is affected. If the +// specified width-height combination is not supported, the best +// matching supported dimension is chosen. +void LLWindowSDL::switchMonitorResolution(S32 width, S32 height) { - // *FIX: What to do with SDL? +#if LL_XRANDR + // XRandR 1.2 based per CRTC configuration doesn't work well yet + // (on my NVIDIA 100.14.19 server at least), so we will use XRandR + // 1.0 compatible method. + if (mXRRVersion >= 10) + { + GdkScreen * const screen = gtk_window_get_screen(GTK_WINDOW(mWindow)); + Display * const x11_display = GDK_SCREEN_XDISPLAY(screen); + const Window x11_root_window = RootWindow(x11_display, GDK_SCREEN_XNUMBER(screen)); + XRRScreenConfiguration * const screen_config = XRRGetScreenInfo(x11_display, x11_root_window); + int nsizes = 0; + const XRRScreenSize * const size_list = XRRConfigSizes(screen_config, &nsizes); + + // Look for a best matching resolution. + std::vector resolutions(size_list, size_list + nsizes); + const int best = find_best_resolution(resolutions, width, height); + + // Actually switch the monitor resolution. + Rotation rotation; + XRRConfigRotations(screen_config, &rotation); + Time timestamp; + XRRConfigTimes(screen_config, ×tamp); + const Status status = XRRSetScreenConfig(x11_display, screen_config, x11_root_window, + best, rotation, timestamp); + XRRFreeScreenConfigInfo(screen_config); + + if (RRSetConfigSuccess == status) + { + return; + } + } +#endif + +#if LL_XF86VM + if (mXF86VMVersion >= 10) + { + GdkScreen * screen = gtk_window_get_screen(GTK_WINDOW(mWindow)); + Display * x11_display = GDK_SCREEN_XDISPLAY(screen); + const int x11_screen_number = GDK_SCREEN_XNUMBER(screen); + + BOOL ok = FALSE; + + XF86VidModeModeInfo **mode_info_array = NULL; + int nmodes; + if (XF86VidModeGetAllModeLines(x11_display, x11_screen_number, &nmodes, &mode_info_array)) + { + // Find a best matching mode line and switch to it. + const std::vector resolutions(mode_info_array, mode_info_array + nmodes); + const int best = find_best_resolution(resolutions, width, height); + ok = XF86VidModeSwitchToMode(x11_display, x11_screen_number, mode_info_array[best]); + if (ok) + { + // Although the origin of XF86VM coordinate is at the + // top-left, that of GL is at the bottom-left. We + // need to compensate the top-left cooridnate of the + // viewport against the screen dimension. Raw X11 + // screen dimenstion is used here in case it is + // different from GTK's. (Although I don't know + // whether it can.) + const int screen_height = DisplayHeight(x11_display, x11_screen_number); + const int vertical_offset = screen_height - mode_info_array[best]->vdisplay; + XF86VidModeSetViewPort(x11_display, x11_screen_number, 0, vertical_offset); + } + } + if (mode_info_array) + { + XFree(mode_info_array); + } + + if (ok) + { + return; + } + } +#endif } -void LLWindowSDL::restore() +LLWindow::LLWindowResolution * LLWindowSDL::getSupportedResolutions(S32 &num_resolutions) { - // *FIX: What to do with SDL? + // We rebuild the list of supported resolutions everytime this + // function is called, because (1) it is called infrequently so + // the overhead doesn't matter, and (2) under the multiple monitor + // environment, the application window may have been moved to a + // different monitor that supports different set of resolutions. + // When a method returned a singleton, we skip to the next method, + // since the reported resolution is very likely a synthetic. + std::vector resolutions; + +#if LL_XRANDR + if (2 > resolutions.size() && 10 <= mXRRVersion) + { + GdkScreen *screen = gtk_widget_get_screen(mWindow); + Display * x11_display = GDK_SCREEN_XDISPLAY(screen); + Window x11_root_window = RootWindow(x11_display, GDK_SCREEN_XNUMBER(screen)); + XRRScreenConfiguration *screen_config = XRRGetScreenInfo(x11_display, x11_root_window); + int nsizes = 0; + XRRScreenSize * size_list = XRRConfigSizes(screen_config, &nsizes); + if (size_list && nsizes > 0) + { + resolutions.assign(size_list, size_list + nsizes); + } + XRRFreeScreenConfigInfo(screen_config); + } +#endif + +#if LL_XF86VM + if (2 > resolutions.size() && 20 <= mXF86VMVersion) + { + GdkScreen * screen = gtk_window_get_screen(GTK_WINDOW(mWindow)); + Display * x11_display = GDK_SCREEN_XDISPLAY(screen); + int x11_screen_number = GDK_SCREEN_XNUMBER(screen); + XF86VidModeModeInfo **mode_info_array; + int nmodes = 0; + if (XF86VidModeGetAllModeLines(x11_display, x11_screen_number, &nmodes, &mode_info_array) + && nmodes > 0) + { + // I think we should filter the returned list with + // XF86VidModeValidateModeLine() before copying to + // resolutions. However, the function seems *always* to + // return non zero value if run with ATI/AMD vide cards... + resolutions.assign(mode_info_array, mode_info_array + nmodes); + } + XFree(mode_info_array); + } +#endif + + // Filter the unnecessary and/or unusable resolutions. We also + // need to remove duplicates, since a list of XF86 mode lines + // often contain timing variations for a same width-height + // combination. + std::vector::iterator end = resolutions.end(); + end = std::remove_if(resolutions.begin(), end, LLResolution::too_small); + std::sort(resolutions.begin(), end); + end = std::unique(resolutions.begin(), end); + resolutions.erase(end, resolutions.end()); + + // If any of the above doesn't work, or we got no meaningful list, + // we consider that we can't switch the monitor resolution. + // Report the current GTK monitor dimension as the only fullscreen + // resolution. + if (2 > resolutions.size()) + { + GdkScreen * screen = gtk_window_get_screen(GTK_WINDOW(mWindow)); + gint monitor_number = gdk_screen_get_monitor_at_window(screen, mWindow->window); + GdkRectangle monitor_rectangle; + gdk_screen_get_monitor_geometry(screen, monitor_number, &monitor_rectangle); + resolutions.assign(1, monitor_rectangle); + } + + // I'm not sure we need to maintain mNumSupportedResolutions and + // mSupportedResolutions, but it seems safe to do so. + delete[] mSupportedResolutions; + mSupportedResolutions = new LLWindowResolution[resolutions.size()]; + std::copy(resolutions.begin(), resolutions.end(), mSupportedResolutions); + mNumSupportedResolutions = resolutions.size(); + + num_resolutions = mNumSupportedResolutions; + return mSupportedResolutions; } - -// close() destroys all OS-specific code associated with a window. -// Usually called from LLWindowManager::destroyWindow() -void LLWindowSDL::close() +// Change fullscreen resolution and/or switch between windowed and fullscreen mode. +BOOL LLWindowSDL::switchContext(BOOL fullscreen, LLCoordScreen size, BOOL disable_vsync) { - // Is window is already closed? - // if (!mWindow) - // { - // return; - // } + if (mFullscreen && fullscreen) + { + // Change fullscreen resolution. + mFullscreenWidth = size.mX; + mFullscreenHeight = size.mY; + switchMonitorResolution(size.mX, size.mY); + } + else if (mFullscreen && !fullscreen) + { + // Switch from fullscreen to windowed. + switchMonitorResolution(mOriginalMonitorWidth, mOriginalMonitorHeight); + gtk_window_unfullscreen(GTK_WINDOW(mWindow)); + } + else if (!mFullscreen && fullscreen) + { + // Switch from windowed to fullscreen. + getMonitorResolution(&mOriginalMonitorWidth, &mOriginalMonitorHeight); + gtk_window_fullscreen(GTK_WINDOW(mWindow)); + mFullscreenWidth = size.mX; + mFullscreenHeight = size.mY; + switchMonitorResolution(size.mX, size.mY); + } + else + { + // Switch from windowed to windowed... Do nothing anyway. + } - // Make sure cursor is visible and we haven't mangled the clipping state. - setMouseClipping(FALSE); - showCursor(); + mFullscreen = fullscreen; - destroyContext(); + // We always return TRUE, since under some configuration it can be + // impossible to fulfill the requested resolution change, and + // success/failure of fullscreen/unfullscreen changes is + // undetectable under GTK. (If we are running on GNOME desktop, + // it always succeeds.). + return TRUE; } +// Make monitor resolutioin suitable for fullscreen operation. +void LLWindowSDL::enterFullscreenResolution() +{ + getMonitorResolution(&mOriginalMonitorWidth, &mOriginalMonitorHeight); + switchMonitorResolution(mFullscreenWidth, mFullscreenHeight); +} + +// Make monitor resolution suitable for windowed operation, i.e., the +// original desktop resolution. +void LLWindowSDL::leaveFullscreenResolution() +{ + switchMonitorResolution(mOriginalMonitorWidth, mOriginalMonitorHeight); +} + BOOL LLWindowSDL::isValid() { return (mWindow != NULL); } -BOOL LLWindowSDL::getVisible() +void LLWindowSDL::show() { - BOOL result = FALSE; + gtk_window_present(GTK_WINDOW(mWindow)); +} - // *FIX: This isn't really right... - // Then what is? - if (mWindow) - { - result = TRUE; - } - - return(result); +void LLWindowSDL::hide() +{ + setMouseClipping(FALSE); + gtk_widget_hide(mWindow); } -BOOL LLWindowSDL::getMinimized() +void LLWindowSDL::minimize() { - BOOL result = FALSE; - - if (mWindow && (1 == mIsMinimized)) + setMouseClipping(FALSE); + if (mFullscreen) { - result = TRUE; + leaveFullscreenResolution(); } - return(result); + gtk_window_iconify(GTK_WINDOW(mWindow)); } -BOOL LLWindowSDL::getMaximized() +BOOL LLWindowSDL::maximize() { - BOOL result = FALSE; + setMouseClipping(FALSE); + gtk_window_maximize(GTK_WINDOW(mWindow)); + return 0 != (gdk_window_get_state(mWindow->window) & GDK_WINDOW_STATE_MAXIMIZED); +} - if (mWindow) +void LLWindowSDL::restore() +{ + const GdkWindowState state = gdk_window_get_state(mWindow->window); + if (state & GDK_WINDOW_STATE_ICONIFIED) { - // TODO + gtk_window_deiconify(GTK_WINDOW(mWindow)); + if (mFullscreen) + { + enterFullscreenResolution(); + } } + if (state & GDK_WINDOW_STATE_MAXIMIZED) + { + setMouseClipping(FALSE); + gtk_window_unmaximize(GTK_WINDOW(mWindow)); + } +} - return(result); +BOOL LLWindowSDL::getVisible() +{ + return mWindow && gdk_window_is_visible(mWindow->window); } -BOOL LLWindowSDL::maximize() +BOOL LLWindowSDL::getMinimized() { - // TODO - return FALSE; + return mWindow && 0 != (gdk_window_get_state(mWindow->window) & GDK_WINDOW_STATE_ICONIFIED); } -BOOL LLWindowSDL::getFullscreen() +BOOL LLWindowSDL::getMaximized() { - return mFullscreen; + return mWindow && 0 != (gdk_window_get_state(mWindow->window) & GDK_WINDOW_STATE_MAXIMIZED); } BOOL LLWindowSDL::getPosition(LLCoordScreen *position) { - // *FIX: can anything be done with this? - position->mX = 0; - position->mY = 0; - return TRUE; + if (!mWindow) + { + return FALSE; + } + + position->mX = mWindow->allocation.x; + position->mY = mWindow->allocation.y; + return TRUE; } BOOL LLWindowSDL::getSize(LLCoordScreen *size) { - if (mWindow) + if (!mWindow) { - size->mX = mWindow->w; - size->mY = mWindow->h; - return (TRUE); - } + return FALSE; + } - llerrs << "LLWindowSDL::getPosition(): no window and not fullscreen!" << llendl; - return (FALSE); + size->mX = mWindow->allocation.width; + size->mY = mWindow->allocation.height; + return TRUE; } BOOL LLWindowSDL::getSize(LLCoordWindow *size) { - if (mWindow) + // I believe the following is the equivalent behaviour to + // LLWindowWin32::getSize(LLXCoordWindow*), but in GTK, mWindow + // and mGLWidget have exactly same size, so we could have just one + // method impl... + + if (!mWindow) { - size->mX = mWindow->w; - size->mY = mWindow->h; - return (TRUE); - } + return FALSE; + } - llerrs << "LLWindowSDL::getPosition(): no window and not fullscreen!" << llendl; - return (FALSE); + size->mX = mGLWidget->allocation.width; + size->mY = mGLWidget->allocation.height; + return TRUE; } BOOL LLWindowSDL::setPosition(const LLCoordScreen position) { if(mWindow) { - // *FIX: (???) - //MacMoveWindow(mWindow, position.mX, position.mY, false); + gtk_window_move(GTK_WINDOW(mWindow), position.mX, position.mY); } return TRUE; @@ -942,8 +1213,7 @@ { if(mWindow) { - // *FIX: (???) - //SizeWindow(mWindow, size.mX, size.mY, true); + gtk_window_resize(GTK_WINDOW(mWindow), size.mX, size.mY); } return TRUE; @@ -952,110 +1222,235 @@ void LLWindowSDL::swapBuffers() { if (mWindow) - SDL_GL_SwapBuffers(); + { + GdkGLDrawable *gl_drawable = gtk_widget_get_gl_drawable(mGLWidget); + + // Our GL drawable is always double buffered. The following + // condition is just for a safe guard. + if (gdk_gl_drawable_is_double_buffered(gl_drawable)) + { + gdk_gl_drawable_swap_buffers(gl_drawable); + } + } } F32 LLWindowSDL::getGamma() { - return 1/mGamma; + return mGamma; } BOOL LLWindowSDL::restoreGamma() { - //CGDisplayRestoreColorSyncSettings(); - SDL_SetGamma(1.0f, 1.0f, 1.0f); - return true; + // We should save initial gamma ramp tables and restore them here. + // FIXME. + return setGamma(1); } -BOOL LLWindowSDL::setGamma(const F32 gamma) +// Calculate a 16 bit gamma corrected level for a given intensity value. +static inline U16 ll_gamma(F32 value, F32 gamma) { - mGamma = gamma; - if (mGamma == 0) mGamma = 0.1f; - mGamma = 1/mGamma; - SDL_SetGamma(mGamma, mGamma, mGamma); - return true; + return llround(powf(value, gamma) * 65535); } -BOOL LLWindowSDL::isCursorHidden() +// Try to initialize the window to use DirectColor visual so that +// gamma adjustment by direct color colormap method works. We need to +// do this after creating the GTK window and before mapping/realizing +// it. +void LLWindowSDL::initializeGamma() { - return mCursorHidden; + GdkVisual *visual = gdk_visual_get_best_with_type(GDK_VISUAL_DIRECT_COLOR); + if (visual && + visual->type == GDK_VISUAL_DIRECT_COLOR && + visual->bits_per_rgb >= 8) + { + // Yes, this visual is what we need. Use it. + GdkColormap *colormap = gdk_colormap_new(visual, TRUE); + + const F32 scale = 1.f / (colormap->size - 1); + for (int i = 0; i < colormap->size; i++) + { + // Although undocument, gdk_colormap_change() ignores + // GdkColor::pixel for DirectColor visuals, so we don't + // care about it. + GdkColor &color = colormap->colors[i]; + const guint16 level = ll_gamma(i * scale, 1); + color.red = level; + color.green = level; + color.blue = level; + } + gdk_colormap_change(colormap, colormap->size); + gtk_widget_set_colormap(mWindow, colormap); + } } +static const char GAMMA_DIRECTCOLOR[] = "DirectColor colormap"; +static const char GAMMA_XRANDR[] = "XRandR Extension"; +static const char GAMMA_XF86VM[] = "XF86VideoMode Extension"; - -// Constrains the mouse to the window. -void LLWindowSDL::setMouseClipping( BOOL b ) +static void report_gamma_adjustment_method(const char * const method) { - //llinfos << "LLWindowSDL::setMouseClipping " << b << llendl; - // Just stash the requested state. We'll simulate this when the cursor is hidden by decoupling. - mIsMouseClipping = b; - //SDL_WM_GrabInput(b ? SDL_GRAB_ON : SDL_GRAB_OFF); - adjustCursorDecouple(); + static const char * previous = NULL; + if (method != previous) + { + llinfos << "Using " << method << " for gamma adjustment" << llendl; + previous = method; + } } -BOOL LLWindowSDL::setCursorPosition(const LLCoordWindow position) +BOOL LLWindowSDL::setGamma(F32 gamma) { - BOOL result = TRUE; - LLCoordScreen screen_pos; + // Let the gamma value be in a reasonable range. + mGamma = (gamma == 0 ? 1.f : llclamp(gamma, 0.1f, 10.f)); - if (!convertCoords(position, &screen_pos)) + // First, we try to use colormap to adjust the intensity level. + // This method should always be possible with GTK/GDK, but it + // requires a DirectColor visual. It should work best under some + // high-end graphics cards (such as Wildcat products of blessed + // memory) with multiple hardware LUTs, since under such + // configuration, the gamma value set here affects this window + // only. It also works good with entry level 3D graphics cards + // with single LUT, because the gamma takes place only when this + // window is active. Other methods, XRandR or XF86VM, affects + // entire screen and the effect is persistent. + GdkColormap * const colormap = gtk_widget_get_colormap(mWindow); + if (GDK_VISUAL_DIRECT_COLOR == gdk_colormap_get_visual(colormap)->type) { - return FALSE; + const F32 scale = 1.f / (colormap->size - 1); + for (int i = 0; i < colormap->size; i++) + { + GdkColor &color = colormap->colors[i]; + const guint16 level = ll_gamma(i * scale, mGamma); + color.red = level; + color.green = level; + color.blue = level; + } + gdk_colormap_change(colormap, colormap->size); + report_gamma_adjustment_method(GAMMA_DIRECTCOLOR); + return TRUE; } - //llinfos << "setCursorPosition(" << screen_pos.mX << ", " << screen_pos.mY << ")" << llendl; +#if LL_XRANDR + // Try gamma manipulation API available in XRandR 1.2. Well, I + // have never seen X server (display driver) that support XRandR + // 1.2 based gamma correction. All implementations apparently + // support it, but returns a zero for XRRGetCrtcGammaSize(), that + // is unusable. I can't say I have tested the following code... + if (mXRRVersion >= 12) + { + Display *x11_display = GDK_DISPLAY(); + GdkScreen *screen = gtk_window_get_screen(GTK_WINDOW(mWindow)); + Window x11_root_window = RootWindow(x11_display, GDK_SCREEN_XNUMBER(screen)); + + // Build a list of CRTCs that covers (part of) this window and + // provides usable gamma ramp table. + std::vector crtcs; + XRRScreenResources *screen_resources = XRRGetScreenResources(x11_display, x11_root_window); + const S32 window_left = mWindow->allocation.x; + const S32 window_top = mWindow->allocation.y; + const S32 window_right = window_left + mWindow->allocation.width; + const S32 window_bottom = window_top + mWindow->allocation.height; + for (int i = 0; i < screen_resources->ncrtc; i++) + { + const RRCrtc crtc = screen_resources->crtcs[i]; + XRRCrtcInfo * const info = XRRGetCrtcInfo(x11_display, screen_resources, crtc); + if (info && None != info->mode + && llmax((S32) info->x, window_left) < llmin((S32) (info->x + info->width), window_right) + && llmax((S32) info->y, window_top) < llmin((S32) (info->y + info->height), window_bottom) + && XRRGetCrtcGammaSize(x11_display, crtc) >= 3) + { + crtcs.push_back(crtc); + } + XRRFreeCrtcInfo(info); + } + XRRFreeScreenResources(screen_resources); - SDL_WarpMouse(screen_pos.mX, screen_pos.mY); + if (!crtcs.empty()) + { + // Update the gamma tables as requested. + for (int i = 0; i < crtcs.size(); i++) + { + const int ramps = XRRGetCrtcGammaSize(x11_display, crtcs[i]); + XRRCrtcGamma * const new_gamma_table = XRRAllocGamma(ramps); + const F32 scale = 1.f / (ramps - 1); + for (int r = 0; r < ramps; r++) + { + const int level = ll_gamma(r * scale, mGamma); + new_gamma_table->red[r] = level; + new_gamma_table->green[r] = level; + new_gamma_table->blue[r] = level; + } + XRRSetCrtcGamma(x11_display, crtcs[i], new_gamma_table); + XRRFreeGamma(new_gamma_table); + } + report_gamma_adjustment_method(GAMMA_XRANDR); + return TRUE; + } + } +#endif - // Under certain circumstances, this will trigger us to decouple the cursor. - adjustCursorDecouple(true); +#if LL_XF86VM + // Try XF86 Video Mode 2.0 method as a last resort. + if (mXF86VMVersion >= 20) + { + Display *x11_display = GDK_DISPLAY(); + XF86VidModeGamma vid_mode_gamma; + memset(&vid_mode_gamma, 0, sizeof vid_mode_gamma); + // XF86VM gamma is inverted... + F32 xf86vm_gamma = 1.f / mGamma; + vid_mode_gamma.red = xf86vm_gamma; + vid_mode_gamma.green = xf86vm_gamma; + vid_mode_gamma.blue = xf86vm_gamma; + GdkScreen * screen = gtk_window_get_screen(GTK_WINDOW(mWindow)); + Status result = XF86VidModeSetGamma(x11_display, GDK_SCREEN_XNUMBER(screen), &vid_mode_gamma); + if (result) + { + report_gamma_adjustment_method(GAMMA_XF86VM); + return TRUE; + } + } +#endif - return result; + // We couldn't change gamma. + return FALSE; } -BOOL LLWindowSDL::getCursorPosition(LLCoordWindow *position) +// Constrains the mouse to the window. +void LLWindowSDL::setMouseClipping( BOOL b ) { - //Point cursor_point; - LLCoordScreen screen_pos; - - //GetMouse(&cursor_point); - int x, y; - SDL_GetMouseState(&x, &y); - - screen_pos.mX = x; - screen_pos.mY = y; - - return convertCoords(screen_pos, position); + // We need gdk_pointer_grab() to restrict the mouse pointer + // movement. However, gdk_pointer_grab is already used by + // reallyCaptureInput(BOOL) to hack some window managers. + // It is not easy to make them co-exist. + // + // We give up mouse clipping. + // + // In a future, we may eventually add this functionality by + // somehow mediating reallyCaptureInput(BOOL) and this method. } -void LLWindowSDL::adjustCursorDecouple(bool warpingMouse) +BOOL LLWindowSDL::setCursorPosition(const LLCoordWindow position_window) { - if(mIsMouseClipping && mCursorHidden) + LLCoordScreen position_screen; + if (!convertCoords(position_window, &position_screen)) { - if(warpingMouse) - { - // The cursor should be decoupled. Make sure it is. - if(!mCursorDecoupled) - { - // llinfos << "adjustCursorDecouple: decoupling cursor" << llendl; - //CGAssociateMouseAndMouseCursorPosition(false); - mCursorDecoupled = true; - mCursorIgnoreNextDelta = TRUE; - } - } + return FALSE; } - else - { - // The cursor should not be decoupled. Make sure it isn't. - if(mCursorDecoupled) - { - // llinfos << "adjustCursorDecouple: recoupling cursor" << llendl; - //CGAssociateMouseAndMouseCursorPosition(true); - mCursorDecoupled = false; - } - } + + GdkDisplay * display = gtk_widget_get_display(mWindow); + GdkScreen * screen = gtk_widget_get_screen(mWindow); + gdk_display_warp_pointer(display, screen, position_screen.mX, position_screen.mY); + return TRUE; } +BOOL LLWindowSDL::getCursorPosition(LLCoordWindow *position_window) +{ + gint x, y; + gdk_display_get_pointer(gtk_widget_get_display(mWindow), NULL, &x, &y, NULL); + + const LLCoordScreen position_screen(x, y); + return convertCoords(position_screen, position_window); +} + F32 LLWindowSDL::getNativeAspectRatio() { #if 0 @@ -1065,7 +1460,6 @@ S32 num_resolutions; LLWindowResolution* resolutions = getSupportedResolutions(num_resolutions); - return ((F32)resolutions[num_resolutions - 1].mWidth / (F32)resolutions[num_resolutions - 1].mHeight); //rn: AC #endif @@ -1087,18 +1481,30 @@ // The constructor for this class grabs the aspect ratio of the monitor before doing any resolution // switching, and stashes it in mOriginalAspectRatio. Here, we just return it. + // ----------------------- + + // All the comments above this are inherited from the older SDL + // version. What we are doing here is similar to the last + // commented behaviour; that is, grabbing the monitor pixel + // dimension before entering fullscreen, and assuming pixels are + // square. If we really need physically correct aspect ratio of + // the monitor, we can query the physical dimension (i.e., width + // and height in millimeters) for each operating mode through + // XRandR that are taken from the display device itself through + // EDID. I'm not sure it's worth doing so. + if (mOverrideAspectRatio > 0.f) { return mOverrideAspectRatio; } - return mOriginalAspectRatio; + return (F32) mOriginalMonitorWidth / (F32) mOriginalMonitorHeight; } F32 LLWindowSDL::getPixelAspectRatio() { F32 pixel_aspect = 1.f; - if (getFullscreen()) + if (mFullscreen) { LLCoordScreen screen_size; getSize(&screen_size); @@ -1112,57 +1518,15 @@ // some of this stuff is to support 'temporarily windowed' mode so that // dialogs are still usable in fullscreen. HOWEVER! - it's not enabled/working // yet. -static LLCoordScreen old_size; -static BOOL old_fullscreen; + void LLWindowSDL::beforeDialog() { - llinfos << "LLWindowSDL::beforeDialog()" << llendl; - - if (SDLReallyCaptureInput(FALSE) // must ungrab input so popup works! - && getSize(&old_size)) - { - old_fullscreen = was_fullscreen; - - if (old_fullscreen) - { - // NOT YET WORKING - //switchContext(FALSE, old_size, TRUE); - } - } - -#if LL_X11 - if (mSDL_Display) - { - // Everything that we/SDL asked for should happen before we - // potentially hand control over to GTK. - maybe_lock_display(); - XSync(mSDL_Display, False); - maybe_unlock_display(); - } -#endif // LL_X11 - -#if LL_GTK - // this is a good time to grab some GTK version information for - // diagnostics, if not already done. - ll_try_gtk_init(); -#endif // LL_GTK - - maybe_lock_display(); + // Nothing special to do. } void LLWindowSDL::afterDialog() { - llinfos << "LLWindowSDL::afterDialog()" << llendl; - - maybe_unlock_display(); - - if (old_fullscreen && !was_fullscreen) - { - // *FIX: NOT YET WORKING (see below) - //switchContext(TRUE, old_size, TRUE); - } - // *FIX: we need to restore the GL context using - // LLViewerWindow::restoreGL() - but how?? + // Nothing special to do. } @@ -1171,629 +1535,66 @@ return ::stat( file_name, stat_info ); } -#if LL_X11 -// set/reset the XWMHints flag for 'urgency' that usually makes the icon flash -void LLWindowSDL::x11_set_urgent(BOOL urgent) -{ - if (mSDL_Display && !mFullscreen) - { - XWMHints *wm_hints; - - llinfos << "X11 hint for urgency, " << urgent << llendl; - - maybe_lock_display(); - wm_hints = XGetWMHints(mSDL_Display, mSDL_XWindowID); - if (!wm_hints) - wm_hints = XAllocWMHints(); - - if (urgent) - wm_hints->flags |= XUrgencyHint; - else - wm_hints->flags &= ~XUrgencyHint; - - XSetWMHints(mSDL_Display, mSDL_XWindowID, wm_hints); - XFree(wm_hints); - XSync(mSDL_Display, False); - maybe_unlock_display(); - } -} -#endif // LL_X11 - void LLWindowSDL::flashIcon(F32 seconds) { -#if !LL_X11 - llinfos << "Stub LLWindowSDL::flashIcon(" << seconds << ")" << llendl; -#else - llinfos << "X11 LLWindowSDL::flashIcon(" << seconds << ")" << llendl; - F32 remaining_time = mFlashTimer.getRemainingTimeF32(); if (remaining_time < seconds) remaining_time = seconds; mFlashTimer.reset(); mFlashTimer.setTimerExpirySec(remaining_time); - x11_set_urgent(TRUE); + gtk_window_set_urgency_hint(GTK_WINDOW(mWindow), TRUE); mFlashing = TRUE; -#endif // LL_X11 } -#if LL_X11 -/* Lots of low-level X11 stuff to handle X11 copy-and-paste */ -/* Our X11 clipboard support is a bit bizarre in various - organically-grown ways. Ideally it should be fixed to do - real string-type negotiation (this would make pasting to - xterm faster and pasting to UTF-8 emacs work properly), but - right now it has the rare and desirable trait of being - generally stable and working. */ - -typedef Atom x11clipboard_type; - -/* PRIMARY and CLIPBOARD are the two main kinds of - X11 clipboard. A third are the CUT_BUFFERs which an - obsolete holdover from X10 days and use a quite orthogonal - mechanism. CLIPBOARD is the type whose design most - closely matches SL's own win32-alike explicit copy-and-paste - paradigm. - - Pragmatically we support all three to varying degrees. When - we paste into SL, it is strictly from CLIPBOARD. When we copy, - we support (to as full an extent as the clipboard content type - allows) CLIPBOARD, PRIMARY, and CUT_BUFFER0. - */ -static x11clipboard_type get_x11_readwrite_clipboard_type(void) -{ - return XInternAtom(get_SDL_Display(), "CLIPBOARD", False); -} - -static x11clipboard_type get_x11_write_clipboard_type(void) -{ - return XA_PRIMARY; -} - -/* This is where our own private cutbuffer goes - we don't use - a regular cutbuffer (XA_CUT_BUFFER0 etc) for intermediate - storage because their use isn't really defined for holding UTF8. */ -static x11clipboard_type get_x11_cutbuffer_clipboard_type(void) -{ - return XInternAtom(get_SDL_Display(), "SECONDLIFE_CUTBUFFER", False); -} - -/* Some X11 atom-generators */ -static Atom get_x11_targets_atom(void) -{ - return XInternAtom(get_SDL_Display(), "TARGETS", False); -} - -static Atom get_x11_text_atom(void) -{ - return XInternAtom(get_SDL_Display(), "TEXT", False); -} - -/* These defines, and convert_data/convert_x11clipboard, - mostly exist to support non-text or unusually-encoded - clipboard data, which we don't really have a need for at - the moment. */ -#define SDLCLIPTYPE(A, B, C, D) (int)(((A)<<24)|((B)<<16)|((C)<<8)|((D)<<0)) -#define FORMAT_PREFIX "SECONDLIFE_x11clipboard_0x" - -static -x11clipboard_type convert_format(int type) -{ - if (!gWindowImplementation) - { - llwarns << "!gWindowImplementation in convert_format()" - << llendl; - return XA_STRING; - } - - switch (type) - { - case SDLCLIPTYPE('T', 'E', 'X', 'T'): - // old-style X11 clipboard, strictly only ISO 8859-1 encoding - return XA_STRING; - case SDLCLIPTYPE('U', 'T', 'F', '8'): - // newer de-facto UTF8 clipboard atom - return XInternAtom(gWindowImplementation->mSDL_Display, - "UTF8_STRING", False); - default: - { - /* completely arbitrary clipboard types... we don't actually use - these right now, and support is skeletal. */ - char format[sizeof(FORMAT_PREFIX)+8+1]; /* Flawfinder: ignore */ - - snprintf(format, sizeof(format), "%s%08lx", FORMAT_PREFIX, (unsigned long)type); - return XInternAtom(gWindowImplementation->mSDL_Display, - format, False); - } - } -} - -/* convert platform string to x11 clipboard format. for our - purposes this is pretty trivial right now. */ -static int -convert_data(int type, char *dst, const char *src, int srclen) -{ - int dstlen; - - dstlen = 0; - switch (type) - { - case SDLCLIPTYPE('T', 'E', 'X', 'T'): - case SDLCLIPTYPE('U', 'T', 'F', '8'): - if (src == NULL) - { - break; - } - if ( srclen == 0 ) - srclen = strlen(src); /* Flawfinder: ignore */ - - dstlen = srclen + 1; - - if ( dst ) // assume caller made it big enough by asking us - { - memcpy(dst, src, srclen); /* Flawfinder: ignore */ - dst[srclen] = '\0'; - } - break; - - default: - llwarns << "convert_data: Unknown medium type" << llendl; - break; - } - return(dstlen); -} - -/* Convert x11clipboard data to platform string. This too is - pretty trivial for our needs right now, and just about identical - to above. */ -static int -convert_x11clipboard(int type, char *dst, const char *src, int srclen) -{ - int dstlen; - - dstlen = 0; - switch (type) - { - case SDLCLIPTYPE('U', 'T', 'F', '8'): - case SDLCLIPTYPE('T', 'E', 'X', 'T'): - if (src == NULL) - { - break; - } - if ( srclen == 0 ) - srclen = strlen(src); /* Flawfinder: ignore */ - - dstlen = srclen + 1; - - if ( dst ) // assume caller made it big enough by asking us - { - memcpy(dst, src, srclen); /* Flawfinder: ignore */ - dst[srclen] = '\0'; - } - break; - - default: - llwarns << "convert_x11clipboard: Unknown medium type" << llendl; - break; - } - return dstlen; -} - -int -LLWindowSDL::is_empty_x11clipboard(void) -{ - int retval; - - maybe_lock_display(); - retval = ( XGetSelectionOwner(mSDL_Display, get_x11_readwrite_clipboard_type()) == None ); - maybe_unlock_display(); - - return(retval); -} - -void -LLWindowSDL::put_x11clipboard(int type, int srclen, const char *src) -{ - x11clipboard_type format; - int dstlen; - char *dst; - - format = convert_format(type); - dstlen = convert_data(type, NULL, src, srclen); - - dst = (char *)malloc(dstlen); - if ( dst != NULL ) - { - maybe_lock_display(); - Window root = DefaultRootWindow(mSDL_Display); - convert_data(type, dst, src, srclen); - // Cutbuffers are only allowed to have STRING atom types, - // but Emacs puts UTF8 inside them anyway. We cautiously - // don't. - if (type == SDLCLIPTYPE('T','E','X','T')) - { - // dstlen-1 so we don't include the trailing \0 - llinfos << "X11: Populating cutbuffer." <event.xevent; - - if ( (xevent.type == SelectionNotify)&& - (xevent.xselection.requestor == owner) ) - selection_response = 1; - } - } else { - llinfos << "X11: Waiting for SYSWM event..." << llendl; - } - } - llinfos << "X11: Clipboard arrived." <type != SDL_SYSWMEVENT ) - { - return(1); - } - - /* Handle window-manager specific clipboard events */ - switch (event->syswm.msg->event.xevent.type) { - /* Copy the selection from SECONDLIFE_CUTBUFFER to the requested property */ - case SelectionRequest: { - XSelectionRequestEvent *req; - XEvent sevent; - int seln_format; - unsigned long nbytes; - unsigned long overflow; - unsigned char *seln_data; - - req = &event->syswm.msg->event.xevent.xselectionrequest; - sevent.xselection.type = SelectionNotify; - sevent.xselection.display = req->display; - sevent.xselection.selection = req->selection; - sevent.xselection.target = None; - sevent.xselection.property = None; - sevent.xselection.requestor = req->requestor; - sevent.xselection.time = req->time; - if ( XGetWindowProperty(get_SDL_Display(), DefaultRootWindow(get_SDL_Display()), - get_x11_cutbuffer_clipboard_type(), 0, INT_MAX/4, False, req->target, - &sevent.xselection.target, &seln_format, - &nbytes, &overflow, &seln_data) == Success ) - { - if ( sevent.xselection.target == req->target) - { - if ( sevent.xselection.target == XA_STRING || - sevent.xselection.target == - convert_format(SDLCLIPTYPE('U','T','F','8')) ) - { - if ( seln_data[nbytes-1] == '\0' ) - --nbytes; - } - XChangeProperty(get_SDL_Display(), req->requestor, req->property, - req->target, seln_format, PropModeReplace, - seln_data, nbytes); - sevent.xselection.property = req->property; - } else if (get_x11_targets_atom() == req->target) { - /* only advertise what we currently support */ - const int num_supported = 3; - Atom supported[num_supported] = { - XA_STRING, // will be over-written below - get_x11_text_atom(), - get_x11_targets_atom() - }; - supported[0] = sevent.xselection.target; - XChangeProperty(get_SDL_Display(), req->requestor, - req->property, XA_ATOM, 32, PropModeReplace, - (unsigned char*)supported, - num_supported); - sevent.xselection.property = req->property; - llinfos << "Clipboard: An app asked us what selections format we offer." << llendl; - } else { - llinfos << "Clipboard: An app requested an unsupported selection format " << req->target << ", we have " << sevent.xselection.target << llendl; - sevent.xselection.target = None; - } - XFree(seln_data); - } - int sendret = - XSendEvent(get_SDL_Display(),req->requestor,False,0,&sevent); - if ((sendret==BadValue) || (sendret==BadWindow)) - llwarns << "Clipboard SendEvent failed" << llendl; - XSync(get_SDL_Display(), False); - } - break; - } - - /* Post the event for X11 clipboard reading above */ - return(1); -} - -int -LLWindowSDL::init_x11clipboard(void) -{ - SDL_SysWMinfo info; - int retval; - - /* Grab the window manager specific information */ - retval = -1; - SDL_SetError("SDL is not running on known window manager"); - - SDL_VERSION(&info.version); - if ( SDL_GetWMInfo(&info) ) - { - /* Save the information for later use */ - if ( info.subsystem == SDL_SYSWM_X11 ) - { - mSDL_Display = info.info.x11.display; - mSDL_XWindowID = info.info.x11.wmwindow; - Lock_Display = info.info.x11.lock_func; - Unlock_Display = info.info.x11.unlock_func; - - /* Enable the special window hook events */ - SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE); - SDL_SetEventFilter(clipboard_filter_callback); - - retval = 0; - } - else - { - SDL_SetError("SDL is not running on X11"); - } - } - return(retval); -} - -void -LLWindowSDL::quit_x11clipboard(void) -{ - mSDL_Display = NULL; - mSDL_XWindowID = None; - Lock_Display = NULL; - Unlock_Display = NULL; - - SDL_SetEventFilter(NULL); // Stop custom event filtering -} - /************************************************/ BOOL LLWindowSDL::isClipboardTextAvailable() { - return !is_empty_x11clipboard(); + GtkClipboard * const clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); + return gtk_clipboard_wait_is_text_available(clipboard); } -BOOL LLWindowSDL::pasteTextFromClipboard(LLWString &dst) +BOOL LLWindowSDL::pasteTextFromClipboard(LLWString &text) { - int cliplen; // seems 1 or 2 bytes longer than expected - char *cliptext = NULL; - get_x11clipboard(SDLCLIPTYPE('U','T','F','8'), &cliplen, &cliptext); - if (cliptext) + GtkClipboard * const clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); + gchar * const data = gtk_clipboard_wait_for_text(clipboard); + if (data) { - llinfos << "X11: Got UTF8 clipboard text." << llendl; - // at some future time we can use cliplen instead of relying on \0, - // if we ever grok non-ascii, non-utf8 encodings on the clipboard. - std::string clip_str(cliptext); - // we can't necessarily trust the incoming text to be valid UTF-8, - // but utf8str_to_wstring() seems to do an appropriate level of - // validation for avoiding over-reads. - dst = utf8str_to_wstring(clip_str); - /*llinfos << "X11 pasteTextFromClipboard: cliplen=" << cliplen << - " strlen(cliptext)=" << strlen(cliptext) << - " clip_str.length()=" << clip_str.length() << - " dst.length()=" << dst.length() << - llendl;*/ - free(cliptext); - return TRUE; // success + text = LLWString(utf8str_to_wstring(data)); + g_free(data); + return TRUE; } - get_x11clipboard(SDLCLIPTYPE('T','E','X','T'), &cliplen, &cliptext); - if (cliptext) - { - llinfos << "X11: Got ISO 8859-1 clipboard text." << llendl; - std::string clip_str(cliptext); - std::string utf8_str = rawstr_to_utf8(clip_str); - dst = utf8str_to_wstring(utf8_str); - free(cliptext); - } - return FALSE; // failure + return FALSE; } -BOOL LLWindowSDL::copyTextToClipboard(const LLWString &s) +BOOL LLWindowSDL::copyTextToClipboard(const LLWString &text) { - std::string utf8text = wstring_to_utf8str(s); - const char* cstr = utf8text.c_str(); - if (cstr == NULL) - { - return FALSE; - } - int cstrlen = strlen(cstr); /* Flawfinder: ignore */ - int i; - for (i=0; iw; - int h = r->h; - if ((w >= 800) && (h >= 600)) - { - // make sure we don't add the same resolution multiple times! - if ( (mNumSupportedResolutions == 0) || - ((mSupportedResolutions[mNumSupportedResolutions-1].mWidth != w) && - (mSupportedResolutions[mNumSupportedResolutions-1].mHeight != h)) ) - { - mSupportedResolutions[mNumSupportedResolutions].mWidth = w; - mSupportedResolutions[mNumSupportedResolutions].mHeight = h; - mNumSupportedResolutions++; - } - } - } - } - } - - num_resolutions = mNumSupportedResolutions; - return mSupportedResolutions; -} - BOOL LLWindowSDL::convertCoords(LLCoordGL from, LLCoordWindow *to) { if (!to) + { return FALSE; + } to->mX = from.mX; - to->mY = mWindow->h - from.mY - 1; + to->mY = mGLWidget->allocation.height - from.mY - 1; return TRUE; } @@ -1801,10 +1602,12 @@ BOOL LLWindowSDL::convertCoords(LLCoordWindow from, LLCoordGL* to) { if (!to) + { return FALSE; + } to->mX = from.mX; - to->mY = mWindow->h - from.mY - 1; + to->mY = mGLWidget->allocation.height - from.mY - 1; return TRUE; } @@ -1812,23 +1615,31 @@ BOOL LLWindowSDL::convertCoords(LLCoordScreen from, LLCoordWindow* to) { if (!to) + { return FALSE; + } - // In the fullscreen case, window and screen coordinates are the same. - to->mX = from.mX; - to->mY = from.mY; - return (TRUE); + gint x, y; + gdk_window_get_position(mWindow->window, &x, &y); + to->mX = from.mX - x; + to->mY = from.mY - y; + + return TRUE; } BOOL LLWindowSDL::convertCoords(LLCoordWindow from, LLCoordScreen *to) { if (!to) + { return FALSE; + } - // In the fullscreen case, window and screen coordinates are the same. - to->mX = from.mX; - to->mY = from.mY; - return (TRUE); + gint x, y; + gdk_window_get_position(mWindow->window, &x, &y); + to->mX = from.mX + x; + to->mY = from.mY + y; + + return TRUE; } BOOL LLWindowSDL::convertCoords(LLCoordScreen from, LLCoordGL *to) @@ -1845,9 +1656,6 @@ return(convertCoords(from, &window_coord) && convertCoords(window_coord, to)); } - - - void LLWindowSDL::setupFailure(const char* text, const char* caption, U32 type) { destroyContext(); @@ -1855,90 +1663,52 @@ OSMessageBox(text, caption, type); } -BOOL LLWindowSDL::SDLReallyCaptureInput(BOOL capture) +void LLWindowSDL::reallyCaptureInput(BOOL capture) { + // Converted (and slightly simplified) from SDL versions' Xlib API + // based operation. + // note: this used to be safe to call nestedly, but in the // end that's not really a wise usage pattern, so don't. if (capture) - mReallyCapturedCount = 1; - else - mReallyCapturedCount = 0; - - SDL_GrabMode wantmode, newmode; - if (mReallyCapturedCount <= 0) // uncapture { - wantmode = SDL_GRAB_OFF; - } else // capture - { - wantmode = SDL_GRAB_ON; + gdk_pointer_grab(mWindow->window, TRUE, (GdkEventMask)0, NULL, NULL, GDK_CURRENT_TIME); } - - if (mReallyCapturedCount < 0) // yuck, imbalance. + else { - mReallyCapturedCount = 0; - llwarns << "ReallyCapture count was < 0" << llendl; + gdk_pointer_ungrab(GDK_CURRENT_TIME); } + + // SDL version calls XSync() only after ungrabbing, + // with the following comment: + // + // // Make sure the ungrab happens RIGHT NOW. + // + // I don't think it is important to ungrab quickly, since it + // occurs when a user releases a _grabby_ key to do something + // other than the operation he/she did with holding the key, and + // it is unusual to click very soon after releasing the key. On + // the other hand, it may be important to grab as soon as possible + // after a user presses a grabby key, since he/she may press the + // key and mouse button almost simultaneously. Moreover, I see no + // point waiting the completion of the grab/ungrab request here, + // since anyway the user may perform the _next_ action, regardless + // whether we are waiting or keep going. + // + // Based on the above consideration, I decided to put a flush + // operation here for execution after both grabbing and + // ungrabbing. Opinions? -- Alissa - if (!mFullscreen) /* only bother if we're windowed anyway */ - { -#if LL_X11 - if (mSDL_Display) - { - /* we dirtily mix raw X11 with SDL so that our pointer - isn't (as often) constrained to the limits of the - window while grabbed, which feels nicer and - hopefully eliminates some reported 'sticky pointer' - problems. We use raw X11 instead of - SDL_WM_GrabInput() because the latter constrains - the pointer to the window and also steals all - *keyboard* input from the window manager, which was - frustrating users. */ - int result; - if (wantmode == SDL_GRAB_ON) - { - //llinfos << "X11 POINTER GRABBY" << llendl; - //newmode = SDL_WM_GrabInput(wantmode); - maybe_lock_display(); - result = XGrabPointer(mSDL_Display, mSDL_XWindowID, - True, 0, GrabModeAsync, - GrabModeAsync, - None, None, CurrentTime); - maybe_unlock_display(); - if (GrabSuccess == result) - newmode = SDL_GRAB_ON; - else - newmode = SDL_GRAB_OFF; - } else if (wantmode == SDL_GRAB_OFF) - { - //llinfos << "X11 POINTER UNGRABBY" << llendl; - newmode = SDL_GRAB_OFF; - //newmode = SDL_WM_GrabInput(SDL_GRAB_OFF); - - maybe_lock_display(); - XUngrabPointer(mSDL_Display, CurrentTime); - // Make sure the ungrab happens RIGHT NOW. - XSync(mSDL_Display, False); - maybe_unlock_display(); - } else - { - newmode = SDL_GRAB_QUERY; // neutral - } - } else // not actually running on X11, for some reason - newmode = wantmode; -#endif // LL_X11 - } else { - // pretend we got what we wanted, when really we don't care. - newmode = wantmode; - } - - // return boolean success for whether we ended up in the desired state - return (capture && SDL_GRAB_ON==newmode) || - (!capture && SDL_GRAB_OFF==newmode); + gdk_display_flush(gtk_widget_get_display(mWindow)); } -U32 LLWindowSDL::SDLCheckGrabbyKeys(SDLKey keysym, BOOL gain) +U32 LLWindowSDL::checkGrabbyKeys(guint keysym, BOOL gain) { + // The following comments are from SDL version, but GDK shares + // same grabbing model and window manager environments with X11, + // so they apply. + /* part of the fix for SL-13243: Some popular window managers like to totally eat alt-drag for the purposes of moving windows. We spoil their day by acquiring the exclusive X11 mouse lock for as @@ -1955,359 +1725,509 @@ U32 mask = 0; switch (keysym) { - case SDLK_LALT: - mask = 1U << 0; break; - case SDLK_LCTRL: - mask = 1U << 1; break; - case SDLK_RCTRL: - mask = 1U << 2; break; + case GDK_Alt_L: + mask = 1U << 0; + break; + case GDK_Control_L: + mask = 1U << 1; + break; + case GDK_Control_R: + mask = 1U << 2; + break; default: break; } if (gain) + { mGrabbyKeyFlags |= mask; + } else + { mGrabbyKeyFlags &= ~mask; + } - //llinfos << "mGrabbyKeyFlags=" << mGrabbyKeyFlags << llendl; - /* 0 means we don't need to mousegrab, otherwise grab. */ return mGrabbyKeyFlags; } void LLWindowSDL::gatherInput() { - const Uint32 CLICK_THRESHOLD = 300; // milliseconds - static int leftClick = 0; - static int rightClick = 0; - static Uint32 lastLeftDown = 0; - static Uint32 lastRightDown = 0; - SDL_Event event; + // XXX Are we still current in GL? XXX + GdkGLContext * const gl_context = gtk_widget_get_gl_context(mGLWidget); + if (gdk_gl_context_get_current() != gl_context) + { + llwarns << "*** OOPS! someone took GL rendering context! Recovering..." << llendl; + GdkGLDrawable * const gl_drawable = gtk_widget_get_gl_drawable(mGLWidget); + gdk_gl_drawable_gl_begin(gl_drawable, gl_context); + } -#if LL_GTK && LL_LIBXUL_ENABLED - // Pump GTK events so embedded Gecko doesn't starve. - if (ll_try_gtk_init()) - { - // Yuck, Mozilla's GTK callbacks play with the locale - push/pop - // the locale to protect it, as exotic/non-C locales - // causes our code lots of general critical weirdness - // and crashness. (SL-35450) - std::string saved_locale = setlocale(LC_ALL, NULL); + // The following comment is still valid, but the real problem, + // IMHO, is not in Mozilla. Both GTK and Mozilla simply follow + // well accepted posix C programming conventioin: call + // setlocale(LC_ALL, "") always. However, The SL viewer code is + // written in a style of early 1980 programming, ignoring the fact + // that there are some cultual conventions other than US's... + // It's clear we need to somehow handle it, but it is just another + // story. The issue I (Alissa) am tackling now is the GtkGLExt + // and GTK immodule adaptation. For the moment, I gave up to + // fix the issue inside SL viewer code, and wrote a quick + // workaround outside of the program. See wrapper.sh for details. - // Do a limited number of pumps so SL doesn't starve! - // *TODO: this should ideally be time-limited, not count-limited. - gtk_main_iteration_do(0); // Always do one non-blocking pump - for (int iter=0; iter<10; ++iter) - if (gtk_events_pending()) - gtk_main_iteration(); + // Yuck, Mozilla's GTK callbacks play with the locale - push/pop + // the locale to protect it, as exotic/non-C locales + // causes our code lots of general critical weirdness + // and crashness. (SL-35450) +// std::string saved_locale = setlocale(LC_ALL, NULL); - setlocale(LC_ALL, saved_locale.c_str() ); - } -#endif // LL_GTK && LL_LIBXUL_ENABLED + // Handle all pending events. Should we limit the number of + // events or elapsed time per an invocation so that the viewer + // doesn't stop polling network or updating frame? FIXME. + while (gtk_events_pending()) + { + gtk_main_iteration(); + } - // Handle all outstanding SDL events - while (SDL_PollEvent(&event)) +// setlocale(LC_ALL, saved_locale.c_str() ); + + // This is a good time to stop flashing the icon if our mFlashTimer has + // expired. + if (mFlashing && mFlashTimer.hasExpired()) { - switch (event.type) - { - case SDL_MOUSEMOTION: - { - LLCoordWindow winCoord(event.button.x, event.button.y); - LLCoordGL openGlCoord; - convertCoords(winCoord, &openGlCoord); - MASK mask = gKeyboard->currentMask(TRUE); - mCallbacks->handleMouseMove(this, openGlCoord, mask); - break; - } + gtk_window_set_urgency_hint(GTK_WINDOW(mWindow), FALSE); + mFlashing = FALSE; + } +} - case SDL_KEYDOWN: - gKeyboard->handleKeyDown(event.key.keysym.sym, event.key.keysym.mod); - // part of the fix for SL-13243 - if (SDLCheckGrabbyKeys(event.key.keysym.sym, TRUE) != 0) - SDLReallyCaptureInput(TRUE); +gboolean LLWindowSDL::handleGtkMotionNotifyEvent(GdkEventMotion *event) +{ + const LLCoordWindow window_coord((S32) event->x, (S32) event->y); + LLCoordGL gl_coord; + convertCoords(window_coord, &gl_coord); + const MASK mask = gKeyboard->currentMask(TRUE); + mCallbacks->handleMouseMove(this, gl_coord, mask); + return TRUE; +} - if (event.key.keysym.unicode) - { - handleUnicodeUTF16(event.key.keysym.unicode, - gKeyboard->currentMask(FALSE)); - } - break; +gboolean LLWindowSDL::handleGtkKeyPressEvent(GdkEventKey *event) +{ + // *HACK: To make immodule's _reconversion_ feature to work under + // X11 environment, we need to pass user's text selection to + // PRIMARY clipboard on some timing before the user presses + // RECONVERT key. The natual manner may be to let UI components + // (LLLineEditor and LLTextEditor, or LLPreeditor from our + // standing point of view) notify us (LLWindow from their standing + // point of view) when a selection is made or changed. Another + // benefits from the design might be SL viewer being a better + // GNOME citizen. However, doing so requires some big changes on + // LLUI components, and it is only needed on X11. I don't think + // it is a good idea to do so. The following code is an + // alternative. Although I have just wrote that we need to pass + // the selection to clioboard *before* the user presses RECONVERT + // key, it is technically inaccurate. The accurate statement is: + // the selection needs to be passed before the immodule processes + // a key press event for RECONVERT key. So, we tries to detect + // the timing *between* the user presses the RECONVERT key and the + // immodule processes it. Let's see what happens. Also note + // that, although in principle we should always clear the + // clipboard when our preeditor has no text selection on it, doing + // so requires additional communication to X server, causing more + // type-to-echo delay. We use a flag, + // mPrimaryClipboardNeedsReset, to avoid it. + if (mPreeditor) + { + S32 position, length; + mPreeditor->getSelectionRange(&position, &length); + if (length > 0) + { + // We have a selection. Pass it to the PRIMARY clipboard + // so that the immodule can examine it for reconversion. + LLWString selection(mPreeditor->getWText(), position, length); + std::string selection_utf8 = wstring_to_utf8str(selection); + GtkClipboard * const clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY); + gtk_clipboard_set_text(clipboard, selection_utf8.c_str(), selection_utf8.length()); + mPrimaryClipboardNeedsReset = TRUE; + } + else if (mPrimaryClipboardNeedsReset) + { + GtkClipboard * const clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY); + gtk_clipboard_set_text(clipboard, "", 0); // or gtk_clipboard_clear? + mPrimaryClipboardNeedsReset = FALSE; + } + } - case SDL_KEYUP: - if (SDLCheckGrabbyKeys(event.key.keysym.sym, FALSE) == 0) - SDLReallyCaptureInput(FALSE); // part of the fix for SL-13243 + // Pass the event to the GtkIMContext for processing and + // filtering, if and only if language text input is allowed. Note + // that this switching technique is only effective with back end + // types of input methods, and it doesn't work with Windows IMM or + // MacOS TSM based immodules. + if (mPreeditor) + { + if (gtk_im_context_filter_keypress(mIMContext, event)) + { + return TRUE; + } + } - gKeyboard->handleKeyUp(event.key.keysym.sym, event.key.keysym.mod); - break; + // Process as a raw key down event, if not filtered. + gKeyboard->handleKeyDown(event->keyval, event->state); + // Part of the fix for SL-13243. + if (checkGrabbyKeys(event->keyval, TRUE) != 0) + { + reallyCaptureInput(TRUE); + } - case SDL_MOUSEBUTTONDOWN: - { - bool isDoubleClick = false; - LLCoordWindow winCoord(event.button.x, event.button.y); - LLCoordGL openGlCoord; - convertCoords(winCoord, &openGlCoord); - MASK mask = gKeyboard->currentMask(TRUE); + // Take care of unicode from an unfiltered key stroke. + const guint32 unicode = gdk_keyval_to_unicode(event->keyval); + if (unicode) + { + const MASK mask = gKeyboard->currentMask(FALSE); + if ((mask & (MASK_CONTROL | MASK_ALT)) == 0) + { + // gdk_keyval_to_unicode() doesn't consider Ctrl/Alt keys. + // If we produce any "unicode" for those key strokes with + // ctrl/alt, the viewer application will be confused. + // gdk_keyval_to_unicode() *does* consider Shift keys, so + // we don't need to take cre of it. + mCallbacks->handleUnicodeChar((llwchar)unicode, mask); + } + } + else if ((GDK_Return == event->keyval || GDK_KP_Enter == event->keyval) + && 0 == (event->state & ~GDK_SHIFT_MASK & ~GDK_LOCK_MASK)) + { + // This is a _counter_hack_ against a hack in + // LLViewerWindow::handleTranslatedKeyDown(KEY,MASK,BOOL) and + // LLViewerWindow::handleUnicodeChar(llwchar,MASK). The hack + // presumes that the RETURN and ENTER keys generate Unicode + // U+000D ('\r') when pressed alone, but the + // gdk_keyval_to_unicode() doesn't. We need to supply one by + // ourselves. We allow SHIFT and CAPS LOCK here, because a + // user should have intended to type ENTER when he/she hit + // ENTER key with one of them. + mCallbacks->handleUnicodeChar((llwchar)13, MASK_NONE); + } - if (event.button.button == SDL_BUTTON_LEFT) // SDL doesn't manage double clicking... - { - Uint32 now = SDL_GetTicks(); - if ((now - lastLeftDown) > CLICK_THRESHOLD) - leftClick = 1; - else - { - if (++leftClick >= 2) - { - leftClick = 0; - isDoubleClick = true; - } - } - lastLeftDown = now; - } - else if (event.button.button == SDL_BUTTON_RIGHT) - { - Uint32 now = SDL_GetTicks(); - if ((now - lastRightDown) > CLICK_THRESHOLD) - rightClick = 1; - else - { - if (++rightClick >= 2) - { - rightClick = 0; - isDoubleClick = true; - } - } - lastRightDown = now; - } + return TRUE; +} - if (event.button.button == SDL_BUTTON_LEFT) // left - { - if (isDoubleClick) - mCallbacks->handleDoubleClick(this, openGlCoord, mask); - else - mCallbacks->handleMouseDown(this, openGlCoord, mask); - } +gboolean LLWindowSDL::handleGtkKeyReleaseEvent(GdkEventKey *event) +{ + // We need to pass key release events to + // gtk_im_context_filter_keypress() irrespective of its name. + if (mPreeditor) + { + if (gtk_im_context_filter_keypress(mIMContext, event)) + { + return TRUE; + } + } - else if (event.button.button == SDL_BUTTON_RIGHT) // right ... yes, it's 3, not 2, in SDL... - { - // right double click isn't handled right now in Second Life ... if (isDoubleClick) - mCallbacks->handleRightMouseDown(this, openGlCoord, mask); - } + if (checkGrabbyKeys(event->keyval, FALSE) == 0) + { + reallyCaptureInput(FALSE); + } - else if (event.button.button == SDL_BUTTON_MIDDLE) // middle - { - mCallbacks->handleMiddleMouseDown(this, openGlCoord, mask); - } - else if (event.button.button == 4) // mousewheel up...thanks to X11 for making SDL consider these "buttons". - mCallbacks->handleScrollWheel(this, -1); - else if (event.button.button == 5) // mousewheel down...thanks to X11 for making SDL consider these "buttons". - mCallbacks->handleScrollWheel(this, 1); + // This is a testing hack to pop up a dialog when 4 is released + // if (event->keyval == '4') + //OSMessageBox("a whole bunch of text goes right here, whee! test test test.", "this is the title!", OSMB_YESNO); - break; - } + gKeyboard->handleKeyUp(event->keyval, event->state); - case SDL_MOUSEBUTTONUP: - { - LLCoordWindow winCoord(event.button.x, event.button.y); - LLCoordGL openGlCoord; - convertCoords(winCoord, &openGlCoord); - MASK mask = gKeyboard->currentMask(TRUE); + return TRUE; +} - if (event.button.button == SDL_BUTTON_LEFT) // left - mCallbacks->handleMouseUp(this, openGlCoord, mask); - else if (event.button.button == SDL_BUTTON_RIGHT) // right ... yes, it's 3, not 2, in SDL... - mCallbacks->handleRightMouseUp(this, openGlCoord, mask); - else if (event.button.button == SDL_BUTTON_MIDDLE) // middle - { - mCallbacks->handleMiddleMouseUp(this, openGlCoord, mask); - } - // don't handle mousewheel here... +gboolean LLWindowSDL::handleGtkButtonPressEvent(GdkEventButton *event) +{ + // LLUI framework recognizes double clicks only on the left (1st) + // button. Those on other buttons are handled as two consequtive + // single-clicks. I have a feeling it's wrong; double clicks on + // other buttons should be handled as *one* single-click, IMHO. + // For now, however, we follow the way double clicks are handled + // on other platforms (i.e., Windows/MacOS.) Gtk recognizes + // triple clicks but LLUI doesn't. I believe a tripple should be + // considered as a double click by ignoring the third click. For + // now, however again, we follow the way it is handled on other + // platforms. - break; - } + // A case we *can't* simulate is *four* rapid left clicks. On + // Windows, it is recognized as two double clicks. However, it is + // not practical to handle the case similarly under Gtk. Can it + // make any problem? I don't think so... - case SDL_VIDEOEXPOSE: // VIDEOEXPOSE doesn't specify the damage, but hey, it's OpenGL...repaint the whole thing! - mCallbacks->handlePaint(this, 0, 0, mWindow->w, mWindow->h); - break; + // The cases for double/triple-clicks are handled on the + // GDK_BUTTON_PRESS event. We can just ignore GDK_2BUTTON_PRESS + // and GDK_3BUTTON_PRESS events. + if (event->type == GDK_2BUTTON_PRESS || event->type == GDK_3BUTTON_PRESS) + { + return TRUE; + } - case SDL_VIDEORESIZE: // *FIX: handle this? - llinfos << "Handling a resize event: " << event.resize.w << - "x" << event.resize.h << llendl; + // Determine the context. + const LLCoordWindow window_coord((S32) event->x, (S32) event->y); + LLCoordGL gl_coord; + convertCoords(window_coord, &gl_coord); + MASK mask = gKeyboard->currentMask(TRUE); - // *FIX: I'm not sure this is necessary! - mWindow = SDL_SetVideoMode(event.resize.w, event.resize.h, 32, mSDLFlags); - if (!mWindow) + // Flush the remaining language text input if any. + interruptLanguageTextInput(); + + switch (event->button) + { + case 1: // The left button { - // *FIX: More informative dialog? - llinfos << "Could not recreate context after resize! Quitting..." << llendl; - if(mCallbacks->handleCloseRequest(this)) - { - // Get the app to initiate cleanup. - mCallbacks->handleQuit(this); - // The app is responsible for calling destroyWindow when done with GL - } - break; - } - - mCallbacks->handleResize(this, event.resize.w, event.resize.h ); - break; - - case SDL_ACTIVEEVENT: - if (event.active.state & SDL_APPINPUTFOCUS) - { - // Note that for SDL (particularly on X11), keyboard - // and mouse focus are independent things. Here we are - // tracking keyboard focus state changes. - - // We have to do our own state massaging because SDL - // can send us two unfocus events in a row for example, - // which confuses the focus code [SL-24071]. - if (event.active.gain != mHaveInputFocus) + // See if this is a second click of a double-clicking. + BOOL double_click = FALSE; + GdkEvent *next_event = gdk_event_peek(); + if (next_event) { - if (event.active.gain) - mCallbacks->handleFocus(this); - else - mCallbacks->handleFocusLost(this); - - mHaveInputFocus = !!event.active.gain; + double_click = (next_event->type == GDK_2BUTTON_PRESS); + gdk_event_free(next_event); } - } - if (event.active.state & SDL_APPACTIVE) - { - // Change in iconification/minimization state. - if ((!event.active.gain) != mIsMinimized) + if (double_click) { - mCallbacks->handleActivate(this, !!event.active.gain); - llinfos << "SDL deiconification state switched to " << BOOL(event.active.gain) << llendl; - - mIsMinimized = (!event.active.gain); + mCallbacks->handleDoubleClick(this, gl_coord, mask); } else { - llinfos << "Ignored bogus redundant SDL deiconification state switch to " << BOOL(event.active.gain) << llendl; + mCallbacks->handleMouseDown(this, gl_coord, mask); } - } - break; + } + break; - case SDL_QUIT: - if(mCallbacks->handleCloseRequest(this)) - { - // Get the app to initiate cleanup. - mCallbacks->handleQuit(this); - // The app is responsible for calling destroyWindow when done with GL - } - break; - default: - //llinfos << "Unhandled SDL event type " << event.type << llendl; + case 2: // The middle button + mCallbacks->handleMiddleMouseDown(this, gl_coord, mask); break; - } - } -#if LL_X11 - // This is a good time to stop flashing the icon if our mFlashTimer has - // expired. - if (mFlashing && mFlashTimer.hasExpired()) - { - x11_set_urgent(FALSE); - mFlashing = FALSE; - } -#endif // LL_X11 + case 3: // The right button + mCallbacks->handleRightMouseDown(this, gl_coord, mask); + break; + + default: + llinfos << "Ignoring mouse button #" << event->button << " down" << llendl; + break; + } + + return TRUE; } -static SDL_Cursor *makeSDLCursorFromBMP(const char *filename, int hotx, int hoty) +gboolean LLWindowSDL::handleGtkButtonReleaseEvent(GdkEventButton *event) { - SDL_Cursor *sdlcursor = NULL; - SDL_Surface *bmpsurface; + const LLCoordWindow window_coord((S32) event->x, (S32) event->y); + LLCoordGL gl_coord; + convertCoords(window_coord, &gl_coord); + const MASK mask = gKeyboard->currentMask(TRUE); - // Load cursor pixel data from BMP file - bmpsurface = Load_BMP_Resource(filename); - if (bmpsurface && bmpsurface->w%8==0) + switch (event->button) { - SDL_Surface *cursurface; - lldebugs << "Loaded cursor file " << filename << " " - << bmpsurface->w << "x" << bmpsurface->h << llendl; - cursurface = SDL_CreateRGBSurface (SDL_SWSURFACE, - bmpsurface->w, - bmpsurface->h, - 32, - SDL_SwapLE32(0xFFU), - SDL_SwapLE32(0xFF00U), - SDL_SwapLE32(0xFF0000U), - SDL_SwapLE32(0xFF000000U)); - SDL_FillRect(cursurface, NULL, SDL_SwapLE32(0x00000000U)); + case 1: // The left button + mCallbacks->handleMouseUp(this, gl_coord, mask); + break; - // Blit the cursor pixel data onto a 32-bit RGBA surface so we - // only have to cope with processing one type of pixel format. - if (0 == SDL_BlitSurface(bmpsurface, NULL, - cursurface, NULL)) - { - // n.b. we already checked that width is a multiple of 8. - const int bitmap_bytes = (cursurface->w * cursurface->h) / 8; - unsigned char *cursor_data = new unsigned char[bitmap_bytes]; - unsigned char *cursor_mask = new unsigned char[bitmap_bytes]; - memset(cursor_data, 0, bitmap_bytes); - memset(cursor_mask, 0, bitmap_bytes); - int i,j; - // Walk the RGBA cursor pixel data, extracting both data and - // mask to build SDL-friendly cursor bitmaps from. The mask - // is inferred by color-keying against 200,200,200 - for (i=0; ih; ++i) { - for (j=0; jw; ++j) { - U8 *pixelp = - ((U8*)cursurface->pixels) - + cursurface->pitch * i - + j*cursurface->format->BytesPerPixel; - U8 srcred = pixelp[0]; - U8 srcgreen = pixelp[1]; - U8 srcblue = pixelp[2]; - BOOL mask_bit = (srcred != 200) - || (srcgreen != 200) - || (srcblue != 200); - BOOL data_bit = mask_bit && (srcgreen <= 80);//not 0x80 - unsigned char bit_offset = (cursurface->w/8) * i - + j/8; - cursor_data[bit_offset] |= (data_bit) << (7 - (j&7)); - cursor_mask[bit_offset] |= (mask_bit) << (7 - (j&7)); - } - } - sdlcursor = SDL_CreateCursor((Uint8*)cursor_data, - (Uint8*)cursor_mask, - cursurface->w, cursurface->h, - hotx, hoty); - delete[] cursor_data; - delete[] cursor_mask; - } else { - llwarns << "CURSOR BLIT FAILURE, cursurface: " << cursurface << llendl; - } - SDL_FreeSurface(cursurface); - SDL_FreeSurface(bmpsurface); - } else { - llwarns << "CURSOR LOAD FAILURE " << filename << llendl; + case 2: // The middle button + mCallbacks->handleMiddleMouseUp(this, gl_coord, mask); + break; + + case 3: // The right button + mCallbacks->handleRightMouseUp(this, gl_coord, mask); + break; + + default: + llinfos << "Ignoring mouse button #" << event->button << " up" << llendl; + break; } + return TRUE; +} + +gboolean LLWindowSDL::handleGtkScrollEvent(GdkEventScroll *event) +{ + switch (event->direction) + { + case GDK_SCROLL_UP: + mCallbacks->handleScrollWheel(this, -1); + break; + case GDK_SCROLL_DOWN: + mCallbacks->handleScrollWheel(this, 1); + break; + default: + llinfos << "Ignoring scroll wheel #" << event->direction << llendl; + break; + } + return TRUE; +} - return sdlcursor; +gboolean LLWindowSDL::handleGtkFocusInEvent(GdkEventFocus *event) +{ + if (!mWindowIsFocused) + { + mCallbacks->handleFocus(this); + mWindowIsFocused = TRUE; + mPrimaryClipboardNeedsReset = TRUE; + updateIMFocus(); + } + return FALSE; } -void LLWindowSDL::setCursor(ECursorType cursor) +gboolean LLWindowSDL::handleGtkFocusOutEvent(GdkEventFocus *event) { - if (mCurrentCursor != cursor) + // SDL code included the following comment here. + // I believe GTK is more stable on focus in/out matching... + // + // // We have to do our own state massaging because SDL + // // can send us two unfocus events in a row for example, + // // which confuses the focus code [SL-24071]. + + if (mWindowIsFocused) { - if (cursor < UI_CURSOR_COUNT) - { - SDL_Cursor *sdlcursor = mSDLCursors[cursor]; - // Try to default to the arrow for any cursors that - // did not load correctly. - if (!sdlcursor && mSDLCursors[UI_CURSOR_ARROW]) - sdlcursor = mSDLCursors[UI_CURSOR_ARROW]; - if (sdlcursor) - SDL_SetCursor(sdlcursor); - } else { - llwarns << "Tried to set invalid cursor number " << cursor << llendl; - } - mCurrentCursor = cursor; + interruptLanguageTextInput(); + mWindowIsFocused = FALSE; + updateIMFocus(); + mCallbacks->handleFocusLost(this); } + return FALSE; } +gboolean LLWindowSDL::handleGtkExposeEvent(GdkEventExpose *event) +{ + mCallbacks->handlePaint(this, event->area.x, event->area.y, event->area.width, event->area.height); + return FALSE; +} + +gboolean LLWindowSDL::handleGtkConfigureEvent(GdkEventConfigure *event) +{ + mCallbacks->handleResize(this, event->width, event->height); + return FALSE; +} + +gboolean LLWindowSDL::handleGtkWindowStateEvent(GdkEventWindowState *event) +{ + if (event->changed_mask & GDK_WINDOW_STATE_ICONIFIED) + { + const BOOL active = ((event->new_window_state & GDK_WINDOW_STATE_ICONIFIED) == 0); + mCallbacks->handleActivate(this, active); + } + return FALSE; +} + +gboolean LLWindowSDL::handleGtkDeleteEvent(GdkEvent *event) +{ + if(mCallbacks->handleCloseRequest(this)) + { + // Get the app to initiate cleanup. + mCallbacks->handleQuit(this); + // The app is responsible for calling destroyWindow when done with GL + } + return TRUE; +} + +// Load a BMP file and build a cursor (mouse pointer figure) from it. +// The BMP file may be greyscale, paletted, or truecolor, but in any +// case it should not have alpha channel. It is keyed with +// <200,200,200> grey, i.e., pixels with that color is converted into +// 100% transparent pixel. This specification is simply inherited +// from the SDL version. I believe it's far easier to use PNG with +// alpha in GTK environment. + +static GdkCursor * make_gdk_cursor_from_bmp(GdkDisplay * display, const char *filename, int x, int y) +{ + const guchar key_r = 200; + const guchar key_g = 200; + const guchar key_b = 200; + + GdkPixbuf * raw_pixbuf = load_bmp_resource(filename, "cursor"); + if (!raw_pixbuf) + { + return NULL; + } + + GdkPixbuf * pixbuf = gdk_pixbuf_add_alpha(raw_pixbuf, TRUE, key_r, key_g, key_b); + if (!pixbuf) + { + g_object_unref(raw_pixbuf); + return NULL; + } + + GdkCursor * cursor = gdk_cursor_new_from_pixbuf(display, pixbuf, x, y); + g_object_unref(pixbuf); + return cursor; +} + +#if 0 + +// This is an alternative to make_gdk_cursor_from_bmp() above. Unlike +// SDL, gdk-pixbuf provides reading of Windows .CUR files, and we +// don't need to struggle with bmp files with hard-coded hot spot +// coordinates or with manual keying (for transparent backgrounds). +// The problem is, switching from BMP based cursor to CUR based cursor +// requires re-layout of the resource files in the source tree +// largely, as well as updates to the build/package scripts. It is +// nearly impossible for an open source developer outside LL to do, +// until LL switches to more open development process... + +static GdkCursor * make_gdk_cursor(GdkDisplay * display, const char *filename) +{ + GdkPixbuf * pixbuf = load_bmp_resource(filename, "cursor"); + if (!pixbuf) + { + return NULL; + } + + // gdk pixbuf supports loading of Windows .CUR files, and sets the + // hot spot coordinates in "x_hot" and "y_hot" optioins, but gdk + // cursor package does not recognize these options in gdk + // pixbuf... I'm not sure why. We need to take care of hot spot + // by ourselves. + + const gchar * const x_hot_ptr = gdk_pixbuf_get_option(pixbuf, "x_hot"); + { + llwarns << "Cursor file " << filename << " has no x_hot" << llendl; + return NULL; + } + const gint64 x_hot = g_ascii_strtoll(x_hot_ptr, NULL, 10); + if (x_host < 0 || x_hot > gdk_pixbuf_get_width(pixbuf)) + { + llwarns << "Cursor x_host out of range in " << filename << llendl; + return NULL; + } + + const gchar * const y_hot_ptr = gdk_pixbuf_get_option(pixbuf, "y_hot"); + { + llwarns << "Cursor file " << filename << " has no y_hot" << llendl; + return NULL; + } + const gint64 y_hot = g_ascii_strtoll(y_hot_ptr, NULL, 10); + if (y_host < 0 || y_hot > gdk_pixbuf_get_width(pixbuf)) + { + llwarns << "Cursor y_host out of range in " << filename << llendl; + return NULL; + } + + GdkCursor * cursor = gdk_cursor_new_from_pixbuf(display, pixbuf, (gint) x_hot, (gint) y_hot); + g_object_unref(pixbuf); + return cursor; +} + +#endif + +void LLWindowSDL::setCursor(ECursorType cursor_type) +{ + if (mCurrentCursor == cursor_type) + { + return; + } + + if (cursor_type >= UI_CURSOR_COUNT) + { + llwarns << "Tried to set invalid cursor number " << cursor_type << llendl; + cursor_type = UI_CURSOR_ARROW; + } + mCurrentCursor = cursor_type; + + if (!mCursorHidden) + { + gdk_window_set_cursor(mWindow->window, mCursors[cursor_type]); + } +} + ECursorType LLWindowSDL::getCursor() { return mCurrentCursor; @@ -2315,124 +2235,134 @@ void LLWindowSDL::initCursors() { - int i; // Blank the cursor pointer array for those we may miss. - for (i=0; iwindow, mHiddenCursor); } - else - { - // llinfos << "hideCursor: already hidden" << llendl; - } - - adjustCursorDecouple(); } void LLWindowSDL::showCursor() { if(mCursorHidden) { - // llinfos << "showCursor: showing" << llendl; mCursorHidden = FALSE; mHideCursorPermanent = FALSE; - SDL_ShowCursor(1); + gdk_window_set_cursor(mWindow->window, mCursors[mCurrentCursor]); } - else - { - // llinfos << "showCursor: already visible" << llendl; - } - - adjustCursorDecouple(); } void LLWindowSDL::showCursorFromMouseMove() @@ -2448,12 +2378,11 @@ if (!mHideCursorPermanent) { hideCursor(); + // hideCursor turns mHideCursorPermanent TRUE! mHideCursorPermanent = FALSE; } } - - // // LLSplashScreenSDL - I don't think we'll bother to implement this; it's // fairly obsolete at this point. @@ -2478,9 +2407,6 @@ { } - - -#if LL_GTK static void response_callback (GtkDialog *dialog, gint arg1, gpointer user_data) @@ -2497,23 +2423,13 @@ ll_try_gtk_init(); - if(gWindowImplementation != NULL) - gWindowImplementation->beforeDialog(); - - if (ll_try_gtk_init() - // We can NOT expect to combine GTK and SDL's aggressive fullscreen - && ((NULL==gWindowImplementation) || (!was_fullscreen)) - ) + GtkWidget *win = NULL; + GtkWindow * parent = gWindowImplementation ? GTK_WINDOW(gWindowImplementation->getGtkWindow()) : NULL; + GtkDialogFlags flags = GTK_DIALOG_MODAL; + GtkMessageType messagetype; + GtkButtonsType buttons; + switch (type) { - GtkWidget *win = NULL; - - llinfos << "Creating a dialog because we're in windowed mode and GTK is happy." << llendl; - - GtkDialogFlags flags = GTK_DIALOG_MODAL; - GtkMessageType messagetype; - GtkButtonsType buttons; - switch (type) - { default: case OSMB_OK: messagetype = GTK_MESSAGE_WARNING; @@ -2527,49 +2443,29 @@ messagetype = GTK_MESSAGE_QUESTION; buttons = GTK_BUTTONS_YES_NO; break; - } - win = gtk_message_dialog_new(NULL, - flags, messagetype, buttons, - text); + } + win = gtk_message_dialog_new(parent, flags, messagetype, buttons, text); -# if LL_X11 - // Make GTK tell the window manager to associate this - // dialog with our non-GTK SDL window, which should try - // to keep it on top etc. - if (gWindowImplementation && - gWindowImplementation->mSDL_XWindowID != None) - { - gtk_widget_realize(GTK_WIDGET(win)); // so we can get its gdkwin - GdkWindow *gdkwin = gdk_window_foreign_new(gWindowImplementation->mSDL_XWindowID); - gdk_window_set_transient_for(GTK_WIDGET(win)->window, - gdkwin); - } -# endif //LL_X11 + gtk_window_set_position(GTK_WINDOW(win), GTK_WIN_POS_CENTER_ON_PARENT); - gtk_window_set_position(GTK_WINDOW(win), - GTK_WIN_POS_CENTER_ON_PARENT); + gtk_window_set_type_hint(GTK_WINDOW(win), GDK_WINDOW_TYPE_HINT_DIALOG); - gtk_window_set_type_hint(GTK_WINDOW(win), - GDK_WINDOW_TYPE_HINT_DIALOG); + if (caption) + { + gtk_window_set_title(GTK_WINDOW(win), caption); + } - if (caption) - gtk_window_set_title(GTK_WINDOW(win), caption); + gint response = GTK_RESPONSE_NONE; + g_signal_connect(win, "response", G_CALLBACK(response_callback), &response); - gint response = GTK_RESPONSE_NONE; - g_signal_connect (win, - "response", - G_CALLBACK (response_callback), - &response); + // we should be able to use a gtk_dialog_run(), but it's + // apparently not written to exist in a world without a higher + // gtk_main(), so we manage its signal/destruction outselves. + gtk_widget_show_all (win); + gtk_main(); - // we should be able to use a gtk_dialog_run(), but it's - // apparently not written to exist in a world without a higher - // gtk_main(), so we manage its signal/destruction outselves. - gtk_widget_show_all (win); - gtk_main(); - - //llinfos << "response: " << response << llendl; - switch (response) - { + switch (response) + { case GTK_RESPONSE_OK: rtn = OSBTN_OK; break; case GTK_RESPONSE_YES: rtn = OSBTN_YES; break; case GTK_RESPONSE_NO: rtn = OSBTN_NO; break; @@ -2579,18 +2475,8 @@ case GTK_RESPONSE_CLOSE: case GTK_RESPONSE_DELETE_EVENT: default: rtn = OSBTN_CANCEL; - } } - else - { - llinfos << "MSGBOX: " << caption << ": " << text << llendl; - llinfos << "Skipping dialog because we're in fullscreen mode or GTK is not happy." << llendl; - rtn = OSBTN_OK; - } - if(gWindowImplementation != NULL) - gWindowImplementation->afterDialog(); - return rtn; } @@ -2603,179 +2489,454 @@ gtk_color_selection_get_current_color(colorsel, colorp); } -BOOL LLWindowSDL::dialog_color_picker ( F32 *r, F32 *g, F32 *b) +BOOL LLWindowSDL::dialog_color_picker(F32 *r, F32 *g, F32 *b) { BOOL rtn = FALSE; beforeDialog(); - if (ll_try_gtk_init() - // We can NOT expect to combine GTK and SDL's aggressive fullscreen - && !was_fullscreen - ) + GtkWidget *win = gtk_color_selection_dialog_new(NULL); + gtk_window_set_transient_for(GTK_WINDOW(win), GTK_WINDOW(getGtkWindow())); + + GtkColorSelection *colorsel = GTK_COLOR_SELECTION (GTK_COLOR_SELECTION_DIALOG(win)->colorsel); + + GdkColor color, orig_color; + orig_color.red = guint16(65535 * *r); + orig_color.green= guint16(65535 * *g); + orig_color.blue = guint16(65535 * *b); + color = orig_color; + + gtk_color_selection_set_previous_color(colorsel, &color); + gtk_color_selection_set_current_color(colorsel, &color); + gtk_color_selection_set_has_palette(colorsel, TRUE); + gtk_color_selection_set_has_opacity_control(colorsel, FALSE); + + gint response = GTK_RESPONSE_NONE; + g_signal_connect(win, "response", G_CALLBACK(response_callback), &response); + g_signal_connect (G_OBJECT (colorsel), "color_changed", G_CALLBACK(color_changed_callback), &color); + + gtk_window_set_modal(GTK_WINDOW(win), TRUE); + gtk_widget_show_all(win); + // hide the help button - we don't service it. + gtk_widget_hide(GTK_COLOR_SELECTION_DIALOG(win)->help_button); + gtk_main(); + + if (response == GTK_RESPONSE_OK && + (orig_color.red != color.red + || orig_color.green != color.green + || orig_color.blue != color.blue) ) { - GtkWidget *win = NULL; + *r = color.red / 65535.0f; + *g = color.green / 65535.0f; + *b = color.blue / 65535.0f; + rtn = TRUE; + } - win = gtk_color_selection_dialog_new(NULL); + afterDialog(); -# if LL_X11 - // Get GTK to tell the window manager to associate this - // dialog with our non-GTK SDL window, which should try - // to keep it on top etc. - if (mSDL_XWindowID != None) - { - gtk_widget_realize(GTK_WIDGET(win)); // so we can get its gdkwin - GdkWindow *gdkwin = gdk_window_foreign_new(mSDL_XWindowID); - gdk_window_set_transient_for(GTK_WIDGET(win)->window, - gdkwin); - } -# endif //LL_X11 + return rtn; +} - GtkColorSelection *colorsel = GTK_COLOR_SELECTION (GTK_COLOR_SELECTION_DIALOG(win)->colorsel); +// Open a URL with the user's default web browser. +// Must begin with protocol identifier. +void spawn_web_browser(const char* escaped_url) +{ + llinfos << "spawn_web_browser: " << escaped_url << llendl; + + // Just in case - before forking. + gdk_flush(); - GdkColor color, orig_color; - orig_color.red = guint16(65535 * *r); - orig_color.green= guint16(65535 * *g); - orig_color.blue = guint16(65535 * *b); - color = orig_color; + const std::string cmd + = gDirUtilp->getAppRODataDir() + + gDirUtilp->getDirDelimiter() + + "launch_url.sh"; + gchar * argv[3]; + argv[0] = const_cast(cmd.c_str()); + argv[1] = const_cast(escaped_url); + argv[2] = NULL; - gtk_color_selection_set_previous_color (colorsel, &color); - gtk_color_selection_set_current_color (colorsel, &color); - gtk_color_selection_set_has_palette (colorsel, TRUE); - gtk_color_selection_set_has_opacity_control(colorsel, FALSE); + GError * error = NULL; + gboolean success = g_spawn_async(NULL, argv, NULL, (GSpawnFlags)0, NULL, NULL, NULL, &error); + if (!success) + { + llwarns << "failure on spawning web browser: " << error->message << llendl; + g_error_free(error); + } +} - gint response = GTK_RESPONSE_NONE; - g_signal_connect (win, - "response", - G_CALLBACK (response_callback), - &response); +void shell_open( const char* file_path ) +{ + // *TODO: This function is deprecated and should probably go away. + llwarns << "Deprecated shell_open(): " << file_path << llendl; +} - g_signal_connect (G_OBJECT (colorsel), "color_changed", - G_CALLBACK (color_changed_callback), - &color); +void *LLWindowSDL::getPlatformWindow() +{ + // Unlike SDL version, our main window (mWindow) is now a genuine + // GTK window. However, the _platform_window_ that this method + // returns is used as a container for a hidden mozilla GtkWidget, + // and mWindow has no more space for another child... We need to + // create another container for the purpose. The window we + // creates here will *never* be destroyed, as in SDL version and + // MacOS version. - gtk_window_set_modal(GTK_WINDOW(win), TRUE); - gtk_widget_show_all(win); - // hide the help button - we don't service it. - gtk_widget_hide(GTK_COLOR_SELECTION_DIALOG(win)->help_button); - gtk_main(); + GtkWidget *win = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_decorated(GTK_WINDOW(win), FALSE); + gtk_widget_realize(win); + return win; +} - if (response == GTK_RESPONSE_OK && - (orig_color.red != color.red - || orig_color.green != color.green - || orig_color.blue != color.blue) ) - { - *r = color.red / 65535.0f; - *g = color.green / 65535.0f; - *b = color.blue / 65535.0f; - rtn = TRUE; - } +void LLWindowSDL::bringToFront() +{ + gtk_window_present(GTK_WINDOW(mWindow)); +} + +// Handling of input methods through GTK im module. + +void LLWindowSDL::allowLanguageTextInput(LLPreeditor *preeditor, BOOL b) +{ + if (preeditor == NULL && b) + { + llwarns << "allowLanguageTextInput(NULL, TRUE) ... This call is prohibited!" << llendl; + return; } - afterDialog(); + if (preeditor != mPreeditor && !b) + { + return; + } - return rtn; + // Take care of old and new preeditors. + if (preeditor != mPreeditor || !b) + { + // Needs to interrupt before updating mPreeditor so that any + // forced commit occures on the old preeditor. + interruptLanguageTextInput(); + mPreeditor = (b ? preeditor : NULL); + mPrimaryClipboardNeedsReset = TRUE; + updateIMFocus(); + } } -#else -S32 OSMessageBoxSDL(const char* text, const char* caption, U32 type) + +// Reset the IM context and discard any remnant preedits both from UI +// (preeditor) and from our own internal storage. + +void LLWindowSDL::interruptLanguageTextInput() { - llinfos << "MSGBOX: " << caption << ": " << text << llendl; - return 0; + gtk_im_context_reset(mIMContext); + if (mPreeditText.length() > 0) + { + mPreeditText.clear(); + mPreeditSegmentLengths.clear(); + mPreeditStandouts.clear(); + mPreeditCaret = 0; + } + if (mPreeditor) + { + mPreeditor->resetPreedit(); + mPreeditor->updatePreedit(mPreeditText, mPreeditSegmentLengths, mPreeditStandouts, mPreeditCaret); + } } -BOOL LLWindowSDL::dialog_color_picker ( F32 *r, F32 *g, F32 *b) +void LLWindowSDL::updateLanguageTextInputArea() { - return (FALSE); + if (!mPreeditor) + { + return; + } + + LLCoordGL caret_coord_gl; + LLRect preedit_bounds_gl; + if (!mPreeditor->getPreeditLocation(-1, &caret_coord_gl, &preedit_bounds_gl, NULL)) + { + return; + } + + // We assume the cursor (caret) rect is 2 pixels wide and as high + // as the preedit bounds. This is to mimic the behaviour of + // GtkEntry widget where all immodule should support. + + const LLCoordGL cursor_top_left_gl(caret_coord_gl.mX, preedit_bounds_gl.mTop); + LLCoordWindow cursor_top_left_window; + convertCoords(cursor_top_left_gl, &cursor_top_left_window); + + const LLCoordGL cursor_bottom_right_gl(caret_coord_gl.mX, preedit_bounds_gl.mBottom); + LLCoordWindow cursor_bottom_right_window; + convertCoords(cursor_bottom_right_gl, &cursor_bottom_right_window); + + GdkRectangle rectangle; + rectangle.x = cursor_top_left_window.mX; + rectangle.y = cursor_top_left_window.mY; + rectangle.width = 2; + rectangle.height = cursor_bottom_right_window.mY - cursor_top_left_window.mY + 1; + + gtk_im_context_set_cursor_location(mIMContext, &rectangle); } -#endif // LL_GTK -// Open a URL with the user's default web browser. -// Must begin with protocol identifier. -void spawn_web_browser(const char* escaped_url) +void LLWindowSDL::handleGtkCommitSignal(const gchar *committed_text) { - llinfos << "spawn_web_browser: " << escaped_url << llendl; - -#if LL_LINUX || LL_SOLARIS -# if LL_X11 - if (gWindowImplementation && gWindowImplementation->mSDL_Display) + if (!committed_text || !*committed_text) { - maybe_lock_display(); - // Just in case - before forking. - XSync(gWindowImplementation->mSDL_Display, False); - maybe_unlock_display(); + return; } -# endif // LL_X11 - std::string cmd; - cmd = gDirUtilp->getAppRODataDir().c_str(); - cmd += gDirUtilp->getDirDelimiter().c_str(); - cmd += "launch_url.sh"; - char* const argv[] = {(char*)cmd.c_str(), (char*)escaped_url, NULL}; + if (mPreeditor) + { + // Pass the committed characters on-by-one to the preeditor, + // preserving the current preedit. + mPreeditor->resetPreedit(); + const LLWString text = utf8str_to_wstring(committed_text); + for (LLWString::const_iterator i = text.begin(); i != text.end(); i++) + { + mPreeditor->handleUnicodeCharHere(*i, FALSE); + } + mPreeditor->updatePreedit(mPreeditText, mPreeditSegmentLengths, mPreeditStandouts, mPreeditCaret); + updateLanguageTextInputArea(); + } + else + { + // Since we don't pass key press/release events to IM context + // without preeditor, it should never emit a commit signal. + // The following _fallback_code_ should never be executed. + llwarns << "commit signal with no preeditor." << llendl; - fflush(NULL); - pid_t pid = fork(); - if (pid == 0) - { // child - // disconnect from stdin/stdout/stderr, or child will - // keep our output pipe undesirably alive if it outlives us. - close(0); - close(1); - close(2); - // end ourself by running the command - execv(cmd.c_str(), argv); /* Flawfinder: ignore */ - // if execv returns at all, there was a problem. - llwarns << "execv failure when trying to start " << cmd << llendl; - _exit(1); // _exit because we don't want atexit() clean-up! - } else { - if (pid > 0) + // We have no active preeditor, so fallback to the default + // behaviour and pass the characters to the application + // callback. We only invoke handleUnicodeChar, since this is + // purely a _text_ event, and not related to key + // pressing/releasing as it is. + const MASK mask = gKeyboard->currentMask(FALSE); + const LLWString text = utf8str_to_wstring(committed_text); + for (LLWString::const_iterator i = text.begin(); i != text.end(); i++) { - // parent - wait for child to die - int childExitStatus; - waitpid(pid, &childExitStatus, 0); - } else { - llwarns << "fork failure." << llendl; + mCallbacks->handleUnicodeChar(*i, mask); } } -#endif // LL_LINUX || LL_SOLARIS +} - llinfos << "spawn_web_browser returning." << llendl; +void LLWindowSDL::handleGtkPreeditChangedSignal() +{ + // Extract preedit from IM. + gchar *raw_string = NULL; // in UTF-8 + PangoAttrList *attr_list = NULL; + gint cursor_pos = 0; // in _characters_ + gtk_im_context_get_preedit_string(mIMContext, &raw_string, &attr_list, &cursor_pos); + + // Turn the preedit into our own representation. + if (!raw_string || !*raw_string) + { + // Preedit has been flushed. + mPreeditText.clear(); + mPreeditSegmentLengths.clear(); + mPreeditStandouts.clear(); + mPreeditCaret = 0; + } + else + { + mPreeditText = utf8str_to_wstring(raw_string); + + // Map one Pango attribute item to one clause segment. + // Consider a range with a BACKGROUND attribute a standout. + // Yes, this is hacky, but it works reasonably well on *all* + // immodules that I know of without per-immodule switching. + // -- Alissa + + const PangoAttrType standout = PANGO_ATTR_BACKGROUND; + const S32 string_length = strlen(raw_string); + mPreeditSegmentLengths.clear(); + mPreeditStandouts.clear(); + gint last = 0; + PangoAttrIterator * itor = pango_attr_list_get_iterator(attr_list); + do + { + // It seems that some immodules may return a strange + // PangoAttrList, e.g., exceeding the preedit string + // length or having several _gaps_. We need to be careful + // to live symbiotically with them. + gint start, end; + pango_attr_iterator_range(itor, &start, &end); + if (start >= string_length) + { + break; + } + if (end > string_length) + { + end = string_length; + } + if (start > last) + { + mPreeditSegmentLengths.push_back(g_utf8_strlen(raw_string + last, start - last)); + mPreeditStandouts.push_back(FALSE); + } + if (end > start) + { + mPreeditSegmentLengths.push_back(g_utf8_strlen(raw_string + start, end - start)); + mPreeditStandouts.push_back(pango_attr_iterator_get(itor, standout) != NULL); + } + last = end; + } while (pango_attr_iterator_next(itor)); + pango_attr_iterator_destroy(itor); + if (last < string_length) + { + mPreeditSegmentLengths.push_back(g_utf8_strlen(raw_string + last, -1)); + mPreeditStandouts.push_back(FALSE); + } + + // cursor_pos is defined to be in _characters_ (as opposed to + // UTF-8 bytes), so we can just use it. Hope all immodule + // developers know what is *a* character in Unicode environments... + mPreeditCaret = cursor_pos; + } + + // Don't forget to release GTK resources. + if (raw_string) + { + g_free(raw_string); + } + if (attr_list) + { + pango_attr_list_unref(attr_list); + } + + // Show it on the preeditor. I don't think preedit-changed signal + // is emitted when we have no preeditor, because we don't pass key + // events to IM context in the case. The condition is just a + // safe guard. + if (mPreeditor) + { + mPreeditor->resetPreedit(); + mPreeditor->updatePreedit(mPreeditText, mPreeditSegmentLengths, mPreeditStandouts, mPreeditCaret); + } + else + { + llwarns << "preedit-changed signal with no preeditor." << llendl; + } + + // Text caret should have been moved, so tell the new location to IM. + updateLanguageTextInputArea(); } - -void *LLWindowSDL::getPlatformWindow() +void LLWindowSDL::handleGtkRetrieveSurroundingSignal() { -#if LL_GTK && LL_LIBXUL_ENABLED - if (ll_try_gtk_init()) + if (!mPreeditor) { - maybe_lock_display(); - GtkWidget *win = gtk_window_new(GTK_WINDOW_TOPLEVEL); + llwarns << "retrieve-srrounding signal with no preeditor." << llendl; + return; + } - // show the hidden-widget while debugging (needs mozlib change) - //gtk_widget_show_all(GTK_WIDGET(win)); + S32 preedit_start, preedit_length; + mPreeditor->getPreeditRange(&preedit_start, &preedit_length); + const S32 preedit_end = preedit_start + preedit_length; + const LLWString & text = mPreeditor->getWText(); - gtk_widget_realize(GTK_WIDGET(win)); - maybe_unlock_display(); - return win; + // Find a suitable _surrounding_context_. + S32 end = text.find((llwchar) '\n', preedit_end); + if (end == LLWString::npos) + { + end = text.length(); } -#endif // LL_GTK && LL_LIBXUL_ENABLED - // Unixoid mozilla really needs GTK. - return NULL; + S32 start = text.rfind((llwchar) '\n', preedit_start - 1); + if (start == LLWString::npos) + { + start = 0; + } + else + { + start++; + } + + // Combine the contexts before and after the preedit string. The + // context string to set should not contain uncommitted preedit + // text. We also need to convert the string into utf-8. + std::string context = wstring_to_utf8str(LLWString(text, start, preedit_start - start)); + const gint cursor_index = context.length(); + context += wstring_to_utf8str(LLWString(text, preedit_end, end - preedit_end)); + + gtk_im_context_set_surrounding(mIMContext, context.c_str(), (gint) context.length(), cursor_index); } -void LLWindowSDL::bringToFront() +gboolean LLWindowSDL::handleGtkDeleteSurroundingSignal(gint offset, gint n_chars) { - // This is currently used when we are 'launched' to a specific - // map position externally. - llinfos << "bringToFront" << llendl; -#if LL_X11 - if (mSDL_Display && !mFullscreen) + if (!mPreeditor) { - maybe_lock_display(); - XRaiseWindow(mSDL_Display, mSDL_XWindowID); - XSync(mSDL_Display, False); - maybe_unlock_display(); + llwarns << "delete-surrounding signal with no preeditor." << llendl; + return FALSE; } -#endif // LL_X11 + + // To be honest, I'me having a feeling that the LLPreeditor + // interface is biased toward Windows IMM API and not suitable for + // us. We needed some trick to implement this handler on top of + // it. It may be better to revise the LLPreditor methods. FIXME. + + mPreeditor->resetPreedit(); + S32 cursor, unused; + mPreeditor->getPreeditRange(&cursor, &unused); + const S32 from = llmax(0, cursor + offset); + const S32 to = llmin(cursor + offset + n_chars, (S32) mPreeditor->getWText().length()); + mPreeditor->markAsPreedit(from, to - from); + mPreeditor->resetPreedit(); + mPreeditor->updatePreedit(mPreeditText, mPreeditSegmentLengths, mPreeditStandouts, mPreeditCaret); + updateLanguageTextInputArea(); + + return TRUE; } -#endif // LL_SDL +// More thunks for IM context signal handlers. + +static void handleGtkCommitSignal_cb(GtkIMContext *context, gchar *committed_text, gpointer user_data) +{ + static_cast(user_data)->handleGtkCommitSignal(committed_text); +} + +static void handleGtkPreeditChangedSignal_cb(GtkIMContext *imcontext, gpointer user_data) +{ + static_cast(user_data)->handleGtkPreeditChangedSignal(); +} + +static void handleGtkRetrieveSurroundingSignal_cb(GtkIMContext *imcontext, gpointer user_data) +{ + static_cast(user_data)->handleGtkRetrieveSurroundingSignal(); +} + +static gboolean handleGtkDeleteSurroundingSignal_cb(GtkIMContext *context, gint offset, gint n_chars, gpointer user_data) +{ + return static_cast(user_data)->handleGtkDeleteSurroundingSignal(offset, n_chars); +} + +void LLWindowSDL::createIMContext() +{ + mIMContext = gtk_im_multicontext_new(); + g_signal_connect(G_OBJECT(mIMContext), "commit", G_CALLBACK(handleGtkCommitSignal_cb), this); + g_signal_connect(G_OBJECT(mIMContext), "preedit-changed", G_CALLBACK(handleGtkPreeditChangedSignal_cb), this); + g_signal_connect(G_OBJECT(mIMContext), "retrieve-surrounding", G_CALLBACK(handleGtkRetrieveSurroundingSignal_cb), this); + g_signal_connect(G_OBJECT(mIMContext), "delete-surrounding", G_CALLBACK(handleGtkDeleteSurroundingSignal_cb), this); + + gtk_im_context_set_client_window(mIMContext, mWindow->window); +} + +void LLWindowSDL::destroyIMContext() +{ + gtk_im_context_set_client_window(mIMContext, NULL); + g_object_unref(mIMContext); +} + +void LLWindowSDL::updateIMFocus() +{ + if (mPreeditor && mWindowIsFocused) + { + if (!mIMContextIsFocused) + { + gtk_im_context_focus_in(mIMContext); + mIMContextIsFocused = TRUE; + updateLanguageTextInputArea(); + } + } + else + { + if (mIMContextIsFocused) + { + gtk_im_context_focus_out(mIMContext); + mIMContextIsFocused = FALSE; + } + } +} Index: linden/indra/SConstruct =================================================================== --- linden/indra/SConstruct (.../tags/1.19.0.4) (revision 520) +++ linden/indra/SConstruct (.../branches/1.19.0-GTK) (revision 520) @@ -137,13 +137,14 @@ 'gdk-pixbuf-2.0', 'glib-2.0', 'gmodule-2.0', + 'gstreamer-0.10', 'gtk+-2.0', + 'gtkglext-1.0', 'libpng', 'pango', 'pangoft2', 'pangox', 'pangoxft', - 'sdl', 'vorbis', 'vorbisenc', 'vorbisfile', @@ -247,7 +248,7 @@ # Generic GCC flags cflags = '-g -pipe -Wall -Wno-reorder -Wno-trigraphs -Wno-sign-compare -Werror -fexceptions ' cxxflags = '' - cppflags = '-D_FORTIFY_SOURCE=2 ' + cppflags = '' if standalone: cppflags += '-DLL_STANDALONE ' @@ -282,13 +283,13 @@ cppflags += '-DAPPID=secondlife -DLL_SDL=1 ' if arch == 'x86_64' or arch == 'x86_64cross' or not enable_fmod: cppflags += '-DLL_FMOD=0 ' - cppflags += '-DLL_X11=1 -DLL_GTK=1 ' + cppflags += '-DLL_X11=1 -DLL_XRANDR=1 -DLL_XF86VM -DLL_GTK=1 ' if standalone: include_dirs += [d[2:] for d in pkgconfig('--cflags-only-I').split()] else: - client_external_libs += [ 'gtk-x11-2.0', 'atk-1.0', 'gmodule-2.0', 'gdk-x11-2.0', 'gdk_pixbuf-2.0', 'pango-1.0', 'pangoft2-1.0', 'pangox-1.0', 'pangoxft-1.0', 'Xinerama' ] - incdirs = [ 'ELFIO', 'atk-1.0', 'glib-2.0', 'gtk-2.0', + client_external_libs += [ 'libgtkglext-x11-1.0', 'gtk-x11-2.0', 'atk-1.0', 'gmodule-2.0', 'gdk-x11-2.0', 'gdk_pixbuf-2.0', 'pango-1.0', 'pangoft2-1.0', 'pangox-1.0', 'pangoxft-1.0', 'Xinerama' ] + incdirs = [ 'ELFIO', 'atk-1.0', 'glib-2.0', 'gobject-2.0', 'gtk-2.0', 'gtkglext-1.0', 'llfreetype2', 'pango-1.0' ] include_dirs += ['../libraries/' + system_str + '/include/' + d for d in incdirs] @@ -618,7 +619,7 @@ external_libs += [d[2:] for d in pkgconfig('--libs-only-l', ['gtk+-2.0']).split()] else: - external_libs = net_external_libs + [ 'db-4.2', 'gtk-x11-2.0' ] + external_libs = net_external_libs + [ 'db-4.5', 'gtk-x11-2.0' ] internal_libs = [ 'llui', 'llxml', 'llmessage', 'llvfs', 'llmath', 'llcommon' ] create_executable(output_crashlogger_bin + '-globalsyms', 'linux_crash_logger', @@ -643,10 +644,10 @@ external_libs += [ d[2:] for d in pkgconfig('--libs-only-l').split() ] else: - external_libs += [ 'freetype', 'SDL', 'vorbisenc', - 'vorbisfile', 'vorbis', 'ogg', 'db-4.2' ] + external_libs += [ 'freetype', 'ogg', 'vorbisenc', + 'vorbisfile', 'vorbis', 'db-4.5' ] - external_libs += [ 'jpeg', 'openjpeg', 'png12', 'GL', 'GLU' ] + external_libs += [ 'jpeg', 'openjpeg', 'png12', 'GL', 'GLU', 'Xxf86vm' ] if arch != 'x86_64' and arch != 'x86_64cross': if enable_fmod: