summaryrefslogtreecommitdiff
path: root/indra/llwindow/llwindowmacosx.cpp
diff options
context:
space:
mode:
authorJames Cook <james@lindenlab.com>2007-01-02 08:33:20 +0000
committerJames Cook <james@lindenlab.com>2007-01-02 08:33:20 +0000
commit420b91db29485df39fd6e724e782c449158811cb (patch)
treeb471a94563af914d3ed3edd3e856d21cb1b69945 /indra/llwindow/llwindowmacosx.cpp
Print done when done.
Diffstat (limited to 'indra/llwindow/llwindowmacosx.cpp')
-rw-r--r--indra/llwindow/llwindowmacosx.cpp2904
1 files changed, 2904 insertions, 0 deletions
diff --git a/indra/llwindow/llwindowmacosx.cpp b/indra/llwindow/llwindowmacosx.cpp
new file mode 100644
index 0000000000..4d75a30a8e
--- /dev/null
+++ b/indra/llwindow/llwindowmacosx.cpp
@@ -0,0 +1,2904 @@
+/**
+ * @file llwindowmacosx.cpp
+ * @brief Platform-dependent implementation of llwindow
+ *
+ * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc.
+ * $License$
+ */
+
+#if LL_DARWIN
+
+#include "linden_common.h"
+
+#include <Carbon/Carbon.h>
+
+#include "llwindowmacosx.h"
+#include "llkeyboardmacosx.h"
+#include "llerror.h"
+#include "llgl.h"
+#include "llstring.h"
+#include "lldir.h"
+
+#include "llglheaders.h"
+
+#include "indra_constants.h"
+
+#include "llwindowmacosx-objc.h"
+
+extern BOOL gDebugWindowProc;
+
+// culled from winuser.h
+//const S32 WHEEL_DELTA = 120; /* Value for rolling one detent */
+// On the Mac, the scroll wheel reports a delta of 1 for each detent.
+// There's also acceleration for faster scrolling, based on a slider in the system preferences.
+const S32 WHEEL_DELTA = 1; /* Value for rolling one detent */
+const S32 BITS_PER_PIXEL = 32;
+const S32 MAX_NUM_RESOLUTIONS = 32;
+
+
+//
+// LLWindowMacOSX
+//
+
+// Cross-platform bits:
+
+void show_window_creation_error(const char* title)
+{
+ llwarns << title << llendl;
+ shell_open( "help/window_creation_error.html");
+ /*
+ OSMessageBox(
+ "Second Life is unable to run because it can't set up your display.\n"
+ "We need to be able to make a 32-bit color window at 1024x768, with\n"
+ "an 8 bit alpha channel.\n"
+ "\n"
+ "First, be sure your monitor is set to True Color (32-bit) in\n"
+ "Start -> Control Panels -> Display -> Settings.\n"
+ "\n"
+ "Otherwise, this may be due to video card driver issues.\n"
+ "Please make sure you have the latest video card drivers installed.\n"
+ "ATI drivers are available at http://www.ati.com/\n"
+ "nVidia drivers are available at http://www.nvidia.com/\n"
+ "\n"
+ "If you continue to receive this message, contact customer service.",
+ title,
+ OSMB_OK);
+ */
+}
+
+BOOL check_for_card(const char* RENDERER, const char* bad_card)
+{
+ if (!strnicmp(RENDERER, bad_card, strlen(bad_card)))
+ {
+ char buffer[1024];
+ sprintf(buffer,
+ "Your video card appears to be a %s, which Second Life does not support.\n"
+ "\n"
+ "Second Life requires a video card with 32 Mb of memory or more, as well as\n"
+ "multitexture support. We explicitly support nVidia GeForce 2 or better, \n"
+ "and ATI Radeon 8500 or better.\n"
+ "\n"
+ "If you own a supported card and continue to receive this message, try \n"
+ "updating to the latest video card drivers. Otherwise look in the\n"
+ "secondlife.com support section or e-mail technical support\n"
+ "\n"
+ "You can try to run Second Life, but it will probably crash or run\n"
+ "very slowly. Try anyway?",
+ bad_card);
+ S32 button = OSMessageBox(buffer, "Unsupported video card", OSMB_YESNO);
+ if (OSBTN_YES == button)
+ {
+ return FALSE;
+ }
+ else
+ {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+
+
+// Switch to determine whether we capture all displays, or just the main one.
+// We may want to base this on the setting of _DEBUG...
+
+#define CAPTURE_ALL_DISPLAYS 0
+static double getDictDouble (CFDictionaryRef refDict, CFStringRef key);
+static long getDictLong (CFDictionaryRef refDict, CFStringRef key);
+
+
+
+
+// CarbonEvents we're interested in.
+static EventTypeSpec WindowHandlerEventList[] =
+{
+ // Window-related events
+ // { kEventClassWindow, kEventWindowCollapsing },
+ // { kEventClassWindow, kEventWindowCollapsed },
+ // { kEventClassWindow, kEventWindowShown },
+ { kEventClassWindow, kEventWindowActivated },
+ { kEventClassWindow, kEventWindowDeactivated },
+ { kEventClassWindow, kEventWindowShown },
+ { kEventClassWindow, kEventWindowHidden },
+ { kEventClassWindow, kEventWindowCollapsed },
+ { kEventClassWindow, kEventWindowExpanded },
+ { kEventClassWindow, kEventWindowGetClickActivation },
+ { kEventClassWindow, kEventWindowClose },
+ { kEventClassWindow, kEventWindowBoundsChanging },
+ { kEventClassWindow, kEventWindowBoundsChanged },
+ // { kEventClassWindow, kEventWindowZoomed },
+ // { kEventClassWindow, kEventWindowDrawContent },
+
+ // Mouse events
+ { kEventClassMouse, kEventMouseDown },
+ { kEventClassMouse, kEventMouseUp },
+ { kEventClassMouse, kEventMouseDragged },
+ { kEventClassMouse, kEventMouseWheelMoved },
+ { kEventClassMouse, kEventMouseMoved },
+
+ // Keyboard events
+ // No longer handle raw key down events directly.
+ // When text input events come in, extract the raw key events from them and process at that point.
+ // This allows input methods to eat keystrokes the way they're supposed to.
+// { kEventClassKeyboard, kEventRawKeyDown },
+// { kEventClassKeyboard, kEventRawKeyRepeat },
+ { kEventClassKeyboard, kEventRawKeyUp },
+ { kEventClassKeyboard, kEventRawKeyModifiersChanged },
+
+ // Text input events
+ { kEventClassTextInput, kEventTextInputUnicodeForKeyEvent }
+
+};
+
+static EventTypeSpec GlobalHandlerEventList[] =
+{
+ // Mouse events
+ { kEventClassMouse, kEventMouseDown },
+ { kEventClassMouse, kEventMouseUp },
+ { kEventClassMouse, kEventMouseDragged },
+ { kEventClassMouse, kEventMouseWheelMoved },
+ { kEventClassMouse, kEventMouseMoved },
+
+ // Keyboard events
+ // No longer handle raw key down events directly.
+ // When text input events come in, extract the raw key events from them and process at that point.
+ // This allows input methods to eat keystrokes the way they're supposed to.
+// { kEventClassKeyboard, kEventRawKeyDown },
+// { kEventClassKeyboard, kEventRawKeyRepeat },
+ { kEventClassKeyboard, kEventRawKeyUp },
+ { kEventClassKeyboard, kEventRawKeyModifiersChanged },
+
+ // Text input events
+ { kEventClassTextInput, kEventTextInputUnicodeForKeyEvent }
+};
+
+static EventTypeSpec CommandHandlerEventList[] =
+{
+ { kEventClassCommand, kEventCommandProcess }
+};
+
+// MBW -- HACK ALERT
+// On the Mac, to put up an OS dialog in full screen mode, we must first switch OUT of full screen mode.
+// The proper way to do this is to bracket the dialog with calls to beforeDialog() and afterDialog(), but these
+// require a pointer to the LLWindowMacOSX object. Stash it here and maintain in the constructor and destructor.
+// This assumes that there will be only one object of this class at any time. Hopefully this is true.
+static LLWindowMacOSX *gWindowImplementation = NULL;
+
+
+
+LLWindowMacOSX::LLWindowMacOSX(char *title, char *name, S32 x, S32 y, S32 width,
+ S32 height, U32 flags,
+ BOOL fullscreen, BOOL clearBg,
+ BOOL disable_vsync, BOOL use_gl,
+ BOOL ignore_pixel_depth)
+ : LLWindow(fullscreen, flags)
+{
+ // Voodoo for calling cocoa from carbon (see llwindowmacosx-objc.mm).
+ setupCocoa();
+
+ // Initialize the keyboard
+ gKeyboard = new LLKeyboardMacOSX();
+
+ // Ignore use_gl for now, only used for drones on PC
+ mWindow = NULL;
+ mContext = NULL;
+ mPixelFormat = NULL;
+ mDisplay = CGMainDisplayID();
+ mOldDisplayMode = NULL;
+ mTimer = NULL;
+ mSimulatedRightClick = FALSE;
+ mLastModifiers = 0;
+ mHandsOffEvents = FALSE;
+ mCursorDecoupled = FALSE;
+ mCursorLastEventDeltaX = 0;
+ mCursorLastEventDeltaY = 0;
+ mCursorIgnoreNextDelta = FALSE;
+ mNeedsResize = FALSE;
+ mOverrideAspectRatio = 0.f;
+ mMinimized = FALSE;
+
+ // For reasons that aren't clear to me, LLTimers seem to be created in the "started" state.
+ // Since the started state of this one is used to track whether the NMRec has been installed, it wants to start out in the "stopped" state.
+ mBounceTimer.stop();
+
+ // Get the original aspect ratio of the main device.
+ mOriginalAspectRatio = (double)CGDisplayPixelsWide(mDisplay) / (double)CGDisplayPixelsHigh(mDisplay);
+
+ // Stash the window title
+ strcpy((char*)mWindowTitle + 1, title);
+ mWindowTitle[0] = strlen(title);
+
+ mEventHandlerUPP = NewEventHandlerUPP(staticEventHandler);
+ mGlobalHandlerRef = NULL;
+ mWindowHandlerRef = NULL;
+
+ // We're not clipping yet
+ SetRect( &mOldMouseClip, 0, 0, 0, 0 );
+
+ // Set up global event handlers (the fullscreen case needs this)
+ InstallStandardEventHandler(GetApplicationEventTarget());
+
+ // Stash an object pointer for OSMessageBox()
+ gWindowImplementation = this;
+
+ // Create the GL context and set it up for windowed or fullscreen, as appropriate.
+ if(createContext(x, y, width, height, 32, fullscreen, disable_vsync))
+ {
+ if(mWindow != NULL)
+ {
+ // MBW -- XXX -- I think we can now do this here?
+ // Constrain the window to the screen it's mostly on, resizing if necessary.
+ ConstrainWindowToScreen(
+ mWindow,
+ kWindowStructureRgn,
+ kWindowConstrainMayResize |
+ // kWindowConstrainStandardOptions |
+ 0,
+ NULL,
+ NULL);
+
+ MacShowWindow(mWindow);
+ BringToFront(mWindow);
+ }
+
+ if (!gGLManager.initGL())
+ {
+ setupFailure(
+ "Second Life is unable to run because your video card drivers\n"
+ "are out of date or unsupported. Please make sure you have\n"
+ "the latest video card drivers installed.\n"
+ "If you continue to receive this message, contact customer service.",
+ "Error",
+ OSMB_OK);
+ return;
+ }
+
+ //start with arrow cursor
+ initCursors();
+ setCursor( UI_CURSOR_ARROW );
+ }
+
+ stop_glerror();
+}
+
+BOOL LLWindowMacOSX::createContext(int x, int y, int width, int height, int bits, BOOL fullscreen, BOOL disable_vsync)
+{
+ OSStatus err;
+ BOOL glNeedsInit = FALSE;
+
+ if(mGlobalHandlerRef == NULL)
+ {
+ InstallApplicationEventHandler(mEventHandlerUPP, GetEventTypeCount (CommandHandlerEventList), CommandHandlerEventList, (void*)this, &mGlobalHandlerRef);
+ }
+
+ mFullscreen = fullscreen;
+
+ if (mFullscreen && (mOldDisplayMode == NULL))
+ {
+ llinfos << "createContext: setting up fullscreen " << width << "x" << height << llendl;
+
+ // NOTE: The refresh rate will be REPORTED AS 0 for many DVI and notebook displays. Plan accordingly.
+ double refresh = getDictDouble (CGDisplayCurrentMode (mDisplay), kCGDisplayRefreshRate);
+
+ // If the requested width or height is 0, find the best default for the monitor.
+ if((width == 0) || (height == 0))
+ {
+ // Scan through the list of modes, looking for one which has:
+ // height between 700 and 800
+ // aspect ratio closest to the user's original mode
+ S32 resolutionCount = 0;
+ LLWindowResolution *resolutionList = getSupportedResolutions(resolutionCount);
+
+ if(resolutionList != NULL)
+ {
+ F32 closestAspect = 0;
+ U32 closestHeight = 0;
+ U32 closestWidth = 0;
+ int i;
+
+ llinfos << "createContext: searching for a display mode, original aspect is " << mOriginalAspectRatio << llendl;
+
+ for(i=0; i < resolutionCount; i++)
+ {
+ F32 aspect = (F32)resolutionList[i].mWidth / (F32)resolutionList[i].mHeight;
+
+ llinfos << "createContext: width " << resolutionList[i].mWidth << " height " << resolutionList[i].mHeight << " aspect " << aspect << llendl;
+
+ if( (resolutionList[i].mHeight >= 700) && (resolutionList[i].mHeight <= 800) &&
+ (fabs(aspect - mOriginalAspectRatio) < fabs(closestAspect - mOriginalAspectRatio)))
+ {
+ llinfos << " (new closest mode) " << llendl;
+
+ // This is the closest mode we've seen yet.
+ closestWidth = resolutionList[i].mWidth;
+ closestHeight = resolutionList[i].mHeight;
+ closestAspect = aspect;
+ }
+ }
+
+ width = closestWidth;
+ height = closestHeight;
+ }
+ }
+
+ if((width == 0) || (height == 0))
+ {
+ // Mode search failed for some reason. Use the old-school default.
+ width = 1024;
+ height = 768;
+ }
+
+ if (true)
+ {
+ // Fullscreen support
+ CFDictionaryRef refDisplayMode = 0;
+ boolean_t exactMatch = false;
+
+#if CAPTURE_ALL_DISPLAYS
+ // Capture all displays (may want to do this for final build)
+ CGCaptureAllDisplays ();
+#else
+ // Capture only the main display (useful for debugging)
+ CGDisplayCapture (mDisplay);
+#endif
+
+ // Switch the display to the desired resolution and refresh
+ refDisplayMode = CGDisplayBestModeForParametersAndRefreshRate(
+ mDisplay,
+ BITS_PER_PIXEL,
+ width,
+ height,
+ refresh,
+ &exactMatch);
+
+ if (refDisplayMode)
+ {
+ llinfos << "createContext: switching display resolution" << llendl;
+ mOldDisplayMode = CGDisplayCurrentMode (mDisplay);
+ CGDisplaySwitchToMode (mDisplay, refDisplayMode);
+ // CFRelease(refDisplayMode);
+
+ AddEventTypesToHandler(mGlobalHandlerRef, GetEventTypeCount (GlobalHandlerEventList), GlobalHandlerEventList);
+ }
+
+
+ mFullscreen = TRUE;
+ mFullscreenWidth = CGDisplayPixelsWide(mDisplay);
+ mFullscreenHeight = CGDisplayPixelsHigh(mDisplay);
+ mFullscreenBits = CGDisplayBitsPerPixel(mDisplay);
+ mFullscreenRefresh = llround(getDictDouble (CGDisplayCurrentMode (mDisplay), kCGDisplayRefreshRate));
+
+ llinfos << "Running at " << mFullscreenWidth
+ << "x" << mFullscreenHeight
+ << "x" << mFullscreenBits
+ << " @ " << mFullscreenRefresh
+ << llendl;
+ }
+ else
+ {
+ // No fullscreen support
+ mFullscreen = FALSE;
+ mFullscreenWidth = -1;
+ mFullscreenHeight = -1;
+ mFullscreenBits = -1;
+ mFullscreenRefresh = -1;
+
+ char error[256];
+ sprintf(error, "Unable to run fullscreen at %d x %d.\nRunning in window.", width, height);
+ OSMessageBox(error, "Error", OSMB_OK);
+ }
+ }
+
+ if(!mFullscreen && (mWindow == NULL))
+ {
+ Rect window_rect;
+ //int displayWidth = CGDisplayPixelsWide(mDisplay);
+ //int displayHeight = CGDisplayPixelsHigh(mDisplay);
+ //const int menuBarPlusTitleBar = 44; // Ugly magic number.
+
+ llinfos << "createContext: creating window" << llendl;
+
+ window_rect.left = (long) x;
+ window_rect.right = (long) x + width;
+ window_rect.top = (long) y;
+ window_rect.bottom = (long) y + height;
+
+ //-----------------------------------------------------------------------
+ // Create the window
+ //-----------------------------------------------------------------------
+ mWindow = NewCWindow(
+ NULL,
+ &window_rect,
+ mWindowTitle,
+ false, // Create the window invisible. Whoever calls createContext() should show it after any moving/resizing.
+ // noGrowDocProc, // Window with no grow box and no zoom box
+ zoomDocProc, // Window with a grow box and a zoom box
+ // zoomNoGrow, // Window with a zoom box but no grow box
+ kFirstWindowOfClass,
+ true,
+ (long)this);
+
+
+ if (!mWindow)
+ {
+ setupFailure("Window creation error", "Error", OSMB_OK);
+ return FALSE;
+ }
+
+ // Turn on live resize.
+ // For this to work correctly, we need to be able to call LLViewerWindow::draw from
+ // the event handler for kEventWindowBoundsChanged. It's not clear that we have access from here.
+ // err = ChangeWindowAttributes(mWindow, kWindowLiveResizeAttribute, 0);
+
+ // Set up window event handlers (some window-related events ONLY go to window handlers.)
+ InstallStandardEventHandler(GetWindowEventTarget(mWindow));
+ InstallWindowEventHandler (mWindow, mEventHandlerUPP, GetEventTypeCount (WindowHandlerEventList), WindowHandlerEventList, (void*)this, &mWindowHandlerRef); // add event handler
+
+ }
+
+ if(mContext == NULL)
+ {
+ AGLRendererInfo rendererInfo = NULL;
+
+ //-----------------------------------------------------------------------
+ // Create GL drawing context
+ //-----------------------------------------------------------------------
+
+ if(mPixelFormat == NULL)
+ {
+ if(mFullscreen)
+ {
+ GLint fullscreenAttrib[] =
+ {
+ AGL_RGBA,
+ AGL_FULLSCREEN,
+ // AGL_NO_RECOVERY, // MBW -- XXX -- Not sure if we want this attribute
+ AGL_DOUBLEBUFFER,
+ AGL_CLOSEST_POLICY,
+ AGL_ACCELERATED,
+ AGL_RED_SIZE, 8,
+ AGL_GREEN_SIZE, 8,
+ AGL_BLUE_SIZE, 8,
+ AGL_ALPHA_SIZE, 8,
+ AGL_DEPTH_SIZE, 24,
+ AGL_STENCIL_SIZE, 8,
+ AGL_NONE
+ };
+
+ llinfos << "createContext: creating fullscreen pixelformat" << llendl;
+
+ GDHandle gdhDisplay = NULL;
+ err = DMGetGDeviceByDisplayID ((DisplayIDType)mDisplay, &gdhDisplay, false);
+
+ mPixelFormat = aglChoosePixelFormat(&gdhDisplay, 1, fullscreenAttrib);
+ rendererInfo = aglQueryRendererInfo(&gdhDisplay, 1);
+ }
+ else
+ {
+ GLint windowedAttrib[] =
+ {
+ AGL_RGBA,
+ AGL_DOUBLEBUFFER,
+ AGL_CLOSEST_POLICY,
+ AGL_ACCELERATED,
+ AGL_RED_SIZE, 8,
+ AGL_GREEN_SIZE, 8,
+ AGL_BLUE_SIZE, 8,
+ AGL_ALPHA_SIZE, 8,
+ AGL_DEPTH_SIZE, 24,
+ AGL_STENCIL_SIZE, 8,
+ AGL_NONE
+ };
+
+ llinfos << "createContext: creating windowed pixelformat" << llendl;
+
+ mPixelFormat = aglChoosePixelFormat(NULL, 0, windowedAttrib);
+
+ GDHandle gdhDisplay = GetMainDevice();
+ rendererInfo = aglQueryRendererInfo(&gdhDisplay, 1);
+ }
+
+ // May want to get the real error text like this:
+ // (char *) aglErrorString(aglGetError());
+
+ if(aglGetError() != AGL_NO_ERROR)
+ {
+ setupFailure("Can't find suitable pixel format", "Error", OSMB_OK);
+ return FALSE;
+ }
+ }
+
+ if(mPixelFormat)
+ {
+ llinfos << "createContext: creating GL context" << llendl;
+ mContext = aglCreateContext(mPixelFormat, NULL);
+ }
+
+ if(mContext == NULL)
+ {
+ setupFailure("Can't make GL context", "Error", OSMB_OK);
+ return FALSE;
+ }
+
+ gGLManager.mVRAM = 0;
+
+ if(rendererInfo != NULL)
+ {
+ GLint result;
+
+ if(aglDescribeRenderer(rendererInfo, AGL_VIDEO_MEMORY, &result))
+ {
+ // llinfos << "createContext: aglDescribeRenderer(AGL_VIDEO_MEMORY) returned " << result << llendl;
+ gGLManager.mVRAM = result / (1024 * 1024);
+ }
+ else
+ {
+ // llinfos << "createContext: aglDescribeRenderer(AGL_VIDEO_MEMORY) failed." << llendl;
+ }
+
+ // This could be useful at some point, if it takes into account the memory already used by screen buffers, etc...
+ if(aglDescribeRenderer(rendererInfo, AGL_TEXTURE_MEMORY, &result))
+ {
+ // llinfos << "createContext: aglDescribeRenderer(AGL_TEXTURE_MEMORY) returned " << result << llendl;
+ }
+ else
+ {
+ // llinfos << "createContext: aglDescribeRenderer(AGL_TEXTURE_MEMORY) failed." << llendl;
+ }
+
+ aglDestroyRendererInfo(rendererInfo);
+ }
+
+ // Since we just created the context, it needs to be set up.
+ glNeedsInit = TRUE;
+ }
+
+ // Hook up the context to a drawable
+ if (mFullscreen && (mOldDisplayMode != NULL))
+ {
+ // We successfully captured the display. Use a fullscreen drawable
+
+ llinfos << "createContext: attaching fullscreen drawable" << llendl;
+
+#if CAPTURE_ALL_DISPLAYS
+ // Capture all displays (may want to do this for final build)
+ aglDisable (mContext, AGL_FS_CAPTURE_SINGLE);
+#else
+ // Capture only the main display (useful for debugging)
+ aglEnable (mContext, AGL_FS_CAPTURE_SINGLE);
+#endif
+
+ if (!aglSetFullScreen (mContext, 0, 0, 0, 0))
+ {
+ setupFailure("Can't set GL fullscreen", "Error", OSMB_OK);
+ return FALSE;
+ }
+ }
+ else if(!mFullscreen && (mWindow != NULL))
+ {
+ llinfos << "createContext: attaching windowed drawable" << llendl;
+
+ // We created a window. Use it as the drawable.
+ if(!aglSetDrawable(mContext, GetWindowPort (mWindow)))
+ {
+ setupFailure("Can't set GL drawable", "Error", OSMB_OK);
+ return FALSE;
+ }
+ }
+ else
+ {
+ setupFailure("Can't get fullscreen or windowed drawable.", "Error", OSMB_OK);
+ return FALSE;
+ }
+
+ if(mContext != NULL)
+ {
+ llinfos << "createContext: setting current context" << llendl;
+
+ if (!aglSetCurrentContext(mContext))
+ {
+ setupFailure("Can't activate GL rendering context", "Error", OSMB_OK);
+ return FALSE;
+ }
+ }
+
+ if(glNeedsInit)
+ {
+ // Check for some explicitly unsupported cards.
+ const char* RENDERER = (const char*) glGetString(GL_RENDERER);
+
+ const char* CARD_LIST[] =
+ { "RAGE 128",
+ "RIVA TNT2",
+ "Intel 810",
+ "3Dfx/Voodoo3",
+ "Radeon 7000",
+ "Radeon 7200",
+ "Radeon 7500",
+ "Radeon DDR",
+ "Radeon VE",
+ "GDI Generic" };
+ const S32 CARD_COUNT = sizeof(CARD_LIST)/sizeof(char*);
+
+ // Future candidates:
+ // ProSavage/Twister
+ // SuperSavage
+
+ S32 i;
+ for (i = 0; i < CARD_COUNT; i++)
+ {
+ if (check_for_card(RENDERER, CARD_LIST[i]))
+ {
+ close();
+ shell_open( "help/unsupported_card.html" );
+ return FALSE;
+ }
+ }
+ }
+
+ GLint colorBits, alphaBits, depthBits, stencilBits;
+
+ if( !aglDescribePixelFormat(mPixelFormat, AGL_BUFFER_SIZE, &colorBits) ||
+ !aglDescribePixelFormat(mPixelFormat, AGL_ALPHA_SIZE, &alphaBits) ||
+ !aglDescribePixelFormat(mPixelFormat, AGL_DEPTH_SIZE, &depthBits) ||
+ !aglDescribePixelFormat(mPixelFormat, AGL_STENCIL_SIZE, &stencilBits))
+ {
+ close();
+ setupFailure("Can't get pixel format description", "Error", OSMB_OK);
+ return FALSE;
+ }
+
+ llinfos << "GL buffer: Color Bits " << S32(colorBits)
+ << " Alpha Bits " << S32(alphaBits)
+ << " Depth Bits " << S32(depthBits)
+ << " Stencil Bits" << S32(stencilBits)
+ << llendl;
+
+ if (colorBits < 32)
+ {
+ close();
+ setupFailure(
+ "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"
+ "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 (alphaBits < 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;
+ }
+
+ // Disable vertical sync for swap
+ GLint frames_per_swap = 0;
+ if (disable_vsync)
+ {
+ llinfos << "Disabling vertical sync" << llendl;
+ frames_per_swap = 0;
+ }
+ else
+ {
+ llinfos << "Keeping vertical sync" << llendl;
+ frames_per_swap = 1;
+ }
+ aglSetInteger(mContext, AGL_SWAP_INTERVAL, &frames_per_swap);
+
+ // Don't need to get the current gamma, since there's a call that restores it to the system defaults.
+ return TRUE;
+}
+
+
+// changing fullscreen resolution, or switching between windowed and fullscreen mode.
+BOOL LLWindowMacOSX::switchContext(BOOL fullscreen, LLCoordScreen size, BOOL disable_vsync)
+{
+ BOOL needsRebuild = FALSE;
+ BOOL result = true;
+
+ if(fullscreen)
+ {
+ if(mFullscreen)
+ {
+ // Switching resolutions in fullscreen mode. Don't need to rebuild for this.
+ // Fullscreen support
+ CFDictionaryRef refDisplayMode = 0;
+ boolean_t exactMatch = false;
+
+ // Switch the display to the desired resolution and refresh
+ refDisplayMode = CGDisplayBestModeForParametersAndRefreshRate(
+ mDisplay,
+ BITS_PER_PIXEL,
+ size.mX,
+ size.mY,
+ getDictDouble (CGDisplayCurrentMode (mDisplay), kCGDisplayRefreshRate),
+ &exactMatch);
+
+ if (refDisplayMode)
+ {
+ CGDisplaySwitchToMode (mDisplay, refDisplayMode);
+ // CFRelease(refDisplayMode);
+ }
+
+ mFullscreenWidth = CGDisplayPixelsWide(mDisplay);
+ mFullscreenHeight = CGDisplayPixelsHigh(mDisplay);
+ mFullscreenBits = CGDisplayBitsPerPixel(mDisplay);
+ mFullscreenRefresh = llround(getDictDouble (CGDisplayCurrentMode (mDisplay), kCGDisplayRefreshRate));
+
+ llinfos << "Switched resolution to " << mFullscreenWidth
+ << "x" << mFullscreenHeight
+ << "x" << mFullscreenBits
+ << " @ " << mFullscreenRefresh
+ << llendl;
+
+ // Update the GL context to the new screen size
+ if (!aglUpdateContext(mContext))
+ {
+ setupFailure("Can't set GL fullscreen", "Error", OSMB_OK);
+ result = FALSE;
+ }
+ }
+ else
+ {
+ // Switching from windowed to fullscreen
+ needsRebuild = TRUE;
+ }
+ }
+ else
+ {
+ if(mFullscreen)
+ {
+ // Switching from fullscreen to windowed
+ needsRebuild = TRUE;
+ }
+ else
+ {
+ // Windowed to windowed -- not sure why we would be called like this. Just change the window size.
+ // The bounds changed event handler will do the rest.
+ if(mWindow != NULL)
+ {
+ ::SizeWindow(mWindow, size.mX, size.mY, true);
+ }
+ }
+ }
+
+ stop_glerror();
+ if(needsRebuild)
+ {
+ destroyContext();
+ result = createContext(0, 0, size.mX, size.mY, 0, fullscreen, disable_vsync);
+ if (result)
+ {
+ if(mWindow != NULL)
+ {
+ MacShowWindow(mWindow);
+ BringToFront(mWindow);
+ }
+
+ llverify(gGLManager.initGL());
+
+ //start with arrow cursor
+ initCursors();
+ setCursor( UI_CURSOR_ARROW );
+ }
+ }
+
+ stop_glerror();
+
+ return result;
+}
+
+void LLWindowMacOSX::destroyContext()
+{
+ if (!mContext)
+ {
+ // We don't have a context
+ return;
+ }
+ // Unhook the GL context from any drawable it may have
+ if(mContext != NULL)
+ {
+ llinfos << "destroyContext: unhooking drawable " << llendl;
+
+ aglSetCurrentContext (NULL);
+ aglSetDrawable(mContext, NULL);
+ }
+
+ // Make sure the display resolution gets restored
+ if(mOldDisplayMode != NULL)
+ {
+ llinfos << "destroyContext: restoring display resolution " << llendl;
+
+ CGDisplaySwitchToMode (mDisplay, mOldDisplayMode);
+
+#if CAPTURE_ALL_DISPLAYS
+ // Uncapture all displays (may want to do this for final build)
+ CGReleaseAllDisplays ();
+#else
+ // Uncapture only the main display (useful for debugging)
+ CGDisplayRelease (mDisplay);
+#endif
+
+ // CFRelease(mOldDisplayMode);
+
+ mOldDisplayMode = NULL;
+
+ // Remove the global event handlers the fullscreen case needed
+ RemoveEventTypesFromHandler(mGlobalHandlerRef, GetEventTypeCount (GlobalHandlerEventList), GlobalHandlerEventList);
+ }
+
+ // Clean up remaining GL state before blowing away window
+ gGLManager.shutdownGL();
+
+ // Clean up the pixel format
+ if(mPixelFormat != NULL)
+ {
+ llinfos << "destroyContext: destroying pixel format " << llendl;
+ aglDestroyPixelFormat(mPixelFormat);
+ mPixelFormat = NULL;
+ }
+
+ // Remove any Carbon Event handlers we installed
+ if(mGlobalHandlerRef != NULL)
+ {
+ llinfos << "destroyContext: removing global event handler" << llendl;
+ RemoveEventHandler(mGlobalHandlerRef);
+ mGlobalHandlerRef = NULL;
+ }
+
+ if(mWindowHandlerRef != NULL)
+ {
+ llinfos << "destroyContext: removing window event handler" << llendl;
+ RemoveEventHandler(mWindowHandlerRef);
+ mWindowHandlerRef = NULL;
+ }
+
+ // Close the window
+ if(mWindow != NULL)
+ {
+ llinfos << "destroyContext: disposing window" << llendl;
+ DisposeWindow(mWindow);
+ mWindow = NULL;
+ }
+
+ // Clean up the GL context
+ if(mContext != NULL)
+ {
+ llinfos << "destroyContext: destroying GL context" << llendl;
+ aglDestroyContext(mContext);
+ mContext = NULL;
+ }
+
+}
+
+LLWindowMacOSX::~LLWindowMacOSX()
+{
+ destroyContext();
+
+ if(mSupportedResolutions != NULL)
+ {
+ delete []mSupportedResolutions;
+ }
+
+ gWindowImplementation = NULL;
+
+}
+
+
+void LLWindowMacOSX::show()
+{
+ if(IsWindowCollapsed(mWindow))
+ CollapseWindow(mWindow, false);
+
+ MacShowWindow(mWindow);
+ BringToFront(mWindow);
+}
+
+void LLWindowMacOSX::hide()
+{
+ setMouseClipping(FALSE);
+ HideWindow(mWindow);
+}
+
+void LLWindowMacOSX::minimize()
+{
+ setMouseClipping(FALSE);
+ showCursor();
+ CollapseWindow(mWindow, true);
+}
+
+void LLWindowMacOSX::restore()
+{
+ show();
+}
+
+
+// close() destroys all OS-specific code associated with a window.
+// Usually called from LLWindowManager::destroyWindow()
+void LLWindowMacOSX::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();
+}
+
+BOOL LLWindowMacOSX::isValid()
+{
+ if(mFullscreen)
+ {
+ return(TRUE);
+ }
+
+ return (mWindow != NULL);
+}
+
+BOOL LLWindowMacOSX::getVisible()
+{
+ BOOL result = FALSE;
+
+ if(mFullscreen)
+ {
+ result = TRUE;
+ }if (mWindow)
+ {
+ if(MacIsWindowVisible(mWindow))
+ result = TRUE;
+ }
+
+ return(result);
+}
+
+BOOL LLWindowMacOSX::getMinimized()
+{
+ BOOL result = FALSE;
+
+ // Since the set of states where we want to act "minimized" is non-trivial, it's easier to
+ // track things locally than to try and retrieve the state from the window manager.
+ result = mMinimized;
+
+ return(result);
+}
+
+BOOL LLWindowMacOSX::getMaximized()
+{
+ BOOL result = FALSE;
+
+ if (mWindow)
+ {
+ // TODO
+ }
+
+ return(result);
+}
+
+BOOL LLWindowMacOSX::maximize()
+{
+ // TODO
+ return FALSE;
+}
+
+BOOL LLWindowMacOSX::getFullscreen()
+{
+ return mFullscreen;
+}
+
+void LLWindowMacOSX::gatherInput()
+{
+ // stop bouncing icon after fixed period of time
+ if (mBounceTimer.getStarted() && mBounceTimer.getElapsedTimeF32() > mBounceTime)
+ {
+ stopDockTileBounce();
+ }
+
+ // Use the old-school version so we get AppleEvent handler dispatch and menuselect handling.
+ // Anything that has an event handler will get processed inside WaitNextEvent, so we only need to handle
+ // the odd stuff here.
+ EventRecord evt;
+ while(WaitNextEvent(everyEvent, &evt, 0, NULL))
+ {
+ // printf("WaitNextEvent returned true, event is %d.\n", evt.what);
+ switch(evt.what)
+ {
+ case mouseDown:
+ {
+ short part;
+ WindowRef window;
+ long selectResult;
+ part = FindWindow(evt.where, &window);
+ switch ( part )
+ {
+ case inMenuBar:
+ selectResult = MenuSelect(evt.where);
+
+ HiliteMenu(0);
+ break;
+ }
+ }
+ break;
+
+ case kHighLevelEvent:
+ AEProcessAppleEvent (&evt);
+ break;
+
+ case updateEvt:
+ // We shouldn't be getting these regularly (since our window will be buffered), but we need to handle them correctly...
+ BeginUpdate((WindowRef)evt.message);
+ EndUpdate((WindowRef)evt.message);
+ break;
+
+ }
+ }
+}
+
+BOOL LLWindowMacOSX::getPosition(LLCoordScreen *position)
+{
+ Rect window_rect;
+ OSStatus err = -1;
+
+ if(mFullscreen)
+ {
+ position->mX = 0;
+ position->mY = 0;
+ err = noErr;
+ }
+ else if(mWindow)
+ {
+ err = GetWindowBounds(mWindow, kWindowContentRgn, &window_rect);
+
+ position->mX = window_rect.left;
+ position->mY = window_rect.top;
+ }
+ else
+ {
+ llerrs << "LLWindowMacOSX::getPosition(): no window and not fullscreen!" << llendl;
+ }
+
+ return (err == noErr);
+}
+
+BOOL LLWindowMacOSX::getSize(LLCoordScreen *size)
+{
+ Rect window_rect;
+ OSStatus err = -1;
+
+ if(mFullscreen)
+ {
+ size->mX = mFullscreenWidth;
+ size->mY = mFullscreenHeight;
+ err = noErr;
+ }
+ else if(mWindow)
+ {
+ err = GetWindowBounds(mWindow, kWindowContentRgn, &window_rect);
+
+ size->mX = window_rect.right - window_rect.left;
+ size->mY = window_rect.bottom - window_rect.top;
+ }
+ else
+ {
+ llerrs << "LLWindowMacOSX::getPosition(): no window and not fullscreen!" << llendl;
+ }
+
+ return (err == noErr);
+}
+
+BOOL LLWindowMacOSX::getSize(LLCoordWindow *size)
+{
+ Rect window_rect;
+ OSStatus err = -1;
+
+ if(mFullscreen)
+ {
+ size->mX = mFullscreenWidth;
+ size->mY = mFullscreenHeight;
+ err = noErr;
+ }
+ else if(mWindow)
+ {
+ err = GetWindowBounds(mWindow, kWindowContentRgn, &window_rect);
+
+ size->mX = window_rect.right - window_rect.left;
+ size->mY = window_rect.bottom - window_rect.top;
+ }
+ else
+ {
+ llerrs << "LLWindowMacOSX::getPosition(): no window and not fullscreen!" << llendl;
+ }
+
+ return (err == noErr);
+}
+
+BOOL LLWindowMacOSX::setPosition(const LLCoordScreen position)
+{
+ if(mWindow)
+ {
+ MacMoveWindow(mWindow, position.mX, position.mY, false);
+ }
+
+ return TRUE;
+}
+
+BOOL LLWindowMacOSX::setSize(const LLCoordScreen size)
+{
+ if(mWindow)
+ {
+ SizeWindow(mWindow, size.mX, size.mY, true);
+ }
+
+ return TRUE;
+}
+
+void LLWindowMacOSX::swapBuffers()
+{
+ aglSwapBuffers(mContext);
+}
+
+F32 LLWindowMacOSX::getGamma()
+{
+ F32 result = 1.8; // Default to something sane
+
+ CGGammaValue redMin;
+ CGGammaValue redMax;
+ CGGammaValue redGamma;
+ CGGammaValue greenMin;
+ CGGammaValue greenMax;
+ CGGammaValue greenGamma;
+ CGGammaValue blueMin;
+ CGGammaValue blueMax;
+ CGGammaValue blueGamma;
+
+ if(CGGetDisplayTransferByFormula(
+ mDisplay,
+ &redMin,
+ &redMax,
+ &redGamma,
+ &greenMin,
+ &greenMax,
+ &greenGamma,
+ &blueMin,
+ &blueMax,
+ &blueGamma) == noErr)
+ {
+ // So many choices...
+ // Let's just return the green channel gamma for now.
+ result = greenGamma;
+ }
+
+ return result;
+}
+
+BOOL LLWindowMacOSX::restoreGamma()
+{
+ CGDisplayRestoreColorSyncSettings();
+ return true;
+}
+
+BOOL LLWindowMacOSX::setGamma(const F32 gamma)
+{
+ CGGammaValue redMin;
+ CGGammaValue redMax;
+ CGGammaValue redGamma;
+ CGGammaValue greenMin;
+ CGGammaValue greenMax;
+ CGGammaValue greenGamma;
+ CGGammaValue blueMin;
+ CGGammaValue blueMax;
+ CGGammaValue blueGamma;
+
+ // MBW -- XXX -- Should we allow this in windowed mode?
+
+ if(CGGetDisplayTransferByFormula(
+ mDisplay,
+ &redMin,
+ &redMax,
+ &redGamma,
+ &greenMin,
+ &greenMax,
+ &greenGamma,
+ &blueMin,
+ &blueMax,
+ &blueGamma) != noErr)
+ {
+ return false;
+ }
+
+ if(CGSetDisplayTransferByFormula(
+ mDisplay,
+ redMin,
+ redMax,
+ gamma,
+ greenMin,
+ greenMax,
+ gamma,
+ blueMin,
+ blueMax,
+ gamma) != noErr)
+ {
+ return false;
+ }
+
+
+ return true;
+}
+
+BOOL LLWindowMacOSX::isCursorHidden()
+{
+ return mCursorHidden;
+}
+
+
+
+// Constrains the mouse to the window.
+void LLWindowMacOSX::setMouseClipping( BOOL b )
+{
+ // Just stash the requested state. We'll simulate this when the cursor is hidden by decoupling.
+ mIsMouseClipping = b;
+
+ if(b)
+ {
+ // llinfos << "setMouseClipping(TRUE)" << llendl
+ }
+ else
+ {
+ // llinfos << "setMouseClipping(FALSE)" << llendl
+ }
+
+ adjustCursorDecouple();
+}
+
+BOOL LLWindowMacOSX::setCursorPosition(const LLCoordWindow position)
+{
+ BOOL result = FALSE;
+ LLCoordScreen screen_pos;
+
+ if (!convertCoords(position, &screen_pos))
+ {
+ return FALSE;
+ }
+
+ CGPoint newPosition;
+
+ // llinfos << "setCursorPosition(" << screen_pos.mX << ", " << screen_pos.mY << ")" << llendl
+
+ newPosition.x = screen_pos.mX;
+ newPosition.y = screen_pos.mY;
+
+ CGSetLocalEventsSuppressionInterval(0.0);
+ if(CGWarpMouseCursorPosition(newPosition) == noErr)
+ {
+ result = TRUE;
+ }
+
+ // Under certain circumstances, this will trigger us to decouple the cursor.
+ adjustCursorDecouple(true);
+
+ return result;
+}
+
+static void fixOrigin(void)
+{
+ GrafPtr port;
+ Rect portrect;
+
+ ::GetPort(&port);
+ ::GetPortBounds(port, &portrect);
+ if((portrect.left != 0) || (portrect.top != 0))
+ {
+ // Mozilla sometimes changes our port origin. Fuckers.
+ ::SetOrigin(0,0);
+ }
+}
+
+BOOL LLWindowMacOSX::getCursorPosition(LLCoordWindow *position)
+{
+ Point cursor_point;
+ LLCoordScreen screen_pos;
+ GrafPtr save;
+
+ if(mWindow == NULL)
+ return FALSE;
+
+ ::GetPort(&save);
+ ::SetPort(GetWindowPort(mWindow));
+ fixOrigin();
+
+ // gets the mouse location in local coordinates
+ ::GetMouse(&cursor_point);
+
+// lldebugs << "getCursorPosition(): cursor is at " << cursor_point.h << ", " << cursor_point.v << " port origin: " << portrect.left << ", " << portrect.top << llendl;
+
+ ::SetPort(save);
+
+ if(mCursorDecoupled)
+ {
+ // CGMouseDelta x, y;
+
+ // If the cursor's decoupled, we need to read the latest movement delta as well.
+ // CGGetLastMouseDelta( &x, &y );
+ // cursor_point.h += x;
+ // cursor_point.v += y;
+
+ // CGGetLastMouseDelta may behave strangely when the cursor's first captured.
+ // Stash in the event handler instead.
+ cursor_point.h += mCursorLastEventDeltaX;
+ cursor_point.v += mCursorLastEventDeltaY;
+ }
+
+ position->mX = cursor_point.h;
+ position->mY = cursor_point.v;
+
+ return TRUE;
+}
+
+void LLWindowMacOSX::adjustCursorDecouple(bool warpingMouse)
+{
+ if(mIsMouseClipping && mCursorHidden)
+ {
+ if(warpingMouse)
+ {
+ // The cursor should be decoupled. Make sure it is.
+ if(!mCursorDecoupled)
+ {
+ // llinfos << "adjustCursorDecouple: decoupling cursor" << llendl;
+ CGAssociateMouseAndMouseCursorPosition(false);
+ mCursorDecoupled = true;
+ mCursorIgnoreNextDelta = TRUE;
+ }
+ }
+ }
+ else
+ {
+ // The cursor should not be decoupled. Make sure it isn't.
+ if(mCursorDecoupled)
+ {
+ // llinfos << "adjustCursorDecouple: recoupling cursor" << llendl;
+ CGAssociateMouseAndMouseCursorPosition(true);
+ mCursorDecoupled = false;
+ }
+ }
+}
+
+F32 LLWindowMacOSX::getNativeAspectRatio()
+{
+ if (mFullscreen)
+ {
+ return (F32)mFullscreenWidth / (F32)mFullscreenHeight;
+ }
+ else
+ {
+ // 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.
+
+ if (mOverrideAspectRatio > 0.f)
+ {
+ return mOverrideAspectRatio;
+ }
+
+ return mOriginalAspectRatio;
+ }
+}
+
+F32 LLWindowMacOSX::getPixelAspectRatio()
+{
+ //OS X always enforces a 1:1 pixel aspect ratio, regardless of video mode
+ return 1.f;
+}
+
+//static SInt32 oldWindowLevel;
+
+// MBW -- XXX -- There's got to be a better way than this. Find it, please...
+
+void LLWindowMacOSX::beforeDialog()
+{
+ if(mFullscreen)
+ {
+
+#if CAPTURE_ALL_DISPLAYS
+ // Uncapture all displays (may want to do this for final build)
+ CGReleaseAllDisplays ();
+#else
+ // Uncapture only the main display (useful for debugging)
+ CGDisplayRelease (mDisplay);
+#endif
+ // kDocumentWindowClass
+ // kMovableModalWindowClass
+ // kAllWindowClasses
+
+ // GLint order = 0;
+ // aglSetInteger(mContext, AGL_ORDER_CONTEXT_TO_FRONT, &order);
+ aglSetDrawable(mContext, NULL);
+ // GetWindowGroupLevel(GetWindowGroupOfClass(kAllWindowClasses), &oldWindowLevel);
+ // SetWindowGroupLevel(GetWindowGroupOfClass(kAllWindowClasses), CGShieldingWindowLevel());
+
+ mHandsOffEvents = TRUE;
+
+ }
+}
+
+void LLWindowMacOSX::afterDialog()
+{
+ if(mFullscreen)
+ {
+ mHandsOffEvents = FALSE;
+
+ // SetWindowGroupLevel(GetWindowGroupOfClass(kAllWindowClasses), oldWindowLevel);
+ aglSetFullScreen(mContext, 0, 0, 0, 0);
+ // GLint order = 1;
+ // aglSetInteger(mContext, AGL_ORDER_CONTEXT_TO_FRONT, &order);
+
+#if CAPTURE_ALL_DISPLAYS
+ // Capture all displays (may want to do this for final build)
+ CGCaptureAllDisplays ();
+#else
+ // Capture only the main display (useful for debugging)
+ CGDisplayCapture (mDisplay);
+#endif
+ }
+}
+
+
+S32 LLWindowMacOSX::stat(const char* file_name, struct stat* stat_info)
+{
+ return ::stat( file_name, stat_info );
+}
+
+void LLWindowMacOSX::flashIcon(F32 seconds)
+{
+ // Don't do this if we're already started, since this would try to install the NMRec twice.
+ if(!mBounceTimer.getStarted())
+ {
+ OSErr err;
+
+ mBounceTime = seconds;
+ memset(&mBounceRec, sizeof(mBounceRec), 0);
+ mBounceRec.qType = nmType;
+ mBounceRec.nmMark = 1;
+ err = NMInstall(&mBounceRec);
+ if(err == noErr)
+ {
+ mBounceTimer.start();
+ }
+ else
+ {
+ // This is very not-fatal (only problem is the icon will not bounce), but we'd like to find out about it somehow...
+ llinfos << "NMInstall failed with error code " << err << llendl;
+ }
+ }
+}
+
+BOOL LLWindowMacOSX::isClipboardTextAvailable()
+{
+ OSStatus err;
+ ScrapRef scrap;
+ ScrapFlavorFlags flags;
+ BOOL result = false;
+
+ err = GetCurrentScrap(&scrap);
+
+ if(err == noErr)
+ {
+ err = GetScrapFlavorFlags(scrap, kScrapFlavorTypeUnicode, &flags);
+ }
+
+ if(err == noErr)
+ result = true;
+
+ return result;
+}
+
+BOOL LLWindowMacOSX::pasteTextFromClipboard(LLWString &dst)
+{
+ OSStatus err;
+ ScrapRef scrap;
+ Size len;
+ BOOL result = false;
+
+ err = GetCurrentScrap(&scrap);
+
+ if(err == noErr)
+ {
+ err = GetScrapFlavorSize(scrap, kScrapFlavorTypeUnicode, &len);
+ }
+
+ if((err == noErr) && (len > 0))
+ {
+ int u16len = len / sizeof(U16);
+ U16 *temp = new U16[u16len + 1];
+ if (temp)
+ {
+ memset(temp, 0, (u16len + 1) * sizeof(temp[0]));
+ err = GetScrapFlavorData(scrap, kScrapFlavorTypeUnicode, &len, temp);
+ if (err == noErr)
+ {
+ // convert \r\n to \n and \r to \n in the incoming text.
+ U16 *s, *d;
+ for(s = d = temp; s[0] != '\0'; s++, d++)
+ {
+ if(s[0] == '\r')
+ {
+ if(s[1] == '\n')
+ {
+ // CRLF, a.k.a. DOS newline. Collapse to a single '\n'.
+ s++;
+ }
+
+ d[0] = '\n';
+ }
+ else
+ {
+ d[0] = s[0];
+ }
+ }
+
+ d[0] = '\0';
+
+ dst = utf16str_to_wstring(temp);
+
+ result = true;
+ }
+ delete[] temp;
+ }
+ }
+
+ return result;
+}
+
+BOOL LLWindowMacOSX::copyTextToClipboard(const LLWString &s)
+{
+ OSStatus err;
+ ScrapRef scrap;
+ //Size len;
+ //char *temp;
+ BOOL result = false;
+
+ if (!s.empty())
+ {
+ err = GetCurrentScrap(&scrap);
+ if (err == noErr)
+ err = ClearScrap(&scrap);
+
+ if (err == noErr)
+ {
+ llutf16string utf16str = wstring_to_utf16str(s);
+ size_t u16len = utf16str.length() * sizeof(U16);
+ err = PutScrapFlavor(scrap, kScrapFlavorTypeUnicode, kScrapFlavorMaskNone, u16len, utf16str.data());
+ if (err == noErr)
+ result = true;
+ }
+ }
+
+ return result;
+}
+
+
+BOOL LLWindowMacOSX::sendEmail(const char* address, const char* subject, const char* body_text,
+ const char* attachment, const char* attachment_displayed_name )
+{
+ // MBW -- XXX -- Um... yeah. I'll get to this later.
+
+ return false;
+}
+
+
+// protected
+BOOL LLWindowMacOSX::resetDisplayResolution()
+{
+ // This is only called from elsewhere in this class, and it's not used by the Mac implementation.
+ return true;
+}
+
+
+LLWindow::LLWindowResolution* LLWindowMacOSX::getSupportedResolutions(S32 &num_resolutions)
+{
+ if (!mSupportedResolutions)
+ {
+ CFArrayRef modes = CGDisplayAvailableModes(mDisplay);
+
+ if(modes != NULL)
+ {
+ CFIndex index, cnt;
+
+ mSupportedResolutions = new LLWindowResolution[MAX_NUM_RESOLUTIONS];
+ mNumSupportedResolutions = 0;
+
+ // Examine each mode
+ cnt = CFArrayGetCount( modes );
+
+ for ( index = 0; (index < cnt) && (mNumSupportedResolutions < MAX_NUM_RESOLUTIONS); index++ )
+ {
+ // Pull the mode dictionary out of the CFArray
+ CFDictionaryRef mode = (CFDictionaryRef)CFArrayGetValueAtIndex( modes, index );
+ long width = getDictLong(mode, kCGDisplayWidth);
+ long height = getDictLong(mode, kCGDisplayHeight);
+ long bits = getDictLong(mode, kCGDisplayBitsPerPixel);
+
+ if(bits == BITS_PER_PIXEL && width >= 800 && height >= 600)
+ {
+ BOOL resolution_exists = FALSE;
+ for(S32 i = 0; i < mNumSupportedResolutions; i++)
+ {
+ if (mSupportedResolutions[i].mWidth == width &&
+ mSupportedResolutions[i].mHeight == height)
+ {
+ resolution_exists = TRUE;
+ }
+ }
+ if (!resolution_exists)
+ {
+ mSupportedResolutions[mNumSupportedResolutions].mWidth = width;
+ mSupportedResolutions[mNumSupportedResolutions].mHeight = height;
+ mNumSupportedResolutions++;
+ }
+ }
+ }
+ }
+ }
+
+ num_resolutions = mNumSupportedResolutions;
+ return mSupportedResolutions;
+}
+
+BOOL LLWindowMacOSX::convertCoords(LLCoordGL from, LLCoordWindow *to)
+{
+ S32 client_height;
+ Rect client_rect;
+
+ if(mFullscreen)
+ {
+ // In the fullscreen case, the "window" is the entire screen.
+ client_rect.left = 0;
+ client_rect.top = 0;
+ client_rect.right = mFullscreenWidth;
+ client_rect.bottom = mFullscreenHeight;
+ }
+ else if (!mWindow ||
+ (GetWindowBounds(mWindow, kWindowContentRgn, &client_rect) != noErr) ||
+ NULL == to)
+ {
+ return FALSE;
+ }
+
+ to->mX = from.mX;
+ client_height = client_rect.bottom - client_rect.top;
+ to->mY = client_height - from.mY - 1;
+
+ return TRUE;
+}
+
+BOOL LLWindowMacOSX::convertCoords(LLCoordWindow from, LLCoordGL* to)
+{
+ S32 client_height;
+ Rect client_rect;
+
+ if(mFullscreen)
+ {
+ // In the fullscreen case, the "window" is the entire screen.
+ client_rect.left = 0;
+ client_rect.top = 0;
+ client_rect.right = mFullscreenWidth;
+ client_rect.bottom = mFullscreenHeight;
+ }
+ else if (!mWindow ||
+ (GetWindowBounds(mWindow, kWindowContentRgn, &client_rect) != noErr) ||
+ NULL == to)
+ {
+ return FALSE;
+ }
+
+ to->mX = from.mX;
+ client_height = client_rect.bottom - client_rect.top;
+ to->mY = client_height - from.mY - 1;
+
+ return TRUE;
+}
+
+BOOL LLWindowMacOSX::convertCoords(LLCoordScreen from, LLCoordWindow* to)
+{
+ if(mFullscreen)
+ {
+ // In the fullscreen case, window and screen coordinates are the same.
+ to->mX = from.mX;
+ to->mY = from.mY;
+ return TRUE;
+ }
+ else if(mWindow)
+ {
+ GrafPtr save;
+ Point mouse_point;
+
+ mouse_point.h = from.mX;
+ mouse_point.v = from.mY;
+
+ ::GetPort(&save);
+ ::SetPort(GetWindowPort(mWindow));
+ fixOrigin();
+
+ ::GlobalToLocal(&mouse_point);
+
+ to->mX = mouse_point.h;
+ to->mY = mouse_point.v;
+
+ ::SetPort(save);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+BOOL LLWindowMacOSX::convertCoords(LLCoordWindow from, LLCoordScreen *to)
+{
+ if(mFullscreen)
+ {
+ // In the fullscreen case, window and screen coordinates are the same.
+ to->mX = from.mX;
+ to->mY = from.mY;
+ return TRUE;
+ }
+ else if(mWindow)
+ {
+ GrafPtr save;
+ Point mouse_point;
+
+ mouse_point.h = from.mX;
+ mouse_point.v = from.mY;
+ ::GetPort(&save);
+ ::SetPort(GetWindowPort(mWindow));
+ fixOrigin();
+
+ LocalToGlobal(&mouse_point);
+
+ to->mX = mouse_point.h;
+ to->mY = mouse_point.v;
+
+ ::SetPort(save);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+BOOL LLWindowMacOSX::convertCoords(LLCoordScreen from, LLCoordGL *to)
+{
+ LLCoordWindow window_coord;
+
+ return(convertCoords(from, &window_coord) && convertCoords(window_coord, to));
+}
+
+BOOL LLWindowMacOSX::convertCoords(LLCoordGL from, LLCoordScreen *to)
+{
+ LLCoordWindow window_coord;
+
+ return(convertCoords(from, &window_coord) && convertCoords(window_coord, to));
+}
+
+
+
+
+void LLWindowMacOSX::setupFailure(const char* text, const char* caption, U32 type)
+{
+ destroyContext();
+
+ OSMessageBox(text, caption, type);
+}
+
+pascal OSStatus LLWindowMacOSX::staticEventHandler(EventHandlerCallRef myHandler, EventRef event, void* userData)
+{
+ LLWindowMacOSX *self = (LLWindowMacOSX*)userData;
+
+ return(self->eventHandler(myHandler, event));
+}
+
+OSStatus LLWindowMacOSX::eventHandler (EventHandlerCallRef myHandler, EventRef event)
+{
+ OSStatus result = eventNotHandledErr;
+ UInt32 evtClass = GetEventClass (event);
+ UInt32 evtKind = GetEventKind (event);
+
+ // Always handle command events, even in hands-off mode.
+ if((evtClass == kEventClassCommand) && (evtKind == kEventCommandProcess))
+ {
+ HICommand command;
+ GetEventParameter (event, kEventParamDirectObject, typeHICommand, NULL, sizeof(command), NULL, &command);
+
+ switch(command.commandID)
+ {
+ case kHICommandQuit:
+ if(mCallbacks->handleCloseRequest(this))
+ {
+ // Get the app to initiate cleanup.
+ mCallbacks->handleQuit(this);
+ // The app is responsible for calling destroyWindow when done with GL
+ }
+ result = noErr;
+ break;
+
+ default:
+ // MBW -- XXX -- Should we handle other events here?
+ break;
+ }
+ }
+
+ if(mHandsOffEvents)
+ {
+ return(result);
+ }
+
+ switch (evtClass)
+ {
+ case kEventClassTextInput:
+ {
+ switch (evtKind)
+ {
+ case kEventTextInputUnicodeForKeyEvent:
+ {
+ UInt32 modifiers = 0;
+
+ // First, process the raw event.
+ {
+ EventRef rawEvent;
+
+ // Get the original event and extract the modifier keys, so we can ignore command-key events.
+ if (GetEventParameter(event, kEventParamTextInputSendKeyboardEvent, typeEventRef, NULL, sizeof(rawEvent), NULL, &rawEvent) == noErr)
+ {
+ // Grab the modifiers for later use in this function...
+ GetEventParameter (rawEvent, kEventParamKeyModifiers, typeUInt32, NULL, sizeof(UInt32), NULL, &modifiers);
+
+ // and call this function recursively to handle the raw key event.
+ eventHandler (myHandler, rawEvent);
+ }
+ }
+
+ OSStatus err = noErr;
+ EventParamType actualType = typeUnicodeText;
+ UInt32 actualSize = 0;
+ size_t actualCount = 0;
+ U16 *buffer = NULL;
+
+ // Get the size of the unicode data
+ err = GetEventParameter (event, kEventParamTextInputSendText, typeUnicodeText, &actualType, 0, &actualSize, NULL);
+ if(err == noErr)
+ {
+ // allocate a buffer and get the actual data.
+ actualCount = actualSize / sizeof(U16);
+ buffer = new U16[actualCount];
+ err = GetEventParameter (event, kEventParamTextInputSendText, typeUnicodeText, &actualType, actualSize, &actualSize, buffer);
+ }
+
+ if(err == noErr)
+ {
+ if(modifiers & (cmdKey | controlKey))
+ {
+ // This was a menu key equivalent. Ignore it.
+ }
+ else
+ {
+ MASK mask = 0;
+ if(modifiers & shiftKey) { mask |= MASK_SHIFT; }
+ if(modifiers & (cmdKey | controlKey)) { mask |= MASK_CONTROL; }
+ if(modifiers & optionKey) { mask |= MASK_ALT; }
+
+ llassert( actualType == typeUnicodeText );
+
+ // The result is a UTF16 buffer. Pass the characters in turn to handleUnicodeChar.
+
+ // Convert to UTF32 and go character-by-character.
+ llutf16string utf16(buffer, actualCount);
+ LLWString utf32 = utf16str_to_wstring(utf16);
+ LLWString::iterator iter;
+
+ for(iter = utf32.begin(); iter != utf32.end(); iter++)
+ {
+ mCallbacks->handleUnicodeChar(*iter, mask);
+ }
+ }
+ }
+
+ if(buffer != NULL)
+ {
+ delete[] buffer;
+ }
+
+ result = err;
+ }
+ break;
+ }
+ }
+ break;
+
+ case kEventClassKeyboard:
+ {
+ UInt32 keyCode = 0;
+ char charCode = 0;
+ UInt32 modifiers = 0;
+
+ // Some of these may fail for some event types. That's fine.
+ GetEventParameter (event, kEventParamKeyCode, typeUInt32, NULL, sizeof(UInt32), NULL, &keyCode);
+ GetEventParameter (event, kEventParamKeyModifiers, typeUInt32, NULL, sizeof(UInt32), NULL, &modifiers);
+
+ // printf("key event, key code = 0x%08x, char code = 0x%02x (%c), modifiers = 0x%08x\n", keyCode, charCode, (char)charCode, modifiers);
+ // fflush(stdout);
+
+ switch (evtKind)
+ {
+ case kEventRawKeyDown:
+ case kEventRawKeyRepeat:
+ if (gDebugWindowProc)
+ {
+ printf("key down, key code = 0x%08x, char code = 0x%02x (%c), modifiers = 0x%08x\n",
+ (unsigned int)keyCode, charCode, (char)charCode, (unsigned int)modifiers);
+ fflush(stdout);
+ }
+ gKeyboard->handleKeyDown(keyCode, modifiers);
+ result = eventNotHandledErr;
+ break;
+
+ case kEventRawKeyUp:
+ if (gDebugWindowProc)
+ {
+ printf("key up, key code = 0x%08x, char code = 0x%02x (%c), modifiers = 0x%08x\n",
+ (unsigned int)keyCode, charCode, (char)charCode, (unsigned int)modifiers);
+ fflush(stdout);
+ }
+ gKeyboard->handleKeyUp(keyCode, modifiers);
+ result = eventNotHandledErr;
+ break;
+
+ case kEventRawKeyModifiersChanged:
+ // The keyboard input system wants key up/down events for modifier keys.
+ // Mac OS doesn't supply these directly, but can supply events when the collective modifier state changes.
+ // Use these events to generate up/down events for the modifiers.
+
+ if((modifiers & shiftKey) && !(mLastModifiers & shiftKey))
+ {
+ if (gDebugWindowProc) printf("Shift key down event\n");
+ gKeyboard->handleKeyDown(0x38, (modifiers & 0x00FFFFFF) | ((0x38 << 24) & 0xFF000000));
+ }
+ else if(!(modifiers & shiftKey) && (mLastModifiers & shiftKey))
+ {
+ if (gDebugWindowProc) printf("Shift key up event\n");
+ gKeyboard->handleKeyUp(0x38, (modifiers & 0x00FFFFFF) | ((0x38 << 24) & 0xFF000000));
+ }
+
+ if((modifiers & alphaLock) && !(mLastModifiers & alphaLock))
+ {
+ if (gDebugWindowProc) printf("Caps lock down event\n");
+ gKeyboard->handleKeyDown(0x39, (modifiers & 0x00FFFFFF) | ((0x39 << 24) & 0xFF000000));
+ }
+ else if(!(modifiers & alphaLock) && (mLastModifiers & alphaLock))
+ {
+ if (gDebugWindowProc) printf("Caps lock up event\n");
+ gKeyboard->handleKeyUp(0x39, (modifiers & 0x00FFFFFF) | ((0x39 << 24) & 0xFF000000));
+ }
+
+ if((modifiers & controlKey) && !(mLastModifiers & controlKey))
+ {
+ if (gDebugWindowProc) printf("Control key down event\n");
+ gKeyboard->handleKeyDown(0x3b, (modifiers & 0x00FFFFFF) | ((0x3b << 24) & 0xFF000000));
+ }
+ else if(!(modifiers & controlKey) && (mLastModifiers & controlKey))
+ {
+ if (gDebugWindowProc) printf("Control key up event\n");
+ gKeyboard->handleKeyUp(0x3b, (modifiers & 0x00FFFFFF) | ((0x3b << 24) & 0xFF000000));
+ }
+
+ if((modifiers & optionKey) && !(mLastModifiers & optionKey))
+ {
+ if (gDebugWindowProc) printf("Option key down event\n");
+ gKeyboard->handleKeyDown(0x3a, (modifiers & 0x00FFFFFF) | ((0x3a << 24) & 0xFF000000));
+ }
+ else if(!(modifiers & optionKey) && (mLastModifiers & optionKey))
+ {
+ if (gDebugWindowProc) printf("Option key up event\n");
+ gKeyboard->handleKeyUp(0x3a, (modifiers & 0x00FFFFFF) | ((0x3a << 24) & 0xFF000000));
+ }
+
+ // When the state of the 'Fn' key (the one that changes some of the mappings on a powerbook/macbook keyboard
+ // to an embedded keypad) changes, it may subsequently cause a key up event to be lost, which may lead to
+ // a movement key getting "stuck" down. This is bad.
+ // This is an OS bug -- even the GetKeys() API doesn't tell you the key has been released.
+ // This workaround causes all held-down keys to be reset whenever the state of the Fn key changes. This isn't
+ // exactly what we want, but it does avoid the case where you get stuck running forward.
+ if((modifiers & kEventKeyModifierFnMask) != (mLastModifiers & kEventKeyModifierFnMask))
+ {
+ if (gDebugWindowProc) printf("Fn key state change event\n");
+ gKeyboard->resetKeys();
+ }
+
+ if (gDebugWindowProc) fflush(stdout);
+
+ mLastModifiers = modifiers;
+ result = eventNotHandledErr;
+ break;
+ }
+ }
+ break;
+
+ case kEventClassMouse:
+ {
+ result = CallNextEventHandler(myHandler, event);
+ if (eventNotHandledErr == result)
+ { // only handle events not already handled (prevents wierd resize interaction)
+ EventMouseButton button = kEventMouseButtonPrimary;
+ HIPoint location = {0.0f, 0.0f};
+ UInt32 modifiers = 0;
+ UInt32 clickCount = 1;
+ long wheelDelta = 0;
+ LLCoordScreen inCoords;
+ LLCoordGL outCoords;
+ MASK mask = 0;
+
+ GetEventParameter(event, kEventParamMouseButton, typeMouseButton, NULL, sizeof(button), NULL, &button);
+ GetEventParameter(event, kEventParamMouseLocation, typeHIPoint, NULL, sizeof(location), NULL, &location);
+ GetEventParameter(event, kEventParamKeyModifiers, typeUInt32, NULL, sizeof(modifiers), NULL, &modifiers);
+ GetEventParameter(event, kEventParamMouseWheelDelta, typeLongInteger, NULL, sizeof(wheelDelta), NULL, &wheelDelta);
+ GetEventParameter(event, kEventParamClickCount, typeUInt32, NULL, sizeof(clickCount), NULL, &clickCount);
+
+ inCoords.mX = llround(location.x);
+ inCoords.mY = llround(location.y);
+
+ if(modifiers & shiftKey) { mask |= MASK_SHIFT; }
+ if(modifiers & controlKey) { mask |= MASK_CONTROL; }
+ if(modifiers & optionKey) { mask |= MASK_ALT; }
+
+ if(mCursorDecoupled)
+ {
+ CGMouseDelta x, y;
+
+ // If the cursor's decoupled, we need to read the latest movement delta as well.
+ CGGetLastMouseDelta( &x, &y );
+ mCursorLastEventDeltaX = x;
+ mCursorLastEventDeltaY = y;
+
+ if(mCursorIgnoreNextDelta)
+ {
+ mCursorLastEventDeltaX = 0;
+ mCursorLastEventDeltaY = 0;
+ mCursorIgnoreNextDelta = FALSE;
+ }
+ }
+ else
+ {
+ mCursorLastEventDeltaX = 0;
+ mCursorLastEventDeltaY = 0;
+ }
+
+ inCoords.mX += mCursorLastEventDeltaX;
+ inCoords.mY += mCursorLastEventDeltaY;
+
+ convertCoords(inCoords, &outCoords);
+
+ // printf("coords in: %d, %d; coords out: %d, %d\n", inCoords.mX, inCoords.mY, outCoords.mX, outCoords.mY);
+ // fflush(stdout);
+
+
+ switch (evtKind)
+ {
+ case kEventMouseDown:
+ switch(button)
+ {
+ case kEventMouseButtonPrimary:
+ if(modifiers & cmdKey)
+ {
+ // Simulate a right click
+ mSimulatedRightClick = true;
+ mCallbacks->handleRightMouseDown(this, outCoords, mask);
+ }
+ else if(clickCount == 2)
+ {
+ // Windows double-click events replace the second mousedown event in a double-click.
+ mCallbacks->handleDoubleClick(this, outCoords, mask);
+ }
+ else
+ {
+ mCallbacks->handleMouseDown(this, outCoords, mask);
+ }
+ break;
+ case kEventMouseButtonSecondary:
+ mCallbacks->handleRightMouseDown(this, outCoords, mask);
+ break;
+ }
+ result = noErr;
+ break;
+ case kEventMouseUp:
+
+ switch(button)
+ {
+ case kEventMouseButtonPrimary:
+ if(mSimulatedRightClick)
+ {
+ // End of simulated right click
+ mSimulatedRightClick = false;
+ mCallbacks->handleRightMouseUp(this, outCoords, mask);
+ }
+ else
+ {
+ mCallbacks->handleMouseUp(this, outCoords, mask);
+ }
+ break;
+ case kEventMouseButtonSecondary:
+ mCallbacks->handleRightMouseUp(this, outCoords, mask);
+ break;
+ }
+ result = noErr;
+ break;
+
+ case kEventMouseWheelMoved:
+ {
+ static S32 z_delta = 0;
+
+ z_delta += wheelDelta;
+
+ if (z_delta <= -WHEEL_DELTA || WHEEL_DELTA <= z_delta)
+ {
+ mCallbacks->handleScrollWheel(this, -z_delta / WHEEL_DELTA);
+ z_delta = 0;
+ }
+ }
+ result = noErr;
+ break;
+
+ case kEventMouseDragged:
+ case kEventMouseMoved:
+ mCallbacks->handleMouseMove(this, outCoords, mask);
+ result = noErr;
+ break;
+
+ }
+ }
+ }
+ break;
+
+ case kEventClassWindow:
+ switch(evtKind)
+ {
+ case kEventWindowBoundsChanging:
+ {
+ Rect currentBounds;
+ Rect previousBounds;
+
+ GetEventParameter(event, kEventParamCurrentBounds, typeQDRectangle, NULL, sizeof(Rect), NULL, &currentBounds);
+ GetEventParameter(event, kEventParamPreviousBounds, typeQDRectangle, NULL, sizeof(Rect), NULL, &previousBounds);
+
+ // This is where we would constrain move/resize to a particular screen
+ if(0)
+ {
+ SetEventParameter(event, kEventParamCurrentBounds, typeQDRectangle, sizeof(Rect), &currentBounds);
+ }
+ }
+ break;
+
+ case kEventWindowBoundsChanged:
+ {
+ Rect newBounds;
+
+ GetEventParameter(event, kEventParamCurrentBounds, typeQDRectangle, NULL, sizeof(Rect), NULL, &newBounds);
+ aglUpdateContext(mContext);
+ mCallbacks->handleResize(this, newBounds.right - newBounds.left, newBounds.bottom - newBounds.top);
+
+
+ }
+ break;
+
+ case kEventWindowClose:
+ if(mCallbacks->handleCloseRequest(this))
+ {
+ // Get the app to initiate cleanup.
+ mCallbacks->handleQuit(this);
+ // The app is responsible for calling destroyWindow when done with GL
+ }
+ result = noErr;
+ break;
+
+ case kEventWindowHidden:
+ // llinfos << "LLWindowMacOSX: Deactivating on hide" << llendl;
+ mMinimized = TRUE;
+ mCallbacks->handleActivate(this, false);
+ // result = noErr;
+ break;
+
+ case kEventWindowShown:
+ // llinfos << "LLWindowMacOSX: Activating on show" << llendl;
+ mMinimized = FALSE;
+ mCallbacks->handleActivate(this, true);
+ // result = noErr;
+ break;
+
+ case kEventWindowCollapsed:
+ // llinfos << "LLWindowMacOSX: Deactivating on collapse" << llendl;
+ mMinimized = TRUE;
+ mCallbacks->handleActivate(this, false);
+ // result = noErr;
+ break;
+
+ case kEventWindowExpanded:
+ // llinfos << "LLWindowMacOSX: Activating on expand" << llendl;
+ mMinimized = FALSE;
+ mCallbacks->handleActivate(this, true);
+ // result = noErr;
+ break;
+
+ case kEventWindowGetClickActivation:
+ // BringToFront(mWindow);
+ // result = noErr;
+ break;
+ }
+ break;
+ }
+ return result;
+}
+
+const char* cursorIDToName(int id)
+{
+ switch (id)
+ {
+ case UI_CURSOR_ARROW: return "UI_CURSOR_ARROW";
+ case UI_CURSOR_WAIT: return "UI_CURSOR_WAIT";
+ case UI_CURSOR_HAND: return "UI_CURSOR_HAND";
+ case UI_CURSOR_IBEAM: return "UI_CURSOR_IBEAM";
+ case UI_CURSOR_CROSS: return "UI_CURSOR_CROSS";
+ case UI_CURSOR_SIZENWSE: return "UI_CURSOR_SIZENWSE";
+ case UI_CURSOR_SIZENESW: return "UI_CURSOR_SIZENESW";
+ case UI_CURSOR_SIZEWE: return "UI_CURSOR_SIZEWE";
+ case UI_CURSOR_SIZENS: return "UI_CURSOR_SIZENS";
+ case UI_CURSOR_NO: return "UI_CURSOR_NO";
+ case UI_CURSOR_WORKING: return "UI_CURSOR_WORKING";
+ case UI_CURSOR_TOOLGRAB: return "UI_CURSOR_TOOLGRAB";
+ case UI_CURSOR_TOOLLAND: return "UI_CURSOR_TOOLLAND";
+ case UI_CURSOR_TOOLFOCUS: return "UI_CURSOR_TOOLFOCUS";
+ case UI_CURSOR_TOOLCREATE: return "UI_CURSOR_TOOLCREATE";
+ case UI_CURSOR_ARROWDRAG: return "UI_CURSOR_ARROWDRAG";
+ case UI_CURSOR_ARROWCOPY: return "UI_CURSOR_ARROWCOPY";
+ case UI_CURSOR_ARROWDRAGMULTI: return "UI_CURSOR_ARROWDRAGMULTI";
+ case UI_CURSOR_ARROWCOPYMULTI: return "UI_CURSOR_ARROWCOPYMULTI";
+ case UI_CURSOR_NOLOCKED: return "UI_CURSOR_NOLOCKED";
+ case UI_CURSOR_ARROWLOCKED: return "UI_CURSOR_ARROWLOCKED";
+ case UI_CURSOR_GRABLOCKED: return "UI_CURSOR_GRABLOCKED";
+ case UI_CURSOR_TOOLTRANSLATE: return "UI_CURSOR_TOOLTRANSLATE";
+ case UI_CURSOR_TOOLROTATE: return "UI_CURSOR_TOOLROTATE";
+ case UI_CURSOR_TOOLSCALE: return "UI_CURSOR_TOOLSCALE";
+ case UI_CURSOR_TOOLCAMERA: return "UI_CURSOR_TOOLCAMERA";
+ case UI_CURSOR_TOOLPAN: return "UI_CURSOR_TOOLPAN";
+ case UI_CURSOR_TOOLZOOMIN: return "UI_CURSOR_TOOLZOOMIN";
+ case UI_CURSOR_TOOLPICKOBJECT3: return "UI_CURSOR_TOOLPICKOBJECT3";
+ case UI_CURSOR_TOOLSIT: return "UI_CURSOR_TOOLSIT";
+ case UI_CURSOR_TOOLBUY: return "UI_CURSOR_TOOLBUY";
+ case UI_CURSOR_TOOLPAY: return "UI_CURSOR_TOOLPAY";
+ case UI_CURSOR_TOOLOPEN: return "UI_CURSOR_TOOLOPEN";
+ case UI_CURSOR_PIPETTE: return "UI_CURSOR_PIPETTE";
+ }
+
+ llerrs << "cursorIDToName: unknown cursor id" << id << llendl;
+
+ return "UI_CURSOR_ARROW";
+}
+
+static CursorRef gCursors[UI_CURSOR_COUNT];
+
+
+static void initPixmapCursor(int cursorid, int hotspotX, int hotspotY)
+{
+ // cursors are in <Application Bundle>/Contents/Resources/cursors_mac/UI_CURSOR_FOO.tif
+ std::string fullpath = gDirUtilp->getAppRODataDir();
+ fullpath += gDirUtilp->getDirDelimiter();
+ fullpath += "cursors_mac";
+ fullpath += gDirUtilp->getDirDelimiter();
+ fullpath += cursorIDToName(cursorid);
+ fullpath += ".tif";
+
+ gCursors[cursorid] = createImageCursor(fullpath.c_str(), hotspotX, hotspotY);
+}
+
+void LLWindowMacOSX::setCursor(ECursorType cursor)
+{
+ OSStatus result = noErr;
+
+ if (cursor == UI_CURSOR_ARROW
+ && mBusyCount > 0)
+ {
+ cursor = UI_CURSOR_WORKING;
+ }
+
+ if(mCurrentCursor == cursor)
+ return;
+
+ // RN: replace multi-drag cursors with single versions
+ if (cursor == UI_CURSOR_ARROWDRAGMULTI)
+ {
+ cursor = UI_CURSOR_ARROWDRAG;
+ }
+ else if (cursor == UI_CURSOR_ARROWCOPYMULTI)
+ {
+ cursor = UI_CURSOR_ARROWCOPY;
+ }
+
+ switch(cursor)
+ {
+ default:
+ case UI_CURSOR_ARROW:
+ InitCursor();
+ if(mCursorHidden)
+ {
+ // Since InitCursor resets the hide level, correct for it here.
+ ::HideCursor();
+ }
+ break;
+
+ // MBW -- XXX -- Some of the standard Windows cursors have no standard Mac equivalents.
+ // Find out what they look like and replicate them.
+
+ // These are essentially correct
+ case UI_CURSOR_WAIT: SetThemeCursor(kThemeWatchCursor); break;
+ case UI_CURSOR_IBEAM: SetThemeCursor(kThemeIBeamCursor); break;
+ case UI_CURSOR_CROSS: SetThemeCursor(kThemeCrossCursor); break;
+ case UI_CURSOR_HAND: SetThemeCursor(kThemePointingHandCursor); break;
+ // case UI_CURSOR_NO: SetThemeCursor(kThemeNotAllowedCursor); break;
+ case UI_CURSOR_ARROWCOPY: SetThemeCursor(kThemeCopyArrowCursor); break;
+
+ // Double-check these
+ case UI_CURSOR_NO:
+ case UI_CURSOR_SIZEWE:
+ case UI_CURSOR_SIZENS:
+ case UI_CURSOR_SIZENWSE:
+ case UI_CURSOR_SIZENESW:
+ case UI_CURSOR_WORKING:
+ case UI_CURSOR_TOOLGRAB:
+ case UI_CURSOR_TOOLLAND:
+ case UI_CURSOR_TOOLFOCUS:
+ case UI_CURSOR_TOOLCREATE:
+ case UI_CURSOR_ARROWDRAG:
+ case UI_CURSOR_NOLOCKED:
+ case UI_CURSOR_ARROWLOCKED:
+ case UI_CURSOR_GRABLOCKED:
+ case UI_CURSOR_TOOLTRANSLATE:
+ case UI_CURSOR_TOOLROTATE:
+ case UI_CURSOR_TOOLSCALE:
+ case UI_CURSOR_TOOLCAMERA:
+ case UI_CURSOR_TOOLPAN:
+ case UI_CURSOR_TOOLZOOMIN:
+ case UI_CURSOR_TOOLPICKOBJECT3:
+ case UI_CURSOR_TOOLSIT:
+ case UI_CURSOR_TOOLBUY:
+ case UI_CURSOR_TOOLPAY:
+ case UI_CURSOR_TOOLOPEN:
+ result = setImageCursor(gCursors[cursor]);
+ break;
+
+ }
+
+ if(result != noErr)
+ {
+ InitCursor();
+ }
+
+ mCurrentCursor = cursor;
+}
+
+ECursorType LLWindowMacOSX::getCursor()
+{
+ return mCurrentCursor;
+}
+
+void LLWindowMacOSX::initCursors()
+{
+ initPixmapCursor(UI_CURSOR_NO, 8, 8);
+ initPixmapCursor(UI_CURSOR_WORKING, 1, 1);
+ initPixmapCursor(UI_CURSOR_TOOLGRAB, 2, 14);
+ initPixmapCursor(UI_CURSOR_TOOLLAND, 13, 8);
+ initPixmapCursor(UI_CURSOR_TOOLFOCUS, 7, 6);
+ initPixmapCursor(UI_CURSOR_TOOLCREATE, 7, 7);
+ initPixmapCursor(UI_CURSOR_ARROWDRAG, 1, 1);
+ initPixmapCursor(UI_CURSOR_ARROWCOPY, 1, 1);
+ initPixmapCursor(UI_CURSOR_NOLOCKED, 8, 8);
+ initPixmapCursor(UI_CURSOR_ARROWLOCKED, 1, 1);
+ initPixmapCursor(UI_CURSOR_GRABLOCKED, 2, 14);
+ initPixmapCursor(UI_CURSOR_TOOLTRANSLATE, 1, 1);
+ initPixmapCursor(UI_CURSOR_TOOLROTATE, 1, 1);
+ initPixmapCursor(UI_CURSOR_TOOLSCALE, 1, 1);
+ initPixmapCursor(UI_CURSOR_TOOLCAMERA, 7, 6);
+ initPixmapCursor(UI_CURSOR_TOOLPAN, 7, 6);
+ initPixmapCursor(UI_CURSOR_TOOLZOOMIN, 7, 6);
+ initPixmapCursor(UI_CURSOR_TOOLPICKOBJECT3, 1, 1);
+ initPixmapCursor(UI_CURSOR_TOOLSIT, 1, 1);
+ initPixmapCursor(UI_CURSOR_TOOLBUY, 1, 1);
+ initPixmapCursor(UI_CURSOR_TOOLPAY, 1, 1);
+ initPixmapCursor(UI_CURSOR_TOOLOPEN, 1, 1);
+
+ initPixmapCursor(UI_CURSOR_SIZENWSE, 10, 10);
+ initPixmapCursor(UI_CURSOR_SIZENESW, 10, 10);
+ initPixmapCursor(UI_CURSOR_SIZEWE, 10, 10);
+ initPixmapCursor(UI_CURSOR_SIZENS, 10, 10);
+
+}
+
+void LLWindowMacOSX::captureMouse()
+{
+ // By registering a global CarbonEvent handler for mouse move events, we ensure that
+ // mouse events are always processed. Thus, capture and release are unnecessary.
+}
+
+void LLWindowMacOSX::releaseMouse()
+{
+ // By registering a global CarbonEvent handler for mouse move events, we ensure that
+ // mouse events are always processed. Thus, capture and release are unnecessary.
+}
+
+void LLWindowMacOSX::hideCursor()
+{
+ if(!mCursorHidden)
+ {
+ // llinfos << "hideCursor: hiding" << llendl;
+ mCursorHidden = TRUE;
+ mHideCursorPermanent = TRUE;
+ ::HideCursor();
+ }
+ else
+ {
+ // llinfos << "hideCursor: already hidden" << llendl;
+ }
+
+ adjustCursorDecouple();
+}
+
+void LLWindowMacOSX::showCursor()
+{
+ if(mCursorHidden)
+ {
+ // llinfos << "showCursor: showing" << llendl;
+ mCursorHidden = FALSE;
+ mHideCursorPermanent = FALSE;
+ ::ShowCursor();
+ }
+ else
+ {
+ // llinfos << "showCursor: already visible" << llendl;
+ }
+
+ adjustCursorDecouple();
+}
+
+void LLWindowMacOSX::showCursorFromMouseMove()
+{
+ if (!mHideCursorPermanent)
+ {
+ showCursor();
+ }
+}
+
+void LLWindowMacOSX::hideCursorUntilMouseMove()
+{
+ if (!mHideCursorPermanent)
+ {
+ hideCursor();
+ mHideCursorPermanent = FALSE;
+ }
+}
+
+
+
+//
+// LLSplashScreenMacOSX
+//
+LLSplashScreenMacOSX::LLSplashScreenMacOSX()
+{
+ mWindow = NULL;
+}
+
+LLSplashScreenMacOSX::~LLSplashScreenMacOSX()
+{
+}
+
+void LLSplashScreenMacOSX::showImpl()
+{
+ // This code _could_ be used to display a spash screen...
+#if 0
+ IBNibRef nib = NULL;
+ OSStatus err;
+
+ err = CreateNibReference(CFSTR("SecondLife"), &nib);
+
+ if(err == noErr)
+ {
+ CreateWindowFromNib(nib, CFSTR("Splash Screen"), &mWindow);
+
+ DisposeNibReference(nib);
+ }
+
+ if(mWindow != NULL)
+ {
+ ShowWindow(mWindow);
+ }
+#endif
+}
+
+void LLSplashScreenMacOSX::updateImpl(const char* mesg)
+{
+ if(mWindow != NULL)
+ {
+ CFStringRef string = NULL;
+
+ if(mesg != NULL)
+ {
+ string = CFStringCreateWithCString(NULL, mesg, kCFStringEncodingUTF8);
+ }
+ else
+ {
+ string = CFStringCreateWithCString(NULL, "", kCFStringEncodingUTF8);
+ }
+
+ if(string != NULL)
+ {
+ ControlRef progressText = NULL;
+ ControlID id;
+ OSStatus err;
+
+ id.signature = 'what';
+ id.id = 0;
+
+ err = GetControlByID(mWindow, &id, &progressText);
+ if(err == noErr)
+ {
+ err = SetControlData(progressText, kControlEntireControl, kControlStaticTextCFStringTag, sizeof(CFStringRef), (Ptr)&string);
+ Draw1Control(progressText);
+ }
+
+ CFRelease(string);
+ }
+ }
+}
+
+
+void LLSplashScreenMacOSX::hideImpl()
+{
+ if(mWindow != NULL)
+ {
+ DisposeWindow(mWindow);
+ mWindow = NULL;
+ }
+}
+
+
+
+S32 OSMessageBoxMacOSX(const char* text, const char* caption, U32 type)
+{
+ S32 result = OSBTN_CANCEL;
+ SInt16 retval_mac = 1;
+ AlertStdCFStringAlertParamRec params;
+ CFStringRef errorString = NULL;
+ CFStringRef explanationString = NULL;
+ DialogRef alert = NULL;
+ AlertType alertType = kAlertCautionAlert;
+ OSStatus err;
+
+ if(text != NULL)
+ {
+ explanationString = CFStringCreateWithCString(NULL, text, kCFStringEncodingUTF8);
+ }
+ else
+ {
+ explanationString = CFStringCreateWithCString(NULL, "", kCFStringEncodingUTF8);
+ }
+
+ if(caption != NULL)
+ {
+ errorString = CFStringCreateWithCString(NULL, caption, kCFStringEncodingUTF8);
+ }
+ else
+ {
+ errorString = CFStringCreateWithCString(NULL, "", kCFStringEncodingUTF8);
+ }
+
+ params.version = kStdCFStringAlertVersionOne;
+ params.movable = false;
+ params.helpButton = false;
+ params.defaultText = (CFStringRef)kAlertDefaultOKText;
+ params.cancelText = 0;
+ params.otherText = 0;
+ params.defaultButton = 1;
+ params.cancelButton = 0;
+ params.position = kWindowDefaultPosition;
+ params.flags = 0;
+
+ switch(type)
+ {
+ case OSMB_OK:
+ default:
+ break;
+ case OSMB_OKCANCEL:
+ params.cancelText = (CFStringRef)kAlertDefaultCancelText;
+ params.cancelButton = 2;
+ break;
+ case OSMB_YESNO:
+ alertType = kAlertNoteAlert;
+ params.defaultText = CFSTR("Yes");
+ params.cancelText = CFSTR("No");
+ params.cancelButton = 2;
+ break;
+ }
+
+ if(gWindowImplementation != NULL)
+ gWindowImplementation->beforeDialog();
+
+ err = CreateStandardAlert(
+ alertType,
+ errorString,
+ explanationString,
+ &params,
+ &alert);
+
+ if(err == noErr)
+ {
+ err = RunStandardAlert(
+ alert,
+ NULL,
+ &retval_mac);
+ }
+
+ if(gWindowImplementation != NULL)
+ gWindowImplementation->afterDialog();
+
+ switch(type)
+ {
+ case OSMB_OK:
+ case OSMB_OKCANCEL:
+ default:
+ if(retval_mac == 1)
+ result = OSBTN_OK;
+ else
+ result = OSBTN_CANCEL;
+ break;
+ case OSMB_YESNO:
+ if(retval_mac == 1)
+ result = OSBTN_YES;
+ else
+ result = OSBTN_NO;
+ break;
+ }
+
+ if(errorString != NULL)
+ {
+ CFRelease(errorString);
+ }
+
+ if(explanationString != NULL)
+ {
+ CFRelease(explanationString);
+ }
+
+ return result;
+}
+
+// Open a URL with the user's default web browser.
+// Must begin with protocol identifier.
+void spawn_web_browser(const char* escaped_url)
+{
+ bool found = false;
+ S32 i;
+ for (i = 0; i < gURLProtocolWhitelistCount; i++)
+ {
+ S32 len = strlen(gURLProtocolWhitelist[i]);
+ if (!strncmp(escaped_url, gURLProtocolWhitelist[i], len)
+ && escaped_url[len] == ':')
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ {
+ llwarns << "spawn_web_browser() called for url with protocol not on whitelist: " << escaped_url << llendl;
+ return;
+ }
+
+ OSStatus result = noErr;
+ CFURLRef urlRef = NULL;
+
+ llinfos << "Opening URL " << escaped_url << llendl;
+
+ CFStringRef stringRef = CFStringCreateWithCString(NULL, escaped_url, kCFStringEncodingUTF8);
+ if (stringRef)
+ {
+ // This will succeed if the string is a full URL, including the http://
+ // Note that URLs specified this way need to be properly percent-escaped.
+ urlRef = CFURLCreateWithString(NULL, stringRef, NULL);
+
+ // Don't use CRURLCreateWithFileSystemPath -- only want valid URLs
+
+ CFRelease(stringRef);
+ }
+
+ if (urlRef)
+ {
+ result = LSOpenCFURLRef(urlRef, NULL);
+
+ if (result != noErr)
+ {
+ llinfos << "Error " << result << " on open." << llendl;
+ }
+
+ CFRelease(urlRef);
+ }
+ else
+ {
+ llinfos << "Error: couldn't create URL." << llendl;
+ }
+}
+
+void shell_open( const char* file_path )
+{
+ OSStatus result = noErr;
+
+ llinfos << "Opening " << file_path << llendl;
+ CFURLRef urlRef = NULL;
+
+ CFStringRef stringRef = CFStringCreateWithCString(NULL, file_path, kCFStringEncodingUTF8);
+ if (stringRef)
+ {
+ // This will succeed if the string is a full URL, including the http://
+ // Note that URLs specified this way need to be properly percent-escaped.
+ urlRef = CFURLCreateWithString(NULL, stringRef, NULL);
+
+ if(urlRef == NULL)
+ {
+ // This will succeed if the string is a full or partial posix path.
+ // This will work even if the path contains characters that would need to be percent-escaped
+ // in the URL (such as spaces).
+ urlRef = CFURLCreateWithFileSystemPath(NULL, stringRef, kCFURLPOSIXPathStyle, false);
+ }
+
+ CFRelease(stringRef);
+ }
+
+ if (urlRef)
+ {
+ result = LSOpenCFURLRef(urlRef, NULL);
+
+ if (result != noErr)
+ {
+ llinfos << "Error " << result << " on open." << llendl;
+ }
+ CFRelease(urlRef);
+ }
+ else
+ {
+ llinfos << "Error: couldn't create URL." << llendl;
+ }
+}
+
+BOOL LLWindowMacOSX::dialog_color_picker ( F32 *r, F32 *g, F32 *b)
+{
+ BOOL retval = FALSE;
+ OSErr error = noErr;
+ NColorPickerInfo info;
+
+ memset(&info, 0, sizeof(info));
+ info.theColor.color.rgb.red = (UInt16)(*r * 65535.f);
+ info.theColor.color.rgb.green = (UInt16)(*g * 65535.f);
+ info.theColor.color.rgb.blue = (UInt16)(*b * 65535.f);
+ info.placeWhere = kCenterOnMainScreen;
+
+ if(gWindowImplementation != NULL)
+ gWindowImplementation->beforeDialog();
+
+ error = NPickColor(&info);
+
+ if(gWindowImplementation != NULL)
+ gWindowImplementation->afterDialog();
+
+ if (error == noErr)
+ {
+ retval = info.newColorChosen;
+ if (info.newColorChosen)
+ {
+ *r = ((float) info.theColor.color.rgb.red) / 65535.0;
+ *g = ((float) info.theColor.color.rgb.green) / 65535.0;
+ *b = ((float) info.theColor.color.rgb.blue) / 65535.0;
+ }
+ }
+ return (retval);
+}
+
+static WindowRef dummywindowref = NULL;
+
+void *LLWindowMacOSX::getPlatformWindow()
+{
+ if(mWindow != NULL)
+ return (void*)mWindow;
+
+ // If we're in fullscreen mode, there's no window pointer available.
+ // Since Mozilla needs one to function, create a dummy window here.
+ // Note that we will never destroy it, but since only one will be created per run of the application, that's okay.
+
+ if(dummywindowref == NULL)
+ {
+ Rect window_rect = {100, 100, 200, 200};
+
+ dummywindowref = NewCWindow(
+ NULL,
+ &window_rect,
+ "\p",
+ false, // Create the window invisible.
+ zoomDocProc, // Window with a grow box and a zoom box
+ kLastWindowOfClass, // create it behind other windows
+ false, // no close box
+ 0);
+ }
+
+ return (void*)dummywindowref;
+}
+
+void LLWindowMacOSX::stopDockTileBounce()
+{
+ NMRemove(&mBounceRec);
+ mBounceTimer.stop();
+}
+
+// get a double value from a dictionary
+static double getDictDouble (CFDictionaryRef refDict, CFStringRef key)
+{
+ double double_value;
+ CFNumberRef number_value = (CFNumberRef) CFDictionaryGetValue(refDict, key);
+ if (!number_value) // if can't get a number for the dictionary
+ return -1; // fail
+ if (!CFNumberGetValue(number_value, kCFNumberDoubleType, &double_value)) // or if cant convert it
+ return -1; // fail
+ return double_value; // otherwise return the long value
+}
+
+// get a long value from a dictionary
+static long getDictLong (CFDictionaryRef refDict, CFStringRef key)
+{
+ long int_value;
+ CFNumberRef number_value = (CFNumberRef) CFDictionaryGetValue(refDict, key);
+ if (!number_value) // if can't get a number for the dictionary
+ return -1; // fail
+ if (!CFNumberGetValue(number_value, kCFNumberLongType, &int_value)) // or if cant convert it
+ return -1; // fail
+ return int_value; // otherwise return the long value
+}
+
+#endif // LL_DARWIN