/* 
 * Copyright (C) 1997-98 Rasca, Berlin
 * Copyright (C) 2003-06 Karl H. Beckers, Frankfurt
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *
 * This file contains routines used for capturing individual frames.
 * Theese routines are called from the record button callback in
 * the GUI and call themselves again till they are stopped by a stop
 * button event handler, a timeout, exceeding the maximum number of
 * frames (see the various VC_... states)
 *
 */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#ifdef HAVE_STDINT_H
#include <stdint.h>
#endif                          // HAVE_STDINT_H

#include <stdio.h>
#include <limits.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/XWDFile.h>
#include <X11/Xproto.h>
#include <X11/Xlibint.h>
#include <X11/cursorfont.h>

#ifdef SOLARIS
#include <X11/X.h>
#include <X11/Xlib.h>
#endif                          // SOLARIS

#ifdef HAVE_SHMAT
#include <sys/ipc.h>
#include <sys/shm.h>
#include <X11/extensions/XShm.h>
#endif                          // HAVE_SHMAT

#ifdef HasDGA
#include <X11/extensions/xf86dga.h>
#endif                          // HasDGA

#ifdef HasVideo4Linux
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include "video.h"
#endif                          // HasVideo4Linux

#include "capture.h"
#include "job.h"
#include "control.h"


#define DEBUGFILE "capture.c"




/* 
 * the following function finds out where the mouse pointer is
 */
static void getCurrentPointer(int *x, int *y, Job * mjob)
{
#define DEBUGFUNCTION "getCurrentPointer()"
    Window mrootwindow, childwindow;
    int dummy;
    Display *dpy;
    Screen *scr;

    if (!(mjob->flags & FLG_NOGUI)) {
        scr = mjob->win_attr.screen;
        dpy = DisplayOfScreen(scr);
    } else {
        dpy = XOpenDisplay(NULL);
    }
    mrootwindow = DefaultRootWindow(dpy);

    if (XQueryPointer(dpy, mrootwindow, &mrootwindow, &childwindow,
                      x, y, &dummy, &dummy, (unsigned int *) &dummy)) {
        // if the XQueryPointer was successfull, we have everything we
        // need in the variables passed as result pointers
    } else {
        fprintf(stderr,
                "%s %s: couldn't find mouse pointer for disp: %p , rootwindow: %p\n",
                DEBUGFILE, DEBUGFUNCTION, dpy,
                &(RootWindow(dpy, DefaultScreen(dpy))));
        *x = -1;
        *y = -1;
    }

    if (mjob->flags & FLG_NOGUI) {
        XCloseDisplay(dpy);
    }
#undef DEBUGFUNCTION
}


/* 
 * paint the dummy mouse pointer into a given frame
 */
static void paintMousePointer(int *x, int *y, XImage * image)
{
#define DEBUGFUNCTION "paintMousePointer()"
    uint16_t mousePointerBlack[] =
        { 0, 49152, 40960, 36864, 34816, 33792, 33280, 33024, 32896, 32832,
        33728, 37376, 43264, 51456, 1152, 1152, 576, 576, 448, 0
    };
    uint16_t mousePointerWhite[] =
        { 0, 0, 16384, 24576, 28672, 30720, 31744, 32256, 32512, 32640,
        31744,
        27648, 17920, 1536, 768, 768, 384, 384, 0, 0
    };
    Job *mjob;

    mjob = xvc_job_ptr();
    // only paint a mouse pointer into the dummy frame if the position of
    // the mouse is within the rectangle defined by the capture frame

    if ((*x - mjob->area->x) >= 0
        && *x < (mjob->area->width + mjob->area->x)
        && (*y - mjob->area->y) >= 0
        && *y < (mjob->area->height + mjob->area->y)) {
        int line;
        uint8_t *im_data = (uint8_t *) image->data;

        // move the cursor to the right starting position
        // first: shift to right line
        im_data += (image->bytes_per_line * (*y - mjob->area->y));
        // then: shift to right pixel
        im_data += (image->bits_per_pixel / 8 * (*x - mjob->area->x));

        switch (image->bits_per_pixel) {
        case 32:
            {
                uint32_t *cursor;
                int width;
                uint16_t bm_b, bm_w, mask;

                // the dummy mouse pointer is 20 pixels high ...
                for (line = 0; line < 20; line++) {
                    if (mjob->mouseWanted == 1) {
                        bm_b = mousePointerBlack[line];
                        bm_w = mousePointerWhite[line];
                    } else {
                        bm_b = mousePointerWhite[line];
                        bm_w = mousePointerBlack[line];
                    }
                    mask = (0x0001 << 15);

                    // make sure we don't run out of the screenshot
                    if ((*y + line) < (mjob->area->height + mjob->area->y)) {
                        // ... and 16 pixels wide
                        for (cursor = (uint32_t *) im_data, width = 0;
                             ((width + *x) <
                              (mjob->area->width + mjob->area->x)
                              && width < 16); cursor++, width++) {
                            if ((bm_b & mask) > 0) {
                                *cursor &=
                                    ((~image->red_mask) & (~image->
                                                           green_mask) &
                                     (~image->blue_mask));
                            } else if ((bm_w & mask) > 0) {
                                *cursor |=
                                    (image->red_mask | image->
                                     green_mask | image->blue_mask);
                            }
                            mask >>= 1;
                        }
                        im_data += image->bytes_per_line;
                    }
                }
            }
            break;
        case 24:
            // not sure this can occur at all ..........
            fprintf(stderr,
                    "%s %s: input image bits_per_pixel %i not implemented with mouse pointer capture ... \naborting!\n",
                    DEBUGFILE, DEBUGFUNCTION, image->bits_per_pixel);
            fprintf(stderr,
                    "Please file a bug at http://www.sourceforge.net/projects/xvidcap/\n");
            exit(1);
            break;
        case 16:
            {
                uint16_t *cursor;
                int width;
                uint16_t bm_b, bm_w, mask;

                // the dummy mouse pointer is 20 pixels high ....
                for (line = 0; line < 20; line++) {
                    if (mjob->mouseWanted == 1) {
                        bm_b = mousePointerBlack[line];
                        bm_w = mousePointerWhite[line];
                    } else {
                        bm_b = mousePointerWhite[line];
                        bm_w = mousePointerBlack[line];
                    }
                    mask = (0x0001 << 15);

                    // make sure we don't run out of the screenshot
                    if ((*y + line) < (mjob->area->height + mjob->area->y)) {
                        // ... and 16 pixels wide
                        for (cursor = (uint16_t *) im_data, width = 0;
                             ((width + *x) <
                              (mjob->area->width + mjob->area->x)
                              && width < 16); cursor++, width++) {
                            if ((bm_b & mask) > 0) {
                                *cursor &=
                                    ((~image->red_mask) & (~image->
                                                           green_mask) &
                                     (~image->blue_mask));
                            } else if ((bm_w & mask) > 0) {
                                *cursor |=
                                    (image->red_mask | image->
                                     green_mask | image->blue_mask);
                            }
                            mask >>= 1;
                        }
                        im_data += image->bytes_per_line;
                    }
                }
            }
            break;
        case 8:
            {
                uint8_t *cursor;
                int width;
                uint16_t bm_b, bm_w, mask;

                // the dummy mouse pointer is 20 pixels high ...
                for (line = 0; line < 20; line++) {
                    if (mjob->mouseWanted == 1) {
                        bm_b = mousePointerBlack[line];
                        bm_w = mousePointerWhite[line];
                    } else {
                        bm_b = mousePointerWhite[line];
                        bm_w = mousePointerBlack[line];
                    }
                    mask = (0x0001 << 15);

                    // make sure we don't run out of the screenshot
                    if ((*y + line) < (mjob->area->height + mjob->area->y)) {
                        // ... and 16 pixels wide
                        for (cursor = im_data, width = 0;
                             ((width + *x) <
                              (mjob->area->width + mjob->area->x)
                              && width < 16); cursor++, width++) {
                            if ((bm_b & mask) > 0) {
                                *cursor = 0;
                            } else if ((bm_w & mask) > 0) {
                                *cursor = 1;
                            }
                            mask >>= 1;
                        }
                        im_data += image->bytes_per_line;
                    }
                }
            }
            break;
        default:
            fprintf(stderr,
                    "%s %s: input image bits_per_pixel %i not supported with mouse pointer capture ... \naborting!\n",
                    DEBUGFILE, DEBUGFUNCTION, image->bits_per_pixel);
            exit(1);
        }
    }
#undef DEBUGFUNCTION
}



