bitchx/source/keys.c

1512 lines
40 KiB
C

/*
* keys.c: Keeps track of what happens whe you press a key.
*
* Written By Michael Sandrof
* Copyright(c) 1990 Michael Sandrof
* See the COPYRIGHT file, or do a HELP IRCII COPYRIGHT
*
* Substantial re-implementation by Jeremy Nelson
* Copyright 1998 EPIC Software Labs
* See the COPYRIGHT file, or do a HELP IRCII COPYRIGHT
*/
#include "irc.h"
static char cvsrevision[] = "$Id$";
CVS_REVISION(keys_c)
#include "struct.h"
#include "config.h"
#include "commands.h"
#include "history.h"
#include "ircaux.h"
#include "input.h"
#include "keys.h"
#include "names.h"
#include "output.h"
#include "screen.h"
#include "ircterm.h"
#include "vars.h"
#include "window.h"
#define MAIN_SOURCE
#include "modval.h"
#define KEY(meta, ch) (*keys[meta])[(unsigned char)(ch)]
static void new_key(int, unsigned char, int, int, char *);
static void snew_key(int meta, unsigned chr, char *what);
static char *display_key(char c);
static int lookup_function(const char *name, int *lf_index);
static int parse_key(const char *sequence, char *term);
#ifdef GUI
char *mouse_actions[] =
{
"RCLICK",
"STATUSRCLICK",
"NICKLISTRCLICK",
"LCLICK",
"STATUSLCLICK",
"NICKLISTLCLICK",
"MCLICK",
"STATUSMCLICK",
"NICKLISTMCLICK",
"RDBLCLICK",
"STATUSRDBLCLICK",
"NICKLISTRDBLCLICK",
"LDBLCLICK",
"STATUSLDBLCLICK",
"NICKLISTLDBLCLICK",
"MDBLCLICK",
"STATUSMDBLCLICK",
"NICKLISTMDBLCLICK"
};
#endif
/*
* Yet again we've changed how the key maps are held. This time, hopefully
* it's the second to last time, as we've made the entire things independent
* of the number of meta keymaps that are available (it can change.) The
* only thing i see left to be done is to encapsulate all this data inside
* a class so that different contexts can have different bindings sets.
* I'm sure that will come up some day. But anyhow, on to the details!
*/
/*
* The actual "stuff" that is in a keybinding is defined in keys.h.
* At the time i write this, its just an int, two pointers, and a char,
* so its 16 bytes (after padding)....
*
* typedef struct
* {
* int key_index;
* char *stuff;
* char *filename;
* char changed;
* } KeyMap;
*
* Some special notes about 'key_index'. No longer are the meta bindings
* just normal bindings -- having them hardcoded as normal bindings limited
* the ability of the user to set the number of meta states that they wanted.
* Now all the negative values for 'key_index' are reserved for those meta
* states. If something is bound to META2_CHARACTER, then 'key_index' is
* given the value -2. Otherwise, 'key_index' is given the value of the
* entry of the binding in the 'key_names' table. Note that 'key_index' is
* not strictly sorted. The first two bindings are special and are hardcoded,
* and you must not change them. Entry 0 must always be "NOTHING", and
* entry 1 must always be "SELF_INSERT". The enum that was in keys.h
* is now totally obsolete -- we no longer use the symbolic names, but instead
* always use the full string name for the binding. This makes it much
* easier to add new key bindings, as there is only one place to manage.
*
*
* Each meta map is a collection of 256 bindings...
*
* typedef KeyMap MetaMap[256];
*
* But since most of the meta maps are either sparse or empty, it makes very
* little sense to actually allocate 4k (16 * 256) for each of the maps only
* to have them unused. So instead the meta map is just 256 pointers to
* these objects, which then are allocated dynamically.
*
* typedef KeyMap *MetaMap[256]; (better)
*
* That means that the overhead for a completely empty meta map is 1k, and
* the map needs to be 75% full before it uses more memory than it would have
* otherwise. So its a reasonable win.
*
* Now we need to have the meta maps stored somehow. Originally, we used
* to just make an array of them...
*
* typedef MetaMap KeyTable[MAX_META];
*
* but then again, many of those MetaMaps are going to be totally empty.
* Why should we allocate 1k to something that isn't going to be used?
* So instead we should keep pointers to the maps and allocate them as
* necessary at runtime...
*
* typedef MetaMap *KeyTable[MAX_META]; (better)
*
* Which is what we had before. This works out fine, except, that the
* number of meta maps is hardcoded into the client at compile time.
* Wouldn't it be nice to be able to determine at runtime how many maps
* we want and be able to change them as necessary? We can do this by
* having a pointer to the set of pointers of MetaMaps...
*
* typedef MetaMap **KeyTable; (dyanmic now)
*
* And so we dynamically allocate to MetaSet enough pointers to hold the
* number of MetaMap's we want. Then any time we want to use a MetaMap,
* we allocate space for it. Then any time we want to use a binding in the
* MetaMap, we allocate space for it and set its pointer.
*
* So the final dereference for a specific key binding 'X' in meta map 'Y' is:
*
* KeyTable keys;
* keys[Y] The pointer to the 'Y'th MetaMap
* (*keys[Y]) The MetaMap itself
* (*keys[Y])[X] The pointer to the 'X'th character in the
* 'Y'th MetaMap
* (*keys[Y])[X]->key_index
* What 'Y,X' is actually bound to. This is a
* negative value for a transition to a new
* meta state Y, positive for a terminating
* binding.
*/
/* * * * * * * * * * * * * * * METAMAP MANAGEMENT * * * * * * * * * * * */
/*
* This is where all the bindings are stored
* Also we store how big it thinks it is, and how big it actually is.
*/
/* KeyMapNames: the structure of the keymap to realname array */
typedef struct
{
char * name;
KeyBinding func;
} KeyMapNames;
static KeyMapNames key_names[] =
{
/* The first two here are "magic" binds */
{ "NOTHING", NULL },
{ "SELF_INSERT", input_add_character },
{ "ALTCHARSET", insert_altcharset },
{ "AUTOREPLY", input_autoreply },
{ "AUTOREPLY_BACK", input_autoreplyback },
{ "BACKSPACE", input_backspace },
{ "BACKWARD_CHARACTER", backward_character },
{ "BACKWARD_HISTORY", backward_history },
{ "BACKWARD_WORD", input_backward_word },
{ "BEGINNING_OF_LINE", input_beginning_of_line },
{ "BLINK", insert_blink },
{ "BOLD", insert_bold },
#ifdef WANT_CDCC
{ "CDCC_PLIST", cdcc_plist },
#endif
{ "CHANNEL_CHOPS", channel_chops },
{ "CHANNEL_NONOPS", channel_nonops },
{ "CHANGE_TO_SPLIT", change_to_split },
#ifdef WANT_CHELP
{ "CHELP", do_chelp },
#endif
{ "CLEAR_SCREEN", input_clear_screen },
{ "COMMAND_COMPLETION", command_completion },
{ "CPU_SAVER", cpu_saver_on },
{ "DCC_PLIST", dcc_plist },
{ "DCC_STATS", dcc_ostats },
{ "DELETE_CHARACTER", input_delete_character },
{ "DELETE_NEXT_WORD", input_delete_next_word },
{ "DELETE_PREVIOUS_WORD", input_delete_previous_word },
{ "DELETE_TO_PREVIOUS_SPACE", input_delete_to_previous_space },
{ "END_OF_LINE", input_end_of_line },
#ifdef TRANSLATE
{ "ENTER_DIGRAPH", enter_digraph },
#endif
{ "ERASE_LINE", input_clear_line },
{ "ERASE_TO_BEG_OF_LINE", input_clear_to_bol },
{ "ERASE_TO_END_OF_LINE", input_clear_to_eol },
{ "FORWARD_CHARACTER", forward_character },
{ "FORWARD_HISTORY", forward_history },
{ "FORWARD_WORD", input_forward_word },
{ "HIGHLIGHT_OFF", highlight_off },
{ "IGNORE_NICK", ignore_last_nick },
{ "JOIN_LAST_INVITE", join_last_invite },
{ "NEW_BEGINNING_OF_LINE", new_input_beginning_of_line },
{ "NEW_SCROLL_BACKWARD", my_scrollback },
{ "NEW_SCROLL_END", my_scrollend },
{ "NEW_SCROLL_FORWARD", my_scrollforward},
{ "NEXT_WINDOW", BX_next_window },
{ "NICK_COMPLETION", nick_completion },
{ "PARSE_COMMAND", parse_text },
#ifdef GUI
{ "PASTE_TO_INPUT", paste_to_input },
#endif
{ "PREVIOUS_WINDOW", BX_previous_window },
{ "QUIT_IRC", irc_quit },
{ "QUOTE_CHARACTER", quote_char },
{ "REFRESH_INPUTLINE", refresh_inputline },
{ "REFRESH_SCREEN", (KeyBinding) refresh_screen },
{ "REFRESH_STATUS", (KeyBinding) BX_update_all_status },
{ "REVERSE", insert_reverse },
{ "SCROLL_BACKWARD", BX_scrollback_backwards },
{ "SCROLL_END", BX_scrollback_end },
{ "SCROLL_FORWARD", BX_scrollback_forwards },
{ "SCROLL_START", BX_scrollback_start },
{ "SEND_LINE", send_line },
{ "SHOVE_TO_HISTORY", shove_to_history },
#ifdef ALLOW_STOP_IRC
{ "STOP_IRC", term_pause },
#endif
{ "SWAP_LAST_WINDOW", BX_swap_last_window },
{ "SWAP_NEXT_WINDOW", BX_swap_next_window },
{ "SWAP_PREVIOUS_WINDOW", BX_swap_previous_window },
{ "SWITCH_CHANNELS", switch_channels },
#ifdef WANT_TABKEY
{ "TAB_COMPLETION", tab_completion },
#endif
{ "TAB_MSG", input_msgreply },
{ "TAB_MSG_BACK", input_msgreplyback },
{ "TOGGLE_CLOAK", toggle_cloak },
{ "TOGGLE_INSERT_MODE", toggle_insert_mode },
{ "TOGGLE_STOP_SCREEN", BX_toggle_stop_screen },
{ "TRANSPOSE_CHARACTERS", input_transpose_characters },
{ "TYPE_TEXT", type_text },
{ "UNCLEAR_SCREEN", input_unclear_screen },
{ "UNDERLINE", insert_underline },
{ "UNSTOP_ALL_WINDOWS", BX_unstop_all_windows },
{ "WHOLEFT", wholeft },
{ "WINDOW_BALANCE", window_key_balance },
{ "WINDOW_GROW_ONE", window_grow_one },
#ifdef WANT_CHELP
{ "WINDOW_HELP", w_help },
#endif
{ "WINDOW_HIDE", window_key_hide },
{ "WINDOW_KILL", window_key_kill },
{ "WINDOW_LIST", window_key_list },
{ "WINDOW_MOVE", window_key_move },
{ "WINDOW_SHRINK_ONE", window_shrink_one },
{ "WINDOW_SWAP_1", window_swap1 },
{ "WINDOW_SWAP_2", window_swap2 },
{ "WINDOW_SWAP_3", window_swap3 },
{ "WINDOW_SWAP_4", window_swap4 },
{ "WINDOW_SWAP_5", window_swap5 },
{ "WINDOW_SWAP_6", window_swap6 },
{ "WINDOW_SWAP_7", window_swap7 },
{ "WINDOW_SWAP_8", window_swap8 },
{ "WINDOW_SWAP_9", window_swap9 },
{ "WINDOW_SWAP_10", window_swap10 },
{ "YANK_FROM_CUTBUFFER", input_yank_cut_buffer },
{ "NULL", NULL }
};
#define NUMBER_OF_FUNCTIONS (sizeof(key_names) / sizeof(KeyMapNames)) - 1
/* KeyMap: the structure of the irc keymaps */
typedef struct
{
int key_index;
char * stuff;
char * filename;
char changed;
} KeyMap;
typedef KeyMap * MetaMap[256];
typedef MetaMap ** KeyTable;
static KeyTable keys = NULL;
int curr_keys_size = 0;
static int max_keys_size = 0;
#define MAX_META curr_keys_size - 1
static void delete_metamap (int i);
/*
* resize_metamap -- When we need to increase or decrease the number of
* metamaps that the system is handling, you call this function with the
* new size, and everything automagically adjusts from there. This function
* always succeeds if it returns. This function is the callback for the
* /SET META_STATES action.
*/
void resize_metamap (int new_size)
{
int old_size = curr_keys_size;
int i, j;
/*
* Sorry, just too much will break if you go lower than 5.
*/
if (new_size < 5)
{
say("You can't set META_STATES to less than 5.");
set_int_var(META_STATES_VAR, 5);
}
if (old_size == new_size)
return; /* What-EVER */
/*
* If we're growing the meta table, resize and copy the data.
*/
if (old_size < new_size)
{
/*
* Realloc and copy if necessary
*/
if (new_size > max_keys_size)
{
KeyTable new_keys;
new_keys = new_malloc(sizeof(KeyTable *) * new_size);
for (i = 0; i < old_size; i++)
new_keys[i] = keys[i];
for (i = old_size; i < new_size; i++)
new_keys[i] = NULL;
new_free((void **)&keys);
keys = new_keys;
max_keys_size = new_size;
}
curr_keys_size = new_size;
}
/*
* If we're shrinking the meta table, just garbage collect all
* the old bindings, don't actually bother resizing the table.
*/
else
{
for (i = new_size; i < old_size; i++)
delete_metamap(i);
curr_keys_size = new_size;
/*
* This is a bit tricky -- There might be meta transitions
* in other states that point to the now defunct states.
* If we leave those bindings around, then they will point
* to either meaningless, or bogus data, and either cause
* undefined behavior or a total program crash. So we walk
* all of the remaining states and garbage collect any
* meta transitions that are out of bounds.
*/
for (i = 0; i < new_size; i++)
{
if (!keys[i])
continue;
for (j = 0; j < 256; j++)
if (KEY(i, j) && (KEY(i, j)->key_index <= -new_size))
snew_key(i, j, NULL);
}
}
set_int_var(META_STATES_VAR, curr_keys_size);
}
/*
* new_metamap -- When you "touch" a metamap for the first time,
* the table for the 256 bindings in that metamap must be created, so
* you call this function to do that. You must never call this function
* unless the metamap does not exist, or it will panic.
*/
static void new_metamap (int which)
{
int j;
if (keys[which])
ircpanic("metamap already exists");
keys[which] = new_malloc(sizeof(MetaMap));
for (j = 0; j <= 255; j++)
KEY(which, j) = NULL;
}
/*
* delete_metamap -- When you're all done with a metamap you can call
* this function to garbage collect it. If there are any bindings in the
* metamap when you call this, they will be summarily disposed of.
*/
static void delete_metamap (int i)
{
int j;
/* This is cheating, but do i care? ;-) */
for (j = 0; j <= 255; j++)
snew_key(i, j, NULL);
new_free((char **)&keys[i]);
}
/* * * * * * * * * * * * * KEY BINDING MANAGEMENT * * * * * * * * * * * * */
/* special interface to new_key for the default key bindings */
static void snew_key (int meta, unsigned chr, char *what)
{
int i;
int j;
if ((j = lookup_function(what, &i)) == 1)
new_key(meta, chr, i, 0, NULL);
#if 0
else
ircpanic("Something bogus passed to snew_key");
#endif
}
static void snew_key_from_str(const char *string, char *what)
{
int i;
int meta;
int old_display;
char chr;
old_display = window_display;
window_display = 0;
if ((meta = parse_key(string, &chr)) == -1)
return;
window_display = old_display;
if (lookup_function(what, &i) == 1)
new_key(meta, chr, i, 0, NULL);
return;
}
static void new_key(int meta, unsigned char chr, int type, int change, char *stuff)
{
/*
* Create a map first time we bind into it. We have to do this
* Because its possible to do /bind METAX-f when there is not
* otherwise any key bound to METAX.
*/
if (!keys)
return;
if (!keys[meta])
new_metamap(meta);
if (KEY(meta, chr))
{
if (KEY(meta, chr)->stuff)
new_free(&(KEY(meta, chr)->stuff));
if (KEY(meta, chr)->filename)
new_free(&(KEY(meta, chr)->filename));
new_free(&(KEY(meta, chr)));
KEY(meta, chr) = NULL;
}
if (type != 0)
{
KEY(meta, chr) = (KeyMap *)new_malloc(sizeof(KeyMap));
KEY(meta, chr)->key_index = type;
KEY(meta, chr)->changed = change;
/* KEY(meta, chr)->filename = m_strdup(current_package());*/
if (stuff)
KEY(meta, chr)->stuff = m_strdup(stuff);
else
KEY(meta, chr)->stuff = NULL;
}
}
/*
* show_binding: Given an unsigned character 'X' in the meta map 'Y', this
* function will display to the screen the status of that bindings in a
* human-readable way.
*/
static void show_binding(int meta, unsigned char c)
{
char meta_str[8];
*meta_str = 0;
if (meta < 1 || meta > MAX_META)
meta = 0;
else
snprintf(meta_str, sizeof meta_str, "META%d-", meta);
if (keys[meta] && KEY(meta, c))
{
#ifdef GUI
if(meta == MAX_META && c < MAX_MOUSE)
say("%s is bound to %s %s", mouse_actions[c],
key_names[(*keys[meta])[c]->key_index].name,
SAFE(KEY(meta, c)->stuff));
else if (KEY(meta, c)->key_index < 0)
#else
if (KEY(meta, c)->key_index < 0)
#endif
say("%s%s is bound to META%d_CHARACTER",
meta_str,
display_key(c),
-(KEY(meta, c)->key_index));
else
say("%s%s is bound to %s %s",
meta_str,
display_key(c),
key_names[KEY(meta, c)->key_index].name,
SAFE(KEY(meta, c)->stuff));
}
else
say("%s%s is bound to NOTHING", meta_str, display_key(c));
}
#ifdef GUI
void wm_process(int param)
{
void (*func) (char, char *) = NULL;
char *ptr = NULL;
if (keys[MAX_META] && (*keys[MAX_META]) && (*keys[MAX_META])[param])
{
func = key_names[(*keys[MAX_META])[param]->key_index].func;
ptr = (*keys[MAX_META])[param]->stuff;
}
if (func)
func(param, ptr ? ptr : empty_string);
}
#endif
/*
* save_bindings: This writes all the key bindings for ircII to the given
* FILE pointer suitable for being /LOADed again in the future.
*/
void save_bindings (FILE *fp, int do_all)
{
int meta, j;
int charsize = charset_size();
char meta_str[10];
*meta_str = 0;
for (meta = 0; meta <= MAX_META; meta++)
{
if (meta != 0)
snprintf(meta_str, sizeof meta_str, "META%d-", meta);
for (j = 0; j < charsize; j++)
{
if (keys[meta] && KEY(meta, j) && KEY(meta, j)->changed)
{
if (KEY(meta, j)->key_index < 0)
fprintf(fp, "BIND %s%s META%d\n",
meta_str,
display_key(j),
-(KEY(meta, j)->key_index));
else
fprintf(fp, "BIND %s%s %s %s\n",
meta_str,
display_key(j),
key_names[KEY(meta, j)->key_index].name,
SAFE(KEY(meta, j)->stuff));
}
}
}
}
/*
* This is a function used by edit_char to retrieve the details for a
* specific key binding. This function provides the only external access
* to the key bindings. The arguments are the meta state and the character
* whose information you want to retrieve. That information is stored into
* the 'func' and 'name' pointers you pass in.
*
* The function will return 0 if the binding you request is a "normal" one.
* If the binding is "NOTHING", then func will be set to NULL
* If the binding is an action, the func will be set to its callback.
*
* The function will return a positive number if the binding you request is
* a "meta" character.
* The value of 'func' will be NULL but you should not depend on that.
*/
int get_binding(int meta, unsigned char c, KeyBinding *func, char **name)
{
*func = NULL;
*name = NULL;
if (meta >= 0 && meta <= MAX_META)
{
if (keys[meta] && KEY(meta, c))
{
/*
* If this is a meta binding, return the new meta
* state -- this is a "special" value.
*/
if (KEY(meta, c)->key_index < 0)
return -(KEY(meta, c)->key_index);
/*
* Otherwise, assign to 'func' and 'name' the
* appropriate values.
*/
*func = key_names[KEY(meta, c)->key_index].func;
*name = KEY(meta, c)->stuff;
}
}
return 0;
}
void remove_bindings (void)
{
int i;
for (i = 0; i <= MAX_META; i++)
delete_metamap(i);
}
void unload_bindings (const char *filename)
{
int i, j;
for (i = 0; i <= MAX_META; i++)
{
if (!keys[i])
continue;
for (j = 0; j < 256; j++)
if (KEY(i, j) && !strcmp(KEY(i, j)->filename, filename))
snew_key(i, j, NULL);
}
}
static void show_all_bindings (int meta)
{
int i, j, k;
if (meta == -1)
{
for (i = 0; i <= MAX_META; i++)
show_all_bindings(i);
return;
}
if (meta > MAX_META || !keys[meta])
return;
k = charset_size();
for (j = 0; j < k; j++)
if (KEY(meta, j) && KEY(meta, j)->key_index != 1)
show_binding(meta, j);
}
/* * * * * * * * * * * * * * PARSEKEY * * * * * * * * * * * * */
/* parsekeycmd: does the PARSEKEY command. */
BUILT_IN_COMMAND(parsekeycmd)
{
int i;
char *arg;
if ((arg = next_arg(args, &args)) != NULL)
{
int keyval = lookup_function(arg, &i);
switch (keyval)
{
case 0:
say("No such function %s", arg);
break;
case 1:
if (i < 0)
last_input_screen->meta_hit = -i;
else if (key_names[i].func)
key_names[i].func(0, args);
break;
default:
say("Ambigious function %s", arg);
break;
}
}
}
/* * * * * * * * * * * * * * RBIND * * * * * * * * * * * * * * */
/*
* rbindcmd: This is the /RBIND command. If you give it a bind action,
* it will show you all of the key bindings that have that action. You
* probably cannot lookup the action NOTHING as it is a magic action.
*/
BUILT_IN_COMMAND(rbindcmd)
{
int f;
char *arg;
int i, j;
int charsize = charset_size();
if ((arg = next_arg(args, &args)) == NULL)
return; /* No args is a no-op */
switch (lookup_function(arg, &f))
{
case 0:
say("No such function %s", arg);
return;
case 1:
break;
default:
say("Ambigious function %s", arg);
return;
}
for (i = 0; i <= MAX_META; i++)
{
if (!keys[i])
continue;
for (j = 0; j < charsize; j++)
if (KEY(i, j) && KEY(i, j)->key_index == f)
show_binding(i, j);
}
}
/* * * * * * * * * * * * * * BIND * * * * * * * * * * * * * */
static int grok_meta(const char *ptr, char **end)
{
int meta = -1;
const char *str;
/*
* Well, if it is going to be anywhere, META has to be out front,
* so lets slurp it up if its there.
*/
if (!my_strnicmp(ptr, "META", 4))
{
str = ptr = ptr + 4;
while (isdigit((unsigned char)*ptr))
ptr++;
if (*ptr == '_' && !my_strnicmp(ptr, "_CHARACTER", 10))
ptr = ptr + 10;
if (*ptr == '-')
ptr++;
meta = atol(str);
}
if (end)
*end = (char *)ptr;
return meta;
}
/*
* copy_redux:
* This converts an ordinary sequence into something more suitable to
* work with, including the redux of ^X into X-64.
* You can then work with the sequence after processing.
*/
void copy_redux(const char *orig, char *result)
{
const char *ptr;
for (ptr = orig; ptr && *ptr; ptr++, result++)
{
int c;
if (*ptr != '^')
{
*result = *ptr;
continue;
}
ptr++;
c = toupper((unsigned char)*ptr);
switch (c)
{
case 0: /* ^<nul> is ^ */
*result++ = '^';
goto out;
case '?': /* ^? is DEL */
*result = 0177;
break;
default:
if (c < 64 || c > 127)
{
say("Illegal key sequence: ^%c", *ptr);
goto out;
}
*result = c - 64;
break;
}
}
out:
*result = 0;
return;
}
/*
* find_meta_map: Finds a meta map that does not already contain a
* binding to the specified character.
*/
int find_meta_map(char key)
{
int curr = MAX_META;
for (curr = MAX_META; curr > 4; curr--)
{
if (!keys[curr])
return curr;
if (!KEY(curr, key))
return curr;
}
resize_metamap(curr_keys_size + 1);
return MAX_META; /* Well, its empty now */
}
/*
* Purpose:
* To make sure that the key sequence X is valid upon return.
* Composition of X is: [<key>]*<key>
* Where <key> is an ascii char > 32, or a caret followed by an ascii char.
*
* First, remove the last character
* Second, remove the leading part of X that is a valid meta descriptor
*
* At this point, we have <META> + [<unbound-key>]* + <final-key>
*
* If unbound-key is present, then we have to bind it. The first thing to
* do is find a place where <final-key> can be stashed. Look for the highest
* metamap that has an open spot for <final-key>
*
* Now we have <META> + [<unbound-key>]* + <unbound-key> + <final-key>
* And we now know what meta map the first three parts have to conclude to.
* So that is the return value.
*
* Now we need to build up to that. Repeat this process until there is
* nothing left in the unbound-key segment.
*/
/*
* A lot of magic goes on in this function. The general purpose of this
* function is to take a "key-description" of any form, and canonicalize
* it down into a resulting meta map (which is the return value), and return
* the final character in 'term'. Older algorithms only allowed you to
* specify META(X)-(Y), where X is the meta value to be returned and Y is
* the character, and anything else was an error. Now we allow you to specify
* any arbitrary string -- if the leading part of the string is already bound
* to a META key, then we can deal with that. If the leading part of the
* string is NOT bound to anything in particular, then we will bind it FOR
* you, and the resulting meta state is returned. This allows things like
* this to work:
*
* /BIND META2-C BIND-ACTION (Specify a meta map directly)
* /BIND ^[[A BIND-ACTION (^[[ is bound to META2 by default)
* /BIND ^[[11~ BIND-ACTION (Force us to make suer ^[[11 is bound
* to a meta map before returning.)
*/
static int parse_key(const char *sequence, char *term)
{
char *copy, *end;
int return_meta = 0;
int meta;
char last_character;
char terminal_character;
int last;
int somethingN;
#ifdef GUI
int mouse;
#endif
/*
* Make a local copy of the string to be bound. Redux all of
* the ^x modifers to their literal control characters.
*/
copy = alloca(strlen(sequence) + 4);
copy_redux(sequence, copy);
end = copy + strlen(copy) - 1;
#ifdef GUI
for( mouse = 0; mouse < MAX_MOUSE; mouse++)
{
if (!my_strnicmp(sequence, mouse_actions[mouse], strlen(mouse_actions[mouse])))
{
*term=(char)mouse;
return MAX_META;
}
}
#endif
if (x_debug & DEBUG_AUTOKEY)
yell("Starting with COPY := [%s]", copy);
/*
* Remove any leading META description
*/
if ((meta = grok_meta(copy, &copy)) == -1)
meta = 0;
if (x_debug & DEBUG_AUTOKEY)
yell("After META grokked, COPY := [%s]", copy);
/*
* Remove any leading characters that also comprise a META
* description
*/
while (copy[0] && copy[1])
{
if (keys[meta] && KEY(meta, *copy) &&
KEY(meta, *copy)->key_index < 0)
{
meta = -(KEY(meta, *copy)->key_index);
copy++;
if (x_debug & DEBUG_AUTOKEY)
{
yell("First character of COPY switches to meta [%d]", meta);
yell("After META grokked, COPY := [%s]", copy);
}
continue;
}
break;
}
if (x_debug & DEBUG_AUTOKEY)
yell("After ALL META grokked, COPY := [%s]", copy);
/*
* Check to see if the entire sequence was just a meta modifier
* or if it is a META-KEY modifier. Either way, we're done.
*/
if (!copy[0] || !copy[1])
{
*term = copy[0];
return meta;
}
/*
* Right now the input boils down to this:
*
* input := SOME_CHARACTERS + TERMINAL_CHARACTER + LAST_CHARACTER
* SOME_CHARACTERS := <key>*
* TERMINAL_CHARACTER := <key>
* LAST_CHARACTER := <key>
*
* The previous check assures that 'terminal character' is not
* an empty value at this point.
*/
last_character = *end;
*end-- = 0;
terminal_character = *end;
*end-- = 0;
if (x_debug & DEBUG_AUTOKEY)
{
yell("Starting to work on the string:");
yell("SOME_CHARACTERS := [%s] (%d)", copy, strlen(copy));
yell("TERMINAL_CHARACTER := [%c]", terminal_character);
yell("LAST_CHARACTER := [%c]", last_character);
}
/*
* Our ultimate goal is to return when the operation:
* /bind META<something>-LAST_CHARACTER <binding>
* will succeed. So we need to find a place to put LAST_CHARACTER.
*/
last = return_meta = find_meta_map(last_character);
if (x_debug & DEBUG_AUTOKEY)
{
yell("FIND_META_MAP says we can put [%c] in META [%d]",
last_character, return_meta);
}
/*
* So now we need to work backwards through the string linking
* each of the characters to the next one. Starting with
* TERMINAL_CHARACTER, we find a meta map where that can be linked
* from (that map is somethingN1). We then do:
*
* /bind META<somethingN1>-TERMINAL_CHARACTER META<LAST>
*
* Where 'last' is the most previous meta map we linked to, starting
* with 'something'.
*/
while (*copy)
{
if (x_debug & DEBUG_AUTOKEY)
{
yell("COPY: [%s] (%d)", copy, strlen(copy));
yell("Now we are going to bind the [%c] character to meta [%d] somehow.",
terminal_character, last);
}
/*
* <something> is any meta map such that:
* /bind META<somethingN>-[TERMINAL CHARACTER] META<something>
*/
somethingN = find_meta_map(terminal_character);
if (x_debug & DEBUG_AUTOKEY)
yell("FIND_META_MAP says we can do this in META [%d]", somethingN);
new_key(somethingN, terminal_character, -last, 1, NULL);
show_binding(somethingN, terminal_character);
/*
* Now we walk backwards in the string: 'last' now becomes
* the meta map we just linked, and we pop TERMINAL_CHARACTER
* off the end of SOME_CHARACTERS. We repeat this until
* SOME_CHARACTERS is empty.
*/
last = somethingN;
terminal_character = *end;
*end-- = 0;
}
/*
* Make the final link from the initial meta state to our newly
* constructed chain...
*/
new_key(meta, terminal_character, -last, 1, NULL);
show_binding(meta, terminal_character);
/*
* Return the interesting information
*/
*term = last_character;
return return_meta;
#if 0
/* The rest of this isn't finished, hence is unsupported */
say("The bind cannot occur because the character sequence to bind contains a leading substring that is bound to something else.");
return -1;
#endif
}
/*
* bindcmd: The /BIND command. The general syntax is:
*
* /BIND ([key-descr] ([bind-command] ([args])))
* Where:
* KEY-DESCR := ([^]C | META[num])
* BIND-COMMAND := <Any string in the key_names lookup table>
*
* If given no arguments, this command shows all non-empty bindings
* current registered.
*
* If given one argument, that argument is to be a description of a valid
* key sequence. The command will show the binding of that sequence.
*
* If given two arguments, the first argument is to be a description of a
* valid key sequence and the second argument is to be a valid binding
* command followed by any optionally appropriate arguments. The key
* sequence is then bound to that action.
*
* The special binding command "NOTHING" actually unbinds the key.
*/
BUILT_IN_COMMAND(bindcmd)
{
char *key;
char *function;
char *newkey;
int meta;
char dakey;
int bi_index;
int cnt,
i;
/*
* See if they specified a key argument. If they didnt, show all
* binds and return
*/
if ((key = new_next_arg(args, &args)) == NULL)
{
show_all_bindings(-1);
return;
}
/*
* Grok any flags (only one, for now)
*/
if (*key == '-')
{
if (!my_strnicmp(key + 1, "DEFAULTS", 1))
{
init_keys();
init_keys2();
}
return;
}
/*
* Grok the key argument and see what we can make of it
* If there is an error at this point, don't continue.
* Most of the work is done here.
*/
newkey = get_term_capability(key, 0, 1);
if ((meta = parse_key(newkey ? newkey : key, &dakey)) == -1)
if (!newkey || (parse_key(key, &dakey) == -1))
return;
/*
* See if they specified an action argument. If they didnt, then
* check to see if they specified /bind METAX or if they specified
* /bind <char sequence>, and output as is appropriate.
*/
if ((function = next_arg(args, &args)) == NULL)
{
/* They did /bind ^C */
if (dakey)
show_binding(meta, dakey);
/* They did /bind meta2 */
else
show_all_bindings(meta);
return;
}
/*
* Look up the action they want to take. If it is invalid, tell
* them so, if it is ambiguous, show the possible choices, and if
* if it valid, then actually do the bind action. Note that if we
* do the bind, we do a show() so the user knows we took the action.
*/
switch ((cnt = lookup_function(function, &bi_index)))
{
case 0:
say("No such function: %s", function);
break;
case 1:
if (meta < 1 || meta > MAX_META)
meta = 0;
new_key(meta, dakey, bi_index, 1, *args ? args : NULL);
show_binding(meta, dakey);
break;
default:
say("Ambiguous function name: %s", function);
for (i = 0; i < cnt; i++, bi_index++)
put_it("%s", key_names[bi_index].name);
break;
}
}
/* * * * * * * * * * * BINDING ACTIONS * * * * * * * * * */
/* I hate typedefs... */
/*
* lookup_function: When you want to convert a "binding" name (such as
* BACKSPACE or SELF_INSERT) over to its offset in the binding lookup table,
* you must call this function to retrieve that offset. The first argument
* is the name you want to look up, and the second argument is where the
* offset is to be stored.
*
* Return value: (its tricky)
* -1 -- The name is a META binding that is invalid.
* Zero -- The name is not a valid binding name.
* One -- The name is a valid, unambiguous binding name.
* If it is a META binding, lf_index will be negative,
* Otherwise, lf_index will be positive.
* Other -- The name is an ambiguous (therefore invalid) binding name.
*
* In the case of a return value of any positive value, "lf_index" will be
* set to the first item that matches the 'name'. For all other return
* values, "lf_index" will have the value -1.
*/
static int lookup_function(const char *orig_name, int *lf_index)
{
size_t len;
int cnt, i;
char *name;
if (!orig_name)
{
*lf_index = 0;
return 1;
}
name = LOCAL_COPY(orig_name);
upper(name);
len = strlen(name);
*lf_index = -1;
/* Handle "META" descriptions especially. */
if (strbegins(name, "META"))
{
int meta = grok_meta(name, NULL);
if (meta < 0)
return meta;
*lf_index = -meta;
return 1;
}
for (cnt = 0, i = 0; i < NUMBER_OF_FUNCTIONS; i++)
{
if (strncmp(name, key_names[i].name, len) == 0)
{
cnt++;
if (*lf_index == -1)
*lf_index = i;
}
}
if (*lf_index == -1)
return 0;
if (strcmp(name, key_names[*lf_index].name) == 0)
return 1;
else
return cnt;
}
/* I don't know where this belongs. */
/*
* display_key: Given a (possibly unprintable) unsigned character 'c',
* convert that character into a printable string. For characters less
* than 32, and the character 127, they will be converted into the "control"
* sequence by having a prepended caret ('^'). Other characters will be
* left alone. The return value belongs to the function -- don't mangle it.
*/
static char *display_key(char c)
{
static char key[3];
key[2] = 0;
if (c >= 0 && c < 32)
{
key[0] = '^';
key[1] = c + 64;
}
else if (c == '\177')
{
key[0] = '^';
key[1] = '?';
}
else
{
key[0] = c;
key[1] = 0;
}
return key;
}
char *convert_to_keystr(char *key)
{
int ret, loc;
static char keyloc[80];
ret = lookup_function(key, &loc);
*keyloc = 0;
if (ret == 1)
{
char meta_str[8];
int i, j;
int charsize = charset_size();
*meta_str = 0;
for (i = 0; i <= MAX_META; i++)
{
if (!keys[i])
continue;
for (j = 0; j < charsize; j++)
if (KEY(i, j) && KEY(i, j)->key_index == loc)
{
if (i > 0)
snprintf(meta_str, sizeof meta_str, "META%d-", i);
snprintf(keyloc, sizeof keyloc, "%s%s", meta_str, display_key(j));
return keyloc;
}
}
}
return keyloc;
}
/* * * * * * * * * * * * * * * * * * INITIALIZATION * * * * * * * * * * * */
/*
* This is where you put all the default key bindings. This is a lot
* simpler, just defining those you need, instead of all of them, isn't
* it? And it takes up so much less memory, too...
*/
void init_keys (void)
{
int i;
/*
* Make sure the meta map is big enough to hold all these bindings.
*/
remove_bindings();
resize_metamap(40); /* Whatever. */
/*
* All the "default" bindings are self_insert unless we bind
* them differently
*/
for (i = 1; i <= 255; i++)
snew_key(0, i, "SELF_INSERT");
/* "default" characters that arent self_insert */
snew_key(0, 1, "BEGINNING_OF_LINE"); /* ^A */
snew_key(0, 2, "BOLD"); /* ^B */
snew_key(0, 4, "DELETE_CHARACTER"); /* ^D */
snew_key(0, 5, "CHANGE_TO_SPLIT"); /* ^E */
snew_key(0, 6, "WHOLEFT"); /* ^F */
snew_key(0, 8, "BACKSPACE"); /* ^H (delete) */
snew_key(0, 9, "TAB_COMPLETION"); /* ^I (tab) */
snew_key(0, 10, "SEND_LINE"); /* ^J (enter) */
snew_key(0, 11, "JOIN_LAST_INVITE"); /* ^K */
snew_key(0, 12, "REFRESH_SCREEN"); /* ^L (linefeed) */
snew_key(0, 13, "SEND_LINE"); /* ^M (return) */
snew_key(0, 14, "QUOTE_CHARACTER"); /* ^N */
snew_key(0, 15, "IGNORE_NICK"); /* ^O */
snew_key(0, 16, "BACKWARD_HISTORY"); /* ^P */
snew_key(0, 17, "QUOTE_CHARACTER"); /* ^Q */
snew_key(0, 18, "NICK_COMPLETION"); /* ^R */
snew_key(0, 19, "TOGGLE_STOP_SCREEN"); /* ^S */
snew_key(0, 20, "TRANSPOSE_CHARACTERS"); /* ^T */
snew_key(0, 21, "ERASE_LINE"); /* ^U */
snew_key(0, 22, "REVERSE"); /* ^V */
snew_key(0, 23, "META2_CHARACTER"); /* ^W */
snew_key(0, 24, "SWITCH_CHANNELS"); /* ^X */
snew_key(0, 25, "YANK_FROM_CUTBUFFER"); /* ^Y */
#ifdef ALLOW_STOP_IRC
#ifndef PUBLIC_ACCESS
snew_key(0, 26, "STOP_IRC"); /* ^Z */
#endif
#endif
snew_key(0, 27, "META1_CHARACTER"); /* ^[ (escape) */
snew_key(0, 29, "AUTOREPLY"); /* ^] */
snew_key(0, 31, "UNDERLINE"); /* ^_ */
snew_key(0, 127, "BACKSPACE"); /* ^? (delete) */
/*
* european keyboards (and probably others) use the eighth bit
* for extended characters. Having these keys bound by default
* causes them lots of grief, so unless you really want to use
* these, they are commented out.
*/
#ifdef EMACS_KEYBINDS
snew_key(0, 188, "SCROLL_START");
snew_key(0, 190, "SCROLL_END");
snew_key(0, 226, "BACKWARD_WORD");
snew_key(0, 228, "DELETE_NEXT_WORD");
snew_key(0, 229, "SCROLL_END");
snew_key(0, 230, "FORWARD_WORD");
snew_key(0, 232, "DELETE_PREVIOUS_WORD");
snew_key(0, 255, "DELETE_PREVIOUS_WORD");
#endif
/* meta 1 characters */
snew_key(1, 27, "COMMAND_COMPLETION");
snew_key(1, 46, "CLEAR_SCREEN");
snew_key(1, 60, "SCROLL_START");
snew_key(1, 62, "SCROLL_END");
snew_key(1, 79, "META2_CHARACTER");
snew_key(1, 91, "META2_CHARACTER");
snew_key(1, 98, "BACKWARD_WORD");
snew_key(1, 100, "DELETE_NEXT_WORD");
snew_key(1, 101, "SCROLL_END");
snew_key(1, 102, "FORWARD_WORD");
snew_key(1, 104, "DELETE_PREVIOUS_WORD");
snew_key(1, 110, "SCROLL_FORWARD");
snew_key(1, 112, "SCROLL_BACKWARD");
snew_key(1, 127, "DELETE_PREVIOUS_WORD");
snew_key(1, '1', "WINDOW_SWAP_1");
snew_key(1, '2', "WINDOW_SWAP_2");
snew_key(1, '3', "WINDOW_SWAP_3");
snew_key(1, '4', "WINDOW_SWAP_4");
snew_key(1, '5', "WINDOW_SWAP_5");
snew_key(1, '6', "WINDOW_SWAP_6");
snew_key(1, '7', "WINDOW_SWAP_7");
snew_key(1, '8', "WINDOW_SWAP_8");
snew_key(1, '9', "WINDOW_SWAP_9");
snew_key(1, '0', "WINDOW_SWAP_10");
/* meta 2 characters */
#ifdef ALLOW_STOP_IRC
#ifndef PUBLIC_ACCESS
snew_key(2, 26, "STOP_IRC");
#endif
#endif
snew_key(2, 110, "SWAP_NEXT_WINDOW");
snew_key(2, 112, "PREVIOUS_WINDOW");
#ifdef __EMX__
/* meta 3 characters */
snew_key(2, '[', "META3_CHARACTER");
snew_key(2, '1', "META3_CHARACTER");
snew_key(1, 71, "NEW_BEGINNING_OF_LINE");
snew_key(1, 83, "TOGGLE_CLOAK");
snew_key(1, 79, "NEW_SCROLL_END");
snew_key(1, 73, "NEW_SCROLL_BACKWARD");
snew_key(1, 81, "NEW_SCROLL_FORWARD");
snew_key(2, '?', "WINDOW_HELP");
snew_key(2, '+', "WINDOW_GROW_ONE");
snew_key(2, '-', "WINDOW_SHRINK_ONE");
snew_key(2, 'm', "WINDOW_MOVE");
snew_key(2, 'l', "WINDOW_LIST");
snew_key(2, 'k', "WINDOW_KILL");
snew_key(2, 'b', "WINDOW_BALANCE");
snew_key(2, 'h', "WINDOW_HIDE");
snew_key(2, '[', "META3_CHARACTER");
snew_key(2, '1', "META3_CHARACTER");
#else /* __EMX__ */
snew_key(2, '?', "WINDOW_HELP");
snew_key(2, '+', "WINDOW_GROW_ONE");
snew_key(2, '-', "WINDOW_SHRINK_ONE");
snew_key(2, 'm', "WINDOW_MOVE");
snew_key(2, 'l', "WINDOW_LIST");
snew_key(2, 'k', "WINDOW_KILL");
snew_key(2, 'b', "WINDOW_BALANCE");
snew_key(2, 'h', "WINDOW_HIDE");
snew_key(2, '[', "META3_CHARACTER");
snew_key(2, 70, "SCROLL_START"); /* Freebsd home */
snew_key(2, 71, "SCROLL_FORWARD"); /* Freebsd pgdown */
snew_key(2, 72, "SCROLL_END"); /* Freebsd end */
snew_key(2, 73, "SCROLL_BACKWARD"); /* Freebsd pgup */
snew_key(2, '1', "META32_CHARACTER");
snew_key(2, '4', "META33_CHARACTER");
snew_key(2, '5', "META30_CHARACTER");
snew_key(2, '6', "META31_CHARACTER");
#endif
/* meta 4 characters -- vi key mappings */
snew_key(4, 8, "BACKWARD_CHARACTER");
snew_key(4, 32, "FORWARD_CHARACTER");
snew_key(4, 65, "META4");
snew_key(4, 72, "BACKWARD_CHARACTER");
snew_key(4, 73, "META4");
snew_key(4, 74, "FORWARD_HISTORY");
snew_key(4, 75, "BACKWARD_HISTORY");
snew_key(4, 76, "FORWARD_CHARACTER");
snew_key(4, 88, "DELETE_CHARACTER");
snew_key(4, 97, "META4");
snew_key(4, 104, "BACKWARD_CHARACTER");
snew_key(4, 105, "META4");
snew_key(4, 106, "FORWARD_HISTORY");
snew_key(4, 107, "BACKWARD_HISTORY");
snew_key(4, 108, "FORWARD_CHARACTER");
snew_key(4, 120, "DELETE_CHARACTER");
/* I used 30-something to keep them out of others' way */
snew_key(30, '~', "SCROLL_BACKWARD");
snew_key(31, '~', "SCROLL_FORWARD");
snew_key(32, '~', "SCROLL_START");
snew_key(33, '~', "SCROLL_END");
}
#define LKEY(x, y) \
{ \
char *l = get_term_capability(#x, 0, 1); \
if (l) \
snew_key_from_str(l, #y); \
}
void init_keys2 (void)
{
/* keys bound from terminfo/termcap */
LKEY(key_up, BACKWARD_HISTORY)
LKEY(key_down, FORWARD_HISTORY)
LKEY(key_left, BACKWARD_CHARACTER)
LKEY(key_right, FORWARD_CHARACTER)
LKEY(key_ppage, SCROLL_BACKWARD)
LKEY(key_npage, SCROLL_FORWARD)
LKEY(key_home, SCROLL_START)
LKEY(key_end, SCROLL_END)
LKEY(key_ic, TOGGLE_INSERT_MODE)
/*LKEY(key_dc, DELETE_CHARACTER)*/
LKEY(key_f1, CHELP)
LKEY(key_f2, CHANNEL_CHOPS)
LKEY(key_f3, CHANNEL_NONOPS)
#ifdef WANT_CDCC
LKEY(key_f4, CDCC_PLIST)
#endif
LKEY(key_f5, DCC_PLIST)
LKEY(key_f6, DCC_STATS)
LKEY(key_dc, TOGGLE_CLOAK)
}
void disable_stop(void)
{
snew_key(0, 26, "SELF_INSERT");
}