openvibers/openvibe.link Commits
v0.37.2: each Node directory has its own Windows sign-in start (#99)
Changes
@@@ -5,7 +5,7 @@"wave": "T15","plan": "Plan track T15 (Bot complete) and ADR-043 (Bot: devices, pairing, control and safety); the computer plugin and the job worker serve OpenVibe.Actor and the Codes job runner (tracks T14/T17)","kind": "client",-- "version": "0.37.1",++ "version": "0.37.2","release": "v0.36.0: the ai plugin (0.2.0) passes tools to the machine's own model server and hands back the calls the model asked for, never running one, so an agent on OpenVibe (Actor) can work on a model this machine serves; its capabilities say tools: true (contracts 0.277.0). v0.35.0: the Node applies the roles the network's machines hub gives it at each check-in (codes.worker starts the job worker it was installed with, desks.host and ai.host are checked against what the machine was installed for, an unknown role is ignored, and the local configuration always wins), and its status reports them back; a desk's first browser start in a cold sandbox gets 90 s. v0.34.0: on Windows a killed plugin takes every process it started with it (a Job Object with kill-on-close), as the process group does on Linux and macOS, so a bridge helper cannot keep a robot's link open after the Node stops or restarts its plugin; the Node reads what the coding tools remember (read-only, local, redacted) and `openvibe-node memory` uploads it to your own store; the heartbeat carries the machine's fleet of coding agents; the Codes job runner pin is v0.5.2","runtime": "go 1.24 (one static binary per platform); plugins in Python 3; an ESP32 Arduino library","database": "none",@@@ -193,7 +193,7 @@ func runWithStdin(args []string, stdin io.Reader, stdout, stderr io.Writer) interr = cmdInstall(g, *svcUser, stdout)case "uninstall":if *session {-- err = cmdUninstallUserSession(stdout)++ err = cmdUninstallUserSession(g, stdout)break}err = service.Control(serviceOptions(g, ""), "stop")@@@ -21,6 +21,20 @@ const (runValueName = "OpenVibe Node")++// runValueFor is the Run value of one Node directory. A machine may run more than one Node at sign-in (a working++// robot and a test node, 11-02), so each --home has its own value; before v0.37.2 every install wrote the one++// "OpenVibe Node" value, and installing a second home replaced the first's sign-in start.++func runValueFor(home string) string { return runValueName + " (" + home + ")" }++++// legacyRunsHome says whether a Run value's command line (the old single "OpenVibe Node" value) starts this home:++// only then is it this Node's to move or remove. A value that starts another home is another Node's and stays.++func legacyRunsHome(data, home string) bool {++ if data == "" || home == "" {++ return false++ }++ return strings.Contains(data, " --home "+quoteArg(home)+" ") || strings.HasSuffix(data, " --home "+quoteArg(home))++}++var errNeedHome = errors.New(`--user-session needs --home DIR: the directory you paired with, for example --home "%LOCALAPPDATA%\OpenVibe Node"`)// userSessionCommand is the command line the Run key starts: the executable, `run`, and the Node's own directory.@@@ -59,26 +73,61 @@ func cmdInstallUserSession(g *globals, stdout io.Writer) error {return err}line := userSessionCommand(exe, home, g.logLevel)-- if err := registerUserSession(line); err != nil {++ if err := registerUserSession(runValueFor(home), line); err != nil {return err}++ // The old single value, if it started this same home, is this Node's: the new value replaces it.++ if data, ok := readUserSession(runValueName); ok && legacyRunsHome(data, home) {++ _ = unregisterUserSession(runValueName)++ }fmt.Fprintln(stdout, "The Node now starts when you sign in, in its own window:", line)if err := startUserSession(exe, home, g.logLevel); err != nil {fmt.Fprintln(stdout, "It did not start now (", err, "); sign out and in, or run the line above.")} else {fmt.Fprintln(stdout, "Started it now. Closing its window stops the Node and every actuator.")}-- fmt.Fprintln(stdout, "Remove the sign-in start with `openvibe-node uninstall --user-session`.")++ fmt.Fprintf(stdout, "Remove the sign-in start with `openvibe-node uninstall --user-session --home %s`.\n", quoteArg(home))return nil}--func cmdUninstallUserSession(stdout io.Writer) error {++func cmdUninstallUserSession(g *globals, stdout io.Writer) error {if err := userSessionSupported(); err != nil {return err}-- if err := unregisterUserSession(); err != nil {++ if g.home == "" {++ return errNeedHome++ }++ home, err := filepath.Abs(g.home)++ if err != nil {++ return err++ }++ if err := unregisterUserSession(runValueFor(home)); err != nil {return err}++ // An install from before v0.37.2 wrote the single "OpenVibe Node" value; it is removed only if it starts this home.++ if data, ok := readUserSession(runValueName); ok && legacyRunsHome(data, home) {++ if err := unregisterUserSession(runValueName); err != nil {++ return err++ }++ }fmt.Fprintln(stdout, "The Node no longer starts at sign-in. Close its window to stop the running one; config and credential are kept.")return nil}++++// parseRegQuery finds a value's data in `reg query <key> /v <name>` output, whose line reads++// " <name> REG_SZ <data>". ok is false when the value is not there.++func parseRegQuery(out, name string) (string, bool) {++ for _, line := range strings.Split(strings.ReplaceAll(out, "\r\n", "\n"), "\n") {++ t := strings.TrimSpace(line)++ if !strings.HasPrefix(t, name+" ") {++ continue++ }++ rest := strings.TrimSpace(strings.TrimPrefix(t, name))++ for _, typ := range []string{"REG_SZ", "REG_EXPAND_SZ"} {++ if strings.HasPrefix(rest, typ) {++ return strings.TrimSpace(strings.TrimPrefix(rest, typ)), true++ }++ }++ }++ return "", false++}@@@ -7,6 +7,7 @@ import "errors"var errUserSessionWindowsOnly = errors.New("--user-session is for Windows; on Linux and macOS install the service (sudo openvibe-node install), or start `openvibe-node run --home DIR` from your desktop's autostart")func userSessionSupported() error { return errUserSessionWindowsOnly }--func registerUserSession(string) error { return errUserSessionWindowsOnly }--func unregisterUserSession() error { return errUserSessionWindowsOnly }++func registerUserSession(string, string) error { return errUserSessionWindowsOnly }++func unregisterUserSession(string) error { return errUserSessionWindowsOnly }++func readUserSession(string) (string, bool) { return "", false }func startUserSession(string, string, string) error { return errUserSessionWindowsOnly }@@@ -27,3 +27,36 @@ func TestUserSessionRefusedOffWindows(t *testing.T) {t.Fatalf("code %d, stderr %q", code, errb.String())}}++++// Each Node directory has its own sign-in start (v0.37.2): a test node beside a working robot's Node no longer replaces++// its Run value, and the old single value is this Node's only when it starts this same home.++func TestUserSessionPerHome(t *testing.T) {++ cozmo, test := `C:\Users\m\AppData\Local\OpenVibe Node`, `D:\looi-test`++ if a, b := runValueFor(cozmo), runValueFor(test); a == b || !strings.HasPrefix(a, "OpenVibe Node (") {++ t.Fatalf("one value per home: %q %q", a, b)++ }++ legacy := userSessionCommand(`C:\bin\openvibe-node.exe`, cozmo, "info")++ if !legacyRunsHome(legacy, cozmo) {++ t.Fatal("the old value starts the Cozmo home")++ }++ if legacyRunsHome(legacy, test) {++ t.Fatal("the old value is another Node's: never this test home's")++ }++ if legacyRunsHome(userSessionCommand(`C:\bin\n.exe`, `D:\ov2`, "debug"), `D:\ov`) {++ t.Fatal(`D:\ov is not D:\ov2`)++ }++ if !legacyRunsHome(userSessionCommand(`C:\bin\n.exe`, `D:\ov`, "debug"), `D:\ov`) {++ t.Fatal("with a log level after it, the home still matches")++ }++}++++func TestParseRegQuery(t *testing.T) {++ out := "\r\nHKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\r\n OpenVibe Node REG_SZ \"C:\\bin\\openvibe-node.exe\" run --home \"C:\\Users\\m\\OpenVibe Node\"\r\n\r\n"++ got, ok := parseRegQuery(out, "OpenVibe Node")++ if !ok || got != `"C:\bin\openvibe-node.exe" run --home "C:\Users\m\OpenVibe Node"` {++ t.Fatalf("got %q %v", got, ok)++ }++ if _, ok := parseRegQuery(out, "OpenVibe Node (D:\\x)"); ok {++ t.Fatal("another value is not found")++ }++}@@@ -10,22 +10,31 @@ import (func userSessionSupported() error { return nil }--func registerUserSession(line string) error {-- out, err := exec.Command("reg", "add", runKey, "/v", runValueName, "/t", "REG_SZ", "/d", line, "/f").CombinedOutput()++func registerUserSession(name, line string) error {++ out, err := exec.Command("reg", "add", runKey, "/v", name, "/t", "REG_SZ", "/d", line, "/f").CombinedOutput()if err != nil {return fmt.Errorf("reg add %s: %v: %s", runKey, err, strings.TrimSpace(string(out)))}return nil}--func unregisterUserSession() error {-- out, err := exec.Command("reg", "delete", runKey, "/v", runValueName, "/f").CombinedOutput()++func unregisterUserSession(name string) error {++ out, err := exec.Command("reg", "delete", runKey, "/v", name, "/f").CombinedOutput()if err != nil && !strings.Contains(strings.ToLower(string(out)), "unable to find") {return fmt.Errorf("reg delete %s: %v: %s", runKey, err, strings.TrimSpace(string(out)))}return nil}++// readUserSession reads one Run value's command line; ok is false when there is no such value.++func readUserSession(name string) (string, bool) {++ out, err := exec.Command("reg", "query", runKey, "/v", name).CombinedOutput()++ if err != nil {++ return "", false++ }++ return parseRegQuery(string(out), name)++}++// startUserSession starts the Node now in a window of its own, as the Run key will at the next sign-in.func startUserSession(exe, home, logLevel string) error {args := []string{"/c", "start", "OpenVibe Node", exe, "run", "--home", home}@@@ -99,7 +99,7 @@ openvibe-node install --user-session --home "%OVHOME%"`install --user-session` starts the Node when you sign in, in a console window that shows its log (Windows keeps noservice log yet). Closing the window stops the Node and every actuator; nothing restarts it until the next sign-in.--`openvibe-node uninstall --user-session` removes the sign-in start. On Windows a killed plugin takes the processes it++`openvibe-node uninstall --user-session --home DIR` removes that directory's sign-in start (each Node directory has its own, since v0.37.2, so a test node never replaces a working one). On Windows a killed plugin takes the processes itstarted with it (a Job Object), as it does on Linux and macOS, so a bridge helper cannot keep the Bluetooth link openafter the Node stops or restarts the plugin.@@@ -149,7 +149,7 @@ console window that shows its log; closing the window stops the Node and every a| `openvibe-node install [--user NAME]` | install and start the service || `openvibe-node uninstall` | stop and remove the service (config and credential are kept) || `openvibe-node install --user-session --home DIR` | Windows: start at sign-in in your own session instead of as a service |--| `openvibe-node uninstall --user-session` | Windows: remove that sign-in start |++| `openvibe-node uninstall --user-session --home DIR` | Windows: remove that directory's sign-in start (one per directory) || `openvibe-node status [--json]` | link, stop latch, limits, plugins, video, battery, running jobs || `openvibe-node stop` | **kill switch**: stop every actuator now and hold it stopped || `openvibe-node resume` | release the local stop (and a server e-stop) |
@@ -5,7 +5,7 @@
@@ -5,7 +5,7 @@
"wave": "T15",
"wave": "T15",
"plan": "Plan track T15 (Bot complete) and ADR-043 (Bot: devices, pairing, control and safety); the computer plugin and the job worker serve OpenVibe.Actor and the Codes job runner (tracks T14/T17)",
"plan": "Plan track T15 (Bot complete) and ADR-043 (Bot: devices, pairing, control and safety); the computer plugin and the job worker serve OpenVibe.Actor and the Codes job runner (tracks T14/T17)",
"kind": "client",
"kind": "client",
- "version": "0.37.1",
+ "version": "0.37.2",
"release": "v0.36.0: the ai plugin (0.2.0) passes tools to the machine's own model server and hands back the calls the model asked for, never running one, so an agent on OpenVibe (Actor) can work on a model this machine serves; its capabilities say tools: true (contracts 0.277.0). v0.35.0: the Node applies the roles the network's machines hub gives it at each check-in (codes.worker starts the job worker it was installed with, desks.host and ai.host are checked against what the machine was installed for, an unknown role is ignored, and the local configuration always wins), and its status reports them back; a desk's first browser start in a cold sandbox gets 90 s. v0.34.0: on Windows a killed plugin takes every process it started with it (a Job Object with kill-on-close), as the process group does on Linux and macOS, so a bridge helper cannot keep a robot's link open after the Node stops or restarts its plugin; the Node reads what the coding tools remember (read-only, local, redacted) and `openvibe-node memory` uploads it to your own store; the heartbeat carries the machine's fleet of coding agents; the Codes job runner pin is v0.5.2",
"release": "v0.36.0: the ai plugin (0.2.0) passes tools to the machine's own model server and hands back the calls the model asked for, never running one, so an agent on OpenVibe (Actor) can work on a model this machine serves; its capabilities say tools: true (contracts 0.277.0). v0.35.0: the Node applies the roles the network's machines hub gives it at each check-in (codes.worker starts the job worker it was installed with, desks.host and ai.host are checked against what the machine was installed for, an unknown role is ignored, and the local configuration always wins), and its status reports them back; a desk's first browser start in a cold sandbox gets 90 s. v0.34.0: on Windows a killed plugin takes every process it started with it (a Job Object with kill-on-close), as the process group does on Linux and macOS, so a bridge helper cannot keep a robot's link open after the Node stops or restarts its plugin; the Node reads what the coding tools remember (read-only, local, redacted) and `openvibe-node memory` uploads it to your own store; the heartbeat carries the machine's fleet of coding agents; the Codes job runner pin is v0.5.2",
"runtime": "go 1.24 (one static binary per platform); plugins in Python 3; an ESP32 Arduino library",
"runtime": "go 1.24 (one static binary per platform); plugins in Python 3; an ESP32 Arduino library",
"database": "none",
"database": "none",
@@ -193,7 +193,7 @@ func runWithStdin(args []string, stdin io.Reader, stdout, stderr io.Writer) int
@@ -193,7 +193,7 @@ func runWithStdin(args []string, stdin io.Reader, stdout, stderr io.Writer) int
err = cmdInstall(g, *svcUser, stdout)
err = cmdInstall(g, *svcUser, stdout)
case "uninstall":
case "uninstall":
if *session {
if *session {
- err = cmdUninstallUserSession(stdout)
+ err = cmdUninstallUserSession(g, stdout)
break
break
}
}
err = service.Control(serviceOptions(g, ""), "stop")
err = service.Control(serviceOptions(g, ""), "stop")
@@ -21,6 +21,20 @@ const (
@@ -21,6 +21,20 @@ const (
runValueName = "OpenVibe Node"
runValueName = "OpenVibe Node"
)
)
+// runValueFor is the Run value of one Node directory. A machine may run more than one Node at sign-in (a working
+// robot and a test node, 11-02), so each --home has its own value; before v0.37.2 every install wrote the one
+// "OpenVibe Node" value, and installing a second home replaced the first's sign-in start.
+func runValueFor(home string) string { return runValueName + " (" + home + ")" }
+
+// legacyRunsHome says whether a Run value's command line (the old single "OpenVibe Node" value) starts this home:
+// only then is it this Node's to move or remove. A value that starts another home is another Node's and stays.
+func legacyRunsHome(data, home string) bool {
+ if data == "" || home == "" {
+ return false
+ }
+ return strings.Contains(data, " --home "+quoteArg(home)+" ") || strings.HasSuffix(data, " --home "+quoteArg(home))
+}
+
var errNeedHome = errors.New(`--user-session needs --home DIR: the directory you paired with, for example --home "%LOCALAPPDATA%\OpenVibe Node"`)
var errNeedHome = errors.New(`--user-session needs --home DIR: the directory you paired with, for example --home "%LOCALAPPDATA%\OpenVibe Node"`)
// userSessionCommand is the command line the Run key starts: the executable, `run`, and the Node's own directory.
// userSessionCommand is the command line the Run key starts: the executable, `run`, and the Node's own directory.
@@ -59,26 +73,61 @@ func cmdInstallUserSession(g *globals, stdout io.Writer) error {
@@ -59,26 +73,61 @@ func cmdInstallUserSession(g *globals, stdout io.Writer) error {
return err
return err
}
}
line := userSessionCommand(exe, home, g.logLevel)
line := userSessionCommand(exe, home, g.logLevel)
- if err := registerUserSession(line); err != nil {
+ if err := registerUserSession(runValueFor(home), line); err != nil {
return err
return err
}
}
+ // The old single value, if it started this same home, is this Node's: the new value replaces it.
+ if data, ok := readUserSession(runValueName); ok && legacyRunsHome(data, home) {
+ _ = unregisterUserSession(runValueName)
+ }
fmt.Fprintln(stdout, "The Node now starts when you sign in, in its own window:", line)
fmt.Fprintln(stdout, "The Node now starts when you sign in, in its own window:", line)
if err := startUserSession(exe, home, g.logLevel); err != nil {
if err := startUserSession(exe, home, g.logLevel); err != nil {
fmt.Fprintln(stdout, "It did not start now (", err, "); sign out and in, or run the line above.")
fmt.Fprintln(stdout, "It did not start now (", err, "); sign out and in, or run the line above.")
} else {
} else {
fmt.Fprintln(stdout, "Started it now. Closing its window stops the Node and every actuator.")
fmt.Fprintln(stdout, "Started it now. Closing its window stops the Node and every actuator.")
}
}
- fmt.Fprintln(stdout, "Remove the sign-in start with `openvibe-node uninstall --user-session`.")
+ fmt.Fprintf(stdout, "Remove the sign-in start with `openvibe-node uninstall --user-session --home %s`.\n", quoteArg(home))
return nil
return nil
}
}
-func cmdUninstallUserSession(stdout io.Writer) error {
+func cmdUninstallUserSession(g *globals, stdout io.Writer) error {
if err := userSessionSupported(); err != nil {
if err := userSessionSupported(); err != nil {
return err
return err
}
}
- if err := unregisterUserSession(); err != nil {
+ if g.home == "" {
+ return errNeedHome
+ }
+ home, err := filepath.Abs(g.home)
+ if err != nil {
+ return err
+ }
+ if err := unregisterUserSession(runValueFor(home)); err != nil {
return err
return err
}
}
+ // An install from before v0.37.2 wrote the single "OpenVibe Node" value; it is removed only if it starts this home.
+ if data, ok := readUserSession(runValueName); ok && legacyRunsHome(data, home) {
+ if err := unregisterUserSession(runValueName); err != nil {
+ return err
+ }
+ }
fmt.Fprintln(stdout, "The Node no longer starts at sign-in. Close its window to stop the running one; config and credential are kept.")
fmt.Fprintln(stdout, "The Node no longer starts at sign-in. Close its window to stop the running one; config and credential are kept.")
return nil
return nil
}
}
+
+// parseRegQuery finds a value's data in `reg query <key> /v <name>` output, whose line reads
+// " <name> REG_SZ <data>". ok is false when the value is not there.
+func parseRegQuery(out, name string) (string, bool) {
+ for _, line := range strings.Split(strings.ReplaceAll(out, "\r\n", "\n"), "\n") {
+ t := strings.TrimSpace(line)
+ if !strings.HasPrefix(t, name+" ") {
+ continue
+ }
+ rest := strings.TrimSpace(strings.TrimPrefix(t, name))
+ for _, typ := range []string{"REG_SZ", "REG_EXPAND_SZ"} {
+ if strings.HasPrefix(rest, typ) {
+ return strings.TrimSpace(strings.TrimPrefix(rest, typ)), true
+ }
+ }
+ }
+ return "", false
+}
@@ -7,6 +7,7 @@ import "errors"
@@ -7,6 +7,7 @@ import "errors"
var errUserSessionWindowsOnly = errors.New("--user-session is for Windows; on Linux and macOS install the service (sudo openvibe-node install), or start `openvibe-node run --home DIR` from your desktop's autostart")
var errUserSessionWindowsOnly = errors.New("--user-session is for Windows; on Linux and macOS install the service (sudo openvibe-node install), or start `openvibe-node run --home DIR` from your desktop's autostart")
func userSessionSupported() error { return errUserSessionWindowsOnly }
func userSessionSupported() error { return errUserSessionWindowsOnly }
-func registerUserSession(string) error { return errUserSessionWindowsOnly }
-func unregisterUserSession() error { return errUserSessionWindowsOnly }
+func registerUserSession(string, string) error { return errUserSessionWindowsOnly }
+func unregisterUserSession(string) error { return errUserSessionWindowsOnly }
+func readUserSession(string) (string, bool) { return "", false }
func startUserSession(string, string, string) error { return errUserSessionWindowsOnly }
func startUserSession(string, string, string) error { return errUserSessionWindowsOnly }
@@ -27,3 +27,36 @@ func TestUserSessionRefusedOffWindows(t *testing.T) {
@@ -27,3 +27,36 @@ func TestUserSessionRefusedOffWindows(t *testing.T) {
t.Fatalf("code %d, stderr %q", code, errb.String())
t.Fatalf("code %d, stderr %q", code, errb.String())
}
}
}
}
+
+// Each Node directory has its own sign-in start (v0.37.2): a test node beside a working robot's Node no longer replaces
+// its Run value, and the old single value is this Node's only when it starts this same home.
+func TestUserSessionPerHome(t *testing.T) {
+ cozmo, test := `C:\Users\m\AppData\Local\OpenVibe Node`, `D:\looi-test`
+ if a, b := runValueFor(cozmo), runValueFor(test); a == b || !strings.HasPrefix(a, "OpenVibe Node (") {
+ t.Fatalf("one value per home: %q %q", a, b)
+ }
+ legacy := userSessionCommand(`C:\bin\openvibe-node.exe`, cozmo, "info")
+ if !legacyRunsHome(legacy, cozmo) {
+ t.Fatal("the old value starts the Cozmo home")
+ }
+ if legacyRunsHome(legacy, test) {
+ t.Fatal("the old value is another Node's: never this test home's")
+ }
+ if legacyRunsHome(userSessionCommand(`C:\bin\n.exe`, `D:\ov2`, "debug"), `D:\ov`) {
+ t.Fatal(`D:\ov is not D:\ov2`)
+ }
+ if !legacyRunsHome(userSessionCommand(`C:\bin\n.exe`, `D:\ov`, "debug"), `D:\ov`) {
+ t.Fatal("with a log level after it, the home still matches")
+ }
+}
+
+func TestParseRegQuery(t *testing.T) {
+ out := "\r\nHKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\r\n OpenVibe Node REG_SZ \"C:\\bin\\openvibe-node.exe\" run --home \"C:\\Users\\m\\OpenVibe Node\"\r\n\r\n"
+ got, ok := parseRegQuery(out, "OpenVibe Node")
+ if !ok || got != `"C:\bin\openvibe-node.exe" run --home "C:\Users\m\OpenVibe Node"` {
+ t.Fatalf("got %q %v", got, ok)
+ }
+ if _, ok := parseRegQuery(out, "OpenVibe Node (D:\\x)"); ok {
+ t.Fatal("another value is not found")
+ }
+}
@@ -10,22 +10,31 @@ import (
@@ -10,22 +10,31 @@ import (
func userSessionSupported() error { return nil }
func userSessionSupported() error { return nil }
-func registerUserSession(line string) error {
- out, err := exec.Command("reg", "add", runKey, "/v", runValueName, "/t", "REG_SZ", "/d", line, "/f").CombinedOutput()
+func registerUserSession(name, line string) error {
+ out, err := exec.Command("reg", "add", runKey, "/v", name, "/t", "REG_SZ", "/d", line, "/f").CombinedOutput()
if err != nil {
if err != nil {
return fmt.Errorf("reg add %s: %v: %s", runKey, err, strings.TrimSpace(string(out)))
return fmt.Errorf("reg add %s: %v: %s", runKey, err, strings.TrimSpace(string(out)))
}
}
return nil
return nil
}
}
-func unregisterUserSession() error {
- out, err := exec.Command("reg", "delete", runKey, "/v", runValueName, "/f").CombinedOutput()
+func unregisterUserSession(name string) error {
+ out, err := exec.Command("reg", "delete", runKey, "/v", name, "/f").CombinedOutput()
if err != nil && !strings.Contains(strings.ToLower(string(out)), "unable to find") {
if err != nil && !strings.Contains(strings.ToLower(string(out)), "unable to find") {
return fmt.Errorf("reg delete %s: %v: %s", runKey, err, strings.TrimSpace(string(out)))
return fmt.Errorf("reg delete %s: %v: %s", runKey, err, strings.TrimSpace(string(out)))
}
}
return nil
return nil
}
}
+// readUserSession reads one Run value's command line; ok is false when there is no such value.
+func readUserSession(name string) (string, bool) {
+ out, err := exec.Command("reg", "query", runKey, "/v", name).CombinedOutput()
+ if err != nil {
+ return "", false
+ }
+ return parseRegQuery(string(out), name)
+}
+
// startUserSession starts the Node now in a window of its own, as the Run key will at the next sign-in.
// startUserSession starts the Node now in a window of its own, as the Run key will at the next sign-in.
func startUserSession(exe, home, logLevel string) error {
func startUserSession(exe, home, logLevel string) error {
args := []string{"/c", "start", "OpenVibe Node", exe, "run", "--home", home}
args := []string{"/c", "start", "OpenVibe Node", exe, "run", "--home", home}
@@ -99,7 +99,7 @@ openvibe-node install --user-session --home "%OVHOME%"
@@ -99,7 +99,7 @@ openvibe-node install --user-session --home "%OVHOME%"
`install --user-session` starts the Node when you sign in, in a console window that shows its log (Windows keeps no
`install --user-session` starts the Node when you sign in, in a console window that shows its log (Windows keeps no
service log yet). Closing the window stops the Node and every actuator; nothing restarts it until the next sign-in.
service log yet). Closing the window stops the Node and every actuator; nothing restarts it until the next sign-in.
-`openvibe-node uninstall --user-session` removes the sign-in start. On Windows a killed plugin takes the processes it
+`openvibe-node uninstall --user-session --home DIR` removes that directory's sign-in start (each Node directory has its own, since v0.37.2, so a test node never replaces a working one). On Windows a killed plugin takes the processes it
started with it (a Job Object), as it does on Linux and macOS, so a bridge helper cannot keep the Bluetooth link open
started with it (a Job Object), as it does on Linux and macOS, so a bridge helper cannot keep the Bluetooth link open
after the Node stops or restarts the plugin.
after the Node stops or restarts the plugin.
@@ -149,7 +149,7 @@ console window that shows its log; closing the window stops the Node and every a
@@ -149,7 +149,7 @@ console window that shows its log; closing the window stops the Node and every a
| `openvibe-node install [--user NAME]` | install and start the service |
| `openvibe-node install [--user NAME]` | install and start the service |
| `openvibe-node uninstall` | stop and remove the service (config and credential are kept) |
| `openvibe-node uninstall` | stop and remove the service (config and credential are kept) |
| `openvibe-node install --user-session --home DIR` | Windows: start at sign-in in your own session instead of as a service |
| `openvibe-node install --user-session --home DIR` | Windows: start at sign-in in your own session instead of as a service |
-| `openvibe-node uninstall --user-session` | Windows: remove that sign-in start |
+| `openvibe-node uninstall --user-session --home DIR` | Windows: remove that directory's sign-in start (one per directory) |
| `openvibe-node status [--json]` | link, stop latch, limits, plugins, video, battery, running jobs |
| `openvibe-node status [--json]` | link, stop latch, limits, plugins, video, battery, running jobs |
| `openvibe-node stop` | **kill switch**: stop every actuator now and hold it stopped |
| `openvibe-node stop` | **kill switch**: stop every actuator now and hold it stopped |
| `openvibe-node resume` | release the local stop (and a server e-stop) |
| `openvibe-node resume` | release the local stop (and a server e-stop) |