/*
 * Modifications Copyright 1993, 1994, 1995, 1996, 1999,
 *  2000, 2001, 2002, 2004, 2005 by Paul Mattes.
 * Original X11 Port Copyright 1990 by Jeff Sparkes.
 *  Permission to use, copy, modify, and distribute this software and its
 *  documentation for any purpose and without fee is hereby granted,
 *  provided that the above copyright notice appear in all copies and that
 *  both that copyright notice and this permission notice appear in
 *  supporting documentation.
 *
 * Copyright 1989 by Georgia Tech Research Corporation, Atlanta, GA 30332.
 *  All Rights Reserved.  GTRC hereby grants public use of this software.
 *  Derivative works based on this software must incorporate this copyright
 *  notice.
 *
 * x3270, c3270, s3270 and tcl3270 are distributed in the hope that they will
 * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the file LICENSE
 * for more details.
 */

/*
 *	kybd.c
 *		This module handles the keyboard for the 3270 emulator.
 */

#include "globals.h"

#if defined(X3270_DISPLAY) /*[*/
#include <X11/Xatom.h>
#endif
#define XK_3270
#if defined(X3270_APL) /*[*/
#define XK_APL
#endif /*]*/
#include <X11/keysym.h>

#include <fcntl.h>
#include "3270ds.h"
#include "appres.h"
#include "ctlr.h"
#include "resources.h"

#include "actionsc.h"
#include "ansic.h"
#include "aplc.h"
#include "ctlrc.h"
#include "ftc.h"
#include "hostc.h"
#include "idlec.h"
#include "keymapc.h"
#include "keypadc.h"
#include "kybdc.h"
#include "macrosc.h"
#include "popupsc.h"
#include "printc.h"
#include "screenc.h"
#if defined(X3270_DISPLAY) /*[*/
#include "selectc.h"
#endif /*]*/
#include "statusc.h"
#include "tablesc.h"
#include "telnetc.h"
#include "togglesc.h"
#include "trace_dsc.h"
#include "utilc.h"
#if defined(X3270_DBCS) /*[*/
#include "widec.h"
#endif /*]*/

/* Statics */
static enum	{ NONE, COMPOSE, FIRST } composing = NONE;
static unsigned char pf_xlate[] = { 
	AID_PF1,  AID_PF2,  AID_PF3,  AID_PF4,  AID_PF5,  AID_PF6,
	AID_PF7,  AID_PF8,  AID_PF9,  AID_PF10, AID_PF11, AID_PF12,
	AID_PF13, AID_PF14, AID_PF15, AID_PF16, AID_PF17, AID_PF18,
	AID_PF19, AID_PF20, AID_PF21, AID_PF22, AID_PF23, AID_PF24
};
static unsigned char pa_xlate[] = { 
	AID_PA1, AID_PA2, AID_PA3
};
#define PF_SZ	(sizeof(pf_xlate)/sizeof(pf_xlate[0]))
#define PA_SZ	(sizeof(pa_xlate)/sizeof(pa_xlate[0]))
static unsigned long unlock_id;
#define UNLOCK_MS	350
static Boolean key_Character(int code, Boolean with_ge, Boolean pasting);
static Boolean flush_ta(void);
static void key_AID(unsigned char aid_code);
static void kybdlock_set(unsigned int bits, const char *cause);
static KeySym MyStringToKeysym(char *s, enum keytype *keytypep);

#if defined(X3270_DBCS) /*[*/
static Boolean key_WCharacter(unsigned char code[]);
#endif /*]*/

static int nxk = 0;
static struct xks {
	KeySym key;
	KeySym assoc;
} *xk;

static Boolean		insert = False;		/* insert mode */
static Boolean		reverse = False;	/* reverse-input mode */

/* Globals */
unsigned int	kybdlock = KL_NOT_CONNECTED;
unsigned char	aid = AID_NO;		/* current attention ID */

/* Composite key mappings. */

struct akeysym {
	KeySym keysym;
	enum keytype keytype;
};
static struct akeysym cc_first;
static struct composite {
	struct akeysym k1, k2;
	struct akeysym translation;
} *composites = NULL;
static int n_composites = 0;

#define ak_eq(k1, k2)	(((k1).keysym  == (k2).keysym) && \
			 ((k1).keytype == (k2).keytype))

static struct ta {
	struct ta *next;
	XtActionProc fn;
	char *parm1;
	char *parm2;
} *ta_head = (struct ta *) NULL,
  *ta_tail = (struct ta *) NULL;

static char dxl[] = "0123456789abcdef";
#define FROM_HEX(c)	(strchr(dxl, tolower(c)) - dxl)

extern Widget *screen;

#define KYBDLOCK_IS_OERR	(kybdlock && !(kybdlock & ~KL_OERR_MASK))


/*
 * Put an action on the typeahead queue.
 */
static void
enq_ta(XtActionProc fn, char *parm1, char *parm2)
{
	struct ta *ta;

	/* If no connection, forget it. */
	if (!CONNECTED) {
		trace_event("  dropped (not connected)\n");
		return;
	}

	/* If operator error, complain and drop it. */
	if (kybdlock & KL_OERR_MASK) {
		ring_bell();
		trace_event("  dropped (operator error)\n");
		return;
	}

	/* If scroll lock, complain and drop it. */
	if (kybdlock & KL_SCROLLED) {
		ring_bell();
		trace_event("  dropped (scrolled)\n");
		return;
	}

	/* If typeahead disabled, complain and drop it. */
	if (!appres.typeahead) {
		trace_event("  dropped (no typeahead)\n");
		return;
	}

	ta = (struct ta *) Malloc(sizeof(*ta));
	ta->next = (struct ta *) NULL;
	ta->fn = fn;
	ta->parm1 = ta->parm2 = CN;
	if (parm1) {
		ta->parm1 = NewString(parm1);
		if (parm2)
			ta->parm2 = NewString(parm2);
	}
	if (ta_head)
		ta_tail->next = ta;
	else {
		ta_head = ta;
		status_typeahead(True);
	}
	ta_tail = ta;

	trace_event("  action queued (kybdlock 0x%x)\n", kybdlock);
}

/*
 * Execute an action from the typeahead queue.
 */
Boolean
run_ta(void)
{
	struct ta *ta;

	if (kybdlock || (ta = ta_head) == (struct ta *)NULL)
		return False;

	if ((ta_head = ta->next) == (struct ta *)NULL) {
		ta_tail = (struct ta *)NULL;
		status_typeahead(False);
	}

	action_internal(ta->fn, IA_TYPEAHEAD, ta->parm1, ta->parm2);
	Free(ta->parm1);
	Free(ta->parm2);
	Free(ta);

	return True;
}

/*
 * Flush the typeahead queue.
 * Returns whether or not anything was flushed.
 */
static Boolean
flush_ta(void)
{
	struct ta *ta, *next;
	Boolean any = False;

	for (ta = ta_head; ta != (struct ta *) NULL; ta = next) {
		Free(ta->parm1);
		Free(ta->parm2);
		next = ta->next;
		Free(ta);
		any = True;
	}
	ta_head = ta_tail = (struct ta *) NULL;
	status_typeahead(False);
	return any;
}

/* Set bits in the keyboard lock. */
static void
kybdlock_set(unsigned int bits, const char *cause unused)
{
	unsigned int n;

	n = kybdlock | bits;
	if (n != kybdlock) {
#if defined(KYBDLOCK_TRACE) /*[*/
	       trace_event("  %s: kybdlock |= 0x%04x, 0x%04x -> 0x%04x\n",
		    cause, bits, kybdlock, n);
#endif /*]*/
		kybdlock = n;
		status_kybdlock();
	}
}

/* Clear bits in the keyboard lock. */
void
kybdlock_clr(unsigned int bits, const char *cause unused)
{
	unsigned int n;

	n = kybdlock & ~bits;
	if (n != kybdlock) {
#if defined(KYBDLOCK_TRACE) /*[*/
		trace_event("  %s: kybdlock &= ~0x%04x, 0x%04x -> 0x%04x\n",
		    cause, bits, kybdlock, n);
#endif /*]*/
		kybdlock = n;
		status_kybdlock();
	}
}

/*
 * Set or clear enter-inhibit mode.
 */
void
kybd_inhibit(Boolean inhibit)
{
	if (inhibit) {
		kybdlock_set(KL_ENTER_INHIBIT, "kybd_inhibit");
		if (kybdlock == KL_ENTER_INHIBIT)
			status_reset();
	} else {
		kybdlock_clr(KL_ENTER_INHIBIT, "kybd_inhibit");
		if (!kybdlock)
			status_reset();
	}
}

/*
 * Called when a host connects or disconnects.
 */
static void
kybd_connect(Boolean connected)
{
	if (kybdlock & KL_DEFERRED_UNLOCK)
		RemoveTimeOut(unlock_id);
	kybdlock_clr(-1, "kybd_connect");

	if (connected) {
		/* Wait for any output or a WCC(restore) from the host */
		kybdlock_set(KL_AWAITING_FIRST, "kybd_connect");
	} else {
		kybdlock_set(KL_NOT_CONNECTED, "kybd_connect");
		(void) flush_ta();
	}
}

/*
 * Called when we switch between 3270 and ANSI modes.
 */
static void
kybd_in3270(Boolean in3270 unused)
{
	if (kybdlock & KL_DEFERRED_UNLOCK)
		RemoveTimeOut(unlock_id);
	kybdlock_clr(~KL_AWAITING_FIRST, "kybd_in3270");

	/* There might be a macro pending. */
	if (CONNECTED)
		ps_process();
}

/*
 * Called to initialize the keyboard logic.
 */
void
kybd_init(void)
{
	/* Register interest in connect and disconnect events. */
	register_schange(ST_CONNECT, kybd_connect);
	register_schange(ST_3270_MODE, kybd_in3270);
}

/*
 * Toggle insert mode.
 */
static void
insert_mode(Boolean on)
{
	insert = on;
	status_insert_mode(on);
}

/*
 * Toggle reverse mode.
 */
static void
reverse_mode(Boolean on)
{
	reverse = on;
	status_reverse_mode(on);
}

/*
 * Lock the keyboard because of an operator error.
 */
static void
operator_error(int error_type)
{
	if (sms_redirect())
		popup_an_error("Keyboard locked");
	if (appres.oerr_lock || sms_redirect()) {
		status_oerr(error_type);
		mcursor_locked();
		kybdlock_set((unsigned int)error_type, "operator_error");
		(void) flush_ta();
	} else {
		ring_bell();
	}
}


/*
 * Handle an AID (Attention IDentifier) key.  This is the common stuff that
 * gets executed for all AID keys (PFs, PAs, Clear and etc).
 */
static void
key_AID(unsigned char aid_code)
{
#if defined(X3270_ANSI) /*[*/
	if (IN_ANSI) {
		register unsigned i;

		if (aid_code == AID_ENTER) {
			net_sendc('\r');
			return;
		}
		for (i = 0; i < PF_SZ; i++)
			if (aid_code == pf_xlate[i]) {
				ansi_send_pf(i+1);
				return;
			}
		for (i = 0; i < PA_SZ; i++)
			if (aid_code == pa_xlate[i]) {
				ansi_send_pa(i+1);
				return;
			}
		return;
	}
#endif /*]*/
	if (IN_SSCP) {
		if (kybdlock & KL_OIA_MINUS)
			return;
		if (aid_code != AID_ENTER && aid_code != AID_CLEAR) {
			status_minus();
			kybdlock_set(KL_OIA_MINUS, "key_AID");
			return;
		}
	}
	if (IN_SSCP && aid_code == AID_ENTER) {
		/* Act as if the host had written our input. */
		buffer_addr = cursor_addr;
	}
	if (!IN_SSCP || aid_code != AID_CLEAR) {
		status_twait();
		mcursor_waiting();
		insert_mode(False);
		kybdlock_set(KL_OIA_TWAIT | KL_OIA_LOCKED, "key_AID");
	}
	aid = aid_code;
	ctlr_read_modified(aid, False);
	ticking_start(False);
	status_ctlr_done();
}

void
PF_action(Widget w unused, XEvent *event, String *params, Cardinal *num_params)
{
	unsigned k;

	action_debug(PF_action, event, params, num_params);
	if (check_usage(PF_action, *num_params, 1, 1) < 0)
		return;
	k = atoi(params[0]);
	if (k < 1 || k > PF_SZ) {
		popup_an_error("%s: Invalid argument '%s'",
		    action_name(PF_action), params[0]);
		cancel_if_idle_command();
		return;
	}
	reset_idle_timer();
	if (kybdlock & KL_OIA_MINUS)
		return;
	else if (kybdlock)
		enq_ta(PF_action, params[0], CN);
	else
		key_AID(pf_xlate[k-1]);
}

void
PA_action(Widget w unused, XEvent *event, String *params, Cardinal *num_params)
{
	unsigned k;

	action_debug(PA_action, event, params, num_params);
	if (check_usage(PA_action, *num_params, 1, 1) < 0)
		return;
	k = atoi(params[0]);
	if (k < 1 || k > PA_SZ) {
		popup_an_error("%s: Invalid argument '%s'",
		    action_name(PA_action), params[0]);
		cancel_if_idle_command();
		return;
	}
	reset_idle_timer();
	if (kybdlock & KL_OIA_MINUS)
		return;
	else if (kybdlock)
		enq_ta(PA_action, params[0], CN);
	else
		key_AID(pa_xlate[k-1]);
}


/*
 * ATTN key, per RFC 2355.  Sends IP, regardless.
 */
void
Attn_action(Widget w unused, XEvent *event, String *params,
    Cardinal *num_params)
{
	action_debug(Attn_action, event, params, num_params);
	if (!IN_3270)
		return;
	reset_idle_timer();
	net_interrupt();
}

/*
 * IAC IP, which works for 5250 System Request and interrupts the program
 * on an AS/400, even when the keyboard is locked.
 *
 * This is now the same as the Attn action.
 */
