// SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2002-2006 Marcus Geelnard // SPDX-FileCopyrightText: 2006-2019 Camilla Löwy // SPDX-FileCopyrightText: 2022 The Ebitengine Authors package goglfw import ( "errors" "fmt" "math" "runtime" "unsafe" "golang.org/x/sys/windows" "github.com/hajimehoshi/ebiten/v2/internal/microsoftgdk" "github.com/hajimehoshi/ebiten/v2/internal/winver" ) func (w *Window) getWindowStyle() uint32 { var style uint32 = _WS_CLIPSIBLINGS | _WS_CLIPCHILDREN if w.monitor != nil { style |= _WS_POPUP } else { style |= _WS_SYSMENU | _WS_MINIMIZEBOX if w.decorated { style |= _WS_CAPTION if w.resizable { style |= _WS_THICKFRAME if w.maxwidth == DontCare && w.maxheight == DontCare { style |= _WS_MAXIMIZEBOX } } } else { style |= _WS_POPUP } } return style } func (w *Window) getWindowExStyle() uint32 { var style uint32 = _WS_EX_APPWINDOW if w.floating { style |= _WS_EX_TOPMOST } return style } func chooseImage(images []*Image, width, height int) *Image { var leastDiff uint = math.MaxUint32 var closest *Image for _, image := range images { currDiff := abs(image.Width*image.Height - width*height) if currDiff < leastDiff { closest = image leastDiff = currDiff } } return closest } func createIcon(image *Image, xhot, yhot int, icon bool) (_HICON, error) { var bi _BITMAPV5HEADER bi.bV5Size = uint32(unsafe.Sizeof(bi)) bi.bV5Width = int32(image.Width) bi.bV5Height = int32(-image.Height) bi.bV5Planes = 1 bi.bV5BitCount = 32 bi.bV5Compression = _BI_BITFIELDS bi.bV5RedMask = 0x00ff0000 bi.bV5GreenMask = 0x0000ff00 bi.bV5BlueMask = 0x000000ff bi.bV5AlphaMask = 0xff000000 dc, err := _GetDC(0) if err != nil { return 0, err } defer _ReleaseDC(0, dc) color, targetPtr, err := _CreateDIBSection(dc, &bi, _DIB_RGB_COLORS, 0, 0) if err != nil { return 0, err } defer func() { _ = _DeleteObject(_HGDIOBJ(color)) }() mask, err := _CreateBitmap(int32(image.Width), int32(image.Height), 1, 1, nil) if err != nil { return 0, err } defer func() { _ = _DeleteObject(_HGDIOBJ(mask)) }() source := image.Pixels target := unsafe.Slice((*byte)(unsafe.Pointer(targetPtr)), len(source)) for i := 0; i < len(source)/4; i++ { target[4*i] = source[4*i+2] target[4*i+1] = source[4*i+1] target[4*i+2] = source[4*i+0] target[4*i+3] = source[4*i+3] } var iconInt32 int32 if icon { iconInt32 = 1 } ii := _ICONINFO{ fIcon: iconInt32, xHotspot: uint32(xhot), yHotspot: uint32(yhot), hbmMask: mask, hbmColor: color, } handle, err := _CreateIconIndirect(&ii) if err != nil { return 0, err } return handle, nil } func getFullWindowSize(style uint32, exStyle uint32, contentWidth, contentHeight int, dpi uint32) (fullWidth, fullHeight int, err error) { if microsoftgdk.IsXbox() { return contentWidth, contentHeight, nil } rect := _RECT{ left: 0, top: 0, right: int32(contentWidth), bottom: int32(contentHeight), } if winver.IsWindows10AnniversaryUpdateOrGreater() { if err := _AdjustWindowRectExForDpi(&rect, style, false, exStyle, dpi); err != nil { return 0, 0, err } } else { if err := _AdjustWindowRectEx(&rect, style, false, exStyle); err != nil { return 0, 0, err } } return int(rect.right - rect.left), int(rect.bottom - rect.top), nil } func (w *Window) applyAspectRatio(edge int, area *_RECT) error { ratio := float32(w.numer) / float32(w.denom) var dpi uint32 = _USER_DEFAULT_SCREEN_DPI if winver.IsWindows10AnniversaryUpdateOrGreater() { dpi = _GetDpiForWindow(w.platform.handle) } xoff, yoff, err := getFullWindowSize(w.getWindowStyle(), w.getWindowExStyle(), 0, 0, dpi) if err != nil { return err } if edge == _WMSZ_LEFT || edge == _WMSZ_BOTTOMLEFT || edge == _WMSZ_RIGHT || edge == _WMSZ_BOTTOMRIGHT { area.bottom = area.top + int32(yoff) + int32(float32(area.right-area.left-int32(xoff))/ratio) } else if edge == _WMSZ_TOPLEFT || edge == _WMSZ_TOPRIGHT { area.top = area.bottom - int32(yoff) - int32(float32(area.right-area.left-int32(xoff))/ratio) } else if edge == _WMSZ_TOP || edge == _WMSZ_BOTTOM { area.right = area.left + int32(xoff) + int32(float32(area.bottom-area.top-int32(yoff))*ratio) } return nil } func (w *Window) updateCursorImage() error { if w.cursorMode == CursorNormal { if w.cursor != nil { _SetCursor(w.cursor.platform.handle) } else { cursor, err := _LoadCursorW(0, _IDC_ARROW) if err != nil { return err } _SetCursor(cursor) } } else { _SetCursor(0) } return nil } func (w *Window) clientToScreen(rect _RECT) (_RECT, error) { point := _POINT{ x: rect.left, y: rect.top, } if err := _ClientToScreen(w.platform.handle, &point); err != nil { return _RECT{}, err } rect.left = point.x rect.top = point.y point = _POINT{ x: rect.right, y: rect.bottom, } if err := _ClientToScreen(w.platform.handle, &point); err != nil { return _RECT{}, err } rect.right = point.x rect.bottom = point.y return rect, nil } func updateClipRect(window *Window) error { if window != nil { clipRect, err := _GetClientRect(window.platform.handle) if err != nil { return err } clipRect, err = window.clientToScreen(clipRect) if err != nil { return err } if err := _ClipCursor(&clipRect); err != nil { return err } } else { if err := _ClipCursor(nil); err != nil { return err } } return nil } func (w *Window) enableRawMouseMotion() error { rid := []_RAWINPUTDEVICE{ { usUsagePage: 0x01, usUsage: 0x02, dwFlags: 0, hwndTarget: w.platform.handle, }, } return _RegisterRawInputDevices(rid) } func (w *Window) disableRawMouseMotion() error { rid := []_RAWINPUTDEVICE{ { usUsagePage: 0x01, usUsage: 0x02, dwFlags: _RIDEV_REMOVE, hwndTarget: 0, }, } return _RegisterRawInputDevices(rid) } func (w *Window) disableCursor() error { _glfw.platformWindow.disabledCursorWindow = w x, y, err := w.platformGetCursorPos() if err != nil { return err } _glfw.platformWindow.restoreCursorPosX, _glfw.platformWindow.restoreCursorPosY = x, y if err := w.updateCursorImage(); err != nil { return err } if err := w.centerCursorInContentArea(); err != nil { return err } if err := updateClipRect(w); err != nil { return err } if w.rawMouseMotion { if err := w.enableRawMouseMotion(); err != nil { return err } } return nil } func (w *Window) enableCursor() error { if w.rawMouseMotion { if err := w.disableRawMouseMotion(); err != nil { return err } } _glfw.platformWindow.disabledCursorWindow = nil if err := updateClipRect(nil); err != nil { return err } if err := w.platformSetCursorPos(_glfw.platformWindow.restoreCursorPosX, _glfw.platformWindow.restoreCursorPosY); err != nil { return err } if err := w.updateCursorImage(); err != nil { return err } return nil } func (w *Window) cursorInContentArea() (bool, error) { if microsoftgdk.IsXbox() { return true, nil } pos, err := _GetCursorPos() if err != nil { if errors.Is(err, windows.ERROR_ACCESS_DENIED) { return false, nil } return false, err } if _WindowFromPoint(pos) != w.platform.handle { return false, nil } area, err := _GetClientRect(w.platform.handle) if err != nil { return false, err } area, err = w.clientToScreen(area) if err != nil { return false, err } return _PtInRect(&area, pos), nil } func (w *Window) updateWindowStyles() error { s, err := _GetWindowLongW(w.platform.handle, _GWL_STYLE) if err != nil { return err } style := uint32(s) style &^= _WS_OVERLAPPEDWINDOW | _WS_POPUP style |= w.getWindowStyle() rect, err := _GetClientRect(w.platform.handle) if err != nil { return err } if winver.IsWindows10AnniversaryUpdateOrGreater() { if err := _AdjustWindowRectExForDpi(&rect, style, false, w.getWindowExStyle(), _GetDpiForWindow(w.platform.handle)); err != nil { return err } } else { if err := _AdjustWindowRectEx(&rect, style, false, w.getWindowExStyle()); err != nil { return err } } rect, err = w.clientToScreen(rect) if err != nil { return err } if _, err := _SetWindowLongW(w.platform.handle, _GWL_STYLE, int32(style)); err != nil { return err } if err := _SetWindowPos(w.platform.handle, _HWND_TOP, rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top, _SWP_FRAMECHANGED|_SWP_NOACTIVATE|_SWP_NOZORDER); err != nil { return err } return nil } func (w *Window) updateFramebufferTransparency() error { if !winver.IsWindowsVistaOrGreater() { return nil } composition, err := _DwmIsCompositionEnabled() if err != nil { // Ignore an error from DWM functions as they might not be implemented e.g. on Proton (#2113). return nil } if !composition { return nil } var opaque bool if !winver.IsWindows8OrGreater() { _, opaque, err = _DwmGetColorizationColor() if err != nil { // Ignore an error from DWM functions as they might not be implemented e.g. on Proton (#2113). return nil } } if winver.IsWindows8OrGreater() || !opaque { region, err := _CreateRectRgn(0, 0, -1, -1) if err != nil { return err } defer func() { _ = _DeleteObject(_HGDIOBJ(region)) }() bb := _DWM_BLURBEHIND{ dwFlags: _DWM_BB_ENABLE | _DWM_BB_BLURREGION, hRgnBlur: region, fEnable: 1, // true } // Ignore an error from DWM functions as they might not be implemented e.g. on Proton (#2113). _ = _DwmEnableBlurBehindWindow(w.platform.handle, &bb) } else { // HACK: Disable framebuffer transparency on Windows 7 when the // colorization color is opaque, because otherwise the window // contents is blended additively with the previous frame instead // of replacing it bb := _DWM_BLURBEHIND{ dwFlags: _DWM_BB_ENABLE, } // Ignore an error from DWM functions as they might not be implemented e.g. on Proton (#2113). _ = _DwmEnableBlurBehindWindow(w.platform.handle, &bb) } return nil } func getKeyMods() ModifierKey { var mods ModifierKey if uint16(_GetKeyState(_VK_SHIFT))&0x8000 != 0 { mods |= ModShift } if uint16(_GetKeyState(_VK_CONTROL))&0x8000 != 0 { mods |= ModControl } if uint16(_GetKeyState(_VK_MENU))&0x8000 != 0 { mods |= ModAlt } if uint16(_GetKeyState(_VK_LWIN)|_GetKeyState(_VK_RWIN))&0x8000 != 0 { mods |= ModSuper } if _GetKeyState(_VK_CAPITAL)&1 != 0 { mods |= ModCapsLock } if _GetKeyState(_VK_NUMLOCK)&1 != 0 { mods |= ModNumLock } return mods } func (w *Window) fitToMonitor() error { mi, ok := _GetMonitorInfoW(w.monitor.platform.handle) if !ok { return nil } var hWndInsertAfter windows.HWND if w.floating { hWndInsertAfter = _HWND_TOPMOST } else { hWndInsertAfter = _HWND_NOTOPMOST } if err := _SetWindowPos(w.platform.handle, hWndInsertAfter, mi.rcMonitor.left, mi.rcMonitor.top, mi.rcMonitor.right-mi.rcMonitor.left, mi.rcMonitor.bottom-mi.rcMonitor.top, _SWP_NOZORDER|_SWP_NOACTIVATE|_SWP_NOCOPYBITS); err != nil { return err } return nil } func (w *Window) acquireMonitor() error { if _glfw.platformWindow.acquiredMonitorCount == 0 { _SetThreadExecutionState(_ES_CONTINUOUS | _ES_DISPLAY_REQUIRED) // HACK: When mouse trails are enabled the cursor becomes invisible when // the OpenGL ICD switches to page flipping if winver.IsWindowsXPOrGreater() { if err := _SystemParametersInfoW(_SPI_GETMOUSETRAILS, 0, uintptr(unsafe.Pointer(&_glfw.platformWindow.mouseTrailSize)), 0); err != nil { return err } if err := _SystemParametersInfoW(_SPI_SETMOUSETRAILS, 0, 0, 0); err != nil { return err } } } if w.monitor.window == nil { _glfw.platformWindow.acquiredMonitorCount++ } if err := w.monitor.setVideoModeWin32(&w.videoMode); err != nil { return err } w.monitor.inputMonitorWindow(w) return nil } func (w *Window) releaseMonitor() error { if w.monitor.window != w { return nil } _glfw.platformWindow.acquiredMonitorCount-- if _glfw.platformWindow.acquiredMonitorCount == 0 { _SetThreadExecutionState(_ES_CONTINUOUS) // HACK: Restore mouse trail length saved in acquireMonitor if winver.IsWindowsXPOrGreater() { if err := _SystemParametersInfoW(_SPI_SETMOUSETRAILS, _glfw.platformWindow.mouseTrailSize, 0, 0); err != nil { return err } } } w.monitor.inputMonitorWindow(nil) w.monitor.restoreVideoModeWin32() return nil } func (w *Window) maximizeWindowManually() error { mi, _ := _GetMonitorInfoW(_MonitorFromWindow(w.platform.handle, _MONITOR_DEFAULTTONEAREST)) rect := mi.rcWork if w.maxwidth != DontCare && w.maxheight != DontCare { if rect.right-rect.left > int32(w.maxwidth) { rect.right = rect.left + int32(w.maxwidth) } if rect.bottom-rect.top > int32(w.maxheight) { rect.bottom = rect.top + int32(w.maxheight) } } s, err := _GetWindowLongW(w.platform.handle, _GWL_STYLE) if err != nil { return err } style := uint32(s) style |= _WS_MAXIMIZE if _, err := _SetWindowLongW(w.platform.handle, _GWL_STYLE, int32(style)); err != nil { return err } if w.decorated { s, err := _GetWindowLongW(w.platform.handle, _GWL_EXSTYLE) if err != nil { return err } exStyle := uint32(s) if winver.IsWindows10AnniversaryUpdateOrGreater() { dpi := _GetDpiForWindow(w.platform.handle) if err := _AdjustWindowRectExForDpi(&rect, style, false, exStyle, dpi); err != nil { return err } m, err := _GetSystemMetricsForDpi(_SM_CYCAPTION, dpi) if err != nil { return err } _OffsetRect(&rect, 0, m) } else { if err := _AdjustWindowRectEx(&rect, style, false, exStyle); err != nil { return err } m, err := _GetSystemMetrics(_SM_CYCAPTION) if err != nil { return err } _OffsetRect(&rect, 0, m) } if rect.bottom > mi.rcWork.bottom { rect.bottom = mi.rcWork.bottom } } if err := _SetWindowPos(w.platform.handle, _HWND_TOP, rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top, _SWP_NOACTIVATE|_SWP_NOZORDER|_SWP_FRAMECHANGED); err != nil { return err } return nil } func windowProc(hWnd windows.HWND, uMsg uint32, wParam _WPARAM, lParam _LPARAM) uintptr /*_LRESULT*/ { window := handleToWindow[hWnd] if window == nil { // This is the message handling for the hidden helper window // and for a regular window during its initial creation switch uMsg { case _WM_NCCREATE: if winver.IsWindows10AnniversaryUpdateOrGreater() { cs := (*_CREATESTRUCTW)(unsafe.Pointer(lParam)) wndconfig := (*wndconfig)(cs.lpCreateParams) // On per-monitor DPI aware V1 systems, only enable // non-client scaling for windows that scale the client area // We need WM_GETDPISCALEDSIZE from V2 to keep the client // area static when the non-client area is scaled if wndconfig != nil && wndconfig.scaleToMonitor { if err := _EnableNonClientDpiScaling(hWnd); err != nil { _glfw.errors = append(_glfw.errors, err) return 0 } } } case _WM_DISPLAYCHANGE: if err := pollMonitorsWin32(); err != nil { _glfw.errors = append(_glfw.errors, err) return 0 } } return uintptr(_DefWindowProcW(hWnd, uMsg, wParam, lParam)) } switch uMsg { case _WM_MOUSEACTIVATE: // HACK: Postpone cursor disabling when the window was activated by // clicking a caption button if _HIWORD(uint32(lParam)) == _WM_LBUTTONDOWN { if _LOWORD(uint32(lParam)) != _HTCLIENT { window.platform.frameAction = true } } case _WM_CAPTURECHANGED: // HACK: Disable the cursor once the caption button action has been // completed or cancelled if lParam == 0 && window.platform.frameAction { if window.cursorMode == CursorDisabled { if err := window.disableCursor(); err != nil { _glfw.errors = append(_glfw.errors, err) return 0 } } window.platform.frameAction = false } case _WM_SETFOCUS: window.inputWindowFocus(true) // HACK: Do not disable cursor while the user is interacting with // a caption button if window.platform.frameAction { break } if window.cursorMode == CursorDisabled { if err := window.disableCursor(); err != nil { _glfw.errors = append(_glfw.errors, err) return 0 } } return 0 case _WM_KILLFOCUS: if window.cursorMode == CursorDisabled { if err := window.enableCursor(); err != nil { _glfw.errors = append(_glfw.errors, err) return 0 } } if window.monitor != nil && window.autoIconify { window.platformIconifyWindow() } window.inputWindowFocus(false) return 0 case _WM_SYSCOMMAND: switch wParam & 0xfff0 { case _SC_SCREENSAVE, _SC_MONITORPOWER: if window.monitor != nil { // We are running in full screen mode, so disallow // screen saver and screen blanking return 0 } else { break } // User trying to access application menu using ALT? case _SC_KEYMENU: return 0 } case _WM_CLOSE: window.inputWindowCloseRequest() return 0 case _WM_INPUTLANGCHANGE: updateKeyNamesWin32() return 0 case _WM_CHAR, _WM_SYSCHAR: if wParam >= 0xd800 && wParam <= 0xdbff { window.platform.highSurrogate = uint16(wParam) } else { var codepoint rune if wParam >= 0xdc00 && wParam <= 0xdfff { if window.platform.highSurrogate != 0 { codepoint += (rune(window.platform.highSurrogate) - 0xd800) << 10 codepoint += (rune(wParam) & 0xffff) - 0xdc00 codepoint += 0x10000 } } else { codepoint = rune(wParam) & 0xffff } window.platform.highSurrogate = 0 window.inputChar(codepoint, getKeyMods(), uMsg != _WM_SYSCHAR) } return 0 case _WM_UNICHAR: if wParam == _UNICODE_NOCHAR { // WM_UNICHAR is not sent by Windows, but is sent by some // third-party input method engine // Returning TRUE here announces support for this message return 1 } window.inputChar(rune(wParam), getKeyMods(), true) return 0 case _WM_KEYDOWN, _WM_SYSKEYDOWN, _WM_KEYUP, _WM_SYSKEYUP: action := Press if _HIWORD(uint32(lParam))&_KF_UP != 0 { action = Release } mods := getKeyMods() scancode := uint32((_HIWORD(uint32(lParam)) & (_KF_EXTENDED | 0xff))) if scancode == 0 { if microsoftgdk.IsXbox() { break } // NOTE: Some synthetic key messages have a scancode of zero // HACK: Map the virtual key back to a usable scancode scancode = _MapVirtualKeyW(uint32(wParam), _MAPVK_VK_TO_VSC) } // HACK: Alt+PrtSc has a different scancode than just PrtSc if scancode == 0x54 { scancode = 0x137 } // HACK: Ctrl+Pause has a different scancode than just Pause if scancode == 0x146 { scancode = 0x45 } // HACK: CJK IME sets the extended bit for right Shift if scancode == 0x136 { scancode = 0x36 } key := _glfw.platformWindow.keycodes[scancode] // The Ctrl keys require special handling if wParam == _VK_CONTROL { if _HIWORD(uint32(lParam))&_KF_EXTENDED != 0 { // Right side keys have the extended key bit set key = KeyRightControl } else { // NOTE: Alt Gr sends Left Ctrl followed by Right Alt // HACK: We only want one event for Alt Gr, so if we detect // this sequence we discard this Left Ctrl message now // and later report Right Alt normally var next _MSG time := _GetMessageTime() if _PeekMessageW(&next, 0, 0, 0, _PM_NOREMOVE) { if next.message == _WM_KEYDOWN || next.message == _WM_SYSKEYDOWN || next.message == _WM_KEYUP || next.message == _WM_SYSKEYUP { if next.wParam == _VK_MENU && (_HIWORD(uint32(next.lParam))&_KF_EXTENDED) != 0 && next.time == uint32(time) { // Next message is Right Alt down so discard this break } } } // This is a regular Left Ctrl message key = KeyLeftControl } } else if wParam == _VK_PROCESSKEY { // IME notifies that keys have been filtered by setting the // virtual key-code to VK_PROCESSKEY break } if action == Release && wParam == _VK_SHIFT { // HACK: Release both Shift keys on Shift up event, as when both // are pressed the first release does not emit any event // NOTE: The other half of this is in _glfwPlatformPollEvents window.inputKey(KeyLeftShift, int(scancode), action, mods) window.inputKey(KeyRightShift, int(scancode), action, mods) } else if wParam == _VK_SNAPSHOT { // HACK: Key down is not reported for the Print Screen key window.inputKey(key, int(scancode), Press, mods) window.inputKey(key, int(scancode), Release, mods) } else { window.inputKey(key, int(scancode), action, mods) } case _WM_LBUTTONDOWN, _WM_RBUTTONDOWN, _WM_MBUTTONDOWN, _WM_XBUTTONDOWN, _WM_LBUTTONUP, _WM_RBUTTONUP, _WM_MBUTTONUP, _WM_XBUTTONUP: var button MouseButton if uMsg == _WM_LBUTTONDOWN || uMsg == _WM_LBUTTONUP { button = MouseButtonLeft } else if uMsg == _WM_RBUTTONDOWN || uMsg == _WM_RBUTTONUP { button = MouseButtonRight } else if uMsg == _WM_MBUTTONDOWN || uMsg == _WM_MBUTTONUP { button = MouseButtonMiddle } else if _GET_XBUTTON_WPARAM(wParam) == _XBUTTON1 { button = MouseButton4 } else { button = MouseButton5 } var action Action if uMsg == _WM_LBUTTONDOWN || uMsg == _WM_RBUTTONDOWN || uMsg == _WM_MBUTTONDOWN || uMsg == _WM_XBUTTONDOWN { action = Press } else { action = Release } var i MouseButton for i = 0; i <= MouseButtonLast; i++ { if window.mouseButtons[i] == Press { break } } if i > MouseButtonLast { _SetCapture(hWnd) } window.inputMouseClick(button, action, getKeyMods()) for i = 0; i <= MouseButtonLast; i++ { if window.mouseButtons[i] == Press { break } } if i > MouseButtonLast { if err := _ReleaseCapture(); err != nil { _glfw.errors = append(_glfw.errors, err) return 0 } } if uMsg == _WM_XBUTTONDOWN || uMsg == _WM_XBUTTONUP { return 1 } return 0 case _WM_MOUSEMOVE: x := _GET_X_LPARAM(lParam) y := _GET_Y_LPARAM(lParam) if !window.platform.cursorTracked { var tme _TRACKMOUSEEVENT tme.cbSize = uint32(unsafe.Sizeof(tme)) tme.dwFlags = _TME_LEAVE tme.hwndTrack = window.platform.handle if err := _TrackMouseEvent(&tme); err != nil { _glfw.errors = append(_glfw.errors, err) return 0 } window.platform.cursorTracked = true window.inputCursorEnter(true) } if window.cursorMode == CursorDisabled { dx := x - window.platform.lastCursorPosX dy := y - window.platform.lastCursorPosY if _glfw.platformWindow.disabledCursorWindow != window { break } if window.rawMouseMotion { break } window.inputCursorPos(window.virtualCursorPosX+float64(dx), window.virtualCursorPosY+float64(dy)) } else { window.inputCursorPos(float64(x), float64(y)) } window.platform.lastCursorPosX = x window.platform.lastCursorPosY = y return 0 case _WM_INPUT: if _glfw.platformWindow.disabledCursorWindow != window { break } if !window.rawMouseMotion { break } ri := _HRAWINPUT(lParam) var size uint32 if _, err := _GetRawInputData(ri, _RID_INPUT, nil, &size); err != nil { _glfw.errors = append(_glfw.errors, err) return 0 } if size > uint32(len(_glfw.platformWindow.rawInput)) { _glfw.platformWindow.rawInput = make([]byte, size) } size = uint32(len(_glfw.platformWindow.rawInput)) if _, err := _GetRawInputData(ri, _RID_INPUT, unsafe.Pointer(&_glfw.platformWindow.rawInput[0]), &size); err != nil { _glfw.errors = append(_glfw.errors, err) return 0 // TODO: break? } var dx, dy int data := (*_RAWINPUT)(unsafe.Pointer(&_glfw.platformWindow.rawInput[0])) if data.mouse.usFlags&_MOUSE_MOVE_ABSOLUTE != 0 { dx = int(data.mouse.lLastX) - window.platform.lastCursorPosX dy = int(data.mouse.lLastY) - window.platform.lastCursorPosY } else { dx = int(data.mouse.lLastX) dy = int(data.mouse.lLastY) } window.inputCursorPos(window.virtualCursorPosX+float64(dx), window.virtualCursorPosY+float64(dy)) window.platform.lastCursorPosX += dx window.platform.lastCursorPosY += dy case _WM_MOUSELEAVE: window.platform.cursorTracked = false window.inputCursorEnter(false) return 0 case _WM_MOUSEWHEEL: window.inputScroll(0, float64(int16(_HIWORD(uint32(wParam))))/_WHEEL_DELTA) return 0 case _WM_MOUSEHWHEEL: // This message is only sent on Windows Vista and later // NOTE: The X-axis is inverted for consistency with macOS and X11 window.inputScroll(float64(-(int16(_HIWORD(uint32(wParam))))/_WHEEL_DELTA), 0) return 0 case _WM_ENTERSIZEMOVE, _WM_ENTERMENULOOP: if window.platform.frameAction { break } // HACK: Enable the cursor while the user is moving or // resizing the window or using the window menu if window.cursorMode == CursorDisabled { if err := window.enableCursor(); err != nil { _glfw.errors = append(_glfw.errors, err) return 0 } } case _WM_EXITSIZEMOVE, _WM_EXITMENULOOP: if window.platform.frameAction { break } // HACK: Disable the cursor once the user is done moving or // resizing the window or using the menu if window.cursorMode == CursorDisabled { if err := window.disableCursor(); err != nil { _glfw.errors = append(_glfw.errors, err) return 0 } } case _WM_SIZE: width := int(_LOWORD(uint32(lParam))) height := int(_HIWORD(uint32(lParam))) iconified := wParam == _SIZE_MINIMIZED maximized := wParam == _SIZE_MAXIMIZED || (window.platform.maximized && wParam != _SIZE_RESTORED) if _glfw.platformWindow.disabledCursorWindow == window { if err := updateClipRect(window); err != nil { _glfw.errors = append(_glfw.errors, err) return 0 } } if window.platform.iconified != iconified { window.inputWindowIconify(iconified) } if window.platform.maximized != maximized { window.inputWindowMaximize(maximized) } if width != window.platform.width || height != window.platform.height { window.platform.width = width window.platform.height = height window.inputFramebufferSize(width, height) window.inputWindowSize(width, height) } if window.monitor != nil && window.platform.iconified != iconified { if iconified { if err := window.releaseMonitor(); err != nil { _glfw.errors = append(_glfw.errors, err) return 0 } } else { if err := window.acquireMonitor(); err != nil { _glfw.errors = append(_glfw.errors, err) return 0 } if err := window.fitToMonitor(); err != nil { _glfw.errors = append(_glfw.errors, err) return 0 } } } window.platform.iconified = iconified window.platform.maximized = maximized return 0 case _WM_MOVE: if _glfw.platformWindow.disabledCursorWindow == window { if err := updateClipRect(window); err != nil { _glfw.errors = append(_glfw.errors, err) return 0 } } // NOTE: This cannot use LOWORD/HIWORD recommended by MSDN, as // those macros do not handle negative window positions correctly window.inputWindowPos(_GET_X_LPARAM(lParam), _GET_Y_LPARAM(lParam)) return 0 case _WM_SIZING: if window.numer == DontCare || window.denom == DontCare { break } if err := window.applyAspectRatio(int(wParam), (*_RECT)(unsafe.Pointer(lParam))); err != nil { _glfw.errors = append(_glfw.errors, err) return 0 } return 1 case _WM_GETMINMAXINFO: var dpi uint32 = _USER_DEFAULT_SCREEN_DPI mmi := (*_MINMAXINFO)(unsafe.Pointer(lParam)) if window.monitor != nil { break } if winver.IsWindows10AnniversaryUpdateOrGreater() { dpi = _GetDpiForWindow(window.platform.handle) } xoff, yoff, err := getFullWindowSize(window.getWindowStyle(), window.getWindowExStyle(), 0, 0, dpi) if err != nil { _glfw.errors = append(_glfw.errors, err) return 0 } if window.minwidth != DontCare && window.minheight != DontCare { mmi.ptMinTrackSize.x = int32(window.minwidth + xoff) mmi.ptMinTrackSize.y = int32(window.minheight + yoff) } if window.maxwidth != DontCare && window.maxheight != DontCare { mmi.ptMaxTrackSize.x = int32(window.maxwidth + xoff) mmi.ptMaxTrackSize.y = int32(window.maxheight + yoff) } if !window.decorated { mh := _MonitorFromWindow(window.platform.handle, _MONITOR_DEFAULTTONEAREST) mi, _ := _GetMonitorInfoW(mh) mmi.ptMaxPosition.x = mi.rcWork.left - mi.rcMonitor.left mmi.ptMaxPosition.y = mi.rcWork.top - mi.rcMonitor.top mmi.ptMaxSize.x = mi.rcWork.right - mi.rcWork.left mmi.ptMaxSize.y = mi.rcWork.bottom - mi.rcWork.top } return 0 case _WM_PAINT: window.inputWindowDamage() case _WM_ERASEBKGND: return 1 case _WM_NCACTIVATE, _WM_NCPAINT: // Prevent title bar from being drawn after restoring a minimized // undecorated window if !window.decorated { return 1 } case _WM_DWMCOMPOSITIONCHANGED, _WM_DWMCOLORIZATIONCOLORCHANGED: if window.platform.transparent { if err := window.updateFramebufferTransparency(); err != nil { _glfw.errors = append(_glfw.errors, err) return 0 } } return 0 case _WM_GETDPISCALEDSIZE: if window.platform.scaleToMonitor { break } // Adjust the window size to keep the content area size constant if winver.IsWindows10CreatorsUpdateOrGreater() { var source, target _RECT size := (*_SIZE)(unsafe.Pointer(lParam)) if err := _AdjustWindowRectExForDpi(&source, window.getWindowStyle(), false, window.getWindowExStyle(), _GetDpiForWindow(window.platform.handle)); err != nil { _glfw.errors = append(_glfw.errors, err) return 0 } if err := _AdjustWindowRectExForDpi(&target, window.getWindowStyle(), false, window.getWindowExStyle(), uint32(_LOWORD(uint32(wParam)))); err != nil { _glfw.errors = append(_glfw.errors, err) return 0 } size.cx += (target.right - target.left) - (source.right - source.left) size.cy += (target.bottom - target.top) - (source.bottom - source.top) return 1 } case _WM_DPICHANGED: xscale := float32(_HIWORD(uint32(wParam))) / float32(_USER_DEFAULT_SCREEN_DPI) yscale := float32(_LOWORD(uint32(wParam))) / float32(_USER_DEFAULT_SCREEN_DPI) // Resize windowed mode windows that either permit rescaling or that // need it to compensate for non-client area scaling if window.monitor == nil && (window.platform.scaleToMonitor || winver.IsWindows10CreatorsUpdateOrGreater()) { suggested := (*_RECT)(unsafe.Pointer(lParam)) if err := _SetWindowPos(window.platform.handle, _HWND_TOP, suggested.left, suggested.top, suggested.right-suggested.left, suggested.bottom-suggested.top, _SWP_NOACTIVATE|_SWP_NOZORDER); err != nil { _glfw.errors = append(_glfw.errors, err) return 0 } } window.inputWindowContentScale(xscale, yscale) case _WM_SETCURSOR: if _LOWORD(uint32(lParam)) == _HTCLIENT { if err := window.updateCursorImage(); err != nil { _glfw.errors = append(_glfw.errors, err) return 0 } return 1 } case _WM_DROPFILES: drop := _HDROP(wParam) count := _DragQueryFileW(drop, 0xffffffff, nil) paths := make([]string, count) // Move the mouse to the position of the drop pt, _ := _DragQueryPoint(drop) window.inputCursorPos(float64(pt.x), float64(pt.y)) for i := range paths { length := _DragQueryFileW(drop, uint32(i), nil) buffer := make([]uint16, length+1) _DragQueryFileW(drop, uint32(i), buffer) paths[i] = windows.UTF16ToString(buffer) } window.inputDrop(paths) _DragFinish(drop) return 0 } return uintptr(_DefWindowProcW(hWnd, uMsg, wParam, lParam)) } var windowProcPtr = windows.NewCallbackCDecl(windowProc) var handleToWindow = map[windows.HWND]*Window{} func (w *Window) createNativeWindow(wndconfig *wndconfig, fbconfig *fbconfig) error { style := w.getWindowStyle() exStyle := w.getWindowExStyle() var xpos, ypos, fullWidth, fullHeight int32 if w.monitor != nil { mi, ok := _GetMonitorInfoW(w.monitor.platform.handle) if !ok { return fmt.Errorf("goglfw: GetMonitorInfoW failed") } // NOTE: This window placement is temporary and approximate, as the // correct position and size cannot be known until the monitor // video mode has been picked in _glfwSetVideoModeWin32 xpos = mi.rcMonitor.left ypos = mi.rcMonitor.top fullWidth = mi.rcMonitor.right - mi.rcMonitor.left fullHeight = mi.rcMonitor.bottom - mi.rcMonitor.top } else { xpos = _CW_USEDEFAULT ypos = _CW_USEDEFAULT w.platform.maximized = wndconfig.maximized if wndconfig.maximized { style |= _WS_MAXIMIZE } w, h, err := getFullWindowSize(style, exStyle, wndconfig.width, wndconfig.height, _USER_DEFAULT_SCREEN_DPI) if err != nil { return err } fullWidth, fullHeight = int32(w), int32(h) } h, err := _CreateWindowExW(exStyle, _GLFW_WNDCLASSNAME, wndconfig.title, style, xpos, ypos, fullWidth, fullHeight, 0, // No parent window 0, // No window menu _glfw.platformWindow.instance, unsafe.Pointer(wndconfig)) if err != nil { return err } w.platform.handle = h handleToWindow[w.platform.handle] = w if !microsoftgdk.IsXbox() && winver.IsWindows7OrGreater() { if err := _ChangeWindowMessageFilterEx(w.platform.handle, _WM_DROPFILES, _MSGFLT_ALLOW, nil); err != nil { return err } if err := _ChangeWindowMessageFilterEx(w.platform.handle, _WM_COPYDATA, _MSGFLT_ALLOW, nil); err != nil { return err } if err := _ChangeWindowMessageFilterEx(w.platform.handle, _WM_COPYGLOBALDATA, _MSGFLT_ALLOW, nil); err != nil { return err } } w.platform.scaleToMonitor = wndconfig.scaleToMonitor // Adjust window rect to account for DPI scaling of the window frame and // (if enabled) DPI scaling of the content area // This cannot be done until we know what monitor the window was placed on if !microsoftgdk.IsXbox() && w.monitor == nil { rect := _RECT{ left: 0, top: 0, right: int32(wndconfig.width), bottom: int32(wndconfig.height), } mh := _MonitorFromWindow(w.platform.handle, _MONITOR_DEFAULTTONEAREST) // Adjust window rect to account for DPI scaling of the window frame and // (if enabled) DPI scaling of the content area // This cannot be done until we know what monitor the window was placed on // Only update the restored window rect as the window may be maximized if wndconfig.scaleToMonitor { xscale, yscale, err := getMonitorContentScaleWin32(mh) if err != nil { return err } if xscale > 0 && yscale > 0 { rect.right = int32(float32(rect.right) * xscale) rect.bottom = int32(float32(rect.bottom) * yscale) } } rect, err = w.clientToScreen(rect) if err != nil { return err } if winver.IsWindows10AnniversaryUpdateOrGreater() { if err := _AdjustWindowRectExForDpi(&rect, style, false, exStyle, _GetDpiForWindow(w.platform.handle)); err != nil { return err } } else { if err := _AdjustWindowRectEx(&rect, style, false, exStyle); err != nil { return err } } // Only update the restored window rect as the window may be maximized wp, err := _GetWindowPlacement(w.platform.handle) if err != nil { return err } _OffsetRect(&rect, wp.rcNormalPosition.left-rect.left, wp.rcNormalPosition.top-rect.top) wp.rcNormalPosition = rect wp.showCmd = _SW_HIDE if err := _SetWindowPlacement(w.platform.handle, &wp); err != nil { return err } // Adjust rect of maximized undecorated window, because by default Windows will // make such a window cover the whole monitor instead of its workarea if wndconfig.maximized && !wndconfig.decorated { mi, _ := _GetMonitorInfoW(mh) if err := _SetWindowPos(w.platform.handle, _HWND_TOP, mi.rcWork.left, mi.rcWork.top, mi.rcWork.right-mi.rcWork.left, mi.rcWork.bottom-mi.rcWork.top, _SWP_NOACTIVATE|_SWP_NOZORDER); err != nil { return err } } } if !microsoftgdk.IsXbox() { _DragAcceptFiles(w.platform.handle, true) } if fbconfig.transparent { if err := w.updateFramebufferTransparency(); err != nil { return err } w.platform.transparent = true } width, height, err := w.platformGetWindowSize() if err != nil { return err } w.platform.width, w.platform.height = width, height return nil } func registerWindowClassWin32() error { var wc _WNDCLASSEXW wc.cbSize = uint32(unsafe.Sizeof(wc)) wc.style = _CS_HREDRAW | _CS_VREDRAW | _CS_OWNDC wc.lpfnWndProc = _WNDPROC(windowProcPtr) wc.hInstance = _glfw.platformWindow.instance cursor, err := _LoadCursorW(0, _IDC_ARROW) if err != nil { return err } wc.hCursor = cursor className, err := windows.UTF16FromString(_GLFW_WNDCLASSNAME) if err != nil { panic("goglfw: _GLFW_WNDCLASSNAME must not inclucde a NUL character") } wc.lpszClassName = &className[0] defer runtime.KeepAlive(className) // In the original GLFW implementation, an embedded resource GLFW_ICON is used if possible. // See https://www.glfw.org/docs/3.3/group__window.html if !microsoftgdk.IsXbox() { icon, err := _LoadImageW(0, _IDI_APPLICATION, _IMAGE_ICON, 0, 0, _LR_DEFAULTSIZE|_LR_SHARED) if err != nil { return err } wc.hIcon = _HICON(icon) } if _, err := _RegisterClassExW(&wc); err != nil { return err } return nil } func unregisterWindowClassWin32() error { if err := _UnregisterClassW(_GLFW_WNDCLASSNAME, _glfw.platformWindow.instance); err != nil { return err } return nil } func (w *Window) platformCreateWindow(wndconfig *wndconfig, ctxconfig *ctxconfig, fbconfig *fbconfig) error { if err := w.createNativeWindow(wndconfig, fbconfig); err != nil { return err } if ctxconfig.client != NoAPI { if ctxconfig.source == NativeContextAPI { if err := initWGL(); err != nil { return err } if err := w.createContextWGL(ctxconfig, fbconfig); err != nil { return err } } if err := w.refreshContextAttribs(ctxconfig); err != nil { return err } } if wndconfig.mousePassthrough { if err := w.platformSetWindowMousePassthrough(true); err != nil { return err } } if w.monitor != nil { w.platformShowWindow() if err := w.platformFocusWindow(); err != nil { return err } if err := w.acquireMonitor(); err != nil { return err } if err := w.fitToMonitor(); err != nil { return err } if wndconfig.centerCursor { if err := w.centerCursorInContentArea(); err != nil { return err } } } else { if wndconfig.visible { w.platformShowWindow() if wndconfig.focused { if err := w.platformFocusWindow(); err != nil { return err } } } } return nil } func (w *Window) platformDestroyWindow() error { if w.monitor != nil { if err := w.releaseMonitor(); err != nil { return err } } if w.context.destroy != nil { if err := w.context.destroy(w); err != nil { return err } } if _glfw.platformWindow.disabledCursorWindow == w { _glfw.platformWindow.disabledCursorWindow = nil } if w.platform.handle != 0 { if !microsoftgdk.IsXbox() { // An error 'invalid window handle' can occur without any specific reasons (#2551). // As there is nothing to do, just ignore this error. if err := _DestroyWindow(w.platform.handle); err != nil && !errors.Is(err, windows.ERROR_INVALID_WINDOW_HANDLE) { return err } } delete(handleToWindow, w.platform.handle) w.platform.handle = 0 } if w.platform.bigIcon != 0 { if err := _DestroyIcon(w.platform.bigIcon); err != nil { return err } } if w.platform.smallIcon != 0 { if err := _DestroyIcon(w.platform.smallIcon); err != nil { return err } } return nil } func (w *Window) platformSetWindowTitle(title string) error { if microsoftgdk.IsXbox() { return nil } return _SetWindowTextW(w.platform.handle, title) } func (w *Window) platformSetWindowIcon(images []*Image) error { var bigIcon, smallIcon _HICON if len(images) > 0 { cxIcon, err := _GetSystemMetrics(_SM_CXICON) if err != nil { return err } cyIcon, err := _GetSystemMetrics(_SM_CYICON) if err != nil { return err } cxsmIcon, err := _GetSystemMetrics(_SM_CXSMICON) if err != nil { return err } cysmIcon, err := _GetSystemMetrics(_SM_CYSMICON) if err != nil { return err } bigImage := chooseImage(images, int(cxIcon), int(cyIcon)) smallImage := chooseImage(images, int(cxsmIcon), int(cysmIcon)) bigIcon, err = createIcon(bigImage, 0, 0, true) if err != nil { return err } smallIcon, err = createIcon(smallImage, 0, 0, false) if err != nil { return err } } else { i, err := _GetClassLongPtrW(w.platform.handle, _GCLP_HICON) if err != nil { return err } bigIcon = _HICON(i) i, err = _GetClassLongPtrW(w.platform.handle, _GCLP_HICONSM) if err != nil { return err } smallIcon = _HICON(i) } _SendMessageW(w.platform.handle, _WM_SETICON, _ICON_BIG, _LPARAM(bigIcon)) _SendMessageW(w.platform.handle, _WM_SETICON, _ICON_SMALL, _LPARAM(smallIcon)) if w.platform.bigIcon != 0 { if err := _DestroyIcon(w.platform.bigIcon); err != nil { return err } } if w.platform.smallIcon != 0 { if err := _DestroyIcon(w.platform.smallIcon); err != nil { return err } } if len(images) > 0 { w.platform.bigIcon = bigIcon w.platform.smallIcon = smallIcon } else { w.platform.bigIcon = 0 w.platform.smallIcon = 0 } return nil } func (w *Window) platformGetWindowPos() (xpos, ypos int, err error) { if microsoftgdk.IsXbox() { return 0, 0, nil } var pos _POINT if err := _ClientToScreen(w.platform.handle, &pos); err != nil { return 0, 0, err } return int(pos.x), int(pos.y), nil } func (w *Window) platformSetWindowPos(xpos, ypos int) error { if microsoftgdk.IsXbox() { return nil } rect := _RECT{ left: int32(xpos), top: int32(ypos), right: int32(xpos), bottom: int32(ypos), } if winver.IsWindows10AnniversaryUpdateOrGreater() { if err := _AdjustWindowRectExForDpi(&rect, w.getWindowStyle(), false, w.getWindowExStyle(), _GetDpiForWindow(w.platform.handle)); err != nil { return err } } else { if err := _AdjustWindowRectEx(&rect, w.getWindowStyle(), false, w.getWindowExStyle()); err != nil { return err } } if err := _SetWindowPos(w.platform.handle, 0, rect.left, rect.top, 0, 0, _SWP_NOACTIVATE|_SWP_NOZORDER|_SWP_NOSIZE); err != nil { return err } return nil } func (w *Window) platformGetWindowSize() (width, height int, err error) { area, err := _GetClientRect(w.platform.handle) if err != nil { return 0, 0, err } return int(area.right), int(area.bottom), nil } func (w *Window) platformSetWindowSize(width, height int) error { if w.monitor != nil { if w.monitor.window == w { if err := w.acquireMonitor(); err != nil { return err } if err := w.fitToMonitor(); err != nil { return err } } } else { rect := _RECT{ left: 0, top: 0, right: int32(width), bottom: int32(height), } if winver.IsWindows10AnniversaryUpdateOrGreater() { if err := _AdjustWindowRectExForDpi(&rect, w.getWindowStyle(), false, w.getWindowExStyle(), _GetDpiForWindow(w.platform.handle)); err != nil { return err } } else { if err := _AdjustWindowRectEx(&rect, w.getWindowStyle(), false, w.getWindowExStyle()); err != nil { return err } } if err := _SetWindowPos(w.platform.handle, _HWND_TOP, 0, 0, rect.right-rect.left, rect.bottom-rect.top, _SWP_NOACTIVATE|_SWP_NOOWNERZORDER|_SWP_NOMOVE|_SWP_NOZORDER); err != nil { return err } } return nil } func (w *Window) platformSetWindowSizeLimits(minwidth, minheight, maxwidth, maxheight int) error { if (minwidth == DontCare || minheight == DontCare) && (maxwidth == DontCare || maxheight == DontCare) { return nil } area, err := _GetWindowRect(w.platform.handle) if err != nil { return err } if err := _MoveWindow(w.platform.handle, area.left, area.top, area.right-area.left, area.bottom-area.top, true); err != nil { return err } if err := w.updateWindowStyles(); err != nil { return err } return nil } func (w *Window) platformSetWindowAspectRatio(numer, denom int) error { if numer == DontCare || denom == DontCare { return nil } area, err := _GetWindowRect(w.platform.handle) if err != nil { return err } if err := w.applyAspectRatio(_WMSZ_BOTTOMRIGHT, &area); err != nil { return err } if err := _MoveWindow(w.platform.handle, area.left, area.top, area.right-area.left, area.bottom-area.top, true); err != nil { return err } return nil } func (w *Window) platformGetFramebufferSize() (width, height int, err error) { return w.platformGetWindowSize() } func (w *Window) platformGetWindowFrameSize() (left, top, right, bottom int, err error) { width, height, err := w.platformGetWindowSize() if err != nil { return 0, 0, 0, 0, err } rect := _RECT{ left: 0, top: 0, right: int32(width), bottom: int32(height), } if winver.IsWindows10AnniversaryUpdateOrGreater() { if err := _AdjustWindowRectExForDpi(&rect, w.getWindowStyle(), false, w.getWindowExStyle(), _GetDpiForWindow(w.platform.handle)); err != nil { return 0, 0, 0, 0, err } } else { if err := _AdjustWindowRectEx(&rect, w.getWindowStyle(), false, w.getWindowExStyle()); err != nil { return 0, 0, 0, 0, err } } return -int(rect.left), -int(rect.top), int(rect.right) - width, int(rect.bottom) - height, nil } func (w *Window) platformGetWindowContentScale() (xscale, yscale float32, err error) { handle := _MonitorFromWindow(w.platform.handle, _MONITOR_DEFAULTTONEAREST) return getMonitorContentScaleWin32(handle) } func (w *Window) platformIconifyWindow() { _ShowWindow(w.platform.handle, _SW_MINIMIZE) } func (w *Window) platformRestoreWindow() { _ShowWindow(w.platform.handle, _SW_RESTORE) } func (w *Window) platformMaximizeWindow() error { if _IsWindowVisible(w.platform.handle) { _ShowWindow(w.platform.handle, _SW_MAXIMIZE) } else { if err := w.maximizeWindowManually(); err != nil { return err } } return nil } func (w *Window) platformShowWindow() { _ShowWindow(w.platform.handle, _SW_SHOWNA) } func (w *Window) platformHideWindow() { _ShowWindow(w.platform.handle, _SW_HIDE) } func (w *Window) platformRequestWindowAttention() { _FlashWindow(w.platform.handle, true) } func (w *Window) platformFocusWindow() error { if microsoftgdk.IsXbox() { return nil } if err := _BringWindowToTop(w.platform.handle); err != nil { return err } _SetForegroundWindow(w.platform.handle) if _, err := _SetFocus(w.platform.handle); err != nil { return err } return nil } func (w *Window) platformSetWindowMonitor(monitor *Monitor, xpos, ypos, width, height, refreshRate int) error { if w.monitor == monitor { if monitor != nil { if monitor.window == w { if err := w.acquireMonitor(); err != nil { return err } if err := w.fitToMonitor(); err != nil { return err } } } else { rect := _RECT{ left: int32(xpos), top: int32(ypos), right: int32(xpos + width), bottom: int32(ypos + height), } if winver.IsWindows10AnniversaryUpdateOrGreater() { if err := _AdjustWindowRectExForDpi(&rect, w.getWindowStyle(), false, w.getWindowExStyle(), _GetDpiForWindow(w.platform.handle)); err != nil { return err } } else { if err := _AdjustWindowRectEx(&rect, w.getWindowStyle(), false, w.getWindowExStyle()); err != nil { return err } } if err := _SetWindowPos(w.platform.handle, _HWND_TOP, rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top, _SWP_NOCOPYBITS|_SWP_NOACTIVATE|_SWP_NOZORDER); err != nil { return err } } return nil } if w.monitor != nil { if err := w.releaseMonitor(); err != nil { return err } } w.inputWindowMonitor(monitor) if w.monitor != nil { var flags uint32 = _SWP_SHOWWINDOW | _SWP_NOACTIVATE | _SWP_NOCOPYBITS if w.decorated { s, err := _GetWindowLongW(w.platform.handle, _GWL_STYLE) if err != nil { return err } style := uint32(s) style &^= _WS_OVERLAPPEDWINDOW style |= w.getWindowStyle() if _, err := _SetWindowLongW(w.platform.handle, _GWL_STYLE, int32(style)); err != nil { return err } flags |= _SWP_FRAMECHANGED } if err := w.acquireMonitor(); err != nil { return err } mi, _ := _GetMonitorInfoW(w.monitor.platform.handle) var hWnd windows.HWND = _HWND_NOTOPMOST if w.floating { hWnd = _HWND_TOPMOST } if err := _SetWindowPos(w.platform.handle, hWnd, mi.rcMonitor.left, mi.rcMonitor.top, mi.rcMonitor.right-mi.rcMonitor.left, mi.rcMonitor.bottom-mi.rcMonitor.top, flags); err != nil { return err } } else { var flags uint32 = _SWP_NOACTIVATE | _SWP_NOCOPYBITS if w.decorated { s, err := _GetWindowLongW(w.platform.handle, _GWL_STYLE) if err != nil { return err } style := uint32(s) style &^= _WS_POPUP style |= w.getWindowStyle() if _, err := _SetWindowLongW(w.platform.handle, _GWL_STYLE, int32(style)); err != nil { return err } flags |= _SWP_FRAMECHANGED } rect := _RECT{ left: int32(xpos), top: int32(ypos), right: int32(xpos + width), bottom: int32(ypos + height), } if winver.IsWindows10AnniversaryUpdateOrGreater() { if err := _AdjustWindowRectExForDpi(&rect, w.getWindowStyle(), false, w.getWindowExStyle(), _GetDpiForWindow(w.platform.handle)); err != nil { return err } } else { if err := _AdjustWindowRectEx(&rect, w.getWindowStyle(), false, w.getWindowExStyle()); err != nil { return err } } var after windows.HWND if w.floating { after = _HWND_TOPMOST } else { after = _HWND_NOTOPMOST } if err := _SetWindowPos(w.platform.handle, after, rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top, flags); err != nil { return err } } return nil } func (w *Window) platformWindowFocused() bool { if microsoftgdk.IsXbox() { return true } return w.platform.handle == _GetActiveWindow() } func (w *Window) platformWindowIconified() bool { if microsoftgdk.IsXbox() { return false } return _IsIconic(w.platform.handle) } func (w *Window) platformWindowVisible() bool { if microsoftgdk.IsXbox() { return true } return _IsWindowVisible(w.platform.handle) } func (w *Window) platformWindowMaximized() bool { if microsoftgdk.IsXbox() { return false } return _IsZoomed(w.platform.handle) } func (w *Window) platformWindowHovered() (bool, error) { if microsoftgdk.IsXbox() { return true, nil } return w.cursorInContentArea() } func (w *Window) platformFramebufferTransparent() bool { if microsoftgdk.IsXbox() { return false } if !w.platform.transparent { return false } if !winver.IsWindowsVistaOrGreater() { return false } composition, err := _DwmIsCompositionEnabled() if err != nil || !composition { return false } if !winver.IsWindows8OrGreater() { // HACK: Disable framebuffer transparency on Windows 7 when the // colorization color is opaque, because otherwise the window // contents is blended additively with the previous frame instead // of replacing it _, opaque, err := _DwmGetColorizationColor() if err != nil || opaque { return false } } return true } func (w *Window) platformSetWindowResizable(enabled bool) error { return w.updateWindowStyles() } func (w *Window) platformSetWindowDecorated(enabled bool) error { return w.updateWindowStyles() } func (w *Window) platformSetWindowFloating(enabled bool) error { var after windows.HWND = _HWND_NOTOPMOST if enabled { after = _HWND_TOPMOST } return _SetWindowPos(w.platform.handle, after, 0, 0, 0, 0, _SWP_NOACTIVATE|_SWP_NOMOVE|_SWP_NOSIZE) } func (w *Window) platformSetWindowMousePassthrough(enabled bool) error { exStyle, err := _GetWindowLongW(w.platform.handle, _GWL_EXSTYLE) if err != nil { return err } var key _COLORREF var alpha byte var flags uint32 if exStyle&_WS_EX_LAYERED != 0 { var err error key, alpha, flags, err = _GetLayeredWindowAttributes(w.platform.handle) if err != nil { return err } } if enabled { exStyle |= _WS_EX_TRANSPARENT | _WS_EX_LAYERED } else { exStyle &^= _WS_EX_TRANSPARENT // NOTE: Window opacity also needs the layered window style so do not // remove it if the window is alpha blended if exStyle&_WS_EX_LAYERED != 0 { if flags&_LWA_ALPHA == 0 { exStyle &^= _WS_EX_LAYERED } } } if _, err := _SetWindowLongW(w.platform.handle, _GWL_EXSTYLE, exStyle); err != nil { return err } if enabled { if err := _SetLayeredWindowAttributes(w.platform.handle, key, alpha, flags); err != nil { return err } } return nil } func (w *Window) platformGetWindowOpacity() (float32, error) { style, err := _GetWindowLongW(w.platform.handle, _GWL_EXSTYLE) if err != nil { return 0, err } if style&_WS_EX_LAYERED != 0 { _, alpha, flags, err := _GetLayeredWindowAttributes(w.platform.handle) if err != nil { return 0, err } if flags&_LWA_ALPHA != 0 { return float32(alpha) / 255, nil } } return 1, nil } func (w *Window) platformSetWindowOpacity(opacity float32) error { if opacity < 1 { alpha := byte(255 * opacity) style, err := _GetWindowLongW(w.platform.handle, _GWL_EXSTYLE) if err != nil { return err } style |= _WS_EX_LAYERED if _, err := _SetWindowLongW(w.platform.handle, _GWL_EXSTYLE, style); err != nil { return err } if err := _SetLayeredWindowAttributes(w.platform.handle, 0, alpha, _LWA_ALPHA); err != nil { return err } } else { style, err := _GetWindowLongW(w.platform.handle, _GWL_EXSTYLE) if err != nil { return err } style &^= _WS_EX_LAYERED if _, err := _SetWindowLongW(w.platform.handle, _GWL_EXSTYLE, style); err != nil { return err } } return nil } func (w *Window) platformSetRawMouseMotion(enabled bool) error { if _glfw.platformWindow.disabledCursorWindow != w { return nil } if enabled { if err := w.enableRawMouseMotion(); err != nil { return err } } else { if err := w.disableRawMouseMotion(); err != nil { return err } } return nil } func platformRawMouseMotionSupported() bool { return true } func platformPollEvents() error { if len(_glfw.errors) > 0 { return _glfw.errors[0] } var msg _MSG for _PeekMessageW(&msg, 0, 0, 0, _PM_REMOVE) { if msg.message == _WM_QUIT { // NOTE: While GLFW does not itself post WM_QUIT, other processes // may post it to this one, for example Task Manager // HACK: Treat WM_QUIT as a close on all windows for _, window := range _glfw.windows { window.inputWindowCloseRequest() } } else { _TranslateMessage(&msg) _DispatchMessageW(&msg) } } var handle windows.HWND if microsoftgdk.IsXbox() { // Assume that there is always exactly one active window. handle = _glfw.windows[0].platform.handle } else { handle = _GetActiveWindow() } // HACK: Release modifier keys that the system did not emit KEYUP for // NOTE: Shift keys on Windows tend to "stick" when both are pressed as // no key up message is generated by the first key release // NOTE: Windows key is not reported as released by the Win+V hotkey // Other Win hotkeys are handled implicitly by _glfwInputWindowFocus // because they change the input focus // NOTE: The other half of this is in the WM_*KEY* handler in windowProc if handle != 0 { if window := handleToWindow[handle]; window != nil { keys := [...]struct { VK int Key Key }{ {_VK_LSHIFT, KeyLeftShift}, {_VK_RSHIFT, KeyRightShift}, {_VK_LWIN, KeyLeftSuper}, {_VK_RWIN, KeyRightSuper}, } for i := range keys { vk := keys[i].VK key := keys[i].Key scancode := _glfw.platformWindow.scancodes[key] if uint32(_GetKeyState(int32(vk)))&0x8000 != 0 { continue } if window.keys[key] != Press { continue } window.inputKey(key, int(scancode), Release, getKeyMods()) } } } if window := _glfw.platformWindow.disabledCursorWindow; window != nil { width, height, err := window.platformGetWindowSize() if err != nil { return err } // NOTE: Re-center the cursor only if it has moved since the last call, // to avoid breaking glfwWaitEvents with WM_MOUSEMOVE if window.platform.lastCursorPosX != width/2 || window.platform.lastCursorPosY != height/2 { if err := window.platformSetCursorPos(float64(width/2), float64(height/2)); err != nil { return err } } } return nil } func platformWaitEvents() error { if err := _WaitMessage(); err != nil { return err } if err := platformPollEvents(); err != nil { return err } return nil } func platformWaitEventsTimeout(timeout float64) error { if _, err := _MsgWaitForMultipleObjects(0, nil, false, uint32(timeout*1e3), _QS_ALLEVENTS); err != nil { return err } if err := platformPollEvents(); err != nil { return err } return nil } func platformPostEmptyEvent() error { return _PostMessageW(_glfw.platformWindow.helperWindowHandle, _WM_NULL, 0, 0) } func (w *Window) platformGetCursorPos() (xpos, ypos float64, err error) { pos, err := _GetCursorPos() if err != nil { if errors.Is(err, windows.ERROR_ACCESS_DENIED) { return 0, 0, nil } return 0, 0, err } if !microsoftgdk.IsXbox() { if err := _ScreenToClient(w.platform.handle, &pos); err != nil { return 0, 0, err } } return float64(pos.x), float64(pos.y), nil } func (w *Window) platformSetCursorPos(xpos, ypos float64) error { pos := _POINT{ x: int32(xpos), y: int32(ypos), } // Store the new position so it can be recognized later w.platform.lastCursorPosX = int(pos.x) w.platform.lastCursorPosY = int(pos.y) if !microsoftgdk.IsXbox() { if err := _ClientToScreen(w.platform.handle, &pos); err != nil { return err } } if err := _SetCursorPos(pos.x, pos.y); err != nil { return err } return nil } func (w *Window) platformSetCursorMode(mode int) error { if mode == CursorDisabled { if w.platformWindowFocused() { if err := w.disableCursor(); err != nil { return err } } return nil } if _glfw.platformWindow.disabledCursorWindow == w { if err := w.enableCursor(); err != nil { return err } return nil } in, err := w.cursorInContentArea() if err != nil { return err } if in { if err := w.updateCursorImage(); err != nil { return err } } return nil } func platformGetScancodeName(scancode int) (string, error) { if scancode < 0 || scancode > (_KF_EXTENDED|0xff) || _glfw.platformWindow.keycodes[scancode] == KeyUnknown { return "", fmt.Errorf("glwfwin: invalid scancode %d: %w", scancode, InvalidValue) } return _glfw.platformWindow.keynames[_glfw.platformWindow.keycodes[scancode]], nil } func platformGetKeyScancode(key Key) int { return _glfw.platformWindow.scancodes[key] } func (c *Cursor) platformCreateStandardCursor(shape StandardCursor) error { if microsoftgdk.IsXbox() { return nil } var id int switch shape { case ArrowCursor: id = _OCR_NORMAL case IBeamCursor: id = _OCR_IBEAM case CrosshairCursor: id = _OCR_CROSS case HandCursor: id = _OCR_HAND case HResizeCursor: id = _OCR_SIZEWE case VResizeCursor: id = _OCR_SIZENS case ResizeNWSECursor: // v3.4 id = _OCR_SIZENWSE case ResizeNESWCursor: // v3.4 id = _OCR_SIZENESW case ResizeAllCursor: // v3.4 id = _OCR_SIZEALL case NotAllowedCursor: // v3.4 id = _OCR_NO default: return fmt.Errorf("goglfw: invalid shape: %d", shape) } h, err := _LoadImageW(0, uintptr(id), _IMAGE_CURSOR, 0, 0, _LR_DEFAULTSIZE|_LR_SHARED) if err != nil { return err } c.platform.handle = _HCURSOR(h) return nil } func (c *Cursor) platformDestroyCursor() error { if c.platform.handle != 0 { if err := _DestroyIcon(_HICON(c.platform.handle)); err != nil { return err } } return nil } func (w *Window) platformSetCursor(cursor *Cursor) error { in, err := w.cursorInContentArea() if err != nil { return err } if in { if err := w.updateCursorImage(); err != nil { return err } } return nil } func platformSetClipboardString(str string) error { panic("goglfw: platformSetClipboardString is not implemented") } func platformGetClipboardString() (string, error) { panic("goglfw: platformGetClipboardString is not implemented") } func (w *Window) GetWin32Window() (windows.HWND, error) { if !_glfw.initialized { return 0, NotInitialized } return w.platform.handle, nil }