Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70819c6c77 | ||
|
|
42846b8e7b | ||
|
|
64838e272f |
@@ -8,48 +8,49 @@ GOFILES=*.go
|
||||
|
||||
PLATFORM_LIST = \
|
||||
linux-amd64 \
|
||||
linux-arm64
|
||||
# linux-arm64
|
||||
|
||||
WINDOWS_ARCH_LIST = \
|
||||
windows-amd64 \
|
||||
windows-arm64
|
||||
|
||||
linux-amd64:
|
||||
GOOS=linux GOARCH=amd64 CGO_ENABLED=1 go build --ldflags="-s -w" -v -x -a -o $(BINDIR)/$(NAME)-$@ $(GOFILES)
|
||||
GOOS=linux GOARCH=amd64 CGO_ENABLED=1 go build --ldflags="-s -w" -v -x -a -o $(BINDIR)/$(NAME)-$(VERSION)-$@ $(GOFILES)
|
||||
|
||||
linux-arm64:
|
||||
GOOS=linux GOARCH=arm64 CGO_ENABLED=1 go build --ldflags="-s -w" -v -x -a -o $(BINDIR)/$(NAME)-$@ $(GOFILES)
|
||||
GOOS=linux GOARCH=arm64 CGO_ENABLED=1 go build --ldflags="-s -w" -v -x -a -o $(BINDIR)/$(NAME)-$(VERSION)-$@ $(GOFILES)
|
||||
|
||||
darwin-amd64:
|
||||
GOOS=darwin GOARCH=amd64 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES)
|
||||
GOOS=darwin GOARCH=amd64 $(GOBUILD) -o $(BINDIR)/$(NAME)-$(VERSION)-$@ $(GOFILES)
|
||||
|
||||
darwin-arm64:
|
||||
GOOS=darwin GOARCH=arm64 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES)
|
||||
GOOS=darwin GOARCH=arm64 $(GOBUILD) -o $(BINDIR)/$(NAME)-$(VERSION)-$@ $(GOFILES)
|
||||
|
||||
# https://github.com/tc-hib/go-winres
|
||||
windows-amd64:
|
||||
GOOS=windows GOARCH=amd64 go-winres make --in winres/winres.json --out winres/rsrc
|
||||
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-s -w -H windowsgui" -o $(BINDIR)/$(NAME)-$@.exe $(GOFILES)
|
||||
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-s -w -H windowsgui" -o $(BINDIR)/$(NAME)-$(VERSION)-$@.exe $(GOFILES)
|
||||
|
||||
windows-arm64:
|
||||
GOOS=windows GOARCH=arm64 go-winres make --in winres/winres.json --out winres/rsrc
|
||||
GOOS=windows GOARCH=arm64 CGO_ENABLED=0 go build -ldflags="-s -w -H windowsgui" -o $(BINDIR)/$(NAME)-$@.exe $(GOFILES)
|
||||
GOOS=windows GOARCH=arm64 CGO_ENABLED=0 go build -ldflags="-s -w -H windowsgui" -o $(BINDIR)/$(NAME)-$(VERSION)-$@.exe $(GOFILES)
|
||||
|
||||
# go install gioui.org/cmd/gogio@latest
|
||||
android:
|
||||
gogio -x -work -target android -minsdk 33 -version $(VERSION).3 -signkey build/sign.keystore -signpass android -appid gost.plus -o $(BINDIR)/$(NAME)-$(VERSION).aab .
|
||||
gogio -x -work -target android -minsdk 22 -version $(VERSION).8 -name GOST+ -signkey build/sign.keystore -signpass android -appid gost.plus -o $(BINDIR)/$(NAME)-$(VERSION).aab .
|
||||
gogio -x -work -target android -minsdk 22 -version $(VERSION).8 -name GOST+ -signkey build/sign.keystore -signpass android -appid gost.plus -o $(BINDIR)/$(NAME)-$(VERSION).apk .
|
||||
|
||||
gz_releases=$(addsuffix .gz, $(PLATFORM_LIST))
|
||||
zip_releases=$(addsuffix .zip, $(WINDOWS_ARCH_LIST))
|
||||
|
||||
$(gz_releases): %.gz : %
|
||||
chmod +x $(BINDIR)/$(NAME)-$(basename $@)
|
||||
gzip -f -S -$(VERSION).gz $(BINDIR)/$(NAME)-$(basename $@)
|
||||
chmod +x $(BINDIR)/$(NAME)-$(VERSION)-$(basename $@)
|
||||
gzip -f -S .gz $(BINDIR)/$(NAME)-$(VERSION)-$(basename $@)
|
||||
|
||||
$(zip_releases): %.zip : %
|
||||
zip -m -j $(BINDIR)/$(NAME)-$(basename $@)-$(VERSION).zip $(BINDIR)/$(NAME)-$(basename $@).exe
|
||||
zip -m -j $(BINDIR)/$(NAME)-$(VERSION)-$(basename $@).zip $(BINDIR)/$(NAME)-$(VERSION)-$(basename $@).exe
|
||||
|
||||
releases: $(gz_releases) $(zip_releases)
|
||||
releases: $(gz_releases) $(zip_releases) android
|
||||
|
||||
clean:
|
||||
rm *.syso -f
|
||||
|
||||
|
Before Width: | Height: | Size: 157 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 148 KiB After Width: | Height: | Size: 116 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 778 KiB After Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 391 KiB After Width: | Height: | Size: 144 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 751 KiB After Width: | Height: | Size: 1.1 MiB |
@@ -18,6 +18,7 @@ import (
|
||||
|
||||
const (
|
||||
configFile = "config.yml"
|
||||
logFile = "gost-plus.log"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -58,10 +59,20 @@ func Init() {
|
||||
func initLog() {
|
||||
cfg := Get().Log
|
||||
if cfg == nil {
|
||||
logDir := filepath.Join(configDir, "logs")
|
||||
os.MkdirAll(logDir, 0755)
|
||||
slog.Info(fmt.Sprintf("log dir: %s", logDir))
|
||||
|
||||
cfg = &xconfig.LogConfig{
|
||||
Output: "stdout",
|
||||
Output: filepath.Join(logDir, logFile),
|
||||
Level: string(logger.InfoLevel),
|
||||
Format: string(logger.JSONFormat),
|
||||
Rotation: &xconfig.LogRotationConfig{
|
||||
MaxSize: 10,
|
||||
MaxAge: 7,
|
||||
MaxBackups: 10,
|
||||
LocalTime: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
module github.com/go-gost/gost.plus
|
||||
|
||||
go 1.22.0
|
||||
go 1.22
|
||||
|
||||
toolchain go1.22.2
|
||||
|
||||
require (
|
||||
gioui.org v0.6.0
|
||||
gioui.org/x v0.6.1
|
||||
github.com/go-gost/core v0.0.0-20240309124515-fea73cf68235
|
||||
github.com/go-gost/x v0.0.0-20240131151842-25dcf536c6f5
|
||||
gioui.org v0.7.0
|
||||
gioui.org/x v0.7.0
|
||||
github.com/go-gost/core v0.1.0
|
||||
github.com/go-gost/x v0.1.0
|
||||
github.com/google/uuid v1.6.0
|
||||
golang.org/x/exp/shiny v0.0.0-20240103183307-be819d1f06fc
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
@@ -15,30 +17,29 @@ require (
|
||||
require (
|
||||
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 // indirect
|
||||
gioui.org/shader v1.0.8 // indirect
|
||||
git.wow.st/gmp/jni v0.0.0-20210610011705-34026c7e22d0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/go-gost/gosocks5 v0.4.0 // indirect
|
||||
github.com/go-gost/gosocks5 v0.4.2 // indirect
|
||||
github.com/go-gost/plugin v0.0.0-20240103125338-9c84e29cb81a // indirect
|
||||
github.com/go-gost/relay v0.5.0 // indirect
|
||||
github.com/go-gost/tls-dissector v0.0.2-0.20220408131628-aac992c27451 // indirect
|
||||
github.com/go-redis/redis/v8 v8.11.5 // indirect
|
||||
github.com/go-text/typesetting v0.1.1 // indirect
|
||||
github.com/gobwas/glob v0.2.3 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/gorilla/websocket v1.5.1 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.1.1 // indirect
|
||||
github.com/pires/go-proxyproto v0.7.0 // indirect
|
||||
github.com/prometheus/client_golang v1.17.0 // indirect
|
||||
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect
|
||||
github.com/prometheus/common v0.44.0 // indirect
|
||||
github.com/prometheus/procfs v0.11.1 // indirect
|
||||
github.com/prometheus/client_golang v1.19.1 // indirect
|
||||
github.com/prometheus/client_model v0.5.0 // indirect
|
||||
github.com/prometheus/common v0.48.0 // indirect
|
||||
github.com/prometheus/procfs v0.12.0 // indirect
|
||||
github.com/rs/xid v1.3.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
@@ -49,17 +50,18 @@ require (
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.18.2 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/vishvananda/netns v0.0.4 // indirect
|
||||
github.com/xtaci/smux v1.5.24 // indirect
|
||||
github.com/yl2chen/cidranger v1.0.2 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
|
||||
golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 // indirect
|
||||
golang.org/x/image v0.14.0 // indirect
|
||||
golang.org/x/net v0.19.0 // indirect
|
||||
golang.org/x/sys v0.18.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect
|
||||
google.golang.org/grpc v1.59.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
golang.org/x/net v0.27.0 // indirect
|
||||
golang.org/x/sys v0.22.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect
|
||||
google.golang.org/grpc v1.65.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
)
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d h1:ARo7NCVvN2NdhLlJE9xAbKweuI9L6UgfTbYb0YwPacY=
|
||||
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod h1:OYVuxibdk9OSLX8vAqydtRPP87PyTFcT9uH3MlEGBQA=
|
||||
gioui.org v0.6.0 h1:ZSXO/AbpFZJ2L9NU69uFQfDI3BKIH+YEJElrn0B+aZI=
|
||||
gioui.org v0.6.0/go.mod h1:eUvGo6FAzA7jUqeSu5a+M1W03yc9r1nanIBS8A5+Nng=
|
||||
gioui.org v0.7.0 h1:5I+7Uu2yjTu7W5p7HWQrgsDPO3vex+8T1DsvCLGBfuI=
|
||||
gioui.org v0.7.0/go.mod h1:19wZxaNP+eHN4H2YdZwEfbkAAgoYB5rcIbDHo4BqUl4=
|
||||
gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
|
||||
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 h1:AGDDxsJE1RpcXTAxPG2B4jrwVUJGFDjINIPi1jtO6pc=
|
||||
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
|
||||
gioui.org/shader v1.0.8 h1:6ks0o/A+b0ne7RzEqRZK5f4Gboz2CfG+mVliciy6+qA=
|
||||
gioui.org/shader v1.0.8/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
|
||||
gioui.org/x v0.6.1 h1:cHxT3CA07YYm6yNv6FrHRUqbNH2KId8vli3uIM8BF5A=
|
||||
gioui.org/x v0.6.1/go.mod h1:18BNyA2swIgDWV3/Nf7cQh9TaCTQQGbdLUg+pmURS3o=
|
||||
gioui.org/x v0.7.0 h1:8rkLfy4MAHM+MgEv8IUwRcmB9rxJqYnRzbuOJqXzG6Y=
|
||||
gioui.org/x v0.7.0/go.mod h1:zJdmUbD5PuGHL8fzX0kehLTZ6TmIvfo6mg10sW54dsU=
|
||||
git.wow.st/gmp/jni v0.0.0-20210610011705-34026c7e22d0 h1:bGG/g4ypjrCJoSvFrP5hafr9PPB5aw8SjcOWWila7ZI=
|
||||
git.wow.st/gmp/jni v0.0.0-20210610011705-34026c7e22d0/go.mod h1:+axXBRUTIDlCeE73IKeD/os7LoEnTKdkp8/gQOFjqyo=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
@@ -23,18 +25,18 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/go-gost/core v0.0.0-20240309124515-fea73cf68235 h1:2iju5ndwCnazCaOcacxJaVFf3J/yb0L0ebXYh2J4rik=
|
||||
github.com/go-gost/core v0.0.0-20240309124515-fea73cf68235/go.mod h1:ndkgWVYRLwupVaFFWv8ML1Nr8tD3xhHK245PLpUDg4E=
|
||||
github.com/go-gost/gosocks5 v0.4.0 h1:EIrOEkpJez4gwHrMa33frA+hHXJyevjp47thpMQsJzI=
|
||||
github.com/go-gost/gosocks5 v0.4.0/go.mod h1:1G6I7HP7VFVxveGkoK8mnprnJqSqJjdcASKsdUn4Pp4=
|
||||
github.com/go-gost/core v0.1.0 h1:LJJc8PIlRflE8ZIpxls+wYX1e8OGB0nUKJYh8HevM4U=
|
||||
github.com/go-gost/core v0.1.0/go.mod h1:WGI43jOka7FAsSAwi/fSMaqxdR+E339ycb4NBGlFr6A=
|
||||
github.com/go-gost/gosocks5 v0.4.2 h1:IianxHTkACPqCwiOAT3MHoMdSUl+SEPSRu1ikawC1Pc=
|
||||
github.com/go-gost/gosocks5 v0.4.2/go.mod h1:1G6I7HP7VFVxveGkoK8mnprnJqSqJjdcASKsdUn4Pp4=
|
||||
github.com/go-gost/plugin v0.0.0-20240103125338-9c84e29cb81a h1:ME7P1Brcg4C640DSPqlvQr7JuvvQfJ8QpmS3yCFlK3A=
|
||||
github.com/go-gost/plugin v0.0.0-20240103125338-9c84e29cb81a/go.mod h1:qXr2Zm9Ex2ATqnWuNUzVZqySPMnuIihvblYZt4MlZLw=
|
||||
github.com/go-gost/relay v0.5.0 h1:JG1tgy/KWiVXS0ukuVXvbM0kbYuJTWxYpJ5JwzsCf/c=
|
||||
github.com/go-gost/relay v0.5.0/go.mod h1:lcX+23LCQ3khIeASBo+tJ/WbwXFO32/N5YN6ucuYTG8=
|
||||
github.com/go-gost/tls-dissector v0.0.2-0.20220408131628-aac992c27451 h1:xj8gUZGYO3nb5+6Bjw9+tsFkA9sYynrOvDvvC4uDV2I=
|
||||
github.com/go-gost/tls-dissector v0.0.2-0.20220408131628-aac992c27451/go.mod h1:/9QfdewqmHdaE362Hv5nDaSWLx3pCmtD870d6GaquXs=
|
||||
github.com/go-gost/x v0.0.0-20240131151842-25dcf536c6f5 h1:IiZLdqGMx0lGVbDBy/N9LPu10qSlxm939EBvZ77qJNI=
|
||||
github.com/go-gost/x v0.0.0-20240131151842-25dcf536c6f5/go.mod h1:FDqjiiPbCqJLU/wY+q2IZCBVcYnfTJTw+SJLrspLQms=
|
||||
github.com/go-gost/x v0.1.0 h1:2pDxVCISX1NS/YRdlQ3qmHM3VqOd3DtBoaxZ4xseE8U=
|
||||
github.com/go-gost/x v0.1.0/go.mod h1:Te5EMigKCWAjTzdOow+S9BTfxJszDpj2B5T4mNntUIg=
|
||||
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
||||
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
||||
github.com/go-text/typesetting v0.1.1 h1:bGAesCuo85nXnEN5LmFMVGAGpGkCPtHrZLi//qD7EJo=
|
||||
@@ -43,13 +45,8 @@ github.com/go-text/typesetting-utils v0.0.0-20231211103740-d9332ae51f04 h1:zBx+p
|
||||
github.com/go-text/typesetting-utils v0.0.0-20231211103740-d9332ae51f04/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||
@@ -62,8 +59,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
@@ -81,14 +76,14 @@ github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
|
||||
github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
|
||||
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM=
|
||||
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
|
||||
github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
|
||||
github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
|
||||
github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI=
|
||||
github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY=
|
||||
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
|
||||
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
|
||||
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
|
||||
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
|
||||
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
|
||||
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
|
||||
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
||||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/rs/xid v1.3.0 h1:6NjYksEUlhurdVehpc7S7dk6DAmcKv8V9gG0FsVN2U4=
|
||||
@@ -120,35 +115,33 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
||||
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||
github.com/xtaci/smux v1.5.24 h1:77emW9dtnOxxOQ5ltR+8BbsX1kzcOxQ5gB+aaV9hXOY=
|
||||
github.com/xtaci/smux v1.5.24/go.mod h1:OMlQbT5vcgl2gb49mFkYo6SMf+zP3rcjcwQz7ZU7IGY=
|
||||
github.com/yl2chen/cidranger v1.0.2 h1:lbOWZVCG1tCRX4u24kuM1Tb4nHqWkDxwLdoS+SevawU=
|
||||
github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
|
||||
golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 h1:LoYXNGAShUG3m/ehNk4iFctuhGX/+R1ZpfJ4/ia80JM=
|
||||
golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
|
||||
golang.org/x/exp/shiny v0.0.0-20240103183307-be819d1f06fc h1:OG+uKOKt/BW+ydf/M7gym7ONo8U+dyIlLazys3du298=
|
||||
golang.org/x/exp/shiny v0.0.0-20240103183307-be819d1f06fc/go.mod h1:3F+MieQB7dRYLTmnncoFbb1crS5lfQoTfDgQy6K4N0o=
|
||||
golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4=
|
||||
golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
||||
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc=
|
||||
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
|
||||
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
|
||||
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
|
||||
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
|
||||
@@ -2,6 +2,8 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
_ "net"
|
||||
"os"
|
||||
"time"
|
||||
@@ -16,6 +18,9 @@ import (
|
||||
"github.com/go-gost/gost.plus/tunnel"
|
||||
"github.com/go-gost/gost.plus/tunnel/entrypoint"
|
||||
"github.com/go-gost/gost.plus/ui"
|
||||
"github.com/go-gost/gost.plus/ui/page"
|
||||
"github.com/go-gost/gost.plus/ui/theme"
|
||||
"github.com/go-gost/gost.plus/ui/widget"
|
||||
_ "github.com/go-gost/gost.plus/winres"
|
||||
)
|
||||
|
||||
@@ -23,11 +28,7 @@ func main() {
|
||||
Init()
|
||||
|
||||
go func() {
|
||||
var w app.Window
|
||||
w.Option(app.Title("GOST+"))
|
||||
w.Option(app.MinSize(800, 600))
|
||||
err := run(&w)
|
||||
if err != nil {
|
||||
if err := run(); err != nil {
|
||||
logger.Default().Fatal(err)
|
||||
}
|
||||
os.Exit(0)
|
||||
@@ -35,16 +36,12 @@ func main() {
|
||||
app.Main()
|
||||
}
|
||||
|
||||
func run(w *app.Window) error {
|
||||
go func() {
|
||||
for e := range runner.Event() {
|
||||
if e.TaskID == runner.TaskUpdateStats {
|
||||
w.Invalidate()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
func run() error {
|
||||
ui := ui.NewUI()
|
||||
|
||||
go handleEvent(ui)
|
||||
|
||||
w := ui.Window()
|
||||
var ops op.Ops
|
||||
for {
|
||||
switch e := w.Event().(type) {
|
||||
@@ -60,6 +57,34 @@ func run(w *app.Window) error {
|
||||
}
|
||||
}
|
||||
|
||||
func handleEvent(ui *ui.UI) {
|
||||
for {
|
||||
select {
|
||||
case e := <-ui.Router().Event():
|
||||
switch e.ID {
|
||||
case page.EventThemeChanged:
|
||||
slog.Debug("theme changed", "event", e.ID)
|
||||
ui.Window().Option(app.StatusColor(theme.Current().Material.Bg))
|
||||
}
|
||||
|
||||
case e := <-runner.Event():
|
||||
switch e.TaskID {
|
||||
case runner.TaskUpdateStats:
|
||||
ui.Window().Invalidate()
|
||||
|
||||
default:
|
||||
if e.Err != nil {
|
||||
slog.Error(fmt.Sprintf("task: %s", e.Err), "task", e.TaskID)
|
||||
ui.Router().Notify(widget.Message{
|
||||
Type: widget.Error,
|
||||
Content: e.Err.Error(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Init() {
|
||||
config.Init()
|
||||
tunnel.LoadConfig()
|
||||
|
||||
@@ -4,11 +4,11 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
stats_pkg "github.com/go-gost/core/observer/stats"
|
||||
"github.com/go-gost/gost.plus/config"
|
||||
"github.com/go-gost/gost.plus/runner"
|
||||
"github.com/go-gost/gost.plus/tunnel"
|
||||
"github.com/go-gost/gost.plus/tunnel/entrypoint"
|
||||
stats_pkg "github.com/go-gost/x/stats"
|
||||
)
|
||||
|
||||
type updateStatsTask struct{}
|
||||
|
||||
@@ -12,9 +12,11 @@ import (
|
||||
"github.com/go-gost/core/handler"
|
||||
"github.com/go-gost/core/listener"
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/core/observer/stats"
|
||||
"github.com/go-gost/core/service"
|
||||
cfg "github.com/go-gost/gost.plus/config"
|
||||
"github.com/go-gost/gost.plus/tunnel"
|
||||
xchain "github.com/go-gost/x/chain"
|
||||
"github.com/go-gost/x/config"
|
||||
chain_parser "github.com/go-gost/x/config/parsing/chain"
|
||||
"github.com/go-gost/x/handler/forward/local"
|
||||
@@ -22,7 +24,6 @@ import (
|
||||
"github.com/go-gost/x/listener/tcp"
|
||||
mdx "github.com/go-gost/x/metadata"
|
||||
xservice "github.com/go-gost/x/service"
|
||||
"github.com/go-gost/x/stats"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
@@ -171,12 +172,13 @@ func (s *tcpEntryPoint) Run() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
handlerLogger := log.WithFields(map[string]any{"kind": "handler", "handler": "tcp"})
|
||||
h := local.NewHandler(
|
||||
handler.RouterOption(chain.NewRouter(
|
||||
handler.RouterOption(xchain.NewRouter(
|
||||
chain.ChainRouterOption(ch),
|
||||
chain.LoggerRouterOption(log),
|
||||
chain.LoggerRouterOption(handlerLogger),
|
||||
)),
|
||||
handler.LoggerOption(log.WithFields(map[string]any{"kind": "handler", "handler": "tcp"})),
|
||||
handler.LoggerOption(handlerLogger),
|
||||
)
|
||||
if err = h.Init(mdx.NewMetadata(cfg.Handler.Metadata)); err != nil {
|
||||
return
|
||||
|
||||
@@ -12,9 +12,11 @@ import (
|
||||
"github.com/go-gost/core/handler"
|
||||
"github.com/go-gost/core/listener"
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/core/observer/stats"
|
||||
"github.com/go-gost/core/service"
|
||||
cfg "github.com/go-gost/gost.plus/config"
|
||||
"github.com/go-gost/gost.plus/tunnel"
|
||||
xchain "github.com/go-gost/x/chain"
|
||||
"github.com/go-gost/x/config"
|
||||
chain_parser "github.com/go-gost/x/config/parsing/chain"
|
||||
"github.com/go-gost/x/handler/forward/local"
|
||||
@@ -22,7 +24,6 @@ import (
|
||||
"github.com/go-gost/x/listener/udp"
|
||||
mdx "github.com/go-gost/x/metadata"
|
||||
xservice "github.com/go-gost/x/service"
|
||||
"github.com/go-gost/x/stats"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
@@ -175,12 +176,13 @@ func (s *udpEntryPoint) Run() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
handlerLogger := log.WithFields(map[string]any{"kind": "handler", "handler": "udp"})
|
||||
h := local.NewHandler(
|
||||
handler.RouterOption(chain.NewRouter(
|
||||
handler.RouterOption(xchain.NewRouter(
|
||||
chain.ChainRouterOption(ch),
|
||||
chain.LoggerRouterOption(log),
|
||||
chain.LoggerRouterOption(handlerLogger),
|
||||
)),
|
||||
handler.LoggerOption(log.WithFields(map[string]any{"kind": "handler", "handler": "udp"})),
|
||||
handler.LoggerOption(handlerLogger),
|
||||
)
|
||||
if err = h.Init(mdx.NewMetadata(cfg.Handler.Metadata)); err != nil {
|
||||
return
|
||||
|
||||
@@ -14,9 +14,11 @@ import (
|
||||
"github.com/go-gost/core/handler"
|
||||
"github.com/go-gost/core/listener"
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/core/observer/stats"
|
||||
"github.com/go-gost/core/service"
|
||||
cfg "github.com/go-gost/gost.plus/config"
|
||||
xauth "github.com/go-gost/x/auth"
|
||||
xchain "github.com/go-gost/x/chain"
|
||||
"github.com/go-gost/x/config"
|
||||
chain_parser "github.com/go-gost/x/config/parsing/chain"
|
||||
"github.com/go-gost/x/handler/file"
|
||||
@@ -26,7 +28,6 @@ import (
|
||||
"github.com/go-gost/x/listener/tcp"
|
||||
mdx "github.com/go-gost/x/metadata"
|
||||
xservice "github.com/go-gost/x/service"
|
||||
"github.com/go-gost/x/stats"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
@@ -207,19 +208,22 @@ func (s *fileTunnel) Run() (err error) {
|
||||
pStats.Add(stats.KindTotalErrs, int64(s.stats.TotalErrs))
|
||||
}
|
||||
|
||||
listenerLogger := log.WithFields(map[string]any{"kind": "listener", "listener": "rtcp"})
|
||||
cfg := s.config.Services[1]
|
||||
ln := rtcp.NewListener(
|
||||
listener.AddrOption(cfg.Addr),
|
||||
listener.ChainOption(ch),
|
||||
listener.LoggerOption(log.WithFields(map[string]any{"kind": "listener", "listener": "rtcp"})),
|
||||
listener.RouterOption(xchain.NewRouter(chain.ChainRouterOption(ch), chain.LoggerRouterOption(listenerLogger))),
|
||||
listener.LoggerOption(listenerLogger),
|
||||
listener.StatsOption(pStats),
|
||||
)
|
||||
if err = ln.Init(mdx.NewMetadata(cfg.Listener.Metadata)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
handlerLogger := log.WithFields(map[string]any{"kind": "handler", "handler": "rtcp"})
|
||||
h := remote.NewHandler(
|
||||
handler.LoggerOption(log.WithFields(map[string]any{"kind": "handler", "handler": "rtcp"})),
|
||||
handler.RouterOption(xchain.NewRouter(chain.LoggerRouterOption(handlerLogger))),
|
||||
handler.LoggerOption(handlerLogger),
|
||||
)
|
||||
if err = h.Init(mdx.NewMetadata(cfg.Handler.Metadata)); err != nil {
|
||||
return
|
||||
|
||||
@@ -12,9 +12,11 @@ import (
|
||||
"github.com/go-gost/core/handler"
|
||||
"github.com/go-gost/core/listener"
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/core/observer/stats"
|
||||
"github.com/go-gost/core/service"
|
||||
cfg "github.com/go-gost/gost.plus/config"
|
||||
xauth "github.com/go-gost/x/auth"
|
||||
xchain "github.com/go-gost/x/chain"
|
||||
"github.com/go-gost/x/config"
|
||||
chain_parser "github.com/go-gost/x/config/parsing/chain"
|
||||
"github.com/go-gost/x/handler/forward/remote"
|
||||
@@ -22,7 +24,6 @@ import (
|
||||
"github.com/go-gost/x/listener/rtcp"
|
||||
mdx "github.com/go-gost/x/metadata"
|
||||
xservice "github.com/go-gost/x/service"
|
||||
"github.com/go-gost/x/stats"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
@@ -109,17 +110,16 @@ func (s *httpTunnel) init() error {
|
||||
node := &config.ForwardNodeConfig{
|
||||
Name: s.opts.Name,
|
||||
Addr: s.opts.Endpoint,
|
||||
HTTP: &config.HTTPNodeConfig{},
|
||||
}
|
||||
if s.opts.Username != "" {
|
||||
node.Auth = &config.AuthConfig{
|
||||
node.HTTP.Auth = &config.AuthConfig{
|
||||
Username: s.opts.Username,
|
||||
Password: s.opts.Password,
|
||||
}
|
||||
}
|
||||
if s.opts.Hostname != "" {
|
||||
node.HTTP = &config.HTTPNodeConfig{
|
||||
Host: s.opts.Hostname,
|
||||
}
|
||||
node.HTTP.Host = s.opts.Hostname
|
||||
}
|
||||
if s.opts.EnableTLS {
|
||||
node.TLS = &config.TLSNodeConfig{}
|
||||
@@ -176,21 +176,23 @@ func (s *httpTunnel) Run() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
listenerLogger := log.WithFields(map[string]any{"kind": "listener", "listener": "rtcp"})
|
||||
stats := &stats.Stats{}
|
||||
|
||||
cfg := s.config.Services[0]
|
||||
ln := rtcp.NewListener(
|
||||
listener.AddrOption(cfg.Addr),
|
||||
listener.ChainOption(ch),
|
||||
listener.LoggerOption(log.WithFields(map[string]any{"kind": "listener", "listener": "rtcp"})),
|
||||
listener.RouterOption(xchain.NewRouter(chain.ChainRouterOption(ch), chain.LoggerRouterOption(listenerLogger))),
|
||||
listener.LoggerOption(listenerLogger),
|
||||
listener.StatsOption(stats),
|
||||
)
|
||||
if err = ln.Init(mdx.NewMetadata(cfg.Listener.Metadata)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
handlerLogger := log.WithFields(map[string]any{"kind": "handler", "handler": "rtcp"})
|
||||
h := remote.NewHandler(
|
||||
handler.LoggerOption(log.WithFields(map[string]any{"kind": "handler", "handler": "rtcp"})),
|
||||
handler.RouterOption(xchain.NewRouter(chain.LoggerRouterOption(handlerLogger))),
|
||||
handler.LoggerOption(handlerLogger),
|
||||
)
|
||||
if err = h.Init(mdx.NewMetadata(cfg.Handler.Metadata)); err != nil {
|
||||
return
|
||||
|
||||
@@ -12,8 +12,10 @@ import (
|
||||
"github.com/go-gost/core/handler"
|
||||
"github.com/go-gost/core/listener"
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/core/observer/stats"
|
||||
"github.com/go-gost/core/service"
|
||||
cfg "github.com/go-gost/gost.plus/config"
|
||||
xchain "github.com/go-gost/x/chain"
|
||||
"github.com/go-gost/x/config"
|
||||
chain_parser "github.com/go-gost/x/config/parsing/chain"
|
||||
"github.com/go-gost/x/handler/forward/remote"
|
||||
@@ -21,7 +23,6 @@ import (
|
||||
"github.com/go-gost/x/listener/rtcp"
|
||||
mdx "github.com/go-gost/x/metadata"
|
||||
xservice "github.com/go-gost/x/service"
|
||||
"github.com/go-gost/x/stats"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
@@ -158,21 +159,23 @@ func (s *tcpTunnel) Run() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
listenerLogger := log.WithFields(map[string]any{"kind": "listener", "listener": "rtcp"})
|
||||
stats := &stats.Stats{}
|
||||
|
||||
cfg := s.config.Services[0]
|
||||
ln := rtcp.NewListener(
|
||||
listener.AddrOption(cfg.Addr),
|
||||
listener.ChainOption(ch),
|
||||
listener.LoggerOption(log.WithFields(map[string]any{"kind": "listener", "listener": "rtcp"})),
|
||||
listener.RouterOption(xchain.NewRouter(chain.ChainRouterOption(ch), chain.LoggerRouterOption(listenerLogger))),
|
||||
listener.LoggerOption(listenerLogger),
|
||||
listener.StatsOption(stats),
|
||||
)
|
||||
if err = ln.Init(mdx.NewMetadata(cfg.Listener.Metadata)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
handlerLogger := log.WithFields(map[string]any{"kind": "handler", "handler": "rtcp"})
|
||||
h := remote.NewHandler(
|
||||
handler.LoggerOption(log.WithFields(map[string]any{"kind": "handler", "handler": "rtcp"})),
|
||||
handler.RouterOption(xchain.NewRouter(chain.LoggerRouterOption(handlerLogger))),
|
||||
handler.LoggerOption(handlerLogger),
|
||||
)
|
||||
if err = h.Init(mdx.NewMetadata(cfg.Handler.Metadata)); err != nil {
|
||||
return
|
||||
|
||||
@@ -12,8 +12,10 @@ import (
|
||||
"github.com/go-gost/core/handler"
|
||||
"github.com/go-gost/core/listener"
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/core/observer/stats"
|
||||
"github.com/go-gost/core/service"
|
||||
cfg "github.com/go-gost/gost.plus/config"
|
||||
xchain "github.com/go-gost/x/chain"
|
||||
"github.com/go-gost/x/config"
|
||||
chain_parser "github.com/go-gost/x/config/parsing/chain"
|
||||
"github.com/go-gost/x/handler/forward/remote"
|
||||
@@ -21,7 +23,6 @@ import (
|
||||
"github.com/go-gost/x/listener/rudp"
|
||||
mdx "github.com/go-gost/x/metadata"
|
||||
xservice "github.com/go-gost/x/service"
|
||||
"github.com/go-gost/x/stats"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
@@ -158,21 +159,23 @@ func (s *udpTunnel) Run() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
listenerLogger := log.WithFields(map[string]any{"kind": "listener", "listener": "rudp"})
|
||||
stats := &stats.Stats{}
|
||||
|
||||
cfg := s.config.Services[0]
|
||||
ln := rudp.NewListener(
|
||||
listener.AddrOption(cfg.Addr),
|
||||
listener.ChainOption(ch),
|
||||
listener.LoggerOption(log.WithFields(map[string]any{"kind": "listener", "listener": "rudp"})),
|
||||
listener.RouterOption(xchain.NewRouter(chain.ChainRouterOption(ch), chain.LoggerRouterOption(listenerLogger))),
|
||||
listener.LoggerOption(listenerLogger),
|
||||
listener.StatsOption(stats),
|
||||
)
|
||||
if err = ln.Init(mdx.NewMetadata(cfg.Listener.Metadata)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
handlerLogger := log.WithFields(map[string]any{"kind": "handler", "handler": "rudp"})
|
||||
h := remote.NewHandler(
|
||||
handler.LoggerOption(log.WithFields(map[string]any{"kind": "handler", "handler": "rudp"})),
|
||||
handler.RouterOption(xchain.NewRouter(chain.LoggerRouterOption(handlerLogger))),
|
||||
handler.LoggerOption(handlerLogger),
|
||||
)
|
||||
if err = h.Init(mdx.NewMetadata(cfg.Handler.Metadata)); err != nil {
|
||||
return
|
||||
|
||||
@@ -8,19 +8,28 @@ import (
|
||||
"gioui.org/font/opentype"
|
||||
)
|
||||
|
||||
//go:embed NotoSansSC-Regular.ttf
|
||||
var fontNotoRegular []byte
|
||||
var (
|
||||
|
||||
//go:embed NotoSansSC-Bold.ttf
|
||||
var fontNotoBold []byte
|
||||
//go:embed NotoSans-Regular.ttf
|
||||
notoSansRegular []byte
|
||||
//go:embed NotoSans-SemiBold.ttf
|
||||
notoSansSemiBold []byte
|
||||
|
||||
//go:embed NotoSansSC-Regular.ttf
|
||||
notoSansSCRegular []byte
|
||||
//go:embed NotoSansSC-SemiBold.ttf
|
||||
notoSansSCSemiBold []byte
|
||||
)
|
||||
|
||||
var (
|
||||
collection []font.FontFace
|
||||
)
|
||||
|
||||
func init() {
|
||||
register(fontNotoRegular)
|
||||
register(fontNotoBold)
|
||||
register(notoSansRegular)
|
||||
register(notoSansSemiBold)
|
||||
register(notoSansSCRegular)
|
||||
register(notoSansSCSemiBold)
|
||||
}
|
||||
|
||||
func Collection() []font.FontFace {
|
||||
@@ -32,5 +41,5 @@ func register(ttf []byte) {
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to parse font: %v", err))
|
||||
}
|
||||
collection = append(collection, faces[0])
|
||||
collection = append(collection, faces...)
|
||||
}
|
||||
|
||||
@@ -22,11 +22,6 @@ var en_US = map[Key]string{
|
||||
UDPTunnelDesc: "Expose local UDP service to public network",
|
||||
TCPEntrypointDesc: "Create an entrypoint to the specified TCP tunnel",
|
||||
UDPEntrypointDesc: "Create an entrypoint to the specified UDP tunnel",
|
||||
Settings: "Settings",
|
||||
Language: "Language",
|
||||
Theme: "Theme",
|
||||
Light: "Light",
|
||||
Dark: "Dark",
|
||||
OK: "OK",
|
||||
Cancel: "Cancel",
|
||||
DeleteTunnel: "Delete tunnel?",
|
||||
@@ -36,6 +31,12 @@ var en_US = map[Key]string{
|
||||
ErrDigitOnly: "Must contain only digits",
|
||||
ErrDirectory: "is not a directory",
|
||||
|
||||
English: "English",
|
||||
Chinese: "Chinese",
|
||||
English: "English",
|
||||
Chinese: "Chinese",
|
||||
Settings: "Settings",
|
||||
Language: "Language",
|
||||
Theme: "Theme",
|
||||
ThemeLight: "Light",
|
||||
ThemeDark: "Dark",
|
||||
ThemeSystem: "System",
|
||||
}
|
||||
|
||||
@@ -72,11 +72,6 @@ const (
|
||||
UDPTunnelDesc Key = "udpTunnelDesc"
|
||||
TCPEntrypointDesc Key = "tcpEntrypointDesc"
|
||||
UDPEntrypointDesc Key = "udpEntrypointDesc"
|
||||
Settings Key = "settings"
|
||||
Language Key = "language"
|
||||
Theme Key = "theme"
|
||||
Light Key = "light"
|
||||
Dark Key = "dark"
|
||||
OK Key = "ok"
|
||||
Cancel Key = "cancel"
|
||||
DeleteTunnel Key = "deleteTunnel"
|
||||
@@ -86,8 +81,14 @@ const (
|
||||
ErrDigitOnly Key = "errDigitOnly"
|
||||
ErrDirectory Key = "errDir"
|
||||
|
||||
English Key = "english"
|
||||
Chinese Key = "chinese"
|
||||
Settings Key = "settings"
|
||||
Language Key = "language"
|
||||
English Key = "english"
|
||||
Chinese Key = "chinese"
|
||||
Theme Key = "theme"
|
||||
ThemeLight Key = "themeLight"
|
||||
ThemeDark Key = "themeDark"
|
||||
ThemeSystem Key = "themeSystem"
|
||||
)
|
||||
|
||||
type Key string
|
||||
|
||||
@@ -22,11 +22,6 @@ var zh_CN = map[Key]string{
|
||||
UDPTunnelDesc: "将本地的一个UDP服务暴露到公网",
|
||||
TCPEntrypointDesc: "创建一个指定TCP隧道的入口点",
|
||||
UDPEntrypointDesc: "创建一个指定UDP隧道的入口点",
|
||||
Settings: "设置",
|
||||
Language: "语言",
|
||||
Theme: "主题",
|
||||
Light: "浅色",
|
||||
Dark: "深色",
|
||||
OK: "确认",
|
||||
Cancel: "取消",
|
||||
DeleteTunnel: "删除隧道?",
|
||||
@@ -36,6 +31,12 @@ var zh_CN = map[Key]string{
|
||||
ErrDigitOnly: "仅能输入数字",
|
||||
ErrDirectory: "不是一个目录",
|
||||
|
||||
English: "英语",
|
||||
Chinese: "中文",
|
||||
Settings: "设置",
|
||||
Language: "语言",
|
||||
Theme: "主题",
|
||||
English: "英语",
|
||||
Chinese: "中文",
|
||||
ThemeLight: "浅色",
|
||||
ThemeDark: "深色",
|
||||
ThemeSystem: "系统",
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ var (
|
||||
IconActionUpdate = mustIcon(icons.ActionUpdate)
|
||||
IconActionHourGlassEmpty = mustIcon(icons.ActionHourglassEmpty)
|
||||
IconInfo = mustIcon(icons.ActionInfo)
|
||||
IconAlert = mustIcon(icons.AlertErrorOutline)
|
||||
)
|
||||
|
||||
func mustIcon(data []byte) *widget.Icon {
|
||||
|
||||
@@ -342,6 +342,10 @@ func (p *tcpPage) create() error {
|
||||
|
||||
if err := ep.Run(); err != nil {
|
||||
ep.Close()
|
||||
p.router.Notify(ui_widget.Message{
|
||||
Type: ui_widget.Error,
|
||||
Content: err.Error(),
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -368,6 +372,10 @@ func (p *tcpPage) update(opts ...tunnel.Option) tunnel.Tunnel {
|
||||
|
||||
if err := ep.Run(); err != nil {
|
||||
ep.Close()
|
||||
p.router.Notify(ui_widget.Message{
|
||||
Type: ui_widget.Error,
|
||||
Content: err.Error(),
|
||||
})
|
||||
logger.Default().Error(err)
|
||||
}
|
||||
|
||||
|
||||
@@ -399,6 +399,10 @@ func (p *udpPage) create() error {
|
||||
|
||||
if err := ep.Run(); err != nil {
|
||||
ep.Close()
|
||||
p.router.Notify(ui_widget.Message{
|
||||
Type: ui_widget.Error,
|
||||
Content: err.Error(),
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -429,6 +433,10 @@ func (p *udpPage) update(opts ...tunnel.Option) tunnel.Tunnel {
|
||||
|
||||
if err := ep.Run(); err != nil {
|
||||
ep.Close()
|
||||
p.router.Notify(ui_widget.Message{
|
||||
Type: ui_widget.Error,
|
||||
Content: err.Error(),
|
||||
})
|
||||
logger.Default().Error(err)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package page
|
||||
|
||||
type EventID string
|
||||
|
||||
const (
|
||||
EventThemeChanged EventID = "event.theme.changed"
|
||||
)
|
||||
|
||||
type Event struct {
|
||||
ID EventID
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"image/color"
|
||||
"sync/atomic"
|
||||
|
||||
"gioui.org/font"
|
||||
"gioui.org/layout"
|
||||
"gioui.org/widget"
|
||||
"gioui.org/widget/material"
|
||||
@@ -90,14 +89,7 @@ func (p *homePage) Layout(gtx C) D {
|
||||
gtx.Constraints.Max.X = gtx.Dp(50)
|
||||
return icons.IconApp.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Width: 8}.Layout),
|
||||
layout.Flexed(1, func(gtx C) D {
|
||||
label := material.H6(th, "GOST+")
|
||||
label.Font.Weight = font.SemiBold
|
||||
return label.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Width: 8}.Layout),
|
||||
|
||||
layout.Flexed(1, layout.Spacer{Width: 8}.Layout),
|
||||
layout.Rigid(func(gtx C) D {
|
||||
if p.btnFavorite.Clicked(gtx) {
|
||||
p.favorite.Store(!p.favorite.Load())
|
||||
|
||||
@@ -2,6 +2,7 @@ package page
|
||||
|
||||
import (
|
||||
"gioui.org/layout"
|
||||
"gioui.org/widget/material"
|
||||
)
|
||||
|
||||
type PagePath string
|
||||
@@ -27,6 +28,10 @@ type PageOptions struct {
|
||||
|
||||
type PageOption func(opts *PageOptions)
|
||||
|
||||
type C = layout.Context
|
||||
type D = layout.Dimensions
|
||||
type T = material.Theme
|
||||
|
||||
func WithPageID(id string) PageOption {
|
||||
return func(opts *PageOptions) {
|
||||
opts.ID = id
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
package page
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gioui.org/app"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/layout"
|
||||
"gioui.org/op/clip"
|
||||
"gioui.org/op/paint"
|
||||
@@ -10,33 +15,39 @@ import (
|
||||
"gioui.org/x/component"
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/gost.plus/ui/theme"
|
||||
ui_widget "github.com/go-gost/gost.plus/ui/widget"
|
||||
)
|
||||
|
||||
const (
|
||||
MaxWidth = 800
|
||||
)
|
||||
|
||||
type C = layout.Context
|
||||
type D = layout.Dimensions
|
||||
|
||||
type Route struct {
|
||||
Path PagePath
|
||||
ID string
|
||||
}
|
||||
|
||||
type Router struct {
|
||||
w *app.Window
|
||||
pages map[PagePath]Page
|
||||
stack routeStack
|
||||
current Route
|
||||
*material.Theme
|
||||
modal *component.ModalLayer
|
||||
modal *component.ModalLayer
|
||||
notification *ui_widget.Notification
|
||||
events chan Event
|
||||
}
|
||||
|
||||
func NewRouter(th *material.Theme) *Router {
|
||||
func NewRouter(w *app.Window, th *T) *Router {
|
||||
r := &Router{
|
||||
w: w,
|
||||
pages: make(map[PagePath]Page),
|
||||
Theme: th,
|
||||
modal: component.NewModal(),
|
||||
notification: ui_widget.NewNotification(3*time.Second, func() {
|
||||
w.Invalidate()
|
||||
}),
|
||||
events: make(chan Event, 16),
|
||||
}
|
||||
|
||||
return r
|
||||
@@ -72,7 +83,6 @@ func (r *Router) Back() {
|
||||
}
|
||||
r.current = route
|
||||
|
||||
page.Init(WithPageID(route.ID))
|
||||
logger.Default().WithFields(map[string]any{
|
||||
"kind": "router",
|
||||
"route.id": route.ID,
|
||||
@@ -80,6 +90,25 @@ func (r *Router) Back() {
|
||||
}
|
||||
|
||||
func (r *Router) Layout(gtx C) D {
|
||||
if r.stack.Peek().Path != PageHome {
|
||||
event.Op(gtx.Ops, r.w)
|
||||
for {
|
||||
ev, ok := gtx.Event(
|
||||
key.Filter{Name: key.NameBack},
|
||||
key.Filter{Name: key.NameEscape},
|
||||
)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
switch ev := ev.(type) {
|
||||
case key.Event:
|
||||
if ev.State == key.Press {
|
||||
r.Back()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
r.Theme.Palette = theme.Current().Material
|
||||
|
||||
defer r.modal.Layout(gtx, r.Theme)
|
||||
@@ -113,7 +142,21 @@ func (r *Router) Layout(gtx C) D {
|
||||
}
|
||||
|
||||
return inset.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return page.Layout(gtx)
|
||||
return layout.Stack{
|
||||
Alignment: layout.N,
|
||||
}.Layout(gtx,
|
||||
layout.Expanded(page.Layout),
|
||||
layout.Stacked(func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Inset{
|
||||
Top: 16,
|
||||
Bottom: 16,
|
||||
Right: gtx.Metric.PxToDp(gtx.Constraints.Max.X) / 5,
|
||||
Left: gtx.Metric.PxToDp(gtx.Constraints.Max.X) / 5,
|
||||
}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return r.notification.Layout(gtx, r.Theme)
|
||||
})
|
||||
}),
|
||||
)
|
||||
})
|
||||
},
|
||||
)
|
||||
@@ -124,7 +167,7 @@ func (r *Router) ShowModal(gtx layout.Context, w func(gtx C, th *material.Theme)
|
||||
if gtx.Constraints.Max.X > gtx.Dp(MaxWidth) {
|
||||
gtx.Constraints.Max.X = gtx.Dp(MaxWidth)
|
||||
}
|
||||
gtx.Constraints.Max.X = gtx.Constraints.Max.X * 2 / 3
|
||||
gtx.Constraints.Max.X = gtx.Constraints.Max.X * 3 / 4
|
||||
|
||||
var clk widget.Clickable
|
||||
return clk.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
@@ -138,6 +181,21 @@ func (r *Router) HideModal(gtx C) {
|
||||
r.modal.Disappear(gtx.Now)
|
||||
}
|
||||
|
||||
func (r *Router) Notify(message ui_widget.Message) {
|
||||
r.notification.Show(message)
|
||||
}
|
||||
|
||||
func (r *Router) Emit(event Event) {
|
||||
select {
|
||||
case r.events <- event:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Router) Event() <-chan Event {
|
||||
return r.events
|
||||
}
|
||||
|
||||
type routeStack struct {
|
||||
routes []Route
|
||||
}
|
||||
|
||||
@@ -29,11 +29,6 @@ type settingsPage struct {
|
||||
func NewPage(r *page.Router) page.Page {
|
||||
return &settingsPage{
|
||||
router: r,
|
||||
menu: ui_widget.Menu{
|
||||
List: layout.List{
|
||||
Axis: layout.Vertical,
|
||||
},
|
||||
},
|
||||
list: widget.List{
|
||||
List: layout.List{
|
||||
Axis: layout.Vertical,
|
||||
@@ -52,27 +47,30 @@ func (p *settingsPage) Init(opts ...page.PageOption) {
|
||||
if settings.Lang == "" {
|
||||
settings.Lang = i18n.Current().Value
|
||||
}
|
||||
if settings.Theme == "" {
|
||||
settings.Theme = theme.Light
|
||||
}
|
||||
|
||||
p.lang.Clear()
|
||||
p.lang.Select(ui_widget.SelectorItem{
|
||||
Name: i18n.Current().Name,
|
||||
Key: i18n.Current().Name,
|
||||
Value: i18n.Current().Value,
|
||||
})
|
||||
|
||||
p.theme.Clear()
|
||||
if settings.Theme == theme.Light {
|
||||
switch settings.Theme {
|
||||
case theme.Light:
|
||||
p.theme.Select(ui_widget.SelectorItem{
|
||||
Name: i18n.Light,
|
||||
Key: i18n.ThemeLight,
|
||||
Value: settings.Theme,
|
||||
})
|
||||
} else {
|
||||
case theme.Dark:
|
||||
p.theme.Select(ui_widget.SelectorItem{
|
||||
Name: i18n.Dark,
|
||||
Key: i18n.ThemeDark,
|
||||
Value: settings.Theme,
|
||||
})
|
||||
default:
|
||||
p.theme.Select(ui_widget.SelectorItem{
|
||||
Key: i18n.ThemeSystem,
|
||||
Value: theme.System,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,81 +126,105 @@ func (p *settingsPage) Layout(gtx layout.Context) layout.Dimensions {
|
||||
}
|
||||
|
||||
func (p *settingsPage) layout(gtx layout.Context, th *material.Theme) layout.Dimensions {
|
||||
return component.SurfaceStyle{
|
||||
Theme: th,
|
||||
ShadowStyle: component.ShadowStyle{
|
||||
CornerRadius: 12,
|
||||
},
|
||||
Fill: theme.Current().ContentSurfaceBg,
|
||||
}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.UniformInset(16).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{
|
||||
Axis: layout.Vertical,
|
||||
Alignment: layout.Middle,
|
||||
}.Layout(gtx,
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{
|
||||
Axis: layout.Vertical,
|
||||
Alignment: layout.Middle,
|
||||
}.Layout(gtx,
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Center.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
gtx.Constraints.Max.X = gtx.Dp(80)
|
||||
return icons.IconApp.Layout(gtx)
|
||||
})
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: 8}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
label := material.H6(th, "GOST+")
|
||||
label.Font.Weight = font.Bold
|
||||
return label.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: 8}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return material.Body1(th, version.Version).Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: 16}.Layout),
|
||||
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if p.lang.Clicked(gtx) {
|
||||
p.showLangMenu(gtx)
|
||||
}
|
||||
return p.lang.Layout(gtx, th)
|
||||
label := material.Body1(th, "GOST+")
|
||||
label.Font.Weight = font.SemiBold
|
||||
return layout.Center.Layout(gtx, label.Layout)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: 8}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if p.theme.Clicked(gtx) {
|
||||
p.showThemeMenu(gtx)
|
||||
}
|
||||
return p.theme.Layout(gtx, th)
|
||||
return layout.Center.Layout(gtx, material.Body1(th, version.Version).Layout)
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: 32}.Layout),
|
||||
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return component.SurfaceStyle{
|
||||
Theme: th,
|
||||
ShadowStyle: component.ShadowStyle{
|
||||
CornerRadius: 12,
|
||||
},
|
||||
Fill: theme.Current().ContentSurfaceBg,
|
||||
}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.UniformInset(16).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{
|
||||
Axis: layout.Vertical,
|
||||
}.Layout(gtx,
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if p.lang.Clicked(gtx) {
|
||||
p.showLangMenu(gtx)
|
||||
}
|
||||
return p.lang.Layout(gtx, th)
|
||||
}),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if p.theme.Clicked(gtx) {
|
||||
p.showThemeMenu(gtx)
|
||||
}
|
||||
return p.theme.Layout(gtx, th)
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
func (p *settingsPage) showLangMenu(gtx layout.Context) {
|
||||
var items []ui_widget.MenuItem
|
||||
var options []ui_widget.MenuOption
|
||||
for _, lang := range i18n.Langs() {
|
||||
items = append(items, ui_widget.MenuItem{
|
||||
options = append(options, ui_widget.MenuOption{
|
||||
Key: lang.Name,
|
||||
Value: lang.Value,
|
||||
})
|
||||
}
|
||||
|
||||
var found bool
|
||||
for i := range items {
|
||||
if found = p.lang.Any(items[i].Key.Value()); found {
|
||||
items[i].Selected = found
|
||||
for i := range options {
|
||||
if found = p.lang.AnyValue(options[i].Value); found {
|
||||
options[i].Selected = found
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
items[0].Selected = true
|
||||
options[0].Selected = true
|
||||
}
|
||||
|
||||
p.menu.Title = i18n.Language
|
||||
p.menu.Items = items
|
||||
p.menu.Selected = func(index int) {
|
||||
p.lang.Clear()
|
||||
p.lang.Select(ui_widget.SelectorItem{
|
||||
Name: p.menu.Items[index].Key,
|
||||
Value: p.menu.Items[index].Value,
|
||||
})
|
||||
p.menu.Options = options
|
||||
p.menu.OnClick = func(ok bool) {
|
||||
p.router.HideModal(gtx)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
p.lang.Clear()
|
||||
|
||||
for index := range p.menu.Options {
|
||||
if p.menu.Options[index].Selected {
|
||||
p.lang.Select(ui_widget.SelectorItem{
|
||||
Key: p.menu.Options[index].Key,
|
||||
Value: p.menu.Options[index].Value,
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
cfg := config.Get()
|
||||
if cfg.Settings == nil {
|
||||
@@ -222,31 +244,42 @@ func (p *settingsPage) showLangMenu(gtx layout.Context) {
|
||||
}
|
||||
|
||||
func (p *settingsPage) showThemeMenu(gtx layout.Context) {
|
||||
items := []ui_widget.MenuItem{
|
||||
{Key: i18n.Light, Value: theme.Light},
|
||||
{Key: i18n.Dark, Value: theme.Dark},
|
||||
options := []ui_widget.MenuOption{
|
||||
{Key: i18n.ThemeSystem, Value: theme.System},
|
||||
{Key: i18n.ThemeLight, Value: theme.Light},
|
||||
{Key: i18n.ThemeDark, Value: theme.Dark},
|
||||
}
|
||||
|
||||
var found bool
|
||||
for i := range items {
|
||||
if found = p.theme.Any(items[i].Key.Value()); found {
|
||||
items[i].Selected = found
|
||||
for i := range options {
|
||||
if found = p.theme.AnyValue(options[i].Value); found {
|
||||
options[i].Selected = found
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
items[0].Selected = true
|
||||
options[0].Selected = true
|
||||
}
|
||||
|
||||
p.menu.Title = i18n.Theme
|
||||
p.menu.Items = items
|
||||
p.menu.Selected = func(index int) {
|
||||
p.theme.Clear()
|
||||
p.theme.Select(ui_widget.SelectorItem{
|
||||
Name: p.menu.Items[index].Key,
|
||||
Value: p.menu.Items[index].Value,
|
||||
})
|
||||
p.menu.Options = options
|
||||
p.menu.OnClick = func(ok bool) {
|
||||
p.router.HideModal(gtx)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
p.theme.Clear()
|
||||
|
||||
for index := range p.menu.Options {
|
||||
if p.menu.Options[index].Selected {
|
||||
p.theme.Select(ui_widget.SelectorItem{
|
||||
Key: p.menu.Options[index].Key,
|
||||
Value: p.menu.Options[index].Value,
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
cfg := config.Get()
|
||||
if cfg.Settings == nil {
|
||||
@@ -263,6 +296,7 @@ func (p *settingsPage) showThemeMenu(gtx layout.Context) {
|
||||
default:
|
||||
theme.UseLight()
|
||||
}
|
||||
p.router.Emit(page.Event{ID: page.EventThemeChanged})
|
||||
}
|
||||
|
||||
p.router.ShowModal(gtx, func(gtx page.C, th *material.Theme) page.D {
|
||||
|
||||
@@ -495,6 +495,10 @@ func (p *filePage) create() error {
|
||||
|
||||
if err := tun.Run(); err != nil {
|
||||
tun.Close()
|
||||
p.router.Notify(ui_widget.Message{
|
||||
Type: ui_widget.Error,
|
||||
Content: err.Error(),
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -528,6 +532,10 @@ func (p *filePage) update(opts ...tunnel.Option) tunnel.Tunnel {
|
||||
|
||||
if err := tun.Run(); err != nil {
|
||||
tun.Close()
|
||||
p.router.Notify(ui_widget.Message{
|
||||
Type: ui_widget.Error,
|
||||
Content: err.Error(),
|
||||
})
|
||||
logger.Default().Error(err)
|
||||
}
|
||||
|
||||
|
||||
@@ -553,6 +553,10 @@ func (p *httpPage) create() error {
|
||||
|
||||
if err := tun.Run(); err != nil {
|
||||
tun.Close()
|
||||
p.router.Notify(ui_widget.Message{
|
||||
Type: ui_widget.Error,
|
||||
Content: err.Error(),
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -592,6 +596,10 @@ func (p *httpPage) update(opts ...tunnel.Option) tunnel.Tunnel {
|
||||
|
||||
if err := tun.Run(); err != nil {
|
||||
tun.Close()
|
||||
p.router.Notify(ui_widget.Message{
|
||||
Type: ui_widget.Error,
|
||||
Content: err.Error(),
|
||||
})
|
||||
logger.Default().Error(err)
|
||||
}
|
||||
|
||||
|
||||
@@ -398,6 +398,10 @@ func (p *tcpPage) create() error {
|
||||
|
||||
if err := tun.Run(); err != nil {
|
||||
tun.Close()
|
||||
p.router.Notify(ui_widget.Message{
|
||||
Type: ui_widget.Error,
|
||||
Content: err.Error(),
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -424,6 +428,10 @@ func (p *tcpPage) update(opts ...tunnel.Option) tunnel.Tunnel {
|
||||
|
||||
if err := tun.Run(); err != nil {
|
||||
tun.Close()
|
||||
p.router.Notify(ui_widget.Message{
|
||||
Type: ui_widget.Error,
|
||||
Content: err.Error(),
|
||||
})
|
||||
logger.Default().Error(err)
|
||||
}
|
||||
|
||||
|
||||
@@ -398,6 +398,10 @@ func (p *udpPage) create() error {
|
||||
|
||||
if err := tun.Run(); err != nil {
|
||||
tun.Close()
|
||||
p.router.Notify(ui_widget.Message{
|
||||
Type: ui_widget.Error,
|
||||
Content: err.Error(),
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -424,6 +428,10 @@ func (p *udpPage) update(opts ...tunnel.Option) tunnel.Tunnel {
|
||||
|
||||
if err := tun.Run(); err != nil {
|
||||
tun.Close()
|
||||
p.router.Notify(ui_widget.Message{
|
||||
Type: ui_widget.Error,
|
||||
Content: err.Error(),
|
||||
})
|
||||
logger.Default().Error(err)
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,9 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
Light string = "light"
|
||||
Dark string = "dark"
|
||||
System string = "system"
|
||||
Light string = "light"
|
||||
Dark string = "dark"
|
||||
)
|
||||
|
||||
type Palette struct {
|
||||
@@ -19,14 +20,18 @@ type Palette struct {
|
||||
ListBg color.NRGBA
|
||||
NavButtonBg color.NRGBA
|
||||
NavButtonContrastBg color.NRGBA
|
||||
ItemBg color.NRGBA
|
||||
NotificationBg color.NRGBA
|
||||
}
|
||||
|
||||
type Theme struct {
|
||||
Name string
|
||||
Palette
|
||||
}
|
||||
|
||||
var (
|
||||
light = Theme{
|
||||
Name: Light,
|
||||
Palette: Palette{
|
||||
Material: material.Palette{
|
||||
Fg: color.NRGBA(colornames.Black),
|
||||
@@ -41,12 +46,15 @@ var (
|
||||
},
|
||||
ContentSurfaceBg: color.NRGBA(colornames.Grey50),
|
||||
ListBg: color.NRGBA(colornames.BlueGrey50),
|
||||
NavButtonBg: color.NRGBA(colornames.White),
|
||||
NavButtonBg: color.NRGBA(colornames.BlueGrey50),
|
||||
NavButtonContrastBg: color.NRGBA(colornames.BlueGrey100),
|
||||
ItemBg: color.NRGBA(colornames.Grey300),
|
||||
NotificationBg: color.NRGBA(colornames.Grey200),
|
||||
},
|
||||
}
|
||||
|
||||
dark = Theme{
|
||||
Name: Dark,
|
||||
Palette: Palette{
|
||||
Material: material.Palette{
|
||||
Fg: color.NRGBA(colornames.White),
|
||||
@@ -58,6 +66,8 @@ var (
|
||||
ListBg: color.NRGBA(colornames.Grey700),
|
||||
NavButtonBg: color.NRGBA(colornames.Grey800),
|
||||
NavButtonContrastBg: color.NRGBA(colornames.Grey600),
|
||||
ItemBg: color.NRGBA(colornames.Grey600),
|
||||
NotificationBg: color.NRGBA(colornames.Grey700),
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"gioui.org/app"
|
||||
"gioui.org/layout"
|
||||
"gioui.org/text"
|
||||
"gioui.org/widget/material"
|
||||
gio_theme "gioui.org/x/pref/theme"
|
||||
"github.com/go-gost/gost.plus/config"
|
||||
"github.com/go-gost/gost.plus/ui/fonts"
|
||||
"github.com/go-gost/gost.plus/ui/i18n"
|
||||
@@ -25,6 +27,7 @@ type C = layout.Context
|
||||
type D = layout.Dimensions
|
||||
|
||||
type UI struct {
|
||||
w *app.Window
|
||||
router *page.Router
|
||||
}
|
||||
|
||||
@@ -44,7 +47,14 @@ func NewUI() *UI {
|
||||
th.Shaper = text.NewShaper(text.WithCollection(fonts.Collection()))
|
||||
th.Palette = theme.Current().Material
|
||||
|
||||
router := page.NewRouter(th)
|
||||
w := &app.Window{}
|
||||
w.Option(
|
||||
app.Title("GOST"),
|
||||
app.MinSize(800, 600),
|
||||
app.StatusColor(th.Bg),
|
||||
)
|
||||
|
||||
router := page.NewRouter(w, th)
|
||||
router.Register(page.PageHome, home.NewPage(router))
|
||||
router.Register(page.PageTunnel, tunnel.NewPage(router))
|
||||
router.Register(page.PageTunnelFile, file.NewPage(router))
|
||||
@@ -61,10 +71,32 @@ func NewUI() *UI {
|
||||
})
|
||||
|
||||
return &UI{
|
||||
w: w,
|
||||
router: router,
|
||||
}
|
||||
}
|
||||
|
||||
func (ui *UI) Layout(gtx C) D {
|
||||
if settings := config.Get().Settings; settings != nil {
|
||||
if settings.Theme != theme.Dark && settings.Theme != theme.Light {
|
||||
if dark, _ := gio_theme.IsDarkMode(); dark {
|
||||
if theme.Current().Name == theme.Light {
|
||||
theme.UseDark()
|
||||
}
|
||||
} else {
|
||||
if theme.Current().Name == theme.Dark {
|
||||
theme.UseLight()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ui.router.Layout(gtx)
|
||||
}
|
||||
|
||||
func (ui *UI) Window() *app.Window {
|
||||
return ui.w
|
||||
}
|
||||
|
||||
func (ui *UI) Router() *page.Router {
|
||||
return ui.router
|
||||
}
|
||||
|
||||
@@ -91,14 +91,14 @@ func (p *Dialog) Layout(gtx layout.Context, th *material.Theme) layout.Dimension
|
||||
|
||||
return material.ButtonLayoutStyle{
|
||||
Background: th.Bg,
|
||||
CornerRadius: 20,
|
||||
CornerRadius: 18,
|
||||
Button: &p.btnCancel,
|
||||
}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Inset{
|
||||
Top: 8,
|
||||
Bottom: 8,
|
||||
Left: 24,
|
||||
Right: 24,
|
||||
Left: 20,
|
||||
Right: 20,
|
||||
}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
label := material.Body1(th, i18n.Cancel.Value())
|
||||
label.Color = th.Fg
|
||||
@@ -117,14 +117,14 @@ func (p *Dialog) Layout(gtx layout.Context, th *material.Theme) layout.Dimension
|
||||
|
||||
return material.ButtonLayoutStyle{
|
||||
Background: th.Bg,
|
||||
CornerRadius: 20,
|
||||
CornerRadius: 18,
|
||||
Button: &p.btnOK,
|
||||
}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Inset{
|
||||
Top: 8,
|
||||
Bottom: 8,
|
||||
Left: 24,
|
||||
Right: 24,
|
||||
Left: 20,
|
||||
Right: 20,
|
||||
}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
label := material.Body1(th, i18n.OK.Value())
|
||||
label.Color = th.Fg
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package widget
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"gioui.org/layout"
|
||||
"gioui.org/widget"
|
||||
"gioui.org/widget/material"
|
||||
@@ -10,16 +12,32 @@ import (
|
||||
)
|
||||
|
||||
type Menu struct {
|
||||
List layout.List
|
||||
Items []MenuItem
|
||||
Title i18n.Key
|
||||
Selected func(index int)
|
||||
btnAdd widget.Clickable
|
||||
ShowAdd bool
|
||||
Multiple bool
|
||||
Title i18n.Key
|
||||
Options []MenuOption
|
||||
OnClick func(ok bool)
|
||||
list material.ListStyle
|
||||
btnAdd widget.Clickable
|
||||
OnAdd func()
|
||||
Multiple bool
|
||||
btnCancel widget.Clickable
|
||||
btnOK widget.Clickable
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
func (p *Menu) Layout(gtx layout.Context, th *material.Theme) layout.Dimensions {
|
||||
p.once.Do(func() {
|
||||
p.list = material.List(th, &widget.List{
|
||||
List: layout.List{
|
||||
Axis: layout.Vertical,
|
||||
},
|
||||
})
|
||||
p.list.AnchorStrategy = material.Overlay
|
||||
})
|
||||
|
||||
if p.btnAdd.Clicked(gtx) && p.OnAdd != nil {
|
||||
p.OnAdd()
|
||||
}
|
||||
|
||||
return layout.Center.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Inset{
|
||||
Top: 16,
|
||||
@@ -40,51 +58,117 @@ func (p *Menu) Layout(gtx layout.Context, th *material.Theme) layout.Dimensions
|
||||
}.Layout(gtx,
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Inset{
|
||||
Top: 8,
|
||||
Bottom: 8,
|
||||
Left: 24,
|
||||
Right: 24,
|
||||
Left: 24,
|
||||
Right: 24,
|
||||
}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{
|
||||
Alignment: layout.Middle,
|
||||
}.Layout(gtx,
|
||||
layout.Flexed(1, material.H6(th, p.Title.Value()).Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if !p.ShowAdd {
|
||||
if p.OnAdd == nil {
|
||||
return layout.Dimensions{}
|
||||
}
|
||||
btn := material.IconButton(th, &p.btnAdd, icons.IconAdd, "Add")
|
||||
btn.Background = th.Bg
|
||||
btn.Color = th.Fg
|
||||
btn.Inset = layout.UniformInset(0)
|
||||
// btn.Inset = layout.UniformInset(8)
|
||||
return btn.Layout(gtx)
|
||||
}),
|
||||
)
|
||||
})
|
||||
}),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
gtx.Constraints.Max.Y -= gtx.Dp(80)
|
||||
|
||||
return layout.Inset{
|
||||
Top: 8,
|
||||
Bottom: 8,
|
||||
}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return p.List.Layout(gtx, len(p.Items), func(gtx layout.Context, index int) layout.Dimensions {
|
||||
if p.Items[index].state.Clicked(gtx) {
|
||||
if p.Multiple {
|
||||
p.Items[index].Selected = !p.Items[index].Selected
|
||||
} else {
|
||||
for i := range p.Items {
|
||||
p.Items[i].Selected = false
|
||||
return p.list.Layout(gtx, len(p.Options), func(gtx layout.Context, index int) layout.Dimensions {
|
||||
if p.Options[index].state.Clicked(gtx) {
|
||||
p.Options[index].Selected = !p.Options[index].Selected
|
||||
|
||||
if !p.Multiple {
|
||||
for i := range p.Options {
|
||||
if i != index {
|
||||
p.Options[i].Selected = false
|
||||
}
|
||||
}
|
||||
p.Items[index].Selected = true
|
||||
}
|
||||
if p.Selected != nil {
|
||||
p.Selected(index)
|
||||
}
|
||||
}
|
||||
return p.Items[index].Layout(gtx, th)
|
||||
|
||||
return p.Options[index].Layout(gtx, th)
|
||||
})
|
||||
})
|
||||
}),
|
||||
|
||||
layout.Rigid(layout.Spacer{Height: 8}.Layout),
|
||||
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Inset{
|
||||
Left: 24,
|
||||
Right: 24,
|
||||
}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{
|
||||
Spacing: layout.SpaceBetween,
|
||||
Alignment: layout.Middle,
|
||||
}.Layout(gtx,
|
||||
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Spacer{Width: 8}.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if p.btnCancel.Clicked(gtx) && p.OnClick != nil {
|
||||
p.OnClick(false)
|
||||
}
|
||||
|
||||
return material.ButtonLayoutStyle{
|
||||
Background: th.Bg,
|
||||
CornerRadius: 18,
|
||||
Button: &p.btnCancel,
|
||||
}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Inset{
|
||||
Top: 8,
|
||||
Bottom: 8,
|
||||
Left: 20,
|
||||
Right: 20,
|
||||
}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
label := material.Body1(th, i18n.Cancel.Value())
|
||||
label.Color = th.Fg
|
||||
return label.Layout(gtx)
|
||||
})
|
||||
|
||||
})
|
||||
}),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Spacer{Width: 8}.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if p.btnOK.Clicked(gtx) && p.OnClick != nil {
|
||||
p.OnClick(true)
|
||||
}
|
||||
|
||||
return material.ButtonLayoutStyle{
|
||||
Background: th.Bg,
|
||||
CornerRadius: 18,
|
||||
Button: &p.btnOK,
|
||||
}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Inset{
|
||||
Top: 8,
|
||||
Bottom: 8,
|
||||
Left: 20,
|
||||
Right: 20,
|
||||
}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
label := material.Body1(th, i18n.OK.Value())
|
||||
label.Color = th.Fg
|
||||
return label.Layout(gtx)
|
||||
})
|
||||
|
||||
})
|
||||
}),
|
||||
)
|
||||
})
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -92,29 +176,39 @@ func (p *Menu) Layout(gtx layout.Context, th *material.Theme) layout.Dimensions
|
||||
})
|
||||
}
|
||||
|
||||
type MenuItem struct {
|
||||
type MenuOption struct {
|
||||
state widget.Clickable
|
||||
Key i18n.Key
|
||||
Name string
|
||||
Value string
|
||||
Selected bool
|
||||
}
|
||||
|
||||
func (p *MenuItem) Layout(gtx layout.Context, th *material.Theme) layout.Dimensions {
|
||||
func (p *MenuOption) Layout(gtx layout.Context, th *material.Theme) layout.Dimensions {
|
||||
return material.ButtonLayoutStyle{
|
||||
Background: th.Bg,
|
||||
Button: &p.state,
|
||||
}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Inset{
|
||||
Top: 8,
|
||||
Bottom: 8,
|
||||
Left: 16,
|
||||
Right: 16,
|
||||
Top: 12,
|
||||
Bottom: 12,
|
||||
Left: 24,
|
||||
Right: 24,
|
||||
}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{
|
||||
Spacing: layout.SpaceBetween,
|
||||
Alignment: layout.Middle,
|
||||
}.Layout(gtx,
|
||||
layout.Flexed(1, material.Body2(th, p.Key.Value()).Layout),
|
||||
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
|
||||
name := p.Name
|
||||
if name == "" {
|
||||
name = p.Key.Value()
|
||||
}
|
||||
if name == "" {
|
||||
name = p.Value
|
||||
}
|
||||
return material.Body2(th, name).Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Width: 8}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if p.Selected {
|
||||
|
||||
@@ -45,15 +45,10 @@ func (p *Nav) Layout(gtx layout.Context, th *material.Theme) layout.Dimensions {
|
||||
if p.current == index {
|
||||
btn.background = theme.Current().NavButtonContrastBg
|
||||
} else {
|
||||
btn.background = theme.Current().Material.Bg
|
||||
btn.background = theme.Current().NavButtonBg
|
||||
}
|
||||
|
||||
return layout.Inset{
|
||||
Top: 8,
|
||||
Bottom: 8,
|
||||
Left: 12,
|
||||
Right: 12,
|
||||
}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.UniformInset(8).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return btn.Layout(gtx, th)
|
||||
})
|
||||
})
|
||||
@@ -62,15 +57,13 @@ func (p *Nav) Layout(gtx layout.Context, th *material.Theme) layout.Dimensions {
|
||||
type NavButton struct {
|
||||
btn widget.Clickable
|
||||
cornerRadius unit.Dp
|
||||
borderWidth unit.Dp
|
||||
background color.NRGBA
|
||||
text i18n.Key
|
||||
}
|
||||
|
||||
func NewNavButton(text i18n.Key) *NavButton {
|
||||
return &NavButton{
|
||||
cornerRadius: 18,
|
||||
borderWidth: 1,
|
||||
cornerRadius: 20,
|
||||
text: text,
|
||||
}
|
||||
}
|
||||
@@ -83,14 +76,13 @@ func (btn *NavButton) Layout(gtx layout.Context, th *material.Theme) layout.Dime
|
||||
}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return widget.Border{
|
||||
Color: theme.Current().NavButtonContrastBg,
|
||||
Width: btn.borderWidth,
|
||||
CornerRadius: btn.cornerRadius,
|
||||
}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Inset{
|
||||
Top: 8,
|
||||
Bottom: 8,
|
||||
Left: 20,
|
||||
Right: 20,
|
||||
Left: 16,
|
||||
Right: 16,
|
||||
}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
label := material.Body1(th, btn.text.Value())
|
||||
return label.Layout(gtx)
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
package widget
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"gioui.org/layout"
|
||||
"gioui.org/widget/material"
|
||||
"gioui.org/x/component"
|
||||
"github.com/go-gost/gost.plus/ui/icons"
|
||||
"github.com/go-gost/gost.plus/ui/theme"
|
||||
"golang.org/x/exp/shiny/materialdesign/colornames"
|
||||
)
|
||||
|
||||
const (
|
||||
Success string = "success"
|
||||
Info string = "info"
|
||||
Warn string = "warn"
|
||||
Error string = "error"
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
Type string
|
||||
Content string
|
||||
}
|
||||
|
||||
type Notification struct {
|
||||
show atomic.Bool
|
||||
messages chan Message
|
||||
current Message
|
||||
duration time.Duration
|
||||
callback func()
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func NewNotification(d time.Duration, callback func()) *Notification {
|
||||
p := &Notification{
|
||||
messages: make(chan Message, 16),
|
||||
duration: d,
|
||||
callback: callback,
|
||||
}
|
||||
go p.run()
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Notification) Layout(gtx C, th *T) D {
|
||||
if !p.show.Load() {
|
||||
return D{}
|
||||
}
|
||||
|
||||
p.mu.RLock()
|
||||
message := p.current
|
||||
p.mu.RUnlock()
|
||||
|
||||
return component.SurfaceStyle{
|
||||
Theme: th,
|
||||
ShadowStyle: component.ShadowStyle{
|
||||
CornerRadius: 8,
|
||||
},
|
||||
Fill: theme.Current().NotificationBg,
|
||||
}.Layout(gtx, func(gtx C) D {
|
||||
return layout.Inset{
|
||||
Top: 8,
|
||||
Bottom: 8,
|
||||
Left: 8,
|
||||
Right: 8,
|
||||
}.Layout(gtx, func(gtx C) D {
|
||||
return layout.Flex{
|
||||
Alignment: layout.Middle,
|
||||
}.Layout(gtx,
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
|
||||
switch message.Type {
|
||||
case Success:
|
||||
return icons.IconInfo.Layout(gtx, color.NRGBA(colornames.Green500))
|
||||
case Warn:
|
||||
return icons.IconInfo.Layout(gtx, color.NRGBA(colornames.Orange500))
|
||||
case Error:
|
||||
return icons.IconAlert.Layout(gtx, color.NRGBA(colornames.Red500))
|
||||
case Info:
|
||||
fallthrough
|
||||
default:
|
||||
return icons.IconInfo.Layout(gtx, color.NRGBA(colornames.Blue500))
|
||||
}
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Width: 8}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return material.Body2(th, message.Content).Layout(gtx)
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (p *Notification) Show(message Message) {
|
||||
select {
|
||||
case p.messages <- message:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Notification) run() {
|
||||
for m := range p.messages {
|
||||
p.mu.Lock()
|
||||
p.current = m
|
||||
p.mu.Unlock()
|
||||
|
||||
p.show.Store(true)
|
||||
if p.callback != nil {
|
||||
p.callback()
|
||||
}
|
||||
|
||||
<-time.After(p.duration)
|
||||
|
||||
p.show.Store(false)
|
||||
if p.callback != nil {
|
||||
p.callback()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,19 @@
|
||||
package widget
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"gioui.org/layout"
|
||||
"gioui.org/widget"
|
||||
"gioui.org/widget/material"
|
||||
"gioui.org/x/component"
|
||||
"gioui.org/x/outlay"
|
||||
"github.com/go-gost/gost.plus/ui/i18n"
|
||||
"github.com/go-gost/gost.plus/ui/icons"
|
||||
"github.com/go-gost/gost.plus/ui/theme"
|
||||
)
|
||||
|
||||
type SelectorItem struct {
|
||||
Name i18n.Key
|
||||
Name string
|
||||
Key i18n.Key
|
||||
Value string
|
||||
}
|
||||
|
||||
@@ -24,8 +26,8 @@ type Selector struct {
|
||||
func (p *Selector) Layout(gtx layout.Context, th *material.Theme) layout.Dimensions {
|
||||
return material.Clickable(gtx, &p.clickable, func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Inset{
|
||||
Top: 8,
|
||||
Bottom: 8,
|
||||
Top: 4,
|
||||
Bottom: 4,
|
||||
}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{
|
||||
Alignment: layout.Middle,
|
||||
@@ -36,15 +38,50 @@ func (p *Selector) Layout(gtx layout.Context, th *material.Theme) layout.Dimensi
|
||||
return layout.E.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
var names []string
|
||||
for _, item := range p.items {
|
||||
if item.Name != "" {
|
||||
names = append(names, item.Name.Value())
|
||||
if item.Value == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
name := item.Name
|
||||
if name == "" {
|
||||
name = item.Key.Value()
|
||||
}
|
||||
if name == "" {
|
||||
name = item.Value
|
||||
}
|
||||
names = append(names, name)
|
||||
}
|
||||
return material.Body2(th, strings.Join(names, ",")).Layout(gtx)
|
||||
|
||||
return outlay.FlowWrap{
|
||||
Alignment: layout.Middle,
|
||||
}.Layout(gtx, len(names), func(gtx layout.Context, i int) layout.Dimensions {
|
||||
return layout.UniformInset(4).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return component.SurfaceStyle{
|
||||
Theme: th,
|
||||
ShadowStyle: component.ShadowStyle{CornerRadius: 14},
|
||||
Fill: theme.Current().ItemBg,
|
||||
}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Inset{
|
||||
Top: 4,
|
||||
Bottom: 4,
|
||||
Left: 10,
|
||||
Right: 10,
|
||||
}.Layout(gtx, material.Body2(th, names[i]).Layout)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return icons.IconNavRight.Layout(gtx, th.Fg)
|
||||
if len(p.items) > 0 {
|
||||
return layout.Dimensions{}
|
||||
}
|
||||
return layout.Inset{
|
||||
Top: 4,
|
||||
Bottom: 5,
|
||||
}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return icons.IconNavRight.Layout(gtx, th.Fg)
|
||||
})
|
||||
}),
|
||||
)
|
||||
})
|
||||
@@ -57,31 +94,42 @@ func (p *Selector) Clicked(gtx layout.Context) bool {
|
||||
|
||||
func (p *Selector) Select(items ...SelectorItem) {
|
||||
for _, item := range items {
|
||||
if item.Name == "" {
|
||||
if item.Name == "" && item.Value == "" {
|
||||
continue
|
||||
}
|
||||
p.items = append(p.items, item)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Selector) Unselect(name string) {
|
||||
for i := range p.items {
|
||||
if p.items[i].Name.Value() == name {
|
||||
p.items = append(p.items[:i], p.items[i+1:]...)
|
||||
return
|
||||
func (p *Selector) Any(items ...SelectorItem) bool {
|
||||
for _, item := range items {
|
||||
if p.contains(item) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *Selector) Any(names ...string) bool {
|
||||
for _, name := range names {
|
||||
if p.contains(name) {
|
||||
func (p *Selector) contains(item SelectorItem) bool {
|
||||
for i := range p.items {
|
||||
if p.items[i] == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *Selector) AnyValue(values ...string) bool {
|
||||
for _, value := range values {
|
||||
for i := range p.items {
|
||||
if p.items[i].Value == value {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *Selector) Item() SelectorItem {
|
||||
if len(p.items) == 0 {
|
||||
return SelectorItem{}
|
||||
@@ -93,17 +141,18 @@ func (p *Selector) Items() []SelectorItem {
|
||||
return p.items
|
||||
}
|
||||
|
||||
func (p *Selector) contains(name string) bool {
|
||||
if name == "" && len(p.items) == 0 {
|
||||
return true
|
||||
func (p *Selector) Value() string {
|
||||
if len(p.items) == 0 {
|
||||
return ""
|
||||
}
|
||||
return p.items[0].Value
|
||||
}
|
||||
|
||||
func (p *Selector) Values() (values []string) {
|
||||
for i := range p.items {
|
||||
if p.items[i].Name.Value() == name {
|
||||
return true
|
||||
}
|
||||
values = append(values, p.items[i].Value)
|
||||
}
|
||||
return false
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Selector) Clear() {
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package widget
|
||||
|
||||
import (
|
||||
"gioui.org/layout"
|
||||
"gioui.org/widget/material"
|
||||
)
|
||||
|
||||
type C = layout.Context
|
||||
type D = layout.Dimensions
|
||||
type T = material.Theme
|
||||
|
||||
type Widget interface {
|
||||
Layout(gtx C, th *T) D
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
package version
|
||||
|
||||
var Version = "0.3.0"
|
||||
var Version = "0.3.2"
|
||||
|
||||