瀏覽代碼

platform: Add openURL event

世界 11 月之前
父節點
當前提交
718cffea9a

+ 7 - 1
experimental/libbox/command_client.go

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

+ 40 - 0
experimental/libbox/command_event.go

@@ -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)
+}

+ 8 - 0
experimental/libbox/command_server.go

@@ -33,6 +33,7 @@ type CommandServer struct {
 	urlTestUpdate chan struct{}
 	modeUpdate    chan struct{}
 	logReset      chan struct{}
+	events        chan myEvent
 
 	closedConnections []Connection
 }
@@ -52,6 +53,7 @@ func NewCommandServer(handler CommandServerHandler, maxLines int32) *CommandServ
 		urlTestUpdate: make(chan struct{}, 1),
 		modeUpdate:    make(chan struct{}, 1),
 		logReset:      make(chan struct{}, 1),
+		events:        make(chan myEvent, 8),
 	}
 	server.observer = observable.NewObserver[string](server.subscriber, 64)
 	return server
@@ -61,6 +63,12 @@ func (s *CommandServer) SetService(newService *BoxService) {
 	if newService != nil {
 		service.PtrFromContext[urltest.HistoryStorage](newService.ctx).SetHook(s.urlTestUpdate)
 		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.notifyURLTestUpdate()

+ 59 - 10
experimental/libbox/command_status.go

@@ -1,6 +1,7 @@
 package libbox
 
 import (
+	std_bufio "bufio"
 	"encoding/binary"
 	"net"
 	"runtime"
@@ -9,9 +10,15 @@ import (
 	"github.com/sagernet/sing-box/common/conntrack"
 	"github.com/sagernet/sing-box/experimental/clashapi"
 	E "github.com/sagernet/sing/common/exceptions"
+	F "github.com/sagernet/sing/common/format"
 	"github.com/sagernet/sing/common/memory"
 )
 
+const (
+	eventTypeEmpty byte = iota
+	eventTypeOpenURL
+)
+
 type StatusMessage struct {
 	Memory           int64
 	Goroutines       int32
@@ -44,31 +51,73 @@ func (s *CommandServer) readStatus() StatusMessage {
 }
 
 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
-	err := binary.Read(conn, binary.BigEndian, &interval)
+	err = binary.Read(conn, binary.BigEndian, &interval)
 	if err != nil {
 		return E.Cause(err, "read interval")
 	}
 	ticker := time.NewTicker(time.Duration(interval))
 	defer ticker.Stop()
 	ctx := connKeepAlive(conn)
-	for {
-		err = binary.Write(conn, binary.BigEndian, s.readStatus())
-		if err != nil {
-			return err
+	writer := std_bufio.NewWriter(conn)
+	if isMainClient {
+		for {
+			writer.WriteByte(eventTypeEmpty)
+			err = binary.Write(writer, binary.BigEndian, s.readStatus())
+			if err != nil {
+				return err
+			}
+			writer.Flush()
+			select {
+			case <-ctx.Done():
+				return ctx.Err()
+			case <-ticker.C:
+			case event := <-s.events:
+				event.writeTo(writer)
+				writer.Flush()
+			}
 		}
-		select {
-		case <-ctx.Done():
-			return ctx.Err()
-		case <-ticker.C:
+	} else {
+		for {
+			err = binary.Write(writer, binary.BigEndian, s.readStatus())
+			if err != nil {
+				return err
+			}
+			writer.Flush()
+			select {
+			case <-ctx.Done():
+				return ctx.Err()
+			case <-ticker.C:
+			}
 		}
 	}
 }
 
 func (c *CommandClient) handleStatusConn(conn net.Conn) {
+	reader := std_bufio.NewReader(conn)
 	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
-		err := binary.Read(conn, binary.BigEndian, &message)
+		err := binary.Read(reader, binary.BigEndian, &message)
 		if err != nil {
 			c.handler.Disconnected(err.Error())
 			return

+ 3 - 0
experimental/libbox/config.go

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

+ 1 - 0
experimental/libbox/platform/interface.go

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

+ 12 - 4
experimental/libbox/service.go

@@ -34,9 +34,9 @@ type BoxService struct {
 	ctx                   context.Context
 	cancel                context.CancelFunc
 	instance              *box.Box
+	platformInterface     *platformInterfaceWrapper
 	pauseManager          pause.Manager
 	urlTestHistoryStorage *urltest.HistoryStorage
-
 	servicePauseFields
 }
 
@@ -67,6 +67,7 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box
 		ctx:                   ctx,
 		cancel:                cancel,
 		instance:              instance,
+		platformInterface:     platformWrapper,
 		urlTestHistoryStorage: urlTestHistoryStorage,
 		pauseManager:          service.FromContext[pause.Manager](ctx),
 	}, nil
@@ -102,9 +103,10 @@ var (
 )
 
 type platformInterfaceWrapper struct {
-	iif       PlatformInterface
-	useProcFS bool
-	router    adapter.Router
+	iif         PlatformInterface
+	useProcFS   bool
+	router      adapter.Router
+	openURLFunc func(url string)
 }
 
 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) {
 	w.iif.WriteLog(message)
 }
+
+func (w *platformInterfaceWrapper) OpenURL(url string) {
+	if w.openURLFunc != nil {
+		w.openURLFunc(url)
+	}
+}