在Go语言中获取Windows窗口的几何信息

huangapple go评论92阅读模式
英文:

Getting window geometry in Go for Windows

问题

我想用Go语言创建一个工具,可以调整屏幕上多个窗口的大小。举个例子,假设我想找到我的Firefox窗口和Atom(文本编辑器)窗口,并将它们放置在屏幕的一半位置(Firefox在左侧,Atom在右侧)。

到目前为止,我已经意识到我需要使用Windows API来实现这个功能。我创建了一个方法,可以获取所有窗口的句柄和标题,但是我在处理几何信息时遇到了困难。我了解到API调用GetWindowRect可以帮助我,但是我如何从指向矩形的指针中获取信息呢?

后续问题1:我还能获取窗口的哪些其他信息?
后续问题2:如何调整窗口的大小,使其正好占据屏幕的一半?我猜我需要另一个调用来获取监视器的尺寸。

我目前的代码如下。主程序找到所有句柄,并显示标题中包含“Atom”的窗口。Windows包含访问Windows API的代码。

我的当前结果是我得到了两个Atom的句柄(为什么不只有一个?)。我猜我还需要学习更多关于Windows API的知识。有没有好的摘要来理解基础知识?

main.go:

package main

import (
    "resizer/windows"
    "fmt"
    "log"
    "strings"
)

func main() {
    const title = "Atom"    
    m := windows.GetAllWindows()
    fmt.Printf("窗口映射:\n")
    for handle := range m {
        if strings.Contains(m[handle].Title(), title) {
            fmt.Printf("'%v'\n", m[handle])
        }
    }
}

windows.go:

package windows

import (
    "fmt"
    "log"
    "syscall"
    "unsafe"
)

var (
    user32             = syscall.MustLoadDLL("user32.dll")
    procEnumWindows    = user32.MustFindProc("EnumWindows")
    procGetWindowTextW = user32.MustFindProc("GetWindowTextW")
)

// Window表示Windows操作系统中打开的任何窗口
type Window struct {
    handle syscall.Handle
    title  string
}

// Title返回窗口的标题
func (w Window) Title() string {
    return w.title
}

// GetAllWindows查找所有当前打开的窗口
func GetAllWindows() map[syscall.Handle]Window {
    m := make(map[syscall.Handle]Window)
    cb := syscall.NewCallback(func(h syscall.Handle, p uintptr) uintptr {
        bytes := make([]uint16, 200)
        _, err := GetWindowText(h, &bytes[0], int32(len(bytes)))
        title := "|||找不到标题|||"
        if err == nil {
            title = syscall.UTF16ToString(bytes)
        }
        m[h] = Window{h, title}
        return 1 // 继续枚举
    })
    EnumWindows(cb, 0)
    return m
}

// EnumWindows循环遍历所有窗口,并在每个窗口上调用回调函数
func EnumWindows(enumFunc uintptr, lparam uintptr) (err error) {
    r1, _, e1 := syscall.Syscall(procEnumWindows.Addr(), 2, uintptr(enumFunc), uintptr(lparam), 0)
    if r1 == 0 {
        if e1 != 0 {
            err = error(e1)
        } else {
            err = syscall.EINVAL
        }
    }
    return
}

// GetWindowText根据给定的句柄获取窗口的标题
func GetWindowText(hwnd syscall.Handle, str *uint16, maxCount int32) (len int32, err error) {
    r0, _, e1 := syscall.Syscall(procGetWindowTextW.Addr(), 3, uintptr(hwnd), uintptr(unsafe.Pointer(str)), uintptr(maxCount))
    len = int32(r0)
    if len == 0 {
        if e1 != 0 {
            err = error(e1)
        } else {
            err = syscall.EINVAL
        }
    }
    return
}
英文:

I want to create a tool with Go that lets me resize multiple windows on my screen. As an example lets assume that I want to find my Firefox window and my Atom (text editor) window and place them, so that they take up exactly half of my screen (FF left, Atom right).

So far I realized, that I need to use the Windows API for that. I created a method that gives me all handles and the titles of all windows, but I'm struggling with geometry information. I understand that the api call GetWindowRect will help, but how can I get the information out of a pointer to a rect?

Follow up question 1: what other information can I get about the windows?
Follow up question 2: How do I resize the window so that it takes exactly half my screen size? I guess, I need another call to get the monitor dimensions.

What I have so far is the code below. The main program finds all handles and displays those containing 'Atom' in the title. The windows package contains the code accessing the windows API.

