diff --git a/main.go b/main.go index f8e49fc..593bbf9 100644 --- a/main.go +++ b/main.go @@ -1,98 +1,243 @@ -package main - -import ( - "fmt" - "os/exec" - "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" -) - -const ( - TIME_INBETWEEN_NOTIFICATIONS = 20 * time.Minute -) - -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...) - out, err := cmd.CombinedOutput() - if out == nil && err == nil { - return true - } - } else if strings.Contains(err.Error(), "executable file not found in ") { - err := beeep.Notify(title, message, "assets/Eye-Reminder-Icon.png") - if err != nil { - panic(err) - } - } - - return false -} - -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(-20 * time.Minute) - - for { - if enabled && time.Since(lastTimeNotificationWasSent) > TIME_INBETWEEN_NOTIFICATIONS { - 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) - - systrayOnExit := func() { - } - systray.Run(systrayOnReady, systrayOnExit) -} +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) +}