/*
 * File:        chvpinferno1.c
 *
 * Production:  Cock Hero Inferno Canto 1
 *
 * Author:      Doremi and friends
 *
 * Date:        2019/11/30
 *
 * Version:     1.0
 *
 * Description: Play a video file using the VLC library in fullscreen mode.
 *              If the video size is bigger than the screen, it is shrunk
 *              to the screen size.  If the video size is smaller than the
 *              screen, the video is played as is surrounded by a black
 *              background.  Tested under:
 *                - FreeBSD 11.2 using libvlc 3.0.6, libX11 1.6.7
 *                  and qvwm 1.1.12_2015.
 *
 *              Does not work under:
 *                - Debian 10 using libvlc 3.0.6, libX11 1.6.7
 *                  and Gnome 3.30.2.
 *                    libvlc doesn't seem to open a video window,
 *                    although the sound is being played.  This
 *                    part of the project will be on the ice until
 *                    Debian Linux release a new VLC package.
 *
 *              Should be fairly easy to port because there are not
 *              that many dependencies.
 *
 *              Accepted keyboard keys are:
 *                - 1            Jump to Round 1
 *                - 2            Jump to Round 2
 *                - 3            Jump to Round 3
 *                - 4            Jump to Round 4
 *                - 5            Jump to Round 5
 *                - 6            Jump to Round 6
 *                - 7            Jump to Extra Round 7
 *                - ENTER        I just came!  LOL!!!
 *                - ESC          Stop the video
 *                - ARROW RIGHT  Jump forward 10 seconds
 *                - ARROW LEFT   Jump backward 10 seconds
 *                - ARROW UP     Jump forward 1 minutes
 *                - ARROW DOWN   Jump backward 1 minutes
 *                - PAGE UP      Jump forward 10 minutes
 *                - PAGE DOWN    Jump backward 10 minutes
 *                - HOME         Jump to the beginning
 *                - END          Jump 10 seconds before the end
 *                - SPACE        Pause the video
 *
 *              Accepted keypad keys are:
 *                - +            Increase the volume
 *                - -            Decrease the volume
 *                - *            Change the audio track
 *                - 1-9          Move a smaller view to the
 *                               specified area within the screen.
 *
 *              Note that since fullscreen mode is not well implemented
 *              and/or documented in X11 and window managers, the
 *              technique used here is simply to offset the window
 *              decorations out of the visible screen area.  However,
 *              minimal MOTIF wizardry is attempted first should a
 *              'modern' window manager be present.
 *
 *              Known issues:
 *                1) There's a 1 second short play before fullscreen is
 *                   kicked in.  This is because libvlc creates its
 *                   playing thread only at the PLAY command, and there's
 *                   a delay before video stats are made available.
 *
 *                2) Can't run chvpinferno1 if VLC is open.  This is
 *                   because libvlc doesn't provide a way to get the
 *                   window-id that it creates.
 *
 *                3) There's a 0.4 second max delay when pressing the
 *                   video browsing keys.  This will get fixed when I'll
 *                   have a better understanding of X11.
 *
 * Parameters:  The-video-offset-millisec  The-video-file-to-play
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <vlc/vlc.h>
#include <X11/Xlib.h>
#include <X11/keysym.h>
#include <X11/Xatom.h>
#include <X11/extensions/xf86vmode.h>




/*
 *  Constants
 */

// # define CHVP_DEBUG       1
#define CHVP_HSEC            500
#define CHVP_HSEC2           700
#define CHVP_10SEC           10000
#define CHVP_1MIN            60000
#define CHVP_10MIN           600000
#define CHVP_LIBVLCWMNAME    "VLC media player"
#define CHVP_WMNAME          "chvpinferno1"

#define ERROR_CHVP_USAGE     1
#define ERROR_CHVP_X11       2
#define ERROR_CHVP_VLC       3
#define ERROR_CHVP_FAIL      4
#define ERROR_CHVP_MEM       5
#define ERROR_CHVP_VLCRUN    6
#define ERROR_CHVP_LEN       7

#define LNSZ                 200




/*
 *  Global variables
 */

int           giOffset;
libvlc_time_t giEndTimeMs;




/*
 *  Crossed
 */

int
Crossed(libvlc_time_t iPrevious, libvlc_time_t iRef, libvlc_time_t iCurrent)
{
   return (iPrevious < iRef && iRef <= iCurrent
           && (iRef-iPrevious < CHVP_HSEC2 || iCurrent-iRef <= CHVP_HSEC2));
}




/*
 *  FilenameExist
 */

int
FilenameExist(const char *szFilename)
{
   int i;
   struct stat sStat;


   i = !stat(szFilename,     &sStat);
   if (i)
      i = (sStat.st_mode & S_IFREG);

   return(i);
}




/*
 *  MapState2sz
 */

void
MapState2sz(long iState,     char *pState)
{
   if (iState == IsUnmapped)
      strcpy(pState, "IsUnmapped");
   else if (iState == IsUnviewable)
      strcpy(pState, "IsUnviewable");
   else if (iState == IsViewable)
      strcpy(pState, "IsViewable");
   else
      sprintf(pState, "%ld", iState);
}




/*
 *  FindMaster
 */

void
FindMaster(Display *pX11Display, Window w,     Window *pMaster)
{
   int            iRet;
   unsigned int   nChildren;
   Window         wParent,
                  wRoot,
                  *pChildren;


   *pMaster = w;

   do
   {
      pChildren = NULL;
      iRet = XQueryTree(pX11Display, w,     &wRoot, &wParent,
                                            &pChildren, &nChildren);
      if (pChildren)
         XFree(pChildren);
      if (iRet)
      {
         if (wParent == wRoot)
            *pMaster = w;
         else
            w = wParent;
      }
   }
   while (iRet && wParent != wRoot);
}




