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 <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 #if wxCHECK_VERSION(3,0,0)
00231     m_pidLast = wxExecute(cmd, wxEXEC_ASYNC | wxEXEC_MAKE_GROUP_LEADER, process, &m_executeEnv);
00232 #else
00233     m_pidLast = wxExecute(cmd, wxEXEC_ASYNC | wxEXEC_MAKE_GROUP_LEADER, process);
00234 #endif
00235     if ( m_pidLast == 0 )
00236     {
00237         wxLogError(_T("Execution of '%s' failed."), cmd.c_str());
00238         delete process;
00239         return -1;
00240     }
00241     else
00242     {
00243         AddAsyncProcess(process);
00244 #ifndef _WIN32
00245                 //on linux we put the new process into a separate group, 
00246                 //so it can be paused with all it's children at the same time
00247                 setpgid(m_pidLast,m_pidLast);   
00248 #endif
00249     }
00250     return 0;   
00251 }
00252 
00253 int MyExecPanel::ExecQueue(HuginQueue::CommandQueue* queue)
00254 {
00255 #if wxCHECK_VERSION(3,0,0)
00256     wxConfigBase* config = wxConfigBase::Get();
00257     const long threads = config->Read(wxT("/output/NumberOfThreads"), 0l);
00258     if (threads > 0)
00259     {
00260         wxString s;
00261         s << threads;
00262         m_executeEnv.env["OMP_NUM_THREADS"] = s;
00263     };
00264     // set temp dir
00265     wxString tempDir = config->Read(wxT("tempDir"), wxT(""));
00266     if (!tempDir.IsEmpty())
00267     {
00268 #ifdef UNIX_LIKE
00269         m_executeEnv.env["TMPDIR"] = tempDir;
00270 #else
00271         m_executeEnv.env["TMP"] = tempDir;
00272 #endif
00273     };
00274 #endif
00275     m_queue = queue;
00276     m_queueLength = queue->size() + 1;
00277     if (m_queue->empty())
00278     {
00279         return 0;
00280     };
00281     return ExecNextQueue();
00282 };
00283 
00285 int MyExecPanel::ExecNextQueue()
00286 {
00287     if (m_queue)
00288     {
00289         // get next command
00290         HuginQueue::NormalCommand* cmd = m_queue->front();
00291         const wxString cmdString = cmd->GetCommand();
00292         // get comment, append line break and display comment in panel
00293         AddString(cmd->GetComment());
00294         m_checkReturnCode = cmd->CheckReturnCode();
00295         // delete command from queue
00296         delete cmd;
00297         m_queue->erase(m_queue->begin());
00298         // notify parent
00299         if (this->GetParent())
00300         {
00301             wxCommandEvent event(EVT_QUEUE_PROGRESS, wxID_ANY);
00302             event.SetInt(hugin_utils::roundi((m_queueLength - m_queue->size()) * 100.0f / m_queueLength));
00303             this->GetParent()->GetEventHandler()->AddPendingEvent(event);
00304         };
00305         // now execute command
00306         return ExecWithRedirect(cmdString);
00307     }
00308     else
00309     {
00310         return -1;
00311     };
00312 };
00313 
00314 void MyExecPanel::AddAsyncProcess(MyPipedProcess *process)
00315 {
00316     if ( m_running.IsEmpty() )
00317     {
00318         // we want to start getting the timer events to ensure that a
00319         // steady stream of idle events comes in -- otherwise we
00320         // wouldn't be able to poll the child process input
00321         m_timerIdleWakeUp.Start(200);
00322     }
00323     //else: the timer is already running
00324 
00325     m_running.Add(process);
00326 }
00327 
00328 
00329 void MyExecPanel::RemoveAsyncProcess(MyPipedProcess *process)
00330 {
00331     m_running.Remove(process);
00332 
00333     if ( m_running.IsEmpty() )
00334     {
00335         // we don't need to get idle events all the time any more
00336         m_timerIdleWakeUp.Stop();
00337     }
00338 }
00339 
00340 
00341 // ----------------------------------------------------------------------------
00342 // various helpers
00343 // ----------------------------------------------------------------------------
00344 
00345 void MyExecPanel::AddToOutput(wxInputStream & s)
00346 {
00347     DEBUG_TRACE("");
00348 #if defined __WXGTK__ && wxCHECK_VERSION(3,0,0)
00349     wxTextInputStream ts(s, " \t", wxConvLibc);
00350 #else
00351     wxTextInputStream ts(s);
00352 #endif
00353     bool lastCR= false;
00354     wxString currLine = m_textctrl->GetRange(m_lastLineStart, m_textctrl->GetLastPosition());
00355     while(s.CanRead()) {
00356         wxChar c = ts.GetChar();
00357         if (c == '\b') {
00358             lastCR=false;
00359             // backspace
00360             if (currLine.size() > 0) {
00361                 if (currLine.Last() != wxChar('\n') )
00362                     currLine.Trim();
00363             }
00364         } else if (c == 0x0d) {
00365             lastCR=true;
00366 #ifndef __WXMSW__
00367             // back to start of line
00368              if (currLine.Last() != wxChar('\n') ) {
00369                 currLine = currLine.BeforeLast('\n');
00370                 if(currLine.size() > 0) {
00371                     currLine.Append('\n');
00372                 }
00373              }
00374 #endif
00375         } else if (c == '\n') {
00376             currLine.Append(c);
00377             lastCR=false;
00378         } else {
00379 #ifdef __WXMSW__
00380             if (lastCR) {
00381             // back to start of line
00382                 if (currLine.Last() != wxChar('\n') ) {
00383                     currLine = currLine.BeforeLast('\n');
00384                     if(currLine.size() > 0) {
00385                         currLine.Append('\n');
00386                     }
00387                 }
00388             }
00389 #endif
00390             currLine.Append(c);
00391             lastCR=false;
00392         }
00393     }
00394 
00395     m_textctrl->Replace(m_lastLineStart, m_textctrl->GetLastPosition(), currLine);
00396     size_t lret = currLine.find_last_of(wxChar('\n'));
00397     if (lret > 0 && lret+1 < currLine.size()) {
00398         m_lastLineStart += lret+1;
00399     }
00400 }
00401 
00402 void MyExecPanel::OnTimer(wxTimerEvent& WXUNUSED(event))
00403 {
00404     size_t count = m_running.GetCount();
00405     for ( size_t n = 0; n < count; n++ )
00406     {
00407         while ( m_running[n]->IsInputAvailable() )
00408         {
00409             AddToOutput(*(m_running[n]->GetInputStream()));
00410         };
00411         while ( m_running[n]->IsErrorAvailable() )
00412         {
00413             AddToOutput(*(m_running[n]->GetErrorStream()));
00414         };
00415     };
00416 
00417 // Slightly reworked fix for BUG_2075064
00418 #if defined __WXMAC__ && defined __ppc__
00419         int osVersionMajor;
00420         int osVersionMinor;
00421         
00422         int os = wxGetOsVersion(&osVersionMajor, &osVersionMinor);
00423         
00424         cerr << "osVersionCheck: os is " << os << "\n"  << endl;
00425         cerr << "osVersionCheck: osVersionMajor = " << osVersionMajor << endl;
00426         cerr << "osVersionCheck: osVersionMinor = " << osVersionMinor << endl;
00427 
00428         if ((osVersionMajor == 0x10) && (osVersionMinor >= 0x50))
00429     {   
00430                 cerr <<  "osVersionCheck: Leopard loop 2" << endl;      
00431                 if(m_pidLast)
00432                 {
00433                         if(kill((pid_t)m_pidLast,0)!=0) //if not pid exists
00434                         {
00435                                 DEBUG_DEBUG("Found terminated process: " << (pid_t)m_pidLast)
00436             
00437                                 // probably should clean up the wxProcess object which was newed when the process was launched.
00438                                 // for now, nevermind the tiny memory leak... it's a hack to workaround the bug anyway
00439             
00440                                 //notify dialog that it's finished.
00441                                 if (this->GetParent()) {
00442                                         wxProcessEvent event( wxID_ANY, m_pidLast, 0); // assume 0 exit code
00443                                         event.SetEventObject( this );
00444                                         DEBUG_TRACE("Sending wxProcess event");   
00445                                         this->GetParent()->ProcessEvent( event );
00446                                 }
00447                         }
00448                 }
00449         }
00450         else
00451         {
00452                 cerr <<  "osVersionCheck: Tiger loop 2" << endl;
00453         }
00454 #endif
00455 }
00456 
00457 void MyExecPanel::OnProcessTerminated(MyPipedProcess *process, int pid, int status)
00458 {
00459     DEBUG_TRACE("process terminated: pid " << pid << " exit code:" << status);
00460     // show the rest of the output
00461     AddToOutput(*(process->GetInputStream()));
00462     AddToOutput(*(process->GetErrorStream()));
00463 
00464     RemoveAsyncProcess(process);
00465 
00466     if (m_queue && !m_queue->empty())
00467     {
00468         // queue has further commands
00469         // should we check the exit code?
00470         if ((m_checkReturnCode && status == 0) || (!m_checkReturnCode))
00471         {
00472             if (ExecNextQueue() == 0)
00473             {
00474                 return;
00475             };
00476         };
00477     };
00478     // send termination to parent
00479     if (this->GetParent())
00480     {
00481         wxProcessEvent event(wxID_ANY, pid, m_checkReturnCode ? status : 0);
00482         event.SetEventObject(this);
00483         DEBUG_TRACE("Sending wxProcess event");
00484         this->GetParent()->GetEventHandler()->AddPendingEvent(event);
00485         // notify parent to hide progress 
00486         wxCommandEvent event2(EVT_QUEUE_PROGRESS, wxID_ANY);
00487         event2.SetInt(-1);
00488         this->GetParent()->GetEventHandler()->AddPendingEvent(event2);
00489     };
00490 }
00491 
00492 MyExecPanel::~MyExecPanel()
00493 {
00494     delete m_textctrl;
00495 }
00496 
00497 bool MyExecPanel::SaveLog(const wxString &filename)
00498 {
00499     return m_textctrl->SaveFile(filename);
00500 };
00501 
00502 void MyExecPanel::CopyLogToClipboard()
00503 {
00504     m_textctrl->SelectAll();
00505     m_textctrl->Copy();
00506 };
00507 
00508 void MyExecPanel::AddString(const wxString& s)
00509 {
00510     if (!s.IsEmpty())
00511     {
00512         m_textctrl->AppendText(s + wxT("\n"));
00513         m_lastLineStart = m_textctrl->GetLastPosition();
00514     };
00515 };
00516 
00517 // ----------------------------------------------------------------------------
00518 // MyPipedProcess
00519 // ----------------------------------------------------------------------------
00520 
00521 void MyPipedProcess::OnTerminate(int pid, int status)
00522 {
00523     DEBUG_DEBUG("Process " << pid << " terminated with return code: " << status);
00524     m_parent->OnProcessTerminated(this, pid, status);
00525 
00526     // we're not needed any more
00527     delete this;
00528 }
00529 
00530 // ==============================================================================
00531 // MyExecDialog
00532 
00533 BEGIN_EVENT_TABLE(MyExecDialog, wxDialog)
00534     EVT_BUTTON(wxID_CANCEL,  MyExecDialog::OnCancel)
00535     EVT_END_PROCESS(wxID_ANY, MyExecDialog::OnProcessTerminate)
00536 END_EVENT_TABLE()
00537 
00538 MyExecDialog::MyExecDialog(wxWindow * parent, const wxString& title, const wxPoint& pos, const wxSize& size)
00539     : wxDialog(parent, wxID_ANY, title, pos, size, wxRESIZE_BORDER | wxCAPTION | wxCLOSE_BOX | wxSYSTEM_MENU)
00540 {
00541 
00542     wxBoxSizer * topsizer = new wxBoxSizer( wxVERTICAL );
00543     m_execPanel = new MyExecPanel(this);
00544     m_cancelled = false;
00545     
00546     topsizer->Add(m_execPanel, 1, wxEXPAND | wxALL, 2);
00547 
00548     topsizer->Add( new wxButton(this, wxID_CANCEL, _("Cancel")),
00549                    0, wxALL | wxALIGN_RIGHT, 10);
00550 
00551 #ifdef __WXMSW__
00552     // wxFrame does have a strange background color on Windows..
00553     this->SetBackgroundColour(m_execPanel->GetBackgroundColour());
00554 #endif
00555     SetSizer( topsizer );
00556 //    topsizer->SetSizeHints( this );
00557 }
00558 
00559 void MyExecDialog::OnProcessTerminate(wxProcessEvent & event)
00560 {
00561     DEBUG_DEBUG("Process terminated with return code: " << event.GetExitCode());
00562     if(wxConfigBase::Get()->Read(wxT("CopyLogToClipboard"), 0l)==1l)
00563     {
00564         m_execPanel->CopyLogToClipboard();
00565     };
00566     if (m_cancelled) {
00567         EndModal(HUGIN_EXIT_CODE_CANCELLED);
00568     } else {
00569         EndModal(event.GetExitCode());
00570     }
00571 }
00572 
00573 void MyExecDialog::OnCancel(wxCommandEvent& WXUNUSED(event))
00574 {
00575     DEBUG_DEBUG("Cancel Pressed");
00576     m_cancelled = true;
00577     m_execPanel->KillProcess();
00578 }
00579 
00580 
00581 int MyExecDialog::ExecWithRedirect(wxString cmd)
00582 {
00583     if (m_execPanel->ExecWithRedirect(cmd) == -1) {
00584         return -1;
00585     }
00586 
00587     return ShowModal();
00588 }
00589 
00590 int MyExecDialog::ExecQueue(HuginQueue::CommandQueue* queue)
00591 {
00592     if (m_execPanel->ExecQueue(queue) == -1)
00593     {
00594         return -1;
00595     }
00596     return ShowModal();
00597 }
00598 
00599 void MyExecDialog::AddString(const wxString& s)
00600 {
00601     m_execPanel->AddString(s);
00602 };
00603 
00604 MyExecDialog::~MyExecDialog() {
00605     delete m_execPanel;
00606 }
00607 
00608 int MyExecuteCommandOnDialog(wxString command, wxString args, wxWindow* parent,
00609                              wxString title, bool isQuoted)
00610 {
00611     if(!isQuoted)
00612     {
00613         command = hugin_utils::wxQuoteFilename(command);
00614     };
00615     wxString cmdline = command + wxT(" ") + args;
00616     MyExecDialog dlg(parent, title,
00617                      wxDefaultPosition, wxSize(640, 400));
00618 #ifdef __WXMAC__
00619     dlg.CentreOnParent();
00620 #endif
00621     return dlg.ExecWithRedirect(cmdline);
00622 }
00623 
00624 int MyExecuteCommandQueue(HuginQueue::CommandQueue* queue, wxWindow* parent, const wxString& title, const wxString& comment)
00625 {
00626     MyExecDialog dlg(parent, title, wxDefaultPosition, wxSize(640, 400));
00627 #ifdef __WXMAC__
00628     dlg.CentreOnParent();
00629 #endif
00630     if (!comment.IsEmpty())
00631     {
00632         dlg.AddString(comment);
00633     };
00634     int returnValue = dlg.ExecQueue(queue);
00635     while (!queue->empty())
00636     {
00637         delete queue->back();
00638         queue->pop_back();
00639     };
00640     delete queue;
00641     return returnValue;
00642 };
00643 

Generated on 28 Apr 2016 for Hugintrunk by  doxygen 1.4.7