/* 
 * just read new data in the image structure, the image
 * structure inclusive the data area must be allocated before
 */
static Boolean
XGetZPixmap(Display * dpy, Drawable d, XImage * image, int x, int y)
{
#define DEBUGFUNCTION "XGetZPixmap()"
    register xGetImageReq *req = NULL;
    xGetImageReply rep;
    long nbytes;

#ifdef DEBUG
    printf("%s %s: Entering\n", DEBUGFILE, DEBUGFUNCTION);
#endif                          // DEBUG

    if (!image)
        return (False);
    LockDisplay(dpy);
    GetReq(GetImage, req);
    /* 
     * first set up the standard stuff in the request
     */
    req->drawable = d;
    req->x = x;
    req->y = y;
    req->width = image->width;
    req->height = image->height;
    req->planeMask = AllPlanes;
    req->format = ZPixmap;

    if (_XReply(dpy, (xReply *) & rep, 0, xFalse) == 0 || rep.length == 0) {
        UnlockDisplay(dpy);
        SyncHandle();
        return (False);
    }

    nbytes = (long) rep.length << 2;
#ifdef DEBUG
    printf("%s %s: read %i bytes\n", DEBUGFILE, DEBUGFUNCTION, nbytes);
#endif                          // DEBUG

    _XReadPad(dpy, image->data, nbytes);

    UnlockDisplay(dpy);
    SyncHandle();

#ifdef DEBUG
    printf("%s %s: Leaving\n", DEBUGFILE, DEBUGFUNCTION);
#endif                          // DEBUG

    return (True);
#undef DEBUGFUNCTION
}


FILE *getOutputFile(Job * job)
{
#define DEBUGFUNCTION "getOutputFile()"
    char file[PATH_MAX + 1];
    FILE *fp = NULL;

    if ((job->flags & FLG_MULTI_IMAGE) != 0) {
        sprintf(file, job->file, job->movie_no);
    } else {
        sprintf(file, job->file, job->pic_no);
    }

    fp = fopen(file, job->open_flags);
    if (!fp) {
        perror(file);
        job->state = VC_STOP;
    }

    return fp;                  // possibly NULL
#undef DEBUGFUNCTION
}


Display *getCaptureDisplay(Job * job)
{
#define DEBUGFUNCTION "getCaptureDisplay()"
    Screen *scr;
    Display *dpy;

    if (!(job->flags & FLG_NOGUI)) {
        scr = job->win_attr.screen;
        dpy = DisplayOfScreen(scr);
    } else {
        dpy = XOpenDisplay(NULL);
    }

    return dpy;                 // possibly NULL
#undef DEBUGFUNCTION
}


XImage *captureFrameCreatingImage(Display * dpy, Job * job)
{
#define DEBUGFUNCTION "captureFrameCreatingImage()"
    int x, y;                   // trace mouse pointer
    XImage *image = NULL;

#ifdef DEBUG
    printf("%s %s: Entering\n", DEBUGFILE, DEBUGFUNCTION);
#endif                          // DEBUG

    // don't find the current pointer position if we don't need it
    if (job->mouseWanted > 0) {
        getCurrentPointer(&x, &y, job);
    }
#ifdef DEBUG
    printf("%s %s: going to fetch image next\n", DEBUGFILE, DEBUGFUNCTION);
#endif                          // DEBUG
    // get the image here
    image = XGetImage(dpy, RootWindow(dpy, DefaultScreen(dpy)),
                      job->area->x, job->area->y,
                      job->area->width, job->area->height,
                      AllPlanes, ZPixmap);
    if (!image) {
        printf("%s %s: Can't get image: %dx%d+%d+%d\n",
               DEBUGFILE, DEBUGFUNCTION, job->area->width,
               job->area->height, job->area->x, job->area->y);
        job->state = VC_STOP;
    } else {
        // paint the mouse pointer into the captured image if necessary
        if (job->mouseWanted > 0) {
            paintMousePointer(&x, &y, image);
        }
    }

#ifdef DEBUG
    printf("%s %s: Leaving\n", DEBUGFILE, DEBUGFUNCTION);
#endif                          // DEBUG

    return image;
#undef DEBUGFUNCTION
}