/*
 *  FindX11Window
 */

int
FindX11Window(Display *pX11Display, Window w, Atom aName, Atom aType,
              char *szValue,     Window *pReturn, Window *pMaster)
{
   int            i,
                  iFormat,
                  iFound = 0,
                  iRet;
   unsigned int   nChildren;
   unsigned long  iNumItems,
                  iByteRemaining;
   unsigned char  *pProperty = NULL;
   Atom           aRet;
   Status         iStatus;
   Window         wParent,
                  wRoot,
                  *pChildren = NULL;


   iRet = XGetWindowProperty(pX11Display, w, aName, 0, 4, 0, aType,
             &aRet, &iFormat, &iNumItems, &iByteRemaining, &pProperty);
   if (iRet == Success && pProperty)
   {
      iFound = !strcmp((char *)pProperty, szValue);
      if (iFound)
         *pReturn = w;  // Note that pMaster was found earlier in the tree
   }

   // Check the window's children if needed
   if (!iFound && XQueryTree(pX11Display, w,     &wRoot, &wParent,
                                                 &pChildren, &nChildren))
   {
      if (pChildren && nChildren)
         for (i = 0 ; i < nChildren && !iFound ; i++)
            if (pChildren[i] != w && pChildren[i] != wRoot
                && pChildren[i] != wParent)
            {
               if (w == wRoot)
                  *pMaster = pChildren[i];
               iFound = FindX11Window(pX11Display, pChildren[i], aName,
                                      aType, szValue,
                                                        pReturn, pMaster);
            }
   }

   if (pChildren)
      XFree(pChildren);

   if (pProperty)
      XFree(pProperty);

   return(iFound);
}




/*
 *  FindVlcWindow
 */

int
FindVlcWindow(Display *pX11Display, Window wRoot,
                                       Window *pWinVlc, Window *pMaster)
{
   int  iErr = 0;
   Atom aName,
        aTypeStr;


   // WM_NAME(STRING) = "VLC media player"
   *pWinVlc = 0;  // The VLC window
   *pMaster = 0;  // The VLC parent window that has ROOT as its parent
   aName = XInternAtom(pX11Display, "WM_NAME", False);
   aTypeStr = XInternAtom(pX11Display, "STRING", False);
   if (aName != None && aTypeStr != None)
      FindX11Window(pX11Display, wRoot, aName, aTypeStr,
                    CHVP_LIBVLCWMNAME,     pWinVlc, pMaster);
   if (!(*pWinVlc))
      iErr = ERROR_CHVP_FAIL;

   return(iErr);
}




/*
 *  Offset
 */

libvlc_time_t
Offset(libvlc_time_t iTime)
{
   iTime += giOffset;
   if (iTime < 0)
      iTime = 0;
   else if (iTime > giEndTimeMs - CHVP_10SEC)
      iTime = giEndTimeMs - CHVP_10SEC;

   return (iTime);
}




/*
 *  PositionWindow
 */

void
PositionWindow(Display *pX11Display, Window w, int iKeypadPos,
               unsigned int scrx, unsigned int scry)
{
   int            x,
                  y;
   unsigned int   iBorder,
                  iDepth,
                  iHeight,
                  iWidth;
   Window         wRoot;


   if (XGetGeometry(pX11Display, w,     &wRoot, &x, &y, &iWidth, &iHeight,
                                        &iBorder, &iDepth))
      if (iWidth < scrx && iHeight < scry)
      {
         if (iKeypadPos == 1 || iKeypadPos == 4 || iKeypadPos == 7)
            x = 0;
         else if (iKeypadPos == 3 || iKeypadPos == 6 || iKeypadPos == 9)
            x = scrx - iWidth;
         else
            x = (scrx - iWidth) / 2;

         if (iKeypadPos == 7 || iKeypadPos == 8 || iKeypadPos == 9)
            y = 0;
         else if (iKeypadPos == 1 || iKeypadPos == 2 || iKeypadPos == 3)
            y = scry - iHeight;
         else
            y = (scry - iHeight) / 2;

         XMoveWindow(pX11Display, w, x, y);
      }
}




/*
 *  TaskbarFindAndUnmap
 *
 *  Very environment dependant.  This code currently looks for the xload
 *  program.  Other toolbar programs could also be tested if needed.
 */

void
TaskbarFindAndUnmap(Display *pX11Display, Window wRoot,
                                                      Window *pTaskbar)
{
   int      iRet;
   Atom     aName,
            aTypeStr;
   Window   wMaster = 0,
            wXload = 0;
   XWindowAttributes attrib;

#ifdef CHVP_DEBUG
   char     szState[LNSZ];
#endif // CHVP_DEBUG


   *pTaskbar = 0;
   // WM_NAME(STRING) = "xload"
   aName = XInternAtom(pX11Display, "WM_NAME", False);
   aTypeStr = XInternAtom(pX11Display, "STRING", False);
   if (aName != None && aTypeStr != None)
      FindX11Window(pX11Display, wRoot, aName, aTypeStr, "xload",
                                                      &wXload, &wMaster);
   if (wXload)
      if (XGetWindowAttributes(pX11Display, wMaster,     &attrib))
         if (attrib.override_redirect && attrib.map_state == IsViewable)
         {
            *pTaskbar = wMaster;

            //
            // Oldschool fullscreen, get rid of the taskbar
            //
            iRet = XUnmapWindow(pX11Display, wMaster);
#ifdef CHVP_DEBUG
            printf("%d=XUnmapWindow(w=0x%lX)\n", iRet, wMaster);
#endif // CHVP_DEBUG

            // Wait for the unmap to occur (so it seems)
            if (XGetWindowAttributes(pX11Display, wMaster,     &attrib))
            {
#ifdef CHVP_DEBUG
               MapState2sz(attrib.map_state,     szState);
               printf("XGetWindowAttributes: w=0x%lX, x=%d, y=%d,"
                      " width=%d, h=%d, state=%s, OvRedir=%d\n", wMaster,
                      attrib.x, attrib.y, attrib.width, attrib.height,
                      szState, attrib.override_redirect);
#endif // CHVP_DEBUG
            }
         }
}