void
Interrupt_action(Widget w unused, XEvent *event, String *params,
    Cardinal *num_params)
{
	action_debug(Interrupt_action, event, params, num_params);
	if (!IN_3270)
		return;
	reset_idle_timer();
	net_interrupt();
}



/*
 * Prepare for an insert of 'count' bytes.
 * Returns True if the insert is legal, False otherwise.
 */
static Boolean
ins_prep(int faddr, int baddr, int count)
{
	int next_faddr;
	int xaddr;
	int need;
	int ntb;
	int tb_start = -1;
	int copy_len;

	/* Find the end of the field. */
	if (faddr == -1) {
		/* Unformatted.  Use the end of the line. */
		next_faddr = (((baddr / COLS) + 1) * COLS) % (ROWS*COLS);
	} else {
		next_faddr = faddr;
		INC_BA(next_faddr);
		while (next_faddr != faddr && !ea_buf[next_faddr].fa) {
			INC_BA(next_faddr);
		}
	}

	/* Are there enough NULLs or trailing blanks available? */
	xaddr = baddr;
	need = count;
	ntb = 0;
	while (need && (xaddr != next_faddr)) {
		if (ea_buf[xaddr].cc == EBC_null)
			need--;
		else if (toggled(BLANK_FILL) &&
			 (ea_buf[xaddr].cc == EBC_space)) {
			if (tb_start == -1)
				tb_start = xaddr;
			ntb++;
		} else {
			tb_start = -1;
			ntb = 0;
		}
		INC_BA(xaddr);
	}
#if defined(_ST) /*[*/
	printf("need %d at %d, tb_start at %d\n", count, baddr, tb_start);
#endif /*]*/
	if (need - ntb > 0) {
		operator_error(KL_OERR_OVERFLOW);
		return False;
	}

	/*
	 * Shift the buffer to the right until we've consumed the available
	 * (and needed) NULLs.
	 */
	need = count;
	xaddr = baddr;
	while (need && (xaddr != next_faddr)) {
		int n_nulls = 0;
		int first_null = -1;

		while (need &&
		       ((ea_buf[xaddr].cc == EBC_null) ||
		        (tb_start >= 0 && xaddr >= tb_start))) {
			need--;
			n_nulls++;
			if (first_null == -1)
				first_null = xaddr;
			INC_BA(xaddr);
		}
		if (n_nulls) {
			int to;

			/* Shift right n_nulls worth. */
			copy_len = first_null - baddr;
			if (copy_len < 0)
				copy_len += ROWS*COLS;
			to = (baddr + n_nulls) % (ROWS*COLS);
#if defined(_ST) /*[*/
			printf("found %d NULLs at %d\n", n_nulls, first_null);
			printf("copying %d from %d to %d\n", copy_len, to,
			    first_null);
#endif /*]*/
			if (copy_len)
				ctlr_wrapping_memmove(to, baddr, copy_len);
		}
		INC_BA(xaddr);
	}

	return True;

}

#define GE_WFLAG	0x100
#define PASTE_WFLAG	0x200

static void
key_Character_wrapper(Widget w unused, XEvent *event unused, String *params,
    Cardinal *num_params unused)
{
	int code;
	Boolean with_ge = False;
	Boolean pasting = False;

	code = atoi(params[0]);
	if (code & GE_WFLAG) {
		with_ge = True;
		code &= ~GE_WFLAG;
	}
	if (code & PASTE_WFLAG) {
		pasting = True;
		code &= ~PASTE_WFLAG;
	}
	trace_event(" %s -> Key(%s\"%s\")\n",
	    ia_name[(int) ia_cause],
	    with_ge ? "GE " : "",
	    ctl_see((int) ebc2asc[code]));
	(void) key_Character(code, with_ge, pasting);
}

/*
 * Handle an ordinary displayable character key.  Lots of stuff to handle
 * insert-mode, protected fields and etc.
 */
static Boolean
key_Character(int code, Boolean with_ge, Boolean pasting)
{
	register int	baddr, faddr, xaddr;
	register unsigned char	fa;
	enum dbcs_why why;

	if (kybdlock) {
		char codename[64];

		(void) sprintf(codename, "%d", code |
			(with_ge ? GE_WFLAG : 0) |
			(pasting ? PASTE_WFLAG : 0));
		enq_ta(key_Character_wrapper, codename, CN);
		return False;
	}
	baddr = cursor_addr;
	faddr = find_field_attribute(baddr);
	fa = get_field_attribute(baddr);
	if (ea_buf[baddr].fa || FA_IS_PROTECTED(fa)) {
		operator_error(KL_OERR_PROTECTED);
		return False;
	}
	if (appres.numeric_lock && FA_IS_NUMERIC(fa) &&
	    !((code >= EBC_0 && code <= EBC_9) ||
	      code == EBC_minus || code == EBC_period)) {
		operator_error(KL_OERR_NUMERIC);
		return False;
	}

	/* Can't put an SBCS in a DBCS field. */
	if (ea_buf[faddr].cs == CS_DBCS) {
		operator_error(KL_OERR_DBCS);
		return False;
	}

	/* If it's an SI (end of DBCS subfield), move over one position. */
	if (ea_buf[baddr].cc == EBC_si) {
		INC_BA(baddr);
		if (baddr == faddr) {
			operator_error(KL_OERR_OVERFLOW);
			return False;
		}
	}

	/* Add the character. */
	if (ea_buf[baddr].cc == EBC_so) {

		if (insert) {
			if (!ins_prep(faddr, baddr, 1))
				return False;
		} else {
			Boolean was_si = False;

			/*
			 * Overwriting an SO (start of DBCS subfield).
			 * If it's followed by an SI, replace the SO/SI
			 * pair with x/space.  If not, replace it and
			 * the following DBCS character with
			 * x/space/SO.
			 */
			xaddr = baddr;
			INC_BA(xaddr);
			was_si = (ea_buf[xaddr].cc == EBC_si);
			ctlr_add(xaddr, EBC_space, CS_BASE);
			ctlr_add_fg(xaddr, 0);
#if defined(X3270_ANSI) /*[*/
			ctlr_add_bg(xaddr, 0);
#endif /*]*/
			if (!was_si) {
				INC_BA(xaddr);
				ctlr_add(xaddr, EBC_so, CS_BASE);
				ctlr_add_fg(xaddr, 0);
#if defined(X3270_ANSI) /*[*/
				ctlr_add_bg(xaddr, 0);
#endif /*]*/
			}
		}

	} else switch (ctlr_lookleft_state(baddr, &why)) {
	case DBCS_RIGHT:
		DEC_BA(baddr);
		/* fall through... */
	case DBCS_LEFT:
		if (why == DBCS_ATTRIBUTE) {
			if (insert) {
				if (!ins_prep(faddr, baddr, 1))
					return False;
			} else {
				/*
				 * Replace single DBCS char with
				 * x/space.
				 */
				xaddr = baddr;
				INC_BA(xaddr);
				ctlr_add(xaddr, EBC_space, CS_BASE);
				ctlr_add_fg(xaddr, 0);
				ctlr_add_gr(xaddr, 0);
			}
		} else {
			Boolean was_si;

			if (insert) {
				/*
				 * Inserting SBCS into a DBCS subfield.
				 * If this is the first position, we
				 * can just insert one character in
				 * front of the SO.  Otherwise, we'll
				 * need room for SI (to end subfield),
				 * the character, and SO (to begin the
				 * subfield again).
				 */
				xaddr = baddr;
				DEC_BA(xaddr);
				if (ea_buf[xaddr].cc == EBC_so) {
					DEC_BA(baddr);
					if (!ins_prep(faddr, baddr, 1))
						return False;
				} else {
					if (!ins_prep(faddr, baddr, 3))
						return False;
					xaddr = baddr;
					ctlr_add(xaddr, EBC_si,
					    CS_BASE);
					ctlr_add_fg(xaddr, 0);
					ctlr_add_gr(xaddr, 0);
					INC_BA(xaddr);
					INC_BA(baddr);
					INC_BA(xaddr);
					ctlr_add(xaddr, EBC_so,
					    CS_BASE);
					ctlr_add_fg(xaddr, 0);
					ctlr_add_gr(xaddr, 0);
				}
			} else {
				/* Overwriting part of a subfield. */
				xaddr = baddr;
				ctlr_add(xaddr, EBC_si, CS_BASE);
				ctlr_add_fg(xaddr, 0);
				ctlr_add_gr(xaddr, 0);
				INC_BA(xaddr);
				INC_BA(baddr);
				INC_BA(xaddr);
				was_si = (ea_buf[xaddr].cc == EBC_si);
				ctlr_add(xaddr, EBC_space, CS_BASE);
				ctlr_add_fg(xaddr, 0);
				ctlr_add_gr(xaddr, 0);
				if (!was_si) {
					INC_BA(xaddr);
					ctlr_add(xaddr, EBC_so,
					    CS_BASE);
					ctlr_add_fg(xaddr, 0);
					ctlr_add_gr(xaddr, 0);
				}
			}
		}
		break;
	default:
	case DBCS_NONE:
		if (insert && !ins_prep(faddr, baddr, 1))
			return False;
		break;
	}
	ctlr_add(baddr, (unsigned char)code,
	    (unsigned char)(with_ge ? CS_GE : 0));
	ctlr_add_fg(baddr, 0);
	ctlr_add_gr(baddr, 0);
	INC_BA(baddr);

	/* Replace leading nulls with blanks, if desired. */
	if (formatted && toggled(BLANK_FILL)) {
		register int	baddr_fill = baddr;

		DEC_BA(baddr_fill);
		while (baddr_fill != faddr) {

			/* Check for backward line wrap. */
			if ((baddr_fill % COLS) == COLS - 1) {
				Boolean aborted = True;
				register int baddr_scan = baddr_fill;

				/*
				 * Check the field within the preceeding line
				 * for NULLs.
				 */
				while (baddr_scan != faddr) {
					if (ea_buf[baddr_scan].cc != EBC_null) {
						aborted = False;
						break;
					}
					if (!(baddr_scan % COLS))
						break;
					DEC_BA(baddr_scan);
				}
				if (aborted)
					break;
			}

			if (ea_buf[baddr_fill].cc == EBC_null)
				ctlr_add(baddr_fill, EBC_space, 0);
			DEC_BA(baddr_fill);
		}
	}

	mdt_set(cursor_addr);

	/*
	 * Implement auto-skip, and don't land on attribute bytes.
	 * This happens for all pasted data (even DUP), and for all
	 * keyboard-generated data except DUP.
	 */
	if (pasting || (code != EBC_dup)) {
		while (ea_buf[baddr].fa) {
			if (FA_IS_SKIP(ea_buf[baddr].fa))
				baddr = next_unprotected(baddr);
			else
				INC_BA(baddr);
		}
		cursor_move(baddr);
	}

	(void) ctlr_dbcs_postprocess();
	return True;
}

#if defined(X3270_DBCS) /*[*/
static void
key_WCharacter_wrapper(Widget w unused, XEvent *event unused, String *params,
    Cardinal *num_params unused)
{
	int code;
	unsigned char codebuf[2];

	code = atoi(params[0]);
	trace_event(" %s -> Key(0x%04x)\n",
	    ia_name[(int) ia_cause], code);
	codebuf[0] = (code >> 8) & 0xff;
	codebuf[1] = code & 0xff;
	(void) key_WCharacter(codebuf);
}

/*
 * Handle a DBCS character.
 */
