Capture the screen in Go? - go

Is there is a cross-platform way to capture the screen in Google's Go? Or any way for that matter, but cross platform would be preferred.

Now there is:
https://github.com/vova616/screenshot
go get github.com/vova616/screenshot
Example:
package main
import "github.com/vova616/screenshot"
func main() {
img, err := screenshot.CaptureScreen() // *image.RGBA
myImg := image.Image(img) // can cast to image.Image, but not necessary
}
If you need macOS support as well (until the pull request is merged), get:
https://github.com/kesarion/screenshot

Unfortunately, there is no library to do this. There are a couple of bindings for magickwand (C programming language and the ImageMagick image processing libraries), see http://go-lang.cat-v.org/library-bindings but these are incomplete and do not have the screen capture feature.
Meanwhile as GeertJohan suggested, you can use os.exec to run an external program and capture the screen (see sample code below). For example, you can use import command from imagemagick to capture screen (should work on a platform that can run imagemagick)
package main
import (
"bytes"
"fmt"
"log"
"os/exec"
)
func main() {
var buf bytes.Buffer
path, err := exec.LookPath("import")
if err != nil {
log.Fatal("import not installed !")
}
fmt.Printf("import is available at %s\n", path)
cmd := exec.Command("import", "-window", "root", "root.png")
cmd.Stdout = &buf
cmd.Stderr = &buf
err = cmd.Run()
if err != nil {
panic(err)
}
fmt.Println(buf.String())
}

I don't know of any cross-platform library, but you can do this with the xgbutil library when an X server is present. You can see an example of how to capture a screenshot here.
If you wanted to get this working on Mac/Windows systems, I'd probably start by examining the source for go.wde, which includes backends for Windows and Mac. I doubt you'll directly find code to capture a screenshot in there, but it might give you some hints or a path to follow.

There is no cross-platform way to capture the screen in Google's Go, because capturing screen relies on a specific API of underlying operating systems. But there are libraries for Go that do this.
For example https://github.com/vova616/screenshot

This library seems to meet your needs:
https://godoc.org/github.com/kbinani/screenshot
captures screen-shot image as image.RGBA. Mac, Windows, Linux, FreeBSD, OpenBSD, NetBSD, and Solaris are supported.
func Capture(x, y, width, height int) (*image.RGBA, error)
func CaptureDisplay(displayIndex int) (*image.RGBA, error)
func CaptureRect(rect image.Rectangle) (*image.RGBA, error)
func GetDisplayBounds(displayIndex int) image.Rectangle
func NumActiveDisplays() int

I cannot find a library to do this. Stable cross-platform screen-capturing requires a lot of work. Screen capturing requires interfacing with the operating systems' display manager/server or frame-buffer, which is different for a lot of operating systems and Linux distributions. You would have to write interfaces for each OS API (or wrap the libraries that provide the functionality), and then abstract all the different methods in a single package so it works cross-platform.
Another way to do this would be to run a existing screen capture application (command-line) to do the screen-capture work for you, including saving to a file. Then read the file in your go application. To make a Go application run a third-party application, use the os/exec package, it is in the standard library. For Linux you might use fbgrab to save the frame-buffer to a png file.

