#ifndef lint
static char *rcsid = "$Id: IMProto.c,v 1.20 1999/04/12 08:52:23 ishisone Exp $";
#endif
/*- 
 * Copyright (c) 1991, 1994  Software Research Associates, Inc.
 *
 * 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, and that the name of Software Research Associates not be
 * used in advertising or publicity pertaining to distribution of the
 * software without specific, written prior permission.  Software Research
 * Associates makes no representations about the suitability of this software
 * for any purpose.  It is provided "as is" without express or implied
 * warranty.
 *
 * Author:  Makoto Ishisone, Software Research Associates, Inc., Japan
 */

/*
 * X Input Method Protocol handler is considered to be still in
 * beta testing phase.  Current version has the following
 * restrictions.
 *	- it does not support on-demand-synchronous method.
 *	- it does not support front-end model.
 *	- it does not SetIMValues operation.
 *	- it supports only X, local and TCP transports.
 * Also, there might be various bugs.
 */

#define DEBUG_VAR debug_IMProtocol

#include <ctype.h>
#include <X11/Xos.h>
#include <sys/stat.h>
#include <X11/IntrinsicP.h>
#include <X11/StringDefs.h>
#include <X11/Xatom.h>
#include <X11/Xmu/SysUtil.h>
#include "IMProtoP.h"
#include "ParseKey.h"
#include "InputConv.h"
#include "im.h"


#define SERVER_NAME		"kinput2"
#define UNIX_SOCKET_DIR		"/tmp/.ki2-unix"

/*- resource table -*/
static XtResource resources[] = {
#define offset(field) XtOffset(IMProtocolWidget, imp.field)
    { XtNserverName, XtCServerName, XtRString, sizeof(String),
	offset(server_name), XtRString, (XtPointer)SERVER_NAME },
    { XtNlanguage, XtCLanguage, XtRString, sizeof(String),
	offset(language), XtRImmediate, (XtPointer)NULL },
    { XtNlocales, XtCLocales, XtRString, sizeof(String),
	offset(locales), XtRImmediate, (XtPointer)NULL },
    { XtNinputObjectClass, XtCClass, XtRPointer, sizeof(WidgetClass),
	offset(input_object_class), XtRImmediate, (XtPointer)NULL },
    { XtNdisplayObjectClass, XtCClass, XtRPointer, sizeof(WidgetClass),
	offset(display_object_class), XtRImmediate, (XtPointer)NULL },
    { XtNdefaultFontList, XtCFontList, XtRString, sizeof(String),
	offset(default_fontlist), XtRImmediate, (XtPointer)NULL },
    { XtNconversionStartKeys, XtCConversionStartKeys, XtRString, sizeof(String),
	offset(conversion_start_keys), XtRImmediate, (XtPointer)NULL },
    { XtNforeground, XtCForeground, XtRPixel, sizeof (Pixel),
	offset(foreground), XtRString, XtDefaultForeground },
    { XtNstatusWidth, XtCStatusWidth, XtRDimension, sizeof(Dimension),
	offset(status_width), XtRImmediate, (XtPointer)0 },
    { XtNtransports, XtCTransports, XtRString, sizeof(String),
	offset(transport_list), XtRString, (XtPointer)"tcp,unix,x" },
#undef offset
};

static void Initialize _Pt_((Widget req, Widget new,
			     ArgList args, Cardinal *num_args));
static void Destroy _Pt_((Widget w));
static void Realize _Pt_((Widget w, XtValueMask *mask,
			  XSetWindowAttributes *value));