My current result is that I get 2 handles for atom (why not just 1?). I guess, I have to learn more about the Windows API, too. Are there good summaries to understand the basics?

main.go:

package main

import (
	"resizer/windows"
	"fmt"
	"log"
	"strings"
)

func main() {
	const title = "Atom"    
	m := windows.GetAllWindows()
	fmt.Printf("Map of windows: \n")
	for handle := range m {
		if strings.Contains(m[handle].Title(), title) {
			fmt.Printf("'%v'\n", m[handle])
		}
	}
}

windows.go:

package windows

import (
	"fmt"
	"log"
	"syscall"
	"unsafe"
)

var (
	user32             = syscall.MustLoadDLL("user32.dll")
	procEnumWindows    = user32.MustFindProc("EnumWindows")
	procGetWindowTextW = user32.MustFindProc("GetWindowTextW")
)

// Window represents any Window that is opened in the Windows OS
type Window struct {
	handle syscall.Handle
	title  string
}

// Title returns the title of the window
func (w Window) Title() string {
	return w.title
}

// GetAllWindows finds all currently opened windows
func GetAllWindows() map[syscall.Handle]Window {
	m := make(map[syscall.Handle]Window)
	cb := syscall.NewCallback(func(h syscall.Handle, p uintptr) uintptr {
		bytes := make([]uint16, 200)
		_, err := GetWindowText(h, &bytes[0], int32(len(bytes)))
		title := "||| no title found |||"
		if err == nil {
			title = syscall.UTF16ToString(bytes)
		}
		m[h] = Window{h, title}
		return 1 // continue enumeration
	})
	EnumWindows(cb, 0)
	return m
}

// EnumWindows loops through all windows and calls a callback function on each
func EnumWindows(enumFunc uintptr, lparam uintptr) (err error) {
	r1, _, e1 := syscall.Syscall(procEnumWindows.Addr(), 2, uintptr(enumFunc), uintptr(lparam), 0)
	if r1 == 0 {
		if e1 != 0 {
			err = error(e1)
		} else {
			err = syscall.EINVAL
		}
	}
	return
}

// GetWindowText gets the title of a Window given by a certain handle
func GetWindowText(hwnd syscall.Handle, str *uint16, maxCount int32) (len int32, err error) {
	r0, _, e1 := syscall.Syscall(procGetWindowTextW.Addr(), 3, uintptr(hwnd), uintptr(unsafe.Pointer(str)), uintptr(maxCount))
	len = int32(r0)
	if len == 0 {
		if e1 != 0 {
			err = error(e1)
		} else {
			err = syscall.EINVAL
		}
	}
	return
}

答案1

得分: 1

GetWindowRect()函数将窗口的几何信息写入你传递指针的RECT结构中。它的操作方式与你已经使用的GetWindowText()函数相同,不同之处在于你需要自己提供RECT结构。

你应该可以直接复制该结构。如果要替换数据类型,请参考这个页面RECT的定义中指定了所有字段都是LONG类型,该页面说明LONG是“32位有符号整数”。因此,以下定义应该足够:

type RECT struct {
    left   int32 // 如果要导出该类型,则可以使用 Left、Top 等命名
    top    int32
    right  int32
    bottom int32
}

(可能与本问题无关,但值得指出的是,RECT的操作方式与image.Rectangle相同,其中lefttop对应于Minrightbottom对应于Max。它们并不完全相同,因为image.Rectangle使用的是int类型,所以如果你想使用image的几何函数来操作矩形而不是GDI的函数,你可能需要考虑提供转换函数。)

英文:

GetWindowRect() writes the geometry to the RECT structure you pass the pointer to in. It operates exactly like the GetWindowText() call you already have; the difference is you have to provide the RECT structure yourself.

You should be able to just get away with copying the structure verbatim. To substitute data types, use this page. The definition for RECT says all the fields are LONG, which that page says is "[a] 32-bit signed integer". So this should suffice:

type RECT struct {
    left   int32 // or Left, Top, etc. if this type is to be exported
    top    int32
    right  int32
    bottom int32
}

(Most likely irrelevant, but it's worth pointing out that RECT operates identically to image.Rectangle, with left and top being Min and right and bottom being Max. They are not identical because image.Rectangle uses int, so you may want to consider providing conversion functions if you want to use image's geometry functions to manipulate rectangles instead of GDI's.)

huangapple
  • 本文由 发表于 2017年7月3日 01:11:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/44873287.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定