www.pudn.com > xvoice-0.8.1.rar > Voice.cc


/**
 * Voice.cc
 *
 * Description: Interface to IBM's ViaVoice SMAPI lib
 *
 * Copyright (c) 1999, David Z. Creemer, Tom Doris.
 * See the LICENSE file. All rights not granted therein are reserved.
 *
 * @author David Z. Creemer
 * @author Tom Doris
 * @version 1.0
 *
 * 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.
 *
 */

// NOTE: Most of this file is taken from IBM sample code,
// which has the following copyright notice, and which supercedes
// the above copyright:

/*================================================================*/
/* COPYRIGHT:                                                     */
/* ----------                                                     */
/* Copyright (C) International Business Machines Corp., 1999.     */
/*                                                                */
/* DISCLAIMER OF WARRANTIES:                                      */
/* -------------------------                                      */
/* The following [enclosed] code is sample code created by IBM    */
/* Corporation.  This sample code is not part of any standard IBM */
/* product and is provided to you solely for the purpose of       */
/* assisting you in the development of your applications.  The    */
/* code is provided "AS IS", without warranty of any kind.  IBM   */
/* shall not be liable for any damages arising out of your use of */
/* the sample code, even if they have been advised of the         */
/* possibility of such damages.                                   */
/*================================================================*/

#include 
#include 
#include 
#include 
#include 
#include 

#include "Error.h"
#include "Target.h"
#include "MainWin.h"
#include "xvoice.h"
#include "config.h"
#include "EventStream.h"
#include "ParseEventStream.h"
#include 
#include 
#include 

/* defines */
#define CheckSmRC() do { \
  int rc; \
  SmGetRc(reply, &rc); \
  if (rc != SM_RC_OK) { \
      fprintf(stderr, "%s: rc = %d\n", __FUNCTION__, rc); \
      gMainWin->errorMsg(E_FATAL, __FUNCTION__, "Voice engine error, rc = %d\n", rc ); \
      return (SM_RC_OK);                             \
  }                                                    \
} while (0)
        
/*
 * For SMAPI return codes indicating bad params, memory errors, etc.
 * These are usually fatal.
 */
#define SMAPI_ERROR "Error calling SMAPI function"
        
/* local variables */
static  applicationList *gAppList = NULL;

static char vTextVocab[] = "text";      // Dictation vocabulary

static char *vIdleVocabName = "IdleVocab";
static defVocab vIdleVocab[] = { 
    { CCOMMAND, "command mode" },
    { CDICTATE, "dictate mode" },
    { CMICOFF, "microphone off" },
    { CREBUILD, "build grammar files" },
    { -1, NULL }
};

static char *vCommandVocabName = "CommandVocab";
static defVocab vCommandVocab[] = { 
    { CSTOPCOMMAND, "stop command" },
    { CIDLE, "idle mode" },
    { -1, NULL }
};

static char *vDictateVocabName = "DictateVocab";
static defVocab vDictateVocab[] = { 
    { CSTOPDICTATE, "stop dictation" },
    { CCORRECTION, "correction" },
    { CIDLE, "idle mode" },
    { -1, NULL }
};

struct lstrcmp                          //  map of current grammars
{
        bool operator()(const string s1, const string s2) const
        {
                return s1.compare(s2) < 0;
        }
};

class grammar
{
    public:
        void *trans;
        gramHdlr handler;
        void *user;
        grammar(gramHdlr h, void *t, void *u) {
                handler = h;
                trans = t;
                user = u;
        }
        grammar() { };
};

typedef map gramMap;
static gramMap actvGram;

class vocab
{
    public:
        string name;
        defVocab *trans;
        vocHdlr handler;
        void *user;
        vocab(char *n, vocHdlr h, defVocab *t, void *u) {
                name = n;
                handler = h;
                trans = t;
                user = u;
        }
        vocab() { };
};

typedef list vocList;
static vocList actvVoc;


typedef list grList;
static grList appGram;

                                        //  ViaVoice information
static char   userid   [ 80 ] = SM_USE_CURRENT;
static char   enrollid [ 80 ] = SM_USE_CURRENT;
static char   taskid   [ 80 ] = SM_USE_CURRENT;

static bool paragraphStart = true;      // dictate support
static bool sentenceStart = true;
static stack wordLengths;
static stack words;

/*  functions */


/*
 * moved notifier to MainWin, on the grounds that the event loop is
 * a ui thing, not a voice engine thing. No gdk references in Voice.cc.
 * We still need this wrapper because of the c++ idiocy which mucks up
 * the idea of callbacks.
 */

static int myNotifier(int socket_handle, int (*recv_fn)(),
        void * recv_data, void * client_data )
{
    gMainWin->notify(socket_handle, recv_fn, recv_data, client_data);
    return SM_RC_OK;
}