It is. It's a 2-step process:
Study https://github.com/ShareX/ShareX/tree/master/ShareX.ScreenCaptureLib to see which win32 API calls to make to capture the screen/window
Translate that logic to Go. You can use one of few existing win32 api Go bindings (e.g. https://github.com/AllenDang/w32). If they're missing needed functionality, you can add more wrappers.

For a native Windows solution, there is an example in C from the official Windows docs:
https://learn.microsoft.com/en-gb/windows/win32/gdi/capturing-an-image
Now, from this example, this is the code using Windigo library:
package main
import (
"runtime"
"unsafe"
"github.com/rodrigocfd/windigo/win"
"github.com/rodrigocfd/windigo/win/co"
)
func main() {
runtime.LockOSThread()
cxScreen := win.GetSystemMetrics(co.SM_CXSCREEN)
cyScreen := win.GetSystemMetrics(co.SM_CYSCREEN)
hdcScreen := win.HWND(0).GetDC()
defer win.HWND(0).ReleaseDC(hdcScreen)
hBmp := hdcScreen.CreateCompatibleBitmap(cxScreen, cyScreen)
defer hBmp.DeleteObject()
hdcMem := hdcScreen.CreateCompatibleDC()
defer hdcMem.DeleteDC()
hBmpOld := hdcMem.SelectObjectBitmap(hBmp)
defer hdcMem.SelectObjectBitmap(hBmpOld)
hdcMem.BitBlt(
win.POINT{X: 0, Y: 0},
win.SIZE{Cx: cxScreen, Cy: cyScreen},
hdcScreen,
win.POINT{X: 0, Y: 0},
co.ROP_SRCCOPY,
)
bi := win.BITMAPINFO{
BmiHeader: win.BITMAPINFOHEADER{
BiWidth: cxScreen,
BiHeight: cyScreen,
BiPlanes: 1,
BiBitCount: 32,
BiCompression: co.BI_RGB,
},
}
bi.BmiHeader.SetBiSize()
bmpObj := win.BITMAP{}
hBmp.GetObject(&bmpObj)
bmpSize := bmpObj.CalcBitmapSize(bi.BmiHeader.BiBitCount)
rawMem := win.GlobalAlloc(co.GMEM_FIXED|co.GMEM_ZEROINIT, bmpSize)
defer rawMem.GlobalFree()
bmpSlice := rawMem.GlobalLock(int(bmpSize))
defer rawMem.GlobalUnlock()
hdcScreen.GetDIBits(hBmp, 0, int(cyScreen), bmpSlice, &bi, co.DIB_RGB_COLORS)
bfh := win.BITMAPFILEHEADER{}
bfh.SetBfType()
bfh.SetBfOffBits(uint32(unsafe.Sizeof(bfh) + unsafe.Sizeof(bi.BmiHeader)))
bfh.SetBfSize(bfh.BfOffBits() + uint32(bmpSize))
fo, _ := win.FileOpen("C:\\users\\rodrigo\\desktop\\a.bmp", co.FILE_OPEN_RW_OPEN_OR_CREATE)
defer fo.Close()
fo.Write(bfh.Serialize())
fo.Write(bi.BmiHeader.Serialize())
fo.Write(bmpSlice)
println("Done")
}

Related

What is the idiomatic way to read urls with a file scheme as filenames for ReadFile?

Is there an idiomatic way to read a file from the system starting from a (file scheme) url and not a path?
I tried this first:
fileUrlStr := "file:///path/to/file.json"
jsonBuffer, _ := ioutil.ReadFile(fileUrlStr)
This is my current (mostly working version) but I'm concerned there are some gotchas that I'm missing, so I'm hoping there's a more tried and true way to do it:
fileUrlStr := "file:///path/to/file.json"
fileUrl, _ := url.Parse(fileUrlStr)
jsonBuffer, _ := ioutil.ReadFile(fileUrl.Path)
(Bonus if I can support both file:///Users/jdoe/temp.json and file:///c:/WINDOWS/clock.json without having to add code-paths accounting for them)
Using net/url, the solution that you were using, is the correct one.
It's properly deals with hostnames and paths across platforms and also gives you a chance to check the url scheme is the file scheme.
package main
import (
"fmt"
"net/url"
)
func main() {
for _, path := range []string{
"file:///path/to/file.json",
"file:///c:/WINDOWS/clock.json",
"file://localhost/path/to/file.json",
"file://localhost/c:/WINDOWS/clock.avi",
// A case that you probably don't need to handle given the rarity,
// but is a known legacy win32 issue when translating \\remotehost\share\dir\file.txt
"file:////remotehost/share/dir/file.txt",
} {
u, _ := url.ParseRequestURI(path)
fmt.Printf("url:%v\nscheme:%v host:%v Path:%v\n\n", u, u.Scheme, u.Host, u.Path)
}
}

How to load image resource from windows `syscall` in golang?

I am writing a golang program using go-bindata to embed the image resources, and use the Asset(string) ([]byte, error) function to access resources. But my existing library codes go like this:
func NewIconFromFile(filePath string) (uintptr, error) {
absFilePath, err := filepath.Abs(filePath)
if err != nil {
return 0, err
}
hicon, _, _ := LoadImage.Call(
0,
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(absFilePath))),
IMAGE_ICON,
0,
0,
LR_DEFAULTSIZE|LR_LOADFROMFILE)
if hicon == 0 {
return 0, errors.New("load image failed: " + filePath)
}
return hicon, nil
}
How can I rewrite this functions to:
func NewIconFromRawBytes(imgBytes []byte) (uintptr, error)
so it can support for loading images from []byte ? Any helps? thanks.
Edit: There is a similar c++ version question, how can I port it to golang.
LoadImage() deals with Windows resources, which are built into Windows executables directly. go-bindata doesn't seem to deal in these, and doing this with Go directly isn't trivial.
If you want to be able to write a NewIconFromRawBytes() that creates an HICON from memory, you'll need to use the confusingly-named CreateIconFromResourceEx() function. If you do that, you may want to keep the info in the answer here in mind.
If, however, this is an image instead of an icon and you want an HBITMAP out of it, you have a bit more work to do involving the CreateDIBSection() function. The answer here shows what to do, though understanding it may be a bit harder. Of important note is that CreateDIBSection() allocates the image memory for you, so you'll have to copy it from Go to the memory location provided.
Side note: if you have a *image.RGBA or *image.NRGBA, you'll need to flip the bytes around if you want to shove that into an HBITMAP, as Windows expects the bytes in BGRA order, not RGBA order.

