/* $TOG: XpNotifyPdm.c /main/4 1997/05/28 11:41:41 kaleb $ */
/******************************************************************************
 ******************************************************************************
 **
 ** Description:  XpNotifyPdm is used to engage a Page Dialog Manager
 **               (PDM).  Though the PDM is not a formal part of the
 **               Print Extension, the concept and likely usage of
 **               PDM's is strong enough that this routine is being
 **               provided as a sample standard mechanism for engaging
 **               PDM's from the Print Extension.
 **
 ** (c) Copyright 1996 Hewlett-Packard Company
 ** (c) Copyright 1996 International Business Machines Corp.
 ** (c) Copyright 1996 Sun Microsystems, Inc.
 ** (c) Copyright 1996 Novell, Inc.
 ** (c) Copyright 1996 Digital Equipment Corp.
 ** (c) Copyright 1996 Fujitsu Limited
 ** (c) Copyright 1996 Hitachi, Ltd.
 ** 
 ** Permission is hereby granted, free of charge, to any person obtaining a copy
 ** of this software and associated documentation files (the "Software"), to deal
 ** in the Software without restriction, including without limitation the rights
 ** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 ** copies of the Software, and to permit persons to whom the Software is
 ** furnished to do so, subject to the following conditions:
 **
 ** The above copyright notice and this permission notice shall be included in
 ** all copies or substantial portions of the Software.
 **
 ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
 ** COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 ** IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 ** CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 **
 ** Except as contained in this notice, the names of the copyright holders shall
 ** not be used in advertising or otherwise to promote the sale, use or other
 ** dealings in this Software without prior written authorization from said
 ** copyright holders.
 **
 ******************************************************************************
 *****************************************************************************/

#include "Print.h"
#include "X11/Xlibint.h"
#include "X11/Xutil.h"
#include "X11/Xatom.h"
#include "X11/Xauth.h"
#include <stdlib.h>
#include <X11/Xos.h>

/*
 * Alternate selection environment variables.
 */
#define ALT_PDM_SELECTION_ENV_VAR  "XPDMSELECTION"
#define ALT_PMD_DISPLAY_ENV_VAR    "XPDMDISPLAY"

/*
 * X-Authority file for authorization tickets.
 */
#define PDM_XAUTHORITY_ENV_VAR     "XPDMXAUTHORITY"

/*
 * str_dup using Xmalloc
 */
char *_xpstrdup(char * str)
{
    int len;
    char *newstr;

    if (!str)
	return( (char *) NULL );

    len = strlen(str) + 1;
    newstr = (char *) Xmalloc( len );
    memcpy( newstr, str, len );
    return( newstr );
}

/******************************************************************************
 *
 * _XpGetSelectionServer
 *
 * Routine that considers print_display, video_display, and alt
 * environment variables, and figures out the selection_display
 * and selection_atom.
 *
 * selection_display can be one of print_display, video_display,
 * or a brand new display connection that the caller will later
 * have to close.
 */
