Files
lambda/gtk-helpers.go
T

534 lines
13 KiB
Go
Raw Normal View History

2026-01-29 21:35:36 +00:00
package main
import (
2026-04-06 11:04:53 +01:00
"bytes"
2026-01-31 15:08:54 +00:00
"context"
2026-01-30 15:56:58 +00:00
"fmt"
2026-04-06 11:04:53 +01:00
"github.com/boxes-ltd/imaging"
"github.com/crazy3lf/colorconv"
"github.com/diamondburned/gotk4/pkg/gdk/v4"
2026-04-06 11:04:53 +01:00
"github.com/diamondburned/gotk4/pkg/gdkpixbuf/v2"
2026-01-29 21:35:36 +00:00
"github.com/diamondburned/gotk4/pkg/glib/v2"
"github.com/diamondburned/gotk4/pkg/gtk/v4"
2026-02-03 10:38:41 +00:00
"github.com/diamondburned/gotk4/pkg/pango"
2026-04-06 11:04:53 +01:00
"github.com/rrivera/identicon"
2026-01-30 10:40:38 +00:00
"gosrc.io/xmpp/stanza"
2026-04-06 11:04:53 +01:00
"image"
"image/png"
xmpp_color "mellium.im/xmpp/color"
"strconv"
2026-01-29 21:35:36 +00:00
)
func scrollToBottomAfterUpdate(scrolledWindow *gtk.ScrolledWindow) {
glib.IdleAdd(func() bool {
vAdj := scrolledWindow.VAdjustment()
vAdj.SetValue(vAdj.Upper() - vAdj.PageSize())
return false // Return false to run only once
})
}
2026-04-26 10:40:13 +01:00
func createTab(jid string, isMuc bool, name string) bool {
fmt.Println("Creating tab", jid, "isMuc:", isMuc)
_, ok := tabs.Load(jid)
2026-02-03 10:38:41 +00:00
_, uok := userdevices.Load(jid)
_, mok := mucmembers.Load(jid)
if !ok && !uok && !mok {
2026-01-29 21:35:36 +00:00
newTab := new(chatTab)
newTab.isMuc = isMuc
newTab.msgs = gtk.NewListBox()
newTab.msgs.SetVExpand(true)
newTab.msgs.SetShowSeparators(true)
2026-04-26 10:40:13 +01:00
newTab.name = name
2026-01-29 21:35:36 +00:00
2026-04-28 12:58:00 +01:00
newTab.msgs.Append(gtk.NewButtonWithLabel(loadedLocale["getPastMessages"]))
tabs.Store(jid, newTab)
return true
2026-01-29 21:35:36 +00:00
}
return false
2026-01-29 21:35:36 +00:00
}
2026-01-31 15:38:02 +00:00
func switchToTab(jid string, w *gtk.Window) {
2026-01-29 21:35:36 +00:00
current = jid
tab, ok := tabs.Load(current)
if !ok {
return
}
2026-01-30 10:40:38 +00:00
typed_tab := tab.(*chatTab)
scroller.SetChild(typed_tab.msgs)
typingStatus.SetText("")
if typed_tab.isMuc {
2026-02-04 10:12:49 +00:00
m, ok := mucmembers.Load(jid)
if !ok {
return
}
ma, ok := m.(mucUnit)
if !ok {
return
}
mm := ma.Members
2026-04-28 12:58:00 +01:00
gen := gtk.NewBox(gtk.OrientationVertical, 10)
2026-01-30 15:56:58 +00:00
i := 0
2026-04-06 11:04:53 +01:00
rangeOrdered(&mm, (func(k, v any) bool {
i++
2026-04-28 12:58:00 +01:00
userbox := gtk.NewBox(gtk.OrientationHorizontal, 2)
2026-01-30 15:56:58 +00:00
u := v.(stanza.Presence)
var mu MucUser
var ocu OccupantID
u.Get(&mu)
u.Get(&ocu)
2026-04-06 11:04:53 +01:00
if mu.MucUserItem.Role == "moderator" {
gen.Prepend(userbox)
} else {
gen.Append(userbox)
}
//id := ocu.ID
//if id == "" {
id := JidMustParse(u.From).Resource
//}
2026-01-30 15:56:58 +00:00
nick_label := gtk.NewLabel(JidMustParse(u.From).Resource)
2026-02-03 10:38:41 +00:00
nick_label.SetEllipsize(pango.EllipsizeEnd)
2026-02-02 13:23:08 +00:00
nick_label.AddCSSClass(mu.MucUserItem.Role)
if mu.MucUserItem.Role == "visitor" {
nick_label.SetOpacity(0.5)
}
2026-04-28 12:58:00 +01:00
userbox.SetTooltipText(fmt.Sprintf("%s\n%s\n%s\n%s", u.From, mu.MucUserItem.Role, mu.MucUserItem.Affiliation, loadedLocale["clickForMoreInfo"]))
userbox.Append(nick_label)
2026-02-02 13:23:08 +00:00
var hats Hats
ok := u.Get(&hats)
if ok {
for _, hat := range hats.Hats {
2026-04-06 11:04:53 +01:00
var val float64
if hat.Hue != "" {
tval, _ := strconv.Atoi(hat.Hue)
val = float64(tval)
} else {
xc := xmpp_color.String(hat.URI, 255, loadedConfig.CVD)
r, g, b, _ := xc.RGBA()
val, _, _ = colorconv.RGBToHSV(uint8(r), uint8(g), uint8(b))
}
tB := tagBytes
img, _, _ := image.Decode(bytes.NewReader(tB))
i_rgba := imaging.AdjustHue(img, val)
var buf bytes.Buffer
png.Encode(&buf, i_rgba)
tB = buf.Bytes()
loader := gdkpixbuf.NewPixbufLoader()
loader.Write(tB)
loader.Close()
tag := gtk.NewPictureForPaintable(gdk.NewTextureForPixbuf(loader.Pixbuf()))
2026-02-16 09:54:37 +00:00
tag.SetTooltipText(hat.Title)
userbox.Prepend(tag)
}
}
status := gtk.NewImageFromPaintable(clientAssets["status_"+string(u.Show)])
status.SetTooltipText(string(u.Show))
status.SetHAlign(gtk.AlignEnd)
// medal.SetHExpand(true)
userbox.Prepend(status)
2026-02-02 13:23:08 +00:00
medal := gtk.NewImageFromPaintable(clientAssets[mu.MucUserItem.Affiliation])
medal.SetTooltipText(mu.MucUserItem.Affiliation)
2026-02-02 13:23:08 +00:00
medal.SetHAlign(gtk.AlignEnd)
medal.SetHExpand(true)
userbox.Append(medal)
2026-01-31 15:08:54 +00:00
gesture := gtk.NewGestureClick()
gesture.SetButton(1)
mod_gesture := gtk.NewGestureClick()
mod_gesture.SetButton(3)
popover := gtk.NewPopover()
popover.SetHasArrow(false)
popover.SetParent(userbox)
rc_box := gtk.NewBox(gtk.OrientationVertical, 0)
2026-04-28 12:58:00 +01:00
bb := gtk.NewButtonWithLabel(loadedLocale["ban"])
kb := gtk.NewButtonWithLabel(loadedLocale["kick"])
ab := gtk.NewButtonWithLabel(loadedLocale["setAffil"])
rb := gtk.NewButtonWithLabel(loadedLocale["setRole"])
kb.ConnectClicked(func() {
client.SendRaw(fmt.Sprintf(`
<iq from='%s'
id='kick1'
to='%s'
type='set'>
<query xmlns='http://jabber.org/protocol/muc#admin'>
<item nick='%s' role='none'>
</item>
</query>
</iq>
`, clientroot.Session.BindJid, jid, JidMustParse(u.From).Resource))
})
bb.ConnectClicked(func() {
var mu MucUser
ok = u.Get(&mu)
if ok {
if mu.MucUserItem.JID != "" {
client.SendRaw(fmt.Sprintf(`
<iq from='%s'
id='ban1'
to='%s'
type='set'>
<query xmlns='http://jabber.org/protocol/muc#admin'>
<item affiliation='outcast' jid='%s'/>
</query>
</iq>
`, clientroot.Session.BindJid, jid, JidMustParse(mu.MucUserItem.JID).Bare()))
}
}
})
ab.ConnectClicked(func() {
var mu MucUser
ok = u.Get(&mu)
if ok {
if mu.MucUserItem.JID != "" {
2026-03-15 09:57:08 +00:00
win := gtk.NewWindow()
win.SetDefaultSize(400, 1)
win.SetResizable(false)
2026-03-15 09:57:08 +00:00
box := gtk.NewBox(gtk.OrientationVertical, 0)
2026-04-28 12:58:00 +01:00
box.Append(gtk.NewLabel(loadedLocale["setAffilDescPartOne"] + JidMustParse(u.From).Resource + loadedLocale["setAffilDescPartTwo"]))
2026-03-15 09:57:08 +00:00
the_entry := gtk.NewEntry()
the_entry.SetText(mu.MucUserItem.Affiliation)
2026-04-28 12:58:00 +01:00
submit := gtk.NewButtonWithLabel(loadedLocale["submit"])
2026-03-15 09:57:08 +00:00
submit.ConnectClicked(func() {
client.SendRaw(fmt.Sprintf(`
<iq from='%s'
id='ba1'
to='%s'
type='set'>
<query xmlns='http://jabber.org/protocol/muc#admin'>
<item affiliation='%s' jid='%s'/>
</query>
</iq>
`, clientroot.Session.BindJid, jid, the_entry.Text(), JidMustParse(mu.MucUserItem.JID).Bare()))
2026-03-15 09:57:08 +00:00
win.SetVisible(false)
})
2026-03-15 09:57:08 +00:00
box.Append(the_entry)
box.Append(submit)
2026-03-15 09:57:08 +00:00
win.SetChild(box)
win.SetVisible(true)
}
}
})
rb.ConnectClicked(func() {
var mu MucUser
ok = u.Get(&mu)
if ok {
if mu.MucUserItem.JID != "" {
2026-03-15 09:57:08 +00:00
win := gtk.NewWindow()
win.SetDefaultSize(400, 1)
win.SetResizable(false)
2026-03-15 09:57:08 +00:00
box := gtk.NewBox(gtk.OrientationVertical, 0)
2026-04-28 12:58:00 +01:00
box.Append(gtk.NewLabel(loadedLocale["setRoleDescPartOne"] + JidMustParse(u.From).Resource + loadedLocale["setRoleDescPartTwo"]))
box.Append(gtk.NewLabel(loadedLocale["setRoleWarning"]))
2026-03-15 09:57:08 +00:00
the_entry := gtk.NewEntry()
the_entry.SetText(mu.MucUserItem.Role)
2026-04-28 12:58:00 +01:00
submit := gtk.NewButtonWithLabel(loadedLocale["submit"])
2026-03-15 09:57:08 +00:00
submit.ConnectClicked(func() {
2026-03-15 09:57:08 +00:00
client.SendRaw(fmt.Sprintf(`
<iq from='%s'
id='kick1'
to='%s'
type='set'>
<query xmlns='http://jabber.org/protocol/muc#admin'>
<item nick='%s' role='%s'>
</item>
</query>
</iq>
`, clientroot.Session.BindJid, jid, JidMustParse(u.From).Resource, the_entry.Text()))
2026-03-15 09:57:08 +00:00
})
2026-03-15 09:57:08 +00:00
box.Append(the_entry)
box.Append(submit)
2026-03-15 09:57:08 +00:00
win.SetChild(box)
win.SetVisible(true)
}
}
})
rc_box.Append(bb)
rc_box.Append(kb)
rc_box.Append(ab)
rc_box.Append(rb)
popover.SetChild(rc_box)
mod_gesture.Connect("pressed", func(n_press, x, y int) {
rect := gdk.NewRectangle(x, y, 1, 1)
popover.SetPointingTo(&rect)
popover.Popup()
})
2026-01-31 15:08:54 +00:00
gesture.Connect("pressed", func(n_press, x, y int) {
win := gtk.NewWindow()
win.SetDefaultSize(400, 400)
profile_box := gtk.NewBox(gtk.OrientationVertical, 0)
nick := gtk.NewLabel(JidMustParse(u.From).Resource)
2026-04-28 12:58:00 +01:00
ver_text := gtk.NewLabel(loadedLocale["gettingVersion"])
2026-03-10 16:35:56 +00:00
ver_text.AddCSSClass("visitor")
2026-02-02 13:23:08 +00:00
win.SetTitle(JidMustParse(u.From).Resource)
2026-01-31 15:08:54 +00:00
nick.AddCSSClass("author")
2026-04-06 11:04:53 +01:00
nick.SetSelectable(true)
2026-01-31 15:08:54 +00:00
profile_box.Append(nick)
2026-03-10 16:35:56 +00:00
profile_box.Append(ver_text)
fr := gtk.NewLabel(u.From)
fr.AddCSSClass("jid")
2026-04-06 11:04:53 +01:00
fr.SetSelectable(true)
2026-03-10 16:35:56 +00:00
profile_box.Append(fr)
profile_box.Append(ver_text)
2026-01-31 15:08:54 +00:00
iqResp, err := stanza.NewIQ(stanza.Attrs{
Type: "get",
From: clientroot.Session.BindJid,
To: u.From,
Id: "vc2",
Lang: "en",
})
if err != nil {
panic(err)
}
iqResp.Payload = &stanza.Version{}
loading_version_text := gtk.NewLabel("...")
var hats Hats
ok := u.Get(&hats)
if ok {
for _, hat := range hats.Hats {
l := gtk.NewLabel(hat.Title)
l.AddCSSClass("hat")
profile_box.Append(l)
2026-01-31 15:08:54 +00:00
}
}
var mu MucUser
ok = u.Get(&mu)
if ok {
if mu.MucUserItem.JID != "" {
2026-03-10 16:35:56 +00:00
ji := (gtk.NewLabel(mu.MucUserItem.JID))
ji.AddCSSClass("jid")
2026-04-06 11:04:53 +01:00
ji.SetSelectable(true)
2026-03-10 16:35:56 +00:00
profile_box.Append(ji)
}
2026-04-28 12:58:00 +01:00
profile_box.Append(gtk.NewLabel(loadedLocale["connectedWithRole"] + mu.MucUserItem.Role))
profile_box.Append(gtk.NewLabel(loadedLocale["affiliatedAs"] + mu.MucUserItem.Affiliation))
}
go func() {
fmt.Println("Attempting to get Disco info")
myIQ, err := stanza.NewIQ(stanza.Attrs{
Type: "get",
From: clientroot.Session.BindJid,
To: u.From,
Id: "dicks",
Lang: "en",
})
if err != nil {
panic(err)
}
myIQ.Payload = &stanza.DiscoInfo{}
ctx := context.TODO()
mychan, err := client.SendIQ(ctx, myIQ)
if err == nil {
result := <-mychan
res, ok := result.Payload.(*stanza.DiscoInfo)
if ok {
idents := res.Identity
for i, ident := range idents {
2026-03-10 16:35:56 +00:00
profile_box.Append(gtk.NewLabel(fmt.Sprintf("%d: Name: %s, Category: %s, Type: %s", i+1, ident.Name, ident.Category, ident.Type)))
}
2026-03-10 16:35:56 +00:00
/*
2026-03-10 16:48:10 +00:00
s := fmt.Sprintf("%v", res.Features)
h := sha1.New()
h.Write([]byte(s))
sha1_hash := hex.EncodeToString(h.Sum(nil))
2026-03-10 16:35:56 +00:00
*/
sw := gtk.NewScrolledWindow()
s := ""
for _, feature := range res.Features {
s = s + feature.Var + "\n"
}
sw.SetChild(gtk.NewLabel(s))
profile_box.Append(sw)
}
}
}()
2026-01-31 15:08:54 +00:00
go func() {
ctx := context.TODO()
mychan, err := client.SendIQ(ctx, iqResp)
if err == nil {
result := <-mychan
ver, ok := result.Payload.(*stanza.Version)
if ok {
loading_version_text.SetVisible(false)
name := ver.Name
version := ver.Version
os := ver.OS
vr := fmt.Sprintf("%s %s %s", name, version, os)
if name == "" && version == "" && os == "" {
2026-04-28 12:58:00 +01:00
ver_text.SetText(loadedLocale["versionQueryEmpty"])
} else {
ver_text.SetText(vr)
ver_text.RemoveCSSClass("visitor")
}
2026-03-10 16:35:56 +00:00
} else if result.Error != nil && result.Error.Type != "" {
2026-04-28 12:58:00 +01:00
ver_text.SetText(loadedLocale["versionQueryError"])
2026-03-10 16:48:10 +00:00
ver_text.SetTooltipText(result.Error.Reason + ": " + result.Error.Text)
2026-03-10 16:35:56 +00:00
ver_text.RemoveCSSClass("visitor")
ver_text.AddCSSClass("error")
2026-03-10 16:48:10 +00:00
}
2026-01-31 15:08:54 +00:00
}
}()
go func() {
mo, _ := mucmembers.Load(JidMustParse(u.From).Bare())
2026-01-31 15:08:54 +00:00
mm := mo.(mucUnit)
mmm := mm.Members
mmmm, ok := mmm.Load(id)
2026-01-31 15:08:54 +00:00
if ok {
pres := mmmm.(stanza.Presence)
var vu VCardUpdate
pres.Get(&vu)
if vu.Photo != "" {
im := getAvatar(u.From, vu.Photo)
im.SetPixelSize(80)
im.AddCSSClass("author_img")
2026-03-10 16:35:56 +00:00
profile_box.Prepend(im)
2026-01-31 15:08:54 +00:00
} else {
2026-04-06 11:04:53 +01:00
im := createIdenticon(u.From)
2026-01-31 15:08:54 +00:00
im.SetPixelSize(80)
im.AddCSSClass("author_img")
2026-03-10 16:35:56 +00:00
profile_box.Prepend(im)
2026-01-31 15:08:54 +00:00
}
} else {
2026-04-06 11:04:53 +01:00
im := createIdenticon(u.From)
2026-01-31 15:08:54 +00:00
im.SetPixelSize(80)
im.AddCSSClass("author_img")
2026-03-10 16:35:56 +00:00
profile_box.Prepend(im)
2026-01-31 15:08:54 +00:00
}
}()
win.SetChild(profile_box)
2026-01-31 15:38:02 +00:00
win.SetTransientFor(win)
2026-01-31 15:08:54 +00:00
win.Present()
})
2026-01-31 15:08:54 +00:00
userbox.AddController(gesture)
userbox.AddController(mod_gesture)
return true
2026-04-06 11:04:53 +01:00
}))
headerBox := gtk.NewBox(gtk.OrientationHorizontal, 0)
if i >= 500 {
headerBox.Append(gtk.NewImageFromPaintable(clientAssets["world"]))
} else if i >= 50 {
headerBox.Append(gtk.NewImageFromPaintable(clientAssets["large_group"]))
} else {
headerBox.Append(gtk.NewImageFromPaintable(clientAssets["group"]))
}
2026-04-28 12:58:00 +01:00
headerBox.Append(gtk.NewLabel(fmt.Sprintf("%d %s", i, loadedLocale["participants"])))
gen.Prepend(headerBox)
2026-02-28 16:24:37 +00:00
muci := getAvatar(jid, jid)
muci.SetPixelSize(80)
gen.Prepend(muci)
2026-04-26 10:40:13 +01:00
muc_name := gtk.NewLabel(typed_tab.name)
muc_name.AddCSSClass("author")
gen.Prepend(muc_name)
memberList.SetChild(gen)
} else {
memberList.SetChild(gtk.NewLabel(jid))
}
2026-01-30 10:40:38 +00:00
2026-01-29 21:35:36 +00:00
}
func showErrorDialog(err error) {
2026-04-26 10:40:13 +01:00
err_win := gtk.NewWindow()
2026-04-28 12:58:00 +01:00
err_win.SetTitle(loadedLocale["error"])
2026-04-26 10:40:13 +01:00
err_win.SetDefaultSize(400, 200)
err_win.SetResizable(false)
box := gtk.NewBox(gtk.OrientationVertical, 0)
err_label := gtk.NewLabel(err.Error())
err_label.SetSelectable(true)
box.Append(err_label)
2026-04-28 12:58:00 +01:00
close_btn := gtk.NewButtonWithLabel(loadedLocale["close"])
2026-04-26 10:40:13 +01:00
close_btn.ConnectClicked(func() {
err_win.SetVisible(false)
})
box.Append(close_btn)
err_win.SetChild(box)
err_win.Present()
2026-01-29 21:35:36 +00:00
}
2026-04-06 11:04:53 +01:00
func createIdenticon(word string) *gtk.Image { // This function generates an identicon
if !loadedConfig.Identicons {
i := gtk.NewImageFromPaintable(clientAssets["DefaultAvatar"])
i.AddCSSClass(loadedConfig.CVD.String() + "_CVD")
2026-04-06 11:04:53 +01:00
return i
}
gen, _ := identicon.New("github", 5, 3)
ii, _ := gen.Draw(word)
im := ii.Image(25)
buf := new(bytes.Buffer)
err := png.Encode(buf, im)
if err != nil {
panic(err)
}
loader := gdkpixbuf.NewPixbufLoader()
loader.Write(buf.Bytes())
loader.Close()
i := gtk.NewImageFromPaintable(gdk.NewTextureForPixbuf(loader.Pixbuf()))
i.AddCSSClass(loadedConfig.CVD.String() + "_CVD")
return i
}