Repository: reujab/wallpaper Branch: master Commit: 5f9f655b3740 Files: 14 Total size: 17.0 KB Directory structure: gitextract_e534wbot/ ├── .gitignore ├── darwin.go ├── example/ │ └── main.go ├── gnome.go ├── go.mod ├── go.sum ├── kde.go ├── license ├── linux.go ├── lxde.go ├── main.go ├── readme.md ├── windows.go └── xfce.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .idea ================================================ FILE: darwin.go ================================================ // +build darwin package wallpaper import ( "os/exec" "os/user" "path/filepath" "strconv" "strings" ) // Get returns the path to the current wallpaper. func Get() (string, error) { stdout, err := exec.Command("osascript", "-e", `tell application "Finder" to get POSIX path of (get desktop picture as alias)`).Output() if err != nil { return "", err } // is calling strings.TrimSpace() necessary? return strings.TrimSpace(string(stdout)), nil } // SetFromFile uses AppleScript to tell Finder to set the desktop wallpaper to specified file. func SetFromFile(file string) error { return exec.Command("osascript", "-e", `tell application "System Events" to tell every desktop to set picture to `+strconv.Quote(file)).Run() } // SetMode does nothing on macOS. func SetMode(mode Mode) error { return nil } func getCacheDir() (string, error) { usr, err := user.Current() if err != nil { return "", err } return filepath.Join(usr.HomeDir, "Library", "Caches"), nil } ================================================ FILE: example/main.go ================================================ package main import ( "fmt" "github.com/reujab/wallpaper" ) func main() { background, err := wallpaper.Get() if err != nil { panic(err) } fmt.Println("Current wallpaper:", background) err = wallpaper.SetFromFile("/usr/share/backgrounds/gnome/adwaita-day.jpg") if err != nil { panic(err) } err = wallpaper.SetFromURL("https://i.imgur.com/pIwrYeM.jpg") if err != nil { panic(err) } } ================================================ FILE: gnome.go ================================================ package wallpaper import ( "os/exec" "strings" yaml "gopkg.in/yaml.v2" ) func removeProtocol(input string) string { if len(input) >= 7 && input[:7] == "file://" { return input[7:] } return input } func parseDconf(command string, args ...string) (string, error) { output, err := exec.Command(command, args...).Output() if err != nil { return "", err } // unquote string var unquoted string // the output is quoted with single quotes, which cannot be unquoted using strconv.Unquote, but it is valid yaml err = yaml.UnmarshalStrict(output, &unquoted) if err != nil { return unquoted, err } return removeProtocol(unquoted), nil } func isGNOMECompliant() bool { return strings.Contains(Desktop, "GNOME") || Desktop == "Unity" || Desktop == "Pantheon" } func (mode Mode) getGNOMEString() string { switch mode { case Center: return "centered" case Crop: return "zoom" case Fit: return "scaled" case Span: return "spanned" case Stretch: return "stretched" case Tile: return "wallpaper" default: panic("invalid wallpaper mode") } } ================================================ FILE: go.mod ================================================ module github.com/reujab/wallpaper go 1.16 require ( github.com/smartystreets/goconvey v1.6.4 // indirect golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a gopkg.in/ini.v1 v1.62.0 gopkg.in/yaml.v2 v2.4.0 ) ================================================ FILE: go.sum ================================================ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= ================================================ FILE: kde.go ================================================ //+build linux package wallpaper import ( "bufio" "errors" "os" "os/exec" "os/user" "path/filepath" "strconv" "strings" ) func getKDE() (string, error) { usr, err := user.Current() if err != nil { return "", err } filename := filepath.Join(usr.HomeDir, ".config", "plasma-org.kde.plasma.desktop-appletsrc") if err != nil { return "", err } file, err := os.Open(filename) if err != nil { return "", err } defer file.Close() scanner := bufio.NewScanner(file) for scanner.Scan() { line := scanner.Text() if len(line) >= 6 && line[:6] == "Image=" { return strings.TrimSpace(removeProtocol(line[6:])), nil } } if scanner.Err() != nil { return "", scanner.Err() } err = file.Close() if err != nil { return "", err } return "", errors.New("kde image not found") } func setKDE(path string) error { return evalKDE(` for (const desktop of desktops()) { desktop.currentConfigGroup = ["Wallpaper", "org.kde.image", "General"] desktop.writeConfig("Image", ` + strconv.Quote("file://"+path) + `) } `) } func setKDEMode(mode Mode) error { return evalKDE(` for (const desktop of desktops()) { desktop.currentConfigGroup = ["Wallpaper", "org.kde.image", "General"] desktop.writeConfig("FillMode", ` + mode.getKDEString() + `) } `) } func evalKDE(script string) error { return exec.Command("qdbus", "org.kde.plasmashell", "/PlasmaShell", "org.kde.PlasmaShell.evaluateScript", script).Run() } func (mode Mode) getKDEString() string { switch mode { case Center: return "6" case Crop: return "2" case Fit: return "1" case Span: return "2" case Stretch: return "0" case Tile: return "3" default: panic("invalid walllpaper mode") } } ================================================ FILE: license ================================================ This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to ================================================ FILE: linux.go ================================================ // +build linux package wallpaper import ( "os/exec" "os/user" "path/filepath" "strconv" ) // Get returns the current wallpaper. func Get() (string, error) { if isGNOMECompliant() { return parseDconf("gsettings", "get", "org.gnome.desktop.background", "picture-uri") } switch Desktop { case "KDE": return getKDE() case "X-Cinnamon": return parseDconf("dconf", "read", "/org/cinnamon/desktop/background/picture-uri") case "MATE": return parseDconf("dconf", "read", "/org/mate/desktop/background/picture-filename") case "XFCE": return getXFCE() case "LXDE": return getLXDE() case "Deepin": return parseDconf("dconf", "read", "/com/deepin/wrap/gnome/desktop/background/picture-uri") default: return "", ErrUnsupportedDE } } // SetFromFile sets wallpaper from a file path. func SetFromFile(file string) error { if isGNOMECompliant() { return exec.Command("gsettings", "set", "org.gnome.desktop.background", "picture-uri", strconv.Quote("file://"+file)).Run() } switch Desktop { case "KDE": return setKDE(file) case "X-Cinnamon": return exec.Command("dconf", "write", "/org/cinnamon/desktop/background/picture-uri", strconv.Quote("file://"+file)).Run() case "MATE": return exec.Command("dconf", "write", "/org/mate/desktop/background/picture-filename", strconv.Quote(file)).Run() case "XFCE": return setXFCE(file) case "LXDE": return exec.Command("pcmanfm", "-w", file).Run() case "Deepin": return exec.Command("dconf", "write", "/com/deepin/wrap/gnome/desktop/background/picture-uri", strconv.Quote("file://"+file)).Run() default: err := exec.Command("swaybg", "-i", file).Start() // if the command completed successfully, return if err == nil { return nil } return exec.Command("feh", "-bg-fill", file).Run() } } // SetMode sets the wallpaper mode. func SetMode(mode Mode) error { if isGNOMECompliant() { return exec.Command("gsettings", "set", "org.gnome.desktop.background", "picture-options", strconv.Quote(mode.getGNOMEString())).Run() } switch Desktop { case "KDE": return setKDEMode(mode) case "X-Cinnamon": return exec.Command("dconf", "write", "/org/cinnamon/desktop/background/picture-options", strconv.Quote(mode.getGNOMEString())).Run() case "MATE": return exec.Command("dconf", "write", "/org/mate/desktop/background/picture-options", strconv.Quote(mode.getGNOMEString())).Run() case "XFCE": return setXFCEMode(mode) case "LXDE": return exec.Command("pcmanfm", "--wallpaper-mode", mode.getLXDEString()).Run() case "Deepin": return exec.Command("dconf", "write", "/com/deepin/wrap/gnome/desktop/background/picture-options", strconv.Quote(mode.getGNOMEString())).Run() default: return ErrUnsupportedDE } } func getCacheDir() (string, error) { usr, err := user.Current() if err != nil { return "", err } return filepath.Join(usr.HomeDir, ".cache"), nil } ================================================ FILE: lxde.go ================================================ package wallpaper import ( "os/user" "path/filepath" ini "gopkg.in/ini.v1" ) func getLXDE() (string, error) { usr, err := user.Current() if err != nil { return "", err } if DesktopSession == "" { DesktopSession = "LXDE" } cfg, err := ini.Load(filepath.Join(usr.HomeDir, ".config/pcmanfm/"+DesktopSession+"/desktop-items-0.conf")) if err != nil { return "", err } key, err := cfg.Section("*").GetKey("wallpaper") if err != nil { return "", err } return key.String(), err } func (mode Mode) getLXDEString() string { switch mode { case Center: return "center" case Crop: return "crop" case Fit: return "fit" case Span: return "screen" case Stretch: return "stretch" case Tile: return "tile" default: panic("invalid wallpaper mode") } } ================================================ FILE: main.go ================================================ package wallpaper import ( "errors" "io" "net/http" "os" "path/filepath" ) type Mode int const ( Center Mode = iota Crop Fit Span Stretch Tile ) // Desktop contains the current desktop environment on Linux. // Empty string on all other operating systems. var Desktop = os.Getenv("XDG_CURRENT_DESKTOP") // DesktopSession is used by LXDE on Linux. var DesktopSession = os.Getenv("DESKTOP_SESSION") // ErrUnsupportedDE is thrown when Desktop is not a supported desktop environment. var ErrUnsupportedDE = errors.New("your desktop environment is not supported") func downloadImage(url string) (string, error) { res, err := http.Get(url) if err != nil { return "", err } defer res.Body.Close() if res.StatusCode < 200 || res.StatusCode >= 300 { return "", errors.New("non-200 status code") } cacheDir, err := getCacheDir() if err != nil { return "", err } file, err := os.Create(filepath.Join(cacheDir, "wallpaper")) if err != nil { return "", err } _, err = io.Copy(file, res.Body) if err != nil { return "", err } err = file.Close() if err != nil { return "", err } return file.Name(), nil } // SetFromURL downloads the image to a cache directory and calls SetFromFile. func SetFromURL(url string) error { file, err := downloadImage(url) if err != nil { return err } return SetFromFile(file) } ================================================ FILE: readme.md ================================================ # wallpaper [![godoc](https://godoc.org/github.com/reujab/wallpaper?status.svg)](https://godoc.org/github.com/reujab/wallpaper) A cross-platform (Linux, Windows, and macOS) Golang library for getting and setting the desktop background. ## Installation ```sh go get github.com/reujab/wallpaper ``` ## Example ```go package main import ( "fmt" "github.com/reujab/wallpaper" ) func main() { background, err := wallpaper.Get() check(err) fmt.Println("Current wallpaper:", background) err = wallpaper.SetFromFile("/usr/share/backgrounds/gnome/adwaita-day.jpg") check(err) err = wallpaper.SetFromURL("https://i.imgur.com/pIwrYeM.jpg") check(err) err = wallpaper.SetMode(wallpaper.Crop) check(err) } ``` ## Supported desktops * Windows * macOS * GNOME * KDE * Cinnamon * Unity * Budgie * XFCE * LXDE * MATE * Deepin * Most Wayland compositors (set only, requires swaybg) * i3 (set only, requires feh) ================================================ FILE: windows.go ================================================ // +build windows package wallpaper import ( "golang.org/x/sys/windows/registry" "os" "strings" "syscall" "unicode/utf16" "unsafe" ) // https://msdn.microsoft.com/en-us/library/windows/desktop/ms724947.aspx const ( spiGetDeskWallpaper = 0x0073 spiSetDeskWallpaper = 0x0014 uiParam = 0x0000 spifUpdateINIFile = 0x01 spifSendChange = 0x02 ) // https://msdn.microsoft.com/en-us/library/windows/desktop/ms724947.aspx var ( user32 = syscall.NewLazyDLL("user32.dll") systemParametersInfo = user32.NewProc("SystemParametersInfoW") ) // Get returns the current wallpaper. func Get() (string, error) { // the maximum length of a windows path is 256 utf16 characters var filename [256]uint16 systemParametersInfo.Call( uintptr(spiGetDeskWallpaper), uintptr(cap(filename)), // the memory address of the first byte of the array uintptr(unsafe.Pointer(&filename[0])), uintptr(0), ) return strings.Trim(string(utf16.Decode(filename[:])), "\x00"), nil } // SetFromFile sets the wallpaper for the current user. func SetFromFile(filename string) error { filenameUTF16, err := syscall.UTF16PtrFromString(filename) if err != nil { return err } systemParametersInfo.Call( uintptr(spiSetDeskWallpaper), uintptr(uiParam), uintptr(unsafe.Pointer(filenameUTF16)), uintptr(spifUpdateINIFile|spifSendChange), ) return nil } // SetMode sets the wallpaper mode. func SetMode(mode Mode) error { key, _, err := registry.CreateKey(registry.CURRENT_USER, "Control Panel\\Desktop", registry.SET_VALUE) if err != nil { return err } defer key.Close() var tile string if mode == Tile { tile = "1" } else { tile = "0" } err = key.SetStringValue("TileWallpaper", tile) if err != nil { return err } var style string switch mode { case Center, Tile: style = "0" case Fit: style = "6" case Span: style = "22" case Stretch: style = "2" case Crop: style = "10" default: panic("invalid wallpaper mode") } err = key.SetStringValue("WallpaperStyle", style) if err != nil { return err } // updates wallpaper path, err := Get() if err != nil { return err } return SetFromFile(path) } func getCacheDir() (string, error) { return os.TempDir(), nil } ================================================ FILE: xfce.go ================================================ package wallpaper import ( "os/exec" "path" "strings" ) func getXFCEProps(key string) ([]string, error) { output, err := exec.Command("xfconf-query", "--channel", "xfce4-desktop", "--list").Output() if err != nil { return nil, err } lines := strings.Split(strings.Trim(string(output), "\n"), "\n") var desktops []string for _, line := range lines { if path.Base(line) == key { desktops = append(desktops, line) } } return desktops, nil } func getXFCE() (string, error) { desktops, err := getXFCEProps("last-image") if err != nil || len(desktops) == 0 { return "", err } output, err := exec.Command("xfconf-query", "--channel", "xfce4-desktop", "--property", desktops[0]).Output() if err != nil { return "", err } return strings.TrimSpace(string(output)), nil } func setXFCE(file string) error { desktops, err := getXFCEProps("last-image") if err != nil { return err } for _, desktop := range desktops { err := exec.Command("xfconf-query", "--channel", "xfce4-desktop", "--property", desktop, "--set", file).Run() if err != nil { return err } } return nil } func setXFCEMode(mode Mode) error { styles, err := getXFCEProps("image-style") if err != nil { return err } for _, style := range styles { err = exec.Command("xfconf-query", "--channel", "xfce4-desktop", "--property", style, "--set", mode.getXFCEString()).Run() if err != nil { return err } } return nil } func (mode Mode) getXFCEString() string { switch mode { case Center: return "1" case Crop: return "5" case Fit: return "4" case Span: return "5" case Stretch: return "3" case Tile: return "2" default: panic("invalid wallpaper mode") } }