/* * VBI recording tool and shared memory monitor * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License Version 2 as * published by the Free Software Foundation. You find a copy of this * license in the file COPYRIGHT in the root directory of this release. * * 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. * * * Description: * * This module is the main module of the small standalone tool * "vbirec". It's purpose is in debugging the EPG interface * of TV applications. The tool takes the place of nxtvepg, * i.e. it sets up communication channels and waits for a TV app * to connect. While a TV application is connected, all TV app * controled values are displayed and constantly updated in the * GUI. * * It also offers the possibility to record all EPG teletext packets * into a file. In case of Windows teletext data is forwarded by * the TV app, in case of UNIX it's read from the VBI device. On * Windows the recorded data can later be played back with vbiplay, * on UNIX it requires patching of ttxdecode.c (Plackback is meant * for debugging by developers only anyways.) * * Author: Thorsten Zoerner * * $Id: vbirec_main.c,v 1.21 2004/12/12 15:39:19 tom Exp tom $ */ #define DEBUG_SWITCH DEBUG_SWITCH_TVSIM //#define DPRINTF_OFF #ifdef WIN32 #include #endif #include #include #include #include #include #include #include #include #include #ifndef WIN32 #include #endif #include #include #include "epgctl/mytypes.h" #include "epgctl/debug.h" #include "epgvbi/btdrv.h" #include "epgvbi/ttxdecode.h" #include "epgvbi/winshm.h" #include "epgvbi/winshmsrv.h" #include "epgvbi/cni_tables.h" #include "epgvbi/hamming.h" #include "epgdb/epgblock.h" #include "epgtcl/combobox.h" #include "tvsim/vbirec_gui.h" #include "tvsim/tvsim_version.h" // prior to 8.4 there's a SEGV when evaluating const scripts (Tcl tries to modify the string) #if (TCL_MAJOR_VERSION > 8) || ((TCL_MAJOR_VERSION == 8) && (TCL_MINOR_VERSION >= 4)) # define TCL_EVAL_CONST(INTERP, SCRIPT) Tcl_EvalEx(INTERP, SCRIPT, -1, TCL_EVAL_GLOBAL) #else # define TCL_EVAL_CONST(INTERP, SCRIPT) Tcl_VarEval(INTERP, (char *) SCRIPT, NULL) #endif #ifndef USE_PRECOMPILED_TCL_LIBS # if !defined(TCL_LIBRARY_PATH) || !defined(TK_LIBRARY_PATH) # error "Must define both TK_ and TCL_LIBRARY_PATH" # endif #else # define TCL_LIBRARY_PATH "." # define TK_LIBRARY_PATH "." # include "epgtcl/tcl_libs.h" # include "epgtcl/tk_libs.h" #endif #ifndef O_BINARY #define O_BINARY 0 // for M$-Windows only #endif char tvsim_rcs_id_str[] = TVSIM_VERSION_RCS_ID; Tcl_Interp *interp; // Interpreter for application #define TCL_COMM_BUF_SIZE 1000 // add one extra byte at the end of the comm buffer for overflow detection char comm[TCL_COMM_BUF_SIZE + 1]; #ifdef WIN32 static Tcl_AsyncHandler asyncThreadHandler = NULL; static bool haveIdleHandler = FALSE; #else static Tcl_AsyncHandler exitAsyncHandler = NULL; #endif static Tcl_TimerToken clockHandler = NULL; static bool should_exit; #ifdef WIN32 volatile EPGACQ_BUF * pVbiBuf = NULL; #endif static int fdTtxFile = -1; typedef struct { uint cni; uint pil; uchar text[PDC_TEXT_LEN + 1]; } CNIPIL; typedef struct { CNIPIL vps; CNIPIL pdc; CNIPIL p8301; } CNI_DEC_STATS; static TTX_DEC_STATS ttxStats; static CNI_DEC_STATS cniStats; static uint epgPageNo; // Default TV card index - identifies which TV card is used by the simulator // (note that in contrary to the other TV card parameters this value is not // taken from the nxtvepg rc/ini file; can be overriden with -card option) #define TVSIM_CARD_IDX 0 // input source index of the TV tuner with btdrv4win.c #define TVSIM_INPUT_IDX 0 #ifndef WIN32 static uint videoCardIndex = TVSIM_CARD_IDX; #endif static bool startIconified = FALSE; #ifndef WIN32 // --------------------------------------------------------------------------- // Dummies for xawtv module // #include "epgdb/epgdbfil.h" #include "epgctl/epgacqctl.h" #include "epgdb/epgdbif.h" #include "epgui/epgmain.h" #include "epgui/pibox.h" #include "epgui/wintvcfg.h" #include "epgui/xawtv.h" void EpgDbLockDatabase( EPGDB_CONTEXT * dbc, uchar enable ) {} bool EpgDbIsLocked( const EPGDB_CONTEXT * dbc ) { return TRUE; } const AI_BLOCK * EpgDbGetAi( const EPGDB_CONTEXT * dbc ) { return NULL; } const PI_BLOCK * EpgDbSearchPiByPil( const EPGDB_CONTEXT * dbc, uchar netwop_no, uint pil ) { return NULL; } const PI_BLOCK * EpgDbSearchFirstPiAfter( const EPGDB_CONTEXT * dbc, time_t min_time, EPGDB_TIME_SEARCH_MODE startOrStop, const FILTER_CONTEXT *fc ) { return NULL; } FILTER_CONTEXT * EpgDbFilterCreateContext( void ) { return NULL; } void EpgDbFilterDestroyContext( FILTER_CONTEXT * fc ) {} void EpgDbFilterEnable( FILTER_CONTEXT *fc, uint mask ) {} void EpgDbFilterInitNetwop( FILTER_CONTEXT *fc ) {} void EpgDbFilterSetNetwop( FILTER_CONTEXT *fc, uchar netwopNo ) {} void AddMainIdleEvent( Tcl_IdleProc *IdleProc, ClientData clientData, bool unique ) { Tcl_CancelIdleCall(IdleProc, clientData); Tcl_DoWhenIdle(IdleProc, clientData); } bool RemoveMainIdleEvent( Tcl_IdleProc * IdleProc, ClientData clientData, bool matchData ) { Tcl_CancelIdleCall(IdleProc, clientData); return FALSE; } EPGDB_CONTEXT * pUiDbContext = NULL; EPGDB_CONTEXT * pAcqDbContext = NULL; void EpgAcqCtl_DescribeAcqState( EPGACQ_DESCR * pAcqState ) {} const EPGDB_ACQ_VPS_PDC * EpgAcqCtl_GetVpsPdc( VPSPDC_REQ_ID clientId ) { return NULL; } bool EpgAcqCtl_CheckDeviceAccess( void ) { return TRUE; } void EpgAcqCtl_ResetVpsPdc( void ) {} void PiBox_GotoPi( const PI_BLOCK * pPiBlock ) {} uint WintvCfg_StationNameToCni( char * pName, uint MapName2Cni(const char * station) ) { return 0; } bool WintvCfg_CheckAirTimes( uint cni ) { return TRUE; } // --------------------------------------------------------------------------- // called by interrupt handlers when the application should exit // static int AsyncHandler_AppTerminate( ClientData clientData, Tcl_Interp *interp, int code ) { // do nothing - the only purpose was to wake up the event handler, // so that we can leave the main loop after this NOP was processed return code; } // --------------------------------------------------------------------------- // graceful exit upon signals // static void signal_handler( int sigval ) { should_exit = TRUE; if (exitAsyncHandler != NULL) { // trigger an event, so that the Tcl/Tk event handler immediately wakes up Tcl_AsyncMark(exitAsyncHandler); } } // ---------------------------------------------------------------------------- // TV application changed the input channel // static int Xawtv_CbStationChange(ClientData ttp, Tcl_Interp *interp, int argc, CONST84 char *argv[]) { const char * const pUsage = "Usage: XawtvConfigShmChannelChange "; int tvCurFreq; int result; if ( (argc != 3) || (Tcl_GetInt(interp, argv[2], &tvCurFreq) != TCL_OK) ) { // parameter count is invalid Tcl_SetResult(interp, (char *)pUsage, TCL_STATIC); result = TCL_ERROR; } else { // channel change -> reset results & state machine pVbiBuf->chanChangeReq += 1; memset(&cniStats, 0, sizeof(cniStats)); eval_check(interp, "InitGuiVars"); epgPageNo = pVbiBuf->epgPageNo = 0x1DF; Tcl_SetVar(interp, "ttx_pgno", "1DF", TCL_GLOBAL_ONLY); Tcl_SetVar(interp, "tvChanName", argv[1], TCL_GLOBAL_ONLY); if (tvCurFreq != 0) { sprintf(comm, "%d = %.2f MHz", tvCurFreq, (double)tvCurFreq / 16); Tcl_SetVar(interp, "tvCurFreq", comm, TCL_GLOBAL_ONLY); } result = TCL_OK; } return result; } #else // WIN32 // --------------------------------------------------------------------------- // Callback to deal with Windows shutdown // - called when WM_QUERYENDSESSION or WM_ENDSESSION message is received // this currenlty requires a patch in the tk library! // - the driver must be stopped before the applications exits // or the system will crash ("blue-screen") // static void WinApiDestructionHandler( ClientData clientData) { debug0("received destroy event"); WintvSharedMem_Exit(); // exit the application ExitProcess(0); } #ifdef __MINGW32__ static LONG WINAPI WinApiExceptionHandler(struct _EXCEPTION_POINTERS *exc_info) { //debug1("FATAL exception caught: %d", GetExceptionCode()); debug0("FATAL exception caught"); WintvSharedMem_Exit(); ExitProcess(-1); // dummy return return EXCEPTION_EXECUTE_HANDLER; } #endif // Declare the new callback registration function // this function is patched into the tk83.dll and hence not listed in the standard header files extern TCL_STORAGE_CLASS void Tk_RegisterMainDestructionHandler( Tcl_CloseProc * handler ); /* *------------------------------------------------------------------------- * * setargv -- [This is taken from the wish main in the Tcl/Tk package] * * Parse the Windows command line string into argc/argv. Done here * because we don't trust the builtin argument parser in crt0. * Windows applications are responsible for breaking their command * line into arguments. * * 2N backslashes + quote -> N backslashes + begin quoted string * 2N + 1 backslashes + quote -> literal * N backslashes + non-quote -> literal * quote + quote in a quoted string -> single quote * quote + quote not in quoted string -> empty string * quote -> begin quoted string * * Results: * Fills argcPtr with the number of arguments and argvPtr with the * array of arguments. * * Parameters: * int *argcPtr; Filled with number of argument strings * char ***argvPtr; Filled with argument strings (malloc'd) * *-------------------------------------------------------------------------- */ static void SetArgv( int * argcPtr, char *** argvPtr ) { char *cmdLine, *p, *arg, *argSpace; char **argv; int argc, size, inquote, copy, slashes; cmdLine = GetCommandLine(); // Precompute an overly pessimistic guess at the number of arguments // in the command line by counting non-space spans. size = 2; for (p = cmdLine; *p != '\0'; p++) { if (isspace(*p)) { size++; while (isspace(*p)) { p++; } if (*p == '\0') { break; } } } argSpace = (char *) xmalloc((unsigned) (size * sizeof(char *) + strlen(cmdLine) + 1)); argv = (char **) argSpace; argSpace += size * sizeof(char *); size--; p = cmdLine; for (argc = 0; argc < size; argc++) { argv[argc] = arg = argSpace; while (isspace(*p)) { p++; } if (*p == '\0') { break; } inquote = 0; slashes = 0; while (1) { copy = 1; while (*p == '\\') { slashes++; p++; } if (*p == '"') { if ((slashes & 1) == 0) { copy = 0; if ((inquote) && (p[1] == '"')) { p++; copy = 1; } else { inquote = !inquote; } } slashes >>= 1; } while (slashes) { *arg = '\\'; arg++; slashes--; } if ((*p == '\0') || (!inquote && isspace(*p))) { break; } if (copy != 0) { *arg = *p; arg++; } p++; } *arg = '\0'; argSpace = arg + 1; } argv[argc] = NULL; *argcPtr = argc; *argvPtr = argv; } // ---------------------------------------------------------------------------- // Handle EPG events // - executed inside the main thread, but triggered by the msg receptor thread // - the driver invokes callbacks into the tvsim module to trigger GUI action, // e.g. to display incoming program title info // static void VbiRec_IdleHandler( ClientData clientData ) { haveIdleHandler = FALSE; WintvSharedMem_HandleTvCmd(); } // ---------------------------------------------------------------------------- // 2nd stage of EPG event handling: delay handling until GUI is idle // static int VbiRec_AsyncThreadHandler( ClientData clientData, Tcl_Interp *interp, int code ) { // if an idle handler is already installed, nothing needs to be done // because the handler processes all incoming messages (not only one) if (haveIdleHandler == FALSE) { haveIdleHandler = TRUE; Tcl_DoWhenIdle(VbiRec_IdleHandler, NULL); } return code; } // --------------------------------------------------------------------------- // Called by TV event receptor thread // - trigger an event, so that the Tcl/Tk event handler immediately wakes up; // no message processing is done here because we're running in a separate // thread here! We'd have to do some serious mutual exclusion efforts. // - note that there are 2 stages until the message is actually handled: // 1. install an async. event at the top of the event queue; it'll get executed // A.S.A.P., i.e. next time the Tcl event loop is entered // 2. in the async handler an idle handler is installed, which delays the // actual handling until the current actions are completed // This is neccessary to avoid that we draw something in the EPG message // handler, which is then overwritten by an action that was already scheduled // earlier (i.e. EPG info is cleared again by a timer event in the queue) // static void VbiRec_CbTvEvent( void ) { if (asyncThreadHandler != NULL) { // install event at top of Tcl event loop Tcl_AsyncMark(asyncThreadHandler); } } // ---------------------------------------------------------------------------- // TV application changed the input channel // static void VbiRec_CbStationSelected( void ) { char station[50]; uint chanQueryIdx; TVAPP_COMM * pTvShm; // channel change -> reset results & state machine pVbiBuf->chanChangeReq += 1; memset(&cniStats, 0, sizeof(cniStats)); eval_check(interp, "InitGuiVars"); // query which station was selected if ( WintvSharedMem_QueryChanName(station, sizeof(station), &chanQueryIdx) ) { Tcl_SetVar(interp, "tvChanName", station, TCL_GLOBAL_ONLY); } epgPageNo = pVbiBuf->epgPageNo = 0x1DF; Tcl_SetVar(interp, "ttx_pgno", "1DF", TCL_GLOBAL_ONLY); // really dirty hack to get the pointer to shared memory from the VBI buffer address pTvShm = (TVAPP_COMM *)((uchar *)pVbiBuf - (uchar *)&((TVAPP_COMM *)0)->vbiBuf); if (pTvShm->tvChanCni != 0) { sprintf(comm, "0x%04x", pTvShm->tvChanCni); Tcl_SetVar(interp, "tvChanCni", comm, TCL_GLOBAL_ONLY); } else Tcl_SetVar(interp, "tvChanCni", "---", TCL_GLOBAL_ONLY); if (pTvShm->tvCurInput != EPG_REQ_INPUT_NONE) { sprintf(comm, "%d", pTvShm->tvCurInput); Tcl_SetVar(interp, "tvCurInput", comm, TCL_GLOBAL_ONLY); } if (pTvShm->tvCurFreq != EPG_REQ_FREQ_NONE) { sprintf(comm, "%d = %.2f MHz", pTvShm->tvCurFreq, (double)pTvShm->tvCurFreq / 16); Tcl_SetVar(interp, "tvCurFreq", comm, TCL_GLOBAL_ONLY); } if (pTvShm->tvGrantTuner) { Tcl_SetVar(interp, "tvGrantTuner", "yes", TCL_GLOBAL_ONLY); } // always reply will NULL info WintvSharedMem_SetEpgInfo(0, 0, "", 0, NULL, chanQueryIdx); } // ---------------------------------------------------------------------------- // TV application changed tuner grant setting // static void VbiRec_CbTunerGrant( bool enable ) { Tcl_SetVar(interp, "tvGrantTuner", (enable ? "yes" : "no"), TCL_GLOBAL_ONLY); } // ---------------------------------------------------------------------------- // TV application attached or detached // static void VbiRec_CbAttachTv( bool enable, bool acqEnabled, bool slaveStateChange ) { char tvAppName[100]; if (WintvSharedMem_IsConnected(tvAppName, sizeof(tvAppName), NULL)) { sprintf(comm, "ConnectEpg 1 {%s}\n", tvAppName); } else sprintf(comm, "ConnectEpg 0 {}\n"); eval_check(interp, comm); } #endif // WIN32 // --------------------------------------------------------------------------- // Update a CNI/PIL/text and network variable // static void UpdateCni( volatile CNI_ACQ_STATE * pNew, CNIPIL * pOld, char * pVar ) { if ( (pNew->haveCni) && ( (pOld->cni != pNew->haveCni) || (pOld->pil != (pNew->havePil ? pNew->outPil : INVALID_VPS_PIL)) )) { // CNI and/or PIL has changed -> copy the new values pOld->cni = pNew->outCni; pOld->pil = (pNew->havePil ? pNew->outPil : INVALID_VPS_PIL); pOld->text[0] = 0; if (pOld->cni != 0) { if ( VPS_PIL_IS_VALID(pOld->pil) ) { // both CNI and PIL are available sprintf(comm, "%04X, %02d.%02d. %02d:%02d", pOld->cni, (pOld->pil >> 15) & 0x1F, (pOld->pil >> 11) & 0x0F, (pOld->pil >> 6) & 0x1F, (pOld->pil) & 0x3F); } else if (pOld->pil == VPS_PIL_CODE_EMPTY) sprintf(comm, "%04X (PIL: \"fill material\")", pOld->cni); else if (pOld->pil == VPS_PIL_CODE_PAUSE) sprintf(comm, "%04X (PIL: \"pause\")", pOld->cni); else sprintf(comm, "%04X", pOld->cni); } else strcpy(comm, "---"); Tcl_SetVar(interp, pVar, comm, TCL_GLOBAL_ONLY); } if ( (pNew->haveText) && (pOld->cni == 0) && (strncmp(pOld->text, (char *) pNew->outText, PDC_TEXT_LEN) != 0) ) { // no CNI, but "status display" text is available strncpy(pOld->text, (char *) pNew->outText, sizeof(pOld->text)); pNew->haveText = FALSE; // display the text instead of CNI value, but place it inside "" sprintf(comm, "\"%s\"", pOld->text); Tcl_SetVar(interp, pVar, comm, TCL_GLOBAL_ONLY); } } // --------------------------------------------------------------------------- // Update network name from the "best" source // static void UpdateCniName( volatile CNI_ACQ_STATE * pNew ) { const char * pName; const char * pCountry; CNI_TYPE type; if (pVbiBuf != NULL) { // search for the best available source for (type=0; type < CNI_TYPE_COUNT; type++) if (pVbiBuf->cnis[type].haveCni) break; comm[0] = 0; if (type < CNI_TYPE_COUNT) { // Return descriptive text for a given 16-bit network code pName = CniGetDescription(pVbiBuf->cnis[type].outCni, &pCountry); if (pName != NULL) { if ((pCountry != NULL) && (strstr(pName, pCountry) == NULL)) { sprintf(comm, "%s (%s)", pName, pCountry); } else strcpy(comm, pName); } } Tcl_SetVar(interp, "cni_name", comm, TCL_GLOBAL_ONLY); // Reset all result values (but not the entire state machine, // i.e. repetition counters stay at 2 so that any newly received // CNI will immediately make an result available again) for (type=0; type < CNI_TYPE_COUNT; type++) { pVbiBuf->cnis[type].haveCni = FALSE; } } } // --------------------------------------------------------------------------- // Update teletext header // static void UpdateTtxHeader( const uchar * pHeaderData ) { uchar headerText[41]; schar dec; uint textIdx; uint dataIdx; textIdx=0; for (dataIdx=0; dataIdx < 32; dataIdx++) { dec = (schar)parityTab[pHeaderData[dataIdx]]; if (dec < 0) headerText[textIdx++] = '_'; else if (dec >= ' ') headerText[textIdx++] = dec; else headerText[textIdx++] = ' '; } headerText[textIdx] = 0; Tcl_SetVar(interp, "ttx_head", headerText, TCL_GLOBAL_ONLY); } // --------------------------------------------------------------------------- // Second timer: read EPG packets and store them in the file // static void SecTimerEvent( ClientData clientData ) { if (pVbiBuf->chanChangeReq == pVbiBuf->chanChangeCnf) { if ( (pVbiBuf->mipPageNo != EPG_ILLEGAL_PAGENO) && (pVbiBuf->mipPageNo != epgPageNo) ) { pVbiBuf->chanChangeReq += 1; pVbiBuf->epgPageNo = pVbiBuf->mipPageNo; epgPageNo = pVbiBuf->mipPageNo; sprintf(comm, "%03X", epgPageNo); Tcl_SetVar(interp, "ttx_pgno", comm, TCL_GLOBAL_ONLY); } while (pVbiBuf->reader_idx != pVbiBuf->writer_idx) { if (fdTtxFile != -1) { write(fdTtxFile, (char *) &pVbiBuf->line[pVbiBuf->reader_idx], sizeof(VBI_LINE)); } pVbiBuf->reader_idx = (pVbiBuf->reader_idx + 1) % EPGACQ_BUF_COUNT; } if (pVbiBuf->lastHeader.pageno != 0xffff) UpdateTtxHeader((char *)pVbiBuf->lastHeader.data + 13 - 5); if (ttxStats.ttxPkgCount != pVbiBuf->ttxStats.ttxPkgCount) { sprintf(comm, "%d", pVbiBuf->ttxStats.ttxPkgCount); Tcl_SetVar(interp, "ttx_pkg", comm, TCL_GLOBAL_ONLY); ttxStats.ttxPkgCount = pVbiBuf->ttxStats.ttxPkgCount; } if (ttxStats.ttxPkgRate != pVbiBuf->ttxStats.ttxPkgRate) { sprintf(comm, "%.1f (%.0f baud)", (double)pVbiBuf->ttxStats.ttxPkgRate / (1 << TTX_PKG_RATE_FIXP), (double)pVbiBuf->ttxStats.ttxPkgRate / (1 << TTX_PKG_RATE_FIXP) * 42 * 8 * 25); Tcl_SetVar(interp, "ttx_rate", comm, TCL_GLOBAL_ONLY); ttxStats.ttxPkgRate = pVbiBuf->ttxStats.ttxPkgRate; } if (ttxStats.vpsLineCount != pVbiBuf->ttxStats.vpsLineCount) { sprintf(comm, "%d", pVbiBuf->ttxStats.vpsLineCount); Tcl_SetVar(interp, "vps_cnt", comm, TCL_GLOBAL_ONLY); ttxStats.vpsLineCount = pVbiBuf->ttxStats.vpsLineCount; } if (ttxStats.epgPkgCount != pVbiBuf->ttxStats.epgPkgCount) { sprintf(comm, "%d", pVbiBuf->ttxStats.epgPkgCount); Tcl_SetVar(interp, "epg_pkg", comm, TCL_GLOBAL_ONLY); ttxStats.epgPkgCount = pVbiBuf->ttxStats.epgPkgCount; } if (ttxStats.epgPagCount != pVbiBuf->ttxStats.epgPagCount) { sprintf(comm, "%d", pVbiBuf->ttxStats.epgPagCount); Tcl_SetVar(interp, "epg_pag", comm, TCL_GLOBAL_ONLY); ttxStats.epgPagCount = pVbiBuf->ttxStats.epgPagCount; } UpdateCni(pVbiBuf->cnis + CNI_TYPE_VPS, &cniStats.vps, "cni_vps"); UpdateCni(pVbiBuf->cnis + CNI_TYPE_PDC, &cniStats.pdc, "cni_pdc"); UpdateCni(pVbiBuf->cnis + CNI_TYPE_NI, &cniStats.p8301, "cni_p8301"); UpdateCniName(pVbiBuf->cnis); } clockHandler = Tcl_CreateTimerHandler(500, SecTimerEvent, NULL); } // --------------------------------------------------------------------------- // Toggle teletext dump on/off // static char * TclCb_EnableDump( ClientData clientData, Tcl_Interp * interp, CONST84 char * name1, CONST84 char * name2, int flags ) { const char * pFileName; CONST84 char * pTmpStr; int enable; pFileName = Tcl_GetVar(interp, "dumpttx_filename", TCL_GLOBAL_ONLY); pTmpStr = Tcl_GetVar(interp, "dumpttx_enable", TCL_GLOBAL_ONLY); if ((pFileName != NULL) && (*pFileName != 0) && (pTmpStr != NULL)) { if (Tcl_GetInt(interp, pTmpStr, &enable) == TCL_OK) { if (enable && (fdTtxFile == -1)) { // dump has been switched on fdTtxFile = open(pFileName, O_WRONLY|O_CREAT|O_APPEND|O_BINARY, 0666); if (fdTtxFile == -1) { // failed to create the file -> give error message and disable dump Tcl_SetVar(interp, "dumpttx_enable", "0", TCL_GLOBAL_ONLY); sprintf(comm, "tk_messageBox -type ok -icon error " "-message {Failed to open '%s' for writing: %s}", pFileName, strerror(errno)); eval_check(interp, comm); } } else if ((enable == FALSE) && (fdTtxFile != -1)) { // dump has been switched off close(fdTtxFile); fdTtxFile = -1; } } } else { Tcl_SetVar(interp, "dumpttx_enable", "0", TCL_GLOBAL_ONLY); } return NULL; } // ---------------------------------------------------------------------------- // Callback to send EPG OSD info to TV app // static int TclCb_SendEpgOsd( ClientData ttp, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[] ) { const char * const pUsage = "Usage: C_SendEpgOsd <start-time> <stop-time>"; int start_time, stop_time; int result; if ( (objc != 1+3) || (Tcl_GetIntFromObj(interp, objv[2], &start_time) != TCL_OK) || (Tcl_GetIntFromObj(interp, objv[3], &stop_time) != TCL_OK) ) { // parameter count is invalid Tcl_SetResult(interp, (char *)pUsage, TCL_STATIC); result = TCL_ERROR; } else { #ifdef WIN32 uchar no_themes, theme_arr[8]; uint chanQueryIdx; char station[50]; WintvSharedMem_QueryChanName(station, sizeof(station), &chanQueryIdx); no_themes = 0; WintvSharedMem_SetEpgInfo(start_time, stop_time, Tcl_GetString(objv[1]), no_themes, theme_arr, chanQueryIdx); #else char start_str[10], stop_str[10]; time_t ltime; uint cmdLen; ltime = start_time; strftime(start_str, 10, "%H:%M", localtime(<ime)); ltime = stop_time; strftime(stop_str, 10, "%H:%M", localtime(<ime)); cmdLen = sprintf(comm, "message%c%s-%s %s%c", 0, start_str, stop_str, Tcl_GetString(objv[1]), 0); Xawtv_SendCmdArgv(interp, comm, cmdLen); #endif result = TCL_OK; } return result; } #ifdef WIN32 // ---------------------------------------------------------------------------- // Callback to send a command to TV app // static int TclCb_SendTvCmd(ClientData ttp, Tcl_Interp *interp, int argc, CONST84 char *argv[]) { const char * const pUsage = "Usage: C_Tvapp_SendCmd <command> [<args> [<...>]]"; Tcl_DString *pass_dstr, *tmp_dstr; char * pass; int idx, len; int result; if (argc < 2) { // parameter count is invalid Tcl_SetResult(interp, (char *)pUsage, TCL_STATIC); result = TCL_ERROR; } else { // sum up the total length of all parameters, including terminating 0-Bytes pass_dstr = xmalloc(sizeof(Tcl_DString) * argc); // allocate one too many //dprintf0("SendCmd: "); len = 0; for (idx = 1; idx < argc; idx++) { tmp_dstr = pass_dstr + idx - 1; // convert Tcl internal Unicode to Latin-1 Tcl_UtfToExternalDString(NULL, argv[idx], -1, tmp_dstr); //dprintf1("%s ", Tcl_DStringValue(tmp_dstr)); len += Tcl_DStringLength(tmp_dstr) + 1; } //dprintf0("\n"); // concatenate the parameters into one char-array, separated by 0-Bytes pass = xmalloc(len); len = 0; pass[0] = 0; for (idx = 1; idx < argc; idx++) { tmp_dstr = pass_dstr + idx - 1; strcpy(pass + len, Tcl_DStringValue(tmp_dstr)); len += strlen(pass + len) + 1; Tcl_DStringFree(tmp_dstr); } xfree(pass_dstr); // send the string to the TV app WintvSharedMem_SetEpgCommand(argc - 1, pass, len); xfree(pass); result = TCL_OK; } return result; } #endif // WIN32 // --------------------------------------------------------------------------- // Dummy functions for winshmsrv.c // #ifdef WIN32 bool BtDriver_Init( void ) { return TRUE; } void BtDriver_Exit( void ) {} bool BtDriver_StartAcq( void ) { return TRUE; } void BtDriver_StopAcq( void ) {} bool BtDriver_Restart( void ) { return TRUE; } bool BtDriver_GetState( bool * pEnabled, bool * pHasDriver, uint * pCardIdx ) { if (pEnabled != NULL) *pEnabled = FALSE; return TRUE; } bool TtxDecode_GetCniAndPil( uint * pCni, uint *pPil, volatile EPGACQ_BUF *pThisVbiBuf ) { return FALSE; } void TtxDecode_NotifyChannelChange( volatile EPGACQ_BUF * pThisVbiBuf ) {} // ---------------------------------------------------------------------------- // Struct with callback functions for shared memory server module // static const WINSHMSRV_CB vbiRecCb = { VbiRec_CbTvEvent, VbiRec_CbStationSelected, VbiRec_CbTunerGrant, VbiRec_CbAttachTv }; #endif // --------------------------------------------------------------------------- // Initialize the Tcl/Tk interpreter and load our scripts // static int ui_init( int argc, char **argv ) { char * args; // set up the default locale to be standard "C" locale so parsing is performed correctly setlocale(LC_ALL, "C"); if (argc >= 1) { Tcl_FindExecutable(argv[0]); } #if DEBUG_SWITCH == ON // set last byte of command buffer to zero to detect overflow comm[sizeof(comm) - 1] = 0; #endif interp = Tcl_CreateInterp(); if (argc > 1) { args = Tcl_Merge(argc - 1, (CONST84 char **) argv + 1); Tcl_SetVar(interp, "argv", args, TCL_GLOBAL_ONLY); sprintf(comm, "%d", argc - 1); Tcl_SetVar(interp, "argc", comm, TCL_GLOBAL_ONLY); } Tcl_SetVar(interp, "argv0", argv[0], TCL_GLOBAL_ONLY); Tcl_SetVar(interp, "tcl_library", TCL_LIBRARY_PATH, TCL_GLOBAL_ONLY); Tcl_SetVar(interp, "tk_library", TK_LIBRARY_PATH, TCL_GLOBAL_ONLY); Tcl_SetVar(interp, "tcl_interactive", "0", TCL_GLOBAL_ONLY); Tcl_SetVar(interp, "TVSIM_VERSION", TVSIM_VERSION_STR, TCL_GLOBAL_ONLY); #ifndef WIN32 Tcl_SetVar(interp, "x11_appdef_path", X11_APP_DEFAULTS, TCL_GLOBAL_ONLY); Tcl_SetVar2Ex(interp, "is_unix", NULL, Tcl_NewIntObj(1), TCL_GLOBAL_ONLY); #else Tcl_SetVar2Ex(interp, "is_unix", NULL, Tcl_NewIntObj(0), TCL_GLOBAL_ONLY); #endif Tcl_Init(interp); if (Tk_Init(interp) != TCL_OK) { #ifndef USE_PRECOMPILED_TCL_LIBS fprintf(stderr, "Failed to initialise the Tk library at '%s' - exiting.\nTk error message: %s\n", TK_LIBRARY_PATH, Tcl_GetStringResult(interp)); exit(1); #endif } #ifdef USE_PRECOMPILED_TCL_LIBS if (TCL_EVAL_CONST(interp, tcl_libs_tcl_static) != TCL_OK) { debug1("tcl_libs_tcl_static error: %s\n", Tcl_GetStringResult(interp)); debugTclErr(interp, "tcl_libs_tcl_static"); } if (TCL_EVAL_CONST(interp, tk_libs_tcl_static) != TCL_OK) { debug1("tk_libs_tcl_static error: %s\n", Tcl_GetStringResult(interp)); debugTclErr(interp, "tk_libs_tcl_static"); } #endif if (TCL_EVAL_CONST(interp, combobox_tcl_dynamic) != TCL_OK) { debug1("combobox_tcl_dynamic error: %s\n", Tcl_GetStringResult(interp)); debugTclErr(interp, "combobox_tcl_dynamic"); } #ifdef DISABLE_TCL_BGERR // switch off Tcl/Tk background error reports for release version sprintf(comm, "proc bgerror foo {}\n"); eval_check(interp, comm); #endif #ifdef WIN32 // create an asynchronous event source that allows to receive triggers from the EPG message receptor thread asyncThreadHandler = Tcl_AsyncCreate(VbiRec_AsyncThreadHandler, NULL); Tcl_CreateCommand(interp, "C_Tvapp_SendCmd", TclCb_SendTvCmd, (ClientData) NULL, NULL); #else exitAsyncHandler = Tcl_AsyncCreate(AsyncHandler_AppTerminate, NULL); Tcl_CreateCommand(interp, "XawtvConfigShmChannelChange", Xawtv_CbStationChange, (ClientData) NULL, NULL); #endif Tcl_CreateObjCommand(interp, "C_SendEpgOsd", TclCb_SendEpgOsd, (ClientData) NULL, NULL); if (TCL_EVAL_CONST(interp, vbirec_gui_tcl_static) != TCL_OK) { debug1("vbirec_gui_tcl_static error: %s\n", Tcl_GetStringResult(interp)); debugTclErr(interp, "vbirec_gui_tcl_static"); } Tcl_TraceVar(interp, "dumpttx_enable", TCL_TRACE_WRITES|TCL_GLOBAL_ONLY, TclCb_EnableDump, NULL); Tcl_ResetResult(interp); return (TRUE); } // --------------------------------------------------------------------------- // Print Usage and exit // static void Usage( const char *argv0, const char *argvn, const char * reason ) { #ifndef WIN32 fprintf(stderr, "%s: %s: %s\n" #else sprintf(comm, "%s: %s: %s\n" #endif "Usage: %s [options]\n" " -help \t\t: this message\n" " -geometry <geometry>\t: window position\n" " -iconic \t\t: iconify window\n" #ifndef WIN32 " -card <digit> \t: index of TV card for acq (starting at 0)\n" #endif , argv0, reason, argvn, argv0); #if 0 /*balance brackets for syntax highlighting*/ ) #endif #ifdef WIN32 MessageBox(NULL, comm, "VbiRec usage", MB_ICONSTOP | MB_OK | MB_TASKMODAL | MB_SETFOREGROUND); #endif exit(1); } // --------------------------------------------------------------------------- // Parse command line options // static void ParseArgv( int argc, char * argv[] ) { int argIdx = 1; while (argIdx < argc) { if (argv[argIdx][0] == '-') { if (!strcmp(argv[argIdx], "-help")) { char versbuf[50]; sprintf(versbuf, "(version %s)", TVSIM_VERSION_STR); Usage(argv[0], versbuf, "the following command line options are available"); } else if (!strcmp(argv[argIdx], "-card")) { #ifndef WIN32 if (argIdx + 1 < argc) { // read index of TV card device char *pe; ulong cardIdx = strtol(argv[argIdx + 1], &pe, 0); if ((pe != (argv[argIdx + 1] + strlen(argv[argIdx + 1]))) || (cardIdx > 9)) Usage(argv[0], argv[argIdx+1], "invalid index (range 0-9)"); videoCardIndex = (uint) cardIdx; argIdx += 2; } else Usage(argv[0], argv[argIdx], "missing card index after"); #else Usage(argv[0], argv[argIdx], "only supported on UNIX systems"); #endif } else if ( !strcmp(argv[argIdx], "-iconic") ) { // start with iconified main window startIconified = TRUE; argIdx += 1; } else if ( !strcmp(argv[argIdx], "-geometry") #ifndef WIN32 || !strcmp(argv[argIdx], "-display") || !strcmp(argv[argIdx], "-name") #endif ) { // ignore arguments that are handled by Tk if (argIdx + 1 >= argc) Usage(argv[0], argv[argIdx], "missing argument after"); argIdx += 2; } else Usage(argv[0], argv[argIdx], "unknown option"); } else Usage(argv[0], argv[argIdx], "unexpected argument"); } } // --------------------------------------------------------------------------- // entry point // #ifdef WIN32 int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) #else int main( int argc, char *argv[] ) #endif { #ifdef WIN32 int argc; char ** argv; // initialize Tcl/Tk interpreter and compile all scripts SetArgv(&argc, &argv); #endif ParseArgv(argc, argv); // mark Tcl/Tk interpreter as uninitialized interp = NULL; memset(&ttxStats, 0, sizeof(ttxStats)); memset(&cniStats, 0, sizeof(cniStats)); should_exit = FALSE; ui_init(argc, argv); #ifdef WIN32 if (WintvSharedMem_Init()) #endif { #ifdef WIN32 // set up callback to catch shutdown messages (requires tk83.dll patch!) Tk_RegisterMainDestructionHandler(WinApiDestructionHandler); #ifndef __MINGW32__ __try { #else SetUnhandledExceptionFilter(WinApiExceptionHandler); #endif WintvSharedMem_SetCallbacks(&vbiRecCb); if (WintvSharedMem_StartStop(TRUE, NULL)) #else // not WIN32 signal(SIGINT, signal_handler); signal(SIGTERM, signal_handler); signal(SIGHUP, signal_handler); BtDriver_Init(); eval_check(interp, "set xawtvcf {tunetv 1 follow 0 dopop 0 poptype 0 duration 7}"); eval_check(interp, "proc CreateTuneTvButton {} {}\n"); eval_check(interp, "proc RemoveTuneTvButton {} {}\n"); eval_check(interp, "proc XawtvConfigShmAttach {ena} {ConnectEpg $ena Xawtv}\n"); Xawtv_Init(NULL); // pass bt8x8 driver parameters to the driver BtDriver_Configure(videoCardIndex, 0 /*prio*/, 0, 0, 0, 0, 0); if ( BtDriver_StartAcq() ) #endif { #ifdef WIN32 pVbiBuf = WintvSharedMem_GetVbiBuf(); #endif if (pVbiBuf != NULL) { // skip first VBI frame, reset ttx decoder, then set reader idx to writer idx pVbiBuf->chanChangeReq = pVbiBuf->chanChangeCnf = 0; pVbiBuf->reader_idx = pVbiBuf->writer_idx; // pass the configuration variables to the ttx process via IPC epgPageNo = pVbiBuf->epgPageNo = 0x1DF; pVbiBuf->isEpgScan = FALSE; // enable acquisition in the slave process/thread pVbiBuf->chanChangeReq = pVbiBuf->chanChangeCnf + 2; pVbiBuf->isEnabled = TRUE; #ifdef WIN32 VbiRec_CbStationSelected(); #endif // trigger second event for the first time to install the timer SecTimerEvent(NULL); // set window title & disable resizing eval_check(interp, "wm title . {VBI recorder " TVSIM_VERSION_STR "}\nwm resizable . 0 0\n"); if (startIconified) eval_check(interp, "wm iconify ."); // wait until window is open and everything displayed while ( (Tk_GetNumMainWindows() > 0) && (should_exit == FALSE) && Tcl_DoOneEvent(TCL_ALL_EVENTS | TCL_DONT_WAIT) ) ; // process GUI events & callbacks until the main window is closed while ((Tk_GetNumMainWindows() > 0) && (should_exit == FALSE)) { Tcl_DoOneEvent(TCL_ALL_EVENTS); } if (clockHandler != NULL) Tcl_DeleteTimerHandler(clockHandler); Tcl_UntraceVar(interp, "dumpttx_enable", TCL_TRACE_WRITES|TCL_GLOBAL_ONLY, TclCb_EnableDump, NULL); if (fdTtxFile != -1) close(fdTtxFile); #ifndef WIN32 BtDriver_StopAcq(); #endif } } else #ifdef WIN32 { // Win32: failed to setup shared memory const char * pShmErrMsg; char * pErrBuf; pShmErrMsg = WinSharedMem_GetErrorMsg(); if (pShmErrMsg != NULL) { pErrBuf = xmalloc(strlen(pShmErrMsg) + 100); sprintf(pErrBuf, "tk_messageBox -type ok -icon error -message {%s}", pShmErrMsg); eval_check(interp, pErrBuf); xfree((void *) pErrBuf); xfree((void *) pShmErrMsg); } } #else // WIN32 { // failed to start VBI acquisition -> display error message const char * pErrStr; strcpy(comm, "tk_messageBox -type ok -icon error -parent . -message {Failed to start acquisition: "); pErrStr = BtDriver_GetLastError(); if (pErrStr != NULL) strcat(comm, pErrStr); else strcat(comm, "(Internal error, please report. Try to restart the application.)\n"); strcat(comm, "}\n"); eval_check(interp, comm); } #endif #if defined(WIN32) && !defined(__MINGW32__) } __except (EXCEPTION_EXECUTE_HANDLER) { // caught a fatal exception -> free IPC resources debug1("FATAL exception caught: %d", GetExceptionCode()); WintvSharedMem_Exit(); ExitProcess(-1); } #endif #ifdef WIN32 WintvSharedMem_Exit(); #else BtDriver_Exit(); Xawtv_Destroy(); #endif } exit(0); return (0); }