static Boolean
key_WCharacter(unsigned char code[])
{
	int baddr;
	register unsigned char fa;
	int faddr;
	enum dbcs_state d;
	int xaddr;
	Boolean done = False;
	extern unsigned char reply_mode; /* XXX */

	if (kybdlock) {
		char codename[64];

		(void) sprintf(codename, "%d", (code[0] << 8) | code[1]);
		enq_ta(key_WCharacter_wrapper, codename, CN);
		return False;
	}

	/* In DBCS mode? */
	if (!dbcs) {
		trace_event("DBCS character received when not in DBCS mode, "
		    "ignoring.\n");
		return True;
	}

#if defined(X3270_ANSI) /*[*/
	/* In ANSI mode? */
	if (IN_ANSI) {
	    char mb[16];

	    dbcs_to_mb(code[0], code[1], mb);
	    net_sends(mb);
	    return True;
	}
#endif /*]*/

	baddr = cursor_addr;
	fa = get_field_attribute(baddr);
	faddr = find_field_attribute(baddr);

	/* Protected? */
	if (ea_buf[baddr].fa || FA_IS_PROTECTED(fa)) {
		operator_error(KL_OERR_PROTECTED);
		return False;
	}

	/* Numeric? */
	if (appres.numeric_lock && FA_IS_NUMERIC(fa)) {
		operator_error(KL_OERR_NUMERIC);
		return False;
	}

	/* Check for insert mode. */

	/*
	 * Figure our what to do based on the DBCS state of the buffer.
	 * Leaves baddr pointing to the next unmodified position.
	 */
	switch (d = ctlr_dbcs_state(baddr)) {
	case DBCS_RIGHT:
	case DBCS_RIGHT_WRAP:
		/* Back up one position and process it as a LEFT. */
		DEC_BA(baddr);
		/* fall through... */
	case DBCS_LEFT:
	case DBCS_LEFT_WRAP:
		/* Overwrite the existing character. */
		if (insert) {
			if (!ins_prep(faddr, baddr, 2)) {
				return False;
			}
		}
		ctlr_add(baddr, code[0], ea_buf[baddr].cs);
		INC_BA(baddr);
		ctlr_add(baddr, code[1], ea_buf[baddr].cs);
		INC_BA(baddr);
		done = True;
		break;
	case DBCS_SB:
		/* Back up one position and process it as an SI. */
		DEC_BA(baddr);
		/* fall through... */
	case DBCS_SI:
		/* Make sure there's room to extend the subfield. */
		/* XXX: Make sure we don't create SI, SI. */
		if (insert) {
			if (!ins_prep(faddr, baddr, 2)) {
				return False;
			}
		} else {
			xaddr = baddr;
			INC_BA(xaddr);
			if (ea_buf[xaddr].fa)
				break;
			INC_BA(xaddr);
			if (ea_buf[xaddr].fa)
				break;
		}
		ctlr_add(baddr, code[0], ea_buf[baddr].cs);
		INC_BA(baddr);
		ctlr_add(baddr, code[1], ea_buf[baddr].cs);
		INC_BA(baddr);
		ctlr_add(baddr, EBC_si, ea_buf[baddr].cs);
		done = True;
		break;
	case DBCS_DEAD:
		break;
	case DBCS_NONE:
		/* Can we add SO/SI to this field? */
		if (ea_buf[faddr].ic) {
			/* Is there room? */
			if (insert) {
				if (!ins_prep(faddr, baddr, 4)) {
					return False;
				}
			} else {
				xaddr = baddr;
				INC_BA(xaddr); /* C0 */
				if (ea_buf[xaddr].fa)
					break;
				INC_BA(xaddr); /* C1 */
				if (ea_buf[xaddr].fa)
					break;
				INC_BA(xaddr); /* SI */
				if (ea_buf[xaddr].fa)
					break;
			}
			/* Yes, add it. */
			ctlr_add(baddr, EBC_so, ea_buf[baddr].cs);
			INC_BA(baddr);
			ctlr_add(baddr, code[0], ea_buf[baddr].cs);
			INC_BA(baddr);
			ctlr_add(baddr, code[1], ea_buf[baddr].cs);
			INC_BA(baddr);
			ctlr_add(baddr, EBC_si, ea_buf[baddr].cs);
			done = True;
		} else if (reply_mode == SF_SRM_CHAR) {
			/* Use the character attribute. */
			if (insert) {
				if (!ins_prep(faddr, baddr, 2)) {
					return False;
				}
			} else {
				xaddr = baddr;
				INC_BA(xaddr);
				if (ea_buf[xaddr].fa)
					break;
			}
			ctlr_add(baddr, code[0], CS_DBCS);
			INC_BA(baddr);
			ctlr_add(baddr, code[1], CS_DBCS);
			INC_BA(baddr);
			done = True;
		}
		break;
	}

	if (done) {
		/* Implement blank fill mode. */
		if (toggled(BLANK_FILL)) {
			xaddr = faddr;
			INC_BA(xaddr);
			while (xaddr != baddr) {
				if (ea_buf[xaddr].cc == EBC_null)
					ctlr_add(xaddr, EBC_space, CS_BASE);
				else
					break;
				INC_BA(xaddr);
			}
		}

		mdt_set(cursor_addr);
		/* Implement auto-skip. */
		while (ea_buf[baddr].fa) {
			if (FA_IS_SKIP(ea_buf[baddr].fa))
				baddr = next_unprotected(baddr);
			else
				INC_BA(baddr);
		}
		cursor_move(baddr);
		(void) ctlr_dbcs_postprocess();
		return True;
	} else {
		operator_error(KL_OERR_DBCS);
		return False;
	}
}
#endif /*]*/

/*
 * Handle an ordinary character key, given an ASCII code.
 */
static void
key_ACharacter(unsigned char c, enum keytype keytype, enum iaction cause)
{
	register int i;
	struct akeysym ak;

	ak.keysym = c;
	ak.keytype = keytype;

	switch (composing) {
	    case NONE:
		break;
	    case COMPOSE:
		for (i = 0; i < n_composites; i++)
			if (ak_eq(composites[i].k1, ak) ||
			    ak_eq(composites[i].k2, ak))
				break;
		if (i < n_composites) {
			cc_first.keysym = c;
			cc_first.keytype = keytype;
			composing = FIRST;
			status_compose(True, c, keytype);
		} else {
			ring_bell();
			composing = NONE;
			status_compose(False, 0, KT_STD);
		}
		return;
	    case FIRST:
		composing = NONE;
		status_compose(False, 0, KT_STD);
		for (i = 0; i < n_composites; i++)
			if ((ak_eq(composites[i].k1, cc_first) &&
			     ak_eq(composites[i].k2, ak)) ||
			    (ak_eq(composites[i].k1, ak) &&
			     ak_eq(composites[i].k2, cc_first)))
				break;
		if (i < n_composites) {
			c = composites[i].translation.keysym;
			keytype = composites[i].translation.keytype;
		} else {
			ring_bell();
			return;
		}
		break;
	}

	trace_event(" %s -> Key(\"%s\")\n",
	    ia_name[(int) cause], ctl_see((int) c));
	if (IN_3270) {
		if (c < ' ') {
			trace_event("  dropped (control char)\n");
			return;
		}
		(void) key_Character((int) asc2ebc[c], keytype == KT_GE, False);
	}
#if defined(X3270_ANSI) /*[*/
	else if (IN_ANSI) {
		net_sendc((char) c);
	}
#endif /*]*/
	else {
		trace_event("  dropped (not connected)\n");
	}
}


/*
 * Simple toggles.
 */
#if defined(X3270_DISPLAY) /*[*/
void
AltCursor_action(Widget w unused, XEvent *event, String *params,
    Cardinal *num_params)
{
	action_debug(AltCursor_action, event, params, num_params);
	reset_idle_timer();
	do_toggle(ALT_CURSOR);
}
#endif /*]*/

void
MonoCase_action(Widget w unused, XEvent *event, String *params,
    Cardinal *num_params)
{
	action_debug(MonoCase_action, event, params, num_params);
	reset_idle_timer();
	do_toggle(MONOCASE);
}

/*
 * Flip the display left-to-right
 */
void
Flip_action(Widget w unused, XEvent *event, String *params,
    Cardinal *num_params)
{
	action_debug(Flip_action, event, params, num_params);
	reset_idle_timer();
	screen_flip();
}



/*
 * Tab forward to next field.
 */
void
Tab_action(Widget w unused, XEvent *event, String *params, Cardinal *num_params)
{
	action_debug(Tab_action, event, params, num_params);
	reset_idle_timer();
	if (kybdlock) {
		if (KYBDLOCK_IS_OERR) {
			kybdlock_clr(KL_OERR_MASK, "Tab");
			status_reset();
		} else {
			enq_ta(Tab_action, CN, CN);
			return;
		}
	}
#if defined(X3270_ANSI) /*[*/
	if (IN_ANSI) {
		net_sendc('\t');
		return;
	}
#endif /*]*/
	cursor_move(next_unprotected(cursor_addr));
}


/*
 * Tab backward to previous field.
 */
void
BackTab_action(Widget w unused, XEvent *event, String *params,
    Cardinal *num_params)
{
	register int	baddr, nbaddr;
	int		sbaddr;

	action_debug(BackTab_action, event, params, num_params);
	reset_idle_timer();
	if (kybdlock) {
		if (KYBDLOCK_IS_OERR) {
			kybdlock_clr(KL_OERR_MASK, "BackTab");
			status_reset();
		} else {
			enq_ta(BackTab_action, CN, CN);
			return;
		}
	}
	if (!IN_3270)
		return;
	baddr = cursor_addr;
	DEC_BA(baddr);
	if (ea_buf[baddr].fa)	/* at bof */
		DEC_BA(baddr);
	sbaddr = baddr;
	while (True) {
		nbaddr = baddr;
		INC_BA(nbaddr);
		if (ea_buf[baddr].fa &&
		    !FA_IS_PROTECTED(ea_buf[baddr].fa) &&
		    !ea_buf[nbaddr].fa)
			break;
		DEC_BA(baddr);
		if (baddr == sbaddr) {
			cursor_move(0);
			return;
		}
	}
	INC_BA(baddr);
	cursor_move(baddr);
}


/*
 * Deferred keyboard unlock.
 */

static void
defer_unlock(void)
{
	kybdlock_clr(KL_DEFERRED_UNLOCK, "defer_unlock");
	status_reset();
	if (CONNECTED)
		ps_process();
}

/*
 * Reset keyboard lock.
 */
void
do_reset(Boolean explicit)
{
	/*
	 * If explicit (from the keyboard) and there is typeahead or
	 * a half-composed key, simply flush it.
	 */
	if (explicit
#if defined(X3270_FT) /*[*/
	    || ft_state != FT_NONE
#endif /*]*/
	    ) {
		Boolean half_reset = False;

		if (flush_ta())
			half_reset = True;
		if (composing != NONE) {
			composing = NONE;
			status_compose(False, 0, KT_STD);
			half_reset = True;
		}
		if (half_reset)
			return;
	}

	/* Always clear insert mode. */
	insert_mode(False);

	/* Otherwise, if not connect, reset is a no-op. */
	if (!CONNECTED)
		return;

	/*
	 * Remove any deferred keyboard unlock.  We will either unlock the
	 * keyboard now, or want to defer further into the future.
	 */
	if (kybdlock & KL_DEFERRED_UNLOCK)
		RemoveTimeOut(unlock_id);

	/*
	 * If explicit (from the keyboard), unlock the keyboard now.
	 * Otherwise (from the host), schedule a deferred keyboard unlock.
	 */
	if (explicit
#if defined(X3270_FT) /*[*/
	    || ft_state != FT_NONE
#endif /*]*/
	    || (!appres.unlock_delay && !sms_in_macro())) {
		kybdlock_clr(-1, "do_reset");
	} else if (kybdlock &
  (KL_DEFERRED_UNLOCK | KL_OIA_TWAIT | KL_OIA_LOCKED | KL_AWAITING_FIRST)) {
		kybdlock_clr(~KL_DEFERRED_UNLOCK, "do_reset");
		kybdlock_set(KL_DEFERRED_UNLOCK, "do_reset");
		unlock_id = AddTimeOut(UNLOCK_MS, defer_unlock);
	}

	/* Clean up other modes. */
	status_reset();
	mcursor_normal();
	composing = NONE;
	status_compose(False, 0, KT_STD);
}

void
Reset_action(Widget w unused, XEvent *event, String *params,
    Cardinal *num_params)
{
	action_debug(Reset_action, event, params, num_params);
	reset_idle_timer();
	do_reset(True);
}


/*
 * Move to first unprotected field on screen.
 */
void
Home_action(Widget w unused, XEvent *event, String *params,
    Cardinal *num_params)
{
	action_debug(Home_action, event, params, num_params);
	reset_idle_timer();
	if (kybdlock) {
		enq_ta(Home_action, CN, CN);
		return;
	}
#if defined(X3270_ANSI) /*[*/
	if (IN_ANSI) {
		ansi_send_home();
		return;
	}
#endif /*]*/
	if (!formatted) {
		cursor_move(0);
		return;
	}
	cursor_move(next_unprotected(ROWS*COLS-1));
}


/*
 * Cursor left 1 position.
 */
static void
do_left(void)
{
	register int	baddr;
	enum dbcs_state d;

	baddr = cursor_addr;
	DEC_BA(baddr);
	d = ctlr_dbcs_state(baddr);
	if (IS_LEFT(d))
		DEC_BA(baddr);
	cursor_move(baddr);
}

void
Left_action(Widget w unused, XEvent *event, String *params,
    Cardinal *num_params)
{
	action_debug(Left_action, event, params, num_params);
	reset_idle_timer();
	if (kybdlock) {
		if (KYBDLOCK_IS_OERR) {
			kybdlock_clr(KL_OERR_MASK, "Left");
			status_reset();
		} else {
			enq_ta(Left_action, CN, CN);
			return;
		}
	}
#if defined(X3270_ANSI) /*[*/
	if (IN_ANSI) {
		ansi_send_left();
		return;
	}
#endif /*]*/
	if (!flipped)
		do_left();
	else {
		register int	baddr;

		baddr = cursor_addr;
		INC_BA(baddr);
		/* XXX: DBCS? */
		cursor_move(baddr);
	}
}


/*
 * Delete char key.
 * Returns "True" if succeeds, "False" otherwise.
 */
static Boolean
do_delete(void)
{
	register int	baddr, end_baddr;
	int xaddr;
	register unsigned char	fa;
	int ndel;
	register int i;

	baddr = cursor_addr;

	/* Can't delete a field attribute. */
	fa = get_field_attribute(baddr);
	if (FA_IS_PROTECTED(fa) || ea_buf[baddr].fa) {
		operator_error(KL_OERR_PROTECTED);
		return False;
	}
	if (ea_buf[baddr].cc == EBC_so || ea_buf[baddr].cc == EBC_si) {
		/*
		 * Can't delete SO or SI, unless it's adjacent to its
		 * opposite.
		 */
		xaddr = baddr;
		INC_BA(xaddr);
		if (ea_buf[xaddr].cc == SOSI(ea_buf[baddr].cc)) {
			ndel = 2;
		} else {
			operator_error(KL_OERR_PROTECTED);
			return False;
		}
	} else if (IS_DBCS(ea_buf[baddr].db)) {
		if (IS_RIGHT(ea_buf[baddr].db))
			DEC_BA(baddr);
		ndel = 2;
	} else
		ndel = 1;

	/* find next fa */
	if (formatted) {
		end_baddr = baddr;
		do {
			INC_BA(end_baddr);
			if (ea_buf[end_baddr].fa)
				break;
		} while (end_baddr != baddr);
		DEC_BA(end_baddr);
	} else {
		if ((baddr % COLS) == COLS - ndel)
			return True;
		end_baddr = baddr + (COLS - (baddr % COLS)) - 1;
	}

	/* Shift the remainder of the field left. */
	if (end_baddr > baddr) {
		ctlr_bcopy(baddr + ndel, baddr, end_baddr - (baddr + ndel) + 1,
		    0);
	} else if (end_baddr != baddr) {
		/* XXX: Need to verify this. */
		ctlr_bcopy(baddr + ndel, baddr,
		    ((ROWS * COLS) - 1) - (baddr + ndel) + 1, 0);
		ctlr_bcopy(0, (ROWS * COLS) - ndel, ndel, 0);
		ctlr_bcopy(ndel, 0, end_baddr - ndel + 1, 0);
	}

	/* NULL fill at the end. */
	for (i = 0; i < ndel; i++)
		ctlr_add(end_baddr - i, EBC_null, 0);

	/* Set the MDT for this field. */
	mdt_set(cursor_addr);

	/* Patch up the DBCS state for display. */
	(void) ctlr_dbcs_postprocess();
	return True;
}