int captureFrameToImage(Display * dpy, Job * job, XImage * image)
{
#define DEBUGFUNCTION "captureFrameToImage()"
    int x, y, ret = 0;

#ifdef DEBUG
    printf("%s %s: Entering\n", DEBUGFILE, DEBUGFUNCTION);
#endif                          // DEBUG

    // don't find the current pointer position if we don't need it
    if (job->mouseWanted > 0) {
        getCurrentPointer(&x, &y, job);
    }
#ifdef DEBUG
    printf("%s %s: going to fetch image next\n", DEBUGFILE, DEBUGFUNCTION);
#endif                          // DEBUG
    // get the image here
    if (XGetZPixmap(dpy,
                    RootWindow(dpy, DefaultScreen(dpy)),
                    image, job->area->x, job->area->y)) {
        // paint the mouse pointer into the captured image if necessary
        if (job->mouseWanted > 0) {
            paintMousePointer(&x, &y, image);
        }
        ret = 1;
    }
#ifdef DEBUG
    printf("%s %s: Leaving\n", DEBUGFILE, DEBUGFUNCTION);
#endif                          // DEBUG

    return ret;
#undef DEBUGFUNCTION
}


int captureFrameToImageSHM(Display * dpy, Job * job, XImage * image)
{
#define DEBUGFUNCTION "captureFrameToImageSHM()"
    int x, y, ret = 0;

#ifdef DEBUG
    printf("%s %s: Entering\n", DEBUGFILE, DEBUGFUNCTION);
#endif                          // DEBUG

    // don't find the current pointer position if we don't need it
    if (job->mouseWanted > 0) {
        getCurrentPointer(&x, &y, job);
    }
#ifdef DEBUG
    printf("%s %s: going to fetch image next\n", DEBUGFILE, DEBUGFUNCTION);
#endif                          // DEBUG
    // get the image here
    if (XShmGetImage(dpy,
                     RootWindow(dpy, DefaultScreen(dpy)),
                     image, job->area->x, job->area->y, AllPlanes)) {
        // paint the mouse pointer into the captured image if necessary
        if (job->mouseWanted > 0) {
            paintMousePointer(&x, &y, image);
        }
        ret = 1;
    }
#ifdef DEBUG
    printf("%s %s: Leaving\n", DEBUGFILE, DEBUGFUNCTION);
#endif                          // DEBUG

    return ret;
#undef DEBUGFUNCTION
}


XImage *captureFrameCreatingImageSHM(Display * dpy, Job * job,
                                     XShmSegmentInfo * shminfo)
{
#define DEBUGFUNCTION "captureFrameCreatingImageSHM()"
    int x, y;                   // trace mouse pointer
    XImage *image = NULL;

    Visual *visual = job->win_attr.visual;
    unsigned int depth = job->win_attr.depth;

#ifdef DEBUG
    printf("%s %s: Entering\n", DEBUGFILE, DEBUGFUNCTION);
#endif                          // DEBUG

    // don't find the current pointer position if we don't need it
    if (job->mouseWanted > 0) {
        getCurrentPointer(&x, &y, job);
    }
#ifdef DEBUG
    printf("%s %s: going to fetch image next\n", DEBUGFILE, DEBUGFUNCTION);
#endif                          // DEBUG
    // get the image here
    image = XShmCreateImage(dpy, visual, depth, ZPixmap, NULL,
                            shminfo, job->area->width, job->area->height);
    if (!image) {
        printf("%s %s: Can't get image: %dx%d+%d+%d\n",
               DEBUGFILE, DEBUGFUNCTION, job->area->width,
               job->area->height, job->area->x, job->area->y);
        job_set_state(VC_STOP);
    } else {
        shminfo->shmid = shmget(IPC_PRIVATE,
                                image->bytes_per_line * image->height,
                                IPC_CREAT | 0777);
        if (shminfo->shmid == -1) {
            printf("%s %s: Fatal: Can't get shared memory!\n",
                   DEBUGFILE, DEBUGFUNCTION);
            exit(1);
        }
        shminfo->shmaddr = image->data = shmat(shminfo->shmid, 0, 0);
        shminfo->readOnly = False;

        if (XShmAttach(dpy, shminfo) == 0) {
            printf("%s %s: Fatal: Failed to attach shared memory!\n",
                   DEBUGFILE, DEBUGFUNCTION);
            exit(1);
        }

        captureFrameToImageSHM(dpy, job, image);
    }

#ifdef DEBUG
    printf("%s %s: Leaving\n", DEBUGFILE, DEBUGFUNCTION);
#endif                          // DEBUG

    return image;
#undef DEBUGFUNCTION
}


