Skip to content
Tom Feist edited this page Dec 7, 2011 · 6 revisions

Irssi Keyboard Input

This file is concerned only with the fe-text version of irssi (that is, the standard tty client), and investigates the inner workings of the binding, dispatch and key handling code.

Exciting Fun Discoveries

YMMV.

Calling /bind functions from scripts

If you register a signal of the form key <key_name> where <key_name> is one of the functions listed with /bind -list, you can trigger that action by subsequent emission of the signal.

For example:

/bind meta-q /se signal_register({'key backward_history' => \
[qw/string string int/]}); signal_emit('key backward_history', '', 0, 0)

In practice, the signal only needs to be registered once (is persistent across a whole irssi process instance, and survives even /script reset)

TODO: Are the arguments important? The majority of key_* function definitions in gui-readline.c are prototyped void, but some (key_change_window(const char *data), key_completion(int erase, int backward)) are exceptions. Not sure how they'd take to being called in a horrible way like this.

Open Questions

  • Is it possible to listen for signals for complex key events by binding a key sequence to key <foo>, and then observing the key <foo> signal? Apparently not, so far.

Interestingly, /eval /bind meta-q key bacon; /bind bacon /echo bacon pressed appears to work - indicating that arbitrary key names can be used.

  • Using "key nothing" as a hook seems to work. Registered with a single string argument:
Irssi::signal_register({'key nothing' => [qw/string/]});
Irssi::signal_add_first('key nothing' => \\&sig_key_nothing);
Irssi::command("bind meta-q nothing SOMESTRING");

sub sig_key_nothing {
    my ($data) = @_;
    print "key nothing: " . Dumper($data);

}

Not perfect (still have to observe every "key nothing", rather than a single "key mykey", but definitely progress.

Details

Main files:

Associated Signals:

  • "keyboard created"
  • "keyboard destroyed"
  • "key created"
  • "key destroyed"
  • "keyinfo created"
  • "keyinfo destroyed"
  • "key <keyname>"
  • "gui key pressed"

Data Structures:

  • KEYBOARD_REC

    char *key_state; /* the ongoing key combo */
    void *gui_data; /* GUI specific data sent in "key pressed" signal */

  • KEYINFO_REC

    char *id;
    char *description;
    GSList *keys, *default_keys;

  • KEY_REC

    KEYINFO_REC *info;
    char *key;
    char *data;

High Level

The following is a high-level description of the keyboard and binding system. The GLib event-loop that irssi operates around detects keystroke input. Each key triggers a gui key pressed signal, which is then further

Setup

Keyboard Creation

gui-readline is responsible for creating and owning the KEYBOARD_REC instance. The keyboard instance is created in gui_readline_init(), setting rec->gui_data to NULL. rec->key_state isn't initialised.

Only a single keyboard instance is ever created. The creation causes the emission of a "keyboard created" signal, but this occurs before the perl script interpreter is loaded, and hence cannot be captured for use in scripts.

Once the keyboard object is created, is it populated with the default bindings, still within the gui_readline_init() function. A simple lock-style function-pair (key_configure_freeze(), key_configure_thaw()) allows the manipulation of large numbers of key entries at once without running the costly key_states_rescan() function each time.

Binding Defaults

void key_bind(const char *id, const char *description, const char *key_default, const char *data, SIGNAL_FUNC func)

is the entry-point for setting up both bindings, and the actions which bindings perform. An example of each:

key_bind("key", NULL, " ", "space", (SIGNAL_FUNC) key_combo);
key_bind("backward_character", "Move the cursor a character backward", "left", NULL, (SIGNAL_FUNC) key_backward_character);

There seem to be a few things a key can be bound to:

  • command -- fires a "key command" signal when the bound key-seq is pressed.
  • key -- alias one key to another. Seems to operate below the signal dispatch level.
  • multi -- allow multiple actions, separated by ;.
  • nothing -- Do...Nothing.

Dispatching Keypresses

sig_gui_key_pressed

sig_gui_key_pressed is the entry point to the key dispatcher, as a low-level signal handler listening for "gui key pressed" signals. It checks if keyboard entry is being redirected (for example, the password entry mode of /oper). If so, throws it off to handle_key_redirect, which we'll ignore for now.

Otherwise, it checks the value of the pressed key, and does various things depending on what it is. The interesting bits seem to be a char str[20] buffer, into which the key is "symbolically" decoded.

If it's a control character (key < 32), it's prepended with a ^, and the value + 64 is used. So 0x01 -> "^A", etc.

If key == 127, it's converted to "^?", the generally accepted sequence for backspace.

Failing both those cases, some utf-8 decoding magic happens, upon which we probably have a symbolic char representation in str.

The string is then passed to key_pressed() in keyboard.c, unless it was preceeded by an escape char, in which case it's just inserted into the input line. If the return value key_pressed() < 0, the lookup is assumed to have not matched, and the key is again inserted into the input line as a fallback.

TODO: Note about key repeat when key_pressed() == 0.

key_pressed

Lower level

things.

Clone this wiki locally