show_key kitten: In kitty mode show the actual bytes sent by the terminal rather than a re-encoding of the parsed key event
Also port the kitten to Go
This commit is contained in:
parent
7b6d11fd1e
commit
019359b219
@ -4,11 +4,33 @@
|
||||
import sys
|
||||
from typing import List, Optional
|
||||
|
||||
from ..show_key.kitty_mode import format_mods
|
||||
from kitty.key_encoding import ALT, CAPS_LOCK, CTRL, HYPER, META, NUM_LOCK, SHIFT, SUPER
|
||||
|
||||
from ..tui.handler import Handler
|
||||
from ..tui.loop import Loop, MouseEvent
|
||||
from ..tui.operations import MouseTracking
|
||||
|
||||
mod_names = {
|
||||
SHIFT: 'Shift',
|
||||
ALT: 'Alt',
|
||||
CTRL: 'Ctrl',
|
||||
SUPER: 'Super',
|
||||
HYPER: 'Hyper',
|
||||
META: 'Meta',
|
||||
NUM_LOCK: 'NumLock',
|
||||
CAPS_LOCK: 'CapsLock',
|
||||
}
|
||||
|
||||
|
||||
def format_mods(mods: int) -> str:
|
||||
if not mods:
|
||||
return ''
|
||||
lmods = []
|
||||
for m, name in mod_names.items():
|
||||
if mods & m:
|
||||
lmods.append(name)
|
||||
return '+'.join(lmods)
|
||||
|
||||
|
||||
class Mouse(Handler):
|
||||
mouse_tracking = MouseTracking.full
|
||||
|
||||
78
kittens/show_key/kitty.go
Normal file
78
kittens/show_key/kitty.go
Normal file
@ -0,0 +1,78 @@
|
||||
// License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||
|
||||
package show_key
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"kitty/tools/cli/markup"
|
||||
"kitty/tools/tui/loop"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
func csi(csi string) string {
|
||||
return "CSI " + strings.NewReplacer(":", " : ", ";", " ; ").Replace(csi[:len(csi)-1]) + " " + csi[len(csi)-1:]
|
||||
}
|
||||
|
||||
func run_kitty_loop(opts *Options) (err error) {
|
||||
lp, err := loop.New(loop.FullKeyboardProtocol)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx := markup.New(true)
|
||||
|
||||
lp.OnInitialize = func() (string, error) {
|
||||
lp.SetCursorVisible(false)
|
||||
lp.SetWindowTitle("kitty extended keyboard protocol demo")
|
||||
lp.Println("Press any keys - Ctrl+C or Ctrl+D will terminate")
|
||||
return "", nil
|
||||
}
|
||||
|
||||
lp.OnKeyEvent = func(e *loop.KeyEvent) (err error) {
|
||||
e.Handled = true
|
||||
if e.MatchesPressOrRepeat("ctrl+c") || e.MatchesPressOrRepeat("ctrl+d") {
|
||||
lp.Quit(0)
|
||||
return
|
||||
}
|
||||
mods := e.Mods.String()
|
||||
if mods != "" {
|
||||
mods += "+"
|
||||
}
|
||||
etype := e.Type.String()
|
||||
key := e.Key
|
||||
if key == " " {
|
||||
key = "space"
|
||||
}
|
||||
key = mods + key
|
||||
lp.Printf("%s %s %s\r\n", ctx.Green(key), ctx.Yellow(etype), e.Text)
|
||||
lp.Println(ctx.Cyan(csi(e.CSI)))
|
||||
if e.AlternateKey != "" || e.ShiftedKey != "" {
|
||||
if e.ShiftedKey != "" {
|
||||
lp.QueueWriteString(ctx.Dim("Shifted key: "))
|
||||
lp.QueueWriteString(e.ShiftedKey + " ")
|
||||
}
|
||||
if e.AlternateKey != "" {
|
||||
lp.QueueWriteString(ctx.Dim("Alternate key: "))
|
||||
lp.QueueWriteString(e.AlternateKey + " ")
|
||||
}
|
||||
lp.Println()
|
||||
}
|
||||
lp.Println()
|
||||
return
|
||||
}
|
||||
|
||||
err = lp.Run()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ds := lp.DeathSignalName()
|
||||
if ds != "" {
|
||||
fmt.Println("Killed by signal: ", ds)
|
||||
lp.KillIfSignalled()
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@ -1,79 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from kittens.tui.handler import Handler
|
||||
from kittens.tui.loop import Loop
|
||||
from kitty.key_encoding import ALT, CAPS_LOCK, CTRL, HYPER, META, NUM_LOCK, PRESS, RELEASE, REPEAT, SHIFT, SUPER, KeyEvent, encode_key_event
|
||||
|
||||
mod_names = {
|
||||
SHIFT: 'Shift',
|
||||
ALT: 'Alt',
|
||||
CTRL: 'Ctrl',
|
||||
SUPER: 'Super',
|
||||
HYPER: 'Hyper',
|
||||
META: 'Meta',
|
||||
NUM_LOCK: 'NumLock',
|
||||
CAPS_LOCK: 'CapsLock',
|
||||
}
|
||||
|
||||
|
||||
def format_mods(mods: int) -> str:
|
||||
if not mods:
|
||||
return ''
|
||||
lmods = []
|
||||
for m, name in mod_names.items():
|
||||
if mods & m:
|
||||
lmods.append(name)
|
||||
return '+'.join(lmods)
|
||||
|
||||
|
||||
class KeysHandler(Handler):
|
||||
|
||||
def initialize(self) -> None:
|
||||
self.cmd.set_window_title('kitty extended keyboard protocol demo')
|
||||
self.cmd.set_cursor_visible(False)
|
||||
self.print('Press any keys - Ctrl+C or Ctrl+D will terminate')
|
||||
|
||||
def on_key_event(self, key_event: KeyEvent, in_bracketed_paste: bool = False) -> None:
|
||||
etype = {
|
||||
PRESS: 'PRESS',
|
||||
REPEAT: 'REPEAT',
|
||||
RELEASE: 'RELEASE'
|
||||
}[key_event.type]
|
||||
mods = format_mods(key_event.mods)
|
||||
if mods:
|
||||
mods += '+'
|
||||
kk = key_event.key
|
||||
if kk == ' ':
|
||||
kk = 'SPACE'
|
||||
key = f'{mods}{kk} '
|
||||
self.cmd.colored(key, 'green')
|
||||
self.cmd.colored(etype + ' ', 'yellow')
|
||||
self.cmd.styled(key_event.text, italic=True)
|
||||
self.print()
|
||||
rep = f'CSI {encode_key_event(key_event)[2:]}'
|
||||
rep = rep.replace(';', ' ; ').replace(':', ' : ')[:-1] + ' ' + rep[-1]
|
||||
self.cmd.styled(rep, fg='magenta')
|
||||
if (key_event.shifted_key or key_event.alternate_key):
|
||||
self.print()
|
||||
if key_event.shifted_key:
|
||||
self.cmd.colored('Shifted key: ', 'gray')
|
||||
self.print(key_event.shifted_key + ' ', end='')
|
||||
if key_event.alternate_key:
|
||||
self.cmd.colored('Alternate key: ', 'gray')
|
||||
self.print(key_event.alternate_key + ' ', end='')
|
||||
self.print()
|
||||
self.print()
|
||||
|
||||
def on_interrupt(self) -> None:
|
||||
self.quit_loop(0)
|
||||
|
||||
def on_eot(self) -> None:
|
||||
self.quit_loop(0)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
loop = Loop()
|
||||
handler = KeysHandler()
|
||||
loop.loop(handler)
|
||||
raise SystemExit(loop.return_code)
|
||||
76
kittens/show_key/legacy.go
Normal file
76
kittens/show_key/legacy.go
Normal file
@ -0,0 +1,76 @@
|
||||
// License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||
|
||||
package show_key
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"kitty/tools/cli/markup"
|
||||
"kitty/tools/tty"
|
||||
"os"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
func print_key(buf []byte, ctx *markup.Context) {
|
||||
const ctrl_keys = "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
|
||||
unix := ""
|
||||
send_text := ""
|
||||
for _, ch := range buf {
|
||||
switch {
|
||||
case int(ch) < len(ctrl_keys):
|
||||
unix += "^" + ctrl_keys[ch:ch+1]
|
||||
case ch == 127:
|
||||
unix += "^?"
|
||||
default:
|
||||
unix += string(rune(ch))
|
||||
}
|
||||
}
|
||||
for _, ch := range string(buf) {
|
||||
q := fmt.Sprintf("%#v", string(ch))
|
||||
send_text += q[1 : len(q)-1]
|
||||
}
|
||||
os.Stdout.WriteString(unix + "\t\t")
|
||||
os.Stdout.WriteString(ctx.Yellow(send_text) + "\r\n")
|
||||
}
|
||||
|
||||
func run_legacy_loop(opts *Options) (err error) {
|
||||
term, err := tty.OpenControllingTerm(tty.SetRaw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
term.RestoreAndClose()
|
||||
}()
|
||||
if opts.KeyMode != "unchanged" {
|
||||
os.Stdout.WriteString("\x1b[?1")
|
||||
switch opts.KeyMode {
|
||||
case "normal":
|
||||
os.Stdout.WriteString("l")
|
||||
default:
|
||||
os.Stdout.WriteString("h")
|
||||
}
|
||||
defer func() {
|
||||
os.Stdout.WriteString("\x1b[?1l")
|
||||
}()
|
||||
}
|
||||
fmt.Print("Press any keys - Ctrl+D will terminate this program\r\n")
|
||||
ctx := markup.New(true)
|
||||
fmt.Print(ctx.Green("UNIX\t\tsend_text\r\n"))
|
||||
buf := make([]byte, 64)
|
||||
for {
|
||||
n, err := term.Read(buf)
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
return err
|
||||
}
|
||||
print_key(buf[:n], ctx)
|
||||
if n == 1 && buf[0] == 4 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
27
kittens/show_key/main.go
Normal file
27
kittens/show_key/main.go
Normal file
@ -0,0 +1,27 @@
|
||||
// License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||
|
||||
package show_key
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"kitty/tools/cli"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
func main(cmd *cli.Command, opts *Options, args []string) (rc int, err error) {
|
||||
if opts.KeyMode == "kitty" {
|
||||
err = run_kitty_loop(opts)
|
||||
} else {
|
||||
err = run_legacy_loop(opts)
|
||||
}
|
||||
if err != nil {
|
||||
rc = 1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func EntryPoint(parent *cli.Command) {
|
||||
create_cmd(parent, main)
|
||||
}
|
||||
@ -2,56 +2,9 @@
|
||||
# License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
|
||||
import os
|
||||
import sys
|
||||
from typing import List
|
||||
|
||||
from kittens.tui.operations import raw_mode, styled
|
||||
from kitty.cli import parse_args
|
||||
from kitty.cli_stub import ShowKeyCLIOptions
|
||||
|
||||
ctrl_keys = '@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_'
|
||||
|
||||
|
||||
def print_key(raw: bytearray) -> None:
|
||||
unix = ''
|
||||
for ch in raw:
|
||||
if ch < len(ctrl_keys):
|
||||
unix += f'^{ctrl_keys[ch]}'
|
||||
elif ch == 127:
|
||||
unix += '^?'
|
||||
else:
|
||||
unix += chr(ch)
|
||||
print(unix + '\t\t', end='')
|
||||
for ch in raw:
|
||||
x = chr(ch).encode('utf-8')
|
||||
print(styled(repr(x)[2:-1], fg='yellow'), end='')
|
||||
print(end='\r\n', flush=True)
|
||||
|
||||
|
||||
def read_keys() -> None:
|
||||
fd = sys.stdin.fileno()
|
||||
while True:
|
||||
try:
|
||||
raw = bytearray(os.read(fd, 64))
|
||||
except OSError as err:
|
||||
print(err, file=sys.stderr, flush=True)
|
||||
break
|
||||
if not raw:
|
||||
break
|
||||
print_key(raw)
|
||||
if len(raw) == 1 and raw[0] == 4:
|
||||
break
|
||||
|
||||
|
||||
def legacy_main() -> None:
|
||||
print('Press any keys - Ctrl+D will terminate this program', end='\r\n', flush=True)
|
||||
print(styled('UNIX', italic=True, fg='green'), styled('send_text', italic=True, fg='green'), sep='\t\t', end='\r\n')
|
||||
|
||||
with raw_mode():
|
||||
read_keys()
|
||||
|
||||
|
||||
OPTIONS = r'''
|
||||
--key-mode -m
|
||||
default=normal
|
||||
@ -66,17 +19,7 @@ usage = ''
|
||||
|
||||
|
||||
def main(args: List[str]) -> None:
|
||||
cli_opts, items = parse_args(args[1:], OPTIONS, '', help_text, 'kitty +kitten show_key', result_class=ShowKeyCLIOptions)
|
||||
if cli_opts.key_mode == 'kitty':
|
||||
from .kitty_mode import main as kitty_main
|
||||
return kitty_main()
|
||||
if cli_opts.key_mode != 'unchanged':
|
||||
print(end='\x1b[?1' + ('l' if cli_opts.key_mode == 'normal' else 'h'), flush=True)
|
||||
try:
|
||||
return legacy_main()
|
||||
finally:
|
||||
if cli_opts.key_mode != 'unchanged':
|
||||
print(end='\x1b[?1l', flush=True)
|
||||
raise SystemExit('This should be reun as kitten show_key')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
@ -86,3 +29,4 @@ elif __name__ == '__doc__':
|
||||
cd['usage'] = usage
|
||||
cd['options'] = OPTIONS
|
||||
cd['help_text'] = help_text
|
||||
cd['short_desc'] = help_text
|
||||
|
||||
@ -24,7 +24,7 @@ exec_kitty() {
|
||||
|
||||
|
||||
is_wrapped_kitten() {
|
||||
wrapped_kittens="clipboard icat hyperlinked_grep ask hints unicode_input ssh themes diff"
|
||||
wrapped_kittens="clipboard icat hyperlinked_grep ask hints unicode_input ssh themes diff show_key"
|
||||
[ -n "$1" ] && {
|
||||
case " $wrapped_kittens " in
|
||||
*" $1 "*) printf "%s" "$1" ;;
|
||||
|
||||
@ -11,6 +11,7 @@ import (
|
||||
"kitty/kittens/hints"
|
||||
"kitty/kittens/hyperlinked_grep"
|
||||
"kitty/kittens/icat"
|
||||
"kitty/kittens/show_key"
|
||||
"kitty/kittens/ssh"
|
||||
"kitty/kittens/themes"
|
||||
"kitty/kittens/unicode_input"
|
||||
@ -41,6 +42,8 @@ func KittyToolEntryPoints(root *cli.Command) {
|
||||
ssh.EntryPoint(root)
|
||||
// unicode_input
|
||||
unicode_input.EntryPoint(root)
|
||||
// show_key
|
||||
show_key.EntryPoint(root)
|
||||
// hyperlinked_grep
|
||||
hyperlinked_grep.EntryPoint(root)
|
||||
// ask
|
||||
|
||||
@ -105,6 +105,9 @@ type KeyEvent struct {
|
||||
AlternateKey string
|
||||
Text string
|
||||
Handled bool
|
||||
|
||||
// The CSI string this key event was decoded from. Empty if not decoded from CSI.
|
||||
CSI string
|
||||
}
|
||||
|
||||
func (self *KeyEvent) String() string {
|
||||
@ -133,6 +136,7 @@ func KeyEventFromCSI(csi string) *KeyEvent {
|
||||
if len(csi) == 0 {
|
||||
return nil
|
||||
}
|
||||
orig_csi := csi
|
||||
last_char := csi[len(csi)-1:]
|
||||
if !strings.Contains("u~ABCDEHFPQRS", last_char) || (last_char == "~" && (csi == "200~" || csi == "201~")) {
|
||||
return nil
|
||||
@ -165,7 +169,7 @@ func KeyEventFromCSI(csi string) *KeyEvent {
|
||||
if len(sections) > 2 {
|
||||
third_section = get_sub_sections(sections[2], 0)
|
||||
}
|
||||
var ans = KeyEvent{Type: PRESS}
|
||||
var ans = KeyEvent{Type: PRESS, CSI: orig_csi}
|
||||
var keynum int
|
||||
if val, ok := letter_trailer_to_csi_number_map[last_char]; ok {
|
||||
keynum = val
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user