diff options
Diffstat (limited to 'nx-X11/lib/XprintUtil/xprintutil_printtofile.c')
-rw-r--r-- | nx-X11/lib/XprintUtil/xprintutil_printtofile.c | 498 |
1 files changed, 498 insertions, 0 deletions
diff --git a/nx-X11/lib/XprintUtil/xprintutil_printtofile.c b/nx-X11/lib/XprintUtil/xprintutil_printtofile.c new file mode 100644 index 000000000..331e7ad5b --- /dev/null +++ b/nx-X11/lib/XprintUtil/xprintutil_printtofile.c @@ -0,0 +1,498 @@ +/****************************************************************************** + ****************************************************************************** + ** + ** (c) Copyright 2001-2004 Roland Mainz <roland.mainz@nrubsig.org> + ** + ** 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. */ |