From 718cffea9a34c9f2d552728ee8b3f978aaf82c03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 30 Oct 2024 22:48:10 +0800 Subject: [PATCH] platform: Add openURL event --- experimental/libbox/command_client.go | 8 ++- experimental/libbox/command_event.go | 40 +++++++++++++ experimental/libbox/command_server.go | 8 +++ experimental/libbox/command_status.go | 69 +++++++++++++++++++---- experimental/libbox/config.go | 3 + experimental/libbox/platform/interface.go | 1 + experimental/libbox/service.go | 16 ++++-- 7 files changed, 130 insertions(+), 15 deletions(-) create mode 100644 experimental/libbox/command_event.go diff --git a/experimental/libbox/command_client.go b/experimental/libbox/command_client.go index bd995115..a7c21d68 100644 --- a/experimental/libbox/command_client.go +++ b/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) diff --git a/experimental/libbox/command_event.go b/experimental/libbox/command_event.go new file mode 100644 index 00000000..1be45d1e --- /dev/null +++ b/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) +} diff --git a/experimental/libbox/command_server.go b/experimental/libbox/command_server.go index 26b4aa79..52705a37 100644 --- a/experimental/libbox/command_server.go +++ b/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() diff --git a/experimental/libbox/command_status.go b/experimental/libbox/command_status.go index 4ab09d4b..7591ac24 100644 --- a/experimental/libbox/command_status.go +++ b/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 diff --git a/experimental/libbox/config.go b/experimental/libbox/config.go index b7731143..e7ce487e 100644 --- a/experimental/libbox/config.go +++ b/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 { diff --git a/experimental/libbox/platform/interface.go b/experimental/libbox/platform/interface.go index 3bec13fa..08397bb0 100644 --- a/experimental/libbox/platform/interface.go +++ b/experimental/libbox/platform/interface.go @@ -25,4 +25,5 @@ type Interface interface { ClearDNSCache() ReadWIFIState() adapter.WIFIState process.Searcher + OpenURL(url string) } diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index c87c960c..7e2e1414 100644 --- a/experimental/libbox/service.go +++ b/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) + } +}