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 }