/*- IMProtocolClassRec -*/
IMProtocolClassRec imProtocolClassRec = {
  { /* core fields */
    /* superclass		*/	(WidgetClass) &widgetClassRec,
    /* class_name		*/	"IMProtocol",
    /* widget_size		*/	sizeof(IMProtocolRec),
    /* class_initialize		*/	NULL,
    /* class_part_initialize	*/	NULL,
    /* class_inited		*/	FALSE,
    /* initialize		*/	Initialize,
    /* initialize_hook		*/	NULL,
    /* realize			*/	Realize,
    /* actions			*/	NULL,
    /* num_actions		*/	0,
    /* resources		*/	resources,
    /* num_resources		*/	XtNumber(resources),
    /* xrm_class		*/	NULLQUARK,
    /* compress_motion		*/	TRUE,
    /* compress_exposure	*/	TRUE,
    /* compress_enterleave	*/	TRUE,
    /* visible_interest		*/	FALSE,
    /* destroy			*/	Destroy,
    /* resize			*/	NULL,
    /* expose			*/	NULL,
    /* set_values		*/	NULL,
    /* set_values_hook		*/	NULL,
    /* set_values_almost	*/	XtInheritSetValuesAlmost,
    /* get_values_hook		*/	NULL,
    /* accept_focus		*/	NULL,
    /* version			*/	XtVersion,
    /* callback_private		*/	NULL,
    /* tm_table			*/	NULL,
    /* query_geometry		*/	XtInheritQueryGeometry,
    /* display_accelerator	*/	XtInheritDisplayAccelerator,
    /* extension		*/	NULL
  },
  { /* imProtocol fields */
    /* dummy			*/	0
  }
};

WidgetClass imProtocolWidgetClass = (WidgetClass)&imProtocolClassRec;

static void getAtoms _Pt_((IMProtocolWidget ipw));
static void setProperty _Pt_((IMProtocolWidget ipw));
static int ownSelection _Pt_((IMProtocolWidget ipw));
static Boolean convertSelection _Pt_((Widget w, Atom *selectionp,
				      Atom *targetp, Atom *typep,
				      XtPointer *valuep,
				      unsigned long *lengthp, int *formatp));
static void loseSelection _Pt_((Widget w, Atom *selectionp));
#ifdef IM_TCP_TRANSPORT
static void acceptTCPService _Pt_((XtPointer client_data,
				  int *sourcep, XtInputId *idp));
#endif
#ifdef IM_UNIX_TRANSPORT
static void acceptUnixService _Pt_((XtPointer client_data,
				    int *sourcep, XtInputId *idp));
#endif
#ifdef IM_X_TRANSPORT
static void acceptXService _Pt_((Widget w, XtPointer client_data,
				 XEvent *event, Boolean *continuep));
#endif
static void initializeError _Pt_((Widget w, String resname));
static char *compactList _Pt_((char *s));
static void setTransport _Pt_((Widget w));
static int makeConverter _Pt_((Widget w));
static void getTriggerKeys _Pt_((Widget w));
static void ioeCallback _Pt_((XPointer cldata));


/*
 *+ Core class methods
 */