int loadGrammars()
{
    if (gAppList != NULL) delete gAppList;
    gAppList=parseFile(xvoicexml, grammarDir);

    /* look for a global config */
    if (gAppList == NULL) 
        gAppList=parseFile(DATADIR "/" PACKAGE "/xvoice.xml", grammarDir);
    
    /*
     * Some desperate measures in case the program hasn't been installed yet
     */
    if (gAppList == NULL) gAppList=parseFile("xvoice.xml", grammarDir);
    if (gAppList == NULL) gAppList=parseFile("../xvoice.xml", grammarDir);
    if (gAppList == NULL) LogMessage(E_FATAL, "Could not find a config file.\nNo grammars will be available.");
    return false;
}

/**
 * clear all corrections pending
 */
static void ClearCorrections()
{
    while (!wordLengths.empty())
        wordLengths.pop();
    while (!words.empty())
        words.pop();
}

void TurnMicOn()
{
    int rc;

    rc = SmMicOn(SmAsynchronous);
    if (rc != SM_RC_OK) {
        LogMessage(E_FATAL, "%s: %d\n", SMAPI_ERROR, rc);
    }
}

void TurnMicOff()
{
    int rc;

    rc = SmMicOff(SmAsynchronous);
    if (rc != SM_RC_OK) {
        LogMessage(E_FATAL, "%s: %d\n", SMAPI_ERROR, rc);
    }
}

bool disableVocab(char *name)
{
    SmDisableVocab(name, SmAsynchronous);
}

bool enableVocab(char *name)
{
    SmEnableVocab(name, SmAsynchronous);
}

static SmHandler DefineVocabCB(SM_MSG reply, caddr_t client, caddr_t call_data);
bool installVocab(char *name, vocHdlr callback, defVocab *cmds, 
        void *user)
{
    SM_MSG reply;
    SM_VOCWORD *voc_words;
    SM_VOCWORD **voc_ptrs;
    int i, count;
    int rc;

    for (count = 0; cmds[count].phrase != NULL; ++count);

    voc_words = (SM_VOCWORD *)malloc(count*sizeof(SM_VOCWORD));
    voc_ptrs = (SM_VOCWORD **)malloc(count*sizeof(SM_VOCWORD));
    /*-------------------------------------------------------------------*/
    /* The SmDefineVocab call expects an array of pointers to SM_VOCWORD */
    /* structures rather than the SM_VOCWORDs themself                   */
    /*-------------------------------------------------------------------*/
    for (i = 0; i < count; i++) {
        dbgprintf(("vocab %s, cmd %s\n", name, cmds[i].phrase));
        voc_ptrs[i] = &(voc_words[i]);
        voc_words[i].spelling = cmds[i].phrase;
        voc_words[i].spelling_size = strlen(voc_words[i].spelling);
        voc_words[i].flags = 0;
    }

    dbgprintf(("%d commands\n",i));
    /* using synchronous mode here to avoid a race condition. */
    /* i'm not sure this is necessary */
    rc = SmDefineVocab(name, i, voc_ptrs, &reply);
    if (rc != SM_RC_OK) {
        LogMessage(E_FATAL, "%s: %d\n", SMAPI_ERROR, rc);
        free(voc_words);
        free(voc_ptrs);
        return false;
    }
    actvVoc.push_back(vocab(name, callback, cmds, user));
    dbgprintf(("vocab count %d\n", actvVoc.size())); 
    DefineVocabCB (reply, (caddr_t)NULL, (caddr_t)NULL );
    free(voc_words);
    free(voc_ptrs);
    return true;
}

static SmHandler SetCB(SM_MSG reply, caddr_t client, caddr_t call_data)
{
    return (SM_RC_OK);
}

static SmHandler MicOnCB(SM_MSG reply, caddr_t client, caddr_t call_data)
{
    int rc;
    SmGetRc(reply, &rc);
    switch (rc) {
            /* MicStateCB should handle these */
            case SM_RC_MIC_OFF_PENDING:
            case SM_RC_MIC_ON_PENDING:
            case SM_RC_MIC_ALREADY_ON:
            case SM_RC_OK:
                break;
            default:
                gMainWin->micOff();
                LogMessage(E_CONFIG, "Couldn't turn on microphone.\nIs it locked by another application?\n\nSMAPI error %d\n", rc);
                return SM_RC_OK;
    }

    /*-------------------------------------------------------------------*/
    /* VERY IMPORTANT - this tells the recognizer to 'go' (ie. start     */
    /* capturing the audio and processing it)                            */
    /*-------------------------------------------------------------------*/
    SmRecognizeNextWord(SmAsynchronous);
    return (SM_RC_OK);
}

