platform: Add openURL event

This commit is contained in:
世界 2024-10-30 22:48:10 +08:00
parent 9585c53e9f
commit 718cffea9a
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
7 changed files with 130 additions and 15 deletions

View file

@ -20,6 +20,7 @@ type CommandClient struct {
type CommandClientOptions struct { type CommandClientOptions struct {
Command int32 Command int32
StatusInterval int64 StatusInterval int64
IsMainClient bool
} }
type CommandClientHandler interface { type CommandClientHandler interface {
@ -28,6 +29,7 @@ type CommandClientHandler interface {
ClearLogs() ClearLogs()
WriteLogs(messageList StringIterator) WriteLogs(messageList StringIterator)
WriteStatus(message *StatusMessage) WriteStatus(message *StatusMessage)
OpenURL(url string)
WriteGroups(message OutboundGroupIterator) WriteGroups(message OutboundGroupIterator)
InitializeClashMode(modeList StringIterator, currentMode string) InitializeClashMode(modeList StringIterator, currentMode string)
UpdateClashMode(newMode string) UpdateClashMode(newMode string)
@ -91,9 +93,13 @@ func (c *CommandClient) Connect() error {
c.handler.Connected() c.handler.Connected()
go c.handleLogConn(conn) go c.handleLogConn(conn)
case CommandStatus: case CommandStatus:
err = binary.Write(conn, binary.BigEndian, c.options.IsMainClient)
if err != nil {
return E.Cause(err, "write is main client")
}
err = binary.Write(conn, binary.BigEndian, c.options.StatusInterval) err = binary.Write(conn, binary.BigEndian, c.options.StatusInterval)
if err != nil { if err != nil {
return E.Cause(err, "write interval") return E.Cause(err, "write header")
} }
c.handler.Connected() c.handler.Connected()
go c.handleStatusConn(conn) go c.handleStatusConn(conn)

View file

@ -0,0 +1,40 @@
package libbox
import (
"encoding/binary"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/varbin"
)
type myEvent interface {
writeTo(writer varbin.Writer)
}
func readEvent(reader varbin.Reader) (myEvent, error) {
eventType, err := reader.ReadByte()
if err != nil {
return nil, err
}
switch eventType {
case eventTypeEmpty:
return nil, nil
case eventTypeOpenURL:
url, err := varbin.ReadValue[string](reader, binary.BigEndian)
if err != nil {
return nil, err
}
return &eventOpenURL{URL: url}, nil
default:
return nil, E.New("unknown event type: ", eventType)
}
}
type eventOpenURL struct {
URL string
}
func (e *eventOpenURL) writeTo(writer varbin.Writer) {
writer.WriteByte(eventTypeOpenURL)
varbin.Write(writer, binary.BigEndian, e.URL)
}

View file

@ -33,6 +33,7 @@ type CommandServer struct {
urlTestUpdate chan struct{} urlTestUpdate chan struct{}
modeUpdate chan struct{} modeUpdate chan struct{}
logReset chan struct{} logReset chan struct{}
events chan myEvent
closedConnections []Connection closedConnections []Connection
} }
@ -52,6 +53,7 @@ func NewCommandServer(handler CommandServerHandler, maxLines int32) *CommandServ
urlTestUpdate: make(chan struct{}, 1), urlTestUpdate: make(chan struct{}, 1),
modeUpdate: make(chan struct{}, 1), modeUpdate: make(chan struct{}, 1),
logReset: make(chan struct{}, 1), logReset: make(chan struct{}, 1),
events: make(chan myEvent, 8),
} }
server.observer = observable.NewObserver[string](server.subscriber, 64) server.observer = observable.NewObserver[string](server.subscriber, 64)
return server return server
@ -61,6 +63,12 @@ func (s *CommandServer) SetService(newService *BoxService) {
if newService != nil { if newService != nil {
service.PtrFromContext[urltest.HistoryStorage](newService.ctx).SetHook(s.urlTestUpdate) service.PtrFromContext[urltest.HistoryStorage](newService.ctx).SetHook(s.urlTestUpdate)
newService.instance.Router().ClashServer().(*clashapi.Server).SetModeUpdateHook(s.modeUpdate) newService.instance.Router().ClashServer().(*clashapi.Server).SetModeUpdateHook(s.modeUpdate)
newService.platformInterface.openURLFunc = func(url string) {
select {
case s.events <- &eventOpenURL{URL: url}:
default:
}
}
} }
s.service = newService s.service = newService
s.notifyURLTestUpdate() s.notifyURLTestUpdate()

View file

@ -1,6 +1,7 @@
package libbox package libbox
import ( import (
std_bufio "bufio"
"encoding/binary" "encoding/binary"
"net" "net"
"runtime" "runtime"
@ -9,9 +10,15 @@ import (
"github.com/sagernet/sing-box/common/conntrack" "github.com/sagernet/sing-box/common/conntrack"
"github.com/sagernet/sing-box/experimental/clashapi" "github.com/sagernet/sing-box/experimental/clashapi"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/memory" "github.com/sagernet/sing/common/memory"
) )
const (
eventTypeEmpty byte = iota
eventTypeOpenURL
)
type StatusMessage struct { type StatusMessage struct {
Memory int64 Memory int64
Goroutines int32 Goroutines int32
@ -44,19 +51,44 @@ func (s *CommandServer) readStatus() StatusMessage {
} }
func (s *CommandServer) handleStatusConn(conn net.Conn) error { func (s *CommandServer) handleStatusConn(conn net.Conn) error {
var isMainClient bool
err := binary.Read(conn, binary.BigEndian, &isMainClient)
if err != nil {
return E.Cause(err, "read is main client")
}
var interval int64 var interval int64
err := binary.Read(conn, binary.BigEndian, &interval) err = binary.Read(conn, binary.BigEndian, &interval)
if err != nil { if err != nil {
return E.Cause(err, "read interval") return E.Cause(err, "read interval")
} }
ticker := time.NewTicker(time.Duration(interval)) ticker := time.NewTicker(time.Duration(interval))
defer ticker.Stop() defer ticker.Stop()
ctx := connKeepAlive(conn) ctx := connKeepAlive(conn)
writer := std_bufio.NewWriter(conn)
if isMainClient {
for { for {
err = binary.Write(conn, binary.BigEndian, s.readStatus()) writer.WriteByte(eventTypeEmpty)
err = binary.Write(writer, binary.BigEndian, s.readStatus())
if err != nil { if err != nil {
return err return err
} }
writer.Flush()
select {
case <-ctx.Done():
return ctx.Err()
case <-ticker.C:
case event := <-s.events:
event.writeTo(writer)
writer.Flush()
}
}
} else {
for {
err = binary.Write(writer, binary.BigEndian, s.readStatus())
if err != nil {
return err
}
writer.Flush()
select { select {
case <-ctx.Done(): case <-ctx.Done():
return ctx.Err() return ctx.Err()
@ -64,11 +96,28 @@ func (s *CommandServer) handleStatusConn(conn net.Conn) error {
} }
} }
} }
}
func (c *CommandClient) handleStatusConn(conn net.Conn) { func (c *CommandClient) handleStatusConn(conn net.Conn) {
reader := std_bufio.NewReader(conn)
for { for {
if c.options.IsMainClient {
rawEvent, err := readEvent(reader)
if err != nil {
c.handler.Disconnected(err.Error())
return
}
switch event := rawEvent.(type) {
case *eventOpenURL:
c.handler.OpenURL(event.URL)
continue
case nil:
default:
panic(F.ToString("unexpected event type: ", event))
}
}
var message StatusMessage var message StatusMessage
err := binary.Read(conn, binary.BigEndian, &message) err := binary.Read(reader, binary.BigEndian, &message)
if err != nil { if err != nil {
c.handler.Disconnected(err.Error()) c.handler.Disconnected(err.Error())
return return

View file

@ -134,6 +134,9 @@ func (s *interfaceMonitorStub) RegisterCallback(callback tun.DefaultInterfaceUpd
func (s *interfaceMonitorStub) UnregisterCallback(element *list.Element[tun.DefaultInterfaceUpdateCallback]) { func (s *interfaceMonitorStub) UnregisterCallback(element *list.Element[tun.DefaultInterfaceUpdateCallback]) {
} }
func (s *platformInterfaceStub) OpenURL(url string) {
}
func FormatConfig(configContent string) (string, error) { func FormatConfig(configContent string) (string, error) {
options, err := parseConfig(configContent) options, err := parseConfig(configContent)
if err != nil { if err != nil {

View file

@ -25,4 +25,5 @@ type Interface interface {
ClearDNSCache() ClearDNSCache()
ReadWIFIState() adapter.WIFIState ReadWIFIState() adapter.WIFIState
process.Searcher process.Searcher
OpenURL(url string)
} }

View file

@ -34,9 +34,9 @@ type BoxService struct {
ctx context.Context ctx context.Context
cancel context.CancelFunc cancel context.CancelFunc
instance *box.Box instance *box.Box
platformInterface *platformInterfaceWrapper
pauseManager pause.Manager pauseManager pause.Manager
urlTestHistoryStorage *urltest.HistoryStorage urlTestHistoryStorage *urltest.HistoryStorage
servicePauseFields servicePauseFields
} }
@ -67,6 +67,7 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box
ctx: ctx, ctx: ctx,
cancel: cancel, cancel: cancel,
instance: instance, instance: instance,
platformInterface: platformWrapper,
urlTestHistoryStorage: urlTestHistoryStorage, urlTestHistoryStorage: urlTestHistoryStorage,
pauseManager: service.FromContext[pause.Manager](ctx), pauseManager: service.FromContext[pause.Manager](ctx),
}, nil }, nil
@ -105,6 +106,7 @@ type platformInterfaceWrapper struct {
iif PlatformInterface iif PlatformInterface
useProcFS bool useProcFS bool
router adapter.Router router adapter.Router
openURLFunc func(url string)
} }
func (w *platformInterfaceWrapper) Initialize(ctx context.Context, router adapter.Router) error { func (w *platformInterfaceWrapper) Initialize(ctx context.Context, router adapter.Router) error {
@ -238,3 +240,9 @@ func (w *platformInterfaceWrapper) DisableColors() bool {
func (w *platformInterfaceWrapper) WriteMessage(level log.Level, message string) { func (w *platformInterfaceWrapper) WriteMessage(level log.Level, message string) {
w.iif.WriteLog(message) w.iif.WriteLog(message)
} }
func (w *platformInterfaceWrapper) OpenURL(url string) {
if w.openURLFunc != nil {
w.openURLFunc(url)
}
}