🆕 Add window size persistence, exit codes, and screen size detection
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
README.md.tinyenc
|
||||||
secure-editor-go
|
secure-editor-go
|
||||||
|
|
||||||
# ---> macOS
|
# ---> macOS
|
||||||
|
|||||||
@@ -4,13 +4,17 @@ import (
|
|||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"image/color"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
"fyne.io/fyne/v2"
|
||||||
"fyne.io/fyne/v2/app"
|
"fyne.io/fyne/v2/app"
|
||||||
"fyne.io/fyne/v2/container"
|
"fyne.io/fyne/v2/container"
|
||||||
"fyne.io/fyne/v2/dialog"
|
"fyne.io/fyne/v2/dialog"
|
||||||
|
"fyne.io/fyne/v2/theme"
|
||||||
"fyne.io/fyne/v2/widget"
|
"fyne.io/fyne/v2/widget"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,6 +22,17 @@ const (
|
|||||||
Version = "0.1.1"
|
Version = "0.1.1"
|
||||||
Title = "Secure Editor"
|
Title = "Secure Editor"
|
||||||
AlgorithmAes256Gcm = "aes-256-gcm"
|
AlgorithmAes256Gcm = "aes-256-gcm"
|
||||||
|
|
||||||
|
DefaultWindowWidth = 800.0
|
||||||
|
DefaultWindowHeight = 600.0
|
||||||
|
|
||||||
|
ConfigFileName = "secure-editor-go-config.json"
|
||||||
|
|
||||||
|
// Exit codes:
|
||||||
|
// 0 -> file was saved and the editor exited
|
||||||
|
// 2 -> editor closed without saving
|
||||||
|
ExitCodeOK = 0
|
||||||
|
ExitCodeNoSave = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
func decrypt(ciphertext, key, nonce []byte) ([]byte, error) {
|
func decrypt(ciphertext, key, nonce []byte) ([]byte, error) {
|
||||||
@@ -74,6 +89,93 @@ func isReadonly() bool {
|
|||||||
return os.Getenv("READONLY") == "true"
|
return os.Getenv("READONLY") == "true"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// windowConfig is persisted to ~/.cache/secure-editor-go-config.json so the
|
||||||
|
// editor remembers its window size across runs.
|
||||||
|
type windowConfig struct {
|
||||||
|
Width float32 `json:"width"`
|
||||||
|
Height float32 `json:"height"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func configPath() (string, error) {
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return filepath.Join(home, ".cache", ConfigFileName), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultConfig() windowConfig {
|
||||||
|
return windowConfig{Width: DefaultWindowWidth, Height: DefaultWindowHeight}
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadConfig() windowConfig {
|
||||||
|
p, err := configPath()
|
||||||
|
if err != nil {
|
||||||
|
return defaultConfig()
|
||||||
|
}
|
||||||
|
data, err := os.ReadFile(p)
|
||||||
|
if err != nil {
|
||||||
|
return defaultConfig()
|
||||||
|
}
|
||||||
|
var c windowConfig
|
||||||
|
if err := json.Unmarshal(data, &c); err != nil {
|
||||||
|
return defaultConfig()
|
||||||
|
}
|
||||||
|
if c.Width <= 0 || c.Height <= 0 {
|
||||||
|
return defaultConfig()
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveConfig(size fyne.Size) {
|
||||||
|
p, err := configPath()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(filepath.Dir(p), 0755); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c := windowConfig{Width: size.Width, Height: size.Height}
|
||||||
|
data, err := json.MarshalIndent(c, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = os.WriteFile(p, data, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
// clampToScreen shrinks size so it never exceeds the visible desktop area.
|
||||||
|
// When the desktop size can't be determined it returns size unchanged.
|
||||||
|
func clampToScreen(size fyne.Size) fyne.Size {
|
||||||
|
sw, sh := desktopSize()
|
||||||
|
if sw <= 0 || sh <= 0 {
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
w, h := size.Width, size.Height
|
||||||
|
if w > sw {
|
||||||
|
w = sw
|
||||||
|
}
|
||||||
|
if h > sh {
|
||||||
|
h = sh
|
||||||
|
}
|
||||||
|
return fyne.NewSize(w, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// lightEditorTheme forces a light variant with a pure white input background
|
||||||
|
// so the editable text area reads as a white editing surface regardless of
|
||||||
|
// the system appearance setting. Readonly mode keeps the default theme (grey).
|
||||||
|
type lightEditorTheme struct {
|
||||||
|
fyne.Theme
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *lightEditorTheme) Color(name fyne.ThemeColorName, _ fyne.ThemeVariant) color.Color {
|
||||||
|
if name == theme.ColorNameInputBackground {
|
||||||
|
return color.White
|
||||||
|
}
|
||||||
|
// Always resolve against the light variant so white input background
|
||||||
|
// stays paired with dark, readable text.
|
||||||
|
return t.Theme.Color(name, theme.VariantLight)
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
args := os.Args
|
args := os.Args
|
||||||
if len(args) == 2 && args[1] == "version" {
|
if len(args) == 2 && args[1] == "version" {
|
||||||
@@ -126,9 +228,19 @@ func main() {
|
|||||||
isReadonly := isReadonly()
|
isReadonly := isReadonly()
|
||||||
|
|
||||||
secureEditorApp := app.New()
|
secureEditorApp := app.New()
|
||||||
|
if !isReadonly {
|
||||||
|
secureEditorApp.Settings().SetTheme(&lightEditorTheme{Theme: theme.DefaultTheme()})
|
||||||
|
}
|
||||||
window := secureEditorApp.NewWindow(Title)
|
window := secureEditorApp.NewWindow(Title)
|
||||||
window.Resize(fyne.NewSize(800, 600))
|
|
||||||
|
cfg := loadConfig()
|
||||||
|
window.Resize(clampToScreen(fyne.NewSize(cfg.Width, cfg.Height)))
|
||||||
|
|
||||||
|
// exitCode defaults to "closed without saving". The Save button raises
|
||||||
|
// it to ExitCodeOK. It is read after the event loop ends.
|
||||||
|
exitCode := ExitCodeNoSave
|
||||||
window.SetOnClosed(func() {
|
window.SetOnClosed(func() {
|
||||||
|
saveConfig(window.Canvas().Size())
|
||||||
secureEditorApp.Quit()
|
secureEditorApp.Quit()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -147,6 +259,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
exitButton := widget.NewButton(" [ Exit ] ", func() {
|
exitButton := widget.NewButton(" [ Exit ] ", func() {
|
||||||
|
exitCode = ExitCodeNoSave
|
||||||
secureEditorApp.Quit()
|
secureEditorApp.Quit()
|
||||||
})
|
})
|
||||||
saveButton := widget.NewButton(" [ Save ] ", func() {
|
saveButton := widget.NewButton(" [ Save ] ", func() {
|
||||||
@@ -161,6 +274,7 @@ func main() {
|
|||||||
showErrorMessage(err, window)
|
showErrorMessage(err, window)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
exitCode = ExitCodeOK
|
||||||
secureEditorApp.Quit()
|
secureEditorApp.Quit()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -180,4 +294,6 @@ func main() {
|
|||||||
window.SetContent(content)
|
window.SetContent(content)
|
||||||
window.CenterOnScreen()
|
window.CenterOnScreen()
|
||||||
window.ShowAndRun()
|
window.ShowAndRun()
|
||||||
|
|
||||||
|
os.Exit(exitCode)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
//go:build darwin
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// desktopSize returns the visible desktop size in points (device-independent
|
||||||
|
// pixels), matching Fyne's coordinate system. It uses osascript instead of
|
||||||
|
// cgo so the binary stays a plain Go build. Returns (0, 0) if it can't be
|
||||||
|
// determined, in which case the caller skips clamping.
|
||||||
|
func desktopSize() (float32, float32) {
|
||||||
|
// "bounds of window of desktop" -> "0, 0, W, H" in points.
|
||||||
|
out, err := exec.Command("osascript", "-e",
|
||||||
|
`tell application "Finder" to get bounds of window of desktop`).Output()
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0
|
||||||
|
}
|
||||||
|
parts := strings.Split(strings.TrimSpace(string(out)), ", ")
|
||||||
|
if len(parts) < 4 {
|
||||||
|
return 0, 0
|
||||||
|
}
|
||||||
|
w, errW := strconv.ParseFloat(strings.TrimSpace(parts[2]), 32)
|
||||||
|
h, errH := strconv.ParseFloat(strings.TrimSpace(parts[3]), 32)
|
||||||
|
if errW != nil || errH != nil {
|
||||||
|
return 0, 0
|
||||||
|
}
|
||||||
|
return float32(w), float32(h)
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
//go:build !darwin
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
// desktopSize is a no-op fallback on non-darwin platforms where we don't have
|
||||||
|
// a CGo-free way to query the screen. Returns (0, 0) so the caller skips
|
||||||
|
// clamping and uses the saved/default size as-is.
|
||||||
|
func desktopSize() (float32, float32) {
|
||||||
|
return 0, 0
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user