/*- Initialize: intern Atoms, get default fonts, etc. -*/
/* ARGSUSED */
static void
Initialize(req, new, args, num_args)
Widget req;
Widget new;
ArgList args;
Cardinal *num_args;
{
    IMProtocolWidget ipw = (IMProtocolWidget)new;

    TRACE(("IMProtocolWidget:Initialize()\n"));

    /*
     * Check resources which must be specified at the initialization.
     */
#define NULLorEMPTY(p)	((p) == NULL || (p)[0] == '\0')
    if (NULLorEMPTY(ipw->imp.server_name)) {
	initializeError(new, XtNserverName);
    }

    ipw->imp.server_name = XtNewString(ipw->imp.server_name);

    if (NULLorEMPTY(ipw->imp.language)) {
	initializeError(new, XtNlanguage);
    } else if (NULLorEMPTY(ipw->imp.locales)) {
	initializeError(new, XtNlocales);
    } else if (ipw->imp.input_object_class == NULL) {
	initializeError(new, XtNinputObjectClass);
    } else if (ipw->imp.display_object_class == NULL) {
	initializeError(new, XtNdisplayObjectClass);
    }
    ipw->imp.locales = compactList(XtNewString(ipw->imp.locales));
#undef NULLorEMPTY

    /*
     * Initialize converter info.
     */
    if (makeConverter(new) < 0) {
	/*
	 * locales is empty.
	 */
	String params[1];
	Cardinal num_params;

	params[0] = XtClass(new)->core_class.class_name;
	num_params = 1;
	XtAppErrorMsg(XtWidgetToApplicationContext(new),
		      "initializeError", "invalidValue", "WidgetError",
		      "%s: locale list is empty",
		      params, &num_params);
    }

    /*
     * Create font bank (a bank of cached fonts) and enter
     * default fonts.
     */
    ipw->imp.font_bank = FontBankCreate(XtDisplay(new), ipw->imp.language);
    if (ipw->imp.font_bank == NULL) {
	/*
	 * The specified language is not supported.
	 */
	String params[2];
	Cardinal num_params;

	params[0] = XtClass(new)->core_class.class_name;
	params[1] = ipw->imp.language;
	num_params = 2;
	XtAppErrorMsg(XtWidgetToApplicationContext(new),
		      "initializeError", "invalidValue", "WidgetError",
		      "%s: language %s not supported",
		      params, &num_params);
    }

    if (ipw->imp.default_fontlist != NULL) {
	ipw->imp.default_fontlist = XtNewString(ipw->imp.default_fontlist);

	DDPRINT(2, ("cache default fonts: %s\n", ipw->imp.default_fontlist));
	ipw->imp.default_fonts = FontBankGet(ipw->imp.font_bank,
					     ipw->imp.default_fontlist,
					     &ipw->imp.num_default_fonts);
    } else {
	ipw->imp.default_fonts = NULL;
	ipw->imp.num_default_fonts = 0;
    }

    /*
     * Initialize private data.
     */
    ipw->imp.connection_list = NULL;
    ipw->imp.no_more_connections = False;
    ipw->imp.scheduler_queue = NULL;
    setTransport(new);
    getTriggerKeys(new);
    IMInitHash(new);
    getAtoms(ipw);

    /*
     * Initialilze transport layer.
     */
    /* 1. TCP/IP */
    ipw->imp.tcp_sock = -1;
#ifdef IM_TCP_TRANSPORT
    if (ipw->imp.use_tcp_transport) {
	ipw->imp.tcp_port = 0;	/* let the system choose the port number */
	ipw->imp.tcp_sock = IMCreateTCPService(&ipw->imp.tcp_port);
    }
    if (ipw->imp.tcp_sock >= 0) {
	TRACE(("call XtAppAddInput for tcp socket(%d)\n", ipw->imp.tcp_sock));
	ipw->imp.tcp_id = XtAppAddInput(XtWidgetToApplicationContext(new),
					ipw->imp.tcp_sock,
					(XtPointer)XtInputReadMask,
					acceptTCPService, (XtPointer)ipw);
    }
#endif /* IM_TCP_TRANSPORT */

    /* 2. UNIX domain */
    ipw->imp.unix_sock = -1;
#ifdef IM_UNIX_TRANSPORT
    if (ipw->imp.use_unix_transport) {
	char path[1024];
	char *p;

	/*
	 * The unix domain socket pathname has the following form:
	 *   <UNIX_SOCKET_DIR>/<Display Name>-<Language>
	 */
	(void)mkdir(UNIX_SOCKET_DIR, 0777);
#ifdef S_IFLNK
	{
	    /*
	     * This system has symbolic links.  Make sure UNIX_SOCKET_DIR
	     * is not a symbolic link but a directory before calling
	     * chmod().
	     */
	    struct stat st;
	    if (lstat(UNIX_SOCKET_DIR, &st) == 0 &&
		(st.st_mode & S_IFMT) == S_IFDIR) {
		(void)chmod(UNIX_SOCKET_DIR, 0777);
	    }
	}
#else
	(void)chmod(UNIX_SOCKET_DIR, 0777);
#endif
	(void)sprintf(path, "%s/%s", UNIX_SOCKET_DIR,
		      DisplayString(XtDisplay(new)));
	/*
	 * Omit screen number and the preceding period.
	 */
	for (p = path + strlen(path) - 1; p > path && *p != ':'; p--) {
	    if (*p == '.') {
		*p = '\0';
		break;
	    }
	}
	/*
	 * Append language part.
	 */
	(void)strcat(path, "-");
	(void)strcat(path, ipw->imp.language);
	/*
	 * Change every ':' in the path name to '_', since ':' is not
	 * included in POSIX Portable Filename Character Set.
	 */
	for (p = path; *p != '\0'; p++) {
	    if (*p == ':') *p = '_';
	}
	ipw->imp.unix_path = XtNewString(path);
	ipw->imp.unix_sock = IMCreateUnixService(ipw->imp.unix_path);
    }
    if (ipw->imp.unix_sock >= 0) {
	TRACE(("call XtAppAddInput for unix socket(%d)\n", ipw->imp.unix_sock));
	ipw->imp.unix_id = XtAppAddInput(XtWidgetToApplicationContext(new),
					 ipw->imp.unix_sock,
					 (XtPointer)XtInputReadMask,
					 acceptUnixService, (XtPointer)ipw);
	ipw->imp.ioe_handle = XIOESet(ioeCallback, (XPointer)ipw);
    }
#endif /* IM_UNIX_TRANSPORT */

#ifdef IM_X_TRANSPORT
    if (ipw->imp.use_x_transport) {
	TRACE(("call XtAddEventHandler for X transport\n"));
	XtAddEventHandler(new, NoEventMask, True, acceptXService,
			  (XtPointer)NULL);
    }
#endif /* IM_X_TRANSPORT */

    /*
     * Compile request dispatching table.
     */
    IMCompileReq();
}