static Display *
_XpGetSelectionServer (
    Display         *print_display,
    Display         *video_display,
    Atom            *sel_atom			/* return value */
)
{
    char          *tstr1, *tstr2, *tstr3, *tstrptr;
    char          *sel_displaystr;
    Display       *sel_display;
    char          *selectionstr;


    /*
     * Possibly tickle a selection on an alternate display.
     */
    if ( (sel_displaystr = getenv( ALT_PMD_DISPLAY_ENV_VAR )) != NULL ) {
	if ( !strcmp(sel_displaystr, "print") ) {
	    /*
	     * Explicitly use the Print display.
	     */
	    sel_display = print_display;
	}
	else if ( !strcmp(sel_displaystr, "video") ) {
	    /*
	     * Explicitly use the Video display.
	     */
	    sel_display = video_display;
	}
	else {
	    /*
	     * Do more work to determine the selection server connection.
	     * The following is not clever enough to handle host aliases.
	     */
	    tstr1 = _xpstrdup( sel_displaystr );
	    tstr2 = _xpstrdup( XDisplayString(print_display) );
	    tstr3 = _xpstrdup( XDisplayString(video_display) );

	    /*
	     * remove ".scr" portion from "host:disp.scr" strings.
	     */
	    if ( tstrptr = strchr( tstr1, ':' ) )
		if ( tstrptr = strchr( tstrptr, '.' ) )
		    *tstrptr = '\0';

	    if ( tstrptr = strchr( tstr2, ':' ) )
		if ( tstrptr = strchr( tstrptr, '.' ) )
		    *tstrptr = '\0';

	    if ( tstrptr = strchr( tstr3, ':' ) )
		if ( tstrptr = strchr( tstrptr, '.' ) )
		    *tstrptr = '\0';

	    if ( !strcmp( tstr1, tstr2 ) ) {
		/*
		 * Explicitly use the Print display.
		 */
		sel_display = print_display;
	    }
	    else if ( !strcmp( tstr1, tstr3 ) ) {
		/*
		 * Explicitly use the Print display.
		 */
		sel_display = video_display;
	    }
	    else {
		/*
		 * Create a new display connection to a server
		 * never previously contacted. The caller will
		 * have to realize this is a new display handle
		 * (ie, its not equal to print_display or
		 * video_display) when done.
		 */
		sel_display = XOpenDisplay(sel_displaystr);
	    }

	    XFree( tstr3 );
	    XFree( tstr2 );
	    XFree( tstr1 );
	}
    }
    else {
	/*
	 * If no alt selection server, use the print server.
	 */
	sel_display = print_display;
    }

    if (sel_display) {
	/*
	 * Tickle (possibly alternate) PDM_MANAGER selection
	 */
	if ( (selectionstr = getenv( ALT_PDM_SELECTION_ENV_VAR )) == NULL )
	    selectionstr = "PDM_MANAGER";

	    *sel_atom = XInternAtom( sel_display, selectionstr, False );
    }

    return( sel_display );
}



/******************************************************************************
 *
 * XpGetPdmStartParams
 */
Status
XpGetPdmStartParams (
    Display         *print_display,
    Window          print_window,
    XPContext       print_context,
    Display         *video_display,
    Window          video_window,
    Display         **selection_display,	/* return value */
    Atom            *selection,			/* return value */
    Atom            *type,			/* return value */
    int             *format,			/* return value */
    unsigned char   **data,			/* return value */
    int             *nelements			/* return value */
)
{
    Display       *sel_display;
    XTextProperty text_prop;
    int           status;
    char          str1[128], str2[128], str3[128];
    char          *list[6];


    *selection_display = _XpGetSelectionServer( print_display, video_display,
						selection );

    if (*selection_display == NULL) {
	/*
	 * Error - cannot determine or establish a selection_display.
	 */
	return( (Status) NULL );
    }

    /*
     * Create a property that can be included in the PDM_MANAGER
     * selection to communicate information.
     *
     *    video_display    host:display[.anyScreen]
     *    video_window
     *    print_display    host:display[.anyScreen]
     *    print_window
     *    print_context    use to derive host:display.properScreen and other
     *    locale_hint
     */
    list[0] = XDisplayString( video_display );
    sprintf( str1, "0x%x", video_window );
    list[1] = str1;

    list[2] = XDisplayString( print_display );
    sprintf( str2, "0x%x", print_window );
    list[3] = str2;

    sprintf( str3, "0x%x", print_context );
    list[4] = str3;

    list[5] = XpGetLocaleNetString();

    status = XSupportsLocale();
    if ( ! status ) {
	/*
	 * Error.
	 */
	if ( (*selection_display != print_display) &&
	     (*selection_display != video_display)    ) {
	    XCloseDisplay( *selection_display );
	    *selection_display = (Display *) NULL;
	}
	return( (Status) NULL );
    }

    status = XmbTextListToTextProperty( *selection_display, list, 6,
					XStdICCTextStyle, &text_prop );

    if ( status < 0 ) {
	/*
	 * Error.
	 */
	if ( (*selection_display != print_display) &&
	     (*selection_display != video_display)    ) {
	    XCloseDisplay( *selection_display );
	    *selection_display = (Display *) NULL;
	}
	return( (Status) NULL );
    }

    *type              = text_prop.encoding;
    *format            = text_prop.format;
    *data              = text_prop.value;
    *nelements         = text_prop.nitems;

    XFree(list[5]);

    return( (Status) 1 );
}

