From 0b573e3de82e91774af1e95dc170a8130390ba09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enzo=20Guti=C3=A9rrez=20Pereira?= Date: Tue, 14 Jul 2026 23:16:02 -0300 Subject: [PATCH 1/2] alert if user tries to update with session running --- cmd/update_windows.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cmd/update_windows.go b/cmd/update_windows.go index c8e8a1d..e3d7bfc 100644 --- a/cmd/update_windows.go +++ b/cmd/update_windows.go @@ -79,6 +79,14 @@ var updateWindowsCmd = &cobra.Command{ Use: "update", Short: "Update blindspot (CLI and tray)", Run: func(cmd *cobra.Command, args []string) { + // A running session keeps blindspot.exe open, so Windows can't overwrite it + // during the update (the move fails with the file in use). Tell the user to + // disconnect first, in red, and bail out before attempting anything. + if isSessionRunning() { + fmt.Printf("\033[31mYou are connected to a blindspot network. Disconnect before updating (run: blindspot disconnect).\033[0m\n") + os.Exit(1) + } + exe, err := os.Executable() if err != nil { fmt.Fprintf(os.Stderr, "Could not locate the blindspot executable: %v\n", err) From cc92194c7b3062139cac2ed4ae1a01ff038233c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enzo=20Guti=C3=A9rrez=20Pereira?= Date: Tue, 14 Jul 2026 23:38:52 -0300 Subject: [PATCH 2/2] tray with custom rendezvous --- .../blindspot/internal/gui/trayservice.js | 12 ++++---- internal/gui/frontend/public/style.css | 2 ++ internal/gui/frontend/src/App.tsx | 28 +++++++++++++++++-- internal/gui/main.go | 13 ++++++--- internal/gui/trayservice.go | 12 +++++--- 5 files changed, 52 insertions(+), 15 deletions(-) diff --git a/internal/gui/frontend/bindings/github.com/neozmmv/blindspot/internal/gui/trayservice.js b/internal/gui/frontend/bindings/github.com/neozmmv/blindspot/internal/gui/trayservice.js index c5a1c1a..3018255 100644 --- a/internal/gui/frontend/bindings/github.com/neozmmv/blindspot/internal/gui/trayservice.js +++ b/internal/gui/frontend/bindings/github.com/neozmmv/blindspot/internal/gui/trayservice.js @@ -28,16 +28,18 @@ export function CancelReceive() { } /** - * Connect runs `blindspot connect -s -p [-n]`, which triggers - * the UAC elevation + daemon launch and blocks until the session is up (or fails). - * It returns the final status line the CLI printed. + * Connect runs `blindspot connect -s -p [-n] [-H ]`, + * which triggers the UAC elevation + daemon launch and blocks until the session is + * up (or fails). A non-empty hostname overrides the default rendezvous server. It + * returns the final status line the CLI printed. * @param {string} session * @param {string} password * @param {boolean} isNew + * @param {string} hostname * @returns {$CancellablePromise} */ -export function Connect(session, password, isNew) { - return $Call.ByID(1698713851, session, password, isNew); +export function Connect(session, password, isNew, hostname) { + return $Call.ByID(1698713851, session, password, isNew, hostname); } /** diff --git a/internal/gui/frontend/public/style.css b/internal/gui/frontend/public/style.css index 3dd04aa..a4590c4 100644 --- a/internal/gui/frontend/public/style.css +++ b/internal/gui/frontend/public/style.css @@ -376,6 +376,8 @@ button { font-family: inherit; } user-select: none; } .checkbox input { width: 16px; height: 16px; accent-color: var(--blue); } +/* The custom-rendezvous field appears directly under its checkbox; give it room. */ +.checkbox + .input { margin-top: 8px; } .btn-primary.btn-full { margin-top: 16px; padding: 11px; } .connect-note { margin: 12px 0 0; font-size: 11.5px; line-height: 1.5; color: var(--text-3); } diff --git a/internal/gui/frontend/src/App.tsx b/internal/gui/frontend/src/App.tsx index b90219f..1379a94 100644 --- a/internal/gui/frontend/src/App.tsx +++ b/internal/gui/frontend/src/App.tsx @@ -63,6 +63,8 @@ function App() { const [session, setSession] = useState('') const [password, setPassword] = useState('') const [isNew, setIsNew] = useState(false) + const [useCustomRendezvous, setUseCustomRendezvous] = useState(false) + const [hostname, setHostname] = useState('') const [copied, setCopied] = useState(false) const [copiedPeer, setCopiedPeer] = useState('') const [version, setVersion] = useState('') @@ -103,7 +105,8 @@ function App() { const doConnect = async () => { setNotice('') try { - const msg = await TrayService.Connect(session, password, isNew) + const rendezvous = useCustomRendezvous ? hostname : '' + const msg = await TrayService.Connect(session, password, isNew, rendezvous) flash(msg || 'Connected.') setPassword('') } catch (e: any) { @@ -255,7 +258,7 @@ function App() { setSession(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter' && session) doConnect() }} @@ -280,6 +283,27 @@ function App() { Create a new encrypted network + + + {useCustomRendezvous && ( + setHostname(e.target.value)} + onKeyDown={(e) => { if (e.key === 'Enter' && session) doConnect() }} + autoComplete="off" + /> + )} + diff --git a/internal/gui/main.go b/internal/gui/main.go index 8450ef1..e86d6b2 100644 --- a/internal/gui/main.go +++ b/internal/gui/main.go @@ -31,8 +31,11 @@ func Run() { tray := &TrayService{} // Declared up front so the SingleInstance callback below (and the tray menu) can - // close over it; assigned once the app exists. + // close over them; assigned once the app exists. The tray is captured too so those + // paths open the panel anchored to the tray icon (ShowWindow) rather than letting + // Wails center a bare window.Show(). var window *application.WebviewWindow + var systemTray *application.SystemTray app := application.New(application.Options{ Name: "Blindspot", @@ -51,14 +54,16 @@ func Run() { SingleInstance: &application.SingleInstanceOptions{ UniqueID: "dev.enzogp.blindspot.tray", OnSecondInstanceLaunch: func(application.SecondInstanceData) { - if window != nil { + if systemTray != nil { + systemTray.ShowWindow() // anchored to the tray, like a tray-icon click + } else if window != nil { window.Show().Focus() } }, }, }) - systemTray := app.SystemTray.New() + systemTray = app.SystemTray.New() // quitting flips true only when the user chooses Quit, so the close hook below // knows to let the app actually terminate instead of just hiding the window. @@ -135,7 +140,7 @@ func Run() { // setting a menu makes Wails wire right-click to open it (see SystemTray.bind). trayMenu := application.NewMenu() trayMenu.Add("Show Blindspot").OnClick(func(_ *application.Context) { - window.Show().Focus() + systemTray.ShowWindow() // anchored to the tray, like a tray-icon click }) trayMenu.AddSeparator() trayMenu.Add("Quit Blindspot").OnClick(func(_ *application.Context) { diff --git a/internal/gui/trayservice.go b/internal/gui/trayservice.go index 9243652..77cc516 100644 --- a/internal/gui/trayservice.go +++ b/internal/gui/trayservice.go @@ -189,10 +189,11 @@ func (s *TrayService) clearTransferAfter(msg string, d time.Duration) { }() } -// Connect runs `blindspot connect -s -p [-n]`, which triggers -// the UAC elevation + daemon launch and blocks until the session is up (or fails). -// It returns the final status line the CLI printed. -func (s *TrayService) Connect(session, password string, isNew bool) (string, error) { +// Connect runs `blindspot connect -s -p [-n] [-H ]`, +// which triggers the UAC elevation + daemon launch and blocks until the session is +// up (or fails). A non-empty hostname overrides the default rendezvous server. It +// returns the final status line the CLI printed. +func (s *TrayService) Connect(session, password string, isNew bool, hostname string) (string, error) { session = strings.TrimSpace(session) if session == "" { return "", fmt.Errorf("session name is required") @@ -211,6 +212,9 @@ func (s *TrayService) Connect(session, password string, isNew bool) (string, err if isNew { args = append(args, "-n") } + if hostname = strings.TrimSpace(hostname); hostname != "" { + args = append(args, "-H", hostname) + } s.setBusy(true) defer s.setBusy(false)