173 lines
4.1 KiB
Go
173 lines
4.1 KiB
Go
// screenshot-server: Serves the latest screenshot from ~/Desktop, then deletes it
|
|
// Run: go build -o screenshot-server && ./screenshot-server
|
|
// Fetch: curl http://mac-ip:9123/screenshot -o screenshot.png
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"regexp"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
port = ":9123"
|
|
desktopDir = "" // Will be set to ~/Desktop
|
|
)
|
|
|
|
func getDesktopPath() string {
|
|
home, err := os.UserHomeDir()
|
|
if err != nil {
|
|
log.Fatal("Cannot find home directory:", err)
|
|
}
|
|
return filepath.Join(home, "Desktop")
|
|
}
|
|
|
|
type screenshotFile struct {
|
|
path string
|
|
modTime time.Time
|
|
}
|
|
|
|
func findLatestScreenshot(desktop string) (string, error) {
|
|
entries, err := os.ReadDir(desktop)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
var screenshots []screenshotFile
|
|
for _, e := range entries {
|
|
name := e.Name()
|
|
if strings.HasPrefix(name, "Screenshot") && strings.HasSuffix(name, ".png") {
|
|
info, err := e.Info()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
screenshots = append(screenshots, screenshotFile{
|
|
path: filepath.Join(desktop, name),
|
|
modTime: info.ModTime(),
|
|
})
|
|
}
|
|
}
|
|
|
|
if len(screenshots) == 0 {
|
|
return "", fmt.Errorf("no screenshots found")
|
|
}
|
|
|
|
// Sort by modification time (newest first)
|
|
sort.Slice(screenshots, func(i, j int) bool {
|
|
return screenshots[i].modTime.After(screenshots[j].modTime)
|
|
})
|
|
|
|
return screenshots[0].path, nil
|
|
}
|
|
|
|
func serveAndDelete(w http.ResponseWriter, path string) {
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
http.Error(w, "Cannot open file", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
defer f.Close()
|
|
|
|
filename := filepath.Base(path)
|
|
w.Header().Set("Content-Type", "image/png")
|
|
w.Header().Set("X-Screenshot-Name", filename)
|
|
|
|
_, err = io.Copy(w, f)
|
|
if err != nil {
|
|
log.Println("Error sending:", err)
|
|
return
|
|
}
|
|
|
|
f.Close()
|
|
if err := os.Remove(path); err != nil {
|
|
log.Println("Warning: could not delete:", err)
|
|
} else {
|
|
log.Println("Served and deleted:", filename)
|
|
}
|
|
}
|
|
|
|
func screenshotHandler(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet {
|
|
http.Error(w, "GET only", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
desktop := getDesktopPath()
|
|
path, err := findLatestScreenshot(desktop)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusNotFound)
|
|
log.Println("No screenshot:", err)
|
|
return
|
|
}
|
|
|
|
serveAndDelete(w, path)
|
|
}
|
|
|
|
func captureHandler(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "POST only", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
display := r.URL.Query().Get("display")
|
|
if display == "" {
|
|
display = "1"
|
|
}
|
|
|
|
tmpFile := filepath.Join(os.TempDir(), fmt.Sprintf("capture-%d.png", time.Now().UnixNano()))
|
|
|
|
cmd := exec.Command("screencapture", "-x", "-D", display, tmpFile)
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
http.Error(w, fmt.Sprintf("capture failed: %v: %s", err, out), http.StatusInternalServerError)
|
|
log.Printf("Capture failed (display %s): %v: %s", display, err, out)
|
|
os.Remove(tmpFile)
|
|
return
|
|
}
|
|
|
|
// Resize to 50% unless full resolution requested
|
|
if r.URL.Query().Get("full") != "1" {
|
|
if out, err := exec.Command("sips", "-g", "pixelWidth", tmpFile).Output(); err == nil {
|
|
re := regexp.MustCompile(`pixelWidth:\s*(\d+)`)
|
|
if m := re.FindSubmatch(out); m != nil {
|
|
if w, _ := strconv.Atoi(string(m[1])); w > 0 {
|
|
exec.Command("sips", "--resampleWidth", strconv.Itoa(w/2), tmpFile).Run()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
log.Printf("Captured display %s", display)
|
|
serveAndDelete(w, tmpFile)
|
|
}
|
|
|
|
func healthHandler(w http.ResponseWriter, r *http.Request) {
|
|
w.Write([]byte("ok"))
|
|
}
|
|
|
|
func main() {
|
|
http.HandleFunc("/screenshot", screenshotHandler)
|
|
http.HandleFunc("/capture", captureHandler)
|
|
http.HandleFunc("/health", healthHandler)
|
|
|
|
log.Printf("Screenshot server starting on %s", port)
|
|
log.Printf("Desktop: %s", getDesktopPath())
|
|
log.Printf("Endpoints:")
|
|
log.Printf(" GET /screenshot - fetch & delete latest screenshot")
|
|
log.Printf(" POST /capture?display=N - take screenshot of display N")
|
|
log.Printf(" GET /health - health check")
|
|
|
|
if err := http.ListenAndServe(port, nil); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|