diff --git a/assets.go b/assets.go
index 7cecf77..7557e1e 100644
--- a/assets.go
+++ b/assets.go
@@ -208,6 +208,9 @@ var yaximCapBytes []byte
//go:embed assets/client_icons/psi.png
var psiCapBytes []byte
+//go:embed assets/client_icons/spark.png
+var sparkCapBytes []byte
+
var clientAssets sync.Map
func loadAsset(key string, data []byte) {
@@ -296,9 +299,10 @@ func init() {
"https://conversejs.org": converseJSCapBytes,
"http://pidgin.im/": pidginCapBytes,
"https://dev.narayana.im/narayana/Conversations-Classic": anotherImCapBytes,
- "https://movim.eu/": movimCapBytes,
- "https://yaxim.org/": yaximCapBytes,
- "https://psi-im.org": psiCapBytes,
+ "https://movim.eu/": movimCapBytes,
+ "https://yaxim.org/": yaximCapBytes,
+ "https://psi-im.org": psiCapBytes,
+ "https://igniterealtime.org/projects/smack": sparkCapBytes,
} {
loadAsset(key, data)
}
diff --git a/debug_christmas.png b/debug_christmas.png
new file mode 100644
index 0000000..04ff8aa
Binary files /dev/null and b/debug_christmas.png differ
diff --git a/debug_halloween.png b/debug_halloween.png
new file mode 100644
index 0000000..acc97bd
Binary files /dev/null and b/debug_halloween.png differ
diff --git a/go-xmpp/stanza/form.go b/go-xmpp/stanza/form.go
index f758f74..7c69218 100644
--- a/go-xmpp/stanza/form.go
+++ b/go-xmpp/stanza/form.go
@@ -14,6 +14,7 @@ const (
// See XEP-0004 and XEP-0068
// Pointer semantics
type Form struct {
+ MsgExtension
XMLName xml.Name `xml:"jabber:x:data x"`
Instructions []string `xml:"instructions"`
Title string `xml:"title,omitempty"`
@@ -66,3 +67,7 @@ type Option struct {
Label string `xml:"label,attr,omitempty"`
ValuesList []string `xml:"value"`
}
+
+func init() {
+ TypeRegistry.MapExtension(PKTMessage, xml.Name{Space: "jabber:x:data", Local: "x"}, Form{})
+}
diff --git a/go-xmpp/stanza/packet.go b/go-xmpp/stanza/packet.go
index 567796d..0fd1011 100644
--- a/go-xmpp/stanza/packet.go
+++ b/go-xmpp/stanza/packet.go
@@ -10,7 +10,7 @@ type Attrs struct {
Id string `xml:"id,attr,omitempty"`
From string `xml:"from,attr,omitempty"`
To string `xml:"to,attr,omitempty"`
- Lang string `xml:"lang,attr,omitempty"`
+ Lang string `xml:"xml:lang,attr,omitempty"`
}
type packetFormatter interface {
diff --git a/go.mod b/go.mod
index 4b40381..1816472 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
module lambda-im
-go 1.25.5
+go 1.26.0
require (
github.com/BurntSushi/toml v1.6.0
@@ -16,16 +16,28 @@ require (
github.com/rrivera/identicon v0.0.0-20240116195454-d5ba35832c0d
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef
+ github.com/tdewolff/canvas v0.0.0-20260923214215-09804640d00c
gosrc.io/xmpp v0.5.1
mellium.im/xmpp v0.23.0
)
require (
+ codeberg.org/go-pdf/fpdf v0.12.0 // indirect
git.sr.ht/~jackmordaunt/go-toast v1.1.2 // indirect
+ github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298 // indirect
+ github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966 // indirect
+ github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc // indirect
+ github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046 // indirect
+ github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f // indirect
+ github.com/andybalholm/brotli v1.2.2 // indirect
+ github.com/benoitkugler/textlayout v0.3.2 // indirect
+ github.com/benoitkugler/textprocessing v0.0.6 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/esiqveland/notify v0.14.0 // indirect
github.com/go-analyze/bulk v0.1.5 // indirect
+ github.com/go-fonts/latin-modern v0.3.3 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
+ github.com/go-text/typesetting v0.3.4 // indirect
github.com/godbus/dbus/v5 v5.2.2 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/jackmordaunt/icns/v3 v3.0.1 // indirect
@@ -34,16 +46,24 @@ require (
github.com/rogpeppe/go-internal v1.16.0 // indirect
github.com/sergeymakinen/go-bmp v1.0.0 // indirect
github.com/sergeymakinen/go-ico v1.0.0 // indirect
+ github.com/srwiley/scanx v0.0.0-20190309010443-e94503791388 // indirect
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af // indirect
+ github.com/tdewolff/font v0.0.0-20260822205238-d0d2f004b117 // indirect
+ github.com/tdewolff/minify/v2 v2.24.17 // indirect
+ github.com/tdewolff/parse/v2 v2.8.16 // indirect
+ github.com/yuin/goldmark v1.8.5 // indirect
golang.org/x/image v0.45.0 // indirect
- golang.org/x/net v0.57.0 // indirect
+ golang.org/x/net v0.58.0 // indirect
golang.org/x/sync v0.22.0 // indirect
golang.org/x/sys v0.47.0 // indirect
golang.org/x/text v0.41.0 // indirect
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
mellium.im/reader v0.1.0 // indirect
mellium.im/xmlstream v0.15.4 // indirect
+ modernc.org/knuth v0.6.0 // indirect
+ modernc.org/token v1.1.0 // indirect
nhooyr.io/websocket v1.8.17 // indirect
+ star-tex.org/x/tex v0.7.1 // indirect
)
replace gosrc.io/xmpp => ./go-xmpp/
diff --git a/go.sum b/go.sum
index bf21154..fa13b66 100644
--- a/go.sum
+++ b/go.sum
@@ -1,14 +1,43 @@
+codeberg.org/go-fonts/liberation v0.6.0 h1:15Gh6SdwYve22CWCm9jYpVpRuaTh726av2TgHTHvAtQ=
+codeberg.org/go-fonts/liberation v0.6.0/go.mod h1:J15VAa+lyxdcI/Je7lDDDl6QOhLk9feNBnnwXqEHXOk=
+codeberg.org/go-latex/latex v0.3.0 h1:LKTaDHFbEC2PH1sh0sYv6PZ1pzs/g2aoeV1HItWj/bg=
+codeberg.org/go-latex/latex v0.3.0/go.mod h1:8ETijTpK2bFtwRAXLXe1RZJrYxnc5pibibZfQBj+Lk4=
+codeberg.org/go-pdf/fpdf v0.12.0 h1:g8E/1VqGqB2lZUUaqQrrTnA0IEJLPTTX1DZ0qS/ZmhU=
+codeberg.org/go-pdf/fpdf v0.12.0/go.mod h1:WJNJ2bvCj81rZBdhOf7lKOGoSl+OKMXcIcXqDcP8r5Y=
git.sr.ht/~jackmordaunt/go-toast v1.1.2 h1:/yrfI55LRt1M7H1vkaw+NaH1+L1CDxrqDltwm5euVuE=
git.sr.ht/~jackmordaunt/go-toast v1.1.2/go.mod h1:jA4OqHKTQ4AFBdwrSnwnskUIIS3HYzlJSgdzCKqfavo=
+git.sr.ht/~sbinet/cmpimg v0.1.0 h1:E0zPRk2muWuCqSKSVZIWsgtU9pjsw3eKHi8VmQeScxo=
+git.sr.ht/~sbinet/cmpimg v0.1.0/go.mod h1:FU12psLbF4TfNXkKH2ZZQ29crIqoiqTZmeQ7dkp/pxE=
+git.sr.ht/~sbinet/gg v0.8.0 h1:PjQ4AgUWRz7Dy6PVBKMLLg96eRCos1U+H6F3FJuKWHo=
+git.sr.ht/~sbinet/gg v0.8.0/go.mod h1:XhlGCvSXts+BaZ1XMZLxLSFUDvwsJ/2El5F6XzPep6o=
+github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298 h1:1qlsVAQJXZHsaM8b6OLVo6muQUQd4CwkH/D3fnnbHXA=
+github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298/go.mod h1:D+QujdIlUNfa0igpNMk6UIvlb6C252URs4yupRUV4lQ=
+github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966 h1:lTG4HQym5oPKjL7nGs+csTgiDna685ZXjxijkne828g=
+github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966/go.mod h1:Mid70uvE93zn9wgF92A/r5ixgnvX8Lh68fxp9KQBaI0=
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
+github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc h1:7D+Bh06CRPCJO3gr2F7h1sriovOZ8BMhca2Rg85c2nk=
+github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046 h1:O/r2Sj+8QcMF7V5IcmiE2sMFV2q3J47BEirxbXJAdzA=
+github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046/go.mod h1:uw9h2sd4WWHOPdJ13MQpwK5qYWKYDumDqxWWIknEQ+k=
+github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f h1:l7moT9o/v/9acCWA64Yz/HDLqjcRTvc0noQACi4MsJw=
+github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f/go.mod h1:vIOkSdX3NDCPwgu8FIuTat2zDF0FPXXQ0RYFRy+oQic=
+github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw=
+github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM=
+github.com/andybalholm/brotli v1.2.2 h1:HzTuoo2ErYQqf5qvcJInB8uvqSVxRttzkFexPWtnceM=
+github.com/andybalholm/brotli v1.2.2/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
+github.com/benoitkugler/pstokenizer v1.0.0/go.mod h1:l1G2Voirz0q/jj0TQfabNxVsa8HZXh/VMxFSRALWTiE=
+github.com/benoitkugler/textlayout v0.3.2 h1:Y/LTkwpQ9G0Fia9yhPmZA9IR5AnE8Cq30j3C+Gx5/IE=
+github.com/benoitkugler/textlayout v0.3.2/go.mod h1:o+1hFV+JSHBC9qNLIuwVoLedERU7sBPgEFcuSgfvi/w=
+github.com/benoitkugler/textlayout-testdata v0.1.1/go.mod h1:i/qZl09BbUOtd7Bu/W1CAubRwTWrEXWq6JwMkw8wYxo=
+github.com/benoitkugler/textprocessing v0.0.6 h1:obkMyj62GEPg3xUVYqROlCN22z1OleuZm6ULqX9Om1g=
+github.com/benoitkugler/textprocessing v0.0.6/go.mod h1:Io0gN08/PXEzrSOWFa88xHx2Xv3VjvLMY7H76YoI23A=
github.com/boxes-ltd/imaging v1.7.5 h1:k4kYxJEhysoGhEEN1IEeKoSbnG8/8snjj7M48Ok0fnk=
github.com/boxes-ltd/imaging v1.7.5/go.mod h1:+8H+oRvis3InOFtTpcoCCB1RDXqo6p9tQBtjZfWnrC8=
github.com/crazy3lf/colorconv v1.2.0 h1:UM7kSZWnwFMGiC+PpYrjxQSOd6sEyWb+dRKKTd3KslA=
github.com/crazy3lf/colorconv v1.2.0/go.mod h1:2jTJ7QCWCj2sSLOhF4Gzi0J5/hoX8/VY8VzNvXAlD1I=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/diamondburned/gotk4/pkg v0.4.1 h1:w4BwVUUBkKyNLz5HKHN2Oz3kZCTLHfpfKgB/su1p6uo=
github.com/diamondburned/gotk4/pkg v0.4.1/go.mod h1:BAEEDXtj5srQ1S2++5HX2K1s9c6CtuWSa60dAywrkRM=
@@ -22,8 +51,14 @@ github.com/go-analyze/bulk v0.1.5 h1:Zj8w3gEOhEnp8aRZ7DHMDqaG3/Bp7CvQ7pu6Mw9YhFc
github.com/go-analyze/bulk v0.1.5/go.mod h1:afon/KtFJYnekIyN20H/+XUvcLFjE8sKR1CfpqfClgM=
github.com/go-analyze/charts v0.6.0 h1:aBE2cbdr58faYGwPk3RUzGQMyg5fGsSAxepwYTYmvhg=
github.com/go-analyze/charts v0.6.0/go.mod h1:m8hqGNSpYYdMMc0Im/RCowY9CIA7705jiltuOVQZWuI=
+github.com/go-fonts/latin-modern v0.3.3 h1:g2xNgI8yzdNzIVm+qvbMryB6yGPe0pSMss8QT3QwlJ0=
+github.com/go-fonts/latin-modern v0.3.3/go.mod h1:tHaiWDGze4EPB0Go4cLT5M3QzRY3peya09Z/8KSCrpY=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
+github.com/go-text/typesetting v0.3.4 h1:YYurUOtEb9kGSOz4uE3k4OpBGsp1dDL8+fjCeaFamAU=
+github.com/go-text/typesetting v0.3.4/go.mod h1:4qZCQphq4KSgGTAeI0uMEkVbROgfah8BuyF5LRYr7XY=
+github.com/go-text/typesetting-utils v0.0.0-20260223113751-2d88ac90dae3 h1:drBZzMgdYPbmyXqOto4YhhJGrFIQCX94FpR4MzTCsos=
+github.com/go-text/typesetting-utils v0.0.0-20260223113751-2d88ac90dae3/go.mod h1:3/62I4La/HBRX9TcTpBj4eipLiwzf+vhI+7whTc9V7o=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=
github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
@@ -46,8 +81,9 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.16.0 h1:O9DK+vNMDVGLr2BeZqmpLeMjiMNkuXfcqntWbZV6S5g=
github.com/rogpeppe/go-internal v1.16.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs=
@@ -61,38 +97,68 @@ github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiY
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q=
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ=
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE=
+github.com/srwiley/scanFT v0.0.0-20220128184157-0d1ee492111f h1:uLR2GaV0kWYZ3Ns3l3sjtiN+mOWAQadvrL8HXcyKjl0=
+github.com/srwiley/scanFT v0.0.0-20220128184157-0d1ee492111f/go.mod h1:LZwgIPG9X6nH6j5Ef+xMFspl6Hru4b5EJxzMfeqHYJY=
+github.com/srwiley/scanx v0.0.0-20190309010443-e94503791388 h1:ZdkidVdpLW13BQ9a+/3uerT2ezy9J7KQWH18JCfhDmI=
+github.com/srwiley/scanx v0.0.0-20190309010443-e94503791388/go.mod h1:C/WY5lmWfMtPFYYBTd3Lzdn4FTLr+RxlIeiBNye+/os=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
-github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
-github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
+github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
+github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af h1:6yITBqGTE2lEeTPG04SN9W+iWHCRyHqlVYILiSXziwk=
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af/go.mod h1:4F09kP5F+am0jAwlQLddpoMDM+iewkxxt6nxUQ5nq5o=
-golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
-golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
+github.com/tdewolff/canvas v0.0.0-20260923214215-09804640d00c h1:eXJXpQAo8l0AdVnsjJctG38GlBnHnwHfhLOTLYxZYNE=
+github.com/tdewolff/canvas v0.0.0-20260923214215-09804640d00c/go.mod h1:K9Ux3cwFtyAFRITZ46g7v+ORXgI3ENtQa5rL9yjNz5Y=
+github.com/tdewolff/font v0.0.0-20260822205238-d0d2f004b117 h1:5vSJjsbV9AH0U+Ex0uej8ROrpOBlOVBrw0mGlVef7oo=
+github.com/tdewolff/font v0.0.0-20260822205238-d0d2f004b117/go.mod h1:ipESUihcEdgRBhIwTRHmBTOMamccMQrp4iCxfa1aFws=
+github.com/tdewolff/minify/v2 v2.24.17 h1:6AbitfVyq0M7aW6i+XL7+49DeTQZwloOMs9O574arBg=
+github.com/tdewolff/minify/v2 v2.24.17/go.mod h1:kVqn9vxXUKtlHexSNrWbYePqioOT5mc4ou/KVSMpfCM=
+github.com/tdewolff/parse/v2 v2.8.16 h1:bLk5svUOQRkW/Y2SJ+DeENSIkZBcTIkq+Atyv5D8feI=
+github.com/tdewolff/parse/v2 v2.8.16/go.mod h1:XdsoSFThlVIRIajAuqz1evNY7bagZS8LBOPA3aVopwQ=
+github.com/tdewolff/test v1.0.12 h1:7F21DqIajswxuche0geHdrUZRCWE4oko4b7bcmkkrxk=
+github.com/tdewolff/test v1.0.12/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
+github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
+github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
+github.com/yuin/goldmark v1.8.5 h1:r6N5afV5qj/5S4UTch8agZHJ8UxNCMwX7WjkkJam2NA=
+github.com/yuin/goldmark v1.8.5/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
+go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
+go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
+golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
+golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis=
+golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.45.0 h1:FMb1nTbH5H9vF55SriQHgFw5GnNL9Jg6L25BwXKzhB0=
golang.org/x/image v0.45.0/go.mod h1:n62x/7RqlwXDvGsSU4u6IUTUf6KghUZ9Bt7cG/T9Fx4=
golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk=
golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40=
-golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
-golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
+golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
+golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE=
golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk=
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY=
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
+gonum.org/v1/plot v0.17.0 h1:d0DwPVBe9jnEGqQBoZGl/P2M9WciJbG2CnV59C9QBT4=
+gonum.org/v1/plot v0.17.0/go.mod h1:ipt2GUN1oqzr2O7wCjLDtw1ShfIYYNBp4o0O1Ez5B3Y=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
mellium.im/reader v0.1.0 h1:UUEMev16gdvaxxZC7fC08j7IzuDKh310nB6BlwnxTww=
mellium.im/reader v0.1.0/go.mod h1:F+X5HXpkIfJ9EE1zHQG9lM/hO946iYAmU7xjg5dsQHI=
@@ -102,5 +168,13 @@ mellium.im/xmlstream v0.15.4 h1:gLKxcWl4rLMUpKgtzrTBvr4OexPeO/edYus+uK3F6ZI=
mellium.im/xmlstream v0.15.4/go.mod h1:yXaCW2++fmVO4L9piKVkyLDqnCmictVYF7FDQW8prb4=
mellium.im/xmpp v0.23.0 h1:rvKvOvMdIURCLaAWEJN8J0QpO3AJYCDKjxLsqtTPSjY=
mellium.im/xmpp v0.23.0/go.mod h1:GHDKlKKQe0LNmD9YqExyxnFEEBiz84KGqnfiA2VNzb8=
+modernc.org/knuth v0.6.0 h1:zLv0hzgdcpcCUB/iMwpQvn9m/iciqO/hyNXlpWymMKU=
+modernc.org/knuth v0.6.0/go.mod h1:ISAZV+URZIlE8h1BwNVg1NzpwHKndV5lQ13NPPj3tds=
+modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
+modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
nhooyr.io/websocket v1.8.17 h1:KEVeLJkUywCKVsnLIDlD/5gtayKp8VoCkksHCGGfT9Y=
nhooyr.io/websocket v1.8.17/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c=
+rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4=
+rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
+star-tex.org/x/tex v0.7.1 h1:4qGAByRyY0WQsOjtcHlxz+FgrYxz8fzxIds2Gjepp5U=
+star-tex.org/x/tex v0.7.1/go.mod h1:Y3y0U7sZTltTh/CDZIx0oAtMjG7eMaTuTtvDZGdyhJo=
diff --git a/gtk-helpers.go b/gtk-helpers.go
index cd54f93..337e811 100644
--- a/gtk-helpers.go
+++ b/gtk-helpers.go
@@ -7,7 +7,10 @@ import (
"image"
"image/color"
"image/png"
+ "slices"
"strconv"
+ "strings"
+ _ "sync"
"github.com/boxes-ltd/imaging"
"github.com/crazy3lf/colorconv"
@@ -77,7 +80,11 @@ func showUserWindow(u stanza.Presence, custom_nick string) {
win.SetTitle(JidMustParse(u.From).Resource)
nick.AddCSSClass("author")
nick.SetSelectable(true)
- profile_box.Append(nick)
+
+ nbox := gtk.NewBox(gtk.OrientationHorizontal, 5)
+ nbox.SetHAlign(gtk.AlignCenter)
+ nbox.Append(nick)
+ profile_box.Append(nbox)
if u.Status != "" {
status_message := gtk.NewLabel(u.Status)
status_message.AddCSSClass("status")
@@ -128,6 +135,7 @@ func showUserWindow(u stanza.Presence, custom_nick string) {
if ok {
for _, hat := range hats.Hats {
l := gtk.NewLabel(hat.Title)
+ l.SetSelectable(true)
l.AddCSSClass("hat")
profile_box.Append(l)
}
@@ -141,6 +149,11 @@ func showUserWindow(u stanza.Presence, custom_nick string) {
ji.AddCSSClass("jid")
ji.SetSelectable(true)
profile_box.Append(ji)
+ if JidMustParse(mu.MucUserItem.JID).Bare() == JidMustParse(clientroot.Session.BindJid).Bare() {
+ you_label := gtk.NewLabel("(You)")
+ you_label.AddCSSClass("you")
+ nbox.Append(you_label)
+ }
}
profile_box.Append(gtk.NewLabel(loadedLocale["connectedWithRole"] + mu.MucUserItem.Role))
var affil string
@@ -308,7 +321,7 @@ func createTab(jid string, isMuc bool, name string) bool {
})
newTab.name = name
newTab.msg_refsID = make(map[string]*abMessage)
- newTab.msg_refsOSID = make(map[string]*abMessage)
+ //newTab.msg_refsOSID = make(map[string]*abMessage)
tabs.Store(jid, newTab)
return true
}
@@ -785,11 +798,215 @@ func showErrorDialog(err error, w *gtk.Window) {
err_win.Present()
}
+func showDialog(s string, w *gtk.Window) {
+ err_win := gtk.NewWindow()
+ err_win.SetTitle("Information")
+ err_win.SetDefaultSize(400, 200)
+ err_win.SetResizable(false)
+
+ box := gtk.NewBox(gtk.OrientationVertical, 0)
+ err_label := gtk.NewLabel(s)
+ err_label.SetSelectable(true)
+ box.Append(err_label)
+
+ close_btn := gtk.NewButtonWithLabel(loadedLocale["close"])
+ close_btn.ConnectClicked(func() {
+ err_win.Destroy()
+ })
+ box.Append(close_btn)
+ err_win.SetChild(box)
+ err_win.SetTransientFor(w)
+ err_win.Present()
+}
+
+func showFormDialog(f stanza.Form) (*stanza.Form, gtk.Window, string) {
+ form_type := ""
+
+ var ret *stanza.Form = &f
+
+ form_win := gtk.NewWindow()
+ form_win.SetTitle(f.Title)
+ form_win.SetDefaultSize(400, 400)
+ form_win.SetResizable(false)
+
+ form_box := gtk.NewBox(gtk.OrientationVertical, 2)
+
+ for _, instruction := range f.Instructions {
+ form_box.Append(gtk.NewLabel(instruction))
+ }
+
+ // for x, item := range f.Items {
+ for y, field := range f.Fields {
+ if field.Var == "FORM_TYPE" {
+ form_type = field.ValuesList[0]
+ }
+ field_box := gtk.NewBox(gtk.OrientationHorizontal, 2)
+ field_label := gtk.NewLabel(field.Label)
+ field_label.SetTooltipText(field.Description)
+
+ btn_box := gtk.NewBox(gtk.OrientationVertical, 0)
+ btn_box.SetHAlign(gtk.AlignEnd)
+ btn_box.SetHExpand(true)
+
+ switch field.Type {
+ case "boolean":
+ if len(field.ValuesList) == 0 {
+ field.ValuesList = append(field.ValuesList, "0")
+ f.Fields[y].ValuesList = append(f.Fields[y].ValuesList, "0")
+ }
+
+ tick := gtk.NewCheckButton()
+ if field.ValuesList[0] == "1" {
+ tick.SetActive(true)
+ } else {
+ tick.SetActive(false)
+ }
+
+ tick.ConnectToggled(func() {
+ str := ""
+ if tick.Active() {
+ str = "1"
+ } else {
+ str = "0"
+ }
+ f.Fields[y].ValuesList[0] = str
+ })
+ btn_box.Append(tick)
+ case "fixed":
+ for _, v := range field.ValuesList {
+ label := gtk.NewLabel(v)
+ label.AddCSSClass("visitor")
+ btn_box.SetHAlign(gtk.AlignCenter)
+ btn_box.Append(label)
+ }
+ case "hidden":
+ // do nothing
+ case "jid-multi":
+ // TODO
+ case "jid-single":
+ e_box := gtk.NewBox(gtk.OrientationHorizontal, 0)
+ jid_entry := gtk.NewEntry()
+ jid_entry.SetText(field.ValuesList[0])
+ buildAJIDButton := gtk.NewImageFromPaintable(clientAssetsLoad("jabber"))
+ buildAJIDButton.Connect("pressed", func() {
+ jidBuilder(jid_entry)
+ })
+
+ jid_entry.ConnectChanged(func() {
+ f.Fields[y].ValuesList[0] = jid_entry.Text()
+ })
+
+ e_box.Append(buildAJIDButton)
+ e_box.Append(jid_entry)
+
+ btn_box.Append(e_box)
+ case "list-multi":
+ // TODO
+ case "list-single":
+ var selected uint
+ str := []string{}
+ for i, option := range field.Options {
+ if slices.Equal(option.ValuesList, field.ValuesList) {
+ selected = uint(i)
+ }
+ str = append(str, option.ValuesList[0])
+ }
+
+ dropdown := gtk.NewDropDownFromStrings(str)
+ dropdown.SetSelected(selected)
+ dropdown.ConnectActivate(func() {
+ f.Fields[y].ValuesList[0] = field.Options[dropdown.Selected()].ValuesList[0]
+ })
+
+ btn_box.Append(dropdown)
+ case "text-private":
+ p_entry := gtk.NewPasswordEntry()
+ p_entry.SetText(field.ValuesList[0])
+ p_entry.ConnectChanged(func() {
+ f.Fields[y].ValuesList[0] = p_entry.Text()
+ })
+
+ btn_box.Append(p_entry)
+ case "text-multi":
+ textView := gtk.NewTextView()
+ textView.SetWrapMode(gtk.WrapWordChar)
+ txt := ""
+
+ for i, line := range field.ValuesList {
+ txt = txt + line
+ if i != len(field.ValuesList)-1 {
+ txt = txt + "\n"
+ }
+ }
+ textView.Buffer().SetText(txt)
+
+ textView.Buffer().ConnectChanged(func() {
+ txt := textView.Buffer().Text(textView.Buffer().StartIter(), textView.Buffer().EndIter(), false)
+ f.Fields[y].ValuesList = strings.Split(txt, "\n")
+ })
+
+ scroll := gtk.NewScrolledWindow()
+ scroll.SetChild(textView)
+ scroll.SetMinContentHeight(150)
+
+ btn_box.Append(scroll)
+
+ default: // includes text-single
+ en := gtk.NewEntry()
+ txt := ""
+ if len(field.ValuesList) != 0 {
+ txt = field.ValuesList[0]
+ }
+ en.SetText(txt)
+ en.ConnectChanged(func() {
+ if len(field.ValuesList) == 0 {
+ f.Fields[y].ValuesList = append(f.Fields[y].ValuesList, en.Text())
+ } else {
+ f.Fields[y].ValuesList[0] = en.Text()
+ }
+ })
+
+ btn_box.Append(en)
+ }
+
+ field_box.Append(field_label)
+ field_box.Append(btn_box)
+ form_box.Append(field_box)
+ }
+
+ form_box.Append(gtk.NewSeparator(gtk.OrientationHorizontal))
+
+ btn_box := gtk.NewBox(gtk.OrientationHorizontal, 0)
+ btn_box.SetHAlign(gtk.AlignCenter)
+
+ cancelbtn := gtk.NewButtonWithLabel("Cancel")
+ cancelbtn.ConnectClicked(func() {
+ form_win.Close()
+ })
+
+ okbtn := gtk.NewButtonWithLabel(loadedLocale["submit"])
+ okbtn.ConnectClicked(func() {
+ ret = &f
+ form_win.Close()
+ })
+
+ btn_box.Append(okbtn)
+ btn_box.Append(cancelbtn)
+
+ form_box.Append(btn_box)
+
+ form_win.SetChild(form_box)
+ form_win.SetVisible(true)
+
+ return ret, *form_win, form_type
+}
+
func createIdenticon(word string, always_create bool, i *gtk.Image) { // This function generates an identicon
if !loadedConfig.Identicons && !always_create {
// return gtk.NewImageFromPaintable(clientAssetsLoad("DefaultAvatar"))
- glib.IdleAdd(func() {
- i.SetFromPaintable(clientAssetsLoad("DefaultAvatar"))
+ p := clientAssetsLoad("DefaultAvatar")
+ glib.IdleAddPriority(glib.PriorityLow, func() {
+ i.SetFromPaintable(p)
})
return
}
@@ -814,7 +1031,7 @@ func createIdenticon(word string, always_create bool, i *gtk.Image) { // This fu
p := loader.Pixbuf()
gt := gdk.NewTextureForPixbuf(p)
- glib.IdleAdd(func() {
+ glib.IdleAddPriority(glib.PriorityLow, func() {
i.SetFromPaintable(gt)
})
}
diff --git a/gtk-message.go b/gtk-message.go
index aa26fcd..8ff66e0 100644
--- a/gtk-message.go
+++ b/gtk-message.go
@@ -6,19 +6,21 @@ import (
"context"
"encoding/base64"
"fmt"
- "github.com/diamondburned/gotk4/pkg/gdk/v4"
- "github.com/diamondburned/gotk4/pkg/glib/v2"
- "github.com/diamondburned/gotk4/pkg/gtk/v4"
- "github.com/google/uuid"
- "github.com/jasonlovesdoggo/gopen"
- "gosrc.io/xmpp/stanza"
- xmpp_color "mellium.im/xmpp/color"
- "mellium.im/xmpp/jid"
"os"
"path/filepath"
"runtime"
"strings"
"time"
+
+ "github.com/diamondburned/gotk4/pkg/gdk/v4"
+ "github.com/diamondburned/gotk4/pkg/glib/v2"
+ "github.com/diamondburned/gotk4/pkg/gtk/v4"
+ "github.com/google/uuid"
+ "github.com/jasonlovesdoggo/gopen"
+
+ "gosrc.io/xmpp/stanza"
+ xmpp_color "mellium.im/xmpp/color"
+ "mellium.im/xmpp/jid"
)
func generatePresenceWidget(p stanza.Packet) gtk.Widgetter {
@@ -102,7 +104,8 @@ func generateMessageWidget(p stanza.Packet, blocked bool, target string) (gtk.Wi
msg.ocu_id = ocu.ID
if sid.ID != "" {
- typed_tab.msg_refsOSID[sid.ID] = msg
+ // typed_tab.msg_refsOSID[sid.ID] = msg
+ typed_tab.msg_refsOSID.Store(sid.ID, msg)
}
}
@@ -241,6 +244,7 @@ func generateMessageWidget(p stanza.Packet, blocked bool, target string) (gtk.Wi
live_edit := gtk.NewButtonWithLabel("Live edit")
live_edit.ConnectClicked(func() {
en := gtk.NewEntry()
+ en.SetText(m.Body)
en.ConnectChanged(func() {
client.SendRaw(fmt.Sprintf("%s",
uuid.NewString(),
@@ -277,7 +281,13 @@ func generateMessageWidget(p stanza.Packet, blocked bool, target string) (gtk.Wi
al := gtk.NewLabel(name)
ycbcr := xmpp_color.String(id, 255, loadedConfig.CVD)
r, g, b, _ := ycbcr.RGBA()
- hexcolor := fmt.Sprintf("#%02x%02x%02x", r, g, b)
+
+ r = uint32((float32(r) / 65536) * 255)
+ g = uint32((float32(g) / 65536) * 255)
+ b = uint32((float32(b) / 65536) * 255)
+
+ hexcolor := fmt.Sprintf("#%x%x%x", r, g, b)
+ fmt.Println(hexcolor)
al.SetMarkup(fmt.Sprintf("%s", hexcolor, name))
al.SetSelectable(true)
@@ -416,10 +426,19 @@ func generateMessageWidget(p stanza.Packet, blocked bool, target string) (gtk.Wi
body = XEPToPango(body, false)
mlabel := gtk.NewLabel("")
- if m.Body == "" {
+
+ form := new(stanza.Form)
+ contains_form := m.Get(form)
+
+ if m.Body == "" && m.Subject == "" && !contains_form {
return nil, false
}
+ if m.Body == "" {
+ mlabel.SetText("No body set")
+ mlabel.AddCSSClass("visitor")
+ }
+
mlabel.SetMarkup(body)
mlabel.SetWrap(true)
mlabel.SetSelectable(true)
@@ -469,27 +488,49 @@ func generateMessageWidget(p stanza.Packet, blocked bool, target string) (gtk.Wi
mainBox.Append(lp_box)
}
+ if contains_form {
+ btn := gtk.NewButtonWithLabel(fmt.Sprintf("Open attached form - %s", form.Title))
+ btn.ConnectClicked(func() {
+ f, w, _ := showFormDialog(*form)
+ w.ConnectCloseRequest(func() (ok bool) {
+ go func() {
+ form_reply := stanza.NewMessage(stanza.Attrs{
+ Type: m.Type,
+ Id: m.Id,
+ To: m.From,
+ })
+
+ form_reply.Extensions = []stanza.MsgExtension{f}
+ client.Send(form_reply)
+ }()
+ return false
+ })
+ })
+ mainBox.Append(btn)
+ }
+
}
- if loadedConfig.BlockingMode == Highlight && blocked {
- mainBox.AddCSSClass("blocked")
- }
+ if blocked {
+ switch loadedConfig.BlockingMode {
+ case Highlight:
+ mainBox.AddCSSClass("blocked")
+ case Hide:
+ mainBox.SetVisible(false)
+ blocked_box := gtk.NewBox(gtk.OrientationVertical, 0)
+ blocked_part := gtk.NewBox(gtk.OrientationHorizontal, 0)
+ blocked_part.Append(gtk.NewLabel("This message is from a user you have blocked. "))
- if loadedConfig.BlockingMode == Hide && blocked {
- mainBox.SetVisible(false)
- blocked_box := gtk.NewBox(gtk.OrientationVertical, 0)
- blocked_part := gtk.NewBox(gtk.OrientationHorizontal, 0)
- blocked_part.Append(gtk.NewLabel("This message is from a user you have blocked. "))
+ show_hide := gtk.NewButtonWithLabel("Toggle")
+ show_hide.ConnectClicked(func() {
+ mainBox.SetVisible(!mainBox.IsVisible())
+ })
- show_hide := gtk.NewButtonWithLabel("Toggle")
- show_hide.ConnectClicked(func() {
- mainBox.SetVisible(!mainBox.IsVisible())
- })
-
- blocked_part.Append(show_hide)
- blocked_box.Append(blocked_part)
- blocked_box.Append(mainBox)
- return blocked_box, true
+ blocked_part.Append(show_hide)
+ blocked_box.Append(blocked_part)
+ blocked_box.Append(mainBox)
+ return blocked_box, true
+ }
}
return mainBox, true
diff --git a/gtk-signin.go b/gtk-signin.go
index 726b243..3bbc7b0 100644
--- a/gtk-signin.go
+++ b/gtk-signin.go
@@ -103,7 +103,7 @@ func dropToSignInPage(err error) {
if err != nil {
panic(err)
}
- os.WriteFile(filepath.Join(p, "lambda.toml"), b.Bytes(), 0644)
+ os.WriteFile(filepath.Join(p, config_filename), b.Bytes(), 0644)
window.SetVisible(false)
main()
diff --git a/gtk-update.go b/gtk-update.go
index c123648..8136ac9 100644
--- a/gtk-update.go
+++ b/gtk-update.go
@@ -121,7 +121,7 @@ func save(choice string) {
if err != nil {
panic(err)
}
- os.WriteFile(filepath.Join(p, "lambda.toml"), b.Bytes(), 0644)
+ os.WriteFile(filepath.Join(p, config_filename), b.Bytes(), 0644)
fmt.Println("a")
// Please add tenary operators to go istg this is painful
diff --git a/main.go b/main.go
index 704e776..847d705 100644
--- a/main.go
+++ b/main.go
@@ -19,6 +19,7 @@ import (
"github.com/diamondburned/gotk4/pkg/gio/v2"
"github.com/diamondburned/gotk4/pkg/glib/v2"
"github.com/diamondburned/gotk4/pkg/gtk/v4"
+ "github.com/diamondburned/gotk4/pkg/pango"
"github.com/gen2brain/beeep"
"github.com/go-analyze/charts"
"github.com/google/uuid"
@@ -83,11 +84,18 @@ var xmlLog *os.File
// Basic roster
var Roster = make(map[string]bool)
+var config_filename = "lambda.toml"
+var app_id = "net.sunglocto.lambda"
+
func init() {
beeep.AppName = loadedLocale["appName"]
}
func main() {
+ if os.Getenv("PROFILE") != "" {
+ config_filename = fmt.Sprintf("lambda_%s.toml", os.Getenv("PROFILE"))
+ app_id = "net.sunglocto.lambda." + os.Getenv("PROFILE")
+ }
pingTimes = append(pingTimes, []float64{})
p, err := ensureConfig()
@@ -95,7 +103,7 @@ func main() {
panic(err)
}
- b, err := os.ReadFile(filepath.Join(p, "lambda.toml"))
+ b, err := os.ReadFile(filepath.Join(p, config_filename))
if err != nil {
dropToSignInPage(err)
return
@@ -169,6 +177,7 @@ func main() {
iqResp.Type = "error"
iqResp.From = clientroot.Session.BindJid
+ iqResp.Id = iq.Id
stanza_err := new(stanza.Err)
stanza_err.Type = stanza.ErrorTypeCancel
@@ -203,6 +212,7 @@ func main() {
{Var: "urn:xmpp:ping"},
{Var: "jabber:iq:oob"},
{Var: "jabber:x:oob"},
+ {Var: "jabber:x:data"},
},
}
iqResp.Payload = &payload
@@ -230,6 +240,7 @@ func main() {
iqResp.Type = "error"
iqResp.From = clientroot.Session.BindJid
+ iqResp.Id = iq.Id
stanza_err := new(stanza.Err)
stanza_err.Type = stanza.ErrorTypeCancel
@@ -360,8 +371,8 @@ func main() {
// Handle read indicators
marker := new(Marker)
ok = m.Get(marker)
- if ok {
- _, ok := typed_tab.msg_refsOSID[marker.ID]
+ if ok && !loadedConfig.CompactMode {
+ _, ok := typed_tab.msg_refsOSID.Load(marker.ID)
if ok {
im := gtk.NewImage()
im.SetPixelSize(20)
@@ -381,10 +392,13 @@ func main() {
go getAvatar(m.From, vu.Photo, im)
}
- ind, ok := typed_tab.msg_refsOSID[marker.ID]
- if ok {
- if ind.read_indicator != nil {
- ind.read_indicator.Append(im)
+ uind, loaded := typed_tab.msg_refsOSID.Load(marker.ID)
+ if loaded {
+ ind, ok := uind.(*abMessage)
+ if ok {
+ if ind.read_indicator != nil {
+ ind.read_indicator.Append(im)
+ }
}
}
return
@@ -423,30 +437,21 @@ func main() {
tab, ok := tabs.Load(originator)
if ok {
typed_tab := tab.(*chatTab)
- /*
- to_retract := ""
- refs := typed_tab.msg_refsID
-
- // If it is a DM, then we use regular message ID. If it is a MUC, we use stanza id
- to_retract = sid.ID
- if typed_tab.isMuc && sid != nil {
- to_retract = sid.ID
- refs = typed_tab.msg_refsOSID
- }
- */
-
- msg, ok := typed_tab.msg_refsOSID[retraction.ID]
+ umsg, ok := typed_tab.msg_refsOSID.Load(retraction.ID)
if ok {
- lab := msg.label_ref
- glib.IdleAdd(func() {
- GenerateRetraction(lab, *retraction)
- })
- return
+ msg, ok := umsg.(*abMessage)
+ if ok {
+ lab := msg.label_ref
+ glib.IdleAdd(func() {
+ GenerateRetraction(lab, *retraction)
+ })
+ return
+ }
}
}
}
- glib.IdleAdd(func() {
+ glib.IdleAddPriority(glib.PriorityLow, func() {
b := gtk.NewBox(gtk.OrientationVertical, 0)
tab, ok := tabs.Load(originator)
@@ -472,9 +477,11 @@ func main() {
if valid && converted_successfully {
mStatus.SetText(originator)
- typed_tab.msgs.Append(b)
- b.Append(ba)
- b.Append(gtk.NewSeparator(gtk.OrientationVertical))
+ if typed_tab.msgs != nil {
+ typed_tab.msgs.Append(b)
+ b.Append(ba)
+ b.Append(gtk.NewSeparator(gtk.OrientationVertical))
+ }
}
})
})
@@ -496,6 +503,20 @@ func main() {
ok = presence.Get(&mu)
if ok { // This is a presence stanza from a user in a MUC
+ from_self, created := false, false
+ for _, status := range mu.StatusCodes {
+ if status.Code == "110" {
+ from_self = true
+ }
+
+ if status.Code == "201" {
+ created = true
+ }
+ }
+
+ if from_self && created {
+ showDialog("You have created a new MUC. In order for people to be able to join this MUC, you must first configure it. You can configure this MUC by clicking on the MUC button in the top right and then clicking 'Configre MUC'.", &window.Window)
+ }
presence.Get(&ocu)
id := JidMustParse(presence.From).Resource
@@ -524,7 +545,7 @@ func main() {
if presence.Type != "unavailable" {
_, ok := typed_unit.Members.Load(id)
if !ok && loadedConfig.ShowPresenceUpdates {
- glib.IdleAdd(func() {
+ glib.IdleAddPriority(glib.PriorityLow, func() {
b := gtk.NewBox(gtk.OrientationVertical, 0)
ba, ok := generatePresenceWidget(p).(*gtk.Box)
if ok {
@@ -548,7 +569,7 @@ func main() {
} else {
typed_unit.Members.Delete(id)
if loadedConfig.ShowPresenceUpdates {
- glib.IdleAdd(func() {
+ glib.IdleAddPriority(glib.PriorityLow, func() {
b := gtk.NewBox(gtk.OrientationVertical, 0)
ba, ok := generatePresenceWidget(p).(*gtk.Box)
if ok {
@@ -604,7 +625,7 @@ func main() {
cm := xmpp.NewStreamManager(c, func(c xmpp.Sender) {
fmt.Println("XMPP client connected")
- // client = c
+ client = c
// clientroot = c
go func() {
@@ -810,7 +831,7 @@ func main() {
}
}
- app := gtk.NewApplication("net.sunglocto.lambda", gio.ApplicationDefaultFlags)
+ app := gtk.NewApplication(app_id, gio.ApplicationDefaultFlags)
app.ConnectActivate(func() {
activate(app)
go conc()
@@ -834,6 +855,86 @@ func activate(app *gtk.Application) {
fileMenu := gio.NewMenu()
fileMenu.Append(loadedLocale["joinMUCMenu"], "app.join")
fileMenu.Append(loadedLocale["destroyMUCMenu"], "app.destroymuc")
+ fileMenu.Append("MUC Affiliations", "app.mucaffils")
+ fileMenu.Append("Configure MUC", "app.configuremuc")
+
+ viewAffilsMucAction := gio.NewSimpleAction("mucaffils", nil)
+ viewAffilsMucAction.ConnectActivate(func(p *glib.Variant) {
+ go func() {
+ cur, ok := tabs.Load(current)
+ if ok {
+ cur := cur.(*chatTab)
+ if cur.isMuc {
+ affils := []string{"owner", "admin", "member", "outcast"}
+ items := []MucAdminItem{}
+
+ for _, affil := range affils {
+ iq, err := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, To: current, Id: uuid.NewString(), From: clientroot.Session.BindJid})
+ if err != nil {
+ return
+ }
+
+ q := new(MucAdminQuery)
+ q.MucAdminItems = append(q.MucAdminItems, MucAdminItem{Affiliation: affil})
+ iq.Payload = q
+ ctx := context.TODO()
+ mychan, err := client.SendIQ(ctx, iq)
+
+ result := <-mychan
+ res, ok := result.Payload.(*MucAdminQuery)
+ if ok {
+ for _, item := range res.MucAdminItems {
+ items = append(items, item)
+ }
+ }
+ }
+
+ glib.IdleAdd(func() {
+ affil_win := gtk.NewWindow()
+ affil_box := gtk.NewBox(gtk.OrientationVertical, 2)
+ affil_box.Append(gtk.NewLabel("Affiliations"))
+
+ scroller := gtk.NewScrolledWindow()
+ scroller_grid := gtk.NewGrid()
+ scroller_grid.SetVExpand(true)
+ scroller.SetChild(scroller_grid)
+ scroller.SetVExpand(true)
+
+ affil_box.Append(scroller)
+
+ affil_win.SetChild(affil_box)
+ scroller_grid.Attach(gtk.NewLabel("Affiliation"), 0, 0, 1, 1)
+ scroller_grid.Attach(gtk.NewLabel("JID"), 1, 0, 1, 1)
+ scroller_grid.Attach(gtk.NewLabel("Nick"), 2, 0, 1, 1)
+ scroller_grid.Attach(gtk.NewLabel("Reason"), 3, 0, 1, 1)
+
+ for i, v := range items {
+ medal_box := gtk.NewBox(gtk.OrientationHorizontal, 0)
+ medal_box.Append(gtk.NewImageFromPaintable(clientAssetsLoad(v.Affiliation)))
+ medal_box.Append(gtk.NewLabel(v.Affiliation))
+ scroller_grid.Attach(medal_box, 0, i+1, 1, 1)
+
+ jid := gtk.NewLabel(v.JID)
+ jid.AddCSSClass("jid")
+ jid.SetSelectable(true)
+ scroller_grid.Attach(jid, 1, i+1, 1, 1)
+
+ nick := gtk.NewLabel(v.Nick)
+ nick.SetSelectable(true)
+ scroller_grid.Attach(nick, 2, i+1, 1, 1)
+
+ reason := gtk.NewLabel(v.Reason)
+ reason.SetSelectable(true)
+ reason.SetEllipsize(pango.EllipsizeEnd)
+ scroller_grid.Attach(reason, 3, i+1, 1, 1)
+ }
+
+ affil_win.SetVisible(true)
+ })
+ }
+ }
+ }()
+ })
dmMenu := gio.NewMenu()
dmMenu.Append(loadedLocale["startDMMenu"], "app.dm")
@@ -890,12 +991,81 @@ func activate(app *gtk.Application) {
a.SetAuthors([]string{"Sunglocto", "Hydrogen", "Kuyuhi"})
a.SetLicenseType(gtk.LicenseGPL30)
a.SetLogo(clientAssetsLoad("logo"))
- a.SetWebsite("https://forge.sunglocto.net/sunglocto/lambda")
+ a.SetWebsite("https://sunglocto.net/lambda")
a.SetWebsiteLabel("Website")
a.SetVisible(true)
})
+ configureMucAction := gio.NewSimpleAction("configuremuc", nil)
+ configureMucAction.ConnectActivate(func(p *glib.Variant) {
+ go func() {
+ cur, ok := tabs.Load(current)
+ if ok {
+ cur := cur.(*chatTab)
+ if cur.isMuc {
+ iq, err := stanza.NewIQ(stanza.Attrs{
+ Type: stanza.IQTypeGet,
+ Id: uuid.NewString(),
+ To: current,
+ })
+
+ if err != nil {
+ glib.IdleAdd(func() { showErrorDialog(err, &window.Window) })
+ return
+ }
+
+ iq.Payload = &MucOwnerQuery{}
+
+ ctx := context.TODO()
+ mychan, err := client.SendIQ(ctx, iq)
+ if err != nil {
+ glib.IdleAdd(func() { showErrorDialog(err, &window.Window) })
+ return
+ }
+
+ result := <-mychan
+ res, ok := result.Payload.(*MucOwnerQuery)
+
+ if !ok {
+ return
+ }
+
+ glib.IdleAdd(func() {
+ f, win, _ := showFormDialog(res.Form)
+ win.ConnectCloseRequest(func() (ok bool) {
+ go func() {
+ iq, err := stanza.NewIQ(stanza.Attrs{
+ Id: result.Id,
+ To: result.From,
+ Type: stanza.IQTypeSet,
+ })
+ if err != nil {
+ glib.IdleAdd(func() { showErrorDialog(err, &window.Window) })
+ return
+ }
+
+ f.Type = "submit"
+ mo := new(MucOwnerQuery)
+ mo.Form = *f
+
+ iq.Payload = mo
+
+ ctx := context.TODO()
+ _, err = client.SendIQ(ctx, iq)
+ if err != nil {
+ glib.IdleAdd(func() { showErrorDialog(err, &window.Window) })
+ return
+ }
+ }()
+ return false
+ })
+ })
+ }
+ }
+ }()
+ })
+
destroymucAction := gio.NewSimpleAction("destroymuc", nil)
destroymucAction.ConnectActivate(func(p *glib.Variant) {
cur, ok := tabs.Load(current)
@@ -919,7 +1089,6 @@ func activate(app *gtk.Application) {
submit := gtk.NewButtonWithLabel(loadedLocale["destroyMUCActionButton"])
submit.ConnectClicked(func() {
- fmt.Println(en.Text())
if en.Text() == loadedLocale["destroyMUCPassword"] {
cur, ok := tabs.Load(current)
if ok {
@@ -1069,7 +1238,6 @@ func activate(app *gtk.Application) {
var res *stanza.DiscoInfo
allowed := true
- fmt.Println("Attempting to get Disco info")
myIQ, err := stanza.NewIQ(stanza.Attrs{
Type: "get",
@@ -1225,7 +1393,9 @@ func activate(app *gtk.Application) {
app.AddAction(joinAction)
app.AddAction(aboutAction)
app.AddAction(destroymucAction)
+ app.AddAction(configureMucAction)
app.AddAction(startDmAction)
+ app.AddAction(viewAffilsMucAction)
the_menu.AppendSubmenu("MUC", fileMenu)
the_menu.AppendSubmenu("DM", dmMenu)
@@ -1350,16 +1520,13 @@ func activate(app *gtk.Application) {
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)
diff --git a/style.css b/style.css
index fd09de1..4f5ea99 100644
--- a/style.css
+++ b/style.css
@@ -139,3 +139,8 @@
color: black;
}
+
+.you {
+ color: red;
+ text-decoration: underline;
+}
diff --git a/types.go b/types.go
index 16609a1..300ab35 100644
--- a/types.go
+++ b/types.go
@@ -14,10 +14,11 @@ type abMessage struct {
}
type chatTab struct {
- isMuc bool
- msgs *gtk.ListBox
- msg_refsID map[string]*abMessage
- msg_refsOSID map[string]*abMessage
+ isMuc bool
+ msgs *gtk.ListBox
+ msg_refsID map[string]*abMessage
+ // msg_refsOSID map[string]*abMessage
+ msg_refsOSID sync.Map
name string
current_nick string
muc_presence *stanza.Presence
diff --git a/version.go b/version.go
index a570502..1d36c6b 100644
--- a/version.go
+++ b/version.go
@@ -1,3 +1,3 @@
package main
-var lambda_version string = "10"
+var lambda_version string = "11"
diff --git a/xmpp-helpers.go b/xmpp-helpers.go
index cb3d4ac..d2aa54c 100644
--- a/xmpp-helpers.go
+++ b/xmpp-helpers.go
@@ -37,6 +37,7 @@ func joinMuc(c xmpp.Sender, jid string, muc string, nick string, password string
Attrs: stanza.Attrs{
From: jid,
To: addr,
+ Lang: "en",
},
Extensions: []stanza.PresExtension{
&stanza.MucPresence{},
@@ -47,6 +48,7 @@ func joinMuc(c xmpp.Sender, jid string, muc string, nick string, password string
Attrs: stanza.Attrs{
From: jid,
To: addr,
+ Lang: "en",
},
Extensions: []stanza.PresExtension{
&stanza.MucPresence{
@@ -162,11 +164,13 @@ func addPEPBookmark(c xmpp.Sender, jid string, name string, nick string, autojoi
persist.ValuesList = []string{"true"}
options_form.Fields = append(options_form.Fields, persist)
- max_items := new(stanza.Field)
- max_items.Type = "text-single"
- max_items.Var = "pubsub#max_items"
- max_items.ValuesList = []string{"max"}
- options_form.Fields = append(options_form.Fields, max_items)
+ /*
+ max_items := new(stanza.Field)
+ max_items.Type = "text-single"
+ max_items.Var = "pubsub#max_items"
+ max_items.ValuesList = []string{"max"}
+ options_form.Fields = append(options_form.Fields, max_items)
+ */
send_last_published_item := new(stanza.Field)
send_last_published_item.Type = "text-single"
diff --git a/xmpp-mucadmin.go b/xmpp-mucadmin.go
new file mode 100644
index 0000000..8ccb8f8
--- /dev/null
+++ b/xmpp-mucadmin.go
@@ -0,0 +1,35 @@
+package main
+
+// Implementation of XEP-0045 admin
+// https://xmpp.org/extensions/xep-0045.html
+
+import (
+ "encoding/xml"
+ "gosrc.io/xmpp/stanza"
+)
+
+type MucAdminQuery struct {
+ XMLName xml.Name `xml:"http://jabber.org/protocol/muc#admin query"`
+ MucAdminItems []MucAdminItem `xml:"http://jabber.org/protocol/muc#admin item"`
+ ResultSet *stanza.ResultSet `xml:"set,omitempty"`
+}
+
+type MucAdminItem struct {
+ Affiliation string `xml:"affiliation,attr,omitempty"`
+ JID string `xml:"jid,attr,omitempty"`
+ Nick string `xml:"nick,attr,omitempty"`
+ Role string `xml:"role,attr,omitempty"`
+ Reason string `xml:"reason,omitempty"`
+}
+
+func (v *MucAdminQuery) Namespace() string {
+ return v.XMLName.Space
+}
+
+func (v *MucAdminQuery) GetSet() *stanza.ResultSet {
+ return v.ResultSet
+}
+
+func init() {
+ stanza.TypeRegistry.MapExtension(stanza.PKTIQ, xml.Name{Space: "http://jabber.org/protocol/muc#admin", Local: "query"}, MucAdminQuery{})
+}
diff --git a/xmpp-mucowner.go b/xmpp-mucowner.go
new file mode 100644
index 0000000..93a6fd7
--- /dev/null
+++ b/xmpp-mucowner.go
@@ -0,0 +1,27 @@
+package main
+
+// Implementation of XEP-0045 admin
+// https://xmpp.org/extensions/xep-0045.html
+
+import (
+ "encoding/xml"
+ "gosrc.io/xmpp/stanza"
+)
+
+type MucOwnerQuery struct {
+ XMLName xml.Name `xml:"http://jabber.org/protocol/muc#owner query"`
+ ResultSet *stanza.ResultSet `xml:"set,omitempty"`
+ Form stanza.Form `xml:"jabber:x:data x,omitempty"`
+}
+
+func (v *MucOwnerQuery) Namespace() string {
+ return v.XMLName.Space
+}
+
+func (v *MucOwnerQuery) GetSet() *stanza.ResultSet {
+ return v.ResultSet
+}
+
+func init() {
+ stanza.TypeRegistry.MapExtension(stanza.PKTIQ, xml.Name{Space: "http://jabber.org/protocol/muc#owner", Local: "query"}, MucOwnerQuery{})
+}
diff --git a/xmpp-mucuser.go b/xmpp-mucuser.go
index 5a5a704..24ec78f 100644
--- a/xmpp-mucuser.go
+++ b/xmpp-mucuser.go
@@ -11,8 +11,9 @@ import (
type MucUser struct {
stanza.PresExtension
stanza.MsgExtension
- XMLName xml.Name `xml:"http://jabber.org/protocol/muc#user x"`
- MucUserItem MucUserItem `xml:"item,omitempty"`
+ XMLName xml.Name `xml:"http://jabber.org/protocol/muc#user x"`
+ StatusCodes []MucUserStatusCode `xml:"status,omitempty"`
+ MucUserItem MucUserItem `xml:"item,omitempty"`
}
type MucUserItem struct {
@@ -29,6 +30,10 @@ type Actor struct {
Nick string `xml:"nick,attr"`
}
+type MucUserStatusCode struct {
+ Code string `xml:"code,attr"`
+}
+
func init() {
stanza.TypeRegistry.MapExtension(stanza.PKTPresence, xml.Name{Space: "http://jabber.org/protocol/muc#user", Local: "x"}, MucUser{})
stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{Space: "http://jabber.org/protocol/muc#user", Local: "x"}, MucUser{})