From c5815ed5f7d8742044ccacd10e57e379737597e2 Mon Sep 17 00:00:00 2001 From: Yevhen Pavlov Date: Wed, 10 Apr 2024 00:06:57 +0300 Subject: [PATCH] feat: Restore previous settings after restarting program (#36) --- go.mod | 2 ++ go.sum | 5 +++ internal/config/config.go | 69 ++++++++++++++++++++++++++++++-------- internal/tray/tray.go | 48 ++++++++++++++++----------- pkg/config/config.go | 70 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 160 insertions(+), 34 deletions(-) create mode 100644 pkg/config/config.go diff --git a/go.mod b/go.mod index bc34578..ea1981c 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,10 @@ module github.com/sonjek/mouse-stay-up go 1.22 require ( + github.com/adrg/xdg v0.4.0 github.com/getlantern/systray v1.2.2 github.com/go-vgo/robotgo v0.110.0 + gopkg.in/ini.v1 v1.67.0 ) require ( diff --git a/go.sum b/go.sum index 4fcae15..bb0b033 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298/go.mod h1:D+QujdIlUNfa0igpNMk6UIvlb6C252URs4yupRUV4lQ= github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966/go.mod h1:Mid70uvE93zn9wgF92A/r5ixgnvX8Lh68fxp9KQBaI0= +github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls= +github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -150,6 +152,7 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -170,6 +173,8 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T gopkg.in/Knetic/govaluate.v3 v3.0.0/go.mod h1:csKLBORsPbafmSCGTEh3U7Ozmsuq8ZSIlKk1bcqph0E= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/config/config.go b/internal/config/config.go index 57c26c3..af9156e 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,7 +1,15 @@ package config +import ( + "fmt" + + "github.com/sonjek/mouse-stay-up/pkg/config" +) + const ( - gitRepo string = "https://github.com/sonjek/mouse-stay-up" + GitRepo string = "https://github.com/sonjek/mouse-stay-up" + appName string = "mouse-stay-up" + configFileName string = "config.conf" workingHours00_00 = "00:00-00:00" workingHours08_18 = "08:00-18:00" @@ -10,34 +18,67 @@ const ( workingHours10_20 = "10:00-20:00" ) -var workingHours = []string{ - workingHours08_18, - workingHours09_19, - workingHours10_19, - workingHours10_20, - workingHours00_00, -} +var ( + configFilePath string + + workingHours = []string{ + workingHours08_18, + workingHours09_19, + workingHours10_19, + workingHours10_20, + workingHours00_00, + } +) type Config struct { - Enabled bool - GitRepo string - WorkingHoursInterval string - WorkingHours []string + Enabled bool `ini:"enabled"` + WorkingHoursInterval string `ini:"working-hours-interval"` + WorkingHours []string `ini:"working-hours,omitempty"` +} + +func init() { + // Create a folder to save the config file if it doesn't exist yet + if err := config.CreateConfigFolder(appName); err != nil { + fmt.Println(err) + } + + // Calculate the configuration file path for saving when app parameters change + configFilePath = config.GetConfigFilePath(appName, configFileName) } func NewConfig() *Config { - return &Config{ + // Define the default Config struct + appConfig := &Config{ Enabled: true, - GitRepo: gitRepo, WorkingHoursInterval: workingHours10_19, WorkingHours: workingHours, } + + // Restores the configuration from disk if it exists + isRestored := config.LoadFileToStruct(configFilePath, appConfig) + + // Saves the struct to an configuration file if it doesn't exist yet + if !isRestored { + saveConfig(appConfig) + } + + return appConfig } func (c *Config) ToggleEnableDisable() { c.Enabled = !c.Enabled + + saveConfig(c) } func (c *Config) SetWorkingHoursInterval(interval string) { c.WorkingHoursInterval = interval + + saveConfig(c) +} + +func saveConfig(appConfig *Config) { + if err := config.SaveStructToFile(configFilePath, appConfig); err != nil { + fmt.Println(err) + } } diff --git a/internal/tray/tray.go b/internal/tray/tray.go index bfb585c..755e1f9 100644 --- a/internal/tray/tray.go +++ b/internal/tray/tray.go @@ -32,6 +32,9 @@ func loadIcon() ([]byte, error) { type Tray struct { mouseController *mouse.Controller conf *config.Config + mEnable *systray.MenuItem + mDisable *systray.MenuItem + mWorkingHours *systray.MenuItem workingHoursMenuItems map[string]*systray.MenuItem } @@ -58,25 +61,21 @@ func (t *Tray) onReady() { systray.SetTooltip("Enable or Disable periodic mouse movements") // Create menu items for enable/disable mouse movement, change working hours and exit - mEnable := systray.AddMenuItem("Enable", "Enable mouse movement") - mDisable := systray.AddMenuItem("Disable", "Disable mouse movement") - mWorkingHours := systray.AddMenuItem("Working hours", "Select a range of working hours") + t.mEnable = systray.AddMenuItem("Enable", "Enable mouse movement") + t.mDisable = systray.AddMenuItem("Disable", "Disable mouse movement") + t.mWorkingHours = systray.AddMenuItem("Working hours", "Select a range of working hours") systray.AddSeparator() mAbout := systray.AddMenuItem("About", "Open GitHub repo") mQuit := systray.AddMenuItem("Quit", "Quit the application") - // Hide the enable option since it's already enabled by default - if t.conf.Enabled { - mEnable.Hide() - } else { - mDisable.Hide() - } - // Add interval selection submenu items for _, hours := range t.conf.WorkingHours { - t.addWorkingHoursItems(mWorkingHours, hours) + t.addWorkingHoursItems(t.mWorkingHours, hours) } + // Adjust visibilities based on the app state + t.applyEnableDisable() + // Set a marker for the default working hours interval t.workingHoursMenuItems[t.conf.WorkingHoursInterval].Check() @@ -85,23 +84,19 @@ func (t *Tray) onReady() { go func() { for { select { - case <-mEnable.ClickedCh: + case <-t.mEnable.ClickedCh: t.conf.ToggleEnableDisable() - mEnable.Hide() - mDisable.Show() - mWorkingHours.Enable() + t.applyEnableDisable() go t.mouseController.MoveMouse() - case <-mDisable.ClickedCh: + case <-t.mDisable.ClickedCh: t.conf.ToggleEnableDisable() - mDisable.Hide() - mEnable.Show() - mWorkingHours.Disable() + t.applyEnableDisable() case workingHoursInterval := <-workingHoursIntervalClicks: // When an hours interval item is clicked, update the workingHoursInterval interval and checkmarks t.conf.SetWorkingHoursInterval(workingHoursInterval) t.updateNightModeIntervalChecks(t.conf.WorkingHoursInterval) case <-mAbout.ClickedCh: - if err := utils.OpenWebPage(t.conf.GitRepo); err != nil { + if err := utils.OpenWebPage(config.GitRepo); err != nil { panic(err) } case <-mQuit.ClickedCh: @@ -147,5 +142,18 @@ func (t *Tray) updateNightModeIntervalChecks(selectedInterval string) { } } +// Adjust visibilities based on the app state +func (t *Tray) applyEnableDisable() { + if t.conf.Enabled { + t.mEnable.Hide() + t.mDisable.Show() + t.mWorkingHours.Enable() + } else { + t.mEnable.Show() + t.mDisable.Hide() + t.mWorkingHours.Disable() + } +} + func (t *Tray) onExit() { } diff --git a/pkg/config/config.go b/pkg/config/config.go new file mode 100644 index 0000000..d0b5d5e --- /dev/null +++ b/pkg/config/config.go @@ -0,0 +1,70 @@ +package config + +import ( + "fmt" + "os" + + "github.com/adrg/xdg" + "gopkg.in/ini.v1" +) + +// Create a folder to save the config file if it doesn't exist yet +func CreateConfigFolder(appName string) error { + // Use xdg to get the config file folder path + configPath, err := xdg.ConfigFile(appName) + if err != nil { + return fmt.Errorf("error getting config file path: %w", err) + } + + // Create a home folder if it does not exist + if _, err := os.Stat(configPath); os.IsNotExist(err) { + if err := os.MkdirAll(configPath, os.ModePerm); err != nil { + return fmt.Errorf("error creating config directory: %w", err) + } + } + + return nil +} + +// Calculate the configuration file path +func GetConfigFilePath(appName, configFileName string) string { + configPath, err := xdg.ConfigFile(appName + "/" + configFileName) + if err != nil { + fmt.Println("Error getting config file path:", err) + return "" + } + return configPath +} + +// Saves the struct to an configuration file +func SaveStructToFile(configFilePath string, config any) error { + // Reflect data sources from given struct + cfg := ini.Empty() + if err := ini.ReflectFrom(cfg, config); err != nil { + return fmt.Errorf("error reflecting config struct: %w", err) + } + + // Save the configuration file into disk + if err := cfg.SaveTo(configFilePath); err != nil { + return fmt.Errorf("error saving config to file: %w", err) + } + + return nil +} + +// Restores the configuration file into the struct +func LoadFileToStruct(configFilePath string, config any) bool { + // Try to load the configuration file from disk if it exists + cfg, err := ini.Load(configFilePath) + if err != nil { + return false + } + + // Try to map the configuration file from disk into the struct + if err := cfg.MapTo(config); err != nil { + fmt.Printf("error mapping INI to config struct: %s", err.Error()) + return false + } + + return true +}