Golang retrieve application uptime

I'm trying to retrieve the current uptime of my Go application.
I've seen there's a package syscall which provides a type Sysinfo_t and a method Sysinfo(*Sysinfo_t) which apparently allows you to retrieve the Uptime (since it's a field of the Sysinfo_t struct)
What I've done so far is:
sysi := &syscall.Sysinfo_t{}
if err := syscall.Sysinfo(sysi); err != nil {
return http.StatusInternalServerError, nil
}
The problem is that at compile time I get this:
/path/to/file/res_system.go:43: undefined: syscall.Sysinfo_t
/path/to/file/res_system.go:45: undefined: syscall.Sysinfo
I've searched a bit and apparently that method and type are available only on Linux and I need the application to run both on Linux and OsX (which I'm currently using).
Is there a cross-compatible way to retrieve the application uptime?
NOTE: I'd rather not use any third party libraries (unless they're absolutely necessary)
Simple way to get uptime is to store service start time:
https://play.golang.org/p/by_nkvhzqD
package main
import (
"fmt"
"time"
)
var startTime time.Time
func uptime() time.Duration {
return time.Since(startTime)
}
func init() {
startTime = time.Now()
}
func main() {
fmt.Println("started")
time.Sleep(time.Second * 1)
fmt.Printf("uptime %s\n", uptime())
time.Sleep(time.Second * 5)
fmt.Printf("uptime %s\n", uptime())
}
You should use Since function from time package.
create time value when application start:
startTime := time.Now()
then ask whenever you want:
uptime := time.Since(startTime)
Package syscall was frozen on Go 1.4.
NOTE: This package is locked down. Code outside the standard Go repository should be migrated to use the corresponding package in the golang.org/x/sys repository. That is also where updates required by new systems or versions should be applied. See https://golang.org/s/go1.4-syscall for more information.
Use Sysinfo from golang.org/x/sys it should support this in a cross-platform way, at least on Unix.
The unix package in Go Standard Library go1.19.4 on macOS 13.1 Darwin xnu can now determine process start time using unix.SysctlKinfoProc
I have an open source Go library doing this here: https://github.com/haraldrudell/parl/blob/main/mains/process-start.go
ie.
import "github.com/haraldrudell/parl/mains"
println(mains.ProcessStartTime())
unix.SysctlKinfoProc uses macOS libSystem ie. it is supported by Apple, Inc. and uses direct kernel calls and no dumbities
Code is basically:
if unixKinfoProc, err = unix.SysctlKinfoProc(kernProcPid, os.Getpid()); perrors.Is(&err, "unix.SysctlKinfoProc: %T %+[1]v", err) {
panic(err)
}
var unixTimeval unix.Timeval = unixKinfoProc.Proc.P_starttime
sec, nsec := unixTimeval.Unix()
createTime = time.Unix(sec, nsec)
Difficulties
import "syscall" has been starved on most of its functionality which has been extracted to platform specific code in import "golang.org/x/sys/unix" and import "golang.org/x/sys/windows".
macOS GOOS==Darwin sorts under unix. The code in unix and windows is platform-specific, ie. if windows is imported on unix, the result is
error while importing golang.org/x/sys/windows: build constraints exclude all Go files in …
This means the program has to have a portable layer defining a portable function name, and that function is implemented for each supported platform like _darwin.go _linux.go and _windows.go which has to be tested on the real operating system.
The alternative is to use a third-party package where portability is already implemented. What you do then is to browse to Go Package search and pick a well-written candidate.
Solution
I browsed to Go Package search for Sysinfo: https://pkg.go.dev/search?q=sysinfo
Top result is gosysinfo "github.com/elastic/go-sysinfo". This package is awkwardly written as can be seen by a hyphen in its name and a peculiar package structure. It works, and the code goes like:
import (
gosysinfo "github.com/elastic/go-sysinfo"
"github.com/elastic/go-sysinfo/types"
"github.com/haraldrudell/parl"
)
func goSysinfo() {
var process types.Process
var err error
if process, err = gosysinfo.Self(); err != nil {
panic(parl.Errorf("go-sysinfo.Self: %w", err))
}
var processInfo types.ProcessInfo
if processInfo, err = process.Info(); err != nil {
panic(parl.Errorf("go-sysinfo.Info: %w", err))
}
startTime := processInfo.StartTime
fmt.Printf("Process start time: %s\n", startTime.Format(parl.Rfc3339s))
}
→
Process start time: 2022-03-22 10:15:05-07:00

