Reading arbitrary MIME from clipboard now works
This commit is contained in:
parent
f6ab641b00
commit
f29ce19097
@ -4,17 +4,25 @@
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
OPTIONS = r'''
|
OPTIONS = r'''
|
||||||
--get-clipboard
|
--get-clipboard -g
|
||||||
type=bool-set
|
type=bool-set
|
||||||
Output the current contents of the clipboard to STDOUT. Note that by default
|
Output the current contents of the clipboard to STDOUT. Note that by default
|
||||||
kitty will prompt for permission to access the clipboard. Can be controlled
|
kitty will prompt for permission to access the clipboard. Can be controlled
|
||||||
by :opt:`clipboard_control`.
|
by :opt:`clipboard_control`.
|
||||||
|
|
||||||
|
|
||||||
--use-primary
|
--use-primary -p
|
||||||
type=bool-set
|
type=bool-set
|
||||||
Use the primary selection rather than the clipboard on systems that support it,
|
Use the primary selection rather than the clipboard on systems that support it,
|
||||||
such as X11.
|
such as Linux.
|
||||||
|
|
||||||
|
|
||||||
|
--mime -m
|
||||||
|
type=list
|
||||||
|
The mimetype of the specified file. Useful when the auto-detected mimetype is
|
||||||
|
likely to be incorrect or the filename has no extension and therefore no mimetype
|
||||||
|
can be detected. If more than one file is specified, this option should be specified multiple
|
||||||
|
times, once for each specified file.
|
||||||
|
|
||||||
|
|
||||||
--wait-for-completion
|
--wait-for-completion
|
||||||
|
|||||||
@ -257,8 +257,9 @@ class ClipboardRequestManager:
|
|||||||
self.in_flight_write_request: Optional[WriteRequest] = None
|
self.in_flight_write_request: Optional[WriteRequest] = None
|
||||||
|
|
||||||
def parse_osc_5522(self, data: str) -> None:
|
def parse_osc_5522(self, data: str) -> None:
|
||||||
|
import base64
|
||||||
from .notify import sanitize_id
|
from .notify import sanitize_id
|
||||||
metadata, _, payload = data.partition(';')
|
metadata, _, epayload = data.partition(';')
|
||||||
m: Dict[str, str] = {}
|
m: Dict[str, str] = {}
|
||||||
for record in metadata.split(':'):
|
for record in metadata.split(':'):
|
||||||
try:
|
try:
|
||||||
@ -268,10 +269,11 @@ class ClipboardRequestManager:
|
|||||||
return
|
return
|
||||||
m[k] = v
|
m[k] = v
|
||||||
typ = m.get('type', '')
|
typ = m.get('type', '')
|
||||||
|
payload = base64.standard_b64decode(epayload)
|
||||||
if typ == 'read':
|
if typ == 'read':
|
||||||
rr = ReadRequest(
|
rr = ReadRequest(
|
||||||
is_primary_selection=m.get('loc', '') == 'primary',
|
is_primary_selection=m.get('loc', '') == 'primary',
|
||||||
mime_types=tuple(payload.split()),
|
mime_types=tuple(payload.decode('utf-8').split()),
|
||||||
protocol_type=ProtocolType.osc_5522, id=sanitize_id(m.get('id', ''))
|
protocol_type=ProtocolType.osc_5522, id=sanitize_id(m.get('id', ''))
|
||||||
)
|
)
|
||||||
self.handle_read_request(rr)
|
self.handle_read_request(rr)
|
||||||
|
|||||||
@ -3,9 +3,22 @@
|
|||||||
package clipboard
|
package clipboard
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
"kitty/tools/cli"
|
"kitty/tools/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func run_mime_loop(opts *Options, args []string) (err error) {
|
||||||
|
cwd, err = os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if opts.GetClipboard {
|
||||||
|
return run_get_loop(opts, args)
|
||||||
|
}
|
||||||
|
return run_set_loop(opts, args)
|
||||||
|
}
|
||||||
|
|
||||||
func clipboard_main(cmd *cli.Command, opts *Options, args []string) (rc int, err error) {
|
func clipboard_main(cmd *cli.Command, opts *Options, args []string) (rc int, err error) {
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
return 0, run_mime_loop(opts, args)
|
return 0, run_mime_loop(opts, args)
|
||||||
|
|||||||
@ -1,115 +0,0 @@
|
|||||||
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
|
|
||||||
|
|
||||||
package clipboard
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"kitty/tools/tui/loop"
|
|
||||||
"kitty/tools/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ = fmt.Print
|
|
||||||
|
|
||||||
const OSC_NUMBER = "5522"
|
|
||||||
|
|
||||||
func run_get_loop(opts *Options, args []string) (err error) {
|
|
||||||
lp, err := loop.New(loop.NoAlternateScreen, loop.NoRestoreColors, loop.NoMouseTracking)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var available_mimes []string
|
|
||||||
reading_available_mimes := true
|
|
||||||
|
|
||||||
lp.OnInitialize = func() (string, error) {
|
|
||||||
lp.QueueWriteString("\x1b]" + OSC_NUMBER + ";type=read;.\x1b\\")
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
lp.OnEscapeCode = func(etype loop.EscapeCodeType, data []byte) (err error) {
|
|
||||||
if etype != loop.OSC || !bytes.HasPrefix(data, utils.UnsafeStringToBytes(OSC_NUMBER+";")) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
parts := bytes.SplitN(data, utils.UnsafeStringToBytes(";"), 3)
|
|
||||||
metadata := make(map[string]string)
|
|
||||||
var payload []byte
|
|
||||||
if len(parts) > 2 && len(parts[2]) > 0 {
|
|
||||||
payload, err = base64.StdEncoding.DecodeString(utils.UnsafeBytesToString(parts[2]))
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Received OSC %s packet from terminal with invalid base64 encoded payload", OSC_NUMBER)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(parts) > 1 {
|
|
||||||
for _, record := range bytes.Split(parts[1], utils.UnsafeStringToBytes(":")) {
|
|
||||||
rp := bytes.SplitN(record, utils.UnsafeStringToBytes("="), 2)
|
|
||||||
v := ""
|
|
||||||
if len(rp) == 2 {
|
|
||||||
v = string(rp[1])
|
|
||||||
}
|
|
||||||
metadata[string(rp[0])] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if reading_available_mimes {
|
|
||||||
switch metadata["status"] {
|
|
||||||
case "DATA":
|
|
||||||
available_mimes = strings.Split(utils.UnsafeBytesToString(payload), " ")
|
|
||||||
case "OK":
|
|
||||||
case "DONE":
|
|
||||||
reading_available_mimes = false
|
|
||||||
if len(available_mimes) == 0 {
|
|
||||||
return fmt.Errorf("The clipboard is empty")
|
|
||||||
}
|
|
||||||
return fmt.Errorf("TODO: Implement processing available mimes")
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("Failed to read list of available data types in the clipboard with error: %s", metadata["status"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
esc_count := 0
|
|
||||||
lp.OnKeyEvent = func(event *loop.KeyEvent) error {
|
|
||||||
if event.MatchesPressOrRepeat("ctrl+c") || event.MatchesPressOrRepeat("esc") {
|
|
||||||
event.Handled = true
|
|
||||||
esc_count++
|
|
||||||
if esc_count < 2 {
|
|
||||||
key := "Esc"
|
|
||||||
if event.MatchesPressOrRepeat("ctrl+c") {
|
|
||||||
key = "Ctrl+C"
|
|
||||||
}
|
|
||||||
lp.QueueWriteString(fmt.Sprintf("Waiting for response from terminal, press %s again to abort. This could cause garbage to be spewed to the screen.\r\n", key))
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("Aborted by user!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err = lp.Run()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ds := lp.DeathSignalName()
|
|
||||||
if ds != "" {
|
|
||||||
fmt.Println("Killed by signal: ", ds)
|
|
||||||
lp.KillIfSignalled()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func run_set_loop(opts *Options, args []string) (err error) {
|
|
||||||
return fmt.Errorf("TODO: Implement me")
|
|
||||||
}
|
|
||||||
|
|
||||||
func run_mime_loop(opts *Options, args []string) error {
|
|
||||||
if opts.GetClipboard {
|
|
||||||
return run_get_loop(opts, args)
|
|
||||||
}
|
|
||||||
return run_set_loop(opts, args)
|
|
||||||
}
|
|
||||||
376
tools/cmd/clipboard/read.go
Normal file
376
tools/cmd/clipboard/read.go
Normal file
@ -0,0 +1,376 @@
|
|||||||
|
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
package clipboard
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/gif"
|
||||||
|
"image/jpeg"
|
||||||
|
"image/png"
|
||||||
|
"mime"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"kitty/tools/tty"
|
||||||
|
"kitty/tools/tui/loop"
|
||||||
|
"kitty/tools/utils"
|
||||||
|
|
||||||
|
"golang.org/x/image/bmp"
|
||||||
|
"golang.org/x/image/tiff"
|
||||||
|
_ "golang.org/x/image/webp"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = fmt.Print
|
||||||
|
var cwd string
|
||||||
|
|
||||||
|
const OSC_NUMBER = "5522"
|
||||||
|
|
||||||
|
var decodable_image_types = map[string]bool{
|
||||||
|
"image/jpeg": true, "image/png": true, "image/bmp": true, "image/tiff": true, "image/webp": true, "image/gif": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
var encodable_image_types = map[string]bool{
|
||||||
|
"image/jpeg": true, "image/png": true, "image/bmp": true, "image/tiff": true, "image/gif": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
type Output struct {
|
||||||
|
arg string
|
||||||
|
ext string
|
||||||
|
arg_is_stream bool
|
||||||
|
mime_type string
|
||||||
|
remote_mime_type string
|
||||||
|
image_needs_conversion bool
|
||||||
|
is_stream bool
|
||||||
|
dest_is_tty bool
|
||||||
|
dest *os.File
|
||||||
|
err error
|
||||||
|
started bool
|
||||||
|
all_data_received bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Output) cleanup() {
|
||||||
|
if self.dest != nil {
|
||||||
|
self.dest.Close()
|
||||||
|
if !self.is_stream {
|
||||||
|
os.Remove(self.dest.Name())
|
||||||
|
}
|
||||||
|
self.dest = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Output) add_data(data []byte) {
|
||||||
|
if self.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if self.dest == nil {
|
||||||
|
if !self.image_needs_conversion && self.arg_is_stream {
|
||||||
|
self.is_stream = true
|
||||||
|
self.dest = os.Stdout
|
||||||
|
if self.arg == "/dev/stderr" {
|
||||||
|
self.dest = os.Stderr
|
||||||
|
}
|
||||||
|
self.dest_is_tty = tty.IsTerminal(self.dest.Fd())
|
||||||
|
} else {
|
||||||
|
d := cwd
|
||||||
|
if strings.ContainsRune(self.arg, os.PathSeparator) && !self.arg_is_stream {
|
||||||
|
d = filepath.Dir(self.arg)
|
||||||
|
}
|
||||||
|
f, err := os.CreateTemp(d, "."+filepath.Base(self.arg))
|
||||||
|
if err != nil {
|
||||||
|
self.err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.dest = f
|
||||||
|
}
|
||||||
|
self.started = true
|
||||||
|
}
|
||||||
|
if self.dest_is_tty {
|
||||||
|
data = bytes.ReplaceAll(data, utils.UnsafeStringToBytes("\n"), utils.UnsafeStringToBytes("\r\n"))
|
||||||
|
}
|
||||||
|
_, self.err = self.dest.Write(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Output) write_image(img image.Image) (err error) {
|
||||||
|
var output *os.File
|
||||||
|
if self.arg_is_stream {
|
||||||
|
output = os.Stdout
|
||||||
|
if self.arg == "/dev/stderr" {
|
||||||
|
output = os.Stderr
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
output, err = os.Create(self.arg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
output.Close()
|
||||||
|
if err != nil && !self.arg_is_stream {
|
||||||
|
os.Remove(output.Name())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
switch self.mime_type {
|
||||||
|
case "image/png":
|
||||||
|
return png.Encode(output, img)
|
||||||
|
case "image/jpeg":
|
||||||
|
return jpeg.Encode(output, img, nil)
|
||||||
|
case "image/bmp":
|
||||||
|
return bmp.Encode(output, img)
|
||||||
|
case "image/gif":
|
||||||
|
return gif.Encode(output, img, nil)
|
||||||
|
case "image/tiff":
|
||||||
|
return tiff.Encode(output, img, nil)
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("Unsupported output image MIME type %s", self.mime_type)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Output) commit() {
|
||||||
|
if self.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if self.image_needs_conversion {
|
||||||
|
self.dest.Seek(0, os.SEEK_SET)
|
||||||
|
img, _, err := image.Decode(self.dest)
|
||||||
|
self.dest.Close()
|
||||||
|
os.Remove(self.dest.Name())
|
||||||
|
if err == nil {
|
||||||
|
err = self.write_image(img)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
self.err = fmt.Errorf("Failed to encode image data to %s with error: %w", self.mime_type, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.dest.Close()
|
||||||
|
if !self.is_stream {
|
||||||
|
self.err = os.Rename(self.dest.Name(), self.arg)
|
||||||
|
if self.err != nil {
|
||||||
|
os.Remove(self.dest.Name())
|
||||||
|
self.err = fmt.Errorf("Failed to rename temporary file used for downloading to destination: %s with error: %w", self.arg, self.err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.dest = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Output) assign_mime_type(available_mimes []string) (err error) {
|
||||||
|
if utils.Contains(available_mimes, self.mime_type) {
|
||||||
|
self.remote_mime_type = self.mime_type
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if self.mime_type == "." {
|
||||||
|
self.remote_mime_type = "."
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if encodable_image_types[self.mime_type] {
|
||||||
|
for _, mt := range available_mimes {
|
||||||
|
if decodable_image_types[mt] {
|
||||||
|
self.remote_mime_type = mt
|
||||||
|
self.image_needs_conversion = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("The MIME type %s for %s not available on the clipboard", self.mime_type, self.arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func encode(metadata map[string]string, payload string) string {
|
||||||
|
ans := strings.Builder{}
|
||||||
|
ans.Grow(2048)
|
||||||
|
ans.WriteString("\x1b]")
|
||||||
|
ans.WriteString(OSC_NUMBER)
|
||||||
|
ans.WriteString(";")
|
||||||
|
for k, v := range metadata {
|
||||||
|
if !strings.HasSuffix(ans.String(), ";") {
|
||||||
|
ans.WriteString(":")
|
||||||
|
}
|
||||||
|
ans.WriteString(k)
|
||||||
|
ans.WriteString("=")
|
||||||
|
ans.WriteString(v)
|
||||||
|
}
|
||||||
|
if len(payload) > 0 {
|
||||||
|
ans.WriteString(";")
|
||||||
|
ans.WriteString(base64.StdEncoding.EncodeToString(utils.UnsafeStringToBytes(payload)))
|
||||||
|
}
|
||||||
|
ans.WriteString("\x1b\\")
|
||||||
|
return ans.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func run_get_loop(opts *Options, args []string) (err error) {
|
||||||
|
lp, err := loop.New(loop.NoAlternateScreen, loop.NoRestoreColors, loop.NoMouseTracking)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var available_mimes []string
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
var getting_data_for string
|
||||||
|
requested_mimes := make(map[string]*Output)
|
||||||
|
reading_available_mimes := true
|
||||||
|
outputs := make([]*Output, len(args))
|
||||||
|
|
||||||
|
for i, arg := range args {
|
||||||
|
outputs[i] = &Output{arg: arg, arg_is_stream: arg == "/dev/stdout" || arg == "/dev/stderr", ext: filepath.Ext(arg)}
|
||||||
|
if len(opts.Mime) > i {
|
||||||
|
outputs[i].mime_type = opts.Mime[i]
|
||||||
|
} else {
|
||||||
|
if outputs[i].arg_is_stream {
|
||||||
|
outputs[i].mime_type = "text/plain"
|
||||||
|
} else {
|
||||||
|
outputs[i].mime_type = mime.TypeByExtension(outputs[i].ext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if outputs[i].mime_type == "" {
|
||||||
|
return fmt.Errorf("Could not detect the MIME type for: %s use --mime to specify it manually", arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
for _, o := range outputs {
|
||||||
|
if o.dest != nil {
|
||||||
|
o.cleanup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
lp.OnInitialize = func() (string, error) {
|
||||||
|
lp.QueueWriteString(encode(map[string]string{"type": "read"}, "."))
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
lp.OnEscapeCode = func(etype loop.EscapeCodeType, data []byte) (err error) {
|
||||||
|
if etype != loop.OSC || !bytes.HasPrefix(data, utils.UnsafeStringToBytes(OSC_NUMBER+";")) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
parts := bytes.SplitN(data, utils.UnsafeStringToBytes(";"), 3)
|
||||||
|
metadata := make(map[string]string)
|
||||||
|
var payload []byte
|
||||||
|
if len(parts) > 2 && len(parts[2]) > 0 {
|
||||||
|
payload, err = base64.StdEncoding.DecodeString(utils.UnsafeBytesToString(parts[2]))
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Received OSC %s packet from terminal with invalid base64 encoded payload", OSC_NUMBER)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(parts) > 1 {
|
||||||
|
for _, record := range bytes.Split(parts[1], utils.UnsafeStringToBytes(":")) {
|
||||||
|
rp := bytes.SplitN(record, utils.UnsafeStringToBytes("="), 2)
|
||||||
|
v := ""
|
||||||
|
if len(rp) == 2 {
|
||||||
|
v = string(rp[1])
|
||||||
|
}
|
||||||
|
metadata[string(rp[0])] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if reading_available_mimes {
|
||||||
|
switch metadata["status"] {
|
||||||
|
case "DATA":
|
||||||
|
available_mimes = strings.Split(utils.UnsafeBytesToString(payload), " ")
|
||||||
|
case "OK":
|
||||||
|
case "DONE":
|
||||||
|
reading_available_mimes = false
|
||||||
|
if len(available_mimes) == 0 {
|
||||||
|
return fmt.Errorf("The clipboard is empty")
|
||||||
|
}
|
||||||
|
for _, o := range outputs {
|
||||||
|
err = o.assign_mime_type(available_mimes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
requested_mimes[o.remote_mime_type] = o
|
||||||
|
if o.remote_mime_type == "." {
|
||||||
|
o.started = true
|
||||||
|
o.add_data(utils.UnsafeStringToBytes(strings.Join(available_mimes, "\n")))
|
||||||
|
o.all_data_received = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lp.QueueWriteString(encode(map[string]string{"type": "read"}, strings.Join(utils.Keys(requested_mimes), " ")))
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Failed to read list of available data types in the clipboard with error: %s", metadata["status"])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch metadata["status"] {
|
||||||
|
case "DATA":
|
||||||
|
current_mime := metadata["mime"]
|
||||||
|
o := requested_mimes[current_mime]
|
||||||
|
if o != nil {
|
||||||
|
if getting_data_for != current_mime {
|
||||||
|
if prev := requested_mimes[getting_data_for]; prev != nil && !prev.all_data_received {
|
||||||
|
prev.all_data_received = true
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
prev.commit()
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
}
|
||||||
|
getting_data_for = current_mime
|
||||||
|
}
|
||||||
|
if !o.all_data_received {
|
||||||
|
o.add_data(payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "OK":
|
||||||
|
case "DONE":
|
||||||
|
if prev := requested_mimes[getting_data_for]; getting_data_for != "" && prev != nil && !prev.all_data_received {
|
||||||
|
prev.all_data_received = true
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
prev.commit()
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
getting_data_for = ""
|
||||||
|
}
|
||||||
|
lp.Quit(0)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Failed to read data from the clipboard with error: %s", metadata["status"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
esc_count := 0
|
||||||
|
lp.OnKeyEvent = func(event *loop.KeyEvent) error {
|
||||||
|
if event.MatchesPressOrRepeat("ctrl+c") || event.MatchesPressOrRepeat("esc") {
|
||||||
|
event.Handled = true
|
||||||
|
esc_count++
|
||||||
|
if esc_count < 2 {
|
||||||
|
key := "Esc"
|
||||||
|
if event.MatchesPressOrRepeat("ctrl+c") {
|
||||||
|
key = "Ctrl+C"
|
||||||
|
}
|
||||||
|
lp.QueueWriteString(fmt.Sprintf("Waiting for response from terminal, press %s again to abort. This could cause garbage to be spewed to the screen.\r\n", key))
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("Aborted by user!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = lp.Run()
|
||||||
|
wg.Wait()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ds := lp.DeathSignalName()
|
||||||
|
if ds != "" {
|
||||||
|
fmt.Println("Killed by signal: ", ds)
|
||||||
|
lp.KillIfSignalled()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, o := range outputs {
|
||||||
|
if o.err != nil {
|
||||||
|
err = fmt.Errorf("Failed to get %s with error: %w", o.arg, o.err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !o.started {
|
||||||
|
err = fmt.Errorf("No data for %s with MIME type: %s", o.arg, o.mime_type)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
13
tools/cmd/clipboard/write.go
Normal file
13
tools/cmd/clipboard/write.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
package clipboard
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = fmt.Print
|
||||||
|
|
||||||
|
func run_set_loop(opts *Options, args []string) (err error) {
|
||||||
|
return fmt.Errorf("TODO: Implement me")
|
||||||
|
}
|
||||||
@ -73,3 +73,28 @@ func Min[T constraints.Ordered](a T, items ...T) (ans T) {
|
|||||||
}
|
}
|
||||||
return ans
|
return ans
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Index[T comparable](haystack []T, needle T) int {
|
||||||
|
for i, x := range haystack {
|
||||||
|
if x == needle {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func Contains[T comparable](haystack []T, needle T) bool {
|
||||||
|
return Index(haystack, needle) > -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keys returns the keys of the map m.
|
||||||
|
// The keys will be an indeterminate order.
|
||||||
|
func Keys[M ~map[K]V, K comparable, V any](m M) []K {
|
||||||
|
r := make([]K, len(m))
|
||||||
|
i := 0
|
||||||
|
for k := range m {
|
||||||
|
r[i] = k
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user