static SmHandler MicOffCB(SM_MSG reply, caddr_t client, caddr_t call_data)
{
    int rc;
    SmGetRc(reply, &rc);
    switch (rc) {
            /* MicStateCB should handle these */
            case SM_RC_MIC_OFF_PENDING:
            case SM_RC_MIC_ON_PENDING:
            case SM_RC_MIC_ALREADY_OFF:
            case SM_RC_OK:
                break;
            default:
                gMainWin->micOn();
                LogMessage(E_CONFIG, "Couldn't turn off microphone.\nIs it locked by another application?\n\nSMAPI error %d\n", rc);
                return SM_RC_OK;
    }
}

static SmHandler MicStateCB(SM_MSG reply, caddr_t client, caddr_t call_data)
{
    int rc;
    unsigned long state;
    SmGetMicState(reply, &state);
    if (state == SM_NOTIFY_MIC_ON) gMainWin->micOn();
    else gMainWin->micOff();
    return SM_RC_OK;
}

static SmHandler EnableVocabCB(SM_MSG reply, caddr_t client, caddr_t call_data)
{
    char *vocab;
    SmGetVocabName(reply, &vocab);
    dbgprintf(("%s\n", vocab));

    CheckSmRC();
    gMainWin->vocab(true, vocab);


    return (SM_RC_OK);
}

static SmHandler DisableVocabCB(SM_MSG reply, caddr_t client, caddr_t call_data)
{
    char *vocab;
    SmGetVocabName(reply, &vocab);
    dbgprintf(("%s\n", vocab));
    CheckSmRC();

    gMainWin->vocab(false, vocab);
    return (SM_RC_OK);
}



static SmHandler DefineGrammarCB(SM_MSG reply, caddr_t client, caddr_t caller)
{
    int rc;
    char *vocab;
    fstring msg, filename;
    SM_VOCWORD *missing;
    unsigned long i, num_missing;

    SmGetRc(reply, &rc);
    SmGetVocabName(reply, &vocab);

    switch (rc) { 
        case SM_RC_OK: 
            break;

        /*
         * XXX -- supposedly we can get both of these delivered for a single
         * grammar request. This is inconvenient since we need to erase the
         * actvGram element once if either or both are returned. When we start
         * using externs, this may cause us to barf on erase(), or pop up
         * multiple errors.
         */

        case SM_RC_NOT_INVOCAB: 
        case SM_RC_MISSING_EXTERN:

            SmGetVocWords(reply, &num_missing, &missing);
            msg.appendf("Missing %d word(s) from '%s': %s\n",
                    num_missing, vocab, msg.c_str());
            for (i = 0; i < num_missing; i++)
                msg.appendf("%s ", missing[i].spelling);

            actvGram[vocab].handler(G_FAIL, vocab, msg.c_str(), NULL);
            actvGram.erase(vocab);
            /* CB return codes are not documented. wtf is this -1 for? */
            return (-1); 
            break;

        case SM_RC_SERVER_FILE_OPEN_ERROR:
        case SM_RC_SERVER_FILE_READ_ERROR:
            msg.appendf("File error while reading grammar '%s'\n", vocab);
            actvGram[vocab].handler(G_FAIL, actvGram[vocab].user, 
                    msg.c_str(), NULL);
            actvGram.erase(vocab);
            return (SM_RC_OK);
            break;
        default:
            msg.appendf("%s: %d\n", SMAPI_ERROR, rc );
            actvGram[vocab].handler(G_FAIL, actvGram[vocab].user, 
                    msg.c_str(), NULL);
            actvGram.erase(vocab);
            return (SM_RC_OK);
    }

    dbgprintf(( "Defined '%s'\n", vocab));

    /* translations */
    void *tr;
    filename.appendf("%s/%s.fst", grammarDir, vocab);
    dbgprintf(( "translation is %s\n", filename.c_str()));
    if (VtLoadFSG((char*)filename.c_str(), &tr)) {
        msg.appendf("No translation %s\n",filename.c_str());
        actvGram[vocab].handler(G_FAIL, actvGram[vocab].user, msg.c_str(),
                NULL);
        actvGram.erase(vocab);
        SmUndefineVocab((char*)vocab, SmAsynchronous);
        return SM_RC_OK;
    } else {
        actvGram[vocab].trans = tr;
    }

    /* 
     * XXX - We don't bother trying to clean up actvGram if
     * EnableVocabCB reports an error. In that case, either
     * the voice engine or XVoice is toast, anyway.
     */
    actvGram[vocab].handler(G_SUCCESS, actvGram[vocab].user, NULL, NULL);
    rc = SmEnableVocab(vocab, SmAsynchronous);
    return (0);
}

static SmHandler FocusCB(SM_MSG reply, caddr_t client, caddr_t call_data)
{
  CheckSmRC();

  return (SM_RC_OK);
}