/*- Destroy: free allocated memory -*/
static void
Destroy(w)
Widget w;
{
    IMProtocolWidget ipw = (IMProtocolWidget)w;
    IMConnection *conn;
    int i;

    TRACE(("IMProtocolWidget:Destroy()\n"));

    XtFree(ipw->imp.server_name);
    XtFree(ipw->imp.locales);
    if (ipw->imp.default_fontlist != NULL) XtFree(ipw->imp.default_fontlist);
    if (ipw->imp.trigger_keys != NULL) XtFree((char *)ipw->imp.trigger_keys);

    for (i = 0; i < ipw->imp.converter.num_locales; i++) {
	XtFree(ipw->imp.converter.supported_locales[i]);
    }
    XtFree((char *)ipw->imp.converter.supported_locales);

    /*
     * Close down all connections.
     */
    conn = ipw->imp.connection_list;
    while (conn != NULL) {
	IMConnection *next = conn->next;

	IMCloseConnection(conn);
	conn = next;
    }

    /*
     * Close down TCP/Unix service sockets.
     */
    if (ipw->imp.tcp_sock >= 0) {
	XtRemoveInput(ipw->imp.tcp_id);
	(void)close(ipw->imp.tcp_sock);
    }
    if (ipw->imp.unix_sock >= 0) {
	XIOEUnset(ipw->imp.ioe_handle);
	(void)unlink(ipw->imp.unix_path);
	XtRemoveInput(ipw->imp.unix_id);
	(void)close(ipw->imp.unix_sock);
	XtFree(ipw->imp.unix_path);
    }

    /*
     * Unload default fonts.
     */
    if (ipw->imp.num_default_fonts > 0) {
	FontBankFreeFonts(ipw->imp.font_bank,
			  ipw->imp.default_fonts,
			  ipw->imp.num_default_fonts);
    }

    /*
     * Free font bank.
     */
    FontBankDestroy(ipw->imp.font_bank);
}

/*- Realize: own selection -*/
static void
Realize(w, mask, value)
Widget w;
XtValueMask *mask;
XSetWindowAttributes *value;
{
    IMProtocolWidget ipw = (IMProtocolWidget)w;
    CoreWidgetClass super = (CoreWidgetClass)XtClass(w)->core_class.superclass;

    TRACE(("IMProtocolWidget:Realize()\n"));

    (*super->core_class.realize)(w, mask, value);

    if (ownSelection(ipw) < 0) {
	String params[1];
	Cardinal num_params;

	params[0] = XtClass(w)->core_class.class_name;
	num_params = 1;
	XtAppWarningMsg(XtWidgetToApplicationContext(w),
			"selectionError", "ownSelection", "WidgetError",
			"%s: can't own selection", params, &num_params);

	XtDestroyWidget(w);
    }

    setProperty(ipw);
}

/*
 *+ Atom, property and selection handling
 */

/*- getAtoms: intern atoms -*/
static void
getAtoms(ipw)
IMProtocolWidget ipw;
{
    Display *dpy = XtDisplay((Widget)ipw);
    char buf[256];

    TRACE(("IMProtocolWidget:getAtoms()\n"));

    (void)strcpy(buf, "@server=");
    (void)strcat(buf, ipw->imp.server_name);
#define MAKEATOM(s)	XInternAtom(dpy, s, False)
    ipw->imp.server_atom = MAKEATOM(buf);
    ipw->imp.ctext_atom = MAKEATOM("COMPOUND_TEXT");
    ipw->imp.locales_atom = MAKEATOM("LOCALES");
    ipw->imp.transport_atom = MAKEATOM("TRANSPORT");
    ipw->imp.ki2comm_atom = MAKEATOM("_KINPUT2_COMM");
    ipw->imp.xim_xconnect = MAKEATOM("_XIM_XCONNECT");
    ipw->imp.xim_protocol = MAKEATOM("_XIM_PROTOCOL");
    ipw->imp.xim_moredata = MAKEATOM("_XIM_MOREDATA");
#undef MAKEATOM
}

