package main import ( "errors" "fmt" "io" "net/http" "os" "os/exec" "runtime" "strings" "time" disabledicon "gitea.ronald1985.uk/ronald1985/Eye-Reminder/icons/disabledicon" enabledicon "gitea.ronald1985.uk/ronald1985/Eye-Reminder/icons/enabledicon" "github.com/gen2brain/beeep" "github.com/getlantern/systray" ge "github.com/ronaldr1985/graceful-exit" "gopkg.in/yaml.v3" ) const ( DEFAULT_TIME_INBETWEEN_NOTIFICATIONS = 20 * time.Minute EYE_REMINDER_ICON_URL = "https://gitea.ronald1985.uk/ronald1985/Eye-Reminder/raw/branch/master/assets/Eye-Reminder-Icon.png" ) type Config struct { Interval int `yaml:"Interval"` imageLocation string } var ( DEFAULT_UNIX_DIRECTORY = os.Getenv("HOME") + "/.config/Eye-Reminder/" DEFAULT_WINDOWS_DIRECTORY = os.Getenv("LOCALAPPDATA") + "/Eye-Reminder/" ProgramConfig Config ) func print_fatal_error(err ...string) { fmt.Fprintln(os.Stderr, err) os.Exit(1) } func ReadConfig(filename string) (Config, error) { config := &Config{} bytes, err := os.ReadFile(filename) if err != nil { return *config, err } err = yaml.Unmarshal(bytes, config) if err != nil { return *config, fmt.Errorf("in file %q: %w", filename, err) } return *config, err } func CheckIfFileExists(filename string) (bool, error) { if _, err := os.Stat(filename); err == nil { return true, nil } else if errors.Is(err, os.ErrNotExist) { return false, nil } else { return false, err } } func DownloadFile(filename, url string) bool { out, err := os.Create(filename) if err != nil { return false } defer out.Close() resp, err := http.Get(url) if err != nil { out.Close() os.Remove(filename) return false } defer resp.Body.Close() _, err = io.Copy(out, resp.Body) if err != nil { return false } return true } func SendNotification(title string, message string) bool { path, err := exec.LookPath("herbe") if err == nil { var args []string args = append(args, title, message) cmd := exec.Command(path, args...) err := cmd.Start() if err != nil { return false } } else if strings.Contains(err.Error(), "executable file not found in ") { err := beeep.Notify(title, message, "assets/Eye-Reminder-Icon.png") if err != nil { return false } } return true } func systrayOnReady() { var enabled bool = true systray.SetTitle("Eye Reminder") systray.SetTooltip("Eye Reminder") systray.SetIcon(enabledicon.Data) mQuitOrig := systray.AddMenuItem("Quit", "Quit the whole app") go func() { <-mQuitOrig.ClickedCh fmt.Println("Requesting quit") systray.Quit() fmt.Println("Finished quitting") }() go func() { lastTimeNotificationWasSent := time.Now().Add(time.Duration(-ProgramConfig.Interval) * time.Minute) for { if enabled && time.Since(lastTimeNotificationWasSent) > time.Duration(ProgramConfig.Interval) { fmt.Println("Sending notification") SendNotification("Eye Reminder", "Look away for the screen for atleast 20 seconds") lastTimeNotificationWasSent = time.Now() } } }() go func() { systray.AddSeparator() mEnabled := systray.AddMenuItemCheckbox("Enabled", "Check Me", true) for { select { case <-mEnabled.ClickedCh: if mEnabled.Checked() { mEnabled.Uncheck() enabled = false systray.SetIcon(disabledicon.Data) } else { mEnabled.Check() enabled = true systray.SetIcon(enabledicon.Data) } } } }() } func main() { ge.HandleSignals(false) var possibleConfigFileLocations []string possibleConfigFileLocations = append(possibleConfigFileLocations, os.Getenv("HOME")+"/.config/Eye-Reminder/config.yaml") possibleConfigFileLocations = append(possibleConfigFileLocations, os.Getenv("HOME")+"/.config/Eye-Reminder/config.yml") possibleConfigFileLocations = append(possibleConfigFileLocations, os.Getenv("XDG_CONFIG_HOME")+"/Eye-Reminder/config.yaml") possibleConfigFileLocations = append(possibleConfigFileLocations, os.Getenv("XDG_CONFIG_HOME")+"/Eye-Reminder/config.yml") possibleConfigFileLocations = append(possibleConfigFileLocations, os.Getenv("LOCALAPPDATA")+"/Eye-Reminder/config.yml") possibleConfigFileLocations = append(possibleConfigFileLocations, os.Getenv("LOCALAPPDATA")+"/Eye-Reminder/config.yml") configFileLocation := "not found" for _, file := range possibleConfigFileLocations { if fileExists, err := CheckIfFileExists(file); fileExists { configFileLocation = file break } else if err != nil { fmt.Fprintln(os.Stderr, "Error checking for config file:", err) } } var err error appDirectory := "not found" if configFileLocation == "not found" { ProgramConfig.Interval = int(DEFAULT_TIME_INBETWEEN_NOTIFICATIONS) var possibleAppDirectoryLocations []string possibleAppDirectoryLocations = append(possibleAppDirectoryLocations, os.Getenv("HOME")+"/.config/Eye-Reminder/") possibleAppDirectoryLocations = append(possibleAppDirectoryLocations, os.Getenv("HOME")+"/.config/Eye-Reminder/") possibleAppDirectoryLocations = append(possibleAppDirectoryLocations, os.Getenv("XDG_CONFIG_HOME")+"/Eye-Reminder/") possibleAppDirectoryLocations = append(possibleAppDirectoryLocations, os.Getenv("XDG_CONFIG_HOME")+"/Eye-Reminder/") possibleAppDirectoryLocations = append(possibleAppDirectoryLocations, os.Getenv("LOCALAPPDATA")+"/Eye-Reminder/") possibleAppDirectoryLocations = append(possibleAppDirectoryLocations, os.Getenv("LOCALAPPDATA")+"/Eye-Reminder/") for _, folder := range possibleAppDirectoryLocations { if _, err := os.Stat(folder); !os.IsNotExist(err) { appDirectory = folder break } } if appDirectory == "not found" { if runtime.GOOS == "windows" { appDirectory = DEFAULT_WINDOWS_DIRECTORY } else { appDirectory = DEFAULT_UNIX_DIRECTORY } fmt.Println("Creating config directory:", appDirectory) err := os.Mkdir(appDirectory, 0755) fmt.Println("Created folder:", appDirectory) if err != nil { panic(err) } } } else { appDirectory = configFileLocation[:strings.IndexByte(configFileLocation, '/')+1] ProgramConfig, err = ReadConfig(configFileLocation) if err != nil { panic(err) } } ProgramConfig.imageLocation = appDirectory + "Eye-Reminder-Icon.png" if fileExists, err := CheckIfFileExists(ProgramConfig.imageLocation); !fileExists && err == nil { fmt.Println("Downloading icon") DownloadFile(ProgramConfig.imageLocation, EYE_REMINDER_ICON_URL) fmt.Println("Downloaded icon") } else if err != nil { print_fatal_error("Failed to check if ", ProgramConfig.imageLocation, " exists.") } systrayOnExit := func() { } systray.Run(systrayOnReady, systrayOnExit) }