1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-11-23 17:30:17 +01:00

Bug 2360 - Scripting: "Message:" command may crash when using Nyquist with Python

This commit is contained in:
Leland Lucius
2020-04-16 22:14:45 -05:00
parent 9827d4a753
commit a6d0b3f902
9 changed files with 89 additions and 186 deletions

View File

@@ -26,150 +26,80 @@ code out of ModuleManager.
#include "AppCommandEvent.h"
#include "../Project.h"
#include <wx/app.h>
#include <wx/window.h>
#include <wx/string.h>
#include <thread>
// Declare static class members
CommandHandler *ScriptCommandRelay::sCmdHandler;
tpRegScriptServerFunc ScriptCommandRelay::sScriptFn;
void ScriptCommandRelay::SetRegScriptServerFunc(tpRegScriptServerFunc scriptFn)
{
sScriptFn = scriptFn;
}
void ScriptCommandRelay::SetCommandHandler(CommandHandler &ch)
{
sCmdHandler = &ch;
}
/// Calls the script function, passing it the function for obeying commands
void ScriptCommandRelay::Run()
{
wxASSERT( sScriptFn != NULL );
while( true )
sScriptFn(&ExecCommand);
}
/// Send a command to a project, to be applied in that context.
void ScriptCommandRelay::PostCommand(
wxWindow *pWindow, const OldStyleCommandPointer &cmd)
{
wxASSERT( pWindow );
wxASSERT(cmd != NULL);
if ( pWindow ) {
AppCommandEvent ev;
ev.SetCommand(cmd);
pWindow->GetEventHandler()->AddPendingEvent(ev);
}
}
/// This is the function which actually obeys one command. Rather than applying
/// the command directly, an event containing a reference to the command is sent
/// to the main (GUI) thread. This is because having more than one thread access
/// the GUI at a time causes problems with wxwidgets.
int ExecCommand(wxString *pIn, wxString *pOut)
/// This is the function which actually obeys one command.
static int ExecCommand(wxString *pIn, wxString *pOut, bool fromMain)
{
{
CommandBuilder builder(::GetActiveProject(), *pIn);
if (builder.WasValid())
{
OldStyleCommandPointer cmd = builder.GetCommand();
ScriptCommandRelay::PostCommand(wxTheApp->GetTopWindow(), cmd);
*pOut = wxEmptyString;
}
else
{
*pOut = wxT("Syntax error!\n");
*pOut += builder.GetErrorMessage() + wxT("\n");
}
}
// Wait until all responses from the command have been received.
// The last response is signalled by an empty line.
wxString msg = ScriptCommandRelay::ReceiveResponse().GetMessage();
while (msg != wxT("\n"))
{
//wxLogDebug( "Msg: %s", msg );
*pOut += msg + wxT("\n");
msg = ScriptCommandRelay::ReceiveResponse().GetMessage();
}
return 0;
}
/// This is the function which actually obeys one command. Rather than applying
/// the command directly, an event containing a reference to the command is sent
/// to the main (GUI) thread. This is because having more than one thread access
/// the GUI at a time causes problems with wxwidgets.
int ExecCommand2(wxString *pIn, wxString *pOut)
{
{
CommandBuilder builder(::GetActiveProject(), *pIn);
if (builder.WasValid())
{
OldStyleCommandPointer cmd = builder.GetCommand();
AppCommandEvent ev;
ev.SetCommand(cmd);
// Use SafelyProcessEvent, which stops exceptions, because this is
// expected to be reached from within the XLisp runtime
wxTheApp->SafelyProcessEvent( ev );
*pOut = wxEmptyString;
if (fromMain)
{
// Use SafelyProcessEvent, which stops exceptions, because this is
// expected to be reached from within the XLisp runtime
wxTheApp->SafelyProcessEvent(ev);
}
else
{
// Send the event to the main thread
wxTheApp->AddPendingEvent(ev);
}
}
else
{
*pOut = wxT("Syntax error!\n");
*pOut += builder.GetErrorMessage() + wxT("\n");
}
}
// Wait until all responses from the command have been received.
// The last response is signalled by an empty line.
//
// LLL: Allow ExecCommand() to process the responses, otherwise
// it will hang waiting for more responses that will not be
// forthcoming.
#if 0
wxString msg = ScriptCommandRelay::ReceiveResponse().GetMessage();
while (msg != wxT("\n"))
{
//wxLogDebug( "Msg: %s", msg );
*pOut += msg + wxT("\n");
msg = ScriptCommandRelay::ReceiveResponse().GetMessage();
// Wait for and retrieve the response
*pOut = builder.GetResponse();
}
#endif
return 0;
}
/// Executes a command in the worker (script) thread
static int ExecFromWorker(wxString *pIn, wxString *pOut)
{
return ExecCommand(pIn, pOut, false);
}
/// Executes a command on the main (GUI) thread.
static int ExecFromMain(wxString *pIn, wxString *pOut)
{
return ExecCommand(pIn, pOut, true);
}
/// Starts the script server
void ScriptCommandRelay::StartScriptServer(tpRegScriptServerFunc scriptFn)
{
wxASSERT(scriptFn != NULL);
auto server = [](tpRegScriptServerFunc function)
{
while (true)
{
function(ExecFromWorker);
}
};
std::thread(server, scriptFn).detach();
}
// The void * return is actually a Lisp LVAL and will be cast to such as needed.
extern void * ExecForLisp( char * pIn );
extern void * nyq_make_opaque_string( int size, unsigned char *src );
extern void * nyq_reformat_aud_do_response(const wxString & Str);
void * ExecForLisp( char * pIn ){
wxString Str1( pIn );
wxString Str2;
ExecCommand2( &Str1, &Str2 );
// wxString provides a const char *
//const char * pStr = static_cast<const char*>(Str2);
// We'll be passing it as a non-const unsigned char *
// That 'unsafe' cast is actually safe. nyq_make_opaque_string is just copying the string.
void * pResult = nyq_reformat_aud_do_response( Str2 );
return pResult;
};
/// Gets a response from the queue (may block)
Response ScriptCommandRelay::ReceiveResponse()
void * ExecForLisp( char * pIn )
{
return ResponseQueueTarget::sResponseQueue().WaitAndGetResponse();
wxString Str1(pIn);
wxString Str2;
ExecFromMain(&Str1, &Str2);
return nyq_reformat_aud_do_response(Str2);
}