/*
 *  TaskbarRaise
 */

void
TaskbarRaise(Display *pX11Display, Window wTaskbar)
{
   int iRet;
   XWindowAttributes attrib;

#ifdef CHVP_DEBUG
   char szState[LNSZ];
#endif // CHVP_DEBUG


   //
   // Oldschool fullscreen, restore the taskbar
   //
   if (wTaskbar)
   {
      iRet = XMapRaised(pX11Display, wTaskbar);
#ifdef CHVP_DEBUG
      printf("%d=XMapRaised(w=0x%lX)\n", iRet, wTaskbar);
#endif // CHVP_DEBUG

      // Wait for the raise to occur (so it seems)
      if (XGetWindowAttributes(pX11Display, wTaskbar,     &attrib))
      {
#ifdef CHVP_DEBUG
         MapState2sz(attrib.map_state,     szState);
         printf("XGetWindowAttributes: w=0x%lX, x=%d, y=%d, width=%d,"
                " h=%d, state=%s, OvRedir=%d\n", wTaskbar, attrib.x,
                attrib.y, attrib.width, attrib.height, szState,
                attrib.override_redirect);
#endif // CHVP_DEBUG
      }
   }
}




/*
 *  SetWindowFullscreen
 */

void
SetWindowFullscreen(Display *pX11Display, Window w, Window wMaster,
                    Window wRoot, unsigned int scrx, unsigned int scry)
{
   int    iRet,
          x = 0,
          y = 0;
   Bool   iOverrideRedirect;
   Atom   aMotifWmHints,
          aWmState,
          aWmStateFs;
   Window w2;
   XEvent ev;
   XSetWindowAttributes attribSet;
   XWindowAttributes attribGet;


   //
   // Remove the window decorations
   //

   struct
   {
      unsigned long flags;
      unsigned long functions;
      unsigned long decorations;
      long input_mode;
      unsigned long status;
   } sMotifWmHints;

   memset(&sMotifWmHints, 0, sizeof(sMotifWmHints));
   sMotifWmHints.flags = 2;   // MWM_HINTS_DECORATIONS;
   sMotifWmHints.decorations = 0;
   aMotifWmHints = XInternAtom(pX11Display, "_MOTIF_WM_HINTS", False);
   if (aMotifWmHints != None)
      XChangeProperty(pX11Display, w, aMotifWmHints, aMotifWmHints, 32,
                      PropModeReplace, (unsigned char *)&sMotifWmHints,
                      sizeof(sMotifWmHints) / sizeof(long));

   iRet = XMoveResizeWindow(pX11Display, w, 0, 0, scrx, scry);

#ifdef CHVP_DEBUG
   printf("%d=XMoveResizeWindow(0x%lX)\n", iRet, w);
#endif // CHVP_DEBUG

   //
   // Notify the parent window to go fullscreen
   //
   aWmState = XInternAtom(pX11Display, "_NET_WM_STATE", False);
   aWmStateFs = XInternAtom(pX11Display, "_NET_WM_STATE_FULLSCREEN",
                            False);
   if (aWmState != None && aWmStateFs != None)
   {
      memset(&ev, 0, sizeof(ev));
      ev.type = ClientMessage;
      ev.xclient.window = w;
      ev.xclient.message_type = aWmState;
      ev.xclient.format = 32;
      ev.xclient.data.l[0] = 1;
      ev.xclient.data.l[1] = aWmStateFs;
      ev.xclient.data.l[2] = 0;
      if (!XSendEvent(pX11Display, wRoot, 0, SubstructureNotifyMask, &ev))
         printf("WARNING: XSendEvent(_NET_WM_STATE) aborted!\n");
   }

   //
   // Oldschool fullscreen, just move the window decorations out of view
   //

   iRet = XTranslateCoordinates(pX11Display, w, wRoot, 0, 0,
                                                            &x, &y, &w2);
#ifdef CHVP_DEBUG
   printf("%d=XTranslateCoordinates(w=0x%lX,     x=%d, y=%d, w2=0x%lX)\n", iRet, w, x, y, w2);
#endif // CHVP_DEBUG

   if (x > 0 || y > 0)
   {
      if (XGetWindowAttributes(pX11Display, wMaster,     &attribGet))
      {
         iOverrideRedirect = attribGet.override_redirect;
         if (!iOverrideRedirect)
         {
            memset(&attribSet, 0, sizeof(attribSet));
            attribSet.override_redirect = 1;
            iRet = XChangeWindowAttributes(pX11Display, wMaster,
                                           CWOverrideRedirect, &attribSet);

#ifdef CHVP_DEBUG
            printf("%d=XChangeWindowAttributes(w=0x%lX, OvRedir=1)\n",
                   iRet, wMaster);
#endif // CHVP_DEBUG
         }
      }
      else
         iOverrideRedirect = 1;

      iRet = XMoveWindow(pX11Display, wMaster, -x, -y);

#ifdef CHVP_DEBUG
      printf("%d=XMoveWindow(w=0x%lX, x=%d, y=%d)\n",
             iRet, wMaster, -x, -y);
#endif // CHVP_DEBUG

      // Wait for the move to occur (so it seems)
      if (XGetWindowAttributes(pX11Display, wMaster,     &attribGet))
      {
#ifdef CHVP_DEBUG
         printf("XGetWindowAttributes: w=0x%lX, x=%d, y=%d, width=%d,"
                " h=%d, OvRedir=%d\n", wMaster, attribGet.x, attribGet.y,
                attribGet.width, attribGet.height,
                attribGet.override_redirect);
#endif // CHVP_DEBUG
      }

      if (!iOverrideRedirect)
      {
         attribSet.override_redirect = 0;
         iRet = XChangeWindowAttributes(pX11Display, wMaster,
                                        CWOverrideRedirect, &attribSet);

#ifdef CHVP_DEBUG
         printf("%d=XChangeWindowAttributes(w=0x%lX, OvRedir=0)\n",
                                            iRet, wMaster);
#endif // CHVP_DEBUG
      }

      // Wait for the attribute to return to normal (so it seems)
      if (XGetWindowAttributes(pX11Display, wMaster,     &attribGet))
      {
#ifdef CHVP_DEBUG
         printf("XGetWindowAttributes: w=0x%lX, x=%d, y=%d, width=%d,"
                " h=%d, OvRedir=%d\n", wMaster, attribGet.x, attribGet.y,
                attribGet.width, attribGet.height,
                attribGet.override_redirect);
#endif // CHVP_DEBUG
      }
   }
}