static char* getApp()
{
    regex_t preg;
    const char *target = gMainWin->getTarget()->name();

    applicationList::iterator app;    
    for(app=gAppList->begin(); app!=gAppList->end(); app++) {
        dbgprintf(("(%s) looking at %s\n",target, app->name));
        if (regcomp(&preg, app->expr, REG_EXTENDED|REG_NOSUB) == 0) {
            if (regexec(&preg, target, 0, NULL, 0) == 0) {
                dbgprintf(("matched\n"));
                return app->name;
            }
        }
    }
    return NULL;
}

static void appHandler(int ev, const char* n, const char *ph, const char *tr);
static void grammarEvent(Event& ev)
{
    if (ev.val.grammar.enable) {
        appGram.push_back(ev.val.grammar.name);
        enableCommandGrammar(ev.val.grammar.name, appHandler, 
                (void *)appGram.back().c_str());
    } else {
        disableCommandGrammar(ev.val.grammar.name);
        grList::iterator gr;
        for (gr=appGram.begin(); gr!=appGram.end(); gr++) {
            if (!strcmp(gr->c_str(), ev.val.grammar.name)) {
                appGram.erase(gr);
                break;
            }
        }
    }
}

static bool dispatch(const char *translation, Target *targ)
{
    EventStream* es = parseBuff(translation);
    dbgprintf(("The translation is : \"%s\"\n",translation));
    dbgprintf(("Length of event stream %d\n", es->size()));
    if (!es) {
        LogMessage(E_CONFIG, "XML error parsing \"%s\"\n",translation);  
        delete es;
        return false;
    }

    while (!es->empty()) {
        Event ev = es->front();
        if (ev.type == EVGRAM) {
            dbgprintf(("grammar event %d %s\n",
                        ev.val.grammar.enable,
                        ev.val.grammar.name));
            grammarEvent(ev);
            es->pop_front();
        } else if (targ->sendEventStream(es)) { /* lost target */
            gMainWin->setTarget();
            vCommand(CIDLE);
            break;
        }
    }
    delete es;
    return false;
}

static void navHandler(int ev, const char *name, const char *ph, 
        const char *tr)
{
     switch (ev) {
        case G_FAIL:
            LogMessage(E_CONFIG, "Couldn't activate Window Manager commands\n%s", ph);
            break;
        case G_SUCCESS:
            break;
        case G_RECO:
            dispatch(tr, gMainWin->getNavTarget());
            break;
        default:
            break;
    }
}

static void appHandler(int ev, const char *name, const char *ph, 
        const char *tr)
{
    grList::iterator gr;
    switch (ev) {
        case G_FAIL:
            LogMessage(E_CONFIG, "Couldn't activate grammar %s\n%s\n", name, ph);
            for (gr=appGram.begin(); gr!=appGram.end(); gr++) {
                if (!strcmp(gr->c_str(), name)) {
                    appGram.erase(gr);
                    break;
                }
            }
            break;
        case G_SUCCESS:
            break;
        case G_RECO:
            dispatch(tr, gMainWin->getTarget());
            break;
        default:
            break;
    }
}

/* 
 * This method is a callback which is invoked when speech has been recognised
 * when speaking while a grammar (not dynamic vocab nor dictation) is active
 */

static SmHandler RecoPhraseCB(SM_MSG reply, caddr_t client, caddr_t caller)
{
    int rc;
    SM_WORD * firm;
    unsigned long num_firm;
    int i;
    char *vocab ;
    unsigned long flags;

    SmGetVocabName(reply, &vocab);
    SmGetRc(reply, &rc);

    if (rc != SM_RC_OK)
    {
        LogMessage(E_FATAL, "%s: %d\n", SMAPI_ERROR, rc);    
        rc = SmRecognizeNextWord(SmAsynchronous);
        return (rc);
    }

    /*------------------------------------------------------------------*/
    /* Get the 'phrase state' - this tells (among other things) whether */
    /* the phrase was accepted or rejected by the engine                */
    /*------------------------------------------------------------------*/
    rc = SmGetPhraseState(reply, &flags);

    if (rc != SM_RC_OK)
    {
        LogMessage(E_FATAL, "%s: %d\n", SMAPI_ERROR, rc);    
        rc = SmRecognizeNextWord(SmAsynchronous);
        return (rc);
    }

    /*------------------------------------------------------------------*/
    /* As with a recognized command, extract the recognized words       */
    /* (the 'phrase') from the reply structure                          */
    /*------------------------------------------------------------------*/
    rc = SmGetFirmWords(reply, &num_firm, &firm);
    if (rc != SM_RC_OK)
    {
        LogMessage(E_FATAL, "%s: %d\n", SMAPI_ERROR, rc);    
        rc = SmRecognizeNextWord(SmAsynchronous);
        return (rc);
    }

    /*------------------------------------------------------------------*/
    /* And go through them, making a string that has the complete       */
    /* phrase in it so that it can be displayed                         */
    /*------------------------------------------------------------------*/ 
    fstring phrase = firm [0].spelling;
    for (i = 1 ; i < num_firm ; i++)
        phrase.appendf( " %s", firm [i].spelling );

    dbgprintf(("%s\n",phrase.c_str()));
    if (!(flags & SM_PHRASE_ACCEPTED)) {
        gMainWin->reco(phrase.c_str(), false);
        rc = SmRecognizeNextWord(SmAsynchronous);
        return (rc);
    }
    gMainWin->reco(phrase.c_str(), true);

    /* dunno how to get new() to do this. it barfs on pointer types. */
    char **words = (char**)malloc(sizeof(char*)*(num_firm+1));
    for (i = 0 ; i < num_firm ; i++) words[i]=firm[i].spelling;
    words[i]=0;

    char *translation;
    if (VtGetTranslation(actvGram[vocab].trans, words, &translation)) {
        LogMessage(E_SEVERE, "No translation found for %s\n", phrase.c_str());
        free(words);
        rc = SmRecognizeNextWord(SmAsynchronous);
        return (rc);	
    }
    actvGram[vocab].handler(G_RECO, actvGram[vocab].user, phrase.c_str(), 
            translation);
    free(words);
    rc = SmRecognizeNextWord(SmAsynchronous);
    return (rc); 
}