long checkCaptureDuration(Job * job, long time, long time1)
{
#define DEBUGFUNCTION "checkCaptureDuration()"
    struct timeval curr_time;   // for measuring the duration of a frame
    // capture

    // update monitor widget, here time is the time the capture took
    // time == 0 resets led_meter
    if (time1 < 1)
        time1 = 1;
    if (!(job->flags & FLG_NOGUI))
        xvc_idle_add(xvc_frame_monitor, (void *) time1, FALSE);
    // calculate the remaining time we have till capture of next frame
    time1 = job->time_per_frame - time1;

    if (time1 > 0) {
        // get time again because updating frame drop meter took some time
        // we're only doing this if wait time for next frame hasn't been
        // negative already before
        gettimeofday(&curr_time, NULL);
        time = (curr_time.tv_sec * 1000 + curr_time.tv_usec / 1000) - time;
        time = job->time_per_frame - time;
    } else {
        time = time1;
    }
    if (time < 0) {
        if (job->flags & FLG_RUN_VERBOSE)
            printf
                ("missing %ld milli secs (%d needed per frame), pic no %d\n",
                 time, job->time_per_frame, job->pic_no);
    }
    // we need a little time to update the GUI (frame_drop_meter)
    /* if (time < 2) time = 2; */

    return time;
#undef DEBUGFUNCTION
}


// timer callback for capturing
// this is used with source = x11 ... capture from X11 display w/o SHM
long TCbCaptureX11(XtPointer xtp, XtIntervalId * id)
{
#define DEBUGFUNCTION "TCbCaptureX11()"
    Job *job = (Job *) xtp;
    static XImage *image = NULL;
    static FILE *fp = NULL;     // file handle to write the frame to
    long time, time1;           // for measuring the duration of a frame
    // capture
    struct timeval curr_time;   // for measuring the duration of a frame
    // capture
    static Display *dpy;

#ifdef DEBUG
    printf("%s %s: pic_no=%d - state=%i\n", DEBUGFILE, DEBUGFUNCTION,
           job->pic_no, job->state);
#endif                          // DEBUG

    // wait for next iteration if pausing
    if (job->state & VC_REC) {  // if recording ...

#ifdef DEBUG
        printf("%s %s: we're recording\n", DEBUGFILE, DEBUGFUNCTION);
#endif                          // DEBUG

        // the next bit is true if we have configured max_frames and we're
        // reaching the limit with this captured frame
        if (job->max_frames && ((job->pic_no - job->start_no) >
                                job->max_frames - 1)) {

#ifdef DEBUG
            printf("%s %s: this is the final frame\n", DEBUGFILE,
                   DEBUGFUNCTION);
#endif                          // DEBUG

            if (job->flags & FLG_RUN_VERBOSE)
                printf("%s %s: Stopped! pic_no=%d max_frames=%d\n",
                       DEBUGFILE, DEBUGFUNCTION, job->pic_no,
                       job->max_frames);
            if (!(job->flags & FLG_NOGUI))
                xvc_idle_add(xvc_change_filename_display,
                             (void *) job->pic_no, FALSE);

            // we need to stop the capture to go through the necessary
            // cleanup routines for writing a correct file. If we have
            // autocontinue on we're setting a flag to let the cleanup
            // code know we need
            // to restart again afterwards
            if (job->flags & FLG_AUTO_CONTINUE) {
                job_merge_state(VC_CONTINUE);
            }

            goto CLEAN_X11;
        }
        // this might be a single step. If so, remove the state flag so we 
        // 
        // don't keep single stepping
        job_remove_state(VC_STEP);

        // take the time before starting the capture
        gettimeofday(&curr_time, NULL);
        time = curr_time.tv_sec * 1000 + curr_time.tv_usec / 1000;

        // open the output file we need to do this for every frame for
        // individual frame 
        // capture and only once for capture to movie
        if ((!(job->flags & FLG_MULTI_IMAGE)) ||
            ((job->flags & FLG_MULTI_IMAGE) && (job->state & VC_START))) {

#ifdef DEBUG
            printf("%s %s: opening file for captured frame(s)\n",
                   DEBUGFILE, DEBUGFUNCTION);
#endif                          // DEBUG

            fp = getOutputFile(job);
            if (!fp)
                return FALSE;
        }
        // if this is the first frame for the current job ...
        if (job->state & VC_START) {

#ifdef DEBUG
            printf("%s %s: create data structure for image\n", DEBUGFILE,
                   DEBUGFUNCTION);
#endif                          // DEBUG

            // the first time this procedure is started
            // we must create a new image ..
            dpy = getCaptureDisplay(job);
            if (!dpy)
                return FALSE;

            image = captureFrameCreatingImage(dpy, job);
            if (image) {
                // call the necessary XtoXYZ function to process the image
                (*job->save) (fp, image, job);
                job_remove_state(VC_START);
            }
        } else {                // we're recording and not in the first
                                // frame ....
            // so we just read new data into the present image structure 

#ifdef DEBUG
            printf("%s %s: reading an image in a data sturctur present\n",
                   DEBUGFILE, DEBUGFUNCTION);
#endif                          // DEBUG

            if (captureFrameToImage(dpy, job, image) > 0)
                // call the necessary XtoXYZ function to process the image
                (*job->save) (fp, image, job);
            else
                printf("%s %s: XGetZPixmap returned 'False'!\n", DEBUGFILE,
                       DEBUGFUNCTION);
        }
        // this again is for recording, no matter if first frame or any
        // other close the image file if single frame capture mode
        if (!(job->flags & FLG_MULTI_IMAGE)) {

#ifdef DEBUG
            printf("%s %s: done saving image, closing file descriptor\n",
                   DEBUGFILE, DEBUGFUNCTION);
#endif                          // DEBUG

            fclose(fp);
        }
        // substract the time we needed for creating and saving the frame
        // to the file 
        gettimeofday(&curr_time, NULL);
        time1 =
            (curr_time.tv_sec * 1000 + curr_time.tv_usec / 1000) - time;

        time = checkCaptureDuration(job, time, time1);
        // time the next capture

#ifdef DEBUG
        printf
            ("%s %s: submitting capture of next frame in %ld milliseconds\n",
             DEBUGFILE, DEBUGFUNCTION, time);
#endif                          // DEBUG

        job->pic_no += job->step;
        // if we're not short on time, update frame count
        if (!(job->flags & FLG_NOGUI))
            xvc_idle_add(xvc_change_filename_display, (void *) job->pic_no,
                         FALSE);

        // the following means VC_STATE != VC_REC
        // this can only happen in capture.c if we were capturing and are
        // just stopping
    } else {
        int orig_state;

        // clean up 
      CLEAN_X11:

#ifdef DEBUG
        printf("%s %s: cleaning up\n", DEBUGFILE, DEBUGFUNCTION);
#endif                          // DEBUG

        time = 0;
        orig_state = job->state;    // store state here, esp. VC_CONTINUE

        if (image) {
            XDestroyImage(image);
            image = NULL;
        }

        job_set_state(VC_STOP);

        // set the sensitive stuff for the control panel if we don't
        // autocontinue 
        if ((orig_state & VC_CONTINUE) == 0)
            xvc_idle_add(xvc_capture_stop, job, FALSE);

        // clean up the save routines in xtoXXX.c 
        if (job->clean)
            (*job->clean) (job);
        // if we're recording to a movie, we must close the file now
        if (job->flags & FLG_MULTI_IMAGE)
            if (fp)
                fclose(fp);
        fp = NULL;

        if ((orig_state & VC_CONTINUE) == 0) {
            // after this we're ready to start recording again 
            job_merge_state(VC_READY);
        } else {

#ifdef DEBUG
            printf
                ("%s %s: autocontinue selected, preparing autocontinue\n",
                 DEBUGFILE, DEBUGFUNCTION);
#endif                          // DEBUG

            // prepare autocontinue
            job->movie_no += 1;
            job->pic_no = job->start_no;
            job_merge_and_remove_state((VC_START | VC_REC), VC_STOP);
            // start new capture session
            // xvc_capture_start(job);
            // and leave this one without further ado ...
            return time;
        }

        if (job->flags & FLG_NOGUI) {
            XCloseDisplay(dpy);
        }
    }

    return time;

#undef DEBUGFUNCTION
}


