diff --git a/README.cn.md b/README.cn.md index a309b2a..3507f1d 100644 --- a/README.cn.md +++ b/README.cn.md @@ -202,6 +202,14 @@ DefaultDownloadPath = /Users/username/Downloads/ - 关于进度条,己传文件大小和传输速度不是精确值,会有一些偏差,它的主要作用只是指示传输正在进行中。 +## 剪贴板集成 + +- 使用 `-o` 或 `--osc52` 启用剪贴板集成功能,例如 `trzsz -o ssh remote_server`。 + +- 启用剪贴板集成功能后,支持远程服务器通过 OSC52 序列写入本地剪贴板。 + +- 在 Linux 系统,剪贴板集成功能需要安装 `xclip` 或 `xsel` 命令。 + ## 常见问题 - 如果 [MSYS2](https://www.msys2.org/) 或 [Git Bash](https://www.atlassian.com/git/tutorials/git-bash) 遇到错误 `The handle is invalid`。 diff --git a/README.md b/README.md index d3330ce..e92b208 100644 --- a/README.md +++ b/README.md @@ -202,6 +202,14 @@ DefaultDownloadPath = /Users/username/Downloads/ - About the progress, the transferred and speed are not precise, there will be some deviation. It just indicating that the transfer is in progress. +## Clipboard integration + +- Use `-o` or `--osc52` to enable the clipboard integration feature. e.g., `trzsz -o ssh remote_server`. + +- Clipboard integration allows remote servers to write to the local clipboard via OSC52 sequences. + +- On Linux, clipboard integration requires `xclip` or `xsel` command to be installed. + ## Trouble shooting - If using [MSYS2](https://www.msys2.org/) or [Git Bash](https://www.atlassian.com/git/tutorials/git-bash) on windows, and getting an error `The handle is invalid`. diff --git a/go.mod b/go.mod index 1014d18..6611df7 100644 --- a/go.mod +++ b/go.mod @@ -3,17 +3,18 @@ module github.com/trzsz/trzsz-go go 1.20 require ( - github.com/UserExistsError/conpty v0.1.3 + github.com/UserExistsError/conpty v0.1.4 + github.com/atotto/clipboard v0.1.4 github.com/creack/pty v1.1.21 - github.com/klauspost/compress v1.17.8 + github.com/klauspost/compress v1.17.9 github.com/mattn/go-runewidth v0.0.15 - github.com/ncruces/zenity v0.10.12 + github.com/ncruces/zenity v0.10.13 github.com/stretchr/testify v1.8.4 github.com/trzsz/go-arg v1.5.3 github.com/trzsz/promptui v0.10.7 - golang.org/x/sys v0.20.0 - golang.org/x/term v0.20.0 - golang.org/x/text v0.15.0 + golang.org/x/sys v0.22.0 + golang.org/x/term v0.22.0 + golang.org/x/text v0.16.0 ) require ( @@ -26,6 +27,6 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/randall77/makefat v0.0.0-20210315173500-7ddd0e42c844 // indirect github.com/rivo/uniseg v0.4.7 // indirect - golang.org/x/image v0.16.0 // indirect + golang.org/x/image v0.18.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 8f72154..01809a8 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,11 @@ -github.com/UserExistsError/conpty v0.1.3 h1:YzGQkHAiBBkAihOCO5J2cAnahzb8ePvje2YxG7et1E0= -github.com/UserExistsError/conpty v0.1.3/go.mod h1:PDglKIkX3O/2xVk0MV9a6bCWxRmPVfxqZoTG/5sSd9I= +github.com/UserExistsError/conpty v0.1.4 h1:+3FhJhiqhyEJa+K5qaK3/w6w+sN3Nh9O9VbJyBS02to= +github.com/UserExistsError/conpty v0.1.4/go.mod h1:PDglKIkX3O/2xVk0MV9a6bCWxRmPVfxqZoTG/5sSd9I= github.com/akavel/rsrc v0.10.2 h1:Zxm8V5eI1hW4gGaYsJQUhxpjkENuG91ki8B4zCrvEsw= github.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw= github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o= +github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= +github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= @@ -19,12 +21,12 @@ github.com/dchest/jsmin v0.0.0-20220218165748-59f39799265f h1:OGqDDftRTwrvUoL6pO github.com/dchest/jsmin v0.0.0-20220218165748-59f39799265f/go.mod h1:Dv9D0NUlAsaQcGQZa5kc5mqR9ua72SmA8VXi4cd+cBw= github.com/josephspurrier/goversioninfo v1.4.0 h1:Puhl12NSHUSALHSuzYwPYQkqa2E1+7SrtAPJorKK0C8= github.com/josephspurrier/goversioninfo v1.4.0/go.mod h1:JWzv5rKQr+MmW+LvM412ToT/IkYDZjaclF2pKDss8IY= -github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= -github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/ncruces/zenity v0.10.12 h1:o4SErDa0kQijlqG6W4OYYzO6kA0fGu34uegvJGcMLBI= -github.com/ncruces/zenity v0.10.12/go.mod h1:5OZIERViRR2fN0FcJCcisqxI+lYMDGzEDCEwB/+8iao= +github.com/ncruces/zenity v0.10.13 h1:0Gd/EdjjEQIhrFaJ05Q5ZvyjlcjnorlZpdzgUzqQIH0= +github.com/ncruces/zenity v0.10.13/go.mod h1:UyAUPSjHm1hOdeZa3Lrh/zmItyGq+iH5AP6xSCx8CFM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/randall77/makefat v0.0.0-20210315173500-7ddd0e42c844 h1:GranzK4hv1/pqTIhMTXt2X8MmMOuH3hMeUR0o9SP5yc= @@ -43,17 +45,17 @@ github.com/trzsz/go-arg v1.5.3/go.mod h1:IC6Z/FiVH7uYvcbp1/gJhDYCFPS/GkL0APYakVv github.com/trzsz/promptui v0.10.7 h1:77uBrmsIPYYJS/9n+zwFRhwOz82EKXkkdjOiWSEUPpk= github.com/trzsz/promptui v0.10.7/go.mod h1:9dp59ixe32qBV9GjDxQ1PDWwbzHjTzveZenQwEoVHbg= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -golang.org/x/image v0.16.0 h1:9kloLAKhUufZhA12l5fwnx2NZW39/we1UhBesW433jw= -golang.org/x/image v0.16.0/go.mod h1:ugSZItdV4nOxyqp56HmXwH0Ry0nBCpjnZdpDaIHdoPs= +golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ= +golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= -golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= +golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/trzsz/filter.go b/trzsz/filter.go index 6d7ba3f..c42fb9c 100644 --- a/trzsz/filter.go +++ b/trzsz/filter.go @@ -26,6 +26,8 @@ package trzsz import ( "bufio" + "bytes" + "encoding/base64" "fmt" "io" "net" @@ -39,6 +41,7 @@ import ( "sync/atomic" "time" + "github.com/atotto/clipboard" "github.com/ncruces/zenity" "github.com/trzsz/promptui" ) @@ -55,6 +58,8 @@ type TrzszOptions struct { DetectTraceLog bool // EnableZmodem enable zmodem lrzsz ( rz / sz ) feature. EnableZmodem bool + // EnableOSC52 enable OSC52 clipboard feature. + EnableOSC52 bool } // TrzszFilter is a filter that supports trzsz ( trz / tsz ). @@ -79,6 +84,7 @@ type TrzszFilter struct { defaultUploadPath atomic.Pointer[string] defaultDownloadPath atomic.Pointer[string] tunnelConnector atomic.Pointer[func(int) net.Conn] + osc52Sequence *bytes.Buffer } // NewTrzszFilter create a TrzszFilter to support trzsz ( trz / tsz ). @@ -695,6 +701,9 @@ func (filter *TrzszFilter) wrapOutput() { } } } + if filter.options.EnableOSC52 { + filter.detectOSC52(buf) + } var trigger *trzszTrigger buf, trigger = detector.detectTrzsz(buf, filter.tunnelConnector.Load() != nil) @@ -738,3 +747,52 @@ func (filter *TrzszFilter) wrapOutput() { } } } + +func (filter *TrzszFilter) detectOSC52(buf []byte) { + if filter.osc52Sequence == nil { + pos := bytes.Index(buf, []byte("\x1b]52;c;")) + if pos < 0 { + return + } + buf = buf[pos+7:] + pos = bytes.IndexByte(buf, '\a') + if pos < 0 { + filter.osc52Sequence = bytes.NewBuffer(nil) + filter.osc52Sequence.Write(buf) + return + } + writeToClipboard(buf[:pos]) + buf = buf[pos+1:] + if len(buf) > 0 { + filter.detectOSC52(buf) + } + return + } + + pos := bytes.IndexByte(buf, '\a') + if pos < 0 { + filter.osc52Sequence.Write(buf) + if filter.osc52Sequence.Len() > 100000 { + // something went wrong, just ignore it + filter.osc52Sequence = nil + } + return + } + + filter.osc52Sequence.Write(buf[:pos]) + writeToClipboard(filter.osc52Sequence.Bytes()) + filter.osc52Sequence = nil + + buf = buf[pos+1:] + if len(buf) > 0 { + filter.detectOSC52(buf) + } +} + +func writeToClipboard(buf []byte) { + text, err := base64.StdEncoding.DecodeString(string(buf)) + if err != nil { + return + } + _ = clipboard.WriteAll(string(text)) +} diff --git a/trzsz/trzsz.go b/trzsz/trzsz.go index 8bceaea..38ac824 100644 --- a/trzsz/trzsz.go +++ b/trzsz/trzsz.go @@ -40,6 +40,7 @@ type trzszArgs struct { TraceLog bool DragFile bool Zmodem bool + OSC52 bool Name string Args []string } @@ -59,7 +60,8 @@ func printHelp() { " -r, --relay run as a trzsz relay server\n" + " -t, --tracelog eanble trace log for debugging\n" + " -d, --dragfile enable drag file(s) to upload\n" + - " -z, --zmodem enable zmodem lrzsz ( rz / sz )\n") + " -z, --zmodem enable zmodem lrzsz (rz / sz)\n" + + " -o, --osc52 enable clipboard integration\n") } func parseTrzszArgs() *trzszArgs { @@ -80,6 +82,8 @@ func parseTrzszArgs() *trzszArgs { args.DragFile = true } else if os.Args[i] == "-z" || os.Args[i] == "--zmodem" { args.Zmodem = true + } else if os.Args[i] == "-o" || os.Args[i] == "--osc52" { + args.OSC52 = true } else { break } @@ -173,6 +177,7 @@ func TrzszMain() int { DetectDragFile: args.DragFile, DetectTraceLog: args.TraceLog, EnableZmodem: args.Zmodem, + EnableOSC52: args.OSC52, }) pty.OnResize(filter.SetTerminalColumns) // handle signal