Initial commit

This commit is contained in:
Johan Jongsma 2026-02-01 08:04:10 +00:00
commit fbd5c87dbf
4 changed files with 195 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
node_modules/
.env
*.log
.DS_Store

53
README.md Normal file
View File

@ -0,0 +1,53 @@
# Screenshot Server
Tiny HTTP server that serves the latest screenshot from ~/Desktop, then deletes it.
## Build (on Mac)
```bash
cd /path/to/screenshot-server
go build -o screenshot-server
```
## Install as service
```bash
# Copy binary
sudo cp screenshot-server /usr/local/bin/
# Install launchd service
cp com.inou.screenshot-server.plist ~/Library/LaunchAgents/
# Start it
launchctl load ~/Library/LaunchAgents/com.inou.screenshot-server.plist
# Check status
launchctl list | grep screenshot
```
## Usage
Take a screenshot (Cmd+Shift+4), then:
```bash
curl http://192.168.1.14:9123/screenshot -o screenshot.png
```
## Endpoints
- `GET /screenshot` - Returns latest screenshot PNG, then deletes it
- `GET /health` - Returns "ok"
## Uninstall
```bash
launchctl unload ~/Library/LaunchAgents/com.inou.screenshot-server.plist
rm ~/Library/LaunchAgents/com.inou.screenshot-server.plist
sudo rm /usr/local/bin/screenshot-server
```
## Logs
```bash
tail -f /tmp/screenshot-server.log
```

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.inou.screenshot-server</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/screenshot-server</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/tmp/screenshot-server.log</string>
<key>StandardErrorPath</key>
<string>/tmp/screenshot-server.log</string>
</dict>
</plist>

117
main.go Normal file
View File

@ -0,0 +1,117 @@
// 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"
"path/filepath"
"sort"
"strings"
)
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")
}
func findLatestScreenshot(desktop string) (string, error) {
entries, err := os.ReadDir(desktop)
if err != nil {
return "", err
}
var screenshots []os.DirEntry
for _, e := range entries {
name := e.Name()
if strings.HasPrefix(name, "Screenshot") && strings.HasSuffix(name, ".png") {
screenshots = append(screenshots, e)
}
}
if len(screenshots) == 0 {
return "", fmt.Errorf("no screenshots found")
}
// Sort by modification time (newest first)
sort.Slice(screenshots, func(i, j int) bool {
infoI, _ := screenshots[i].Info()
infoJ, _ := screenshots[j].Info()
return infoI.ModTime().After(infoJ.ModTime())
})
return filepath.Join(desktop, screenshots[0].Name()), nil
}
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
}
// Open and serve
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
}
// Delete after successful send
f.Close()
if err := os.Remove(path); err != nil {
log.Println("Warning: could not delete:", err)
} else {
log.Println("Served and deleted:", filename)
}
}
func healthHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ok"))
}
func main() {
http.HandleFunc("/screenshot", screenshotHandler)
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(" GET /health - health check")
if err := http.ListenAndServe(port, nil); err != nil {
log.Fatal(err)
}
}