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

Generated on 2 Aug 2015 for Hugintrunk by  doxygen 1.4.7