Making a full screen Terminal application with Go

I'm trying to build a full screen terminal application. I'm using Go as my language of choice. I've figured out how to read from os.Stdin, but I'm unclear on how to clear the terminal window and manipulate the cursor position. I also want to capture the terminal input without it being printed (echoed back).
My questions are:
How can I effectively clear and print to the terminal with column/row coordinates?
How do I stop the terminal from printing keys pressed
My intent:
I want to create a full screen terminal application that renders it's own UI and handles input internally (hot keys/navigation/etc...).
If there are any libraries that cover this sort of use case please feel free to suggest them.
The easiest way to clear the terminal and set position is via ansi escape codes. However, this may not be the ideal way as variation in terminals may come back to bite you.
fmt.Print("\033[2J") //Clear screen
fmt.Printf("\033[%d;%dH", line, col) // Set cursor position
A better alternative would be to use a library like goncurses or termbox-go (credit: second is from Tim Cooper's comment).
With such a library you can do things like this:
import (
gc "code.google.com/p/goncurses"
)
func main() {
s, err := gc.Init()
if err != nil {
panic(err)
}
defer gc.End()
s.Move(5, 2)
s.Println("Hello")
s.GetChar()
}
Code above copied from Rosetta Code
As of December 2019, I would recommend using rivo/tview library.
(goncurses mentioned by #vastlysuperiorman has not been updated since June 2019 and termbox-go is explicitly declared unmaintained).
Here's the "hello world" app, taken from the project's README (reformatted for readability):
package main
import (
"github.com/rivo/tview"
)
func main() {
box := tview.NewBox().
SetBorder(true).
SetTitle("Hello, world!")
if err := tview.NewApplication().SetRoot(box, true).Run(); err != nil {
panic(err)
}
}
tview provides screenshots and example code as well as the standard godoc reference.
To stop the terminal from printing keys pressed you can use the below code:
import (
"fmt"
"syscall"
"golang.org/x/crypto/ssh/terminal"
)
func main(){
fmt.Print("Enter Value: ")
byteInput, _ := terminal.ReadPassword(int(syscall.Stdin))
input:= string(byteInput)
fmt.Println() // it's necessary to add a new line after user's input
fmt.Printf("Your input is '%s'", input)
}

Taking control of another window with Go

I'm wondering if there are any libraries that help me take control of another window. For example if user has calc.exe running, I'd like my go code to move it, resize it, maybe even remove it's frame, attach stuff to it, idk.
Right now I only know how to do it with scripting languages like autoit or autohotkey or whatnot.
Yes there are several libraries which can be found using godoc.org or go-search.org. In this example I'm using w32 and w32syscall (which supplies some additional functions):
package main
import (
"log"
"strings"
"syscall"
"github.com/AllenDang/w32"
"github.com/hnakamur/w32syscall"
)
func main() {
err := w32syscall.EnumWindows(func(hwnd syscall.Handle, lparam uintptr) bool {
h := w32.HWND(hwnd)
text := w32.GetWindowText(h)
if strings.Contains(text, "Calculator") {
w32.MoveWindow(h, 0, 0, 200, 600, true)
}
return true
}, 0)
if err != nil {
log.Fatalln(err)
}
}
Both of these libraries are merely exposing the underlying win32 API with minimal wrapping, so you will have to read the corresponding documentation from Microsoft to really know how to use them.

Resources