/******************************************************************************
******************************************************************************
**
** (c) Copyright 2001-2004 Roland Mainz <[email protected]>
**
** 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 "xprintutil.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <limits.h>
#include <errno.h>
#ifdef XPU_USE_THREADS
#include <time.h>
#ifdef XPU_USE_NSPR
#include <prthread.h>
#else
#include <pthread.h>
#endif /* XPU_USE_NSPR */
#endif /* XPU_USE_THREADS */
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
/* local prototypes */
#ifdef DEBUG
static void PrintXPGetDocStatus( XPGetDocStatus status );
#endif
static Bool XNextEventTimeout( Display *display, XEvent *event_return, struct timeval *timeout );
static void *XpuPrintToFile( Display *pdpy, XPContext pcontext, const char *filename );
static void MyPrintToFileProc( Display *pdpy, XPContext pcontext, unsigned char *data, unsigned int data_len, XPointer client_data );
static void MyFinishProc( Display *pdpy, XPContext pcontext, XPGetDocStatus status, XPointer client_data );
#ifdef XPU_USE_NSPR
static void PrintToFile_Consumer( void *handle );
#else
static void *PrintToFile_Consumer( void *handle );
#endif
void XpuStartJobToSpooler(Display *pdpy)
{
XpStartJob(pdpy, XPSpool);
}
void *XpuStartJobToFile( Display *pdpy, XPContext pcontext, const char *filename )
{
void *handle;
XpStartJob(pdpy, XPGetData);
handle = XpuPrintToFile(pdpy, pcontext, filename);
if (!handle)
{
/* Cancel the print job and discard all events... */
XpCancelJob(pdpy, True);
}
return(handle);
}
#ifdef DEBUG
/* DEBUG: Print XPGetDocStatus */
static
void PrintXPGetDocStatus( XPGetDocStatus status )
{
switch(status)
{
case XPGetDocFinished: puts("PrintXPGetDocStatus: XPGetDocFinished"); break;
case XPGetDocSecondConsumer: puts("PrintXPGetDocStatus: XPGetDocSecondConsumer"); break;
case XPGetDocError: puts("PrintXPGetDocStatus: XPGetDocError"); break;
default: puts("PrintXPGetDocStatus: <unknown value"); break;
}
}
#endif /* DEBUG */
/* XNextEvent() with timeout */
static
Bool XNextEventTimeout( Display *display, XEvent *event_return, struct timeval *timeout )
{
int res;
fd_set readfds;
int display_fd = XConnectionNumber(display);
/* small shortcut... */
if( timeout == NULL )
{
XNextEvent(display, event_return);
return(True);
}
FD_ZERO(&readfds);
FD_SET(display_fd, &readfds);
/* Note/bug: In the case of internal X events (like used to trigger callbacks
* registered by XpGetDocumentData()&co.) select() will return with "new info"
* - but XNextEvent() below processes these _internal_ events silently - and
* will block if there are no other non-internal events.
* The workaround here is to check with XEventsQueued() if there are non-internal
* events queued - if not select() will be called again - unfortunately we use
* the old timeout here instead of the "remaining" time... (this only would hurt
* if the timeout would be really long - but for current use with values below
* 1/2 secs it does not hurt... =:-)
*/
while( XEventsQueued(display, QueuedAfterFlush) == 0 )
{
res = select(display_fd+1, &readfds, NULL, NULL, timeout);
switch(res)
{
case -1: /* select() error - should not happen */
perror("XNextEventTimeout: select() failure");
return(False);
case 0: /* timeout */
return(False);
}
}
XNextEvent(display, event_return);
return(True);
}
#ifdef XPU_USE_THREADS
/**
** XpuPrintToFile() - threaded version
** Create consumer thread which creates it's own display connection to print server
** (a 2nd display connection/thread is required to avoid deadlocks within Xlib),
** registers (Xlib-internal) consumer callback (via XpGetDocumentData(3Xp)) and
** processes/eats all incoming events via MyPrintToFileProc(). A final call to
** MyPrintToFileProc() cleans-up all stuff and sets the "done" flag.
** Note that these callbacks are called directly by Xlib while waiting for events in
** XNextEvent() because XpGetDocumentData() registeres them as "internal" callbacks,
** e.g. XNextEvent() does _not_ return before/after processing these events !!
**
** Usage:
** XpStartJob(pdpy, XPGetData);
** handle = XpuPrintToFile(pdpy, pcontext, "myfile");
** // render something
** XpEndJob(); // or XpCancelJob()
** status = XpuWaitForPrintFileChild(handle);
**
*/
typedef struct
{
#ifdef XPU_USE_NSPR
PRThread *prthread;
#else
pthread_t tid;
#endif
char *displayname;
Display *pdpy;
Display *parent_pdpy;
XPContext pcontext;
const char *file_name;
FILE *file;
XPGetDocStatus status;
Bool done;
} MyPrintFileData;
static
void *XpuPrintToFile( Display *pdpy, XPContext pcontext, const char *filename )
{
MyPrintFileData *mpfd; /* warning: shared between threads !! */
if( (mpfd = malloc(sizeof(MyPrintFileData))) == NULL )
return(NULL);
mpfd->parent_pdpy = pdpy;
mpfd->displayname = XDisplayString(pdpy);
mpfd->pdpy = NULL;
mpfd->pcontext = pcontext;
mpfd->file_name = filename;
mpfd->file = NULL;
mpfd->status = XPGetDocError;
/* make sure we can open the file for writing */
if( (mpfd->file = fopen(mpfd->file_name, "w")) == NULL )
{
/* fopen() error */
free(mpfd);
return(NULL);
}
/* its important to flush before we start the consumer thread,
* to make sure that the XpStartJob gets through first in the parent
*/
XFlush(pdpy);
#ifdef XPU_USE_NSPR
if( (mpfd->prthread = PR_CreateThread(PR_SYSTEM_THREAD, PrintToFile_Consumer, mpfd, PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0)) == NULL )
#else
if( pthread_create(&(mpfd->tid), NULL, PrintToFile_Consumer, mpfd) != 0 )
#endif
{
/* pthread_create() error */
fclose(mpfd->file);
free(mpfd);
return(NULL);
}
/* we're still in the parent */
XPU_DEBUG_ONLY(printf("### parent started consumer thread.\n" ));
return(mpfd);
}
XPGetDocStatus XpuWaitForPrintFileChild( void *handle )
{
MyPrintFileData *mpfd = (MyPrintFileData *)handle;
void *res;
XPGetDocStatus status;
/* Flush data a last time to make sure we send all data to Xprt and that
* the Xlib internal hooks have called a last time */
XFlush(mpfd->parent_pdpy);
#ifdef XPU_USE_NSPR
if( PR_JoinThread(mpfd->prthread) != PR_SUCCESS )
perror("XpuWaitForPrintFileChild: PR_JoinThread() failure"); /* fixme(later): use NSPR error handling calls... */
#else
if( XPU_TRACE(pthread_join(mpfd->tid, &res)) != 0 )
perror("XpuWaitForPrintFileChild: pthread_join() failure");
#endif
status = mpfd->status;
free(handle);
XPU_DEBUG_ONLY(PrintXPGetDocStatus(status));
return(status);
}
#else /* XPU_USE_THREADS */
/**
** XpuPrintToFile() - fork() version
** Create consumer thread which creates it's own display connection to print server
** (a 2nd display connection/process is required to avoid deadlocks within Xlib),
** registers (Xlib-internal) consumer callback (via XpGetDocumentData(3Xp)) and
** processes/eats all incoming events via MyPrintToFileProc(). A final call to
** MyPrintToFileProc() cleans-up all stuff and sets the "done" flag.
** Note that these callbacks are called directly by Xlib while waiting for events in
** XNextEvent() because XpGetDocumentData() registeres them as "internal" callbacks,
** e.g. XNextEvent() does _not_ return before/after processing these events !!
**
** Usage:
** XpStartJob(pdpy, XPGetData);
** handle = XpuPrintToFile(pdpy, pcontext, "myfile");
** // render something
** XpEndJob(); // or XpCancelJob()
** status = XpuWaitForPrintFileChild(handle);
**
*/
typedef struct
{
pid_t pid;
int pipe[2]; /* child-->parent communication pipe */
const char *displayname;
Display *pdpy;
Display *parent_pdpy;
XPContext pcontext;
const char *file_name;
FILE *file;
XPGetDocStatus status;
Bool done;
} MyPrintFileData;
static
void *XpuPrintToFile( Display *pdpy, XPContext pcontext, const char *filename )
{
MyPrintFileData *mpfd;
if( (mpfd = (MyPrintFileData *)malloc(sizeof(MyPrintFileData))) == NULL )
return(NULL);
/* create pipe */
if( pipe(mpfd->pipe) == -1 )
{
/* this should never happen, but... */
perror("XpuPrintToFile: cannot create pipe");
free(mpfd);
return(NULL);
}
mpfd->parent_pdpy = pdpy;
mpfd->displayname = XDisplayString(pdpy);
mpfd->pcontext = pcontext;
mpfd->file_name = filename;
mpfd->file = NULL;
mpfd->status = XPGetDocError;
/* make sure we can open the file for writing */
if( (mpfd->file = fopen(mpfd->file_name, "w")) == NULL )
{
/* fopen() error */
close(mpfd->pipe[1]);
close(mpfd->pipe[0]);
free(mpfd);
return(NULL);
}
/* its important to flush before we fork, to make sure that the
* XpStartJob gets through first in the parent
*/
XFlush(pdpy);
mpfd->pid = fork();
if( mpfd->pid == 0 )
{
/* we're now in the fork()'ed child */
PrintToFile_Consumer(mpfd);
}
else if( mpfd->pid < 0 )
{
/* fork() error */
close(mpfd->pipe[1]);
close(mpfd->pipe[0]);
fclose(mpfd->file);
free(mpfd);
return(NULL);
}
/* we're still in the parent */
XPU_DEBUG_ONLY(printf("### parent fork()'ed consumer child.\n"));
/* child will write into file - we don't need it anymore here... :-) */
fclose(mpfd->file);
close(mpfd->pipe[1]);
return(mpfd);
}
XPGetDocStatus XpuWaitForPrintFileChild( void *handle )
{
MyPrintFileData *mpfd = (MyPrintFileData *)handle;
int res;
XPGetDocStatus status = XPGetDocError; /* used when read() from pipe fails */
/* Flush data a last time to make sure we send all data to Xprt and that
* the Xlib internal hooks have called a last time */
XFlush(mpfd->parent_pdpy);
if( XPU_TRACE(waitpid(mpfd->pid, &res, 0)) == -1 )
perror("XpuWaitForPrintFileChild: waitpid failure");
/* read the status from the child */
if( read(mpfd->pipe[0], &status, sizeof(XPGetDocStatus)) != sizeof(XPGetDocStatus) )
{
perror("XpuWaitForPrintFileChild: can't read XPGetDocStatus");
}
close(mpfd->pipe[0]);
free(handle);
XPU_DEBUG_ONLY(PrintXPGetDocStatus(status));
return(status);
}
#endif /* XPU_USE_THREADS */
static
void MyPrintToFileProc( Display *pdpy,
XPContext pcontext,
unsigned char *data,
unsigned int data_len,
XPointer client_data )
{
MyPrintFileData *mpfd = (MyPrintFileData *)client_data;
/* write to the file */
XPU_TRACE_CHILD((void)fwrite(data, data_len, 1, mpfd->file)); /* what about error handling ? */
}
static
void MyFinishProc( Display *pdpy,
XPContext pcontext,
XPGetDocStatus status,
XPointer client_data )
{
MyPrintFileData *mpfd = (MyPrintFileData *)client_data;
/* remove the file if unsuccessful */
if( status != XPGetDocFinished )
{
XPU_DEBUG_ONLY(printf("MyFinishProc: error %d\n", (int)status));
XPU_TRACE_CHILD(remove(mpfd->file_name));
}
XPU_TRACE_CHILD((void)fclose(mpfd->file)); /* what about error handling ? */
mpfd->status = status;
mpfd->done = True;
}
static
#ifdef XPU_USE_NSPR
void PrintToFile_Consumer( void *handle )
#else
void *PrintToFile_Consumer( void *handle )
#endif
{
MyPrintFileData *mpfd = (MyPrintFileData *)handle;
XEvent dummy;
struct timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 100000; /* 1/10 s */
XPU_DEBUG_ONLY(printf("### child running, getting data from '%s'.\n", mpfd->displayname));
/* we cannot reuse fork()'ed display handles - our child needs his own one */
if( (mpfd->pdpy = XPU_TRACE_CHILD(XOpenDisplay(mpfd->displayname))) == NULL )
{
perror("child cannot open display");
#ifdef XPU_USE_NSPR
return;
#else
return(NULL);
#endif
}
mpfd->done = False;
/* register "consumer" callbacks */
if( XPU_TRACE_CHILD(XpGetDocumentData(mpfd->pdpy, mpfd->pcontext,
MyPrintToFileProc, MyFinishProc,
(XPointer)mpfd)) == 0 )
{
XPU_DEBUG_ONLY(printf("XpGetDocumentData cannot register callbacks\n"));
#ifdef XPU_USE_NSPR
return;
#else
return(NULL);
#endif
}
/* loop forever - libXp has registered hidden event callbacks for the consumer
* callbacks - the finishCB will call set the "done" boolean after all...
*/
while( mpfd->done != True )
{
XNextEventTimeout(mpfd->pdpy, &dummy, &timeout);
}
XCloseDisplay(mpfd->pdpy);
#ifdef XPU_USE_THREADS
#ifdef XPU_USE_NSPR
return;
#else
return(NULL);
#endif
#else
/* write the status to the parent */
if( XPU_TRACE_CHILD(write(mpfd->pipe[1], &mpfd->status, sizeof(XPGetDocStatus))) != sizeof(XPGetDocStatus) )
{
perror("PrintToFile_Consumer: can't write XPGetDocStatus");
}
/* we don't do any free's or close's, as we are just
* going to exit, in fact, get out without calling any C++
* destructors, etc., as we don't want anything funny to happen
* to the parent
*/
XPU_TRACE_CHILD(_exit(EXIT_SUCCESS));
#endif /* XPU_USE_THREADS */
}
/* EOF. */
|