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
|
import sys
|
||||||
from typing import List, Optional
|
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.handler import Handler
|
||||||
from ..tui.loop import Loop, MouseEvent
|
from ..tui.loop import Loop, MouseEvent
|
||||||
from ..tui.operations import MouseTracking
|
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):
|
class Mouse(Handler):
|
||||||
mouse_tracking = MouseTracking.full
|
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>
|
# License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
import sys
|
||||||
from typing import List
|
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'''
|
OPTIONS = r'''
|
||||||
--key-mode -m
|
--key-mode -m
|
||||||
default=normal
|
default=normal
|
||||||
@ -66,17 +19,7 @@ usage = ''
|
|||||||
|
|
||||||
|
|
||||||
def main(args: List[str]) -> None:
|
def main(args: List[str]) -> None:
|
||||||
cli_opts, items = parse_args(args[1:], OPTIONS, '', help_text, 'kitty +kitten show_key', result_class=ShowKeyCLIOptions)
|
raise SystemExit('This should be reun as kitten show_key')
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
@ -86,3 +29,4 @@ elif __name__ == '__doc__':
|
|||||||
cd['usage'] = usage
|
cd['usage'] = usage
|
||||||
cd['options'] = OPTIONS
|
cd['options'] = OPTIONS
|
||||||
cd['help_text'] = help_text
|
cd['help_text'] = help_text
|
||||||
|
cd['short_desc'] = help_text
|
||||||
|
|||||||
@ -24,7 +24,7 @@ exec_kitty() {
|
|||||||
|
|
||||||
|
|
||||||
is_wrapped_kitten() {
|
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" ] && {
|
[ -n "$1" ] && {
|
||||||
case " $wrapped_kittens " in
|
case " $wrapped_kittens " in
|
||||||
*" $1 "*) printf "%s" "$1" ;;
|
*" $1 "*) printf "%s" "$1" ;;
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import (
|
|||||||
"kitty/kittens/hints"
|
"kitty/kittens/hints"
|
||||||
"kitty/kittens/hyperlinked_grep"
|
"kitty/kittens/hyperlinked_grep"
|
||||||
"kitty/kittens/icat"
|
"kitty/kittens/icat"
|
||||||
|
"kitty/kittens/show_key"
|
||||||
"kitty/kittens/ssh"
|
"kitty/kittens/ssh"
|
||||||
"kitty/kittens/themes"
|
"kitty/kittens/themes"
|
||||||
"kitty/kittens/unicode_input"
|
"kitty/kittens/unicode_input"
|
||||||
@ -41,6 +42,8 @@ func KittyToolEntryPoints(root *cli.Command) {
|
|||||||
ssh.EntryPoint(root)
|
ssh.EntryPoint(root)
|
||||||
// unicode_input
|
// unicode_input
|
||||||
unicode_input.EntryPoint(root)
|
unicode_input.EntryPoint(root)
|
||||||
|
// show_key
|
||||||
|
show_key.EntryPoint(root)
|
||||||
// hyperlinked_grep
|
// hyperlinked_grep
|
||||||
hyperlinked_grep.EntryPoint(root)
|
hyperlinked_grep.EntryPoint(root)
|
||||||
// ask
|
// ask
|
||||||
|
|||||||
@ -105,6 +105,9 @@ type KeyEvent struct {
|
|||||||
AlternateKey string
|
AlternateKey string
|
||||||
Text string
|
Text string
|
||||||
Handled bool
|
Handled bool
|
||||||
|
|
||||||
|
// The CSI string this key event was decoded from. Empty if not decoded from CSI.
|
||||||
|
CSI string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *KeyEvent) String() string {
|
func (self *KeyEvent) String() string {
|
||||||
@ -133,6 +136,7 @@ func KeyEventFromCSI(csi string) *KeyEvent {
|
|||||||
if len(csi) == 0 {
|
if len(csi) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
orig_csi := csi
|
||||||
last_char := csi[len(csi)-1:]
|
last_char := csi[len(csi)-1:]
|
||||||
if !strings.Contains("u~ABCDEHFPQRS", last_char) || (last_char == "~" && (csi == "200~" || csi == "201~")) {
|
if !strings.Contains("u~ABCDEHFPQRS", last_char) || (last_char == "~" && (csi == "200~" || csi == "201~")) {
|
||||||
return nil
|
return nil
|
||||||
@ -165,7 +169,7 @@ func KeyEventFromCSI(csi string) *KeyEvent {
|
|||||||
if len(sections) > 2 {
|
if len(sections) > 2 {
|
||||||
third_section = get_sub_sections(sections[2], 0)
|
third_section = get_sub_sections(sections[2], 0)
|
||||||
}
|
}
|
||||||
var ans = KeyEvent{Type: PRESS}
|
var ans = KeyEvent{Type: PRESS, CSI: orig_csi}
|
||||||
var keynum int
|
var keynum int
|
||||||
if val, ok := letter_trailer_to_csi_number_map[last_char]; ok {
|
if val, ok := letter_trailer_to_csi_number_map[last_char]; ok {
|
||||||
keynum = val
|
keynum = val
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user