/*- setProperty: set XIM_SERVERS property -*/
static void
setProperty(ipw)
IMProtocolWidget ipw;
{
    Display *dpy = XtDisplay((Widget)ipw);
    Atom xim_servers = XInternAtom(dpy, "XIM_SERVERS", False);
    Atom server_atom = ipw->imp.server_atom;
    Window root0 = RootWindow(dpy, 0);
    int op_mode = PropModePrepend;
    int no_registration = 0;
    Atom type;
    int format;
    unsigned long nitems;
    unsigned long bytes_after;
    unsigned char *value;
    unsigned long data;

    TRACE(("IMProtocolWidget:setProperty()\n"));

    /*
     * For atomic operation, grab the server.
     */
#ifndef DEBUG
    XGrabServer(dpy);
#endif

    /*
     * First, check the XIM_SERVERS property's existance.
     * If it exists, examine the contents.
     */
    if (XGetWindowProperty(dpy, root0, xim_servers, 0L, 1024L, False,
			   AnyPropertyType, &type, &format, &nitems,
			   &bytes_after, &value) == Success) {
	if (type != XA_ATOM || format != 32) {
	    /*
	     * The contents of the property is invalid.
	     */
	    DDPRINT(2, ("XIM_SERVERS is corrupted (type=%ld, format=%d)\n",
		       type, format));
	    op_mode = PropModeReplace;
	} else {
	    int i;
	    unsigned long *atoms = (unsigned long *)value;

	    for (i = 0; i < nitems; i++) {
		if (atoms[i] == server_atom) {
		    /*
		     * Already registered.
		     */
		    TRACE(("server is already registered in XIM_SERVERS\n"));
		    no_registration = 1;
		    break;
		}
	    }
	}
	if (value != NULL) XFree((char *)value);
    }

    if (!no_registration) {
	TRACE(("changing XIM_SERVERS property\n"));
	data = ipw->imp.server_atom;
	XChangeProperty(dpy, root0, xim_servers, XA_ATOM, 32, op_mode,
			(unsigned char *)&data, 1);
    } else {
	TRACE(("touching XIM_SERVERS property to generate PropertyNotify\n"));
	XChangeProperty(dpy, root0, xim_servers, XA_ATOM, 32, PropModeAppend,
			(unsigned char *)&data, 0);
    }

#ifndef DEBUG
    XUngrabServer(dpy);
#endif
}

/*- ownSelection: own conversion selection -*/
static int
ownSelection(ipw)
IMProtocolWidget ipw;
{
    Display *dpy = XtDisplay((Widget)ipw);
    Time time = XtLastTimestampProcessed(dpy);


    TRACE(("IMProtocolWidget:ownSelection()\n"));

    if (!XtOwnSelection((Widget)ipw, ipw->imp.server_atom, time,
			convertSelection, loseSelection,
			(XtSelectionDoneProc)NULL)) {
	DPRINT(("cannot own selection"));
	return -1;
    }
    DPRINT(("selection atom:%ld owner: %08lx (%ld)\n", ipw->imp.server_atom,
	    XtWindow((Widget)ipw), XtWindow((Widget)ipw)));
    return 0;
}

