Golang实现Wayland环境下的屏幕截图

Golang实现Wayland环境下的屏幕截图 我想用Go语言捕获屏幕截图。

在X11环境下一切正常。但在Wayland环境下,我得到的是黑屏。

我尝试通过DBUS捕获屏幕,成功了。

但我希望捕获屏幕并保存到同一文件夹中(不闪烁 + 不弹出提示)。

请帮我修复这个问题。

以下是我正在使用的代码:

package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
	"os"

	"github.com/godbus/dbus"
)

func main() {
	conn, err := dbus.SessionBus()
	if err != nil {
		log.Fatalf("Failed to connect to session bus: %v", err)
	}
	defer conn.Close()

	dest := "org.freedesktop.portal.Desktop"
	path := "/org/freedesktop/portal/desktop"
	iface := "org.freedesktop.portal.Screenshot"
	options := map[string]dbus.Variant{
		"Path":  dbus.MakeVariant("/home/capture-wayland/screenshot.png"),
		"Flash": dbus.MakeVariant(false),
	}

	obj := conn.Object(dest, dbus.ObjectPath(path))
	caller := obj.Call(iface+".Screenshot", 0, "", options)

	fmt.Println(caller)
	fmt.Println(caller.Path)
	fmt.Println(caller.Destination)
	fmt.Println(caller.Method)
	fmt.Println(caller.Args)
	var response map[string]dbus.Variant
	// caller.Store(&response)
	// fmt.Println("response:", response)
	// a, _ := json.Marshal(caller)
	// fmt.Println("caller string:", string(a))
	_ = obj.Call(iface+".Screenshot", 1, "", options).Store(&response)
	fmt.Println(response)
	if err != nil {
		log.Fatalf("Failed to call Screenshot method: %v", err)
	}

	uri, ok := response["uri"]
	if !ok {
		log.Fatal("No URI found in the response")
	}

	// Download the image from the URI and save it to image.png
	downloadAndSave(uri.Value().(string), "image.png")
}

func downloadAndSave(url, filename string) {
	resp, err := http.Get(url)
	if err != nil {
		log.Fatalf("Failed to download the image: %v", err)
	}
	defer resp.Body.Close()

	out, err := os.Create(filename)
	if err != nil {
		log.Fatalf("Failed to create the image file: %v", err)
	}
	defer out.Close()

	_, err = io.Copy(out, resp.Body)
	if err != nil {
		log.Fatalf("Failed to save the image: %v", err)
	}
	fmt.Println("Saved screenshot to", filename)
}

更多关于Golang实现Wayland环境下的屏幕截图的实战教程也可以访问 https://www.itying.com/category-94-b0.html

3 回复

duckduck:

除了通过 dbus 的途径,另一种方法是使用这个库。我已在 Windows 和 Darwin 上成功使用,但不确定它是否能在 X 或 Wayland 上工作。

GitHub - kbinani/screenshot: Go library to capture desktop to image

我已经使用过这个库了。它在 Wayland 上不工作,所以我转而使用 dbus。

更多关于Golang实现Wayland环境下的屏幕截图的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


除了通过 dbus 的途径外,另一种方法是使用这个库,我已在 Windows 和 Darwin 上成功使用,但不确定它是否适用于 X 或 Wayland。

GitHub

GitHub - kbinani/screenshot: Go library to capture desktop to image

Go library to capture desktop to image. Contribute to kbinani/screenshot development by creating an account on GitHub.

在Wayland环境下通过DBus捕获屏幕截图时,需要正确处理portal API的异步响应。以下是修复后的代码:

package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
	"net/url"
	"os"
	"path/filepath"

	"github.com/godbus/dbus"
)