static SmHandler DefineVocabCB(SM_MSG reply, caddr_t client, caddr_t call_data)
{
    char *vocab;
    int rc;
    SM_VOCWORD *missing;
    unsigned long i, num_missing;
    fstring msg;

    SmGetRc(reply, &rc);
    SmGetVocabName(reply, &vocab);

    if (rc != SM_RC_OK) {
        msg.appendf("%s: %d\n", SMAPI_ERROR, rc);
        vocList::iterator voc;
        for (voc=actvVoc.begin(); voc!=actvVoc.end(); voc++) {
            if (!strcmp(voc->name.c_str(), vocab)) {
                voc->handler(G_FAIL, voc->user, msg.c_str(), 0);
                actvVoc.erase(voc);
                break;
            }
        }
        return SM_RC_OK;
    }
    /*-------------------------------------------------------------------*/
    /* Check to see if any of the words from the vocabulary are missing  */
    /* from the recognizers pool(s)                                      */
    /*-------------------------------------------------------------------*/
    rc = SmGetVocWords(reply, & num_missing, & missing );

    if (num_missing) {
        for (i = 0; i < num_missing; i++)
            msg.appendf("%s ", missing[i].spelling);

        LogMessage(E_CONFIG, "Missing %d word(s) from '%s': %s\n",
                num_missing, vocab, msg.c_str());
    }

    return (SM_RC_OK);
}

/* 
 * This loads the .fsg grammar file 
 */

bool enableCommandGrammar(const char *name, gramHdlr callback, void *user)
{ 
    int rc;
    fstring filename;
    SM_MSG reply;

    if (name == NULL || actvGram.find(name) != actvGram.end()) return false;
    filename.appendf( "%s/%s.fsg", grammarDir, name );
    dbgprintf (( "grammar is %s\n", filename.c_str() ));

    /* 
     * XXX - There is a potential problem here if someone clears the grammars
     * before this is enabled. 
     */

    actvGram[name]=grammar(callback, (void *)NULL, user);
    actvGram[name].user = user;
    //IMPORTANT: the define grammar callback enables the new grammar 
    //for us to ensure everything's ok
    rc = SmDefineGrammar ((char*)name, (char*)filename.c_str(), 0, SmAsynchronous);

    return true;
}

bool disableCommandGrammar(const char *name)
{
    if (actvGram.find(name) == actvGram.end()) return false;
    dbgprintf(("removing %s\n", name));
    VtUnloadFSG(actvGram[name].trans);
    actvGram.erase(name);
    SmDisableVocab((char*)name, SmAsynchronous );  
    SmUndefineVocab((char*)name, SmAsynchronous);
    return true;
}

#if 0
static SmHandler UndefineVocabCB(SM_MSG reply, caddr_t client, caddr_t call_data)
{
        char *vocab;
        CheckSmRC();

        SmGetVocabName(reply, & vocab);
        actvGram.erase(vocab);
}
#endif

static SmHandler GetNextWordCB(SM_MSG reply, caddr_t client, caddr_t call_data)
{
  CheckSmRC();

  return (SM_RC_OK);
}

/*
 * If this is too slow we should use a hash to look up
 * commands.
 */

static bool matchCommand(char *word)
{
    int j;
    defVocab *t;

    vocList::iterator voc;
    for (voc=actvVoc.begin(); voc!=actvVoc.end(); voc++) {
        for (t = voc->trans; t->phrase != NULL; ++t) {
            if (!strcmp(t->phrase, word)) {
                voc->handler(G_RECO, voc->user, t->phrase, t->val);
                return true;
            }
        }
    }
    return false;
}

