// Package win32 封装 Fyne 未提供的 Windows 原生能力: // 显示器枚举、窗口置顶/移动、全局热键、DPI 查询。 package win32 import ( "fmt" "sync" "syscall" "unsafe" "golang.org/x/sys/windows" ) var ( user32 = windows.NewLazySystemDLL("user32.dll") shcore = windows.NewLazySystemDLL("shcore.dll") procFindWindowW = user32.NewProc("FindWindowW") procFindWindowExW = user32.NewProc("FindWindowExW") procGetWindowRect = user32.NewProc("GetWindowRect") procSetWindowPos = user32.NewProc("SetWindowPos") procGetCursorPos = user32.NewProc("GetCursorPos") procEnumDisplayMonitors = user32.NewProc("EnumDisplayMonitors") procGetMonitorInfoW = user32.NewProc("GetMonitorInfoW") procGetDpiForMonitor = shcore.NewProc("GetDpiForMonitor") ) const ( swpNoSize = 0x0001 swpNoMove = 0x0002 swpNoZOrder = 0x0004 swpNoActivate = 0x0010 swpShowWindow = 0x0040 hwndTopMost = ^uintptr(0) // (HWND)-1 mdtEffectiveDPI = 0 monitorInfoPrimary = 1 ) // Rect 是以物理像素表示的屏幕矩形。 type Rect struct { Left, Top, Right, Bottom int } // Width 返回矩形宽度。 func (r Rect) Width() int { return r.Right - r.Left } // Height 返回矩形高度。 func (r Rect) Height() int { return r.Bottom - r.Top } // Contains 判断点是否落在矩形内。 func (r Rect) Contains(x, y int) bool { return x >= r.Left && x < r.Right && y >= r.Top && y < r.Bottom } type nativeRect struct { Left, Top, Right, Bottom int32 } // Monitor 描述一台显示器。 type Monitor struct { Index int Rect Rect // 完整屏幕区域(物理像素) Work Rect // 去掉任务栏的可用区域 Primary bool Scale float64 // 系统缩放,例如 1.25 } type nativeMonitorInfo struct { CbSize uint32 RcMonitor nativeRect RcWork nativeRect DwFlags uint32 } type point struct { X, Y int32 } var ( enumMu sync.Mutex enumSink *[]Monitor enumCallback uintptr ) // Monitors 枚举当前所有显示器,坐标已换算为物理像素。 func Monitors() []Monitor { enumMu.Lock() defer enumMu.Unlock() var out []Monitor enumSink = &out if enumCallback == 0 { enumCallback = windows.NewCallback(enumProc) } procEnumDisplayMonitors.Call(0, 0, enumCallback, 0) enumSink = nil for i := range out { out[i].Index = i } return out } func enumProc(hMonitor, _, _, _ uintptr) uintptr { if enumSink == nil { return 1 } info := nativeMonitorInfo{CbSize: uint32(unsafe.Sizeof(nativeMonitorInfo{}))} if r, _, _ := procGetMonitorInfoW.Call(hMonitor, uintptr(unsafe.Pointer(&info))); r == 0 { return 1 } m := Monitor{ Rect: Rect{ Left: int(info.RcMonitor.Left), Top: int(info.RcMonitor.Top), Right: int(info.RcMonitor.Right), Bottom: int(info.RcMonitor.Bottom), }, Work: Rect{ Left: int(info.RcWork.Left), Top: int(info.RcWork.Top), Right: int(info.RcWork.Right), Bottom: int(info.RcWork.Bottom), }, Primary: info.DwFlags&monitorInfoPrimary != 0, Scale: 1, } var dpiX, dpiY uint32 if hr, _, _ := procGetDpiForMonitor.Call(hMonitor, mdtEffectiveDPI, uintptr(unsafe.Pointer(&dpiX)), uintptr(unsafe.Pointer(&dpiY))); hr == 0 && dpiX > 0 { m.Scale = float64(dpiX) / 96 } *enumSink = append(*enumSink, m) return 1 } // VirtualScreen 返回所有显示器的并集,作为虚拟桌面坐标参考。 func VirtualScreen() Rect { ms := Monitors() if len(ms) == 0 { return Rect{} } union := ms[0].Rect for _, m := range ms[1:] { if m.Rect.Left < union.Left { union.Left = m.Rect.Left } if m.Rect.Top < union.Top { union.Top = m.Rect.Top } if m.Rect.Right > union.Right { union.Right = m.Rect.Right } if m.Rect.Bottom > union.Bottom { union.Bottom = m.Rect.Bottom } } return union } // CursorPos 返回鼠标当前位置(物理像素)。 func CursorPos() (int, int) { var p point if r, _, _ := procGetCursorPos.Call(uintptr(unsafe.Pointer(&p))); r == 0 { return 0, 0 } return int(p.X), int(p.Y) } // MonitorAt 返回包含指定点的显示器,找不到时退化为第一台。 func MonitorAt(x, y int) (Monitor, bool) { ms := Monitors() for _, m := range ms { if m.Rect.Contains(x, y) { return m, true } } if len(ms) > 0 { return ms[0], false } return Monitor{}, false } // MonitorAtCursor 返回鼠标所在的显示器。 func MonitorAtCursor() (Monitor, bool) { x, y := CursorPos() return MonitorAt(x, y) } // FindWindowByTitle 按标题查找顶层窗口句柄。 func FindWindowByTitle(title string) (syscall.Handle, error) { p, err := windows.UTF16PtrFromString(title) if err != nil { return 0, err } r, _, e := procFindWindowW.Call(0, uintptr(unsafe.Pointer(p))) if r == 0 { return 0, fmt.Errorf("未找到标题为 %q 的窗口: %w", title, e) } return syscall.Handle(r), nil } // FindWindowByClass 按窗口类名查找顶层窗口句柄。 func FindWindowByClass(class string) (syscall.Handle, error) { p, err := windows.UTF16PtrFromString(class) if err != nil { return 0, err } r, _, e := procFindWindowExW.Call(0, 0, uintptr(unsafe.Pointer(p)), 0) if r == 0 { return 0, fmt.Errorf("未找到类名为 %q 的窗口: %w", class, e) } return syscall.Handle(r), nil } // WindowRect 返回窗口在屏幕上的物理像素矩形。 func WindowRect(hwnd syscall.Handle) (Rect, error) { var r nativeRect if ok, _, e := procGetWindowRect.Call(uintptr(hwnd), uintptr(unsafe.Pointer(&r))); ok == 0 { return Rect{}, fmt.Errorf("GetWindowRect 失败: %w", e) } return Rect{Left: int(r.Left), Top: int(r.Top), Right: int(r.Right), Bottom: int(r.Bottom)}, nil } // SetTopMost 让窗口保持置顶但不抢焦点。 func SetTopMost(hwnd syscall.Handle) error { if ok, _, e := procSetWindowPos.Call(uintptr(hwnd), hwndTopMost, 0, 0, 0, 0, swpNoMove|swpNoSize|swpNoActivate); ok == 0 { return fmt.Errorf("SetWindowPos(TOPMOST) 失败: %w", e) } return nil } // MoveTo 把窗口移动到指定物理矩形(不改变 Z 序)。 func MoveTo(hwnd syscall.Handle, r Rect) error { if ok, _, e := procSetWindowPos.Call(uintptr(hwnd), 0, uintptr(r.Left), uintptr(r.Top), uintptr(r.Width()), uintptr(r.Height()), swpNoZOrder|swpNoActivate|swpShowWindow); ok == 0 { return fmt.Errorf("SetWindowPos(MoveTo) 失败: %w", e) } return nil }