diff --git a/dev.deedles.Trayscale.metainfo.xml b/dev.deedles.Trayscale.metainfo.xml
index 7169e75..fd56257 100644
--- a/dev.deedles.Trayscale.metainfo.xml
+++ b/dev.deedles.Trayscale.metainfo.xml
@@ -54,6 +54,12 @@
+
+
+ Update to use new Libadwaita widgets.
+ Fix a bug with ordering of Mullvad nodes.
+
+
Fix self and Mullvad pages disappearing when re-opening the window.
diff --git a/internal/tsutil/tsutil.go b/internal/tsutil/tsutil.go
index 8b37352..0398a87 100644
--- a/internal/tsutil/tsutil.go
+++ b/internal/tsutil/tsutil.go
@@ -50,3 +50,16 @@ func CompareLocations(loc1, loc2 *tailcfg.Location) int {
cmp.Compare(loc1.City, loc2.City),
)
}
+
+// ComparePeers compares two peers. It does so by location if
+// available or hostname if not. It returns the peers in a
+// deterministic order if their locations or hostnames are identical,
+// so the result of calling this is never 0. To determine if peers are
+// the same, compare their IDs manually.
+func ComparePeers(p1, p2 *ipnstate.PeerStatus) int {
+ ids := cmp.Compare(p1.ID, p2.ID)
+ if (p1.Location == nil) || (p2.Location == nil) {
+ return cmp.Or(cmp.Compare(p1.HostName, p2.HostName), ids)
+ }
+ return cmp.Or(CompareLocations(p1.Location, p2.Location), ids)
+}
diff --git a/internal/ui/mainwindow.ui b/internal/ui/mainwindow.ui
index ed28761..ef43442 100644
--- a/internal/ui/mainwindow.ui
+++ b/internal/ui/mainwindow.ui
@@ -1,5 +1,5 @@
-
+
@@ -13,7 +13,7 @@
200
@@ -67,7 +67,7 @@
diff --git a/internal/ui/mullvadpage.go b/internal/ui/mullvadpage.go
index c7463a3..2b87abc 100644
--- a/internal/ui/mullvadpage.go
+++ b/internal/ui/mullvadpage.go
@@ -1,7 +1,6 @@
package ui
import (
- "cmp"
"context"
_ "embed"
"fmt"
@@ -55,17 +54,15 @@ func (page *MullvadPage) init(a *App, status tsutil.Status) {
row := exitNodeRow{
peer: peer,
- w: adw.NewActionRow(),
- r: gtk.NewSwitch(),
+ w: adw.NewSwitchRow(),
}
- row.w.AddSuffix(row.r)
row.w.SetTitle(mullvadExitNodeName(peer))
- row.r.SetMarginTop(12)
- row.r.SetMarginBottom(12)
- row.r.ConnectStateSet(func(s bool) bool {
- if s == row.r.State() {
+ row.r().SetMarginTop(12)
+ row.r().SetMarginBottom(12)
+ row.r().ConnectStateSet(func(s bool) bool {
+ if s == row.r().State() {
return false
}
@@ -84,7 +81,7 @@ func (page *MullvadPage) init(a *App, status tsutil.Status) {
err := a.TS.ExitNode(context.TODO(), node)
if err != nil {
slog.Error("set exit node", "err", err)
- row.r.SetActive(!s)
+ row.r().SetActive(!s)
return true
}
a.poller.Poll() <- struct{}{}
@@ -112,12 +109,7 @@ func (page *MullvadPage) Update(a *App, peer *ipnstate.PeerStatus, status tsutil
}
}
}
- slices.SortFunc(nodes, func(p1, p2 *ipnstate.PeerStatus) int {
- if (p1.Location == nil) || (p2.Location == nil) {
- return cmp.Compare(p1.HostName, p2.HostName)
- }
- return tsutil.CompareLocations(p1.Location, p2.Location)
- })
+ slices.SortFunc(nodes, tsutil.ComparePeers)
page.exitNodeRows.Update(nodes)
}
@@ -125,8 +117,11 @@ func (page *MullvadPage) Update(a *App, peer *ipnstate.PeerStatus, status tsutil
type exitNodeRow struct {
peer *ipnstate.PeerStatus
- w *adw.ActionRow
- r *gtk.Switch
+ w *adw.SwitchRow
+}
+
+func (row *exitNodeRow) r() *gtk.Switch {
+ return row.w.ActivatableWidget().(*gtk.Switch)
}
func (row *exitNodeRow) Update(peer *ipnstate.PeerStatus) {
@@ -134,8 +129,8 @@ func (row *exitNodeRow) Update(peer *ipnstate.PeerStatus) {
row.w.SetTitle(mullvadExitNodeName(peer))
- row.r.SetState(peer.ExitNode)
- row.r.SetActive(peer.ExitNode)
+ row.r().SetState(peer.ExitNode)
+ row.r().SetActive(peer.ExitNode)
}
func (row *exitNodeRow) Widget() gtk.Widgetter {
diff --git a/internal/ui/mullvadpage.ui b/internal/ui/mullvadpage.ui
index a0fb5ce..bf85f9b 100644
--- a/internal/ui/mullvadpage.ui
+++ b/internal/ui/mullvadpage.ui
@@ -1,5 +1,5 @@
-
+
diff --git a/internal/ui/peerpage.go b/internal/ui/peerpage.go
index cde941c..9d47ce8 100644
--- a/internal/ui/peerpage.go
+++ b/internal/ui/peerpage.go
@@ -46,8 +46,7 @@ type PeerPage struct {
PreferredDERP *gtk.Label
DERPLatencies *adw.ExpanderRow
MiscGroup *adw.PreferencesGroup
- ExitNodeRow *adw.ActionRow
- ExitNodeSwitch *gtk.Switch
+ ExitNodeRow *adw.SwitchRow
OnlineRow *adw.ActionRow
Online *gtk.Image
LastSeenRow *adw.ActionRow
@@ -186,8 +185,8 @@ func (page *PeerPage) init(a *App, peer *ipnstate.PeerStatus, status tsutil.Stat
return &row
}
- page.ExitNodeSwitch.ConnectStateSet(func(s bool) bool {
- if s == page.ExitNodeSwitch.State() {
+ page.ExitNodeRow.ActivatableWidget().(*gtk.Switch).ConnectStateSet(func(s bool) bool {
+ if s == page.ExitNodeRow.ActivatableWidget().(*gtk.Switch).State() {
return false
}
@@ -206,7 +205,7 @@ func (page *PeerPage) init(a *App, peer *ipnstate.PeerStatus, status tsutil.Stat
err := a.TS.ExitNode(context.TODO(), node)
if err != nil {
slog.Error("set exit node", "err", err)
- page.ExitNodeSwitch.SetActive(!s)
+ page.ExitNodeRow.ActivatableWidget().(*gtk.Switch).SetActive(!s)
return true
}
a.poller.Poll() <- struct{}{}
@@ -242,8 +241,8 @@ func (page *PeerPage) Update(a *App, peer *ipnstate.PeerStatus, status tsutil.St
page.routeRows.Update(eroutes)
page.ExitNodeRow.SetVisible(peer.ExitNodeOption)
- page.ExitNodeSwitch.SetState(peer.ExitNode)
- page.ExitNodeSwitch.SetActive(peer.ExitNode)
+ page.ExitNodeRow.ActivatableWidget().(*gtk.Switch).SetState(peer.ExitNode)
+ page.ExitNodeRow.ActivatableWidget().(*gtk.Switch).SetActive(peer.ExitNode)
page.RxBytes.SetText(strconv.FormatInt(peer.RxBytes, 10))
page.TxBytes.SetText(strconv.FormatInt(peer.TxBytes, 10))
page.Created.SetText(formatTime(peer.Created))
diff --git a/internal/ui/peerpage.ui b/internal/ui/peerpage.ui
index 2420937..7c9779a 100644
--- a/internal/ui/peerpage.ui
+++ b/internal/ui/peerpage.ui
@@ -1,8 +1,8 @@
-
+
-
+