static bool commanding = false;
static bool dictating = false;

static void CommandState(bool enter)
{
    char *name;

    if (enter) {
        dbgprintf(("enter\n"));
        while (appGram.size() > 0) {
            disableCommandGrammar(appGram.front().c_str());
            appGram.pop_front();
        }

        name = getApp();
        if (name != NULL) {
            appGram.push_back(name);
            enableCommandGrammar(name, appHandler, (void*)appGram.back().c_str());
            if (!commanding) 
                enableVocab(vCommandVocabName);
            commanding = true;
        } else {
            enter = false; /* fall through to disable */
        }
    }

    if (!enter && commanding) {
        disableVocab(vCommandVocabName);
        while (appGram.size() > 0) {
            disableCommandGrammar(appGram.front().c_str());
            appGram.pop_front();
        }
        commanding = false;
    }
    gMainWin->targState(commanding, dictating);
    return;
}

static void DictateState(bool enter)
{
    if (enter && !dictating) {
        dbgprintf(("enter\n"));
        enableVocab(vTextVocab);
        enableVocab(vDictateVocabName);
        dictating = true;
    } else if (!enter && dictating) {
        disableVocab(vDictateVocabName);
        disableVocab(vTextVocab);
        dictating = false;
    }
    gMainWin->targState(commanding, dictating);
}

void vCommand(vCommandType t)
{
    if (t == CCOMMAND || t == CDICTATE) {
        if (gMainWin->setTarget()) {
            dbgprintf(("new window\n"))
            DictateState(false);
            CommandState(false);
        }
        if (gMainWin->getTarget()->name() == NULL) {
            t = CIDLE;
        }
    }
    switch (t) {
        case CMICON:
            TurnMicOn();
            return;
        case CMICOFF:
            TurnMicOff();
            return;
        case CREBUILD:
            loadGrammars();
            return;
        case CIDLE:
            DictateState(false);
            CommandState(false);
            break;
        case CCOMMAND:
            CommandState(true);
            break;
        case CSTOPCOMMAND:
            CommandState(false);
            break;
        case CDICTATE:
            DictateState(true);
            break;
        case CSTOPDICTATE:
            DictateState(false);
            break;
        case CCORRECTION:
            {
                Target *targ = gMainWin->getTarget();
                dbgprintf(("correction\n"));
                if (targ != NULL && !wordLengths.empty())
                {
                    int lastWordLen = wordLengths.top();
                    wordLengths.pop();

                    fstring buffer(lastWordLen, '\b');
                    if (gMainWin->getTarget()->sendText( buffer.c_str() )) { /* lost target */
                        gMainWin->setTarget();
                        vCommand(CIDLE);
                    }
                }
            }
            break;
        default:
            break;
    }
}

static void vocHandler(int ev, void *user, const char *ph, int val)
{
    switch (ev) {
        case G_FAIL:
            break;
        case G_SUCCESS:
            break;
        case G_RECO:
            vCommand((enum vCommandType)val);
            break;
    }
}

/*
 * This method is called when a word from a dynamic vocab has been recognised
 */

static SmHandler RecoWordCB(SM_MSG reply, caddr_t client, caddr_t call_data)
{
    char *vocab;
    int rc;
    unsigned long i, num_firm;
    SM_WORD *firm;

    CheckSmRC();
    SmGetVocabName(reply, &vocab);

    rc = SmGetFirmWords(reply, &num_firm, &firm);

    for (i = 0 ; i < num_firm; i++) {
        if (firm[i].spelling[0] == '\0') {
            gMainWin->reco("(unknown)", true);
        } else {
            gMainWin->reco(firm[i].spelling, true);
            if (!matchCommand(firm[i].spelling)) {
                LogMessage(E_FATAL, "Recognized word we don't have a record of.\nThis should never happen\n");
            }
        }
    }

    /*-------------------------------------------------------------------*/
    /* Tell the recognizer to 'go' again.  It stops so that if we wanted */
    /* to, we could change vocabs...                                     */
    /*-------------------------------------------------------------------*/

    rc = SmRecognizeNextWord(SmAsynchronous);
    return (SM_RC_OK);
}

/*
 * This method is called when a chunk of text has been recognised in dictation
 * mode. The method sends this text to the target application
 */
