You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

319 lines
8.3 KiB
Go

package main
import (
"errors"
"fmt"
"io"
"net/http"
"os"
"os/exec"
"runtime"
"strings"
"time"
darkmusicicon "gitea.ronald1985.uk/ronald1985/Eye-Reminder/icons/darkmusicicon"
disabledicon "gitea.ronald1985.uk/ronald1985/Eye-Reminder/icons/disabledicon"
enabledicon "gitea.ronald1985.uk/ronald1985/Eye-Reminder/icons/enabledicon"
lightmusicicon "gitea.ronald1985.uk/ronald1985/Eye-Reminder/icons/lightmusicicon"
"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"
DEFAULT_CONFIG_FILE = "Interval: 20\nSoundEnabled: false\nWAVFile: \"\"\nDarkTheme: true"
)
type Config struct {
Interval int `yaml:"Interval"`
SoundEnabled bool `yaml:"SoundEnabled"`
WAVFile string `yaml:"WAVFile"`
DarkTheme bool `yaml:"DarkTheme"`
imageLocation string
}
var (
DEFAULT_UNIX_DIRECTORY = os.Getenv("HOME") + "/.config/Eye-Reminder/"
DEFAULT_WINDOWS_DIRECTORY = os.Getenv("LOCALAPPDATA") + "/Eye-Reminder/"
ProgramConfig Config
)
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)
return err == nil
}
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
var notificationsEnabled bool = true
var soundEnabled bool = ProgramConfig.SoundEnabled
var stopSound bool = false
currentIcon := enabledicon.Data
systray.SetTitle("Eye Reminder")
systray.SetTooltip("Eye Reminder")
systray.SetIcon(enabledicon.Data)
mEnabled := systray.AddMenuItemCheckbox(
"Enabled", "Disables notifications and sound", true,
)
systray.AddSeparator()
mStopSound := systray.AddMenuItem(
"Stop sound", "Stops the sound that is currently being played",
)
mStopSound.Disable()
mNotificationsEnabled := systray.AddMenuItemCheckbox(
"Notifications enabled", "Whether notifications are enabled", true,
)
mSoundEnabled := systray.AddMenuItemCheckbox(
"Sound enabled", "Whether sound is played", ProgramConfig.SoundEnabled,
)
systray.AddSeparator()
mDarkThemeEnabled := systray.AddMenuItemCheckbox(
"Dark mode enabled", "Whether the dark theme icons are used", ProgramConfig.DarkTheme,
)
systray.AddSeparator()
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)*time.Minute {
if notificationsEnabled {
fmt.Println("Sending notification")
SendNotification("Eye Reminder", "Look away for the screen for atleast 20 seconds")
}
if soundEnabled {
fmt.Println("Playing sound file")
mStopSound.Enable()
if ProgramConfig.DarkTheme {
systray.SetIcon(lightmusicicon.Data)
} else {
systray.SetIcon(darkmusicicon.Data)
}
PlayWAVFile(ProgramConfig.WAVFile, &stopSound)
mStopSound.Disable()
stopSound = false
systray.SetIcon(currentIcon)
}
lastTimeNotificationWasSent = time.Now()
}
}
}()
go func() {
for {
select {
case <-mEnabled.ClickedCh:
enabled = !enabled
if mEnabled.Checked() {
mEnabled.Uncheck()
currentIcon = disabledicon.Data
systray.SetIcon(currentIcon)
} else {
mEnabled.Check()
currentIcon = enabledicon.Data
systray.SetIcon(currentIcon)
}
case <-mNotificationsEnabled.ClickedCh:
notificationsEnabled = !notificationsEnabled
if mNotificationsEnabled.Checked() {
mNotificationsEnabled.Uncheck()
} else {
mNotificationsEnabled.Check()
}
case <-mSoundEnabled.ClickedCh:
soundEnabled = !soundEnabled
if mSoundEnabled.Checked() {
mSoundEnabled.Uncheck()
} else {
mSoundEnabled.Check()
}
case <-mStopSound.ClickedCh:
stopSound = true
case <-mDarkThemeEnabled.ClickedCh:
ProgramConfig.DarkTheme = !ProgramConfig.DarkTheme
if mDarkThemeEnabled.Checked() {
mDarkThemeEnabled.Uncheck()
} else {
mDarkThemeEnabled.Check()
}
}
}
}()
}
func main() {
ge.HandleSignals(false)
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/")
appDirectory := "not found"
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)
}
}
configFile := ""
if fileExists, err := CheckIfFileExists(appDirectory + "config.yaml"); fileExists {
configFile = appDirectory + "config.yaml"
} else if !fileExists && err == nil {
if fileExists, _ := CheckIfFileExists(appDirectory + "config.yml"); fileExists {
configFile = appDirectory + "config.yml"
}
}
if configFile != "" {
fmt.Println("Found config file at", configFile)
}
if configFile == "" {
configFile = appDirectory + "config.yaml"
fmt.Println("Creating config file:", configFile)
f, err := os.Create(configFile)
if err != nil {
panic("Failed to create config file")
}
fmt.Println("Created config file:", configFile)
fmt.Println("Writing default config to", configFile)
_, err = f.WriteString(DEFAULT_CONFIG_FILE)
if err != nil {
f.Close()
panic("Failed to write to config file: " + configFile)
}
f.Close()
fmt.Println("Written default config to ", configFile)
}
var err error
ProgramConfig, err = ReadConfig(configFile)
if err != nil {
panic("Failed to read config file")
}
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 {
panic("Failed to check if " + ProgramConfig.imageLocation + " exists.")
}
systrayOnExit := func() {
}
systray.Run(systrayOnReady, systrayOnExit)
}