void
Delete_action(Widget w unused, XEvent *event, String *params,
    Cardinal *num_params)
{
	action_debug(Delete_action, event, params, num_params);
	reset_idle_timer();
	if (kybdlock) {
		enq_ta(Delete_action, CN, CN);
		return;
	}
#if defined(X3270_ANSI) /*[*/
	if (IN_ANSI) {
		net_sendc('\177');
		return;
	}
#endif /*]*/
	if (!do_delete())
		return;
	if (reverse) {
		int baddr = cursor_addr;

		DEC_BA(baddr);
		if (!ea_buf[baddr].fa)
			cursor_move(baddr);
	}
}


/*
 * 3270-style backspace.
 */
void
BackSpace_action(Widget w unused, XEvent *event, String *params,
    Cardinal *num_params)
{
	action_debug(BackSpace_action, event, params, num_params);
	reset_idle_timer();
	if (kybdlock) {
		enq_ta(BackSpace_action, CN, CN);
		return;
	}
#if defined(X3270_ANSI) /*[*/
	if (IN_ANSI) {
		net_send_erase();
		return;
	}
#endif /*]*/
	if (reverse)
		(void) do_delete();
	else if (!flipped)
		do_left();
	else {
		register int	baddr;

		baddr = cursor_addr;
		DEC_BA(baddr);
		cursor_move(baddr);
	}
}


/*
 * Destructive backspace, like Unix "erase".
 */
static void
do_erase(void)
{
	int	baddr, faddr;
	enum dbcs_state d;

	baddr = cursor_addr;
	faddr = find_field_attribute(baddr);
	if (faddr == baddr || FA_IS_PROTECTED(ea_buf[baddr].fa)) {
		operator_error(KL_OERR_PROTECTED);
		return;
	}
	if (baddr && faddr == baddr - 1)
		return;
	do_left();

	/*
	 * If we are now on an SI, move left again.
	 */
	if (ea_buf[cursor_addr].cc == EBC_si) {
		baddr = cursor_addr;
		DEC_BA(baddr);
		cursor_move(baddr);
	}

	/*
	 * If we landed on the right-hand side of a DBCS character, move to the
	 * left-hand side.
	 * This ensures that if this is the end of a DBCS subfield, we will
	 * land on the SI, instead of on the character following.
	 */
	d = ctlr_dbcs_state(cursor_addr);
	if (IS_RIGHT(d)) {
		baddr = cursor_addr;
		DEC_BA(baddr);
		cursor_move(baddr);
	}

	/*
	 * Try to delete this character.
	 */
	if (!do_delete())
		return;

	/*
	 * If we've just erased the last character of a DBCS subfield, erase
	 * the SO/SI pair as well.
	 */
	baddr = cursor_addr;
	DEC_BA(baddr);
	if (ea_buf[baddr].cc == EBC_so && ea_buf[cursor_addr].cc == EBC_si) {
		cursor_move(baddr);
		(void) do_delete();
	}
}

void
Erase_action(Widget w unused, XEvent *event, String *params,
    Cardinal *num_params)
{
	action_debug(Erase_action, event, params, num_params);
	reset_idle_timer();
	if (kybdlock) {
		enq_ta(Erase_action, CN, CN);
		return;
	}
#if defined(X3270_ANSI) /*[*/
	if (IN_ANSI) {
		net_send_erase();
		return;
	}
#endif /*]*/
	do_erase();
}


/*
 * Cursor right 1 position.
 */
void
Right_action(Widget w unused, XEvent *event, String *params,
    Cardinal *num_params)
{
	register int	baddr;
	enum dbcs_state d;

	action_debug(Right_action, event, params, num_params);
	reset_idle_timer();
	if (kybdlock) {
		if (KYBDLOCK_IS_OERR) {
			kybdlock_clr(KL_OERR_MASK, "Right");
			status_reset();
		} else {
			enq_ta(Right_action, CN, CN);
			return;
		}
	}
#if defined(X3270_ANSI) /*[*/
	if (IN_ANSI) {
		ansi_send_right();
		return;
	}
#endif /*]*/
	if (!flipped) {
		baddr = cursor_addr;
		INC_BA(baddr);
		d = ctlr_dbcs_state(baddr);
		if (IS_RIGHT(d))
			INC_BA(baddr);
		cursor_move(baddr);
	} else
		do_left();
}


/*
 * Cursor left 2 positions.
 */
void
Left2_action(Widget w unused, XEvent *event, String *params,
    Cardinal *num_params)
{
	register int	baddr;
	enum dbcs_state d;

	action_debug(Left2_action, event, params, num_params);
	reset_idle_timer();
	if (kybdlock) {
		if (KYBDLOCK_IS_OERR) {
			kybdlock_clr(KL_OERR_MASK, "Left2");
			status_reset();
		} else {
			enq_ta(Left2_action, CN, CN);
			return;
		}
	}
#if defined(X3270_ANSI) /*[*/
	if (IN_ANSI)
		return;
#endif /*]*/
	baddr = cursor_addr;
	DEC_BA(baddr);
	d = ctlr_dbcs_state(baddr);
	if (IS_LEFT(d))
		DEC_BA(baddr);
	DEC_BA(baddr);
	d = ctlr_dbcs_state(baddr);
	if (IS_LEFT(d))
		DEC_BA(baddr);
	cursor_move(baddr);
}


/*
 * Cursor to previous word.
 */
void
PreviousWord_action(Widget w unused, XEvent *event, String *params,
    Cardinal *num_params)
{
	register int baddr;
	int baddr0;
	unsigned char  c;
	Boolean prot;

	action_debug(PreviousWord_action, event, params, num_params);
	reset_idle_timer();
	if (kybdlock) {
		enq_ta(PreviousWord_action, CN, CN);
		return;
	}
#if defined(X3270_ANSI) /*[*/
	if (IN_ANSI)
		return;
#endif /*]*/
	if (!formatted)
		return;

	baddr = cursor_addr;
	prot = FA_IS_PROTECTED(get_field_attribute(baddr));

	/* Skip to before this word, if in one now. */
	if (!prot) {
		c = ea_buf[baddr].cc;
		while (!ea_buf[baddr].fa && c != EBC_space && c != EBC_null) {
			DEC_BA(baddr);
			if (baddr == cursor_addr)
				return;
			c = ea_buf[baddr].cc;
		}
	}
	baddr0 = baddr;

	/* Find the end of the preceding word. */
	do {
		c = ea_buf[baddr].cc;
		if (ea_buf[baddr].fa) {
			DEC_BA(baddr);
			prot = FA_IS_PROTECTED(get_field_attribute(baddr));
			continue;
		}
		if (!prot && c != EBC_space && c != EBC_null)
			break;
		DEC_BA(baddr);
	} while (baddr != baddr0);

	if (baddr == baddr0)
		return;

	/* Go it its front. */
	for (;;) {
		DEC_BA(baddr);
		c = ea_buf[baddr].cc;
		if (ea_buf[baddr].fa || c == EBC_space || c == EBC_null) {
			break;
		}
	}
	INC_BA(baddr);
	cursor_move(baddr);
}


/*
 * Cursor right 2 positions.
 */
void
Right2_action(Widget w unused, XEvent *event, String *params,
    Cardinal *num_params)
{
	register int	baddr;
	enum dbcs_state d;

	action_debug(Right2_action, event, params, num_params);
	reset_idle_timer();
	if (kybdlock) {
		if (KYBDLOCK_IS_OERR) {
			kybdlock_clr(KL_OERR_MASK, "Right2");
			status_reset();
		} else {
			enq_ta(Right2_action, CN, CN);
			return;
		}
	}
#if defined(X3270_ANSI) /*[*/
	if (IN_ANSI)
		return;
#endif /*]*/
	baddr = cursor_addr;
	INC_BA(baddr);
	d = ctlr_dbcs_state(baddr);
	if (IS_RIGHT(d))
		INC_BA(baddr);
	INC_BA(baddr);
	d = ctlr_dbcs_state(baddr);
	if (IS_RIGHT(d))
		INC_BA(baddr);
	cursor_move(baddr);
}


/* Find the next unprotected word, or -1 */
static int
nu_word(int baddr)
{
	int baddr0 = baddr;
	unsigned char c;
	Boolean prot;

	prot = FA_IS_PROTECTED(get_field_attribute(baddr));

	do {
		c = ea_buf[baddr].cc;
		if (ea_buf[baddr].fa)
			prot = FA_IS_PROTECTED(ea_buf[baddr].fa);
		else if (!prot && c != EBC_space && c != EBC_null)
			return baddr;
		INC_BA(baddr);
	} while (baddr != baddr0);

	return -1;
}

/* Find the next word in this field, or -1 */
static int
nt_word(int baddr)
{
	int baddr0 = baddr;
	unsigned char c;
	Boolean in_word = True;

	do {
		c = ea_buf[baddr].cc;
		if (ea_buf[baddr].fa)
			return -1;
		if (in_word) {
			if (c == EBC_space || c == EBC_null)
				in_word = False;
		} else {
			if (c != EBC_space && c != EBC_null)
				return baddr;
		}
		INC_BA(baddr);
	} while (baddr != baddr0);

	return -1;
}


/*
 * Cursor to next unprotected word.
 */
void
NextWord_action(Widget w unused, XEvent *event, String *params, Cardinal *num_params)
{
	register int	baddr;
	unsigned char c;

	action_debug(NextWord_action, event, params, num_params);
	reset_idle_timer();
	if (kybdlock) {
		enq_ta(NextWord_action, CN, CN);
		return;
	}
#if defined(X3270_ANSI) /*[*/
	if (IN_ANSI)
		return;
#endif /*]*/
	if (!formatted)
		return;

	/* If not in an unprotected field, go to the next unprotected word. */
	if (ea_buf[cursor_addr].fa ||
	    FA_IS_PROTECTED(get_field_attribute(cursor_addr))) {
		baddr = nu_word(cursor_addr);
		if (baddr != -1)
			cursor_move(baddr);
		return;
	}

	/* If there's another word in this field, go to it. */
	baddr = nt_word(cursor_addr);
	if (baddr != -1) {
		cursor_move(baddr);
		return;
	}

	/* If in a word, go to just after its end. */
	c = ea_buf[cursor_addr].cc;
	if (c != EBC_space && c != EBC_null) {
		baddr = cursor_addr;
		do {
			c = ea_buf[baddr].cc;
			if (c == EBC_space || c == EBC_null) {
				cursor_move(baddr);
				return;
			} else if (ea_buf[baddr].fa) {
				baddr = nu_word(baddr);
				if (baddr != -1)
					cursor_move(baddr);
				return;
			}
			INC_BA(baddr);
		} while (baddr != cursor_addr);
	}
	/* Otherwise, go to the next unprotected word. */
	else {
		baddr = nu_word(cursor_addr);
		if (baddr != -1)
			cursor_move(baddr);
	}
}


/*
 * Cursor up 1 position.
 */
void
Up_action(Widget w unused, XEvent *event, String *params, Cardinal *num_params)
{
	register int	baddr;

	action_debug(Up_action, event, params, num_params);
	reset_idle_timer();
	if (kybdlock) {
		if (KYBDLOCK_IS_OERR) {
			kybdlock_clr(KL_OERR_MASK, "Up");
			status_reset();
		} else {
			enq_ta(Up_action, CN, CN);
			return;
		}
	}
#if defined(X3270_ANSI) /*[*/
	if (IN_ANSI) {
		ansi_send_up();
		return;
	}
#endif /*]*/
	baddr = cursor_addr - COLS;
	if (baddr < 0)
		baddr = (cursor_addr + (ROWS * COLS)) - COLS;
	cursor_move(baddr);
}


/*
 * Cursor down 1 position.
 */
void
Down_action(Widget w unused, XEvent *event, String *params, Cardinal *num_params)
{
	register int	baddr;

	action_debug(Down_action, event, params, num_params);
	reset_idle_timer();
	if (kybdlock) {
		if (KYBDLOCK_IS_OERR) {
			kybdlock_clr(KL_OERR_MASK, "Down");
			status_reset();
		} else {
			enq_ta(Down_action, CN, CN);
			return;
		}
	}
#if defined(X3270_ANSI) /*[*/
	if (IN_ANSI) {
		ansi_send_down();
		return;
	}
#endif /*]*/
	baddr = (cursor_addr + COLS) % (COLS * ROWS);
	cursor_move(baddr);
}


/*
 * Cursor to first field on next line or any lines after that.
 */
void
Newline_action(Widget w unused, XEvent *event, String *params, Cardinal *num_params)
{
	register int	baddr, faddr;
	register unsigned char	fa;

	action_debug(Newline_action, event, params, num_params);
	reset_idle_timer();
	if (kybdlock) {
		enq_ta(Newline_action, CN, CN);
		return;
	}
#if defined(X3270_ANSI) /*[*/
	if (IN_ANSI) {
		net_sendc('\n');
		return;
	}
#endif /*]*/
	baddr = (cursor_addr + COLS) % (COLS * ROWS);	/* down */
	baddr = (baddr / COLS) * COLS;			/* 1st col */
	faddr = find_field_attribute(baddr);
	fa = ea_buf[faddr].fa;
	if (faddr != baddr && !FA_IS_PROTECTED(fa))
		cursor_move(baddr);
	else
		cursor_move(next_unprotected(baddr));
}


/*
 * DUP key
 */
void
Dup_action(Widget w unused, XEvent *event, String *params, Cardinal *num_params)
{
	action_debug(Dup_action, event, params, num_params);
	reset_idle_timer();
	if (kybdlock) {
		enq_ta(Dup_action, CN, CN);
		return;
	}
#if defined(X3270_ANSI) /*[*/
	if (IN_ANSI)
		return;
#endif /*]*/
	if (key_Character(EBC_dup, False, False))
		cursor_move(next_unprotected(cursor_addr));
}