#ifdef HAVE_SHMAT
/* 
 * timer callback for capturing with shared memory
 */
long TCbCaptureSHM(XtPointer xtp, XtIntervalId * id)
{
#define DEBUGFUNCTION "TCbCaptureSHM()"

    Job *job = (Job *) xtp;
    static XImage *image = NULL;
    static FILE *fp = NULL;     // file handle to write the frame to
    long time, time1;           // for measuring the duration of a frame
    // capture
    struct timeval curr_time;   // for measuring the duration of a frame
    // capture
    static Display *dpy;
    static XShmSegmentInfo shminfo;

#ifdef DEBUG
    printf("%s %s: pic_no=%d flags=%d state=%i ", DEBUGFILE, DEBUGFUNCTION,
           job->pic_no, job->flags, job->state);
    printf("VC_REC %i - VC_STOP %i\n", (job->state & VC_REC),
           (job->state & VC_STOP));
    xvc_job_dump();
#endif                          // DEBUG

    // wait for next iteration if pausing
    if (job->state & VC_REC) {  // if recording ...

#ifdef DEBUG
        printf("%s %s: we're recording\n", DEBUGFILE, DEBUGFUNCTION);
#endif                          // DEBUG

        // the next bit is true if we have configured max_frames and we're
        // reaching the limit with this captured frame
        if (job->max_frames && ((job->pic_no - job->start_no) >
                                job->max_frames - 1)) {

#ifdef DEBUG
            printf("%s %s: this is the final frame\n", DEBUGFILE,
                   DEBUGFUNCTION);
#endif                          // DEBUG

            if (job->flags & FLG_RUN_VERBOSE)
                printf("%s %s: Stopped! pic_no=%d max_frames=%d\n",
                       DEBUGFILE, DEBUGFUNCTION, job->pic_no,
                       job->max_frames);
            if (!(job->flags & FLG_NOGUI))
                xvc_idle_add(xvc_change_filename_display,
                             (void *) job->pic_no, FALSE);

            // we need to stop the capture to go through the necessary
            // cleanup routines for writing a correct file. If we have
            // autocontinue on we're setting a flag to let the cleanup
            // code know we need
            // to restart again afterwards
            if (job->flags & FLG_AUTO_CONTINUE) {
                job_merge_state(VC_CONTINUE);
            }

            goto CLEAN_SHM;
        }
        // this might be a single step. If so, remove the state flag so we 
        // 
        // don't keep single stepping
        job_remove_state(VC_STEP);

        // take the time before starting the capture
        gettimeofday(&curr_time, NULL);
        time = curr_time.tv_sec * 1000 + curr_time.tv_usec / 1000;

        // open the output file we need to do this for every frame for
        // individual frame 
        // capture and only once for capture to movie
        if ((!(job->flags & FLG_MULTI_IMAGE)) ||
            ((job->flags & FLG_MULTI_IMAGE) && (job->state & VC_START))) {

#ifdef DEBUG
            printf("%s %s: opening file for captured frame(s)\n",
                   DEBUGFILE, DEBUGFUNCTION);
#endif                          // DEBUG

            fp = getOutputFile(job);
            if (!fp)
                return FALSE;
        }
        // if this is the first frame for the current job ...
        if (job->state & VC_START) {

#ifdef DEBUG
            printf("%s %s: create data structure for image\n", DEBUGFILE,
                   DEBUGFUNCTION);
#endif                          // DEBUG

            // the first time this procedure is started
            // we must create a new image ..
            dpy = getCaptureDisplay(job);
            if (!dpy)
                return FALSE;

            image = captureFrameCreatingImageSHM(dpy, job, &shminfo);
            if (image) {
                // call the necessary XtoXYZ function to process the image
                (*job->save) (fp, image, job);
                job_remove_state(VC_START);
            }
        } else {                // we're recording and not in the first
                                // frame ....
            // so we just read new data into the present image structure 

#ifdef DEBUG
            printf("%s %s: reading an image in a data sturctur present\n",
                   DEBUGFILE, DEBUGFUNCTION);
#endif                          // DEBUG

            if (captureFrameToImage(dpy, job, image) > 0)
                // call the necessary XtoXYZ function to process the image
                (*job->save) (fp, image, job);
            else
                printf("%s %s: XGetZPixmap returned 'False'!\n", DEBUGFILE,
                       DEBUGFUNCTION);
        }
        // this again is for recording, no matter if first frame or any
        // other close the image file if single frame capture mode
        if (!(job->flags & FLG_MULTI_IMAGE)) {

#ifdef DEBUG
            printf("%s %s: done saving image, closing file descriptor\n",
                   DEBUGFILE, DEBUGFUNCTION);
#endif                          // DEBUG

            fclose(fp);
        }
        // substract the time we needed for creating and saving the frame
        // to the file 
        gettimeofday(&curr_time, NULL);
        time1 =
            (curr_time.tv_sec * 1000 + curr_time.tv_usec / 1000) - time;

        time = checkCaptureDuration(job, time, time1);
        // time the next capture

#ifdef DEBUG
        printf
            ("%s %s: submitting capture of next frame in %ld milliseconds\n",
             DEBUGFILE, DEBUGFUNCTION, time);
#endif                          // DEBUG
        // xvc_add_timeout(time, job->capture);

        job->pic_no += job->step;
        if (!(job->flags & FLG_NOGUI))
            xvc_idle_add(xvc_change_filename_display, (void *) job->pic_no,
                         FALSE);

        // the following means VC_STATE != VC_REC
        // this can only happen in capture.c if we were capturing and are
        // just stopping
    } else {
        int orig_state;

        // clean up 
      CLEAN_SHM:

#ifdef DEBUG
        printf("%s %s: cleaning up\n", DEBUGFILE, DEBUGFUNCTION);
#endif                          // DEBUG

        time = 0;
        orig_state = job->state;    // store state here, esp. VC_CONTINUE

        if (image) {
            XShmDetach(dpy, &shminfo);
            image->data = NULL;
            XDestroyImage(image);
            image = NULL;
            shmdt(shminfo.shmaddr);
            shmctl(shminfo.shmid, IPC_RMID, 0);
        }

        job_set_state(VC_STOP);

        // set the sensitive stuff for the control panel if we don't
        // autocontinue 
        if ((orig_state & VC_CONTINUE) == 0)
            xvc_idle_add(xvc_capture_stop, job, FALSE);

        // clean up the save routines in xtoXXX.c 
        if (job->clean)
            (*job->clean) (job);
        // if we're recording to a movie, we must close the file now
        if (job->flags & FLG_MULTI_IMAGE)
            if (fp)
                fclose(fp);
        fp = NULL;

        if ((orig_state & VC_CONTINUE) == 0) {
            // after this we're ready to start recording again 
            job_merge_state(VC_READY);
        } else {

#ifdef DEBUG
            printf
                ("%s %s: autocontinue selected, preparing autocontinue\n",
                 DEBUGFILE, DEBUGFUNCTION);
#endif                          // DEBUG

            // prepare autocontinue
            job->movie_no += 1;
            job->pic_no = job->start_no;
            job_merge_and_remove_state((VC_START | VC_REC), VC_STOP);
            // start new capture session
            // xvc_capture_start(job);
            // and leave this one without further ado ...
            return time;
        }

        if (job->flags & FLG_NOGUI) {
            XCloseDisplay(dpy);
        }
    }

    return time;

#undef DEBUGFUNCTION
}

