File: ExecuteInDialog.cpp

package info (click to toggle)
4pane 7.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 10,044 kB
  • sloc: cpp: 38,264; sh: 4,110; ansic: 3,620; makefile: 157; xml: 27
file content (224 lines) | stat: -rw-r--r-- 9,084 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
/////////////////////////////////////////////////////////////////////////////
// Name:       ExecuteInDialog.cpp
// Purpose:    Displays exec output in a dialog
// Part of:    4Pane
// Author:     David Hart
// Copyright:  (c) 2019 David Hart
// Licence:    GPL v3
/////////////////////////////////////////////////////////////////////////////
#include "wx/wxprec.h" 

#include "wx/xrc/xmlres.h"

#include "Externs.h"
#include "ExecuteInDialog.h"

DisplayProcess::DisplayProcess(CommandDisplay* display)   :  wxProcess(wxPROCESS_REDIRECT), m_WasCancelled(false), m_parent(display)
{
m_text = m_parent->text;
}

bool DisplayProcess::HasInput()    // The following methods are adapted from the exec sample.  This one manages the stream redirection
{
char c;

bool hasInput = false;
    // The original used wxTextInputStream to read a line at a time.  Fine, except when there was no \n, whereupon the thing would hang
    // Instead, read the stream (which may occasionally contain non-ascii bytes e.g. from g++) into a memorybuffer, then to a wxString
while (IsInputAvailable())                                  // If there's std input
  { wxMemoryBuffer buf;
    do
      { c = GetInputStream()->GetC();                       // Get a char from the input
        if (GetInputStream()->Eof()) break;                 // Check we've not just overrun
        
        buf.AppendByte(c);
        if (c==wxT('\n')) break;                            // If \n, break to print the line
      }
      while (IsInputAvailable());                           // Unless \n, loop to get another char
    wxString line((const char*)buf.GetData(), wxConvUTF8, buf.GetDataLen()); // Convert the line to utf8

    m_parent->AddInput(line);                               // Either there's a full line in 'line', or we've run out of input. Either way, print it

    hasInput = true;
  }

while (IsErrorAvailable())
  { wxMemoryBuffer buf;
    do
      { c = GetErrorStream()->GetC();
        if (GetErrorStream()->Eof()) break;
        
        buf.AppendByte(c);
        if (c==wxT('\n')) break;
      }
      while (IsErrorAvailable());
    wxString line((const char*)buf.GetData(), wxConvUTF8, buf.GetDataLen());

    m_parent->AddInput(line);

    hasInput = true;
  }

return hasInput;
}

void DisplayProcess::OnTerminate(int pid, int status)  // When the subprocess has finished, show the rest of the output, then an exit message
{
while (HasInput());

wxString exitmessage;
if (!status) exitmessage = _("Success\n");
 else
   if (!AlreadyCancelled()) exitmessage = _("Process failed\n");

*m_text << exitmessage;

m_parent->exitstatus = status;                                // Tell the dlg what the exit status was, in case caller is interested
m_parent->OnProcessTerminated(this);
}

void DisplayProcess::OnKillProcess()
{
if (!m_pid) return;

  // Take the pid we stored earlier & kill it.  SIGTERM seems to work now we use wxEXEC_MAKE_GROUP_LEADER and wxKILL_CHILDREN but first send a SIGHUP because of 
  // the stream redirection (else if the child is saying "Are you sure?" and waiting for input, SIGTERM fails in practice -> zombie processes and a non-deleted wxProcess)
Kill(m_pid, wxSIGHUP, wxKILL_CHILDREN);

wxKillError result = Kill(m_pid, wxSIGTERM, wxKILL_CHILDREN);
if (result == wxKILL_OK)
  { (*m_text) << _("Process successfully aborted\n");
    m_parent->exitstatus = 1;                                 // Tell the dlg we exited abnormally, in case caller is interested
    m_parent->OnProcessKilled(this);                          // Go thru the termination process, but detaching rather than deleting
  }
 else
  { (*m_text) << _("SIGTERM failed\nTrying SIGKILL\n");       // If we could be bothered, result holds the index in an enum that'd tell us why it failed
    result = Kill(m_pid, wxSIGKILL, wxKILL_CHILDREN);         // Fight dirty
    if (result == wxKILL_OK)
      { (*m_text) << _("Process successfully killed\n");
        m_parent->exitstatus = 1;
        m_parent->OnProcessKilled(this);
      }
     else
      { (*m_text) << _("Sorry, Cancel failed\n");
        m_parent->OnProcessKilled(this);                      // We need to do this anyway, otherwise the dialog can't be closed
      }
  }

m_WasCancelled = true;
}

