Release version 0.2.4
Prevent repeated application launches by using a local single-instance control channel. A second process forwards a show command to the already running instance and exits. Bump the application version to 0.2.4 and update README artifact examples plus docs/CHANGELOG.md.
This commit is contained in:
@@ -86,7 +86,7 @@ The binary is written to:
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
# GUI executable produced by scripts\build-windows.bat.
|
# GUI executable produced by scripts\build-windows.bat.
|
||||||
dist\windows\pysentry-0.2.3-windows-amd64.exe
|
dist\windows\pysentry-0.2.4-windows-amd64.exe
|
||||||
```
|
```
|
||||||
|
|
||||||
Linux:
|
Linux:
|
||||||
@@ -101,7 +101,7 @@ The binary is written to:
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
# Linux executable produced by scripts/build-linux.sh.
|
# Linux executable produced by scripts/build-linux.sh.
|
||||||
dist/linux/pysentry-0.2.3-linux-amd64
|
dist/linux/pysentry-0.2.4-linux-amd64
|
||||||
```
|
```
|
||||||
|
|
||||||
Linux using Docker:
|
Linux using Docker:
|
||||||
@@ -118,7 +118,7 @@ The binary is copied to:
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
# Linux executable copied out of the Docker build image.
|
# Linux executable copied out of the Docker build image.
|
||||||
dist\linux\pysentry-0.2.3-linux-amd64
|
dist\linux\pysentry-0.2.4-linux-amd64
|
||||||
```
|
```
|
||||||
|
|
||||||
Release build from Linux:
|
Release build from Linux:
|
||||||
@@ -143,13 +143,13 @@ The binaries are copied to:
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
# Linux artifact.
|
# Linux artifact.
|
||||||
dist/linux/pysentry-0.2.3-linux-amd64
|
dist/linux/pysentry-0.2.4-linux-amd64
|
||||||
|
|
||||||
# Linux arm64 artifact.
|
# Linux arm64 artifact.
|
||||||
dist/linux/pysentry-0.2.3-linux-arm64
|
dist/linux/pysentry-0.2.4-linux-arm64
|
||||||
|
|
||||||
# Windows artifact cross-compiled from Linux.
|
# Windows artifact cross-compiled from Linux.
|
||||||
dist/windows/pysentry-0.2.3-windows-amd64.exe
|
dist/windows/pysentry-0.2.4-windows-amd64.exe
|
||||||
```
|
```
|
||||||
|
|
||||||
## Run From Source
|
## Run From Source
|
||||||
@@ -292,7 +292,7 @@ Linux:
|
|||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
Type=Application
|
Type=Application
|
||||||
Name=PySentry
|
Name=PySentry
|
||||||
Exec=/opt/pysentry/pysentry-0.2.3-linux-amd64
|
Exec=/opt/pysentry/pysentry-0.2.4-linux-amd64
|
||||||
Terminal=false
|
Terminal=false
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,11 @@
|
|||||||
|
|
||||||
All notable PySentry changes are recorded in this file.
|
All notable PySentry changes are recorded in this file.
|
||||||
|
|
||||||
|
## 0.2.4 - 2026-06-16
|
||||||
|
|
||||||
|
- Prevented repeated application launches by forwarding a second start attempt to the already running instance.
|
||||||
|
- A second instance now asks the first instance to show and focus the existing window, then exits.
|
||||||
|
|
||||||
## 0.2.3 - 2026-06-15
|
## 0.2.3 - 2026-06-15
|
||||||
|
|
||||||
- Changed History to use chronological ordering with new records appended at the bottom.
|
- Changed History to use chronological ordering with new records appended at the bottom.
|
||||||
|
|||||||
+1
-1
@@ -3,4 +3,4 @@ package core
|
|||||||
// Version is the application version shown in the GUI and used by build
|
// 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
|
// 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.
|
// can override it with Go ldflags when CI tags a build.
|
||||||
var Version = "0.2.3"
|
var Version = "0.2.4"
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package gui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"runtime"
|
"runtime"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
@@ -31,6 +33,8 @@ const settingsLabelWidth float32 = 140
|
|||||||
const settingsControlWidth float32 = 330
|
const settingsControlWidth float32 = 330
|
||||||
const settingsStatusWidth float32 = 280
|
const settingsStatusWidth float32 = 280
|
||||||
const projectRepositoryURL = "https://gitea.mixdep.ru/mix/gosentry"
|
const projectRepositoryURL = "https://gitea.mixdep.ru/mix/gosentry"
|
||||||
|
const singleInstanceAddress = "127.0.0.1:37653"
|
||||||
|
const singleInstanceShowCommand = "show"
|
||||||
|
|
||||||
// The GUI package aliases core types to keep widget callbacks short. The actual
|
// The GUI package aliases core types to keep widget callbacks short. The actual
|
||||||
// durable model still lives in src/core, so GUI code does not define a second
|
// durable model still lives in src/core, so GUI code does not define a second
|
||||||
@@ -40,6 +44,14 @@ type event = core.RunRecord
|
|||||||
|
|
||||||
func Run() {
|
func Run() {
|
||||||
started := time.Now()
|
started := time.Now()
|
||||||
|
instanceListener, primary := acquireSingleInstance()
|
||||||
|
if !primary {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if instanceListener != nil {
|
||||||
|
defer instanceListener.Close()
|
||||||
|
}
|
||||||
|
|
||||||
// A stable app ID lets Fyne persist desktop preferences consistently across
|
// A stable app ID lets Fyne persist desktop preferences consistently across
|
||||||
// launches and gives tray/window integration a predictable identity.
|
// launches and gives tray/window integration a predictable identity.
|
||||||
a := app.NewWithID(appID)
|
a := app.NewWithID(appID)
|
||||||
@@ -49,6 +61,7 @@ func Run() {
|
|||||||
configureSystemTray(a, w)
|
configureSystemTray(a, w)
|
||||||
w.Resize(fyne.NewSize(1120, 720))
|
w.Resize(fyne.NewSize(1120, 720))
|
||||||
w.SetContent(newMainView(w, started))
|
w.SetContent(newMainView(w, started))
|
||||||
|
serveSingleInstance(instanceListener, w)
|
||||||
w.ShowAndRun()
|
w.ShowAndRun()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,6 +96,47 @@ func configureSystemTray(a fyne.App, w fyne.Window) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func acquireSingleInstance() (net.Listener, bool) {
|
||||||
|
listener, err := net.Listen("tcp", singleInstanceAddress)
|
||||||
|
if err == nil {
|
||||||
|
return listener, true
|
||||||
|
}
|
||||||
|
|
||||||
|
connection, dialErr := net.DialTimeout("tcp", singleInstanceAddress, time.Second)
|
||||||
|
if dialErr == nil {
|
||||||
|
_, _ = io.WriteString(connection, singleInstanceShowCommand)
|
||||||
|
_ = connection.Close()
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the port is unavailable but does not answer as GoSentry, continue
|
||||||
|
// startup instead of making the application impossible to open because of an
|
||||||
|
// unrelated local listener. In the normal duplicate-start case the dial above
|
||||||
|
// succeeds and this process exits after waking the first instance.
|
||||||
|
return nil, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveSingleInstance(listener net.Listener, w fyne.Window) {
|
||||||
|
if listener == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
connection, err := listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
command, _ := io.ReadAll(io.LimitReader(connection, 32))
|
||||||
|
_ = connection.Close()
|
||||||
|
if strings.TrimSpace(string(command)) != singleInstanceShowCommand {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
w.Show()
|
||||||
|
w.RequestFocus()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
func newMainView(w fyne.Window, started time.Time) fyne.CanvasObject {
|
func newMainView(w fyne.Window, started time.Time) fyne.CanvasObject {
|
||||||
store, jobs, err := core.OpenStore()
|
store, jobs, err := core.OpenStore()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user