static SmHandler RecoTextCB(SM_MSG reply, caddr_t client, caddr_t call_data) 
{ 
    int rc;
    int i; 
    unsigned long num_firm; 
    SM_WORD *firm; 
    fstring buffer;

    CheckSmRC();
    Target *t = gMainWin->getTarget();
    if(t==NULL){
        LogMessage(E_SEVERE, "Attempt to dictate with no active target!\n");
        return SM_RC_OK;
    }

    rc = SmGetFirmWords(reply, &num_firm, &firm);

    for (i = 0 ; i < ( int ) num_firm; i++)
    {
        buffer.resize(0); // no clear() function?
        char* word = firm[i].spelling;
        gMainWin->reco(firm[i].spelling, true);
        //printf("Word: %s Tag:%d\n",firm[i].spelling, firm[i].tag);
        //fist deal with special case stuff sent by the engine:
        if (strcmp(word,"NEW-PARAGRAPH")==0) {
            if (gMainWin->getTarget()->sendText("\r\r")) { /* lost target */
                gMainWin->setTarget();
                vCommand(CIDLE);
                return SM_RC_OK;
            }
            paragraphStart=true;
            continue;
        } else if (strcmp(word,"NEW-LINE")==0) {
            if (gMainWin->getTarget()->sendText("\r")) { /* lost target */
                gMainWin->setTarget();
                vCommand(CIDLE);
                return SM_RC_OK;
            }
            sentenceStart=true;
            continue;
        } else if (( strlen( word ) == 1) &&
                ispunct( word[0] ) ) {
            buffer = word;

            char p = word[0];
            if (p == '.' || p == '?' || p == '!') {
                sentenceStart = true;
            }
        } else {//this is an ordinary word

            //if this is the start of a new paragraph, indent & capitalise
            if(paragraphStart) {
                word[0]=toupper(word[0]);
                buffer.appendf("   %s", word );  
                paragraphStart = false;
            } else if(sentenceStart) {
                // capitalize first word
                word[0]=toupper(word[0]);
                buffer.appendf(" %s", word);
                sentenceStart = false;
            } else {
                //word in the middle of a sentence
                buffer.appendf(" %s", word);
            }	 
        }
        wordLengths.push( buffer.size());
        words.push(firm[i]);

        if (gMainWin->getTarget()->sendText( buffer.c_str() )) { /* lost target */
            gMainWin->setTarget();
            vCommand(CIDLE);
        }
    }

    return (SM_RC_OK);
}

static SmHandler UtteranceCB(SM_MSG reply, caddr_t client, caddr_t call_data)
{
    dbgprintf(("\n"));
    /*-------------------------------------------------------------------*/
    /* The engine has turned the mic off and processed all of the audio  */
    /*-------------------------------------------------------------------*/
    //printf("Utterance callback \n");
    //SmSet(SM_SAVE_AUDIO,true,&reply);

    //  TurnMicOn();
    //int rc=SmQuery(SM_SAVE_AUDIO,&reply);
    //  printf("SM_SAVE_AUDIO=%d\n",rc);
    return (SM_RC_OK);
}

static int initialState()
{

    installVocab(vCommandVocabName, vocHandler, vCommandVocab, NULL);
    installVocab(vDictateVocabName, vocHandler, vDictateVocab, NULL);
    installVocab(vIdleVocabName, vocHandler, vIdleVocab, NULL);

    SmEnableVocab(vIdleVocabName, SmAsynchronous);
    gMainWin->initVocabs();
    gMainWin->setTarget();

    enableCommandGrammar("windowmanagershortcuts", navHandler, (void*)NULL);
    return true;
}

/*---------------------------------------------------------------------*/
/* called after connection to the speech recognition engine            */
/*---------------------------------------------------------------------*/
static SmHandler ConnectCB(SM_MSG reply, caddr_t client, caddr_t call_data)
{
    CheckSmRC();
    initialState();
    return (SM_RC_OK);
}

static SmHandler DisconnectCB(SM_MSG reply, caddr_t client, caddr_t call_data)
{
  return (SM_RC_OK);
}

bool exitVoice()
{
    SM_MSG reply;
    /* 
     * This better be synchronous, because we're about to lose
     * our event loop & our connect to the voice engine goes with it.
     */
    SmDisconnect(0, NULL, &reply);
    return true;
}

