/****************************************************************************** ****************************************************************************** ** ** (c) Copyright 2001-2004 Roland Mainz ** ** 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 #include #include #include #include #ifdef XPU_USE_THREADS #include #ifdef XPU_USE_NSPR #include #else #include #endif /* XPU_USE_NSPR */ #endif /* XPU_USE_THREADS */ #include #include #include #include /* 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: 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. */