Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
33cb0d2
internal/ui.old: rename for quick reference
DeedleFake May 25, 2025
372bcda
cmd/trayscale: remove `default.pgo`
DeedleFake May 25, 2025
e630dab
internal/ui: start rewriting the whole darn thing in C
DeedleFake May 25, 2025
19094b5
internal/ui: move extra files back
DeedleFake May 25, 2025
d88e81c
internal/ui: add `ref` in the hopes that it'll be useful at some point
DeedleFake May 25, 2025
c3a9493
internal/trayscale: add
DeedleFake May 25, 2025
7db7a4f
internal/ui: follow the conventions a bit better
DeedleFake May 25, 2025
c522240
internal/ui: load custom CSS
DeedleFake May 25, 2025
7a4fce3
internal/ui: hold app when it's starting
DeedleFake May 25, 2025
b4dbf94
internal/ui: better `idle()` flow
DeedleFake May 25, 2025
f743079
internal/ui: clean up the CSS provider properly
DeedleFake May 25, 2025
c7af314
internal/ui: delete idle handles after they're used
DeedleFake May 25, 2025
a2994e3
internal/ui: show the tray icon
DeedleFake May 25, 2025
683d26a
interntal/trayscale: update tray icon
DeedleFake May 25, 2025
401b37b
internal/trayscale: implement all tray icon functionality
DeedleFake May 25, 2025
c4fde75
internal/ui: add notifications when connection status changes
DeedleFake May 25, 2025
c13842d
internal/ui: begin working on `UiMainWindow`
DeedleFake May 25, 2025
af74819
internal/ui: implement settings support for the tray icon
DeedleFake May 25, 2025
66e623d
internal/ui: clean up `UiApp->g_settings`
DeedleFake May 25, 2025
8396db0
internal/ui: implement setting the polling interval
DeedleFake May 26, 2025
0e59530
internal/ui: add `update` signal to `UiApp`
DeedleFake May 26, 2025
d48a170
internal/tray, internal/trayscale: fix some data races
DeedleFake May 26, 2025
7507fbb
internal/ui: convert UI files to templates
DeedleFake May 26, 2025
28e801b
internal/ui: load files on demand instead of in advance
DeedleFake May 26, 2025
8d3a86d
internal/ui: get window to open
DeedleFake May 26, 2025
a562609
internal/ui: get menus in the window to load and display
DeedleFake May 26, 2025
611333c
internal/ui: implement `app.quit` action
DeedleFake May 26, 2025
a3e5537
internal/ui: rename some but not all of the widgets in the UI files
DeedleFake May 26, 2025
79805a0
internal/ui: test for the existence of the settings schema before try…
DeedleFake May 26, 2025
cf4d75c
internal/ui: fix a bunch of problems, including not chaining up virtu…
DeedleFake May 26, 2025
a9d2e21
internal/ui: trying to fix some memory leaks
DeedleFake May 26, 2025
ea10a03
internal/ui: lock the thread
DeedleFake May 26, 2025
8590042
internal/ui: close the window when closing the app to make sure it's …
DeedleFake May 26, 2025
ffb4a34
internal/ui: initialize some stuff in `startup()` instead of in `init()`
DeedleFake May 26, 2025
9c2b3f5
internal/ui: hook up the status switch
DeedleFake May 27, 2025
b4a3fe0
intenral/ui: add some more utility functions and remove an unussed gl…
DeedleFake May 27, 2025
1d38c6b
internal/ui: move menu model loading into an `init()` function
DeedleFake May 27, 2025
c643ec4
internal/ui: move everything into just `ui.h`
DeedleFake May 27, 2025
101f466
internal/ui: rename a function
DeedleFake May 27, 2025
c516098
internal/ui: implement subtyping in Go using Go structs
DeedleFake May 27, 2025
ebabca4
internal/ui: more custom types testing
DeedleFake May 27, 2025
b07719a
internal/ui: moving things over to a better Go wrapper
DeedleFake May 28, 2025
a698d43
internal/ui: implement `(*GObjectClass).SetDispose()`
DeedleFake May 28, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file removed cmd/trayscale/default.pgo
Binary file not shown.
6 changes: 3 additions & 3 deletions cmd/trayscale/trayscale.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"os/signal"
"runtime/pprof"

"deedles.dev/trayscale/internal/ui"
"deedles.dev/trayscale/internal/trayscale"
)

