Using Golang to get Windows idle time (GetLastInputInfo or similar)
Asked Answered
U

3

12

Is there an example or method of getting a Windows system's idle time using Go?
I've been looking at the documentation at the Golang site but I think I'm missing how to access (and use) the API to get system information including the idle time.

Unsuccessful answered 8/4, 2014 at 22:29 Comment(0)
M
26

Go's website is hardcoded to show the documentation for the standard library packages on Linux. You will need to get godoc and run it yourself:

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

then open http://127.0.0.1:6060/ in your web browser.

Of note is package syscall, which provides facilities for accessing functions in DLLs, including UTF-16 helpers and callback generation functions.

Doing a quick recursive search of the Go tree says it doesn't have an API for GetLastInputInfo() in particular, so unless I'm missing something, you should be able to call that function from the DLL directly:

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())
}

Your case involves a structure. As far as I know, you can just recreate the structure flat (keeping fields in the same order), but you must convert any int fields in the original to int32, otherwise things will break on 64-bit Windows. Consult the Windows Data Types page on MSDN for the appropriate type equivalents. In your case, this would be

var lastInputInfo struct {
    cbSize uint32
    dwTime uint32
}

Because this (like so many structs in the Windows API) has a cbSize field that requires you to initialize it with the size of the struct, we must do so too:

lastInputInfo.cbSize = uint32(unsafe.Sizeof(lastInputInfo))

Now we just need to pass a pointer to that lastInputInfo variable to the function:

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

and just remember to import syscall and unsafe.

All args to DLL/LazyDLL.Call() are uintptr, as is the r1 return. The _ return is never used on Windows (it has to do with the ABI used).


Since I went over most of what you need to know to use the Windows API in Go that you can't gather from reading the syscall docs, I will also say (and this is irrelevant to the above question) that if a function has both ANSI and Unicode versions, you should use the Unicode versions (W suffix) and the UTF-16 conversion functions in package syscall for best results.

I think that's all the info you (or anyone, for that matter) will need to use the Windows API in Go programs.

Muchness answered 8/4, 2014 at 23:54 Comment(4)
I don't even use windows but this was very informative.Beanie
When I try the sampling, I keep getting back 1 for r1. Is that result saying that it's been 1 timer tick since idle, or is it simply returning a boolean true? Should I be getting the dwTime? (Thank you for the informative explanation, @andlabs. I think I'm on the right path, just missing some key understanding of what information I'm pulling.)Unsuccessful
@BartSilverstrim (sorry for the delay in responding) yes, r1 in this case is the return value from GetLastInputInfo(), which according to MSDN represents whether the function succeeded or not. You'll need to check r1, and if nonzero, get dwTime out of your structure after the call, which is the value you actually want. (See also the _Out_ annotation, which indicates that the function returns values there.) If r1 == 0, err will contain the error information (that is, GetLastError() is called for you).Muchness
@Muchness Thanks for the clarification! I've been working on the application beyond that and while I didn't think to check r1 for the result before getting the actual tick count from dwTime, I did experiment and get results from that. You've been super helpful and thank you again! I'll have to add a note to go back and add that check for thoroughness later.Unsuccessful
F
3

Regarding for answer from andlabs. This is ready for use example:

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())
    }
}

This is code print idle time every second. Try run and don't touch mouse/keyboard

Fitment answered 9/11, 2018 at 16:24 Comment(0)
C
0

If you are willing to use an existing package instead of implementing your own, see the following repo by lextoumbourou:

https://github.com/lextoumbourou/idle

usage example:

package main

import (
    "fmt"

    "github.com/lextoumbourou/idle"
)

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

prints the current user's idle time

Curitiba answered 18/11, 2023 at 18:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.