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