/******************************************************************************
 *
 * XpSendOneTicket
 *
 * The ticket transfer protocol is as follows:
 *    unsigned short ticket_state;
 *       - 0 means NULL ticket, terminating ticket
 *       - 1 means non-NULL ticket, terminating ticket
 *       - 2 means non-NULL ticket, and more tickets to send
 *    unsigned short address_length;
 *    unsigned short number_length;
 *    unsigned short name_length;
 *    unsigned short data_length;
 *    unsigned short family;
 *
 *    char *address;
 *    char *number;
 *    char *name;
 *    char *data;
 *
 */

Status XpSendOneTicket(
    Display *display,
    Window  window,
    Xauth   *ticket,
    Bool    more )
{
    XClientMessageEvent ev;
    int                 bigstrlen, left, gofor;
    char                *bigstr, *tptr;
    Status              status;

    /*
     * Fixed portion of event.
     */
    ev.type = ClientMessage;
    ev.display = display;
    ev.window  = window;
    ev.message_type = XInternAtom( display, "PDM_MAIL", False );

    /*
     * Special build the first ClientMessage to carry the
     * ticket transfer header.
     */
    ev.format = 16;

    if (!ticket) {
	ev.data.s[0] = 0;
    }
    else {
	if (more)
	    ev.data.s[0] = 2;
	else
	    ev.data.s[0] = 1;
	ev.data.s[1] = (short) ticket->address_length;
	ev.data.s[2] = (short) ticket->number_length;
	ev.data.s[3] = (short) ticket->name_length;
	ev.data.s[4] = (short) ticket->data_length;
	ev.data.s[5] = (short) ticket->family;
    }

    status = XSendEvent( display, window, False, 0L, (XEvent *) &ev );
    if (!status)
	return( (Status) 0 );

    if (!ticket)
	return( (Status) 1 );;

    /*
     * Break down the remaining ticket data and build the
     * second thru N ClientMessages.
     */
    ev.format = 8;
    bigstrlen = (int) ticket->address_length +
		(int) ticket->number_length +
		(int) ticket->name_length +
		(int) ticket->data_length;

    bigstr = Xmalloc( bigstrlen );

    tptr = bigstr;
    memcpy( tptr, ticket->address, ticket->address_length );
    tptr += ticket->address_length;
    memcpy( tptr, ticket->number, ticket->number_length );
    tptr += ticket->number_length;
    memcpy( tptr, ticket->name, ticket->name_length );
    tptr += ticket->name_length;
    memcpy( tptr, ticket->data, ticket->data_length );

    left = bigstrlen;
    tptr = bigstr;

    while ( left ) {
	if (left > 20)
	    gofor = 20;
	else
	    gofor = left;

	memcpy( ev.data.b, tptr, gofor );

	tptr += gofor;
	left -= gofor;

	status = XSendEvent( display, window, False, 0L, (XEvent *) &ev );
	if (!status) {
	    Xfree( bigstr );
	    return( (Status) 0 );
	}
    }

    Xfree( bigstr );

    return( (Status) 1 );
}

Status XpSendAuth( Display *display, Window window )
{

    FILE    *auth_file;
    char    *auth_name;
    Xauth   *entry;

    if ( !(auth_name = getenv( PDM_XAUTHORITY_ENV_VAR )) ) {
        return( (Status) 0 );
	/* auth_name = XauFileName (); */
    }

    if (access (auth_name, R_OK) != 0)          /* checks REAL id */
        return( (Status) 0 );

    auth_file = fopen (auth_name, "r");
    if (!auth_file)
        return( (Status) 0 );

    for (;;) {
        entry = XauReadAuth (auth_file);
        if (!entry)
            break;

	/*
	 * NOTE: in and around this area, an optimization can
	 * be made.  Rather than sending all the tickets in
	 * .Xauthority, just pull out the *one* that is needed
	 * by the PDM.
	 */
	XpSendOneTicket( display, window, entry, True );

	XauDisposeAuth (entry);
    }

    XpSendOneTicket( display, window, (Xauth *) NULL, False );

    (void) fclose (auth_file);

    return( (Status) 1 );
}