/*
 * FM key
 */
void
FieldMark_action(Widget w unused, XEvent *event, String *params, Cardinal *num_params)
{
	action_debug(FieldMark_action, event, params, num_params);
	reset_idle_timer();
	if (kybdlock) {
		enq_ta(FieldMark_action, CN, CN);
		return;
	}
#if defined(X3270_ANSI) /*[*/
	if (IN_ANSI)
		return;
#endif /*]*/
	(void) key_Character(EBC_fm, False, False);
}


/*
 * Vanilla AID keys.
 */
void
Enter_action(Widget w unused, XEvent *event, String *params, Cardinal *num_params)
{
	action_debug(Enter_action, event, params, num_params);
	reset_idle_timer();
	if (kybdlock & KL_OIA_MINUS)
		return;
	else if (kybdlock)
		enq_ta(Enter_action, CN, CN);
	else
		key_AID(AID_ENTER);
}


void
SysReq_action(Widget w unused, XEvent *event, String *params, Cardinal *num_params)
{
	action_debug(SysReq_action, event, params, num_params);
	reset_idle_timer();
	if (IN_ANSI)
		return;
#if defined(X3270_TN3270E) /*[*/
	if (IN_E) {
		net_abort();
	} else
#endif /*]*/
	{
		if (kybdlock & KL_OIA_MINUS)
			return;
		else if (kybdlock)
			enq_ta(SysReq_action, CN, CN);
		else
			key_AID(AID_SYSREQ);
	}
}


/*
 * Clear AID key
 */
void
Clear_action(Widget w unused, XEvent *event, String *params, Cardinal *num_params)
{
	action_debug(Clear_action, event, params, num_params);
	reset_idle_timer();
	if (kybdlock & KL_OIA_MINUS)
		return;
	if (kybdlock && CONNECTED) {
		enq_ta(Clear_action, CN, CN);
		return;
	}
#if defined(X3270_ANSI) /*[*/
	if (IN_ANSI) {
		ansi_send_clear();
		return;
	}
#endif /*]*/
	buffer_addr = 0;
	ctlr_clear(True);
	cursor_move(0);
	if (CONNECTED)
		key_AID(AID_CLEAR);
}


/*
 * Cursor Select key (light pen simulator).
 */
static void
lightpen_select(int baddr)
{
	int faddr;
	register unsigned char	fa;
	int designator;
#if defined(X3270_DBCS) /*[*/
	int designator2;
#endif /*]*/

	faddr = find_field_attribute(baddr);
	fa = ea_buf[faddr].fa;
	if (!FA_IS_SELECTABLE(fa)) {
		ring_bell();
		return;
	}
	designator = faddr;
	INC_BA(designator);

#if defined(X3270_DBCS) /*[*/
	if (dbcs) {
		if (ea_buf[baddr].cs == CS_DBCS) {
			designator2 = designator;
			INC_BA(designator2);
			if ((ea_buf[designator].db != DBCS_LEFT &&
			     ea_buf[designator].db != DBCS_LEFT_WRAP) &&
			    (ea_buf[designator2].db != DBCS_RIGHT &&
			     ea_buf[designator2].db != DBCS_RIGHT_WRAP)) {
				ring_bell();
				return;
			}
			if (ea_buf[designator].cc == 0x42 &&
			    ea_buf[designator2].cc == EBC_greater) {
				ctlr_add(designator2, EBC_question, CS_DBCS);
				mdt_clear(faddr);
			} else if (ea_buf[designator].cc == 0x42 &&
				   ea_buf[designator2].cc == EBC_question) {
				ctlr_add(designator2, EBC_greater, CS_DBCS);
				mdt_clear(faddr);
			} else if ((ea_buf[designator].cc == EBC_space &&
				    ea_buf[designator2].cc == EBC_space) ||
			           (ea_buf[designator].cc == EBC_null &&
				    ea_buf[designator2].cc == EBC_null)) {
				ctlr_add(designator2, EBC_greater, CS_DBCS);
				mdt_set(faddr);
				key_AID(AID_SELECT);
			} else if (ea_buf[designator].cc == 0x42 &&
				   ea_buf[designator2].cc == EBC_ampersand) {
				mdt_set(faddr);
				key_AID(AID_ENTER);
			} else {
				ring_bell();
			}
			return;
		}
	} 
#endif /*]*/

	switch (ea_buf[designator].cc) {
	    case EBC_greater:		/* > */
		ctlr_add(designator, EBC_question, 0); /* change to ? */
		mdt_clear(faddr);
		break;
	    case EBC_question:		/* ? */
		ctlr_add(designator, EBC_greater, 0);	/* change to > */
		mdt_set(faddr);
		break;
	    case EBC_space:		/* space */
	    case EBC_null:		/* null */
		mdt_set(faddr);
		key_AID(AID_SELECT);
		break;
	    case EBC_ampersand:		/* & */
		mdt_set(faddr);
		key_AID(AID_ENTER);
		break;
	    default:
		ring_bell();
		break;
	}
}

/*
 * Cursor Select key (light pen simulator) -- at the current cursor location.
 */
void
CursorSelect_action(Widget w unused, XEvent *event, String *params,
    Cardinal *num_params)
{
	action_debug(CursorSelect_action, event, params, num_params);
	reset_idle_timer();
	if (kybdlock) {
		enq_ta(CursorSelect_action, CN, CN);
		return;
	}

#if defined(X3270_ANSI) /*[*/
	if (IN_ANSI)
		return;
#endif /*]*/
	lightpen_select(cursor_addr);
}

#if defined(X3270_DISPLAY) /*[*/
/*
 * Cursor Select mouse action (light pen simulator).
 */
void
MouseSelect_action(Widget w, XEvent *event, String *params,
    Cardinal *num_params)
{
	action_debug(MouseSelect_action, event, params, num_params);
	if (w != *screen)
		return;
	reset_idle_timer();
	if (kybdlock)
		return;
#if defined(X3270_ANSI) /*[*/
	if (IN_ANSI)
		return;
#endif /*]*/
	lightpen_select(mouse_baddr(w, event));
}
#endif /*]*/


/*
 * Erase End Of Field Key.
 */
void
EraseEOF_action(Widget w unused, XEvent *event, String *params, Cardinal *num_params)
{
	register int	baddr;
	register unsigned char	fa;
	enum dbcs_state d;
	enum dbcs_why why;

	action_debug(EraseEOF_action, event, params, num_params);
	reset_idle_timer();
	if (kybdlock) {
		enq_ta(EraseEOF_action, CN, CN);
		return;
	}
#if defined(X3270_ANSI) /*[*/
	if (IN_ANSI)
		return;
#endif /*]*/
	baddr = cursor_addr;
	fa = get_field_attribute(baddr);
	if (FA_IS_PROTECTED(fa) || ea_buf[baddr].fa) {
		operator_error(KL_OERR_PROTECTED);
		return;
	}
	if (formatted) {	/* erase to next field attribute */
		do {
			ctlr_add(baddr, EBC_null, 0);
			INC_BA(baddr);
		} while (!ea_buf[baddr].fa);
		mdt_set(cursor_addr);
	} else {	/* erase to end of screen */
		do {
			ctlr_add(baddr, EBC_null, 0);
			INC_BA(baddr);
		} while (baddr != 0);
	}

	/* If the cursor was in a DBCS subfield, re-create the SI. */
	d = ctlr_lookleft_state(cursor_addr, &why);
	if (IS_DBCS(d) && why == DBCS_SUBFIELD) {
		if (d == DBCS_RIGHT) {
			baddr = cursor_addr;
			DEC_BA(baddr);
			ea_buf[baddr].cc = EBC_si;
		} else
			ea_buf[cursor_addr].cc = EBC_si;
	}
	(void) ctlr_dbcs_postprocess();
}


/*
 * Erase all Input Key.
 */
void
EraseInput_action(Widget w unused, XEvent *event, String *params, Cardinal *num_params)
{
	register int	baddr, sbaddr;
	unsigned char	fa;
	Boolean		f;

	action_debug(EraseInput_action, event, params, num_params);
	reset_idle_timer();
	if (kybdlock) {
		enq_ta(EraseInput_action, CN, CN);
		return;
	}
#if defined(X3270_ANSI) /*[*/
	if (IN_ANSI)
		return;
#endif /*]*/
	if (formatted) {
		/* find first field attribute */
		baddr = 0;
		do {
			if (ea_buf[baddr].fa)
				break;
			INC_BA(baddr);
		} while (baddr != 0);
		sbaddr = baddr;
		f = False;
		do {
			fa = ea_buf[baddr].fa;
			if (!FA_IS_PROTECTED(fa)) {
				mdt_clear(baddr);
				do {
					INC_BA(baddr);
					if (!f) {
						cursor_move(baddr);
						f = True;
					}
					if (!ea_buf[baddr].fa) {
						ctlr_add(baddr, EBC_null, 0);
					}
				} while (!ea_buf[baddr].fa);
			} else {	/* skip protected */
				do {
					INC_BA(baddr);
				} while (!ea_buf[baddr].fa);
			}
		} while (baddr != sbaddr);
		if (!f)
			cursor_move(0);
	} else {
		ctlr_clear(True);
		cursor_move(0);
	}
}



/*
 * Delete word key.  Backspaces the cursor until it hits the front of a word,
 * deletes characters until it hits a blank or null, and deletes all of these
 * but the last.
 *
 * Which is to say, does a ^W.
 */
void
DeleteWord_action(Widget w unused, XEvent *event, String *params, Cardinal *num_params)
{
	register int baddr;
	register unsigned char	fa;

	action_debug(DeleteWord_action, event, params, num_params);
	reset_idle_timer();
	if (kybdlock) {
		enq_ta(DeleteWord_action, CN, CN);
		return;
	}
#if defined(X3270_ANSI) /*[*/
	if (IN_ANSI) {
		net_send_werase();
		return;
	}
#endif /*]*/
	if (!formatted)
		return;

	baddr = cursor_addr;
	fa = get_field_attribute(baddr);

	/* Make sure we're on a modifiable field. */
	if (FA_IS_PROTECTED(fa) || ea_buf[baddr].fa) {
		operator_error(KL_OERR_PROTECTED);
		return;
	}

	/* Backspace over any spaces to the left of the cursor. */
	for (;;) {
		baddr = cursor_addr;
		DEC_BA(baddr);
		if (ea_buf[baddr].fa)
			return;
		if (ea_buf[baddr].cc == EBC_null ||
		    ea_buf[baddr].cc == EBC_space)
			do_erase();
		else
			break;
	}

	/* Backspace until the character to the left of the cursor is blank. */
	for (;;) {
		baddr = cursor_addr;
		DEC_BA(baddr);
		if (ea_buf[baddr].fa)
			return;
		if (ea_buf[baddr].cc == EBC_null ||
		    ea_buf[baddr].cc == EBC_space)
			break;
		else
			do_erase();
	}
}



/*
 * Delete field key.  Similar to EraseEOF, but it wipes out the entire field
 * rather than just to the right of the cursor, and it leaves the cursor at
 * the front of the field.
 *
 * Which is to say, does a ^U.
 */
void
DeleteField_action(Widget w unused, XEvent *event, String *params, Cardinal *num_params)
{
	register int	baddr;
	register unsigned char	fa;

	action_debug(DeleteField_action, event, params, num_params);
	reset_idle_timer();
	if (kybdlock) {
		enq_ta(DeleteField_action, CN, CN);
		return;
	}
#if defined(X3270_ANSI) /*[*/
	if (IN_ANSI) {
		net_send_kill();
		return;
	}
#endif /*]*/
	if (!formatted)
		return;

	baddr = cursor_addr;
	fa = get_field_attribute(baddr);
	if (FA_IS_PROTECTED(fa) || ea_buf[baddr].fa) {
		operator_error(KL_OERR_PROTECTED);
		return;
	}
	while (!ea_buf[baddr].fa)
		DEC_BA(baddr);
	INC_BA(baddr);
	mdt_set(cursor_addr);
	cursor_move(baddr);
	while (!ea_buf[baddr].fa) {
		ctlr_add(baddr, EBC_null, 0);
		INC_BA(baddr);
	}
}



/*
 * Set insert mode key.
 */
void
Insert_action(Widget w unused, XEvent *event, String *params, Cardinal *num_params)
{
	action_debug(Insert_action, event, params, num_params);
	reset_idle_timer();
	if (kybdlock) {
		enq_ta(Insert_action, CN, CN);
		return;
	}
#if defined(X3270_ANSI) /*[*/
	if (IN_ANSI)
		return;
#endif /*]*/
	insert_mode(True);
}


/*
 * Toggle insert mode key.
 */
void
ToggleInsert_action(Widget w unused, XEvent *event, String *params, Cardinal *num_params)
{
	action_debug(ToggleInsert_action, event, params, num_params);
	reset_idle_timer();
	if (kybdlock) {
		enq_ta(ToggleInsert_action, CN, CN);
		return;
	}
#if defined(X3270_ANSI) /*[*/
	if (IN_ANSI)
		return;
#endif /*]*/
	if (insert)
		insert_mode(False);
	else
		insert_mode(True);
}


/*
 * Toggle reverse mode key.
 */
void
ToggleReverse_action(Widget w unused, XEvent *event, String *params, Cardinal *num_params)
{
	action_debug(ToggleReverse_action, event, params, num_params);
	reset_idle_timer();
	if (kybdlock) {
		enq_ta(ToggleReverse_action, CN, CN);
		return;
	}
#if defined(X3270_ANSI) /*[*/
	if (IN_ANSI)
		return;
#endif /*]*/
	reverse_mode(!reverse);
}


/*
 * Move the cursor to the first blank after the last nonblank in the
 * field, or if the field is full, to the last character in the field.
 */
