|
|
|
@@ -3,6 +3,7 @@ package main
|
|
|
|
import (
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
"image"
|
|
|
|
"io"
|
|
|
|
"io"
|
|
|
|
"math/rand"
|
|
|
|
"math/rand"
|
|
|
|
"net/http"
|
|
|
|
"net/http"
|
|
|
|
@@ -14,6 +15,12 @@ import (
|
|
|
|
"sort"
|
|
|
|
"sort"
|
|
|
|
"strings"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_ "image/gif"
|
|
|
|
|
|
|
|
"image/jpeg"
|
|
|
|
|
|
|
|
"image/png"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
"golang.org/x/image/draw"
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
const (
|
|
|
|
@@ -66,6 +73,12 @@ type WallhavenResponse struct {
|
|
|
|
} `json:"data"`
|
|
|
|
} `json:"data"`
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
type monitor struct {
|
|
|
|
|
|
|
|
Name string `json:"name"`
|
|
|
|
|
|
|
|
Width int `json:"width"`
|
|
|
|
|
|
|
|
Height int `json:"height"`
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
func main() {
|
|
|
|
// Initialize random source
|
|
|
|
// Initialize random source
|
|
|
|
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
|
|
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
|
|
@@ -162,7 +175,7 @@ func downloadRandomWallpaper(wallpaperPath string, r *rand.Rand, topics []string
|
|
|
|
fmt.Fprintf(os.Stderr, "Searching for wallpapers related to: %s\n", displayName)
|
|
|
|
fmt.Fprintf(os.Stderr, "Searching for wallpapers related to: %s\n", displayName)
|
|
|
|
|
|
|
|
|
|
|
|
// Get wallpapers from Wallhaven API
|
|
|
|
// Get wallpapers from Wallhaven API
|
|
|
|
resp, err := http.Get(fmt.Sprintf("%s/search?q=%s&purity=100&categories=110&sorting=random", wallhavenAPI, query))
|
|
|
|
resp, err := http.Get(fmt.Sprintf("%s/search?q=%s&purity=100&categories=110&sorting=random&atleast=3440x1440", wallhavenAPI, query))
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
fmt.Fprintf(os.Stderr, "Error fetching from Wallhaven: %v\n", err)
|
|
|
|
fmt.Fprintf(os.Stderr, "Error fetching from Wallhaven: %v\n", err)
|
|
|
|
return "", ""
|
|
|
|
return "", ""
|
|
|
|
@@ -213,27 +226,165 @@ func downloadRandomWallpaper(wallpaperPath string, r *rand.Rand, topics []string
|
|
|
|
return filepath, displayName
|
|
|
|
return filepath, displayName
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func activeMonitors() ([]monitor, error) {
|
|
|
|
|
|
|
|
out, err := exec.Command("hyprctl", "-j", "monitors").Output()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
var monitors []monitor
|
|
|
|
|
|
|
|
if err := json.Unmarshal(out, &monitors); err != nil {
|
|
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return monitors, nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func ensureSized(wallpaperPath string) (string, error) {
|
|
|
|
|
|
|
|
monitors, err := activeMonitors()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
return "", err
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(monitors) == 0 {
|
|
|
|
|
|
|
|
return wallpaperPath, nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
targetWidth := 0
|
|
|
|
|
|
|
|
targetHeight := 0
|
|
|
|
|
|
|
|
for _, m := range monitors {
|
|
|
|
|
|
|
|
if m.Width > targetWidth {
|
|
|
|
|
|
|
|
targetWidth = m.Width
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if m.Height > targetHeight {
|
|
|
|
|
|
|
|
targetHeight = m.Height
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if targetWidth == 0 || targetHeight == 0 {
|
|
|
|
|
|
|
|
return wallpaperPath, nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
file, err := os.Open(wallpaperPath)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
return "", err
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src, format, err := image.Decode(file)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
return "", err
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
srcWidth := float64(src.Bounds().Dx())
|
|
|
|
|
|
|
|
srcHeight := float64(src.Bounds().Dy())
|
|
|
|
|
|
|
|
targetW := float64(targetWidth)
|
|
|
|
|
|
|
|
targetH := float64(targetHeight)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Calculate scale factor to fit image within target while maintaining aspect ratio
|
|
|
|
|
|
|
|
scaleW := targetW / srcWidth
|
|
|
|
|
|
|
|
scaleH := targetH / srcHeight
|
|
|
|
|
|
|
|
scale := min(scaleW, scaleH)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// If image already fits within target dimensions, no resize needed
|
|
|
|
|
|
|
|
if scale >= 1.0 {
|
|
|
|
|
|
|
|
return wallpaperPath, nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Calculate new dimensions maintaining aspect ratio (best fit)
|
|
|
|
|
|
|
|
newWidth := int(srcWidth * scale)
|
|
|
|
|
|
|
|
newHeight := int(srcHeight * scale)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
dst := image.NewRGBA(image.Rect(0, 0, newWidth, newHeight))
|
|
|
|
|
|
|
|
draw.CatmullRom.Scale(dst, dst.Bounds(), src, src.Bounds(), draw.Over, nil)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var ext string
|
|
|
|
|
|
|
|
switch format {
|
|
|
|
|
|
|
|
case "jpeg":
|
|
|
|
|
|
|
|
ext = ".jpg"
|
|
|
|
|
|
|
|
case "png":
|
|
|
|
|
|
|
|
ext = ".png"
|
|
|
|
|
|
|
|
case "gif":
|
|
|
|
|
|
|
|
ext = ".png"
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
|
|
ext = filepath.Ext(wallpaperPath)
|
|
|
|
|
|
|
|
if ext == "" {
|
|
|
|
|
|
|
|
ext = ".jpg"
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
base := strings.TrimSuffix(filepath.Base(wallpaperPath), filepath.Ext(wallpaperPath))
|
|
|
|
|
|
|
|
resizedPath := filepath.Join(filepath.Dir(wallpaperPath), fmt.Sprintf("%s-%dx%d%s", base, newWidth, newHeight, ext))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
outFile, err := os.Create(resizedPath)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
return "", err
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
defer outFile.Close()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
switch format {
|
|
|
|
|
|
|
|
case "png", "gif":
|
|
|
|
|
|
|
|
if err := png.Encode(outFile, dst); err != nil {
|
|
|
|
|
|
|
|
return "", err
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
|
|
if err := jpeg.Encode(outFile, dst, &jpeg.Options{Quality: 90}); err != nil {
|
|
|
|
|
|
|
|
return "", err
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return resizedPath, nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func changeWallpaper(wallpaperPath, topic string) {
|
|
|
|
func changeWallpaper(wallpaperPath, topic string) {
|
|
|
|
|
|
|
|
resizedPath, err := ensureSized(wallpaperPath)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
fmt.Fprintf(os.Stderr, "Error resizing wallpaper: %v\n", err)
|
|
|
|
|
|
|
|
resizedPath = wallpaperPath
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if resizedPath == "" {
|
|
|
|
|
|
|
|
resizedPath = wallpaperPath
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Save current wallpaper path
|
|
|
|
// Save current wallpaper path
|
|
|
|
homeDir, _ := os.UserHomeDir()
|
|
|
|
homeDir, _ := os.UserHomeDir()
|
|
|
|
wallpaperFile := filepath.Join(homeDir, ".wallpaper")
|
|
|
|
wallpaperFile := filepath.Join(homeDir, ".wallpaper")
|
|
|
|
if err := os.WriteFile(wallpaperFile, []byte(wallpaperPath), 0644); err != nil {
|
|
|
|
if err := os.WriteFile(wallpaperFile, []byte(resizedPath), 0644); err != nil {
|
|
|
|
fmt.Fprintf(os.Stderr, "Error saving wallpaper path: %v\n", err)
|
|
|
|
fmt.Fprintf(os.Stderr, "Error saving wallpaper path: %v\n", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Change wallpaper using hyprctl
|
|
|
|
monitors, monitorErr := activeMonitors()
|
|
|
|
cmd := exec.Command("hyprctl", "hyprpaper", "reload", ","+wallpaperPath)
|
|
|
|
if monitorErr != nil {
|
|
|
|
|
|
|
|
fmt.Fprintf(os.Stderr, "Error getting monitors: %v\n", monitorErr)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cmd := exec.Command("hyprctl", "hyprpaper", "preload", resizedPath)
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
|
|
fmt.Fprintf(os.Stderr, "Error changing wallpaper: %v\n", err)
|
|
|
|
fmt.Fprintf(os.Stderr, "Error preloading wallpaper: %v\n", err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if monitorErr != nil || len(monitors) == 0 {
|
|
|
|
|
|
|
|
cmd = exec.Command("hyprctl", "hyprpaper", "wallpaper", ","+resizedPath)
|
|
|
|
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
|
|
|
|
|
|
fmt.Fprintf(os.Stderr, "Error applying wallpaper: %v\n", err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
for _, m := range monitors {
|
|
|
|
|
|
|
|
cmd = exec.Command("hyprctl", "hyprpaper", "wallpaper", fmt.Sprintf("%s,%s", m.Name, resizedPath))
|
|
|
|
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
|
|
|
|
|
|
fmt.Fprintf(os.Stderr, "Error applying wallpaper for monitor %s: %v\n", m.Name, err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cmd = exec.Command("hyprctl", "hyprpaper", "reload")
|
|
|
|
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
|
|
|
|
|
|
fmt.Fprintf(os.Stderr, "Error reloading hyprpaper: %v\n", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Send notification with wallpaper as icon
|
|
|
|
// Send notification with wallpaper as icon
|
|
|
|
filename := filepath.Base(wallpaperPath)
|
|
|
|
filename := filepath.Base(resizedPath)
|
|
|
|
message := fmt.Sprintf("Wallpaper changed to %s", filename)
|
|
|
|
message := fmt.Sprintf("Wallpaper changed to %s", filename)
|
|
|
|
if topic != "" {
|
|
|
|
if topic != "" {
|
|
|
|
message += fmt.Sprintf(" (%s)", topic)
|
|
|
|
message += fmt.Sprintf(" (%s)", topic)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
notifyWithIcon(message, "normal", wallpaperPath)
|
|
|
|
notifyWithIcon(message, "normal", resizedPath)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func notify(message, urgency string) {
|
|
|
|
func notify(message, urgency string) {
|
|
|
|
|