使用Golang获取Windows系统的空闲时间(使用GetLastInputInfo或类似方法)

12
有没有关于使用Go获取Windows系统空闲时间的示例或方法?我一直在查看Golang网站上的文档,但我认为我不知道如何访问(和使用)API来获取包括空闲时间在内的系统信息。
3个回答

26

Go语言的官网是默认展示Linux标准库文档的,而你需要获取godoc并自行运行:

go get golang.org/x/tools/cmd/godoc
godoc --http=:6060

然后在您的Web浏览器中打开http://127.0.0.1:6060/

值得注意的是syscall包,它提供了访问DLL中函数的设施,包括UTF-16助手和回调生成函数。

快速递归搜索Go树可知它没有特别的GetLastInputInfo() API,所以除非我错过了什么,否则您应该能够直接从DLL中调用该函数:

user32 := syscall.MustLoadDLL("user32.dll") // or NewLazyDLL() to defer loading
getLastInputInfo := user32.MustFindProc("GetLastInputInfo") // or NewProc() if you used NewLazyDLL()
// or you can handle the errors in the above if you want to provide some alternative
r1, _, err := getLastInputInfo.Call(uintptr(arg))
// err will always be non-nil; you need to check r1 (the return value)
if r1 == 0 { // in this case
    panic("error getting last input info: " + err.Error())
}

您的情况涉及一种结构。据我所知,您可以仅按相同顺序将字段保持平面重建该结构,但必须将原始结构中的任何int字段转换为int32,否则在64位Windows上会出现问题。请参阅MSDN上的Windows数据类型页面以获取适当的类型等效项。在您的情况下,应该是:

var lastInputInfo struct {
    cbSize uint32
    dwTime uint32
}

由于在Windows API中,许多结构体(包括这个)都有一个需要用结构体大小进行初始化的cbSize字段,因此我们也必须这样做:
lastInputInfo.cbSize = uint32(unsafe.Sizeof(lastInputInfo))

现在我们只需要将指向那个lastInputInfo变量的指针传递给函数:

r1, _, err := getLastInputInfo.Call(
    uintptr(unsafe.Pointer(&lastInputInfo)))

只需记住导入 syscallunsafe。所有传递给 DLL/LazyDLL.Call() 的参数都是 uintptr 类型,返回值 r1 也是如此。在 Windows 上从不使用返回的 _(这与使用的 ABI 有关)。由于我已经讲解了大多数你需要了解的关于在 Go 中使用 Windows API 的内容,这些内容无法从阅读 syscall 文档中获得。我还要说的是,如果一个函数同时有 ANSI 和 Unicode 版本,则应该使用 Unicode 版本(带有 W 后缀),并使用包 syscall 中的 UTF-16 转换函数,以获得最佳结果。我认为这就是所有人(包括您)在编写 Go 程序时使用 Windows API 所需的所有信息。

1
我甚至不使用Windows,但这非常有启发性。 - OneOfOne
当我尝试取样时,r1一直返回1。这个结果是说自闲置以来已经过了1个计时器滴答,还是只是简单地返回一个布尔值true?我应该得到dwTime吗?(感谢@andlabs的详细解释。我认为我走在正确的道路上,只是缺少对我正在提取的信息的关键理解。) - Bart Silverstrim
1
@BartSilverstrim(回复晚了很抱歉),在这种情况下,r1是从GetLastInputInfo()返回的返回值,根据MSDN的说明,它表示函数是否成功。您需要检查r1,如果非零,则在调用后从结构中获取dwTime,这是您实际想要的值。(请参见_Out_注释,该注释指示函数在那里返回值。)如果r1 == 0,则err将包含错误信息(即为您调用了GetLastError())。 - andlabs
1
@andlabs 感谢您的澄清!我一直在开发这个应用程序,虽然在获取dwTime的实际计数之前我没有想到要检查r1的结果,但我进行了实验并得到了结果。您非常有帮助,再次感谢!我将添加一个注释以便稍后回来进行彻底检查。 - Bart Silverstrim

3

关于andlabs的答案,这是一个可供使用的示例:

import (
    "time"
    "unsafe"
    "syscall"
    "fmt"
)

var (
    user32            = syscall.MustLoadDLL("user32.dll")
    kernel32          = syscall.MustLoadDLL("kernel32.dll")
    getLastInputInfo  = user32.MustFindProc("GetLastInputInfo")
    getTickCount      = kernel32.MustFindProc("GetTickCount")
    lastInputInfo struct {
        cbSize uint32
        dwTime uint32
    }
)

func IdleTime() time.Duration {
    lastInputInfo.cbSize = uint32(unsafe.Sizeof(lastInputInfo))
    currentTickCount, _, _ := getTickCount.Call()
    r1, _, err := getLastInputInfo.Call(uintptr(unsafe.Pointer(&lastInputInfo)))
    if r1 == 0 {
            panic("error getting last input info: " + err.Error())
    }
    return time.Duration((uint32(currentTickCount) - lastInputInfo.dwTime)) * time.Millisecond
}

func main() {
    t := time.NewTicker(1 * time.Second)
    for range t.C {
        fmt.Println(IdleTime())
    }
}

这是打印空闲时间的代码,每秒钟打印一次。尝试运行并不要触摸鼠标/键盘。


0
如果你愿意使用现有的软件包而不是自己实现,可以参考lextoumbourou的以下存储库。

https://github.com/lextoumbourou/idle

用法示例:

package main

import (
    "fmt"

    "github.com/lextoumbourou/idle"
)

func main() {
    idleTime, _ := idle.Get()
    fmt.Println(idleTime)
}


打印当前用户的空闲时间

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接