1 // -*- c-basic-offset: 4 -*-
2 /** @file MyExternalCmdExecDialog.cpp
3 *
4 *  @author Ippei UKAI <ippei_ukai@mac.com>
5 *
6 *  $Id$
7 *
8 *  This is free software; you can redistribute it and/or
9 *  modify it under the terms of the GNU General Public
10 *  License as published by the Free Software Foundation; either
11 *  version 2 of the License, or (at your option) any later version.
12 *
13 *  This software is distributed in the hope that it will be useful,
14 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 *  Lesser General Public License for more details.
17 *
18 *  You should have received a copy of the GNU General Public
19 *  License along with this software. If not, see
20 *  <http://www.gnu.org/licenses/>.
21 *
22 */
23 
24 
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 
40 #ifdef _WIN32
41     #include "wx/dde.h"
42 	#include <windows.h>
43 	#include <tlhelp32.h>	//needed to pause process on windows
44 #else
45 	#include <sys/types.h>
46 	#include <signal.h>	//needed to pause on unix - kill function
47 	#include <unistd.h> //needed to separate the process group of make
48 #endif // _WIN32
49 
50 // Slightly reworked fix for BUG_2075064
51 #ifdef __WXMAC__
52 	#include <iostream>
53 	#include <stdio.h>
54 	#include "wx/utils.h"
55 #endif
56 
57 #include "MyExternalCmdExecDialog.h"
58 #include "hugin/config_defaults.h"
59 
60 // ----------------------------------------------------------------------------
61 // event tables and other macros for wxWidgets
62 // ----------------------------------------------------------------------------
63 
64 wxDEFINE_EVENT(EVT_QUEUE_PROGRESS, wxCommandEvent);
65 
BEGIN_EVENT_TABLE(MyExecPanel,wxPanel)66 BEGIN_EVENT_TABLE(MyExecPanel, wxPanel)
67     EVT_TIMER(wxID_ANY, MyExecPanel::OnTimer)
68 END_EVENT_TABLE()
69 
70 
71 // ============================================================================
72 // implementation
73 // ============================================================================
74 
75 
76 // frame constructor
77 MyExecPanel::MyExecPanel(wxWindow * parent)
78        : wxPanel(parent),
79        m_timerIdleWakeUp(this), m_queue(NULL), m_queueLength(0), m_checkReturnCode(true)
80 {
81     m_pidLast = 0;
82 
83     wxBoxSizer * topsizer = new wxBoxSizer( wxVERTICAL );
84     // create the listbox in which we will show misc messages as they come
85     m_textctrl = new wxTextCtrl(this, wxID_ANY, _T(""), wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE|wxTE_READONLY);
86     m_lastLineStart = 0;
87 
88 #ifdef __WXMAC__
89     wxFont font(10, wxFONTFAMILY_TELETYPE, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
90 #else
91     wxFont font(8, wxFONTFAMILY_TELETYPE, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
92 #endif
93 
94     if ( font.Ok() ) {
95         m_textctrl->SetFont(font);
96     }
97 
98     topsizer->Add(m_textctrl, 1, wxEXPAND | wxALL, 10);
99     SetSizer( topsizer );
100 }
101 
KillProcess()102 void MyExecPanel::KillProcess()
103 {
104     if (m_pidLast) {
105 #ifdef __WXMSW__
106         DEBUG_DEBUG("Killing process " << m_pidLast << " with sigkill");
107         wxKillError rc = wxProcess::Kill(m_pidLast, wxSIGKILL, wxKILL_CHILDREN);
108 #else
109         DEBUG_DEBUG("Killing process " << m_pidLast << " with sigterm");
110         wxKillError rc = wxProcess::Kill(m_pidLast, wxSIGTERM, wxKILL_CHILDREN);
111 #endif
112         if ( rc != wxKILL_OK ) {
113             static const wxChar *errorText[] =
114             {
115                 _T(""), // no error
116                 _T("signal not supported"),
117                 _T("permission denied"),
118                 _T("no such process"),
119                 _T("unspecified error"),
120             };
121 
122             wxLogError(_("Failed to kill process %ld, error %d: %s"),
123                         m_pidLast, rc, errorText[rc]);
124         }
125     }
126 }
127 
128 /**function to pause running process, argument pause defaults to true - to resume, set it to false*/
PauseProcess(bool pause)129 void MyExecPanel::PauseProcess(bool pause)
130 {
131 #ifdef __WXMSW__
132 	HANDLE hProcessSnapshot = NULL;
133 	PROCESSENTRY32 pEntry = {0};
134 	THREADENTRY32 tEntry = {0};
135 
136 	//we take a snapshot of all system processes
137 	hProcessSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
138 
139 	if (hProcessSnapshot == INVALID_HANDLE_VALUE)
140 		wxLogError(_("Error pausing process %ld, code 1"),m_pidLast);
141 	else
142 	{
143 		pEntry.dwSize = sizeof(PROCESSENTRY32);
144 		tEntry.dwSize = sizeof(THREADENTRY32);
145 
146 		//we traverse all processes in the system
147 		if(Process32First(hProcessSnapshot, &pEntry))
148 		{
149 			do
150 			{
151 				//we pause threads of the main (make) process and its children (nona,enblend...)
152 				if((pEntry.th32ProcessID == m_pidLast) || (pEntry.th32ParentProcessID == m_pidLast))
153 				{
154 					//we take a snapshot of all system threads
155 					HANDLE hThreadSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
156 					if (hThreadSnapshot == INVALID_HANDLE_VALUE)
157 						wxLogError(_("Error pausing process %ld, code 2"),m_pidLast);
158 
159 					//we traverse all threads
160 					if(Thread32First(hThreadSnapshot, &tEntry))
161 					{
162 						do
163 						{
164 							//we find all threads of the process
165 							if(tEntry.th32OwnerProcessID == pEntry.th32ProcessID)
166 							{
167 								HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, tEntry.th32ThreadID);
168 								if(pause)
169 									SuspendThread(hThread);
170 								else
171 									ResumeThread(hThread);
172 								CloseHandle(hThread);
173 							}
174 						}while(Thread32Next(hThreadSnapshot, &tEntry));
175 					}
176 					CloseHandle(hThreadSnapshot);
177 				}
178 			}while(Process32Next(hProcessSnapshot, &pEntry));
179 		}
180 	}
181 	CloseHandle(hProcessSnapshot);
182 #else
183 	//send the process group a pause/cont signal
184 	if(pause)
185 		killpg(m_pidLast,SIGSTOP);
186 	else
187 		killpg(m_pidLast,SIGCONT);
188 #endif //__WXMSW__
189 }
190 
ContinueProcess()191 void MyExecPanel::ContinueProcess()
192 {
193 	PauseProcess(false);
194 }
195 
GetPid()196 long MyExecPanel::GetPid()
197 {
198 	return m_pidLast;
199 }
200 
ExecWithRedirect(wxString cmd)201 int MyExecPanel::ExecWithRedirect(wxString cmd)
202 {
203     if (!cmd)
204         return -1;
205 
206     // Slightly reworked fix for BUG_2075064
207 #if defined __WXMAC__ && defined __ppc__
208     int osVersionMajor;
209     int osVersionMinor;
210 
211     int os = wxGetOsVersion(&osVersionMajor, &osVersionMinor);
212 
213     cout << "osVersionCheck: os is " << os << "\n"  << endl;
214     cout << "osVersionCheck: osVersionMajor = " << osVersionMajor << endl;
215     cout << "osVersionCheck: osVersionMinor = " << osVersionMinor << endl;
216     if ((osVersionMajor == 0x10) && (osVersionMinor >= 0x50))
217     {
218         //let the child process exit without becoming zombie
219         //may do some harm to internal handling by wxWidgets, but hey it's not working anyway
220         signal(SIGCHLD,SIG_IGN);
221         cout <<  "osVersionCheck: Leopard loop 1" << endl;
222     }
223     else
224     {
225         cout <<  "osVersionCheck: Tiger loop 1" << endl;
226     }
227 #endif
228 
229     MyPipedProcess *process = new MyPipedProcess(this, cmd);
230     m_pidLast = wxExecute(cmd, wxEXEC_ASYNC | wxEXEC_MAKE_GROUP_LEADER, process, &m_executeEnv);
231     if ( m_pidLast == 0 )
232     {
233         wxLogError(_T("Execution of '%s' failed."), cmd.c_str());
234         delete process;
235         return -1;
236     }
237     else
238     {
239         AddAsyncProcess(process);
240 #ifndef _WIN32
241 		//on linux we put the new process into a separate group,
242 		//so it can be paused with all it's children at the same time
243 		setpgid(m_pidLast,m_pidLast);
244 #endif
245     }
246     return 0;
247 }
248 
ExecQueue(HuginQueue::CommandQueue * queue)249 int MyExecPanel::ExecQueue(HuginQueue::CommandQueue* queue)
250 {
251     wxConfigBase* config = wxConfigBase::Get();
252     const long threads = config->Read(wxT("/output/NumberOfThreads"), 0l);
253     // read all current environment variables
254     wxGetEnvMap(&m_executeEnv.env);
255     // now modify some variables before passing them to wxExecute
256     if (threads > 0)
257     {
258         wxString s;
259         s << threads;
260         m_executeEnv.env["OMP_NUM_THREADS"] = s;
261     };
262     // set temp dir
263     wxString tempDir = config->Read(wxT("tempDir"), wxT(""));
264     if (!tempDir.IsEmpty())
265     {
266 #ifdef UNIX_LIKE
267         m_executeEnv.env["TMPDIR"] = tempDir;
268 #else
269         m_executeEnv.env["TMP"] = tempDir;
270 #endif
271     };
272     m_queue = queue;
273     m_queueLength = queue->size() + 1;
274     if (m_queue->empty())
275     {
276         return 0;
277     };
278     return ExecNextQueue();
279 };
280 
281 /** execute next command in queue */
ExecNextQueue()282 int MyExecPanel::ExecNextQueue()
283 {
284     if (m_queue)
285     {
286         // get next command
287         HuginQueue::NormalCommand* cmd = m_queue->front();
288         const wxString cmdString = cmd->GetCommand();
289         // get comment, append line break and display comment in panel
290         AddString(cmd->GetComment());
291         m_checkReturnCode = cmd->CheckReturnCode();
292         // delete command from queue
293         delete cmd;
294         m_queue->erase(m_queue->begin());
295         // notify parent
296         if (this->GetParent())
297         {
298             wxCommandEvent event(EVT_QUEUE_PROGRESS, wxID_ANY);
299             event.SetInt(hugin_utils::roundi((m_queueLength - m_queue->size()) * 100.0f / m_queueLength));
300             this->GetParent()->GetEventHandler()->AddPendingEvent(event);
301         };
302         // now execute command
303         return ExecWithRedirect(cmdString);
304     }
305     else
306     {
307         return -1;
308     };
309 };
310 
AddAsyncProcess(MyPipedProcess * process)311 void MyExecPanel::AddAsyncProcess(MyPipedProcess *process)
312 {
313     if ( m_running.IsEmpty() )
314     {
315         // we want to start getting the timer events to ensure that a
316         // steady stream of idle events comes in -- otherwise we
317         // wouldn't be able to poll the child process input
318         m_timerIdleWakeUp.Start(200);
319     }
320     //else: the timer is already running
321 
322     m_running.Add(process);
323 }
324 
325 
RemoveAsyncProcess(MyPipedProcess * process)326 void MyExecPanel::RemoveAsyncProcess(MyPipedProcess *process)
327 {
328     m_running.Remove(process);
329 
330     if ( m_running.IsEmpty() )
331     {
332         // we don't need to get idle events all the time any more
333         m_timerIdleWakeUp.Stop();
334     }
335 }
336 
337 
338 // ----------------------------------------------------------------------------
339 // various helpers
340 // ----------------------------------------------------------------------------
341 
AddToOutput(wxInputStream & s)342 void MyExecPanel::AddToOutput(wxInputStream & s)
343 {
344     DEBUG_TRACE("");
345 #if defined __WXGTK__
346     wxTextInputStream ts(s, " \t", wxConvLibc);
347 #else
348     wxTextInputStream ts(s);
349 #endif
350     bool lastCR= false;
351     wxString currLine = m_textctrl->GetRange(m_lastLineStart, m_textctrl->GetLastPosition());
352     while(s.CanRead()) {
353         wxChar c = ts.GetChar();
354         if (c == '\b') {
355             lastCR=false;
356             // backspace
357             if (!currLine.empty()) {
358                 if (currLine.Last() != wxChar('\n') )
359                     currLine.Trim();
360             }
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 > 0 && lret+1 < currLine.size()) {
395         m_lastLineStart += lret+1;
396     }
397 }
398 
OnTimer(wxTimerEvent & WXUNUSED (event))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 
OnProcessTerminated(MyPipedProcess * process,int pid,int status)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     // send termination to parent
476     if (this->GetParent())
477     {
478         wxProcessEvent event(wxID_ANY, pid, m_checkReturnCode ? status : 0);
479         event.SetEventObject(this);
480         DEBUG_TRACE("Sending wxProcess event");
481         this->GetParent()->GetEventHandler()->AddPendingEvent(event);
482         // notify parent to hide progress
483         wxCommandEvent event2(EVT_QUEUE_PROGRESS, wxID_ANY);
484         event2.SetInt(-1);
485         this->GetParent()->GetEventHandler()->AddPendingEvent(event2);
486     };
487 }
488 
~MyExecPanel()489 MyExecPanel::~MyExecPanel()
490 {
491     delete m_textctrl;
492 }
493 
SaveLog(const wxString & filename)494 bool MyExecPanel::SaveLog(const wxString &filename)
495 {
496     return m_textctrl->SaveFile(filename);
497 };
498 
CopyLogToClipboard()499 void MyExecPanel::CopyLogToClipboard()
500 {
501     m_textctrl->SelectAll();
502     m_textctrl->Copy();
503 };
504 
GetLogAsArrayString()505 wxArrayString MyExecPanel::GetLogAsArrayString()
506 {
507     wxArrayString output;
508     for (size_t i = 0; i < m_textctrl->GetNumberOfLines(); ++i)
509     {
510         output.push_back(m_textctrl->GetLineText(i));
511     }
512     return output;
513 };
514 
AddString(const wxString & s)515 void MyExecPanel::AddString(const wxString& s)
516 {
517     if (!s.IsEmpty())
518     {
519         m_textctrl->AppendText(s + wxT("\n"));
520         m_lastLineStart = m_textctrl->GetLastPosition();
521     };
522 };
523 
524 // ----------------------------------------------------------------------------
525 // MyPipedProcess
526 // ----------------------------------------------------------------------------
527 
OnTerminate(int pid,int status)528 void MyPipedProcess::OnTerminate(int pid, int status)
529 {
530     DEBUG_DEBUG("Process " << pid << " terminated with return code: " << status);
531     m_parent->OnProcessTerminated(this, pid, status);
532 
533     // we're not needed any more
534     delete this;
535 }
536 
537 // ==============================================================================
538 // MyExecDialog
539 
BEGIN_EVENT_TABLE(MyExecDialog,wxDialog)540 BEGIN_EVENT_TABLE(MyExecDialog, wxDialog)
541     EVT_BUTTON(wxID_CANCEL,  MyExecDialog::OnCancel)
542     EVT_END_PROCESS(wxID_ANY, MyExecDialog::OnProcessTerminate)
543 END_EVENT_TABLE()
544 
545 MyExecDialog::MyExecDialog(wxWindow * parent, const wxString& title, const wxPoint& pos, const wxSize& size)
546     : wxDialog(parent, wxID_ANY, title, pos, size, wxRESIZE_BORDER | wxCAPTION | wxCLOSE_BOX | wxSYSTEM_MENU)
547 {
548 
549     wxBoxSizer * topsizer = new wxBoxSizer( wxVERTICAL );
550     m_execPanel = new MyExecPanel(this);
551     m_cancelled = false;
552 
553     topsizer->Add(m_execPanel, 1, wxEXPAND | wxALL, 2);
554 
555     topsizer->Add( new wxButton(this, wxID_CANCEL, _("Cancel")),
556                    0, wxALL | wxALIGN_RIGHT, 10);
557 
558 #ifdef __WXMSW__
559     // wxFrame does have a strange background color on Windows..
560     this->SetBackgroundColour(m_execPanel->GetBackgroundColour());
561 #endif
562     SetSizer( topsizer );
563 //    topsizer->SetSizeHints( this );
564 }
565 
OnProcessTerminate(wxProcessEvent & event)566 void MyExecDialog::OnProcessTerminate(wxProcessEvent & event)
567 {
568     DEBUG_DEBUG("Process terminated with return code: " << event.GetExitCode());
569     if(wxConfigBase::Get()->Read(wxT("CopyLogToClipboard"), 0l)==1l)
570     {
571         m_execPanel->CopyLogToClipboard();
572     };
573     if (m_cancelled) {
574         EndModal(HUGIN_EXIT_CODE_CANCELLED);
575     } else {
576         EndModal(event.GetExitCode());
577     }
578 }
579 
OnCancel(wxCommandEvent & WXUNUSED (event))580 void MyExecDialog::OnCancel(wxCommandEvent& WXUNUSED(event))
581 {
582     DEBUG_DEBUG("Cancel Pressed");
583     m_cancelled = true;
584     m_execPanel->KillProcess();
585 }
586 
587 
ExecWithRedirect(wxString cmd)588 int MyExecDialog::ExecWithRedirect(wxString cmd)
589 {
590     if (m_execPanel->ExecWithRedirect(cmd) == -1) {
591         return -1;
592     }
593 
594     return ShowModal();
595 }
596 
ExecQueue(HuginQueue::CommandQueue * queue)597 int MyExecDialog::ExecQueue(HuginQueue::CommandQueue* queue)
598 {
599     if (m_execPanel->ExecQueue(queue) == -1)
600     {
601         return -1;
602     }
603     return ShowModal();
604 }
605 
AddString(const wxString & s)606 void MyExecDialog::AddString(const wxString& s)
607 {
608     m_execPanel->AddString(s);
609 };
610 
~MyExecDialog()611 MyExecDialog::~MyExecDialog() {
612     delete m_execPanel;
613 }
614 
MyExecuteCommandOnDialog(wxString command,wxString args,wxWindow * parent,wxString title,bool isQuoted)615 int MyExecuteCommandOnDialog(wxString command, wxString args, wxWindow* parent,
616                              wxString title, bool isQuoted)
617 {
618     if(!isQuoted)
619     {
620         command = hugin_utils::wxQuoteFilename(command);
621     };
622     wxString cmdline = command + wxT(" ") + args;
623     MyExecDialog dlg(parent, title,
624                      wxDefaultPosition, wxSize(640, 400));
625 #ifdef __WXMAC__
626     dlg.CentreOnParent();
627 #endif
628     return dlg.ExecWithRedirect(cmdline);
629 }
630 
MyExecuteCommandQueue(HuginQueue::CommandQueue * queue,wxWindow * parent,const wxString & title,const wxString & comment)631 int MyExecuteCommandQueue(HuginQueue::CommandQueue* queue, wxWindow* parent, const wxString& title, const wxString& comment)
632 {
633     MyExecDialog dlg(parent, title, wxDefaultPosition, wxSize(640, 400));
634 #ifdef __WXMAC__
635     dlg.CentreOnParent();
636 #endif
637     if (!comment.IsEmpty())
638     {
639         dlg.AddString(comment);
640     };
641     int returnValue = dlg.ExecQueue(queue);
642     while (!queue->empty())
643     {
644         delete queue->back();
645         queue->pop_back();
646     };
647     delete queue;
648     return returnValue;
649 };
650 
651