/*- convertSelection: convert selections -*/
/* ARGSUSED */
static Boolean
convertSelection(w, selectionp, targetp, typep, valuep, lengthp, formatp)
Widget w;
Atom *selectionp;
Atom *targetp;
Atom *typep;
XtPointer *valuep;
unsigned long *lengthp;
int *formatp;
{
    Display *dpy = XtDisplay(w);
    IMProtocolWidget ipw = (IMProtocolWidget)w;

    TRACE(("IMProtocolWidget:convertSelection()\n"));

    if (*targetp == XInternAtom(dpy, "TARGETS", False)) {
	Atom *targets;

	TRACE(("target is \"TARGETS\"\n"));
	targets = (Atom *)XtMalloc(sizeof(Atom) * 2);
	targets[0] = ATOM_LOCALES(w);
	targets[1] = ATOM_TRANSPORT(w);

	*typep = XA_ATOM;
	*valuep = (XtPointer)targets;
	*lengthp = 2;
	*formatp = 32;
	return True;
    } else if (*targetp == ATOM_LOCALES(w)) {
	char buf[1024];

	TRACE(("target is \"LOCALES\"\n"));
	(void)strcpy(buf, "@locale=");
	(void)strcat(buf, ipw->imp.locales);
	TRACE(("\ttype: STRING, value: %s\n", buf));
	/*
	 * The protocol spec is unclear on the type of the
	 * selection value.  Since R6 sample implementation
	 * uses LOCALES, use it.
	 */
	*typep = *targetp;
	/* *typep = XA_STRING; */
	*valuep = (XtPointer)XtNewString(buf);
	*lengthp = strlen(buf);
	*formatp = 8;
	return True;
    } else if (*targetp == ATOM_TRANSPORT(w)) {
	char buf[1024];
	char hostname[256];

	TRACE(("target is \"TRANSPORT\"\n"));

	XmuGetHostname(hostname, 256);

	(void)strcpy(buf, "@transport=");

#ifdef IM_X_TRANSPORT
	if (ipw->imp.use_x_transport) {
	    (void)strcat(buf, "X/,");
	}
#endif /* IM_X_TRANSPORT */

#ifdef IM_TCP_TRANSPORT
	if (ipw->imp.use_tcp_transport) {
	    char t_buf[1024];
	    (void)sprintf(t_buf, "tcp/%s:%d,", hostname, ipw->imp.tcp_port);
	    (void)strcat(buf, t_buf);
	}
#endif /* IM_TCP_TRANSPORT */

#ifdef IM_UNIX_TRANSPORT
	if (ipw->imp.use_unix_transport) {
	    char u_buf[1024];
	    (void)sprintf(u_buf, "local/%s:%s,", hostname, ipw->imp.unix_path);
	    (void)strcat(buf, u_buf);
	}
#endif /* IM_UNIX_TRANSPORT */

	/* delete trailing comma */
	if (buf[strlen(buf) - 1] == ',') buf[strlen(buf) - 1] = '\0';
	TRACE(("\ttype: STRING, value: %s\n", buf));

	*typep = *targetp;	/* -- see the comment on LOCALES above */
	*valuep = (XtPointer)XtNewString(buf);
	*lengthp = strlen(buf);
	*formatp = 8;
	return True;
    } else {
	DDPRINT(2, ("unknown target atom (%ld)\n", *targetp));
	return False;
    }
}

/*- loseSelection: disable IM protocol handling -*/
/* ARGSUSED */
static void
loseSelection(w, selectionp)
Widget w;
Atom *selectionp;
{
    IMProtocolWidget ipw = (IMProtocolWidget)w;

    TRACE(("IMProtocolWidget:loseSelection()\n"));

    /*
     * Someone takes over the selection.  That means
     * another kinput2 process has been started.
     * Let the newly process handle new clients, but
     * as long as existing clients are remained, we have
     * to maintain them.
     */

    if (ipw->imp.connection_list == NULL) {
	/*
	 * There are no clients.  It is OK to destroy protocol handler.
	 */
	XtDestroyWidget(w);
	return;
    }

    ipw->imp.no_more_connections = True;

    /*
     * Close down TCP/Unix service sockets.
     */
    if (ipw->imp.tcp_sock >= 0) {
	TRACE(("\tclose tcp socket\n"));
	XtRemoveInput(ipw->imp.tcp_id);
	(void)close(ipw->imp.tcp_sock);
	ipw->imp.tcp_sock = -1;
    }
    if (ipw->imp.unix_sock >= 0) {
	TRACE(("\tclose unix socket\n"));
	XtRemoveInput(ipw->imp.unix_id);
	(void)close(ipw->imp.unix_sock);
	ipw->imp.unix_sock = -1;
    }
}


/*
 *+ Connection acceptance
 */