void
FieldEnd_action(Widget w unused, XEvent *event, String *params, Cardinal *num_params)
{
	int	baddr, faddr;
	unsigned char	fa, c;
	int	last_nonblank = -1;

	action_debug(FieldEnd_action, event, params, num_params);
	reset_idle_timer();
	if (kybdlock) {
		enq_ta(FieldEnd_action, CN, CN);
		return;
	}
#if defined(X3270_ANSI) /*[*/
	if (IN_ANSI)
		return;
#endif /*]*/
	if (!formatted)
		return;
	baddr = cursor_addr;
	faddr = find_field_attribute(baddr);
	fa = ea_buf[faddr].fa;
	if (faddr == baddr || FA_IS_PROTECTED(fa))
		return;

	baddr = faddr;
	while (True) {
		INC_BA(baddr);
		c = ea_buf[baddr].cc;
		if (ea_buf[baddr].fa)
			break;
		if (c != EBC_null && c != EBC_space)
			last_nonblank = baddr;
	}

	if (last_nonblank == -1) {
		baddr = faddr;
		INC_BA(baddr);
	} else {
		baddr = last_nonblank;
		INC_BA(baddr);
		if (ea_buf[baddr].fa)
			baddr = last_nonblank;
	}
	cursor_move(baddr);
}

/*
 * MoveCursor action.  Depending on arguments, this is either a move to the
 * mouse cursor position, or to an absolute location.
 */
void
MoveCursor_action(Widget w, XEvent *event, String *params, Cardinal *num_params)
{
	register int baddr;
	int row, col;

	action_debug(MoveCursor_action, event, params, num_params);

	reset_idle_timer();
	if (kybdlock) {
		if (*num_params == 2)
			enq_ta(MoveCursor_action, params[0], params[1]);
		return;
	}

	switch (*num_params) {
#if defined(X3270_DISPLAY) /*[*/
	    case 0:		/* mouse click, presumably */
		if (w != *screen)
			return;
		cursor_move(mouse_baddr(w, event));
		break;
#endif /*]*/
	    case 2:		/* probably a macro call */
		row = atoi(params[0]);
		col = atoi(params[1]);
		if (!IN_3270) {
			row--;
			col--;
		}
		if (row < 0)
			row = 0;
		if (col < 0)
			col = 0;
		baddr = ((row * COLS) + col) % (ROWS * COLS);
		cursor_move(baddr);
		break;
	    default:		/* couln't say */
		popup_an_error("%s requires 0 or 2 arguments",
		    action_name(MoveCursor_action));
		cancel_if_idle_command();
		break;
	}
}


#if defined(X3270_DBCS) && defined(X3270_DISPLAY) /*[*/
/*
 * Run a KeyPress through XIM.
 * Returns True if there is further processing to do, False otherwise.
 */
static Boolean
xim_lookup(XKeyEvent *event)
{
	static char *buf = NULL;
	static UChar *Ubuf = NULL;
	static int buf_len = 0, rlen;
	KeySym k;
	Status status;
	extern XIC ic;
	int i;
	Boolean rv = False;
	int wlen;
#define BASE_BUFSIZE 50

	if (ic == NULL)
		return True;

	if (buf == NULL) {
		buf_len = BASE_BUFSIZE;
		buf = Malloc(buf_len);
		Ubuf = (UChar *)Malloc(buf_len * sizeof(UChar));
	}

	for (;;) {
		memset(buf, '\0', buf_len);
		rlen = XmbLookupString(ic, event, buf, buf_len - 1, &k,
					&status);
		if (status != XBufferOverflow)
			break;
		buf_len += BASE_BUFSIZE;
		buf = Realloc(buf, buf_len);
		Ubuf = (UChar *)Realloc(Ubuf, buf_len * sizeof(UChar));
	}

	switch (status) {
	case XLookupNone:
		rv = False;
		break;
	case XLookupKeySym:
		rv = True;
		break;
	case XLookupChars:
		trace_event("%d XIM char%s:", rlen, (rlen != 1)? "s": "");
		for (i = 0; i < rlen; i++) {
			trace_event(" %02x", buf[i] & 0xff);
		}
		trace_event("\n");
		wlen = mb_to_unicode(buf, rlen, Ubuf, buf_len, NULL);
		if (wlen < 0)
			trace_event("  conversion failed\n");
		for (i = 0; i < wlen; i++) {
			unsigned char asc;
			unsigned char ebc[2];

			if (dbcs_map8(Ubuf[i], &asc)) {
				trace_event("  U+%04x -> "
				    "EBCDIC SBCS X'%02x'\n",
				    Ubuf[i] & 0xffff, asc2ebc[asc]);
				key_ACharacter(asc, KT_STD, ia_cause);
			} else if (dbcs_map16(Ubuf[i], ebc)) {
				trace_event("  U+%04x -> "
				    "EBCDIC DBCS X'%02x%02x'\n",
				    Ubuf[i] & 0xffff, ebc[0], ebc[1]);
				key_WCharacter(ebc);
			} else
				trace_event("  Cannot convert U+%04x to "
				    "EBCDIC\n", Ubuf[i] & 0xffff);
		}
		rv = False;
		break;
	case XLookupBoth:
		rv = True;
		break;
	}
	return rv;
}
#endif /*]*/


/*
 * Key action.
 */
void
Key_action(Widget w unused, XEvent *event, String *params, Cardinal *num_params)
{
	Cardinal i;
	KeySym k;
	enum keytype keytype;

	action_debug(Key_action, event, params, num_params);
	reset_idle_timer();

	for (i = 0; i < *num_params; i++) {
		char *s = params[i];

		k = MyStringToKeysym(s, &keytype);
		if (k == NoSymbol) {
			popup_an_error("%s: Nonexistent or invalid KeySym: %s",
			    action_name(Key_action), s);
			cancel_if_idle_command();
			continue;
		}
		if (k & ~0xff) {
			popup_an_error("%s: Invalid KeySym: %s",
			    action_name(Key_action), s);
			cancel_if_idle_command();
			continue;
		}
		key_ACharacter((unsigned char)(k & 0xff), keytype, IA_KEY);
	}
}

/*
 * String action.
 */
void
String_action(Widget w unused, XEvent *event, String *params, Cardinal *num_params)
{
	Cardinal i;
	int len = 0;
	char *s;

	action_debug(String_action, event, params, num_params);
	reset_idle_timer();

	/* Determine the total length of the strings. */
	for (i = 0; i < *num_params; i++)
		len += strlen(params[i]);
	if (!len)
		return;

	/* Allocate a block of memory and copy them in. */
	s = Malloc(len + 1);
	*s = '\0';
	for (i = 0; i < *num_params; i++)
		(void) strcat(s, params[i]);

	/* Set a pending string. */
	ps_set(s, False);
}

/*
 * HexString action.
 */
void
HexString_action(Widget w unused, XEvent *event, String *params, Cardinal *num_params)
{
	Cardinal i;
	int len = 0;
	char *s;
	char *t;

	action_debug(HexString_action, event, params, num_params);
	reset_idle_timer();

	/* Determine the total length of the strings. */
	for (i = 0; i < *num_params; i++) {
		t = params[i];
		if (!strncmp(t, "0x", 2) || !strncmp(t, "0X", 2))
			t += 2;
		len += strlen(t);
	}
	if (!len)
		return;

	/* Allocate a block of memory and copy them in. */
	s = Malloc(len + 1);
	*s = '\0';
	for (i = 0; i < *num_params; i++) {
		t = params[i];
		if (!strncmp(t, "0x", 2) || !strncmp(t, "0X", 2))
			t += 2;
		(void) strcat(s, t);
	}

	/* Set a pending string. */
	ps_set(s, True);
}

/*
 * Dual-mode action for the "asciicircum" ("^") key:
 *  If in ANSI mode, pass through untranslated.
 *  If in 3270 mode, translate to "notsign".
 * This action is obsoleted by the use of 3270-mode and NVT-mode keymaps, but
 * is still defined here for backwards compatibility with old keymaps.
 */
void
CircumNot_action(Widget w unused, XEvent *event, String *params, Cardinal *num_params)
{
	action_debug(CircumNot_action, event, params, num_params);
	reset_idle_timer();

	if (IN_3270 && composing == NONE)
		key_ACharacter(0xac, KT_STD, IA_KEY);
	else
		key_ACharacter('^', KT_STD, IA_KEY);
}

/* PA key action for String actions */
static void
do_pa(unsigned n)
{
	if (n < 1 || n > PA_SZ) {
		popup_an_error("Unknown PA key %d", n);
		cancel_if_idle_command();
		return;
	}
	if (kybdlock) {
		char nn[3];

		(void) sprintf(nn, "%d", n);
		enq_ta(PA_action, nn, CN);
		return;
	}
	key_AID(pa_xlate[n-1]);
}

/* PF key action for String actions */
static void
do_pf(unsigned n)
{
	if (n < 1 || n > PF_SZ) {
		popup_an_error("Unknown PF key %d", n);
		cancel_if_idle_command();
		return;
	}
	if (kybdlock) {
		char nn[3];

		(void) sprintf(nn, "%d", n);
		enq_ta(PF_action, nn, CN);
		return;
	}
	key_AID(pf_xlate[n-1]);
}

/*
 * Set or clear the keyboard scroll lock.
 */
void
kybd_scroll_lock(Boolean lock)
{
	if (!IN_3270)
		return;
	if (lock)
		kybdlock_set(KL_SCROLLED, "kybd_scroll_lock");
	else
		kybdlock_clr(KL_SCROLLED, "kybd_scroll_lock");
}

/*
 * Move the cursor back within the legal paste area.
 * Returns a Boolean indicating success.
 */
static Boolean
remargin(int lmargin)
{
	Boolean ever = False;
	int baddr, b0 = 0;
	int faddr;
	unsigned char fa;

	baddr = cursor_addr;
	while (BA_TO_COL(baddr) < lmargin) {
		baddr = ROWCOL_TO_BA(BA_TO_ROW(baddr), lmargin);
		if (!ever) {
			b0 = baddr;
			ever = True;
		}
		faddr = find_field_attribute(baddr);
		fa = ea_buf[faddr].fa;
		if (faddr == baddr || FA_IS_PROTECTED(fa)) {
			baddr = next_unprotected(baddr);
			if (baddr <= b0)
				return False;
		}
	}

	cursor_move(baddr);
	return True;
}

/*
 * Pretend that a sequence of keys was entered at the keyboard.
 *
 * "Pasting" means that the sequence came from the X clipboard.  Returns are
 * ignored; newlines mean "move to beginning of next line"; tabs and formfeeds
 * become spaces.  Backslashes are not special, but ASCII ESC characters are
 * used to signify 3270 Graphic Escapes.
 *
 * "Not pasting" means that the sequence is a login string specified in the
 * hosts file, or a parameter to the String action.  Returns are "move to
 * beginning of next line"; newlines mean "Enter AID" and the termination of
 * processing the string.  Backslashes are processed as in C.
 *
 * Returns the number of unprocessed characters.
 */
