11 Commits

Author SHA1 Message Date
3f6b87623d Add license button 2025-09-02 19:40:35 +01:00
d3bffcef81 Finalize join bookmarks button, fix now playing button and make messages containing a single emoji bigger 2025-09-02 19:25:54 +01:00
53367e5982 Add MPRIS player grabbing to Delta menu 2025-09-02 14:47:39 +01:00
b765e8f11c lol 2025-09-02 13:21:40 +01:00
969199b0d7 lol 2025-09-02 13:20:46 +01:00
5b0072a488 lol 2025-09-02 13:17:59 +01:00
636df591b8 dep info 2025-09-02 13:15:13 +01:00
d4433a20ed Merge branch 'master' of https://forge.sunglocto.net/sunglocto/pi-im 2025-09-02 09:55:13 +01:00
80489d1877 some changes 2025-09-02 09:55:04 +01:00
713bde2d23 Merge pull request 'meow' (#18) from nyx/pi-im:nyx-patch-1 into master
Reviewed-on: #18
Reviewed-by: sunglocto <sunglocto@protonmail.com>
2025-09-02 08:53:59 +00:00
nyx
ffd6023ae2 meow 2025-09-02 08:52:23 +00:00
5 changed files with 4188 additions and 56 deletions

View File

@@ -124,20 +124,63 @@ You can file an issue and explain the problem you are having.
If you would like a more instant method of communication, join the [pi XMPP room.](xmpp:pi@room.sunglocto.net?join)
## εξαρτήσεις
(dependencies)
### Running
Pi will require no additional dependencies to run on MacOS and Windows systems.
On other *NIX systems, `dbus` is needed for notification support, but is not required.
If your system has an X11 or Wayland setup, Pi will most likely work without issues.
Pi does not require GTK, Qt or any other graphical layout.
### Compiling
In general, you need Go, a C compiler and graphical libraries.
*This information is from the [the Fyne documentation.](https://docs.fyne.io/started) Windows and MacOS users are heavily reccomended to simply use pre-compiled binaries, due a more complex set of dependencies.*
Linux systems:
Debian, Ubuntu and Raspberry Pi OS: `sudo apt-get install golang gcc libgl1-mesa-dev xorg-dev libxkbcommon-dev`
Fedora: `sudo dnf install golang golang-misc gcc libXcursor-devel libXrandr-devel mesa-libGL-devel libXi-devel libXinerama-devel libXxf86vm-devel libxkbcommon-devel wayland-devel`
Arch Linux: `sudo pacman -S go xorg-server-devel libxcursor libxrandr libxinerama libxi libxkbcommon`
Solus: `sudo eopkg it -c system.devel golang mesalib-devel libxrandr-devel libxcursor-devel libxi-devel libxinerama-devel libxkbcommon-devel`
openSUSE: `sudo zypper install go gcc libXcursor-devel libXrandr-devel Mesa-libGL-devel libXi-devel libXinerama-devel libXxf86vm-devel libxkbcommon-devel`
Void Linux: `sudo xbps-install -S go base-devel xorg-server-devel libXrandr-devel libXcursor-devel libXinerama-devel libXxf86vm-devel libxkbcommon-devel wayland-devel`
Alpine Linux: `sudo apk add go gcc libxcursor-dev libxrandr-dev libxinerama-dev libxi-dev linux-headers mesa-dev libxkbcommon-dev wayland-dev``
NixOS: `nix-shell -p libGL pkg-config xorg.libX11.dev xorg.libXcursor xorg.libXi xorg.libXinerama xorg.libXrandr xorg.libXxf86vm libxkbcommon wayland`
BSD systems:
FreeBSD: `sudo pkg install go gcc xorg pkgconf`
OpenBSD: `sudo pkg_add go`
NetBSD: `sudo pkgin install go pkgconf`
## αρμονία
(compatibility)
pi has guaranteed support for the following operating systems:
pi will run on the following operating systems:
- Windows 10 and up
- Most modern GNU/Linux installations
- FreeBSD
- NetBSD
- OpenBSD
- MacOS
pi has experimental support for the following operating system:
- Android [1]
pi most likely does **not** have support for the following operating systems:
pi most likely will **not** run on the following operating systems:
- 32-bit systems
- iOS
- ARM systems

4016
credits.go Normal file

File diff suppressed because it is too large Load Diff

2
go.mod
View File

@@ -5,7 +5,9 @@ go 1.24.6
require (
fyne.io/fyne/v2 v2.6.3
fyne.io/x/fyne v0.0.0-20250827163406-39fd826f385e
github.com/makeworld-the-better-one/go-isemoji v1.3.0
github.com/rrivera/identicon v0.0.0-20240116195454-d5ba35832c0d
github.com/shreve/musicwand v0.0.1
mellium.im/xmpp v0.22.0
pain.agency/oasis-sdk v0.0.0-20250831105702-85385dca3a95
)

16
go.sum
View File

@@ -4,8 +4,11 @@ fyne.io/systray v1.11.0 h1:D9HISlxSkx+jHSniMBR6fCFOUjk1x/OOOJLa9lJYAKg=
fyne.io/systray v1.11.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs=
fyne.io/x/fyne v0.0.0-20250827163406-39fd826f385e h1:oJM+HGkpSuq1J+JqUq/jo7KPrKj2K2/VIZlyus04w3Y=
fyne.io/x/fyne v0.0.0-20250827163406-39fd826f385e/go.mod h1:u3LF1EkElytjOT8OHxft16trctGndF9qpsoH6YIDOUU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
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/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
@@ -32,6 +35,7 @@ github.com/go-text/typesetting v0.2.1 h1:x0jMOGyO3d1qFAPI0j4GSsh7M0Q3Ypjzr4+CEVg
github.com/go-text/typesetting v0.2.1/go.mod h1:mTOxEwasOFpAMBjEQDhdWRckoLLeI/+qrQeBCTGEt6M=
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0=
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y=
@@ -46,6 +50,8 @@ github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 h1:YLvr1eE6cdCqjOe9
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/makeworld-the-better-one/go-isemoji v1.3.0 h1:vrxfd0W0Xs8t7BnIYXkvSK7m+GDk/0x1ClXPbWjQ5A0=
github.com/makeworld-the-better-one/go-isemoji v1.3.0/go.mod h1:FBjkPl9rr0G4vlZCc+Mr+QcnOfGCTbGWYW8/1sp06I0=
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/nicksnyder/go-i18n/v2 v2.5.1 h1:IxtPxYsR9Gp60cGXjfuR/llTqV8aYMsC472zD0D1vHk=
@@ -58,14 +64,21 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rrivera/identicon v0.0.0-20240116195454-d5ba35832c0d h1:l3+2LWCbVxn5itfvXAfH9n4YL9jh8l1g5zcncbIc1cs=
github.com/rrivera/identicon v0.0.0-20240116195454-d5ba35832c0d/go.mod h1:TbpErkob6SY7cyozRVSGoB3OlO2qOAgVN8O3KAJ4fMI=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/rymdport/portal v0.4.1 h1:2dnZhjf5uEaeDjeF/yBIeeRo6pNI2QAKm7kq1w/kbnA=
github.com/rymdport/portal v0.4.1/go.mod h1:kFF4jslnJ8pD5uCi17brj/ODlfIidOxlgUDTO5ncnC4=
github.com/shreve/musicwand v0.0.1 h1:uF1GDmk6b9xZPAGJU3iWBSqKruyVeyVs/cqM6YzaJ3k=
github.com/shreve/musicwand v0.0.1/go.mod h1:y7oBkMLTfGmpIxoKQFwXlzWLtffcQIpX/1no+iNPxwQ=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE=
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
@@ -87,6 +100,9 @@ golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/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=

163
main.go
View File

@@ -3,6 +3,7 @@ package main
import (
//core - required
"encoding/xml"
"errors"
"fmt"
_ "image/color"
"io"
@@ -23,7 +24,9 @@ import (
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
extraWidgets "fyne.io/x/fyne/widget"
"github.com/makeworld-the-better-one/go-isemoji"
"github.com/rrivera/identicon"
"github.com/shreve/musicwand/pkg/mpris"
// xmpp - required
_ "mellium.im/xmpp/disco"
@@ -240,28 +243,38 @@ func CreateUITab(chatJidStr string) ChatTabUI {
content.SetText(msgContent)
if chatTabs[chatJidStr].Messages[i].ReplyID != "PICLIENT:UNAVAILABLE" {
reply := chatTabs[chatJidStr].Messages[i].Raw.Reply
guy := jid.MustParse(reply.To).Resourcepart()
// TODO: EXPERIMENTALLY GET REPLIED TO TEXT
for i := len(chatTabs[chatJidStr].Messages) - 1; i >= 0; i-- {
if reply.ID == chatTabs[chatJidStr].Messages[i].Raw.StanzaID.ID {
replytext.Show()
replytext.SetText(fmt.Sprintf("> %s", chatTabs[chatJidStr].Messages[i].Content))
guy = chatTabs[chatJidStr].Messages[i].Author
break
}
}
author.SetText(fmt.Sprintf("%s > %s", chatTabs[chatJidStr].Messages[i].Author, jid.MustParse(reply.To).Resourcepart()))
author.SetText(fmt.Sprintf("%s > %s", chatTabs[chatJidStr].Messages[i].Author, guy))
} else {
author.SetText(chatTabs[chatJidStr].Messages[i].Author)
replytext.Hide()
}
sl := strings.Split(msgContent, " ")
if len(sl) == 1 && isemoji.IsEmoji(sl[0]) {
content.SizeName = fyne.ThemeSizeName(theme.SizeNameHeadingText)
content.Refresh()
}
if sl[0] == "/me" {
author.SetText(author.Text + " " + strings.Join(sl[1:], " "))
content.SetText(" ")
}
scroller.SetItemHeight(i, vbox.MinSize().Height)
vbox.Refresh()
},
)
@@ -315,7 +328,7 @@ func addChatTab(isMuc bool, chatJid jid.JID, nick string) {
}
func dropToSignInPage(reason string) {
w = a.NewWindow("Welcome to Pi")
w = a.NewWindow("Welcome to pi")
w.Resize(fyne.NewSize(500, 500))
rt := widget.NewRichTextFromMarkdown("# Welcome to pi\nIt appears you do not have a valid account configured. Let's create one!")
footer := widget.NewRichTextFromMarkdown(fmt.Sprintf("Reason for being dropped to the sign-in page:\n\n```%s```", reason))
@@ -741,6 +754,10 @@ func main() {
dialog.ShowInformation("about pi", fmt.Sprintf("the XMPP client from hell\n\npi is an experimental XMPP client\nwritten by Sunglocto in Go.\n\nVersion %s", version), w)
})
licensesbtn := fyne.NewMenuItem("license", func() {
CreditsWindow(fyne.CurrentApp(), fyne.NewSize(800, 400)).Show()
})
reconnect := fyne.NewMenuItem("reconnect", func() {
go func() {
err := client.Connect()
@@ -969,64 +986,64 @@ func main() {
})
/*
servDisc := fyne.NewMenuItem("Disco features", func() {
//var search jid.JID
dialog.ShowEntryDialog("Disco features", "JID: ", func(s string) { // TODO: replace with undeprecated widgetd
d := dialog.NewCustom("Please wait", "Close", widget.NewLabel("..."), w)
d.Show()
go func() {
//search, err = jid.Parse(s)
//if err != nil {
// d.Hide()
// dialog.ShowError(err, w)
// return
//}
txt := `<iq from='ringen@muc.isekai.rocks/sunglocto'
to='ringen@muc.iskai.rocks/snit'
type='get'
id='vc2'>
<vCard xmlns='vcard-temp'/>
</iq>`
var stan stanza.IQ
xml.Unmarshal([]byte(txt), &stan)
if err != nil {
d.Hide()
dialog.ShowError(err, w)
return
}
r, err := client.Session.EncodeIQ(client.Ctx, stan)
if err != nil {
d.Hide()
dialog.ShowError(err, w)
return
}
ra, _ := r.Token()
t, _ := xml.MarshalIndent(ra, "", "\t")
fmt.Println(string(t))
d.Hide()
/*
myBox := container.NewGridWithColumns(1, widget.NewLabel("Items"))
info, err := disco.GetInfo(client.Ctx, "", search, client.Session)
servDisc := fyne.NewMenuItem("Disco features", func() {
//var search jid.JID
dialog.ShowEntryDialog("Disco features", "JID: ", func(s string) { // TODO: replace with undeprecated widgetd
d := dialog.NewCustom("Please wait", "Close", widget.NewLabel("..."), w)
d.Show()
go func() {
//search, err = jid.Parse(s)
//if err != nil {
// d.Hide()
// dialog.ShowError(err, w)
// return
//}
txt := `<iq from='ringen@muc.isekai.rocks/sunglocto'
to='ringen@muc.iskai.rocks/snit'
type='get'
id='vc2'>
<vCard xmlns='vcard-temp'/>
</iq>`
var stan stanza.IQ
xml.Unmarshal([]byte(txt), &stan)
if err != nil {
d.Hide()
dialog.ShowError(err, w)
return
}
m := info.Identity
bytes, err := xml.MarshalIndent(m, "", "\t")
r, err := client.Session.EncodeIQ(client.Ctx, stan)
if err != nil {
d.Hide()
dialog.ShowError(err, w)
return
}
fyne.Do(func() {d.Hide()})
myBox.Objects = append(myBox.Objects, widget.NewLabel(string(bytes)))
dialog.ShowCustom("Service discovery", "cancel", myBox, w)
}()
ra, _ := r.Token()
t, _ := xml.MarshalIndent(ra, "", "\t")
fmt.Println(string(t))
d.Hide()
/*
}, w)
})
myBox := container.NewGridWithColumns(1, widget.NewLabel("Items"))
info, err := disco.GetInfo(client.Ctx, "", search, client.Session)
if err != nil {
d.Hide()
dialog.ShowError(err, w)
return
}
m := info.Identity
bytes, err := xml.MarshalIndent(m, "", "\t")
if err != nil {
d.Hide()
dialog.ShowError(err, w)
return
}
fyne.Do(func() {d.Hide()})
myBox.Objects = append(myBox.Objects, widget.NewLabel(string(bytes)))
dialog.ShowCustom("Service discovery", "cancel", myBox, w)
}()
}, w)
})
*/
savedata := fyne.NewMenuItem("DEBUG: Save tab data to disk", func() {
@@ -1042,7 +1059,8 @@ func main() {
os.Create("test.xml")
os.WriteFile("text.xml", b, os.ModeAppend)
})
menu_help := fyne.NewMenu("π", mit, reconnect, savedata, fyne.NewMenuItem("Join rooms in bookmarks", func() {
jbookmarks := fyne.NewMenuItem("Join rooms in bookmarks", func() {
// FIXME: Race condition
client.FetchBookmarks()
rooms := client.BookmarkCache()
@@ -1064,9 +1082,11 @@ func main() {
}
}()
}
}))
})
menu_changeroom := fyne.NewMenu("Α", mic, beginADM, joinARoom, leaveRoom)
menu_help := fyne.NewMenu("π", mit, reconnect, licensesbtn, savedata)
menu_changeroom := fyne.NewMenu("Α", mic, beginADM, joinARoom, leaveRoom, jbookmarks)
menu_configureview := fyne.NewMenu("Β", mia, mis, jtt, jtb)
hafjag := fyne.NewMenuItem("Hafjag", func() {
entry.Text = "Hafjag"
@@ -1103,7 +1123,42 @@ func main() {
entry.Text = fmt.Sprintf("It is currently %s", time.Now().Format(time.RFC850))
SendCallback()
})
menu_jokes := fyne.NewMenu("Δ", mycurrenttime, hafjag, hotfuck, agree)
mycurrentplayingsong := fyne.NewMenuItem("Get currently playing song", func() {
client, err := mpris.NewClient()
if err != nil {
dialog.ShowError(err, w)
return
}
present := false
for _, player := range client.Players() {
fmt.Println(player.RawMetadata())
old := entry.Text
newtext := ""
title, t_ok := player.RawMetadata()["xesam:title"]
artist, a_ok := player.RawMetadata()["xesam:artist"]
if t_ok && a_ok {
newtext = fmt.Sprintf("I'm currently listening to %s by %s", title.String(), artist.String())
} else if t_ok {
newtext = fmt.Sprintf("I'm currently listening to %s", title.String())
} else if a_ok {
newtext = fmt.Sprintf("I'm currently listening to a song by %s", artist.String())
} else {
dialog.ShowError(errors.New("error: There's a playing song, but we could not get the artist or title information."), w)
return
}
entry.SetText(newtext)
SendCallback()
entry.SetText(old)
present = true
}
if !present {
dialog.ShowInformation("Failed", "Could not find any open players. You might need an MPRIS plugin for players such as mpv.\nSee the MPRIS ArchWiki article for more information:\nhttps://wiki.archlinux.org/title/MPRIS", w)
}
})
menu_jokes := fyne.NewMenu("Δ", mycurrenttime, hafjag, hotfuck, agree, mycurrentplayingsong)
bit := fyne.NewMenuItem("mark selected message as read", func() {
selectedScroller, ok := AppTabs.Selected().Content.(*widget.List)
if !ok {
@@ -1152,7 +1207,7 @@ func main() {
dialog.ShowCustom("Message", "Close", pre, w)
})
red := fyne.NewMenuItem("show people who have read this message", func() {
red := fyne.NewMenuItem("show read receipts on message", func() {
pre := container.NewVBox()
selectedScroller, ok := AppTabs.Selected().Content.(*widget.List)