//This is called almost immediately after the app launches
//it initialises the speech engine 
bool initVoice(bool save)
{
    static int first = true;
    int        rc;
    int        smc;
    SmArg      smargs [30];
    static     int input_id;

    if (first)
    {
	smc = 0;
	SmSetArg(smargs [smc], SmNapplicationName, PACKAGE);   smc++;
	SmSetArg(smargs [smc], SmNexternalNotifier,       myNotifier); smc++;
	SmSetArg(smargs [smc], SmNexternalNotifierData, & input_id);   smc++;

	/*-----------------------------------------------------------------*/
	/* The call to SmOpen initializes any data that's inside of libSm  */
	/*-----------------------------------------------------------------*/
	rc = SmOpen(smc, smargs);

        if (rc != SM_RC_OK) {
            LogMessage(E_FATAL, "%s: %d\n", SMAPI_ERROR, rc);
        }

	/*-----------------------------------------------------------------*/
	/* Add the callbacks to catch the messages coming back from the    */
	/* reco engine                                                     */
	/*-----------------------------------------------------------------*/
	SmAddCallback(SmNconnectCallback,             ConnectCB,       NULL);
	SmAddCallback(SmNdisconnectCallback,          DisconnectCB,    NULL);
	SmAddCallback(SmNsetCallback,                 SetCB,           NULL);
	SmAddCallback(SmNmicOnCallback,               MicOnCB,         NULL);
	SmAddCallback(SmNmicOffCallback,              MicOffCB,        NULL);
	SmAddCallback(SmNmicStateCallback,            MicStateCB,      NULL);
	SmAddCallback(SmNenableVocabCallback,         EnableVocabCB,   NULL);
	SmAddCallback(SmNdisableVocabCallback,        DisableVocabCB,  NULL);
	SmAddCallback(SmNdefineVocabCallback,         DefineVocabCB,   NULL);    
	SmAddCallback(SmNdefineGrammarCallback,       DefineGrammarCB, NULL);
	SmAddCallback(SmNrecognizeNextWordCallback,   GetNextWordCB,   NULL);
	SmAddCallback(SmNrecognizedWordCallback,      RecoWordCB,      NULL);  
	SmAddCallback(SmNrecognizedPhraseCallback,    RecoPhraseCB,    NULL);
	SmAddCallback(SmNrecognizedTextCallback,      RecoTextCB,      NULL);
	SmAddCallback(SmNutteranceCompletedCallback,  UtteranceCB,     NULL);
	SmAddCallback(SmNfocusGrantedCallback,        FocusCB,        NULL);

	first = false;
    }

    /*-----------------------------------------------------------------*/
    /* Now connect to the engine (asynchronously, which means that     */
    /* the ConnectCB will get the results)                             */
    /*-----------------------------------------------------------------*/
    smc = 0;
    SmSetArg(smargs [smc], SmNuserId,       userid   );  smc++;
    SmSetArg(smargs [smc], SmNenrollId,     enrollid );  smc++;
    SmSetArg(smargs [smc], SmNtask,         taskid   );  smc++;
    SmSetArg(smargs [smc], SmNrecognize,    true     );  smc++;
    SmSetArg(smargs [smc], SmNoverrideLock, true     );  smc++;

    rc = SmConnect(smc, smargs, SmAsynchronous);
    
    if (rc != SM_RC_OK) {
        LogMessage(E_FATAL, "%s: %d\nThis probably means you did not run '. vvsetenv' prior to running %s.\nDo that and try again.\n", SMAPI_ERROR, PACKAGE, rc);
        return false;
    }
    SM_MSG reply;
    if (save) SmSet(SM_SAVE_AUDIO, true, &reply);
    SmSet(SM_NOTIFY_MIC_STATE, true, &reply);
    return true;
}


int compileGrammar(char *name)
{
    int vtc;
    VtArg vtargs[5];
    fstring ifilename;
    fstring ofilename;
    
#ifndef VTCOMPILE
    ifilename.appendf("/usr/lib/ViaVoice/bin/vtbnfc -en -o %s/%s.fsg %s/%s.bnf", 
            grammarDir, name, grammarDir, name);
    system(ifilename.c_str());
    ofilename.appendf("/usr/lib/ViaVoice/bin/vtbnfc -tr -o %s/%s.fst %s/%s.bnf", 
            grammarDir, name, grammarDir, name);
    system(ofilename.c_str());

#else 
    /* 
     * this code consistantly segfaults in weird ways.
     * a VtCompileGrammar() bug? or something here?
     */
    ifilename.appendf("%s/%s.bnf", grammarDir, name);
    ofilename.appendf("%s/%s.fst", grammarDir, name);

    vtc = 0;
    VtSetArg(vtargs[vtc], VtNbnfFile,       ifilename.c_str());  vtc++;
    VtSetArg(vtargs[vtc], VtNfsgFile,       ofilename.c_str());  vtc++;
    VtSetArg(vtargs[vtc], VtNtrMode,        1);  vtc++;
    VtSetArg(vtargs[vtc], VtNfsgFlags, SM_PHRASE_ALLOW_SILENCES);  vtc++;
    VtCompileGrammar(vtc, vtargs);

    ofilename.resize(0);
    ofilename.appendf("%s/%s.fsg", grammarDir, name);

    vtc = 0;
    VtSetArg(vtargs[vtc], VtNbnfFile,       ifilename.c_str());  vtc++;
    VtSetArg(vtargs[vtc], VtNfsgFile,       ofilename.c_str());  vtc++;
    VtSetArg(vtargs[vtc], VtNenMode,        1);  vtc++;
    VtSetArg(vtargs[vtc], VtNfsgFlags, SM_PHRASE_ALLOW_SILENCES);  vtc++;
    VtCompileGrammar(vtc, vtargs);
#endif

    return 1;
}