diff options
Diffstat (limited to 'main.go')
-rw-r--r-- | main.go | 249 |
1 files changed, 249 insertions, 0 deletions
@@ -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 +} |