4930 lines
159 KiB
C++
4930 lines
159 KiB
C++
/**
|
|
* @file llwindowwin32.cpp
|
|
* @brief Platform-dependent implementation of llwindow
|
|
*
|
|
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
|
|
* Second Life Viewer Source Code
|
|
* Copyright (C) 2010, Linden Research, Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation;
|
|
* version 2.1 of the License only.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
|
* $/LicenseInfo$
|
|
*/
|
|
|
|
#include "linden_common.h"
|
|
|
|
#if LL_WINDOWS && !LL_MESA_HEADLESS
|
|
|
|
#include "llwindowwin32.h"
|
|
|
|
// LLWindow library includes
|
|
#include "llkeyboardwin32.h"
|
|
#include "lldragdropwin32.h"
|
|
#include "llpreeditor.h"
|
|
#include "llwindowcallbacks.h"
|
|
|
|
// Linden library includes
|
|
#include "llerror.h"
|
|
#include "llexception.h"
|
|
#include "llfasttimer.h"
|
|
#include "llgl.h"
|
|
#include "llstring.h"
|
|
#include "lldir.h"
|
|
#include "llsdutil.h"
|
|
#include "llglslshader.h"
|
|
#include "llthreadsafequeue.h"
|
|
#include "stringize.h"
|
|
#include "llframetimer.h"
|
|
|
|
// System includes
|
|
#include <commdlg.h>
|
|
#include <WinUser.h>
|
|
#include <mapi.h>
|
|
#include <process.h> // for _spawn
|
|
#include <shellapi.h>
|
|
#include <fstream>
|
|
#include <Imm.h>
|
|
#include <iomanip>
|
|
#include <future>
|
|
#include <sstream>
|
|
#include <utility> // std::pair
|
|
|
|
#include <d3d9.h>
|
|
#include <dxgi1_4.h>
|
|
#include <timeapi.h>
|
|
|
|
// Require DirectInput version 8
|
|
#define DIRECTINPUT_VERSION 0x0800
|
|
|
|
#include <dinput.h>
|
|
#include <Dbt.h.>
|
|
#include <InitGuid.h> // needed for llurlentry test to build on some systems
|
|
#pragma comment(lib, "dxguid.lib") // needed for llurlentry test to build on some systems
|
|
#pragma comment(lib, "dinput8")
|
|
|
|
const S32 MAX_MESSAGE_PER_UPDATE = 20;
|
|
const S32 BITS_PER_PIXEL = 32;
|
|
const S32 MAX_NUM_RESOLUTIONS = 32;
|
|
const F32 ICON_FLASH_TIME = 0.5f;
|
|
|
|
#ifndef WM_DPICHANGED
|
|
#define WM_DPICHANGED 0x02E0
|
|
#endif
|
|
|
|
#ifndef USER_DEFAULT_SCREEN_DPI
|
|
#define USER_DEFAULT_SCREEN_DPI 96 // Win7
|
|
#endif
|
|
|
|
// Claim a couple unused GetMessage() message IDs
|
|
const UINT WM_DUMMY_(WM_USER + 0x0017);
|
|
const UINT WM_POST_FUNCTION_(WM_USER + 0x0018);
|
|
|
|
extern bool gDebugWindowProc;
|
|
|
|
static std::thread::id sWindowThreadId;
|
|
static std::thread::id sMainThreadId;
|
|
|
|
#if 1 // flip to zero to enable assertions for functions being called from wrong thread
|
|
#define ASSERT_MAIN_THREAD()
|
|
#define ASSERT_WINDOW_THREAD()
|
|
#else
|
|
#define ASSERT_MAIN_THREAD() llassert(LLThread::currentID() == sMainThreadId)
|
|
#define ASSERT_WINDOW_THREAD() llassert(LLThread::currentID() == sWindowThreadId)
|
|
#endif
|
|
|
|
|
|
LPWSTR gIconResource = IDI_APPLICATION;
|
|
LPDIRECTINPUT8 gDirectInput8;
|
|
|
|
LLW32MsgCallback gAsyncMsgCallback = NULL;
|
|
|
|
#ifndef DPI_ENUMS_DECLARED
|
|
|
|
typedef enum PROCESS_DPI_AWARENESS {
|
|
PROCESS_DPI_UNAWARE = 0,
|
|
PROCESS_SYSTEM_DPI_AWARE = 1,
|
|
PROCESS_PER_MONITOR_DPI_AWARE = 2
|
|
} PROCESS_DPI_AWARENESS;
|
|
|
|
typedef enum MONITOR_DPI_TYPE {
|
|
MDT_EFFECTIVE_DPI = 0,
|
|
MDT_ANGULAR_DPI = 1,
|
|
MDT_RAW_DPI = 2,
|
|
MDT_DEFAULT = MDT_EFFECTIVE_DPI
|
|
} MONITOR_DPI_TYPE;
|
|
|
|
#endif
|
|
|
|
typedef HRESULT(STDAPICALLTYPE *SetProcessDpiAwarenessType)(_In_ PROCESS_DPI_AWARENESS value);
|
|
|
|
typedef HRESULT(STDAPICALLTYPE *GetProcessDpiAwarenessType)(
|
|
_In_ HANDLE hprocess,
|
|
_Out_ PROCESS_DPI_AWARENESS *value);
|
|
|
|
typedef HRESULT(STDAPICALLTYPE *GetDpiForMonitorType)(
|
|
_In_ HMONITOR hmonitor,
|
|
_In_ MONITOR_DPI_TYPE dpiType,
|
|
_Out_ UINT *dpiX,
|
|
_Out_ UINT *dpiY);
|
|
|
|
//
|
|
// LLWindowWin32
|
|
//
|
|
|
|
void show_window_creation_error(const std::string& title)
|
|
{
|
|
LL_WARNS("Window") << title << LL_ENDL;
|
|
}
|
|
|
|
HGLRC SafeCreateContext(HDC &hdc)
|
|
{
|
|
__try
|
|
{
|
|
return wglCreateContext(hdc);
|
|
}
|
|
__except(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
GLuint SafeChoosePixelFormat(HDC &hdc, const PIXELFORMATDESCRIPTOR *ppfd)
|
|
{
|
|
__try
|
|
{
|
|
return ChoosePixelFormat(hdc, ppfd);
|
|
}
|
|
__except (EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
// convert to C++ styled exception
|
|
// C exception don't allow classes, so it's a regular char array
|
|
char integer_string[32];
|
|
sprintf(integer_string, "SEH, code: %lu\n", GetExceptionCode());
|
|
throw std::exception(integer_string);
|
|
}
|
|
}
|
|
|
|
//static
|
|
bool LLWindowWin32::sIsClassRegistered = false;
|
|
|
|
bool LLWindowWin32::sLanguageTextInputAllowed = true;
|
|
bool LLWindowWin32::sWinIMEOpened = false;
|
|
HKL LLWindowWin32::sWinInputLocale = 0;
|
|
DWORD LLWindowWin32::sWinIMEConversionMode = IME_CMODE_NATIVE;
|
|
DWORD LLWindowWin32::sWinIMESentenceMode = IME_SMODE_AUTOMATIC;
|
|
LLCoordWindow LLWindowWin32::sWinIMEWindowPosition(-1,-1);
|
|
|
|
static HWND sWindowHandleForMessageBox = NULL;
|
|
|
|
// The following class LLWinImm delegates Windows IMM APIs.
|
|
// It was originally introduced to support US Windows XP, on which we needed
|
|
// to dynamically load IMM32.DLL and use GetProcAddress to resolve its entry
|
|
// points. Now that that's moot, we retain this wrapper only for hooks for
|
|
// metrics.
|
|
|
|
class LLWinImm
|
|
{
|
|
public:
|
|
static bool isAvailable() { return true; }
|
|
|
|
public:
|
|
// Wrappers for IMM API.
|
|
static bool isIME(HKL hkl);
|
|
static HIMC getContext(HWND hwnd);
|
|
static bool releaseContext(HWND hwnd, HIMC himc);
|
|
static bool getOpenStatus(HIMC himc);
|
|
static bool setOpenStatus(HIMC himc, bool status);
|
|
static bool getConversionStatus(HIMC himc, LPDWORD conversion, LPDWORD sentence);
|
|
static bool setConversionStatus(HIMC himc, DWORD conversion, DWORD sentence);
|
|
static bool getCompositionWindow(HIMC himc, LPCOMPOSITIONFORM form);
|
|
static bool setCompositionWindow(HIMC himc, LPCOMPOSITIONFORM form);
|
|
static LONG getCompositionString(HIMC himc, DWORD index, LPVOID data, DWORD length);
|
|
static bool setCompositionString(HIMC himc, DWORD index, LPVOID pComp, DWORD compLength, LPVOID pRead, DWORD readLength);
|
|
static bool setCompositionFont(HIMC himc, LPLOGFONTW logfont);
|
|
static bool setCandidateWindow(HIMC himc, LPCANDIDATEFORM candidate_form);
|
|
static bool notifyIME(HIMC himc, DWORD action, DWORD index, DWORD value);
|
|
};
|
|
|
|
// static
|
|
bool LLWinImm::isIME(HKL hkl)
|
|
{
|
|
return ImmIsIME(hkl);
|
|
}
|
|
|
|
// static
|
|
HIMC LLWinImm::getContext(HWND hwnd)
|
|
{
|
|
return ImmGetContext(hwnd);
|
|
}
|
|
|
|
//static
|
|
bool LLWinImm::releaseContext(HWND hwnd, HIMC himc)
|
|
{
|
|
return ImmReleaseContext(hwnd, himc);
|
|
}
|
|
|
|
// static
|
|
bool LLWinImm::getOpenStatus(HIMC himc)
|
|
{
|
|
return ImmGetOpenStatus(himc);
|
|
}
|
|
|
|
// static
|
|
bool LLWinImm::setOpenStatus(HIMC himc, bool status)
|
|
{
|
|
return ImmSetOpenStatus(himc, status);
|
|
}
|
|
|
|
// static
|
|
bool LLWinImm::getConversionStatus(HIMC himc, LPDWORD conversion, LPDWORD sentence)
|
|
{
|
|
return ImmGetConversionStatus(himc, conversion, sentence);
|
|
}
|
|
|
|
// static
|
|
bool LLWinImm::setConversionStatus(HIMC himc, DWORD conversion, DWORD sentence)
|
|
{
|
|
return ImmSetConversionStatus(himc, conversion, sentence);
|
|
}
|
|
|
|
// static
|
|
bool LLWinImm::getCompositionWindow(HIMC himc, LPCOMPOSITIONFORM form)
|
|
{
|
|
return ImmGetCompositionWindow(himc, form);
|
|
}
|
|
|
|
// static
|
|
bool LLWinImm::setCompositionWindow(HIMC himc, LPCOMPOSITIONFORM form)
|
|
{
|
|
return ImmSetCompositionWindow(himc, form);
|
|
}
|
|
|
|
|
|
// static
|
|
LONG LLWinImm::getCompositionString(HIMC himc, DWORD index, LPVOID data, DWORD length)
|
|
{
|
|
return ImmGetCompositionString(himc, index, data, length);
|
|
}
|
|
|
|
|
|
// static
|
|
bool LLWinImm::setCompositionString(HIMC himc, DWORD index, LPVOID pComp, DWORD compLength, LPVOID pRead, DWORD readLength)
|
|
{
|
|
return ImmSetCompositionString(himc, index, pComp, compLength, pRead, readLength);
|
|
}
|
|
|
|
// static
|
|
bool LLWinImm::setCompositionFont(HIMC himc, LPLOGFONTW pFont)
|
|
{
|
|
return ImmSetCompositionFont(himc, pFont);
|
|
}
|
|
|
|
// static
|
|
bool LLWinImm::setCandidateWindow(HIMC himc, LPCANDIDATEFORM form)
|
|
{
|
|
return ImmSetCandidateWindow(himc, form);
|
|
}
|
|
|
|
// static
|
|
bool LLWinImm::notifyIME(HIMC himc, DWORD action, DWORD index, DWORD value)
|
|
{
|
|
return ImmNotifyIME(himc, action, index, value);
|
|
}
|
|
|
|
|
|
|
|
class LLMonitorInfo
|
|
{
|
|
public:
|
|
|
|
std::vector<std::string> getResolutionsList() { return mResList; }
|
|
|
|
LLMonitorInfo()
|
|
{
|
|
EnumDisplayMonitors(0, 0, MonitorEnum, (LPARAM)this);
|
|
}
|
|
|
|
private:
|
|
|
|
static BOOL CALLBACK MonitorEnum(HMONITOR hMon, HDC hdc, LPRECT lprcMonitor, LPARAM pData)
|
|
{
|
|
int monitor_width = lprcMonitor->right - lprcMonitor->left;
|
|
int monitor_height = lprcMonitor->bottom - lprcMonitor->top;
|
|
|
|
std::ostringstream sstream;
|
|
sstream << monitor_width << "x" << monitor_height;;
|
|
std::string res = sstream.str();
|
|
|
|
LLMonitorInfo* pThis = reinterpret_cast<LLMonitorInfo*>(pData);
|
|
pThis->mResList.push_back(res);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
std::vector<std::string> mResList;
|
|
};
|
|
|
|
static LLMonitorInfo sMonitorInfo;
|
|
|
|
|
|
// Thread that owns the Window Handle
|
|
// This whole struct is private to LLWindowWin32, which needs to mess with its
|
|
// members, which is why it's a struct rather than a class. In effect, we make
|
|
// the containing class a friend.
|
|
struct LLWindowWin32::LLWindowWin32Thread : public LL::ThreadPool
|
|
{
|
|
static const int MAX_QUEUE_SIZE = 2048;
|
|
|
|
LLThreadSafeQueue<MSG> mMessageQueue;
|
|
|
|
LLWindowWin32Thread();
|
|
|
|
void run() override;
|
|
void close() override;
|
|
|
|
// closes queue, wakes thread, waits until thread closes
|
|
void wakeAndDestroy();
|
|
|
|
void glReady()
|
|
{
|
|
mGLReady = true;
|
|
}
|
|
|
|
// Use DXGI to check memory (because WMI doesn't report more than 4Gb)
|
|
void checkDXMem();
|
|
|
|
/// called by main thread to post work to this window thread
|
|
template <typename CALLABLE>
|
|
void post(CALLABLE&& func)
|
|
{
|
|
// Ignore bool return. Shutdown timing is tricky: the main thread can
|
|
// end up trying to post a cursor position after having closed the
|
|
// WorkQueue.
|
|
getQueue().post(std::forward<CALLABLE>(func));
|
|
}
|
|
|
|
/**
|
|
* Like post(), Post() is a way of conveying a single work item to this
|
|
* thread. Its virtue is that it will definitely be executed "soon" rather
|
|
* than potentially waiting for the next frame: it uses PostMessage() to
|
|
* break us out of the window thread's blocked GetMessage() call. It's
|
|
* more expensive, though, not only from the Windows API latency of
|
|
* PostMessage() and GetMessage(), but also because it involves heap
|
|
* allocation and release.
|
|
*
|
|
* Require HWND from caller, even though we store an HWND locally.
|
|
* Otherwise, if our mWindowHandle was accessed from both threads, we'd
|
|
* have to protect it with a mutex.
|
|
*/
|
|
template <typename CALLABLE>
|
|
void Post(HWND windowHandle, CALLABLE&& func)
|
|
{
|
|
// Move func to the heap. If we knew FuncType could fit into LPARAM,
|
|
// we could simply instantiate FuncType and pass it by value. But
|
|
// since we don't, we must put that on the heap as well as the
|
|
// internal heap allocation it likely requires to store func.
|
|
auto ptr = new FuncType(std::move(func));
|
|
WPARAM wparam{ 0xF1C };
|
|
LL_DEBUGS("Window") << "PostMessage(" << std::hex << windowHandle
|
|
<< ", " << WM_POST_FUNCTION_
|
|
<< ", " << wparam << std::dec << LL_ENDL;
|
|
PostMessage(windowHandle, WM_POST_FUNCTION_, wparam, LPARAM(ptr));
|
|
}
|
|
|
|
using FuncType = std::function<void()>;
|
|
// call GetMessage() and pull enqueue messages for later processing
|
|
HWND mWindowHandleThrd = NULL;
|
|
HDC mhDCThrd = 0;
|
|
|
|
// *HACK: Attempt to prevent startup crashes by deferring memory accounting
|
|
// until after some graphics setup. See SL-20177. -Cosmic,2023-09-18
|
|
bool mGLReady = false;
|
|
bool mGotGLBuffer = false;
|
|
};
|
|
|
|
|
|
LLWindowWin32::LLWindowWin32(LLWindowCallbacks* callbacks,
|
|
const std::string& title, const std::string& name, S32 x, S32 y, S32 width,
|
|
S32 height, U32 flags,
|
|
bool fullscreen, bool clearBg,
|
|
bool enable_vsync, bool use_gl,
|
|
bool ignore_pixel_depth,
|
|
U32 fsaa_samples,
|
|
U32 max_cores,
|
|
F32 max_gl_version)
|
|
:
|
|
LLWindow(callbacks, fullscreen, flags),
|
|
mMaxGLVersion(max_gl_version),
|
|
mMaxCores(max_cores)
|
|
{
|
|
sMainThreadId = LLThread::currentID();
|
|
mWindowThread = new LLWindowWin32Thread();
|
|
|
|
//MAINT-516 -- force a load of opengl32.dll just in case windows went sideways
|
|
LoadLibrary(L"opengl32.dll");
|
|
|
|
|
|
if (mMaxCores != 0)
|
|
{
|
|
HANDLE hProcess = GetCurrentProcess();
|
|
mMaxCores = llmin(mMaxCores, (U32) 64);
|
|
DWORD_PTR mask = 0;
|
|
|
|
for (U32 i = 0; i < mMaxCores; ++i)
|
|
{
|
|
mask |= ((DWORD_PTR) 1) << i;
|
|
}
|
|
|
|
SetProcessAffinityMask(hProcess, mask);
|
|
}
|
|
|
|
#if 0 // this is probably a bad idea, but keep it in your back pocket if you see what looks like
|
|
// process deprioritization during profiles
|
|
// force high thread priority
|
|
HANDLE hProcess = GetCurrentProcess();
|
|
|
|
if (hProcess)
|
|
{
|
|
int priority = GetPriorityClass(hProcess);
|
|
if (priority < REALTIME_PRIORITY_CLASS)
|
|
{
|
|
if (SetPriorityClass(hProcess, REALTIME_PRIORITY_CLASS))
|
|
{
|
|
LL_INFOS() << "Set process priority to REALTIME_PRIORITY_CLASS" << LL_ENDL;
|
|
}
|
|
else
|
|
{
|
|
LL_INFOS() << "Failed to set process priority: " << std::hex << GetLastError() << LL_ENDL;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if 0 // this is also probably a bad idea, but keep it in your back pocket for getting main thread off of background thread cores (see also LLThread::threadRun)
|
|
HANDLE hThread = GetCurrentThread();
|
|
|
|
SYSTEM_INFO sysInfo;
|
|
|
|
GetSystemInfo(&sysInfo);
|
|
U32 core_count = sysInfo.dwNumberOfProcessors;
|
|
|
|
if (max_cores != 0)
|
|
{
|
|
core_count = llmin(core_count, max_cores);
|
|
}
|
|
|
|
if (hThread)
|
|
{
|
|
int priority = GetThreadPriority(hThread);
|
|
|
|
if (priority < THREAD_PRIORITY_TIME_CRITICAL)
|
|
{
|
|
if (SetThreadPriority(hThread, THREAD_PRIORITY_TIME_CRITICAL))
|
|
{
|
|
LL_INFOS() << "Set thread priority to THREAD_PRIORITY_TIME_CRITICAL" << LL_ENDL;
|
|
}
|
|
else
|
|
{
|
|
LL_INFOS() << "Failed to set thread priority: " << std::hex << GetLastError() << LL_ENDL;
|
|
}
|
|
|
|
// tell main thread to prefer core 0
|
|
SetThreadIdealProcessor(hThread, 0);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
mFSAASamples = fsaa_samples;
|
|
mIconResource = gIconResource;
|
|
mOverrideAspectRatio = 0.f;
|
|
mNativeAspectRatio = 0.f;
|
|
mInputProcessingPaused = false;
|
|
mPreeditor = NULL;
|
|
mKeyCharCode = 0;
|
|
mKeyScanCode = 0;
|
|
mKeyVirtualKey = 0;
|
|
mhDC = NULL;
|
|
mhRC = NULL;
|
|
memset(mCurrentGammaRamp, 0, sizeof(mCurrentGammaRamp));
|
|
memset(mPrevGammaRamp, 0, sizeof(mPrevGammaRamp));
|
|
mCustomGammaSet = false;
|
|
mWindowHandle = NULL;
|
|
|
|
mRect = {0, 0, 0, 0};
|
|
mClientRect = {0, 0, 0, 0};
|
|
|
|
if (!SystemParametersInfo(SPI_GETMOUSEVANISH, 0, &mMouseVanish, 0))
|
|
{
|
|
mMouseVanish = true;
|
|
}
|
|
|
|
// Initialize the keyboard
|
|
gKeyboard = new LLKeyboardWin32();
|
|
gKeyboard->setCallbacks(callbacks);
|
|
|
|
// Initialize the Drag and Drop functionality
|
|
mDragDrop = new LLDragDropWin32;
|
|
|
|
// Initialize (boot strap) the Language text input management,
|
|
// based on the system's (user's) default settings.
|
|
allowLanguageTextInput(mPreeditor, false);
|
|
|
|
WNDCLASS wc;
|
|
RECT window_rect;
|
|
|
|
// Set the window title
|
|
if (title.empty())
|
|
{
|
|
mWindowTitle = new WCHAR[50];
|
|
wsprintf(mWindowTitle, L"OpenGL Window");
|
|
}
|
|
else
|
|
{
|
|
mWindowTitle = new WCHAR[256]; // Assume title length < 255 chars.
|
|
mbstowcs(mWindowTitle, title.c_str(), 255);
|
|
mWindowTitle[255] = 0;
|
|
}
|
|
|
|
// Set the window class name
|
|
if (name.empty())
|
|
{
|
|
mWindowClassName = new WCHAR[50];
|
|
wsprintf(mWindowClassName, L"OpenGL Window");
|
|
}
|
|
else
|
|
{
|
|
mWindowClassName = new WCHAR[256]; // Assume title length < 255 chars.
|
|
mbstowcs(mWindowClassName, name.c_str(), 255);
|
|
mWindowClassName[255] = 0;
|
|
}
|
|
|
|
|
|
// We're not clipping yet
|
|
SetRect( &mOldMouseClip, 0, 0, 0, 0 );
|
|
|
|
// Make an instance of our window then define the window class
|
|
mhInstance = GetModuleHandle(NULL);
|
|
|
|
// Init Direct Input - needed for joystick / Spacemouse
|
|
|
|
LPDIRECTINPUT8 di8_interface;
|
|
HRESULT status = DirectInput8Create(
|
|
mhInstance, // HINSTANCE hinst,
|
|
DIRECTINPUT_VERSION, // DWORD dwVersion,
|
|
IID_IDirectInput8, // REFIID riidltf,
|
|
(LPVOID*)&di8_interface, // LPVOID * ppvOut,
|
|
NULL // LPUNKNOWN punkOuter
|
|
);
|
|
if (status == DI_OK)
|
|
{
|
|
gDirectInput8 = di8_interface;
|
|
}
|
|
|
|
mSwapMethod = SWAP_METHOD_UNDEFINED;
|
|
|
|
// No WPARAM yet.
|
|
mLastSizeWParam = 0;
|
|
|
|
// Windows GDI rects don't include rightmost pixel
|
|
window_rect.left = (long) 0;
|
|
window_rect.right = (long) width;
|
|
window_rect.top = (long) 0;
|
|
window_rect.bottom = (long) height;
|
|
|
|
// Grab screen size to sanitize the window
|
|
S32 window_border_y = GetSystemMetrics(SM_CYBORDER);
|
|
S32 virtual_screen_x = GetSystemMetrics(SM_XVIRTUALSCREEN);
|
|
S32 virtual_screen_y = GetSystemMetrics(SM_YVIRTUALSCREEN);
|
|
S32 virtual_screen_width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
|
|
S32 virtual_screen_height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
|
|
|
|
if (x < virtual_screen_x) x = virtual_screen_x;
|
|
if (y < virtual_screen_y - window_border_y) y = virtual_screen_y - window_border_y;
|
|
|
|
if (x + width > virtual_screen_x + virtual_screen_width) x = virtual_screen_x + virtual_screen_width - width;
|
|
if (y + height > virtual_screen_y + virtual_screen_height) y = virtual_screen_y + virtual_screen_height - height;
|
|
|
|
if (!sIsClassRegistered)
|
|
{
|
|
// Force redraw when resized and create a private device context
|
|
|
|
// Makes double click messages.
|
|
wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC | CS_DBLCLKS;
|
|
|
|
// Set message handler function
|
|
wc.lpfnWndProc = (WNDPROC) mainWindowProc;
|
|
|
|
// unused
|
|
wc.cbClsExtra = 0;
|
|
wc.cbWndExtra = 0;
|
|
|
|
wc.hInstance = mhInstance;
|
|
wc.hIcon = LoadIcon(mhInstance, mIconResource);
|
|
|
|
// We will set the cursor ourselves
|
|
wc.hCursor = NULL;
|
|
|
|
// background color is not used
|
|
if (clearBg)
|
|
{
|
|
wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
|
|
}
|
|
else
|
|
{
|
|
wc.hbrBackground = (HBRUSH) NULL;
|
|
}
|
|
|
|
// we don't use windows menus
|
|
wc.lpszMenuName = NULL;
|
|
|
|
wc.lpszClassName = mWindowClassName;
|
|
|
|
if (!RegisterClass(&wc))
|
|
{
|
|
OSMessageBox(mCallbacks->translateString("MBRegClassFailed"),
|
|
mCallbacks->translateString("MBError"), OSMB_OK);
|
|
return;
|
|
}
|
|
sIsClassRegistered = true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Get the current refresh rate
|
|
//-----------------------------------------------------------------------
|
|
|
|
DEVMODE dev_mode;
|
|
::ZeroMemory(&dev_mode, sizeof(DEVMODE));
|
|
dev_mode.dmSize = sizeof(DEVMODE);
|
|
DWORD current_refresh;
|
|
if (EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &dev_mode))
|
|
{
|
|
current_refresh = dev_mode.dmDisplayFrequency;
|
|
mNativeAspectRatio = ((F32)dev_mode.dmPelsWidth) / ((F32)dev_mode.dmPelsHeight);
|
|
}
|
|
else
|
|
{
|
|
current_refresh = 60;
|
|
}
|
|
mRefreshRate = current_refresh;
|
|
//-----------------------------------------------------------------------
|
|
// Drop resolution and go fullscreen
|
|
// use a display mode with our desired size and depth, with a refresh
|
|
// rate as close at possible to the users' default
|
|
//-----------------------------------------------------------------------
|
|
if (mFullscreen)
|
|
{
|
|
bool success = false;
|
|
DWORD closest_refresh = 0;
|
|
|
|
for (S32 mode_num = 0;; mode_num++)
|
|
{
|
|
if (!EnumDisplaySettings(NULL, mode_num, &dev_mode))
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (dev_mode.dmPelsWidth == width &&
|
|
dev_mode.dmPelsHeight == height &&
|
|
dev_mode.dmBitsPerPel == BITS_PER_PIXEL)
|
|
{
|
|
success = true;
|
|
if ((dev_mode.dmDisplayFrequency - current_refresh)
|
|
< (closest_refresh - current_refresh))
|
|
{
|
|
closest_refresh = dev_mode.dmDisplayFrequency;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (closest_refresh == 0)
|
|
{
|
|
LL_WARNS("Window") << "Couldn't find display mode " << width << " by " << height << " at " << BITS_PER_PIXEL << " bits per pixel" << LL_ENDL;
|
|
//success = false;
|
|
|
|
if (!EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &dev_mode))
|
|
{
|
|
success = false;
|
|
}
|
|
else
|
|
{
|
|
if (dev_mode.dmBitsPerPel == BITS_PER_PIXEL)
|
|
{
|
|
LL_WARNS("Window") << "Current BBP is OK falling back to that" << LL_ENDL;
|
|
window_rect.right=width=dev_mode.dmPelsWidth;
|
|
window_rect.bottom=height=dev_mode.dmPelsHeight;
|
|
success = true;
|
|
}
|
|
else
|
|
{
|
|
LL_WARNS("Window") << "Current BBP is BAD" << LL_ENDL;
|
|
success = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we found a good resolution, use it.
|
|
if (success)
|
|
{
|
|
success = setDisplayResolution(width, height, BITS_PER_PIXEL, closest_refresh);
|
|
}
|
|
|
|
// Keep a copy of the actual current device mode in case we minimize
|
|
// and change the screen resolution. JC
|
|
EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &dev_mode);
|
|
|
|
// If it failed, we don't want to run fullscreen
|
|
if (success)
|
|
{
|
|
mFullscreen = true;
|
|
mFullscreenWidth = dev_mode.dmPelsWidth;
|
|
mFullscreenHeight = dev_mode.dmPelsHeight;
|
|
mFullscreenBits = dev_mode.dmBitsPerPel;
|
|
mFullscreenRefresh = dev_mode.dmDisplayFrequency;
|
|
|
|
LL_INFOS("Window") << "Running at " << dev_mode.dmPelsWidth
|
|
<< "x" << dev_mode.dmPelsHeight
|
|
<< "x" << dev_mode.dmBitsPerPel
|
|
<< " @ " << dev_mode.dmDisplayFrequency
|
|
<< LL_ENDL;
|
|
}
|
|
else
|
|
{
|
|
mFullscreen = false;
|
|
mFullscreenWidth = -1;
|
|
mFullscreenHeight = -1;
|
|
mFullscreenBits = -1;
|
|
mFullscreenRefresh = -1;
|
|
|
|
std::map<std::string,std::string> args;
|
|
args["[WIDTH]"] = llformat("%d", width);
|
|
args["[HEIGHT]"] = llformat ("%d", height);
|
|
OSMessageBox(mCallbacks->translateString("MBFullScreenErr", args),
|
|
mCallbacks->translateString("MBError"), OSMB_OK);
|
|
}
|
|
}
|
|
|
|
// TODO: add this after resolving _WIN32_WINNT issue
|
|
// if (!fullscreen)
|
|
// {
|
|
// TRACKMOUSEEVENT track_mouse_event;
|
|
// track_mouse_event.cbSize = sizeof( TRACKMOUSEEVENT );
|
|
// track_mouse_event.dwFlags = TME_LEAVE;
|
|
// track_mouse_event.hwndTrack = mWindowHandle;
|
|
// track_mouse_event.dwHoverTime = HOVER_DEFAULT;
|
|
// TrackMouseEvent( &track_mouse_event );
|
|
// }
|
|
|
|
// SL-12971 dual GPU display
|
|
DISPLAY_DEVICEA display_device;
|
|
int display_index = -1;
|
|
DWORD display_flags = 0; // EDD_GET_DEVICE_INTERFACE_NAME ?
|
|
const size_t display_bytes = sizeof(display_device);
|
|
|
|
do
|
|
{
|
|
if (display_index >= 0)
|
|
{
|
|
// CHAR DeviceName [ 32] Adapter name
|
|
// CHAR DeviceString[128]
|
|
CHAR text[256];
|
|
|
|
size_t name_len = strlen(display_device.DeviceName );
|
|
size_t desc_len = strlen(display_device.DeviceString);
|
|
|
|
CHAR *name = name_len ? display_device.DeviceName : "???";
|
|
CHAR *desc = desc_len ? display_device.DeviceString : "???";
|
|
|
|
sprintf(text, "Display Device %d: %s, %s", display_index, name, desc);
|
|
LL_INFOS("Window") << text << LL_ENDL;
|
|
}
|
|
|
|
::ZeroMemory(&display_device,display_bytes);
|
|
display_device.cb = display_bytes;
|
|
|
|
display_index++;
|
|
} while( EnumDisplayDevicesA(NULL, display_index, &display_device, display_flags ));
|
|
|
|
LL_INFOS("Window") << "Total Display Devices: " << display_index << LL_ENDL;
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Create GL drawing context
|
|
//-----------------------------------------------------------------------
|
|
LLCoordScreen windowPos(x,y);
|
|
LLCoordScreen windowSize(window_rect.right - window_rect.left,
|
|
window_rect.bottom - window_rect.top);
|
|
if (!switchContext(mFullscreen, windowSize, enable_vsync, &windowPos))
|
|
{
|
|
return;
|
|
}
|
|
|
|
//start with arrow cursor
|
|
initCursors();
|
|
setCursor( UI_CURSOR_ARROW );
|
|
|
|
mRawMouse.usUsagePage = 0x01; // HID_USAGE_PAGE_GENERIC
|
|
mRawMouse.usUsage = 0x02; // HID_USAGE_GENERIC_MOUSE
|
|
mRawMouse.dwFlags = 0; // adds mouse and also ignores legacy mouse messages
|
|
mRawMouse.hwndTarget = 0;
|
|
|
|
RegisterRawInputDevices(&mRawMouse, 1, sizeof(mRawMouse));
|
|
|
|
// Initialize (boot strap) the Language text input management,
|
|
// based on the system's (or user's) default settings.
|
|
allowLanguageTextInput(NULL, false);
|
|
}
|
|
|
|
|
|
LLWindowWin32::~LLWindowWin32()
|
|
{
|
|
if (sWindowHandleForMessageBox == mWindowHandle)
|
|
{
|
|
sWindowHandleForMessageBox = NULL;
|
|
}
|
|
|
|
delete mDragDrop;
|
|
|
|
delete [] mWindowTitle;
|
|
mWindowTitle = NULL;
|
|
|
|
delete [] mSupportedResolutions;
|
|
mSupportedResolutions = NULL;
|
|
|
|
delete [] mWindowClassName;
|
|
mWindowClassName = NULL;
|
|
|
|
delete mWindowThread;
|
|
}
|
|
|
|
void LLWindowWin32::show()
|
|
{
|
|
LL_DEBUGS("Window") << "Setting window to show" << LL_ENDL;
|
|
ShowWindow(mWindowHandle, SW_SHOW);
|
|
SetForegroundWindow(mWindowHandle);
|
|
SetFocus(mWindowHandle);
|
|
}
|
|
|
|
void LLWindowWin32::hide()
|
|
{
|
|
setMouseClipping(false);
|
|
ShowWindow(mWindowHandle, SW_HIDE);
|
|
}
|
|
|
|
//virtual
|
|
void LLWindowWin32::minimize()
|
|
{
|
|
setMouseClipping(false);
|
|
showCursor();
|
|
ShowWindow(mWindowHandle, SW_MINIMIZE);
|
|
}
|
|
|
|
//virtual
|
|
void LLWindowWin32::restore()
|
|
{
|
|
ShowWindow(mWindowHandle, SW_RESTORE);
|
|
SetForegroundWindow(mWindowHandle);
|
|
SetFocus(mWindowHandle);
|
|
}
|
|
|
|
// See SL-12170
|
|
// According to callstack "c0000005 Access violation" happened inside __try block,
|
|
// deep in DestroyWindow and crashed viewer, which shouldn't be possible.
|
|
// I tried manually causing this exception and it was caught without issues, so
|
|
// I'm turning off optimizations for this part to be sure code executes as intended
|
|
// (it is a straw, but I have no idea why else __try can get overruled)
|
|
#pragma optimize("", off)
|
|
bool destroy_window_handler(HWND hWnd)
|
|
{
|
|
bool res;
|
|
__try
|
|
{
|
|
res = DestroyWindow(hWnd);
|
|
}
|
|
__except (EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
res = false;
|
|
}
|
|
return res;
|
|
}
|
|
#pragma optimize("", on)
|
|
|
|
// close() destroys all OS-specific code associated with a window.
|
|
// Usually called from LLWindowManager::destroyWindow()
|
|
void LLWindowWin32::close()
|
|
{
|
|
LL_DEBUGS("Window") << "Closing LLWindowWin32" << LL_ENDL;
|
|
// Is window is already closed?
|
|
if (!mWindowHandle)
|
|
{
|
|
return;
|
|
}
|
|
|
|
mDragDrop->reset();
|
|
|
|
|
|
// Go back to screen mode written in the registry.
|
|
if (mFullscreen)
|
|
{
|
|
resetDisplayResolution();
|
|
}
|
|
|
|
// Make sure cursor is visible and we haven't mangled the clipping state.
|
|
showCursor();
|
|
setMouseClipping(false);
|
|
if (gKeyboard)
|
|
{
|
|
gKeyboard->resetKeys();
|
|
}
|
|
|
|
// Clean up remaining GL state
|
|
if (gGLManager.mInited)
|
|
{
|
|
LL_INFOS("Window") << "Cleaning up GL" << LL_ENDL;
|
|
gGLManager.shutdownGL();
|
|
}
|
|
|
|
LL_DEBUGS("Window") << "Releasing Context" << LL_ENDL;
|
|
if (mhRC)
|
|
{
|
|
if (!wglMakeCurrent(NULL, NULL))
|
|
{
|
|
LL_WARNS("Window") << "Release of DC and RC failed" << LL_ENDL;
|
|
}
|
|
|
|
if (!wglDeleteContext(mhRC))
|
|
{
|
|
LL_WARNS("Window") << "Release of rendering context failed" << LL_ENDL;
|
|
}
|
|
|
|
mhRC = NULL;
|
|
}
|
|
|
|
// Restore gamma to the system values.
|
|
restoreGamma();
|
|
|
|
LL_DEBUGS("Window") << "Destroying Window" << LL_ENDL;
|
|
|
|
if (sWindowHandleForMessageBox == mWindowHandle)
|
|
{
|
|
sWindowHandleForMessageBox = NULL;
|
|
}
|
|
|
|
mhDC = NULL;
|
|
mWindowHandle = NULL;
|
|
|
|
mWindowThread->wakeAndDestroy();
|
|
}
|
|
|
|
bool LLWindowWin32::isValid()
|
|
{
|
|
return (mWindowHandle != NULL);
|
|
}
|
|
|
|
bool LLWindowWin32::getVisible()
|
|
{
|
|
return (mWindowHandle && IsWindowVisible(mWindowHandle));
|
|
}
|
|
|
|
bool LLWindowWin32::getMinimized()
|
|
{
|
|
return (mWindowHandle && IsIconic(mWindowHandle));
|
|
}
|
|
|
|
bool LLWindowWin32::getMaximized()
|
|
{
|
|
return (mWindowHandle && IsZoomed(mWindowHandle));
|
|
}
|
|
|
|
bool LLWindowWin32::maximize()
|
|
{
|
|
bool success = false;
|
|
if (!mWindowHandle) return success;
|
|
|
|
mWindowThread->post([=]
|
|
{
|
|
WINDOWPLACEMENT placement;
|
|
placement.length = sizeof(WINDOWPLACEMENT);
|
|
|
|
if (GetWindowPlacement(mWindowHandle, &placement))
|
|
{
|
|
placement.showCmd = SW_MAXIMIZE;
|
|
SetWindowPlacement(mWindowHandle, &placement);
|
|
}
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
bool LLWindowWin32::getFullscreen()
|
|
{
|
|
return mFullscreen;
|
|
}
|
|
|
|
bool LLWindowWin32::getPosition(LLCoordScreen *position)
|
|
{
|
|
position->mX = mRect.left;
|
|
position->mY = mRect.top;
|
|
return true;
|
|
}
|
|
|
|
bool LLWindowWin32::getSize(LLCoordScreen *size)
|
|
{
|
|
size->mX = mRect.right - mRect.left;
|
|
size->mY = mRect.bottom - mRect.top;
|
|
return true;
|
|
}
|
|
|
|
bool LLWindowWin32::getSize(LLCoordWindow *size)
|
|
{
|
|
size->mX = mClientRect.right - mClientRect.left;
|
|
size->mY = mClientRect.bottom - mClientRect.top;
|
|
return true;
|
|
}
|
|
|
|
bool LLWindowWin32::setPosition(const LLCoordScreen position)
|
|
{
|
|
LLCoordScreen size;
|
|
|
|
if (!mWindowHandle)
|
|
{
|
|
return false;
|
|
}
|
|
getSize(&size);
|
|
moveWindow(position, size);
|
|
return true;
|
|
}
|
|
|
|
bool LLWindowWin32::setSizeImpl(const LLCoordScreen size)
|
|
{
|
|
LLCoordScreen position;
|
|
|
|
getPosition(&position);
|
|
if (!mWindowHandle)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
mWindowThread->post([=]()
|
|
{
|
|
WINDOWPLACEMENT placement;
|
|
placement.length = sizeof(WINDOWPLACEMENT);
|
|
|
|
if (GetWindowPlacement(mWindowHandle, &placement))
|
|
{
|
|
placement.showCmd = SW_RESTORE;
|
|
SetWindowPlacement(mWindowHandle, &placement);
|
|
}
|
|
});
|
|
|
|
moveWindow(position, size);
|
|
return true;
|
|
}
|
|
|
|
bool LLWindowWin32::setSizeImpl(const LLCoordWindow size)
|
|
{
|
|
RECT window_rect = {0, 0, size.mX, size.mY };
|
|
DWORD dw_ex_style = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
|
|
DWORD dw_style = WS_OVERLAPPEDWINDOW;
|
|
|
|
AdjustWindowRectEx(&window_rect, dw_style, FALSE, dw_ex_style);
|
|
|
|
return setSizeImpl(LLCoordScreen(window_rect.right - window_rect.left, window_rect.bottom - window_rect.top));
|
|
}
|
|
|
|
// changing fullscreen resolution
|
|
bool LLWindowWin32::switchContext(bool fullscreen, const LLCoordScreen& size, bool enable_vsync, const LLCoordScreen* const posp)
|
|
{
|
|
//called from main thread
|
|
GLuint pixel_format;
|
|
DEVMODE dev_mode;
|
|
::ZeroMemory(&dev_mode, sizeof(DEVMODE));
|
|
dev_mode.dmSize = sizeof(DEVMODE);
|
|
DWORD current_refresh;
|
|
DWORD dw_ex_style;
|
|
DWORD dw_style;
|
|
RECT window_rect = { 0, 0, 0, 0 };
|
|
S32 width = size.mX;
|
|
S32 height = size.mY;
|
|
bool auto_show = false;
|
|
|
|
if (mhRC)
|
|
{
|
|
auto_show = true;
|
|
resetDisplayResolution();
|
|
}
|
|
|
|
if (EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &dev_mode))
|
|
{
|
|
current_refresh = dev_mode.dmDisplayFrequency;
|
|
}
|
|
else
|
|
{
|
|
current_refresh = 60;
|
|
}
|
|
mRefreshRate = current_refresh;
|
|
|
|
gGLManager.shutdownGL();
|
|
//destroy gl context
|
|
if (mhRC)
|
|
{
|
|
if (!wglMakeCurrent(NULL, NULL))
|
|
{
|
|
LL_WARNS("Window") << "Release of DC and RC failed" << LL_ENDL;
|
|
}
|
|
|
|
if (!wglDeleteContext(mhRC))
|
|
{
|
|
LL_WARNS("Window") << "Release of rendering context failed" << LL_ENDL;
|
|
}
|
|
|
|
mhRC = NULL;
|
|
}
|
|
|
|
if (fullscreen)
|
|
{
|
|
mFullscreen = true;
|
|
bool success = false;
|
|
DWORD closest_refresh = 0;
|
|
|
|
for (S32 mode_num = 0;; mode_num++)
|
|
{
|
|
if (!EnumDisplaySettings(NULL, mode_num, &dev_mode))
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (dev_mode.dmPelsWidth == width &&
|
|
dev_mode.dmPelsHeight == height &&
|
|
dev_mode.dmBitsPerPel == BITS_PER_PIXEL)
|
|
{
|
|
success = true;
|
|
if ((dev_mode.dmDisplayFrequency - current_refresh)
|
|
< (closest_refresh - current_refresh))
|
|
{
|
|
closest_refresh = dev_mode.dmDisplayFrequency;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (closest_refresh == 0)
|
|
{
|
|
LL_WARNS("Window") << "Couldn't find display mode " << width << " by " << height << " at " << BITS_PER_PIXEL << " bits per pixel" << LL_ENDL;
|
|
return false;
|
|
}
|
|
|
|
// If we found a good resolution, use it.
|
|
if (success)
|
|
{
|
|
success = setDisplayResolution(width, height, BITS_PER_PIXEL, closest_refresh);
|
|
}
|
|
|
|
// Keep a copy of the actual current device mode in case we minimize
|
|
// and change the screen resolution. JC
|
|
EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &dev_mode);
|
|
|
|
if (success)
|
|
{
|
|
mFullscreen = true;
|
|
mFullscreenWidth = dev_mode.dmPelsWidth;
|
|
mFullscreenHeight = dev_mode.dmPelsHeight;
|
|
mFullscreenBits = dev_mode.dmBitsPerPel;
|
|
mFullscreenRefresh = dev_mode.dmDisplayFrequency;
|
|
|
|
LL_INFOS("Window") << "Running at " << dev_mode.dmPelsWidth
|
|
<< "x" << dev_mode.dmPelsHeight
|
|
<< "x" << dev_mode.dmBitsPerPel
|
|
<< " @ " << dev_mode.dmDisplayFrequency
|
|
<< LL_ENDL;
|
|
|
|
window_rect.left = (long)0;
|
|
window_rect.right = (long)width; // Windows GDI rects don't include rightmost pixel
|
|
window_rect.top = (long)0;
|
|
window_rect.bottom = (long)height;
|
|
dw_ex_style = WS_EX_APPWINDOW;
|
|
dw_style = WS_POPUP;
|
|
|
|
// Move window borders out not to cover window contents.
|
|
// This converts client rect to window rect, i.e. expands it by the window border size.
|
|
AdjustWindowRectEx(&window_rect, dw_style, FALSE, dw_ex_style);
|
|
}
|
|
// If it failed, we don't want to run fullscreen
|
|
else
|
|
{
|
|
mFullscreen = false;
|
|
mFullscreenWidth = -1;
|
|
mFullscreenHeight = -1;
|
|
mFullscreenBits = -1;
|
|
mFullscreenRefresh = -1;
|
|
|
|
LL_INFOS("Window") << "Unable to run fullscreen at " << width << "x" << height << LL_ENDL;
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mFullscreen = false;
|
|
window_rect.left = (long)(posp ? posp->mX : 0);
|
|
window_rect.right = (long)width + window_rect.left; // Windows GDI rects don't include rightmost pixel
|
|
window_rect.top = (long)(posp ? posp->mY : 0);
|
|
window_rect.bottom = (long)height + window_rect.top;
|
|
// Window with an edge
|
|
dw_ex_style = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
|
|
dw_style = WS_OVERLAPPEDWINDOW;
|
|
}
|
|
|
|
|
|
// don't post quit messages when destroying old windows
|
|
mPostQuit = false;
|
|
|
|
|
|
// create window
|
|
LL_DEBUGS("Window") << "Creating window with X: " << window_rect.left
|
|
<< " Y: " << window_rect.top
|
|
<< " Width: " << (window_rect.right - window_rect.left)
|
|
<< " Height: " << (window_rect.bottom - window_rect.top)
|
|
<< " Fullscreen: " << mFullscreen
|
|
<< LL_ENDL;
|
|
|
|
recreateWindow(window_rect, dw_ex_style, dw_style);
|
|
|
|
if (mWindowHandle)
|
|
{
|
|
LL_INFOS("Window") << "window is created." << LL_ENDL ;
|
|
}
|
|
else
|
|
{
|
|
LL_WARNS("Window") << "Window creation failed, code: " << GetLastError() << LL_ENDL;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Create GL drawing context
|
|
//-----------------------------------------------------------------------
|
|
static PIXELFORMATDESCRIPTOR pfd =
|
|
{
|
|
sizeof(PIXELFORMATDESCRIPTOR),
|
|
1,
|
|
PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
|
|
PFD_TYPE_RGBA,
|
|
BITS_PER_PIXEL,
|
|
0, 0, 0, 0, 0, 0, // RGB bits and shift, unused
|
|
8, // alpha bits
|
|
0, // alpha shift
|
|
0, // accum bits
|
|
0, 0, 0, 0, // accum RGBA
|
|
24, // depth bits
|
|
8, // stencil bits, avi added for stencil test
|
|
0,
|
|
PFD_MAIN_PLANE,
|
|
0,
|
|
0, 0, 0
|
|
};
|
|
|
|
if (!mhDC)
|
|
{
|
|
close();
|
|
OSMessageBox(mCallbacks->translateString("MBDevContextErr"),
|
|
mCallbacks->translateString("MBError"), OSMB_OK);
|
|
return false;
|
|
}
|
|
|
|
LL_INFOS("Window") << "Device context retrieved." << LL_ENDL ;
|
|
|
|
try
|
|
{
|
|
// Looks like ChoosePixelFormat can crash in case of faulty driver
|
|
if (!(pixel_format = SafeChoosePixelFormat(mhDC, &pfd)))
|
|
{
|
|
LL_WARNS("Window") << "ChoosePixelFormat failed, code: " << GetLastError() << LL_ENDL;
|
|
OSMessageBox(mCallbacks->translateString("MBPixelFmtErr"),
|
|
mCallbacks->translateString("MBError"), OSMB_OK);
|
|
close();
|
|
return false;
|
|
}
|
|
}
|
|
catch (...)
|
|
{
|
|
LOG_UNHANDLED_EXCEPTION("ChoosePixelFormat");
|
|
OSMessageBox(mCallbacks->translateString("MBPixelFmtErr"),
|
|
mCallbacks->translateString("MBError"), OSMB_OK);
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
LL_INFOS("Window") << "Pixel format chosen." << LL_ENDL ;
|
|
|
|
// Verify what pixel format we actually received.
|
|
if (!DescribePixelFormat(mhDC, pixel_format, sizeof(PIXELFORMATDESCRIPTOR),
|
|
&pfd))
|
|
{
|
|
OSMessageBox(mCallbacks->translateString("MBPixelFmtDescErr"),
|
|
mCallbacks->translateString("MBError"), OSMB_OK);
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
// (EXP-1765) dump pixel data to see if there is a pattern that leads to unreproducible crash
|
|
LL_INFOS("Window") << "--- begin pixel format dump ---" << LL_ENDL ;
|
|
LL_INFOS("Window") << "pixel_format is " << pixel_format << LL_ENDL ;
|
|
LL_INFOS("Window") << "pfd.nSize: " << pfd.nSize << LL_ENDL ;
|
|
LL_INFOS("Window") << "pfd.nVersion: " << pfd.nVersion << LL_ENDL ;
|
|
LL_INFOS("Window") << "pfd.dwFlags: 0x" << std::hex << pfd.dwFlags << std::dec << LL_ENDL ;
|
|
LL_INFOS("Window") << "pfd.iPixelType: " << (int)pfd.iPixelType << LL_ENDL ;
|
|
LL_INFOS("Window") << "pfd.cColorBits: " << (int)pfd.cColorBits << LL_ENDL ;
|
|
LL_INFOS("Window") << "pfd.cRedBits: " << (int)pfd.cRedBits << LL_ENDL ;
|
|
LL_INFOS("Window") << "pfd.cRedShift: " << (int)pfd.cRedShift << LL_ENDL ;
|
|
LL_INFOS("Window") << "pfd.cGreenBits: " << (int)pfd.cGreenBits << LL_ENDL ;
|
|
LL_INFOS("Window") << "pfd.cGreenShift: " << (int)pfd.cGreenShift << LL_ENDL ;
|
|
LL_INFOS("Window") << "pfd.cBlueBits: " << (int)pfd.cBlueBits << LL_ENDL ;
|
|
LL_INFOS("Window") << "pfd.cBlueShift: " << (int)pfd.cBlueShift << LL_ENDL ;
|
|
LL_INFOS("Window") << "pfd.cAlphaBits: " << (int)pfd.cAlphaBits << LL_ENDL ;
|
|
LL_INFOS("Window") << "pfd.cAlphaShift: " << (int)pfd.cAlphaShift << LL_ENDL ;
|
|
LL_INFOS("Window") << "pfd.cAccumBits: " << (int)pfd.cAccumBits << LL_ENDL ;
|
|
LL_INFOS("Window") << "pfd.cAccumRedBits: " << (int)pfd.cAccumRedBits << LL_ENDL ;
|
|
LL_INFOS("Window") << "pfd.cAccumGreenBits: " << (int)pfd.cAccumGreenBits << LL_ENDL ;
|
|
LL_INFOS("Window") << "pfd.cAccumBlueBits: " << (int)pfd.cAccumBlueBits << LL_ENDL ;
|
|
LL_INFOS("Window") << "pfd.cAccumAlphaBits: " << (int)pfd.cAccumAlphaBits << LL_ENDL ;
|
|
LL_INFOS("Window") << "pfd.cDepthBits: " << (int)pfd.cDepthBits << LL_ENDL ;
|
|
LL_INFOS("Window") << "pfd.cStencilBits: " << (int)pfd.cStencilBits << LL_ENDL ;
|
|
LL_INFOS("Window") << "pfd.cAuxBuffers: " << (int)pfd.cAuxBuffers << LL_ENDL ;
|
|
LL_INFOS("Window") << "pfd.iLayerType: " << (int)pfd.iLayerType << LL_ENDL ;
|
|
LL_INFOS("Window") << "pfd.bReserved: " << (int)pfd.bReserved << LL_ENDL ;
|
|
LL_INFOS("Window") << "pfd.dwLayerMask: " << pfd.dwLayerMask << LL_ENDL ;
|
|
LL_INFOS("Window") << "pfd.dwVisibleMask: " << pfd.dwVisibleMask << LL_ENDL ;
|
|
LL_INFOS("Window") << "pfd.dwDamageMask: " << pfd.dwDamageMask << LL_ENDL ;
|
|
LL_INFOS("Window") << "--- end pixel format dump ---" << LL_ENDL ;
|
|
|
|
if (!SetPixelFormat(mhDC, pixel_format, &pfd))
|
|
{
|
|
OSMessageBox(mCallbacks->translateString("MBPixelFmtSetErr"),
|
|
mCallbacks->translateString("MBError"), OSMB_OK);
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
|
|
if (!(mhRC = SafeCreateContext(mhDC)))
|
|
{
|
|
OSMessageBox(mCallbacks->translateString("MBGLContextErr"),
|
|
mCallbacks->translateString("MBError"), OSMB_OK);
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
if (!wglMakeCurrent(mhDC, mhRC))
|
|
{
|
|
OSMessageBox(mCallbacks->translateString("MBGLContextActErr"),
|
|
mCallbacks->translateString("MBError"), OSMB_OK);
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
LL_INFOS("Window") << "Drawing context is created." << LL_ENDL ;
|
|
|
|
gGLManager.initWGL();
|
|
|
|
if (wglChoosePixelFormatARB && wglGetPixelFormatAttribivARB)
|
|
{
|
|
// OK, at this point, use the ARB wglChoosePixelFormatsARB function to see if we
|
|
// can get exactly what we want.
|
|
GLint attrib_list[256];
|
|
S32 cur_attrib = 0;
|
|
|
|
attrib_list[cur_attrib++] = WGL_DEPTH_BITS_ARB;
|
|
attrib_list[cur_attrib++] = 24;
|
|
|
|
//attrib_list[cur_attrib++] = WGL_STENCIL_BITS_ARB; //stencil buffer is deprecated (performance penalty)
|
|
//attrib_list[cur_attrib++] = 8;
|
|
|
|
attrib_list[cur_attrib++] = WGL_DRAW_TO_WINDOW_ARB;
|
|
attrib_list[cur_attrib++] = GL_TRUE;
|
|
|
|
attrib_list[cur_attrib++] = WGL_ACCELERATION_ARB;
|
|
attrib_list[cur_attrib++] = WGL_FULL_ACCELERATION_ARB;
|
|
|
|
attrib_list[cur_attrib++] = WGL_SUPPORT_OPENGL_ARB;
|
|
attrib_list[cur_attrib++] = GL_TRUE;
|
|
|
|
attrib_list[cur_attrib++] = WGL_DOUBLE_BUFFER_ARB;
|
|
attrib_list[cur_attrib++] = GL_TRUE;
|
|
|
|
attrib_list[cur_attrib++] = WGL_COLOR_BITS_ARB;
|
|
attrib_list[cur_attrib++] = 24;
|
|
|
|
attrib_list[cur_attrib++] = WGL_ALPHA_BITS_ARB;
|
|
attrib_list[cur_attrib++] = 0;
|
|
|
|
U32 end_attrib = 0;
|
|
if (mFSAASamples > 0)
|
|
{
|
|
end_attrib = cur_attrib;
|
|
attrib_list[cur_attrib++] = WGL_SAMPLE_BUFFERS_ARB;
|
|
attrib_list[cur_attrib++] = GL_TRUE;
|
|
|
|
attrib_list[cur_attrib++] = WGL_SAMPLES_ARB;
|
|
attrib_list[cur_attrib++] = mFSAASamples;
|
|
}
|
|
|
|
// End the list
|
|
attrib_list[cur_attrib++] = 0;
|
|
|
|
GLint pixel_formats[256];
|
|
U32 num_formats = 0;
|
|
|
|
// First we try and get a 32 bit depth pixel format
|
|
BOOL result = wglChoosePixelFormatARB(mhDC, attrib_list, NULL, 256, pixel_formats, &num_formats);
|
|
|
|
while(!result && mFSAASamples > 0)
|
|
{
|
|
LL_WARNS() << "FSAASamples: " << mFSAASamples << " not supported." << LL_ENDL ;
|
|
|
|
mFSAASamples /= 2 ; //try to decrease sample pixel number until to disable anti-aliasing
|
|
if(mFSAASamples < 2)
|
|
{
|
|
mFSAASamples = 0 ;
|
|
}
|
|
|
|
if (mFSAASamples > 0)
|
|
{
|
|
attrib_list[end_attrib + 3] = mFSAASamples;
|
|
}
|
|
else
|
|
{
|
|
cur_attrib = end_attrib ;
|
|
end_attrib = 0 ;
|
|
attrib_list[cur_attrib++] = 0 ; //end
|
|
}
|
|
result = wglChoosePixelFormatARB(mhDC, attrib_list, NULL, 256, pixel_formats, &num_formats);
|
|
|
|
if(result)
|
|
{
|
|
LL_WARNS() << "Only support FSAASamples: " << mFSAASamples << LL_ENDL ;
|
|
}
|
|
}
|
|
|
|
if (!result)
|
|
{
|
|
LL_WARNS() << "mFSAASamples: " << mFSAASamples << LL_ENDL ;
|
|
|
|
close();
|
|
show_window_creation_error("Error after wglChoosePixelFormatARB 32-bit");
|
|
return false;
|
|
}
|
|
|
|
if (!num_formats)
|
|
{
|
|
if (end_attrib > 0)
|
|
{
|
|
LL_INFOS("Window") << "No valid pixel format for " << mFSAASamples << "x anti-aliasing." << LL_ENDL;
|
|
attrib_list[end_attrib] = 0;
|
|
|
|
BOOL result = wglChoosePixelFormatARB(mhDC, attrib_list, NULL, 256, pixel_formats, &num_formats);
|
|
if (!result)
|
|
{
|
|
close();
|
|
show_window_creation_error("Error after wglChoosePixelFormatARB 32-bit no AA");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!num_formats)
|
|
{
|
|
LL_INFOS("Window") << "No 32 bit z-buffer, trying 24 bits instead" << LL_ENDL;
|
|
// Try 24-bit format
|
|
attrib_list[1] = 24;
|
|
BOOL result = wglChoosePixelFormatARB(mhDC, attrib_list, NULL, 256, pixel_formats, &num_formats);
|
|
if (!result)
|
|
{
|
|
close();
|
|
show_window_creation_error("Error after wglChoosePixelFormatARB 24-bit");
|
|
return false;
|
|
}
|
|
|
|
if (!num_formats)
|
|
{
|
|
LL_WARNS("Window") << "Couldn't get 24 bit z-buffer,trying 16 bits instead!" << LL_ENDL;
|
|
attrib_list[1] = 16;
|
|
BOOL result = wglChoosePixelFormatARB(mhDC, attrib_list, NULL, 256, pixel_formats, &num_formats);
|
|
if (!result || !num_formats)
|
|
{
|
|
close();
|
|
show_window_creation_error("Error after wglChoosePixelFormatARB 16-bit");
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
LL_INFOS("Window") << "Choosing pixel formats: " << num_formats << " pixel formats returned" << LL_ENDL;
|
|
}
|
|
|
|
LL_INFOS("Window") << "pixel formats done." << LL_ENDL ;
|
|
|
|
S32 swap_method = 0;
|
|
S32 cur_format = 0;
|
|
const S32 max_format = (S32)num_formats - 1;
|
|
GLint swap_query = WGL_SWAP_METHOD_ARB;
|
|
|
|
// SL-14705 Fix name tags showing in front of objects with AMD GPUs.
|
|
// On AMD hardware we need to iterate from the first pixel format to the end.
|
|
// Spec:
|
|
// https://www.khronos.org/registry/OpenGL/extensions/ARB/WGL_ARB_pixel_format.txt
|
|
while (wglGetPixelFormatAttribivARB(mhDC, pixel_formats[cur_format], 0, 1, &swap_query, &swap_method))
|
|
{
|
|
if (swap_method == WGL_SWAP_UNDEFINED_ARB)
|
|
{
|
|
break;
|
|
}
|
|
else if (cur_format >= max_format)
|
|
{
|
|
cur_format = 0;
|
|
break;
|
|
}
|
|
|
|
++cur_format;
|
|
}
|
|
|
|
pixel_format = pixel_formats[cur_format];
|
|
|
|
if (mhDC != 0) // Does The Window Have A Device Context?
|
|
{
|
|
wglMakeCurrent(mhDC, 0); // Set The Current Active Rendering Context To Zero
|
|
if (mhRC != 0) // Does The Window Have A Rendering Context?
|
|
{
|
|
wglDeleteContext (mhRC); // Release The Rendering Context
|
|
mhRC = 0; // Zero The Rendering Context
|
|
}
|
|
}
|
|
|
|
// will release and recreate mhDC, mWindowHandle
|
|
recreateWindow(window_rect, dw_ex_style, dw_style);
|
|
|
|
RECT rect;
|
|
RECT client_rect;
|
|
//initialize immediately on main thread
|
|
if (GetWindowRect(mWindowHandle, &rect) &&
|
|
GetClientRect(mWindowHandle, &client_rect))
|
|
{
|
|
mRect = rect;
|
|
mClientRect = client_rect;
|
|
};
|
|
|
|
if (mWindowHandle)
|
|
{
|
|
LL_INFOS("Window") << "recreate window done." << LL_ENDL ;
|
|
}
|
|
else
|
|
{
|
|
// Note: if value is NULL GetDC retrieves the DC for the entire screen.
|
|
LL_WARNS("Window") << "Window recreation failed, code: " << GetLastError() << LL_ENDL;
|
|
}
|
|
|
|
if (!mhDC)
|
|
{
|
|
OSMessageBox(mCallbacks->translateString("MBDevContextErr"), mCallbacks->translateString("MBError"), OSMB_OK);
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
if (!SetPixelFormat(mhDC, pixel_format, &pfd))
|
|
{
|
|
OSMessageBox(mCallbacks->translateString("MBPixelFmtSetErr"),
|
|
mCallbacks->translateString("MBError"), OSMB_OK);
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
if (wglGetPixelFormatAttribivARB(mhDC, pixel_format, 0, 1, &swap_query, &swap_method))
|
|
{
|
|
switch (swap_method)
|
|
{
|
|
case WGL_SWAP_EXCHANGE_ARB:
|
|
mSwapMethod = SWAP_METHOD_EXCHANGE;
|
|
LL_DEBUGS("Window") << "Swap Method: Exchange" << LL_ENDL;
|
|
break;
|
|
case WGL_SWAP_COPY_ARB:
|
|
mSwapMethod = SWAP_METHOD_COPY;
|
|
LL_DEBUGS("Window") << "Swap Method: Copy" << LL_ENDL;
|
|
break;
|
|
case WGL_SWAP_UNDEFINED_ARB:
|
|
mSwapMethod = SWAP_METHOD_UNDEFINED;
|
|
LL_DEBUGS("Window") << "Swap Method: Undefined" << LL_ENDL;
|
|
break;
|
|
default:
|
|
mSwapMethod = SWAP_METHOD_UNDEFINED;
|
|
LL_DEBUGS("Window") << "Swap Method: Unknown" << LL_ENDL;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBVideoDrvErr"));
|
|
// mWindowHandle is 0, going to crash either way
|
|
LL_ERRS("Window") << "No wgl_ARB_pixel_format extension!" << LL_ENDL;
|
|
}
|
|
|
|
// Verify what pixel format we actually received.
|
|
if (!DescribePixelFormat(mhDC, pixel_format, sizeof(PIXELFORMATDESCRIPTOR),
|
|
&pfd))
|
|
{
|
|
OSMessageBox(mCallbacks->translateString("MBPixelFmtDescErr"), mCallbacks->translateString("MBError"), OSMB_OK);
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
LL_INFOS("Window") << "GL buffer: Color Bits " << S32(pfd.cColorBits)
|
|
<< " Alpha Bits " << S32(pfd.cAlphaBits)
|
|
<< " Depth Bits " << S32(pfd.cDepthBits)
|
|
<< LL_ENDL;
|
|
|
|
mhRC = 0;
|
|
if (wglCreateContextAttribsARB)
|
|
{ //attempt to create a specific versioned context
|
|
mhRC = (HGLRC) createSharedContext();
|
|
if (!mhRC)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!wglMakeCurrent(mhDC, mhRC))
|
|
{
|
|
OSMessageBox(mCallbacks->translateString("MBGLContextActErr"), mCallbacks->translateString("MBError"), OSMB_OK);
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
if (!gGLManager.initGL())
|
|
{
|
|
OSMessageBox(mCallbacks->translateString("MBVideoDrvErr"), mCallbacks->translateString("MBError"), OSMB_OK);
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
// Disable vertical sync for swap
|
|
toggleVSync(enable_vsync);
|
|
|
|
SetWindowLongPtr(mWindowHandle, GWLP_USERDATA, (LONG_PTR)this);
|
|
|
|
// register this window as handling drag/drop events from the OS
|
|
DragAcceptFiles( mWindowHandle, TRUE );
|
|
|
|
mDragDrop->init( mWindowHandle );
|
|
|
|
//register joystick timer callback
|
|
SetTimer( mWindowHandle, 0, 1000 / 30, NULL ); // 30 fps timer
|
|
|
|
// ok to post quit messages now
|
|
mPostQuit = true;
|
|
|
|
// *HACK: Attempt to prevent startup crashes by deferring memory accounting
|
|
// until after some graphics setup. See SL-20177. -Cosmic,2023-09-18
|
|
mWindowThread->post([=]()
|
|
{
|
|
mWindowThread->glReady();
|
|
});
|
|
|
|
if (auto_show)
|
|
{
|
|
show();
|
|
glClearColor(0.0f, 0.0f, 0.0f, 0.f);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
swapBuffers();
|
|
}
|
|
|
|
LL_PROFILER_GPU_CONTEXT;
|
|
|
|
return true;
|
|
}
|
|
|
|
void LLWindowWin32::recreateWindow(RECT window_rect, DWORD dw_ex_style, DWORD dw_style)
|
|
{
|
|
auto oldWindowHandle = mWindowHandle;
|
|
auto oldDCHandle = mhDC;
|
|
|
|
if (sWindowHandleForMessageBox == mWindowHandle)
|
|
{
|
|
sWindowHandleForMessageBox = NULL;
|
|
}
|
|
|
|
// zero out mWindowHandle and mhDC before destroying window so window
|
|
// thread falls back to peekmessage
|
|
mWindowHandle = NULL;
|
|
mhDC = NULL;
|
|
|
|
std::promise<std::pair<HWND, HDC>> promise;
|
|
// What follows must be done on the window thread.
|
|
auto window_work =
|
|
[this,
|
|
self=mWindowThread,
|
|
oldWindowHandle,
|
|
oldDCHandle,
|
|
// bind CreateWindowEx() parameters by value instead of
|
|
// back-referencing LLWindowWin32 members
|
|
windowClassName=mWindowClassName,
|
|
windowTitle=mWindowTitle,
|
|
hInstance=mhInstance,
|
|
window_rect,
|
|
dw_ex_style,
|
|
dw_style,
|
|
&promise]
|
|
()
|
|
{
|
|
LL_DEBUGS("Window") << "recreateWindow(): window_work entry" << LL_ENDL;
|
|
self->mWindowHandleThrd = 0;
|
|
self->mhDCThrd = 0;
|
|
|
|
if (oldWindowHandle)
|
|
{
|
|
if (oldDCHandle && !ReleaseDC(oldWindowHandle, oldDCHandle))
|
|
{
|
|
LL_WARNS("Window") << "Failed to ReleaseDC" << LL_ENDL;
|
|
}
|
|
|
|
// important to call DestroyWindow() from the window thread
|
|
if (!destroy_window_handler(oldWindowHandle))
|
|
{
|
|
|
|
LL_WARNS("Window") << "Failed to properly close window before recreating it!"
|
|
<< LL_ENDL;
|
|
}
|
|
}
|
|
|
|
auto handle = CreateWindowEx(dw_ex_style,
|
|
windowClassName,
|
|
windowTitle,
|
|
WS_CLIPSIBLINGS | WS_CLIPCHILDREN | dw_style,
|
|
window_rect.left, // x pos
|
|
window_rect.top, // y pos
|
|
window_rect.right - window_rect.left, // width
|
|
window_rect.bottom - window_rect.top, // height
|
|
NULL,
|
|
NULL,
|
|
hInstance,
|
|
NULL);
|
|
|
|
if (! handle)
|
|
{
|
|
// Failed to create window: clear the variables. This
|
|
// assignment is valid because we're running on mWindowThread.
|
|
self->mWindowHandleThrd = NULL;
|
|
self->mhDCThrd = 0;
|
|
}
|
|
else
|
|
{
|
|
// Update mWindowThread's own mWindowHandle and mhDC.
|
|
self->mWindowHandleThrd = handle;
|
|
self->mhDCThrd = GetDC(handle);
|
|
}
|
|
|
|
updateWindowRect();
|
|
|
|
// It's important to wake up the future either way.
|
|
promise.set_value(std::make_pair(self->mWindowHandleThrd, self->mhDCThrd));
|
|
LL_DEBUGS("Window") << "recreateWindow(): window_work done" << LL_ENDL;
|
|
};
|
|
// But how we pass window_work to the window thread depends on whether we
|
|
// already have a window handle.
|
|
if (!oldWindowHandle)
|
|
{
|
|
// Pass window_work using the WorkQueue: without an existing window
|
|
// handle, the window thread can't call GetMessage().
|
|
LL_DEBUGS("Window") << "posting window_work to WorkQueue" << LL_ENDL;
|
|
mWindowThread->post(window_work);
|
|
}
|
|
else
|
|
{
|
|
// Pass window_work using PostMessage(). We can still
|
|
// PostMessage(oldHandle) because oldHandle won't be destroyed until
|
|
// the window thread has retrieved and executed window_work.
|
|
LL_DEBUGS("Window") << "posting window_work to message queue" << LL_ENDL;
|
|
mWindowThread->Post(oldWindowHandle, window_work);
|
|
}
|
|
|
|
auto future = promise.get_future();
|
|
// This blocks until mWindowThread processes CreateWindowEx() and calls
|
|
// promise.set_value().
|
|
auto pair = future.get();
|
|
mWindowHandle = pair.first;
|
|
mhDC = pair.second;
|
|
|
|
sWindowHandleForMessageBox = mWindowHandle;
|
|
}
|
|
|
|
void* LLWindowWin32::createSharedContext()
|
|
{
|
|
mMaxGLVersion = llclamp(mMaxGLVersion, 3.f, 4.6f);
|
|
|
|
S32 version_major = llfloor(mMaxGLVersion);
|
|
S32 version_minor = (S32)llround((mMaxGLVersion-version_major)*10);
|
|
|
|
S32 attribs[] =
|
|
{
|
|
WGL_CONTEXT_MAJOR_VERSION_ARB, version_major,
|
|
WGL_CONTEXT_MINOR_VERSION_ARB, version_minor,
|
|
WGL_CONTEXT_PROFILE_MASK_ARB, LLRender::sGLCoreProfile ? WGL_CONTEXT_CORE_PROFILE_BIT_ARB : WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB,
|
|
WGL_CONTEXT_FLAGS_ARB, gDebugGL ? WGL_CONTEXT_DEBUG_BIT_ARB : 0,
|
|
0
|
|
};
|
|
|
|
HGLRC rc = 0;
|
|
|
|
bool done = false;
|
|
while (!done)
|
|
{
|
|
rc = wglCreateContextAttribsARB(mhDC, mhRC, attribs);
|
|
|
|
if (!rc)
|
|
{
|
|
if (attribs[3] > 0)
|
|
{ //decrement minor version
|
|
attribs[3]--;
|
|
}
|
|
else if (attribs[1] > 3)
|
|
{ //decrement major version and start minor version over at 3
|
|
attribs[1]--;
|
|
attribs[3] = 3;
|
|
}
|
|
else
|
|
{ //we reached 3.0 and still failed, bail out
|
|
done = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LL_INFOS() << "Created OpenGL " << llformat("%d.%d", attribs[1], attribs[3]) <<
|
|
(LLRender::sGLCoreProfile ? " core" : " compatibility") << " context." << LL_ENDL;
|
|
done = true;
|
|
}
|
|
}
|
|
|
|
if (!rc && !(rc = wglCreateContext(mhDC)))
|
|
{
|
|
close();
|
|
OSMessageBox(mCallbacks->translateString("MBGLContextErr"), mCallbacks->translateString("MBError"), OSMB_OK);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
void LLWindowWin32::makeContextCurrent(void* contextPtr)
|
|
{
|
|
wglMakeCurrent(mhDC, (HGLRC) contextPtr);
|
|
LL_PROFILER_GPU_CONTEXT;
|
|
}
|
|
|
|
void LLWindowWin32::destroySharedContext(void* contextPtr)
|
|
{
|
|
wglDeleteContext((HGLRC)contextPtr);
|
|
}
|
|
|
|
void LLWindowWin32::toggleVSync(bool enable_vsync)
|
|
{
|
|
if (wglSwapIntervalEXT == nullptr)
|
|
{
|
|
LL_INFOS("Window") << "VSync: wglSwapIntervalEXT not initialized" << LL_ENDL;
|
|
}
|
|
else if (!enable_vsync)
|
|
{
|
|
LL_INFOS("Window") << "Disabling vertical sync" << LL_ENDL;
|
|
wglSwapIntervalEXT(0);
|
|
}
|
|
else
|
|
{
|
|
LL_INFOS("Window") << "Enabling vertical sync" << LL_ENDL;
|
|
wglSwapIntervalEXT(1);
|
|
}
|
|
}
|
|
|
|
void LLWindowWin32::moveWindow( const LLCoordScreen& position, const LLCoordScreen& size )
|
|
{
|
|
if( mIsMouseClipping )
|
|
{
|
|
RECT client_rect_in_screen_space;
|
|
if( getClientRectInScreenSpace( &client_rect_in_screen_space ) )
|
|
{
|
|
ClipCursor( &client_rect_in_screen_space );
|
|
}
|
|
}
|
|
|
|
// if the window was already maximized, MoveWindow seems to still set the maximized flag even if
|
|
// the window is smaller than maximized.
|
|
// So we're going to do a restore first (which is a ShowWindow call) (SL-44655).
|
|
|
|
// THIS CAUSES DEV-15484 and DEV-15949
|
|
//ShowWindow(mWindowHandle, SW_RESTORE);
|
|
// NOW we can call MoveWindow
|
|
mWindowThread->post([=]()
|
|
{
|
|
MoveWindow(mWindowHandle, position.mX, position.mY, size.mX, size.mY, TRUE);
|
|
});
|
|
}
|
|
|
|
void LLWindowWin32::setTitle(const std::string title)
|
|
{
|
|
// TODO: Do we need to use the wide string version of this call
|
|
// to support non-ascii usernames (and region names?)
|
|
mWindowThread->post([=]()
|
|
{
|
|
SetWindowTextA(mWindowHandle, title.c_str());
|
|
});
|
|
}
|
|
|
|
bool LLWindowWin32::setCursorPosition(const LLCoordWindow position)
|
|
{
|
|
ASSERT_MAIN_THREAD();
|
|
|
|
if (!mWindowHandle)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
LLCoordScreen screen_pos(position.convert());
|
|
|
|
// instantly set the cursor position from the app's point of view
|
|
mCursorPosition = position;
|
|
mLastCursorPosition = position;
|
|
|
|
// Inform the application of the new mouse position (needed for per-frame
|
|
// hover/picking to function).
|
|
mCallbacks->handleMouseMove(this, position.convert(), (MASK)0);
|
|
|
|
// actually set the cursor position on the window thread
|
|
mWindowThread->post([=]()
|
|
{
|
|
// actually set the OS cursor position
|
|
SetCursorPos(screen_pos.mX, screen_pos.mY);
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
bool LLWindowWin32::getCursorPosition(LLCoordWindow *position)
|
|
{
|
|
ASSERT_MAIN_THREAD();
|
|
if (!position)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
*position = mCursorPosition;
|
|
return true;
|
|
}
|
|
|
|
bool LLWindowWin32::getCursorDelta(LLCoordCommon* delta)
|
|
{
|
|
if (delta == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
*delta = mMouseFrameDelta;
|
|
|
|
return true;
|
|
}
|
|
|
|
void LLWindowWin32::hideCursor()
|
|
{
|
|
ASSERT_MAIN_THREAD();
|
|
|
|
mWindowThread->post([=]()
|
|
{
|
|
while (ShowCursor(FALSE) >= 0)
|
|
{
|
|
// nothing, wait for cursor to push down
|
|
}
|
|
});
|
|
|
|
mCursorHidden = true;
|
|
mHideCursorPermanent = true;
|
|
}
|
|
|
|
void LLWindowWin32::showCursor()
|
|
{
|
|
LL_PROFILE_ZONE_SCOPED_CATEGORY_WIN32;
|
|
|
|
ASSERT_MAIN_THREAD();
|
|
|
|
mWindowThread->post([=]()
|
|
{
|
|
// makes sure the cursor shows up
|
|
while (ShowCursor(TRUE) < 0)
|
|
{
|
|
// do nothing, wait for cursor to pop out
|
|
}
|
|
});
|
|
|
|
mCursorHidden = false;
|
|
mHideCursorPermanent = false;
|
|
}
|
|
|
|
void LLWindowWin32::showCursorFromMouseMove()
|
|
{
|
|
if (!mHideCursorPermanent)
|
|
{
|
|
showCursor();
|
|
}
|
|
}
|
|
|
|
void LLWindowWin32::hideCursorUntilMouseMove()
|
|
{
|
|
if (!mHideCursorPermanent && mMouseVanish)
|
|
{
|
|
hideCursor();
|
|
mHideCursorPermanent = false;
|
|
}
|
|
}
|
|
|
|
bool LLWindowWin32::isCursorHidden()
|
|
{
|
|
return mCursorHidden;
|
|
}
|
|
|
|
|
|
HCURSOR LLWindowWin32::loadColorCursor(LPCTSTR name)
|
|
{
|
|
return (HCURSOR)LoadImage(mhInstance,
|
|
name,
|
|
IMAGE_CURSOR,
|
|
0, // default width
|
|
0, // default height
|
|
LR_DEFAULTCOLOR);
|
|
}
|
|
|
|
|
|
void LLWindowWin32::initCursors()
|
|
{
|
|
mCursor[ UI_CURSOR_ARROW ] = LoadCursor(NULL, IDC_ARROW);
|
|
mCursor[ UI_CURSOR_WAIT ] = LoadCursor(NULL, IDC_WAIT);
|
|
mCursor[ UI_CURSOR_HAND ] = LoadCursor(NULL, IDC_HAND);
|
|
mCursor[ UI_CURSOR_IBEAM ] = LoadCursor(NULL, IDC_IBEAM);
|
|
mCursor[ UI_CURSOR_CROSS ] = LoadCursor(NULL, IDC_CROSS);
|
|
mCursor[ UI_CURSOR_SIZENWSE ] = LoadCursor(NULL, IDC_SIZENWSE);
|
|
mCursor[ UI_CURSOR_SIZENESW ] = LoadCursor(NULL, IDC_SIZENESW);
|
|
mCursor[ UI_CURSOR_SIZEWE ] = LoadCursor(NULL, IDC_SIZEWE);
|
|
mCursor[ UI_CURSOR_SIZENS ] = LoadCursor(NULL, IDC_SIZENS);
|
|
mCursor[ UI_CURSOR_SIZEALL ] = LoadCursor(NULL, IDC_SIZEALL);
|
|
mCursor[ UI_CURSOR_NO ] = LoadCursor(NULL, IDC_NO);
|
|
mCursor[ UI_CURSOR_WORKING ] = LoadCursor(NULL, IDC_APPSTARTING);
|
|
|
|
HMODULE module = GetModuleHandle(NULL);
|
|
mCursor[ UI_CURSOR_TOOLGRAB ] = LoadCursor(module, TEXT("TOOLGRAB"));
|
|
mCursor[ UI_CURSOR_TOOLLAND ] = LoadCursor(module, TEXT("TOOLLAND"));
|
|
mCursor[ UI_CURSOR_TOOLFOCUS ] = LoadCursor(module, TEXT("TOOLFOCUS"));
|
|
mCursor[ UI_CURSOR_TOOLCREATE ] = LoadCursor(module, TEXT("TOOLCREATE"));
|
|
mCursor[ UI_CURSOR_ARROWDRAG ] = LoadCursor(module, TEXT("ARROWDRAG"));
|
|
mCursor[ UI_CURSOR_ARROWCOPY ] = LoadCursor(module, TEXT("ARROWCOPY"));
|
|
mCursor[ UI_CURSOR_ARROWDRAGMULTI ] = LoadCursor(module, TEXT("ARROWDRAGMULTI"));
|
|
mCursor[ UI_CURSOR_ARROWCOPYMULTI ] = LoadCursor(module, TEXT("ARROWCOPYMULTI"));
|
|
mCursor[ UI_CURSOR_NOLOCKED ] = LoadCursor(module, TEXT("NOLOCKED"));
|
|
mCursor[ UI_CURSOR_ARROWLOCKED ]= LoadCursor(module, TEXT("ARROWLOCKED"));
|
|
mCursor[ UI_CURSOR_GRABLOCKED ] = LoadCursor(module, TEXT("GRABLOCKED"));
|
|
mCursor[ UI_CURSOR_TOOLTRANSLATE ] = LoadCursor(module, TEXT("TOOLTRANSLATE"));
|
|
mCursor[ UI_CURSOR_TOOLROTATE ] = LoadCursor(module, TEXT("TOOLROTATE"));
|
|
mCursor[ UI_CURSOR_TOOLSCALE ] = LoadCursor(module, TEXT("TOOLSCALE"));
|
|
mCursor[ UI_CURSOR_TOOLCAMERA ] = LoadCursor(module, TEXT("TOOLCAMERA"));
|
|
mCursor[ UI_CURSOR_TOOLPAN ] = LoadCursor(module, TEXT("TOOLPAN"));
|
|
mCursor[ UI_CURSOR_TOOLZOOMIN ] = LoadCursor(module, TEXT("TOOLZOOMIN"));
|
|
mCursor[ UI_CURSOR_TOOLZOOMOUT ] = LoadCursor(module, TEXT("TOOLZOOMOUT"));
|
|
mCursor[ UI_CURSOR_TOOLPICKOBJECT3 ] = LoadCursor(module, TEXT("TOOLPICKOBJECT3"));
|
|
mCursor[ UI_CURSOR_PIPETTE ] = LoadCursor(module, TEXT("TOOLPIPETTE"));
|
|
mCursor[ UI_CURSOR_TOOLSIT ] = LoadCursor(module, TEXT("TOOLSIT"));
|
|
mCursor[ UI_CURSOR_TOOLBUY ] = LoadCursor(module, TEXT("TOOLBUY"));
|
|
mCursor[ UI_CURSOR_TOOLOPEN ] = LoadCursor(module, TEXT("TOOLOPEN"));
|
|
mCursor[ UI_CURSOR_TOOLPATHFINDING ] = LoadCursor(module, TEXT("TOOLPATHFINDING"));
|
|
mCursor[ UI_CURSOR_TOOLPATHFINDING_PATH_START_ADD ] = LoadCursor(module, TEXT("TOOLPATHFINDINGPATHSTARTADD"));
|
|
mCursor[ UI_CURSOR_TOOLPATHFINDING_PATH_START ] = LoadCursor(module, TEXT("TOOLPATHFINDINGPATHSTART"));
|
|
mCursor[ UI_CURSOR_TOOLPATHFINDING_PATH_END ] = LoadCursor(module, TEXT("TOOLPATHFINDINGPATHEND"));
|
|
mCursor[ UI_CURSOR_TOOLPATHFINDING_PATH_END_ADD ] = LoadCursor(module, TEXT("TOOLPATHFINDINGPATHENDADD"));
|
|
mCursor[ UI_CURSOR_TOOLNO ] = LoadCursor(module, TEXT("TOOLNO"));
|
|
|
|
// Color cursors
|
|
mCursor[ UI_CURSOR_TOOLPLAY ] = loadColorCursor(TEXT("TOOLPLAY"));
|
|
mCursor[ UI_CURSOR_TOOLPAUSE ] = loadColorCursor(TEXT("TOOLPAUSE"));
|
|
mCursor[ UI_CURSOR_TOOLMEDIAOPEN ] = loadColorCursor(TEXT("TOOLMEDIAOPEN"));
|
|
|
|
// Note: custom cursors that are not found make LoadCursor() return NULL.
|
|
for( S32 i = 0; i < UI_CURSOR_COUNT; i++ )
|
|
{
|
|
if( !mCursor[i] )
|
|
{
|
|
mCursor[i] = LoadCursor(NULL, IDC_ARROW);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void LLWindowWin32::updateCursor()
|
|
{
|
|
ASSERT_MAIN_THREAD();
|
|
LL_PROFILE_ZONE_SCOPED_CATEGORY_WIN32;
|
|
if (mNextCursor == UI_CURSOR_ARROW
|
|
&& mBusyCount > 0)
|
|
{
|
|
mNextCursor = UI_CURSOR_WORKING;
|
|
}
|
|
|
|
if( mCurrentCursor != mNextCursor )
|
|
{
|
|
mCurrentCursor = mNextCursor;
|
|
auto nextCursor = mCursor[mNextCursor];
|
|
mWindowThread->post([=]()
|
|
{
|
|
SetCursor(nextCursor);
|
|
});
|
|
}
|
|
}
|
|
|
|
ECursorType LLWindowWin32::getCursor() const
|
|
{
|
|
return mCurrentCursor;
|
|
}
|
|
|
|
void LLWindowWin32::captureMouse()
|
|
{
|
|
SetCapture(mWindowHandle);
|
|
}
|
|
|
|
void LLWindowWin32::releaseMouse()
|
|
{
|
|
LL_PROFILE_ZONE_SCOPED_CATEGORY_WIN32;
|
|
ReleaseCapture();
|
|
}
|
|
|
|
|
|
void LLWindowWin32::delayInputProcessing()
|
|
{
|
|
mInputProcessingPaused = true;
|
|
}
|
|
|
|
|
|
void LLWindowWin32::gatherInput()
|
|
{
|
|
ASSERT_MAIN_THREAD();
|
|
LL_PROFILE_ZONE_SCOPED_CATEGORY_WIN32;
|
|
MSG msg;
|
|
|
|
{
|
|
LLMutexLock lock(&mRawMouseMutex);
|
|
mMouseFrameDelta = mRawMouseDelta;
|
|
|
|
mRawMouseDelta.mX = 0;
|
|
mRawMouseDelta.mY = 0;
|
|
}
|
|
|
|
|
|
if (mWindowThread->getQueue().size())
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("gi - PostMessage");
|
|
kickWindowThread();
|
|
}
|
|
|
|
while (mWindowThread->mMessageQueue.tryPopBack(msg))
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("gi - message queue");
|
|
if (mInputProcessingPaused)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// For async host by name support. Really hacky.
|
|
if (gAsyncMsgCallback && (LL_WM_HOST_RESOLVED == msg.message))
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("gi - callback");
|
|
gAsyncMsgCallback(msg);
|
|
}
|
|
}
|
|
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("gi - PeekMessage");
|
|
S32 msg_count = 0;
|
|
while ((msg_count < MAX_MESSAGE_PER_UPDATE) && PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_REMOVE))
|
|
{
|
|
TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
msg_count++;
|
|
}
|
|
}
|
|
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("gi - function queue");
|
|
//process any pending functions
|
|
std::function<void()> curFunc;
|
|
while (mFunctionQueue.tryPopBack(curFunc))
|
|
{
|
|
curFunc();
|
|
}
|
|
}
|
|
|
|
// send one and only one mouse move event per frame BEFORE handling mouse button presses
|
|
if (mLastCursorPosition != mCursorPosition)
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("gi - mouse move");
|
|
mCallbacks->handleMouseMove(this, mCursorPosition.convert(), mMouseMask);
|
|
}
|
|
|
|
mLastCursorPosition = mCursorPosition;
|
|
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("gi - mouse queue");
|
|
// handle mouse button presses AFTER updating mouse cursor position
|
|
std::function<void()> curFunc;
|
|
while (mMouseQueue.tryPopBack(curFunc))
|
|
{
|
|
curFunc();
|
|
}
|
|
}
|
|
|
|
mInputProcessingPaused = false;
|
|
|
|
updateCursor();
|
|
}
|
|
|
|
static LLTrace::BlockTimerStatHandle FTM_KEYHANDLER("Handle Keyboard");
|
|
static LLTrace::BlockTimerStatHandle FTM_MOUSEHANDLER("Handle Mouse");
|
|
|
|
#define WINDOW_IMP_POST(x) window_imp->post([=]() { x; })
|
|
|
|
LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_param, LPARAM l_param)
|
|
{
|
|
ASSERT_WINDOW_THREAD();
|
|
LL_PROFILE_ZONE_SCOPED_CATEGORY_WIN32;
|
|
|
|
if (u_msg == WM_POST_FUNCTION_)
|
|
{
|
|
// from LLWindowWin32Thread::Post()
|
|
// Cast l_param back to the pointer to the heap FuncType
|
|
// allocated by Post(). Capture in unique_ptr so we'll delete
|
|
// once we're done with it.
|
|
std::unique_ptr<LLWindowWin32Thread::FuncType>
|
|
ptr(reinterpret_cast<LLWindowWin32Thread::FuncType*>(l_param));
|
|
(*ptr)();
|
|
return 0;
|
|
}
|
|
|
|
// Ignore clicks not originated in the client area, i.e. mouse-up events not preceded with a WM_LBUTTONDOWN.
|
|
// This helps prevent avatar walking after maximizing the window by double-clicking the title bar.
|
|
static bool sHandleLeftMouseUp = true;
|
|
|
|
// Ignore the double click received right after activating app.
|
|
// This is to avoid triggering double click teleport after returning focus (see MAINT-3786).
|
|
static bool sHandleDoubleClick = true;
|
|
|
|
LLWindowWin32* window_imp = (LLWindowWin32*)GetWindowLongPtr(h_wnd, GWLP_USERDATA);
|
|
|
|
if (NULL != window_imp)
|
|
{
|
|
// Juggle to make sure we can get negative positions for when
|
|
// mouse is outside window.
|
|
LLCoordWindow window_coord((S32)(S16)LOWORD(l_param), (S32)(S16)HIWORD(l_param));
|
|
|
|
// pass along extended flag in mask
|
|
MASK mask = (l_param >> 16 & KF_EXTENDED) ? MASK_EXTENDED : 0x0;
|
|
bool eat_keystroke = true;
|
|
|
|
switch (u_msg)
|
|
{
|
|
RECT update_rect;
|
|
S32 update_width;
|
|
S32 update_height;
|
|
|
|
case WM_TIMER:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_TIMER");
|
|
WINDOW_IMP_POST(window_imp->mCallbacks->handleTimerEvent(window_imp));
|
|
break;
|
|
}
|
|
|
|
case WM_DEVICECHANGE:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_DEVICECHANGE");
|
|
if (w_param == DBT_DEVNODES_CHANGED || w_param == DBT_DEVICEARRIVAL)
|
|
{
|
|
WINDOW_IMP_POST(window_imp->mCallbacks->handleDeviceChange(window_imp));
|
|
|
|
return 1;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case WM_PAINT:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_PAINT");
|
|
GetUpdateRect(window_imp->mWindowHandle, &update_rect, FALSE);
|
|
update_width = update_rect.right - update_rect.left + 1;
|
|
update_height = update_rect.bottom - update_rect.top + 1;
|
|
|
|
WINDOW_IMP_POST(window_imp->mCallbacks->handlePaint(window_imp, update_rect.left, update_rect.top,
|
|
update_width, update_height));
|
|
break;
|
|
}
|
|
case WM_PARENTNOTIFY:
|
|
{
|
|
break;
|
|
}
|
|
|
|
case WM_SETCURSOR:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_SETCURSOR");
|
|
// This message is sent whenever the cursor is moved in a window.
|
|
// You need to set the appropriate cursor appearance.
|
|
|
|
// Only take control of cursor over client region of window
|
|
// This allows Windows(tm) to handle resize cursors, etc.
|
|
if (LOWORD(l_param) == HTCLIENT)
|
|
{
|
|
SetCursor(window_imp->mCursor[window_imp->mCurrentCursor]);
|
|
return 0;
|
|
}
|
|
break;
|
|
}
|
|
case WM_ENTERMENULOOP:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_ENTERMENULOOP");
|
|
WINDOW_IMP_POST(window_imp->mCallbacks->handleWindowBlock(window_imp));
|
|
break;
|
|
}
|
|
|
|
case WM_EXITMENULOOP:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_EXITMENULOOP");
|
|
WINDOW_IMP_POST(window_imp->mCallbacks->handleWindowUnblock(window_imp));
|
|
break;
|
|
}
|
|
|
|
case WM_ACTIVATEAPP:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_ACTIVATEAPP");
|
|
window_imp->post([=]()
|
|
{
|
|
// This message should be sent whenever the app gains or loses focus.
|
|
BOOL activating = (BOOL)w_param;
|
|
|
|
if (window_imp->mFullscreen)
|
|
{
|
|
// When we run fullscreen, restoring or minimizing the app needs
|
|
// to switch the screen resolution
|
|
if (activating)
|
|
{
|
|
window_imp->setFullscreenResolution();
|
|
window_imp->restore();
|
|
}
|
|
else
|
|
{
|
|
window_imp->minimize();
|
|
window_imp->resetDisplayResolution();
|
|
}
|
|
}
|
|
|
|
if (!activating)
|
|
{
|
|
sHandleDoubleClick = false;
|
|
}
|
|
|
|
window_imp->mCallbacks->handleActivateApp(window_imp, activating);
|
|
});
|
|
break;
|
|
}
|
|
case WM_ACTIVATE:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_ACTIVATE");
|
|
window_imp->post([=]()
|
|
{
|
|
// Can be one of WA_ACTIVE, WA_CLICKACTIVE, or WA_INACTIVE
|
|
BOOL activating = (LOWORD(w_param) != WA_INACTIVE);
|
|
|
|
if (!activating && LLWinImm::isAvailable() && window_imp->mPreeditor)
|
|
{
|
|
window_imp->interruptLanguageTextInput();
|
|
}
|
|
});
|
|
|
|
break;
|
|
}
|
|
|
|
case WM_QUERYOPEN:
|
|
// TODO: use this to return a nice icon
|
|
break;
|
|
|
|
case WM_SYSCOMMAND:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_SYSCOMMAND");
|
|
switch (w_param)
|
|
{
|
|
case SC_KEYMENU:
|
|
// Disallow the ALT key from triggering the default system menu.
|
|
return 0;
|
|
|
|
case SC_SCREENSAVE:
|
|
case SC_MONITORPOWER:
|
|
// eat screen save messages and prevent them!
|
|
return 0;
|
|
}
|
|
break;
|
|
}
|
|
case WM_CLOSE:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_CLOSE");
|
|
window_imp->post([=]()
|
|
{
|
|
// Will the app allow the window to close?
|
|
if (window_imp->mCallbacks->handleCloseRequest(window_imp))
|
|
{
|
|
// Get the app to initiate cleanup.
|
|
window_imp->mCallbacks->handleQuit(window_imp);
|
|
// The app is responsible for calling destroyWindow when done with GL
|
|
}
|
|
});
|
|
return 0;
|
|
}
|
|
case WM_DESTROY:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_DESTROY");
|
|
if (window_imp->shouldPostQuit())
|
|
{
|
|
PostQuitMessage(0); // Posts WM_QUIT with an exit code of 0
|
|
}
|
|
return 0;
|
|
}
|
|
case WM_COMMAND:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_COMMAND");
|
|
if (!HIWORD(w_param)) // this message is from a menu
|
|
{
|
|
WINDOW_IMP_POST(window_imp->mCallbacks->handleMenuSelect(window_imp, LOWORD(w_param)));
|
|
}
|
|
break;
|
|
}
|
|
case WM_SYSKEYDOWN:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_SYSKEYDOWN");
|
|
// allow system keys, such as ALT-F4 to be processed by Windows
|
|
eat_keystroke = false;
|
|
// intentional fall-through here
|
|
[[fallthrough]];
|
|
}
|
|
case WM_KEYDOWN:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_KEYDOWN");
|
|
window_imp->post([=]()
|
|
{
|
|
window_imp->mKeyCharCode = 0; // don't know until wm_char comes in next
|
|
window_imp->mKeyScanCode = (l_param >> 16) & 0xff;
|
|
window_imp->mKeyVirtualKey = (U32)w_param;
|
|
window_imp->mRawMsg = u_msg;
|
|
window_imp->mRawWParam = (U32)w_param;
|
|
window_imp->mRawLParam = (U32)l_param;
|
|
|
|
gKeyboard->handleKeyDown((U16)w_param, mask);
|
|
});
|
|
if (eat_keystroke) return 0; // skip DefWindowProc() handling if we're consuming the keypress
|
|
break;
|
|
}
|
|
case WM_SYSKEYUP:
|
|
eat_keystroke = false;
|
|
// intentional fall-through here
|
|
[[fallthrough]];
|
|
case WM_KEYUP:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_KEYUP");
|
|
window_imp->post([=]()
|
|
{
|
|
window_imp->mKeyScanCode = (l_param >> 16) & 0xff;
|
|
window_imp->mKeyVirtualKey = (U32)w_param;
|
|
window_imp->mRawMsg = u_msg;
|
|
window_imp->mRawWParam = (U32)w_param;
|
|
window_imp->mRawLParam = (U32)l_param;
|
|
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_KEYUP");
|
|
gKeyboard->handleKeyUp((U16)w_param, mask);
|
|
}
|
|
});
|
|
if (eat_keystroke) return 0; // skip DefWindowProc() handling if we're consuming the keypress
|
|
break;
|
|
}
|
|
case WM_IME_SETCONTEXT:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_IME_SETCONTEXT");
|
|
if (LLWinImm::isAvailable() && window_imp->mPreeditor)
|
|
{
|
|
l_param &= ~ISC_SHOWUICOMPOSITIONWINDOW;
|
|
// Invoke DefWinProc with the modified LPARAM.
|
|
}
|
|
break;
|
|
}
|
|
case WM_IME_STARTCOMPOSITION:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_IME_STARTCOMPOSITION");
|
|
if (LLWinImm::isAvailable() && window_imp->mPreeditor)
|
|
{
|
|
WINDOW_IMP_POST(window_imp->handleStartCompositionMessage());
|
|
return 0;
|
|
}
|
|
break;
|
|
}
|
|
case WM_IME_ENDCOMPOSITION:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_IME_ENDCOMPOSITION");
|
|
if (LLWinImm::isAvailable() && window_imp->mPreeditor)
|
|
{
|
|
return 0;
|
|
}
|
|
break;
|
|
}
|
|
case WM_IME_COMPOSITION:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_IME_COMPOSITION");
|
|
if (LLWinImm::isAvailable() && window_imp->mPreeditor)
|
|
{
|
|
WINDOW_IMP_POST(window_imp->handleCompositionMessage((U32)l_param));
|
|
return 0;
|
|
}
|
|
break;
|
|
}
|
|
case WM_IME_REQUEST:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_IME_REQUEST");
|
|
if (LLWinImm::isAvailable() && window_imp->mPreeditor)
|
|
{
|
|
LRESULT result;
|
|
window_imp->handleImeRequests(w_param, l_param, &result);
|
|
return result;
|
|
}
|
|
break;
|
|
}
|
|
case WM_CHAR:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_CHAR");
|
|
window_imp->post([=]()
|
|
{
|
|
window_imp->mKeyCharCode = (U32)w_param;
|
|
window_imp->mRawMsg = u_msg;
|
|
window_imp->mRawWParam = (U32)w_param;
|
|
window_imp->mRawLParam = (U32)l_param;
|
|
|
|
// Should really use WM_UNICHAR eventually, but it requires a specific Windows version and I need
|
|
// to figure out how that works. - Doug
|
|
//
|
|
// ... Well, I don't think so.
|
|
// How it works is explained in Win32 API document, but WM_UNICHAR didn't work
|
|
// as specified at least on Windows XP SP1 Japanese version. I have never used
|
|
// it since then, and I'm not sure whether it has been fixed now, but I don't think
|
|
// it is worth trying. The good old WM_CHAR works just fine even for supplementary
|
|
// characters. We just need to take care of surrogate pairs sent as two WM_CHAR's
|
|
// by ourselves. It is not that tough. -- Alissa Sabre @ SL
|
|
|
|
// Even if LLWindowCallbacks::handleUnicodeChar(llwchar, bool) returned false,
|
|
// we *did* processed the event, so I believe we should not pass it to DefWindowProc...
|
|
window_imp->handleUnicodeUTF16((U16)w_param, gKeyboard->currentMask(false));
|
|
});
|
|
return 0;
|
|
}
|
|
case WM_NCLBUTTONDOWN:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_NCLBUTTONDOWN");
|
|
{
|
|
// A click in a non-client area, e.g. title bar or window border.
|
|
window_imp->post([=]()
|
|
{
|
|
sHandleLeftMouseUp = false;
|
|
sHandleDoubleClick = true;
|
|
});
|
|
}
|
|
break;
|
|
}
|
|
case WM_LBUTTONDOWN:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_LBUTTONDOWN");
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_MOUSEHANDLER);
|
|
window_imp->postMouseButtonEvent([=]()
|
|
{
|
|
sHandleLeftMouseUp = true;
|
|
|
|
if (LLWinImm::isAvailable() && window_imp->mPreeditor)
|
|
{
|
|
window_imp->interruptLanguageTextInput();
|
|
}
|
|
|
|
MASK mask = gKeyboard->currentMask(true);
|
|
auto gl_coord = window_imp->mCursorPosition.convert();
|
|
window_imp->mCallbacks->handleMouseMove(window_imp, gl_coord, mask);
|
|
window_imp->mCallbacks->handleMouseDown(window_imp, gl_coord, mask);
|
|
});
|
|
|
|
return 0;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case WM_LBUTTONDBLCLK:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_LBUTTONDBLCLK");
|
|
window_imp->postMouseButtonEvent([=]()
|
|
{
|
|
//RN: ignore right button double clicks for now
|
|
//case WM_RBUTTONDBLCLK:
|
|
if (!sHandleDoubleClick)
|
|
{
|
|
sHandleDoubleClick = true;
|
|
return;
|
|
}
|
|
MASK mask = gKeyboard->currentMask(true);
|
|
|
|
// generate move event to update mouse coordinates
|
|
window_imp->mCursorPosition = window_coord;
|
|
window_imp->mCallbacks->handleDoubleClick(window_imp, window_imp->mCursorPosition.convert(), mask);
|
|
});
|
|
|
|
return 0;
|
|
}
|
|
case WM_LBUTTONUP:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_LBUTTONUP");
|
|
{
|
|
window_imp->postMouseButtonEvent([=]()
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_MOUSEHANDLER);
|
|
if (!sHandleLeftMouseUp)
|
|
{
|
|
sHandleLeftMouseUp = true;
|
|
return;
|
|
}
|
|
sHandleDoubleClick = true;
|
|
|
|
|
|
MASK mask = gKeyboard->currentMask(true);
|
|
// generate move event to update mouse coordinates
|
|
window_imp->mCursorPosition = window_coord;
|
|
window_imp->mCallbacks->handleMouseUp(window_imp, window_imp->mCursorPosition.convert(), mask);
|
|
});
|
|
}
|
|
return 0;
|
|
}
|
|
case WM_RBUTTONDBLCLK:
|
|
case WM_RBUTTONDOWN:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_RBUTTONDOWN");
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_MOUSEHANDLER);
|
|
window_imp->post([=]()
|
|
{
|
|
if (LLWinImm::isAvailable() && window_imp->mPreeditor)
|
|
{
|
|
WINDOW_IMP_POST(window_imp->interruptLanguageTextInput());
|
|
}
|
|
|
|
MASK mask = gKeyboard->currentMask(true);
|
|
// generate move event to update mouse coordinates
|
|
auto gl_coord = window_imp->mCursorPosition.convert();
|
|
window_imp->mCallbacks->handleMouseMove(window_imp, gl_coord, mask);
|
|
window_imp->mCallbacks->handleRightMouseDown(window_imp, gl_coord, mask);
|
|
});
|
|
}
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case WM_RBUTTONUP:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_RBUTTONUP");
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_MOUSEHANDLER);
|
|
window_imp->postMouseButtonEvent([=]()
|
|
{
|
|
MASK mask = gKeyboard->currentMask(true);
|
|
window_imp->mCallbacks->handleRightMouseUp(window_imp, window_imp->mCursorPosition.convert(), mask);
|
|
});
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_MBUTTONDOWN:
|
|
// case WM_MBUTTONDBLCLK:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_MBUTTONDOWN");
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_MOUSEHANDLER);
|
|
window_imp->postMouseButtonEvent([=]()
|
|
{
|
|
if (LLWinImm::isAvailable() && window_imp->mPreeditor)
|
|
{
|
|
window_imp->interruptLanguageTextInput();
|
|
}
|
|
|
|
MASK mask = gKeyboard->currentMask(true);
|
|
window_imp->mCallbacks->handleMiddleMouseDown(window_imp, window_imp->mCursorPosition.convert(), mask);
|
|
});
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_MBUTTONUP:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_MBUTTONUP");
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_MOUSEHANDLER);
|
|
window_imp->postMouseButtonEvent([=]()
|
|
{
|
|
MASK mask = gKeyboard->currentMask(true);
|
|
window_imp->mCallbacks->handleMiddleMouseUp(window_imp, window_imp->mCursorPosition.convert(), mask);
|
|
});
|
|
}
|
|
}
|
|
break;
|
|
case WM_XBUTTONDOWN:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_XBUTTONDOWN");
|
|
window_imp->postMouseButtonEvent([=]()
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_MOUSEHANDLER);
|
|
S32 button = GET_XBUTTON_WPARAM(w_param);
|
|
if (LLWinImm::isAvailable() && window_imp->mPreeditor)
|
|
{
|
|
window_imp->interruptLanguageTextInput();
|
|
}
|
|
|
|
MASK mask = gKeyboard->currentMask(true);
|
|
// Windows uses numbers 1 and 2 for buttons, remap to 4, 5
|
|
window_imp->mCallbacks->handleOtherMouseDown(window_imp, window_imp->mCursorPosition.convert(), mask, button + 3);
|
|
});
|
|
|
|
}
|
|
break;
|
|
|
|
case WM_XBUTTONUP:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_XBUTTONUP");
|
|
window_imp->postMouseButtonEvent([=]()
|
|
{
|
|
|
|
LL_RECORD_BLOCK_TIME(FTM_MOUSEHANDLER);
|
|
|
|
S32 button = GET_XBUTTON_WPARAM(w_param);
|
|
MASK mask = gKeyboard->currentMask(true);
|
|
// Windows uses numbers 1 and 2 for buttons, remap to 4, 5
|
|
window_imp->mCallbacks->handleOtherMouseUp(window_imp, window_imp->mCursorPosition.convert(), mask, button + 3);
|
|
});
|
|
}
|
|
break;
|
|
|
|
case WM_MOUSEWHEEL:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_MOUSEWHEEL");
|
|
static short z_delta = 0;
|
|
|
|
RECT client_rect;
|
|
|
|
// eat scroll events that occur outside our window, since we use mouse position to direct scroll
|
|
// instead of keyboard focus
|
|
// NOTE: mouse_coord is in *window* coordinates for scroll events
|
|
POINT mouse_coord = { (S32)(S16)LOWORD(l_param), (S32)(S16)HIWORD(l_param) };
|
|
|
|
if (ScreenToClient(window_imp->mWindowHandle, &mouse_coord)
|
|
&& GetClientRect(window_imp->mWindowHandle, &client_rect))
|
|
{
|
|
// we have a valid mouse point and client rect
|
|
if (mouse_coord.x < client_rect.left || client_rect.right < mouse_coord.x
|
|
|| mouse_coord.y < client_rect.top || client_rect.bottom < mouse_coord.y)
|
|
{
|
|
// mouse is outside of client rect, so don't do anything
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
S16 incoming_z_delta = HIWORD(w_param);
|
|
z_delta += incoming_z_delta;
|
|
// cout << "z_delta " << z_delta << endl;
|
|
|
|
// current mouse wheels report changes in increments of zDelta (+120, -120)
|
|
// Future, higher resolution mouse wheels may report smaller deltas.
|
|
// So we sum the deltas and only act when we've exceeded WHEEL_DELTA
|
|
//
|
|
// If the user rapidly spins the wheel, we can get messages with
|
|
// large deltas, like 480 or so. Thus we need to scroll more quickly.
|
|
if (z_delta <= -WHEEL_DELTA || WHEEL_DELTA <= z_delta)
|
|
{
|
|
short clicks = -z_delta / WHEEL_DELTA;
|
|
WINDOW_IMP_POST(window_imp->mCallbacks->handleScrollWheel(window_imp, clicks));
|
|
z_delta = 0;
|
|
}
|
|
return 0;
|
|
}
|
|
/*
|
|
// TODO: add this after resolving _WIN32_WINNT issue
|
|
case WM_MOUSELEAVE:
|
|
{
|
|
window_imp->mCallbacks->handleMouseLeave(window_imp);
|
|
|
|
// TRACKMOUSEEVENT track_mouse_event;
|
|
// track_mouse_event.cbSize = sizeof( TRACKMOUSEEVENT );
|
|
// track_mouse_event.dwFlags = TME_LEAVE;
|
|
// track_mouse_event.hwndTrack = h_wnd;
|
|
// track_mouse_event.dwHoverTime = HOVER_DEFAULT;
|
|
// TrackMouseEvent( &track_mouse_event );
|
|
return 0;
|
|
}
|
|
*/
|
|
case WM_MOUSEHWHEEL:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_MOUSEHWHEEL");
|
|
static short h_delta = 0;
|
|
|
|
RECT client_rect;
|
|
|
|
// eat scroll events that occur outside our window, since we use mouse position to direct scroll
|
|
// instead of keyboard focus
|
|
// NOTE: mouse_coord is in *window* coordinates for scroll events
|
|
POINT mouse_coord = { (S32)(S16)LOWORD(l_param), (S32)(S16)HIWORD(l_param) };
|
|
|
|
if (ScreenToClient(window_imp->mWindowHandle, &mouse_coord)
|
|
&& GetClientRect(window_imp->mWindowHandle, &client_rect))
|
|
{
|
|
// we have a valid mouse point and client rect
|
|
if (mouse_coord.x < client_rect.left || client_rect.right < mouse_coord.x
|
|
|| mouse_coord.y < client_rect.top || client_rect.bottom < mouse_coord.y)
|
|
{
|
|
// mouse is outside of client rect, so don't do anything
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
S16 incoming_h_delta = HIWORD(w_param);
|
|
h_delta += incoming_h_delta;
|
|
|
|
// If the user rapidly spins the wheel, we can get messages with
|
|
// large deltas, like 480 or so. Thus we need to scroll more quickly.
|
|
if (h_delta <= -WHEEL_DELTA || WHEEL_DELTA <= h_delta)
|
|
{
|
|
WINDOW_IMP_POST(window_imp->mCallbacks->handleScrollHWheel(window_imp, h_delta / WHEEL_DELTA));
|
|
h_delta = 0;
|
|
}
|
|
return 0;
|
|
}
|
|
// Handle mouse movement within the window
|
|
case WM_MOUSEMOVE:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_MOUSEMOVE");
|
|
// DO NOT use mouse event queue for move events to ensure cursor position is updated
|
|
// when button events are handled
|
|
WINDOW_IMP_POST(
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_MOUSEMOVE lambda");
|
|
|
|
MASK mask = gKeyboard->currentMask(true);
|
|
window_imp->mMouseMask = mask;
|
|
window_imp->mCursorPosition = window_coord;
|
|
});
|
|
return 0;
|
|
}
|
|
|
|
case WM_GETMINMAXINFO:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_GETMINMAXINFO");
|
|
LPMINMAXINFO min_max = (LPMINMAXINFO)l_param;
|
|
min_max->ptMinTrackSize.x = window_imp->mMinWindowWidth;
|
|
min_max->ptMinTrackSize.y = window_imp->mMinWindowHeight;
|
|
return 0;
|
|
}
|
|
|
|
case WM_MOVE:
|
|
{
|
|
window_imp->updateWindowRect();
|
|
return 0;
|
|
}
|
|
case WM_SIZE:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_SIZE");
|
|
window_imp->updateWindowRect();
|
|
|
|
// There's an odd behavior with WM_SIZE that I would call a bug. If
|
|
// the window is maximized, and you call MoveWindow() with a size smaller
|
|
// than a maximized window, it ends up sending WM_SIZE with w_param set
|
|
// to SIZE_MAXIMIZED -- which isn't true. So the logic below doesn't work.
|
|
// (SL-44655). Fixed it by calling ShowWindow(SW_RESTORE) first (see
|
|
// LLWindowWin32::moveWindow in this file).
|
|
|
|
// If we are now restored, but we weren't before, this
|
|
// means that the window was un-minimized.
|
|
if (w_param == SIZE_RESTORED && window_imp->mLastSizeWParam != SIZE_RESTORED)
|
|
{
|
|
WINDOW_IMP_POST(window_imp->mCallbacks->handleActivate(window_imp, true));
|
|
}
|
|
|
|
// handle case of window being maximized from fully minimized state
|
|
if (w_param == SIZE_MAXIMIZED && window_imp->mLastSizeWParam != SIZE_MAXIMIZED)
|
|
{
|
|
WINDOW_IMP_POST(window_imp->mCallbacks->handleActivate(window_imp, true));
|
|
}
|
|
|
|
// Also handle the minimization case
|
|
if (w_param == SIZE_MINIMIZED && window_imp->mLastSizeWParam != SIZE_MINIMIZED)
|
|
{
|
|
WINDOW_IMP_POST(window_imp->mCallbacks->handleActivate(window_imp, false));
|
|
}
|
|
|
|
// Actually resize all of our views
|
|
if (w_param != SIZE_MINIMIZED)
|
|
{
|
|
// Ignore updates for minimizing and minimized "windows"
|
|
WINDOW_IMP_POST(window_imp->mCallbacks->handleResize(window_imp,
|
|
LOWORD(l_param),
|
|
HIWORD(l_param)));
|
|
}
|
|
|
|
window_imp->mLastSizeWParam = w_param;
|
|
|
|
return 0;
|
|
}
|
|
|
|
case WM_DPICHANGED:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_DPICHANGED");
|
|
LPRECT lprc_new_scale;
|
|
F32 new_scale = F32(LOWORD(w_param)) / F32(USER_DEFAULT_SCREEN_DPI);
|
|
lprc_new_scale = (LPRECT)l_param;
|
|
S32 new_width = lprc_new_scale->right - lprc_new_scale->left;
|
|
S32 new_height = lprc_new_scale->bottom - lprc_new_scale->top;
|
|
WINDOW_IMP_POST(window_imp->mCallbacks->handleDPIChanged(window_imp, new_scale, new_width, new_height));
|
|
|
|
SetWindowPos(h_wnd,
|
|
HWND_TOP,
|
|
lprc_new_scale->left,
|
|
lprc_new_scale->top,
|
|
new_width,
|
|
new_height,
|
|
SWP_NOZORDER | SWP_NOACTIVATE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
case WM_SETFOCUS:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_SETFOCUS");
|
|
WINDOW_IMP_POST(window_imp->mCallbacks->handleFocus(window_imp));
|
|
return 0;
|
|
}
|
|
|
|
case WM_KILLFOCUS:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_KILLFOCUS");
|
|
WINDOW_IMP_POST(window_imp->mCallbacks->handleFocusLost(window_imp));
|
|
return 0;
|
|
}
|
|
|
|
case WM_COPYDATA:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_COPYDATA");
|
|
{
|
|
// received a URL
|
|
PCOPYDATASTRUCT myCDS = (PCOPYDATASTRUCT)l_param;
|
|
void* data = new U8[myCDS->cbData];
|
|
memcpy(data, myCDS->lpData, myCDS->cbData);
|
|
auto myType = myCDS->dwData;
|
|
|
|
window_imp->post([=]()
|
|
{
|
|
window_imp->mCallbacks->handleDataCopy(window_imp, (S32)myType, data);
|
|
delete[] data;
|
|
});
|
|
};
|
|
return 0;
|
|
}
|
|
case WM_SETTINGCHANGE:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_SETTINGCHANGE");
|
|
if (w_param == SPI_SETMOUSEVANISH)
|
|
{
|
|
if (!SystemParametersInfo(SPI_GETMOUSEVANISH, 0, &window_imp->mMouseVanish, 0))
|
|
{
|
|
WINDOW_IMP_POST(window_imp->mMouseVanish = true);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_INPUT:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("MWP - WM_INPUT");
|
|
|
|
UINT dwSize = 0;
|
|
GetRawInputData((HRAWINPUT)l_param, RID_INPUT, NULL, &dwSize, sizeof(RAWINPUTHEADER));
|
|
llassert(dwSize < 1024);
|
|
|
|
U8 lpb[1024];
|
|
|
|
if (GetRawInputData((HRAWINPUT)l_param, RID_INPUT, (void*)lpb, &dwSize, sizeof(RAWINPUTHEADER)) == dwSize)
|
|
{
|
|
RAWINPUT* raw = (RAWINPUT*)lpb;
|
|
|
|
if (raw->header.dwType == RIM_TYPEMOUSE)
|
|
{
|
|
LLMutexLock lock(&window_imp->mRawMouseMutex);
|
|
|
|
bool absolute_coordinates = (raw->data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE);
|
|
|
|
if (absolute_coordinates)
|
|
{
|
|
static S32 prev_absolute_x = 0;
|
|
static S32 prev_absolute_y = 0;
|
|
S32 absolute_x;
|
|
S32 absolute_y;
|
|
|
|
if ((raw->data.mouse.usFlags & 0x10) == 0x10) // touch screen? touch? Not defined in header
|
|
{
|
|
// touch screen spams (0,0) coordinates in a number of situations
|
|
// (0,0) might need to be filtered
|
|
absolute_x = raw->data.mouse.lLastX;
|
|
absolute_y = raw->data.mouse.lLastY;
|
|
}
|
|
else
|
|
{
|
|
bool v_desktop = (raw->data.mouse.usFlags & MOUSE_VIRTUAL_DESKTOP) == MOUSE_VIRTUAL_DESKTOP;
|
|
|
|
S32 width = GetSystemMetrics(v_desktop ? SM_CXVIRTUALSCREEN : SM_CXSCREEN);
|
|
S32 height = GetSystemMetrics(v_desktop ? SM_CYVIRTUALSCREEN : SM_CYSCREEN);
|
|
|
|
absolute_x = (S32)((raw->data.mouse.lLastX / 65535.0f) * width);
|
|
absolute_y = (S32)((raw->data.mouse.lLastY / 65535.0f) * height);
|
|
}
|
|
|
|
window_imp->mRawMouseDelta.mX += absolute_x - prev_absolute_x;
|
|
window_imp->mRawMouseDelta.mY -= absolute_y - prev_absolute_y;
|
|
|
|
prev_absolute_x = absolute_x;
|
|
prev_absolute_y = absolute_y;
|
|
}
|
|
else
|
|
{
|
|
S32 speed;
|
|
const S32 DEFAULT_SPEED(10);
|
|
SystemParametersInfo(SPI_GETMOUSESPEED, 0, &speed, 0);
|
|
if (speed == DEFAULT_SPEED)
|
|
{
|
|
window_imp->mRawMouseDelta.mX += raw->data.mouse.lLastX;
|
|
window_imp->mRawMouseDelta.mY -= raw->data.mouse.lLastY;
|
|
}
|
|
else
|
|
{
|
|
window_imp->mRawMouseDelta.mX += (S32)round((F32)raw->data.mouse.lLastX * (F32)speed / DEFAULT_SPEED);
|
|
window_imp->mRawMouseDelta.mY -= (S32)round((F32)raw->data.mouse.lLastY * (F32)speed / DEFAULT_SPEED);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
//list of messages we get often that we don't care to log about
|
|
case WM_NCHITTEST:
|
|
case WM_NCMOUSEMOVE:
|
|
case WM_NCMOUSELEAVE:
|
|
case WM_MOVING:
|
|
case WM_WINDOWPOSCHANGING:
|
|
case WM_WINDOWPOSCHANGED:
|
|
break;
|
|
|
|
default:
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - default");
|
|
LL_DEBUGS("Window") << "Unhandled windows message code: 0x" << std::hex << U32(u_msg) << LL_ENDL;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// (NULL == window_imp)
|
|
LL_DEBUGS("Window") << "No window implementation to handle message with, message code: " << U32(u_msg) << LL_ENDL;
|
|
}
|
|
|
|
// pass unhandled messages down to Windows
|
|
LRESULT ret;
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - DefWindowProc");
|
|
ret = DefWindowProc(h_wnd, u_msg, w_param, l_param);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
bool LLWindowWin32::convertCoords(LLCoordGL from, LLCoordWindow *to)
|
|
{
|
|
S32 client_height;
|
|
RECT client_rect;
|
|
LLCoordWindow window_position;
|
|
|
|
if (!mWindowHandle ||
|
|
!GetClientRect(mWindowHandle, &client_rect) ||
|
|
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 LLWindowWin32::convertCoords(LLCoordWindow from, LLCoordGL* to)
|
|
{
|
|
S32 client_height;
|
|
RECT client_rect;
|
|
|
|
if (!mWindowHandle ||
|
|
!GetClientRect(mWindowHandle, &client_rect) ||
|
|
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 LLWindowWin32::convertCoords(LLCoordScreen from, LLCoordWindow* to)
|
|
{
|
|
POINT mouse_point;
|
|
|
|
mouse_point.x = from.mX;
|
|
mouse_point.y = from.mY;
|
|
bool result = ScreenToClient(mWindowHandle, &mouse_point);
|
|
|
|
if (result)
|
|
{
|
|
to->mX = mouse_point.x;
|
|
to->mY = mouse_point.y;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool LLWindowWin32::convertCoords(LLCoordWindow from, LLCoordScreen *to)
|
|
{
|
|
POINT mouse_point;
|
|
|
|
mouse_point.x = from.mX;
|
|
mouse_point.y = from.mY;
|
|
bool result = ClientToScreen(mWindowHandle, &mouse_point);
|
|
|
|
if (result)
|
|
{
|
|
to->mX = mouse_point.x;
|
|
to->mY = mouse_point.y;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool LLWindowWin32::convertCoords(LLCoordScreen from, LLCoordGL *to)
|
|
{
|
|
LLCoordWindow window_coord;
|
|
|
|
if (!mWindowHandle || (NULL == to))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
convertCoords(from, &window_coord);
|
|
convertCoords(window_coord, to);
|
|
return true;
|
|
}
|
|
|
|
bool LLWindowWin32::convertCoords(LLCoordGL from, LLCoordScreen *to)
|
|
{
|
|
LLCoordWindow window_coord;
|
|
|
|
if (!mWindowHandle || (NULL == to))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
convertCoords(from, &window_coord);
|
|
convertCoords(window_coord, to);
|
|
return true;
|
|
}
|
|
|
|
|
|
bool LLWindowWin32::isClipboardTextAvailable()
|
|
{
|
|
return IsClipboardFormatAvailable(CF_UNICODETEXT);
|
|
}
|
|
|
|
|
|
bool LLWindowWin32::pasteTextFromClipboard(LLWString &dst)
|
|
{
|
|
bool success = false;
|
|
|
|
if (IsClipboardFormatAvailable(CF_UNICODETEXT))
|
|
{
|
|
if (OpenClipboard(mWindowHandle))
|
|
{
|
|
HGLOBAL h_data = GetClipboardData(CF_UNICODETEXT);
|
|
if (h_data)
|
|
{
|
|
WCHAR *utf16str = (WCHAR*) GlobalLock(h_data);
|
|
if (utf16str)
|
|
{
|
|
dst = utf16str_to_wstring(utf16str);
|
|
LLWStringUtil::removeWindowsCR(dst);
|
|
GlobalUnlock(h_data);
|
|
success = true;
|
|
}
|
|
}
|
|
CloseClipboard();
|
|
}
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
|
|
bool LLWindowWin32::copyTextToClipboard(const LLWString& wstr)
|
|
{
|
|
bool success = false;
|
|
|
|
if (OpenClipboard(mWindowHandle))
|
|
{
|
|
EmptyClipboard();
|
|
|
|
// Provide a copy of the data in Unicode format.
|
|
LLWString sanitized_string(wstr);
|
|
LLWStringUtil::addCRLF(sanitized_string);
|
|
llutf16string out_utf16 = wstring_to_utf16str(sanitized_string);
|
|
const size_t size_utf16 = (out_utf16.length() + 1) * sizeof(WCHAR);
|
|
|
|
// Memory is allocated and then ownership of it is transfered to the system.
|
|
HGLOBAL hglobal_copy_utf16 = GlobalAlloc(GMEM_MOVEABLE, size_utf16);
|
|
if (hglobal_copy_utf16)
|
|
{
|
|
WCHAR* copy_utf16 = (WCHAR*) GlobalLock(hglobal_copy_utf16);
|
|
if (copy_utf16)
|
|
{
|
|
memcpy(copy_utf16, out_utf16.c_str(), size_utf16); /* Flawfinder: ignore */
|
|
GlobalUnlock(hglobal_copy_utf16);
|
|
|
|
if (SetClipboardData(CF_UNICODETEXT, hglobal_copy_utf16))
|
|
{
|
|
success = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
CloseClipboard();
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
// Constrains the mouse to the window.
|
|
void LLWindowWin32::setMouseClipping( bool b )
|
|
{
|
|
LL_PROFILE_ZONE_SCOPED_CATEGORY_WIN32;
|
|
ASSERT_MAIN_THREAD();
|
|
if( b != mIsMouseClipping )
|
|
{
|
|
bool success = false;
|
|
|
|
if( b )
|
|
{
|
|
GetClipCursor( &mOldMouseClip );
|
|
|
|
RECT client_rect_in_screen_space;
|
|
if( getClientRectInScreenSpace( &client_rect_in_screen_space ) )
|
|
{
|
|
success = ClipCursor( &client_rect_in_screen_space );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Must restore the old mouse clip, which may be set by another window.
|
|
success = ClipCursor( &mOldMouseClip );
|
|
SetRect( &mOldMouseClip, 0, 0, 0, 0 );
|
|
}
|
|
|
|
if( success )
|
|
{
|
|
mIsMouseClipping = b;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool LLWindowWin32::getClientRectInScreenSpace( RECT* rectp )
|
|
{
|
|
bool success = false;
|
|
|
|
RECT client_rect;
|
|
if (mWindowHandle && GetClientRect(mWindowHandle, &client_rect))
|
|
{
|
|
POINT top_left;
|
|
top_left.x = client_rect.left;
|
|
top_left.y = client_rect.top;
|
|
ClientToScreen(mWindowHandle, &top_left);
|
|
|
|
POINT bottom_right;
|
|
bottom_right.x = client_rect.right;
|
|
bottom_right.y = client_rect.bottom;
|
|
ClientToScreen(mWindowHandle, &bottom_right);
|
|
|
|
SetRect(rectp,
|
|
top_left.x,
|
|
top_left.y,
|
|
bottom_right.x,
|
|
bottom_right.y);
|
|
|
|
success = true;
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
void LLWindowWin32::flashIcon(F32 seconds)
|
|
{
|
|
mWindowThread->post([=]()
|
|
{
|
|
FLASHWINFO flash_info;
|
|
|
|
flash_info.cbSize = sizeof(FLASHWINFO);
|
|
flash_info.hwnd = mWindowHandle;
|
|
flash_info.dwFlags = FLASHW_TRAY;
|
|
flash_info.uCount = UINT(seconds / ICON_FLASH_TIME);
|
|
flash_info.dwTimeout = DWORD(1000.f * ICON_FLASH_TIME); // milliseconds
|
|
FlashWindowEx(&flash_info);
|
|
});
|
|
}
|
|
|
|
F32 LLWindowWin32::getGamma()
|
|
{
|
|
return mCurrentGamma;
|
|
}
|
|
|
|
bool LLWindowWin32::restoreGamma()
|
|
{
|
|
ASSERT_MAIN_THREAD();
|
|
if (mCustomGammaSet)
|
|
{
|
|
LL_DEBUGS("Window") << "Restoring gamma" << LL_ENDL;
|
|
mCustomGammaSet = false;
|
|
return SetDeviceGammaRamp(mhDC, mPrevGammaRamp);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool LLWindowWin32::setGamma(const F32 gamma)
|
|
{
|
|
ASSERT_MAIN_THREAD();
|
|
mCurrentGamma = gamma;
|
|
|
|
//Get the previous gamma ramp to restore later.
|
|
if (!mCustomGammaSet)
|
|
{
|
|
if (!gGLManager.mIsIntel) // skip for Intel GPUs (see SL-11341)
|
|
{
|
|
LL_DEBUGS("Window") << "Getting the previous gamma ramp to restore later" << LL_ENDL;
|
|
if (!GetDeviceGammaRamp(mhDC, mPrevGammaRamp))
|
|
{
|
|
LL_WARNS("Window") << "Failed to get the previous gamma ramp" << LL_ENDL;
|
|
return false;
|
|
}
|
|
}
|
|
mCustomGammaSet = true;
|
|
}
|
|
|
|
LL_DEBUGS("Window") << "Setting gamma to " << gamma << LL_ENDL;
|
|
|
|
for ( int i = 0; i < 256; ++i )
|
|
{
|
|
int mult = 256 - ( int ) ( ( gamma - 1.0f ) * 128.0f );
|
|
|
|
int value = mult * i;
|
|
|
|
if ( value > 0xffff )
|
|
value = 0xffff;
|
|
|
|
mCurrentGammaRamp[0][i] =
|
|
mCurrentGammaRamp[1][i] =
|
|
mCurrentGammaRamp[2][i] = (WORD) value;
|
|
};
|
|
|
|
return SetDeviceGammaRamp ( mhDC, mCurrentGammaRamp );
|
|
}
|
|
|
|
void LLWindowWin32::setFSAASamples(const U32 fsaa_samples)
|
|
{
|
|
ASSERT_MAIN_THREAD();
|
|
mFSAASamples = fsaa_samples;
|
|
}
|
|
|
|
U32 LLWindowWin32::getFSAASamples()
|
|
{
|
|
return mFSAASamples;
|
|
}
|
|
|
|
LLWindow::LLWindowResolution* LLWindowWin32::getSupportedResolutions(S32 &num_resolutions)
|
|
{
|
|
ASSERT_MAIN_THREAD();
|
|
if (!mSupportedResolutions)
|
|
{
|
|
mSupportedResolutions = new LLWindowResolution[MAX_NUM_RESOLUTIONS];
|
|
DEVMODE dev_mode;
|
|
::ZeroMemory(&dev_mode, sizeof(DEVMODE));
|
|
dev_mode.dmSize = sizeof(DEVMODE);
|
|
|
|
mNumSupportedResolutions = 0;
|
|
for (S32 mode_num = 0; mNumSupportedResolutions < MAX_NUM_RESOLUTIONS; mode_num++)
|
|
{
|
|
if (!EnumDisplaySettings(NULL, mode_num, &dev_mode))
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (dev_mode.dmBitsPerPel == BITS_PER_PIXEL &&
|
|
dev_mode.dmPelsWidth >= 800 &&
|
|
dev_mode.dmPelsHeight >= 600)
|
|
{
|
|
bool resolution_exists = false;
|
|
for(S32 i = 0; i < mNumSupportedResolutions; i++)
|
|
{
|
|
if (mSupportedResolutions[i].mWidth == dev_mode.dmPelsWidth &&
|
|
mSupportedResolutions[i].mHeight == dev_mode.dmPelsHeight)
|
|
{
|
|
resolution_exists = true;
|
|
}
|
|
}
|
|
if (!resolution_exists)
|
|
{
|
|
mSupportedResolutions[mNumSupportedResolutions].mWidth = dev_mode.dmPelsWidth;
|
|
mSupportedResolutions[mNumSupportedResolutions].mHeight = dev_mode.dmPelsHeight;
|
|
mNumSupportedResolutions++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
num_resolutions = mNumSupportedResolutions;
|
|
return mSupportedResolutions;
|
|
}
|
|
|
|
|
|
F32 LLWindowWin32::getNativeAspectRatio()
|
|
{
|
|
if (mOverrideAspectRatio > 0.f)
|
|
{
|
|
return mOverrideAspectRatio;
|
|
}
|
|
else if (mNativeAspectRatio > 0.f)
|
|
{
|
|
// we grabbed this value at startup, based on the user's desktop settings
|
|
return mNativeAspectRatio;
|
|
}
|
|
// 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);
|
|
}
|
|
|
|
F32 LLWindowWin32::getPixelAspectRatio()
|
|
{
|
|
F32 pixel_aspect = 1.f;
|
|
if (getFullscreen())
|
|
{
|
|
LLCoordScreen screen_size;
|
|
getSize(&screen_size);
|
|
pixel_aspect = getNativeAspectRatio() * (F32)screen_size.mY / (F32)screen_size.mX;
|
|
}
|
|
|
|
return pixel_aspect;
|
|
}
|
|
|
|
// Change display resolution. Returns true if successful.
|
|
// protected
|
|
bool LLWindowWin32::setDisplayResolution(S32 width, S32 height, S32 bits, S32 refresh)
|
|
{
|
|
DEVMODE dev_mode;
|
|
::ZeroMemory(&dev_mode, sizeof(DEVMODE));
|
|
dev_mode.dmSize = sizeof(DEVMODE);
|
|
bool success = false;
|
|
|
|
// Don't change anything if we don't have to
|
|
if (EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &dev_mode))
|
|
{
|
|
if (dev_mode.dmPelsWidth == width &&
|
|
dev_mode.dmPelsHeight == height &&
|
|
dev_mode.dmBitsPerPel == bits &&
|
|
dev_mode.dmDisplayFrequency == refresh )
|
|
{
|
|
// ...display mode identical, do nothing
|
|
return true;
|
|
}
|
|
}
|
|
|
|
memset(&dev_mode, 0, sizeof(dev_mode));
|
|
dev_mode.dmSize = sizeof(dev_mode);
|
|
dev_mode.dmPelsWidth = width;
|
|
dev_mode.dmPelsHeight = height;
|
|
dev_mode.dmBitsPerPel = bits;
|
|
dev_mode.dmDisplayFrequency = refresh;
|
|
dev_mode.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY;
|
|
|
|
// CDS_FULLSCREEN indicates that this is a temporary change to the device mode.
|
|
LONG cds_result = ChangeDisplaySettings(&dev_mode, CDS_FULLSCREEN);
|
|
|
|
success = (DISP_CHANGE_SUCCESSFUL == cds_result);
|
|
|
|
if (!success)
|
|
{
|
|
LL_WARNS("Window") << "setDisplayResolution failed, "
|
|
<< width << "x" << height << "x" << bits << " @ " << refresh << LL_ENDL;
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
// protected
|
|
bool LLWindowWin32::setFullscreenResolution()
|
|
{
|
|
if (mFullscreen)
|
|
{
|
|
return setDisplayResolution( mFullscreenWidth, mFullscreenHeight, mFullscreenBits, mFullscreenRefresh);
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// protected
|
|
bool LLWindowWin32::resetDisplayResolution()
|
|
{
|
|
LL_DEBUGS("Window") << "resetDisplayResolution START" << LL_ENDL;
|
|
|
|
LONG cds_result = ChangeDisplaySettings(NULL, 0);
|
|
|
|
bool success = (DISP_CHANGE_SUCCESSFUL == cds_result);
|
|
|
|
if (!success)
|
|
{
|
|
LL_WARNS("Window") << "resetDisplayResolution failed" << LL_ENDL;
|
|
}
|
|
|
|
LL_DEBUGS("Window") << "resetDisplayResolution END" << LL_ENDL;
|
|
|
|
return success;
|
|
}
|
|
|
|
void LLWindowWin32::swapBuffers()
|
|
{
|
|
{
|
|
LL_PROFILE_ZONE_SCOPED_CATEGORY_WIN32;
|
|
SwapBuffers(mhDC);
|
|
}
|
|
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("GPU Collect");
|
|
LL_PROFILER_GPU_COLLECT;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// LLSplashScreenImp
|
|
//
|
|
LLSplashScreenWin32::LLSplashScreenWin32()
|
|
: mWindow(NULL)
|
|
{
|
|
}
|
|
|
|
LLSplashScreenWin32::~LLSplashScreenWin32()
|
|
{
|
|
}
|
|
|
|
void LLSplashScreenWin32::showImpl()
|
|
{
|
|
// This appears to work. ???
|
|
HINSTANCE hinst = GetModuleHandle(NULL);
|
|
|
|
mWindow = CreateDialog(hinst,
|
|
TEXT("SPLASHSCREEN"),
|
|
NULL, // no parent
|
|
(DLGPROC) LLSplashScreenWin32::windowProc);
|
|
ShowWindow(mWindow, SW_SHOW);
|
|
|
|
// Should set taskbar text without creating a header for the window (caption)
|
|
SetWindowTextA(mWindow, "Second Life");
|
|
}
|
|
|
|
|
|
void LLSplashScreenWin32::updateImpl(const std::string& mesg)
|
|
{
|
|
if (!mWindow) return;
|
|
|
|
int output_str_len = MultiByteToWideChar(CP_UTF8, 0, mesg.c_str(), static_cast<int>(mesg.length()), NULL, 0);
|
|
if( output_str_len>1024 )
|
|
return;
|
|
|
|
WCHAR w_mesg[1025];//big enought to keep null terminatos
|
|
|
|
MultiByteToWideChar (CP_UTF8, 0, mesg.c_str(), static_cast<int>(mesg.length()), w_mesg, output_str_len);
|
|
|
|
//looks like MultiByteToWideChar didn't add null terminator to converted string, see EXT-4858
|
|
w_mesg[output_str_len] = 0;
|
|
|
|
SendDlgItemMessage(mWindow,
|
|
666, // HACK: text id
|
|
WM_SETTEXT,
|
|
FALSE,
|
|
(LPARAM)w_mesg);
|
|
}
|
|
|
|
|
|
void LLSplashScreenWin32::hideImpl()
|
|
{
|
|
if (mWindow)
|
|
{
|
|
if (!destroy_window_handler(mWindow))
|
|
{
|
|
LL_WARNS("Window") << "Failed to properly close splash screen window!" << LL_ENDL;
|
|
}
|
|
mWindow = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
// static
|
|
LRESULT CALLBACK LLSplashScreenWin32::windowProc(HWND h_wnd, UINT u_msg,
|
|
WPARAM w_param, LPARAM l_param)
|
|
{
|
|
// Just give it to windows
|
|
return DefWindowProc(h_wnd, u_msg, w_param, l_param);
|
|
}
|
|
|
|
//
|
|
// Helper Funcs
|
|
//
|
|
|
|
S32 OSMessageBoxWin32(const std::string& text, const std::string& caption, U32 type)
|
|
{
|
|
UINT uType;
|
|
|
|
switch(type)
|
|
{
|
|
case OSMB_OK:
|
|
uType = MB_OK;
|
|
break;
|
|
case OSMB_OKCANCEL:
|
|
uType = MB_OKCANCEL;
|
|
break;
|
|
case OSMB_YESNO:
|
|
uType = MB_YESNO;
|
|
break;
|
|
default:
|
|
uType = MB_OK;
|
|
break;
|
|
}
|
|
|
|
// AG: Of course, the using of the static global variable sWindowHandleForMessageBox
|
|
// instead of using the field mWindowHandle of the class LLWindowWin32 looks strange.
|
|
// But in fact, the function OSMessageBoxWin32() doesn't have access to gViewerWindow
|
|
// because the former is implemented in the library llwindow which is abstract enough.
|
|
//
|
|
// "This is why I'm doing it this way, instead of what you would think would be more obvious..."
|
|
// (C) Nat Goodspeed
|
|
int retval_win = MessageBoxW(sWindowHandleForMessageBox, // HWND
|
|
ll_convert_string_to_wide(text).c_str(),
|
|
ll_convert_string_to_wide(caption).c_str(),
|
|
uType);
|
|
S32 retval;
|
|
|
|
switch(retval_win)
|
|
{
|
|
case IDYES:
|
|
retval = OSBTN_YES;
|
|
break;
|
|
case IDNO:
|
|
retval = OSBTN_NO;
|
|
break;
|
|
case IDOK:
|
|
retval = OSBTN_OK;
|
|
break;
|
|
case IDCANCEL:
|
|
retval = OSBTN_CANCEL;
|
|
break;
|
|
default:
|
|
retval = OSBTN_CANCEL;
|
|
break;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
|
|
void LLWindowWin32::spawnWebBrowser(const std::string& escaped_url, bool async)
|
|
{
|
|
bool found = false;
|
|
S32 i;
|
|
for (i = 0; i < gURLProtocolWhitelistCount; i++)
|
|
{
|
|
if (escaped_url.find(gURLProtocolWhitelist[i]) == 0)
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
{
|
|
LL_WARNS("Window") << "spawn_web_browser() called for url with protocol not on whitelist: " << escaped_url << LL_ENDL;
|
|
return;
|
|
}
|
|
|
|
LL_INFOS("Window") << "Opening URL " << escaped_url << LL_ENDL;
|
|
|
|
// replaced ShellExecute code with ShellExecuteEx since ShellExecute doesn't work
|
|
// reliablly on Vista.
|
|
|
|
// this is madness.. no, this is..
|
|
LLWString url_wstring = utf8str_to_wstring( escaped_url );
|
|
llutf16string url_utf16 = wstring_to_utf16str( url_wstring );
|
|
|
|
// let the OS decide what to use to open the URL
|
|
SHELLEXECUTEINFO sei = { sizeof( sei ) };
|
|
// NOTE: this assumes that SL will stick around long enough to complete the DDE message exchange
|
|
// necessary for ShellExecuteEx to complete
|
|
if (async)
|
|
{
|
|
sei.fMask = SEE_MASK_ASYNCOK;
|
|
}
|
|
sei.nShow = SW_SHOWNORMAL;
|
|
sei.lpVerb = L"open";
|
|
sei.lpFile = url_utf16.c_str();
|
|
ShellExecuteEx( &sei );
|
|
}
|
|
|
|
/*
|
|
Make the raw keyboard data available - used to poke through to LLQtWebKit so
|
|
that Qt/Webkit has access to the virtual keycodes etc. that it needs
|
|
*/
|
|
LLSD LLWindowWin32::getNativeKeyData()
|
|
{
|
|
LLSD result = LLSD::emptyMap();
|
|
|
|
result["scan_code"] = (S32)mKeyScanCode;
|
|
result["virtual_key"] = (S32)mKeyVirtualKey;
|
|
result["msg"] = ll_sd_from_U32(mRawMsg);
|
|
result["w_param"] = ll_sd_from_U32(mRawWParam);
|
|
result["l_param"] = ll_sd_from_U32(mRawLParam);
|
|
|
|
return result;
|
|
}
|
|
|
|
bool LLWindowWin32::dialogColorPicker( F32 *r, F32 *g, F32 *b )
|
|
{
|
|
bool retval = false;
|
|
|
|
static CHOOSECOLOR cc;
|
|
static COLORREF crCustColors[16];
|
|
cc.lStructSize = sizeof(CHOOSECOLOR);
|
|
cc.hwndOwner = mWindowHandle;
|
|
cc.hInstance = NULL;
|
|
cc.rgbResult = RGB ((*r * 255.f),(*g *255.f),(*b * 255.f));
|
|
//cc.rgbResult = RGB (0x80,0x80,0x80);
|
|
cc.lpCustColors = crCustColors;
|
|
cc.Flags = CC_RGBINIT | CC_FULLOPEN;
|
|
cc.lCustData = 0;
|
|
cc.lpfnHook = NULL;
|
|
cc.lpTemplateName = NULL;
|
|
|
|
// This call is modal, so pause agent
|
|
//send_agent_pause(); // this is in newview and we don't want to set up a dependency
|
|
{
|
|
retval = ChooseColor(&cc);
|
|
}
|
|
//send_agent_resume(); // this is in newview and we don't want to set up a dependency
|
|
|
|
*b = ((F32)((cc.rgbResult >> 16) & 0xff)) / 255.f;
|
|
|
|
*g = ((F32)((cc.rgbResult >> 8) & 0xff)) / 255.f;
|
|
|
|
*r = ((F32)(cc.rgbResult & 0xff)) / 255.f;
|
|
|
|
return (retval);
|
|
}
|
|
|
|
void *LLWindowWin32::getPlatformWindow()
|
|
{
|
|
return (void*)mWindowHandle;
|
|
}
|
|
|
|
void LLWindowWin32::bringToFront()
|
|
{
|
|
mWindowThread->post([=]()
|
|
{
|
|
BringWindowToTop(mWindowHandle);
|
|
});
|
|
}
|
|
|
|
// set (OS) window focus back to the client
|
|
void LLWindowWin32::focusClient()
|
|
{
|
|
mWindowThread->post([=]()
|
|
{
|
|
SetFocus(mWindowHandle);
|
|
});
|
|
}
|
|
|
|
void LLWindowWin32::allowLanguageTextInput(LLPreeditor *preeditor, bool b)
|
|
{
|
|
if (b == sLanguageTextInputAllowed || !LLWinImm::isAvailable())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (preeditor != mPreeditor && !b)
|
|
{
|
|
// This condition may occur with a call to
|
|
// setEnabled(bool) from LLTextEditor or LLLineEditor
|
|
// when the control is not focused.
|
|
// We need to silently ignore the case so that
|
|
// the language input status of the focused control
|
|
// is not disturbed.
|
|
return;
|
|
}
|
|
|
|
// Take care of old and new preeditors.
|
|
if (preeditor != mPreeditor || !b)
|
|
{
|
|
if (sLanguageTextInputAllowed)
|
|
{
|
|
interruptLanguageTextInput();
|
|
}
|
|
mPreeditor = (b ? preeditor : NULL);
|
|
}
|
|
|
|
sLanguageTextInputAllowed = b;
|
|
|
|
if (sLanguageTextInputAllowed)
|
|
{
|
|
mWindowThread->post([=]()
|
|
{
|
|
// Allowing: Restore the previous IME status, so that the user has a feeling that the previous
|
|
// text input continues naturally. Be careful, however, the IME status is meaningful only during the user keeps
|
|
// using same Input Locale (aka Keyboard Layout).
|
|
if (sWinIMEOpened && GetKeyboardLayout(0) == sWinInputLocale)
|
|
{
|
|
HIMC himc = LLWinImm::getContext(mWindowHandle);
|
|
LLWinImm::setOpenStatus(himc, true);
|
|
LLWinImm::setConversionStatus(himc, sWinIMEConversionMode, sWinIMESentenceMode);
|
|
LLWinImm::releaseContext(mWindowHandle, himc);
|
|
}
|
|
});
|
|
}
|
|
else
|
|
{
|
|
mWindowThread->post([=]()
|
|
{
|
|
// Disallowing: Turn off the IME so that succeeding key events bypass IME and come to us directly.
|
|
// However, do it after saving the current IME status. We need to restore the status when
|
|
// allowing language text input again.
|
|
sWinInputLocale = GetKeyboardLayout(0);
|
|
sWinIMEOpened = LLWinImm::isIME(sWinInputLocale);
|
|
if (sWinIMEOpened)
|
|
{
|
|
HIMC himc = LLWinImm::getContext(mWindowHandle);
|
|
sWinIMEOpened = LLWinImm::getOpenStatus(himc);
|
|
if (sWinIMEOpened)
|
|
{
|
|
LLWinImm::getConversionStatus(himc, &sWinIMEConversionMode, &sWinIMESentenceMode);
|
|
|
|
// We need both ImmSetConversionStatus and ImmSetOpenStatus here to surely disable IME's
|
|
// keyboard hooking, because Some IME reacts only on the former and some other on the latter...
|
|
LLWinImm::setConversionStatus(himc, IME_CMODE_NOCONVERSION, sWinIMESentenceMode);
|
|
LLWinImm::setOpenStatus(himc, false);
|
|
}
|
|
LLWinImm::releaseContext(mWindowHandle, himc);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
void LLWindowWin32::fillCandidateForm(const LLCoordGL& caret, const LLRect& bounds,
|
|
CANDIDATEFORM *form)
|
|
{
|
|
LLCoordWindow caret_coord, top_left, bottom_right;
|
|
convertCoords(caret, &caret_coord);
|
|
convertCoords(LLCoordGL(bounds.mLeft, bounds.mTop), &top_left);
|
|
convertCoords(LLCoordGL(bounds.mRight, bounds.mBottom), &bottom_right);
|
|
|
|
memset(form, 0, sizeof(CANDIDATEFORM));
|
|
form->dwStyle = CFS_EXCLUDE;
|
|
form->ptCurrentPos.x = caret_coord.mX;
|
|
form->ptCurrentPos.y = caret_coord.mY;
|
|
form->rcArea.left = top_left.mX;
|
|
form->rcArea.top = top_left.mY;
|
|
form->rcArea.right = bottom_right.mX;
|
|
form->rcArea.bottom = bottom_right.mY;
|
|
}
|
|
|
|
|
|
// Put the IME window at the right place (near current text input). Point coordinates should be the top of the current text line.
|
|
void LLWindowWin32::setLanguageTextInput( const LLCoordGL & position )
|
|
{
|
|
if (sLanguageTextInputAllowed && LLWinImm::isAvailable())
|
|
{
|
|
HIMC himc = LLWinImm::getContext(mWindowHandle);
|
|
|
|
LLCoordWindow win_pos;
|
|
convertCoords( position, &win_pos );
|
|
|
|
if ( win_pos.mX >= 0 && win_pos.mY >= 0 &&
|
|
(win_pos.mX != sWinIMEWindowPosition.mX) || (win_pos.mY != sWinIMEWindowPosition.mY) )
|
|
{
|
|
COMPOSITIONFORM ime_form;
|
|
memset( &ime_form, 0, sizeof(ime_form) );
|
|
ime_form.dwStyle = CFS_POINT;
|
|
ime_form.ptCurrentPos.x = win_pos.mX;
|
|
ime_form.ptCurrentPos.y = win_pos.mY;
|
|
|
|
LLWinImm::setCompositionWindow( himc, &ime_form );
|
|
|
|
sWinIMEWindowPosition = win_pos;
|
|
}
|
|
|
|
LLWinImm::releaseContext(mWindowHandle, himc);
|
|
}
|
|
}
|
|
|
|
|
|
void LLWindowWin32::fillCharPosition(const LLCoordGL& caret, const LLRect& bounds, const LLRect& control,
|
|
IMECHARPOSITION *char_position)
|
|
{
|
|
LLCoordScreen caret_coord, top_left, bottom_right;
|
|
convertCoords(caret, &caret_coord);
|
|
convertCoords(LLCoordGL(bounds.mLeft, bounds.mTop), &top_left);
|
|
convertCoords(LLCoordGL(bounds.mRight, bounds.mBottom), &bottom_right);
|
|
|
|
char_position->pt.x = caret_coord.mX;
|
|
char_position->pt.y = top_left.mY; // Windows wants the coordinate of upper left corner of a character...
|
|
char_position->cLineHeight = bottom_right.mY - top_left.mY;
|
|
char_position->rcDocument.left = top_left.mX;
|
|
char_position->rcDocument.top = top_left.mY;
|
|
char_position->rcDocument.right = bottom_right.mX;
|
|
char_position->rcDocument.bottom = bottom_right.mY;
|
|
}
|
|
|
|
void LLWindowWin32::fillCompositionLogfont(LOGFONT *logfont)
|
|
{
|
|
// Our font is a list of FreeType recognized font files that may
|
|
// not have a corresponding ones in Windows' fonts. Hence, we
|
|
// can't simply tell Windows which font we are using. We will
|
|
// notify a _standard_ font for a current input locale instead.
|
|
// We use a hard-coded knowledge about the Windows' standard
|
|
// configuration to do so...
|
|
|
|
memset(logfont, 0, sizeof(LOGFONT));
|
|
|
|
const WORD lang_id = LOWORD(GetKeyboardLayout(0));
|
|
switch (PRIMARYLANGID(lang_id))
|
|
{
|
|
case LANG_CHINESE:
|
|
// We need to identify one of two Chinese fonts.
|
|
switch (SUBLANGID(lang_id))
|
|
{
|
|
case SUBLANG_CHINESE_SIMPLIFIED:
|
|
case SUBLANG_CHINESE_SINGAPORE:
|
|
logfont->lfCharSet = GB2312_CHARSET;
|
|
lstrcpy(logfont->lfFaceName, TEXT("SimHei"));
|
|
break;
|
|
case SUBLANG_CHINESE_TRADITIONAL:
|
|
case SUBLANG_CHINESE_HONGKONG:
|
|
case SUBLANG_CHINESE_MACAU:
|
|
default:
|
|
logfont->lfCharSet = CHINESEBIG5_CHARSET;
|
|
lstrcpy(logfont->lfFaceName, TEXT("MingLiU"));
|
|
break;
|
|
}
|
|
break;
|
|
case LANG_JAPANESE:
|
|
logfont->lfCharSet = SHIFTJIS_CHARSET;
|
|
lstrcpy(logfont->lfFaceName, TEXT("MS Gothic"));
|
|
break;
|
|
case LANG_KOREAN:
|
|
logfont->lfCharSet = HANGUL_CHARSET;
|
|
lstrcpy(logfont->lfFaceName, TEXT("Gulim"));
|
|
break;
|
|
default:
|
|
logfont->lfCharSet = ANSI_CHARSET;
|
|
lstrcpy(logfont->lfFaceName, TEXT("Tahoma"));
|
|
break;
|
|
}
|
|
|
|
logfont->lfHeight = mPreeditor->getPreeditFontSize();
|
|
logfont->lfWeight = FW_NORMAL;
|
|
}
|
|
|
|
U32 LLWindowWin32::fillReconvertString(const LLWString &text,
|
|
S32 focus, S32 focus_length, RECONVERTSTRING *reconvert_string)
|
|
{
|
|
const llutf16string text_utf16 = wstring_to_utf16str(text);
|
|
const DWORD required_size = sizeof(RECONVERTSTRING) + (static_cast<DWORD>(text_utf16.length()) + 1) * sizeof(WCHAR);
|
|
if (reconvert_string && reconvert_string->dwSize >= required_size)
|
|
{
|
|
const DWORD focus_utf16_at = wstring_utf16_length(text, 0, focus);
|
|
const DWORD focus_utf16_length = wstring_utf16_length(text, focus, focus_length);
|
|
|
|
reconvert_string->dwVersion = 0;
|
|
reconvert_string->dwStrLen = static_cast<DWORD>(text_utf16.length());
|
|
reconvert_string->dwStrOffset = sizeof(RECONVERTSTRING);
|
|
reconvert_string->dwCompStrLen = focus_utf16_length;
|
|
reconvert_string->dwCompStrOffset = focus_utf16_at * sizeof(WCHAR);
|
|
reconvert_string->dwTargetStrLen = 0;
|
|
reconvert_string->dwTargetStrOffset = focus_utf16_at * sizeof(WCHAR);
|
|
|
|
const LPWSTR text = (LPWSTR)((BYTE *)reconvert_string + sizeof(RECONVERTSTRING));
|
|
memcpy(text, text_utf16.c_str(), (text_utf16.length() + 1) * sizeof(WCHAR));
|
|
}
|
|
return required_size;
|
|
}
|
|
|
|
void LLWindowWin32::updateLanguageTextInputArea()
|
|
{
|
|
if (!mPreeditor || !LLWinImm::isAvailable())
|
|
{
|
|
return;
|
|
}
|
|
|
|
LLCoordGL caret_coord;
|
|
LLRect preedit_bounds;
|
|
if (mPreeditor->getPreeditLocation(-1, &caret_coord, &preedit_bounds, NULL))
|
|
{
|
|
mLanguageTextInputPointGL = caret_coord;
|
|
mLanguageTextInputAreaGL = preedit_bounds;
|
|
|
|
CANDIDATEFORM candidate_form;
|
|
fillCandidateForm(caret_coord, preedit_bounds, &candidate_form);
|
|
|
|
HIMC himc = LLWinImm::getContext(mWindowHandle);
|
|
// Win32 document says there may be up to 4 candidate windows.
|
|
// This magic number 4 appears only in the document, and
|
|
// there are no constant/macro for the value...
|
|
for (int i = 3; i >= 0; --i)
|
|
{
|
|
candidate_form.dwIndex = i;
|
|
LLWinImm::setCandidateWindow(himc, &candidate_form);
|
|
}
|
|
LLWinImm::releaseContext(mWindowHandle, himc);
|
|
}
|
|
}
|
|
|
|
void LLWindowWin32::interruptLanguageTextInput()
|
|
{
|
|
ASSERT_MAIN_THREAD();
|
|
if (mPreeditor && LLWinImm::isAvailable())
|
|
{
|
|
HIMC himc = LLWinImm::getContext(mWindowHandle);
|
|
LLWinImm::notifyIME(himc, NI_COMPOSITIONSTR, CPS_COMPLETE, 0);
|
|
LLWinImm::releaseContext(mWindowHandle, himc);
|
|
}
|
|
}
|
|
|
|
void LLWindowWin32::handleStartCompositionMessage()
|
|
{
|
|
// Let IME know the font to use in feedback UI.
|
|
LOGFONT logfont;
|
|
fillCompositionLogfont(&logfont);
|
|
HIMC himc = LLWinImm::getContext(mWindowHandle);
|
|
LLWinImm::setCompositionFont(himc, &logfont);
|
|
LLWinImm::releaseContext(mWindowHandle, himc);
|
|
}
|
|
|
|
// Handle WM_IME_COMPOSITION message.
|
|
|
|
void LLWindowWin32::handleCompositionMessage(const U32 indexes)
|
|
{
|
|
if (!mPreeditor)
|
|
{
|
|
return;
|
|
}
|
|
bool needs_update = false;
|
|
LLWString result_string;
|
|
LLWString preedit_string;
|
|
S32 preedit_string_utf16_length = 0;
|
|
LLPreeditor::segment_lengths_t preedit_segment_lengths;
|
|
LLPreeditor::standouts_t preedit_standouts;
|
|
|
|
// Step I: Receive details of preedits from IME.
|
|
|
|
HIMC himc = LLWinImm::getContext(mWindowHandle);
|
|
|
|
if (indexes & GCS_RESULTSTR)
|
|
{
|
|
LONG size = LLWinImm::getCompositionString(himc, GCS_RESULTSTR, NULL, 0);
|
|
if (size >= 0)
|
|
{
|
|
const LPWSTR data = new WCHAR[size / sizeof(WCHAR) + 1];
|
|
size = LLWinImm::getCompositionString(himc, GCS_RESULTSTR, data, size);
|
|
if (size > 0)
|
|
{
|
|
result_string = utf16str_to_wstring(llutf16string(data, size / sizeof(WCHAR)));
|
|
}
|
|
delete[] data;
|
|
needs_update = true;
|
|
}
|
|
}
|
|
|
|
if (indexes & GCS_COMPSTR)
|
|
{
|
|
LONG size = LLWinImm::getCompositionString(himc, GCS_COMPSTR, NULL, 0);
|
|
if (size >= 0)
|
|
{
|
|
const LPWSTR data = new WCHAR[size / sizeof(WCHAR) + 1];
|
|
size = LLWinImm::getCompositionString(himc, GCS_COMPSTR, data, size);
|
|
if (size > 0)
|
|
{
|
|
preedit_string_utf16_length = size / sizeof(WCHAR);
|
|
preedit_string = utf16str_to_wstring(llutf16string(data, size / sizeof(WCHAR)));
|
|
}
|
|
delete[] data;
|
|
needs_update = true;
|
|
}
|
|
}
|
|
|
|
if ((indexes & GCS_COMPCLAUSE) && preedit_string.length() > 0)
|
|
{
|
|
LONG size = LLWinImm::getCompositionString(himc, GCS_COMPCLAUSE, NULL, 0);
|
|
if (size > 0)
|
|
{
|
|
const LPDWORD data = new DWORD[size / sizeof(DWORD)];
|
|
size = LLWinImm::getCompositionString(himc, GCS_COMPCLAUSE, data, size);
|
|
if (size >= sizeof(DWORD) * 2
|
|
&& data[0] == 0 && data[size / sizeof(DWORD) - 1] == preedit_string_utf16_length)
|
|
{
|
|
preedit_segment_lengths.resize(size / sizeof(DWORD) - 1);
|
|
S32 offset = 0;
|
|
for (U32 i = 0; i < preedit_segment_lengths.size(); i++)
|
|
{
|
|
const S32 length = wstring_wstring_length_from_utf16_length(preedit_string, offset, data[i + 1] - data[i]);
|
|
preedit_segment_lengths[i] = length;
|
|
offset += length;
|
|
}
|
|
}
|
|
delete[] data;
|
|
}
|
|
}
|
|
|
|
if ((indexes & GCS_COMPATTR) && preedit_segment_lengths.size() > 1)
|
|
{
|
|
LONG size = LLWinImm::getCompositionString(himc, GCS_COMPATTR, NULL, 0);
|
|
if (size > 0)
|
|
{
|
|
const LPBYTE data = new BYTE[size / sizeof(BYTE)];
|
|
size = LLWinImm::getCompositionString(himc, GCS_COMPATTR, data, size);
|
|
if (size == preedit_string_utf16_length)
|
|
{
|
|
preedit_standouts.assign(preedit_segment_lengths.size(), false);
|
|
S32 offset = 0;
|
|
for (U32 i = 0; i < preedit_segment_lengths.size(); i++)
|
|
{
|
|
if (ATTR_TARGET_CONVERTED == data[offset] || ATTR_TARGET_NOTCONVERTED == data[offset])
|
|
{
|
|
preedit_standouts[i] = true;
|
|
}
|
|
offset += wstring_utf16_length(preedit_string, offset, preedit_segment_lengths[i]);
|
|
}
|
|
}
|
|
delete[] data;
|
|
}
|
|
}
|
|
|
|
S32 caret_position = static_cast<S32>(preedit_string.length());
|
|
if (indexes & GCS_CURSORPOS)
|
|
{
|
|
const S32 caret_position_utf16 = LLWinImm::getCompositionString(himc, GCS_CURSORPOS, NULL, 0);
|
|
if (caret_position_utf16 >= 0 && caret_position <= preedit_string_utf16_length)
|
|
{
|
|
caret_position = wstring_wstring_length_from_utf16_length(preedit_string, 0, caret_position_utf16);
|
|
}
|
|
}
|
|
|
|
if (indexes == 0)
|
|
{
|
|
// I'm not sure this condition really happens, but
|
|
// Windows SDK document says it is an indication
|
|
// of "reset everything."
|
|
needs_update = true;
|
|
}
|
|
|
|
LLWinImm::releaseContext(mWindowHandle, himc);
|
|
|
|
// Step II: Update the active preeditor.
|
|
|
|
if (needs_update)
|
|
{
|
|
if (preedit_string.length() != 0 || result_string.length() != 0)
|
|
{
|
|
mPreeditor->resetPreedit();
|
|
}
|
|
|
|
if (result_string.length() > 0)
|
|
{
|
|
for (LLWString::const_iterator i = result_string.begin(); i != result_string.end(); i++)
|
|
{
|
|
mPreeditor->handleUnicodeCharHere(*i);
|
|
}
|
|
}
|
|
|
|
if (preedit_string.length() == 0)
|
|
{
|
|
preedit_segment_lengths.clear();
|
|
preedit_standouts.clear();
|
|
}
|
|
else
|
|
{
|
|
if (preedit_segment_lengths.size() == 0)
|
|
{
|
|
preedit_segment_lengths.assign(1, static_cast<S32>(preedit_string.length()));
|
|
}
|
|
if (preedit_standouts.size() == 0)
|
|
{
|
|
preedit_standouts.assign(preedit_segment_lengths.size(), false);
|
|
}
|
|
}
|
|
mPreeditor->updatePreedit(preedit_string, preedit_segment_lengths, preedit_standouts, caret_position);
|
|
|
|
// Some IME doesn't query char position after WM_IME_COMPOSITION,
|
|
// so we need to update them actively.
|
|
updateLanguageTextInputArea();
|
|
}
|
|
}
|
|
|
|
// Given a text and a focus range, find_context finds and returns a
|
|
// surrounding context of the focused subtext. A variable pointed
|
|
// to by offset receives the offset in llwchars of the beginning of
|
|
// the returned context string in the given wtext.
|
|
|
|
static LLWString find_context(const LLWString & wtext, S32 focus, S32 focus_length, S32 *offset)
|
|
{
|
|
static const S32 CONTEXT_EXCESS = 30; // This value is by experiences.
|
|
|
|
const S32 e = llmin((S32) wtext.length(), focus + focus_length + CONTEXT_EXCESS);
|
|
S32 end = focus + focus_length;
|
|
while (end < e && '\n' != wtext[end])
|
|
{
|
|
end++;
|
|
}
|
|
|
|
const S32 s = llmax(0, focus - CONTEXT_EXCESS);
|
|
S32 start = focus;
|
|
while (start > s && '\n' != wtext[start - 1])
|
|
{
|
|
--start;
|
|
}
|
|
|
|
*offset = start;
|
|
return wtext.substr(start, end - start);
|
|
}
|
|
|
|
// final stage of handling drop requests - both from WM_DROPFILES message
|
|
// for files and via IDropTarget interface requests.
|
|
LLWindowCallbacks::DragNDropResult LLWindowWin32::completeDragNDropRequest( const LLCoordGL gl_coord, const MASK mask, LLWindowCallbacks::DragNDropAction action, const std::string url )
|
|
{
|
|
ASSERT_MAIN_THREAD();
|
|
return mCallbacks->handleDragNDrop( this, gl_coord, mask, action, url );
|
|
}
|
|
|
|
// Handle WM_IME_REQUEST message.
|
|
// If it handled the message, returns true. Otherwise, false.
|
|
// When it handled the message, the value to be returned from
|
|
// the Window Procedure is set to *result.
|
|
|
|
bool LLWindowWin32::handleImeRequests(WPARAM request, LPARAM param, LRESULT *result)
|
|
{
|
|
if ( mPreeditor )
|
|
{
|
|
switch (request)
|
|
{
|
|
case IMR_CANDIDATEWINDOW: // http://msdn2.microsoft.com/en-us/library/ms776080.aspx
|
|
{
|
|
LLCoordGL caret_coord;
|
|
LLRect preedit_bounds;
|
|
mPreeditor->getPreeditLocation(-1, &caret_coord, &preedit_bounds, NULL);
|
|
|
|
CANDIDATEFORM *const form = (CANDIDATEFORM *)param;
|
|
DWORD const dwIndex = form->dwIndex;
|
|
fillCandidateForm(caret_coord, preedit_bounds, form);
|
|
form->dwIndex = dwIndex;
|
|
|
|
*result = 1;
|
|
return true;
|
|
}
|
|
case IMR_QUERYCHARPOSITION:
|
|
{
|
|
IMECHARPOSITION *const char_position = (IMECHARPOSITION *)param;
|
|
|
|
// char_position->dwCharPos counts in number of
|
|
// WCHARs, i.e., UTF-16 encoding units, so we can't simply pass the
|
|
// number to getPreeditLocation.
|
|
|
|
const LLWString & wtext = mPreeditor->getPreeditString();
|
|
S32 preedit, preedit_length;
|
|
mPreeditor->getPreeditRange(&preedit, &preedit_length);
|
|
LLCoordGL caret_coord;
|
|
LLRect preedit_bounds, text_control;
|
|
const S32 position = wstring_wstring_length_from_utf16_length(wtext, preedit, char_position->dwCharPos);
|
|
|
|
if (!mPreeditor->getPreeditLocation(position, &caret_coord, &preedit_bounds, &text_control))
|
|
{
|
|
LL_WARNS("Window") << "*** IMR_QUERYCHARPOSITON called but getPreeditLocation failed." << LL_ENDL;
|
|
return false;
|
|
}
|
|
|
|
fillCharPosition(caret_coord, preedit_bounds, text_control, char_position);
|
|
|
|
*result = 1;
|
|
return true;
|
|
}
|
|
case IMR_COMPOSITIONFONT:
|
|
{
|
|
fillCompositionLogfont((LOGFONT *)param);
|
|
|
|
*result = 1;
|
|
return true;
|
|
}
|
|
case IMR_RECONVERTSTRING:
|
|
{
|
|
mPreeditor->resetPreedit();
|
|
const LLWString & wtext = mPreeditor->getPreeditString();
|
|
S32 select, select_length;
|
|
mPreeditor->getSelectionRange(&select, &select_length);
|
|
|
|
S32 context_offset;
|
|
const LLWString context = find_context(wtext, select, select_length, &context_offset);
|
|
|
|
RECONVERTSTRING * const reconvert_string = (RECONVERTSTRING *)param;
|
|
const U32 size = fillReconvertString(context, select - context_offset, select_length, reconvert_string);
|
|
if (reconvert_string)
|
|
{
|
|
if (select_length == 0)
|
|
{
|
|
// Let the IME to decide the reconversion range, and
|
|
// adjust the reconvert_string structure accordingly.
|
|
HIMC himc = LLWinImm::getContext(mWindowHandle);
|
|
const bool adjusted = LLWinImm::setCompositionString(himc,
|
|
SCS_QUERYRECONVERTSTRING, reconvert_string, size, NULL, 0);
|
|
LLWinImm::releaseContext(mWindowHandle, himc);
|
|
if (adjusted)
|
|
{
|
|
const llutf16string & text_utf16 = wstring_to_utf16str(context);
|
|
const S32 new_preedit_start = reconvert_string->dwCompStrOffset / sizeof(WCHAR);
|
|
const S32 new_preedit_end = new_preedit_start + reconvert_string->dwCompStrLen;
|
|
select = utf16str_wstring_length(text_utf16, new_preedit_start);
|
|
select_length = utf16str_wstring_length(text_utf16, new_preedit_end) - select;
|
|
select += context_offset;
|
|
}
|
|
}
|
|
mPreeditor->markAsPreedit(select, select_length);
|
|
}
|
|
|
|
*result = size;
|
|
return true;
|
|
}
|
|
case IMR_CONFIRMRECONVERTSTRING:
|
|
{
|
|
*result = 0;
|
|
return true;
|
|
}
|
|
case IMR_DOCUMENTFEED:
|
|
{
|
|
const LLWString & wtext = mPreeditor->getPreeditString();
|
|
S32 preedit, preedit_length;
|
|
mPreeditor->getPreeditRange(&preedit, &preedit_length);
|
|
|
|
S32 context_offset;
|
|
LLWString context = find_context(wtext, preedit, preedit_length, &context_offset);
|
|
preedit -= context_offset;
|
|
preedit_length = llmin(preedit_length, (S32)context.length() - preedit);
|
|
if (preedit_length > 0 && preedit >= 0)
|
|
{
|
|
// IMR_DOCUMENTFEED may be called when we have an active preedit.
|
|
// We should pass the context string *excluding* the preedit string.
|
|
// Otherwise, some IME are confused.
|
|
context.erase(preedit, preedit_length);
|
|
}
|
|
|
|
RECONVERTSTRING *reconvert_string = (RECONVERTSTRING *)param;
|
|
*result = fillReconvertString(context, preedit, 0, reconvert_string);
|
|
return true;
|
|
}
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//static
|
|
void LLWindowWin32::setDPIAwareness()
|
|
{
|
|
HMODULE hShcore = LoadLibrary(L"shcore.dll");
|
|
if (hShcore != NULL)
|
|
{
|
|
SetProcessDpiAwarenessType pSPDA;
|
|
pSPDA = (SetProcessDpiAwarenessType)GetProcAddress(hShcore, "SetProcessDpiAwareness");
|
|
if (pSPDA)
|
|
{
|
|
|
|
HRESULT hr = pSPDA(PROCESS_PER_MONITOR_DPI_AWARE);
|
|
if (hr != S_OK)
|
|
{
|
|
LL_WARNS() << "SetProcessDpiAwareness() function returned an error. Will use legacy DPI awareness API of Win XP/7" << LL_ENDL;
|
|
}
|
|
}
|
|
FreeLibrary(hShcore);
|
|
}
|
|
else
|
|
{
|
|
LL_WARNS() << "Could not load shcore.dll library (included by <ShellScalingAPI.h> from Win 8.1 SDK. Will use legacy DPI awareness API of Win XP/7" << LL_ENDL;
|
|
}
|
|
}
|
|
|
|
void* LLWindowWin32::getDirectInput8()
|
|
{
|
|
return &gDirectInput8;
|
|
}
|
|
|
|
bool LLWindowWin32::getInputDevices(U32 device_type_filter,
|
|
std::function<bool(std::string&, LLSD&, void*)> osx_callback,
|
|
void * di8_devices_callback,
|
|
void* userdata)
|
|
{
|
|
if (gDirectInput8 != NULL)
|
|
{
|
|
// Enumerate devices
|
|
HRESULT status = gDirectInput8->EnumDevices(
|
|
(DWORD) device_type_filter, // DWORD dwDevType,
|
|
(LPDIENUMDEVICESCALLBACK)di8_devices_callback, // LPDIENUMDEVICESCALLBACK lpCallback, // BOOL DIEnumDevicesCallback( LPCDIDEVICEINSTANCE lpddi, LPVOID pvRef ) // BOOL CALLBACK DinputDevice::DevicesCallback
|
|
(LPVOID*)userdata, // LPVOID pvRef
|
|
DIEDFL_ATTACHEDONLY // DWORD dwFlags
|
|
);
|
|
|
|
return status == DI_OK;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
F32 LLWindowWin32::getSystemUISize()
|
|
{
|
|
F32 scale_value = 1.f;
|
|
HWND hWnd = (HWND)getPlatformWindow();
|
|
HDC hdc = GetDC(hWnd);
|
|
HMONITOR hMonitor;
|
|
HANDLE hProcess = GetCurrentProcess();
|
|
PROCESS_DPI_AWARENESS dpi_awareness;
|
|
|
|
HMODULE hShcore = LoadLibrary(L"shcore.dll");
|
|
|
|
if (hShcore != NULL)
|
|
{
|
|
GetProcessDpiAwarenessType pGPDA;
|
|
pGPDA = (GetProcessDpiAwarenessType)GetProcAddress(hShcore, "GetProcessDpiAwareness");
|
|
GetDpiForMonitorType pGDFM;
|
|
pGDFM = (GetDpiForMonitorType)GetProcAddress(hShcore, "GetDpiForMonitor");
|
|
if (pGPDA != NULL && pGDFM != NULL)
|
|
{
|
|
pGPDA(hProcess, &dpi_awareness);
|
|
if (dpi_awareness == PROCESS_PER_MONITOR_DPI_AWARE)
|
|
{
|
|
POINT pt;
|
|
UINT dpix = 0, dpiy = 0;
|
|
HRESULT hr = E_FAIL;
|
|
RECT rect;
|
|
|
|
GetWindowRect(hWnd, &rect);
|
|
// Get the DPI for the monitor, on which the center of window is displayed and set the scaling factor
|
|
pt.x = (rect.left + rect.right) / 2;
|
|
pt.y = (rect.top + rect.bottom) / 2;
|
|
hMonitor = MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST);
|
|
hr = pGDFM(hMonitor, MDT_EFFECTIVE_DPI, &dpix, &dpiy);
|
|
if (hr == S_OK)
|
|
{
|
|
scale_value = F32(dpix) / F32(USER_DEFAULT_SCREEN_DPI);
|
|
}
|
|
else
|
|
{
|
|
LL_WARNS() << "Could not determine DPI for monitor. Setting scale to default 100 %" << LL_ENDL;
|
|
scale_value = 1.0f;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LL_WARNS() << "Process is not per-monitor DPI-aware. Setting scale to default 100 %" << LL_ENDL;
|
|
scale_value = 1.0f;
|
|
}
|
|
}
|
|
FreeLibrary(hShcore);
|
|
}
|
|
else
|
|
{
|
|
LL_WARNS() << "Could not load shcore.dll library (included by <ShellScalingAPI.h> from Win 8.1 SDK). Using legacy DPI awareness API of Win XP/7" << LL_ENDL;
|
|
scale_value = F32(GetDeviceCaps(hdc, LOGPIXELSX)) / F32(USER_DEFAULT_SCREEN_DPI);
|
|
}
|
|
|
|
ReleaseDC(hWnd, hdc);
|
|
return scale_value;
|
|
}
|
|
|
|
//static
|
|
std::vector<std::string> LLWindowWin32::getDisplaysResolutionList()
|
|
{
|
|
return sMonitorInfo.getResolutionsList();
|
|
}
|
|
|
|
//static
|
|
std::vector<std::string> LLWindowWin32::getDynamicFallbackFontList()
|
|
{
|
|
// Fonts previously in getFontListSans() have moved to fonts.xml.
|
|
return std::vector<std::string>();
|
|
}
|
|
#endif // LL_WINDOWS
|
|
|
|
inline LLWindowWin32::LLWindowWin32Thread::LLWindowWin32Thread()
|
|
: LL::ThreadPool("Window Thread", 1, MAX_QUEUE_SIZE, true /*should be false, temporary workaround for SL-18721*/)
|
|
{
|
|
LL::ThreadPool::start();
|
|
}
|
|
|
|
void LLWindowWin32::LLWindowWin32Thread::close()
|
|
{
|
|
if (!mQueue->isClosed())
|
|
{
|
|
LL_WARNS() << "Closing window thread without using destroy_window_handler" << LL_ENDL;
|
|
LL::ThreadPool::close();
|
|
|
|
// Workaround for SL-18721 in case window closes too early and abruptly
|
|
LLSplashScreen::show();
|
|
LLSplashScreen::update("..."); // will be updated later
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* LogChange is to log changes in status while trying to avoid spamming the
|
|
* log with repeated messages, especially in a tight loop. It refuses to log
|
|
* a continuous run of identical messages, but logs every time the message
|
|
* changes. (It will happily spam when messages quickly bounce back and
|
|
* forth.)
|
|
*/
|
|
class LogChange
|
|
{
|
|
public:
|
|
LogChange(const std::string& tag):
|
|
mTag(tag)
|
|
{}
|
|
|
|
template <typename... Items>
|
|
void always(Items&&... items)
|
|
{
|
|
// This odd construct ensures that the stringize() call is only
|
|
// executed if DEBUG logging is enabled for the passed tag.
|
|
LL_DEBUGS(mTag.c_str());
|
|
log(LL_CONT, stringize(std::forward<Items>(items)...));
|
|
LL_ENDL;
|
|
}
|
|
|
|
template <typename... Items>
|
|
void onChange(Items&&... items)
|
|
{
|
|
LL_DEBUGS(mTag.c_str());
|
|
auto str = stringize(std::forward<Items>(items)...);
|
|
if (str != mPrev)
|
|
{
|
|
log(LL_CONT, str);
|
|
}
|
|
LL_ENDL;
|
|
}
|
|
|
|
private:
|
|
void log(std::ostream& out, const std::string& message)
|
|
{
|
|
mPrev = message;
|
|
out << message;
|
|
}
|
|
std::string mTag;
|
|
std::string mPrev;
|
|
};
|
|
|
|
void LLWindowWin32::LLWindowWin32Thread::checkDXMem()
|
|
{
|
|
if (!mGLReady || mGotGLBuffer) { return; }
|
|
|
|
IDXGIFactory4* p_factory = nullptr;
|
|
|
|
HRESULT res = CreateDXGIFactory1(__uuidof(IDXGIFactory4), (void**)&p_factory);
|
|
|
|
if (FAILED(res))
|
|
{
|
|
LL_WARNS() << "CreateDXGIFactory1 failed: 0x" << std::hex << res << LL_ENDL;
|
|
}
|
|
else
|
|
{
|
|
IDXGIAdapter3* p_dxgi_adapter = nullptr;
|
|
UINT graphics_adapter_index = 0;
|
|
while (true)
|
|
{
|
|
res = p_factory->EnumAdapters(graphics_adapter_index, reinterpret_cast<IDXGIAdapter**>(&p_dxgi_adapter));
|
|
if (FAILED(res))
|
|
{
|
|
if (graphics_adapter_index == 0)
|
|
{
|
|
LL_WARNS() << "EnumAdapters failed: 0x" << std::hex << res << LL_ENDL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (graphics_adapter_index == 0) // Should it check largest one isntead of first?
|
|
{
|
|
DXGI_QUERY_VIDEO_MEMORY_INFO info;
|
|
p_dxgi_adapter->QueryVideoMemoryInfo(0, DXGI_MEMORY_SEGMENT_GROUP_LOCAL, &info);
|
|
|
|
// Alternatively use GetDesc from below to get adapter's memory
|
|
UINT64 budget_mb = info.Budget / (1024 * 1024);
|
|
if (gGLManager.mVRAM < (S32)budget_mb)
|
|
{
|
|
gGLManager.mVRAM = (S32)budget_mb;
|
|
LL_INFOS("RenderInit") << "New VRAM Budget (DX9): " << gGLManager.mVRAM << " MB" << LL_ENDL;
|
|
}
|
|
else
|
|
{
|
|
LL_INFOS("RenderInit") << "VRAM Budget (DX9): " << budget_mb
|
|
<< " MB, current (WMI): " << gGLManager.mVRAM << " MB" << LL_ENDL;
|
|
}
|
|
}
|
|
|
|
DXGI_ADAPTER_DESC desc;
|
|
p_dxgi_adapter->GetDesc(&desc);
|
|
std::wstring description_w((wchar_t*)desc.Description);
|
|
std::string description = ll_convert_wide_to_string(description_w);
|
|
LL_INFOS("Window") << "Graphics adapter index: " << graphics_adapter_index << ", "
|
|
<< "Description: " << description << ", "
|
|
<< "DeviceId: " << desc.DeviceId << ", "
|
|
<< "SubSysId: " << desc.SubSysId << ", "
|
|
<< "AdapterLuid: " << desc.AdapterLuid.HighPart << "_" << desc.AdapterLuid.LowPart << ", "
|
|
<< "DedicatedVideoMemory: " << desc.DedicatedVideoMemory / 1024 / 1024 << ", "
|
|
<< "DedicatedSystemMemory: " << desc.DedicatedSystemMemory / 1024 / 1024 << ", "
|
|
<< "SharedSystemMemory: " << desc.SharedSystemMemory / 1024 / 1024 << LL_ENDL;
|
|
}
|
|
|
|
if (p_dxgi_adapter)
|
|
{
|
|
p_dxgi_adapter->Release();
|
|
p_dxgi_adapter = NULL;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
|
|
graphics_adapter_index++;
|
|
}
|
|
}
|
|
|
|
if (p_factory)
|
|
{
|
|
p_factory->Release();
|
|
}
|
|
|
|
mGotGLBuffer = true;
|
|
}
|
|
|
|
void LLWindowWin32::LLWindowWin32Thread::run()
|
|
{
|
|
sWindowThreadId = std::this_thread::get_id();
|
|
LogChange logger("Window");
|
|
|
|
//as good a place as any to up the MM timer resolution (see ms_sleep)
|
|
//attempt to set timer resolution to 1ms
|
|
TIMECAPS tc;
|
|
if (timeGetDevCaps(&tc, sizeof(TIMECAPS)) == TIMERR_NOERROR)
|
|
{
|
|
timeBeginPeriod(llclamp((U32) 1, tc.wPeriodMin, tc.wPeriodMax));
|
|
}
|
|
|
|
while (! getQueue().done())
|
|
{
|
|
LL_PROFILE_ZONE_SCOPED_CATEGORY_WIN32;
|
|
|
|
// Check memory budget using DirectX
|
|
checkDXMem();
|
|
|
|
if (mWindowHandleThrd != 0)
|
|
{
|
|
MSG msg;
|
|
BOOL status;
|
|
if (mhDCThrd == 0)
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("w32t - PeekMessage");
|
|
logger.onChange("PeekMessage(", std::hex, mWindowHandleThrd, ")");
|
|
status = PeekMessage(&msg, mWindowHandleThrd, 0, 0, PM_REMOVE);
|
|
}
|
|
else
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("w32t - GetMessage");
|
|
logger.always("GetMessage(", std::hex, mWindowHandleThrd, ")");
|
|
status = GetMessage(&msg, NULL, 0, 0);
|
|
}
|
|
if (status > 0)
|
|
{
|
|
logger.always("got MSG (", std::hex, msg.hwnd, ", ", msg.message,
|
|
", ", msg.wParam, ")");
|
|
TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
|
|
mMessageQueue.pushFront(msg);
|
|
}
|
|
}
|
|
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("w32t - Function Queue");
|
|
logger.onChange("runPending()");
|
|
//process any pending functions
|
|
getQueue().runPending();
|
|
}
|
|
|
|
#if 0
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("w32t - Sleep");
|
|
logger.always("sleep(1)");
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void LLWindowWin32::LLWindowWin32Thread::wakeAndDestroy()
|
|
{
|
|
if (mQueue->isClosed())
|
|
{
|
|
LL_WARNS() << "Tried to close Queue. Win32 thread Queue already closed." << LL_ENDL;
|
|
return;
|
|
}
|
|
|
|
// Make sure we don't leave a blank toolbar button.
|
|
// Also hiding window now prevents user from suspending it
|
|
// via some action (like dragging it around)
|
|
ShowWindow(mWindowHandleThrd, SW_HIDE);
|
|
|
|
// Schedule destruction
|
|
HWND old_handle = mWindowHandleThrd;
|
|
post([this]()
|
|
{
|
|
if (IsWindow(mWindowHandleThrd))
|
|
{
|
|
if (mhDCThrd)
|
|
{
|
|
if (!ReleaseDC(mWindowHandleThrd, mhDCThrd))
|
|
{
|
|
LL_WARNS("Window") << "Release of ghDC failed!" << LL_ENDL;
|
|
}
|
|
mhDCThrd = NULL;
|
|
}
|
|
|
|
// This causes WM_DESTROY to be sent *immediately*
|
|
if (!destroy_window_handler(mWindowHandleThrd))
|
|
{
|
|
LL_WARNS("Window") << "Failed to destroy Window! " << std::hex << GetLastError() << LL_ENDL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Something killed the window while we were busy destroying gl or handle somehow got broken
|
|
LL_WARNS("Window") << "Failed to destroy Window, invalid handle!" << LL_ENDL;
|
|
}
|
|
mWindowHandleThrd = NULL;
|
|
mhDCThrd = NULL;
|
|
mGLReady = false;
|
|
});
|
|
|
|
LL_DEBUGS("Window") << "Closing window's pool queue" << LL_ENDL;
|
|
mQueue->close();
|
|
|
|
// Post a nonsense user message to wake up the thread in
|
|
// case it is waiting for a getMessage()
|
|
if (old_handle)
|
|
{
|
|
WPARAM wparam{ 0xB0B0 };
|
|
LL_DEBUGS("Window") << "PostMessage(" << std::hex << old_handle
|
|
<< ", " << WM_DUMMY_
|
|
<< ", " << wparam << ")" << std::dec << LL_ENDL;
|
|
PostMessage(old_handle, WM_DUMMY_, wparam, 0x1337);
|
|
}
|
|
|
|
// There are cases where window will refuse to close,
|
|
// can't wait forever on join, check state instead
|
|
LLTimer timeout;
|
|
timeout.setTimerExpirySec(2.0);
|
|
while (!getQueue().done() && !timeout.hasExpired() && mWindowHandleThrd)
|
|
{
|
|
ms_sleep(100);
|
|
}
|
|
|
|
if (getQueue().done() || mWindowHandleThrd == NULL)
|
|
{
|
|
// Window is closed, started closing or is cleaning up
|
|
// now wait for our single thread to die.
|
|
if (mWindowHandleThrd)
|
|
{
|
|
LL_INFOS("Window") << "Window is closing, waiting on pool's thread to join, time since post: " << timeout.getElapsedSeconds() << "s" << LL_ENDL;
|
|
}
|
|
else
|
|
{
|
|
LL_DEBUGS("Window") << "Waiting on pool's thread, time since post: " << timeout.getElapsedSeconds() << "s" << LL_ENDL;
|
|
}
|
|
for (auto& pair : mThreads)
|
|
{
|
|
pair.second.join();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Something suspended window thread, can't afford to wait forever
|
|
// so kill thread instead
|
|
// Ex: This can happen if user starts dragging window arround (if it
|
|
// was visible) or a modal notification pops up
|
|
LL_WARNS("Window") << "Window is frozen, couldn't perform clean exit" << LL_ENDL;
|
|
|
|
for (auto& pair : mThreads)
|
|
{
|
|
// very unsafe
|
|
TerminateThread(pair.second.native_handle(), 0);
|
|
pair.second.detach();
|
|
}
|
|
}
|
|
LL_DEBUGS("Window") << "thread pool shutdown complete" << LL_ENDL;
|
|
}
|
|
|
|
void LLWindowWin32::post(const std::function<void()>& func)
|
|
{
|
|
mFunctionQueue.pushFront(func);
|
|
}
|
|
|
|
void LLWindowWin32::postMouseButtonEvent(const std::function<void()>& func)
|
|
{
|
|
mMouseQueue.pushFront(func);
|
|
}
|
|
|
|
void LLWindowWin32::kickWindowThread(HWND windowHandle)
|
|
{
|
|
if (! windowHandle)
|
|
windowHandle = mWindowHandle;
|
|
if (windowHandle)
|
|
{
|
|
// post a nonsense user message to wake up the Window Thread in
|
|
// case any functions are pending and no windows events came
|
|
// through this frame
|
|
WPARAM wparam{ 0xB0B0 };
|
|
LL_DEBUGS("Window") << "PostMessage(" << std::hex << windowHandle
|
|
<< ", " << WM_DUMMY_
|
|
<< ", " << wparam << ")" << std::dec << LL_ENDL;
|
|
PostMessage(windowHandle, WM_DUMMY_, wparam, 0x1337);
|
|
}
|
|
}
|
|
|
|
void LLWindowWin32::updateWindowRect()
|
|
{
|
|
LL_PROFILE_ZONE_SCOPED_CATEGORY_WIN32;
|
|
//called from window thread
|
|
RECT rect;
|
|
RECT client_rect;
|
|
|
|
if (GetWindowRect(mWindowHandle, &rect) &&
|
|
GetClientRect(mWindowHandle, &client_rect))
|
|
{
|
|
post([=]
|
|
{
|
|
mRect = rect;
|
|
mClientRect = client_rect;
|
|
});
|
|
}
|
|
}
|