diff --git a/dev.deedles.Trayscale.metainfo.xml b/dev.deedles.Trayscale.metainfo.xml
index ec53d4d..a27f561 100644
--- a/dev.deedles.Trayscale.metainfo.xml
+++ b/dev.deedles.Trayscale.metainfo.xml
@@ -54,6 +54,12 @@
+
+
+ Change tray icon when exit node is in use.
+ Bug fixes and performance improvements.
+
+
diff --git a/go.mod b/go.mod
index 338feb3..6fbcfa5 100644
--- a/go.mod
+++ b/go.mod
@@ -4,20 +4,20 @@ go 1.23.0
require (
deedles.dev/mk v0.1.0
- deedles.dev/xiter v0.0.0-20240815221547-ba5e081b01e3
+ deedles.dev/xiter v0.0.0-20240823045626-63c91578a1b7
fyne.io/systray v1.11.0
github.com/diamondburned/gotk4-adwaita/pkg v0.0.0-20240712143708-824c3ce8a5f4
github.com/diamondburned/gotk4/pkg v0.3.1
github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf
golang.org/x/net v0.28.0
gotest.tools/v3 v3.5.1
- honnef.co/go/tools v0.4.6
- tailscale.com v1.72.0
+ honnef.co/go/tools v0.5.1
+ tailscale.com v1.72.1
)
require (
filippo.io/edwards25519 v1.1.0 // indirect
- github.com/BurntSushi/toml v1.3.2 // indirect
+ github.com/BurntSushi/toml v1.4.1-0.20240615085220-eb727477b3f7 // indirect
github.com/KarpelesLab/weak v0.1.1 // indirect
github.com/akutz/memconn v0.1.0 // indirect
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect
@@ -29,7 +29,7 @@ require (
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/go-cmp v0.6.0 // indirect
- github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 // indirect
+ github.com/google/nftables v0.2.1-0.20240815064735-6ddeb7caed5d // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/csrf v1.7.2 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
@@ -46,20 +46,19 @@ require (
github.com/peterbourgon/ff/v3 v3.4.0 // indirect
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect
- github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 // indirect
- github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85 // indirect
+ github.com/tailscale/goupnp v1.0.1-0.20210804030727-66b27ba4e403 // indirect
+ github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 // indirect
github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1 // indirect
github.com/tcnksm/go-httpstat v0.2.0 // indirect
github.com/toqueteos/webbrowser v1.2.0 // indirect
- github.com/vishvananda/netlink v1.2.1-beta.2 // indirect
github.com/vishvananda/netns v0.0.4 // indirect
github.com/x448/float16 v0.8.4 // indirect
go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6 // indirect
golang.org/x/crypto v0.26.0 // indirect
- golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect
- golang.org/x/exp/typeparams v0.0.0-20240119083558-1b970713d09a // indirect
+ golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 // indirect
+ golang.org/x/exp/typeparams v0.0.0-20240823005443-9b4947da3948 // indirect
golang.org/x/mod v0.20.0 // indirect
golang.org/x/oauth2 v0.22.0 // indirect
golang.org/x/sync v0.8.0 // indirect
diff --git a/go.sum b/go.sum
index e4d6444..cf7e08f 100644
--- a/go.sum
+++ b/go.sum
@@ -1,15 +1,15 @@
deedles.dev/mk v0.1.0 h1:xrvuJA3+R/j6/6AZPc+o31I1rotdKLrAYJxhZJwdOuc=
deedles.dev/mk v0.1.0/go.mod h1:TSFsz0T+BvhNqJae0yrj+KadkN4elx248PCpq2Ol4ME=
-deedles.dev/xiter v0.0.0-20240815221547-ba5e081b01e3 h1:aoM+rT4PLB+NMqh7dDGPtT2+6BdsCEbw21pXxLhj+ek=
-deedles.dev/xiter v0.0.0-20240815221547-ba5e081b01e3/go.mod h1:59997UHUsKAy/8bHUClTfeXdyuLZ6z/+yF++vIpxfx8=
+deedles.dev/xiter v0.0.0-20240823045626-63c91578a1b7 h1:wWGMKcFAyBmkzZ0Sxmd/HAa1a8LAPg9jwHuYifHWe1o=
+deedles.dev/xiter v0.0.0-20240823045626-63c91578a1b7/go.mod h1:59997UHUsKAy/8bHUClTfeXdyuLZ6z/+yF++vIpxfx8=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
filippo.io/mkcert v1.4.4 h1:8eVbbwfVlaqUM7OwuftKc2nuYOoTDQWqsoXmzoXZdbc=
filippo.io/mkcert v1.4.4/go.mod h1:VyvOchVuAye3BoUsPUOOofKygVwLV2KQMVFJNRq+1dA=
fyne.io/systray v1.11.0 h1:D9HISlxSkx+jHSniMBR6fCFOUjk1x/OOOJLa9lJYAKg=
fyne.io/systray v1.11.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs=
-github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
-github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
+github.com/BurntSushi/toml v1.4.1-0.20240615085220-eb727477b3f7 h1:GwfNF1ZrlhC1rccPhl176V0DcyO+1Gwd9zqbGTZP2zg=
+github.com/BurntSushi/toml v1.4.1-0.20240615085220-eb727477b3f7/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/KarpelesLab/weak v0.1.1 h1:fNnlPo3aypS9tBzoEQluY13XyUfd/eWaSE/vMvo9s4g=
github.com/KarpelesLab/weak v0.1.1/go.mod h1:pzXsWs5f2bf+fpgHayTlBE1qJpO3MpJKo5sRaLu1XNw=
github.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A=
@@ -51,8 +51,8 @@ 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/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdFk6Vl1yPGtSRtwGpVkWyZww1OCil2MI=
-github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4=
+github.com/google/nftables v0.2.1-0.20240815064735-6ddeb7caed5d h1:oBc6aRXtBgtYSG6y8ceSK2PU9TDaDT+I6Qt+qo7uvws=
+github.com/google/nftables v0.2.1-0.20240815064735-6ddeb7caed5d/go.mod h1:Fo/xFnOxWlRQtnHdNi46KbIjufTDzbKhtghpWrmsSUg=
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/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI=
@@ -102,10 +102,10 @@ github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8Jj4P4c1a3CtQyMaTVCznlkLZI++hok4=
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55/go.mod h1:4k4QO+dQ3R5FofL+SanAUZe+/QfeK0+OIuwDIRu2vSg=
-github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 h1:4chzWmimtJPxRs2O36yuGRW3f9SYV+bMTTvMBI0EKio=
-github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8=
-github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85 h1:zrsUcqrG2uQSPhaUPjUQwozcRdDdSxxqhNgNZ3drZFk=
-github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0=
+github.com/tailscale/goupnp v1.0.1-0.20210804030727-66b27ba4e403 h1:tB2UwtefWtWjIOp5UjU2eHPdP1EY3JZyAkes6WOsvIo=
+github.com/tailscale/goupnp v1.0.1-0.20210804030727-66b27ba4e403/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8=
+github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 h1:uFsXVBE9Qr4ZoF094vE6iYTLDl0qCiKzYXlL6UeWObU=
+github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0=
github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1 h1:tdUdyPqJ0C97SJfjB9tW6EylTtreyee9C44de+UBG0g=
github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ=
github.com/tailscale/wireguard-go v0.0.0-20240731203015-71393c576b98 h1:RNpJrXfI5u6e+uzyIzvmnXbhmhdRkVf//90sMBH3lso=
@@ -116,8 +116,6 @@ github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9r
github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM=
github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e h1:BA9O3BmlTmpjbvajAwzWx4Wo2TRVdpPXZEeemGQcajw=
github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
-github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs=
-github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
@@ -131,10 +129,10 @@ go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6 h1:lGdhQUN
go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
-golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI=
-golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
-golang.org/x/exp/typeparams v0.0.0-20240119083558-1b970713d09a h1:8qmSSA8Gz/1kTrCe0nqR0R3Gb/NDhykzWw2q2mWZydM=
-golang.org/x/exp/typeparams v0.0.0-20240119083558-1b970713d09a/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
+golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA=
+golang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
+golang.org/x/exp/typeparams v0.0.0-20240823005443-9b4947da3948 h1:mwwJFsdsQzu/zhRdxEXmpMvRMnAR6QpbAXx3cNooEf0=
+golang.org/x/exp/typeparams v0.0.0-20240823005443-9b4947da3948/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
@@ -168,8 +166,8 @@ gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
gvisor.dev/gvisor v0.0.0-20240722211153-64c016c92987 h1:TU8z2Lh3Bbq77w0t1eG8yRlLcNHzZu3x6mhoH2Mk0c8=
gvisor.dev/gvisor v0.0.0-20240722211153-64c016c92987/go.mod h1:sxc3Uvk/vHcd3tj7/DHVBoR5wvWT/MmRq2pj7HRJnwU=
-honnef.co/go/tools v0.4.6 h1:oFEHCKeID7to/3autwsWfnuv69j3NsfcXbvJKuIcep8=
-honnef.co/go/tools v0.4.6/go.mod h1:+rnGS1THNh8zMwnd2oVOTL9QF6vmfyG6ZXBULae2uc0=
+honnef.co/go/tools v0.5.1 h1:4bH5o3b5ZULQ4UrBmP+63W9r7qIkqJClEA9ko5YKx+I=
+honnef.co/go/tools v0.5.1/go.mod h1:e9irvo83WDG9/irijV44wr3tbhcFeRnfpVlRqVwpzMs=
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
k8s.io/client-go v0.31.0 h1:QqEJzNjbN2Yv1H79SsS+SWnXkBgVu4Pj3CJQgbx0gI8=
@@ -178,5 +176,5 @@ sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=
software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
-tailscale.com v1.72.0 h1:emsPxupFM72zJLt2wvSzpa1vymqPYbL0WVVO+170/s0=
-tailscale.com v1.72.0/go.mod h1:v7OHtg0KLAnhOVf81Z8WrjNefj238QbFhgkWJQoKxbs=
+tailscale.com v1.72.1 h1:hk82jek36ph2S3Tfsh57NVWKEm/pZ9nfUonvlowpfaA=
+tailscale.com v1.72.1/go.mod h1:v7OHtg0KLAnhOVf81Z8WrjNefj238QbFhgkWJQoKxbs=
diff --git a/internal/tsutil/poller.go b/internal/tsutil/poller.go
index 7bf51ca..2e8ab38 100644
--- a/internal/tsutil/poller.go
+++ b/internal/tsutil/poller.go
@@ -59,6 +59,7 @@ func (p *Poller) Run(ctx context.Context) {
if interval < 0 {
interval = 5 * time.Second
}
+ retry := interval
check := time.NewTicker(interval)
defer check.Stop()
@@ -70,7 +71,15 @@ func (p *Poller) Run(ctx context.Context) {
return
}
slog.Error("get Tailscale status", "err", err)
- continue
+ select {
+ case <-ctx.Done():
+ return
+ case <-time.After(retry):
+ if retry < 30*time.Second {
+ retry *= 2
+ }
+ continue
+ }
}
prefs, err := Prefs(ctx)
@@ -79,9 +88,19 @@ func (p *Poller) Run(ctx context.Context) {
return
}
slog.Error("get Tailscale prefs", "err", err)
- continue
+ select {
+ case <-ctx.Done():
+ return
+ case <-time.After(retry):
+ if retry < 30*time.Second {
+ retry *= 2
+ }
+ continue
+ }
}
+ retry = interval
+
var files []apitype.WaitingFile
if status.Self.HasCap(tailcfg.CapabilityFileSharing) {
files, err = WaitingFiles(ctx)
diff --git a/internal/ui/app.go b/internal/ui/app.go
index 270a665..8a85e62 100644
--- a/internal/ui/app.go
+++ b/internal/ui/app.go
@@ -2,7 +2,6 @@ package ui
import (
"context"
- "iter"
"log/slog"
"os"
"slices"
@@ -11,7 +10,6 @@ import (
"deedles.dev/mk"
"deedles.dev/trayscale/internal/tray"
"deedles.dev/trayscale/internal/tsutil"
- "deedles.dev/xiter"
"github.com/diamondburned/gotk4-adwaita/pkg/adw"
"github.com/diamondburned/gotk4/pkg/gdk/v4"
"github.com/diamondburned/gotk4/pkg/gio/v2"
@@ -129,11 +127,14 @@ func (a *App) updatePeers(status tsutil.Status) {
}
peerMap := status.Status.Peer
- peers := slices.SortedFunc(iter.Seq[key.NodePublic](xiter.Filter(xiter.MapKeys(status.Status.Peer),
- func(peer key.NodePublic) bool {
- return !tsutil.IsMullvad(peerMap[peer])
- })),
- key.NodePublic.Compare)
+ peers := make([]key.NodePublic, 0, len(status.Status.Peer))
+ for k, p := range peerMap {
+ if tsutil.IsMullvad(p) {
+ continue
+ }
+ peers = append(peers, k)
+ }
+ slices.SortFunc(peers, key.NodePublic.Compare)
for key, page := range a.peerPages {
if _, ok := peerMap[key]; !ok {
diff --git a/internal/ui/peerpage.go b/internal/ui/peerpage.go
index 85712fb..3cbacde 100644
--- a/internal/ui/peerpage.go
+++ b/internal/ui/peerpage.go
@@ -4,7 +4,6 @@ import (
"cmp"
"context"
_ "embed"
- "iter"
"log/slog"
"net/netip"
"slices"
@@ -225,23 +224,46 @@ func (page *PeerPage) Update(a *App, peer *ipnstate.PeerStatus, status tsutil.St
slices.SortFunc(peer.TailscaleIPs, netip.Addr.Compare)
page.addrRows.Update(peer.TailscaleIPs)
- if peer.PrimaryRoutes != nil {
- page.routes = peer.PrimaryRoutes.AsSlice()
+ routes := func(yield func(netip.Prefix) bool) {
+ if peer.PrimaryRoutes == nil {
+ return
+ }
+ for i := 0; i < peer.PrimaryRoutes.Len(); i++ {
+ r := peer.PrimaryRoutes.At(i)
+ if r.Bits() == 0 {
+ continue
+ }
+ if !yield(r) {
+ return
+ }
+ }
}
- page.routes = slices.SortedFunc(iter.Seq[netip.Prefix](xiter.Filter(xiter.OfSlice(page.routes),
- func(p netip.Prefix) bool { return p.Bits() != 0 })),
+ routes = xiter.Or(
+ routes,
+ xiter.Of(netip.Prefix{}),
+ )
+
+ clear(page.routes)
+ page.routes = page.routes[:0]
+ page.routes = slices.AppendSeq(page.routes, routes)
+ slices.SortFunc(
+ page.routes,
func(p1, p2 netip.Prefix) int {
- return cmp.Or(p1.Addr().Compare(p2.Addr()), p1.Bits()-p2.Bits())
- })
- if len(page.routes) == 0 {
- page.routes = append(page.routes, netip.Prefix{})
- }
- eroutes := make([]enum[netip.Prefix], 0, len(page.routes))
- for i, r := range page.routes {
- i = -1
- eroutes = append(eroutes, enumerate(i, r))
+ return cmp.Or(
+ p1.Addr().Compare(p2.Addr()),
+ cmp.Compare(p1.Bits(), p2.Bits()),
+ )
+ },
+ )
+
+ eroutes := func(yield func(enum[netip.Prefix]) bool) {
+ for _, r := range page.routes {
+ if !yield(enumerate(-1, r)) {
+ return
+ }
+ }
}
- page.routeRows.Update(eroutes)
+ page.routeRows.UpdateFromSeq(eroutes, len(page.routes))
page.ExitNodeRow.SetVisible(peer.ExitNodeOption)
page.ExitNodeRow.ActivatableWidget().(*gtk.Switch).SetState(peer.ExitNode)
diff --git a/internal/ui/rowmanager.go b/internal/ui/rowmanager.go
index ca24415..a5ab1bf 100644
--- a/internal/ui/rowmanager.go
+++ b/internal/ui/rowmanager.go
@@ -1,8 +1,10 @@
package ui
import (
+ "iter"
"slices"
+ "deedles.dev/xiter"
"github.com/diamondburned/gotk4/pkg/gtk/v4"
)
@@ -31,9 +33,14 @@ func (m *rowManager[Data]) resize(size int) {
}
func (m *rowManager[Data]) Update(data []Data) {
- m.resize(len(data))
+ m.UpdateFromSeq(slices.Values(data), len(data))
+}
+
+func (m *rowManager[Data]) UpdateFromSeq(data iter.Seq[Data], size int) {
+ m.resize(size)
- for i, d := range data {
+ edata := xiter.Enumerate(xiter.Seq[Data](data))
+ for i, d := range edata {
if i < len(m.rows) {
m.rows[i].Update(d)
continue
diff --git a/internal/ui/selfpage.go b/internal/ui/selfpage.go
index 2eff473..3701b39 100644
--- a/internal/ui/selfpage.go
+++ b/internal/ui/selfpage.go
@@ -4,7 +4,6 @@ import (
"cmp"
"context"
_ "embed"
- "iter"
"log/slog"
"net/netip"
"slices"
@@ -354,12 +353,16 @@ func (page *SelfPage) init(a *App, peer *ipnstate.PeerStatus, status tsutil.Stat
page.PreferredDERP.SetText(dm.Regions[r.PreferredDERP].RegionName)
page.DERPLatencies.SetVisible(true)
- namedLats := slices.SortedFunc(iter.Seq[xiter.Pair[string, time.Duration]](xiter.Map(xiter.ToPair(xiter.OfMap(r.RegionLatency)),
- func(p xiter.Pair[int, time.Duration]) xiter.Pair[string, time.Duration] {
- return xiter.P(dm.Regions[p.V1].RegionName, p.V2)
- })),
- func(p1, p2 xiter.Pair[string, time.Duration]) int { return cmp.Compare(p1.V2, p2.V2) })
- latencyRows.Update(namedLats)
+ namedLats := func(yield func(latencyEntry) bool) {
+ for id, latency := range r.RegionLatency {
+ named := xiter.P(dm.Regions[id].RegionName, latency)
+ if !yield(named) {
+ return
+ }
+ }
+ }
+ sortedLats := slices.SortedFunc(namedLats, func(p1, p2 latencyEntry) int { return cmp.Compare(p1.V2, p2.V2) })
+ latencyRows.Update(sortedLats)
})
}
@@ -383,19 +386,39 @@ func (page *SelfPage) Update(a *App, peer *ipnstate.PeerStatus, status tsutil.St
page.fileRows.Update(status.Files)
page.FilesGroup.SetVisible(len(status.Files) > 0)
- page.routes = slices.SortedFunc(iter.Seq[netip.Prefix](xiter.Filter(xiter.OfSlice(status.Prefs.AdvertiseRoutes),
- func(p netip.Prefix) bool { return p.Bits() != 0 })), // Filter
- func(p1, p2 netip.Prefix) int { // SortedFunc
- return cmp.Or(p1.Addr().Compare(p2.Addr()), p1.Bits()-p2.Bits())
- })
- if len(page.routes) == 0 {
- page.routes = append(page.routes, netip.Prefix{})
+ routes := func(yield func(netip.Prefix) bool) {
+ for _, r := range status.Prefs.AdvertiseRoutes {
+ if r.Bits() == 0 {
+ continue
+ }
+ if !yield(r) {
+ return
+ }
+ }
}
- eroutes := make([]enum[netip.Prefix], 0, len(page.routes))
- for i, r := range page.routes {
- eroutes = append(eroutes, enumerate(i, r))
+ routes = xiter.Or(
+ routes,
+ xiter.Of(netip.Prefix{}),
+ )
+
+ clear(page.routes)
+ page.routes = page.routes[:0]
+ page.routes = slices.AppendSeq(page.routes, routes)
+ slices.SortFunc(page.routes, func(p1, p2 netip.Prefix) int {
+ return cmp.Or(
+ p1.Addr().Compare(p2.Addr()),
+ cmp.Compare(p1.Bits(), p2.Bits()),
+ )
+ })
+
+ eroutes := func(yield func(enum[netip.Prefix]) bool) {
+ for i, r := range page.routes {
+ if !yield(enumerate(i, r)) {
+ return
+ }
+ }
}
- page.routeRows.Update(eroutes)
+ page.routeRows.UpdateFromSeq(eroutes, len(page.routes))
}
type addrRow struct {