clipboard kitten: Allow using a password to avoid repeated confirmation prompts when accessing the clipboard

Fixes #8789
This commit is contained in:
Kovid Goyal
2025-07-13 14:11:07 +05:30
parent 2ac35658d9
commit 9e7c46b253
7 changed files with 218 additions and 20 deletions

View File

@@ -3,7 +3,12 @@
package clipboard
import (
"fmt"
"io"
"os"
"strconv"
"strings"
"unicode"
"github.com/kovidgoyal/kitty/tools/cli"
)
@@ -20,10 +25,47 @@ func run_mime_loop(opts *Options, args []string) (err error) {
}
func clipboard_main(cmd *cli.Command, opts *Options, args []string) (rc int, err error) {
if opts.Password != "" {
if opts.HumanName == "" {
return 1, fmt.Errorf("must specify --human-name when using a password")
}
ptype, val, found := strings.Cut(opts.Password, ":")
if !found {
return 1, fmt.Errorf("invalid password: %#v no password type specified", opts.Password)
}
switch ptype {
case "text":
opts.Password = val
case "fd":
if fd, err := strconv.Atoi(val); err == nil {
if f := os.NewFile(uintptr(fd), "password-fd"); f == nil {
return 1, fmt.Errorf("invalid file descriptor: %d", fd)
} else {
data, err := io.ReadAll(f)
f.Close()
if err != nil {
return 1, fmt.Errorf("failed to read from file descriptor: %d with error: %w", fd, err)
}
opts.Password = strings.TrimRightFunc(string(data), unicode.IsSpace)
}
} else {
return 1, fmt.Errorf("not a valid file descriptor number: %#v", val)
}
case "file":
if data, err := os.ReadFile(val); err == nil {
opts.Password = strings.TrimRightFunc(string(data), unicode.IsSpace)
} else {
return 1, fmt.Errorf("failed to read from file: %#v with error: %w", val, err)
}
}
}
if len(args) > 0 {
return 0, run_mime_loop(opts, args)
}
if opts.Password != "" || opts.HumanName != "" {
return 1, fmt.Errorf("cannot use --human-name or --password in filter mode")
}
return 0, run_plain_text_loop(opts)
}

View File

@@ -44,6 +44,18 @@ other :code:`text/*` MIME is present.
type=bool-set
Wait till the copy to clipboard is complete before exiting. Useful if running
the kitten in a dedicated, ephemeral window. Only needed in filter mode.
--password
A password to use when accessing the clipboard. If the user chooses to accept the password
future invocations of the kitten will not have a permission prompt in this tty session. Does not
work in filter mode. Must be of the form: text:actual-password or fd:integer (a file descriptor
number to read the password from) or file:path-to-file (a file from which to read the password).
Note that you must also specify a human friendly name using the :option:`--human-name` flag.
--human-name
A human friendly name to show the user when asking for permission to access the clipboard.
'''.format
help_text = '''\
Read or write to the system clipboard.

View File

@@ -329,9 +329,14 @@ func run_get_loop(opts *Options, args []string) (err error) {
if opts.UsePrimary {
basic_metadata["loc"] = "primary"
}
lp.OnInitialize = func() (string, error) {
lp.QueueWriteString(encode(basic_metadata, "."))
if opts.Password != "" {
basic_metadata["pw"] = base64.StdEncoding.EncodeToString(utils.UnsafeStringToBytes(opts.Password))
}
if opts.HumanName != "" {
basic_metadata["name"] = base64.StdEncoding.EncodeToString(utils.UnsafeStringToBytes(opts.HumanName))
}
return "", nil
}

View File

@@ -3,6 +3,7 @@
package clipboard
import (
"encoding/base64"
"errors"
"fmt"
"io"
@@ -80,6 +81,14 @@ func write_loop(inputs []*Input, opts *Options) (err error) {
if mime != "" {
ans["mime"] = mime
}
if ptype == "write" {
if opts.Password != "" {
ans["pw"] = base64.StdEncoding.EncodeToString(utils.UnsafeStringToBytes(opts.Password))
}
if opts.HumanName != "" {
ans["name"] = base64.StdEncoding.EncodeToString(utils.UnsafeStringToBytes(opts.HumanName))
}
}
return ans
}