github.com/secoba/wails/v2@v2.6.4/internal/frontend/desktop/windows/screen.go (about)

     1  //go:build windows
     2  // +build windows
     3  
     4  package windows
     5  
     6  import (
     7  	"fmt"
     8  	"syscall"
     9  	"unsafe"
    10  
    11  	"github.com/pkg/errors"
    12  	"github.com/secoba/wails/v2/internal/frontend/desktop/windows/winc"
    13  	"github.com/secoba/wails/v2/internal/frontend/desktop/windows/winc/w32"
    14  )
    15  
    16  func MonitorsEqual(first w32.MONITORINFO, second w32.MONITORINFO) bool {
    17  	// Checks to make sure all the fields are the same.
    18  	// A cleaner way would be to check identity of devices. but I couldn't find a way of doing that using the win32 API
    19  	return first.DwFlags == second.DwFlags &&
    20  		first.RcMonitor.Top == second.RcMonitor.Top &&
    21  		first.RcMonitor.Bottom == second.RcMonitor.Bottom &&
    22  		first.RcMonitor.Right == second.RcMonitor.Right &&
    23  		first.RcMonitor.Left == second.RcMonitor.Left &&
    24  		first.RcWork.Top == second.RcWork.Top &&
    25  		first.RcWork.Bottom == second.RcWork.Bottom &&
    26  		first.RcWork.Right == second.RcWork.Right &&
    27  		first.RcWork.Left == second.RcWork.Left
    28  }
    29  
    30  func GetMonitorInfo(hMonitor w32.HMONITOR) (*w32.MONITORINFO, error) {
    31  	// Adapted from winc.utils.getMonitorInfo TODO: add this to win32
    32  	// See docs for
    33  	//https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmonitorinfoa
    34  
    35  	var info w32.MONITORINFO
    36  	info.CbSize = uint32(unsafe.Sizeof(info))
    37  	succeeded := w32.GetMonitorInfo(hMonitor, &info)
    38  	if !succeeded {
    39  		return &info, errors.New("Windows call to getMonitorInfo failed")
    40  	}
    41  	return &info, nil
    42  }
    43  
    44  func EnumProc(hMonitor w32.HMONITOR, hdcMonitor w32.HDC, lprcMonitor *w32.RECT, screenContainer *ScreenContainer) uintptr {
    45  	// adapted from https://stackoverflow.com/a/23492886/4188138
    46  
    47  	// see docs for the following pages to better understand this function
    48  	// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enumdisplaymonitors
    49  	// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nc-winuser-monitorenumproc
    50  	// https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-monitorinfo
    51  	// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-monitorfromwindow
    52  
    53  	ourMonitorData := Screen{}
    54  	currentMonHndl := w32.MonitorFromWindow(screenContainer.mainWinHandle, w32.MONITOR_DEFAULTTONEAREST)
    55  	currentMonInfo, currErr := GetMonitorInfo(currentMonHndl)
    56  
    57  	if currErr != nil {
    58  		screenContainer.errors = append(screenContainer.errors, currErr)
    59  		screenContainer.monitors = append(screenContainer.monitors, Screen{})
    60  		// not sure what the consequences of returning false are, so let's just return true and handle it ourselves
    61  		return w32.TRUE
    62  	}
    63  
    64  	monInfo, err := GetMonitorInfo(hMonitor)
    65  	if err != nil {
    66  		screenContainer.errors = append(screenContainer.errors, err)
    67  		screenContainer.monitors = append(screenContainer.monitors, Screen{})
    68  		return w32.TRUE
    69  	}
    70  
    71  	width := lprcMonitor.Right - lprcMonitor.Left
    72  	height := lprcMonitor.Bottom - lprcMonitor.Top
    73  	ourMonitorData.IsPrimary = monInfo.DwFlags&w32.MONITORINFOF_PRIMARY == 1
    74  	ourMonitorData.Height = int(height)
    75  	ourMonitorData.Width = int(width)
    76  	ourMonitorData.IsCurrent = MonitorsEqual(*currentMonInfo, *monInfo)
    77  
    78  	ourMonitorData.PhysicalSize.Width = int(width)
    79  	ourMonitorData.PhysicalSize.Height = int(height)
    80  
    81  	var dpiX, dpiY uint
    82  	w32.GetDPIForMonitor(hMonitor, w32.MDT_EFFECTIVE_DPI, &dpiX, &dpiY)
    83  	if dpiX == 0 || dpiY == 0 {
    84  		screenContainer.errors = append(screenContainer.errors, fmt.Errorf("unable to get DPI for screen"))
    85  		screenContainer.monitors = append(screenContainer.monitors, Screen{})
    86  		return w32.TRUE
    87  	}
    88  	ourMonitorData.Size.Width = winc.ScaleToDefaultDPI(ourMonitorData.PhysicalSize.Width, dpiX)
    89  	ourMonitorData.Size.Height = winc.ScaleToDefaultDPI(ourMonitorData.PhysicalSize.Height, dpiY)
    90  
    91  	// the reason we need a container is that we have don't know how many times this function will be called
    92  	// this "append" call could potentially do an allocation and rewrite the pointer to monitors. So we save the pointer in screenContainer.monitors
    93  	// and retrieve the values after all EnumProc calls
    94  	// If EnumProc is multi-threaded, this could be problematic. Although, I don't think it is.
    95  	screenContainer.monitors = append(screenContainer.monitors, ourMonitorData)
    96  	// let's keep screenContainer.errors the same size as screenContainer.monitors in case we want to match them up later if necessary
    97  	screenContainer.errors = append(screenContainer.errors, nil)
    98  	return w32.TRUE
    99  }
   100  
   101  type ScreenContainer struct {
   102  	monitors      []Screen
   103  	errors        []error
   104  	mainWinHandle w32.HWND
   105  }
   106  
   107  func GetAllScreens(mainWinHandle w32.HWND) ([]Screen, error) {
   108  	// TODO fix hack of container sharing by having a proper data sharing mechanism between windows and the runtime
   109  	monitorContainer := ScreenContainer{mainWinHandle: mainWinHandle}
   110  	returnErr := error(nil)
   111  	errorStrings := []string{}
   112  
   113  	dc := w32.GetDC(0)
   114  	defer w32.ReleaseDC(0, dc)
   115  	succeeded := w32.EnumDisplayMonitors(dc, nil, syscall.NewCallback(EnumProc), unsafe.Pointer(&monitorContainer))
   116  	if !succeeded {
   117  		return monitorContainer.monitors, errors.New("Windows call to EnumDisplayMonitors failed")
   118  	}
   119  	for idx, err := range monitorContainer.errors {
   120  		if err != nil {
   121  			errorStrings = append(errorStrings, fmt.Sprintf("Error from monitor #%v, %v", idx+1, err))
   122  		}
   123  	}
   124  
   125  	if len(errorStrings) > 0 {
   126  		returnErr = fmt.Errorf("%v errors encountered: %v", len(errorStrings), errorStrings)
   127  	}
   128  	return monitorContainer.monitors, returnErr
   129  }