MyExternalCmdExecDialog.cpp

Go to the documentation of this file.
00001 // -*- c-basic-offset: 4 -*-
00025 // This class is written based on 'exec' sample of wxWidgets library.
00026 
00027 #include "hugin_config.h"
00028 #include "panoinc_WX.h"
00029 #include "panoinc.h"
00030 
00031 #include <errno.h>
00032 
00033 #include "base_wx/wxPlatform.h"
00034 
00035 #include "wx/ffile.h"
00036 #include "wx/process.h"
00037 #include "wx/mimetype.h"
00038 #include <wx/sstream.h>
00039 
00040 #ifdef _WIN32
00041     #include "wx/dde.h"
00042         #include <windows.h>
00043         #include <tlhelp32.h>   //needed to pause process on windows
00044 #else
00045         #include <sys/types.h>  
00046         #include <signal.h>     //needed to pause on unix - kill function
00047         #include <unistd.h> //needed to separate the process group of make
00048 #endif // _WIN32
00049 
00050 // Slightly reworked fix for BUG_2075064 
00051 #ifdef __WXMAC__
00052         #include <iostream>
00053         #include <stdio.h>
00054         #include "wx/utils.h"
00055 #endif
00056 
00057 #include "MyExternalCmdExecDialog.h"
00058 #include "hugin/config_defaults.h"
00059 
00060 // ----------------------------------------------------------------------------
00061 // event tables and other macros for wxWidgets
00062 // ----------------------------------------------------------------------------
00063 
00064 DEFINE_EVENT_TYPE(EVT_QUEUE_PROGRESS)
00065 
00066 BEGIN_EVENT_TABLE(MyExecPanel, wxPanel)
00067     EVT_TIMER(wxID_ANY, MyExecPanel::OnTimer)
00068 END_EVENT_TABLE()
00069 
00070 
00071 // ============================================================================
00072 // implementation
00073 // ============================================================================
00074 
00075 
00076 // frame constructor
00077 MyExecPanel::MyExecPanel(wxWindow * parent)
00078        : wxPanel(parent),
00079        m_timerIdleWakeUp(this), m_queue(NULL), m_queueLength(0), m_checkReturnCode(true)
00080 {
00081     m_pidLast = 0;
00082 
00083     wxBoxSizer * topsizer = new wxBoxSizer( wxVERTICAL );
00084     // create the listbox in which we will show misc messages as they come
00085     m_textctrl = new wxTextCtrl(this, wxID_ANY, _T(""), wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE|wxTE_READONLY);
00086     m_lastLineStart = 0;
00087     
00088 #ifdef __WXMAC__
00089     wxFont font(10, wxFONTFAMILY_TELETYPE, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
00090 #else
00091     wxFont font(8, wxFONTFAMILY_TELETYPE, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
00092 #endif
00093     
00094     if ( font.Ok() ) {
00095         m_textctrl->SetFont(font);
00096     }
00097 
00098     topsizer->Add(m_textctrl, 1, wxEXPAND | wxALL, 10);
00099     SetSizer( topsizer );
00100 }
00101 
00102 void MyExecPanel::KillProcess()
00103 {
00104     if (m_pidLast) {
00105 #ifdef __WXMSW__
00106         DEBUG_DEBUG("Killing process " << m_pidLast << " with sigkill");
00107         wxKillError rc = wxProcess::Kill(m_pidLast, wxSIGKILL, wxKILL_CHILDREN);
00108 #else
00109         DEBUG_DEBUG("Killing process " << m_pidLast << " with sigterm");
00110         wxKillError rc = wxProcess::Kill(m_pidLast, wxSIGTERM, wxKILL_CHILDREN);
00111 #endif
00112         if ( rc != wxKILL_OK ) {
00113             static const wxChar *errorText[] =
00114             {
00115                 _T(""), // no error
00116                 _T("signal not supported"),
00117                 _T("permission denied"),
00118                 _T("no such process"),
00119                 _T("unspecified error"),
00120             };
00121 
00122             wxLogError(_("Failed to kill process %ld, error %d: %s"),
00123                         m_pidLast, rc, errorText[rc]);
00124         }
00125     }
00126 }
00127 
00129 void MyExecPanel::PauseProcess(bool pause)
00130 {
00131 #ifdef __WXMSW__
00132         HANDLE hProcessSnapshot = NULL;
00133         PROCESSENTRY32 pEntry = {0};
00134         THREADENTRY32 tEntry = {0};
00135 
00136         //we take a snapshot of all system processes
00137         hProcessSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
00138 
00139         if (hProcessSnapshot == INVALID_HANDLE_VALUE)
00140                 wxLogError(_("Error pausing process %ld, code 1"),m_pidLast);
00141         else
00142         {
00143                 pEntry.dwSize = sizeof(PROCESSENTRY32);
00144                 tEntry.dwSize = sizeof(THREADENTRY32);
00145                 
00146                 //we traverse all processes in the system
00147                 if(Process32First(hProcessSnapshot, &pEntry))
00148                 {
00149                         do
00150                         {
00151                                 //we pause threads of the main (make) process and its children (nona,enblend...)
00152                                 if((pEntry.th32ProcessID == m_pidLast) || (pEntry.th32ParentProcessID == m_pidLast))
00153                                 {
00154                                         //we take a snapshot of all system threads
00155                                         HANDLE hThreadSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); 
00156                                         if (hThreadSnapshot == INVALID_HANDLE_VALUE)
00157                                                 wxLogError(_("Error pausing process %ld, code 2"),m_pidLast);
00158 
00159                                         //we traverse all threads
00160                                         if(Thread32First(hThreadSnapshot, &tEntry))
00161                                         {
00162                                                 do
00163                                                 {
00164                                                         //we find all threads of the process
00165                                                         if(tEntry.th32OwnerProcessID == pEntry.th32ProcessID)
00166                                                         {
00167                                                                 HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, tEntry.th32ThreadID);
00168                                                                 if(pause)
00169                                                                         SuspendThread(hThread);
00170                                                                 else
00171                                                                         ResumeThread(hThread);
00172                                                                 CloseHandle(hThread);
00173                                                         }
00174                                                 }while(Thread32Next(hThreadSnapshot, &tEntry));
00175                                         }
00176                                         CloseHandle(hThreadSnapshot);
00177                                 }
00178                         }while(Process32Next(hProcessSnapshot, &pEntry));
00179                 }
00180         }
00181         CloseHandle(hProcessSnapshot);
00182 #else
00183         //send the process group a pause/cont signal
00184         if(pause)
00185                 killpg(m_pidLast,SIGSTOP);
00186         else
00187                 killpg(m_pidLast,SIGCONT);
00188 #endif //__WXMSW__
00189 }
00190 
00191 void MyExecPanel::ContinueProcess()
00192 {
00193         PauseProcess(false);
00194 }
00195 
00196 long MyExecPanel::GetPid()
00197 {
00198         return m_pidLast;
00199 }
00200 
00201 int MyExecPanel::ExecWithRedirect(wxString cmd)
00202 {
00203     if (!cmd)
00204         return -1;
00205 
00206     // Slightly reworked fix for BUG_2075064 
00207 #if defined __WXMAC__ && defined __ppc__
00208     int osVersionMajor;
00209     int osVersionMinor;
00210 
00211     int os = wxGetOsVersion(&osVersionMajor, &osVersionMinor);
00212 
00213     cout << "osVersionCheck: os is " << os << "\n"  << endl;
00214     cout << "osVersionCheck: osVersionMajor = " << osVersionMajor << endl;
00215     cout << "osVersionCheck: osVersionMinor = " << osVersionMinor << endl;
00216     if ((osVersionMajor == 0x10) && (osVersionMinor >= 0x50))
00217     {
00218         //let the child process exit without becoming zombie
00219         //may do some harm to internal handling by wxWidgets, but hey it's not working anyway
00220         signal(SIGCHLD,SIG_IGN);
00221         cout <<  "osVersionCheck: Leopard loop 1" << endl;
00222     }
00223     else
00224     {
00225         cout <<  "osVersionCheck: Tiger loop 1" << endl;
00226     }   
00227 #endif
00228 
00229     MyPipedProcess *process = new MyPipedProcess(this, cmd);
00230     m_pidLast = wxExecute(cmd, wxEXEC_ASYNC | wxEXEC_MAKE_GROUP_LEADER, process, &m_executeEnv);
00231     if ( m_pidLast == 0 )
00232     {
00233         wxLogError(_T("Execution of '%s' failed."), cmd.c_str());
00234         delete process;
00235         return -1;
00236     }
00237     else
00238     {
00239         AddAsyncProcess(process);
00240 #ifndef _WIN32
00241                 //on linux we put the new process into a separate group, 
00242                 //so it can be paused with all it's children at the same time
00243                 setpgid(m_pidLast,m_pidLast);   
00244 #endif
00245     }
00246     return 0;   
00247 }
00248 
00249 int MyExecPanel::ExecQueue(HuginQueue::CommandQueue* queue)
00250 {
00251     wxConfigBase* config = wxConfigBase::Get();
00252     const long threads = config->Read(wxT("/output/NumberOfThreads"), 0l);
00253     // read all current environment variables
00254     wxGetEnvMap(&m_executeEnv.env);
00255     // now modify some variables before passing them to wxExecute
00256     if (threads > 0)
00257     {
00258         wxString s;
00259         s << threads;
00260         m_executeEnv.env["OMP_NUM_THREADS"] = s;
00261     };
00262     // set temp dir
00263     wxString tempDir = config->Read(wxT("tempDir"), wxT(""));
00264     if (!tempDir.IsEmpty())
00265     {
00266 #ifdef UNIX_LIKE
00267         m_executeEnv.env["TMPDIR"] = tempDir;
00268 #else
00269         m_executeEnv.env["TMP"] = tempDir;
00270 #endif
00271     };
00272     m_queue = queue;
00273     m_queueLength = queue->size() + 1;
00274     if (m_queue->empty())
00275     {
00276         return 0;
00277     };
00278     return ExecNextQueue();
00279 };
00280 
00282 int MyExecPanel::ExecNextQueue()
00283 {
00284     if (m_queue)
00285     {
00286         // get next command
00287         HuginQueue::NormalCommand* cmd = m_queue->front();
00288         const wxString cmdString = cmd->GetCommand();
00289         // get comment, append line break and display comment in panel
00290         AddString(cmd->GetComment());
00291         m_checkReturnCode = cmd->CheckReturnCode();
00292         // delete command from queue
00293         delete cmd;
00294         m_queue->erase(m_queue->begin());
00295         // notify parent
00296         if (this->GetParent())
00297         {
00298             wxCommandEvent event(EVT_QUEUE_PROGRESS, wxID_ANY);
00299             event.SetInt(hugin_utils::roundi((m_queueLength - m_queue->size()) * 100.0f / m_queueLength));
00300             this->GetParent()->GetEventHandler()->AddPendingEvent(event);
00301         };
00302         // now execute command
00303         return ExecWithRedirect(cmdString);
00304     }
00305     else
00306     {
00307         return -1;
00308     };
00309 };
00310 
00311 void MyExecPanel::AddAsyncProcess(MyPipedProcess *process)
00312 {
00313     if ( m_running.IsEmpty() )
00314     {
00315         // we want to start getting the timer events to ensure that a
00316         // steady stream of idle events comes in -- otherwise we
00317         // wouldn't be able to poll the child process input
00318         m_timerIdleWakeUp.Start(200);
00319     }
00320     //else: the timer is already running
00321 
00322     m_running.Add(process);
00323 }
00324 
00325 
00326 void MyExecPanel::RemoveAsyncProcess(MyPipedProcess *process)
00327 {
00328     m_running.Remove(process);
00329 
00330     if ( m_running.IsEmpty() )
00331     {
00332         // we don't need to get idle events all the time any more
00333         m_timerIdleWakeUp.Stop();
00334     }
00335 }
00336 
00337 
00338 // ----------------------------------------------------------------------------
00339 // various helpers
00340 // ----------------------------------------------------------------------------
00341 
00342 void MyExecPanel::AddToOutput(wxInputStream & s)
00343 {
00344     DEBUG_TRACE("");
00345 #if defined __WXGTK__
00346     wxTextInputStream ts(s, " \t", wxConvLibc);
00347 #else
00348     wxTextInputStream ts(s);
00349 #endif
00350     bool lastCR= false;
00351     wxString currLine = m_textctrl->GetRange(m_lastLineStart, m_textctrl->GetLastPosition());
00352     while(s.CanRead()) {
00353         wxChar c = ts.GetChar();
00354         if (c == '\b') {
00355             lastCR=false;
00356             // backspace
00357             if (currLine.size() > 0) {
00358                 if (currLine.Last() != wxChar('\n') )
00359                     currLine.Trim();
00360             }
00361         } else if (c == 0x0d) {
00362             lastCR=true;
00363 #ifndef __WXMSW__
00364             // back to start of line
00365              if (currLine.Last() != wxChar('\n') ) {
00366                 currLine = currLine.BeforeLast('\n');
00367                 if(currLine.size() > 0) {
00368                     currLine.Append('\n');
00369                 }
00370              }
00371 #endif
00372         } else if (c == '\n') {
00373             currLine.Append(c);
00374             lastCR=false;
00375         } else {
00376 #ifdef __WXMSW__
00377             if (lastCR) {
00378             // back to start of line
00379                 if (currLine.Last() != wxChar('\n') ) {
00380                     currLine = currLine.BeforeLast('\n');
00381                     if(currLine.size() > 0) {
00382                         currLine.Append('\n');
00383                     }
00384                 }
00385             }
00386 #endif
00387             currLine.Append(c);
00388             lastCR=false;
00389         }
00390     }
00391 
00392     m_textctrl->Replace(m_lastLineStart, m_textctrl->GetLastPosition(), currLine);
00393     size_t lret = currLine.find_last_of(wxChar('\n'));
00394     if (lret > 0 && lret+1 < currLine.size()) {
00395         m_lastLineStart += lret+1;
00396     }
00397 }
00398 
00399 void MyExecPanel::OnTimer(wxTimerEvent& WXUNUSED(event))
00400 {
00401     size_t count = m_running.GetCount();
00402     for ( size_t n = 0; n < count; n++ )
00403     {
00404         while ( m_running[n]->IsInputAvailable() )
00405         {
00406             AddToOutput(*(m_running[n]->GetInputStream()));
00407         };
00408         while ( m_running[n]->IsErrorAvailable() )
00409         {
00410             AddToOutput(*(m_running[n]->GetErrorStream()));
00411         };
00412     };
00413 
00414 // Slightly reworked fix for BUG_2075064
00415 #if defined __WXMAC__ && defined __ppc__
00416         int osVersionMajor;
00417         int osVersionMinor;
00418         
00419         int os = wxGetOsVersion(&osVersionMajor, &osVersionMinor);
00420         
00421         cerr << "osVersionCheck: os is " << os << "\n"  << endl;
00422         cerr << "osVersionCheck: osVersionMajor = " << osVersionMajor << endl;
00423         cerr << "osVersionCheck: osVersionMinor = " << osVersionMinor << endl;
00424 
00425         if ((osVersionMajor == 0x10) && (osVersionMinor >= 0x50))
00426     {   
00427                 cerr <<  "osVersionCheck: Leopard loop 2" << endl;      
00428                 if(m_pidLast)
00429                 {
00430                         if(kill((pid_t)m_pidLast,0)!=0) //if not pid exists
00431                         {
00432                                 DEBUG_DEBUG("Found terminated process: " << (pid_t)m_pidLast)
00433             
00434                                 // probably should clean up the wxProcess object which was newed when the process was launched.
00435                                 // for now, nevermind the tiny memory leak... it's a hack to workaround the bug anyway
00436             
00437                                 //notify dialog that it's finished.
00438                                 if (this->GetParent()) {
00439                                         wxProcessEvent event( wxID_ANY, m_pidLast, 0); // assume 0 exit code
00440                                         event.SetEventObject( this );
00441                                         DEBUG_TRACE("Sending wxProcess event");   
00442                                         this->GetParent()->ProcessEvent( event );
00443                                 }
00444                         }
00445                 }
00446         }
00447         else
00448         {
00449                 cerr <<  "osVersionCheck: Tiger loop 2" << endl;
00450         }
00451 #endif
00452 }
00453 
00454 void MyExecPanel::OnProcessTerminated(MyPipedProcess *process, int pid, int status)
00455 {
00456     DEBUG_TRACE("process terminated: pid " << pid << " exit code:" << status);
00457     // show the rest of the output
00458     AddToOutput(*(process->GetInputStream()));
00459     AddToOutput(*(process->GetErrorStream()));
00460 
00461     RemoveAsyncProcess(process);
00462 
00463     if (m_queue && !m_queue->empty())
00464     {
00465         // queue has further commands
00466         // should we check the exit code?
00467         if ((m_checkReturnCode && status == 0) || (!m_checkReturnCode))
00468         {
00469             if (ExecNextQueue() == 0)
00470             {
00471                 return;
00472             };
00473         };
00474     };
00475     // send termination to parent
00476     if (this->GetParent())
00477     {
00478         wxProcessEvent event(wxID_ANY, pid, m_checkReturnCode ? status : 0);
00479         event.SetEventObject(this);
00480         DEBUG_TRACE("Sending wxProcess event");
00481         this->GetParent()->GetEventHandler()->AddPendingEvent(event);
00482         // notify parent to hide progress 
00483         wxCommandEvent event2(EVT_QUEUE_PROGRESS, wxID_ANY);
00484         event2.SetInt(-1);
00485         this->GetParent()->GetEventHandler()->AddPendingEvent(event2);
00486     };
00487 }
00488 
00489 MyExecPanel::~MyExecPanel()
00490 {
00491     delete m_textctrl;
00492 }
00493 
00494 bool MyExecPanel::SaveLog(const wxString &filename)
00495 {
00496     return m_textctrl->SaveFile(filename);
00497 };
00498 
00499 void MyExecPanel::CopyLogToClipboard()
00500 {
00501     m_textctrl->SelectAll();
00502     m_textctrl->Copy();
00503 };
00504 
00505 void MyExecPanel::AddString(const wxString& s)
00506 {
00507     if (!s.IsEmpty())
00508     {
00509         m_textctrl->AppendText(s + wxT("\n"));
00510         m_lastLineStart = m_textctrl->GetLastPosition();
00511     };
00512 };
00513 
00514 // ----------------------------------------------------------------------------
00515 // MyPipedProcess
00516 // ----------------------------------------------------------------------------
00517 
00518 void MyPipedProcess::OnTerminate(int pid, int status)
00519 {
00520     DEBUG_DEBUG("Process " << pid << " terminated with return code: " << status);
00521     m_parent->OnProcessTerminated(this, pid, status);
00522 
00523     // we're not needed any more
00524     delete this;
00525 }
00526 
00527 // ==============================================================================
00528 // MyExecDialog
00529 
00530 BEGIN_EVENT_TABLE(MyExecDialog, wxDialog)
00531     EVT_BUTTON(wxID_CANCEL,  MyExecDialog::OnCancel)
00532     EVT_END_PROCESS(wxID_ANY, MyExecDialog::OnProcessTerminate)
00533 END_EVENT_TABLE()
00534 
00535 MyExecDialog::MyExecDialog(wxWindow * parent, const wxString& title, const wxPoint& pos, const wxSize& size)
00536     : wxDialog(parent, wxID_ANY, title, pos, size, wxRESIZE_BORDER | wxCAPTION | wxCLOSE_BOX | wxSYSTEM_MENU)
00537 {
00538 
00539     wxBoxSizer * topsizer = new wxBoxSizer( wxVERTICAL );
00540     m_execPanel = new MyExecPanel(this);
00541     m_cancelled = false;
00542     
00543     topsizer->Add(m_execPanel, 1, wxEXPAND | wxALL, 2);
00544 
00545     topsizer->Add( new wxButton(this, wxID_CANCEL, _("Cancel")),
00546                    0, wxALL | wxALIGN_RIGHT, 10);
00547 
00548 #ifdef __WXMSW__
00549     // wxFrame does have a strange background color on Windows..
00550     this->SetBackgroundColour(m_execPanel->GetBackgroundColour());
00551 #endif
00552     SetSizer( topsizer );
00553 //    topsizer->SetSizeHints( this );
00554 }
00555 
00556 void MyExecDialog::OnProcessTerminate(wxProcessEvent & event)
00557 {
00558     DEBUG_DEBUG("Process terminated with return code: " << event.GetExitCode());
00559     if(wxConfigBase::Get()->Read(wxT("CopyLogToClipboard"), 0l)==1l)
00560     {
00561         m_execPanel->CopyLogToClipboard();
00562     };
00563     if (m_cancelled) {
00564         EndModal(HUGIN_EXIT_CODE_CANCELLED);
00565     } else {
00566         EndModal(event.GetExitCode());
00567     }
00568 }
00569 
00570 void MyExecDialog::OnCancel(wxCommandEvent& WXUNUSED(event))
00571 {
00572     DEBUG_DEBUG("Cancel Pressed");
00573     m_cancelled = true;
00574     m_execPanel->KillProcess();
00575 }
00576 
00577 
00578 int MyExecDialog::ExecWithRedirect(wxString cmd)
00579 {
00580     if (m_execPanel->ExecWithRedirect(cmd) == -1) {
00581         return -1;
00582     }
00583 
00584     return ShowModal();
00585 }
00586 
00587 int MyExecDialog::ExecQueue(HuginQueue::CommandQueue* queue)
00588 {
00589     if (m_execPanel->ExecQueue(queue) == -1)
00590     {
00591         return -1;
00592     }
00593     return ShowModal();
00594 }
00595 
00596 void MyExecDialog::AddString(const wxString& s)
00597 {
00598     m_execPanel->AddString(s);
00599 };
00600 
00601 MyExecDialog::~MyExecDialog() {
00602     delete m_execPanel;
00603 }
00604 
00605 int MyExecuteCommandOnDialog(wxString command, wxString args, wxWindow* parent,
00606                              wxString title, bool isQuoted)
00607 {
00608     if(!isQuoted)
00609     {
00610         command = hugin_utils::wxQuoteFilename(command);
00611     };
00612     wxString cmdline = command + wxT(" ") + args;
00613     MyExecDialog dlg(parent, title,
00614                      wxDefaultPosition, wxSize(640, 400));
00615 #ifdef __WXMAC__
00616     dlg.CentreOnParent();
00617 #endif
00618     return dlg.ExecWithRedirect(cmdline);
00619 }
00620 
00621 int MyExecuteCommandQueue(HuginQueue::CommandQueue* queue, wxWindow* parent, const wxString& title, const wxString& comment)
00622 {
00623     MyExecDialog dlg(parent, title, wxDefaultPosition, wxSize(640, 400));
00624 #ifdef __WXMAC__
00625     dlg.CentreOnParent();
00626 #endif
00627     if (!comment.IsEmpty())
00628     {
00629         dlg.AddString(comment);
00630     };
00631     int returnValue = dlg.ExecQueue(queue);
00632     while (!queue->empty())
00633     {
00634         delete queue->back();
00635         queue->pop_back();
00636     };
00637     delete queue;
00638     return returnValue;
00639 };
00640 

Generated on 21 Jul 2017 for Hugintrunk by  doxygen 1.4.7