Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature request: minimal cli for remote control #1646

tammersaleh opened this issue May 25, 2019 · 5 comments

Feature request: minimal cli for remote control #1646

tammersaleh opened this issue May 25, 2019 · 5 comments


Copy link

I use Kitty on my laptop, and I often ssh into other servers. In order to make use of the remote-control features, I need to install all of Kitty on those instances (including python, etc). I'd love to have a tiny CLI that I can install instead, which doesn't have the Kitty terminal features, but only knows how to send remote-control commands. Ideally, this CLI would be statically compiled for easy installation.

Alternatively, could you publish the remote-control protocol so we might be able to make such a CLI ourselves?

Copy link

I'm not particularly interested in creating a standalone client, but I
am definitely willing to document the protocol.

Copy link

daniel-pfeiffer commented Jun 11, 2019

I have already created such a beast in pure Shell (bash or busybox sh – not too heavy on advanced features, so it could be ported to dash). Currently I can't grab the response, and awk (available even on embedded platforms) might not be enough to parse it, but the sending part works. There is a simple little language, which creates the cumbersome json-message and echoes it to kitty. The args are any of key, key+, key-, key.literal, key:string, key{, }
which map to null, true, false, the exact literal, the escaped string and a nested json object:

kitty_() {
    [ $# = 0 ] && { echo usage: kitty_ cmd '[key-value ...]
    Where key-value is one of: key key+ key- key.literal key:string key{ }
    for: null, true, false, exact literal, escaped string, nested json object' >&2; return 1; }
    local noresp=+ stty
#    [ "x$1" = x-r ] && { noresp=-; shift; trap "stty $(stty -g); trap - RETURN INT" RETURN INT; stty -echo; }

    local cmd=$1

    [ $# = 0 ] || set payload\{ "$@" \} # wrap into outer parts of json msg
    set cmd:$cmd version.'[0, 14, 1]' "$@" no_response$noresp

    local json sep='' arg key val
    for arg; do
        key=${arg%%[^a-z0-9_]*}     # can't do this in case, e.g. *.* wrongly grabs key:a.b
        val=${arg#$key}             # cut off rest
        case $val in
            '') val=null;;
            +) val=true;;
            -) val=false;;
            .*) val=${val#.};;
            :*) val=${val#:}; val=${val//\\/'\\\\'}; val=\"${val//\"/\\\"}\";; # in dash redo last 2 in sed
            \{) json="$json$sep\"$key\": {"; sep=''; continue;;
            \}) json="$json}"; sep=', '; continue;;
            *) echo "wrong key-value '$arg'" >&2; kitty_; return;;
        json="$json$sep\"$key\": $val"
        sep=', '
    cmd=-en; [ -z "$kitty_debug" ] || cmd=-E
    echo $cmd "\eP@kitty-cmd{$json}\e\\"
#    [ $noresp = - ] &&
#   awk 'BEGIN { RS="\33\\\\" }
#   { sub( /^\33P@kitty-cmd{"ok": ?(false|true), "data": /, "AA", $0 ); print; exit }' </dev/tty
#    :

This can be used as

ktitle() {
    kitty_ set-window-title title:"$1" match temporary+


# kbgcolor 4f8f00
kbgcolor() {
    kitty_ set-colors title:"background=#$1" match_window match_tab all- configured- colors\{ background.$(( 0x$1 )) \} reset-

I even have a poor man's scp which types a file into another window. While this seems cumbersome at first, it's great for following you across chained ssh to directly unreachable hosts, sudo etc. Usage is kcp file target-window-id, or - for stdin. It switches you to the receiving window (as a safeguard to not send MBs to wrong window). There it already typed in the receive command for you, which you complete with a filename or pipe to another command. It encodes the content, so as to not send nasties like ^C or ^D. Base85 would be the best encoding, but it's not widely available, so use base64:

kcp() {
    [ "$2" -gt 0 ] 2>&- || { echo usage: kcp '{infile|-}' target-window-id >&2; return 1; }
    local id="id:$2" st1 st2
    st1=$(kitty_ send-text match:$id is_binary- match_tab- text:X)
    st2=${st1#*X} # reuse cached message, varying only the sent string
    echo -n "${st1}kcp_receive ${KITTY_WINDOW_ID:-0} $st2"
    kitty_ focus-window match:$id
    read -p "Go & start kcp_receive on $id!  Come back & hit return to send / ^C to cancel " </dev/tty
    base64 -w 1023 "$1" |
        while read; do echo -n "$st1$REPLY\n$st2"; done
    echo -n "$st1\\\\4$st2"

kcp_receive() {
    [ "$1" -ge 0 ] 2>&- || { echo usage: kcp_receive origin-window-id '[outfile]' >&2; return 1; }
    local stty=$(stty -g)
    trap "stty $stty; trap - INT QUIT TERM" INT QUIT TERM # only bash has RETURN
    stty -echo
    local id
    [ "$1" = 0 ] || id="id:$1"
    echo "Go back to kcp${id+ on $id}!  There hit return to send / ^C to cancel" >&2
    [ "$id" ] && kitty_ focus-window match:"$id" >/dev/tty
    if [ $# -gt 1 ]; then
        base64 -d > "$2"
        base64 -d
    stty $stty

Copy link

daniel-pfeiffer commented Jun 11, 2019

That didn't answer the question about the protocol. The part of the protocol we need to care about, is the inner payload json object. This reflects all the possible options of a given command. Sadly on this level they are not optional. That means as new options appear (and the version gets bumped on line 9 of kitty_) the following may need to be repeated.

There are 2 ways of sniffing and roughly transforming what a kitty @ command does. Either you run it through strace with long strings:

strace -s 9999 -e trace=write   kitty @ send-text aha

Then you pipe what kitty writes through this command.

# mostly transform STRACE STRING to kitty_ syntax
sed -E 's/\\"([a-z][a-z0-9_]*)\\": /\1\cA/g;
        s/\cA\{/\\{ /g;

Or, if you have Emacs M-x shell, where what kitty @ writes just appears as output (without the above leaning toothpick syndrome of escaped doublequotes), use this:

# mostly transform OUTPUT to kitty_ syntax
sed -E 's/"([a-z][a-z0-9_]*)": /\1\cA/g;
        s/\cA\{/\\{ /g;

Rather than my few functions with hardwired options, one could of course write more complex functions with getopt, which behave like the originals. I had no need for such luxury.

Copy link

There is no need for them not be optional, I haven't really designed the
current protocol with an eye towards interoperability, it can be easily
modified for that goal.

Copy link

Note that I have not completed the documentation for individual commands, contributions are welcome!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
None yet
None yet

No branches or pull requests

3 participants