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