mirror of
https://github.com/sipeed/NanoKVM.git
synced 2026-09-11 09:02:05 -05:00
Add an MCP Streamable HTTP server with token authentication, screenshot capture, keyboard, and mouse tools. Introduce shared control-mode and input-control coordination so MCP, PicoClaw, and local HID paths serialize ownership safely. Integrate PicoClaw gateway/runtime control handoff with PID-managed startup and focused unit coverage.
98 lines
1.7 KiB
Go
98 lines
1.7 KiB
Go
package controlmode
|
|
|
|
import (
|
|
"errors"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
var ErrActivityWaitTimeout = errors.New("timed out waiting for active control operations")
|
|
|
|
// activityGate allows normal control operations to share the current mode while
|
|
// giving mode transitions exclusive access. Unlike sync.RWMutex, exclusive
|
|
// acquisition is bounded so an abandoned client cannot leave the manager stuck
|
|
// in the transitioning state forever.
|
|
type activityGate struct {
|
|
mu sync.Mutex
|
|
active int
|
|
exclusive bool
|
|
changed chan struct{}
|
|
}
|
|
|
|
func (g *activityGate) acquireShared() func() {
|
|
for {
|
|
g.mu.Lock()
|
|
if !g.exclusive {
|
|
g.active++
|
|
g.mu.Unlock()
|
|
|
|
var once sync.Once
|
|
return func() {
|
|
once.Do(func() {
|
|
g.mu.Lock()
|
|
if g.active > 0 {
|
|
g.active--
|
|
}
|
|
g.signalLocked()
|
|
g.mu.Unlock()
|
|
})
|
|
}
|
|
}
|
|
|
|
changed := g.changedLocked()
|
|
g.mu.Unlock()
|
|
<-changed
|
|
}
|
|
}
|
|
|
|
func (g *activityGate) acquireExclusive(timeout time.Duration) (func(), error) {
|
|
if timeout <= 0 {
|
|
return nil, ErrActivityWaitTimeout
|
|
}
|
|
|
|
timer := time.NewTimer(timeout)
|
|
defer timer.Stop()
|
|
|
|
for {
|
|
g.mu.Lock()
|
|
if !g.exclusive && g.active == 0 {
|
|
g.exclusive = true
|
|
g.mu.Unlock()
|
|
|
|
var once sync.Once
|
|
return func() {
|
|
once.Do(func() {
|
|
g.mu.Lock()
|
|
g.exclusive = false
|
|
g.signalLocked()
|
|
g.mu.Unlock()
|
|
})
|
|
}, nil
|
|
}
|
|
|
|
changed := g.changedLocked()
|
|
g.mu.Unlock()
|
|
|
|
select {
|
|
case <-changed:
|
|
case <-timer.C:
|
|
return nil, ErrActivityWaitTimeout
|
|
}
|
|
}
|
|
}
|
|
|
|
func (g *activityGate) changedLocked() <-chan struct{} {
|
|
if g.changed == nil {
|
|
g.changed = make(chan struct{})
|
|
}
|
|
return g.changed
|
|
}
|
|
|
|
func (g *activityGate) signalLocked() {
|
|
if g.changed == nil {
|
|
return
|
|
}
|
|
close(g.changed)
|
|
g.changed = make(chan struct{})
|
|
}
|