// Copyright (C) 2026 Danilo M. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License version 2 as // published by the Free Software Foundation. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. package notify import ( "encoding/json" "os" "path/filepath" "strconv" ) // RuntimeDir is where the daemon publishes. It is tmpfs, so a reboot clears // every file and there is no cleanup code. func RuntimeDir() string { if d := os.Getenv("XDG_RUNTIME_DIR"); d != "" { return filepath.Join(d, "notifyd") } return filepath.Join(os.TempDir(), "notifyd") } // Publish writes the live queue and the history ring, each whole, through a // temporary file and a rename. A reader never sees a half-written value, the // same atomic write the status registry relies on. func Publish(dir string, live, history []Popup) error { if err := os.MkdirAll(dir, 0o700); err != nil { return err } if live == nil { live = []Popup{} } if history == nil { history = []Popup{} } if err := writeJSON(filepath.Join(dir, "queue.json"), live); err != nil { return err } return writeJSON(filepath.Join(dir, "history.json"), history) } // ImagesDir is where the daemon writes decoded image-data. func ImagesDir(dir string) string { return filepath.Join(dir, "img") } // WriteImage writes a decoded image as .png under the image directory. func WriteImage(dir string, id uint32, data []byte) (string, error) { imgDir := ImagesDir(dir) if err := os.MkdirAll(imgDir, 0o700); err != nil { return "", err } path := filepath.Join(imgDir, strconv.FormatUint(uint64(id), 10)+".png") tmp, err := os.CreateTemp(imgDir, ".img-*") if err != nil { return "", err } tmpName := tmp.Name() if _, err := tmp.Write(data); err != nil { tmp.Close() os.Remove(tmpName) return "", err } if err := tmp.Close(); err != nil { os.Remove(tmpName) return "", err } if err := os.Rename(tmpName, path); err != nil { os.Remove(tmpName) return "", err } return path, nil } func writeJSON(path string, v any) error { data, err := json.Marshal(v) if err != nil { return err } tmp, err := os.CreateTemp(filepath.Dir(path), ".tmp-*") if err != nil { return err } tmpName := tmp.Name() renamed := false defer func() { if !renamed { os.Remove(tmpName) } }() if _, err := tmp.Write(data); err != nil { tmp.Close() return err } if err := tmp.Close(); err != nil { return err } if err := os.Rename(tmpName, path); err != nil { return err } renamed = true return nil }