318 lines
6.6 KiB
Go
318 lines
6.6 KiB
Go
|
|
package main
|
||
|
|
|
||
|
|
import (
|
||
|
|
"os"
|
||
|
|
|
||
|
|
"context"
|
||
|
|
"fmt"
|
||
|
|
"github.com/diamondburned/gotk4/pkg/gdk/v4"
|
||
|
|
"github.com/diamondburned/gotk4/pkg/gio/v2"
|
||
|
|
"github.com/diamondburned/gotk4/pkg/glib/v2"
|
||
|
|
"github.com/diamondburned/gotk4/pkg/gtk/v4"
|
||
|
|
|
||
|
|
"github.com/BurntSushi/toml"
|
||
|
|
"gosrc.io/xmpp"
|
||
|
|
"gosrc.io/xmpp/stanza"
|
||
|
|
"mellium.im/xmpp/jid"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
_ "embed"
|
||
|
|
"encoding/xml"
|
||
|
|
"errors"
|
||
|
|
)
|
||
|
|
|
||
|
|
var loadedConfig lambdaConfig
|
||
|
|
|
||
|
|
var empty_dialog *gtk.Label
|
||
|
|
|
||
|
|
// var msgs *gtk.ListBox
|
||
|
|
var content *gtk.Widgetter
|
||
|
|
|
||
|
|
var tabs map[string]*chatTab = make(map[string]*chatTab)
|
||
|
|
var current string
|
||
|
|
|
||
|
|
var scroller *gtk.ScrolledWindow
|
||
|
|
|
||
|
|
//go:embed style.css
|
||
|
|
var styleCSS string
|
||
|
|
var client xmpp.Sender
|
||
|
|
var clientroot *xmpp.Client
|
||
|
|
|
||
|
|
var uiQueue = make(chan func(), 100)
|
||
|
|
|
||
|
|
func init() {
|
||
|
|
go func() {
|
||
|
|
for fn := range uiQueue {
|
||
|
|
glib.IdleAdd(func() bool {
|
||
|
|
fn()
|
||
|
|
return false
|
||
|
|
})
|
||
|
|
time.Sleep(10 * time.Millisecond) // Small delay between updates
|
||
|
|
}
|
||
|
|
}()
|
||
|
|
}
|
||
|
|
|
||
|
|
func main() {
|
||
|
|
|
||
|
|
b, err := os.ReadFile("lambda.toml")
|
||
|
|
if err != nil {
|
||
|
|
showErrorDialog(err)
|
||
|
|
panic(err)
|
||
|
|
}
|
||
|
|
|
||
|
|
_, err = toml.Decode(string(b), &loadedConfig)
|
||
|
|
|
||
|
|
config := xmpp.Config{
|
||
|
|
TransportConfiguration: xmpp.TransportConfiguration{
|
||
|
|
Address: loadedConfig.Server,
|
||
|
|
},
|
||
|
|
Jid: loadedConfig.Username,
|
||
|
|
Credential: xmpp.Password(loadedConfig.Password),
|
||
|
|
Insecure: loadedConfig.Insecure,
|
||
|
|
StreamLogger: os.Stdout,
|
||
|
|
}
|
||
|
|
router := xmpp.NewRouter()
|
||
|
|
|
||
|
|
router.NewRoute().IQNamespaces(stanza.NSDiscoInfo).HandlerFunc(func(s xmpp.Sender, p stanza.Packet) {
|
||
|
|
iq, ok := p.(*stanza.IQ)
|
||
|
|
if !ok || iq.Type != stanza.IQTypeGet {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
iqResp, err := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id})
|
||
|
|
if err != nil {
|
||
|
|
panic(err)
|
||
|
|
}
|
||
|
|
|
||
|
|
identity := stanza.Identity{
|
||
|
|
Name: "Lambda",
|
||
|
|
Category: "client", // TODO: Allow spoofing on user request
|
||
|
|
Type: "pc",
|
||
|
|
}
|
||
|
|
payload := stanza.DiscoInfo{
|
||
|
|
XMLName: xml.Name{
|
||
|
|
Space: stanza.NSDiscoInfo,
|
||
|
|
Local: "query",
|
||
|
|
},
|
||
|
|
Identity: []stanza.Identity{identity},
|
||
|
|
Features: []stanza.Feature{
|
||
|
|
{Var: stanza.NSDiscoInfo},
|
||
|
|
{Var: stanza.NSDiscoItems},
|
||
|
|
{Var: "jabber:iq:version"},
|
||
|
|
{Var: "urn:xmpp:delegation:1"},
|
||
|
|
{Var: "http://jabber.org/protocol/muc"},
|
||
|
|
{Var: "urn:xmpp:reply:0"},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
iqResp.Payload = &payload
|
||
|
|
s.Send(iqResp)
|
||
|
|
})
|
||
|
|
|
||
|
|
router.NewRoute().IQNamespaces("jabber:iq:version").HandlerFunc(func(s xmpp.Sender, p stanza.Packet) {
|
||
|
|
iq, ok := p.(*stanza.IQ)
|
||
|
|
if !ok || iq.Type != stanza.IQTypeGet {
|
||
|
|
return
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
iqResp, err := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id})
|
||
|
|
if err != nil {
|
||
|
|
panic(err)
|
||
|
|
}
|
||
|
|
|
||
|
|
v := &stanza.Version{}
|
||
|
|
v = v.SetInfo("Lambda", "1.0", "Windows") // TODO: Allow spoofing and report correct information
|
||
|
|
|
||
|
|
iqResp.Payload = v
|
||
|
|
s.Send(iqResp)
|
||
|
|
})
|
||
|
|
|
||
|
|
router.HandleFunc("message", func(s xmpp.Sender, p stanza.Packet) {
|
||
|
|
m, ok := p.(stanza.Message)
|
||
|
|
if !ok {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
if m.Body == "" {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
originator := jid.MustParse(m.From).Bare().String()
|
||
|
|
|
||
|
|
glib.IdleAdd(func() {
|
||
|
|
uiQueue <- func() {
|
||
|
|
b := gtk.NewBox(gtk.OrientationVertical, 0)
|
||
|
|
ba, ok := generateMessageWidget(p).(*gtk.Box)
|
||
|
|
if ok {
|
||
|
|
b = ba
|
||
|
|
}
|
||
|
|
|
||
|
|
_, ok = tabs[originator]
|
||
|
|
|
||
|
|
if ok {
|
||
|
|
fmt.Println("valid")
|
||
|
|
tabs[originator].msgs.Append(b)
|
||
|
|
scrollToBottomAfterUpdate(scroller)
|
||
|
|
} else {
|
||
|
|
fmt.Println("Got message when the tab does not exist!")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
})
|
||
|
|
})
|
||
|
|
c, err := xmpp.NewClient(&config, router, func(err error) {
|
||
|
|
showErrorDialog(err)
|
||
|
|
panic(err)
|
||
|
|
})
|
||
|
|
if err != nil {
|
||
|
|
showErrorDialog(err)
|
||
|
|
panic(err)
|
||
|
|
}
|
||
|
|
client = c
|
||
|
|
clientroot = c
|
||
|
|
|
||
|
|
cm := xmpp.NewStreamManager(c, func(c xmpp.Sender) {
|
||
|
|
fmt.Println("XMPP client connected")
|
||
|
|
/*
|
||
|
|
*/
|
||
|
|
})
|
||
|
|
|
||
|
|
go func() {
|
||
|
|
err = cm.Run()
|
||
|
|
if err != nil {
|
||
|
|
showErrorDialog(err)
|
||
|
|
panic(err)
|
||
|
|
}
|
||
|
|
}()
|
||
|
|
|
||
|
|
app := gtk.NewApplication("net.sunglocto.lambda", gio.ApplicationFlagsNone)
|
||
|
|
app.ConnectActivate(func() { activate(app) })
|
||
|
|
|
||
|
|
if code := app.Run(os.Args); code > 0 {
|
||
|
|
os.Exit(code)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func activate(app *gtk.Application) {
|
||
|
|
// Load the CSS and apply it globally.
|
||
|
|
gtk.StyleContextAddProviderForDisplay(
|
||
|
|
gdk.DisplayGetDefault(), loadCSS(styleCSS),
|
||
|
|
gtk.STYLE_PROVIDER_PRIORITY_APPLICATION,
|
||
|
|
)
|
||
|
|
|
||
|
|
window := gtk.NewApplicationWindow(app)
|
||
|
|
app.SetMenubar(gio.NewMenu())
|
||
|
|
|
||
|
|
window.SetTitle("Lambda")
|
||
|
|
menu := gtk.NewBox(gtk.OrientationHorizontal, 0)
|
||
|
|
/*
|
||
|
|
f_menu := gtk.NewMenuButton()
|
||
|
|
f_menu.SetLabel("File")
|
||
|
|
f_menu.SetAlwaysShowArrow(false)
|
||
|
|
|
||
|
|
e_menu := gtk.NewMenuButton()
|
||
|
|
e_menu.SetLabel("Edit")
|
||
|
|
e_menu.SetAlwaysShowArrow(false)
|
||
|
|
|
||
|
|
v_menu := gtk.NewMenuButton()
|
||
|
|
v_menu.SetLabel("View")
|
||
|
|
v_menu.SetAlwaysShowArrow(false)
|
||
|
|
|
||
|
|
b_menu := gtk.NewMenuButton()
|
||
|
|
b_menu.SetLabel("Bookmarks")
|
||
|
|
b_menu.SetAlwaysShowArrow(false)
|
||
|
|
|
||
|
|
h_menu := gtk.NewMenuButton()
|
||
|
|
h_menu.SetLabel("Help")
|
||
|
|
h_menu.SetAlwaysShowArrow(false)
|
||
|
|
|
||
|
|
menu.Append(f_menu)
|
||
|
|
menu.Append(e_menu)
|
||
|
|
menu.Append(v_menu)
|
||
|
|
menu.Append(b_menu)
|
||
|
|
menu.Append(h_menu)
|
||
|
|
*/
|
||
|
|
|
||
|
|
empty_dialog = gtk.NewLabel("You are not focused on any chats.")
|
||
|
|
empty_dialog.SetVExpand(true)
|
||
|
|
|
||
|
|
scroller = gtk.NewScrolledWindow()
|
||
|
|
|
||
|
|
box := gtk.NewBox(gtk.OrientationVertical, 0)
|
||
|
|
// scroller.SetChild(empty_dialog)
|
||
|
|
scroller.SetChild(empty_dialog)
|
||
|
|
menu_scroll := gtk.NewScrolledWindow()
|
||
|
|
menu_scroll.SetChild(menu)
|
||
|
|
box.Append(menu_scroll)
|
||
|
|
box.Append(scroller)
|
||
|
|
|
||
|
|
entry_box := gtk.NewBox(gtk.OrientationHorizontal, 0)
|
||
|
|
|
||
|
|
en := gtk.NewEntry()
|
||
|
|
en.SetPlaceholderText("Say something, what else are you gonna do here?")
|
||
|
|
b := gtk.NewButtonWithLabel("Send")
|
||
|
|
|
||
|
|
sendtxt := func() {
|
||
|
|
t := en.Text()
|
||
|
|
if t == "" {
|
||
|
|
dialog := >k.AlertDialog{}
|
||
|
|
dialog.SetDetail("detail")
|
||
|
|
dialog.SetMessage("message")
|
||
|
|
dialog.SetButtons([]string{"yes, no"})
|
||
|
|
dialog.Choose(context.TODO(), &window.Window, nil)
|
||
|
|
}
|
||
|
|
|
||
|
|
err := sendMessage(client, current, stanza.MessageTypeGroupchat, t, "", "")
|
||
|
|
if err != nil {
|
||
|
|
panic(err) // TODO: Show error message via GTK
|
||
|
|
}
|
||
|
|
en.SetText("")
|
||
|
|
scrollToBottomAfterUpdate(scroller)
|
||
|
|
}
|
||
|
|
|
||
|
|
en.Connect("activate", sendtxt)
|
||
|
|
|
||
|
|
b.ConnectClicked(sendtxt)
|
||
|
|
|
||
|
|
en.SetHExpand(true)
|
||
|
|
|
||
|
|
entry_box.Append(en)
|
||
|
|
entry_box.Append(b)
|
||
|
|
|
||
|
|
debug_btn := gtk.NewButtonWithLabel("Join muc")
|
||
|
|
|
||
|
|
debug_btn.ConnectClicked(func() {
|
||
|
|
showErrorDialog(errors.New("test error"))
|
||
|
|
t := en.Text()
|
||
|
|
_, ok := tabs[t]
|
||
|
|
if !ok {
|
||
|
|
err := joinMuc(client, clientroot.Session.BindJid, t, loadedConfig.Nick)
|
||
|
|
if err != nil {
|
||
|
|
panic(err)
|
||
|
|
}
|
||
|
|
|
||
|
|
createTab(t, true)
|
||
|
|
b := gtk.NewButtonWithLabel(t)
|
||
|
|
b.ConnectClicked(func() {
|
||
|
|
b.AddCSSClass("accent")
|
||
|
|
switchToTab(t)
|
||
|
|
})
|
||
|
|
menu.Append(b)
|
||
|
|
}
|
||
|
|
|
||
|
|
})
|
||
|
|
|
||
|
|
entry_box.Append(debug_btn)
|
||
|
|
|
||
|
|
box.Append(entry_box)
|
||
|
|
|
||
|
|
window.SetChild(box)
|
||
|
|
|
||
|
|
window.SetVisible(true)
|
||
|
|
}
|
||
|
|
|
||
|
|
func loadCSS(content string) *gtk.CSSProvider {
|
||
|
|
prov := gtk.NewCSSProvider()
|
||
|
|
prov.LoadFromString(content)
|
||
|
|
return prov
|
||
|
|
}
|