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