2022-05-27 13:38:45 +02:00
|
|
|
// SPDX-License-Identifier: Zlib
|
|
|
|
// SPDX-FileCopyrightText: 2002-2006 Marcus Geelnard
|
|
|
|
// SPDX-FileCopyrightText: 2006-2019 Camilla Löwy
|
|
|
|
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
|
2022-04-19 17:49:43 +02:00
|
|
|
|
2023-01-21 14:09:12 +01:00
|
|
|
package goglfw
|
2022-04-19 17:49:43 +02:00
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"unsafe"
|
|
|
|
|
|
|
|
"golang.org/x/sys/windows"
|
2022-05-29 12:13:31 +02:00
|
|
|
|
|
|
|
"github.com/hajimehoshi/ebiten/v2/internal/microsoftgdk"
|
2023-03-30 19:07:35 +02:00
|
|
|
"github.com/hajimehoshi/ebiten/v2/internal/winver"
|
2022-04-19 17:49:43 +02:00
|
|
|
)
|
|
|
|
|
2022-05-24 19:21:30 +02:00
|
|
|
func monitorCallback(handle _HMONITOR, dc _HDC, rect *_RECT, monitor *Monitor /* _LPARAM */) uintptr /* _BOOL */ {
|
2022-04-19 17:49:43 +02:00
|
|
|
if mi, ok := _GetMonitorInfoW_Ex(handle); ok {
|
2023-02-07 19:05:46 +01:00
|
|
|
if windows.UTF16ToString(mi.szDevice[:]) == monitor.platform.adapterName {
|
|
|
|
monitor.platform.handle = handle
|
2022-04-19 17:49:43 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
|
|
|
var monitorCallbackPtr = windows.NewCallbackCDecl(monitorCallback)
|
|
|
|
|
|
|
|
func createMonitor(adapter *_DISPLAY_DEVICEW, display *_DISPLAY_DEVICEW) (*Monitor, error) {
|
|
|
|
var name string
|
|
|
|
if display != nil {
|
|
|
|
name = windows.UTF16ToString(display.DeviceString[:])
|
|
|
|
} else {
|
|
|
|
name = windows.UTF16ToString(adapter.DeviceString[:])
|
|
|
|
}
|
|
|
|
if name == "" {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
adapterDeviceName := windows.UTF16ToString(adapter.DeviceName[:])
|
|
|
|
dm, ok := _EnumDisplaySettingsW(adapterDeviceName, _ENUM_CURRENT_SETTINGS)
|
|
|
|
if !ok {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
monitor := &Monitor{
|
2022-05-27 13:12:43 +02:00
|
|
|
name: name,
|
2022-04-19 17:49:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if adapter.StateFlags&_DISPLAY_DEVICE_MODESPRUNED != 0 {
|
2023-02-07 19:05:46 +01:00
|
|
|
monitor.platform.modesPruned = true
|
2022-04-19 17:49:43 +02:00
|
|
|
}
|
|
|
|
|
2023-02-07 19:05:46 +01:00
|
|
|
monitor.platform.adapterName = adapterDeviceName
|
2022-04-19 17:49:43 +02:00
|
|
|
if display != nil {
|
2023-02-07 19:05:46 +01:00
|
|
|
monitor.platform.displayName = windows.UTF16ToString(display.DeviceName[:])
|
2022-04-19 17:49:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
rect := _RECT{
|
|
|
|
left: dm.dmPosition.x,
|
|
|
|
top: dm.dmPosition.y,
|
|
|
|
right: dm.dmPosition.x + int32(dm.dmPelsWidth),
|
|
|
|
bottom: dm.dmPosition.y + int32(dm.dmPelsHeight),
|
|
|
|
}
|
|
|
|
if err := _EnumDisplayMonitors(0, &rect, monitorCallbackPtr, _LPARAM(unsafe.Pointer(monitor))); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return monitor, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func pollMonitorsWin32() error {
|
2022-05-29 12:13:31 +02:00
|
|
|
if microsoftgdk.IsXbox() {
|
2022-05-27 08:55:21 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-04-19 17:49:43 +02:00
|
|
|
disconnected := make([]*Monitor, len(_glfw.monitors))
|
|
|
|
copy(disconnected, _glfw.monitors)
|
|
|
|
|
|
|
|
adapterLoop:
|
|
|
|
for adapterIndex := uint32(0); ; adapterIndex++ {
|
|
|
|
adapter, ok := _EnumDisplayDevicesW("", adapterIndex, 0)
|
|
|
|
if !ok {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if adapter.StateFlags&_DISPLAY_DEVICE_ACTIVE == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
typ := _GLFW_INSERT_LAST
|
|
|
|
if adapter.StateFlags&_DISPLAY_DEVICE_PRIMARY_DEVICE != 0 {
|
|
|
|
typ = _GLFW_INSERT_FIRST
|
|
|
|
}
|
|
|
|
|
|
|
|
var found bool
|
|
|
|
displayLoop:
|
|
|
|
for displayIndex := uint32(0); ; displayIndex++ {
|
|
|
|
display, ok := _EnumDisplayDevicesW(windows.UTF16ToString(adapter.DeviceName[:]), displayIndex, 0)
|
|
|
|
if !ok {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
found = true
|
|
|
|
if display.StateFlags&_DISPLAY_DEVICE_ACTIVE == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, monitor := range disconnected {
|
2023-02-07 19:05:46 +01:00
|
|
|
if monitor != nil && monitor.platform.displayName == windows.UTF16ToString(display.DeviceName[:]) {
|
2022-04-19 17:49:43 +02:00
|
|
|
disconnected[i] = nil
|
2022-09-09 18:52:46 +02:00
|
|
|
err := _EnumDisplayMonitors(0, nil, monitorCallbackPtr, _LPARAM(unsafe.Pointer(_glfw.monitors[i])))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-04-19 17:49:43 +02:00
|
|
|
continue displayLoop
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
monitor, err := createMonitor(&adapter, &display)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if monitor == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := inputMonitor(monitor, Connected, typ); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
typ = _GLFW_INSERT_LAST
|
|
|
|
}
|
|
|
|
|
|
|
|
// HACK: If an active adapter does not have any display devices
|
|
|
|
// (as sometimes happens), add it directly as a monitor
|
|
|
|
if !found {
|
|
|
|
for i, monitor := range disconnected {
|
2023-02-07 19:05:46 +01:00
|
|
|
if monitor != nil && monitor.platform.displayName == windows.UTF16ToString(adapter.DeviceName[:]) {
|
2022-04-19 17:49:43 +02:00
|
|
|
disconnected[i] = nil
|
|
|
|
continue adapterLoop
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
monitor, err := createMonitor(&adapter, nil)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if monitor == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := inputMonitor(monitor, Connected, typ); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, monitor := range disconnected {
|
|
|
|
if monitor != nil {
|
|
|
|
if err := inputMonitor(monitor, Disconnected, 0); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Monitor) setVideoModeWin32(desired *VidMode) error {
|
|
|
|
best, err := m.chooseVideoMode(desired)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
current := m.platformGetVideoMode()
|
|
|
|
if best.equals(current) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
dm := _DEVMODEW{
|
|
|
|
dmFields: _DM_PELSWIDTH | _DM_PELSHEIGHT | _DM_BITSPERPEL | _DM_DISPLAYFREQUENCY,
|
|
|
|
dmPelsWidth: uint32(best.Width),
|
|
|
|
dmPelsHeight: uint32(best.Height),
|
|
|
|
dmBitsPerPel: uint32(best.RedBits + best.GreenBits + best.BlueBits),
|
|
|
|
dmDisplayFrequency: uint32(best.RefreshRate),
|
|
|
|
}
|
|
|
|
dm.dmSize = uint16(unsafe.Sizeof(dm))
|
|
|
|
if dm.dmBitsPerPel < 15 || dm.dmBitsPerPel >= 24 {
|
|
|
|
dm.dmBitsPerPel = 32
|
|
|
|
}
|
2023-02-07 19:05:46 +01:00
|
|
|
switch _ChangeDisplaySettingsExW(m.platform.adapterName, &dm, 0, _CDS_FULLSCREEN, nil) {
|
2022-04-19 17:49:43 +02:00
|
|
|
case _DISP_CHANGE_SUCCESSFUL:
|
2023-02-07 19:05:46 +01:00
|
|
|
m.platform.modeChanged = true
|
2022-04-19 17:49:43 +02:00
|
|
|
return nil
|
|
|
|
case _DISP_CHANGE_BADDUALVIEW:
|
2023-01-21 14:09:12 +01:00
|
|
|
return errors.New("goglfw: the system uses DualView at Monitor.setVideoModeWin32")
|
2022-04-19 17:49:43 +02:00
|
|
|
case _DISP_CHANGE_BADFLAGS:
|
2023-01-21 14:09:12 +01:00
|
|
|
return errors.New("goglfw: invalid flags at Monitor.setVideoModeWin32")
|
2022-04-19 17:49:43 +02:00
|
|
|
case _DISP_CHANGE_BADMODE:
|
2023-01-21 14:09:12 +01:00
|
|
|
return errors.New("goglfw: graphics mode not supported at Monitor.setVideoModeWin32")
|
2022-04-19 17:49:43 +02:00
|
|
|
case _DISP_CHANGE_BADPARAM:
|
2023-01-21 14:09:12 +01:00
|
|
|
return errors.New("goglfw: invalid parameter at Monitor.setVideoModeWin32")
|
2022-04-19 17:49:43 +02:00
|
|
|
case _DISP_CHANGE_FAILED:
|
2023-01-21 14:09:12 +01:00
|
|
|
return errors.New("goglfw: graphics mode failed at Monitor.setVideoModeWin32")
|
2022-04-19 17:49:43 +02:00
|
|
|
case _DISP_CHANGE_NOTUPDATED:
|
2023-01-21 14:09:12 +01:00
|
|
|
return errors.New("goglfw: failed to write to registry at Monitor.setVideoModeWin32")
|
2022-04-19 17:49:43 +02:00
|
|
|
case _DISP_CHANGE_RESTART:
|
2023-01-21 14:09:12 +01:00
|
|
|
return errors.New("goglfw: computer restart required at Monitor.setVideoModeWin32")
|
2022-04-19 17:49:43 +02:00
|
|
|
default:
|
2023-01-21 14:09:12 +01:00
|
|
|
return errors.New("goglfw: unknown error at Monitor.setVideoModeWin32")
|
2022-04-19 17:49:43 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Monitor) restoreVideoModeWin32() {
|
2023-02-07 19:05:46 +01:00
|
|
|
if m.platform.modeChanged {
|
|
|
|
_ChangeDisplaySettingsExW(m.platform.adapterName, nil, 0, _CDS_FULLSCREEN, nil)
|
|
|
|
m.platform.modeChanged = false
|
2022-04-19 17:49:43 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func getMonitorContentScaleWin32(handle _HMONITOR) (xscale, yscale float32, err error) {
|
|
|
|
var xdpi, ydpi uint32
|
|
|
|
|
2023-03-30 19:07:35 +02:00
|
|
|
if winver.IsWindows8Point1OrGreater() {
|
2022-04-19 17:49:43 +02:00
|
|
|
var err error
|
|
|
|
xdpi, ydpi, err = _GetDpiForMonitor(handle, _MDT_EFFECTIVE_DPI)
|
|
|
|
if err != nil {
|
|
|
|
return 0, 0, err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
dc, err := _GetDC(0)
|
|
|
|
if err != nil {
|
|
|
|
return 0, 0, err
|
|
|
|
}
|
|
|
|
defer _ReleaseDC(0, dc)
|
|
|
|
|
|
|
|
xdpi = uint32(_GetDeviceCaps(dc, _LOGPIXELSX))
|
|
|
|
ydpi = uint32(_GetDeviceCaps(dc, _LOGPIXELSY))
|
|
|
|
}
|
|
|
|
|
|
|
|
xscale = float32(xdpi) / _USER_DEFAULT_SCREEN_DPI
|
|
|
|
yscale = float32(ydpi) / _USER_DEFAULT_SCREEN_DPI
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Monitor) platformGetMonitorPos() (xpos, ypos int, ok bool) {
|
2022-05-29 12:13:31 +02:00
|
|
|
if microsoftgdk.IsXbox() {
|
2022-05-29 08:53:43 +02:00
|
|
|
return 0, 0, true
|
|
|
|
}
|
|
|
|
|
2023-02-07 19:05:46 +01:00
|
|
|
dm, ok := _EnumDisplaySettingsExW(m.platform.adapterName, _ENUM_CURRENT_SETTINGS, _EDS_ROTATEDMODE)
|
2022-04-19 17:49:43 +02:00
|
|
|
if !ok {
|
|
|
|
return 0, 0, false
|
|
|
|
}
|
|
|
|
return int(dm.dmPosition.x), int(dm.dmPosition.y), true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Monitor) platformGetMonitorContentScale() (xscale, yscale float32, err error) {
|
2022-05-29 12:13:31 +02:00
|
|
|
if microsoftgdk.IsXbox() {
|
2022-05-29 08:53:43 +02:00
|
|
|
return 1, 1, nil
|
|
|
|
}
|
|
|
|
|
2023-02-07 19:05:46 +01:00
|
|
|
return getMonitorContentScaleWin32(m.platform.handle)
|
2022-04-19 17:49:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Monitor) platformGetMonitorWorkarea() (xpos, ypos, width, height int) {
|
2022-05-29 12:13:31 +02:00
|
|
|
if microsoftgdk.IsXbox() {
|
|
|
|
w, h := microsoftgdk.MonitorResolution()
|
2022-05-29 08:53:43 +02:00
|
|
|
return 0, 0, w, h
|
|
|
|
}
|
|
|
|
|
2023-02-07 19:05:46 +01:00
|
|
|
mi, ok := _GetMonitorInfoW(m.platform.handle)
|
2022-04-19 17:49:43 +02:00
|
|
|
if !ok {
|
|
|
|
return 0, 0, 0, 0
|
|
|
|
}
|
|
|
|
xpos = int(mi.rcWork.left)
|
|
|
|
ypos = int(mi.rcWork.top)
|
|
|
|
width = int(mi.rcWork.right - mi.rcWork.left)
|
|
|
|
height = int(mi.rcWork.bottom - mi.rcWork.top)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Monitor) platformAppendVideoModes(monitors []*VidMode) ([]*VidMode, error) {
|
|
|
|
origLen := len(monitors)
|
|
|
|
loop:
|
|
|
|
for modeIndex := uint32(0); ; modeIndex++ {
|
2023-02-07 19:05:46 +01:00
|
|
|
dm, ok := _EnumDisplaySettingsW(m.platform.adapterName, modeIndex)
|
2022-04-19 17:49:43 +02:00
|
|
|
if !ok {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
// Skip modes with less than 15 BPP
|
|
|
|
if dm.dmBitsPerPel < 15 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
r, g, b := splitBPP(int(dm.dmBitsPerPel))
|
|
|
|
mode := &VidMode{
|
|
|
|
Width: int(dm.dmPelsWidth),
|
|
|
|
Height: int(dm.dmPelsHeight),
|
|
|
|
RefreshRate: int(dm.dmDisplayFrequency),
|
|
|
|
RedBits: r,
|
|
|
|
GreenBits: g,
|
|
|
|
BlueBits: b,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Skip duplicate modes
|
|
|
|
for _, m := range monitors[origLen:] {
|
|
|
|
if m.equals(mode) {
|
|
|
|
continue loop
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-07 19:05:46 +01:00
|
|
|
if m.platform.modesPruned {
|
2022-04-19 17:49:43 +02:00
|
|
|
// Skip modes not supported by the connected displays
|
2023-02-07 19:05:46 +01:00
|
|
|
if _ChangeDisplaySettingsExW(m.platform.adapterName, &dm, 0, _CDS_TEST, nil) != _DISP_CHANGE_SUCCESSFUL {
|
2022-04-19 17:49:43 +02:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
monitors = append(monitors, mode)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(monitors) == origLen {
|
|
|
|
// HACK: Report the current mode if no valid modes were found
|
|
|
|
monitors = append(monitors, m.platformGetVideoMode())
|
|
|
|
}
|
|
|
|
|
|
|
|
return monitors, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Monitor) platformGetVideoMode() *VidMode {
|
2022-05-29 12:13:31 +02:00
|
|
|
if microsoftgdk.IsXbox() {
|
2022-05-29 08:53:43 +02:00
|
|
|
return m.modes[0]
|
|
|
|
}
|
|
|
|
|
2023-02-07 19:05:46 +01:00
|
|
|
dm, _ := _EnumDisplaySettingsW(m.platform.adapterName, _ENUM_CURRENT_SETTINGS)
|
2022-04-19 17:49:43 +02:00
|
|
|
r, g, b := splitBPP(int(dm.dmBitsPerPel))
|
|
|
|
return &VidMode{
|
|
|
|
Width: int(dm.dmPelsWidth),
|
|
|
|
Height: int(dm.dmPelsHeight),
|
|
|
|
RefreshRate: int(dm.dmDisplayFrequency),
|
|
|
|
RedBits: r,
|
|
|
|
GreenBits: g,
|
|
|
|
BlueBits: b,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Monitor) in32Adapter() (string, error) {
|
|
|
|
if !_glfw.initialized {
|
|
|
|
return "", NotInitialized
|
|
|
|
}
|
2023-02-07 19:05:46 +01:00
|
|
|
return m.platform.adapterName, nil
|
2022-04-19 17:49:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Monitor) win32Monitor() (string, error) {
|
|
|
|
if !_glfw.initialized {
|
|
|
|
return "", NotInitialized
|
|
|
|
}
|
2023-02-07 19:05:46 +01:00
|
|
|
return m.platform.displayName, nil
|
2022-04-19 17:49:43 +02:00
|
|
|
}
|