Rest in peace Dolly Patron
@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
an XMPP client
|
an XMPP client
|
||||||
|
|
||||||
|
Experimental XMPP client written in Go, with the GTK4 UI toolkit and the Fluux IO XMPP libraries.
|
||||||
|
|
||||||
icons are from Psi+ ([https://github.com/psi-im](https://github.com/psi-im))
|
icons are from Psi+ ([https://github.com/psi-im](https://github.com/psi-im))
|
||||||
|
|
||||||
additional icons are by Mark James's Silk Icon Set [https://github.com/markjames/famfamfam-silk-icons](https://github.com/markjames/famfamfam-silk-icons)
|
additional icons are by Mark James's Silk Icon Set [https://github.com/markjames/famfamfam-silk-icons](https://github.com/markjames/famfamfam-silk-icons) and the [Haiku project](https://www.haiku-os.org/)
|
||||||
|
|
||||||
|
pepper & carrot default avatar is from [Pepper&Carrot](https://peppercarrot.com) - created by David Revoy, licensed under Creative Commons Attribution 4.0. Change made: cropped and made black & white
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed debug.png
|
// If you want to change the default profile picture used when Identicons are disabled, you can change the below line to an image in the repository and recompile Lambda.
|
||||||
|
|
||||||
|
//go:embed debug_psi.png
|
||||||
var defaultAvatarBytes []byte
|
var defaultAvatarBytes []byte
|
||||||
|
|
||||||
//go:embed failed_load.png
|
//go:embed failed_load.png
|
||||||
@@ -159,6 +161,53 @@ var solarBytes []byte
|
|||||||
//go:embed assets/timeline_marker.png
|
//go:embed assets/timeline_marker.png
|
||||||
var timelineMarkerBytes []byte
|
var timelineMarkerBytes []byte
|
||||||
|
|
||||||
|
//go:embed assets/cap.png
|
||||||
|
var capIconBytes []byte
|
||||||
|
|
||||||
|
// client icons
|
||||||
|
|
||||||
|
//go:embed assets/client_icons/monocles.png
|
||||||
|
var monoclesCapBytes []byte
|
||||||
|
|
||||||
|
//go:embed assets/client_icons/gajim.png
|
||||||
|
var gajimCapBytes []byte
|
||||||
|
|
||||||
|
//go:embed assets/client_icons/conversations.png
|
||||||
|
var conversationsCapBytes []byte
|
||||||
|
|
||||||
|
//go:embed assets/client_icons/dino.png
|
||||||
|
var dinoCapBytes []byte
|
||||||
|
|
||||||
|
//go:embed assets/client_icons/blabber.png
|
||||||
|
var blabberCapBytes []byte
|
||||||
|
|
||||||
|
//go:embed assets/client_icons/profanity.png
|
||||||
|
var profanityCapBytes []byte
|
||||||
|
|
||||||
|
//go:embed assets/client_icons/poezio.png
|
||||||
|
var poezioCapBytes []byte
|
||||||
|
|
||||||
|
//go:embed assets/client_icons/mcabber.png
|
||||||
|
var mcabberCapBytes []byte
|
||||||
|
|
||||||
|
//go:embed assets/client_icons/converse.png
|
||||||
|
var converseJSCapBytes []byte
|
||||||
|
|
||||||
|
//go:embed assets/client_icons/pidgin.png
|
||||||
|
var pidginCapBytes []byte
|
||||||
|
|
||||||
|
//go:embed assets/client_icons/anotherim.png
|
||||||
|
var anotherImCapBytes []byte
|
||||||
|
|
||||||
|
//go:embed assets/client_icons/movim.png
|
||||||
|
var movimCapBytes []byte
|
||||||
|
|
||||||
|
//go:embed assets/client_icons/yaxim.png
|
||||||
|
var yaximCapBytes []byte
|
||||||
|
|
||||||
|
//go:embed assets/client_icons/psi.png
|
||||||
|
var psiCapBytes []byte
|
||||||
|
|
||||||
var clientAssets sync.Map
|
var clientAssets sync.Map
|
||||||
|
|
||||||
func loadAsset(key string, data []byte) {
|
func loadAsset(key string, data []byte) {
|
||||||
@@ -234,6 +283,22 @@ func init() {
|
|||||||
"update": updateBytes,
|
"update": updateBytes,
|
||||||
"solar": solarBytes,
|
"solar": solarBytes,
|
||||||
"timeline_marker": timelineMarkerBytes,
|
"timeline_marker": timelineMarkerBytes,
|
||||||
|
"cap": capIconBytes,
|
||||||
|
|
||||||
|
"https://monocles.eu": monoclesCapBytes,
|
||||||
|
"https://gajim.org": gajimCapBytes,
|
||||||
|
"http://conversations.im": conversationsCapBytes,
|
||||||
|
"https://dino.im": dinoCapBytes,
|
||||||
|
"http://blabber.im": blabberCapBytes,
|
||||||
|
"http://profanity-im.github.io": profanityCapBytes,
|
||||||
|
"https://poez.io": poezioCapBytes,
|
||||||
|
"http://mcabber.com/caps": mcabberCapBytes,
|
||||||
|
"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,
|
||||||
} {
|
} {
|
||||||
loadAsset(key, data)
|
loadAsset(key, data)
|
||||||
}
|
}
|
||||||
|
|||||||
|
After Width: | Height: | Size: 287 B |
|
After Width: | Height: | Size: 783 B |
|
After Width: | Height: | Size: 196 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 246 B |
|
After Width: | Height: | Size: 551 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 362 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 588 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 263 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 91 KiB |
@@ -0,0 +1,26 @@
|
|||||||
|
package stanza
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Capabilities
|
||||||
|
// Reference: https://xmpp.org/extensions/xep-0115.html#stream
|
||||||
|
// "A server MAY include its entity capabilities in a stream feature element so that connecting clients
|
||||||
|
// and peer servers do not need to send service discovery requests each time they connect."
|
||||||
|
// This is not a stream feature but a way to let client cache server disco info.
|
||||||
|
|
||||||
|
// Moved from stream_features.go and made it a presence extension as per spec
|
||||||
|
|
||||||
|
type Caps struct {
|
||||||
|
PresExtension
|
||||||
|
XMLName xml.Name `xml:"http://jabber.org/protocol/caps c"`
|
||||||
|
Hash string `xml:"hash,attr"`
|
||||||
|
Node string `xml:"node,attr"`
|
||||||
|
Ver string `xml:"ver,attr"`
|
||||||
|
Ext string `xml:"ext,attr,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
TypeRegistry.MapExtension(PKTPresence, xml.Name{Space: "http://jabber.org/protocol/caps", Local: "c"}, Caps{})
|
||||||
|
}
|
||||||
@@ -42,19 +42,6 @@ func (streamFeatureDecoder) decode(p *xml.Decoder, se xml.StartElement) (StreamF
|
|||||||
return packet, err
|
return packet, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Capabilities
|
|
||||||
// Reference: https://xmpp.org/extensions/xep-0115.html#stream
|
|
||||||
// "A server MAY include its entity capabilities in a stream feature element so that connecting clients
|
|
||||||
// and peer servers do not need to send service discovery requests each time they connect."
|
|
||||||
// This is not a stream feature but a way to let client cache server disco info.
|
|
||||||
type Caps struct {
|
|
||||||
XMLName xml.Name `xml:"http://jabber.org/protocol/caps c"`
|
|
||||||
Hash string `xml:"hash,attr"`
|
|
||||||
Node string `xml:"node,attr"`
|
|
||||||
Ver string `xml:"ver,attr"`
|
|
||||||
Ext string `xml:"ext,attr,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Supported Stream Features
|
// Supported Stream Features
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"image/png"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/boxes-ltd/imaging"
|
"github.com/boxes-ltd/imaging"
|
||||||
"github.com/crazy3lf/colorconv"
|
"github.com/crazy3lf/colorconv"
|
||||||
"github.com/diamondburned/gotk4/pkg/gdk/v4"
|
"github.com/diamondburned/gotk4/pkg/gdk/v4"
|
||||||
@@ -14,20 +19,45 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/rrivera/identicon"
|
"github.com/rrivera/identicon"
|
||||||
"gosrc.io/xmpp/stanza"
|
"gosrc.io/xmpp/stanza"
|
||||||
"image"
|
|
||||||
"image/color"
|
|
||||||
"image/png"
|
|
||||||
xmpp_color "mellium.im/xmpp/color"
|
xmpp_color "mellium.im/xmpp/color"
|
||||||
"strconv"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func createTabBox(name, jid, av_hash string, removable bool) *gtk.Box {
|
||||||
|
box := gtk.NewBox(gtk.OrientationHorizontal, 10)
|
||||||
|
name_label := gtk.NewLabel(name)
|
||||||
|
left_click_gesture := gtk.NewGestureClick()
|
||||||
|
left_click_gesture.SetButton(1)
|
||||||
|
left_click_gesture.Connect("pressed", func() {
|
||||||
|
switchToTab(jid, &window.Window)
|
||||||
|
})
|
||||||
|
box.AddController(left_click_gesture)
|
||||||
|
|
||||||
|
if removable {
|
||||||
|
right_click_gesture := gtk.NewGestureClick()
|
||||||
|
right_click_gesture.SetButton(3)
|
||||||
|
right_click_gesture.Connect("pressed", func() {
|
||||||
|
removeTab(jid, &window.Window)
|
||||||
|
box.SetVisible(false)
|
||||||
|
})
|
||||||
|
box.AddController(right_click_gesture)
|
||||||
|
}
|
||||||
|
|
||||||
|
box.Append(name_label)
|
||||||
|
image := gtk.NewImage()
|
||||||
|
image.SetPixelSize(40)
|
||||||
|
go getAvatar(jid, av_hash, image)
|
||||||
|
box.Prepend(image)
|
||||||
|
|
||||||
|
return box
|
||||||
}
|
}
|
||||||
|
|
||||||
func showUserWindow(u stanza.Presence, custom_nick string) {
|
func showUserWindow(u stanza.Presence, custom_nick string) {
|
||||||
var ocu OccupantID
|
var ocu OccupantID
|
||||||
u.Get(&ocu)
|
u.Get(&ocu)
|
||||||
|
|
||||||
|
var caps stanza.Caps
|
||||||
|
u.Get(&caps)
|
||||||
|
|
||||||
var id string
|
var id string
|
||||||
id = JidMustParse(u.From).Resource
|
id = JidMustParse(u.From).Resource
|
||||||
|
|
||||||
@@ -67,7 +97,7 @@ func showUserWindow(u stanza.Presence, custom_nick string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if loadedConfig.BlockingOI[ocu.ID] {
|
if loadedConfig.BlockingOI[ocu.ID] {
|
||||||
blocked_note := gtk.NewLabel("You have blocked this user!")
|
blocked_note := gtk.NewLabel(loadedLocale["blockedUserNotice"])
|
||||||
blocked_note.AddCSSClass("blocked")
|
blocked_note.AddCSSClass("blocked")
|
||||||
profile_box.Prepend(blocked_note)
|
profile_box.Prepend(blocked_note)
|
||||||
}
|
}
|
||||||
@@ -82,7 +112,7 @@ func showUserWindow(u stanza.Presence, custom_nick string) {
|
|||||||
Type: "get",
|
Type: "get",
|
||||||
From: clientroot.Session.BindJid,
|
From: clientroot.Session.BindJid,
|
||||||
To: u.From,
|
To: u.From,
|
||||||
Id: uuid.New().String(),
|
Id: uuid.NewString(),
|
||||||
Lang: "en",
|
Lang: "en",
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -135,7 +165,7 @@ func showUserWindow(u stanza.Presence, custom_nick string) {
|
|||||||
Type: "get",
|
Type: "get",
|
||||||
From: clientroot.Session.BindJid,
|
From: clientroot.Session.BindJid,
|
||||||
To: u.From,
|
To: u.From,
|
||||||
Id: uuid.New().String(),
|
Id: uuid.NewString(),
|
||||||
Lang: "en",
|
Lang: "en",
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -210,30 +240,43 @@ func showUserWindow(u stanza.Presence, custom_nick string) {
|
|||||||
mmmm, ok := mmm.Load(id)
|
mmmm, ok := mmm.Load(id)
|
||||||
|
|
||||||
im := gtk.NewImage()
|
im := gtk.NewImage()
|
||||||
|
im.SetPixelSize(160)
|
||||||
|
im.AddCSSClass("author_img")
|
||||||
|
profile_box.Prepend(im)
|
||||||
|
createIdenticon(u.From, false, im)
|
||||||
|
|
||||||
if ok {
|
if ok {
|
||||||
pres := mmmm.(stanza.Presence)
|
pres := mmmm.(stanza.Presence)
|
||||||
|
|
||||||
var vu VCardUpdate
|
var vu VCardUpdate
|
||||||
pres.Get(&vu)
|
pres.Get(&vu)
|
||||||
if vu.Photo != "" {
|
if vu.Photo != "" {
|
||||||
getAvatar(u.From, vu.Photo, im)
|
getAvatar(u.From, vu.Photo, im)
|
||||||
im.SetPixelSize(80)
|
|
||||||
im.AddCSSClass("author_img")
|
|
||||||
profile_box.Prepend(im)
|
|
||||||
} else {
|
|
||||||
createIdenticon(u.From, false, im)
|
|
||||||
im.SetPixelSize(80)
|
|
||||||
im.AddCSSClass("author_img")
|
|
||||||
profile_box.Prepend(im)
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
createIdenticon(u.From, false, im)
|
|
||||||
im.SetPixelSize(80)
|
|
||||||
im.AddCSSClass("author_img")
|
|
||||||
profile_box.Prepend(im)
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
if caps.Hash != "" {
|
||||||
|
cap_box := gtk.NewBox(gtk.OrientationHorizontal, 2)
|
||||||
|
cap_box.SetHAlign(gtk.AlignCenter)
|
||||||
|
icon := gtk.NewImageFromPaintable(clientAssetsLoad("cap"))
|
||||||
|
icon.SetCursor(gdk.NewCursorFromName("pointer", nil))
|
||||||
|
|
||||||
|
cap_box.Append(icon)
|
||||||
|
cap_box.Append(gtk.NewLabel(fmt.Sprintf("Entity capabilities: Node %s", caps.Node)))
|
||||||
|
hasher := gtk.NewLabel(fmt.Sprintf("Using %s hash: %s", caps.Hash, caps.Ver))
|
||||||
|
hasher.SetVisible(false)
|
||||||
|
cap_box.Append(hasher)
|
||||||
|
|
||||||
|
left_click_gesture := gtk.NewGestureClick()
|
||||||
|
left_click_gesture.SetButton(1)
|
||||||
|
left_click_gesture.Connect("pressed", func() {
|
||||||
|
hasher.SetVisible(!(hasher.Visible()))
|
||||||
|
})
|
||||||
|
icon.AddController(left_click_gesture)
|
||||||
|
profile_box.Append(cap_box)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
win.SetChild(profile_box)
|
win.SetChild(profile_box)
|
||||||
win.SetTransientFor(win)
|
win.SetTransientFor(win)
|
||||||
win.Present()
|
win.Present()
|
||||||
@@ -311,9 +354,8 @@ func switchToTab(jid string, w *gtk.Window) {
|
|||||||
typed_tab := tab.(*chatTab)
|
typed_tab := tab.(*chatTab)
|
||||||
scroller.SetChild(typed_tab.msgs)
|
scroller.SetChild(typed_tab.msgs)
|
||||||
typingStatus.SetText("")
|
typingStatus.SetText("")
|
||||||
if typed_tab.isMuc {
|
|
||||||
m, ok := mucmembers.Load(jid)
|
createFailBox := func() {
|
||||||
if !ok {
|
|
||||||
box := gtk.NewBox(gtk.OrientationVertical, 10)
|
box := gtk.NewBox(gtk.OrientationVertical, 10)
|
||||||
failed_icon := gtk.NewImageFromPaintable(clientAssetsLoad("fail"))
|
failed_icon := gtk.NewImageFromPaintable(clientAssetsLoad("fail"))
|
||||||
failed_icon.SetPixelSize(100)
|
failed_icon.SetPixelSize(100)
|
||||||
@@ -322,26 +364,20 @@ func switchToTab(jid string, w *gtk.Window) {
|
|||||||
jid_label.AddCSSClass("author")
|
jid_label.AddCSSClass("author")
|
||||||
jid_label.AddCSSClass("jid")
|
jid_label.AddCSSClass("jid")
|
||||||
box.Append(jid_label)
|
box.Append(jid_label)
|
||||||
label := gtk.NewLabel("There was an error loading the members of this room. Maybe the MUC does not give user presence, it does not exist, you have been banned from it, or you have to fill a CAPTCHA to access it. Try joining the room again or check if the room exists.")
|
label := gtk.NewLabel(loadedLocale["mucLoadError"])
|
||||||
label.SetWrap(true)
|
label.SetWrap(true)
|
||||||
box.Append(label)
|
box.Append(label)
|
||||||
memberList.SetChild(box)
|
memberList.SetChild(box)
|
||||||
|
}
|
||||||
|
if typed_tab.isMuc {
|
||||||
|
m, ok := mucmembers.Load(jid)
|
||||||
|
if !ok {
|
||||||
|
createFailBox()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ma, ok := m.(mucUnit)
|
ma, ok := m.(mucUnit)
|
||||||
if !ok {
|
if !ok {
|
||||||
box := gtk.NewBox(gtk.OrientationVertical, 10)
|
createFailBox()
|
||||||
failed_icon := gtk.NewImageFromPaintable(clientAssetsLoad("fail"))
|
|
||||||
failed_icon.SetPixelSize(100)
|
|
||||||
box.Append(failed_icon)
|
|
||||||
jid_label := gtk.NewLabel(jid)
|
|
||||||
jid_label.AddCSSClass("author")
|
|
||||||
jid_label.AddCSSClass("jid")
|
|
||||||
box.Append(jid_label)
|
|
||||||
label := gtk.NewLabel("There was an error loading the members of this room. Maybe the MUC does not give user presence, it does not exist, you have been banned from it, or you have to fill a CAPTCHA to access it. Try joining the room again or check if the room exists.")
|
|
||||||
label.SetWrap(true)
|
|
||||||
box.Append(label)
|
|
||||||
memberList.SetChild(box)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
mm := ma.Members
|
mm := ma.Members
|
||||||
@@ -370,11 +406,6 @@ func switchToTab(jid string, w *gtk.Window) {
|
|||||||
gen.Append(gtk.NewSeparator(gtk.OrientationHorizontal))
|
gen.Append(gtk.NewSeparator(gtk.OrientationHorizontal))
|
||||||
}
|
}
|
||||||
|
|
||||||
//id := ocu.ID
|
|
||||||
//if id == "" {
|
|
||||||
// id := JidMustParse(u.From).Resource
|
|
||||||
//}
|
|
||||||
|
|
||||||
nick_label := gtk.NewLabel(JidMustParse(u.From).Resource)
|
nick_label := gtk.NewLabel(JidMustParse(u.From).Resource)
|
||||||
|
|
||||||
var custom_nick string
|
var custom_nick string
|
||||||
@@ -403,7 +434,7 @@ func switchToTab(jid string, w *gtk.Window) {
|
|||||||
nick_label.SetOpacity(0.5)
|
nick_label.SetOpacity(0.5)
|
||||||
}
|
}
|
||||||
|
|
||||||
userbox.SetTooltipText(fmt.Sprintf("%s\n%s\n%s\n%s", u.From, mu.MucUserItem.Role, mu.MucUserItem.Affiliation, loadedLocale["clickForMoreInfo"]))
|
// 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)
|
userbox.Append(nick_label)
|
||||||
|
|
||||||
var hats Hats
|
var hats Hats
|
||||||
@@ -418,7 +449,6 @@ func switchToTab(jid string, w *gtk.Window) {
|
|||||||
xc := xmpp_color.String(hat.URI, 255, loadedConfig.CVD)
|
xc := xmpp_color.String(hat.URI, 255, loadedConfig.CVD)
|
||||||
r, g, b, _ := xc.RGBA()
|
r, g, b, _ := xc.RGBA()
|
||||||
val, _, _ = colorconv.RGBToHSV(uint8(r), uint8(g), uint8(b))
|
val, _, _ = colorconv.RGBToHSV(uint8(r), uint8(g), uint8(b))
|
||||||
|
|
||||||
}
|
}
|
||||||
tB := tagBytes
|
tB := tagBytes
|
||||||
img, _, _ := image.Decode(bytes.NewReader(tB))
|
img, _, _ := image.Decode(bytes.NewReader(tB))
|
||||||
@@ -452,12 +482,26 @@ func switchToTab(jid string, w *gtk.Window) {
|
|||||||
userbox.Append(status_message)
|
userbox.Append(status_message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
end_box := gtk.NewBox(gtk.OrientationHorizontal, 0)
|
||||||
|
|
||||||
|
var caps stanza.Caps
|
||||||
|
ok = u.Get(&caps)
|
||||||
|
if ok {
|
||||||
|
client_icon := gtk.NewImageFromPaintable(clientAssetsLoad(caps.Node))
|
||||||
|
//client_icon.SetHAlign(gtk.AlignEnd)
|
||||||
|
//client_icon.SetHExpand(true)
|
||||||
|
end_box.Append(client_icon)
|
||||||
|
}
|
||||||
medal := gtk.NewImageFromPaintable(clientAssetsLoad(mu.MucUserItem.Affiliation))
|
medal := gtk.NewImageFromPaintable(clientAssetsLoad(mu.MucUserItem.Affiliation))
|
||||||
medal.SetTooltipText(mu.MucUserItem.Affiliation)
|
medal.SetTooltipText(mu.MucUserItem.Affiliation)
|
||||||
|
|
||||||
medal.SetHAlign(gtk.AlignEnd)
|
//medal.SetHAlign(gtk.AlignEnd)
|
||||||
medal.SetHExpand(true)
|
//medal.SetHExpand(true)
|
||||||
userbox.Append(medal)
|
end_box.Append(medal)
|
||||||
|
end_box.SetHAlign(gtk.AlignEnd)
|
||||||
|
end_box.SetHExpand(true)
|
||||||
|
|
||||||
|
userbox.Append(end_box)
|
||||||
|
|
||||||
if loadedConfig.ShowAvatarsInMemberList {
|
if loadedConfig.ShowAvatarsInMemberList {
|
||||||
default_av := gtk.NewImage()
|
default_av := gtk.NewImage()
|
||||||
@@ -479,6 +523,52 @@ func switchToTab(jid string, w *gtk.Window) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
userbox.SetHasTooltip(true)
|
||||||
|
userbox.Connect("query-tooltip", func(
|
||||||
|
x int,
|
||||||
|
y int,
|
||||||
|
keyboardMode bool,
|
||||||
|
tooltip *gtk.Tooltip,
|
||||||
|
) bool {
|
||||||
|
box := gtk.NewBox(gtk.OrientationHorizontal, 4)
|
||||||
|
av_box := gtk.NewBox(gtk.OrientationHorizontal, 0)
|
||||||
|
if loadedConfig.ShowAvatarsInMemberList {
|
||||||
|
default_av := gtk.NewImage()
|
||||||
|
default_av.SetPixelSize(100)
|
||||||
|
createIdenticon(u.From, false, default_av)
|
||||||
|
av_box.Prepend(default_av)
|
||||||
|
var vcu VCardUpdate
|
||||||
|
ok = u.Get(&vcu)
|
||||||
|
if ok {
|
||||||
|
photo := vcu.Photo
|
||||||
|
go func() {
|
||||||
|
glib.IdleAdd(func() {
|
||||||
|
new_im := gtk.NewImage()
|
||||||
|
new_im.SetPixelSize(100)
|
||||||
|
av_box.Remove(default_av)
|
||||||
|
av_box.Append(new_im)
|
||||||
|
go getAvatar(u.From, photo, new_im)
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
box.Append(av_box)
|
||||||
|
|
||||||
|
content_box := gtk.NewBox(gtk.OrientationVertical, 4)
|
||||||
|
content_box.Append(gtk.NewLabel(u.From))
|
||||||
|
content_box.Append(gtk.NewLabel(mu.MucUserItem.Role))
|
||||||
|
content_box.Append(gtk.NewLabel(mu.MucUserItem.Affiliation))
|
||||||
|
content_box.Append(gtk.NewLabel(mu.MucUserItem.JID))
|
||||||
|
|
||||||
|
box.Append(content_box)
|
||||||
|
|
||||||
|
tooltip.SetCustom(box)
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
gesture := gtk.NewGestureClick()
|
gesture := gtk.NewGestureClick()
|
||||||
gesture.SetButton(1)
|
gesture.SetButton(1)
|
||||||
|
|
||||||
@@ -498,7 +588,7 @@ func switchToTab(jid string, w *gtk.Window) {
|
|||||||
kb.ConnectClicked(func() {
|
kb.ConnectClicked(func() {
|
||||||
client.SendRaw(fmt.Sprintf(`
|
client.SendRaw(fmt.Sprintf(`
|
||||||
<iq from='%s'
|
<iq from='%s'
|
||||||
id='kick1'
|
id='%s'
|
||||||
to='%s'
|
to='%s'
|
||||||
type='set'>
|
type='set'>
|
||||||
<query xmlns='http://jabber.org/protocol/muc#admin'>
|
<query xmlns='http://jabber.org/protocol/muc#admin'>
|
||||||
@@ -506,7 +596,7 @@ func switchToTab(jid string, w *gtk.Window) {
|
|||||||
</item>
|
</item>
|
||||||
</query>
|
</query>
|
||||||
</iq>
|
</iq>
|
||||||
`, clientroot.Session.BindJid, jid, JidMustParse(u.From).Resource))
|
`, clientroot.Session.BindJid, uuid.NewString(), jid, JidMustParse(u.From).Resource))
|
||||||
})
|
})
|
||||||
|
|
||||||
bb.ConnectClicked(func() {
|
bb.ConnectClicked(func() {
|
||||||
@@ -516,14 +606,14 @@ func switchToTab(jid string, w *gtk.Window) {
|
|||||||
if mu.MucUserItem.JID != "" {
|
if mu.MucUserItem.JID != "" {
|
||||||
client.SendRaw(fmt.Sprintf(`
|
client.SendRaw(fmt.Sprintf(`
|
||||||
<iq from='%s'
|
<iq from='%s'
|
||||||
id='ban1'
|
id='%s'
|
||||||
to='%s'
|
to='%s'
|
||||||
type='set'>
|
type='set'>
|
||||||
<query xmlns='http://jabber.org/protocol/muc#admin'>
|
<query xmlns='http://jabber.org/protocol/muc#admin'>
|
||||||
<item affiliation='outcast' jid='%s'/>
|
<item affiliation='outcast' jid='%s'/>
|
||||||
</query>
|
</query>
|
||||||
</iq>
|
</iq>
|
||||||
`, clientroot.Session.BindJid, jid, JidMustParse(mu.MucUserItem.JID).Bare()))
|
`, clientroot.Session.BindJid, uuid.NewString(), jid, JidMustParse(mu.MucUserItem.JID).Bare()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -547,14 +637,14 @@ func switchToTab(jid string, w *gtk.Window) {
|
|||||||
submit.ConnectClicked(func() {
|
submit.ConnectClicked(func() {
|
||||||
client.SendRaw(fmt.Sprintf(`
|
client.SendRaw(fmt.Sprintf(`
|
||||||
<iq from='%s'
|
<iq from='%s'
|
||||||
id='ba1'
|
id='%s'
|
||||||
to='%s'
|
to='%s'
|
||||||
type='set'>
|
type='set'>
|
||||||
<query xmlns='http://jabber.org/protocol/muc#admin'>
|
<query xmlns='http://jabber.org/protocol/muc#admin'>
|
||||||
<item affiliation='%s' jid='%s'/>
|
<item affiliation='%s' jid='%s'/>
|
||||||
</query>
|
</query>
|
||||||
</iq>
|
</iq>
|
||||||
`, clientroot.Session.BindJid, jid, the_entry.Text(), JidMustParse(mu.MucUserItem.JID).Bare()))
|
`, clientroot.Session.BindJid, uuid.NewString(), jid, the_entry.Text(), JidMustParse(mu.MucUserItem.JID).Bare()))
|
||||||
win.Destroy()
|
win.Destroy()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -588,7 +678,7 @@ func switchToTab(jid string, w *gtk.Window) {
|
|||||||
|
|
||||||
client.SendRaw(fmt.Sprintf(`
|
client.SendRaw(fmt.Sprintf(`
|
||||||
<iq from='%s'
|
<iq from='%s'
|
||||||
id='kick1'
|
id='%s'
|
||||||
to='%s'
|
to='%s'
|
||||||
type='set'>
|
type='set'>
|
||||||
<query xmlns='http://jabber.org/protocol/muc#admin'>
|
<query xmlns='http://jabber.org/protocol/muc#admin'>
|
||||||
@@ -596,7 +686,7 @@ func switchToTab(jid string, w *gtk.Window) {
|
|||||||
</item>
|
</item>
|
||||||
</query>
|
</query>
|
||||||
</iq>
|
</iq>
|
||||||
`, clientroot.Session.BindJid, jid, JidMustParse(u.From).Resource, the_entry.Text()))
|
`, clientroot.Session.BindJid, uuid.NewString(), jid, JidMustParse(u.From).Resource, the_entry.Text()))
|
||||||
})
|
})
|
||||||
|
|
||||||
box.Append(the_entry)
|
box.Append(the_entry)
|
||||||
@@ -642,7 +732,13 @@ func switchToTab(jid string, w *gtk.Window) {
|
|||||||
} else {
|
} else {
|
||||||
headerBox.Append(gtk.NewImageFromPaintable(clientAssetsLoad("group")))
|
headerBox.Append(gtk.NewImageFromPaintable(clientAssetsLoad("group")))
|
||||||
}
|
}
|
||||||
headerBox.Append(gtk.NewLabel(fmt.Sprintf("%d %s", i, loadedLocale["participants"])))
|
|
||||||
|
pword := loadedLocale["participants"]
|
||||||
|
if i == 1 {
|
||||||
|
pword = loadedLocale["participant"]
|
||||||
|
}
|
||||||
|
|
||||||
|
headerBox.Append(gtk.NewLabel(fmt.Sprintf("%d %s", i, pword)))
|
||||||
gen.Prepend(headerBox)
|
gen.Prepend(headerBox)
|
||||||
|
|
||||||
muc_presence := typed_tab.muc_presence
|
muc_presence := typed_tab.muc_presence
|
||||||
@@ -717,37 +813,29 @@ func createIdenticon(word string, always_create bool, i *gtk.Image) { // This fu
|
|||||||
loader.Close()
|
loader.Close()
|
||||||
p := loader.Pixbuf()
|
p := loader.Pixbuf()
|
||||||
gt := gdk.NewTextureForPixbuf(p)
|
gt := gdk.NewTextureForPixbuf(p)
|
||||||
/*
|
|
||||||
i := gtk.NewImageFromPaintable(gt)
|
|
||||||
i.AddCSSClass(loadedConfig.CVD.String() + "_CVD")
|
|
||||||
return i
|
|
||||||
*/
|
|
||||||
|
|
||||||
glib.IdleAdd(func() {
|
glib.IdleAdd(func() {
|
||||||
i.SetFromPaintable(gt)
|
i.SetFromPaintable(gt)
|
||||||
i.AddCSSClass(loadedConfig.CVD.String() + "_CVD")
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func jidBuilder(en *gtk.Entry) { // This function spawns a window that allows the user to interactively build a JID
|
func jidBuilder(en *gtk.Entry) { // This function spawns a window that allows the user to interactively build a JID
|
||||||
// TODO: Localise this
|
|
||||||
|
|
||||||
win := gtk.NewWindow()
|
win := gtk.NewWindow()
|
||||||
win.SetTitle("Build-A-JID")
|
win.SetTitle(loadedLocale["buildAJID"])
|
||||||
win.SetDefaultSize(400, 1)
|
win.SetDefaultSize(400, 1)
|
||||||
win.SetResizable(false)
|
win.SetResizable(false)
|
||||||
|
|
||||||
box := gtk.NewBox(gtk.OrientationVertical, 2)
|
box := gtk.NewBox(gtk.OrientationVertical, 2)
|
||||||
header := gtk.NewLabel("Build-A-JID")
|
header := gtk.NewLabel(loadedLocale["buildAJID"])
|
||||||
header.AddCSSClass("author")
|
header.AddCSSClass("author")
|
||||||
box.Append(header)
|
box.Append(header)
|
||||||
|
|
||||||
box.Append(gtk.NewLabel("All fields except for domain are optional"))
|
box.Append(gtk.NewLabel(loadedLocale["buildAJIDOptionalFieldsNote"]))
|
||||||
|
|
||||||
jid_builder := gtk.NewBox(gtk.OrientationHorizontal, 2)
|
jid_builder := gtk.NewBox(gtk.OrientationHorizontal, 2)
|
||||||
|
|
||||||
localPartEntry := gtk.NewEntry()
|
localPartEntry := gtk.NewEntry()
|
||||||
localPartEntry.SetPlaceholderText("localpart")
|
localPartEntry.SetPlaceholderText(loadedLocale["BAJlocalpart"])
|
||||||
jid_builder.Append(localPartEntry)
|
jid_builder.Append(localPartEntry)
|
||||||
|
|
||||||
at_sign := gtk.NewLabel("@")
|
at_sign := gtk.NewLabel("@")
|
||||||
@@ -755,7 +843,7 @@ func jidBuilder(en *gtk.Entry) { // This function spawns a window that allows th
|
|||||||
jid_builder.Append(at_sign)
|
jid_builder.Append(at_sign)
|
||||||
|
|
||||||
domainEntry := gtk.NewEntry()
|
domainEntry := gtk.NewEntry()
|
||||||
domainEntry.SetPlaceholderText("domain")
|
domainEntry.SetPlaceholderText(loadedLocale["BAJdomain"])
|
||||||
jid_builder.Append(domainEntry)
|
jid_builder.Append(domainEntry)
|
||||||
|
|
||||||
resource_sign := gtk.NewLabel("/")
|
resource_sign := gtk.NewLabel("/")
|
||||||
@@ -763,7 +851,7 @@ func jidBuilder(en *gtk.Entry) { // This function spawns a window that allows th
|
|||||||
jid_builder.Append(resource_sign)
|
jid_builder.Append(resource_sign)
|
||||||
|
|
||||||
resourceEntry := gtk.NewEntry()
|
resourceEntry := gtk.NewEntry()
|
||||||
resourceEntry.SetPlaceholderText("resource")
|
resourceEntry.SetPlaceholderText(loadedLocale["BAJresource"])
|
||||||
jid_builder.Append(resourceEntry)
|
jid_builder.Append(resourceEntry)
|
||||||
|
|
||||||
box.Append(jid_builder)
|
box.Append(jid_builder)
|
||||||
|
|||||||
@@ -81,12 +81,29 @@ func generateMessageWidget(p stanza.Packet, blocked bool, target string) (gtk.Wi
|
|||||||
return gtk.NewLabel("Unsupported message."), true
|
return gtk.NewLabel("Unsupported message."), true
|
||||||
}
|
}
|
||||||
|
|
||||||
readmarker := Marker{}
|
sid := StanzaID{}
|
||||||
ok = m.Get(&readmarker)
|
m.Get(&sid)
|
||||||
if ok {
|
|
||||||
b := gtk.NewBox(gtk.OrientationHorizontal, 0)
|
ocu := OccupantID{}
|
||||||
b.Append(gtk.NewLabel(fmt.Sprintf("%s%s", JidMustParse(m.From).Resource, loadedLocale["readWidget"])))
|
m.Get(&ocu)
|
||||||
return b, true
|
|
||||||
|
store_label := false
|
||||||
|
var msg *abMessage
|
||||||
|
tab, ok := tabs.Load(target)
|
||||||
|
typed_tab, ok2 := tab.(*chatTab)
|
||||||
|
if ok && ok2 {
|
||||||
|
store_label = true
|
||||||
|
msg, ok = typed_tab.msg_refsID[m.Id]
|
||||||
|
if !ok {
|
||||||
|
msg = new(abMessage)
|
||||||
|
typed_tab.msg_refsID[m.Id] = msg
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.ocu_id = ocu.ID
|
||||||
|
|
||||||
|
if sid.ID != "" {
|
||||||
|
typed_tab.msg_refsOSID[sid.ID] = msg
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
composing := stanza.StateComposing{}
|
composing := stanza.StateComposing{}
|
||||||
@@ -108,19 +125,6 @@ func generateMessageWidget(p stanza.Packet, blocked bool, target string) (gtk.Wi
|
|||||||
return error_box, true
|
return error_box, true
|
||||||
}
|
}
|
||||||
|
|
||||||
store_label := false
|
|
||||||
tab, ok := tabs.Load(target)
|
|
||||||
if ok {
|
|
||||||
store_label = true
|
|
||||||
}
|
|
||||||
typed_tab := tab.(*chatTab)
|
|
||||||
|
|
||||||
sid := StanzaID{}
|
|
||||||
m.Get(&sid)
|
|
||||||
|
|
||||||
ocu := OccupantID{}
|
|
||||||
|
|
||||||
m.Get(&ocu)
|
|
||||||
id := JidMustParse(m.From).Resource
|
id := JidMustParse(m.From).Resource
|
||||||
|
|
||||||
name := id
|
name := id
|
||||||
@@ -179,7 +183,7 @@ func generateMessageWidget(p stanza.Packet, blocked bool, target string) (gtk.Wi
|
|||||||
<reaction>%s</reaction>
|
<reaction>%s</reaction>
|
||||||
</reactions>
|
</reactions>
|
||||||
</message>
|
</message>
|
||||||
`, m.To, jid.MustParse(m.From).Bare().String(), uuid.New().String(), m.Type, sid.ID, v))
|
`, m.To, jid.MustParse(m.From).Bare().String(), uuid.NewString(), m.Type, sid.ID, v))
|
||||||
})
|
})
|
||||||
reactions.Append(like)
|
reactions.Append(like)
|
||||||
}
|
}
|
||||||
@@ -198,7 +202,7 @@ func generateMessageWidget(p stanza.Packet, blocked bool, target string) (gtk.Wi
|
|||||||
<reaction>%s</reaction>
|
<reaction>%s</reaction>
|
||||||
</reactions>
|
</reactions>
|
||||||
</message>
|
</message>
|
||||||
`, m.To, jid.MustParse(m.From).Bare().String(), uuid.New().String(), m.Type, sid.ID, custom.Text()))
|
`, m.To, jid.MustParse(m.From).Bare().String(), uuid.NewString(), m.Type, sid.ID, custom.Text()))
|
||||||
})
|
})
|
||||||
rc_box.Append(custom)
|
rc_box.Append(custom)
|
||||||
rc_box.Append(enter_custom)
|
rc_box.Append(enter_custom)
|
||||||
@@ -234,6 +238,26 @@ func generateMessageWidget(p stanza.Packet, blocked bool, target string) (gtk.Wi
|
|||||||
|
|
||||||
rc_box.Append(vp)
|
rc_box.Append(vp)
|
||||||
|
|
||||||
|
live_edit := gtk.NewButtonWithLabel("Live edit")
|
||||||
|
live_edit.ConnectClicked(func() {
|
||||||
|
en := gtk.NewEntry()
|
||||||
|
en.ConnectChanged(func() {
|
||||||
|
client.SendRaw(fmt.Sprintf("<message id='%s' to='%s' type='%s'><body>%s</body><replace id='%s' xmlns='urn:xmpp:message-correct:0'/></message>",
|
||||||
|
uuid.NewString(),
|
||||||
|
target,
|
||||||
|
m.Type,
|
||||||
|
en.Text(),
|
||||||
|
m.Id,
|
||||||
|
))
|
||||||
|
})
|
||||||
|
|
||||||
|
win := gtk.NewWindow()
|
||||||
|
win.SetChild(en)
|
||||||
|
win.SetVisible(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
rc_box.Append(live_edit)
|
||||||
|
|
||||||
popover.SetChild(rc_box)
|
popover.SetChild(rc_box)
|
||||||
rect := gdk.NewRectangle(x, y, 1, 1)
|
rect := gdk.NewRectangle(x, y, 1, 1)
|
||||||
popover.SetPointingTo(&rect)
|
popover.SetPointingTo(&rect)
|
||||||
@@ -273,18 +297,8 @@ func generateMessageWidget(p stanza.Packet, blocked bool, target string) (gtk.Wi
|
|||||||
|
|
||||||
// TODO: Decouple this from widget generation
|
// TODO: Decouple this from widget generation
|
||||||
if store_label {
|
if store_label {
|
||||||
msg, ok := typed_tab.msg_refsID[m.Id]
|
|
||||||
if !ok {
|
|
||||||
msg = new(abMessage)
|
|
||||||
typed_tab.msg_refsID[m.Id] = msg
|
|
||||||
}
|
|
||||||
|
|
||||||
msg.label_ref = mlabel
|
msg.label_ref = mlabel
|
||||||
msg.ocu_id = ocu.ID
|
msg.ocu_id = ocu.ID
|
||||||
|
|
||||||
if sid.ID != "" {
|
|
||||||
typed_tab.msg_refsOSID[sid.ID] = msg
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
authorBox := gtk.NewBox(gtk.OrientationHorizontal, 10)
|
authorBox := gtk.NewBox(gtk.OrientationHorizontal, 10)
|
||||||
@@ -312,10 +326,11 @@ func generateMessageWidget(p stanza.Packet, blocked bool, target string) (gtk.Wi
|
|||||||
}
|
}
|
||||||
|
|
||||||
im := gtk.NewImage()
|
im := gtk.NewImage()
|
||||||
im.SetFromPaintable(clientAssetsLoad("DefaultAvatar"))
|
// im := gtk.NewImageFromPaintable(clientAssetsLoad("DefaultAvatar"))
|
||||||
|
go createIdenticon(m.From, false, im)
|
||||||
|
// im.SetFromPaintable(clientAssetsLoad("DefaultAvatar"))
|
||||||
im.SetPixelSize(40)
|
im.SetPixelSize(40)
|
||||||
im.AddCSSClass("author_img")
|
im.AddCSSClass("author_img")
|
||||||
// createIdenticon(m.From, false, im)
|
|
||||||
authorBox.Append(im)
|
authorBox.Append(im)
|
||||||
if can_generate {
|
if can_generate {
|
||||||
pres := mmmm.(stanza.Presence)
|
pres := mmmm.(stanza.Presence)
|
||||||
@@ -325,14 +340,38 @@ func generateMessageWidget(p stanza.Packet, blocked bool, target string) (gtk.Wi
|
|||||||
go getAvatar(m.From, vu.Photo, im)
|
go getAvatar(m.From, vu.Photo, im)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
createIdenticon(m.From, false, im)
|
go createIdenticon(m.From, false, im)
|
||||||
}
|
}
|
||||||
} else if m.Type == stanza.MessageTypeChat {
|
} else if m.Type == stanza.MessageTypeChat {
|
||||||
al.SetText(al.Text() + loadedLocale["whispers"])
|
al.SetText(al.Text() + loadedLocale["whispers"])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
color_ind := gtk.NewLabel("")
|
||||||
|
|
||||||
|
ycbcr := xmpp_color.String(id, 255, loadedConfig.CVD)
|
||||||
|
r, g, b, _ := ycbcr.RGBA()
|
||||||
|
hexcolor := fmt.Sprintf("#%02x%02x%02x", r, g, b)
|
||||||
|
|
||||||
|
color_ind.SetMarkup(fmt.Sprintf("<span foreground='%s'>|</span>", hexcolor))
|
||||||
|
|
||||||
|
authorBox.Append(color_ind)
|
||||||
authorBox.Append(al)
|
authorBox.Append(al)
|
||||||
|
|
||||||
|
oob := stanza.OOB{}
|
||||||
|
ok = m.Get(&oob)
|
||||||
|
if ok {
|
||||||
|
// media := newPictureFromWeb(oob.URL)
|
||||||
|
// media.SetCanShrink(false)
|
||||||
|
// mainBox.Append(media)
|
||||||
|
// media.AddCSSClass("chat_image")
|
||||||
|
mbtn := gtk.NewButtonWithLabel("🖼️")
|
||||||
|
// mbtn.SetChild(newImageFromWeb(oob.URL))
|
||||||
|
mbtn.ConnectClicked(func() {
|
||||||
|
gopen.Open(oob.URL)
|
||||||
|
})
|
||||||
|
authorBox.Append(mbtn)
|
||||||
|
}
|
||||||
|
|
||||||
if m.Thread != "" {
|
if m.Thread != "" {
|
||||||
im := gtk.NewImage()
|
im := gtk.NewImage()
|
||||||
createIdenticon(m.Thread, true, im)
|
createIdenticon(m.Thread, true, im)
|
||||||
@@ -346,6 +385,33 @@ func generateMessageWidget(p stanza.Packet, blocked bool, target string) (gtk.Wi
|
|||||||
authorBox.Append(gtk.NewLabel("🎮"))
|
authorBox.Append(gtk.NewLabel("🎮"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stamp_label := gtk.NewLabel("")
|
||||||
|
stamp_label.SetHExpand(true)
|
||||||
|
stamp_label.SetHAlign(gtk.AlignEnd)
|
||||||
|
stamp_label.AddCSSClass("visitor")
|
||||||
|
|
||||||
|
stamp_label.SetText(time.Now().Format("03:04 PM"))
|
||||||
|
stamp_label.SetTooltipText(time.Now().Format("2006-01-02 15:04:05"))
|
||||||
|
|
||||||
|
stamp := new(Delayed_Stamp)
|
||||||
|
ok = m.Get(stamp)
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
stamped_time, err := stamp.GetStamp()
|
||||||
|
if err == nil {
|
||||||
|
stamped_time := *stamped_time
|
||||||
|
loc := time.Now().Location()
|
||||||
|
|
||||||
|
stamped_time = stamped_time.In(loc)
|
||||||
|
str := stamped_time.Format("03:04 PM")
|
||||||
|
str_full := stamped_time.Format("2006-01-02 15:04:05")
|
||||||
|
stamp_label.SetText(str)
|
||||||
|
stamp_label.SetTooltipText(str_full)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
authorBox.Append(stamp_label)
|
||||||
|
|
||||||
body := m.Body
|
body := m.Body
|
||||||
body = XEPToPango(body, false)
|
body = XEPToPango(body, false)
|
||||||
|
|
||||||
@@ -361,40 +427,15 @@ func generateMessageWidget(p stanza.Packet, blocked bool, target string) (gtk.Wi
|
|||||||
|
|
||||||
// TODO: Decouple this from widget generation
|
// TODO: Decouple this from widget generation
|
||||||
if store_label {
|
if store_label {
|
||||||
msg, ok := typed_tab.msg_refsID[m.Id]
|
|
||||||
if !ok {
|
|
||||||
msg = new(abMessage)
|
|
||||||
typed_tab.msg_refsID[m.Id] = msg
|
|
||||||
}
|
|
||||||
|
|
||||||
msg.label_ref = mlabel
|
msg.label_ref = mlabel
|
||||||
msg.ocu_id = ocu.ID
|
msg.read_indicator = gtk.NewBox(gtk.OrientationHorizontal, padding)
|
||||||
|
authorBox.Append(msg.read_indicator)
|
||||||
if sid.ID != "" {
|
|
||||||
typed_tab.msg_refsOSID[sid.ID] = msg
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
contentBox.Append(mlabel)
|
contentBox.Append(mlabel)
|
||||||
|
|
||||||
mainBox.Append(authorBox)
|
mainBox.Append(authorBox)
|
||||||
mainBox.Append(contentBox)
|
mainBox.Append(contentBox)
|
||||||
|
|
||||||
oob := stanza.OOB{}
|
|
||||||
ok = m.Get(&oob)
|
|
||||||
if ok {
|
|
||||||
// media := newPictureFromWeb(oob.URL)
|
|
||||||
// media.SetCanShrink(false)
|
|
||||||
// mainBox.Append(media)
|
|
||||||
// media.AddCSSClass("chat_image")
|
|
||||||
mbtn := gtk.NewButtonWithLabel("🖼️")
|
|
||||||
// mbtn.SetChild(newImageFromWeb(oob.URL))
|
|
||||||
mbtn.ConnectClicked(func() {
|
|
||||||
gopen.Open(oob.URL)
|
|
||||||
})
|
|
||||||
authorBox.Append(mbtn)
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.Subject != "" {
|
if m.Subject != "" {
|
||||||
subjectlabel := gtk.NewLabel(m.Subject)
|
subjectlabel := gtk.NewLabel(m.Subject)
|
||||||
subjectlabel.SetWrap(true)
|
subjectlabel.SetWrap(true)
|
||||||
@@ -460,6 +501,8 @@ func getVAdjustment(scrolledWindow *gtk.ScrolledWindow) *gtk.Adjustment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getAvatar(j, hash string, i *gtk.Image) { // TODO: This function probably shouldn't be here, and should probably be in xmpp-helpers or somewhere similar.
|
func getAvatar(j, hash string, i *gtk.Image) { // TODO: This function probably shouldn't be here, and should probably be in xmpp-helpers or somewhere similar.
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
hash = strings.ReplaceAll(hash, "/", "-")
|
||||||
oghash := hash
|
oghash := hash
|
||||||
p, err := ensureCache()
|
p, err := ensureCache()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -492,7 +535,7 @@ func getAvatar(j, hash string, i *gtk.Image) { // TODO: This function probably s
|
|||||||
Type: "get",
|
Type: "get",
|
||||||
From: clientroot.Session.BindJid,
|
From: clientroot.Session.BindJid,
|
||||||
To: j,
|
To: j,
|
||||||
Id: uuid.New().String(),
|
Id: uuid.NewString(),
|
||||||
Lang: "en",
|
Lang: "en",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/diamondburned/gotk4/pkg/gtk/v4"
|
||||||
"html"
|
"html"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"github.com/diamondburned/gotk4/pkg/gtk/v4"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -65,6 +65,9 @@ func XEPToPango(text string, correction bool) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GenerateRetraction(lab *gtk.Label, retraction Retraction) {
|
func GenerateRetraction(lab *gtk.Label, retraction Retraction) {
|
||||||
|
if lab == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
if retraction.Moderated.OccupantID.ID != "" {
|
if retraction.Moderated.OccupantID.ID != "" {
|
||||||
lab.AddCSSClass("moderated")
|
lab.AddCSSClass("moderated")
|
||||||
lab.SetTooltipMarkup(fmt.Sprintf("<span weight='bold'>This message was <span foreground='magenta'>retracted</span> by a moderator.</span>\nReason: '%s'", html.EscapeString(retraction.Reason)))
|
lab.SetTooltipMarkup(fmt.Sprintf("<span weight='bold'>This message was <span foreground='magenta'>retracted</span> by a moderator.</span>\nReason: '%s'", html.EscapeString(retraction.Reason)))
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"gosrc.io/xmpp/stanza"
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
@@ -23,3 +25,28 @@ func rangeOrdered(m *sync.Map, fn func(k, v any) bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getPresenceFromJIDMuc(jid *stanza.Jid) (*stanza.Presence, error) {
|
||||||
|
mo, ok := mucmembers.Load(jid.Bare())
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("tab is not present in mucmembers")
|
||||||
|
}
|
||||||
|
|
||||||
|
mm, ok := mo.(mucUnit)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("mucUnit is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
mmm := mm.Members
|
||||||
|
mmmm, ok := mmm.Load(jid.Resource)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("user not in room")
|
||||||
|
}
|
||||||
|
|
||||||
|
pres, ok := mmmm.(stanza.Presence)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("presence is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pres, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ var enGB = map[string]string{ // British English
|
|||||||
// gtk-helpers.go
|
// gtk-helpers.go
|
||||||
|
|
||||||
"getPastMessages": "Get past messages...",
|
"getPastMessages": "Get past messages...",
|
||||||
|
"mucLoadError": "There was an error loading the members of this room. Maybe the MUC does not give user presence, it does not exist, you have been banned from it, or you have to fill a CAPTCHA to access it. Try joining the room again or check if the room exists.",
|
||||||
"clickForMoreInfo": "Click for more information",
|
"clickForMoreInfo": "Click for more information",
|
||||||
"ban": "Ban",
|
"ban": "Ban",
|
||||||
"kick": "Kick",
|
"kick": "Kick",
|
||||||
@@ -91,9 +92,16 @@ var enGB = map[string]string{ // British English
|
|||||||
"connectedWithRole": "Connected with role ",
|
"connectedWithRole": "Connected with role ",
|
||||||
"affiliatedAs": "Affiliated as ",
|
"affiliatedAs": "Affiliated as ",
|
||||||
"unaffiliated": "Unaffiliated",
|
"unaffiliated": "Unaffiliated",
|
||||||
"participants": "participant(s)",
|
"participants": "participants",
|
||||||
|
"participant": "participant",
|
||||||
"versionQueryEmpty": "You do not have permission to view this client's version",
|
"versionQueryEmpty": "You do not have permission to view this client's version",
|
||||||
"versionQueryError": "Got error trying to get version",
|
"versionQueryError": "Got error trying to get version",
|
||||||
|
"blockedUserNotice": "You have blocked this user!",
|
||||||
|
"buildAJID": "Build-A-JID",
|
||||||
|
"buildAJIDOptionalFieldsNote": "All fields except for domain are optional",
|
||||||
|
"BAJlocalpart": "localpart",
|
||||||
|
"BAJdomain": "domain",
|
||||||
|
"BAJresource": "resource",
|
||||||
|
|
||||||
"away": "Away",
|
"away": "Away",
|
||||||
"chat": "Free for chat",
|
"chat": "Free for chat",
|
||||||
@@ -49,11 +49,6 @@ var mIcon *gtk.Image
|
|||||||
var gStatus *gtk.Label
|
var gStatus *gtk.Label
|
||||||
var gIcon *gtk.Image
|
var gIcon *gtk.Image
|
||||||
|
|
||||||
/*
|
|
||||||
var sStatus *gtk.Label
|
|
||||||
var sIcon *gtk.Image
|
|
||||||
*/
|
|
||||||
|
|
||||||
var typingStatus *gtk.Label
|
var typingStatus *gtk.Label
|
||||||
|
|
||||||
var pingStatus *gtk.Label
|
var pingStatus *gtk.Label
|
||||||
@@ -93,9 +88,6 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
go func() {
|
|
||||||
log.Println(http.ListenAndServe("localhost:6060", nil))
|
|
||||||
}()
|
|
||||||
|
|
||||||
pingTimes = append(pingTimes, []float64{})
|
pingTimes = append(pingTimes, []float64{})
|
||||||
p, err := ensureConfig()
|
p, err := ensureConfig()
|
||||||
@@ -113,6 +105,12 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
if loadedConfig.Debug {
|
||||||
|
go func() {
|
||||||
|
log.Println(http.ListenAndServe("localhost:6060", nil))
|
||||||
|
}()
|
||||||
|
log.Println("pprof debug server running at port 6060")
|
||||||
|
}
|
||||||
|
|
||||||
if loadedConfig.Resource == "" {
|
if loadedConfig.Resource == "" {
|
||||||
fmt.Println(loadedLocale["configResourceEmptyWarning"])
|
fmt.Println(loadedLocale["configResourceEmptyWarning"])
|
||||||
@@ -257,6 +255,31 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
originator := JidMustParse(m.From).Bare()
|
||||||
|
var typed_tab *chatTab
|
||||||
|
tab, ok := tabs.Load(originator)
|
||||||
|
if ok {
|
||||||
|
typed_tab = tab.(*chatTab)
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if m.Type != stanza.MessageTypeGroupchat {
|
||||||
|
createTab(originator, false, originator)
|
||||||
|
tab, ok = tabs.Load(originator)
|
||||||
|
if ok {
|
||||||
|
typed_tab = tab.(*chatTab)
|
||||||
|
glib.IdleAdd(func() {
|
||||||
|
box := createTabBox(originator, originator, originator, true)
|
||||||
|
menu.Append(box)
|
||||||
|
menu.Append(gtk.NewSeparator(gtk.OrientationVertical))
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
oi := new(OccupantID)
|
oi := new(OccupantID)
|
||||||
ok = m.Get(oi)
|
ok = m.Get(oi)
|
||||||
if ok {
|
if ok {
|
||||||
@@ -276,12 +299,6 @@ func main() {
|
|||||||
sid := new(StanzaID)
|
sid := new(StanzaID)
|
||||||
m.Get(sid)
|
m.Get(sid)
|
||||||
|
|
||||||
originator := JidMustParse(m.From).Bare()
|
|
||||||
|
|
||||||
glib.IdleAdd(func() {
|
|
||||||
mStatus.SetText(originator)
|
|
||||||
})
|
|
||||||
|
|
||||||
at := new(Attention)
|
at := new(Attention)
|
||||||
ok = m.Get(at)
|
ok = m.Get(at)
|
||||||
if ok {
|
if ok {
|
||||||
@@ -340,6 +357,41 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle read indicators
|
||||||
|
marker := new(Marker)
|
||||||
|
ok = m.Get(marker)
|
||||||
|
if ok {
|
||||||
|
_, ok := typed_tab.msg_refsOSID[marker.ID]
|
||||||
|
if ok {
|
||||||
|
im := gtk.NewImage()
|
||||||
|
im.SetPixelSize(20)
|
||||||
|
im.SetTooltipText(JidMustParse(m.From).Resource)
|
||||||
|
glib.IdleAdd(func() {
|
||||||
|
createIdenticon(m.From, false, im)
|
||||||
|
})
|
||||||
|
|
||||||
|
pres, err := getPresenceFromJIDMuc(JidMustParse(m.From))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var vu VCardUpdate
|
||||||
|
ok = pres.Get(&vu)
|
||||||
|
if ok {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Handle corrections BEFORE attempting to generate a message
|
// Handle corrections BEFORE attempting to generate a message
|
||||||
correction := new(Correction)
|
correction := new(Correction)
|
||||||
ok = m.Get(correction)
|
ok = m.Get(correction)
|
||||||
@@ -352,10 +404,12 @@ func main() {
|
|||||||
if ok {
|
if ok {
|
||||||
if msg.ocu_id == oi.ID && msg.ocu_id != "" {
|
if msg.ocu_id == oi.ID && msg.ocu_id != "" {
|
||||||
lab := msg.label_ref
|
lab := msg.label_ref
|
||||||
|
if lab != nil {
|
||||||
glib.IdleAdd(func() {
|
glib.IdleAdd(func() {
|
||||||
lab.SetMarkup(XEPToPango(m.Body, true))
|
lab.SetMarkup(XEPToPango(m.Body, true))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -387,6 +441,7 @@ func main() {
|
|||||||
glib.IdleAdd(func() {
|
glib.IdleAdd(func() {
|
||||||
GenerateRetraction(lab, *retraction)
|
GenerateRetraction(lab, *retraction)
|
||||||
})
|
})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -399,7 +454,10 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
typed_tab := tab.(*chatTab)
|
typed_tab, ok := tab.(*chatTab)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if ok {
|
if ok {
|
||||||
if current == JidMustParse(m.From).Bare() {
|
if current == JidMustParse(m.From).Bare() {
|
||||||
@@ -413,6 +471,7 @@ func main() {
|
|||||||
ba, converted_successfully := untyped_box.(*gtk.Box)
|
ba, converted_successfully := untyped_box.(*gtk.Box)
|
||||||
|
|
||||||
if valid && converted_successfully {
|
if valid && converted_successfully {
|
||||||
|
mStatus.SetText(originator)
|
||||||
typed_tab.msgs.Append(b)
|
typed_tab.msgs.Append(b)
|
||||||
b.Append(ba)
|
b.Append(ba)
|
||||||
b.Append(gtk.NewSeparator(gtk.OrientationVertical))
|
b.Append(gtk.NewSeparator(gtk.OrientationVertical))
|
||||||
@@ -439,10 +498,7 @@ func main() {
|
|||||||
if ok { // This is a presence stanza from a user in a MUC
|
if ok { // This is a presence stanza from a user in a MUC
|
||||||
|
|
||||||
presence.Get(&ocu)
|
presence.Get(&ocu)
|
||||||
// id := ocu.ID
|
|
||||||
// if id == "" {
|
|
||||||
id := JidMustParse(presence.From).Resource
|
id := JidMustParse(presence.From).Resource
|
||||||
// }
|
|
||||||
from, _ := stanza.NewJid(presence.From)
|
from, _ := stanza.NewJid(presence.From)
|
||||||
muc := from.Bare()
|
muc := from.Bare()
|
||||||
_, ok = mucmembers.Load(muc)
|
_, ok = mucmembers.Load(muc)
|
||||||
@@ -567,7 +623,7 @@ func main() {
|
|||||||
pingStatus.AddCSSClass("pending")
|
pingStatus.AddCSSClass("pending")
|
||||||
before := time.Now()
|
before := time.Now()
|
||||||
iq := new(stanza.IQ)
|
iq := new(stanza.IQ)
|
||||||
iq.Id = uuid.New().String()
|
iq.Id = uuid.NewString()
|
||||||
iq.From = clientroot.Session.BindJid
|
iq.From = clientroot.Session.BindJid
|
||||||
iq.Type = "get"
|
iq.Type = "get"
|
||||||
iq.Payload = &Ping{}
|
iq.Payload = &Ping{}
|
||||||
@@ -621,22 +677,8 @@ func main() {
|
|||||||
name := "Note To Self"
|
name := "Note To Self"
|
||||||
createTab(jid, false, name)
|
createTab(jid, false, name)
|
||||||
glib.IdleAdd(func() {
|
glib.IdleAdd(func() {
|
||||||
box := gtk.NewBox(gtk.OrientationHorizontal, 10)
|
b := createTabBox(name, jid, jid, false)
|
||||||
b := gtk.NewLabel(name)
|
menu.Append(b)
|
||||||
gesture1 := gtk.NewGestureClick()
|
|
||||||
gesture1.SetButton(1)
|
|
||||||
gesture1.Connect("pressed", func() {
|
|
||||||
switchToTab(jid, &window.Window)
|
|
||||||
})
|
|
||||||
|
|
||||||
box.Append(b)
|
|
||||||
im := gtk.NewImage()
|
|
||||||
im.SetPixelSize(40)
|
|
||||||
go getAvatar(jid, jid, im)
|
|
||||||
box.Prepend(im)
|
|
||||||
|
|
||||||
box.AddController(gesture1)
|
|
||||||
menu.Append(box)
|
|
||||||
menu.Append(gtk.NewSeparator(gtk.OrientationHorizontal))
|
menu.Append(gtk.NewSeparator(gtk.OrientationHorizontal))
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -666,30 +708,7 @@ func main() {
|
|||||||
|
|
||||||
createTab(jid, false, name)
|
createTab(jid, false, name)
|
||||||
glib.IdleAdd(func() {
|
glib.IdleAdd(func() {
|
||||||
box := gtk.NewBox(gtk.OrientationHorizontal, 10)
|
box := createTabBox(name, jid, jid, false)
|
||||||
b := gtk.NewLabel(name)
|
|
||||||
gesture1 := gtk.NewGestureClick()
|
|
||||||
gesture1.SetButton(1)
|
|
||||||
gesture1.Connect("pressed", func() {
|
|
||||||
switchToTab(jid, &window.Window)
|
|
||||||
})
|
|
||||||
gesture2 := gtk.NewGestureClick()
|
|
||||||
gesture2.SetButton(3)
|
|
||||||
gesture2.Connect("pressed", func() {
|
|
||||||
removeTab(jid, &window.Window)
|
|
||||||
box.SetVisible(false)
|
|
||||||
})
|
|
||||||
|
|
||||||
im := gtk.NewImage()
|
|
||||||
im.SetPixelSize(40)
|
|
||||||
|
|
||||||
box.Append(im)
|
|
||||||
box.Append(b)
|
|
||||||
|
|
||||||
// TODO: Use PEP avatar and do not use JID as hash
|
|
||||||
go getAvatar(jid, jid, im)
|
|
||||||
|
|
||||||
box.AddController(gesture1)
|
|
||||||
menu.Append(box)
|
menu.Append(box)
|
||||||
menu.Append(gtk.NewSeparator(gtk.OrientationHorizontal))
|
menu.Append(gtk.NewSeparator(gtk.OrientationHorizontal))
|
||||||
})
|
})
|
||||||
@@ -754,31 +773,7 @@ func main() {
|
|||||||
joinMuc(client, clientroot.Session.BindJid, jid, nick, password)
|
joinMuc(client, clientroot.Session.BindJid, jid, nick, password)
|
||||||
createTab(jid, true, name)
|
createTab(jid, true, name)
|
||||||
glib.IdleAdd(func() {
|
glib.IdleAdd(func() {
|
||||||
box := gtk.NewBox(gtk.OrientationHorizontal, 10)
|
box := createTabBox(name, jid, jid, true)
|
||||||
b := gtk.NewLabel(name)
|
|
||||||
gesture1 := gtk.NewGestureClick()
|
|
||||||
gesture1.SetButton(1)
|
|
||||||
gesture1.Connect("pressed", func() {
|
|
||||||
switchToTab(jid, &window.Window)
|
|
||||||
})
|
|
||||||
|
|
||||||
gesture2 := gtk.NewGestureClick()
|
|
||||||
gesture2.SetButton(3)
|
|
||||||
gesture2.Connect("pressed", func() {
|
|
||||||
removeTab(jid, &window.Window)
|
|
||||||
box.SetVisible(false)
|
|
||||||
})
|
|
||||||
|
|
||||||
im := gtk.NewImage()
|
|
||||||
im.SetPixelSize(40)
|
|
||||||
|
|
||||||
box.Append(b)
|
|
||||||
box.Prepend(im)
|
|
||||||
|
|
||||||
go getAvatar(jid, jid, im)
|
|
||||||
|
|
||||||
box.AddController(gesture1)
|
|
||||||
box.AddController(gesture2)
|
|
||||||
menu.Append(box)
|
menu.Append(box)
|
||||||
menu.Append(gtk.NewSeparator(gtk.OrientationHorizontal))
|
menu.Append(gtk.NewSeparator(gtk.OrientationHorizontal))
|
||||||
})
|
})
|
||||||
@@ -795,31 +790,7 @@ func main() {
|
|||||||
joinMuc(client, clientroot.Session.BindJid, jid, nick, "")
|
joinMuc(client, clientroot.Session.BindJid, jid, nick, "")
|
||||||
createTab(jid, true, jid)
|
createTab(jid, true, jid)
|
||||||
glib.IdleAdd(func() {
|
glib.IdleAdd(func() {
|
||||||
box := gtk.NewBox(gtk.OrientationHorizontal, 10)
|
box := createTabBox(jid, jid, jid, true)
|
||||||
b := gtk.NewLabel(jid)
|
|
||||||
gesture1 := gtk.NewGestureClick()
|
|
||||||
gesture1.SetButton(1)
|
|
||||||
gesture1.Connect("pressed", func() {
|
|
||||||
switchToTab(jid, &window.Window)
|
|
||||||
})
|
|
||||||
|
|
||||||
gesture2 := gtk.NewGestureClick()
|
|
||||||
gesture2.SetButton(3)
|
|
||||||
gesture2.Connect("pressed", func() {
|
|
||||||
removeTab(jid, &window.Window)
|
|
||||||
box.SetVisible(false)
|
|
||||||
})
|
|
||||||
|
|
||||||
im := gtk.NewImage()
|
|
||||||
im.SetPixelSize(40)
|
|
||||||
|
|
||||||
box.Append(b)
|
|
||||||
box.Prepend(im)
|
|
||||||
|
|
||||||
go getAvatar(jid, jid, im)
|
|
||||||
|
|
||||||
box.AddController(gesture1)
|
|
||||||
box.AddController(gesture2)
|
|
||||||
menu.Append(box)
|
menu.Append(box)
|
||||||
menu.Append(gtk.NewSeparator(gtk.OrientationHorizontal))
|
menu.Append(gtk.NewSeparator(gtk.OrientationHorizontal))
|
||||||
})
|
})
|
||||||
@@ -891,26 +862,8 @@ func activate(app *gtk.Application) {
|
|||||||
submit := gtk.NewButtonWithLabel(loadedLocale["submit"])
|
submit := gtk.NewButtonWithLabel(loadedLocale["submit"])
|
||||||
submit.ConnectClicked(func() {
|
submit.ConnectClicked(func() {
|
||||||
t := jid_entry.Text()
|
t := jid_entry.Text()
|
||||||
n := t
|
createTab(t, false, t)
|
||||||
createTab(t, false, n)
|
box := createTabBox(t, t, t, true)
|
||||||
box := gtk.NewBox(gtk.OrientationHorizontal, 0)
|
|
||||||
b := gtk.NewLabel(n)
|
|
||||||
box.Append(b)
|
|
||||||
gesture1 := gtk.NewGestureClick()
|
|
||||||
gesture1.SetButton(1)
|
|
||||||
gesture1.Connect("pressed", func() {
|
|
||||||
switchToTab(t, &window.Window)
|
|
||||||
})
|
|
||||||
|
|
||||||
gesture2 := gtk.NewGestureClick()
|
|
||||||
gesture2.SetButton(3)
|
|
||||||
gesture2.Connect("pressed", func() {
|
|
||||||
removeTab(t, &window.Window)
|
|
||||||
box.SetVisible(false)
|
|
||||||
})
|
|
||||||
|
|
||||||
box.AddController(gesture1)
|
|
||||||
box.AddController(gesture2)
|
|
||||||
menu.Append(box)
|
menu.Append(box)
|
||||||
menu.Append(gtk.NewSeparator(gtk.OrientationHorizontal))
|
menu.Append(gtk.NewSeparator(gtk.OrientationHorizontal))
|
||||||
dm_window.SetVisible(false)
|
dm_window.SetVisible(false)
|
||||||
@@ -1007,19 +960,14 @@ func activate(app *gtk.Application) {
|
|||||||
if JidMustParse(mu.MucUserItem.JID).Bare() == JidMustParse(clientroot.Session.BindJid).Bare() {
|
if JidMustParse(mu.MucUserItem.JID).Bare() == JidMustParse(clientroot.Session.BindJid).Bare() {
|
||||||
if mu.MucUserItem.Affiliation != "owner" {
|
if mu.MucUserItem.Affiliation != "owner" {
|
||||||
box.Append(gtk.NewLabel(loadedLocale["destroyMUCNotOwnerWarning"]))
|
box.Append(gtk.NewLabel(loadedLocale["destroyMUCNotOwnerWarning"]))
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
panic("not ok")
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
panic("not ok")
|
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
panic("not ok")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
win.SetChild(box)
|
win.SetChild(box)
|
||||||
@@ -1101,31 +1049,7 @@ func activate(app *gtk.Application) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
createTab(t, true, n)
|
createTab(t, true, n)
|
||||||
box := gtk.NewBox(gtk.OrientationHorizontal, 10)
|
box := createTabBox(n, t, t, true)
|
||||||
im := gtk.NewImage()
|
|
||||||
im.SetPixelSize(40)
|
|
||||||
|
|
||||||
go getAvatar(t, t, im)
|
|
||||||
|
|
||||||
b := gtk.NewLabel(n)
|
|
||||||
|
|
||||||
box.Append(im)
|
|
||||||
box.Append(b)
|
|
||||||
gesture1 := gtk.NewGestureClick()
|
|
||||||
gesture1.SetButton(1)
|
|
||||||
gesture1.Connect("pressed", func() {
|
|
||||||
switchToTab(t, &window.Window)
|
|
||||||
})
|
|
||||||
|
|
||||||
gesture2 := gtk.NewGestureClick()
|
|
||||||
gesture2.SetButton(3)
|
|
||||||
gesture2.Connect("pressed", func() {
|
|
||||||
removeTab(t, &window.Window)
|
|
||||||
box.SetVisible(false)
|
|
||||||
})
|
|
||||||
|
|
||||||
box.AddController(gesture1)
|
|
||||||
box.AddController(gesture2)
|
|
||||||
menu.Append(box)
|
menu.Append(box)
|
||||||
menu.Append(gtk.NewSeparator(gtk.OrientationHorizontal))
|
menu.Append(gtk.NewSeparator(gtk.OrientationHorizontal))
|
||||||
|
|
||||||
@@ -1137,7 +1061,6 @@ func activate(app *gtk.Application) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
||||||
if !disco_check.Active() {
|
if !disco_check.Active() {
|
||||||
jm(t, "")
|
jm(t, "")
|
||||||
win.SetVisible(false)
|
win.SetVisible(false)
|
||||||
@@ -1152,7 +1075,7 @@ func activate(app *gtk.Application) {
|
|||||||
Type: "get",
|
Type: "get",
|
||||||
From: clientroot.Session.BindJid,
|
From: clientroot.Session.BindJid,
|
||||||
To: t,
|
To: t,
|
||||||
Id: "dicks",
|
Id: uuid.NewString(),
|
||||||
Lang: "en",
|
Lang: "en",
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1163,8 +1086,15 @@ func activate(app *gtk.Application) {
|
|||||||
myIQ.Payload = &stanza.DiscoInfo{}
|
myIQ.Payload = &stanza.DiscoInfo{}
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
mychan, err := client.SendIQ(ctx, myIQ)
|
|
||||||
if err == nil {
|
var mychan chan stanza.IQ
|
||||||
|
//go func() {
|
||||||
|
mychan, err = client.SendIQ(ctx, myIQ)
|
||||||
|
if err != nil {
|
||||||
|
showErrorDialog(err, &window.Window)
|
||||||
|
}
|
||||||
|
//}()
|
||||||
|
|
||||||
result := <-mychan
|
result := <-mychan
|
||||||
res, ok = result.Payload.(*stanza.DiscoInfo)
|
res, ok = result.Payload.(*stanza.DiscoInfo)
|
||||||
if ok {
|
if ok {
|
||||||
@@ -1280,7 +1210,6 @@ func activate(app *gtk.Application) {
|
|||||||
showErrorDialog(fmt.Errorf(loadedLocale["discoFail"]), win)
|
showErrorDialog(fmt.Errorf(loadedLocale["discoFail"]), win)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if allowed {
|
if allowed {
|
||||||
jm(res.Identity[0].Name, "")
|
jm(res.Identity[0].Name, "")
|
||||||
|
|||||||
@@ -72,6 +72,9 @@
|
|||||||
background-color: red;
|
background-color: red;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// a11y
|
||||||
|
|
||||||
.RedGreen_CVD {
|
.RedGreen_CVD {
|
||||||
filter: hue-rotate(30deg) saturate(120%) contrast(110%);
|
filter: hue-rotate(30deg) saturate(120%) contrast(110%);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
type abMessage struct {
|
type abMessage struct {
|
||||||
label_ref *gtk.Label
|
label_ref *gtk.Label
|
||||||
ocu_id string
|
ocu_id string
|
||||||
|
read_indicator *gtk.Box
|
||||||
}
|
}
|
||||||
|
|
||||||
type chatTab struct {
|
type chatTab struct {
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"gosrc.io/xmpp/stanza"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Implementation of XEP-0203: Delayed Delivery
|
||||||
|
// https://xmpp.org/extensions/xep-0203.html
|
||||||
|
|
||||||
|
type Delayed_Stamp struct {
|
||||||
|
XMLName xml.Name `xml:"urn:xmpp:delay delay"`
|
||||||
|
From string `xml:"from,attr"`
|
||||||
|
Stamp string `xml:"stamp,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Delayed_Stamp) GetStamp() (*time.Time, error) {
|
||||||
|
stamp := d.Stamp
|
||||||
|
time, err := time.Parse(time.RFC3339, stamp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &time, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{Space: "urn:xmpp:delay", Local: "delay"}, Delayed_Stamp{})
|
||||||
|
}
|
||||||
@@ -19,7 +19,6 @@ type Moderated struct {
|
|||||||
XMLName xml.Name `xml:"urn:xmpp:message-moderate:1 moderated"`
|
XMLName xml.Name `xml:"urn:xmpp:message-moderate:1 moderated"`
|
||||||
By string `xml:"by,attr"`
|
By string `xml:"by,attr"`
|
||||||
OccupantID OccupantID
|
OccupantID OccupantID
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// To whoever thought putting the same element twice in the same message, but in different parents, and not specifying this behaviour anywhere in the XEP was a good idea, I want to kill you. Seriously, I do. If I see you walking home at night I will kill you. Brutally. Slowly. I will relish in the act. I will do it.
|
// To whoever thought putting the same element twice in the same message, but in different parents, and not specifying this behaviour anywhere in the XEP was a good idea, I want to kill you. Seriously, I do. If I see you walking home at night I will kill you. Brutally. Slowly. I will relish in the act. I will do it.
|
||||||
|
|||||||