Hugintrunk  0.1
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
MyExternalCmdExecDialog.cpp
Go to the documentation of this file.
1 // -*- c-basic-offset: 4 -*-
25 // This class is written based on 'exec' sample of wxWidgets library.
26 
27 #include "hugin_config.h"
28 #include "panoinc_WX.h"
29 #include "panoinc.h"
30 
31 #include <errno.h>
32 
33 #include "base_wx/wxPlatform.h"
34 
35 #include "wx/ffile.h"
36 #include "wx/process.h"
37 #include "wx/mimetype.h"
38 #include <wx/sstream.h>
39 #include <wx/tokenzr.h>
40 
41 #ifdef _WIN32
42  #include "wx/dde.h"
43  #include <windows.h>
44  #include <tlhelp32.h> //needed to pause process on windows
45 #else
46  #include <sys/types.h>
47  #include <signal.h> //needed to pause on unix - kill function
48  #include <unistd.h> //needed to separate the process group of make
49 #endif // _WIN32
50 
51 // Slightly reworked fix for BUG_2075064
52 #ifdef __WXMAC__
53  #include <iostream>
54  #include <stdio.h>
55  #include "wx/utils.h"
56 #endif
57 
59 #include "hugin/config_defaults.h"
60 
61 // ----------------------------------------------------------------------------
62 // event tables and other macros for wxWidgets
63 // ----------------------------------------------------------------------------
64 
65 wxDEFINE_EVENT(EVT_QUEUE_PROGRESS, wxCommandEvent);
66 
67 BEGIN_EVENT_TABLE(MyExecPanel, wxPanel)
68  EVT_TIMER(wxID_ANY, MyExecPanel::OnTimer)
70 
71 
72 // ============================================================================
73 // implementation
74 // ============================================================================
75 
76 
77 // frame constructor
78 MyExecPanel::MyExecPanel(wxWindow * parent)
79  : wxPanel(parent),
80  m_timerIdleWakeUp(this), m_queue(NULL), m_queueLength(0), m_checkReturnCode(true)
81 {
82  m_pidLast = 0;
83 
84  wxBoxSizer * topsizer = new wxBoxSizer( wxVERTICAL );
85  // create the listbox in which we will show misc messages as they come
86  m_textctrl = new wxTextCtrl(this, wxID_ANY, _T(""), wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE|wxTE_READONLY);
87  m_lastLineStart = 0;
88 
89  wxFont font(wxDEFAULT, wxFONTFAMILY_TELETYPE, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
90  if ( font.Ok() ) {
91  m_textctrl->SetFont(font);
92  }
93 
94  topsizer->Add(m_textctrl, 1, wxEXPAND | wxALL, 10);
95  SetSizer( topsizer );
96 }
97 
99 {
100  if (m_pidLast) {
101 #ifdef __WXMSW__
102  DEBUG_DEBUG("Killing process " << m_pidLast << " with sigkill");
103  wxKillError rc = wxProcess::Kill(m_pidLast, wxSIGKILL, wxKILL_CHILDREN);
104 #else
105  DEBUG_DEBUG("Killing process " << m_pidLast << " with sigterm");
106  wxKillError rc = wxProcess::Kill(m_pidLast, wxSIGTERM, wxKILL_CHILDREN);
107 #endif
108  if ( rc != wxKILL_OK ) {
109  static const wxChar *errorText[] =
110  {
111  _T(""), // no error
112  _T("signal not supported"),
113  _T("permission denied"),
114  _T("no such process"),
115  _T("unspecified error"),
116  };
117 
118  wxLogError(_("Failed to kill process %ld, error %d: %s"),
119  m_pidLast, rc, errorText[rc]);
120  }
121  }
122 }
123 
126 {
127 #ifdef __WXMSW__
128  HANDLE hProcessSnapshot = NULL;
129  PROCESSENTRY32 pEntry = {0};
130  THREADENTRY32 tEntry = {0};
131 
132  //we take a snapshot of all system processes
133  hProcessSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
134 
135  if (hProcessSnapshot == INVALID_HANDLE_VALUE)
136  wxLogError(_("Error pausing process %ld, code 1"),m_pidLast);
137  else
138  {
139  pEntry.dwSize = sizeof(PROCESSENTRY32);
140  tEntry.dwSize = sizeof(THREADENTRY32);
141 
142  //we traverse all processes in the system
143  if(Process32First(hProcessSnapshot, &pEntry))
144  {
145  do
146  {
147  //we pause threads of the main (make) process and its children (nona,enblend...)
148  if((pEntry.th32ProcessID == m_pidLast) || (pEntry.th32ParentProcessID == m_pidLast))
149  {
150  //we take a snapshot of all system threads
151  HANDLE hThreadSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
152  if (hThreadSnapshot == INVALID_HANDLE_VALUE)
153  wxLogError(_("Error pausing process %ld, code 2"),m_pidLast);
154 
155  //we traverse all threads
156  if(Thread32First(hThreadSnapshot, &tEntry))
157  {
158  do
159  {
160  //we find all threads of the process
161  if(tEntry.th32OwnerProcessID == pEntry.th32ProcessID)
162  {
163  HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, tEntry.th32ThreadID);
164  if(pause)
165  SuspendThread(hThread);
166  else
167  ResumeThread(hThread);
168  CloseHandle(hThread);
169  }
170  }while(Thread32Next(hThreadSnapshot, &tEntry));
171  }
172  CloseHandle(hThreadSnapshot);
173  }
174  }while(Process32Next(hProcessSnapshot, &pEntry));
175  }
176  }
177  CloseHandle(hProcessSnapshot);
178 #else
179  //send the process group a pause/cont signal
180  if(pause)
181  killpg(m_pidLast,SIGSTOP);
182  else
183  killpg(m_pidLast,SIGCONT);
184 #endif //__WXMSW__
185 }
186 
188 {
189  PauseProcess(false);
190 }
191 
193 {
194  return m_pidLast;
195 }
196 
198 {
199  if (!cmd)
200  return -1;
201 
202  // Slightly reworked fix for BUG_2075064
203 #if defined __WXMAC__ && defined __ppc__
204  int osVersionMajor;
205  int osVersionMinor;
206 
207  int os = wxGetOsVersion(&osVersionMajor, &osVersionMinor);
208 
209  cout << "osVersionCheck: os is " << os << "\n" << endl;
210  cout << "osVersionCheck: osVersionMajor = " << osVersionMajor << endl;
211  cout << "osVersionCheck: osVersionMinor = " << osVersionMinor << endl;
212  if ((osVersionMajor == 0x10) && (osVersionMinor >= 0x50))
213  {
214  //let the child process exit without becoming zombie
215  //may do some harm to internal handling by wxWidgets, but hey it's not working anyway
216  signal(SIGCHLD,SIG_IGN);
217  cout << "osVersionCheck: Leopard loop 1" << endl;
218  }
219  else
220  {
221  cout << "osVersionCheck: Tiger loop 1" << endl;
222  }
223 #endif
224 
225  MyPipedProcess *process = new MyPipedProcess(this, cmd);
226  m_pidLast = wxExecute(cmd, wxEXEC_ASYNC | wxEXEC_MAKE_GROUP_LEADER, process, &m_executeEnv);
227  if ( m_pidLast == 0 )
228  {
229  wxLogError(_T("Execution of '%s' failed."), cmd.c_str());
230  delete process;
231  return -1;
232  }
233  else
234  {
235  AddAsyncProcess(process);
236 #ifndef _WIN32
237  //on linux we put the new process into a separate group,
238  //so it can be paused with all it's children at the same time
239  setpgid(m_pidLast,m_pidLast);
240 #endif
241  }
242  return 0;
243 }
244 
246 {
247  wxConfigBase* config = wxConfigBase::Get();
248  const long threads = config->Read(wxT("/output/NumberOfThreads"), 0l);
249  // read all current environment variables
250  wxGetEnvMap(&m_executeEnv.env);
251  // now modify some variables before passing them to wxExecute
252  if (threads > 0)
253  {
254  wxString s;
255  s << threads;
256  m_executeEnv.env["OMP_NUM_THREADS"] = s;
257  };
258  // set temp dir
259  wxString tempDir = config->Read(wxT("tempDir"), wxT(""));
260  if (!tempDir.IsEmpty())
261  {
262 #ifdef UNIX_LIKE
263  m_executeEnv.env["TMPDIR"] = tempDir;
264 #else
265  m_executeEnv.env["TMP"] = tempDir;
266 #endif
267  };
268  m_queue = queue;
269  m_queueLength = queue->size() + 1;
270  if (m_queue->empty())
271  {
272  return 0;
273  };
274  m_stopWatch.Start();
275  return ExecNextQueue();
276 };
277 
280 {
281  if (m_queue)
282  {
283  // get next command
284  HuginQueue::NormalCommand* cmd = m_queue->front();
285  const wxString cmdString = cmd->GetCommand();
286  // get comment, append line break and display comment in panel
287  AddString(cmd->GetComment());
289  // delete command from queue
290  delete cmd;
291  m_queue->erase(m_queue->begin());
292  // notify parent
293  if (this->GetParent())
294  {
295  wxCommandEvent event(EVT_QUEUE_PROGRESS, wxID_ANY);
296  event.SetInt(hugin_utils::roundi((m_queueLength - m_queue->size()) * 100.0f / m_queueLength));
297  this->GetParent()->GetEventHandler()->AddPendingEvent(event);
298  };
299  // now execute command
300  return ExecWithRedirect(cmdString);
301  }
302  else
303  {
304  return -1;
305  };
306 };
307 
309 {
310  if ( m_running.IsEmpty() )
311  {
312  // we want to start getting the timer events to ensure that a
313  // steady stream of idle events comes in -- otherwise we
314  // wouldn't be able to poll the child process input
315  m_timerIdleWakeUp.Start(200);
316  }
317  //else: the timer is already running
318 
319  m_running.Add(process);
320 }
321 
322 
324 {
325  m_running.Remove(process);
326 
327  if ( m_running.IsEmpty() )
328  {
329  // we don't need to get idle events all the time any more
330  m_timerIdleWakeUp.Stop();
331  }
332 }
333 
334 
335 // ----------------------------------------------------------------------------
336 // various helpers
337 // ----------------------------------------------------------------------------
338 
339 void MyExecPanel::AddToOutput(wxInputStream & s)
340 {
341  DEBUG_TRACE("");
342 #if defined __WXGTK__
343  wxTextInputStream ts(s, " \t", wxConvLibc);
344 #else
345  wxTextInputStream ts(s);
346 #endif
347  bool lastCR= false;
348  wxString currLine = m_textctrl->GetRange(m_lastLineStart, m_textctrl->GetLastPosition());
349  while(s.CanRead()) {
350  wxChar c = ts.GetChar();
351  if (c == '\b') {
352  lastCR=false;
353  // backspace
354  if (!currLine.empty()) {
355  if (currLine.Last() != wxChar('\n') )
356  currLine.Trim();
357  }
358  } else if (c == 0x0) {
359  // ignore 0x0 chars
360  continue;
361  } else if (c == 0x0d) {
362  lastCR=true;
363 #ifndef __WXMSW__
364  // back to start of line
365  if (currLine.Last() != wxChar('\n') ) {
366  currLine = currLine.BeforeLast('\n');
367  if(!currLine.empty()) {
368  currLine.Append('\n');
369  }
370  }
371 #endif
372  } else if (c == '\n') {
373  currLine.Append(c);
374  lastCR=false;
375  } else {
376 #ifdef __WXMSW__
377  if (lastCR) {
378  // back to start of line
379  if (currLine.Last() != wxChar('\n') ) {
380  currLine = currLine.BeforeLast('\n');
381  if(!currLine.empty()) {
382  currLine.Append('\n');
383  }
384  }
385  }
386 #endif
387  currLine.Append(c);
388  lastCR=false;
389  }
390  }
391 
392  m_textctrl->Replace(m_lastLineStart, m_textctrl->GetLastPosition(), currLine);
393  size_t lret = currLine.find_last_of(wxChar('\n'));
394  if (lret != wxString::npos && lret>0 && lret+1 < currLine.size()) {
395  m_lastLineStart += lret+1;
396  }
397 }
398 
399 void MyExecPanel::OnTimer(wxTimerEvent& WXUNUSED(event))
400 {
401  size_t count = m_running.GetCount();
402  for ( size_t n = 0; n < count; n++ )
403  {
404  while ( m_running[n]->IsInputAvailable() )
405  {
406  AddToOutput(*(m_running[n]->GetInputStream()));
407  };
408  while ( m_running[n]->IsErrorAvailable() )
409  {
410  AddToOutput(*(m_running[n]->GetErrorStream()));
411  };
412  };
413 
414 // Slightly reworked fix for BUG_2075064
415 #if defined __WXMAC__ && defined __ppc__
416  int osVersionMajor;
417  int osVersionMinor;
418 
419  int os = wxGetOsVersion(&osVersionMajor, &osVersionMinor);
420 
421  cerr << "osVersionCheck: os is " << os << "\n" << endl;
422  cerr << "osVersionCheck: osVersionMajor = " << osVersionMajor << endl;
423  cerr << "osVersionCheck: osVersionMinor = " << osVersionMinor << endl;
424 
425  if ((osVersionMajor == 0x10) && (osVersionMinor >= 0x50))
426  {
427  cerr << "osVersionCheck: Leopard loop 2" << endl;
428  if(m_pidLast)
429  {
430  if(kill((pid_t)m_pidLast,0)!=0) //if not pid exists
431  {
432  DEBUG_DEBUG("Found terminated process: " << (pid_t)m_pidLast)
433 
434  // probably should clean up the wxProcess object which was newed when the process was launched.
435  // for now, nevermind the tiny memory leak... it's a hack to workaround the bug anyway
436 
437  //notify dialog that it's finished.
438  if (this->GetParent()) {
439  wxProcessEvent event( wxID_ANY, m_pidLast, 0); // assume 0 exit code
440  event.SetEventObject( this );
441  DEBUG_TRACE("Sending wxProcess event");
442  this->GetParent()->ProcessEvent( event );
443  }
444  }
445  }
446  }
447  else
448  {
449  cerr << "osVersionCheck: Tiger loop 2" << endl;
450  }
451 #endif
452 }
453 
454 void MyExecPanel::OnProcessTerminated(MyPipedProcess *process, int pid, int status)
455 {
456  DEBUG_TRACE("process terminated: pid " << pid << " exit code:" << status);
457  // show the rest of the output
458  AddToOutput(*(process->GetInputStream()));
459  AddToOutput(*(process->GetErrorStream()));
460 
461  RemoveAsyncProcess(process);
462 
463  if (m_queue && !m_queue->empty())
464  {
465  // queue has further commands
466  // should we check the exit code?
467  if ((m_checkReturnCode && status == 0) || (!m_checkReturnCode))
468  {
469  if (ExecNextQueue() == 0)
470  {
471  return;
472  };
473  };
474  };
475  {
476  // add elapsed time
477  const long duration = hugin_utils::roundi(m_stopWatch.Time() / 1000.0);
478  if (duration >= 60)
479  {
480  std::ldiv_t result=std::ldiv(duration, 60l);
481  *m_textctrl << "\n" << wxString::Format(_("Process took %ld:%2ld min"), result.quot, result.rem) << "\n";
482  }
483  else
484  {
485  *m_textctrl << "\n" << wxString::Format(_("Process took %ld s"), duration) << "\n";
486  };
487  };
488  // send termination to parent
489  if (this->GetParent())
490  {
491  wxProcessEvent event(wxID_ANY, pid, m_checkReturnCode ? status : 0);
492  event.SetEventObject(this);
493  DEBUG_TRACE("Sending wxProcess event");
494  this->GetParent()->GetEventHandler()->AddPendingEvent(event);
495  // notify parent to hide progress
496  wxCommandEvent event2(EVT_QUEUE_PROGRESS, wxID_ANY);
497  event2.SetInt(-1);
498  this->GetParent()->GetEventHandler()->AddPendingEvent(event2);
499  };
500 }
501 
503 {
504  delete m_textctrl;
505 }
506 
507 bool MyExecPanel::SaveLog(const wxString &filename)
508 {
509  return m_textctrl->SaveFile(filename);
510 };
511 
513 {
514  m_textctrl->SelectAll();
515  m_textctrl->Copy();
516 };
517 
519 {
520  return wxStringTokenize(m_textctrl->GetValue(), "\r\n");
521 };
522 
523 void MyExecPanel::AddString(const wxString& s)
524 {
525  if (!s.IsEmpty())
526  {
527  m_textctrl->AppendText(s + wxT("\n"));
528  m_lastLineStart = m_textctrl->GetLastPosition();
529  };
530 };
531 
532 // ----------------------------------------------------------------------------
533 // MyPipedProcess
534 // ----------------------------------------------------------------------------
535 
536 void MyPipedProcess::OnTerminate(int pid, int status)
537 {
538  DEBUG_DEBUG("Process " << pid << " terminated with return code: " << status);
539  m_parent->OnProcessTerminated(this, pid, status);
540 
541  // we're not needed any more
542  delete this;
543 }
544 
545 // ==============================================================================
546 // MyExecDialog
547 
548 BEGIN_EVENT_TABLE(MyExecDialog, wxDialog)
549  EVT_BUTTON(wxID_CANCEL, MyExecDialog::OnCancel)
550  EVT_END_PROCESS(wxID_ANY, MyExecDialog::OnProcessTerminate)
552 
553 MyExecDialog::MyExecDialog(wxWindow * parent, const wxString& title, const wxPoint& pos, const wxSize& size)
554  : wxDialog(parent, wxID_ANY, title, pos, size, wxRESIZE_BORDER | wxCAPTION | wxCLOSE_BOX | wxSYSTEM_MENU)
555 {
556 
557  wxBoxSizer * topsizer = new wxBoxSizer( wxVERTICAL );
558  m_execPanel = new MyExecPanel(this);
559  m_cancelled = false;
560 
561  topsizer->Add(m_execPanel, 1, wxEXPAND | wxALL, 2);
562 
563  topsizer->Add( new wxButton(this, wxID_CANCEL, _("Cancel")),
564  0, wxALL | wxALIGN_RIGHT, 10);
565 
566 #ifdef __WXMSW__
567  // wxFrame does have a strange background color on Windows..
568  this->SetBackgroundColour(m_execPanel->GetBackgroundColour());
569 #endif
570  SetSizer( topsizer );
571 // topsizer->SetSizeHints( this );
572 }
573 
574 void MyExecDialog::OnProcessTerminate(wxProcessEvent & event)
575 {
576  DEBUG_DEBUG("Process terminated with return code: " << event.GetExitCode());
577  if(wxConfigBase::Get()->Read(wxT("CopyLogToClipboard"), 0l)==1l)
578  {
580  };
581  if (m_cancelled) {
582  EndModal(HUGIN_EXIT_CODE_CANCELLED);
583  } else {
584  EndModal(event.GetExitCode());
585  }
586 }
587 
588 void MyExecDialog::OnCancel(wxCommandEvent& WXUNUSED(event))
589 {
590  DEBUG_DEBUG("Cancel Pressed");
591  m_cancelled = true;
593 }
594 
595 
597 {
598  if (m_execPanel->ExecWithRedirect(cmd) == -1) {
599  return -1;
600  }
601 
602  return ShowModal();
603 }
604 
606 {
607  if (m_execPanel->ExecQueue(queue) == -1)
608  {
609  return -1;
610  }
611  return ShowModal();
612 }
613 
614 void MyExecDialog::AddString(const wxString& s)
615 {
617 };
618 
620  delete m_execPanel;
621 }
622 
623 int MyExecuteCommandOnDialog(wxString command, wxString args, wxWindow* parent,
624  wxString title, bool isQuoted)
625 {
626  if(!isQuoted)
627  {
628  command = hugin_utils::wxQuoteFilename(command);
629  };
630  wxString cmdline = command + wxT(" ") + args;
631  MyExecDialog dlg(parent, title,
632  wxDefaultPosition, wxSize(640, 400));
633 #ifdef __WXMAC__
634  dlg.CentreOnParent();
635 #endif
636  return dlg.ExecWithRedirect(cmdline);
637 }
638 
639 int MyExecuteCommandQueue(HuginQueue::CommandQueue* queue, wxWindow* parent, const wxString& title, const wxString& comment)
640 {
641  MyExecDialog dlg(parent, title, wxDefaultPosition, wxSize(640, 400));
642 #ifdef __WXMAC__
643  dlg.CentreOnParent();
644 #endif
645  if (!comment.IsEmpty())
646  {
647  dlg.AddString(comment);
648  };
649  int returnValue = dlg.ExecQueue(queue);
650  while (!queue->empty())
651  {
652  delete queue->back();
653  queue->pop_back();
654  };
655  delete queue;
656  return returnValue;
657 };
658 
wxDEFINE_EVENT(EVT_QUEUE_PROGRESS, wxCommandEvent)
int MyExecuteCommandOnDialog(wxString command, wxString args, wxWindow *parent, wxString title, bool isQuoted)
execute a single command in own dialog, redirect output to frame and allow canceling ...
int ExecWithRedirect(wxString command)
int MyExecuteCommandQueue(HuginQueue::CommandQueue *queue, wxWindow *parent, const wxString &title, const wxString &comment)
execute all commands in queue with redirection of output to frame and allow canceling the queue will ...
void RemoveAsyncProcess(MyPipedProcess *process)
normal command for queue, processing is stopped if an error occurred in program
Definition: Executor.h:37
virtual wxString GetCommand() const
Definition: Executor.cpp:60
wxArrayString GetLogAsArrayString()
returns the output
int ExecNextQueue()
execute next command in queue
void AddToOutput(wxInputStream &s)
void AddString(const wxString &s)
display the string in the panel
int roundi(T x)
Definition: hugin_math.h:73
void OnCancel(wxCommandEvent &event)
const int HUGIN_EXIT_CODE_CANCELLED
#define DEBUG_TRACE(msg)
Definition: utils.h:67
int ExecQueue(HuginQueue::CommandQueue *queue)
void OnProcessTerminated(MyPipedProcess *process, int pid, int status)
MyProcessesArray m_running
int ExecQueue(HuginQueue::CommandQueue *queue)
END_EVENT_TABLE()
include file for the hugin project
int ExecWithRedirect(wxString command)
void OnTimer(wxTimerEvent &event)
void OnProcessTerminate(wxProcessEvent &event)
virtual void OnTerminate(int pid, int status)
wxExecuteEnv m_executeEnv
wxTextCtrl * m_textctrl
void AddString(const wxString &s)
display the string in the panel
virtual void OnProcessTerminated(MyPipedProcess *process, int pid, int status)=0
include file for the hugin project
wxString GetComment() const
Definition: Executor.cpp:65
void AddAsyncProcess(MyPipedProcess *process)
MyExecPanel * m_execPanel
#define DEBUG_DEBUG(msg)
Definition: utils.h:68
platform/compiler specific stuff.
MyProcessListener * m_parent
void PauseProcess(bool pause=true)
function to pause running process, argument pause defaults to true - to resume, set it to false ...
str wxQuoteFilename(const str &arg)
Quote a filename, so that it is surrounded by &quot;&quot;.
Definition: wxPlatform.h:82
virtual bool CheckReturnCode() const
Definition: Executor.cpp:55
std::vector< NormalCommand * > CommandQueue
Definition: Executor.h:61
void CopyLogToClipboard()
copy the content of the log window into the clipboard
bool SaveLog(const wxString &filename)
save the content of the window into a given log file
HuginQueue::CommandQueue * m_queue