#ifdef IM_TCP_TRANSPORT
/*- acceptTCPService: establish connection via TCP transport -*/
/* ARGSUSED */
static void
acceptTCPService(client_data, sourcep, idp)
XtPointer client_data;
int *sourcep;
XtInputId *idp;
{
    IMProtocolWidget ipw = (IMProtocolWidget)client_data;
    IMConnection *conn;

    TRACE(("IMProtocolWidget:acceptTCPService()\n"));

    /*
     * Accept connection request.
     */
    conn = IMTCPConnection((Widget)ipw, *sourcep);

    /*
     * Set dispatcher.
     */
    if (conn != NULL) IMSetInitialDispatcher(conn);

    /*
     * Enter to the connections list.
     */
    if (conn != NULL) IMRegisterConnection(conn);
}
#endif /* IM_TCP_TRANSPORT */

#ifdef IM_UNIX_TRANSPORT
/*- acceptUnixService: establish connection via UNIX domain transport -*/
/* ARGSUSED */
static void
acceptUnixService(client_data, sourcep, idp)
XtPointer client_data;
int *sourcep;
XtInputId *idp;
{
    IMProtocolWidget ipw = (IMProtocolWidget)client_data;
    IMConnection *conn;

    TRACE(("IMProtocolWidget:acceptUnixService()\n"));

    /*
     * Accept connection request.
     */
    conn = IMUnixConnection((Widget)ipw, *sourcep);

    /*
     * Set dispatcher.
     */
    if (conn != NULL) IMSetInitialDispatcher(conn);

    /*
     * Enter to the connections list.
     */
    if (conn != NULL) IMRegisterConnection(conn);
}
#endif /* IM_UNIX_TRANSPORT */

#ifdef IM_X_TRANSPORT
/*- acceptXService: establish connection via X transport -*/
/* ARGSUSED */
static void
acceptXService(w, client_data, event, continuep)
Widget w;
XtPointer client_data;
XEvent *event;
Boolean *continuep;
{
    IMConnection *conn;

    TRACE(("IMProtocolWidget:acceptXService()\n"));

    /*
     * Check if the event is really a connection request.
     */
    if (event->type != ClientMessage) return;
    conn = IMXConnection(w, event);

    /*
     * Set dispatcher.
     */
    if (conn != NULL) IMSetInitialDispatcher(conn);

    /*
     * Enter to the connections list.
     */
    if (conn != NULL) IMRegisterConnection(conn);
}
#endif /* IM_X_TRANSPORT */


/*
 *+ utility functions
 */

/*- initializeError: display error message when resource isn't specified -*/
static void
initializeError(w, resname)
Widget w;
String resname;
{
    String params[2];
    Cardinal num_params;

    params[0] = XtClass(w)->core_class.class_name;
    params[1] = resname;
    num_params = 2;
    XtAppErrorMsg(XtWidgetToApplicationContext(w),
		  "initializeError", "noResource", "WidgetError",
		  "%s: resource %s must be specified at widget creation",
		  params, &num_params);
}

/*- compactList: remove unnecessary spaces in a comma-separated list -*/
static char *
compactList(s)
char *s;
{
    char *src, *dst;
    int c;

    src = dst = s;
    for (;;) {
	/* skip leading space */
	while (isspace(*src)) src++;

	if (*src == '\0') {
	    *dst = '\0';
	    return s;
	}

	/* copy string until comma or NUL appears */
	while ((c = *dst++ = *src++) != ',') {
	    if (c == '\0') return s;
	}
    }
}

/*- setTransport: determine which transport to be used -*/
static void
setTransport(w)
Widget w;
{
    IMProtocolWidget ipw = (IMProtocolWidget)w;
    char *p;

    TRACE(("IMProtocolWidget:setTransport(%s)\n", ipw->imp.transport_list));

    ipw->imp.use_tcp_transport = False;
    ipw->imp.use_unix_transport = False;
    ipw->imp.use_x_transport = False;

    p = ipw->imp.transport_list;
    while (*p != '\0') {
	char lower[256];
	char *q;

	while (isspace(*p)) p++;
	if (*p == '\0') break;

	q = lower;
	while (*p != '\0' && *p != ',' && !isspace(*p)) {
	    if (isupper(*p)) {
		*q++ = tolower(*p);
	    } else {
		*q++ = *p;
	    }
	    p++;
	}
	*q = '\0';
	while (isspace(*p)) p++;
	if (*p == ',') p++;

	if (!strcmp(lower, "tcp")) {
	    TRACE(("\tTCP transport\n"));
	    ipw->imp.use_tcp_transport = True;
	} else if (!strcmp(lower, "unix")) {
	    TRACE(("\tUNIX domain transport\n"));
	    ipw->imp.use_unix_transport = True;
	} else if (!strcmp(lower, "x")) {
	    TRACE(("\tX transport\n"));
	    ipw->imp.use_x_transport = True;
	}
    }
}