/******************************************************************************
 *
 *
 */
Status
XpGetAuthParams (
    Display         *print_display,
    Display         *video_display,
    Display         **sel_display,		/* return value */
    Atom            *sel_atom,			/* return value */
    Atom            *sel_target_atom		/* return value */
)
{
    *sel_display = _XpGetSelectionServer( print_display, video_display,
					  sel_atom );

    if (*sel_display == NULL) {
	/*
	 * Error - cannot determine or establish a selection_display.
	 */
	return( (Status) NULL );
    }

    /*
     * Create property and transfer data to.
     */
    *sel_target_atom = XInternAtom( *sel_display, "PDM_MBOX", False );
}


/******************************************************************************
 ******************************************************************************
 **
 ** UNSUPPORTED ROUTINES used for testing and debugging.
 **
 **/


/******************************************************************************
 *
 * Predicate routines to dig out events from the users event queue.
 */

/*
 * client_data structures to use with XIfEvent()
 */
typedef struct {
    Window	requestor;
    Atom	selection;
    Atom	target;
} Dosnrec;

typedef struct {
    Window	window;
    Atom	atom;
} Dopnrec, Docmrec;


/*
 * Dig out a selection notify from the users event
 * queue.
 */
static Bool digOutSelectionNotify(
    Display *display,
    XEvent *event,
    char *arg
)

{
    Dosnrec *t;

    if (event->type == SelectionNotify) {
	t = (Dosnrec *) arg;

	/*
	 * Selection complete because of good reply.
	 */
	if ( ( t->requestor == event->xselection.requestor ) &&
	     ( t->selection == event->xselection.selection ) &&
	     ( t->target    == event->xselection.target    )    ) {
	    return( True );
	}

	/*
	 * Selection complete because of problem, and X-Server
	 * had to generate the event.
	 */
	if ( ( t->requestor == event->xselection.requestor ) &&
	     ( t->selection == event->xselection.selection ) &&
	     ( None         == event->xselection.property  )    ) {
	    return( True );
	}
    }
    return( False );
}

/*
 * Dig out a property notify event for the XpNotifyPdm
 * selection mechanism from the users event queue.
 */
static Bool digOutPropertyNotify(
    Display *display,
    XEvent *event,
    char *arg
)
{
    Dopnrec *t;

    if (event->type == PropertyNotify) {
	t = (Dopnrec *) arg;
	if ( ( t->window == event->xproperty.window ) &&
	     ( t->atom   == event->xproperty.atom   )    ) {
	    return( True );
	}
    }
    return( False );
}

/*
 * Dig out a ClientMessage of type PDM_REPLY.
 */
static Bool digOutClientMessage(
    Display *display,
    XEvent *event,
    char *arg
)
{
    Docmrec *t;

    if (event->type == ClientMessage) {
	t = (Docmrec *) arg;
	if ( ( t->window  == event->xclient.window       ) &&
	     ( t->atom    == event->xclient.message_type )    ) {
	    return( True );
	}
    }
    return( False );
}



/******************************************************************************
 *
 * XpCookieToPdm
 */
