diff --git a/assets.go b/assets.go index a737c0f..16cbfa9 100644 --- a/assets.go +++ b/assets.go @@ -39,6 +39,27 @@ var outcastMedalB64 string = base64.StdEncoding.EncodeToString(outcastMedalBytes var cancelBytes []byte var cancelB64 string = base64.StdEncoding.EncodeToString(cancelBytes) + +//go:embed assets/status_away.png +var sABytes []byte +var sAB64 string = base64.StdEncoding.EncodeToString(sABytes) + +//go:embed assets/status_busy.png +var sBBytes []byte +var sBB64 string = base64.StdEncoding.EncodeToString(sBBytes) + +//go:embed assets/status_chatty.png +var sCBytes []byte +var sCB64 string = base64.StdEncoding.EncodeToString(sCBytes) + +//go:embed assets/status_online.png +var sOBytes []byte +var sOB64 string = base64.StdEncoding.EncodeToString(sOBytes) + +//go:embed assets/status_xa.png +var xaBytes []byte +var xaB64 string = base64.StdEncoding.EncodeToString(xaBytes) + //go:embed assets/tag.png var tagBytes []byte var tagB64 string = base64.StdEncoding.EncodeToString(tagBytes) @@ -104,7 +125,6 @@ func init() { loader.Close() clientAssets["DefaultAvatar"] = gdk.NewTextureForPixbuf(loader.Pixbuf()) - loader = gdkpixbuf.NewPixbufLoader() failedData, _ := base64.StdEncoding.DecodeString(failedB64) @@ -272,4 +292,46 @@ func init() { loader.Close() clientAssets["information"] = gdk.NewTextureForPixbuf(loader.Pixbuf()) + + loader = gdkpixbuf.NewPixbufLoader() + + sAData, _ := base64.StdEncoding.DecodeString(sAB64) + loader.Write(sAData) + loader.Close() + + clientAssets["status_away"] = gdk.NewTextureForPixbuf(loader.Pixbuf()) + + + loader = gdkpixbuf.NewPixbufLoader() + + sBData, _ := base64.StdEncoding.DecodeString(sBB64) + loader.Write(sBData) + loader.Close() + + clientAssets["status_dnd"] = gdk.NewTextureForPixbuf(loader.Pixbuf()) + + loader = gdkpixbuf.NewPixbufLoader() + + sCData, _ := base64.StdEncoding.DecodeString(sCB64) + loader.Write(sCData) + loader.Close() + + clientAssets["status_chat"] = gdk.NewTextureForPixbuf(loader.Pixbuf()) + + loader = gdkpixbuf.NewPixbufLoader() + + xaData, _ := base64.StdEncoding.DecodeString(xaB64) + loader.Write(xaData) + loader.Close() + + clientAssets["status_xa"] = gdk.NewTextureForPixbuf(loader.Pixbuf()) + + + loader = gdkpixbuf.NewPixbufLoader() + + sOData, _ := base64.StdEncoding.DecodeString(sOB64) + loader.Write(sOData) + loader.Close() + + clientAssets["status_"] = gdk.NewTextureForPixbuf(loader.Pixbuf()) } diff --git a/assets/status_away.png b/assets/status_away.png new file mode 100644 index 0000000..70bcbcc Binary files /dev/null and b/assets/status_away.png differ diff --git a/assets/status_busy.png b/assets/status_busy.png new file mode 100644 index 0000000..987c806 Binary files /dev/null and b/assets/status_busy.png differ diff --git a/assets/status_chatty.png b/assets/status_chatty.png new file mode 100644 index 0000000..ed631fb Binary files /dev/null and b/assets/status_chatty.png differ diff --git a/assets/status_online.png b/assets/status_online.png new file mode 100644 index 0000000..947bd4b Binary files /dev/null and b/assets/status_online.png differ diff --git a/assets/status_xa.png b/assets/status_xa.png new file mode 100644 index 0000000..0e3768f Binary files /dev/null and b/assets/status_xa.png differ diff --git a/cache.go b/cache.go index 267ca68..73ff099 100644 --- a/cache.go +++ b/cache.go @@ -19,7 +19,7 @@ import ( var textureCache = make(map[string]gdk.Paintabler) // Invalid images, if an image/avatar cannot be loaded on the system (e.g: incompatible format) it's put here -var invalidImages = make(map[string]bool) +var invalidImages = make(map[string]bool) func ensureCache() (string, error) { cachePath := configdir.LocalCache("lambda-im") diff --git a/failed_load.png b/failed_load.png index 291626c..24a42d3 100644 Binary files a/failed_load.png and b/failed_load.png differ diff --git a/gtk-helpers.go b/gtk-helpers.go index f6b5e11..033ebbb 100644 --- a/gtk-helpers.go +++ b/gtk-helpers.go @@ -3,6 +3,7 @@ package main import ( "context" "fmt" + "github.com/diamondburned/gotk4/pkg/gdk/v4" "github.com/diamondburned/gotk4/pkg/glib/v2" "github.com/diamondburned/gotk4/pkg/gtk/v4" "github.com/diamondburned/gotk4/pkg/pango" @@ -82,7 +83,7 @@ func switchToTab(jid string, w *gtk.Window) { nick_label.SetOpacity(0.5) } - userbox.SetTooltipText(fmt.Sprintf("%s\n%s\n%s\nRight-click for more information", u.From, mu.MucUserItem.Role, mu.MucUserItem.Affiliation)) + userbox.SetTooltipText(fmt.Sprintf("%s\n%s\n%s\nClick for more information", u.From, mu.MucUserItem.Role, mu.MucUserItem.Affiliation)) userbox.Append(nick_label) var hats Hats @@ -95,6 +96,13 @@ func switchToTab(jid string, w *gtk.Window) { } } + status := gtk.NewImageFromPaintable(clientAssets["status_"+string(u.Show)]) + status.SetTooltipText(string(u.Show)) + + status.SetHAlign(gtk.AlignEnd) + // medal.SetHExpand(true) + userbox.Prepend(status) + medal := gtk.NewImageFromPaintable(clientAssets[mu.MucUserItem.Affiliation]) medal.SetTooltipText(mu.MucUserItem.Affiliation) @@ -103,7 +111,145 @@ func switchToTab(jid string, w *gtk.Window) { userbox.Append(medal) gesture := gtk.NewGestureClick() - gesture.SetButton(3) // Right click + gesture.SetButton(1) + + mod_gesture := gtk.NewGestureClick() + mod_gesture.SetButton(3) + + popover := gtk.NewPopover() + popover.SetHasArrow(false) + popover.SetParent(userbox) + + rc_box := gtk.NewBox(gtk.OrientationVertical, 0) + bb := gtk.NewButtonWithLabel("Ban") + kb := gtk.NewButtonWithLabel("Kick") + ab := gtk.NewButtonWithLabel("Set affil") + rb := gtk.NewButtonWithLabel("Set role") + + kb.ConnectClicked(func() { + client.SendRaw(fmt.Sprintf(` + + + + + + + `, clientroot.Session.BindJid, jid, JidMustParse(u.From).Resource)) + }) + + bb.ConnectClicked(func() { + var mu MucUser + ok = u.Get(&mu) + if ok { + if mu.MucUserItem.JID != "" { + client.SendRaw(fmt.Sprintf(` + + + + + + `, clientroot.Session.BindJid, jid, JidMustParse(mu.MucUserItem.JID).Bare())) + } + } + }) + + ab.ConnectClicked(func() { + var mu MucUser + ok = u.Get(&mu) + if ok { + if mu.MucUserItem.JID != "" { + win := gtk.NewWindow() + win.SetDefaultSize(400, 1) + win.SetResizable(false) + + box := gtk.NewBox(gtk.OrientationVertical, 0) + box.Append(gtk.NewLabel("Set "+JidMustParse(u.From).Resource+"'s affiliation")) + + the_entry := gtk.NewEntry() + the_entry.SetText(mu.MucUserItem.Affiliation) + + submit := gtk.NewButtonWithLabel("Submit") + submit.ConnectClicked(func() { + client.SendRaw(fmt.Sprintf(` + + + + + + `, clientroot.Session.BindJid, jid, the_entry.Text(), JidMustParse(mu.MucUserItem.JID).Bare())) + win.SetVisible(false) + }) + + box.Append(the_entry) + box.Append(submit) + + win.SetChild(box) + win.SetVisible(true) + }} + }) + + + rb.ConnectClicked(func() { + var mu MucUser + ok = u.Get(&mu) + if ok { + if mu.MucUserItem.JID != "" { + win := gtk.NewWindow() + win.SetDefaultSize(400, 1) + win.SetResizable(false) + + box := gtk.NewBox(gtk.OrientationVertical, 0) + box.Append(gtk.NewLabel("Set "+JidMustParse(u.From).Resource+"'s role")) + box.Append(gtk.NewLabel("Important: if you want this to be permanent, set their affiliation instead")) + + the_entry := gtk.NewEntry() + the_entry.SetText(mu.MucUserItem.Role) + + submit := gtk.NewButtonWithLabel("Submit") + submit.ConnectClicked(func() { + + client.SendRaw(fmt.Sprintf(` + + + + + + + `, clientroot.Session.BindJid, jid, JidMustParse(u.From).Resource, the_entry.Text())) + }) + + box.Append(the_entry) + box.Append(submit) + + win.SetChild(box) + win.SetVisible(true) + }} + }) + + rc_box.Append(bb) + rc_box.Append(kb) + rc_box.Append(ab) + rc_box.Append(rb) + + popover.SetChild(rc_box) + + mod_gesture.Connect("pressed", func(n_press, x, y int) { + rect := gdk.NewRectangle(x, y, 1, 1) + popover.SetPointingTo(&rect) + popover.Popup() + }) gesture.Connect("pressed", func(n_press, x, y int) { win := gtk.NewWindow() @@ -218,8 +364,13 @@ func switchToTab(jid string, w *gtk.Window) { version := ver.Version os := ver.OS - ver_text.SetText(fmt.Sprintf("%s %s %s", name, version, os)) - ver_text.RemoveCSSClass("visitor") + vr := fmt.Sprintf("%s %s %s", name, version, os) + if name == "" && version == "" && os == "" { + ver_text.SetText("Client responded with empty version") + } else { + ver_text.SetText(vr) + ver_text.RemoveCSSClass("visitor") + } } else if result.Error != nil && result.Error.Type != "" { ver_text.SetText("Got error trying to get version") ver_text.SetTooltipText(result.Error.Reason + ": " + result.Error.Text) @@ -262,7 +413,10 @@ func switchToTab(jid string, w *gtk.Window) { win.SetTransientFor(win) win.Present() }) + userbox.AddController(gesture) + userbox.AddController(mod_gesture) + if mu.MucUserItem.Role == "moderator" { gen.Prepend(userbox) } else { diff --git a/main.go b/main.go index 7c5401c..b285cd5 100644 --- a/main.go +++ b/main.go @@ -1,8 +1,8 @@ package main import ( - "strings" "os" + "strings" "sync" "context" @@ -188,8 +188,6 @@ func main() { return } - pretty.Println(m) - e := stanza.PubSubEvent{} ok = m.Get(&e) if ok { @@ -211,6 +209,15 @@ func main() { beeep.Notify("Attention", fmt.Sprintf("%s: %s", JidMustParse(m.From).Resource, m.Body), commentBytes) // TODO: Use localpart if DM } + // Handle mentions + for _, ext := range m.Extensions { + mention, ok := ext.(*Mention) + if ok { + pretty.Println(mention) + } + + } + sc := new(SentCarbon) ok = m.Get(sc) if ok { @@ -492,7 +499,7 @@ func main() { } }) - go func() { + conc := func() { time.Sleep(3 * time.Second) connectionStatus.SetText("Connecting...") connectionIcon.SetFromPaintable(clientAssets["hourglass"]) @@ -503,10 +510,13 @@ func main() { connectionStatus.SetText(fmt.Sprintf("Disconnected: %s", err.Error())) connectionIcon.SetFromPaintable(clientAssets["disconnect"]) } - }() + } app := gtk.NewApplication("net.sunglocto.lambda", gio.ApplicationFlagsNone) - app.ConnectActivate(func() { activate(app) }) + app.ConnectActivate(func() { + go conc() + activate(app) + }) if code := app.Run(os.Args); code > 0 { os.Exit(code) @@ -870,8 +880,15 @@ func activate(app *gtk.Application) { } if strings.Contains(t, "@everyone") { + start := strings.Index(t, "@everyone") + end := start + len("@everyone") + new_mention := new(Mention) - new_mention.Mentions = "urn:xmpp:mentions:0#channel" + new_mention.Type = "urn:xmpp:mentions:0#channel" + + new_mention.Begin = start + new_mention.End = end + exts = append(exts, new_mention) } else if strings.Contains(t, "@here") { new_attention := new(Attention) diff --git a/types.go b/types.go index f4204d6..4500488 100644 --- a/types.go +++ b/types.go @@ -2,6 +2,7 @@ package main import ( "github.com/diamondburned/gotk4/pkg/gtk/v4" + "mellium.im/xmpp/color" "sync" ) @@ -18,10 +19,11 @@ type lambdaConfig struct { Insecure bool Nick string JoinBookmarks bool + CVD color.CVD } type mucUnit struct { - // key: OccupantID + // key: Resource // value: last user presence Members sync.Map } diff --git a/version.go b/version.go index 069a1a6..a838089 100644 --- a/version.go +++ b/version.go @@ -1,3 +1,3 @@ package main -var lambda_version string = "0.1.0" +var lambda_version string = "26w11a" diff --git a/xmpp-mentions.go b/xmpp-mentions.go index 7344a18..29dde9e 100644 --- a/xmpp-mentions.go +++ b/xmpp-mentions.go @@ -10,13 +10,12 @@ import ( type Mention struct { stanza.MsgExtension - XMLName xml.Name `xml:"urn:xmpp:mentions:0 mention"` - Mentions string `xml:"mentions,attr,omitempty"` - URI string `xml:"uri,attr,omitempty"` - Begin int `xml:"begin,attr,omitempty"` - End int `xml:"end,attr,omitempty"` - OccupantID string `xml:"occupantid,attr,omitempty"` - JID string `xml:"ji,attr,omitempty"` + XMLName xml.Name `xml:"urn:xmpp:mentions:0 mention"` + URI string `xml:"uri,attr,omitempty"` + Begin int `xml:"begin,attr,omitempty"` + End int `xml:"end,attr,omitempty"` + Type string `xml:"type,attr"` + Target string `xml:"target,attr,omitempty"` } func init() {