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