//-----------------------------------------------------------------------------------------------------------------------
void CommandDisplay::Init(wxString& command)
{
m_running = NULL;                                           // Null to show there isn't currently a running process
m_timerIdleWakeUp.SetOwner(this, wakeupidle);               // Initialise the process timer
shutdowntimer.SetOwner(this, endmodal);                     //   & the shutdown timer
text = (wxTextCtrl*)FindWindow(wxT("text"));

RunCommand(command);                                        // Actually run the process. This has to happen before ShowModal()
}

void CommandDisplay::RunCommand(wxString& command) // Do the Process/Execute things to run the command
{
if (command.IsEmpty()) return;

DisplayProcess* process = new DisplayProcess(this);         // Make a new Process, the sort with built-in redirection
long pid = wxExecute(command, wxEXEC_ASYNC | wxEXEC_MAKE_GROUP_LEADER, process); // Try & run the command.  pid of 0 means failed

if (!pid)
  { wxLogError(_("Execution of '%s' failed."), command.c_str());
    delete process;
  }
 else
  { process->SetPid(pid);                                   // Store the pid in case Cancel is pressed
    AddAsyncProcess(process);                               // Start the timer to poll for output
  }
}

void CommandDisplay::AddInput(wxString input)    // Displays input received from the running Process
{
if (input.IsEmpty()) return;

text->AppendText(input);                                    // Add string to textctrl
}

void CommandDisplay::OnCancel(wxCommandEvent& event)
{
if (m_running)    m_running->OnKillProcess();               // If there's a running process, murder it
 else                                                       // If not, the user just pressed Close
  { shutdowntimer.Stop();                                   // Stop the shutdowntimer, in case it was running (to avoid 2 coinciding EndModals)
     wxDialog::EndModal(!exitstatus);
  }
}

void CommandDisplay::AddAsyncProcess(DisplayProcess *process)  // Starts up the timer that generates wake-ups for OnIdle
{ 
if (m_running==NULL)                                        // If it's not null, there's already a running process
  { m_timerIdleWakeUp.Start(100);                           // Tell the timer to create idle events every 1/10th sec
    m_running = process;                                    // Store the process
  }
}

void CommandDisplay::OnProcessTerminated(DisplayProcess *process)  // Stops everything, deletes process
{
m_timerIdleWakeUp.Stop();                                   // Stop the process timer

delete process;
m_running = NULL;

SetupClose();                                               // Relabels Cancel button as Close, & starts the endmodal timer if desired
}

void CommandDisplay::OnProcessKilled(DisplayProcess *process)  // Similar to OnProcessTerminated but Detaches rather than deletes, following a Cancel
{
m_timerIdleWakeUp.Stop();

process->Detach();
m_running = NULL;

SetupClose();
}

void CommandDisplay::SetupClose()  // Relabels Cancel button as Close, & starts the endmodal timer if desired
{
if (((wxCheckBox*)FindWindow(wxT("Autoclose")))->IsChecked())
  shutdowntimer.Start(exitstatus ? AUTOCLOSE_FAILTIMEOUT : AUTOCLOSE_TIMEOUT, wxTIMER_ONE_SHOT);

((wxButton*)FindWindow(wxT("wxID_CANCEL")))->SetLabel(_("Close"));
}

void CommandDisplay::OnProcessTimer(wxTimerEvent& WXUNUSED(event))  // When the timer fires, it wakes up the OnIdle method
{
wxWakeUpIdle();
}

void CommandDisplay::OnEndmodalTimer(wxTimerEvent& WXUNUSED(event))  // Called when the one-shot shutdown timer fires
{
wxDialog::EndModal(!exitstatus);
}

void CommandDisplay::OnIdle(wxIdleEvent& event)  // Made to happen by the process timer firing; runs HasInput()
{
if (m_running==NULL) return;                                // Ensure there IS a process

m_running->HasInput();                                      // Check for input from the process
}

void CommandDisplay::OnAutocloseCheckBox(wxCommandEvent& event)
{
shutdowntimer.Stop();                                       // Either way, start with stop

if (event.IsChecked()                                       // If the autoclose checkbox was unchecked, & has just been checked
    && m_running==NULL)                                     // If the process has finished/aborted, start the shutdown timer
  shutdowntimer.Start(exitstatus ? AUTOCLOSE_FAILTIMEOUT : AUTOCLOSE_TIMEOUT, wxTIMER_ONE_SHOT);
}

BEGIN_EVENT_TABLE(CommandDisplay, wxDialog)
  EVT_CHECKBOX(XRCID("Autoclose"), CommandDisplay::OnAutocloseCheckBox)
  EVT_IDLE(CommandDisplay::OnIdle)
  EVT_BUTTON(wxID_CANCEL, CommandDisplay::OnCancel)
  EVT_TIMER(wakeupidle, CommandDisplay::OnProcessTimer)
  EVT_TIMER(endmodal, CommandDisplay::OnEndmodalTimer)
END_EVENT_TABLE()