Index: cmake/00-Common.cmake =================================================================== --- cmake/00-Common.cmake (.../tags/1.22.4/linden/indra) (revision 829) +++ cmake/00-Common.cmake (.../branches/1.22-gtk/linden/indra) (revision 829) @@ -204,6 +204,7 @@ glib-2.0 gstreamer-0.10 gtk-2.0 + gtkglext-1.0 llfreetype2 pango-1.0 ) Index: cmake/UI.cmake =================================================================== --- cmake/UI.cmake (.../tags/1.22.4/linden/indra) (revision 829) +++ cmake/UI.cmake (.../branches/1.22-gtk/linden/indra) (revision 829) @@ -13,6 +13,7 @@ glib-2.0 gmodule-2.0 gtk+-2.0 + gtkglext-1.0 gthread-2.0 libpng pango @@ -43,6 +44,7 @@ gobject-2.0 gthread-2.0 gtk-x11-2.0 + gtkglext-x11-1.0 pango-1.0 pangoft2-1.0 pangox-1.0 Index: newview/llfilepicker.h =================================================================== --- newview/llfilepicker.h (.../tags/1.22.4/linden/indra) (revision 829) +++ newview/llfilepicker.h (.../branches/1.22-gtk/linden/indra) (revision 829) @@ -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 - class LLFilePicker { #ifdef LL_GTK Index: newview/llfilepicker.cpp =================================================================== --- newview/llfilepicker.cpp (.../tags/1.22.4/linden/indra) (revision 829) +++ newview/llfilepicker.cpp (.../branches/1.22-gtk/linden/indra) (revision 829) @@ -38,10 +38,6 @@ #include "lldir.h" #include "llframetimer.h" -#if LL_SDL -#include "llwindowsdl.h" // for some X/GTK utils to help with filepickers -#endif // LL_SDL - // // Globals // @@ -933,8 +929,14 @@ GtkWindow* LLFilePicker::buildFilePicker(bool is_save, bool is_folder, std::string context) { - if (LLWindowSDL::ll_try_gtk_init() && - ! gViewerWindow->getWindow()->getFullscreen()) + // We don't call LLWindow{SDL,GTK}::ll_try_gtk_init() here, + // because (1) We have a valid gViewerWindow here, and creation of + // the main viewer window should have invoked ll_try_gtk_init() + // already; and (2) It is not easy to integrate calls to + // implementation specific static functions into the runtime + // switching framework of LLWindow{SDL,GTK}. + + if (!gViewerWindow->getWindow()->getFullscreen()) { GtkWidget *win = NULL; GtkFileChooserAction pickertype = @@ -970,23 +972,21 @@ 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 = LLWindowSDL::get_SDL_XWindowID(); - if (None != XWindowID) + + GdkWindow *gdkwin = (GdkWindow *)gViewerWindow->getPlatformWindow(); + if (gdkwin) { 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; + llwarns << "Hmm, couldn't get GdkWindow * to use for transient." << llendl; } -# endif //LL_X11 g_signal_connect (GTK_FILE_CHOOSER(win), "response", Index: newview/llappviewerlinux.cpp =================================================================== --- newview/llappviewerlinux.cpp (.../tags/1.22.4/linden/indra) (revision 829) +++ newview/llappviewerlinux.cpp (.../branches/1.22-gtk/linden/indra) (revision 829) @@ -39,7 +39,6 @@ #include "llurldispatcher.h" // SLURL from other app instance #include "llviewernetwork.h" #include "llviewercontrol.h" -#include "llwindowsdl.h" #include "llmd5.h" #include "llfindlocale.h" Index: llwindow/llkeyboardgtk.h =================================================================== --- llwindow/llkeyboardgtk.h (.../tags/1.22.4/linden/indra) (revision 0) +++ llwindow/llkeyboardgtk.h (.../branches/1.22-gtk/linden/indra) (revision 829) @@ -0,0 +1,64 @@ +/** + * @file llkeyboardgtk.h + * @brief Handler for assignable key bindings + * + * $LicenseInfo:firstyear=2004&license=viewergpl$ + * + * Copyright (c) 2004-2008, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLKEYBOARDGTK_H +#define LL_LLKEYBOARDGTK_H + +#include "llkeyboard.h" +#include "gtk/gtk.h" + +class LLWindowGTK; + +class LLKeyboardGTK : public LLKeyboard +{ +public: + LLKeyboardGTK(LLWindowGTK *window); + /*virtual*/ ~LLKeyboardGTK() {}; + + /*virtual*/ BOOL handleKeyUp(const U16 key, MASK mask); + /*virtual*/ BOOL handleKeyDown(const U16 key, MASK mask); + /*virtual*/ void resetMaskKeys(); + /*virtual*/ MASK currentMask(BOOL for_mouse_event); + /*virtual*/ void scanKeyboard(); + +protected: + MASK updateModifiers(const U32 mask); + void setModifierKeyLevel(KEY key, BOOL new_state); + BOOL translateNumpadKey(const U16 os_key, KEY *translated_key); + U16 inverseTranslateNumpadKey(const KEY translated_key); +protected: + LLWindowGTK * const mWindow; +private: + std::map mTranslateNumpadMap; // special map for translating OS keys to numpad keys + std::map mInvTranslateNumpadMap; // inverse of the above +}; + +#endif // LL_LLKEYBOARDGTK_H Index: llwindow/llwindowsdl.h =================================================================== --- llwindow/llwindowsdl.h (.../tags/1.22.4/linden/indra) (revision 829) +++ llwindow/llwindowsdl.h (.../branches/1.22-gtk/linden/indra) (revision 829) @@ -113,7 +113,10 @@ /*virtual*/ BOOL dialog_color_picker(F32 *r, F32 *g, F32 *b); + // LLWindowSDL::getPlatformWindow() returns a pointer to GdkWindow object. This must synchronize with LLWindowGTK. /*virtual*/ void *getPlatformWindow(); + // LLWindowSDL::getMediaWindow() returns a pointer to GtkContainer object. This must synchronize with LLWindowGTK. + /*virtual*/ void *getMediaWindow(); /*virtual*/ void bringToFront(); /*virtual*/ void spawnWebBrowser(const std::string& escaped_url); @@ -122,24 +125,18 @@ // 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); +#if LL_X11 + static Window get_SDL_XWindowID(void); + static Display* get_SDL_Display(void); +#endif // LL_X11 #if LL_GTK // Lazily initialize and check the runtime GTK version for goodness. static bool ll_try_gtk_init(void); #endif // LL_GTK -#if LL_X11 - static Window get_SDL_XWindowID(void); - static Display* get_SDL_Display(void); -#endif // LL_X11 - protected: LLWindowSDL( const std::string& title, int x, int y, int width, int height, U32 flags, @@ -202,6 +199,10 @@ int mHaveInputFocus; /* 0=no, 1=yes, else unknown */ int mIsMinimized; /* 0=no, 1=yes, else unknown */ +#if LL_GTK + void * mGdkWindow; +#endif + friend class LLWindowManager; #if LL_X11 @@ -215,6 +216,8 @@ void x11_set_urgent(BOOL urgent); BOOL mFlashing; LLTimer mFlashTimer; + Window mSDL_XWindowID; + Display *mSDL_Display; #endif //LL_X11 }; Index: llwindow/llwindowgtk.h =================================================================== --- llwindow/llwindowgtk.h (.../tags/1.22.4/linden/indra) (revision 0) +++ llwindow/llwindowgtk.h (.../branches/1.22-gtk/linden/indra) (revision 829) @@ -0,0 +1,265 @@ +/** + * @file llwindowgtk.h + * @brief GTK implementation of LLWindow class + * + * $LicenseInfo:firstyear=2001&license=viewergpl$ + * + * Copyright (c) 2001-2008, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLWINDOWGTK_H +#define LL_LLWINDOWGTK_H + +// GTK-X11 + GtkGLExt + immodule implementation of LLWindow class + +#include "llwindow.h" +#include "llpreeditor.h" +#include "gtk/gtk.h" + +// AssertMacros.h does bad things. +#undef verify +#undef check +#undef require + + +class LLWindowGTK : public LLWindow +{ +public: + /*virtual*/ void show(); + /*virtual*/ void hide(); + /*virtual*/ void close(); + /*virtual*/ BOOL getVisible(); + /*virtual*/ BOOL getMinimized(); + /*virtual*/ BOOL getMaximized(); + /*virtual*/ BOOL maximize(); + /*virtual*/ BOOL getPosition(LLCoordScreen *position); + /*virtual*/ BOOL getSize(LLCoordScreen *size); + /*virtual*/ BOOL getSize(LLCoordWindow *size); + /*virtual*/ BOOL setPosition(LLCoordScreen position); + /*virtual*/ BOOL setSize(LLCoordScreen size); + /*virtual*/ BOOL switchContext(BOOL fullscreen, const LLCoordScreen &size, BOOL disable_vsync, const LLCoordScreen * const posp = NULL); + /*virtual*/ BOOL setCursorPosition(LLCoordWindow position); + /*virtual*/ BOOL getCursorPosition(LLCoordWindow *position); + /*virtual*/ void showCursor(); + /*virtual*/ void hideCursor(); + /*virtual*/ void showCursorFromMouseMove(); + /*virtual*/ void hideCursorUntilMouseMove(); + /*virtual*/ BOOL isCursorHidden(); + /*virtual*/ void setCursor(ECursorType cursor); + /*virtual*/ ECursorType getCursor(); + /*virtual*/ void captureMouse(); + /*virtual*/ void releaseMouse(); + /*virtual*/ void setMouseClipping( BOOL b ); + /*virtual*/ BOOL isClipboardTextAvailable(); + /*virtual*/ BOOL pasteTextFromClipboard(LLWString &dst); + /*virtual*/ BOOL copyTextToClipboard(const LLWString & src); + /*virtual*/ void flashIcon(F32 seconds); + /*virtual*/ F32 getGamma(); + /*virtual*/ BOOL setGamma(const F32 gamma); // Set the gamma + /*virtual*/ U32 getFSAASamples(); + /*virtual*/ void setFSAASamples(const U32 samples); + /*virtual*/ BOOL restoreGamma(); // Restore original gamma table (before updating gamma) + /*virtual*/ ESwapMethod getSwapMethod() { return mSwapMethod; } + /*virtual*/ void processMiscNativeEvents(); + /*virtual*/ void gatherInput(); + /*virtual*/ void swapBuffers(); + + /*virtual*/ void delayInputProcessing() { }; + + // handy coordinate space conversion routines + /*virtual*/ BOOL convertCoords(LLCoordScreen from, LLCoordWindow *to); + /*virtual*/ BOOL convertCoords(LLCoordWindow from, LLCoordScreen *to); + /*virtual*/ BOOL convertCoords(LLCoordWindow from, LLCoordGL *to); + /*virtual*/ BOOL convertCoords(LLCoordGL from, LLCoordWindow *to); + /*virtual*/ BOOL convertCoords(LLCoordScreen from, LLCoordGL *to); + /*virtual*/ BOOL convertCoords(LLCoordGL from, LLCoordScreen *to); + + /*virtual*/ LLWindowResolution* getSupportedResolutions(S32 &num_resolutions); + /*virtual*/ F32 getNativeAspectRatio(); + /*virtual*/ F32 getPixelAspectRatio(); + /*virtual*/ void setNativeAspectRatio(F32 ratio) { mOverrideAspectRatio = ratio; } + + /*virtual*/ void beforeDialog(); + /*virtual*/ void afterDialog(); + + /*virtual*/ BOOL dialog_color_picker(F32 *r, F32 *g, F32 *b); + + // LLWindowGTK::getPlatformWindow() returns a pointer to GdkWindow object. This must synchronize with LLWindowSDL. + /*virtual*/ void *getPlatformWindow(); + // LLWindowGTK::getMediaWindow() returns a pointer to GtkContainer object. This must synchronize with LLWindowSDL. + /*virtual*/ void *getMediaWindow(); + /*virtual*/ void bringToFront(); + + /*virtual*/ void spawnWebBrowser(const std::string& escaped_url); + + static std::string getFontListSans(); + + /*virtual*/ void allowLanguageTextInput(LLPreeditor *preeditor, BOOL b); + /*virtual*/ void updateLanguageTextInputArea(); + /*virtual*/ void interruptLanguageTextInput(); + + // Lazily initialize and check the runtime GTK version for goodness. + static bool ll_try_gtk_init(void); + + // I want to make getPlatformWindow() to reutrn GtkWidget *, + // but the abstract behavior of getPlatformWindow() must match + // with LLWindowSDL::getPlatformWindow(), and it is impossible + // to _cast_ SDL window to GTK window. So, we provide + // getGtkWindow() as a LLWindowGTK specific member function. + GtkWidget * getGtkWindow() const { return mWindow; } + +protected: + LLWindowGTK( + const std::string& title, int x, int y, int width, int height, U32 flags, + BOOL fullscreen, BOOL clearBg, BOOL disable_vsync, BOOL use_gl, + BOOL ignore_pixel_depth, U32 fsaa_samples); + ~LLWindowGTK(); + + void initCursors(); + void quitCursors(); + BOOL isValid(); + void moveWindow(const LLCoordScreen& position,const LLCoordScreen& size); + + void minimize(); + void restore(); + + BOOL shouldPostQuit() { return mPostQuit; } + +protected: + // + // Platform specific methods + // + + // 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 std::string& text, const std::string& caption, U32 type); + void fixWindowSize(void); + 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; + GtkWidget * mWindow; + std::string 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; + F32 mOverrideAspectRatio; + F32 mGamma; + GdkCursor * mHiddenCursor; + GdkCursor * mCursors[UI_CURSOR_COUNT]; + S32 mXRRVersion; + S32 mXF86VMVersion; + U32 mFSAASamples; + GtkWidget * mGLWidget; + S32 mOriginalMonitorWidth; + S32 mOriginalMonitorHeight; + + friend class LLWindowManager; + +protected: + BOOL mFlashing; + LLTimer mFlashTimer; + +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; +}; + + +class LLSplashScreenGTK : public LLSplashScreen +{ +public: + LLSplashScreenGTK(); + virtual ~LLSplashScreenGTK(); + + /*virtual*/ void showImpl(); + /*virtual*/ void updateImpl(const std::string& mesg); + /*virtual*/ void hideImpl(); +}; + +S32 OSMessageBoxGTK(const std::string& text, const std::string& caption, U32 type); + +void load_url_external(const char* url); + +// Lazily initialize and check the runtime GTK version for goodness. +BOOL ll_try_gtk_init(void); + +#endif //LL_LLWINDOWGTK_H Index: llwindow/llkeyboardwin32.cpp =================================================================== --- llwindow/llkeyboardwin32.cpp (.../tags/1.22.4/linden/indra) (revision 829) +++ llwindow/llkeyboardwin32.cpp (.../branches/1.22-gtk/linden/indra) (revision 829) @@ -73,6 +73,16 @@ // keyboards in the US. Numeric keypad '+' generates VK_ADD below. // Thus we translate it as '='. // Potential bug: This may not be true on international keyboards. JC + // + // The definition of VK_OEM_PLUS is "the key that produces a '+' + // symbol." On US keyboars, "the '=' key" is "the key that + // produces a '+' symbol", because '+' is located above '=', and + // users hits "the '=' key" when he/she wants to type a '+' + // symbol. On Japanese standard keyboard (usually known as "OADG + // 106"), for example, the '+' is above ';'. (BTW ';' has + // its own non-shift case key on OADG 106.) So, the VK_OEM_PLUS + // has nothing to do with '=' sign. I'm not sure, however, + // whether it makes a problem. -- Alissa mTranslateKeyMap[VK_OEM_PLUS] = '='; mTranslateKeyMap[VK_OEM_COMMA] = ','; mTranslateKeyMap[VK_OEM_MINUS] = '-'; Index: llwindow/CMakeLists.txt =================================================================== --- llwindow/CMakeLists.txt (.../tags/1.22.4/linden/indra) (revision 829) +++ llwindow/CMakeLists.txt (.../branches/1.22-gtk/linden/indra) (revision 829) @@ -79,10 +79,14 @@ list(APPEND viewer_SOURCE_FILES llkeyboardsdl.cpp llwindowsdl.cpp + llkeyboardgtk.cpp + llwindowgtk.cpp ) list(APPEND viewer_HEADER_FILES llkeyboardsdl.h llwindowsdl.h + llkeyboardgtk.h + llwindowgtk.h ) endif (LINUX) Index: llwindow/llkeyboard.h =================================================================== --- llwindow/llkeyboard.h (.../tags/1.22.4/linden/indra) (revision 829) +++ llwindow/llkeyboard.h (.../branches/1.22-gtk/linden/indra) (revision 829) @@ -67,11 +67,13 @@ class LLKeyboard { public: + // "numpad distinct" in llkeyboard represents the parameter + // NumpadControl. See app_settings/settings.xml for definition. typedef enum e_numpad_distinct { - ND_NEVER, - ND_NUMLOCK_OFF, - ND_NUMLOCK_ON + ND_NEVER = 0, + ND_NUMLOCK_OFF = 1, + ND_NUMLOCK_ON = 2 } ENumpadDistinct; public: Index: llwindow/llkeyboardgtk.cpp =================================================================== --- llwindow/llkeyboardgtk.cpp (.../tags/1.22.4/linden/indra) (revision 0) +++ llwindow/llkeyboardgtk.cpp (.../branches/1.22-gtk/linden/indra) (revision 829) @@ -0,0 +1,381 @@ +/** + * @file llkeyboardgtk.cpp + * @brief Handler for assignable key bindings + * + * $LicenseInfo:firstyear=2001&license=viewergpl$ + * + * Copyright (c) 2001-2008, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" +#include "llkeyboardgtk.h" +#include "llwindowgtk.h" +#include "gtk/gtk.h" +#include "gdk/gdkkeysyms.h" + +LLKeyboardGTK::LLKeyboardGTK(LLWindowGTK *window) + : mWindow(window) +{ + // Set up key mapping for GTK - eventually can read this from a file? + // Anything not in the key map gets dropped + + // GDK keyval for letter keys is more like a wParam from WM_CHAR + // messages than that from WM_KEYDOWN/WM_KEYUP messages of Win32. + // A same physical key produces different keyval's depending on + // the Shift key status. Except for alphabet keys, mapping + // between lower-case symbol and upper-case symbol depends on the + // language of the keybaord. Simulating the Windows' behaviour on + // top of GDK key events is not easy. Fortunately, we need just a + // small set of symbol keys to be identified as KEY_*, and we can + // handle them specially. + + // GDK maps ASCII characters with their ASCII codes. + for (U16 cur_char = 0x20; cur_char < 0x7F; cur_char++) + { + mTranslateKeyMap[cur_char] = cur_char; + } + for (U16 cur_char = 'a'; cur_char <= 'z'; cur_char++) + { + // Ignore the Shift status for alphabet keys and use + // upper-case code values. This is for compatibility with + // Win32. + mTranslateKeyMap[cur_char] = (cur_char - 'a') + 'A'; + } + + // Special symbols that have dedicated LL KEY_* values. We can't + // easily find the paired symbol on a same key, so just forget + // about it. + mTranslateKeyMap[GDK_minus] = KEY_HYPHEN; + mTranslateKeyMap[GDK_equal] = KEY_EQUALS; + mTranslateKeyMap[GDK_slash] = KEY_DIVIDE; + mTranslateKeyMap[GDK_asterisk] = KEY_MULTIPLY; + mTranslateKeyMap[GDK_plus] = KEY_ADD; + + // Command/control keys. + 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_BackSpace] = KEY_BACKSPACE; + mTranslateKeyMap[GDK_Delete] = KEY_DELETE; + mTranslateKeyMap[GDK_Insert] = KEY_INSERT; + 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_Caps_Lock] = KEY_CAPSLOCK; + mTranslateKeyMap[GDK_Tab] = KEY_TAB; + 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; + + // GDK generates GDK_ISO_Left_Tab when Shift-TAB (back tab) is + // pressed on a standard PC keybaord. LLUI wants TAB to be + // KEY_TAB regardless of modifier keys. The following hack breaks + // if the custom keyboard has separate TAB and LEFT TAB keys, + // although it is unlikely. + mTranslateKeyMap[GDK_ISO_Left_Tab] = KEY_TAB; + mTranslateKeyMap[GDK_ISO_Enter] = KEY_RETURN; + + // Function keys + 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; + + // Numpad keys. + mTranslateKeyMap[GDK_KP_Enter] = KEY_RETURN; + 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_KP_Equal] = KEY_EQUALS; + + // Build inverse map + std::map::iterator iter; + for (iter = mTranslateKeyMap.begin(); iter != mTranslateKeyMap.end(); iter++) + { + mInvTranslateKeyMap[iter->second] = iter->first; + } + + // numpad number keys. GDK generates separate keyval's for numpad + // number keys depending on the NUM LOCK status. Also, we need to + // take care of numpad distinct status. We use a special mapping + // table for numpad number keys. + static struct { U16 keyval; KEY keys[3]; } numpad_map[] = { + // no NUM LOCK + { GDK_KP_Insert, { KEY_INSERT, KEY_PAD_INS, KEY_PAD_INS } }, + { GDK_KP_End, { KEY_END, KEY_PAD_END, KEY_PAD_END } }, + { GDK_KP_Down, { KEY_DOWN, KEY_PAD_DOWN, KEY_PAD_DOWN } }, + { GDK_KP_Page_Down, { KEY_PAGE_DOWN, KEY_PAD_PGDN, KEY_PAD_PGDN } }, + { GDK_KP_Left, { KEY_LEFT, KEY_PAD_LEFT, KEY_PAD_LEFT } }, + { GDK_KP_Begin, { KEY_PAD_CENTER, KEY_PAD_CENTER, KEY_PAD_CENTER } }, + { GDK_KP_Right, { KEY_RIGHT, KEY_PAD_RIGHT, KEY_PAD_RIGHT } }, + { GDK_KP_Home, { KEY_HOME, KEY_PAD_HOME, KEY_PAD_HOME } }, + { GDK_KP_Up, { KEY_UP, KEY_PAD_UP, KEY_PAD_UP } }, + { GDK_KP_Page_Up, { KEY_PAGE_UP, KEY_PAD_PGUP, KEY_PAD_PGUP } }, + { GDK_KP_Delete, { KEY_DELETE, KEY_PAD_DEL, KEY_PAD_DEL } }, + // NUM LOCK + { GDK_KP_0, { '0', '0', KEY_PAD_INS } }, + { GDK_KP_1, { '1', '1', KEY_PAD_END } }, + { GDK_KP_2, { '2', '2', KEY_PAD_DOWN } }, + { GDK_KP_3, { '3', '3', KEY_PAD_PGDN } }, + { GDK_KP_4, { '4', '4', KEY_PAD_LEFT } }, + { GDK_KP_5, { '5', '5', KEY_PAD_CENTER } }, + { GDK_KP_6, { '6', '6', KEY_PAD_RIGHT } }, + { GDK_KP_7, { '7', '7', KEY_PAD_HOME } }, + { GDK_KP_8, { '8', '8', KEY_PAD_UP } }, + { GDK_KP_9, { '9', '9', KEY_PAD_PGUP } }, + { GDK_KP_Decimal, { '.', '.', KEY_PAD_DEL } }, + // end of list + {} + }; + for (int i = 0; numpad_map[i].keyval; i++) + { + mTranslateNumpadMap[numpad_map[i].keyval] = numpad_map[i].keys; + } +} + +void LLKeyboardGTK::resetMaskKeys() +{ + GdkModifierType mask; + GdkDisplay *display = gdk_drawable_get_display(GDK_DRAWABLE(mWindow->getPlatformWindow())); + gdk_display_get_pointer(display, 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 & GDK_SHIFT_MASK) + { + mKeyLevel[KEY_SHIFT] = TRUE; + } + + if(mask & GDK_CONTROL_MASK) + { + mKeyLevel[KEY_CONTROL] = TRUE; + } + + if(mask & GDK_MOD1_MASK) + { + mKeyLevel[KEY_ALT] = TRUE; + } +} + + +// Translate the Gtk modifier mask into LL mask. +MASK LLKeyboardGTK::updateModifiers(const MASK mask) +{ + const GdkModifierType in_mask = (GdkModifierType) mask; + MASK out_mask = MASK_NONE; + + if(in_mask & GDK_SHIFT_MASK) + { + out_mask |= MASK_SHIFT; + } + + if(in_mask & GDK_CONTROL_MASK) + { + out_mask |= MASK_CONTROL; + } + + if(in_mask & GDK_MOD1_MASK) + { + out_mask |= MASK_ALT; + } + + return out_mask; +} + + +static U16 adjustNativekeyFromUnhandledMask(const U16 key, const MASK mask) +{ + return key; +} + + +BOOL LLKeyboardGTK::handleKeyDown(const U16 key, const MASK mask) +{ + U16 adjusted_nativekey; + KEY translated_key = 0; + U32 translated_mask = MASK_NONE; + BOOL handled = FALSE; + + adjusted_nativekey = adjustNativekeyFromUnhandledMask(key, mask); + + translated_mask = updateModifiers(mask); + + if(translateNumpadKey(adjusted_nativekey, &translated_key)) + { + llinfos << "KeyPress translated: " << gdk_keyval_name(key) << " (" << key << ")" << llendl; + handled = handleTranslatedKeyDown(translated_key, translated_mask); + } + else + { + llinfos << "KeyPress ignored: " << gdk_keyval_name(key) << " (" << key << ")" << llendl; + } + + return handled; +} + + +BOOL LLKeyboardGTK::handleKeyUp(const U16 key, const MASK mask) +{ + U16 adjusted_nativekey; + KEY translated_key = 0; + U32 translated_mask = MASK_NONE; + BOOL handled = FALSE; + + adjusted_nativekey = adjustNativekeyFromUnhandledMask(key, mask); + + translated_mask = updateModifiers(mask); + + if(translateNumpadKey(adjusted_nativekey, &translated_key)) + { + llinfos << "KeyRelease translated: " << gdk_keyval_name(key) << " (" << key << ")" << llendl; + handled = handleTranslatedKeyUp(translated_key, translated_mask); + } + else + { + llinfos << "KeyRelease ignored: " << gdk_keyval_name(key) << " (" << key << ")" << llendl; + } + + return handled; +} + +MASK LLKeyboardGTK::currentMask(BOOL for_mouse_event) +{ + MASK result = MASK_NONE; + GdkModifierType modifiers; + GdkDisplay *display = gdk_drawable_get_display(GDK_DRAWABLE(mWindow->getPlatformWindow())); + gdk_display_get_pointer(display, NULL, NULL, NULL, &modifiers); + if (modifiers & GDK_SHIFT_MASK) + { + result |= MASK_SHIFT; + } + if (modifiers & GDK_CONTROL_MASK) + { + result |= MASK_CONTROL; + } + if (modifiers & GDK_MOD1_MASK) + { + result |= MASK_ALT; + } + +#if 0 + // 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 (modifiers & GDK_MOD2_MASK) + { + result |= MASK_CONTROL; + } + } +#endif + + return result; +} + +void LLKeyboardGTK::scanKeyboard() +{ + for (S32 key = 0; key < KEY_COUNT; key++) + { + // Generate callback if any event has occurred on this key this frame. + // Can't just test mKeyLevel, because this could be a slow frame and + // key might have gone down then up. JC + if (mKeyLevel[key] || mKeyDown[key] || mKeyUp[key]) + { + mCurScanKey = key; + mCallbacks->handleScanKey(key, mKeyDown[key], mKeyUp[key], mKeyLevel[key]); + } + } + + // Reset edges for next frame + for (S32 key = 0; key < KEY_COUNT; key++) + { + mKeyUp[key] = FALSE; + mKeyDown[key] = FALSE; + if (mKeyLevel[key]) + { + mKeyLevelFrameCount[key]++; + } + } +} + +BOOL LLKeyboardGTK::translateNumpadKey(const U16 os_key, KEY *translated_key) +{ + // No numpad key keycodes are in our mTranslateKeyMap, so we can + // translate normal keys first. + if (translateKey(os_key, translated_key)) + { + return TRUE; + } + + switch (mNumpadDistinct) + { + case ND_NEVER: + case ND_NUMLOCK_OFF: + case ND_NUMLOCK_ON: + break; + default: + llwarns << "unknow NumpadDistinct (NumpadControl) value: " << mNumpadDistinct << llendl; + return FALSE; + } + + std::map::iterator iter= mTranslateNumpadMap.find(os_key); + if(iter == mTranslateNumpadMap.end()) + { + return FALSE; + } + *translated_key = iter->second[mNumpadDistinct]; + return TRUE; +} + +U16 LLKeyboardGTK::inverseTranslateNumpadKey(const KEY translated_key) +{ + if(mNumpadDistinct == ND_NUMLOCK_ON) + { + std::map::iterator iter= mInvTranslateNumpadMap.find(translated_key); + if(iter != mInvTranslateNumpadMap.end()) + { + return iter->second; + } + } + return inverseTranslateKey(translated_key); +} Index: llwindow/llwindow.cpp =================================================================== --- llwindow/llwindow.cpp (.../tags/1.22.4/linden/indra) (revision 829) +++ llwindow/llwindow.cpp (.../branches/1.22-gtk/linden/indra) (revision 829) @@ -36,6 +36,7 @@ #include "llwindowmesaheadless.h" #elif LL_SDL #include "llwindowsdl.h" +#include "llwindowgtk.h" #elif LL_WINDOWS #include "llwindowwin32.h" #elif LL_DARWIN @@ -219,6 +220,12 @@ } +static bool ll_use_sdl() +{ + const char *value = getenv("LL_USE_SDL"); + return value && strcmp(value, "1") == 0; +} + S32 OSMessageBox(const std::string& text, const std::string& caption, U32 type) { // Properly hide the splash screen when displaying the message box @@ -238,7 +245,14 @@ #elif LL_DARWIN result = OSMessageBoxMacOSX(text, caption, type); #elif LL_SDL - result = OSMessageBoxSDL(text, caption, type); + if (ll_use_sdl()) + { + result = OSMessageBoxSDL(text, caption, type); + } + else + { + result = OSMessageBoxGTK(text, caption, type); + } #else #error("OSMessageBox not implemented for this platform!") #endif @@ -315,7 +329,14 @@ #elif LL_DARWIN return LLWindowMacOSX::getFontListSans(); #elif LL_SDL - return LLWindowSDL::getFontListSans(); + if (ll_use_sdl()) + { + return LLWindowSDL::getFontListSans(); + } + else + { + return LLWindowGTK::getFontListSans(); + } #else return ""; #endif @@ -479,9 +500,18 @@ title, name, x, y, width, height, flags, fullscreen, clearBg, disable_vsync, use_gl, ignore_pixel_depth); #elif LL_SDL - new_window = new LLWindowSDL( - title, x, y, width, height, flags, - fullscreen, clearBg, disable_vsync, use_gl, ignore_pixel_depth, fsaa_samples); + if (ll_use_sdl()) + { + new_window = new LLWindowSDL( + title, x, y, width, height, flags, + fullscreen, clearBg, disable_vsync, use_gl, ignore_pixel_depth, fsaa_samples); + } + else + { + new_window = new LLWindowGTK( + title, x, y, width, height, flags, + fullscreen, clearBg, disable_vsync, use_gl, ignore_pixel_depth, fsaa_samples); + } #elif LL_WINDOWS new_window = new LLWindowWin32( title, name, x, y, width, height, flags, Index: llwindow/llwindowsdl.cpp =================================================================== --- llwindow/llwindowsdl.cpp (.../tags/1.22.4/linden/indra) (revision 829) +++ llwindow/llwindowsdl.cpp (.../branches/1.22-gtk/linden/indra) (revision 829) @@ -194,6 +194,8 @@ BOOL ignore_pixel_depth, U32 fsaa_samples) : LLWindow(fullscreen, flags), mGamma(1.0f) { + llinfos << "Initializing SDL Window." << llendl; + // Initialize the keyboard gKeyboard = new LLKeyboardSDL(); // Note that we can't set up key-repeat until after SDL has init'd video @@ -223,6 +225,7 @@ // initialized with a non-C locale and cause lots of serious random // weirdness. ll_try_gtk_init(); + mGdkWindow = NULL; #endif // LL_GTK // Get the original aspect ratio of the main device. @@ -718,6 +721,12 @@ #if LL_X11 init_x11clipboard(); +#if LL_GTK + // Prepare a GDK Window for use as our platform window. We do + // this after init_x11clipboard(), because mSDL_XWindowID is + // initialized there. + mGdkWindow = gdk_window_foreign_new((GdkNativeWindow)mSDL_XWindowID); +#endif #endif // LL_X11 //make sure multisampling is disabled by default @@ -763,6 +772,10 @@ void LLWindowSDL::destroyContext() { llinfos << "destroyContext begins" << llendl; +#if LL_GTK + g_object_unref(mGdkWindow); + mGdkWindow = NULL; +#endif #if LL_X11 quit_x11clipboard(); #endif // LL_X11 @@ -1289,7 +1302,7 @@ return XA_STRING; case SDLCLIPTYPE('U', 'T', 'F', '8'): // newer de-facto UTF8 clipboard atom - return XInternAtom(gWindowImplementation->mSDL_Display, + return XInternAtom(LLWindowSDL::get_SDL_Display(), "UTF8_STRING", False); default: { @@ -1298,7 +1311,7 @@ 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, + return XInternAtom(LLWindowSDL::get_SDL_Display(), format, False); } } @@ -2541,19 +2554,17 @@ win = gtk_message_dialog_new(NULL, flags, messagetype, buttons, "%s", text.c_str()); -# 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) + gWindowImplementation->getPlatformWindow()) { gtk_widget_realize(GTK_WIDGET(win)); // so we can get its gdkwin - GdkWindow *gdkwin = gdk_window_foreign_new(gWindowImplementation->mSDL_XWindowID); + GdkWindow *gdkwin = GDK_WINDOW(gWindowImplementation->getPlatformWindow()); 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); @@ -2627,18 +2638,17 @@ win = gtk_color_selection_dialog_new(NULL); -# 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) + if (gWindowImplementation && + gWindowImplementation->getPlatformWindow()) { gtk_widget_realize(GTK_WIDGET(win)); // so we can get its gdkwin - GdkWindow *gdkwin = gdk_window_foreign_new(mSDL_XWindowID); + GdkWindow *gdkwin = GDK_WINDOW(gWindowImplementation->getPlatformWindow()); gdk_window_set_transient_for(GTK_WIDGET(win)->window, gdkwin); } -# endif //LL_X11 GtkColorSelection *colorsel = GTK_COLOR_SELECTION (GTK_COLOR_SELECTION_DIALOG(win)->colorsel); @@ -2762,6 +2772,15 @@ void *LLWindowSDL::getPlatformWindow() { +#if LL_GTK + return mGdkWindow; +#else + return NULL; +#endif +} + +void *LLWindowSDL::getMediaWindow() +{ #if LL_GTK && LL_LLMOZLIB_ENABLED if (LLWindowSDL::ll_try_gtk_init()) { Index: llwindow/llwindowgtk.cpp =================================================================== --- llwindow/llwindowgtk.cpp (.../tags/1.22.4/linden/indra) (revision 0) +++ llwindow/llwindowgtk.cpp (.../branches/1.22-gtk/linden/indra) (revision 829) @@ -0,0 +1,3223 @@ +/** + * @file llwindowgtk.cpp + * @brief GTK implementation of LLWindow class + * + * $LicenseInfo:firstyear=2001&license=viewergpl$ + * + * Copyright (c) 2001-2008, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llwindowgtk.h" +#include "llkeyboardgtk.h" +#include "llerror.h" +#include "llgl.h" +#include "llstring.h" +#include "lldir.h" +#include "llfindlocale.h" + +#include "indra_constants.h" + +#include "llpreeditor.h" + +#include "gdk/gdk.h" +#include "gdk/gdkkeysyms.h" +#include "gtk/gtk.h" +#include "gtk/gtkgl.h" + +#if LL_XRANDR +#include "gdk/gdkx.h" +#include +#include +#include +#endif + +#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 +extern "C" { +# include "fontconfig/fontconfig.h" +} + +#include +#include + +#if !GTK_CHECK_VERSION(2,8,0) +#define LL_OLD_FASHIONED_GTK 1 +#endif + +// Do we need "ATI mouse cursor work-around"? +// static variable for ATI mouse cursor crash work-around: +static bool ATIbug = false; + +// +// LLWindowGTK +// + +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 LLWindowGTK 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 LLWindowGTK *gWindowImplementation = NULL; + +//void maybe_lock_display(void) +//{ +// if (gWindowImplementation && gWindowImplementation->Lock_Display) { +// gWindowImplementation->Lock_Display(); +// } +//} + +//void maybe_unlock_display(void) +//{ +// if (gWindowImplementation && gWindowImplementation->Unlock_Display) { +// gWindowImplementation->Unlock_Display(); +// } +//} + +// Lazily initialize and check the runtime GTK version for goodness. +//static +bool LLWindowGTK::ll_try_gtk_init(void) +{ + static BOOL tried_gtk_init = FALSE; + static BOOL gtk_is_good = FALSE; + + if (tried_gtk_init) + { + return gtk_is_good; + } + tried_gtk_init = TRUE; + + llinfos << "Starting GTK Initialization." << llendl; + + // We *need* to issue setlocale to run immodules. + // gtk_disable_setlocale(); + +#if LL_GSTREAMER_ENABLED + if (!g_thread_supported()) + { + g_thread_init(NULL); + } +#endif // LL_GSTREAMER_ENABLED + + if (!gtk_init_check(NULL, NULL)) + { + 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; + } + + llinfos << "GTK Initialized." << llendl; + llinfos << "- Compiled against GTK version " + << GTK_MAJOR_VERSION << "." + << GTK_MINOR_VERSION << "." + << GTK_MICRO_VERSION << llendl; + llinfos << "- Running against GTK version " + << gtk_major_version << "." + << gtk_minor_version << "." + << gtk_micro_version << llendl; + 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; + } + + 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; +} + + +LLWindowGTK::LLWindowGTK(const std::string& title, S32 x, S32 y, S32 width, + S32 height, U32 flags, + BOOL fullscreen, BOOL clearBg, + BOOL disable_vsync, BOOL use_gl, + BOOL ignore_pixel_depth, U32 fsaa_samples) + : LLWindow(fullscreen, flags), mGamma(1.0f) +{ + llinfos << "Initializing SDL Window." << llendl; + + // Initialize the keyboard + gKeyboard = new LLKeyboardGTK(this); + + // Ignore use_gl for now, only used for drones on PC + mWindow = NULL; + mGLWidget = NULL; + mOverrideAspectRatio = 0.f; + mGrabbyKeyFlags = 0; + mPreeditor = NULL; + mFlashing = FALSE; + mWindowIsFocused = FALSE; + mIMContextIsFocused = FALSE; + mPrimaryClipboardNeedsReset = TRUE; + mSupportedResolutions = NULL; + mFSAASamples = fsaa_samples; + + // 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. + // + // 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()) + { + return; + } + +#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. + if (title.empty()) + mWindowTitle = "GTK Window"; // *FIX: (???) + else + mWindowTitle = title; + + // Create the GL context and set it up for windowed or fullscreen, as appropriate. + if(createContext(x, y, width, height, fullscreen, disable_vsync)) + { + gGLManager.initGL(); + + //start with arrow cursor + initCursors(); + setCursor( UI_CURSOR_ARROW ); + } + stop_glerror(); + + // Initialize input method. + createIMContext(); + + gWindowImplementation = this; +} + +// 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 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 +// This is an XFree86/XOrg-specific hack for detecting the amount of Video RAM +// on this machine. It works by searching /var/log/var/log/Xorg.?.log or +// /var/log/XFree86.?.log for a ': (VideoRAM|Memory): (%d+) kB' regex, where +// '?' is the X11 display number derived from $DISPLAY +static int x11_detect_VRAM_kb_fp(FILE *fp, const char *prefix_str) +{ + const int line_buf_size = 1000; + char line_buf[line_buf_size]; + while (fgets(line_buf, line_buf_size, fp)) + { + //lldebugs << "XLOG: " << line_buf << llendl; + + // Why the ad-hoc parser instead of using a regex? Our + // favourite regex implementation - libboost_regex - is + // quite a heavy and troublesome dependency for the client, so + // it seems a shame to introduce it for such a simple task. + const char *part1_template = prefix_str; + const char part2_template[] = " kB"; + char *part1 = strstr(line_buf, part1_template); + if (part1) // found start of matching line + { + part1 = &part1[strlen(part1_template)]; // -> after + char *part2 = strstr(part1, part2_template); + if (part2) // found end of matching line + { + // now everything between part1 and part2 is + // supposed to be numeric, describing the + // number of kB of Video RAM supported + int rtn = 0; + for (; part1 < part2; ++part1) + { + if (*part1 < '0' || *part1 > '9') + { + // unexpected char, abort parse + rtn = 0; + break; + } + rtn *= 10; + rtn += (*part1) - '0'; + } + if (rtn > 0) + { + // got the kB number. return it now. + return rtn; + } + } + } + } + return 0; // 'could not detect' +} + +static int x11_detect_VRAM_kb() +{ +#if LL_SOLARIS +#error Can this be done without an explicit architecture test, ie a test FOR xorg? Was followed by: && defined(__sparc) + // NOTE: there's no Xorg server on SPARC so just return 0 + // and allow SDL to attempt to get the amount of VRAM + return(0); +#else + + std::string x_log_location("/var/log/"); + std::string fname; + int rtn = 0; // 'could not detect' + int display_num = 0; + FILE *fp; + char *display_env = getenv("DISPLAY"); // e.g. :0 or :0.0 or :1.0 etc + // parse DISPLAY number so we can go grab the right log file + if (display_env[0] == ':' && + display_env[1] >= '0' && display_env[1] <= '9') + { + display_num = display_env[1] - '0'; + } + + // *TODO: we could be smarter and see which of Xorg/XFree86 has the + // freshest time-stamp. + + // Try Xorg log first + fname = x_log_location; + fname += "Xorg."; + fname += ('0' + display_num); + fname += ".log"; + fp = fopen(fname.c_str(), "r"); + if (fp) + { + llinfos << "Looking in " << fname + << " for VRAM info..." << llendl; + rtn = x11_detect_VRAM_kb_fp(fp, ": VideoRAM: "); + fclose(fp); + if (0 == rtn) + { + fp = fopen(fname.c_str(), "r"); + if (fp) + { + rtn = x11_detect_VRAM_kb_fp(fp, ": Memory: "); + fclose(fp); + } + } + } + else + { + llinfos << "Could not open " << fname + << " - skipped." << llendl; + // Try old XFree86 log otherwise + fname = x_log_location; + fname += "XFree86."; + fname += ('0' + display_num); + fname += ".log"; + fp = fopen(fname.c_str(), "r"); + if (fp) + { + llinfos << "Looking in " << fname + << " for VRAM info..." << llendl; + rtn = x11_detect_VRAM_kb_fp(fp, ": VideoRAM: "); + fclose(fp); + if (0 == rtn) + { + fp = fopen(fname.c_str(), "r"); + if (fp) + { + rtn = x11_detect_VRAM_kb_fp(fp, ": Memory: "); + fclose(fp); + } + } + } + else + { + llinfos << "Could not open " << fname + << " - skipped." << llendl; + } + } + return rtn; +#endif // LL_SOLARIS +} +#endif // LL_X11 + +// 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 LLWindowGTK::createContext(S32 x, S32 y, S32 width, S32 height, BOOL fullscreen, BOOL disable_vsync) +{ + 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); + + 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) + { + // 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); + } + +// We need to take care of various environments before we go +// mainstream. See corresponding code in original SDL version, +// including alpha bits. The following fragment shows just a part of +// such cares. +// +// mWindow = SDL_SetVideoMode(width, height, bits, sdlflags | SDL_FULLSCREEN); +// if (!mWindow && bits > 16) +// { +// SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16); +// mWindow = SDL_SetVideoMode(width, height, bits, sdlflags | SDL_FULLSCREEN); +// } +// +// SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, (bits <= 16) ? 16 : 24); +// // We need stencil support for a few (minor) things. +// if (!getenv("LL_GL_NO_STENCIL")) +// { +// SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); +// } + + // 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) + { + llwarns << "gdk_gl_config_new_by_mode failed." << llendl; + setupFailure("Window creation error", "Error", OSMB_OK); + return FALSE; + } + +// if (mFSAASamples > 0) +// { +// SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); +// SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, mFSAASamples); +// } + + gboolean ok = gtk_widget_set_gl_capability(mGLWidget, gl_config, NULL, TRUE, GDK_GL_RGBA_TYPE); + if (!ok) + { + llwarns << "gtk_widget_set_gl_capability failed." << llendl; + setupFailure("Window creation error", "Error", OSMB_OK); + return FALSE; + } + + // Set the application icon and title. + GdkPixbuf *icon_pixbuf = load_bmp_resource("ll_icon.BMP", "icon"); + if (icon_pixbuf) + { + // Second Life icon is keyed with black. + GdkPixbuf * pixbuf = gdk_pixbuf_add_alpha(icon_pixbuf, TRUE, 0, 0, 0); + if (pixbuf) + { + 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()); + + // 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); + + // We need to show (map/realize in fact) the GL widget before + // using GL functions. + gtk_widget_show_all(mWindow); + + // Take care of full screen resolutions. + + 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; + } + + // Detect video memory size. + // 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 + 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 depth_bits, stencil_bits, red_bits, green_bits, blue_bits, alpha_bits; + + 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(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; + + 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 + // relaxed about if we have to. + +#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 + const S32 minimum_color_bits = 32; +#endif + + if (color_bits < minimum_color_bits) + { + close(); + setupFailure( +#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" + " 'svccfg -s svc:/application/x11/x11-server setprop options/default_depth=24'\n" +#else + "Second Life requires True Color (32-bit) to run in a window.\n" + "Please go to Control Panels -> Display -> Settings and\n" + "set the screen to 32-bit color.\n" +#endif + "Alternately, if you choose to run fullscreen, Second Life\n" + "will automatically adjust the screen each time it runs.", + "Error", + OSMB_OK); + return FALSE; + } + +#if 0 // *FIX: we're going to brave it for now... + if (alpha_bits < 8) + { + close(); + setupFailure( + "Second Life is unable to run because it can't get an 8 bit alpha\n" + "channel. Usually this is due to video card driver issues.\n" + "Please make sure you have the latest video card drivers installed.\n" + "Also be sure your monitor is set to True Color (32-bit) in\n" + "Control Panels -> Display -> Settings.\n" + "If you continue to receive this message, contact customer service.", + "Error", + OSMB_OK); + return FALSE; + } +#endif + + g_signal_connect(G_OBJECT(mWindow), "motion-notify-event", G_CALLBACK(handleGtkMotionNotifyEvent_cb), this); + g_signal_connect(G_OBJECT(mWindow), "key-press-event", G_CALLBACK(handleGtkKeyPressEvent_cb), this); + g_signal_connect(G_OBJECT(mWindow), "key-release-event", G_CALLBACK(handleGtkKeyReleaseEvent_cb), this); + g_signal_connect(G_OBJECT(mWindow), "button-press-event", G_CALLBACK(handleGtkButtonPressEvent_cb), this); + g_signal_connect(G_OBJECT(mWindow), "button-release-event", G_CALLBACK(handleGtkButtonReleaseEvent_cb), this); + g_signal_connect(G_OBJECT(mWindow), "scroll-event", G_CALLBACK(handleGtkScrollEvent_cb), this); + g_signal_connect(G_OBJECT(mWindow), "focus-in-event", G_CALLBACK(handleGtkFocusInEvent_cb), this); + g_signal_connect(G_OBJECT(mWindow), "focus-out-event", G_CALLBACK(handleGtkFocusOutEvent_cb), this); + g_signal_connect(G_OBJECT(mWindow), "expose-event", G_CALLBACK(handleGtkExposeEvent_cb), this); + g_signal_connect(G_OBJECT(mWindow), "configure-event", G_CALLBACK(handleGtkConfigureEvent_cb), this); + g_signal_connect(G_OBJECT(mWindow), "window-state-event", G_CALLBACK(handleGtkWindowStateEvent_cb), this); + g_signal_connect(G_OBJECT(mWindow), "delete-event", G_CALLBACK(handleGtkDeleteEvent_cb), this); + + gtk_widget_add_events(mWindow, + GDK_POINTER_MOTION_MASK | GDK_BUTTON_MOTION_MASK | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_SCROLL_MASK | GDK_FOCUS_CHANGE_MASK | + GDK_STRUCTURE_MASK | GDK_EXPOSURE_MASK); + + // This call seems redundant... + gdk_gl_drawable_gl_begin(gl_drawable, gl_context); + + //make sure multisampling is disabled by default + glDisable(GL_MULTISAMPLE_ARB); + +// Key repeat? +// +// // 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 + + 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 +} + +#endif + +void LLWindowGTK::destroyContext() +{ + if (mWindow) + { + restoreGamma(); + if (mFullscreen) + { + leaveFullscreenResolution(); + } + + // 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); + + gtk_widget_destroy(mWindow); + + mGLWidget = NULL; + mWindow = NULL; + } +} + +LLWindowGTK::~LLWindowGTK() +{ + quitCursors(); + destroyIMContext(); + destroyContext(); + + delete [] mSupportedResolutions; + mSupportedResolutions = NULL; + + if (this == gWindowImplementation) + { + gWindowImplementation = NULL; + } +} + +// close() destroys all OS-specific code associated with a window. +// Usually called from LLWindowManager::destroyWindow() +void LLWindowGTK::close() +{ + // Is window is already closed? + // if (!mWindow) + // { + // return; + // } + + // 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. + +// 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 +{ + 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; + } +}; + +#if 0 +// 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; +} +#endif + +// 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 LLWindowGTK::getMonitorResolution(S32 *width, S32 *height) +{ + 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; +} + +// 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 LLWindowGTK::switchMonitorResolution(S32 width, S32 height) +{ +#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 +} + +LLWindow::LLWindowResolution * LLWindowGTK::getSupportedResolutions(S32 &num_resolutions) +{ + // 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; +} + +// Change fullscreen resolution and/or switch between windowed and fullscreen mode. +BOOL LLWindowGTK::switchContext(BOOL fullscreen, const LLCoordScreen &size, BOOL disable_vsync, const LLCoordScreen * const posp) +{ + 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. + } + + mFullscreen = fullscreen; + + // 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 LLWindowGTK::enterFullscreenResolution() +{ + getMonitorResolution(&mOriginalMonitorWidth, &mOriginalMonitorHeight); + switchMonitorResolution(mFullscreenWidth, mFullscreenHeight); +} + +// Make monitor resolution suitable for windowed operation, i.e., the +// original desktop resolution. +void LLWindowGTK::leaveFullscreenResolution() +{ + switchMonitorResolution(mOriginalMonitorWidth, mOriginalMonitorHeight); +} + +BOOL LLWindowGTK::isValid() +{ + return (mWindow != NULL); +} + +void LLWindowGTK::show() +{ + gtk_window_present(GTK_WINDOW(mWindow)); +} + +void LLWindowGTK::hide() +{ + setMouseClipping(FALSE); + gtk_widget_hide(mWindow); +} + +void LLWindowGTK::minimize() +{ + setMouseClipping(FALSE); + if (mFullscreen) + { + leaveFullscreenResolution(); + } + gtk_window_iconify(GTK_WINDOW(mWindow)); +} + +BOOL LLWindowGTK::maximize() +{ + setMouseClipping(FALSE); + gtk_window_maximize(GTK_WINDOW(mWindow)); + return 0 != (gdk_window_get_state(mWindow->window) & GDK_WINDOW_STATE_MAXIMIZED); +} + +void LLWindowGTK::restore() +{ + const GdkWindowState state = gdk_window_get_state(mWindow->window); + if (state & GDK_WINDOW_STATE_ICONIFIED) + { + gtk_window_deiconify(GTK_WINDOW(mWindow)); + if (mFullscreen) + { + enterFullscreenResolution(); + } + } + if (state & GDK_WINDOW_STATE_MAXIMIZED) + { + setMouseClipping(FALSE); + gtk_window_unmaximize(GTK_WINDOW(mWindow)); + } +} + +BOOL LLWindowGTK::getVisible() +{ + return mWindow && gdk_window_is_visible(mWindow->window); +} + +BOOL LLWindowGTK::getMinimized() +{ + return mWindow && 0 != (gdk_window_get_state(mWindow->window) & GDK_WINDOW_STATE_ICONIFIED); +} + +BOOL LLWindowGTK::getMaximized() +{ + return mWindow && 0 != (gdk_window_get_state(mWindow->window) & GDK_WINDOW_STATE_MAXIMIZED); +} + +BOOL LLWindowGTK::getPosition(LLCoordScreen *position) +{ + if (mWindow) + { + position->mX = mWindow->allocation.x; + position->mY = mWindow->allocation.y; + return TRUE; + } + + return FALSE; +} + +BOOL LLWindowGTK::getSize(LLCoordScreen *size) +{ + if (mWindow) + { + size->mX = mWindow->allocation.width; + size->mY = mWindow->allocation.height; + return TRUE; + } + + return FALSE; +} + +BOOL LLWindowGTK::getSize(LLCoordWindow *size) +{ + // 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 = mGLWidget->allocation.width; + size->mY = mGLWidget->allocation.height; + return TRUE; + } + + return FALSE; +} + +BOOL LLWindowGTK::setPosition(const LLCoordScreen position) +{ + if(mWindow) + { + gtk_window_move(GTK_WINDOW(mWindow), position.mX, position.mY); + } + + return TRUE; +} + +BOOL LLWindowGTK::setSize(const LLCoordScreen size) +{ + if(mWindow) + { + gtk_window_resize(GTK_WINDOW(mWindow), size.mX, size.mY); + } + + return TRUE; +} + +void LLWindowGTK::swapBuffers() +{ + if (mWindow) + { + 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); + } + } +} + +U32 LLWindowGTK::getFSAASamples() +{ + return mFSAASamples; +} + +void LLWindowGTK::setFSAASamples(const U32 samples) +{ + mFSAASamples = samples; +} + +F32 LLWindowGTK::getGamma() +{ + return mGamma; +} + +BOOL LLWindowGTK::restoreGamma() +{ + // We should save initial gamma ramp tables and restore them here. + // FIXME. + return setGamma(1); +} + +// Calculate a 16 bit gamma corrected level for a given intensity value. +static inline U16 ll_gamma(F32 value, F32 gamma) +{ + return llround(powf(value, gamma) * 65535); +} + +// 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 LLWindowGTK::initializeGamma() +{ + 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"; + +static void report_gamma_adjustment_method(const char * const method) +{ + static const char * previous = NULL; + if (method != previous) + { + llinfos << "Using " << method << " for gamma adjustment" << llendl; + previous = method; + } +} + +BOOL LLWindowGTK::setGamma(F32 gamma) +{ + // Let the gamma value be in a reasonable range. + mGamma = (gamma == 0 ? 1.f : llclamp(gamma, 0.1f, 10.f)); + + // 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) + { + 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; + } + +#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); + + 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 + +#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 + + // We couldn't change gamma. + return FALSE; +} + +// Constrains the mouse to the window. +void LLWindowGTK::setMouseClipping( BOOL b ) +{ + // 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. +} + +BOOL LLWindowGTK::setCursorPosition(const LLCoordWindow position_window) +{ +#if LL_OLD_FASHIONED_GTK +#if LL_X11 + Display *x11_display = GDK_WINDOW_XDISPLAY(gtk_widget_get_display(mWindow)); + Window x11_window = GDK_WINDOW_XID(mWindow->window); + XWarpPointer(x11_display, None, x11_window, 0, 0, 0, 0, position_window.mX, position_window.mY); +#endif +#else + LLCoordScreen position_screen; + if (!convertCoords(position_window, &position_screen)) + { + return 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); +#endif + return TRUE; +} + +BOOL LLWindowGTK::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 LLWindowGTK::getNativeAspectRatio() +{ +#if 0 + // RN: this hack presumes that the largest supported resolution is monitor-limited + // and that pixels in that mode are square, therefore defining the native aspect ratio + // of the monitor...this seems to work to a close approximation for most CRTs/LCDs + S32 num_resolutions; + LLWindowResolution* resolutions = getSupportedResolutions(num_resolutions); + + return ((F32)resolutions[num_resolutions - 1].mWidth / (F32)resolutions[num_resolutions - 1].mHeight); + //rn: AC +#endif + + // MBW -- there are a couple of bad assumptions here. One is that the display list won't include + // ridiculous resolutions nobody would ever use. The other is that the list is in order. + + // New assumptions: + // - pixels are square (the only reasonable choice, really) + // - The user runs their display at a native resolution, so the resolution of the display + // when the app is launched has an aspect ratio that matches the monitor. + + //RN: actually, the assumption that there are no ridiculous resolutions (above the display's native capabilities) has + // been born out in my experience. + // Pixels are often not square (just ask the people who run their LCDs at 1024x768 or 800x600 when running fullscreen, like me) + // The ordering of display list is a blind assumption though, so we should check for max values + // Things might be different on the Mac though, so I'll defer to MBW + + // 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 (F32) mOriginalMonitorWidth / (F32) mOriginalMonitorHeight; +} + +F32 LLWindowGTK::getPixelAspectRatio() +{ + F32 pixel_aspect = 1.f; + if (mFullscreen) + { + LLCoordScreen screen_size; + if (getSize(&screen_size)) + { + pixel_aspect = getNativeAspectRatio() * (F32)screen_size.mY / (F32)screen_size.mX; + } + } + + return pixel_aspect; +} + + +// 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. + +void LLWindowGTK::beforeDialog() +{ + // Nothing special to do. +} + +void LLWindowGTK::afterDialog() +{ + // Nothing special to do. +} + + +void LLWindowGTK::flashIcon(F32 seconds) +{ +#if !LL_OLD_FASHIONED_GTK + F32 remaining_time = mFlashTimer.getRemainingTimeF32(); + if (remaining_time < seconds) + remaining_time = seconds; + mFlashTimer.reset(); + mFlashTimer.setTimerExpirySec(remaining_time); + gtk_window_set_urgency_hint(GTK_WINDOW(mWindow), TRUE); + mFlashing = TRUE; +#endif +} + + +/************************************************/ + +BOOL LLWindowGTK::isClipboardTextAvailable() +{ + GtkClipboard * const clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); + return gtk_clipboard_wait_is_text_available(clipboard); +} + +BOOL LLWindowGTK::pasteTextFromClipboard(LLWString &text) +{ + GtkClipboard * const clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); + gchar * const data = gtk_clipboard_wait_for_text(clipboard); + if (data) + { + text = LLWString(utf8str_to_wstring(data)); + g_free(data); + return TRUE; + } + return FALSE; +} + +BOOL LLWindowGTK::copyTextToClipboard(const LLWString &text) +{ + const std::string utf8 = wstring_to_utf8str(text); + GtkClipboard * const clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); + gtk_clipboard_set_text(clipboard, utf8.c_str(), utf8.length()); + return TRUE; +} + +/***********************************************/ + +BOOL LLWindowGTK::convertCoords(LLCoordGL from, LLCoordWindow *to) +{ + if (!to) + { + return FALSE; + } + + to->mX = from.mX; + to->mY = mGLWidget->allocation.height - from.mY - 1; + + return TRUE; +} + +BOOL LLWindowGTK::convertCoords(LLCoordWindow from, LLCoordGL* to) +{ + if (!to) + { + return FALSE; + } + + to->mX = from.mX; + to->mY = mGLWidget->allocation.height - from.mY - 1; + + return TRUE; +} + +BOOL LLWindowGTK::convertCoords(LLCoordScreen from, LLCoordWindow* to) +{ + if (!to) + { + return FALSE; + } + + gint x, y; + gdk_window_get_position(mWindow->window, &x, &y); + to->mX = from.mX - x; + to->mY = from.mY - y; + + return TRUE; +} + +BOOL LLWindowGTK::convertCoords(LLCoordWindow from, LLCoordScreen *to) +{ + if (!to) + { + return FALSE; + } + + gint x, y; + gdk_window_get_position(mWindow->window, &x, &y); + to->mX = from.mX + x; + to->mY = from.mY + y; + + return TRUE; +} + +BOOL LLWindowGTK::convertCoords(LLCoordScreen from, LLCoordGL *to) +{ + LLCoordWindow window_coord; + + return(convertCoords(from, &window_coord) && convertCoords(window_coord, to)); +} + +BOOL LLWindowGTK::convertCoords(LLCoordGL from, LLCoordScreen *to) +{ + LLCoordWindow window_coord; + + return(convertCoords(from, &window_coord) && convertCoords(window_coord, to)); +} + +void LLWindowGTK::setupFailure(const std::string& text, const std::string& caption, U32 type) +{ + destroyContext(); + + OSMessageBox(text, caption, type); +} + +void LLWindowGTK::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) + { + gdk_pointer_grab(mWindow->window, TRUE, (GdkEventMask)0, NULL, NULL, GDK_CURRENT_TIME); + } + else + { + 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 immediately 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 (but sync operation) here for execution after + // both grabbing and ungrabbing. Opinions? -- Alissa + + gdk_display_flush(gtk_widget_get_display(mWindow)); +} + +U32 LLWindowGTK::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 + long as LALT is held down, so the window manager can't easily + see what's happening. Tested successfully with Metacity. + And... do the same with CTRL, for other darn WMs. We don't + care about other metakeys as SL doesn't use them with dragging + (for now). */ + + /* We maintain a bitmap of critical keys which are up and down + instead of simply key-counting, because SDL sometimes reports + misbalanced keyup/keydown event pairs to us for whatever reason. */ + + U32 mask = 0; + switch (keysym) + { + 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; + } + + /* 0 means we don't need to mousegrab, otherwise grab. */ + return mGrabbyKeyFlags; +} + +// We have several issues regarding event pumping. +// +// The first issue is that we are now a native GTK appliation and our +// event pump is shared with others (e.g., llmozlib.) I'm not sure +// what is the best balance between event processing and frame +// updating... +// +// The second issue is on the locale handling. 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. +// +// We need to revisit here. + +// virtual +void LLWindowGTK::processMiscNativeEvents() +{ +#if LL_GTK && (LL_LLMOZLIB_ENABLED || LL_DBUS_ENABLED) + // Pump GTK events to avoid starvation for: + // * Embedded Gecko + // * DBUS servicing + // * Anything else which quietly hooks into the default glib/GTK loop + 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) + static std::string saved_locale; + saved_locale = ll_safe_string(setlocale(LC_ALL, NULL)); + + // Pump until we've nothing left to do or passed 1/15th of a + // second pumping for this frame. + static LLTimer pump_timer; + pump_timer.reset(); + pump_timer.setTimerExpirySec(1.0f / 15.0f); + do { + // Always do at least one non-blocking pump + gtk_main_iteration_do(0); + } while (gtk_events_pending() && + !pump_timer.hasExpired()); + setlocale(LC_ALL, saved_locale.c_str() ); + } +#endif // LL_GTK && (LL_LLMOZLIB_ENABLED || LL_DBUS_ENABLED) +} + +void LLWindowGTK::gatherInput() +{ + // 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); + } + +// std::string saved_locale = setlocale(LC_ALL, NULL); + + // 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(); + } + +// setlocale(LC_ALL, saved_locale.c_str() ); + + // This is a good time to stop flashing the icon if our mFlashTimer has + // expired. +#if !LL_OLD_FASHIONED_GTK + if (mFlashing && mFlashTimer.hasExpired()) + { + gtk_window_set_urgency_hint(GTK_WINDOW(mWindow), FALSE); + mFlashing = FALSE; + } +#endif +} + +gboolean LLWindowGTK::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; +} + +gboolean LLWindowGTK::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; + } + } + + // 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. (Because the + // underlying input method sees and processes the IM keys + // before we receive events. gtk_im_context_filter_keypress() + // on those platforms are just a fake to mediate with GTK's + // X11 biased API. + if (mPreeditor) + { + if (gtk_im_context_filter_keypress(mIMContext, event)) + { + llinfos << "KeyPress filtered: " << gdk_keyval_name(event->keyval) << " (" << event->keyval << ")" << llendl; + return TRUE; + } + } + + // 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); + } + + // Take care of unicode from an unfiltered key stroke. + // gdk_keyval_to_unicode() doesn't consider Ctrl/Alt keys, and it + // returns 'a' for Ctrl-A for example. If we produce any + // "unicode" for key strokes when ctrl/alt pressed, it confuses + // the viewer application. So, we invoke unicode handler only if + // control and alt are not pressed. + const MASK mask = gKeyboard->currentMask(FALSE); + if (!(mask & (MASK_CONTROL | MASK_ALT))) + { + const guint32 unicode = gdk_keyval_to_unicode(event->keyval); + if (unicode) + { + mCallbacks->handleUnicodeChar((llwchar)unicode, mask); + } + else if (GDK_Return == event->keyval || GDK_KP_Enter == event->keyval) + { + // This is a _counter_hack_ against a hack in + // LLViewerWindow::handleTranslatedKeyDown(KEY, MASK, + // BOOL) and LLViewerWindow::handleUnicodeChar(llwchar, + // MASK). The hack in LLViewerWindow presumes that both + // RETURN and ENTER keys generate Unicode U+000D ('\r') + // when pressed alone, although gdk_keyval_to_unicode() + // doesn't. We need to supply ones by ourselves. We need + // to do the same for Shift-ENTER and CAPS-ENTER, because + // it is usual a user hits ENTER key before releasing + // Shift/CAPS when the last character before the ENTER was + // an upper case. We shouldn't, however, for Ctrl-ENTER + // or Alt-ENTER, because they are purely a command and + // nothing to do with text input. + mCallbacks->handleUnicodeChar((llwchar)'\r', mask); + } + } + + return TRUE; +} + +gboolean LLWindowGTK::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)) + { + llinfos << "KeyRelease filtered: " << gdk_keyval_name(event->keyval) << " (" << event->keyval << ")" << llendl; + return TRUE; + } + } + + if (checkGrabbyKeys(event->keyval, FALSE) == 0) + { + reallyCaptureInput(FALSE); + } + + // 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); + + gKeyboard->handleKeyUp(event->keyval, event->state); + + return TRUE; +} + +gboolean LLWindowGTK::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. + + // 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... + + // 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; + } + + // 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); + + // Flush the remaining language text input if any. + interruptLanguageTextInput(); + + switch (event->button) + { + case 1: // The left button + { + // See if this is a second click of a double-clicking. + BOOL double_click = FALSE; + GdkEvent *next_event = gdk_event_peek(); + if (next_event) + { + double_click = (next_event->type == GDK_2BUTTON_PRESS); + gdk_event_free(next_event); + } + if (double_click) + { + mCallbacks->handleDoubleClick(this, gl_coord, mask); + } + else + { + mCallbacks->handleMouseDown(this, gl_coord, mask); + } + } + break; + + case 2: // The middle button + mCallbacks->handleMiddleMouseDown(this, gl_coord, mask); + break; + + case 3: // The right button + mCallbacks->handleRightMouseDown(this, gl_coord, mask); + break; + + default: + llinfos << "Ignoring mouse button #" << event->button << " down" << llendl; + break; + } + + return TRUE; +} + +gboolean LLWindowGTK::handleGtkButtonReleaseEvent(GdkEventButton *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); + + switch (event->button) + { + case 1: // The left button + mCallbacks->handleMouseUp(this, gl_coord, mask); + break; + + 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 LLWindowGTK::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; +} + +gboolean LLWindowGTK::handleGtkFocusInEvent(GdkEventFocus *event) +{ + if (!mWindowIsFocused) + { + mCallbacks->handleFocus(this); + mWindowIsFocused = TRUE; + mPrimaryClipboardNeedsReset = TRUE; + updateIMFocus(); + } + return FALSE; +} + +gboolean LLWindowGTK::handleGtkFocusOutEvent(GdkEventFocus *event) +{ + // 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) + { + interruptLanguageTextInput(); + mWindowIsFocused = FALSE; + updateIMFocus(); + mCallbacks->handleFocusLost(this); + } + return FALSE; +} + +gboolean LLWindowGTK::handleGtkExposeEvent(GdkEventExpose *event) +{ + mCallbacks->handlePaint(this, event->area.x, event->area.y, event->area.width, event->area.height); + return FALSE; +} + +gboolean LLWindowGTK::handleGtkConfigureEvent(GdkEventConfigure *event) +{ + mCallbacks->handleResize(this, event->width, event->height); + return FALSE; +} + +gboolean LLWindowGTK::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 LLWindowGTK::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 LLWindowGTK::setCursor(ECursorType cursor_type) +{ + if (ATIbug) { + // cursor-updating is very flaky when this bug is + // present; do nothing. + return; + } + + 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 LLWindowGTK::getCursor() +{ + return mCurrentCursor; +} + +void LLWindowGTK::initCursors() +{ + // Blank the cursor pointer array for those we may miss. + for (int i=0; iwindow, mHiddenCursor); + } +} + +void LLWindowGTK::showCursor() +{ + if(mCursorHidden) + { + mCursorHidden = FALSE; + mHideCursorPermanent = FALSE; + gdk_window_set_cursor(mWindow->window, mCursors[mCurrentCursor]); + } +} + +void LLWindowGTK::showCursorFromMouseMove() +{ + if (!mHideCursorPermanent) + { + showCursor(); + } +} + +void LLWindowGTK::hideCursorUntilMouseMove() +{ + if (!mHideCursorPermanent) + { + hideCursor(); + // hideCursor turns mHideCursorPermanent TRUE! + mHideCursorPermanent = FALSE; + } +} + +// +// LLSplashScreenGTK - I don't think we'll bother to implement this; it's +// fairly obsolete at this point. +// +LLSplashScreenGTK::LLSplashScreenGTK() +{ +} + +LLSplashScreenGTK::~LLSplashScreenGTK() +{ +} + +void LLSplashScreenGTK::showImpl() +{ +} + +void LLSplashScreenGTK::updateImpl(const std::string& mesg) +{ +} + +void LLSplashScreenGTK::hideImpl() +{ +} + +static void response_callback (GtkDialog *dialog, + gint arg1, + gpointer user_data) +{ + gint *response = (gint*)user_data; + *response = arg1; + gtk_widget_destroy(GTK_WIDGET(dialog)); + gtk_main_quit(); +} + +S32 OSMessageBoxGTK(const std::string& text, const std::string& caption, U32 type) +{ + S32 rtn = OSBTN_CANCEL; + +// ll_try_gtk_init(); + + GtkWidget *win = NULL; + GtkDialogFlags flags = GTK_DIALOG_MODAL; + GtkMessageType messagetype; + GtkButtonsType buttons; + switch (type) + { + default: + case OSMB_OK: + messagetype = GTK_MESSAGE_WARNING; + buttons = GTK_BUTTONS_OK; + break; + case OSMB_OKCANCEL: + messagetype = GTK_MESSAGE_QUESTION; + buttons = GTK_BUTTONS_OK_CANCEL; + break; + case OSMB_YESNO: + messagetype = GTK_MESSAGE_QUESTION; + buttons = GTK_BUTTONS_YES_NO; + break; + } + GtkWindow *parent = gWindowImplementation ? GTK_WINDOW(gWindowImplementation->getGtkWindow()) : NULL; + win = gtk_message_dialog_new(parent, flags, messagetype, buttons, text.c_str()); + + 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); + if (!caption.empty()) + { + gtk_window_set_title(GTK_WINDOW(win), caption.c_str()); + } + + 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(); + + 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; + case GTK_RESPONSE_APPLY: rtn = OSBTN_OK; break; + case GTK_RESPONSE_NONE: + case GTK_RESPONSE_CANCEL: + case GTK_RESPONSE_CLOSE: + case GTK_RESPONSE_DELETE_EVENT: + default: rtn = OSBTN_CANCEL; + } + + return rtn; +} + +static void color_changed_callback(GtkWidget *widget, + gpointer user_data) +{ + GtkColorSelection *colorsel = GTK_COLOR_SELECTION(widget); + GdkColor *colorp = (GdkColor*)user_data; + + gtk_color_selection_get_current_color(colorsel, colorp); +} + +BOOL LLWindowGTK::dialog_color_picker(F32 *r, F32 *g, F32 *b) +{ + BOOL rtn = FALSE; + + beforeDialog(); + + 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) ) + { + *r = color.red / 65535.0f; + *g = color.green / 65535.0f; + *b = color.blue / 65535.0f; + rtn = TRUE; + } + + afterDialog(); + + return rtn; +} + +// I'm not sure which swapn code is better. + +#if 0 + +#if LL_LINUX || LL_SOLARIS +// extracted from spawnWebBrowser for clarity and to eliminate +// compiler confusion regarding close(int fd) vs. LLWindow::close() +void exec_cmd(const std::string& cmd, const std::string& arg) +{ + char* const argv[] = {(char*)cmd.c_str(), (char*)arg.c_str(), NULL}; + 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) + { + // parent - wait for child to die + int childExitStatus; + waitpid(pid, &childExitStatus, 0); + } else { + llwarns << "fork failure." << llendl; + } + } +} +#endif + +// Open a URL with the user's default web browser. +// Must begin with protocol identifier. +void LLWindowGTK::spawnWebBrowser(const std::string& escaped_url) +{ + llinfos << "spawn_web_browser: " << escaped_url << llendl; + +#if LL_LINUX || LL_SOLARIS +# if LL_X11 + if (mSDL_Display) + { + maybe_lock_display(); + // Just in case - before forking. + XSync(mSDL_Display, False); + maybe_unlock_display(); + } +# endif // LL_X11 + + std::string cmd, arg; + cmd = gDirUtilp->getAppRODataDir().c_str(); + cmd += gDirUtilp->getDirDelimiter().c_str(); + cmd += "launch_url.sh"; + arg = escaped_url; + exec_cmd(cmd, arg); +#endif // LL_LINUX || LL_SOLARIS + + llinfos << "spawn_web_browser returning." << llendl; +} + +#else + +// Open a URL with the user's default web browser. +// Must begin with protocol identifier. +void LLWindowGTK::spawnWebBrowser(const std::string& escaped_url) +{ + llinfos << "spawn_web_browser: " << escaped_url << llendl; + + // Just in case - before forking. + gdk_flush(); + + 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.c_str()); + argv[2] = NULL; + + 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); + } +} + +#endif + +void *LLWindowGTK::getPlatformWindow() +{ + return mWindow->window; +} + +void *LLWindowGTK::getMediaWindow() +{ + // 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, unlike Windows, 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, so we don't keep the pointer to the + // window. + +#if LL_GTK && LL_LLMOZLIB_ENABLED + if (LLWindowGTK::ll_try_gtk_init()) + { + GtkWidget *owin = gtk_window_new(GTK_WINDOW_POPUP); + // Why a layout widget? A MozContainer would be ideal, but + // it involves exposing Mozilla headers to mozlib-using apps. + // A layout widget with a GtkWindow parent has the desired + // properties of being plain GTK, having a window, and being + // derived from a GtkContainer. + GtkWidget *rtnw = gtk_layout_new(NULL, NULL); + gtk_container_add(GTK_CONTAINER(owin), rtnw); + gtk_widget_realize(rtnw); + GTK_WIDGET_UNSET_FLAGS(GTK_WIDGET(rtnw), GTK_NO_WINDOW); + return rtnw; + } +#endif // LL_GTK && LL_LLMOZLIB_ENABLED + // Unixoid mozilla really needs GTK. + return NULL; +} + + +void LLWindowGTK::bringToFront() +{ + gtk_window_present(GTK_WINDOW(mWindow)); +} + +// Handling of input methods through GTK im module. + +void LLWindowGTK::allowLanguageTextInput(LLPreeditor *preeditor, BOOL b) +{ + if (preeditor == NULL && b) + { + llwarns << "allowLanguageTextInput(NULL, TRUE) ... This call is prohibited!" << llendl; + return; + } + + if (preeditor != mPreeditor && !b) + { + return; + } + + // 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(); + } +} + +// Reset the IM context and discard any remnant preedits both from UI +// (preeditor) and from our own internal storage. + +void LLWindowGTK::interruptLanguageTextInput() +{ + 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); + } +} + +void LLWindowGTK::updateLanguageTextInputArea() +{ + 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); +} + +void LLWindowGTK::handleGtkCommitSignal(const gchar *committed_text) +{ + if (!committed_text || !*committed_text) + { + return; + } + + 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); + } + 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; + + // 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++) + { + mCallbacks->handleUnicodeChar(*i, mask); + } + } +} + +void LLWindowGTK::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 LLWindowGTK::handleGtkRetrieveSurroundingSignal() +{ + if (!mPreeditor) + { + llwarns << "retrieve-srrounding signal with no preeditor." << llendl; + return; + } + + S32 preedit_start, preedit_length; + mPreeditor->getPreeditRange(&preedit_start, &preedit_length); + const S32 preedit_end = preedit_start + preedit_length; + const LLWString & text = mPreeditor->getWText(); + + // Find a suitable _surrounding_context_. + S32 end = text.find((llwchar) '\n', preedit_end); + if (end == LLWString::npos) + { + end = text.length(); + } + 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); +} + +gboolean LLWindowGTK::handleGtkDeleteSurroundingSignal(gint offset, gint n_chars) +{ + if (!mPreeditor) + { + llwarns << "delete-surrounding signal with no preeditor." << llendl; + return FALSE; + } + + // 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; +} + +// 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 LLWindowGTK::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 LLWindowGTK::destroyIMContext() +{ + gtk_im_context_set_client_window(mIMContext, NULL); + g_object_unref(mIMContext); +} + +void LLWindowGTK::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; + } + } +} + +//static +std::string LLWindowGTK::getFontListSans() +{ + // Use libfontconfig to find us a nice ordered list of fallback fonts + // specific to this system. + std::string final_fallback("/usr/share/fonts/truetype/kochi/kochi-gothic.ttf"); + // Our 'ideal' font properties which define the sorting results. + // slant=0 means Roman, index=0 means the first face in a font file + // (the one we actually use), weight=80 means medium weight, + // spacing=0 means proportional spacing. + std::string sort_order("slant=0:index=0:weight=80:spacing=0"); + // elide_unicode_coverage removes fonts from the list whose unicode + // range is covered by fonts earlier in the list. This usually + // removes ~90% of the fonts as redundant (which is great because + // the font list can be huge), but might unnecessarily reduce the + // renderable range if for some reason our FreeType actually fails + // to use some of the fonts we want it to. + const bool elide_unicode_coverage = true; + std::string rtn; + FcFontSet *fs = NULL; + FcPattern *sortpat = NULL; + int font_count = 0; + + llinfos << "Getting system font list from FontConfig..." << llendl; + + // If the user has a system-wide language preference, then favor + // fonts from that language group. This doesn't affect the types + // of languages that can be displayed, but ensures that their + // preferred language is rendered from a single consistent font where + // possible. + FL_Locale *locale = NULL; + FL_Success success = FL_FindLocale(&locale, FL_MESSAGES); + if (success != 0) + { + if (success >= 2 && locale->lang) // confident! + { + LL_INFOS("AppInit") << "Language " << locale->lang << LL_ENDL; + LL_INFOS("AppInit") << "Location " << locale->country << LL_ENDL; + LL_INFOS("AppInit") << "Variant " << locale->variant << LL_ENDL; + + llinfos << "Preferring fonts of language: " + << locale->lang + << llendl; + sort_order = "lang=" + std::string(locale->lang) + ":" + + sort_order; + } + } + FL_FreeLocale(&locale); + + if (!FcInit()) + { + llwarns << "FontConfig failed to initialize." << llendl; + return final_fallback; + } + + sortpat = FcNameParse((FcChar8*) sort_order.c_str()); + if (sortpat) + { + // Sort the list of system fonts from most-to-least-desirable. + fs = FcFontSort(NULL, sortpat, elide_unicode_coverage, + NULL, NULL); + FcPatternDestroy(sortpat); + } + + if (fs) + { + // Get the full pathnames to the fonts, where available, + // which is what we really want. + int i; + for (i=0; infont; ++i) + { + FcChar8 *filename; + if (FcResultMatch == FcPatternGetString(fs->fonts[i], + FC_FILE, 0, + &filename) + && filename) + { + rtn += std::string((const char*)filename)+";"; + ++font_count; + } + } + FcFontSetDestroy (fs); + } + + lldebugs << "Using font list: " << rtn << llendl; + llinfos << "Using " << font_count << " system font(s)." << llendl; + + return rtn + final_fallback; +}