func main() {
	conn, err := dbus.SessionBus()
	if err != nil {
		log.Fatalf("Failed to connect to session bus: %v", err)
	}
	defer conn.Close()

	dest := "org.freedesktop.portal.Desktop"
	path := "/org/freedesktop/portal/desktop"
	iface := "org.freedesktop.portal.Screenshot"
	
	// 获取当前工作目录
	cwd, err := os.Getwd()
	if err != nil {
		log.Fatalf("Failed to get current directory: %v", err)
	}
	
	// 构建完整的文件路径
	filePath := filepath.Join(cwd, "screenshot.png")
	fileURI := "file://" + url.PathEscape(filePath)
	
	options := map[string]dbus.Variant{
		"handle_token": dbus.MakeVariant("go_screenshot"),
		"modal":        dbus.MakeVariant(false),
	}

	obj := conn.Object(dest, dbus.ObjectPath(path))
	
	// 创建信号通道
	signalChan := make(chan *dbus.Signal, 10)
	conn.Signal(signalChan)
	defer conn.RemoveSignal(signalChan)
	
	// 设置信号匹配
	matchRule := fmt.Sprintf(
		"type='signal',interface='org.freedesktop.portal.Request',path='%s'",
		"/org/freedesktop/portal/desktop/request/go_screenshot",
	)
	conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, matchRule)
	
	// 调用Screenshot方法
	call := obj.Call(iface+".Screenshot", 0, "", options)
	if call.Err != nil {
		log.Fatalf("Failed to call Screenshot method: %v", call.Err)
	}
	
	// 等待响应
	var requestPath dbus.ObjectPath
	if err := call.Store(&requestPath); err != nil {
		log.Fatalf("Failed to store request path: %v", err)
	}
	
	// 监听响应信号
	for signal := range signalChan {
		if signal.Name == "org.freedesktop.portal.Request.Response" &&
			len(signal.Body) >= 2 {
			
			responseCode := signal.Body[0].(uint32)
			results := signal.Body[1].(map[string]dbus.Variant)
			
			if responseCode == 0 {
				if uri, ok := results["uri"]; ok {
					fileURL := uri.Value().(string)
					
					// 如果是file:// URI,直接复制文件
					if len(fileURL) > 7 && fileURL[:7] == "file://" {
						srcPath, _ := url.PathUnescape(fileURL[7:])
						copyFile(srcPath, filePath)
						fmt.Printf("Screenshot saved to: %s\n", filePath)
						return
					}
					
					// 否则下载文件
					downloadAndSave(fileURL, filePath)
					fmt.Printf("Screenshot saved to: %s\n", filePath)
					return
				}
			}
			log.Fatalf("Screenshot request failed with code: %d", responseCode)
		}
	}
}

func copyFile(src, dst string) {
	srcFile, err := os.Open(src)
	if err != nil {
		log.Fatalf("Failed to open source file: %v", err)
	}
	defer srcFile.Close()
	
	dstFile, err := os.Create(dst)
	if err != nil {
		log.Fatalf("Failed to create destination file: %v", err)
	}
	defer dstFile.Close()
	
	_, err = io.Copy(dstFile, srcFile)
	if err != nil {
		log.Fatalf("Failed to copy file: %v", err)
	}
}

func downloadAndSave(urlStr, filename string) {
	resp, err := http.Get(urlStr)
	if err != nil {
		log.Fatalf("Failed to download the image: %v", err)
	}
	defer resp.Body.Close()

	out, err := os.Create(filename)
	if err != nil {
		log.Fatalf("Failed to create the image file: %v", err)
	}
	defer out.Close()

	_, err = io.Copy(out, resp.Body)
	if err != nil {
		log.Fatalf("Failed to save the image: %v", err)
	}
}

关键修复点:

  1. 添加handle_token:portal API需要唯一的handle_token来标识请求
  2. 正确处理异步响应:通过DBus信号监听响应,而不是同步等待
  3. 设置modal为false:避免弹出对话框
  4. 处理file:// URI:直接复制文件而不是重新下载
  5. 使用当前工作目录:确保文件保存到程序所在目录

这个实现会在Wayland环境下无闪烁、无提示地捕获屏幕截图,并保存到程序运行的同一文件夹中。

回到顶部