/* SPDX-License-Identifier: GPL-2.0-or-later * Copyright 2001-2002 NaN Holding BV. All rights reserved. */ /** \file * \ingroup GHOST */ #include "GHOST_WindowWin32.h" #include "GHOST_ContextD3D.h" #include "GHOST_ContextNone.h" #include "GHOST_DropTargetWin32.h" #include "GHOST_SystemWin32.h" #include "GHOST_WindowManager.h" #include "utf_winfunc.h" #include "utfconv.h" #include "GHOST_ContextWGL.h" #include #include #include #include #include #include #ifndef GET_POINTERID_WPARAM # define GET_POINTERID_WPARAM(wParam) (LOWORD(wParam)) #endif /* GET_POINTERID_WPARAM */ const wchar_t *GHOST_WindowWin32::s_windowClassName = L"GHOST_WindowClass"; const int GHOST_WindowWin32::s_maxTitleLength = 128; /* force NVidia Optimus to used dedicated graphics */ extern "C" { __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; } GHOST_WindowWin32::GHOST_WindowWin32(GHOST_SystemWin32 *system, const char *title, int32_t left, int32_t top, uint32_t width, uint32_t height, GHOST_TWindowState state, GHOST_TDrawingContextType type, bool wantStereoVisual, bool alphaBackground, GHOST_WindowWin32 *parentwindow, bool is_debug, bool dialog) : GHOST_Window(width, height, state, wantStereoVisual, false), m_mousePresent(false), m_inLiveResize(false), m_system(system), m_dropTarget(NULL), m_hWnd(0), m_hDC(0), m_isDialog(dialog), m_hasMouseCaptured(false), m_hasGrabMouse(false), m_nPressedButtons(0), m_customCursor(0), m_wantAlphaBackground(alphaBackground), m_Bar(NULL), m_wintab(NULL), m_lastPointerTabletData(GHOST_TABLET_DATA_NONE), m_normal_state(GHOST_kWindowStateNormal), m_user32(::LoadLibrary("user32.dll")), m_parentWindowHwnd(parentwindow ? parentwindow->m_hWnd : HWND_DESKTOP), m_directManipulationHelper(NULL), m_debug_context(is_debug) { DWORD style = parentwindow ? WS_POPUPWINDOW | WS_CAPTION | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_SIZEBOX : WS_OVERLAPPEDWINDOW; if (state == GHOST_kWindowStateFullScreen) { style |= WS_MAXIMIZE; } /* Forces owned windows onto taskbar and allows minimization. */ DWORD extended_style = parentwindow ? WS_EX_APPWINDOW : 0; if (dialog) { /* When we are ready to make windows of this type: * style = WS_POPUPWINDOW | WS_CAPTION * extended_style = WS_EX_DLGMODALFRAME | WS_EX_TOPMOST */ } RECT win_rect = {left, top, long(left + width), long(top + height)}; adjustWindowRectForClosestMonitor(&win_rect, style, extended_style); wchar_t *title_16 = alloc_utf16_from_8((char *)title, 0); m_hWnd = ::CreateWindowExW(extended_style, /* window extended style */ s_windowClassName, /* pointer to registered class name */ title_16, /* pointer to window name */ style, /* window style */ win_rect.left, /* horizontal position of window */ win_rect.top, /* vertical position of window */ win_rect.right - win_rect.left, /* window width */ win_rect.bottom - win_rect.top, /* window height */ m_parentWindowHwnd, /* handle to parent or owner window */ 0, /* handle to menu or child-window identifier */ ::GetModuleHandle(0), /* handle to application instance */ 0); /* pointer to window-creation data */ free(title_16); if (m_hWnd == NULL) { return; } /* Store the device context. */ m_hDC = ::GetDC(m_hWnd); if (!setDrawingContextType(type)) { const char *title = "Blender - Unsupported Graphics Card Configuration"; const char *text = "A graphics card and driver with support for OpenGL 3.3 or higher is " "required.\n\nInstalling the latest driver for your graphics card might resolve the " "issue."; if (GetSystemMetrics(SM_CMONITORS) > 1) { text = "A graphics card and driver with support for OpenGL 3.3 or higher is " "required.\n\nPlugging all monitors into your primary graphics card might resolve " "this issue. Installing the latest driver for your graphics card could also help."; } MessageBox(m_hWnd, text, title, MB_OK | MB_ICONERROR); ::ReleaseDC(m_hWnd, m_hDC); ::DestroyWindow(m_hWnd); m_hWnd = NULL; if (!parentwindow) { exit(0); } return; } RegisterTouchWindow(m_hWnd, 0); /* Register as drop-target. #OleInitialize(0) required first, done in GHOST_SystemWin32. */ m_dropTarget = new GHOST_DropTargetWin32(this, m_system); ::RegisterDragDrop(m_hWnd, m_dropTarget); /* Store a pointer to this class in the window structure. */ ::SetWindowLongPtr(m_hWnd, GWLP_USERDATA, (LONG_PTR)this); if (!m_system->m_windowFocus) { /* If we don't want focus then lower to bottom. */ ::SetWindowPos(m_hWnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); } if (parentwindow) { /* Release any parent capture to allow immediate interaction (T90110). */ ::ReleaseCapture(); parentwindow->lostMouseCapture(); } /* Show the window. */ int nCmdShow; switch (state) { case GHOST_kWindowStateMaximized: nCmdShow = SW_SHOWMAXIMIZED; break; case GHOST_kWindowStateMinimized: nCmdShow = (m_system->m_windowFocus) ? SW_SHOWMINIMIZED : SW_SHOWMINNOACTIVE; break; case GHOST_kWindowStateNormal: default: nCmdShow = (m_system->m_windowFocus) ? SW_SHOWNORMAL : SW_SHOWNOACTIVATE; break; } ThemeRefresh(); ::ShowWindow(m_hWnd, nCmdShow); #ifdef WIN32_COMPOSITING if (alphaBackground && parentwindowhwnd == 0) { HRESULT hr = S_OK; /* Create and populate the Blur Behind structure. */ DWM_BLURBEHIND bb = {0}; /* Enable Blur Behind and apply to the entire client area. */ bb.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION; bb.fEnable = true; bb.hRgnBlur = CreateRectRgn(0, 0, -1, -1); /* Apply Blur Behind. */ hr = DwmEnableBlurBehindWindow(m_hWnd, &bb); DeleteObject(bb.hRgnBlur); } #endif /* Force an initial paint of the window. */ ::UpdateWindow(m_hWnd); /* Initialize WINTAB. */ if (system->getTabletAPI() != GHOST_kTabletWinPointer) { loadWintab(GHOST_kWindowStateMinimized != state); } /* Allow the showing of a progress bar on the taskbar. */ CoCreateInstance( CLSID_TaskbarList, NULL, CLSCTX_INPROC_SERVER, IID_ITaskbarList3, (LPVOID *)&m_Bar); /* Initialize Direct Manipulation. */ m_directManipulationHelper = GHOST_DirectManipulationHelper::create(m_hWnd, getDPIHint()); } void GHOST_WindowWin32::updateDirectManipulation() { if (!m_directManipulationHelper) { return; } m_directManipulationHelper->update(); } void GHOST_WindowWin32::onPointerHitTest(WPARAM wParam) { /* Only #DM_POINTERHITTEST can be the first message of input sequence of touch-pad input. */ if (!m_directManipulationHelper) { return; } UINT32 pointerId = GET_POINTERID_WPARAM(wParam); POINTER_INPUT_TYPE pointerType; if (GetPointerType(pointerId, &pointerType) && pointerType == PT_TOUCHPAD) { m_directManipulationHelper->onPointerHitTest(pointerId); } } GHOST_TTrackpadInfo GHOST_WindowWin32::getTrackpadInfo() { if (!m_directManipulationHelper) { return {0, 0, 0}; } return m_directManipulationHelper->getTrackpadInfo(); } GHOST_WindowWin32::~GHOST_WindowWin32() { if (m_Bar) { m_Bar->SetProgressState(m_hWnd, TBPF_NOPROGRESS); m_Bar->Release(); m_Bar = NULL; } closeWintab(); if (m_user32) { FreeLibrary(m_user32); m_user32 = NULL; } if (m_customCursor) { DestroyCursor(m_customCursor); m_customCursor = NULL; } if (m_hWnd != NULL && m_hDC != NULL && releaseNativeHandles()) { ::ReleaseDC(m_hWnd, m_hDC); m_hDC = NULL; } if (m_hWnd) { /* If this window is referenced by others as parent, clear that relation or windows will free * the handle while we still reference it. */ for (GHOST_IWindow *iter_win : m_system->getWindowManager()->getWindows()) { GHOST_WindowWin32 *iter_winwin = (GHOST_WindowWin32 *)iter_win; if (iter_winwin->m_parentWindowHwnd == m_hWnd) { ::SetWindowLongPtr(iter_winwin->m_hWnd, GWLP_HWNDPARENT, NULL); iter_winwin->m_parentWindowHwnd = 0; } } if (m_dropTarget) { /* Disable DragDrop. */ RevokeDragDrop(m_hWnd); /* Release our reference of the DropTarget and it will delete itself eventually. */ m_dropTarget->Release(); m_dropTarget = NULL; } ::SetWindowLongPtr(m_hWnd, GWLP_USERDATA, NULL); ::DestroyWindow(m_hWnd); m_hWnd = 0; } delete m_directManipulationHelper; m_directManipulationHelper = NULL; } void GHOST_WindowWin32::adjustWindowRectForClosestMonitor(LPRECT win_rect, DWORD dwStyle, DWORD dwExStyle) { /* Get Details of the closest monitor. */ HMONITOR hmonitor = MonitorFromRect(win_rect, MONITOR_DEFAULTTONEAREST); MONITORINFOEX monitor; monitor.cbSize = sizeof(MONITORINFOEX); monitor.dwFlags = 0; GetMonitorInfo(hmonitor, &monitor); /* Constrain requested size and position to fit within this monitor. */ LONG width = min(monitor.rcWork.right - monitor.rcWork.left, win_rect->right - win_rect->left); LONG height = min(monitor.rcWork.bottom - monitor.rcWork.top, win_rect->bottom - win_rect->top); win_rect->left = min(max(monitor.rcWork.left, win_rect->left), monitor.rcWork.right - width); win_rect->right = win_rect->left + width; win_rect->top = min(max(monitor.rcWork.top, win_rect->top), monitor.rcWork.bottom - height); win_rect->bottom = win_rect->top + height; /* With Windows 10 and newer we can adjust for chrome that differs with DPI and scale. */ GHOST_WIN32_AdjustWindowRectExForDpi fpAdjustWindowRectExForDpi = nullptr; if (m_user32) { fpAdjustWindowRectExForDpi = (GHOST_WIN32_AdjustWindowRectExForDpi)::GetProcAddress( m_user32, "AdjustWindowRectExForDpi"); } /* Adjust to allow for caption, borders, shadows, scaling, etc. Resulting values can be * correctly outside of monitor bounds. NOTE: You cannot specify #WS_OVERLAPPED when calling. */ if (fpAdjustWindowRectExForDpi) { UINT dpiX, dpiY; GetDpiForMonitor(hmonitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY); fpAdjustWindowRectExForDpi(win_rect, dwStyle & ~WS_OVERLAPPED, FALSE, dwExStyle, dpiX); } else { AdjustWindowRectEx(win_rect, dwStyle & ~WS_OVERLAPPED, FALSE, dwExStyle); } /* But never allow a top position that can hide part of the title bar. */ win_rect->top = max(monitor.rcWork.top, win_rect->top); } bool GHOST_WindowWin32::getValid() const { return GHOST_Window::getValid() && m_hWnd != 0 && m_hDC != 0; } HWND GHOST_WindowWin32::getHWND() const { return m_hWnd; } void GHOST_WindowWin32::setTitle(const char *title) { wchar_t *title_16 = alloc_utf16_from_8((char *)title, 0); ::SetWindowTextW(m_hWnd, (wchar_t *)title_16); free(title_16); } std::string GHOST_WindowWin32::getTitle() const { std::wstring wtitle(::GetWindowTextLengthW(m_hWnd) + 1, L'\0'); ::GetWindowTextW(m_hWnd, &wtitle[0], wtitle.capacity()); std::string title(count_utf_8_from_16(wtitle.c_str()) + 1, '\0'); conv_utf_16_to_8(wtitle.c_str(), &title[0], title.capacity()); return title; } void GHOST_WindowWin32::getWindowBounds(GHOST_Rect &bounds) const { RECT rect; ::GetWindowRect(m_hWnd, &rect); bounds.m_b = rect.bottom; bounds.m_l = rect.left; bounds.m_r = rect.right; bounds.m_t = rect.top; } void GHOST_WindowWin32::getClientBounds(GHOST_Rect &bounds) const { RECT rect; POINT coord; if (!IsIconic(m_hWnd)) { ::GetClientRect(m_hWnd, &rect); coord.x = rect.left; coord.y = rect.top; ::ClientToScreen(m_hWnd, &coord); bounds.m_l = coord.x; bounds.m_t = coord.y; coord.x = rect.right; coord.y = rect.bottom; ::ClientToScreen(m_hWnd, &coord); bounds.m_r = coord.x; bounds.m_b = coord.y; } else { bounds.m_b = 0; bounds.m_l = 0; bounds.m_r = 0; bounds.m_t = 0; } } GHOST_TSuccess GHOST_WindowWin32::setClientWidth(uint32_t width) { GHOST_TSuccess success; GHOST_Rect cBnds, wBnds; getClientBounds(cBnds); if (cBnds.getWidth() != (int32_t)width) { getWindowBounds(wBnds); int cx = wBnds.getWidth() + width - cBnds.getWidth(); int cy = wBnds.getHeight(); success = ::SetWindowPos(m_hWnd, HWND_TOP, 0, 0, cx, cy, SWP_NOMOVE | SWP_NOZORDER) ? GHOST_kSuccess : GHOST_kFailure; } else { success = GHOST_kSuccess; } return success; } GHOST_TSuccess GHOST_WindowWin32::setClientHeight(uint32_t height) { GHOST_TSuccess success; GHOST_Rect cBnds, wBnds; getClientBounds(cBnds); if (cBnds.getHeight() != (int32_t)height) { getWindowBounds(wBnds); int cx = wBnds.getWidth(); int cy = wBnds.getHeight() + height - cBnds.getHeight(); success = ::SetWindowPos(m_hWnd, HWND_TOP, 0, 0, cx, cy, SWP_NOMOVE | SWP_NOZORDER) ? GHOST_kSuccess : GHOST_kFailure; } else { success = GHOST_kSuccess; } return success; } GHOST_TSuccess GHOST_WindowWin32::setClientSize(uint32_t width, uint32_t height) { GHOST_TSuccess success; GHOST_Rect cBnds, wBnds; getClientBounds(cBnds); if ((cBnds.getWidth() != (int32_t)width) || (cBnds.getHeight() != (int32_t)height)) { getWindowBounds(wBnds); int cx = wBnds.getWidth() + width - cBnds.getWidth(); int cy = wBnds.getHeight() + height - cBnds.getHeight(); success = ::SetWindowPos(m_hWnd, HWND_TOP, 0, 0, cx, cy, SWP_NOMOVE | SWP_NOZORDER) ? GHOST_kSuccess : GHOST_kFailure; } else { success = GHOST_kSuccess; } return success; } GHOST_TWindowState GHOST_WindowWin32::getState() const { if (::IsIconic(m_hWnd)) { return GHOST_kWindowStateMinimized; } else if (::IsZoomed(m_hWnd)) { LONG_PTR result = ::GetWindowLongPtr(m_hWnd, GWL_STYLE); return (result & WS_CAPTION) ? GHOST_kWindowStateMaximized : GHOST_kWindowStateFullScreen; } return GHOST_kWindowStateNormal; } void GHOST_WindowWin32::screenToClient(int32_t inX, int32_t inY, int32_t &outX, int32_t &outY) const { POINT point = {inX, inY}; ::ScreenToClient(m_hWnd, &point); outX = point.x; outY = point.y; } void GHOST_WindowWin32::clientToScreen(int32_t inX, int32_t inY, int32_t &outX, int32_t &outY) const { POINT point = {inX, inY}; ::ClientToScreen(m_hWnd, &point); outX = point.x; outY = point.y; } GHOST_TSuccess GHOST_WindowWin32::setState(GHOST_TWindowState state) { GHOST_TWindowState curstate = getState(); LONG_PTR style = GetWindowLongPtr(m_hWnd, GWL_STYLE) | WS_CAPTION; WINDOWPLACEMENT wp; wp.length = sizeof(WINDOWPLACEMENT); ::GetWindowPlacement(m_hWnd, &wp); switch (state) { case GHOST_kWindowStateMinimized: wp.showCmd = SW_MINIMIZE; break; case GHOST_kWindowStateMaximized: wp.showCmd = SW_SHOWMAXIMIZED; break; case GHOST_kWindowStateFullScreen: if (curstate != state && curstate != GHOST_kWindowStateMinimized) { m_normal_state = curstate; } wp.showCmd = SW_SHOWMAXIMIZED; wp.ptMaxPosition.x = 0; wp.ptMaxPosition.y = 0; style &= ~(WS_CAPTION | WS_MAXIMIZE); break; case GHOST_kWindowStateNormal: default: if (curstate == GHOST_kWindowStateFullScreen && m_normal_state == GHOST_kWindowStateMaximized) { wp.showCmd = SW_SHOWMAXIMIZED; m_normal_state = GHOST_kWindowStateNormal; } else { wp.showCmd = SW_SHOWNORMAL; } break; } ::SetWindowLongPtr(m_hWnd, GWL_STYLE, style); /* #SetWindowLongPtr Docs: * Frame changes not visible until #SetWindowPos with #SWP_FRAMECHANGED. */ ::SetWindowPos(m_hWnd, 0, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); return ::SetWindowPlacement(m_hWnd, &wp) == TRUE ? GHOST_kSuccess : GHOST_kFailure; } GHOST_TSuccess GHOST_WindowWin32::setOrder(GHOST_TWindowOrder order) { HWND hWndInsertAfter, hWndToRaise; if (order == GHOST_kWindowOrderBottom) { hWndInsertAfter = HWND_BOTTOM; hWndToRaise = ::GetWindow(m_hWnd, GW_HWNDNEXT); /* the window to raise */ } else { if (getState() == GHOST_kWindowStateMinimized) { setState(GHOST_kWindowStateNormal); } hWndInsertAfter = HWND_TOP; hWndToRaise = NULL; } if (::SetWindowPos(m_hWnd, hWndInsertAfter, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE) == FALSE) { return GHOST_kFailure; } if (hWndToRaise && ::SetWindowPos(hWndToRaise, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE) == FALSE) { return GHOST_kFailure; } return GHOST_kSuccess; } GHOST_TSuccess GHOST_WindowWin32::invalidate() { GHOST_TSuccess success; if (m_hWnd) { success = ::InvalidateRect(m_hWnd, 0, FALSE) != 0 ? GHOST_kSuccess : GHOST_kFailure; } else { success = GHOST_kFailure; } return success; } GHOST_Context *GHOST_WindowWin32::newDrawingContext(GHOST_TDrawingContextType type) { if (type == GHOST_kDrawingContextTypeOpenGL) { GHOST_Context *context; /* - AMD and Intel give us exactly this version * - NVIDIA gives at least this version <-- desired behavior * So we ask for 4.5, 4.4 ... 3.3 in descending order * to get the best version on the user's system. */ for (int minor = 5; minor >= 0; --minor) { context = new GHOST_ContextWGL(m_wantStereoVisual, m_wantAlphaBackground, m_hWnd, m_hDC, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, 4, minor, (m_debug_context ? WGL_CONTEXT_DEBUG_BIT_ARB : 0), GHOST_OPENGL_WGL_RESET_NOTIFICATION_STRATEGY); if (context->initializeDrawingContext()) { return context; } else { delete context; } } context = new GHOST_ContextWGL(m_wantStereoVisual, m_wantAlphaBackground, m_hWnd, m_hDC, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, 3, 3, (m_debug_context ? WGL_CONTEXT_DEBUG_BIT_ARB : 0), GHOST_OPENGL_WGL_RESET_NOTIFICATION_STRATEGY); if (context && !context->initializeDrawingContext()) { delete context; context = nullptr; } return context; } else if (type == GHOST_kDrawingContextTypeD3D) { GHOST_Context *context; context = new GHOST_ContextD3D(false, m_hWnd); if (!context->initializeDrawingContext()) { delete context; context = nullptr; } return context; } return NULL; } void GHOST_WindowWin32::lostMouseCapture() { if (m_hasMouseCaptured) { m_hasGrabMouse = false; m_nPressedButtons = 0; m_hasMouseCaptured = false; } } bool GHOST_WindowWin32::isDialog() const { return m_isDialog; } void GHOST_WindowWin32::updateMouseCapture(GHOST_MouseCaptureEventWin32 event) { switch (event) { case MousePressed: m_nPressedButtons++; break; case MouseReleased: if (m_nPressedButtons) m_nPressedButtons--; break; case OperatorGrab: m_hasGrabMouse = true; break; case OperatorUngrab: m_hasGrabMouse = false; break; } if (!m_nPressedButtons && !m_hasGrabMouse && m_hasMouseCaptured) { ::ReleaseCapture(); m_hasMouseCaptured = false; } else if ((m_nPressedButtons || m_hasGrabMouse) && !m_hasMouseCaptured) { ::SetCapture(m_hWnd); m_hasMouseCaptured = true; } } HCURSOR GHOST_WindowWin32::getStandardCursor(GHOST_TStandardCursor shape) const { /* Convert GHOST cursor to Windows OEM cursor. */ HANDLE cursor = NULL; HMODULE module = ::GetModuleHandle(0); uint32_t flags = LR_SHARED | LR_DEFAULTSIZE; int cx = 0, cy = 0; switch (shape) { case GHOST_kStandardCursorCustom: if (m_customCursor) { return m_customCursor; } else { return NULL; } case GHOST_kStandardCursorRightArrow: cursor = ::LoadImage(module, "arrowright_cursor", IMAGE_CURSOR, cx, cy, flags); break; case GHOST_kStandardCursorLeftArrow: cursor = ::LoadImage(module, "arrowleft_cursor", IMAGE_CURSOR, cx, cy, flags); break; case GHOST_kStandardCursorUpArrow: cursor = ::LoadImage(module, "arrowup_cursor", IMAGE_CURSOR, cx, cy, flags); break; case GHOST_kStandardCursorDownArrow: cursor = ::LoadImage(module, "arrowdown_cursor", IMAGE_CURSOR, cx, cy, flags); break; case GHOST_kStandardCursorVerticalSplit: cursor = ::LoadImage(module, "splitv_cursor", IMAGE_CURSOR, cx, cy, flags); break; case GHOST_kStandardCursorHorizontalSplit: cursor = ::LoadImage(module, "splith_cursor", IMAGE_CURSOR, cx, cy, flags); break; case GHOST_kStandardCursorKnife: cursor = ::LoadImage(module, "knife_cursor", IMAGE_CURSOR, cx, cy, flags); break; case GHOST_kStandardCursorEyedropper: cursor = ::LoadImage(module, "eyedropper_cursor", IMAGE_CURSOR, cx, cy, flags); break; case GHOST_kStandardCursorZoomIn: cursor = ::LoadImage(module, "zoomin_cursor", IMAGE_CURSOR, cx, cy, flags); break; case GHOST_kStandardCursorZoomOut: cursor = ::LoadImage(module, "zoomout_cursor", IMAGE_CURSOR, cx, cy, flags); break; case GHOST_kStandardCursorMove: cursor = ::LoadImage(module, "handopen_cursor", IMAGE_CURSOR, cx, cy, flags); break; case GHOST_kStandardCursorNSEWScroll: cursor = ::LoadImage(module, "scrollnsew_cursor", IMAGE_CURSOR, cx, cy, flags); break; case GHOST_kStandardCursorNSScroll: cursor = ::LoadImage(module, "scrollns_cursor", IMAGE_CURSOR, cx, cy, flags); break; case GHOST_kStandardCursorEWScroll: cursor = ::LoadImage(module, "scrollew_cursor", IMAGE_CURSOR, cx, cy, flags); break; case GHOST_kStandardCursorHelp: cursor = ::LoadImage(NULL, IDC_HELP, IMAGE_CURSOR, cx, cy, flags); break; /* Arrow and question mark */ case GHOST_kStandardCursorWait: cursor = ::LoadImage(NULL, IDC_WAIT, IMAGE_CURSOR, cx, cy, flags); break; /* Hourglass */ case GHOST_kStandardCursorText: cursor = ::LoadImage(NULL, IDC_IBEAM, IMAGE_CURSOR, cx, cy, flags); break; /* I-beam */ case GHOST_kStandardCursorCrosshair: cursor = ::LoadImage(module, "cross_cursor", IMAGE_CURSOR, cx, cy, flags); break; /* Standard Cross */ case GHOST_kStandardCursorCrosshairA: cursor = ::LoadImage(module, "crossA_cursor", IMAGE_CURSOR, cx, cy, flags); break; /* Crosshair A */ case GHOST_kStandardCursorCrosshairB: cursor = ::LoadImage(module, "crossB_cursor", IMAGE_CURSOR, cx, cy, flags); break; /* Diagonal Crosshair B */ case GHOST_kStandardCursorCrosshairC: cursor = ::LoadImage(module, "crossC_cursor", IMAGE_CURSOR, cx, cy, flags); break; /* Minimal Crosshair C */ case GHOST_kStandardCursorBottomSide: case GHOST_kStandardCursorUpDown: cursor = ::LoadImage(module, "movens_cursor", IMAGE_CURSOR, cx, cy, flags); break; /* Double-pointed arrow pointing north and south */ case GHOST_kStandardCursorLeftSide: case GHOST_kStandardCursorLeftRight: cursor = ::LoadImage(module, "moveew_cursor", IMAGE_CURSOR, cx, cy, flags); break; /* Double-pointed arrow pointing west and east */ case GHOST_kStandardCursorTopSide: cursor = ::LoadImage(NULL, IDC_UPARROW, IMAGE_CURSOR, cx, cy, flags); break; /* Vertical arrow */ case GHOST_kStandardCursorTopLeftCorner: cursor = ::LoadImage(NULL, IDC_SIZENWSE, IMAGE_CURSOR, cx, cy, flags); break; case GHOST_kStandardCursorTopRightCorner: cursor = ::LoadImage(NULL, IDC_SIZENESW, IMAGE_CURSOR, cx, cy, flags); break; case GHOST_kStandardCursorBottomRightCorner: cursor = ::LoadImage(NULL, IDC_SIZENWSE, IMAGE_CURSOR, cx, cy, flags); break; case GHOST_kStandardCursorBottomLeftCorner: cursor = ::LoadImage(NULL, IDC_SIZENESW, IMAGE_CURSOR, cx, cy, flags); break; case GHOST_kStandardCursorPencil: cursor = ::LoadImage(module, "pencil_cursor", IMAGE_CURSOR, cx, cy, flags); break; case GHOST_kStandardCursorEraser: cursor = ::LoadImage(module, "eraser_cursor", IMAGE_CURSOR, cx, cy, flags); break; case GHOST_kStandardCursorDestroy: case GHOST_kStandardCursorStop: cursor = ::LoadImage(module, "forbidden_cursor", IMAGE_CURSOR, cx, cy, flags); break; /* Slashed circle */ case GHOST_kStandardCursorDefault: cursor = NULL; break; default: return NULL; } if (cursor == NULL) { cursor = ::LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, cx, cy, flags); } return (HCURSOR)cursor; } void GHOST_WindowWin32::loadCursor(bool visible, GHOST_TStandardCursor shape) const { if (!visible) { while (::ShowCursor(FALSE) >= 0) ; } else { while (::ShowCursor(TRUE) < 0) ; } HCURSOR cursor = getStandardCursor(shape); if (cursor == NULL) { cursor = getStandardCursor(GHOST_kStandardCursorDefault); } ::SetCursor(cursor); } GHOST_TSuccess GHOST_WindowWin32::setWindowCursorVisibility(bool visible) { if (::GetForegroundWindow() == m_hWnd) { loadCursor(visible, getCursorShape()); } return GHOST_kSuccess; } GHOST_TSuccess GHOST_WindowWin32::setWindowCursorGrab(GHOST_TGrabCursorMode mode) { if (mode != GHOST_kGrabDisable) { if (mode != GHOST_kGrabNormal) { m_system->getCursorPosition(m_cursorGrabInitPos[0], m_cursorGrabInitPos[1]); setCursorGrabAccum(0, 0); if (mode == GHOST_kGrabHide) { setWindowCursorVisibility(false); } } updateMouseCapture(OperatorGrab); } else { if (m_cursorGrab == GHOST_kGrabHide) { m_system->setCursorPosition(m_cursorGrabInitPos[0], m_cursorGrabInitPos[1]); setWindowCursorVisibility(true); } if (m_cursorGrab != GHOST_kGrabNormal) { /* Use to generate a mouse move event, otherwise the last event * blender gets can be outside the screen causing menus not to show * properly unless the user moves the mouse. */ int32_t pos[2]; m_system->getCursorPosition(pos[0], pos[1]); m_system->setCursorPosition(pos[0], pos[1]); } /* Almost works without but important otherwise the mouse GHOST location * can be incorrect on exit. */ setCursorGrabAccum(0, 0); m_cursorGrabBounds.m_l = m_cursorGrabBounds.m_r = -1; /* disable */ updateMouseCapture(OperatorUngrab); } return GHOST_kSuccess; } GHOST_TSuccess GHOST_WindowWin32::setWindowCursorShape(GHOST_TStandardCursor cursorShape) { if (::GetForegroundWindow() == m_hWnd) { loadCursor(getCursorVisibility(), cursorShape); } return GHOST_kSuccess; } GHOST_TSuccess GHOST_WindowWin32::hasCursorShape(GHOST_TStandardCursor cursorShape) { return (getStandardCursor(cursorShape)) ? GHOST_kSuccess : GHOST_kFailure; } GHOST_TSuccess GHOST_WindowWin32::getPointerInfo( std::vector &outPointerInfo, WPARAM wParam, LPARAM lParam) { int32_t pointerId = GET_POINTERID_WPARAM(wParam); int32_t isPrimary = IS_POINTER_PRIMARY_WPARAM(wParam); GHOST_SystemWin32 *system = (GHOST_SystemWin32 *)GHOST_System::getSystem(); uint32_t outCount = 0; if (!(GetPointerPenInfoHistory(pointerId, &outCount, NULL))) { return GHOST_kFailure; } std::vector pointerPenInfo(outCount); outPointerInfo.resize(outCount); if (!(GetPointerPenInfoHistory(pointerId, &outCount, pointerPenInfo.data()))) { return GHOST_kFailure; } for (uint32_t i = 0; i < outCount; i++) { POINTER_INFO pointerApiInfo = pointerPenInfo[i].pointerInfo; /* Obtain the basic information from the event. */ outPointerInfo[i].pointerId = pointerId; outPointerInfo[i].isPrimary = isPrimary; switch (pointerApiInfo.ButtonChangeType) { case POINTER_CHANGE_FIRSTBUTTON_DOWN: case POINTER_CHANGE_FIRSTBUTTON_UP: outPointerInfo[i].buttonMask = GHOST_kButtonMaskLeft; break; case POINTER_CHANGE_SECONDBUTTON_DOWN: case POINTER_CHANGE_SECONDBUTTON_UP: outPointerInfo[i].buttonMask = GHOST_kButtonMaskRight; break; case POINTER_CHANGE_THIRDBUTTON_DOWN: case POINTER_CHANGE_THIRDBUTTON_UP: outPointerInfo[i].buttonMask = GHOST_kButtonMaskMiddle; break; case POINTER_CHANGE_FOURTHBUTTON_DOWN: case POINTER_CHANGE_FOURTHBUTTON_UP: outPointerInfo[i].buttonMask = GHOST_kButtonMaskButton4; break; case POINTER_CHANGE_FIFTHBUTTON_DOWN: case POINTER_CHANGE_FIFTHBUTTON_UP: outPointerInfo[i].buttonMask = GHOST_kButtonMaskButton5; break; default: break; } outPointerInfo[i].pixelLocation = pointerApiInfo.ptPixelLocation; outPointerInfo[i].tabletData.Active = GHOST_kTabletModeStylus; outPointerInfo[i].tabletData.Pressure = 1.0f; outPointerInfo[i].tabletData.Xtilt = 0.0f; outPointerInfo[i].tabletData.Ytilt = 0.0f; outPointerInfo[i].time = system->performanceCounterToMillis(pointerApiInfo.PerformanceCount); if (pointerPenInfo[i].penMask & PEN_MASK_PRESSURE) { outPointerInfo[i].tabletData.Pressure = pointerPenInfo[i].pressure / 1024.0f; } if (pointerPenInfo[i].penFlags & PEN_FLAG_ERASER) { outPointerInfo[i].tabletData.Active = GHOST_kTabletModeEraser; } if (pointerPenInfo[i].penMask & PEN_MASK_TILT_X) { outPointerInfo[i].tabletData.Xtilt = fmin(fabs(pointerPenInfo[i].tiltX / 90.0f), 1.0f); } if (pointerPenInfo[i].penMask & PEN_MASK_TILT_Y) { outPointerInfo[i].tabletData.Ytilt = fmin(fabs(pointerPenInfo[i].tiltY / 90.0f), 1.0f); } } if (!outPointerInfo.empty()) { m_lastPointerTabletData = outPointerInfo.back().tabletData; } return GHOST_kSuccess; } void GHOST_WindowWin32::resetPointerPenInfo() { m_lastPointerTabletData = GHOST_TABLET_DATA_NONE; } GHOST_Wintab *GHOST_WindowWin32::getWintab() const { return m_wintab; } void GHOST_WindowWin32::loadWintab(bool enable) { if (!m_wintab) { WINTAB_PRINTF("Loading Wintab for window %p\n", m_hWnd); if (m_wintab = GHOST_Wintab::loadWintab(m_hWnd)) { if (enable) { m_wintab->enable(); /* Focus Wintab if cursor is inside this window. This ensures Wintab is enabled when the * tablet is used to change the Tablet API. */ int32_t x, y; if (m_system->getCursorPosition(x, y)) { GHOST_Rect rect; getClientBounds(rect); if (rect.isInside(x, y)) { m_wintab->gainFocus(); } } } } } } void GHOST_WindowWin32::closeWintab() { WINTAB_PRINTF("Closing Wintab for window %p\n", m_hWnd); delete m_wintab; m_wintab = NULL; } bool GHOST_WindowWin32::usingTabletAPI(GHOST_TTabletAPI api) const { if (m_system->getTabletAPI() == api) { return true; } else if (m_system->getTabletAPI() == GHOST_kTabletAutomatic) { if (m_wintab && m_wintab->devicesPresent()) { return api == GHOST_kTabletWintab; } else { return api == GHOST_kTabletWinPointer; } } else { return false; } } GHOST_TabletData GHOST_WindowWin32::getTabletData() { if (usingTabletAPI(GHOST_kTabletWintab)) { return m_wintab ? m_wintab->getLastTabletData() : GHOST_TABLET_DATA_NONE; } else { return m_lastPointerTabletData; } } void GHOST_WindowWin32::ThemeRefresh() { DWORD lightMode; DWORD pcbData = sizeof(lightMode); if (RegGetValueW(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize\\", L"AppsUseLightTheme", RRF_RT_REG_DWORD, NULL, &lightMode, &pcbData) == ERROR_SUCCESS) { BOOL DarkMode = !lightMode; /* `20 == DWMWA_USE_IMMERSIVE_DARK_MODE` in Windows 11 SDK. * This value was undocumented for Windows 10 versions 2004 and later, * supported for Windows 11 Build 22000 and later. */ DwmSetWindowAttribute(this->m_hWnd, 20, &DarkMode, sizeof(DarkMode)); } } void GHOST_WindowWin32::updateDPI() { if (m_directManipulationHelper) { m_directManipulationHelper->setDPI(getDPIHint()); } } uint16_t GHOST_WindowWin32::getDPIHint() { if (m_user32) { GHOST_WIN32_GetDpiForWindow fpGetDpiForWindow = (GHOST_WIN32_GetDpiForWindow)::GetProcAddress( m_user32, "GetDpiForWindow"); if (fpGetDpiForWindow) { return fpGetDpiForWindow(this->m_hWnd); } } return USER_DEFAULT_SCREEN_DPI; } /** Reverse the bits in a uint8_t */ static uint8_t uns8ReverseBits(uint8_t ch) { ch = ((ch >> 1) & 0x55) | ((ch << 1) & 0xAA); ch = ((ch >> 2) & 0x33) | ((ch << 2) & 0xCC); ch = ((ch >> 4) & 0x0F) | ((ch << 4) & 0xF0); return ch; } #if 0 /* UNUSED */ /** Reverse the bits in a uint16_t */ static uint16_t uns16ReverseBits(uint16_t shrt) { shrt = ((shrt >> 1) & 0x5555) | ((shrt << 1) & 0xAAAA); shrt = ((shrt >> 2) & 0x3333) | ((shrt << 2) & 0xCCCC); shrt = ((shrt >> 4) & 0x0F0F) | ((shrt << 4) & 0xF0F0); shrt = ((shrt >> 8) & 0x00FF) | ((shrt << 8) & 0xFF00); return shrt; } #endif GHOST_TSuccess GHOST_WindowWin32::setWindowCustomCursorShape( uint8_t *bitmap, uint8_t *mask, int sizeX, int sizeY, int hotX, int hotY, bool canInvertColor) { uint32_t andData[32]; uint32_t xorData[32]; uint32_t fullBitRow, fullMaskRow; int x, y, cols; cols = sizeX / 8; /* Number of whole bytes per row (width of bitmap/mask). */ if (sizeX % 8) { cols++; } if (m_customCursor) { DestroyCursor(m_customCursor); m_customCursor = NULL; } memset(&andData, 0xFF, sizeof(andData)); memset(&xorData, 0, sizeof(xorData)); for (y = 0; y < sizeY; y++) { fullBitRow = 0; fullMaskRow = 0; for (x = cols - 1; x >= 0; x--) { fullBitRow <<= 8; fullMaskRow <<= 8; fullBitRow |= uns8ReverseBits(bitmap[cols * y + x]); fullMaskRow |= uns8ReverseBits(mask[cols * y + x]); } xorData[y] = fullBitRow & fullMaskRow; andData[y] = ~fullMaskRow; } m_customCursor = ::CreateCursor(::GetModuleHandle(0), hotX, hotY, 32, 32, andData, xorData); if (!m_customCursor) { return GHOST_kFailure; } if (::GetForegroundWindow() == m_hWnd) { loadCursor(getCursorVisibility(), GHOST_kStandardCursorCustom); } return GHOST_kSuccess; } GHOST_TSuccess GHOST_WindowWin32::setProgressBar(float progress) { /* #SetProgressValue sets state to #TBPF_NORMAL automatically. */ if (m_Bar && S_OK == m_Bar->SetProgressValue(m_hWnd, 10000 * progress, 10000)) { return GHOST_kSuccess; } return GHOST_kFailure; } GHOST_TSuccess GHOST_WindowWin32::endProgressBar() { if (m_Bar && S_OK == m_Bar->SetProgressState(m_hWnd, TBPF_NOPROGRESS)) { return GHOST_kSuccess; } return GHOST_kFailure; } #ifdef WITH_INPUT_IME void GHOST_WindowWin32::beginIME(int32_t x, int32_t y, int32_t w, int32_t h, bool completed) { m_imeInput.BeginIME(m_hWnd, GHOST_Rect(x, y - h, x, y), completed); } void GHOST_WindowWin32::endIME() { m_imeInput.EndIME(m_hWnd); } #endif /* WITH_INPUT_IME */