#endif                          /* HAVE_SHMAT */



// FIXME: update capture modes after this
#ifdef HasVideo4Linux
/* 
 * timer callback for capturing direct from bttv driver (only linux)
 */
#ifndef linux
#error only for linux
#endif
/* 
 * from bttv.h 
 */

Boolean TCbCaptureV4L(XtPointer xtp, XtIntervalId id *)
{
    Job *job = (Job *) xtp;
    static char file[PATH_MAX + 1];
    static XImage *image = NULL;
    static void *fp = NULL;
    static VIDEO *video = 0;
    static int size;
    static struct video_mmap vi_mmap;
    static struct video_mbuf vi_memb;
    static struct video_picture vi_pict;
    long time, time1;
    struct timeval curr_time;
    Display *dpy;
    Screen *scr;

    if (!(job->flags & FLG_NOGUI)) {
        scr = job->win_attr.screen;
        dpy = DisplayOfScreen(scr);
    } else {
        dpy = XOpenDisplay(NULL);
    }

#ifdef DEBUG2
    printf("TCbCaptureV4L() pic_no=%d\n", job->pic_no);
#endif
    if ((job->state & VC_PAUSE) && !(job->state & VC_STEP)) {
        xvc_add_timeout(job->time_per_frame, job->capture, job);

    } else if (job->state & VC_REC) {
        if (job->max_frames && ((job->pic_no - job->start_no) >
                                job->max_frames - 1)) {
            if (job->flags & FLG_RUN_VERBOSE)
                printf("Stopped! pic_no=%d max_frames=%d\n",
                       job->pic_no, job->max_frames);
            if ((!(job->flags & FLG_RUN_VERBOSE)) &
                (!(job->flags & FLG_NOGUI)))
                xvc_change_filename_display(job->pic_no);

            if (job->flags & FLG_AUTO_CONTINUE)
                job->state |= VC_CONTINUE;
            goto CLEAN_V4L;
        }
        job->state &= ~VC_STEP;
        gettimeofday(&curr_time, NULL);
        time = curr_time.tv_sec * 1000 + curr_time.tv_usec / 1000;

        if ((!(job->flags & FLG_MULTI_IMAGE)) ||
            ((job->flags & FLG_MULTI_IMAGE) && (job->state & VC_START))) {
            if (job->flags & FLG_MULTI_IMAGE)
                sprintf(file, job->file, job->movie_no);
            else
                sprintf(file, job->file, job->pic_no);
            fp = (*job->open) (file, job->open_flags);
            if (!fp) {
                perror(file);
                job->state = VC_STOP;
                return;
            }
        }
        if (job->state & VC_START) {
            /* 
             * the first time this procedure is started
             * we must prepare some stuff ..
             */
            if (!job->bpp)      /* use depth of the default window */
                job->bpp = job->win_attr.depth;

            sync();             /* remove if your bttv driver runs stable
                                 * .. */
            video = video_open(job->video_dev, O_RDWR);
            if (!video) {
                perror(job->video_dev);
                goto CLEAN_V4L;
            }
            vi_pict.depth = 0;
            /* 
             * read default values for hue, etc.. 
             */
            ioctl(video->fd, VIDIOCGPICT, &vi_pict);

            printf("%d->%d %d\n", job->bpp, vi_pict.depth,
                   vi_pict.palette);
            switch (job->bpp) {
            case 24:
                vi_mmap.format = vi_pict.palette = VIDEO_PALETTE_RGB24;
                break;
            case 16:
                vi_mmap.format = vi_pict.palette = VIDEO_PALETTE_RGB565;
                break;
            case 15:
                vi_mmap.format = vi_pict.palette = VIDEO_PALETTE_RGB555;
                break;
            default:
                printf("Fatal: unsupported bpp (%d)\n", job->bpp);
                exit(3);
                break;
            }
            ioctl(video->fd, VIDIOCSPICT, &vi_pict);
            printf("%d->%d %d\n", job->bpp, vi_pict.depth,
                   vi_pict.palette);


            vi_memb.size = 0;
            ioctl(video->fd, VIDIOCGMBUF, &vi_memb);
            printf("%d %d %d\n", vi_memb.size, vi_memb.frames,
                   vi_memb.offsets);

            image = (XImage *) XtMalloc(sizeof(XImage));
            if (!image) {
                printf("Can't get image: %dx%d+%d+%d\n", job->area->width,
                       job->area->height, job->area->x, job->area->y);
                goto CLEAN_V4L;
            }
            switch (job->bpp) {
            case 24:
                image->red_mask = 0xFF0000;
                image->green_mask = 0x00FF00;
                image->blue_mask = 0x0000FF;
                break;
            case 16:
                image->red_mask = 0x00F800;
                image->green_mask = 0x0007E0;
                image->blue_mask = 0x00001F;
                break;
            case 15:
                image->red_mask = 0x00F800;
                image->green_mask = 0x0007E0;
                image->blue_mask = 0x00001F;
                break;
            default:
                printf("Fatal: unsupported bpp (%d)\n", job->bpp);
                exit(3);
                break;
            }
            image->width = job->area->width;
            image->height = job->area->height;
            image->bits_per_pixel = job->bpp;
            image->bytes_per_line = job->bpp / 8 * image->width;
            image->byte_order = MSBFirst;
            size = image->width * image->height * job->bpp;
            video->size = vi_memb.size;
            video_mmap(video, 1);
            if (video->mmap == NULL) {
                perror("mmap()");
                goto CLEAN_V4L;
            }

            vi_mmap.width = image->width;
            vi_mmap.height = image->height;
            vi_mmap.frame = 0;
            image->data = video->mmap;
        }
        /* 
         * just read new data in the image structure 
         */
        if (ioctl(video->fd, VIDIOCMCAPTURE, &vi_mmap) < 0) {
            perror("ioctl(capture)");
            /* 
             * if (vb.frame) vb.frame = 0; else vb.frame = 1; 
             */
            goto CLEAN_V4L;
        }
        printf("syncing ..\n");
        if (ioctl(video->fd, VIDIOCSYNC, vi_mmap.frame) < 0) {
            perror("ioctl(sync)");
        }
        printf("synced()\n");

        (*job->save) (fp, image, job);
        job->state &= ~VC_START;

        if (!(job->flags & FLG_MULTI_IMAGE))
            (*job->close) (fp);

        /* 
         * substract the time we needed for creating and saving the frame
         * to the file 
         */
        gettimeofday(&curr_time, NULL);
        time1 =
            (curr_time.tv_sec * 1000 + curr_time.tv_usec / 1000) - time;
        // update monitor widget, here time is the time the capture took
        // time == 0 resets led_meter
        if (!time1)
            time1 = 1;
        if (!(job->flags & FLG_NOGUI))
            xvc_frame_monitor(time1);
        // printf("time: %i time_per_frame: %i\n", time1,
        // job->time_per_frame);
        // calculate the remaining time we have till capture of next frame
        time1 = job->time_per_frame - time1;

        if (time1 > 0) {
            // get time again because updating frame drop meter took some
            // time
            gettimeofday(&curr_time, NULL);
            time =
                (curr_time.tv_sec * 1000 + curr_time.tv_usec / 1000) -
                time;
            time = job->time_per_frame - time;
        } else {
            time = time1;
        }
        if (time < 0) {
            if (job->flags & FLG_RUN_VERBOSE)
                printf
                    ("missing %ld milli secs (%d needed per frame), pic no %d\n",
                     time, job->time_per_frame, job->pic_no);
        }
        if (time < 2)
            time = 2;

        xvc_add_timeout(time, job->capture, job);

        job->pic_no += job->step;
        if (time >= 2 && (!(job->flags & FLG_NOGUI)))
            xvc_change_filename_display(job->pic_no);

    } else {
        int orig_state;

        /* 
         * clean up 
         */
      CLEAN_V4L:
        orig_state = job->state;    // store state here

        if (!(job->flags & FLG_NOGUI)) {
            // maybe the last update didn't succeed
            xvc_change_filename_display(job->pic_no);
            xvc_frame_monitor(0);
        }

        job->state = VC_STOP;
        if (image) {
            XtFree((char *) image);
            image = NULL;
        }
        if (video) {
            video_mmap(video, 0);
            video_close(video);
            video = NULL;
        }

        /* 
         * set the sensitive stuff for the control panel if we don't
         * autocontinue 
         */
        if ((orig_state & VC_CONTINUE) == 0)
            xvc_capture_stop(job);

        /* 
         * clean up the save routines in xtoXXX.c 
         */
        if (job->clean)
            (*job->clean) (job);
        if (job->flags & FLG_MULTI_IMAGE)
            if (fp)
                (*job->close) (fp);
        fp = NULL;

        if ((orig_state & VC_CONTINUE) == 0) {
            /* 
             * after this we're ready to start recording again 
             */
            job->state |= VC_READY;
        } else {
            job->movie_no += 1;
            job->pic_no = job->start_no;
            job->state &= ~VC_STOP;
            job->state |= VC_START;
            job->state |= VC_REC;
            xvc_capture_start(job);
            return;
        }

    }

    /* 
     * after this we're ready to start recording again 
     */
    job->state |= VC_READY;

    if (job->flags & FLG_NOGUI && (!is_filename_mutable(job->file))) {
        XCloseDisplay(dpy);
    }

    return FALSE;
}
#endif                          /* HasVideo4Linux */