/*
 *  main
 */

int
main(int argc, char* argv[])
{
   int                        i,
                              iDotClock,
                              iErr = 0,
                              iHero = 0,
                              iNumVlcAudioTracks,
                              iPlay = 0,
                              iRet,
                              iRunning = 1,
                              iVlcAudioTrack,
                              iX11DefaultScreen,
                              iX11fd,
                              *pVlcAudioTrackId = NULL,
                              x,                y;
   unsigned int               scrx,             scry,
                              vidx,             vidy;
   unsigned long              iX11Black;
   char                       szErr[LNSZ],
                              szFilename[LNSZ];
   struct timeval             sEventLoopTimeout;
   Atom                       aString,
                              aWmName;
   Display                    *pX11Display = NULL;
   fd_set                     readfds;
   KeyCode                    kc1,              kc2,
                              kc3,              kc4,
                              kc5,              kc6,
                              kc7,              kcDown,
                              kcEnd,            kcEsc,
                              kcHome,           kcKpBegin,
                              kcKpDown,         kcKpEnd,
                              kcKpHome,         kcKpLeft,
                              kcKpMinus,        kcKpMult,
                              kcKpPlus,         kcKpPageDown,
                              kcKpPageUp,       kcKpUp,
                              kcKpRight,        kcLeft,
                              kcPgDown,         kcPgUp,
                              kcReturn,         kcRight,
                              kcSpace,          kcUp;
   libvlc_time_t              iPrevious = 0,
                              iTimeMs;
   libvlc_instance_t          *pVlcInst = NULL;
   libvlc_media_t             *pVlcMedia;
   libvlc_media_player_t      *pVlcPlayer = NULL;
   libvlc_track_description_t *pVlcAudioTrackDesc,
                              *pVlcATD;
   Status                     iStatus;
   Window                     w,
                              wInput,
                              wInputMaster,
                              wMaster,
                              wRoot,
                              wTaskbar = 0,
                              wVlc;
   XEvent                     loopEvent;
   XF86VidModeModeLine        modeLine;
   XSetWindowAttributes       attribSet;

 
   *szErr = 0;
   *szFilename = 0;
   memset(&modeLine, 0, sizeof(modeLine));

   if (argc < 2 || argc > 3)
      iErr = ERROR_CHVP_USAGE;
   if (!iErr)
   {
      for (i = 1 ; i < argc ; i++)
         if (FilenameExist(argv[i]) && strlen(argv[i]) < LNSZ)
            strcpy(szFilename, argv[i]);
         else
            giOffset = atoi(argv[i]);

      if (!(*szFilename))
         iErr = ERROR_CHVP_USAGE;
   }
   if (!iErr)
   {
      iStatus = XInitThreads();
#ifdef CHVP_DEBUG
      if (iStatus)
         printf("X11 Thread Support Active!\n");
      else
         printf("X11 Thread Support Unavailable!\n");
#endif // CHVP_DEBUG

      pX11Display = XOpenDisplay(NULL);
      if (!pX11Display)
      {
         iErr = ERROR_CHVP_X11;
         strcat(szErr, "XOpenDisplay() failed!");
      }
   }
   if (!iErr)
   {
      iX11DefaultScreen = XDefaultScreen(pX11Display);
      wRoot = XDefaultRootWindow(pX11Display);
      iX11Black = XBlackPixel(pX11Display, iX11DefaultScreen);
      XF86VidModeGetModeLine(pX11Display, iX11DefaultScreen,
                                  &iDotClock, &modeLine);
      scrx=modeLine.hdisplay;
      scry=modeLine.vdisplay;
      if (modeLine.private)
      {
         XFree(modeLine.private);
         modeLine.privsize = 0;
         modeLine.private = NULL;
      }
      printf("X11 Screen Size: %dx%d.\n", scrx, scry);

      XF86VidModeSetViewPort(pX11Display, iX11DefaultScreen, 0, 0);

      // Check if VLC is already running, and if so, stop!
      //
      // This check is required because libvlc doesn't provide us
      // the window-id that it creates.

      iErr = FindVlcWindow(pX11Display, wRoot,     &wVlc, &wMaster);
      if (iErr)
         iErr = 0;   // All good, not supposed to run yet
      else
         iErr = ERROR_CHVP_VLCRUN;

#ifdef CHVP_DEBUG
      printf("TESTRUN FindVlcWindow: root=0x%lX, vlc=0x%lX, master=0x%lX\n",
             wRoot, wVlc, wMaster);
#endif // CHVP_DEBUG
   }
   if (!iErr)
   {
      // Create the keyboard input/background window
      memset(&attribSet, 0, sizeof(attribSet));
      attribSet.background_pixel = iX11Black;
      attribSet.event_mask = KeyPressMask|ButtonReleaseMask;
      wInput = XCreateWindow(pX11Display, wRoot, 0, 0, scrx, scry,
                             0, 0, InputOutput, CopyFromParent,
                             CWBackPixel|CWEventMask,     &attribSet);
      if (!wInput)
      {
         iErr = ERROR_CHVP_X11;
         strcat(szErr, "XCreateWindow(wInput) failed!");
      }
   }
   if (!iErr)
   {
      aString = XInternAtom(pX11Display, "STRING", False);
      aWmName = XInternAtom(pX11Display, "WM_NAME", False);
      if (aString == None || aWmName == None)
      {
         iErr = ERROR_CHVP_X11;
         strcat(szErr, "XInternAtom() failed!");
      }
   }
   if (!iErr)
   {
      XChangeProperty(pX11Display, wInput, aWmName, aString, 8,
                      PropModeReplace, (unsigned char *)CHVP_WMNAME, 8);

      iRet = XMapWindow(pX11Display, wInput);
#ifdef CHVP_DEBUG
      printf("%d=XMapWindow(w=0x%lX)\n", iRet, wInput);
#endif // CHVP_DEBUG

      // Initialize the relevant keycodes
      kc1 =          XKeysymToKeycode(pX11Display, XK_1);
      kc2 =          XKeysymToKeycode(pX11Display, XK_2);
      kc3 =          XKeysymToKeycode(pX11Display, XK_3);
      kc4 =          XKeysymToKeycode(pX11Display, XK_4);
      kc5 =          XKeysymToKeycode(pX11Display, XK_5);
      kc6 =          XKeysymToKeycode(pX11Display, XK_6);
      kc7 =          XKeysymToKeycode(pX11Display, XK_7);
      kcDown =       XKeysymToKeycode(pX11Display, XK_Down);
      kcEnd =        XKeysymToKeycode(pX11Display, XK_End);
      kcEsc =        XKeysymToKeycode(pX11Display, XK_Escape);
      kcHome =       XKeysymToKeycode(pX11Display, XK_Home);
      kcKpBegin =    XKeysymToKeycode(pX11Display, XK_KP_Begin);
      kcKpDown =     XKeysymToKeycode(pX11Display, XK_KP_Down);
      kcKpEnd =      XKeysymToKeycode(pX11Display, XK_KP_End);
      kcKpHome =     XKeysymToKeycode(pX11Display, XK_KP_Home);
      kcKpLeft =     XKeysymToKeycode(pX11Display, XK_KP_Left);
      kcKpMinus =    XKeysymToKeycode(pX11Display, XK_KP_Subtract);
      kcKpMult =     XKeysymToKeycode(pX11Display, XK_KP_Multiply);
      kcKpPageDown = XKeysymToKeycode(pX11Display, XK_KP_Page_Down);
      kcKpPageUp =   XKeysymToKeycode(pX11Display, XK_KP_Page_Up);
      kcKpPlus =     XKeysymToKeycode(pX11Display, XK_KP_Add);
      kcKpRight =    XKeysymToKeycode(pX11Display, XK_KP_Right);
      kcKpUp =       XKeysymToKeycode(pX11Display, XK_KP_Up);
      kcLeft =       XKeysymToKeycode(pX11Display, XK_Left);
      kcPgDown =     XKeysymToKeycode(pX11Display, XK_Page_Down);
      kcPgUp =       XKeysymToKeycode(pX11Display, XK_Page_Up);
      kcReturn =     XKeysymToKeycode(pX11Display, XK_Return);
      kcRight =      XKeysymToKeycode(pX11Display, XK_Right);
      kcSpace =      XKeysymToKeycode(pX11Display, XK_space);
      kcUp =         XKeysymToKeycode(pX11Display, XK_Up);
      if (!(kc1 && kc2 && kc3 && kc4 && kc5 && kc6 && kc7 && kcDown
            && kcEnd && kcEsc && kcHome && kcKpBegin && kcKpDown
            && kcKpEnd && kcKpHome && kcKpLeft && kcKpMinus && kcKpMult
            && kcKpPageDown && kcKpPageUp && kcKpPlus && kcKpRight
            && kcKpUp && kcLeft && kcPgDown && kcPgUp && kcReturn
            && kcRight && kcSpace && kcUp))
         printf("WARNING: X11 keycodes weren't all found so some video"
                " browsing features may be missing at this time.\n");
                                 
      //Load the VLC engine
      printf("LibVLC Version %s, %s\n",
             libvlc_get_version(), libvlc_get_compiler());
      pVlcInst = libvlc_new(0, NULL);
      if (!pVlcInst)
      {
         iErr = ERROR_CHVP_VLC;
         strcat(szErr, "libvlc_new() failed!");
      }
   }
   if (!iErr)
   {
      /* Create a new item */
      pVlcMedia = libvlc_media_new_path(pVlcInst, szFilename);
      if (pVlcMedia)
      {
         /* Create a media player playing environement */
         pVlcPlayer = libvlc_media_player_new_from_media(pVlcMedia);
         if (!pVlcPlayer)
         {
            iErr = ERROR_CHVP_VLC;
            strcat(szErr, "libvlc_media_player_new_from_media() failed!");
         }

         /* No need to keep the media now */
         libvlc_media_release(pVlcMedia);
      }
      else
      {
         iErr = ERROR_CHVP_VLC;
         strcat(szErr, "libvlc_media_new_path() failed!");
      }
   }
   if (!iErr)
   {
      iPlay = 1;
      iErr = libvlc_media_player_play(pVlcPlayer);
      if (iErr)
         printf("Warning: VLC Play Failed!\n");
      libvlc_media_player_pause(pVlcPlayer);
      sleep(1); /* wait until the tracks are created */

#ifdef CHVP_DEBUG
      printf("0x%X=libvlc_media_player_get_xwindow()\n",
             libvlc_media_player_get_xwindow(pVlcPlayer));
#endif // CHVP_DEBUG

      iErr = libvlc_video_get_size(pVlcPlayer, 0,     &vidx, &vidy);
      if (iErr)
      {
         iErr = ERROR_CHVP_VLC;
         strcat(szErr, "libvlc_video_get_size() didn't find the video!");
      }
   }
   if (!iErr)
   {
      giEndTimeMs = libvlc_media_player_get_length(pVlcPlayer);
      if (giEndTimeMs <= 0)
      {
         iErr = ERROR_CHVP_VLC;
         strcat(szErr, "libvlc_media_player_get_length() failed!");
      }
      else if (giEndTimeMs < 4356064 - CHVP_HSEC + giOffset
               || giEndTimeMs > 4356064 + CHVP_HSEC + giOffset)
         iErr = ERROR_CHVP_LEN;
   }
   if (!iErr)
   {
      iNumVlcAudioTracks = libvlc_audio_get_track_count(pVlcPlayer);
      if (iNumVlcAudioTracks > 1)
      {
         pVlcAudioTrackId = malloc(iNumVlcAudioTracks * sizeof(int));
         if (pVlcAudioTrackId)
         {
            memset(pVlcAudioTrackId, 0,
                   sizeof(iNumVlcAudioTracks * sizeof(int)));
            pVlcAudioTrackDesc = pVlcATD
               = libvlc_audio_get_track_description(pVlcPlayer);
            if (pVlcAudioTrackDesc)
            {
               for (iRet = 0 ; pVlcATD && iRet < iNumVlcAudioTracks
                             ; iRet++)
               {
                  printf("Audio track found: %s\n\n", pVlcATD->psz_name);
                  pVlcAudioTrackId[iRet] = pVlcATD->i_id;
                  pVlcATD = pVlcATD->p_next;
               }
               libvlc_track_description_list_release(pVlcAudioTrackDesc);
            }
         }
         else
            iErr = ERROR_CHVP_MEM;
      }
   }
   if (!iErr)
   {      
      printf("Video %dx%d, length: %li sec., %d audio tracks.\n",
             vidx, vidy, giEndTimeMs/1000, iNumVlcAudioTracks);
      iErr = FindVlcWindow(pX11Display, wRoot,     &wVlc, &wMaster);
#ifdef CHVP_DEBUG
      printf("FindVlcWindow: root=0x%lX, vlc=0x%lX, master=0x%lX\n",
             wRoot, wVlc, wMaster);

      // A stop here in Debian to check what libvlc has launched
      //gets(szErr);
#endif // CHVP_DEBUG
   }
   if (!iErr)
   {
      FindMaster(pX11Display, wInput,     &wInputMaster);
      
      if (vidx >= scrx || vidy >= scry)
         SetWindowFullscreen(pX11Display, wVlc, wMaster, wRoot, scrx, scry);
      else
      {
         SetWindowFullscreen(pX11Display, wInput, wInputMaster, wRoot,
                             scrx, scry);

         // Test for VLC's fullscreen feature.  It dosen't work on all
         // X11 window managers (i.e. old ones) for libvlc v3.0.6.
         //
         //libvlc_set_fullscreen(pVlcPlayer, 1);

         PositionWindow(pX11Display, wMaster, 5 /* center */, scrx, scry);
      }
      TaskbarFindAndUnmap(pX11Display, wRoot,     &wTaskbar);

      // Play the media_player
      libvlc_media_player_set_time(pVlcPlayer, 0);
      i = libvlc_media_player_play(pVlcPlayer);
      if (i)
      {
         iErr = ERROR_CHVP_VLC;
         strcat(szErr, "libvlc_media_player_play() failed!");
      }
   }

   //
   // X11 Event Loop
   //

   if (!iErr)
   {
      iX11fd = ConnectionNumber(pX11Display);
      FD_ZERO(&readfds);
      FD_SET(iX11fd, &readfds);
   }
   while (iRunning && !iErr)
   {
      sEventLoopTimeout.tv_usec = 400000; // maximum sleep of 0.4 second
      sEventLoopTimeout.tv_sec = 1;
      select(iX11fd + 1, &readfds, 0, 0, &sEventLoopTimeout);

      while (XPending(pX11Display))
      {
         loopEvent.type = 0;
         XNextEvent(pX11Display,     &loopEvent);
         if (loopEvent.type == KeyPress)
         {
            if (kc1 && loopEvent.xkey.keycode == kc1)
               libvlc_media_player_set_time(pVlcPlayer, Offset(113280));
            else if (kc2 && loopEvent.xkey.keycode == kc2)
               libvlc_media_player_set_time(pVlcPlayer, Offset(423173));
            else if (kc3 && loopEvent.xkey.keycode == kc3)
               libvlc_media_player_set_time(pVlcPlayer, Offset(800551));
            else if (kc4 && loopEvent.xkey.keycode == kc4)
               libvlc_media_player_set_time(pVlcPlayer, Offset(1062187));
            else if (kc5 && loopEvent.xkey.keycode == kc5)
               libvlc_media_player_set_time(pVlcPlayer, Offset(1538789));
            else if (kc6 && loopEvent.xkey.keycode == kc6)
               libvlc_media_player_set_time(pVlcPlayer, Offset(2012137));
            else if (kc7 && loopEvent.xkey.keycode == kc7)
               libvlc_media_player_set_time(pVlcPlayer, Offset(2834084));
            else if (kcReturn && loopEvent.xkey.keycode == kcReturn)
            {
               if (iTimeMs < Offset(423340))
                  libvlc_media_player_set_time(pVlcPlayer, Offset(3610152));
               else if (iTimeMs < Offset(798088))
                  libvlc_media_player_set_time(pVlcPlayer, Offset(3698156));
               else if (iTimeMs < Offset(1055806))
                  libvlc_media_player_set_time(pVlcPlayer, Offset(3783825));
               else if (iTimeMs < Offset(1526735))
                  libvlc_media_player_set_time(pVlcPlayer, Offset(3871538));
               else if (iTimeMs < Offset(2006965))
                  libvlc_media_player_set_time(pVlcPlayer, Offset(3991450));
               else if (iTimeMs < Offset(2755255))
                  libvlc_media_player_set_time(pVlcPlayer, Offset(4122456));
               else if (iTimeMs < Offset(3517309))
               {
                  libvlc_media_player_pause(pVlcPlayer);
                  if (!iHero)
                     iHero--;
               }
               else if (iTimeMs < Offset(3532751))
               {
                  libvlc_media_player_pause(pVlcPlayer);
                  if (!iHero)
                     iHero++;
               }
            }
            else if (kcDown && loopEvent.xkey.keycode == kcDown)
            {
               iTimeMs = libvlc_media_player_get_time(pVlcPlayer);
               if (iTimeMs <= CHVP_1MIN)
                  iTimeMs = 0;
               else
                  iTimeMs -= CHVP_1MIN;
               libvlc_media_player_set_time(pVlcPlayer, iTimeMs);
            }
            else if (kcEnd && loopEvent.xkey.keycode == kcEnd)
            {
               iTimeMs = giEndTimeMs - CHVP_10SEC;
               libvlc_media_player_set_time(pVlcPlayer, iTimeMs);
            }
            else if (kcEsc && loopEvent.xkey.keycode == kcEsc)
            {
               iRunning = 0;
#ifdef CHVP_DEBUG
               printf("Final time: %ld, IsPlaying:%d, State:%d\n",
                      libvlc_media_player_get_time(pVlcPlayer),
                      libvlc_media_player_is_playing(pVlcPlayer),
                      libvlc_media_player_get_state(pVlcPlayer));
#endif // CHVP_DEBUG
            }
            else if (kcHome && loopEvent.xkey.keycode == kcHome)
            {
               iTimeMs = 0;
               libvlc_media_player_set_time(pVlcPlayer, iTimeMs);
            }
            else if (kcKpBegin && loopEvent.xkey.keycode == kcKpBegin)
               PositionWindow(pX11Display, wMaster, 5, scrx, scry);
            else if (kcKpDown && loopEvent.xkey.keycode == kcKpDown)
               PositionWindow(pX11Display, wMaster, 2, scrx, scry);
            else if (kcKpEnd && loopEvent.xkey.keycode == kcKpEnd)
               PositionWindow(pX11Display, wMaster, 1, scrx, scry);
            else if (kcKpHome && loopEvent.xkey.keycode == kcKpHome)
               PositionWindow(pX11Display, wMaster, 7, scrx, scry);
            else if (kcKpLeft && loopEvent.xkey.keycode == kcKpLeft)
               PositionWindow(pX11Display, wMaster, 4, scrx, scry);
            else if (kcKpMinus && loopEvent.xkey.keycode == kcKpMinus)
            {
               iRet = libvlc_audio_get_volume(pVlcPlayer) - 10;
               if (iRet < 0)
                  iRet = 0;
               libvlc_audio_set_volume(pVlcPlayer, iRet);
            }
            else if (kcKpMult && loopEvent.xkey.keycode == kcKpMult
                     && iNumVlcAudioTracks > 1)
            {
               iVlcAudioTrack++;
               if (iVlcAudioTrack == iNumVlcAudioTracks)
                  iVlcAudioTrack = 0;
               libvlc_audio_set_track(pVlcPlayer,
                                      pVlcAudioTrackId[iVlcAudioTrack]);
            }
            else if (kcKpPageDown && loopEvent.xkey.keycode == kcKpPageDown)
               PositionWindow(pX11Display, wMaster, 3, scrx, scry);
            else if (kcKpPageUp && loopEvent.xkey.keycode == kcKpPageUp)
               PositionWindow(pX11Display, wMaster, 9, scrx, scry);
            else if (kcKpPlus && loopEvent.xkey.keycode == kcKpPlus)
            {
               iRet = libvlc_audio_get_volume(pVlcPlayer) + 10;
               if (iRet > 100)
                  iRet = 100;
               libvlc_audio_set_volume(pVlcPlayer, iRet);
            }
            else if (kcKpRight && loopEvent.xkey.keycode == kcKpRight)
               PositionWindow(pX11Display, wMaster, 6, scrx, scry);
            else if (kcKpUp && loopEvent.xkey.keycode == kcKpUp)
               PositionWindow(pX11Display, wMaster, 8, scrx, scry);
            else if (kcLeft && loopEvent.xkey.keycode == kcLeft)
            {
               iTimeMs = libvlc_media_player_get_time(pVlcPlayer);
               if (iTimeMs <= CHVP_10SEC)
                  iTimeMs = 0;
               else
                  iTimeMs -= CHVP_10SEC;
               libvlc_media_player_set_time(pVlcPlayer, iTimeMs);
            }
            else if (kcPgDown && loopEvent.xkey.keycode == kcPgDown)
            {
               iTimeMs = libvlc_media_player_get_time(pVlcPlayer);
               if (iTimeMs <= CHVP_10MIN)
                  iTimeMs = 0;
               else
                  iTimeMs -= CHVP_10MIN;
               libvlc_media_player_set_time(pVlcPlayer, iTimeMs);
            }
            else if (kcPgUp && loopEvent.xkey.keycode == kcPgUp)
            {
               iTimeMs = libvlc_media_player_get_time(pVlcPlayer);
               if (iTimeMs < giEndTimeMs - CHVP_10SEC)
               {
                  if (iTimeMs < giEndTimeMs - CHVP_10MIN)
                     iTimeMs += CHVP_10MIN;
                  else
                     iTimeMs = giEndTimeMs - CHVP_10SEC;
                  libvlc_media_player_set_time(pVlcPlayer, iTimeMs);
               }
            }
            else if (kcRight && loopEvent.xkey.keycode == kcRight)
            {
               iTimeMs = libvlc_media_player_get_time(pVlcPlayer)
                         + CHVP_10SEC;
               if (iTimeMs < giEndTimeMs)
                  libvlc_media_player_set_time(pVlcPlayer, iTimeMs);
            }
            else if (kcSpace && loopEvent.xkey.keycode == kcSpace)
               libvlc_media_player_pause(pVlcPlayer);
            else if (kcUp && loopEvent.xkey.keycode == kcUp)
            {
               iTimeMs = libvlc_media_player_get_time(pVlcPlayer);
               if (iTimeMs < giEndTimeMs - CHVP_10SEC)
               {
                  if (iTimeMs < giEndTimeMs - CHVP_1MIN)
                     iTimeMs += CHVP_1MIN;
                  else
                     iTimeMs = giEndTimeMs - CHVP_10SEC;
                  libvlc_media_player_set_time(pVlcPlayer, iTimeMs);
               }
            }

#ifdef CHVP_DEBUG
            else
               printf("KeySym=0x%lX\n", XKeycodeToKeysym(pX11Display,
                                          loopEvent.xkey.keycode, 0));
#endif // CHVP_DEBUG
         }
         else if (loopEvent.type == ButtonRelease)
         {
            XRaiseWindow(pX11Display, wMaster);
            XSetInputFocus(pX11Display, wInput, RevertToNone, 0);
         }
      }

      if (iRunning)
      {
         if (libvlc_media_player_get_state(pVlcPlayer) >= libvlc_Stopped)
            iRunning = 0;
      }
      if (iRunning)
      {
         iTimeMs = libvlc_media_player_get_time(pVlcPlayer);
         if (iTimeMs < 0)
         {
            iErr = ERROR_CHVP_VLC;
            strcat(szErr, "libvlc_media_player_get_time() failed!");
         }
         if (!iErr)
         {
            if (iTimeMs >= giEndTimeMs)
               iRunning = 0;
         }
      }      
      if (iRunning)
      {
         XGetInputFocus(pX11Display,     &w, &iRet);
         if (w == wVlc || w == wMaster)
         {
            XRaiseWindow(pX11Display, wInputMaster);
            XRaiseWindow(pX11Display, wMaster);
            XSetInputFocus(pX11Display, wInput, RevertToNone, 0);
         }

         if (Crossed(iPrevious, Offset(3606148), iTimeMs))
         {
            if (iHero == 1)
            {
               iHero++;
               libvlc_media_player_set_time(pVlcPlayer, Offset(4257883));
            }
            else
               libvlc_media_player_pause(pVlcPlayer);
         }
         else if (Crossed(iPrevious, Offset(2828036), iTimeMs)
                  || Crossed(iPrevious, Offset(3696989), iTimeMs)
                  || Crossed(iPrevious, Offset(3780113), iTimeMs)
                  || Crossed(iPrevious, Offset(3869995), iTimeMs)
                  || Crossed(iPrevious, Offset(3987237), iTimeMs)
                  || Crossed(iPrevious, Offset(4119453), iTimeMs)
                  || Crossed(iPrevious, Offset(4255589), iTimeMs))
            libvlc_media_player_pause(pVlcPlayer);

         iPrevious = iTimeMs;
      }
   }

   /* Stop playing */
   if (iPlay)
      libvlc_media_player_stop(pVlcPlayer);

   TaskbarRaise(pX11Display, wTaskbar);

   switch (iErr)
   {
      case ERROR_CHVP_USAGE:
         printf("USAGE: chvpinferno1 [offset ms.] <filename>\n");
         break;

      case ERROR_CHVP_X11:
         printf("X11 ERROR: %s\n", szErr);
         break;

      case ERROR_CHVP_VLC:
         printf("VLC ERROR: %s\n", szErr);
         break;
   
      case ERROR_CHVP_FAIL:
         printf("ERROR: LibVLC's Video Window Not Found!\n");
         break;
   
      case ERROR_CHVP_LEN:
         printf("ERROR: Video Length Not 1h12:36 !\n");
         break;

      case ERROR_CHVP_MEM:
         printf("ERROR: Out Of Memory!\n");
         break;
   
      case ERROR_CHVP_VLCRUN:
         printf("ERROR: VLC Already Running!\n");
         break;
   }
 
   if (pVlcPlayer)
   {
#ifdef CHVP_DEBUG
      printf("libvlc_media_player_release()\n");
#endif // CHVP_DEBUG
      libvlc_media_player_release(pVlcPlayer);
   }

   if (pVlcInst)
   {
#ifdef CHVP_DEBUG
      printf("libvlc_release()\n");
#endif // CHVP_DEBUG
      libvlc_release(pVlcInst);
   }

   if (pVlcAudioTrackId)
      free(pVlcAudioTrackId);

   if (wInput)
   {
#ifdef CHVP_DEBUG
      printf("XDestroyWindow(0x%lX)\n", wInput);
#endif // CHVP_DEBUG
      XDestroyWindow(pX11Display, wInput);
   }

   if (pX11Display)
   {
#ifdef CHVP_DEBUG
      printf("XCloseDisplay()\n");
#endif // CHVP_DEBUG
      XCloseDisplay(pX11Display);
   }

   return(0);
}