int
emulate_input(char *s, int len, Boolean pasting)
{
	enum {
	    BASE, BACKSLASH, BACKX, BACKP, BACKPA, BACKPF, OCTAL, HEX, XGE
	} state = BASE;
	int literal = 0;
	int nc = 0;
	enum iaction ia = pasting ? IA_PASTE : IA_STRING;
	int orig_addr = cursor_addr;
	int orig_col = BA_TO_COL(cursor_addr);
#if defined(X3270_DBCS) /*[*/
	unsigned char ebc[2];
	unsigned char cx;
	static UChar *w_ibuf = NULL;
	static size_t w_ibuf_len = 0;
	UChar c;
	UChar *ws;
#else /*][*/
	char c;
	char *ws;
#endif /*]*/

	/*
	 * Convert from a multi-byte string to a Unicode string.
	 */
#if defined(X3270_DBCS) /*[*/
	if (len > w_ibuf_len) {
		w_ibuf_len = len;
		w_ibuf = (UChar *)Realloc(w_ibuf, w_ibuf_len * sizeof(UChar));
	}
	len = mb_to_unicode(s, len, w_ibuf, w_ibuf_len, NULL);
	if (len < 0) {
		return 0; /* failed */
	}
	ws = w_ibuf;
#else /*][*/
	ws = s;
#endif /*]*/

	/*
	 * In the switch statements below, "break" generally means "consume
	 * this character," while "continue" means "rescan this character."
	 */
	while (len) {

		/*
		 * It isn't possible to unlock the keyboard from a string,
		 * so if the keyboard is locked, it's fatal
		 */
		if (kybdlock) {
			trace_event("  keyboard locked, string dropped\n");
			return 0;
		}

		if (pasting && IN_3270) {

			/* Check for cursor wrap to top of screen. */
			if (cursor_addr < orig_addr)
				return len-1;		/* wrapped */

			/* Jump cursor over left margin. */
			if (toggled(MARGINED_PASTE) &&
			    BA_TO_COL(cursor_addr) < orig_col) {
				if (!remargin(orig_col))
					return len-1;
			}
		}

		c = *ws;

		switch (state) {
		    case BASE:
			switch (c) {
			    case '\b':
				action_internal(Left_action, ia, CN, CN);
				continue;
			    case '\f':
				if (pasting) {
					key_ACharacter((unsigned char) ' ',
					    KT_STD, ia);
				} else {
					action_internal(Clear_action, ia, CN, CN);
					if (IN_3270)
						return len-1;
				}
				break;
			    case '\n':
				if (pasting)
					action_internal(Newline_action, ia, CN, CN);
				else {
					action_internal(Enter_action, ia, CN, CN);
					if (IN_3270)
						return len-1;
				}
				break;
			    case '\r':	/* ignored */
				break;
			    case '\t':
				action_internal(Tab_action, ia, CN, CN);
				break;
			    case '\\':	/* backslashes are NOT special when pasting */
				if (!pasting)
					state = BACKSLASH;
				else
					key_ACharacter((unsigned char) c,
					    KT_STD, ia);
				break;
			    case '\033': /* ESC is special only when pasting */
				if (pasting)
					state = XGE;
				break;
			    case '[':	/* APL left bracket */
				if (pasting && appres.apl_mode)
					key_ACharacter(
					    (unsigned char) XK_Yacute,
					    KT_GE, ia);
				else
					key_ACharacter((unsigned char) c,
					    KT_STD, ia);
				break;
			    case ']':	/* APL right bracket */
				if (pasting && appres.apl_mode)
					key_ACharacter(
					    (unsigned char) XK_diaeresis,
					    KT_GE, ia);
				else
					key_ACharacter((unsigned char) c,
					    KT_STD, ia);
				break;
			default:
#if defined(X3270_DBCS) /*[*/
				/*
				 * Try mapping it to the 8-bit character set,
				 * otherwise to the 16-bit character set.
				 */
				if (dbcs_map8(c, &cx)) {
					key_ACharacter((unsigned char)cx,
					    KT_STD, ia_cause);
					break;
				} else if (dbcs_map16(c, ebc)) {
					key_WCharacter(ebc);
					break;
				} else {
					trace_event("Cannot convert U+%04x to "
					    "EBCDIC\n", c & 0xffff);
					break;
				}
#endif /*]*/
				key_ACharacter((unsigned char) c, KT_STD,
				    ia);
				break;
			}
			break;
		    case BACKSLASH:	/* last character was a backslash */
			switch (c) {
			    case 'a':
				popup_an_error("%s: Bell not supported",
				    action_name(String_action));
				cancel_if_idle_command();
				state = BASE;
				break;
			    case 'b':
				action_internal(Left_action, ia, CN, CN);
				state = BASE;
				break;
			    case 'f':
				action_internal(Clear_action, ia, CN, CN);
				state = BASE;
				if (IN_3270)
					return len-1;
				else
					break;
			    case 'n':
				action_internal(Enter_action, ia, CN, CN);
				state = BASE;
				if (IN_3270)
					return len-1;
				else
					break;
			    case 'p':
				state = BACKP;
				break;
			    case 'r':
				action_internal(Newline_action, ia, CN, CN);
				state = BASE;
				break;
			    case 't':
				action_internal(Tab_action, ia, CN, CN);
				state = BASE;
				break;
			    case 'T':
				action_internal(BackTab_action, ia, CN, CN);
				state = BASE;
				break;
			    case 'v':
				popup_an_error("%s: Vertical tab not supported",
				    action_name(String_action));
				cancel_if_idle_command();
				state = BASE;
				break;
			    case 'x':
				state = BACKX;
				break;
			    case '\\':
				key_ACharacter((unsigned char) c, KT_STD, ia);
				state = BASE;
				break;
			    case '0': 
			    case '1': 
			    case '2': 
			    case '3':
			    case '4': 
			    case '5': 
			    case '6': 
			    case '7':
				state = OCTAL;
				literal = 0;
				nc = 0;
				continue;
			default:
				state = BASE;
				continue;
			}
			break;
		    case BACKP:	/* last two characters were "\p" */
			switch (c) {
			    case 'a':
				literal = 0;
				nc = 0;
				state = BACKPA;
				break;
			    case 'f':
				literal = 0;
				nc = 0;
				state = BACKPF;
				break;
			    default:
				popup_an_error("%s: Unknown character after \\p",
				    action_name(String_action));
				cancel_if_idle_command();
				state = BASE;
				break;
			}
			break;
		    case BACKPF: /* last three characters were "\pf" */
			if (nc < 2 && isdigit(c)) {
				literal = (literal * 10) + (c - '0');
				nc++;
			} else if (!nc) {
				popup_an_error("%s: Unknown character after \\pf",
				    action_name(String_action));
				cancel_if_idle_command();
				state = BASE;
			} else {
				do_pf(literal);
				if (IN_3270)
					return len-1;
				state = BASE;
				continue;
			}
			break;
		    case BACKPA: /* last three characters were "\pa" */
			if (nc < 1 && isdigit(c)) {
				literal = (literal * 10) + (c - '0');
				nc++;
			} else if (!nc) {
				popup_an_error("%s: Unknown character after \\pa",
				    action_name(String_action));
				cancel_if_idle_command();
				state = BASE;
			} else {
				do_pa(literal);
				if (IN_3270)
					return len-1;
				state = BASE;
				continue;
			}
			break;
		    case BACKX:	/* last two characters were "\x" */
			if (isxdigit(c)) {
				state = HEX;
				literal = 0;
				nc = 0;
				continue;
			} else {
				popup_an_error("%s: Missing hex digits after \\x",
				    action_name(String_action));
				cancel_if_idle_command();
				state = BASE;
				continue;
			}
		    case OCTAL:	/* have seen \ and one or more octal digits */
			if (nc < 3 && isdigit(c) && c < '8') {
				literal = (literal * 8) + FROM_HEX(c);
				nc++;
				break;
			} else {
				key_ACharacter((unsigned char) literal, KT_STD,
				    ia);
				state = BASE;
				continue;
			}
		    case HEX:	/* have seen \ and one or more hex digits */
			if (nc < 2 && isxdigit(c)) {
				literal = (literal * 16) + FROM_HEX(c);
				nc++;
				break;
			} else {
				key_ACharacter((unsigned char) literal, KT_STD,
				    ia);
				state = BASE;
				continue;
			}
		    case XGE:	/* have seen ESC */
			switch (c) {
			    case ';':	/* FM */
				key_Character(EBC_fm, False, True);
				break;
			    case '*':	/* DUP */
				key_Character(EBC_dup, False, True);
				break;
			    default:
				key_ACharacter((unsigned char) c, KT_GE, ia);
				break;
			}
			state = BASE;
			break;
		}
		ws++;
		len--;
	}

	switch (state) {
	    case BASE:
		if (toggled(MARGINED_PASTE) &&
		    BA_TO_COL(cursor_addr) < orig_col) {
			(void) remargin(orig_col);
		}
		break;
	    case OCTAL:
	    case HEX:
		key_ACharacter((unsigned char) literal, KT_STD, ia);
		state = BASE;
		if (toggled(MARGINED_PASTE) &&
		    BA_TO_COL(cursor_addr) < orig_col) {
			(void) remargin(orig_col);
		}
		break;
	    case BACKPF:
		if (nc > 0) {
			do_pf(literal);
			state = BASE;
		}
		break;
	    case BACKPA:
		if (nc > 0) {
			do_pa(literal);
			state = BASE;
		}
		break;
	    default:
		popup_an_error("%s: Missing data after \\",
		    action_name(String_action));
		cancel_if_idle_command();
		break;
	}

	return len;
}

/*
 * Pretend that a sequence of hexadecimal characters was entered at the
 * keyboard.  The input is a sequence of hexadecimal bytes, 2 characters
 * per byte.  If connected in ANSI mode, these are treated as ASCII
 * characters; if in 3270 mode, they are considered EBCDIC.
 *
 * Graphic Escapes are handled as \E.
 */
void
hex_input(char *s)
{
	char *t;
	Boolean escaped;
#if defined(X3270_ANSI) /*[*/
	unsigned char *xbuf = (unsigned char *)NULL;
	unsigned char *tbuf = (unsigned char *)NULL;
	int nbytes = 0;
#endif /*]*/

	/* Validate the string. */
	if (strlen(s) % 2) {
		popup_an_error("%s: Odd number of characters in specification",
		    action_name(HexString_action));
		cancel_if_idle_command();
		return;
	}
	t = s;
	escaped = False;
	while (*t) {
		if (isxdigit(*t) && isxdigit(*(t + 1))) {
			escaped = False;
#if defined(X3270_ANSI) /*[*/
			nbytes++;
#endif /*]*/
		} else if (!strncmp(t, "\\E", 2) || !strncmp(t, "\\e", 2)) {
			if (escaped) {
				popup_an_error("%s: Double \\E",
				    action_name(HexString_action));
				cancel_if_idle_command();
				return;
			}
			if (!IN_3270) {
				popup_an_error("%s: \\E in ANSI mode",
				    action_name(HexString_action));
				cancel_if_idle_command();
				return;
			}
			escaped = True;
		} else {
			popup_an_error("%s: Illegal character in specification",
			    action_name(HexString_action));
			cancel_if_idle_command();
			return;
		}
		t += 2;
	}
	if (escaped) {
		popup_an_error("%s: Nothing follows \\E",
		    action_name(HexString_action));
		cancel_if_idle_command();
		return;
	}

#if defined(X3270_ANSI) /*[*/
	/* Allocate a temporary buffer. */
	if (!IN_3270 && nbytes)
		tbuf = xbuf = (unsigned char *)Malloc(nbytes);
#endif /*]*/

	/* Pump it in. */
	t = s;
	escaped = False;
	while (*t) {
		if (isxdigit(*t) && isxdigit(*(t + 1))) {
			unsigned c;

			c = (FROM_HEX(*t) * 16) + FROM_HEX(*(t + 1));
			if (IN_3270)
				key_Character(c, escaped, True);
#if defined(X3270_ANSI) /*[*/
			else
				*tbuf++ = (unsigned char)c;
#endif /*]*/
			escaped = False;
		} else if (!strncmp(t, "\\E", 2) || !strncmp(t, "\\e", 2)) {
			escaped = True;
		}
		t += 2;
	}
#if defined(X3270_ANSI) /*[*/
	if (!IN_3270 && nbytes) {
		net_hexansi_out(xbuf, nbytes);
		Free(xbuf);
	}
#endif /*]*/
}
 
void
ignore_action(Widget w unused, XEvent *event, String *params, Cardinal *num_params)
{
	action_debug(ignore_action, event, params, num_params);
	reset_idle_timer();
}

#if defined(X3270_FT) /*[*/
/*
 * Set up the cursor and input field for command input.
 * Returns the length of the input field, or 0 if there is no field
 * to set up.
 */
int
kybd_prime(void)
{
	int baddr;
	register unsigned char fa;
	int len = 0;

	/*
	 * No point in trying if the screen isn't formatted, the keyboard
	 * is locked, or we aren't in 3270 mode.
	 */
	if (!formatted || kybdlock || !IN_3270)
		return 0;

	fa = get_field_attribute(cursor_addr);
	if (ea_buf[cursor_addr].fa || FA_IS_PROTECTED(fa)) {
		/*
		 * The cursor is not in an unprotected field.  Find the
		 * next one.
		 */
		baddr = next_unprotected(cursor_addr);

		/* If there isn't any, give up. */
		if (!baddr)
			return 0;

		/* Move the cursor there. */
	} else {
		/* Already in an unprotected field.  Find its start. */
		baddr = cursor_addr;
		while (!ea_buf[baddr].fa) {
			DEC_BA(baddr);
		}
		INC_BA(baddr);
	}

	/* Move the cursor to the beginning of the field. */
	cursor_move(baddr);

	/* Erase it. */
	while (!ea_buf[baddr].fa) {
		ctlr_add(baddr, 0, 0);
		len++;
		INC_BA(baddr);
	}

	/* Return the field length. */
	return len;
}
#endif /*]*/

/*
 * Translate a keysym name to a keysym, including APL and extended
 * characters.
 */
static KeySym
MyStringToKeysym(char *s, enum keytype *keytypep)
{
	KeySym k;
	int cc;
	char *ptr;

#if defined(X3270_APL) /*[*/
	if (!strncmp(s, "apl_", 4)) {
		int is_ge;

		k = APLStringToKeysym(s, &is_ge);
		if (is_ge)
			*keytypep = KT_GE;
		else
			*keytypep = KT_STD;
	} else
#endif /*]*/
	{
		k = StringToKeysym(s);
		*keytypep = KT_STD;
	}
	if (k == NoSymbol && !strcasecmp(s, "euro"))
		k = 0xa4;
	if (k == NoSymbol && strlen(s) == 1)
		k = s[0] & 0xff;
	if (k < ' ')
		k = NoSymbol;
	else if (k > 0xff) {
		int i;

		for (i = 0; i < nxk; i++)
			if (xk[i].key == k) {
				k = xk[i].assoc;
				break;
			}
		if (k > 0xff)
			k &= 0xff;
	}

	/* Allow arbitrary values, e.g., 0x03 for ^C. */
	if (k == NoSymbol &&
	    (cc = strtoul(s, &ptr, 0)) > 0 &&
	    cc < 0xff &&
	    ptr != s &&
	    *ptr == '\0')
		k = cc;

	return k;
}

/* Add a key to the extended association table. */
void
add_xk(KeySym key, KeySym assoc)
{
	int i;

	for (i = 0; i < nxk; i++)
		if (xk[i].key == key) {
			xk[i].assoc = assoc;
			return;
		}
	xk = (struct xks *)Realloc(xk, (nxk + 1) * sizeof(struct xks));
	xk[nxk].key = key;
	xk[nxk].assoc = assoc;
	nxk++;
}

/* Clear the extended association table. */
void
clear_xks(void)
{
	if (nxk) {
		Free(xk);
		xk = (struct xks *)NULL;
		nxk = 0;
	}
}

#if defined(X3270_DISPLAY) /*[*/
/*
 * X-dependent code starts here.
 */

/*
 * Translate a keymap (from an XQueryKeymap or a KeymapNotify event) into
 * a bitmap of Shift, Meta or Alt keys pressed.
 */
#define key_is_down(kc, bitmap) (kc && ((bitmap)[(kc)/8] & (1<<((kc)%8))))
int
state_from_keymap(char keymap[32])
{
	static Boolean	initted = False;
	static KeyCode	kc_Shift_L, kc_Shift_R;
	static KeyCode	kc_Meta_L, kc_Meta_R;
	static KeyCode	kc_Alt_L, kc_Alt_R;
	int	pseudo_state = 0;

	if (!initted) {
		kc_Shift_L = XKeysymToKeycode(display, XK_Shift_L);
		kc_Shift_R = XKeysymToKeycode(display, XK_Shift_R);
		kc_Meta_L  = XKeysymToKeycode(display, XK_Meta_L);
		kc_Meta_R  = XKeysymToKeycode(display, XK_Meta_R);
		kc_Alt_L   = XKeysymToKeycode(display, XK_Alt_L);
		kc_Alt_R   = XKeysymToKeycode(display, XK_Alt_R);
		initted = True;
	}
	if (key_is_down(kc_Shift_L, keymap) ||
	    key_is_down(kc_Shift_R, keymap))
		pseudo_state |= ShiftKeyDown;
	if (key_is_down(kc_Meta_L, keymap) ||
	    key_is_down(kc_Meta_R, keymap))
		pseudo_state |= MetaKeyDown;
	if (key_is_down(kc_Alt_L, keymap) ||
	    key_is_down(kc_Alt_R, keymap))
		pseudo_state |= AltKeyDown;
	return pseudo_state;
}
#undef key_is_down

