b46e4ed7fa
Extract three focused modules from run.go and mainwindow.go: - tray.go: configureSystemTray() and related tray setup - singleinstance.go: acquireSingleInstance(), serveSingleInstance(), and constants - layout.go: minWidthLayout type and methods No behavior change; purely mechanical extraction to improve file organization. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
65 lines
1.7 KiB
Go
65 lines
1.7 KiB
Go
package ui
|
|
|
|
import (
|
|
"io"
|
|
"net"
|
|
"strings"
|
|
"time"
|
|
|
|
"fyne.io/fyne/v2"
|
|
)
|
|
|
|
const singleInstanceAddress = "127.0.0.1:37653"
|
|
const singleInstanceShowCommand = "show"
|
|
|
|
func acquireSingleInstance(showExisting bool) (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 {
|
|
// The first instance listens only on localhost and understands one tiny
|
|
// command: "show". That keeps the implementation dependency-free and easy
|
|
// to inspect, which matters more here than introducing a named-pipe or
|
|
// platform-specific IPC abstraction just to focus an existing window.
|
|
if showExisting {
|
|
_, _ = 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
|
|
}
|
|
// Accept runs on its own goroutine, so focusing the window must be
|
|
// marshaled onto the main thread like every other widget update.
|
|
fyne.Do(func() {
|
|
w.Show()
|
|
w.RequestFocus()
|
|
})
|
|
}
|
|
}()
|
|
}
|