835 lines
19 KiB
Go
835 lines
19 KiB
Go
package main
|
|
|
|
import (
|
|
"os"
|
|
"sync"
|
|
|
|
"context"
|
|
"fmt"
|
|
"github.com/diamondburned/gotk4/pkg/gdk/v4"
|
|
"github.com/diamondburned/gotk4/pkg/gdkpixbuf/v2"
|
|
"github.com/diamondburned/gotk4/pkg/gio/v2"
|
|
"github.com/diamondburned/gotk4/pkg/glib/v2"
|
|
"github.com/diamondburned/gotk4/pkg/gtk/v4"
|
|
"github.com/gen2brain/beeep"
|
|
"github.com/go-analyze/charts"
|
|
"path/filepath"
|
|
|
|
"github.com/BurntSushi/toml"
|
|
"gosrc.io/xmpp"
|
|
"gosrc.io/xmpp/stanza"
|
|
"mellium.im/xmpp/jid"
|
|
"time"
|
|
|
|
_ "embed"
|
|
"encoding/xml"
|
|
"github.com/kr/pretty"
|
|
"runtime"
|
|
)
|
|
|
|
var loadedConfig lambdaConfig
|
|
|
|
var empty_dialog *gtk.Image
|
|
|
|
var connectionStatus *gtk.Label
|
|
var connectionIcon *gtk.Image
|
|
|
|
var mStatus *gtk.Label
|
|
var mIcon *gtk.Image
|
|
|
|
var pingStatus *gtk.Label
|
|
|
|
// var msgs *gtk.ListBox
|
|
var content *gtk.Widgetter
|
|
|
|
// var tabs map[string]*chatTab = make(map[string]*chatTab)
|
|
var tabs sync.Map
|
|
var current string
|
|
|
|
var scroller *gtk.ScrolledWindow
|
|
var memberList *gtk.ScrolledWindow
|
|
var menu *gtk.Box
|
|
|
|
//go:embed style.css
|
|
var styleCSS string
|
|
var client xmpp.Sender
|
|
var clientroot *xmpp.Client
|
|
|
|
var uiQueue = make(chan func(), 100)
|
|
|
|
var window *gtk.ApplicationWindow
|
|
|
|
// stores members of mucs
|
|
var mucmembers sync.Map
|
|
|
|
// stores devices of users
|
|
var userdevices sync.Map
|
|
|
|
var pingTimes = [][]float64{}
|
|
|
|
var clientAssets map[string]gdk.Paintabler = make(map[string]gdk.Paintabler)
|
|
|
|
func init() {
|
|
beeep.AppName = "Lambda"
|
|
|
|
go func() {
|
|
for fn := range uiQueue {
|
|
glib.IdleAdd(func() bool {
|
|
fn()
|
|
return false
|
|
})
|
|
time.Sleep(10 * time.Millisecond) // Small delay between updates
|
|
}
|
|
}()
|
|
|
|
}
|
|
|
|
func main() {
|
|
pingTimes = append(pingTimes, []float64{})
|
|
p, err := ensureConfig()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
b, err := os.ReadFile(filepath.Join(p, "lambda.toml"))
|
|
if err != nil {
|
|
dropToSignInPage(err)
|
|
return
|
|
// panic(err)
|
|
}
|
|
|
|
_, err = toml.Decode(string(b), &loadedConfig)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
if loadedConfig.Resource == "" {
|
|
fmt.Println("Config resource is empty! Generating a random one")
|
|
loadedConfig.Resource = randomClientResource()
|
|
}
|
|
|
|
config := xmpp.Config{
|
|
TransportConfiguration: xmpp.TransportConfiguration{
|
|
Address: loadedConfig.Server,
|
|
},
|
|
Jid: loadedConfig.Username + "/" + loadedConfig.Resource,
|
|
Credential: xmpp.Password(loadedConfig.Password),
|
|
Insecure: loadedConfig.Insecure,
|
|
// StreamLogger: os.Stdout,
|
|
StreamManagementEnable: true,
|
|
}
|
|
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: "λ"},
|
|
},
|
|
}
|
|
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", lambda_version, runtime.GOOS) // TODO: Allow spoofing on user request
|
|
|
|
iqResp.Payload = v
|
|
s.Send(iqResp)
|
|
})
|
|
|
|
router.HandleFunc("message", func(s xmpp.Sender, p stanza.Packet) {
|
|
m, ok := p.(stanza.Message)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
pretty.Println(m)
|
|
|
|
e := stanza.PubSubEvent{}
|
|
ok = m.Get(&e)
|
|
if ok {
|
|
fmt.Println(e)
|
|
}
|
|
|
|
/*
|
|
if m.Body == "" {
|
|
return
|
|
}
|
|
*/
|
|
|
|
originator := JidMustParse(m.From).Bare()
|
|
mStatus.SetText(originator)
|
|
|
|
glib.IdleAdd(func() {
|
|
//uiQueue <- func() {
|
|
b := gtk.NewBox(gtk.OrientationVertical, 0)
|
|
|
|
tab, ok := tabs.Load(originator)
|
|
typed_tab := tab.(*chatTab)
|
|
|
|
if ok {
|
|
typed_tab.msgs.Append(b)
|
|
scrollToBottomAfterUpdate(scroller)
|
|
} else {
|
|
fmt.Println("Got message when the tab does not exist!")
|
|
}
|
|
|
|
ba, ok := generateMessageWidget(p).(*gtk.Box)
|
|
if ok {
|
|
b.Append(ba)
|
|
}
|
|
//}
|
|
})
|
|
})
|
|
|
|
router.HandleFunc("presence", func(s xmpp.Sender, p stanza.Packet) {
|
|
presence, ok := p.(stanza.Presence)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
if presence.Error.Reason != "" {
|
|
beeep.Notify(fmt.Sprintf("%s : %s", presence.From, presence.Error.Reason), presence.Error.Text, cancelBytes)
|
|
return
|
|
}
|
|
|
|
var mu MucUser
|
|
var ocu OccupantID
|
|
|
|
ok = presence.Get(&mu)
|
|
|
|
if ok { // This is a presence stanza from a user in a MUC
|
|
presence.Get(&ocu)
|
|
id := ocu.ID
|
|
if id == "" {
|
|
id = JidMustParse(presence.From).Resource
|
|
}
|
|
from, _ := stanza.NewJid(presence.From)
|
|
muc := from.Bare()
|
|
_, ok = mucmembers.Load(muc)
|
|
if !ok {
|
|
mucmembers.Store(muc, mucUnit{})
|
|
}
|
|
|
|
unit, ok := mucmembers.Load(muc)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
typed_unit := unit.(mucUnit)
|
|
|
|
if presence.Type != "unavailable" {
|
|
_, ok := typed_unit.Members.Load(id)
|
|
if !ok {
|
|
glib.IdleAdd(func() {
|
|
b := gtk.NewBox(gtk.OrientationVertical, 0)
|
|
ba, ok := generatePresenceWidget(p).(*gtk.Box)
|
|
if ok {
|
|
b = ba
|
|
}
|
|
|
|
tab, ok := tabs.Load(muc)
|
|
typed_tab := tab.(*chatTab)
|
|
|
|
if ok {
|
|
typed_tab.msgs.Append(b)
|
|
scrollToBottomAfterUpdate(scroller)
|
|
} else {
|
|
fmt.Println("Got message when the tab does not exist!")
|
|
}
|
|
})
|
|
}
|
|
typed_unit.Members.Store(id, presence)
|
|
} else {
|
|
typed_unit.Members.Delete(id)
|
|
glib.IdleAdd(func() {
|
|
b := gtk.NewBox(gtk.OrientationVertical, 0)
|
|
ba, ok := generatePresenceWidget(p).(*gtk.Box)
|
|
if ok {
|
|
b = ba
|
|
}
|
|
|
|
tab, ok := tabs.Load(muc)
|
|
typed_tab := tab.(*chatTab)
|
|
|
|
if ok {
|
|
typed_tab.msgs.Append(b)
|
|
scrollToBottomAfterUpdate(scroller)
|
|
} else {
|
|
fmt.Println("Got message when the tab does not exist!")
|
|
}
|
|
})
|
|
}
|
|
|
|
mucmembers.Store(muc, typed_unit)
|
|
|
|
} else { // This is a presence stanza from a regular user
|
|
// The code is basically the exact same as above, we just don't check for mucuser
|
|
user := jid.MustParse(presence.From).Bare().String()
|
|
_, ok := userdevices.Load(user)
|
|
_, mok := mucmembers.Load(user)
|
|
if !ok && !mok { // FIXME: The initial muc presence gets picked up from this check
|
|
ok := createTab(user, false)
|
|
if ok {
|
|
userdevices.Store(user, userUnit{})
|
|
|
|
b := gtk.NewLabel(user)
|
|
gesture1 := gtk.NewGestureClick()
|
|
gesture1.SetButton(1)
|
|
gesture1.Connect("pressed", func() {
|
|
switchToTab(user, &window.Window)
|
|
})
|
|
|
|
b.AddController(gesture1)
|
|
menu.Append(b)
|
|
}
|
|
}
|
|
|
|
unit, ok := userdevices.Load(user)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
resource := jid.MustParse(presence.From).Resourcepart()
|
|
|
|
typed_unit := unit.(userUnit)
|
|
|
|
if presence.Type != "unavailable" {
|
|
typed_unit.Devices.Store(resource, presence)
|
|
} else {
|
|
typed_unit.Devices.Delete(resource)
|
|
}
|
|
|
|
userdevices.Store(user, typed_unit)
|
|
}
|
|
time.Sleep(1 * time.Second)
|
|
})
|
|
|
|
c, err := xmpp.NewClient(&config, router, func(err error) {
|
|
connectionStatus.SetText(fmt.Sprintf("Disconnected: %s", err.Error()))
|
|
connectionIcon.SetFromPaintable(clientAssets["disconnect"])
|
|
})
|
|
|
|
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() {
|
|
for {
|
|
time.Sleep(5 * time.Second)
|
|
pingStatus.AddCSSClass("pending")
|
|
before := time.Now()
|
|
iq := new(stanza.IQ)
|
|
iq.From = clientroot.Session.BindJid
|
|
iq.To = iq.From
|
|
iq.Type = "get"
|
|
|
|
ctx, _ := context.WithTimeout(context.Background(), 30*time.Second)
|
|
mychan, err := client.SendIQ(ctx, iq)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
_ = <-mychan
|
|
|
|
pingStatus.RemoveCSSClass("pending")
|
|
delay := time.Since(before) / time.Millisecond
|
|
pingStatus.SetText(fmt.Sprintf("%d ms", delay))
|
|
pingTimes[0] = append(pingTimes[0], float64(delay))
|
|
|
|
}
|
|
}()
|
|
connectionStatus.SetText(fmt.Sprintf("Connected as %s", JidMustParse(clientroot.Session.BindJid).Bare()))
|
|
connectionStatus.SetTooltipText(fmt.Sprintf("Binded JID: %s\nUsing TLS: %t", clientroot.Session.BindJid, clientroot.Session.TlsEnabled))
|
|
connectionIcon.SetFromPaintable(clientAssets["connect"])
|
|
// Join rooms in bookmarks
|
|
if loadedConfig.JoinBookmarks {
|
|
books, err := stanza.NewItemsRequest("", "urn:xmpp:bookmarks:1", 0)
|
|
if err == nil {
|
|
mychan, err := c.SendIQ(context.TODO(), books)
|
|
result := <-mychan
|
|
if err == nil {
|
|
res, ok := result.Payload.(*stanza.PubSubGeneric)
|
|
if ok {
|
|
for _, item := range res.Items.List {
|
|
go func() {
|
|
jid := item.Id
|
|
node := item.Any
|
|
autojoin := false
|
|
nick := loadedConfig.Nick
|
|
for _, attr := range node.Attrs {
|
|
if attr.Name.Local == "autojoin" {
|
|
autojoin = attr.Value == "true"
|
|
}
|
|
}
|
|
|
|
for _, node := range node.Nodes {
|
|
if node.XMLName.Local == "nick" {
|
|
nick = node.Content
|
|
}
|
|
}
|
|
|
|
_, ok := tabs.Load(jid)
|
|
if !ok && autojoin {
|
|
err := joinMuc(client, clientroot.Session.BindJid, jid, nick)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
createTab(jid, true)
|
|
b := gtk.NewLabel(jid)
|
|
gesture1 := gtk.NewGestureClick()
|
|
gesture1.SetButton(1)
|
|
gesture1.Connect("pressed", func() {
|
|
switchToTab(jid, &window.Window)
|
|
})
|
|
|
|
b.AddController(gesture1)
|
|
menu.Append(b)
|
|
}
|
|
}()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
go func() {
|
|
time.Sleep(3 * time.Second)
|
|
connectionStatus.SetText("Connecting...")
|
|
connectionIcon.SetFromPaintable(clientAssets["hourglass"])
|
|
|
|
err = cm.Run()
|
|
if err != nil {
|
|
fmt.Println(err.Error())
|
|
connectionStatus.SetText(fmt.Sprintf("Disconnected: %s", err.Error()))
|
|
connectionIcon.SetFromPaintable(clientAssets["disconnect"])
|
|
}
|
|
}()
|
|
|
|
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)
|
|
the_menu := gio.NewMenu()
|
|
|
|
fileMenu := gio.NewMenu()
|
|
fileMenu.Append("Join MUC", "app.join")
|
|
fileMenu.Append("Start DM", "app.dm")
|
|
fileMenu.Append("Destroy MUC", "app.destroymuc")
|
|
|
|
helpMenu := gio.NewMenu()
|
|
helpMenu.Append("About", "app.about")
|
|
|
|
aboutAction := gio.NewSimpleAction("about", nil)
|
|
aboutAction.ConnectActivate(func(p *glib.Variant) {
|
|
a := gtk.AboutDialog{}
|
|
a.SetVisible(true)
|
|
})
|
|
|
|
destroymucAction := gio.NewSimpleAction("destroymuc", nil)
|
|
destroymucAction.ConnectActivate(func(p *glib.Variant) {
|
|
cur, ok := tabs.Load(current)
|
|
if ok {
|
|
cur := cur.(*chatTab)
|
|
if cur.isMuc {
|
|
win := gtk.NewWindow()
|
|
win.SetTitle("Destroy MUC")
|
|
win.SetDefaultSize(400, 1)
|
|
win.SetResizable(false)
|
|
|
|
box := gtk.NewBox(gtk.OrientationVertical, 0)
|
|
box.Append(gtk.NewLabel("Are you sure? This MUC will be gone forever! (a very long time)"))
|
|
box.Append(gtk.NewLabel("If you wish to continue, type 'I understand'"))
|
|
cancel := gtk.NewButtonWithLabel("Cancel")
|
|
cancel.ConnectClicked(func() {
|
|
win.SetVisible(false)
|
|
})
|
|
en := gtk.NewEntry()
|
|
en.SetPlaceholderText("...")
|
|
|
|
submit := gtk.NewButtonWithLabel("Destroy")
|
|
submit.ConnectClicked(func() {
|
|
fmt.Println(en.Text())
|
|
if en.Text() == "I understand" {
|
|
cur, ok := tabs.Load(current)
|
|
if ok {
|
|
cur := cur.(*chatTab)
|
|
if cur.isMuc {
|
|
client.SendRaw(fmt.Sprintf(`
|
|
<iq from='%s'
|
|
id='begone'
|
|
to='%s'
|
|
type='set'>
|
|
<query xmlns='http://jabber.org/protocol/muc#owner'>
|
|
<destroy jid='%s'>
|
|
<reason>User requested</reason>
|
|
</destroy>
|
|
</query>
|
|
</iq>
|
|
`, clientroot.Session.BindJid, current, JidMustParse(clientroot.Session.BindJid).Bare()))
|
|
}
|
|
}
|
|
win.SetVisible(false)
|
|
}
|
|
})
|
|
|
|
box.Append(en)
|
|
box.Append(submit)
|
|
box.Append(cancel)
|
|
|
|
mu, ok := mucmembers.Load(current)
|
|
if ok {
|
|
typed_mu := mu.(mucUnit)
|
|
typed_mu.Members.Range(func(k, v any) bool {
|
|
user, ok := v.(stanza.Presence)
|
|
if ok {
|
|
mu := MucUser{}
|
|
ok := user.Get(&mu)
|
|
if ok {
|
|
if mu.MucUserItem.JID != "" {
|
|
if JidMustParse(mu.MucUserItem.JID).Bare() == JidMustParse(clientroot.Session.BindJid).Bare() {
|
|
if mu.MucUserItem.Affiliation != "owner" {
|
|
box.Append(gtk.NewLabel("You are not an owner of this MUC and thus will most likely not be able to delete it"))
|
|
}
|
|
// return false
|
|
}
|
|
}
|
|
} else {
|
|
panic("not ok")
|
|
}
|
|
} else {
|
|
panic("not ok")
|
|
}
|
|
return true
|
|
})
|
|
} else {
|
|
panic("not ok")
|
|
}
|
|
|
|
win.SetChild(box)
|
|
win.SetVisible(true)
|
|
}
|
|
}
|
|
|
|
})
|
|
|
|
joinAction := gio.NewSimpleAction("join", nil)
|
|
joinAction.ConnectActivate(func(p *glib.Variant) {
|
|
box := gtk.NewBox(gtk.OrientationVertical, 0)
|
|
jid_box := gtk.NewBox(gtk.OrientationHorizontal, 0)
|
|
nick_box := gtk.NewBox(gtk.OrientationHorizontal, 0)
|
|
|
|
jid_entry := gtk.NewEntry()
|
|
nick_entry := gtk.NewEntry()
|
|
|
|
jid_entry.SetHAlign(gtk.AlignEnd)
|
|
jid_entry.SetHExpand(true)
|
|
|
|
nick_entry.SetHAlign(gtk.AlignEnd)
|
|
nick_entry.SetHExpand(true)
|
|
|
|
nick_entry.SetText(loadedConfig.Nick)
|
|
|
|
jid_box.Append(gtk.NewLabel("MUC JID:"))
|
|
jid_box.Append(jid_entry)
|
|
|
|
nick_box.Append(gtk.NewLabel("Nick:"))
|
|
nick_box.Append(nick_entry)
|
|
|
|
box.Append(jid_box)
|
|
box.Append(nick_box)
|
|
|
|
btn := gtk.NewButtonWithLabel("Submit")
|
|
btn.SetVAlign(gtk.AlignBaseline)
|
|
|
|
box.Append(btn)
|
|
|
|
win := gtk.NewWindow()
|
|
win.SetTitle("Join MUC")
|
|
win.SetDefaultSize(400, 1)
|
|
win.SetResizable(false)
|
|
win.SetChild(box)
|
|
|
|
btn.ConnectClicked(func() {
|
|
t := jid_entry.Text()
|
|
_, ok := tabs.Load(t)
|
|
if !ok {
|
|
err := joinMuc(client, clientroot.Session.BindJid, t, nick_entry.Text())
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
createTab(t, true)
|
|
b := gtk.NewLabel(t)
|
|
gesture1 := gtk.NewGestureClick()
|
|
gesture1.SetButton(1)
|
|
gesture1.Connect("pressed", func() {
|
|
switchToTab(t, &window.Window)
|
|
})
|
|
|
|
b.AddController(gesture1)
|
|
menu.Append(b)
|
|
}
|
|
win.SetVisible(false)
|
|
})
|
|
|
|
win.SetTransientFor(win)
|
|
win.Present()
|
|
})
|
|
|
|
app.AddAction(joinAction)
|
|
app.AddAction(aboutAction)
|
|
app.AddAction(destroymucAction)
|
|
|
|
the_menu.AppendSubmenu("File", fileMenu)
|
|
the_menu.AppendSubmenu("Help", helpMenu)
|
|
|
|
the_menuBar := gtk.NewPopoverMenuBarFromModel(the_menu)
|
|
app.SetMenubar(gio.NewMenu())
|
|
|
|
window.SetTitle("Lambda")
|
|
window.Window.AddCSSClass("ssd")
|
|
window.Window.SetDefaultSize(500, 500)
|
|
menu = gtk.NewBox(gtk.OrientationVertical, 0)
|
|
|
|
empty_dialog = gtk.NewImageFromPaintable(clientAssets["disabled_logo"])
|
|
empty_dialog.SetPixelSize(100)
|
|
empty_dialog.SetVExpand(true)
|
|
|
|
scroller = gtk.NewScrolledWindow()
|
|
memberList = gtk.NewScrolledWindow()
|
|
|
|
scroller.SetHExpand(true)
|
|
memberList.SetHExpand(true)
|
|
|
|
box := gtk.NewBox(gtk.OrientationVertical, 0)
|
|
box.Append(the_menuBar)
|
|
|
|
statBar := gtk.NewBox(gtk.OrientationHorizontal, 0)
|
|
|
|
cBox := gtk.NewBox(gtk.OrientationHorizontal, 0)
|
|
connectionIcon = gtk.NewImageFromPaintable((clientAssets["disconnect"]))
|
|
connectionIcon.AddCSSClass("icon")
|
|
connectionStatus = gtk.NewLabel("Disconnected")
|
|
|
|
cBox.Append(connectionIcon)
|
|
cBox.Append(connectionStatus)
|
|
|
|
statBar.Append(cBox)
|
|
|
|
mBox := gtk.NewBox(gtk.OrientationHorizontal, 0)
|
|
|
|
gesture1 := gtk.NewGestureClick()
|
|
gesture1.SetButton(1)
|
|
gesture1.Connect("pressed", func() {
|
|
current = mStatus.Text()
|
|
switchToTab(current, &window.Window)
|
|
})
|
|
|
|
mIcon = gtk.NewImageFromPaintable((clientAssets["comment"]))
|
|
mIcon.AddCSSClass("icon")
|
|
mStatus = gtk.NewLabel("-")
|
|
mStatus.AddController(gesture1)
|
|
|
|
cBox.Append(mIcon)
|
|
cBox.Append(mStatus)
|
|
|
|
statBar.Append(mBox)
|
|
|
|
pBox := gtk.NewBox(gtk.OrientationHorizontal, 0)
|
|
pBox.SetTooltipText("Ping between you and your XMPP server\nRight-click to see graph")
|
|
gesture := gtk.NewGestureClick()
|
|
gesture.SetButton(3)
|
|
gesture.Connect("pressed", func() {
|
|
opt := charts.NewLineChartOptionWithData(pingTimes)
|
|
opt.Title = charts.TitleOption{
|
|
Text: "Server latency",
|
|
}
|
|
/*
|
|
opt.XAxis.Labels = []string{
|
|
// The 7 labels here match to the 7 values above
|
|
"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun",
|
|
}*/
|
|
opt.Legend = charts.LegendOption{
|
|
SeriesNames: []string{
|
|
"Ping (ms)",
|
|
},
|
|
}
|
|
|
|
opt.StrokeSmoothingTension = 0.9
|
|
|
|
p := charts.NewPainter(charts.PainterOptions{
|
|
Width: 600,
|
|
Height: 400,
|
|
})
|
|
err := p.LineChart(opt)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
buf, err := p.Bytes()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
loader := gdkpixbuf.NewPixbufLoader()
|
|
loader.Write(buf)
|
|
loader.Close()
|
|
|
|
i := gtk.NewPictureForPaintable(gdk.NewTextureForPixbuf(loader.Pixbuf()))
|
|
win := gtk.NewWindow()
|
|
win.SetDefaultSize(600, 400)
|
|
win.SetTitle("Server latency")
|
|
box := gtk.NewBox(gtk.OrientationVertical, 0)
|
|
box.Append(i)
|
|
win.SetChild(box)
|
|
win.SetVisible(true)
|
|
})
|
|
pBox.AddController(gesture)
|
|
|
|
i := (gtk.NewImageFromPaintable(clientAssets["chart_bar"]))
|
|
i.AddCSSClass("icon")
|
|
pBox.Append(i)
|
|
pingStatus = gtk.NewLabel("...")
|
|
pBox.Append(pingStatus)
|
|
statBar.Append(pBox)
|
|
|
|
scrollerStatBar := gtk.NewScrolledWindow()
|
|
scrollerStatBar.SetChild(statBar)
|
|
box.Append(scrollerStatBar)
|
|
|
|
// scroller.SetChild(empty_dialog)
|
|
scroller.SetChild(empty_dialog)
|
|
menu_scroll := gtk.NewScrolledWindow()
|
|
menu_scroll.SetHExpand(true)
|
|
|
|
menu_scroll.SetChild(menu)
|
|
// box.Append(menu_scroll)
|
|
|
|
chatbox := gtk.NewBox(gtk.OrientationHorizontal, 0)
|
|
// chatbox.Append(menu_scroll)
|
|
chat_pane := gtk.NewPaned(gtk.OrientationHorizontal)
|
|
chat_pane.SetStartChild(scroller)
|
|
chat_pane.SetEndChild(memberList)
|
|
chat_pane.SetPosition(225)
|
|
|
|
main_pane := gtk.NewPaned(gtk.OrientationHorizontal)
|
|
main_pane.SetStartChild(menu_scroll)
|
|
main_pane.SetEndChild(chat_pane)
|
|
main_pane.SetPosition(135)
|
|
|
|
chatbox.Append(main_pane)
|
|
box.Append(chatbox)
|
|
|
|
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)
|
|
}
|
|
|
|
message_type := stanza.MessageTypeChat
|
|
tab, ok := tabs.Load(current)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
typed_tab := tab.(*chatTab)
|
|
if typed_tab.isMuc {
|
|
message_type = stanza.MessageTypeGroupchat
|
|
}
|
|
|
|
err := sendMessage(client, current, message_type, 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)
|
|
|
|
box.Append(entry_box)
|
|
|
|
window.SetChild(box)
|
|
|
|
window.SetVisible(true)
|
|
}
|
|
|
|
func loadCSS(content string) *gtk.CSSProvider {
|
|
prov := gtk.NewCSSProvider()
|
|
prov.LoadFromString(content)
|
|
return prov
|
|
}
|