#ifdef HasDGA
/* 
 * direct graphic access
 * this doesn't work until now and may be removed in future..!?
 *
 * IT HAS ALSO NOT BEEN REWRITTEN FOR GTK GUI SUPPORT
 */
Boolean TCbCaptureDGA(XtPointer xtp, XtIntervalId id *)
{
    Job *job = (Job *) xtp;
    static char file[PATH_MAX + 1];
    static XImage *image = NULL;
    static void *fp;
    static int size;
    static Display *dpy;
    long time;
    struct timeval curr_time;

#ifdef DEBUG2
    printf("TCbCaptureDGA() pic_no=%d state=%d\n", job->pic_no,
           job->state);
#endif
    if ((job->state & VC_PAUSE) && !(job->state & VC_STEP)) {
        XtAppAddTimeOut(XtWidgetToApplicationContext(job->toplevel),
                        job->time_per_frame,
                        (XtTimerCallbackProc) job->capture, job);

    } else if (job->state & VC_REC) {
        if (job->max_frames && ((job->pic_no - job->start_no) >
                                job->max_frames - 1)) {
            if (job->flags & FLG_RUN_VERBOSE)
                printf("Stopped! pic_no=%d max_frames=%d\n",
                       job->pic_no, job->max_frames);
            if (!(job->flags & FLG_RUN_VERBOSE))
                ChangeLabel(job->pic_no);
            goto CLEAN_DGA;
        }
        job->state &= ~VC_STEP;
        gettimeofday(&curr_time, NULL);
        time = curr_time.tv_sec * 1000 + curr_time.tv_usec / 1000;

        if ((!(job->flags & FLG_MULTI_IMAGE)) ||
            ((job->flags & FLG_MULTI_IMAGE) && (job->state & VC_START))) {
            if (job->flags & FLG_MULTI_IMAGE)
                sprintf(file, job->file, job->movie_no);
            else
                sprintf(file, job->file, job->pic_no);
            fp = (*job->open) (file, job->open_flags);
            if (!fp) {
                perror(file);
                job->state = VC_STOP;
                return;
            }
        }
        if (job->state & VC_START) {
            /* 
             * the first time this procedure is started
             * we must create a new image structure ..
             */
            dpy = XtDisplay(job->toplevel);
            image = (XImage *) XtMalloc(sizeof(XImage));
            if (!image) {
                printf("Can't get image: %dx%d+%d+%d\n", job->area->width,
                       job->area->height, job->area->x, job->area->y);
                goto CLEAN_DGA;
            }
            image->width = job->area->width;
            image->height = job->area->height;
            image->bits_per_pixel = 3 * 8;
            image->bytes_per_line = 3 * image->width;
            image->byte_order = MSBFirst;
            size = image->width * image->height;
            {
                int width, bank, ram;
                char *base;
                XF86DGAGetVideo(dpy, XDefaultScreen(dpy), (char **) &base,
                                &width, &bank, &ram);
                image->data = base;
            }
            XF86DGADirectVideo(dpy, XDefaultScreen(dpy),
                               XF86DGADirectGraphics);
            (*job->save) (fp, image, job);
            XF86DGADirectVideo(dpy, XDefaultScreen(dpy), 0);
            job->state &= ~VC_START;
        } else {
            /* 
             * just read new data in the image structure 
             */
            XF86DGADirectVideo(dpy, XDefaultScreen(dpy),
                               XF86DGADirectGraphics);
            (*job->save) (fp, image, job);
            XF86DGADirectVideo(dpy, XDefaultScreen(dpy), 0);
        }
        if (!(job->flags & FLG_MULTI_IMAGE))
            (*job->close) (fp);

        /* 
         * substract the time we needed for creating and saving the frame
         * to the file 
         */
        gettimeofday(&curr_time, NULL);
        time = (curr_time.tv_sec * 1000 + curr_time.tv_usec / 1000) - time;
        time = job->time_per_frame - time;
        if (time < 0) {
            if (job->flags & FLG_RUN_VERBOSE)
                printf
                    ("missing %ld milli secs (%d needed per frame), pic no %d\n",
                     time, job->time_per_frame, job->pic_no);
            time = 0;
        }
        XtAppAddTimeOut(XtWidgetToApplicationContext(job->toplevel),
                        time, (XtTimerCallbackProc) job->capture, job);
        job->pic_no += job->step;
        /* 
         * update the label if we have time to do this 
         */
        if (time)
            ChangeLabel(job->pic_no);
    } else {
        /* 
         * clean up 
         */
      CLEAN_DGA:
        /* 
         * may be the last update failed .. so do it here before stop 
         */
        ChangeLabel(job->pic_no);
        job->state = VC_STOP;
        if (image) {
            XtFree((char *) image);
            image = NULL;
        }
        XF86DGADirectVideo(dpy, XDefaultScreen(dpy), 0);
        /* 
         * set the sensitive stuff for the control panel 
         */
        CbStop(NULL, NULL, NULL);

        /* 
         * clean up the save routines in xtoXXX.c 
         */
        if (job->clean)
            (*job->clean) (job);
        if (job->flags & FLG_MULTI_IMAGE)
            if (fp)
                (*job->close) (fp);
        fp = NULL;

        return FALSE;
    }
    return TRUE;
}
#endif                          /* HasDGA */


syntax highlighted by Code2HTML, v. 0.9.1