add: clipboard functionality in room view

CTRL+V now checks if the clipboard contains an image and pastes it into the room.

The library used for clipboard functionality requires an additional
dependency:

> Linux: require X11 dev package. For instance, install libx11-dev or xorg-dev or libX11-devel to access X window system.
This commit is contained in:
i401 2023-10-05 22:33:21 +02:00
parent 09a9279558
commit 8472803cd0
6 changed files with 103 additions and 36 deletions

View File

@ -39,4 +39,5 @@ room:
'Ctrl+n': scroll_down
'PageUp': scroll_up
'PageDown': scroll_down
'Ctrl+v': paste
'Enter': send

13
go.mod
View File

@ -19,8 +19,8 @@ require (
go.mau.fi/cbind v0.0.0-20220415094356-e1d579b7925e
go.mau.fi/mauview v0.2.1
go.mau.fi/tcell v0.4.0
golang.org/x/image v0.1.0
golang.org/x/net v0.2.0
golang.org/x/image v0.6.0
golang.org/x/net v0.6.0
gopkg.in/toast.v1 v1.0.0-20180812000517-0a84660828b2
gopkg.in/vansante/go-ffprobe.v2 v2.1.1
gopkg.in/yaml.v3 v3.0.1
@ -40,10 +40,13 @@ require (
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tidwall/sjson v1.2.4 // indirect
golang.design/x/clipboard v0.7.0 // indirect
golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9 // indirect
golang.org/x/sys v0.2.0 // indirect
golang.org/x/term v0.2.0 // indirect
golang.org/x/text v0.4.0 // indirect
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 // indirect
golang.org/x/mobile v0.0.0-20230301163155-e0f57694e12c // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/term v0.5.0 // indirect
golang.org/x/text v0.8.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
maunium.net/go/maulogger/v2 v2.3.2 // indirect
)

29
go.sum
View File

@ -1,3 +1,4 @@
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -68,24 +69,43 @@ go.mau.fi/mauview v0.2.1 h1:Sv+L3MQoo0VWuqgO/SIzhTzDcd7iqPGZgxH3au2kUGw=
go.mau.fi/mauview v0.2.1/go.mod h1:aTb1VjsjFmZ5YsdMQTIHrma9Ki2O0WwkS2Er7bIgoUs=
go.mau.fi/tcell v0.4.0 h1:IPFKhkzF3yZkcRYjzgYBWWiW0JWPTwEBoXlWTBT8o/4=
go.mau.fi/tcell v0.4.0/go.mod h1:77zV/6KL4Zip1u9ndjswACmu/LWwZ/oe3BE188uWMrA=
golang.design/x/clipboard v0.7.0 h1:4Je8M/ys9AJumVnl8m+rZnIvstSnYj1fvzqYrU3TXvo=
golang.design/x/clipboard v0.7.0/go.mod h1:PQIvqYO9GP29yINEfsEn5zSQKAz3UgXmZKzDA6dnq2E=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9 h1:NUzdAbFtCJSXU20AOXgeqaUwg8Ypg4MPYmL+d+rsB5c=
golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 h1:estk1glOnSVeJ9tdEZZc5mAMDZk5lNJNyJ6DvrBkTEU=
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.1.0 h1:r8Oj8ZA2Xy12/b5KZYj3tuv7NG/fBz3TwQVvpJ9l8Rk=
golang.org/x/image v0.1.0/go.mod h1:iyPr49SD/G/TBxYVB/9RRtGUT5eNbo2u4NamWeQcD5c=
golang.org/x/image v0.6.0 h1:bR8b5okrPI3g/gyZakLZHeWxAR8Dn5CyxXv1hLH5g/4=
golang.org/x/image v0.6.0/go.mod h1:MXLdDR43H7cDJq5GEGXEVeeNhPgi+YYEQ2pC1byI1x0=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20230301163155-e0f57694e12c h1:Gk61ECugwEHL6IiyyNLXNzmu8XslmRP2dS0xjIYhbb4=
golang.org/x/mobile v0.0.0-20230301163155-e0f57694e12c/go.mod h1:aAjjkJNdrh3PMckS4B10TGS2nag27cbKR1y2BpUxsiY=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -93,18 +113,27 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=

View File

@ -26,6 +26,7 @@ import (
"strings"
"time"
pasteclip "golang.design/x/clipboard"
flag "maunium.net/go/mauflag"
"maunium.net/go/gomuks/debug"
@ -102,6 +103,12 @@ func main() {
matrix.SkipVersionCheck = *skipVersionCheck
gmx := NewGomuks(MainUIProvider, configDir, dataDir, cacheDir, downloadDir)
// initialize clipboard
err = pasteclip.Init()
if err != nil {
debug.Print("Failed to initialize clipboard")
}
if *clearCache {
debug.Print("Clearing cache as requested by CLI flag")
gmx.config.Clear()

View File

@ -17,6 +17,7 @@
package matrix
import (
"bytes"
"context"
"crypto/tls"
"encoding/gob"
@ -32,6 +33,7 @@ import (
"reflect"
"runtime"
dbg "runtime/debug"
"strings"
"time"
"maunium.net/go/mautrix"
@ -996,45 +998,62 @@ func (c *Container) SendEvent(evt *muksevt.Event) (id.EventID, error) {
return resp.EventID, nil
}
func (c *Container) UploadMedia(path string, encrypt bool) (*ifc.UploadedMediaInfo, error) {
var err error
path, err = filepath.Abs(path)
if err != nil {
return nil, fmt.Errorf("failed to get absolute path: %w", err)
}
msgtype, info, err := getMediaInfo(path)
if err != nil {
return nil, err
}
file, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("failed to open file: %w", err)
}
stat, err := file.Stat()
if err != nil {
return nil, fmt.Errorf("failed to get file info: %w", err)
}
uploadFileName := stat.Name()
uploadMimeType := info.MimeType
func (c *Container) UploadMedia(media string, encrypt bool) (*ifc.UploadedMediaInfo, error) {
var content io.Reader
var fileSize int64
var uploadFileName string
var uploadMimeType string
var msgtype event.MessageType
var info event.FileInfo
mime := http.DetectContentType([]byte(media))
pasted := strings.HasPrefix(mime, "image")
if pasted {
debug.Print("An image was pasted")
content = bytes.NewReader([]byte(media))
fileSize = content.(*bytes.Reader).Size()
msgtype = event.MsgImage
uploadMimeType = mime
uploadFileName = "image"
} else {
path, err := filepath.Abs(media)
if err != nil {
return nil, fmt.Errorf("failed to get absolute path: %w", err)
}
file, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("failed to open file: %w", err)
}
defer file.Close()
stat, err := file.Stat()
if err != nil {
return nil, fmt.Errorf("failed to get file info: %w", err)
}
fileSize = stat.Size()
uploadFileName = stat.Name()
msgtype, info, err = getMediaInfo(path)
if err != nil {
return nil, err
}
uploadMimeType = info.MimeType
content = file
}
var encryptionInfo *attachment.EncryptedFile
if encrypt {
encryptionInfo = attachment.NewEncryptedFile()
content = encryptionInfo.EncryptStream(content)
uploadMimeType = "application/octet-stream"
uploadFileName = ""
encryptionInfo = attachment.NewEncryptedFile()
content = encryptionInfo.EncryptStream(file)
} else {
content = file
}
resp, err := c.client.UploadMedia(mautrix.ReqUploadMedia{
Content: content,
ContentLength: stat.Size(),
ContentLength: fileSize,
ContentType: uploadMimeType,
FileName: uploadFileName,
})
@ -1046,7 +1065,7 @@ func (c *Container) UploadMedia(path string, encrypt bool) (*ifc.UploadedMediaIn
return &ifc.UploadedMediaInfo{
RespMediaUpload: resp,
EncryptionInfo: encryptionInfo,
Name: stat.Name(),
Name: uploadFileName,
MsgType: msgtype,
Info: &info,
}, nil

View File

@ -26,6 +26,7 @@ import (
"github.com/kyokomi/emoji/v2"
"github.com/mattn/go-runewidth"
"github.com/zyedidia/clipboard"
pasteclip "golang.design/x/clipboard"
"go.mau.fi/mauview"
"go.mau.fi/tcell"
@ -376,7 +377,14 @@ func (view *RoomView) OnKeyEvent(event mauview.KeyEvent) bool {
case "send":
view.InputSubmit(view.input.GetText())
return true
case "paste":
cb := pasteclip.Read(pasteclip.FmtImage)
if cb != nil {
view.SendMessageMedia(string(cb))
}
return true
}
return view.input.OnKeyEvent(event)
}