func profile() func() {
Expand Down Expand Up @@ -45,6 +45,6 @@ func main() {
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()

var a ui.App
a.Run(ctx)
var app trayscale.App
app.Run(ctx)
}
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ require (
deedles.dev/mk v0.1.0
deedles.dev/tray v0.1.9
deedles.dev/xiter v0.2.1
github.com/atotto/clipboard v0.1.4
github.com/diamondburned/gotk4-adwaita/pkg v0.0.0-20250310094704-65bb91d1403f
github.com/diamondburned/gotk4/pkg v0.3.1
github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf
github.com/klauspost/compress v1.18.0
github.com/stretchr/testify v1.10.0
golang.org/x/net v0.40.0
tailscale.com v1.84.0
)

Expand Down Expand Up @@ -86,6 +86,7 @@ require (
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect
golang.org/x/exp/typeparams v0.0.0-20250305212735-054e65f0b394 // indirect
golang.org/x/mod v0.24.0 // indirect
golang.org/x/net v0.40.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sync v0.14.0 // indirect
golang.org/x/sys v0.33.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7V
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38yqWM=
github.com/aws/aws-sdk-go-v2 v1.36.3/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg=
github.com/aws/aws-sdk-go-v2/config v1.29.14 h1:f+eEi/2cKCg9pqKBoAIwRGzVb70MRKqWX4dg1BDcSJM=
Expand Down
12 changes: 12 additions & 0 deletions internal/ctxutil/ctxutil.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package ctxutil

import "context"

func Recv[T any, C ~<-chan T](ctx context.Context, c C) (v T, ok bool) {
select {
case <-ctx.Done():
return v, false
case v, ok := <-c:
return v, ok
}
}
30 changes: 25 additions & 5 deletions internal/tray/tray.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
_ "embed"
"fmt"
"image/png"
"sync"

"deedles.dev/tray"
"deedles.dev/trayscale/internal/tsutil"
Expand Down Expand Up @@ -46,6 +47,7 @@ type Tray struct {
OnSelfNode func()
OnQuit func()

m sync.RWMutex
item *tray.Item
icon *tray.Pixmap

Expand All @@ -56,7 +58,10 @@ type Tray struct {
quitItem *tray.MenuItem
}

func (t *Tray) Start(s *tsutil.IPNStatus) error {
func (t *Tray) Start(status *tsutil.IPNStatus) error {
t.m.Lock()
defer t.m.Unlock()

if t.item != nil {
return nil
}
Expand Down Expand Up @@ -84,13 +89,16 @@ func (t *Tray) Start(s *tsutil.IPNStatus) error {
menu.AddChild(tray.MenuItemType(tray.Separator))
t.quitItem, _ = menu.AddChild(tray.MenuItemLabel("Quit"), handler(t.OnQuit))

t.Update(s)
t.update(status)

return nil
}

func (t *Tray) Close() error {
if t == nil || t.item == nil {
t.m.Lock()
defer t.m.Unlock()

if t.item == nil {
return nil
}

Expand All @@ -100,8 +108,20 @@ func (t *Tray) Close() error {
return err
}

func (t *Tray) Update(status *tsutil.IPNStatus) {
if t == nil || t.item == nil {
func (t *Tray) Update(s tsutil.Status) {
status, ok := s.(*tsutil.IPNStatus)
if !ok {
return
}

t.m.RLock()
defer t.m.RUnlock()

t.update(status)
}

func (t *Tray) update(status *tsutil.IPNStatus) {
if t.item == nil {
return
}

Expand Down
139 changes: 139 additions & 0 deletions internal/trayscale/trayscale.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package trayscale

import (
"context"
"log/slog"
"os"
"runtime"
"time"

"deedles.dev/trayscale/internal/ctxutil"
"deedles.dev/trayscale/internal/tray"
"deedles.dev/trayscale/internal/tsutil"
"deedles.dev/trayscale/internal/ui"
"github.com/atotto/clipboard"
)

type App struct {
poller *tsutil.Poller
tray *tray.Tray
app *ui.App
}

func (app *App) Run(ctx context.Context) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()

context.AfterFunc(ctx, app.Quit)

app.poller = &tsutil.Poller{
Interval: 5 * time.Second,
New: app.Update,
}

app.tray = &tray.Tray{
OnShow: app.app.ShowWindow,
OnConnToggle: func() { app.toggleConn(ctx) },
OnExitToggle: func() { app.toggleExit(ctx) },
OnSelfNode: func() { app.copySelf(ctx) },
OnQuit: app.Quit,
}

app.app = ui.NewApp(app)
defer app.app.Unref()

go app.poller.Run(ctx)

runtime.LockOSThread()
defer runtime.UnlockOSThread()
app.app.Run(os.Args)
}

func (app *App) Quit() {
app.app.Quit()
}

func (app *App) Update(status tsutil.Status) {
app.tray.Update(status)
app.app.Update(status)
}

func (app *App) toggleConn(ctx context.Context) {
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()

status, ok := ctxutil.Recv(ctx, app.poller.GetIPN())
if !ok {
return
}

f := tsutil.Start
if status.Online() {
f = tsutil.Stop
}

err := f(ctx)
if err != nil {
slog.Error("failed to toggle Tailscale", "source", "tray icon", "err", err)
return
}

ctxutil.Recv(ctx, app.poller.Poll())
}

func (app *App) toggleExit(ctx context.Context) {
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()

status, ok := ctxutil.Recv(ctx, app.poller.GetIPN())
if !ok {
return
}

exitNodeActive := status.ExitNodeActive()
err := tsutil.SetUseExitNode(ctx, !exitNodeActive)
if err != nil {
app.app.Notify("Toggle exit node", err.Error())
slog.Error("failed to toggle Tailscale", "source", "tray icon", "err", err)
return
}

if exitNodeActive {
app.app.Notify("Tailscale exit node", "Disabled")
return
}
app.app.Notify("Exit node", "Enabled")
}

func (app *App) copySelf(ctx context.Context) {
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()

status, ok := ctxutil.Recv(ctx, app.poller.GetIPN())
if !ok {
return
}

addr := status.SelfAddr()
if !addr.IsValid() {
slog.Error("self address was invalid")
return
}

err := clipboard.WriteAll(addr.String())
if err != nil {
slog.Error("failed to copy self address", "err", err)
app.app.Notify("Copy address to clipboard", err.Error())
return
}

app.app.Notify("Trayscale", "Copied address to clipboard")
}

func (app *App) Poller() *tsutil.Poller {
return app.poller
}

func (app *App) Tray() *tray.Tray {
return app.tray
}
Loading
Loading