Bump version and add changelog

This commit is contained in:
mixeme
2026-06-15 08:23:24 +03:00
parent 91080a7a9d
commit ddabfd2da2
7 changed files with 184 additions and 69 deletions
+83 -45
View File
@@ -7,76 +7,68 @@ import (
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
)
const autostartUnitName = "pysentry.service"
const autostartDesktopFileName = "pysentry.desktop"
func SetAutostart(enabled bool, executablePath string) error {
unitDir, err := userSystemdDir()
desktopPath, err := autostartDesktopPath()
if err != nil {
return err
}
unitPath := filepath.Join(unitDir, autostartUnitName)
if enabled {
if err := os.MkdirAll(unitDir, 0o755); err != nil {
return err
}
unit := fmt.Sprintf(`[Unit]
Description=PySentry desktop scheduler
[Service]
ExecStart=%s
Restart=on-failure
[Install]
WantedBy=default.target
`, executablePath)
if err := os.WriteFile(unitPath, []byte(unit), 0o644); err != nil {
return err
}
if err := exec.Command("systemctl", "--user", "daemon-reload").Run(); err != nil {
return err
}
return exec.Command("systemctl", "--user", "enable", "--now", autostartUnitName).Run()
}
_ = exec.Command("systemctl", "--user", "disable", "--now", autostartUnitName).Run()
if err := os.Remove(unitPath); err != nil && !os.IsNotExist(err) {
if err := cleanupLegacySystemdAutostart(); err != nil {
return err
}
return exec.Command("systemctl", "--user", "daemon-reload").Run()
if enabled {
if err := os.MkdirAll(filepath.Dir(desktopPath), 0o755); err != nil {
return err
}
desktopFile := fmt.Sprintf(`[Desktop Entry]
Type=Application
Name=PySentry
Comment=PySentry desktop scheduler
Exec=%s
Terminal=false
X-GNOME-Autostart-enabled=true
`, quoteDesktopExec(executablePath))
return os.WriteFile(desktopPath, []byte(desktopFile), 0o644)
}
if err := os.Remove(desktopPath); err != nil && !os.IsNotExist(err) {
return err
}
return nil
}
func AutostartStatus(expectedEnabled bool, executablePath string) (bool, string) {
unitDir, err := userSystemdDir()
desktopPath, err := autostartDesktopPath()
if err != nil {
return false, "Cannot resolve user systemd directory"
return false, "Cannot resolve XDG autostart directory"
}
unitPath := filepath.Join(unitDir, autostartUnitName)
data, readErr := os.ReadFile(unitPath)
enabledErr := exec.Command("systemctl", "--user", "is-enabled", "--quiet", autostartUnitName).Run()
if legacySystemdAutostartExists() {
return false, "Legacy systemd autostart entry still exists"
}
data, readErr := os.ReadFile(desktopPath)
if !expectedEnabled {
if os.IsNotExist(readErr) && enabledErr != nil {
if os.IsNotExist(readErr) {
return true, "Autostart is off"
}
return false, "Autostart unit exists while setting is off"
return false, "Autostart desktop entry exists while setting is off"
}
if readErr != nil {
return false, "Autostart unit is missing"
return false, "Autostart desktop entry is missing"
}
if !strings.Contains(string(data), executablePath) {
return false, "Autostart unit points to another executable"
}
if enabledErr != nil {
return false, "Autostart unit is not enabled"
if !strings.Contains(string(data), "Exec="+quoteDesktopExec(executablePath)) {
return false, "Autostart desktop entry points to another executable"
}
return true, "Autostart is configured"
}
func userSystemdDir() (string, error) {
func autostartDesktopPath() (string, error) {
configHome := os.Getenv("XDG_CONFIG_HOME")
if configHome == "" {
home, err := os.UserHomeDir()
@@ -85,5 +77,51 @@ func userSystemdDir() (string, error) {
}
configHome = filepath.Join(home, ".config")
}
return filepath.Join(configHome, "systemd", "user"), nil
return filepath.Join(configHome, "autostart", autostartDesktopFileName), nil
}
func quoteDesktopExec(path string) string {
return strconv.Quote(path)
}
func cleanupLegacySystemdAutostart() error {
unitPath, err := legacySystemdUnitPath()
if err != nil {
return err
}
if _, err := os.Stat(unitPath); os.IsNotExist(err) {
return nil
}
// Older PySentry builds used a systemd user unit for autostart. The current
// Linux implementation uses XDG Autostart because PySentry is a GUI/tray
// application and should be launched by the desktop session. Disable and
// remove the old unit so the two mechanisms do not fight or start duplicates.
_ = exec.Command("systemctl", "--user", "disable", "pysentry.service").Run()
if err := os.Remove(unitPath); err != nil && !os.IsNotExist(err) {
return err
}
_ = exec.Command("systemctl", "--user", "daemon-reload").Run()
return nil
}
func legacySystemdAutostartExists() bool {
unitPath, err := legacySystemdUnitPath()
if err != nil {
return false
}
_, err = os.Stat(unitPath)
return err == nil
}
func legacySystemdUnitPath() (string, error) {
configHome := os.Getenv("XDG_CONFIG_HOME")
if configHome == "" {
home, err := os.UserHomeDir()
if err != nil {
return "", err
}
configHome = filepath.Join(home, ".config")
}
return filepath.Join(configHome, "systemd", "user", "pysentry.service"), nil
}
+28 -4
View File
@@ -1,8 +1,9 @@
package core
import (
"fmt"
"os/exec"
"path/filepath"
"strconv"
"strings"
)
@@ -17,7 +18,7 @@ func SetAutostart(enabled bool, executablePath string) error {
configureHiddenWindow(deleteCommand)
_ = deleteCommand.Run()
command := exec.Command("reg.exe", "add", `HKCU\Software\Microsoft\Windows\CurrentVersion\Run`, "/v", autostartName, "/t", "REG_SZ", "/d", fmt.Sprintf("%q", executablePath), "/f")
command := exec.Command("reg.exe", "add", `HKCU\Software\Microsoft\Windows\CurrentVersion\Run`, "/v", autostartName, "/t", "REG_SZ", "/d", strconv.Quote(executablePath), "/f")
configureHiddenWindow(command)
return command.Run()
}
@@ -41,9 +42,32 @@ func AutostartStatus(expectedEnabled bool, executablePath string) (bool, string)
return false, "Autostart entry is missing"
}
text := strings.ReplaceAll(string(output), `"`, "")
if !strings.Contains(text, executablePath) {
actual, ok := parseRegistryRunValue(string(output))
if !ok {
return false, "Autostart entry cannot be read"
}
if !sameWindowsPath(actual, executablePath) {
return false, "Autostart points to another executable"
}
return true, "Autostart is configured"
}
func parseRegistryRunValue(output string) (string, bool) {
for _, line := range strings.Split(output, "\n") {
fields := strings.Fields(strings.TrimSpace(line))
for index, field := range fields {
if field == "REG_SZ" && index+1 < len(fields) {
value := strings.Join(fields[index+1:], " ")
value = strings.Trim(value, `"`)
return value, value != ""
}
}
}
return "", false
}
func sameWindowsPath(left string, right string) bool {
left = filepath.Clean(strings.Trim(left, `"`))
right = filepath.Clean(strings.Trim(right, `"`))
return strings.EqualFold(left, right)
}
+25
View File
@@ -0,0 +1,25 @@
//go:build windows
package core
import "testing"
func TestParseRegistryRunValue(t *testing.T) {
output := `
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run
PySentry REG_SZ "D:\Apps\PySentry\pysentry.exe"
`
value, ok := parseRegistryRunValue(output)
if !ok {
t.Fatal("expected registry value to parse")
}
if value != `D:\Apps\PySentry\pysentry.exe` {
t.Fatalf("unexpected value: %q", value)
}
}
func TestSameWindowsPathIgnoresCaseAndQuotes(t *testing.T) {
if !sameWindowsPath(`"D:\Apps\PySentry\pysentry.exe"`, `d:\apps\pysentry\pysentry.exe`) {
t.Fatal("expected paths to match")
}
}
+1 -1
View File
@@ -3,4 +3,4 @@ package core
// Version is the application version shown in the GUI and used by build
// scripts in artifact names. It is a var rather than a const so release builds
// can override it with Go ldflags when CI tags a build.
var Version = "0.1.0"
var Version = "0.2.0"
+5 -1
View File
@@ -591,7 +591,7 @@ func settingsView(w fyne.Window, store *core.Store, jobs *[]job) fyne.CanvasObje
startOnLogin.SetChecked(store.Config.StartOnLogin)
autostartStatus := widget.NewLabel("")
refreshAutostartStatus := func() {
ok, message := core.AutostartStatus(startOnLogin.Checked, store.Paths.ExecutablePath)
ok, message := core.AutostartStatus(store.Config.StartOnLogin, store.Paths.ExecutablePath)
if ok {
autostartStatus.SetText("OK: " + message)
return
@@ -599,6 +599,10 @@ func settingsView(w fyne.Window, store *core.Store, jobs *[]job) fyne.CanvasObje
autostartStatus.SetText("Problem: " + message)
}
startOnLogin.OnChanged = func(bool) {
if startOnLogin.Checked != store.Config.StartOnLogin {
autostartStatus.SetText("Pending: save settings to apply")
return
}
refreshAutostartStatus()
}
refreshAutostartStatus()