1 // Command window object
2 // Executes a command and returns the results in the command window
3 // Close button to close the window (but not kill the child process)
4 // Cancel button to kill the child process (but not close the window)
5 // The object deletes itself when the close button is pressed
6 // The command window can be a free-floating window or can be
7 // a window which will always float over the owner window
8
9 #include "config.h"
10 #include "i18n.h"
11
12 #include <signal.h>
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <sys/wait.h>
16 #include <sys/ioctl.h>
17 #include <unistd.h>
18 #include <fcntl.h>
19 #include <errno.h>
20
21 #include <fx.h>
22
23 #include "xfedefs.h"
24 #include "icons.h"
25 #include "MessageBox.h"
26 #include "CommandWindow.h"
27
28
29 // Main window
30 extern FXMainWindow* mainWindow;
31
32
33 // Map
34 FXDEFMAP(CommandWindow) CommandWindowMap[] =
35 {
36 FXMAPFUNC(SEL_COMMAND, CommandWindow::ID_CLOSE, CommandWindow::onCmdClose),
37 FXMAPFUNC(SEL_COMMAND, CommandWindow::ID_KILLPROCESS, CommandWindow::onCmdKillProcess),
38 FXMAPFUNC(SEL_UPDATE, CommandWindow::ID_KILLPROCESS, CommandWindow::onUpdKillProcess),
39 FXMAPFUNC(SEL_UPDATE, CommandWindow::ID_CLOSE, CommandWindow::onUpdClose),
40 FXMAPFUNC(SEL_CHORE, CommandWindow::ID_WATCHPROCESS, CommandWindow::onWatchProcess),
41 };
42
43
44 // Object implementation
FXIMPLEMENT(CommandWindow,DialogBox,CommandWindowMap,ARRAYNUMBER (CommandWindowMap))45 FXIMPLEMENT(CommandWindow, DialogBox, CommandWindowMap, ARRAYNUMBER(CommandWindowMap))
46
47
48 // Construct window which will always float over the owner window
49 CommandWindow::CommandWindow(FXWindow* owner, const FXString& name, FXString strcmd, int nblines, int nbcols) :
50 DialogBox(owner, name, DECOR_TITLE|DECOR_BORDER|DECOR_RESIZE|DECOR_MAXIMIZE|DECOR_CLOSE, 0, 0, 0, 0, 6, 6, 6, 6, 4, 4)
51 {
52 // Get command to execute
53 command = strcmd;
54
55 // Bottom part
56 FXHorizontalFrame* buttonbox = new FXHorizontalFrame(this, LAYOUT_SIDE_BOTTOM|LAYOUT_FILL_X|PACK_UNIFORM_WIDTH);
57 new FXButton(buttonbox, _("Cl&ose"), NULL, this, ID_CLOSE, BUTTON_DEFAULT|LAYOUT_RIGHT|FRAME_RAISED|FRAME_THICK, 0, 0, 0, 0, 20, 20, 5, 5);
58 FXButton* cancelbutton = new FXButton(buttonbox, _("&Cancel"), NULL, this, ID_KILLPROCESS, BUTTON_INITIAL|BUTTON_DEFAULT|LAYOUT_RIGHT|FRAME_RAISED|FRAME_THICK, 0, 0, 0, 0, 20, 20, 5, 5);
59
60 // Text part
61 FXHorizontalFrame* textbox = new FXHorizontalFrame(this, LAYOUT_SIDE_TOP|LAYOUT_FILL_X|LAYOUT_FILL_Y|FRAME_SUNKEN, 0, 0, 0, 0, 0, 0, 0, 0);
62 text = new FXText(textbox, NULL, 0, TEXT_READONLY|TEXT_WORDWRAP|LAYOUT_FILL_X|LAYOUT_FILL_Y);
63 text->setVisibleRows(nblines);
64 text->setVisibleColumns(nbcols);
65
66 appendText(_("Please wait...\n\n"));
67
68 cancelbutton->setFocus();
69
70 // Initialize variables
71 pid = -1;
72 killed = false;
73 closed = false;
74 }
75
76
77 // Construct free-floating window
CommandWindow(FXApp * a,const FXString & name,FXString strcmd,int nblines,int nbcols)78 CommandWindow::CommandWindow(FXApp* a, const FXString& name, FXString strcmd, int nblines, int nbcols) :
79 DialogBox(a, name, DECOR_TITLE|DECOR_BORDER|DECOR_RESIZE|DECOR_MAXIMIZE|DECOR_MINIMIZE|DECOR_CLOSE, 0, 0, 0, 0, 6, 6, 6, 6, 4, 4)
80 {
81 // Get command to execute
82 command = strcmd;
83
84 // Bottom part
85 FXHorizontalFrame* buttonbox = new FXHorizontalFrame(this, LAYOUT_SIDE_BOTTOM|LAYOUT_FILL_X|PACK_UNIFORM_WIDTH);
86 new FXButton(buttonbox, _("Cl&ose"), NULL, this, ID_CLOSE, BUTTON_DEFAULT|LAYOUT_RIGHT|FRAME_RAISED|FRAME_THICK, 0, 0, 0, 0, 20, 20, 5, 5);
87 FXButton* cancelbutton = new FXButton(buttonbox, _("&Cancel"), NULL, this, ID_KILLPROCESS, BUTTON_INITIAL|BUTTON_DEFAULT|LAYOUT_RIGHT|FRAME_RAISED|FRAME_THICK, 0, 0, 0, 0, 20, 20, 5, 5);
88
89 // Text part
90 FXHorizontalFrame* textbox = new FXHorizontalFrame(this, LAYOUT_SIDE_TOP|LAYOUT_FILL_X|LAYOUT_FILL_Y|FRAME_SUNKEN, 0, 0, 0, 0, 0, 0, 0, 0);
91 text = new FXText(textbox, NULL, 0, TEXT_READONLY|TEXT_WORDWRAP|LAYOUT_FILL_X|LAYOUT_FILL_Y);
92 text->setVisibleRows(nblines);
93 text->setVisibleColumns(nbcols);
94
95 appendText(_("Please wait...\n\n"));
96
97 cancelbutton->setFocus();
98
99 // Initialize variables
100 pid = -1;
101 killed = false;
102 closed = false;
103 }
104
105
106 // Make window
create()107 void CommandWindow::create()
108 {
109 // Set text font
110 FXString fontspec;
111
112 fontspec = getApp()->reg().readStringEntry("SETTINGS", "textfont", DEFAULT_TEXT_FONT);
113 if (!fontspec.empty())
114 {
115 FXFont* font = new FXFont(getApp(), fontspec);
116 font->create();
117 text->setFont(font);
118 }
119
120 DialogBox::create();
121 show(PLACEMENT_OWNER);
122
123 // Execute command
124 execCmd(command.text());
125 }
126
127
128 // Kill process when clicking on the cancel button
onCmdKillProcess(FXObject *,FXSelector,void *)129 long CommandWindow::onCmdKillProcess(FXObject*, FXSelector, void*)
130 {
131 kill((-1*pid), SIGTERM); // Kills the process group
132 killed = true;
133 return(0);
134 }
135
136
137 // Update cancel button
onUpdKillProcess(FXObject * sender,FXSelector,void *)138 long CommandWindow::onUpdKillProcess(FXObject* sender, FXSelector, void*)
139 {
140 FXButton* btn = (FXButton*)sender;
141
142 if (!getApp()->hasChore(this, ID_WATCHPROCESS))
143 {
144 btn->disable();
145 }
146 else
147 {
148 btn->enable();
149 }
150 return(1);
151 }
152
153
154 // Update close button
onUpdClose(FXObject * sender,FXSelector,void *)155 long CommandWindow::onUpdClose(FXObject* sender, FXSelector, void*)
156 {
157 FXButton* btn = (FXButton*)sender;
158
159 if (!getApp()->hasChore(this, ID_WATCHPROCESS))
160 {
161 btn->enable();
162 }
163 else
164 {
165 btn->disable();
166 }
167 return(1);
168 }
169
170
171 // Execute a command and capture its output
execCmd(FXString command)172 int CommandWindow::execCmd(FXString command)
173 {
174 // Open pipes to communicate with child process
175 if (pipe(pipes) == -1)
176 {
177 return(-1);
178 }
179
180 // Create child process
181 pid = fork();
182 if (pid == -1)
183 {
184 fprintf(stderr, _("Error: Fork failed: %s\n"), strerror(errno));
185 return(-1);
186 }
187 if (pid == 0) // Child
188 {
189 char* args[4];
190 int ret1 = dup2(pipes[0], STDIN_FILENO); // Use the pipes as the new channels
191 int ret2 = dup2(pipes[1], STDOUT_FILENO); // (where stdout and stderr
192 int ret3 = dup2(pipes[1], STDERR_FILENO); // go to the same pipe!).
193
194 if ((ret1 < 0) || (ret2 < 0) || (ret3 < 0))
195 {
196 int errcode = errno;
197 if (errcode)
198 {
199 MessageBox::error(this, BOX_OK, _("Error"), _("Can't duplicate pipes: %s"), strerror(errcode));
200 }
201 else
202 {
203 MessageBox::error(this, BOX_OK, _("Error"), _("Can't duplicate pipes"));
204 }
205
206 return(-1);
207 }
208
209 args[0] = (char*)"sh"; // Setup arguments
210 args[1] = (char*)"-vc"; // to run command (option -v to display the command to execute)
211 args[2] = (char*)command.text(); // in a shell in
212 args[3] = NULL; // a new process.
213 setpgid(0, 0); // Allows to kill the whole group
214 execvp(args[0], args); // Start a new process which will execute the command.
215 _exit(EXIT_FAILURE); // We'll get here only if an error occurred.
216 }
217 else // Parent
218 {
219 // Make sure we get called so we can check when child has finished
220 getApp()->addChore(this, ID_WATCHPROCESS);
221 }
222 return(0);
223 }
224
225
226 // Watch progress of child process
onWatchProcess(FXObject *,FXSelector,void *)227 long CommandWindow::onWatchProcess(FXObject*, FXSelector, void*)
228 {
229 char buf[1024];
230 int nread;
231
232 if (closed)
233 {
234 // The close button was pressed : just close the pipes
235 // and delete the object
236
237 // Close pipes
238 ::close(pipes[0]);
239 ::close(pipes[1]);
240
241 // Object deletes itself!
242 delete this;
243 }
244
245 else if ((waitpid(pid, NULL, WNOHANG) == 0))
246 {
247 // Child is still running, just wait
248 getApp()->addChore(this, ID_WATCHPROCESS);
249
250 // Read data from the running child (first, set I-O to non-blocking)
251 int pflags;
252 if ((pflags = fcntl(pipes[0], F_GETFL)) >= 0)
253 {
254 pflags |= O_NONBLOCK;
255 if (fcntl(pipes[0], F_SETFL, pflags) >= 0)
256 {
257 // Now read the data from the pipe
258 while ((nread = read(pipes[0], buf, sizeof(buf)-1)) > 0)
259 {
260 buf[nread] = '\0';
261 // Remove backspace characters, if any
262 FXString strbuf = buf;
263 strbuf = strbuf.substitute("\b", ".");
264 text->appendText(strbuf.text(), strlen(strbuf.text()));
265 scrollToLastLine();
266 if (nread < (int)(sizeof(buf)-1))
267 {
268 break;
269 }
270 }
271 }
272 }
273 }
274
275 else
276 {
277 // Child has finished.
278 // Read data from the finished child
279 while ((nread = read(pipes[0], buf, sizeof(buf)-1)) > 0)
280 {
281 buf[nread] = '\0';
282 // Remove backspace characters, if any
283 FXString strbuf = buf;
284 strbuf = strbuf.substitute("\b", ".");
285 text->appendText(strbuf.text(), strlen(strbuf.text()));
286 scrollToLastLine();
287 if (nread < (int)(sizeof(buf)-1))
288 {
289 break;
290 }
291 }
292 if (killed)
293 {
294 appendText(_("\n>>>> COMMAND CANCELLED <<<<"));
295 }
296 else
297 {
298 appendText(_("\n>>>> END OF COMMAND <<<<"));
299 }
300 scrollToLastLine();
301
302 // Close pipes
303 ::close(pipes[0]);
304 ::close(pipes[1]);
305 }
306
307
308 return(1);
309 }
310
311
312 // Close dialog when clicking on the close button
onCmdClose(FXObject *,FXSelector,void *)313 long CommandWindow::onCmdClose(FXObject*, FXSelector, void*)
314 {
315 getApp()->stopModal(this, true);
316 hide();
317 closed = true;
318
319 // Object deletes itself
320 delete this;
321
322 // Set focus to main window
323 mainWindow->setFocus();
324
325 return(1);
326 }
327
328
329 // Change the text in the buffer to new text
setText(const char * str)330 void CommandWindow::setText(const char* str)
331 {
332 text->setText(str, strlen(str));
333 getApp()->repaint();
334 }
335
336
337 // Append new text at the end of the buffer
appendText(const char * str)338 void CommandWindow::appendText(const char* str)
339 {
340 text->appendText(str, strlen(str));
341 getApp()->repaint();
342 }
343
344
345 // Scroll to the last line
scrollToLastLine(void)346 void CommandWindow::scrollToLastLine(void)
347 {
348 text->makePositionVisible(text->getLength());
349 getApp()->repaint();
350 }
351
352
353 // Get text length
getLength(void)354 int CommandWindow::getLength(void)
355 {
356 return(text->getLength());
357 }
358
359
360 // Clean up
~CommandWindow()361 CommandWindow::~CommandWindow()
362 {
363 getApp()->removeChore(this, ID_WATCHPROCESS);
364
365 text = (FXText*)-1;
366 }
367