From 59c7f02ad05af0223a08f47e3193c331a86445d5 Mon Sep 17 00:00:00 2001 From: xengineering Date: Sun, 16 Apr 2023 11:44:28 +0200 Subject: Switch completely to Go and Gio UI The properties of the Go language, standard library and the Gio UI library are well suited for this project. The existing experimental Go code exceeds the GTK4 and SDL attempts by far with respect to quality and code simplicity. --- main.go | 249 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 249 insertions(+) create mode 100644 main.go (limited to 'main.go') diff --git a/main.go b/main.go new file mode 100644 index 0000000..2803e68 --- /dev/null +++ b/main.go @@ -0,0 +1,249 @@ +package main + +import ( + "image/color" + "log" + "net" + "os" + "reflect" + + "gioui.org/app" + "gioui.org/font/gofont" + "gioui.org/io/system" + "gioui.org/layout" + "gioui.org/op" + "gioui.org/text" + "gioui.org/unit" + "gioui.org/widget" + "gioui.org/widget/material" +) + +type XmppEvent uint8 + +const ( + XmppDisconnect XmppEvent = iota + XmppConnect +) + +type GuiEvent uint8 + +const ( + Disconnect GuiEvent = iota +) + +type LimoxState uint8 + +const ( + Disconnected LimoxState = iota + Connecting + Connected +) + +type Limox struct { + JidEditor widget.Editor + PwdEditor widget.Editor + MainButton widget.Clickable + XmppEvents chan any + GuiEvents chan GuiEvent + State LimoxState + Window *app.Window + Operations op.Ops + Theme *material.Theme +} + +func main() { + limox := NewLimox() + + go func() { + err := limox.run() + if err != nil { + log.Fatal(err) + } + os.Exit(0) + }() + + app.Main() +} + +func NewLimox() Limox { + return Limox{ + Window: app.NewWindow( + app.Title("LimoX"), + app.Size(unit.Dp(400), unit.Dp(600)), + ), + Operations: op.Ops{}, + Theme: material.NewTheme(gofont.Collection()), + XmppEvents: make(chan any), + GuiEvents: make(chan GuiEvent), + State: Disconnected, + } +} + +func (l *Limox) run() error { + for { + select { + case e := <-l.Window.Events(): + switch e := e.(type) { + case system.DestroyEvent: + return e.Err + case system.FrameEvent: + if l.MainButton.Clicked() { + l.buttonCallback() + } + l.draw(e) + } + case ev := <-l.XmppEvents: + switch ev.(type) { + case error: + log.Print(ev) + l.State = Disconnected + case XmppEvent: + switch ev { + case XmppDisconnect: + l.State = Disconnected + case XmppConnect: + l.State = Connected + default: + log.Fatalf("Unknown XmppEvent '%d'\n", ev) + } + default: + log.Fatalf("Unknown event type '%s'.\n", reflect.TypeOf(ev)) + } + } + } +} + +func (l *Limox) buttonCallback() { + switch l.State { + case Disconnected: + log.Println("Starting connection establishment") + go l.xmpp(l.JidEditor.Text(), l.PwdEditor.Text()) + l.State = Connecting + case Connecting: + log.Println("Aborted connection establishment") + l.State = Disconnected + case Connected: + log.Println("Disconnected") + l.GuiEvents <- Disconnect + l.State = Disconnected + } +} + +func (l *Limox) xmpp(jid string, pwd string) { + log.Printf("JID: '%s' PWD: '%s'\n", jid, pwd) + + domain := domainpart(jid) + log.Printf("Domain: '%s'\n", domain) + + tcpServer, err := net.ResolveTCPAddr("tcp", domain+":"+"5222") + if err != nil { + l.XmppEvents <- err + return + } + log.Printf("Server: %s\n", tcpServer) + + conn, err := net.DialTCP("tcp", nil, tcpServer) + if err != nil { + l.XmppEvents <- err + return + } + l.XmppEvents <- XmppConnect + + var closing bool = false + for { + ev := <-l.GuiEvents + switch ev { + case Disconnect: + closing = true + default: + log.Fatalf("Unknown GuiEvent '%d'!\n", ev) + } + if closing { + break + } + } + + conn.Close() + l.XmppEvents <- XmppDisconnect +} + +// domainpart extracts the domain name from a JID / XMPP address. See +// https://datatracker.ietf.org/doc/html/rfc7622#section-3.2 for details. +func domainpart(jid string) string { + list := []rune(jid) + + for i, v := range list { + if v == '/' { + list = list[:i] + break + } + } + + for i, v := range list { + if v == '@' { + list = list[i+1:] + break + } + } + + return string(list) +} + +func (l *Limox) draw(e system.FrameEvent) { + gtx := layout.NewContext(&l.Operations, e) + + flex := layout.Flex{ + Axis: layout.Vertical, + Spacing: layout.SpaceBetween, + } + + flex.Layout(gtx, + layout.Rigid( + func(gtx layout.Context) layout.Dimensions { + h2 := material.H2(l.Theme, "LimoX") + h2.Color = color.NRGBA{R: 20, G: 20, B: 20, A: 255} + h2.Alignment = text.Middle + return h2.Layout(gtx) + }, + ), + layout.Rigid( + func(gtx layout.Context) layout.Dimensions { + jid := material.Editor(l.Theme, &l.JidEditor, "user@example.com") + jid.Editor.Alignment = text.Middle + return jid.Layout(gtx) + }, + ), + layout.Rigid( + func(gtx layout.Context) layout.Dimensions { + pwd := material.Editor(l.Theme, &l.PwdEditor, "mySafePassword") + pwd.Editor.Alignment = text.Middle + pwd.Editor.Mask = '*' + return pwd.Layout(gtx) + }, + ), + layout.Rigid( + func(gtx layout.Context) layout.Dimensions { + btn := material.Button(l.Theme, &l.MainButton, + l.buttonLabel()) + return btn.Layout(gtx) + }, + ), + ) + + e.Frame(gtx.Ops) +} + +func (l *Limox) buttonLabel() string { + var label string + switch l.State { + case Disconnected: + label = "Connect" + case Connecting: + label = "Abort" + case Connected: + label = "Disconnect" + default: + log.Fatalf("Unknown Limox state '%d'\n", l.State) + } + return label +} -- cgit v1.2.3-70-g09d2