/*
 * Process shift keyboard events.  The code has to look for the raw Shift keys,
 * rather than using the handy "state" field in the event structure.  This is
 * because the event state is the state _before_ the key was pressed or
 * released.  This isn't enough information to distinguish between "left
 * shift released" and "left shift released, right shift still held down"
 * events, for example.
 *
 * This function is also called as part of Focus event processing.
 */
void
PA_Shift_action(Widget w unused, XEvent *event unused, String *params unused,
    Cardinal *num_params unused)
{
	char	keys[32];

#if defined(INTERNAL_ACTION_DEBUG) /*[*/
	action_debug(PA_Shift_action, event, params, num_params);
#endif /*]*/
	XQueryKeymap(display, keys);
	shift_event(state_from_keymap(keys));
}
#endif /*]*/

#if defined(X3270_DISPLAY) || defined(C3270) /*[*/
static Boolean
build_composites(void)
{
	char *c, *c0, *c1;
	char *ln;
	char ksname[3][64];
	char junk[2];
	KeySym k[3];
	enum keytype a[3];
	int i;
	struct composite *cp;

	if (appres.compose_map == CN) {
		popup_an_error("%s: No %s defined", action_name(Compose_action),
		    ResComposeMap);
		return False;
	}
	c0 = get_fresource("%s.%s", ResComposeMap, appres.compose_map);
	if (c0 == CN) {
		popup_an_error("%s: Cannot find %s \"%s\"",
		    action_name(Compose_action), ResComposeMap,
		    appres.compose_map);
		return False;
	}
	c1 = c = NewString(c0);	/* will be modified by strtok */
	while ((ln = strtok(c, "\n"))) {
		Boolean okay = True;

		c = NULL;
		if (sscanf(ln, " %63[^+ \t] + %63[^= \t] =%63s%1s",
		    ksname[0], ksname[1], ksname[2], junk) != 3) {
			popup_an_error("%s: Invalid syntax: %s",
			    action_name(Compose_action), ln);
			continue;
		}
		for (i = 0; i < 3; i++) {
			k[i] = MyStringToKeysym(ksname[i], &a[i]);
			if (k[i] == NoSymbol) {
				popup_an_error("%s: Invalid KeySym: \"%s\"",
				    action_name(Compose_action), ksname[i]);
				okay = False;
				break;
			}
		}
		if (!okay)
			continue;
		composites = (struct composite *) Realloc((char *)composites,
		    (n_composites + 1) * sizeof(struct composite));
		cp = composites + n_composites;
		cp->k1.keysym = k[0];
		cp->k1.keytype = a[0];
		cp->k2.keysym = k[1];
		cp->k2.keytype = a[1];
		cp->translation.keysym = k[2];
		cp->translation.keytype = a[2];
		n_composites++;
	}
	Free(c1);
	return True;
}

/*
 * Called by the toolkit when the "Compose" key is pressed.  "Compose" is
 * implemented by pressing and releasing three keys: "Compose" and two
 * data keys.  For example, "Compose" "s" "s" gives the German "ssharp"
 * character, and "Compose" "C", "," gives a capital "C" with a cedilla
 * (symbol Ccedilla).
 *
 * The mechanism breaks down a little when the user presses "Compose" and
 * then a non-data key.  Oh well.
 */
void
Compose_action(Widget w unused, XEvent *event, String *params, Cardinal *num_params)
{
	action_debug(Compose_action, event, params, num_params);
	reset_idle_timer();

	if (!composites && !build_composites())
		return;

	if (composing == NONE) {
		composing = COMPOSE;
		status_compose(True, 0, KT_STD);
	}
}
#endif /*]*/

#if defined(X3270_DISPLAY) /*[*/

/*
 * Called by the toolkit for any key without special actions.
 */
void
Default_action(Widget w unused, XEvent *event, String *params, Cardinal *num_params)
{
	XKeyEvent	*kevent = (XKeyEvent *)event;
	char		buf[32];
	KeySym		ks;
	int		ll;

	action_debug(Default_action, event, params, num_params);
	switch (event->type) {
	    case KeyPress:
#if defined(X3270_DBCS) /*[*/
		if (!xim_lookup((XKeyEvent *)event))
			return;
#endif /*]*/
		ll = XLookupString(kevent, buf, 32, &ks, (XComposeStatus *) 0);
		if (ll == 1) {
			/* Add Meta; XLookupString won't. */
			if (event_is_meta(kevent->state))
				buf[0] |= 0x80;
			/* Remap certain control characters. */
			if (!IN_ANSI) switch (buf[0]) {
			    case '\t':
				action_internal(Tab_action, IA_DEFAULT, CN, CN);
				break;
			   case '\177':
				action_internal(Delete_action, IA_DEFAULT, CN,
				    CN);
				break;
			    case '\b':
				action_internal(Erase_action, IA_DEFAULT,
				    CN, CN);
				break;
			    case '\r':
				action_internal(Enter_action, IA_DEFAULT, CN,
				    CN);
				break;
			    case '\n':
				action_internal(Newline_action, IA_DEFAULT, CN,
				    CN);
				break;
			    default:
				key_ACharacter((unsigned char) buf[0], KT_STD,
				    IA_DEFAULT);
				break;
			} else {
				key_ACharacter((unsigned char) buf[0], KT_STD,
				    IA_DEFAULT);
			}
			return;
		}

		/* Pick some other reasonable defaults. */
		switch (ks) {
		    case XK_Up:
			action_internal(Up_action, IA_DEFAULT, CN, CN);
			break;
		    case XK_Down:
			action_internal(Down_action, IA_DEFAULT, CN, CN);
			break;
		    case XK_Left:
			action_internal(Left_action, IA_DEFAULT, CN, CN);
			break;
		    case XK_Right:
			action_internal(Right_action, IA_DEFAULT, CN, CN);
			break;
		    case XK_Insert:
#if defined(XK_KP_Insert) /*[*/
		    case XK_KP_Insert:
#endif /*]*/
			action_internal(Insert_action, IA_DEFAULT, CN, CN);
			break;
		    case XK_Delete:
			action_internal(Delete_action, IA_DEFAULT, CN, CN);
			break;
		    case XK_Home:
			action_internal(Home_action, IA_DEFAULT, CN, CN);
			break;
		    case XK_Tab:
			action_internal(Tab_action, IA_DEFAULT, CN, CN);
			break;
#if defined(XK_ISO_Left_Tab) /*[*/
		    case XK_ISO_Left_Tab:
			action_internal(BackTab_action, IA_DEFAULT, CN, CN);
			break;
#endif /*]*/
		    case XK_Clear:
			action_internal(Clear_action, IA_DEFAULT, CN, CN);
			break;
		    case XK_Sys_Req:
			action_internal(SysReq_action, IA_DEFAULT, CN, CN);
			break;
#if defined(XK_EuroSign) /*[*/
		    case XK_EuroSign:
			action_internal(Key_action, IA_DEFAULT, "currency",
				CN);
			break;
#endif /*]*/

#if defined(XK_3270_Duplicate) /*[*/
		    /* Funky 3270 keysyms. */
		    case XK_3270_Duplicate:
			action_internal(Dup_action, IA_DEFAULT, CN, CN);
			break;
		    case XK_3270_FieldMark:
			action_internal(FieldMark_action, IA_DEFAULT, CN, CN);
			break;
		    case XK_3270_Right2:
			action_internal(Right2_action, IA_DEFAULT, CN, CN);
			break;
		    case XK_3270_Left2:
			action_internal(Left2_action, IA_DEFAULT, CN, CN);
			break;
		    case XK_3270_BackTab:
			action_internal(BackTab_action, IA_DEFAULT, CN, CN);
			break;
		    case XK_3270_EraseEOF:
			action_internal(EraseEOF_action, IA_DEFAULT, CN, CN);
			break;
		    case XK_3270_EraseInput:
			action_internal(EraseInput_action, IA_DEFAULT, CN, CN);
			break;
		    case XK_3270_Reset:
			action_internal(Reset_action, IA_DEFAULT, CN, CN);
			break;
		    case XK_3270_PA1:
			action_internal(PA_action, IA_DEFAULT, "1", CN);
			break;
		    case XK_3270_PA2:
			action_internal(PA_action, IA_DEFAULT, "2", CN);
			break;
		    case XK_3270_PA3:
			action_internal(PA_action, IA_DEFAULT, "3", CN);
			break;
		    case XK_3270_Attn:
			action_internal(Attn_action, IA_DEFAULT, CN, CN);
			break;
		    case XK_3270_AltCursor:
			action_internal(AltCursor_action, IA_DEFAULT, CN, CN);
			break;
		    case XK_3270_CursorSelect:
			action_internal(CursorSelect_action, IA_DEFAULT, CN,
			    CN);
			break;
		    case XK_3270_Enter:
			action_internal(Enter_action, IA_DEFAULT, CN, CN);
			break;
#endif /*]*/

#if defined(X3270_APL) /*[*/
		    /* Funky APL keysyms. */
		    case XK_downcaret:
			action_internal(Key_action, IA_DEFAULT, "apl_downcaret",
			    CN);
			break;
		    case XK_upcaret:
			action_internal(Key_action, IA_DEFAULT, "apl_upcaret",
			    CN);
			break;
		    case XK_overbar:
			action_internal(Key_action, IA_DEFAULT, "apl_overbar",
			    CN);
			break;
		    case XK_downtack:
			action_internal(Key_action, IA_DEFAULT, "apl_downtack",
			    CN);
			break;
		    case XK_upshoe:
			action_internal(Key_action, IA_DEFAULT, "apl_upshoe",
			    CN);
			break;
		    case XK_downstile:
			action_internal(Key_action, IA_DEFAULT, "apl_downstile",
			    CN);
			break;
		    case XK_underbar:
			action_internal(Key_action, IA_DEFAULT, "apl_underbar",
			    CN);
			break;
		    case XK_jot:
			action_internal(Key_action, IA_DEFAULT, "apl_jot", CN);
			break;
		    case XK_quad:
			action_internal(Key_action, IA_DEFAULT, "apl_quad", CN);
			break;
		    case XK_uptack:
			action_internal(Key_action, IA_DEFAULT, "apl_uptack",
			    CN);
			break;
		    case XK_circle:
			action_internal(Key_action, IA_DEFAULT, "apl_circle",
			    CN);
			break;
		    case XK_upstile:
			action_internal(Key_action, IA_DEFAULT, "apl_upstile",
			    CN);
			break;
		    case XK_downshoe:
			action_internal(Key_action, IA_DEFAULT, "apl_downshoe",
			    CN);
			break;
		    case XK_rightshoe:
			action_internal(Key_action, IA_DEFAULT, "apl_rightshoe",
			    CN);
			break;
		    case XK_leftshoe:
			action_internal(Key_action, IA_DEFAULT, "apl_leftshoe",
			    CN);
			break;
		    case XK_lefttack:
			action_internal(Key_action, IA_DEFAULT, "apl_lefttack",
			    CN);
			break;
		    case XK_righttack:
			action_internal(Key_action, IA_DEFAULT, "apl_righttack",
			    CN);
			break;
#endif /*]*/

		    default:
			if (ks >= XK_F1 && ks <= XK_F24) {
				(void) sprintf(buf, "%ld", ks - XK_F1 + 1);
				action_internal(PF_action, IA_DEFAULT, buf, CN);
#if defined(XK_CYRILLIC) /*[*/
			} else if (ks == XK_Cyrillic_io ||
				   ks == XK_Cyrillic_IO ||
				   (ks >= XK_Cyrillic_yu &&
				    ks <= XK_Cyrillic_HARDSIGN)) {
				/* Map XK to KOI8-R. */
				(void) sprintf(buf, "%ld", ks & 0xff);
				action_internal(Key_action, IA_DEFAULT, buf,
						CN);
#endif /*]*/
			} else
				trace_event(" %s: dropped (unknown keysym)\n",
				    action_name(Default_action));
			break;
		}
		break;

	    case ButtonPress:
	    case ButtonRelease:
		trace_event(" %s: dropped (no action configured)\n",
		    action_name(Default_action));
		break;
	    default:
		trace_event(" %s: dropped (unknown event type)\n",
		    action_name(Default_action));
		break;
	}
}

/*
 * Set or clear a temporary keymap.
 *
 *   TemporaryKeymap(x)		toggle keymap "x" (add "x" to the keymap, or if
 *				"x" was already added, remove it)
 *   TemporaryKeymap()		removes the previous keymap, if any
 *   TemporaryKeymap(None)	removes the previous keymap, if any
 */
void
TemporaryKeymap_action(Widget w unused, XEvent *event, String *params, Cardinal *num_params)
{
	action_debug(TemporaryKeymap_action, event, params, num_params);
	reset_idle_timer();

	if (check_usage(TemporaryKeymap_action, *num_params, 0, 1) < 0)
		return;

	if (*num_params == 0 || !strcmp(params[0], "None")) {
		(void) temporary_keymap(CN);
		return;
	}

	if (temporary_keymap(params[0]) < 0) {
		popup_an_error("%s: Can't find %s %s",
		    action_name(TemporaryKeymap_action), ResKeymap, params[0]);
		cancel_if_idle_command();
	}
}

#endif /*]*/


syntax highlighted by Code2HTML, v. 0.9.1