static char *
XpCookieToPdm (
    Display         *print_display,
    Display         *video_display,
    Window          sel_window
)
{
    Display *sel_display;
    Atom    prop_atom, sel_atom, pdm_mbox_atom, ttype;
    Dosnrec dosnrec;
    Dopnrec dopnrec;
    XEvent  tevent;
    int           tformat;
    unsigned long nitems, bytes_after;
    unsigned char *return_data;
    char *sel_str;
    char    cdata[2048];
    Window tmpw;


    if ( !getenv(PDM_XAUTHORITY_ENV_VAR) ) {
	/*
	 * short cut for lack of cookie file.
	 */
        return( (char *) NULL );
    }

    if (! XpGetAuthParams( print_display, video_display,
			     &sel_display, &sel_atom, &pdm_mbox_atom ) ) {
	sprintf(cdata,
		 "XpCookieToPdm: XpGetAuthParams failed");
	return( _xpstrdup( cdata ) );
    }

    prop_atom = XInternAtom( sel_display, "PDM_MBOX_PROP", False );

    XConvertSelection( sel_display, sel_atom, pdm_mbox_atom,
		       prop_atom, sel_window, CurrentTime );

    /*
     * Hang out waiting for a SelectionNotify.  Dig out from
     * event queue when it arrives.
     */
    dosnrec.requestor = sel_window;
    dosnrec.selection = sel_atom;
    dosnrec.target    = pdm_mbox_atom;

    XIfEvent( sel_display, &tevent, digOutSelectionNotify, (char *) &dosnrec );

    /*
     * See if selection was successful.
     */
    if ( tevent.xselection.property == None ) {
	/*
	 * The selection failed.
	 */
	sel_str = XGetAtomName( sel_display, sel_atom );
	sprintf(cdata,
		 "XpCookieToPdm: Unable to make selection on %s", sel_str);
	XFree(sel_str);

	XDeleteProperty( sel_display, sel_window, prop_atom );
	if ((sel_display != print_display) && (sel_display != video_display))
	    XCloseDisplay( sel_display );
	return( _xpstrdup( cdata ) );
    }

    /*
     * Read property content for status.
     */
    XGetWindowProperty( sel_display, sel_window,
			prop_atom, 0, 100000, True, AnyPropertyType,
			&ttype, &tformat, &nitems, &bytes_after,
			&return_data );

    /*
     * So that the client won't see how XpCookieToPdm is implemented,
     * run through the event queue, dig out, and destroy all
     * PropertyNotify events related to this call.
     */
    dopnrec.window = sel_window;
    dopnrec.atom   = prop_atom;
    while ( XCheckIfEvent( sel_display, &tevent, digOutPropertyNotify,
		 (char *) &dopnrec ) );

    if ((sel_display != print_display) && (sel_display != video_display))
	XCloseDisplay( sel_display );

    if ((ttype != XA_WINDOW) && (tformat != 32) && (nitems != 1)) {
	/*
	 * Unable to read SelectionNotify property.
	 */ 
	sprintf(cdata,
	     "XpCookieToPdm: Unable to read SelectionNotify property" );
	return( _xpstrdup( cdata ) );
    }

    tmpw = *((Window *) return_data);
    Xfree( return_data );

    /*
     * Now send cookie information.
     */
    XpSendAuth( sel_display, tmpw );

    return( (char *) NULL );
}


/******************************************************************************
 *
 * XpNotifyPdm
 */
