Implement a send_text action to allow using keyboard shortcuts to send arbitrary text
Fixes #94
This commit is contained in:
@@ -28,7 +28,7 @@ from .fonts.render import set_font_family
|
|||||||
from .borders import BordersProgram
|
from .borders import BordersProgram
|
||||||
from .char_grid import cursor_shader, cell_shader
|
from .char_grid import cursor_shader, cell_shader
|
||||||
from .constants import is_key_pressed
|
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 .session import create_session
|
||||||
from .shaders import Sprites, ShaderProgram
|
from .shaders import Sprites, ShaderProgram
|
||||||
from .tabs import TabManager, SpecialWindow
|
from .tabs import TabManager, SpecialWindow
|
||||||
@@ -308,7 +308,7 @@ class Boss(Thread):
|
|||||||
return
|
return
|
||||||
if window.char_grid.scrolled_by and key not in MODIFIER_KEYS and action == GLFW_PRESS:
|
if window.char_grid.scrolled_by and key not in MODIFIER_KEYS and action == GLFW_PRESS:
|
||||||
window.scroll_end()
|
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:
|
if data:
|
||||||
window.write_to_child(data)
|
window.write_to_child(data)
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
# vim:fileencoding=utf-8
|
# vim:fileencoding=utf-8
|
||||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
import ast
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
@@ -84,16 +85,22 @@ named_keys = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def parse_key(val, keymap):
|
def parse_shortcut(sc):
|
||||||
sc, action = val.partition(' ')[::2]
|
|
||||||
action = action.strip()
|
|
||||||
sc = sc.strip()
|
|
||||||
if not sc or not action:
|
|
||||||
return
|
|
||||||
parts = sc.split('+')
|
parts = sc.split('+')
|
||||||
mods = parse_mods(parts[:-1])
|
mods = parse_mods(parts[:-1])
|
||||||
key = parts[-1].upper()
|
key = parts[-1].upper()
|
||||||
key = getattr(defines, 'GLFW_KEY_' + named_keys.get(key, key), None)
|
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:
|
if key is None:
|
||||||
safe_print(
|
safe_print(
|
||||||
'Shortcut: {} has an unknown key, ignoring'.format(val),
|
'Shortcut: {} has an unknown key, ignoring'.format(val),
|
||||||
@@ -137,6 +144,39 @@ def parse_symbol_map(val):
|
|||||||
return symbol_map
|
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):
|
def to_open_url_modifiers(val):
|
||||||
return parse_mods(val.split('+'))
|
return parse_mods(val.split('+'))
|
||||||
|
|
||||||
@@ -199,7 +239,7 @@ for a in ('active', 'inactive'):
|
|||||||
|
|
||||||
|
|
||||||
def parse_config(lines):
|
def parse_config(lines):
|
||||||
ans = {'keymap': {}, 'symbol_map': {}}
|
ans = {'keymap': {}, 'symbol_map': {}, 'send_text_map': {'kitty': {}, 'normal': {}, 'application': {}}}
|
||||||
for line in lines:
|
for line in lines:
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
if not line or line.startswith('#'):
|
if not line or line.startswith('#'):
|
||||||
@@ -213,6 +253,11 @@ def parse_config(lines):
|
|||||||
if key == 'symbol_map':
|
if key == 'symbol_map':
|
||||||
ans['symbol_map'].update(parse_symbol_map(val))
|
ans['symbol_map'].update(parse_symbol_map(val))
|
||||||
continue
|
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)
|
tm = type_map.get(key)
|
||||||
if tm is not None:
|
if tm is not None:
|
||||||
val = tm(val)
|
val = tm(val)
|
||||||
@@ -236,7 +281,7 @@ def update_dict(a, b):
|
|||||||
|
|
||||||
def merge_dicts(vals, defaults):
|
def merge_dicts(vals, defaults):
|
||||||
return {
|
return {
|
||||||
k: update_dict(v, vals.get(k, {}))
|
k: merge_dicts(v, vals.get(k, {}))
|
||||||
if isinstance(v, dict) else vals.get(k, v)
|
if isinstance(v, dict) else vals.get(k, v)
|
||||||
for k, v in defaults.items()
|
for k, v in defaults.items()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,6 +105,12 @@ def get_key_map(screen):
|
|||||||
return cursor_key_mode_map[screen.cursor_key_mode]
|
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 = {
|
valid_localized_key_names = {
|
||||||
k: getattr(defines, 'GLFW_KEY_' + k)
|
k: getattr(defines, 'GLFW_KEY_' + k)
|
||||||
for k in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
for k in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||||
@@ -195,3 +201,11 @@ def interpret_text_event(codepoint, mods, window):
|
|||||||
def get_shortcut(keymap, mods, key, scancode):
|
def get_shortcut(keymap, mods, key, scancode):
|
||||||
key = get_localized_key(key, scancode)
|
key = get_localized_key(key, scancode)
|
||||||
return keymap.get((mods & 0b1111, key))
|
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))
|
||||||
|
|||||||
@@ -231,6 +231,23 @@ map ctrl+shift+equal increase_font_size
|
|||||||
map ctrl+shift+minus decrease_font_size
|
map ctrl+shift+minus decrease_font_size
|
||||||
map ctrl+shift+backspace restore_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
|
# Symbol mapping (special font for specified unicode code points). Map the
|
||||||
# specified unicode codepoints to a particular font. Useful if you need special
|
# specified unicode codepoints to a particular font. Useful if you need special
|
||||||
# rendering for some symbols, such as for Powerline. Avoids the need for
|
# rendering for some symbols, such as for Powerline. Avoids the need for
|
||||||
|
|||||||
Reference in New Issue
Block a user