Skip to content

Commit

Permalink
Implement a send_text action to allow using keyboard shortcuts to sen…
Browse files Browse the repository at this point in the history
…d arbitrary text

Fixes #94
  • Loading branch information
kovidgoyal committed Jul 23, 2017
1 parent 304d42d commit dd3af45
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 10 deletions.
4 changes: 2 additions & 2 deletions kitty/boss.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from .borders import BordersProgram
from .char_grid import cursor_shader, cell_shader
from .constants import is_key_pressed
from .keys import interpret_text_event, interpret_key_event, get_shortcut
from .keys import interpret_text_event, interpret_key_event, get_shortcut, get_sent_data
from .session import create_session
from .shaders import Sprites, ShaderProgram
from .tabs import TabManager, SpecialWindow
Expand Down Expand Up @@ -308,7 +308,7 @@ def on_key(self, window, key, scancode, action, mods):
return
if window.char_grid.scrolled_by and key not in MODIFIER_KEYS and action == GLFW_PRESS:
window.scroll_end()
data = interpret_key_event(key, scancode, mods, window, action)
data = get_sent_data(self.opts.send_text_map, key, scancode, mods, window, action) or interpret_key_event(key, scancode, mods, window, action)
if data:
window.write_to_child(data)

Expand Down
61 changes: 53 additions & 8 deletions kitty/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>

import ast
import json
import os
import re
Expand Down Expand Up @@ -84,16 +85,22 @@ def map_mod(m):
}


def parse_key(val, keymap):
sc, action = val.partition(' ')[::2]
action = action.strip()
sc = sc.strip()
if not sc or not action:
return
def parse_shortcut(sc):
parts = sc.split('+')
mods = parse_mods(parts[:-1])
key = parts[-1].upper()
key = getattr(defines, 'GLFW_KEY_' + named_keys.get(key, key), None)
if key is not None:
return mods, key
return None, None


def parse_key(val, keymap):
sc, action = val.partition(' ')[::2]
sc, action = sc.strip(), action.strip()
if not sc or not action:
return
mods, key = parse_shortcut(sc)
if key is None:
safe_print(
'Shortcut: {} has an unknown key, ignoring'.format(val),
Expand Down Expand Up @@ -137,6 +144,39 @@ def to_chr(x):
return symbol_map


def parse_send_text(val):
parts = val.split(' ')

def abort(msg):
safe_print(
'Send text: {} is invalid ({}), ignoring'.format(val, msg), file=sys.stderr
)
return {}

if len(parts) < 3:
return abort('Incomplete')

text = ' '.join(parts[2:])
mode, sc = parts[:2]
mods, key = parse_shortcut(sc.strip())
if key is None:
return abort('Invalid shortcut')
text = ast.literal_eval("'''" + text + "'''").encode('utf-8')
if not text:
return abort('Empty text')

if mode in ('all', '*'):
modes = parse_send_text.all_modes
else:
modes = frozenset(mode.split(',')).intersection(parse_send_text.all_modes)
if not modes:
return abort('Invalid keyboard modes')
return {mode: {(mods, key): text} for mode in modes}


parse_send_text.all_modes = frozenset({'normal', 'application', 'kitty'})


def to_open_url_modifiers(val):
return parse_mods(val.split('+'))

Expand Down Expand Up @@ -199,7 +239,7 @@ def positive_float(x):


def parse_config(lines):
ans = {'keymap': {}, 'symbol_map': {}}
ans = {'keymap': {}, 'symbol_map': {}, 'send_text_map': {'kitty': {}, 'normal': {}, 'application': {}}}
for line in lines:
line = line.strip()
if not line or line.startswith('#'):
Expand All @@ -213,6 +253,11 @@ def parse_config(lines):
if key == 'symbol_map':
ans['symbol_map'].update(parse_symbol_map(val))
continue
if key == 'send_text':
stvals = parse_send_text(val)
for k, v in ans['send_text_map'].items():
v.update(stvals.get(k, {}))
continue
tm = type_map.get(key)
if tm is not None:
val = tm(val)
Expand All @@ -236,7 +281,7 @@ def update_dict(a, b):

def merge_dicts(vals, defaults):
return {
k: update_dict(v, vals.get(k, {}))
k: merge_dicts(v, vals.get(k, {}))
if isinstance(v, dict) else vals.get(k, v)
for k, v in defaults.items()
}
Expand Down
14 changes: 14 additions & 0 deletions kitty/keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ def get_key_map(screen):
return cursor_key_mode_map[screen.cursor_key_mode]


def keyboard_mode_name(screen):
if screen.extended_keyboard:
return 'kitty'
return 'application' if screen.cursor_key_mode else 'normal'


valid_localized_key_names = {
k: getattr(defines, 'GLFW_KEY_' + k)
for k in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
Expand Down Expand Up @@ -195,3 +201,11 @@ def interpret_text_event(codepoint, mods, window):
def get_shortcut(keymap, mods, key, scancode):
key = get_localized_key(key, scancode)
return keymap.get((mods & 0b1111, key))


def get_sent_data(send_text_map, key, scancode, mods, window, action):
if action in (defines.GLFW_PRESS, defines.GLFW_REPEAT):
key = get_localized_key(key, scancode)
m = keyboard_mode_name(window.screen)
keymap = send_text_map[m]
return keymap.get((mods & 0b1111, key))
17 changes: 17 additions & 0 deletions kitty/kitty.conf
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,23 @@ map ctrl+shift+equal increase_font_size
map ctrl+shift+minus decrease_font_size
map ctrl+shift+backspace restore_font_size

# Sending arbitrary text on shortcut key presses
# You can tell kitty to send arbitrary (UTF-8) encoded text to
# the client program when pressing specified shortcut keys. For example:
# send_text all ctrl+alt+a Special text
# This will send "Special text" when you press the Ctrl+Alt+a key combination.
# The text to be sent is a python string literal so you can use escapes like
# \x1b to send control codes or \u21fb to send unicode characters (or you can
# just input the unicode characters directly as UTF-8 text). The first argument
# to send_text is the keyboard modes in which to activate the shortcut. The possible
# values are normal or application or kitty or a comma separated combination of them.
# The special keyword all means all modes. The modes normal and application refer to
# the DECCKM cursor key mode for terminals, and kitty refers to the special kitty
# extended keyboard protocol. Another example, that outputs a word and then moves the cursor
# to the start of the line (same as pressing the Home key):
# send_text normal ctrl+alt+a Word\x1b[H
# send_text application ctrl+alt+a Word\x1bOH

# Symbol mapping (special font for specified unicode code points). Map the
# specified unicode codepoints to a particular font. Useful if you need special
# rendering for some symbols, such as for Powerline. Avoids the need for
Expand Down

0 comments on commit dd3af45

Please sign in to comment.