char *
XpNotifyPdm (
    Display         *print_display,
    Window          print_window,
    XPContext       print_context,
    Display         *video_display,
    Window          video_window,
    Bool            auth_flag
)
{
    char          cdata[2048];
    char          *tptr;
    Dosnrec       dosnrec;
    Dopnrec       dopnrec;
    XEvent        tevent;

    Display       *sel_display;
    int           sel_screen;
    Atom          sel_atom, pdm_start;
    char          *sel_str;

    Window        sel_window;
    Atom          prop_type;
    int           prop_format;
    unsigned char *prop_data;
    int           prop_nelements;

    Atom          ttype, prop_atom;
    int           tformat;
    unsigned long nitems, bytes_after;
    unsigned char *return_data;

    int           tmpi;



    if ( ! XpGetPdmStartParams( print_display, print_window, print_context,
			 video_display, video_window,
			 &sel_display,
			 &sel_atom,
			 &prop_type,
			 &prop_format,
			 &prop_data,
			 &prop_nelements ) ) {

	sprintf(cdata, "XpNotifyPdm: XpGetPdmStartParams failed" );
	return( _xpstrdup( cdata ) );
    }


    sel_screen = DefaultScreen( sel_display );
    sel_window = XCreateSimpleWindow( sel_display,
					DefaultRootWindow( sel_display ),
					0, 0, 1, 1, 1,
					BlackPixel(sel_display, sel_screen),
					WhitePixel(sel_display, sel_screen) );

    /*
     * Possibly send over authorization cookies first.
     */
    if (auth_flag) {
	tptr = XpCookieToPdm ( print_display, video_display, sel_window );
	if (tptr)
	    return( tptr );
    }

    /*
     * Create property and transfer data to.
     */
    prop_atom     = XInternAtom( sel_display, "PDM_CLIENT_PROP", False );

    XChangeProperty( sel_display,
		     sel_window, prop_atom,
		     prop_type,
		     prop_format,
		     PropModeReplace,
		     prop_data,
		     prop_nelements );

    XFree( prop_data );

    /*
     * Tickle PDM_MANAGER selection with PDM_START target
     */
    pdm_start = XInternAtom( sel_display, "PDM_START", False );

    XConvertSelection( sel_display, sel_atom, pdm_start,
		       prop_atom, sel_window, CurrentTime );

    /*
     * Hang out waiting for a SelectionNotify.  Dig out from
     * event queue when it arrives.
     */
    dosnrec.requestor = sel_window;
    dosnrec.selection = sel_atom;
    dosnrec.target    = pdm_start;

    XIfEvent( sel_display, &tevent, digOutSelectionNotify, (char *) &dosnrec );

    /*
     * See if selection was successful.
     */
    if ( tevent.xselection.property == None ) {
	/*
	 * The selection failed.
	 */
	sel_str = XGetAtomName( sel_display, sel_atom );
	sprintf(cdata,
		 "XpNotifyPdm: Unable to make selection on %s", sel_str);
	XFree(sel_str);

	XDeleteProperty( sel_display, sel_window, prop_atom );
	XDestroyWindow( sel_display, sel_window );
	if ((sel_display != print_display) && (sel_display != video_display))
	    XCloseDisplay( sel_display );
	return( _xpstrdup( cdata ) );
    }

    /*
     * Read property content for status.
     */
    XGetWindowProperty( sel_display, sel_window,
			prop_atom, 0, 100000, True, AnyPropertyType,
			&ttype, &tformat, &nitems, &bytes_after,
			&return_data );

    /*
     * So that the client won't see how XpNotifyPdm is implemented,
     * run through the event queue, dig out, and destroy all
     * PropertyNotify events related to this call.
     */
    dopnrec.window = sel_window;
    dopnrec.atom   = prop_atom;
    while ( XCheckIfEvent( sel_display, &tevent, digOutPropertyNotify,
		 (char *) &dopnrec ) );

    XDestroyWindow( sel_display, sel_window );
    if ((sel_display != print_display) && (sel_display != video_display))
	XCloseDisplay( sel_display );

    if ((ttype != XA_ATOM) && (tformat != 32) && (nitems != 1)) {
	/*
	 * Unable to read SelectionNotify property.
	 */ 
	sprintf(cdata,
	     "XpNotifyPdm: Unable to read SelectionNotify property" );
	return( _xpstrdup( cdata ) );
    }

    tmpi = *((Atom *) return_data);
    Xfree( return_data );

    if ( tmpi == XInternAtom( sel_display, "PDM_START_OK", False ) ) {
	return( (char *) NULL );
    }
    else if ( tmpi == XInternAtom( sel_display, "PDM_START_VXAUTH", False ) ) {
	sprintf(cdata,
	     "XpNotifyPdm: PDM not authorized to connect to video display." );
	return( _xpstrdup( cdata ) );
    }
    else if ( tmpi == XInternAtom( sel_display, "PDM_START_PXAUTH", False ) ) {
	sprintf(cdata,
	     "XpNotifyPdm: PDM not authorized to connect to print display." );
	return( _xpstrdup( cdata ) );
    }
    else if ( tmpi == XInternAtom( sel_display, "PDM_START_ERROR", False ) ) {
	sprintf(cdata,
	     "XpNotifyPdm: PDM encountered an error. See PDM log file." );
	return( _xpstrdup( cdata ) );
    }
    else {
	sprintf(cdata,
	     "XpNotifyPdm: unknown PDM error." );
	return( _xpstrdup( cdata ) );
    }
}

