mirror of
https://github.com/bolucat/Archive.git
synced 2025-10-10 02:20:33 +08:00
Update On Thu Mar 27 19:36:13 CET 2025
This commit is contained in:
1
.github/update.log
vendored
1
.github/update.log
vendored
@@ -954,3 +954,4 @@ Update On Sun Mar 23 19:33:38 CET 2025
|
|||||||
Update On Mon Mar 24 19:37:20 CET 2025
|
Update On Mon Mar 24 19:37:20 CET 2025
|
||||||
Update On Tue Mar 25 19:35:52 CET 2025
|
Update On Tue Mar 25 19:35:52 CET 2025
|
||||||
Update On Wed Mar 26 19:37:44 CET 2025
|
Update On Wed Mar 26 19:37:44 CET 2025
|
||||||
|
Update On Thu Mar 27 19:36:04 CET 2025
|
||||||
|
@@ -174,6 +174,12 @@ func (l *Listener) HandleConn(conn net.Conn, h *sing.ListenerHandler) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// It seems that mihomo does not implement a connection error reporting mechanism, so we report success directly.
|
||||||
|
err = stream.HandshakeSuccess()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
h.NewConnection(ctx, stream, M.Metadata{
|
h.NewConnection(ctx, stream, M.Metadata{
|
||||||
Source: M.SocksaddrFromNet(conn.RemoteAddr()),
|
Source: M.SocksaddrFromNet(conn.RemoteAddr()),
|
||||||
Destination: destination,
|
Destination: destination,
|
||||||
|
@@ -15,10 +15,10 @@ import (
|
|||||||
const CheckMark = -1
|
const CheckMark = -1
|
||||||
|
|
||||||
var DefaultPaddingScheme = []byte(`stop=8
|
var DefaultPaddingScheme = []byte(`stop=8
|
||||||
0=34-120
|
0=30-30
|
||||||
1=100-400
|
1=100-400
|
||||||
2=400-500,c,500-1000,c,400-500,c,500-1000,c,500-1000,c,400-500
|
2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000
|
||||||
3=500-1000
|
3=9-9,500-1000
|
||||||
4=500-1000
|
4=500-1000
|
||||||
5=500-1000
|
5=500-1000
|
||||||
6=500-1000
|
6=500-1000
|
||||||
|
@@ -83,11 +83,7 @@ func (c *Client) CreateStream(ctx context.Context) (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
stream.dieHook = func() {
|
stream.dieHook = func() {
|
||||||
if session.IsClosed() {
|
if !session.IsClosed() {
|
||||||
if session.dieHook != nil {
|
|
||||||
session.dieHook()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
select {
|
select {
|
||||||
case <-c.die.Done():
|
case <-c.die.Done():
|
||||||
// Now client has been closed
|
// Now client has been closed
|
||||||
@@ -154,10 +150,10 @@ func (c *Client) Close() error {
|
|||||||
|
|
||||||
c.sessionsLock.Lock()
|
c.sessionsLock.Lock()
|
||||||
sessionToClose := make([]*Session, 0, len(c.sessions))
|
sessionToClose := make([]*Session, 0, len(c.sessions))
|
||||||
for seq, session := range c.sessions {
|
for _, session := range c.sessions {
|
||||||
sessionToClose = append(sessionToClose, session)
|
sessionToClose = append(sessionToClose, session)
|
||||||
delete(c.sessions, seq)
|
|
||||||
}
|
}
|
||||||
|
c.sessions = make(map[uint64]*Session)
|
||||||
c.sessionsLock.Unlock()
|
c.sessionsLock.Unlock()
|
||||||
|
|
||||||
for _, session := range sessionToClose {
|
for _, session := range sessionToClose {
|
||||||
|
@@ -9,9 +9,14 @@ const ( // cmds
|
|||||||
cmdSYN = 1 // stream open
|
cmdSYN = 1 // stream open
|
||||||
cmdPSH = 2 // data push
|
cmdPSH = 2 // data push
|
||||||
cmdFIN = 3 // stream close, a.k.a EOF mark
|
cmdFIN = 3 // stream close, a.k.a EOF mark
|
||||||
cmdSettings = 4 // Settings
|
cmdSettings = 4 // Settings (Client send to Server)
|
||||||
cmdAlert = 5 // Alert
|
cmdAlert = 5 // Alert
|
||||||
cmdUpdatePaddingScheme = 6 // update padding scheme
|
cmdUpdatePaddingScheme = 6 // update padding scheme
|
||||||
|
// Since version 2
|
||||||
|
cmdSYNACK = 7 // Server reports to the client that the stream has been opened
|
||||||
|
cmdHeartRequest = 8 // Keep alive command
|
||||||
|
cmdHeartResponse = 9 // Keep alive command
|
||||||
|
cmdServerSettings = 10 // Settings (Server send to client)
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@@ -3,9 +3,11 @@ package session
|
|||||||
import (
|
import (
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -30,11 +32,16 @@ type Session struct {
|
|||||||
die chan struct{}
|
die chan struct{}
|
||||||
dieHook func()
|
dieHook func()
|
||||||
|
|
||||||
|
synDone func()
|
||||||
|
synDoneLock sync.Mutex
|
||||||
|
|
||||||
// pool
|
// pool
|
||||||
seq uint64
|
seq uint64
|
||||||
idleSince time.Time
|
idleSince time.Time
|
||||||
padding *atomic.TypedValue[*padding.PaddingFactory]
|
padding *atomic.TypedValue[*padding.PaddingFactory]
|
||||||
|
|
||||||
|
peerVersion byte
|
||||||
|
|
||||||
// client
|
// client
|
||||||
isClient bool
|
isClient bool
|
||||||
sendPadding bool
|
sendPadding bool
|
||||||
@@ -76,7 +83,7 @@ func (s *Session) Run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
settings := util.StringMap{
|
settings := util.StringMap{
|
||||||
"v": "1",
|
"v": "2",
|
||||||
"client": "mihomo/" + constant.Version,
|
"client": "mihomo/" + constant.Version,
|
||||||
"padding-md5": s.padding.Load().Md5,
|
"padding-md5": s.padding.Load().Md5,
|
||||||
}
|
}
|
||||||
@@ -105,15 +112,16 @@ func (s *Session) Close() error {
|
|||||||
close(s.die)
|
close(s.die)
|
||||||
once = true
|
once = true
|
||||||
})
|
})
|
||||||
|
|
||||||
if once {
|
if once {
|
||||||
if s.dieHook != nil {
|
if s.dieHook != nil {
|
||||||
s.dieHook()
|
s.dieHook()
|
||||||
|
s.dieHook = nil
|
||||||
}
|
}
|
||||||
s.streamLock.Lock()
|
s.streamLock.Lock()
|
||||||
for k := range s.streams {
|
for _, stream := range s.streams {
|
||||||
s.streams[k].sessionClose()
|
stream.Close()
|
||||||
}
|
}
|
||||||
|
s.streams = make(map[uint32]*Stream)
|
||||||
s.streamLock.Unlock()
|
s.streamLock.Unlock()
|
||||||
return s.conn.Close()
|
return s.conn.Close()
|
||||||
} else {
|
} else {
|
||||||
@@ -132,6 +140,17 @@ func (s *Session) OpenStream() (*Stream, error) {
|
|||||||
|
|
||||||
//logrus.Debugln("stream open", sid, s.streams)
|
//logrus.Debugln("stream open", sid, s.streams)
|
||||||
|
|
||||||
|
if sid >= 2 && s.peerVersion >= 2 {
|
||||||
|
s.synDoneLock.Lock()
|
||||||
|
if s.synDone != nil {
|
||||||
|
s.synDone()
|
||||||
|
}
|
||||||
|
s.synDone = util.NewDeadlineWatcher(time.Second*3, func() {
|
||||||
|
s.Close()
|
||||||
|
})
|
||||||
|
s.synDoneLock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := s.writeFrame(newFrame(cmdSYN, sid)); err != nil {
|
if _, err := s.writeFrame(newFrame(cmdSYN, sid)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -195,13 +214,37 @@ func (s *Session) recvLoop() error {
|
|||||||
if _, ok := s.streams[sid]; !ok {
|
if _, ok := s.streams[sid]; !ok {
|
||||||
stream := newStream(sid, s)
|
stream := newStream(sid, s)
|
||||||
s.streams[sid] = stream
|
s.streams[sid] = stream
|
||||||
if s.onNewStream != nil {
|
go func() {
|
||||||
go s.onNewStream(stream)
|
if s.onNewStream != nil {
|
||||||
} else {
|
s.onNewStream(stream)
|
||||||
go s.Close()
|
} else {
|
||||||
}
|
stream.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
s.streamLock.Unlock()
|
s.streamLock.Unlock()
|
||||||
|
case cmdSYNACK: // should be client only
|
||||||
|
s.synDoneLock.Lock()
|
||||||
|
if s.synDone != nil {
|
||||||
|
s.synDone()
|
||||||
|
s.synDone = nil
|
||||||
|
}
|
||||||
|
s.synDoneLock.Unlock()
|
||||||
|
if hdr.Length() > 0 {
|
||||||
|
buffer := pool.Get(int(hdr.Length()))
|
||||||
|
if _, err := io.ReadFull(s.conn, buffer); err != nil {
|
||||||
|
pool.Put(buffer)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// report error
|
||||||
|
s.streamLock.RLock()
|
||||||
|
stream, ok := s.streams[sid]
|
||||||
|
s.streamLock.RUnlock()
|
||||||
|
if ok {
|
||||||
|
stream.CloseWithError(fmt.Errorf("remote: %s", string(buffer)))
|
||||||
|
}
|
||||||
|
pool.Put(buffer)
|
||||||
|
}
|
||||||
case cmdFIN:
|
case cmdFIN:
|
||||||
s.streamLock.RLock()
|
s.streamLock.RLock()
|
||||||
stream, ok := s.streams[sid]
|
stream, ok := s.streams[sid]
|
||||||
@@ -240,6 +283,20 @@ func (s *Session) recvLoop() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// check client's version
|
||||||
|
if v, err := strconv.Atoi(m["v"]); err == nil && v >= 2 {
|
||||||
|
s.peerVersion = byte(v)
|
||||||
|
// send cmdServerSettings
|
||||||
|
f := newFrame(cmdServerSettings, 0)
|
||||||
|
f.data = util.StringMap{
|
||||||
|
"v": "2",
|
||||||
|
}.ToBytes()
|
||||||
|
_, err = s.writeFrame(f)
|
||||||
|
if err != nil {
|
||||||
|
pool.Put(buffer)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pool.Put(buffer)
|
pool.Put(buffer)
|
||||||
}
|
}
|
||||||
@@ -265,12 +322,35 @@ func (s *Session) recvLoop() error {
|
|||||||
}
|
}
|
||||||
if s.isClient {
|
if s.isClient {
|
||||||
if padding.UpdatePaddingScheme(rawScheme, s.padding) {
|
if padding.UpdatePaddingScheme(rawScheme, s.padding) {
|
||||||
log.Infoln("[Update padding succeed] %x\n", md5.Sum(rawScheme))
|
log.Debugln("[Update padding succeed] %x\n", md5.Sum(rawScheme))
|
||||||
} else {
|
} else {
|
||||||
log.Warnln("[Update padding failed] %x\n", md5.Sum(rawScheme))
|
log.Warnln("[Update padding failed] %x\n", md5.Sum(rawScheme))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case cmdHeartRequest:
|
||||||
|
if _, err := s.writeFrame(newFrame(cmdHeartResponse, sid)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case cmdHeartResponse:
|
||||||
|
// Active keepalive checking is not implemented yet
|
||||||
|
break
|
||||||
|
case cmdServerSettings:
|
||||||
|
if hdr.Length() > 0 {
|
||||||
|
buffer := pool.Get(int(hdr.Length()))
|
||||||
|
if _, err := io.ReadFull(s.conn, buffer); err != nil {
|
||||||
|
pool.Put(buffer)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if s.isClient {
|
||||||
|
// check server's version
|
||||||
|
m := util.StringMapFromBytes(buffer)
|
||||||
|
if v, err := strconv.Atoi(m["v"]); err == nil {
|
||||||
|
s.peerVersion = byte(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pool.Put(buffer)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
// I don't know what command it is (can't have data)
|
// I don't know what command it is (can't have data)
|
||||||
}
|
}
|
||||||
@@ -280,8 +360,10 @@ func (s *Session) recvLoop() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// notify the session that a stream has closed
|
|
||||||
func (s *Session) streamClosed(sid uint32) error {
|
func (s *Session) streamClosed(sid uint32) error {
|
||||||
|
if s.IsClosed() {
|
||||||
|
return io.ErrClosedPipe
|
||||||
|
}
|
||||||
_, err := s.writeFrame(newFrame(cmdFIN, sid))
|
_, err := s.writeFrame(newFrame(cmdFIN, sid))
|
||||||
s.streamLock.Lock()
|
s.streamLock.Lock()
|
||||||
delete(s.streams, sid)
|
delete(s.streams, sid)
|
||||||
|
@@ -22,6 +22,9 @@ type Stream struct {
|
|||||||
|
|
||||||
dieOnce sync.Once
|
dieOnce sync.Once
|
||||||
dieHook func()
|
dieHook func()
|
||||||
|
dieErr error
|
||||||
|
|
||||||
|
reportOnce sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
// newStream initiates a Stream struct
|
// newStream initiates a Stream struct
|
||||||
@@ -36,7 +39,11 @@ func newStream(id uint32, sess *Session) *Stream {
|
|||||||
|
|
||||||
// Read implements net.Conn
|
// Read implements net.Conn
|
||||||
func (s *Stream) Read(b []byte) (n int, err error) {
|
func (s *Stream) Read(b []byte) (n int, err error) {
|
||||||
return s.pipeR.Read(b)
|
n, err = s.pipeR.Read(b)
|
||||||
|
if s.dieErr != nil {
|
||||||
|
err = s.dieErr
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write implements net.Conn
|
// Write implements net.Conn
|
||||||
@@ -54,25 +61,28 @@ func (s *Stream) Write(b []byte) (n int, err error) {
|
|||||||
|
|
||||||
// Close implements net.Conn
|
// Close implements net.Conn
|
||||||
func (s *Stream) Close() error {
|
func (s *Stream) Close() error {
|
||||||
if s.sessionClose() {
|
return s.CloseWithError(io.ErrClosedPipe)
|
||||||
// notify remote
|
|
||||||
return s.sess.streamClosed(s.id)
|
|
||||||
} else {
|
|
||||||
return io.ErrClosedPipe
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// sessionClose close stream from session side, do not notify remote
|
func (s *Stream) CloseWithError(err error) error {
|
||||||
func (s *Stream) sessionClose() (once bool) {
|
// if err != io.ErrClosedPipe {
|
||||||
|
// logrus.Debugln(err)
|
||||||
|
// }
|
||||||
|
var once bool
|
||||||
s.dieOnce.Do(func() {
|
s.dieOnce.Do(func() {
|
||||||
|
s.dieErr = err
|
||||||
s.pipeR.Close()
|
s.pipeR.Close()
|
||||||
once = true
|
once = true
|
||||||
|
})
|
||||||
|
if once {
|
||||||
if s.dieHook != nil {
|
if s.dieHook != nil {
|
||||||
s.dieHook()
|
s.dieHook()
|
||||||
s.dieHook = nil
|
s.dieHook = nil
|
||||||
}
|
}
|
||||||
})
|
return s.sess.streamClosed(s.id)
|
||||||
return
|
} else {
|
||||||
|
return s.dieErr
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Stream) SetReadDeadline(t time.Time) error {
|
func (s *Stream) SetReadDeadline(t time.Time) error {
|
||||||
@@ -108,3 +118,33 @@ func (s *Stream) RemoteAddr() net.Addr {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HandshakeFailure should be called when Server fail to create outbound proxy
|
||||||
|
func (s *Stream) HandshakeFailure(err error) error {
|
||||||
|
var once bool
|
||||||
|
s.reportOnce.Do(func() {
|
||||||
|
once = true
|
||||||
|
})
|
||||||
|
if once && err != nil && s.sess.peerVersion >= 2 {
|
||||||
|
f := newFrame(cmdSYNACK, s.id)
|
||||||
|
f.data = []byte(err.Error())
|
||||||
|
if _, err := s.sess.writeFrame(f); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandshakeSuccess should be called when Server success to create outbound proxy
|
||||||
|
func (s *Stream) HandshakeSuccess() error {
|
||||||
|
var once bool
|
||||||
|
s.reportOnce.Do(func() {
|
||||||
|
once = true
|
||||||
|
})
|
||||||
|
if once && s.sess.peerVersion >= 2 {
|
||||||
|
if _, err := s.sess.writeFrame(newFrame(cmdSYNACK, s.id)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
25
clash-meta/transport/anytls/util/deadline.go
Normal file
25
clash-meta/transport/anytls/util/deadline.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewDeadlineWatcher(ddl time.Duration, timeOut func()) (done func()) {
|
||||||
|
t := time.NewTimer(ddl)
|
||||||
|
closeCh := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
defer t.Stop()
|
||||||
|
select {
|
||||||
|
case <-closeCh:
|
||||||
|
case <-t.C:
|
||||||
|
timeOut()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
var once sync.Once
|
||||||
|
return func() {
|
||||||
|
once.Do(func() {
|
||||||
|
close(closeCh)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
4
clash-nyanpasu/.github/workflows/daily.yml
vendored
4
clash-nyanpasu/.github/workflows/daily.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
|||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '20'
|
node-version: '22'
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v4
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
@@ -57,7 +57,7 @@ jobs:
|
|||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '20'
|
node-version: '22'
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v4
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
|
@@ -34,7 +34,7 @@ jobs:
|
|||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '20'
|
node-version: '22'
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v4
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
|
@@ -53,7 +53,7 @@
|
|||||||
"@csstools/normalize.css": "12.1.1",
|
"@csstools/normalize.css": "12.1.1",
|
||||||
"@emotion/babel-plugin": "11.13.5",
|
"@emotion/babel-plugin": "11.13.5",
|
||||||
"@emotion/react": "11.14.0",
|
"@emotion/react": "11.14.0",
|
||||||
"@iconify/json": "2.2.319",
|
"@iconify/json": "2.2.321",
|
||||||
"@monaco-editor/react": "4.7.0",
|
"@monaco-editor/react": "4.7.0",
|
||||||
"@tanstack/react-query": "5.69.0",
|
"@tanstack/react-query": "5.69.0",
|
||||||
"@tanstack/react-router": "1.114.27",
|
"@tanstack/react-router": "1.114.27",
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
"mihomo_alpha": "alpha-7b38261",
|
"mihomo_alpha": "alpha-7b38261",
|
||||||
"clash_rs": "v0.7.6",
|
"clash_rs": "v0.7.6",
|
||||||
"clash_premium": "2023-09-05-gdcc8d87",
|
"clash_premium": "2023-09-05-gdcc8d87",
|
||||||
"clash_rs_alpha": "0.7.6-alpha+sha.153fb70"
|
"clash_rs_alpha": "0.7.6-alpha+sha.fa04093"
|
||||||
},
|
},
|
||||||
"arch_template": {
|
"arch_template": {
|
||||||
"mihomo": {
|
"mihomo": {
|
||||||
@@ -69,5 +69,5 @@
|
|||||||
"linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf"
|
"linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"updated_at": "2025-03-24T22:21:04.112Z"
|
"updated_at": "2025-03-26T22:20:53.137Z"
|
||||||
}
|
}
|
||||||
|
10
clash-nyanpasu/pnpm-lock.yaml
generated
10
clash-nyanpasu/pnpm-lock.yaml
generated
@@ -333,8 +333,8 @@ importers:
|
|||||||
specifier: 11.14.0
|
specifier: 11.14.0
|
||||||
version: 11.14.0(@types/react@19.0.12)(react@19.0.0)
|
version: 11.14.0(@types/react@19.0.12)(react@19.0.0)
|
||||||
'@iconify/json':
|
'@iconify/json':
|
||||||
specifier: 2.2.319
|
specifier: 2.2.321
|
||||||
version: 2.2.319
|
version: 2.2.321
|
||||||
'@monaco-editor/react':
|
'@monaco-editor/react':
|
||||||
specifier: 4.7.0
|
specifier: 4.7.0
|
||||||
version: 4.7.0(monaco-editor@0.52.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
version: 4.7.0(monaco-editor@0.52.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||||
@@ -1674,8 +1674,8 @@ packages:
|
|||||||
'@vue/compiler-sfc':
|
'@vue/compiler-sfc':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@iconify/json@2.2.319':
|
'@iconify/json@2.2.321':
|
||||||
resolution: {integrity: sha512-ZGX8O3PXDxXdgltuW2JlXa7IuZ6uc34qKVIBRyPNo63fxjbw7rSOox7HKi3fJyhXqoL3aN0AtK1yOb5Cgcte8w==}
|
resolution: {integrity: sha512-0D1OjRK77jD7dhrb4IhGiBTqLufi6I6HaYso6qkSkvm0WqbWgzGnoNEpw+g/jzSJAiLfuBwOGz6b7Q/ZJqsYrw==}
|
||||||
|
|
||||||
'@iconify/types@2.0.0':
|
'@iconify/types@2.0.0':
|
||||||
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
|
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
|
||||||
@@ -9450,7 +9450,7 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@iconify/json@2.2.319':
|
'@iconify/json@2.2.321':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@iconify/types': 2.0.0
|
'@iconify/types': 2.0.0
|
||||||
pathe: 1.1.2
|
pathe: 1.1.2
|
||||||
|
@@ -1,13 +1,13 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# 运行 clippy
|
# 运行 clippy
|
||||||
cargo clippy --manifest-path ./src-tauri/Cargo.toml --fix
|
# cargo clippy --manifest-path ./src-tauri/Cargo.toml --fix
|
||||||
|
|
||||||
# 如果 clippy 失败,阻止 push
|
# 如果 clippy 失败,阻止 push
|
||||||
if [ $? -ne 0 ]; then
|
# if [ $? -ne 0 ]; then
|
||||||
echo "Clippy found issues in sub_crate. Please fix them before pushing."
|
# echo "Clippy found issues in sub_crate. Please fix them before pushing."
|
||||||
exit 1
|
# exit 1
|
||||||
fi
|
# fi
|
||||||
|
|
||||||
# 允许 push
|
# 允许 push
|
||||||
exit 0
|
exit 0
|
||||||
|
@@ -2,14 +2,24 @@
|
|||||||
|
|
||||||
#### 已知问题
|
#### 已知问题
|
||||||
- 仅在Ubuntu 22.04/24.04,Fedora 41 **Gnome桌面环境** 做过简单测试,不保证其他其他Linux发行版可用,将在未来做进一步适配和调优
|
- 仅在Ubuntu 22.04/24.04,Fedora 41 **Gnome桌面环境** 做过简单测试,不保证其他其他Linux发行版可用,将在未来做进一步适配和调优
|
||||||
|
- MacOS 自定义图标与速率显示推荐图标尺寸为 256x256。其他尺寸(可能)会导致不正常图标和速率间隙
|
||||||
|
|
||||||
### 2.2.3-alpha 相对于 2.2.2
|
### 2.2.3-alpha 相对于 2.2.2
|
||||||
#### 修复了:
|
#### 修复了:
|
||||||
- 首页“当前代理”因为重复刷新导致的CPU占用过高的问题
|
- 首页“当前代理”因为重复刷新导致的CPU占用过高的问题
|
||||||
|
- “开启自启”和“DNS覆写”开关跳动问题
|
||||||
|
- 自定义托盘图标未能应用更改
|
||||||
|
- MacOS 自定义托盘图标显示速率时图标和文本间隙过大
|
||||||
|
- MacOS 托盘速率显示不全
|
||||||
|
|
||||||
#### 优化
|
#### 新增了:
|
||||||
- 重构了内核管理逻辑,更轻量化和有效的管理内核,提高了性能和稳定性
|
- ClashVergeRev 从现在开始不再强依赖系统服务和管理权限
|
||||||
- 集中管理应用数据,优化数据获取和刷新逻辑
|
|
||||||
|
#### 优化了:
|
||||||
|
- 重构了后端内核管理逻辑,更轻量化和有效的管理内核,提高了性能和稳定性
|
||||||
|
- 前端统一刷新应用数据,优化数据获取和刷新逻辑
|
||||||
|
- 优化首页流量图表代码,调整图表文字边距
|
||||||
|
- MacOS 托盘速率更好的显示样式和更新逻辑
|
||||||
|
|
||||||
## v2.2.2
|
## v2.2.2
|
||||||
|
|
||||||
|
193
clash-verge-rev/src-tauri/Cargo.lock
generated
193
clash-verge-rev/src-tauri/Cargo.lock
generated
@@ -276,17 +276,6 @@ dependencies = [
|
|||||||
"futures-lite 1.13.0",
|
"futures-lite 1.13.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "async-fs"
|
|
||||||
version = "2.1.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a"
|
|
||||||
dependencies = [
|
|
||||||
"async-lock 3.4.0",
|
|
||||||
"blocking",
|
|
||||||
"futures-lite 2.6.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-io"
|
name = "async-io"
|
||||||
version = "1.13.0"
|
version = "1.13.0"
|
||||||
@@ -374,25 +363,6 @@ dependencies = [
|
|||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "async-process"
|
|
||||||
version = "2.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb"
|
|
||||||
dependencies = [
|
|
||||||
"async-channel 2.3.1",
|
|
||||||
"async-io 2.4.0",
|
|
||||||
"async-lock 3.4.0",
|
|
||||||
"async-signal",
|
|
||||||
"async-task",
|
|
||||||
"blocking",
|
|
||||||
"cfg-if",
|
|
||||||
"event-listener 5.3.0",
|
|
||||||
"futures-lite 2.6.0",
|
|
||||||
"rustix 0.38.44",
|
|
||||||
"tracing",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-recursion"
|
name = "async-recursion"
|
||||||
version = "1.1.1"
|
version = "1.1.1"
|
||||||
@@ -1118,7 +1088,6 @@ dependencies = [
|
|||||||
"tauri-plugin-dialog",
|
"tauri-plugin-dialog",
|
||||||
"tauri-plugin-fs",
|
"tauri-plugin-fs",
|
||||||
"tauri-plugin-global-shortcut",
|
"tauri-plugin-global-shortcut",
|
||||||
"tauri-plugin-notification",
|
|
||||||
"tauri-plugin-process",
|
"tauri-plugin-process",
|
||||||
"tauri-plugin-shell",
|
"tauri-plugin-shell",
|
||||||
"tauri-plugin-updater",
|
"tauri-plugin-updater",
|
||||||
@@ -1761,16 +1730,6 @@ dependencies = [
|
|||||||
"dirs-sys 0.5.0",
|
"dirs-sys 0.5.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "dirs-next"
|
|
||||||
version = "2.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"dirs-sys-next",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dirs-sys"
|
name = "dirs-sys"
|
||||||
version = "0.3.7"
|
version = "0.3.7"
|
||||||
@@ -1794,17 +1753,6 @@ dependencies = [
|
|||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "dirs-sys-next"
|
|
||||||
version = "0.1.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"redox_users 0.4.6",
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dispatch"
|
name = "dispatch"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -3795,19 +3743,6 @@ version = "0.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
|
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "mac-notification-sys"
|
|
||||||
version = "0.6.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "dce8f34f3717aa37177e723df6c1fc5fb02b2a1087374ea3fe0ea42316dc8f91"
|
|
||||||
dependencies = [
|
|
||||||
"cc",
|
|
||||||
"dirs-next",
|
|
||||||
"objc-foundation",
|
|
||||||
"objc_id",
|
|
||||||
"time",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "malloc_buf"
|
name = "malloc_buf"
|
||||||
version = "0.0.6"
|
version = "0.0.6"
|
||||||
@@ -4195,20 +4130,6 @@ version = "0.3.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
|
checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "notify-rust"
|
|
||||||
version = "4.11.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7fa3b9f2364a09bd359aa0206702882e208437450866a374d5372d64aece4029"
|
|
||||||
dependencies = [
|
|
||||||
"futures-lite 2.6.0",
|
|
||||||
"log",
|
|
||||||
"mac-notification-sys",
|
|
||||||
"serde",
|
|
||||||
"tauri-winrt-notification",
|
|
||||||
"zbus",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ntapi"
|
name = "ntapi"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
@@ -4370,17 +4291,6 @@ dependencies = [
|
|||||||
"malloc_buf",
|
"malloc_buf",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "objc-foundation"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9"
|
|
||||||
dependencies = [
|
|
||||||
"block",
|
|
||||||
"objc",
|
|
||||||
"objc_id",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc-sys"
|
name = "objc-sys"
|
||||||
version = "0.3.5"
|
version = "0.3.5"
|
||||||
@@ -4645,15 +4555,6 @@ dependencies = [
|
|||||||
"objc2-foundation 0.3.0",
|
"objc2-foundation 0.3.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "objc_id"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b"
|
|
||||||
dependencies = [
|
|
||||||
"objc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "object"
|
name = "object"
|
||||||
version = "0.36.7"
|
version = "0.36.7"
|
||||||
@@ -5411,15 +5312,6 @@ version = "2.0.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "quick-xml"
|
|
||||||
version = "0.31.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33"
|
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quick-xml"
|
name = "quick-xml"
|
||||||
version = "0.32.0"
|
version = "0.32.0"
|
||||||
@@ -6570,11 +6462,11 @@ checksum = "13f2b548cd8447f8de0fdf1c592929f70f4fc7039a05e47404b0d096ec6987a1"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"async-channel 1.9.0",
|
"async-channel 1.9.0",
|
||||||
"async-executor",
|
"async-executor",
|
||||||
"async-fs 1.6.0",
|
"async-fs",
|
||||||
"async-io 1.13.0",
|
"async-io 1.13.0",
|
||||||
"async-lock 2.8.0",
|
"async-lock 2.8.0",
|
||||||
"async-net",
|
"async-net",
|
||||||
"async-process 1.8.1",
|
"async-process",
|
||||||
"blocking",
|
"blocking",
|
||||||
"futures-lite 1.13.0",
|
"futures-lite 1.13.0",
|
||||||
]
|
]
|
||||||
@@ -7176,25 +7068,6 @@ dependencies = [
|
|||||||
"thiserror 2.0.12",
|
"thiserror 2.0.12",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tauri-plugin-notification"
|
|
||||||
version = "2.2.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c474c7cc524385e682ccc1e149e13913a66fd8586ac4c2319cf01b78f070d309"
|
|
||||||
dependencies = [
|
|
||||||
"log",
|
|
||||||
"notify-rust",
|
|
||||||
"rand 0.8.5",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"serde_repr",
|
|
||||||
"tauri",
|
|
||||||
"tauri-plugin",
|
|
||||||
"thiserror 2.0.12",
|
|
||||||
"time",
|
|
||||||
"url",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-process"
|
name = "tauri-plugin-process"
|
||||||
version = "2.2.0"
|
version = "2.2.0"
|
||||||
@@ -7369,17 +7242,6 @@ dependencies = [
|
|||||||
"toml",
|
"toml",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tauri-winrt-notification"
|
|
||||||
version = "0.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f89f5fb70d6f62381f5d9b2ba9008196150b40b75f3068eb24faeddf1c686871"
|
|
||||||
dependencies = [
|
|
||||||
"quick-xml 0.31.0",
|
|
||||||
"windows 0.56.0",
|
|
||||||
"windows-version",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.19.1"
|
version = "3.19.1"
|
||||||
@@ -8721,16 +8583,6 @@ dependencies = [
|
|||||||
"windows-version",
|
"windows-version",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows"
|
|
||||||
version = "0.56.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1de69df01bdf1ead2f4ac895dc77c9351aefff65b2f3db429a343f9cbf05e132"
|
|
||||||
dependencies = [
|
|
||||||
"windows-core 0.56.0",
|
|
||||||
"windows-targets 0.52.6",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows"
|
name = "windows"
|
||||||
version = "0.57.0"
|
version = "0.57.0"
|
||||||
@@ -8782,18 +8634,6 @@ dependencies = [
|
|||||||
"windows-targets 0.52.6",
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-core"
|
|
||||||
version = "0.56.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4698e52ed2d08f8658ab0c39512a7c00ee5fe2688c65f8c0a4f06750d729f2a6"
|
|
||||||
dependencies = [
|
|
||||||
"windows-implement 0.56.0",
|
|
||||||
"windows-interface 0.56.0",
|
|
||||||
"windows-result 0.1.2",
|
|
||||||
"windows-targets 0.52.6",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-core"
|
name = "windows-core"
|
||||||
version = "0.57.0"
|
version = "0.57.0"
|
||||||
@@ -8842,17 +8682,6 @@ dependencies = [
|
|||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-implement"
|
|
||||||
version = "0.56.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.100",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-implement"
|
name = "windows-implement"
|
||||||
version = "0.57.0"
|
version = "0.57.0"
|
||||||
@@ -8886,17 +8715,6 @@ dependencies = [
|
|||||||
"syn 2.0.100",
|
"syn 2.0.100",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-interface"
|
|
||||||
version = "0.56.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.100",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-interface"
|
name = "windows-interface"
|
||||||
version = "0.57.0"
|
version = "0.57.0"
|
||||||
@@ -9553,15 +9371,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "59c333f648ea1b647bc95dc1d34807c8e25ed7a6feff3394034dc4776054b236"
|
checksum = "59c333f648ea1b647bc95dc1d34807c8e25ed7a6feff3394034dc4776054b236"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-broadcast",
|
"async-broadcast",
|
||||||
"async-executor",
|
|
||||||
"async-fs 2.1.2",
|
|
||||||
"async-io 2.4.0",
|
|
||||||
"async-lock 3.4.0",
|
|
||||||
"async-process 2.3.0",
|
|
||||||
"async-recursion",
|
"async-recursion",
|
||||||
"async-task",
|
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"blocking",
|
|
||||||
"enumflags2",
|
"enumflags2",
|
||||||
"event-listener 5.3.0",
|
"event-listener 5.3.0",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
@@ -35,10 +35,10 @@ delay_timer = "0.11.6"
|
|||||||
parking_lot = "0.12"
|
parking_lot = "0.12"
|
||||||
percent-encoding = "2.3.1"
|
percent-encoding = "2.3.1"
|
||||||
tokio = { version = "1.44", features = [
|
tokio = { version = "1.44", features = [
|
||||||
"rt-multi-thread",
|
"rt-multi-thread",
|
||||||
"macros",
|
"macros",
|
||||||
"time",
|
"time",
|
||||||
"sync",
|
"sync",
|
||||||
] }
|
] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
reqwest = { version = "0.12", features = ["json", "rustls-tls", "cookies"] }
|
reqwest = { version = "0.12", features = ["json", "rustls-tls", "cookies"] }
|
||||||
@@ -47,17 +47,16 @@ sysproxy = { git = "https://github.com/clash-verge-rev/sysproxy-rs", rev = "3d74
|
|||||||
image = "0.25.6"
|
image = "0.25.6"
|
||||||
imageproc = "0.25.0"
|
imageproc = "0.25.0"
|
||||||
tauri = { version = "2.4.0", features = [
|
tauri = { version = "2.4.0", features = [
|
||||||
"protocol-asset",
|
"protocol-asset",
|
||||||
"devtools",
|
"devtools",
|
||||||
"tray-icon",
|
"tray-icon",
|
||||||
"image-ico",
|
"image-ico",
|
||||||
"image-png",
|
"image-png",
|
||||||
] }
|
] }
|
||||||
network-interface = { version = "2.0.0", features = ["serde"] }
|
network-interface = { version = "2.0.0", features = ["serde"] }
|
||||||
tauri-plugin-shell = "2.2.0"
|
tauri-plugin-shell = "2.2.0"
|
||||||
tauri-plugin-dialog = "2.2.0"
|
tauri-plugin-dialog = "2.2.0"
|
||||||
tauri-plugin-fs = "2.2.0"
|
tauri-plugin-fs = "2.2.0"
|
||||||
tauri-plugin-notification = "2.2.2"
|
|
||||||
tauri-plugin-process = "2.2.0"
|
tauri-plugin-process = "2.2.0"
|
||||||
tauri-plugin-clipboard-manager = "2.2.2"
|
tauri-plugin-clipboard-manager = "2.2.2"
|
||||||
tauri-plugin-deep-link = "2.2.0"
|
tauri-plugin-deep-link = "2.2.0"
|
||||||
|
@@ -68,7 +68,6 @@
|
|||||||
"shell:allow-spawn",
|
"shell:allow-spawn",
|
||||||
"shell:allow-stdin-write",
|
"shell:allow-stdin-write",
|
||||||
"dialog:allow-open",
|
"dialog:allow-open",
|
||||||
"notification:default",
|
|
||||||
"global-shortcut:allow-is-registered",
|
"global-shortcut:allow-is-registered",
|
||||||
"global-shortcut:allow-register",
|
"global-shortcut:allow-register",
|
||||||
"global-shortcut:allow-register-all",
|
"global-shortcut:allow-register-all",
|
||||||
@@ -79,7 +78,6 @@
|
|||||||
"clipboard-manager:allow-read-text",
|
"clipboard-manager:allow-read-text",
|
||||||
"clipboard-manager:allow-write-text",
|
"clipboard-manager:allow-write-text",
|
||||||
"shell:default",
|
"shell:default",
|
||||||
"dialog:default",
|
"dialog:default"
|
||||||
"notification:default"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,9 @@
|
|||||||
use super::CmdResult;
|
use super::CmdResult;
|
||||||
use crate::{feat, utils::dirs, wrap_err};
|
use crate::{
|
||||||
|
feat, logging,
|
||||||
|
utils::{dirs, logging::Type},
|
||||||
|
wrap_err,
|
||||||
|
};
|
||||||
use tauri::Manager;
|
use tauri::Manager;
|
||||||
|
|
||||||
/// 打开应用程序所在目录
|
/// 打开应用程序所在目录
|
||||||
@@ -194,7 +198,14 @@ pub fn copy_icon_file(path: String, icon_info: IconInfo) -> CmdResult<String> {
|
|||||||
)
|
)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
}
|
}
|
||||||
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::CMD,
|
||||||
|
true,
|
||||||
|
"Copying icon file path: {:?} -> file dist: {:?}",
|
||||||
|
path,
|
||||||
|
dest_path
|
||||||
|
);
|
||||||
match fs::copy(file_path, &dest_path) {
|
match fs::copy(file_path, &dest_path) {
|
||||||
Ok(_) => Ok(dest_path.to_string_lossy().to_string()),
|
Ok(_) => Ok(dest_path.to_string_lossy().to_string()),
|
||||||
Err(err) => Err(err.to_string()),
|
Err(err) => Err(err.to_string()),
|
||||||
|
@@ -2,8 +2,8 @@ use super::CmdResult;
|
|||||||
use crate::{
|
use crate::{
|
||||||
config::*,
|
config::*,
|
||||||
core::*,
|
core::*,
|
||||||
feat, log_err, ret_err,
|
feat, log_err, logging, ret_err,
|
||||||
utils::{dirs, help},
|
utils::{dirs, help, logging::Type},
|
||||||
wrap_err,
|
wrap_err,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -77,20 +77,19 @@ pub async fn delete_profile(index: String) -> CmdResult {
|
|||||||
/// 修改profiles的配置
|
/// 修改profiles的配置
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult<bool> {
|
pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult<bool> {
|
||||||
println!("[cmd配置patch] 开始修改配置文件");
|
logging!(info, Type::CMD, true, "开始修改配置文件");
|
||||||
|
|
||||||
// 保存当前配置,以便在验证失败时恢复
|
// 保存当前配置,以便在验证失败时恢复
|
||||||
let current_profile = Config::profiles().latest().current.clone();
|
let current_profile = Config::profiles().latest().current.clone();
|
||||||
println!("[cmd配置patch] 当前配置: {:?}", current_profile);
|
logging!(info, Type::CMD, true, "当前配置: {:?}", current_profile);
|
||||||
|
|
||||||
// 更新profiles配置
|
// 更新profiles配置
|
||||||
println!("[cmd配置patch] 正在更新配置草稿");
|
logging!(info, Type::CMD, true, "正在更新配置草稿");
|
||||||
wrap_err!({ Config::profiles().draft().patch_config(profiles) })?;
|
wrap_err!({ Config::profiles().draft().patch_config(profiles) })?;
|
||||||
|
|
||||||
// 更新配置并进行验证
|
// 更新配置并进行验证
|
||||||
match CoreManager::global().update_config().await {
|
match CoreManager::global().update_config().await {
|
||||||
Ok((true, _)) => {
|
Ok((true, _)) => {
|
||||||
println!("[cmd配置patch] 配置更新成功");
|
logging!(info, Type::CMD, true, "配置更新成功");
|
||||||
handle::Handle::refresh_clash();
|
handle::Handle::refresh_clash();
|
||||||
let _ = tray::Tray::global().update_tooltip();
|
let _ = tray::Tray::global().update_tooltip();
|
||||||
Config::profiles().apply();
|
Config::profiles().apply();
|
||||||
@@ -98,12 +97,17 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult<bool> {
|
|||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
Ok((false, error_msg)) => {
|
Ok((false, error_msg)) => {
|
||||||
println!("[cmd配置patch] 配置验证失败: {}", error_msg);
|
logging!(warn, Type::CMD, true, "配置验证失败: {}", error_msg);
|
||||||
Config::profiles().discard();
|
Config::profiles().discard();
|
||||||
|
|
||||||
// 如果验证失败,恢复到之前的配置
|
// 如果验证失败,恢复到之前的配置
|
||||||
if let Some(prev_profile) = current_profile {
|
if let Some(prev_profile) = current_profile {
|
||||||
println!("[cmd配置patch] 尝试恢复到之前的配置: {}", prev_profile);
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::CMD,
|
||||||
|
true,
|
||||||
|
"尝试恢复到之前的配置: {}",
|
||||||
|
prev_profile
|
||||||
|
);
|
||||||
let restore_profiles = IProfiles {
|
let restore_profiles = IProfiles {
|
||||||
current: Some(prev_profile),
|
current: Some(prev_profile),
|
||||||
items: None,
|
items: None,
|
||||||
@@ -112,7 +116,7 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult<bool> {
|
|||||||
wrap_err!({ Config::profiles().draft().patch_config(restore_profiles) })?;
|
wrap_err!({ Config::profiles().draft().patch_config(restore_profiles) })?;
|
||||||
Config::profiles().apply();
|
Config::profiles().apply();
|
||||||
wrap_err!(Config::profiles().data().save_file())?;
|
wrap_err!(Config::profiles().data().save_file())?;
|
||||||
println!("[cmd配置patch] 成功恢复到之前的配置");
|
logging!(info, Type::CMD, true, "成功恢复到之前的配置");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 发送验证错误通知
|
// 发送验证错误通知
|
||||||
@@ -120,7 +124,7 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult<bool> {
|
|||||||
Ok(false)
|
Ok(false)
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("[cmd配置patch] 更新过程发生错误: {}", e);
|
logging!(warn, Type::CMD, true, "更新过程发生错误: {}", e);
|
||||||
Config::profiles().discard();
|
Config::profiles().discard();
|
||||||
handle::Handle::notice_message("config_validate::boot_error", e.to_string());
|
handle::Handle::notice_message("config_validate::boot_error", e.to_string());
|
||||||
Ok(false)
|
Ok(false)
|
||||||
|
@@ -4,21 +4,21 @@ use crate::module::mihomo::MihomoManager;
|
|||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn get_proxies() -> CmdResult<serde_json::Value> {
|
pub async fn get_proxies() -> CmdResult<serde_json::Value> {
|
||||||
let mannager = MihomoManager::global();
|
let mannager = MihomoManager::global();
|
||||||
let proxies = mannager
|
|
||||||
|
mannager
|
||||||
.refresh_proxies()
|
.refresh_proxies()
|
||||||
.await
|
.await
|
||||||
.map(|_| mannager.get_proxies())
|
.map(|_| mannager.get_proxies())
|
||||||
.or_else(|_| Ok(mannager.get_proxies()));
|
.or_else(|_| Ok(mannager.get_proxies()))
|
||||||
proxies
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn get_providers_proxies() -> CmdResult<serde_json::Value> {
|
pub async fn get_providers_proxies() -> CmdResult<serde_json::Value> {
|
||||||
let mannager = MihomoManager::global();
|
let mannager = MihomoManager::global();
|
||||||
let providers = mannager
|
|
||||||
|
mannager
|
||||||
.refresh_providers_proxies()
|
.refresh_providers_proxies()
|
||||||
.await
|
.await
|
||||||
.map(|_| mannager.get_providers_proxies())
|
.map(|_| mannager.get_providers_proxies())
|
||||||
.or_else(|_| Ok(mannager.get_providers_proxies()));
|
.or_else(|_| Ok(mannager.get_providers_proxies()))
|
||||||
providers
|
|
||||||
}
|
}
|
||||||
|
@@ -4,9 +4,9 @@ use crate::{
|
|||||||
config::*,
|
config::*,
|
||||||
core::{
|
core::{
|
||||||
handle,
|
handle,
|
||||||
service::{self, is_service_available},
|
service::{self},
|
||||||
},
|
},
|
||||||
log_err, logging, logging_error,
|
logging, logging_error,
|
||||||
module::mihomo::MihomoManager,
|
module::mihomo::MihomoManager,
|
||||||
utils::{
|
utils::{
|
||||||
dirs,
|
dirs,
|
||||||
@@ -63,7 +63,14 @@ impl CoreManager {
|
|||||||
let content = match std::fs::read_to_string(path) {
|
let content = match std::fs::read_to_string(path) {
|
||||||
Ok(content) => content,
|
Ok(content) => content,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::warn!(target: "app", "无法读取文件以检测类型: {}, 错误: {}", path, err);
|
logging!(
|
||||||
|
warn,
|
||||||
|
Type::Config,
|
||||||
|
true,
|
||||||
|
"无法读取文件以检测类型: {}, 错误: {}",
|
||||||
|
path,
|
||||||
|
err
|
||||||
|
);
|
||||||
return Err(anyhow::anyhow!(
|
return Err(anyhow::anyhow!(
|
||||||
"Failed to read file to detect type: {}",
|
"Failed to read file to detect type: {}",
|
||||||
err
|
err
|
||||||
@@ -114,7 +121,13 @@ impl CoreManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 默认情况:无法确定时,假设为非脚本文件(更安全)
|
// 默认情况:无法确定时,假设为非脚本文件(更安全)
|
||||||
log::debug!(target: "app", "无法确定文件类型,默认当作YAML处理: {}", path);
|
logging!(
|
||||||
|
debug,
|
||||||
|
Type::Config,
|
||||||
|
true,
|
||||||
|
"无法确定文件类型,默认当作YAML处理: {}",
|
||||||
|
path
|
||||||
|
);
|
||||||
Ok(false)
|
Ok(false)
|
||||||
}
|
}
|
||||||
/// 使用默认配置
|
/// 使用默认配置
|
||||||
@@ -147,7 +160,7 @@ impl CoreManager {
|
|||||||
) -> Result<(bool, String)> {
|
) -> Result<(bool, String)> {
|
||||||
// 检查程序是否正在退出,如果是则跳过验证
|
// 检查程序是否正在退出,如果是则跳过验证
|
||||||
if handle::Handle::global().is_exiting() {
|
if handle::Handle::global().is_exiting() {
|
||||||
println!("[core配置验证] 应用正在退出,跳过验证");
|
logging!(info, Type::Core, true, "应用正在退出,跳过验证");
|
||||||
return Ok((true, String::new()));
|
return Ok((true, String::new()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,8 +173,11 @@ impl CoreManager {
|
|||||||
|
|
||||||
// 如果是合并文件且不是强制验证,执行语法检查但不进行完整验证
|
// 如果是合并文件且不是强制验证,执行语法检查但不进行完整验证
|
||||||
if is_merge_file.unwrap_or(false) {
|
if is_merge_file.unwrap_or(false) {
|
||||||
println!(
|
logging!(
|
||||||
"[core配置验证] 检测到Merge文件,仅进行语法检查: {}",
|
info,
|
||||||
|
Type::Config,
|
||||||
|
true,
|
||||||
|
"检测到Merge文件,仅进行语法检查: {}",
|
||||||
config_path
|
config_path
|
||||||
);
|
);
|
||||||
return self.validate_file_syntax(config_path).await;
|
return self.validate_file_syntax(config_path).await;
|
||||||
@@ -175,19 +191,38 @@ impl CoreManager {
|
|||||||
Ok(result) => result,
|
Ok(result) => result,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
// 如果无法确定文件类型,尝试使用Clash内核验证
|
// 如果无法确定文件类型,尝试使用Clash内核验证
|
||||||
log::warn!(target: "app", "无法确定文件类型: {}, 错误: {}", config_path, err);
|
logging!(
|
||||||
|
warn,
|
||||||
|
Type::Config,
|
||||||
|
true,
|
||||||
|
"无法确定文件类型: {}, 错误: {}",
|
||||||
|
config_path,
|
||||||
|
err
|
||||||
|
);
|
||||||
return self.validate_config_internal(config_path).await;
|
return self.validate_config_internal(config_path).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if is_script {
|
if is_script {
|
||||||
log::info!(target: "app", "检测到脚本文件,使用JavaScript验证: {}", config_path);
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Config,
|
||||||
|
true,
|
||||||
|
"检测到脚本文件,使用JavaScript验证: {}",
|
||||||
|
config_path
|
||||||
|
);
|
||||||
return self.validate_script_file(config_path).await;
|
return self.validate_script_file(config_path).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 对YAML配置文件使用Clash内核验证
|
// 对YAML配置文件使用Clash内核验证
|
||||||
log::info!(target: "app", "使用Clash内核验证配置文件: {}", config_path);
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Config,
|
||||||
|
true,
|
||||||
|
"使用Clash内核验证配置文件: {}",
|
||||||
|
config_path
|
||||||
|
);
|
||||||
self.validate_config_internal(config_path).await
|
self.validate_config_internal(config_path).await
|
||||||
}
|
}
|
||||||
/// 内部验证配置文件的实现
|
/// 内部验证配置文件的实现
|
||||||
@@ -234,7 +269,7 @@ impl CoreManager {
|
|||||||
logging!(info, Type::Config, true, "-------- 验证结果 --------");
|
logging!(info, Type::Config, true, "-------- 验证结果 --------");
|
||||||
|
|
||||||
if !stderr.is_empty() {
|
if !stderr.is_empty() {
|
||||||
logging!(info, Type::Core, true, "stderr输出:\n{}", stderr);
|
logging!(info, Type::Config, true, "stderr输出:\n{}", stderr);
|
||||||
}
|
}
|
||||||
|
|
||||||
if has_error {
|
if has_error {
|
||||||
@@ -259,29 +294,28 @@ impl CoreManager {
|
|||||||
}
|
}
|
||||||
/// 只进行文件语法检查,不进行完整验证
|
/// 只进行文件语法检查,不进行完整验证
|
||||||
async fn validate_file_syntax(&self, config_path: &str) -> Result<(bool, String)> {
|
async fn validate_file_syntax(&self, config_path: &str) -> Result<(bool, String)> {
|
||||||
println!("[core配置语法检查] 开始检查文件: {}", config_path);
|
logging!(info, Type::Config, true, "开始检查文件: {}", config_path);
|
||||||
|
|
||||||
// 读取文件内容
|
// 读取文件内容
|
||||||
let content = match std::fs::read_to_string(config_path) {
|
let content = match std::fs::read_to_string(config_path) {
|
||||||
Ok(content) => content,
|
Ok(content) => content,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let error_msg = format!("Failed to read file: {}", err);
|
let error_msg = format!("Failed to read file: {}", err);
|
||||||
println!("[core配置语法检查] 无法读取文件: {}", error_msg);
|
logging!(error, Type::Config, true, "无法读取文件: {}", error_msg);
|
||||||
return Ok((false, error_msg));
|
return Ok((false, error_msg));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 对YAML文件尝试解析,只检查语法正确性
|
// 对YAML文件尝试解析,只检查语法正确性
|
||||||
println!("[core配置语法检查] 进行YAML语法检查");
|
logging!(info, Type::Config, true, "进行YAML语法检查");
|
||||||
match serde_yaml::from_str::<serde_yaml::Value>(&content) {
|
match serde_yaml::from_str::<serde_yaml::Value>(&content) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
println!("[core配置语法检查] YAML语法检查通过");
|
logging!(info, Type::Config, true, "YAML语法检查通过");
|
||||||
Ok((true, String::new()))
|
Ok((true, String::new()))
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
// 使用标准化的前缀,以便错误处理函数能正确识别
|
// 使用标准化的前缀,以便错误处理函数能正确识别
|
||||||
let error_msg = format!("YAML syntax error: {}", err);
|
let error_msg = format!("YAML syntax error: {}", err);
|
||||||
println!("[core配置语法检查] YAML语法错误: {}", error_msg);
|
logging!(error, Type::Config, true, "YAML语法错误: {}", error_msg);
|
||||||
Ok((false, error_msg))
|
Ok((false, error_msg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -293,13 +327,13 @@ impl CoreManager {
|
|||||||
Ok(content) => content,
|
Ok(content) => content,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let error_msg = format!("Failed to read script file: {}", err);
|
let error_msg = format!("Failed to read script file: {}", err);
|
||||||
log::warn!(target: "app", "脚本语法错误: {}", err);
|
logging!(warn, Type::Config, true, "脚本语法错误: {}", err);
|
||||||
//handle::Handle::notice_message("config_validate::script_syntax_error", &error_msg);
|
//handle::Handle::notice_message("config_validate::script_syntax_error", &error_msg);
|
||||||
return Ok((false, error_msg));
|
return Ok((false, error_msg));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
log::debug!(target: "app", "验证脚本文件: {}", path);
|
logging!(debug, Type::Config, true, "验证脚本文件: {}", path);
|
||||||
|
|
||||||
// 使用boa引擎进行基本语法检查
|
// 使用boa引擎进行基本语法检查
|
||||||
use boa_engine::{Context, Source};
|
use boa_engine::{Context, Source};
|
||||||
@@ -309,7 +343,7 @@ impl CoreManager {
|
|||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
log::debug!(target: "app", "脚本语法验证通过: {}", path);
|
logging!(debug, Type::Config, true, "脚本语法验证通过: {}", path);
|
||||||
|
|
||||||
// 检查脚本是否包含main函数
|
// 检查脚本是否包含main函数
|
||||||
if !content.contains("function main")
|
if !content.contains("function main")
|
||||||
@@ -317,7 +351,7 @@ impl CoreManager {
|
|||||||
&& !content.contains("let main")
|
&& !content.contains("let main")
|
||||||
{
|
{
|
||||||
let error_msg = "Script must contain a main function";
|
let error_msg = "Script must contain a main function";
|
||||||
log::warn!(target: "app", "脚本缺少main函数: {}", path);
|
logging!(warn, Type::Config, true, "脚本缺少main函数: {}", path);
|
||||||
//handle::Handle::notice_message("config_validate::script_missing_main", error_msg);
|
//handle::Handle::notice_message("config_validate::script_missing_main", error_msg);
|
||||||
return Ok((false, error_msg.to_string()));
|
return Ok((false, error_msg.to_string()));
|
||||||
}
|
}
|
||||||
@@ -326,7 +360,7 @@ impl CoreManager {
|
|||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let error_msg = format!("Script syntax error: {}", err);
|
let error_msg = format!("Script syntax error: {}", err);
|
||||||
log::warn!(target: "app", "脚本语法错误: {}", err);
|
logging!(warn, Type::Config, true, "脚本语法错误: {}", err);
|
||||||
//handle::Handle::notice_message("config_validate::script_syntax_error", &error_msg);
|
//handle::Handle::notice_message("config_validate::script_syntax_error", &error_msg);
|
||||||
Ok((false, error_msg))
|
Ok((false, error_msg))
|
||||||
}
|
}
|
||||||
@@ -336,39 +370,45 @@ impl CoreManager {
|
|||||||
pub async fn update_config(&self) -> Result<(bool, String)> {
|
pub async fn update_config(&self) -> Result<(bool, String)> {
|
||||||
// 检查程序是否正在退出,如果是则跳过完整验证流程
|
// 检查程序是否正在退出,如果是则跳过完整验证流程
|
||||||
if handle::Handle::global().is_exiting() {
|
if handle::Handle::global().is_exiting() {
|
||||||
println!("[core配置更新] 应用正在退出,跳过验证");
|
logging!(info, Type::Config, true, "应用正在退出,跳过验证");
|
||||||
return Ok((true, String::new()));
|
return Ok((true, String::new()));
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("[core配置更新] 开始更新配置");
|
logging!(info, Type::Config, true, "开始更新配置");
|
||||||
|
|
||||||
// 1. 先生成新的配置内容
|
// 1. 先生成新的配置内容
|
||||||
println!("[core配置更新] 生成新的配置内容");
|
logging!(info, Type::Config, true, "生成新的配置内容");
|
||||||
Config::generate().await?;
|
Config::generate().await?;
|
||||||
|
|
||||||
// 2. 生成临时文件并进行验证
|
// 2. 生成临时文件并进行验证
|
||||||
println!("[core配置更新] 生成临时配置文件用于验证");
|
logging!(info, Type::Config, true, "生成临时配置文件用于验证");
|
||||||
let temp_config = Config::generate_file(ConfigType::Check)?;
|
let temp_config = Config::generate_file(ConfigType::Check)?;
|
||||||
let temp_config = dirs::path_to_str(&temp_config)?;
|
let temp_config = dirs::path_to_str(&temp_config)?;
|
||||||
println!("[core配置更新] 临时配置文件路径: {}", temp_config);
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Config,
|
||||||
|
true,
|
||||||
|
"临时配置文件路径: {}",
|
||||||
|
temp_config
|
||||||
|
);
|
||||||
|
|
||||||
// 3. 验证配置
|
// 3. 验证配置
|
||||||
match self.validate_config().await {
|
match self.validate_config().await {
|
||||||
Ok((true, _)) => {
|
Ok((true, _)) => {
|
||||||
println!("[core配置更新] 配置验证通过");
|
logging!(info, Type::Config, true, "配置验证通过");
|
||||||
// 4. 验证通过后,生成正式的运行时配置
|
// 4. 验证通过后,生成正式的运行时配置
|
||||||
println!("[core配置更新] 生成运行时配置");
|
logging!(info, Type::Config, true, "生成运行时配置");
|
||||||
let run_path = Config::generate_file(ConfigType::Run)?;
|
let run_path = Config::generate_file(ConfigType::Run)?;
|
||||||
logging_error!(Type::Core, true, self.put_configs_force(run_path).await);
|
logging_error!(Type::Config, true, self.put_configs_force(run_path).await);
|
||||||
Ok((true, "something".into()))
|
Ok((true, "something".into()))
|
||||||
}
|
}
|
||||||
Ok((false, error_msg)) => {
|
Ok((false, error_msg)) => {
|
||||||
println!("[core配置更新] 配置验证失败: {}", error_msg);
|
logging!(warn, Type::Config, true, "配置验证失败: {}", error_msg);
|
||||||
Config::runtime().discard();
|
Config::runtime().discard();
|
||||||
Ok((false, error_msg))
|
Ok((false, error_msg))
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("[core配置更新] 验证过程发生错误: {}", e);
|
logging!(warn, Type::Config, true, "验证过程发生错误: {}", e);
|
||||||
Config::runtime().discard();
|
Config::runtime().discard();
|
||||||
Err(e)
|
Err(e)
|
||||||
}
|
}
|
||||||
@@ -480,14 +520,10 @@ impl CoreManager {
|
|||||||
|
|
||||||
pub async fn init(&self) -> Result<()> {
|
pub async fn init(&self) -> Result<()> {
|
||||||
logging!(trace, Type::Core, "Initializing core");
|
logging!(trace, Type::Core, "Initializing core");
|
||||||
if is_service_available().await.is_ok() {
|
self.start_core().await?;
|
||||||
Self::global().start_core_by_service().await?;
|
|
||||||
} else {
|
|
||||||
Self::global().start_core_by_sidecar().await?;
|
|
||||||
}
|
|
||||||
logging!(trace, Type::Core, "Initied core");
|
logging!(trace, Type::Core, "Initied core");
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
log_err!(Tray::global().subscribe_traffic().await);
|
logging_error!(Type::Core, true, Tray::global().subscribe_traffic().await);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -503,7 +539,10 @@ impl CoreManager {
|
|||||||
|
|
||||||
/// 启动核心
|
/// 启动核心
|
||||||
pub async fn start_core(&self) -> Result<()> {
|
pub async fn start_core(&self) -> Result<()> {
|
||||||
if is_service_available().await.is_ok() {
|
if service::is_service_available().await.is_ok() {
|
||||||
|
if service::check_service_needs_reinstall().await {
|
||||||
|
service::reinstall_service().await?;
|
||||||
|
}
|
||||||
self.start_core_by_service().await?;
|
self.start_core_by_service().await?;
|
||||||
} else {
|
} else {
|
||||||
self.start_core_by_sidecar().await?;
|
self.start_core_by_sidecar().await?;
|
||||||
|
@@ -8,7 +8,7 @@ use crate::{
|
|||||||
feat,
|
feat,
|
||||||
module::{lightweight::entry_lightweight_mode, mihomo::Rate},
|
module::{lightweight::entry_lightweight_mode, mihomo::Rate},
|
||||||
resolve,
|
resolve,
|
||||||
utils::{dirs, i18n::t, resolve::VERSION},
|
utils::{dirs::find_target_icons, i18n::t, resolve::VERSION},
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
@@ -20,10 +20,7 @@ use parking_lot::Mutex;
|
|||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub use speed_rate::{SpeedRate, Traffic};
|
pub use speed_rate::{SpeedRate, Traffic};
|
||||||
#[cfg(target_os = "macos")]
|
use std::fs;
|
||||||
use std::collections::hash_map::DefaultHasher;
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
use std::hash::{Hash, Hasher};
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tauri::{
|
use tauri::{
|
||||||
@@ -35,19 +32,124 @@ use tauri::{
|
|||||||
use tokio::sync::broadcast;
|
use tokio::sync::broadcast;
|
||||||
|
|
||||||
use super::handle;
|
use super::handle;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct TrayState {}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub struct Tray {
|
pub struct Tray {
|
||||||
pub speed_rate: Arc<Mutex<Option<SpeedRate>>>,
|
pub speed_rate: Arc<Mutex<Option<SpeedRate>>>,
|
||||||
shutdown_tx: Arc<RwLock<Option<broadcast::Sender<()>>>>,
|
shutdown_tx: Arc<RwLock<Option<broadcast::Sender<()>>>>,
|
||||||
is_subscribed: Arc<RwLock<bool>>,
|
is_subscribed: Arc<RwLock<bool>>,
|
||||||
pub icon_hash: Arc<Mutex<Option<u64>>>,
|
|
||||||
pub icon_cache: Arc<Mutex<Option<Vec<u8>>>>,
|
|
||||||
pub rate_cache: Arc<Mutex<Option<Rate>>>,
|
pub rate_cache: Arc<Mutex<Option<Rate>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
pub struct Tray {}
|
pub struct Tray {}
|
||||||
|
|
||||||
|
impl TrayState {
|
||||||
|
pub fn get_common_tray_icon() -> (bool, Vec<u8>) {
|
||||||
|
let verge = Config::verge().latest().clone();
|
||||||
|
let is_common_tray_icon = verge.common_tray_icon.clone().unwrap_or(false);
|
||||||
|
if is_common_tray_icon {
|
||||||
|
if let Some(common_icon_path) = find_target_icons("common").unwrap() {
|
||||||
|
let icon_data = fs::read(common_icon_path).unwrap();
|
||||||
|
return (true, icon_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
let tray_icon_colorful = verge.tray_icon.clone().unwrap_or("monochrome".to_string());
|
||||||
|
if tray_icon_colorful == "monochrome" {
|
||||||
|
(
|
||||||
|
false,
|
||||||
|
include_bytes!("../../../icons/tray-icon-mono.ico").to_vec(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
false,
|
||||||
|
include_bytes!("../../../icons/tray-icon.ico").to_vec(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
{
|
||||||
|
(
|
||||||
|
false,
|
||||||
|
include_bytes!("../../../icons/tray-icon.ico").to_vec(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_sysproxy_tray_icon() -> (bool, Vec<u8>) {
|
||||||
|
let verge = Config::verge().latest().clone();
|
||||||
|
let is_sysproxy_tray_icon = verge.sysproxy_tray_icon.clone().unwrap_or(false);
|
||||||
|
if is_sysproxy_tray_icon {
|
||||||
|
if let Some(sysproxy_icon_path) = find_target_icons("sysproxy").unwrap() {
|
||||||
|
let icon_data = fs::read(sysproxy_icon_path).unwrap();
|
||||||
|
return (true, icon_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
let tray_icon_colorful = verge.tray_icon.clone().unwrap_or("monochrome".to_string());
|
||||||
|
if tray_icon_colorful == "monochrome" {
|
||||||
|
(
|
||||||
|
false,
|
||||||
|
include_bytes!("../../../icons/tray-icon-sys-mono.ico").to_vec(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
false,
|
||||||
|
include_bytes!("../../../icons/tray-icon-sys.ico").to_vec(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
{
|
||||||
|
(
|
||||||
|
false,
|
||||||
|
include_bytes!("../../../icons/tray-icon-sys.ico").to_vec(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_tun_tray_icon() -> (bool, Vec<u8>) {
|
||||||
|
let verge = Config::verge().latest().clone();
|
||||||
|
let is_tun_tray_icon = verge.tun_tray_icon.clone().unwrap_or(false);
|
||||||
|
if is_tun_tray_icon {
|
||||||
|
if let Some(tun_icon_path) = find_target_icons("tun").unwrap() {
|
||||||
|
let icon_data = fs::read(tun_icon_path).unwrap();
|
||||||
|
return (true, icon_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
let tray_icon_colorful = verge.tray_icon.clone().unwrap_or("monochrome".to_string());
|
||||||
|
if tray_icon_colorful == "monochrome" {
|
||||||
|
(
|
||||||
|
false,
|
||||||
|
include_bytes!("../../../icons/tray-icon-tun-mono.ico").to_vec(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
false,
|
||||||
|
include_bytes!("../../../icons/tray-icon-tun.ico").to_vec(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
{
|
||||||
|
(
|
||||||
|
false,
|
||||||
|
include_bytes!("../../../icons/tray-icon-tun.ico").to_vec(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Tray {
|
impl Tray {
|
||||||
pub fn global() -> &'static Tray {
|
pub fn global() -> &'static Tray {
|
||||||
static TRAY: OnceCell<Tray> = OnceCell::new();
|
static TRAY: OnceCell<Tray> = OnceCell::new();
|
||||||
@@ -57,8 +159,6 @@ impl Tray {
|
|||||||
speed_rate: Arc::new(Mutex::new(None)),
|
speed_rate: Arc::new(Mutex::new(None)),
|
||||||
shutdown_tx: Arc::new(RwLock::new(None)),
|
shutdown_tx: Arc::new(RwLock::new(None)),
|
||||||
is_subscribed: Arc::new(RwLock::new(false)),
|
is_subscribed: Arc::new(RwLock::new(false)),
|
||||||
icon_hash: Arc::new(Mutex::new(None)),
|
|
||||||
icon_cache: Arc::new(Mutex::new(None)),
|
|
||||||
rate_cache: Arc::new(Mutex::new(None)),
|
rate_cache: Arc::new(Mutex::new(None)),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -159,107 +259,29 @@ impl Tray {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 更新托盘图标
|
/// 更新托盘图标
|
||||||
#[allow(unused_variables)]
|
|
||||||
pub fn update_icon(&self, rate: Option<Rate>) -> Result<()> {
|
pub fn update_icon(&self, rate: Option<Rate>) -> Result<()> {
|
||||||
let app_handle = handle::Handle::global().app_handle().unwrap();
|
|
||||||
let verge = Config::verge().latest().clone();
|
let verge = Config::verge().latest().clone();
|
||||||
let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false);
|
let system_mode = verge.enable_system_proxy.as_ref().unwrap_or(&false);
|
||||||
let tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false);
|
let tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false);
|
||||||
|
|
||||||
let common_tray_icon = verge.common_tray_icon.as_ref().unwrap_or(&false);
|
let app_handle = handle::Handle::global().app_handle().unwrap();
|
||||||
let sysproxy_tray_icon = verge.sysproxy_tray_icon.as_ref().unwrap_or(&false);
|
|
||||||
let tun_tray_icon = verge.tun_tray_icon.as_ref().unwrap_or(&false);
|
|
||||||
|
|
||||||
let tray = app_handle.tray_by_id("main").unwrap();
|
let tray = app_handle.tray_by_id("main").unwrap();
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
let (is_custom_icon, icon_bytes) = match (*system_mode, *tun_mode) {
|
||||||
let tray_icon = verge.tray_icon.clone().unwrap_or("monochrome".to_string());
|
(true, true) => TrayState::get_tun_tray_icon(),
|
||||||
|
(true, false) => TrayState::get_sysproxy_tray_icon(),
|
||||||
let icon_bytes = if *system_proxy && !*tun_mode {
|
(false, true) => TrayState::get_tun_tray_icon(),
|
||||||
#[cfg(target_os = "macos")]
|
(false, false) => TrayState::get_common_tray_icon(),
|
||||||
let mut icon = match tray_icon.as_str() {
|
|
||||||
"colorful" => include_bytes!("../../../icons/tray-icon-sys.ico").to_vec(),
|
|
||||||
_ => include_bytes!("../../../icons/tray-icon-sys-mono.ico").to_vec(),
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
|
||||||
let mut icon = include_bytes!("../../../icons/tray-icon-sys.ico").to_vec();
|
|
||||||
if *sysproxy_tray_icon {
|
|
||||||
let icon_dir_path = dirs::app_home_dir()?.join("icons");
|
|
||||||
let png_path = icon_dir_path.join("sysproxy.png");
|
|
||||||
let ico_path = icon_dir_path.join("sysproxy.ico");
|
|
||||||
if ico_path.exists() {
|
|
||||||
icon = std::fs::read(ico_path).unwrap();
|
|
||||||
} else if png_path.exists() {
|
|
||||||
icon = std::fs::read(png_path).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
icon
|
|
||||||
} else if *tun_mode {
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
let mut icon = match tray_icon.as_str() {
|
|
||||||
"colorful" => include_bytes!("../../../icons/tray-icon-tun.ico").to_vec(),
|
|
||||||
_ => include_bytes!("../../../icons/tray-icon-tun-mono.ico").to_vec(),
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
|
||||||
let mut icon = include_bytes!("../../../icons/tray-icon-tun.ico").to_vec();
|
|
||||||
if *tun_tray_icon {
|
|
||||||
let icon_dir_path = dirs::app_home_dir()?.join("icons");
|
|
||||||
let png_path = icon_dir_path.join("tun.png");
|
|
||||||
let ico_path = icon_dir_path.join("tun.ico");
|
|
||||||
if ico_path.exists() {
|
|
||||||
icon = std::fs::read(ico_path).unwrap();
|
|
||||||
} else if png_path.exists() {
|
|
||||||
icon = std::fs::read(png_path).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
icon
|
|
||||||
} else {
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
let mut icon = match tray_icon.as_str() {
|
|
||||||
"colorful" => include_bytes!("../../../icons/tray-icon.ico").to_vec(),
|
|
||||||
_ => include_bytes!("../../../icons/tray-icon-mono.ico").to_vec(),
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
|
||||||
let mut icon = include_bytes!("../../../icons/tray-icon.ico").to_vec();
|
|
||||||
if *common_tray_icon {
|
|
||||||
let icon_dir_path = dirs::app_home_dir()?.join("icons");
|
|
||||||
let png_path = icon_dir_path.join("common.png");
|
|
||||||
let ico_path = icon_dir_path.join("common.ico");
|
|
||||||
if ico_path.exists() {
|
|
||||||
icon = std::fs::read(ico_path).unwrap();
|
|
||||||
} else if png_path.exists() {
|
|
||||||
icon = std::fs::read(png_path).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
icon
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
{
|
{
|
||||||
let enable_tray_speed = verge.enable_tray_speed.unwrap_or(true);
|
let enable_tray_speed = verge.enable_tray_speed.unwrap_or(true);
|
||||||
let enable_tray_icon = verge.enable_tray_icon.unwrap_or(true);
|
let enable_tray_icon = verge.enable_tray_icon.unwrap_or(true);
|
||||||
let is_colorful = tray_icon == "colorful";
|
let colorful = verge.tray_icon.clone().unwrap_or("monochrome".to_string());
|
||||||
|
let is_colorful = colorful == "colorful";
|
||||||
let icon_hash = {
|
|
||||||
let mut hasher = DefaultHasher::new();
|
|
||||||
icon_bytes.clone().hash(&mut hasher);
|
|
||||||
hasher.finish()
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut icon_hash_guard = self.icon_hash.lock();
|
|
||||||
let mut icon_bytes_guard = self.icon_cache.lock();
|
|
||||||
if *icon_hash_guard != Some(icon_hash) {
|
|
||||||
*icon_hash_guard = Some(icon_hash);
|
|
||||||
*icon_bytes_guard = Some(icon_bytes.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
if !enable_tray_speed {
|
if !enable_tray_speed {
|
||||||
let _ = tray.set_icon(Some(tauri::image::Image::from_bytes(
|
let _ = tray.set_icon(Some(tauri::image::Image::from_bytes(&icon_bytes)?));
|
||||||
&(*icon_bytes_guard).clone().unwrap(),
|
|
||||||
)?));
|
|
||||||
let _ = tray.set_icon_as_template(!is_colorful);
|
let _ = tray.set_icon_as_template(!is_colorful);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -280,16 +302,20 @@ impl Tray {
|
|||||||
*rate_guard = rate;
|
*rate_guard = rate;
|
||||||
|
|
||||||
let bytes = if enable_tray_icon {
|
let bytes = if enable_tray_icon {
|
||||||
Some(icon_bytes_guard.as_ref().unwrap())
|
Some(icon_bytes)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let rate = rate_guard.as_ref();
|
let rate = rate_guard.as_ref();
|
||||||
let rate_bytes = SpeedRate::add_speed_text(bytes, rate).unwrap();
|
let rate_bytes = SpeedRate::add_speed_text(is_custom_icon, bytes, rate).unwrap();
|
||||||
|
|
||||||
let _ = tray.set_icon(Some(tauri::image::Image::from_bytes(&rate_bytes)?));
|
let _ = tray.set_icon(Some(tauri::image::Image::from_bytes(&rate_bytes)?));
|
||||||
let _ = tray.set_icon_as_template(!is_colorful);
|
if !is_custom_icon {
|
||||||
|
let _ = tray.set_icon_as_template(!is_colorful);
|
||||||
|
} else {
|
||||||
|
let _ = tray.set_icon_as_template(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@@ -77,14 +77,15 @@ impl SpeedRate {
|
|||||||
|
|
||||||
// 分离图标加载和速率渲染
|
// 分离图标加载和速率渲染
|
||||||
pub fn add_speed_text<'a>(
|
pub fn add_speed_text<'a>(
|
||||||
icon_bytes: Option<&'a Vec<u8>>,
|
is_custom_icon: bool,
|
||||||
|
icon_bytes: Option<Vec<u8>>,
|
||||||
rate: Option<&'a Rate>,
|
rate: Option<&'a Rate>,
|
||||||
) -> Result<Vec<u8>> {
|
) -> Result<Vec<u8>> {
|
||||||
let rate = rate.unwrap_or(&Rate { up: 0, down: 0 });
|
let rate = rate.unwrap_or(&Rate { up: 0, down: 0 });
|
||||||
|
|
||||||
let (mut icon_width, mut icon_height) = (0, 256);
|
let (mut icon_width, mut icon_height) = (0, 256);
|
||||||
let icon_image = if let Some(bytes) = icon_bytes {
|
let icon_image = if let Some(bytes) = icon_bytes.clone() {
|
||||||
let icon_image = image::load_from_memory(bytes)?;
|
let icon_image = image::load_from_memory(&bytes)?;
|
||||||
icon_width = icon_image.width();
|
icon_width = icon_image.width();
|
||||||
icon_height = icon_image.height();
|
icon_height = icon_image.height();
|
||||||
icon_image
|
icon_image
|
||||||
@@ -94,23 +95,24 @@ impl SpeedRate {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 判断是否为彩色图标
|
// 判断是否为彩色图标
|
||||||
let is_colorful = if let Some(bytes) = icon_bytes {
|
let is_colorful = if let Some(bytes) = icon_bytes.clone() {
|
||||||
!crate::utils::help::is_monochrome_image_from_bytes(bytes).unwrap_or(false)
|
!crate::utils::help::is_monochrome_image_from_bytes(&bytes).unwrap_or(false)
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
|
|
||||||
// 增加文本宽度和间距
|
let total_width = match (is_custom_icon, icon_bytes.is_some()) {
|
||||||
let total_width = if icon_bytes.is_some() {
|
(true, true) => 510,
|
||||||
if icon_width < 580 {
|
(true, false) => 740,
|
||||||
icon_width + 580
|
(false, false) => 740,
|
||||||
} else {
|
(false, true) => icon_width + 740,
|
||||||
icon_width
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
580
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// println!(
|
||||||
|
// "icon_height: {}, icon_wight: {}, total_width: {}",
|
||||||
|
// icon_height, icon_width, total_width
|
||||||
|
// );
|
||||||
|
|
||||||
// 创建新的透明画布
|
// 创建新的透明画布
|
||||||
let mut combined_image = RgbaImage::new(total_width, icon_height);
|
let mut combined_image = RgbaImage::new(total_width, icon_height);
|
||||||
|
|
||||||
@@ -145,17 +147,30 @@ impl SpeedRate {
|
|||||||
let scale = ab_glyph::PxScale::from(font_size);
|
let scale = ab_glyph::PxScale::from(font_size);
|
||||||
|
|
||||||
// 使用更简洁的速率格式
|
// 使用更简洁的速率格式
|
||||||
let up_text = format_bytes_speed(rate.up);
|
let up_text = format!("↑ {}", format_bytes_speed(rate.up));
|
||||||
let down_text = format_bytes_speed(rate.down);
|
let down_text = format!("↓ {}", format_bytes_speed(rate.down));
|
||||||
|
|
||||||
|
// For test rate display
|
||||||
|
// let down_text = format!("↓ {}", format_bytes_speed(102 * 1020 * 1024));
|
||||||
|
|
||||||
// 计算文本位置,确保垂直间距合适
|
// 计算文本位置,确保垂直间距合适
|
||||||
// 修改文本位置为居右显示
|
// 修改文本位置为居右显示
|
||||||
let up_text_width = imageproc::drawing::text_size(scale, &font, &up_text).0 as u32;
|
|
||||||
let down_text_width = imageproc::drawing::text_size(scale, &font, &down_text).0 as u32;
|
|
||||||
|
|
||||||
// 计算右对齐的文本位置
|
// 计算右对齐的文本位置
|
||||||
let up_text_x = total_width - up_text_width;
|
// let up_text_width = imageproc::drawing::text_size(scale, &font, &up_text).0 as u32;
|
||||||
let down_text_x = total_width - down_text_width;
|
// let down_text_width = imageproc::drawing::text_size(scale, &font, &down_text).0 as u32;
|
||||||
|
// let up_text_x = total_width - up_text_width;
|
||||||
|
// let down_text_x = total_width - down_text_width;
|
||||||
|
|
||||||
|
// 计算左对齐的文本位置
|
||||||
|
let (up_text_x, down_text_x) = {
|
||||||
|
if is_custom_icon || icon_bytes.is_some() {
|
||||||
|
let text_left_offset = 30;
|
||||||
|
let left_begin = icon_width + text_left_offset;
|
||||||
|
(left_begin, left_begin)
|
||||||
|
} else {
|
||||||
|
(icon_width, icon_width)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 优化垂直位置,使速率显示的高度和上下间距正好等于图标大小
|
// 优化垂直位置,使速率显示的高度和上下间距正好等于图标大小
|
||||||
let text_height = font_size as i32;
|
let text_height = font_size as i32;
|
||||||
|
@@ -110,7 +110,6 @@ pub fn run() {
|
|||||||
.plugin(tauri_plugin_clipboard_manager::init())
|
.plugin(tauri_plugin_clipboard_manager::init())
|
||||||
.plugin(tauri_plugin_process::init())
|
.plugin(tauri_plugin_process::init())
|
||||||
.plugin(tauri_plugin_global_shortcut::Builder::new().build())
|
.plugin(tauri_plugin_global_shortcut::Builder::new().build())
|
||||||
.plugin(tauri_plugin_notification::init())
|
|
||||||
.plugin(tauri_plugin_fs::init())
|
.plugin(tauri_plugin_fs::init())
|
||||||
.plugin(tauri_plugin_dialog::init())
|
.plugin(tauri_plugin_dialog::init())
|
||||||
.plugin(tauri_plugin_shell::init())
|
.plugin(tauri_plugin_shell::init())
|
||||||
|
@@ -77,9 +77,39 @@ pub fn app_profiles_dir() -> Result<PathBuf> {
|
|||||||
Ok(app_home_dir()?.join("profiles"))
|
Ok(app_home_dir()?.join("profiles"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// icons dir
|
||||||
|
pub fn app_icons_dir() -> Result<PathBuf> {
|
||||||
|
Ok(app_home_dir()?.join("icons"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find_target_icons(target: &str) -> Result<Option<String>> {
|
||||||
|
let icons_dir = app_icons_dir()?;
|
||||||
|
let mut matching_files = Vec::new();
|
||||||
|
|
||||||
|
for entry in fs::read_dir(icons_dir)? {
|
||||||
|
let entry = entry?;
|
||||||
|
let path = entry.path();
|
||||||
|
|
||||||
|
if let Some(file_name) = path.file_name().and_then(|n| n.to_str()) {
|
||||||
|
if file_name.starts_with(target)
|
||||||
|
&& (file_name.ends_with(".ico") || file_name.ends_with(".png"))
|
||||||
|
{
|
||||||
|
matching_files.push(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if matching_files.is_empty() {
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
let first = path_to_str(matching_files.first().unwrap())?;
|
||||||
|
Ok(Some(first.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// logs dir
|
/// logs dir
|
||||||
pub fn app_logs_dir() -> Result<PathBuf> {
|
pub fn app_logs_dir() -> Result<PathBuf> {
|
||||||
Ok(app_home_dir()?.join("logs"))
|
Ok(app_home_dir()?.join("icons"))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clash_path() -> Result<PathBuf> {
|
pub fn clash_path() -> Result<PathBuf> {
|
||||||
|
@@ -167,15 +167,16 @@ macro_rules! t {
|
|||||||
/// ```
|
/// ```
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub fn format_bytes_speed(speed: u64) -> String {
|
pub fn format_bytes_speed(speed: u64) -> String {
|
||||||
if speed < 1024 {
|
const UNITS: [&str; 4] = ["B", "KB", "MB", "GB"];
|
||||||
format!("{}B/s", speed)
|
let mut size = speed as f64;
|
||||||
} else if speed < 1024 * 1024 {
|
let mut unit_index = 0;
|
||||||
format!("{:.1}KB/s", speed as f64 / 1024.0)
|
|
||||||
} else if speed < 1024 * 1024 * 1024 {
|
while size >= 1000.0 && unit_index < UNITS.len() - 1 {
|
||||||
format!("{:.1}MB/s", speed as f64 / 1024.0 / 1024.0)
|
size /= 1024.0;
|
||||||
} else {
|
unit_index += 1;
|
||||||
format!("{:.1}GB/s", speed as f64 / 1024.0 / 1024.0 / 1024.0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
format!("{:.1}{}/s", size, UNITS[unit_index])
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
|
@@ -7,6 +7,7 @@ pub enum Type {
|
|||||||
Hotkey,
|
Hotkey,
|
||||||
Window,
|
Window,
|
||||||
Config,
|
Config,
|
||||||
|
CMD,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Type {
|
impl fmt::Display for Type {
|
||||||
@@ -17,6 +18,7 @@ impl fmt::Display for Type {
|
|||||||
Type::Hotkey => write!(f, "[Hotkey]"),
|
Type::Hotkey => write!(f, "[Hotkey]"),
|
||||||
Type::Window => write!(f, "[Window]"),
|
Type::Window => write!(f, "[Window]"),
|
||||||
Type::Config => write!(f, "[Config]"),
|
Type::Config => write!(f, "[Config]"),
|
||||||
|
Type::CMD => write!(f, "[CMD]"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -18,7 +18,6 @@ use tauri::{App, Manager};
|
|||||||
use tauri::Url;
|
use tauri::Url;
|
||||||
//#[cfg(not(target_os = "linux"))]
|
//#[cfg(not(target_os = "linux"))]
|
||||||
// use window_shadows::set_shadow;
|
// use window_shadows::set_shadow;
|
||||||
use tauri_plugin_notification::NotificationExt;
|
|
||||||
|
|
||||||
pub static VERSION: OnceCell<String> = OnceCell::new();
|
pub static VERSION: OnceCell<String> = OnceCell::new();
|
||||||
|
|
||||||
@@ -239,8 +238,6 @@ pub fn create_window() {
|
|||||||
pub async fn resolve_scheme(param: String) -> Result<()> {
|
pub async fn resolve_scheme(param: String) -> Result<()> {
|
||||||
log::info!(target:"app", "received deep link: {}", param);
|
log::info!(target:"app", "received deep link: {}", param);
|
||||||
|
|
||||||
let app_handle = handle::Handle::global().app_handle().unwrap();
|
|
||||||
|
|
||||||
let param_str = if param.starts_with("[") && param.len() > 4 {
|
let param_str = if param.starts_with("[") && param.len() > 4 {
|
||||||
param
|
param
|
||||||
.get(2..param.len() - 2)
|
.get(2..param.len() - 2)
|
||||||
@@ -280,24 +277,9 @@ pub async fn resolve_scheme(param: String) -> Result<()> {
|
|||||||
let uid = item.uid.clone().unwrap();
|
let uid = item.uid.clone().unwrap();
|
||||||
let _ = wrap_err!(Config::profiles().data().append_item(item));
|
let _ = wrap_err!(Config::profiles().data().append_item(item));
|
||||||
handle::Handle::notice_message("import_sub_url::ok", uid);
|
handle::Handle::notice_message("import_sub_url::ok", uid);
|
||||||
|
|
||||||
app_handle
|
|
||||||
.notification()
|
|
||||||
.builder()
|
|
||||||
.title("Clash Verge")
|
|
||||||
.body("Import profile success")
|
|
||||||
.show()
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
handle::Handle::notice_message("import_sub_url::error", e.to_string());
|
handle::Handle::notice_message("import_sub_url::error", e.to_string());
|
||||||
app_handle
|
|
||||||
.notification()
|
|
||||||
.builder()
|
|
||||||
.title("Clash Verge")
|
|
||||||
.body(format!("Import profile failed: {e}"))
|
|
||||||
.show()
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -39,69 +39,35 @@ export interface EnhancedTrafficGraphRef {
|
|||||||
// 时间范围类型
|
// 时间范围类型
|
||||||
type TimeRange = 1 | 5 | 10; // 分钟
|
type TimeRange = 1 | 5 | 10; // 分钟
|
||||||
|
|
||||||
// 创建一个明确的类型
|
// 数据点类型
|
||||||
type DataPoint = ITrafficItem & { name: string; timestamp: number };
|
type DataPoint = ITrafficItem & { name: string; timestamp: number };
|
||||||
|
|
||||||
// 控制帧率的工具函数
|
|
||||||
const FPS_LIMIT = 1; // 限制为1fps,因为数据每秒才更新一次
|
|
||||||
const FRAME_MIN_TIME = 1000 / FPS_LIMIT; // 每帧最小时间间隔,即1000ms
|
|
||||||
|
|
||||||
// 全局存储流量数据历史记录
|
|
||||||
declare global {
|
|
||||||
interface Window {
|
|
||||||
trafficHistoryData?: DataPoint[];
|
|
||||||
trafficHistoryStyle?: "line" | "area";
|
|
||||||
trafficHistoryTimeRange?: TimeRange;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化全局存储
|
|
||||||
if (typeof window !== "undefined" && !window.trafficHistoryData) {
|
|
||||||
window.trafficHistoryData = [];
|
|
||||||
window.trafficHistoryStyle = "area";
|
|
||||||
window.trafficHistoryTimeRange = 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 增强型流量图表组件
|
* 增强型流量图表组件
|
||||||
* 基于 Recharts 实现,支持线图和面积图两种模式
|
|
||||||
*/
|
*/
|
||||||
export const EnhancedTrafficGraph = memo(forwardRef<EnhancedTrafficGraphRef>(
|
export const EnhancedTrafficGraph = memo(forwardRef<EnhancedTrafficGraphRef>(
|
||||||
(props, ref) => {
|
(props, ref) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
// 从全局变量恢复状态
|
// 基础状态
|
||||||
const [timeRange, setTimeRange] = useState<TimeRange>(
|
const [timeRange, setTimeRange] = useState<TimeRange>(10);
|
||||||
window.trafficHistoryTimeRange || 10
|
const [chartStyle, setChartStyle] = useState<"line" | "area">("area");
|
||||||
);
|
|
||||||
const [chartStyle, setChartStyle] = useState<"line" | "area">(
|
|
||||||
window.trafficHistoryStyle || "area"
|
|
||||||
);
|
|
||||||
|
|
||||||
// 使用useRef存储数据,避免不必要的重渲染
|
|
||||||
const dataBufferRef = useRef<DataPoint[]>([]);
|
|
||||||
// 只为渲染目的的状态
|
|
||||||
const [displayData, setDisplayData] = useState<DataPoint[]>([]);
|
const [displayData, setDisplayData] = useState<DataPoint[]>([]);
|
||||||
|
|
||||||
// 帧率控制
|
// 数据缓冲区
|
||||||
const lastUpdateTimeRef = useRef<number>(0);
|
const dataBufferRef = useRef<DataPoint[]>([]);
|
||||||
const pendingUpdateRef = useRef<boolean>(false);
|
|
||||||
const rafIdRef = useRef<number | null>(null);
|
|
||||||
|
|
||||||
// 根据时间范围计算保留的数据点数量
|
// 根据时间范围计算保留的数据点数量
|
||||||
const getMaxPointsByTimeRange = useCallback(
|
const getMaxPointsByTimeRange = useCallback(
|
||||||
(minutes: TimeRange): number => {
|
(minutes: TimeRange): number => minutes * 30,
|
||||||
// 使用更低的采样率来减少点的数量,每2秒一个点而不是每秒一个点
|
[]
|
||||||
return minutes * 30; // 每分钟30个点(每2秒1个点)
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// 最大数据点数量 - 基于选择的时间范围
|
// 最大数据点数量
|
||||||
const MAX_BUFFER_SIZE = useMemo(
|
const MAX_BUFFER_SIZE = useMemo(
|
||||||
() => getMaxPointsByTimeRange(10),
|
() => getMaxPointsByTimeRange(10),
|
||||||
[getMaxPointsByTimeRange],
|
[getMaxPointsByTimeRange]
|
||||||
);
|
);
|
||||||
|
|
||||||
// 颜色配置
|
// 颜色配置
|
||||||
@@ -113,150 +79,51 @@ export const EnhancedTrafficGraph = memo(forwardRef<EnhancedTrafficGraphRef>(
|
|||||||
tooltip: theme.palette.background.paper,
|
tooltip: theme.palette.background.paper,
|
||||||
text: theme.palette.text.primary,
|
text: theme.palette.text.primary,
|
||||||
}),
|
}),
|
||||||
[theme],
|
[theme]
|
||||||
);
|
);
|
||||||
|
|
||||||
// 切换时间范围
|
// 切换时间范围
|
||||||
const handleTimeRangeClick = useCallback(() => {
|
const handleTimeRangeClick = useCallback(() => {
|
||||||
setTimeRange((prevRange) => {
|
setTimeRange((prevRange) => {
|
||||||
// 在1、5、10分钟之间循环切换
|
// 在1、5、10分钟之间循环切换
|
||||||
const newRange = prevRange === 1 ? 5 : prevRange === 5 ? 10 : 1;
|
return prevRange === 1 ? 5 : prevRange === 5 ? 10 : 1;
|
||||||
window.trafficHistoryTimeRange = newRange; // 保存到全局
|
|
||||||
return newRange;
|
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 初始化数据缓冲区
|
// 初始化数据缓冲区
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let initialBuffer: DataPoint[] = [];
|
// 创建初始空数据
|
||||||
|
const now = Date.now();
|
||||||
// 如果全局有保存的数据,优先使用
|
const tenMinutesAgo = now - 10 * 60 * 1000;
|
||||||
if (window.trafficHistoryData && window.trafficHistoryData.length > 0) {
|
|
||||||
initialBuffer = [...window.trafficHistoryData];
|
const initialBuffer = Array.from(
|
||||||
|
{ length: MAX_BUFFER_SIZE },
|
||||||
// 确保数据长度符合要求
|
(_, index) => {
|
||||||
if (initialBuffer.length > MAX_BUFFER_SIZE) {
|
const pointTime =
|
||||||
initialBuffer = initialBuffer.slice(-MAX_BUFFER_SIZE);
|
tenMinutesAgo + index * ((10 * 60 * 1000) / MAX_BUFFER_SIZE);
|
||||||
} else if (initialBuffer.length < MAX_BUFFER_SIZE) {
|
const date = new Date(pointTime);
|
||||||
// 如果历史数据不足,则在前面补充空数据
|
|
||||||
const now = Date.now();
|
return {
|
||||||
const oldestTimestamp = initialBuffer.length > 0
|
up: 0,
|
||||||
? initialBuffer[0].timestamp
|
down: 0,
|
||||||
: now - 10 * 60 * 1000;
|
timestamp: pointTime,
|
||||||
|
name: date.toLocaleTimeString("en-US", {
|
||||||
const additionalPoints = MAX_BUFFER_SIZE - initialBuffer.length;
|
hour12: false,
|
||||||
const timeInterval = initialBuffer.length > 0
|
hour: "2-digit",
|
||||||
? (initialBuffer[0].timestamp - (now - 10 * 60 * 1000)) / additionalPoints
|
minute: "2-digit",
|
||||||
: (10 * 60 * 1000) / MAX_BUFFER_SIZE;
|
second: "2-digit",
|
||||||
|
}),
|
||||||
const emptyPrefix: DataPoint[] = Array.from(
|
};
|
||||||
{ length: additionalPoints },
|
|
||||||
(_, index) => {
|
|
||||||
const pointTime = oldestTimestamp - (additionalPoints - index) * timeInterval;
|
|
||||||
const date = new Date(pointTime);
|
|
||||||
|
|
||||||
return {
|
|
||||||
up: 0,
|
|
||||||
down: 0,
|
|
||||||
timestamp: pointTime,
|
|
||||||
name: date.toLocaleTimeString("en-US", {
|
|
||||||
hour12: false,
|
|
||||||
hour: "2-digit",
|
|
||||||
minute: "2-digit",
|
|
||||||
second: "2-digit",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
initialBuffer = [...emptyPrefix, ...initialBuffer];
|
|
||||||
}
|
}
|
||||||
} else {
|
);
|
||||||
// 没有历史数据时,创建空的初始缓冲区
|
|
||||||
const now = Date.now();
|
|
||||||
const tenMinutesAgo = now - 10 * 60 * 1000;
|
|
||||||
|
|
||||||
initialBuffer = Array.from(
|
|
||||||
{ length: MAX_BUFFER_SIZE },
|
|
||||||
(_, index) => {
|
|
||||||
const pointTime =
|
|
||||||
tenMinutesAgo + index * ((10 * 60 * 1000) / MAX_BUFFER_SIZE);
|
|
||||||
const date = new Date(pointTime);
|
|
||||||
|
|
||||||
return {
|
|
||||||
up: 0,
|
|
||||||
down: 0,
|
|
||||||
timestamp: pointTime,
|
|
||||||
name: date.toLocaleTimeString("en-US", {
|
|
||||||
hour12: false,
|
|
||||||
hour: "2-digit",
|
|
||||||
minute: "2-digit",
|
|
||||||
second: "2-digit",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
dataBufferRef.current = initialBuffer;
|
dataBufferRef.current = initialBuffer;
|
||||||
window.trafficHistoryData = initialBuffer; // 保存到全局
|
|
||||||
|
|
||||||
// 更新显示数据
|
// 更新显示数据
|
||||||
const pointsToShow = getMaxPointsByTimeRange(timeRange);
|
const pointsToShow = getMaxPointsByTimeRange(timeRange);
|
||||||
setDisplayData(initialBuffer.slice(-pointsToShow));
|
setDisplayData(initialBuffer.slice(-pointsToShow));
|
||||||
|
|
||||||
// 清理函数,取消任何未完成的动画帧
|
|
||||||
return () => {
|
|
||||||
if (rafIdRef.current !== null) {
|
|
||||||
cancelAnimationFrame(rafIdRef.current);
|
|
||||||
rafIdRef.current = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, [MAX_BUFFER_SIZE, getMaxPointsByTimeRange, timeRange]);
|
}, [MAX_BUFFER_SIZE, getMaxPointsByTimeRange, timeRange]);
|
||||||
|
|
||||||
// 处理数据更新并控制帧率的函数
|
|
||||||
const updateDisplayData = useCallback(() => {
|
|
||||||
if (pendingUpdateRef.current) {
|
|
||||||
pendingUpdateRef.current = false;
|
|
||||||
|
|
||||||
// 根据当前时间范围计算需要显示的点数
|
|
||||||
const pointsToShow = getMaxPointsByTimeRange(timeRange);
|
|
||||||
// 从缓冲区中获取最新的数据点
|
|
||||||
const newDisplayData = dataBufferRef.current.slice(-pointsToShow);
|
|
||||||
setDisplayData(newDisplayData);
|
|
||||||
}
|
|
||||||
|
|
||||||
rafIdRef.current = null;
|
|
||||||
}, [timeRange, getMaxPointsByTimeRange]);
|
|
||||||
|
|
||||||
// 节流更新函数
|
|
||||||
const throttledUpdateData = useCallback(() => {
|
|
||||||
pendingUpdateRef.current = true;
|
|
||||||
|
|
||||||
const now = performance.now();
|
|
||||||
const timeSinceLastUpdate = now - lastUpdateTimeRef.current;
|
|
||||||
|
|
||||||
if (rafIdRef.current === null) {
|
|
||||||
if (timeSinceLastUpdate >= FRAME_MIN_TIME) {
|
|
||||||
// 如果距离上次更新已经超过最小帧时间,立即更新
|
|
||||||
lastUpdateTimeRef.current = now;
|
|
||||||
rafIdRef.current = requestAnimationFrame(updateDisplayData);
|
|
||||||
} else {
|
|
||||||
// 否则,在适当的时间进行更新
|
|
||||||
const timeToWait = FRAME_MIN_TIME - timeSinceLastUpdate;
|
|
||||||
setTimeout(() => {
|
|
||||||
lastUpdateTimeRef.current = performance.now();
|
|
||||||
rafIdRef.current = requestAnimationFrame(updateDisplayData);
|
|
||||||
}, timeToWait);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [updateDisplayData]);
|
|
||||||
|
|
||||||
// 监听时间范围变化,更新显示数据
|
|
||||||
useEffect(() => {
|
|
||||||
throttledUpdateData();
|
|
||||||
}, [timeRange, throttledUpdateData]);
|
|
||||||
|
|
||||||
// 添加数据点方法
|
// 添加数据点方法
|
||||||
const appendData = useCallback((data: ITrafficItem) => {
|
const appendData = useCallback((data: ITrafficItem) => {
|
||||||
// 安全处理数据
|
// 安全处理数据
|
||||||
@@ -281,24 +148,24 @@ export const EnhancedTrafficGraph = memo(forwardRef<EnhancedTrafficGraphRef>(
|
|||||||
timestamp: timestamp,
|
timestamp: timestamp,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 更新ref,但保持原数组大小
|
// 更新缓冲区,保持原数组大小
|
||||||
const newBuffer = [...dataBufferRef.current.slice(1), newPoint];
|
const newBuffer = [...dataBufferRef.current.slice(1), newPoint];
|
||||||
dataBufferRef.current = newBuffer;
|
dataBufferRef.current = newBuffer;
|
||||||
|
|
||||||
// 保存到全局变量
|
// 更新显示数据
|
||||||
window.trafficHistoryData = newBuffer;
|
const pointsToShow = getMaxPointsByTimeRange(timeRange);
|
||||||
|
setDisplayData(newBuffer.slice(-pointsToShow));
|
||||||
// 使用节流更新显示数据
|
}, [timeRange, getMaxPointsByTimeRange]);
|
||||||
throttledUpdateData();
|
|
||||||
}, [throttledUpdateData]);
|
// 监听时间范围变化,更新显示数据
|
||||||
|
useEffect(() => {
|
||||||
|
const pointsToShow = getMaxPointsByTimeRange(timeRange);
|
||||||
|
setDisplayData(dataBufferRef.current.slice(-pointsToShow));
|
||||||
|
}, [timeRange, getMaxPointsByTimeRange]);
|
||||||
|
|
||||||
// 切换图表样式
|
// 切换图表样式
|
||||||
const toggleStyle = useCallback(() => {
|
const toggleStyle = useCallback(() => {
|
||||||
setChartStyle((prev) => {
|
setChartStyle((prev) => prev === "line" ? "area" : "line");
|
||||||
const newStyle = prev === "line" ? "area" : "line";
|
|
||||||
window.trafficHistoryStyle = newStyle; // 保存到全局
|
|
||||||
return newStyle;
|
|
||||||
});
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 暴露方法给父组件
|
// 暴露方法给父组件
|
||||||
@@ -308,13 +175,12 @@ export const EnhancedTrafficGraph = memo(forwardRef<EnhancedTrafficGraphRef>(
|
|||||||
appendData,
|
appendData,
|
||||||
toggleStyle,
|
toggleStyle,
|
||||||
}),
|
}),
|
||||||
[appendData, toggleStyle],
|
[appendData, toggleStyle]
|
||||||
);
|
);
|
||||||
|
|
||||||
// 格式化工具提示内容
|
// 格式化工具提示内容
|
||||||
const formatTooltip = useCallback((value: number, name: string, props: any) => {
|
const formatTooltip = useCallback((value: number, name: string, props: any) => {
|
||||||
const [num, unit] = parseTraffic(value);
|
const [num, unit] = parseTraffic(value);
|
||||||
// 使用props.dataKey判断是上传还是下载
|
|
||||||
return [`${num} ${unit}/s`, props?.dataKey === "up" ? t("Upload") : t("Download")];
|
return [`${num} ${unit}/s`, props?.dataKey === "up" ? t("Upload") : t("Download")];
|
||||||
}, [t]);
|
}, [t]);
|
||||||
|
|
||||||
@@ -327,7 +193,6 @@ export const EnhancedTrafficGraph = memo(forwardRef<EnhancedTrafficGraphRef>(
|
|||||||
// 格式化X轴标签
|
// 格式化X轴标签
|
||||||
const formatXLabel = useCallback((value: string) => {
|
const formatXLabel = useCallback((value: string) => {
|
||||||
if (!value) return "";
|
if (!value) return "";
|
||||||
// 只显示小时和分钟
|
|
||||||
const parts = value.split(":");
|
const parts = value.split(":");
|
||||||
return `${parts[0]}:${parts[1]}`;
|
return `${parts[0]}:${parts[1]}`;
|
||||||
}, []);
|
}, []);
|
||||||
@@ -352,7 +217,7 @@ export const EnhancedTrafficGraph = memo(forwardRef<EnhancedTrafficGraphRef>(
|
|||||||
isAnimationActive: false, // 禁用动画以减少CPU使用
|
isAnimationActive: false, // 禁用动画以减少CPU使用
|
||||||
}), []);
|
}), []);
|
||||||
|
|
||||||
// 曲线类型 - 使用线性曲线避免错位
|
// 曲线类型
|
||||||
const curveType = "monotone";
|
const curveType = "monotone";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -386,7 +251,7 @@ export const EnhancedTrafficGraph = memo(forwardRef<EnhancedTrafficGraphRef>(
|
|||||||
tick={{ fontSize: 10, fill: colors.text }}
|
tick={{ fontSize: 10, fill: colors.text }}
|
||||||
tickLine={{ stroke: colors.grid }}
|
tickLine={{ stroke: colors.grid }}
|
||||||
axisLine={{ stroke: colors.grid }}
|
axisLine={{ stroke: colors.grid }}
|
||||||
width={40}
|
width={43}
|
||||||
domain={[0, "auto"]}
|
domain={[0, "auto"]}
|
||||||
/>
|
/>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
@@ -470,7 +335,7 @@ export const EnhancedTrafficGraph = memo(forwardRef<EnhancedTrafficGraphRef>(
|
|||||||
tick={{ fontSize: 10, fill: colors.text }}
|
tick={{ fontSize: 10, fill: colors.text }}
|
||||||
tickLine={{ stroke: colors.grid }}
|
tickLine={{ stroke: colors.grid }}
|
||||||
axisLine={{ stroke: colors.grid }}
|
axisLine={{ stroke: colors.grid }}
|
||||||
width={40}
|
width={43}
|
||||||
domain={[0, "auto"]}
|
domain={[0, "auto"]}
|
||||||
padding={{ top: 10, bottom: 0 }}
|
padding={{ top: 10, bottom: 0 }}
|
||||||
/>
|
/>
|
||||||
|
@@ -5,7 +5,6 @@ import {
|
|||||||
SettingsRounded,
|
SettingsRounded,
|
||||||
ShuffleRounded,
|
ShuffleRounded,
|
||||||
LanRounded,
|
LanRounded,
|
||||||
DnsRounded,
|
|
||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
import { DialogRef, Notice, Switch } from "@/components/base";
|
import { DialogRef, Notice, Switch } from "@/components/base";
|
||||||
import { useClash } from "@/hooks/use-clash";
|
import { useClash } from "@/hooks/use-clash";
|
||||||
@@ -49,7 +48,16 @@ const SettingClash = ({ onError }: Props) => {
|
|||||||
const { enable_random_port = false, verge_mixed_port } = verge ?? {};
|
const { enable_random_port = false, verge_mixed_port } = verge ?? {};
|
||||||
|
|
||||||
// 独立跟踪DNS设置开关状态
|
// 独立跟踪DNS设置开关状态
|
||||||
const [dnsSettingsEnabled, setDnsSettingsEnabled] = useState(false);
|
const [dnsSettingsEnabled, setDnsSettingsEnabled] = useState(() => {
|
||||||
|
// 尝试从localStorage获取之前保存的状态
|
||||||
|
const savedState = localStorage.getItem("dns_settings_enabled");
|
||||||
|
if (savedState !== null) {
|
||||||
|
return savedState === "true";
|
||||||
|
}
|
||||||
|
// 如果没有保存的状态,则从verge配置中获取
|
||||||
|
return verge?.enable_dns_settings ?? false;
|
||||||
|
});
|
||||||
|
|
||||||
const { addListener } = useListen();
|
const { addListener } = useListen();
|
||||||
|
|
||||||
const webRef = useRef<DialogRef>(null);
|
const webRef = useRef<DialogRef>(null);
|
||||||
@@ -59,12 +67,6 @@ const SettingClash = ({ onError }: Props) => {
|
|||||||
const networkRef = useRef<DialogRef>(null);
|
const networkRef = useRef<DialogRef>(null);
|
||||||
const dnsRef = useRef<DialogRef>(null);
|
const dnsRef = useRef<DialogRef>(null);
|
||||||
|
|
||||||
// 初始化时从verge配置中加载DNS设置开关状态
|
|
||||||
useEffect(() => {
|
|
||||||
const dnsSettingsState = verge?.enable_dns_settings ?? false;
|
|
||||||
setDnsSettingsEnabled(dnsSettingsState);
|
|
||||||
}, [verge]);
|
|
||||||
|
|
||||||
const onSwitchFormat = (_e: any, value: boolean) => value;
|
const onSwitchFormat = (_e: any, value: boolean) => value;
|
||||||
const onChangeData = (patch: Partial<IConfigData>) => {
|
const onChangeData = (patch: Partial<IConfigData>) => {
|
||||||
mutateClash((old) => ({ ...(old! || {}), ...patch }), false);
|
mutateClash((old) => ({ ...(old! || {}), ...patch }), false);
|
||||||
@@ -84,15 +86,21 @@ const SettingClash = ({ onError }: Props) => {
|
|||||||
// 实现DNS设置开关处理函数
|
// 实现DNS设置开关处理函数
|
||||||
const handleDnsToggle = useLockFn(async (enable: boolean) => {
|
const handleDnsToggle = useLockFn(async (enable: boolean) => {
|
||||||
try {
|
try {
|
||||||
|
// 立即更新UI状态
|
||||||
setDnsSettingsEnabled(enable);
|
setDnsSettingsEnabled(enable);
|
||||||
|
// 保存到localStorage,用于记住用户的选择
|
||||||
|
localStorage.setItem("dns_settings_enabled", String(enable));
|
||||||
|
// 更新verge配置
|
||||||
await patchVerge({ enable_dns_settings: enable });
|
await patchVerge({ enable_dns_settings: enable });
|
||||||
await invoke("apply_dns_config", { apply: enable });
|
await invoke("apply_dns_config", { apply: enable });
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
mutateClash();
|
mutateClash();
|
||||||
}, 500); // 延迟500ms确保后端完成处理
|
}, 500); // 延迟500ms确保后端完成处理
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Notice.error(err.message || err.toString());
|
// 如果出错,恢复原始状态
|
||||||
setDnsSettingsEnabled(!enable);
|
setDnsSettingsEnabled(!enable);
|
||||||
|
localStorage.setItem("dns_settings_enabled", String(!enable));
|
||||||
|
Notice.error(err.message || err.toString());
|
||||||
await patchVerge({ enable_dns_settings: !enable }).catch(() => {
|
await patchVerge({ enable_dns_settings: !enable }).catch(() => {
|
||||||
// 忽略恢复状态时的错误
|
// 忽略恢复状态时的错误
|
||||||
});
|
});
|
||||||
@@ -143,7 +151,6 @@ const SettingClash = ({ onError }: Props) => {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{/* 使用独立状态,不再依赖dns?.enable */}
|
|
||||||
<Switch
|
<Switch
|
||||||
edge="end"
|
edge="end"
|
||||||
checked={dnsSettingsEnabled}
|
checked={dnsSettingsEnabled}
|
||||||
|
@@ -43,6 +43,7 @@ const SettingSystem = ({ onError }: Props) => {
|
|||||||
const { data: autoLaunchEnabled } = useSWR(
|
const { data: autoLaunchEnabled } = useSWR(
|
||||||
"getAutoLaunchStatus",
|
"getAutoLaunchStatus",
|
||||||
getAutoLaunchStatus,
|
getAutoLaunchStatus,
|
||||||
|
{ revalidateOnFocus: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
// 当实际自启动状态与配置不同步时更新配置
|
// 当实际自启动状态与配置不同步时更新配置
|
||||||
|
@@ -36,11 +36,96 @@ export default defineConfig({
|
|||||||
entry: "monaco-yaml/yaml.worker",
|
entry: "monaco-yaml/yaml.worker",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
globalAPI: false,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
build: {
|
build: {
|
||||||
outDir: "../dist",
|
outDir: "../dist",
|
||||||
emptyOutDir: true,
|
emptyOutDir: true,
|
||||||
|
target: "esnext",
|
||||||
|
minify: "terser",
|
||||||
|
chunkSizeWarningLimit: 4000,
|
||||||
|
reportCompressedSize: false,
|
||||||
|
sourcemap: false,
|
||||||
|
cssCodeSplit: true,
|
||||||
|
cssMinify: true,
|
||||||
|
rollupOptions: {
|
||||||
|
treeshake: {
|
||||||
|
preset: "recommended",
|
||||||
|
moduleSideEffects: (id) => !/\.css$/.test(id),
|
||||||
|
tryCatchDeoptimization: false,
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
compact: true,
|
||||||
|
experimentalMinChunkSize: 30000,
|
||||||
|
dynamicImportInCjs: true,
|
||||||
|
manualChunks(id) {
|
||||||
|
if (id.includes("node_modules")) {
|
||||||
|
// Monaco Editor should be a separate chunk
|
||||||
|
if (id.includes("monaco-editor")) return "monaco-editor";
|
||||||
|
|
||||||
|
// React-related libraries (react, react-dom, react-router-dom, etc.)
|
||||||
|
if (
|
||||||
|
id.includes("react") ||
|
||||||
|
id.includes("react-dom") ||
|
||||||
|
id.includes("react-router-dom") ||
|
||||||
|
id.includes("react-transition-group") ||
|
||||||
|
id.includes("react-error-boundary") ||
|
||||||
|
id.includes("react-hook-form") ||
|
||||||
|
id.includes("react-markdown") ||
|
||||||
|
id.includes("react-virtuoso")
|
||||||
|
) {
|
||||||
|
return "react";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utilities chunk: group commonly used utility libraries
|
||||||
|
if (
|
||||||
|
id.includes("axios") ||
|
||||||
|
id.includes("lodash-es") ||
|
||||||
|
id.includes("dayjs") ||
|
||||||
|
id.includes("js-base64") ||
|
||||||
|
id.includes("js-yaml") ||
|
||||||
|
id.includes("cli-color") ||
|
||||||
|
id.includes("nanoid")
|
||||||
|
) {
|
||||||
|
return "utils";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tauri-related plugins: grouping together Tauri plugins
|
||||||
|
if (
|
||||||
|
id.includes("@tauri-apps/api") ||
|
||||||
|
id.includes("@tauri-apps/plugin-clipboard-manager") ||
|
||||||
|
id.includes("@tauri-apps/plugin-dialog") ||
|
||||||
|
id.includes("@tauri-apps/plugin-fs") ||
|
||||||
|
id.includes("@tauri-apps/plugin-global-shortcut") ||
|
||||||
|
id.includes("@tauri-apps/plugin-notification") ||
|
||||||
|
id.includes("@tauri-apps/plugin-process") ||
|
||||||
|
id.includes("@tauri-apps/plugin-shell") ||
|
||||||
|
id.includes("@tauri-apps/plugin-updater")
|
||||||
|
) {
|
||||||
|
return "tauri-plugins";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Material UI libraries (grouped together)
|
||||||
|
if (
|
||||||
|
id.includes("@mui/material") ||
|
||||||
|
id.includes("@mui/icons-material") ||
|
||||||
|
id.includes("@mui/lab") ||
|
||||||
|
id.includes("@mui/x-data-grid")
|
||||||
|
) {
|
||||||
|
return "mui";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Small vendor packages
|
||||||
|
const pkg = id.match(/node_modules\/([^\/]+)/)?.[1];
|
||||||
|
if (pkg && pkg.length < 8) return "small-vendors";
|
||||||
|
|
||||||
|
// Large vendor packages
|
||||||
|
return "large-vendor";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
@@ -23,14 +23,14 @@ Signed-off-by: Davide Fioravanti <pantanastyle@gmail.com>
|
|||||||
obj-$(CONFIG_MTD_SPI_NAND) += spinand.o
|
obj-$(CONFIG_MTD_SPI_NAND) += spinand.o
|
||||||
--- a/drivers/mtd/nand/spi/core.c
|
--- a/drivers/mtd/nand/spi/core.c
|
||||||
+++ b/drivers/mtd/nand/spi/core.c
|
+++ b/drivers/mtd/nand/spi/core.c
|
||||||
@@ -940,6 +940,7 @@ static const struct nand_ops spinand_ops
|
@@ -941,6 +941,7 @@ static const struct spinand_manufacturer
|
||||||
static const struct spinand_manufacturer *spinand_manufacturers[] = {
|
|
||||||
&ato_spinand_manufacturer,
|
&ato_spinand_manufacturer,
|
||||||
&esmt_c8_spinand_manufacturer,
|
&esmt_c8_spinand_manufacturer,
|
||||||
+ &fidelix_spinand_manufacturer,
|
|
||||||
&etron_spinand_manufacturer,
|
&etron_spinand_manufacturer,
|
||||||
|
+ &fidelix_spinand_manufacturer,
|
||||||
&gigadevice_spinand_manufacturer,
|
&gigadevice_spinand_manufacturer,
|
||||||
¯onix_spinand_manufacturer,
|
¯onix_spinand_manufacturer,
|
||||||
|
µn_spinand_manufacturer,
|
||||||
--- /dev/null
|
--- /dev/null
|
||||||
+++ b/drivers/mtd/nand/spi/fidelix.c
|
+++ b/drivers/mtd/nand/spi/fidelix.c
|
||||||
@@ -0,0 +1,76 @@
|
@@ -0,0 +1,76 @@
|
||||||
@@ -56,8 +56,8 @@ Signed-off-by: Davide Fioravanti <pantanastyle@gmail.com>
|
|||||||
+ SPINAND_PROG_LOAD(true, 0, NULL, 0));
|
+ SPINAND_PROG_LOAD(true, 0, NULL, 0));
|
||||||
+
|
+
|
||||||
+static SPINAND_OP_VARIANTS(update_cache_variants,
|
+static SPINAND_OP_VARIANTS(update_cache_variants,
|
||||||
+ SPINAND_PROG_LOAD_X4(false, 0, NULL, 0),
|
+ SPINAND_PROG_LOAD_X4(true, 0, NULL, 0),
|
||||||
+ SPINAND_PROG_LOAD(false, 0, NULL, 0));
|
+ SPINAND_PROG_LOAD(true, 0, NULL, 0));
|
||||||
+
|
+
|
||||||
+static int fm35x1ga_ooblayout_ecc(struct mtd_info *mtd, int section,
|
+static int fm35x1ga_ooblayout_ecc(struct mtd_info *mtd, int section,
|
||||||
+ struct mtd_oob_region *region)
|
+ struct mtd_oob_region *region)
|
||||||
|
@@ -0,0 +1,133 @@
|
|||||||
|
--- a/drivers/mtd/nand/spi/Makefile
|
||||||
|
+++ b/drivers/mtd/nand/spi/Makefile
|
||||||
|
@@ -1,3 +1,3 @@
|
||||||
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
-spinand-objs := core.o ato.o esmt.o etron.o fidelix.o gigadevice.o macronix.o micron.o paragon.o toshiba.o winbond.o xtx.o
|
||||||
|
+spinand-objs := core.o ato.o esmt.o etron.o fidelix.o fudanmicro.o gigadevice.o macronix.o micron.o paragon.o toshiba.o winbond.o xtx.o
|
||||||
|
obj-$(CONFIG_MTD_SPI_NAND) += spinand.o
|
||||||
|
--- a/drivers/mtd/nand/spi/core.c
|
||||||
|
+++ b/drivers/mtd/nand/spi/core.c
|
||||||
|
@@ -942,6 +942,7 @@ static const struct spinand_manufacturer
|
||||||
|
&esmt_c8_spinand_manufacturer,
|
||||||
|
&etron_spinand_manufacturer,
|
||||||
|
&fidelix_spinand_manufacturer,
|
||||||
|
+ &fudan_spinand_manufacturer,
|
||||||
|
&gigadevice_spinand_manufacturer,
|
||||||
|
¯onix_spinand_manufacturer,
|
||||||
|
µn_spinand_manufacturer,
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/drivers/mtd/nand/spi/fudanmicro.c
|
||||||
|
@@ -0,0 +1,103 @@
|
||||||
|
+#include <linux/device.h>
|
||||||
|
+#include <linux/kernel.h>
|
||||||
|
+#include <linux/mtd/spinand.h>
|
||||||
|
+
|
||||||
|
+#define SPINAND_MFR_FUDAN 0xA1
|
||||||
|
+
|
||||||
|
+#define FM25S01B_STATUS_ECC_MASK (7 << 4)
|
||||||
|
+#define STATUS_ECC_1_3_BITFLIPS (1 << 4)
|
||||||
|
+#define STATUS_ECC_4_6_BITFLIPS (3 << 4)
|
||||||
|
+#define STATUS_ECC_7_8_BITFLIPS (5 << 4)
|
||||||
|
+
|
||||||
|
+static SPINAND_OP_VARIANTS(read_cache_variants,
|
||||||
|
+ SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0),
|
||||||
|
+ SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0),
|
||||||
|
+ SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0),
|
||||||
|
+ SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0));
|
||||||
|
+
|
||||||
|
+static SPINAND_OP_VARIANTS(write_cache_variants,
|
||||||
|
+ SPINAND_PROG_LOAD_X4(true, 0, NULL, 0),
|
||||||
|
+ SPINAND_PROG_LOAD(true, 0, NULL, 0));
|
||||||
|
+
|
||||||
|
+static SPINAND_OP_VARIANTS(update_cache_variants,
|
||||||
|
+ SPINAND_PROG_LOAD_X4(false, 0, NULL, 0),
|
||||||
|
+ SPINAND_PROG_LOAD(false, 0, NULL, 0));
|
||||||
|
+
|
||||||
|
+static int fm25s01b_ooblayout_ecc(struct mtd_info *mtd, int section,
|
||||||
|
+ struct mtd_oob_region *region)
|
||||||
|
+{
|
||||||
|
+ if (section)
|
||||||
|
+ return -ERANGE;
|
||||||
|
+
|
||||||
|
+ region->offset = 64;
|
||||||
|
+ region->length = 64;
|
||||||
|
+
|
||||||
|
+ return 0;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static int fm25s01b_ooblayout_free(struct mtd_info *mtd, int section,
|
||||||
|
+ struct mtd_oob_region *region)
|
||||||
|
+{
|
||||||
|
+ if (section > 3)
|
||||||
|
+ return -ERANGE;
|
||||||
|
+
|
||||||
|
+ region->offset = (16 * section) + 4;
|
||||||
|
+ region->length = 12;
|
||||||
|
+
|
||||||
|
+ return 0;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static const struct mtd_ooblayout_ops fm25s01b_ooblayout = {
|
||||||
|
+ .ecc = fm25s01b_ooblayout_ecc,
|
||||||
|
+ .free = fm25s01b_ooblayout_free,
|
||||||
|
+};
|
||||||
|
+
|
||||||
|
+static int fm25s01b_ecc_get_status(struct spinand_device *spinand,
|
||||||
|
+ u8 status)
|
||||||
|
+{
|
||||||
|
+ switch (status & FM25S01B_STATUS_ECC_MASK) {
|
||||||
|
+ case STATUS_ECC_NO_BITFLIPS:
|
||||||
|
+ return 0;
|
||||||
|
+
|
||||||
|
+ case STATUS_ECC_UNCOR_ERROR:
|
||||||
|
+ return -EBADMSG;
|
||||||
|
+
|
||||||
|
+ case STATUS_ECC_1_3_BITFLIPS:
|
||||||
|
+ return 3;
|
||||||
|
+
|
||||||
|
+ case STATUS_ECC_4_6_BITFLIPS:
|
||||||
|
+ return 6;
|
||||||
|
+
|
||||||
|
+ case STATUS_ECC_7_8_BITFLIPS:
|
||||||
|
+ return 8;
|
||||||
|
+
|
||||||
|
+ default:
|
||||||
|
+ break;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ return -EINVAL;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static const struct spinand_info fudan_spinand_table[] = {
|
||||||
|
+ SPINAND_INFO("FM25S01B",
|
||||||
|
+ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xD4),
|
||||||
|
+ NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1),
|
||||||
|
+ NAND_ECCREQ(8, 512),
|
||||||
|
+ SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||||
|
+ &write_cache_variants,
|
||||||
|
+ &update_cache_variants),
|
||||||
|
+ SPINAND_HAS_QE_BIT,
|
||||||
|
+ SPINAND_ECCINFO(&fm25s01b_ooblayout,
|
||||||
|
+ fm25s01b_ecc_get_status)),
|
||||||
|
+};
|
||||||
|
+
|
||||||
|
+static const struct spinand_manufacturer_ops fudan_spinand_manuf_ops = {
|
||||||
|
+};
|
||||||
|
+
|
||||||
|
+const struct spinand_manufacturer fudan_spinand_manufacturer = {
|
||||||
|
+ .id = SPINAND_MFR_FUDAN,
|
||||||
|
+ .name = "FUDAN Micron",
|
||||||
|
+ .chips = fudan_spinand_table,
|
||||||
|
+ .nchips = ARRAY_SIZE(fudan_spinand_table),
|
||||||
|
+ .ops = &fudan_spinand_manuf_ops,
|
||||||
|
+};
|
||||||
|
--- a/include/linux/mtd/spinand.h
|
||||||
|
+++ b/include/linux/mtd/spinand.h
|
||||||
|
@@ -264,6 +264,7 @@ struct spinand_manufacturer {
|
||||||
|
extern const struct spinand_manufacturer esmt_c8_spinand_manufacturer;
|
||||||
|
extern const struct spinand_manufacturer etron_spinand_manufacturer;
|
||||||
|
extern const struct spinand_manufacturer fidelix_spinand_manufacturer;
|
||||||
|
+extern const struct spinand_manufacturer fudan_spinand_manufacturer;
|
||||||
|
extern const struct spinand_manufacturer gigadevice_spinand_manufacturer;
|
||||||
|
extern const struct spinand_manufacturer macronix_spinand_manufacturer;
|
||||||
|
extern const struct spinand_manufacturer micron_spinand_manufacturer;
|
@@ -174,6 +174,12 @@ func (l *Listener) HandleConn(conn net.Conn, h *sing.ListenerHandler) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// It seems that mihomo does not implement a connection error reporting mechanism, so we report success directly.
|
||||||
|
err = stream.HandshakeSuccess()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
h.NewConnection(ctx, stream, M.Metadata{
|
h.NewConnection(ctx, stream, M.Metadata{
|
||||||
Source: M.SocksaddrFromNet(conn.RemoteAddr()),
|
Source: M.SocksaddrFromNet(conn.RemoteAddr()),
|
||||||
Destination: destination,
|
Destination: destination,
|
||||||
|
@@ -15,10 +15,10 @@ import (
|
|||||||
const CheckMark = -1
|
const CheckMark = -1
|
||||||
|
|
||||||
var DefaultPaddingScheme = []byte(`stop=8
|
var DefaultPaddingScheme = []byte(`stop=8
|
||||||
0=34-120
|
0=30-30
|
||||||
1=100-400
|
1=100-400
|
||||||
2=400-500,c,500-1000,c,400-500,c,500-1000,c,500-1000,c,400-500
|
2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000
|
||||||
3=500-1000
|
3=9-9,500-1000
|
||||||
4=500-1000
|
4=500-1000
|
||||||
5=500-1000
|
5=500-1000
|
||||||
6=500-1000
|
6=500-1000
|
||||||
|
@@ -83,11 +83,7 @@ func (c *Client) CreateStream(ctx context.Context) (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
stream.dieHook = func() {
|
stream.dieHook = func() {
|
||||||
if session.IsClosed() {
|
if !session.IsClosed() {
|
||||||
if session.dieHook != nil {
|
|
||||||
session.dieHook()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
select {
|
select {
|
||||||
case <-c.die.Done():
|
case <-c.die.Done():
|
||||||
// Now client has been closed
|
// Now client has been closed
|
||||||
@@ -154,10 +150,10 @@ func (c *Client) Close() error {
|
|||||||
|
|
||||||
c.sessionsLock.Lock()
|
c.sessionsLock.Lock()
|
||||||
sessionToClose := make([]*Session, 0, len(c.sessions))
|
sessionToClose := make([]*Session, 0, len(c.sessions))
|
||||||
for seq, session := range c.sessions {
|
for _, session := range c.sessions {
|
||||||
sessionToClose = append(sessionToClose, session)
|
sessionToClose = append(sessionToClose, session)
|
||||||
delete(c.sessions, seq)
|
|
||||||
}
|
}
|
||||||
|
c.sessions = make(map[uint64]*Session)
|
||||||
c.sessionsLock.Unlock()
|
c.sessionsLock.Unlock()
|
||||||
|
|
||||||
for _, session := range sessionToClose {
|
for _, session := range sessionToClose {
|
||||||
|
@@ -9,9 +9,14 @@ const ( // cmds
|
|||||||
cmdSYN = 1 // stream open
|
cmdSYN = 1 // stream open
|
||||||
cmdPSH = 2 // data push
|
cmdPSH = 2 // data push
|
||||||
cmdFIN = 3 // stream close, a.k.a EOF mark
|
cmdFIN = 3 // stream close, a.k.a EOF mark
|
||||||
cmdSettings = 4 // Settings
|
cmdSettings = 4 // Settings (Client send to Server)
|
||||||
cmdAlert = 5 // Alert
|
cmdAlert = 5 // Alert
|
||||||
cmdUpdatePaddingScheme = 6 // update padding scheme
|
cmdUpdatePaddingScheme = 6 // update padding scheme
|
||||||
|
// Since version 2
|
||||||
|
cmdSYNACK = 7 // Server reports to the client that the stream has been opened
|
||||||
|
cmdHeartRequest = 8 // Keep alive command
|
||||||
|
cmdHeartResponse = 9 // Keep alive command
|
||||||
|
cmdServerSettings = 10 // Settings (Server send to client)
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@@ -3,9 +3,11 @@ package session
|
|||||||
import (
|
import (
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -30,11 +32,16 @@ type Session struct {
|
|||||||
die chan struct{}
|
die chan struct{}
|
||||||
dieHook func()
|
dieHook func()
|
||||||
|
|
||||||
|
synDone func()
|
||||||
|
synDoneLock sync.Mutex
|
||||||
|
|
||||||
// pool
|
// pool
|
||||||
seq uint64
|
seq uint64
|
||||||
idleSince time.Time
|
idleSince time.Time
|
||||||
padding *atomic.TypedValue[*padding.PaddingFactory]
|
padding *atomic.TypedValue[*padding.PaddingFactory]
|
||||||
|
|
||||||
|
peerVersion byte
|
||||||
|
|
||||||
// client
|
// client
|
||||||
isClient bool
|
isClient bool
|
||||||
sendPadding bool
|
sendPadding bool
|
||||||
@@ -76,7 +83,7 @@ func (s *Session) Run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
settings := util.StringMap{
|
settings := util.StringMap{
|
||||||
"v": "1",
|
"v": "2",
|
||||||
"client": "mihomo/" + constant.Version,
|
"client": "mihomo/" + constant.Version,
|
||||||
"padding-md5": s.padding.Load().Md5,
|
"padding-md5": s.padding.Load().Md5,
|
||||||
}
|
}
|
||||||
@@ -105,15 +112,16 @@ func (s *Session) Close() error {
|
|||||||
close(s.die)
|
close(s.die)
|
||||||
once = true
|
once = true
|
||||||
})
|
})
|
||||||
|
|
||||||
if once {
|
if once {
|
||||||
if s.dieHook != nil {
|
if s.dieHook != nil {
|
||||||
s.dieHook()
|
s.dieHook()
|
||||||
|
s.dieHook = nil
|
||||||
}
|
}
|
||||||
s.streamLock.Lock()
|
s.streamLock.Lock()
|
||||||
for k := range s.streams {
|
for _, stream := range s.streams {
|
||||||
s.streams[k].sessionClose()
|
stream.Close()
|
||||||
}
|
}
|
||||||
|
s.streams = make(map[uint32]*Stream)
|
||||||
s.streamLock.Unlock()
|
s.streamLock.Unlock()
|
||||||
return s.conn.Close()
|
return s.conn.Close()
|
||||||
} else {
|
} else {
|
||||||
@@ -132,6 +140,17 @@ func (s *Session) OpenStream() (*Stream, error) {
|
|||||||
|
|
||||||
//logrus.Debugln("stream open", sid, s.streams)
|
//logrus.Debugln("stream open", sid, s.streams)
|
||||||
|
|
||||||
|
if sid >= 2 && s.peerVersion >= 2 {
|
||||||
|
s.synDoneLock.Lock()
|
||||||
|
if s.synDone != nil {
|
||||||
|
s.synDone()
|
||||||
|
}
|
||||||
|
s.synDone = util.NewDeadlineWatcher(time.Second*3, func() {
|
||||||
|
s.Close()
|
||||||
|
})
|
||||||
|
s.synDoneLock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := s.writeFrame(newFrame(cmdSYN, sid)); err != nil {
|
if _, err := s.writeFrame(newFrame(cmdSYN, sid)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -195,13 +214,37 @@ func (s *Session) recvLoop() error {
|
|||||||
if _, ok := s.streams[sid]; !ok {
|
if _, ok := s.streams[sid]; !ok {
|
||||||
stream := newStream(sid, s)
|
stream := newStream(sid, s)
|
||||||
s.streams[sid] = stream
|
s.streams[sid] = stream
|
||||||
if s.onNewStream != nil {
|
go func() {
|
||||||
go s.onNewStream(stream)
|
if s.onNewStream != nil {
|
||||||
} else {
|
s.onNewStream(stream)
|
||||||
go s.Close()
|
} else {
|
||||||
}
|
stream.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
s.streamLock.Unlock()
|
s.streamLock.Unlock()
|
||||||
|
case cmdSYNACK: // should be client only
|
||||||
|
s.synDoneLock.Lock()
|
||||||
|
if s.synDone != nil {
|
||||||
|
s.synDone()
|
||||||
|
s.synDone = nil
|
||||||
|
}
|
||||||
|
s.synDoneLock.Unlock()
|
||||||
|
if hdr.Length() > 0 {
|
||||||
|
buffer := pool.Get(int(hdr.Length()))
|
||||||
|
if _, err := io.ReadFull(s.conn, buffer); err != nil {
|
||||||
|
pool.Put(buffer)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// report error
|
||||||
|
s.streamLock.RLock()
|
||||||
|
stream, ok := s.streams[sid]
|
||||||
|
s.streamLock.RUnlock()
|
||||||
|
if ok {
|
||||||
|
stream.CloseWithError(fmt.Errorf("remote: %s", string(buffer)))
|
||||||
|
}
|
||||||
|
pool.Put(buffer)
|
||||||
|
}
|
||||||
case cmdFIN:
|
case cmdFIN:
|
||||||
s.streamLock.RLock()
|
s.streamLock.RLock()
|
||||||
stream, ok := s.streams[sid]
|
stream, ok := s.streams[sid]
|
||||||
@@ -240,6 +283,20 @@ func (s *Session) recvLoop() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// check client's version
|
||||||
|
if v, err := strconv.Atoi(m["v"]); err == nil && v >= 2 {
|
||||||
|
s.peerVersion = byte(v)
|
||||||
|
// send cmdServerSettings
|
||||||
|
f := newFrame(cmdServerSettings, 0)
|
||||||
|
f.data = util.StringMap{
|
||||||
|
"v": "2",
|
||||||
|
}.ToBytes()
|
||||||
|
_, err = s.writeFrame(f)
|
||||||
|
if err != nil {
|
||||||
|
pool.Put(buffer)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pool.Put(buffer)
|
pool.Put(buffer)
|
||||||
}
|
}
|
||||||
@@ -265,12 +322,35 @@ func (s *Session) recvLoop() error {
|
|||||||
}
|
}
|
||||||
if s.isClient {
|
if s.isClient {
|
||||||
if padding.UpdatePaddingScheme(rawScheme, s.padding) {
|
if padding.UpdatePaddingScheme(rawScheme, s.padding) {
|
||||||
log.Infoln("[Update padding succeed] %x\n", md5.Sum(rawScheme))
|
log.Debugln("[Update padding succeed] %x\n", md5.Sum(rawScheme))
|
||||||
} else {
|
} else {
|
||||||
log.Warnln("[Update padding failed] %x\n", md5.Sum(rawScheme))
|
log.Warnln("[Update padding failed] %x\n", md5.Sum(rawScheme))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case cmdHeartRequest:
|
||||||
|
if _, err := s.writeFrame(newFrame(cmdHeartResponse, sid)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case cmdHeartResponse:
|
||||||
|
// Active keepalive checking is not implemented yet
|
||||||
|
break
|
||||||
|
case cmdServerSettings:
|
||||||
|
if hdr.Length() > 0 {
|
||||||
|
buffer := pool.Get(int(hdr.Length()))
|
||||||
|
if _, err := io.ReadFull(s.conn, buffer); err != nil {
|
||||||
|
pool.Put(buffer)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if s.isClient {
|
||||||
|
// check server's version
|
||||||
|
m := util.StringMapFromBytes(buffer)
|
||||||
|
if v, err := strconv.Atoi(m["v"]); err == nil {
|
||||||
|
s.peerVersion = byte(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pool.Put(buffer)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
// I don't know what command it is (can't have data)
|
// I don't know what command it is (can't have data)
|
||||||
}
|
}
|
||||||
@@ -280,8 +360,10 @@ func (s *Session) recvLoop() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// notify the session that a stream has closed
|
|
||||||
func (s *Session) streamClosed(sid uint32) error {
|
func (s *Session) streamClosed(sid uint32) error {
|
||||||
|
if s.IsClosed() {
|
||||||
|
return io.ErrClosedPipe
|
||||||
|
}
|
||||||
_, err := s.writeFrame(newFrame(cmdFIN, sid))
|
_, err := s.writeFrame(newFrame(cmdFIN, sid))
|
||||||
s.streamLock.Lock()
|
s.streamLock.Lock()
|
||||||
delete(s.streams, sid)
|
delete(s.streams, sid)
|
||||||
|
@@ -22,6 +22,9 @@ type Stream struct {
|
|||||||
|
|
||||||
dieOnce sync.Once
|
dieOnce sync.Once
|
||||||
dieHook func()
|
dieHook func()
|
||||||
|
dieErr error
|
||||||
|
|
||||||
|
reportOnce sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
// newStream initiates a Stream struct
|
// newStream initiates a Stream struct
|
||||||
@@ -36,7 +39,11 @@ func newStream(id uint32, sess *Session) *Stream {
|
|||||||
|
|
||||||
// Read implements net.Conn
|
// Read implements net.Conn
|
||||||
func (s *Stream) Read(b []byte) (n int, err error) {
|
func (s *Stream) Read(b []byte) (n int, err error) {
|
||||||
return s.pipeR.Read(b)
|
n, err = s.pipeR.Read(b)
|
||||||
|
if s.dieErr != nil {
|
||||||
|
err = s.dieErr
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write implements net.Conn
|
// Write implements net.Conn
|
||||||
@@ -54,25 +61,28 @@ func (s *Stream) Write(b []byte) (n int, err error) {
|
|||||||
|
|
||||||
// Close implements net.Conn
|
// Close implements net.Conn
|
||||||
func (s *Stream) Close() error {
|
func (s *Stream) Close() error {
|
||||||
if s.sessionClose() {
|
return s.CloseWithError(io.ErrClosedPipe)
|
||||||
// notify remote
|
|
||||||
return s.sess.streamClosed(s.id)
|
|
||||||
} else {
|
|
||||||
return io.ErrClosedPipe
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// sessionClose close stream from session side, do not notify remote
|
func (s *Stream) CloseWithError(err error) error {
|
||||||
func (s *Stream) sessionClose() (once bool) {
|
// if err != io.ErrClosedPipe {
|
||||||
|
// logrus.Debugln(err)
|
||||||
|
// }
|
||||||
|
var once bool
|
||||||
s.dieOnce.Do(func() {
|
s.dieOnce.Do(func() {
|
||||||
|
s.dieErr = err
|
||||||
s.pipeR.Close()
|
s.pipeR.Close()
|
||||||
once = true
|
once = true
|
||||||
|
})
|
||||||
|
if once {
|
||||||
if s.dieHook != nil {
|
if s.dieHook != nil {
|
||||||
s.dieHook()
|
s.dieHook()
|
||||||
s.dieHook = nil
|
s.dieHook = nil
|
||||||
}
|
}
|
||||||
})
|
return s.sess.streamClosed(s.id)
|
||||||
return
|
} else {
|
||||||
|
return s.dieErr
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Stream) SetReadDeadline(t time.Time) error {
|
func (s *Stream) SetReadDeadline(t time.Time) error {
|
||||||
@@ -108,3 +118,33 @@ func (s *Stream) RemoteAddr() net.Addr {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HandshakeFailure should be called when Server fail to create outbound proxy
|
||||||
|
func (s *Stream) HandshakeFailure(err error) error {
|
||||||
|
var once bool
|
||||||
|
s.reportOnce.Do(func() {
|
||||||
|
once = true
|
||||||
|
})
|
||||||
|
if once && err != nil && s.sess.peerVersion >= 2 {
|
||||||
|
f := newFrame(cmdSYNACK, s.id)
|
||||||
|
f.data = []byte(err.Error())
|
||||||
|
if _, err := s.sess.writeFrame(f); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandshakeSuccess should be called when Server success to create outbound proxy
|
||||||
|
func (s *Stream) HandshakeSuccess() error {
|
||||||
|
var once bool
|
||||||
|
s.reportOnce.Do(func() {
|
||||||
|
once = true
|
||||||
|
})
|
||||||
|
if once && s.sess.peerVersion >= 2 {
|
||||||
|
if _, err := s.sess.writeFrame(newFrame(cmdSYNACK, s.id)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
25
mihomo/transport/anytls/util/deadline.go
Normal file
25
mihomo/transport/anytls/util/deadline.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewDeadlineWatcher(ddl time.Duration, timeOut func()) (done func()) {
|
||||||
|
t := time.NewTimer(ddl)
|
||||||
|
closeCh := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
defer t.Stop()
|
||||||
|
select {
|
||||||
|
case <-closeCh:
|
||||||
|
case <-t.C:
|
||||||
|
timeOut()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
var once sync.Once
|
||||||
|
return func() {
|
||||||
|
once.Do(func() {
|
||||||
|
close(closeCh)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@@ -7,13 +7,13 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=alist
|
PKG_NAME:=alist
|
||||||
PKG_VERSION:=3.43.0
|
PKG_VERSION:=3.44.0
|
||||||
PKG_WEB_VERSION:=3.43.0
|
PKG_WEB_VERSION:=3.44.0
|
||||||
PKG_RELEASE:=1
|
PKG_RELEASE:=1
|
||||||
|
|
||||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||||
PKG_SOURCE_URL:=https://codeload.github.com/AlistGo/alist/tar.gz/v$(PKG_VERSION)?
|
PKG_SOURCE_URL:=https://codeload.github.com/AlistGo/alist/tar.gz/v$(PKG_VERSION)?
|
||||||
PKG_HASH:=803e3568d055053cbd345834237308708872cd6e154df1da7c00bc46803e4a8a
|
PKG_HASH:=8511e147e912933ba05c31c28f25c4245cc3529f854b0471ba947e33c09f297d
|
||||||
|
|
||||||
PKG_LICENSE:=GPL-3.0
|
PKG_LICENSE:=GPL-3.0
|
||||||
PKG_LICENSE_FILE:=LICENSE
|
PKG_LICENSE_FILE:=LICENSE
|
||||||
@@ -23,7 +23,7 @@ define Download/$(PKG_NAME)-web
|
|||||||
FILE:=$(PKG_NAME)-web-$(PKG_WEB_VERSION).tar.gz
|
FILE:=$(PKG_NAME)-web-$(PKG_WEB_VERSION).tar.gz
|
||||||
URL_FILE:=dist.tar.gz
|
URL_FILE:=dist.tar.gz
|
||||||
URL:=https://github.com/AlistGo/alist-web/releases/download/$(PKG_WEB_VERSION)/
|
URL:=https://github.com/AlistGo/alist-web/releases/download/$(PKG_WEB_VERSION)/
|
||||||
HASH:=22d1fcbd10af2257029c65add00a589201fabf9509ea57494a6d088b76a4e8a2
|
HASH:=3709bec59bbc14f0f9f74193cebbb25a317c978fa3a5ae06a900eb341e1b5ae7
|
||||||
endef
|
endef
|
||||||
|
|
||||||
PKG_BUILD_DEPENDS:=golang/host
|
PKG_BUILD_DEPENDS:=golang/host
|
||||||
|
@@ -11,7 +11,7 @@ end
|
|||||||
|
|
||||||
local fs = api.fs
|
local fs = api.fs
|
||||||
local sys = api.sys
|
local sys = api.sys
|
||||||
local has_singbox = api.finded_com("singbox")
|
local has_singbox = api.finded_com("sing-box")
|
||||||
local has_xray = api.finded_com("xray")
|
local has_xray = api.finded_com("xray")
|
||||||
local has_geoview = api.is_finded("geoview")
|
local has_geoview = api.is_finded("geoview")
|
||||||
local has_gfwlist = fs.access("/usr/share/passwall/rules/gfwlist")
|
local has_gfwlist = fs.access("/usr/share/passwall/rules/gfwlist")
|
||||||
|
@@ -2,7 +2,7 @@ local api = require "luci.passwall.api"
|
|||||||
local appname = "passwall"
|
local appname = "passwall"
|
||||||
local datatypes = api.datatypes
|
local datatypes = api.datatypes
|
||||||
local fs = api.fs
|
local fs = api.fs
|
||||||
local has_singbox = api.finded_com("singbox")
|
local has_singbox = api.finded_com("sing-box")
|
||||||
local has_xray = api.finded_com("xray")
|
local has_xray = api.finded_com("xray")
|
||||||
local has_geoview = api.is_finded("geoview")
|
local has_geoview = api.is_finded("geoview")
|
||||||
local has_gfwlist = fs.access("/usr/share/passwall/rules/gfwlist")
|
local has_gfwlist = fs.access("/usr/share/passwall/rules/gfwlist")
|
||||||
|
@@ -4,7 +4,7 @@ local appname = "passwall"
|
|||||||
local has_ss = api.is_finded("ss-redir")
|
local has_ss = api.is_finded("ss-redir")
|
||||||
local has_ss_rust = api.is_finded("sslocal")
|
local has_ss_rust = api.is_finded("sslocal")
|
||||||
local has_trojan_plus = api.is_finded("trojan-plus")
|
local has_trojan_plus = api.is_finded("trojan-plus")
|
||||||
local has_singbox = api.finded_com("singbox")
|
local has_singbox = api.finded_com("sing-box")
|
||||||
local has_xray = api.finded_com("xray")
|
local has_xray = api.finded_com("xray")
|
||||||
local has_hysteria2 = api.finded_com("hysteria")
|
local has_hysteria2 = api.finded_com("hysteria")
|
||||||
local ss_type = {}
|
local ss_type = {}
|
||||||
|
@@ -25,7 +25,7 @@ end
|
|||||||
local has_ss = api.is_finded("ss-redir")
|
local has_ss = api.is_finded("ss-redir")
|
||||||
local has_ss_rust = api.is_finded("sslocal")
|
local has_ss_rust = api.is_finded("sslocal")
|
||||||
local has_trojan_plus = api.is_finded("trojan-plus")
|
local has_trojan_plus = api.is_finded("trojan-plus")
|
||||||
local has_singbox = api.finded_com("singbox")
|
local has_singbox = api.finded_com("sing-box")
|
||||||
local has_xray = api.finded_com("xray")
|
local has_xray = api.finded_com("xray")
|
||||||
local has_hysteria2 = api.finded_com("hysteria")
|
local has_hysteria2 = api.finded_com("hysteria")
|
||||||
local ss_type = {}
|
local ss_type = {}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
local api = require "luci.passwall.api"
|
local api = require "luci.passwall.api"
|
||||||
local appname = "passwall"
|
local appname = "passwall"
|
||||||
local fs = api.fs
|
local fs = api.fs
|
||||||
local has_singbox = api.finded_com("singbox")
|
local has_singbox = api.finded_com("sing-box")
|
||||||
local has_xray = api.finded_com("xray")
|
local has_xray = api.finded_com("xray")
|
||||||
local has_fw3 = api.is_finded("fw3")
|
local has_fw3 = api.is_finded("fw3")
|
||||||
local has_fw4 = api.is_finded("fw4")
|
local has_fw4 = api.is_finded("fw4")
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
local api = require "luci.passwall.api"
|
local api = require "luci.passwall.api"
|
||||||
local appname = "passwall"
|
local appname = "passwall"
|
||||||
local has_xray = api.finded_com("xray")
|
local has_xray = api.finded_com("xray")
|
||||||
local has_singbox = api.finded_com("singbox")
|
local has_singbox = api.finded_com("sing-box")
|
||||||
|
|
||||||
m = Map(appname)
|
m = Map(appname)
|
||||||
api.set_apply_on_parse(m)
|
api.set_apply_on_parse(m)
|
||||||
|
@@ -9,7 +9,7 @@ if not arg[1] or not m:get(arg[1]) then
|
|||||||
luci.http.redirect(m.redirect)
|
luci.http.redirect(m.redirect)
|
||||||
end
|
end
|
||||||
|
|
||||||
local has_singbox = api.finded_com("singbox")
|
local has_singbox = api.finded_com("sing-box")
|
||||||
local has_xray = api.finded_com("xray")
|
local has_xray = api.finded_com("xray")
|
||||||
|
|
||||||
local nodes_table = {}
|
local nodes_table = {}
|
||||||
|
@@ -2,7 +2,7 @@ local m, s = ...
|
|||||||
|
|
||||||
local api = require "luci.passwall.api"
|
local api = require "luci.passwall.api"
|
||||||
|
|
||||||
local singbox_bin = api.finded_com("singbox")
|
local singbox_bin = api.finded_com("sing-box")
|
||||||
local geoview_bin = api.is_finded("geoview")
|
local geoview_bin = api.is_finded("geoview")
|
||||||
|
|
||||||
if not singbox_bin then
|
if not singbox_bin then
|
||||||
|
@@ -2,7 +2,7 @@ local m, s = ...
|
|||||||
|
|
||||||
local api = require "luci.passwall.api"
|
local api = require "luci.passwall.api"
|
||||||
|
|
||||||
local singbox_bin = api.finded_com("singbox")
|
local singbox_bin = api.finded_com("sing-box")
|
||||||
|
|
||||||
if not singbox_bin then
|
if not singbox_bin then
|
||||||
return
|
return
|
||||||
|
@@ -24,7 +24,7 @@ _M.hysteria = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_M.singbox = {
|
_M["sing-box"] = {
|
||||||
name = "Sing-Box",
|
name = "Sing-Box",
|
||||||
repo = "SagerNet/sing-box",
|
repo = "SagerNet/sing-box",
|
||||||
get_url = gh_release_url,
|
get_url = gh_release_url,
|
||||||
|
@@ -142,7 +142,7 @@ local function start()
|
|||||||
bin = ln_run("/usr/bin/ssserver", "ssserver", "-c " .. config_file, log_path)
|
bin = ln_run("/usr/bin/ssserver", "ssserver", "-c " .. config_file, log_path)
|
||||||
elseif type == "sing-box" then
|
elseif type == "sing-box" then
|
||||||
config = require(require_dir .. "util_sing-box").gen_config_server(user)
|
config = require(require_dir .. "util_sing-box").gen_config_server(user)
|
||||||
bin = ln_run(api.get_app_path("singbox"), "sing-box", "run -c " .. config_file, log_path)
|
bin = ln_run(api.get_app_path("sing-box"), "sing-box", "run -c " .. config_file, log_path)
|
||||||
elseif type == "Xray" then
|
elseif type == "Xray" then
|
||||||
config = require(require_dir .. "util_xray").gen_config_server(user)
|
config = require(require_dir .. "util_xray").gen_config_server(user)
|
||||||
bin = ln_run(api.get_app_path("xray"), "xray", "run -c " .. config_file, log_path)
|
bin = ln_run(api.get_app_path("xray"), "xray", "run -c " .. config_file, log_path)
|
||||||
|
@@ -7,7 +7,7 @@ local appname = "passwall"
|
|||||||
local fs = api.fs
|
local fs = api.fs
|
||||||
local split = api.split
|
local split = api.split
|
||||||
|
|
||||||
local local_version = api.get_app_version("singbox")
|
local local_version = api.get_app_version("sing-box")
|
||||||
local version_ge_1_11_0 = api.compare_versions(local_version:match("[^v]+"), ">=", "1.11.0")
|
local version_ge_1_11_0 = api.compare_versions(local_version:match("[^v]+"), ">=", "1.11.0")
|
||||||
|
|
||||||
local geosite_all_tag = {}
|
local geosite_all_tag = {}
|
||||||
|
@@ -201,7 +201,7 @@ geosite:category-games'
|
|||||||
|
|
||||||
config shunt_rules 'AIGC'
|
config shunt_rules 'AIGC'
|
||||||
option remarks 'AIGC'
|
option remarks 'AIGC'
|
||||||
option domain_list 'geosite:category-ai-chat-!cn
|
option domain_list 'geosite:category-ai-!cn
|
||||||
domain:apple-relay.apple.com'
|
domain:apple-relay.apple.com'
|
||||||
|
|
||||||
config shunt_rules 'Streaming'
|
config shunt_rules 'Streaming'
|
||||||
|
@@ -24,7 +24,7 @@ uci:revert(appname)
|
|||||||
local has_ss = api.is_finded("ss-redir")
|
local has_ss = api.is_finded("ss-redir")
|
||||||
local has_ss_rust = api.is_finded("sslocal")
|
local has_ss_rust = api.is_finded("sslocal")
|
||||||
local has_trojan_plus = api.is_finded("trojan-plus")
|
local has_trojan_plus = api.is_finded("trojan-plus")
|
||||||
local has_singbox = api.finded_com("singbox")
|
local has_singbox = api.finded_com("sing-box")
|
||||||
local has_xray = api.finded_com("xray")
|
local has_xray = api.finded_com("xray")
|
||||||
local has_hysteria2 = api.finded_com("hysteria")
|
local has_hysteria2 = api.finded_com("hysteria")
|
||||||
local allowInsecure_default = nil
|
local allowInsecure_default = nil
|
||||||
|
@@ -20,12 +20,12 @@ jobs:
|
|||||||
toolchain: stable
|
toolchain: stable
|
||||||
- target: aarch64-unknown-linux-musl
|
- target: aarch64-unknown-linux-musl
|
||||||
toolchain: stable
|
toolchain: stable
|
||||||
- target: mips-unknown-linux-gnu
|
#- target: mips-unknown-linux-gnu
|
||||||
toolchain: nightly
|
# toolchain: nightly
|
||||||
- target: mipsel-unknown-linux-gnu
|
#- target: mipsel-unknown-linux-gnu
|
||||||
toolchain: nightly
|
# toolchain: nightly
|
||||||
- target: mips64el-unknown-linux-gnuabi64
|
#- target: mips64el-unknown-linux-gnuabi64
|
||||||
toolchain: nightly
|
# toolchain: nightly
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Free Disk Space (Ubuntu)
|
- name: Free Disk Space (Ubuntu)
|
||||||
|
24
shadowsocks-rust/Cargo.lock
generated
24
shadowsocks-rust/Cargo.lock
generated
@@ -592,18 +592,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.32"
|
version = "4.5.34"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83"
|
checksum = "e958897981290da2a852763fe9cdb89cd36977a5d729023127095fa94d95e2ff"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_builder"
|
name = "clap_builder"
|
||||||
version = "4.5.32"
|
version = "4.5.34"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8"
|
checksum = "83b0f35019843db2160b5bb19ae09b4e6411ac33fc6a712003c33e03090e2489"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
@@ -1009,7 +1009,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
|
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1980,7 +1980,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
|
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"windows-targets 0.52.6",
|
"windows-targets 0.48.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2725,7 +2725,7 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"socket2",
|
"socket2",
|
||||||
"tracing",
|
"tracing",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3054,7 +3054,7 @@ dependencies = [
|
|||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3587,9 +3587,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "spin"
|
name = "spin"
|
||||||
version = "0.9.8"
|
version = "0.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lock_api",
|
"lock_api",
|
||||||
]
|
]
|
||||||
@@ -3728,7 +3728,7 @@ dependencies = [
|
|||||||
"getrandom 0.3.2",
|
"getrandom 0.3.2",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix",
|
"rustix",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4346,7 +4346,7 @@ version = "0.1.9"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@@ -129,7 +129,7 @@ once_cell = "1.17"
|
|||||||
thiserror = "2.0"
|
thiserror = "2.0"
|
||||||
arc-swap = "1.7"
|
arc-swap = "1.7"
|
||||||
|
|
||||||
spin = { version = "0.9" }
|
spin = { version = "0.10" }
|
||||||
lru_time_cache = "0.11"
|
lru_time_cache = "0.11"
|
||||||
bytes = "1.7"
|
bytes = "1.7"
|
||||||
byte_string = "1.0"
|
byte_string = "1.0"
|
||||||
|
@@ -58,7 +58,7 @@ byte_string = "1.0"
|
|||||||
base64 = "0.22"
|
base64 = "0.22"
|
||||||
url = "2.5"
|
url = "2.5"
|
||||||
once_cell = "1.17"
|
once_cell = "1.17"
|
||||||
spin = { version = "0.9", features = ["std"] }
|
spin = { version = "0.10", features = ["std"] }
|
||||||
pin-project = "1.1"
|
pin-project = "1.1"
|
||||||
bloomfilter = { version = "3.0.0", optional = true }
|
bloomfilter = { version = "3.0.0", optional = true }
|
||||||
thiserror = "2.0"
|
thiserror = "2.0"
|
||||||
|
9
sing-box/.github/workflows/build.yml
vendored
9
sing-box/.github/workflows/build.yml
vendored
@@ -176,6 +176,9 @@ jobs:
|
|||||||
PKG_NAME="sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.arch }}${ARM_VERSION}"
|
PKG_NAME="sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.arch }}${ARM_VERSION}"
|
||||||
echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}"
|
echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}"
|
||||||
echo "PKG_NAME=${PKG_NAME}" >> "${GITHUB_ENV}"
|
echo "PKG_NAME=${PKG_NAME}" >> "${GITHUB_ENV}"
|
||||||
|
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
|
||||||
|
PKG_VERSION="${PKG_VERSION//-/\~}"
|
||||||
|
echo "PKG_VERSION=${PKG_VERSION}" >> "${GITHUB_ENV}"
|
||||||
- name: Package DEB
|
- name: Package DEB
|
||||||
if: matrix.debian != ''
|
if: matrix.debian != ''
|
||||||
run: |
|
run: |
|
||||||
@@ -183,7 +186,7 @@ jobs:
|
|||||||
sudo gem install fpm
|
sudo gem install fpm
|
||||||
sudo apt-get install -y debsigs
|
sudo apt-get install -y debsigs
|
||||||
fpm -t deb \
|
fpm -t deb \
|
||||||
-v "${{ needs.calculate_version.outputs.version }}" \
|
-v "$PKG_VERSION" \
|
||||||
-p "dist/${PKG_NAME}.deb" \
|
-p "dist/${PKG_NAME}.deb" \
|
||||||
--architecture ${{ matrix.debian }} \
|
--architecture ${{ matrix.debian }} \
|
||||||
dist/sing-box=/usr/bin/sing-box
|
dist/sing-box=/usr/bin/sing-box
|
||||||
@@ -200,7 +203,7 @@ jobs:
|
|||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
sudo gem install fpm
|
sudo gem install fpm
|
||||||
fpm -t rpm \
|
fpm -t rpm \
|
||||||
-v "${{ needs.calculate_version.outputs.version }}" \
|
-v "$PKG_VERSION" \
|
||||||
-p "dist/${PKG_NAME}.rpm" \
|
-p "dist/${PKG_NAME}.rpm" \
|
||||||
--architecture ${{ matrix.rpm }} \
|
--architecture ${{ matrix.rpm }} \
|
||||||
dist/sing-box=/usr/bin/sing-box
|
dist/sing-box=/usr/bin/sing-box
|
||||||
@@ -219,7 +222,7 @@ jobs:
|
|||||||
sudo gem install fpm
|
sudo gem install fpm
|
||||||
sudo apt-get install -y libarchive-tools
|
sudo apt-get install -y libarchive-tools
|
||||||
fpm -t pacman \
|
fpm -t pacman \
|
||||||
-v "${{ needs.calculate_version.outputs.version }}" \
|
-v "$PKG_VERSION" \
|
||||||
-p "dist/${PKG_NAME}.pkg.tar.zst" \
|
-p "dist/${PKG_NAME}.pkg.tar.zst" \
|
||||||
--architecture ${{ matrix.pacman }} \
|
--architecture ${{ matrix.pacman }} \
|
||||||
dist/sing-box=/usr/bin/sing-box
|
dist/sing-box=/usr/bin/sing-box
|
||||||
|
9
sing-box/.github/workflows/linux.yml
vendored
9
sing-box/.github/workflows/linux.yml
vendored
@@ -109,6 +109,11 @@ jobs:
|
|||||||
if: contains(needs.calculate_version.outputs.version, '-')
|
if: contains(needs.calculate_version.outputs.version, '-')
|
||||||
run: |-
|
run: |-
|
||||||
echo "NAME=sing-box-beta" >> "$GITHUB_ENV"
|
echo "NAME=sing-box-beta" >> "$GITHUB_ENV"
|
||||||
|
- name: Set version
|
||||||
|
run: |-
|
||||||
|
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
|
||||||
|
PKG_VERSION="${PKG_VERSION//-/\~}"
|
||||||
|
echo "PKG_VERSION=${PKG_VERSION}" >> "${GITHUB_ENV}"
|
||||||
- name: Package DEB
|
- name: Package DEB
|
||||||
if: matrix.debian != ''
|
if: matrix.debian != ''
|
||||||
run: |
|
run: |
|
||||||
@@ -117,7 +122,7 @@ jobs:
|
|||||||
sudo apt-get install -y debsigs
|
sudo apt-get install -y debsigs
|
||||||
fpm -t deb \
|
fpm -t deb \
|
||||||
--name "${NAME}" \
|
--name "${NAME}" \
|
||||||
-v "${{ needs.calculate_version.outputs.version }}" \
|
-v "$PKG_VERSION" \
|
||||||
-p "dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.debian }}.deb" \
|
-p "dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.debian }}.deb" \
|
||||||
--architecture ${{ matrix.debian }} \
|
--architecture ${{ matrix.debian }} \
|
||||||
dist/sing-box=/usr/bin/sing-box
|
dist/sing-box=/usr/bin/sing-box
|
||||||
@@ -135,7 +140,7 @@ jobs:
|
|||||||
sudo gem install fpm
|
sudo gem install fpm
|
||||||
fpm -t rpm \
|
fpm -t rpm \
|
||||||
--name "${NAME}" \
|
--name "${NAME}" \
|
||||||
-v "${{ needs.calculate_version.outputs.version }}" \
|
-v "$PKG_VERSION" \
|
||||||
-p "dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.rpm }}.rpm" \
|
-p "dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.rpm }}.rpm" \
|
||||||
--architecture ${{ matrix.rpm }} \
|
--architecture ${{ matrix.rpm }} \
|
||||||
dist/sing-box=/usr/bin/sing-box
|
dist/sing-box=/usr/bin/sing-box
|
||||||
|
@@ -1,3 +1,3 @@
|
|||||||
VERSION_CODE=488
|
VERSION_CODE=497
|
||||||
VERSION_NAME=1.11.5
|
VERSION_NAME=1.11.6
|
||||||
GO_VERSION=go1.24.1
|
GO_VERSION=go1.24.1
|
||||||
|
@@ -140,12 +140,12 @@ func (t *Transport) exchange(ctx context.Context, message *mDNS.Msg, conn quic.C
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer stream.Close()
|
|
||||||
defer stream.CancelRead(0)
|
|
||||||
err = transport.WriteMessage(stream, 0, message)
|
err = transport.WriteMessage(stream, 0, message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
stream.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
stream.Close()
|
||||||
return transport.ReadMessage(stream)
|
return transport.ReadMessage(stream)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,10 +2,16 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
#### 1.12.0-alpha.20
|
#### 1.12.0-alpha.21
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
|
### 1.11.6
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we violated the rules (TestFlight users are not affected)._
|
||||||
|
|
||||||
#### 1.12.0-alpha.19
|
#### 1.12.0-alpha.19
|
||||||
|
|
||||||
* Update gVisor to 20250319.0
|
* Update gVisor to 20250319.0
|
||||||
|
@@ -44,10 +44,10 @@ Default padding scheme:
|
|||||||
|
|
||||||
```
|
```
|
||||||
stop=8
|
stop=8
|
||||||
0=34-120
|
0=30-30
|
||||||
1=100-400
|
1=100-400
|
||||||
2=400-500,c,500-1000,c,400-500,c,500-1000,c,500-1000,c,400-500
|
2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000
|
||||||
3=500-1000
|
3=9-9,500-1000
|
||||||
4=500-1000
|
4=500-1000
|
||||||
5=500-1000
|
5=500-1000
|
||||||
6=500-1000
|
6=500-1000
|
||||||
|
@@ -44,10 +44,10 @@ AnyTLS 填充方案行数组。
|
|||||||
|
|
||||||
```
|
```
|
||||||
stop=8
|
stop=8
|
||||||
0=34-120
|
0=30-30
|
||||||
1=100-400
|
1=100-400
|
||||||
2=400-500,c,500-1000,c,400-500,c,500-1000,c,500-1000,c,400-500
|
2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000
|
||||||
3=500-1000
|
3=9-9,500-1000
|
||||||
4=500-1000
|
4=500-1000
|
||||||
5=500-1000
|
5=500-1000
|
||||||
6=500-1000
|
6=500-1000
|
||||||
|
@@ -3,7 +3,7 @@ module github.com/sagernet/sing-box
|
|||||||
go 1.23.1
|
go 1.23.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/anytls/sing-anytls v0.0.6
|
github.com/anytls/sing-anytls v0.0.7
|
||||||
github.com/caddyserver/certmagic v0.21.7
|
github.com/caddyserver/certmagic v0.21.7
|
||||||
github.com/cloudflare/circl v1.6.0
|
github.com/cloudflare/circl v1.6.0
|
||||||
github.com/cretz/bine v0.2.0
|
github.com/cretz/bine v0.2.0
|
||||||
|
@@ -8,8 +8,8 @@ github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7V
|
|||||||
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
||||||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||||
github.com/anytls/sing-anytls v0.0.6 h1:UatIjl/OvzWQGXQ1I2bAIkabL9WtihW0fA7G+DXGBUg=
|
github.com/anytls/sing-anytls v0.0.7 h1:0Q5dHNB2sqkFAWZCyK2vjQ/ckI5Iz3V/Frf3k7mBrGc=
|
||||||
github.com/anytls/sing-anytls v0.0.6/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8=
|
github.com/anytls/sing-anytls v0.0.7/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8=
|
||||||
github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE=
|
github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE=
|
||||||
github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||||
github.com/caddyserver/certmagic v0.21.7 h1:66KJioPFJwttL43KYSWk7ErSmE6LfaJgCQuhm8Sg6fg=
|
github.com/caddyserver/certmagic v0.21.7 h1:66KJioPFJwttL43KYSWk7ErSmE6LfaJgCQuhm8Sg6fg=
|
||||||
|
@@ -2,8 +2,10 @@ package tailscale
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
@@ -212,6 +214,18 @@ func (t *Endpoint) Start(stage adapter.StartStage) error {
|
|||||||
t.stack = ipStack
|
t.stack = ipStack
|
||||||
|
|
||||||
localBackend := t.server.ExportLocalBackend()
|
localBackend := t.server.ExportLocalBackend()
|
||||||
|
localBackend.SetHTTPTestClient(&http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
ForceAttemptHTTP2: true,
|
||||||
|
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
|
return t.server.Dialer.DialContext(ctx, network, M.ParseSocksaddr(address))
|
||||||
|
},
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
RootCAs: adapter.RootPoolFromContext(t.ctx),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
perfs := &ipn.MaskedPrefs{
|
perfs := &ipn.MaskedPrefs{
|
||||||
ExitNodeIPSet: true,
|
ExitNodeIPSet: true,
|
||||||
AdvertiseRoutesSet: true,
|
AdvertiseRoutesSet: true,
|
||||||
|
@@ -11,7 +11,7 @@ end
|
|||||||
|
|
||||||
local fs = api.fs
|
local fs = api.fs
|
||||||
local sys = api.sys
|
local sys = api.sys
|
||||||
local has_singbox = api.finded_com("singbox")
|
local has_singbox = api.finded_com("sing-box")
|
||||||
local has_xray = api.finded_com("xray")
|
local has_xray = api.finded_com("xray")
|
||||||
local has_geoview = api.is_finded("geoview")
|
local has_geoview = api.is_finded("geoview")
|
||||||
local has_gfwlist = fs.access("/usr/share/passwall/rules/gfwlist")
|
local has_gfwlist = fs.access("/usr/share/passwall/rules/gfwlist")
|
||||||
|
@@ -2,7 +2,7 @@ local api = require "luci.passwall.api"
|
|||||||
local appname = "passwall"
|
local appname = "passwall"
|
||||||
local datatypes = api.datatypes
|
local datatypes = api.datatypes
|
||||||
local fs = api.fs
|
local fs = api.fs
|
||||||
local has_singbox = api.finded_com("singbox")
|
local has_singbox = api.finded_com("sing-box")
|
||||||
local has_xray = api.finded_com("xray")
|
local has_xray = api.finded_com("xray")
|
||||||
local has_geoview = api.is_finded("geoview")
|
local has_geoview = api.is_finded("geoview")
|
||||||
local has_gfwlist = fs.access("/usr/share/passwall/rules/gfwlist")
|
local has_gfwlist = fs.access("/usr/share/passwall/rules/gfwlist")
|
||||||
|
@@ -4,7 +4,7 @@ local appname = "passwall"
|
|||||||
local has_ss = api.is_finded("ss-redir")
|
local has_ss = api.is_finded("ss-redir")
|
||||||
local has_ss_rust = api.is_finded("sslocal")
|
local has_ss_rust = api.is_finded("sslocal")
|
||||||
local has_trojan_plus = api.is_finded("trojan-plus")
|
local has_trojan_plus = api.is_finded("trojan-plus")
|
||||||
local has_singbox = api.finded_com("singbox")
|
local has_singbox = api.finded_com("sing-box")
|
||||||
local has_xray = api.finded_com("xray")
|
local has_xray = api.finded_com("xray")
|
||||||
local has_hysteria2 = api.finded_com("hysteria")
|
local has_hysteria2 = api.finded_com("hysteria")
|
||||||
local ss_type = {}
|
local ss_type = {}
|
||||||
|
@@ -25,7 +25,7 @@ end
|
|||||||
local has_ss = api.is_finded("ss-redir")
|
local has_ss = api.is_finded("ss-redir")
|
||||||
local has_ss_rust = api.is_finded("sslocal")
|
local has_ss_rust = api.is_finded("sslocal")
|
||||||
local has_trojan_plus = api.is_finded("trojan-plus")
|
local has_trojan_plus = api.is_finded("trojan-plus")
|
||||||
local has_singbox = api.finded_com("singbox")
|
local has_singbox = api.finded_com("sing-box")
|
||||||
local has_xray = api.finded_com("xray")
|
local has_xray = api.finded_com("xray")
|
||||||
local has_hysteria2 = api.finded_com("hysteria")
|
local has_hysteria2 = api.finded_com("hysteria")
|
||||||
local ss_type = {}
|
local ss_type = {}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
local api = require "luci.passwall.api"
|
local api = require "luci.passwall.api"
|
||||||
local appname = "passwall"
|
local appname = "passwall"
|
||||||
local fs = api.fs
|
local fs = api.fs
|
||||||
local has_singbox = api.finded_com("singbox")
|
local has_singbox = api.finded_com("sing-box")
|
||||||
local has_xray = api.finded_com("xray")
|
local has_xray = api.finded_com("xray")
|
||||||
local has_fw3 = api.is_finded("fw3")
|
local has_fw3 = api.is_finded("fw3")
|
||||||
local has_fw4 = api.is_finded("fw4")
|
local has_fw4 = api.is_finded("fw4")
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
local api = require "luci.passwall.api"
|
local api = require "luci.passwall.api"
|
||||||
local appname = "passwall"
|
local appname = "passwall"
|
||||||
local has_xray = api.finded_com("xray")
|
local has_xray = api.finded_com("xray")
|
||||||
local has_singbox = api.finded_com("singbox")
|
local has_singbox = api.finded_com("sing-box")
|
||||||
|
|
||||||
m = Map(appname)
|
m = Map(appname)
|
||||||
api.set_apply_on_parse(m)
|
api.set_apply_on_parse(m)
|
||||||
|
@@ -9,7 +9,7 @@ if not arg[1] or not m:get(arg[1]) then
|
|||||||
luci.http.redirect(m.redirect)
|
luci.http.redirect(m.redirect)
|
||||||
end
|
end
|
||||||
|
|
||||||
local has_singbox = api.finded_com("singbox")
|
local has_singbox = api.finded_com("sing-box")
|
||||||
local has_xray = api.finded_com("xray")
|
local has_xray = api.finded_com("xray")
|
||||||
|
|
||||||
local nodes_table = {}
|
local nodes_table = {}
|
||||||
|
@@ -2,7 +2,7 @@ local m, s = ...
|
|||||||
|
|
||||||
local api = require "luci.passwall.api"
|
local api = require "luci.passwall.api"
|
||||||
|
|
||||||
local singbox_bin = api.finded_com("singbox")
|
local singbox_bin = api.finded_com("sing-box")
|
||||||
local geoview_bin = api.is_finded("geoview")
|
local geoview_bin = api.is_finded("geoview")
|
||||||
|
|
||||||
if not singbox_bin then
|
if not singbox_bin then
|
||||||
|
@@ -2,7 +2,7 @@ local m, s = ...
|
|||||||
|
|
||||||
local api = require "luci.passwall.api"
|
local api = require "luci.passwall.api"
|
||||||
|
|
||||||
local singbox_bin = api.finded_com("singbox")
|
local singbox_bin = api.finded_com("sing-box")
|
||||||
|
|
||||||
if not singbox_bin then
|
if not singbox_bin then
|
||||||
return
|
return
|
||||||
|
@@ -24,7 +24,7 @@ _M.hysteria = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_M.singbox = {
|
_M["sing-box"] = {
|
||||||
name = "Sing-Box",
|
name = "Sing-Box",
|
||||||
repo = "SagerNet/sing-box",
|
repo = "SagerNet/sing-box",
|
||||||
get_url = gh_release_url,
|
get_url = gh_release_url,
|
||||||
|
@@ -142,7 +142,7 @@ local function start()
|
|||||||
bin = ln_run("/usr/bin/ssserver", "ssserver", "-c " .. config_file, log_path)
|
bin = ln_run("/usr/bin/ssserver", "ssserver", "-c " .. config_file, log_path)
|
||||||
elseif type == "sing-box" then
|
elseif type == "sing-box" then
|
||||||
config = require(require_dir .. "util_sing-box").gen_config_server(user)
|
config = require(require_dir .. "util_sing-box").gen_config_server(user)
|
||||||
bin = ln_run(api.get_app_path("singbox"), "sing-box", "run -c " .. config_file, log_path)
|
bin = ln_run(api.get_app_path("sing-box"), "sing-box", "run -c " .. config_file, log_path)
|
||||||
elseif type == "Xray" then
|
elseif type == "Xray" then
|
||||||
config = require(require_dir .. "util_xray").gen_config_server(user)
|
config = require(require_dir .. "util_xray").gen_config_server(user)
|
||||||
bin = ln_run(api.get_app_path("xray"), "xray", "run -c " .. config_file, log_path)
|
bin = ln_run(api.get_app_path("xray"), "xray", "run -c " .. config_file, log_path)
|
||||||
|
@@ -7,7 +7,7 @@ local appname = "passwall"
|
|||||||
local fs = api.fs
|
local fs = api.fs
|
||||||
local split = api.split
|
local split = api.split
|
||||||
|
|
||||||
local local_version = api.get_app_version("singbox")
|
local local_version = api.get_app_version("sing-box")
|
||||||
local version_ge_1_11_0 = api.compare_versions(local_version:match("[^v]+"), ">=", "1.11.0")
|
local version_ge_1_11_0 = api.compare_versions(local_version:match("[^v]+"), ">=", "1.11.0")
|
||||||
|
|
||||||
local geosite_all_tag = {}
|
local geosite_all_tag = {}
|
||||||
|
@@ -201,7 +201,7 @@ geosite:category-games'
|
|||||||
|
|
||||||
config shunt_rules 'AIGC'
|
config shunt_rules 'AIGC'
|
||||||
option remarks 'AIGC'
|
option remarks 'AIGC'
|
||||||
option domain_list 'geosite:category-ai-chat-!cn
|
option domain_list 'geosite:category-ai-!cn
|
||||||
domain:apple-relay.apple.com'
|
domain:apple-relay.apple.com'
|
||||||
|
|
||||||
config shunt_rules 'Streaming'
|
config shunt_rules 'Streaming'
|
||||||
|
@@ -24,7 +24,7 @@ uci:revert(appname)
|
|||||||
local has_ss = api.is_finded("ss-redir")
|
local has_ss = api.is_finded("ss-redir")
|
||||||
local has_ss_rust = api.is_finded("sslocal")
|
local has_ss_rust = api.is_finded("sslocal")
|
||||||
local has_trojan_plus = api.is_finded("trojan-plus")
|
local has_trojan_plus = api.is_finded("trojan-plus")
|
||||||
local has_singbox = api.finded_com("singbox")
|
local has_singbox = api.finded_com("sing-box")
|
||||||
local has_xray = api.finded_com("xray")
|
local has_xray = api.finded_com("xray")
|
||||||
local has_hysteria2 = api.finded_com("hysteria")
|
local has_hysteria2 = api.finded_com("hysteria")
|
||||||
local allowInsecure_default = nil
|
local allowInsecure_default = nil
|
||||||
|
@@ -144,7 +144,7 @@ s = m:section(NamedSection, sid, "servers")
|
|||||||
s.anonymous = true
|
s.anonymous = true
|
||||||
s.addremove = false
|
s.addremove = false
|
||||||
|
|
||||||
o = s:option(DummyValue, "ssr_url", "SS/SSR/V2RAY/TROJAN URL")
|
o = s:option(DummyValue, "ssr_url", "SS/SSR/V2RAY/TROJAN/HYSTERIA2 URL")
|
||||||
o.rawhtml = true
|
o.rawhtml = true
|
||||||
o.template = "shadowsocksr/ssrurl"
|
o.template = "shadowsocksr/ssrurl"
|
||||||
o.value = sid
|
o.value = sid
|
||||||
|
@@ -10,7 +10,6 @@ require "luci.util"
|
|||||||
require "luci.sys"
|
require "luci.sys"
|
||||||
require "luci.jsonc"
|
require "luci.jsonc"
|
||||||
require "luci.model.ipkg"
|
require "luci.model.ipkg"
|
||||||
local ucursor = require "luci.model.uci".cursor()
|
|
||||||
|
|
||||||
-- these global functions are accessed all the time by the event handler
|
-- these global functions are accessed all the time by the event handler
|
||||||
-- so caching them is worth the effort
|
-- so caching them is worth the effort
|
||||||
@@ -76,11 +75,20 @@ local encrypt_methods_ss = {
|
|||||||
"camellia-256-cfb",
|
"camellia-256-cfb",
|
||||||
"salsa20",
|
"salsa20",
|
||||||
"chacha20",
|
"chacha20",
|
||||||
"chacha20-ietf" ]]
|
"chacha20-ietf" ]]--
|
||||||
}
|
}
|
||||||
-- 分割字符串
|
-- 分割字符串
|
||||||
local function split(full, sep)
|
local function split(full, sep)
|
||||||
full = full:gsub("%z", "") -- 这里不是很清楚 有时候结尾带个\0
|
if full == nil or type(full) ~= "string" then
|
||||||
|
-- print("Debug: split() received nil or non-string value")
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
full = full:gsub("%z", ""):gsub("^%s+", ""):gsub("%s+$", "") -- 去除首尾空白字符和\0
|
||||||
|
if full == "" then
|
||||||
|
-- print("Debug: split() received empty string after trimming")
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
sep = sep or "," -- 默认分隔符
|
||||||
local off, result = 1, {}
|
local off, result = 1, {}
|
||||||
while true do
|
while true do
|
||||||
local nStart, nEnd = full:find(sep, off)
|
local nStart, nEnd = full:find(sep, off)
|
||||||
@@ -155,13 +163,33 @@ local function checkTabValue(tab)
|
|||||||
end
|
end
|
||||||
return revtab
|
return revtab
|
||||||
end
|
end
|
||||||
|
-- JSON完整性检查
|
||||||
|
local function isCompleteJSON(str)
|
||||||
|
-- 检查JSON格式
|
||||||
|
if type(str) ~= "string" or str:match("^%s*$") then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
-- 尝试解析JSON验证完整性
|
||||||
|
local success, _ = pcall(jsonParse, str)
|
||||||
|
return success
|
||||||
|
end
|
||||||
-- 处理数据
|
-- 处理数据
|
||||||
local function processData(szType, content)
|
local function processData(szType, content)
|
||||||
local result = {type = szType, local_port = 1234, kcp_param = '--nocomp'}
|
local result = {type = szType, local_port = 1234, kcp_param = '--nocomp'}
|
||||||
|
-- 检查JSON的格式如不完整丢弃
|
||||||
|
if not isCompleteJSON(content) then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
if szType == "hysteria2" then
|
if szType == "hysteria2" then
|
||||||
local url = URL.parse("http://" .. content)
|
local url = URL.parse("http://" .. content)
|
||||||
local params = url.query
|
local params = url.query
|
||||||
|
|
||||||
|
-- 调试输出所有参数
|
||||||
|
-- log("Hysteria2 原始参数:")
|
||||||
|
-- for k,v in pairs(params) do
|
||||||
|
-- log(k.."="..v)
|
||||||
|
-- end
|
||||||
|
|
||||||
result.alias = url.fragment and UrlDecode(url.fragment) or nil
|
result.alias = url.fragment and UrlDecode(url.fragment) or nil
|
||||||
result.type = hy2_type
|
result.type = hy2_type
|
||||||
result.server = url.host
|
result.server = url.host
|
||||||
@@ -171,12 +199,12 @@ local function processData(szType, content)
|
|||||||
result.transport_protocol = params.protocol or "udp"
|
result.transport_protocol = params.protocol or "udp"
|
||||||
end
|
end
|
||||||
result.hy2_auth = url.user
|
result.hy2_auth = url.user
|
||||||
result.uplink_capacity = params.upmbps
|
result.uplink_capacity = params.upmbps or "5"
|
||||||
result.downlink_capacity = params.downmbps
|
result.downlink_capacity = params.downmbps or "20"
|
||||||
if params.obfs and params.obfs-password then
|
if params.obfs then
|
||||||
result.flag_obfs = "1"
|
result.flag_obfs = "1"
|
||||||
result.transport_protocol = params.obfs
|
result.obfs_type = params.obfs
|
||||||
result.transport_protocol = params.obfs-password
|
result.salamander = params["obfs-password"] or params["obfs_password"]
|
||||||
end
|
end
|
||||||
if params.sni then
|
if params.sni then
|
||||||
result.tls = "1"
|
result.tls = "1"
|
||||||
@@ -210,8 +238,13 @@ local function processData(szType, content)
|
|||||||
result.alias = "[" .. group .. "] "
|
result.alias = "[" .. group .. "] "
|
||||||
end
|
end
|
||||||
result.alias = result.alias .. base64Decode(params.remarks)
|
result.alias = result.alias .. base64Decode(params.remarks)
|
||||||
elseif szType == 'vmess' then
|
elseif szType == "vmess" then
|
||||||
local info = jsonParse(content)
|
-- 解析正常节点
|
||||||
|
local success, info = pcall(jsonParse, content)
|
||||||
|
if not success or type(info) ~= "table" then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
-- 处理有效数据
|
||||||
result.type = 'v2ray'
|
result.type = 'v2ray'
|
||||||
result.v2ray_protocol = 'vmess'
|
result.v2ray_protocol = 'vmess'
|
||||||
result.server = info.add
|
result.server = info.add
|
||||||
@@ -312,12 +345,21 @@ local function processData(szType, content)
|
|||||||
idx_sp = content:find("#")
|
idx_sp = content:find("#")
|
||||||
alias = content:sub(idx_sp + 1, -1)
|
alias = content:sub(idx_sp + 1, -1)
|
||||||
end
|
end
|
||||||
local info = content:sub(1, idx_sp - 1)
|
local info = content:sub(1, idx_sp > 0 and idx_sp - 1 or #content)
|
||||||
local hostInfo = split(base64Decode(info), "@")
|
local hostInfo = split(base64Decode(info), "@")
|
||||||
|
if #hostInfo < 2 then
|
||||||
|
--log("SS节点格式错误,解码后内容:", base64Decode(info))
|
||||||
|
return nil
|
||||||
|
end
|
||||||
local host = split(hostInfo[2], ":")
|
local host = split(hostInfo[2], ":")
|
||||||
|
if #host < 2 then
|
||||||
|
--log("SS节点主机格式错误:", hostInfo[2])
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
-- 提取用户信息
|
||||||
local userinfo = base64Decode(hostInfo[1])
|
local userinfo = base64Decode(hostInfo[1])
|
||||||
local method = userinfo:sub(1, userinfo:find(":") - 1)
|
local method, password = userinfo:match("^([^:]*):(.*)$")
|
||||||
local password = userinfo:sub(userinfo:find(":") + 1, #userinfo)
|
-- 填充结果
|
||||||
result.alias = UrlDecode(alias)
|
result.alias = UrlDecode(alias)
|
||||||
result.type = v2_ss
|
result.type = v2_ss
|
||||||
result.v2ray_protocol = (v2_ss == "v2ray") and "shadowsocks" or nil
|
result.v2ray_protocol = (v2_ss == "v2ray") and "shadowsocks" or nil
|
||||||
@@ -325,39 +367,47 @@ local function processData(szType, content)
|
|||||||
result.encrypt_method_ss = method
|
result.encrypt_method_ss = method
|
||||||
result.password = password
|
result.password = password
|
||||||
result.server = host[1]
|
result.server = host[1]
|
||||||
if host[2]:find("/%?") then
|
-- 处理端口和插件
|
||||||
local query = split(host[2], "/%?")
|
local port_part = host[2]
|
||||||
|
if port_part:find("/%?") then
|
||||||
|
local query = split(port_part, "/%?")
|
||||||
result.server_port = query[1]
|
result.server_port = query[1]
|
||||||
local params = {}
|
if query[2] then
|
||||||
for _, v in pairs(split(query[2], '&')) do
|
local params = {}
|
||||||
local t = split(v, '=')
|
for _, v in pairs(split(query[2], '&')) do
|
||||||
params[t[1]] = t[2]
|
local t = split(v, '=')
|
||||||
end
|
if #t >= 2 then
|
||||||
if params.plugin then
|
params[t[1]] = t[2]
|
||||||
local plugin_info = UrlDecode(params.plugin)
|
end
|
||||||
local idx_pn = plugin_info:find(";")
|
|
||||||
if idx_pn then
|
|
||||||
result.plugin = plugin_info:sub(1, idx_pn - 1)
|
|
||||||
result.plugin_opts = plugin_info:sub(idx_pn + 1, #plugin_info)
|
|
||||||
else
|
|
||||||
result.plugin = plugin_info
|
|
||||||
result.plugin_opts = ""
|
|
||||||
end
|
end
|
||||||
-- 部分机场下发的插件名为 simple-obfs,这里应该改为 obfs-local
|
if params.plugin then
|
||||||
if result.plugin == "simple-obfs" then
|
local plugin_info = UrlDecode(params.plugin)
|
||||||
result.plugin = "obfs-local"
|
local idx_pn = plugin_info:find(";")
|
||||||
end
|
if idx_pn then
|
||||||
-- 如果插件不為 none,確保 enable_plugin 為 1
|
result.plugin = plugin_info:sub(1, idx_pn - 1)
|
||||||
if result.plugin ~= "none" and result.plugin ~= "" then
|
result.plugin_opts = plugin_info:sub(idx_pn + 1, #plugin_info)
|
||||||
result.enable_plugin = 1
|
else
|
||||||
|
result.plugin = plugin_info
|
||||||
|
result.plugin_opts = ""
|
||||||
|
end
|
||||||
|
-- 部分机场下发的插件名为 simple-obfs,这里应该改为 obfs-local
|
||||||
|
if result.plugin == "simple-obfs" then
|
||||||
|
result.plugin = "obfs-local"
|
||||||
|
end
|
||||||
|
-- 如果插件不为 none,确保 enable_plugin 为 1
|
||||||
|
if result.plugin ~= "none" and result.plugin ~= "" then
|
||||||
|
result.enable_plugin = 1
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
result.server_port = host[2]:gsub("/","")
|
result.server_port = port_part:gsub("/","")
|
||||||
end
|
end
|
||||||
|
-- 检查加密方法
|
||||||
if not checkTabValue(encrypt_methods_ss)[method] then
|
if not checkTabValue(encrypt_methods_ss)[method] then
|
||||||
-- 1202 年了还不支持 SS AEAD 的屑机场
|
-- 1202 年了还不支持 SS AEAD 的屑机场
|
||||||
result.server = nil
|
-- log("不支持的SS加密方法:", method)
|
||||||
|
result.server = nil
|
||||||
end
|
end
|
||||||
elseif szType == "sip008" then
|
elseif szType == "sip008" then
|
||||||
result.type = v2_ss
|
result.type = v2_ss
|
||||||
@@ -601,7 +651,7 @@ local function processData(szType, content)
|
|||||||
end
|
end
|
||||||
-- wget
|
-- wget
|
||||||
local function wget(url)
|
local function wget(url)
|
||||||
local stdout = luci.sys.exec('wget-ssl -q --user-agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36" --no-check-certificate -O- "' .. url .. '"')
|
local stdout = luci.sys.exec('wget-ssl --timeout=20 --tries=3 -q --user-agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36" --no-check-certificate -O- "' .. url .. '"')
|
||||||
return trim(stdout)
|
return trim(stdout)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -814,5 +864,3 @@ if subscribe_url and #subscribe_url > 0 then
|
|||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@@ -6,12 +6,12 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=sing-box
|
PKG_NAME:=sing-box
|
||||||
PKG_VERSION:=1.11.5
|
PKG_VERSION:=1.11.6
|
||||||
PKG_RELEASE:=1
|
PKG_RELEASE:=1
|
||||||
|
|
||||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||||
PKG_SOURCE_URL:=https://codeload.github.com/SagerNet/sing-box/tar.gz/v$(PKG_VERSION)?
|
PKG_SOURCE_URL:=https://codeload.github.com/SagerNet/sing-box/tar.gz/v$(PKG_VERSION)?
|
||||||
PKG_HASH:=ac95287a65ae9297aa0b9f25934ead8468bf7ef3f9045e483659ddc400e758a1
|
PKG_HASH:=01b697683c2433d93e3a15a362241e5e709c01a8e4cc97c889f6d2c5f9db275e
|
||||||
|
|
||||||
PKG_LICENSE:=GPL-3.0-or-later
|
PKG_LICENSE:=GPL-3.0-or-later
|
||||||
PKG_LICENSE_FILES:=LICENSE
|
PKG_LICENSE_FILES:=LICENSE
|
||||||
|
@@ -21,13 +21,13 @@ define Download/geoip
|
|||||||
HASH:=83337c712b04d8c16351cf5a5394eae5cb9cfa257fb4773485945dce65dcea76
|
HASH:=83337c712b04d8c16351cf5a5394eae5cb9cfa257fb4773485945dce65dcea76
|
||||||
endef
|
endef
|
||||||
|
|
||||||
GEOSITE_VER:=20250326132209
|
GEOSITE_VER:=20250327125346
|
||||||
GEOSITE_FILE:=dlc.dat.$(GEOSITE_VER)
|
GEOSITE_FILE:=dlc.dat.$(GEOSITE_VER)
|
||||||
define Download/geosite
|
define Download/geosite
|
||||||
URL:=https://github.com/v2fly/domain-list-community/releases/download/$(GEOSITE_VER)/
|
URL:=https://github.com/v2fly/domain-list-community/releases/download/$(GEOSITE_VER)/
|
||||||
URL_FILE:=dlc.dat
|
URL_FILE:=dlc.dat
|
||||||
FILE:=$(GEOSITE_FILE)
|
FILE:=$(GEOSITE_FILE)
|
||||||
HASH:=1eeb1aa717971c6cabd5fa935a74e2a12c4f264b8641cec340309d85524895a7
|
HASH:=a9114bff9cb6dcdf553b4cd21e56f589a65b7fb1836084b3c32b0acc6f2814a8
|
||||||
endef
|
endef
|
||||||
|
|
||||||
GEOSITE_IRAN_VER:=202503240038
|
GEOSITE_IRAN_VER:=202503240038
|
||||||
|
2
socratex/.github/workflows/build.yml
vendored
2
socratex/.github/workflows/build.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
|||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 22
|
||||||
registry-url: https://registry.npmjs.org/
|
registry-url: https://registry.npmjs.org/
|
||||||
- run: git config --global user.name 'Leask Wong'
|
- run: git config --global user.name 'Leask Wong'
|
||||||
- run: git config --global user.email 'i@leaskh.com'
|
- run: git config --global user.email 'i@leaskh.com'
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "socratex",
|
"name": "socratex",
|
||||||
"description": "A Secure Web Proxy. Which is fast, secure, and easy to use.",
|
"description": "A Secure Web Proxy. Which is fast, secure, and easy to use.",
|
||||||
"version": "2.0.18",
|
"version": "2.0.20",
|
||||||
"private": false,
|
"private": false,
|
||||||
"homepage": "https://github.com/Leask/socratex",
|
"homepage": "https://github.com/Leask/socratex",
|
||||||
"main": "index.mjs",
|
"main": "index.mjs",
|
||||||
|
@@ -151,7 +151,7 @@ func NewProcess(tmpl *Template,
|
|||||||
}
|
}
|
||||||
return nil, fmt.Errorf("unexpected exiting: check the log for more information")
|
return nil, fmt.Errorf("unexpected exiting: check the log for more information")
|
||||||
}
|
}
|
||||||
if time.Since(startTime) > 15*time.Second {
|
if time.Since(startTime) > 30*time.Second {
|
||||||
return nil, fmt.Errorf("timeout: check the log for more information")
|
return nil, fmt.Errorf("timeout: check the log for more information")
|
||||||
}
|
}
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
@@ -72,6 +72,7 @@ namespace ServiceLib
|
|||||||
public const string LocalAppData = "V2RAYN_LOCAL_APPLICATION_DATA_V2";
|
public const string LocalAppData = "V2RAYN_LOCAL_APPLICATION_DATA_V2";
|
||||||
public const string V2RayLocalAsset = "V2RAY_LOCATION_ASSET";
|
public const string V2RayLocalAsset = "V2RAY_LOCATION_ASSET";
|
||||||
public const string XrayLocalAsset = "XRAY_LOCATION_ASSET";
|
public const string XrayLocalAsset = "XRAY_LOCATION_ASSET";
|
||||||
|
public const string XrayLocalCert = "XRAY_LOCATION_CERT";
|
||||||
public const int SpeedTestPageSize = 1000;
|
public const int SpeedTestPageSize = 1000;
|
||||||
public const string LinuxBash = "/bin/bash";
|
public const string LinuxBash = "/bin/bash";
|
||||||
|
|
||||||
|
@@ -24,6 +24,7 @@ namespace ServiceLib.Handler
|
|||||||
|
|
||||||
Environment.SetEnvironmentVariable(Global.V2RayLocalAsset, Utils.GetBinPath(""), EnvironmentVariableTarget.Process);
|
Environment.SetEnvironmentVariable(Global.V2RayLocalAsset, Utils.GetBinPath(""), EnvironmentVariableTarget.Process);
|
||||||
Environment.SetEnvironmentVariable(Global.XrayLocalAsset, Utils.GetBinPath(""), EnvironmentVariableTarget.Process);
|
Environment.SetEnvironmentVariable(Global.XrayLocalAsset, Utils.GetBinPath(""), EnvironmentVariableTarget.Process);
|
||||||
|
Environment.SetEnvironmentVariable(Global.XrayLocalCert, Utils.GetBinPath(""), EnvironmentVariableTarget.Process);
|
||||||
|
|
||||||
//Copy the bin folder to the storage location (for init)
|
//Copy the bin folder to the storage location (for init)
|
||||||
if (Environment.GetEnvironmentVariable(Global.LocalAppData) == "1")
|
if (Environment.GetEnvironmentVariable(Global.LocalAppData) == "1")
|
||||||
|
@@ -148,7 +148,7 @@ dependencies {
|
|||||||
|
|
||||||
// UI Libraries
|
// UI Libraries
|
||||||
implementation(libs.material)
|
implementation(libs.material)
|
||||||
implementation(libs.toastcompat)
|
implementation(libs.toasty)
|
||||||
implementation(libs.editorkit)
|
implementation(libs.editorkit)
|
||||||
implementation(libs.flexbox)
|
implementation(libs.flexbox)
|
||||||
|
|
||||||
|
@@ -35,7 +35,6 @@
|
|||||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
<!-- <useapplications-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> -->
|
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission
|
<uses-permission
|
||||||
@@ -213,7 +212,8 @@
|
|||||||
android:icon="@drawable/ic_stat_name"
|
android:icon="@drawable/ic_stat_name"
|
||||||
android:label="@string/app_tile_name"
|
android:label="@string/app_tile_name"
|
||||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
|
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
|
||||||
android:process=":RunSoLibV2RayDaemon">
|
android:process=":RunSoLibV2RayDaemon"
|
||||||
|
tools:targetApi="24">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
@@ -8,7 +8,7 @@ import android.os.Build
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import com.v2ray.ang.AngApplication
|
import com.v2ray.ang.AngApplication
|
||||||
import me.drakeet.support.toast.ToastCompat
|
import es.dmoral.toasty.Toasty
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
@@ -23,7 +23,7 @@ val Context.v2RayApplication: AngApplication?
|
|||||||
* @param message The resource ID of the message to show.
|
* @param message The resource ID of the message to show.
|
||||||
*/
|
*/
|
||||||
fun Context.toast(message: Int) {
|
fun Context.toast(message: Int) {
|
||||||
ToastCompat.makeText(this, message, Toast.LENGTH_SHORT).show()
|
Toasty.normal(this, message).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,9 +32,46 @@ fun Context.toast(message: Int) {
|
|||||||
* @param message The text of the message to show.
|
* @param message The text of the message to show.
|
||||||
*/
|
*/
|
||||||
fun Context.toast(message: CharSequence) {
|
fun Context.toast(message: CharSequence) {
|
||||||
ToastCompat.makeText(this, message, Toast.LENGTH_SHORT).show()
|
Toasty.normal(this, message).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a toast message with the given resource ID.
|
||||||
|
*
|
||||||
|
* @param message The resource ID of the message to show.
|
||||||
|
*/
|
||||||
|
fun Context.toastSuccess(message: Int) {
|
||||||
|
Toasty.success(this, message, Toast.LENGTH_SHORT, true).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a toast message with the given text.
|
||||||
|
*
|
||||||
|
* @param message The text of the message to show.
|
||||||
|
*/
|
||||||
|
fun Context.toastSuccess(message: CharSequence) {
|
||||||
|
Toasty.success(this, message, Toast.LENGTH_SHORT, true).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a toast message with the given resource ID.
|
||||||
|
*
|
||||||
|
* @param message The resource ID of the message to show.
|
||||||
|
*/
|
||||||
|
fun Context.toastError(message: Int) {
|
||||||
|
Toasty.error(this, message, Toast.LENGTH_SHORT, true).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a toast message with the given text.
|
||||||
|
*
|
||||||
|
* @param message The text of the message to show.
|
||||||
|
*/
|
||||||
|
fun Context.toastError(message: CharSequence) {
|
||||||
|
Toasty.error(this, message, Toast.LENGTH_SHORT, true).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Puts a key-value pair into the JSONObject.
|
* Puts a key-value pair into the JSONObject.
|
||||||
*
|
*
|
||||||
|
@@ -14,6 +14,8 @@ import com.v2ray.ang.BuildConfig
|
|||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.databinding.ActivityAboutBinding
|
import com.v2ray.ang.databinding.ActivityAboutBinding
|
||||||
import com.v2ray.ang.extension.toast
|
import com.v2ray.ang.extension.toast
|
||||||
|
import com.v2ray.ang.extension.toastError
|
||||||
|
import com.v2ray.ang.extension.toastSuccess
|
||||||
import com.v2ray.ang.handler.SpeedtestManager
|
import com.v2ray.ang.handler.SpeedtestManager
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import com.v2ray.ang.util.ZipUtil
|
import com.v2ray.ang.util.ZipUtil
|
||||||
@@ -50,9 +52,9 @@ class AboutActivity : BaseActivity() {
|
|||||||
binding.layoutBackup.setOnClickListener {
|
binding.layoutBackup.setOnClickListener {
|
||||||
val ret = backupConfiguration(extDir.absolutePath)
|
val ret = backupConfiguration(extDir.absolutePath)
|
||||||
if (ret.first) {
|
if (ret.first) {
|
||||||
toast(R.string.toast_success)
|
toastSuccess(R.string.toast_success)
|
||||||
} else {
|
} else {
|
||||||
toast(R.string.toast_failure)
|
toastError(R.string.toast_failure)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,7 +74,7 @@ class AboutActivity : BaseActivity() {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
toast(R.string.toast_failure)
|
toastError(R.string.toast_failure)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,7 +128,7 @@ class AboutActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun backupConfiguration(outputZipFilePos: String): Pair<Boolean, String> {
|
private fun backupConfiguration(outputZipFilePos: String): Pair<Boolean, String> {
|
||||||
val dateFormated = SimpleDateFormat(
|
val dateFormated = SimpleDateFormat(
|
||||||
"yyyy-MM-dd-HH-mm-ss",
|
"yyyy-MM-dd-HH-mm-ss",
|
||||||
Locale.getDefault()
|
Locale.getDefault()
|
||||||
@@ -147,7 +149,7 @@ class AboutActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun restoreConfiguration(zipFile: File): Boolean {
|
private fun restoreConfiguration(zipFile: File): Boolean {
|
||||||
val backupDir = this.cacheDir.absolutePath + "/${System.currentTimeMillis()}"
|
val backupDir = this.cacheDir.absolutePath + "/${System.currentTimeMillis()}"
|
||||||
|
|
||||||
if (!ZipUtil.unzipToFolder(zipFile, backupDir)) {
|
if (!ZipUtil.unzipToFolder(zipFile, backupDir)) {
|
||||||
@@ -185,13 +187,13 @@ class AboutActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (restoreConfiguration(targetFile)) {
|
if (restoreConfiguration(targetFile)) {
|
||||||
toast(R.string.toast_success)
|
toastSuccess(R.string.toast_success)
|
||||||
} else {
|
} else {
|
||||||
toast(R.string.toast_failure)
|
toastError(R.string.toast_failure)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(AppConfig.ANG_PACKAGE, "Error during file restore: ${e.message}", e)
|
Log.e(AppConfig.ANG_PACKAGE, "Error during file restore: ${e.message}", e)
|
||||||
toast(R.string.toast_failure)
|
toastError(R.string.toast_failure)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
package com.v2ray.ang.ui
|
package com.v2ray.ang.ui
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
@@ -11,6 +12,7 @@ import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
|||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.databinding.ActivityLogcatBinding
|
import com.v2ray.ang.databinding.ActivityLogcatBinding
|
||||||
import com.v2ray.ang.extension.toast
|
import com.v2ray.ang.extension.toast
|
||||||
|
import com.v2ray.ang.extension.toastSuccess
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -21,7 +23,7 @@ import java.io.IOException
|
|||||||
class LogcatActivity : BaseActivity(), SwipeRefreshLayout.OnRefreshListener {
|
class LogcatActivity : BaseActivity(), SwipeRefreshLayout.OnRefreshListener {
|
||||||
private val binding by lazy { ActivityLogcatBinding.inflate(layoutInflater) }
|
private val binding by lazy { ActivityLogcatBinding.inflate(layoutInflater) }
|
||||||
|
|
||||||
var logsetsAll: MutableList<String> = mutableListOf()
|
private var logsetsAll: MutableList<String> = mutableListOf()
|
||||||
var logsets: MutableList<String> = mutableListOf()
|
var logsets: MutableList<String> = mutableListOf()
|
||||||
private val adapter by lazy { LogcatRecyclerAdapter(this) }
|
private val adapter by lazy { LogcatRecyclerAdapter(this) }
|
||||||
|
|
||||||
@@ -62,7 +64,7 @@ class LogcatActivity : BaseActivity(), SwipeRefreshLayout.OnRefreshListener {
|
|||||||
launch(Dispatchers.Main) {
|
launch(Dispatchers.Main) {
|
||||||
logsetsAll = allText.toMutableList()
|
logsetsAll = allText.toMutableList()
|
||||||
logsets = allText.toMutableList()
|
logsets = allText.toMutableList()
|
||||||
adapter.notifyDataSetChanged()
|
refreshData()
|
||||||
binding.refreshLayout.isRefreshing = false
|
binding.refreshLayout.isRefreshing = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -84,7 +86,7 @@ class LogcatActivity : BaseActivity(), SwipeRefreshLayout.OnRefreshListener {
|
|||||||
launch(Dispatchers.Main) {
|
launch(Dispatchers.Main) {
|
||||||
logsetsAll.clear()
|
logsetsAll.clear()
|
||||||
logsets.clear()
|
logsets.clear()
|
||||||
adapter.notifyDataSetChanged()
|
refreshData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
@@ -118,7 +120,7 @@ class LogcatActivity : BaseActivity(), SwipeRefreshLayout.OnRefreshListener {
|
|||||||
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
||||||
R.id.copy_all -> {
|
R.id.copy_all -> {
|
||||||
Utils.setClipboard(this, logsets.joinToString("\n"))
|
Utils.setClipboard(this, logsets.joinToString("\n"))
|
||||||
toast(R.string.toast_success)
|
toastSuccess(R.string.toast_success)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,11 +140,16 @@ class LogcatActivity : BaseActivity(), SwipeRefreshLayout.OnRefreshListener {
|
|||||||
logsetsAll.filter { it.contains(key) }.toMutableList()
|
logsetsAll.filter { it.contains(key) }.toMutableList()
|
||||||
}
|
}
|
||||||
|
|
||||||
adapter?.notifyDataSetChanged()
|
refreshData()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRefresh() {
|
override fun onRefresh() {
|
||||||
getLogcat()
|
getLogcat()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
|
fun refreshData() {
|
||||||
|
adapter.notifyDataSetChanged()
|
||||||
|
}
|
||||||
}
|
}
|
@@ -1,6 +1,7 @@
|
|||||||
package com.v2ray.ang.ui
|
package com.v2ray.ang.ui
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
@@ -31,6 +32,7 @@ import com.v2ray.ang.R
|
|||||||
import com.v2ray.ang.databinding.ActivityMainBinding
|
import com.v2ray.ang.databinding.ActivityMainBinding
|
||||||
import com.v2ray.ang.dto.EConfigType
|
import com.v2ray.ang.dto.EConfigType
|
||||||
import com.v2ray.ang.extension.toast
|
import com.v2ray.ang.extension.toast
|
||||||
|
import com.v2ray.ang.extension.toastError
|
||||||
import com.v2ray.ang.handler.AngConfigManager
|
import com.v2ray.ang.handler.AngConfigManager
|
||||||
import com.v2ray.ang.handler.MigrateManager
|
import com.v2ray.ang.handler.MigrateManager
|
||||||
import com.v2ray.ang.handler.MmkvManager
|
import com.v2ray.ang.handler.MmkvManager
|
||||||
@@ -204,6 +206,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
private fun setupViewModel() {
|
private fun setupViewModel() {
|
||||||
mainViewModel.updateListAction.observe(this) { index ->
|
mainViewModel.updateListAction.observe(this) { index ->
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
@@ -269,7 +272,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
binding.tabGroup.isVisible = true
|
binding.tabGroup.isVisible = true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startV2Ray() {
|
private fun startV2Ray() {
|
||||||
if (MmkvManager.getSelectServer().isNullOrEmpty()) {
|
if (MmkvManager.getSelectServer().isNullOrEmpty()) {
|
||||||
toast(R.string.title_file_chooser)
|
toast(R.string.title_file_chooser)
|
||||||
return
|
return
|
||||||
@@ -277,7 +280,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
V2RayServiceManager.startVService(this)
|
V2RayServiceManager.startVService(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun restartV2Ray() {
|
private fun restartV2Ray() {
|
||||||
if (mainViewModel.isRunning.value == true) {
|
if (mainViewModel.isRunning.value == true) {
|
||||||
V2RayServiceManager.stopVService(this)
|
V2RayServiceManager.stopVService(this)
|
||||||
}
|
}
|
||||||
@@ -503,13 +506,13 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
}
|
}
|
||||||
|
|
||||||
countSub > 0 -> initGroupTab()
|
countSub > 0 -> initGroupTab()
|
||||||
else -> toast(R.string.toast_failure)
|
else -> toastError(R.string.toast_failure)
|
||||||
}
|
}
|
||||||
binding.pbWaiting.hide()
|
binding.pbWaiting.hide()
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
toast(R.string.toast_failure)
|
toastError(R.string.toast_failure)
|
||||||
binding.pbWaiting.hide()
|
binding.pbWaiting.hide()
|
||||||
}
|
}
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
@@ -613,7 +616,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
toast(getString(R.string.title_update_config_count, count))
|
toast(getString(R.string.title_update_config_count, count))
|
||||||
mainViewModel.reloadServerList()
|
mainViewModel.reloadServerList()
|
||||||
} else {
|
} else {
|
||||||
toast(R.string.toast_failure)
|
toastError(R.string.toast_failure)
|
||||||
}
|
}
|
||||||
binding.pbWaiting.hide()
|
binding.pbWaiting.hide()
|
||||||
}
|
}
|
||||||
@@ -629,7 +632,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
if (ret > 0)
|
if (ret > 0)
|
||||||
toast(getString(R.string.title_export_config_count, ret))
|
toast(getString(R.string.title_export_config_count, ret))
|
||||||
else
|
else
|
||||||
toast(R.string.toast_failure)
|
toastError(R.string.toast_failure)
|
||||||
binding.pbWaiting.hide()
|
binding.pbWaiting.hide()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -759,9 +762,9 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
// }
|
// }
|
||||||
// if (mainViewModel.appendCustomConfigServer(server)) {
|
// if (mainViewModel.appendCustomConfigServer(server)) {
|
||||||
// mainViewModel.reloadServerList()
|
// mainViewModel.reloadServerList()
|
||||||
// toast(R.string.toast_success)
|
// toastSuccess(R.string.toast_success)
|
||||||
// } else {
|
// } else {
|
||||||
// toast(R.string.toast_failure)
|
// toastError(R.string.toast_failure)
|
||||||
// }
|
// }
|
||||||
// //adapter.notifyItemInserted(mainViewModel.serverList.lastIndex)
|
// //adapter.notifyItemInserted(mainViewModel.serverList.lastIndex)
|
||||||
// } catch (e: Exception) {
|
// } catch (e: Exception) {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user