/*- makeConverter: create converter record -*/
static int
makeConverter(w)
Widget w;
{
    IMProtocolWidget ipw = (IMProtocolWidget)w;
    char *locales[100];
    int num_locales;
    int size;
    char *p;

    TRACE(("IMProtocolWidget:makeConverter()\n"));

    ipw->imp.converter.input_object_class = ipw->imp.input_object_class;
    ipw->imp.converter.display_object_class = ipw->imp.display_object_class;

    p = ipw->imp.locales;
    num_locales = 0;
    do {
	char buf[256];
	char *q = buf;

	while (isspace(*p)) p++;
	if (*p == '\0') break;

	while (*p != '\0' && *p != ',' && !isspace(*p)) *q++ = *p++;
	*q = '\0';
	TRACE(("\tsupported locale: %s\n", buf));
	locales[num_locales++] = XtNewString(buf);
	while (isspace(*p)) p++;
	if (*p == ',') p++;
    } while (*p != '\0' && num_locales < 100);
    TRACE(("\tnumber of supported locales: %d\n", num_locales));

    if (num_locales == 0) return -1;
    ipw->imp.converter.num_locales = num_locales;

    size = sizeof(char *) * num_locales;
    ipw->imp.converter.supported_locales = (char **)XtMalloc(size);
    bcopy((char *)locales, (char *)ipw->imp.converter.supported_locales, size);
    return 0;
}

/*- getTriggerKeys: parse conversion trigger key specification -*/
static void
getTriggerKeys(w)
Widget w;
{
    IMProtocolWidget ipw = (IMProtocolWidget)w;
    char *key_str;
    IMTriggerKey keys[100];
    int num_keys;
    int c, n;
    ICTriggerKey *ckeys, *ekeys;

    TRACE(("IMProtocolWidget:getTriggerKeys()\n"));

    key_str = ipw->imp.conversion_start_keys;
    num_keys = 0;
    TRACE(("\tstart keys: %s\n", key_str));

    if (key_str != NULL) {
	do {
	    char buf[256];
	    char *p = buf;
	    KeySym keysym;
	    long mods, chk_mods;

	    while ((c = *key_str++) != '\0' && c != '\n') {
		*p++ = c;
	    }
	    *p = '\0';
	    if (ParseKeyEvent(buf, &keysym, &mods, &chk_mods)) {
		TRACE(("\tkeysym: %08lx, modifiers: %04lx, check: %04lx\n",
		       keysym, mods, chk_mods));
		keys[num_keys].keysym = keysym;
		keys[num_keys].modifiers = mods;
		keys[num_keys].check_modifiers = chk_mods;
		num_keys++;
	    }
	} while  (c != '\0' && num_keys < 100);
    }

    n = ICGetTriggerKeysOfInputObjectClass(ipw->imp.input_object_class,
					   &ckeys);
    for (ekeys = ckeys + n ;
	 ckeys < ekeys && num_keys < (sizeof(keys) / sizeof(IMTriggerKey)) ;
	 ckeys++) {
      keys[num_keys].keysym = ckeys->keysym;
      keys[num_keys].modifiers = ckeys->modifiers;
      keys[num_keys].check_modifiers = ckeys->modifiermask;
      num_keys++;
    }

    TRACE(("\tnumber of trigger keys: %d\n", num_keys));
    ipw->imp.num_trigger_keys = num_keys;

    if (num_keys > 0) {
	int size;

	size = sizeof(IMTriggerKey) * num_keys;
	ipw->imp.trigger_keys = (IMTriggerKey *)XtMalloc(size);
	bcopy((char *)keys, (char *)ipw->imp.trigger_keys, size);
    } else {
	ipw->imp.trigger_keys = NULL;
    }
}

/*- ioeCallback: callback procedure for X I/O error -*/
static void
ioeCallback(cldata)
XPointer cldata;
{
    IMProtocolWidget ipw = (IMProtocolWidget)cldata;

    if (ipw->imp.unix_sock >= 0 && ipw->imp.unix_path != NULL) {
	(void)unlink(ipw->imp.unix_path);
    }
}
